widget/cocoa/nsMenuBarX.mm

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 <objc/objc-runtime.h>
michael@0 7
michael@0 8 #include "nsMenuBarX.h"
michael@0 9 #include "nsMenuX.h"
michael@0 10 #include "nsMenuItemX.h"
michael@0 11 #include "nsMenuUtilsX.h"
michael@0 12 #include "nsCocoaFeatures.h"
michael@0 13 #include "nsCocoaUtils.h"
michael@0 14 #include "nsCocoaWindow.h"
michael@0 15 #include "nsChildView.h"
michael@0 16
michael@0 17 #include "nsCOMPtr.h"
michael@0 18 #include "nsString.h"
michael@0 19 #include "nsGkAtoms.h"
michael@0 20 #include "nsObjCExceptions.h"
michael@0 21 #include "nsThreadUtils.h"
michael@0 22
michael@0 23 #include "nsIContent.h"
michael@0 24 #include "nsIWidget.h"
michael@0 25 #include "nsIDocument.h"
michael@0 26 #include "nsIDOMDocument.h"
michael@0 27 #include "nsIDOMElement.h"
michael@0 28
michael@0 29 NativeMenuItemTarget* nsMenuBarX::sNativeEventTarget = nil;
michael@0 30 nsMenuBarX* nsMenuBarX::sLastGeckoMenuBarPainted = nullptr; // Weak
michael@0 31 nsMenuBarX* nsMenuBarX::sCurrentPaintDelayedMenuBar = nullptr; // Weak
michael@0 32 NSMenu* sApplicationMenu = nil;
michael@0 33 BOOL gSomeMenuBarPainted = NO;
michael@0 34
michael@0 35 // We keep references to the first quit and pref item content nodes we find, which
michael@0 36 // will be from the hidden window. We use these when the document for the current
michael@0 37 // window does not have a quit or pref item. We don't need strong refs here because
michael@0 38 // these items are always strong ref'd by their owning menu bar (instance variable).
michael@0 39 static nsIContent* sAboutItemContent = nullptr;
michael@0 40 static nsIContent* sUpdateItemContent = nullptr;
michael@0 41 static nsIContent* sPrefItemContent = nullptr;
michael@0 42 static nsIContent* sQuitItemContent = nullptr;
michael@0 43
michael@0 44 NS_IMPL_ISUPPORTS(nsNativeMenuServiceX, nsINativeMenuService)
michael@0 45
michael@0 46 NS_IMETHODIMP nsNativeMenuServiceX::CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode)
michael@0 47 {
michael@0 48 NS_ASSERTION(NS_IsMainThread(), "Attempting to create native menu bar on wrong thread!");
michael@0 49
michael@0 50 nsRefPtr<nsMenuBarX> mb = new nsMenuBarX();
michael@0 51 if (!mb)
michael@0 52 return NS_ERROR_OUT_OF_MEMORY;
michael@0 53
michael@0 54 return mb->Create(aParent, aMenuBarNode);
michael@0 55 }
michael@0 56
michael@0 57 nsMenuBarX::nsMenuBarX()
michael@0 58 : nsMenuGroupOwnerX(), mParentWindow(nullptr), mAwaitingDelayedPaint(false)
michael@0 59 {
michael@0 60 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 61
michael@0 62 mNativeMenu = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar" andMenuBarOwner:this];
michael@0 63
michael@0 64 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 65 }
michael@0 66
michael@0 67 nsMenuBarX::~nsMenuBarX()
michael@0 68 {
michael@0 69 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 70
michael@0 71 if (nsMenuBarX::sLastGeckoMenuBarPainted == this)
michael@0 72 nsMenuBarX::sLastGeckoMenuBarPainted = nullptr;
michael@0 73
michael@0 74 // the quit/pref items of a random window might have been used if there was no
michael@0 75 // hidden window, thus we need to invalidate the weak references.
michael@0 76 if (sAboutItemContent == mAboutItemContent)
michael@0 77 sAboutItemContent = nullptr;
michael@0 78 if (sUpdateItemContent == mUpdateItemContent)
michael@0 79 sUpdateItemContent = nullptr;
michael@0 80 if (sQuitItemContent == mQuitItemContent)
michael@0 81 sQuitItemContent = nullptr;
michael@0 82 if (sPrefItemContent == mPrefItemContent)
michael@0 83 sPrefItemContent = nullptr;
michael@0 84
michael@0 85 // make sure we unregister ourselves as a content observer
michael@0 86 UnregisterForContentChanges(mContent);
michael@0 87
michael@0 88 // We have to manually clear the array here because clearing causes menu items
michael@0 89 // to call back into the menu bar to unregister themselves. We don't want to
michael@0 90 // depend on member variable ordering to ensure that the array gets cleared
michael@0 91 // before the registration hash table is destroyed.
michael@0 92 mMenuArray.Clear();
michael@0 93
michael@0 94 [mNativeMenu resetMenuBarOwner];
michael@0 95 [mNativeMenu release];
michael@0 96
michael@0 97 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 98 }
michael@0 99
michael@0 100 nsresult nsMenuBarX::Create(nsIWidget* aParent, nsIContent* aContent)
michael@0 101 {
michael@0 102 if (!aParent || !aContent)
michael@0 103 return NS_ERROR_INVALID_ARG;
michael@0 104
michael@0 105 mParentWindow = aParent;
michael@0 106 mContent = aContent;
michael@0 107
michael@0 108 AquifyMenuBar();
michael@0 109
michael@0 110 nsresult rv = nsMenuGroupOwnerX::Create(aContent);
michael@0 111 if (NS_FAILED(rv))
michael@0 112 return rv;
michael@0 113
michael@0 114 RegisterForContentChanges(aContent, this);
michael@0 115
michael@0 116 ConstructNativeMenus();
michael@0 117
michael@0 118 // Give this to the parent window. The parent takes ownership.
michael@0 119 static_cast<nsCocoaWindow*>(mParentWindow)->SetMenuBar(this);
michael@0 120
michael@0 121 return NS_OK;
michael@0 122 }
michael@0 123
michael@0 124 void nsMenuBarX::ConstructNativeMenus()
michael@0 125 {
michael@0 126 uint32_t count = mContent->GetChildCount();
michael@0 127 for (uint32_t i = 0; i < count; i++) {
michael@0 128 nsIContent *menuContent = mContent->GetChildAt(i);
michael@0 129 if (menuContent &&
michael@0 130 menuContent->Tag() == nsGkAtoms::menu &&
michael@0 131 menuContent->IsXUL()) {
michael@0 132 nsMenuX* newMenu = new nsMenuX();
michael@0 133 if (newMenu) {
michael@0 134 nsresult rv = newMenu->Create(this, this, menuContent);
michael@0 135 if (NS_SUCCEEDED(rv))
michael@0 136 InsertMenuAtIndex(newMenu, GetMenuCount());
michael@0 137 else
michael@0 138 delete newMenu;
michael@0 139 }
michael@0 140 }
michael@0 141 }
michael@0 142 }
michael@0 143
michael@0 144 uint32_t nsMenuBarX::GetMenuCount()
michael@0 145 {
michael@0 146 return mMenuArray.Length();
michael@0 147 }
michael@0 148
michael@0 149 bool nsMenuBarX::MenuContainsAppMenu()
michael@0 150 {
michael@0 151 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 152
michael@0 153 return ([mNativeMenu numberOfItems] > 0 &&
michael@0 154 [[mNativeMenu itemAtIndex:0] submenu] == sApplicationMenu);
michael@0 155
michael@0 156 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
michael@0 157 }
michael@0 158
michael@0 159 nsresult nsMenuBarX::InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex)
michael@0 160 {
michael@0 161 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 162
michael@0 163 // If we haven't created a global Application menu yet, do it.
michael@0 164 if (!sApplicationMenu) {
michael@0 165 nsresult rv = NS_OK; // avoid warning about rv being unused
michael@0 166 rv = CreateApplicationMenu(aMenu);
michael@0 167 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't create Application menu");
michael@0 168
michael@0 169 // Hook the new Application menu up to the menu bar.
michael@0 170 NSMenu* mainMenu = [NSApp mainMenu];
michael@0 171 NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
michael@0 172 [[mainMenu itemAtIndex:0] setSubmenu:sApplicationMenu];
michael@0 173 }
michael@0 174
michael@0 175 // add menu to array that owns our menus
michael@0 176 mMenuArray.InsertElementAt(aIndex, aMenu);
michael@0 177
michael@0 178 // hook up submenus
michael@0 179 nsIContent* menuContent = aMenu->Content();
michael@0 180 if (menuContent->GetChildCount() > 0 &&
michael@0 181 !nsMenuUtilsX::NodeIsHiddenOrCollapsed(menuContent)) {
michael@0 182 int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(this, aMenu);
michael@0 183 if (MenuContainsAppMenu())
michael@0 184 insertionIndex++;
michael@0 185 [mNativeMenu insertItem:aMenu->NativeMenuItem() atIndex:insertionIndex];
michael@0 186 }
michael@0 187
michael@0 188 return NS_OK;
michael@0 189
michael@0 190 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 191 }
michael@0 192
michael@0 193 void nsMenuBarX::RemoveMenuAtIndex(uint32_t aIndex)
michael@0 194 {
michael@0 195 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 196
michael@0 197 NS_ASSERTION(aIndex < mMenuArray.Length(), "Attempting submenu removal with bad index!");
michael@0 198
michael@0 199 // Our native menu and our internal menu object array might be out of sync.
michael@0 200 // This happens, for example, when a submenu is hidden. Because of this we
michael@0 201 // should not assume that a native submenu is hooked up.
michael@0 202 NSMenuItem* nativeMenuItem = mMenuArray[aIndex]->NativeMenuItem();
michael@0 203 int nativeMenuItemIndex = [mNativeMenu indexOfItem:nativeMenuItem];
michael@0 204 if (nativeMenuItemIndex != -1)
michael@0 205 [mNativeMenu removeItemAtIndex:nativeMenuItemIndex];
michael@0 206
michael@0 207 mMenuArray.RemoveElementAt(aIndex);
michael@0 208
michael@0 209 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 210 }
michael@0 211
michael@0 212 void nsMenuBarX::ObserveAttributeChanged(nsIDocument* aDocument,
michael@0 213 nsIContent* aContent,
michael@0 214 nsIAtom* aAttribute)
michael@0 215 {
michael@0 216 }
michael@0 217
michael@0 218 void nsMenuBarX::ObserveContentRemoved(nsIDocument* aDocument,
michael@0 219 nsIContent* aChild,
michael@0 220 int32_t aIndexInContainer)
michael@0 221 {
michael@0 222 RemoveMenuAtIndex(aIndexInContainer);
michael@0 223 }
michael@0 224
michael@0 225 void nsMenuBarX::ObserveContentInserted(nsIDocument* aDocument,
michael@0 226 nsIContent* aContainer,
michael@0 227 nsIContent* aChild)
michael@0 228 {
michael@0 229 nsMenuX* newMenu = new nsMenuX();
michael@0 230 if (newMenu) {
michael@0 231 nsresult rv = newMenu->Create(this, this, aChild);
michael@0 232 if (NS_SUCCEEDED(rv))
michael@0 233 InsertMenuAtIndex(newMenu, aContainer->IndexOf(aChild));
michael@0 234 else
michael@0 235 delete newMenu;
michael@0 236 }
michael@0 237 }
michael@0 238
michael@0 239 void nsMenuBarX::ForceUpdateNativeMenuAt(const nsAString& indexString)
michael@0 240 {
michael@0 241 NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
michael@0 242 length:indexString.Length()];
michael@0 243 NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
michael@0 244 unsigned int indexCount = [indexes count];
michael@0 245 if (indexCount == 0)
michael@0 246 return;
michael@0 247
michael@0 248 nsMenuX* currentMenu = NULL;
michael@0 249 int targetIndex = [[indexes objectAtIndex:0] intValue];
michael@0 250 int visible = 0;
michael@0 251 uint32_t length = mMenuArray.Length();
michael@0 252 // first find a menu in the menu bar
michael@0 253 for (unsigned int i = 0; i < length; i++) {
michael@0 254 nsMenuX* menu = mMenuArray[i];
michael@0 255 if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) {
michael@0 256 visible++;
michael@0 257 if (visible == (targetIndex + 1)) {
michael@0 258 currentMenu = menu;
michael@0 259 break;
michael@0 260 }
michael@0 261 }
michael@0 262 }
michael@0 263
michael@0 264 if (!currentMenu)
michael@0 265 return;
michael@0 266
michael@0 267 // fake open/close to cause lazy update to happen so submenus populate
michael@0 268 currentMenu->MenuOpened();
michael@0 269 currentMenu->MenuClosed();
michael@0 270
michael@0 271 // now find the correct submenu
michael@0 272 for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
michael@0 273 targetIndex = [[indexes objectAtIndex:i] intValue];
michael@0 274 visible = 0;
michael@0 275 length = currentMenu->GetItemCount();
michael@0 276 for (unsigned int j = 0; j < length; j++) {
michael@0 277 nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
michael@0 278 if (!targetMenu)
michael@0 279 return;
michael@0 280 if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
michael@0 281 visible++;
michael@0 282 if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
michael@0 283 currentMenu = static_cast<nsMenuX*>(targetMenu);
michael@0 284 // fake open/close to cause lazy update to happen
michael@0 285 currentMenu->MenuOpened();
michael@0 286 currentMenu->MenuClosed();
michael@0 287 break;
michael@0 288 }
michael@0 289 }
michael@0 290 }
michael@0 291 }
michael@0 292 }
michael@0 293
michael@0 294 // Calling this forces a full reload of the menu system, reloading all native
michael@0 295 // menus and their items.
michael@0 296 // Without this testing is hard because changes to the DOM affect the native
michael@0 297 // menu system lazily.
michael@0 298 void nsMenuBarX::ForceNativeMenuReload()
michael@0 299 {
michael@0 300 // tear down everything
michael@0 301 while (GetMenuCount() > 0)
michael@0 302 RemoveMenuAtIndex(0);
michael@0 303
michael@0 304 // construct everything
michael@0 305 ConstructNativeMenus();
michael@0 306 }
michael@0 307
michael@0 308 nsMenuX* nsMenuBarX::GetMenuAt(uint32_t aIndex)
michael@0 309 {
michael@0 310 if (mMenuArray.Length() <= aIndex) {
michael@0 311 NS_ERROR("Requesting menu at invalid index!");
michael@0 312 return NULL;
michael@0 313 }
michael@0 314 return mMenuArray[aIndex];
michael@0 315 }
michael@0 316
michael@0 317 nsMenuX* nsMenuBarX::GetXULHelpMenu()
michael@0 318 {
michael@0 319 // The Help menu is usually (always?) the last one, so we start there and
michael@0 320 // count back.
michael@0 321 for (int32_t i = GetMenuCount() - 1; i >= 0; --i) {
michael@0 322 nsMenuX* aMenu = GetMenuAt(i);
michael@0 323 if (aMenu && nsMenuX::IsXULHelpMenu(aMenu->Content()))
michael@0 324 return aMenu;
michael@0 325 }
michael@0 326 return nil;
michael@0 327 }
michael@0 328
michael@0 329 // On SnowLeopard and later we must tell the OS which is our Help menu.
michael@0 330 // Otherwise it will only add Spotlight for Help (the Search item) to our
michael@0 331 // Help menu if its label/title is "Help" -- i.e. if the menu is in English.
michael@0 332 // This resolves bugs 489196 and 539317.
michael@0 333 void nsMenuBarX::SetSystemHelpMenu()
michael@0 334 {
michael@0 335 nsMenuX* xulHelpMenu = GetXULHelpMenu();
michael@0 336 if (xulHelpMenu) {
michael@0 337 NSMenu* helpMenu = (NSMenu*)xulHelpMenu->NativeData();
michael@0 338 if (helpMenu)
michael@0 339 [NSApp setHelpMenu:helpMenu];
michael@0 340 }
michael@0 341 }
michael@0 342
michael@0 343 nsresult nsMenuBarX::Paint(bool aDelayed)
michael@0 344 {
michael@0 345 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 346
michael@0 347 if (!aDelayed && mAwaitingDelayedPaint) {
michael@0 348 return NS_OK;
michael@0 349 }
michael@0 350 mAwaitingDelayedPaint = false;
michael@0 351
michael@0 352 // Don't try to optimize anything in this painting by checking
michael@0 353 // sLastGeckoMenuBarPainted because the menubar can be manipulated by
michael@0 354 // native dialogs and sheet code and other things besides this paint method.
michael@0 355
michael@0 356 // We have to keep the same menu item for the Application menu so we keep
michael@0 357 // passing it along.
michael@0 358 NSMenu* outgoingMenu = [NSApp mainMenu];
michael@0 359 NS_ASSERTION([outgoingMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
michael@0 360
michael@0 361 // To work around bug 722676, we sometimes need to delay making mNativeMenu
michael@0 362 // the main menu. This is an Apple bug that sometimes causes a top-level
michael@0 363 // menu item to remain highlighted after pressing a Cmd+key combination that
michael@0 364 // opens a new window, then closing the window. The OS temporarily
michael@0 365 // highlights the appropriate top-level menu item whenever you press the
michael@0 366 // Cmd+key combination for one of its submenus. (It does this by setting a
michael@0 367 // "pressed" attribute on it.) The OS then uses a timer to remove this
michael@0 368 // "pressed" attribute. But without our workaround we sometimes change the
michael@0 369 // main menu before the timer has fired, so when it fires the menu item it
michael@0 370 // was intended to unhighlight is no longer present in the main menu. This
michael@0 371 // causes the item to remain semi-permanently highlighted (until you quit
michael@0 372 // Firefox or navigate the main menu by hand).
michael@0 373 if ((outgoingMenu != mNativeMenu) &&
michael@0 374 [outgoingMenu isKindOfClass:[GeckoNSMenu class]]) {
michael@0 375 if (aDelayed) {
michael@0 376 [(GeckoNSMenu *)outgoingMenu setDelayResignMainMenu:false];
michael@0 377 } else if ([(GeckoNSMenu *)outgoingMenu delayResignMainMenu]) {
michael@0 378 PaintMenuBarAfterDelay();
michael@0 379 return NS_OK;
michael@0 380 }
michael@0 381 }
michael@0 382
michael@0 383 if (outgoingMenu != mNativeMenu) {
michael@0 384 NSMenuItem* appMenuItem = [[outgoingMenu itemAtIndex:0] retain];
michael@0 385 [outgoingMenu removeItemAtIndex:0];
michael@0 386 [mNativeMenu insertItem:appMenuItem atIndex:0];
michael@0 387 [appMenuItem release];
michael@0 388 // Set menu bar and event target.
michael@0 389 [NSApp setMainMenu:mNativeMenu];
michael@0 390 }
michael@0 391 SetSystemHelpMenu();
michael@0 392 nsMenuBarX::sLastGeckoMenuBarPainted = this;
michael@0 393
michael@0 394 gSomeMenuBarPainted = YES;
michael@0 395
michael@0 396 return NS_OK;
michael@0 397
michael@0 398 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 399 }
michael@0 400
michael@0 401 // Used to delay a call to nsMenuBarX::Paint(). Needed to work around
michael@0 402 // bug 722676.
michael@0 403 void nsMenuBarX::PaintMenuBarAfterDelay()
michael@0 404 {
michael@0 405 mAwaitingDelayedPaint = true;
michael@0 406 nsMenuBarX::sCurrentPaintDelayedMenuBar = this;
michael@0 407 [mNativeMenu retain];
michael@0 408 // The delay for Apple's unhighlight timer is 0.1f, so we make ours a bit longer.
michael@0 409 [mNativeMenu performSelector:@selector(delayedPaintMenuBar:)
michael@0 410 withObject:nil
michael@0 411 afterDelay:0.15f];
michael@0 412 }
michael@0 413
michael@0 414 // Returns the 'key' attribute of the 'shortcutID' object (if any) in the
michael@0 415 // currently active menubar's DOM document. 'shortcutID' should be the id
michael@0 416 // (i.e. the name) of a component that defines a commonly used (and
michael@0 417 // localized) cmd+key shortcut, and belongs to a keyset containing similar
michael@0 418 // objects. For example "key_selectAll". Returns a value that can be
michael@0 419 // compared to the first character of [NSEvent charactersIgnoringModifiers]
michael@0 420 // when [NSEvent modifierFlags] == NSCommandKeyMask.
michael@0 421 char nsMenuBarX::GetLocalizedAccelKey(const char *shortcutID)
michael@0 422 {
michael@0 423 if (!sLastGeckoMenuBarPainted)
michael@0 424 return 0;
michael@0 425
michael@0 426 nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(sLastGeckoMenuBarPainted->mDocument));
michael@0 427 if (!domDoc)
michael@0 428 return 0;
michael@0 429
michael@0 430 NS_ConvertASCIItoUTF16 shortcutIDStr((const char *)shortcutID);
michael@0 431 nsCOMPtr<nsIDOMElement> shortcutElement;
michael@0 432 domDoc->GetElementById(shortcutIDStr, getter_AddRefs(shortcutElement));
michael@0 433 nsCOMPtr<nsIContent> shortcutContent = do_QueryInterface(shortcutElement);
michael@0 434 if (!shortcutContent)
michael@0 435 return 0;
michael@0 436
michael@0 437 nsAutoString key;
michael@0 438 shortcutContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key);
michael@0 439 NS_LossyConvertUTF16toASCII keyASC(key.get());
michael@0 440 const char *keyASCPtr = keyASC.get();
michael@0 441 if (!keyASCPtr)
michael@0 442 return 0;
michael@0 443 // If keyID's 'key' attribute isn't exactly one character long, it's not
michael@0 444 // what we're looking for.
michael@0 445 if (strlen(keyASCPtr) != sizeof(char))
michael@0 446 return 0;
michael@0 447 // Make sure retval is lower case.
michael@0 448 char retval = tolower(keyASCPtr[0]);
michael@0 449
michael@0 450 return retval;
michael@0 451 }
michael@0 452
michael@0 453 // Hide the item in the menu by setting the 'hidden' attribute. Returns it in |outHiddenNode| so
michael@0 454 // the caller can hang onto it if they so choose. It is acceptable to pass nsull
michael@0 455 // for |outHiddenNode| if the caller doesn't care about the hidden node.
michael@0 456 void nsMenuBarX::HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode)
michael@0 457 {
michael@0 458 nsCOMPtr<nsIDOMElement> menuItem;
michael@0 459 inDoc->GetElementById(inID, getter_AddRefs(menuItem));
michael@0 460 nsCOMPtr<nsIContent> menuContent(do_QueryInterface(menuItem));
michael@0 461 if (menuContent) {
michael@0 462 menuContent->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, NS_LITERAL_STRING("true"), false);
michael@0 463 if (outHiddenNode) {
michael@0 464 *outHiddenNode = menuContent.get();
michael@0 465 NS_IF_ADDREF(*outHiddenNode);
michael@0 466 }
michael@0 467 }
michael@0 468 }
michael@0 469
michael@0 470 // Do what is necessary to conform to the Aqua guidelines for menus.
michael@0 471 void nsMenuBarX::AquifyMenuBar()
michael@0 472 {
michael@0 473 nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mContent->GetDocument()));
michael@0 474 if (domDoc) {
michael@0 475 // remove the "About..." item and its separator
michael@0 476 HideItem(domDoc, NS_LITERAL_STRING("aboutSeparator"), nullptr);
michael@0 477 HideItem(domDoc, NS_LITERAL_STRING("aboutName"), getter_AddRefs(mAboutItemContent));
michael@0 478 if (!sAboutItemContent)
michael@0 479 sAboutItemContent = mAboutItemContent;
michael@0 480
michael@0 481 // Hide the software update menu item, since it belongs in the application
michael@0 482 // menu on Mac OS X.
michael@0 483 HideItem(domDoc, NS_LITERAL_STRING("updateSeparator"), nullptr);
michael@0 484 HideItem(domDoc, NS_LITERAL_STRING("checkForUpdates"), getter_AddRefs(mUpdateItemContent));
michael@0 485 if (!sUpdateItemContent)
michael@0 486 sUpdateItemContent = mUpdateItemContent;
michael@0 487
michael@0 488 // remove quit item and its separator
michael@0 489 HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitSeparator"), nullptr);
michael@0 490 HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitItem"), getter_AddRefs(mQuitItemContent));
michael@0 491 if (!sQuitItemContent)
michael@0 492 sQuitItemContent = mQuitItemContent;
michael@0 493
michael@0 494 // remove prefs item and its separator, but save off the pref content node
michael@0 495 // so we can invoke its command later.
michael@0 496 HideItem(domDoc, NS_LITERAL_STRING("menu_PrefsSeparator"), nullptr);
michael@0 497 HideItem(domDoc, NS_LITERAL_STRING("menu_preferences"), getter_AddRefs(mPrefItemContent));
michael@0 498 if (!sPrefItemContent)
michael@0 499 sPrefItemContent = mPrefItemContent;
michael@0 500
michael@0 501 // hide items that we use for the Application menu
michael@0 502 HideItem(domDoc, NS_LITERAL_STRING("menu_mac_services"), nullptr);
michael@0 503 HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_app"), nullptr);
michael@0 504 HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_others"), nullptr);
michael@0 505 HideItem(domDoc, NS_LITERAL_STRING("menu_mac_show_all"), nullptr);
michael@0 506 }
michael@0 507 }
michael@0 508
michael@0 509 // for creating menu items destined for the Application menu
michael@0 510 NSMenuItem* nsMenuBarX::CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action,
michael@0 511 int tag, NativeMenuItemTarget* target)
michael@0 512 {
michael@0 513 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
michael@0 514
michael@0 515 nsCOMPtr<nsIDocument> doc = inMenu->Content()->GetDocument();
michael@0 516 if (!doc)
michael@0 517 return nil;
michael@0 518
michael@0 519 nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(doc));
michael@0 520 if (!domdoc)
michael@0 521 return nil;
michael@0 522
michael@0 523 // Get information from the gecko menu item
michael@0 524 nsAutoString label;
michael@0 525 nsAutoString modifiers;
michael@0 526 nsAutoString key;
michael@0 527 nsCOMPtr<nsIDOMElement> menuItem;
michael@0 528 domdoc->GetElementById(nodeID, getter_AddRefs(menuItem));
michael@0 529 if (menuItem) {
michael@0 530 menuItem->GetAttribute(NS_LITERAL_STRING("label"), label);
michael@0 531 menuItem->GetAttribute(NS_LITERAL_STRING("modifiers"), modifiers);
michael@0 532 menuItem->GetAttribute(NS_LITERAL_STRING("key"), key);
michael@0 533 }
michael@0 534 else {
michael@0 535 return nil;
michael@0 536 }
michael@0 537
michael@0 538 // Get more information about the key equivalent. Start by
michael@0 539 // finding the key node we need.
michael@0 540 NSString* keyEquiv = nil;
michael@0 541 unsigned int macKeyModifiers = 0;
michael@0 542 if (!key.IsEmpty()) {
michael@0 543 nsCOMPtr<nsIDOMElement> keyElement;
michael@0 544 domdoc->GetElementById(key, getter_AddRefs(keyElement));
michael@0 545 if (keyElement) {
michael@0 546 nsCOMPtr<nsIContent> keyContent (do_QueryInterface(keyElement));
michael@0 547 // first grab the key equivalent character
michael@0 548 nsAutoString keyChar(NS_LITERAL_STRING(" "));
michael@0 549 keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar);
michael@0 550 if (!keyChar.EqualsLiteral(" ")) {
michael@0 551 keyEquiv = [[NSString stringWithCharacters:reinterpret_cast<const unichar*>(keyChar.get())
michael@0 552 length:keyChar.Length()] lowercaseString];
michael@0 553 }
michael@0 554 // now grab the key equivalent modifiers
michael@0 555 nsAutoString modifiersStr;
michael@0 556 keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
michael@0 557 uint8_t geckoModifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
michael@0 558 macKeyModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(geckoModifiers);
michael@0 559 }
michael@0 560 }
michael@0 561 // get the label into NSString-form
michael@0 562 NSString* labelString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(label.get())
michael@0 563 length:label.Length()];
michael@0 564
michael@0 565 if (!labelString)
michael@0 566 labelString = @"";
michael@0 567 if (!keyEquiv)
michael@0 568 keyEquiv = @"";
michael@0 569
michael@0 570 // put together the actual NSMenuItem
michael@0 571 NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:labelString action:action keyEquivalent:keyEquiv];
michael@0 572
michael@0 573 [newMenuItem setTag:tag];
michael@0 574 [newMenuItem setTarget:target];
michael@0 575 [newMenuItem setKeyEquivalentModifierMask:macKeyModifiers];
michael@0 576
michael@0 577 MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:this];
michael@0 578 [newMenuItem setRepresentedObject:info];
michael@0 579 [info release];
michael@0 580
michael@0 581 return newMenuItem;
michael@0 582
michael@0 583 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
michael@0 584 }
michael@0 585
michael@0 586 // build the Application menu shared by all menu bars
michael@0 587 nsresult nsMenuBarX::CreateApplicationMenu(nsMenuX* inMenu)
michael@0 588 {
michael@0 589 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
michael@0 590
michael@0 591 // At this point, the application menu is the application menu from
michael@0 592 // the nib in cocoa widgets. We do not have a way to create an application
michael@0 593 // menu manually, so we grab the one from the nib and use that.
michael@0 594 sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
michael@0 595
michael@0 596 /*
michael@0 597 We support the following menu items here:
michael@0 598
michael@0 599 Menu Item DOM Node ID Notes
michael@0 600
michael@0 601 ========================
michael@0 602 = About This App = <- aboutName
michael@0 603 = Check for Updates... = <- checkForUpdates
michael@0 604 ========================
michael@0 605 = Preferences... = <- menu_preferences
michael@0 606 ========================
michael@0 607 = Services > = <- menu_mac_services <- (do not define key equivalent)
michael@0 608 ========================
michael@0 609 = Hide App = <- menu_mac_hide_app
michael@0 610 = Hide Others = <- menu_mac_hide_others
michael@0 611 = Show All = <- menu_mac_show_all
michael@0 612 ========================
michael@0 613 = Quit = <- menu_FileQuitItem
michael@0 614 ========================
michael@0 615
michael@0 616 If any of them are ommitted from the application's DOM, we just don't add
michael@0 617 them. We always add a "Quit" item, but if an app developer does not provide a
michael@0 618 DOM node with the right ID for the Quit item, we add it in English. App
michael@0 619 developers need only add each node with a label and a key equivalent (if they
michael@0 620 want one). Other attributes are optional. Like so:
michael@0 621
michael@0 622 <menuitem id="menu_preferences"
michael@0 623 label="&preferencesCmdMac.label;"
michael@0 624 key="open_prefs_key"/>
michael@0 625
michael@0 626 We need to use this system for localization purposes, until we have a better way
michael@0 627 to define the Application menu to be used on Mac OS X.
michael@0 628 */
michael@0 629
michael@0 630 if (sApplicationMenu) {
michael@0 631 // This code reads attributes we are going to care about from the DOM elements
michael@0 632
michael@0 633 NSMenuItem *itemBeingAdded = nil;
michael@0 634 BOOL addAboutSeparator = FALSE;
michael@0 635
michael@0 636 // Add the About menu item
michael@0 637 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("aboutName"), @selector(menuItemHit:),
michael@0 638 eCommand_ID_About, nsMenuBarX::sNativeEventTarget);
michael@0 639 if (itemBeingAdded) {
michael@0 640 [sApplicationMenu addItem:itemBeingAdded];
michael@0 641 [itemBeingAdded release];
michael@0 642 itemBeingAdded = nil;
michael@0 643
michael@0 644 addAboutSeparator = TRUE;
michael@0 645 }
michael@0 646
michael@0 647 // Add the software update menu item
michael@0 648 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("checkForUpdates"), @selector(menuItemHit:),
michael@0 649 eCommand_ID_Update, nsMenuBarX::sNativeEventTarget);
michael@0 650 if (itemBeingAdded) {
michael@0 651 [sApplicationMenu addItem:itemBeingAdded];
michael@0 652 [itemBeingAdded release];
michael@0 653 itemBeingAdded = nil;
michael@0 654
michael@0 655 addAboutSeparator = TRUE;
michael@0 656 }
michael@0 657
michael@0 658 // Add separator if either the About item or software update item exists
michael@0 659 if (addAboutSeparator)
michael@0 660 [sApplicationMenu addItem:[NSMenuItem separatorItem]];
michael@0 661
michael@0 662 // Add the Preferences menu item
michael@0 663 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_preferences"), @selector(menuItemHit:),
michael@0 664 eCommand_ID_Prefs, nsMenuBarX::sNativeEventTarget);
michael@0 665 if (itemBeingAdded) {
michael@0 666 [sApplicationMenu addItem:itemBeingAdded];
michael@0 667 [itemBeingAdded release];
michael@0 668 itemBeingAdded = nil;
michael@0 669
michael@0 670 // Add separator after Preferences menu
michael@0 671 [sApplicationMenu addItem:[NSMenuItem separatorItem]];
michael@0 672 }
michael@0 673
michael@0 674 // Add Services menu item
michael@0 675 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_services"), nil,
michael@0 676 0, nil);
michael@0 677 if (itemBeingAdded) {
michael@0 678 [sApplicationMenu addItem:itemBeingAdded];
michael@0 679
michael@0 680 // set this menu item up as the Mac OS X Services menu
michael@0 681 NSMenu* servicesMenu = [[GeckoServicesNSMenu alloc] initWithTitle:@""];
michael@0 682 [itemBeingAdded setSubmenu:servicesMenu];
michael@0 683 [NSApp setServicesMenu:servicesMenu];
michael@0 684
michael@0 685 [itemBeingAdded release];
michael@0 686 itemBeingAdded = nil;
michael@0 687
michael@0 688 // Add separator after Services menu
michael@0 689 [sApplicationMenu addItem:[NSMenuItem separatorItem]];
michael@0 690 }
michael@0 691
michael@0 692 BOOL addHideShowSeparator = FALSE;
michael@0 693
michael@0 694 // Add menu item to hide this application
michael@0 695 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_app"), @selector(menuItemHit:),
michael@0 696 eCommand_ID_HideApp, nsMenuBarX::sNativeEventTarget);
michael@0 697 if (itemBeingAdded) {
michael@0 698 [sApplicationMenu addItem:itemBeingAdded];
michael@0 699 [itemBeingAdded release];
michael@0 700 itemBeingAdded = nil;
michael@0 701
michael@0 702 addHideShowSeparator = TRUE;
michael@0 703 }
michael@0 704
michael@0 705 // Add menu item to hide other applications
michael@0 706 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_others"), @selector(menuItemHit:),
michael@0 707 eCommand_ID_HideOthers, nsMenuBarX::sNativeEventTarget);
michael@0 708 if (itemBeingAdded) {
michael@0 709 [sApplicationMenu addItem:itemBeingAdded];
michael@0 710 [itemBeingAdded release];
michael@0 711 itemBeingAdded = nil;
michael@0 712
michael@0 713 addHideShowSeparator = TRUE;
michael@0 714 }
michael@0 715
michael@0 716 // Add menu item to show all applications
michael@0 717 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_show_all"), @selector(menuItemHit:),
michael@0 718 eCommand_ID_ShowAll, nsMenuBarX::sNativeEventTarget);
michael@0 719 if (itemBeingAdded) {
michael@0 720 [sApplicationMenu addItem:itemBeingAdded];
michael@0 721 [itemBeingAdded release];
michael@0 722 itemBeingAdded = nil;
michael@0 723
michael@0 724 addHideShowSeparator = TRUE;
michael@0 725 }
michael@0 726
michael@0 727 // Add a separator after the hide/show menus if at least one exists
michael@0 728 if (addHideShowSeparator)
michael@0 729 [sApplicationMenu addItem:[NSMenuItem separatorItem]];
michael@0 730
michael@0 731 // Add quit menu item
michael@0 732 itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_FileQuitItem"), @selector(menuItemHit:),
michael@0 733 eCommand_ID_Quit, nsMenuBarX::sNativeEventTarget);
michael@0 734 if (itemBeingAdded) {
michael@0 735 [sApplicationMenu addItem:itemBeingAdded];
michael@0 736 [itemBeingAdded release];
michael@0 737 itemBeingAdded = nil;
michael@0 738 }
michael@0 739 else {
michael@0 740 // the current application does not have a DOM node for "Quit". Add one
michael@0 741 // anyway, in English.
michael@0 742 NSMenuItem* defaultQuitItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(menuItemHit:)
michael@0 743 keyEquivalent:@"q"] autorelease];
michael@0 744 [defaultQuitItem setTarget:nsMenuBarX::sNativeEventTarget];
michael@0 745 [defaultQuitItem setTag:eCommand_ID_Quit];
michael@0 746 [sApplicationMenu addItem:defaultQuitItem];
michael@0 747 }
michael@0 748 }
michael@0 749
michael@0 750 return (sApplicationMenu) ? NS_OK : NS_ERROR_FAILURE;
michael@0 751
michael@0 752 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
michael@0 753 }
michael@0 754
michael@0 755 void nsMenuBarX::SetParent(nsIWidget* aParent)
michael@0 756 {
michael@0 757 mParentWindow = aParent;
michael@0 758 }
michael@0 759
michael@0 760
michael@0 761 //
michael@0 762 // Objective-C class used to allow us to have keyboard commands
michael@0 763 // look like they are doing something but actually do nothing.
michael@0 764 // We allow mouse actions to work normally.
michael@0 765 //
michael@0 766
michael@0 767 // Controls whether or not native menu items should invoke their commands.
michael@0 768 static BOOL gMenuItemsExecuteCommands = YES;
michael@0 769
michael@0 770 @implementation GeckoNSMenu
michael@0 771
michael@0 772 - (id)initWithTitle:(NSString *)aTitle
michael@0 773 {
michael@0 774 if (self = [super initWithTitle:aTitle]) {
michael@0 775 mMenuBarOwner = nullptr;
michael@0 776 mDelayResignMainMenu = false;
michael@0 777 }
michael@0 778 return self;
michael@0 779 }
michael@0 780
michael@0 781 - (id)initWithTitle:(NSString *)aTitle andMenuBarOwner:(nsMenuBarX *)aMenuBarOwner
michael@0 782 {
michael@0 783 if (self = [super initWithTitle:aTitle]) {
michael@0 784 mMenuBarOwner = aMenuBarOwner;
michael@0 785 mDelayResignMainMenu = false;
michael@0 786 }
michael@0 787 return self;
michael@0 788 }
michael@0 789
michael@0 790 - (void)resetMenuBarOwner
michael@0 791 {
michael@0 792 mMenuBarOwner = nil;
michael@0 793 }
michael@0 794
michael@0 795 - (bool)delayResignMainMenu
michael@0 796 {
michael@0 797 return mDelayResignMainMenu;
michael@0 798 }
michael@0 799
michael@0 800 - (void)setDelayResignMainMenu:(bool)aShouldDelay
michael@0 801 {
michael@0 802 mDelayResignMainMenu = aShouldDelay;
michael@0 803 }
michael@0 804
michael@0 805 // Used to delay a call to nsMenuBarX::Paint(). Needed to work around
michael@0 806 // bug 722676.
michael@0 807 - (void)delayedPaintMenuBar:(id)unused
michael@0 808 {
michael@0 809 if (mMenuBarOwner) {
michael@0 810 if (mMenuBarOwner == nsMenuBarX::sCurrentPaintDelayedMenuBar) {
michael@0 811 mMenuBarOwner->Paint(true);
michael@0 812 nsMenuBarX::sCurrentPaintDelayedMenuBar = nullptr;
michael@0 813 } else {
michael@0 814 mMenuBarOwner->ResetAwaitingDelayedPaint();
michael@0 815 }
michael@0 816 }
michael@0 817 [self release];
michael@0 818 }
michael@0 819
michael@0 820 // Undocumented method, present unchanged since OS X 10.6, used to temporarily
michael@0 821 // highlight a top-level menu item when an appropriate Cmd+key combination is
michael@0 822 // pressed.
michael@0 823 - (void)_performActionWithHighlightingForItemAtIndex:(NSInteger)index;
michael@0 824 {
michael@0 825 NSMenu *mainMenu = [NSApp mainMenu];
michael@0 826 if ([mainMenu isKindOfClass:[GeckoNSMenu class]]) {
michael@0 827 [(GeckoNSMenu *)mainMenu setDelayResignMainMenu:true];
michael@0 828 }
michael@0 829 [super _performActionWithHighlightingForItemAtIndex:index];
michael@0 830 }
michael@0 831
michael@0 832 // Keyboard commands should not cause menu items to invoke their
michael@0 833 // commands when there is a key window because we'd rather send
michael@0 834 // the keyboard command to the window. We still have the menus
michael@0 835 // go through the mechanics so they'll give the proper visual
michael@0 836 // feedback.
michael@0 837 - (BOOL)performKeyEquivalent:(NSEvent *)theEvent
michael@0 838 {
michael@0 839 // We've noticed that Mac OS X expects this check in subclasses before
michael@0 840 // calling NSMenu's "performKeyEquivalent:".
michael@0 841 //
michael@0 842 // There is no case in which we'd need to do anything or return YES
michael@0 843 // when we have no items so we can just do this check first.
michael@0 844 if ([self numberOfItems] <= 0) {
michael@0 845 return NO;
michael@0 846 }
michael@0 847
michael@0 848 NSWindow *keyWindow = [NSApp keyWindow];
michael@0 849
michael@0 850 // If there is no key window then just behave normally. This
michael@0 851 // probably means that this menu is associated with Gecko's
michael@0 852 // hidden window.
michael@0 853 if (!keyWindow) {
michael@0 854 return [super performKeyEquivalent:theEvent];
michael@0 855 }
michael@0 856
michael@0 857 // Plugins normally eat all keyboard commands, this hack mitigates
michael@0 858 // the problem.
michael@0 859 BOOL handleForPluginHack = NO;
michael@0 860 NSResponder *firstResponder = [keyWindow firstResponder];
michael@0 861 if (firstResponder &&
michael@0 862 [firstResponder isKindOfClass:[ChildView class]] &&
michael@0 863 [(ChildView*)firstResponder isPluginView]) {
michael@0 864 handleForPluginHack = YES;
michael@0 865 // Maintain a list of cmd+key combinations that we never act on (in the
michael@0 866 // browser) when the keyboard focus is in a plugin. What a particular
michael@0 867 // cmd+key combo means here (to the browser) is governed by browser.dtd,
michael@0 868 // which "contains the browser main menu items".
michael@0 869 UInt32 modifierFlags = [theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
michael@0 870 if (modifierFlags == NSCommandKeyMask) {
michael@0 871 NSString *unmodchars = [theEvent charactersIgnoringModifiers];
michael@0 872 if ([unmodchars length] == 1) {
michael@0 873 if ([unmodchars characterAtIndex:0] == nsMenuBarX::GetLocalizedAccelKey("key_selectAll")) {
michael@0 874 handleForPluginHack = NO;
michael@0 875 }
michael@0 876 }
michael@0 877 }
michael@0 878 }
michael@0 879
michael@0 880 gMenuItemsExecuteCommands = handleForPluginHack;
michael@0 881 [super performKeyEquivalent:theEvent];
michael@0 882 gMenuItemsExecuteCommands = YES; // return to default
michael@0 883
michael@0 884 // Return YES if we invoked a command and there is now no key window or we changed
michael@0 885 // the first responder. In this case we do not want to propagate the event because
michael@0 886 // we don't want it handled again.
michael@0 887 if (handleForPluginHack) {
michael@0 888 if (![NSApp keyWindow] || [[NSApp keyWindow] firstResponder] != firstResponder) {
michael@0 889 return YES;
michael@0 890 }
michael@0 891 }
michael@0 892
michael@0 893 // Return NO so that we can handle the event via NSView's "keyDown:".
michael@0 894 return NO;
michael@0 895 }
michael@0 896
michael@0 897 @end
michael@0 898
michael@0 899 //
michael@0 900 // Objective-C class used as action target for menu items
michael@0 901 //
michael@0 902
michael@0 903 @implementation NativeMenuItemTarget
michael@0 904
michael@0 905 // called when some menu item in this menu gets hit
michael@0 906 -(IBAction)menuItemHit:(id)sender
michael@0 907 {
michael@0 908 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 909
michael@0 910 // menuGroupOwner below is an nsMenuBarX object, which we sometimes access
michael@0 911 // after it's been deleted, causing crashes (see bug 704866 and bug 670914).
michael@0 912 // To fix this "correctly", in nsMenuBarX::~nsMenuBarX() we'd need to
michael@0 913 // iterate through every NSMenuItem in nsMenuBarX::mNativeMenu and its
michael@0 914 // submenus, which might be quite time consuming. (For every NSMenuItem
michael@0 915 // that has a "representedObject" that's a MenuItemInfo object, we'd need
michael@0 916 // need to null out its "menuGroupOwner" if it's the same as the nsMenuBarX
michael@0 917 // object being destroyed.) But if the nsMenuBarX object being destroyed
michael@0 918 // corresponds to the currently focused window, it's likely that the
michael@0 919 // nsMenuBarX destructor will null out sLastGeckoMenuBarPainted. So we can
michael@0 920 // probably eliminate most of these crashes if we use this variable being
michael@0 921 // null as an indicator that we're likely to crash below when we dereference
michael@0 922 // menuGroupOwner.
michael@0 923 if (!nsMenuBarX::sLastGeckoMenuBarPainted) {
michael@0 924 return;
michael@0 925 }
michael@0 926
michael@0 927 if (!gMenuItemsExecuteCommands) {
michael@0 928 return;
michael@0 929 }
michael@0 930
michael@0 931 int tag = [sender tag];
michael@0 932
michael@0 933 MenuItemInfo* info = [sender representedObject];
michael@0 934 if (!info)
michael@0 935 return;
michael@0 936
michael@0 937 nsMenuGroupOwnerX* menuGroupOwner = [info menuGroupOwner];
michael@0 938 if (!menuGroupOwner)
michael@0 939 return;
michael@0 940
michael@0 941 nsMenuBarX* menuBar = nullptr;
michael@0 942 if (menuGroupOwner->MenuObjectType() == eMenuBarObjectType)
michael@0 943 menuBar = static_cast<nsMenuBarX*>(menuGroupOwner);
michael@0 944
michael@0 945 // Do special processing if this is for an app-global command.
michael@0 946 if (tag == eCommand_ID_About) {
michael@0 947 nsIContent* mostSpecificContent = sAboutItemContent;
michael@0 948 if (menuBar && menuBar->mAboutItemContent)
michael@0 949 mostSpecificContent = menuBar->mAboutItemContent;
michael@0 950 nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
michael@0 951 return;
michael@0 952 }
michael@0 953 else if (tag == eCommand_ID_Update) {
michael@0 954 nsIContent* mostSpecificContent = sUpdateItemContent;
michael@0 955 if (menuBar && menuBar->mUpdateItemContent)
michael@0 956 mostSpecificContent = menuBar->mUpdateItemContent;
michael@0 957 nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
michael@0 958 }
michael@0 959 else if (tag == eCommand_ID_Prefs) {
michael@0 960 nsIContent* mostSpecificContent = sPrefItemContent;
michael@0 961 if (menuBar && menuBar->mPrefItemContent)
michael@0 962 mostSpecificContent = menuBar->mPrefItemContent;
michael@0 963 nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
michael@0 964 return;
michael@0 965 }
michael@0 966 else if (tag == eCommand_ID_HideApp) {
michael@0 967 [NSApp hide:sender];
michael@0 968 return;
michael@0 969 }
michael@0 970 else if (tag == eCommand_ID_HideOthers) {
michael@0 971 [NSApp hideOtherApplications:sender];
michael@0 972 return;
michael@0 973 }
michael@0 974 else if (tag == eCommand_ID_ShowAll) {
michael@0 975 [NSApp unhideAllApplications:sender];
michael@0 976 return;
michael@0 977 }
michael@0 978 else if (tag == eCommand_ID_Quit) {
michael@0 979 nsIContent* mostSpecificContent = sQuitItemContent;
michael@0 980 if (menuBar && menuBar->mQuitItemContent)
michael@0 981 mostSpecificContent = menuBar->mQuitItemContent;
michael@0 982 // If we have some content for quit we execute it. Otherwise we send a native app terminate
michael@0 983 // message. If you want to stop a quit from happening, provide quit content and return
michael@0 984 // the event as unhandled.
michael@0 985 if (mostSpecificContent) {
michael@0 986 nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
michael@0 987 }
michael@0 988 else {
michael@0 989 [NSApp terminate:nil];
michael@0 990 }
michael@0 991 return;
michael@0 992 }
michael@0 993
michael@0 994 // given the commandID, look it up in our hashtable and dispatch to
michael@0 995 // that menu item.
michael@0 996 if (menuGroupOwner) {
michael@0 997 nsMenuItemX* menuItem = menuGroupOwner->GetMenuItemForCommandID(static_cast<uint32_t>(tag));
michael@0 998 if (menuItem)
michael@0 999 menuItem->DoCommand();
michael@0 1000 }
michael@0 1001
michael@0 1002 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 1003 }
michael@0 1004
michael@0 1005 @end
michael@0 1006
michael@0 1007 // Objective-C class used for menu items on the Services menu to allow Gecko
michael@0 1008 // to override their standard behavior in order to stop key equivalents from
michael@0 1009 // firing in certain instances. When gMenuItemsExecuteCommands is NO, we return
michael@0 1010 // a dummy target and action instead of the actual target and action.
michael@0 1011
michael@0 1012 @implementation GeckoServicesNSMenuItem
michael@0 1013
michael@0 1014 - (id) target
michael@0 1015 {
michael@0 1016 id realTarget = [super target];
michael@0 1017 if (gMenuItemsExecuteCommands)
michael@0 1018 return realTarget;
michael@0 1019 else
michael@0 1020 return realTarget ? self : nil;
michael@0 1021 }
michael@0 1022
michael@0 1023 - (SEL) action
michael@0 1024 {
michael@0 1025 SEL realAction = [super action];
michael@0 1026 if (gMenuItemsExecuteCommands)
michael@0 1027 return realAction;
michael@0 1028 else
michael@0 1029 return realAction ? @selector(_doNothing:) : NULL;
michael@0 1030 }
michael@0 1031
michael@0 1032 - (void) _doNothing:(id)sender
michael@0 1033 {
michael@0 1034 }
michael@0 1035
michael@0 1036 @end
michael@0 1037
michael@0 1038 // Objective-C class used as the Services menu so that Gecko can override the
michael@0 1039 // standard behavior of the Services menu in order to stop key equivalents
michael@0 1040 // from firing in certain instances.
michael@0 1041
michael@0 1042 @implementation GeckoServicesNSMenu
michael@0 1043
michael@0 1044 - (void)addItem:(NSMenuItem *)newItem
michael@0 1045 {
michael@0 1046 [self _overrideClassOfMenuItem:newItem];
michael@0 1047 [super addItem:newItem];
michael@0 1048 }
michael@0 1049
michael@0 1050 - (NSMenuItem *)addItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv
michael@0 1051 {
michael@0 1052 NSMenuItem * newItem = [super addItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv];
michael@0 1053 [self _overrideClassOfMenuItem:newItem];
michael@0 1054 return newItem;
michael@0 1055 }
michael@0 1056
michael@0 1057 - (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index
michael@0 1058 {
michael@0 1059 [self _overrideClassOfMenuItem:newItem];
michael@0 1060 [super insertItem:newItem atIndex:index];
michael@0 1061 }
michael@0 1062
michael@0 1063 - (NSMenuItem *)insertItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv atIndex:(NSInteger)index
michael@0 1064 {
michael@0 1065 NSMenuItem * newItem = [super insertItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv atIndex:index];
michael@0 1066 [self _overrideClassOfMenuItem:newItem];
michael@0 1067 return newItem;
michael@0 1068 }
michael@0 1069
michael@0 1070 - (void) _overrideClassOfMenuItem:(NSMenuItem *)menuItem
michael@0 1071 {
michael@0 1072 if ([menuItem class] == [NSMenuItem class])
michael@0 1073 object_setClass(menuItem, [GeckoServicesNSMenuItem class]);
michael@0 1074 }
michael@0 1075
michael@0 1076 @end

mercurial