tor-browser

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

Node-moveBefore.html (14345B)


      1 <!DOCTYPE html>
      2 <title>Node.moveBefore</title>
      3 <script src="/resources/testharness.js"></script>
      4 <script src="/resources/testharnessreport.js"></script>
      5 <div id="log"></div>
      6 <!-- First test shared pre-insertion checks that work similarly for replaceChild
      7     and moveBefore -->
      8 <script>
      9  var insertFunc = Node.prototype.moveBefore;
     10 </script>
     11 <script src="../pre-insertion-validation-hierarchy.js"></script>
     12 <script>
     13 preInsertionValidateHierarchy("moveBefore");
     14 
     15 test(function() {
     16  // WebIDL: first argument.
     17  assert_throws_js(TypeError, function() { document.body.moveBefore(null, null) })
     18  assert_throws_js(TypeError, function() { document.body.moveBefore(null, document.body.firstChild) })
     19  assert_throws_js(TypeError, function() { document.body.moveBefore({'a':'b'}, document.body.firstChild) })
     20 }, "Calling moveBefore with a non-Node first argument must throw TypeError.")
     21 
     22 test(function() {
     23  // WebIDL: second argument.
     24  assert_throws_js(TypeError, function() { document.body.moveBefore(document.createTextNode("child")) })
     25  assert_throws_js(TypeError, function() { document.body.moveBefore(document.createTextNode("child"), {'a':'b'}) })
     26 }, "Calling moveBefore with second argument missing, or other than Node, null, or undefined, must throw TypeError.")
     27 
     28 test(() => {
     29  assert_false("moveBefore" in document.doctype, "moveBefore() not on DocumentType");
     30  assert_false("moveBefore" in document.createTextNode("text"), "moveBefore() not on TextNode");
     31  assert_false("moveBefore" in new Comment("comment"), "moveBefore() not on CommentNode");
     32  assert_false("moveBefore" in document.createProcessingInstruction("foo", "bar"), "moveBefore() not on ProcessingInstruction");
     33 }, "moveBefore() method does not exist on non-ParentNode Nodes");
     34 
     35 // Pre-move validity, step 1:
     36 // "If either parent or node are not connected, then throw a
     37 // "HierarchyRequestError" DOMException."
     38 //
     39 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity
     40 test(t => {
     41  const connectedTarget = document.body.appendChild(document.createElement('div'));
     42  const disconnectedDestination = document.createElement('div');
     43  t.add_cleanup(() => connectedTarget.remove());
     44 
     45  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
     46    disconnectedDestination.moveBefore(connectedTarget, null);
     47  });
     48 }, "moveBefore() on disconnected parent throws a HierarchyRequestError");
     49 test(t => {
     50  const connectedDestination = document.body.appendChild(document.createElement('div'));
     51  const disconnectedTarget = document.createElement('div');
     52  t.add_cleanup(() => connectedDestination.remove());
     53 
     54  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
     55    connectedDestination.moveBefore(disconnectedTarget, null);
     56  });
     57 }, "moveBefore() with disconnected target node throws a HierarchyRequestError");
     58 
     59 // Pre-move validity, step 2:
     60 // "If parent’s shadow-including root is not the same as node’s shadow-including
     61 // "root, then throw a "HierarchyRequestError" DOMException."
     62 //
     63 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity
     64 test(t => {
     65  const iframe = document.createElement('iframe');
     66  document.body.append(iframe);
     67  const connectedCrossDocChild = iframe.contentDocument.createElement('div');
     68  const connectedLocalParent = document.querySelector('div');
     69  t.add_cleanup(() => iframe.remove());
     70 
     71  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
     72    connectedLocalParent.moveBefore(connectedCrossDocChild, null);
     73  });
     74 }, "moveBefore() on a cross-document target node throws a HierarchyRequestError");
     75 
     76 // Pre-move validity, step 3:
     77 // "If parent is not a Document, DocumentFragment, or Element node, then throw a
     78 // "HierarchyRequestError" DOMException."
     79 //
     80 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity
     81 test(t => {
     82  const iframe = document.body.appendChild(document.createElement('iframe'));
     83  const innerBody = iframe.contentDocument.querySelector('body');
     84 
     85  assert_throws_dom("HIERARCHY_REQUEST_ERR", iframe.contentWindow.DOMException, () => {
     86    // Moving the body into the same place that it already is, which is a valid
     87    // action in the normal case, when moving an Element directly under the
     88    // document. This is not `moveBefore()`-specific behavior; it is consistent
     89    // with traditional Document insertion rules, just like `insertBefore()`.
     90    iframe.contentDocument.moveBefore(innerBody, null);
     91  });
     92 }, "moveBefore() into a Document throws a HierarchyRequestError");
     93 test(t => {
     94  const iframe = document.body.appendChild(document.createElement('iframe'));
     95  const comment = iframe.contentDocument.createComment("comment");
     96  iframe.contentDocument.body.append(comment);
     97 
     98  iframe.contentDocument.moveBefore(comment, null);
     99  assert_equals(comment.parentNode, iframe.contentDocument);
    100 }, "moveBefore() CharacterData into a Document");
    101 
    102 // Pre-move validity, step 4:
    103 // "If node is a host-including inclusive ancestor of parent, then throw a
    104 // "HierarchyRequestError" DOMException."
    105 //
    106 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity
    107 test(t => {
    108  const parentDiv = document.body.appendChild(document.createElement('div'));
    109  const childDiv = parentDiv.appendChild(document.createElement('div'));
    110  t.add_cleanup(() => {
    111    parentDiv.remove();
    112    childDiv.remove();
    113  });
    114 
    115  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
    116    parentDiv.moveBefore(parentDiv, null);
    117  }, "parent moving itself");
    118 
    119  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
    120    childDiv.moveBefore(parentDiv, null);
    121  }, "Moving parent into immediate child");
    122 
    123  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
    124    childDiv.moveBefore(document.body, null);
    125  }, "Moving grandparent into grandchild");
    126 
    127  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
    128    document.body.moveBefore(document.documentElement, childDiv);
    129  }, "Moving documentElement (<html>) into a deeper child");
    130 }, "moveBefore() with node being an inclusive ancestor of parent throws a " +
    131   "HierarchyRequestError");
    132 
    133 // Pre-move validity, step 5:
    134 // "If node is not an Element or a CharacterData node, then throw a
    135 // "HierarchyRequestError" DOMException."
    136 //
    137 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity
    138 test(t => {
    139  assert_true(document.doctype.isConnected);
    140  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
    141    document.body.moveBefore(document.doctype, null);
    142  }, "DocumentType throws");
    143 
    144  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
    145    document.body.moveBefore(new DocumentFragment(), null);
    146  }, "DocumentFragment throws");
    147 
    148  const doc = document.implementation.createHTMLDocument("title");
    149  assert_true(doc.isConnected);
    150  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
    151    document.body.moveBefore(doc, null);
    152  });
    153 }, "moveBefore() with a non-{Element, CharacterData} throws a HierarchyRequestError");
    154 promise_test(async t => {
    155  const text = new Text("child text");
    156  document.body.prepend(text);
    157 
    158  const childElement = document.createElement('p');
    159  document.body.prepend(childElement);
    160 
    161  const comment = new Comment("comment");
    162  document.body.prepend(comment);
    163 
    164  t.add_cleanup(() => {
    165    text.remove();
    166    childElement.remove();
    167    comment.remove();
    168  });
    169 
    170  // Wait until style is computed once, then continue after. This is necessary
    171  // to reproduce a Chromium crash regression with moving Comment nodes in the
    172  // DOM.
    173  await new Promise(r => {
    174    requestAnimationFrame(() => requestAnimationFrame(() => r()));
    175  });
    176 
    177  document.body.moveBefore(text, null);
    178  assert_equals(document.body.lastChild, text);
    179 
    180  document.body.moveBefore(childElement, null);
    181  assert_equals(document.body.lastChild, childElement);
    182 
    183  document.body.moveBefore(text, null);
    184  assert_equals(document.body.lastChild, text);
    185 
    186  document.body.moveBefore(comment, null);
    187  assert_equals(document.body.lastChild, comment);
    188 }, "moveBefore with an Element or CharacterData succeeds");
    189 test(t => {
    190  const p = document.createElement('p');
    191  p.textContent = "Some content";
    192  document.body.prepend(p);
    193 
    194  const text_node = p.firstChild;
    195 
    196  // The Text node is *inside* the paragraph.
    197  assert_equals(text_node.textContent, "Some content");
    198  assert_not_equals(document.body.lastChild, text_node);
    199 
    200  t.add_cleanup(() => {
    201    text_node.remove();
    202    p.remove();
    203  });
    204 
    205  document.body.moveBefore(p.firstChild, null);
    206  assert_equals(document.body.lastChild, text_node);
    207 }, "moveBefore on a paragraph's Text node child");
    208 
    209 // Pre-move validity, step 6:
    210 // "If child is non-null and its parent is not parent, then throw a
    211 // "NotFoundError" DOMException."
    212 //
    213 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity
    214 test(t => {
    215  const a = document.body.appendChild(document.createElement("div"));
    216  const b = document.body.appendChild(document.createElement("div"));
    217  const c = document.body.appendChild(document.createElement("div"));
    218 
    219  t.add_cleanup(() => {
    220    a.remove();
    221    b.remove();
    222    c.remove();
    223  });
    224 
    225  assert_throws_dom("NotFoundError", () => {
    226    a.moveBefore(b, c);
    227  });
    228 }, "moveBefore with reference child whose parent is NOT the destination " +
    229   "parent (context node) throws a NotFoundError.")
    230 
    231 test(() => {
    232  const a = document.body.appendChild(document.createElement("div"));
    233  const b = document.createElement("div");
    234  const c = document.createElement("div");
    235  a.append(b);
    236  a.append(c);
    237  assert_array_equals(a.childNodes, [b, c]);
    238  assert_equals(a.moveBefore(c, b), undefined, "moveBefore() returns undefined");
    239  assert_array_equals(a.childNodes, [c, b]);
    240 }, "moveBefore() returns undefined");
    241 
    242 test(() => {
    243  const a = document.body.appendChild(document.createElement("div"));
    244  const b = document.createElement("div");
    245  const c = document.createElement("div");
    246  a.append(b);
    247  a.append(c);
    248  assert_array_equals(a.childNodes, [b, c]);
    249  a.moveBefore(b, b);
    250  assert_array_equals(a.childNodes, [b, c]);
    251  a.moveBefore(c, c);
    252  assert_array_equals(a.childNodes, [b, c]);
    253 }, "Moving a node before itself should not move the node");
    254 
    255 test(() => {
    256  const disconnectedOrigin = document.createElement('div');
    257  const disconnectedDestination = document.createElement('div');
    258  const p = disconnectedOrigin.appendChild(document.createElement('p'));
    259 
    260  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
    261    disconnectedDestination.moveBefore(p, null);
    262  });
    263 }, "Moving a node from a disconnected container to a disconnected new parent " +
    264   "without a shared ancestor throws a HIERARCHY_REQUEST_ERR");
    265 
    266 test(() => {
    267  const disconnectedOrigin = document.createElement('div');
    268  const disconnectedDestination = disconnectedOrigin.appendChild(document.createElement('div'));
    269  const p = disconnectedOrigin.appendChild(document.createElement('p'));
    270 
    271  disconnectedDestination.moveBefore(p, null);
    272 
    273  assert_equals(disconnectedDestination.firstChild, p, "<p> Was successfully moved");
    274 }, "Moving a node from a disconnected container to a disconnected new parent in the same tree succeeds");
    275 
    276 test(() => {
    277  const disconnectedOrigin = document.createElement('div');
    278  const disconnectedHost = disconnectedOrigin.appendChild(document.createElement('div'));
    279  const p = disconnectedOrigin.appendChild(document.createElement('p'));
    280  const shadow = disconnectedHost.attachShadow({mode: "closed"});
    281  const disconnectedDestination = shadow.appendChild(document.createElement('div'));
    282 
    283  disconnectedDestination.moveBefore(p, null);
    284 
    285  assert_equals(disconnectedDestination.firstChild, p, "<p> Was successfully moved");
    286 }, "Moving a node from a disconnected container to a disconnected new parent in the same tree succeeds," +
    287   "also across shadow-roots");
    288 
    289 test(() => {
    290  const disconnectedOrigin = document.createElement('div');
    291  const connectedDestination = document.body.appendChild(document.createElement('div'));
    292  const p = disconnectedOrigin.appendChild(document.createElement('p'));
    293 
    294  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => connectedDestination.moveBefore(p, null));
    295 }, "Moving a node from disconnected->connected throws a HIERARCHY_REQUEST_ERR");
    296 
    297 test(() => {
    298  const connectedOrigin = document.body.appendChild(document.createElement('div'));
    299  const disconnectedDestination = document.createElement('div');
    300  const p = connectedOrigin.appendChild(document.createElement('p'));
    301 
    302  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => disconnectedDestination.moveBefore(p, null));
    303 }, "Moving a node from connected->disconnected throws a HIERARCHY_REQUEST_ERR");
    304 
    305 promise_test(async t => {
    306  let reactions = [];
    307  const element_name = `ce-${performance.now()}`;
    308  customElements.define(element_name,
    309    class MockCustomElement extends HTMLElement {
    310      connectedMoveCallback() { reactions.push("connectedMove"); }
    311      connectedCallback() { reactions.push("connected"); }
    312      disconnectedCallback() { reactions.push("disconnected"); }
    313    });
    314 
    315  const oldParent = document.createElement('div');
    316  const newParent = oldParent.appendChild(document.createElement('div'));
    317  const element = oldParent.appendChild(document.createElement(element_name));
    318  t.add_cleanup(() => {
    319    element.remove();
    320    newParent.remove();
    321    oldParent.remove();
    322  });
    323 
    324  // Wait a microtask to let any custom element reactions run (should be none,
    325  // since the initial parent is disconnected).
    326  await Promise.resolve();
    327 
    328  newParent.moveBefore(element, null);
    329  await Promise.resolve();
    330  assert_array_equals(reactions, []);
    331 }, "No custom element callbacks are run during disconnected moveBefore()");
    332 
    333 // This is a regression test for a Chromium crash: https://crbug.com/388934346.
    334 test(t => {
    335  // This test caused a crash in Chromium because after the detection of invalid
    336  // /node hierarchy, and throwing the JS error, we did not return from native
    337  // code, and continued to operate on the node tree on bad assumptions.
    338  const outer = document.createElement('div');
    339  const div = outer.appendChild(document.createElement('div'));
    340  assert_throws_dom("HIERARCHY_REQUEST_ERR", () => div.moveBefore(outer, null));
    341 }, "Invalid node hierarchy with null old parent does not crash");
    342 
    343 test(t => {
    344  const outerDiv = document.createElement('div');
    345  const innerDiv = outerDiv.appendChild(document.createElement('div'));
    346  const iframe = innerDiv.appendChild(document.createElement('iframe'));
    347  outerDiv.moveBefore(iframe, null);
    348 }, "Move disconnected iframe does not crash");
    349 </script>