Node-moveBefore.html (14345B)
1 <!DOCTYPE html> 2 <title>Node.moveBefore</title> 3 <script src="/resources/testharness.js"></script> 4 <script src="/resources/testharnessreport.js"></script> 5 <div id="log"></div> 6 <!-- First test shared pre-insertion checks that work similarly for replaceChild 7 and moveBefore --> 8 <script> 9 var insertFunc = Node.prototype.moveBefore; 10 </script> 11 <script src="../pre-insertion-validation-hierarchy.js"></script> 12 <script> 13 preInsertionValidateHierarchy("moveBefore"); 14 15 test(function() { 16 // WebIDL: first argument. 17 assert_throws_js(TypeError, function() { document.body.moveBefore(null, null) }) 18 assert_throws_js(TypeError, function() { document.body.moveBefore(null, document.body.firstChild) }) 19 assert_throws_js(TypeError, function() { document.body.moveBefore({'a':'b'}, document.body.firstChild) }) 20 }, "Calling moveBefore with a non-Node first argument must throw TypeError.") 21 22 test(function() { 23 // WebIDL: second argument. 24 assert_throws_js(TypeError, function() { document.body.moveBefore(document.createTextNode("child")) }) 25 assert_throws_js(TypeError, function() { document.body.moveBefore(document.createTextNode("child"), {'a':'b'}) }) 26 }, "Calling moveBefore with second argument missing, or other than Node, null, or undefined, must throw TypeError.") 27 28 test(() => { 29 assert_false("moveBefore" in document.doctype, "moveBefore() not on DocumentType"); 30 assert_false("moveBefore" in document.createTextNode("text"), "moveBefore() not on TextNode"); 31 assert_false("moveBefore" in new Comment("comment"), "moveBefore() not on CommentNode"); 32 assert_false("moveBefore" in document.createProcessingInstruction("foo", "bar"), "moveBefore() not on ProcessingInstruction"); 33 }, "moveBefore() method does not exist on non-ParentNode Nodes"); 34 35 // Pre-move validity, step 1: 36 // "If either parent or node are not connected, then throw a 37 // "HierarchyRequestError" DOMException." 38 // 39 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity 40 test(t => { 41 const connectedTarget = document.body.appendChild(document.createElement('div')); 42 const disconnectedDestination = document.createElement('div'); 43 t.add_cleanup(() => connectedTarget.remove()); 44 45 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => { 46 disconnectedDestination.moveBefore(connectedTarget, null); 47 }); 48 }, "moveBefore() on disconnected parent throws a HierarchyRequestError"); 49 test(t => { 50 const connectedDestination = document.body.appendChild(document.createElement('div')); 51 const disconnectedTarget = document.createElement('div'); 52 t.add_cleanup(() => connectedDestination.remove()); 53 54 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => { 55 connectedDestination.moveBefore(disconnectedTarget, null); 56 }); 57 }, "moveBefore() with disconnected target node throws a HierarchyRequestError"); 58 59 // Pre-move validity, step 2: 60 // "If parent’s shadow-including root is not the same as node’s shadow-including 61 // "root, then throw a "HierarchyRequestError" DOMException." 62 // 63 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity 64 test(t => { 65 const iframe = document.createElement('iframe'); 66 document.body.append(iframe); 67 const connectedCrossDocChild = iframe.contentDocument.createElement('div'); 68 const connectedLocalParent = document.querySelector('div'); 69 t.add_cleanup(() => iframe.remove()); 70 71 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => { 72 connectedLocalParent.moveBefore(connectedCrossDocChild, null); 73 }); 74 }, "moveBefore() on a cross-document target node throws a HierarchyRequestError"); 75 76 // Pre-move validity, step 3: 77 // "If parent is not a Document, DocumentFragment, or Element node, then throw a 78 // "HierarchyRequestError" DOMException." 79 // 80 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity 81 test(t => { 82 const iframe = document.body.appendChild(document.createElement('iframe')); 83 const innerBody = iframe.contentDocument.querySelector('body'); 84 85 assert_throws_dom("HIERARCHY_REQUEST_ERR", iframe.contentWindow.DOMException, () => { 86 // Moving the body into the same place that it already is, which is a valid 87 // action in the normal case, when moving an Element directly under the 88 // document. This is not `moveBefore()`-specific behavior; it is consistent 89 // with traditional Document insertion rules, just like `insertBefore()`. 90 iframe.contentDocument.moveBefore(innerBody, null); 91 }); 92 }, "moveBefore() into a Document throws a HierarchyRequestError"); 93 test(t => { 94 const iframe = document.body.appendChild(document.createElement('iframe')); 95 const comment = iframe.contentDocument.createComment("comment"); 96 iframe.contentDocument.body.append(comment); 97 98 iframe.contentDocument.moveBefore(comment, null); 99 assert_equals(comment.parentNode, iframe.contentDocument); 100 }, "moveBefore() CharacterData into a Document"); 101 102 // Pre-move validity, step 4: 103 // "If node is a host-including inclusive ancestor of parent, then throw a 104 // "HierarchyRequestError" DOMException." 105 // 106 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity 107 test(t => { 108 const parentDiv = document.body.appendChild(document.createElement('div')); 109 const childDiv = parentDiv.appendChild(document.createElement('div')); 110 t.add_cleanup(() => { 111 parentDiv.remove(); 112 childDiv.remove(); 113 }); 114 115 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => { 116 parentDiv.moveBefore(parentDiv, null); 117 }, "parent moving itself"); 118 119 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => { 120 childDiv.moveBefore(parentDiv, null); 121 }, "Moving parent into immediate child"); 122 123 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => { 124 childDiv.moveBefore(document.body, null); 125 }, "Moving grandparent into grandchild"); 126 127 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => { 128 document.body.moveBefore(document.documentElement, childDiv); 129 }, "Moving documentElement (<html>) into a deeper child"); 130 }, "moveBefore() with node being an inclusive ancestor of parent throws a " + 131 "HierarchyRequestError"); 132 133 // Pre-move validity, step 5: 134 // "If node is not an Element or a CharacterData node, then throw a 135 // "HierarchyRequestError" DOMException." 136 // 137 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity 138 test(t => { 139 assert_true(document.doctype.isConnected); 140 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => { 141 document.body.moveBefore(document.doctype, null); 142 }, "DocumentType throws"); 143 144 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => { 145 document.body.moveBefore(new DocumentFragment(), null); 146 }, "DocumentFragment throws"); 147 148 const doc = document.implementation.createHTMLDocument("title"); 149 assert_true(doc.isConnected); 150 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => { 151 document.body.moveBefore(doc, null); 152 }); 153 }, "moveBefore() with a non-{Element, CharacterData} throws a HierarchyRequestError"); 154 promise_test(async t => { 155 const text = new Text("child text"); 156 document.body.prepend(text); 157 158 const childElement = document.createElement('p'); 159 document.body.prepend(childElement); 160 161 const comment = new Comment("comment"); 162 document.body.prepend(comment); 163 164 t.add_cleanup(() => { 165 text.remove(); 166 childElement.remove(); 167 comment.remove(); 168 }); 169 170 // Wait until style is computed once, then continue after. This is necessary 171 // to reproduce a Chromium crash regression with moving Comment nodes in the 172 // DOM. 173 await new Promise(r => { 174 requestAnimationFrame(() => requestAnimationFrame(() => r())); 175 }); 176 177 document.body.moveBefore(text, null); 178 assert_equals(document.body.lastChild, text); 179 180 document.body.moveBefore(childElement, null); 181 assert_equals(document.body.lastChild, childElement); 182 183 document.body.moveBefore(text, null); 184 assert_equals(document.body.lastChild, text); 185 186 document.body.moveBefore(comment, null); 187 assert_equals(document.body.lastChild, comment); 188 }, "moveBefore with an Element or CharacterData succeeds"); 189 test(t => { 190 const p = document.createElement('p'); 191 p.textContent = "Some content"; 192 document.body.prepend(p); 193 194 const text_node = p.firstChild; 195 196 // The Text node is *inside* the paragraph. 197 assert_equals(text_node.textContent, "Some content"); 198 assert_not_equals(document.body.lastChild, text_node); 199 200 t.add_cleanup(() => { 201 text_node.remove(); 202 p.remove(); 203 }); 204 205 document.body.moveBefore(p.firstChild, null); 206 assert_equals(document.body.lastChild, text_node); 207 }, "moveBefore on a paragraph's Text node child"); 208 209 // Pre-move validity, step 6: 210 // "If child is non-null and its parent is not parent, then throw a 211 // "NotFoundError" DOMException." 212 // 213 // https://whatpr.org/dom/1307.html#concept-node-ensure-pre-move-validity 214 test(t => { 215 const a = document.body.appendChild(document.createElement("div")); 216 const b = document.body.appendChild(document.createElement("div")); 217 const c = document.body.appendChild(document.createElement("div")); 218 219 t.add_cleanup(() => { 220 a.remove(); 221 b.remove(); 222 c.remove(); 223 }); 224 225 assert_throws_dom("NotFoundError", () => { 226 a.moveBefore(b, c); 227 }); 228 }, "moveBefore with reference child whose parent is NOT the destination " + 229 "parent (context node) throws a NotFoundError.") 230 231 test(() => { 232 const a = document.body.appendChild(document.createElement("div")); 233 const b = document.createElement("div"); 234 const c = document.createElement("div"); 235 a.append(b); 236 a.append(c); 237 assert_array_equals(a.childNodes, [b, c]); 238 assert_equals(a.moveBefore(c, b), undefined, "moveBefore() returns undefined"); 239 assert_array_equals(a.childNodes, [c, b]); 240 }, "moveBefore() returns undefined"); 241 242 test(() => { 243 const a = document.body.appendChild(document.createElement("div")); 244 const b = document.createElement("div"); 245 const c = document.createElement("div"); 246 a.append(b); 247 a.append(c); 248 assert_array_equals(a.childNodes, [b, c]); 249 a.moveBefore(b, b); 250 assert_array_equals(a.childNodes, [b, c]); 251 a.moveBefore(c, c); 252 assert_array_equals(a.childNodes, [b, c]); 253 }, "Moving a node before itself should not move the node"); 254 255 test(() => { 256 const disconnectedOrigin = document.createElement('div'); 257 const disconnectedDestination = document.createElement('div'); 258 const p = disconnectedOrigin.appendChild(document.createElement('p')); 259 260 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => { 261 disconnectedDestination.moveBefore(p, null); 262 }); 263 }, "Moving a node from a disconnected container to a disconnected new parent " + 264 "without a shared ancestor throws a HIERARCHY_REQUEST_ERR"); 265 266 test(() => { 267 const disconnectedOrigin = document.createElement('div'); 268 const disconnectedDestination = disconnectedOrigin.appendChild(document.createElement('div')); 269 const p = disconnectedOrigin.appendChild(document.createElement('p')); 270 271 disconnectedDestination.moveBefore(p, null); 272 273 assert_equals(disconnectedDestination.firstChild, p, "<p> Was successfully moved"); 274 }, "Moving a node from a disconnected container to a disconnected new parent in the same tree succeeds"); 275 276 test(() => { 277 const disconnectedOrigin = document.createElement('div'); 278 const disconnectedHost = disconnectedOrigin.appendChild(document.createElement('div')); 279 const p = disconnectedOrigin.appendChild(document.createElement('p')); 280 const shadow = disconnectedHost.attachShadow({mode: "closed"}); 281 const disconnectedDestination = shadow.appendChild(document.createElement('div')); 282 283 disconnectedDestination.moveBefore(p, null); 284 285 assert_equals(disconnectedDestination.firstChild, p, "<p> Was successfully moved"); 286 }, "Moving a node from a disconnected container to a disconnected new parent in the same tree succeeds," + 287 "also across shadow-roots"); 288 289 test(() => { 290 const disconnectedOrigin = document.createElement('div'); 291 const connectedDestination = document.body.appendChild(document.createElement('div')); 292 const p = disconnectedOrigin.appendChild(document.createElement('p')); 293 294 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => connectedDestination.moveBefore(p, null)); 295 }, "Moving a node from disconnected->connected throws a HIERARCHY_REQUEST_ERR"); 296 297 test(() => { 298 const connectedOrigin = document.body.appendChild(document.createElement('div')); 299 const disconnectedDestination = document.createElement('div'); 300 const p = connectedOrigin.appendChild(document.createElement('p')); 301 302 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => disconnectedDestination.moveBefore(p, null)); 303 }, "Moving a node from connected->disconnected throws a HIERARCHY_REQUEST_ERR"); 304 305 promise_test(async t => { 306 let reactions = []; 307 const element_name = `ce-${performance.now()}`; 308 customElements.define(element_name, 309 class MockCustomElement extends HTMLElement { 310 connectedMoveCallback() { reactions.push("connectedMove"); } 311 connectedCallback() { reactions.push("connected"); } 312 disconnectedCallback() { reactions.push("disconnected"); } 313 }); 314 315 const oldParent = document.createElement('div'); 316 const newParent = oldParent.appendChild(document.createElement('div')); 317 const element = oldParent.appendChild(document.createElement(element_name)); 318 t.add_cleanup(() => { 319 element.remove(); 320 newParent.remove(); 321 oldParent.remove(); 322 }); 323 324 // Wait a microtask to let any custom element reactions run (should be none, 325 // since the initial parent is disconnected). 326 await Promise.resolve(); 327 328 newParent.moveBefore(element, null); 329 await Promise.resolve(); 330 assert_array_equals(reactions, []); 331 }, "No custom element callbacks are run during disconnected moveBefore()"); 332 333 // This is a regression test for a Chromium crash: https://crbug.com/388934346. 334 test(t => { 335 // This test caused a crash in Chromium because after the detection of invalid 336 // /node hierarchy, and throwing the JS error, we did not return from native 337 // code, and continued to operate on the node tree on bad assumptions. 338 const outer = document.createElement('div'); 339 const div = outer.appendChild(document.createElement('div')); 340 assert_throws_dom("HIERARCHY_REQUEST_ERR", () => div.moveBefore(outer, null)); 341 }, "Invalid node hierarchy with null old parent does not crash"); 342 343 test(t => { 344 const outerDiv = document.createElement('div'); 345 const innerDiv = outerDiv.appendChild(document.createElement('div')); 346 const iframe = innerDiv.appendChild(document.createElement('iframe')); 347 outerDiv.moveBefore(iframe, null); 348 }, "Move disconnected iframe does not crash"); 349 </script>