Bug 1339543 part 1 Wrap nsIWidget::ExecuteNativeKeyBinding() with a WidgetKeyboardEvent method and users of the method should use it r?smaug draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 19 May 2017 16:50:30 +0900
changeset 581127 ed9037a80cdcafb2e1678d0896010a23c6f5f7fc
parent 580068 7cc6128238179733b9a22d5c34cd6658bbf82839
child 581128 b0a7663a02601f42020543d671e1b7f0000ade8d
push id59777
push usermasayuki@d-toybox.com
push dateFri, 19 May 2017 09:49:08 +0000
reviewerssmaug
bugs1339543
milestone55.0a1
Bug 1339543 part 1 Wrap nsIWidget::ExecuteNativeKeyBinding() with a WidgetKeyboardEvent method and users of the method should use it r?smaug Currently, edit commands for native key bindings are stored in widget. This is stateful and really complicated in content process because it needs to cache them. We can make this simpler if we make WidgetKeyboardEvent store edit commands for the key combination. Then, child process can handle it even if it's delayed event or it's a nested event. This patch adds arrays to WidgetKeyboardEvent to store edit commands which are initialized with nsIWidget::ExecuteNativeKeyBinding() and adds WidgetKeyboardEvent::ExecuteEditCommands() to execute stored edit commands as same as nsIWidget::ExecutenativeKeyBinding(). MozReview-Commit-ID: BGRvBrLz5lp
dom/html/nsTextEditorState.cpp
editor/libeditor/EditorEventListener.cpp
widget/EventForwards.h
widget/PuppetWidget.cpp
widget/TextEvents.h
widget/WidgetEventImpl.cpp
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -36,16 +36,17 @@
 #include "nsIEditor.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/TextEditRules.h"
 #include "mozilla/EventListenerManager.h"
 #include "nsContentUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsTextNode.h"
 #include "nsIController.h"
+#include "mozilla/AutoRestore.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "nsNumberControlFrame.h"
 #include "nsFrameSelection.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/layers/ScrollInputMethods.h"
