tor-browser

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

basic-dom-part-objects.tentative.html (16304B)


      1 <!DOCTYPE html>
      2 <title>DOM Parts: Basic object structure, imperative API</title>
      3 <link rel=author href="mailto:masonf@chromium.org">
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src="./resources/domparts-utils.js"></script>
      7 
      8 <body>
      9 <template id=imperative>
     10  <div>
     11    <div id=target1 style="display:none">
     12      Imperative test element
     13      <span id=a>A</span><span id=b>B
     14        <span id=sub>B-sub1</span>
     15        <span id=sub>B-sub2</span>
     16      </span><span id=c>C</span></div>
     17  </div>
     18  <span id=direct_child_1></span>
     19  <span id=direct_child_2></span>
     20 </template>
     21 
     22 <script>
     23 const template = document.getElementById('imperative');
     24 function addCleanup(t, part) {
     25  t.add_cleanup(() => part.disconnect());
     26  return part;
     27 }
     28 [false,true].forEach(useTemplate => {
     29  const doc = useTemplate ? template.content : document;
     30  let target,wrapper,directChildren;
     31  if (useTemplate) {
     32    target = doc.querySelector('#target1');
     33    directChildren = [doc.querySelector('#direct_child_1'),doc.querySelector('#direct_child_2')];
     34  } else {
     35    wrapper = document.body.appendChild(document.createElement('div'));
     36    wrapper.appendChild(template.content.cloneNode(true));
     37    target = wrapper.querySelector('#target1');
     38    directChildren = [doc.documentElement,doc.documentElement];
     39  }
     40  const a = target.querySelector('#a');
     41  const b = target.querySelector('#b');
     42  const c = target.querySelector('#c');
     43  assert_true(!!(doc && target && target.parentElement && a && b && c));
     44  const description = useTemplate ? "DocumentFragment" : "Document";
     45  test((t) => {
     46    const root = doc.getPartRoot();
     47    assert_true(root instanceof DocumentPartRoot);
     48    const parts = root.getParts();
     49    assert_equals(parts.length,0,'getParts() should start out empty');
     50    assert_true(root.rootContainer instanceof (useTemplate ? DocumentFragment : Document));
     51 
     52    const nodePart = addCleanup(t,new NodePart(root,target,{metadata: ['foo']}));
     53    assertEqualParts([nodePart],[{type:'NodePart',metadata:['foo']}],0,'Basic NodePart');
     54    assert_equals(nodePart.node,target);
     55    assert_equals(nodePart.root,root);
     56    let runningPartsExpectation = [{type:'NodePart',metadata:['foo']}];
     57    assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart],'getParts() for the root should now have this nodePart');
     58    assert_equals(parts.length,0,'Return value of getParts() is not live');
     59 
     60    assert_throws_js(TypeError,() => new NodePart(nodePart,target.children[0]),'Constructing a Part with a NodePart as the PartRoot should throw');
     61 
     62    const attributePart = addCleanup(t,new AttributePart(root,target,'attributename',{metadata: ['attribute']}));
     63    assertEqualParts([attributePart],[{type:'AttributePart',metadata:['attribute']}],0,'Basic AttributePart');
     64    assert_equals(attributePart.node,target);
     65    assert_equals(attributePart.root,root);
     66    assert_equals(attributePart.localName,'attributename');
     67    runningPartsExpectation.push({type:'AttributePart',metadata:['attribute']});
     68    assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,attributePart],'getParts() for the root should now have this attributePart');
     69    assert_equals(parts.length,0,'Return value of getParts() is not live');
     70 
     71    const childNodePart = addCleanup(t,new ChildNodePart(root,target.children[0], target.children[2],{metadata:['bar','baz']}));
     72    assertEqualParts([childNodePart],[{type:'ChildNodePart',metadata:['bar','baz']}],0,'Basic ChildNodePart');
     73    assert_equals(childNodePart.root,root);
     74    assert_equals(childNodePart.previousSibling,target.children[0]);
     75    assert_equals(childNodePart.nextSibling,target.children[2]);
     76    assert_equals(childNodePart.getParts().length,0,'childNodePart.getParts() should start out empty');
     77    runningPartsExpectation.push({type:'ChildNodePart',metadata:['bar','baz']});
     78    assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,attributePart,childNodePart],'getParts() for the root should now have this childNodePart');
     79 
     80    const nodeBefore = target.previousSibling || target.parentNode;
     81    const nodePartBefore = addCleanup(t,new NodePart(root,nodeBefore));
     82    runningPartsExpectation.push({type:'NodePart',metadata:[]});
     83    assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,attributePart,childNodePart,nodePartBefore],'getParts() for the root should now have this nodePart, in construction order');
     84 
     85    const nodePart2 = addCleanup(t,new NodePart(childNodePart,target.children[2],{metadata:['blah']}));
     86    assert_equals(nodePart2.root,childNodePart);
     87    assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,attributePart,childNodePart,nodePartBefore],'getParts() for the root DocumentPartRoot shouldn\'t change');
     88    assertEqualParts(childNodePart.getParts(),[{type:'NodePart',metadata:['blah']}],[nodePart2],'getParts() for the childNodePart should have it');
     89 
     90    nodePart2.disconnect();
     91    assert_equals(nodePart2.root,null,'root should be null after disconnect');
     92    assert_equals(nodePart2.node,null,'node should be null after disconnect');
     93    assert_equals(childNodePart.getParts().length,0,'calling disconnect() should remove the part from root.getParts()');
     94    assertEqualParts(root.getParts(),runningPartsExpectation,[nodePart,attributePart,childNodePart,nodePartBefore],'getParts() for the root DocumentPartRoot still shouldn\'t change');
     95    nodePart2.disconnect(); // Calling twice should be ok.
     96 
     97    childNodePart.disconnect();
     98    assert_equals(childNodePart.root,null,'root should be null after disconnect');
     99    assert_equals(childNodePart.previousSibling,null,'previousSibling should be null after disconnect');
    100    assert_equals(childNodePart.nextSibling,null,'nextSibling should be null after disconnect');
    101    assert_array_equals(root.getParts(),[nodePartBefore,nodePart,attributePart]);
    102  }, `Basic imperative DOM Parts object construction (${description})`);
    103 
    104  function cloneRange(parent,previousSibling,nextSibling) {
    105    const clone = parent.cloneNode(false);
    106    let node = previousSibling;
    107    while (node) {
    108      clone.appendChild(node.cloneNode(true));
    109      if (node == nextSibling) {
    110        break;
    111      }
    112      node = node.nextSibling;
    113    }
    114    return clone;
    115  }
    116 
    117  test((t) => {
    118    const root = doc.getPartRoot();
    119    const nodePart = addCleanup(t,new NodePart(root,target,{metadata:['node1']}));
    120    const attributePart = addCleanup(t,new AttributePart(root,target,'attributeName',{metadata: ['attribute']}));
    121    const childNodePart = addCleanup(t,new ChildNodePart(root,target.children[0], target.children[2],{metadata:['child']}));
    122    const nodePart3 = addCleanup(t,new NodePart(childNodePart,target.children[1].firstChild,{metadata: ['node 3']}));
    123    const nodePart2 = addCleanup(t,new NodePart(childNodePart,target.children[1].firstChild,{metadata: ['node 2']}));
    124    const childNodePart2 = addCleanup(t,new ChildNodePart(childNodePart,target.children[1].firstElementChild,target.children[1].firstElementChild.nextSibling,{metadata: ['childnodepart2']}));
    125    let rootExpectations = [{type:'NodePart',metadata:['node1']},{type:'AttributePart',metadata:['attribute']},{type:'ChildNodePart',metadata:['child']}];
    126    assertEqualParts(root.getParts(),rootExpectations,[nodePart,attributePart,childNodePart],'setup');
    127    let childExpectations = [{type:'NodePart',metadata:['node 3']},{type:'NodePart',metadata:['node 2']},{type:'ChildNodePart',metadata:['childnodepart2']}];
    128    assertEqualParts(childNodePart.getParts(),childExpectations,[nodePart3,nodePart2,childNodePart2],'setup');
    129    assert_array_equals(childNodePart2.getParts(),[]);
    130 
    131    // Test cloning of the entire DocumentPartRoot.
    132    const clonedPartRoot = root.clone();
    133    assertEqualParts(root.getParts(),rootExpectations,[nodePart,attributePart,childNodePart],'cloning a part root should not change the original');
    134    const clonedContainer = clonedPartRoot.rootContainer;
    135    assert_true(clonedPartRoot instanceof DocumentPartRoot);
    136    assert_true(clonedContainer instanceof (useTemplate ? DocumentFragment : Document));
    137    assert_not_equals(clonedPartRoot,root);
    138    assert_not_equals(clonedContainer,doc);
    139    assert_equals(doc.innerHTML,clonedContainer.innerHTML);
    140    assertEqualParts(clonedPartRoot.getParts(),rootExpectations,0,'cloned PartRoot should contain identical parts');
    141    assert_true(!clonedPartRoot.getParts().includes(nodePart),'Original parts should not be retained');
    142    assert_true(!clonedPartRoot.getParts().includes(childNodePart));
    143    const newNodePart = clonedPartRoot.getParts()[0];
    144    const newAttributePart = clonedPartRoot.getParts()[1];
    145    const newChildNodePart = clonedPartRoot.getParts()[2];
    146    assert_not_equals(newNodePart.node,target,'Node references should not point to original nodes');
    147    assert_equals(newNodePart.node.id,target.id,'New parts should point to cloned nodes');
    148    assert_not_equals(newAttributePart.node,target,'Node references should not point to original nodes');
    149    assert_equals(newAttributePart.node.id,target.id,'New parts should point to cloned nodes');
    150    assert_equals(newAttributePart.localName,attributePart.localName,'New attribute parts should carry over localName');
    151    assert_not_equals(newChildNodePart.previousSibling,a,'Node references should not point to original nodes');
    152    assert_equals(newChildNodePart.previousSibling.id,'a');
    153    assert_not_equals(newChildNodePart.nextSibling,c,'Node references should not point to original nodes');
    154    assert_equals(newChildNodePart.nextSibling.id,'c');
    155    assertEqualParts(newChildNodePart.getParts(),childExpectations,0,'cloned PartRoot should contain identical parts');
    156 
    157    // Test cloning of ChildNodeParts.
    158    const clonedChildNodePartRoot = childNodePart.clone();
    159    const clonedChildContainer = clonedChildNodePartRoot.rootContainer;
    160    assert_true(clonedChildNodePartRoot instanceof ChildNodePart);
    161    assert_true(clonedChildContainer instanceof Element);
    162    assert_not_equals(clonedChildContainer,target);
    163    assert_equals(clonedChildContainer.outerHTML,cloneRange(target,a,c).outerHTML);
    164    assertEqualParts(clonedChildNodePartRoot.getParts(),childExpectations,0,'clone of childNodePart should match');
    165  }, `Cloning (${description})`);
    166 
    167  ['Element','Text','Comment'].forEach(nodeType => {
    168    test((t) => {
    169      const root = doc.getPartRoot();
    170      assert_equals(root.getParts().length,0);
    171      let node;
    172      switch (nodeType) {
    173        case 'Element' : node = document.createElement('div'); break;
    174        case 'Text' : node = document.createTextNode('hello'); break;
    175        case 'Comment': node = document.createComment('comment'); break;
    176      }
    177      t.add_cleanup(() => node.remove());
    178      doc.firstElementChild.append(node);
    179      // NodePart
    180      const nodePart = addCleanup(t,new NodePart(root,node,{metadata:['foobar']}));
    181      assert_true(!!nodePart);
    182      const clone = root.clone();
    183      assert_equals(clone.getParts().length,1);
    184      assertEqualParts(clone.getParts(),[{type:'NodePart',metadata:['foobar']}],0,'getParts');
    185      assert_true(clone.getParts()[0].node instanceof window[nodeType]);
    186 
    187      // ChildNodePart
    188      const node2 = node.cloneNode(false);
    189      node.parentElement.appendChild(node2);
    190      const childNodePart = addCleanup(t,new ChildNodePart(root,node,node2,{metadata:['baz']}));
    191      assert_true(!!childNodePart);
    192      const clone2 = root.clone();
    193      assert_equals(clone2.getParts().length,2);
    194      assertEqualParts(clone2.getParts(),[{type:'NodePart',metadata:['foobar']},{type:'ChildNodePart',metadata:['baz']}],0,'getParts2');
    195      assert_true(clone2.getParts()[1].previousSibling instanceof window[nodeType]);
    196    }, `Cloning ${nodeType} (${description})`);
    197  });
    198 
    199  test((t) => {
    200    const root = doc.getPartRoot();
    201    assert_equals(root.getParts().length,0,'Test harness check: tests should clean up parts');
    202 
    203    const nodePartB = addCleanup(t,new NodePart(root,b));
    204    const nodePartA = addCleanup(t,new NodePart(root,a));
    205    const nodePartC = addCleanup(t,new NodePart(root,c));
    206    assert_array_equals(root.getParts(),[nodePartB,nodePartA,nodePartC],'Parts can be out of order, if added out of order');
    207    b.remove();
    208    assert_array_equals(root.getParts(),[nodePartB,nodePartA,nodePartC],'Removals are not tracked');
    209    target.parentElement.insertBefore(b,target);
    210    assert_array_equals(root.getParts(),[nodePartB,nodePartA,nodePartC],'Insertions are not tracked');
    211    target.insertBefore(b,c);
    212    assert_array_equals(root.getParts(),[nodePartB,nodePartA,nodePartC],'Nothing is tracked');
    213    nodePartA.disconnect();
    214    nodePartB.disconnect();
    215    nodePartC.disconnect();
    216    assert_array_equals(root.getParts(),[],'disconnections are tracked');
    217 
    218    const childPartAC = addCleanup(t,new ChildNodePart(root,a,c));
    219    assert_array_equals(root.getParts(),[childPartAC]);
    220    a.remove();
    221    assert_array_equals(root.getParts(),[],'Removing endpoints invalidates the part');
    222    target.insertBefore(a,b); // Restore
    223    assert_array_equals(root.getParts(),[],'Insertions are not tracked');
    224 
    225    target.insertBefore(c,a);
    226    assert_array_equals(root.getParts(),[],'Endpoints out of order');
    227    target.appendChild(c); // Restore
    228    assert_array_equals(root.getParts(),[],'Insertions are not tracked');
    229 
    230    document.body.appendChild(c);
    231    assert_array_equals(root.getParts(),[],'Parts are\'t invalidated when endpoints have different parents');
    232    target.appendChild(c); // Restore
    233    assert_array_equals(root.getParts(),[],'Insertions are not tracked');
    234 
    235    const oldParent = target.parentElement;
    236    target.remove();
    237    assert_array_equals(root.getParts(),[],'Parts are\'t invalidated when disconnected');
    238    oldParent.appendChild(target); // Restore
    239    assert_array_equals(root.getParts(),[]);
    240  }, `DOM mutations are not tracked (${description})`);
    241 
    242  test((t) => {
    243    const root = doc.getPartRoot();
    244    assert_equals(root.getParts().length,0,'Test harness check: tests should clean up parts');
    245    const otherNode = document.createElement('div');
    246 
    247    const childPartAA = addCleanup(t,new ChildNodePart(root,a,a));
    248    const childPartAB = addCleanup(t,new ChildNodePart(root,a,b));
    249    const childPartAC = addCleanup(t,new ChildNodePart(root,a,c));
    250    assert_throws_dom('InvalidStateError',() => childPartAA.replaceChildren(otherNode),'Can\'t replace children if part is invalid');
    251    assert_array_equals(childPartAA.children,[],'Invalid parts should return empty children');
    252    assert_array_equals(childPartAB.children,[],'Children should not include endpoints');
    253    assert_array_equals(childPartAC.children,[b],'Children should not include endpoints');
    254    childPartAB.replaceChildren(otherNode);
    255    assert_array_equals(childPartAB.children,[otherNode],'Replacechildren should work');
    256    assert_array_equals(childPartAC.children,[otherNode,b],'replaceChildren should leave endpoints alone');
    257    childPartAC.replaceChildren(otherNode);
    258    assert_array_equals(childPartAC.children,[otherNode],'Replacechildren with existing children should work');
    259    assert_array_equals(childPartAB.children,[]);
    260    childPartAC.replaceChildren(b);
    261    assert_array_equals(target.children,[a,b,c]);
    262  }, `ChildNodePart children manipulation (${description})`);
    263 
    264  test((t) => {
    265    const root = doc.getPartRoot();
    266    // Make sure no crashes occur for parts with mismatched endpoint nodes.
    267    const cornerCasePartsInvalid = [
    268      addCleanup(t,new ChildNodePart(root,target, target.children[2],{metadata: ['different parents']})),
    269      addCleanup(t,new ChildNodePart(root,target.children[0], target,{metadata: ['different parents']})),
    270      addCleanup(t,new ChildNodePart(root,target.children[2], target.children[0],{metadata: ['reversed endpoints']})),
    271    ];
    272    const cornerCasePartsValid = [];
    273    if (directChildren[0] !== directChildren[1]) {
    274      cornerCasePartsValid.push(addCleanup(t,new ChildNodePart(root,directChildren[0], directChildren[1],{metadata: ['direct parent of the root container']})));
    275    }
    276    assert_array_equals(root.getParts(),cornerCasePartsValid);
    277    assert_equals(root.clone().getParts().length,cornerCasePartsValid.length);
    278  }, `Corner case ChildNodePart construction and cloning (${description})`);
    279 
    280  wrapper?.remove(); // Cleanup
    281 });
    282 </script>