summaryrefslogtreecommitdiffstats
path: root/editor/libeditor/tests/test_middle_click_paste.html
diff options
context:
space:
mode:
Diffstat (limited to 'editor/libeditor/tests/test_middle_click_paste.html')
-rw-r--r--editor/libeditor/tests/test_middle_click_paste.html680
1 files changed, 680 insertions, 0 deletions
diff --git a/editor/libeditor/tests/test_middle_click_paste.html b/editor/libeditor/tests/test_middle_click_paste.html
new file mode 100644
index 0000000000..eaa918c194
--- /dev/null
+++ b/editor/libeditor/tests/test_middle_click_paste.html
@@ -0,0 +1,680 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for paste with middle button click</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none;">
+
+</div>
+
+<div id="container"></div>
+
+<textarea id="toCopyPlaintext" style="display: none;"></textarea>
+<iframe id="toCopyHTMLContent" srcdoc="<body></body>" style="display: none;"></iframe>
+
+<pre id="test">
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+// TODO: This file should test complicated cases too.
+// E.g., pasting into existing content, e.g., pasting invalid child
+// element for the parent elements at insertion point.
+
+async function copyPlaintext(aText) {
+ return new Promise(resolve => {
+ SimpleTest.waitForClipboard(aText,
+ () => {
+ let element = document.getElementById("toCopyPlaintext");
+ element.style.display = "block";
+ element.focus();
+ element.value = aText;
+ synthesizeKey("a", {accelKey: true});
+ synthesizeKey("c", {accelKey: true});
+ },
+ () => {
+ ok(true, `Succeeded to copy "${aText}" to clipboard`);
+ let element = document.getElementById("toCopyPlaintext");
+ element.style.display = "none";
+ resolve();
+ },
+ () => {
+ ok(false, `Failed to copy "${aText}" to clipboard`);
+ SimpleTest.finish();
+ });
+ });
+}
+
+async function copyHTMLContent(aInnerHTML) {
+ let iframe = document.getElementById("toCopyHTMLContent");
+ iframe.style.display = "block";
+ iframe.contentDocument.body.scrollTop;
+ iframe.contentDocument.body.innerHTML = aInnerHTML;
+ iframe.contentWindow.focus();
+ iframe.contentWindow.getSelection().selectAllChildren(iframe.contentDocument.body);
+ return new Promise(resolve => {
+ SimpleTest.waitForClipboard(
+ () => { return true; },
+ () => {
+ synthesizeKey("c", {accelKey: true}, iframe.contentWindow);
+ },
+ () => {
+ ok(true, `Succeeded to copy "${aInnerHTML}" to clipboard as HTML`);
+ iframe.style.display = "none";
+ resolve();
+ },
+ () => {
+ ok(false, `Failed to copy "${aInnerHTML}" to clipboard`);
+ SimpleTest.finish();
+ },
+ "text/html");
+ });
+}
+
+function checkInputEvent(aEvent, aInputType, aData, aDataTransfer, aTargetRanges, aDescription) {
+ ok(aEvent instanceof InputEvent,
+ `"${aEvent.type}" event should be dispatched with InputEvent interface ${aDescription}`);
+ is(aEvent.cancelable, aEvent.type === "beforeinput",
+ `"${aEvent.type}" event should ${aEvent.type === "beforeinput" ? "be" : "be never"} cancelable ${aDescription}`);
+ is(aEvent.bubbles, true,
+ `"${aEvent.type}" event should always bubble ${aDescription}`);
+ is(aEvent.inputType, aInputType,
+ `inputType of "${aEvent.type}" event should be "${aInputType}" ${aDescription}`);
+ is(aEvent.data, aData,
+ `data of "${aEvent.type}" event should be ${aData} ${aDescription}`);
+ if (aDataTransfer === null) {
+ is(aEvent.dataTransfer, null,
+ `dataTransfer of "${aEvent.type}" event should be null ${aDescription}`);
+ } else {
+ for (let dataTransfer of aDataTransfer) {
+ is(aEvent.dataTransfer.getData(dataTransfer.type), dataTransfer.data,
+ `dataTransfer of "${aEvent.type}" should have "${dataTransfer.data}" whose type is "${dataTransfer.type}" ${aDescription}`);
+ }
+ }
+ let targetRanges = aEvent.getTargetRanges();
+ if (aTargetRanges.length === 0) {
+ is(targetRanges.length, 0,
+ `getTargetRange() of "${aEvent.type}" event should return empty array: ${aDescription}`);
+ } else {
+ is(targetRanges.length, aTargetRanges.length,
+ `getTargetRange() of "${aEvent.type}" event should return static range array: ${aDescription}`);
+ if (targetRanges.length == aTargetRanges.length) {
+ for (let i = 0; i < targetRanges.length; i++) {
+ is(targetRanges[i].startContainer, aTargetRanges[i].startContainer,
+ `startContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ is(targetRanges[i].startOffset, aTargetRanges[i].startOffset,
+ `startOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ is(targetRanges[i].endContainer, aTargetRanges[i].endContainer,
+ `endContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ is(targetRanges[i].endOffset, aTargetRanges[i].endOffset,
+ `endOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
+ }
+ }
+ }
+}
+
+async function doTextareaTests(aTextarea) {
+ let beforeInputEvents = [];
+ let inputEvents = [];
+ function onBeforeInput(aEvent) {
+ beforeInputEvents.push(aEvent);
+ }
+ function onInput(aEvent) {
+ inputEvents.push(aEvent);
+ }
+ aTextarea.addEventListener("beforeinput", onBeforeInput);
+ aTextarea.addEventListener("input", onInput);
+
+ await copyPlaintext("abc\ndef\nghi");
+ aTextarea.focus();
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
+ is(aTextarea.value,
+ "> abc\n> def\n> ghi\n\n",
+ "Pasted each line should start with \"> \"");
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired #1');
+ checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\nghi", null, [], "#1");
+ is(inputEvents.length, 1,
+ 'One "input" event should be fired #1');
+ checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\nghi", null, [], "#1");
+ aTextarea.value = "";
+
+ await copyPlaintext("> abc\n> def\n> ghi");
+ aTextarea.focus();
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
+ is(aTextarea.value,
+ ">> abc\n>> def\n>> ghi\n\n",
+ "Pasted each line should be start with \">> \" when already quoted one level");
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired #2');
+ checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", "> abc\n> def\n> ghi", null, [], "#2");
+ is(inputEvents.length, 1,
+ 'One "input" event should be fired #2');
+ checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "> abc\n> def\n> ghi", null, [], "#2");
+ aTextarea.value = "";
+
+ await copyPlaintext("> abc\n> def\n\nghi");
+ aTextarea.focus();
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
+ is(aTextarea.value,
+ ">> abc\n>> def\n> \n> ghi\n\n",
+ "Pasted each line should be start with \">> \" when already quoted one level");
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired #3');
+ checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", "> abc\n> def\n\nghi", null, [], "#3");
+ is(inputEvents.length, 1,
+ 'One "input" event should be fired #3');
+ checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "> abc\n> def\n\nghi", null, [], "#3");
+ aTextarea.value = "";
+
+ await copyPlaintext("abc\ndef\n\n");
+ aTextarea.focus();
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
+ is(aTextarea.value,
+ "> abc\n> def\n> \n",
+ "If pasted text ends with \"\\n\", only the last line should not started with \">\"");
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired #4');
+ checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\n\n", null, [], "#4");
+ is(inputEvents.length, 1,
+ 'One "input" event should be fired #4');
+ checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\n\n", null, [], "#4");
+ aTextarea.value = "";
+
+ await copyPlaintext("abc\ndef\n\n");
+ aTextarea.addEventListener("paste", (event) => { event.preventDefault(); }, {once: true});
+ aTextarea.focus();
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
+ is(aTextarea.value, "",
+ 'Pasting as quote should have been canceled if "paste" event was canceled');
+ is(beforeInputEvents.length, 0,
+ 'No "beforeinput" event should be fired since "paste" event was canceled #5');
+ is(inputEvents.length, 0,
+ 'No "input" event should be fired since "paste" was canceled #5');
+ aTextarea.value = "";
+
+ await copyPlaintext("abc\ndef\n\n");
+ aTextarea.addEventListener("beforeinput", (event) => { event.preventDefault(); }, {once: true});
+ aTextarea.focus();
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
+ is(aTextarea.value, "",
+ 'Pasting as quote should have been canceled if "beforeinput" event was canceled');
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired #5');
+ checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\n\n", null, [], "#6");
+ is(inputEvents.length, 0,
+ 'No "input" event should be fired since "beforeinput" was canceled #6');
+ aTextarea.value = "";
+
+ let pasteEventCount = 0;
+ function pasteEventLogger(event) {
+ pasteEventCount++;
+ }
+ aTextarea.addEventListener("paste", pasteEventLogger);
+
+ await copyPlaintext("abc");
+ aTextarea.focus();
+ document.body.addEventListener("click", (event) => { event.preventDefault(); }, {capture: true, once: true});
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1});
+ is(aTextarea.value, "abc",
+ "If 'click' event is consumed at capturing phase of the <body>, paste should not be canceled");
+ is(pasteEventCount, 1,
+ "If 'click' event is consumed at capturing phase of the <body>, 'paste' event should still be fired");
+ is(beforeInputEvents.length, 1,
+ '"beforeinput" event should be fired when the "click" event is canceled');
+ checkInputEvent(beforeInputEvents[0], "insertFromPaste", "abc", null, [], 'when the "click" event is canceled');
+ is(inputEvents.length, 1,
+ '"input" event should be fired when the "click" event is canceled');
+ checkInputEvent(inputEvents[0], "insertFromPaste", "abc", null, [], 'when the "click" event is canceled');
+ aTextarea.value = "";
+
+ await copyPlaintext("abc");
+ aTextarea.focus();
+ aTextarea.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
+ pasteEventCount = 0;
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1});
+ is(aTextarea.value, "abc",
+ "Even if 'mouseup' event is consumed, paste should be done");
+ is(pasteEventCount, 1,
+ "Even if 'mouseup' event is consumed, 'paste' event should be fired once");
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired even if "mouseup" event is canceled');
+ checkInputEvent(beforeInputEvents[0], "insertFromPaste", "abc", null, [], 'even if "mouseup" event is canceled');
+ is(inputEvents.length, 1,
+ 'One "input" event should be fired even if "mouseup" event is canceled');
+ checkInputEvent(inputEvents[0], "insertFromPaste", "abc", null, [], 'even if "mouseup" event is canceled');
+ aTextarea.value = "";
+
+ await copyPlaintext("abc");
+ aTextarea.focus();
+ aTextarea.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
+ pasteEventCount = 0;
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1});
+ is(aTextarea.value, "abc",
+ "If 'click' event handler is added to the <textarea>, paste should not be canceled");
+ is(pasteEventCount, 1,
+ "If 'click' event handler is added to the <textarea>, 'paste' event should be fired once");
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired even if "click" event is canceled in bubbling phase');
+ checkInputEvent(beforeInputEvents[0], "insertFromPaste", "abc", null, [], 'even if "click" event is canceled in bubbling phase');
+ is(inputEvents.length, 1,
+ 'One "input" event should be fired even if "click" event is canceled in bubbling phase');
+ checkInputEvent(inputEvents[0], "insertFromPaste", "abc", null, [], 'even if "click" event is canceled in bubbling phase');
+ aTextarea.value = "";
+
+ await copyPlaintext("abc");
+ aTextarea.focus();
+ aTextarea.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
+ pasteEventCount = 0;
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1});
+ is(aTextarea.value, "",
+ "If 'auxclick' event is consumed, paste should be canceled");
+ is(pasteEventCount, 0,
+ "If 'auxclick' event is consumed, 'paste' event should not be fired once");
+ is(beforeInputEvents.length, 0,
+ 'No "beforeinput" event should be fired if "auxclick" event is canceled');
+ is(inputEvents.length, 0,
+ 'No "input" event should be fired if "auxclick" event is canceled');
+ aTextarea.value = "";
+
+ await copyPlaintext("abc");
+ aTextarea.focus();
+ aTextarea.addEventListener("paste", (event) => { event.preventDefault(); }, {once: true});
+ pasteEventCount = 0;
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1});
+ is(aTextarea.value, "",
+ "If 'paste' event is consumed, paste should be canceled");
+ is(pasteEventCount, 1,
+ 'One "paste" event should be fired for making it possible to consume');
+ is(beforeInputEvents.length, 0,
+ 'No "beforeinput" event should be fired if "paste" event is canceled');
+ is(inputEvents.length, 0,
+ 'No "input" event should be fired if "paste" event is canceled');
+ aTextarea.value = "";
+
+ await copyPlaintext("abc");
+ aTextarea.focus();
+ aTextarea.addEventListener("beforeinput", (event) => { event.preventDefault(); }, {once: true});
+ pasteEventCount = 0;
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aTextarea, {button: 1});
+ is(aTextarea.value, "",
+ "If 'beforeinput' event is consumed, paste should be canceled");
+ is(pasteEventCount, 1,
+ 'One "paste" event should be fired before "beforeinput" event is consumed');
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired for making it possible to consume');
+ checkInputEvent(beforeInputEvents[0], "insertFromPaste", "abc", null, [], 'when "beforeinput" is canceled in bubbling phase');
+ is(inputEvents.length, 0,
+ 'No "input" event should be fired if "paste" event is canceled');
+ aTextarea.value = "";
+
+ aTextarea.removeEventListener("paste", pasteEventLogger);
+ aTextarea.removeEventListener("beforeinput", onBeforeInput);
+ aTextarea.removeEventListener("input", onInput);
+}
+
+async function doContenteditableTests(aEditableDiv) {
+ let beforeInputEvents = [];
+ let inputEvents = [];
+ let selectionRanges = [];
+ function onBeforeInput(aEvent) {
+ beforeInputEvents.push(aEvent);
+ let selection = document.getSelection();
+ selectionRanges = [];
+ for (let i = 0; i < selection.rangeCount; i++) {
+ let range = selection.getRangeAt(i);
+ selectionRanges.push({startContainer: range.startContainer, startOffset: range.startOffset,
+ endContainer: range.endContainer, endOffset: range.endOffset});
+ }
+ }
+ function onInput(aEvent) {
+ inputEvents.push(aEvent);
+ }
+ aEditableDiv.addEventListener("beforeinput", onBeforeInput);
+ aEditableDiv.addEventListener("input", onInput);
+
+ await copyPlaintext("abc\ndef\nghi");
+ aEditableDiv.focus();
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
+ is(aEditableDiv.innerHTML,
+ "<blockquote type=\"cite\">abc<br>def<br>ghi</blockquote>",
+ "Pasted plaintext should be in <blockquote> element and each linebreaker should be <br> element");
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired on the editing host');
+ checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", null,
+ [{type: "text/plain", data: "abc\ndef\nghi"}], selectionRanges, "(contenteditable)");
+ is(inputEvents.length, 1,
+ 'One "input" event should be fired on the editing host');
+ checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", null,
+ [{type: "text/plain", data: "abc\ndef\nghi"}], [], "(contenteditable)");
+ aEditableDiv.innerHTML = "";
+
+ let pasteEventCount = 0;
+ function pasteEventLogger(event) {
+ pasteEventCount++;
+ }
+ aEditableDiv.addEventListener("paste", pasteEventLogger);
+
+ await copyPlaintext("abc");
+ aEditableDiv.focus();
+ window.addEventListener("click", (event) => { event.preventDefault(); }, {capture: true, once: true});
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+ is(aEditableDiv.innerHTML, "abc",
+ "If 'click' event is consumed at capturing phase of the window, paste should not be canceled");
+ is(pasteEventCount, 1,
+ "If 'click' event is consumed at capturing phase of the window, 'paste' event should be fired once");
+ is(beforeInputEvents.length, 1,
+ '"beforeinput" event should still be fired when the "click" event is canceled (contenteditable)');
+ checkInputEvent(beforeInputEvents[0], "insertFromPaste", null,
+ [{type: "text/plain", data: "abc"}], selectionRanges, 'when the "click" event is canceled (contenteditable)');
+ is(inputEvents.length, 1,
+ '"input" event should still be fired when the "click" event is canceled (contenteditable)');
+ checkInputEvent(inputEvents[0], "insertFromPaste", null,
+ [{type: "text/plain", data: "abc"}], [], 'when the "click" event is canceled (contenteditable)');
+ aEditableDiv.innerHTML = "";
+
+ await copyPlaintext("abc");
+ aEditableDiv.focus();
+ aEditableDiv.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
+ pasteEventCount = 0;
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+ is(aEditableDiv.innerHTML, "abc",
+ "Even if 'mouseup' event is consumed, paste should be done");
+ is(pasteEventCount, 1,
+ "Even if 'mouseup' event is consumed, 'paste' event should be fired once");
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired even if "mouseup" event is canceled (contenteditable)');
+ checkInputEvent(beforeInputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: "abc"}], selectionRanges,
+ 'even if "mouseup" event is canceled (contenteditable)');
+ is(inputEvents.length, 1,
+ 'One "input" event should be fired even if "mouseup" event is canceled (contenteditable)');
+ checkInputEvent(inputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: "abc"}], [],
+ 'even if "mouseup" event is canceled (contenteditable)');
+ aEditableDiv.innerHTML = "";
+
+ await copyPlaintext("abc");
+ aEditableDiv.focus();
+ aEditableDiv.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
+ pasteEventCount = 0;
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+ is(aEditableDiv.innerHTML, "abc",
+ "Even if 'click' event handler is added to the editing host, paste should not be canceled");
+ is(pasteEventCount, 1,
+ "Even if 'click' event handler is added to the editing host, 'paste' event should be fired");
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired even if "click" event is canceled in bubbling phase (contenteditable)');
+ checkInputEvent(beforeInputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: "abc"}], selectionRanges,
+ 'even if "click" event is canceled in bubbling phase (contenteditable)');
+ is(inputEvents.length, 1,
+ 'One "input" event should be fired even if "click" event is canceled in bubbling phase (contenteditable)');
+ checkInputEvent(inputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: "abc"}], [],
+ 'even if "click" event is canceled in bubbling phase (contenteditable)');
+ aEditableDiv.innerHTML = "";
+
+ await copyPlaintext("abc");
+ aEditableDiv.focus();
+ aEditableDiv.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
+ pasteEventCount = 0;
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+ is(aEditableDiv.innerHTML, "",
+ "If 'auxclick' event is consumed, paste should be canceled");
+ is(pasteEventCount, 0,
+ "If 'auxclick' event is consumed, 'paste' event should not be fired");
+ is(beforeInputEvents.length, 0,
+ 'No "beforeinput" event should be fired if "auxclick" event is canceled (contenteditable)');
+ is(inputEvents.length, 0,
+ 'No "input" event should be fired if "auxclick" event is canceled (contenteditable)');
+ aEditableDiv.innerHTML = "";
+
+ await copyPlaintext("abc");
+ aEditableDiv.focus();
+ aEditableDiv.addEventListener("paste", (event) => { event.preventDefault(); }, {once: true});
+ pasteEventCount = 0;
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+ is(aEditableDiv.innerHTML, "",
+ "If 'paste' event is consumed, paste should be canceled");
+ is(pasteEventCount, 1,
+ 'One "paste" event should be fired for making it possible to consume');
+ is(beforeInputEvents.length, 0,
+ 'No "beforeinput" event should be fired if "paste" event is canceled (contenteditable)');
+ is(inputEvents.length, 0,
+ 'No "input" event should be fired if "paste" event is canceled (contenteditable)');
+ aEditableDiv.innerHTML = "";
+
+ await copyPlaintext("abc");
+ aEditableDiv.focus();
+ aEditableDiv.addEventListener("beforeinput", (event) => { event.preventDefault(); }, {once: true});
+ pasteEventCount = 0;
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+ is(aEditableDiv.innerHTML, "",
+ "If 'paste' event is consumed, paste should be canceled");
+ is(pasteEventCount, 1,
+ 'One "paste" event should be fired before "beforeinput" event');
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired for making it possible to consume (contenteditable)');
+ checkInputEvent(beforeInputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: "abc"}], selectionRanges,
+ 'when "beforeinput" will be canceled (contenteditable)');
+ is(inputEvents.length, 0,
+ 'No "input" event should be fired if "beforeinput" event is canceled (contenteditable)');
+ aEditableDiv.innerHTML = "";
+
+ // If clipboard event is disabled, InputEvent.dataTransfer should have only empty string.
+ await SpecialPowers.pushPrefEnv({"set": [["dom.event.clipboardevents.enabled", false]]});
+ await copyPlaintext("abc");
+ aEditableDiv.focus();
+ pasteEventCount = 0;
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+ is(aEditableDiv.innerHTML, "abc",
+ "Even if clipboard event is disabled, paste should be done");
+ is(pasteEventCount, 0,
+ "If clipboard event is disabled, 'paste' event shouldn't be fired once");
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired even if clipboard event is disabled (contenteditable)');
+ checkInputEvent(beforeInputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: ""}], selectionRanges,
+ "when clipboard event is disabled (contenteditable)");
+ is(inputEvents.length, 1,
+ 'One "input" event should be fired even if clipboard event is disabled (contenteditable)');
+ checkInputEvent(inputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: ""}], [],
+ "when clipboard event is disabled (contenteditable)");
+ await SpecialPowers.pushPrefEnv({"set": [["dom.event.clipboardevents.enabled", true]]});
+ aEditableDiv.innerHTML = "";
+
+ aEditableDiv.removeEventListener("paste", pasteEventLogger);
+
+ // Oddly, copyHTMLContent fails randomly only on Linux. Let's skip this.
+ if (navigator.platform.startsWith("Linux")) {
+ aEditableDiv.removeEventListener("input", onInput);
+ return;
+ }
+
+ await copyHTMLContent("<p>abc</p><p>def</p><p>ghi</p>");
+ aEditableDiv.focus();
+ beforeInputEvents = [];
+ inputEvents = [];
+ synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
+ if (!navigator.appVersion.includes("Android")) {
+ is(aEditableDiv.innerHTML,
+ "<blockquote type=\"cite\"><p>abc</p><p>def</p><p>ghi</p></blockquote>",
+ "Pasted HTML content should be set to the <blockquote>");
+ } else {
+ // Oddly, on Android, we use <br> elements for pasting <p> elements.
+ is(aEditableDiv.innerHTML,
+ "<blockquote type=\"cite\">abc<br><br>def<br><br>ghi</blockquote>",
+ "Pasted HTML content should be set to the <blockquote>");
+ }
+ // On windows, HTML clipboard includes extra data.
+ // The values are from widget/windows/nsDataObj.cpp.
+ const kHTMLPrefix = (navigator.platform.includes("Win")) ? kTextHtmlPrefixClipboardDataWindows : "";
+ const kHTMLPostfix = (navigator.platform.includes("Win")) ? kTextHtmlSuffixClipboardDataWindows : "";
+ is(beforeInputEvents.length, 1,
+ 'One "beforeinput" event should be fired when pasting HTML');
+ checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", null,
+ [{type: "text/html",
+ data: `${kHTMLPrefix}<p>abc</p><p>def</p><p>ghi</p>${kHTMLPostfix}`}],
+ selectionRanges,
+ "when pasting HTML");
+ is(inputEvents.length, 1,
+ 'One "input" event should be fired when pasting HTML');
+ checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", null,
+ [{type: "text/html",
+ data: `${kHTMLPrefix}<p>abc</p><p>def</p><p>ghi</p>${kHTMLPostfix}`}],
+ [],
+ "when pasting HTML");
+ aEditableDiv.innerHTML = "";
+
+ aEditableDiv.removeEventListener("beforeinput", onBeforeInput);
+ aEditableDiv.removeEventListener("input", onInput);
+}
+
+async function doNestedEditorTests(aEditableDiv) {
+ await copyPlaintext("CLIPBOARD TEXT");
+ aEditableDiv.innerHTML = '<p id="p">foo</p><textarea id="textarea"></textarea>';
+ aEditableDiv.focus();
+ let textarea = document.getElementById("textarea");
+ let pasteTarget = null;
+ function onPaste(aEvent) {
+ pasteTarget = aEvent.target;
+ }
+ document.addEventListener("paste", onPaste);
+
+ synthesizeMouseAtCenter(textarea, {button: 1});
+ is(pasteTarget.getAttribute("id"), "textarea",
+ "Target of 'paste' event should be the clicked <textarea>");
+ is(textarea.value, "CLIPBOARD TEXT",
+ "Clicking in <textarea> in an editable <div> should paste the clipboard text into the <textarea>");
+ is(aEditableDiv.innerHTML, '<p id="p">foo</p><textarea id="textarea"></textarea>',
+ "Pasting in the <textarea> shouldn't be handled by the HTMLEditor");
+
+ textarea.value = "";
+ textarea.readOnly = true;
+ pasteTarget = null;
+ synthesizeMouseAtCenter(textarea, {button: 1});
+ is(pasteTarget, textarea,
+ "Target of 'paste' event should be the clicked <textarea> even if it's read-only");
+ is(textarea.value, "",
+ "Clicking in read-only <textarea> in an editable <div> should not paste the clipboard text into the read-only <textarea>");
+ // HTMLEditor thinks that read-only <textarea> is not modifiable.
+ // Therefore, HTMLEditor does not paste the text.
+ is(aEditableDiv.innerHTML, '<p id="p">foo</p><textarea id="textarea" readonly=""></textarea>',
+ "Clicking in read-only <textarea> shouldn't cause pasting the clipboard text into its parent HTMLEditor");
+
+ textarea.value = "";
+ textarea.readOnly = false;
+ textarea.disabled = true;
+ pasteTarget = null;
+ synthesizeMouseAtCenter(textarea, {button: 1});
+ // Although, this compares with <textarea>, I'm not sure it's proper event
+ // target because of disabled <textarea>.
+ todo_is(pasteTarget, textarea,
+ "Target of 'paste' event should be the clicked <textarea> even if it's disabled");
+ is(textarea.value, "",
+ "Clicking in disabled <textarea> in an editable <div> should not paste the clipboard text into the disabled <textarea>");
+ // HTMLEditor thinks that disabled <textarea> is not modifiable.
+ // Therefore, HTMLEditor does not paste the text.
+ is(aEditableDiv.innerHTML, '<p id="p">foo</p><textarea id="textarea" disabled=""></textarea>',
+ "Clicking in disabled <textarea> shouldn't cause pasting the clipboard text into its parent HTMLEditor");
+
+ document.removeEventListener("paste", onPaste);
+ aEditableDiv.innerHTML = "";
+}
+
+async function doAfterRemoveOfClickedElementTest(aEditableDiv) {
+ await copyPlaintext("CLIPBOARD TEXT");
+ aEditableDiv.innerHTML = '<p id="p">foo<span id="span">bar</span></p>';
+ aEditableDiv.focus();
+ let span = document.getElementById("span");
+ let pasteTarget = null;
+ document.addEventListener("paste", (aEvent) => { pasteTarget = aEvent.target; }, {once: true});
+ document.addEventListener("auxclick", (aEvent) => {
+ is(aEvent.target.getAttribute("id"), "span",
+ "Target of auxclick event should be the <span> element");
+ span.parentElement.removeChild(span);
+ }, {once: true});
+ synthesizeMouseAtCenter(span, {button: 1});
+ is(pasteTarget.getAttribute("id"), "p",
+ "Target of 'paste' event should be the <p> element since <span> has gone");
+ // XXX Currently, pasted to start of the <p> because EventStateManager
+ // do not recompute event target frame.
+ todo_is(aEditableDiv.innerHTML, '<p id="p">fooCLIPBOARD TEXT</p>',
+ "Clipbpard text should looks like replacing the <span> element");
+ aEditableDiv.innerHTML = "";
+}
+
+async function doNotStartAutoscrollInContentEditable(aEditableDiv) {
+ await SpecialPowers.pushPrefEnv({"set": [["general.autoScroll", true]]});
+ await copyPlaintext("CLIPBOARD TEXT");
+ aEditableDiv.innerHTML = '<p id="p">foo<span id="span">bar</span></p>';
+ aEditableDiv.focus();
+ let span = document.getElementById("span");
+ synthesizeMouseAtCenter(span, {button: 1});
+ ok(aEditableDiv.innerHTML.includes("CLIPBOARD TEXT"),
+ "Clipbpard text should be inserted");
+ aEditableDiv.innerHTML = "";
+}
+
+async function doTests() {
+ await SpecialPowers.pushPrefEnv({"set": [["middlemouse.paste", true],
+ ["middlemouse.contentLoadURL", false],
+ ["dom.event.clipboardevents.enabled", true]]});
+ let container = document.getElementById("container");
+ container.innerHTML = "<textarea id=\"editor\"></textarea>";
+ await doTextareaTests(document.getElementById("editor"));
+ container.innerHTML = "<div id=\"editor\" contenteditable style=\"min-height: 1em;\"></div>";
+ await doContenteditableTests(document.getElementById("editor"));
+ await doNestedEditorTests(document.getElementById("editor"));
+ await doAfterRemoveOfClickedElementTest(document.getElementById("editor"));
+ // NOTE: The following test sets `general.autoScroll` to true.
+ await doNotStartAutoscrollInContentEditable(document.getElementById("editor"));
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(doTests);
+</script>
+</pre>
+</body>
+</html>