[selectmenu] Don't run controller code if event was preventDefaulted
If an author does preventDefault on a click or keyboard event, then
that event shouldn't trigger <selectmenu> controller code behavior.
This can be useful for cases where authors want to nest interactive
content (buttons, etc) inside <selectmenu> parts. They may want to
preventDefault events handled by the nested content so that the event
doesn't bubble up to the outer part and trigger <selectmenu> behavior.
Bug: 1121840
Change-Id: Ia2b20b73cc4f9137e2501016c372a4a064aefe6b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3319507
Reviewed-by: Mason Freed <masonf@chromium.org>
Reviewed-by: Ionel Popescu <iopopesc@microsoft.com>
Commit-Queue: Dan Clark <daniec@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#949164}
diff --git a/html/semantics/forms/the-selectmenu-element/selectmenu-events.tentative.html b/html/semantics/forms/the-selectmenu-element/selectmenu-events.tentative.html
new file mode 100644
index 0000000..89069e4
--- /dev/null
+++ b/html/semantics/forms/the-selectmenu-element/selectmenu-events.tentative.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<title>HTMLSelectMenuElement Test: events</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>
+
+<selectmenu id="selectMenu0">
+ <div slot="button" behavior="button">
+ <span behavior="selected-value"></span>
+ <button id="selectMenu0-button">selectMenu0-button</button>
+ </div>
+ <option>one</option>
+ <option>two</option>
+ <option>three</option>
+</selectmenu>
+
+<selectmenu id="selectMenu1">
+ <option>one</option>
+ <option>
+ two
+ <button id="selectMenu1-button">selectMenu1-button</button>
+ </option>
+ <option>three</option>
+</selectmenu>
+
+<script>
+
+ function clickOn(element) {
+ const actions = new test_driver.Actions();
+ return actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ }
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu0");
+ const selectMenuButton = document.getElementById("selectMenu0-button");
+ assert_false(selectMenu.open);
+ const selectMenuButtonPromise = new Promise(async resolve => {
+ selectMenuButton.addEventListener("click", (e) => {
+ assert_false(selectMenu.open, "Listbox shouldn't have opened yet");
+ // PreventDefaulting the event here should prevent UA controller code
+ // on the button part from opening the listbox.
+ e.preventDefault();
+ resolve();
+ });
+ });
+
+ const selectMenuPromise = new Promise(async resolve => {
+ selectMenu.addEventListener("click", (e) => {
+ assert_true(e.defaultPrevented, "Event should have been defaultPrevented by selectMenuButton click handler");
+ assert_false(selectMenu.open, "Listbox shouldn't have opened, because click event was defaultPrevented.");
+ resolve();
+ });
+ });
+
+ await clickOn(selectMenuButton);
+ return Promise.all([selectMenuButtonPromise, selectMenuPromise]);
+ }, "Button controller code should not run if the click event is preventDefaulted.");
+
+ // See https://w3c.github.io/webdriver/#keyboard-actions
+ const KEY_CODE_MAP = {
+ 'Enter': '\uE007',
+ 'Space': '\uE00D',
+ 'ArrowUp': '\uE013',
+ 'ArrowDown': '\uE015'
+ };
+
+ promise_test(async () => {
+ const selectMenu = document.getElementById("selectMenu1");
+ const selectMenuButton = document.getElementById("selectMenu1-button");
+ await clickOn(selectMenu);
+ assert_true(selectMenu.open);
+ const selectMenuButtonPromise = new Promise(async resolve => {
+ selectMenuButton.addEventListener("click", (e) => {
+ assert_true(selectMenu.open, "Listbox shouldn't have closed yet");
+ // PreventDefaulting the event here should prevent UA controller code
+ // on the listbox part from selecting the option and closing the listbox.
+ e.preventDefault();
+ resolve();
+ });
+ });
+
+ const selectMenuPromise = new Promise(async resolve => {
+ selectMenu.addEventListener("click", (e) => {
+ assert_true(e.defaultPrevented, "Event should have been defaultPrevented by selectMenuButton click handler");
+ assert_true(selectMenu.open, "Listbox shouldn't have closed, because keydown event was defaultPrevented.");
+ assert_equals(selectMenu.value, "one", "<selectmenu> shouldn't have changed value, because keydown event was defaultPrevented.");
+ resolve();
+ });
+ });
+
+ await clickOn(selectMenuButton);
+ return Promise.all([selectMenuButtonPromise, selectMenuPromise]);
+ }, "Listbox controller code should not run if the click event is preventDefaulted.");
+</script>