commit f335211ab3db662d7c04f5e5cc92526f2e55fb25
parent c06ec8a3cba1b11c264780eab390784d6118175a
Author: Mason Freed <masonf@chromium.org>
Date: Thu, 6 Nov 2025 21:34:35 +0000
Bug 1997632 [wpt PR 55794] - Refactor menu event handling code [2/4], a=testonly
Automatic update from web-platform-tests
Refactor menu event handling code [2/4]
Another refactoring CL for menu element code. Previously, the command
invoker logic was split (and roughly duplicated) between
HTMLButtonElement and HTMLMenuItemElement. Since both can be command
invokers, it makes sense to move the command management stuff to the
common ancestor class, HTMLElement. This CL does that, and de-dupes
the code out of both subclasses. There is now just one base class
method that is overridden in sub-classes that can be command
invokers: CanBeCommandInvoker().
There was also a weird combination of popover activation command
code, which belongs on HTMLElement because any element can be a
popover, and menu activation command code, which belongs on the
HTMLMenuItemElement class, because only that class handles menu
commands.
This touches a few outside classes like ax_object, which get simpler
now that they don't have to deal with each case of a command invoker.
There is one behavior change here: now, if a menuitem is *both*
checkable *and* a sub-menu invoker, the sub-menu wins, and the
item does not do checked/unchecked behaviors.
I also added a test that seemed missing: make sure only buttons and
menuitems can be command invokers. Even though <menuitem> isn't
shipped, this test should pass because it explicitly skips <menuitem>,
and anyway menuitem isn't included in the HTML5_ELEMENTS list.
Bug: 406566432
Change-Id: I281eda0eda4b0dced3ad38bd09a488f2c6b1ddf4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7078858
Reviewed-by: Dominic Farolino <dom@chromium.org>
Commit-Queue: Mason Freed <masonf@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1538871}
--
wpt-commits: 65880d14fe4ab0b6a492ccdc6d04fe2dd52512eb
wpt-pr: 55794
Diffstat:
2 files changed, 70 insertions(+), 5 deletions(-)
diff --git a/testing/web-platform/tests/html/semantics/menu/tentative/menubar-invoke-menulist.html b/testing/web-platform/tests/html/semantics/menu/tentative/menubar-invoke-menulist.html
@@ -128,14 +128,13 @@ test(() => {
}, "Menuitem with invalid command/commandfor cannot invoke menulist popover.");
test(() => {
- checkableMenuitem.command = "toggle-menu";
- checkableMenuitem.commandForElement = menulist;
-
+ assert_equals(checkableMenuitem.commandForElement,null,
+ "To start, checkable item shouldn't be an invoker")
checkableMenuitem.click();
assert_true(checkableMenuitem.checked,
"checkable menu item becomes checked");
- assert_true(menulist.matches(":popover-open"),
- "menulist matches :popover-open");
+ assert_false(menulist.matches(":popover-open"),
+ "not an invoker yet");
checkableMenuitem.click();
@@ -143,5 +142,19 @@ test(() => {
"checkable menu item is no longer checked");
assert_false(menulist.matches(":popover-open"),
"menulist no longer matches :popover-open");
+
+ // Being an invoker for a sub-menu causes checkability to stop.
+ checkableMenuitem.command = "toggle-menu";
+ checkableMenuitem.commandForElement = menulist;
+ checkableMenuitem.click();
+ assert_false(checkableMenuitem.checked,
+ "checkable menu item that invokes a menu does not become checked");
+ assert_true(menulist.matches(":popover-open"),
+ "menulist matches :popover-open");
+ checkableMenuitem.click();
+ assert_false(checkableMenuitem.checked,
+ "checkable menu item is still not checked");
+ assert_false(menulist.matches(":popover-open"),
+ "menulist no longer matches :popover-open");
}, "Checkable menuitems can still invoke menulist popovers");
</script>
diff --git a/testing/web-platform/tests/html/semantics/the-button-element/command-and-commandfor/invalid-element-types.html b/testing/web-platform/tests/html/semantics/the-button-element/command-and-commandfor/invalid-element-types.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<meta name="timeout" content="long" />
+<meta charset="utf-8" />
+<meta name="author" href="mailto:masonf@chromium.org" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<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>
+<script src="resources/invoker-utils.js"></script>
+<script src="/html/resources/common.js"></script>
+
+<meta name=variant content=?command=--custom-event&half=first>
+<meta name=variant content=?command=--custom-event&half=second>
+<meta name=variant content=?command=show-popover&half=first>
+<meta name=variant content=?command=show-popover&half=second>
+<meta name=variant content=?command=show-modal&half=first>
+<meta name=variant content=?command=show-modal&half=second>
+
+<div id="invokee"></div>
+
+<script>
+ // The command parameter is provided by variants, to
+ // effectively split this (slow) test into multiple runs.
+ const urlParams = new URLSearchParams(window.location.search);
+ command = urlParams.get('command');
+ half = urlParams.get('half');
+ const firstHalf = half === 'first';
+ assert_true(firstHalf || half === 'second');
+
+ const allowed = ['button','input','menuitem'];
+ const allTags = HTML5_ELEMENTS.filter(el => (!allowed.includes(el)));
+ const midpoint = Math.floor(allTags.length / 2);
+ const tags = firstHalf ? allTags.slice(0,midpoint) : allTags.slice(midpoint);
+ let gotEvent = false;
+ invokee.addEventListener('command',() => (gotEvent = true));
+ for(const tag of tags) {
+ promise_test(async () => {
+ gotEvent = false;
+ const element = document.createElement(tag);
+ element.setAttribute('commandfor','invokee');
+ element.setAttribute('command',command);
+ element.style.display = 'block'; // For normally invisible elements
+ document.body.appendChild(element);
+ // Click two ways
+ element.click();
+ await clickOn(element);
+ assert_false(gotEvent,'Command should not be fired');
+ },`command/commandfor on <${tag}> with command=${command} should not function`);
+ }
+</script>