Bug 1409580 - Support edit command mapping in headless MacOS. r=masayuki
authorBrendan Dahl <brendan.dahl@gmail.com>
Fri, 13 Oct 2017 17:40:27 -0700
changeset 392338 264ccb66a86c18f1f08285499cc82100660c835f
parent 392337 30022d08636eb217866c1cd7f0d0ce591bdca980
child 392339 2bd51020fa81ad8fd79c196e1f9ba47ebbcd6b26
push id32920
push usernerli@mozilla.com
push dateFri, 17 Nov 2017 22:01:05 +0000
treeherdermozilla-central@7ef46e350289 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki
bugs1409580, 756984, 1094000
milestone59.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 1409580 - Support edit command mapping in headless MacOS. r=masayuki Extracts out the creation of an NSEvent from a WidgetKeyEvent in TextInputHandler.mm into generic helper method. The helper is used by headless to create a fake NSEvent to then build edit commands from key events. Fixes: - test_selectevents.html - test_bug756984.html - test_movement_by_characters.html - test_movement_by_words.html - test_backspace_vs.html - test_bug1094000.html - ... many key event tests MozReview-Commit-ID: 1Jur5MHOrkp
widget/TextEvents.h
widget/cocoa/TextInputHandler.mm
widget/cocoa/nsCocoaUtils.h
widget/cocoa/nsCocoaUtils.mm
widget/headless/HeadlessKeyBindings.cpp
widget/headless/HeadlessKeyBindings.h
widget/headless/HeadlessKeyBindingsCocoa.mm
widget/headless/HeadlessWidget.cpp
widget/headless/HeadlessWidget.h
widget/headless/moz.build
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -7,27 +7,27 @@
 #define mozilla_TextEvents_h__
 
 #include <stdint.h>
 
 #include "mozilla/Assertions.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/EventForwards.h" // for KeyNameIndex, temporarily
+#include "mozilla/FontRange.h"
 #include "mozilla/TextRange.h"
-#include "mozilla/FontRange.h"
+#include "mozilla/WritingModes.h"
 #include "nsCOMPtr.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsISelectionController.h"
 #include "nsISelectionListener.h"
 #include "nsITransferable.h"
 #include "nsRect.h"
 #include "nsStringGlue.h"
 #include "nsTArray.h"
-#include "WritingModes.h"
 
 class nsStringHashKey;
 template<class, class> class nsDataHashtable;
 
 /******************************************************************************
  * virtual keycode values
  ******************************************************************************/
 
--- a/widget/cocoa/TextInputHandler.mm
+++ b/widget/cocoa/TextInputHandler.mm
@@ -4424,64 +4424,22 @@ TextInputHandlerBase::AttachNativeKeyEve
     return NS_OK;
   }
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, "
      "mod=0x%X", this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode,
      aKeyEvent.mModifiers));
 
-  NSEventType eventType;
-  if (aKeyEvent.mMessage == eKeyUp) {
-    eventType = NSKeyUp;
-  } else {
-    eventType = NSKeyDown;
-  }
-
-  static const uint32_t sModifierFlagMap[][2] = {
-    { MODIFIER_SHIFT,    NSShiftKeyMask },
-    { MODIFIER_CONTROL,  NSControlKeyMask },
-    { MODIFIER_ALT,      NSAlternateKeyMask },
-    { MODIFIER_ALTGRAPH, NSAlternateKeyMask },
-    { MODIFIER_META,     NSCommandKeyMask },
-    { MODIFIER_CAPSLOCK, NSAlphaShiftKeyMask },
-    { MODIFIER_NUMLOCK,  NSNumericPadKeyMask }
-  };
-
-  NSUInteger modifierFlags = 0;
-  for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
-    if (aKeyEvent.mModifiers & sModifierFlagMap[i][0]) {
-      modifierFlags |= sModifierFlagMap[i][1];
-    }
-  }
-
   NSInteger windowNumber = [[mView window] windowNumber];
