tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>