summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/dom
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:37 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:37 +0000
commita90a5cba08fdf6c0ceb95101c275108a152a3aed (patch)
tree532507288f3defd7f4dcf1af49698bcb76034855 /testing/web-platform/tests/dom
parentAdding debian version 126.0.1-1. (diff)
downloadfirefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.tar.xz
firefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.zip
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/dom')
-rw-r--r--testing/web-platform/tests/dom/events/scrolling/WEB_FEATURES.yml4
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-meta-referrer-and-script-from-fragment.tentative.html54
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/Node-moveBefore.html297
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/chrome-338071841-crash.html6
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-animation-left.html51
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-animation-transform.html48
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-left-pseudo.html54
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-left.html41
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-transform-pseudo.html54
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-transform.html36
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-animation-commit-styles.html45
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-cross-document.html45
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-cross-shadow.html64
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-to-disconnected-document.html43
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-trigger.html43
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/focus-preserve.html85
-rw-r--r--testing/web-platform/tests/dom/nodes/moveBefore/tentative/fullscreen-preserve.html49
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-every.any.js250
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-filter.any.js12
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-find.any.js85
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-inspect.any.js412
-rw-r--r--testing/web-platform/tests/dom/observable/tentative/observable-some.any.js96
-rw-r--r--testing/web-platform/tests/dom/ranges/Range-isPointInRange-shadowdom.tentative.html75
-rw-r--r--testing/web-platform/tests/dom/xslt/resources/xml2html.xsl2
24 files changed, 1929 insertions, 22 deletions
diff --git a/testing/web-platform/tests/dom/events/scrolling/WEB_FEATURES.yml b/testing/web-platform/tests/dom/events/scrolling/WEB_FEATURES.yml
new file mode 100644
index 0000000000..e4fba841b6
--- /dev/null
+++ b/testing/web-platform/tests/dom/events/scrolling/WEB_FEATURES.yml
@@ -0,0 +1,4 @@
+features:
+- name: scrollend
+ files:
+ - scrollend-*
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-meta-referrer-and-script-from-fragment.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-meta-referrer-and-script-from-fragment.tentative.html
index d247797603..e80e3b45b6 100644
--- a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-meta-referrer-and-script-from-fragment.tentative.html
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-meta-referrer-and-script-from-fragment.tentative.html
@@ -5,25 +5,37 @@
<script src=/resources/testharnessreport.js></script>
<script>
promise_test(async t => {
- const script = document.createElement("script");
- const meta = document.createElement("meta");
- meta.name = "referrer";
- meta.content = "no-referrer";
- const fragment = new DocumentFragment();
- const done = new Promise(resolve => {
- window.didFetch = resolve;
- });
- script.textContent = `
- (async function() {
- const response = await fetch("/html/infrastructure/urls/terminology-0/resources/echo-referrer-text.py")
- const text = await response.text();
- window.didFetch(text);
- })();
- `;
- fragment.append(script, meta);
- document.head.append(fragment);
- const result = await done;
- assert_equals(result, "");
-}, "<meta name=referrer> should apply before script, as it is an insertion step " +
- "and not a post-insertion step");
+ const preMetaScript = document.createElement("script");
+ preMetaScript.textContent = `
+ window.preMetaScriptPromise = fetch('/html/infrastructure/urls/terminology-0/resources/echo-referrer-text.py')
+ .then(response => response.text());
+ `;
+
+ const meta = document.createElement("meta");
+ meta.name = "referrer";
+ meta.content = "no-referrer";
+
+ const postMetaScript = document.createElement("script");
+ postMetaScript.textContent = `
+ window.postMetaScriptPromise = fetch('/html/infrastructure/urls/terminology-0/resources/echo-referrer-text.py')
+ .then(response => response.text());
+ `;
+
+ const fragment = new DocumentFragment();
+ fragment.append(preMetaScript, meta, postMetaScript);
+ document.head.append(fragment);
+
+ const preMetaReferrer = await window.preMetaScriptPromise;
+ assert_equals(preMetaReferrer, location.href,
+ "preMetaReferrer is the full URL; by the time the first script runs in " +
+ "its post-insertion steps, the later-inserted meta tag has not run its " +
+ "post-insertion steps, which is where meta tags are processed");
+
+ const postMetaReferrer = await window.postMetaScriptPromise;
+ assert_equals(postMetaReferrer, "",
+ "postMetaReferrer is empty; by the time the second script runs in " +
+ "its post-insertion steps, the later-inserted meta tag has run its " +
+ "post-insertion steps, and observes the meta tag's effect");
+}, "<meta name=referrer> gets processed and applied in the post-insertion " +
+ "steps");
</script>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/Node-moveBefore.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/Node-moveBefore.html
new file mode 100644
index 0000000000..8a1db6f93b
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/Node-moveBefore.html
@@ -0,0 +1,297 @@
+<!DOCTYPE html>
+<title>Node.moveBefore</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<!-- First test shared pre-insertion checks that work similarly for replaceChild
+ and moveBefore -->
+<script>
+ var insertFunc = Node.prototype.moveBefore;
+</script>
+<script src="../../pre-insertion-validation-notfound.js"></script>
+<script src="../../pre-insertion-validation-hierarchy.js"></script>
+<script>
+preInsertionValidateHierarchy("moveBefore");
+
+function testLeafNode(nodeName, createNodeFunction) {
+ test(function() {
+ var node = createNodeFunction();
+ assert_throws_js(TypeError, function() { node.moveBefore(null, null) })
+ }, "Calling moveBefore with a non-Node first argument on a leaf node " + nodeName + " must throw TypeError.")
+ test(function() {
+ var node = createNodeFunction();
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { node.moveBefore(document.createTextNode("fail"), null) })
+ // Would be step 2.
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { node.moveBefore(node, null) })
+ // Would be step 3.
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { node.moveBefore(node, document.createTextNode("child")) })
+ }, "Calling moveBefore an a leaf node " + nodeName + " must throw HIERARCHY_REQUEST_ERR.")
+}
+
+test(function() {
+ // WebIDL: first argument.
+ assert_throws_js(TypeError, function() { document.body.moveBefore(null, null) })
+ assert_throws_js(TypeError, function() { document.body.moveBefore(null, document.body.firstChild) })
+ assert_throws_js(TypeError, function() { document.body.moveBefore({'a':'b'}, document.body.firstChild) })
+}, "Calling moveBefore with a non-Node first argument must throw TypeError.")
+
+test(function() {
+ // WebIDL: second argument.
+ assert_throws_js(TypeError, function() { document.body.moveBefore(document.createTextNode("child")) })
+ assert_throws_js(TypeError, function() { document.body.moveBefore(document.createTextNode("child"), {'a':'b'}) })
+}, "Calling moveBefore with second argument missing, or other than Node, null, or undefined, must throw TypeError.")
+
+testLeafNode("DocumentType", function () { return document.doctype; } )
+testLeafNode("Text", function () { return document.createTextNode("Foo") })
+testLeafNode("Comment", function () { return document.createComment("Foo") })
+testLeafNode("ProcessingInstruction", function () { return document.createProcessingInstruction("foo", "bar") })
+
+test(function() {
+ // Step 2.
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { document.body.moveBefore(document.body, document.getElementById("log")) })
+ assert_throws_dom("HIERARCHY_REQUEST_ERR", function() { document.body.moveBefore(document.documentElement, document.getElementById("log")) })
+}, "Calling moveBefore with an inclusive ancestor of the context object must throw HIERARCHY_REQUEST_ERR.")
+
+// Step 3.
+test(function() {
+ var a = document.createElement("div");
+ var b = document.createElement("div");
+ var c = document.createElement("div");
+ assert_throws_dom("NotFoundError", function() {
+ a.moveBefore(b, c);
+ });
+}, "Calling moveBefore with a reference child whose parent is not the context node must throw a NotFoundError.")
+
+// Step 4.1.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var doc2 = document.implementation.createHTMLDocument("title2");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(doc2, doc.documentElement);
+ });
+
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(doc.createTextNode("text"), doc.documentElement);
+ });
+}, "If the context node is a document, inserting a document or text node should throw a HierarchyRequestError.")
+
+// Step 4.2.1.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ doc.removeChild(doc.documentElement);
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ df.appendChild(doc.createElement("b"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(df, doc.firstChild);
+ });
+
+ df = doc.createDocumentFragment();
+ df.appendChild(doc.createTextNode("text"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(df, doc.firstChild);
+ });
+
+ df = doc.createDocumentFragment();
+ df.appendChild(doc.createComment("comment"));
+ df.appendChild(doc.createTextNode("text"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(df, doc.firstChild);
+ });
+}, "If the context node is a document, inserting a DocumentFragment that contains a text node or too many elements should throw a HierarchyRequestError.")
+
+// Step 4.2.2.
+test(function() {
+ // The context node has an element child.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.appendChild(doc.createComment("foo"));
+ assert_array_equals(doc.childNodes, [doc.doctype, doc.documentElement, comment]);
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(df, doc.doctype);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(df, doc.documentElement);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(df, comment);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(df, null);
+ });
+}, "If the context node is a document, inserting a DocumentFragment with an element if there already is an element child should throw a HierarchyRequestError.")
+test(function() {
+ // /child/ is a doctype.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.moveBefore(doc.createComment("foo"), doc.firstChild);
+ doc.removeChild(doc.documentElement);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype]);
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(df, doc.doctype);
+ });
+}, "If the context node is a document and a doctype is following the reference child, inserting a DocumentFragment with an element should throw a HierarchyRequestError.")
+test(function() {
+ // /child/ is not null and a doctype is following /child/.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.moveBefore(doc.createComment("foo"), doc.firstChild);
+ doc.removeChild(doc.documentElement);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype]);
+
+ var df = doc.createDocumentFragment();
+ df.appendChild(doc.createElement("a"));
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(df, comment);
+ });
+}, "If the context node is a document, inserting a DocumentFragment with an element before the doctype should throw a HierarchyRequestError.")
+
+// Step 4.3.
+test(function() {
+ // The context node has an element child.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.appendChild(doc.createComment("foo"));
+ assert_array_equals(doc.childNodes, [doc.doctype, doc.documentElement, comment]);
+
+ var a = doc.createElement("a");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(a, doc.doctype);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(a, doc.documentElement);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(a, comment);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(a, null);
+ });
+}, "If the context node is a document, inserting an element if there already is an element child should throw a HierarchyRequestError.")
+test(function() {
+ // /child/ is a doctype.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.moveBefore(doc.createComment("foo"), doc.firstChild);
+ doc.removeChild(doc.documentElement);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype]);
+
+ var a = doc.createElement("a");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(a, doc.doctype);
+ });
+}, "If the context node is a document, inserting an element before the doctype should throw a HierarchyRequestError.")
+test(function() {
+ // /child/ is not null and a doctype is following /child/.
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.moveBefore(doc.createComment("foo"), doc.firstChild);
+ doc.removeChild(doc.documentElement);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype]);
+
+ var a = doc.createElement("a");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(a, comment);
+ });
+}, "If the context node is a document and a doctype is following the reference child, inserting an element should throw a HierarchyRequestError.")
+
+// Step 4.4.
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.moveBefore(doc.createComment("foo"), doc.firstChild);
+ assert_array_equals(doc.childNodes, [comment, doc.doctype, doc.documentElement]);
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(doctype, comment);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(doctype, doc.doctype);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(doctype, doc.documentElement);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(doctype, null);
+ });
+}, "If the context node is a document, inserting a doctype if there already is a doctype child should throw a HierarchyRequestError.")
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.appendChild(doc.createComment("foo"));
+ doc.removeChild(doc.doctype);
+ assert_array_equals(doc.childNodes, [doc.documentElement, comment]);
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(doctype, comment);
+ });
+}, "If the context node is a document, inserting a doctype after the document element should throw a HierarchyRequestError.")
+test(function() {
+ var doc = document.implementation.createHTMLDocument("title");
+ var comment = doc.appendChild(doc.createComment("foo"));
+ doc.removeChild(doc.doctype);
+ assert_array_equals(doc.childNodes, [doc.documentElement, comment]);
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ doc.moveBefore(doctype, null);
+ });
+}, "If the context node is a document with and element child, appending a doctype should throw a HierarchyRequestError.")
+
+// Step 5.
+test(function() {
+ var df = document.createDocumentFragment();
+ var a = df.appendChild(document.createElement("a"));
+
+ var doc = document.implementation.createHTMLDocument("title");
+ assert_throws_dom("HierarchyRequestError", function() {
+ df.moveBefore(doc, a);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ df.moveBefore(doc, null);
+ });
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ df.moveBefore(doctype, a);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ df.moveBefore(doctype, null);
+ });
+}, "If the context node is a DocumentFragment, inserting a document or a doctype should throw a HierarchyRequestError.")
+test(function() {
+ var el = document.createElement("div");
+ var a = el.appendChild(document.createElement("a"));
+
+ var doc = document.implementation.createHTMLDocument("title");
+ assert_throws_dom("HierarchyRequestError", function() {
+ el.moveBefore(doc, a);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ el.moveBefore(doc, null);
+ });
+
+ var doctype = document.implementation.createDocumentType("html", "", "");
+ assert_throws_dom("HierarchyRequestError", function() {
+ el.moveBefore(doctype, a);
+ });
+ assert_throws_dom("HierarchyRequestError", function() {
+ el.moveBefore(doctype, null);
+ });
+}, "If the context node is an element, inserting a document or a doctype should throw a HierarchyRequestError.")
+
+// Step 7.
+test(function() {
+ var a = document.createElement("div");
+ var b = document.createElement("div");
+ var c = document.createElement("div");
+ a.appendChild(b);
+ a.appendChild(c);
+ assert_array_equals(a.childNodes, [b, c]);
+ assert_equals(a.moveBefore(b, b), b);
+ assert_array_equals(a.childNodes, [b, c]);
+ assert_equals(a.moveBefore(c, c), c);
+ assert_array_equals(a.childNodes, [b, c]);
+}, "Inserting a node before itself should not move the node");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/chrome-338071841-crash.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/chrome-338071841-crash.html
new file mode 100644
index 0000000000..26adfb1cbf
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/chrome-338071841-crash.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<link rel="help" href="https://crbug.com/338071841">
+<div id="p"><span></span><!-- comment --></div>
+<script>
+ p.moveBefore(p.lastChild, p.firstChild);
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-animation-left.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-animation-left.html
new file mode 100644
index 0000000000..8c7f73e3c9
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-animation-left.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Node.moveBefore should preserve CSS animation state (left)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+ <section id="old-parent">
+ <div id="item"></div>
+ </section>
+ <section id="new-parent">
+ </section>
+ <style>
+ @keyframes anim {
+ from {
+ left: 100px;
+ }
+
+ to {
+ left: 400px;
+ }
+ }
+
+ section {
+ position: absolute;
+ }
+
+ #item {
+ position: relative;
+ width: 100px;
+ height: 100px;
+ background: green;
+ animation: 1s linear infinite alternate anim;
+ animation-delay: 100ms;
+ }
+ </style>
+ <script>
+ promise_test(async t => {
+ const item = document.querySelector("#item");
+ let num_events = 0;
+ await new Promise(resolve => addEventListener("animationstart", () => {
+ num_events++;
+ resolve();
+ }));
+
+ // Reparent item
+ document.body.querySelector("#new-parent").moveBefore(item, null);
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
+ assert_equals(num_events, 1);
+ assert_not_equals(getComputedStyle(item).left, "0px");
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-animation-transform.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-animation-transform.html
new file mode 100644
index 0000000000..e7a285893a
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-animation-transform.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>Node.moveBefore should preserve CSS animation state (transform)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+ <section id="old-parent">
+ <div id="item"></div>
+ </section>
+ <section id="new-parent">
+ </section>
+ <style>
+ @keyframes anim {
+ from {
+ transform: translateX(100px);
+ }
+
+ to {
+ transform: translateX(400px);
+ }
+ }
+
+ #item {
+ position: relative;
+ width: 100px;
+ height: 100px;
+ background: green;
+ animation: 1s linear infinite alternate anim;
+ animation-delay: 100ms;
+ }
+ </style>
+ <script>
+ promise_test(async t => {
+ const item = document.querySelector("#item");
+ let num_events = 0;
+ await new Promise(resolve => addEventListener("animationstart", () => {
+ num_events++;
+ resolve();
+ }));
+
+ // Reparent item
+ document.body.querySelector("#new-parent").moveBefore(item, null);
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
+ assert_equals(num_events, 1);
+ assert_not_equals(getComputedStyle(item).transform, "none");
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-left-pseudo.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-left-pseudo.html
new file mode 100644
index 0000000000..fa51b16887
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-left-pseudo.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Node.moveBefore should preserve CSS transition state on pseudo-elements (left)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+ <section id="old-parent">
+ <div id="item"></div>
+ </section>
+ <section id="new-parent">
+ </section>
+ <style>
+ #item {
+ width: 100px;
+ height: 100px;
+ background: green;
+ position: absolute;
+ left: 0;
+ }
+
+ #item::before {
+ content: "Foo";
+ width: 100px;
+ height: 100px;
+ background: green;
+ transition: left 60s steps(1, jump-both);
+ left: 0px;
+ position: absolute;
+ }
+
+ #item.big::before {
+ left: 400px;
+ }
+
+ section {
+ position: relative;
+ }
+
+ body {
+ margin-left: 0;
+ }
+ </style>
+ <script>
+ promise_test(async t => {
+ const item = document.querySelector("#item");
+ assert_equals(item.getBoundingClientRect().x, 0);
+ item.classList.add("big");
+ await new Promise(resolve => item.addEventListener("transitionstart", resolve));
+ document.querySelector("#new-parent").moveBefore(item, null);
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
+ assert_equals(getComputedStyle(item, "::before").left, "200px");
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-left.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-left.html
new file mode 100644
index 0000000000..2b8e04b26e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-left.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>Node.moveBefore should preserve CSS transition state (left)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+ <section id="old-parent">
+ <div id="item"></div>
+ </section>
+ <section id="new-parent">
+ </section>
+ <style>
+ #item {
+ width: 100px;
+ height: 100px;
+ background: green;
+ transition: left 10s;
+ position: absolute;
+ left: 0;
+ }
+
+ section {
+ position: relative;
+ }
+
+ body {
+ margin-left: 0;
+ }
+ </style>
+ <script>
+ promise_test(async t => {
+ const item = document.querySelector("#item");
+ assert_equals(item.getBoundingClientRect().x, 0);
+ item.style.left = "400px";
+ await new Promise(resolve => item.addEventListener("transitionstart", resolve));
+ document.querySelector("#new-parent").moveBefore(item, null);
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
+ assert_less_than(item.getBoundingClientRect().x, 399);
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-transform-pseudo.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-transform-pseudo.html
new file mode 100644
index 0000000000..d02c72561c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-transform-pseudo.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Node.moveBefore should preserve CSS transition state on pseudo-elements (transform)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+ <section id="old-parent">
+ <div id="item"></div>
+ </section>
+ <section id="new-parent">
+ </section>
+ <style>
+ #item {
+ width: 100px;
+ height: 100px;
+ background: green;
+ position: absolute;
+ left: 0;
+ }
+
+ #item::before {
+ content: "Foo";
+ width: 100px;
+ height: 100px;
+ background: green;
+ transition: transform 60s steps(1, jump-both);
+ transform: none;
+ position: absolute;
+ }
+
+ #item.big::before {
+ transform: translateX(400px);
+ }
+
+ section {
+ position: relative;
+ }
+
+ body {
+ margin-left: 0;
+ }
+ </style>
+ <script>
+ promise_test(async t => {
+ const item = document.querySelector("#item");
+ assert_equals(item.getBoundingClientRect().x, 0);
+ item.classList.add("big");
+ await new Promise(resolve => item.addEventListener("transitionstart", resolve));
+ document.querySelector("#new-parent").moveBefore(item, null);
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
+ assert_not_equals(getComputedStyle(item, "::before").transform, "none");
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-transform.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-transform.html
new file mode 100644
index 0000000000..f09edca144
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/continue-css-transition-transform.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Node.moveBefore should preserve CSS transition state (transform)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+ <section id="old-parent">
+ <div id="item"></div>
+ </section>
+ <section id="new-parent">
+ </section>
+ <style>
+ #item {
+ width: 100px;
+ height: 100px;
+ background: green;
+ transition: transform 60s steps(1, jump-both);
+ }
+
+ body {
+ margin-left: 0;
+ }
+ </style>
+ <script>
+ promise_test(async t => {
+ const item = document.querySelector("#item");
+ assert_equals(item.getBoundingClientRect().x, 0);
+ item.style.transform = "translateX(400px)";
+ await new Promise(resolve => item.addEventListener("transitionstart", resolve));
+ document.querySelector("#new-parent").moveBefore(item, null);
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
+ assert_equals(item.getBoundingClientRect().x, 200);
+ assert_equals(item.getAnimations().length, 1);
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-animation-commit-styles.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-animation-commit-styles.html
new file mode 100644
index 0000000000..86bb7c33e4
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-animation-commit-styles.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Calling commitStyles after Node.moveBefore should commit mid-transition value</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+ <section id="old-parent">
+ <div id="item"></div>
+ </section>
+ <section id="new-parent">
+ </section>
+ <style>
+ @keyframes anim {
+ from {
+ transform: translateX(100px);
+ }
+
+ to {
+ transform: translateX(400px);
+ }
+ }
+
+ #item {
+ position: relative;
+ width: 100px;
+ height: 100px;
+ background: green;
+ animation: 1s linear infinite alternate anim;
+ animation-delay: 100ms;
+ }
+ </style>
+ <script>
+ promise_test(async t => {
+ const item = document.querySelector("#item");
+ await new Promise(resolve => item.addEventListener("animationstart", resolve));
+
+ // Reparent item
+ document.body.querySelector("#new-parent").moveBefore(item, null);
+
+ item.getAnimations()[0].commitStyles();
+ assert_true("transform" in item.style);
+ assert_not_equals(item.style.transform, "none");
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-cross-document.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-cross-document.html
new file mode 100644
index 0000000000..f3c8fafbfa
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-cross-document.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Node.moveBefore should not preserve CSS transition state when crossing document boundaries</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+ <iframe id="iframe">
+ </iframe>
+ <section id="new-parent">
+ </section>
+ <style id="style">
+ #item {
+ width: 100px;
+ height: 100px;
+ background: green;
+ transition: left 10s;
+ position: absolute;
+ left: 0;
+ }
+
+ section {
+ position: relative;
+ }
+
+ body {
+ margin-left: 0;
+ }
+ </style>
+ <script>
+ promise_test(async t => {
+ const iframe = document.querySelector("#iframe");
+ const style = document.querySelector("#style");
+ iframe.contentDocument.head.append(style.cloneNode(true));
+ const item = iframe.contentDocument.createElement("div");
+ item.id = "item";
+ iframe.contentDocument.body.append(item);
+ assert_equals(item.getBoundingClientRect().x, 0);
+ item.style.left = "400px";
+ await new Promise(resolve => item.addEventListener("transitionstart", resolve));
+ document.querySelector("#new-parent").moveBefore(item, null);
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
+ assert_greater_than(item.getBoundingClientRect().x, 399);
+ }, "Moving a transition across documents should reset its state");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-cross-shadow.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-cross-shadow.html
new file mode 100644
index 0000000000..145f40ba50
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-cross-shadow.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<title>Node.moveBefore should not preserve CSS transition state when crossing shadow boundaries</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+ <section id="old-parent">
+ <div id="item"></div>
+ </section>
+ <div id="shadow-container">
+ <template shadowrootmode="open">
+ <style>
+ #item {
+ width: 100px;
+ height: 100px;
+ background: green;
+ transition: left 10s;
+ position: absolute;
+ left: 0;
+ }
+
+ section {
+ position: relative;
+ }
+
+ body {
+ margin-left: 0;
+ }
+ </style>
+ <section id="new-parent">
+ </section>
+ </template>
+ </div>
+ <style>
+ #item {
+ width: 100px;
+ height: 100px;
+ background: green;
+ transition: left 10s;
+ position: absolute;
+ left: 0;
+ }
+
+ section {
+ position: relative;
+ }
+
+ body {
+ margin-left: 0;
+ }
+ </style>
+ <script>
+ promise_test(async t => {
+ const item = document.querySelector("#item");
+ assert_equals(item.getBoundingClientRect().x, 0);
+ item.style.left = "400px";
+ await new Promise(resolve => item.addEventListener("transitionstart", resolve));
+ const shadowContainer = document.querySelector("#shadow-container");
+ shadowContainer.shadowRoot.querySelector("#new-parent").moveBefore(item, null);
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
+ assert_greater_than(item.getBoundingClientRect().x, 399);
+ }, "Moving an element with a transition across shadow boundaries should reset the transition");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-to-disconnected-document.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-to-disconnected-document.html
new file mode 100644
index 0000000000..537edfe9b6
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-to-disconnected-document.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>Node.moveBefore should act like insertBefore when moving to a disconnected document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+ <section id="old-parent">
+ <div id="item"></div>
+ </section>
+ <section id="new-parent">
+ </section>
+ <style>
+ #item {
+ width: 100px;
+ height: 100px;
+ background: green;
+ transition: left 10s;
+ position: absolute;
+ left: 0;
+ }
+
+ section {
+ position: relative;
+ }
+
+ body {
+ margin-left: 0;
+ }
+ </style>
+
+ <script>
+ promise_test(async t => {
+ const item = document.querySelector("#item");
+ assert_equals(item.getBoundingClientRect().x, 0);
+ item.style.left = "400px";
+ await new Promise(resolve => item.addEventListener("transitionstart", resolve));
+ const doc = document.implementation.createHTMLDocument();
+ doc.body.moveBefore(item, null);
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
+ assert_equals(item.getBoundingClientRect().x, 0);
+ }, "Moving an element with a transition to a disconnected document should reset the transitionm state");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-trigger.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-trigger.html
new file mode 100644
index 0000000000..0cb5608a69
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/css-transition-trigger.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>Node.moveBefore should trigger CSS transition state (left) if needed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+ <section id="old-parent">
+ <div id="item"></div>
+ </section>
+ <section id="new-parent">
+ </section>
+ <style>
+ #item {
+ width: 100px;
+ height: 100px;
+ background: green;
+ transition: left 10s steps(1, jump-both);
+ position: absolute;
+ left: 0;
+ }
+
+ #new-parent #item {
+ left: 400px;
+ }
+
+ section {
+ position: relative;
+ }
+
+ body {
+ margin-left: 0;
+ }
+ </style>
+ <script>
+ promise_test(async t => {
+ const item = document.querySelector("#item");
+ assert_equals(item.getBoundingClientRect().x, 0);
+ document.querySelector("#new-parent").moveBefore(item, null);
+ await new Promise(resolve => item.addEventListener("transitionstart", resolve));
+ assert_equals(item.getBoundingClientRect().x, 200);
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/focus-preserve.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/focus-preserve.html
new file mode 100644
index 0000000000..a00e8b7788
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/focus-preserve.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<title>moveBefore should not automatically clear focus</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<section id="old_parent">
+<button id="button" tabindex="1">Button</button>
+</section>
+<section id="new_parent">
+</section>
+<section id="inert_parent" inert>
+</section>
+<section id="hidden_parent" hidden>
+</section>
+<script>
+
+function eventually_blurred(t, item, timeout = 1000) {
+ return new Promise((resolve, reject) => {
+ function onblur() {
+ resolve();
+ item.removeEventListener("blur", onblur);
+ }
+ item.addEventListener("blur", onblur);
+ t.step_timeout(reject, timeout);
+ });
+}
+
+test(t => {
+ const old_parent = document.querySelector("#old_parent");
+ const button = document.querySelector("#button");
+ t.add_cleanup(() => old_parent.append(button));
+ button.focus();
+ assert_equals(document.activeElement, button);
+ new_parent.moveBefore(button, null);
+ assert_equals(document.activeElement, button);
+}, "when reparenting an element, don't automatically reset the document focus");
+
+promise_test(async t => {
+ const old_parent = document.querySelector("#old_parent");
+ const button = document.querySelector("#button");
+ t.add_cleanup(() => old_parent.append(button));
+ const inert_parent = document.querySelector("#inert_parent");
+ button.focus();
+ assert_equals(document.activeElement, button);
+ inert_parent.moveBefore(button, null);
+
+ // The button will still be considered the active element. It will blur asynchronously.
+ assert_equals(document.activeElement, button);
+ await eventually_blurred(t, button);
+ assert_equals(document.activeElement, document.body);
+}, "when reparenting a focused element into an inert parent, reset the document focus");
+
+
+promise_test(async t => {
+ const old_parent = document.querySelector("#old_parent");
+ const button = document.querySelector("#button");
+ t.add_cleanup(() => old_parent.append(button));
+ const hidden_parent = document.querySelector("#hidden_parent");
+ button.focus();
+ assert_equals(document.activeElement, button);
+ hidden_parent.moveBefore(button, null);
+
+ // The button will still be considered the active element. It will blur asynchronously.
+ // This is similar to other operations that can cause a blur due to change in inert trees,
+ // e.g. a style change that makes an ancestor `display: none`.
+ assert_equals(document.activeElement, button);
+ await eventually_blurred(t, button);
+ assert_equals(document.activeElement, document.body);
+}, "when reparenting a focused element into a hidden parent, reset the document focus");
+
+promise_test(async t => {
+ const old_parent = document.querySelector("#old_parent");
+ const button = document.querySelector("#button");
+ t.add_cleanup(() => document.body.append(old_parent));
+ const hidden_parent = document.querySelector("#hidden_parent");
+ button.focus();
+ assert_equals(document.activeElement, button);
+ hidden_parent.moveBefore(old_parent, null);
+
+ // The button will still be considered the active element. It will blur asynchronously.
+ assert_equals(document.activeElement, button);
+ await eventually_blurred(t, button);
+ assert_equals(document.activeElement, document.body);
+}, "when reparenting an ancestor of an focused element into a hidden parent, reset the document focus");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/moveBefore/tentative/fullscreen-preserve.html b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/fullscreen-preserve.html
new file mode 100644
index 0000000000..810eeac9af
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/moveBefore/tentative/fullscreen-preserve.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>Document#fullscreenElement</title>
+<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="/fullscreen/trusted-click.js"></script>
+<section id="old_parent">
+ <div id="item"></div>
+</section>
+<section id="new_parent">
+ <div id="item"></div>
+</section>
+<script>
+ promise_test(async function (t) {
+ const item = document.querySelector("#item");
+
+ await trusted_click();
+
+ assert_equals(
+ document.fullscreenElement,
+ null,
+ "fullscreenElement before requestFullscreen()"
+ );
+
+ await item.requestFullscreen();
+ assert_equals(
+ document.fullscreenElement,
+ item,
+ "fullscreenElement before moveBefore()"
+ );
+
+ document.querySelector("#new_parent").moveBefore(item, null);
+
+ assert_equals(
+ document.fullscreenElement,
+ item,
+ "fullscreenElement after moveBefore()"
+ );
+
+ await Promise.all([document.exitFullscreen(), fullScreenChange()]);
+
+ assert_equals(
+ document.fullscreenElement,
+ null,
+ "fullscreenElement after exiting fullscreen"
+ );
+ });
+</script>
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-every.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-every.any.js
new file mode 100644
index 0000000000..74a344b8f7
--- /dev/null
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-every.any.js
@@ -0,0 +1,250 @@
+promise_test(async () => {
+ const source = new Observable(subscriber => {
+ subscriber.next("good");
+ subscriber.next("good");
+ subscriber.next("good");
+ subscriber.complete();
+ });
+
+ const result = await source.every((value) => value === "good");
+
+ assert_true(result, "Promise resolves with true if all values pass the predicate");
+}, "every(): Promise resolves to true if all values pass the predicate");
+
+promise_test(async () => {
+ const source = new Observable(subscriber => {
+ subscriber.next("good");
+ subscriber.next("good");
+ subscriber.next("bad");
+ subscriber.complete();
+ });
+
+ const result = await source.every((value) => value === "good");
+
+ assert_false(result, "Promise resolves with false if any value fails the predicate");
+}, "every(): Promise resolves to false if any value fails the predicate");
+
+promise_test(async () => {
+ let tornDown = false;
+ let subscriberActiveAfterFailingPredicate = true;
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => tornDown = true);
+ subscriber.next("good");
+ subscriber.next("good");
+ subscriber.next("bad");
+ subscriberActiveAfterFailingPredicate = subscriber.active;
+ subscriber.next("good");
+ subscriber.complete();
+ });
+
+ const result = await source.every((value) => value === "good");
+
+ assert_false(result, "Promise resolves with false if any value fails the predicate");
+ assert_false(subscriberActiveAfterFailingPredicate,
+ "Subscriber becomes inactive because every() unsubscribed");
+}, "every(): Abort the subscription to the source if the predicate does not pass");
+
+promise_test(async () => {
+ const logs = [];
+
+ const source = createTestSubject({
+ onSubscribe: () => logs.push("subscribed to source"),
+ onTeardown: () => logs.push("teardown"),
+ });
+
+ const resultPromise = source.every((value, index) => {
+ logs.push(`Predicate called with ${value}, ${index}`);
+ return true;
+ });
+
+ let promiseResolved = false;
+
+ resultPromise.then(() => promiseResolved = true);
+
+ assert_array_equals(logs, ["subscribed to source"],
+ "calling every() subscribes to the source immediately");
+
+ source.next("a");
+ assert_array_equals(logs, [
+ "subscribed to source",
+ "Predicate called with a, 0"
+ ], "Predicate called with the value and the index");
+
+ source.next("b");
+ assert_array_equals(logs, [
+ "subscribed to source",
+ "Predicate called with a, 0",
+ "Predicate called with b, 1",
+ ], "Predicate called with the value and the index");
+
+ // wait a tick, just to prove that you have to wait for complete to be called.
+ await Promise.resolve();
+
+ assert_false(promiseResolved,
+ "Promise should not resolve until after the source completes");
+
+ source.complete();
+ assert_array_equals(logs, [
+ "subscribed to source",
+ "Predicate called with a, 0",
+ "Predicate called with b, 1",
+ "teardown",
+ ], "Teardown function called immediately after the source completes");
+
+ const result = await resultPromise;
+
+ assert_true(result,
+ "Promise resolves with true if all values pass the predicate");
+}, "every(): Lifecycle checks when all values pass the predicate");
+
+promise_test(async () => {
+ const logs = [];
+
+ const source = createTestSubject({
+ onSubscribe: () => logs.push("subscribed to source"),
+ onTeardown: () => logs.push("teardown"),
+ });
+
+ const resultPromise = source.every((value, index) => {
+ logs.push(`Predicate called with ${value}, ${index}`);
+ return value === "good";
+ });
+
+ let promiseResolved = false;
+
+ resultPromise.then(() => promiseResolved = true);
+
+ assert_array_equals(logs, ["subscribed to source"],
+ "calling every() subscribes to the source immediately");
+
+ source.next("good");
+ source.next("good");
+ assert_array_equals(logs, [
+ "subscribed to source",
+ "Predicate called with good, 0",
+ "Predicate called with good, 1",
+ ], "Predicate called with the value and the index");
+
+ assert_false(promiseResolved, "Promise should not resolve until after the predicate fails");
+
+ source.next("bad");
+ assert_array_equals(logs, [
+ "subscribed to source",
+ "Predicate called with good, 0",
+ "Predicate called with good, 1",
+ "Predicate called with bad, 2",
+ "teardown",
+ ], "Predicate called with the value and the index, failing predicate immediately aborts subscription to source");
+
+ const result = await resultPromise;
+
+ assert_false(result, "Promise resolves with false if any value fails the predicate");
+}, "every(): Lifecycle checks when any value fails the predicate");
+
+promise_test(async () => {
+ const source = new Observable(subscriber => {
+ subscriber.complete();
+ });
+
+ const result = await source.every(() => true);
+
+ assert_true(result,
+ "Promise resolves with true if the observable completes without " +
+ "emitting a value");
+}, "every(): Resolves with true if the observable completes without " +
+ "emitting a value");
+
+promise_test(async t => {
+ const error = new Error("error from source");
+ const source = new Observable(subscriber => {
+ subscriber.error(error);
+ });
+
+ promise_rejects_exactly(t, error, source.every(() => true),
+ "Promise rejects with the error emitted from the source observable");
+}, "every(): Rejects with any error emitted from the source observable");
+
+promise_test(async t => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ const error = new Error("bad value");
+ const promise = source.every(value => {
+ if (value <= 2) return true;
+ throw error;
+ });
+
+ promise_rejects_exactly(t, error, promise, "Promise rejects with the " +
+ "error thrown from the predicate");
+}, "every(): Rejects with any error thrown from the predicate");
+
+promise_test(async () => {
+ const indices = [];
+
+ const source = new Observable(subscriber => {
+ subscriber.next("a");
+ subscriber.next("b");
+ subscriber.next("c");
+ subscriber.complete();
+ });
+
+ const value = await source.every((value, index) => {
+ indices.push(index);
+ return true;
+ });
+
+ assert_array_equals(indices, [0, 1, 2]);
+
+ assert_true(value,
+ "Promise resolves with true if all values pass the predicate");
+}, "every(): Index is passed into the predicate");
+
+promise_test(async t => {
+ const source = new Observable(subscriber => {});
+
+ const controller = new AbortController();
+ const promise = source.every(() => true, { signal: controller.signal });
+ controller.abort();
+
+ promise_rejects_dom(t, 'AbortError', promise, "Promise rejects with a " +
+ "DOMException if the source Observable is aborted");
+}, "every(): Rejects with a DOMException if the source Observable is aborted");
+
+function createTestSubject(options) {
+ const onTeardown = options?.onTeardown;
+
+ const subscribers = new Set();
+ const subject = new Observable(subscriber => {
+ options?.onSubscribe?.();
+ subscribers.add(subscriber);
+ subscriber.addTeardown(() => subscribers.delete(subscriber));
+ if (onTeardown) {
+ subscriber.addTeardown(onTeardown);
+ }
+ });
+
+ subject.next = (value) => {
+ for (const subscriber of Array.from(subscribers)) {
+ subscriber.next(value);
+ }
+ };
+ subject.error = (error) => {
+ for (const subscriber of Array.from(subscribers)) {
+ subscriber.error(error);
+ }
+ };
+ subject.complete = () => {
+ for (const subscriber of Array.from(subscribers)) {
+ subscriber.complete();
+ }
+ };
+ subject.subscriberCount = () => {
+ return subscribers.size;
+ };
+
+ return subject;
+}
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-filter.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-filter.any.js
index 8a49bcf467..3c1a7d7824 100644
--- a/testing/web-platform/tests/dom/observable/tentative/observable-filter.any.js
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-filter.any.js
@@ -103,3 +103,15 @@ test(() => {
['source teardown', 'source abort event', 'filter observable complete']);
}, "filter(): Upon source completion, source Observable teardown sequence " +
"happens after downstream filter complete() is called");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next('value1');
+ subscriber.next('value2');
+ subscriber.next('value3');
+ });
+
+ const indices = [];
+ source.filter((value, index) => indices.push(index)).subscribe();
+ assert_array_equals(indices, [0, 1, 2]);
+}, "filter(): Index is passed correctly to predicate");
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-find.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-find.any.js
new file mode 100644
index 0000000000..0e09060fc5
--- /dev/null
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-find.any.js
@@ -0,0 +1,85 @@
+promise_test(async () => {
+ let inactiveAfterB = false;
+ const source = new Observable(subscriber => {
+ subscriber.next("a");
+ subscriber.next("b");
+ inactiveAfterB = !subscriber.active;
+ subscriber.next("c");
+ subscriber.complete();
+ });
+
+ const value = await source.find((value) => value === "b");
+
+ assert_equals(value, "b", "Promise resolves with the first value that passes the predicate");
+
+ assert_true(inactiveAfterB, "subscriber is inactive after the first value that passes the predicate");
+}, "find(): Promise resolves with the first value that passes the predicate");
+
+promise_test(async () => {
+ const source = new Observable(subscriber => {
+ subscriber.next("a");
+ subscriber.next("b");
+ subscriber.next("c");
+ subscriber.complete();
+ });
+
+ const value = await source.find(() => false);
+
+ assert_equals(value, undefined, "Promise resolves with undefined if no value passes the predicate");
+}, "find(): Promise resolves with undefined if no value passes the predicate");
+
+promise_test(async t => {
+ const error = new Error("error from source");
+ const source = new Observable(subscriber => {
+ subscriber.error(error);
+ });
+
+ promise_rejects_exactly(t, error, source.find(() => true), "Promise " +
+ "rejects with the error emitted from the source Observable");
+}, "find(): Promise rejects with the error emitted from the source Observable");
+
+promise_test(async t => {
+ const source = new Observable(subscriber => {
+ subscriber.next("ignored");
+ });
+
+ const error = new Error("thrown from predicate");
+ promise_rejects_exactly(t, error, source.find(() => {throw error}),
+ "Promise rejects with any error thrown from the predicate");
+}, "find(): Promise rejects with any error thrown from the predicate");
+
+promise_test(async () => {
+ let indices = [];
+
+ const source = new Observable(subscriber => {
+ subscriber.next("a");
+ subscriber.next("b");
+ subscriber.next("c");
+ subscriber.complete();
+ });
+
+ const value = await source.find((value, index) => {
+ indices.push(index);
+ return false;
+ });
+
+ assert_equals(value, undefined, "Promise resolves with undefined if no value passes the predicate");
+
+ assert_array_equals(indices, [0, 1, 2], "find(): Passes the index of the value to the predicate");
+}, "find(): Passes the index of the value to the predicate");
+
+promise_test(async t => {
+ const controller = new AbortController();
+ const source = new Observable(subscriber => {
+ subscriber.next("a");
+ subscriber.next("b");
+ subscriber.next("c");
+ subscriber.complete();
+ });
+
+ controller.abort();
+ const promise = source.find(() => true, { signal: controller.signal });
+
+ promise_rejects_dom(t, 'AbortError', promise, "Promise rejects with " +
+ "DOMException when the signal is aborted");
+}, "find(): Rejects with AbortError when the signal is aborted");
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-inspect.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-inspect.any.js
new file mode 100644
index 0000000000..8aff741d26
--- /dev/null
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-inspect.any.js
@@ -0,0 +1,412 @@
+// Because we test that the global error handler is called at various times.
+setup({ allow_uncaught_exception: true });
+
+test(() => {
+ const results = [];
+ let sourceSubscriptionCall = 0;
+ const source = new Observable(subscriber => {
+ sourceSubscriptionCall++;
+ results.push(`source subscribe ${sourceSubscriptionCall}`);
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ let inspectSubscribeCall = 0;
+ const result = source.inspect({
+ subscribe: () => {
+ inspectSubscribeCall++;
+ results.push(`inspect() subscribe ${inspectSubscribeCall}`);
+ },
+ next: (value) => results.push(`inspect() next ${value}`),
+ error: (e) => results.push(`inspect() error ${e.message}`),
+ complete: () => results.push(`inspect() complete`),
+ });
+
+ result.subscribe({
+ next: (value) => results.push(`result next ${value}`),
+ error: (e) => results.push(`result error ${e.message}`),
+ complete: () => results.push(`result complete`),
+ });
+
+ result.subscribe({
+ next: (value) => results.push(`result next ${value}`),
+ error: (e) => results.push(`result error ${e.message}`),
+ complete: () => results.push(`result complete`),
+ });
+
+ assert_array_equals(results,
+ [
+ "inspect() subscribe 1",
+ "source subscribe 1",
+ "inspect() next 1",
+ "result next 1",
+ "inspect() next 2",
+ "result next 2",
+ "inspect() next 3",
+ "result next 3",
+ "inspect() complete",
+ "result complete",
+ "inspect() subscribe 2",
+ "source subscribe 2",
+ "inspect() next 1",
+ "result next 1",
+ "inspect() next 2",
+ "result next 2",
+ "inspect() next 3",
+ "result next 3",
+ "inspect() complete",
+ "result complete",
+ ]);
+}, "inspect(): Provides a pre-subscription subscribe callback");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ const results = [];
+
+ const result = source.inspect({
+ next: value => results.push(value),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ result.subscribe();
+ result.subscribe();
+
+ assert_array_equals(results, [1, 2, 3, "complete", 1, 2, 3, "complete"]);
+}, "inspect(): Provides a way to tap into the values and completions of the " +
+ "source observable using an observer");
+
+test(() => {
+ const error = new Error("error from source");
+ const source = new Observable(subscriber => subscriber.error(error));
+
+ const results = [];
+
+ const result = source.inspect({
+ next: value => results.push(value),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ let errorReported = null;
+ self.addEventListener('error', e => errorReported = e.error, {once: true});
+ result.subscribe();
+
+ assert_array_equals(results, [error]);
+ assert_equals(errorReported, error,
+ "errorReported to global matches error from source Observable");
+}, "inspect(): Error handler does not stop error from being reported to the " +
+ "global, when subscriber does not pass error handler");
+
+test(() => {
+ const error = new Error("error from source");
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.error(error);
+ });
+
+ const results = [];
+
+ const result = source.inspect({
+ next: value => results.push(value),
+ error: e => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ const observer = {
+ error: e => results.push(e),
+ };
+ result.subscribe(observer);
+ result.subscribe(observer);
+
+ assert_array_equals(results, [1, 2, 3, error, error, 1, 2, 3, error, error]);
+}, "inspect(): Provides a way to tap into the values and errors of the " +
+ "source observable using an observer. Errors are passed through");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ const results = [];
+
+ const result = source.inspect(value => results.push(value));
+
+ result.subscribe();
+ result.subscribe();
+
+ assert_array_equals(results, [1, 2, 3, 1, 2, 3]);
+}, "inspect(): ObserverCallback passed in");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ });
+
+ const error = new Error("error from inspect() next handler");
+ const result = source.inspect({
+ next: (value) => {
+ if (value === 2) {
+ throw error;
+ }
+ },
+ });
+
+ const results1 = [];
+ result.subscribe({
+ next: (value) => results1.push(value),
+ error: (e) => results1.push(e),
+ complete: () => results1.push("complete"),
+ });
+
+ const results2 = [];
+ result.subscribe({
+ next: (value) => results2.push(value),
+ error: (e) => results2.push(e),
+ complete: () => results2.push("complete"),
+ });
+
+ assert_array_equals(results1, [1, error]);
+ assert_array_equals(results2, [1, error]);
+}, "inspect(): Throwing an error in the observer next handler is caught and " +
+ "sent to the error callback of the result observable");
+
+test(() => {
+ const sourceError = new Error("error from source");
+ const inspectError = new Error("error from inspect() error handler");
+
+ const source = new Observable(subscriber => {
+ subscriber.error(sourceError);
+ });
+
+ const result = source.inspect({
+ error: () => {
+ throw inspectError;
+ },
+ });
+
+ const results = [];
+ result.subscribe({
+ next: () => results.push("next"),
+ error: (e) => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [inspectError]);
+}, "inspect(): Throwing an error in the observer error handler in " +
+ "inspect() is caught and sent to the error callback of the result " +
+ "observable");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ const error = new Error("error from inspect() complete handler");
+ const result = source.inspect({
+ complete: () => {
+ throw error;
+ },
+ });
+
+ const results = [];
+ result.subscribe({
+ next: (value) => results.push(value),
+ error: (e) => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [1, 2, 3, error]);
+}, "inspect(): Throwing an error in the observer complete handler is caught " +
+ "and sent to the error callback of the result observable");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ });
+
+ const error = new Error("error from inspect() next handler");
+ const result = source.inspect({
+ next: (value) => {
+ if (value === 2) {
+ throw error;
+ }
+ },
+ });
+
+ const results = [];
+ result.subscribe({
+ next: (value) => results.push(value),
+ error: (e) => results.push(e),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, [1, error]);
+}, "inspect(): Throwing an error in the next handler function in do should " +
+ "be caught and sent to the error callback of the result observable");
+
+test(() => {
+ const source = new Observable(subscriber => {});
+
+ const result = source.inspect({
+ subscribe: () => {
+ throw new Error("error from do subscribe handler");
+ },
+ });
+
+ const results = [];
+ result.subscribe({
+ next: () => results.push("next"),
+ error: (e) => results.push(e.message),
+ complete: () => results.push("complete"),
+ });
+
+ assert_array_equals(results, ["error from do subscribe handler"]);
+}, "inspect(): Errors thrown in subscribe() Inspector handler subscribe " +
+ "handler are caught and sent to error callback");
+
+test(() => {
+ const results = [];
+ let sourceTeardownCall = 0;
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => {
+ sourceTeardownCall++;
+ results.push(`source teardown ${sourceTeardownCall}`);
+ });
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ let doUnsubscribeCall = 0;
+ const result = source.inspect({
+ abort: (reason) => {
+ doUnsubscribeCall++;
+ results.push(`inspect() abort ${doUnsubscribeCall} ${reason}`);
+ },
+ next: (value) => results.push(`inspect() next ${value}`),
+ error: (e) => results.push(`inspect() error ${e.message}`),
+ complete: () => results.push(`inspect() complete`),
+ });
+
+ const controller = new AbortController();
+ result.subscribe({
+ next: (value) => {
+ results.push(`result next ${value}`);
+ if (value === 2) {
+ controller.abort("abort reason");
+ }
+ },
+ error: (e) => results.push(`result error ${e.message}`),
+ complete: () => results.push(`result complete`),
+ }, { signal: controller.signal });
+
+ assert_array_equals(results, [
+ "inspect() next 1",
+ "result next 1",
+ "inspect() next 2",
+ "result next 2",
+ "inspect() abort 1 abort reason",
+ "source teardown 1",
+ ]);
+}, "inspect(): Provides a way to tap into the moment a source observable is unsubscribed from");
+
+test(() => {
+ const results = [];
+ let sourceTeardownCall = 0;
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => {
+ sourceTeardownCall++;
+ results.push(`source teardown ${sourceTeardownCall}`);
+ });
+ subscriber.next(1);
+ subscriber.next(2);
+ subscriber.next(3);
+ subscriber.complete();
+ });
+
+ let inspectUnsubscribeCall = 0;
+ const result = source.inspect({
+ next: (value) => results.push(`inspect() next ${value}`),
+ complete: () => results.push(`inspect() complete`),
+ abort: (reason) => {
+ inspectUnsubscribeCall++;
+ results.push(`inspect() abort ${inspectUnsubscribeCall} ${reason}`);
+ },
+ });
+
+ result.subscribe({
+ next: (value) => results.push(`result next ${value}`),
+ complete: () => results.push(`result complete`),
+ });
+
+ assert_array_equals(results, [
+ "inspect() next 1",
+ "result next 1",
+ "inspect() next 2",
+ "result next 2",
+ "inspect() next 3",
+ "result next 3",
+ "source teardown 1",
+ "inspect() complete",
+ "result complete",
+ ]);
+}, "inspect(): Inspector abort() handler is not called if the source " +
+ "completes before the result is unsubscribed from");
+
+test(() => {
+ const source = new Observable(subscriber => {
+ subscriber.next(1);
+ });
+
+ const results = [];
+
+ const result = source.inspect({
+ abort: () => {
+ results.push('abort() handler run');
+ throw new Error("error from inspect() subscribe handler");
+ },
+ });
+
+ const controller = new AbortController();
+
+ self.on('error').take(1).subscribe(e =>
+ results.push(e.message + ', from report exception'));
+
+ result.subscribe({
+ next: (value) => {
+ results.push(value);
+ controller.abort();
+ },
+ // This should not be invoked at all!!
+ error: (e) => results.push(e.message + ', from Observer#error()'),
+ complete: () => results.push("complete"),
+ }, {signal: controller.signal});
+
+ assert_array_equals(results, [1, "abort() handler run",
+ "Uncaught Error: error from inspect() subscribe handler, from report " +
+ "exception"]);
+}, "inspect(): Errors thrown from inspect()'s abort() handler are caught " +
+ "and reported to the global, because the subscription is already closed " +
+ "by the time the handler runs");
diff --git a/testing/web-platform/tests/dom/observable/tentative/observable-some.any.js b/testing/web-platform/tests/dom/observable/tentative/observable-some.any.js
new file mode 100644
index 0000000000..b692610df3
--- /dev/null
+++ b/testing/web-platform/tests/dom/observable/tentative/observable-some.any.js
@@ -0,0 +1,96 @@
+promise_test(async () => {
+ let inactiveAfterFirstGood = true;
+
+ const source = new Observable(subscriber => {
+ subscriber.next("good");
+ inactiveAfterFirstGood = !subscriber.active;
+ subscriber.next("good");
+ subscriber.next("good");
+ subscriber.complete();
+ });
+
+ const result = await source.some((value) => value === "good");
+
+ assert_true(result, "Promise resolves with true if any value passes the predicate");
+
+ assert_true(inactiveAfterFirstGood,
+ "subscriber is inactive after the first value that passes the " +
+ "predicate, because the source was unsubscribed from");
+}, "some(): subscriber is inactive after the first value that passes the predicate, because the source was unsubscribed from");
+
+promise_test(async () => {
+ const source = new Observable(subscriber => {
+ subscriber.next("bad");
+ subscriber.next("bad");
+ subscriber.next("bad");
+ subscriber.complete();
+ });
+
+ const result = await source.some((value) => value === "good");
+
+ assert_false(result, "some(): Promise resolves with false if no value passes the predicate");
+});
+
+promise_test(async () => {
+ const source = new Observable(subscriber => {
+ subscriber.next("bad");
+ subscriber.next("bad");
+ subscriber.next("good");
+ subscriber.complete();
+ });
+
+ const result = await source.some((value) => value === "good");
+
+ assert_true(result, "some(): Promise resolves with true if any value passes the predicate");
+});
+
+promise_test(async t => {
+ const source = new Observable(subscriber => {
+ subscriber.next("not used");
+ });
+
+ const error = new Error("thrown from predicate");
+ promise_rejects_exactly(t, error, source.some(() => {throw error}),
+ "The returned promise rejects with an error if the predicate errors");
+}, "some(): The returned promise rejects with an error if the predicate errors");
+
+promise_test(async t => {
+ const error = new Error("error from source");
+ const source = new Observable(subscriber => {
+ subscriber.error(error);
+ });
+
+ promise_rejects_exactly(t, error, source.some(() => true),
+ "The returned promise rejects with an error if the source observable errors");
+}, "some(): The returned promise rejects with an error if the source observable errors");
+
+promise_test(async () => {
+ const source = new Observable(subscriber => {
+ subscriber.complete();
+ });
+
+ const result = await source.some(() => true);
+
+ assert_false(result,
+ "The returned promise resolves as false if the source observable " +
+ "completes without emitting a value");
+}, "some(): The returned promise resolves as false if the source observable " +
+ "completes without emitting a value");
+
+promise_test(async t => {
+ let teardownCalled = false;
+ const source = new Observable(subscriber => {
+ subscriber.addTeardown(() => {
+ teardownCalled = true;
+ });
+ });
+
+ const controller = new AbortController();
+ const promise = source.some(() => true, { signal: controller.signal });
+
+ controller.abort();
+
+ promise_rejects_dom(t, 'AbortError', promise);
+ assert_true(teardownCalled,
+ "The teardown function is called when the signal is aborted");
+}, "some(): The return promise rejects with a DOMException if the signal is aborted");
diff --git a/testing/web-platform/tests/dom/ranges/Range-isPointInRange-shadowdom.tentative.html b/testing/web-platform/tests/dom/ranges/Range-isPointInRange-shadowdom.tentative.html
new file mode 100644
index 0000000000..a90ddcf584
--- /dev/null
+++ b/testing/web-platform/tests/dom/ranges/Range-isPointInRange-shadowdom.tentative.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<title>Range.isPointInRange() with ShadowDOM selection tests</title>
+<link rel="author" title="Sean Feng" href=sefeng@mozilla.com>
+<div id=log></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<span id="start">Start</span>
+<div id="host">
+ <template shadowrootmode="open">
+ <span id="inner1">Inner1</span>
+ <span id="inner2">Inner2</span>
+ </template>
+</div>
+<span id="end">End</span>
+<script>
+"use strict";
+
+test(function() {
+ assert_implements(window.getSelection().getComposedRanges, "GetComposedRanges is not supported");
+ const start = document.getElementById("start");
+ const shadowRoot = document.getElementById("host").shadowRoot;
+
+ const end = shadowRoot.getElementById("inner2");
+ const inner1 = shadowRoot.getElementById("inner1");
+
+ window.getSelection().setBaseAndExtent(start.firstChild, 3, end.firstChild, 3);
+
+ const composedRange = window.getSelection().getComposedRanges(shadowRoot)[0];
+ // Sanity check to make sure we have selected something across the shadow boundary.
+ assert_true(composedRange.startContainer == start.firstChild);
+ assert_true(composedRange.startOffset == 3);
+ assert_true(composedRange.endContainer == end.firstChild);
+ assert_true(composedRange.endOffset == 3);
+
+ assert_true(window.getSelection().isCollapsed, "Selection should be collapsed");
+
+ const range = window.getSelection().getRangeAt(0);
+ assert_false(range.isPointInRange(inner1, 0), "inner1 is in the shadow tree, should not be in the range");
+ assert_true(range.comparePoint(inner1, 0) == -1, "inner1 is in the shadow tree, should return -1 for comparison");
+}, "isPointInRange() test for collapsed selection");
+
+test(function() {
+ assert_implements(window.getSelection().getComposedRanges, "GetComposedRanges is not supported");
+ const start = document.getElementById("start");
+ const shadowRoot = document.getElementById("host").shadowRoot;
+
+ const end = document.getElementById("end");
+ const inner1 = shadowRoot.getElementById("inner1");
+
+ window.getSelection().setBaseAndExtent(start.firstChild, 3, end.firstChild, 3);
+
+ const composedRange = window.getSelection().getRangeAt(0);
+ // Sanity check to make sure we have selected something
+ assert_true(composedRange.startContainer == start.firstChild);
+ assert_true(composedRange.startOffset == 3);
+ assert_true(composedRange.endContainer == end.firstChild);
+ assert_true(composedRange.endOffset == 3);
+
+ assert_false(window.getSelection().isCollapsed, "Range should not be collapsed");
+
+ const range = window.getSelection().getRangeAt(0);
+
+ assert_false(range.isPointInRange(inner1, 0), "inner1 is in the shadow tree, should not be in the range");
+
+ // The selection is not collapsed so inner1 is not in the same tree as the selection.
+ assert_throws_dom("WrongDocumentError", function() {
+ range.comparePoint(inner1, 0);
+ });
+
+ const host = document.getElementById("host");
+ assert_true(range.isPointInRange(host, 0), "host is not in the shadow tree, should be in the range");
+ assert_true(range.comparePoint(host, 0) == 0, "host is not in the shadow tree, should return 0 for comparison");
+}, "isPointInRange() test for non-collapsed selection");
+
+</script>
diff --git a/testing/web-platform/tests/dom/xslt/resources/xml2html.xsl b/testing/web-platform/tests/dom/xslt/resources/xml2html.xsl
index 07b967500f..88b74a9620 100644
--- a/testing/web-platform/tests/dom/xslt/resources/xml2html.xsl
+++ b/testing/web-platform/tests/dom/xslt/resources/xml2html.xsl
@@ -5,7 +5,7 @@ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
- <h2>My CD Collection</h2>
+ <h2 style="margin-top:0">My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>