Bug 1685491 - part.1: Map typical commands to synthesized keyboard events for test on Linux and macOS r=smaug,remote-protocol-reviewers

Currently, we don't allow keyboard events synthesized for tests retrieve native
key bindings in content process.  However, due to this, we cannot test keyboard
navigation, deleting per word, etc on Linux and macOS either with mochitest
or WPT.  For making better compatibility with the other browsers, we should
write WPT more with the test driver.  Therefore, we should allow keyboard
events synthesized for tests retrieve native key bindings.

On the other hand, if we make them retrieve customized keyboard shortcuts
in the environment, some developers may not be able to run tests locally without
resetting their customization.  Therefore, this patch makes `NativeKeyBindings`
set "standard" shortcut keys on the platform instead of retrieving actual
shortcut key results.

If referring the default shortcut key bindings is not good thing for
WebDriver/CDP, perhaps, `TextInputProcessor` should have a new flag which can
refer customized shortcut keys even in content process.  But I think that it
should be done in another bug because some edit commands are mapped forcibly
like this patch.
https://searchfox.org/mozilla-central/rev/c03e8de87cdb0ce0378c0886d3c0ce8bbf9dc44e/remote/domains/parent/Input.jsm#82-102

Differential Revision: https://phabricator.services.mozilla.com/D102877
This commit is contained in:
Masayuki Nakano 2021-02-02 03:02:30 +00:00
Родитель 26aa295c1e
Коммит cb448c5095
13 изменённых файлов: 633 добавлений и 118 удалений

Просмотреть файл

