summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/semantics/interactive-elements/the-details-element')
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-ax-slot-recalc-crash.html28
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-details-element-fragment.html29
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-window-find-crash.html18
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/closed-details-layout-apis.tentative.html28
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary-ref.html5
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary.html25
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-cq-crash.html16
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-findstring-crash.html15
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-keyboard-activation.html52
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details.html47
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html9
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/modified-details-crash.html31
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html469
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-details-crash.html26
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-top-layer-elements-in-details-crash.html17
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/support/empty-html-document.html2
-rw-r--r--testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/toggleEvent.html183
17 files changed, 1000 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-ax-slot-recalc-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-ax-slot-recalc-crash.html
new file mode 100644
index 0000000000..0ecd30dda3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-ax-slot-recalc-crash.html
@@ -0,0 +1,28 @@
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1255406">
+<script type="text/javascript">
+var nodes = Array();
+var text = Array();
+ {
+ nodes[9] = document.createElement('textarea');
+ nodes[11] = document.createElement('legend');
+ nodes[44] = document.createElement('details');
+ document.documentElement.appendChild(nodes[44]);
+ nodes[68] = document.createElement('fieldset');
+ nodes[81] = document.createElement('option');
+
+
+
+ nodes[85] = document.createElement('img');
+ text[42] = document.createTextNode('744879385');
+ nodes[44].appendChild(text[42]);
+ nodes[68].appendChild(nodes[11]);
+ nodes[44].appendChild(nodes[68]);
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ try { nodes[85].appendChild(nodes[68]); } catch(e) {}
+ });
+ });
+ nodes[44].appendChild(nodes[9]);
+ requestAnimationFrame(() => { document.execCommand("SelectAll", false, ""); });
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-details-element-fragment.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-details-element-fragment.html
new file mode 100644
index 0000000000..d3d04f07a1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-details-element-fragment.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+<link rel="help" href="https://github.com/whatwg/html/pull/6466">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div style="height:2000px">spacer</div>
+
+<details id=details>
+ <div id=target>target</div>
+</details>
+
+<script>
+async_test(t => {
+ assert_false(details.hasAttribute('open'),
+ `The <details> should be closed at the start of the test.`);
+ assert_equals(window.pageYOffset, 0,
+ `The page should be scrolled to the top at the start of the test.`);
+
+ window.location.hash = '#target';
+
+ requestAnimationFrame(t.step_func_done(() => {
+ assert_true(details.hasAttribute('open'),
+ `<details> should be opened by navigating to an element inside it.`);
+ assert_not_equals(window.pageYOffset, 0,
+ `The page should be scrolled down to the <details> element.`);
+ }));
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-window-find-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-window-find-crash.html
new file mode 100644
index 0000000000..d24b4634cd
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/auto-expand-window-find-crash.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1264704">
+
+<script>
+function runTest() {
+ details2.appendChild(child);
+ document.caretRangeFromPoint();
+}
+</script>
+
+<body onload=runTest()>
+
+<details style="writing-mode: vertical-rl">
+ <div id=child>foo</div>
+</details>
+
+<details id=details2 open=true ontoggle="window.find()">
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/closed-details-layout-apis.tentative.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/closed-details-layout-apis.tentative.html
new file mode 100644
index 0000000000..1936cdb67d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/closed-details-layout-apis.tentative.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/6466">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<details id=details>
+ <div style="width:100px; height:100px; background-color:red" id=innerdiv></div>
+</details>
+
+<script>
+test(() => {
+ assert_not_equals(innerdiv.getBoundingClientRect().x, 0, 'x before open');
+ assert_not_equals(innerdiv.getBoundingClientRect().y, 0, 'y before open');
+ assert_not_equals(innerdiv.getBoundingClientRect().width, 0, 'width before open');
+ assert_not_equals(innerdiv.getBoundingClientRect().height, 0, 'height before open');
+ details.open = true;
+ assert_not_equals(innerdiv.getBoundingClientRect().x, 0, 'x after open');
+ assert_not_equals(innerdiv.getBoundingClientRect().y, 0, 'y after open');
+ assert_not_equals(innerdiv.getBoundingClientRect().width, 0, 'width after open');
+ assert_not_equals(innerdiv.getBoundingClientRect().height, 0, 'height after open');
+ details.open = false;
+ assert_not_equals(innerdiv.getBoundingClientRect().x, 0, 'x after close');
+ assert_not_equals(innerdiv.getBoundingClientRect().y, 0, 'y after close');
+ assert_not_equals(innerdiv.getBoundingClientRect().width, 0, 'width after close');
+ assert_not_equals(innerdiv.getBoundingClientRect().height, 0, 'height after close');
+}, `Verifies the layout results of elements inside a closed <details> based on the usage of content-visibility:hidden.`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary-ref.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary-ref.html
new file mode 100644
index 0000000000..14f2be232f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<details>
+ <summary>new summary</summary>
+ details
+</details>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary.html
new file mode 100644
index 0000000000..1b0062e43a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-add-summary.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel=match href="details-add-summary-ref.html">
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://github.com/whatwg/html/pull/6466">
+
+<!-- This test makes sure that new <summary> elements get rendered correctly
+ when added to a <details> element. I ran into it when adding
+ content-visibility:hidden to the second slot of <details>. -->
+
+<script>
+onload = () => {
+ const newsummary = document.createElement('summary');
+ newsummary.textContent = 'new summary';
+ document.getElementById('detailsid').insertBefore(newsummary,
+ document.getElementById('oldsummary'));
+
+ document.documentElement.classList.remove('reftest-wait');
+};
+</script>
+
+<details id=detailsid>
+ <summary id=oldsummary>old summary</summary>
+ details
+</details>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-cq-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-cq-crash.html
new file mode 100644
index 0000000000..393e464c4c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-cq-crash.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1334983">
+
+<canvas>
+ <details>
+ <card card>
+
+<script>
+async function trigger() {
+ document.querySelector("canvas").style.setProperty("container-type", "size");
+ document.querySelector("canvas").style.setProperty("column-span", "all");
+ document.querySelector("card").setAttribute("contenteditable", "true");
+}
+onload = requestAnimationFrame(() => requestAnimationFrame(trigger));
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-findstring-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-findstring-crash.html
new file mode 100644
index 0000000000..dc8686b216
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-findstring-crash.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1264507">
+
+<script>
+window.onload = () => {
+ window.getSelection().selectAllChildren(document.body);
+ document.querySelector('object').remove();
+ document.execCommand('FindString',false,0);
+};
+</script>
+
+<details>
+ <object id='id6'></object>
+</details>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-keyboard-activation.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-keyboard-activation.html
new file mode 100644
index 0000000000..a5534e24d1
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details-keyboard-activation.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Details activation with space bar</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1726454">
+<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>
+<style>
+:root {
+ scroll-behavior: instant;
+}
+.spacer {
+ height: 200vh;
+}
+</style>
+<details>
+ <summary>Activate me with the <kbd>Space</kbd> key</summary>
+ <p>Summary</p>
+</details>
+<div class="spacer"></div>
+<script>
+function tick() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+}
+
+promise_test(async t => {
+ const details = document.querySelector("details");
+ const summary = details.querySelector("summary");
+
+ summary.focus();
+ assert_equals(document.activeElement, summary, "Summary should be focusable");
+ assert_false(details.open, "Details should be closed");
+
+ const oldScrollY = window.scrollY;
+ assert_equals(oldScrollY, 0, "Should be at top");
+
+ window.addEventListener("scroll", t.unreached_func("Unexpected scroll event"));
+
+ await test_driver.send_keys(summary, " ");
+
+ assert_true(details.open, "Space bar on summary should open details");
+ assert_equals(window.scrollY, oldScrollY, "Scroll position shouldn't change");
+
+ await tick();
+
+ assert_equals(window.scrollY, oldScrollY, "Scroll position shouldn't change");
+});
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details.html
new file mode 100644
index 0000000000..5ed14c53af
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/details.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<html>
+ <head>
+ <title>HTML details element API</title>
+ <style>#one, #two { visibility: hidden; }</style>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+
+ <!-- Used in parsing tests -->
+ <div id='one'><details></details><details></details></div>
+ <div id='two'><p><details></details></div>
+
+ <script type="text/javascript">
+
+function makeDetails () {
+ return document.createElement('details');
+}
+
+
+// <details>
+test(function () {
+ var times = document.getElementById('one').getElementsByTagName('details');
+ assert_equals( times.length, 2 );
+}, 'HTML parsing should locate 2 details elements in this document');
+
+test(function () {
+ assert_equals( document.getElementById('two').getElementsByTagName('p')[0].innerHTML, '' );
+}, 'HTML parsing should close an unclosed <p> before <details>');
+
+test(function () {
+ assert_true( !!window.HTMLDetailsElement );
+}, 'HTMLDetailsElement should be exposed for prototyping');
+
+test(function () {
+ assert_true( makeDetails() instanceof window.HTMLDetailsElement);
+}, 'a dynamically created details element should be instanceof HTMLDetailsElement');
+
+test(function () {
+ assert_true( document.getElementById('one').getElementsByTagName('details')[0] instanceof window.HTMLDetailsElement);
+}, 'a details element from the parser should be instanceof HTMLDetailsElement');
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html
new file mode 100644
index 0000000000..80812cccb5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/display-table-with-rt-crash.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=969619">
+<details open="open" style="display:table;"><rt></rt></details>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ test(()=> { }, "No crash");
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/modified-details-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/modified-details-crash.html
new file mode 100644
index 0000000000..35ddca1fa6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/modified-details-crash.html
@@ -0,0 +1,31 @@
+<!doctype HTML>
+<link rel=author href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://crbug.com/1276488">
+
+<style>
+.first + .second {}
+</style>
+
+<script>
+const details = document.createElement('details');
+const ol = document.createElement('ol');
+const text = document.createTextNode('abcdefghijklmnopqrstuvxyz');
+
+function step3() {
+ ol.setAttribute('class', 'first');
+}
+
+function step2() {
+ details.appendChild(text);
+ requestAnimationFrame(step3);
+}
+
+function runTest() {
+ document.documentElement.appendChild(details);
+ details.appendChild(ol);
+
+ requestAnimationFrame(step2);
+}
+
+onload = runTest;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html
new file mode 100644
index 0000000000..2685546e9b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/name-attribute.html
@@ -0,0 +1,469 @@
+<!DOCTYPE HTML>
+<meta charset=UTF-8>
+<title>Test for the name attribute creating exclusive accordions from details elements</title>
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Google" href="http://www.google.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-details-element">
+<link rel="help" href="https://open-ui.org/components/accordion.explainer">
+<link rel="help" href="https://github.com/openui/open-ui/issues/725">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1444057">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="container">
+</div>
+
+<script>
+
+function assert_element_states(elements, expectations, description) {
+ assert_array_equals(elements.map(e => Number(e.open)), expectations, description);
+}
+
+let container = document.getElementById("container");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a">
+ <summary>1</summary>
+ This is the first item.
+ </details>
+
+ <details name="a">
+ <summary>2</summary>
+ This is the second item.
+ </details>
+ `;
+ let first = container.firstElementChild;
+ let second = first.nextElementSibling;
+ assert_false(first.open);
+ assert_false(second.open);
+ first.open = true;
+ assert_true(first.open);
+ assert_false(second.open);
+ second.open = true;
+ assert_false(first.open);
+ assert_true(second.open);
+ second.open = true;
+ assert_false(first.open);
+ assert_true(second.open);
+ second.open = false;
+ assert_false(first.open);
+ assert_false(second.open);
+}, "basic handling of mutually exclusive details");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a" open>
+ <summary>1</summary>
+ This is the first item.
+ </details>
+
+ <details name="a">
+ <summary>2</summary>
+ This is the second item.
+ </details>
+
+ <details name="a" open>
+ <summary>3</summary>
+ This is the third item.
+ </details>
+ `;
+ let first = container.firstElementChild;
+ let second = first.nextElementSibling;
+ let third = second.nextElementSibling;
+ function assert_states(expected_first, expected_second, expected_third, description) {
+ assert_array_equals([first.open, second.open, third.open], [expected_first, expected_second, expected_third], description);
+ }
+
+ assert_states(true, false, false, "initial states from open attribute");
+ first.open = true;
+ assert_states(true, false, false, "non-mutation doesn't change state");
+ second.open = true;
+ assert_states(false, true, false, "mutation closes multiple open elements");
+ third.setAttribute("open", "");
+ assert_states(false, false, true, "setAttribute closes other open element");
+}, "more complex handling of mutually exclusive details");
+
+promise_test(async t => {
+ let details_elements_string = `
+ <details name="a"></details>
+ <details name="a" open></details>
+ <details name="b"></details>
+ <details name="b"></details>
+ `;
+ container.innerHTML = `
+ ${details_elements_string}
+ <div id="shadow_host"></div>
+ `;
+ let shadow_root = document.getElementById("shadow_host").attachShadow({ mode: "open" });
+ shadow_root.innerHTML = details_elements_string;
+ let elements = Array.from(container.querySelectorAll("details")).concat(Array.from(shadow_root.querySelectorAll("details")));
+
+ assert_element_states(elements, [0, 1, 0, 0, 0, 1, 0, 0], "initial states from open attribute");
+ elements[4].open = true;
+ assert_element_states(elements, [0, 1, 0, 0, 1, 0, 0, 0], "after mutation in shadow tree");
+ for (let i = 0; i < 8; ++i) {
+ elements[i].open = true;
+ }
+ assert_element_states(elements, [0, 1, 0, 1, 0, 1, 0, 1], "after setting all elements open");
+ elements[0].open = true;
+ assert_element_states(elements, [1, 0, 0, 1, 0, 1, 0, 1], "after final mutation");
+}, "mutually exclusive details across multiple names and multiple tree scopes");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a" id="e0" open></details>
+ <details name="a" id="e1"></details>
+ <details name="a" id="e3" open></details>
+ `;
+ let e2 = document.createElement("details");
+ e2.id = "e2";
+ e2.name = "a";
+ e2.open = true;
+ let elements = [ document.getElementById("e0"),
+ document.getElementById("e1"),
+ e2,
+ document.getElementById("e3") ];
+ container.insertBefore(e2, elements[3]);
+
+ let mutation_event_received_ids = [];
+ let mutation_listener = event => {
+ assert_equals(event.type, "DOMSubtreeModified");
+ assert_equals(event.target.nodeType, Node.ELEMENT_NODE);
+ let element = event.target;
+ assert_equals(element.localName, "details");
+ mutation_event_received_ids.push(element.id);
+ };
+ let toggle_event_received_ids = [];
+ let toggle_event_promises = [];
+ for (let element of elements) {
+ element.addEventListener("DOMSubtreeModified", mutation_listener);
+ toggle_event_promises.push(new Promise((resolve, reject) => {
+ element.addEventListener("toggle", event => {
+ assert_equals(event.type, "toggle");
+ assert_equals(event.target, element);
+ toggle_event_received_ids.push(element.id);
+ resolve(undefined);
+ });
+ }));
+ }
+ assert_array_equals(mutation_event_received_ids, []);
+ assert_element_states(elements, [1, 0, 0, 0], "states before mutation");
+ elements[1].open = true;
+ if (mutation_event_received_ids.length == 0) {
+ // ok if mutation events are not supported
+ } else {
+ assert_array_equals(mutation_event_received_ids, ["e1"],
+ "mutation events received only for open attribute mutation and not for closing other element");
+ }
+ assert_element_states(elements, [0, 1, 0, 0], "states after mutation");
+ assert_array_equals(toggle_event_received_ids, [], "toggle events received before awaiting promises");
+ await Promise.all(toggle_event_promises);
+ assert_array_equals(toggle_event_received_ids, ["e3", "e2", "e1", "e0"], "toggle events received after awaiting promises, including toggle events from parser insertion");
+}, "mutation event and toggle event order");
+
+// This function is used to guard tests that test behavior that is
+// relevant only because of Mutation Events. If mutation events (for
+// attribute addition/removal) are removed from the web, the tests using
+// this function can be removed.
+function mutation_events_for_attribute_removal_supported() {
+ if (!("MutationEvent" in window)) {
+ return false;
+ }
+ container.innerHTML = `<div id="event-removal-test"></div>`;
+ let element = container.firstChild;
+ let event_fired = false;
+ element.addEventListener("DOMSubtreeModified", event => event_fired = true);
+ element.removeAttribute("id");
+ return event_fired;
+}
+
+promise_test(async t => {
+ if (!mutation_events_for_attribute_removal_supported()) {
+ return;
+ }
+ container.innerHTML = `
+ <details name="a" id="e0" open></details>
+ <details name="a" id="e1"></details>
+ <details name="a" id="e2" open></details>
+ `;
+ let elements = [ document.getElementById("e0"),
+ document.getElementById("e1"),
+ document.getElementById("e2") ];
+
+ let received_ids = [];
+ let listener = event => {
+ received_ids.push(event.target.id);
+ let i = 0;
+ for (let element of elements) {
+ element.setAttribute("name", `b${i++}`);
+ }
+ };
+ for (let element of elements) {
+ element.addEventListener("DOMSubtreeModified", listener);
+ }
+ assert_array_equals(received_ids, []);
+ assert_element_states(elements, [1, 0, 0], "states before mutation");
+ elements[1].open = true;
+ assert_array_equals(received_ids, ["e1"],
+ "mutation events received only for open attribute mutation and not for closing other element");
+ assert_element_states(elements, [0, 1, 0], "states after mutation");
+}, "interaction of open attribute changes with mutation events");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details></details>
+ <details></details>
+ <details name></details>
+ <details name></details>
+ <details name=""></details>
+ <details name=""></details>
+ `;
+ let elements = Array.from(container.querySelectorAll("details"));
+
+ assert_element_states(elements, [0, 0, 0, 0, 0, 0], "initial states from open attribute");
+ for (let i = 0; i < 6; ++i) {
+ elements[i].open = true;
+ }
+ assert_element_states(elements, [1, 1, 1, 1, 1, 1], "after setting all elements open");
+}, "empty and missing name attributes do not create groups");
+
+const connected_scenarios = {
+ "connected": {
+ "create": data => container,
+ "cleanup": data => {},
+ },
+ "disconnected": {
+ "create": data => document.createElement("div"),
+ "cleanup": data => {},
+ },
+ "shadow": {
+ "create": data => {
+ let e = document.createElement("div");
+ container.appendChild(e);
+ data.wrapper = e;
+ let shadowRoot = e.attachShadow({ mode: "open" });
+ let d = document.createElement("div");
+ shadowRoot.appendChild(d);
+ return d;
+ },
+ "cleanup": data => { data.wrapper.remove(); },
+ },
+ "shadow-in-disconnected": {
+ "create": data => {
+ let e = document.createElement("div");
+ let shadowRoot = e.attachShadow({ mode: "open" });
+ let d = document.createElement("div");
+ shadowRoot.appendChild(d);
+ return d;
+ },
+ "cleanup": data => {},
+ },
+ "template-in-disconnected": {
+ "create": data => {
+ let e = document.createElement("div");
+ e.innerHTML = `
+ <template>
+ <div></div>
+ </template>
+ `;
+ return e.firstElementChild.content.firstElementChild;
+ },
+ "cleanup": data => {},
+ },
+ "connected-in-xhr-response": {
+ "create": data => new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "support/empty-html-document.html");
+ xhr.responseType = "document";
+ xhr.send();
+ xhr.addEventListener("load", event => { resolve(xhr.response.body); });
+ let reject_with_type =
+ event => { reject(`${event.type} event received`); }
+ xhr.addEventListener("error", reject_with_type);
+ xhr.addEventListener("abort", reject_with_type);
+ }),
+ "cleanup": data => {},
+ },
+ "connected-in-implementation-create-document": {
+ "create": data => {
+ let doc = document.implementation.createHTMLDocument("impl-created");
+ return doc.body;
+ },
+ "cleanup": data => {},
+ },
+ "connected-in-template": {
+ "create": data => {
+ container.innerHTML = `
+ <template>
+ <div></div>
+ </template>
+ `;
+ return container.firstElementChild.content.firstElementChild;
+ },
+ "cleanup": data => { container.innerHTML = ""; },
+ },
+};
+
+for (const [scenario, scenario_callbacks] of Object.entries(connected_scenarios)) {
+ promise_test(async t => {
+ let data = {};
+ let container = await scenario_callbacks.create(data);
+ t.add_cleanup(async () => await scenario_callbacks.cleanup(data));
+ assert_true(container instanceof HTMLDivElement ||
+ container instanceof HTMLBodyElement,
+ "error in test setup");
+
+ container.innerHTML = `
+ <details name="scenariotest" open></details>
+ <details name="scenariotest"></details>
+ `;
+
+ let elements = Array.from(container.querySelectorAll("details[name='scenariotest']"));
+ assert_element_states(elements, [1, 0], "state before toggle");
+ elements[1].open = true;
+ assert_element_states(elements, [0, 1], "state after toggle enforces exclusivity");
+ }, `exclusivity enforcement with attachment scenario ${scenario}`);
+}
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a" id="e0" open></details>
+ <details name="a" id="e1"></details>
+ <details name="b" id="e2" open></details>
+ `;
+ let elements = [ document.getElementById("e0"),
+ document.getElementById("e1"),
+ document.getElementById("e2") ];
+
+ let mutation_received_ids = [];
+ let listener = event => {
+ mutation_received_ids.push(event.target.id);
+ };
+ for (let element of elements) {
+ element.addEventListener("DOMSubtreeModified", listener);
+ }
+
+ assert_element_states(elements, [1, 0, 1], "states before first mutation");
+ assert_array_equals(mutation_received_ids, [], "mutation events received before first mutation");
+ elements[2].name = "a";
+ assert_element_states(elements, [1, 0, 0], "states after first mutation");
+ if (mutation_received_ids.length != 0) {
+ // OK to not support mutation events, or to send DOMSubtreeModified
+ // only for attribute addition/removal (open) but not for attribute
+ // change (name)
+ assert_array_equals(mutation_received_ids, ["e2"], "mutation events received after first mutation");
+ }
+ elements[0].name = "c";
+ elements[2].open = true;
+ assert_element_states(elements, [1, 0, 1], "states before second mutation");
+ if (mutation_received_ids.length != 0) { // OK to not support mutation events
+ if (mutation_received_ids.length == 1) {
+ // OK to receive DOMSubtreeModified for attribute addition/removal
+ // (open) but not for attribute change (name)
+ assert_array_equals(mutation_received_ids, ["e2"], "mutation events received before second mutation");
+ } else {
+ assert_array_equals(mutation_received_ids, ["e2", "e0", "e2"], "mutation events received before second mutation");
+ }
+ }
+ elements[0].name = "a";
+ assert_element_states(elements, [0, 0, 1], "states after second mutation");
+ if (mutation_received_ids.length != 0) { // OK to not support mutation events
+ if (mutation_received_ids.length == 1) {
+ // OK to receive DOMSubtreeModified for attribute addition/removal
+ // (open) but not for attribute change (name)
+ assert_array_equals(mutation_received_ids, ["e2"], "mutation events received before second mutation");
+ } else {
+ assert_array_equals(mutation_received_ids, ["e2", "e0", "e2", "e0"], "mutation events received after second mutation");
+ }
+ }
+}, "handling of name attribute changes");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a" id="e0" open></details>
+ <details name="a" id="e1" open></details>
+ <details open name="a" id="e2"></details>
+ `;
+ let elements = [ document.getElementById("e0"),
+ document.getElementById("e1"),
+ document.getElementById("e2") ];
+
+ assert_element_states(elements, [1, 0, 0], "states after insertion by parser");
+}, "closing as a result of parsing doesn't depend on attribute order");
+
+promise_test(async t => {
+ container.innerHTML = `
+ <details name="a" id="e0" open></details>
+ <details name="a" id="e1"></details>
+ `;
+ let elements = [ document.getElementById("e0"),
+ document.getElementById("e1") ];
+
+ assert_element_states(elements, [1, 0], "states before first mutation");
+
+ let make_details = () => {
+ let e = document.createElement("details");
+ e.setAttribute("name", "a");
+ return e;
+ };
+
+ let watch_e0 = new EventWatcher(t, elements[0], ['toggle']);
+ let watch_e1 = new EventWatcher(t, elements[1], ['toggle']);
+
+ let expect_opening = async (watcher) => {
+ await watcher.wait_for(['toggle'], {record: 'all'}).then((events) => {
+ assert_equals(events[0].oldState, "closed");
+ assert_equals(events[0].newState, "open");
+ });
+ };
+
+ let expect_closing = async (watcher) => {
+ await watcher.wait_for(['toggle'], {record: 'all'}).then((events) => {
+ assert_equals(events[0].oldState, "open");
+ assert_equals(events[0].newState, "closed");
+ });
+ };
+
+ let track_mutations = (element) => {
+ let result = { count: 0 };
+ let listener = event => {
+ ++result.count;
+ };
+ element.addEventListener("DOMSubtreeModified", listener);
+ return result;
+ }
+
+ await expect_opening(watch_e0);
+
+ // Test appending an open element in the group.
+ let new1 = make_details();
+ let mutations1 = track_mutations(new1);
+ let watch_new1 = new EventWatcher(t, new1, ['toggle']);
+ new1.open = true;
+ assert_in_array(mutations1.count, [0, 1], "mutation events count before inserting new1");
+ await expect_opening(watch_new1);
+ container.appendChild(new1);
+ await expect_closing(watch_new1);
+ assert_in_array(mutations1.count, [0, 1], "mutation events count after inserting new1");
+
+ // Test appending a closed element in the group.
+ let new2 = make_details();
+ let mutations2 = track_mutations(new2);
+ let watch_new2 = new EventWatcher(t, new2, ['toggle']);
+ container.appendChild(new2);
+ assert_equals(mutations2.count, 0, "mutation events count after inserting new2");
+
+ // Test inserting an open element at the start of the group.
+ let new3 = make_details();
+ let mutations3 = track_mutations(new3);
+ new3.open = true; // this time do this before creating the EventWatcher
+ let watch_new3 = new EventWatcher(t, new3, ['toggle']);
+ assert_in_array(mutations3.count, [0, 1], "mutation events count before inserting new3");
+ await expect_opening(watch_new3);
+ container.insertBefore(new3, elements[0]);
+ await expect_closing(watch_new3);
+ assert_in_array(mutations3.count, [0, 1], "mutation events count after inserting new3");
+}, "handling of insertion of elements into group");
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-details-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-details-crash.html
new file mode 100644
index 0000000000..f3e821a950
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-details-crash.html
@@ -0,0 +1,26 @@
+<!doctype HTML>
+<link rel=author href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://crbug.com/1270206">
+
+<script type="text/javascript">
+function eventHandler1() {
+ document.getElementById('target').insertAdjacentText("afterEnd", "");
+ document.getElementById('target').focus();
+ document.getElementById('target').hidden = "true";
+}
+function operate() {
+ document.addEventListener('DOMNodeInsertedIntoDocument', eventHandler1, true);
+}
+function exec_event() {
+ event = new Event('DOMNodeInsertedIntoDocument')
+ document.dispatchEvent(event)
+}
+function go(){
+ operate();
+ exec_event();
+}
+</script>
+<body onload="go();" contentEditable="true">
+<details onselectstart='eventHandler2();'>
+<dfn id='target' class=onload='eventHandler1();'>
+<details id= onmsgesturehold='eventHandler2();'>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-top-layer-elements-in-details-crash.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-top-layer-elements-in-details-crash.html
new file mode 100644
index 0000000000..c8d8ae4ed7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/nested-top-layer-elements-in-details-crash.html
@@ -0,0 +1,17 @@
+<!doctype HTML>
+<link rel=author href="mailto:vmpstr@chromium.org">
+<link rel=help href="https://crbug.com/1273395">
+
+<dialog id="parentElement">
+ <details id="childElement" open="true" ontoggle="toggleHandler()">
+ <dialog id="grandchildElement">
+ </dialog>
+ </details>
+</dialog>
+<script>
+function toggleHandler() {
+ grandchildElement.showModal();
+ parentElement.showModal();
+ childElement.open = false;
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/support/empty-html-document.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/support/empty-html-document.html
new file mode 100644
index 0000000000..56415b8476
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/support/empty-html-document.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<title>Empty HTML Document</title>
diff --git a/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/toggleEvent.html b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/toggleEvent.html
new file mode 100644
index 0000000000..c918f8eb62
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/interactive-elements/the-details-element/toggleEvent.html
@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>The details element</title>
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#the-details-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<details id=details1>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details2>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details3 style="display:none;">
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details4>
+</details>
+<details id=details6>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details7>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details8>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<script>
+ window.details9TogglePromise = new Promise(resolve => {
+ window.details9TogglePromiseResolver = resolve;
+ });
+</script>
+<details id=details9 ontoggle="window.details9TogglePromiseResolver()" open>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<details id=details10>
+ <summary>Lorem ipsum</summary>
+ <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+</details>
+<script>
+ var t1 = async_test("Adding open to 'details' should fire a toggle event at the 'details' element, with 'oldState: closed' and 'newState: open'"),
+ t2 = async_test("Adding open to 'details' and then removing open from that 'details' should fire only one toggle event at the 'details' element, with 'oldState: closed' and 'newState: closed'"),
+ t3 = async_test("Adding open to 'details' (display:none) should fire a toggle event at the 'details' element, with 'oldState: closed' and 'newState: open'"),
+ t4 = async_test("Adding open to 'details' (no children) should fire a toggle event at the 'details' element, with 'oldState: closed' and 'newState: open'"),
+ t6 = async_test("Adding open to 'details' and then removing open from that 'details' and then again adding open to that 'details' should fire only one toggle event at the 'details' element, with 'oldState: closed' and 'newState: closed'"),
+ t7 = async_test("Adding open to 'details' using setAttribute('open', '') should fire a toggle event at the 'details' element, with 'oldState: closed' and 'newState: open'"),
+ t8 = async_test("Adding open to 'details' and then calling removeAttribute('open') should fire only one toggle event at the 'details' element, with 'oldState: closed' and 'newState: closed'"),
+ t9 = async_test("Setting open=true on an opened 'details' element should not fire a toggle event at the 'details' element"),
+ t10 = async_test("Setting open=false on a closed 'details' element should not fire a toggle event at the 'details' element"),
+
+ details1 = document.getElementById('details1'),
+ details2 = document.getElementById('details2'),
+ details3 = document.getElementById('details3'),
+ details4 = document.getElementById('details4'),
+ details6 = document.getElementById('details6'),
+ details7 = document.getElementById('details7'),
+ details8 = document.getElementById('details8'),
+ details9 = document.getElementById('details9'),
+ details10 = document.getElementById('details10'),
+ loop=false;
+
+ function testEvent(evt) {
+ assert_true(evt.isTrusted, "event is trusted");
+ assert_false(evt.bubbles, "event doesn't bubble");
+ assert_false(evt.cancelable, "event is not cancelable");
+ assert_equals(Object.getPrototypeOf(evt), ToggleEvent.prototype, "Prototype of toggle event is ToggleEvent.prototype");
+ }
+
+ details1.ontoggle = t1.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "open");
+ assert_true(details1.open);
+ testEvent(evt)
+ });
+ details1.open = true; // opens details1
+
+ details2.ontoggle = t2.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "closed");
+ assert_false(details2.open);
+ testEvent(evt);
+ });
+ details2.open = true;
+ details2.open = false; // closes details2
+
+ details3.ontoggle = t3.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "open");
+ assert_true(details3.open);
+ testEvent(evt);
+ });
+ details3.open = true; // opens details3
+
+ details4.ontoggle = t4.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "open");
+ assert_true(details4.open);
+ testEvent(evt);
+ });
+ details4.open = true; // opens details4
+
+ async_test(function(t) {
+ var details5 = document.createElement("details");
+ details5.ontoggle = t.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "open");
+ assert_true(details5.open);
+ testEvent(evt);
+ })
+ details5.open = true;
+ }, "Adding open to 'details' (not in the document) should fire a toggle event at the 'details' element, with 'oldState: closed' and 'newState: open'");
+
+ details6.open = true;
+ details6.open = false;
+ details6.ontoggle = t6.step_func(function(evt) {
+ if (loop) {
+ assert_unreached("toggle event fired twice");
+ } else {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "closed");
+ loop = true;
+ }
+ });
+ t6.step_timeout(function() {
+ assert_true(loop);
+ t6.done();
+ }, 0);
+
+ details7.ontoggle = t7.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "open");
+ assert_true(details7.open);
+ testEvent(evt)
+ });
+ details7.setAttribute('open', ''); // opens details7
+
+ details8.ontoggle = t8.step_func_done(function(evt) {
+ assert_equals(evt.oldState, "closed");
+ assert_equals(evt.newState, "closed");
+ assert_false(details8.open);
+ testEvent(evt)
+ });
+ details8.open = true;
+ details8.removeAttribute('open'); // closes details8
+
+ window.details9TogglePromise.then(t9.step_func(() => {
+ // The toggle event should be fired once when declaring details9 with open
+ // attribute.
+ details9.ontoggle = t9.step_func(() => {
+ assert_unreached("toggle event fired twice on opened details element");
+ });
+ // setting open=true on details9 should not fire another event since it is
+ // already open.
+ details9.open = true;
+ t9.step_timeout(() => {
+ assert_true(details9.open);
+ t9.done();
+ });
+ }));
+
+ details10.ontoggle = t10.step_func_done(function(evt) {
+ assert_unreached("toggle event fired on closed details element");
+ });
+ details10.open = false; // closes details10
+ t10.step_timeout(function() {
+ assert_false(details10.open);
+ t10.done();
+ }, 0);
+
+ async_test(function(t) {
+ new DOMParser().parseFromString("<details open>", "text/html").querySelector("details").ontoggle = t.step_func_done(function(e) {
+ assert_true(e.target.open);
+ });
+ }, "Setting open from the parser fires a toggle event");
+</script>