tor-browser

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

common.js (39001B)


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