-
-  NSString* characters;
-  if (aKeyEvent.mCharCode) {
-    characters = [NSString stringWithCharacters:
-      reinterpret_cast<const unichar*>(&(aKeyEvent.mCharCode)) length:1];
-  } else {
-    uint32_t cocoaCharCode =
-      nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.mKeyCode);
-    characters = [NSString stringWithCharacters:
-      reinterpret_cast<const unichar*>(&cocoaCharCode) length:1];
-  }
-
+  NSGraphicsContext* context = [NSGraphicsContext currentContext];
   aKeyEvent.mNativeKeyEvent =
-    [NSEvent     keyEventWithType:eventType
-                         location:NSMakePoint(0,0)
-                    modifierFlags:modifierFlags
-                        timestamp:0
-                     windowNumber:windowNumber
-                          context:[NSGraphicsContext currentContext]
-                       characters:characters
-      charactersIgnoringModifiers:characters
-                        isARepeat:NO
-                          keyCode:0]; // Native key code not currently needed
+    nsCocoaUtils::MakeNewCococaEventFromWidgetEvent(aKeyEvent,
+                                                    windowNumber,
+                                                    context);
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 bool
 TextInputHandlerBase::SetSelection(NSRange& aRange)
--- a/widget/cocoa/nsCocoaUtils.h
+++ b/widget/cocoa/nsCocoaUtils.h
@@ -323,16 +323,24 @@ public:
 
   /**
    * Makes NSEvent instance for aEventTytpe and aEvent.
    */
   static NSEvent* MakeNewCocoaEventWithType(NSEventType aEventType,
                                             NSEvent *aEvent);
 
   /**
+   * Makes a cocoa event from a widget keyboard event.
+   */
+  static NSEvent* MakeNewCococaEventFromWidgetEvent(
+                    const mozilla::WidgetKeyboardEvent& aKeyEvent,
+                    NSInteger aWindowNumber,
+                    NSGraphicsContext* aContext);
+
+  /**
    * Initializes aNPCocoaEvent.
    */
   static void InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent);
 
   /**
    * Initializes WidgetInputEvent for aNativeEvent or aModifiers.
    */
   static void InitInputEvent(mozilla::WidgetInputEvent &aInputEvent,
--- a/widget/cocoa/nsCocoaUtils.mm
+++ b/widget/cocoa/nsCocoaUtils.mm
@@ -593,16 +593,75 @@ nsCocoaUtils::MakeNewCocoaEventWithType(
                         isARepeat:[aEvent isARepeat]
                           keyCode:[aEvent keyCode]];
   return newEvent;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 // static
+NSEvent*
+nsCocoaUtils::MakeNewCococaEventFromWidgetEvent(
+                const WidgetKeyboardEvent& aKeyEvent,
+                NSInteger aWindowNumber,
+                NSGraphicsContext* aContext)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+  NSEventType eventType;
+  if (aKeyEvent.mMessage == eKeyUp) {
+    eventType = NSKeyUp;
+  } else {
+    eventType = NSKeyDown;
+  }
+
+  static const uint32_t sModifierFlagMap[][2] = {
+    { MODIFIER_SHIFT,    NSShiftKeyMask },
+    { MODIFIER_CONTROL,  NSControlKeyMask },
+    { MODIFIER_ALT,      NSAlternateKeyMask },
+    { MODIFIER_ALTGRAPH, NSAlternateKeyMask },
+    { MODIFIER_META,     NSCommandKeyMask },
+    { MODIFIER_CAPSLOCK, NSAlphaShiftKeyMask },
+    { MODIFIER_NUMLOCK,  NSNumericPadKeyMask }
+  };
+
+  NSUInteger modifierFlags = 0;
+  for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
+    if (aKeyEvent.mModifiers & sModifierFlagMap[i][0]) {
+      modifierFlags |= sModifierFlagMap[i][1];
+    }
+  }
+
+  NSString* characters;
+  if (aKeyEvent.mCharCode) {
+    characters = [NSString stringWithCharacters:
+      reinterpret_cast<const unichar*>(&(aKeyEvent.mCharCode)) length:1];
+  } else {
+    uint32_t cocoaCharCode =
+      nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.mKeyCode);
+    characters = [NSString stringWithCharacters:
+      reinterpret_cast<const unichar*>(&cocoaCharCode) length:1];
+  }
+
+  return
+    [NSEvent     keyEventWithType:eventType
+                         location:NSMakePoint(0,0)
+                    modifierFlags:modifierFlags
+                        timestamp:0
+                     windowNumber:aWindowNumber
+                          context:aContext
+                       characters:characters
+      charactersIgnoringModifiers:characters
+                        isARepeat:NO
+                          keyCode:0]; // Native key code not currently needed
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// static
 void
 nsCocoaUtils::InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent)
 {
   memset(aNPCocoaEvent, 0, sizeof(NPCocoaEvent));
 }
 
 // static
 void
new file mode 100644
--- /dev/null
+++ b/widget/headless/HeadlessKeyBindings.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HeadlessKeyBindings.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+namespace widget {
+
+HeadlessKeyBindings&
+HeadlessKeyBindings::GetInstance()
+{
+  static UniquePtr<HeadlessKeyBindings> sInstance;
+  if (!sInstance) {
+    sInstance.reset(new HeadlessKeyBindings());
+    ClearOnShutdown(&sInstance);
+  }
+  return *sInstance;
+}
+
+nsresult
+HeadlessKeyBindings::AttachNativeKeyEvent(WidgetKeyboardEvent& aEvent)
+{
+  // Stub for non-mac platforms.
+  return NS_OK;
+}
+
+void
+HeadlessKeyBindings::GetEditCommands(nsIWidget::NativeKeyBindingsType aType,
+                                     const WidgetKeyboardEvent& aEvent,
+                                     nsTArray<CommandInt>& aCommands)
+{
+  // Stub for non-mac platforms.
+}
+
+} // namespace widget
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/headless/HeadlessKeyBindings.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_HeadlessKeyBindings_h
+#define mozilla_widget_HeadlessKeyBindings_h
+
+#include "mozilla/TextEvents.h"
+#include "nsIWidget.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * Helper to emulate native key bindings. Currently only MacOS is supported.
+ */
+
+class HeadlessKeyBindings final
+{
+public:
+  HeadlessKeyBindings() = default;
+
+  static HeadlessKeyBindings& GetInstance();
+
+  void GetEditCommands(nsIWidget::NativeKeyBindingsType aType,
+                       const WidgetKeyboardEvent& aEvent,
+                       nsTArray<CommandInt>& aCommands);
+  MOZ_MUST_USE nsresult AttachNativeKeyEvent(WidgetKeyboardEvent& aEvent);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_HeadlessKeyBindings_h
new file mode 100644
--- /dev/null
+++ b/widget/headless/HeadlessKeyBindingsCocoa.mm
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HeadlessKeyBindings.h"
+#import <Cocoa/Cocoa.h>
+#include "nsCocoaUtils.h"
+#include "NativeKeyBindings.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+namespace widget {
+
+HeadlessKeyBindings&
+HeadlessKeyBindings::GetInstance()
+{
+  static UniquePtr<HeadlessKeyBindings> sInstance;
+  if (!sInstance) {
+    sInstance.reset(new HeadlessKeyBindings());
+    ClearOnShutdown(&sInstance);
+  }
+  return *sInstance;
+}
+
+nsresult
+HeadlessKeyBindings::AttachNativeKeyEvent(WidgetKeyboardEvent& aEvent)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  aEvent.mNativeKeyEvent =
+    nsCocoaUtils::MakeNewCococaEventFromWidgetEvent(aEvent, 0, nil);
+
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+HeadlessKeyBindings::GetEditCommands(nsIWidget::NativeKeyBindingsType aType,
+                                     const WidgetKeyboardEvent& aEvent,
+                                     nsTArray<CommandInt>& aCommands)
+{
+  // Convert the widget keyboard into a cocoa event so it can be translated
+  // into commands in the NativeKeyBindings.
+  WidgetKeyboardEvent modifiedEvent(aEvent);
+  modifiedEvent.mNativeKeyEvent =
+    nsCocoaUtils::MakeNewCococaEventFromWidgetEvent(aEvent, 0, nil);
+
+  NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+  keyBindings->GetEditCommands(modifiedEvent, aCommands);
+}
+
+
+} // namespace widget
+} // namespace mozilla
--- a/widget/headless/HeadlessWidget.cpp
+++ b/widget/headless/HeadlessWidget.cpp
@@ -5,16 +5,18 @@
 #include "HeadlessWidget.h"
 #include "HeadlessCompositorWidget.h"
 #include "Layers.h"
 #include "BasicLayers.h"
 #include "BasicEvents.h"
 #include "MouseEvents.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/TextEvents.h"
