delete-without-unwrapping-first-line-of-child-block.html (46529B)
1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="timeout" content="long"> 6 <meta name="variant" content="?method=BackspaceKey&lineBreak=br"> 7 <meta name="variant" content="?method=DeleteKey&lineBreak=br"> 8 <meta name="variant" content="?method=deleteCommand&lineBreak=br"> 9 <meta name="variant" content="?method=forwardDeleteCommand&lineBreak=br"> 10 <meta name="variant" content="?method=BackspaceKey&lineBreak=preformat"> 11 <meta name="variant" content="?method=DeleteKey&lineBreak=preformat"> 12 <meta name="variant" content="?method=deleteCommand&lineBreak=preformat"> 13 <meta name="variant" content="?method=forwardDeleteCommand&lineBreak=preformat"> 14 <title>Tests for deleting preceding lines of right child block if range ends at start of the right child</title> 15 <script src="/resources/testharness.js"></script> 16 <script src="/resources/testharnessreport.js"></script> 17 <script src="/resources/testdriver.js"></script> 18 <script src="/resources/testdriver-vendor.js"></script> 19 <script src="/resources/testdriver-actions.js"></script> 20 <script src="../include/editor-test-utils.js"></script> 21 <script> 22 "use strict"; 23 24 /** 25 * Browsers delete only preceding lines (and selected content in the child 26 * block) when the deleting range starts from a line and ends in a child block 27 * without unwrapping the (new) first line of the child block at end. Note that 28 * this is a special handling for the above case, i.e., if the range starts from 29 * a middle of a preceding line of the child block, the first line of the child 30 * block should be unwrapped and merged into the preceding line. This is also 31 * applied when the range is directly replaced with new content like typing a 32 * character. Finally, selection should be collapsed at start of the child 33 * block and new content should be inserted at start of the child block. 34 * 35 * This file also tests getTargetRanges() of `beforeinput` of at deletion and 36 * replacing the selection directly. In the former case, if the range ends at 37 * start of the child block, browsers do not touch the child block. Therefore, 38 * the target ranges should the a range deleting the preceding lines, i.e., 39 * should be end at the child block. When the range is replaced directly, the 40 * content will be inserted at start of the child block, and also when the range 41 * selects some content in the child block, browsers touch the child block. 42 * Therefore, the target range should end at the next insertion point. 43 */ 44 45 const searchParams = new URLSearchParams(document.location.search); 46 const testUserInput = searchParams.get("method") == "BackspaceKey" || searchParams.get("method") == "DeleteKey"; 47 const testBackward = searchParams.get("method") == "BackspaceKey" || searchParams.get("method") == "deleteCommand"; 48 const deleteMethod = 49 testUserInput 50 ? testBackward ? "Backspace" : "Delete" 51 : `document.execCommand("${testBackward ? "delete" : "forwarddelete"}")`; 52 const insertTextMethod = testUserInput ? "Typing \"X\"" : "document.execCommand(\"insertText\", false, \"X\")"; 53 const lineBreak = searchParams.get("lineBreak") == "br" ? "<br>" : "\n"; 54 const lineBreakIsBR = lineBreak == "<br>"; 55 56 function run(editorUtils) { 57 if (testUserInput) { 58 return testBackward ? editorUtils.sendBackspaceKey() : editorUtils.sendDeleteKey(); 59 } 60 editorUtils.document.execCommand(testBackward ? "delete" : "forwardDelete"); 61 } 62 63 function typeCharacter(editorUtils, ch) { 64 if (testUserInput) { 65 return editorUtils.sendKey(ch); 66 } 67 document.execCommand("insertText", false, ch); 68 } 69 70 async function runDeleteTest( 71 runningTest, 72 testUtils, 73 initialInnerHTML, 74 expectedAfterDeletion, 75 whatShouldHappenAfterDeletion, 76 expectedAfterDeletionAndInsertion, 77 whatShouldHappenAfterDeletionAndInsertion, 78 expectedTargetRangesAtDeletion, 79 whatGetTargetRangesShouldReturn 80 ) { 81 let targetRanges = []; 82 if (testUserInput) { 83 testUtils.editingHost.addEventListener( 84 "beforeinput", 85 event => targetRanges = event.getTargetRanges(), 86 {once: true} 87 ); 88 } 89 await run(testUtils); 90 (Array.isArray(expectedAfterDeletion) ? assert_in_array : assert_equals)( 91 testUtils.editingHost.innerHTML, 92 expectedAfterDeletion, 93 `${runningTest.name} ${whatShouldHappenAfterDeletion}` 94 ); 95 if (testUserInput) { 96 test(() => { 97 const arrayOfStringifiedExpectedTargetRanges = (() => { 98 let arrayOfTargetRanges = []; 99 for (const expectedTargetRanges of expectedTargetRangesAtDeletion) { 100 arrayOfTargetRanges.push( 101 EditorTestUtils.getRangeArrayDescription(expectedTargetRanges) 102 ); 103 } 104 return arrayOfTargetRanges; 105 })(); 106 assert_in_array( 107 EditorTestUtils.getRangeArrayDescription(targetRanges), 108 arrayOfStringifiedExpectedTargetRanges 109 ); 110 }, `getTargetRanges() for ${runningTest.name} ${whatGetTargetRangesShouldReturn}`); 111 } 112 await typeCharacter(testUtils, "X"); 113 (Array.isArray(expectedAfterDeletionAndInsertion) ? assert_in_array : assert_equals)( 114 testUtils.editingHost.innerHTML, 115 expectedAfterDeletionAndInsertion, 116 `${insertTextMethod} after ${runningTest.name} ${whatShouldHappenAfterDeletionAndInsertion}` 117 ); 118 } 119 120 async function runReplacingTest( 121 runningTest, 122 testUtils, 123 initialInnerHTML, 124 expectedAfterReplacing, 125 whatShouldHappenAfterReplacing, 126 expectedTargetRangesAtReplace, 127 whatGetTargetRangesShouldReturn 128 ) { 129 let targetRanges = []; 130 if (testUserInput) { 131 testUtils.editingHost.addEventListener( 132 "beforeinput", 133 event => targetRanges = event.getTargetRanges(), 134 {once: true} 135 ); 136 } 137 await typeCharacter(testUtils, "X"); 138 (Array.isArray(expectedAfterReplacing) ? assert_in_array : assert_equals)( 139 testUtils.editingHost.innerHTML, 140 expectedAfterReplacing, 141 `${runningTest.name} ${whatShouldHappenAfterReplacing}` 142 ); 143 if (testUserInput) { 144 test(() => { 145 const arrayOfStringifiedExpectedTargetRanges = (() => { 146 let arrayOfTargetRanges = []; 147 for (const expectedTargetRanges of expectedTargetRangesAtReplace) { 148 arrayOfTargetRanges.push( 149 EditorTestUtils.getRangeArrayDescription(expectedTargetRanges) 150 ); 151 } 152 return arrayOfTargetRanges; 153 })(); 154 assert_in_array( 155 EditorTestUtils.getRangeArrayDescription(targetRanges), 156 arrayOfStringifiedExpectedTargetRanges 157 ); 158 }, `getTargetRanges() for ${runningTest.name} ${whatGetTargetRangesShouldReturn}`); 159 } 160 } 161 162 addEventListener("load", () => { 163 const editingHost = document.querySelector("div[contenteditable]"); 164 const selStart = lineBreakIsBR ? "{" : "["; 165 const selCollapsed = lineBreakIsBR ? "{}" : "[]"; 166 editingHost.style.whiteSpace = lineBreakIsBR ? "normal" : "pre"; 167 const testUtils = new EditorTestUtils(editingHost); 168 (() => { 169 const initialInnerHTML = 170 `abc${lineBreak}${selStart}${lineBreak}<div id="child">]def<br>ghi</div>`; 171 promise_test(async t => { 172 testUtils.setupEditingHost(initialInnerHTML); 173 await runDeleteTest( 174 t, testUtils, initialInnerHTML, 175 [ 176 `abc${lineBreak}<div id="child">def<br>ghi</div>`, 177 `abc<div id="child">def<br>ghi</div>`, 178 ], 179 "should delete only the preceding empty line of the child <div>", 180 [ 181 `abc${lineBreak}<div id="child">Xdef<br>ghi</div>`, 182 `abc<div id="child">Xdef<br>ghi</div>`, 183 ], 184 "should insert text into the child <div>", 185 lineBreakIsBR 186 ? [ 187 // abc<br>{<br>}<div> 188 [{ startContainer: editingHost, startOffset: 2, endContainer: editingHost, endOffset: 3 }], 189 // abc{<br><br>}<div> 190 [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 3 }], 191 // abc[<br><br>}<div> 192 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 3 }], 193 ] 194 : [ 195 // abc\n[\n}<div> 196 [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost, endOffset: 1 }], 197 // abc[\n\n}<div> 198 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 1 }], 199 // abc\n[\n]<div> 200 [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }], 201 // abc[\n\n]<div> 202 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }], 203 ], 204 "should return a range before the child <div>" 205 ); 206 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 207 208 promise_test(async t => { 209 testUtils.setupEditingHost(initialInnerHTML); 210 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 211 await runReplacingTest( 212 t, testUtils, initialInnerHTML, 213 [ 214 `abc${lineBreak}<div id="child">Xdef<br>ghi</div>`, 215 `abc<div id="child">Xdef<br>ghi</div>`, 216 ], 217 "should not unwrap the first line of the child <div>", 218 lineBreakIsBR 219 ? [ 220 // abc<br>{<br><div>]def 221 [{ startContainer: editingHost, startOffset: 2, endContainer: firstTextInChildDiv, endOffset: 0 }], 222 // abc{<br><br><div>]def 223 [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 0 }], 224 // abc[<br><br><div>]def 225 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 0 }], 226 ] 227 : [ 228 // abc\n[\n<div>]def 229 [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: firstTextInChildDiv, endOffset: 0 }], 230 // abc[\n\n<div>]def 231 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 0 }], 232 ], 233 "should return a range ending in the child <div>" 234 ); 235 }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`); 236 })(); 237 238 (() => { 239 const initialInnerHTML = 240 `${lineBreak}[abc${lineBreak}<div id="child">]def<br>ghi</div>`; 241 promise_test(async t => { 242 testUtils.setupEditingHost(initialInnerHTML); 243 await runDeleteTest( 244 t, testUtils, initialInnerHTML, 245 `${lineBreak}<div id="child">def<br>ghi</div>`, 246 "should delete only the preceding empty line of the child <div>", 247 `${lineBreak}<div id="child">Xdef<br>ghi</div>`, 248 "should insert text into the child <div>", 249 lineBreakIsBR 250 ? [ 251 // <br>[abc<br>}<div> 252 [{ startContainer: editingHost.firstChild.nextSibling, startOffset: 0, endContainer: editingHost, endOffset: 3 }], 253 ] 254 : [ 255 // \n[abc\n}<div> 256 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost, endOffset: 1 }], 257 // \n[abc\n]<div> 258 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost.firstChild, endOffset: "\nabc\n".length }], 259 ], 260 "should return a range before the child <div>" 261 ); 262 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 263 264 promise_test(async t => { 265 testUtils.setupEditingHost(initialInnerHTML); 266 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 267 await runReplacingTest( 268 t, testUtils, initialInnerHTML, 269 `${lineBreak}<div id="child">Xdef<br>ghi</div>`, 270 "should not unwrap the first line of the child <div>", 271 lineBreakIsBR 272 ? [ 273 // <br>[abc<br><div>]def 274 [{ startContainer: editingHost.firstChild.nextSibling, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 0 }], 275 ] 276 : [ 277 // \n[abc\n<div>]def 278 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 0 }], 279 ], 280 "should return a range ending in the child <div>" 281 ); 282 }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`); 283 })(); 284 285 (() => { 286 const initialInnerHTML = 287 `${lineBreak}${selStart}${lineBreak}<div id="child">]def<br>ghi</div>`; 288 promise_test(async t => { 289 testUtils.setupEditingHost(initialInnerHTML); 290 await runDeleteTest( 291 t, testUtils, initialInnerHTML, 292 `${lineBreak}<div id="child">def<br>ghi</div>`, 293 "should delete only the preceding empty line of the child <div>", 294 `${lineBreak}<div id="child">Xdef<br>ghi</div>`, 295 "should insert text into the child <div>", 296 lineBreakIsBR 297 ? [ 298 // <br>{<br>}<div> 299 [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 2 }], 300 ] 301 : [ 302 // \n[\n}<div> 303 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost, endOffset: 1 }], 304 // \n[\n]<div> 305 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost.firstChild, endOffset: "\n\n".length }], 306 ], 307 "should return a range before the child <div>" 308 ); 309 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 310 311 promise_test(async t => { 312 testUtils.setupEditingHost(initialInnerHTML); 313 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 314 await runReplacingTest( 315 t, testUtils, initialInnerHTML, 316 `${lineBreak}<div id="child">Xdef<br>ghi</div>`, 317 "should not unwrap the first line of the child <div>", 318 lineBreakIsBR 319 ? [ 320 // <br>{<br><div>]def 321 [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 0 }], 322 ] 323 : [ 324 // \n[\n<div>]def 325 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 0 }], 326 ], 327 "should return a range ending in the child <div>" 328 ); 329 }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`); 330 })(); 331 332 (() => { 333 const initialInnerHTML = 334 `${selStart}${lineBreak}${lineBreak}<div id="child">]def<br>ghi</div>`; 335 promise_test(async t => { 336 testUtils.setupEditingHost(initialInnerHTML); 337 await runDeleteTest( 338 t, testUtils, initialInnerHTML, 339 `<div id="child">def<br>ghi</div>`, 340 "should delete only the preceding empty line of the child <div>", 341 `<div id="child">Xdef<br>ghi</div>`, 342 "should insert text into the child <div>", 343 lineBreakIsBR 344 ? [ 345 // {<br><br>}<div> 346 [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 2 }], 347 ] 348 : [ 349 // [\n\n}<div> 350 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost, endOffset: 1 }], 351 // [\n\n}<div> 352 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost.firstChild, endOffset: "\n\n".length }], 353 ], 354 "should return a range before the child <div>" 355 ); 356 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 357 358 promise_test(async t => { 359 testUtils.setupEditingHost(initialInnerHTML); 360 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 361 await runReplacingTest( 362 t, testUtils, initialInnerHTML, 363 `<div id="child">Xdef<br>ghi</div>`, 364 "should not unwrap the first line of the child <div>", 365 lineBreakIsBR 366 ? [ 367 // {<br><br><div>]def 368 [{ startContainer: editingHost, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 0 }], 369 ] 370 : [ 371 // [\n\n<div>]def 372 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 0 }], 373 ], 374 "should return a range ending in the child <div>" 375 ); 376 }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`); 377 })(); 378 379 (() => { 380 const initialInnerHTML = 381 `[abc${lineBreak}${lineBreak}<div id="child">]def<br>ghi</div>`; 382 promise_test(async t => { 383 testUtils.setupEditingHost(initialInnerHTML); 384 await runDeleteTest( 385 t, testUtils, initialInnerHTML, 386 `<div id="child">def<br>ghi</div>`, 387 "should delete only the preceding empty line of the child <div>", 388 `<div id="child">Xdef<br>ghi</div>`, 389 "should insert text into the child <div>", 390 lineBreakIsBR 391 ? [ 392 // [abc<br><br>}<div> 393 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost, endOffset: 3 }], 394 ] 395 : [ 396 // {abc\n\n}<div> 397 [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }], 398 // [abc\n\n}<div> 399 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost, endOffset: 1 }], 400 // [abc\n\n}<div> 401 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }], 402 ], 403 "should return a range before the child <div>" 404 ); 405 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 406 407 promise_test(async t => { 408 testUtils.setupEditingHost(initialInnerHTML); 409 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 410 await runReplacingTest( 411 t, testUtils, initialInnerHTML, 412 `<div id="child">Xdef<br>ghi</div>`, 413 "should not unwrap the first line of the child <div>", 414 lineBreakIsBR 415 ? [ 416 // [abc<br><div>]def 417 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 0 }], 418 ] 419 : [ 420 // [abc\n<div>]def 421 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 0 }], 422 ], 423 "should return a range ending in the child <div>" 424 ); 425 }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`); 426 })(); 427 428 (() => { 429 const initialInnerHTML = 430 `abc${lineBreak}${selStart}${lineBreak}<div id="child">d]ef<br>ghi</div>`; 431 promise_test(async t => { 432 testUtils.setupEditingHost(initialInnerHTML); 433 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 434 await runDeleteTest( 435 t, testUtils, initialInnerHTML, 436 [ 437 `abc${lineBreak}<div id="child">ef<br>ghi</div>`, 438 `abc<div id="child">ef<br>ghi</div>`, 439 ], 440 "should delete only the preceding empty line of the child <div> and selected text in the <div>", 441 [ 442 `abc${lineBreak}<div id="child">Xef<br>ghi</div>`, 443 `abc<div id="child">Xef<br>ghi</div>`, 444 ], 445 "should insert text into the child <div>", 446 lineBreakIsBR 447 ? [ 448 // abc<br>{<br><div>d]ef 449 [{ startContainer: editingHost, startOffset: 2, endContainer: firstTextInChildDiv, endOffset: 1 }], 450 // abc{<br><br><div>d]ef 451 [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 1 }], 452 // abc[<br><br><div>d]ef 453 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 1 }], 454 ] 455 : [ 456 // abc\n[\n}<div>d]ef 457 [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }], 458 // abc[\n\n}<div>d]ef 459 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 1 }], 460 ], 461 "should return a range ends at start of the child <div>" 462 ); 463 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 464 465 promise_test(async t => { 466 testUtils.setupEditingHost(initialInnerHTML); 467 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 468 await runReplacingTest( 469 t, testUtils, initialInnerHTML, 470 [ 471 `abc${lineBreak}<div id="child">Xef<br>ghi</div>`, 472 `abc<div id="child">Xef<br>ghi</div>`, 473 ], 474 "should not unwrap the first line of the child <div>", 475 lineBreakIsBR 476 ? [ 477 // abc<br>{<br><div>d]ef 478 [{ startContainer: editingHost, startOffset: 2, endContainer: firstTextInChildDiv, endOffset: 1 }], 479 // abc{<br><br><div>d]ef 480 [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 1 }], 481 // abc[<br><br><div>d]ef 482 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 1 }], 483 ] 484 : [ 485 // abc\n[\n<div>d]ef 486 [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }], 487 // abc[\n\n<div>d]ef 488 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: firstTextInChildDiv, endOffset: 1 }], 489 ], 490 "should return a range ends at start of the child <div>" 491 ); 492 }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`); 493 })(); 494 495 (() => { 496 const initialInnerHTML = 497 `${lineBreak}[abc${lineBreak}<div id="child">d]ef<br>ghi</div>`; 498 promise_test(async t => { 499 testUtils.setupEditingHost(initialInnerHTML); 500 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 501 await runDeleteTest( 502 t, testUtils, initialInnerHTML, 503 `${lineBreak}<div id="child">ef<br>ghi</div>`, 504 "should delete only the preceding empty line of the child <div> and the selected content in the <div>", 505 `${lineBreak}<div id="child">Xef<br>ghi</div>`, 506 "should insert text into the child <div>", 507 lineBreakIsBR 508 ? [ 509 // <br>[abc<br><div>d]ef 510 [{ startContainer: editingHost.firstChild.nextSibling, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }], 511 ] 512 : [ 513 // \n[abc\n<div>d]ef 514 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }], 515 ], 516 "should return a range ends at start of the child <div>" 517 ); 518 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 519 520 promise_test(async t => { 521 testUtils.setupEditingHost(initialInnerHTML); 522 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 523 await runReplacingTest( 524 t, testUtils, initialInnerHTML, 525 `${lineBreak}<div id="child">Xef<br>ghi</div>`, 526 "should not unwrap the first line of the child <div>", 527 lineBreakIsBR 528 ? [ 529 // <br>[abc<br><div>d]ef 530 [{ startContainer: editingHost.firstChild.nextSibling, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }], 531 ] 532 : [ 533 // \n[abc\n<div>d]ef 534 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }], 535 ], 536 "should return a range ends at start of the child <div>" 537 ); 538 }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`); 539 })(); 540 541 (() => { 542 const initialInnerHTML = 543 `${lineBreak}${selStart}${lineBreak}<div id="child">d]ef<br>ghi</div>`; 544 promise_test(async t => { 545 testUtils.setupEditingHost(initialInnerHTML); 546 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 547 await runDeleteTest( 548 t, testUtils, initialInnerHTML, 549 `${lineBreak}<div id="child">ef<br>ghi</div>`, 550 "should delete only the preceding empty line of the child <div> and selected content in the <div>", 551 `${lineBreak}<div id="child">Xef<br>ghi</div>`, 552 "should insert text into the child <div>", 553 lineBreakIsBR 554 ? [ 555 // <br>{<br><div>d]ef 556 [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 1 }], 557 ] 558 : [ 559 // \n[\n<div>d]ef 560 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }], 561 ], 562 "should return a range ends at start of the child <div>" 563 ); 564 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 565 566 promise_test(async t => { 567 testUtils.setupEditingHost(initialInnerHTML); 568 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 569 await runReplacingTest( 570 t, testUtils, initialInnerHTML, 571 `${lineBreak}<div id="child">Xef<br>ghi</div>`, 572 "should not unwrap the first line of the child <div>", 573 lineBreakIsBR 574 ? [ 575 // <br>{<br><div>d]ef 576 [{ startContainer: editingHost, startOffset: 1, endContainer: firstTextInChildDiv, endOffset: 1 }], 577 ] 578 : [ 579 // \n[\n<div>d]ef 580 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: firstTextInChildDiv, endOffset: 1 }], 581 ], 582 "should return a range ends at start of the child <div>" 583 ); 584 }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`); 585 })(); 586 587 (() => { 588 const initialInnerHTML = 589 `${selStart}${lineBreak}${lineBreak}<div id="child">d]ef<br>ghi</div>`; 590 promise_test(async t => { 591 testUtils.setupEditingHost(initialInnerHTML); 592 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 593 await runDeleteTest( 594 t, testUtils, initialInnerHTML, 595 `<div id="child">ef<br>ghi</div>`, 596 "should delete only the preceding empty line of the child <div> and selected content in the <div>", 597 `<div id="child">Xef<br>ghi</div>`, 598 "should insert text into the child <div>", 599 lineBreakIsBR 600 ? [ 601 // {<br><br><div>d]ef 602 [{ startContainer: editingHost, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }], 603 ] 604 : [ 605 // [\n\n<div>d]ef 606 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }], 607 ], 608 "should return a range ends at start of the child <div>" 609 ); 610 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 611 612 promise_test(async t => { 613 testUtils.setupEditingHost(initialInnerHTML); 614 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 615 await runReplacingTest( 616 t, testUtils, initialInnerHTML, 617 `<div id="child">Xef<br>ghi</div>`, 618 "should not unwrap the first line of the child <div>", 619 lineBreakIsBR 620 ? [ 621 // {<br><br><div>d]ef 622 [{ startContainer: editingHost, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }], 623 ] 624 : [ 625 // [\n\n<div>d]ef 626 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }], 627 ], 628 "should return a range ends at start of the child <div>" 629 ); 630 }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`); 631 })(); 632 633 (() => { 634 const initialInnerHTML = 635 `[abc${lineBreak}${lineBreak}<div id="child">d]ef<br>ghi</div>`; 636 promise_test(async t => { 637 testUtils.setupEditingHost(initialInnerHTML); 638 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 639 await runDeleteTest( 640 t, testUtils, initialInnerHTML, 641 `<div id="child">ef<br>ghi</div>`, 642 "should delete only the preceding empty line of the child <div> and selected content in the <div>", 643 `<div id="child">Xef<br>ghi</div>`, 644 "should insert text into the child <div>", 645 lineBreakIsBR 646 ? [ 647 // [abc<br><br><div>d]ef 648 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }], 649 ] 650 : [ 651 // [abc\n\n}<div>d]ef 652 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }], 653 ], 654 "should return a range ends at start of the child <div>" 655 ); 656 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 657 658 promise_test(async t => { 659 testUtils.setupEditingHost(initialInnerHTML); 660 const firstTextInChildDiv = editingHost.querySelector("div").firstChild; 661 await runReplacingTest( 662 t, testUtils, initialInnerHTML, 663 `<div id="child">Xef<br>ghi</div>`, 664 "should not unwrap the first line of the child <div>", 665 lineBreakIsBR 666 ? [ 667 // [abc<br><div>d]ef 668 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }], 669 ] 670 : [ 671 // [abc\n<div>d]ef 672 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: firstTextInChildDiv, endOffset: 1 }], 673 ], 674 "should return a range ends at start of the child <div>" 675 ); 676 }, `${insertTextMethod} at ${initialInnerHTML.replaceAll("\n", "\\n")}`); 677 })(); 678 679 (function test_BackspaceForCollapsedSelection() { 680 if (!testBackward) { 681 return; 682 } 683 (() => { 684 const initialInnerHTML = 685 `abc${lineBreak}${lineBreak}<div id="child">[]def<br>ghi</div>`; 686 promise_test(async t => { 687 testUtils.setupEditingHost(initialInnerHTML); 688 await runDeleteTest( 689 t, testUtils, initialInnerHTML, 690 [ 691 `abc${lineBreak}<div id="child">def<br>ghi</div>`, 692 `abc<div id="child">def<br>ghi</div>`, 693 ], 694 "should delete only the preceding empty line of the child <div>", 695 [ 696 `abc${lineBreak}<div id="child">Xdef<br>ghi</div>`, 697 `abc<div id="child">Xdef<br>ghi</div>`, 698 ], 699 "should insert text into the child <div>", 700 lineBreakIsBR 701 ? [ 702 // abc<br>{<br>}<div> 703 [{ startContainer: editingHost, startOffset: 2, endContainer: editingHost, endOffset: 3 }], 704 // abc{<br><br>}<div> 705 [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 3 }], 706 // abc[<br><br>}<div> 707 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 3 }], 708 ] 709 : [ 710 // abc\n[\n}<div> 711 [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost, endOffset: 1 }], 712 // abc[\n\n}<div> 713 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 1 }], 714 // abc\n[\n]<div> 715 [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }], 716 // abc[\n\n]<div> 717 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }], 718 ], 719 "should return a range before the child <div>" 720 ); 721 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 722 })(); 723 724 (() => { 725 const initialInnerHTML = 726 `${lineBreak}${lineBreak}<div id="child">[]def<br>ghi</div>`; 727 promise_test(async t => { 728 testUtils.setupEditingHost(initialInnerHTML); 729 await runDeleteTest( 730 t, testUtils, initialInnerHTML, 731 `${lineBreak}<div id="child">def<br>ghi</div>`, 732 "should delete only the preceding empty line of the child <div>", 733 `${lineBreak}<div id="child">Xdef<br>ghi</div>`, 734 "should insert text into the child <div>", 735 lineBreakIsBR 736 ? [ 737 // <br>{<br>}<div> 738 [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 2 }], 739 ] 740 : [ 741 // \n[\n}<div> 742 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost, endOffset: 1 }], 743 // \n[\n]<div> 744 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost.firstChild, endOffset: "\n\n".length }], 745 ], 746 "should return a range before the child <div>" 747 ); 748 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 749 })(); 750 751 (() => { 752 const initialInnerHTML = 753 `${lineBreak}<div id="child">[]def<br>ghi</div>`; 754 promise_test(async t => { 755 testUtils.setupEditingHost(initialInnerHTML); 756 await runDeleteTest( 757 t, testUtils, initialInnerHTML, 758 `<div id="child">def<br>ghi</div>`, 759 "should delete only the preceding empty line of the child <div>", 760 `<div id="child">Xdef<br>ghi</div>`, 761 "should insert text into the child <div>", 762 lineBreakIsBR 763 ? [ 764 // {<br>}<div> 765 [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }], 766 ] 767 : [ 768 // {\n}<div> 769 [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }], 770 // [\n}<div> 771 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost, endOffset: 1 }], 772 // [\n]<div> 773 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost.firstChild, endOffset: "\n".length }], 774 ], 775 "should return a range before the child <div>" 776 ); 777 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 778 })(); 779 780 (() => { 781 const initialInnerHTML = 782 `<b>abc${lineBreak}${lineBreak}</b></b><div id="child">[]def<br>ghi</div>`; 783 promise_test(async t => { 784 testUtils.setupEditingHost(initialInnerHTML); 785 const b = editingHost.querySelector("b"); 786 await runDeleteTest( 787 t, testUtils, initialInnerHTML, 788 [ 789 `<b>abc${lineBreak}</b><div id="child">def<br>ghi</div>`, 790 `<b>abc</b><div id="child">def<br>ghi</div>`, 791 ], 792 "should delete only the preceding empty line of the child <div> (<b> should stay)", 793 [ 794 `<b>abc${lineBreak}</b><div id="child">Xdef<br>ghi</div>`, 795 `<b>abc</b><div id="child">Xdef<br>ghi</div>`, 796 `<b>abc${lineBreak}</b><div id="child"><b>X</b>def<br>ghi</div>`, 797 `<b>abc</b><div id="child"><b>X</b>def<br>ghi</div>`, 798 ], 799 "should insert text into the child <div> with or without <b>", 800 lineBreakIsBR 801 ? [ 802 // <b>abc<br>{<br>}</b><div> 803 [{ startContainer: b, startOffset: 2, endContainer: b, endOffset: 3 }], 804 // <b>abc{<br><br>}</b><div> 805 [{ startContainer: b, startOffset: 1, endContainer: b, endOffset: 3 }], 806 // <b>abc[<br><br>}</b><div> 807 [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b, endOffset: 3 }], 808 ] 809 : [ 810 // <b>abc\n[\n}</b><div> 811 [{ startContainer: b.firstChild, startOffset: "abc\n".length, endContainer: b, endOffset: 1 }], 812 // <b>abc[\n\n}</b><div> 813 [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b, endOffset: 1 }], 814 // <b>abc\n[\n]</b><div> 815 [{ startContainer: b.firstChild, startOffset: "abc\n".length, endContainer: b.firstChild, endOffset: "abc\n\n".length }], 816 // <b>abc[\n\n]</b><div> 817 [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b.firstChild, endOffset: "abc\n\n".length }], 818 ], 819 "should return a range before the child <div>" 820 ); 821 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 822 })(); 823 824 (() => { 825 const initialInnerHTML = 826 `<b>${lineBreak}</b><div id="child">[]def<br>ghi</div>`; 827 promise_test(async t => { 828 testUtils.setupEditingHost(initialInnerHTML); 829 await runDeleteTest( 830 t, testUtils, initialInnerHTML, 831 `<div id="child">def<br>ghi</div>`, 832 "should delete only the preceding empty line (including the <b>) of the child <div>", 833 [ 834 `<div id="child">Xdef<br>ghi</div>`, 835 `<div id="child"><b>X</b>def<br>ghi</div>`, 836 ], 837 "should insert text into the child <div> with or without <b>", 838 [ 839 // {<b><br></b>}<div> or {<b>\n</b>}<div> 840 [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }], 841 ], 842 "should return a range before the child <div>" 843 ); 844 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 845 })(); 846 })(); 847 848 (function test_ForwardDeleteForCollapsedSelection() { 849 if (testBackward) { 850 return; 851 } 852 (() => { 853 const initialInnerHTML = 854 `abc${lineBreak}${selCollapsed}${lineBreak}<div id="child">def<br>ghi</div>`; 855 promise_test(async t => { 856 testUtils.setupEditingHost(initialInnerHTML); 857 await runDeleteTest( 858 t, testUtils, initialInnerHTML, 859 [ 860 `abc${lineBreak}<div id="child">def<br>ghi</div>`, 861 `abc<div id="child">def<br>ghi</div>`, 862 ], 863 "should delete only the preceding empty line of the child <div>", 864 [ 865 `abc${lineBreak}<div id="child">Xdef<br>ghi</div>`, 866 `abc<div id="child">Xdef<br>ghi</div>`, 867 ], 868 "should insert text into the child <div>", 869 lineBreakIsBR 870 ? [ 871 // abc<br>{<br>}<div> 872 [{ startContainer: editingHost, startOffset: 2, endContainer: editingHost, endOffset: 3 }], 873 // abc{<br><br>}<div> 874 [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 3 }], 875 // abc[<br><br>}<div> 876 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 3 }], 877 ] 878 : [ 879 // abc\n[\n}<div> 880 [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost, endOffset: 1 }], 881 // abc[\n\n}<div> 882 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost, endOffset: 1 }], 883 // abc\n[\n]<div> 884 [{ startContainer: editingHost.firstChild, startOffset: "abc\n".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }], 885 // abc[\n\n]<div> 886 [{ startContainer: editingHost.firstChild, startOffset: "abc".length, endContainer: editingHost.firstChild, endOffset: "abc\n\n".length }], 887 ], 888 "should return a range before the child <div>" 889 ); 890 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 891 })(); 892 893 (() => { 894 const initialInnerHTML = 895 `${lineBreak}${selCollapsed}${lineBreak}<div id="child">def<br>ghi</div>`; 896 promise_test(async t => { 897 testUtils.setupEditingHost(initialInnerHTML); 898 await runDeleteTest( 899 t, testUtils, initialInnerHTML, 900 `${lineBreak}<div id="child">def<br>ghi</div>`, 901 "should delete only the preceding empty line of the child <div>", 902 `${lineBreak}<div id="child">Xdef<br>ghi</div>`, 903 "should insert text into the child <div>", 904 lineBreakIsBR 905 ? [ 906 // <br>{<br>}<div> 907 [{ startContainer: editingHost, startOffset: 1, endContainer: editingHost, endOffset: 2 }], 908 ] 909 : [ 910 // \n[\n}<div> 911 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost, endOffset: 1 }], 912 // \n[\n]<div> 913 [{ startContainer: editingHost.firstChild, startOffset: "\n".length, endContainer: editingHost.firstChild, endOffset: "\n\n".length }], 914 ], 915 "should return a range before the child <div>" 916 ); 917 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 918 })(); 919 920 (() => { 921 const initialInnerHTML = 922 `${selCollapsed}${lineBreak}<div id="child">def<br>ghi</div>`; 923 promise_test(async t => { 924 testUtils.setupEditingHost(initialInnerHTML); 925 await runDeleteTest( 926 t, testUtils, initialInnerHTML, 927 `<div id="child">def<br>ghi</div>`, 928 "should delete only the preceding empty line of the child <div>", 929 `<div id="child">Xdef<br>ghi</div>`, 930 "should insert text into the child <div>", 931 lineBreakIsBR 932 ? [ 933 // {<br>}<div> 934 [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }], 935 ] 936 : [ 937 // {\n}<div> 938 [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }], 939 // [\n}<div> 940 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost, endOffset: 1 }], 941 // [\n]<div> 942 [{ startContainer: editingHost.firstChild, startOffset: 0, endContainer: editingHost.firstChild, endOffset: "\n".length }], 943 ], 944 "should return a range before the child <div>" 945 ); 946 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 947 })(); 948 949 (() => { 950 const initialInnerHTML = 951 `<b>abc${lineBreak}${selCollapsed}${lineBreak}</b></b><div id="child">def<br>ghi</div>`; 952 promise_test(async t => { 953 testUtils.setupEditingHost(initialInnerHTML); 954 const b = editingHost.querySelector("b"); 955 await runDeleteTest( 956 t, testUtils, initialInnerHTML, 957 [ 958 `<b>abc${lineBreak}</b><div id="child">def<br>ghi</div>`, 959 `<b>abc</b><div id="child">def<br>ghi</div>`, 960 ], 961 "should delete only the preceding empty line of the child <div> (<b> should stay)", 962 [ 963 `<b>abc${lineBreak}</b><div id="child">Xdef<br>ghi</div>`, 964 `<b>abc</b><div id="child">Xdef<br>ghi</div>`, 965 `<b>abc${lineBreak}</b><div id="child"><b>X</b>def<br>ghi</div>`, 966 `<b>abc</b><div id="child"><b>X</b>def<br>ghi</div>`, 967 ], 968 "should insert text into the child <div> with or without <b>", 969 lineBreakIsBR 970 ? [ 971 // <b>abc<br>{<br>}</b><div> 972 [{ startContainer: b, startOffset: 2, endContainer: b, endOffset: 3 }], 973 // <b>abc{<br><br>}</b><div> 974 [{ startContainer: b, startOffset: 1, endContainer: b, endOffset: 3 }], 975 // <b>abc[<br><br>}</b><div> 976 [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b, endOffset: 3 }], 977 ] 978 : [ 979 // <b>abc\n[\n}</b><div> 980 [{ startContainer: b.firstChild, startOffset: "abc\n".length, endContainer: b, endOffset: 1 }], 981 // <b>abc[\n\n}</b><div> 982 [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b, endOffset: 1 }], 983 // <b>abc\n[\n]</b><div> 984 [{ startContainer: b.firstChild, startOffset: "abc\n".length, endContainer: b.firstChild, endOffset: "abc\n\n".length }], 985 // <b>abc[\n\n]</b><div> 986 [{ startContainer: b.firstChild, startOffset: "abc".length, endContainer: b.firstChild, endOffset: "abc\n\n".length }], 987 ], 988 "should return a range before the child <div>" 989 ); 990 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 991 })(); 992 993 (() => { 994 const initialInnerHTML = 995 `<b>${selCollapsed}${lineBreak}</b><div id="child">def<br>ghi</div>`; 996 promise_test(async t => { 997 testUtils.setupEditingHost(initialInnerHTML); 998 await runDeleteTest( 999 t, testUtils, initialInnerHTML, 1000 `<div id="child">def<br>ghi</div>`, 1001 "should delete only the preceding empty line (including the <b>) of the child <div>", 1002 [ 1003 `<div id="child">Xdef<br>ghi</div>`, 1004 `<div id="child"><b>X</b>def<br>ghi</div>`, 1005 ], 1006 "should insert text into the child <div> with or without <b>", 1007 [ 1008 // {<b><br></b>}<div> or {<b>\n</b>}<div> 1009 [{ startContainer: editingHost, startOffset: 0, endContainer: editingHost, endOffset: 1 }], 1010 ], 1011 "should return a range before the child <div>" 1012 ); 1013 }, `${deleteMethod} at ${initialInnerHTML.replaceAll("\n", "\\\\n")}`); 1014 })(); 1015 })(); 1016 }, {once: true}); 1017 </script> 1018 </head> 1019 <body><div contenteditable></div></body> 1020 </html>