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