Bug 1529208 [wpt PR 15469] - [Code Health] Fix incorrect test name, a=testonly
[gecko.git] / layout / xul / nsMenuBarListener.cpp
blob84db35a51445519820f11cc90cca9810d3641c90
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"
15 #include "nsCOMPtr.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()
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(NS_LITERAL_STRING("keypress"), this,
60 false);
61 mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("keydown"), this,
62 false);
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
68 // cancelled.
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"),
77 this, false);
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"),
85 this, true);
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,
96 false);
97 mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), this,
98 false);
99 mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keyup"), this,
100 false);
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,
107 false);
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;
128 InitAccessKey();
129 *aAccessKey = mAccessKey;
130 return NS_OK;
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.
138 #ifdef XP_MACOSX
139 mAccessKey = 0;
140 mAccessKeyMask = 0;
141 #else
142 mAccessKey = dom::KeyboardEvent_Binding::DOM_VK_ALT;
143 mAccessKeyMask = MODIFIER_ALT;
144 #endif
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;
151 break;
152 case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
153 mAccessKeyMask = MODIFIER_CONTROL;
154 break;
155 case dom::KeyboardEvent_Binding::DOM_VK_ALT:
156 mAccessKeyMask = MODIFIER_ALT;
157 break;
158 case dom::KeyboardEvent_Binding::DOM_VK_META:
159 mAccessKeyMask = MODIFIER_META;
160 break;
161 case dom::KeyboardEvent_Binding::DOM_VK_WIN:
162 mAccessKeyMask = MODIFIER_OS;
163 break;
164 default:
165 // Don't touch mAccessKeyMask.
166 break;
170 void nsMenuBarListener::ToggleMenuActiveState() {
171 nsMenuFrame* closemenu = mMenuBarFrame->ToggleMenuActiveState();
172 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
173 if (pm && closemenu) {
174 nsMenuPopupFrame* popupFrame = closemenu->GetPopup();
175 if (popupFrame)
176 pm->HidePopup(popupFrame->GetContent(), false, false, true, false);
180 ////////////////////////////////////////////////////////////////////////
181 nsresult nsMenuBarListener::KeyUp(Event* aKeyEvent) {
182 RefPtr<KeyboardEvent> keyEvent = aKeyEvent->AsKeyboardEvent();
183 if (!keyEvent) {
184 return NS_OK;
187 InitAccessKey();
189 // handlers shouldn't be triggered by non-trusted events.
190 if (!keyEvent->IsTrusted()) {
191 return NS_OK;
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
210 // disabled.
211 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
212 if (pm) {
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();
230 if (active) {
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()) {
249 return NS_OK;
252 InitAccessKey();
254 if (mAccessKey) {
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) {
260 return NS_OK;
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;
271 #ifndef XP_MACOSX
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();
284 return NS_OK;
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);
295 # endif
296 aKeyEvent->StopPropagation();
297 aKeyEvent->PreventDefault();
301 return NS_OK;
303 #endif // !XP_MACOSX
305 nsMenuFrame* menuFrameForKey = GetMenuForKeyEvent(keyEvent, false);
306 if (!menuFrameForKey) {
307 return NS_OK;
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();
319 return NS_OK;
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();
334 return NS_OK;
337 bool nsMenuBarListener::IsAccessKeyPressed(KeyboardEvent* aKeyEvent) {
338 InitAccessKey();
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 |
353 MODIFIER_OS);
354 return (inputEvent->mModifiers & kPossibleModifiersForAccessKey);
357 nsMenuFrame* nsMenuBarListener::GetMenuForKeyEvent(KeyboardEvent* aKeyEvent,
358 bool aPeek) {
359 if (!IsAccessKeyPressed(aKeyEvent)) {
360 return nullptr;
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);
381 return nullptr;
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) {
394 InitAccessKey();
396 // handlers shouldn't be triggered by non-trusted events.
397 if (!aKeyEvent || !aKeyEvent->IsTrusted()) {
398 return NS_OK;
401 RefPtr<KeyboardEvent> keyEvent = aKeyEvent->AsKeyboardEvent();
402 if (!keyEvent) {
403 return NS_OK;
406 uint32_t theChar = keyEvent->KeyCode();
408 uint16_t eventPhase = keyEvent->EventPhase();
409 bool capturing = (eventPhase == dom::Event_Binding::CAPTURING_PHASE);
411 #ifndef XP_MACOSX
412 if (capturing && !mAccessKeyDown && theChar == NS_VK_F10 &&
413 (GetModifiersForAccessKey(keyEvent) & ~MODIFIER_CONTROL) == 0) {
414 ReserveKeyIfNeeded(aKeyEvent);
416 #endif
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,
430 // ignore the event.
431 if (!isAccessKeyDownEvent) {
432 return NS_OK;
435 // Otherwise, accept the accesskey state.
436 mAccessKeyDown = true;
437 // If default is prevented already, cancel the access key down.
438 mAccessKeyDownCanceled = defaultPrevented;
439 return NS_OK;
442 // If the pressed accesskey was canceled already or the event was
443 // consumed already, ignore the event.
444 if (mAccessKeyDownCanceled || defaultPrevented) {
445 return NS_OK;
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
490 // target) phase.
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) {
497 return NS_OK;
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();
512 return NS_OK;
515 ////////////////////////////////////////////////////////////////////////
516 nsresult nsMenuBarListener::HandleEvent(Event* aEvent) {
517 // If the menu bar is collapsed, don't do anything.
518 if (!mMenuBarFrame->StyleVisibility()->IsVisible()) {
519 return NS_OK;
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")) {
538 return Blur(aEvent);
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");
551 return NS_OK;