tor-browser

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

input-events-get-target-ranges-deleting-range-across-editing-host-boundaries.tentative.html (33244B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8">
      3 <title>InputEvent.getTargetRanges() of deleting  a range across editing host boundaries</title>
      4 <div contenteditable></div>
      5 <script src="input-events-get-target-ranges.js"></script>
      6 <script src="/resources/testharness.js"></script>
      7 <script src="/resources/testharnessreport.js"></script>
      8 <script src="/resources/testdriver.js"></script>
      9 <script src="/resources/testdriver-vendor.js"></script>
     10 <script src="/resources/testdriver-actions.js"></script>
     11 <script>
     12 "use strict";
     13 
     14 // This test just check whether the deleted content range(s) and target ranges of `beforeinput`
     15 // are match or different.  The behavior should be defined by editing API.
     16 // https://github.com/w3c/editing/issues/283
     17 
     18 promise_test(async () => {
     19  initializeTest('<p>ab[c<span contenteditable="false">no]n-editable</span>def</p>');
     20  await sendBackspaceKey();
     21  const kNothingDeletedCase = '<p>abc<span contenteditable="false">non-editable</span>def</p>';
     22  const kOnlyEditableTextDeletedCase = '<p>ab<span contenteditable="false">non-editable</span>def</p>';
     23  const kNonEditableElementDeleteCase = '<p>abdef</p>';
     24  if (gEditor.innerHTML === kNothingDeletedCase) {
     25    if (gBeforeinput.length === 0) {
     26      assert_true(true, "If nothing changed, `beforeinput` event may not be fired");
     27      assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired");
     28      return;
     29    }
     30    assert_equals(gBeforeinput.length, 1,
     31      "If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves");
     32    assert_equals(gBeforeinput[0].cachedRanges.length, 0,
     33      `If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${
     34        getRangeDescription(gBeforeinput[0].cachedRanges[0])
     35      })`);
     36    assert_equals(gBeforeinput[0].inputType, "deleteContentBackward",
     37      "If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward");
     38    assert_equals(gInput.length, 0,
     39      "If nothing changed but `beforeinput` event is fired, `input` event should not be fired");
     40    return;
     41  }
     42  if (gEditor.innerHTML === kOnlyEditableTextDeletedCase) {
     43    assert_equals(gBeforeinput.length, 1,
     44      "If only editable text is deleted, `beforeinput` event should be fired");
     45    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
     46      "If only editable text is deleted, `beforeinput` event should have a target range");
     47    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
     48      getRangeDescription({
     49        startContainer: gEditor.firstChild.firstChild,
     50        startOffset: 2,
     51        endContainer: gEditor.firstChild.firstChild,
     52        endOffset: 3,
     53      }),
     54      "If only editable text is deleted, its target range should be the deleted text range");
     55    assert_equals(gBeforeinput[0].inputType, "deleteContent",
     56      "If only editable text is deleted, its input type should be deleteContent");
     57    assert_equals(gInput.length, 1,
     58      "If only editable text is deleted, `input` event should be fired");
     59    return;
     60  }
     61  if (gEditor.innerHTML === kNonEditableElementDeleteCase) {
     62    assert_equals(gBeforeinput.length, 1,
     63      "If editable text and non-editable element are deleted, `beforeinput` event should be fired");
     64    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
     65      "If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
     66    assert_in_array(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
     67      [
     68        getRangeDescription({
     69          startContainer: gEditor.firstChild.firstChild,
     70          startOffset: 2,
     71          endContainer: gEditor.firstChild,
     72          endOffset: 2,
     73        }),
     74        getRangeDescription({
     75          startContainer: gEditor.firstChild.firstChild,
     76          startOffset: 2,
     77          endContainer: gEditor.firstChild.firstChild.nextSibling,
     78          endOffset: 0,
     79        }),
     80      ],
     81      "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
     82    assert_equals(gBeforeinput[0].inputType, "deleteContent",
     83      "If editable text and non-editable element are deleted, its input type should be deleteContent");
     84    assert_equals(gInput.length, 1,
     85      "If editable text and non-editable element are deleted, `input` event should be fired");
     86    return;
     87  }
     88  assert_in_array(gEditor.innerHTML,
     89    [
     90      kNothingDeletedCase,
     91      kOnlyEditableTextDeletedCase,
     92      kNonEditableElementDeleteCase,
     93    ], "The result content is unexpected");
     94 }, 'Backspace at "<p>ab[c<span contenteditable="false">no]n-editable</span>def</p>"');
     95 
     96 promise_test(async () => {
     97  initializeTest('<p>abc<span contenteditable="false">non-[editable</span>de]f</p>');
     98  await sendBackspaceKey();
     99  const kNothingDeletedCase = '<p>abc<span contenteditable="false">non-editable</span>def</p>';
    100  const kOnlyEditableTextDeletedCase = '<p>abc<span contenteditable="false">non-editable</span>f</p>';
    101  const kNonEditableElementDeletedCase = '<p>abcf</p>';;
    102  if (gEditor.innerHTML === kNothingDeletedCase) {
    103    if (gBeforeinput.length === 0) {
    104      assert_true(true, "If nothing changed, `beforeinput` event may not be fired");
    105      assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired");
    106      return;
    107    }
    108    assert_equals(gBeforeinput.length, 1,
    109      "If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves");
    110    assert_equals(gBeforeinput[0].cachedRanges.length, 0,
    111      `If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${
    112        getRangeDescription(gBeforeinput[0].cachedRanges[0])
    113      })`);
    114    assert_equals(gBeforeinput[0].inputType, "deleteContentBackward",
    115      "If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward");
    116    assert_equals(gInput.length, 0,
    117      "If nothing changed but `beforeinput` event is fired, `input` event should not be fired");
    118    return;
    119  }
    120  if (gEditor.innerHTML === kOnlyEditableTextDeletedCase) {
    121    assert_equals(gBeforeinput.length, 1,
    122      "If only editable text is deleted, `beforeinput` event should be fired");
    123    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    124      "If only editable text is deleted, `beforeinput` event should have a target range");
    125    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    126      getRangeDescription({
    127        startContainer: gEditor.firstChild.lastChild,
    128        startOffset: 0,
    129        endContainer: gEditor.firstChild.lastChild,
    130        endOffset: 2,
    131      }),
    132      "If only editable text is deleted, its target range should be the deleted text range");
    133    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    134      "If only editable text is deleted, its input type should be deleteContent");
    135    assert_equals(gInput.length, 1,
    136      "If only editable text is deleted, `input` event should be fired");
    137    return;
    138  }
    139  if (gEditor.innerHTML === kNonEditableElementDeletedCase) {
    140    assert_equals(gBeforeinput.length, 1,
    141      "If editable text and non-editable element are deleted, `beforeinput` event should be fired");
    142    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    143      "If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
    144    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    145      getRangeDescription({
    146        startContainer: gEditor.firstChild,
    147        startOffset: 1,
    148        endContainer: gEditor.firstChild.lastChild,
    149        endOffset: 2,
    150      }),
    151      "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
    152    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    153      "If editable text and non-editable element are deleted, its input type should be deleteContent");
    154    assert_equals(gInput.length, 1,
    155      "If editable text and non-editable element are deleted, `input` event should be fired");
    156    return;
    157  }
    158  assert_in_array(gEditor.innerHTML,
    159    [
    160      kNothingDeletedCase,
    161      kOnlyEditableTextDeletedCase,
    162      kNonEditableElementDeletedCase,
    163    ], "The result content is unexpected");
    164 }, 'Backspace at "<p>abc<span contenteditable="false">non-[editable</span>de]f</p>"');
    165 
    166 
    167 promise_test(async () => {
    168  initializeTest('<p contenteditable="false"><span contenteditable>a[bc</span>non-editable<span contenteditable>de]f</span></p>');
    169  let firstRange = gSelection.getRangeAt(0);
    170  if (!firstRange ||
    171      firstRange.startContainer != gEditor.firstChild.firstChild.firstChild ||
    172      firstRange.startOffset != 1 ||
    173      firstRange.endContainer != gEditor.firstChild.lastChild.firstChild ||
    174      firstRange.endOffset != 2) {
    175    assert_true(true, "Selection couldn't set across editing host boundaries");
    176    return;
    177  }
    178  await sendBackspaceKey();
    179  const kNothingDeletedCase = '<p contenteditable="false"><span contenteditable="">abc</span>non-editable<span contenteditable="">def</span></p>';
    180  const kOnlyEditableContentDeletedCase = '<p contenteditable="false"><span contenteditable="">a</span>non-editable<span contenteditable="">f</span></p>';
    181  const kNonEditableElementDeletedCase = '<p contenteditable="false"><span contenteditable="">af</span></p>';
    182  const kDeleteEditableContentBeforeNonEditableContentCase = '<p contenteditable="false"><span contenteditable="">a</span>non-editable<span contenteditable="">def</span></p>';
    183  const kDeleteEditableContentAfterNonEditableContentCase = '<p contenteditable="false"><span contenteditable="">abc</span>non-editable<span contenteditable="">f</span></p>';
    184  if (gEditor.innerHTML === kNothingDeletedCase) {
    185    if (gBeforeinput.length === 0) {
    186      assert_true(true, "If nothing changed, `beforeinput` event may not be fired");
    187      assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired");
    188      return;
    189    }
    190    assert_equals(gBeforeinput.length, 1,
    191      "If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves");
    192    assert_equals(gBeforeinput[0].cachedRanges.length, 0,
    193      `If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${
    194        getRangeDescription(gBeforeinput[0].cachedRanges[0])
    195      })`);
    196    assert_equals(gBeforeinput[0].inputType, "deleteContentBackward",
    197      "If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward");
    198    assert_equals(gInput.length, 0,
    199      "If nothing changed but `beforeinput` event is fired, `input` event should not be fired");
    200    return;
    201  }
    202  if (gEditor.innerHTML === kOnlyEditableContentDeletedCase) {
    203    assert_equals(gBeforeinput.length, 1,
    204      "If only editable text is deleted, `beforeinput` event should be fired");
    205    assert_equals(gBeforeinput[0].cachedRanges.length, 2,
    206      "If only editable text is deleted, `beforeinput` event should have 2 target ranges");
    207    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    208      getRangeDescription({
    209        startContainer: gEditor.firstChild.firstChild.firstChild,
    210        startOffset: 1,
    211        endContainer: gEditor.lastChild,
    212        endOffset: 3,
    213      }),
    214      "If only editable text is deleted, its first target range should be the deleted text range in the first text node");
    215    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[1]),
    216      getRangeDescription({
    217        startContainer: gEditor.firstChild.last.firstChild,
    218        startOffset: 0,
    219        endContainer: gEditor.firstChild.last.firstChild,
    220        endOffset: 2,
    221      }),
    222      "If only editable text is deleted, its second target range should be the deleted text range in the last text node");
    223    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    224      "If only editable text is deleted, its input type should be deleteContent");
    225    assert_equals(gInput.length, 1,
    226      "If only editable text is deleted, `input` event should be fired");
    227    return;
    228  }
    229  if (gEditor.innerHTML === kNonEditableElementDeletedCase) {
    230    assert_equals(gBeforeinput.length, 1,
    231      "If editable text and non-editable element are deleted, `beforeinput` event should be fired");
    232    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    233      "If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
    234    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    235      getRangeDescription({
    236        startContainer: gEditor.firstChild.firstChild.firstChild,
    237        startOffset: 1,
    238        endContainer: gEditor.firstChild.lastChild.firstChild,
    239        endOffset: 2,
    240      }),
    241      "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
    242    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    243      "If editable text and non-editable element are deleted, its input type should be deleteContent");
    244    assert_equals(gInput.length, 1,
    245      "If editable text and non-editable element are deleted, `input` event should be fired");
    246    return;
    247  }
    248  if (gEditor.innerHTML === kDeleteEditableContentBeforeNonEditableContentCase) {
    249    assert_equals(gBeforeinput.length, 1,
    250      "If editable text before non-editable element is deleted, `beforeinput` event should be fired");
    251    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    252      "If editable text before non-editable element is deleted, `beforeinput` event should have a target range");
    253    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    254      getRangeDescription({
    255        startContainer: gEditor.firstChild.firstChild.firstChild,
    256        startOffset: 1,
    257        endContainer: gEditor.firstChild.firstChild.firstChild,
    258        endOffset: 3,
    259      }),
    260      "If editable text before non-editable element is deleted, its target range should be only the deleted text");
    261    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    262      "If editable text before non-editable element is deleted, its input type should be deleteContent");
    263    assert_equals(gInput.length, 1,
    264      "If editable text before non-editable element is deleted, `input` event should be fired");
    265    return;
    266  }
    267  if (gEditor.innerHTML === kDeleteEditableContentAfterNonEditableContentCase) {
    268    assert_equals(gBeforeinput.length, 1,
    269      "If editable text after non-editable element is deleted, `beforeinput` event should be fired");
    270    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    271      "If editable text after non-editable element is deleted, `beforeinput` event should have a target range");
    272    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    273      getRangeDescription({
    274        startContainer: gEditor.firstChild.lastChild.firstChild,
    275        startOffset: 1,
    276        endContainer: gEditor.firstChild.lastChild.firstChild,
    277        endOffset: 3,
    278      }),
    279      "If editable text after non-editable element is deleted, its target range should be only the deleted text");
    280    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    281      "If editable text after non-editable element is deleted, its input type should be deleteContent");
    282    assert_equals(gInput.length, 1,
    283      "If editable text after non-editable element is deleted, `input` event should be fired");
    284    return;
    285  }
    286  assert_in_array(gEditor.innerHTML,
    287    [
    288      kNothingDeletedCase,
    289      kOnlyEditableContentDeletedCase,
    290      kNonEditableElementDeletedCase,
    291      kDeleteEditableContentBeforeNonEditableContentCase,
    292      kDeleteEditableContentAfterNonEditableContentCase,
    293    ], "The result content is unexpected");
    294 }, 'Backspace at "<p contenteditable="false"><span contenteditable>a[bc</span>non-editable<span contenteditable>de]f</span></p>"');
    295 
    296 promise_test(async () => {
    297  initializeTest('<p>a[bc<span contenteditable="false">non-editable<span contenteditable>de]f</span></span></p>');
    298  let firstRange = gSelection.getRangeAt(0);
    299  if (!firstRange ||
    300      firstRange.startContainer != gEditor.firstChild.firstChild ||
    301      firstRange.startOffset != 1 ||
    302      firstRange.endContainer != gEditor.querySelector("span span").firstChild ||
    303      firstRange.endOffset != 2) {
    304    assert_true(true, "Selection couldn't set across editing host boundaries");
    305    return;
    306  }
    307  await sendBackspaceKey();
    308  const kNothingDeletedCase = '<p>abc<span contenteditable="false">non-editable<span contenteditable="">def</span></span></p>';
    309  const kOnlyEditableContentDeletedCase = '<p>a<span contenteditable="false">non-editable<span contenteditable="">f</span></span></p>';
    310  const kNonEditableElementDeletedCase1 = '<p>af</p>';
    311  const kNonEditableElementDeletedCase2 = '<p>a<span contenteditable="">f</span></p>';
    312  const kDeleteEditableContentBeforeNonEditableContentCase ='<p>a<span contenteditable="false">non-editable<span contenteditable="">def</span></span></p>';
    313  const kDeleteEditableContentAfterNonEditableContentCase ='<p>abc<span contenteditable="false">non-editable<span contenteditable="">f</span></span></p>';
    314  if (gEditor.innerHTML === kNothingDeletedCase) {
    315    if (gBeforeinput.length === 0) {
    316      assert_true(true, "If nothing changed, `beforeinput` event may not be fired");
    317      assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired");
    318      return;
    319    }
    320    assert_equals(gBeforeinput.length, 1,
    321      "If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves");
    322    assert_equals(gBeforeinput[0].cachedRanges.length, 0,
    323      `If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${
    324        getRangeDescription(gBeforeinput[0].cachedRanges[0])
    325      })`);
    326    assert_equals(gBeforeinput[0].inputType, "deleteContentBackward",
    327      "If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward");
    328    assert_equals(gInput.length, 0,
    329      "If nothing changed but `beforeinput` event is fired, `input` event should not be fired");
    330    return;
    331  }
    332  if (gEditor.innerHTML === kOnlyEditableContentDeletedCase) {
    333    assert_equals(gBeforeinput.length, 1,
    334      "If only editable text is deleted, `beforeinput` event should be fired");
    335    assert_equals(gBeforeinput[0].cachedRanges.length, 2,
    336      "If only editable text is deleted, `beforeinput` event should have 2 target ranges");
    337    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    338      getRangeDescription({
    339        startContainer: gEditor.firstChild.firstChild,
    340        startOffset: 1,
    341        endContainer: gEditor.firstChild.firstChild,
    342        endOffset: 3,
    343      }),
    344      "If only editable text is deleted, its first target range should be the deleted text range in the first text node");
    345    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[1]),
    346      getRangeDescription({
    347        startContainer: gEditor.querySelector("span span").firstChild,
    348        startOffset: 0,
    349        endContainer: gEditor.querySelector("span span").firstChild,
    350        endOffset: 2,
    351      }),
    352      "If only editable text is deleted, its second target range should be the deleted text range in the last text node");
    353    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    354      "If only editable text is deleted, its input type should be deleteContent");
    355    assert_equals(gInput.length, 1,
    356      "If only editable text is deleted, `input` event should be fired");
    357    return;
    358  }
    359  if (gEditor.innerHTML === kNonEditableElementDeletedCase1) {
    360    assert_equals(gBeforeinput.length, 1,
    361      "If editable text and non-editable element are deleted, `beforeinput` event should be fired");
    362    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    363      "If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
    364    // XXX If the text nodes are merged, we need to cache it for here.
    365    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    366      getRangeDescription({
    367        startContainer: gEditor.firstChild.firstChild,
    368        startOffset: 1,
    369        endContainer: gEditor.firstChild.lastChild,
    370        endOffset: 2,
    371      }),
    372      "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
    373    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    374      "If editable text and non-editable element are deleted, its input type should be deleteContent");
    375    assert_equals(gInput.length, 1,
    376      "If editable text and non-editable element are deleted, `input` event should be fired");
    377    return;
    378  }
    379  if (gEditor.innerHTML === kNonEditableElementDeletedCase2) {
    380    assert_equals(gBeforeinput.length, 1,
    381      "If editable text and non-editable element are deleted, `beforeinput` event should be fired");
    382    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    383      "If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
    384    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    385      getRangeDescription({
    386        startContainer: gEditor.firstChild,
    387        startOffset: 1,
    388        endContainer: gEditor.querySelector("span").firstChild,
    389        endOffset: 2,
    390      }),
    391      "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
    392    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    393      "If editable text and non-editable element are deleted, its input type should be deleteContent");
    394    assert_equals(gInput.length, 1,
    395      "If editable text and non-editable element are deleted, `input` event should be fired");
    396    return;
    397  }
    398  if (gEditor.innerHTML === kDeleteEditableContentBeforeNonEditableContentCase) {
    399    assert_equals(gBeforeinput.length, 1,
    400      "If editable text before non-editable element is deleted, `beforeinput` event should be fired");
    401    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    402      "If editable text before non-editable element is deleted, `beforeinput` event should have a target range");
    403    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    404      getRangeDescription({
    405        startContainer: gEditor.firstChild.firstChild,
    406        startOffset: 1,
    407        endContainer: gEditor.firstChild.firstChild,
    408        endOffset: 3,
    409      }),
    410      "If editable text before non-editable element is deleted, its target range should be only the deleted text");
    411    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    412      "If editable text before non-editable element is deleted, its input type should be deleteContent");
    413    assert_equals(gInput.length, 1,
    414      "If editable text before non-editable element is deleted, `input` event should be fired");
    415    return;
    416  }
    417  if (gEditor.innerHTML === kDeleteEditableContentAfterNonEditableContentCase) {
    418    assert_equals(gBeforeinput.length, 1,
    419      "If editable text after non-editable element is deleted, `beforeinput` event should be fired");
    420    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    421      "If editable text after non-editable element is deleted, `beforeinput` event should have a target range");
    422    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    423      getRangeDescription({
    424        startContainer: gEditor.querySelector("span").firstChild,
    425        startOffset: 0,
    426        endContainer: gEditor.querySelector("span").firstChild,
    427        endOffset: 2,
    428      }),
    429      "If editable text after non-editable element is deleted, its target range should be only the deleted text");
    430    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    431      "If editable text after non-editable element is deleted, its input type should be deleteContent");
    432    assert_equals(gInput.length, 1,
    433      "If editable text after non-editable element is deleted, `input` event should be fired");
    434    return;
    435  }
    436  assert_in_array(gEditor.innerHTML,
    437    [
    438      kNothingDeletedCase,
    439      kOnlyEditableContentDeletedCase,
    440      kNonEditableElementDeletedCase1,
    441      kNonEditableElementDeletedCase2,
    442      kDeleteEditableContentBeforeNonEditableContentCase,
    443      kDeleteEditableContentAfterNonEditableContentCase,
    444    ], "The result content is unexpected");
    445 }, 'Backspace at "<p>a[bc<span contenteditable="false">non-editable<span contenteditable>de]f</span></span></p>"');
    446 
    447 promise_test(async () => {
    448  initializeTest('<p><span contenteditable="false"><span contenteditable>a[bc</span>non-editable</span>de]f</p>');
    449  let firstRange = gSelection.getRangeAt(0);
    450  if (!firstRange ||
    451      firstRange.startContainer != gEditor.querySelector("span span").firstChild ||
    452      firstRange.startOffset != 1 ||
    453      firstRange.endContainer != gEditor.firstChild.lastChild.firstChild ||
    454      firstRange.endOffset != 2) {
    455    assert_true(true, "Selection couldn't set across editing host boundaries");
    456    return;
    457  }
    458  await sendBackspaceKey();
    459  const kNothingDeletedCase = '<p><span contenteditable="false"><span contenteditable="">abc</span>non-editable</span>def</p>';
    460  const kOnlyEditableContentDeletedCase = '<p><span contenteditable="false"><span contenteditable="">a</span>non-editable</span>f</p>';
    461  const kNonEditableElementDeletedCase1 = '<p><span contenteditable="false"><span contenteditable="">af</span></span></p>';
    462  const kNonEditableElementDeletedCase2 = '<p><span contenteditable="false"><span contenteditable="">a</span></span>f</p>';
    463  const kDeleteEditableContentBeforeNonEditableContentCase = '<p><span contenteditable="false"><span contenteditable="">a</span>non-editable</span>def</p>';
    464  const kDeleteEditableContentAfterNonEditableContentCase = '<p><span contenteditable="false"><span contenteditable="">abc</span>non-editable</span>f</p>';
    465  if (gEditor.innerHTML === kNothingDeletedCase) {
    466    if (gBeforeinput.length === 0) {
    467      assert_true(true, "If nothing changed, `beforeinput` event may not be fired");
    468      assert_equals(gInput.length, 0, "If nothing changed, `input` event should not be fired");
    469      return;
    470    }
    471    assert_equals(gBeforeinput.length, 1,
    472      "If nothing changed, `beforeinput` event can be fired for web apps can handle by themselves");
    473    assert_equals(gBeforeinput[0].cachedRanges.length, 0,
    474      `If nothing changed but \`beforeinput\` event is fired, its target range should be empty array (got ${
    475        getRangeDescription(gBeforeinput[0].cachedRanges[0])
    476      })`);
    477    assert_equals(gBeforeinput[0].inputType, "deleteContentBackward",
    478      "If nothing changed but `beforeinput` event is fired, its input type should be deleteContentBackward");
    479    assert_equals(gInput.length, 0,
    480      "If nothing changed but `beforeinput` event is fired, `input` event should not be fired");
    481    return;
    482  }
    483  if (gEditor.innerHTML === kOnlyEditableContentDeletedCase) {
    484    assert_equals(gBeforeinput.length, 1,
    485      "If only editable text is deleted, `beforeinput` event should be fired");
    486    assert_equals(gBeforeinput[0].cachedRanges.length, 2,
    487      "If only editable text is deleted, `beforeinput` event should have 2 target ranges");
    488    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    489      getRangeDescription({
    490        startContainer: gEditor.querySelector("span span").firstChild,
    491        startOffset: 1,
    492        endContainer: gEditor.querySelector("span span").firstChild,
    493        endOffset: 3,
    494      }),
    495      "If only editable text is deleted, its first target range should be the deleted text range in the first text node");
    496    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[1]),
    497      getRangeDescription({
    498        startContainer: gEditor.firstChild.lastChild,
    499        startOffset: 0,
    500        endContainer: gEditor.firstChild.lastChild,
    501        endOffset: 2,
    502      }),
    503      "If only editable text is deleted, its second target range should be the deleted text range in the last text node");
    504    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    505      "If only editable text is deleted, its input type should be deleteContent");
    506    assert_equals(gInput.length, 1,
    507      "If only editable text is deleted, `input` event should be fired");
    508    return;
    509  }
    510  if (gEditor.innerHTML === kNonEditableElementDeletedCase1) {
    511    assert_equals(gBeforeinput.length, 1,
    512      "If editable text and non-editable element are deleted, `beforeinput` event should be fired");
    513    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    514      "If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
    515    // XXX If the text nodes are merged, we need to cache it for here.
    516    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    517      getRangeDescription({
    518        startContainer: gEditor.querySelector("span span").firstChild,
    519        startOffset: 1,
    520        endContainer: gEditor.querySelector("span span").lastChild,
    521        endOffset: 2,
    522      }),
    523      "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
    524    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    525      "If editable text and non-editable element are deleted, its input type should be deleteContent");
    526    assert_equals(gInput.length, 1,
    527      "If editable text and non-editable element are deleted, `input` event should be fired");
    528    return;
    529  }
    530  if (gEditor.innerHTML === kNonEditableElementDeletedCase2) {
    531    assert_equals(gBeforeinput.length, 1,
    532      "If editable text and non-editable element are deleted, `beforeinput` event should be fired");
    533    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    534      "If editable text and non-editable element are deleted, `beforeinput` event should have a target range");
    535    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    536      getRangeDescription({
    537        startContainer: gEditor.querySelector("span span").firstChild,
    538        startOffset: 1,
    539        endContainer: gEditor.firstChild.lastChild,
    540        endOffset: 2,
    541      }),
    542      "If editable text and non-editable element are deleted, its target range should include the deleted non-editable element");
    543    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    544      "If editable text and non-editable element are deleted, its input type should be deleteContent");
    545    assert_equals(gInput.length, 1,
    546      "If editable text and non-editable element are deleted, `input` event should be fired");
    547    return;
    548  }
    549  if (gEditor.innerHTML === kDeleteEditableContentBeforeNonEditableContentCase) {
    550    assert_equals(gBeforeinput.length, 1,
    551      "If editable text before non-editable element is deleted, `beforeinput` event should be fired");
    552    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    553      "If editable text before non-editable element is deleted, `beforeinput` event should have a target range");
    554    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    555      getRangeDescription({
    556        startContainer: gEditor.querySelector("span span").firstChild,
    557        startOffset: 1,
    558        endContainer: gEditor.querySelector("span span").firstChild,
    559        endOffset: 3,
    560      }),
    561      "If editable text before non-editable element is deleted, its target range should be only the deleted text");
    562    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    563      "If editable text before non-editable element is deleted, its input type should be deleteContent");
    564    assert_equals(gInput.length, 1,
    565      "If editable text before non-editable element is deleted, `input` event should be fired");
    566    return;
    567  }
    568  if (gEditor.innerHTML === kDeleteEditableContentAfterNonEditableContentCase) {
    569    assert_equals(gBeforeinput.length, 1,
    570      "If editable text after non-editable element is deleted, `beforeinput` event should be fired");
    571    assert_equals(gBeforeinput[0].cachedRanges.length, 1,
    572      "If editable text after non-editable element is deleted, `beforeinput` event should have a target range");
    573    assert_equals(getRangeDescription(gBeforeinput[0].cachedRanges[0]),
    574      getRangeDescription({
    575        startContainer: gEditor.firstChild.lastChild,
    576        startOffset: 0,
    577        endContainer: gEditor.firstChild.lastChild,
    578        endOffset: 2,
    579      }),
    580      "If editable text after non-editable element is deleted, its target range should be only the deleted text");
    581    assert_equals(gBeforeinput[0].inputType, "deleteContent",
    582      "If editable text after non-editable element is deleted, its input type should be deleteContent");
    583    assert_equals(gInput.length, 1,
    584      "If editable text after non-editable element is deleted, `input` event should be fired");
    585    return;
    586  }
    587  assert_in_array(gEditor.innerHTML,
    588    [
    589      kNothingDeletedCase,
    590      kOnlyEditableContentDeletedCase,
    591      kNonEditableElementDeletedCase1,
    592      kNonEditableElementDeletedCase2,
    593      kDeleteEditableContentBeforeNonEditableContentCase,
    594      kDeleteEditableContentAfterNonEditableContentCase,
    595    ], "The result content is unexpected");
    596 }, 'Backspace at "<p><span contenteditable="false"><span contenteditable>a[bc</span>non-editable</span>de]f</p>"');
    597 
    598 </script>