[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>