1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/xul/nsMenuBarListener.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,435 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsMenuBarListener.h" 1.10 +#include "nsMenuBarFrame.h" 1.11 +#include "nsMenuPopupFrame.h" 1.12 +#include "nsIDOMEvent.h" 1.13 + 1.14 +// Drag & Drop, Clipboard 1.15 +#include "nsIServiceManager.h" 1.16 +#include "nsWidgetsCID.h" 1.17 +#include "nsCOMPtr.h" 1.18 +#include "nsIDOMKeyEvent.h" 1.19 +#include "nsIContent.h" 1.20 +#include "nsIDOMNode.h" 1.21 +#include "nsIDOMElement.h" 1.22 + 1.23 +#include "nsContentUtils.h" 1.24 +#include "mozilla/Preferences.h" 1.25 +#include "mozilla/TextEvents.h" 1.26 + 1.27 +using namespace mozilla; 1.28 + 1.29 +/* 1.30 + * nsMenuBarListener implementation 1.31 + */ 1.32 + 1.33 +NS_IMPL_ISUPPORTS(nsMenuBarListener, nsIDOMEventListener) 1.34 + 1.35 +#define MODIFIER_SHIFT 1 1.36 +#define MODIFIER_CONTROL 2 1.37 +#define MODIFIER_ALT 4 1.38 +#define MODIFIER_META 8 1.39 +#define MODIFIER_OS 16 1.40 + 1.41 +//////////////////////////////////////////////////////////////////////// 1.42 + 1.43 +int32_t nsMenuBarListener::mAccessKey = -1; 1.44 +uint32_t nsMenuBarListener::mAccessKeyMask = 0; 1.45 +bool nsMenuBarListener::mAccessKeyFocuses = false; 1.46 + 1.47 +nsMenuBarListener::nsMenuBarListener(nsMenuBarFrame* aMenuBar) 1.48 + :mAccessKeyDown(false), mAccessKeyDownCanceled(false) 1.49 +{ 1.50 + mMenuBarFrame = aMenuBar; 1.51 +} 1.52 + 1.53 +//////////////////////////////////////////////////////////////////////// 1.54 +nsMenuBarListener::~nsMenuBarListener() 1.55 +{ 1.56 +} 1.57 + 1.58 +void 1.59 +nsMenuBarListener::InitializeStatics() 1.60 +{ 1.61 + Preferences::AddBoolVarCache(&mAccessKeyFocuses, 1.62 + "ui.key.menuAccessKeyFocuses"); 1.63 +} 1.64 + 1.65 +nsresult 1.66 +nsMenuBarListener::GetMenuAccessKey(int32_t* aAccessKey) 1.67 +{ 1.68 + if (!aAccessKey) 1.69 + return NS_ERROR_INVALID_POINTER; 1.70 + InitAccessKey(); 1.71 + *aAccessKey = mAccessKey; 1.72 + return NS_OK; 1.73 +} 1.74 + 1.75 +void nsMenuBarListener::InitAccessKey() 1.76 +{ 1.77 + if (mAccessKey >= 0) 1.78 + return; 1.79 + 1.80 + // Compiled-in defaults, in case we can't get LookAndFeel -- 1.81 + // mac doesn't have menu shortcuts, other platforms use alt. 1.82 +#ifdef XP_MACOSX 1.83 + mAccessKey = 0; 1.84 + mAccessKeyMask = 0; 1.85 +#else 1.86 + mAccessKey = nsIDOMKeyEvent::DOM_VK_ALT; 1.87 + mAccessKeyMask = MODIFIER_ALT; 1.88 +#endif 1.89 + 1.90 + // Get the menu access key value from prefs, overriding the default: 1.91 + mAccessKey = Preferences::GetInt("ui.key.menuAccessKey", mAccessKey); 1.92 + if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT) 1.93 + mAccessKeyMask = MODIFIER_SHIFT; 1.94 + else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL) 1.95 + mAccessKeyMask = MODIFIER_CONTROL; 1.96 + else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT) 1.97 + mAccessKeyMask = MODIFIER_ALT; 1.98 + else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META) 1.99 + mAccessKeyMask = MODIFIER_META; 1.100 + else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_WIN) 1.101 + mAccessKeyMask = MODIFIER_OS; 1.102 +} 1.103 + 1.104 +void 1.105 +nsMenuBarListener::ToggleMenuActiveState() 1.106 +{ 1.107 + nsMenuFrame* closemenu = mMenuBarFrame->ToggleMenuActiveState(); 1.108 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.109 + if (pm && closemenu) { 1.110 + nsMenuPopupFrame* popupFrame = closemenu->GetPopup(); 1.111 + if (popupFrame) 1.112 + pm->HidePopup(popupFrame->GetContent(), false, false, true, false); 1.113 + } 1.114 +} 1.115 + 1.116 +//////////////////////////////////////////////////////////////////////// 1.117 +nsresult 1.118 +nsMenuBarListener::KeyUp(nsIDOMEvent* aKeyEvent) 1.119 +{ 1.120 + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); 1.121 + if (!keyEvent) { 1.122 + return NS_OK; 1.123 + } 1.124 + 1.125 + InitAccessKey(); 1.126 + 1.127 + //handlers shouldn't be triggered by non-trusted events. 1.128 + bool trustedEvent = false; 1.129 + aKeyEvent->GetIsTrusted(&trustedEvent); 1.130 + 1.131 + if (!trustedEvent) { 1.132 + return NS_OK; 1.133 + } 1.134 + 1.135 + if (mAccessKey && mAccessKeyFocuses) 1.136 + { 1.137 + bool defaultPrevented = false; 1.138 + aKeyEvent->GetDefaultPrevented(&defaultPrevented); 1.139 + 1.140 + // On a press of the ALT key by itself, we toggle the menu's 1.141 + // active/inactive state. 1.142 + // Get the ascii key code. 1.143 + uint32_t theChar; 1.144 + keyEvent->GetKeyCode(&theChar); 1.145 + 1.146 + if (!defaultPrevented && mAccessKeyDown && !mAccessKeyDownCanceled && 1.147 + (int32_t)theChar == mAccessKey) 1.148 + { 1.149 + // The access key was down and is now up, and no other 1.150 + // keys were pressed in between. 1.151 + if (!mMenuBarFrame->IsActive()) { 1.152 + mMenuBarFrame->SetActiveByKeyboard(); 1.153 + } 1.154 + ToggleMenuActiveState(); 1.155 + } 1.156 + mAccessKeyDown = false; 1.157 + mAccessKeyDownCanceled = false; 1.158 + 1.159 + bool active = mMenuBarFrame->IsActive(); 1.160 + if (active) { 1.161 + aKeyEvent->StopPropagation(); 1.162 + aKeyEvent->PreventDefault(); 1.163 + return NS_OK; // I am consuming event 1.164 + } 1.165 + } 1.166 + 1.167 + return NS_OK; // means I am NOT consuming event 1.168 +} 1.169 + 1.170 +//////////////////////////////////////////////////////////////////////// 1.171 +nsresult 1.172 +nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent) 1.173 +{ 1.174 + // if event has already been handled, bail 1.175 + if (aKeyEvent) { 1.176 + bool eventHandled = false; 1.177 + aKeyEvent->GetDefaultPrevented(&eventHandled); 1.178 + if (eventHandled) { 1.179 + return NS_OK; // don't consume event 1.180 + } 1.181 + } 1.182 + 1.183 + //handlers shouldn't be triggered by non-trusted events. 1.184 + bool trustedEvent = false; 1.185 + if (aKeyEvent) { 1.186 + aKeyEvent->GetIsTrusted(&trustedEvent); 1.187 + } 1.188 + 1.189 + if (!trustedEvent) 1.190 + return NS_OK; 1.191 + 1.192 + nsresult retVal = NS_OK; // default is to not consume event 1.193 + 1.194 + InitAccessKey(); 1.195 + 1.196 + if (mAccessKey) 1.197 + { 1.198 + bool preventDefault; 1.199 + aKeyEvent->GetDefaultPrevented(&preventDefault); 1.200 + if (!preventDefault) { 1.201 + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); 1.202 + uint32_t keyCode, charCode; 1.203 + keyEvent->GetKeyCode(&keyCode); 1.204 + keyEvent->GetCharCode(&charCode); 1.205 + 1.206 + bool hasAccessKeyCandidates = charCode != 0; 1.207 + if (!hasAccessKeyCandidates) { 1.208 + WidgetKeyboardEvent* nativeKeyEvent = 1.209 + aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); 1.210 + if (nativeKeyEvent) { 1.211 + nsAutoTArray<uint32_t, 10> keys; 1.212 + nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent, keys); 1.213 + hasAccessKeyCandidates = !keys.IsEmpty(); 1.214 + } 1.215 + } 1.216 + 1.217 + // Cancel the access key flag unless we are pressing the access key. 1.218 + if (keyCode != (uint32_t)mAccessKey) { 1.219 + mAccessKeyDownCanceled = true; 1.220 + } 1.221 + 1.222 + if (IsAccessKeyPressed(keyEvent) && hasAccessKeyCandidates) { 1.223 + // Do shortcut navigation. 1.224 + // A letter was pressed. We want to see if a shortcut gets matched. If 1.225 + // so, we'll know the menu got activated. 1.226 + nsMenuFrame* result = mMenuBarFrame->FindMenuWithShortcut(keyEvent); 1.227 + if (result) { 1.228 + mMenuBarFrame->SetActiveByKeyboard(); 1.229 + mMenuBarFrame->SetActive(true); 1.230 + result->OpenMenu(true); 1.231 + 1.232 + // The opened menu will listen next keyup event. 1.233 + // Therefore, we should clear the keydown flags here. 1.234 + mAccessKeyDown = mAccessKeyDownCanceled = false; 1.235 + 1.236 + aKeyEvent->StopPropagation(); 1.237 + aKeyEvent->PreventDefault(); 1.238 + retVal = NS_OK; // I am consuming event 1.239 + } 1.240 + } 1.241 +#ifndef XP_MACOSX 1.242 + // Also need to handle F10 specially on Non-Mac platform. 1.243 + else if (keyCode == NS_VK_F10) { 1.244 + if ((GetModifiers(keyEvent) & ~MODIFIER_CONTROL) == 0) { 1.245 + // The F10 key just went down by itself or with ctrl pressed. 1.246 + // In Windows, both of these activate the menu bar. 1.247 + mMenuBarFrame->SetActiveByKeyboard(); 1.248 + ToggleMenuActiveState(); 1.249 + 1.250 + if (mMenuBarFrame->IsActive()) { 1.251 +#ifdef MOZ_WIDGET_GTK 1.252 + // In GTK, this also opens the first menu. 1.253 + mMenuBarFrame->GetCurrentMenuItem()->OpenMenu(true); 1.254 +#endif 1.255 + aKeyEvent->StopPropagation(); 1.256 + aKeyEvent->PreventDefault(); 1.257 + return NS_OK; // consume the event 1.258 + } 1.259 + } 1.260 + } 1.261 +#endif // !XP_MACOSX 1.262 + } 1.263 + } 1.264 + 1.265 + return retVal; 1.266 +} 1.267 + 1.268 +bool 1.269 +nsMenuBarListener::IsAccessKeyPressed(nsIDOMKeyEvent* aKeyEvent) 1.270 +{ 1.271 + InitAccessKey(); 1.272 + // No other modifiers are allowed to be down except for Shift. 1.273 + uint32_t modifiers = GetModifiers(aKeyEvent); 1.274 + 1.275 + return (mAccessKeyMask != MODIFIER_SHIFT && 1.276 + (modifiers & mAccessKeyMask) && 1.277 + (modifiers & ~(mAccessKeyMask | MODIFIER_SHIFT)) == 0); 1.278 +} 1.279 + 1.280 +uint32_t 1.281 +nsMenuBarListener::GetModifiers(nsIDOMKeyEvent* aKeyEvent) 1.282 +{ 1.283 + uint32_t modifiers = 0; 1.284 + WidgetInputEvent* inputEvent = 1.285 + aKeyEvent->GetInternalNSEvent()->AsInputEvent(); 1.286 + MOZ_ASSERT(inputEvent); 1.287 + 1.288 + if (inputEvent->IsShift()) { 1.289 + modifiers |= MODIFIER_SHIFT; 1.290 + } 1.291 + 1.292 + if (inputEvent->IsControl()) { 1.293 + modifiers |= MODIFIER_CONTROL; 1.294 + } 1.295 + 1.296 + if (inputEvent->IsAlt()) { 1.297 + modifiers |= MODIFIER_ALT; 1.298 + } 1.299 + 1.300 + if (inputEvent->IsMeta()) { 1.301 + modifiers |= MODIFIER_META; 1.302 + } 1.303 + 1.304 + if (inputEvent->IsOS()) { 1.305 + modifiers |= MODIFIER_OS; 1.306 + } 1.307 + 1.308 + return modifiers; 1.309 +} 1.310 + 1.311 +//////////////////////////////////////////////////////////////////////// 1.312 +nsresult 1.313 +nsMenuBarListener::KeyDown(nsIDOMEvent* aKeyEvent) 1.314 +{ 1.315 + InitAccessKey(); 1.316 + 1.317 + //handlers shouldn't be triggered by non-trusted events. 1.318 + bool trustedEvent = false; 1.319 + if (aKeyEvent) { 1.320 + aKeyEvent->GetIsTrusted(&trustedEvent); 1.321 + } 1.322 + 1.323 + if (!trustedEvent) 1.324 + return NS_OK; 1.325 + 1.326 + if (mAccessKey && mAccessKeyFocuses) 1.327 + { 1.328 + bool defaultPrevented = false; 1.329 + aKeyEvent->GetDefaultPrevented(&defaultPrevented); 1.330 + 1.331 + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); 1.332 + uint32_t theChar; 1.333 + keyEvent->GetKeyCode(&theChar); 1.334 + 1.335 + // No other modifiers can be down. 1.336 + // Especially CTRL. CTRL+ALT == AltGR, and we'll fuck up on non-US 1.337 + // enhanced 102-key keyboards if we don't check this. 1.338 + bool isAccessKeyDownEvent = 1.339 + ((theChar == (uint32_t)mAccessKey) && 1.340 + (GetModifiers(keyEvent) & ~mAccessKeyMask) == 0); 1.341 + 1.342 + if (!mAccessKeyDown) { 1.343 + // If accesskey isn't being pressed and the key isn't the accesskey, 1.344 + // ignore the event. 1.345 + if (!isAccessKeyDownEvent) { 1.346 + return NS_OK; 1.347 + } 1.348 + 1.349 + // Otherwise, accept the accesskey state. 1.350 + mAccessKeyDown = true; 1.351 + // If default is prevented already, cancel the access key down. 1.352 + mAccessKeyDownCanceled = defaultPrevented; 1.353 + return NS_OK; 1.354 + } 1.355 + 1.356 + // If the pressed accesskey was canceled already or the event was 1.357 + // consumed already, ignore the event. 1.358 + if (mAccessKeyDownCanceled || defaultPrevented) { 1.359 + return NS_OK; 1.360 + } 1.361 + 1.362 + // Some key other than the access key just went down, 1.363 + // so we won't activate the menu bar when the access key is released. 1.364 + mAccessKeyDownCanceled = !isAccessKeyDownEvent; 1.365 + } 1.366 + 1.367 + return NS_OK; // means I am NOT consuming event 1.368 +} 1.369 + 1.370 +//////////////////////////////////////////////////////////////////////// 1.371 + 1.372 +nsresult 1.373 +nsMenuBarListener::Blur(nsIDOMEvent* aEvent) 1.374 +{ 1.375 + if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) { 1.376 + ToggleMenuActiveState(); 1.377 + } 1.378 + // Reset the accesskey state because we cannot receive the keyup event for 1.379 + // the pressing accesskey. 1.380 + mAccessKeyDown = false; 1.381 + mAccessKeyDownCanceled = false; 1.382 + return NS_OK; // means I am NOT consuming event 1.383 +} 1.384 + 1.385 +//////////////////////////////////////////////////////////////////////// 1.386 +nsresult 1.387 +nsMenuBarListener::MouseDown(nsIDOMEvent* aMouseEvent) 1.388 +{ 1.389 + // NOTE: MouseDown method listens all phases 1.390 + 1.391 + // Even if the mousedown event is canceled, it means the user don't want 1.392 + // to activate the menu. Therefore, we need to record it at capturing (or 1.393 + // target) phase. 1.394 + if (mAccessKeyDown) { 1.395 + mAccessKeyDownCanceled = true; 1.396 + } 1.397 + 1.398 + uint16_t phase = 0; 1.399 + nsresult rv = aMouseEvent->GetEventPhase(&phase); 1.400 + NS_ENSURE_SUCCESS(rv, rv); 1.401 + // Don't do anything at capturing phase, any behavior should be cancelable. 1.402 + if (phase == nsIDOMEvent::CAPTURING_PHASE) { 1.403 + return NS_OK; 1.404 + } 1.405 + 1.406 + if (!mMenuBarFrame->IsMenuOpen() && mMenuBarFrame->IsActive()) 1.407 + ToggleMenuActiveState(); 1.408 + 1.409 + return NS_OK; // means I am NOT consuming event 1.410 +} 1.411 + 1.412 +//////////////////////////////////////////////////////////////////////// 1.413 +nsresult 1.414 +nsMenuBarListener::HandleEvent(nsIDOMEvent* aEvent) 1.415 +{ 1.416 + nsAutoString eventType; 1.417 + aEvent->GetType(eventType); 1.418 + 1.419 + if (eventType.EqualsLiteral("keyup")) { 1.420 + return KeyUp(aEvent); 1.421 + } 1.422 + if (eventType.EqualsLiteral("keydown")) { 1.423 + return KeyDown(aEvent); 1.424 + } 1.425 + if (eventType.EqualsLiteral("keypress")) { 1.426 + return KeyPress(aEvent); 1.427 + } 1.428 + if (eventType.EqualsLiteral("blur")) { 1.429 + return Blur(aEvent); 1.430 + } 1.431 + if (eventType.EqualsLiteral("mousedown")) { 1.432 + return MouseDown(aEvent); 1.433 + } 1.434 + 1.435 + NS_ABORT(); 1.436 + 1.437 + return NS_OK; 1.438 +}