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
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -36,16 +36,17 @@
#include "nsIEditor.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/TextEditRules.h"
#include "mozilla/EventListenerManager.h"
#include "nsContentUtils.h"
#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"
#include "nsNumberControlFrame.h"
#include "nsFrameSelection.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Telemetry.h"
#include "mozilla/layers/ScrollInputMethods.h"
@@ -987,25 +988,32 @@ nsTextInputListener::HandleEvent(nsIDOME
if (keyEvent->mMessage != eKeyPress) {
return NS_OK;
}
nsIWidget::NativeKeyBindingsType nativeKeyBindingsType =
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;
}
// BEGIN nsIEditorObserver
NS_IMETHODIMP
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -622,20 +622,26 @@ EditorEventListener::KeyPress(WidgetKeyb
if (!widget) {
nsCOMPtr<nsIPresShell> ps = GetPresShell();
nsPresContext* pc = ps ? ps->GetPresContext() : nullptr;
widget = pc ? pc->GetNearestWidget() : nullptr;
NS_ENSURE_TRUE(widget, NS_OK);
}
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;
}
nsresult
EditorEventListener::MouseClick(nsIDOMMouseEvent* aMouseEvent)
{
--- a/widget/EventForwards.h
+++ b/widget/EventForwards.h
@@ -158,16 +158,17 @@ typedef AutoTArray<ShortcutKeyCandidate,
// TextRange.h
typedef uint8_t RawTextRangeType;
enum class TextRangeType : RawTextRangeType;
struct TextRangeStyle;
struct TextRange;
+class EditCommands;
class TextRangeArray;
// FontRange.h
struct FontRange;
} // namespace mozilla
#endif // mozilla_EventForwards_h__
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -554,19 +554,20 @@ PuppetWidget::AsyncPanZoomEnabled() cons
bool
PuppetWidget::ExecuteNativeKeyBinding(
NativeKeyBindingsType aType,
const mozilla::WidgetKeyboardEvent& aEvent,
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;
}
mTabChild->RequestNativeKeyBindings(&autoCache, &aEvent);
}
MOZ_ASSERT(mNativeKeyCommandsValid);
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -125,16 +125,19 @@ protected:
#endif // #ifdef XP_MACOSX
, mKeyNameIndex(KEY_NAME_INDEX_Unidentified)
, mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN)
, mInputMethodAppState(eNotHandled)
, mIsRepeat(false)
, mIsComposing(false)
, mIsReserved(false)
, mIsSynthesizedByTIP(false)
+ , mEditCommandsForSingleLineEditorInitialized(false)
+ , mEditCommandsForMultiLineEditorInitialized(false)
+ , mEditCommandsForRichTextEditorInitialized(false)
{
}
public:
virtual WidgetKeyboardEvent* AsKeyboardEvent() override { return this; }
WidgetKeyboardEvent(bool aIsTrusted, EventMessage aMessage,
nsIWidget* aWidget,
@@ -153,16 +156,19 @@ public:
#endif // #ifdef XP_MACOSX
, mKeyNameIndex(KEY_NAME_INDEX_Unidentified)
, mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN)
, mInputMethodAppState(eNotHandled)
, mIsRepeat(false)
, 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 =
mFlags.mNoCrossProcessBoundaryForwarding = IsKeyEventOnPlugin();
}
static bool IsKeyDownOrKeyDownOnPlugin(EventMessage aMessage)
{
@@ -192,16 +198,19 @@ public:
virtual WidgetEvent* Duplicate() const override
{
MOZ_ASSERT(mClass == eKeyboardEventClass,
"Duplicate() must be overridden by sub class");
// Not copying widget, it is a weak reference.
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;
}
// OS translated Unicode chars which are used for accesskey and accelkey
// handling. The handlers will try from first character to last character.
nsTArray<AlternativeCharCode> mAlternativeCharCodes;
// DOM KeyboardEvent.key only when mKeyNameIndex is KEY_NAME_INDEX_USE_STRING.
@@ -273,16 +282,39 @@ public:
bool mIsComposing;
// Indicates if the key combination is reserved by chrome. This is set by
// nsXBLWindowKeyHandler at capturing phase of the default event group.
bool mIsReserved;
// Indicates whether the event is synthesized from Text Input Processor
// 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;
// mCharCode value of non-eKeyPress events is always 0. However, if
// non-eKeyPress event has one or more alternative char code values,
// its first item should be the mCharCode value of following eKeyPress event.
// PseudoCharCode() returns mCharCode value for eKeyPress event,
@@ -403,27 +435,84 @@ public:
mNativeModifierFlags = aEvent.mNativeModifierFlags;
mNativeCharacters.Assign(aEvent.mNativeCharacters);
mNativeCharactersIgnoringModifiers.
Assign(aEvent.mNativeCharactersIgnoringModifiers);
mPluginTextEventString.Assign(aEvent.mPluginTextEventString);
#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:
static const char16_t* const kKeyNames[];
static const char16_t* const kCodeNames[];
typedef nsDataHashtable<nsStringHashKey,
KeyNameIndex> KeyNameIndexHashtable;
typedef nsDataHashtable<nsStringHashKey,
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);
};
/******************************************************************************
* mozilla::WidgetCompositionEvent
******************************************************************************/
class WidgetCompositionEvent : public WidgetGUIEvent
{
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -598,16 +598,67 @@ const char16_t* const WidgetKeyboardEven
};
#undef NS_DEFINE_PHYSICAL_KEY_CODE_NAME
WidgetKeyboardEvent::KeyNameIndexHashtable*
WidgetKeyboardEvent::sKeyNameIndexHashtable = nullptr;
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
{
// Currently, we don't dispatch keypress events of modifier keys and
// dead keys.
switch (mKeyNameIndex) {
case KEY_NAME_INDEX_Alt:
case KEY_NAME_INDEX_AltGraph: