tor-browser

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

common.js (41767B)


      1 "use strict";
      2 // Written by Aryeh Gregor <ayg@aryeh.name>
      3 
      4 // TODO: iframes, contenteditable/designMode
      5 
      6 // Everything is done in functions in this test harness, so we have to declare
      7 // all the variables before use to make sure they can be reused.
      8 var testDiv, paras, detachedDiv, detachedPara1, detachedPara2,
      9    foreignDoc, foreignPara1, foreignPara2, xmlDoc, xmlElement,
     10    detachedXmlElement, detachedTextNode, foreignTextNode,
     11    detachedForeignTextNode, xmlTextNode, detachedXmlTextNode,
     12    processingInstruction, detachedProcessingInstruction, comment,
     13    detachedComment, foreignComment, detachedForeignComment, xmlComment,
     14    detachedXmlComment, docfrag, foreignDocfrag, xmlDocfrag, doctype,
     15    foreignDoctype, xmlDoctype;
     16 var testRangesShort, testRanges, testPoints, testNodesShort, testNodes;
     17 
     18 function setupRangeTests() {
     19    testDiv = document.querySelector("#test");
     20    if (testDiv) {
     21        testDiv.parentNode.removeChild(testDiv);
     22    }
     23    testDiv = document.createElement("div");
     24    testDiv.id = "test";
     25    document.body.insertBefore(testDiv, document.body.firstChild);
     26 
     27    paras = [];
     28    paras.push(document.createElement("p"));
     29    paras[0].setAttribute("id", "a");
     30    // Test some diacritics, to make sure browsers are using code units here
     31    // and not something like grapheme clusters.
     32    paras[0].textContent = "A\u0308b\u0308c\u0308d\u0308e\u0308f\u0308g\u0308h\u0308\n";
     33    testDiv.appendChild(paras[0]);
     34 
     35    paras.push(document.createElement("p"));
     36    paras[1].setAttribute("id", "b");
     37    paras[1].setAttribute("style", "display:none");
     38    paras[1].textContent = "Ijklmnop\n";
     39    testDiv.appendChild(paras[1]);
     40 
     41    paras.push(document.createElement("p"));
     42    paras[2].setAttribute("id", "c");
     43    paras[2].textContent = "Qrstuvwx";
     44    testDiv.appendChild(paras[2]);
     45 
     46    paras.push(document.createElement("p"));
     47    paras[3].setAttribute("id", "d");
     48    paras[3].setAttribute("style", "display:none");
     49    paras[3].textContent = "Yzabcdef";
     50    testDiv.appendChild(paras[3]);
     51 
     52    paras.push(document.createElement("p"));
     53    paras[4].setAttribute("id", "e");
     54    paras[4].setAttribute("style", "display:none");
     55    paras[4].textContent = "Ghijklmn";
     56    testDiv.appendChild(paras[4]);
     57 
     58    paras.push(document.createElement("p"));
     59    const xmlDocument = new Document();
     60    paras[5].appendChild(xmlDocument.createCDATASection("1234"));
     61    paras[5].appendChild(xmlDocument.createCDATASection("5678"));
     62    paras[5].append("9012");
     63    testDiv.appendChild(paras[5]);
     64 
     65    detachedDiv = document.createElement("div");
     66    detachedPara1 = document.createElement("p");
     67    detachedPara1.appendChild(document.createTextNode("Opqrstuv"));
     68    detachedPara2 = document.createElement("p");
     69    detachedPara2.appendChild(document.createTextNode("Wxyzabcd"));
     70    detachedDiv.appendChild(detachedPara1);
     71    detachedDiv.appendChild(detachedPara2);
     72 
     73    // Opera doesn't automatically create a doctype for a new HTML document,
     74    // contrary to spec.  It also doesn't let you add doctypes to documents
     75    // after the fact through any means I've tried.  So foreignDoc in Opera
     76    // will have no doctype, foreignDoctype will be null, and Opera will fail
     77    // some tests somewhat mysteriously as a result.
     78    foreignDoc = document.implementation.createHTMLDocument("");
     79    foreignPara1 = foreignDoc.createElement("p");
     80    foreignPara1.appendChild(foreignDoc.createTextNode("Efghijkl"));
     81    foreignPara2 = foreignDoc.createElement("p");
     82    foreignPara2.appendChild(foreignDoc.createTextNode("Mnopqrst"));
     83    foreignDoc.body.appendChild(foreignPara1);
     84    foreignDoc.body.appendChild(foreignPara2);
     85 
     86    // Now we get to do really silly stuff, which nobody in the universe is
     87    // ever going to actually do, but the spec defines behavior, so too bad.
     88    // Testing is fun!
     89    xmlDoctype = document.implementation.createDocumentType("qorflesnorf", "abcde", "x\"'y");
     90    xmlDoc = document.implementation.createDocument(null, null, xmlDoctype);
     91    detachedXmlElement = xmlDoc.createElement("everyone-hates-hyphenated-element-names");
     92    detachedTextNode = document.createTextNode("Uvwxyzab");
     93    detachedForeignTextNode = foreignDoc.createTextNode("Cdefghij");
     94    detachedXmlTextNode = xmlDoc.createTextNode("Klmnopqr");
     95    // PIs only exist in XML documents, so don't bother with document or
     96    // foreignDoc.
     97    detachedProcessingInstruction = xmlDoc.createProcessingInstruction("whippoorwill", "chirp chirp chirp");
     98    detachedComment = document.createComment("Stuvwxyz");
     99    // Hurrah, we finally got to "z" at the end!
    100    detachedForeignComment = foreignDoc.createComment("אריה יהודה");
    101    detachedXmlComment = xmlDoc.createComment("בן חיים אליעזר");
    102 
    103    // We should also test with document fragments that actually contain stuff
    104    // . . . but, maybe later.
    105    docfrag = document.createDocumentFragment();
    106    foreignDocfrag = foreignDoc.createDocumentFragment();
    107    xmlDocfrag = xmlDoc.createDocumentFragment();
    108 
    109    xmlElement = xmlDoc.createElement("igiveuponcreativenames");
    110    xmlTextNode = xmlDoc.createTextNode("do re mi fa so la ti");
    111    xmlElement.appendChild(xmlTextNode);
    112    processingInstruction = xmlDoc.createProcessingInstruction("somePI", 'Did you know that ":syn sync fromstart" is very useful when using vim to edit large amounts of JavaScript embedded in HTML?');
    113    xmlDoc.appendChild(xmlElement);
    114    xmlDoc.appendChild(processingInstruction);
    115    xmlComment = xmlDoc.createComment("I maliciously created a comment that will break incautious XML serializers, but Firefox threw an exception, so all I got was this lousy T-shirt");
    116    xmlDoc.appendChild(xmlComment);
    117 
    118    comment = document.createComment("Alphabet soup?");
    119    testDiv.appendChild(comment);
    120 
    121    foreignComment = foreignDoc.createComment('"Commenter" and "commentator" mean different things.  I\'ve seen non-native speakers trip up on this.');
    122    foreignDoc.appendChild(foreignComment);
    123    foreignTextNode = foreignDoc.createTextNode("I admit that I harbor doubts about whether we really need so many things to test, but it's too late to stop now.");
    124    foreignDoc.body.appendChild(foreignTextNode);
    125 
    126    doctype = document.doctype;
    127    foreignDoctype = foreignDoc.doctype;
    128 
    129    testRangesShort = [
    130        // Various ranges within the text node children of different
    131        // paragraphs.  All should be valid.
    132        "[paras[0].firstChild, 0, paras[0].firstChild, 0]",
    133        "[paras[0].firstChild, 0, paras[0].firstChild, 1]",
    134        "[paras[0].firstChild, 2, paras[0].firstChild, 8]",
    135        "[paras[0].firstChild, 2, paras[0].firstChild, 9]",
    136        "[paras[1].firstChild, 0, paras[1].firstChild, 0]",
    137        "[paras[1].firstChild, 2, paras[1].firstChild, 9]",
    138        "[paras[5].firstChild, 2, paras[5].lastChild, 4]",
    139        "[paras[5].firstChild, 1, paras[5].firstChild, 3]",
    140        "[detachedPara1.firstChild, 0, detachedPara1.firstChild, 0]",
    141        "[detachedPara1.firstChild, 2, detachedPara1.firstChild, 8]",
    142        "[foreignPara1.firstChild, 0, foreignPara1.firstChild, 0]",
    143        "[foreignPara1.firstChild, 2, foreignPara1.firstChild, 8]",
    144        // Now try testing some elements, not just text nodes.
    145        "[document.documentElement, 0, document.documentElement, 1]",
    146        "[document.documentElement, 0, document.documentElement, 2]",
    147        "[document.documentElement, 1, document.documentElement, 2]",
    148        "[document.head, 1, document.head, 1]",
    149        "[document.body, 4, document.body, 5]",
    150        "[foreignDoc.documentElement, 0, foreignDoc.documentElement, 1]",
    151        "[paras[0], 0, paras[0], 1]",
    152        "[detachedPara1, 0, detachedPara1, 1]",
    153        // Now try some ranges that span elements.
    154        "[paras[0].firstChild, 0, paras[1].firstChild, 0]",
    155        "[paras[0].firstChild, 0, paras[1].firstChild, 8]",
    156        "[paras[0].firstChild, 3, paras[3], 1]",
    157        // How about something that spans a node and its descendant?
    158        "[paras[0], 0, paras[0].firstChild, 7]",
    159        "[testDiv, 2, paras[4], 1]",
    160        // Then a few more interesting things just for good measure.
    161        "[document, 0, document, 1]",
    162        "[document, 0, document, 2]",
    163        "[comment, 2, comment, 3]",
    164        "[testDiv, 0, comment, 5]",
    165        "[foreignDoc, 1, foreignComment, 2]",
    166        "[foreignDoc.body, 0, foreignTextNode, 36]",
    167        "[xmlDoc, 1, xmlComment, 0]",
    168        "[detachedTextNode, 0, detachedTextNode, 8]",
    169        "[detachedForeignTextNode, 0, detachedForeignTextNode, 8]",
    170        "[detachedXmlTextNode, 0, detachedXmlTextNode, 8]",
    171        "[detachedComment, 3, detachedComment, 4]",
    172        "[detachedForeignComment, 0, detachedForeignComment, 1]",
    173        "[detachedXmlComment, 2, detachedXmlComment, 6]",
    174        "[docfrag, 0, docfrag, 0]",
    175        "[processingInstruction, 0, processingInstruction, 4]",
    176    ];
    177 
    178    testRanges = testRangesShort.concat([
    179        "[paras[1].firstChild, 0, paras[1].firstChild, 1]",
    180        "[paras[1].firstChild, 2, paras[1].firstChild, 8]",
    181        "[detachedPara1.firstChild, 0, detachedPara1.firstChild, 1]",
    182        "[foreignPara1.firstChild, 0, foreignPara1.firstChild, 1]",
    183        "[foreignDoc.head, 1, foreignDoc.head, 1]",
    184        "[foreignDoc.body, 0, foreignDoc.body, 0]",
    185        "[paras[0], 0, paras[0], 0]",
    186        "[detachedPara1, 0, detachedPara1, 0]",
    187        "[testDiv, 1, paras[2].firstChild, 5]",
    188        "[document.documentElement, 1, document.body, 0]",
    189        "[foreignDoc.documentElement, 1, foreignDoc.body, 0]",
    190        "[document, 1, document, 2]",
    191        "[paras[2].firstChild, 4, comment, 2]",
    192        "[paras[3], 1, comment, 8]",
    193        "[foreignDoc, 0, foreignDoc, 0]",
    194        "[xmlDoc, 0, xmlDoc, 0]",
    195        "[detachedForeignTextNode, 7, detachedForeignTextNode, 7]",
    196        "[detachedXmlTextNode, 7, detachedXmlTextNode, 7]",
    197        "[detachedComment, 5, detachedComment, 5]",
    198        "[detachedForeignComment, 4, detachedForeignComment, 4]",
    199        "[foreignDocfrag, 0, foreignDocfrag, 0]",
    200        "[xmlDocfrag, 0, xmlDocfrag, 0]",
    201    ]);
    202 
    203    testPoints = [
    204        // Various positions within the page, some invalid.  Remember that
    205        // paras[0] is visible, and paras[1] is display: none.
    206        "[paras[0].firstChild, -1]",
    207        "[paras[0].firstChild, 0]",
    208        "[paras[0].firstChild, 1]",
    209        "[paras[0].firstChild, 2]",
    210        "[paras[0].firstChild, 8]",
    211        "[paras[0].firstChild, 9]",
    212        "[paras[0].firstChild, 10]",
    213        "[paras[0].firstChild, 65535]",
    214        "[paras[1].firstChild, -1]",
    215        "[paras[1].firstChild, 0]",
    216        "[paras[1].firstChild, 1]",
    217        "[paras[1].firstChild, 2]",
    218        "[paras[1].firstChild, 8]",
    219        "[paras[1].firstChild, 9]",
    220        "[paras[1].firstChild, 10]",
    221        "[paras[1].firstChild, 65535]",
    222        "[paras[5].firstChild, 2]",
    223        "[paras[5].firstChild, 20]",
    224        "[detachedPara1.firstChild, 0]",
    225        "[detachedPara1.firstChild, 1]",
    226        "[detachedPara1.firstChild, 8]",
    227        "[detachedPara1.firstChild, 9]",
    228        "[foreignPara1.firstChild, 0]",
    229        "[foreignPara1.firstChild, 1]",
    230        "[foreignPara1.firstChild, 8]",
    231        "[foreignPara1.firstChild, 9]",
    232        // Now try testing some elements, not just text nodes.
    233        "[document.documentElement, -1]",
    234        "[document.documentElement, 0]",
    235        "[document.documentElement, 1]",
    236        "[document.documentElement, 2]",
    237        "[document.documentElement, 7]",
    238        "[document.head, 1]",
    239        "[document.body, 3]",
    240        "[foreignDoc.documentElement, 0]",
    241        "[foreignDoc.documentElement, 1]",
    242        "[foreignDoc.head, 0]",
    243        "[foreignDoc.body, 1]",
    244        "[paras[0], 0]",
    245        "[paras[0], 1]",
    246        "[paras[0], 2]",
    247        "[paras[1], 0]",
    248        "[paras[1], 1]",
    249        "[paras[1], 2]",
    250        "[detachedPara1, 0]",
    251        "[detachedPara1, 1]",
    252        "[testDiv, 0]",
    253        "[testDiv, 3]",
    254        // Then a few more interesting things just for good measure.
    255        "[document, -1]",
    256        "[document, 0]",
    257        "[document, 1]",
    258        "[document, 2]",
    259        "[document, 3]",
    260        "[comment, -1]",
    261        "[comment, 0]",
    262        "[comment, 4]",
    263        "[comment, 96]",
    264        "[foreignDoc, 0]",
    265        "[foreignDoc, 1]",
    266        "[foreignComment, 2]",
    267        "[foreignTextNode, 0]",
    268        "[foreignTextNode, 36]",
    269        "[xmlDoc, -1]",
    270        "[xmlDoc, 0]",
    271        "[xmlDoc, 1]",
    272        "[xmlDoc, 5]",
    273        "[xmlComment, 0]",
    274        "[xmlComment, 4]",
    275        "[processingInstruction, 0]",
    276        "[processingInstruction, 5]",
    277        "[processingInstruction, 9]",
    278        "[detachedTextNode, 0]",
    279        "[detachedTextNode, 8]",
    280        "[detachedForeignTextNode, 0]",
    281        "[detachedForeignTextNode, 8]",
    282        "[detachedXmlTextNode, 0]",
    283        "[detachedXmlTextNode, 8]",
    284        "[detachedProcessingInstruction, 12]",
    285        "[detachedComment, 3]",
    286        "[detachedComment, 5]",
    287        "[detachedForeignComment, 0]",
    288        "[detachedForeignComment, 4]",
    289        "[detachedXmlComment, 2]",
    290        "[docfrag, 0]",
    291        "[foreignDocfrag, 0]",
    292        "[xmlDocfrag, 0]",
    293        "[doctype, 0]",
    294        "[doctype, -17]",
    295        "[doctype, 1]",
    296        "[foreignDoctype, 0]",
    297        "[xmlDoctype, 0]",
    298    ];
    299 
    300    testNodesShort = [
    301        "paras[0]",
    302        "paras[0].firstChild",
    303        "paras[1].firstChild",
    304        "paras[5].firstChild",
    305        "foreignPara1",
    306        "foreignPara1.firstChild",
    307        "detachedPara1",
    308        "detachedPara1.firstChild",
    309        "document",
    310        "detachedDiv",
    311        "foreignDoc",
    312        "foreignPara2",
    313        "xmlDoc",
    314        "xmlElement",
    315        "detachedTextNode",
    316        "foreignTextNode",
    317        "processingInstruction",
    318        "detachedProcessingInstruction",
    319        "comment",
    320        "detachedComment",
    321        "docfrag",
    322        "doctype",
    323        "foreignDoctype",
    324    ];
    325 
    326    testNodes = testNodesShort.concat([
    327        "paras[1]",
    328        "detachedPara2",
    329        "detachedPara2.firstChild",
    330        "testDiv",
    331        "detachedXmlElement",
    332        "detachedForeignTextNode",
    333        "xmlTextNode",
    334        "detachedXmlTextNode",
    335        "xmlComment",
    336        "foreignComment",
    337        "detachedForeignComment",
    338        "detachedXmlComment",
    339        "foreignDocfrag",
    340        "xmlDocfrag",
    341        "xmlDoctype",
    342    ]);
    343 }
    344 if ("setup" in window) {
    345    setup(setupRangeTests);
    346 } else {
    347    // Presumably we're running from within an iframe or something
    348    setupRangeTests();
    349 }
    350 
    351 /**
    352 * The "length" of a node as defined by DOM.
    353 */
    354 function nodeLength(node) {
    355    // "The length of a node node depends on node:
    356    //
    357    // "DocumentType
    358    //   "0."
    359    if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
    360        return 0;
    361    }
    362    // "If node is a CharacterData node, then return node’s data’s length."
    363    // Browsers don't historically support the length attribute on
    364    // ProcessingInstruction, so to avoid spurious failures, do
    365    // node.data.length instead of node.length.
    366    if (isText(node) || node.nodeType == Node.PROCESSING_INSTRUCTION_NODE || node.nodeType == Node.COMMENT_NODE) {
    367        return node.data.length;
    368    }
    369    // "Any other node
    370    //   "Its number of children."
    371    return node.childNodes.length;
    372 }
    373 
    374 /**
    375 * Returns the furthest ancestor of a Node as defined by the spec.
    376 */
    377 function furthestAncestor(node) {
    378    var root = node;
    379    while (root.parentNode != null) {
    380        root = root.parentNode;
    381    }
    382    return root;
    383 }
    384 
    385 /**
    386 * "The ancestor containers of a Node are the Node itself and all its
    387 * ancestors."
    388 *
    389 * Is node1 an ancestor container of node2?
    390 */
    391 function isAncestorContainer(node1, node2) {
    392    return node1 == node2 ||
    393        (node2.compareDocumentPosition(node1) & Node.DOCUMENT_POSITION_CONTAINS);
    394 }
    395 
    396 /**
    397 * Returns the first Node that's after node in tree order, or null if node is
    398 * the last Node.
    399 */
    400 function nextNode(node) {
    401    if (node.hasChildNodes()) {
    402        return node.firstChild;
    403    }
    404    return nextNodeDescendants(node);
    405 }
    406 
    407 /**
    408 * Returns the last Node that's before node in tree order, or null if node is
    409 * the first Node.
    410 */
    411 function previousNode(node) {
    412    if (node.previousSibling) {
    413        node = node.previousSibling;
    414        while (node.hasChildNodes()) {
    415            node = node.lastChild;
    416        }
    417        return node;
    418    }
    419    return node.parentNode;
    420 }
    421 
    422 /**
    423 * Returns the next Node that's after node and all its descendants in tree
    424 * order, or null if node is the last Node or an ancestor of it.
    425 */
    426 function nextNodeDescendants(node) {
    427    while (node && !node.nextSibling) {
    428        node = node.parentNode;
    429    }
    430    if (!node) {
    431        return null;
    432    }
    433    return node.nextSibling;
    434 }
    435 
    436 /**
    437 * Returns the ownerDocument of the Node, or the Node itself if it's a
    438 * Document.
    439 */
    440 function ownerDocument(node) {
    441    return node.nodeType == Node.DOCUMENT_NODE
    442        ? node
    443        : node.ownerDocument;
    444 }
    445 
    446 /**
    447 * Returns true if ancestor is an ancestor of descendant, false otherwise.
    448 */
    449 function isAncestor(ancestor, descendant) {
    450    if (!ancestor || !descendant) {
    451        return false;
    452    }
    453    while (descendant && descendant != ancestor) {
    454        descendant = descendant.parentNode;
    455    }
    456    return descendant == ancestor;
    457 }
    458 
    459 /**
    460 * Returns true if ancestor is an inclusive ancestor of descendant, false
    461 * otherwise.
    462 */
    463 function isInclusiveAncestor(ancestor, descendant) {
    464    return ancestor === descendant || isAncestor(ancestor, descendant);
    465 }
    466 
    467 /**
    468 * Returns true if descendant is a descendant of ancestor, false otherwise.
    469 */
    470 function isDescendant(descendant, ancestor) {
    471    return isAncestor(ancestor, descendant);
    472 }
    473 
    474 /**
    475 * Returns true if descendant is an inclusive descendant of ancestor, false
    476 * otherwise.
    477 */
    478 function isInclusiveDescendant(descendant, ancestor) {
    479    return descendant === ancestor || isDescendant(descendant, ancestor);
    480 }
    481 
    482 /**
    483 * The position of two boundary points relative to one another, as defined by
    484 * the spec.
    485 */
    486 function getPosition(nodeA, offsetA, nodeB, offsetB) {
    487    // "If node A is the same as node B, return equal if offset A equals offset
    488    // B, before if offset A is less than offset B, and after if offset A is
    489    // greater than offset B."
    490    if (nodeA == nodeB) {
    491        if (offsetA == offsetB) {
    492            return "equal";
    493        }
    494        if (offsetA < offsetB) {
    495            return "before";
    496        }
    497        if (offsetA > offsetB) {
    498            return "after";
    499        }
    500    }
    501 
    502    // "If node A is after node B in tree order, compute the position of (node
    503    // B, offset B) relative to (node A, offset A). If it is before, return
    504    // after. If it is after, return before."
    505    if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_FOLLOWING) {
    506        var pos = getPosition(nodeB, offsetB, nodeA, offsetA);
    507        if (pos == "before") {
    508            return "after";
    509        }
    510        if (pos == "after") {
    511            return "before";
    512        }
    513    }
    514 
    515    // "If node A is an ancestor of node B:"
    516    if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_CONTAINS) {
    517        // "Let child equal node B."
    518        var child = nodeB;
    519 
    520        // "While child is not a child of node A, set child to its parent."
    521        while (child.parentNode != nodeA) {
    522            child = child.parentNode;
    523        }
    524 
    525        // "If the index of child is less than offset A, return after."
    526        if (indexOf(child) < offsetA) {
    527            return "after";
    528        }
    529    }
    530 
    531    // "Return before."
    532    return "before";
    533 }
    534 
    535 /**
    536 * "contained" as defined by DOM Range: "A Node node is contained in a range
    537 * range if node's furthest ancestor is the same as range's root, and (node, 0)
    538 * is after range's start, and (node, length of node) is before range's end."
    539 */
    540 function isContained(node, range) {
    541    var pos1 = getPosition(node, 0, range.startContainer, range.startOffset);
    542    var pos2 = getPosition(node, nodeLength(node), range.endContainer, range.endOffset);
    543 
    544    return furthestAncestor(node) == furthestAncestor(range.startContainer)
    545        && pos1 == "after"
    546        && pos2 == "before";
    547 }
    548 
    549 /**
    550 * "partially contained" as defined by DOM Range: "A Node is partially
    551 * contained in a range if it is an ancestor container of the range's start but
    552 * not its end, or vice versa."
    553 */
    554 function isPartiallyContained(node, range) {
    555    var cond1 = isAncestorContainer(node, range.startContainer);
    556    var cond2 = isAncestorContainer(node, range.endContainer);
    557    return (cond1 && !cond2) || (cond2 && !cond1);
    558 }
    559 
    560 /**
    561 * Index of a node as defined by the spec.
    562 */
    563 function indexOf(node) {
    564    if (!node.parentNode) {
    565        // No preceding sibling nodes, right?
    566        return 0;
    567    }
    568    var i = 0;
    569    while (node != node.parentNode.childNodes[i]) {
    570        i++;
    571    }
    572    return i;
    573 }
    574 
    575 /**
    576 * extractContents() implementation, following the spec.  If an exception is
    577 * supposed to be thrown, will return a string with the name (e.g.,
    578 * "HIERARCHY_REQUEST_ERR") instead of a document fragment.  It might also
    579 * return an arbitrary human-readable string if a condition is hit that implies
    580 * a spec bug.
    581 */
    582 function myExtractContents(range) {
    583    // "Let frag be a new DocumentFragment whose ownerDocument is the same as
    584    // the ownerDocument of the context object's start node."
    585    var ownerDoc = range.startContainer.nodeType == Node.DOCUMENT_NODE
    586        ? range.startContainer
    587        : range.startContainer.ownerDocument;
    588    var frag = ownerDoc.createDocumentFragment();
    589 
    590    // "If the context object's start and end are the same, abort this method,
    591    // returning frag."
    592    if (range.startContainer == range.endContainer
    593    && range.startOffset == range.endOffset) {
    594        return frag;
    595    }
    596 
    597    // "Let original start node, original start offset, original end node, and
    598    // original end offset be the context object's start and end nodes and
    599    // offsets, respectively."
    600    var originalStartNode = range.startContainer;
    601    var originalStartOffset = range.startOffset;
    602    var originalEndNode = range.endContainer;
    603    var originalEndOffset = range.endOffset;
    604 
    605    // "If original start node is original end node and it is a CharacterData node, then:"
    606    if (range.startContainer == range.endContainer
    607    && (isText(range.startContainer)
    608    || range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE
    609    || range.startContainer.nodeType == Node.COMMENT_NODE)) {
    610        // "Let clone be the result of calling cloneNode(false) on original
    611        // start node."
    612        var clone = originalStartNode.cloneNode(false);
    613 
    614        // "Set the data of clone to the result of calling
    615        // substringData(original start offset, original end offset − original
    616        // start offset) on original start node."
    617        clone.data = originalStartNode.substringData(originalStartOffset,
    618            originalEndOffset - originalStartOffset);
    619 
    620        // "Append clone as the last child of frag."
    621        frag.appendChild(clone);
    622 
    623        // "Call deleteData(original start offset, original end offset −
    624        // original start offset) on original start node."
    625        originalStartNode.deleteData(originalStartOffset,
    626            originalEndOffset - originalStartOffset);
    627 
    628        // "Abort this method, returning frag."
    629        return frag;
    630    }
    631 
    632    // "Let common ancestor equal original start node."
    633    var commonAncestor = originalStartNode;
    634 
    635    // "While common ancestor is not an ancestor container of original end
    636    // node, set common ancestor to its own parent."
    637    while (!isAncestorContainer(commonAncestor, originalEndNode)) {
    638        commonAncestor = commonAncestor.parentNode;
    639    }
    640 
    641    // "If original start node is an ancestor container of original end node,
    642    // let first partially contained child be null."
    643    var firstPartiallyContainedChild;
    644    if (isAncestorContainer(originalStartNode, originalEndNode)) {
    645        firstPartiallyContainedChild = null;
    646    // "Otherwise, let first partially contained child be the first child of
    647    // common ancestor that is partially contained in the context object."
    648    } else {
    649        for (var i = 0; i < commonAncestor.childNodes.length; i++) {
    650            if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
    651                firstPartiallyContainedChild = commonAncestor.childNodes[i];
    652                break;
    653            }
    654        }
    655        if (!firstPartiallyContainedChild) {
    656            throw "Spec bug: no first partially contained child!";
    657        }
    658    }
    659 
    660    // "If original end node is an ancestor container of original start node,
    661    // let last partially contained child be null."
    662    var lastPartiallyContainedChild;
    663    if (isAncestorContainer(originalEndNode, originalStartNode)) {
    664        lastPartiallyContainedChild = null;
    665    // "Otherwise, let last partially contained child be the last child of
    666    // common ancestor that is partially contained in the context object."
    667    } else {
    668        for (var i = commonAncestor.childNodes.length - 1; i >= 0; i--) {
    669            if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
    670                lastPartiallyContainedChild = commonAncestor.childNodes[i];
    671                break;
    672            }
    673        }
    674        if (!lastPartiallyContainedChild) {
    675            throw "Spec bug: no last partially contained child!";
    676        }
    677    }
    678 
    679    // "Let contained children be a list of all children of common ancestor
    680    // that are contained in the context object, in tree order."
    681    //
    682    // "If any member of contained children is a DocumentType, raise a
    683    // HIERARCHY_REQUEST_ERR exception and abort these steps."
    684    var containedChildren = [];
    685    for (var i = 0; i < commonAncestor.childNodes.length; i++) {
    686        if (isContained(commonAncestor.childNodes[i], range)) {
    687            if (commonAncestor.childNodes[i].nodeType
    688            == Node.DOCUMENT_TYPE_NODE) {
    689                return "HIERARCHY_REQUEST_ERR";
    690            }
    691            containedChildren.push(commonAncestor.childNodes[i]);
    692        }
    693    }
    694 
    695    // "If original start node is an ancestor container of original end node,
    696    // set new node to original start node and new offset to original start
    697    // offset."
    698    var newNode, newOffset;
    699    if (isAncestorContainer(originalStartNode, originalEndNode)) {
    700        newNode = originalStartNode;
    701        newOffset = originalStartOffset;
    702    // "Otherwise:"
    703    } else {
    704        // "Let reference node equal original start node."
    705        var referenceNode = originalStartNode;
    706 
    707        // "While reference node's parent is not null and is not an ancestor
    708        // container of original end node, set reference node to its parent."
    709        while (referenceNode.parentNode
    710        && !isAncestorContainer(referenceNode.parentNode, originalEndNode)) {
    711            referenceNode = referenceNode.parentNode;
    712        }
    713 
    714        // "Set new node to the parent of reference node, and new offset to one
    715        // plus the index of reference node."
    716        newNode = referenceNode.parentNode;
    717        newOffset = 1 + indexOf(referenceNode);
    718    }
    719 
    720    // "If first partially contained child is a CharacterData node, then:"
    721    if (firstPartiallyContainedChild
    722    && (isText(firstPartiallyContainedChild)
    723    || firstPartiallyContainedChild.nodeType == Node.PROCESSING_INSTRUCTION_NODE
    724    || firstPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
    725        // "Let clone be the result of calling cloneNode(false) on original
    726        // start node."
    727        var clone = originalStartNode.cloneNode(false);
    728 
    729        // "Set the data of clone to the result of calling substringData() on
    730        // original start node, with original start offset as the first
    731        // argument and (length of original start node − original start offset)
    732        // as the second."
    733        clone.data = originalStartNode.substringData(originalStartOffset,
    734            nodeLength(originalStartNode) - originalStartOffset);
    735 
    736        // "Append clone as the last child of frag."
    737        frag.appendChild(clone);
    738 
    739        // "Call deleteData() on original start node, with original start
    740        // offset as the first argument and (length of original start node −
    741        // original start offset) as the second."
    742        originalStartNode.deleteData(originalStartOffset,
    743            nodeLength(originalStartNode) - originalStartOffset);
    744    // "Otherwise, if first partially contained child is not null:"
    745    } else if (firstPartiallyContainedChild) {
    746        // "Let clone be the result of calling cloneNode(false) on first
    747        // partially contained child."
    748        var clone = firstPartiallyContainedChild.cloneNode(false);
    749 
    750        // "Append clone as the last child of frag."
    751        frag.appendChild(clone);
    752 
    753        // "Let subrange be a new Range whose start is (original start node,
    754        // original start offset) and whose end is (first partially contained
    755        // child, length of first partially contained child)."
    756        var subrange = ownerDoc.createRange();
    757        subrange.setStart(originalStartNode, originalStartOffset);
    758        subrange.setEnd(firstPartiallyContainedChild,
    759            nodeLength(firstPartiallyContainedChild));
    760 
    761        // "Let subfrag be the result of calling extractContents() on
    762        // subrange."
    763        var subfrag = myExtractContents(subrange);
    764 
    765        // "For each child of subfrag, in order, append that child to clone as
    766        // its last child."
    767        for (var i = 0; i < subfrag.childNodes.length; i++) {
    768            clone.appendChild(subfrag.childNodes[i]);
    769        }
    770    }
    771 
    772    // "For each contained child in contained children, append contained child
    773    // as the last child of frag."
    774    for (var i = 0; i < containedChildren.length; i++) {
    775        frag.appendChild(containedChildren[i]);
    776    }
    777 
    778    // "If last partially contained child is a CharacterData node, then:"
    779    if (lastPartiallyContainedChild
    780    && (isText(lastPartiallyContainedChild)
    781    || lastPartiallyContainedChild.nodeType == Node.PROCESSING_INSTRUCTION_NODE
    782    || lastPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
    783        // "Let clone be the result of calling cloneNode(false) on original
    784        // end node."
    785        var clone = originalEndNode.cloneNode(false);
    786 
    787        // "Set the data of clone to the result of calling substringData(0,
    788        // original end offset) on original end node."
    789        clone.data = originalEndNode.substringData(0, originalEndOffset);
    790 
    791        // "Append clone as the last child of frag."
    792        frag.appendChild(clone);
    793 
    794        // "Call deleteData(0, original end offset) on original end node."
    795        originalEndNode.deleteData(0, originalEndOffset);
    796    // "Otherwise, if last partially contained child is not null:"
    797    } else if (lastPartiallyContainedChild) {
    798        // "Let clone be the result of calling cloneNode(false) on last
    799        // partially contained child."
    800        var clone = lastPartiallyContainedChild.cloneNode(false);
    801 
    802        // "Append clone as the last child of frag."
    803        frag.appendChild(clone);
    804 
    805        // "Let subrange be a new Range whose start is (last partially
    806        // contained child, 0) and whose end is (original end node, original
    807        // end offset)."
    808        var subrange = ownerDoc.createRange();
    809        subrange.setStart(lastPartiallyContainedChild, 0);
    810        subrange.setEnd(originalEndNode, originalEndOffset);
    811 
    812        // "Let subfrag be the result of calling extractContents() on
    813        // subrange."
    814        var subfrag = myExtractContents(subrange);
    815 
    816        // "For each child of subfrag, in order, append that child to clone as
    817        // its last child."
    818        for (var i = 0; i < subfrag.childNodes.length; i++) {
    819            clone.appendChild(subfrag.childNodes[i]);
    820        }
    821    }
    822 
    823    // "Set the context object's start and end to (new node, new offset)."
    824    range.setStart(newNode, newOffset);
    825    range.setEnd(newNode, newOffset);
    826 
    827    // "Return frag."
    828    return frag;
    829 }
    830 
    831 /**
    832 * insertNode() implementation, following the spec.  If an exception is meant
    833 * to be thrown, will return a string with the expected exception name, for
    834 * instance "HIERARCHY_REQUEST_ERR".
    835 */
    836 function myInsertNode(range, node) {
    837    // "If range's start node is a ProcessingInstruction or Comment node, or is
    838    // a Text node whose parent is null, or is node, throw an
    839    // "HierarchyRequestError" exception and terminate these steps."
    840    if (range.startContainer.nodeType == Node.PROCESSING_INSTRUCTION_NODE
    841            || range.startContainer.nodeType == Node.COMMENT_NODE
    842            || (isText(range.startContainer)
    843                && !range.startContainer.parentNode)
    844            || range.startContainer == node) {
    845                    return "HIERARCHY_REQUEST_ERR";
    846    }
    847 
    848    // "Let referenceNode be null."
    849    var referenceNode = null;
    850 
    851    // "If range's start node is a Text node, set referenceNode to that Text node."
    852    if (isText(range.startContainer)) {
    853        referenceNode = range.startContainer;
    854 
    855        // "Otherwise, set referenceNode to the child of start node whose index is
    856        // start offset, and null if there is no such child."
    857    } else {
    858        if (range.startOffset < range.startContainer.childNodes.length) {
    859            referenceNode = range.startContainer.childNodes[range.startOffset];
    860        } else {
    861            referenceNode = null;
    862        }
    863    }
    864 
    865    // "Let parent be range's start node if referenceNode is null, and
    866    // referenceNode's parent otherwise."
    867    var parent_ = referenceNode === null ? range.startContainer :
    868        referenceNode.parentNode;
    869 
    870    // "Ensure pre-insertion validity of node into parent before
    871    // referenceNode."
    872    var error = ensurePreInsertionValidity(node, parent_, referenceNode);
    873    if (error) {
    874        return error;
    875    }
    876 
    877    // "If range's start node is a Text node, set referenceNode to the result
    878    // of splitting it with offset range's start offset."
    879    if (isText(range.startContainer)) {
    880        referenceNode = range.startContainer.splitText(range.startOffset);
    881    }
    882 
    883    // "If node is referenceNode, set referenceNode to its next sibling."
    884    if (node == referenceNode) {
    885        referenceNode = referenceNode.nextSibling;
    886    }
    887 
    888    // "If node's parent is not null, remove node from its parent."
    889    if (node.parentNode) {
    890        node.parentNode.removeChild(node);
    891    }
    892 
    893    // "Let newOffset be parent's length if referenceNode is null, and
    894    // referenceNode's index otherwise."
    895    var newOffset = referenceNode === null ? nodeLength(parent_) :
    896        indexOf(referenceNode);
    897 
    898    // "Increase newOffset by node's length if node is a DocumentFragment node,
    899    // and one otherwise."
    900    newOffset += node.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
    901        nodeLength(node) : 1;
    902 
    903    // "Pre-insert node into parent before referenceNode."
    904    parent_.insertBefore(node, referenceNode);
    905 
    906    // "If range's start and end are the same, set range's end to (parent,
    907    // newOffset)."
    908    if (range.startContainer == range.endContainer
    909    && range.startOffset == range.endOffset) {
    910        range.setEnd(parent_, newOffset);
    911    }
    912 }
    913 
    914 // To make filter() calls more readable
    915 function isElement(node) {
    916    return node.nodeType == Node.ELEMENT_NODE;
    917 }
    918 
    919 function isText(node) {
    920    return node.nodeType == Node.TEXT_NODE || node.nodeType == Node.CDATA_SECTION_NODE;
    921 }
    922 
    923 function isDoctype(node) {
    924    return node.nodeType == Node.DOCUMENT_TYPE_NODE;
    925 }
    926 
    927 function ensurePreInsertionValidity(node, parent_, child) {
    928    // "If parent is not a Document, DocumentFragment, or Element node, throw a
    929    // HierarchyRequestError."
    930    if (parent_.nodeType != Node.DOCUMENT_NODE
    931            && parent_.nodeType != Node.DOCUMENT_FRAGMENT_NODE
    932            && parent_.nodeType != Node.ELEMENT_NODE) {
    933                return "HIERARCHY_REQUEST_ERR";
    934    }
    935 
    936    // "If node is a host-including inclusive ancestor of parent, throw a
    937    // HierarchyRequestError."
    938    //
    939    // XXX Does not account for host
    940    if (isInclusiveAncestor(node, parent_)) {
    941        return "HIERARCHY_REQUEST_ERR";
    942    }
    943 
    944    // "If child is not null and its parent is not parent, throw a NotFoundError
    945    // exception."
    946    if (child && child.parentNode != parent_) {
    947        return "NOT_FOUND_ERR";
    948    }
    949 
    950    // "If node is not a DocumentFragment, DocumentType, Element, or CharacterData node,
    951    // then throw a HierarchyRequestError."
    952    if (node.nodeType != Node.DOCUMENT_FRAGMENT_NODE
    953            && node.nodeType != Node.DOCUMENT_TYPE_NODE
    954            && node.nodeType != Node.ELEMENT_NODE
    955            && !isText(node)
    956            && node.nodeType != Node.PROCESSING_INSTRUCTION_NODE
    957            && node.nodeType != Node.COMMENT_NODE) {
    958                return "HIERARCHY_REQUEST_ERR";
    959    }
    960 
    961    // "If either node is a Text node and parent is a document, or node is a
    962    // doctype and parent is not a document, throw a HierarchyRequestError."
    963    if ((isText(node)
    964                && parent_.nodeType == Node.DOCUMENT_NODE)
    965            || (node.nodeType == Node.DOCUMENT_TYPE_NODE
    966                && parent_.nodeType != Node.DOCUMENT_NODE)) {
    967                    return "HIERARCHY_REQUEST_ERR";
    968    }
    969 
    970    // "If parent is a document, and any of the statements below, switched on
    971    // node, are true, throw a HierarchyRequestError."
    972    if (parent_.nodeType == Node.DOCUMENT_NODE) {
    973        switch (node.nodeType) {
    974        case Node.DOCUMENT_FRAGMENT_NODE:
    975            // "If node has more than one element child or has a Text node
    976            // child.  Otherwise, if node has one element child and either
    977            // parent has an element child, child is a doctype, or child is not
    978            // null and a doctype is following child."
    979            if ([].filter.call(node.childNodes, isElement).length > 1) {
    980                return "HIERARCHY_REQUEST_ERR";
    981            }
    982 
    983            if ([].some.call(node.childNodes, isText)) {
    984                return "HIERARCHY_REQUEST_ERR";
    985            }
    986 
    987            if ([].filter.call(node.childNodes, isElement).length == 1) {
    988                if ([].some.call(parent_.childNodes, isElement)) {
    989                    return "HIERARCHY_REQUEST_ERR";
    990                }
    991 
    992                if (child && child.nodeType == Node.DOCUMENT_TYPE_NODE) {
    993                    return "HIERARCHY_REQUEST_ERR";
    994                }
    995 
    996                if (child && [].slice.call(parent_.childNodes, indexOf(child) + 1)
    997                               .filter(isDoctype)) {
    998                    return "HIERARCHY_REQUEST_ERR";
    999                }
   1000            }
   1001            break;
   1002 
   1003        case Node.ELEMENT_NODE:
   1004            // "parent has an element child, child is a doctype, or child is
   1005            // not null and a doctype is following child."
   1006            if ([].some.call(parent_.childNodes, isElement)) {
   1007                return "HIERARCHY_REQUEST_ERR";
   1008            }
   1009 
   1010            if (child.nodeType == Node.DOCUMENT_TYPE_NODE) {
   1011                return "HIERARCHY_REQUEST_ERR";
   1012            }
   1013 
   1014            if (child && [].slice.call(parent_.childNodes, indexOf(child) + 1)
   1015                           .filter(isDoctype)) {
   1016                return "HIERARCHY_REQUEST_ERR";
   1017            }
   1018            break;
   1019 
   1020        case Node.DOCUMENT_TYPE_NODE:
   1021            // "parent has a doctype child, an element is preceding child, or
   1022            // child is null and parent has an element child."
   1023            if ([].some.call(parent_.childNodes, isDoctype)) {
   1024                return "HIERARCHY_REQUEST_ERR";
   1025            }
   1026 
   1027            if (child && [].slice.call(parent_.childNodes, 0, indexOf(child))
   1028                           .some(isElement)) {
   1029                return "HIERARCHY_REQUEST_ERR";
   1030            }
   1031 
   1032            if (!child && [].some.call(parent_.childNodes, isElement)) {
   1033                return "HIERARCHY_REQUEST_ERR";
   1034            }
   1035            break;
   1036        }
   1037    }
   1038 }
   1039 
   1040 /**
   1041 * Asserts that two nodes are equal, in the sense of isEqualNode().  If they
   1042 * aren't, tries to print a relatively informative reason why not.  TODO: Move
   1043 * this to testharness.js?
   1044 */
   1045 function assertNodesEqual(actual, expected, msg) {
   1046    if (!actual.isEqualNode(expected)) {
   1047        msg = "Actual and expected mismatch for " + msg + ".  ";
   1048 
   1049        while (actual && expected) {
   1050            assert_true(actual.nodeType === expected.nodeType
   1051                && actual.nodeName === expected.nodeName
   1052                && actual.nodeValue === expected.nodeValue,
   1053                "First differing node: expected " + format_value(expected)
   1054                + ", got " + format_value(actual) + " [" + msg + "]");
   1055            actual = nextNode(actual);
   1056            expected = nextNode(expected);
   1057        }
   1058 
   1059        assert_unreached("DOMs were not equal but we couldn't figure out why");
   1060    }
   1061 }
   1062 
   1063 /**
   1064 * Given a DOMException, return the name (e.g., "HIERARCHY_REQUEST_ERR").
   1065 */
   1066 function getDomExceptionName(e) {
   1067    var ret = null;
   1068    for (var prop in e) {
   1069        if (/^[A-Z_]+_ERR$/.test(prop) && e[prop] == e.code) {
   1070            return prop;
   1071        }
   1072    }
   1073 
   1074    throw "Exception seems to not be a DOMException?  " + e;
   1075 }
   1076 
   1077 /**
   1078 * Given an array of endpoint data [start container, start offset, end
   1079 * container, end offset], returns a Range with those endpoints.
   1080 */
   1081 function rangeFromEndpoints(endpoints) {
   1082    // If we just use document instead of the ownerDocument of endpoints[0],
   1083    // WebKit will throw on setStart/setEnd.  This is a WebKit bug, but it's in
   1084    // range, not selection, so we don't want to fail anything for it.
   1085    var range = ownerDocument(endpoints[0]).createRange();
   1086    range.setStart(endpoints[0], endpoints[1]);
   1087    range.setEnd(endpoints[2], endpoints[3]);
   1088    return range;
   1089 }