Bug 1532527 - Support "insertFromPasteAsQuotation" inputType value r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 05 Mar 2019 14:35:43 +0000
changeset 520419 577a1e61f6d37c5cc402d9fd0fc1cb40a489c616
parent 520418 1ff318d58cc1ccd0801ff941364fc0098f31f929
child 520420 494abd5b60b8cab73684b24f29541f0d736d36a9
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1532527
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1532527 - Support "insertFromPasteAsQuotation" inputType value r=smaug Only Firefox has an operation to paste clipboard data as quoted text (Control + middle button paste). Input Events Level 1 and Level 2 declared new inputType value for this operation. Therefore, we should support it. Differential Revision: https://phabricator.services.mozilla.com/D22067
dom/events/InputTypeList.h
editor/libeditor/EditAction.h
editor/libeditor/HTMLEditorDataTransfer.cpp
editor/libeditor/TextEditor.cpp
editor/libeditor/tests/test_middle_click_paste.html
widget/EventForwards.h
--- a/dom/events/InputTypeList.h
+++ b/dom/events/InputTypeList.h
@@ -23,16 +23,17 @@ NS_DEFINE_INPUTTYPE(InsertReplacementTex
 NS_DEFINE_INPUTTYPE(InsertLineBreak, "insertLineBreak")
 NS_DEFINE_INPUTTYPE(InsertParagraph, "insertParagraph")
 NS_DEFINE_INPUTTYPE(InsertOrderedList, "insertOrderedList")
 NS_DEFINE_INPUTTYPE(InsertUnorderedList, "insertUnorderedList")
 NS_DEFINE_INPUTTYPE(InsertHorizontalRule, "insertHorizontalRule")
 NS_DEFINE_INPUTTYPE(InsertFromYank, "insertFromYank")
 NS_DEFINE_INPUTTYPE(InsertFromDrop, "insertFromDrop")
 NS_DEFINE_INPUTTYPE(InsertFromPaste, "insertFromPaste")
+NS_DEFINE_INPUTTYPE(InsertFromPasteAsQuotation, "insertFromPasteAsQuotation")
 NS_DEFINE_INPUTTYPE(InsertTranspose, "insertTranspose")
 NS_DEFINE_INPUTTYPE(InsertCompositionText, "insertCompositionText")
 NS_DEFINE_INPUTTYPE(InsertFromComposition,
                     "insertFromComposition")  // Level 2
 NS_DEFINE_INPUTTYPE(InsertLink, "insertLink")
 NS_DEFINE_INPUTTYPE(DeleteByComposition,
                     "deleteByComposition")  // Level 2
 NS_DEFINE_INPUTTYPE(DeleteCompositionText,
--- a/editor/libeditor/EditAction.h
+++ b/editor/libeditor/EditAction.h
@@ -99,16 +99,19 @@ enum class EditAction {
   eCut,
 
   // eCopy indicates to copy selected content to the clipboard.
   eCopy,
 
   // ePaste indicates to paste clipboard data.
   ePaste,
 
+  // ePasteAsQuotation indicates to paste clipboard data as quotation.
+  ePasteAsQuotation,
+
   // eDrop indicates that user drops dragging item into the editor.
   eDrop,
 
   // eIndent indicates that to indent selected line(s).
   eIndent,
 
   // eOutdent indicates that to outdent selected line(s).
   eOutdent,
@@ -487,16 +490,18 @@ inline EditorInputType ToInputType(EditA
     case EditAction::eRemoveUnorderedListElement:
       return EditorInputType::eInsertUnorderedList;
     case EditAction::eInsertHorizontalRuleElement:
       return EditorInputType::eInsertHorizontalRule;
     case EditAction::eDrop:
       return EditorInputType::eInsertFromDrop;
     case EditAction::ePaste:
       return EditorInputType::eInsertFromPaste;
+    case EditAction::ePasteAsQuotation:
+      return EditorInputType::eInsertFromPasteAsQuotation;
     case EditAction::eUpdateComposition:
       return EditorInputType::eInsertCompositionText;
     case EditAction::eCommitComposition:
       if (StaticPrefs::dom_input_events_conform_to_level_1()) {
         return EditorInputType::eInsertCompositionText;
       }
       return EditorInputType::eInsertFromComposition;
     case EditAction::eCancelComposition:
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -1691,17 +1691,17 @@ bool HTMLEditor::CanPasteTransferable(ns
   return false;
 }
 
 nsresult HTMLEditor::PasteAsQuotationAsAction(int32_t aClipboardType,
                                               bool aDispatchPasteEvent) {
   MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
              aClipboardType == nsIClipboard::kSelectionClipboard);
 
-  AutoEditActionDataSetter editActionData(*this, EditAction::ePaste);
+  AutoEditActionDataSetter editActionData(*this, EditAction::ePasteAsQuotation);
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
   editActionData.InitializeDataTransferWithClipboard(
       SettingDataTransfer::eWithFormat, aClipboardType);
 
   if (IsPlaintextEditor()) {
     // XXX In this case, we don't dispatch ePaste event.  Why?
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -1981,17 +1981,17 @@ nsresult TextEditor::ComputeValueInterna
   return NS_OK;
 }
 
 nsresult TextEditor::PasteAsQuotationAsAction(int32_t aClipboardType,
                                               bool aDispatchPasteEvent) {
   MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
              aClipboardType == nsIClipboard::kSelectionClipboard);
 
-  AutoEditActionDataSetter editActionData(*this, EditAction::ePaste);
+  AutoEditActionDataSetter editActionData(*this, EditAction::ePasteAsQuotation);
   if (NS_WARN_IF(!editActionData.CanHandle())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   // Get Clipboard Service
   nsresult rv;
   nsCOMPtr<nsIClipboard> clipboard =
       do_GetService("@mozilla.org/widget/clipboard;1", &rv);
--- a/editor/libeditor/tests/test_middle_click_paste.html
+++ b/editor/libeditor/tests/test_middle_click_paste.html
@@ -71,25 +71,25 @@ async function copyHTMLContent(aInnerHTM
       () => {
         ok(false, `Failed to copy "${aInnerHTML}" to clipboard`);
         SimpleTest.finish();
       },
       "text/html");
   });
 }
 
-function checkInputEvent(aEvent, aData, aDataTransfer, aDescription) {
+function checkInputEvent(aEvent, aInputType, aData, aDataTransfer, aDescription) {
   ok(aEvent instanceof InputEvent,
      `"input" event should be dispatched with InputEvent interface ${aDescription}`);
   is(aEvent.cancelable, false,
      `"input" event should be never cancelable ${aDescription}`);
   is(aEvent.bubbles, true,
      `"input" event should always bubble ${aDescription}`);
-  is(aEvent.inputType, "insertFromPaste",
-     `inputType should be "insertFromPaste" ${aDescription}`);
+  is(aEvent.inputType, aInputType,
+     `inputType should be "${aInputType}" ${aDescription}`);
   is(aEvent.data, aData,
      `data should be ${aData} ${aDescription}`);
   if (aDataTransfer === null) {
     is(aEvent.dataTransfer, null,
        `dataTransfer should be null ${aDescription}`);
   } else {
     for (let dataTransfer of aDataTransfer) {
       is(aEvent.dataTransfer.getData(dataTransfer.type), dataTransfer.data,
@@ -109,53 +109,53 @@ async function doTextareaTests(aTextarea
   aTextarea.focus();
   inputEvents = [];
   synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
   is(aTextarea.value,
      "> abc\n> def\n> ghi\n\n",
      "Pasted each line should start with \"> \"");
   is(inputEvents.length, 1,
      'One "input" event should be fired #1');
-  checkInputEvent(inputEvents[0], "abc\ndef\nghi", null, "#1");
+  checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\nghi", null, "#1");
   aTextarea.value = "";
 
   await copyPlaintext("> abc\n> def\n> ghi");
   aTextarea.focus();
   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(inputEvents.length, 1,
      'One "input" event should be fired #2');
-  checkInputEvent(inputEvents[0], "> abc\n> def\n> ghi", null, "#2");
+  checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "> abc\n> def\n> ghi", null, "#2");
   aTextarea.value = "";
 
   await copyPlaintext("> abc\n> def\n\nghi");
   aTextarea.focus();
   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(inputEvents.length, 1,
      'One "input" event should be fired #3');
-  checkInputEvent(inputEvents[0], "> abc\n> def\n\nghi", null, "#3");
+  checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "> abc\n> def\n\nghi", null, "#3");
   aTextarea.value = "";
 
   await copyPlaintext("abc\ndef\n\n");
   aTextarea.focus();
   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(inputEvents.length, 1,
      'One "input" event should be fired #4');
-  checkInputEvent(inputEvents[0], "abc\ndef\n\n", null, "#4");
+  checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\n\n", null, "#4");
   aTextarea.value = "";
 
   let pasteEventCount = 0;
   function pasteEventLogger(event) {
     pasteEventCount++;
   }
   aTextarea.addEventListener("paste", pasteEventLogger);
 
@@ -179,32 +179,32 @@ async function doTextareaTests(aTextarea
   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(inputEvents.length, 1,
      'One "input" event should be fired even if "mouseup" event is canceled');
-  checkInputEvent(inputEvents[0], "abc", null, '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;
   inputEvents = [];
   synthesizeMouseAtCenter(aTextarea, {button: 1});
   is(aTextarea.value, "abc",
      "Even if 'click' event handler is added to the <textarea>, paste should not be canceled");
   is(pasteEventCount, 1,
      "Even if 'click' event handler is added to the <textarea>, 'paste' event should be fired once");
   is(inputEvents.length, 1,
      'One "input" event should be fired even if "click" event is canceled in bubbling phase');
-  checkInputEvent(inputEvents[0], "abc", null, '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;
   inputEvents = [];
   synthesizeMouseAtCenter(aTextarea, {button: 1});
@@ -231,17 +231,18 @@ async function doContenteditableTests(aE
   aEditableDiv.focus();
   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(inputEvents.length, 1,
      'One "input" event should be fired on the editing host');
-  checkInputEvent(inputEvents[0], null, [{type: "text/plain", data: "abc\ndef\nghi"}], "(contenteditable)");
+  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);
 
@@ -265,33 +266,33 @@ async function doContenteditableTests(aE
   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(inputEvents.length, 1,
      'One "input" event should be fired even if "mouseup" event is canceled (contenteditable)');
-  checkInputEvent(inputEvents[0], null, [{type: "text/plain", data: "abc"}],
+  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;
   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(inputEvents.length, 1,
      'One "input" event should be fired even if "click" event is canceled in bubbling phase (contenteditable)');
-  checkInputEvent(inputEvents[0], null, [{type: "text/plain", data: "abc"}],
+  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;
   inputEvents = [];
@@ -327,17 +328,17 @@ async function doContenteditableTests(aE
        "Pasted HTML content should be set to the <blockquote>");
   }
   is(inputEvents.length, 1,
      'One "input" event should be fired when pasting HTML');
   // On windows, HTML clipboard includes extra data.
   // The values are from widget/windows/nsDataObj.cpp.
   const kHTMLPrefix = (navigator.platform.includes("Win")) ? "<html><body>\n<!--StartFragment-->" : "";
   const kHTMLPostfix = (navigator.platform.includes("Win")) ? "<!--EndFragment-->\n</body>\n</html>" : "";
-  checkInputEvent(inputEvents[0], null,
+  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("input", onInput);
 }
 
--- a/widget/EventForwards.h
+++ b/widget/EventForwards.h
@@ -135,16 +135,17 @@ enum class EditorInputType : EditorInput
  * should have non-null InputEvent.data value.
  */
 inline bool IsDataAvailableOnTextEditor(EditorInputType aInputType) {
   switch (aInputType) {
     case EditorInputType::eInsertText:
     case EditorInputType::eInsertCompositionText:
     case EditorInputType::eInsertFromComposition:  // Only level 2
     case EditorInputType::eInsertFromPaste:
+    case EditorInputType::eInsertFromPasteAsQuotation:
     case EditorInputType::eInsertTranspose:
     case EditorInputType::eInsertFromDrop:
     case EditorInputType::eInsertReplacementText:
     case EditorInputType::eInsertFromYank:
     case EditorInputType::eFormatSetBlockTextDirection:
     case EditorInputType::eFormatSetInlineTextDirection:
       return true;
     default:
@@ -175,16 +176,17 @@ inline bool IsDataAvailableOnHTMLEditor(
 
 /**
  * IsDataTransferAvailableOnHTMLEditor() returns true if aInputType on
  * HTMLEditor should have non-null InputEvent.dataTransfer value.
  */
 inline bool IsDataTransferAvailableOnHTMLEditor(EditorInputType aInputType) {
   switch (aInputType) {
     case EditorInputType::eInsertFromPaste:
+    case EditorInputType::eInsertFromPasteAsQuotation:
     case EditorInputType::eInsertFromDrop:
     case EditorInputType::eInsertTranspose:
     case EditorInputType::eInsertReplacementText:
     case EditorInputType::eInsertFromYank:
       return true;
     default:
       return false;
   }