Bug 1461708 - part 7: Make EventStateManager::HandleMiddleClickPaste() dispatch ePaste event by itself r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 10 Oct 2018 12:05:39 +0000
changeset 496186 0f2549de4e47ed8570b1e0a3f368bc82c17895b9
parent 496185 a22e64bb83ed9873094e753df4f91e0f29853c13
child 496187 9e84f8d5b086020dba510678222a6c3ec568edf2
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1461708
milestone64.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 1461708 - part 7: Make EventStateManager::HandleMiddleClickPaste() dispatch ePaste event by itself r=smaug This is preparation of the last patch. Even if no editor is clicked with middle button, we need to do: - collapse Selection at the clicked point. - dispatch "paste" event. Therefore, HandleMiddleClickPaste() should dispatch ePaste event by itself and each editor methods should have a bool argument which the caller wants ePaste event automatically. Note that Chromium dispatches "paste" event and pastes clipboard content into clicked editor even if preceding "auxclick" event is consumed. However, our traditional behavior is not dispatching "paste" event nor pasting clipboard content. Unless Chromium developer keeps their odd behavior, we should keep our traditional behavior since our behavior is conforming to DOM event model. Differential Revision: https://phabricator.services.mozilla.com/D7854
accessible/generic/HyperTextAccessible-inl.h
dom/events/EventStateManager.cpp
dom/tests/mochitest/general/test_clipboard_events.html
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorCommands.cpp
editor/libeditor/EditorEventListener.cpp
editor/libeditor/HTMLEditor.h
editor/libeditor/HTMLEditorDataTransfer.cpp
editor/libeditor/TextEditor.cpp
editor/libeditor/TextEditor.h
editor/libeditor/TextEditorDataTransfer.cpp
editor/libeditor/tests/test_middle_click_paste.html
--- a/accessible/generic/HyperTextAccessible-inl.h
+++ b/accessible/generic/HyperTextAccessible-inl.h
@@ -6,17 +6,16 @@
 #ifndef mozilla_a11y_HyperTextAccessible_inl_h__
 #define mozilla_a11y_HyperTextAccessible_inl_h__
 
 #include "HyperTextAccessible.h"
 
 #include "nsAccUtils.h"
 
 #include "nsIClipboard.h"
