Bug 1461708 - part 5: Move EditorEventListener::HandleMiddleClickPaste() to EventStateManager r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 10 Oct 2018 12:04:17 +0000
changeset 488849 95d15775e123f881124b1b0b239b2dd8f837bd0e
parent 488848 219527f4c312eb25ee1103ac3bdfa38f87539e02
child 488850 a22e64bb83ed9873094e753df4f91e0f29853c13
push id246
push userfmarier@mozilla.com
push dateSat, 13 Oct 2018 00:15:40 +0000
reviewerssmaug
bugs1461708
milestone64.0a1
Bug 1461708 - part 5: Move EditorEventListener::HandleMiddleClickPaste() to EventStateManager r=smaug EventStateManager needs to handle middle click paste without editor. Therefore, the handler should be in EventStateManager. Differential Revision: https://phabricator.services.mozilla.com/D7852
dom/events/EventStateManager.cpp
dom/events/EventStateManager.h
editor/libeditor/EditorEventListener.cpp
editor/libeditor/EditorEventListener.h
widget/MouseEvents.h
widget/WidgetEventImpl.cpp
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -30,16 +30,17 @@
 #include "ContentEventHandler.h"
 #include "IMEContentObserver.h"
 #include "WheelHandlingHelper.h"
 
 #include "nsCommandParams.h"
 #include "nsCOMPtr.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"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
 #include "nsGkAtoms.h"
@@ -5115,16 +5116,105 @@ EventStateManager::DispatchClickEvents(n
                                    aPresShell, aMouseUpContent, currentTarget,
                                    false, aOverrideClickTarget);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch eMouseAuxClick");
   }
 
   return rv;
 }
 
+nsresult
+EventStateManager::HandleMiddleClickPaste(nsIPresShell* aPresShell,
+                                          WidgetMouseEvent* aMouseEvent,
+                                          nsEventStatus* aStatus,
+                                          TextEditor* aTextEditor)
+{
+  MOZ_ASSERT(aPresShell);
+  MOZ_ASSERT(aMouseEvent);
+  MOZ_ASSERT(aMouseEvent->mMessage == eMouseClick &&
+             aMouseEvent->button == WidgetMouseEventBase::eMiddleButton);
+  MOZ_ASSERT(aStatus);
+  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;
+  nsLayoutUtils::GetContainerAndOffsetAtEvent(aPresShell, aMouseEvent,
+                                              getter_AddRefs(container),
+                                              &offset);
+  if (container) {
+    // XXX If readonly or disabled <input> or <textarea> in contenteditable
+    //     designMode editor is clicked, the point is in the editor.
+    //     However, outer HTMLEditor and Selection should handle it.
+    //     So, in such case, Selection::Collapse() will fail.
+    DebugOnly<nsresult> rv = selection->Collapse(container, offset);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+      "Failed to collapse Selection at middle clicked");
+  }
+
+  int32_t clipboardType = nsIClipboard::kGlobalClipboard;
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIClipboard> clipboardService =
+    do_GetService("@mozilla.org/widget/clipboard;1", &rv);
+  if (NS_SUCCEEDED(rv)) {
+    bool selectionSupported;
+    rv = clipboardService->SupportsSelectionClipboard(&selectionSupported);
+    if (NS_SUCCEEDED(rv) && selectionSupported) {
+      clipboardType = nsIClipboard::kSelectionClipboard;
+    }
+  }
+
+  // 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;
+  }
+
+  // The selection may have been modified during reflow.  Therefore, we
+  // should adjust event target to pass IsAcceptableInputEvent().
+  nsRange* range = selection->GetRangeAt(0);
+  if (!range) {
+    return NS_OK;
+  }
+  WidgetMouseEvent mouseEvent(*aMouseEvent);
+  mouseEvent.mOriginalTarget = range->GetStartContainer();
+  if (NS_WARN_IF(!mouseEvent.mOriginalTarget) ||
+      !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);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste as quotation");
+  } else {
+    DebugOnly<nsresult> rv =
+      aTextEditor->PasteAsAction(clipboardType);
+    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to paste");
+  }
+  *aStatus = nsEventStatus_eConsumeNoDefault;
+
+  return NS_OK;
+}
+
 nsIFrame*
 EventStateManager::GetEventTarget()
 {
   nsIPresShell *shell;
   if (mCurrentTarget ||
       !mPresContext ||
       !(shell = mPresContext->GetPresShell())) {
     return mCurrentTarget;
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -358,16 +358,34 @@ public:
    * If the absolute values of mMultiplierX and/or mMultiplierY are equal or
    * larger than this value, the computed scroll amount isn't rounded down to
    * the page width or height.
    */
   enum {
     MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL = 1000
   };
 
+  /**
+   * HandleMiddleClickPaste() handles middle mouse button event as pasting
+   * clipboard text.
+   *
+   * @param aPresShell              The PresShell for the ESM.  This lifetime
+   *                                should be guaranteed by the caller.
+   * @param aMouseEvent             The eMouseClick event which caused the
+   *                                paste.
+   * @param aStatus                 The event status of aMouseEvent.
+   * @param aTextEditor             TextEditor which may be pasted the
+   *                                clipboard text by the middle click.
+   */
+  MOZ_CAN_RUN_SCRIPT
+  nsresult HandleMiddleClickPaste(nsIPresShell* aPresShell,
+                                  WidgetMouseEvent* aMouseEvent,
+                                  nsEventStatus* aStatus,
+                                  TextEditor* aTextEditor);
+
 protected:
   /**
    * Prefs class capsules preference management.
    */
   class Prefs
   {
   public:
     static bool KeyCausesActivation() { return sKeyCausesActivation; }
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -6,16 +6,17 @@
 
 #include "EditorEventListener.h"
 
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc.
 #include "mozilla/AutoRestore.h"
 #include "mozilla/ContentEvents.h"      // for InternalFocusEvent
 #include "mozilla/EditorBase.h"         // for EditorBase, etc.
 #include "mozilla/EventListenerManager.h" // for EventListenerManager
+#include "mozilla/EventStateManager.h"  // for EventStateManager
 #include "mozilla/IMEStateManager.h"    // for IMEStateManager
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/TextEditor.h"         // for TextEditor
 #include "mozilla/TextEvents.h"         // for WidgetCompositionEvent
 #include "mozilla/dom/Element.h"        // for Element
 #include "mozilla/dom/Event.h"          // for Event
 #include "mozilla/dom/EventTarget.h"    // for EventTarget
 #include "mozilla/dom/MouseEvent.h"     // for MouseEvent
@@ -642,28 +643,28 @@ EditorEventListener::KeyPress(WidgetKeyb
 
 nsresult
 EditorEventListener::MouseClick(MouseEvent* aMouseEvent)
 {
   if (NS_WARN_IF(!aMouseEvent) || DetachedFromEditor()) {
     return NS_OK;
   }
   // nothing to do if editor isn't editable or clicked on out of the editor.
-  RefPtr<EditorBase> editorBase(mEditorBase);
+  RefPtr<TextEditor> textEditor = mEditorBase->AsTextEditor();
   WidgetMouseEvent* clickEvent =
     aMouseEvent->WidgetEventPtr()->AsMouseEvent();
-  if (editorBase->IsReadonly() || editorBase->IsDisabled() ||
-      !editorBase->IsAcceptableInputEvent(clickEvent)) {
+  if (textEditor->IsReadonly() || textEditor->IsDisabled() ||
+      !textEditor->IsAcceptableInputEvent(clickEvent)) {
     return NS_OK;
   }
 
   // Notifies clicking on editor to IMEStateManager even when the event was
   // consumed.
   if (EditorHasFocus()) {
-    nsPresContext* presContext = GetPresContext();
+    RefPtr<nsPresContext> presContext = GetPresContext();
     if (presContext) {
       IMEStateManager::OnClickInEditor(presContext, GetFocusedRootContent(),
                                        clickEvent);
       if (DetachedFromEditor()) {
         return NS_OK;
       }
     }
   }
@@ -674,74 +675,42 @@ EditorEventListener::MouseClick(MouseEve
   }
 
   // If we got a mouse down inside the editing area, we should force the
   // IME to commit before we change the cursor position
   if (!EnsureCommitCompoisition()) {
     return NS_OK;
   }
 
-  if (clickEvent->button == 1) {
-    return HandleMiddleClickPaste(aMouseEvent);
-  }
-  return NS_OK;
-}
-
-nsresult
-EditorEventListener::HandleMiddleClickPaste(MouseEvent* aMouseEvent)
-{
-  MOZ_ASSERT(aMouseEvent);
-
-  WidgetMouseEvent* clickEvent =
-    aMouseEvent->WidgetEventPtr()->AsMouseEvent();
-  MOZ_ASSERT(!DetachedFromEditorOrDefaultPrevented(clickEvent));
-
-  if (!Preferences::GetBool("middlemouse.paste", false)) {
-    // Middle click paste isn't enabled.
+  if (clickEvent->button != WidgetMouseEventBase::eMiddleButton ||
+      !WidgetMouseEvent::IsMiddleClickPasteEnabled()) {
     return NS_OK;
   }
 
-  // Set the selection to the point under the mouse cursor:
-  nsCOMPtr<nsINode> parent = aMouseEvent->GetRangeParent();
-  int32_t offset = aMouseEvent->RangeOffset();
-
-  RefPtr<TextEditor> textEditor = mEditorBase->AsTextEditor();
-  MOZ_ASSERT(textEditor);
-
-  RefPtr<Selection> selection = textEditor->GetSelection();
-  if (selection) {
-    selection->Collapse(parent, offset);
+  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+  if (NS_WARN_IF(!presShell)) {
+    return NS_OK;
+  }
+  nsPresContext* presContext = GetPresContext();
+  if (NS_WARN_IF(!presContext)) {
+    return NS_OK;
   }
-
-  nsresult rv;
-  int32_t clipboard = nsIClipboard::kGlobalClipboard;
-  nsCOMPtr<nsIClipboard> clipboardService =
-    do_GetService("@mozilla.org/widget/clipboard;1", &rv);
-  if (NS_SUCCEEDED(rv)) {
-    bool selectionSupported;
-    rv = clipboardService->SupportsSelectionClipboard(&selectionSupported);
-    if (NS_SUCCEEDED(rv) && selectionSupported) {
-      clipboard = nsIClipboard::kSelectionClipboard;
-    }
+  MOZ_ASSERT(!clickEvent->DefaultPrevented());
+  nsEventStatus status = nsEventStatus_eIgnore;
+  RefPtr<EventStateManager> esm = presContext->EventStateManager();
+  DebugOnly<nsresult> rv =
+    esm->HandleMiddleClickPaste(presShell, clickEvent, &status, textEditor);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "Failed to paste for the middle button click");
+  if (status == nsEventStatus_eConsumeNoDefault) {
+    // Prevent the event from propagating up to be possibly handled
+    // again by the containing window:
+    clickEvent->StopImmediatePropagation();
+    clickEvent->PreventDefault();
   }
-
-  // If the ctrl key is pressed, we'll do paste as quotation.
-  // Would've used the alt key, but the kde wmgr treats alt-middle specially.
-  if (clickEvent->IsControl()) {
-    textEditor->PasteAsQuotationAsAction(clipboard);
-  } else {
-    textEditor->PasteAsAction(clipboard);
-  }
-
-  // Prevent the event from propagating up to be possibly handled
-  // again by the containing window:
-  clickEvent->StopPropagation();
-  clickEvent->PreventDefault();
-
-  // We processed the event, whether drop/paste succeeded or not
   return NS_OK;
 }
 
 bool
 EditorEventListener::NotifyIMEOfMouseButtonEvent(
                        WidgetMouseEvent* aMouseEvent)
 {
   MOZ_ASSERT(aMouseEvent);
--- a/editor/libeditor/EditorEventListener.h
+++ b/editor/libeditor/EditorEventListener.h
@@ -86,18 +86,16 @@ protected:
   nsIPresShell* GetPresShell() const;
   nsPresContext* GetPresContext() const;
   nsIContent* GetFocusedRootContent();
   // Returns true if IME consumes the mouse event.
   bool NotifyIMEOfMouseButtonEvent(WidgetMouseEvent* aMouseEvent);
   bool EditorHasFocus();
   bool IsFileControlTextBox();
   bool ShouldHandleNativeKeyBindings(WidgetKeyboardEvent* aKeyboardEvent);
-  MOZ_CAN_RUN_SCRIPT
-  nsresult HandleMiddleClickPaste(dom::MouseEvent* aMouseEvent);
 
   /**
    * DetachedFromEditor() returns true if editor was detached.
    * Otherwise, false.
    */
   bool DetachedFromEditor() const;
 
   /**
--- a/widget/MouseEvents.h
+++ b/widget/MouseEvents.h
@@ -346,16 +346,21 @@ public:
   /**
    * Returns true if the event is a real mouse event.  Otherwise, i.e., it's
    * a synthesized event by scroll or something, returns false.
    */
   bool IsReal() const
   {
     return mReason == eReal;
   }
+
+  /**
+   * Returns true if middle click paste is enabled.
+   */
+  static bool IsMiddleClickPasteEnabled();
 };
 
 /******************************************************************************
  * mozilla::WidgetDragEvent
  ******************************************************************************/
 
 class WidgetDragEvent : public WidgetMouseEvent
 {
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -646,16 +646,26 @@ WidgetInputEvent::AccelModifier()
 #endif
         break;
     }
   }
   return sAccelModifier;
 }
 
 /******************************************************************************
+ * mozilla::WidgetMouseEvent (MouseEvents.h)
+ ******************************************************************************/
+
+/* static */ bool
+WidgetMouseEvent::IsMiddleClickPasteEnabled()
+{
+  return Preferences::GetBool("middlemouse.paste", false);
+}
+
+/******************************************************************************
  * mozilla::WidgetWheelEvent (MouseEvents.h)
  ******************************************************************************/
 
 /* static */ double
 WidgetWheelEvent::ComputeOverriddenDelta(double aDelta, bool aIsForVertical)
 {
   if (!gfxPrefs::MouseWheelHasRootScrollDeltaOverride()) {
     return aDelta;