tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_content_iterator_subtree_shadow_tree.html (14229B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4  <meta charset="utf-8">
      5  <title>Test for content subtree iterator with ShadowDOM involved</title>
      6  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      7  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
      8 <script>
      9 var Cc = SpecialPowers.Cc;
     10 var Ci = SpecialPowers.Ci;
     11 function finish() {
     12  // The SimpleTest may require usual elements in the template, but they shouldn't be during test.
     13  // So, let's create them at end of the test.
     14  document.body.innerHTML = '<div id="display"></div><div id="content"></div><pre id="test"></pre>';
     15  SimpleTest.finish();
     16 }
     17 
     18 function createContentIterator() {
     19  return Cc["@mozilla.org/scriptable-content-iterator;1"]
     20      .createInstance(Ci.nsIScriptableContentIterator);
     21 }
     22 
     23 function getNodeDescription(aNode) {
     24  if (aNode === undefined) {
     25    return "undefine";
     26  }
     27  if (aNode === null) {
     28    return "null";
     29  }
     30  function getElementDescription(aElement) {
     31    if (aElement.host) {
     32      aElement = aElement.host;
     33    }
     34    if (aElement.tagName === "BR") {
     35      if (aElement.previousSibling) {
     36        return `<br> element after ${getNodeDescription(aElement.previousSibling)}`;
     37      }
     38      return `<br> element in ${getElementDescription(aElement.parentElement)}`;
     39    }
     40    let hasHint = aElement == document.body;
     41    let tag = `<${aElement.tagName.toLowerCase()}`;
     42    if (aElement.getAttribute("id")) {
     43      tag += ` id="${aElement.getAttribute("id")}"`;
     44      hasHint = true;
     45    }
     46    if (aElement.getAttribute("class")) {
     47      tag += ` class="${aElement.getAttribute("class")}"`;
     48      hasHint = true;
     49    }
     50    if (aElement.getAttribute("type")) {
     51      tag += ` type="${aElement.getAttribute("type")}"`;
     52    }
     53    if (aElement.getAttribute("name")) {
     54      tag += ` name="${aElement.getAttribute("name")}"`;
     55    }
     56    if (aElement.getAttribute("value")) {
     57      tag += ` value="${aElement.getAttribute("value")}"`;
     58      hasHint = true;
     59    }
     60    if (aElement.getAttribute("style")) {
     61      tag += ` style="${aElement.getAttribute("style")}"`;
     62      hasHint = true;
     63    }
     64    if (hasHint) {
     65      return tag + ">";
     66    }
     67 
     68    return `${tag}> in ${getElementDescription(aElement.parentElement || aElement.parentNode)}`;
     69  }
     70  switch (aNode.nodeType) {
     71    case aNode.TEXT_NODE:
     72      return `text node, "${aNode.wholeText.replace(/\n/g, '\\n')}"`;
     73    case aNode.COMMENT_NODE:
     74      return `comment node, "${aNode.data.replace(/\n/g, '\\n')}"`;
     75    case aNode.ELEMENT_NODE:
     76      return getElementDescription(SpecialPowers.unwrap(aNode));
     77    default:
     78      return "unknown node";
     79  }
     80 }
     81 
     82 SimpleTest.waitForExplicitFinish();
     83 SimpleTest.waitForFocus(function () {
     84  let iter = createContentIterator();
     85 
     86  function runTest() {
     87    /**
     88     * Basic tests with complicated tree.
     89     */
     90    function check(aIter, aExpectedResult, aDescription) {
     91      if (aExpectedResult.length) {
     92        is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
     93          `${aDescription}: currentNode should be the text node immediately after initialization (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
     94        ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true immediately after initialization`);
     95 
     96        aIter.first();
     97        is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
     98          `${aDescription}: currentNode should be the text node after calling first() (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
     99        ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true after calling first()`);
    100 
    101        for (let expected of aExpectedResult) {
    102          is(SpecialPowers.unwrap(aIter.currentNode), expected,
    103            `${aDescription}: currentNode should be the node (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(expected)})`);
    104          ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true when ${getNodeDescription(expected)} is expected`);
    105          aIter.next();
    106        }
    107 
    108        is(SpecialPowers.unwrap(aIter.currentNode), null,
    109          `${aDescription}: currentNode should be null after calling next() finally (got: ${getNodeDescription(aIter.currentNode)}`);
    110        ok(aIter.isDone, `${aDescription}: isDone should be true after calling next() finally`);
    111      } else {
    112        is(SpecialPowers.unwrap(aIter.currentNode), null,
    113          `${aDescription}: currentNode should be null immediately after initialization (got: ${getNodeDescription(aIter.currentNode)})`);
    114        ok(aIter.isDone, `${aDescription}: isDone should be true immediately after initialization`);
    115 
    116        aIter.first();
    117        is(SpecialPowers.unwrap(aIter.currentNode), null,
    118          `${aDescription}: currentNode should be null after calling first() (got: ${getNodeDescription(aIter.currentNode)})`);
    119        ok(aIter.isDone, `${aDescription}: isDone should be true after calling first()`);
    120      }
    121    }
    122 
    123    // Structure
    124    //   <div>OuterText1</div>
    125    //   <div #host1>
    126    //     #ShadowRoot
    127    //       InnerText1
    128    //   <div>OuterText2</div>
    129    //   <div #host2>
    130    //     #ShadowRoot
    131    //       <div>InnerText2</div>
    132    //       <div>InnerText3</div>
    133    //   <div #host3>
    134    //     #ShadowRoot
    135    //       <div #host4>
    136    //         #ShadowRoot
    137    //           InnerText4
    138    //   OuterText3
    139 
    140    document.body.innerHTML = `<div id="outerText1">OuterText1</div>` +
    141                              `<div id="host1"></div>` +
    142                              `<div id="outerText2">OuterText2</div>` +
    143                              `<div id="host2"></div>` +
    144                              `<div id="host3"></div>` +
    145                              `OuterText3`;
    146    const outerText1 = document.getElementById("outerText1");
    147    const outerText2 = document.getElementById("outerText2");
    148 
    149    const host1 = document.getElementById("host1");
    150    const root1 = host1.attachShadow({mode: "open"});
    151    root1.innerHTML = "InnerText1";
    152 
    153    const host2 = document.getElementById("host2");
    154    const root2 = host2.attachShadow({mode: "open"});
    155    root2.innerHTML = "<div>InnerText2</div><div>InnerText3</div>";
    156 
    157    const host3 = document.getElementById("host3");
    158    const root3 = host3.attachShadow({mode: "open"});
    159    root3.innerHTML = `<div id="host4"></div>`;
    160 
    161    const host4 = root3.getElementById("host4");
    162    const root4 = host4.attachShadow({mode: "open"});
    163    root4.innerHTML = "InnerText4";
    164 
    165    /**
    166     * Selects the <body> with a range.
    167     */
    168    range = document.createRange();
    169    range.selectNode(document.body);
    170    iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
    171    check(iter, [document.body], "Initialized with range selecting the <body>");
    172 
    173    /**
    174     * Selects all children in the <body> with a range.
    175     */
    176    range = document.createRange();
    177    range.selectNodeContents(document.body);
    178    iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
    179    check(iter, [outerText1, host1,
    180                 outerText2, host2,
    181                 host3, // host4 is a child of host3
    182                 document.body.lastChild],
    183          "Initialized with range selecting all children in the <body>");
    184 
    185    /**
    186     * range around elements.
    187     */
    188    range = document.createRange();
    189    SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1.firstChild, 0);
    190    SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root1.firstChild, root1.firstChild.length);
    191    iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
    192    // outerText1.firstChild is a node without children, so the
    193    // next candidate is root1.firstChild, given root1.firstChild
    194    // is also the end container which isn't fully contained
    195    // by this range, so the iterator returns nothing.
    196    check(iter, [], "Initialized with range selecting 'OuterText1 and InnerText1'");
    197 
    198    // From light DOM to Shadow DOM #1
    199    range = document.createRange();
    200    SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1, 0);
    201    SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root1.firstChild, root1.firstChild.length);
    202    iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
    203    // [start] outerText1 is a container and it has children, so the first node
    204    //         is the topmost descendant, which is outerText.firstChild.
    205    // [end] The end point of this iteration is also outerText1.firstChild because
    206    //       it is also the topmost element in the previous node of root1.firstChild.
    207    // Iteration #1: outerText1.firstChild as it is the start node
    208    check(iter, [outerText1.firstChild], "Initialized with range selecting 'OuterText1 and InnerText1'");
    209 
    210    // From light DOM to Shadow DOM #2
    211    SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1, 0);
    212    SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root2, root2.childNodes.length);
    213    iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
    214    // [start] outerText1 is a container and it has children, so the first node
    215    //         is the topmost descendant, which is outerText.firstChild.
    216    // [end] root2 is the container and it has children, so the end node is
    217    //       the last node of root2, which is root2.lastChild
    218    // Iteration #1: outerText1.firstChild, as it's the start node
    219    // Iteration #2: host1, as it's next available node after outerText1.firstChild
    220    // Iteration #3: outerText2, as it's the next sibiling of host1
    221    // Iteration #4: host2, as it's the next sibling of outerText2. Since it's
    222    //               the ancestor of the end node, so we get into this tree and returns
    223    //               root2.firstChild here.
    224    // Iteration #5: root2.lastChild, as it's the next sibling of root2.firstChild
    225    check(iter, [outerText1.firstChild, host1, outerText2, root2.firstChild, root2.lastChild],
    226      "Initialized with range selecting 'OuterText1, InnerText1, OuterText2 and InnerText2'");
    227 
    228    // From Shadow DOM to Shadow DOM #1
    229    SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(root1.firstChild, 0);
    230    SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root2.lastChild, root2.lastChild.length);
    231    iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
    232    // [start] outerText2 is the start because root1.firstChild doesn't have children,
    233    //         so we look for next available node which is outerText2.
    234    // [end] root2.lastChild is the end container, so we look for previous
    235    //       nodes and get root2.firstChild
    236    // Iteration #1: outerText2, as it's the start node
    237    // Iteration #2: host2, as it's the next sibling of outerText2. Since it's
    238    //               the ancestor of the end node, so we get into this tree and returns
    239    //               root2.firstChild here.
    240    check(iter, [outerText2, root2.firstChild], "Initialized with range selecting 'InnerText1, OuterText2 and InnerText2'");
    241 
    242    // From Shadow DOM to Shadow DOM #2
    243    SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(root1, 0);
    244    SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root2.lastChild, root2.lastChild.length);
    245    iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
    246    // [start] root1 is the start container and it has children, so the first node
    247    //         is the topmost descendant, which is root1.firstChild.
    248    // [end] root2.lastChild is the end container, so we look for previous
    249    //       nodes and get root2.firstChild
    250    // Iteration #1: root1.firstChild, as it's the start node
    251    // Iteration #2: outerText2, as it's the next available node
    252    // Iteration #3: host2, as it's the next sibling of outerText2. Since it's
    253    //               the ancestor of the end node, so we get into this tree and returns
    254    //               root2.firstChild here.
    255    check(iter, [root1.firstChild, outerText2, root2.firstChild], "Initialized with range selecting 'InnerText1, OuterText2 and InnerText2'");
    256 
    257    SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(root1.firstChild, 1);
    258    SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(root4.firstChild, root4.firstChild.length);
    259    iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
    260    // [start] outerText2 is the start because root1.firstChild doesn't have children,
    261    //         so we look for next available node which is outerText2.
    262    // [end] host2 is the end container, so we look for previous
    263    //       nodes root4.firstChild and eventually get host2.
    264    // Iteration #1: outerText2, as it's the start node
    265    // Iteration #2: host2, as it's the next sibling of outerText2
    266    check(iter, [outerText2, host2], "Initialized with range selecting 'InnerText1, OuterText2, InnerText2 and InnerText3'");
    267 
    268    // From light to light
    269    SpecialPowers.wrap(range).setStartAllowCrossShadowBoundary(outerText1.firstChild, 0);
    270    SpecialPowers.wrap(range).setEndAllowCrossShadowBoundary(document.body.lastChild, document.body.lastChild.length);
    271    iter.initWithRangeAllowCrossShadowBoundary(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
    272    // [start] host1 is the start because it's the next available node of
    273    //         outerText1.firstChild.
    274    // [end] host3 is the end because the previous node of document.body.lastChild is host3.
    275    // Iteration #1: host1, as it's the start node
    276    // Iteration #2: outerText2, as it's the next sibling of host1
    277    // Iteration #3: host2, as it's the next sibling of outerText2
    278    // Iteration #4: host3, as it's the next sibling of host2
    279    check(iter, [host1, outerText2, host2, host3],
    280      "Initialized with range selecting 'OuterText1, InnerText1, OuterText2, InnerText2, InnerText3 and OuterText3'");
    281 
    282    finish();
    283  }
    284 
    285  SpecialPowers.pushPrefEnv({"set": [["dom.shadowdom.selection_across_boundary.enabled", true]]}, runTest);
    286 });
    287 </script>
    288 </head>
    289 <body></body>
    290 </html>