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
--- 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'