tor-browser

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

Range-surroundContents.html (13471B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>Range.surroundContents() tests</title>
      4 <link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name>
      5 <meta name=timeout content=long>
      6 <p>To debug test failures, add a query parameter "subtest" with the test id (like
      7 "?subtest=5,16").  Only that test will be run.  Then you can look at the resulting
      8 iframes in the DOM.
      9 <div id=log></div>
     10 <script src=/resources/testharness.js></script>
     11 <script src=/resources/testharnessreport.js></script>
     12 <script src=../common.js></script>
     13 <script>
     14 "use strict";
     15 
     16 testDiv.parentNode.removeChild(testDiv);
     17 
     18 function mySurroundContents(range, newParent) {
     19  try {
     20    // "If a non-Text node is partially contained in the context object,
     21    // throw a "InvalidStateError" exception and terminate these steps."
     22    var node = range.commonAncestorContainer;
     23    var stop = nextNodeDescendants(node);
     24    for (; node != stop; node = nextNode(node)) {
     25      if (!isText(node)
     26      && isPartiallyContained(node, range)) {
     27        return "INVALID_STATE_ERR";
     28      }
     29    }
     30 
     31    // "If newParent is a Document, DocumentType, or DocumentFragment node,
     32    // throw an "InvalidNodeTypeError" exception and terminate these
     33    // steps."
     34    if (newParent.nodeType == Node.DOCUMENT_NODE
     35    || newParent.nodeType == Node.DOCUMENT_TYPE_NODE
     36    || newParent.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
     37      return "INVALID_NODE_TYPE_ERR";
     38    }
     39 
     40    // "Call extractContents() on the context object, and let fragment be
     41    // the result."
     42    var fragment = myExtractContents(range);
     43    if (typeof fragment == "string") {
     44      return fragment;
     45    }
     46 
     47    // "While newParent has children, remove its first child."
     48    while (newParent.childNodes.length) {
     49      newParent.removeChild(newParent.firstChild);
     50    }
     51 
     52    // "Call insertNode(newParent) on the context object."
     53    var ret = myInsertNode(range, newParent);
     54    if (typeof ret == "string") {
     55      return ret;
     56    }
     57 
     58    // "Call appendChild(fragment) on newParent."
     59    newParent.appendChild(fragment);
     60 
     61    // "Call selectNode(newParent) on the context object."
     62    //
     63    // We just reimplement this in-place.
     64    if (!newParent.parentNode) {
     65      return "INVALID_NODE_TYPE_ERR";
     66    }
     67    var index = indexOf(newParent);
     68    range.setStart(newParent.parentNode, index);
     69    range.setEnd(newParent.parentNode, index + 1);
     70  } catch (e) {
     71    return getDomExceptionName(e);
     72  }
     73 }
     74 
     75 function restoreIframe(iframe, i, j) {
     76  // Most of this function is designed to work around the fact that Opera
     77  // doesn't let you add a doctype to a document that no longer has one, in
     78  // any way I can figure out.  I eventually compromised on something that
     79  // will still let Opera pass most tests that don't actually involve
     80  // doctypes.
     81  while (iframe.contentDocument.firstChild
     82  && iframe.contentDocument.firstChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
     83    iframe.contentDocument.removeChild(iframe.contentDocument.firstChild);
     84  }
     85 
     86  while (iframe.contentDocument.lastChild
     87  && iframe.contentDocument.lastChild.nodeType != Node.DOCUMENT_TYPE_NODE) {
     88    iframe.contentDocument.removeChild(iframe.contentDocument.lastChild);
     89  }
     90 
     91  if (!iframe.contentDocument.firstChild) {
     92    // This will throw an exception in Opera if we reach here, which is why
     93    // I try to avoid it.  It will never happen in a browser that obeys the
     94    // spec, so it's really just insurance.  I don't think it actually gets
     95    // hit by anything.
     96    iframe.contentDocument.appendChild(iframe.contentDocument.implementation.createDocumentType("html", "", ""));
     97  }
     98  iframe.contentDocument.appendChild(referenceDoc.documentElement.cloneNode(true));
     99  iframe.contentWindow.setupRangeTests();
    100  iframe.contentWindow.testRangeInput = testRangesShort[i];
    101  iframe.contentWindow.testNodeInput = testNodesShort[j];
    102  iframe.contentWindow.run();
    103 }
    104 
    105 function testSurroundContents(i, j) {
    106  var actualRange;
    107  var expectedRange;
    108  var actualNode;
    109  var expectedNode;
    110  var actualRoots = [];
    111  var expectedRoots = [];
    112 
    113  domTests[i][j].step(function() {
    114    restoreIframe(actualIframe, i, j);
    115    restoreIframe(expectedIframe, i, j);
    116 
    117    actualRange = actualIframe.contentWindow.testRange;
    118    expectedRange = expectedIframe.contentWindow.testRange;
    119    actualNode = actualIframe.contentWindow.testNode;
    120    expectedNode = expectedIframe.contentWindow.testNode;
    121 
    122    assert_equals(actualIframe.contentWindow.unexpectedException, null,
    123      "Unexpected exception thrown when setting up Range for actual surroundContents()");
    124    assert_equals(expectedIframe.contentWindow.unexpectedException, null,
    125      "Unexpected exception thrown when setting up Range for simulated surroundContents()");
    126    assert_equals(typeof actualRange, "object",
    127      "typeof Range produced in actual iframe");
    128    assert_not_equals(actualRange, null,
    129      "Range produced in actual iframe was null");
    130    assert_equals(typeof expectedRange, "object",
    131      "typeof Range produced in expected iframe");
    132    assert_not_equals(expectedRange, null,
    133      "Range produced in expected iframe was null");
    134    assert_equals(typeof actualNode, "object",
    135      "typeof Node produced in actual iframe");
    136    assert_not_equals(actualNode, null,
    137      "Node produced in actual iframe was null");
    138    assert_equals(typeof expectedNode, "object",
    139      "typeof Node produced in expected iframe");
    140    assert_not_equals(expectedNode, null,
    141      "Node produced in expected iframe was null");
    142 
    143    // We want to test that the trees containing the ranges are equal, and
    144    // also the trees containing the moved nodes.  These might not be the
    145    // same, if we're inserting a node from a detached tree or a different
    146    // document.
    147    actualRoots.push(furthestAncestor(actualRange.startContainer));
    148    expectedRoots.push(furthestAncestor(expectedRange.startContainer));
    149 
    150    if (furthestAncestor(actualNode) != actualRoots[0]) {
    151      actualRoots.push(furthestAncestor(actualNode));
    152    }
    153    if (furthestAncestor(expectedNode) != expectedRoots[0]) {
    154      expectedRoots.push(furthestAncestor(expectedNode));
    155    }
    156 
    157    assert_equals(actualRoots.length, expectedRoots.length,
    158      "Either the actual node and actual range are in the same tree but the expected are in different trees, or vice versa");
    159 
    160    // This doctype stuff is to work around the fact that Opera 11.00 will
    161    // move around doctypes within a document, even to totally invalid
    162    // positions, but it won't allow a new doctype to be added to a
    163    // document in any way I can figure out.  So if we try moving a doctype
    164    // to some invalid place, in Opera it will actually succeed, and then
    165    // restoreIframe() will remove the doctype along with the root element,
    166    // and then nothing can re-add the doctype.  So instead, we catch it
    167    // during the test itself and move it back to the right place while we
    168    // still can.
    169    //
    170    // I spent *way* too much time debugging and working around this bug.
    171    var actualDoctype = actualIframe.contentDocument.doctype;
    172    var expectedDoctype = expectedIframe.contentDocument.doctype;
    173 
    174    var result;
    175    try {
    176      result = mySurroundContents(expectedRange, expectedNode);
    177    } catch (e) {
    178      if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
    179        expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
    180      }
    181      throw e;
    182    }
    183    if (typeof result == "string") {
    184      assert_throws_dom(result, actualIframe.contentWindow.DOMException, function() {
    185        try {
    186          actualRange.surroundContents(actualNode);
    187        } catch (e) {
    188          if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
    189            expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
    190          }
    191          if (actualDoctype != actualIframe.contentDocument.firstChild) {
    192            actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild);
    193          }
    194          throw e;
    195        }
    196      }, "A " + result + " must be thrown in this case");
    197      // Don't return, we still need to test DOM equality
    198    } else {
    199      try {
    200        actualRange.surroundContents(actualNode);
    201      } catch (e) {
    202        if (expectedDoctype != expectedIframe.contentDocument.firstChild) {
    203          expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild);
    204        }
    205        if (actualDoctype != actualIframe.contentDocument.firstChild) {
    206          actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild);
    207        }
    208        throw e;
    209      }
    210    }
    211 
    212    for (var k = 0; k < actualRoots.length; k++) {
    213      assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root");
    214    }
    215  });
    216  domTests[i][j].done();
    217 
    218  positionTests[i][j].step(function() {
    219    assert_equals(actualIframe.contentWindow.unexpectedException, null,
    220      "Unexpected exception thrown when setting up Range for actual surroundContents()");
    221    assert_equals(expectedIframe.contentWindow.unexpectedException, null,
    222      "Unexpected exception thrown when setting up Range for simulated surroundContents()");
    223    assert_equals(typeof actualRange, "object",
    224      "typeof Range produced in actual iframe");
    225    assert_not_equals(actualRange, null,
    226      "Range produced in actual iframe was null");
    227    assert_equals(typeof expectedRange, "object",
    228      "typeof Range produced in expected iframe");
    229    assert_not_equals(expectedRange, null,
    230      "Range produced in expected iframe was null");
    231    assert_equals(typeof actualNode, "object",
    232      "typeof Node produced in actual iframe");
    233    assert_not_equals(actualNode, null,
    234      "Node produced in actual iframe was null");
    235    assert_equals(typeof expectedNode, "object",
    236      "typeof Node produced in expected iframe");
    237    assert_not_equals(expectedNode, null,
    238      "Node produced in expected iframe was null");
    239 
    240    for (var k = 0; k < actualRoots.length; k++) {
    241      assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root");
    242    }
    243 
    244    assert_equals(actualRange.startOffset, expectedRange.startOffset,
    245      "Unexpected startOffset after surroundContents()");
    246    assert_equals(actualRange.endOffset, expectedRange.endOffset,
    247      "Unexpected endOffset after surroundContents()");
    248    // How do we decide that the two nodes are equal, since they're in
    249    // different trees?  Since the DOMs are the same, it's enough to check
    250    // that the index in the parent is the same all the way up the tree.
    251    // But we can first cheat by just checking they're actually equal.
    252    assert_true(actualRange.startContainer.isEqualNode(expectedRange.startContainer),
    253      "Unexpected startContainer after surroundContents(), expected " +
    254      expectedRange.startContainer.nodeName.toLowerCase() + " but got " +
    255      actualRange.startContainer.nodeName.toLowerCase());
    256    var currentActual = actualRange.startContainer;
    257    var currentExpected = expectedRange.startContainer;
    258    var actual = "";
    259    var expected = "";
    260    while (currentActual && currentExpected) {
    261      actual = indexOf(currentActual) + "-" + actual;
    262      expected = indexOf(currentExpected) + "-" + expected;
    263 
    264      currentActual = currentActual.parentNode;
    265      currentExpected = currentExpected.parentNode;
    266    }
    267    actual = actual.substr(0, actual.length - 1);
    268    expected = expected.substr(0, expected.length - 1);
    269    assert_equals(actual, expected,
    270      "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");
    271  });
    272  positionTests[i][j].done();
    273 }
    274 
    275 var iStart = 0;
    276 var iStop = testRangesShort.length;
    277 var jStart = 0;
    278 var jStop = testNodesShort.length;
    279 
    280 if (/subtest=[0-9]+,[0-9]+/.test(location.search)) {
    281  var matches = /subtest=([0-9]+),([0-9]+)/.exec(location.search);
    282  iStart = Number(matches[1]);
    283  iStop = Number(matches[1]) + 1;
    284  jStart = Number(matches[2]) + 0;
    285  jStop = Number(matches[2]) + 1;
    286 }
    287 
    288 var domTests = [];
    289 var positionTests = [];
    290 for (var i = iStart; i < iStop; i++) {
    291  domTests[i] = [];
    292  positionTests[i] = [];
    293  for (var j = jStart; j < jStop; j++) {
    294    domTests[i][j] = async_test(i + "," + j + ": resulting DOM for range " + testRangesShort[i] + ", node " + testNodesShort[j]);
    295    positionTests[i][j] = async_test(i + "," + j + ": resulting range position for range " + testRangesShort[i] + ", node " + testNodesShort[j]);
    296  }
    297 }
    298 
    299 var actualIframe = document.createElement("iframe");
    300 actualIframe.style.display = "none";
    301 actualIframe.id = "actual";
    302 document.body.appendChild(actualIframe);
    303 
    304 var expectedIframe = document.createElement("iframe");
    305 expectedIframe.style.display = "none";
    306 expectedIframe.id = "expected";
    307 document.body.appendChild(expectedIframe);
    308 
    309 var referenceDoc = document.implementation.createHTMLDocument("");
    310 referenceDoc.removeChild(referenceDoc.documentElement);
    311 
    312 actualIframe.onload = function() {
    313  expectedIframe.onload = function() {
    314    for (var i = iStart; i < iStop; i++) {
    315      for (var j = jStart; j < jStop; j++) {
    316        testSurroundContents(i, j);
    317      }
    318    }
    319  }
    320  expectedIframe.src = "Range-test-iframe.html";
    321  referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
    322 }
    323 actualIframe.src = "Range-test-iframe.html";
    324 </script>