[Invokers] Reland: Only dispatch invoke for built-ins or actions containing '-'

(This relands
https://chromium-review.googlesource.com/c/chromium/src/+/5366502 after
fixing timeout issues in
https://chromium-review.googlesource.com/c/chromium/src/+/5372478 and
new failing tests from
https://chromium-review.googlesource.com/c/chromium/src/+/5374369)

This change refactors the `HandleInvokeInternal` calls to use an enum
class of known attribute values including a `kCustom` key which can determine if the invoke action meets the criteria for being
a "custom" action - that is it includes a dash (see
https://github.com/whatwg/html/pull/9841/commits/b40ba866cc21553f63d8f6244becbdbc38176310).

This also updates the tests to reflect this; only actions with a `-`, or
otherwise known actions, will dispatch an event. All other actions do
not, and must be manually dispatched. This does not preclude web authors
from manually dispatching said events.

Bug: 40947660
Change-Id: Ia704e73e50bf65b01286a148bd5b810241f0c664
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5393265
Reviewed-by: Joey Arhar <jarhar@chromium.org>
Auto-Submit: Keith Cirkel <chromium@keithcirkel.co.uk>
Commit-Queue: Keith Cirkel <chromium@keithcirkel.co.uk>
Cr-Commit-Position: refs/heads/main@{#1279659}
diff --git a/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html b/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html
index 4ccd301..9120cc3 100644
--- a/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html
+++ b/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html
@@ -28,35 +28,63 @@
     assert_equals(event.invoker, invokerbutton, "invoker");
   }, "event dispatches on click");
 
-  promise_test(async function (t) {
-    let event = null;
-    invokee.addEventListener("invoke", (e) => (event = e), { once: true });
-    invokerbutton.invokeAction = "fooBar";
-    await clickOn(invokerbutton);
-    assert_true(event instanceof InvokeEvent, "event is InvokeEvent");
-    assert_equals(event.type, "invoke", "type");
-    assert_equals(event.bubbles, false, "bubbles");
-    assert_equals(event.composed, true, "composed");
-    assert_equals(event.isTrusted, true, "isTrusted");
-    assert_equals(event.action, "fooBar", "action");
-    assert_equals(event.target, invokee, "target");
-    assert_equals(event.invoker, invokerbutton, "invoker");
-  }, "event action is set to invokeAction");
+  // valid custom invokeactions
+  ["-foo", "foo-", "cAsE-cArRiEs", "-", "-a-", "a-b", "---", "show-picker"].forEach(
+    (action) => {
+      promise_test(async function (t) {
+        t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+        let event = null;
+        invokee.addEventListener("invoke", (e) => (event = e), { once: true });
+        invokerbutton.invokeAction = action;
+        await clickOn(invokerbutton);
+        assert_true(event instanceof InvokeEvent, "event is InvokeEvent");
+        assert_equals(event.type, "invoke", "type");
+        assert_equals(event.bubbles, false, "bubbles");
+        assert_equals(event.composed, true, "composed");
+        assert_equals(event.isTrusted, true, "isTrusted");
+        assert_equals(event.action, action, "action");
+        assert_equals(event.target, invokee, "target");
+        assert_equals(event.invoker, invokerbutton, "invoker");
+      }, `setting custom invokeAction property to ${action} (must include dash) sets event action`);
 
-  promise_test(async function (t) {
-    let event = null;
-    invokee.addEventListener("invoke", (e) => (event = e), { once: true });
-    invokerbutton.setAttribute("invokeaction", "BaRbAz");
-    await clickOn(invokerbutton);
-    assert_true(event instanceof InvokeEvent, "event is InvokeEvent");
-    assert_equals(event.type, "invoke", "type");
-    assert_equals(event.bubbles, false, "bubbles");
-    assert_equals(event.composed, true, "composed");
-    assert_equals(event.isTrusted, true, "isTrusted");
-    assert_equals(event.action, "BaRbAz", "action");
-    assert_equals(event.target, invokee, "target");
-    assert_equals(event.invoker, invokerbutton, "invoker");
-  }, "event action is set to invokeaction attribute");
+      promise_test(async function (t) {
+        t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+        let event = null;
+        invokee.addEventListener("invoke", (e) => (event = e), { once: true });
+        invokerbutton.setAttribute("invokeaction", action);
+        await clickOn(invokerbutton);
+        assert_true(event instanceof InvokeEvent, "event is InvokeEvent");
+        assert_equals(event.type, "invoke", "type");
+        assert_equals(event.bubbles, false, "bubbles");
+        assert_equals(event.composed, true, "composed");
+        assert_equals(event.isTrusted, true, "isTrusted");
+        assert_equals(event.action, action, "action");
+        assert_equals(event.target, invokee, "target");
+        assert_equals(event.invoker, invokerbutton, "invoker");
+      }, `setting custom invokeaction attribute to ${action} (must include dash) sets event action`);
+    },
+  );
+
+  // invalid custom invokeactions
+  ["foo", "foobar", "foo bar", "em—dash", "hidedocument"].forEach((action) => {
+    promise_test(async function (t) {
+      t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+      let event = null;
+      invokee.addEventListener("invoke", (e) => (event = e), { once: true });
+      invokerbutton.invokeAction = action;
+      await clickOn(invokerbutton);
+      assert_equals(event, null, "event should not have fired");
+    }, `setting custom invokeAction property to ${action} (no dash) did not dispatch an event`);
+
+    promise_test(async function (t) {
+      t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+      let event = null;
+      invokee.addEventListener("invoke", (e) => (event = e), { once: true });
+      invokerbutton.setAttribute("invokeaction", action);
+      await clickOn(invokerbutton);
+      assert_equals(event, null, "event should not have fired");
+    }, `setting custom invokeaction attribute to ${action} (no dash) did not dispatch an event`);
+  });
 
   promise_test(async function (t) {
     let called = false;
@@ -79,7 +107,7 @@
   }, "event does not dispatch if click:preventDefault is called");
 
   promise_test(async function (t) {
-    t.add_cleanup(() => invokerbutton.removeAttribute('disabled'));
+    t.add_cleanup(() => invokerbutton.removeAttribute("disabled"));
     let called = false;
     invokee.addEventListener(
       "invoke",
@@ -94,7 +122,7 @@
   }, "event does not dispatch if invoker is disabled");
 
   promise_test(async function (t) {
-    svgInvokee = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+    svgInvokee = document.createElementNS("http://www.w3.org/2000/svg", "svg");
     t.add_cleanup(() => {
       invokerbutton.invokeTargetElement = invokee;
       svgInvokee.remove();
@@ -116,7 +144,15 @@
     invokerbutton.invokeTargetElement = svgInvokee;
     await clickOn(invokerbutton);
     assert_true(called, "event was called");
-    assert_equals(eventInvoker, invokerbutton, "event.invoker is set to right element");
-    assert_equals(eventTarget, svgInvokee, "event.target is set to right element");
+    assert_equals(
+      eventInvoker,
+      invokerbutton,
+      "event.invoker is set to right element",
+    );
+    assert_equals(
+      eventTarget,
+      svgInvokee,
+      "event.target is set to right element",
+    );
   }, "event dispatches if invokee is non-HTML Element");
 </script>