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`);
}
}
}
}
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 element";
container.innerHTML = 'Some Text';
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 element";
container.innerHTML = '';
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 ;
container.innerHTML = "";
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 = "
This is some editable text.
";
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 Bold 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 Bold 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 bold 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 bold 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 bold 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 = "
Some bold text
";
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 bold t",
`${description}: dataTransfer should have selected nodes as "text/html"`);
};
document.addEventListener("drop", onDrop);
document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
if (
await trySynthesizePlainDragAndDrop(
description,
{
srcSelection: selection,
destElement: input,
}
)
) {
is(contenteditable.innerHTML, "Soext",
`${description}: Dragged range should be removed from contenteditable`);
is(input.value, "",
`${description}: <input>.value shouldn't be modified`);
is(beforeinputEvents.length, 2,
`${description}: 2 "beforeinput" events should be fired on contenteditable and <input>`);
checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
[{startContainer: selectionContainers[0], startOffset: 2,
endContainer: selectionContainers[1], endOffset: 2}],
description);
checkInputEvent(beforeinputEvents[1], input, "insertFromDrop", "me bold t", null, [], description);
is(inputEvents.length, 1,
`${description}: only one "input" event should be fired on contenteditable`);
checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
is(dragEvents.length, 1,
`${description}: only one "drop" event should be fired on <input>`);
}
document.removeEventListener("drop", onDrop);
document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
})();
}
// -------- Test dragging regular text of text/html to <input type="number">
//
// FIXME(emilio): The -moz-appearance bit is just a hack to
// work around bug 1611720.
await (async function test_dragging_from_span_element_to_input_element_whose_type_number() {
const description = `dragging text in non-editable <span> to <input type="number">`;
container.innerHTML = `<span>123456</span><input type="number"style="-moz-appearance: textfield">`;
const span = document.querySelector("div#container > span");
const input = document.querySelector("div#container > input");
selection.setBaseAndExtent(span.firstChild, 2, span.firstChild, 5);
beforeinputEvents = [];
inputEvents = [];
dragEvents = [];
const onDrop = aEvent => {
dragEvents.push(aEvent);
comparePlainText(aEvent.dataTransfer.getData("text/plain"), span.textContent.substring(2, 5),
`${description}: dataTransfer should have selected text as "text/plain"`);
compareHTML(aEvent.dataTransfer.getData("text/html"), span.outerHTML.replace(/>.+</, `>${span.textContent.substring(2, 5)}<`),
`${description}: dataTransfer should have selected nodes as "text/html"`);
};
document.addEventListener("drop", onDrop);
if (
await trySynthesizePlainDragAndDrop(
description,
{
srcSelection: selection,
destElement: input,
}
)
) {
is(input.value, span.textContent.substring(2, 5),
`${description}: <input>.value should be modified`);
is(beforeinputEvents.length, 1,
`${description}: one "beforeinput" event should be fired on <input>`);
checkInputEvent(beforeinputEvents[0], input, "insertFromDrop", span.textContent.substring(2, 5), null, [], description);
is(inputEvents.length, 1,
`${description}: one "input" event should be fired on <input>`);
checkInputEvent(inputEvents[0], input, "insertFromDrop", span.textContent.substring(2, 5), null, [], description);
is(dragEvents.length, 1,
`${description}: only one "drop" event should be fired on <input>`);
}
document.removeEventListener("drop", onDrop);
})();
// -------- Test dragging only text/plain data (like from another app) to contenteditable.
await (async function test_dragging_only_plain_text_to_contenteditable() {
const description = "dragging both text/plain and text/html data to contenteditable";
container.innerHTML = 'Static';
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 = 'Static';
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 Italic Text"}]], "copy");
is(contenteditable.innerHTML, "Sample Italic 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 Italic 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 Italic 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 to ;
container.innerHTML = "Static";
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 ;
container.innerHTML = "
Some bold text
";
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 bold 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 ;
container.innerHTML = "
Some bold text
";
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 bold 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 bold 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 ;
container.innerHTML = "
Some bold text
";
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 bold t",
`${description}: dataTransfer should have selected nodes as "text/html"`);
};
document.addEventListener("drop", onDrop);
document.addEventListener("beforeinput", preventDefaultInsertFromDrop);
if (
await trySynthesizePlainDragAndDrop(
description,
{
srcSelection: selection,
destElement: textarea,
}
)
) {
is(contenteditable.innerHTML, "Soext",
`${description}: Dragged range should be removed from contenteditable`);
is(textarea.value, "",
`${description}: <textarea>.value shouldn't be modified`);
is(beforeinputEvents.length, 2,
`${description}: 2 "beforeinput" events should be fired on contenteditable and <textarea>`);
checkInputEvent(beforeinputEvents[0], contenteditable, "deleteByDrag", null, null,
[{startContainer: selectionContainers[0], startOffset: 2,
endContainer: selectionContainers[1], endOffset: 2}],
description);
checkInputEvent(beforeinputEvents[1], textarea, "insertFromDrop", "me bold t", null, [], description);
is(inputEvents.length, 1,
`${description}: only one "input" event should be fired on contenteditable`);
checkInputEvent(inputEvents[0], contenteditable, "deleteByDrag", null, null, [], description);
is(dragEvents.length, 1,
`${description}: only one "drop" event should be fired on <textarea>`);
}
document.removeEventListener("drop", onDrop);
document.removeEventListener("beforeinput", preventDefaultInsertFromDrop);
})();
// -------- Test dragging contenteditable to same contenteditable
// Bug 1904272: Android Non-XOrigin incorrectly inserts after the 3rd M
// instead of after the 2nd M in some of the following tests.
const isAndroidException = AppConstants.platform === "android" && !isXOrigin;
await (async function test_dragging_from_contenteditable_to_itself() {
const description = "dragging text in contenteditable to same contenteditable";
container.innerHTML = "
boldMMMM
";
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"), "ol",
`${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, "bdMMolMM",
`${description}: dragged range should be removed from contenteditable`);
isnot(contenteditable.innerHTML, "bdMMMMol",
`${description}: dragged range should be removed from contenteditable`);
} else {
is(contenteditable.innerHTML, "bdMMolMM",
`${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: "ol"},
{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: "ol"},
{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 = "
boldMMMM
";
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"), "ol",
`${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, "boldMMolMM",
`${description}: dragged range shouldn't be removed from contenteditable`);
isnot(contenteditable.innerHTML, "boldMMMMol",
`${description}: dragged range shouldn't be removed from contenteditable`);
} else {
is(contenteditable.innerHTML, "boldMMolMM",
`${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: "ol"},
{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: "ol"},
{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 = "
boldMMMM
";
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"), "ol",
`${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, "bdMMMM",
`${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: "ol"},
{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 = "
boldMMMM
";
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"), "ol",
`${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, "boldMMolMM",
`${description}: dragged range shouldn't be removed from contenteditable`);
isnot(contenteditable.innerHTML, "boldMMMMol",
`${description}: dragged range shouldn't be removed from contenteditable`);
} else {
is(contenteditable.innerHTML, "boldMMolMM",
`${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: "ol"},
{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: "ol"},
{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 = '
bold
';
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"), "ol",
`${description}: dataTransfer should have selected nodes as "text/html"`);
};
document.addEventListener("drop", onDrop);
if (
await trySynthesizePlainDragAndDrop(
description,
{
srcSelection: selection,
destElement: otherContenteditable,
}
)
) {
is(contenteditable.innerHTML, "bd",
`${description}: dragged range should be removed from contenteditable`);
is(otherContenteditable.innerHTML, "ol",
`${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: "ol"},
{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: "ol"},
{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 = '
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.