Bug 1793629 - Implement attention indicator for the unified extensions button, r...
[gecko.git] / layout / xul / nsMenuBarListener.cpp
blobd6d5f02b923f353688ee97917c329386c80f0d1a
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"
14 #include "nsCOMPtr.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()
47 : nullptr),
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
65 // cancelled.
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,
94 false);
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,
102 false);
104 mTopWindowEventTarget->RemoveSystemEventListener(u"deactivate"_ns, this,
105 true);
107 mMenuBarFrame = nullptr;
108 mEventTarget = nullptr;
109 mTopWindowEventTarget = nullptr;
112 nsresult nsMenuBarListener::GetMenuAccessKey(int32_t* aAccessKey) {
113 if (!aAccessKey) return NS_ERROR_INVALID_POINTER;
114 InitAccessKey();
115 *aAccessKey = mAccessKey;
116 return NS_OK;
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.
124 #ifdef XP_MACOSX
125 mAccessKey = 0;
126 mAccessKeyMask = 0;
127 #else
128 mAccessKey = dom::KeyboardEvent_Binding::DOM_VK_ALT;
129 mAccessKeyMask = MODIFIER_ALT;
130 #endif
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;
137 break;
138 case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
139 mAccessKeyMask = MODIFIER_CONTROL;
140 break;
141 case dom::KeyboardEvent_Binding::DOM_VK_ALT:
142 mAccessKeyMask = MODIFIER_ALT;
143 break;
144 case dom::KeyboardEvent_Binding::DOM_VK_META:
145 mAccessKeyMask = MODIFIER_META;
146 break;
147 case dom::KeyboardEvent_Binding::DOM_VK_WIN:
148 mAccessKeyMask = MODIFIER_OS;
149 break;
150 default:
151 // Don't touch mAccessKeyMask.
152 break;
156 void nsMenuBarListener::ToggleMenuActiveState() {
157 nsMenuFrame* closemenu = mMenuBarFrame->ToggleMenuActiveState();
158 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
159 if (pm && closemenu) {
160 nsMenuPopupFrame* popupFrame = closemenu->GetPopup();
161 if (popupFrame)
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) {
171 return NS_OK;
174 InitAccessKey();
176 // handlers shouldn't be triggered by non-trusted events.
177 if (!nativeKeyEvent->IsTrusted()) {
178 return NS_OK;
181 if (!mAccessKey || !StaticPrefs::ui_key_menuAccessKeyFocuses()) {
182 return NS_OK;
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();
199 return NS_OK;
201 // First, close all existing popups because other popups shouldn't
202 // handle key events when menubar is active and IME should be
203 // disabled.
204 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
205 if (pm) {
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();
228 return NS_OK;
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()) {
240 return NS_OK;
243 InitAccessKey();
245 if (mAccessKey) {
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) {
251 return NS_OK;
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;
262 #ifndef XP_MACOSX
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();
275 return NS_OK;
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);
286 # endif
287 aKeyEvent->StopPropagation();
288 aKeyEvent->PreventDefault();
292 return NS_OK;
294 #endif // !XP_MACOSX
296 nsMenuFrame* menuFrameForKey = GetMenuForKeyEvent(keyEvent, false);
297 if (!menuFrameForKey) {
298 return NS_OK;
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();
310 return NS_OK;
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();
325 return NS_OK;
328 bool nsMenuBarListener::IsAccessKeyPressed(KeyboardEvent* aKeyEvent) {
329 InitAccessKey();
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 |
344 MODIFIER_OS);
345 return (inputEvent->mModifiers & kPossibleModifiersForAccessKey);
348 nsMenuFrame* nsMenuBarListener::GetMenuForKeyEvent(KeyboardEvent* aKeyEvent,
349 bool aPeek) {
350 if (!IsAccessKeyPressed(aKeyEvent)) {
351 return nullptr;
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);
372 return nullptr;
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) {
385 InitAccessKey();
387 // handlers shouldn't be triggered by non-trusted events.
388 if (!aKeyEvent || !aKeyEvent->IsTrusted()) {
389 return NS_OK;
392 RefPtr<KeyboardEvent> keyEvent = aKeyEvent->AsKeyboardEvent();
393 if (!keyEvent) {
394 return NS_OK;
397 uint32_t theChar = keyEvent->KeyCode();
399 uint16_t eventPhase = keyEvent->EventPhase();
400 bool capturing = (eventPhase == dom::Event_Binding::CAPTURING_PHASE);
402 #ifndef XP_MACOSX
403 if (capturing && !mAccessKeyDown && theChar == NS_VK_F10 &&
404 (GetModifiersForAccessKey(keyEvent) & ~MODIFIER_CONTROL) == 0) {
405 ReserveKeyIfNeeded(aKeyEvent);
407 #endif
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,
421 // ignore the event.
422 if (!isAccessKeyDownEvent) {
423 return NS_OK;
426 // Otherwise, accept the accesskey state.
427 mAccessKeyDown = true;
428 // If default is prevented already, cancel the access key down.
429 mAccessKeyDownCanceled = defaultPrevented;
430 return NS_OK;
433 // If the pressed accesskey was canceled already or the event was
434 // consumed already, ignore the event.
435 if (mAccessKeyDownCanceled || defaultPrevented) {
436 return NS_OK;
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
481 // target) phase.
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) {
488 return NS_OK;
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();
503 return NS_OK;
506 ////////////////////////////////////////////////////////////////////////
507 nsresult nsMenuBarListener::HandleEvent(Event* aEvent) {
508 // If the menu bar is collapsed, don't do anything.
509 if (!mMenuBarFrame->StyleVisibility()->IsVisible()) {
510 return NS_OK;
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")) {
529 return Blur(aEvent);
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");
542 return NS_OK;