Skip to content

Commit

Permalink
Bug 429824: Properly forward native OSX events to the native menu bar…
Browse files Browse the repository at this point in the history
… if they haven't been handled by the child process in e10s. r=mstange,masayuki
  • Loading branch information
Stephen A Pohl committed May 16, 2017
1 parent 340eaff commit 99336e0
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 37 deletions.
12 changes: 12 additions & 0 deletions dom/events/EventStateManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2840,6 +2840,18 @@ EventStateManager::PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
return;
}

if (!dispatchedToContentProcess) {
// The widget expects a reply for every keyboard event. If the event wasn't
// dispatched to a content process (non-e10s or no content process
// running), we need to short-circuit here. Otherwise, we need to wait for
// the content process to handle the event.
aKeyboardEvent->mWidget->PostHandleKeyEvent(aKeyboardEvent);
if (aKeyboardEvent->DefaultPrevented()) {
aStatus = nsEventStatus_eConsumeNoDefault;
return;
}
}

// XXX Currently, our automated tests don't support mKeyNameIndex.
// Therefore, we still need to handle this with keyCode.
switch(aKeyboardEvent->mKeyCode) {
Expand Down
1 change: 1 addition & 0 deletions dom/ipc/TabChild.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,7 @@ TabChild::RecvRealKeyEvent(const WidgetKeyboardEvent& aEvent,

WidgetKeyboardEvent localEvent(aEvent);
localEvent.mWidget = mPuppetWidget;
localEvent.mUniqueId = aEvent.mUniqueId;
nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);

// Update the end time of the possible repeated event so that we can skip
Expand Down
10 changes: 10 additions & 0 deletions dom/ipc/TabParent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2002,6 +2002,16 @@ TabParent::RecvReplyKeyEvent(const WidgetKeyboardEvent& aEvent)
&localEvent, doc);

EventDispatcher::Dispatch(mFrameElement, presContext, &localEvent);

if (!localEvent.DefaultPrevented() &&
!localEvent.mFlags.mIsSynthesizedForTests) {
nsCOMPtr<nsIWidget> widget = GetWidget();
if (widget) {
widget->PostHandleKeyEvent(&localEvent);
localEvent.StopPropagation();
}
}

return IPC_OK();
}

Expand Down
4 changes: 1 addition & 3 deletions widget/TextEvents.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,7 @@ class WidgetKeyboardEvent : public WidgetInputEvent
// handling should be delayed until it is determined that there exists no
// overriding access key in the content process.
bool mAccessKeyForwardedToChild;
// Unique id associated with a keydown / keypress event. Used in identifing
// keypress events for removal from async event dispatch queue in metrofx
// after preventDefault is called on keydown events. It's ok if this wraps
// Unique id associated with a keydown / keypress event. It's ok if this wraps
// over long periods.
uint32_t mUniqueId;

Expand Down
32 changes: 21 additions & 11 deletions widget/cocoa/TextInputHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,9 @@ class TextInputHandlerBase : public TextEventDispatcherListener
// String which are included in [mKeyEvent characters] and already handled
// by InsertText() call(s).
nsString mInsertedString;
// Unique id associated with a keydown / keypress event. It's ok if this
// wraps over long periods.
uint32_t mUniqueId;
// Whether keydown event was consumed by web contents or chrome contents.
bool mKeyDownHandled;
// Whether keypress event was dispatched for mKeyEvent.
Expand All @@ -542,15 +545,19 @@ class TextInputHandlerBase : public TextEventDispatcherListener
// if it dispatches keypress event.
bool mCompositionDispatched;

KeyEventState() : mKeyEvent(nullptr)
KeyEventState()
: mKeyEvent(nullptr)
, mUniqueId(0)
{
Clear();
}
}

explicit KeyEventState(NSEvent* aNativeKeyEvent) : mKeyEvent(nullptr)
explicit KeyEventState(NSEvent* aNativeKeyEvent, uint32_t aUniqueId = 0)
: mKeyEvent(nullptr)
, mUniqueId(0)
{
Clear();
Set(aNativeKeyEvent);
Set(aNativeKeyEvent, aUniqueId);
}

KeyEventState(const KeyEventState &aOther) = delete;
Expand All @@ -560,18 +567,20 @@ class TextInputHandlerBase : public TextEventDispatcherListener
Clear();
}

void Set(NSEvent* aNativeKeyEvent)
void Set(NSEvent* aNativeKeyEvent, uint32_t aUniqueId = 0)
{
NS_PRECONDITION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
Clear();
mKeyEvent = [aNativeKeyEvent retain];
mUniqueId = aUniqueId;
}

