-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug 1799565 - Allow pointer events on disabled form elements on Night…
…ly r=smaug Corresponds to the latest consensus and also matches what Chrome shipped behind `--enable-blink-features=SendMouseEventsDisabledFormControls`. Imported the portion of tests that is directly impacted here from web-platform-tests/wpt#32381. Others are not directly impacted and thus I'd like to land them separately since there are still some mismatching behavior around `button` element. Differential Revision: https://phabricator.services.mozilla.com/D161537
- Loading branch information
Showing
6 changed files
with
262 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 0 additions & 12 deletions
12
...platform/meta/html/semantics/disabled-elements/disabled-event-dispatch.tentative.html.ini
This file was deleted.
Oops, something went wrong.
12 changes: 1 addition & 11 deletions
12
testing/web-platform/meta/pointerevents/pointerevent_disabled_form_control.html.ini
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,5 @@ | ||
[pointerevent_disabled_form_control.html?mouse] | ||
expected: TIMEOUT | ||
[mouse pointerevent attributes] | ||
expected: NOTRUN | ||
|
||
|
||
prefs: [dom.forms.always_allow_pointer_events.enabled:true] | ||
[pointerevent_disabled_form_control.html?pen] | ||
expected: | ||
if (os == "android") and fission: [ERROR, TIMEOUT] | ||
ERROR | ||
|
||
[pointerevent_disabled_form_control.html?touch] | ||
expected: TIMEOUT | ||
[touch pointerevent attributes] | ||
expected: NOTRUN |
243 changes: 243 additions & 0 deletions
243
...b-platform/tests/html/semantics/disabled-elements/event-propagate-disabled.tentative.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
<!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"> | ||
</div> | ||
|
||
<script> | ||
/** | ||
* @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} target | ||
* @param {*} observedEvent | ||
*/ | ||
function shouldNotBubble(target, observedEvent) { | ||
return ( | ||
target.disabled && | ||
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 = element.disabled ? 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters