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