| <!DOCTYPE html> |
| <meta charset="utf-8"> |
| <title>KeyboardEvent propagation on disabled form elements</title> |
| <link rel="author" href="mailto:[email protected]"> |
| <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> |
| <script src="/resources/testdriver-actions.js"></script> |
| <script> |
| class CustomControl extends HTMLElement { |
| static get formAssociated() {return true;} |
| |
| constructor() { |
| super(); |
| this.internals = this.attachInternals(); |
| |
| this.attachShadow({mode: "open", delegatesFocus: true}); |
| this.shadowRoot.append( |
| document.querySelector("template").content.cloneNode(true) |
| ); |
| } |
| |
| get target() { |
| return this.shadowRoot.getElementById("target"); |
| } |
| } |
| |
| customElements.define("custom-control", CustomControl) |
| </script> |
| |
| <template> |
| <div tabindex="0" id="target"> |
| <slot></slot> |
| </div> |
| </template> |
| |
| <div tabindex="0" id="reset"></div> |
| |
| <form id="form"> |
| <input> <!-- Sanity check with non-disabled control --> |
| <select disabled></select> |
| <textarea disabled></textarea> |
| <input disabled type="button"> |
| <input disabled type="checkbox"> |
| <input disabled type="color" value="#000000"> |
| <input disabled type="date"> |
| <input disabled type="datetime-local"> |
| <input disabled type="email"> |
| <input disabled type="file"> |
| <input disabled type="image"> |
| <input disabled type="month"> |
| <input disabled type="number"> |
| <input disabled type="password"> |
| <input disabled type="radio"> |
| <input disabled type="range" value="0"> |
| <input disabled type="reset"> |
| <input disabled type="search"> |
| <input disabled type="submit"> |
| <input disabled type="tel"> |
| <input disabled type="text"> |
| <input disabled type="time"> |
| <input disabled type="url"> |
| <input disabled type="week"> |
| |
| <fieldset disabled><span tabindex="0">Span</span></fieldset> |
| <button disabled><span tabindex="0">Span</span></button> |
| <custom-control disabled>Text</custom-control> |
| </form> |
| |
| <script> |
| const keyEvents = ["keydown", "keyup"]; |
| |
| function setupTest(t, element, observingElement) { |
| const observedEvents = []; |
| const controller = new AbortController(); |
| const {signal} = controller; |
| const listenerFn = t.step_func(event => { |
| observedEvents.push(event.type); |
| }); |
| for (const event of keyEvents) { |
| observingElement.addEventListener(event, listenerFn, {signal}); |
| } |
| t.add_cleanup(() => controller.abort()); |
| |
| const target = element; |
| return {target, observedEvents}; |
| } |
| |
| function fire_trusted_key_events(target) { |
| const actions = new test_driver.Actions(); |
| return actions.keyDown("a").keyUp("a").send(); |
| } |
| |
| function fire_untrusted_key_events(target) { |
| target.dispatchEvent(new KeyboardEvent("keydown", {bubbles: true})); |
| target.dispatchEvent(new KeyboardEvent("keyup", {bubbles: true})); |
| } |
| |
| const observingElement = document.getElementById("form"); |
| const reset = document.getElementById("reset"); |
| |
| for (const element of observingElement.children) { |
| promise_test(async t => { |
| const {observedEvents} = setupTest(t, element, observingElement); |
| |
| const target = element.firstElementChild ?? element.target ?? element; |
| await t.step_func(fire_untrusted_key_events)(target); |
| await new Promise(resolve => t.step_timeout(resolve, 0)); |
| |
| assert_array_equals(observedEvents, keyEvents, "Observed events"); |
| }, `Untrusted key events on ${element.outerHTML}, observed from <${observingElement.localName}>`); |
| |
| // Only test elements with children for trusted key events. |
| if (!element.firstElementChild && !element.target) { |
| continue; |
| } |
| |
| promise_test(async t => { |
| const {observedEvents} = setupTest(t, element, observingElement); |
| |
| const target = element.firstElementChild ?? element.target; |
| |
| reset.focus(); |
| assert_not_equals(document.activeElement, target, "Reset current focus"); |
| target.focus(); |
| assert_equals(document.activeElement, element.firstElementChild ?? element, "Focus the target element"); |
| |
| await t.step_func(fire_trusted_key_events)(target); |
| await new Promise(resolve => t.step_timeout(resolve, 0)); |
| |
| assert_array_equals(observedEvents, keyEvents, "Observed events"); |
| }, `Trusted key events on ${element.outerHTML}, observed from <${observingElement.localName}>`); |
| } |
| </script> |