| OLD | NEW |
| (Empty) | |
| 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 (lik
e |
| 7 "?subtest=5,16"). Only that test will be run. Then you can look at the resulti
ng |
| 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 (node.nodeType != Node.TEXT_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.cre
ateDocumentType("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 surroundCont
ents()"); |
| 124 assert_equals(expectedIframe.contentWindow.unexpectedException, null, |
| 125 "Unexpected exception thrown when setting up Range for simulated surroundC
ontents()"); |
| 126 assert_equals(typeof actualRange, "object", |
| 127 "typeof Range produced in actual iframe"); |
| 128 assert_false(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_false(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_false(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_false(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 expe
cted 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, expectedIfr
ame.contentDocument.firstChild); |
| 180 } |
| 181 throw e; |
| 182 } |
| 183 if (typeof result == "string") { |
| 184 assert_throws(result, function() { |
| 185 try { |
| 186 actualRange.surroundContents(actualNode); |
| 187 } catch (e) { |
| 188 if (expectedDoctype != expectedIframe.contentDocument.firstChild) { |
| 189 expectedIframe.contentDocument.insertBefore(expectedDoctype, expecte
dIframe.contentDocument.firstChild); |
| 190 } |
| 191 if (actualDoctype != actualIframe.contentDocument.firstChild) { |
| 192 actualIframe.contentDocument.insertBefore(actualDoctype, actualIfram
e.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, expectedI
frame.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 surroundCont
ents()"); |
| 221 assert_equals(expectedIframe.contentWindow.unexpectedException, null, |
| 222 "Unexpected exception thrown when setting up Range for simulated surroundC
ontents()"); |
| 223 assert_equals(typeof actualRange, "object", |
| 224 "typeof Range produced in actual iframe"); |
| 225 assert_false(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_false(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_false(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_false(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.startContai
ner), |
| 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 i
f you trace back its index in all its ancestors (I'm surprised this actually hap
pened"); |
| 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 " + tes
tRangesShort[i] + ", node " + testNodesShort[j]); |
| 295 positionTests[i][j] = async_test(i + "," + j + ": resulting range position f
or 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.cloneNod
e(true)); |
| 322 } |
| 323 actualIframe.src = "Range-test-iframe.html"; |
| 324 </script> |
| OLD | NEW |