layout/xul/nsMenuBarFrame.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "nsMenuBarFrame.h"
michael@0 7 #include "nsIServiceManager.h"
michael@0 8 #include "nsIContent.h"
michael@0 9 #include "nsIAtom.h"
michael@0 10 #include "nsPresContext.h"
michael@0 11 #include "nsStyleContext.h"
michael@0 12 #include "nsCSSRendering.h"
michael@0 13 #include "nsNameSpaceManager.h"
michael@0 14 #include "nsIDocument.h"
michael@0 15 #include "nsGkAtoms.h"
michael@0 16 #include "nsMenuFrame.h"
michael@0 17 #include "nsMenuPopupFrame.h"
michael@0 18 #include "nsUnicharUtils.h"
michael@0 19 #include "nsPIDOMWindow.h"
michael@0 20 #include "nsIInterfaceRequestorUtils.h"
michael@0 21 #include "nsCSSFrameConstructor.h"
michael@0 22 #ifdef XP_WIN
michael@0 23 #include "nsISound.h"
michael@0 24 #include "nsWidgetsCID.h"
michael@0 25 #endif
michael@0 26 #include "nsContentUtils.h"
michael@0 27 #include "nsUTF8Utils.h"
michael@0 28 #include "mozilla/TextEvents.h"
michael@0 29
michael@0 30 using namespace mozilla;
michael@0 31
michael@0 32 //
michael@0 33 // NS_NewMenuBarFrame
michael@0 34 //
michael@0 35 // Wrapper for creating a new menu Bar container
michael@0 36 //
michael@0 37 nsIFrame*
michael@0 38 NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
michael@0 39 {
michael@0 40 return new (aPresShell) nsMenuBarFrame (aPresShell, aContext);
michael@0 41 }
michael@0 42
michael@0 43 NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame)
michael@0 44
michael@0 45 NS_QUERYFRAME_HEAD(nsMenuBarFrame)
michael@0 46 NS_QUERYFRAME_ENTRY(nsMenuBarFrame)
michael@0 47 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
michael@0 48
michael@0 49 //
michael@0 50 // nsMenuBarFrame cntr
michael@0 51 //
michael@0 52 nsMenuBarFrame::nsMenuBarFrame(nsIPresShell* aShell, nsStyleContext* aContext):
michael@0 53 nsBoxFrame(aShell, aContext),
michael@0 54 mMenuBarListener(nullptr),
michael@0 55 mStayActive(false),
michael@0 56 mIsActive(false),
michael@0 57 mCurrentMenu(nullptr),
michael@0 58 mTarget(nullptr)
michael@0 59 {
michael@0 60 } // cntr
michael@0 61
michael@0 62 void
michael@0 63 nsMenuBarFrame::Init(nsIContent* aContent,
michael@0 64 nsIFrame* aParent,
michael@0 65 nsIFrame* aPrevInFlow)
michael@0 66 {
michael@0 67 nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
michael@0 68
michael@0 69 // Create the menu bar listener.
michael@0 70 mMenuBarListener = new nsMenuBarListener(this);
michael@0 71 NS_ADDREF(mMenuBarListener);
michael@0 72
michael@0 73 // Hook up the menu bar as a key listener on the whole document. It will see every
michael@0 74 // key press that occurs, but after everyone else does.
michael@0 75 mTarget = aContent->GetDocument();
michael@0 76
michael@0 77 // Also hook up the listener to the window listening for focus events. This is so we can keep proper
michael@0 78 // state as the user alt-tabs through processes.
michael@0 79
michael@0 80 mTarget->AddEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
michael@0 81 mTarget->AddEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
michael@0 82 mTarget->AddEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
michael@0 83
michael@0 84 // mousedown event should be handled in all phase
michael@0 85 mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
michael@0 86 mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
michael@0 87 mTarget->AddEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
michael@0 88 }
michael@0 89
michael@0 90 NS_IMETHODIMP
michael@0 91 nsMenuBarFrame::SetActive(bool aActiveFlag)
michael@0 92 {
michael@0 93 // If the activity is not changed, there is nothing to do.
michael@0 94 if (mIsActive == aActiveFlag)
michael@0 95 return NS_OK;
michael@0 96
michael@0 97 if (!aActiveFlag) {
michael@0 98 // Don't deactivate when switching between menus on the menubar.
michael@0 99 if (mStayActive)
michael@0 100 return NS_OK;
michael@0 101
michael@0 102 // if there is a request to deactivate the menu bar, check to see whether
michael@0 103 // there is a menu popup open for the menu bar. In this case, don't
michael@0 104 // deactivate the menu bar.
michael@0 105 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 106 if (pm && pm->IsPopupOpenForMenuParent(this))
michael@0 107 return NS_OK;
michael@0 108 }
michael@0 109
michael@0 110 mIsActive = aActiveFlag;
michael@0 111 if (mIsActive) {
michael@0 112 InstallKeyboardNavigator();
michael@0 113 }
michael@0 114 else {
michael@0 115 mActiveByKeyboard = false;
michael@0 116 RemoveKeyboardNavigator();
michael@0 117 }
michael@0 118
michael@0 119 NS_NAMED_LITERAL_STRING(active, "DOMMenuBarActive");
michael@0 120 NS_NAMED_LITERAL_STRING(inactive, "DOMMenuBarInactive");
michael@0 121
michael@0 122 FireDOMEvent(mIsActive ? active : inactive, mContent);
michael@0 123
michael@0 124 return NS_OK;
michael@0 125 }
michael@0 126
michael@0 127 nsMenuFrame*
michael@0 128 nsMenuBarFrame::ToggleMenuActiveState()
michael@0 129 {
michael@0 130 if (mIsActive) {
michael@0 131 // Deactivate the menu bar
michael@0 132 SetActive(false);
michael@0 133 if (mCurrentMenu) {
michael@0 134 nsMenuFrame* closeframe = mCurrentMenu;
michael@0 135 closeframe->SelectMenu(false);
michael@0 136 mCurrentMenu = nullptr;
michael@0 137 return closeframe;
michael@0 138 }
michael@0 139 }
michael@0 140 else {
michael@0 141 // if the menu bar is already selected (eg. mouseover), deselect it
michael@0 142 if (mCurrentMenu)
michael@0 143 mCurrentMenu->SelectMenu(false);
michael@0 144
michael@0 145 // Set the active menu to be the top left item (e.g., the File menu).
michael@0 146 // We use an attribute called "menuactive" to track the current
michael@0 147 // active menu.
michael@0 148 nsMenuFrame* firstFrame = nsXULPopupManager::GetNextMenuItem(this, nullptr, false);
michael@0 149 if (firstFrame) {
michael@0 150 // Activate the menu bar
michael@0 151 SetActive(true);
michael@0 152 firstFrame->SelectMenu(true);
michael@0 153
michael@0 154 // Track this item for keyboard navigation.
michael@0 155 mCurrentMenu = firstFrame;
michael@0 156 }
michael@0 157 }
michael@0 158
michael@0 159 return nullptr;
michael@0 160 }
michael@0 161
michael@0 162 static void
michael@0 163 GetInsertionPoint(nsIPresShell* aShell, nsIFrame* aFrame, nsIFrame* aChild,
michael@0 164 nsIFrame** aResult)
michael@0 165 {
michael@0 166 nsIContent* child = nullptr;
michael@0 167 if (aChild)
michael@0 168 child = aChild->GetContent();
michael@0 169 *aResult = aShell->FrameConstructor()->
michael@0 170 GetInsertionPoint(aFrame->GetContent(), child);
michael@0 171 }
michael@0 172
michael@0 173 nsMenuFrame*
michael@0 174 nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent)
michael@0 175 {
michael@0 176 uint32_t charCode;
michael@0 177 aKeyEvent->GetCharCode(&charCode);
michael@0 178
michael@0 179 nsAutoTArray<uint32_t, 10> accessKeys;
michael@0 180 WidgetKeyboardEvent* nativeKeyEvent =
michael@0 181 aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
michael@0 182 if (nativeKeyEvent)
michael@0 183 nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent, accessKeys);
michael@0 184 if (accessKeys.IsEmpty() && charCode)
michael@0 185 accessKeys.AppendElement(charCode);
michael@0 186
michael@0 187 if (accessKeys.IsEmpty())
michael@0 188 return nullptr; // no character was pressed so just return
michael@0 189
michael@0 190 // Enumerate over our list of frames.
michael@0 191 nsIFrame* immediateParent = nullptr;
michael@0 192 GetInsertionPoint(PresContext()->PresShell(), this, nullptr, &immediateParent);
michael@0 193 if (!immediateParent)
michael@0 194 immediateParent = this;
michael@0 195
michael@0 196 // Find a most preferred accesskey which should be returned.
michael@0 197 nsIFrame* foundMenu = nullptr;
michael@0 198 uint32_t foundIndex = accessKeys.NoIndex;
michael@0 199 nsIFrame* currFrame = immediateParent->GetFirstPrincipalChild();
michael@0 200
michael@0 201 while (currFrame) {
michael@0 202 nsIContent* current = currFrame->GetContent();
michael@0 203
michael@0 204 // See if it's a menu item.
michael@0 205 if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, false)) {
michael@0 206 // Get the shortcut attribute.
michael@0 207 nsAutoString shortcutKey;
michael@0 208 current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, shortcutKey);
michael@0 209 if (!shortcutKey.IsEmpty()) {
michael@0 210 ToLowerCase(shortcutKey);
michael@0 211 const char16_t* start = shortcutKey.BeginReading();
michael@0 212 const char16_t* end = shortcutKey.EndReading();
michael@0 213 uint32_t ch = UTF16CharEnumerator::NextChar(&start, end);
michael@0 214 uint32_t index = accessKeys.IndexOf(ch);
michael@0 215 if (index != accessKeys.NoIndex &&
michael@0 216 (foundIndex == accessKeys.NoIndex || index < foundIndex)) {
michael@0 217 foundMenu = currFrame;
michael@0 218 foundIndex = index;
michael@0 219 }
michael@0 220 }
michael@0 221 }
michael@0 222 currFrame = currFrame->GetNextSibling();
michael@0 223 }
michael@0 224 if (foundMenu) {
michael@0 225 return do_QueryFrame(foundMenu);
michael@0 226 }
michael@0 227
michael@0 228 // didn't find a matching menu item
michael@0 229 #ifdef XP_WIN
michael@0 230 // behavior on Windows - this item is on the menu bar, beep and deactivate the menu bar
michael@0 231 if (mIsActive) {
michael@0 232 nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
michael@0 233 if (soundInterface)
michael@0 234 soundInterface->Beep();
michael@0 235 }
michael@0 236
michael@0 237 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 238 if (pm) {
michael@0 239 nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
michael@0 240 if (popup)
michael@0 241 pm->HidePopup(popup->GetContent(), true, true, true, false);
michael@0 242 }
michael@0 243
michael@0 244 SetCurrentMenuItem(nullptr);
michael@0 245 SetActive(false);
michael@0 246
michael@0 247 #endif // #ifdef XP_WIN
michael@0 248
michael@0 249 return nullptr;
michael@0 250 }
michael@0 251
michael@0 252 /* virtual */ nsMenuFrame*
michael@0 253 nsMenuBarFrame::GetCurrentMenuItem()
michael@0 254 {
michael@0 255 return mCurrentMenu;
michael@0 256 }
michael@0 257
michael@0 258 NS_IMETHODIMP
michael@0 259 nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem)
michael@0 260 {
michael@0 261 if (mCurrentMenu == aMenuItem)
michael@0 262 return NS_OK;
michael@0 263
michael@0 264 if (mCurrentMenu)
michael@0 265 mCurrentMenu->SelectMenu(false);
michael@0 266
michael@0 267 if (aMenuItem)
michael@0 268 aMenuItem->SelectMenu(true);
michael@0 269
michael@0 270 mCurrentMenu = aMenuItem;
michael@0 271
michael@0 272 return NS_OK;
michael@0 273 }
michael@0 274
michael@0 275 void
michael@0 276 nsMenuBarFrame::CurrentMenuIsBeingDestroyed()
michael@0 277 {
michael@0 278 mCurrentMenu->SelectMenu(false);
michael@0 279 mCurrentMenu = nullptr;
michael@0 280 }
michael@0 281
michael@0 282 class nsMenuBarSwitchMenu : public nsRunnable
michael@0 283 {
michael@0 284 public:
michael@0 285 nsMenuBarSwitchMenu(nsIContent* aMenuBar,
michael@0 286 nsIContent *aOldMenu,
michael@0 287 nsIContent *aNewMenu,
michael@0 288 bool aSelectFirstItem)
michael@0 289 : mMenuBar(aMenuBar), mOldMenu(aOldMenu), mNewMenu(aNewMenu),
michael@0 290 mSelectFirstItem(aSelectFirstItem)
michael@0 291 {
michael@0 292 }
michael@0 293
michael@0 294 NS_IMETHOD Run() MOZ_OVERRIDE
michael@0 295 {
michael@0 296 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 297 if (!pm)
michael@0 298 return NS_ERROR_UNEXPECTED;
michael@0 299
michael@0 300 // if switching from one menu to another, set a flag so that the call to
michael@0 301 // HidePopup doesn't deactivate the menubar when the first menu closes.
michael@0 302 nsMenuBarFrame* menubar = nullptr;
michael@0 303 if (mOldMenu && mNewMenu) {
michael@0 304 menubar = do_QueryFrame(mMenuBar->GetPrimaryFrame());
michael@0 305 if (menubar)
michael@0 306 menubar->SetStayActive(true);
michael@0 307 }
michael@0 308
michael@0 309 if (mOldMenu) {
michael@0 310 nsWeakFrame weakMenuBar(menubar);
michael@0 311 pm->HidePopup(mOldMenu, false, false, false, false);
michael@0 312 // clear the flag again
michael@0 313 if (mNewMenu && weakMenuBar.IsAlive())
michael@0 314 menubar->SetStayActive(false);
michael@0 315 }
michael@0 316
michael@0 317 if (mNewMenu)
michael@0 318 pm->ShowMenu(mNewMenu, mSelectFirstItem, false);
michael@0 319
michael@0 320 return NS_OK;
michael@0 321 }
michael@0 322
michael@0 323 private:
michael@0 324 nsCOMPtr<nsIContent> mMenuBar;
michael@0 325 nsCOMPtr<nsIContent> mOldMenu;
michael@0 326 nsCOMPtr<nsIContent> mNewMenu;
michael@0 327 bool mSelectFirstItem;
michael@0 328 };
michael@0 329
michael@0 330 NS_IMETHODIMP
michael@0 331 nsMenuBarFrame::ChangeMenuItem(nsMenuFrame* aMenuItem,
michael@0 332 bool aSelectFirstItem)
michael@0 333 {
michael@0 334 if (mCurrentMenu == aMenuItem)
michael@0 335 return NS_OK;
michael@0 336
michael@0 337 // check if there's an open context menu, we ignore this
michael@0 338 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 339 if (pm && pm->HasContextMenu(nullptr))
michael@0 340 return NS_OK;
michael@0 341
michael@0 342 nsIContent* aOldMenu = nullptr, *aNewMenu = nullptr;
michael@0 343
michael@0 344 // Unset the current child.
michael@0 345 bool wasOpen = false;
michael@0 346 if (mCurrentMenu) {
michael@0 347 wasOpen = mCurrentMenu->IsOpen();
michael@0 348 mCurrentMenu->SelectMenu(false);
michael@0 349 if (wasOpen) {
michael@0 350 nsMenuPopupFrame* popupFrame = mCurrentMenu->GetPopup();
michael@0 351 if (popupFrame)
michael@0 352 aOldMenu = popupFrame->GetContent();
michael@0 353 }
michael@0 354 }
michael@0 355
michael@0 356 // set to null first in case the IsAlive check below returns false
michael@0 357 mCurrentMenu = nullptr;
michael@0 358
michael@0 359 // Set the new child.
michael@0 360 if (aMenuItem) {
michael@0 361 nsCOMPtr<nsIContent> content = aMenuItem->GetContent();
michael@0 362 aMenuItem->SelectMenu(true);
michael@0 363 mCurrentMenu = aMenuItem;
michael@0 364 if (wasOpen && !aMenuItem->IsDisabled())
michael@0 365 aNewMenu = content;
michael@0 366 }
michael@0 367
michael@0 368 // use an event so that hiding and showing can be done synchronously, which
michael@0 369 // avoids flickering
michael@0 370 nsCOMPtr<nsIRunnable> event =
michael@0 371 new nsMenuBarSwitchMenu(GetContent(), aOldMenu, aNewMenu, aSelectFirstItem);
michael@0 372 return NS_DispatchToCurrentThread(event);
michael@0 373 }
michael@0 374
michael@0 375 nsMenuFrame*
michael@0 376 nsMenuBarFrame::Enter(WidgetGUIEvent* aEvent)
michael@0 377 {
michael@0 378 if (!mCurrentMenu)
michael@0 379 return nullptr;
michael@0 380
michael@0 381 if (mCurrentMenu->IsOpen())
michael@0 382 return mCurrentMenu->Enter(aEvent);
michael@0 383
michael@0 384 return mCurrentMenu;
michael@0 385 }
michael@0 386
michael@0 387 bool
michael@0 388 nsMenuBarFrame::MenuClosed()
michael@0 389 {
michael@0 390 SetActive(false);
michael@0 391 if (!mIsActive && mCurrentMenu) {
michael@0 392 mCurrentMenu->SelectMenu(false);
michael@0 393 mCurrentMenu = nullptr;
michael@0 394 return true;
michael@0 395 }
michael@0 396 return false;
michael@0 397 }
michael@0 398
michael@0 399 void
michael@0 400 nsMenuBarFrame::InstallKeyboardNavigator()
michael@0 401 {
michael@0 402 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 403 if (pm)
michael@0 404 pm->SetActiveMenuBar(this, true);
michael@0 405 }
michael@0 406
michael@0 407 void
michael@0 408 nsMenuBarFrame::RemoveKeyboardNavigator()
michael@0 409 {
michael@0 410 if (!mIsActive) {
michael@0 411 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 412 if (pm)
michael@0 413 pm->SetActiveMenuBar(this, false);
michael@0 414 }
michael@0 415 }
michael@0 416
michael@0 417 void
michael@0 418 nsMenuBarFrame::DestroyFrom(nsIFrame* aDestructRoot)
michael@0 419 {
michael@0 420 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 421 if (pm)
michael@0 422 pm->SetActiveMenuBar(this, false);
michael@0 423
michael@0 424 mTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
michael@0 425 mTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
michael@0 426 mTarget->RemoveEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
michael@0 427
michael@0 428 mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
michael@0 429 mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
michael@0 430 mTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
michael@0 431
michael@0 432 NS_IF_RELEASE(mMenuBarListener);
michael@0 433
michael@0 434 nsBoxFrame::DestroyFrom(aDestructRoot);
michael@0 435 }

mercurial