diff options
Diffstat (limited to 'dom/base/test/test_content_iterator_subtree_shadow_tree.html')
-rw-r--r-- | dom/base/test/test_content_iterator_subtree_shadow_tree.html | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/dom/base/test/test_content_iterator_subtree_shadow_tree.html b/dom/base/test/test_content_iterator_subtree_shadow_tree.html new file mode 100644 index 0000000000..033aaf80a7 --- /dev/null +++ b/dom/base/test/test_content_iterator_subtree_shadow_tree.html @@ -0,0 +1,290 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Test for content subtree iterator with ShadowDOM involved</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"> +<script> +var Cc = SpecialPowers.Cc; +var Ci = SpecialPowers.Ci; +function finish() { + // The SimpleTest may require usual elements in the template, but they shouldn't be during test. + // So, let's create them at end of the test. + document.body.innerHTML = '<div id="display"></div><div id="content"></div><pre id="test"></pre>'; + SimpleTest.finish(); +} + +function createContentIterator() { + return Cc["@mozilla.org/scriptable-content-iterator;1"] + .createInstance(Ci.nsIScriptableContentIterator); +} + +function getNodeDescription(aNode) { + if (aNode === undefined) { + return "undefine"; + } + if (aNode === null) { + return "null"; + } + function getElementDescription(aElement) { + if (aElement.host) { + aElement = aElement.host; + } + if (aElement.tagName === "BR") { + if (aElement.previousSibling) { + return `<br> element after ${getNodeDescription(aElement.previousSibling)}`; + } + return `<br> element in ${getElementDescription(aElement.parentElement)}`; + } + let hasHint = aElement == document.body; + let tag = `<${aElement.tagName.toLowerCase()}`; + if (aElement.getAttribute("id")) { + tag += ` id="${aElement.getAttribute("id")}"`; + hasHint = true; + } + if (aElement.getAttribute("class")) { + tag += ` class="${aElement.getAttribute("class")}"`; + hasHint = true; + } + if (aElement.getAttribute("type")) { + tag += ` type="${aElement.getAttribute("type")}"`; + } + if (aElement.getAttribute("name")) { + tag += ` name="${aElement.getAttribute("name")}"`; + } + if (aElement.getAttribute("value")) { + tag += ` value="${aElement.getAttribute("value")}"`; + hasHint = true; + } + if (aElement.getAttribute("style")) { + tag += ` style="${aElement.getAttribute("style")}"`; + hasHint = true; + } + if (hasHint) { + return tag + ">"; + } + + return `${tag}> in ${getElementDescription(aElement.parentElement || aElement.parentNode)}`; + } + switch (aNode.nodeType) { + case aNode.TEXT_NODE: + return `text node, "${aNode.wholeText.replace(/\n/g, '\\n')}"`; + case aNode.COMMENT_NODE: + return `comment node, "${aNode.data.replace(/\n/g, '\\n')}"`; + case aNode.ELEMENT_NODE: + return getElementDescription(SpecialPowers.unwrap(aNode)); + default: + return "unknown node"; + } +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(function () { + let iter = createContentIterator(); + + function runTest() { + /** + * Basic tests with complicated tree. + */ + function check(aIter, aExpectedResult, aDescription) { + if (aExpectedResult.length) { + is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0], + `${aDescription}: currentNode should be the text node immediately after initialization (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`); + ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true immediately after initialization`); + + aIter.first(); + is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0], + `${aDescription}: currentNode should be the text node after calling first() (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`); + ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true after calling first()`); + + for (let expected of aExpectedResult) { + is(SpecialPowers.unwrap(aIter.currentNode), expected, + `${aDescription}: currentNode should be the node (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(expected)})`); + ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true when ${getNodeDescription(expected)} is expected`); + aIter.next(); + } + + is(SpecialPowers.unwrap(aIter.currentNode), null, + `${aDescription}: currentNode should be null after calling next() finally (got: ${getNodeDescription(aIter.currentNode)}`); + ok(aIter.isDone, `${aDescription}: isDone should be true after calling next() finally`); + } else { + is(SpecialPowers.unwrap(aIter.currentNode), null, + `${aDescription}: currentNode should be null immediately after initialization (got: ${getNodeDescription(aIter.currentNode)})`); + ok(aIter.isDone, `${aDescription}: isDone should be true immediately after initialization`); + + aIter.first(); + is(SpecialPowers.unwrap(aIter.currentNode), null, + `${aDescription}: currentNode should be null after calling first() (got: ${getNodeDescription(aIter.currentNode)})`); + ok(aIter.isDone, `${aDescription}: isDone should be true after calling first()`); + } + } + + // Structure + // <div>OuterText1</div> + // <div #host1> + // #ShadowRoot + // InnerText1 + // <div>OuterText2</div> + // <div #host2> + // #ShadowRoot + // <div>InnerText2</div> + // <div>InnerText3</div> + // <div #host3> + // #ShadowRoot + // <div #host4> + // #ShadowRoot + // InnerText4 + // OuterText3 + + document.body.innerHTML = `<div id="outerText1">OuterText1</div>` + + `<div id="host1"></div>` + + `<div id="outerText2">OuterText2</div>` + + `<div id="host2"></div>` + + `<div id="host3"></div>` + + `OuterText3`; + const outerText1 = document.getElementById("outerText1"); + const outerText2 = document.getElementById("outerText2"); + + const host1 = document.getElementById("host1"); + const root1 = host1.attachShadow({mode: "open"}); + root1.innerHTML = "InnerText1"; + + const host2 = document.getElementById("host2"); + const root2 = host2.attachShadow({mode: "open"}); + root2.innerHTML = "<div>InnerText2</div><div>InnerText3</div>"; + + const host3 = document.getElementById("host3"); + const root3 = host3.attachShadow({mode: "open"}); + root3.innerHTML = `<div id="host4"></div>`; + + const host4 = root3.getElementById("host4"); + const root4 = host4.attachShadow({mode: "open"}); + root4.innerHTML = "InnerText4"; + + /** + * Selects the <body> with a range. + */ + range = document.createRange(); + range.selectNode(document.body); + iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range); + check(iter, [document.body], "Initialized with range selecting the <body>"); + + /** + * Selects all children in the <body> with a range. + */ + range = document.createRange(); + range.selectNodeContents(document.body); + iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range); + check(iter, [outerText1, host1, + outerText2, host2, + host3, // host4 is a child of host3 + document.body.lastChild], + "Initialized with range selecting all children in the <body>"); + + /** + * range around elements. + */ + range = document.createRange(); + SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1.firstChild, 0); + SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root1.firstChild, root1.firstChild.length); + iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range); + // outerText1.firstChild is a node without children, so the + // next candidate is root1.firstChild, given root1.firstChild + // is also the end container which isn't fully contained + // by this range, so the iterator returns nothing. + check(iter, [], "Initialized with range selecting 'OuterText1 and InnerText1'"); + + // From light DOM to Shadow DOM #1 + range = document.createRange(); + SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1, 0); + SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root1.firstChild, root1.firstChild.length); + iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range); + // [start] outerText1 is a container and it has children, so the first node + // is the topmost descendant, which is outerText.firstChild. + // [end] The end point of this iteration is also outerText1.firstChild because + // it is also the topmost element in the previous node of root1.firstChild. + // Iteration #1: outerText1.firstChild as it is the start node + check(iter, [outerText1.firstChild], "Initialized with range selecting 'OuterText1 and InnerText1'"); + + // From light DOM to Shadow DOM #2 + SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1, 0); + SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root2, root2.childNodes.length); + iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range); + // [start] outerText1 is a container and it has children, so the first node + // is the topmost descendant, which is outerText.firstChild. + // [end] root2 is the container and it has children, so the end node is + // the last node of root2, which is root2.lastChild + // Iteration #1: outerText1.firstChild, as it's the start node + // Iteration #2: host1, as it's next available node after outerText1.firstChild + // Iteration #3: outerText2, as it's the next sibiling of host1 + // Iteration #4: host2, as it's the next sibling of outerText2. Since it's + // the ancestor of the end node, so we get into this tree and returns + // root2.firstChild here. + // Iteration #5: root2.lastChild, as it's the next sibling of root2.firstChild + check(iter, [outerText1.firstChild, host1, outerText2, root2.firstChild, root2.lastChild], + "Initialized with range selecting 'OuterText1, InnerText1, OuterText2 and InnerText2'"); + + // From Shadow DOM to Shadow DOM #1 + SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(root1.firstChild, 0); + SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root2.lastChild, root2.lastChild.length); + iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range); + // [start] outerText2 is the start because root1.firstChild doesn't have children, + // so we look for next available node which is outerText2. + // [end] root2.lastChild is the end container, so we look for previous + // nodes and get root2.firstChild + // Iteration #1: outerText2, as it's the start node + // Iteration #2: host2, as it's the next sibling of outerText2. Since it's + // the ancestor of the end node, so we get into this tree and returns + // root2.firstChild here. + check(iter, [outerText2, root2.firstChild], "Initialized with range selecting 'InnerText1, OuterText2 and InnerText2'"); + + // From Shadow DOM to Shadow DOM #2 + SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(root1, 0); + SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root2.lastChild, root2.lastChild.length); + iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range); + // [start] root1 is the start container and it has children, so the first node + // is the topmost descendant, which is root1.firstChild. + // [end] root2.lastChild is the end container, so we look for previous + // nodes and get root2.firstChild + // Iteration #1: root1.firstChild, as it's the start node + // Iteration #2: outerText2, as it's the next available node + // Iteration #3: host2, as it's the next sibling of outerText2. Since it's + // the ancestor of the end node, so we get into this tree and returns + // root2.firstChild here. + check(iter, [root1.firstChild, outerText2, root2.firstChild], "Initialized with range selecting 'InnerText1, OuterText2 and InnerText2'"); + + SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(root1.firstChild, 1); + SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root4.firstChild, root4.firstChild.length); + iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range); + // [start] outerText2 is the start because root1.firstChild doesn't have children, + // so we look for next available node which is outerText2. + // [end] host2 is the end container, so we look for previous + // nodes root4.firstChild and eventually get host2. + // Iteration #1: outerText2, as it's the start node + // Iteration #2: host2, as it's the next sibling of outerText2 + check(iter, [outerText2, host2], "Initialized with range selecting 'InnerText1, OuterText2, InnerText2 and InnerText3'"); + + // From light to light + SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1.firstChild, 0); + SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(document.body.lastChild, document.body.lastChild.length); + iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range); + // [start] host1 is the start because it's the next available node of + // outerText1.firstChild. + // [end] host3 is the end because the previous node of document.body.lastChild is host3. + // Iteration #1: host1, as it's the start node + // Iteration #2: outerText2, as it's the next sibling of host1 + // Iteration #3: host2, as it's the next sibling of outerText2 + // Iteration #4: host3, as it's the next sibling of host2 + check(iter, [host1, outerText2, host2, host3], + "Initialized with range selecting 'OuterText1, InnerText1, OuterText2, InnerText2, InnerText3 and OuterText3'"); + + finish(); + } + + SpecialPowers.pushPrefEnv({"set": [["dom.shadowdom.selection_across_boundary.enabled", true]]}, runTest); +}); +</script> +</head> +<body></body> +</html> |