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 }