diff options
Diffstat (limited to 'testing/web-platform/tests/html/semantics/disabled-elements')
7 files changed, 655 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/disabled-checkbox-click.html b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-checkbox-click.html new file mode 100644 index 0000000000..805770c854 --- /dev/null +++ b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-checkbox-click.html @@ -0,0 +1,26 @@ +<!doctype html> +<link rel="author" title="Aditya Keerthi" href="https://github.com/pxlcoder"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls:-the-disabled-attribute"> +<title>Test disabled checkbox does not change state when clicked</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<input type="checkbox" disabled> +<script> +const input = document.querySelector("input"); + +promise_test(async function() { + assert_false(input.checked); + + await new test_driver.Actions() + .pointerMove(0, 0, { origin: input }) + .pointerDown() + .pointerUp() + .send(); + + assert_false(input.checked); +}, `Disabled checkbox does not change state when clicked`); +</script> diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch-additional.tentative.html b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch-additional.tentative.html new file mode 100644 index 0000000000..8ef4e5ccc7 --- /dev/null +++ b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch-additional.tentative.html @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<meta name="timeout" content="long"> +<link rel=author href="mailto:jarhar@chromium.org"> +<link rel=help href="https://github.com/whatwg/html/issues/2368"> +<link rel=help href="https://github.com/whatwg/html/issues/5886"> +<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> + +<!-- This test should be merged with disabled-event-dispatch.tentative.html after interop2023 is over. --> + +<div id=targetparent> + <button disabled> + hello world + <span style="border: 1px solid black">child</span> + </button> + <my-control disabled> + hello world + <span style="border: 1px solid black">child</span> + </my-control> +</div> + +<script> +customElements.define('my-control', class extends HTMLElement { + static get formAssociated() { return true; } +}); + +['dblclick', 'auxclick'].forEach(eventName => { + [true, false].forEach(clickChildElement => { + for (const target of targetparent.children) { + promise_test(async () => { + let parentReceivedEvent = false; + targetparent.addEventListener(eventName, () => parentReceivedEvent = true); + + let targetReceivedEvent = false; + target.addEventListener(eventName, () => targetReceivedEvent = true); + + let childReceivedEvent = false; + let targetchild = target.firstElementChild; + targetchild.addEventListener(eventName, () => childReceivedEvent = true); + + const elementToClick = clickChildElement ? targetchild : target; + if (eventName === 'dblclick') { + await (new test_driver.Actions() + .pointerMove(1, 1, {origin: elementToClick}) + .pointerDown() + .pointerUp() + .pointerDown() + .pointerUp()) + .send(); + } else if (eventName === 'auxclick') { + const actions = new test_driver.Actions(); + await actions + .pointerMove(1, 1, {origin: elementToClick}) + .pointerDown({button: actions.ButtonType.MIDDLE}) + .pointerUp({button: actions.ButtonType.MIDDLE}) + .send(); + } + + + const shouldReceiveEvents = eventName.startsWith('pointer') || eventName === 'auxclick'; + assert_equals(parentReceivedEvent, shouldReceiveEvents, + `parent element received ${eventName} events`); + assert_equals(targetReceivedEvent, shouldReceiveEvents, + `target element received ${eventName} events`); + assert_equals(childReceivedEvent, clickChildElement, + `child element received ${eventName} events`); + }, `Testing ${eventName} events when clicking ${clickChildElement ? 'child of ' : ''}disabled ${target.localName}.`); + } + }); +}); +</script> diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch.tentative.html b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch.tentative.html new file mode 100644 index 0000000000..e2b8846fc3 --- /dev/null +++ b/testing/web-platform/tests/html/semantics/disabled-elements/disabled-event-dispatch.tentative.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<meta name="timeout" content="long"> +<link rel=author href="mailto:jarhar@chromium.org"> +<link rel=help href="https://github.com/whatwg/html/issues/2368"> +<link rel=help href="https://github.com/whatwg/html/issues/5886"> +<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> + +<div id=targetparent> + <button disabled> + hello world + <span style="border: 1px solid black">child</span> + </button> + <my-control disabled> + hello world + <span style="border: 1px solid black">child</span> + </my-control> +</div> + +<script> +customElements.define('my-control', class extends HTMLElement { + static get formAssociated() { return true; } +}); + +['mousedown', 'mouseup', 'pointerdown', 'pointerup', 'click'].forEach(eventName => { + [true, false].forEach(clickChildElement => { + for (const target of targetparent.children) { + promise_test(async () => { + let parentReceivedEvent = false; + targetparent.addEventListener(eventName, () => parentReceivedEvent = true); + + let targetReceivedEvent = false; + target.addEventListener(eventName, () => targetReceivedEvent = true); + + let childReceivedEvent = false; + let targetchild = target.firstElementChild; + targetchild.addEventListener(eventName, () => childReceivedEvent = true); + + await test_driver.click(clickChildElement ? targetchild : target); + + const parentShouldReceiveEvents = eventName.startsWith('pointer'); + assert_equals(parentReceivedEvent, parentShouldReceiveEvents, + `parent element received ${eventName} events`); + + const targetShouldReceiveEvents = eventName.startsWith('pointer'); + assert_equals(targetReceivedEvent, targetShouldReceiveEvents, + `target element received ${eventName} events`); + assert_equals(childReceivedEvent, clickChildElement, + `child element received ${eventName} events`); + }, `Testing ${eventName} events when clicking ${clickChildElement ? 'child of ' : ''}disabled ${target.localName}.`); + } + }); +}); +</script> diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/disabledElement.html b/testing/web-platform/tests/html/semantics/disabled-elements/disabledElement.html new file mode 100644 index 0000000000..03f57424d2 --- /dev/null +++ b/testing/web-platform/tests/html/semantics/disabled-elements/disabledElement.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Disabled elements</title> +<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org"> +<link rel=help href="https://html.spec.whatwg.org/multipage/#disabled-elements"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<button disabled>button</button> +<input disabled> +<select disabled> + <optgroup label="options" disabled> + <option value="option1" disabled>option1 + <option value="option2">option2 +</select> +<textarea disabled>textarea</textarea> +<fieldset disabled> + <input type=radio name=c value=0 checked> + <input type=radio name=c value=1> +</fieldset> +<a href="http://www.w3.org/" disabled>w3</a> +<span tabindex=0 disabled>foobar</span> + +<script> + test(function(){ + assert_equals(document.activeElement, document.body); + }, "The body element must be the active element if no element is focused"); + + ["button", "input", "select", "optgroup", "option", "textarea", "input[type=radio]"].forEach(function(el) { + test(function() { + var element = document.querySelector(el); + element.focus(); + assert_equals(document.activeElement, document.body, "activeElement after focus on a disabled <" + el + "> remains unchanged"); + }, "A disabled <" + el + "> should not be focusable"); + }); + + ["a", "span"].forEach(function(el) { + test(function() { + var element = document.querySelector(el); + element.focus(); + assert_equals(document.activeElement, element, "focus on a <" + el + "> with a disabled attribute should make it the activeElement"); + }, "A disabled <" + el + "> should be focusable"); + }); +</script> diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled-keyboard.tentative.html b/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled-keyboard.tentative.html new file mode 100644 index 0000000000..3aacf21f1d --- /dev/null +++ b/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled-keyboard.tentative.html @@ -0,0 +1,135 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>KeyboardEvent propagation on disabled form elements</title> +<link rel="author" href="mailto:avandolder@mozilla.com"> +<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> diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled.tentative.html b/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled.tentative.html new file mode 100644 index 0000000000..e3dcd43a17 --- /dev/null +++ b/testing/web-platform/tests/html/semantics/disabled-elements/event-propagate-disabled.tentative.html @@ -0,0 +1,264 @@ +<!DOCTYPE html> +<meta charset="utf8"> +<meta name="timeout" content="long"> +<title>Event propagation on disabled form elements</title> +<link rel="author" href="mailto:krosylight@mozilla.com"> +<link rel="help" href="https://github.com/whatwg/html/issues/2368"> +<link rel="help" href="https://github.com/whatwg/html/issues/5886"> +<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> + +<div id="cases"> + <input> <!-- Sanity check with non-disabled control --> + <select disabled></select> + <select disabled> + <!-- <option> can't be clicked as it doesn't have its own painting area --> + <option>foo</option> + </select> + <fieldset disabled>Text</fieldset> + <fieldset disabled><span class="target">Span</span></fieldset> + <button disabled>Text</button> + <button disabled><span class="target">Span</span></button> + <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"> + <!-- Native click will click the bar --> + <input disabled type="range" value="0"> + <!-- Native click will click the slider --> + <input disabled type="range" value="50"> + <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"> + <my-control disabled>Text</my-control> +</div> + +<script> + customElements.define('my-control', class extends HTMLElement { + static get formAssociated() { return true; } + get disabled() { return this.hasAttribute("disabled"); } + }); + + /** + * @param {Element} element + */ + function getEventFiringTarget(element) { + return element.querySelector(".target") || element; + } + + const allEvents = ["pointermove", "mousemove", "pointerdown", "mousedown", "pointerup", "mouseup", "click"]; + + /** + * @param {*} t + * @param {Element} element + * @param {Element} observingElement + */ + function setupTest(t, element, observingElement) { + /** @type {{type: string, composedPath: Node[]}[]} */ + const observedEvents = []; + const controller = new AbortController(); + const { signal } = controller; + const listenerFn = t.step_func(event => { + observedEvents.push({ + type: event.type, + target: event.target, + isTrusted: event.isTrusted, + composedPath: event.composedPath().map(n => n.constructor.name), + }); + }); + for (const event of allEvents) { + observingElement.addEventListener(event, listenerFn, { signal }); + } + t.add_cleanup(() => controller.abort()); + + const target = getEventFiringTarget(element); + return { target, observedEvents }; + } + + /** + * @param {Element} element + * @returns {boolean} + */ + function isFormControl(element) { + if (["button", "input", "select", "textarea"].includes(element.localName)) { + return true; + } + return element.constructor.formAssociated; + } + + function isDisabledFormControl(element) { + return isFormControl(element) && element.disabled; + } + + /** + * @param {Element} target + * @param {*} observedEvent + */ + function shouldNotBubble(target, observedEvent) { + return ( + isDisabledFormControl(target) && + observedEvent.isTrusted && + ["mousedown", "mouseup", "click"].includes(observedEvent.type) + ); + } + + /** + * @param {Event} event + */ + function getExpectedComposedPath(event) { + let target = event.target; + const result = []; + while (target) { + if (shouldNotBubble(target, event)) { + return result; + } + result.push(target.constructor.name); + target = target.parentNode; + } + result.push("Window"); + return result; + } + + /** + * @param {object} options + * @param {Element & { disabled: boolean }} options.element + * @param {Element} options.observingElement + * @param {string[]} options.expectedEvents + * @param {(target: Element) => (Promise<void> | void)} options.clickerFn + * @param {string} options.title + */ + function promise_event_test({ element, observingElement, expectedEvents, nonDisabledExpectedEvents, clickerFn, title }) { + promise_test(async t => { + const { target, observedEvents } = setupTest(t, element, observingElement); + + await t.step_func(clickerFn)(target); + await new Promise(resolve => t.step_timeout(resolve, 0)); + + const expected = isDisabledFormControl(element) ? expectedEvents : nonDisabledExpectedEvents; + assert_array_equals(observedEvents.map(e => e.type), expected, "Observed events"); + + for (const observed of observedEvents) { + assert_equals(observed.target, target, `${observed.type}.target`) + assert_array_equals( + observed.composedPath, + getExpectedComposedPath(observed), + `${observed.type}.composedPath` + ); + } + + }, `${title} on ${element.outerHTML}, observed from <${observingElement.localName}>`); + } + + /** + * @param {object} options + * @param {Element & { disabled: boolean }} options.element + * @param {string[]} options.expectedEvents + * @param {(target: Element) => (Promise<void> | void)} options.clickerFn + * @param {string} options.title + */ + function promise_event_test_hierarchy({ element, expectedEvents, nonDisabledExpectedEvents, clickerFn, title }) { + const targets = [element, document.body]; + if (element.querySelector(".target")) { + targets.unshift(element.querySelector(".target")); + } + for (const observingElement of targets) { + promise_event_test({ element, observingElement, expectedEvents, nonDisabledExpectedEvents, clickerFn, title }); + } + } + + function trusted_click(target) { + // To workaround type=file clicking issue + // https://github.com/w3c/webdriver/issues/1666 + return new test_driver.Actions() + .pointerMove(0, 0, { origin: target }) + .pointerDown() + .pointerUp() + .send(); + } + + const mouseEvents = ["mousemove", "mousedown", "mouseup", "click"]; + const pointerEvents = ["pointermove", "pointerdown", "pointerup"]; + + // Events except mousedown/up/click + const allowedEvents = ["pointermove", "mousemove", "pointerdown", "pointerup"]; + + const elements = document.getElementById("cases").children; + for (const element of elements) { + // Observe on a child element of the control, if exists + const target = element.querySelector(".target"); + if (target) { + promise_event_test({ + element, + observingElement: target, + expectedEvents: allEvents, + nonDisabledExpectedEvents: allEvents, + clickerFn: trusted_click, + title: "Trusted click" + }); + } + + // Observe on the control itself + promise_event_test({ + element, + observingElement: element, + expectedEvents: allowedEvents, + nonDisabledExpectedEvents: allEvents, + clickerFn: trusted_click, + title: "Trusted click" + }); + + // Observe on document.body + promise_event_test({ + element, + observingElement: document.body, + expectedEvents: allowedEvents, + nonDisabledExpectedEvents: allEvents, + clickerFn: trusted_click, + title: "Trusted click" + }); + + const eventFirePair = [ + [MouseEvent, mouseEvents], + [PointerEvent, pointerEvents] + ]; + + for (const [eventInterface, events] of eventFirePair) { + promise_event_test_hierarchy({ + element, + expectedEvents: events, + nonDisabledExpectedEvents: events, + clickerFn: target => { + for (const event of events) { + target.dispatchEvent(new eventInterface(event, { bubbles: true })) + } + }, + title: `Dispatch new ${eventInterface.name}()` + }) + } + + promise_event_test_hierarchy({ + element, + expectedEvents: getEventFiringTarget(element) === element ? [] : ["click"], + nonDisabledExpectedEvents: ["click"], + clickerFn: target => target.click(), + title: `click()` + }) + } +</script> diff --git a/testing/web-platform/tests/html/semantics/disabled-elements/fieldset-event-propagation.tentative.html b/testing/web-platform/tests/html/semantics/disabled-elements/fieldset-event-propagation.tentative.html new file mode 100644 index 0000000000..6d1a39c1de --- /dev/null +++ b/testing/web-platform/tests/html/semantics/disabled-elements/fieldset-event-propagation.tentative.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<link rel=author href="mailto:jarhar@chromium.org"> +<link rel=help href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled"> +<link rel=help href="https://github.com/whatwg/html/issues/5886#issuecomment-1460425364"> +<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> + +<div id=target1parent> + <fieldset disabled id=target1fieldset> + <div id=target1child>hello world</div> + </fieldset> +</div> + +<div id=target2parent> + <fieldset disabled id=target2fieldset>hello world</fieldset> +</div> + +<script> + const clickers = { + "native click": target => test_driver.click(target), + "click()": target => target.click(), + }; + + for (const [clickerName, clicker] of Object.entries(clickers)) { + promise_test(async () => { + let target1parentClicked = false; + let target1childClicked = false; + let target1fieldsetClicked = false; + target1parent.onclick = () => target1parentClicked = true; + target1child.onclick = () => target1childClicked = true; + target1fieldset.onclick = () => target1fieldsetClicked = true; + + await clicker(target1child); + + assert_true(target1parentClicked, 'The parent of the fieldset should receive a click event.'); + assert_true(target1childClicked, 'The child of the fieldset should receive a click event.'); + assert_true(target1fieldsetClicked, 'The fieldset element should receive a click event.'); + }, `Disabled fieldset elements should not prevent click event propagation from ${clickerName}`); + + promise_test(async () => { + let target2parentClicked = false; + let target2fieldsetClicked = false; + target2parent.onclick = () => target2parentClicked = true; + target2fieldset.onclick = () => target2fieldsetClicked = true; + + await clicker(target2fieldset); + + assert_true(target2parentClicked, 'The parent of the fieldset should receive a click event.'); + assert_true(target2fieldsetClicked, 'The fieldset element should receive a click event.'); + }, `Disabled fieldset elements should not block click events from ${clickerName}.`); + } +</script> |