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