-#include "nsIEditor.h"
 #include "nsIPersistentProperties2.h"
 #include "nsFrameSelection.h"
 
 #include "mozilla/TextEditor.h"
 
 namespace mozilla {
 namespace a11y {
 
@@ -117,17 +116,17 @@ HyperTextAccessible::DeleteText(int32_t 
 }
 
 inline void
 HyperTextAccessible::PasteText(int32_t aPosition)
 {
   RefPtr<TextEditor> textEditor = GetEditor();
   if (textEditor) {
     SetSelectionRange(aPosition, aPosition);
-    textEditor->PasteAsAction(nsIClipboard::kGlobalClipboard);
+    textEditor->PasteAsAction(nsIClipboard::kGlobalClipboard, true);
   }
 }
 
 inline index_t
 HyperTextAccessible::ConvertMagicOffset(int32_t aOffset) const
 {
   if (aOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT)
     return CharacterCount();
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -28,16 +28,17 @@
 #include "mozilla/dom/WheelEventBinding.h"
 
 #include "ContentEventHandler.h"
 #include "IMEContentObserver.h"
 #include "WheelHandlingHelper.h"
 
 #include "nsCommandParams.h"
 #include "nsCOMPtr.h"
+#include "nsCopySupport.h"
 #include "nsFocusManager.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIClipboard.h"
 #include "nsIContent.h"
 #include "nsIContentInlines.h"
 #include "nsIDocument.h"
 #include "nsIFrame.h"
 #include "nsIWidget.h"
@@ -5127,23 +5128,19 @@ EventStateManager::HandleMiddleClickPast
                                           nsEventStatus* aStatus,
                                           TextEditor* aTextEditor)
 {
   MOZ_ASSERT(aPresShell);
   MOZ_ASSERT(aMouseEvent);
   MOZ_ASSERT(aMouseEvent->mMessage == eMouseClick &&
              aMouseEvent->button == WidgetMouseEventBase::eMiddleButton);
   MOZ_ASSERT(aStatus);
+  MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault);
   MOZ_ASSERT(aTextEditor);
 
-  if (*aStatus == nsEventStatus_eConsumeNoDefault) {
-    // Already consumed.  Do nothing.
-    return NS_OK;
-  }
-
   RefPtr<Selection> selection = aTextEditor->GetSelection();
   if (NS_WARN_IF(!selection)) {
     return NS_ERROR_FAILURE;
   }
 
   // Move selection to the clicked point.
   nsCOMPtr<nsIContent> container;
   int32_t offset;
@@ -5167,16 +5164,25 @@ EventStateManager::HandleMiddleClickPast
   if (NS_SUCCEEDED(rv)) {
     bool selectionSupported;
     rv = clipboardService->SupportsSelectionClipboard(&selectionSupported);
     if (NS_SUCCEEDED(rv) && selectionSupported) {
       clipboardType = nsIClipboard::kSelectionClipboard;
     }
   }
 
+  // Fire ePaste event by ourselves since we need to dispatch "paste" event
+  // even if the middle click event was consumed for compatibility with
+  // Chromium.
+  if (!nsCopySupport::FireClipboardEvent(ePaste, clipboardType,
+                                         aPresShell, selection)) {
+    *aStatus = nsEventStatus_eConsumeNoDefault;
+    return NS_OK;
+  }
+
   // Check if the editor is still the good target to paste.
   if (aTextEditor->Destroyed() ||
       aTextEditor->IsReadonly() ||
       aTextEditor->IsDisabled()) {
     // XXX Should we consume the event when the editor is readonly and/or
     //     disabled?
     return NS_OK;
   }
@@ -5193,21 +5199,21 @@ EventStateManager::HandleMiddleClickPast
       !aTextEditor->IsAcceptableInputEvent(&mouseEvent)) {
     return NS_OK;
   }
 
   // If Control key is pressed, we should paste clipboard content as
   // quotation.  Otherwise, paste it as is.
   if (aMouseEvent->IsControl()) {
     DebugOnly<nsresult> rv =
-      aTextEditor->PasteAsQuotationAsAction(clipboardType);
+      aTextEditor->PasteAsQuotationAsAction(clipboardType, false);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste as quotation");
   } else {
     DebugOnly<nsresult> rv =
-      aTextEditor->PasteAsAction(clipboardType);
+      aTextEditor->PasteAsAction(clipboardType, false);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste");
   }
   *aStatus = nsEventStatus_eConsumeNoDefault;
 
   return NS_OK;
 }
 
 nsIFrame*
--- a/dom/tests/mochitest/general/test_clipboard_events.html
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -795,21 +795,27 @@ add_task(async function test_event_targe
   contenteditableContainer.innerHTML = '<div contenteditable><p id="p1">foo</p><p id="p2">bar</p></div>';
   contenteditableContainer.firstChild.focus();
 
   let p1 = document.getElementById("p1");
   let p2 = document.getElementById("p2");
   selection.setBaseAndExtent(p1.firstChild, 1, p2.firstChild, 1);
 
   let pasteTarget = null;
-  document.addEventListener("paste", (event) => { pasteTarget = event.target; }, {once: true});
-
+  let pasteEventCount = 0;
+  function pasteEventLogger(event) {
+    pasteTarget = event.target;
+    pasteEventCount++;
+  }
+  document.addEventListener("paste", pasteEventLogger);
   synthesizeKey("v", {accelKey: 1});
   is(pasteTarget.getAttribute("id"), "p1",
      "'paste' event's target should be always an element which includes start container of the first Selection range");
-
+  is(pasteEventCount, 1,
+     "'paste' event should be fired only once when Accel+'v' is pressed");
+  document.removeEventListener("paste", pasteEventLogger);
   contenteditableContainer.innerHTML = "";
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -1196,17 +1196,17 @@ NS_IMETHODIMP
 EditorBase::CanDelete(bool* aCanDelete)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 EditorBase::Paste(int32_t aClipboardType)
 {
-  nsresult rv = AsTextEditor()->PasteAsAction(aClipboardType);
+  nsresult rv = AsTextEditor()->PasteAsAction(aClipboardType, true);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 EditorBase::PasteTransferable(nsITransferable* aTransferable)
--- a/editor/libeditor/EditorCommands.cpp
+++ b/editor/libeditor/EditorCommands.cpp
@@ -554,17 +554,17 @@ PasteCommand::DoCommand(const char* aCom
                         nsISupports* aCommandRefCon)
 {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
   if (NS_WARN_IF(!editor)) {
     return NS_ERROR_FAILURE;
   }
   TextEditor* textEditor = editor->AsTextEditor();
   MOZ_ASSERT(textEditor);
-  return textEditor->PasteAsAction(nsIClipboard::kGlobalClipboard);
+  return textEditor->PasteAsAction(nsIClipboard::kGlobalClipboard, true);
 }
 
 NS_IMETHODIMP
 PasteCommand::DoCommandParams(const char* aCommandName,
                               nsICommandParams* aParams,
                               nsISupports* aCommandRefCon)
 {
   return DoCommand(aCommandName, aCommandRefCon);
@@ -1303,31 +1303,33 @@ PasteQuotationCommand::DoCommand(const c
                                  nsISupports* aCommandRefCon)
 {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
   if (NS_WARN_IF(!editor)) {
     return NS_ERROR_FAILURE;
   }
   TextEditor* textEditor = editor->AsTextEditor();
   MOZ_ASSERT(textEditor);
-  return textEditor->PasteAsQuotationAsAction(nsIClipboard::kGlobalClipboard);
+  return textEditor->PasteAsQuotationAsAction(nsIClipboard::kGlobalClipboard,
+                                              true);
 }
 
 NS_IMETHODIMP
 PasteQuotationCommand::DoCommandParams(const char* aCommandName,
                                        nsICommandParams* aParams,
                                        nsISupports* aCommandRefCon)
 {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
   if (!editor) {
     return NS_ERROR_FAILURE;
   }
   TextEditor* textEditor = editor->AsTextEditor();
   MOZ_ASSERT(textEditor);
-  return textEditor->PasteAsQuotationAsAction(nsIClipboard::kGlobalClipboard);
+  return textEditor->PasteAsQuotationAsAction(nsIClipboard::kGlobalClipboard,
+                                              true);
 }
 
 NS_IMETHODIMP
 PasteQuotationCommand::GetCommandStateParams(const char* aCommandName,
                                              nsICommandParams* aParams,
                                              nsISupports* aCommandRefCon)
 {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -668,17 +668,17 @@ EditorEventListener::MouseClick(WidgetMo
   }
 
   if (DetachedFromEditorOrDefaultPrevented(aMouseClickEvent)) {
     // We're done if 'preventdefault' is true (see for example bug 70698).
     return NS_OK;
   }
 
   // If we got a mouse down inside the editing area, we should force the
-  // IME to commit before we change the cursor position
+  // IME to commit before we change the cursor position.
   if (!EnsureCommitCompoisition()) {
     return NS_OK;
   }
 
   if (aMouseClickEvent->button != WidgetMouseEventBase::eMiddleButton ||
       !WidgetMouseEvent::IsMiddleClickPasteEnabled()) {
     return NS_OK;
   }
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -154,18 +154,21 @@ public:
 
   /**
    * PasteAsQuotationAsAction() pastes content in clipboard with newly created
    * blockquote element.  If the editor is in plaintext mode, will paste the
    * content with appending ">" to start of each line.
    *
    * @param aClipboardType      nsIClipboard::kGlobalClipboard or
    *                            nsIClipboard::kSelectionClipboard.
+   * @param aDispatchPasteEvent true if this should dispatch ePaste event
+   *                            before pasting.  Otherwise, false.
    */
-  virtual nsresult PasteAsQuotationAsAction(int32_t aClipboardType) override;
+  virtual nsresult PasteAsQuotationAsAction(int32_t aClipboardType,
+                                            bool aDispatchPasteEvent) override;
 
   /**
    * Can we paste |aTransferable| or, if |aTransferable| is null, will a call
    * to pasteTransferable later possibly succeed if given an instance of
    * nsITransferable then? True if the doc is modifiable, and, if
    * |aTransfeable| is non-null, we have pasteable data in |aTransfeable|.
    */
   virtual bool CanPasteTransferable(nsITransferable* aTransferable) override;
@@ -1305,20 +1308,23 @@ protected: // Shouldn't be used by frien
                                   ErrorResult& aRv,
                                   bool* aIsCellSelected = nullptr) const;
 
   /**
    * PasteInternal() pasts text with replacing selected content.
    * This tries to dispatch ePaste event first.  If its defaultPrevent() is
    * called, this does nothing but returns NS_OK.
    *
-   * @param aClipboardType  nsIClipboard::kGlobalClipboard or
-   *                        nsIClipboard::kSelectionClipboard.
+   * @param aClipboardType      nsIClipboard::kGlobalClipboard or
+   *                            nsIClipboard::kSelectionClipboard.
+   * @param aDispatchPasteEvent true if this should dispatch ePaste event
+   *                            before pasting.  Otherwise, false.
    */
-  nsresult PasteInternal(int32_t aClipboardType);
+  nsresult PasteInternal(int32_t aClipboardType,
+                         bool aDispatchPasteEvent);
 
   /**
    * InsertNodeIntoProperAncestorWithTransaction() attempts to insert aNode
    * into the document, at aPointToInsert.  Checks with strict dtd to see if
    * containment is allowed.  If not allowed, will attempt to find a parent
    * in the parent hierarchy of aPointToInsert.GetContainer() that will accept
    * aNode as a child.  If such a parent is found, will split the document
    * tree from aPointToInsert up to parent, and then insert aNode.
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -1454,19 +1454,20 @@ HTMLEditor::HavePrivateHTMLFlavor(nsICli
                                            &bHavePrivateHTMLFlavor))) {
     return bHavePrivateHTMLFlavor;
   }
 
   return false;
 }
 
 nsresult
-HTMLEditor::PasteInternal(int32_t aClipboardType)
+HTMLEditor::PasteInternal(int32_t aClipboardType,
+                          bool aDispatchPasteEvent)
 {
-  if (!FireClipboardEvent(ePaste, aClipboardType)) {
+  if (aDispatchPasteEvent && !FireClipboardEvent(ePaste, aClipboardType)) {
     return NS_OK;
   }
 
   // Get Clipboard Service
   nsresult rv;
   nsCOMPtr<nsIClipboard> clipboard =
     do_GetService("@mozilla.org/widget/clipboard;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -1685,22 +1686,24 @@ HTMLEditor::CanPasteTransferable(nsITran
       return true;
     }
   }
 
   return false;
 }
 
 nsresult
-HTMLEditor::PasteAsQuotationAsAction(int32_t aClipboardType)
+HTMLEditor::PasteAsQuotationAsAction(int32_t aClipboardType,
+                                     bool aDispatchPasteEvent)
 {
   MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
              aClipboardType == nsIClipboard::kSelectionClipboard);
 
   if (IsPlaintextEditor()) {
+    // XXX In this case, we don't dispatch ePaste event.  Why?
     return PasteAsPlaintextQuotation(aClipboardType);
   }
 
   // If it's not in plain text edit mode, paste text into new
   // <blockquote type="cite"> element after removing selection.
 
   AutoPlaceholderBatch beginBatching(this);
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
@@ -1738,17 +1741,21 @@ HTMLEditor::PasteAsQuotationAsAction(int
 
   // Collapse Selection in the new <blockquote> element.
   rv = selection->Collapse(newNode, 0);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // XXX Why don't we call HTMLEditRules::DidDoAction() after Paste()?
-  rv = PasteInternal(aClipboardType);
+  // XXX If ePaste event has not been dispatched yet but selected content
+  //     has already been removed and created a <blockquote> element.
+  //     So, web apps cannot prevent the default of ePaste event which
+  //     will be dispatched by PasteInternal().
+  rv = PasteInternal(aClipboardType, aDispatchPasteEvent);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 /**
  * Paste a plaintext quotation.
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -1931,29 +1931,32 @@ TextEditor::ComputeValueInternal(const n
     return NS_ERROR_FAILURE;
   }
 
   // XXX Why don't we call TextEditRules::DidDoAction() here?
   return encoder->EncodeToString(aOutputString);
 }
 
 nsresult
-TextEditor::PasteAsQuotationAsAction(int32_t aClipboardType)
+TextEditor::PasteAsQuotationAsAction(int32_t aClipboardType,
+                                     bool aDispatchPasteEvent)
 {
   MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
              aClipboardType == nsIClipboard::kSelectionClipboard);
 
   // Get Clipboard Service
   nsresult rv;
   nsCOMPtr<nsIClipboard> clipboard =
     do_GetService("@mozilla.org/widget/clipboard;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  // XXX Why don't we dispatch ePaste event here?
+
   // Get the nsITransferable interface for getting the data from the clipboard
   nsCOMPtr<nsITransferable> trans;
   rv = PrepareTransferable(getter_AddRefs(trans));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   if (!trans) {
     return NS_OK;
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -113,18 +113,21 @@ public:
 
   /**
    * PasteAsAction() pastes clipboard content to Selection.  This method
    * may dispatch ePaste event first.  If its defaultPrevent() is called,
    * this does nothing but returns NS_OK.
    *
    * @param aClipboardType      nsIClipboard::kGlobalClipboard or
    *                            nsIClipboard::kSelectionClipboard.
+   * @param aDispatchPasteEvent true if this should dispatch ePaste event
+   *                            before pasting.  Otherwise, false.
    */
-  nsresult PasteAsAction(int32_t aClipboardType);
+  nsresult PasteAsAction(int32_t aClipboardType,
+                         bool aDispatchPasteEvent);
 
   /**
    * InsertTextAsAction() inserts aStringToInsert at selection.
    * Although this method is implementation of nsIPlaintextEditor.insertText(),
    * this treats the input is an edit action.  If you'd like to insert text
    * as part of edit action, you probably should use InsertTextAsSubAction().
    *
    * @param aStringToInsert     The string to insert.
@@ -133,18 +136,21 @@ public:
 
   /**
    * PasteAsQuotationAsAction() pastes content in clipboard as quotation.
    * If the editor is TextEditor or in plaintext mode, will paste the content
    * with appending ">" to start of each line.
    *
    * @param aClipboardType      nsIClipboard::kGlobalClipboard or
    *                            nsIClipboard::kSelectionClipboard.
+   * @param aDispatchPasteEvent true if this should dispatch ePaste event
+   *                            before pasting.  Otherwise, false.
    */
-  virtual nsresult PasteAsQuotationAsAction(int32_t aClipboardType);
+  virtual nsresult PasteAsQuotationAsAction(int32_t aClipboardType,
+                                            bool aDispatchPasteEvent);
 
   /**
    * DeleteSelectionAsAction() removes selection content or content around
    * caret with transactions.  This should be used for handling it as an
    * edit action.  If you'd like to remove selection for preparing to insert
    * something, you probably should use DeleteSelectionAsSubAction().
    *
    * @param aDirection          How much range should be removed.
--- a/editor/libeditor/TextEditorDataTransfer.cpp
+++ b/editor/libeditor/TextEditorDataTransfer.cpp
@@ -290,27 +290,29 @@ TextEditor::OnDrop(DragEvent* aDropEvent
   }
 
   ScrollSelectionIntoView(false);
 
   return NS_OK;
 }
 
 nsresult
-TextEditor::PasteAsAction(int32_t aClipboardType)
+TextEditor::PasteAsAction(int32_t aClipboardType,
+                          bool aDispatchPasteEvent)
 {
   if (AsHTMLEditor()) {
-    nsresult rv = AsHTMLEditor()->PasteInternal(aClipboardType);
+    nsresult rv =
+      AsHTMLEditor()->PasteInternal(aClipboardType, aDispatchPasteEvent);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
-  if (!FireClipboardEvent(ePaste, aClipboardType)) {
+  if (aDispatchPasteEvent && !FireClipboardEvent(ePaste, aClipboardType)) {
     return NS_OK;
   }
 
   // Get Clipboard Service
   nsresult rv;
   nsCOMPtr<nsIClipboard> clipboard =
     do_GetService("@mozilla.org/widget/clipboard;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
--- a/editor/libeditor/tests/test_middle_click_paste.html
+++ b/editor/libeditor/tests/test_middle_click_paste.html
@@ -1,12 +1,12 @@
 <!DOCTYPE html>
 <html>
 <head>
-  <title>Test for paste as quotation with middle button click</title>
+  <title>Test for paste with middle button click</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" 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;">
 
@@ -103,27 +103,129 @@ async function doTextareaTests(aTextarea
 
   await copyPlaintext("abc\ndef\n\n");
   aTextarea.focus();
   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 \">\"");
   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});
+  synthesizeMouseAtCenter(aTextarea, {button: 1});
+  is(aTextarea.value, "",
+     "If 'click' event is consumed at capturing phase of the <body>, paste should be canceled");
+  is(pasteEventCount, 0,
+     "If 'click' event is consumed at capturing phase of the <body>, 'paste' event should not be fired");
+  aTextarea.value = "";
+
+  await copyPlaintext("abc");
+  aTextarea.focus();
+  aTextarea.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  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");
+  aTextarea.value = "";
+
+  await copyPlaintext("abc");
+  aTextarea.focus();
+  aTextarea.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  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");
+  aTextarea.value = "";
+
+  await copyPlaintext("abc");
+  aTextarea.focus();
+  aTextarea.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  synthesizeMouseAtCenter(aTextarea, {button: 1});
+  todo_is(aTextarea.value, "",
+          "If 'auxclick' event is consumed, paste should be canceled");
+  todo_is(pasteEventCount, 0,
+          "If 'auxclick' event is consumed, 'paste' event should not be fired once");
+  aTextarea.value = "";
+
+  aTextarea.removeEventListener("paste", pasteEventLogger);
 }
 
 async function doContenteditableTests(aEditableDiv) {
   await copyPlaintext("abc\ndef\nghi");
   aEditableDiv.focus();
   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");
   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});
+  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+  is(aEditableDiv.innerHTML, "",
+     "If 'click' event is consumed at capturing phase of the window, paste should be canceled");
+  is(pasteEventCount, 0,
+     "If 'click' event is consumed at capturing phase of the window, 'paste' event should be fired once");
+  aEditableDiv.innerHTML = "";
+
+  await copyPlaintext("abc");
+  aEditableDiv.focus();
+  aEditableDiv.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  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");
+  aEditableDiv.innerHTML = "";
+
+  await copyPlaintext("abc");
+  aEditableDiv.focus();
+  aEditableDiv.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  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");
+  aEditableDiv.innerHTML = "";
+
+  await copyPlaintext("abc");
+  aEditableDiv.focus();
+  aEditableDiv.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
+  pasteEventCount = 0;
+  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
+  todo_is(aEditableDiv.innerHTML, "",
+          "If 'auxclick' event is consumed, paste should be canceled");
+  todo_is(pasteEventCount, 0,
+          "If 'auxclick' event is consumed, 'paste' event should not be fired");
+  aEditableDiv.innerHTML = "";
+
+  aEditableDiv.removeEventListener("paste", pasteEventLogger);
+
   // Oddly, copyHTMLContent fails randomly only on Linux.  Let's skip this.
   if (navigator.platform.startsWith("Linux")) {
     return;
   }
 
   await copyHTMLContent("<p>abc</p><p>def</p><p>ghi</p>");
   aEditableDiv.focus();
   synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
@@ -135,24 +237,100 @@ async function doContenteditableTests(aE
     // 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>");
   }
   aEditableDiv.innerHTML = "";
 }
 
+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});
+  todo_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});
+  todo_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 doTests() {
   await SpecialPowers.pushPrefEnv({"set": [["middlemouse.paste", true],
                                            ["middlemouse.contentLoadURL", false]]});
   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"));
   SimpleTest.finish();
 }
 
 SimpleTest.waitForFocus(doTests);
 </script>
 </pre>
 </body>
 </html>