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 "nsWidgetsCID.h"
15 #include "nsIContent.h"
17 #include "nsContentUtils.h"
18 #include "mozilla/BasicEvents.h"
19 #include "mozilla/Preferences.h"
20 #include "mozilla/StaticPrefs_ui.h"
21 #include "mozilla/TextEvents.h"
22 #include "mozilla/dom/Document.h"
23 #include "mozilla/dom/Event.h"
24 #include "mozilla/dom/EventBinding.h"
25 #include "mozilla/dom/KeyboardEvent.h"
26 #include "mozilla/dom/KeyboardEventBinding.h"
28 using namespace mozilla
;
29 using mozilla::dom::Event
;
30 using mozilla::dom::KeyboardEvent
;
33 * nsMenuBarListener implementation
36 NS_IMPL_ISUPPORTS(nsMenuBarListener
, nsIDOMEventListener
)
38 ////////////////////////////////////////////////////////////////////////
40 int32_t nsMenuBarListener::mAccessKey
= -1;
41 Modifiers
nsMenuBarListener::mAccessKeyMask
= 0;
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(u
"keypress"_ns
, this, false);
60 mEventTarget
->AddSystemEventListener(u
"keydown"_ns
, this, false);
61 mEventTarget
->AddSystemEventListener(u
"keyup"_ns
, this, false);
62 mEventTarget
->AddSystemEventListener(u
"mozaccesskeynotfound"_ns
, this, false);
63 // Need a capturing event listener if the user has blocked pages from
64 // overriding system keys so that we can prevent menu accesskeys from being
66 mEventTarget
->AddEventListener(u
"keydown"_ns
, this, true);
68 // mousedown event should be handled in all phase
69 mEventTarget
->AddEventListener(u
"mousedown"_ns
, this, true);
70 mEventTarget
->AddEventListener(u
"mousedown"_ns
, this, false);
71 mEventTarget
->AddEventListener(u
"blur"_ns
, this, true);
73 mEventTarget
->AddEventListener(u
"MozDOMFullscreen:Entered"_ns
, this, false);
75 // Needs to listen to the deactivate event of the window.
76 RefPtr
<dom::EventTarget
> topWindowEventTarget
=
77 nsContentUtils::GetWindowRoot(aMenuBarContent
->GetComposedDoc());
78 mTopWindowEventTarget
= topWindowEventTarget
.get();
80 mTopWindowEventTarget
->AddSystemEventListener(u
"deactivate"_ns
, this, true);
83 ////////////////////////////////////////////////////////////////////////
84 nsMenuBarListener::~nsMenuBarListener() {
85 MOZ_ASSERT(!mEventTarget
,
86 "OnDestroyMenuBarFrame() should've alreay been called");
89 void nsMenuBarListener::OnDestroyMenuBarFrame() {
90 mEventTarget
->RemoveSystemEventListener(u
"keypress"_ns
, this, false);
91 mEventTarget
->RemoveSystemEventListener(u
"keydown"_ns
, this, false);
92 mEventTarget
->RemoveSystemEventListener(u
"keyup"_ns
, this, false);
93 mEventTarget
->RemoveSystemEventListener(u
"mozaccesskeynotfound"_ns
, this,
95 mEventTarget
->RemoveEventListener(u
"keydown"_ns
, this, true);
97 mEventTarget
->RemoveEventListener(u
"mousedown"_ns
, this, true);
98 mEventTarget
->RemoveEventListener(u
"mousedown"_ns
, this, false);
99 mEventTarget
->RemoveEventListener(u
"blur"_ns
, this, true);
101 mEventTarget
->RemoveEventListener(u
"MozDOMFullscreen:Entered"_ns
, this,
104 mTopWindowEventTarget
->RemoveSystemEventListener(u
"deactivate"_ns
, this,
107 mMenuBarFrame
= nullptr;
108 mEventTarget
= nullptr;
109 mTopWindowEventTarget
= nullptr;
112 nsresult
nsMenuBarListener::GetMenuAccessKey(int32_t* aAccessKey
) {
113 if (!aAccessKey
) return NS_ERROR_INVALID_POINTER
;
115 *aAccessKey
= mAccessKey
;
119 void nsMenuBarListener::InitAccessKey() {
120 if (mAccessKey
>= 0) return;
122 // Compiled-in defaults, in case we can't get LookAndFeel --
123 // mac doesn't have menu shortcuts, other platforms use alt.
128 mAccessKey
= dom::KeyboardEvent_Binding::DOM_VK_ALT
;
129 mAccessKeyMask
= MODIFIER_ALT
;
132 // Get the menu access key value from prefs, overriding the default:
133 mAccessKey
= Preferences::GetInt("ui.key.menuAccessKey", mAccessKey
);
134 switch (mAccessKey
) {
135 case dom::KeyboardEvent_Binding::DOM_VK_SHIFT
:
136 mAccessKeyMask
= MODIFIER_SHIFT
;
138 case dom::KeyboardEvent_Binding::DOM_VK_CONTROL
:
139 mAccessKeyMask
= MODIFIER_CONTROL
;
141 case dom::KeyboardEvent_Binding::DOM_VK_ALT
:
142 mAccessKeyMask
= MODIFIER_ALT
;
144 case dom::KeyboardEvent_Binding::DOM_VK_META
:
145 mAccessKeyMask
= MODIFIER_META
;
147 case dom::KeyboardEvent_Binding::DOM_VK_WIN
:
148 mAccessKeyMask
= MODIFIER_OS
;
151 // Don't touch mAccessKeyMask.
156 void nsMenuBarListener::ToggleMenuActiveState() {
157 nsMenuFrame
* closemenu
= mMenuBarFrame
->ToggleMenuActiveState();
158 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
159 if (pm
&& closemenu
) {
160 nsMenuPopupFrame
* popupFrame
= closemenu
->GetPopup();
162 pm
->HidePopup(popupFrame
->GetContent(), false, false, true, false);
166 ////////////////////////////////////////////////////////////////////////
167 nsresult
nsMenuBarListener::KeyUp(Event
* aKeyEvent
) {
168 WidgetKeyboardEvent
* nativeKeyEvent
=
169 aKeyEvent
->WidgetEventPtr()->AsKeyboardEvent();
170 if (!nativeKeyEvent
) {
176 // handlers shouldn't be triggered by non-trusted events.
177 if (!nativeKeyEvent
->IsTrusted()) {
181 if (!mAccessKey
|| !StaticPrefs::ui_key_menuAccessKeyFocuses()) {
185 // On a press of the ALT key by itself, we toggle the menu's
186 // active/inactive state.
187 if (!nativeKeyEvent
->DefaultPrevented() && mAccessKeyDown
&&
188 !mAccessKeyDownCanceled
&&
189 static_cast<int32_t>(nativeKeyEvent
->mKeyCode
) == mAccessKey
) {
190 // The access key was down and is now up, and no other
191 // keys were pressed in between.
192 bool toggleMenuActiveState
= true;
193 if (!mMenuBarFrame
->IsActive()) {
194 // If the focused content is in a remote process, we should allow the
195 // focused web app to prevent to activate the menubar.
196 if (nativeKeyEvent
->WillBeSentToRemoteProcess()) {
197 nativeKeyEvent
->StopImmediatePropagation();
198 nativeKeyEvent
->MarkAsWaitingReplyFromRemoteProcess();
201 // First, close all existing popups because other popups shouldn't
202 // handle key events when menubar is active and IME should be
204 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
206 pm
->Rollup(0, false, nullptr, nullptr);
208 // If menubar active state is changed or the menubar is destroyed
209 // during closing the popups, we should do nothing anymore.
210 toggleMenuActiveState
= !Destroyed() && !mMenuBarFrame
->IsActive();
212 if (toggleMenuActiveState
) {
213 if (!mMenuBarFrame
->IsActive()) {
214 mMenuBarFrame
->SetActiveByKeyboard();
216 ToggleMenuActiveState();
220 mAccessKeyDown
= false;
221 mAccessKeyDownCanceled
= false;
223 if (!Destroyed() && mMenuBarFrame
->IsActive()) {
224 nativeKeyEvent
->StopPropagation();
225 nativeKeyEvent
->PreventDefault();
231 ////////////////////////////////////////////////////////////////////////
232 nsresult
nsMenuBarListener::KeyPress(Event
* aKeyEvent
) {
233 // if event has already been handled, bail
234 if (!aKeyEvent
|| aKeyEvent
->DefaultPrevented()) {
235 return NS_OK
; // don't consume event
238 // handlers shouldn't be triggered by non-trusted events.
239 if (!aKeyEvent
->IsTrusted()) {
246 // If accesskey handling was forwarded to a child process, wait for
247 // the mozaccesskeynotfound event before handling accesskeys.
248 WidgetKeyboardEvent
* nativeKeyEvent
=
249 aKeyEvent
->WidgetEventPtr()->AsKeyboardEvent();
250 if (!nativeKeyEvent
) {
254 RefPtr
<KeyboardEvent
> keyEvent
= aKeyEvent
->AsKeyboardEvent();
255 uint32_t keyCode
= keyEvent
->KeyCode();
257 // Cancel the access key flag unless we are pressing the access key.
258 if (keyCode
!= (uint32_t)mAccessKey
) {
259 mAccessKeyDownCanceled
= true;
263 // Need to handle F10 specially on Non-Mac platform.
264 if (nativeKeyEvent
->mMessage
== eKeyPress
&& keyCode
== NS_VK_F10
) {
265 if ((GetModifiersForAccessKey(keyEvent
) & ~MODIFIER_CONTROL
) == 0) {
266 // If the keyboard event should activate the menubar and will be
267 // sent to a remote process, it should be executed with reply
268 // event from the focused remote process. Note that if the menubar
269 // is active, the event is already marked as "stop cross
270 // process dispatching". So, in that case, this won't wait
271 // reply from the remote content.
272 if (nativeKeyEvent
->WillBeSentToRemoteProcess()) {
273 nativeKeyEvent
->StopImmediatePropagation();
274 nativeKeyEvent
->MarkAsWaitingReplyFromRemoteProcess();
277 // The F10 key just went down by itself or with ctrl pressed.
278 // In Windows, both of these activate the menu bar.
279 mMenuBarFrame
->SetActiveByKeyboard();
280 ToggleMenuActiveState();
282 if (mMenuBarFrame
->IsActive()) {
283 # ifdef MOZ_WIDGET_GTK
284 // In GTK, this also opens the first menu.
285 mMenuBarFrame
->GetCurrentMenuItem()->OpenMenu(false);
287 aKeyEvent
->StopPropagation();
288 aKeyEvent
->PreventDefault();
296 nsMenuFrame
* menuFrameForKey
= GetMenuForKeyEvent(keyEvent
, false);
297 if (!menuFrameForKey
) {
301 // If the keyboard event matches with a menu item's accesskey and
302 // will be sent to a remote process, it should be executed with
303 // reply event from the focused remote process. Note that if the
304 // menubar is active, the event is already marked as "stop cross
305 // process dispatching". So, in that case, this won't wait
306 // reply from the remote content.
307 if (nativeKeyEvent
->WillBeSentToRemoteProcess()) {
308 nativeKeyEvent
->StopImmediatePropagation();
309 nativeKeyEvent
->MarkAsWaitingReplyFromRemoteProcess();
313 mMenuBarFrame
->SetActiveByKeyboard();
314 mMenuBarFrame
->SetActive(true);
315 menuFrameForKey
->OpenMenu(true);
317 // The opened menu will listen next keyup event.
318 // Therefore, we should clear the keydown flags here.
319 mAccessKeyDown
= mAccessKeyDownCanceled
= false;
321 aKeyEvent
->StopPropagation();
322 aKeyEvent
->PreventDefault();
328 bool nsMenuBarListener::IsAccessKeyPressed(KeyboardEvent
* aKeyEvent
) {
330 // No other modifiers are allowed to be down except for Shift.
331 uint32_t modifiers
= GetModifiersForAccessKey(aKeyEvent
);
333 return (mAccessKeyMask
!= MODIFIER_SHIFT
&& (modifiers
& mAccessKeyMask
) &&
334 (modifiers
& ~(mAccessKeyMask
| MODIFIER_SHIFT
)) == 0);
337 Modifiers
nsMenuBarListener::GetModifiersForAccessKey(
338 KeyboardEvent
* aKeyEvent
) {
339 WidgetInputEvent
* inputEvent
= aKeyEvent
->WidgetEventPtr()->AsInputEvent();
340 MOZ_ASSERT(inputEvent
);
342 static const Modifiers kPossibleModifiersForAccessKey
=
343 (MODIFIER_SHIFT
| MODIFIER_CONTROL
| MODIFIER_ALT
| MODIFIER_META
|
345 return (inputEvent
->mModifiers
& kPossibleModifiersForAccessKey
);
348 nsMenuFrame
* nsMenuBarListener::GetMenuForKeyEvent(KeyboardEvent
* aKeyEvent
,
350 if (!IsAccessKeyPressed(aKeyEvent
)) {
354 uint32_t charCode
= aKeyEvent
->CharCode();
355 bool hasAccessKeyCandidates
= charCode
!= 0;
356 if (!hasAccessKeyCandidates
) {
357 WidgetKeyboardEvent
* nativeKeyEvent
=
358 aKeyEvent
->WidgetEventPtr()->AsKeyboardEvent();
360 AutoTArray
<uint32_t, 10> keys
;
361 nativeKeyEvent
->GetAccessKeyCandidates(keys
);
362 hasAccessKeyCandidates
= !keys
.IsEmpty();
365 if (hasAccessKeyCandidates
) {
366 // Do shortcut navigation.
367 // A letter was pressed. We want to see if a shortcut gets matched. If
368 // so, we'll know the menu got activated.
369 return mMenuBarFrame
->FindMenuWithShortcut(aKeyEvent
, aPeek
);
375 void nsMenuBarListener::ReserveKeyIfNeeded(Event
* aKeyEvent
) {
376 WidgetKeyboardEvent
* nativeKeyEvent
=
377 aKeyEvent
->WidgetEventPtr()->AsKeyboardEvent();
378 if (nsContentUtils::ShouldBlockReservedKeys(nativeKeyEvent
)) {
379 nativeKeyEvent
->MarkAsReservedByChrome();
383 ////////////////////////////////////////////////////////////////////////
384 nsresult
nsMenuBarListener::KeyDown(Event
* aKeyEvent
) {
387 // handlers shouldn't be triggered by non-trusted events.
388 if (!aKeyEvent
|| !aKeyEvent
->IsTrusted()) {
392 RefPtr
<KeyboardEvent
> keyEvent
= aKeyEvent
->AsKeyboardEvent();
397 uint32_t theChar
= keyEvent
->KeyCode();
399 uint16_t eventPhase
= keyEvent
->EventPhase();
400 bool capturing
= (eventPhase
== dom::Event_Binding::CAPTURING_PHASE
);
403 if (capturing
&& !mAccessKeyDown
&& theChar
== NS_VK_F10
&&
404 (GetModifiersForAccessKey(keyEvent
) & ~MODIFIER_CONTROL
) == 0) {
405 ReserveKeyIfNeeded(aKeyEvent
);
409 if (mAccessKey
&& StaticPrefs::ui_key_menuAccessKeyFocuses()) {
410 bool defaultPrevented
= aKeyEvent
->DefaultPrevented();
412 // No other modifiers can be down.
413 // Especially CTRL. CTRL+ALT == AltGR, and we'll break on non-US
414 // enhanced 102-key keyboards if we don't check this.
415 bool isAccessKeyDownEvent
=
416 ((theChar
== (uint32_t)mAccessKey
) &&
417 (GetModifiersForAccessKey(keyEvent
) & ~mAccessKeyMask
) == 0);
419 if (!capturing
&& !mAccessKeyDown
) {
420 // If accesskey isn't being pressed and the key isn't the accesskey,
422 if (!isAccessKeyDownEvent
) {
426 // Otherwise, accept the accesskey state.
427 mAccessKeyDown
= true;
428 // If default is prevented already, cancel the access key down.
429 mAccessKeyDownCanceled
= defaultPrevented
;
433 // If the pressed accesskey was canceled already or the event was
434 // consumed already, ignore the event.
435 if (mAccessKeyDownCanceled
|| defaultPrevented
) {
439 // Some key other than the access key just went down,
440 // so we won't activate the menu bar when the access key is released.
441 mAccessKeyDownCanceled
= !isAccessKeyDownEvent
;
444 if (capturing
&& mAccessKey
) {
445 nsMenuFrame
* menuFrameForKey
= GetMenuForKeyEvent(keyEvent
, true);
446 if (menuFrameForKey
) {
447 ReserveKeyIfNeeded(aKeyEvent
);
451 return NS_OK
; // means I am NOT consuming event
454 ////////////////////////////////////////////////////////////////////////
456 nsresult
nsMenuBarListener::Blur(Event
* aEvent
) {
457 if (!mMenuBarFrame
->IsMenuOpen() && mMenuBarFrame
->IsActive()) {
458 ToggleMenuActiveState();
459 mAccessKeyDown
= false;
460 mAccessKeyDownCanceled
= false;
462 return NS_OK
; // means I am NOT consuming event
465 ////////////////////////////////////////////////////////////////////////
467 nsresult
nsMenuBarListener::OnWindowDeactivated(Event
* aEvent
) {
468 // Reset the accesskey state because we cannot receive the keyup event for
469 // the pressing accesskey.
470 mAccessKeyDown
= false;
471 mAccessKeyDownCanceled
= false;
472 return NS_OK
; // means I am NOT consuming event
475 ////////////////////////////////////////////////////////////////////////
476 nsresult
nsMenuBarListener::MouseDown(Event
* aMouseEvent
) {
477 // NOTE: MouseDown method listens all phases
479 // Even if the mousedown event is canceled, it means the user don't want
480 // to activate the menu. Therefore, we need to record it at capturing (or
482 if (mAccessKeyDown
) {
483 mAccessKeyDownCanceled
= true;
486 // Don't do anything at capturing phase, any behavior should be cancelable.
487 if (aMouseEvent
->EventPhase() == dom::Event_Binding::CAPTURING_PHASE
) {
491 if (!mMenuBarFrame
->IsMenuOpen() && mMenuBarFrame
->IsActive())
492 ToggleMenuActiveState();
494 return NS_OK
; // means I am NOT consuming event
497 ////////////////////////////////////////////////////////////////////////
499 nsresult
nsMenuBarListener::Fullscreen(Event
* aEvent
) {
500 if (mMenuBarFrame
->IsActive()) {
501 ToggleMenuActiveState();
506 ////////////////////////////////////////////////////////////////////////
507 nsresult
nsMenuBarListener::HandleEvent(Event
* aEvent
) {
508 // If the menu bar is collapsed, don't do anything.
509 if (!mMenuBarFrame
->StyleVisibility()->IsVisible()) {
513 nsAutoString eventType
;
514 aEvent
->GetType(eventType
);
516 if (eventType
.EqualsLiteral("keyup")) {
517 return KeyUp(aEvent
);
519 if (eventType
.EqualsLiteral("keydown")) {
520 return KeyDown(aEvent
);
522 if (eventType
.EqualsLiteral("keypress")) {
523 return KeyPress(aEvent
);
525 if (eventType
.EqualsLiteral("mozaccesskeynotfound")) {
526 return KeyPress(aEvent
);
528 if (eventType
.EqualsLiteral("blur")) {
531 if (eventType
.EqualsLiteral("deactivate")) {
532 return OnWindowDeactivated(aEvent
);
534 if (eventType
.EqualsLiteral("mousedown")) {
535 return MouseDown(aEvent
);
537 if (eventType
.EqualsLiteral("MozDOMFullscreen:Entered")) {
538 return Fullscreen(aEvent
);
541 MOZ_ASSERT_UNREACHABLE("Unexpected eventType");