<!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", "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>
