Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 "use strict";
2 // TODO: iframes, contenteditable/designMode
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;
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>Äb̈c̈d̈ëf̈g̈ḧ\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");
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);
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);
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("בן חיים אליעזר");
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();
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);
88 comment = document.createComment("Alphabet soup?");
89 testDiv.appendChild(comment);
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);
96 doctype = document.doctype;
97 foreignDoctype = foreignDoc.doctype;
99 testRanges = [
100 // Various ranges within the text node children of different
101 // paragraphs. All should be valid.
102 "[paras[0].firstChild, 0, paras[0].firstChild, 0]",
103 "[paras[0].firstChild, 0, paras[0].firstChild, 1]",
104 "[paras[0].firstChild, 2, paras[0].firstChild, 8]",
105 "[paras[0].firstChild, 2, paras[0].firstChild, 9]",
106 "[paras[1].firstChild, 0, paras[1].firstChild, 0]",
107 "[paras[1].firstChild, 0, paras[1].firstChild, 1]",
108 "[paras[1].firstChild, 2, paras[1].firstChild, 8]",
109 "[paras[1].firstChild, 2, paras[1].firstChild, 9]",
110 "[detachedPara1.firstChild, 0, detachedPara1.firstChild, 0]",
111 "[detachedPara1.firstChild, 0, detachedPara1.firstChild, 1]",
112 "[detachedPara1.firstChild, 2, detachedPara1.firstChild, 8]",
113 "[foreignPara1.firstChild, 0, foreignPara1.firstChild, 0]",
114 "[foreignPara1.firstChild, 0, foreignPara1.firstChild, 1]",
115 "[foreignPara1.firstChild, 2, foreignPara1.firstChild, 8]",
116 // Now try testing some elements, not just text nodes.
117 "[document.documentElement, 0, document.documentElement, 1]",
118 "[document.documentElement, 0, document.documentElement, 2]",
119 "[document.documentElement, 1, document.documentElement, 2]",
120 "[document.head, 1, document.head, 1]",
121 "[document.body, 0, document.body, 1]",
122 "[foreignDoc.documentElement, 0, foreignDoc.documentElement, 1]",
123 "[foreignDoc.head, 1, foreignDoc.head, 1]",
124 "[foreignDoc.body, 0, foreignDoc.body, 0]",
125 "[paras[0], 0, paras[0], 0]",
126 "[paras[0], 0, paras[0], 1]",
127 "[detachedPara1, 0, detachedPara1, 0]",
128 "[detachedPara1, 0, detachedPara1, 1]",
129 // Now try some ranges that span elements.
130 "[paras[0].firstChild, 0, paras[1].firstChild, 0]",
131 "[paras[0].firstChild, 0, paras[1].firstChild, 8]",
132 "[paras[0].firstChild, 3, paras[3], 1]",
133 // How about something that spans a node and its descendant?
134 "[paras[0], 0, paras[0].firstChild, 7]",
135 "[testDiv, 2, paras[4], 1]",
136 "[testDiv, 1, paras[2].firstChild, 5]",
137 "[document.documentElement, 1, document.body, 0]",
138 "[foreignDoc.documentElement, 1, foreignDoc.body, 0]",
139 // Then a few more interesting things just for good measure.
140 "[document, 0, document, 1]",
141 "[document, 0, document, 2]",
142 "[document, 1, document, 2]",
143 "[testDiv, 0, comment, 5]",
144 "[paras[2].firstChild, 4, comment, 2]",
145 "[paras[3], 1, comment, 8]",
146 "[foreignDoc, 0, foreignDoc, 0]",
147 "[foreignDoc, 1, foreignComment, 2]",
148 "[foreignDoc.body, 0, foreignTextNode, 36]",
149 "[xmlDoc, 0, xmlDoc, 0]",
150 // Opera 11 crashes if you extractContents() a range that ends at offset
151 // zero in a comment. Comment out this line to run the tests successfully.
152 "[xmlDoc, 1, xmlComment, 0]",
153 "[detachedTextNode, 0, detachedTextNode, 8]",
154 "[detachedForeignTextNode, 7, detachedForeignTextNode, 7]",
155 "[detachedForeignTextNode, 0, detachedForeignTextNode, 8]",
156 "[detachedXmlTextNode, 7, detachedXmlTextNode, 7]",
157 "[detachedXmlTextNode, 0, detachedXmlTextNode, 8]",
158 "[detachedComment, 3, detachedComment, 4]",
159 "[detachedComment, 5, detachedComment, 5]",
160 "[detachedForeignComment, 0, detachedForeignComment, 1]",
161 "[detachedForeignComment, 4, detachedForeignComment, 4]",
162 "[detachedXmlComment, 2, detachedXmlComment, 6]",
163 "[docfrag, 0, docfrag, 0]",
164 "[foreignDocfrag, 0, foreignDocfrag, 0]",
165 "[xmlDocfrag, 0, xmlDocfrag, 0]",
166 ];
168 testPoints = [
169 // Various positions within the page, some invalid. Remember that
170 // paras[0] is visible, and paras[1] is display: none.
171 "[paras[0].firstChild, -1]",
172 "[paras[0].firstChild, 0]",
173 "[paras[0].firstChild, 1]",
174 "[paras[0].firstChild, 2]",
175 "[paras[0].firstChild, 8]",
176 "[paras[0].firstChild, 9]",
177 "[paras[0].firstChild, 10]",
178 "[paras[0].firstChild, 65535]",
179 "[paras[1].firstChild, -1]",
180 "[paras[1].firstChild, 0]",
181 "[paras[1].firstChild, 1]",
182 "[paras[1].firstChild, 2]",
183 "[paras[1].firstChild, 8]",
184 "[paras[1].firstChild, 9]",
185 "[paras[1].firstChild, 10]",
186 "[paras[1].firstChild, 65535]",
187 "[detachedPara1.firstChild, 0]",
188 "[detachedPara1.firstChild, 1]",
189 "[detachedPara1.firstChild, 8]",
190 "[detachedPara1.firstChild, 9]",
191 "[foreignPara1.firstChild, 0]",
192 "[foreignPara1.firstChild, 1]",
193 "[foreignPara1.firstChild, 8]",
194 "[foreignPara1.firstChild, 9]",
195 // Now try testing some elements, not just text nodes.
196 "[document.documentElement, -1]",
197 "[document.documentElement, 0]",
198 "[document.documentElement, 1]",
199 "[document.documentElement, 2]",
200 "[document.documentElement, 7]",
201 "[document.head, 1]",
202 "[document.body, 3]",
203 "[foreignDoc.documentElement, 0]",
204 "[foreignDoc.documentElement, 1]",
205 "[foreignDoc.head, 0]",
206 "[foreignDoc.body, 1]",
207 "[paras[0], 0]",
208 "[paras[0], 1]",
209 "[paras[0], 2]",
210 "[paras[1], 0]",
211 "[paras[1], 1]",
212 "[paras[1], 2]",
213 "[detachedPara1, 0]",
214 "[detachedPara1, 1]",
215 "[testDiv, 0]",
216 "[testDiv, 3]",
217 // Then a few more interesting things just for good measure.
218 "[document, -1]",
219 "[document, 0]",
220 "[document, 1]",
221 "[document, 2]",
222 "[document, 3]",
223 "[comment, -1]",
224 "[comment, 0]",
225 "[comment, 4]",
226 "[comment, 96]",
227 "[foreignDoc, 0]",
228 "[foreignDoc, 1]",
229 "[foreignComment, 2]",
230 "[foreignTextNode, 0]",
231 "[foreignTextNode, 36]",
232 "[xmlDoc, -1]",
233 "[xmlDoc, 0]",
234 "[xmlDoc, 1]",
235 "[xmlDoc, 5]",
236 "[xmlComment, 0]",
237 "[xmlComment, 4]",
238 "[processingInstruction, 0]",
239 "[processingInstruction, 5]",
240 "[processingInstruction, 9]",
241 "[detachedTextNode, 0]",
242 "[detachedTextNode, 8]",
243 "[detachedForeignTextNode, 0]",
244 "[detachedForeignTextNode, 8]",
245 "[detachedXmlTextNode, 0]",
246 "[detachedXmlTextNode, 8]",
247 "[detachedProcessingInstruction, 12]",
248 "[detachedComment, 3]",
249 "[detachedComment, 5]",
250 "[detachedForeignComment, 0]",
251 "[detachedForeignComment, 4]",
252 "[detachedXmlComment, 2]",
253 "[docfrag, 0]",
254 "[foreignDocfrag, 0]",
255 "[xmlDocfrag, 0]",
256 "[doctype, 0]",
257 "[doctype, -17]",
258 "[doctype, 1]",
259 "[foreignDoctype, 0]",
260 "[xmlDoctype, 0]",
261 ];
263 testNodes = [
264 "paras[0]",
265 "paras[0].firstChild",
266 "paras[1]",
267 "paras[1].firstChild",
268 "foreignPara1",
269 "foreignPara1.firstChild",
270 "detachedPara1",
271 "detachedPara1.firstChild",
272 "detachedPara1",
273 "detachedPara1.firstChild",
274 "testDiv",
275 "document",
276 "detachedDiv",
277 "detachedPara2",
278 "foreignDoc",
279 "foreignPara2",
280 "xmlDoc",
281 "xmlElement",
282 "detachedXmlElement",
283 "detachedTextNode",
284 "foreignTextNode",
285 "detachedForeignTextNode",
286 "xmlTextNode",
287 "detachedXmlTextNode",
288 "processingInstruction",
289 "detachedProcessingInstruction",
290 "comment",
291 "detachedComment",
292 "foreignComment",
293 "detachedForeignComment",
294 "xmlComment",
295 "detachedXmlComment",
296 "docfrag",
297 "foreignDocfrag",
298 "xmlDocfrag",
299 "doctype",
300 "foreignDoctype",
301 "xmlDoctype",
302 ];
303 }
304 if ("setup" in window) {
305 setup(setupRangeTests);
306 } else {
307 // Presumably we're running from within an iframe or something
308 setupRangeTests();
309 }
311 /**
312 * Return the length of a node as specified in DOM Range.
313 */
314 function getNodeLength(node) {
315 if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
316 return 0;
317 }
318 if (node.nodeType == Node.TEXT_NODE || node.nodeType == Node.PROCESSING_INSTRUCTION_NODE || node.nodeType == Node.COMMENT_NODE) {
319 return node.length;
320 }
321 return node.childNodes.length;
322 }
324 /**
325 * Returns the furthest ancestor of a Node as defined by the spec.
326 */
327 function furthestAncestor(node) {
328 var root = node;
329 while (root.parentNode != null) {
330 root = root.parentNode;
331 }
332 return root;
333 }
335 /**
336 * "The ancestor containers of a Node are the Node itself and all its
337 * ancestors."
338 *
339 * Is node1 an ancestor container of node2?
340 */
341 function isAncestorContainer(node1, node2) {
342 return node1 == node2 ||
343 (node2.compareDocumentPosition(node1) & Node.DOCUMENT_POSITION_CONTAINS);
344 }
346 /**
347 * Returns the first Node that's after node in tree order, or null if node is
348 * the last Node.
349 */
350 function nextNode(node) {
351 if (node.hasChildNodes()) {
352 return node.firstChild;
353 }
354 return nextNodeDescendants(node);
355 }
357 /**
358 * Returns the last Node that's before node in tree order, or null if node is
359 * the first Node.
360 */
361 function previousNode(node) {
362 if (node.previousSibling) {
363 node = node.previousSibling;
364 while (node.hasChildNodes()) {
365 node = node.lastChild;
366 }
367 return node;
368 }
369 return node.parentNode;
370 }
372 /**
373 * Returns the next Node that's after node and all its descendants in tree
374 * order, or null if node is the last Node or an ancestor of it.
375 */
376 function nextNodeDescendants(node) {
377 while (node && !node.nextSibling) {
378 node = node.parentNode;
379 }
380 if (!node) {
381 return null;
382 }
383 return node.nextSibling;
384 }
386 /**
387 * Returns the ownerDocument of the Node, or the Node itself if it's a
388 * Document.
389 */
390 function ownerDocument(node) {
391 return node.nodeType == Node.DOCUMENT_NODE
392 ? node
393 : node.ownerDocument;
394 }
396 /**
397 * Returns true if ancestor is an ancestor of descendant, false otherwise.
398 */
399 function isAncestor(ancestor, descendant) {
400 if (!ancestor || !descendant) {
401 return false;
402 }
403 while (descendant && descendant != ancestor) {
404 descendant = descendant.parentNode;
405 }
406 return descendant == ancestor;
407 }
409 /**
410 * Returns true if descendant is a descendant of ancestor, false otherwise.
411 */
412 function isDescendant(descendant, ancestor) {
413 return isAncestor(ancestor, descendant);
414 }
416 /**
417 * The position of two boundary points relative to one another, as defined by
418 * the spec.
419 */
420 function getPosition(nodeA, offsetA, nodeB, offsetB) {
421 // "If node A is the same as node B, return equal if offset A equals offset
422 // B, before if offset A is less than offset B, and after if offset A is
423 // greater than offset B."
424 if (nodeA == nodeB) {
425 if (offsetA == offsetB) {
426 return "equal";
427 }
428 if (offsetA < offsetB) {
429 return "before";
430 }
431 if (offsetA > offsetB) {
432 return "after";
433 }
434 }
436 // "If node A is after node B in tree order, compute the position of (node
437 // B, offset B) relative to (node A, offset A). If it is before, return
438 // after. If it is after, return before."
439 if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_FOLLOWING) {
440 var pos = getPosition(nodeB, offsetB, nodeA, offsetA);
441 if (pos == "before") {
442 return "after";
443 }
444 if (pos == "after") {
445 return "before";
446 }
447 }
449 // "If node A is an ancestor of node B:"
450 if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_CONTAINS) {
451 // "Let child equal node B."
452 var child = nodeB;
454 // "While child is not a child of node A, set child to its parent."
455 while (child.parentNode != nodeA) {
456 child = child.parentNode;
457 }
459 // "If the index of child is less than offset A, return after."
460 if (indexOf(child) < offsetA) {
461 return "after";
462 }
463 }
465 // "Return before."
466 return "before";
467 }
469 /**
470 * "contained" as defined by DOM Range: "A Node node is contained in a range
471 * range if node's furthest ancestor is the same as range's root, and (node, 0)
472 * is after range's start, and (node, length of node) is before range's end."
473 */
474 function isContained(node, range) {
475 var pos1 = getPosition(node, 0, range.startContainer, range.startOffset);
476 var pos2 = getPosition(node, getNodeLength(node), range.endContainer, range.endOffset);
478 return furthestAncestor(node) == furthestAncestor(range.startContainer)
479 && pos1 == "after"
480 && pos2 == "before";
481 }
483 /**
484 * "partially contained" as defined by DOM Range: "A Node is partially
485 * contained in a range if it is an ancestor container of the range's start but
486 * not its end, or vice versa."
487 */
488 function isPartiallyContained(node, range) {
489 var cond1 = isAncestorContainer(node, range.startContainer);
490 var cond2 = isAncestorContainer(node, range.endContainer);
491 return (cond1 && !cond2) || (cond2 && !cond1);
492 }
494 /**
495 * Index of a node as defined by the spec.
496 */
497 function indexOf(node) {
498 if (!node.parentNode) {
499 // No preceding sibling nodes, right?
500 return 0;
501 }
502 var i = 0;
503 while (node != node.parentNode.childNodes[i]) {
504 i++;
505 }
506 return i;
507 }
509 /**
510 * extractContents() implementation, following the spec. If an exception is
511 * supposed to be thrown, will return a string with the name (e.g.,
512 * "HIERARCHY_REQUEST_ERR") instead of a document fragment. It might also
513 * return an arbitrary human-readable string if a condition is hit that implies
514 * a spec bug.
515 */
516 function myExtractContents(range) {
517 // "If the context object's detached flag is set, raise an
518 // INVALID_STATE_ERR exception and abort these steps."
519 try {
520 range.collapsed;
521 } catch (e) {
522 return "INVALID_STATE_ERR";
523 }
525 // "Let frag be a new DocumentFragment whose ownerDocument is the same as
526 // the ownerDocument of the context object's start node."
527 var ownerDoc = range.startContainer.nodeType == Node.DOCUMENT_NODE
528 ? range.startContainer
529 : range.startContainer.ownerDocument;
530 var frag = ownerDoc.createDocumentFragment();
532 // "If the context object's start and end are the same, abort this method,
533 // returning frag."
534 if (range.startContainer == range.endContainer
535 && range.startOffset == range.endOffset) {
536 return frag;
537 }
539 // "Let original start node, original start offset, original end node, and
540 // original end offset be the context object's start and end nodes and
541 // offsets, respectively."
542 var originalStartNode = range.startContainer;
543 var originalStartOffset = range.startOffset;
544 var originalEndNode = range.endContainer;
545 var originalEndOffset = range.endOffset;
547 // "If original start node and original end node are the same, and they are
548 // a Text or Comment node:"
549 if (range.startContainer == range.endContainer
550 && (range.startContainer.nodeType == Node.TEXT_NODE
551 || range.startContainer.nodeType == Node.COMMENT_NODE)) {
552 // "Let clone be the result of calling cloneNode(false) on original
553 // start node."
554 var clone = originalStartNode.cloneNode(false);
556 // "Set the data of clone to the result of calling
557 // substringData(original start offset, original end offset − original
558 // start offset) on original start node."
559 clone.data = originalStartNode.substringData(originalStartOffset,
560 originalEndOffset - originalStartOffset);
562 // "Append clone as the last child of frag."
563 frag.appendChild(clone);
565 // "Call deleteData(original start offset, original end offset −
566 // original start offset) on original start node."
567 originalStartNode.deleteData(originalStartOffset,
568 originalEndOffset - originalStartOffset);
570 // "Abort this method, returning frag."
571 return frag;
572 }
574 // "Let common ancestor equal original start node."
575 var commonAncestor = originalStartNode;
577 // "While common ancestor is not an ancestor container of original end
578 // node, set common ancestor to its own parent."
579 while (!isAncestorContainer(commonAncestor, originalEndNode)) {
580 commonAncestor = commonAncestor.parentNode;
581 }
583 // "If original start node is an ancestor container of original end node,
584 // let first partially contained child be null."
585 var firstPartiallyContainedChild;
586 if (isAncestorContainer(originalStartNode, originalEndNode)) {
587 firstPartiallyContainedChild = null;
588 // "Otherwise, let first partially contained child be the first child of
589 // common ancestor that is partially contained in the context object."
590 } else {
591 for (var i = 0; i < commonAncestor.childNodes.length; i++) {
592 if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
593 firstPartiallyContainedChild = commonAncestor.childNodes[i];
594 break;
595 }
596 }
597 if (!firstPartiallyContainedChild) {
598 throw "Spec bug: no first partially contained child!";
599 }
600 }
602 // "If original end node is an ancestor container of original start node,
603 // let last partially contained child be null."
604 var lastPartiallyContainedChild;
605 if (isAncestorContainer(originalEndNode, originalStartNode)) {
606 lastPartiallyContainedChild = null;
607 // "Otherwise, let last partially contained child be the last child of
608 // common ancestor that is partially contained in the context object."
609 } else {
610 for (var i = commonAncestor.childNodes.length - 1; i >= 0; i--) {
611 if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
612 lastPartiallyContainedChild = commonAncestor.childNodes[i];
613 break;
614 }
615 }
616 if (!lastPartiallyContainedChild) {
617 throw "Spec bug: no last partially contained child!";
618 }
619 }
621 // "Let contained children be a list of all children of common ancestor
622 // that are contained in the context object, in tree order."
623 //
624 // "If any member of contained children is a DocumentType, raise a
625 // HIERARCHY_REQUEST_ERR exception and abort these steps."
626 var containedChildren = [];
627 for (var i = 0; i < commonAncestor.childNodes.length; i++) {
628 if (isContained(commonAncestor.childNodes[i], range)) {
629 if (commonAncestor.childNodes[i].nodeType
630 == Node.DOCUMENT_TYPE_NODE) {
631 return "HIERARCHY_REQUEST_ERR";
632 }
633 containedChildren.push(commonAncestor.childNodes[i]);
634 }
635 }
637 // "If original start node is an ancestor container of original end node,
638 // set new node to original start node and new offset to original start
639 // offset."
640 var newNode, newOffset;
641 if (isAncestorContainer(originalStartNode, originalEndNode)) {
642 newNode = originalStartNode;
643 newOffset = originalStartOffset;
644 // "Otherwise:"
645 } else {
646 // "Let reference node equal original start node."
647 var referenceNode = originalStartNode;
649 // "While reference node's parent is not null and is not an ancestor
650 // container of original end node, set reference node to its parent."
651 while (referenceNode.parentNode
652 && !isAncestorContainer(referenceNode.parentNode, originalEndNode)) {
653 referenceNode = referenceNode.parentNode;
654 }
656 // "Set new node to the parent of reference node, and new offset to one
657 // plus the index of reference node."
658 newNode = referenceNode.parentNode;
659 newOffset = 1 + indexOf(referenceNode);
660 }
662 // "If first partially contained child is a Text or Comment node:"
663 if (firstPartiallyContainedChild
664 && (firstPartiallyContainedChild.nodeType == Node.TEXT_NODE
665 || firstPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
666 // "Let clone be the result of calling cloneNode(false) on original
667 // start node."
668 var clone = originalStartNode.cloneNode(false);
670 // "Set the data of clone to the result of calling substringData() on
671 // original start node, with original start offset as the first
672 // argument and (length of original start node − original start offset)
673 // as the second."
674 clone.data = originalStartNode.substringData(originalStartOffset,
675 getNodeLength(originalStartNode) - originalStartOffset);
677 // "Append clone as the last child of frag."
678 frag.appendChild(clone);
680 // "Call deleteData() on original start node, with original start
681 // offset as the first argument and (length of original start node −
682 // original start offset) as the second."
683 originalStartNode.deleteData(originalStartOffset,
684 getNodeLength(originalStartNode) - originalStartOffset);
685 // "Otherwise, if first partially contained child is not null:"
686 } else if (firstPartiallyContainedChild) {
687 // "Let clone be the result of calling cloneNode(false) on first
688 // partially contained child."
689 var clone = firstPartiallyContainedChild.cloneNode(false);
691 // "Append clone as the last child of frag."
692 frag.appendChild(clone);
694 // "Let subrange be a new Range whose start is (original start node,
695 // original start offset) and whose end is (first partially contained
696 // child, length of first partially contained child)."
697 var subrange = ownerDoc.createRange();
698 subrange.setStart(originalStartNode, originalStartOffset);
699 subrange.setEnd(firstPartiallyContainedChild,
700 getNodeLength(firstPartiallyContainedChild));
702 // "Let subfrag be the result of calling extractContents() on
703 // subrange."
704 var subfrag = myExtractContents(subrange);
706 // "For each child of subfrag, in order, append that child to clone as
707 // its last child."
708 for (var i = 0; i < subfrag.childNodes.length; i++) {
709 clone.appendChild(subfrag.childNodes[i]);
710 }
711 }
713 // "For each contained child in contained children, append contained child
714 // as the last child of frag."
715 for (var i = 0; i < containedChildren.length; i++) {
716 frag.appendChild(containedChildren[i]);
717 }
719 // "If last partially contained child is a Text or Comment node:"
720 if (lastPartiallyContainedChild
721 && (lastPartiallyContainedChild.nodeType == Node.TEXT_NODE
722 || lastPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
723 // "Let clone be the result of calling cloneNode(false) on original
724 // end node."
725 var clone = originalEndNode.cloneNode(false);
727 // "Set the data of clone to the result of calling substringData(0,
728 // original end offset) on original end node."
729 clone.data = originalEndNode.substringData(0, originalEndOffset);
731 // "Append clone as the last child of frag."
732 frag.appendChild(clone);
734 // "Call deleteData(0, original end offset) on original end node."
735 originalEndNode.deleteData(0, originalEndOffset);
736 // "Otherwise, if last partially contained child is not null:"
737 } else if (lastPartiallyContainedChild) {
738 // "Let clone be the result of calling cloneNode(false) on last
739 // partially contained child."
740 var clone = lastPartiallyContainedChild.cloneNode(false);
742 // "Append clone as the last child of frag."
743 frag.appendChild(clone);
745 // "Let subrange be a new Range whose start is (last partially
746 // contained child, 0) and whose end is (original end node, original
747 // end offset)."
748 var subrange = ownerDoc.createRange();
749 subrange.setStart(lastPartiallyContainedChild, 0);
750 subrange.setEnd(originalEndNode, originalEndOffset);
752 // "Let subfrag be the result of calling extractContents() on
753 // subrange."
754 var subfrag = myExtractContents(subrange);
756 // "For each child of subfrag, in order, append that child to clone as
757 // its last child."
758 for (var i = 0; i < subfrag.childNodes.length; i++) {
759 clone.appendChild(subfrag.childNodes[i]);
760 }
761 }
763 // "Set the context object's start and end to (new node, new offset)."
764 range.setStart(newNode, newOffset);
765 range.setEnd(newNode, newOffset);
767 // "Return frag."
768 return frag;
769 }
771 /**
772 * insertNode() implementation, following the spec. If an exception is
773 * supposed to be thrown, will return a string with the name (e.g.,
774 * "HIERARCHY_REQUEST_ERR") instead of a document fragment. It might also
775 * return an arbitrary human-readable string if a condition is hit that implies
776 * a spec bug.
777 */
778 function myInsertNode(range, newNode) {
779 // "If the context object's detached flag is set, raise an
780 // INVALID_STATE_ERR exception and abort these steps."
781 //
782 // Assume that if accessing collapsed throws, it's detached.
783 try {
784 range.collapsed;
785 } catch (e) {
786 return "INVALID_STATE_ERR";
787 }
789 // "If the context object's start node is a Text or Comment node and its
790 // parent is null, raise an HIERARCHY_REQUEST_ERR exception and abort these
791 // steps."
792 if ((range.startContainer.nodeType == Node.TEXT_NODE
793 || range.startContainer.nodeType == Node.COMMENT_NODE)
794 && !range.startContainer.parentNode) {
795 return "HIERARCHY_REQUEST_ERR";
796 }
798 // "If the context object's start node is a Text node, run splitText() on
799 // it with the context object's start offset as its argument, and let
800 // reference node be the result."
801 var referenceNode;
802 if (range.startContainer.nodeType == Node.TEXT_NODE) {
803 // We aren't testing how ranges vary under mutations, and browsers vary
804 // in how they mutate for splitText, so let's just force the correct
805 // way.
806 var start = [range.startContainer, range.startOffset];
807 var end = [range.endContainer, range.endOffset];
809 referenceNode = range.startContainer.splitText(range.startOffset);
811 if (start[0] == end[0]
812 && end[1] > start[1]) {
813 end[0] = referenceNode;
814 end[1] -= start[1];
815 } else if (end[0] == start[0].parentNode
816 && end[1] > indexOf(referenceNode)) {
817 end[1]++;
818 }
819 range.setStart(start[0], start[1]);
820 range.setEnd(end[0], end[1]);
821 // "Otherwise, if the context object's start node is a Comment, let
822 // reference node be the context object's start node."
823 } else if (range.startContainer.nodeType == Node.COMMENT_NODE) {
824 referenceNode = range.startContainer;
825 // "Otherwise, let reference node be the child of the context object's
826 // start node with index equal to the context object's start offset, or
827 // null if there is no such child."
828 } else {
829 referenceNode = range.startContainer.childNodes[range.startOffset];
830 if (typeof referenceNode == "undefined") {
831 referenceNode = null;
832 }
833 }
835 // "If reference node is null, let parent node be the context object's
836 // start node."
837 var parentNode;
838 if (!referenceNode) {
839 parentNode = range.startContainer;
840 // "Otherwise, let parent node be the parent of reference node."
841 } else {
842 parentNode = referenceNode.parentNode;
843 }
845 // "Call insertBefore(newNode, reference node) on parent node, re-raising
846 // any exceptions that call raised."
847 try {
848 parentNode.insertBefore(newNode, referenceNode);
849 } catch (e) {
850 return getDomExceptionName(e);
851 }
852 }
854 /**
855 * Asserts that two nodes are equal, in the sense of isEqualNode(). If they
856 * aren't, tries to print a relatively informative reason why not. TODO: Move
857 * this to testharness.js?
858 */
859 function assertNodesEqual(actual, expected, msg) {
860 if (!actual.isEqualNode(expected)) {
861 msg = "Actual and expected mismatch for " + msg + ". ";
863 while (actual && expected) {
864 assert_true(actual.nodeType === expected.nodeType
865 && actual.nodeName === expected.nodeName
866 && actual.nodeValue === expected.nodeValue
867 && actual.childNodes.length === expected.childNodes.length,
868 "First differing node: expected " + format_value(expected)
869 + ", got " + format_value(actual));
870 actual = nextNode(actual);
871 expected = nextNode(expected);
872 }
874 assert_unreached("DOMs were not equal but we couldn't figure out why");
875 }
876 }
878 /**
879 * Given a DOMException, return the name (e.g., "HIERARCHY_REQUEST_ERR"). In
880 * theory this should be just e.name, but in practice it's not. So I could
881 * legitimately just return e.name, but then every engine but WebKit would fail
882 * every test, since no one seems to care much for standardizing DOMExceptions.
883 * Instead I mangle it to account for browser bugs, so as not to fail
884 * insertNode() tests (for instance) for insertBefore() bugs. Of course, a
885 * standards-compliant browser will work right in any event.
886 *
887 * If the exception has no string property called "name" or "message", we just
888 * re-throw it.
889 */
890 function getDomExceptionName(e) {
891 if (typeof e.name == "string"
892 && /^[A-Z_]+_ERR$/.test(e.name)) {
893 // Either following the standard, or prefixing NS_ERROR_DOM (I'm
894 // looking at you, Gecko).
895 return e.name.replace(/^NS_ERROR_DOM_/, "");
896 }
898 if (typeof e.message == "string"
899 && /^[A-Z_]+_ERR$/.test(e.message)) {
900 // Opera
901 return e.message;
902 }
904 if (typeof e.message == "string"
905 && /^DOM Exception:/.test(e.message)) {
906 // IE
907 return /[A-Z_]+_ERR/.exec(e.message)[0];
908 }
910 throw e;
911 }
913 /**
914 * Given an array of endpoint data [start container, start offset, end
915 * container, end offset], returns a Range with those endpoints.
916 */
917 function rangeFromEndpoints(endpoints) {
918 // If we just use document instead of the ownerDocument of endpoints[0],
919 // WebKit will throw on setStart/setEnd. This is a WebKit bug, but it's in
920 // range, not selection, so we don't want to fail anything for it.
921 var range = ownerDocument(endpoints[0]).createRange();
922 range.setStart(endpoints[0], endpoints[1]);
923 range.setEnd(endpoints[2], endpoints[3]);
924 return range;
925 }
927 /**
928 * Given an array of endpoint data [start container, start offset, end
929 * container, end offset], sets the selection to have those endpoints. Uses
930 * addRange, so the range will be forwards. Accepts an empty array for
931 * endpoints, in which case the selection will just be emptied.
932 */
933 function setSelectionForwards(endpoints) {
934 selection.removeAllRanges();
935 if (endpoints.length) {
936 selection.addRange(rangeFromEndpoints(endpoints));
937 }
938 }
940 /**
941 * Given an array of endpoint data [start container, start offset, end
942 * container, end offset], sets the selection to have those endpoints, with the
943 * direction backwards. Uses extend, so it will throw in IE. Accepts an empty
944 * array for endpoints, in which case the selection will just be emptied.
945 */
946 function setSelectionBackwards(endpoints) {
947 selection.removeAllRanges();
948 if (endpoints.length) {
949 selection.collapse(endpoints[2], endpoints[3]);
950 selection.extend(endpoints[0], endpoints[1]);
951 }
952 }