Range-surroundContents.html (13471B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>Range.surroundContents() tests</title> 4 <link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name> 5 <meta name=timeout content=long> 6 <p>To debug test failures, add a query parameter "subtest" with the test id (like 7 "?subtest=5,16"). Only that test will be run. Then you can look at the resulting 8 iframes in the DOM. 9 <div id=log></div> 10 <script src=/resources/testharness.js></script> 11 <script src=/resources/testharnessreport.js></script> 12 <script src=../common.js></script> 13 <script> 14 "use strict"; 15 16 testDiv.parentNode.removeChild(testDiv); 17 18 function mySurroundContents(range, newParent) { 19 try { 20 // "If a non-Text node is partially contained in the context object, 21 // throw a "InvalidStateError" exception and terminate these steps." 22 var node = range.commonAncestorContainer; 23 var stop = nextNodeDescendants(node); 24 for (; node != stop; node = nextNode(node)) { 25 if (!isText(node) 26 && isPartiallyContained(node, range)) { 27 return "INVALID_STATE_ERR"; 28 } 29 } 30 31 // "If newParent is a Document, DocumentType, or DocumentFragment node, 32 // throw an "InvalidNodeTypeError" exception and terminate these 33 // steps." 34 if (newParent.nodeType == Node.DOCUMENT_NODE 35 || newParent.nodeType == Node.DOCUMENT_TYPE_NODE 36 || newParent.nodeType == Node.DOCUMENT_FRAGMENT_NODE) { 37 return "INVALID_NODE_TYPE_ERR"; 38 } 39 40 // "Call extractContents() on the context object, and let fragment be 41 // the result." 42 var fragment = myExtractContents(range); 43 if (typeof fragment == "string") { 44 return fragment; 45 } 46 47 // "While newParent has children, remove its first child." 48 while (newParent.childNodes.length) { 49 newParent.removeChild(newParent.firstChild); 50 } 51 52 // "Call insertNode(newParent) on the context object." 53 var ret = myInsertNode(range, newParent); 54 if (typeof ret == "string") { 55 return ret; 56 } 57 58 // "Call appendChild(fragment) on newParent." 59 newParent.appendChild(fragment); 60 61 // "Call selectNode(newParent) on the context object." 62 // 63 // We just reimplement this in-place. 64 if (!newParent.parentNode) { 65 return "INVALID_NODE_TYPE_ERR"; 66 } 67 var index = indexOf(newParent); 68 range.setStart(newParent.parentNode, index); 69 range.setEnd(newParent.parentNode, index + 1); 70 } catch (e) { 71 return getDomExceptionName(e); 72 } 73 } 74 75 function restoreIframe(iframe, i, j) { 76 // Most of this function is designed to work around the fact that Opera 77 // doesn't let you add a doctype to a document that no longer has one, in 78 // any way I can figure out. I eventually compromised on something that 79 // will still let Opera pass most tests that don't actually involve 80 // doctypes. 81 while (iframe.contentDocument.firstChild 82 && iframe.contentDocument.firstChild.nodeType != Node.DOCUMENT_TYPE_NODE) { 83 iframe.contentDocument.removeChild(iframe.contentDocument.firstChild); 84 } 85 86 while (iframe.contentDocument.lastChild 87 && iframe.contentDocument.lastChild.nodeType != Node.DOCUMENT_TYPE_NODE) { 88 iframe.contentDocument.removeChild(iframe.contentDocument.lastChild); 89 } 90 91 if (!iframe.contentDocument.firstChild) { 92 // This will throw an exception in Opera if we reach here, which is why 93 // I try to avoid it. It will never happen in a browser that obeys the 94 // spec, so it's really just insurance. I don't think it actually gets 95 // hit by anything. 96 iframe.contentDocument.appendChild(iframe.contentDocument.implementation.createDocumentType("html", "", "")); 97 } 98 iframe.contentDocument.appendChild(referenceDoc.documentElement.cloneNode(true)); 99 iframe.contentWindow.setupRangeTests(); 100 iframe.contentWindow.testRangeInput = testRangesShort[i]; 101 iframe.contentWindow.testNodeInput = testNodesShort[j]; 102 iframe.contentWindow.run(); 103 } 104 105 function testSurroundContents(i, j) { 106 var actualRange; 107 var expectedRange; 108 var actualNode; 109 var expectedNode; 110 var actualRoots = []; 111 var expectedRoots = []; 112 113 domTests[i][j].step(function() { 114 restoreIframe(actualIframe, i, j); 115 restoreIframe(expectedIframe, i, j); 116 117 actualRange = actualIframe.contentWindow.testRange; 118 expectedRange = expectedIframe.contentWindow.testRange; 119 actualNode = actualIframe.contentWindow.testNode; 120 expectedNode = expectedIframe.contentWindow.testNode; 121 122 assert_equals(actualIframe.contentWindow.unexpectedException, null, 123 "Unexpected exception thrown when setting up Range for actual surroundContents()"); 124 assert_equals(expectedIframe.contentWindow.unexpectedException, null, 125 "Unexpected exception thrown when setting up Range for simulated surroundContents()"); 126 assert_equals(typeof actualRange, "object", 127 "typeof Range produced in actual iframe"); 128 assert_not_equals(actualRange, null, 129 "Range produced in actual iframe was null"); 130 assert_equals(typeof expectedRange, "object", 131 "typeof Range produced in expected iframe"); 132 assert_not_equals(expectedRange, null, 133 "Range produced in expected iframe was null"); 134 assert_equals(typeof actualNode, "object", 135 "typeof Node produced in actual iframe"); 136 assert_not_equals(actualNode, null, 137 "Node produced in actual iframe was null"); 138 assert_equals(typeof expectedNode, "object", 139 "typeof Node produced in expected iframe"); 140 assert_not_equals(expectedNode, null, 141 "Node produced in expected iframe was null"); 142 143 // We want to test that the trees containing the ranges are equal, and 144 // also the trees containing the moved nodes. These might not be the 145 // same, if we're inserting a node from a detached tree or a different 146 // document. 147 actualRoots.push(furthestAncestor(actualRange.startContainer)); 148 expectedRoots.push(furthestAncestor(expectedRange.startContainer)); 149 150 if (furthestAncestor(actualNode) != actualRoots[0]) { 151 actualRoots.push(furthestAncestor(actualNode)); 152 } 153 if (furthestAncestor(expectedNode) != expectedRoots[0]) { 154 expectedRoots.push(furthestAncestor(expectedNode)); 155 } 156 157 assert_equals(actualRoots.length, expectedRoots.length, 158 "Either the actual node and actual range are in the same tree but the expected are in different trees, or vice versa"); 159 160 // This doctype stuff is to work around the fact that Opera 11.00 will 161 // move around doctypes within a document, even to totally invalid 162 // positions, but it won't allow a new doctype to be added to a 163 // document in any way I can figure out. So if we try moving a doctype 164 // to some invalid place, in Opera it will actually succeed, and then 165 // restoreIframe() will remove the doctype along with the root element, 166 // and then nothing can re-add the doctype. So instead, we catch it 167 // during the test itself and move it back to the right place while we 168 // still can. 169 // 170 // I spent *way* too much time debugging and working around this bug. 171 var actualDoctype = actualIframe.contentDocument.doctype; 172 var expectedDoctype = expectedIframe.contentDocument.doctype; 173 174 var result; 175 try { 176 result = mySurroundContents(expectedRange, expectedNode); 177 } catch (e) { 178 if (expectedDoctype != expectedIframe.contentDocument.firstChild) { 179 expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild); 180 } 181 throw e; 182 } 183 if (typeof result == "string") { 184 assert_throws_dom(result, actualIframe.contentWindow.DOMException, function() { 185 try { 186 actualRange.surroundContents(actualNode); 187 } catch (e) { 188 if (expectedDoctype != expectedIframe.contentDocument.firstChild) { 189 expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild); 190 } 191 if (actualDoctype != actualIframe.contentDocument.firstChild) { 192 actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild); 193 } 194 throw e; 195 } 196 }, "A " + result + " must be thrown in this case"); 197 // Don't return, we still need to test DOM equality 198 } else { 199 try { 200 actualRange.surroundContents(actualNode); 201 } catch (e) { 202 if (expectedDoctype != expectedIframe.contentDocument.firstChild) { 203 expectedIframe.contentDocument.insertBefore(expectedDoctype, expectedIframe.contentDocument.firstChild); 204 } 205 if (actualDoctype != actualIframe.contentDocument.firstChild) { 206 actualIframe.contentDocument.insertBefore(actualDoctype, actualIframe.contentDocument.firstChild); 207 } 208 throw e; 209 } 210 } 211 212 for (var k = 0; k < actualRoots.length; k++) { 213 assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root"); 214 } 215 }); 216 domTests[i][j].done(); 217 218 positionTests[i][j].step(function() { 219 assert_equals(actualIframe.contentWindow.unexpectedException, null, 220 "Unexpected exception thrown when setting up Range for actual surroundContents()"); 221 assert_equals(expectedIframe.contentWindow.unexpectedException, null, 222 "Unexpected exception thrown when setting up Range for simulated surroundContents()"); 223 assert_equals(typeof actualRange, "object", 224 "typeof Range produced in actual iframe"); 225 assert_not_equals(actualRange, null, 226 "Range produced in actual iframe was null"); 227 assert_equals(typeof expectedRange, "object", 228 "typeof Range produced in expected iframe"); 229 assert_not_equals(expectedRange, null, 230 "Range produced in expected iframe was null"); 231 assert_equals(typeof actualNode, "object", 232 "typeof Node produced in actual iframe"); 233 assert_not_equals(actualNode, null, 234 "Node produced in actual iframe was null"); 235 assert_equals(typeof expectedNode, "object", 236 "typeof Node produced in expected iframe"); 237 assert_not_equals(expectedNode, null, 238 "Node produced in expected iframe was null"); 239 240 for (var k = 0; k < actualRoots.length; k++) { 241 assertNodesEqual(actualRoots[k], expectedRoots[k], k ? "moved node's tree root" : "range's tree root"); 242 } 243 244 assert_equals(actualRange.startOffset, expectedRange.startOffset, 245 "Unexpected startOffset after surroundContents()"); 246 assert_equals(actualRange.endOffset, expectedRange.endOffset, 247 "Unexpected endOffset after surroundContents()"); 248 // How do we decide that the two nodes are equal, since they're in 249 // different trees? Since the DOMs are the same, it's enough to check 250 // that the index in the parent is the same all the way up the tree. 251 // But we can first cheat by just checking they're actually equal. 252 assert_true(actualRange.startContainer.isEqualNode(expectedRange.startContainer), 253 "Unexpected startContainer after surroundContents(), expected " + 254 expectedRange.startContainer.nodeName.toLowerCase() + " but got " + 255 actualRange.startContainer.nodeName.toLowerCase()); 256 var currentActual = actualRange.startContainer; 257 var currentExpected = expectedRange.startContainer; 258 var actual = ""; 259 var expected = ""; 260 while (currentActual && currentExpected) { 261 actual = indexOf(currentActual) + "-" + actual; 262 expected = indexOf(currentExpected) + "-" + expected; 263 264 currentActual = currentActual.parentNode; 265 currentExpected = currentExpected.parentNode; 266 } 267 actual = actual.substr(0, actual.length - 1); 268 expected = expected.substr(0, expected.length - 1); 269 assert_equals(actual, expected, 270 "startContainer superficially looks right but is actually the wrong node if you trace back its index in all its ancestors (I'm surprised this actually happened"); 271 }); 272 positionTests[i][j].done(); 273 } 274 275 var iStart = 0; 276 var iStop = testRangesShort.length; 277 var jStart = 0; 278 var jStop = testNodesShort.length; 279 280 if (/subtest=[0-9]+,[0-9]+/.test(location.search)) { 281 var matches = /subtest=([0-9]+),([0-9]+)/.exec(location.search); 282 iStart = Number(matches[1]); 283 iStop = Number(matches[1]) + 1; 284 jStart = Number(matches[2]) + 0; 285 jStop = Number(matches[2]) + 1; 286 } 287 288 var domTests = []; 289 var positionTests = []; 290 for (var i = iStart; i < iStop; i++) { 291 domTests[i] = []; 292 positionTests[i] = []; 293 for (var j = jStart; j < jStop; j++) { 294 domTests[i][j] = async_test(i + "," + j + ": resulting DOM for range " + testRangesShort[i] + ", node " + testNodesShort[j]); 295 positionTests[i][j] = async_test(i + "," + j + ": resulting range position for range " + testRangesShort[i] + ", node " + testNodesShort[j]); 296 } 297 } 298 299 var actualIframe = document.createElement("iframe"); 300 actualIframe.style.display = "none"; 301 actualIframe.id = "actual"; 302 document.body.appendChild(actualIframe); 303 304 var expectedIframe = document.createElement("iframe"); 305 expectedIframe.style.display = "none"; 306 expectedIframe.id = "expected"; 307 document.body.appendChild(expectedIframe); 308 309 var referenceDoc = document.implementation.createHTMLDocument(""); 310 referenceDoc.removeChild(referenceDoc.documentElement); 311 312 actualIframe.onload = function() { 313 expectedIframe.onload = function() { 314 for (var i = iStart; i < iStop; i++) { 315 for (var j = jStart; j < jStop; j++) { 316 testSurroundContents(i, j); 317 } 318 } 319 } 320 expectedIframe.src = "Range-test-iframe.html"; 321 referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true)); 322 } 323 actualIframe.src = "Range-test-iframe.html"; 324 </script>