@ -985,35 +985,47 @@ nsresult TextInputProcessor::PrepareKeyboardEventToDispatch(
aKeyboardEvent.mKeyNameIndex);
}
aKeyboardEvent.mIsSynthesizedByTIP = !mForTests;
aKeyboardEvent.mIsSynthesizedByTIP = true;
aKeyboardEvent.mFlags.mIsSynthesizedForTests = mForTests;
return NS_OK;
}
nsresult TextInputProcessor::InitEditCommands(
WidgetKeyboardEvent& aKeyboardEvent) const {
MOZ_ASSERT(XRE_IsContentProcess());
MOZ_ASSERT(aKeyboardEvent.mMessage == eKeyPress);
// When this emulates real input only in content process, we need to
// initialize edit commands with the main process's widget via PuppetWidget
// because they are initialized by BrowserParent before content process treats
// them.
if (aKeyboardEvent.mIsSynthesizedByTIP && !XRE_IsParentProcess()) {
// Note that retrieving edit commands from content process is expensive.
// Let's skip it when the keyboard event is inputting text.
if (!aKeyboardEvent.IsInputtingText()) {
// FYI: WidgetKeyboardEvent::InitAllEditCommands() isn't available here
// since it checks whether it's called in the main process to
// avoid performance issues so that we need to initialize each
// command manually here.
if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
nsIWidget::NativeKeyBindingsForSingleLineEditor))) {
return NS_ERROR_NOT_AVAILABLE;
}
if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
nsIWidget::NativeKeyBindingsForMultiLineEditor))) {
return NS_ERROR_NOT_AVAILABLE;
}
if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
nsIWidget::NativeKeyBindingsForRichTextEditor))) {
return NS_ERROR_NOT_AVAILABLE;
}
} else {
aKeyboardEvent.PreventNativeKeyBindings();
}
// And also when this synthesizes keyboard events for tests, we need default
// shortcut keys on the platform for making any developers get constant
// results in any environments.
// Note that retrieving edit commands via PuppetWidget is expensive.
// Let's skip it when the keyboard event is inputting text.
if (aKeyboardEvent.IsInputtingText()) {
aKeyboardEvent.PreventNativeKeyBindings();
return NS_OK;
}
// FYI: WidgetKeyboardEvent::InitAllEditCommands() isn't available here
// since it checks whether it's called in the main process to
// avoid performance issues so that we need to initialize each
// command manually here.
if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
nsIWidget::NativeKeyBindingsForSingleLineEditor))) {
return NS_ERROR_NOT_AVAILABLE;
}
if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
nsIWidget::NativeKeyBindingsForMultiLineEditor))) {
return NS_ERROR_NOT_AVAILABLE;
}
if (NS_WARN_IF(!aKeyboardEvent.InitEditCommandsFor(
nsIWidget::NativeKeyBindingsForRichTextEditor))) {
return NS_ERROR_NOT_AVAILABLE;
}
return NS_OK;
@ -1053,6 +1065,8 @@ nsresult TextInputProcessor::KeydownInternal(
// We shouldn't modify the internal WidgetKeyboardEvent.
WidgetKeyboardEvent keyEvent(aKeyboardEvent);
keyEvent.mFlags.mIsTrusted = true;
keyEvent.mMessage = eKeyDown;
nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
@ -1104,8 +1118,26 @@ nsresult TextInputProcessor::KeydownInternal(
? KEYDOWN_IS_CONSUMED
: KEYEVENT_NOT_CONSUMED;
if (aAllowToDispatchKeypress &&
kungFuDeathGrip->MaybeDispatchKeypressEvents(keyEvent, status)) {
if (!aAllowToDispatchKeypress) {
return NS_OK;
}
keyEvent.mMessage = eKeyPress;
// Only `eKeyPress` events, editor wants to execute system default edit
// commands mapped to the key combination. In e10s world, edit commands can
// be retrieved only in the parent process due to the performance reason.
// Therefore, BrowserParent initializes edit commands for all cases before
// sending the event to focused content process. For emulating this, we
// need to do it now for synthesizing `eKeyPress` events if and only if
// we're dispatching the events in a content process.
if (XRE_IsContentProcess()) {
nsresult rv = InitEditCommands(keyEvent);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
if (kungFuDeathGrip->MaybeDispatchKeypressEvents(keyEvent, status)) {
aConsumedFlags |= (status == nsEventStatus_eConsumeNoDefault)
? KEYPRESS_IS_CONSUMED
: KEYEVENT_NOT_CONSUMED;
@ -1147,6 +1179,8 @@ nsresult TextInputProcessor::KeyupInternal(
// We shouldn't modify the internal WidgetKeyboardEvent.
WidgetKeyboardEvent keyEvent(aKeyboardEvent);
keyEvent.mFlags.mIsTrusted = true;
keyEvent.mMessage = eKeyUp;
nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;

Просмотреть файл

@ -141,6 +141,13 @@ class TextInputProcessor final : public nsITextInputProcessor,
void UnlinkFromTextEventDispatcher();
nsresult PrepareKeyboardEventToDispatch(WidgetKeyboardEvent& aKeyboardEvent,
uint32_t aKeyFlags);
/**
* InitEditCommands() initializes edit commands of aKeyboardEvent.
* This must be called only in a content process, and aKeyboardEvent must
* be used only for `eKeyPress` event.
*/
nsresult InitEditCommands(WidgetKeyboardEvent& aKeyboardEvent) const;
bool IsValidEventTypeForComposition(
const WidgetKeyboardEvent& aKeyboardEvent) const;
nsresult PrepareKeyboardEventForComposition(

Просмотреть файл

@ -1850,6 +1850,9 @@ void BrowserParent::SendRealKeyEvent(WidgetKeyboardEvent& aEvent) {
}
aEvent.mRefPoint = TransformParentToChild(aEvent.mRefPoint);
// NOTE: If you call `InitAllEditCommands()` for the other messages too,
// you also need to update
// TextEventDispatcher::DispatchKeyboardEventInternal().
if (aEvent.mMessage == eKeyPress) {
// XXX Should we do this only when input context indicates an editor having
// focus and the key event won't cause inputting text?

Просмотреть файл

@ -60,12 +60,4 @@ class Input extends ContentProcessDomain {
await eventPromise;
this._eventPromises.delete(eventId);
}
/**
* Expose docShell.doCommand to parent domain.
* Used in temporary workaround for emulating certain native key bindings
*/
_doDocShellCommand(command) {
this.docShell.doCommand(command);
}
}

Просмотреть файл

@ -79,29 +79,6 @@ class Input extends Domain {
EventUtils.synthesizeKey(eventUtilsKey, eventInfo, browserWindow);
}
// Temporary workaround to handle certain native key bindings than cannot
// be synthesized with EventUtils: dispatch editor command directly
if (domType == "keydown") {
switch (Services.appinfo.OS) {
case "Linux":
if (modifiers == ctrl && key == "Backspace") {
await this.executeInChild(
"_doDocShellCommand",
"cmd_deleteWordBackward"
);
}
break;
case "Darwin":
if (modifiers == meta && key == "Backspace") {
await this.executeInChild(
"_doDocShellCommand",
"cmd_deleteToBeginningOfLine"
);
}
}
}
// TODO in case of workaround for native key bindings: wait for input event?
await this.executeInChild("_waitForContentEvent", eventId);
}

Просмотреть файл

@ -520,6 +520,7 @@ bool PuppetWidget::AsyncPanZoomEnabled() const {
bool PuppetWidget::GetEditCommands(NativeKeyBindingsType aType,
const WidgetKeyboardEvent& aEvent,
nsTArray<CommandInt>& aCommands) {
MOZ_ASSERT(!aEvent.mFlags.mIsSynthesizedForTests);
// Validate the arguments.
if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
return false;

Просмотреть файл

@ -536,10 +536,28 @@ bool TextEventDispatcher::DispatchKeyboardEventInternal(
keyEvent.AssignKeyEventData(aKeyboardEvent, false);
// Command arrays are not duplicated by AssignKeyEventData() due to
// both performance and footprint reasons. So, when TextInputProcessor
// emulates real text input, the arrays may be initialized all commands
// already. If so, we need to duplicate the arrays here.
if (keyEvent.mIsSynthesizedByTIP) {
keyEvent.AssignCommands(aKeyboardEvent);
// emulates real text input or synthesizing keyboard events for tests,
// the arrays may be initialized all commands already. If so, we need to
// duplicate the arrays here, but we should do this only when we're
// dispatching eKeyPress events because BrowserParent::SendRealKeyEvent()
// does this only for eKeyPress event. Note that this is not required if
// we're in the main process because in the parent process, the edit commands
// will be initialized by `ExecuteEditCommands()` (when the event is handled
// by editor event listener) or `InitAllEditCommands()` (when the event is
// set to a content process). We should test whether these pathes work or
// not too.
if (XRE_IsContentProcess() && keyEvent.mIsSynthesizedByTIP) {
if (aMessage == eKeyPress) {
keyEvent.AssignCommands(aKeyboardEvent);
} else {
// Prevent retriving native edit commands if we're in a content process
// because only `eKeyPress` events coming from the main process have
// edit commands (See `BrowserParent::SendRealKeyEvent`). And also
// retriving edit commands from a content process requires synchonous
// IPC and that makes running tests slower. Therefore, we should mark
// the `eKeyPress` event does not need to retrieve edit commands anymore.
keyEvent.PreventNativeKeyBindings();
}
}
if (aStatus == nsEventStatus_eConsumeNoDefault) {

Просмотреть файл

@ -455,7 +455,6 @@ class WidgetKeyboardEvent : public WidgetInputEvent {
aType);
}
#ifdef DEBUG
/**
* AreAllEditCommandsInitialized() returns true if edit commands for all
* types were already initialized. Otherwise, false.
@ -465,7 +464,6 @@ class WidgetKeyboardEvent : public WidgetInputEvent {
mEditCommandsForMultiLineEditorInitialized &&
mEditCommandsForRichTextEditorInitialized;
}
#endif // #ifdef DEBUG
/**
* Execute edit commands for aType.

Просмотреть файл

@ -24,7 +24,11 @@
#if defined(XP_WIN)
# include "npapi.h"
# include "WinUtils.h"
#endif
#endif // #if defined (XP_WIN)
#if defined(MOZ_WIDGET_GTK) || defined(XP_MACOSX)
# include "NativeKeyBindings.h"
#endif // #if defined(MOZ_WIDGET_GTK) || defined(XP_MACOSX)
namespace mozilla {
@ -754,23 +758,28 @@ WidgetKeyboardEvent::CodeNameIndexHashtable*
WidgetKeyboardEvent::sCodeNameIndexHashtable = nullptr;
void WidgetKeyboardEvent::InitAllEditCommands() {
// 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;
}
// If this event is synthesized for tests, we don't need to retrieve the
// command via the main process. So, we don't need widget and can trust
// the event.
if (!mFlags.mIsSynthesizedForTests) {
// 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;
}
// 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;
}
// 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;
}
MOZ_ASSERT(
XRE_IsParentProcess(),
"It's too expensive to retrieve all edit commands from remote process");
MOZ_ASSERT(!AreAllEditCommandsInitialized(),
"Shouldn't be called two or more times");
MOZ_ASSERT(
XRE_IsParentProcess(),
"It's too expensive to retrieve all edit commands from remote process");
MOZ_ASSERT(!AreAllEditCommandsInitialized(),
"Shouldn't be called two or more times");
}
DebugOnly<bool> okIgnored =
InitEditCommandsFor(nsIWidget::NativeKeyBindingsForSingleLineEditor);
@ -794,15 +803,30 @@ void WidgetKeyboardEvent::InitAllEditCommands() {
bool WidgetKeyboardEvent::InitEditCommandsFor(
nsIWidget::NativeKeyBindingsType aType) {
if (NS_WARN_IF(!mWidget) || NS_WARN_IF(!IsTrusted())) {
return false;
}
bool& initialized = IsEditCommandsInitializedRef(aType);
if (initialized) {
return true;
}
nsTArray<CommandInt>& commands = EditCommandsRef(aType);
// If this event is synthesized for tests, we shouldn't access customized
// shortcut settings of the environment. Therefore, we don't need to check
// whether `widget` is set or not. And we can treat synthesized events are
// always trusted.
if (mFlags.mIsSynthesizedForTests) {
MOZ_DIAGNOSTIC_ASSERT(IsTrusted());
#if defined(MOZ_WIDGET_GTK) || defined(XP_MACOSX)
// TODO: We should implement `NativeKeyBindings` for Windows and Android
// too in bug 1301497 for getting rid of the #if.
widget::NativeKeyBindings::GetEditCommandsForTests(aType, *this, commands);
#endif
initialized = true;
return true;
}
if (NS_WARN_IF(!mWidget) || NS_WARN_IF(!IsTrusted())) {
return false;
}
initialized = mWidget->GetEditCommands(aType, *this, commands);
return initialized;
}

Просмотреть файл

@ -3,19 +3,20 @@
* 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_NativeKeyBindings_h_
#define mozilla_widget_NativeKeyBindings_h_
#ifndef NativeKeyBindings_h
#define NativeKeyBindings_h
#import <Cocoa/Cocoa.h>
#include "mozilla/Attributes.h"
#include "mozilla/EventForwards.h"
#include "nsDataHashtable.h"
#include "nsIWidget.h"
struct objc_selector;
namespace mozilla {
namespace widget {
typedef nsDataHashtable<nsPtrHashKey<struct objc_selector>, Command>
typedef nsDataHashtable<nsPtrHashKey<objc_selector>, Command>
SelectorCommandHashtable;
class NativeKeyBindings final {
@ -25,6 +26,15 @@ class NativeKeyBindings final {
static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType);
static void Shutdown();
/**
* GetEditCommandsForTests() returns commands performed in native widget
* in typical environment. I.e., this does NOT refer customized shortcut
* key mappings of the environment.
*/
static void GetEditCommandsForTests(NativeKeyBindingsType aType,
const WidgetKeyboardEvent& aEvent,
nsTArray<CommandInt>& aCommands);
void Init(NativeKeyBindingsType aType);
void GetEditCommands(const WidgetKeyboardEvent& aEvent,
@ -33,6 +43,12 @@ class NativeKeyBindings final {
private:
NativeKeyBindings();
void AppendEditCommandsForSelector(objc_selector* aSelector,
nsTArray<CommandInt>& aCommands) const;
void LogEditCommands(const nsTArray<CommandInt>& aCommands,
const char* aDescription) const;
SelectorCommandHashtable mSelectorToCommand;
static NativeKeyBindings* sInstanceForSingleLineEditor;
@ -42,4 +58,4 @@ class NativeKeyBindings final {
} // namespace widget
} // namespace mozilla
#endif // mozilla_widget_NativeKeyBindings_h_
#endif // NativeKeyBindings_h

Просмотреть файл

@ -10,6 +10,8 @@
#include "mozilla/Logging.h"
#include "mozilla/TextEvents.h"
#import <Cocoa/Cocoa.h>
namespace mozilla {
namespace widget {
@ -50,8 +52,9 @@ void NativeKeyBindings::Shutdown() {
NativeKeyBindings::NativeKeyBindings() {}
inline objc_selector* ToObjcSelectorPtr(SEL aSel) { return reinterpret_cast<objc_selector*>(aSel); }
#define SEL_TO_COMMAND(aSel, aCommand) \
mSelectorToCommand.Put(reinterpret_cast<struct objc_selector*>(@selector(aSel)), aCommand)
mSelectorToCommand.Put(ToObjcSelectorPtr(@selector(aSel)), aCommand)
void NativeKeyBindings::Init(NativeKeyBindingsType aType) {
MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::Init", this));
@ -180,6 +183,7 @@ void NativeKeyBindings::Init(NativeKeyBindingsType aType) {
void NativeKeyBindings::GetEditCommands(const WidgetKeyboardEvent& aEvent,
nsTArray<CommandInt>& aCommands) {
MOZ_ASSERT(!aEvent.mFlags.mIsSynthesizedForTests);
MOZ_ASSERT(aCommands.IsEmpty());
MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p NativeKeyBindings::GetEditCommands", this));
@ -219,43 +223,338 @@ void NativeKeyBindings::GetEditCommands(const WidgetKeyboardEvent& aEvent,
NS_LossyConvertUTF16toASCII(nsSelectorString).get()));
}
// Try to find a simple mapping in the hashtable
Command geckoCommand = Command::DoNothing;
if (mSelectorToCommand.Get(reinterpret_cast<struct objc_selector*>(selector), &geckoCommand) &&
geckoCommand != Command::DoNothing) {
aCommands.AppendElement(static_cast<CommandInt>(geckoCommand));
} else if (selector == @selector(selectLine:)) {
// This is functional, but Cocoa's version is direction-less in that
// selection direction is not determined until some future directed action
// is taken. See bug 282097, comment 79 for more details.
aCommands.AppendElement(static_cast<CommandInt>(Command::BeginLine));
aCommands.AppendElement(static_cast<CommandInt>(Command::SelectEndLine));
} else if (selector == @selector(selectWord:)) {
// This is functional, but Cocoa's version is direction-less in that
// selection direction is not determined until some future directed action
// is taken. See bug 282097, comment 79 for more details.
aCommands.AppendElement(static_cast<CommandInt>(Command::WordPrevious));
aCommands.AppendElement(static_cast<CommandInt>(Command::SelectWordNext));
}
AppendEditCommandsForSelector(ToObjcSelectorPtr(selector), aCommands);
}
LogEditCommands(aCommands, "NativeKeyBindings::GetEditCommands");
}
void NativeKeyBindings::AppendEditCommandsForSelector(objc_selector* aSelector,
nsTArray<CommandInt>& aCommands) const {
// Try to find a simple mapping in the hashtable
Command geckoCommand = Command::DoNothing;
if (mSelectorToCommand.Get(aSelector, &geckoCommand) && geckoCommand != Command::DoNothing) {
aCommands.AppendElement(static_cast<CommandInt>(geckoCommand));
} else if (aSelector == ToObjcSelectorPtr(@selector(selectLine:))) {
// This is functional, but Cocoa's version is direction-less in that
// selection direction is not determined until some future directed action
// is taken. See bug 282097, comment 79 for more details.
aCommands.AppendElement(static_cast<CommandInt>(Command::BeginLine));
aCommands.AppendElement(static_cast<CommandInt>(Command::SelectEndLine));
} else if (aSelector == ToObjcSelectorPtr(@selector(selectWord:))) {
// This is functional, but Cocoa's version is direction-less in that
// selection direction is not determined until some future directed action
// is taken. See bug 282097, comment 79 for more details.
aCommands.AppendElement(static_cast<CommandInt>(Command::WordPrevious));
aCommands.AppendElement(static_cast<CommandInt>(Command::SelectWordNext));
}
}
void NativeKeyBindings::LogEditCommands(const nsTArray<CommandInt>& aCommands,
const char* aDescription) const {
if (!MOZ_LOG_TEST(gNativeKeyBindingsLog, LogLevel::Info)) {
return;
}
if (aCommands.IsEmpty()) {
MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
("%p NativeKeyBindings::GetEditCommands, handled=false", this));
MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info, ("%p %s, no edit commands", this, aDescription));
return;
}
for (CommandInt commandInt : aCommands) {
Command geckoCommand = static_cast<Command>(commandInt);
MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
("%p NativeKeyBindings::GetEditCommands, command=%s", this,
("%p %s, command=%s", this, aDescription,
WidgetKeyboardEvent::GetCommandStr(geckoCommand)));
}
}
// static
void NativeKeyBindings::GetEditCommandsForTests(NativeKeyBindingsType aType,
const WidgetKeyboardEvent& aEvent,
nsTArray<CommandInt>& aCommands) {
MOZ_DIAGNOSTIC_ASSERT(aEvent.IsTrusted());
// The following mapping is checked on Big Sur. Some of them are defined in:
// https://support.apple.com/en-us/HT201236#text
NativeKeyBindings* instance = NativeKeyBindings::GetInstance(aType);
if (NS_WARN_IF(!instance)) {
return;
}
switch (aEvent.mKeyNameIndex) {
case KEY_NAME_INDEX_USE_STRING:
if (!aEvent.IsControl() || aEvent.IsAlt() || aEvent.IsMeta()) {
break;
}
switch (aEvent.PseudoCharCode()) {
case 'a':
case 'A':
instance->AppendEditCommandsForSelector(
!aEvent.IsShift()
? ToObjcSelectorPtr(@selector(moveToBeginningOfParagraph:))
: ToObjcSelectorPtr(@selector(moveToBeginningOfParagraphAndModifySelection:)),
aCommands);
break;
case 'b':
case 'B':
instance->AppendEditCommandsForSelector(
!aEvent.IsShift() ? ToObjcSelectorPtr(@selector(moveBackward:))
: ToObjcSelectorPtr(@selector(moveBackwardAndModifySelection:)),
aCommands);
break;
case 'd':
case 'D':
if (!aEvent.IsShift()) {
instance->AppendEditCommandsForSelector(ToObjcSelectorPtr(@selector(deleteForward:)),
aCommands);
}
break;
case 'e':
case 'E':
instance->AppendEditCommandsForSelector(
!aEvent.IsShift()
? ToObjcSelectorPtr(@selector(moveToEndOfParagraph:))
: ToObjcSelectorPtr(@selector(moveToEndOfParagraphAndModifySelection:)),
aCommands);
break;
case 'f':
case 'F':
instance->AppendEditCommandsForSelector(
!aEvent.IsShift() ? ToObjcSelectorPtr(@selector(moveForward:))
: ToObjcSelectorPtr(@selector(moveForwardAndModifySelection:)),
aCommands);
break;
case 'h':
case 'H':
if (!aEvent.IsShift()) {
instance->AppendEditCommandsForSelector(ToObjcSelectorPtr(@selector(deleteBackward:)),
aCommands);
}
break;
case 'k':
case 'K':
if (!aEvent.IsShift()) {
instance->AppendEditCommandsForSelector(
ToObjcSelectorPtr(@selector(deleteToEndOfParagraph:)), aCommands);
}
break;
case 'n':
case 'N':
instance->AppendEditCommandsForSelector(
!aEvent.IsShift() ? ToObjcSelectorPtr(@selector(moveDown:))
: ToObjcSelectorPtr(@selector(moveDownAndModifySelection:)),
aCommands);
break;
case 'p':
case 'P':
instance->AppendEditCommandsForSelector(
!aEvent.IsShift() ? ToObjcSelectorPtr(@selector(moveUp:))
: ToObjcSelectorPtr(@selector(moveUpAndModifySelection:)),
aCommands);
break;
default:
break;
}
break;
case KEY_NAME_INDEX_Backspace:
if (aEvent.IsMeta()) {
if (aEvent.IsAlt() || aEvent.IsControl()) {
break;
}
// Shift is ignored.
instance->AppendEditCommandsForSelector(
ToObjcSelectorPtr(@selector(deleteToBeginningOfLine:)), aCommands);
break;
}
if (aEvent.IsAlt()) {
// Shift and Control are ignored.
instance->AppendEditCommandsForSelector(ToObjcSelectorPtr(@selector(deleteWordBackward:)),
aCommands);
break;
}
if (aEvent.IsControl()) {
if (aEvent.IsShift()) {
instance->AppendEditCommandsForSelector(
ToObjcSelectorPtr(@selector(deleteBackwardByDecomposingPreviousCharacter:)),
aCommands);
}
break;
}
// Shift is ignored.
instance->AppendEditCommandsForSelector(ToObjcSelectorPtr(@selector(deleteBackward:)),
aCommands);
break;
case KEY_NAME_INDEX_Delete:
if (aEvent.IsControl() || aEvent.IsMeta()) {
break;
}
if (aEvent.IsAlt()) {
// Shift is ignored.
instance->AppendEditCommandsForSelector(ToObjcSelectorPtr(@selector(deleteWordForward:)),
aCommands);
break;
}
// Shift is ignored.
instance->AppendEditCommandsForSelector(ToObjcSelectorPtr(@selector(deleteForward:)),
aCommands);
break;
case KEY_NAME_INDEX_PageDown:
if (aEvent.IsControl() || aEvent.IsMeta()) {
break;
}
if (aEvent.IsAlt()) {
// Shift is ignored.
instance->AppendEditCommandsForSelector(ToObjcSelectorPtr(@selector(pageDown:)), aCommands);
break;
}
instance->AppendEditCommandsForSelector(
!aEvent.IsShift() ? ToObjcSelectorPtr(@selector(scrollPageDown:))
: ToObjcSelectorPtr(@selector(pageDownAndModifySelection:)),
aCommands);
break;
case KEY_NAME_INDEX_PageUp:
if (aEvent.IsControl() || aEvent.IsMeta()) {
break;
}
if (aEvent.IsAlt()) {
// Shift is ignored.
instance->AppendEditCommandsForSelector(ToObjcSelectorPtr(@selector(pageUp:)), aCommands);
break;
}
instance->AppendEditCommandsForSelector(
!aEvent.IsShift() ? ToObjcSelectorPtr(@selector(scrollPageUp:))
: ToObjcSelectorPtr(@selector(pageUpAndModifySelection:)),
aCommands);
break;
case KEY_NAME_INDEX_Home:
if (aEvent.IsAlt() || aEvent.IsControl() || aEvent.IsMeta()) {
break;
}
instance->AppendEditCommandsForSelector(
!aEvent.IsShift()
? ToObjcSelectorPtr(@selector(scrollToBeginningOfDocument:))
: ToObjcSelectorPtr(@selector(moveToBeginningOfDocumentAndModifySelection:)),
aCommands);
break;
case KEY_NAME_INDEX_End:
if (aEvent.IsAlt() || aEvent.IsControl() || aEvent.IsMeta()) {
break;
}
instance->AppendEditCommandsForSelector(
!aEvent.IsShift() ? ToObjcSelectorPtr(@selector(scrollToEndOfDocument:))
: ToObjcSelectorPtr(@selector(moveToEndOfDocumentAndModifySelection:)),
aCommands);
break;
case KEY_NAME_INDEX_ArrowLeft:
if (aEvent.IsAlt()) {
break;
}
if (aEvent.IsMeta() || (aEvent.IsControl() && aEvent.IsShift())) {
instance->AppendEditCommandsForSelector(
!aEvent.IsShift()
? ToObjcSelectorPtr(@selector(moveToLeftEndOfLine:))
: ToObjcSelectorPtr(@selector(moveToLeftEndOfLineAndModifySelection:)),
aCommands);
break;
}
if (aEvent.IsControl()) {
break;
}
instance->AppendEditCommandsForSelector(
!aEvent.IsShift() ? ToObjcSelectorPtr(@selector(moveLeft:))
: ToObjcSelectorPtr(@selector(moveLeftAndModifySelection:)),
aCommands);
break;
case KEY_NAME_INDEX_ArrowRight:
if (aEvent.IsAlt()) {
break;
}
if (aEvent.IsMeta() || (aEvent.IsControl() && aEvent.IsShift())) {
instance->AppendEditCommandsForSelector(
!aEvent.IsShift()
? ToObjcSelectorPtr(@selector(moveToRightEndOfLine:))
: ToObjcSelectorPtr(@selector(moveToRightEndOfLineAndModifySelection:)),
aCommands);
break;
}
if (aEvent.IsControl()) {
break;
}
instance->AppendEditCommandsForSelector(
!aEvent.IsShift() ? ToObjcSelectorPtr(@selector(moveRight:))
: ToObjcSelectorPtr(@selector(moveRightAndModifySelection:)),
aCommands);
break;
case KEY_NAME_INDEX_ArrowUp:
if (aEvent.IsControl()) {
break;
}
if (aEvent.IsMeta()) {
if (aEvent.IsAlt()) {
break;
}
instance->AppendEditCommandsForSelector(
!aEvent.IsShift()
? ToObjcSelectorPtr(@selector(moveToBeginningOfDocument:))
: ToObjcSelectorPtr(@selector(moveToBegginingOfDocumentAndModifySelection:)),
aCommands);
break;
}
if (aEvent.IsAlt()) {
if (!aEvent.IsShift()) {
instance->AppendEditCommandsForSelector(ToObjcSelectorPtr(@selector(moveBackward:)),
aCommands);
instance->AppendEditCommandsForSelector(
ToObjcSelectorPtr(@selector(moveToBeginningOfParagraph:)), aCommands);
break;
}
instance->AppendEditCommandsForSelector(
ToObjcSelectorPtr(@selector(moveParagraphBackwardAndModifySelection:)), aCommands);
break;
}
instance->AppendEditCommandsForSelector(
!aEvent.IsShift() ? ToObjcSelectorPtr(@selector(moveUp:))
: ToObjcSelectorPtr(@selector(moveUpAndModifySelection:)),
aCommands);
break;
case KEY_NAME_INDEX_ArrowDown:
if (aEvent.IsControl()) {
break;
}
if (aEvent.IsMeta()) {
if (aEvent.IsAlt()) {
break;
}
instance->AppendEditCommandsForSelector(
!aEvent.IsShift()
? ToObjcSelectorPtr(@selector(moveToEndOfDocument:))
: ToObjcSelectorPtr(@selector(moveToEndOfDocumentAndModifySelection:)),
aCommands);
break;
}
if (aEvent.IsAlt()) {
if (!aEvent.IsShift()) {
instance->AppendEditCommandsForSelector(ToObjcSelectorPtr(@selector(moveForward:)),
aCommands);
instance->AppendEditCommandsForSelector(
ToObjcSelectorPtr(@selector(moveToEndOfParagraph:)), aCommands);
break;
}
instance->AppendEditCommandsForSelector(
ToObjcSelectorPtr(@selector(moveParagraphForwardAndModifySelection:)), aCommands);
break;
}
instance->AppendEditCommandsForSelector(
!aEvent.IsShift() ? ToObjcSelectorPtr(@selector(moveDown:))
: ToObjcSelectorPtr(@selector(moveDownAndModifySelection:)),
aCommands);
break;
default:
break;
}
instance->LogEditCommands(aCommands, "NativeKeyBindings::GetEditCommandsForTests");
}
} // namespace widget
} // namespace mozilla

Просмотреть файл

@ -277,11 +277,11 @@ NativeKeyBindings::~NativeKeyBindings() {
void NativeKeyBindings::GetEditCommands(const WidgetKeyboardEvent& aEvent,
nsTArray<CommandInt>& aCommands) {
// If the native key event is set, it must be synthesized for tests.
// We just ignore such events because this behavior depends on system
// settings.
MOZ_ASSERT(!aEvent.mFlags.mIsSynthesizedForTests);
MOZ_ASSERT(aCommands.IsEmpty());
// It must be a DOM event dispached by chrome script.
if (!aEvent.mNativeKeyEvent) {
// It must be synthesized event or dispatched DOM event from chrome.
return;
}
@ -342,5 +342,139 @@ bool NativeKeyBindings::GetEditCommandsInternal(
return gHandled;
}
// static
void NativeKeyBindings::GetEditCommandsForTests(
NativeKeyBindingsType aType, const WidgetKeyboardEvent& aEvent,
nsTArray<CommandInt>& aCommands) {
MOZ_DIAGNOSTIC_ASSERT(aEvent.IsTrusted());
if (aEvent.IsAlt() || aEvent.IsMeta() || aEvent.IsOS()) {
return;
}
static const size_t kBackward = 0;
static const size_t kForward = 1;
const size_t extentSelection = aEvent.IsShift() ? 1 : 0;
// https://github.com/GNOME/gtk/blob/1f141c19533f4b3f397c3959ade673ce243b6138/gtk/gtktext.c#L1289
// https://github.com/GNOME/gtk/blob/c5dd34344f0c660ceffffb3bf9da43c263db16e1/gtk/gtktextview.c#L1534
Command command = Command::DoNothing;
switch (aEvent.mKeyNameIndex) {
case KEY_NAME_INDEX_USE_STRING:
switch (aEvent.PseudoCharCode()) {
case 'a':
case 'A':
if (aEvent.IsControl()) {
command = Command::SelectAll;
}
break;
case 'c':
case 'C':
if (aEvent.IsControl() && !aEvent.IsShift()) {
command = Command::Copy;
}
break;
case 'u':
case 'U':
if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor &&
aEvent.IsControl() && !aEvent.IsShift()) {
command = sDeleteCommands[GTK_DELETE_PARAGRAPH_ENDS][kBackward];
}
break;
case 'v':
case 'V':
if (aEvent.IsControl() && !aEvent.IsShift()) {
command = Command::Paste;
}
break;
case 'x':
case 'X':
if (aEvent.IsControl() && !aEvent.IsShift()) {
command = Command::Cut;
}
break;
case '/':
if (aEvent.IsControl() && !aEvent.IsShift()) {
command = Command::SelectAll;
}
break;
default:
break;
}
break;
case KEY_NAME_INDEX_Insert:
if (aEvent.IsControl() && !aEvent.IsShift()) {
command = Command::Copy;
} else if (aEvent.IsShift() && !aEvent.IsControl()) {
command = Command::Paste;
}
break;
case KEY_NAME_INDEX_Delete:
if (aEvent.IsShift()) {
command = Command::Cut;
break;
}
[[fallthrough]];
case KEY_NAME_INDEX_Backspace: {
const size_t direction =
aEvent.mKeyNameIndex == KEY_NAME_INDEX_Delete ? kForward : kBackward;
const GtkDeleteType amount =
aEvent.IsControl() && aEvent.IsShift()
? GTK_DELETE_PARAGRAPH_ENDS
// FYI: Shift key for Backspace is ignored to help mis-typing.
: (aEvent.IsControl() ? GTK_DELETE_WORD_ENDS : GTK_DELETE_CHARS);
command = sDeleteCommands[amount][direction];
break;
}
case KEY_NAME_INDEX_ArrowLeft:
case KEY_NAME_INDEX_ArrowRight: {
const size_t direction = aEvent.mKeyNameIndex == KEY_NAME_INDEX_ArrowRight
? kForward
: kBackward;
const GtkMovementStep amount = aEvent.IsControl()
? GTK_MOVEMENT_WORDS
: GTK_MOVEMENT_VISUAL_POSITIONS;
command = sMoveCommands[amount][extentSelection][direction];
break;
}
case KEY_NAME_INDEX_ArrowUp:
case KEY_NAME_INDEX_ArrowDown: {
const size_t direction = aEvent.mKeyNameIndex == KEY_NAME_INDEX_ArrowDown
? kForward
: kBackward;
const GtkMovementStep amount = aEvent.IsControl()
? GTK_MOVEMENT_PARAGRAPHS
: GTK_MOVEMENT_DISPLAY_LINES;
command = sMoveCommands[amount][extentSelection][direction];
break;
}
case KEY_NAME_INDEX_Home:
case KEY_NAME_INDEX_End: {
const size_t direction =
aEvent.mKeyNameIndex == KEY_NAME_INDEX_End ? kForward : kBackward;
const GtkMovementStep amount = aEvent.IsControl()
? GTK_MOVEMENT_BUFFER_ENDS
: GTK_MOVEMENT_DISPLAY_LINE_ENDS;
command = sMoveCommands[amount][extentSelection][direction];
break;
}
case KEY_NAME_INDEX_PageUp:
case KEY_NAME_INDEX_PageDown: {
const size_t direction = aEvent.mKeyNameIndex == KEY_NAME_INDEX_PageDown
? kForward
: kBackward;
const GtkMovementStep amount = aEvent.IsControl()
? GTK_MOVEMENT_HORIZONTAL_PAGES
: GTK_MOVEMENT_PAGES;
command = sMoveCommands[amount][extentSelection][direction];
break;
}
default:
break;
}
if (command != Command::DoNothing) {
aCommands.AppendElement(static_cast<CommandInt>(command));
}
}
} // namespace widget
} // namespace mozilla

Просмотреть файл

@ -3,14 +3,17 @@
* 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_NativeKeyBindings_h_
#define mozilla_widget_NativeKeyBindings_h_
#ifndef NativeKeyBindings_h
#define NativeKeyBindings_h
#include <gtk/gtk.h>
#include "mozilla/Attributes.h"
#include "mozilla/EventForwards.h"
#include "nsIWidget.h"
#include <glib.h> // for guint
using GtkWidget = struct _GtkWidget;
namespace mozilla {
namespace widget {
@ -21,6 +24,15 @@ class NativeKeyBindings final {
static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType);
static void Shutdown();
/**
* GetEditCommandsForTests() returns commands performed in native widget
* in typical environment. I.e., this does NOT refer customized shortcut
* key mappings of the environment.
*/
static void GetEditCommandsForTests(NativeKeyBindingsType aType,
const WidgetKeyboardEvent& aEvent,
nsTArray<CommandInt>& aCommands);
void Init(NativeKeyBindingsType aType);
void GetEditCommands(const WidgetKeyboardEvent& aEvent,
@ -41,4 +53,4 @@ class NativeKeyBindings final {
} // namespace widget
} // namespace mozilla
#endif // mozilla_widget_NativeKeyBindings_h_
#endif // NativeKeyBindings_h