11cb0ef41Sopenharmony_ci<!doctype html>
21cb0ef41Sopenharmony_ci<meta charset="utf8">
31cb0ef41Sopenharmony_ci<meta name="timeout" content="long">
41cb0ef41Sopenharmony_ci<title>Events must dispatch on disabled elements</title>
51cb0ef41Sopenharmony_ci<script src="/resources/testharness.js"></script>
61cb0ef41Sopenharmony_ci<script src="/resources/testharnessreport.js"></script>
71cb0ef41Sopenharmony_ci<script src="/resources/testdriver.js"></script>
81cb0ef41Sopenharmony_ci<script src="/resources/testdriver-vendor.js"></script>
91cb0ef41Sopenharmony_ci<style>
101cb0ef41Sopenharmony_ci  @keyframes fade {
111cb0ef41Sopenharmony_ci    0% {
121cb0ef41Sopenharmony_ci      opacity: 1;
131cb0ef41Sopenharmony_ci    }
141cb0ef41Sopenharmony_ci    100% {
151cb0ef41Sopenharmony_ci      opacity: 0;
161cb0ef41Sopenharmony_ci    }
171cb0ef41Sopenharmony_ci  }
181cb0ef41Sopenharmony_ci</style>
191cb0ef41Sopenharmony_ci<body>
201cb0ef41Sopenharmony_ci<script>
211cb0ef41Sopenharmony_ci// HTML elements that can be disabled
221cb0ef41Sopenharmony_ciconst formElements = ["button", "fieldset", "input", "select", "textarea"];
231cb0ef41Sopenharmony_ci
241cb0ef41Sopenharmony_citest(() => {
251cb0ef41Sopenharmony_ci  for (const localName of formElements) {
261cb0ef41Sopenharmony_ci    const elem = document.createElement(localName);
271cb0ef41Sopenharmony_ci    elem.disabled = true;
281cb0ef41Sopenharmony_ci    // pass becomes true if the event is called and it's the right type.
291cb0ef41Sopenharmony_ci    let pass = false;
301cb0ef41Sopenharmony_ci    const listener = ({ type }) => {
311cb0ef41Sopenharmony_ci      pass = type === "click";
321cb0ef41Sopenharmony_ci    };
331cb0ef41Sopenharmony_ci    elem.addEventListener("click", listener, { once: true });
341cb0ef41Sopenharmony_ci    elem.dispatchEvent(new Event("click"));
351cb0ef41Sopenharmony_ci    assert_true(
361cb0ef41Sopenharmony_ci      pass,
371cb0ef41Sopenharmony_ci      `Untrusted "click" Event didn't dispatch on ${elem.constructor.name}.`
381cb0ef41Sopenharmony_ci    );
391cb0ef41Sopenharmony_ci  }
401cb0ef41Sopenharmony_ci}, "Can dispatch untrusted 'click' Events at disabled HTML elements.");
411cb0ef41Sopenharmony_ci
421cb0ef41Sopenharmony_citest(() => {
431cb0ef41Sopenharmony_ci  for (const localName of formElements) {
441cb0ef41Sopenharmony_ci    const elem = document.createElement(localName);
451cb0ef41Sopenharmony_ci    elem.disabled = true;
461cb0ef41Sopenharmony_ci    // pass becomes true if the event is called and it's the right type.
471cb0ef41Sopenharmony_ci    let pass = false;
481cb0ef41Sopenharmony_ci    const listener = ({ type }) => {
491cb0ef41Sopenharmony_ci      pass = type === "pass";
501cb0ef41Sopenharmony_ci    };
511cb0ef41Sopenharmony_ci    elem.addEventListener("pass", listener, { once: true });
521cb0ef41Sopenharmony_ci    elem.dispatchEvent(new Event("pass"));
531cb0ef41Sopenharmony_ci    assert_true(
541cb0ef41Sopenharmony_ci      pass,
551cb0ef41Sopenharmony_ci      `Untrusted "pass" Event didn't dispatch on ${elem.constructor.name}`
561cb0ef41Sopenharmony_ci    );
571cb0ef41Sopenharmony_ci  }
581cb0ef41Sopenharmony_ci}, "Can dispatch untrusted Events at disabled HTML elements.");
591cb0ef41Sopenharmony_ci
601cb0ef41Sopenharmony_citest(() => {
611cb0ef41Sopenharmony_ci  for (const localName of formElements) {
621cb0ef41Sopenharmony_ci    const elem = document.createElement(localName);
631cb0ef41Sopenharmony_ci    elem.disabled = true;
641cb0ef41Sopenharmony_ci    // pass becomes true if the event is called and it's the right type.
651cb0ef41Sopenharmony_ci    let pass = false;
661cb0ef41Sopenharmony_ci    const listener = ({ type }) => {
671cb0ef41Sopenharmony_ci      pass = type === "custom-pass";
681cb0ef41Sopenharmony_ci    };
691cb0ef41Sopenharmony_ci    elem.addEventListener("custom-pass", listener, { once: true });
701cb0ef41Sopenharmony_ci    elem.dispatchEvent(new CustomEvent("custom-pass"));
711cb0ef41Sopenharmony_ci    assert_true(
721cb0ef41Sopenharmony_ci      pass,
731cb0ef41Sopenharmony_ci      `CustomEvent "custom-pass" didn't dispatch on ${elem.constructor.name}`
741cb0ef41Sopenharmony_ci    );
751cb0ef41Sopenharmony_ci  }
761cb0ef41Sopenharmony_ci}, "Can dispatch CustomEvents at disabled HTML elements.");
771cb0ef41Sopenharmony_ci
781cb0ef41Sopenharmony_citest(() => {
791cb0ef41Sopenharmony_ci  for (const localName of formElements) {
801cb0ef41Sopenharmony_ci    const elem = document.createElement(localName);
811cb0ef41Sopenharmony_ci
821cb0ef41Sopenharmony_ci    // Element is disabled... so this click() MUST NOT fire an event.
831cb0ef41Sopenharmony_ci    elem.disabled = true;
841cb0ef41Sopenharmony_ci    let pass = true;
851cb0ef41Sopenharmony_ci    elem.onclick = e => {
861cb0ef41Sopenharmony_ci      pass = false;
871cb0ef41Sopenharmony_ci    };
881cb0ef41Sopenharmony_ci    elem.click();
891cb0ef41Sopenharmony_ci    assert_true(
901cb0ef41Sopenharmony_ci      pass,
911cb0ef41Sopenharmony_ci      `.click() must not dispatch "click" event on disabled ${
921cb0ef41Sopenharmony_ci        elem.constructor.name
931cb0ef41Sopenharmony_ci      }.`
941cb0ef41Sopenharmony_ci    );
951cb0ef41Sopenharmony_ci
961cb0ef41Sopenharmony_ci    // Element is (re)enabled... so this click() fires an event.
971cb0ef41Sopenharmony_ci    elem.disabled = false;
981cb0ef41Sopenharmony_ci    pass = false;
991cb0ef41Sopenharmony_ci    elem.onclick = e => {
1001cb0ef41Sopenharmony_ci      pass = true;
1011cb0ef41Sopenharmony_ci    };
1021cb0ef41Sopenharmony_ci    elem.click();
1031cb0ef41Sopenharmony_ci    assert_true(
1041cb0ef41Sopenharmony_ci      pass,
1051cb0ef41Sopenharmony_ci      `.click() must dispatch "click" event on enabled ${
1061cb0ef41Sopenharmony_ci        elem.constructor.name
1071cb0ef41Sopenharmony_ci      }.`
1081cb0ef41Sopenharmony_ci    );
1091cb0ef41Sopenharmony_ci  }
1101cb0ef41Sopenharmony_ci}, "Calling click() on disabled elements must not dispatch events.");
1111cb0ef41Sopenharmony_ci
1121cb0ef41Sopenharmony_cipromise_test(async () => {
1131cb0ef41Sopenharmony_ci  // For each form element type, set up transition event handlers.
1141cb0ef41Sopenharmony_ci  for (const localName of formElements) {
1151cb0ef41Sopenharmony_ci    const elem = document.createElement(localName);
1161cb0ef41Sopenharmony_ci    elem.disabled = true;
1171cb0ef41Sopenharmony_ci    document.body.appendChild(elem);
1181cb0ef41Sopenharmony_ci    const eventPromises = [
1191cb0ef41Sopenharmony_ci      "transitionrun",
1201cb0ef41Sopenharmony_ci      "transitionstart",
1211cb0ef41Sopenharmony_ci      "transitionend",
1221cb0ef41Sopenharmony_ci    ].map(eventType => {
1231cb0ef41Sopenharmony_ci      return new Promise(r => {
1241cb0ef41Sopenharmony_ci        elem.addEventListener(eventType, r);
1251cb0ef41Sopenharmony_ci      });
1261cb0ef41Sopenharmony_ci    });
1271cb0ef41Sopenharmony_ci    // Flushing style triggers transition.
1281cb0ef41Sopenharmony_ci    getComputedStyle(elem).opacity;
1291cb0ef41Sopenharmony_ci    elem.style.transition = "opacity .1s";
1301cb0ef41Sopenharmony_ci    elem.style.opacity = 0;
1311cb0ef41Sopenharmony_ci    getComputedStyle(elem).opacity;
1321cb0ef41Sopenharmony_ci    // All the events fire...
1331cb0ef41Sopenharmony_ci    await Promise.all(eventPromises);
1341cb0ef41Sopenharmony_ci    elem.remove();
1351cb0ef41Sopenharmony_ci  }
1361cb0ef41Sopenharmony_ci}, "CSS Transitions transitionrun, transitionstart, transitionend events fire on disabled form elements");
1371cb0ef41Sopenharmony_ci
1381cb0ef41Sopenharmony_cipromise_test(async () => {
1391cb0ef41Sopenharmony_ci  // For each form element type, set up transition event handlers.
1401cb0ef41Sopenharmony_ci  for (const localName of formElements) {
1411cb0ef41Sopenharmony_ci    const elem = document.createElement(localName);
1421cb0ef41Sopenharmony_ci    elem.disabled = true;
1431cb0ef41Sopenharmony_ci    document.body.appendChild(elem);
1441cb0ef41Sopenharmony_ci    getComputedStyle(elem).opacity;
1451cb0ef41Sopenharmony_ci    elem.style.transition = "opacity 100s";
1461cb0ef41Sopenharmony_ci    // We use ontransitionstart to cancel the event.
1471cb0ef41Sopenharmony_ci    elem.ontransitionstart = () => {
1481cb0ef41Sopenharmony_ci      elem.style.display = "none";
1491cb0ef41Sopenharmony_ci    };
1501cb0ef41Sopenharmony_ci    const promiseToCancel = new Promise(r => {
1511cb0ef41Sopenharmony_ci      elem.ontransitioncancel = r;
1521cb0ef41Sopenharmony_ci    });
1531cb0ef41Sopenharmony_ci    // Flushing style triggers the transition.
1541cb0ef41Sopenharmony_ci    elem.style.opacity = 0;
1551cb0ef41Sopenharmony_ci    getComputedStyle(elem).opacity;
1561cb0ef41Sopenharmony_ci    await promiseToCancel;
1571cb0ef41Sopenharmony_ci    // And we are done with this element.
1581cb0ef41Sopenharmony_ci    elem.remove();
1591cb0ef41Sopenharmony_ci  }
1601cb0ef41Sopenharmony_ci}, "CSS Transitions transitioncancel event fires on disabled form elements");
1611cb0ef41Sopenharmony_ci
1621cb0ef41Sopenharmony_cipromise_test(async () => {
1631cb0ef41Sopenharmony_ci  // For each form element type, set up transition event handlers.
1641cb0ef41Sopenharmony_ci  for (const localName of formElements) {
1651cb0ef41Sopenharmony_ci    const elem = document.createElement(localName);
1661cb0ef41Sopenharmony_ci    document.body.appendChild(elem);
1671cb0ef41Sopenharmony_ci    elem.disabled = true;
1681cb0ef41Sopenharmony_ci    const animationStartPromise = new Promise(r => {
1691cb0ef41Sopenharmony_ci      elem.addEventListener("animationstart", () => {
1701cb0ef41Sopenharmony_ci        // Seek to the second iteration to trigger the animationiteration event
1711cb0ef41Sopenharmony_ci        elem.style.animationDelay = "-100s"
1721cb0ef41Sopenharmony_ci        r();
1731cb0ef41Sopenharmony_ci      });
1741cb0ef41Sopenharmony_ci    });
1751cb0ef41Sopenharmony_ci    const animationIterationPromise = new Promise(r => {
1761cb0ef41Sopenharmony_ci      elem.addEventListener("animationiteration", ()=>{
1771cb0ef41Sopenharmony_ci        elem.style.animationDelay = "-200s"
1781cb0ef41Sopenharmony_ci        r();
1791cb0ef41Sopenharmony_ci      });
1801cb0ef41Sopenharmony_ci    });
1811cb0ef41Sopenharmony_ci    const animationEndPromise = new Promise(r => {
1821cb0ef41Sopenharmony_ci      elem.addEventListener("animationend", r);
1831cb0ef41Sopenharmony_ci    });
1841cb0ef41Sopenharmony_ci    elem.style.animation = "fade 100s 2";
1851cb0ef41Sopenharmony_ci    elem.classList.add("animate");
1861cb0ef41Sopenharmony_ci    // All the events fire...
1871cb0ef41Sopenharmony_ci    await Promise.all([
1881cb0ef41Sopenharmony_ci      animationStartPromise,
1891cb0ef41Sopenharmony_ci      animationIterationPromise,
1901cb0ef41Sopenharmony_ci      animationEndPromise,
1911cb0ef41Sopenharmony_ci    ]);
1921cb0ef41Sopenharmony_ci    elem.remove();
1931cb0ef41Sopenharmony_ci  }
1941cb0ef41Sopenharmony_ci}, "CSS Animation animationstart, animationiteration, animationend fire on disabled form elements");
1951cb0ef41Sopenharmony_ci
1961cb0ef41Sopenharmony_cipromise_test(async () => {
1971cb0ef41Sopenharmony_ci  // For each form element type, set up transition event handlers.
1981cb0ef41Sopenharmony_ci  for (const localName of formElements) {
1991cb0ef41Sopenharmony_ci    const elem = document.createElement(localName);
2001cb0ef41Sopenharmony_ci    document.body.appendChild(elem);
2011cb0ef41Sopenharmony_ci    elem.disabled = true;
2021cb0ef41Sopenharmony_ci
2031cb0ef41Sopenharmony_ci    const promiseToCancel = new Promise(r => {
2041cb0ef41Sopenharmony_ci      elem.addEventListener("animationcancel", r);
2051cb0ef41Sopenharmony_ci    });
2061cb0ef41Sopenharmony_ci
2071cb0ef41Sopenharmony_ci    elem.addEventListener("animationstart", () => {
2081cb0ef41Sopenharmony_ci      // Cancel the animation by hiding it.
2091cb0ef41Sopenharmony_ci      elem.style.display = "none";
2101cb0ef41Sopenharmony_ci    });
2111cb0ef41Sopenharmony_ci
2121cb0ef41Sopenharmony_ci    // Trigger the animation
2131cb0ef41Sopenharmony_ci    elem.style.animation = "fade 100s";
2141cb0ef41Sopenharmony_ci    elem.classList.add("animate");
2151cb0ef41Sopenharmony_ci    await promiseToCancel;
2161cb0ef41Sopenharmony_ci    // And we are done with this element.
2171cb0ef41Sopenharmony_ci    elem.remove();
2181cb0ef41Sopenharmony_ci  }
2191cb0ef41Sopenharmony_ci}, "CSS Animation's animationcancel event fires on disabled form elements");
2201cb0ef41Sopenharmony_ci
2211cb0ef41Sopenharmony_cipromise_test(async () => {
2221cb0ef41Sopenharmony_ci  for (const localName of formElements) {
2231cb0ef41Sopenharmony_ci    const elem = document.createElement(localName);
2241cb0ef41Sopenharmony_ci    elem.disabled = true;
2251cb0ef41Sopenharmony_ci    document.body.appendChild(elem);
2261cb0ef41Sopenharmony_ci    // Element is disabled, so clicking must not fire events
2271cb0ef41Sopenharmony_ci    let pass = true;
2281cb0ef41Sopenharmony_ci    elem.onclick = e => {
2291cb0ef41Sopenharmony_ci      pass = false;
2301cb0ef41Sopenharmony_ci    };
2311cb0ef41Sopenharmony_ci    // Disabled elements are not clickable.
2321cb0ef41Sopenharmony_ci    await test_driver.click(elem);
2331cb0ef41Sopenharmony_ci    assert_true(
2341cb0ef41Sopenharmony_ci      pass,
2351cb0ef41Sopenharmony_ci      `${elem.constructor.name} is disabled, so onclick must not fire.`
2361cb0ef41Sopenharmony_ci    );
2371cb0ef41Sopenharmony_ci    // Element is (re)enabled... so this click() will fire an event.
2381cb0ef41Sopenharmony_ci    pass = false;
2391cb0ef41Sopenharmony_ci    elem.disabled = false;
2401cb0ef41Sopenharmony_ci    elem.onclick = () => {
2411cb0ef41Sopenharmony_ci      pass = true;
2421cb0ef41Sopenharmony_ci    };
2431cb0ef41Sopenharmony_ci    await test_driver.click(elem);
2441cb0ef41Sopenharmony_ci    assert_true(
2451cb0ef41Sopenharmony_ci      pass,
2461cb0ef41Sopenharmony_ci      `${elem.constructor.name} is enabled, so onclick must fire.`
2471cb0ef41Sopenharmony_ci    );
2481cb0ef41Sopenharmony_ci    elem.remove();
2491cb0ef41Sopenharmony_ci  }
2501cb0ef41Sopenharmony_ci}, "Real clicks on disabled elements must not dispatch events.");
2511cb0ef41Sopenharmony_ci</script>
252