1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/xul/nsMenuBarFrame.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 "nsMenuBarFrame.h" 1.10 +#include "nsIServiceManager.h" 1.11 +#include "nsIContent.h" 1.12 +#include "nsIAtom.h" 1.13 +#include "nsPresContext.h" 1.14 +#include "nsStyleContext.h" 1.15 +#include "nsCSSRendering.h" 1.16 +#include "nsNameSpaceManager.h" 1.17 +#include "nsIDocument.h" 1.18 +#include "nsGkAtoms.h" 1.19 +#include "nsMenuFrame.h" 1.20 +#include "nsMenuPopupFrame.h" 1.21 +#include "nsUnicharUtils.h" 1.22 +#include "nsPIDOMWindow.h" 1.23 +#include "nsIInterfaceRequestorUtils.h" 1.24 +#include "nsCSSFrameConstructor.h" 1.25 +#ifdef XP_WIN 1.26 +#include "nsISound.h" 1.27 +#include "nsWidgetsCID.h" 1.28 +#endif 1.29 +#include "nsContentUtils.h" 1.30 +#include "nsUTF8Utils.h" 1.31 +#include "mozilla/TextEvents.h" 1.32 + 1.33 +using namespace mozilla; 1.34 + 1.35 +// 1.36 +// NS_NewMenuBarFrame 1.37 +// 1.38 +// Wrapper for creating a new menu Bar container 1.39 +// 1.40 +nsIFrame* 1.41 +NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) 1.42 +{ 1.43 + return new (aPresShell) nsMenuBarFrame (aPresShell, aContext); 1.44 +} 1.45 + 1.46 +NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame) 1.47 + 1.48 +NS_QUERYFRAME_HEAD(nsMenuBarFrame) 1.49 + NS_QUERYFRAME_ENTRY(nsMenuBarFrame) 1.50 +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) 1.51 + 1.52 +// 1.53 +// nsMenuBarFrame cntr 1.54 +// 1.55 +nsMenuBarFrame::nsMenuBarFrame(nsIPresShell* aShell, nsStyleContext* aContext): 1.56 + nsBoxFrame(aShell, aContext), 1.57 + mMenuBarListener(nullptr), 1.58 + mStayActive(false), 1.59 + mIsActive(false), 1.60 + mCurrentMenu(nullptr), 1.61 + mTarget(nullptr) 1.62 +{ 1.63 +} // cntr 1.64 + 1.65 +void 1.66 +nsMenuBarFrame::Init(nsIContent* aContent, 1.67 + nsIFrame* aParent, 1.68 + nsIFrame* aPrevInFlow) 1.69 +{ 1.70 + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); 1.71 + 1.72 + // Create the menu bar listener. 1.73 + mMenuBarListener = new nsMenuBarListener(this); 1.74 + NS_ADDREF(mMenuBarListener); 1.75 + 1.76 + // Hook up the menu bar as a key listener on the whole document. It will see every 1.77 + // key press that occurs, but after everyone else does. 1.78 + mTarget = aContent->GetDocument(); 1.79 + 1.80 + // Also hook up the listener to the window listening for focus events. This is so we can keep proper 1.81 + // state as the user alt-tabs through processes. 1.82 + 1.83 + mTarget->AddEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false); 1.84 + mTarget->AddEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false); 1.85 + mTarget->AddEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false); 1.86 + 1.87 + // mousedown event should be handled in all phase 1.88 + mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true); 1.89 + mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false); 1.90 + mTarget->AddEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true); 1.91 +} 1.92 + 1.93 +NS_IMETHODIMP 1.94 +nsMenuBarFrame::SetActive(bool aActiveFlag) 1.95 +{ 1.96 + // If the activity is not changed, there is nothing to do. 1.97 + if (mIsActive == aActiveFlag) 1.98 + return NS_OK; 1.99 + 1.100 + if (!aActiveFlag) { 1.101 + // Don't deactivate when switching between menus on the menubar. 1.102 + if (mStayActive) 1.103 + return NS_OK; 1.104 + 1.105 + // if there is a request to deactivate the menu bar, check to see whether 1.106 + // there is a menu popup open for the menu bar. In this case, don't 1.107 + // deactivate the menu bar. 1.108 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.109 + if (pm && pm->IsPopupOpenForMenuParent(this)) 1.110 + return NS_OK; 1.111 + } 1.112 + 1.113 + mIsActive = aActiveFlag; 1.114 + if (mIsActive) { 1.115 + InstallKeyboardNavigator(); 1.116 + } 1.117 + else { 1.118 + mActiveByKeyboard = false; 1.119 + RemoveKeyboardNavigator(); 1.120 + } 1.121 + 1.122 + NS_NAMED_LITERAL_STRING(active, "DOMMenuBarActive"); 1.123 + NS_NAMED_LITERAL_STRING(inactive, "DOMMenuBarInactive"); 1.124 + 1.125 + FireDOMEvent(mIsActive ? active : inactive, mContent); 1.126 + 1.127 + return NS_OK; 1.128 +} 1.129 + 1.130 +nsMenuFrame* 1.131 +nsMenuBarFrame::ToggleMenuActiveState() 1.132 +{ 1.133 + if (mIsActive) { 1.134 + // Deactivate the menu bar 1.135 + SetActive(false); 1.136 + if (mCurrentMenu) { 1.137 + nsMenuFrame* closeframe = mCurrentMenu; 1.138 + closeframe->SelectMenu(false); 1.139 + mCurrentMenu = nullptr; 1.140 + return closeframe; 1.141 + } 1.142 + } 1.143 + else { 1.144 + // if the menu bar is already selected (eg. mouseover), deselect it 1.145 + if (mCurrentMenu) 1.146 + mCurrentMenu->SelectMenu(false); 1.147 + 1.148 + // Set the active menu to be the top left item (e.g., the File menu). 1.149 + // We use an attribute called "menuactive" to track the current 1.150 + // active menu. 1.151 + nsMenuFrame* firstFrame = nsXULPopupManager::GetNextMenuItem(this, nullptr, false); 1.152 + if (firstFrame) { 1.153 + // Activate the menu bar 1.154 + SetActive(true); 1.155 + firstFrame->SelectMenu(true); 1.156 + 1.157 + // Track this item for keyboard navigation. 1.158 + mCurrentMenu = firstFrame; 1.159 + } 1.160 + } 1.161 + 1.162 + return nullptr; 1.163 +} 1.164 + 1.165 +static void 1.166 +GetInsertionPoint(nsIPresShell* aShell, nsIFrame* aFrame, nsIFrame* aChild, 1.167 + nsIFrame** aResult) 1.168 +{ 1.169 + nsIContent* child = nullptr; 1.170 + if (aChild) 1.171 + child = aChild->GetContent(); 1.172 + *aResult = aShell->FrameConstructor()-> 1.173 + GetInsertionPoint(aFrame->GetContent(), child); 1.174 +} 1.175 + 1.176 +nsMenuFrame* 1.177 +nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent) 1.178 +{ 1.179 + uint32_t charCode; 1.180 + aKeyEvent->GetCharCode(&charCode); 1.181 + 1.182 + nsAutoTArray<uint32_t, 10> accessKeys; 1.183 + WidgetKeyboardEvent* nativeKeyEvent = 1.184 + aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); 1.185 + if (nativeKeyEvent) 1.186 + nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent, accessKeys); 1.187 + if (accessKeys.IsEmpty() && charCode) 1.188 + accessKeys.AppendElement(charCode); 1.189 + 1.190 + if (accessKeys.IsEmpty()) 1.191 + return nullptr; // no character was pressed so just return 1.192 + 1.193 + // Enumerate over our list of frames. 1.194 + nsIFrame* immediateParent = nullptr; 1.195 + GetInsertionPoint(PresContext()->PresShell(), this, nullptr, &immediateParent); 1.196 + if (!immediateParent) 1.197 + immediateParent = this; 1.198 + 1.199 + // Find a most preferred accesskey which should be returned. 1.200 + nsIFrame* foundMenu = nullptr; 1.201 + uint32_t foundIndex = accessKeys.NoIndex; 1.202 + nsIFrame* currFrame = immediateParent->GetFirstPrincipalChild(); 1.203 + 1.204 + while (currFrame) { 1.205 + nsIContent* current = currFrame->GetContent(); 1.206 + 1.207 + // See if it's a menu item. 1.208 + if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, false)) { 1.209 + // Get the shortcut attribute. 1.210 + nsAutoString shortcutKey; 1.211 + current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, shortcutKey); 1.212 + if (!shortcutKey.IsEmpty()) { 1.213 + ToLowerCase(shortcutKey); 1.214 + const char16_t* start = shortcutKey.BeginReading(); 1.215 + const char16_t* end = shortcutKey.EndReading(); 1.216 + uint32_t ch = UTF16CharEnumerator::NextChar(&start, end); 1.217 + uint32_t index = accessKeys.IndexOf(ch); 1.218 + if (index != accessKeys.NoIndex && 1.219 + (foundIndex == accessKeys.NoIndex || index < foundIndex)) { 1.220 + foundMenu = currFrame; 1.221 + foundIndex = index; 1.222 + } 1.223 + } 1.224 + } 1.225 + currFrame = currFrame->GetNextSibling(); 1.226 + } 1.227 + if (foundMenu) { 1.228 + return do_QueryFrame(foundMenu); 1.229 + } 1.230 + 1.231 + // didn't find a matching menu item 1.232 +#ifdef XP_WIN 1.233 + // behavior on Windows - this item is on the menu bar, beep and deactivate the menu bar 1.234 + if (mIsActive) { 1.235 + nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1"); 1.236 + if (soundInterface) 1.237 + soundInterface->Beep(); 1.238 + } 1.239 + 1.240 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.241 + if (pm) { 1.242 + nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny); 1.243 + if (popup) 1.244 + pm->HidePopup(popup->GetContent(), true, true, true, false); 1.245 + } 1.246 + 1.247 + SetCurrentMenuItem(nullptr); 1.248 + SetActive(false); 1.249 + 1.250 +#endif // #ifdef XP_WIN 1.251 + 1.252 + return nullptr; 1.253 +} 1.254 + 1.255 +/* virtual */ nsMenuFrame* 1.256 +nsMenuBarFrame::GetCurrentMenuItem() 1.257 +{ 1.258 + return mCurrentMenu; 1.259 +} 1.260 + 1.261 +NS_IMETHODIMP 1.262 +nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem) 1.263 +{ 1.264 + if (mCurrentMenu == aMenuItem) 1.265 + return NS_OK; 1.266 + 1.267 + if (mCurrentMenu) 1.268 + mCurrentMenu->SelectMenu(false); 1.269 + 1.270 + if (aMenuItem) 1.271 + aMenuItem->SelectMenu(true); 1.272 + 1.273 + mCurrentMenu = aMenuItem; 1.274 + 1.275 + return NS_OK; 1.276 +} 1.277 + 1.278 +void 1.279 +nsMenuBarFrame::CurrentMenuIsBeingDestroyed() 1.280 +{ 1.281 + mCurrentMenu->SelectMenu(false); 1.282 + mCurrentMenu = nullptr; 1.283 +} 1.284 + 1.285 +class nsMenuBarSwitchMenu : public nsRunnable 1.286 +{ 1.287 +public: 1.288 + nsMenuBarSwitchMenu(nsIContent* aMenuBar, 1.289 + nsIContent *aOldMenu, 1.290 + nsIContent *aNewMenu, 1.291 + bool aSelectFirstItem) 1.292 + : mMenuBar(aMenuBar), mOldMenu(aOldMenu), mNewMenu(aNewMenu), 1.293 + mSelectFirstItem(aSelectFirstItem) 1.294 + { 1.295 + } 1.296 + 1.297 + NS_IMETHOD Run() MOZ_OVERRIDE 1.298 + { 1.299 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.300 + if (!pm) 1.301 + return NS_ERROR_UNEXPECTED; 1.302 + 1.303 + // if switching from one menu to another, set a flag so that the call to 1.304 + // HidePopup doesn't deactivate the menubar when the first menu closes. 1.305 + nsMenuBarFrame* menubar = nullptr; 1.306 + if (mOldMenu && mNewMenu) { 1.307 + menubar = do_QueryFrame(mMenuBar->GetPrimaryFrame()); 1.308 + if (menubar) 1.309 + menubar->SetStayActive(true); 1.310 + } 1.311 + 1.312 + if (mOldMenu) { 1.313 + nsWeakFrame weakMenuBar(menubar); 1.314 + pm->HidePopup(mOldMenu, false, false, false, false); 1.315 + // clear the flag again 1.316 + if (mNewMenu && weakMenuBar.IsAlive()) 1.317 + menubar->SetStayActive(false); 1.318 + } 1.319 + 1.320 + if (mNewMenu) 1.321 + pm->ShowMenu(mNewMenu, mSelectFirstItem, false); 1.322 + 1.323 + return NS_OK; 1.324 + } 1.325 + 1.326 +private: 1.327 + nsCOMPtr<nsIContent> mMenuBar; 1.328 + nsCOMPtr<nsIContent> mOldMenu; 1.329 + nsCOMPtr<nsIContent> mNewMenu; 1.330 + bool mSelectFirstItem; 1.331 +}; 1.332 + 1.333 +NS_IMETHODIMP 1.334 +nsMenuBarFrame::ChangeMenuItem(nsMenuFrame* aMenuItem, 1.335 + bool aSelectFirstItem) 1.336 +{ 1.337 + if (mCurrentMenu == aMenuItem) 1.338 + return NS_OK; 1.339 + 1.340 + // check if there's an open context menu, we ignore this 1.341 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.342 + if (pm && pm->HasContextMenu(nullptr)) 1.343 + return NS_OK; 1.344 + 1.345 + nsIContent* aOldMenu = nullptr, *aNewMenu = nullptr; 1.346 + 1.347 + // Unset the current child. 1.348 + bool wasOpen = false; 1.349 + if (mCurrentMenu) { 1.350 + wasOpen = mCurrentMenu->IsOpen(); 1.351 + mCurrentMenu->SelectMenu(false); 1.352 + if (wasOpen) { 1.353 + nsMenuPopupFrame* popupFrame = mCurrentMenu->GetPopup(); 1.354 + if (popupFrame) 1.355 + aOldMenu = popupFrame->GetContent(); 1.356 + } 1.357 + } 1.358 + 1.359 + // set to null first in case the IsAlive check below returns false 1.360 + mCurrentMenu = nullptr; 1.361 + 1.362 + // Set the new child. 1.363 + if (aMenuItem) { 1.364 + nsCOMPtr<nsIContent> content = aMenuItem->GetContent(); 1.365 + aMenuItem->SelectMenu(true); 1.366 + mCurrentMenu = aMenuItem; 1.367 + if (wasOpen && !aMenuItem->IsDisabled()) 1.368 + aNewMenu = content; 1.369 + } 1.370 + 1.371 + // use an event so that hiding and showing can be done synchronously, which 1.372 + // avoids flickering 1.373 + nsCOMPtr<nsIRunnable> event = 1.374 + new nsMenuBarSwitchMenu(GetContent(), aOldMenu, aNewMenu, aSelectFirstItem); 1.375 + return NS_DispatchToCurrentThread(event); 1.376 +} 1.377 + 1.378 +nsMenuFrame* 1.379 +nsMenuBarFrame::Enter(WidgetGUIEvent* aEvent) 1.380 +{ 1.381 + if (!mCurrentMenu) 1.382 + return nullptr; 1.383 + 1.384 + if (mCurrentMenu->IsOpen()) 1.385 + return mCurrentMenu->Enter(aEvent); 1.386 + 1.387 + return mCurrentMenu; 1.388 +} 1.389 + 1.390 +bool 1.391 +nsMenuBarFrame::MenuClosed() 1.392 +{ 1.393 + SetActive(false); 1.394 + if (!mIsActive && mCurrentMenu) { 1.395 + mCurrentMenu->SelectMenu(false); 1.396 + mCurrentMenu = nullptr; 1.397 + return true; 1.398 + } 1.399 + return false; 1.400 +} 1.401 + 1.402 +void 1.403 +nsMenuBarFrame::InstallKeyboardNavigator() 1.404 +{ 1.405 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.406 + if (pm) 1.407 + pm->SetActiveMenuBar(this, true); 1.408 +} 1.409 + 1.410 +void 1.411 +nsMenuBarFrame::RemoveKeyboardNavigator() 1.412 +{ 1.413 + if (!mIsActive) { 1.414 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.415 + if (pm) 1.416 + pm->SetActiveMenuBar(this, false); 1.417 + } 1.418 +} 1.419 + 1.420 +void 1.421 +nsMenuBarFrame::DestroyFrom(nsIFrame* aDestructRoot) 1.422 +{ 1.423 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.424 + if (pm) 1.425 + pm->SetActiveMenuBar(this, false); 1.426 + 1.427 + mTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false); 1.428 + mTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false); 1.429 + mTarget->RemoveEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false); 1.430 + 1.431 + mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true); 1.432 + mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false); 1.433 + mTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true); 1.434 + 1.435 + NS_IF_RELEASE(mMenuBarListener); 1.436 + 1.437 + nsBoxFrame::DestroyFrom(aDestructRoot); 1.438 +}