Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Errors
- This test failed 14 times in the preceding 30 days. quicksearch this test
- Manifest: editor/libeditor/tests/mochitest.toml
<!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 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 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(span, input, [[{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(span, input, [[{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(span, input, [[{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
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
// 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<br>",
`${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<br>",
`${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<br>",
`${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<br>",
`${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<br>",
`${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>