1<!doctype html>
2<meta charset="utf8">
3<meta name="timeout" content="long">
4<title>Events must dispatch on disabled elements</title>
5<script src="/resources/testharness.js"></script>
6<script src="/resources/testharnessreport.js"></script>
7<script src="/resources/testdriver.js"></script>
8<script src="/resources/testdriver-vendor.js"></script>
9<style>
10  @keyframes fade {
11    0% {
12      opacity: 1;
13    }
14    100% {
15      opacity: 0;
16    }
17  }
18</style>
19<body>
20<script>
21// HTML elements that can be disabled
22const formElements = ["button", "fieldset", "input", "select", "textarea"];
23
24test(() => {
25  for (const localName of formElements) {
26    const elem = document.createElement(localName);
27    elem.disabled = true;
28    // pass becomes true if the event is called and it's the right type.
29    let pass = false;
30    const listener = ({ type }) => {
31      pass = type === "click";
32    };
33    elem.addEventListener("click", listener, { once: true });
34    elem.dispatchEvent(new Event("click"));
35    assert_true(
36      pass,
37      `Untrusted "click" Event didn't dispatch on ${elem.constructor.name}.`
38    );
39  }
40}, "Can dispatch untrusted 'click' Events at disabled HTML elements.");
41
42test(() => {
43  for (const localName of formElements) {
44    const elem = document.createElement(localName);
45    elem.disabled = true;
46    // pass becomes true if the event is called and it's the right type.
47    let pass = false;
48    const listener = ({ type }) => {
49      pass = type === "pass";
50    };
51    elem.addEventListener("pass", listener, { once: true });
52    elem.dispatchEvent(new Event("pass"));
53    assert_true(
54      pass,
55      `Untrusted "pass" Event didn't dispatch on ${elem.constructor.name}`
56    );
57  }
58}, "Can dispatch untrusted Events at disabled HTML elements.");
59
60test(() => {
61  for (const localName of formElements) {
62    const elem = document.createElement(localName);
63    elem.disabled = true;
64    // pass becomes true if the event is called and it's the right type.
65    let pass = false;
66    const listener = ({ type }) => {
67      pass = type === "custom-pass";
68    };
69    elem.addEventListener("custom-pass", listener, { once: true });
70    elem.dispatchEvent(new CustomEvent("custom-pass"));
71    assert_true(
72      pass,
73      `CustomEvent "custom-pass" didn't dispatch on ${elem.constructor.name}`
74    );
75  }
76}, "Can dispatch CustomEvents at disabled HTML elements.");
77
78test(() => {
79  for (const localName of formElements) {
80    const elem = document.createElement(localName);
81
82    // Element is disabled... so this click() MUST NOT fire an event.
83    elem.disabled = true;
84    let pass = true;
85    elem.onclick = e => {
86      pass = false;
87    };
88    elem.click();
89    assert_true(
90      pass,
91      `.click() must not dispatch "click" event on disabled ${
92        elem.constructor.name
93      }.`
94    );
95
96    // Element is (re)enabled... so this click() fires an event.
97    elem.disabled = false;
98    pass = false;
99    elem.onclick = e => {
100      pass = true;
101    };
102    elem.click();
103    assert_true(
104      pass,
105      `.click() must dispatch "click" event on enabled ${
106        elem.constructor.name
107      }.`
108    );
109  }
110}, "Calling click() on disabled elements must not dispatch events.");
111
112promise_test(async () => {
113  // For each form element type, set up transition event handlers.
114  for (const localName of formElements) {
115    const elem = document.createElement(localName);
116    elem.disabled = true;
117    document.body.appendChild(elem);
118    const eventPromises = [
119      "transitionrun",
120      "transitionstart",
121      "transitionend",
122    ].map(eventType => {
123      return new Promise(r => {
124        elem.addEventListener(eventType, r);
125      });
126    });
127    // Flushing style triggers transition.
128    getComputedStyle(elem).opacity;
129    elem.style.transition = "opacity .1s";
130    elem.style.opacity = 0;
131    getComputedStyle(elem).opacity;
132    // All the events fire...
133    await Promise.all(eventPromises);
134    elem.remove();
135  }
136}, "CSS Transitions transitionrun, transitionstart, transitionend events fire on disabled form elements");
137
138promise_test(async () => {
139  // For each form element type, set up transition event handlers.
140  for (const localName of formElements) {
141    const elem = document.createElement(localName);
142    elem.disabled = true;
143    document.body.appendChild(elem);
144    getComputedStyle(elem).opacity;
145    elem.style.transition = "opacity 100s";
146    // We use ontransitionstart to cancel the event.
147    elem.ontransitionstart = () => {
148      elem.style.display = "none";
149    };
150    const promiseToCancel = new Promise(r => {
151      elem.ontransitioncancel = r;
152    });
153    // Flushing style triggers the transition.
154    elem.style.opacity = 0;
155    getComputedStyle(elem).opacity;
156    await promiseToCancel;
157    // And we are done with this element.
158    elem.remove();
159  }
160}, "CSS Transitions transitioncancel event fires on disabled form elements");
161
162promise_test(async () => {
163  // For each form element type, set up transition event handlers.
164  for (const localName of formElements) {
165    const elem = document.createElement(localName);
166    document.body.appendChild(elem);
167    elem.disabled = true;
168    const animationStartPromise = new Promise(r => {
169      elem.addEventListener("animationstart", () => {
170        // Seek to the second iteration to trigger the animationiteration event
171        elem.style.animationDelay = "-100s"
172        r();
173      });
174    });
175    const animationIterationPromise = new Promise(r => {
176      elem.addEventListener("animationiteration", ()=>{
177        elem.style.animationDelay = "-200s"
178        r();
179      });
180    });
181    const animationEndPromise = new Promise(r => {
182      elem.addEventListener("animationend", r);
183    });
184    elem.style.animation = "fade 100s 2";
185    elem.classList.add("animate");
186    // All the events fire...
187    await Promise.all([
188      animationStartPromise,
189      animationIterationPromise,
190      animationEndPromise,
191    ]);
192    elem.remove();
193  }
194}, "CSS Animation animationstart, animationiteration, animationend fire on disabled form elements");
195
196promise_test(async () => {
197  // For each form element type, set up transition event handlers.
198  for (const localName of formElements) {
199    const elem = document.createElement(localName);
200    document.body.appendChild(elem);
201    elem.disabled = true;
202
203    const promiseToCancel = new Promise(r => {
204      elem.addEventListener("animationcancel", r);
205    });
206
207    elem.addEventListener("animationstart", () => {
208      // Cancel the animation by hiding it.
209      elem.style.display = "none";
210    });
211
212    // Trigger the animation
213    elem.style.animation = "fade 100s";
214    elem.classList.add("animate");
215    await promiseToCancel;
216    // And we are done with this element.
217    elem.remove();
218  }
219}, "CSS Animation's animationcancel event fires on disabled form elements");
220
221promise_test(async () => {
222  for (const localName of formElements) {
223    const elem = document.createElement(localName);
224    elem.disabled = true;
225    document.body.appendChild(elem);
226    // Element is disabled, so clicking must not fire events
227    let pass = true;
228    elem.onclick = e => {
229      pass = false;
230    };
231    // Disabled elements are not clickable.
232    await test_driver.click(elem);
233    assert_true(
234      pass,
235      `${elem.constructor.name} is disabled, so onclick must not fire.`
236    );
237    // Element is (re)enabled... so this click() will fire an event.
238    pass = false;
239    elem.disabled = false;
240    elem.onclick = () => {
241      pass = true;
242    };
243    await test_driver.click(elem);
244    assert_true(
245      pass,
246      `${elem.constructor.name} is enabled, so onclick must fire.`
247    );
248    elem.remove();
249  }
250}, "Real clicks on disabled elements must not dispatch events.");
251</script>
252