tor-browser

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

Range-deleteContents.html (14433B)


      1 <!doctype html>
      2 <title>Range.deleteContents() tests</title>
      3 <link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
      4 <meta name=timeout content=long>
      5 <p>To debug test failures, add a query parameter "subtest" with the test id (like
      6 "?subtest=5").  Only that test will be run.  Then you can look at the resulting
      7 iframe in the DOM.
      8 <div id=log></div>
      9 <script src=/resources/testharness.js></script>
     10 <script src=/resources/testharnessreport.js></script>
     11 <script src=../common.js></script>
     12 <script>
     13 "use strict";
     14 
     15 testDiv.parentNode.removeChild(testDiv);
     16 
     17 var actualIframe = document.createElement("iframe");
     18 actualIframe.style.display = "none";
     19 document.body.appendChild(actualIframe);
     20 
     21 var expectedIframe = document.createElement("iframe");
     22 expectedIframe.style.display = "none";
     23 document.body.appendChild(expectedIframe);
     24 
     25 function restoreIframe(iframe, i) {
     26  // Most of this function is designed to work around the fact that Opera
     27  // doesn't let you add a doctype to a document that no longer has one, in
     28  // any way I can figure out.  I eventually compromised on something that
     29  // will still let Opera pass most tests that don't actually involve
     30  // doctypes.
     31  while (iframe.contentDocument.firstChild
     32  && iframe.contentDocument.firstChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
     33    iframe.contentDocument.removeChild(iframe.contentDocument.firstChild);
     34  }
     35 
     36  while (iframe.contentDocument.lastChild
     37  && iframe.contentDocument.lastChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
     38    iframe.contentDocument.removeChild(iframe.contentDocument.lastChild);
     39  }
     40 
     41  if (!iframe.contentDocument.firstChild) {
     42    // This will throw an exception in Opera if we reach here, which is why
     43    // I try to avoid it.  It will never happen in a browser that obeys the
     44    // spec, so it's really just insurance.  I don't think it actually gets
     45    // hit by anything.
     46    iframe.contentDocument.appendChild(iframe.contentDocument.implementation.createDocumentType("html", "", ""));
     47  }
     48  iframe.contentDocument.appendChild(referenceDoc.documentElement.cloneNode(true));
     49  iframe.contentWindow.setupRangeTests();
     50  iframe.contentWindow.testRangeInput = testRanges[i];
     51  iframe.contentWindow.run();
     52 }
     53 
     54 function myDeleteContents(range) {
     55  // "If the context object's start and end are the same, abort this method."
     56  if (range.startContainer == range.endContainer
     57  && range.startOffset == range.endOffset) {
     58    return;
     59  }
     60 
     61  // "Let original start node, original start offset, original end node, and
     62  // original end offset be the context object's start and end nodes and
     63  // offsets, respectively."
     64  var originalStartNode = range.startContainer;
     65  var originalStartOffset = range.startOffset;
     66  var originalEndNode = range.endContainer;
     67  var originalEndOffset = range.endOffset;
     68 
     69  // "If original start node and original end node are the same, and they are
     70  // a Text, ProcessingInstruction, or Comment node, replace data with node
     71  // original start node, offset original start offset, count original end
     72  // offset minus original start offset, and data the empty string, and then
     73  // terminate these steps"
     74  if (originalStartNode == originalEndNode
     75  && (isText(range.startContainer)
     76  || range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE
     77  || range.startContainer.nodeType == Node.COMMENT_NODE)) {
     78    originalStartNode.deleteData(originalStartOffset, originalEndOffset - originalStartOffset);
     79    return;
     80  }
     81 
     82  // "Let nodes to remove be a list of all the Nodes that are contained in
     83  // the context object, in tree order, omitting any Node whose parent is
     84  // also contained in the context object."
     85  //
     86  // We rely on the fact that the contained nodes must lie in tree order
     87  // between the start node, and the end node's last descendant (inclusive).
     88  var nodesToRemove = [];
     89  var stop = nextNodeDescendants(range.endContainer);
     90  for (var node = range.startContainer; node != stop; node = nextNode(node)) {
     91    if (isContained(node, range)
     92    && !(node.parentNode && isContained(node.parentNode, range))) {
     93      nodesToRemove.push(node);
     94    }
     95  }
     96 
     97  // "If original start node is an ancestor container of original end node,
     98  // set new node to original start node and new offset to original start
     99  // offset."
    100  var newNode;
    101  var newOffset;
    102  if (originalStartNode == originalEndNode
    103  || originalEndNode.compareDocumentPosition(originalStartNode) & Node.DOCUMENT_POSITION_CONTAINS) {
    104    newNode = originalStartNode;
    105    newOffset = originalStartOffset;
    106  // "Otherwise:"
    107  } else {
    108    // "Let reference node equal original start node."
    109    var referenceNode = originalStartNode;
    110 
    111    // "While reference node's parent is not null and is not an ancestor
    112    // container of original end node, set reference node to its parent."
    113    while (referenceNode.parentNode
    114    && referenceNode.parentNode != originalEndNode
    115    && !(originalEndNode.compareDocumentPosition(referenceNode.parentNode) & Node.DOCUMENT_POSITION_CONTAINS)) {
    116      referenceNode = referenceNode.parentNode;
    117    }
    118 
    119    // "Set new node to the parent of reference node, and new offset to one
    120    // plus the index of reference node."
    121    newNode = referenceNode.parentNode;
    122    newOffset = 1 + indexOf(referenceNode);
    123  }
    124 
    125  // "If original start node is a Text, ProcessingInstruction, or Comment node,
    126  // replace data with node original start node, offset original start offset,
    127  // count original start node's length minus original start offset, data the
    128  // empty start"
    129  if (isText(originalStartNode)
    130  || originalStartNode.nodeType == Node.PROCESSING_INSTRUCTION_NODE
    131  || originalStartNode.nodeType == Node.COMMENT_NODE) {
    132    originalStartNode.deleteData(originalStartOffset, nodeLength(originalStartNode) - originalStartOffset);
    133  }
    134 
    135  // "For each node in nodes to remove, in order, remove node from its
    136  // parent."
    137  for (var i = 0; i < nodesToRemove.length; i++) {
    138    nodesToRemove[i].parentNode.removeChild(nodesToRemove[i]);
    139  }
    140 
    141  // "If original end node is a Text, ProcessingInstruction, or Comment node,
    142  // replace data with node original end node, offset 0, count original end
    143  // offset, and data the empty string."
    144  if (isText(originalEndNode)
    145  || originalEndNode.nodeType == Node.PROCESSING_INSTRUCTION_NODE
    146  || originalEndNode.nodeType == Node.COMMENT_NODE) {
    147    originalEndNode.deleteData(0, originalEndOffset);
    148  }
    149 
    150  // "Set the context object's start and end to (new node, new offset)."
    151  range.setStart(newNode, newOffset);
    152  range.setEnd(newNode, newOffset);
    153 }
    154 
    155 function testDeleteContents(i) {
    156  restoreIframe(actualIframe, i);
    157  restoreIframe(expectedIframe, i);
    158 
    159  var actualRange = actualIframe.contentWindow.testRange;
    160  var expectedRange = expectedIframe.contentWindow.testRange;
    161  var actualRoots, expectedRoots;
    162 
    163  domTests[i].step(function() {
    164    assert_equals(actualIframe.contentWindow.unexpectedException, null,
    165      "Unexpected exception thrown when setting up Range for actual deleteContents()");
    166    assert_equals(expectedIframe.contentWindow.unexpectedException, null,
    167      "Unexpected exception thrown when setting up Range for simulated deleteContents()");
    168    assert_equals(typeof actualRange, "object",
    169      "typeof Range produced in actual iframe");
    170    assert_equals(typeof expectedRange, "object",
    171      "typeof Range produced in expected iframe");
    172 
    173    // Just to be pedantic, we'll test not only that the tree we're
    174    // modifying is the same in expected vs. actual, but also that all the
    175    // nodes originally in it were the same.  Typically some nodes will
    176    // become detached when the algorithm is run, but they still exist and
    177    // references can still be kept to them, so they should also remain the
    178    // same.
    179    //
    180    // We initialize the list to all nodes, and later on remove all the
    181    // ones which still have parents, since the parents will presumably be
    182    // tested for isEqualNode() and checking the children would be
    183    // redundant.
    184    var actualAllNodes = [];
    185    var node = furthestAncestor(actualRange.startContainer);
    186    do {
    187      actualAllNodes.push(node);
    188    } while (node = nextNode(node));
    189 
    190    var expectedAllNodes = [];
    191    var node = furthestAncestor(expectedRange.startContainer);
    192    do {
    193      expectedAllNodes.push(node);
    194    } while (node = nextNode(node));
    195 
    196    actualRange.deleteContents();
    197    myDeleteContents(expectedRange);
    198 
    199    actualRoots = [];
    200    for (var j = 0; j < actualAllNodes.length; j++) {
    201      if (!actualAllNodes[j].parentNode) {
    202        actualRoots.push(actualAllNodes[j]);
    203      }
    204    }
    205 
    206    expectedRoots = [];
    207    for (var j = 0; j < expectedAllNodes.length; j++) {
    208      if (!expectedAllNodes[j].parentNode) {
    209        expectedRoots.push(expectedAllNodes[j]);
    210      }
    211    }
    212 
    213    for (var j = 0; j < actualRoots.length; j++) {
    214      if (!actualRoots[j].isEqualNode(expectedRoots[j])) {
    215        var msg = j ? "detached node #" + j : "tree root";
    216        msg = "Actual and expected mismatch for " + msg + ".  ";
    217 
    218        // Find the specific error
    219        var actual = actualRoots[j];
    220        var expected = expectedRoots[j];
    221 
    222        while (actual && expected) {
    223          assert_equals(actual.nodeType, expected.nodeType,
    224            msg + "First difference: differing nodeType");
    225          assert_equals(actual.nodeName, expected.nodeName,
    226            msg + "First difference: differing nodeName");
    227          assert_equals(actual.nodeValue, expected.nodeValue,
    228            msg + 'First difference: differing nodeValue (nodeName = "' + actual.nodeName + '")');
    229          assert_equals(actual.childNodes.length, expected.childNodes.length,
    230            msg + 'First difference: differing number of children (nodeName = "' + actual.nodeName + '")');
    231          actual = nextNode(actual);
    232          expected = nextNode(expected);
    233        }
    234 
    235        assert_unreached("DOMs were not equal but we couldn't figure out why");
    236      }
    237 
    238      if (j == 0) {
    239        // Clearly something is wrong if the node lists are different
    240        // lengths.  We want to report this only after we've already
    241        // checked the main tree for equality, though, so it doesn't
    242        // mask more interesting errors.
    243        assert_equals(actualRoots.length, expectedRoots.length,
    244          "Actual and expected DOMs were broken up into a different number of pieces by deleteContents() (this probably means you created or detached nodes when you weren't supposed to)");
    245      }
    246    }
    247  });
    248  domTests[i].done();
    249 
    250  positionTests[i].step(function() {
    251    assert_equals(actualIframe.contentWindow.unexpectedException, null,
    252      "Unexpected exception thrown when setting up Range for actual deleteContents()");
    253    assert_equals(expectedIframe.contentWindow.unexpectedException, null,
    254      "Unexpected exception thrown when setting up Range for simulated deleteContents()");
    255    assert_equals(typeof actualRange, "object",
    256      "typeof Range produced in actual iframe");
    257    assert_equals(typeof expectedRange, "object",
    258      "typeof Range produced in expected iframe");
    259    assert_true(actualRoots[0].isEqualNode(expectedRoots[0]),
    260      "The resulting DOMs were not equal, so comparing positions makes no sense");
    261 
    262    assert_equals(actualRange.startContainer, actualRange.endContainer,
    263      "startContainer and endContainer must always be the same after deleteContents()");
    264    assert_equals(actualRange.startOffset, actualRange.endOffset,
    265      "startOffset and endOffset must always be the same after deleteContents()");
    266    assert_equals(expectedRange.startContainer, expectedRange.endContainer,
    267      "Test bug!  Expected startContainer and endContainer must always be the same after deleteContents()");
    268    assert_equals(expectedRange.startOffset, expectedRange.endOffset,
    269      "Test bug!  Expected startOffset and endOffset must always be the same after deleteContents()");
    270 
    271    assert_equals(actualRange.startOffset, expectedRange.startOffset,
    272      "Unexpected startOffset after deleteContents()");
    273    // How do we decide that the two nodes are equal, since they're in
    274    // different trees?  Since the DOMs are the same, it's enough to check
    275    // that the index in the parent is the same all the way up the tree.
    276    // But we can first cheat by just checking they're actually equal.
    277    assert_true(actualRange.startContainer.isEqualNode(expectedRange.startContainer),
    278      "Unexpected startContainer after deleteContents(), expected " +
    279      expectedRange.startContainer.nodeName.toLowerCase() + " but got " +
    280      actualRange.startContainer.nodeName.toLowerCase());
    281    var currentActual = actualRange.startContainer;
    282    var currentExpected = expectedRange.startContainer;
    283    var actual = "";
    284    var expected = "";
    285    while (currentActual && currentExpected) {
    286      actual = indexOf(currentActual) + "-" + actual;
    287      expected = indexOf(currentExpected) + "-" + expected;
    288 
    289      currentActual = currentActual.parentNode;
    290      currentExpected = currentExpected.parentNode;
    291    }
    292    actual = actual.substr(0, actual.length - 1);
    293    expected = expected.substr(0, expected.length - 1);
    294    assert_equals(actual, expected,
    295      "startContainer superficially looks right but is actually the wrong node if you trace back its index in all its ancestors (I'm surprised this actually happened");
    296  });
    297  positionTests[i].done();
    298 }
    299 
    300 // First test a detached Range, synchronously
    301 test(function() {
    302  var range = document.createRange();
    303  range.detach();
    304  range.deleteContents();
    305 }, "Detached Range");
    306 
    307 var iStart = 0;
    308 var iStop = testRanges.length;
    309 
    310 if (/subtest=[0-9]+/.test(location.search)) {
    311  var matches = /subtest=([0-9]+)/.exec(location.search);
    312  iStart = Number(matches[1]);
    313  iStop = Number(matches[1]) + 1;
    314 }
    315 
    316 var domTests = [];
    317 var positionTests = [];
    318 
    319 for (var i = iStart; i < iStop; i++) {
    320  domTests[i] = async_test("Resulting DOM for range " + i + " " + testRanges[i]);
    321  positionTests[i] = async_test("Resulting cursor position for range " + i + " " + testRanges[i]);
    322 }
    323 
    324 var referenceDoc = document.implementation.createHTMLDocument("");
    325 referenceDoc.removeChild(referenceDoc.documentElement);
    326 
    327 actualIframe.onload = function() {
    328  expectedIframe.onload = function() {
    329    for (var i = iStart; i < iStop; i++) {
    330      testDeleteContents(i);
    331    }
    332  }
    333  expectedIframe.src = "Range-test-iframe.html";
    334  referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
    335 }
    336 actualIframe.src = "Range-test-iframe.html";
    337 </script>