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