void Clear()
{
if (mKeyEvent) {
[mKeyEvent release];
mKeyEvent = nullptr;
mUniqueId = 0;
}
mInsertString = nullptr;
mInsertedString.Truncate();
Expand Down Expand Up @@ -670,7 +679,7 @@ class TextInputHandlerBase : public TextEventDispatcherListener
/**
* PushKeyEvent() adds the current key event to mCurrentKeyEvents.
*/
KeyEventState* PushKeyEvent(NSEvent* aNativeKeyEvent)
KeyEventState* PushKeyEvent(NSEvent* aNativeKeyEvent, uint32_t aUniqueId = 0)
{
uint32_t nestCount = mCurrentKeyEvents.Length();
for (uint32_t i = 0; i < nestCount; i++) {
Expand All @@ -681,10 +690,10 @@ class TextInputHandlerBase : public TextEventDispatcherListener

KeyEventState* keyEvent = nullptr;
if (nestCount == 0) {
mFirstKeyEvent.Set(aNativeKeyEvent);
mFirstKeyEvent.Set(aNativeKeyEvent, aUniqueId);
keyEvent = &mFirstKeyEvent;
} else {
keyEvent = new KeyEventState(aNativeKeyEvent);
keyEvent = new KeyEventState(aNativeKeyEvent, aUniqueId);
}
return *mCurrentKeyEvents.AppendElement(keyEvent);
}
Expand Down Expand Up @@ -1111,10 +1120,11 @@ class TextInputHandler : public IMEInputHandler
* KeyDown event handler.
*
* @param aNativeEvent A native NSKeyDown event.
* @return TRUE if the event is consumed by web contents
* or chrome contents. Otherwise, FALSE.
* @param aUniqueId A unique ID for the event.
* @return TRUE if the event is dispatched to web
* contents or chrome contents. Otherwise, FALSE.
*/
bool HandleKeyDownEvent(NSEvent* aNativeEvent);
bool HandleKeyDownEvent(NSEvent* aNativeEvent, uint32_t aUniqueId);

/**
* KeyUp event handler.
Expand Down
12 changes: 10 additions & 2 deletions widget/cocoa/TextInputHandler.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1566,7 +1566,8 @@
}

bool
TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent)
TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent,
uint32_t aUniqueId)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;

Expand Down Expand Up @@ -1601,7 +1602,7 @@

RefPtr<nsChildView> widget(mWidget);

KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent);
KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent, aUniqueId);
AutoKeyEventStateCleaner remover(this);

ComplexTextInputPanel* ctiPanel = ComplexTextInputPanel::GetSharedComplexTextInputPanel();
Expand Down Expand Up @@ -2807,6 +2808,12 @@ WidgetContentCommandEvent deleteCommandEvent(true, eContentCommandDelete,
KeyEventState* currentKeyEvent = static_cast<KeyEventState*>(aData);
NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
nsAString* insertString = currentKeyEvent->mInsertString;
if (aKeyboardEvent.mMessage == eKeyPress && aIndexOfKeypress == 0 &&
(!insertString || insertString->IsEmpty())) {
// Inform the child process that this is an event that we want a reply
// from.
aKeyboardEvent.mFlags.mWantReplyFromContentProcess = true;
}
if (KeyboardLayoutOverrideRef().mOverrideEnabled) {
TISInputSourceWrapper tis;
tis.InitByLayoutID(KeyboardLayoutOverrideRef().mKeyboardLayout, true);
Expand Down Expand Up @@ -4706,6 +4713,7 @@ WidgetContentCommandEvent deleteCommandEvent(true, eContentCommandDelete,
keyCode:[mKeyEvent keyCode]];
}

aKeyEvent.mUniqueId = mUniqueId;
aHandler->InitKeyEvent(nativeEvent, aKeyEvent, mInsertString);

NS_OBJC_END_TRY_ABORT_BLOCK;
Expand Down
6 changes: 6 additions & 0 deletions widget/cocoa/nsChildView.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ class WidgetRenderingContext;
- (void)setUsingOMTCompositor:(BOOL)aUseOMTC;

- (NSEvent*)lastKeyDownEvent;

+ (uint32_t)sUniqueKeyEventId;

+ (NSMutableDictionary*)sNativeKeyEventsMap;
@end

class ChildViewMouseTracker {
Expand Down Expand Up @@ -375,6 +379,8 @@ class nsChildView : public nsBaseWidget

virtual bool HasPendingInputEvent() override;

bool SendEventToNativeMenuSystem(NSEvent* aEvent);
virtual void PostHandleKeyEvent(mozilla::WidgetKeyboardEvent* aEvent) override;
virtual nsresult ActivateNativeMenuItemAt(const nsAString& indexString) override;
virtual nsresult ForceUpdateNativeMenuAt(const nsAString& indexString) override;
virtual MOZ_MUST_USE nsresult
Expand Down
87 changes: 66 additions & 21 deletions widget/cocoa/nsChildView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@

static bool sIsTabletPointerActivated = false;

static uint32_t sUniqueKeyEventId = 0;

static NSMutableDictionary* sNativeKeyEventsMap =
[NSMutableDictionary dictionary];

@interface ChildView(Private)

// sets up our view, attaching it to its owning gecko view
Expand Down Expand Up @@ -1249,6 +1254,50 @@ static void PrintViewHierarchy(NSView *view)
return nil;
}

bool
nsChildView::SendEventToNativeMenuSystem(NSEvent* aEvent)
{
bool handled = false;
nsCocoaWindow* widget = GetXULWindowWidget();
if (widget) {
nsMenuBarX* mb = widget->GetMenuBar();
if (mb) {
// Check if main menu wants to handle the event.
handled = mb->PerformKeyEquivalent(aEvent);
}
}

if (!handled && sApplicationMenu) {
// Check if application menu wants to handle the event.
handled = [sApplicationMenu performKeyEquivalent:aEvent];
}

return handled;
}

void
nsChildView::PostHandleKeyEvent(mozilla::WidgetKeyboardEvent* aEvent)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

// We always allow keyboard events to propagate to keyDown: but if they are
// not handled we give menu items a chance to act. This allows for handling of
// custom shortcuts. Note that existing shortcuts cannot be reassigned yet and
// will have been handled by keyDown: before we get here.
NSEvent* cocoaEvent =
[sNativeKeyEventsMap objectForKey:@(aEvent->mUniqueId)];
[sNativeKeyEventsMap removeObjectForKey:@(aEvent->mUniqueId)];
if (!cocoaEvent) {
return;
}

if (SendEventToNativeMenuSystem(cocoaEvent)) {
aEvent->PreventDefault();
}

NS_OBJC_END_TRY_ABORT_BLOCK;
}

// Used for testing native menu system structure and event handling.
nsresult
nsChildView::ActivateNativeMenuItemAt(const nsAString& indexString)
Expand Down Expand Up @@ -5549,29 +5598,21 @@ - (void)keyDown:(NSEvent*)theEvent
#endif // #if !defined(RELEASE_OR_BETA) || defined(DEBUG)

nsAutoRetainCocoaObject kungFuDeathGrip(self);
bool handled = false;
if (mGeckoChild && mTextInputHandler) {
handled = mTextInputHandler->HandleKeyDownEvent(theEvent);
}

// We always allow keyboard events to propagate to keyDown: but if they are
// not handled we give menu items a chance to act. This allows for handling of
// custom shortcuts. Note that existing shortcuts cannot be reassigned yet and
// will have been handled by keyDown: before we get here.
if (!handled && mGeckoChild) {
nsCocoaWindow* widget = mGeckoChild->GetXULWindowWidget();
if (widget) {
nsMenuBarX* mb = widget->GetMenuBar();
if (mb) {
// Check if main menu wants to handle the event.
handled = mb->PerformKeyEquivalent(theEvent);
}
if (mGeckoChild) {
if (mTextInputHandler) {
sUniqueKeyEventId++;
[sNativeKeyEventsMap setObject:theEvent forKey:@(sUniqueKeyEventId)];
// Purge old native events, in case we're still holding on to them. We
// keep at most 10 references to 10 different native events.
[sNativeKeyEventsMap removeObjectForKey:@(sUniqueKeyEventId - 10)];
mTextInputHandler->HandleKeyDownEvent(theEvent, sUniqueKeyEventId);
} else {
// There was no text input handler. Offer the event to the native menu
// system to check if there are any registered custom shortcuts for this
// event.
mGeckoChild->SendEventToNativeMenuSystem(theEvent);
}
}
if (!handled && sApplicationMenu) {
// Check if application menu wants to handle the event.
handled = [sApplicationMenu performKeyEquivalent:theEvent];
}

NS_OBJC_END_TRY_ABORT_BLOCK;
}
Expand Down Expand Up @@ -6499,6 +6540,10 @@ - (id)accessibilityAttributeValue:(NSString*)attribute

#endif /* ACCESSIBILITY */

+ (uint32_t)sUniqueKeyEventId { return sUniqueKeyEventId; }

+ (NSMutableDictionary*)sNativeKeyEventsMap { return sNativeKeyEventsMap; }

@end

#pragma mark -
Expand Down
5 changes: 5 additions & 0 deletions widget/nsBaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2283,6 +2283,11 @@ nsIWidget::OnWindowedPluginKeyEvent(const NativeEventData& aKeyEventData,
return NS_ERROR_NOT_IMPLEMENTED;
}

void
nsIWidget::PostHandleKeyEvent(mozilla::WidgetKeyboardEvent* aEvent)
{
}

namespace mozilla {
namespace widget {

Expand Down
8 changes: 8 additions & 0 deletions widget/nsIWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -1687,6 +1687,14 @@ class nsIWidget : public nsISupports
static int32_t sPointerIdCounter;

public:
/**
* If key events have not been handled by content or XBL handlers, they can
* be offered to the system (for custom application shortcuts set in system
* preferences, for example).
*/
virtual void
PostHandleKeyEvent(mozilla::WidgetKeyboardEvent* aEvent);

/**
* Activates a native menu item at the position specified by the index
* string. The index string is a string of positive integers separated
Expand Down

0 comments on commit 99336e0

Please sign in to comment.