Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "nsMenuBarListener.h"
7 : #include "nsMenuBarFrame.h"
8 : #include "nsMenuPopupFrame.h"
9 : #include "nsIDOMEvent.h"
10 : #include "nsPIWindowRoot.h"
11 :
12 : // Drag & Drop, Clipboard
13 : #include "nsIServiceManager.h"
14 : #include "nsWidgetsCID.h"
15 : #include "nsCOMPtr.h"
16 : #include "nsIDOMKeyEvent.h"
17 : #include "nsIContent.h"
18 : #include "nsIDOMNode.h"
19 : #include "nsIDOMElement.h"
20 :
21 : #include "nsContentUtils.h"
22 : #include "mozilla/BasicEvents.h"
23 : #include "mozilla/Preferences.h"
24 : #include "mozilla/TextEvents.h"
25 :
26 : using namespace mozilla;
27 :
28 : /*
29 : * nsMenuBarListener implementation
30 : */
31 :
32 19 : NS_IMPL_ISUPPORTS(nsMenuBarListener, nsIDOMEventListener)
33 :
34 : ////////////////////////////////////////////////////////////////////////
35 :
36 : int32_t nsMenuBarListener::mAccessKey = -1;
37 : Modifiers nsMenuBarListener::mAccessKeyMask = 0;
38 : bool nsMenuBarListener::mAccessKeyFocuses = false;
39 :
40 1 : nsMenuBarListener::nsMenuBarListener(nsMenuBarFrame* aMenuBarFrame,
41 1 : nsIContent* aMenuBarContent)
42 : : mMenuBarFrame(aMenuBarFrame)
43 1 : , mEventTarget(aMenuBarContent ? aMenuBarContent->GetComposedDoc() : nullptr)
44 : , mTopWindowEventTarget(nullptr)
45 : , mAccessKeyDown(false)
46 2 : , mAccessKeyDownCanceled(false)
47 : {
48 1 : MOZ_ASSERT(mEventTarget);
49 :
50 : // Hook up the menubar as a key listener on the whole document. This will
51 : // see every keypress that occurs, but after everyone else does.
52 :
53 : // Also hook up the listener to the window listening for focus events. This
54 : // is so we can keep proper state as the user alt-tabs through processes.
55 :
56 3 : mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("keypress"),
57 2 : this, false);
58 3 : mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("keydown"),
59 2 : this, false);
60 1 : mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("keyup"), this, false);
61 3 : mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"),
62 2 : this, false);
63 :
64 : // mousedown event should be handled in all phase
65 1 : mEventTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), this, true);
66 1 : mEventTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), this, false);
67 1 : mEventTarget->AddEventListener(NS_LITERAL_STRING("blur"), this, true);
68 :
69 2 : mEventTarget->AddEventListener(
70 2 : NS_LITERAL_STRING("MozDOMFullscreen:Entered"), this, false);
71 :
72 : // Needs to listen to the deactivate event of the window.
73 : RefPtr<EventTarget> topWindowEventTarget =
74 2 : nsContentUtils::GetWindowRoot(aMenuBarContent->GetComposedDoc());
75 1 : mTopWindowEventTarget = topWindowEventTarget.get();
76 :
77 3 : mTopWindowEventTarget->AddSystemEventListener(NS_LITERAL_STRING("deactivate"),
78 2 : this, true);
79 1 : }
80 :
81 : ////////////////////////////////////////////////////////////////////////
82 0 : nsMenuBarListener::~nsMenuBarListener()
83 : {
84 0 : MOZ_ASSERT(!mEventTarget,
85 : "OnDestroyMenuBarFrame() should've alreay been called");
86 0 : }
87 :
88 : void
89 0 : nsMenuBarListener::OnDestroyMenuBarFrame()
90 : {
91 0 : mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"),
92 0 : this, false);
93 0 : mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"),
94 0 : this, false);
95 0 : mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keyup"),
96 0 : this, false);
97 0 : mEventTarget->RemoveSystemEventListener(
98 0 : NS_LITERAL_STRING("mozaccesskeynotfound"), this, false);
99 :
100 0 : mEventTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true);
101 0 : mEventTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"),
102 0 : this, false);
103 0 : mEventTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true);
104 :
105 0 : mEventTarget->RemoveEventListener(
106 0 : NS_LITERAL_STRING("MozDOMFullscreen:Entered"), this, false);
107 :
108 0 : mTopWindowEventTarget->RemoveSystemEventListener(
109 0 : NS_LITERAL_STRING("deactivate"), this, true);
110 :
111 0 : mMenuBarFrame = nullptr;
112 0 : mEventTarget = nullptr;
113 0 : mTopWindowEventTarget = nullptr;
114 0 : }
115 :
116 : void
117 3 : nsMenuBarListener::InitializeStatics()
118 : {
119 : Preferences::AddBoolVarCache(&mAccessKeyFocuses,
120 3 : "ui.key.menuAccessKeyFocuses");
121 3 : }
122 :
123 : nsresult
124 46 : nsMenuBarListener::GetMenuAccessKey(int32_t* aAccessKey)
125 : {
126 46 : if (!aAccessKey)
127 0 : return NS_ERROR_INVALID_POINTER;
128 46 : InitAccessKey();
129 46 : *aAccessKey = mAccessKey;
130 46 : return NS_OK;
131 : }
132 :
133 46 : void nsMenuBarListener::InitAccessKey()
134 : {
135 46 : if (mAccessKey >= 0)
136 45 : return;
137 :
138 : // Compiled-in defaults, in case we can't get LookAndFeel --
139 : // mac doesn't have menu shortcuts, other platforms use alt.
140 : #ifdef XP_MACOSX
141 : mAccessKey = 0;
142 : mAccessKeyMask = 0;
143 : #else
144 1 : mAccessKey = nsIDOMKeyEvent::DOM_VK_ALT;
145 1 : mAccessKeyMask = MODIFIER_ALT;
146 : #endif
147 :
148 : // Get the menu access key value from prefs, overriding the default:
149 1 : mAccessKey = Preferences::GetInt("ui.key.menuAccessKey", mAccessKey);
150 1 : if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT)
151 0 : mAccessKeyMask = MODIFIER_SHIFT;
152 1 : else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL)
153 0 : mAccessKeyMask = MODIFIER_CONTROL;
154 1 : else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT)
155 1 : mAccessKeyMask = MODIFIER_ALT;
156 0 : else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META)
157 0 : mAccessKeyMask = MODIFIER_META;
158 0 : else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_WIN)
159 0 : mAccessKeyMask = MODIFIER_OS;
160 : }
161 :
162 : void
163 0 : nsMenuBarListener::ToggleMenuActiveState()
164 : {
165 0 : nsMenuFrame* closemenu = mMenuBarFrame->ToggleMenuActiveState();
166 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
167 0 : if (pm && closemenu) {
168 0 : nsMenuPopupFrame* popupFrame = closemenu->GetPopup();
169 0 : if (popupFrame)
170 0 : pm->HidePopup(popupFrame->GetContent(), false, false, true, false);
171 : }
172 0 : }
173 :
174 : ////////////////////////////////////////////////////////////////////////
175 : nsresult
176 0 : nsMenuBarListener::KeyUp(nsIDOMEvent* aKeyEvent)
177 : {
178 0 : nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
179 0 : if (!keyEvent) {
180 0 : return NS_OK;
181 : }
182 :
183 0 : InitAccessKey();
184 :
185 : //handlers shouldn't be triggered by non-trusted events.
186 0 : bool trustedEvent = false;
187 0 : aKeyEvent->GetIsTrusted(&trustedEvent);
188 :
189 0 : if (!trustedEvent) {
190 0 : return NS_OK;
191 : }
192 :
193 0 : if (mAccessKey && mAccessKeyFocuses)
194 : {
195 0 : bool defaultPrevented = false;
196 0 : aKeyEvent->GetDefaultPrevented(&defaultPrevented);
197 :
198 : // On a press of the ALT key by itself, we toggle the menu's
199 : // active/inactive state.
200 : // Get the ascii key code.
201 : uint32_t theChar;
202 0 : keyEvent->GetKeyCode(&theChar);
203 :
204 0 : if (!defaultPrevented && mAccessKeyDown && !mAccessKeyDownCanceled &&
205 0 : (int32_t)theChar == mAccessKey)
206 : {
207 : // The access key was down and is now up, and no other
208 : // keys were pressed in between.
209 0 : bool toggleMenuActiveState = true;
210 0 : if (!mMenuBarFrame->IsActive()) {
211 : // First, close all existing popups because other popups shouldn't
212 : // handle key events when menubar is active and IME should be
213 : // disabled.
214 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
215 0 : if (pm) {
216 0 : pm->Rollup(0, false, nullptr, nullptr);
217 : }
218 : // If menubar active state is changed or the menubar is destroyed
219 : // during closing the popups, we should do nothing anymore.
220 0 : toggleMenuActiveState = !Destroyed() && !mMenuBarFrame->IsActive();
221 : }
222 0 : if (toggleMenuActiveState) {
223 0 : if (!mMenuBarFrame->IsActive()) {
224 0 : mMenuBarFrame->SetActiveByKeyboard();
225 : }
226 0 : ToggleMenuActiveState();
227 : }
228 : }
229 0 : mAccessKeyDown = false;
230 0 : mAccessKeyDownCanceled = false;
231 :
232 0 : bool active = !Destroyed() && mMenuBarFrame->IsActive();
233 0 : if (active) {
234 0 : aKeyEvent->StopPropagation();
235 0 : aKeyEvent->PreventDefault();
236 0 : return NS_OK; // I am consuming event
237 : }
238 : }
239 :
240 0 : return NS_OK; // means I am NOT consuming event
241 : }
242 :
243 : ////////////////////////////////////////////////////////////////////////
244 : nsresult
245 0 : nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent)
246 : {
247 : // if event has already been handled, bail
248 0 : if (aKeyEvent) {
249 0 : bool eventHandled = false;
250 0 : aKeyEvent->GetDefaultPrevented(&eventHandled);
251 0 : if (eventHandled) {
252 0 : return NS_OK; // don't consume event
253 : }
254 : }
255 :
256 : //handlers shouldn't be triggered by non-trusted events.
257 0 : bool trustedEvent = false;
258 0 : if (aKeyEvent) {
259 0 : aKeyEvent->GetIsTrusted(&trustedEvent);
260 : }
261 :
262 0 : if (!trustedEvent) {
263 0 : return NS_OK;
264 : }
265 :
266 0 : InitAccessKey();
267 :
268 0 : if (mAccessKey)
269 : {
270 : // If accesskey handling was forwarded to a child process, wait for
271 : // the mozaccesskeynotfound event before handling accesskeys.
272 : WidgetKeyboardEvent* nativeKeyEvent =
273 0 : aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
274 0 : if (!nativeKeyEvent ||
275 0 : (nativeKeyEvent && nativeKeyEvent->mAccessKeyForwardedToChild)) {
276 0 : return NS_OK;
277 : }
278 :
279 0 : nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
280 : uint32_t keyCode, charCode;
281 0 : keyEvent->GetKeyCode(&keyCode);
282 0 : keyEvent->GetCharCode(&charCode);
283 :
284 0 : bool hasAccessKeyCandidates = charCode != 0;
285 0 : if (!hasAccessKeyCandidates) {
286 0 : if (nativeKeyEvent) {
287 0 : AutoTArray<uint32_t, 10> keys;
288 0 : nativeKeyEvent->GetAccessKeyCandidates(keys);
289 0 : hasAccessKeyCandidates = !keys.IsEmpty();
290 : }
291 : }
292 :
293 : // Cancel the access key flag unless we are pressing the access key.
294 0 : if (keyCode != (uint32_t)mAccessKey) {
295 0 : mAccessKeyDownCanceled = true;
296 : }
297 :
298 0 : if (IsAccessKeyPressed(keyEvent) && hasAccessKeyCandidates) {
299 : // Do shortcut navigation.
300 : // A letter was pressed. We want to see if a shortcut gets matched. If
301 : // so, we'll know the menu got activated.
302 0 : nsMenuFrame* result = mMenuBarFrame->FindMenuWithShortcut(keyEvent);
303 0 : if (result) {
304 0 : mMenuBarFrame->SetActiveByKeyboard();
305 0 : mMenuBarFrame->SetActive(true);
306 0 : result->OpenMenu(true);
307 :
308 : // The opened menu will listen next keyup event.
309 : // Therefore, we should clear the keydown flags here.
310 0 : mAccessKeyDown = mAccessKeyDownCanceled = false;
311 :
312 0 : aKeyEvent->StopPropagation();
313 0 : aKeyEvent->PreventDefault();
314 : }
315 : }
316 : #ifndef XP_MACOSX
317 : // Also need to handle F10 specially on Non-Mac platform.
318 0 : else if (nativeKeyEvent->mMessage == eKeyPress && keyCode == NS_VK_F10) {
319 0 : if ((GetModifiersForAccessKey(keyEvent) & ~MODIFIER_CONTROL) == 0) {
320 : // The F10 key just went down by itself or with ctrl pressed.
321 : // In Windows, both of these activate the menu bar.
322 0 : mMenuBarFrame->SetActiveByKeyboard();
323 0 : ToggleMenuActiveState();
324 :
325 0 : if (mMenuBarFrame->IsActive()) {
326 : #ifdef MOZ_WIDGET_GTK
327 : // In GTK, this also opens the first menu.
328 0 : mMenuBarFrame->GetCurrentMenuItem()->OpenMenu(true);
329 : #endif
330 0 : aKeyEvent->StopPropagation();
331 0 : aKeyEvent->PreventDefault();
332 : }
333 : }
334 : }
335 : #endif // !XP_MACOSX
336 : }
337 :
338 0 : return NS_OK;
339 : }
340 :
341 : bool
342 0 : nsMenuBarListener::IsAccessKeyPressed(nsIDOMKeyEvent* aKeyEvent)
343 : {
344 0 : InitAccessKey();
345 : // No other modifiers are allowed to be down except for Shift.
346 0 : uint32_t modifiers = GetModifiersForAccessKey(aKeyEvent);
347 :
348 0 : return (mAccessKeyMask != MODIFIER_SHIFT &&
349 0 : (modifiers & mAccessKeyMask) &&
350 0 : (modifiers & ~(mAccessKeyMask | MODIFIER_SHIFT)) == 0);
351 : }
352 :
353 : Modifiers
354 0 : nsMenuBarListener::GetModifiersForAccessKey(nsIDOMKeyEvent* aKeyEvent)
355 : {
356 : WidgetInputEvent* inputEvent =
357 0 : aKeyEvent->AsEvent()->WidgetEventPtr()->AsInputEvent();
358 0 : MOZ_ASSERT(inputEvent);
359 :
360 : static const Modifiers kPossibleModifiersForAccessKey =
361 : (MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_META |
362 : MODIFIER_OS);
363 0 : return (inputEvent->mModifiers & kPossibleModifiersForAccessKey);
364 : }
365 :
366 : ////////////////////////////////////////////////////////////////////////
367 : nsresult
368 0 : nsMenuBarListener::KeyDown(nsIDOMEvent* aKeyEvent)
369 : {
370 0 : InitAccessKey();
371 :
372 : //handlers shouldn't be triggered by non-trusted events.
373 0 : bool trustedEvent = false;
374 0 : if (aKeyEvent) {
375 0 : aKeyEvent->GetIsTrusted(&trustedEvent);
376 : }
377 :
378 0 : if (!trustedEvent)
379 0 : return NS_OK;
380 :
381 0 : if (mAccessKey && mAccessKeyFocuses)
382 : {
383 0 : bool defaultPrevented = false;
384 0 : aKeyEvent->GetDefaultPrevented(&defaultPrevented);
385 :
386 0 : nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
387 : uint32_t theChar;
388 0 : keyEvent->GetKeyCode(&theChar);
389 :
390 : // No other modifiers can be down.
391 : // Especially CTRL. CTRL+ALT == AltGR, and we'll fuck up on non-US
392 : // enhanced 102-key keyboards if we don't check this.
393 : bool isAccessKeyDownEvent =
394 0 : ((theChar == (uint32_t)mAccessKey) &&
395 0 : (GetModifiersForAccessKey(keyEvent) & ~mAccessKeyMask) == 0);
396 :
397 0 : if (!mAccessKeyDown) {
398 : // If accesskey isn't being pressed and the key isn't the accesskey,
399 : // ignore the event.
400 0 : if (!isAccessKeyDownEvent) {
401 0 : return NS_OK;
402 : }
403 :
404 : // Otherwise, accept the accesskey state.
405 0 : mAccessKeyDown = true;
406 : // If default is prevented already, cancel the access key down.
407 0 : mAccessKeyDownCanceled = defaultPrevented;
408 0 : return NS_OK;
409 : }
410 :
411 : // If the pressed accesskey was canceled already or the event was
412 : // consumed already, ignore the event.
413 0 : if (mAccessKeyDownCanceled || defaultPrevented) {
414 0 : return NS_OK;
415 : }
416 :
417 : // Some key other than the access key just went down,
418 : // so we won't activate the menu bar when the access key is released.
419 0 : mAccessKeyDownCanceled = !isAccessKeyDownEvent;
420 : }
421 :
422 0 : return NS_OK; // means I am NOT consuming event
423 : }
424 :
425 : ////////////////////////////////////////////////////////////////////////
426 :
427 : nsresult
428 0 : nsMenuBarListener::Blur(nsIDOMEvent* aEvent)
429 : {
430 0 : if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) {
431 0 : ToggleMenuActiveState();
432 0 : mAccessKeyDown = false;
433 0 : mAccessKeyDownCanceled = false;
434 : }
435 0 : return NS_OK; // means I am NOT consuming event
436 : }
437 :
438 : ////////////////////////////////////////////////////////////////////////
439 :
440 : nsresult
441 0 : nsMenuBarListener::OnWindowDeactivated(nsIDOMEvent* aEvent)
442 : {
443 : // Reset the accesskey state because we cannot receive the keyup event for
444 : // the pressing accesskey.
445 0 : mAccessKeyDown = false;
446 0 : mAccessKeyDownCanceled = false;
447 0 : return NS_OK; // means I am NOT consuming event
448 : }
449 :
450 : ////////////////////////////////////////////////////////////////////////
451 : nsresult
452 0 : nsMenuBarListener::MouseDown(nsIDOMEvent* aMouseEvent)
453 : {
454 : // NOTE: MouseDown method listens all phases
455 :
456 : // Even if the mousedown event is canceled, it means the user don't want
457 : // to activate the menu. Therefore, we need to record it at capturing (or
458 : // target) phase.
459 0 : if (mAccessKeyDown) {
460 0 : mAccessKeyDownCanceled = true;
461 : }
462 :
463 0 : uint16_t phase = 0;
464 0 : nsresult rv = aMouseEvent->GetEventPhase(&phase);
465 0 : NS_ENSURE_SUCCESS(rv, rv);
466 : // Don't do anything at capturing phase, any behavior should be cancelable.
467 0 : if (phase == nsIDOMEvent::CAPTURING_PHASE) {
468 0 : return NS_OK;
469 : }
470 :
471 0 : if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive())
472 0 : ToggleMenuActiveState();
473 :
474 0 : return NS_OK; // means I am NOT consuming event
475 : }
476 :
477 : ////////////////////////////////////////////////////////////////////////
478 :
479 : nsresult
480 0 : nsMenuBarListener::Fullscreen(nsIDOMEvent* aEvent)
481 : {
482 0 : if (mMenuBarFrame->IsActive()) {
483 0 : ToggleMenuActiveState();
484 : }
485 0 : return NS_OK;
486 : }
487 :
488 : ////////////////////////////////////////////////////////////////////////
489 : nsresult
490 0 : nsMenuBarListener::HandleEvent(nsIDOMEvent* aEvent)
491 : {
492 : // If the menu bar is collapsed, don't do anything.
493 0 : if (!mMenuBarFrame->StyleVisibility()->IsVisible()) {
494 0 : return NS_OK;
495 : }
496 :
497 0 : nsAutoString eventType;
498 0 : aEvent->GetType(eventType);
499 :
500 0 : if (eventType.EqualsLiteral("keyup")) {
501 0 : return KeyUp(aEvent);
502 : }
503 0 : if (eventType.EqualsLiteral("keydown")) {
504 0 : return KeyDown(aEvent);
505 : }
506 0 : if (eventType.EqualsLiteral("keypress")) {
507 0 : return KeyPress(aEvent);
508 : }
509 0 : if (eventType.EqualsLiteral("mozaccesskeynotfound")) {
510 0 : return KeyPress(aEvent);
511 : }
512 0 : if (eventType.EqualsLiteral("blur")) {
513 0 : return Blur(aEvent);
514 : }
515 0 : if (eventType.EqualsLiteral("deactivate")) {
516 0 : return OnWindowDeactivated(aEvent);
517 : }
518 0 : if (eventType.EqualsLiteral("mousedown")) {
519 0 : return MouseDown(aEvent);
520 : }
521 0 : if (eventType.EqualsLiteral("MozDOMFullscreen:Entered")) {
522 0 : return Fullscreen(aEvent);
523 : }
524 :
525 0 : NS_ABORT();
526 :
527 0 : return NS_OK;
528 : }
|