Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(432)

Side by Side Diff: third_party/WebKit/LayoutTests/external/wpt/selection/common.js

Issue 2642393002: Import wpt@40665266227e475bc4a56884247d8c09d78dfb6a (Closed)
Patch Set: rebaseline-cl Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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", "abcd e", "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("whippoor will", "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 yo u know that ":syn sync fromstart" is very useful when using vim to edit large am ounts 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" mea n 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 ab out 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 successful ly.
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_INST RUCTION_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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698