Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- This WPT test may be referenced by the following Test IDs:
- /dom/events/Event-dispatch-on-disabled-elements.html - WPT Dashboard Interop Dashboard
<!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>