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

--HG--
extra : rebase_source : b7ecd704d9c331ca1e0aedc66f230114015b853b
This commit is contained in:
Masayuki Nakano 2017-05-19 16:50:30 +09:00
Родитель 1a9dd92127
Коммит 6d8d004613
6 изменённых файлов: 165 добавлений и 9 удалений

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

@ -41,6 +41,7 @@
#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"
@ -992,15 +993,22 @@ nsTextInputListener::HandleEvent(nsIDOMEvent* aEvent)
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;

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

@ -627,10 +627,16 @@ EditorEventListener::KeyPress(WidgetKeyboardEvent* aKeyboardEvent)
}
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;

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

@ -163,6 +163,7 @@ enum class TextRangeType : RawTextRangeType;
struct TextRangeStyle;
struct TextRange;
class EditCommands;
class TextRangeArray;
// FontRange.h

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

@ -559,9 +559,10 @@ PuppetWidget::ExecuteNativeKeyBinding(
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;

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

@ -130,6 +130,9 @@ protected:
, mIsComposing(false)
, mIsReserved(false)
, mIsSynthesizedByTIP(false)
, mEditCommandsForSingleLineEditorInitialized(false)
, mEditCommandsForMultiLineEditorInitialized(false)
, mEditCommandsForRichTextEditorInitialized(false)
{
}
@ -158,6 +161,9 @@ public:
, 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 =
@ -197,6 +203,9 @@ public:
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;
}
@ -278,6 +287,29 @@ public:
// 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;
@ -408,6 +440,17 @@ public:
#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:
@ -419,6 +462,52 @@ private:
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);
};
/******************************************************************************

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

@ -603,6 +603,57 @@ WidgetKeyboardEvent::KeyNameIndexHashtable*
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
{