tor-browser

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

test_dragdrop.html (185523B)


      1 <!doctype html>
      2 <html>
      3 
      4 <head>
      5  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
      6 
      7  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      8  <script src="/tests/SimpleTest/EventUtils.js"></script>
      9 </head>
     10 
     11 <body>
     12  <div id="dropZone"
     13       ondragenter="event.dataTransfer.dropEffect = 'copy'; event.preventDefault();"
     14       ondragover="event.dataTransfer.dropEffect = 'copy'; event.preventDefault();"
     15       ondrop="event.preventDefault();"
     16       style="height: 4px; background-color: lemonchiffon;"></div>
     17  <div id="container"></div>
     18 
     19 <script type="application/javascript">
     20 
     21 const { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
     22  "resource://gre/modules/AppConstants.sys.mjs"
     23 );
     24 
     25 SimpleTest.waitForExplicitFinish();
     26 
     27 function checkInputEvent(aEvent, aExpectedTarget, aInputType, aData, aDataTransfer, aTargetRanges, aDescription) {
     28  ok(aEvent instanceof InputEvent, `${aDescription}: "${aEvent.type}" event should be dispatched with InputEvent interface`);
     29  is(aEvent.cancelable, aEvent.type === "beforeinput", `${aDescription}: "${aEvent.type}" event should be ${aEvent.type === "beforeinput" ? "" : "never "}cancelable`);
     30  is(aEvent.bubbles, true, `${aDescription}: "${aEvent.type}" event should always bubble`);
     31  is(aEvent.target, aExpectedTarget, `${aDescription}: "${aEvent.type}" event should be fired on the <${aExpectedTarget.tagName.toLowerCase()}> element`);
     32  is(aEvent.inputType, aInputType, `${aDescription}: inputType of "${aEvent.type}" event should be "${aInputType}" on the <${aExpectedTarget.tagName.toLowerCase()}> element`);
     33  is(aEvent.data, aData, `${aDescription}: data of "${aEvent.type}" event should be ${aData} on the <${aExpectedTarget.tagName.toLowerCase()}> element`);
     34  if (aDataTransfer === null) {
     35    is(aEvent.dataTransfer, null, `${aDescription}: dataTransfer should be null on the <${aExpectedTarget.tagName.toLowerCase()}> element`);
     36  } else {
     37    for (let dataTransfer of aDataTransfer) {
     38      let description = `${aDescription}: on the <${aExpectedTarget.tagName.toLowerCase()}> element`;
     39      if (dataTransfer.todo) {
     40        // XXX It seems that synthesizeDrop() don't emulate perfectly if caller specifies the data directly.
     41        todo_is(aEvent.dataTransfer.getData(dataTransfer.type), dataTransfer.data,
     42                `${description}: dataTransfer of "${aEvent.type}" event should have "${dataTransfer.data}" whose type is "${dataTransfer.type}"`);
     43      } else {
     44        is(aEvent.dataTransfer.getData(dataTransfer.type), dataTransfer.data,
     45           `${description}: dataTransfer of "${aEvent.type}" event should have "${dataTransfer.data}" whose type is "${dataTransfer.type}"`);
     46      }
     47    }
     48  }
     49  let targetRanges = aEvent.getTargetRanges();
     50  if (aTargetRanges.length === 0) {
     51    is(targetRanges.length, 0,
     52       `${aDescription}: getTargetRange() of "${aEvent.type}" event should return empty array`);
     53  } else {
     54    is(targetRanges.length, aTargetRanges.length,
     55       `${aDescription}: getTargetRange() of "${aEvent.type}" event should return static range array`);
     56    if (targetRanges.length == aTargetRanges.length) {
     57      for (let i = 0; i < targetRanges.length; i++) {
     58        is(targetRanges[i].startContainer, aTargetRanges[i].startContainer,
     59           `${aDescription}: startContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match`);
     60        is(targetRanges[i].startOffset, aTargetRanges[i].startOffset,
     61           `${aDescription}: startOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match`);
     62        is(targetRanges[i].endContainer, aTargetRanges[i].endContainer,
     63           `${aDescription}: endContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match`);
     64        is(targetRanges[i].endOffset, aTargetRanges[i].endOffset,
     65           `${aDescription}: endOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match`);
     66      }
     67    }
     68  }
     69 }
     70 
     71 // eslint-disable-next-line complexity
     72 async function doTest() {
     73  const container = document.getElementById("container");
     74  const dropZone = document.getElementById("dropZone");
     75 
     76  let beforeinputEvents = [];
     77  let inputEvents = [];
     78  let dragEvents = [];
     79  function onBeforeinput(event) {
     80    beforeinputEvents.push(event);
     81  }
     82  function onInput(event) {
     83    inputEvents.push(event);
     84  }
     85  document.addEventListener("beforeinput", onBeforeinput);
     86  document.addEventListener("input", onInput);
     87 
     88  function preventDefaultDeleteByDrag(aEvent) {
     89    if (aEvent.inputType === "deleteByDrag") {
     90      aEvent.preventDefault();
     91    }
     92  }
     93  function preventDefaultInsertFromDrop(aEvent) {
     94    if (aEvent.inputType === "insertFromDrop") {
     95      aEvent.preventDefault();
     96    }
     97  }
     98 
     99  const selection = window.getSelection();
    100 
    101  const kIsMac = navigator.platform.includes("Mac");
    102  const kIsWin = navigator.platform.includes("Win");
    103 
    104  const kNativeLF = kIsWin ? "\r\n" : "\n";
    105 
    106  const kModifiersToCopy = {
    107    ctrlKey: !kIsMac,
    108    altKey: kIsMac,
    109  }
    110 
    111  function comparePlainText(aGot, aExpected, aDescription) {
    112    is(aGot.replace(/\r\n?/g, "\n"), aExpected, aDescription);
    113  }
    114  function compareHTML(aGot, aExpected, aDescription) {
    115    is(aGot.replace(/\r\n?/g, "\n"), aExpected, aDescription);
    116  }
    117 
    118  async function trySynthesizePlainDragAndDrop(aDescription, aOptions) {
    119    try {
    120      await synthesizePlainDragAndDrop(aOptions);
    121      return true;
    122    } catch (e) {
    123      ok(false, `${aDescription}: Failed to emulate drag and drop (${e.message})`);
    124      return false;
    125    }
    126  }
    127 
    128  // -------- Test dragging regular text
    129  await (async function test_dragging_regular_text() {
    130    const description = "dragging part of non-editable <span> element";
    131    container.innerHTML = '<span style="font-size: 24px;">Some Text</span>';
    132    const span = document.querySelector("div#container > span");
    133    selection.setBaseAndExtent(span.firstChild, 4, span.firstChild, 6);
    134    beforeinputEvents = [];
    135    inputEvents = [];
    136    dragEvents = [];
    137    const onDrop = aEvent => {
    138      dragEvents.push(aEvent);
    139      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
    140                      span.textContent.substring(4, 6),
    141                      `${description}: dataTransfer should have selected text as "text/plain"`);
    142      compareHTML(aEvent.dataTransfer.getData("text/html"),
    143                  span.outerHTML.replace(/>.+</, `>${span.textContent.substring(4, 6)}<`),
    144                  `${description}: dataTransfer should have the parent inline element and only selected text as "text/html"`);
    145    };
    146    document.addEventListener("drop", onDrop);
    147    if (
    148      await trySynthesizePlainDragAndDrop(
    149        description,
    150        {
    151          srcSelection: selection,
    152          destElement: dropZone,
    153        }
    154      )
    155    ) {
    156      is(beforeinputEvents.length, 0,
    157        `${description}: No "beforeinput" event should be fired when dragging non-editable selection to non-editable drop zone`);
    158      is(inputEvents.length, 0,
    159        `${description}: No "input" event should be fired when dragging non-editable selection to non-editable drop zone`);
    160      is(dragEvents.length, 1,
    161        `${description}: only one "drop" event should be fired`);
    162    }
    163    document.removeEventListener("drop", onDrop);
    164  })();
    165 
    166  // -------- Test dragging text from an <input>
    167  await (async function test_dragging_text_from_input_element() {
    168    const description = "dragging part of text in <input> element";
    169    container.innerHTML = '<input value="Drag Me">';
    170    const input = document.querySelector("div#container > input");
    171    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    172    input.setSelectionRange(1, 4);
    173    beforeinputEvents = [];
    174    inputEvents = [];
    175    dragEvents = [];
    176    const onDrop = aEvent => {
    177      dragEvents.push(aEvent);
    178      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
    179                      input.value.substring(1, 4),
    180                      `${description}: dataTransfer should have selected text as "text/plain"`);
    181      is(aEvent.dataTransfer.getData("text/html"), "",
    182        `${description}: dataTransfer should not have data as "text/html"`);
    183    };
    184    document.addEventListener("drop", onDrop);
    185    if (
    186      await trySynthesizePlainDragAndDrop(
    187        description,
    188        {
    189          srcSelection: SpecialPowers.wrap(input).editor.selection,
    190          destElement: dropZone,
    191        }
    192      )
    193    ) {
    194      is(beforeinputEvents.length, 0,
    195        `${description}: No "beforeinput" event should be fired when dragging <input> value to non-editable drop zone`);
    196      is(inputEvents.length, 0,
    197        `${description}: No "input" event should be fired when dragging <input> value to non-editable drop zone`);
    198      is(dragEvents.length, 1,
    199        `${description}: only one "drop" event should be fired`);
    200    }
    201    document.removeEventListener("drop", onDrop);
    202  })();
    203 
    204  // -------- Test dragging text from an <textarea>
    205  await (async function test_dragging_text_from_textarea_element() {
    206    const description = "dragging part of text in <textarea> element";
    207    container.innerHTML = "<textarea>Some Text To Drag</textarea>";
    208    const textarea = document.querySelector("div#container > textarea");
    209    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    210    textarea.setSelectionRange(1, 7);
    211    beforeinputEvents = [];
    212    inputEvents = [];
    213    dragEvents = [];
    214    const onDrop = aEvent => {
    215      dragEvents.push(aEvent);
    216      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
    217                      textarea.value.substring(1, 7),
    218                      `${description}: dataTransfer should have selected text as "text/plain"`);
    219      is(aEvent.dataTransfer.getData("text/html"), "",
    220        `${description}: dataTransfer should not have data as "text/html"`);
    221    };
    222    document.addEventListener("drop", onDrop);
    223    if (
    224      await trySynthesizePlainDragAndDrop(
    225        description,
    226        {
    227          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
    228          destElement: dropZone,
    229        }
    230      )
    231    ) {
    232      is(beforeinputEvents.length, 0,
    233        `${description}: No "beforeinput" event should be fired when dragging <textarea> value to non-editable drop zone`);
    234      is(inputEvents.length, 0,
    235        `${description}: No "input" event should be fired when dragging <textarea> value to non-editable drop zone`);
    236      is(dragEvents.length, 1,
    237        `${description}: only one "drop" event should be fired`);
    238    }
    239    document.removeEventListener("drop", onDrop);
    240  })();
    241 
    242  // -------- Test dragging text from a contenteditable
    243  await (async function test_dragging_text_from_contenteditable() {
    244    const description = "dragging part of text in contenteditable element";
    245    container.innerHTML = "<p contenteditable>This is some <b>editable</b> text.</p>";
    246    const b = document.querySelector("div#container > p > b");
    247    selection.setBaseAndExtent(b.firstChild, 2, b.firstChild, 6);
    248    beforeinputEvents = [];
    249    inputEvents = [];
    250    dragEvents = [];
    251    const onDrop = aEvent => {
    252      dragEvents.push(aEvent);
    253      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
    254                      b.textContent.substring(2, 6),
    255                      `${description}: dataTransfer should have selected text as "text/plain"`);
    256      compareHTML(aEvent.dataTransfer.getData("text/html"),
    257                  b.outerHTML.replace(/>.+</, `>${b.textContent.substring(2, 6)}<`),
    258                  `${description}: dataTransfer should have selected nodes as "text/html"`);
    259    };
    260    document.addEventListener("drop", onDrop);
    261    if (
    262      await trySynthesizePlainDragAndDrop(
    263        description,
    264        {
    265          srcSelection: selection,
    266          destElement: dropZone,
    267        }
    268      )
    269    ) {
    270      is(beforeinputEvents.length, 0,
    271        `${description}: No "beforeinput" event should be fired when dragging <textarea> value to non-editable drop zone`);
    272      is(inputEvents.length, 0,
    273        `${description}: No "input" event should be fired when dragging <textarea> value to non-editable drop zone`);
    274      is(dragEvents.length, 1,
    275        `${description}: only one "drop" event should be fired`);
    276    }
    277    document.removeEventListener("drop", onDrop);
    278  })();
    279 
    280 
    281  for (const inputType of ["text", "search"]) {
    282    // -------- Test dragging regular text of text/html to <input>
    283    await (async function test_dragging_text_from_span_element_to_input_element() {
    284      const description = `dragging text in non-editable <span> to <input type=${inputType}>`;
    285      container.innerHTML = `<span>Static</span><input type="${inputType}">`;
    286      const span = document.querySelector("div#container > span");
    287      const input = document.querySelector("div#container > input");
    288      selection.setBaseAndExtent(span.firstChild, 2, span.firstChild, 5);
    289      beforeinputEvents = [];
    290      inputEvents = [];
    291      dragEvents = [];
    292      const onDrop = aEvent => {
    293        dragEvents.push(aEvent);
    294        comparePlainText(aEvent.dataTransfer.getData("text/plain"),
    295                        span.textContent.substring(2, 5),
    296                        `${description}: dataTransfer should have selected text as "text/plain"`);
    297        compareHTML(aEvent.dataTransfer.getData("text/html"),
    298                    span.outerHTML.replace(/>.+</, `>${span.textContent.substring(2, 5)}<`),
    299                    `${description}: dataTransfer should have selected nodes as "text/html"`);
    300      };
    301      document.addEventListener("drop", onDrop);
    302      if (
    303        await trySynthesizePlainDragAndDrop(
    304          description,
    305          {
    306            srcSelection: selection,
    307            destElement: input,
    308          }
    309        )
    310      ) {
    311        is(input.value, span.textContent.substring(2, 5),
    312          `${description}: <input>.value should be modified`);
    313        is(beforeinputEvents.length, 1,
    314          `${description}: one "beforeinput" event should be fired on <input>`);
    315        checkInputEvent(beforeinputEvents[0], input, "insertFromDrop", span.textContent.substring(2, 5), null, [], description);
    316        is(inputEvents.length, 1,
    317          `${description}: one "input" event should be fired on <input>`);
    318        checkInputEvent(inputEvents[0], input, "insertFromDrop", span.textContent.substring(2, 5), null, [], description);
    319        is(dragEvents.length, 1,
    320          `${description}: only one "drop" event should be fired on <input>`);
    321      }
    322      document.removeEventListener("drop", onDrop);
    323    })();
    324 
    325    // -------- Test dragging regular text of text/html to disabled <input>
    326    await (async function test_dragging_text_from_span_element_to_disabled_input_element() {
    327      const description = `dragging text in non-editable <span> to <input disabled type="${inputType}">`;
    328      container.innerHTML = `<span>Static</span><input disabled type="${inputType}">`;
    329      const span = document.querySelector("div#container > span");
    330      const input = document.querySelector("div#container > input");
    331      selection.setBaseAndExtent(span.firstChild, 2, span.firstChild, 5);
    332      beforeinputEvents = [];
    333      inputEvents = [];
    334      dragEvents = [];
    335      const onDrop = aEvent => {
    336        dragEvents.push(aEvent);
    337      };
    338      document.addEventListener("drop", onDrop);
    339      if (
    340        await trySynthesizePlainDragAndDrop(
    341          description,
    342          {
    343            srcSelection: selection,
    344            destElement: input,
    345          }
    346        )
    347      ) {
    348        is(input.value, "",
    349          `${description}: <input disable>.value should not be modified`);
    350        is(beforeinputEvents.length, 0,
    351          `${description}: no "beforeinput" event should be fired on <input disabled>`);
    352        is(inputEvents.length, 0,
    353          `${description}: no "input" event should be fired on <input disabled>`);
    354        is(dragEvents.length, 0,
    355          `${description}: no "drop" event should be fired on <input disabled>`);
    356      }
    357      document.removeEventListener("drop", onDrop);
    358    })();
    359 
    360    // -------- Test dragging regular text of text/html to readonly <input>
    361    await (async function test_dragging_text_from_span_element_to_readonly_input_element() {
    362      const description = `dragging text in non-editable <span> to <input readonly type="${inputType}">`;
    363      container.innerHTML = `<span>Static</span><input readonly type="${inputType}">`;
    364      const span = document.querySelector("div#container > span");
    365      const input = document.querySelector("div#container > input");
    366      selection.setBaseAndExtent(span.firstChild, 2, span.firstChild, 5);
    367      beforeinputEvents = [];
    368      inputEvents = [];
    369      dragEvents = [];
    370      const onDrop = aEvent => {
    371        dragEvents.push(aEvent);
    372        comparePlainText(aEvent.dataTransfer.getData("text/plain"),
    373                        span.textContent.substring(2, 5),
    374                        `${description}: dataTransfer should have selected text as "text/plain"`);
    375        compareHTML(aEvent.dataTransfer.getData("text/html"),
    376                    span.outerHTML.replace(/>.+</, `>${span.textContent.substring(2, 5)}<`),
    377                    `${description}: dataTransfer should have selected nodes as "text/html"`);
    378      };
    379      document.addEventListener("drop", onDrop);
    380      if (
    381        await trySynthesizePlainDragAndDrop(
    382          description,
    383          {
    384            srcSelection: selection,
    385            destElement: input,
    386          }
    387        )
    388      ) {
    389        is(input.value, "",
    390          `${description}: <input readonly>.value should not be modified`);
    391        is(beforeinputEvents.length, 0,
    392          `${description}: no "beforeinput" event should be fired on <input readonly>`);
    393        is(inputEvents.length, 0,
    394          `${description}: no "input" event should be fired on <input readonly>`);
    395        is(dragEvents.length, 0,
    396          `${description}: no "drop" event should be fired on <input readonly>`);
    397      }
    398      document.removeEventListener("drop", onDrop);
    399    })();
    400 
    401    // -------- Test dragging only text/html data (like from another app) to <input>.
    402    await (async function test_dragging_only_html_text_to_input_element() {
    403      const description = `dragging only text/html data to <input type="${inputType}>`;
    404      container.innerHTML = `<span>Static</span><input type="${inputType}">`;
    405      const span = document.querySelector("div#container > span");
    406      const input = document.querySelector("div#container > input");
    407      selection.selectAllChildren(span);
    408      beforeinputEvents = [];
    409      inputEvents = [];
    410      const onDragStart = aEvent => {
    411        // Clear all dataTransfer data first.  Then, it'll be filled only with
    412        // the text/html data passed to synthesizeDrop().
    413        aEvent.dataTransfer.clearData();
    414      };
    415      window.addEventListener("dragstart", onDragStart, {capture: true});
    416      synthesizeDrop(span, input, [[{type: "text/html", data: "Some <b>Bold<b> Text"}]], "copy");
    417      is(beforeinputEvents.length, 0,
    418        `${description}: no "beforeinput" event should be fired on <input>`);
    419      is(inputEvents.length, 0,
    420        `${description}: no "input" event should be fired on <input>`);
    421      window.removeEventListener("dragstart", onDragStart, {capture: true});
    422    })();
    423 
    424    // -------- Test dragging both text/plain and text/html data (like from another app) to <input>.
    425    await (async function test_dragging_both_html_text_and_plain_text_to_input_element() {
    426      const description = `dragging both text/plain and text/html data to <input type=${inputType}>`;
    427      container.innerHTML = `<span>Static</span><input type="${inputType}">`;
    428      const span = document.querySelector("div#container > span");
    429      const input = document.querySelector("div#container > input");
    430      selection.selectAllChildren(span);
    431      beforeinputEvents = [];
    432      inputEvents = [];
    433      const onDragStart = aEvent => {
    434        // Clear all dataTransfer data first.  Then, it'll be filled only with
    435        // the text/plain data and text/html data passed to synthesizeDrop().
    436        aEvent.dataTransfer.clearData();
    437      };
    438      window.addEventListener("dragstart", onDragStart, {capture: true});
    439      synthesizeDrop(span, input, [[{type: "text/html", data: "Some <b>Bold<b> Text"},
    440                                    {type: "text/plain", data: "Some Plain Text"}]], "copy");
    441      is(input.value, "Some Plain Text",
    442        `${description}: The text/plain data should be inserted`);
    443      is(beforeinputEvents.length, 1,
    444        `${description}: only one "beforeinput" events should be fired on <input> element`);
    445      checkInputEvent(beforeinputEvents[0], input, "insertFromDrop", "Some Plain Text", null, [],
    446                      description);
    447      is(inputEvents.length, 1,
    448        `${description}: only one "input" events should be fired on <input> element`);
    449      checkInputEvent(inputEvents[0], input, "insertFromDrop", "Some Plain Text", null, [],
    450                      description);
    451      window.removeEventListener("dragstart", onDragStart, {capture: true});
    452    })();
    453 
    454    // -------- Test dragging special text type from another app to <input>
    455    await (async function test_dragging_only_moz_text_internal_to_input_element() {
    456      const description = `dragging both text/x-moz-text-internal data to <input type="${inputType}">`;
    457      container.innerHTML = `<span>Static</span><input type="${inputType}">`;
    458      const span = document.querySelector("div#container > span");
    459      const input = document.querySelector("div#container > input");
    460      selection.selectAllChildren(span);
    461      beforeinputEvents = [];
    462      inputEvents = [];
    463      const onDragStart = aEvent => {
    464        // Clear all dataTransfer data first.  Then, it'll be filled only with
    465        // the text/x-moz-text-internal data passed to synthesizeDrop().
    466        aEvent.dataTransfer.clearData();
    467      };
    468      window.addEventListener("dragstart", onDragStart, {capture: true});
    469      synthesizeDrop(span, input, [[{type: "text/x-moz-text-internal", data: "Some Special Text"}]], "copy");
    470      is(input.value, "",
    471        `${description}: <input>.value should not be modified with "text/x-moz-text-internal" data`);
    472      // Note that even if editor does not handle given dataTransfer, web apps
    473      // may handle it by itself.  Therefore, editor should dispatch "beforeinput"
    474      // event.
    475      is(beforeinputEvents.length, 1,
    476        `${description}: one "beforeinput" event should be fired when dropping "text/x-moz-text-internal" data into <input> element`);
    477      // But unfortunately, on <input> and <textarea>, dataTransfer won't be set...
    478      checkInputEvent(beforeinputEvents[0], input, "insertFromDrop", "", null, [], description);
    479      is(inputEvents.length, 0,
    480        `${description}: no "input" event should be fired when dropping "text/x-moz-text-internal" data into <input> element`);
    481      window.removeEventListener("dragstart", onDragStart, {capture: true});
    482    })();
    483 
    484    // -------- Test dragging contenteditable to <input>
    485    await (async function test_dragging_from_contenteditable_to_input_element() {
    486      const description = `dragging text in contenteditable to <input type="${inputType}">`;
    487      container.innerHTML = `<div contenteditable>Some <b>bold</b> text</div><input type="${inputType}">`;
    488      const contenteditable = document.querySelector("div#container > div");
    489      const input = document.querySelector("div#container > input");
    490      const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
    491      selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
    492      beforeinputEvents = [];
    493      inputEvents = [];
    494      dragEvents = [];
    495      const onDrop = aEvent => {
    496        dragEvents.push(aEvent);
    497        is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
    498          `${description}: dataTransfer should have selected text as "text/plain"`);
    499        is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
    500          `${description}: dataTransfer should have selected nodes as "text/html"`);
    501      };
    502      document.addEventListener("drop", onDrop);
    503      if (
    504        await trySynthesizePlainDragAndDrop(
    505          description,
    506          {
    507            srcSelection: selection,
    508            destElement: input,
    509          }
    510        )
    511      ) {
    512        is(contenteditable.innerHTML, "Soext",
    513          `${description}: Dragged range should be removed from contenteditable`);
    514        is(input.value, "me bold t",
    515          `${description}: <input>.value should be modified`);
    516        is(beforeinputEvents.length, 2,
    517                `${description}: 2 "beforeinput" events should be fired on contenteditable and <input>`);
    518        checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
    519                        [{startContainer: selectionContainers[0], startOffset: 2,
    520                          endContainer: selectionContainers[1], endOffset: 2}],
    521                        description);
    522        checkInputEvent(beforeinputEvents[1], input, "insertFromDrop", "me bold t", null, [], description);
    523        is(inputEvents.length, 2,
    524                `${description}: 2 "input" events should be fired on contenteditable and <input>`);
    525        checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
    526        checkInputEvent(inputEvents[1], input, "insertFromDrop", "me bold t", null, [], description);
    527        is(dragEvents.length, 1,
    528          `${description}: only one "drop" event should be fired on <textarea>`);
    529      }
    530      document.removeEventListener("drop", onDrop);
    531    })();
    532 
    533    // -------- Test dragging contenteditable to <input> (canceling "deleteByDrag")
    534    await (async function test_dragging_from_contenteditable_to_input_element_and_canceling_delete_by_drag() {
    535      const description = `dragging text in contenteditable to <input type="${inputType}"> (canceling "deleteByDrag")`;
    536      container.innerHTML = `<div contenteditable>Some <b>bold</b> text</div><input type="${inputType}">`;
    537      const contenteditable = document.querySelector("div#container > div");
    538      const input = document.querySelector("div#container > input");
    539      const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
    540      selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
    541      beforeinputEvents = [];
    542      inputEvents = [];
    543      dragEvents = [];
    544      const onDrop = aEvent => {
    545        dragEvents.push(aEvent);
    546        is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
    547          `${description}: dataTransfer should have selected text as "text/plain"`);
    548        is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
    549          `${description}: dataTransfer should have selected nodes as "text/html"`);
    550      };
    551      document.addEventListener("drop", onDrop);
    552      document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    553      if (
    554        await trySynthesizePlainDragAndDrop(
    555          description,
    556          {
    557            srcSelection: selection,
    558            destElement: input,
    559          }
    560        )
    561      ) {
    562        is(contenteditable.innerHTML, "Some <b>bold</b> text",
    563          `${description}: Dragged range shouldn't be removed from contenteditable`);
    564        is(input.value, "me bold t",
    565          `${description}: <input>.value should be modified`);
    566        is(beforeinputEvents.length, 2,
    567                `${description}: 2 "beforeinput" events should be fired on contenteditable and <input>`);
    568        checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
    569                        [{startContainer: selectionContainers[0], startOffset: 2,
    570                          endContainer: selectionContainers[1], endOffset: 2}],
    571                        description);
    572        checkInputEvent(beforeinputEvents[1], input, "insertFromDrop", "me bold t", null, [], description);
    573        is(inputEvents.length, 1,
    574                `${description}: only one "input" event should be fired on <input>`);
    575        checkInputEvent(inputEvents[0], input, "insertFromDrop", "me bold t", null, [], description);
    576        is(dragEvents.length, 1,
    577          `${description}: only one "drop" event should be fired on <input>`);
    578      }
    579      document.removeEventListener("drop", onDrop);
    580      document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
    581    })();
    582 
    583    // -------- Test dragging contenteditable to <input> (canceling "insertFromDrop")
    584    await (async function test_dragging_from_contenteditable_to_input_element_and_canceling_insert_from_drop() {
    585      const description = `dragging text in contenteditable to <input type="${inputType}"> (canceling "insertFromDrop")`;
    586      container.innerHTML = "<div contenteditable>Some <b>bold</b> text</div><input>";
    587      const contenteditable = document.querySelector("div#container > div");
    588      const input = document.querySelector("div#container > input");
    589      const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
    590      selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
    591      beforeinputEvents = [];
    592      inputEvents = [];
    593      dragEvents = [];
    594      const onDrop = aEvent => {
    595        dragEvents.push(aEvent);
    596        is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
    597          `${description}: dataTransfer should have selected text as "text/plain"`);
    598        is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
    599          `${description}: dataTransfer should have selected nodes as "text/html"`);
    600      };
    601      document.addEventListener("drop", onDrop);
    602      document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    603      if (
    604        await trySynthesizePlainDragAndDrop(
    605          description,
    606          {
    607            srcSelection: selection,
    608            destElement: input,
    609          }
    610        )
    611      ) {
    612        is(contenteditable.innerHTML, "Soext",
    613          `${description}: Dragged range should be removed from contenteditable`);
    614        is(input.value, "",
    615          `${description}: <input>.value shouldn't be modified`);
    616        is(beforeinputEvents.length, 2,
    617                `${description}: 2 "beforeinput" events should be fired on contenteditable and <input>`);
    618        checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
    619                        [{startContainer: selectionContainers[0], startOffset: 2,
    620                          endContainer: selectionContainers[1], endOffset: 2}],
    621                        description);
    622        checkInputEvent(beforeinputEvents[1], input, "insertFromDrop", "me bold t", null, [], description);
    623        is(inputEvents.length, 1,
    624                `${description}: only one "input" event should be fired on contenteditable`);
    625        checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
    626        is(dragEvents.length, 1,
    627          `${description}: only one "drop" event should be fired on <input>`);
    628      }
    629      document.removeEventListener("drop", onDrop);
    630      document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
    631    })();
    632  }
    633 
    634  // -------- Test dragging regular text of text/html to <input type="number">
    635  //
    636  // FIXME(emilio): The -moz-appearance bit is just a hack to
    637  // work around bug 1611720.
    638  await (async function test_dragging_from_span_element_to_input_element_whose_type_number() {
    639    const description = `dragging text in non-editable <span> to <input type="number">`;
    640    container.innerHTML = `<span>123456</span><input type="number" style="-moz-appearance: textfield">`;
    641    const span = document.querySelector("div#container > span");
    642    const input = document.querySelector("div#container > input");
    643    selection.setBaseAndExtent(span.firstChild, 2, span.firstChild, 5);
    644    beforeinputEvents = [];
    645    inputEvents = [];
    646    dragEvents = [];
    647    const onDrop = aEvent => {
    648      dragEvents.push(aEvent);
    649      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
    650                       span.textContent.substring(2, 5),
    651                       `${description}: dataTransfer should have selected text as "text/plain"`);
    652      compareHTML(aEvent.dataTransfer.getData("text/html"),
    653                  span.outerHTML.replace(/>.+</, `>${span.textContent.substring(2, 5)}<`),
    654                  `${description}: dataTransfer should have selected nodes as "text/html"`);
    655    };
    656    document.addEventListener("drop", onDrop);
    657    if (
    658      await trySynthesizePlainDragAndDrop(
    659        description,
    660        {
    661          srcSelection: selection,
    662          destElement: input,
    663        }
    664      )
    665    ) {
    666      is(input.value, span.textContent.substring(2, 5),
    667        `${description}: <input>.value should be modified`);
    668      is(beforeinputEvents.length, 1,
    669        `${description}: one "beforeinput" event should be fired on <input>`);
    670      checkInputEvent(beforeinputEvents[0], input, "insertFromDrop", span.textContent.substring(2, 5), null, [], description);
    671      is(inputEvents.length, 1,
    672        `${description}: one "input" event should be fired on <input>`);
    673      checkInputEvent(inputEvents[0], input, "insertFromDrop", span.textContent.substring(2, 5), null, [], description);
    674      is(dragEvents.length, 1,
    675        `${description}: only one "drop" event should be fired on <input>`);
    676    }
    677    document.removeEventListener("drop", onDrop);
    678  })();
    679 
    680  // -------- Test dragging only text/plain data (like from another app) to contenteditable.
    681  await (async function test_dragging_only_plain_text_to_contenteditable() {
    682    const description = "dragging both text/plain and text/html data to contenteditable";
    683    container.innerHTML = '<span>Static</span><div contenteditable style="min-height: 3em;"></div>';
    684    const span = document.querySelector("div#container > span");
    685    const contenteditable = document.querySelector("div#container > div");
    686    selection.selectAllChildren(span);
    687    beforeinputEvents = [];
    688    inputEvents = [];
    689    const onDragStart = aEvent => {
    690      // Clear all dataTransfer data first.  Then, it'll be filled only with
    691      // the text/plain data and text/html data passed to synthesizeDrop().
    692      aEvent.dataTransfer.clearData();
    693    };
    694    window.addEventListener("dragstart", onDragStart, {capture: true});
    695    synthesizeDrop(span, contenteditable, [[{type: "text/plain", data: "Sample Text"}]], "copy");
    696    is(contenteditable.innerHTML, "Sample Text",
    697      `${description}: The text/plain data should be inserted`);
    698    is(beforeinputEvents.length, 1,
    699      `${description}: only one "beforeinput" events should be fired on contenteditable element`);
    700    checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
    701                    [{todo: true, type: "text/plain", data: "Sample Text"}],
    702                    [{startContainer: contenteditable, startOffset: 0,
    703                      endContainer: contenteditable, endOffset: 0}],
    704                    description);
    705    is(inputEvents.length, 1,
    706      `${description}: only one "input" events should be fired on contenteditable element`);
    707    checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
    708                    [{todo: true, type: "text/plain", data: "Sample Text"}],
    709                    [],
    710                    description);
    711    window.removeEventListener("dragstart", onDragStart, {capture: true});
    712  })();
    713 
    714  // -------- Test dragging only text/html data (like from another app) to contenteditable.
    715  await (async function test_dragging_only_html_text_to_contenteditable() {
    716    const description = "dragging only text/html data to contenteditable";
    717    container.innerHTML = '<span>Static</span><div contenteditable style="min-height: 3em;"></div>';
    718    const span = document.querySelector("div#container > span");
    719    const contenteditable = document.querySelector("div#container > div");
    720    selection.selectAllChildren(span);
    721    beforeinputEvents = [];
    722    inputEvents = [];
    723    const onDragStart = aEvent => {
    724      // Clear all dataTransfer data first.  Then, it'll be filled only with
    725      // the text/plain data and text/html data passed to synthesizeDrop().
    726      aEvent.dataTransfer.clearData();
    727    };
    728    window.addEventListener("dragstart", onDragStart, {capture: true});
    729    synthesizeDrop(span, contenteditable, [[{type: "text/html", data: "Sample <i>Italic</i> Text"}]], "copy");
    730    is(contenteditable.innerHTML, "Sample <i>Italic</i> Text",
    731      `${description}: The text/plain data should be inserted`);
    732    is(beforeinputEvents.length, 1,
    733      `${description}: only one "beforeinput" events should be fired on contenteditable element`);
    734    checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
    735                    [{todo: true, type: "text/html", data: "Sample <i>Italic</i> Text"}],
    736                    [{startContainer: contenteditable, startOffset: 0,
    737                      endContainer: contenteditable, endOffset: 0}],
    738                    description);
    739    is(inputEvents.length, 1,
    740      `${description}: only one "input" events should be fired on contenteditable element`);
    741    checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
    742                    [{todo: true, type: "text/html", data: "Sample <i>Italic</i> Text"}],
    743                    [],
    744                    description);
    745    window.removeEventListener("dragstart", onDragStart, {capture: true});
    746  })();
    747 
    748  // -------- Test dragging regular text of text/plain to <textarea>
    749  await (async function test_dragging_from_span_element_to_textarea_element() {
    750    const description = "dragging text in non-editable <span> to <textarea>";
    751    container.innerHTML = "<span>Static</span><textarea></textarea>";
    752    const span = document.querySelector("div#container > span");
    753    const textarea = document.querySelector("div#container > textarea");
    754    selection.setBaseAndExtent(span.firstChild, 2, span.firstChild, 5);
    755    beforeinputEvents = [];
    756    inputEvents = [];
    757    dragEvents = [];
    758    const onDrop = aEvent => {
    759      dragEvents.push(aEvent);
    760      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
    761                      span.textContent.substring(2, 5),
    762                      `${description}: dataTransfer should have selected text as "text/plain"`);
    763      compareHTML(aEvent.dataTransfer.getData("text/html"),
    764                  span.outerHTML.replace(/>.+</, `>${span.textContent.substring(2, 5)}<`),
    765                  `${description}: dataTransfer should have selected nodes as "text/html"`);
    766    };
    767    document.addEventListener("drop", onDrop);
    768    if (
    769      await trySynthesizePlainDragAndDrop(
    770        description,
    771        {
    772          srcSelection: selection,
    773          destElement: textarea,
    774        }
    775      )
    776    ) {
    777      is(textarea.value, span.textContent.substring(2, 5),
    778        `${description}: <textarea>.value should be modified`);
    779      is(beforeinputEvents.length, 1,
    780        `${description}: one "beforeinput" event should be fired on <textarea>`);
    781      checkInputEvent(beforeinputEvents[0], textarea, "insertFromDrop", span.textContent.substring(2, 5), null, [], description);
    782      is(inputEvents.length, 1,
    783        `${description}: one "input" event should be fired on <textarea>`);
    784      checkInputEvent(inputEvents[0], textarea, "insertFromDrop", span.textContent.substring(2, 5), null, [], description);
    785      is(dragEvents.length, 1,
    786        `${description}: only one "drop" event should be fired on <textarea>`);
    787    }
    788    document.removeEventListener("drop", onDrop);
    789  })();
    790 
    791 
    792  // -------- Test dragging contenteditable to <textarea>
    793  await (async function test_dragging_contenteditable_to_textarea_element() {
    794    const description = "dragging text in contenteditable to <textarea>";
    795    container.innerHTML = "<div contenteditable>Some <b>bold</b> text</div><textarea></textarea>";
    796    const contenteditable = document.querySelector("div#container > div");
    797    const textarea = document.querySelector("div#container > textarea");
    798    const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
    799    selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
    800    beforeinputEvents = [];
    801    inputEvents = [];
    802    dragEvents = [];
    803    const onDrop = aEvent => {
    804      dragEvents.push(aEvent);
    805      is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
    806        `${description}: dataTransfer should have selected text as "text/plain"`);
    807      is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
    808        `${description}: dataTransfer should have selected nodes as "text/html"`);
    809    };
    810    document.addEventListener("drop", onDrop);
    811    if (
    812      await trySynthesizePlainDragAndDrop(
    813        description,
    814        {
    815          srcSelection: selection,
    816          destElement: textarea,
    817        }
    818      )
    819    ) {
    820      is(contenteditable.innerHTML, "Soext",
    821        `${description}: Dragged range should be removed from contenteditable`);
    822      is(textarea.value, "me bold t",
    823        `${description}: <textarea>.value should be modified`);
    824      is(beforeinputEvents.length, 2,
    825        `${description}: 2 "beforeinput" events should be fired on contenteditable and <textarea>`);
    826      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
    827                      [{startContainer: selectionContainers[0], startOffset: 2,
    828                        endContainer: selectionContainers[1], endOffset: 2}],
    829                      description);
    830      checkInputEvent(beforeinputEvents[1], textarea, "insertFromDrop", "me bold t", null, [], description);
    831      is(inputEvents.length, 2,
    832        `${description}: 2 "input" events should be fired on contenteditable and <textarea>`);
    833      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
    834      checkInputEvent(inputEvents[1], textarea, "insertFromDrop", "me bold t", null, [], description);
    835      is(dragEvents.length, 1,
    836        `${description}: only one "drop" event should be fired on <textarea>`);
    837    }
    838    document.removeEventListener("drop", onDrop);
    839  })();
    840 
    841  // -------- Test dragging contenteditable to <textarea> (canceling "deleteByDrag")
    842  await (async function test_dragging_from_contenteditable_to_textarea_and_canceling_delete_by_drag() {
    843    const description = 'dragging text in contenteditable to <textarea> (canceling "deleteByDrag")';
    844    container.innerHTML = "<div contenteditable>Some <b>bold</b> text</div><textarea></textarea>";
    845    const contenteditable = document.querySelector("div#container > div");
    846    const textarea = document.querySelector("div#container > textarea");
    847    const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
    848    selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
    849    beforeinputEvents = [];
    850    inputEvents = [];
    851    dragEvents = [];
    852    const onDrop = aEvent => {
    853      dragEvents.push(aEvent);
    854      is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
    855        `${description}: dataTransfer should have selected text as "text/plain"`);
    856      is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
    857        `${description}: dataTransfer should have selected nodes as "text/html"`);
    858    };
    859    document.addEventListener("drop", onDrop);
    860    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    861    if (
    862      await trySynthesizePlainDragAndDrop(
    863        description,
    864        {
    865          srcSelection: selection,
    866          destElement: textarea,
    867        }
    868      )
    869    ) {
    870      is(contenteditable.innerHTML, "Some <b>bold</b> text",
    871        `${description}: Dragged range shouldn't be removed from contenteditable`);
    872      is(textarea.value, "me bold t",
    873        `${description}: <textarea>.value should be modified`);
    874      is(beforeinputEvents.length, 2,
    875        `${description}: 2 "beforeinput" events should be fired on contenteditable and <textarea>`);
    876      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
    877                      [{startContainer: selectionContainers[0], startOffset: 2,
    878                        endContainer: selectionContainers[1], endOffset: 2}],
    879                      description);
    880      checkInputEvent(beforeinputEvents[1], textarea, "insertFromDrop", "me bold t", null, [], description);
    881      is(inputEvents.length, 1,
    882        `${description}: only one "input" event should be fired on <textarea>`);
    883      checkInputEvent(inputEvents[0], textarea, "insertFromDrop", "me bold t", null, [], description);
    884      is(dragEvents.length, 1,
    885        `${description}: only one "drop" event should be fired on <textarea>`);
    886    }
    887    document.removeEventListener("drop", onDrop);
    888    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
    889  })();
    890 
    891  // -------- Test dragging contenteditable to <textarea> (canceling "insertFromDrop")
    892  await (async function test_dragging_from_contenteditable_to_textarea_and_canceling_insert_from_drop() {
    893    const description = 'dragging text in contenteditable to <textarea> (canceling "insertFromDrop")';
    894    container.innerHTML = "<div contenteditable>Some <b>bold</b> text</div><textarea></textarea>";
    895    const contenteditable = document.querySelector("div#container > div");
    896    const textarea = document.querySelector("div#container > textarea");
    897    const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
    898    selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
    899    beforeinputEvents = [];
    900    inputEvents = [];
    901    dragEvents = [];
    902    const onDrop = aEvent => {
    903      dragEvents.push(aEvent);
    904      is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
    905        `${description}: dataTransfer should have selected text as "text/plain"`);
    906      is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
    907        `${description}: dataTransfer should have selected nodes as "text/html"`);
    908    };
    909    document.addEventListener("drop", onDrop);
    910    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    911    if (
    912      await trySynthesizePlainDragAndDrop(
    913        description,
    914        {
    915          srcSelection: selection,
    916          destElement: textarea,
    917        }
    918      )
    919    ) {
    920      is(contenteditable.innerHTML, "Soext",
    921        `${description}: Dragged range should be removed from contenteditable`);
    922      is(textarea.value, "",
    923        `${description}: <textarea>.value shouldn't be modified`);
    924      is(beforeinputEvents.length, 2,
    925        `${description}: 2 "beforeinput" events should be fired on contenteditable and <textarea>`);
    926      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
    927                      [{startContainer: selectionContainers[0], startOffset: 2,
    928                        endContainer: selectionContainers[1], endOffset: 2}],
    929                      description);
    930      checkInputEvent(beforeinputEvents[1], textarea, "insertFromDrop", "me bold t", null, [], description);
    931      is(inputEvents.length, 1,
    932        `${description}: only one "input" event should be fired on contenteditable`);
    933      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
    934      is(dragEvents.length, 1,
    935        `${description}: only one "drop" event should be fired on <textarea>`);
    936    }
    937    document.removeEventListener("drop", onDrop);
    938    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
    939  })();
    940 
    941  // -------- Test dragging contenteditable to same contenteditable
    942 
    943  await (async function test_dragging_from_contenteditable_to_itself() {
    944    const description = "dragging text in contenteditable to same contenteditable";
    945    container.innerHTML = "<div contenteditable><b>bold</b> <span>MMMM</span></div>";
    946    const contenteditable = document.querySelector("div#container > div");
    947    const b = document.querySelector("div#container > div > b");
    948    const span = document.querySelector("div#container > div > span");
    949    const lastTextNode = span.firstChild;
    950    const selectionContainers = [b.firstChild, b.firstChild];
    951    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    952    beforeinputEvents = [];
    953    inputEvents = [];
    954    dragEvents = [];
    955    const onDrop = aEvent => {
    956      dragEvents.push(aEvent);
    957      is(aEvent.dataTransfer.getData("text/plain"), "ol",
    958        `${description}: dataTransfer should have selected text as "text/plain"`);
    959      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
    960        `${description}: dataTransfer should have selected nodes as "text/html"`);
    961    };
    962    document.addEventListener("drop", onDrop);
    963    if (
    964      await trySynthesizePlainDragAndDrop(
    965        description,
    966        {
    967          srcSelection: selection,
    968          destElement: span,
    969        }
    970      )
    971    ) {
    972      is(contenteditable.innerHTML, "<b>bd</b> <span>MM</span><b>ol</b><span>MM</span>",
    973        `${description}: dragged range should be removed from contenteditable`);
    974      is(beforeinputEvents.length, 2,
    975        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
    976      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
    977                      [{startContainer: selectionContainers[0], startOffset: 1,
    978                        endContainer: selectionContainers[1], endOffset: 3}],
    979                      description);
    980      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
    981                      [{type: "text/html", data: "<b>ol</b>"},
    982                      {type: "text/plain", data: "ol"}],
    983                      [{startContainer: lastTextNode, startOffset: 2,
    984                        endContainer: lastTextNode, endOffset: 2}],
    985                      description);
    986      is(inputEvents.length, 2,
    987        `${description}: 2 "input" events should be fired on contenteditable`);
    988      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
    989      checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
    990                      [{type: "text/html", data: "<b>ol</b>"},
    991                      {type: "text/plain", data: "ol"}],
    992                      [],
    993                      description);
    994      is(dragEvents.length, 1,
    995        `${description}: only one "drop" event should be fired on contenteditable`);
    996    }
    997    document.removeEventListener("drop", onDrop);
    998  })();
    999 
   1000  // -------- Test dragging contenteditable to same contenteditable (canceling "deleteByDrag")
   1001  await (async function test_dragging_from_contenteditable_to_itself_and_canceling_delete_by_drag() {
   1002    const description = 'dragging text in contenteditable to same contenteditable (canceling "deleteByDrag")';
   1003    container.innerHTML = "<div contenteditable><b>bold</b> <span>MMMM</span></div>";
   1004    const contenteditable = document.querySelector("div#container > div");
   1005    const b = document.querySelector("div#container > div > b");
   1006    const span = document.querySelector("div#container > div > span");
   1007    const lastTextNode = span.firstChild;
   1008    const selectionContainers = [b.firstChild, b.firstChild];
   1009    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   1010    beforeinputEvents = [];
   1011    inputEvents = [];
   1012    dragEvents = [];
   1013    const onDrop = aEvent => {
   1014      dragEvents.push(aEvent);
   1015      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1016        `${description}: dataTransfer should have selected text as "text/plain"`);
   1017      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1018        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1019    };
   1020    document.addEventListener("drop", onDrop);
   1021    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
   1022    if (
   1023      await trySynthesizePlainDragAndDrop(
   1024        description,
   1025        {
   1026          srcSelection: selection,
   1027          destElement: span,
   1028        }
   1029      )
   1030    ) {
   1031      is(contenteditable.innerHTML, "<b>bold</b> <span>MM</span><b>ol</b><span>MM</span>",
   1032        `${description}: dragged range shouldn't be removed from contenteditable`);
   1033      is(beforeinputEvents.length, 2,
   1034        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
   1035      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   1036                      [{startContainer: selectionContainers[0], startOffset: 1,
   1037                        endContainer: selectionContainers[1], endOffset: 3}],
   1038                      description);
   1039      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
   1040                      [{type: "text/html", data: "<b>ol</b>"},
   1041                      {type: "text/plain", data: "ol"}],
   1042                      [{startContainer: lastTextNode, startOffset: 2,
   1043                        endContainer: lastTextNode, endOffset: 2}],
   1044                      description);
   1045      is(inputEvents.length, 1,
   1046        `${description}: only one "input" event should be fired on contenteditable`);
   1047      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
   1048                      [{type: "text/html", data: "<b>ol</b>"},
   1049                      {type: "text/plain", data: "ol"}],
   1050                      [],
   1051                      description);
   1052      is(dragEvents.length, 1,
   1053        `${description}: only one "drop" event should be fired on contenteditable`);
   1054    }
   1055    document.removeEventListener("drop", onDrop);
   1056    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
   1057  })();
   1058 
   1059  // -------- Test dragging contenteditable to same contenteditable (canceling "insertFromDrop")
   1060  await (async function test_dragging_from_contenteditable_to_itself_and_canceling_insert_from_drop() {
   1061    const description = 'dragging text in contenteditable to same contenteditable (canceling "insertFromDrop")';
   1062    container.innerHTML = "<div contenteditable><b>bold</b> <span>MMMM</span></div>";
   1063    const contenteditable = document.querySelector("div#container > div");
   1064    const b = document.querySelector("div#container > div > b");
   1065    const span = document.querySelector("div#container > div > span");
   1066    const lastTextNode = span.firstChild;
   1067    const selectionContainers = [b.firstChild, b.firstChild];
   1068    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   1069    beforeinputEvents = [];
   1070    inputEvents = [];
   1071    dragEvents = [];
   1072    const onDrop = aEvent => {
   1073      dragEvents.push(aEvent);
   1074      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1075        `${description}: dataTransfer should have selected text as "text/plain"`);
   1076      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1077        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1078    };
   1079    document.addEventListener("drop", onDrop);
   1080    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
   1081    if (
   1082      await trySynthesizePlainDragAndDrop(
   1083        description,
   1084        {
   1085          srcSelection: selection,
   1086          destElement: span,
   1087        }
   1088      )
   1089    ) {
   1090      is(contenteditable.innerHTML, "<b>bd</b> <span>MMMM</span>",
   1091        `${description}: dragged range should be removed from contenteditable`);
   1092      is(beforeinputEvents.length, 2,
   1093        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
   1094      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   1095                      [{startContainer: selectionContainers[0], startOffset: 1,
   1096                        endContainer: selectionContainers[1], endOffset: 3}],
   1097                      description);
   1098      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
   1099                      [{type: "text/html", data: "<b>ol</b>"},
   1100                      {type: "text/plain", data: "ol"}],
   1101                      [{startContainer: lastTextNode, startOffset: 2,
   1102                        endContainer: lastTextNode, endOffset: 2}],
   1103                      description);
   1104      is(inputEvents.length, 1,
   1105        `${description}: only one "input" event should be fired on contenteditable`);
   1106      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
   1107      is(dragEvents.length, 1,
   1108        `${description}: only one "drop" event should be fired on contenteditable`);
   1109    }
   1110    document.removeEventListener("drop", onDrop);
   1111    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
   1112  })();
   1113 
   1114  // -------- Test copy-dragging contenteditable to same contenteditable
   1115  await (async function test_copy_dragging_from_contenteditable_to_itself() {
   1116    const description = "copy-dragging text in contenteditable to same contenteditable";
   1117    container.innerHTML = "<div contenteditable><b>bold</b> <span>MMMM</span></div>";
   1118    document.documentElement.scrollTop;
   1119    const contenteditable = document.querySelector("div#container > div");
   1120    const b = document.querySelector("div#container > div > b");
   1121    const span = document.querySelector("div#container > div > span");
   1122    const lastTextNode = span.firstChild;
   1123    selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
   1124    beforeinputEvents = [];
   1125    inputEvents = [];
   1126    dragEvents = [];
   1127    const onDrop = aEvent => {
   1128      dragEvents.push(aEvent);
   1129      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1130        `${description}: dataTransfer should have selected text as "text/plain"`);
   1131      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1132        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1133    };
   1134    document.addEventListener("drop", onDrop);
   1135    if (
   1136      await trySynthesizePlainDragAndDrop(
   1137        description,
   1138        {
   1139          srcSelection: selection,
   1140          destElement: span,
   1141          dragEvent: kModifiersToCopy,
   1142        }
   1143      )
   1144    ) {
   1145      is(contenteditable.innerHTML, "<b>bold</b> <span>MM</span><b>ol</b><span>MM</span>",
   1146        `${description}: dragged range shouldn't be removed from contenteditable`);
   1147      is(beforeinputEvents.length, 1,
   1148        `${description}: only 1 "beforeinput" events should be fired on contenteditable`);
   1149      checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
   1150                      [{type: "text/html", data: "<b>ol</b>"},
   1151                      {type: "text/plain", data: "ol"}],
   1152                      [{startContainer: lastTextNode, startOffset: 2,
   1153                        endContainer: lastTextNode, endOffset: 2}],
   1154                      description);
   1155      is(inputEvents.length, 1,
   1156        `${description}: only 1 "input" events should be fired on contenteditable`);
   1157      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
   1158                      [{type: "text/html", data: "<b>ol</b>"},
   1159                      {type: "text/plain", data: "ol"}],
   1160                      [],
   1161                      description);
   1162      is(dragEvents.length, 1,
   1163        `${description}: only one "drop" event should be fired on contenteditable`);
   1164    }
   1165    document.removeEventListener("drop", onDrop);
   1166  })();
   1167 
   1168  // -------- Test dragging contenteditable to other contenteditable
   1169  await (async function test_dragging_from_contenteditable_to_other_contenteditable() {
   1170    const description = "dragging text in contenteditable to other contenteditable";
   1171    container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable style="min-height: 3em;"></div>';
   1172    const contenteditable = document.querySelector("div#container > div");
   1173    const b = document.querySelector("div#container > div > b");
   1174    const otherContenteditable = document.querySelector("div#container > div ~ div");
   1175    const selectionContainers = [b.firstChild, b.firstChild];
   1176    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   1177    beforeinputEvents = [];
   1178    inputEvents = [];
   1179    dragEvents = [];
   1180    const onDrop = aEvent => {
   1181      dragEvents.push(aEvent);
   1182      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1183        `${description}: dataTransfer should have selected text as "text/plain"`);
   1184      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1185        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1186    };
   1187    document.addEventListener("drop", onDrop);
   1188    if (
   1189      await trySynthesizePlainDragAndDrop(
   1190        description,
   1191        {
   1192          srcSelection: selection,
   1193          destElement: otherContenteditable,
   1194        }
   1195      )
   1196    ) {
   1197      is(contenteditable.innerHTML, "<b>bd</b>",
   1198        `${description}: dragged range should be removed from contenteditable`);
   1199      is(otherContenteditable.innerHTML, "<b>ol</b>",
   1200        `${description}: dragged content should be inserted into other contenteditable`);
   1201      is(beforeinputEvents.length, 2,
   1202        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
   1203      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   1204                      [{startContainer: selectionContainers[0], startOffset: 1,
   1205                        endContainer: selectionContainers[1], endOffset: 3}],
   1206                      description);
   1207      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
   1208                      [{type: "text/html", data: "<b>ol</b>"},
   1209                      {type: "text/plain", data: "ol"}],
   1210                      [{startContainer: otherContenteditable, startOffset: 0,
   1211                        endContainer: otherContenteditable, endOffset: 0}],
   1212                      description);
   1213      is(inputEvents.length, 2,
   1214        `${description}: 2 "input" events should be fired on contenteditable`);
   1215      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
   1216      checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
   1217                      [{type: "text/html", data: "<b>ol</b>"},
   1218                      {type: "text/plain", data: "ol"}],
   1219                      [],
   1220                      description);
   1221      is(dragEvents.length, 1,
   1222        `${description}: only one "drop" event should be fired on other contenteditable`);
   1223    }
   1224    document.removeEventListener("drop", onDrop);
   1225  })();
   1226 
   1227  // -------- Test dragging contenteditable to other contenteditable (canceling "deleteByDrag")
   1228  await (async function test_dragging_from_contenteditable_to_other_contenteditable_and_canceling_delete_by_drag() {
   1229    const description = 'dragging text in contenteditable to other contenteditable (canceling "deleteByDrag")';
   1230    container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable style="min-height: 3em;"></div>';
   1231    const contenteditable = document.querySelector("div#container > div");
   1232    const b = document.querySelector("div#container > div > b");
   1233    const otherContenteditable = document.querySelector("div#container > div ~ div");
   1234    const selectionContainers = [b.firstChild, b.firstChild];
   1235    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   1236    beforeinputEvents = [];
   1237    inputEvents = [];
   1238    dragEvents = [];
   1239    const onDrop = aEvent => {
   1240      dragEvents.push(aEvent);
   1241      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1242        `${description}: dataTransfer should have selected text as "text/plain"`);
   1243      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1244        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1245    };
   1246    document.addEventListener("drop", onDrop);
   1247    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
   1248    if (
   1249      await trySynthesizePlainDragAndDrop(
   1250        description,
   1251        {
   1252          srcSelection: selection,
   1253          destElement: otherContenteditable,
   1254        }
   1255      )
   1256    ) {
   1257      is(contenteditable.innerHTML, "<b>bold</b>",
   1258        `${description}: dragged range shouldn't be removed from contenteditable`);
   1259      is(otherContenteditable.innerHTML, "<b>ol</b>",
   1260        `${description}: dragged content should be inserted into other contenteditable`);
   1261      is(beforeinputEvents.length, 2,
   1262        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
   1263      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   1264                      [{startContainer: selectionContainers[0], startOffset: 1,
   1265                        endContainer: selectionContainers[1], endOffset: 3}],
   1266                      description);
   1267      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
   1268                      [{type: "text/html", data: "<b>ol</b>"},
   1269                      {type: "text/plain", data: "ol"}],
   1270                      [{startContainer: otherContenteditable, startOffset: 0,
   1271                        endContainer: otherContenteditable, endOffset: 0}],
   1272                      description);
   1273      is(inputEvents.length, 1,
   1274        `${description}: only one "input" event should be fired on other contenteditable`);
   1275      checkInputEvent(inputEvents[0], otherContenteditable, "insertFromDrop", null,
   1276                      [{type: "text/html", data: "<b>ol</b>"},
   1277                      {type: "text/plain", data: "ol"}],
   1278                      [],
   1279                      description);
   1280      is(dragEvents.length, 1,
   1281        `${description}: only one "drop" event should be fired on other contenteditable`);
   1282    }
   1283    document.removeEventListener("drop", onDrop);
   1284    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
   1285  })();
   1286 
   1287  // -------- Test dragging contenteditable to other contenteditable (canceling "insertFromDrop")
   1288  await (async function test_dragging_from_contenteditable_to_other_contenteditable_and_canceling_insert_from_drop() {
   1289    const description = 'dragging text in contenteditable to other contenteditable (canceling "insertFromDrop")';
   1290    container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable style="min-height: 3em;"></div>';
   1291    const contenteditable = document.querySelector("div#container > div");
   1292    const b = document.querySelector("div#container > div > b");
   1293    const otherContenteditable = document.querySelector("div#container > div ~ div");
   1294    const selectionContainers = [b.firstChild, b.firstChild];
   1295    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   1296    beforeinputEvents = [];
   1297    inputEvents = [];
   1298    dragEvents = [];
   1299    const onDrop = aEvent => {
   1300      dragEvents.push(aEvent);
   1301      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1302        `${description}: dataTransfer should have selected text as "text/plain"`);
   1303      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1304        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1305    };
   1306    document.addEventListener("drop", onDrop);
   1307    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
   1308    if (
   1309      await trySynthesizePlainDragAndDrop(
   1310        description,
   1311        {
   1312          srcSelection: selection,
   1313          destElement: otherContenteditable,
   1314        }
   1315      )
   1316    ) {
   1317      is(contenteditable.innerHTML, "<b>bd</b>",
   1318        `${description}: dragged range should be removed from contenteditable`);
   1319      is(otherContenteditable.innerHTML, "",
   1320        `${description}: dragged content shouldn't be inserted into other contenteditable`);
   1321      is(beforeinputEvents.length, 2,
   1322        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
   1323      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   1324                      [{startContainer: selectionContainers[0], startOffset: 1,
   1325                        endContainer: selectionContainers[1], endOffset: 3}],
   1326                      description);
   1327      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
   1328                      [{type: "text/html", data: "<b>ol</b>"},
   1329                      {type: "text/plain", data: "ol"}],
   1330                      [{startContainer: otherContenteditable, startOffset: 0,
   1331                        endContainer: otherContenteditable, endOffset: 0}],
   1332                      description);
   1333      is(inputEvents.length, 1,
   1334        `${description}: only one "input" event should be fired on contenteditable`);
   1335      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
   1336      is(dragEvents.length, 1,
   1337        `${description}: only one "drop" event should be fired on other contenteditable`);
   1338    }
   1339    document.removeEventListener("drop", onDrop);
   1340    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
   1341  })();
   1342 
   1343  // -------- Test copy-dragging contenteditable to other contenteditable
   1344  await (async function test_copy_dragging_from_contenteditable_to_other_contenteditable() {
   1345    const description = "copy-dragging text in contenteditable to other contenteditable";
   1346    container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable style="min-height: 3em;"></div>';
   1347    const contenteditable = document.querySelector("div#container > div");
   1348    const b = document.querySelector("div#container > div > b");
   1349    const otherContenteditable = document.querySelector("div#container > div ~ div");
   1350    selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
   1351    beforeinputEvents = [];
   1352    inputEvents = [];
   1353    dragEvents = [];
   1354    const onDrop = aEvent => {
   1355      dragEvents.push(aEvent);
   1356      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1357        `${description}: dataTransfer should have selected text as "text/plain"`);
   1358      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1359        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1360    };
   1361    document.addEventListener("drop", onDrop);
   1362    if (
   1363      await trySynthesizePlainDragAndDrop(
   1364        description,
   1365        {
   1366          srcSelection: selection,
   1367          destElement: otherContenteditable,
   1368          dragEvent: kModifiersToCopy,
   1369        }
   1370      )
   1371    ) {
   1372      is(contenteditable.innerHTML, "<b>bold</b>",
   1373        `${description}: dragged range shouldn't be removed from contenteditable`);
   1374      is(otherContenteditable.innerHTML, "<b>ol</b>",
   1375        `${description}: dragged content should be inserted into other contenteditable`);
   1376      is(beforeinputEvents.length, 1,
   1377        `${description}: only one "beforeinput" events should be fired on other contenteditable`);
   1378      checkInputEvent(beforeinputEvents[0], otherContenteditable, "insertFromDrop", null,
   1379                      [{type: "text/html", data: "<b>ol</b>"},
   1380                      {type: "text/plain", data: "ol"}],
   1381                      [{startContainer: otherContenteditable, startOffset: 0,
   1382                        endContainer: otherContenteditable, endOffset: 0}],
   1383                      description);
   1384      is(inputEvents.length, 1,
   1385        `${description}: only one "input" events should be fired on other contenteditable`);
   1386      checkInputEvent(inputEvents[0], otherContenteditable, "insertFromDrop", null,
   1387                      [{type: "text/html", data: "<b>ol</b>"},
   1388                      {type: "text/plain", data: "ol"}],
   1389                      [],
   1390                      description);
   1391      is(dragEvents.length, 1,
   1392        `${description}: only one "drop" event should be fired on other contenteditable`);
   1393    }
   1394    document.removeEventListener("drop", onDrop);
   1395  })();
   1396 
   1397  // -------- Test dragging nested contenteditable to contenteditable
   1398  await (async function test_dragging_from_nested_contenteditable_to_contenteditable() {
   1399    const description = "dragging text in nested contenteditable to contenteditable";
   1400    container.innerHTML = '<div contenteditable><p><br></p><div contenteditable="false"><p contenteditable><b>bold</b></p></div></div>';
   1401    const contenteditable = document.querySelector("div#container > div");
   1402    const otherContenteditable = document.querySelector("div#container > div > div > p");
   1403    const b = document.querySelector("div#container > div > div > p > b");
   1404    contenteditable.focus();
   1405    const selectionContainers = [b.firstChild, b.firstChild];
   1406    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   1407    beforeinputEvents = [];
   1408    inputEvents = [];
   1409    dragEvents = [];
   1410    const onDrop = aEvent => {
   1411      dragEvents.push(aEvent);
   1412      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1413        `${description}: dataTransfer should have selected text as "text/plain"`);
   1414      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1415        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1416    };
   1417    document.addEventListener("drop", onDrop);
   1418    if (
   1419      await trySynthesizePlainDragAndDrop(
   1420        description,
   1421        {
   1422          srcSelection: selection,
   1423          destElement: contenteditable.firstChild,
   1424        }
   1425      )
   1426    ) {
   1427      is(contenteditable.innerHTML, '<p><b>ol</b></p><div contenteditable="false"><p contenteditable=""><b>bd</b></p></div>',
   1428        `${description}: dragged range should be moved from nested contenteditable to the contenteditable`);
   1429      is(beforeinputEvents.length, 2,
   1430        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
   1431      checkInputEvent(beforeinputEvents[0], otherContenteditable, "deleteByDrag", null, null,
   1432                      [{startContainer: selectionContainers[0], startOffset: 1,
   1433                        endContainer: selectionContainers[1], endOffset: 3}],
   1434                      description);
   1435      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
   1436                      [{type: "text/html", data: "<b>ol</b>"},
   1437                      {type: "text/plain", data: "ol"}],
   1438                      [{startContainer: contenteditable.firstChild, startOffset: 0,
   1439                        endContainer: contenteditable.firstChild, endOffset: 0}],
   1440                      description);
   1441      is(inputEvents.length, 2,
   1442        `${description}: 2 "input" events should be fired on contenteditable`);
   1443      checkInputEvent(inputEvents[0], otherContenteditable, "deleteByDrag", null, null, [], description);
   1444      checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
   1445                      [{type: "text/html", data: "<b>ol</b>"},
   1446                      {type: "text/plain", data: "ol"}],
   1447                      [],
   1448                      description);
   1449      is(dragEvents.length, 1,
   1450        `${description}: only one "drop" event should be fired on contenteditable`);
   1451    }
   1452    document.removeEventListener("drop", onDrop);
   1453  })();
   1454 
   1455  // -------- Test dragging nested contenteditable to contenteditable (canceling "deleteByDrag")
   1456  await (async function test_dragging_from_nested_contenteditable_to_contenteditable_canceling_delete_by_drag() {
   1457    const description = 'dragging text in nested contenteditable to contenteditable (canceling "deleteByDrag")';
   1458    container.innerHTML = '<div contenteditable><p><br></p><div contenteditable="false"><p contenteditable><b>bold</b></p></div></div>';
   1459    const contenteditable = document.querySelector("div#container > div");
   1460    const otherContenteditable = document.querySelector("div#container > div > div > p");
   1461    const b = document.querySelector("div#container > div > div > p > b");
   1462    contenteditable.focus();
   1463    const selectionContainers = [b.firstChild, b.firstChild];
   1464    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   1465    beforeinputEvents = [];
   1466    inputEvents = [];
   1467    dragEvents = [];
   1468    const onDrop = aEvent => {
   1469      dragEvents.push(aEvent);
   1470      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1471        `${description}: dataTransfer should have selected text as "text/plain"`);
   1472      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1473        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1474    };
   1475    document.addEventListener("drop", onDrop);
   1476    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
   1477    if (
   1478      await trySynthesizePlainDragAndDrop(
   1479        description,
   1480        {
   1481          srcSelection: selection,
   1482          destElement: contenteditable.firstChild,
   1483        }
   1484      )
   1485    ) {
   1486      is(contenteditable.innerHTML, '<p><b>ol</b></p><div contenteditable="false"><p contenteditable=""><b>bold</b></p></div>',
   1487        `${description}: dragged range should be copied from nested contenteditable to the contenteditable`);
   1488      is(beforeinputEvents.length, 2,
   1489        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
   1490      checkInputEvent(beforeinputEvents[0], otherContenteditable, "deleteByDrag", null, null,
   1491                      [{startContainer: selectionContainers[0], startOffset: 1,
   1492                        endContainer: selectionContainers[1], endOffset: 3}],
   1493                      description);
   1494      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
   1495                      [{type: "text/html", data: "<b>ol</b>"},
   1496                      {type: "text/plain", data: "ol"}],
   1497                      [{startContainer: contenteditable.firstChild, startOffset: 0,
   1498                        endContainer: contenteditable.firstChild, endOffset: 0}],
   1499                      description);
   1500      is(inputEvents.length, 1,
   1501        `${description}: only one "input" event should be fired on contenteditable`);
   1502      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
   1503                      [{type: "text/html", data: "<b>ol</b>"},
   1504                      {type: "text/plain", data: "ol"}],
   1505                      [],
   1506                      description);
   1507      is(dragEvents.length, 1,
   1508        `${description}: only one "drop" event should be fired on contenteditable`);
   1509    }
   1510    document.removeEventListener("drop", onDrop);
   1511    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
   1512  })();
   1513 
   1514  // -------- Test dragging nested contenteditable to contenteditable (canceling "insertFromDrop")
   1515  await (async function test_dragging_from_nested_contenteditable_to_contenteditable_canceling_insert_from_drop() {
   1516    const description = 'dragging text in nested contenteditable to contenteditable (canceling "insertFromDrop")';
   1517    container.innerHTML = '<div contenteditable><p><br></p><div contenteditable="false"><p contenteditable><b>bold</b></p></div></div>';
   1518    const contenteditable = document.querySelector("div#container > div");
   1519    const otherContenteditable = document.querySelector("div#container > div > div > p");
   1520    const b = document.querySelector("div#container > div > div > p > b");
   1521    contenteditable.focus();
   1522    const selectionContainers = [b.firstChild, b.firstChild];
   1523    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   1524    beforeinputEvents = [];
   1525    inputEvents = [];
   1526    dragEvents = [];
   1527    const onDrop = aEvent => {
   1528      dragEvents.push(aEvent);
   1529      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1530        `${description}: dataTransfer should have selected text as "text/plain"`);
   1531      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1532        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1533    };
   1534    document.addEventListener("drop", onDrop);
   1535    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
   1536    if (
   1537      await trySynthesizePlainDragAndDrop(
   1538        description,
   1539        {
   1540          srcSelection: selection,
   1541          destElement: contenteditable.firstChild,
   1542        }
   1543      )
   1544    ) {
   1545      is(contenteditable.innerHTML, '<p><br></p><div contenteditable="false"><p contenteditable=""><b>bd</b></p></div>',
   1546        `${description}: dragged range should be removed from nested contenteditable`);
   1547      is(beforeinputEvents.length, 2,
   1548        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
   1549      checkInputEvent(beforeinputEvents[0], otherContenteditable, "deleteByDrag", null, null,
   1550                      [{startContainer: selectionContainers[0], startOffset: 1,
   1551                        endContainer: selectionContainers[1], endOffset: 3}],
   1552                      description);
   1553      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
   1554                      [{type: "text/html", data: "<b>ol</b>"},
   1555                      {type: "text/plain", data: "ol"}],
   1556                      [{startContainer: contenteditable.firstChild, startOffset: 0,
   1557                        endContainer: contenteditable.firstChild, endOffset: 0}],
   1558                      description);
   1559      is(inputEvents.length, 1,
   1560        `${description}: only one "input" event should be fired on nested contenteditable`);
   1561      checkInputEvent(inputEvents[0], otherContenteditable, "deleteByDrag", null, null, [], description);
   1562      is(dragEvents.length, 1,
   1563        `${description}: only one "drop" event should be fired on contenteditable`);
   1564    }
   1565    document.removeEventListener("drop", onDrop);
   1566    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
   1567  })();
   1568 
   1569  // -------- Test copy-dragging nested contenteditable to contenteditable
   1570  await (async function test_copy_dragging_from_nested_contenteditable_to_contenteditable() {
   1571    const description = "copy-dragging text in nested contenteditable to contenteditable";
   1572    container.innerHTML = '<div contenteditable><p><br></p><div contenteditable="false"><p contenteditable><b>bold</b></p></div></div>';
   1573    const contenteditable = document.querySelector("div#container > div");
   1574    const b = document.querySelector("div#container > div > div > p > b");
   1575    contenteditable.focus();
   1576    selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
   1577    beforeinputEvents = [];
   1578    inputEvents = [];
   1579    dragEvents = [];
   1580    const onDrop = aEvent => {
   1581      dragEvents.push(aEvent);
   1582      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1583        `${description}: dataTransfer should have selected text as "text/plain"`);
   1584      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1585        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1586    };
   1587    document.addEventListener("drop", onDrop);
   1588    if (
   1589      await trySynthesizePlainDragAndDrop(
   1590        description,
   1591        {
   1592          srcSelection: selection,
   1593          destElement: contenteditable.firstChild,
   1594          dragEvent: kModifiersToCopy,
   1595        }
   1596      )
   1597    ) {
   1598      is(contenteditable.innerHTML, '<p><b>ol</b></p><div contenteditable="false"><p contenteditable=""><b>bold</b></p></div>',
   1599        `${description}: dragged range should be moved from nested contenteditable to the contenteditable`);
   1600      is(beforeinputEvents.length, 1,
   1601        `${description}: only one "beforeinput" events should be fired on contenteditable`);
   1602      checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
   1603                      [{type: "text/html", data: "<b>ol</b>"},
   1604                      {type: "text/plain", data: "ol"}],
   1605                      [{startContainer: contenteditable.firstChild, startOffset: 0,
   1606                        endContainer: contenteditable.firstChild, endOffset: 0}],
   1607                      description);
   1608      is(inputEvents.length, 1,
   1609        `${description}: only one "input" events should be fired on contenteditable`);
   1610      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
   1611                      [{type: "text/html", data: "<b>ol</b>"},
   1612                      {type: "text/plain", data: "ol"}],
   1613                      [],
   1614                      description);
   1615      is(dragEvents.length, 1,
   1616        `${description}: only one "drop" event should be fired on contenteditable`);
   1617    }
   1618    document.removeEventListener("drop", onDrop);
   1619  })();
   1620 
   1621  // -------- Test dragging contenteditable to nested contenteditable
   1622  await (async function test_dragging_from_contenteditable_to_nested_contenteditable() {
   1623    const description = "dragging text in contenteditable to nested contenteditable";
   1624    container.innerHTML = '<div contenteditable><p><b>bold</b></p><div contenteditable="false"><p contenteditable><br></p></div></div>';
   1625    const contenteditable = document.querySelector("div#container > div");
   1626    const b = document.querySelector("div#container > div > p > b");
   1627    const otherContenteditable = document.querySelector("div#container > div > div > p");
   1628    contenteditable.focus();
   1629    const selectionContainers = [b.firstChild, b.firstChild];
   1630    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   1631    beforeinputEvents = [];
   1632    inputEvents = [];
   1633    dragEvents = [];
   1634    const onDrop = aEvent => {
   1635      dragEvents.push(aEvent);
   1636      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1637        `${description}: dataTransfer should have selected text as "text/plain"`);
   1638      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1639        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1640    };
   1641    document.addEventListener("drop", onDrop);
   1642    if (
   1643      await trySynthesizePlainDragAndDrop(
   1644        description,
   1645        {
   1646          srcSelection: selection,
   1647          destElement: otherContenteditable,
   1648        }
   1649      )
   1650    ) {
   1651      is(contenteditable.innerHTML, '<p><b>bd</b></p><div contenteditable="false"><p contenteditable=""><b>ol</b></p></div>',
   1652        `${description}: dragged range should be moved from contenteditable to nested contenteditable`);
   1653      is(beforeinputEvents.length, 2,
   1654        `${description}: 2 "beforeinput" events should be fired on contenteditable and nested contenteditable`);
   1655      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   1656                      [{startContainer: selectionContainers[0], startOffset: 1,
   1657                        endContainer: selectionContainers[1], endOffset: 3}],
   1658                      description);
   1659      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
   1660                      [{type: "text/html", data: "<b>ol</b>"},
   1661                      {type: "text/plain", data: "ol"}],
   1662                      [{startContainer: otherContenteditable, startOffset: 0,
   1663                        endContainer: otherContenteditable, endOffset: 0}],
   1664                      description);
   1665      is(inputEvents.length, 2,
   1666        `${description}: 2 "input" events should be fired on contenteditable and nested contenteditable`);
   1667      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
   1668      checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
   1669                      [{type: "text/html", data: "<b>ol</b>"},
   1670                      {type: "text/plain", data: "ol"}],
   1671                      [],
   1672                      description);
   1673      is(dragEvents.length, 1,
   1674        `${description}: only one "drop" event should be fired on contenteditable`);
   1675    }
   1676    document.removeEventListener("drop", onDrop);
   1677  })();
   1678 
   1679  // -------- Test dragging contenteditable to nested contenteditable (canceling "deleteByDrag")
   1680  await (async function test_dragging_from_contenteditable_to_nested_contenteditable_and_canceling_delete_by_drag() {
   1681    const description = 'dragging text in contenteditable to nested contenteditable (canceling "deleteByDrag")';
   1682    container.innerHTML = '<div contenteditable><p><b>bold</b></p><div contenteditable="false"><p contenteditable><br></p></div></div>';
   1683    const contenteditable = document.querySelector("div#container > div");
   1684    const b = document.querySelector("div#container > div > p > b");
   1685    const otherContenteditable = document.querySelector("div#container > div > div > p");
   1686    contenteditable.focus();
   1687    const selectionContainers = [b.firstChild, b.firstChild];
   1688    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   1689    beforeinputEvents = [];
   1690    inputEvents = [];
   1691    dragEvents = [];
   1692    const onDrop = aEvent => {
   1693      dragEvents.push(aEvent);
   1694      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1695        `${description}: dataTransfer should have selected text as "text/plain"`);
   1696      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1697        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1698    };
   1699    document.addEventListener("drop", onDrop);
   1700    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
   1701    if (
   1702      await trySynthesizePlainDragAndDrop(
   1703        description,
   1704        {
   1705          srcSelection: selection,
   1706          destElement: otherContenteditable,
   1707        }
   1708      )
   1709    ) {
   1710      is(contenteditable.innerHTML, '<p><b>bold</b></p><div contenteditable="false"><p contenteditable=""><b>ol</b></p></div>',
   1711        `${description}: dragged range should be copied from contenteditable to nested contenteditable`);
   1712      is(beforeinputEvents.length, 2,
   1713        `${description}: 2 "beforeinput" events should be fired on contenteditable and nested contenteditable`);
   1714      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   1715                      [{startContainer: selectionContainers[0], startOffset: 1,
   1716                        endContainer: selectionContainers[1], endOffset: 3}],
   1717                      description);
   1718      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
   1719                      [{type: "text/html", data: "<b>ol</b>"},
   1720                      {type: "text/plain", data: "ol"}],
   1721                      [{startContainer: otherContenteditable, startOffset: 0,
   1722                        endContainer: otherContenteditable, endOffset: 0}],
   1723                      description);
   1724      is(inputEvents.length, 1,
   1725        `${description}: only one "input" event should be fired on contenteditable and nested contenteditable`);
   1726      checkInputEvent(inputEvents[0], otherContenteditable, "insertFromDrop", null,
   1727                      [{type: "text/html", data: "<b>ol</b>"},
   1728                      {type: "text/plain", data: "ol"}],
   1729                      [],
   1730                      description);
   1731      is(dragEvents.length, 1,
   1732        `${description}: only one "drop" event should be fired on contenteditable`);
   1733    }
   1734    document.removeEventListener("drop", onDrop);
   1735    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
   1736  })();
   1737 
   1738  // -------- Test dragging contenteditable to nested contenteditable (canceling "insertFromDrop")
   1739  await (async function test_dragging_from_contenteditable_to_nested_contenteditable_and_canceling_insert_from_drop() {
   1740    const description = 'dragging text in contenteditable to nested contenteditable (canceling "insertFromDrop")';
   1741    container.innerHTML = '<div contenteditable><p><b>bold</b></p><div contenteditable="false"><p contenteditable><br></p></div></div>';
   1742    const contenteditable = document.querySelector("div#container > div");
   1743    const b = document.querySelector("div#container > div > p > b");
   1744    const otherContenteditable = document.querySelector("div#container > div > div > p");
   1745    contenteditable.focus();
   1746    const selectionContainers = [b.firstChild, b.firstChild];
   1747    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   1748    beforeinputEvents = [];
   1749    inputEvents = [];
   1750    dragEvents = [];
   1751    const onDrop = aEvent => {
   1752      dragEvents.push(aEvent);
   1753      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1754        `${description}: dataTransfer should have selected text as "text/plain"`);
   1755      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1756        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1757    };
   1758    document.addEventListener("drop", onDrop);
   1759    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
   1760    if (
   1761      await trySynthesizePlainDragAndDrop(
   1762        description,
   1763        {
   1764          srcSelection: selection,
   1765          destElement: otherContenteditable,
   1766        }
   1767      )
   1768    ) {
   1769      is(contenteditable.innerHTML, '<p><b>bd</b></p><div contenteditable="false"><p contenteditable=""><br></p></div>',
   1770        `${description}: dragged range should be removed from contenteditable`);
   1771      is(beforeinputEvents.length, 2,
   1772        `${description}: 2 "beforeinput" events should be fired on contenteditable and nested contenteditable`);
   1773      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   1774                      [{startContainer: selectionContainers[0], startOffset: 1,
   1775                        endContainer: selectionContainers[1], endOffset: 3}],
   1776                      description);
   1777      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
   1778                      [{type: "text/html", data: "<b>ol</b>"},
   1779                      {type: "text/plain", data: "ol"}],
   1780                      [{startContainer: otherContenteditable, startOffset: 0,
   1781                        endContainer: otherContenteditable, endOffset: 0}],
   1782                      description);
   1783      is(inputEvents.length, 1,
   1784        `${description}: only one "input" event should be fired on contenteditable`);
   1785      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
   1786      is(dragEvents.length, 1,
   1787        `${description}: only one "drop" event should be fired on contenteditable`);
   1788    }
   1789    document.removeEventListener("drop", onDrop);
   1790    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
   1791  })();
   1792 
   1793  // -------- Test copy-dragging contenteditable to nested contenteditable
   1794  await (async function test_copy_dragging_from_contenteditable_to_nested_contenteditable() {
   1795    const description = "copy-dragging text in contenteditable to nested contenteditable";
   1796    container.innerHTML = '<div contenteditable><p><b>bold</b></p><div contenteditable="false"><p contenteditable><br></p></div></div>';
   1797    const contenteditable = document.querySelector("div#container > div");
   1798    const b = document.querySelector("div#container > div > p > b");
   1799    const otherContenteditable = document.querySelector("div#container > div > div > p");
   1800    contenteditable.focus();
   1801    selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
   1802    beforeinputEvents = [];
   1803    inputEvents = [];
   1804    dragEvents = [];
   1805    const onDrop = aEvent => {
   1806      dragEvents.push(aEvent);
   1807      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   1808        `${description}: dataTransfer should have selected text as "text/plain"`);
   1809      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   1810        `${description}: dataTransfer should have selected nodes as "text/html"`);
   1811    };
   1812    document.addEventListener("drop", onDrop);
   1813    if (
   1814      await trySynthesizePlainDragAndDrop(
   1815        description,
   1816        {
   1817          srcSelection: selection,
   1818          destElement: otherContenteditable,
   1819          dragEvent: kModifiersToCopy,
   1820        }
   1821      )
   1822    ) {
   1823      is(contenteditable.innerHTML, '<p><b>bold</b></p><div contenteditable="false"><p contenteditable=""><b>ol</b></p></div>',
   1824        `${description}: dragged range should be moved from nested contenteditable to the contenteditable`);
   1825      is(beforeinputEvents.length, 1,
   1826        `${description}: only one "beforeinput" events should be fired on contenteditable`);
   1827      checkInputEvent(beforeinputEvents[0], otherContenteditable, "insertFromDrop", null,
   1828                      [{type: "text/html", data: "<b>ol</b>"},
   1829                      {type: "text/plain", data: "ol"}],
   1830                      [{startContainer: otherContenteditable, startOffset: 0,
   1831                        endContainer: otherContenteditable, endOffset: 0}],
   1832                      description);
   1833      is(inputEvents.length, 1,
   1834        `${description}: only one "input" events should be fired on contenteditable`);
   1835      checkInputEvent(inputEvents[0], otherContenteditable, "insertFromDrop", null,
   1836                      [{type: "text/html", data: "<b>ol</b>"},
   1837                      {type: "text/plain", data: "ol"}],
   1838                      [],
   1839                      description);
   1840      is(dragEvents.length, 1,
   1841        `${description}: only one "drop" event should be fired on contenteditable`);
   1842    }
   1843    document.removeEventListener("drop", onDrop);
   1844  })();
   1845 
   1846  // -------- Test dragging text in <input> to contenteditable
   1847  await (async function test_dragging_from_input_element_to_contenteditable() {
   1848    const description = "dragging text in <input> to contenteditable";
   1849    container.innerHTML = '<input value="Some Text"><div contenteditable><br></div>';
   1850    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   1851    const input = document.querySelector("div#container > input");
   1852    const contenteditable = document.querySelector("div#container > div");
   1853    input.setSelectionRange(3, 8);
   1854    beforeinputEvents = [];
   1855    inputEvents = [];
   1856    dragEvents = [];
   1857    const onDrop = aEvent => {
   1858      dragEvents.push(aEvent);
   1859      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   1860        `${description}: dataTransfer should have selected text as "text/plain"`);
   1861      is(aEvent.dataTransfer.getData("text/html"), "",
   1862        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   1863    };
   1864    document.addEventListener("drop", onDrop);
   1865    if (
   1866      await trySynthesizePlainDragAndDrop(
   1867        description,
   1868        {
   1869          srcSelection: SpecialPowers.wrap(input).editor.selection,
   1870          destElement: contenteditable,
   1871        }
   1872      )
   1873    ) {
   1874      is(input.value, "Somt",
   1875        `${description}: dragged range should be removed from <input>`);
   1876      is(contenteditable.innerHTML, "e Tex",
   1877        `${description}: dragged content should be inserted into contenteditable`);
   1878      is(beforeinputEvents.length, 2,
   1879        `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
   1880      checkInputEvent(beforeinputEvents[0], input, "deleteByDrag", null, null, [], description);
   1881      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
   1882                      [{type: "text/plain", data: "e Tex"}],
   1883                      [{startContainer: contenteditable, startOffset: 0,
   1884                        endContainer: contenteditable, endOffset: 0}],
   1885                      description);
   1886      is(inputEvents.length, 2,
   1887        `${description}: 2 "input" events should be fired on <input> and contenteditable`);
   1888      checkInputEvent(inputEvents[0], input, "deleteByDrag", null, null, [], description);
   1889      checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
   1890                      [{type: "text/plain", data: "e Tex"}],
   1891                      [],
   1892                      description);
   1893      is(dragEvents.length, 1,
   1894        `${description}: only one "drop" event should be fired on other contenteditable`);
   1895    }
   1896    document.removeEventListener("drop", onDrop);
   1897  })();
   1898 
   1899  // -------- Test dragging text in <input> to contenteditable (canceling "deleteByDrag")
   1900  await (async function test_dragging_from_input_element_to_contenteditable_and_canceling_delete_by_drag() {
   1901    const description = 'dragging text in <input> to contenteditable (canceling "deleteByDrag")';
   1902    container.innerHTML = '<input value="Some Text"><div contenteditable><br></div>';
   1903    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   1904    const input = document.querySelector("div#container > input");
   1905    const contenteditable = document.querySelector("div#container > div");
   1906    input.setSelectionRange(3, 8);
   1907    beforeinputEvents = [];
   1908    inputEvents = [];
   1909    dragEvents = [];
   1910    const onDrop = aEvent => {
   1911      dragEvents.push(aEvent);
   1912      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   1913        `${description}: dataTransfer should have selected text as "text/plain"`);
   1914      is(aEvent.dataTransfer.getData("text/html"), "",
   1915        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   1916    };
   1917    document.addEventListener("drop", onDrop);
   1918    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
   1919    if (
   1920      await trySynthesizePlainDragAndDrop(
   1921        description,
   1922        {
   1923          srcSelection: SpecialPowers.wrap(input).editor.selection,
   1924          destElement: contenteditable,
   1925        }
   1926      )
   1927    ) {
   1928      is(input.value, "Some Text",
   1929        `${description}: dragged range shouldn't be removed from <input>`);
   1930      is(contenteditable.innerHTML, "e Tex",
   1931        `${description}: dragged content should be inserted into contenteditable`);
   1932      is(beforeinputEvents.length, 2,
   1933        `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
   1934      checkInputEvent(beforeinputEvents[0], input, "deleteByDrag", null, null, [], description);
   1935      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
   1936                      [{type: "text/plain", data: "e Tex"}],
   1937                      [{startContainer: contenteditable, startOffset: 0,
   1938                        endContainer: contenteditable, endOffset: 0}],
   1939                      description);
   1940      is(inputEvents.length, 1,
   1941        `${description}: only one "input" events should be fired on contenteditable`);
   1942      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
   1943                      [{type: "text/plain", data: "e Tex"}],
   1944                      [],
   1945                      description);
   1946      is(dragEvents.length, 1,
   1947        `${description}: only one "drop" event should be fired on other contenteditable`);
   1948    }
   1949    document.removeEventListener("drop", onDrop);
   1950    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
   1951  })();
   1952 
   1953  // -------- Test dragging text in <input> to contenteditable (canceling "insertFromDrop")
   1954  await (async function test_dragging_from_input_element_to_contenteditable_and_canceling_insert_from_drop() {
   1955    const description = 'dragging text in <input> to contenteditable (canceling "insertFromDrop")';
   1956    container.innerHTML = '<input value="Some Text"><div contenteditable><br></div>';
   1957    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   1958    const input = document.querySelector("div#container > input");
   1959    const contenteditable = document.querySelector("div#container > div");
   1960    input.setSelectionRange(3, 8);
   1961    beforeinputEvents = [];
   1962    inputEvents = [];
   1963    dragEvents = [];
   1964    const onDrop = aEvent => {
   1965      dragEvents.push(aEvent);
   1966      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   1967        `${description}: dataTransfer should have selected text as "text/plain"`);
   1968      is(aEvent.dataTransfer.getData("text/html"), "",
   1969        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   1970    };
   1971    document.addEventListener("drop", onDrop);
   1972    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
   1973    if (
   1974      await trySynthesizePlainDragAndDrop(
   1975        description,
   1976        {
   1977          srcSelection: SpecialPowers.wrap(input).editor.selection,
   1978          destElement: contenteditable,
   1979        }
   1980      )
   1981    ) {
   1982      is(input.value, "Somt",
   1983        `${description}: dragged range should be removed from <input>`);
   1984      is(contenteditable.innerHTML, "<br>",
   1985        `${description}: dragged content shouldn't be inserted into contenteditable`);
   1986      is(beforeinputEvents.length, 2,
   1987        `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
   1988      checkInputEvent(beforeinputEvents[0], input, "deleteByDrag", null, null, [], description);
   1989      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
   1990                      [{type: "text/plain", data: "e Tex"}],
   1991                      [{startContainer: contenteditable, startOffset: 0,
   1992                        endContainer: contenteditable, endOffset: 0}],
   1993                      description);
   1994      is(inputEvents.length, 1,
   1995        `${description}: only one "input" event should be fired on <input>`);
   1996      checkInputEvent(inputEvents[0], input, "deleteByDrag", null, null, [], description);
   1997      is(dragEvents.length, 1,
   1998        `${description}: only one "drop" event should be fired on other contenteditable`);
   1999    }
   2000    document.removeEventListener("drop", onDrop);
   2001    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
   2002  })();
   2003 
   2004  // -------- Test copy-dragging text in <input> to contenteditable
   2005  await (async function test_copy_dragging_from_input_element_to_contenteditable() {
   2006    const description = "copy-dragging text in <input> to contenteditable";
   2007    container.innerHTML = '<input value="Some Text"><div contenteditable><br></div>';
   2008    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2009    const input = document.querySelector("div#container > input");
   2010    const contenteditable = document.querySelector("div#container > div");
   2011    input.setSelectionRange(3, 8);
   2012    beforeinputEvents = [];
   2013    inputEvents = [];
   2014    dragEvents = [];
   2015    const onDrop = aEvent => {
   2016      dragEvents.push(aEvent);
   2017      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   2018        `${description}: dataTransfer should have selected text as "text/plain"`);
   2019      is(aEvent.dataTransfer.getData("text/html"), "",
   2020        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2021    };
   2022    document.addEventListener("drop", onDrop);
   2023    if (
   2024      await trySynthesizePlainDragAndDrop(
   2025        description,
   2026        {
   2027          srcSelection: SpecialPowers.wrap(input).editor.selection,
   2028          destElement: contenteditable,
   2029          dragEvent: kModifiersToCopy,
   2030        }
   2031      )
   2032    ) {
   2033      is(input.value, "Some Text",
   2034        `${description}: dragged range shouldn't be removed from <input>`);
   2035      is(contenteditable.innerHTML, "e Tex",
   2036        `${description}: dragged content should be inserted into contenteditable`);
   2037      is(beforeinputEvents.length, 1,
   2038        `${description}: only one "beforeinput" events should be fired on contenteditable`);
   2039      checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
   2040                      [{type: "text/plain", data: "e Tex"}],
   2041                      [{startContainer: contenteditable, startOffset: 0,
   2042                        endContainer: contenteditable, endOffset: 0}],
   2043                      description);
   2044      is(inputEvents.length, 1,
   2045        `${description}: only one "input" events should be fired on contenteditable`);
   2046      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
   2047                      [{type: "text/plain", data: "e Tex"}],
   2048                      [],
   2049                      description);
   2050      is(dragEvents.length, 1,
   2051        `${description}: only one "drop" event should be fired on other contenteditable`);
   2052    }
   2053    document.removeEventListener("drop", onDrop);
   2054  })();
   2055 
   2056  // -------- Test dragging text in <textarea> to contenteditable
   2057  await (async function test_dragging_from_textarea_element_to_contenteditable() {
   2058    const description = "dragging text in <textarea> to contenteditable";
   2059    container.innerHTML = '<textarea>Line1\nLine2</textarea><div contenteditable><br></div>';
   2060    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2061    const textarea = document.querySelector("div#container > textarea");
   2062    const contenteditable = document.querySelector("div#container > div");
   2063    textarea.setSelectionRange(3, 8);
   2064    beforeinputEvents = [];
   2065    inputEvents = [];
   2066    dragEvents = [];
   2067    const onDrop = aEvent => {
   2068      dragEvents.push(aEvent);
   2069      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
   2070        `${description}: dataTransfer should have selected text as "text/plain"`);
   2071      is(aEvent.dataTransfer.getData("text/html"), "",
   2072        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2073    };
   2074    document.addEventListener("drop", onDrop);
   2075    if (
   2076      await trySynthesizePlainDragAndDrop(
   2077        description,
   2078        {
   2079          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   2080          destElement: contenteditable,
   2081        }
   2082      )
   2083    ) {
   2084      is(textarea.value, "Linne2",
   2085        `${description}: dragged range should be removed from <textarea>`);
   2086      is(contenteditable.innerHTML, "e1<br>Li",
   2087        `${description}: dragged content should be inserted into contenteditable`);
   2088      is(beforeinputEvents.length, 2,
   2089        `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
   2090      checkInputEvent(beforeinputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2091      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
   2092                      [{type: "text/plain", data: `e1${kNativeLF}Li`}],
   2093                      [{startContainer: contenteditable, startOffset: 0,
   2094                        endContainer: contenteditable, endOffset: 0}],
   2095                      description);
   2096      is(inputEvents.length, 2,
   2097        `${description}: 2 "input" events should be fired on <input> and contenteditable`);
   2098      checkInputEvent(inputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2099      checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
   2100                      [{type: "text/plain", data: `e1${kNativeLF}Li`}],
   2101                      [],
   2102                      description);
   2103      is(dragEvents.length, 1,
   2104        `${description}: only one "drop" event should be fired on other contenteditable`);
   2105    }
   2106    document.removeEventListener("drop", onDrop);
   2107  })();
   2108 
   2109  // -------- Test copy-dragging text in <textarea> to contenteditable
   2110  await (async function test_copy_dragging_from_textarea_element_to_contenteditable() {
   2111    const description = "copy-dragging text in <textarea> to contenteditable";
   2112    container.innerHTML = '<textarea>Line1\nLine2</textarea><div contenteditable><br></div>';
   2113    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2114    const textarea = document.querySelector("div#container > textarea");
   2115    const contenteditable = document.querySelector("div#container > div");
   2116    textarea.setSelectionRange(3, 8);
   2117    beforeinputEvents = [];
   2118    inputEvents = [];
   2119    dragEvents = [];
   2120    const onDrop = aEvent => {
   2121      dragEvents.push(aEvent);
   2122      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
   2123        `${description}: dataTransfer should have selected text as "text/plain"`);
   2124      is(aEvent.dataTransfer.getData("text/html"), "",
   2125        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2126    };
   2127    document.addEventListener("drop", onDrop);
   2128    if (
   2129      await trySynthesizePlainDragAndDrop(
   2130        description,
   2131        {
   2132          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   2133          destElement: contenteditable,
   2134          dragEvent: kModifiersToCopy,
   2135        }
   2136      )
   2137    ) {
   2138      is(textarea.value, "Line1\nLine2",
   2139        `${description}: dragged range should be removed from <textarea>`);
   2140      is(contenteditable.innerHTML, "e1<br>Li",
   2141        `${description}: dragged content should be inserted into contenteditable`);
   2142      is(beforeinputEvents.length, 1,
   2143        `${description}: only one "beforeinput" events should be fired on contenteditable`);
   2144      checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
   2145                      [{type: "text/plain", data: `e1${kNativeLF}Li`}],
   2146                      [{startContainer: contenteditable, startOffset: 0,
   2147                        endContainer: contenteditable, endOffset: 0}],
   2148                      description);
   2149      is(inputEvents.length, 1,
   2150        `${description}: only one "input" events should be fired on contenteditable`);
   2151      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
   2152                      [{type: "text/plain", data: `e1${kNativeLF}Li`}],
   2153                      [],
   2154                      description);
   2155      is(dragEvents.length, 1,
   2156        `${description}: only one "drop" event should be fired on other contenteditable`);
   2157    }
   2158    document.removeEventListener("drop", onDrop);
   2159  })();
   2160 
   2161  // -------- Test dragging text in <input> to other <input>
   2162  await (async function test_dragging_from_input_element_to_other_input_element() {
   2163    const description = "dragging text in <input> to other <input>";
   2164    container.innerHTML = '<input value="Some Text"><input>';
   2165    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2166    const input = document.querySelector("div#container > input");
   2167    const otherInput = document.querySelector("div#container > input + input");
   2168    input.setSelectionRange(3, 8);
   2169    beforeinputEvents = [];
   2170    inputEvents = [];
   2171    dragEvents = [];
   2172    const onDrop = aEvent => {
   2173      dragEvents.push(aEvent);
   2174      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   2175        `${description}: dataTransfer should have selected text as "text/plain"`);
   2176      is(aEvent.dataTransfer.getData("text/html"), "",
   2177        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2178    };
   2179    document.addEventListener("drop", onDrop);
   2180    if (
   2181      await trySynthesizePlainDragAndDrop(
   2182        description,
   2183        {
   2184          srcSelection: SpecialPowers.wrap(input).editor.selection,
   2185          destElement: otherInput,
   2186        }
   2187      )
   2188    ) {
   2189      is(input.value, "Somt",
   2190        `${description}: dragged range should be removed from <input>`);
   2191      is(otherInput.value, "e Tex",
   2192        `${description}: dragged content should be inserted into other <input>`);
   2193      is(beforeinputEvents.length, 2,
   2194        `${description}: 2 "beforeinput" events should be fired on <input> and other <input>`);
   2195      checkInputEvent(beforeinputEvents[0], input, "deleteByDrag", null, null, [], description);
   2196      checkInputEvent(beforeinputEvents[1], otherInput, "insertFromDrop", "e Tex", null, [], description);
   2197      is(inputEvents.length, 2,
   2198        `${description}: 2 "input" events should be fired on <input> and other <input>`);
   2199      checkInputEvent(inputEvents[0], input, "deleteByDrag", null, null, [], description);
   2200      checkInputEvent(inputEvents[1], otherInput, "insertFromDrop", "e Tex", null, [], description);
   2201      is(dragEvents.length, 1,
   2202        `${description}: only one "drop" event should be fired on other <input>`);
   2203    }
   2204    document.removeEventListener("drop", onDrop);
   2205  })();
   2206 
   2207  // -------- Test dragging text in <input> to other <input> (canceling "deleteByDrag")
   2208  await (async function test_dragging_from_input_element_to_other_input_element_and_canceling_delete_by_drag() {
   2209    const description = 'dragging text in <input> to other <input> (canceling "deleteByDrag")';
   2210    container.innerHTML = '<input value="Some Text"><input>';
   2211    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2212    const input = document.querySelector("div#container > input");
   2213    const otherInput = document.querySelector("div#container > input + input");
   2214    input.setSelectionRange(3, 8);
   2215    beforeinputEvents = [];
   2216    inputEvents = [];
   2217    dragEvents = [];
   2218    const onDrop = aEvent => {
   2219      dragEvents.push(aEvent);
   2220      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   2221        `${description}: dataTransfer should have selected text as "text/plain"`);
   2222      is(aEvent.dataTransfer.getData("text/html"), "",
   2223        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2224    };
   2225    document.addEventListener("drop", onDrop);
   2226    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
   2227    if (
   2228      await trySynthesizePlainDragAndDrop(
   2229        description,
   2230        {
   2231          srcSelection: SpecialPowers.wrap(input).editor.selection,
   2232          destElement: otherInput,
   2233        }
   2234      )
   2235    ) {
   2236      is(input.value, "Some Text",
   2237        `${description}: dragged range shouldn't be removed from <input>`);
   2238      is(otherInput.value, "e Tex",
   2239        `${description}: dragged content should be inserted into other <input>`);
   2240      is(beforeinputEvents.length, 2,
   2241        `${description}: 2 "beforeinput" events should be fired on <input> and other <input>`);
   2242      checkInputEvent(beforeinputEvents[0], input, "deleteByDrag", null, null, [], description);
   2243      checkInputEvent(beforeinputEvents[1], otherInput, "insertFromDrop", "e Tex", null, [], description);
   2244      is(inputEvents.length, 1,
   2245        `${description}: only one "input" events should be fired on other <input>`);
   2246      checkInputEvent(inputEvents[0], otherInput, "insertFromDrop", "e Tex", null, [], description);
   2247      is(dragEvents.length, 1,
   2248        `${description}: only one "drop" event should be fired on other <input>`);
   2249    }
   2250    document.removeEventListener("drop", onDrop);
   2251    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
   2252  })();
   2253 
   2254  // -------- Test dragging text in <input> to other <input> (canceling "insertFromDrop")
   2255  await (async function test_dragging_from_input_element_to_other_input_element_and_canceling_insert_from_drop() {
   2256    const description = 'dragging text in <input> to other <input> (canceling "insertFromDrop")';
   2257    container.innerHTML = '<input value="Some Text"><input>';
   2258    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2259    const input = document.querySelector("div#container > input");
   2260    const otherInput = document.querySelector("div#container > input + input");
   2261    input.setSelectionRange(3, 8);
   2262    beforeinputEvents = [];
   2263    inputEvents = [];
   2264    dragEvents = [];
   2265    const onDrop = aEvent => {
   2266      dragEvents.push(aEvent);
   2267      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   2268        `${description}: dataTransfer should have selected text as "text/plain"`);
   2269      is(aEvent.dataTransfer.getData("text/html"), "",
   2270        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2271    };
   2272    document.addEventListener("drop", onDrop);
   2273    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
   2274    if (
   2275      await trySynthesizePlainDragAndDrop(
   2276        description,
   2277        {
   2278          srcSelection: SpecialPowers.wrap(input).editor.selection,
   2279          destElement: otherInput,
   2280        },
   2281      )
   2282    ) {
   2283      is(input.value, "Somt",
   2284        `${description}: dragged range should be removed from <input>`);
   2285      is(otherInput.value, "",
   2286        `${description}: dragged content shouldn't be inserted into other <input>`);
   2287      is(beforeinputEvents.length, 2,
   2288        `${description}: 2 "beforeinput" events should be fired on <input> and other <input>`);
   2289      checkInputEvent(beforeinputEvents[0], input, "deleteByDrag", null, null, [], description);
   2290      checkInputEvent(beforeinputEvents[1], otherInput, "insertFromDrop", "e Tex", null, [], description);
   2291      is(inputEvents.length, 1,
   2292        `${description}: only one "input" event should be fired on <input>`);
   2293      checkInputEvent(inputEvents[0], input, "deleteByDrag", null, null, [], description);
   2294      is(dragEvents.length, 1,
   2295        `${description}: only one "drop" event should be fired on other <input>`);
   2296    }
   2297    document.removeEventListener("drop", onDrop);
   2298    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
   2299  })();
   2300 
   2301  // -------- Test copy-dragging text in <input> to other <input>
   2302  await (async function test_copy_dragging_from_input_element_to_other_input_element() {
   2303    const description = "copy-dragging text in <input> to other <input>";
   2304    container.innerHTML = '<input value="Some Text"><input>';
   2305    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2306    const input = document.querySelector("div#container > input");
   2307    const otherInput = document.querySelector("div#container > input + input");
   2308    input.setSelectionRange(3, 8);
   2309    beforeinputEvents = [];
   2310    inputEvents = [];
   2311    dragEvents = [];
   2312    const onDrop = aEvent => {
   2313      dragEvents.push(aEvent);
   2314      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   2315        `${description}: dataTransfer should have selected text as "text/plain"`);
   2316      is(aEvent.dataTransfer.getData("text/html"), "",
   2317        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2318    };
   2319    document.addEventListener("drop", onDrop);
   2320    if (
   2321      await trySynthesizePlainDragAndDrop(
   2322        description,
   2323        {
   2324          srcSelection: SpecialPowers.wrap(input).editor.selection,
   2325          destElement: otherInput,
   2326          dragEvent: kModifiersToCopy,
   2327        }
   2328      )
   2329    ) {
   2330      is(input.value, "Some Text",
   2331        `${description}: dragged range shouldn't be removed from <input>`);
   2332      is(otherInput.value, "e Tex",
   2333        `${description}: dragged content should be inserted into other <input>`);
   2334      is(beforeinputEvents.length, 1,
   2335        `${description}: only one "beforeinput" events should be fired on  other <input>`);
   2336      checkInputEvent(beforeinputEvents[0], otherInput, "insertFromDrop", "e Tex", null, [], description);
   2337      is(inputEvents.length, 1,
   2338        `${description}: only one "input" events should be fired on  other <input>`);
   2339      checkInputEvent(inputEvents[0], otherInput, "insertFromDrop", "e Tex", null, [], description);
   2340      is(dragEvents.length, 1,
   2341        `${description}: only one "drop" event should be fired on other <input>`);
   2342    }
   2343    document.removeEventListener("drop", onDrop);
   2344  })();
   2345 
   2346  // -------- Test dragging text in <input> to <textarea>
   2347  await (async function test_dragging_from_input_element_to_textarea_element() {
   2348    const description = "dragging text in <input> to other <textarea>";
   2349    container.innerHTML = '<input value="Some Text"><textarea></textarea>';
   2350    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2351    const input = document.querySelector("div#container > input");
   2352    const textarea = document.querySelector("div#container > textarea");
   2353    input.setSelectionRange(3, 8);
   2354    beforeinputEvents = [];
   2355    inputEvents = [];
   2356    dragEvents = [];
   2357    const onDrop = aEvent => {
   2358      dragEvents.push(aEvent);
   2359      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   2360        `${description}: dataTransfer should have selected text as "text/plain"`);
   2361      is(aEvent.dataTransfer.getData("text/html"), "",
   2362        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2363    };
   2364    document.addEventListener("drop", onDrop);
   2365    if (
   2366      await trySynthesizePlainDragAndDrop(
   2367        description,
   2368        {
   2369          srcSelection: SpecialPowers.wrap(input).editor.selection,
   2370          destElement: textarea,
   2371        }
   2372      )
   2373    ) {
   2374      is(input.value, "Somt",
   2375        `${description}: dragged range should be removed from <input>`);
   2376      is(textarea.value, "e Tex",
   2377        `${description}: dragged content should be inserted into <textarea>`);
   2378      is(beforeinputEvents.length, 2,
   2379        `${description}: 2 "beforeinput" events should be fired on <input> and <textarea>`);
   2380      checkInputEvent(beforeinputEvents[0], input, "deleteByDrag", null, null, [], description);
   2381      checkInputEvent(beforeinputEvents[1], textarea, "insertFromDrop", "e Tex", null, [], description);
   2382      is(inputEvents.length, 2,
   2383        `${description}: 2 "input" events should be fired on <input> and <textarea>`);
   2384      checkInputEvent(inputEvents[0], input, "deleteByDrag", null, null, [], description);
   2385      checkInputEvent(inputEvents[1], textarea, "insertFromDrop", "e Tex", null, [], description);
   2386      is(dragEvents.length, 1,
   2387        `${description}: only one "drop" event should be fired on <textarea>`);
   2388    }
   2389    document.removeEventListener("drop", onDrop);
   2390  })();
   2391 
   2392  // -------- Test dragging text in <input> to <textarea> (canceling "deleteByDrag")
   2393  await (async function test_dragging_from_input_element_to_textarea_element_and_canceling_delete_by_drag() {
   2394    const description = 'dragging text in <input> to other <textarea> (canceling "deleteByDrag")';
   2395    container.innerHTML = '<input value="Some Text"><textarea></textarea>';
   2396    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2397    const input = document.querySelector("div#container > input");
   2398    const textarea = document.querySelector("div#container > textarea");
   2399    input.setSelectionRange(3, 8);
   2400    beforeinputEvents = [];
   2401    inputEvents = [];
   2402    dragEvents = [];
   2403    const onDrop = aEvent => {
   2404      dragEvents.push(aEvent);
   2405      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   2406        `${description}: dataTransfer should have selected text as "text/plain"`);
   2407      is(aEvent.dataTransfer.getData("text/html"), "",
   2408        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2409    };
   2410    document.addEventListener("drop", onDrop);
   2411    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
   2412    if (
   2413      await trySynthesizePlainDragAndDrop(
   2414        description,
   2415        {
   2416          srcSelection: SpecialPowers.wrap(input).editor.selection,
   2417          destElement: textarea,
   2418        }
   2419      )
   2420    ) {
   2421      is(input.value, "Some Text",
   2422        `${description}: dragged range shouldn't be removed from <input>`);
   2423      is(textarea.value, "e Tex",
   2424        `${description}: dragged content should be inserted into <textarea>`);
   2425      is(beforeinputEvents.length, 2,
   2426        `${description}: 2 "beforeinput" events should be fired on <input> and <textarea>`);
   2427      checkInputEvent(beforeinputEvents[0], input, "deleteByDrag", null, null, [], description);
   2428      checkInputEvent(beforeinputEvents[1], textarea, "insertFromDrop", "e Tex", null, [], description);
   2429      is(inputEvents.length, 1,
   2430        `${description}: only one "input" event should be fired on <textarea>`);
   2431      checkInputEvent(inputEvents[0], textarea, "insertFromDrop", "e Tex", null, [], description);
   2432      is(dragEvents.length, 1,
   2433        `${description}: only one "drop" event should be fired on <textarea>`);
   2434    }
   2435    document.removeEventListener("drop", onDrop);
   2436    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
   2437  })();
   2438 
   2439  // -------- Test dragging text in <input> to <textarea> (canceling "insertFromDrop")
   2440  await (async function test_dragging_from_input_element_to_textarea_element_and_canceling_insert_from_drop() {
   2441    const description = 'dragging text in <input> to other <textarea> (canceling "insertFromDrop")';
   2442    container.innerHTML = '<input value="Some Text"><textarea></textarea>';
   2443    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2444    const input = document.querySelector("div#container > input");
   2445    const textarea = document.querySelector("div#container > textarea");
   2446    input.setSelectionRange(3, 8);
   2447    beforeinputEvents = [];
   2448    inputEvents = [];
   2449    dragEvents = [];
   2450    const onDrop = aEvent => {
   2451      dragEvents.push(aEvent);
   2452      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   2453        `${description}: dataTransfer should have selected text as "text/plain"`);
   2454      is(aEvent.dataTransfer.getData("text/html"), "",
   2455        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2456    };
   2457    document.addEventListener("drop", onDrop);
   2458    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
   2459    if (
   2460      await trySynthesizePlainDragAndDrop(
   2461        description,
   2462        {
   2463          srcSelection: SpecialPowers.wrap(input).editor.selection,
   2464          destElement: textarea,
   2465        }
   2466      )
   2467    ) {
   2468      is(input.value, "Somt",
   2469        `${description}: dragged range should be removed from <input>`);
   2470      is(textarea.value, "",
   2471        `${description}: dragged content shouldn't be inserted into <textarea>`);
   2472      is(beforeinputEvents.length, 2,
   2473        `${description}: 2 "beforeinput" events should be fired on <input> and <textarea>`);
   2474      checkInputEvent(beforeinputEvents[0], input, "deleteByDrag", null, null, [], description);
   2475      checkInputEvent(beforeinputEvents[1], textarea, "insertFromDrop", "e Tex", null, [], description);
   2476      is(inputEvents.length, 1,
   2477        `${description}: only one "input" event should be fired on <input>`);
   2478      checkInputEvent(inputEvents[0], input, "deleteByDrag", null, null, [], description);
   2479      is(dragEvents.length, 1,
   2480        `${description}: only one "drop" event should be fired on <textarea>`);
   2481    }
   2482    document.removeEventListener("drop", onDrop);
   2483    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
   2484  })();
   2485 
   2486  // -------- Test copy-dragging text in <input> to <textarea>
   2487  await (async function test_copy_dragging_from_input_element_to_textarea_element() {
   2488    const description = "copy-dragging text in <input> to <textarea>";
   2489    container.innerHTML = '<input value="Some Text"><textarea></textarea>';
   2490    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2491    const input = document.querySelector("div#container > input");
   2492    const textarea = document.querySelector("div#container > textarea");
   2493    input.setSelectionRange(3, 8);
   2494    beforeinputEvents = [];
   2495    inputEvents = [];
   2496    dragEvents = [];
   2497    const onDrop = aEvent => {
   2498      dragEvents.push(aEvent);
   2499      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
   2500        `${description}: dataTransfer should have selected text as "text/plain"`);
   2501      is(aEvent.dataTransfer.getData("text/html"), "",
   2502        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2503    };
   2504    document.addEventListener("drop", onDrop);
   2505    if (
   2506      await trySynthesizePlainDragAndDrop(
   2507        description,
   2508        {
   2509          srcSelection: SpecialPowers.wrap(input).editor.selection,
   2510          destElement: textarea,
   2511          dragEvent: kModifiersToCopy,
   2512        }
   2513      )
   2514    ) {
   2515      is(input.value, "Some Text",
   2516        `${description}: dragged range shouldn't be removed from <input>`);
   2517      is(textarea.value, "e Tex",
   2518        `${description}: dragged content should be inserted into <textarea>`);
   2519      is(beforeinputEvents.length, 1,
   2520        `${description}: only one "beforeinput" events should be fired on  <textarea>`);
   2521      checkInputEvent(beforeinputEvents[0], textarea, "insertFromDrop", "e Tex", null, [], description);
   2522      is(inputEvents.length, 1,
   2523        `${description}: only one "input" events should be fired on  <textarea>`);
   2524      checkInputEvent(inputEvents[0], textarea, "insertFromDrop", "e Tex", null, [], description);
   2525      is(dragEvents.length, 1,
   2526        `${description}: only one "drop" event should be fired on <textarea>`);
   2527    }
   2528    document.removeEventListener("drop", onDrop);
   2529  })();
   2530 
   2531  // -------- Test dragging text in <textarea> to <input>
   2532  await (async function test_dragging_from_textarea_element_to_input_element() {
   2533    const description = "dragging text in <textarea> to <input>";
   2534    container.innerHTML = "<textarea>Line1\nLine2</textarea><input>";
   2535    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2536    const textarea = document.querySelector("div#container > textarea");
   2537    const input = document.querySelector("div#container > input");
   2538    textarea.setSelectionRange(3, 8);
   2539    beforeinputEvents = [];
   2540    inputEvents = [];
   2541    dragEvents = [];
   2542    const onDrop = aEvent => {
   2543      dragEvents.push(aEvent);
   2544      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
   2545        `${description}: dataTransfer should have selected text as "text/plain"`);
   2546      is(aEvent.dataTransfer.getData("text/html"), "",
   2547        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2548    };
   2549    document.addEventListener("drop", onDrop);
   2550    if (
   2551      await trySynthesizePlainDragAndDrop(
   2552        description,
   2553        {
   2554          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   2555          destElement: input,
   2556        }
   2557      )
   2558    ) {
   2559      is(textarea.value, "Linne2",
   2560        `${description}: dragged range should be removed from <textarea>`);
   2561      is(input.value, "e1 Li",
   2562        `${description}: dragged content should be inserted into <input>`);
   2563      is(beforeinputEvents.length, 2,
   2564        `${description}: 2 "beforeinput" events should be fired on <textarea> and <input>`);
   2565      checkInputEvent(beforeinputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2566      checkInputEvent(beforeinputEvents[1], input, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2567      is(inputEvents.length, 2,
   2568        `${description}: 2 "input" events should be fired on <textarea> and <input>`);
   2569      checkInputEvent(inputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2570      checkInputEvent(inputEvents[1], input, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2571      is(dragEvents.length, 1,
   2572        `${description}: only one "drop" event should be fired on <textarea>`);
   2573    }
   2574    document.removeEventListener("drop", onDrop);
   2575  })();
   2576 
   2577  // -------- Test dragging text in <textarea> to <input> (canceling "deleteByDrag")
   2578  await (async function test_dragging_from_textarea_element_to_input_element_and_delete_by_drag() {
   2579    const description = 'dragging text in <textarea> to <input> (canceling "deleteByDrag")';
   2580    container.innerHTML = "<textarea>Line1\nLine2</textarea><input>";
   2581    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2582    const textarea = document.querySelector("div#container > textarea");
   2583    const input = document.querySelector("div#container > input");
   2584    textarea.setSelectionRange(3, 8);
   2585    beforeinputEvents = [];
   2586    inputEvents = [];
   2587    dragEvents = [];
   2588    const onDrop = aEvent => {
   2589      dragEvents.push(aEvent);
   2590      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
   2591        `${description}: dataTransfer should have selected text as "text/plain"`);
   2592      is(aEvent.dataTransfer.getData("text/html"), "",
   2593        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2594    };
   2595    document.addEventListener("drop", onDrop);
   2596    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
   2597    if (
   2598      await trySynthesizePlainDragAndDrop(
   2599        description,
   2600        {
   2601          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   2602          destElement: input,
   2603        }
   2604      )
   2605    ) {
   2606      is(textarea.value, "Line1\nLine2",
   2607        `${description}: dragged range shouldn't be removed from <textarea>`);
   2608      is(input.value, "e1 Li",
   2609        `${description}: dragged content should be inserted into <input>`);
   2610      is(beforeinputEvents.length, 2,
   2611        `${description}: 2 "beforeinput" events should be fired on <textarea> and <input>`);
   2612      checkInputEvent(beforeinputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2613      checkInputEvent(beforeinputEvents[1], input, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2614      is(inputEvents.length, 1,
   2615        `${description}: only one "input" event should be fired on <input>`);
   2616      checkInputEvent(inputEvents[0], input, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2617      is(dragEvents.length, 1,
   2618        `${description}: only one "drop" event should be fired on <textarea>`);
   2619    }
   2620    document.removeEventListener("drop", onDrop);
   2621    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
   2622  })();
   2623 
   2624  // -------- Test dragging text in <textarea> to <input> (canceling "insertFromDrop")
   2625  await (async function test_dragging_from_textarea_element_to_input_element_and_canceling_insert_from_drop() {
   2626    const description = 'dragging text in <textarea> to <input> (canceling "insertFromDrop")';
   2627    container.innerHTML = "<textarea>Line1\nLine2</textarea><input>";
   2628    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2629    const textarea = document.querySelector("div#container > textarea");
   2630    const input = document.querySelector("div#container > input");
   2631    textarea.setSelectionRange(3, 8);
   2632    beforeinputEvents = [];
   2633    inputEvents = [];
   2634    dragEvents = [];
   2635    const onDrop = aEvent => {
   2636      dragEvents.push(aEvent);
   2637      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
   2638        `${description}: dataTransfer should have selected text as "text/plain"`);
   2639      is(aEvent.dataTransfer.getData("text/html"), "",
   2640        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2641    };
   2642    document.addEventListener("drop", onDrop);
   2643    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
   2644    if (
   2645      await trySynthesizePlainDragAndDrop(
   2646        description,
   2647        {
   2648          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   2649          destElement: input,
   2650        }
   2651      )
   2652    ) {
   2653      is(textarea.value, "Linne2",
   2654        `${description}: dragged range should be removed from <textarea>`);
   2655      is(input.value, "",
   2656        `${description}: dragged content shouldn't be inserted into <input>`);
   2657      is(beforeinputEvents.length, 2,
   2658        `${description}: 2 "beforeinput" events should be fired on <textarea> and <input>`);
   2659      checkInputEvent(beforeinputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2660      checkInputEvent(beforeinputEvents[1], input, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2661      is(inputEvents.length, 1,
   2662        `${description}: only one "input" event should be fired on <textarea>`);
   2663      checkInputEvent(inputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2664      is(dragEvents.length, 1,
   2665        `${description}: only one "drop" event should be fired on <textarea>`);
   2666    }
   2667    document.removeEventListener("drop", onDrop);
   2668    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
   2669  })();
   2670 
   2671  // -------- Test copy-dragging text in <textarea> to <input>
   2672  await (async function test_copy_dragging_from_textarea_element_to_input_element() {
   2673    const description = "copy-dragging text in <textarea> to <input>";
   2674    container.innerHTML = "<textarea>Line1\nLine2</textarea><input>";
   2675    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2676    const textarea = document.querySelector("div#container > textarea");
   2677    const input = document.querySelector("div#container > input");
   2678    textarea.setSelectionRange(3, 8);
   2679    beforeinputEvents = [];
   2680    inputEvents = [];
   2681    dragEvents = [];
   2682    const onDrop = aEvent => {
   2683      dragEvents.push(aEvent);
   2684      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
   2685        `${description}: dataTransfer should have selected text as "text/plain"`);
   2686      is(aEvent.dataTransfer.getData("text/html"), "",
   2687        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2688    };
   2689    document.addEventListener("drop", onDrop);
   2690    if (
   2691      await trySynthesizePlainDragAndDrop(
   2692        description,
   2693        {
   2694          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   2695          destElement: input,
   2696          dragEvent: kModifiersToCopy,
   2697        }
   2698      )
   2699    ) {
   2700      is(textarea.value, "Line1\nLine2",
   2701        `${description}: dragged range shouldn't be removed from <textarea>`);
   2702      is(input.value, "e1 Li",
   2703        `${description}: dragged content should be inserted into <input>`);
   2704      is(beforeinputEvents.length, 1,
   2705        `${description}: only one "beforeinput" events should be fired on <input>`);
   2706      checkInputEvent(beforeinputEvents[0], input, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2707      is(inputEvents.length, 1,
   2708        `${description}: only one "input" events should be fired on <input>`);
   2709      checkInputEvent(inputEvents[0], input, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2710      is(dragEvents.length, 1,
   2711        `${description}: only one "drop" event should be fired on <textarea>`);
   2712    }
   2713    document.removeEventListener("drop", onDrop);
   2714  })();
   2715 
   2716  // -------- Test dragging text in <textarea> to other <textarea>
   2717  await (async function test_dragging_from_textarea_element_to_other_textarea_element() {
   2718    const description = "dragging text in <textarea> to other <textarea>";
   2719    container.innerHTML = "<textarea>Line1\nLine2</textarea><textarea></textarea>";
   2720    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2721    const textarea = document.querySelector("div#container > textarea");
   2722    const otherTextarea = document.querySelector("div#container > textarea + textarea");
   2723    textarea.setSelectionRange(3, 8);
   2724    beforeinputEvents = [];
   2725    inputEvents = [];
   2726    dragEvents = [];
   2727    const onDrop = aEvent => {
   2728      dragEvents.push(aEvent);
   2729      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
   2730        `${description}: dataTransfer should have selected text as "text/plain"`);
   2731      is(aEvent.dataTransfer.getData("text/html"), "",
   2732        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2733    };
   2734    document.addEventListener("drop", onDrop);
   2735    if (
   2736      await trySynthesizePlainDragAndDrop(
   2737        description,
   2738        {
   2739          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   2740          destElement: otherTextarea,
   2741        }
   2742      )
   2743    ) {
   2744      is(textarea.value, "Linne2",
   2745        `${description}: dragged range should be removed from <textarea>`);
   2746      is(otherTextarea.value, "e1\nLi",
   2747        `${description}: dragged content should be inserted into other <textarea>`);
   2748      is(beforeinputEvents.length, 2,
   2749        `${description}: 2 "beforeinput" events should be fired on <textarea> and other <textarea>`);
   2750      checkInputEvent(beforeinputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2751      checkInputEvent(beforeinputEvents[1], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2752      is(inputEvents.length, 2,
   2753        `${description}: 2 "input" events should be fired on <textarea> and other <textarea>`);
   2754      checkInputEvent(inputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2755      checkInputEvent(inputEvents[1], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2756      is(dragEvents.length, 1,
   2757        `${description}: only one "drop" event should be fired on <textarea>`);
   2758    }
   2759    document.removeEventListener("drop", onDrop);
   2760  })();
   2761 
   2762  // -------- Test dragging text in <textarea> to other <textarea> (canceling "deleteByDrag")
   2763  await (async function test_dragging_from_textarea_element_to_other_textarea_element_and_canceling_delete_by_drag() {
   2764    const description = 'dragging text in <textarea> to other <textarea> (canceling "deleteByDrag")';
   2765    container.innerHTML = "<textarea>Line1\nLine2</textarea><textarea></textarea>";
   2766    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2767    const textarea = document.querySelector("div#container > textarea");
   2768    const otherTextarea = document.querySelector("div#container > textarea + textarea");
   2769    textarea.setSelectionRange(3, 8);
   2770    beforeinputEvents = [];
   2771    inputEvents = [];
   2772    dragEvents = [];
   2773    const onDrop = aEvent => {
   2774      dragEvents.push(aEvent);
   2775      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
   2776        `${description}: dataTransfer should have selected text as "text/plain"`);
   2777      is(aEvent.dataTransfer.getData("text/html"), "",
   2778        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2779    };
   2780    document.addEventListener("drop", onDrop);
   2781    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
   2782    if (
   2783      await trySynthesizePlainDragAndDrop(
   2784        description,
   2785        {
   2786          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   2787          destElement: otherTextarea,
   2788        }
   2789      )
   2790    ) {
   2791      is(textarea.value, "Line1\nLine2",
   2792        `${description}: dragged range shouldn't be removed from <textarea>`);
   2793      is(otherTextarea.value, "e1\nLi",
   2794        `${description}: dragged content should be inserted into other <textarea>`);
   2795      is(beforeinputEvents.length, 2,
   2796        `${description}: 2 "beforeinput" events should be fired on <textarea> and other <textarea>`);
   2797      checkInputEvent(beforeinputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2798      checkInputEvent(beforeinputEvents[1], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2799      is(inputEvents.length, 1,
   2800        `${description}: only one "input" event should be fired on other <textarea>`);
   2801      checkInputEvent(inputEvents[0], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2802      is(dragEvents.length, 1,
   2803        `${description}: only one "drop" event should be fired on <textarea>`);
   2804    }
   2805    document.removeEventListener("drop", onDrop);
   2806    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
   2807  })();
   2808 
   2809  // -------- Test dragging text in <textarea> to other <textarea> (canceling "insertFromDrop")
   2810  await (async function test_dragging_from_textarea_element_to_other_textarea_element_and_canceling_insert_from_drop() {
   2811    const description = 'dragging text in <textarea> to other <textarea> (canceling "insertFromDrop")';
   2812    container.innerHTML = "<textarea>Line1\nLine2</textarea><textarea></textarea>";
   2813    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2814    const textarea = document.querySelector("div#container > textarea");
   2815    const otherTextarea = document.querySelector("div#container > textarea + textarea");
   2816    textarea.setSelectionRange(3, 8);
   2817    beforeinputEvents = [];
   2818    inputEvents = [];
   2819    dragEvents = [];
   2820    const onDrop = aEvent => {
   2821      dragEvents.push(aEvent);
   2822      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
   2823        `${description}: dataTransfer should have selected text as "text/plain"`);
   2824      is(aEvent.dataTransfer.getData("text/html"), "",
   2825        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2826    };
   2827    document.addEventListener("drop", onDrop);
   2828    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
   2829    if (
   2830      await trySynthesizePlainDragAndDrop(
   2831        description,
   2832        {
   2833          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   2834          destElement: otherTextarea,
   2835        }
   2836      )
   2837    ) {
   2838      is(textarea.value, "Linne2",
   2839        `${description}: dragged range should be removed from <textarea>`);
   2840      is(otherTextarea.value, "",
   2841        `${description}: dragged content shouldn't be inserted into other <textarea>`);
   2842      is(beforeinputEvents.length, 2,
   2843        `${description}: 2 "beforeinput" events should be fired on <textarea> and other <textarea>`);
   2844      checkInputEvent(beforeinputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2845      checkInputEvent(beforeinputEvents[1], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2846      is(inputEvents.length, 1,
   2847        `${description}: only one "input" event should be fired on <textarea>`);
   2848      checkInputEvent(inputEvents[0], textarea, "deleteByDrag", null, null, [], description);
   2849      is(dragEvents.length, 1,
   2850        `${description}: only one "drop" event should be fired on <textarea>`);
   2851    }
   2852    document.removeEventListener("drop", onDrop);
   2853    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
   2854  })();
   2855 
   2856  // -------- Test copy-dragging text in <textarea> to other <textarea>
   2857  await (async function test_copy_dragging_from_textarea_element_to_other_textarea_element() {
   2858    const description = "copy-dragging text in <textarea> to other <textarea>";
   2859    container.innerHTML = "<textarea>Line1\nLine2</textarea><textarea></textarea>";
   2860    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   2861    const textarea = document.querySelector("div#container > textarea");
   2862    const otherTextarea = document.querySelector("div#container > textarea + textarea");
   2863    textarea.setSelectionRange(3, 8);
   2864    beforeinputEvents = [];
   2865    inputEvents = [];
   2866    dragEvents = [];
   2867    const onDrop = aEvent => {
   2868      dragEvents.push(aEvent);
   2869      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
   2870        `${description}: dataTransfer should have selected text as "text/plain"`);
   2871      is(aEvent.dataTransfer.getData("text/html"), "",
   2872        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
   2873    };
   2874    document.addEventListener("drop", onDrop);
   2875    if (
   2876      await trySynthesizePlainDragAndDrop(
   2877        description,
   2878        {
   2879          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   2880          destElement: otherTextarea,
   2881          dragEvent: kModifiersToCopy,
   2882        }
   2883      )
   2884    ) {
   2885      is(textarea.value, "Line1\nLine2",
   2886        `${description}: dragged range shouldn't be removed from <textarea>`);
   2887      is(otherTextarea.value, "e1\nLi",
   2888        `${description}: dragged content should be inserted into other <textarea>`);
   2889      is(beforeinputEvents.length, 1,
   2890        `${description}: only one "beforeinput" events should be fired on other <textarea>`);
   2891      checkInputEvent(beforeinputEvents[0], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2892      is(inputEvents.length, 1,
   2893        `${description}: only one "input" events should be fired on other <textarea>`);
   2894      checkInputEvent(inputEvents[0], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2895      is(dragEvents.length, 1,
   2896        `${description}: only one "drop" event should be fired on <textarea>`);
   2897    }
   2898    document.removeEventListener("drop", onDrop);
   2899  })();
   2900 
   2901  // -------- Test dragging multiple-line text in contenteditable to <input>
   2902  await (async function test_dragging_multiple_line_text_in_contenteditable_to_input_element() {
   2903    const description = "dragging multiple-line text in contenteditable to <input>";
   2904    container.innerHTML = '<div contenteditable><div>Line1</div><div>Line2</div></div><input>';
   2905    const contenteditable = document.querySelector("div#container > div");
   2906    const input = document.querySelector("div#container > input");
   2907    const selectionContainers = [contenteditable.firstChild.firstChild, contenteditable.firstChild.nextSibling.firstChild];
   2908    selection.setBaseAndExtent(selectionContainers[0], 3, selectionContainers[1], 2);
   2909    beforeinputEvents = [];
   2910    inputEvents = [];
   2911    dragEvents = [];
   2912    const onDrop = aEvent => {
   2913      dragEvents.push(aEvent);
   2914      comparePlainText(aEvent.dataTransfer.getData("text/plain"), `e1\nLi`,
   2915        `${description}: dataTransfer should have selected text as "text/plain"`);
   2916      is(aEvent.dataTransfer.getData("text/html"), "<div>e1</div><div>Li</div>",
   2917        `${description}: dataTransfer should have have selected nodes as "text/html"`);
   2918    };
   2919    document.addEventListener("drop", onDrop);
   2920    if (
   2921      await trySynthesizePlainDragAndDrop(
   2922        description,
   2923        {
   2924          srcSelection: selection,
   2925          destElement: input,
   2926        }
   2927      )
   2928    ) {
   2929      is(contenteditable.innerHTML, "<div>Linne2</div>",
   2930        `${description}: dragged content should be removed from contenteditable`);
   2931      is(input.value, "e1 Li",
   2932        `${description}: dragged range should be inserted into <input>`);
   2933      is(beforeinputEvents.length, 2,
   2934        `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
   2935      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   2936                      [{startContainer: selectionContainers[0], startOffset: 3,
   2937                        endContainer: selectionContainers[1], endOffset: 2}],
   2938                      description);
   2939      checkInputEvent(beforeinputEvents[1], input, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2940      is(inputEvents.length, 2,
   2941        `${description}: 2 "input" events should be fired on <input> and contenteditable`);
   2942      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
   2943      checkInputEvent(inputEvents[1], input, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2944      is(dragEvents.length, 1,
   2945        `${description}: only one "drop" event should be fired on other contenteditable`);
   2946    }
   2947    document.removeEventListener("drop", onDrop);
   2948  })();
   2949 
   2950  // -------- Test copy-dragging multiple-line text in contenteditable to <input>
   2951  await (async function test_copy_dragging_multiple_line_text_in_contenteditable_to_input_element() {
   2952    const description = "copy-dragging multiple-line text in contenteditable to <input>";
   2953    container.innerHTML = '<div contenteditable><div>Line1</div><div>Line2</div></div><input>';
   2954    const contenteditable = document.querySelector("div#container > div");
   2955    const input = document.querySelector("div#container > input");
   2956    selection.setBaseAndExtent(contenteditable.firstChild.firstChild, 3,
   2957                              contenteditable.firstChild.nextSibling.firstChild, 2);
   2958    beforeinputEvents = [];
   2959    inputEvents = [];
   2960    dragEvents = [];
   2961    const onDrop = aEvent => {
   2962      dragEvents.push(aEvent);
   2963      comparePlainText(aEvent.dataTransfer.getData("text/plain"), `e1\nLi`,
   2964        `${description}: dataTransfer should have selected text as "text/plain"`);
   2965      is(aEvent.dataTransfer.getData("text/html"), "<div>e1</div><div>Li</div>",
   2966        `${description}: dataTransfer should have have selected nodes as "text/html"`);
   2967    };
   2968    document.addEventListener("drop", onDrop);
   2969    if (
   2970      await trySynthesizePlainDragAndDrop(
   2971        description,
   2972        {
   2973          srcSelection: selection,
   2974          destElement: input,
   2975          dragEvent: kModifiersToCopy,
   2976        }
   2977      )
   2978    ) {
   2979      is(contenteditable.innerHTML, "<div>Line1</div><div>Line2</div>",
   2980        `${description}: dragged content should be removed from contenteditable`);
   2981      is(input.value, "e1 Li",
   2982        `${description}: dragged range should be inserted into <input>`);
   2983      is(beforeinputEvents.length, 1,
   2984        `${description}: only one "beforeinput" events should be fired on contenteditable`);
   2985      checkInputEvent(beforeinputEvents[0], input, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2986      is(inputEvents.length, 1,
   2987        `${description}: only one "input" events should be fired on contenteditable`);
   2988      checkInputEvent(inputEvents[0], input, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   2989      is(dragEvents.length, 1,
   2990        `${description}: only one "drop" event should be fired on other contenteditable`);
   2991    }
   2992    document.removeEventListener("drop", onDrop);
   2993  })();
   2994 
   2995  // -------- Test dragging multiple-line text in contenteditable to <textarea>
   2996  await (async function test_dragging_multiple_line_text_in_contenteditable_to_textarea_element() {
   2997    const description = "dragging multiple-line text in contenteditable to <textarea>";
   2998    container.innerHTML = '<div contenteditable><div>Line1</div><div>Line2</div></div><textarea></textarea>';
   2999    const contenteditable = document.querySelector("div#container > div");
   3000    const textarea = document.querySelector("div#container > textarea");
   3001    const selectionContainers = [contenteditable.firstChild.firstChild, contenteditable.firstChild.nextSibling.firstChild];
   3002    selection.setBaseAndExtent(selectionContainers[0], 3, selectionContainers[1], 2);
   3003    beforeinputEvents = [];
   3004    inputEvents = [];
   3005    dragEvents = [];
   3006    const onDrop = aEvent => {
   3007      dragEvents.push(aEvent);
   3008      comparePlainText(aEvent.dataTransfer.getData("text/plain"), `e1\nLi`,
   3009        `${description}: dataTransfer should have selected text as "text/plain"`);
   3010      is(aEvent.dataTransfer.getData("text/html"), "<div>e1</div><div>Li</div>",
   3011        `${description}: dataTransfer should have have selected nodes as "text/html"`);
   3012    };
   3013    document.addEventListener("drop", onDrop);
   3014    if (
   3015      await trySynthesizePlainDragAndDrop(
   3016        description,
   3017        {
   3018          srcSelection: selection,
   3019          destElement: textarea,
   3020        }
   3021      )
   3022    ) {
   3023      is(contenteditable.innerHTML, "<div>Linne2</div>",
   3024        `${description}: dragged content should be removed from contenteditable`);
   3025      is(textarea.value, "e1\nLi",
   3026        `${description}: dragged range should be inserted into <textarea>`);
   3027      is(beforeinputEvents.length, 2,
   3028        `${description}: 2 "beforeinput" events should be fired on <textarea> and contenteditable`);
   3029      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   3030                      [{startContainer: selectionContainers[0], startOffset: 3,
   3031                        endContainer: selectionContainers[1], endOffset: 2}],
   3032                      description);
   3033      checkInputEvent(beforeinputEvents[1], textarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   3034      is(inputEvents.length, 2,
   3035        `${description}: 2 "input" events should be fired on <textarea> and contenteditable`);
   3036      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
   3037      checkInputEvent(inputEvents[1], textarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   3038      is(dragEvents.length, 1,
   3039        `${description}: only one "drop" event should be fired on other contenteditable`);
   3040    }
   3041    document.removeEventListener("drop", onDrop);
   3042  })();
   3043 
   3044  // -------- Test copy-dragging multiple-line text in contenteditable to <textarea>
   3045  await (async function test_copy_dragging_multiple_line_text_in_contenteditable_to_textarea_element() {
   3046    const description = "copy-dragging multiple-line text in contenteditable to <textarea>";
   3047    container.innerHTML = '<div contenteditable><div>Line1</div><div>Line2</div></div><textarea></textarea>';
   3048    const contenteditable = document.querySelector("div#container > div");
   3049    const textarea = document.querySelector("div#container > textarea");
   3050    selection.setBaseAndExtent(contenteditable.firstChild.firstChild, 3,
   3051                              contenteditable.firstChild.nextSibling.firstChild, 2);
   3052    beforeinputEvents = [];
   3053    inputEvents = [];
   3054    dragEvents = [];
   3055    const onDrop = aEvent => {
   3056      dragEvents.push(aEvent);
   3057      comparePlainText(aEvent.dataTransfer.getData("text/plain"), `e1\nLi`,
   3058        `${description}: dataTransfer should have selected text as "text/plain"`);
   3059      is(aEvent.dataTransfer.getData("text/html"), "<div>e1</div><div>Li</div>",
   3060        `${description}: dataTransfer should have have selected nodes as "text/html"`);
   3061    };
   3062    document.addEventListener("drop", onDrop);
   3063    if (
   3064      await trySynthesizePlainDragAndDrop(
   3065        description,
   3066        {
   3067          srcSelection: selection,
   3068          destElement: textarea,
   3069          dragEvent: kModifiersToCopy,
   3070        }
   3071      )
   3072    ) {
   3073      is(contenteditable.innerHTML, "<div>Line1</div><div>Line2</div>",
   3074        `${description}: dragged content should be removed from contenteditable`);
   3075      is(textarea.value, "e1\nLi",
   3076        `${description}: dragged range should be inserted into <textarea>`);
   3077      is(beforeinputEvents.length, 1,
   3078        `${description}: only one "beforeinput" events should be fired on contenteditable`);
   3079      checkInputEvent(beforeinputEvents[0], textarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   3080      is(inputEvents.length, 1,
   3081        `${description}: only one "input" events should be fired on contenteditable`);
   3082      checkInputEvent(inputEvents[0], textarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
   3083      is(dragEvents.length, 1,
   3084        `${description}: only one "drop" event should be fired on other contenteditable`);
   3085    }
   3086    document.removeEventListener("drop", onDrop);
   3087  })();
   3088 
   3089  // -------- Test dragging text from an <input> and reframing the <input> element before dragend.
   3090  await (async function test_dragging_from_input_element_and_reframing_input_element() {
   3091    const description = "dragging part of text in <input> element and reframing the <input> element before dragend";
   3092    container.innerHTML = '<input value="Drag Me">';
   3093    const input = document.querySelector("div#container > input");
   3094    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   3095    input.setSelectionRange(1, 4);
   3096    beforeinputEvents = [];
   3097    inputEvents = [];
   3098    dragEvents = [];
   3099    const onDragStart = () => {
   3100      input.style.display = "none";
   3101      document.documentElement.scrollTop;
   3102      input.style.display = "";
   3103      document.documentElement.scrollTop;
   3104    };
   3105    const onDrop = aEvent => {
   3106      dragEvents.push(aEvent);
   3107      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
   3108                      input.value.substring(1, 4),
   3109                      `${description}: dataTransfer should have selected text as "text/plain"`);
   3110      is(aEvent.dataTransfer.getData("text/html"), "",
   3111        `${description}: dataTransfer should not have data as "text/html"`);
   3112    };
   3113    document.addEventListener("dragStart", onDragStart);
   3114    document.addEventListener("drop", onDrop);
   3115    if (
   3116      await trySynthesizePlainDragAndDrop(
   3117        description,
   3118        {
   3119          srcSelection: SpecialPowers.wrap(input).editor.selection,
   3120          destElement: dropZone,
   3121        }
   3122      )
   3123    ) {
   3124      is(beforeinputEvents.length, 0,
   3125        `${description}: No "beforeinput" event should be fired when dragging <input> value to non-editable drop zone`);
   3126      is(inputEvents.length, 0,
   3127        `${description}: No "input" event should be fired when dragging <input> value to non-editable drop zone`);
   3128      is(dragEvents.length, 1,
   3129        `${description}: only one "drop" event should be fired`);
   3130    }
   3131    document.removeEventListener("dragStart", onDragStart);
   3132    document.removeEventListener("drop", onDrop);
   3133  })();
   3134 
   3135  // -------- Test dragging text from an <textarea> and reframing the <textarea> element before dragend.
   3136  await (async function test_dragging_from_textarea_element_and_reframing_textarea_element() {
   3137    const description = "dragging part of text in <textarea> element and reframing the <textarea> element before dragend";
   3138    container.innerHTML = "<textarea>Some Text To Drag</textarea>";
   3139    const textarea = document.querySelector("div#container > textarea");
   3140    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   3141    textarea.setSelectionRange(1, 7);
   3142    beforeinputEvents = [];
   3143    inputEvents = [];
   3144    dragEvents = [];
   3145    const onDragStart = () => {
   3146      textarea.style.display = "none";
   3147      document.documentElement.scrollTop;
   3148      textarea.style.display = "";
   3149      document.documentElement.scrollTop;
   3150    };
   3151    const onDrop = aEvent => {
   3152      dragEvents.push(aEvent);
   3153      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
   3154                      textarea.value.substring(1, 7),
   3155                      `${description}: dataTransfer should have selected text as "text/plain"`);
   3156      is(aEvent.dataTransfer.getData("text/html"), "",
   3157        `${description}: dataTransfer should not have data as "text/html"`);
   3158    };
   3159    document.addEventListener("dragStart", onDragStart);
   3160    document.addEventListener("drop", onDrop);
   3161    if (
   3162      await trySynthesizePlainDragAndDrop(
   3163        description,
   3164        {
   3165          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   3166          destElement: dropZone,
   3167        }
   3168      )
   3169    ) {
   3170      is(beforeinputEvents.length, 0,
   3171        `${description}: No "beforeinput" event should be fired when dragging <textarea> value to non-editable drop zone`);
   3172      is(inputEvents.length, 0,
   3173        `${description}: No "input" event should be fired when dragging <textarea> value to non-editable drop zone`);
   3174      is(dragEvents.length, 1,
   3175        `${description}: only one "drop" event should be fired`);
   3176    }
   3177    document.removeEventListener("dragStart", onDragStart);
   3178    document.removeEventListener("drop", onDrop);
   3179  })();
   3180 
   3181  // -------- Test dragging text from an <input> and reframing the <input> element before dragstart.
   3182  await (async function test_dragging_from_input_element_and_reframing_input_element_before_dragstart() {
   3183    const description = "dragging part of text in <input> element and reframing the <input> element before dragstart";
   3184    container.innerHTML = '<input value="Drag Me">';
   3185    const input = document.querySelector("div#container > input");
   3186    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   3187    input.setSelectionRange(1, 4);
   3188    beforeinputEvents = [];
   3189    inputEvents = [];
   3190    dragEvents = [];
   3191    const onMouseMove = () => {
   3192      input.style.display = "none";
   3193      document.documentElement.scrollTop;
   3194      input.style.display = "";
   3195      document.documentElement.scrollTop;
   3196    };
   3197    const onMouseDown = () => {
   3198      document.addEventListener("mousemove", onMouseMove, {once: true});
   3199    }
   3200    const onDrop = aEvent => {
   3201      dragEvents.push(aEvent);
   3202      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
   3203                      input.value.substring(1, 4),
   3204                      `${description}: dataTransfer should have selected text as "text/plain"`);
   3205      is(aEvent.dataTransfer.getData("text/html"), "",
   3206        `${description}: dataTransfer should not have data as "text/html"`);
   3207    };
   3208    document.addEventListener("mousedown", onMouseDown, {once: true});
   3209    document.addEventListener("drop", onDrop);
   3210    if (
   3211      await trySynthesizePlainDragAndDrop(
   3212        description,
   3213        {
   3214          srcSelection: SpecialPowers.wrap(input).editor.selection,
   3215          destElement: dropZone,
   3216        }
   3217      )
   3218    ) {
   3219      is(beforeinputEvents.length, 0,
   3220        `${description}: No "beforeinput" event should be fired when dragging <input> value to non-editable drop zone`);
   3221      is(inputEvents.length, 0,
   3222        `${description}: No "input" event should be fired when dragging <input> value to non-editable drop zone`);
   3223      is(dragEvents.length, 1,
   3224        `${description}: only one "drop" event should be fired`);
   3225    }
   3226    document.removeEventListener("mousedown", onMouseDown);
   3227    document.removeEventListener("mousemove", onMouseMove);
   3228    document.removeEventListener("drop", onDrop);
   3229  })();
   3230 
   3231  // -------- Test dragging text from an <textarea> and reframing the <textarea> element before dragstart.
   3232  await (async function test_dragging_from_textarea_element_and_reframing_textarea_element_before_dragstart() {
   3233    const description = "dragging part of text in <textarea> element and reframing the <textarea> element before dragstart";
   3234    container.innerHTML = "<textarea>Some Text To Drag</textarea>";
   3235    const textarea = document.querySelector("div#container > textarea");
   3236    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
   3237    textarea.setSelectionRange(1, 7);
   3238    beforeinputEvents = [];
   3239    inputEvents = [];
   3240    dragEvents = [];
   3241    const onMouseMove = () => {
   3242      textarea.style.display = "none";
   3243      document.documentElement.scrollTop;
   3244      textarea.style.display = "";
   3245      document.documentElement.scrollTop;
   3246    };
   3247    const onMouseDown = () => {
   3248      document.addEventListener("mousemove", onMouseMove, {once: true});
   3249    }
   3250    const onDrop = aEvent => {
   3251      dragEvents.push(aEvent);
   3252      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
   3253                      textarea.value.substring(1, 7),
   3254                      `${description}: dataTransfer should have selected text as "text/plain"`);
   3255      is(aEvent.dataTransfer.getData("text/html"), "",
   3256        `${description}: dataTransfer should not have data as "text/html"`);
   3257    };
   3258    document.addEventListener("mousedown", onMouseDown, {once: true});
   3259    document.addEventListener("drop", onDrop);
   3260    if (
   3261      await trySynthesizePlainDragAndDrop(
   3262        description,
   3263        {
   3264          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
   3265          destElement: dropZone,
   3266        }
   3267      )
   3268    ) {
   3269      is(beforeinputEvents.length, 0,
   3270        `${description}: No "beforeinput" event should be fired when dragging <textarea> value to non-editable drop zone`);
   3271      is(inputEvents.length, 0,
   3272        `${description}: No "input" event should be fired when dragging <textarea> value to non-editable drop zone`);
   3273      is(dragEvents.length, 1,
   3274        `${description}: only one "drop" event should be fired`);
   3275    }
   3276    document.removeEventListener("mousedown", onMouseDown);
   3277    document.removeEventListener("mousemove", onMouseMove);
   3278    document.removeEventListener("drop", onDrop);
   3279  })();
   3280 
   3281  await (async function test_dragend_when_left_half_of_text_node_dragged_into_textarea() {
   3282    const description = "dragging left half of text in contenteditable into <textarea>";
   3283    container.innerHTML = "<div contenteditable><p>abcdef</p></div><textarea></textarea>";
   3284    const editingHost = container.querySelector("[contenteditable]");
   3285    const textNode = editingHost.querySelector("p").firstChild;
   3286    const textarea = container.querySelector("textarea");
   3287    selection.setBaseAndExtent(textNode, 0, textNode, textNode.length / 2);
   3288    beforeinputEvents = [];
   3289    inputEvents = [];
   3290    dragEvents = [];
   3291    const onDragEnd = aEvent => dragEvents.push(aEvent);
   3292    document.addEventListener("dragend", onDragEnd, {capture: true});
   3293    if (
   3294      await trySynthesizePlainDragAndDrop(
   3295        description,
   3296        {
   3297          srcSelection: selection,
   3298          destElement: textarea,
   3299        }
   3300      )
   3301    ) {
   3302      ok(
   3303        textNode.isConnected,
   3304        `${description}: the text node part of whose text is dragged should not be removed`
   3305      );
   3306      is(
   3307        dragEvents.length,
   3308        1,
   3309        `${description}: only one "dragend" event should be fired`
   3310      );
   3311      is(
   3312        dragEvents[0]?.target,
   3313        textNode,
   3314        `${description}: "dragend" should be fired on the text node which the mouse button down on`
   3315      );
   3316    }
   3317    document.removeEventListener("dragend", onDragEnd, {capture: true});
   3318  })();
   3319 
   3320  await (async function test_dragend_when_right_half_of_text_node_dragged_into_textarea() {
   3321    const description = "dragging right half of text in contenteditable into <textarea>";
   3322    container.innerHTML = "<div contenteditable><p>abcdef</p></div><textarea></textarea>";
   3323    const editingHost = container.querySelector("[contenteditable]");
   3324    const textNode = editingHost.querySelector("p").firstChild;
   3325    const textarea = container.querySelector("textarea");
   3326    selection.setBaseAndExtent(textNode, textNode.length / 2, textNode, textNode.length);
   3327    beforeinputEvents = [];
   3328    inputEvents = [];
   3329    dragEvents = [];
   3330    const onDragEnd = aEvent => dragEvents.push(aEvent);
   3331    document.addEventListener("dragend", onDragEnd, {capture: true});
   3332    if (
   3333      await trySynthesizePlainDragAndDrop(
   3334        description,
   3335        {
   3336          srcSelection: selection,
   3337          destElement: textarea,
   3338        }
   3339      )
   3340    ) {
   3341      ok(
   3342        textNode.isConnected,
   3343        `${description}: the text node part of whose text is dragged should not be removed`
   3344      );
   3345      is(
   3346        dragEvents.length,
   3347        1,
   3348        `${description}: only one "dragend" event should be fired`
   3349      );
   3350      is(
   3351        dragEvents[0]?.target,
   3352        textNode,
   3353        `${description}: "dragend" should be fired on the text node which the mouse button down on`
   3354      );
   3355    }
   3356    document.removeEventListener("dragend", onDragEnd, {capture: true});
   3357  })();
   3358 
   3359  await (async function test_dragend_when_middle_part_of_text_node_dragged_into_textarea() {
   3360    const description = "dragging middle of text in contenteditable into <textarea>";
   3361    container.innerHTML = "<div contenteditable><p>abcdef</p></div><textarea></textarea>";
   3362    const editingHost = container.querySelector("[contenteditable]");
   3363    const textNode = editingHost.querySelector("p").firstChild;
   3364    const textarea = container.querySelector("textarea");
   3365    selection.setBaseAndExtent(textNode, "ab".length, textNode, "abcd".length);
   3366    beforeinputEvents = [];
   3367    inputEvents = [];
   3368    dragEvents = [];
   3369    const onDragEnd = aEvent => dragEvents.push(aEvent);
   3370    document.addEventListener("dragend", onDragEnd, {capture: true});
   3371    if (
   3372      await trySynthesizePlainDragAndDrop(
   3373        description,
   3374        {
   3375          srcSelection: selection,
   3376          destElement: textarea,
   3377        }
   3378      )
   3379    ) {
   3380      ok(
   3381        textNode.isConnected,
   3382        `${description}: the text node part of whose text is dragged should not be removed`
   3383      );
   3384      is(
   3385        dragEvents.length,
   3386        1,
   3387        `${description}: only one "dragend" event should be fired`
   3388      );
   3389      is(
   3390        dragEvents[0]?.target,
   3391        textNode,
   3392        `${description}: "dragend" should be fired on the text node which the mouse button down on`
   3393      );
   3394    }
   3395    document.removeEventListener("dragend", onDragEnd, {capture: true});
   3396  })();
   3397 
   3398  await (async function test_dragend_when_all_of_text_node_dragged_into_textarea() {
   3399    const description = "dragging all of text in contenteditable into <textarea>";
   3400    container.innerHTML = "<div contenteditable><p>abcdef</p></div><textarea></textarea>";
   3401    const editingHost = container.querySelector("[contenteditable]");
   3402    const textNode = editingHost.querySelector("p").firstChild;
   3403    const textarea = container.querySelector("textarea");
   3404    selection.setBaseAndExtent(textNode, 0, textNode, textNode.length);
   3405    beforeinputEvents = [];
   3406    inputEvents = [];
   3407    dragEvents = [];
   3408    const onDragEnd = aEvent => dragEvents.push(aEvent);
   3409    document.addEventListener("dragend", onDragEnd, {capture: true});
   3410    if (
   3411      await trySynthesizePlainDragAndDrop(
   3412          description,
   3413          {
   3414            srcSelection: selection,
   3415            destElement: textarea,
   3416          }
   3417        )
   3418    ) {
   3419      ok(
   3420        !textNode.isConnected,
   3421        `${description}: the text node whose all text is dragged should've been removed from the contenteditable`
   3422      );
   3423      is(
   3424        dragEvents.length,
   3425        1,
   3426        `${description}: only one "dragend" event should be fired`
   3427      );
   3428      is(
   3429        dragEvents[0]?.target,
   3430        editingHost,
   3431        `${description}: "dragend" should be fired on the editing host which is parent of the removed text node`
   3432      );
   3433    }
   3434    document.removeEventListener("dragend", onDragEnd, {capture: true});
   3435  })();
   3436 
   3437  // -------- Test dragging contenteditable to contenteditable=plaintext-only
   3438  await (async function test_dragging_from_contenteditable_to_contenteditable_plaintext_only() {
   3439    const description = "dragging text in contenteditable to contenteditable=plaintext-only";
   3440    container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable="plaintext-only" style="min-height: 3em;"></div>';
   3441    const contenteditable = document.querySelector("div#container > div");
   3442    const b = document.querySelector("div#container > div > b");
   3443    const otherContenteditable = document.querySelector("div#container > div ~ div");
   3444    const selectionContainers = [b.firstChild, b.firstChild];
   3445    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
   3446    beforeinputEvents = [];
   3447    inputEvents = [];
   3448    dragEvents = [];
   3449    const onDrop = aEvent => {
   3450      dragEvents.push(aEvent);
   3451      is(aEvent.dataTransfer.getData("text/plain"), "ol",
   3452        `${description}: dataTransfer should have selected text as "text/plain"`);
   3453      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
   3454        `${description}: dataTransfer should have selected nodes as "text/html"`);
   3455    };
   3456    document.addEventListener("drop", onDrop);
   3457    if (
   3458      await trySynthesizePlainDragAndDrop(
   3459        description,
   3460        {
   3461          srcSelection: selection,
   3462          destElement: otherContenteditable,
   3463        }
   3464      )
   3465    ) {
   3466      is(contenteditable.innerHTML, "<b>bd</b>",
   3467        `${description}: dragged range should be removed from contenteditable`);
   3468      is(otherContenteditable.innerHTML, "ol",
   3469        `${description}: dragged content should be inserted into other contenteditable without formatting`);
   3470      is(beforeinputEvents.length, 2,
   3471        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
   3472      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   3473                      [{startContainer: selectionContainers[0], startOffset: 1,
   3474                        endContainer: selectionContainers[1], endOffset: 3}],
   3475                      description);
   3476      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
   3477                      [{type: "text/html", data: "<b>ol</b>"},
   3478                      {type: "text/plain", data: "ol"}],
   3479                      [{startContainer: otherContenteditable, startOffset: 0,
   3480                        endContainer: otherContenteditable, endOffset: 0}],
   3481                      description);
   3482      is(inputEvents.length, 2,
   3483        `${description}: 2 "input" events should be fired on contenteditable`);
   3484      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
   3485      checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
   3486                      [{type: "text/html", data: "<b>ol</b>"},
   3487                      {type: "text/plain", data: "ol"}],
   3488                      [],
   3489                      description);
   3490      is(dragEvents.length, 1,
   3491        `${description}: only one "drop" event should be fired on other contenteditable`);
   3492    }
   3493    document.removeEventListener("drop", onDrop);
   3494  })();
   3495 
   3496  // -------- Test dragging inline contenteditable to same contenteditable
   3497  await (async function test_dragging_from_inline_contenteditable_to_itself() {
   3498    const description = "dragging text in inline contenteditable to same contenteditable";
   3499    container.innerHTML = `<span contenteditable style="font-size:2em">dragme!!<span>MMMM</span></span>`;
   3500    const contenteditable = document.querySelector("span[contenteditable]");
   3501    const span = contenteditable.querySelector("span");
   3502    selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.firstChild, "dragme".length);
   3503    beforeinputEvents = [];
   3504    inputEvents = [];
   3505    dragEvents = [];
   3506    const onDrop = aEvent => {
   3507      dragEvents.push(aEvent);
   3508      is(aEvent.dataTransfer.getData("text/plain"), "dragme",
   3509        `${description}: dataTransfer should have selected text as "text/plain"`);
   3510      is(aEvent.dataTransfer.getData("text/html"), "dragme",
   3511        `${description}: dataTransfer should have selected text as "text/html"`);
   3512    };
   3513    document.addEventListener("drop", onDrop);
   3514    if (
   3515      await trySynthesizePlainDragAndDrop(
   3516        description,
   3517        {
   3518          srcSelection: selection,
   3519          destElement: span,
   3520        }
   3521      )
   3522    ) {
   3523      is(contenteditable.innerHTML, "!!<span>MM</span>dragme<span>MM</span>",
   3524        `${description}: dragged range should be moved in inline contenteditable`);
   3525      is(beforeinputEvents.length, 2,
   3526        `${description}: 2 "beforeinput" events should be fired on inline contenteditable`);
   3527      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   3528                      [{startContainer: contenteditable.firstChild, startOffset: 0,
   3529                        endContainer: contenteditable.firstChild, endOffset: "dragme".length}],
   3530                      description);
   3531      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
   3532                      [{type: "text/html", data: "dragme"},
   3533                      {type: "text/plain", data: "dragme"}],
   3534                      [{startContainer: span.firstChild, startOffset: 2,
   3535                        endContainer: span.firstChild, endOffset: 2}],
   3536                      description);
   3537      is(inputEvents.length, 2,
   3538        `${description}: 2 "input" events should be fired on inline contenteditable`);
   3539      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
   3540      checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
   3541                      [{type: "text/html", data: "dragme"},
   3542                      {type: "text/plain", data: "dragme"}],
   3543                      [],
   3544                      description);
   3545      is(dragEvents.length, 1,
   3546        `${description}: only one "drop" event should be fired on inline contenteditable`);
   3547    }
   3548    document.removeEventListener("drop", onDrop);
   3549  })();
   3550 
   3551  // -------- Test dragging inline contenteditable to other inline contenteditable
   3552  await (async function test_dragging_from_inline_contenteditable_to_other_inline_contenteditable() {
   3553    const description = "dragging text in inline contenteditable to other inline contenteditable";
   3554    container.innerHTML = '<span contenteditable style="font-size:2em">dragme!!</span><hr><span contenteditable style="font-size:em">MM</span>';
   3555    const contenteditable = document.querySelector("div#container > span[contenteditable]");
   3556    const otherContenteditable = document.querySelector("div#container > span[contenteditable] ~ span[contenteditable]");
   3557    selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.firstChild, "dragme".length);
   3558    beforeinputEvents = [];
   3559    inputEvents = [];
   3560    dragEvents = [];
   3561    const onDrop = aEvent => {
   3562      dragEvents.push(aEvent);
   3563      is(aEvent.dataTransfer.getData("text/plain"), "dragme",
   3564        `${description}: dataTransfer should have selected text as "text/plain"`);
   3565      is(aEvent.dataTransfer.getData("text/html"), "dragme",
   3566        `${description}: dataTransfer should have selected nodes as "text/html"`);
   3567    };
   3568    document.addEventListener("drop", onDrop);
   3569    if (
   3570      await trySynthesizePlainDragAndDrop(
   3571        description,
   3572        {
   3573          srcSelection: selection,
   3574          destElement: otherContenteditable,
   3575        }
   3576      )
   3577    ) {
   3578      is(contenteditable.innerHTML, "!!",
   3579        `${description}: dragged range should be removed from inline contenteditable`);
   3580      is(otherContenteditable.innerHTML, "MdragmeM",
   3581        `${description}: dragged content should be inserted into other inline contenteditable`);
   3582      is(beforeinputEvents.length, 2,
   3583        `${description}: 2 "beforeinput" events should be fired on inline contenteditable`);
   3584      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
   3585                      [{startContainer: contenteditable.firstChild, startOffset: 0,
   3586                        endContainer: contenteditable.firstChild, endOffset: "dragme".length}],
   3587                      description);
   3588      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
   3589                      [{type: "text/html", data: "dragme"},
   3590                      {type: "text/plain", data: "dragme"}],
   3591                      [{startContainer: otherContenteditable.firstChild, startOffset: 1,
   3592                        endContainer: otherContenteditable.firstChild, endOffset: 1}],
   3593                      description);
   3594      is(inputEvents.length, 2,
   3595        `${description}: 2 "input" events should be fired on inline contenteditable`);
   3596      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
   3597      checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
   3598                      [{type: "text/html", data: "dragme"},
   3599                      {type: "text/plain", data: "dragme"}],
   3600                      [],
   3601                      description);
   3602      is(dragEvents.length, 1,
   3603        `${description}: only one "drop" event should be fired on other inline contenteditable`);
   3604    }
   3605    document.removeEventListener("drop", onDrop);
   3606  })();
   3607 
   3608  // We need to clean up contenteditable=plaintext-only before the pref enabling it is cleared.
   3609  container.innerHTML = "";
   3610 
   3611  document.removeEventListener("beforeinput", onBeforeinput);
   3612  document.removeEventListener("input", onInput);
   3613  SimpleTest.finish();
   3614 }
   3615 
   3616 SimpleTest.waitForFocus(doTest);
   3617 
   3618 </script>
   3619 </body>
   3620 </html>