diff options
Diffstat (limited to '')
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> |