Bug 1409580 - Support edit command mapping in headless MacOS. r?masayuki draft
authorBrendan Dahl <brendan.dahl@gmail.com>
Fri, 13 Oct 2017 17:40:27 -0700
changeset 699278 eb49b85332619844a9074f56afb8538579b88f51
parent 699096 a3f183201f7f183c263d554bfb15fbf0b0ed2ea4
child 699279 0feccba425b13d5f9cc94d0bd0717dd696ad95a1
push id89521
push userbmo:bdahl@mozilla.com
push dateThu, 16 Nov 2017 23:49:56 +0000
reviewersmasayuki
bugs1409580, 756984, 1094000
milestone59.0a1
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'