delete-in-child-of-html.tentative.html (17909B)
1 <!doctype html> 2 <html> 3 <head> 4 <meta chareset="utf-8"> 5 <meta name="timeout" content="long"> 6 <meta name="variant" content="?designMode=off&method=backspace"> 7 <meta name="variant" content="?designMode=off&method=forwarddelete"> 8 <meta name="variant" content="?designMode=on&method=backspace"> 9 <meta name="variant" content="?designMode=on&method=forwarddelete"> 10 <title>Join paragraphs outside the body</title> 11 <script src="/resources/testharness.js"></script> 12 <script src="/resources/testharnessreport.js"></script> 13 <script src="/resources/testdriver.js"></script> 14 <script src="/resources/testdriver-vendor.js"></script> 15 <script src="/resources/testdriver-actions.js"></script> 16 <script src="../include/editor-test-utils.js"></script> 17 </head> 18 <body> 19 <iframe srcdoc=""></iframe> 20 <script> 21 "use strict"; 22 23 const searchParams = new URLSearchParams(document.location.search); 24 const testingBackspace = searchParams.get("method") == "backspace"; 25 const commandName = testingBackspace ? "delete" : "forwarddelete"; 26 const testingDesignMode = searchParams.get("designMode") == "on"; 27 28 const iframe = document.querySelector("iframe"); 29 const minimumSrcDoc = 30 "<html>" + 31 "<head>" + 32 "<title>iframe</title>" + 33 "<script src='/resources/testdriver.js'></" + "script>" + 34 "<script src='/resources/testdriver-vendor.js'></" + "script>" + 35 "<script src='/resources/testdriver-actions.js'></" + "script>" + 36 "</head>" + 37 "<body><br></body>" + 38 "</html>"; 39 40 async function initializeAndWaitForLoad(iframeElement, srcDocValue) { 41 const waitForLoad = 42 new Promise( 43 resolve => iframeElement.addEventListener("load", resolve, {once: true}) 44 ); 45 iframeElement.srcdoc = srcDocValue; 46 await waitForLoad; 47 if (testingDesignMode) { 48 iframeElement.contentDocument.designMode = "on"; 49 } else { 50 iframeElement.contentDocument.documentElement.setAttribute("contenteditable", ""); 51 } 52 iframeElement.contentWindow.focus(); 53 iframeElement.contentDocument.execCommand("defaultParagraphSeparator", false, "div"); 54 } 55 56 function removeResourceScriptElements(node) { 57 node.querySelectorAll("script").forEach( 58 element => { 59 if (element.getAttribute("src")?.startsWith("/resources")) { 60 element.remove() 61 } 62 } 63 ); 64 } 65 66 // DO NOT USE multi-line comment in this file, then, you can comment out 67 // unnecessary tests when you need to attach the browser with a debugger. 68 69 // For backward compatibility, normal block elements outside <body> should be 70 // joined by deletion. 71 promise_test(async () => { 72 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 73 const childDoc = iframe.contentDocument; 74 const utils = new EditorTestUtils(childDoc.documentElement); 75 const div1 = childDoc.createElement("div"); 76 div1.innerHTML = "abc"; 77 const div2 = childDoc.createElement("div"); 78 div2.innerHTML = "def"; 79 childDoc.documentElement.appendChild(div1); 80 childDoc.documentElement.appendChild(div2); 81 // Now: </head><body><br></body><div>abc</div><div>def</div> 82 childDoc.getSelection().collapse( 83 testingBackspace ? div2.firstChild : div1.firstChild, 84 testingBackspace ? 0 : div1.firstChild.length 85 ); 86 await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); 87 removeResourceScriptElements(childDoc); 88 assert_in_array( 89 childDoc.documentElement.innerHTML, 90 [ 91 '<head><title>iframe</title></head><body><br></body><div>abcdef</div>', 92 '<head><title>iframe</title></head><body><br></body><div>abcdef<br></div>', 93 ], 94 "The <div> elements should be merged" 95 ); 96 assert_equals( 97 div1.isConnected ^ div2.isConnected, 98 1, 99 "One <div> element should be removed, and the other should stay" 100 ); 101 }, `${commandName} in <div> elements after <body> should join them`); 102 103 // Deleting around end of the <body> should merge the element after the 104 // <body> into the <body>. 105 promise_test(async () => { 106 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 107 const childDoc = iframe.contentDocument; 108 const utils = new EditorTestUtils(childDoc.documentElement); 109 childDoc.body.innerHTML = "abc"; 110 const div = childDoc.createElement("div"); 111 div.innerHTML = "def"; 112 childDoc.documentElement.appendChild(div); 113 // Now: </head><body>abc</body><div>def</div> 114 childDoc.getSelection().collapse( 115 testingBackspace ? div.firstChild : childDoc.body.firstChild, 116 testingBackspace ? 0 : childDoc.body.firstChild.length 117 ); 118 await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); 119 removeResourceScriptElements(childDoc); 120 assert_in_array( 121 childDoc.documentElement.innerHTML, 122 [ 123 '<head><title>iframe</title></head><body>abcdef</body>', 124 '<head><title>iframe</title></head><body>abcdef<br></body>', 125 ], 126 "The text should be merged" 127 ); 128 assert_false( 129 div.isConnected, 130 "The <div> following <body> should be removed" 131 ); 132 }, `${commandName} should merge <div> after <body> into the <body>`); 133 134 promise_test(async () => { 135 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 136 const childDoc = iframe.contentDocument; 137 const utils = new EditorTestUtils(childDoc.documentElement); 138 const div1 = childDoc.createElement("div"); 139 div1.innerHTML = "abc"; 140 const div2 = childDoc.createElement("div"); 141 div2.innerHTML = "def"; 142 childDoc.body.innerHTML = ""; 143 childDoc.body.appendChild(div1); 144 childDoc.documentElement.appendChild(div2); 145 // Now: </head><body><div>abc</div></body><div>def</div> 146 childDoc.getSelection().collapse( 147 testingBackspace ? div2.firstChild : div1.firstChild, 148 testingBackspace ? 0 : div1.firstChild.length 149 ); 150 await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); 151 removeResourceScriptElements(childDoc); 152 assert_in_array( 153 childDoc.documentElement.innerHTML, 154 [ 155 '<head><title>iframe</title></head><body><div>abcdef</div></body>', 156 '<head><title>iframe</title></head><body><div>abcdef<br></div></body>', 157 ], 158 "The <div> elements should be merged" 159 ); 160 assert_true( 161 !div2.isConnected || (div2.isConnected && div2.parentNode == childDoc.body), 162 "The <div> following <body> should be removed or moved into the <body>" 163 ); 164 }, `${commandName} should merge <div> after <body> into the <div> in the <body>`); 165 166 promise_test(async () => { 167 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 168 const childDoc = iframe.contentDocument; 169 const utils = new EditorTestUtils(childDoc.documentElement); 170 const div = childDoc.createElement("div"); 171 div.innerHTML = "abc"; 172 childDoc.documentElement.appendChild(div); 173 // Now: </head><body><br></body><div>abc</div> 174 childDoc.getSelection().collapse( 175 testingBackspace ? div.firstChild : childDoc.body, 176 0 177 ); 178 await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); 179 removeResourceScriptElements(childDoc); 180 assert_in_array( 181 childDoc.documentElement.innerHTML, 182 [ 183 '<head><title>iframe</title></head><body>abc</body>', 184 '<head><title>iframe</title></head><body>abc<br></body>', 185 ], 186 "The <div> element should be merged into the <body>" 187 ); 188 assert_false( 189 div.isConnected, 190 "The <div> element should be removed" 191 ); 192 }, `${commandName} should merge <div> after <body> into the empty <body>`); 193 194 // Deleting around start of the <body> should merge the element before the 195 // <body> into the <body>. 196 promise_test(async () => { 197 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 198 const childDoc = iframe.contentDocument; 199 const utils = new EditorTestUtils(childDoc.documentElement); 200 const div = childDoc.createElement("div"); 201 div.innerHTML = "abc"; 202 childDoc.body.innerHTML = "def"; 203 childDoc.documentElement.insertBefore(div, childDoc.body); 204 // Now: </head><div>abc</div><body>def</body> 205 childDoc.getSelection().collapse( 206 testingBackspace ? childDoc.body.firstChild : div.firstChild, 207 testingBackspace ? 0 : div.firstChild.length 208 ); 209 await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); 210 removeResourceScriptElements(childDoc); 211 assert_in_array( 212 childDoc.documentElement.innerHTML, 213 [ 214 '<head><title>iframe</title></head><body>abcdef</body>', 215 '<head><title>iframe</title></head><body>abcdef<br></body>', 216 ], 217 "The text should be merged" 218 ); 219 assert_false( 220 div.isConnected, 221 "The <div> following <body> should be removed" 222 ); 223 }, `${commandName} should merge <div> before <body> into the <body>`); 224 225 promise_test(async () => { 226 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 227 const childDoc = iframe.contentDocument; 228 const utils = new EditorTestUtils(childDoc.documentElement); 229 const div1 = childDoc.createElement("div"); 230 div1.innerHTML = "abc"; 231 const div2 = childDoc.createElement("div"); 232 div2.innerHTML = "def"; 233 childDoc.documentElement.insertBefore(div1, childDoc.body); 234 childDoc.body.innerHTML = ""; 235 childDoc.body.appendChild(div2); 236 // Now: </head><div>abc</div><body><div>def</div></body> 237 childDoc.getSelection().collapse( 238 testingBackspace ? div2.firstChild : div1.firstChild, 239 testingBackspace ? 0 : div1.firstChild.length 240 ); 241 await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); 242 removeResourceScriptElements(childDoc); 243 assert_in_array( 244 childDoc.documentElement.innerHTML, 245 [ 246 '<head><title>iframe</title></head><body><div>abcdef</div></body>', 247 '<head><title>iframe</title></head><body><div>abcdef<br></div></body>', 248 ], 249 "The <div> elements should be merged" 250 ); 251 assert_true( 252 !div2.isConnected || (div2.isConnected && div2.parentNode == childDoc.body), 253 "The <div> following <body> should be removed or moved into the <body>" 254 ); 255 }, `${commandName} should merge <div> before <body> into the <div> in the <body>`); 256 257 promise_test(async () => { 258 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 259 const childDoc = iframe.contentDocument; 260 const utils = new EditorTestUtils(childDoc.documentElement); 261 const div = childDoc.createElement("div"); 262 div.innerHTML = "abc"; 263 childDoc.documentElement.insertBefore(div, childDoc.body); 264 // Now: </head><div>abc</div><body><br></body> 265 childDoc.getSelection().collapse( 266 testingBackspace ? childDoc.body : div.firstChild, 267 testingBackspace ? 0: div.firstChild.length 268 ); 269 await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); 270 removeResourceScriptElements(childDoc); 271 assert_in_array( 272 childDoc.documentElement.innerHTML, 273 [ 274 '<head><title>iframe</title></head><body>abc</body>', 275 '<head><title>iframe</title></head><body>abc<br></body>', 276 ], 277 "The <div> element should be merged into the <body>" 278 ); 279 assert_false( 280 div.isConnected, 281 "The <div> element should be removed" 282 ); 283 }, `${commandName} should merge <div> before <body> into the empty <body>`); 284 285 // Deleting around end of the <head> should not delete the <head> element. 286 if (testingBackspace) { 287 promise_test(async () => { 288 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 289 const childDoc = iframe.contentDocument; 290 const utils = new EditorTestUtils(childDoc.documentElement); 291 const div = childDoc.createElement("div"); 292 div.innerHTML = "abc"; 293 childDoc.body.innerHTML = "def"; 294 childDoc.documentElement.insertBefore(div, childDoc.body); 295 // Now: </head><div>abc</div><body>def</body> 296 childDoc.getSelection().collapse(div.firstChild, 0); 297 await utils.sendBackspaceKey(); 298 removeResourceScriptElements(childDoc); 299 assert_equals( 300 childDoc.documentElement.innerHTML, 301 '<head><title>iframe</title></head><div>abc</div><body>def</body>', 302 "The <div> element should be merged into the <body>" 303 ); 304 assert_true( 305 div.isConnected, 306 "The <div> element should not be removed" 307 ); 308 }, `delete from <div> following invisible <head> element shouldn't delete the <head> element`); 309 } 310 311 // Joining elements around <head> element should not delete the <head> element. 312 promise_test(async () => { 313 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 314 const childDoc = iframe.contentDocument; 315 const utils = new EditorTestUtils(childDoc.documentElement); 316 const div1 = childDoc.createElement("div"); 317 div1.innerHTML = "abc"; 318 const div2 = childDoc.createElement("div"); 319 div2.innerHTML = "def"; 320 childDoc.documentElement.insertBefore(div1, childDoc.head); 321 childDoc.documentElement.insertBefore(div2, childDoc.body); 322 // Now: <div>abc</div><head>...</head><div>def</div><body><br></body> 323 childDoc.getSelection().collapse( 324 testingBackspace ? div2.firstChild : div1.firstChild, 325 testingBackspace ? 0 : div1.firstChild.length 326 ); 327 await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); 328 removeResourceScriptElements(childDoc); 329 assert_in_array( 330 childDoc.documentElement.innerHTML, 331 [ 332 '<div>abcdef</div><head><title>iframe</title></head><body><br></body>', 333 '<div>abcdef<br></div><head><title>iframe</title></head><body><br></body>', 334 '<head><title>iframe</title></head><div>abcdef</div><body><br></body>', 335 '<head><title>iframe</title></head><div>abcdef<br></div><body><br></body>', 336 ], 337 "The <div> element should be merged into the left <div> without deleting the <head>" 338 ); 339 assert_true( 340 div1.isConnected ^ div2.isConnected, 341 "One <div> element should be removed, but the other should stay" 342 ); 343 }, `${commandName} from <div> around invisible <head> element should not delete the <head>`); 344 345 346 // Same as <body> element boundary, allow joining across <head> elements if 347 // and only if both elements are normal elements. 348 promise_test(async () => { 349 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 350 const childDoc = iframe.contentDocument; 351 childDoc.head.setAttribute("style", "display:block"); 352 const utils = new EditorTestUtils(childDoc.documentElement); 353 const div1 = childDoc.createElement("div"); 354 div1.innerHTML = "abc"; 355 const div2 = childDoc.createElement("div"); 356 div2.innerHTML = "def"; 357 childDoc.head.appendChild(div1); 358 childDoc.documentElement.insertBefore(div2, childDoc.body); 359 // Now: <div>abc</div></head><div>def</div><body><br></body> 360 childDoc.getSelection().collapse( 361 testingBackspace ? div2.firstChild : div1.firstChild, 362 testingBackspace ? 0 : div1.firstChild.length 363 ); 364 await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); 365 removeResourceScriptElements(childDoc); 366 childDoc.head.removeAttribute("style"); 367 assert_in_array( 368 childDoc.documentElement.innerHTML, 369 [ 370 '<head><title>iframe</title><div>abcdef</div></head><body><br></body>', 371 '<head><title>iframe</title><div>abcdef<br></div></head><body><br></body>', 372 ], 373 "The <div> element should be merged into the <div> in the <head>" 374 ); 375 assert_false( 376 div2.isConnected, 377 "The <div> element should be removed" 378 ); 379 }, `${commandName} from <div> following visible <head> element should be merged with the <div> in the <head>`); 380 381 // However, don't allow to join with <script> and <style> elements because 382 // changing them may not be safe. 383 promise_test(async () => { 384 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 385 const childDoc = iframe.contentDocument; 386 childDoc.head.setAttribute("style", "display:block"); 387 const utils = new EditorTestUtils(childDoc.documentElement); 388 const style = childDoc.createElement("style"); 389 style.setAttribute("style", "display:block;white-space:pre"); 390 style.innerHTML = "abc"; 391 const div = childDoc.createElement("div"); 392 div.innerHTML = "def"; 393 childDoc.head.appendChild(style); 394 childDoc.documentElement.insertBefore(div, childDoc.body); 395 // Now: <style>abc</style></head><div>def</div><body><br></body> 396 childDoc.getSelection().collapse( 397 testingBackspace ? div.firstChild : style.firstChild, 398 testingBackspace ? 0 : style.firstChild.length 399 ); 400 await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); 401 removeResourceScriptElements(childDoc); 402 childDoc.head.removeAttribute("style"); 403 style.removeAttribute("style"); 404 assert_equals( 405 childDoc.documentElement.innerHTML, 406 '<head><title>iframe</title><style>abc</style></head><div>def</div><body><br></body>', 407 "The <div> element should not be merged with the <style> in the <head>" 408 ); 409 assert_true( 410 div.isConnected, 411 "The <div> element should not be removed" 412 ); 413 }, `${commandName} from <div> following visible <head> element should be merged with the visible <style> in the <head>`); 414 415 promise_test(async () => { 416 await initializeAndWaitForLoad(iframe, minimumSrcDoc); 417 const childDoc = iframe.contentDocument; 418 childDoc.head.setAttribute("style", "display:block"); 419 const utils = new EditorTestUtils(childDoc.documentElement); 420 const script = childDoc.createElement("script"); 421 script.setAttribute("style", "display:block;white-space:pre"); 422 script.innerHTML = "// abc"; 423 const div = childDoc.createElement("div"); 424 div.innerHTML = "def"; 425 childDoc.head.appendChild(script); 426 childDoc.documentElement.insertBefore(div, childDoc.body); 427 // Now: <script>// abc</ script></head><div>def</div><body><br></body> 428 childDoc.getSelection().collapse( 429 testingBackspace ? div.firstChild : script.firstChild, 430 testingBackspace ? 0 : script.firstChild.length 431 ); 432 await (testingBackspace ? utils.sendBackspaceKey() : utils.sendDeleteKey()); 433 removeResourceScriptElements(childDoc); 434 childDoc.head.removeAttribute("style"); 435 script.removeAttribute("style"); 436 assert_equals( 437 childDoc.documentElement.innerHTML, 438 '<head><title>iframe</title><script>// abc</' + 'script></head><div>def</div><body><br></body>', 439 "The <div> element should not be merged with the <script> in the <head>" 440 ); 441 assert_true( 442 div.isConnected, 443 "The <div> element should not be removed" 444 ); 445 }, `${commandName} from <div> following visible <script> element should be merged with the visible <script> in the <head>`); 446 447 </script> 448 </body> 449 </html>