summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/invokers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/html/semantics/invokers
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/html/semantics/invokers')
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/idlharness.tentative.html16
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invokeelement-interface.tentative.html93
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html104
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invokeevent-interface.tentative.html166
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html119
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html175
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-generic-eventtarget-crash.tentative.html18
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html285
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html218
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html209
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html253
-rw-r--r--testing/web-platform/tests/html/semantics/invokers/resources/invoker-utils.js12
12 files changed, 1668 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/invokers/idlharness.tentative.html b/testing/web-platform/tests/html/semantics/invokers/idlharness.tentative.html
new file mode 100644
index 0000000000..b215f65813
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/idlharness.tentative.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<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/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+
+<script>
+ idl_test(["invokers.tentative"], ["html", "dom"], (idl_array) => {
+ idl_array.add_objects({
+ InvokeEvent: ['new InvokeEvent("invoke")'],
+ });
+ });
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invokeelement-interface.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invokeelement-interface.tentative.html
new file mode 100644
index 0000000000..b003daf20d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invokeelement-interface.tentative.html
@@ -0,0 +1,93 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<button id="invoker" invoketarget="invokee"></button>
+<div id="invokee"></div>
+
+<script>
+ test(function () {
+ assert_equals(invoker.invokeTargetElement, invokee);
+ }, "invokeTargetElement reflects invokee HTML element");
+
+ test(function () {
+ const div = document.body.appendChild(document.createElement("div"));
+ invoker.invokeTargetElement = div;
+ assert_equals(invoker.invokeTargetElement, div);
+ assert_equals(invoker.getAttribute("invoketarget"), "");
+ assert_false(invoker.hasAttribute("invokeaction"));
+ }, "invokeTargetElement reflects set value");
+
+ test(function () {
+ const host = document.body.appendChild(document.createElement("div"));
+ const shadow = host.attachShadow({ mode: "open" });
+ const button = shadow.appendChild(document.createElement("button"));
+ button.invokeTargetElement = invokee;
+ assert_equals(button.invokeTargetElement, invokee);
+ assert_equals(invoker.getAttribute("invoketarget"), "");
+ assert_false(invoker.hasAttribute("invokeaction"));
+ }, "invokeTargetElement reflects set value across shadow root into light dom");
+
+ test(function () {
+ const host = document.body.appendChild(document.createElement("div"));
+ const shadow = host.attachShadow({ mode: "open" });
+ const div = shadow.appendChild(document.createElement("div"));
+ invoker.invokeTargetElement = div;
+ assert_equals(invoker.invokeTargetElement, null);
+ assert_equals(invoker.getAttribute("invoketarget"), "");
+ assert_false(invoker.hasAttribute("invokeaction"));
+ }, "invokeTargetElement does not reflect set value inside shadowroot");
+
+ test(function () {
+ assert_throws_js(
+ TypeError,
+ function () {
+ invoker.invokeTargetElement = {};
+ },
+ "invokeTargetElement attribute must be an instance of Element",
+ );
+ }, "invokeTargetElement throws error on assignment of non Element");
+
+ test(function () {
+ assert_false(invoker.hasAttribute("invokeaction"));
+ assert_equals(invoker.invokeAction, "auto");
+ }, "invokeAction reflects 'auto' when attribute not present");
+
+ test(function () {
+ invoker.setAttribute("invokeaction", "");
+ assert_equals(invoker.getAttribute("invokeaction"), "");
+ assert_equals(invoker.invokeAction, "auto");
+ }, "invokeAction reflects 'auto' when attribute empty");
+
+ test(function () {
+ invoker.invokeAction = "fooBarBaz";
+ assert_equals(invoker.getAttribute("invokeaction"), "fooBarBaz");
+ assert_equals(invoker.invokeAction, "fooBarBaz");
+ }, "invokeAction reflects same casing");
+
+ test(function () {
+ invoker.invokeAction = "";
+ assert_equals(invoker.getAttribute("invokeaction"), "");
+ assert_equals(invoker.invokeAction, "auto");
+ }, "invokeAction reflects 'auto' when attribute empty 2");
+
+ test(function () {
+ invoker.invokeAction = [1, 2, 3];
+ assert_equals(invoker.getAttribute("invokeaction"), "1,2,3");
+ assert_equals(invoker.invokeAction, "1,2,3");
+ }, "invokeAction reflects tostring value");
+
+ test(function () {
+ invoker.invokeAction = [];
+ assert_equals(invoker.getAttribute("invokeaction"), "");
+ assert_equals(invoker.invokeAction, "auto");
+ }, "invokeAction reflects 'auto' when attribute set to []");
+
+ test(function () {
+ invoker.invokeAction = {};
+ assert_equals(invoker.invokeAction, "[object Object]");
+ }, "invokeAction reflects tostring value 2");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html
new file mode 100644
index 0000000000..84337d5723
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invokeevent-dispatch-shadow.tentative.html
@@ -0,0 +1,104 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<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>
+
+<div id="div"></div>
+<button id="button"></button>
+
+<script>
+ test(function () {
+ const host = document.createElement("div");
+ const child = host.appendChild(document.createElement("p"));
+ const shadow = host.attachShadow({ mode: "closed" });
+ const slot = shadow.appendChild(document.createElement("slot"));
+ let childEvent = null;
+ let childEventTarget = null;
+ let childEventInvoker = null;
+ let hostEvent = null;
+ let hostEventTarget = null;
+ let hostEventInvoker = null;
+ slot.addEventListener(
+ "invoke",
+ (e) => {
+ childEvent = e;
+ childEventTarget = e.target;
+ childEventInvoker = e.invoker;
+ },
+ { once: true },
+ );
+ host.addEventListener(
+ "invoke",
+ (e) => {
+ hostEvent = e;
+ hostEventTarget = e.target;
+ hostEventInvoker = e.invoker;
+ },
+ { once: true },
+ );
+ const event = new InvokeEvent("invoke", {
+ bubbles: true,
+ invoker: slot,
+ composed: true,
+ });
+ slot.dispatchEvent(event);
+ assert_true(childEvent instanceof InvokeEvent, "slot saw invoke event");
+ assert_equals(
+ childEventTarget,
+ slot,
+ "target is child inside shadow boundary",
+ );
+ assert_equals(
+ childEventInvoker,
+ slot,
+ "invoker is child inside shadow boundary",
+ );
+ assert_equals(
+ hostEvent,
+ childEvent,
+ "event dispatch propagates across shadow boundary",
+ );
+ assert_equals(
+ hostEventTarget,
+ host,
+ "target is retargeted to shadowroot host",
+ );
+ assert_equals(
+ hostEventInvoker,
+ host,
+ "invoker is retargeted to shadowroot host",
+ );
+ }, "InvokeEvent propagates across shadow boundaries retargeting invoker");
+
+ test(function (t) {
+ const host = document.createElement("div");
+ document.body.append(host);
+ t.add_cleanup(() => host.remove());
+ const shadow = host.attachShadow({ mode: "open" });
+ const button = shadow.appendChild(document.createElement("button"));
+ const invokee = host.appendChild(document.createElement("div"));
+ button.invokeTargetElement = invokee;
+ let event = null;
+ let eventTarget = null;
+ let eventInvoker = null;
+ invokee.addEventListener(
+ "invoke",
+ (e) => {
+ event = e;
+ eventTarget = e.target;
+ eventInvoker = e.invoker;
+ },
+ { once: true },
+ );
+ button.click();
+ assert_true(event instanceof InvokeEvent);
+ assert_equals(eventTarget, invokee, "target is invokee");
+ assert_equals(eventInvoker, host, "invoker is host");
+ }, "cross shadow InvokeEvent retargets invoker to host element");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invokeevent-interface.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invokeevent-interface.tentative.html
new file mode 100644
index 0000000000..82910b3d44
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invokeevent-interface.tentative.html
@@ -0,0 +1,166 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<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>
+
+<div id="div"></div>
+<button id="button"></button>
+
+<script>
+ test(function () {
+ const event = new InvokeEvent("test");
+ assert_equals(event.action, "auto");
+ assert_readonly(event, "action", "readonly attribute value");
+ }, "action is a readonly defaulting to 'auto'");
+
+ test(function () {
+ const event = new InvokeEvent("test");
+ assert_equals(event.invoker, null);
+ assert_readonly(event, "invoker", "readonly attribute value");
+ }, "invoker is readonly defaulting to null");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: "sAmPle" });
+ assert_equals(event.action, "sAmPle");
+ }, "action reflects initialized attribute");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: undefined });
+ assert_equals(event.action, "auto");
+ }, "action set to undefined");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: null });
+ assert_equals(event.action, "null");
+ }, "action set to null");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: false });
+ assert_equals(event.action, "false");
+ }, "action set to false");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: "" });
+ assert_equals(event.action, "");
+ }, "action explicitly set to empty string");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: true });
+ assert_equals(event.action, "true");
+ }, "action set to true");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: 0.5 });
+ assert_equals(event.action, "0.5");
+ }, "action set to a number");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: [] });
+ assert_equals(event.action, "");
+ }, "action set to []");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: [1, 2, 3] });
+ assert_equals(event.action, "1,2,3");
+ }, "action set to [1, 2, 3]");
+
+ test(function () {
+ const event = new InvokeEvent("test", { action: { sample: 0.5 } });
+ assert_equals(event.action, "[object Object]");
+ }, "action set to an object");
+
+ test(function () {
+ const event = new InvokeEvent("test", {
+ action: {
+ toString() {
+ return "sample";
+ },
+ },
+ });
+ assert_equals(event.action, "sample");
+ }, "action set to an object with a toString function");
+
+ test(function () {
+ const eventInit = { action: "sample", invoker: document.body };
+ const event = new InvokeEvent("test", eventInit);
+ assert_equals(event.action, "sample");
+ assert_equals(event.invoker, document.body);
+ }, "InvokeEventInit properties set value");
+
+ test(function () {
+ const eventInit = {
+ action: "open",
+ invoker: document.getElementById("div"),
+ };
+ const event = new InvokeEvent("beforetoggle", eventInit);
+ assert_equals(event.action, "open");
+ assert_equals(event.invoker, document.getElementById("div"));
+ }, "InvokeEventInit properties set value 2");
+
+ test(function () {
+ const eventInit = {
+ action: "closed",
+ invoker: document.getElementById("button"),
+ };
+ const event = new InvokeEvent("toggle", eventInit);
+ assert_equals(event.action, "closed");
+ assert_equals(event.invoker, document.getElementById("button"));
+ }, "InvokeEventInit properties set value 3");
+
+ test(function () {
+ const event = new InvokeEvent("test", { invoker: undefined });
+ assert_equals(event.invoker, null);
+ }, "invoker set to undefined");
+
+ test(function () {
+ const event = new InvokeEvent("test", { invoker: null });
+ assert_equals(event.invoker, null);
+ }, "invoker set to null");
+
+ test(function () {
+ assert_throws_js(
+ TypeError,
+ function () {
+ new InvokeEvent("test", { invoker: false });
+ },
+ "invoker is not an object",
+ );
+ }, "invoker set to false");
+
+ test(function () {
+ assert_throws_js(
+ TypeError,
+ function () {
+ const event = new InvokeEvent("test", { invoker: true });
+ },
+ "invoker is not an object",
+ );
+ }, "invoker set to true");
+
+ test(function () {
+ assert_throws_js(
+ TypeError,
+ function () {
+ const event = new InvokeEvent("test", { invoker: {} });
+ },
+ "invoker is not an object",
+ );
+ }, "invoker set to {}");
+
+ test(function () {
+ assert_throws_js(
+ TypeError,
+ function () {
+ const eventInit = { action: "closed", invoker: new XMLHttpRequest() };
+ const event = new InvokeEvent("toggle", eventInit);
+ },
+ "invoker is not an Element",
+ );
+ }, "invoker set to non-Element EventTarget");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html
new file mode 100644
index 0000000000..b19c1d3adc
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-button-event-dispatch.tentative.html
@@ -0,0 +1,119 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<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>
+
+<div id="invokee"></div>
+<button id="invokerbutton" invoketarget="invokee"></button>
+
+<script>
+ promise_test(async function (t) {
+ let event = null;
+ invokee.addEventListener("invoke", (e) => (event = e), { once: true });
+ 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, "auto", "action");
+ assert_equals(event.target, invokee, "target");
+ 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");
+
+ 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) {
+ let called = false;
+ invokerbutton.addEventListener(
+ "click",
+ (event) => {
+ event.preventDefault();
+ },
+ { once: true },
+ );
+ invokee.addEventListener(
+ "invoke",
+ (event) => {
+ called = true;
+ },
+ { once: true },
+ );
+ await clickOn(invokerbutton);
+ assert_false(called, "event was not called");
+ }, "event does not dispatch if click:preventDefault is called");
+
+ promise_test(async function (t) {
+ t.add_cleanup(() => invokerbutton.removeAttribute('disabled'));
+ let called = false;
+ invokee.addEventListener(
+ "invoke",
+ (event) => {
+ called = true;
+ },
+ { once: true },
+ );
+ invokerbutton.setAttribute("disabled", "");
+ await clickOn(invokerbutton);
+ assert_false(called, "event was not called");
+ }, "event does not dispatch if invoker is disabled");
+
+ promise_test(async function (t) {
+ svgInvokee = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ t.add_cleanup(() => {
+ invokerbutton.invokeTargetElement = invokee;
+ svgInvokee.remove();
+ });
+ document.body.append(svgInvokee);
+ let called = false;
+ assert_false(svgInvokee instanceof HTMLElement);
+ assert_true(svgInvokee instanceof Element);
+ let eventInvoker = null;
+ svgInvokee.addEventListener(
+ "invoke",
+ (event) => {
+ eventInvoker = event.invoker;
+ called = true;
+ },
+ { once: true },
+ );
+ invokerbutton.invokeTargetElement = svgInvokee;
+ await clickOn(invokerbutton);
+ assert_true(called, "event was called");
+ assert_true(eventInvoker == svgInvokee, "event invoker is set to right element");
+ }, "event dispatches if invoker is non-HTML Element");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html
new file mode 100644
index 0000000000..b72020283e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-fullscreen-behavior.tentative.html
@@ -0,0 +1,175 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Luke Warlow" href="mailto:luke@warlow.dev" />
+<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>
+
+<div id="invokee">
+ Fullscreen content
+ <button id="invokerbutton" invoketarget="invokee"></button>
+</div>
+
+<script>
+ // auto
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking div with auto action is no-op");
+
+ // toggleFullscreen
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches(":fullscreen"));
+ }, "invoking div with toggleFullscreen action makes div fullscreen");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ invokerbutton.click();
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking div with toggleFullscreen action (without user activation) is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking div with toggleFullscreen action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokerbutton.setAttribute("invokeaction", "toggleFullscreen");
+ await test_driver.bless('go fullscreen');
+ await invokee.requestFullscreen();
+ assert_true(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking fullscreen div with toggleFullscreen action exits fullscreen");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokerbutton.setAttribute("invokeaction", "tOgGlEFullscreen");
+ await test_driver.bless('go fullscreen');
+ await invokee.requestFullscreen();
+ assert_true(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking fullscreen div with toggleFullscreen (case-insensitive) action exits fullscreen");
+
+ // requestFullscreen
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "requestFullscreen");
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches(":fullscreen"));
+ }, "invoking div with requestFullscreen action makes div fullscreen");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "requestFullscreen");
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking div with requestFullscreen action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokerbutton.setAttribute("invokeaction", "requestFullscreen");
+ await test_driver.bless('go fullscreen');
+ await invokee.requestFullscreen();
+ assert_true(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches(":fullscreen"));
+ }, "invoking fullscreen div with requestFullscreen action is a no-op");
+
+ // exitFullscreen
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ assert_false(invokee.matches(":fullscreen"));
+ invokerbutton.setAttribute("invokeaction", "exitFullscreen");
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking div with exitFullscreen action is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokerbutton.setAttribute("invokeaction", "exitFullscreen");
+ await test_driver.bless('go fullscreen');
+ await invokee.requestFullscreen();
+ assert_true(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":fullscreen"));
+ }, "invoking fullscreen div with exitFullscreen action exits fullscreen");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ if (document.fullscreenElement) await document.exitFullscreen();
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ invokerbutton.setAttribute("invokeaction", "exitFullscreen");
+ await test_driver.bless('go fullscreen');
+ await invokee.requestFullscreen();
+ assert_true(invokee.matches(":fullscreen"));
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches(":fullscreen"));
+ }, "invoking fullscreen div with exitFullscreen action and preventDefault is a no-op");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-generic-eventtarget-crash.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-generic-eventtarget-crash.tentative.html
new file mode 100644
index 0000000000..b2179640dd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-generic-eventtarget-crash.tentative.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" />
+<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
+<div id="invoker"></div>
+
+<script type="module">
+ const invokeEvent = new InvokeEvent('invoke', { bubbles: true });
+ document.body.addEventListener('invoke', e => {
+ e.invoker.toString();
+ });
+ invoker.addEventListener('invoke', e => {
+ e.invoker.toString();
+ });
+ invoker.dispatchEvent(invokeEvent);
+ await Promise.resolve();
+ invokeEvent.invoker.toString();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html
new file mode 100644
index 0000000000..f3abeae165
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html
@@ -0,0 +1,285 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Luke Warlow" href="mailto:luke@warlow.dev" />
+<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>
+
+<audio controls id="invokee" src="/media/sound_5.mp3"></audio>
+<button id="invokerbutton" invoketarget="invokee"></button>
+
+<script>
+ // auto
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking audio with auto action is no-op");
+
+ // playpause
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking audio with playpause action makes audio play");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ invokerbutton.click();
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking audio with playpause action (without user activation) is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking audio with playpause action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play audio');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking playing audio with playpause action pauses it");
+
+ // play
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking audio with play action makes audio play");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ invokerbutton.click();
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking audio with play action (without user activation) is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking audio with play action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play audio');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking playing audio with play action is a no-op");
+
+ // pause
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking audio with pause action is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking audio with pause action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play audio');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking playing audio with pause action makes it pause");
+
+ // mute
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_false(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.muted);
+ }, "invoking audio with toggleMuted action mutes it");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_false(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.muted);
+ }, "invoking audio with toggleMuted action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.muted = true;
+ assert_true(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.muted);
+ }, "invoking muted audio with toggleMuted action unmutes it");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html
new file mode 100644
index 0000000000..c6735e2611
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-details-behavior.tentative.html
@@ -0,0 +1,218 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Luke Warlow" href="mailto:luke@warlow.dev" />
+<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>
+
+<details id="invokee">
+ Details Contents
+</details>
+<button id="invokerbutton" invoketarget="invokee"></button>
+
+<script>
+ // auto
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking closed details with auto action opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches("[open]"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_false(invokee.matches("[open]"));
+ }, "invoking closed details with auto action and preventDefault does not open");
+
+ promise_test(async function (t) {
+ invokee.setAttribute('open', '');
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches("[open]"));
+ }, "invoking open details with auto action closes");
+
+ promise_test(async function (t) {
+ invokee.setAttribute('open', '');
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches("[open]"));
+ }, "invoking open details with auto action and preventDefault does not close");
+
+ promise_test(async function (t) {
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ invokee.addEventListener("invoke", (e) => {
+ invokee.setAttribute('open', '');
+ }, {
+ once: true,
+ });
+ assert_false(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches("[open]"));
+ }, "invoking details with auto action where event listener opens leads to a closed details");
+
+ promise_test(async function (t) {
+ invokee.setAttribute('open', '');
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ invokee.addEventListener("invoke", (e) => {
+ invokee.removeAttribute('open');
+ }, {
+ once: true,
+ });
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches("[open]"));
+ }, "invoking open details with auto action where event listener closes leads to an open details");
+
+ // toggle
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches("[open]"));
+ invokerbutton.setAttribute("invokeaction", "toggle");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking closed details with toggle action opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches("[open]"));
+ invokerbutton.setAttribute("invokeaction", "tOgGlE");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking closed details with toggle (case-insensitive) action opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches("[open]"));
+ invokerbutton.setAttribute("invokeaction", "toggle");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_false(invokee.matches("[open]"));
+ }, "invoking closed details with toggle action and preventDefault does not open");
+
+ promise_test(async function (t) {
+ invokee.setAttribute('open', '');
+ invokerbutton.setAttribute("invokeaction", "toggle");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches("[open]"));
+ }, "invoking open details with toggle action closes");
+
+ promise_test(async function (t) {
+ invokee.setAttribute('open', '');
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ invokerbutton.setAttribute("invokeaction", "toggle");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches("[open]"));
+ }, "invoking open details with toggle action and preventDefault does not close");
+
+ // open
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "open");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking closed details with open action opens");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "oPeN");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking closed details with open (case insensitive) action opens");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "open");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.setAttribute('open', '');
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ }, "invoking open details with open action is noop");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "open");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches("[open]"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_false(invokee.matches("[open]"));
+ }, "invoking closed popover with open action and preventDefault does not open");
+
+ // close
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "close");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches("[open]"));
+ }, "invoking closed details with close action is noop");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "close");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.setAttribute('open', '');
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_false(invokee.matches("[open]"));
+ }, "invoking open details with close action closes");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "cLoSe");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.setAttribute('open', '');
+ assert_true(invokee.matches("[open]"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_false(invokee.matches("[open]"));
+ }, "invoking open details with close (case insensitive) action closes");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "close");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.setAttribute('open', '');
+ t.add_cleanup(() => invokee.removeAttribute('open'));
+ assert_true(invokee.matches("[open]"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ assert_true(invokee.matches("[open]"));
+ }, "invoking open details with close action with preventDefault does not close");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html
new file mode 100644
index 0000000000..03eba22285
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-popover-behavior.tentative.html
@@ -0,0 +1,209 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Keith Cirkel" href="mailto:keithamus@github.com" />
+<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>
+
+<div id="invokee" popover>
+ <button id="invokerbutton2" invoketarget="invokee"></button>
+</div>
+<button id="invokerbutton" invoketarget="invokee"></button>
+
+<script>
+ // auto
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as auto) closed popover opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches(":popover-open"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as auto) closed popover with preventDefault does not open");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as auto) open popover closes");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as auto) from within open popover closes");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ t.add_cleanup(() => invokee.hidePopover());
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as auto) open popover with preventDefault does not close");
+
+ // togglepopover
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches(":popover-open"));
+ invokerbutton.setAttribute("invokeaction", "togglepopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover) closed popover opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches(":popover-open"));
+ invokerbutton.setAttribute("invokeaction", "tOgGlEpOpOvEr");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover - case insensitive) closed popover opens");
+
+ promise_test(async function (t) {
+ assert_false(invokee.matches(":popover-open"));
+ invokerbutton.setAttribute("invokeaction", "togglepopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover) closed popover with preventDefault does not open");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ invokerbutton2.setAttribute("invokeaction", "togglepopover");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover) open popover closes");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ invokerbutton2.setAttribute("invokeaction", "togglepopover");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover) from within open popover closes");
+
+ promise_test(async function (t) {
+ invokee.showPopover();
+ t.add_cleanup(() => invokee.hidePopover());
+ invokerbutton2.setAttribute("invokeaction", "togglepopover");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as togglepopover) open popover with preventDefault does not close");
+
+ // showpopover
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "showpopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as showpopover) closed popover opens");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "sHoWpOpOvEr");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as showpopover - case insensitive) closed popover opens");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "showpopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ invokee.showPopover();
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as showpopover) open popover is noop");
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "showpopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches(":popover-open"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as showpopover) closed popover with preventDefault does not open");
+
+ // hidepopover
+
+ promise_test(async function (t) {
+ invokerbutton.setAttribute("invokeaction", "hidepopover");
+ t.add_cleanup(() => invokerbutton.removeAttribute("invokeaction"));
+ assert_false(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton);
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as hidepopover) closed popover is noop");
+
+ promise_test(async function (t) {
+ invokerbutton2.setAttribute("invokeaction", "hidepopover");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ invokee.showPopover();
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as hidepopover) open popover closes");
+
+ promise_test(async function (t) {
+ invokerbutton2.setAttribute("invokeaction", "hIdEpOpOvEr");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ invokee.showPopover();
+ assert_true(invokee.matches(":popover-open"));
+ await clickOn(invokerbutton2);
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_false(invokee.matches(":popover-open"));
+ }, "invoking (as hidepopover - case insensitive) open popover closes");
+
+ promise_test(async function (t) {
+ invokerbutton2.setAttribute("invokeaction", "hidepopover");
+ t.add_cleanup(() => invokerbutton2.removeAttribute("invokeaction"));
+ invokee.showPopover();
+ t.add_cleanup(() => invokee.hidePopover());
+ assert_true(invokee.matches(":popover-open"));
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ await clickOn(invokerbutton2);
+ assert_true(invokee.matches(":popover-open"));
+ }, "invoking (as hidepopover) open popover with preventDefault does not close");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html
new file mode 100644
index 0000000000..5bbcd83e72
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html
@@ -0,0 +1,253 @@
+<!doctype html>
+<meta charset="utf-8" />
+<meta name="author" title="Luke Warlow" href="mailto:luke@warlow.dev" />
+<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>
+
+<video controls id="invokee" src="/media/movie_5.mp4"></video>
+<button id="invokerbutton" invoketarget="invokee"></button>
+
+<script>
+ // auto
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking video with auto action is no-op");
+
+ // playpause
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking video with playpause action makes video play");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking video with playpause action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play video');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "playpause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking playing video with playpause action pauses it");
+
+ // play
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking video with play action makes video play");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking video with play action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play video');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "play");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.paused);
+ }, "invoking playing video with play action is a no-op");
+
+ // pause
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking video with pause action is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_true(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking video with pause action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ await test_driver.bless('play video');
+ invokee.play();
+ assert_false(invokee.paused);
+ invokerbutton.setAttribute("invokeaction", "pause");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.paused);
+ }, "invoking playing video with pause action makes it pause");
+
+ // mute
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ assert_false(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_true(invokee.muted);
+ }, "invoking video with toggleMuted action mutes it");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.addEventListener("invoke", (e) => e.preventDefault(), {
+ once: true,
+ });
+ assert_false(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.muted);
+ }, "invoking video with toggleMuted action and preventDefault is a no-op");
+
+ promise_test(async function (t) {
+ t.add_cleanup(async () => {
+ invokerbutton.removeAttribute("invokeaction");
+ invokee.pause();
+ invokee.currentTime = 0;
+ invokee.muted = false;
+ });
+ invokee.muted = true;
+ assert_true(invokee.muted);
+ invokerbutton.setAttribute("invokeaction", "toggleMuted");
+ await clickOn(invokerbutton);
+ await new Promise(resolve => {
+ requestAnimationFrame(resolve);
+ });
+ assert_false(invokee.muted);
+ }, "invoking muted video with toggleMuted action unmutes it");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/invokers/resources/invoker-utils.js b/testing/web-platform/tests/html/semantics/invokers/resources/invoker-utils.js
new file mode 100644
index 0000000000..317945502d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/invokers/resources/invoker-utils.js
@@ -0,0 +1,12 @@
+function waitForRender() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+}
+async function clickOn(element) {
+ const actions = new test_driver.Actions();
+ await waitForRender();
+ await actions.pointerMove(0, 0, {origin: element})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ await waitForRender();
+}