@@ -987,25 +988,32 @@ nsTextInputListener::HandleEvent(nsIDOME
   if (keyEvent->mMessage != eKeyPress) {
     return NS_OK;
   }
 
   nsIWidget::NativeKeyBindingsType nativeKeyBindingsType =
     mTxtCtrlElement->IsTextArea() ?
       nsIWidget::NativeKeyBindingsForMultiLineEditor :
       nsIWidget::NativeKeyBindingsForSingleLineEditor;
+
   nsIWidget* widget = keyEvent->mWidget;
   // If the event is created by chrome script, the widget is nullptr.
   if (!widget) {
     widget = mFrame->GetNearestWidget();
     NS_ENSURE_TRUE(widget, NS_OK);
   }
-                                         
-  if (widget->ExecuteNativeKeyBinding(nativeKeyBindingsType,
-                                      *keyEvent, DoCommandCallback, mFrame)) {
+
+  // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
+  // If the event is created by chrome script, it is nullptr but we need to
+  // execute native key bindings.  Therefore, we need to set widget to
+  // WidgetEvent::mWidget temporarily.
+  AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(keyEvent->mWidget);
+  keyEvent->mWidget = widget;
+  if (keyEvent->ExecuteEditCommands(nativeKeyBindingsType,
+                                    DoCommandCallback, mFrame)) {
     aEvent->PreventDefault();
   }
   return NS_OK;
 }
 
 // BEGIN nsIEditorObserver
 
 NS_IMETHODIMP
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -622,20 +622,26 @@ EditorEventListener::KeyPress(WidgetKeyb
   if (!widget) {
     nsCOMPtr<nsIPresShell> ps = GetPresShell();
     nsPresContext* pc = ps ? ps->GetPresContext() : nullptr;
     widget = pc ? pc->GetNearestWidget() : nullptr;
     NS_ENSURE_TRUE(widget, NS_OK);
   }
 
   nsCOMPtr<nsIDocument> doc = editorBase->GetDocument();
-  bool handled = widget->ExecuteNativeKeyBinding(
-                           nsIWidget::NativeKeyBindingsForRichTextEditor,
-                           *aKeyboardEvent, DoCommandCallback, doc);
-  if (handled) {
+
+  // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
+  // If the event is created by chrome script, it is nullptr but we need to
+  // execute native key bindings.  Therefore, we need to set widget to
+  // WidgetEvent::mWidget temporarily.
+  AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(aKeyboardEvent->mWidget);
+  aKeyboardEvent->mWidget = widget;
+  if (aKeyboardEvent->ExecuteEditCommands(
+                        nsIWidget::NativeKeyBindingsForRichTextEditor,
+                        DoCommandCallback, doc)) {
     aKeyboardEvent->PreventDefault();
   }
   return NS_OK;
 }
 
 nsresult
 EditorEventListener::MouseClick(nsIDOMMouseEvent* aMouseEvent)
 {
--- a/widget/EventForwards.h
+++ b/widget/EventForwards.h
@@ -158,16 +158,17 @@ typedef AutoTArray<ShortcutKeyCandidate,
 
 // TextRange.h
 typedef uint8_t RawTextRangeType;
 enum class TextRangeType : RawTextRangeType;
 
 struct TextRangeStyle;
 struct TextRange;
 
+class EditCommands;
 class TextRangeArray;
 
 // FontRange.h
 struct FontRange;
 
 } // namespace mozilla
 
 #endif // mozilla_EventForwards_h__
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -554,19 +554,20 @@ PuppetWidget::AsyncPanZoomEnabled() cons
 
 bool
 PuppetWidget::ExecuteNativeKeyBinding(
                 NativeKeyBindingsType aType,
                 const mozilla::WidgetKeyboardEvent& aEvent,
                 DoCommandCallback aCallback,
                 void* aCallbackData)
 {
+  MOZ_ASSERT(!aEvent.IsEditCommandsInitialized(aType));
+
   AutoCacheNativeKeyCommands autoCache(this);
-  if (!aEvent.mWidget && !mNativeKeyCommandsValid) {
-    MOZ_ASSERT(!aEvent.mFlags.mIsSynthesizedForTests);
+  if (!mNativeKeyCommandsValid) {
     // Abort if untrusted to avoid leaking system settings
     if (NS_WARN_IF(!aEvent.IsTrusted())) {
       return false;
     }
     mTabChild->RequestNativeKeyBindings(&autoCache, &aEvent);
   }
 
   MOZ_ASSERT(mNativeKeyCommandsValid);
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -125,16 +125,19 @@ protected:
 #endif // #ifdef XP_MACOSX
     , mKeyNameIndex(KEY_NAME_INDEX_Unidentified)
     , mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN)
     , mInputMethodAppState(eNotHandled)
     , mIsRepeat(false)
     , mIsComposing(false)
     , mIsReserved(false)
     , mIsSynthesizedByTIP(false)
+    , mEditCommandsForSingleLineEditorInitialized(false)
+    , mEditCommandsForMultiLineEditorInitialized(false)
+    , mEditCommandsForRichTextEditorInitialized(false)
   {
   }
 
 public:
   virtual WidgetKeyboardEvent* AsKeyboardEvent() override { return this; }
 
   WidgetKeyboardEvent(bool aIsTrusted, EventMessage aMessage,
                       nsIWidget* aWidget,
@@ -153,16 +156,19 @@ public:
 #endif // #ifdef XP_MACOSX
     , mKeyNameIndex(KEY_NAME_INDEX_Unidentified)
     , mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN)
     , mInputMethodAppState(eNotHandled)
     , mIsRepeat(false)
     , mIsComposing(false)
     , mIsReserved(false)
     , mIsSynthesizedByTIP(false)
+    , mEditCommandsForSingleLineEditorInitialized(false)
+    , mEditCommandsForMultiLineEditorInitialized(false)
+    , mEditCommandsForRichTextEditorInitialized(false)
   {
     // If this is a keyboard event on a plugin, it shouldn't fired on content.
     mFlags.mOnlySystemGroupDispatchInContent =
       mFlags.mNoCrossProcessBoundaryForwarding = IsKeyEventOnPlugin();
   }
 
   static bool IsKeyDownOrKeyDownOnPlugin(EventMessage aMessage)
   {
@@ -192,16 +198,19 @@ public:
   virtual WidgetEvent* Duplicate() const override
   {
     MOZ_ASSERT(mClass == eKeyboardEventClass,
                "Duplicate() must be overridden by sub class");
     // Not copying widget, it is a weak reference.
     WidgetKeyboardEvent* result =
       new WidgetKeyboardEvent(false, mMessage, nullptr);
     result->AssignKeyEventData(*this, true);
+    result->mEditCommandsForSingleLineEditor = mEditCommandsForSingleLineEditor;
+    result->mEditCommandsForMultiLineEditor = mEditCommandsForMultiLineEditor;
+    result->mEditCommandsForRichTextEditor = mEditCommandsForRichTextEditor;
     result->mFlags = mFlags;
     return result;
   }
 
   // OS translated Unicode chars which are used for accesskey and accelkey
   // handling. The handlers will try from first character to last character.
   nsTArray<AlternativeCharCode> mAlternativeCharCodes;
   // DOM KeyboardEvent.key only when mKeyNameIndex is KEY_NAME_INDEX_USE_STRING.
@@ -273,16 +282,39 @@ public:
   bool mIsComposing;
   // Indicates if the key combination is reserved by chrome.  This is set by
   // nsXBLWindowKeyHandler at capturing phase of the default event group.
   bool mIsReserved;
   // Indicates whether the event is synthesized from Text Input Processor
   // or an actual event from nsAppShell.
   bool mIsSynthesizedByTIP;
 
+#ifdef DEBUG
+  /**
+   * IsEditCommandsInitialized() returns true if edit commands for aType
+   * was already initialized.  Otherwise, false.
+   */
+  bool IsEditCommandsInitialized(
+         nsIWidget::NativeKeyBindingsType aType) const
+  {
+    return const_cast<WidgetKeyboardEvent*>(this)->
+             IsEditCommandsInitializedRef(aType);
+  }
+#endif // #ifdef DEBUG
+
+  /**
+   * Execute edit commands for aType.
+   *
+   * @return        true if the caller should do nothing anymore.
+   *                false, otherwise.
+   */
+  bool ExecuteEditCommands(nsIWidget::NativeKeyBindingsType aType,
+                           nsIWidget::DoCommandCallback aCallback,
+                           void* aCallbackData);
+
   // If the key should cause keypress events, this returns true.
   // Otherwise, false.
   bool ShouldCauseKeypressEvents() const;
 
   // mCharCode value of non-eKeyPress events is always 0.  However, if
   // non-eKeyPress event has one or more alternative char code values,
   // its first item should be the mCharCode value of following eKeyPress event.
   // PseudoCharCode() returns mCharCode value for eKeyPress event,
@@ -403,27 +435,84 @@ public:
     mNativeModifierFlags = aEvent.mNativeModifierFlags;
     mNativeCharacters.Assign(aEvent.mNativeCharacters);
     mNativeCharactersIgnoringModifiers.
       Assign(aEvent.mNativeCharactersIgnoringModifiers);
     mPluginTextEventString.Assign(aEvent.mPluginTextEventString);
 #endif
     mInputMethodAppState = aEvent.mInputMethodAppState;
     mIsSynthesizedByTIP = aEvent.mIsSynthesizedByTIP;
+
+    // Don't copy mEditCommandsFor*Editor because it may require a lot of
+    // memory space.  For example, if the event is dispatched but grabbed by
+    // a JS variable, they are not necessary anymore.
+
+    mEditCommandsForSingleLineEditorInitialized =
+      aEvent.mEditCommandsForSingleLineEditorInitialized;
+    mEditCommandsForMultiLineEditorInitialized =
+      aEvent.mEditCommandsForMultiLineEditorInitialized;
+    mEditCommandsForRichTextEditorInitialized =
+      aEvent.mEditCommandsForRichTextEditorInitialized;
   }
 
 private:
   static const char16_t* const kKeyNames[];
   static const char16_t* const kCodeNames[];
   typedef nsDataHashtable<nsStringHashKey,
                           KeyNameIndex> KeyNameIndexHashtable;
   typedef nsDataHashtable<nsStringHashKey,
                           CodeNameIndex> CodeNameIndexHashtable;
   static KeyNameIndexHashtable* sKeyNameIndexHashtable;
   static CodeNameIndexHashtable* sCodeNameIndexHashtable;
+
+  // mEditCommandsFor*Editor store edit commands.  This should be initialized
+  // with InitEditCommandsFor().
+  // XXX Ideally, this should be array of Command rather than CommandInt.
+  //     However, ParamTraits isn't aware of enum array.
+  nsTArray<CommandInt> mEditCommandsForSingleLineEditor;
+  nsTArray<CommandInt> mEditCommandsForMultiLineEditor;
+  nsTArray<CommandInt> mEditCommandsForRichTextEditor;
+
+  nsTArray<CommandInt>& EditCommandsRef(nsIWidget::NativeKeyBindingsType aType)
+  {
+    switch (aType) {
+      case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+        return mEditCommandsForSingleLineEditor;
+      case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+        return mEditCommandsForMultiLineEditor;
+      case nsIWidget::NativeKeyBindingsForRichTextEditor:
+        return mEditCommandsForRichTextEditor;
+      default:
+        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(
+          "Invalid native key binding type");
+    }
+  }
+
+  // mEditCommandsFor*EditorInitialized are set to true when
+  // InitEditCommandsFor() initializes edit commands for the type.
+  bool mEditCommandsForSingleLineEditorInitialized;
+  bool mEditCommandsForMultiLineEditorInitialized;
+  bool mEditCommandsForRichTextEditorInitialized;
+
+  bool& IsEditCommandsInitializedRef(nsIWidget::NativeKeyBindingsType aType)
+  {
+    switch (aType) {
+      case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+        return mEditCommandsForSingleLineEditorInitialized;
+      case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+        return mEditCommandsForMultiLineEditorInitialized;
+      case nsIWidget::NativeKeyBindingsForRichTextEditor:
+        return mEditCommandsForRichTextEditorInitialized;
+      default:
+        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(
+          "Invalid native key binding type");
+    }
+  }
+
+  void InitEditCommandsFor(nsIWidget::NativeKeyBindingsType aType);
 };
 
 /******************************************************************************
  * mozilla::WidgetCompositionEvent
  ******************************************************************************/
 
 class WidgetCompositionEvent : public WidgetGUIEvent
 {
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -598,16 +598,67 @@ const char16_t* const WidgetKeyboardEven
 };
 #undef NS_DEFINE_PHYSICAL_KEY_CODE_NAME
 
 WidgetKeyboardEvent::KeyNameIndexHashtable*
   WidgetKeyboardEvent::sKeyNameIndexHashtable = nullptr;
 WidgetKeyboardEvent::CodeNameIndexHashtable*
   WidgetKeyboardEvent::sCodeNameIndexHashtable = nullptr;
 
+static void
+DoCommandCallback(Command aCommand, void* aData)
+{
+  static_cast<nsTArray<CommandInt>*>(aData)->AppendElement(aCommand);
+}
+
+void
+WidgetKeyboardEvent::InitEditCommandsFor(nsIWidget::NativeKeyBindingsType aType)
+{
+  MOZ_ASSERT(mWidget);
+  MOZ_RELEASE_ASSERT(IsTrusted());
+
+  bool& initialized = IsEditCommandsInitializedRef(aType);
+  if (initialized) {
+    return;
+  }
+  nsTArray<CommandInt>& commands = EditCommandsRef(aType);
+  mWidget->ExecuteNativeKeyBinding(aType, *this, DoCommandCallback, &commands);
+  initialized = true;
+}
+
+bool
+WidgetKeyboardEvent::ExecuteEditCommands(nsIWidget::NativeKeyBindingsType aType,
+                                         nsIWidget::DoCommandCallback aCallback,
+                                         void* aCallbackData)
+{
+  // If the event was created without widget, e.g., created event in chrome
+  // script, this shouldn't execute native key bindings.
+  if (NS_WARN_IF(!mWidget)) {
+    return false;
+  }
+
+  // This event should be trusted event here and we shouldn't expose native
+  // key binding information to web contents with untrusted events.
+  if (NS_WARN_IF(!IsTrusted())) {
+    return false;
+  }
+
+  InitEditCommandsFor(aType);
+
+  const nsTArray<CommandInt>& commands = EditCommandsRef(aType);
+  if (commands.IsEmpty()) {
+    return false;
+  }
+
+  for (CommandInt command : commands) {
+    aCallback(static_cast<Command>(command), aCallbackData);
+  }
+  return true;
+}
+
 bool
 WidgetKeyboardEvent::ShouldCauseKeypressEvents() const
 {
   // Currently, we don't dispatch keypress events of modifier keys and
   // dead keys.
   switch (mKeyNameIndex) {
     case KEY_NAME_INDEX_Alt:
     case KEY_NAME_INDEX_AltGraph: