| <!doctype html> |
| <meta charset="utf8"> |
| <meta name="timeout" content="long"> |
| <title>Events must dispatch on disabled elements</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/resources/testdriver.js"></script> |
| <script src="/resources/testdriver-vendor.js"></script> |
| <style> |
| @keyframes fade { |
| 0% { |
| opacity: 1; |
| } |
| 100% { |
| opacity: 0; |
| } |
| } |
| </style> |
| <body> |
| <script> |
| // HTML elements that can be disabled |
| const formElements = ["button", "fieldset", "input", "select", "textarea"]; |
| |
| test(() => { |
| for (const localName of formElements) { |
| const elem = document.createElement(localName); |
| elem.disabled = true; |
| // pass becomes true if the event is called and it's the right type. |
| let pass = false; |
| const listener = ({ type }) => { |
| pass = type === "click"; |
| }; |
| elem.addEventListener("click", listener, { once: true }); |
| elem.dispatchEvent(new Event("click")); |
| assert_true( |
| pass, |
| `Untrusted "click" Event didn't dispatch on ${elem.constructor.name}.` |
| ); |
| } |
| }, "Can dispatch untrusted 'click' Events at disabled HTML elements."); |
| |
| test(() => { |
| for (const localName of formElements) { |
| const elem = document.createElement(localName); |
| elem.disabled = true; |
| // pass becomes true if the event is called and it's the right type. |
| let pass = false; |
| const listener = ({ type }) => { |
| pass = type === "pass"; |
| }; |
| elem.addEventListener("pass", listener, { once: true }); |
| elem.dispatchEvent(new Event("pass")); |
| assert_true( |
| pass, |
| `Untrusted "pass" Event didn't dispatch on ${elem.constructor.name}` |
| ); |
| } |
| }, "Can dispatch untrusted Events at disabled HTML elements."); |
| |
| test(() => { |
| for (const localName of formElements) { |
| const elem = document.createElement(localName); |
| elem.disabled = true; |
| // pass becomes true if the event is called and it's the right type. |
| let pass = false; |
| const listener = ({ type }) => { |
| pass = type === "custom-pass"; |
| }; |
| elem.addEventListener("custom-pass", listener, { once: true }); |
| elem.dispatchEvent(new CustomEvent("custom-pass")); |
| assert_true( |
| pass, |
| `CustomEvent "custom-pass" didn't dispatch on ${elem.constructor.name}` |
| ); |
| } |
| }, "Can dispatch CustomEvents at disabled HTML elements."); |
| |
| test(() => { |
| for (const localName of formElements) { |
| const elem = document.createElement(localName); |
| |
| // Element is disabled... so this click() MUST NOT fire an event. |
| elem.disabled = true; |
| let pass = true; |
| elem.onclick = e => { |
| pass = false; |
| }; |
| elem.click(); |
| assert_true( |
| pass, |
| `.click() must not dispatch "click" event on disabled ${ |
| elem.constructor.name |
| }.` |
| ); |
| |
| // Element is (re)enabled... so this click() fires an event. |
| elem.disabled = false; |
| pass = false; |
| elem.onclick = e => { |
| pass = true; |
| }; |
| elem.click(); |
| assert_true( |
| pass, |
| `.click() must dispatch "click" event on enabled ${ |
| elem.constructor.name |
| }.` |
| ); |
| } |
| }, "Calling click() on disabled elements must not dispatch events."); |
| |
| promise_test(async () => { |
| // For each form element type, set up transition event handlers. |
| for (const localName of formElements) { |
| const elem = document.createElement(localName); |
| elem.disabled = true; |
| document.body.appendChild(elem); |
| const eventPromises = [ |
| "transitionrun", |
| "transitionstart", |
| "transitionend", |
| ].map(eventType => { |
| return new Promise(r => { |
| elem.addEventListener(eventType, r); |
| }); |
| }); |
| // Flushing style triggers transition. |
| getComputedStyle(elem).opacity; |
| elem.style.transition = "opacity .1s"; |
| elem.style.opacity = 0; |
| getComputedStyle(elem).opacity; |
| // All the events fire... |
| await Promise.all(eventPromises); |
| elem.remove(); |
| } |
| }, "CSS Transitions transitionrun, transitionstart, transitionend events fire on disabled form elements"); |
| |
| promise_test(async () => { |
| // For each form element type, set up transition event handlers. |
| for (const localName of formElements) { |
| const elem = document.createElement(localName); |
| elem.disabled = true; |
| document.body.appendChild(elem); |
| getComputedStyle(elem).opacity; |
| elem.style.transition = "opacity 100s"; |
| // We use ontransitionstart to cancel the event. |
| elem.ontransitionstart = () => { |
| elem.style.display = "none"; |
| }; |
| const promiseToCancel = new Promise(r => { |
| elem.ontransitioncancel = r; |
| }); |
| // Flushing style triggers the transition. |
| elem.style.opacity = 0; |
| getComputedStyle(elem).opacity; |
| await promiseToCancel; |
| // And we are done with this element. |
| elem.remove(); |
| } |
| }, "CSS Transitions transitioncancel event fires on disabled form elements"); |
| |
| promise_test(async () => { |
| // For each form element type, set up transition event handlers. |
| for (const localName of formElements) { |
| const elem = document.createElement(localName); |
| document.body.appendChild(elem); |
| elem.disabled = true; |
| const animationStartPromise = new Promise(r => { |
| elem.addEventListener("animationstart", () => { |
| // Seek to the second iteration to trigger the animationiteration event |
| elem.style.animationDelay = "-100s" |
| r(); |
| }); |
| }); |
| const animationIterationPromise = new Promise(r => { |
| elem.addEventListener("animationiteration", ()=>{ |
| elem.style.animationDelay = "-200s" |
| r(); |
| }); |
| }); |
| const animationEndPromise = new Promise(r => { |
| elem.addEventListener("animationend", r); |
| }); |
| elem.style.animation = "fade 100s 2"; |
| elem.classList.add("animate"); |
| // All the events fire... |
| await Promise.all([ |
| animationStartPromise, |
| animationIterationPromise, |
| animationEndPromise, |
| ]); |
| elem.remove(); |
| } |
| }, "CSS Animation animationstart, animationiteration, animationend fire on disabled form elements"); |
| |
| promise_test(async () => { |
| // For each form element type, set up transition event handlers. |
| for (const localName of formElements) { |
| const elem = document.createElement(localName); |
| document.body.appendChild(elem); |
| elem.disabled = true; |
| |
| const promiseToCancel = new Promise(r => { |
| elem.addEventListener("animationcancel", r); |
| }); |
| |
| elem.addEventListener("animationstart", () => { |
| // Cancel the animation by hiding it. |
| elem.style.display = "none"; |
| }); |
| |
| // Trigger the animation |
| elem.style.animation = "fade 100s"; |
| elem.classList.add("animate"); |
| await promiseToCancel; |
| // And we are done with this element. |
| elem.remove(); |
| } |
| }, "CSS Animation's animationcancel event fires on disabled form elements"); |
| |
| promise_test(async () => { |
| for (const localName of formElements) { |
| const elem = document.createElement(localName); |
| elem.disabled = true; |
| document.body.appendChild(elem); |
| // Element is disabled, so clicking must not fire events |
| let pass = true; |
| elem.onclick = e => { |
| pass = false; |
| }; |
| // Disabled elements are not clickable. |
| await test_driver.click(elem); |
| assert_true( |
| pass, |
| `${elem.constructor.name} is disabled, so onclick must not fire.` |
| ); |
| // Element is (re)enabled... so this click() will fire an event. |
| pass = false; |
| elem.disabled = false; |
| elem.onclick = () => { |
| pass = true; |
| }; |
| await test_driver.click(elem); |
| assert_true( |
| pass, |
| `${elem.constructor.name} is enabled, so onclick must fire.` |
| ); |
| elem.remove(); |
| } |
| }, "Real clicks on disabled elements must not dispatch events."); |
| </script> |