Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Impressum test_dragdrop.html

  Sprache: HTML
 

 products/Sources/formale Sprachen/C/Firefox/editor/libeditor/tests/test_dragdrop.html


<!doctype html>
<html>

<head>
  <link rel="stylesheet" href="/tests/SimpleTest/test.css">

  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script src="/tests/SimpleTest/EventUtils.js"></script>
</head>

<body>
  <div id="dropZone"
       ondragenter="event.dataTransfer.dropEffect = 'copy'; event.preventDefault();"
       ondragover="event.dataTransfer.dropEffect = 'copy'; event.preventDefault();"
       ondrop="event.preventDefault();"
       style="height: 4px; background-color: lemonchiffon;"></div>
  <div id="container"></div>

<script type="application/javascript">

const { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

SimpleTest.waitForExplicitFinish();

function checkInputEvent(aEvent, aExpectedTarget, aInputType, aData, aDataTransfer, aTargetRanges, aDescription) {
  ok(aEvent instanceof InputEvent, `${aDescription}: "${aEvent.type}" event should be dispatched with InputEvent interface`);
  is(aEvent.cancelable, aEvent.type === "beforeinput", `${aDescription}: "${aEvent.type}" event should be ${aEvent.type === "beforeinput" ? "" : "never "}cancelable`);
  is(aEvent.bubbles, true, `${aDescription}: "${aEvent.type}" event should always bubble`);
  is(aEvent.target, aExpectedTarget, `${aDescription}: "${aEvent.type}" event should be fired on the <${aExpectedTarget.tagName.toLowerCase()}> element`);
  is(aEvent.inputType, aInputType, `${aDescription}: inputType of "${aEvent.type}" event should be "${aInputType}" on the <${aExpectedTarget.tagName.toLowerCase()}> element`);
  is(aEvent.data, aData, `${aDescription}: data of "${aEvent.type}" event should be ${aData} on the <${aExpectedTarget.tagName.toLowerCase()}> element`);
  if (aDataTransfer === null) {
    is(aEvent.dataTransfer, null, `${aDescription}: dataTransfer should be null on the <${aExpectedTarget.tagName.toLowerCase()}> element`);
  } else {
    for (let dataTransfer of aDataTransfer) {
      let description = `${aDescription}: on the <${aExpectedTarget.tagName.toLowerCase()}> element`;
      if (dataTransfer.todo) {
        // XXX It seems that synthesizeDrop() don't emulate perfectly if caller specifies the data directly.
        todo_is(aEvent.dataTransfer.getData(dataTransfer.type), dataTransfer.data,
                `${description}: dataTransfer of "${aEvent.type}" event should have "${dataTransfer.data}" whose type is "${dataTransfer.type}"`);
      } else {
        is(aEvent.dataTransfer.getData(dataTransfer.type), dataTransfer.data,
           `${description}: dataTransfer of "${aEvent.type}" event should have "${dataTransfer.data}" whose type is "${dataTransfer.type}"`);
      }
    }
  }
  let targetRanges = aEvent.getTargetRanges();
  if (aTargetRanges.length === 0) {
    is(targetRanges.length, 0,
       `${aDescription}: getTargetRange() of "${aEvent.type}" event should return empty array`);
  } else {
    is(targetRanges.length, aTargetRanges.length,
       `${aDescription}: getTargetRange() of "${aEvent.type}" event should return static range array`);
    if (targetRanges.length == aTargetRanges.length) {
      for (let i = 0; i < targetRanges.length; i++) {
        is(targetRanges[i].startContainer, aTargetRanges[i].startContainer,
           `${aDescription}: startContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match`);
        is(targetRanges[i].startOffset, aTargetRanges[i].startOffset,
           `${aDescription}: startOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match`);
        is(targetRanges[i].endContainer, aTargetRanges[i].endContainer,
           `${aDescription}: endContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match`);
        is(targetRanges[i].endOffset, aTargetRanges[i].endOffset,
           `${aDescription}: endOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match`);
      }
    }
  }
}

// eslint-disable-next-line complexity
async function doTest() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["dom.element.contenteditable.plaintext-only.enabled", true],
    ],
  });

  const container = document.getElementById("container");
  const dropZone = document.getElementById("dropZone");

  let beforeinputEvents = [];
  let inputEvents = [];
  let dragEvents = [];
  function onBeforeinput(event) {
    beforeinputEvents.push(event);
  }
  function onInput(event) {
    inputEvents.push(event);
  }
  document.addEventListener("beforeinput", onBeforeinput);
  document.addEventListener("input", onInput);

  function preventDefaultDeleteByDrag(aEvent) {
    if (aEvent.inputType === "deleteByDrag") {
      aEvent.preventDefault();
    }
  }
  function preventDefaultInsertFromDrop(aEvent) {
    if (aEvent.inputType === "insertFromDrop") {
      aEvent.preventDefault();
    }
  }

  const selection = window.getSelection();

  const kIsMac = navigator.platform.includes("Mac");
  const kIsWin = navigator.platform.includes("Win");

  const kNativeLF = kIsWin ? "\r\n" : "\n";

  const kModifiersToCopy = {
    ctrlKey: !kIsMac,
    altKey: kIsMac,
  }

  function comparePlainText(aGot, aExpected, aDescription) {
    is(aGot.replace(/\r\n?/g, "\n"), aExpected, aDescription);
  }
  function compareHTML(aGot, aExpected, aDescription) {
    is(aGot.replace(/\r\n?/g, "\n"), aExpected, aDescription);
  }

  async function trySynthesizePlainDragAndDrop(aDescription, aOptions) {
    try {
      await synthesizePlainDragAndDrop(aOptions);
      return true;
    } catch (e) {
      ok(false, `${aDescription}: Failed to emulate drag and drop (${e.message})`);
      return false;
    }
  }

  // -------- Test dragging regular text
  await (async function test_dragging_regular_text() {
    const description = "dragging part of non-editable <span> element";
    container.innerHTML = '<span style="font-size: 24px;">Some Text</span>';
    const span = document.querySelector("div#container > span");
    selection.setBaseAndExtent(span.firstChild, 4, span.firstChild, 6);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                      span.textContent.substring(4, 6),
                      `${description}: dataTransfer should have selected text as "text/plain"`);
      compareHTML(aEvent.dataTransfer.getData("text/html"),
                  span.outerHTML.replace(/>.+</, `>${span.textContent.substring(4, 6)}<`),
                  `${description}: dataTransfer should have the parent inline element and only selected text a"text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: dropZone,
        }
      )
    ) {
      is(beforeinputEvents.length, 0,
        `${description}: No "beforeinput" event should be fired when dragging non-editable selection to non-editable drop zone`);
      is(inputEvents.length, 0,
        `${description}: No "input" event should be fired when dragging non-editable selection to non-editable drop zone`);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text from an <input>
  await (async function test_dragging_text_from_input_element() {
    const description = "dragging part of text in <input> element";
    container.innerHTML = '<input value="Drag Me">';
    const input = document.querySelector("div#container > input");
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    input.setSelectionRange(1, 4);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                      input.value.substring(1, 4),
                      `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should not have data as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: dropZone,
        }
      )
    ) {
      is(beforeinputEvents.length, 0,
        `${description}: No "beforeinput" event should be fired when dragging <input> value to non-editable drop zone`);
      is(inputEvents.length, 0,
        `${description}: No "input" event should be fired when dragging <input> value to non-editable drop zone`);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text from an <textarea>
  await (async function test_dragging_text_from_textarea_element() {
    const description = "dragging part of text in <textarea> element";
    container.innerHTML = "<textarea>Some Text To Drag</textarea>";
    const textarea = document.querySelector("div#container > textarea");
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    textarea.setSelectionRange(1, 7);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                      textarea.value.substring(1, 7),
                      `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should not have data as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: dropZone,
        }
      )
    ) {
      is(beforeinputEvents.length, 0,
        `${description}: No "beforeinput" event should be fired when dragging <textarea> value to non-editable drop zone`);
      is(inputEvents.length, 0,
        `${description}: No "input" event should be fired when dragging <textarea> value to non-editable drop zone`);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text from a contenteditable
  await (async function test_dragging_text_from_contenteditable() {
    const description = "dragging part of text in contenteditable element";
    container.innerHTML = "<p contenteditable>This is some <b>editable</b> text.</p>";
    const b = document.querySelector("div#container > p > b");
    selection.setBaseAndExtent(b.firstChild, 2, b.firstChild, 6);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                      b.textContent.substring(2, 6),
                      `${description}: dataTransfer should have selected text as "text/plain"`);
      compareHTML(aEvent.dataTransfer.getData("text/html"),
                  b.outerHTML.replace(/>.+</, `>${b.textContent.substring(2, 6)}<`),
                  `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: dropZone,
        }
      )
    ) {
      is(beforeinputEvents.length, 0,
        `${description}: No "beforeinput" event should be fired when dragging <textarea> value to non-editable drop zone`);
      is(inputEvents.length, 0,
        `${description}: No "input" event should be fired when dragging <textarea> value to non-editable drop zone`);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired`);
    }
    document.removeEventListener("drop", onDrop);
  })();


  for (const inputType of ["text""search"]) {
    // -------- Test dragging regular text of text/html to <input>
    await (async function test_dragging_text_from_span_element_to_input_element() {
      const description = `dragging text in non-editable <span> to <input type=${inputType}>`;
      container.innerHTML = `<span>Static</span><input type="${inputType}">`;
      const span = document.querySelector("div#container > span");
      const input = document.querySelector("div#container > input");
      selection.setBaseAndExtent(span.firstChild, 2, span.firstChild, 5);
      beforeinputEvents = [];
      inputEvents = [];
      dragEvents = [];
      const onDrop = aEvent => {
        dragEvents.push(aEvent);
        comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                        span.textContent.substring(2, 5),
                        `${description}: dataTransfer should have selected text as "text/plain"`);
        compareHTML(aEvent.dataTransfer.getData("text/html"),
                    span.outerHTML.replace(/>.+</, `>${span.textContent.substring(2, 5)}<`),
                    `${description}: dataTransfer should have selected nodes as "text/html"`);
      };
      document.addEventListener("drop", onDrop);
      if (
        await trySynthesizePlainDragAndDrop(
          description,
          {
            srcSelection: selection,
            destElement: input,
          }
        )
      ) {
        is(input.value, span.textContent.substring(2, 5),
          `${description}: <input>.value should be modified`);
        is(beforeinputEvents.length, 1,
          `${description}: one "beforeinput" event should be fired on <input>`);
        checkInputEvent(beforeinputEvents[0], input"insertFromDrop"span.textContent.substring(2, 5), null, [], description);
        is(inputEvents.length, 1,
          `${description}: one "input" event should be fired on <input>`);
        checkInputEvent(inputEvents[0], input"insertFromDrop"span.textContent.substring(2, 5), null, [], description);
        is(dragEvents.length, 1,
          `${description}: only one "drop" event should be fired on <input>`);
      }
      document.removeEventListener("drop", onDrop);
    })();

    // -------- Test dragging regular text of text/html to disabled <input>
    await (async function test_dragging_text_from_span_element_to_disabled_input_element() {
      const description = `dragging text in non-editable <span> to <input disabled type="${inputType}">`;
      container.innerHTML = `<span>Static</span><input disabled type="${inputType}">`;
      const span = document.querySelector("div#container > span");
      const input = document.querySelector("div#container > input");
      selection.setBaseAndExtent(span.firstChild, 2, span.firstChild, 5);
      beforeinputEvents = [];
      inputEvents = [];
      dragEvents = [];
      const onDrop = aEvent => {
        dragEvents.push(aEvent);
      };
      document.addEventListener("drop", onDrop);
      if (
        await trySynthesizePlainDragAndDrop(
          description,
          {
            srcSelection: selection,
            destElement: input,
          }
        )
      ) {
        is(input.value, "",
          `${description}: <input disable>.value should not be modified`);
        is(beforeinputEvents.length, 0,
          `${description}: no "beforeinput" event should be fired on <input disabled>`);
        is(inputEvents.length, 0,
          `${description}: no "input" event should be fired on <input disabled>`);
        is(dragEvents.length, 0,
          `${description}: no "drop" event should be fired on <input disabled>`);
      }
      document.removeEventListener("drop", onDrop);
    })();

    // -------- Test dragging regular text of text/html to readonly <input>
    await (async function test_dragging_text_from_span_element_to_readonly_input_element() {
      const description = `dragging text in non-editable <span> to <input readonly type="${inputType}">`;
      container.innerHTML = `<span>Static</span><input readonly type="${inputType}">`;
      const span = document.querySelector("div#container > span");
      const input = document.querySelector("div#container > input");
      selection.setBaseAndExtent(span.firstChild, 2, span.firstChild, 5);
      beforeinputEvents = [];
      inputEvents = [];
      dragEvents = [];
      const onDrop = aEvent => {
        dragEvents.push(aEvent);
        comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                        span.textContent.substring(2, 5),
                        `${description}: dataTransfer should have selected text as "text/plain"`);
        compareHTML(aEvent.dataTransfer.getData("text/html"),
                    span.outerHTML.replace(/>.+</, `>${span.textContent.substring(2, 5)}<`),
                    `${description}: dataTransfer should have selected nodes as "text/html"`);
      };
      document.addEventListener("drop", onDrop);
      if (
        await trySynthesizePlainDragAndDrop(
          description,
          {
            srcSelection: selection,
            destElement: input,
          }
        )
      ) {
        is(input.value, "",
          `${description}: <input readonly>.value should not be modified`);
        is(beforeinputEvents.length, 0,
          `${description}: no "beforeinput" event should be fired on <input readonly>`);
        is(inputEvents.length, 0,
          `${description}: no "input" event should be fired on <input readonly>`);
        is(dragEvents.length, 0,
          `${description}: no "drop" event should be fired on <input readonly>`);
      }
      document.removeEventListener("drop", onDrop);
    })();

    // -------- Test dragging only text/html data (like from another app) to <input>.
    await (async function test_dragging_only_html_text_to_input_element() {
      const description = `dragging only text/html data to <input type="${inputType}>`;
      container.innerHTML = `<span>Static</span><input type="${inputType}">`;
      const span = document.querySelector("div#container > span");
      const input = document.querySelector("div#container > input");
      selection.selectAllChildren(span);
      beforeinputEvents = [];
      inputEvents = [];
      const onDragStart = aEvent => {
        // Clear all dataTransfer data first.  Then, it'll be filled only with
        // the text/html data passed to synthesizeDrop().
        aEvent.dataTransfer.clearData();
      };
      window.addEventListener("dragstart", onDragStart, {capture: true});
      synthesizeDrop(spaninput, [[{type: "text/html", data: "Some <b>Bold<b> Text"}]], "copy");
      is(beforeinputEvents.length, 0,
        `${description}: no "beforeinput" event should be fired on <input>`);
      is(inputEvents.length, 0,
        `${description}: no "input" event should be fired on <input>`);
      window.removeEventListener("dragstart", onDragStart, {capture: true});
    })();

    // -------- Test dragging both text/plain and text/html data (like from another app) to <input>.
    await (async function test_dragging_both_html_text_and_plain_text_to_input_element() {
      const description = `dragging both text/plain and text/html data to <input type=${inputType}>`;
      container.innerHTML = `<span>Static</span><input type="${inputType}">`;
      const span = document.querySelector("div#container > span");
      const input = document.querySelector("div#container > input");
      selection.selectAllChildren(span);
      beforeinputEvents = [];
      inputEvents = [];
      const onDragStart = aEvent => {
        // Clear all dataTransfer data first.  Then, it'll be filled only with
        // the text/plain data and text/html data passed to synthesizeDrop().
        aEvent.dataTransfer.clearData();
      };
      window.addEventListener("dragstart", onDragStart, {capture: true});
      synthesizeDrop(spaninput, [[{type: "text/html", data: "Some <b>Bold<b> Text"},
                                    {type: "text/plain", data: "Some Plain Text"}]], "copy");
      is(input.value, "Some Plain Text",
        `${description}: The text/plain data should be inserted`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on <input> element`);
      checkInputEvent(beforeinputEvents[0], input"insertFromDrop""Some Plain Text", null, [],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on <input> element`);
      checkInputEvent(inputEvents[0], input"insertFromDrop""Some Plain Text", null, [],
                      description);
      window.removeEventListener("dragstart", onDragStart, {capture: true});
    })();

    // -------- Test dragging special text type from another app to <input>
    await (async function test_dragging_only_moz_text_internal_to_input_element() {
      const description = `dragging both text/x-moz-text-internal data to <input type="${inputType}">`;
      container.innerHTML = `<span>Static</span><input type="${inputType}">`;
      const span = document.querySelector("div#container > span");
      const input = document.querySelector("div#container > input");
      selection.selectAllChildren(span);
      beforeinputEvents = [];
      inputEvents = [];
      const onDragStart = aEvent => {
        // Clear all dataTransfer data first.  Then, it'll be filled only with
        // the text/x-moz-text-internal data passed to synthesizeDrop().
        aEvent.dataTransfer.clearData();
      };
      window.addEventListener("dragstart", onDragStart, {capture: true});
      synthesizeDrop(spaninput, [[{type: "text/x-moz-text-internal", data: "Some Special Text"}]], "copy");
      is(input.value, "",
        `${description}: <input>.value should not be modified with "text/x-moz-text-internal" data`);
      // Note that even if editor does not handle given dataTransfer, web apps
      // may handle it by itself.  Therefore, editor should dispatch "beforeinput"
      // event.
      is(beforeinputEvents.length, 1,
        `${description}: one "beforeinput" event should be fired when dropping "text/x-moz-text-internal" data into <input> element`);
      // But unfortunately, on <input> and <textarea>, dataTransfer won't be set...
      checkInputEvent(beforeinputEvents[0], input"insertFromDrop""", null, [], description);
      is(inputEvents.length, 0,
        `${description}: no "input" event should be fired when dropping "text/x-moz-text-internal" data into <input> element`);
      window.removeEventListener("dragstart", onDragStart, {capture: true});
    })();

    // -------- Test dragging contenteditable to <input>
    await (async function test_dragging_from_contenteditable_to_input_element() {
      const description = `dragging text in contenteditable to <input type="${inputType}">`;
      container.innerHTML = `<div contenteditable>Some <b>bold</b> text</div><input type="${inputType}">`;
      const contenteditable = document.querySelector("div#container > div");
      const input = document.querySelector("div#container > input");
      const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
      selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
      beforeinputEvents = [];
      inputEvents = [];
      dragEvents = [];
      const onDrop = aEvent => {
        dragEvents.push(aEvent);
        is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
          `${description}: dataTransfer should have selected text as "text/plain"`);
        is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
          `${description}: dataTransfer should have selected nodes as "text/html"`);
      };
      document.addEventListener("drop", onDrop);
      if (
        await trySynthesizePlainDragAndDrop(
          description,
          {
            srcSelection: selection,
            destElement: input,
          }
        )
      ) {
        is(contenteditable.innerHTML, "Soext",
          `${description}: Dragged range should be removed from contenteditable`);
        is(input.value, "me bold t",
          `${description}: <input>.value should be modified`);
        is(beforeinputEvents.length, 2,
                `${description}: 2 "beforeinput" events should be fired on contenteditable and <input>`);
        checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                        [{startContainer: selectionContainers[0], startOffset: 2,
                          endContainer: selectionContainers[1], endOffset: 2}],
                        description);
        checkInputEvent(beforeinputEvents[1], input"insertFromDrop""me bold t", null, [], description);
        is(inputEvents.length, 2,
                `${description}: 2 "input" events should be fired on contenteditable and <input>`);
        checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
        checkInputEvent(inputEvents[1], input"insertFromDrop""me bold t", null, [], description);
        is(dragEvents.length, 1,
          `${description}: only one "drop" event should be fired on <textarea>`);
      }
      document.removeEventListener("drop", onDrop);
    })();

    // -------- Test dragging contenteditable to <input> (canceling "deleteByDrag")
    await (async function test_dragging_from_contenteditable_to_input_element_and_canceling_delete_by_drag() {
      const description = `dragging text in contenteditable to <input type="${inputType}"> (canceling "deleteByDrag")`;
      container.innerHTML = `<div contenteditable>Some <b>bold</b> text</div><input type="${inputType}">`;
      const contenteditable = document.querySelector("div#container > div");
      const input = document.querySelector("div#container > input");
      const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
      selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
      beforeinputEvents = [];
      inputEvents = [];
      dragEvents = [];
      const onDrop = aEvent => {
        dragEvents.push(aEvent);
        is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
          `${description}: dataTransfer should have selected text as "text/plain"`);
        is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
          `${description}: dataTransfer should have selected nodes as "text/html"`);
      };
      document.addEventListener("drop", onDrop);
      document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
      if (
        await trySynthesizePlainDragAndDrop(
          description,
          {
            srcSelection: selection,
            destElement: input,
          }
        )
      ) {
        is(contenteditable.innerHTML, "Some <b>bold</b> text",
          `${description}: Dragged range shouldn't be removed from contenteditable`);
        is(input.value, "me bold t",
          `${description}: <input>.value should be modified`);
        is(beforeinputEvents.length, 2,
                `${description}: 2 "beforeinput" events should be fired on contenteditable and <input>`);
        checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                        [{startContainer: selectionContainers[0], startOffset: 2,
                          endContainer: selectionContainers[1], endOffset: 2}],
                        description);
        checkInputEvent(beforeinputEvents[1], input"insertFromDrop""me bold t", null, [], description);
        is(inputEvents.length, 1,
                `${description}: only one "input" event should be fired on <input>`);
        checkInputEvent(inputEvents[0], input"insertFromDrop""me bold t", null, [], description);
        is(dragEvents.length, 1,
          `${description}: only one "drop" event should be fired on <input>`);
      }
      document.removeEventListener("drop", onDrop);
      document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
    })();

    // -------- Test dragging contenteditable to <input> (canceling "insertFromDrop")
    await (async function test_dragging_from_contenteditable_to_input_element_and_canceling_insert_from_drop() {
      const description = `dragging text in contenteditable to <input type="${inputType}"> (canceling "insertFromDrop")`;
      container.innerHTML = "<div contenteditable>Some <b>bold</b> text</div><input>";
      const contenteditable = document.querySelector("div#container > div");
      const input = document.querySelector("div#container > input");
      const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
      selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
      beforeinputEvents = [];
      inputEvents = [];
      dragEvents = [];
      const onDrop = aEvent => {
        dragEvents.push(aEvent);
        is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
          `${description}: dataTransfer should have selected text as "text/plain"`);
        is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
          `${description}: dataTransfer should have selected nodes as "text/html"`);
      };
      document.addEventListener("drop", onDrop);
      document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
      if (
        await trySynthesizePlainDragAndDrop(
          description,
          {
            srcSelection: selection,
            destElement: input,
          }
        )
      ) {
        is(contenteditable.innerHTML, "Soext",
          `${description}: Dragged range should be removed from contenteditable`);
        is(input.value, "",
          `${description}: <input>.value shouldn't be modified`);
        is(beforeinputEvents.length, 2,
                `${description}: 2 "beforeinput" events should be fired on contenteditable and <input>`);
        checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                        [{startContainer: selectionContainers[0], startOffset: 2,
                          endContainer: selectionContainers[1], endOffset: 2}],
                        description);
        checkInputEvent(beforeinputEvents[1], input"insertFromDrop""me bold t", null, [], description);
        is(inputEvents.length, 1,
                `${description}: only one "input" event should be fired on contenteditable`);
        checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
        is(dragEvents.length, 1,
          `${description}: only one "drop" event should be fired on <input>`);
      }
      document.removeEventListener("drop", onDrop);
      document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
    })();
  }

  // -------- Test dragging regular text of text/html to <input type="number">
  //
  // FIXME(emilio): The -moz-appearance bit is just a hack to
  // work around bug 1611720.
  await (async function test_dragging_from_span_element_to_input_element_whose_type_number() {
    const description = `dragging text in non-editable <span> to <input type="number">`;
    container.innerHTML = `<span>123456</span><input type="number" style="-moz-appearance: textfield">`;
    const span = document.querySelector("div#container > span");
    const input = document.querySelector("div#container > input");
    selection.setBaseAndExtent(span.firstChild, 2, span.firstChild, 5);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                       span.textContent.substring(2, 5),
                       `${description}: dataTransfer should have selected text as "text/plain"`);
      compareHTML(aEvent.dataTransfer.getData("text/html"),
                  span.outerHTML.replace(/>.+</, `>${span.textContent.substring(2, 5)}<`),
                  `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: input,
        }
      )
    ) {
      is(input.value, span.textContent.substring(2, 5),
        `${description}: <input>.value should be modified`);
      is(beforeinputEvents.length, 1,
        `${description}: one "beforeinput" event should be fired on <input>`);
      checkInputEvent(beforeinputEvents[0], input"insertFromDrop"span.textContent.substring(2, 5), null, [], description);
      is(inputEvents.length, 1,
        `${description}: one "input" event should be fired on <input>`);
      checkInputEvent(inputEvents[0], input"insertFromDrop"span.textContent.substring(2, 5), null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <input>`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging only text/plain data (like from another app) to contenteditable.
  await (async function test_dragging_only_plain_text_to_contenteditable() {
    const description = "dragging both text/plain and text/html data to contenteditable";
    container.innerHTML = '<span>Static</span><div contenteditable style="min-height: 3em;"></div>';
    const span = document.querySelector("div#container > span");
    const contenteditable = document.querySelector("div#container > div");
    selection.selectAllChildren(span);
    beforeinputEvents = [];
    inputEvents = [];
    const onDragStart = aEvent => {
      // Clear all dataTransfer data first.  Then, it'll be filled only with
      // the text/plain data and text/html data passed to synthesizeDrop().
      aEvent.dataTransfer.clearData();
    };
    window.addEventListener("dragstart", onDragStart, {capture: true});
    synthesizeDrop(span, contenteditable, [[{type: "text/plain", data: "Sample Text"}]], "copy");
    is(contenteditable.innerHTML, "Sample Text",
      `${description}: The text/plain data should be inserted`);
    is(beforeinputEvents.length, 1,
      `${description}: only one "beforeinput" events should be fired on contenteditable element`);
    checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
                    [{todo: true, type: "text/plain", data: "Sample Text"}],
                    [{startContainer: contenteditable, startOffset: 0,
                      endContainer: contenteditable, endOffset: 0}],
                    description);
    is(inputEvents.length, 1,
      `${description}: only one "input" events should be fired on contenteditable element`);
    checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
                    [{todo: true, type: "text/plain", data: "Sample Text"}],
                    [],
                    description);
    window.removeEventListener("dragstart", onDragStart, {capture: true});
  })();

  // -------- Test dragging only text/html data (like from another app) to contenteditable.
  await (async function test_dragging_only_html_text_to_contenteditable() {
    const description = "dragging only text/html data to contenteditable";
    container.innerHTML = '<span>Static</span><div contenteditable style="min-height: 3em;"></div>';
    const span = document.querySelector("div#container > span");
    const contenteditable = document.querySelector("div#container > div");
    selection.selectAllChildren(span);
    beforeinputEvents = [];
    inputEvents = [];
    const onDragStart = aEvent => {
      // Clear all dataTransfer data first.  Then, it'll be filled only with
      // the text/plain data and text/html data passed to synthesizeDrop().
      aEvent.dataTransfer.clearData();
    };
    window.addEventListener("dragstart", onDragStart, {capture: true});
    synthesizeDrop(span, contenteditable, [[{type: "text/html", data: "Sample <i>Italic</i> Text"}]], "copy");
    is(contenteditable.innerHTML, "Sample <i>Italic</i> Text",
      `${description}: The text/plain data should be inserted`);
    is(beforeinputEvents.length, 1,
      `${description}: only one "beforeinput" events should be fired on contenteditable element`);
    checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
                    [{todo: true, type: "text/html", data: "Sample <i>Italic</i> Text"}],
                    [{startContainer: contenteditable, startOffset: 0,
                      endContainer: contenteditable, endOffset: 0}],
                    description);
    is(inputEvents.length, 1,
      `${description}: only one "input" events should be fired on contenteditable element`);
    checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
                    [{todo: true, type: "text/html", data: "Sample <i>Italic</i> Text"}],
                    [],
                    description);
    window.removeEventListener("dragstart", onDragStart, {capture: true});
  })();

  // -------- Test dragging regular text of text/plain to <textarea>
  await (async function test_dragging_from_span_element_to_textarea_element() {
    const description = "dragging text in non-editable <span> to <textarea>";
    container.innerHTML = "<span>Static</span><textarea></textarea>";
    const span = document.querySelector("div#container > span");
    const textarea = document.querySelector("div#container > textarea");
    selection.setBaseAndExtent(span.firstChild, 2, span.firstChild, 5);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                      span.textContent.substring(2, 5),
                      `${description}: dataTransfer should have selected text as "text/plain"`);
      compareHTML(aEvent.dataTransfer.getData("text/html"),
                  span.outerHTML.replace(/>.+</, `>${span.textContent.substring(2, 5)}<`),
                  `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: textarea,
        }
      )
    ) {
      is(textarea.value, span.textContent.substring(2, 5),
        `${description}: <textarea>.value should be modified`);
      is(beforeinputEvents.length, 1,
        `${description}: one "beforeinput" event should be fired on <textarea>`);
      checkInputEvent(beforeinputEvents[0], textarea"insertFromDrop"span.textContent.substring(2, 5), null, [], description);
      is(inputEvents.length, 1,
        `${description}: one "input" event should be fired on <textarea>`);
      checkInputEvent(inputEvents[0], textarea"insertFromDrop"span.textContent.substring(2, 5), null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
  })();


  // -------- Test dragging contenteditable to <textarea>
  await (async function test_dragging_contenteditable_to_textarea_element() {
    const description = "dragging text in contenteditable to <textarea>";
    container.innerHTML = "<div contenteditable>Some <b>bold</b> text</div><textarea></textarea>";
    const contenteditable = document.querySelector("div#container > div");
    const textarea = document.querySelector("div#container > textarea");
    const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
    selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: textarea,
        }
      )
    ) {
      is(contenteditable.innerHTML, "Soext",
        `${description}: Dragged range should be removed from contenteditable`);
      is(textarea.value, "me bold t",
        `${description}: <textarea>.value should be modified`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable and <textarea>`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 2,
                        endContainer: selectionContainers[1], endOffset: 2}],
                      description);
      checkInputEvent(beforeinputEvents[1], textarea"insertFromDrop""me bold t", null, [], description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on contenteditable and <textarea>`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], textarea"insertFromDrop""me bold t", null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging contenteditable to <textarea> (canceling "deleteByDrag")
  await (async function test_dragging_from_contenteditable_to_textarea_and_canceling_delete_by_drag() {
    const description = 'dragging text in contenteditable to <textarea> (canceling "deleteByDrag")';
    container.innerHTML = "<div contenteditable>Some <b>bold</b> text</div><textarea></textarea>";
    const contenteditable = document.querySelector("div#container > div");
    const textarea = document.querySelector("div#container > textarea");
    const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
    selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: textarea,
        }
      )
    ) {
      is(contenteditable.innerHTML, "Some <b>bold</b> text",
        `${description}: Dragged range shouldn't be removed from contenteditable`);
      is(textarea.value, "me bold t",
        `${description}: <textarea>.value should be modified`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable and <textarea>`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 2,
                        endContainer: selectionContainers[1], endOffset: 2}],
                      description);
      checkInputEvent(beforeinputEvents[1], textarea"insertFromDrop""me bold t", null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on <textarea>`);
      checkInputEvent(inputEvents[0], textarea"insertFromDrop""me bold t", null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
  })();

  // -------- Test dragging contenteditable to <textarea> (canceling "insertFromDrop")
  await (async function test_dragging_from_contenteditable_to_textarea_and_canceling_insert_from_drop() {
    const description = 'dragging text in contenteditable to <textarea> (canceling "insertFromDrop")';
    container.innerHTML = "<div contenteditable>Some <b>bold</b> text</div><textarea></textarea>";
    const contenteditable = document.querySelector("div#container > div");
    const textarea = document.querySelector("div#container > textarea");
    const selectionContainers = [contenteditable.firstChild, contenteditable.firstChild.nextSibling.nextSibling];
    selection.setBaseAndExtent(selectionContainers[0], 2, selectionContainers[1], 2);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "me bold t",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "me <b>bold</b> t",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: textarea,
        }
      )
    ) {
      is(contenteditable.innerHTML, "Soext",
        `${description}: Dragged range should be removed from contenteditable`);
      is(textarea.value, "",
        `${description}: <textarea>.value shouldn't be modified`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable and <textarea>`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 2,
                        endContainer: selectionContainers[1], endOffset: 2}],
                      description);
      checkInputEvent(beforeinputEvents[1], textarea"insertFromDrop""me bold t", null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
  })();

  // -------- Test dragging contenteditable to same contenteditable
  // Bug 1904272: Android Non-XOrigin incorrectly inserts after the 3rd M
  // instead of after the 2nd M in some of the following tests.
  const isAndroidException = AppConstants.platform === "android" && !isXOrigin;

  await (async function test_dragging_from_contenteditable_to_itself() {
    const description = "dragging text in contenteditable to same contenteditable";
    container.innerHTML = "<div contenteditable><b>bold</b> <span>MMMM</span></div>";
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > b");
    const span = document.querySelector("div#container > div > span");
    const lastTextNode = span.firstChild;
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: span,
        }
      )
    ) {
      const kExpectedOffsets = isAndroidException ? [3,3] : [2,2];
      if (isAndroidException) {
        todo_is(contenteditable.innerHTML, "<b>bd</b> <span>MM</span><b>ol</b><span>MM</span>",
          `${description}: dragged range should be removed from contenteditable`);
        isnot(contenteditable.innerHTML, "<b>bd</b> <span>MMMM</span><b>ol</b>",
          `${description}: dragged range should be removed from contenteditable`);
      } else {
        is(contenteditable.innerHTML, "<b>bd</b> <span>MM</span><b>ol</b><span>MM</span>",
          `${description}: dragged range should be removed from contenteditable`);
      }
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: lastTextNode, startOffset: kExpectedOffsets[0],
                        endContainer: lastTextNode, endOffset: kExpectedOffsets[1]}],
                      description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging contenteditable to same contenteditable (canceling "deleteByDrag")
  await (async function test_dragging_from_contenteditable_to_itself_and_canceling_delete_by_drag() {
    const description = 'dragging text in contenteditable to same contenteditable (canceling "deleteByDrag")';
    container.innerHTML = "<div contenteditable><b>bold</b> <span>MMMM</span></div>";
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > b");
    const span = document.querySelector("div#container > div > span");
    const lastTextNode = span.firstChild;
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: span,
        }
      )
    ) {
      const kExpectedOffsets = isAndroidException ? [3,3] : [2,2];
      if (isAndroidException) {
        todo_is(contenteditable.innerHTML, "<b>bold</b> <span>MM</span><b>ol</b><span>MM</span>",
          `${description}: dragged range shouldn't be removed from contenteditable`);
        isnot(contenteditable.innerHTML, "<b>bold</b> <span>MMMM</span><b>ol</b>",
          `${description}: dragged range shouldn't be removed from contenteditable`);
      } else {
        is(contenteditable.innerHTML, "<b>bold</b> <span>MM</span><b>ol</b><span>MM</span>",
          `${description}: dragged range shouldn't be removed from contenteditable`);
      }
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: lastTextNode, startOffset: kExpectedOffsets[0],
                        endContainer: lastTextNode, endOffset: kExpectedOffsets[1]}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
  })();

  // -------- Test dragging contenteditable to same contenteditable (canceling "insertFromDrop")
  await (async function test_dragging_from_contenteditable_to_itself_and_canceling_insert_from_drop() {
    const description = 'dragging text in contenteditable to same contenteditable (canceling "insertFromDrop")';
    container.innerHTML = "<div contenteditable><b>bold</b> <span>MMMM</span></div>";
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > b");
    const span = document.querySelector("div#container > div > span");
    const lastTextNode = span.firstChild;
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: span,
        }
      )
    ) {
      is(contenteditable.innerHTML, "<b>bd</b> <span>MMMM</span>",
        `${description}: dragged range should be removed from contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      const kExpectedOffsets = isAndroidException ? [3,3] : [2,2];
      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: lastTextNode, startOffset: kExpectedOffsets[0],
                        endContainer: lastTextNode, endOffset: kExpectedOffsets[1]}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
  })();

  // -------- Test copy-dragging contenteditable to same contenteditable
  await (async function test_copy_dragging_from_contenteditable_to_itself() {
    const description = "copy-dragging text in contenteditable to same contenteditable";
    container.innerHTML = "<div contenteditable><b>bold</b> <span>MMMM</span></div>";
    document.documentElement.scrollTop;
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > b");
    const span = document.querySelector("div#container > div > span");
    const lastTextNode = span.firstChild;
    selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: span,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      const kExpectedOffsets = isAndroidException ? [3,3] : [2,2];
      if (isAndroidException) {
        todo_is(contenteditable.innerHTML, "<b>bold</b> <span>MM</span><b>ol</b><span>MM</span>",
          `${description}: dragged range shouldn't be removed from contenteditable`);
        isnot(contenteditable.innerHTML, "<b>bold</b> <span>MMMM</span><b>ol</b>",
          `${description}: dragged range shouldn't be removed from contenteditable`);
      } else {
        is(contenteditable.innerHTML, "<b>bold</b> <span>MM</span><b>ol</b><span>MM</span>",
          `${description}: dragged range shouldn't be removed from contenteditable`);
      }
      is(beforeinputEvents.length, 1,
        `${description}: only 1 "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: lastTextNode, startOffset: kExpectedOffsets[0],
                        endContainer: lastTextNode, endOffset: kExpectedOffsets[1]}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only 1 "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging contenteditable to other contenteditable
  await (async function test_dragging_from_contenteditable_to_other_contenteditable() {
    const description = "dragging text in contenteditable to other contenteditable";
    container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable style="min-height: 3em;"></div>';
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > b");
    const otherContenteditable = document.querySelector("div#container > div ~ div");
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: otherContenteditable,
        }
      )
    ) {
      is(contenteditable.innerHTML, "<b>bd</b>",
        `${description}: dragged range should be removed from contenteditable`);
      is(otherContenteditable.innerHTML, "<b>ol</b>",
        `${description}: dragged content should be inserted into other contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: otherContenteditable, startOffset: 0,
                        endContainer: otherContenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging contenteditable to other contenteditable (canceling "deleteByDrag")
  await (async function test_dragging_from_contenteditable_to_other_contenteditable_and_canceling_delete_by_drag() {
    const description = 'dragging text in contenteditable to other contenteditable (canceling "deleteByDrag")';
    container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable style="min-height: 3em;"></div>';
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > b");
    const otherContenteditable = document.querySelector("div#container > div ~ div");
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: otherContenteditable,
        }
      )
    ) {
      is(contenteditable.innerHTML, "<b>bold</b>",
        `${description}: dragged range shouldn't be removed from contenteditable`);
      is(otherContenteditable.innerHTML, "<b>ol</b>",
        `${description}: dragged content should be inserted into other contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: otherContenteditable, startOffset: 0,
                        endContainer: otherContenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on other contenteditable`);
      checkInputEvent(inputEvents[0], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
  })();

  // -------- Test dragging contenteditable to other contenteditable (canceling "insertFromDrop")
  await (async function test_dragging_from_contenteditable_to_other_contenteditable_and_canceling_insert_from_drop() {
    const description = 'dragging text in contenteditable to other contenteditable (canceling "insertFromDrop")';
    container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable style="min-height: 3em;"></div>';
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > b");
    const otherContenteditable = document.querySelector("div#container > div ~ div");
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: otherContenteditable,
        }
      )
    ) {
      is(contenteditable.innerHTML, "<b>bd</b>",
        `${description}: dragged range should be removed from contenteditable`);
      is(otherContenteditable.innerHTML, "",
        `${description}: dragged content shouldn't be inserted into other contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: otherContenteditable, startOffset: 0,
                        endContainer: otherContenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
  })();

  // -------- Test copy-dragging contenteditable to other contenteditable
  await (async function test_copy_dragging_from_contenteditable_to_other_contenteditable() {
    const description = "copy-dragging text in contenteditable to other contenteditable";
    container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable style="min-height: 3em;"></div>';
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > b");
    const otherContenteditable = document.querySelector("div#container > div ~ div");
    selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: otherContenteditable,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      is(contenteditable.innerHTML, "<b>bold</b>",
        `${description}: dragged range shouldn't be removed from contenteditable`);
      is(otherContenteditable.innerHTML, "<b>ol</b>",
        `${description}: dragged content should be inserted into other contenteditable`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on other contenteditable`);
      checkInputEvent(beforeinputEvents[0], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: otherContenteditable, startOffset: 0,
                        endContainer: otherContenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on other contenteditable`);
      checkInputEvent(inputEvents[0], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging nested contenteditable to contenteditable
  await (async function test_dragging_from_nested_contenteditable_to_contenteditable() {
    const description = "dragging text in nested contenteditable to contenteditable";
    container.innerHTML = '<div contenteditable><p><br></p><div contenteditable="false"><p contenteditable><b>bold</b></p></div></div>';
    const contenteditable = document.querySelector("div#container > div");
    const otherContenteditable = document.querySelector("div#container > div > div > p");
    const b = document.querySelector("div#container > div > div > p > b");
    contenteditable.focus();
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: contenteditable.firstChild,
        }
      )
    ) {
      is(contenteditable.innerHTML, '<p><b>ol</b></p><div contenteditable="false"><p contenteditable=""><b>bd</b></p></div>',
        `${description}: dragged range should be moved from nested contenteditable to the contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], otherContenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: contenteditable.firstChild, startOffset: 0,
                        endContainer: contenteditable.firstChild, endOffset: 0}],
                      description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], otherContenteditable, "deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging nested contenteditable to contenteditable (canceling "deleteByDrag")
  await (async function test_dragging_from_nested_contenteditable_to_contenteditable_canceling_delete_by_drag() {
    const description = 'dragging text in nested contenteditable to contenteditable (canceling "deleteByDrag")';
    container.innerHTML = '<div contenteditable><p><br></p><div contenteditable="false"><p contenteditable><b>bold</b></p></div></div>';
    const contenteditable = document.querySelector("div#container > div");
    const otherContenteditable = document.querySelector("div#container > div > div > p");
    const b = document.querySelector("div#container > div > div > p > b");
    contenteditable.focus();
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: contenteditable.firstChild,
        }
      )
    ) {
      is(contenteditable.innerHTML, '<p><b>ol</b></p><div contenteditable="false"><p contenteditable=""><b>bold</b></p></div>',
        `${description}: dragged range should be copied from nested contenteditable to the contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], otherContenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: contenteditable.firstChild, startOffset: 0,
                        endContainer: contenteditable.firstChild, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
  })();

  // -------- Test dragging nested contenteditable to contenteditable (canceling "insertFromDrop")
  await (async function test_dragging_from_nested_contenteditable_to_contenteditable_canceling_insert_from_drop() {
    const description = 'dragging text in nested contenteditable to contenteditable (canceling "insertFromDrop")';
    container.innerHTML = '<div contenteditable><p><br></p><div contenteditable="false"><p contenteditable><b>bold</b></p></div></div>';
    const contenteditable = document.querySelector("div#container > div");
    const otherContenteditable = document.querySelector("div#container > div > div > p");
    const b = document.querySelector("div#container > div > div > p > b");
    contenteditable.focus();
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: contenteditable.firstChild,
        }
      )
    ) {
      is(contenteditable.innerHTML, '<p><br></p><div contenteditable="false"><p contenteditable=""><b>bd</b></p></div>',
        `${description}: dragged range should be removed from nested contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], otherContenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: contenteditable.firstChild, startOffset: 0,
                        endContainer: contenteditable.firstChild, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on nested contenteditable`);
      checkInputEvent(inputEvents[0], otherContenteditable, "deleteByDrag", null, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
  })();

  // -------- Test copy-dragging nested contenteditable to contenteditable
  await (async function test_copy_dragging_from_nested_contenteditable_to_contenteditable() {
    const description = "copy-dragging text in nested contenteditable to contenteditable";
    container.innerHTML = '<div contenteditable><p><br></p><div contenteditable="false"><p contenteditable><b>bold</b></p></div></div>';
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > div > p > b");
    contenteditable.focus();
    selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: contenteditable.firstChild,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      is(contenteditable.innerHTML, '<p><b>ol</b></p><div contenteditable="false"><p contenteditable=""><b>bold</b></p></div>',
        `${description}: dragged range should be moved from nested contenteditable to the contenteditable`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: contenteditable.firstChild, startOffset: 0,
                        endContainer: contenteditable.firstChild, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging contenteditable to nested contenteditable
  await (async function test_dragging_from_contenteditable_to_nested_contenteditable() {
    const description = "dragging text in contenteditable to nested contenteditable";
    container.innerHTML = '<div contenteditable><p><b>bold</b></p><div contenteditable="false"><p contenteditable><br></p></div></div>';
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > p > b");
    const otherContenteditable = document.querySelector("div#container > div > div > p");
    contenteditable.focus();
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: otherContenteditable,
        }
      )
    ) {
      is(contenteditable.innerHTML, '<p><b>bd</b></p><div contenteditable="false"><p contenteditable=""><b>ol</b></p></div>',
        `${description}: dragged range should be moved from contenteditable to nested contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable and nested contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: otherContenteditable, startOffset: 0,
                        endContainer: otherContenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on contenteditable and nested contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging contenteditable to nested contenteditable (canceling "deleteByDrag")
  await (async function test_dragging_from_contenteditable_to_nested_contenteditable_and_canceling_delete_by_drag() {
    const description = 'dragging text in contenteditable to nested contenteditable (canceling "deleteByDrag")';
    container.innerHTML = '<div contenteditable><p><b>bold</b></p><div contenteditable="false"><p contenteditable><br></p></div></div>';
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > p > b");
    const otherContenteditable = document.querySelector("div#container > div > div > p");
    contenteditable.focus();
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: otherContenteditable,
        }
      )
    ) {
      is(contenteditable.innerHTML, '<p><b>bold</b></p><div contenteditable="false"><p contenteditable=""><b>ol</b></p></div>',
        `${description}: dragged range should be copied from contenteditable to nested contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable and nested contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: otherContenteditable, startOffset: 0,
                        endContainer: otherContenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on contenteditable and nested contenteditable`);
      checkInputEvent(inputEvents[0], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
  })();

  // -------- Test dragging contenteditable to nested contenteditable (canceling "insertFromDrop")
  await (async function test_dragging_from_contenteditable_to_nested_contenteditable_and_canceling_insert_from_drop() {
    const description = 'dragging text in contenteditable to nested contenteditable (canceling "insertFromDrop")';
    container.innerHTML = '<div contenteditable><p><b>bold</b></p><div contenteditable="false"><p contenteditable><br></p></div></div>';
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > p > b");
    const otherContenteditable = document.querySelector("div#container > div > div > p");
    contenteditable.focus();
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: otherContenteditable,
        }
      )
    ) {
      is(contenteditable.innerHTML, '<p><b>bd</b></p><div contenteditable="false"><p contenteditable=""><br></p></div>',
        `${description}: dragged range should be removed from contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable and nested contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: otherContenteditable, startOffset: 0,
                        endContainer: otherContenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
  })();

  // -------- Test copy-dragging contenteditable to nested contenteditable
  await (async function test_copy_dragging_from_contenteditable_to_nested_contenteditable() {
    const description = "copy-dragging text in contenteditable to nested contenteditable";
    container.innerHTML = '<div contenteditable><p><b>bold</b></p><div contenteditable="false"><p contenteditable><br></p></div></div>';
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > p > b");
    const otherContenteditable = document.querySelector("div#container > div > div > p");
    contenteditable.focus();
    selection.setBaseAndExtent(b.firstChild, 1, b.firstChild, 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: otherContenteditable,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      is(contenteditable.innerHTML, '<p><b>bold</b></p><div contenteditable="false"><p contenteditable=""><b>ol</b></p></div>',
        `${description}: dragged range should be moved from nested contenteditable to the contenteditable`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: otherContenteditable, startOffset: 0,
                        endContainer: otherContenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text in <input> to contenteditable
  await (async function test_dragging_from_input_element_to_contenteditable() {
    const description = "dragging text in <input> to contenteditable";
    container.innerHTML = '<input value="Some Text"><div contenteditable><br></div>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const contenteditable = document.querySelector("div#container > div");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: contenteditable,
        }
      )
    ) {
      is(input.value, "Somt",
        `${description}: dragged range should be removed from <input>`);
      is(contenteditable.innerHTML, "e Tex",
        `${description}: dragged content should be inserted into contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
      checkInputEvent(beforeinputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/plain", data: "e Tex"}],
                      [{startContainer: contenteditable, startOffset: 0,
                        endContainer: contenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on <input> and contenteditable`);
      checkInputEvent(inputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/plain", data: "e Tex"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text in <input> to contenteditable (canceling "deleteByDrag")
  await (async function test_dragging_from_input_element_to_contenteditable_and_canceling_delete_by_drag() {
    const description = 'dragging text in <input> to contenteditable (canceling "deleteByDrag")';
    container.innerHTML = '<input value="Some Text"><div contenteditable><br></div>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const contenteditable = document.querySelector("div#container > div");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: contenteditable,
        }
      )
    ) {
      is(input.value, "Some Text",
        `${description}: dragged range shouldn't be removed from <input>`);
      is(contenteditable.innerHTML, "e Tex",
        `${description}: dragged content should be inserted into contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
      checkInputEvent(beforeinputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/plain", data: "e Tex"}],
                      [{startContainer: contenteditable, startOffset: 0,
                        endContainer: contenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
                      [{type: "text/plain", data: "e Tex"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
  })();

  // -------- Test dragging text in <input> to contenteditable (canceling "insertFromDrop")
  await (async function test_dragging_from_input_element_to_contenteditable_and_canceling_insert_from_drop() {
    const description = 'dragging text in <input> to contenteditable (canceling "insertFromDrop")';
    container.innerHTML = '<input value="Some Text"><div contenteditable><br></div>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const contenteditable = document.querySelector("div#container > div");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: contenteditable,
        }
      )
    ) {
      is(input.value, "Somt",
        `${description}: dragged range should be removed from <input>`);
      is(contenteditable.innerHTML, "<br>",
        `${description}: dragged content shouldn't be inserted into contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
      checkInputEvent(beforeinputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/plain", data: "e Tex"}],
                      [{startContainer: contenteditable, startOffset: 0,
                        endContainer: contenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on <input>`);
      checkInputEvent(inputEvents[0], input"deleteByDrag", null, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
  })();

  // -------- Test copy-dragging text in <input> to contenteditable
  await (async function test_copy_dragging_from_input_element_to_contenteditable() {
    const description = "copy-dragging text in <input> to contenteditable";
    container.innerHTML = '<input value="Some Text"><div contenteditable><br></div>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const contenteditable = document.querySelector("div#container > div");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: contenteditable,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      is(input.value, "Some Text",
        `${description}: dragged range shouldn't be removed from <input>`);
      is(contenteditable.innerHTML, "e Tex",
        `${description}: dragged content should be inserted into contenteditable`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
                      [{type: "text/plain", data: "e Tex"}],
                      [{startContainer: contenteditable, startOffset: 0,
                        endContainer: contenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
                      [{type: "text/plain", data: "e Tex"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text in <textarea> to contenteditable
  await (async function test_dragging_from_textarea_element_to_contenteditable() {
    const description = "dragging text in <textarea> to contenteditable";
    container.innerHTML = '<textarea>Line1\nLine2</textarea><div contenteditable><br></div>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const textarea = document.querySelector("div#container > textarea");
    const contenteditable = document.querySelector("div#container > div");
    textarea.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: contenteditable,
        }
      )
    ) {
      is(textarea.value, "Linne2",
        `${description}: dragged range should be removed from <textarea>`);
      is(contenteditable.innerHTML, "e1<br>Li",
        `${description}: dragged content should be inserted into contenteditable`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
      checkInputEvent(beforeinputEvents[0], textarea"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/plain", data: `e1${kNativeLF}Li`}],
                      [{startContainer: contenteditable, startOffset: 0,
                        endContainer: contenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on <input> and contenteditable`);
      checkInputEvent(inputEvents[0], textarea"deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/plain", data: `e1${kNativeLF}Li`}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test copy-dragging text in <textarea> to contenteditable
  await (async function test_copy_dragging_from_textarea_element_to_contenteditable() {
    const description = "copy-dragging text in <textarea> to contenteditable";
    container.innerHTML = '<textarea>Line1\nLine2</textarea><div contenteditable><br></div>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const textarea = document.querySelector("div#container > textarea");
    const contenteditable = document.querySelector("div#container > div");
    textarea.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: contenteditable,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      is(textarea.value, "Line1\nLine2",
        `${description}: dragged range should be removed from <textarea>`);
      is(contenteditable.innerHTML, "e1<br>Li",
        `${description}: dragged content should be inserted into contenteditable`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "insertFromDrop", null,
                      [{type: "text/plain", data: `e1${kNativeLF}Li`}],
                      [{startContainer: contenteditable, startOffset: 0,
                        endContainer: contenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "insertFromDrop", null,
                      [{type: "text/plain", data: `e1${kNativeLF}Li`}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text in <input> to other <input>
  await (async function test_dragging_from_input_element_to_other_input_element() {
    const description = "dragging text in <input> to other <input>";
    container.innerHTML = '<input value="Some Text"><input>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const otherInput = document.querySelector("div#container > input + input");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: otherInput,
        }
      )
    ) {
      is(input.value, "Somt",
        `${description}: dragged range should be removed from <input>`);
      is(otherInput.value, "e Tex",
        `${description}: dragged content should be inserted into other <input>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <input> and other <input>`);
      checkInputEvent(beforeinputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], otherInput, "insertFromDrop""e Tex", null, [], description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on <input> and other <input>`);
      checkInputEvent(inputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], otherInput, "insertFromDrop""e Tex", null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other <input>`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text in <input> to other <input> (canceling "deleteByDrag")
  await (async function test_dragging_from_input_element_to_other_input_element_and_canceling_delete_by_drag() {
    const description = 'dragging text in <input> to other <input> (canceling "deleteByDrag")';
    container.innerHTML = '<input value="Some Text"><input>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const otherInput = document.querySelector("div#container > input + input");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: otherInput,
        }
      )
    ) {
      is(input.value, "Some Text",
        `${description}: dragged range shouldn't be removed from <input>`);
      is(otherInput.value, "e Tex",
        `${description}: dragged content should be inserted into other <input>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <input> and other <input>`);
      checkInputEvent(beforeinputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], otherInput, "insertFromDrop""e Tex", null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on other <input>`);
      checkInputEvent(inputEvents[0], otherInput, "insertFromDrop""e Tex", null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other <input>`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
  })();

  // -------- Test dragging text in <input> to other <input> (canceling "insertFromDrop")
  await (async function test_dragging_from_input_element_to_other_input_element_and_canceling_insert_from_drop() {
    const description = 'dragging text in <input> to other <input> (canceling "insertFromDrop")';
    container.innerHTML = '<input value="Some Text"><input>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const otherInput = document.querySelector("div#container > input + input");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: otherInput,
        },
      )
    ) {
      is(input.value, "Somt",
        `${description}: dragged range should be removed from <input>`);
      is(otherInput.value, "",
        `${description}: dragged content shouldn't be inserted into other <input>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <input> and other <input>`);
      checkInputEvent(beforeinputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], otherInput, "insertFromDrop""e Tex", null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on <input>`);
      checkInputEvent(inputEvents[0], input"deleteByDrag", null, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other <input>`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
  })();

  // -------- Test copy-dragging text in <input> to other <input>
  await (async function test_copy_dragging_from_input_element_to_other_input_element() {
    const description = "copy-dragging text in <input> to other <input>";
    container.innerHTML = '<input value="Some Text"><input>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const otherInput = document.querySelector("div#container > input + input");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: otherInput,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      is(input.value, "Some Text",
        `${description}: dragged range shouldn't be removed from <input>`);
      is(otherInput.value, "e Tex",
        `${description}: dragged content should be inserted into other <input>`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on  other <input>`);
      checkInputEvent(beforeinputEvents[0], otherInput, "insertFromDrop""e Tex", null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on  other <input>`);
      checkInputEvent(inputEvents[0], otherInput, "insertFromDrop""e Tex", null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other <input>`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text in <input> to <textarea>
  await (async function test_dragging_from_input_element_to_textarea_element() {
    const description = "dragging text in <input> to other <textarea>";
    container.innerHTML = '<input value="Some Text"><textarea></textarea>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const textarea = document.querySelector("div#container > textarea");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: textarea,
        }
      )
    ) {
      is(input.value, "Somt",
        `${description}: dragged range should be removed from <input>`);
      is(textarea.value, "e Tex",
        `${description}: dragged content should be inserted into <textarea>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <input> and <textarea>`);
      checkInputEvent(beforeinputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], textarea"insertFromDrop""e Tex", null, [], description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on <input> and <textarea>`);
      checkInputEvent(inputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], textarea"insertFromDrop""e Tex", null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text in <input> to <textarea> (canceling "deleteByDrag")
  await (async function test_dragging_from_input_element_to_textarea_element_and_canceling_delete_by_drag() {
    const description = 'dragging text in <input> to other <textarea> (canceling "deleteByDrag")';
    container.innerHTML = '<input value="Some Text"><textarea></textarea>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const textarea = document.querySelector("div#container > textarea");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: textarea,
        }
      )
    ) {
      is(input.value, "Some Text",
        `${description}: dragged range shouldn't be removed from <input>`);
      is(textarea.value, "e Tex",
        `${description}: dragged content should be inserted into <textarea>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <input> and <textarea>`);
      checkInputEvent(beforeinputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], textarea"insertFromDrop""e Tex", null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on <textarea>`);
      checkInputEvent(inputEvents[0], textarea"insertFromDrop""e Tex", null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
  })();

  // -------- Test dragging text in <input> to <textarea> (canceling "insertFromDrop")
  await (async function test_dragging_from_input_element_to_textarea_element_and_canceling_insert_from_drop() {
    const description = 'dragging text in <input> to other <textarea> (canceling "insertFromDrop")';
    container.innerHTML = '<input value="Some Text"><textarea></textarea>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const textarea = document.querySelector("div#container > textarea");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: textarea,
        }
      )
    ) {
      is(input.value, "Somt",
        `${description}: dragged range should be removed from <input>`);
      is(textarea.value, "",
        `${description}: dragged content shouldn't be inserted into <textarea>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <input> and <textarea>`);
      checkInputEvent(beforeinputEvents[0], input"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], textarea"insertFromDrop""e Tex", null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on <input>`);
      checkInputEvent(inputEvents[0], input"deleteByDrag", null, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
  })();

  // -------- Test copy-dragging text in <input> to <textarea>
  await (async function test_copy_dragging_from_input_element_to_textarea_element() {
    const description = "copy-dragging text in <input> to <textarea>";
    container.innerHTML = '<input value="Some Text"><textarea></textarea>';
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const input = document.querySelector("div#container > input");
    const textarea = document.querySelector("div#container > textarea");
    input.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), input.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: textarea,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      is(input.value, "Some Text",
        `${description}: dragged range shouldn't be removed from <input>`);
      is(textarea.value, "e Tex",
        `${description}: dragged content should be inserted into <textarea>`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on  <textarea>`);
      checkInputEvent(beforeinputEvents[0], textarea"insertFromDrop""e Tex", null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on  <textarea>`);
      checkInputEvent(inputEvents[0], textarea"insertFromDrop""e Tex", null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text in <textarea> to <input>
  await (async function test_dragging_from_textarea_element_to_input_element() {
    const description = "dragging text in <textarea> to <input>";
    container.innerHTML = "<textarea>Line1\nLine2</textarea><input>";
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const textarea = document.querySelector("div#container > textarea");
    const input = document.querySelector("div#container > input");
    textarea.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: input,
        }
      )
    ) {
      is(textarea.value, "Linne2",
        `${description}: dragged range should be removed from <textarea>`);
      is(input.value, "e1 Li",
        `${description}: dragged content should be inserted into <input>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <textarea> and <input>`);
      checkInputEvent(beforeinputEvents[0], textarea"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], input"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on <textarea> and <input>`);
      checkInputEvent(inputEvents[0], textarea"deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], input"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text in <textarea> to <input> (canceling "deleteByDrag")
  await (async function test_dragging_from_textarea_element_to_input_element_and_delete_by_drag() {
    const description = 'dragging text in <textarea> to <input> (canceling "deleteByDrag")';
    container.innerHTML = "<textarea>Line1\nLine2</textarea><input>";
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const textarea = document.querySelector("div#container > textarea");
    const input = document.querySelector("div#container > input");
    textarea.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: input,
        }
      )
    ) {
      is(textarea.value, "Line1\nLine2",
        `${description}: dragged range shouldn't be removed from <textarea>`);
      is(input.value, "e1 Li",
        `${description}: dragged content should be inserted into <input>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <textarea> and <input>`);
      checkInputEvent(beforeinputEvents[0], textarea"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], input"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on <input>`);
      checkInputEvent(inputEvents[0], input"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
  })();

  // -------- Test dragging text in <textarea> to <input> (canceling "insertFromDrop")
  await (async function test_dragging_from_textarea_element_to_input_element_and_canceling_insert_from_drop() {
    const description = 'dragging text in <textarea> to <input> (canceling "insertFromDrop")';
    container.innerHTML = "<textarea>Line1\nLine2</textarea><input>";
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const textarea = document.querySelector("div#container > textarea");
    const input = document.querySelector("div#container > input");
    textarea.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: input,
        }
      )
    ) {
      is(textarea.value, "Linne2",
        `${description}: dragged range should be removed from <textarea>`);
      is(input.value, "",
        `${description}: dragged content shouldn't be inserted into <input>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <textarea> and <input>`);
      checkInputEvent(beforeinputEvents[0], textarea"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], input"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on <textarea>`);
      checkInputEvent(inputEvents[0], textarea"deleteByDrag", null, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
  })();

  // -------- Test copy-dragging text in <textarea> to <input>
  await (async function test_copy_dragging_from_textarea_element_to_input_element() {
    const description = "copy-dragging text in <textarea> to <input>";
    container.innerHTML = "<textarea>Line1\nLine2</textarea><input>";
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const textarea = document.querySelector("div#container > textarea");
    const input = document.querySelector("div#container > input");
    textarea.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: input,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      is(textarea.value, "Line1\nLine2",
        `${description}: dragged range shouldn't be removed from <textarea>`);
      is(input.value, "e1 Li",
        `${description}: dragged content should be inserted into <input>`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on <input>`);
      checkInputEvent(beforeinputEvents[0], input"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on <input>`);
      checkInputEvent(inputEvents[0], input"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text in <textarea> to other <textarea>
  await (async function test_dragging_from_textarea_element_to_other_textarea_element() {
    const description = "dragging text in <textarea> to other <textarea>";
    container.innerHTML = "<textarea>Line1\nLine2</textarea><textarea></textarea>";
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const textarea = document.querySelector("div#container > textarea");
    const otherTextarea = document.querySelector("div#container > textarea + textarea");
    textarea.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: otherTextarea,
        }
      )
    ) {
      is(textarea.value, "Linne2",
        `${description}: dragged range should be removed from <textarea>`);
      is(otherTextarea.value, "e1\nLi",
        `${description}: dragged content should be inserted into other <textarea>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <textarea> and other <textarea>`);
      checkInputEvent(beforeinputEvents[0], textarea"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on <textarea> and other <textarea>`);
      checkInputEvent(inputEvents[0], textarea"deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text in <textarea> to other <textarea> (canceling "deleteByDrag")
  await (async function test_dragging_from_textarea_element_to_other_textarea_element_and_canceling_delete_by_drag() {
    const description = 'dragging text in <textarea> to other <textarea> (canceling "deleteByDrag")';
    container.innerHTML = "<textarea>Line1\nLine2</textarea><textarea></textarea>";
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const textarea = document.querySelector("div#container > textarea");
    const otherTextarea = document.querySelector("div#container > textarea + textarea");
    textarea.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultDeleteByDrag);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: otherTextarea,
        }
      )
    ) {
      is(textarea.value, "Line1\nLine2",
        `${description}: dragged range shouldn't be removed from <textarea>`);
      is(otherTextarea.value, "e1\nLi",
        `${description}: dragged content should be inserted into other <textarea>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <textarea> and other <textarea>`);
      checkInputEvent(beforeinputEvents[0], textarea"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on other <textarea>`);
      checkInputEvent(inputEvents[0], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultDeleteByDrag);
  })();

  // -------- Test dragging text in <textarea> to other <textarea> (canceling "insertFromDrop")
  await (async function test_dragging_from_textarea_element_to_other_textarea_element_and_canceling_insert_from_drop() {
    const description = 'dragging text in <textarea> to other <textarea> (canceling "insertFromDrop")';
    container.innerHTML = "<textarea>Line1\nLine2</textarea><textarea></textarea>";
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const textarea = document.querySelector("div#container > textarea");
    const otherTextarea = document.querySelector("div#container > textarea + textarea");
    textarea.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: otherTextarea,
        }
      )
    ) {
      is(textarea.value, "Linne2",
        `${description}: dragged range should be removed from <textarea>`);
      is(otherTextarea.value, "",
        `${description}: dragged content shouldn't be inserted into other <textarea>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <textarea> and other <textarea>`);
      checkInputEvent(beforeinputEvents[0], textarea"deleteByDrag", null, null, [], description);
      checkInputEvent(beforeinputEvents[1], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" event should be fired on <textarea>`);
      checkInputEvent(inputEvents[0], textarea"deleteByDrag", null, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
    document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
  })();

  // -------- Test copy-dragging text in <textarea> to other <textarea>
  await (async function test_copy_dragging_from_textarea_element_to_other_textarea_element() {
    const description = "copy-dragging text in <textarea> to other <textarea>";
    container.innerHTML = "<textarea>Line1\nLine2</textarea><textarea></textarea>";
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    const textarea = document.querySelector("div#container > textarea");
    const otherTextarea = document.querySelector("div#container > textarea + textarea");
    textarea.setSelectionRange(3, 8);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), textarea.value.substring(3, 8),
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should have not have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: otherTextarea,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      is(textarea.value, "Line1\nLine2",
        `${description}: dragged range shouldn't be removed from <textarea>`);
      is(otherTextarea.value, "e1\nLi",
        `${description}: dragged content should be inserted into other <textarea>`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on other <textarea>`);
      checkInputEvent(beforeinputEvents[0], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on other <textarea>`);
      checkInputEvent(inputEvents[0], otherTextarea, "insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on <textarea>`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging multiple-line text in contenteditable to <input>
  await (async function test_dragging_multiple_line_text_in_contenteditable_to_input_element() {
    const description = "dragging multiple-line text in contenteditable to <input>";
    container.innerHTML = '<div contenteditable><div>Line1</div><div>Line2</div></div><input>';
    const contenteditable = document.querySelector("div#container > div");
    const input = document.querySelector("div#container > input");
    const selectionContainers = [contenteditable.firstChild.firstChild, contenteditable.firstChild.nextSibling.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 3, selectionContainers[1], 2);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), `e1\nLi`,
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<div>e1</div><div>Li</div>",
        `${description}: dataTransfer should have have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: input,
        }
      )
    ) {
      is(contenteditable.innerHTML, "<div>Linne2</div>",
        `${description}: dragged content should be removed from contenteditable`);
      is(input.value, "e1 Li",
        `${description}: dragged range should be inserted into <input>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <input> and contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 3,
                        endContainer: selectionContainers[1], endOffset: 2}],
                      description);
      checkInputEvent(beforeinputEvents[1], input"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on <input> and contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], input"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test copy-dragging multiple-line text in contenteditable to <input>
  await (async function test_copy_dragging_multiple_line_text_in_contenteditable_to_input_element() {
    const description = "copy-dragging multiple-line text in contenteditable to <input>";
    container.innerHTML = '<div contenteditable><div>Line1</div><div>Line2</div></div><input>';
    const contenteditable = document.querySelector("div#container > div");
    const input = document.querySelector("div#container > input");
    selection.setBaseAndExtent(contenteditable.firstChild.firstChild, 3,
                              contenteditable.firstChild.nextSibling.firstChild, 2);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), `e1\nLi`,
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<div>e1</div><div>Li</div>",
        `${description}: dataTransfer should have have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: input,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      is(contenteditable.innerHTML, "<div>Line1</div><div>Line2</div>",
        `${description}: dragged content should be removed from contenteditable`);
      is(input.value, "e1 Li",
        `${description}: dragged range should be inserted into <input>`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], input"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], input"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging multiple-line text in contenteditable to <textarea>
  await (async function test_dragging_multiple_line_text_in_contenteditable_to_textarea_element() {
    const description = "dragging multiple-line text in contenteditable to <textarea>";
    container.innerHTML = '<div contenteditable><div>Line1</div><div>Line2</div></div><textarea></textarea>';
    const contenteditable = document.querySelector("div#container > div");
    const textarea = document.querySelector("div#container > textarea");
    const selectionContainers = [contenteditable.firstChild.firstChild, contenteditable.firstChild.nextSibling.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 3, selectionContainers[1], 2);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), `e1\nLi`,
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<div>e1</div><div>Li</div>",
        `${description}: dataTransfer should have have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: textarea,
        }
      )
    ) {
      is(contenteditable.innerHTML, "<div>Linne2</div>",
        `${description}: dragged content should be removed from contenteditable`);
      is(textarea.value, "e1\nLi",
        `${description}: dragged range should be inserted into <textarea>`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on <textarea> and contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 3,
                        endContainer: selectionContainers[1], endOffset: 2}],
                      description);
      checkInputEvent(beforeinputEvents[1], textarea"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on <textarea> and contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], textarea"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test copy-dragging multiple-line text in contenteditable to <textarea>
  await (async function test_copy_dragging_multiple_line_text_in_contenteditable_to_textarea_element() {
    const description = "copy-dragging multiple-line text in contenteditable to <textarea>";
    container.innerHTML = '<div contenteditable><div>Line1</div><div>Line2</div></div><textarea></textarea>';
    const contenteditable = document.querySelector("div#container > div");
    const textarea = document.querySelector("div#container > textarea");
    selection.setBaseAndExtent(contenteditable.firstChild.firstChild, 3,
                              contenteditable.firstChild.nextSibling.firstChild, 2);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"), `e1\nLi`,
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<div>e1</div><div>Li</div>",
        `${description}: dataTransfer should have have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: textarea,
          dragEvent: kModifiersToCopy,
        }
      )
    ) {
      is(contenteditable.innerHTML, "<div>Line1</div><div>Line2</div>",
        `${description}: dragged content should be removed from contenteditable`);
      is(textarea.value, "e1\nLi",
        `${description}: dragged range should be inserted into <textarea>`);
      is(beforeinputEvents.length, 1,
        `${description}: only one "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], textarea"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(inputEvents.length, 1,
        `${description}: only one "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], textarea"insertFromDrop", `e1${kNativeLF}Li`, null, [], description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text from an <input> and reframing the <input> element before dragend.
  await (async function test_dragging_from_input_element_and_reframing_input_element() {
    const description = "dragging part of text in <input> element and reframing the <input> element before dragend";
    container.innerHTML = '<input value="Drag Me">';
    const input = document.querySelector("div#container > input");
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    input.setSelectionRange(1, 4);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDragStart = () => {
      input.style.display = "none";
      document.documentElement.scrollTop;
      input.style.display = "";
      document.documentElement.scrollTop;
    };
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                      input.value.substring(1, 4),
                      `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should not have data as "text/html"`);
    };
    document.addEventListener("dragStart", onDragStart);
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: dropZone,
        }
      )
    ) {
      is(beforeinputEvents.length, 0,
        `${description}: No "beforeinput" event should be fired when dragging <input> value to non-editable drop zone`);
      is(inputEvents.length, 0,
        `${description}: No "input" event should be fired when dragging <input> value to non-editable drop zone`);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired`);
    }
    document.removeEventListener("dragStart", onDragStart);
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text from an <textarea> and reframing the <textarea> element before dragend.
  await (async function test_dragging_from_textarea_element_and_reframing_textarea_element() {
    const description = "dragging part of text in <textarea> element and reframing the <textarea> element before dragend";
    container.innerHTML = "<textarea>Some Text To Drag</textarea>";
    const textarea = document.querySelector("div#container > textarea");
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    textarea.setSelectionRange(1, 7);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDragStart = () => {
      textarea.style.display = "none";
      document.documentElement.scrollTop;
      textarea.style.display = "";
      document.documentElement.scrollTop;
    };
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                      textarea.value.substring(1, 7),
                      `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should not have data as "text/html"`);
    };
    document.addEventListener("dragStart", onDragStart);
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: dropZone,
        }
      )
    ) {
      is(beforeinputEvents.length, 0,
        `${description}: No "beforeinput" event should be fired when dragging <textarea> value to non-editable drop zone`);
      is(inputEvents.length, 0,
        `${description}: No "input" event should be fired when dragging <textarea> value to non-editable drop zone`);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired`);
    }
    document.removeEventListener("dragStart", onDragStart);
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text from an <input> and reframing the <input> element before dragstart.
  await (async function test_dragging_from_input_element_and_reframing_input_element_before_dragstart() {
    const description = "dragging part of text in <input> element and reframing the <input> element before dragstart";
    container.innerHTML = '<input value="Drag Me">';
    const input = document.querySelector("div#container > input");
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    input.setSelectionRange(1, 4);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onMouseMove = () => {
      input.style.display = "none";
      document.documentElement.scrollTop;
      input.style.display = "";
      document.documentElement.scrollTop;
    };
    const onMouseDown = () => {
      document.addEventListener("mousemove", onMouseMove, {once: true});
    }
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                      input.value.substring(1, 4),
                      `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should not have data as "text/html"`);
    };
    document.addEventListener("mousedown", onMouseDown, {once: true});
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(input).editor.selection,
          destElement: dropZone,
        }
      )
    ) {
      is(beforeinputEvents.length, 0,
        `${description}: No "beforeinput" event should be fired when dragging <input> value to non-editable drop zone`);
      is(inputEvents.length, 0,
        `${description}: No "input" event should be fired when dragging <input> value to non-editable drop zone`);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired`);
    }
    document.removeEventListener("mousedown", onMouseDown);
    document.removeEventListener("mousemove", onMouseMove);
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging text from an <textarea> and reframing the <textarea> element before dragstart.
  await (async function test_dragging_from_textarea_element_and_reframing_textarea_element_before_dragstart() {
    const description = "dragging part of text in <textarea> element and reframing the <textarea> element before dragstart";
    container.innerHTML = "<textarea>Some Text To Drag</textarea>";
    const textarea = document.querySelector("div#container > textarea");
    document.documentElement.scrollTop; // Need reflow to create TextControlState and its colleagues.
    textarea.setSelectionRange(1, 7);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onMouseMove = () => {
      textarea.style.display = "none";
      document.documentElement.scrollTop;
      textarea.style.display = "";
      document.documentElement.scrollTop;
    };
    const onMouseDown = () => {
      document.addEventListener("mousemove", onMouseMove, {once: true});
    }
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      comparePlainText(aEvent.dataTransfer.getData("text/plain"),
                      textarea.value.substring(1, 7),
                      `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "",
        `${description}: dataTransfer should not have data as "text/html"`);
    };
    document.addEventListener("mousedown", onMouseDown, {once: true});
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: SpecialPowers.wrap(textarea).editor.selection,
          destElement: dropZone,
        }
      )
    ) {
      is(beforeinputEvents.length, 0,
        `${description}: No "beforeinput" event should be fired when dragging <textarea> value to non-editable drop zone`);
      is(inputEvents.length, 0,
        `${description}: No "input" event should be fired when dragging <textarea> value to non-editable drop zone`);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired`);
    }
    document.removeEventListener("mousedown", onMouseDown);
    document.removeEventListener("mousemove", onMouseMove);
    document.removeEventListener("drop", onDrop);
  })();

  await (async function test_dragend_when_left_half_of_text_node_dragged_into_textarea() {
    const description = "dragging left half of text in contenteditable into <textarea>";
    container.innerHTML = "<div contenteditable><p>abcdef</p></div><textarea></textarea>";
    const editingHost = container.querySelector("[contenteditable]");
    const textNode = editingHost.querySelector("p").firstChild;
    const textarea = container.querySelector("textarea");
    selection.setBaseAndExtent(textNode, 0, textNode, textNode.length / 2);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDragEnd = aEvent => dragEvents.push(aEvent);
    document.addEventListener("dragend", onDragEnd, {capture: true});
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: textarea,
        }
      )
    ) {
      ok(
        textNode.isConnected,
        `${description}: the text node part of whose text is dragged should not be removed`
      );
      is(
        dragEvents.length,
        1,
        `${description}: only one "dragend" event should be fired`
      );
      is(
        dragEvents[0]?.target,
        textNode,
        `${description}: "dragend" should be fired on the text node which the mouse button down on`
      );
    }
    document.removeEventListener("dragend", onDragEnd, {capture: true});
  })();

  await (async function test_dragend_when_right_half_of_text_node_dragged_into_textarea() {
    const description = "dragging right half of text in contenteditable into <textarea>";
    container.innerHTML = "<div contenteditable><p>abcdef</p></div><textarea></textarea>";
    const editingHost = container.querySelector("[contenteditable]");
    const textNode = editingHost.querySelector("p").firstChild;
    const textarea = container.querySelector("textarea");
    selection.setBaseAndExtent(textNode, textNode.length / 2, textNode, textNode.length);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDragEnd = aEvent => dragEvents.push(aEvent);
    document.addEventListener("dragend", onDragEnd, {capture: true});
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: textarea,
        }
      )
    ) {
      ok(
        textNode.isConnected,
        `${description}: the text node part of whose text is dragged should not be removed`
      );
      is(
        dragEvents.length,
        1,
        `${description}: only one "dragend" event should be fired`
      );
      is(
        dragEvents[0]?.target,
        textNode,
        `${description}: "dragend" should be fired on the text node which the mouse button down on`
      );
    }
    document.removeEventListener("dragend", onDragEnd, {capture: true});
  })();

  await (async function test_dragend_when_middle_part_of_text_node_dragged_into_textarea() {
    const description = "dragging middle of text in contenteditable into <textarea>";
    container.innerHTML = "<div contenteditable><p>abcdef</p></div><textarea></textarea>";
    const editingHost = container.querySelector("[contenteditable]");
    const textNode = editingHost.querySelector("p").firstChild;
    const textarea = container.querySelector("textarea");
    selection.setBaseAndExtent(textNode, "ab".length, textNode, "abcd".length);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDragEnd = aEvent => dragEvents.push(aEvent);
    document.addEventListener("dragend", onDragEnd, {capture: true});
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: textarea,
        }
      )
    ) {
      ok(
        textNode.isConnected,
        `${description}: the text node part of whose text is dragged should not be removed`
      );
      is(
        dragEvents.length,
        1,
        `${description}: only one "dragend" event should be fired`
      );
      is(
        dragEvents[0]?.target,
        textNode,
        `${description}: "dragend" should be fired on the text node which the mouse button down on`
      );
    }
    document.removeEventListener("dragend", onDragEnd, {capture: true});
  })();

  await (async function test_dragend_when_all_of_text_node_dragged_into_textarea() {
    const description = "dragging all of text in contenteditable into <textarea>";
    container.innerHTML = "<div contenteditable><p>abcdef</p></div><textarea></textarea>";
    const editingHost = container.querySelector("[contenteditable]");
    const textNode = editingHost.querySelector("p").firstChild;
    const textarea = container.querySelector("textarea");
    selection.setBaseAndExtent(textNode, 0, textNode, textNode.length);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDragEnd = aEvent => dragEvents.push(aEvent);
    document.addEventListener("dragend", onDragEnd, {capture: true});
    if (
      await trySynthesizePlainDragAndDrop(
          description,
          {
            srcSelection: selection,
            destElement: textarea,
          }
        )
    ) {
      ok(
        !textNode.isConnected,
        `${description}: the text node whose all text is dragged should've been removed from the contenteditable`
      );
      is(
        dragEvents.length,
        1,
        `${description}: only one "dragend" event should be fired`
      );
      is(
        dragEvents[0]?.target,
        editingHost,
        `${description}: "dragend" should be fired on the editing host which is parent of the removed text node`
      );
    }
    document.removeEventListener("dragend", onDragEnd, {capture: true});
  })();

  // -------- Test dragging contenteditable to contenteditable=plaintext-only
  await (async function test_dragging_from_contenteditable_to_contenteditable_plaintext_only() {
    const description = "dragging text in contenteditable to contenteditable=plaintext-only";
    container.innerHTML = '<div contenteditable><b>bold</b></div><hr><div contenteditable="plaintext-only" style="min-height: 3em;"></div>';
    const contenteditable = document.querySelector("div#container > div");
    const b = document.querySelector("div#container > div > b");
    const otherContenteditable = document.querySelector("div#container > div ~ div");
    const selectionContainers = [b.firstChild, b.firstChild];
    selection.setBaseAndExtent(selectionContainers[0], 1, selectionContainers[1], 3);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "ol",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "<b>ol</b>",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: otherContenteditable,
        }
      )
    ) {
      is(contenteditable.innerHTML, "<b>bd</b>",
        `${description}: dragged range should be removed from contenteditable`);
      is(otherContenteditable.innerHTML, "ol",
        `${description}: dragged content should be inserted into other contenteditable without formatting`);
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: selectionContainers[0], startOffset: 1,
                        endContainer: selectionContainers[1], endOffset: 3}],
                      description);
      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [{startContainer: otherContenteditable, startOffset: 0,
                        endContainer: otherContenteditable, endOffset: 0}],
                      description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "<b>ol</b>"},
                      {type: "text/plain", data: "ol"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging inline contenteditable to same contenteditable
  await (async function test_dragging_from_inline_contenteditable_to_itself() {
    const description = "dragging text in inline contenteditable to same contenteditable";
    container.innerHTML = `<span contenteditable style="font-size:2em">dragme!!<span>MMMM</span></span>`;
    const contenteditable = document.querySelector("span[contenteditable]");
    const span = contenteditable.querySelector("span");
    selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.firstChild, "dragme".length);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "dragme",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "dragme",
        `${description}: dataTransfer should have selected text as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: span,
        }
      )
    ) {
      const kExpectedOffsets = isAndroidException ? [4,4] : [2,2];
      if (isAndroidException) {
        todo_is(contenteditable.innerHTML, "!!<span>MM</span>dragme<span>MM</span>",
          `${description}: dragged range should be moved in inline contenteditable`);
        is(contenteditable.innerHTML, "!!<span>MMMM</span>dragme",
          `${description}: dragged range should be moved in inline contenteditable`);
      } else {
        is(contenteditable.innerHTML, "!!<span>MM</span>dragme<span>MM</span>",
          `${description}: dragged range should be moved in inline contenteditable`);
      }
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on inline contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: contenteditable.firstChild, startOffset: 0,
                        endContainer: contenteditable.firstChild, endOffset: "dragme".length}],
                      description);
      checkInputEvent(beforeinputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "dragme"},
                      {type: "text/plain", data: "dragme"}],
                      [{startContainer: span.firstChild, startOffset: kExpectedOffsets[0],
                        endContainer: span.firstChild, endOffset: kExpectedOffsets[1]}],
                      description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on inline contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], contenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "dragme"},
                      {type: "text/plain", data: "dragme"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on inline contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // -------- Test dragging inline contenteditable to other inline contenteditable
  await (async function test_dragging_from_inline_contenteditable_to_other_inline_contenteditable() {
    const description = "dragging text in inline contenteditable to other inline contenteditable";
    container.innerHTML = '<span contenteditable style="font-size:2em">dragme!!</span><hr><span contenteditable style="font-size:em">MM</span>';
    const contenteditable = document.querySelector("div#container > span[contenteditable]");
    const otherContenteditable = document.querySelector("div#container > span[contenteditable] ~ span[contenteditable]");
    selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.firstChild, "dragme".length);
    beforeinputEvents = [];
    inputEvents = [];
    dragEvents = [];
    const onDrop = aEvent => {
      dragEvents.push(aEvent);
      is(aEvent.dataTransfer.getData("text/plain"), "dragme",
        `${description}: dataTransfer should have selected text as "text/plain"`);
      is(aEvent.dataTransfer.getData("text/html"), "dragme",
        `${description}: dataTransfer should have selected nodes as "text/html"`);
    };
    document.addEventListener("drop", onDrop);
    if (
      await trySynthesizePlainDragAndDrop(
        description,
        {
          srcSelection: selection,
          destElement: otherContenteditable,
        }
      )
    ) {
      const kExpectedOffsets = isAndroidException ? [2,2] : [1,1];
      is(contenteditable.innerHTML, "!!",
        `${description}: dragged range should be removed from inline contenteditable`);
      if (isAndroidException) {
        todo_is(otherContenteditable.innerHTML, "MdragmeM",
          `${description}: dragged content should be inserted into other inline contenteditable`);
        is(otherContenteditable.innerHTML, "MMdragme",
          `${description}: dragged content should be inserted into other inline contenteditable`);
      } else {
        is(otherContenteditable.innerHTML, "MdragmeM",
          `${description}: dragged content should be inserted into other inline contenteditable`);
      }
      is(beforeinputEvents.length, 2,
        `${description}: 2 "beforeinput" events should be fired on inline contenteditable`);
      checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
                      [{startContainer: contenteditable.firstChild, startOffset: 0,
                        endContainer: contenteditable.firstChild, endOffset: "dragme".length}],
                      description);
      checkInputEvent(beforeinputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "dragme"},
                      {type: "text/plain", data: "dragme"}],
                      [{startContainer: otherContenteditable.firstChild, startOffset: kExpectedOffsets[0],
                        endContainer: otherContenteditable.firstChild, endOffset: kExpectedOffsets[1]}],
                      description);
      is(inputEvents.length, 2,
        `${description}: 2 "input" events should be fired on inline contenteditable`);
      checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
      checkInputEvent(inputEvents[1], otherContenteditable, "insertFromDrop", null,
                      [{type: "text/html", data: "dragme"},
                      {type: "text/plain", data: "dragme"}],
                      [],
                      description);
      is(dragEvents.length, 1,
        `${description}: only one "drop" event should be fired on other inline contenteditable`);
    }
    document.removeEventListener("drop", onDrop);
  })();

  // We need to clean up contenteditable=plaintext-only before the pref enabling it is cleared.
  container.innerHTML = "";

  document.removeEventListener("beforeinput", onBeforeinput);
  document.removeEventListener("input", onInput);
  SimpleTest.finish();
}

SimpleTest.waitForFocus(doTest);

</script>
</body>
</html>

Messung V0.5 in Prozent
C=100 H=100 G=100

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.101Angebot  (Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-05-01) ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge