summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/inert
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/inert
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/inert')
-rw-r--r--testing/web-platform/tests/inert/dynamic-inert-on-focused-element.html72
-rw-r--r--testing/web-platform/tests/inert/inert-and-contenteditable.html54
-rw-r--r--testing/web-platform/tests/inert/inert-and-find.html53
-rw-r--r--testing/web-platform/tests/inert/inert-canvas-fallback-content.html74
-rw-r--r--testing/web-platform/tests/inert/inert-computed-style.html49
-rw-r--r--testing/web-platform/tests/inert/inert-does-not-match-disabled-selector.html27
-rw-r--r--testing/web-platform/tests/inert/inert-iframe-hittest.html108
-rw-r--r--testing/web-platform/tests/inert/inert-iframe-tabbing.html123
-rw-r--r--testing/web-platform/tests/inert/inert-in-shadow-dom.html55
-rw-r--r--testing/web-platform/tests/inert/inert-inlines-around-selection-range-in-contenteditable.html190
-rw-r--r--testing/web-platform/tests/inert/inert-inlines.html55
-rw-r--r--testing/web-platform/tests/inert/inert-label-focus.html53
-rw-r--r--testing/web-platform/tests/inert/inert-node-is-uneditable.html45
-rw-r--r--testing/web-platform/tests/inert/inert-node-is-unfocusable.html79
-rw-r--r--testing/web-platform/tests/inert/inert-node-is-unselectable.html20
-rw-r--r--testing/web-platform/tests/inert/inert-on-non-html.html144
-rw-r--r--testing/web-platform/tests/inert/inert-on-slots.html57
-rw-r--r--testing/web-platform/tests/inert/inert-pseudo-element-hittest.html66
-rw-r--r--testing/web-platform/tests/inert/inert-svg-hittest.html70
-rw-r--r--testing/web-platform/tests/inert/inert-with-modal-dialog-001.html56
-rw-r--r--testing/web-platform/tests/inert/inert-with-modal-dialog-002.html56
21 files changed, 1506 insertions, 0 deletions
diff --git a/testing/web-platform/tests/inert/dynamic-inert-on-focused-element.html b/testing/web-platform/tests/inert/dynamic-inert-on-focused-element.html
new file mode 100644
index 0000000000..0ddf5a995a
--- /dev/null
+++ b/testing/web-platform/tests/inert/dynamic-inert-on-focused-element.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Dynamic inertness on focused element</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#focus-fixup-rule">
+<meta name="assert" content="If a focused element becomes inert, it stops being focused.">
+<div id="log"></div>
+
+<div class="test-wrapper" data-name="<input> that gets 'inert' attribute">
+ <input class="becomes-inert check-focus">
+</div>
+
+<div class="test-wrapper" data-name="<input> whose parent gets 'inert' attribute">
+ <div class="becomes-inert">
+ <input class="check-focus">
+ </div>
+</div>
+
+<div class="test-wrapper" data-name="<button> that gets 'inert' attribute">
+ <button class="becomes-inert check-focus">foo</button>
+</div>
+
+<div class="test-wrapper" data-name="<div> that gets 'inert' attribute">
+ <div class="becomes-inert check-focus" tabindex="-1"></div>
+</div>
+
+<div class="test-wrapper" data-name="<div> whose parent gets 'inert' attribute">
+ <div class="becomes-inert">
+ <div class="check-focus" tabindex="-1">bar</div>
+ </div>
+</div>
+
+<div class="test-wrapper" data-name="<div> whose grandparent gets 'inert' attribute">
+ <div class="becomes-inert">
+ <p>
+ <span class="check-focus" tabindex="-1">baz</span>
+ </p>
+ </div>
+</div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+function nextRepaint() {
+ return new Promise(resolve => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(resolve);
+ });
+ });
+}
+
+const loaded = new Promise(resolve => {
+ addEventListener("load", resolve, {once: true});
+});
+promise_setup(() => loaded);
+
+for (const testWrapper of document.querySelectorAll(".test-wrapper")) {
+ const becomesInert = testWrapper.querySelector(".becomes-inert");
+ const checkFocus = testWrapper.querySelector(".check-focus");
+ promise_test(async function() {
+ checkFocus.focus();
+ assert_equals(document.activeElement, checkFocus, "The element is focused");
+
+ becomesInert.inert = true;
+
+ // The focus fixup rule should blur the element since it's no longer focusable.
+ // In Chromium this happens after a repaint (https://crbug.com/1275097).
+ await nextRepaint();
+ assert_equals(document.activeElement, document.body, "The element stops being focused");
+ }, testWrapper.dataset.name);
+}
+</script>
diff --git a/testing/web-platform/tests/inert/inert-and-contenteditable.html b/testing/web-platform/tests/inert/inert-and-contenteditable.html
new file mode 100644
index 0000000000..01091d1736
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-and-contenteditable.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Inert and contenteditable</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#inert">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#attr-contenteditable">
+<meta assert="assert" content="
+ Executing an editing command in a node marked as both inert and editable
+ should have the same result regardless of which ancestors trigger each effect.
+ Only testing for consistency, the exact result is not interoperable.">
+
+<div id="log"></div>
+
+<div class="wrapper" contenteditable inert>
+ {<p class="target">target</p>}
+</div>
+
+<div class="wrapper" contenteditable>
+ {<p class="target" inert>target</p>}
+</div>
+
+<div class="wrapper" inert>
+ {<p class="target" contenteditable>target</p>}
+</div>
+
+<div class="wrapper">
+ {<p class="target" contenteditable inert>target</p>}
+</div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const results = [];
+const textContents = [];
+setup(function() {
+ const selection = getSelection();
+ for (let wrapper of document.querySelectorAll(".wrapper")) {
+ const target = wrapper.querySelector(".target");
+ selection.collapse(target.firstChild, 3);
+ results.push(document.execCommand("delete"));
+ textContents.push(wrapper.textContent.trim());
+ }
+});
+function testSameValues(array, description) {
+ test(function() {
+ assert_greater_than(array.length, 0, "Array shouldn't be empty");
+ for (let i = 1; i < array.length; ++i) {
+ assert_equals(array[i], array[0], `${JSON.stringify(array)} at index ${i}`);
+ }
+ }, description);
+}
+testSameValues(results, "execCommand should return the same value");
+testSameValues(textContents, "The resulting textContent should be the same value");
+</script>
diff --git a/testing/web-platform/tests/inert/inert-and-find.html b/testing/web-platform/tests/inert/inert-and-find.html
new file mode 100644
index 0000000000..87555b0097
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-and-find.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/#inert-subtrees">
+<title>Basic test for inert and find</title>
+<body>
+<script>
+const t = async_test("Basic test for inert and find");
+
+function testFindable(findCount, textToFind, buildDoc, description) {
+ if (typeof findCount == "boolean")
+ findCount = findCount ? 1 : 0;
+ try {
+ const iframe = document.querySelector("iframe")
+ iframe.contentDocument.documentElement.innerHTML =
+ (typeof buildDoc == "string") ? buildDoc : "";
+
+ if (typeof buildDoc == "function")
+ buildDoc(iframe.contentDocument);
+
+ iframe.contentWindow.getSelection().removeAllRanges();
+ for (let i = findCount; i >= 0; --i) {
+ const expectFindable = i != 0;
+ assert_equals(
+ iframe.contentWindow.find(textToFind),
+ expectFindable,
+ "Should be " + (expectFindable ? "" : "not ") + "findable: " + description + ", text: " + textToFind + ", iter: " + (findCount - i + 1)
+ );
+ }
+ } catch (ex) {
+ assert_unreached(ex);
+ }
+}
+
+let runTests = t.step_func_done(function() {
+ testFindable(true, "me", `
+ Find me please
+ `, "basic functionality test");
+
+ testFindable(false, "me", `
+ <div inert>Do not find me please</div>
+ `, "Basic use case");
+});
+
+window.onload = function() {
+ let iframe = document.createElement("iframe");
+ iframe.onload = runTests;
+ iframe.srcdoc = "<!doctype html><html></html>";
+ document.body.appendChild(iframe);
+};
+</script>
+</body>
diff --git a/testing/web-platform/tests/inert/inert-canvas-fallback-content.html b/testing/web-platform/tests/inert/inert-canvas-fallback-content.html
new file mode 100644
index 0000000000..f22549b503
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-canvas-fallback-content.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Focusability of inert inside canvas</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#focusable-area">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#inert">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/canvas.html#being-used-as-relevant-canvas-fallback-content">
+<link rel="help" href="https://github.com/whatwg/html/issues/7534">
+<meta name="assert" content="
+ Checks that inert elements are not focusable
+ even when being used as relevant canvas fallback content,
+ even in a display:none subtree">
+<div id="log"></div>
+<canvas>
+ <section>
+ <div tabindex="-1" data-focusable="true">
+ normal `div`
+ </div>
+ <span tabindex="-1" data-focusable="true">
+ normal `span`
+ </span>
+ <a href="#" data-focusable="true">
+ normal `a`
+ </a>
+ </section>
+ <section style="display: none">
+ <div tabindex="-1" data-focusable="false">
+ `div` in `display: none`
+ </div>
+ <span tabindex="-1" data-focusable="false">
+ `span` in `display: none`
+ </span>
+ <a href="#" data-focusable="false">
+ `a` in `display: none`
+ </a>
+ </section>
+ <section inert>
+ <div tabindex="-1" data-focusable="false">
+ inert `div`
+ </div>
+ <span tabindex="-1" data-focusable="false">
+ inert `span`
+ </span>
+ <a href="#" data-focusable="false">
+ inert `a`
+ </a>
+ </section>
+ <section inert style="display: none">
+ <div tabindex="-1" data-focusable="false">
+ inert `div` in `display: none`
+ </div>
+ <span tabindex="-1" data-focusable="false">
+ inert `span` in `display: none`
+ </span>
+ <a href="#" data-focusable="false">
+ inert `a` in `display: none`
+ </a>
+ </section>
+</canvas>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+for (let element of document.querySelectorAll("[data-focusable]")) {
+ test(function() {
+ assert_not_equals(document.activeElement, element);
+ element.focus();
+ if (JSON.parse(element.dataset.focusable)) {
+ assert_equals(document.activeElement, element);
+ } else {
+ assert_not_equals(document.activeElement, element);
+ }
+ }, element.textContent);
+}
+</script>
diff --git a/testing/web-platform/tests/inert/inert-computed-style.html b/testing/web-platform/tests/inert/inert-computed-style.html
new file mode 100644
index 0000000000..f1adbab6b7
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-computed-style.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>inert isn't hit-testable, but that isn't expose in the computed style</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ body { margin: 0 }
+ div {
+ width: 100px;
+ height: 100px;
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: blue;
+ }
+ #nonInert {
+ background-color: red;
+ }
+</style>
+<div id="nonInert"></div>
+<div id="inert"></div>
+<script>
+ test(function() {
+ let inert = document.getElementById("inert");
+ assert_equals(
+ document.elementFromPoint(50, 50),
+ inert,
+ "not-yet-inert node hit-test before non-inert node",
+ );
+ inert.inert = true;
+ assert_equals(
+ document.elementFromPoint(50, 50),
+ nonInert,
+ "inert node is transparent to events (as pointer-events: none)",
+ );
+ assert_equals(
+ getComputedStyle(inert).pointerEvents,
+ getComputedStyle(nonInert).pointerEvents,
+ "inert node doesn't change pointer-events computed value",
+ );
+ assert_equals(
+ getComputedStyle(inert).userSelect,
+ getComputedStyle(nonInert).userSelect,
+ "inert node doesn't change user-select computed value",
+ );
+ });
+</script>
diff --git a/testing/web-platform/tests/inert/inert-does-not-match-disabled-selector.html b/testing/web-platform/tests/inert/inert-does-not-match-disabled-selector.html
new file mode 100644
index 0000000000..74b8ac3f7d
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-does-not-match-disabled-selector.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+button {
+ color: green;
+}
+
+button:disabled {
+ color: red;
+}
+
+</style>
+</head>
+<body style="color: green">
+<button inert>The test passes if this is in green.</button>
+<script>
+test(function() {
+ button = document.querySelector('button');
+ color = document.defaultView.getComputedStyle(button).getPropertyValue('color');
+ assert_false(button.matches(':disabled'));
+}, 'Tests that inert elements do not match the :disabled selector.');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/inert/inert-iframe-hittest.html b/testing/web-platform/tests/inert/inert-iframe-hittest.html
new file mode 100644
index 0000000000..d6c3551e99
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-iframe-hittest.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Hit-testing with inert iframe</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#inert">
+<meta assert="assert" content="Contents of an inert iframe can't be reached by hit-testing">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<style>
+body {
+ padding: 5px;
+}
+</style>
+<div id="wrapper" style="width: min-content">
+ <iframe id="iframe" inert></iframe>
+</div>
+
+<script>
+const events = [
+ "mousedown", "mouseenter", "mousemove", "mouseover",
+ "pointerdown", "pointerenter", "pointermove", "pointerover",
+];
+const iframe = document.getElementById("iframe");
+let iframeDoc;
+let target;
+
+promise_setup(async () => {
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, {once: true});
+ iframe.srcdoc = `
+ <style>#target { position: fixed; inset: 0 }</style>
+ <a id="target" href="#">target</a>
+ `;
+ });
+ iframeDoc = iframe.contentDocument;
+ target = iframeDoc.getElementById("target");
+ target.addEventListener("click", e => {
+ e.preventDefault();
+ });
+});
+
+async function mouseDownAndGetEvents(test) {
+ await new test_driver.Actions()
+ .pointerMove(1, 1, { origin: document.body })
+ .send();
+
+ const receivedEvents = {
+ target: [],
+ wrapper: [],
+ };
+ for (let event of events) {
+ target.addEventListener(event, () => {
+ receivedEvents.target.push(event);
+ }, { once: true, capture: true });
+ wrapper.addEventListener(event, () => {
+ receivedEvents.wrapper.push(event);
+ }, { once: true, capture: true });
+ }
+
+ await new test_driver.Actions()
+ .pointerMove(0, 0, { origin: wrapper })
+ .pointerDown()
+ .send();
+ test.add_cleanup(() => test_driver.click(document.body));
+
+ // Exact order of events is not interoperable.
+ receivedEvents.target.sort();
+ receivedEvents.wrapper.sort();
+ return receivedEvents;
+}
+
+promise_test(async function() {
+ const receivedEvents = await mouseDownAndGetEvents(this);
+ assert_array_equals(receivedEvents.target, [], "target got no event");
+ assert_array_equals(receivedEvents.wrapper, events, "wrapper got all events");
+
+ assert_false(target.matches(":focus"), "target is not focused");
+ assert_false(target.matches(":active"), "target is not active");
+ assert_false(target.matches(":hover"), "target is not hovered");
+ assert_true(wrapper.matches(":hover"), "wrapper is hovered");
+}, "Hit-testing doesn't reach contents of an inert iframe");
+
+promise_test(async function() {
+ iframe.inert = false;
+
+ const receivedEvents = await mouseDownAndGetEvents(this);
+ assert_array_equals(receivedEvents.target, events, "target got all events");
+ if (receivedEvents.wrapper.length === 2) {
+ // Firefox is unstable, sometimes missing the mouse events.
+ assert_array_equals(
+ receivedEvents.wrapper,
+ ["pointerenter", "pointerover"],
+ "wrapper got enter and over pointer events");
+ } else {
+ assert_array_equals(
+ receivedEvents.wrapper,
+ ["mouseenter", "mouseover", "pointerenter", "pointerover"],
+ "wrapper got enter and over events");
+ }
+
+ assert_true(target.matches(":active"), "target is active");
+ assert_true(target.matches(":hover"), "target is hovered");
+ assert_true(wrapper.matches(":hover"), "wrapper is hovered");
+}, "Hit-testing can reach contents of a no longer inert iframe");
+</script>
diff --git a/testing/web-platform/tests/inert/inert-iframe-tabbing.html b/testing/web-platform/tests/inert/inert-iframe-tabbing.html
new file mode 100644
index 0000000000..a0146b74cc
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-iframe-tabbing.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tabbing with inert iframe</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#inert">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation">
+<meta assert="assert" content="Tabbing can't enter an inert iframe from the outside, but can move within it and can leave it if focus is already there.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<div id="before" tabindex="0">before</div>
+<div id="inert" inert>
+ <iframe id="iframe"></iframe>
+</div>
+<div id="after" tabindex="0">after</a>
+
+<script>
+const tabKey = "\uE004";
+const before = document.getElementById("before");
+const inert = document.getElementById("inert");
+const after = document.getElementById("after");
+const iframe = document.getElementById("iframe");
+let iframeDoc;
+let start;
+let end;
+
+promise_setup(async () => {
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ iframe.srcdoc = `
+ <div id="start" tabindex="0">target</div>
+ <div id="end" tabindex="0">target</div>
+ `;
+ });
+ iframeDoc = iframe.contentDocument;
+ start = iframeDoc.getElementById("start");
+ end = iframeDoc.getElementById("end");
+});
+
+promise_test(async function() {
+ before.focus();
+ assert_equals(document.activeElement, before, "#before got outer focus");
+ assert_false(iframeDoc.hasFocus(), "iframeDoc doesn't have focus");
+
+ await test_driver.send_keys(document.activeElement, tabKey);
+ assert_equals(document.activeElement, after, "#after got outer focus");
+ assert_false(iframeDoc.hasFocus(), "iframeDoc still doesn't have focus");
+}, "Sequential navigation can't enter an inert iframe");
+
+promise_test(async function() {
+ start.focus();
+ assert_equals(document.activeElement, iframe, "#iframe got outer focus");
+ assert_equals(iframeDoc.activeElement, start, "#start got inner focus");
+ assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
+
+ await test_driver.send_keys(iframeDoc.activeElement, tabKey);
+ assert_equals(document.activeElement, iframe, "#iframe still has outer focus");
+ assert_equals(iframeDoc.activeElement, end, "#end got inner focus");
+ assert_true(iframeDoc.hasFocus(), "iframeDoc still has focus");
+}, "Sequential navigation can move within an inert iframe");
+
+promise_test(async function() {
+ end.focus();
+ assert_equals(document.activeElement, iframe, "#iframe got outer focus");
+ assert_equals(iframeDoc.activeElement, end, "#end got inner focus");
+ assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
+
+ await test_driver.send_keys(iframeDoc.activeElement, tabKey);
+ assert_equals(document.activeElement, after, "#after got outer focus");
+ assert_false(iframeDoc.hasFocus(), "iframeDoc doesn't have focus");
+}, "Sequential navigation can leave an inert iframe");
+
+// Test again without inertness.
+
+promise_test(async function() {
+ inert.inert = false;
+
+ before.focus();
+ assert_equals(document.activeElement, before, "#before got outer focus");
+ assert_false(iframeDoc.hasFocus(), "iframeDoc doesn't have focus");
+
+ await test_driver.send_keys(document.activeElement, tabKey);
+ assert_equals(document.activeElement, iframe, "#iframe got outer focus");
+ assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
+
+ // The document element is also focusable in Firefox.
+ if (iframeDoc.activeElement === iframeDoc.documentElement) {
+ await test_driver.send_keys(document.activeElement, tabKey);
+ assert_equals(document.activeElement, iframe, "#iframe got outer focus");
+ assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
+ }
+ assert_equals(iframeDoc.activeElement, start, "#start got inner focus");
+}, "Sequential navigation can enter a no longer inert iframe");
+
+promise_test(async function() {
+ inert.inert = false;
+
+ start.focus();
+ assert_equals(document.activeElement, iframe, "#iframe got outer focus");
+ assert_equals(iframeDoc.activeElement, start, "#start got inner focus");
+ assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
+
+ await test_driver.send_keys(iframeDoc.activeElement, tabKey);
+ assert_equals(document.activeElement, iframe, "#iframe still has outer focus");
+ assert_equals(iframeDoc.activeElement, end, "#end got inner focus");
+ assert_true(iframeDoc.hasFocus(), "iframeDoc still has focus");
+}, "Sequential navigation can move within a no longer inert iframe");
+
+promise_test(async function() {
+ inert.inert = false;
+
+ end.focus();
+ assert_equals(document.activeElement, iframe, "#iframe got outer focus");
+ assert_equals(iframeDoc.activeElement, end, "#end got inner focus");
+ assert_true(iframeDoc.hasFocus(), "iframeDoc has focus");
+
+ await test_driver.send_keys(iframeDoc.activeElement, tabKey);
+ assert_equals(document.activeElement, after, "#after got outer focus");
+ assert_false(iframeDoc.hasFocus(), "iframeDoc doesn't have focus");
+}, "Sequential navigation can leave a no longer inert iframe");
+</script>
diff --git a/testing/web-platform/tests/inert/inert-in-shadow-dom.html b/testing/web-platform/tests/inert/inert-in-shadow-dom.html
new file mode 100644
index 0000000000..ec825dbfdc
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-in-shadow-dom.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <title>inert on Shadow host affects content in shadow</title>
+ <link rel="author" title="Alice Boxhall" href="aboxhall@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div>Buttons 1 and 2 should be inert.</div>
+ <div id="shadow-host" inert>
+ <button id="button-1">Button 1 (inert)</button>
+ </div>
+ <script>
+ /*
+ Eventual flattened tree structure:
+
+ <div id="shadow-host" inert>
+ #shadow-root (open)
+ | <slot>
+ : <button id="button-1">Button 1 (inert)</button> <!-- slotted -->
+ | </slot>
+ | <button id="button-2">Button 2 (inert)</button> <!-- in shadow -->
+ </div>
+ */
+
+ const shadowHost = document.getElementById("shadow-host");
+ const shadowRoot = shadowHost.attachShadow({ mode: "open" });
+
+ // Button 1 will be slotted
+ const slot = document.createElement("slot");
+ shadowRoot.appendChild(slot);
+
+ const button2 = document.createElement("button");
+ button2.id = "button-2";
+ button2.textContent = "Button 2 (inert)";
+ shadowRoot.appendChild(button2);
+
+ function testCanFocus(selector, canFocus, opt_context) {
+ let context = opt_context || document;
+ const element = context.querySelector(selector);
+ let focusedElement = null;
+ element.addEventListener("focus", function() { focusedElement = element; }, false);
+ element.focus();
+ assert_equals((focusedElement === element), canFocus);
+ }
+
+ test(() => {
+ testCanFocus("#button-1", false);
+ testCanFocus("#button-2", false, shadowRoot);
+ }, "inert on Shadow host affects content in shadow");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/inert/inert-inlines-around-selection-range-in-contenteditable.html b/testing/web-platform/tests/inert/inert-inlines-around-selection-range-in-contenteditable.html
new file mode 100644
index 0000000000..c22c798084
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-inlines-around-selection-range-in-contenteditable.html
@@ -0,0 +1,190 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Delete editable range around elements having inert attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../editing/include/editor-test-utils.js"></script>
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ const editingHost = document.querySelector("div[contenteditable]");
+ const utils = new EditorTestUtils(editingHost);
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name}`
+ assert_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
+ );
+ }, "<span inert>a[bc</span><span>de]f</span>");
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name}`
+ assert_equals(
+ !!editingHost.querySelector("span[inert]"),
+ true,
+ `${desc}: <span inert> should not be deleted`
+ );
+ assert_equals(
+ editingHost.querySelector("span[inert]").textContent,
+ "def",
+ `${desc}: <span inert> content should not be deleted`
+ );
+ assert_not_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should be deleted (but how to handle it is not tested here)`
+ );
+ }, "<span>a[bc</span><span inert>de]f</span>");
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent-reverse" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name} (selection range direction is inverted)`
+ assert_equals(
+ !!editingHost.querySelector("span[inert]"),
+ true,
+ `${desc}: <span inert> should not be deleted`
+ );
+ assert_equals(
+ editingHost.querySelector("span[inert]").textContent,
+ "def",
+ `${desc}: <span inert> content should not be deleted`
+ );
+ assert_not_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should be deleted (but how to handle it is not tested here)`
+ );
+ }, "<span inert>d[ef</span><span>ab]c</span>");
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent-reverse" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name} (selection range direction is inverted)`
+ assert_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
+ );
+ }, "<span>d[ef</span><span inert>ab]c</span>");
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name}`
+ assert_equals(editingHost.innerHTML, "af", `${desc}: <span inert> should be deleted`);
+ }, "a[bc<span inert>XYZ</span>de]f");
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name}`
+ assert_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
+ );
+ }, `<span inert style="display:contents">a[bc</span><span>de]f</span>`);
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name}`
+ assert_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
+ );
+ }, `<span inert style="display:contents">{abc</span><span>de]f</span>`);
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name}`
+ assert_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
+ );
+ }, `<span inert><span style="display:contents">a[bc</span></span><span>de]f</span>`);
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name}`
+ assert_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
+ );
+ }, `<span inert><span style="display:contents">{abc</span></span><span>de]f</span>`);
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name}`
+ assert_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
+ );
+ }, `<span inert style="display:none">a[bc</span><span>de]f</span>`);
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name}`
+ assert_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
+ );
+ }, `<span inert style="display:none">{abc</span><span>de]f</span>`);
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name}`
+ assert_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
+ );
+ }, `<span inert><span style="display:none">a[bc</span></span><span>de]f</span>`);
+
+ test(t => {
+ utils.setupEditingHost(t.name, { selection: "setBaseAndExtent" });
+ const initialInnerHTML = editingHost.innerHTML;
+ document.execCommand("delete");
+ const desc = `execCommand("delete") at ${t.name}`
+ assert_equals(
+ editingHost.innerHTML,
+ initialInnerHTML,
+ `${desc}: <span> content should not be deleted because anchor node of Selection is in the <span inert>`
+ );
+ }, `<span inert><span style="display:none">{abc</span></span><span>de]f</span>`);
+});
+</script>
+</head>
+<body>
+<div contenteditable></div>
+</body>
+</html>
diff --git a/testing/web-platform/tests/inert/inert-inlines.html b/testing/web-platform/tests/inert/inert-inlines.html
new file mode 100644
index 0000000000..b056c6495d
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-inlines.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>inert inlines</title>
+ <link rel="author" title="Alice Boxhall" href="aboxhall@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ </head>
+<body>
+<a inert id="a" href="javascript:void(0)">Click me</a>
+<button inert id="button">Click me</button>
+<div inert id="div" style="background-color: blue; width: 50px; height: 50px">Click me</div>
+<span inert id="span">Click me</span>
+<script>
+function eventFiredOnInertElement(e) {
+ e.target.style.background = 'red';
+ inertElementFiredOn = true;
+}
+
+inertElements = ['a', 'button', 'div', 'span']
+inertElements.forEach(function(id) {
+ element = document.getElementById(id);
+ element.addEventListener('click', eventFiredOnInertElement);
+ element.addEventListener('mousemove', eventFiredOnInertElement);
+});
+
+document.addEventListener('click', function(e) {
+ document.firedOn = true;
+});
+
+promise_test(async () => {
+ for (let id of inertElements) {
+ var element = document.getElementById(id);
+ inertElementFiredOn = false;
+ document.firedOn = false;
+ try {
+ await test_driver.click(element);
+ assert_false(inertElementFiredOn, 'no event should be fired on ' + id);
+ assert_true(document.firedOn, 'event should be fired on document instead of ' + id);
+ } catch (e) {
+ // test driver detects inert elements as unclickable
+ // and throws an error
+ assert_false(inertElementFiredOn, 'no event should be fired on ' + id);
+ }
+ }
+}, 'Tests that inert inlines do not receive mouse events. ' +
+ 'To test manually, click on all the "Click me"s. The test ' +
+ 'fails if you see red.');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/inert/inert-label-focus.html b/testing/web-platform/tests/inert/inert-label-focus.html
new file mode 100644
index 0000000000..8bbe1eca15
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-label-focus.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>inert with label/for</title>
+ <link rel="author" title="Alice Boxhall" href="aboxhall@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ </head>
+ <body>
+ <label inert id="for-submit" for="submit">Label for Submit</label>
+ <input id="text" type="text">
+ <input id="submit" type="submit">
+
+ <label id="for-input-in-inert-subtree"
+ for="input-in-inert-subtree">Label for input in inert subtree</label>
+ <div inert>
+ <input id="input-in-inert-subtree"></input>
+ </div>
+
+ <script>
+ test(() => {
+ label = document.querySelector('#for-submit');
+ label.focus();
+ assert_equals(document.activeElement, document.querySelector('#submit'));
+ }, 'Calling focus() on an inert label should still send focus to its target.');
+
+ promise_test(async () => {
+ text = document.querySelector('#text');
+ text.focus();
+ label = document.querySelector('#for-submit');
+ try {
+ await test_driver.click(label);
+ assert_equals(document.activeElement, document.body);
+ } catch (e) {
+ // test driver detects inert elements as unclickable
+ // and throws an error
+ }
+ }, 'Clicking on an inert label should send focus to document.body.');
+
+ test(() => {
+ text = document.querySelector('#text');
+ text.focus();
+
+ label = document.querySelector('#for-input-in-inert-subtree');
+ label.focus();
+ assert_equals(document.activeElement, text);
+ }, 'Calling focus() on a label for a control which is in an inert subtree ' +
+ 'should have no effect.');
+</script>
+</html>
diff --git a/testing/web-platform/tests/inert/inert-node-is-uneditable.html b/testing/web-platform/tests/inert/inert-node-is-uneditable.html
new file mode 100644
index 0000000000..983831a89f
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-node-is-uneditable.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>inert nodes are uneditable</title>
+ <link rel="author" title="Alice Boxhall" href="aboxhall@chromium.org">
+ <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>
+ </head>
+<body>
+<span inert id="not-editable" contenteditable>I'm not editable.</span>
+<span id="editable" contenteditable>I'm editable.</span>
+<script>
+var notEditable = document.querySelector('#not-editable');
+var editable = document.querySelector('#editable');
+
+function sendKey(key) {
+ return new test_driver.Actions()
+ .keyDown(key)
+ .keyUp(key)
+ .send();
+}
+
+promise_test(async function() {
+ notEditable.focus();
+ var oldValue = notEditable.textContent;
+ assert_equals(oldValue, "I'm not editable.");
+ await sendKey('a');
+ assert_equals(notEditable.textContent, oldValue);
+}, "Can't edit inert contenteditable");
+
+promise_test(async () => {
+ editable.focus();
+ var oldValue = editable.textContent;
+ assert_equals(oldValue, "I'm editable.");
+ await sendKey('a');
+ assert_not_equals(editable.textContent, oldValue);
+}, "Can edit non-inert contenteditable");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/inert/inert-node-is-unfocusable.html b/testing/web-platform/tests/inert/inert-node-is-unfocusable.html
new file mode 100644
index 0000000000..8b5de37fdc
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-node-is-unfocusable.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>inert nodes are unfocusable</title>
+ <link rel="author" title="Alice Boxhall" href="aboxhall@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+<body id="body" tabindex="1">
+ <button id="focusable">Outside of inert container</button>
+ <button inert id="inert">Inert button</button>
+ <div inert id="container">
+ <input id="text" type="text">
+ <input id="datetime" type="datetime">
+ <input id="color" type="color">
+ <select id="select">
+ <optgroup id="optgroup">
+ <option id="option">Option</option>
+ </optgroup>
+ </select>
+ <div id="contenteditable-div" contenteditable>I'm editable</div>
+ <span id="tabindex-span" tabindex="0">I'm tabindexed.</div>
+ <embed id="embed" type="application/x-blink-test-plugin" width=100 height=100></embed>
+ <a id="anchor" href="">Link</a>
+ </div>
+<script>
+function testFocus(element, expectFocus) {
+ focusedElement = null;
+ element.addEventListener('focus', function() { focusedElement = element; }, false);
+ element.focus();
+ theElement = element;
+ assert_equals(focusedElement === theElement, expectFocus);
+}
+
+function testTree(element, expectFocus, excludeCurrent) {
+ if (element.nodeType == Node.ELEMENT_NODE && !excludeCurrent)
+ testFocus(element, expectFocus);
+ if (element.tagName === "SELECT")
+ return;
+ var childNodes = element.childNodes;
+ for (var i = 0; i < childNodes.length; i++)
+ testTree(childNodes[i], expectFocus);
+}
+
+
+test(function() {
+ testFocus(document.getElementById('focusable'), true);
+}, "Button outside of inert container is focusable.");
+
+test(function() {
+ testFocus(document.getElementById('inert'), false);
+}, "Button with inert atribute is unfocusable.");
+
+test(function() {
+ testTree(document.getElementById('container'), false);
+}, "All focusable elements inside inert subtree are unfocusable");
+
+test(function() {
+ assert_false(document.getElementById("focusable").inert, "Inert not set explicitly is false")
+ assert_true(document.getElementById("inert").inert, "Inert set explicitly is true");
+ assert_true(document.getElementById("container").inert, "Inert set on container is true");
+}, "Can get inert via property");
+
+test(function() {
+ assert_false(document.getElementById("text").inert, "Elements inside of inert subtrees return false when getting inert");
+}, "Elements inside of inert subtrees return false when getting 'inert'");
+
+test(function() {
+ document.getElementById('focusable').inert = true;
+ testFocus(document.getElementById('focusable'), false);
+ document.getElementById('inert').inert = false;
+ testFocus(document.getElementById('inert'), true);
+ document.getElementById('container').inert = false;
+ testTree(document.getElementById('container'), true, true);
+}, "Setting inert via property correctly modifies inert state");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/inert/inert-node-is-unselectable.html b/testing/web-platform/tests/inert/inert-node-is-unselectable.html
new file mode 100644
index 0000000000..b99af0d4cd
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-node-is-unselectable.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>inert nodes are unselectable</title>
+ <link rel="author" title="Alice Boxhall" href="aboxhall@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+<body>
+ <div inert>Here is a text node you can't select.</div>
+ <div>I'm selectable.</div>
+<script>
+test(function() {
+ document.execCommand('SelectAll');
+ assert_equals(window.getSelection().toString().trim(), "I'm selectable.");
+}, "Inert nodes cannot be selected.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/inert/inert-on-non-html.html b/testing/web-platform/tests/inert/inert-on-non-html.html
new file mode 100644
index 0000000000..4d4fdd7059
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-on-non-html.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>'inert' is an HTML attribute and has no effect when used on other elements</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<style>
+#tests {
+ line-height: 1.5em;
+}
+#tests svg {
+ height: 1.5em;
+ vertical-align: middle;
+}
+#tests svg > text {
+ transform: translateY(50%);
+ dominant-baseline: central;
+}
+#tests foreignObject {
+ height: 100%;
+ width: 100%;
+}
+</style>
+<div id="log"></div>
+<ul id="tests">
+ <!-- The 'inert' attribute only works on HTML elements -->
+ <li>
+ <span>non-inert</span>
+ </li>
+ <li>
+ <span inert>inert</span>
+ </li>
+ <li>
+ <foo>non-inert</foo>
+ </li>
+ <li>
+ <foo inert>inert</foo>
+ </li>
+ <li>
+ <foo-bar>non-inert</foo-bar>
+ </li>
+ <li>
+ <foo-bar inert>inert</foo-bar>
+ </li>
+ <li>
+ <math><mi>non-inert</mi></math>
+ </li>
+ <li>
+ <math inert><mi>non-inert</mi></math>
+ </li>
+ <li>
+ <math><mi inert>non-inert</mi></math>
+ </li>
+ <li>
+ <svg><text>non-inert</text></svg>
+ </li>
+ <li>
+ <svg inert><text>non-inert</text></svg>
+ </li>
+ <li>
+ <svg><text inert>non-inert</text></svg>
+ </li>
+
+ <!-- But non-HTML are inert if an HTML ancestor has the attribute -->
+ <li>
+ <span inert><span>inert</span></span>
+ </li>
+ <li>
+ <span inert><foo>inert</foo></span>
+ </li>
+ <li>
+ <span inert><foo-bar>inert</foo-bar></span>
+ </li>
+ <li>
+ <span inert><math><mi>inert</mi></math></span>
+ </li>
+ <li>
+ <span inert><svg><text>inert</text></svg></span>
+ </li>
+
+ <!-- HTML elements are not inert if an non-HTML ancestor has the attribute -->
+ <li>
+ <span data-move>non-inert</span>
+ <math inert><mi data-destination></mi></math>
+ </li>
+ <li>
+ <span data-move>non-inert</span>
+ <math><mi inert data-destination></mi></math>
+ </li>
+ <li>
+ <svg inert><foreignObject><span>non-inert</span></foreignObject></svg>
+ </li>
+ <li>
+ <svg><foreignObject inert><span>non-inert</span></foreignObject></svg>
+ </li>
+
+ <!-- HTML elements with non-HTML ancestors are inert if they have the attribute themselves -->
+ <li>
+ <span inert data-move>inert</span>
+ <math><mi data-destination></mi></math>
+ </li>
+ <li>
+ <foo inert data-move>inert</foo>
+ <math><mi data-destination></mi></math>
+ </li>
+ <li>
+ <foo-bar inert data-move>inert</foo-bar>
+ <math><mi data-destination></mi></math>
+ </li>
+ <li>
+ <svg><foreignObject><span inert>inert</span></foreignObject></svg>
+ </li>
+ <li>
+ <svg><foreignObject><foo inert>inert</foo></foreignObject></svg>
+ </li>
+ <li>
+ <svg><foreignObject><foo-bar inert>inert</foo-bar></foreignObject></svg>
+ </li>
+</ul>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+for (let li of document.querySelectorAll("#tests > li")) {
+ // The HTML parser would mess certain trees, fixup here.
+ const move = li.querySelector("[data-move]");
+ if (move) {
+ const destination = li.querySelector("[data-destination]");
+ destination.append(move);
+ move.removeAttribute("data-move");
+ destination.removeAttribute("data-destination");
+ }
+ test(() => {
+ assert_equals(li.childElementCount, 1);
+ const element = li.firstElementChild;
+ const selection = getSelection();
+ selection.selectAllChildren(element);
+ const selectionText = selection.toString().trim();
+ const textContent = element.textContent.trim();
+ if (textContent === "inert") {
+ assert_equals(selectionText, "");
+ } else {
+ assert_equals(selectionText, "non-inert");
+ }
+ }, li.innerHTML.trim());
+}
+</script>
diff --git a/testing/web-platform/tests/inert/inert-on-slots.html b/testing/web-platform/tests/inert/inert-on-slots.html
new file mode 100644
index 0000000000..dd0d7ec6d4
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-on-slots.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <title>inert inside ShadowRoot affects slotted content</title>
+ <link rel="author" title="Alice Boxhall" href="aboxhall@chromium.org">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div>Button 1 should be inert, and Button 2 should not be inert.</div>
+ <div id="shadow-host">
+ <button slot="slot-1" id="button-1">Button 1 (inert)</button>
+ <button slot="slot-2" id="button-2">Button 2 (not inert)</button>
+ </div>
+ <script>
+ /*
+ Eventual flattened tree structure:
+
+ <div id="shadow-host">
+ #shadow-root (open)
+ | <slot id="slot-1" inert>
+ : <button id="button-1">Button 1</button> <!-- slotted -->
+ | </slot>
+ | <slot id="slot-2">
+ : <button id="button-2">Button 2</button> <!-- slotted -->
+ | </slot>
+ </div>
+ */
+
+ const shadowHost = document.getElementById("shadow-host");
+ const shadowRoot = shadowHost.attachShadow({ mode: "open" });
+
+ const slot1 = document.createElement("slot");
+ slot1.name = "slot-1";
+ slot1.inert = true;
+ shadowRoot.appendChild(slot1);
+
+ const slot2 = document.createElement("slot");
+ slot2.name = "slot-2";
+ shadowRoot.appendChild(slot2);
+
+ function testCanFocus(selector, canFocus) {
+ const element = document.querySelector(selector);
+ let focusedElement = null;
+ element.addEventListener("focus", function() { focusedElement = element; }, false);
+ element.focus();
+ assert_equals((focusedElement === element), canFocus);
+ }
+
+ test(() => {
+ testCanFocus("#button-1", false);
+ testCanFocus("#button-2", true);
+ }, "inert inside ShadowRoot affects slotted content");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/inert/inert-pseudo-element-hittest.html b/testing/web-platform/tests/inert/inert-pseudo-element-hittest.html
new file mode 100644
index 0000000000..8268f02aa2
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-pseudo-element-hittest.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Hit-testing on pseudo elements of inert nodes</title>
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<style>
+#target::before {
+ content: "";
+ width: 50px;
+ height: 50px;
+ background-color: green;
+ display: inline-block;
+}
+
+#target:hover::before,
+#target:active::before {
+ background-color: red;
+}
+</style>
+<p>Manual test: hover the green square, pass if it does not turn red.</p>
+<div id="target" inert></div>
+<script>
+const events = [
+ "mousedown", "mouseenter", "mousemove", "mouseover",
+ "pointerdown", "pointerenter", "pointermove", "pointerover",
+];
+async function mouseDownAndGetEvents(test) {
+ const receivedEvents = [];
+ for (let event of events) {
+ target.addEventListener(event, () => {
+ receivedEvents.push(event);
+ }, { once: true, capture: true });
+ }
+
+ let targetRect = target.getBoundingClientRect();
+ await new test_driver.Actions()
+ .pointerMove(targetRect.x + 1, targetRect.y + 1, { origin: "viewport" })
+ .pointerDown()
+ .send();
+ test.add_cleanup(() => test_driver.click(document.body));
+
+ // Exact order of events is not interoperable.
+ receivedEvents.sort();
+ return receivedEvents;
+}
+promise_test(async function() {
+ const receivedEvents = await mouseDownAndGetEvents(this);
+ assert_array_equals(receivedEvents, [], "target got no event");
+ assert_false(target.matches(":active"), "target is not active");
+ assert_false(target.matches(":hover"), "target is not hovered");
+ assert_equals(getComputedStyle(target, "::before").backgroundColor, "rgb(0, 128, 0)", "#target::before has no hover style");
+}, "Hit-testing cannot reach pseudo elements of inert nodes");
+
+promise_test(async function() {
+ target.inert = false;
+ const receivedEvents = await mouseDownAndGetEvents(this);
+ assert_array_equals(receivedEvents, events, "target got all events");
+ assert_true(target.matches(":active"), "target is active");
+ assert_true(target.matches(":hover"), "target is hovered");
+ assert_equals(getComputedStyle(target, "::before").backgroundColor, "rgb(255, 0, 0)", "#target::before has hover style");
+}, "Hit-testing can reach pseudo elements of non-inert nodes");
+</script>
diff --git a/testing/web-platform/tests/inert/inert-svg-hittest.html b/testing/web-platform/tests/inert/inert-svg-hittest.html
new file mode 100644
index 0000000000..f743ed25f4
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-svg-hittest.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Hit-testing with inert SVG</title>
+<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#inert">
+<meta assert="assert" content="SVG inside element with inert attribute should be unreachable with hit-testing">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+
+<div id="wrapper">
+ <div inert id="svg-container">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
+ <rect width="500" height="500" id="target" fill="red">
+ </svg>
+ </div>
+</div>
+
+<script>
+const wrapper = document.getElementById("wrapper");
+const target = document.getElementById("target");
+
+promise_test(async function() {
+ let reachedTarget = false;
+ target.addEventListener("mousedown", () => {
+ reachedTarget = true;
+ }, { once: true });
+
+ let reachedWrapper = false;
+ wrapper.addEventListener("mousedown", () => {
+ reachedWrapper = true;
+ }, { once: true });
+
+ await new test_driver.Actions()
+ .pointerMove(0, 0, { origin: wrapper })
+ .pointerDown()
+ .send();
+ this.add_cleanup(() => test_driver.click(document.body));
+
+ assert_false(target.matches(":active"), "target is not active");
+ assert_false(target.matches(":hover"), "target is not hovered");
+ assert_false(reachedTarget, "target didn't get event");
+
+ assert_true(wrapper.matches(":hover"), "wrapper is hovered");
+ assert_true(reachedWrapper, "wrapper got event");
+}, "Hit-testing doesn't reach contents of an inert SVG");
+
+promise_test(async function() {
+ document.querySelector("#svg-container").inert = false;
+
+ let reachedTarget = false;
+ target.addEventListener("mousedown", () => {
+ reachedTarget = true;
+ }, { once: true });
+
+ await new test_driver.Actions()
+ .pointerMove(0, 0, { origin: wrapper })
+ .pointerDown()
+ .send();
+ this.add_cleanup(() => test_driver.click(document.body));
+
+ assert_true(target.matches(":active"), "target is active");
+ assert_true(target.matches(":hover"), "target is hovered");
+ assert_true(reachedTarget, "target got event");
+
+ assert_true(wrapper.matches(":hover"), "wrapper is hovered");
+}, "Hit-testing can reach contents of a no longer inert SVG");
+</script>
diff --git a/testing/web-platform/tests/inert/inert-with-modal-dialog-001.html b/testing/web-platform/tests/inert/inert-with-modal-dialog-001.html
new file mode 100644
index 0000000000..aa0c29c733
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-with-modal-dialog-001.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Interaction of 'inert' attribute with modal dialog</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#inert">
+<meta name="assert" content="Checks that a modal dialog escapes inertness from ancestors.">
+<div id="log"></div>
+<div id="wrapper">
+ wrapper
+ <dialog id="dialog">
+ dialog
+ <span id="child">
+ child
+ </span>
+ </dialog>
+</div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup(() => {
+ dialog.showModal();
+ add_completion_callback(() => {
+ dialog.close();
+ getSelection().removeAllRanges();
+ });
+});
+
+function checkSelection(expectedText) {
+ const selection = getSelection();
+ selection.selectAllChildren(document.documentElement);
+ const actualText = selection.toString().trim();
+ assert_equals(actualText, expectedText);
+}
+
+test(function() {
+ checkSelection("dialog child");
+}, "Modal dialog only marks outside nodes as inert");
+
+test(function() {
+ child.inert = true;
+ this.add_cleanup(() => { child.inert = false; });
+ checkSelection("dialog");
+}, "Inner nodes with 'inert' attribute become inert anyways");
+
+test(function() {
+ dialog.inert = true;
+ this.add_cleanup(() => { dialog.inert = false; });
+ checkSelection("");
+}, "If the modal dialog has the 'inert' attribute, everything becomes inert");
+
+test(function() {
+ wrapper.inert = true;
+ this.add_cleanup(() => { wrapper.inert = false; });
+ checkSelection("dialog child");
+}, "If an ancestor of the dialog has the 'inert' attribute, the dialog escapes inertness");
+</script>
diff --git a/testing/web-platform/tests/inert/inert-with-modal-dialog-002.html b/testing/web-platform/tests/inert/inert-with-modal-dialog-002.html
new file mode 100644
index 0000000000..499ac80d09
--- /dev/null
+++ b/testing/web-platform/tests/inert/inert-with-modal-dialog-002.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Interaction of 'inert' attribute with modal dialog, when the dialog is the root element</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#inert">
+<meta name="assert" content="Checks that being part of a modal dialog does not protect a node from being marked inert by an 'inert' attribute.">
+<div id="log"></div>
+<dialog id="dialog">
+ dialog
+ <span id="child">
+ child
+ </span>
+</dialog>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const dialog = document.getElementById("dialog");
+const root = document.documentElement;
+
+setup(() => {
+ root.remove();
+ document.append(dialog);
+ dialog.showModal();
+ add_completion_callback(() => {
+ getSelection().removeAllRanges();
+ });
+});
+
+function checkSelection(expectedText) {
+ const selection = getSelection();
+ selection.selectAllChildren(document.documentElement);
+ const actualText = selection.toString().trim();
+ assert_equals(actualText, expectedText);
+}
+
+test(function() {
+ checkSelection("dialog child");
+}, "Modal dialog only marks outside nodes as inert");
+
+test(function() {
+ child.inert = true;
+ this.add_cleanup(() => { child.inert = false; });
+ checkSelection("dialog");
+}, "Inner nodes with 'inert' attribute become inert anyways");
+
+test(function() {
+ dialog.inert = true;
+ this.add_cleanup(() => { dialog.inert = false; });
+ checkSelection("");
+}, "If the modal dialog has the 'inert' attribute, everything becomes inert");
+
+// Ideally this would happen in a completion callback, but then it would
+// be too late: the results would have been shown inside the dialog.
+dialog.remove();
+document.append(root);
+</script>