1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsMenuBarListener.h"
8 #include "nsMenuBarFrame.h"
9 #include "nsMenuPopupFrame.h"
10 #include "nsPIWindowRoot.h"
12 // Drag & Drop, Clipboard
13 #include "nsIServiceManager.h"
14 #include "nsWidgetsCID.h"
16 #include "nsIContent.h"
18 #include "nsContentUtils.h"
19 #include "mozilla/BasicEvents.h"
20 #include "mozilla/Preferences.h"
21 #include "mozilla/TextEvents.h"
22 #include "mozilla/dom/Event.h"
23 #include "mozilla/dom/EventBinding.h"
24 #include "mozilla/dom/KeyboardEvent.h"
25 #include "mozilla/dom/KeyboardEventBinding.h"
27 using namespace mozilla
;
28 using mozilla::dom::Event
;
29 using mozilla::dom::KeyboardEvent
;
32 * nsMenuBarListener implementation
35 NS_IMPL_ISUPPORTS(nsMenuBarListener
, nsIDOMEventListener
)
37 ////////////////////////////////////////////////////////////////////////
39 int32_t nsMenuBarListener::mAccessKey
= -1;
40 Modifiers
nsMenuBarListener::mAccessKeyMask
= 0;
41 bool nsMenuBarListener::mAccessKeyFocuses
= false;
43 nsMenuBarListener::nsMenuBarListener(nsMenuBarFrame
* aMenuBarFrame
,
44 nsIContent
* aMenuBarContent
)
45 : mMenuBarFrame(aMenuBarFrame
),
46 mEventTarget(aMenuBarContent
? aMenuBarContent
->GetComposedDoc()
48 mTopWindowEventTarget(nullptr),
49 mAccessKeyDown(false),
50 mAccessKeyDownCanceled(false) {
51 MOZ_ASSERT(mEventTarget
);
53 // Hook up the menubar as a key listener on the whole document. This will
54 // see every keypress that occurs, but after everyone else does.
56 // Also hook up the listener to the window listening for focus events. This
57 // is so we can keep proper state as the user alt-tabs through processes.
59 mEventTarget
->AddSystemEventListener(NS_LITERAL_STRING("keypress"), this,
61 mEventTarget
->AddSystemEventListener(NS_LITERAL_STRING("keydown"), this,
63 mEventTarget
->AddSystemEventListener(NS_LITERAL_STRING("keyup"), this, false);
64 mEventTarget
->AddSystemEventListener(
65 NS_LITERAL_STRING("mozaccesskeynotfound"), this, false);
66 // Need a capturing event listener if the user has blocked pages from
67 // overriding system keys so that we can prevent menu accesskeys from being
69 mEventTarget
->AddEventListener(NS_LITERAL_STRING("keydown"), this, true);
71 // mousedown event should be handled in all phase
72 mEventTarget
->AddEventListener(NS_LITERAL_STRING("mousedown"), this, true);
73 mEventTarget
->AddEventListener(NS_LITERAL_STRING("mousedown"), this, false);
74 mEventTarget
->AddEventListener(NS_LITERAL_STRING("blur"), this, true);
76 mEventTarget
->AddEventListener(NS_LITERAL_STRING("MozDOMFullscreen:Entered"),
79 // Needs to listen to the deactivate event of the window.
80 RefPtr
<dom::EventTarget
> topWindowEventTarget
=
81 nsContentUtils::GetWindowRoot(aMenuBarContent
->GetComposedDoc());
82 mTopWindowEventTarget
= topWindowEventTarget
.get();
84 mTopWindowEventTarget
->AddSystemEventListener(NS_LITERAL_STRING("deactivate"),
88 ////////////////////////////////////////////////////////////////////////
89 nsMenuBarListener::~nsMenuBarListener() {
90 MOZ_ASSERT(!mEventTarget
,
91 "OnDestroyMenuBarFrame() should've alreay been called");
94 void nsMenuBarListener::OnDestroyMenuBarFrame() {
95 mEventTarget
->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"), this,
97 mEventTarget
->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), this,
99 mEventTarget
->RemoveSystemEventListener(NS_LITERAL_STRING("keyup"), this,
101 mEventTarget
->RemoveSystemEventListener(
102 NS_LITERAL_STRING("mozaccesskeynotfound"), this, false);
103 mEventTarget
->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
105 mEventTarget
->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true);
106 mEventTarget
->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this,
108 mEventTarget
->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true);
110 mEventTarget
->RemoveEventListener(
111 NS_LITERAL_STRING("MozDOMFullscreen:Entered"), this, false);
113 mTopWindowEventTarget
->RemoveSystemEventListener(
114 NS_LITERAL_STRING("deactivate"), this, true);
116 mMenuBarFrame
= nullptr;
117 mEventTarget
= nullptr;
118 mTopWindowEventTarget
= nullptr;
121 void nsMenuBarListener::InitializeStatics() {
122 Preferences::AddBoolVarCache(&mAccessKeyFocuses
,
123 "ui.key.menuAccessKeyFocuses");
126 nsresult
nsMenuBarListener::GetMenuAccessKey(int32_t* aAccessKey
) {
127 if (!aAccessKey
) return NS_ERROR_INVALID_POINTER
;
129 *aAccessKey
= mAccessKey
;
133 void nsMenuBarListener::InitAccessKey() {
134 if (mAccessKey
>= 0) return;
136 // Compiled-in defaults, in case we can't get LookAndFeel --
137 // mac doesn't have menu shortcuts, other platforms use alt.
142 mAccessKey
= dom::KeyboardEvent_Binding::DOM_VK_ALT
;
143 mAccessKeyMask
= MODIFIER_ALT
;
146 // Get the menu access key value from prefs, overriding the default:
147 mAccessKey
= Preferences::GetInt("ui.key.menuAccessKey", mAccessKey
);
148 switch (mAccessKey
) {
149 case dom::KeyboardEvent_Binding::DOM_VK_SHIFT
:
150 mAccessKeyMask
= MODIFIER_SHIFT
;
152 case dom::KeyboardEvent_Binding::DOM_VK_CONTROL
:
153 mAccessKeyMask
= MODIFIER_CONTROL
;
155 case dom::KeyboardEvent_Binding::DOM_VK_ALT
:
156 mAccessKeyMask
= MODIFIER_ALT
;
158 case dom::KeyboardEvent_Binding::DOM_VK_META
:
159 mAccessKeyMask
= MODIFIER_META
;
161 case dom::KeyboardEvent_Binding::DOM_VK_WIN
:
162 mAccessKeyMask
= MODIFIER_OS
;
165 // Don't touch mAccessKeyMask.
170 void nsMenuBarListener::ToggleMenuActiveState() {
171 nsMenuFrame
* closemenu
= mMenuBarFrame
->ToggleMenuActiveState();
172 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
173 if (pm
&& closemenu
) {
174 nsMenuPopupFrame
* popupFrame
= closemenu
->GetPopup();
176 pm
->HidePopup(popupFrame
->GetContent(), false, false, true, false);
180 ////////////////////////////////////////////////////////////////////////
181 nsresult
nsMenuBarListener::KeyUp(Event
* aKeyEvent
) {
182 RefPtr
<KeyboardEvent
> keyEvent
= aKeyEvent
->AsKeyboardEvent();
189 // handlers shouldn't be triggered by non-trusted events.
190 if (!keyEvent
->IsTrusted()) {
194 if (mAccessKey
&& mAccessKeyFocuses
) {
195 bool defaultPrevented
= keyEvent
->DefaultPrevented();
197 // On a press of the ALT key by itself, we toggle the menu's
198 // active/inactive state.
199 // Get the ascii key code.
200 uint32_t theChar
= keyEvent
->KeyCode();
202 if (!defaultPrevented
&& mAccessKeyDown
&& !mAccessKeyDownCanceled
&&
203 (int32_t)theChar
== mAccessKey
) {
204 // The access key was down and is now up, and no other
205 // keys were pressed in between.
206 bool toggleMenuActiveState
= true;
207 if (!mMenuBarFrame
->IsActive()) {
208 // First, close all existing popups because other popups shouldn't
209 // handle key events when menubar is active and IME should be
211 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
213 pm
->Rollup(0, false, nullptr, nullptr);
215 // If menubar active state is changed or the menubar is destroyed
216 // during closing the popups, we should do nothing anymore.
217 toggleMenuActiveState
= !Destroyed() && !mMenuBarFrame
->IsActive();
219 if (toggleMenuActiveState
) {
220 if (!mMenuBarFrame
->IsActive()) {
221 mMenuBarFrame
->SetActiveByKeyboard();
223 ToggleMenuActiveState();
226 mAccessKeyDown
= false;
227 mAccessKeyDownCanceled
= false;
229 bool active
= !Destroyed() && mMenuBarFrame
->IsActive();
231 keyEvent
->StopPropagation();
232 keyEvent
->PreventDefault();
233 return NS_OK
; // I am consuming event
237 return NS_OK
; // means I am NOT consuming event
240 ////////////////////////////////////////////////////////////////////////
241 nsresult
nsMenuBarListener::KeyPress(Event
* aKeyEvent
) {
242 // if event has already been handled, bail
243 if (!aKeyEvent
|| aKeyEvent
->DefaultPrevented()) {
244 return NS_OK
; // don't consume event
247 // handlers shouldn't be triggered by non-trusted events.
248 if (!aKeyEvent
->IsTrusted()) {
255 // If accesskey handling was forwarded to a child process, wait for
256 // the mozaccesskeynotfound event before handling accesskeys.
257 WidgetKeyboardEvent
* nativeKeyEvent
=
258 aKeyEvent
->WidgetEventPtr()->AsKeyboardEvent();
259 if (!nativeKeyEvent
) {
263 RefPtr
<KeyboardEvent
> keyEvent
= aKeyEvent
->AsKeyboardEvent();
264 uint32_t keyCode
= keyEvent
->KeyCode();
266 // Cancel the access key flag unless we are pressing the access key.
267 if (keyCode
!= (uint32_t)mAccessKey
) {
268 mAccessKeyDownCanceled
= true;
272 // Need to handle F10 specially on Non-Mac platform.
273 if (nativeKeyEvent
->mMessage
== eKeyPress
&& keyCode
== NS_VK_F10
) {
274 if ((GetModifiersForAccessKey(keyEvent
) & ~MODIFIER_CONTROL
) == 0) {
275 // If the keyboard event should activate the menubar and will be
276 // sent to a remote process, it should be executed with reply
277 // event from the focused remote process. Note that if the menubar
278 // is active, the event is already marked as "stop cross
279 // process dispatching". So, in that case, this won't wait
280 // reply from the remote content.
281 if (nativeKeyEvent
->WillBeSentToRemoteProcess()) {
282 nativeKeyEvent
->StopImmediatePropagation();
283 nativeKeyEvent
->MarkAsWaitingReplyFromRemoteProcess();
286 // The F10 key just went down by itself or with ctrl pressed.
287 // In Windows, both of these activate the menu bar.
288 mMenuBarFrame
->SetActiveByKeyboard();
289 ToggleMenuActiveState();
291 if (mMenuBarFrame
->IsActive()) {
292 # ifdef MOZ_WIDGET_GTK
293 // In GTK, this also opens the first menu.
294 mMenuBarFrame
->GetCurrentMenuItem()->OpenMenu(false);
296 aKeyEvent
->StopPropagation();
297 aKeyEvent
->PreventDefault();
305 nsMenuFrame
* menuFrameForKey
= GetMenuForKeyEvent(keyEvent
, false);
306 if (!menuFrameForKey
) {
310 // If the keyboard event matches with a menu item's accesskey and
311 // will be sent to a remote process, it should be executed with
312 // reply event from the focused remote process. Note that if the
313 // menubar is active, the event is already marked as "stop cross
314 // process dispatching". So, in that case, this won't wait
315 // reply from the remote content.
316 if (nativeKeyEvent
->WillBeSentToRemoteProcess()) {
317 nativeKeyEvent
->StopImmediatePropagation();
318 nativeKeyEvent
->MarkAsWaitingReplyFromRemoteProcess();
322 mMenuBarFrame
->SetActiveByKeyboard();
323 mMenuBarFrame
->SetActive(true);
324 menuFrameForKey
->OpenMenu(true);
326 // The opened menu will listen next keyup event.
327 // Therefore, we should clear the keydown flags here.
328 mAccessKeyDown
= mAccessKeyDownCanceled
= false;
330 aKeyEvent
->StopPropagation();
331 aKeyEvent
->PreventDefault();
337 bool nsMenuBarListener::IsAccessKeyPressed(KeyboardEvent
* aKeyEvent
) {
339 // No other modifiers are allowed to be down except for Shift.
340 uint32_t modifiers
= GetModifiersForAccessKey(aKeyEvent
);
342 return (mAccessKeyMask
!= MODIFIER_SHIFT
&& (modifiers
& mAccessKeyMask
) &&
343 (modifiers
& ~(mAccessKeyMask
| MODIFIER_SHIFT
)) == 0);
346 Modifiers
nsMenuBarListener::GetModifiersForAccessKey(
347 KeyboardEvent
* aKeyEvent
) {
348 WidgetInputEvent
* inputEvent
= aKeyEvent
->WidgetEventPtr()->AsInputEvent();
349 MOZ_ASSERT(inputEvent
);
351 static const Modifiers kPossibleModifiersForAccessKey
=
352 (MODIFIER_SHIFT
| MODIFIER_CONTROL
| MODIFIER_ALT
| MODIFIER_META
|
354 return (inputEvent
->mModifiers
& kPossibleModifiersForAccessKey
);
357 nsMenuFrame
* nsMenuBarListener::GetMenuForKeyEvent(KeyboardEvent
* aKeyEvent
,
359 if (!IsAccessKeyPressed(aKeyEvent
)) {
363 uint32_t charCode
= aKeyEvent
->CharCode();
364 bool hasAccessKeyCandidates
= charCode
!= 0;
365 if (!hasAccessKeyCandidates
) {
366 WidgetKeyboardEvent
* nativeKeyEvent
=
367 aKeyEvent
->WidgetEventPtr()->AsKeyboardEvent();
369 AutoTArray
<uint32_t, 10> keys
;
370 nativeKeyEvent
->GetAccessKeyCandidates(keys
);
371 hasAccessKeyCandidates
= !keys
.IsEmpty();
374 if (hasAccessKeyCandidates
) {
375 // Do shortcut navigation.
376 // A letter was pressed. We want to see if a shortcut gets matched. If
377 // so, we'll know the menu got activated.
378 return mMenuBarFrame
->FindMenuWithShortcut(aKeyEvent
, aPeek
);
384 void nsMenuBarListener::ReserveKeyIfNeeded(Event
* aKeyEvent
) {
385 WidgetKeyboardEvent
* nativeKeyEvent
=
386 aKeyEvent
->WidgetEventPtr()->AsKeyboardEvent();
387 if (nsContentUtils::ShouldBlockReservedKeys(nativeKeyEvent
)) {
388 nativeKeyEvent
->MarkAsReservedByChrome();
392 ////////////////////////////////////////////////////////////////////////
393 nsresult
nsMenuBarListener::KeyDown(Event
* aKeyEvent
) {
396 // handlers shouldn't be triggered by non-trusted events.
397 if (!aKeyEvent
|| !aKeyEvent
->IsTrusted()) {
401 RefPtr
<KeyboardEvent
> keyEvent
= aKeyEvent
->AsKeyboardEvent();
406 uint32_t theChar
= keyEvent
->KeyCode();
408 uint16_t eventPhase
= keyEvent
->EventPhase();
409 bool capturing
= (eventPhase
== dom::Event_Binding::CAPTURING_PHASE
);
412 if (capturing
&& !mAccessKeyDown
&& theChar
== NS_VK_F10
&&
413 (GetModifiersForAccessKey(keyEvent
) & ~MODIFIER_CONTROL
) == 0) {
414 ReserveKeyIfNeeded(aKeyEvent
);
418 if (mAccessKey
&& mAccessKeyFocuses
) {
419 bool defaultPrevented
= aKeyEvent
->DefaultPrevented();
421 // No other modifiers can be down.
422 // Especially CTRL. CTRL+ALT == AltGR, and we'll fuck up on non-US
423 // enhanced 102-key keyboards if we don't check this.
424 bool isAccessKeyDownEvent
=
425 ((theChar
== (uint32_t)mAccessKey
) &&
426 (GetModifiersForAccessKey(keyEvent
) & ~mAccessKeyMask
) == 0);
428 if (!capturing
&& !mAccessKeyDown
) {
429 // If accesskey isn't being pressed and the key isn't the accesskey,
431 if (!isAccessKeyDownEvent
) {
435 // Otherwise, accept the accesskey state.
436 mAccessKeyDown
= true;
437 // If default is prevented already, cancel the access key down.
438 mAccessKeyDownCanceled
= defaultPrevented
;
442 // If the pressed accesskey was canceled already or the event was
443 // consumed already, ignore the event.
444 if (mAccessKeyDownCanceled
|| defaultPrevented
) {
448 // Some key other than the access key just went down,
449 // so we won't activate the menu bar when the access key is released.
450 mAccessKeyDownCanceled
= !isAccessKeyDownEvent
;
453 if (capturing
&& mAccessKey
) {
454 nsMenuFrame
* menuFrameForKey
= GetMenuForKeyEvent(keyEvent
, true);
455 if (menuFrameForKey
) {
456 ReserveKeyIfNeeded(aKeyEvent
);
460 return NS_OK
; // means I am NOT consuming event
463 ////////////////////////////////////////////////////////////////////////
465 nsresult
nsMenuBarListener::Blur(Event
* aEvent
) {
466 if (!mMenuBarFrame
->IsMenuOpen() && mMenuBarFrame
->IsActive()) {
467 ToggleMenuActiveState();
468 mAccessKeyDown
= false;
469 mAccessKeyDownCanceled
= false;
471 return NS_OK
; // means I am NOT consuming event
474 ////////////////////////////////////////////////////////////////////////
476 nsresult
nsMenuBarListener::OnWindowDeactivated(Event
* aEvent
) {
477 // Reset the accesskey state because we cannot receive the keyup event for
478 // the pressing accesskey.
479 mAccessKeyDown
= false;
480 mAccessKeyDownCanceled
= false;
481 return NS_OK
; // means I am NOT consuming event
484 ////////////////////////////////////////////////////////////////////////
485 nsresult
nsMenuBarListener::MouseDown(Event
* aMouseEvent
) {
486 // NOTE: MouseDown method listens all phases
488 // Even if the mousedown event is canceled, it means the user don't want
489 // to activate the menu. Therefore, we need to record it at capturing (or
491 if (mAccessKeyDown
) {
492 mAccessKeyDownCanceled
= true;
495 // Don't do anything at capturing phase, any behavior should be cancelable.
496 if (aMouseEvent
->EventPhase() == dom::Event_Binding::CAPTURING_PHASE
) {
500 if (!mMenuBarFrame
->IsMenuOpen() && mMenuBarFrame
->IsActive())
501 ToggleMenuActiveState();
503 return NS_OK
; // means I am NOT consuming event
506 ////////////////////////////////////////////////////////////////////////
508 nsresult
nsMenuBarListener::Fullscreen(Event
* aEvent
) {
509 if (mMenuBarFrame
->IsActive()) {
510 ToggleMenuActiveState();
515 ////////////////////////////////////////////////////////////////////////
516 nsresult
nsMenuBarListener::HandleEvent(Event
* aEvent
) {
517 // If the menu bar is collapsed, don't do anything.
518 if (!mMenuBarFrame
->StyleVisibility()->IsVisible()) {
522 nsAutoString eventType
;
523 aEvent
->GetType(eventType
);
525 if (eventType
.EqualsLiteral("keyup")) {
526 return KeyUp(aEvent
);
528 if (eventType
.EqualsLiteral("keydown")) {
529 return KeyDown(aEvent
);
531 if (eventType
.EqualsLiteral("keypress")) {
532 return KeyPress(aEvent
);
534 if (eventType
.EqualsLiteral("mozaccesskeynotfound")) {
535 return KeyPress(aEvent
);
537 if (eventType
.EqualsLiteral("blur")) {
540 if (eventType
.EqualsLiteral("deactivate")) {
541 return OnWindowDeactivated(aEvent
);
543 if (eventType
.EqualsLiteral("mousedown")) {
544 return MouseDown(aEvent
);
546 if (eventType
.EqualsLiteral("MozDOMFullscreen:Entered")) {
547 return Fullscreen(aEvent
);
550 MOZ_ASSERT_UNREACHABLE("Unexpected eventType");