+#include "HeadlessKeyBindings.h"
 
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
 using mozilla::LogLevel;
 
 #ifdef MOZ_LOGGING
 
@@ -420,16 +422,35 @@ HeadlessWidget::MakeFullScreen(bool aFul
     mWidgetListener->SizeModeChanged(mSizeMode);
     mWidgetListener->FullscreenChanged(aFullScreen);
   }
 
   return NS_OK;
 }
 
 nsresult
+HeadlessWidget::AttachNativeKeyEvent(WidgetKeyboardEvent& aEvent)
+{
+  HeadlessKeyBindings& bindings = HeadlessKeyBindings::GetInstance();
+  return bindings.AttachNativeKeyEvent(aEvent);
+}
+
+void
+HeadlessWidget::GetEditCommands(NativeKeyBindingsType aType,
+                                const WidgetKeyboardEvent& aEvent,
+                                nsTArray<CommandInt>& aCommands)
+{
+  // Validate the arguments.
+  nsIWidget::GetEditCommands(aType, aEvent, aCommands);
+
+  HeadlessKeyBindings& bindings = HeadlessKeyBindings::GetInstance();
+  bindings.GetEditCommands(aType, aEvent, aCommands);
+}
+
+nsresult
 HeadlessWidget::DispatchEvent(WidgetGUIEvent* aEvent, nsEventStatus& aStatus)
 {
 #ifdef DEBUG
   debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "HeadlessWidget", 0);
 #endif
 
   aStatus = nsEventStatus_eIgnore;
 
--- a/widget/headless/HeadlessWidget.h
+++ b/widget/headless/HeadlessWidget.h
@@ -113,16 +113,22 @@ public:
 
   virtual LayerManager*
   GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
                   LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
                   LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
 
   void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override;
 
+  virtual MOZ_MUST_USE nsresult AttachNativeKeyEvent(WidgetKeyboardEvent& aEvent) override;
+  virtual void GetEditCommands(
+                 NativeKeyBindingsType aType,
+                 const WidgetKeyboardEvent& aEvent,
+                 nsTArray<CommandInt>& aCommands) override;
+
   virtual nsresult DispatchEvent(WidgetGUIEvent* aEvent,
                                  nsEventStatus& aStatus) override;
 
   virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
                                               uint32_t aNativeMessage,
                                               uint32_t aModifierFlags,
                                               nsIObserver* aObserver) override;
   virtual nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
--- a/widget/headless/moz.build
+++ b/widget/headless/moz.build
@@ -33,11 +33,20 @@ UNIFIED_SOURCES += [
 ]
 
 if widget_dir == 'gtk':
     UNIFIED_SOURCES += [
         'HeadlessLookAndFeelGTK.cpp',
         'HeadlessThemeGTK.cpp',
     ]
 
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+    UNIFIED_SOURCES += [
+        'HeadlessKeyBindingsCocoa.mm',
+    ]
+else:
+    UNIFIED_SOURCES += [
+        'HeadlessKeyBindings.cpp',
+    ]
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'