1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/cocoa/nsMenuBarX.mm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1076 @@ 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 <objc/objc-runtime.h> 1.10 + 1.11 +#include "nsMenuBarX.h" 1.12 +#include "nsMenuX.h" 1.13 +#include "nsMenuItemX.h" 1.14 +#include "nsMenuUtilsX.h" 1.15 +#include "nsCocoaFeatures.h" 1.16 +#include "nsCocoaUtils.h" 1.17 +#include "nsCocoaWindow.h" 1.18 +#include "nsChildView.h" 1.19 + 1.20 +#include "nsCOMPtr.h" 1.21 +#include "nsString.h" 1.22 +#include "nsGkAtoms.h" 1.23 +#include "nsObjCExceptions.h" 1.24 +#include "nsThreadUtils.h" 1.25 + 1.26 +#include "nsIContent.h" 1.27 +#include "nsIWidget.h" 1.28 +#include "nsIDocument.h" 1.29 +#include "nsIDOMDocument.h" 1.30 +#include "nsIDOMElement.h" 1.31 + 1.32 +NativeMenuItemTarget* nsMenuBarX::sNativeEventTarget = nil; 1.33 +nsMenuBarX* nsMenuBarX::sLastGeckoMenuBarPainted = nullptr; // Weak 1.34 +nsMenuBarX* nsMenuBarX::sCurrentPaintDelayedMenuBar = nullptr; // Weak 1.35 +NSMenu* sApplicationMenu = nil; 1.36 +BOOL gSomeMenuBarPainted = NO; 1.37 + 1.38 +// We keep references to the first quit and pref item content nodes we find, which 1.39 +// will be from the hidden window. We use these when the document for the current 1.40 +// window does not have a quit or pref item. We don't need strong refs here because 1.41 +// these items are always strong ref'd by their owning menu bar (instance variable). 1.42 +static nsIContent* sAboutItemContent = nullptr; 1.43 +static nsIContent* sUpdateItemContent = nullptr; 1.44 +static nsIContent* sPrefItemContent = nullptr; 1.45 +static nsIContent* sQuitItemContent = nullptr; 1.46 + 1.47 +NS_IMPL_ISUPPORTS(nsNativeMenuServiceX, nsINativeMenuService) 1.48 + 1.49 +NS_IMETHODIMP nsNativeMenuServiceX::CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode) 1.50 +{ 1.51 + NS_ASSERTION(NS_IsMainThread(), "Attempting to create native menu bar on wrong thread!"); 1.52 + 1.53 + nsRefPtr<nsMenuBarX> mb = new nsMenuBarX(); 1.54 + if (!mb) 1.55 + return NS_ERROR_OUT_OF_MEMORY; 1.56 + 1.57 + return mb->Create(aParent, aMenuBarNode); 1.58 +} 1.59 + 1.60 +nsMenuBarX::nsMenuBarX() 1.61 +: nsMenuGroupOwnerX(), mParentWindow(nullptr), mAwaitingDelayedPaint(false) 1.62 +{ 1.63 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.64 + 1.65 + mNativeMenu = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar" andMenuBarOwner:this]; 1.66 + 1.67 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.68 +} 1.69 + 1.70 +nsMenuBarX::~nsMenuBarX() 1.71 +{ 1.72 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.73 + 1.74 + if (nsMenuBarX::sLastGeckoMenuBarPainted == this) 1.75 + nsMenuBarX::sLastGeckoMenuBarPainted = nullptr; 1.76 + 1.77 + // the quit/pref items of a random window might have been used if there was no 1.78 + // hidden window, thus we need to invalidate the weak references. 1.79 + if (sAboutItemContent == mAboutItemContent) 1.80 + sAboutItemContent = nullptr; 1.81 + if (sUpdateItemContent == mUpdateItemContent) 1.82 + sUpdateItemContent = nullptr; 1.83 + if (sQuitItemContent == mQuitItemContent) 1.84 + sQuitItemContent = nullptr; 1.85 + if (sPrefItemContent == mPrefItemContent) 1.86 + sPrefItemContent = nullptr; 1.87 + 1.88 + // make sure we unregister ourselves as a content observer 1.89 + UnregisterForContentChanges(mContent); 1.90 + 1.91 + // We have to manually clear the array here because clearing causes menu items 1.92 + // to call back into the menu bar to unregister themselves. We don't want to 1.93 + // depend on member variable ordering to ensure that the array gets cleared 1.94 + // before the registration hash table is destroyed. 1.95 + mMenuArray.Clear(); 1.96 + 1.97 + [mNativeMenu resetMenuBarOwner]; 1.98 + [mNativeMenu release]; 1.99 + 1.100 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.101 +} 1.102 + 1.103 +nsresult nsMenuBarX::Create(nsIWidget* aParent, nsIContent* aContent) 1.104 +{ 1.105 + if (!aParent || !aContent) 1.106 + return NS_ERROR_INVALID_ARG; 1.107 + 1.108 + mParentWindow = aParent; 1.109 + mContent = aContent; 1.110 + 1.111 + AquifyMenuBar(); 1.112 + 1.113 + nsresult rv = nsMenuGroupOwnerX::Create(aContent); 1.114 + if (NS_FAILED(rv)) 1.115 + return rv; 1.116 + 1.117 + RegisterForContentChanges(aContent, this); 1.118 + 1.119 + ConstructNativeMenus(); 1.120 + 1.121 + // Give this to the parent window. The parent takes ownership. 1.122 + static_cast<nsCocoaWindow*>(mParentWindow)->SetMenuBar(this); 1.123 + 1.124 + return NS_OK; 1.125 +} 1.126 + 1.127 +void nsMenuBarX::ConstructNativeMenus() 1.128 +{ 1.129 + uint32_t count = mContent->GetChildCount(); 1.130 + for (uint32_t i = 0; i < count; i++) { 1.131 + nsIContent *menuContent = mContent->GetChildAt(i); 1.132 + if (menuContent && 1.133 + menuContent->Tag() == nsGkAtoms::menu && 1.134 + menuContent->IsXUL()) { 1.135 + nsMenuX* newMenu = new nsMenuX(); 1.136 + if (newMenu) { 1.137 + nsresult rv = newMenu->Create(this, this, menuContent); 1.138 + if (NS_SUCCEEDED(rv)) 1.139 + InsertMenuAtIndex(newMenu, GetMenuCount()); 1.140 + else 1.141 + delete newMenu; 1.142 + } 1.143 + } 1.144 + } 1.145 +} 1.146 + 1.147 +uint32_t nsMenuBarX::GetMenuCount() 1.148 +{ 1.149 + return mMenuArray.Length(); 1.150 +} 1.151 + 1.152 +bool nsMenuBarX::MenuContainsAppMenu() 1.153 +{ 1.154 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.155 + 1.156 + return ([mNativeMenu numberOfItems] > 0 && 1.157 + [[mNativeMenu itemAtIndex:0] submenu] == sApplicationMenu); 1.158 + 1.159 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); 1.160 +} 1.161 + 1.162 +nsresult nsMenuBarX::InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex) 1.163 +{ 1.164 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.165 + 1.166 + // If we haven't created a global Application menu yet, do it. 1.167 + if (!sApplicationMenu) { 1.168 + nsresult rv = NS_OK; // avoid warning about rv being unused 1.169 + rv = CreateApplicationMenu(aMenu); 1.170 + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't create Application menu"); 1.171 + 1.172 + // Hook the new Application menu up to the menu bar. 1.173 + NSMenu* mainMenu = [NSApp mainMenu]; 1.174 + NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!"); 1.175 + [[mainMenu itemAtIndex:0] setSubmenu:sApplicationMenu]; 1.176 + } 1.177 + 1.178 + // add menu to array that owns our menus 1.179 + mMenuArray.InsertElementAt(aIndex, aMenu); 1.180 + 1.181 + // hook up submenus 1.182 + nsIContent* menuContent = aMenu->Content(); 1.183 + if (menuContent->GetChildCount() > 0 && 1.184 + !nsMenuUtilsX::NodeIsHiddenOrCollapsed(menuContent)) { 1.185 + int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(this, aMenu); 1.186 + if (MenuContainsAppMenu()) 1.187 + insertionIndex++; 1.188 + [mNativeMenu insertItem:aMenu->NativeMenuItem() atIndex:insertionIndex]; 1.189 + } 1.190 + 1.191 + return NS_OK; 1.192 + 1.193 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.194 +} 1.195 + 1.196 +void nsMenuBarX::RemoveMenuAtIndex(uint32_t aIndex) 1.197 +{ 1.198 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.199 + 1.200 + NS_ASSERTION(aIndex < mMenuArray.Length(), "Attempting submenu removal with bad index!"); 1.201 + 1.202 + // Our native menu and our internal menu object array might be out of sync. 1.203 + // This happens, for example, when a submenu is hidden. Because of this we 1.204 + // should not assume that a native submenu is hooked up. 1.205 + NSMenuItem* nativeMenuItem = mMenuArray[aIndex]->NativeMenuItem(); 1.206 + int nativeMenuItemIndex = [mNativeMenu indexOfItem:nativeMenuItem]; 1.207 + if (nativeMenuItemIndex != -1) 1.208 + [mNativeMenu removeItemAtIndex:nativeMenuItemIndex]; 1.209 + 1.210 + mMenuArray.RemoveElementAt(aIndex); 1.211 + 1.212 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.213 +} 1.214 + 1.215 +void nsMenuBarX::ObserveAttributeChanged(nsIDocument* aDocument, 1.216 + nsIContent* aContent, 1.217 + nsIAtom* aAttribute) 1.218 +{ 1.219 +} 1.220 + 1.221 +void nsMenuBarX::ObserveContentRemoved(nsIDocument* aDocument, 1.222 + nsIContent* aChild, 1.223 + int32_t aIndexInContainer) 1.224 +{ 1.225 + RemoveMenuAtIndex(aIndexInContainer); 1.226 +} 1.227 + 1.228 +void nsMenuBarX::ObserveContentInserted(nsIDocument* aDocument, 1.229 + nsIContent* aContainer, 1.230 + nsIContent* aChild) 1.231 +{ 1.232 + nsMenuX* newMenu = new nsMenuX(); 1.233 + if (newMenu) { 1.234 + nsresult rv = newMenu->Create(this, this, aChild); 1.235 + if (NS_SUCCEEDED(rv)) 1.236 + InsertMenuAtIndex(newMenu, aContainer->IndexOf(aChild)); 1.237 + else 1.238 + delete newMenu; 1.239 + } 1.240 +} 1.241 + 1.242 +void nsMenuBarX::ForceUpdateNativeMenuAt(const nsAString& indexString) 1.243 +{ 1.244 + NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading()) 1.245 + length:indexString.Length()]; 1.246 + NSArray* indexes = [locationString componentsSeparatedByString:@"|"]; 1.247 + unsigned int indexCount = [indexes count]; 1.248 + if (indexCount == 0) 1.249 + return; 1.250 + 1.251 + nsMenuX* currentMenu = NULL; 1.252 + int targetIndex = [[indexes objectAtIndex:0] intValue]; 1.253 + int visible = 0; 1.254 + uint32_t length = mMenuArray.Length(); 1.255 + // first find a menu in the menu bar 1.256 + for (unsigned int i = 0; i < length; i++) { 1.257 + nsMenuX* menu = mMenuArray[i]; 1.258 + if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) { 1.259 + visible++; 1.260 + if (visible == (targetIndex + 1)) { 1.261 + currentMenu = menu; 1.262 + break; 1.263 + } 1.264 + } 1.265 + } 1.266 + 1.267 + if (!currentMenu) 1.268 + return; 1.269 + 1.270 + // fake open/close to cause lazy update to happen so submenus populate 1.271 + currentMenu->MenuOpened(); 1.272 + currentMenu->MenuClosed(); 1.273 + 1.274 + // now find the correct submenu 1.275 + for (unsigned int i = 1; currentMenu && i < indexCount; i++) { 1.276 + targetIndex = [[indexes objectAtIndex:i] intValue]; 1.277 + visible = 0; 1.278 + length = currentMenu->GetItemCount(); 1.279 + for (unsigned int j = 0; j < length; j++) { 1.280 + nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j); 1.281 + if (!targetMenu) 1.282 + return; 1.283 + if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) { 1.284 + visible++; 1.285 + if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) { 1.286 + currentMenu = static_cast<nsMenuX*>(targetMenu); 1.287 + // fake open/close to cause lazy update to happen 1.288 + currentMenu->MenuOpened(); 1.289 + currentMenu->MenuClosed(); 1.290 + break; 1.291 + } 1.292 + } 1.293 + } 1.294 + } 1.295 +} 1.296 + 1.297 +// Calling this forces a full reload of the menu system, reloading all native 1.298 +// menus and their items. 1.299 +// Without this testing is hard because changes to the DOM affect the native 1.300 +// menu system lazily. 1.301 +void nsMenuBarX::ForceNativeMenuReload() 1.302 +{ 1.303 + // tear down everything 1.304 + while (GetMenuCount() > 0) 1.305 + RemoveMenuAtIndex(0); 1.306 + 1.307 + // construct everything 1.308 + ConstructNativeMenus(); 1.309 +} 1.310 + 1.311 +nsMenuX* nsMenuBarX::GetMenuAt(uint32_t aIndex) 1.312 +{ 1.313 + if (mMenuArray.Length() <= aIndex) { 1.314 + NS_ERROR("Requesting menu at invalid index!"); 1.315 + return NULL; 1.316 + } 1.317 + return mMenuArray[aIndex]; 1.318 +} 1.319 + 1.320 +nsMenuX* nsMenuBarX::GetXULHelpMenu() 1.321 +{ 1.322 + // The Help menu is usually (always?) the last one, so we start there and 1.323 + // count back. 1.324 + for (int32_t i = GetMenuCount() - 1; i >= 0; --i) { 1.325 + nsMenuX* aMenu = GetMenuAt(i); 1.326 + if (aMenu && nsMenuX::IsXULHelpMenu(aMenu->Content())) 1.327 + return aMenu; 1.328 + } 1.329 + return nil; 1.330 +} 1.331 + 1.332 +// On SnowLeopard and later we must tell the OS which is our Help menu. 1.333 +// Otherwise it will only add Spotlight for Help (the Search item) to our 1.334 +// Help menu if its label/title is "Help" -- i.e. if the menu is in English. 1.335 +// This resolves bugs 489196 and 539317. 1.336 +void nsMenuBarX::SetSystemHelpMenu() 1.337 +{ 1.338 + nsMenuX* xulHelpMenu = GetXULHelpMenu(); 1.339 + if (xulHelpMenu) { 1.340 + NSMenu* helpMenu = (NSMenu*)xulHelpMenu->NativeData(); 1.341 + if (helpMenu) 1.342 + [NSApp setHelpMenu:helpMenu]; 1.343 + } 1.344 +} 1.345 + 1.346 +nsresult nsMenuBarX::Paint(bool aDelayed) 1.347 +{ 1.348 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.349 + 1.350 + if (!aDelayed && mAwaitingDelayedPaint) { 1.351 + return NS_OK; 1.352 + } 1.353 + mAwaitingDelayedPaint = false; 1.354 + 1.355 + // Don't try to optimize anything in this painting by checking 1.356 + // sLastGeckoMenuBarPainted because the menubar can be manipulated by 1.357 + // native dialogs and sheet code and other things besides this paint method. 1.358 + 1.359 + // We have to keep the same menu item for the Application menu so we keep 1.360 + // passing it along. 1.361 + NSMenu* outgoingMenu = [NSApp mainMenu]; 1.362 + NS_ASSERTION([outgoingMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!"); 1.363 + 1.364 + // To work around bug 722676, we sometimes need to delay making mNativeMenu 1.365 + // the main menu. This is an Apple bug that sometimes causes a top-level 1.366 + // menu item to remain highlighted after pressing a Cmd+key combination that 1.367 + // opens a new window, then closing the window. The OS temporarily 1.368 + // highlights the appropriate top-level menu item whenever you press the 1.369 + // Cmd+key combination for one of its submenus. (It does this by setting a 1.370 + // "pressed" attribute on it.) The OS then uses a timer to remove this 1.371 + // "pressed" attribute. But without our workaround we sometimes change the 1.372 + // main menu before the timer has fired, so when it fires the menu item it 1.373 + // was intended to unhighlight is no longer present in the main menu. This 1.374 + // causes the item to remain semi-permanently highlighted (until you quit 1.375 + // Firefox or navigate the main menu by hand). 1.376 + if ((outgoingMenu != mNativeMenu) && 1.377 + [outgoingMenu isKindOfClass:[GeckoNSMenu class]]) { 1.378 + if (aDelayed) { 1.379 + [(GeckoNSMenu *)outgoingMenu setDelayResignMainMenu:false]; 1.380 + } else if ([(GeckoNSMenu *)outgoingMenu delayResignMainMenu]) { 1.381 + PaintMenuBarAfterDelay(); 1.382 + return NS_OK; 1.383 + } 1.384 + } 1.385 + 1.386 + if (outgoingMenu != mNativeMenu) { 1.387 + NSMenuItem* appMenuItem = [[outgoingMenu itemAtIndex:0] retain]; 1.388 + [outgoingMenu removeItemAtIndex:0]; 1.389 + [mNativeMenu insertItem:appMenuItem atIndex:0]; 1.390 + [appMenuItem release]; 1.391 + // Set menu bar and event target. 1.392 + [NSApp setMainMenu:mNativeMenu]; 1.393 + } 1.394 + SetSystemHelpMenu(); 1.395 + nsMenuBarX::sLastGeckoMenuBarPainted = this; 1.396 + 1.397 + gSomeMenuBarPainted = YES; 1.398 + 1.399 + return NS_OK; 1.400 + 1.401 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.402 +} 1.403 + 1.404 +// Used to delay a call to nsMenuBarX::Paint(). Needed to work around 1.405 +// bug 722676. 1.406 +void nsMenuBarX::PaintMenuBarAfterDelay() 1.407 +{ 1.408 + mAwaitingDelayedPaint = true; 1.409 + nsMenuBarX::sCurrentPaintDelayedMenuBar = this; 1.410 + [mNativeMenu retain]; 1.411 + // The delay for Apple's unhighlight timer is 0.1f, so we make ours a bit longer. 1.412 + [mNativeMenu performSelector:@selector(delayedPaintMenuBar:) 1.413 + withObject:nil 1.414 + afterDelay:0.15f]; 1.415 +} 1.416 + 1.417 +// Returns the 'key' attribute of the 'shortcutID' object (if any) in the 1.418 +// currently active menubar's DOM document. 'shortcutID' should be the id 1.419 +// (i.e. the name) of a component that defines a commonly used (and 1.420 +// localized) cmd+key shortcut, and belongs to a keyset containing similar 1.421 +// objects. For example "key_selectAll". Returns a value that can be 1.422 +// compared to the first character of [NSEvent charactersIgnoringModifiers] 1.423 +// when [NSEvent modifierFlags] == NSCommandKeyMask. 1.424 +char nsMenuBarX::GetLocalizedAccelKey(const char *shortcutID) 1.425 +{ 1.426 + if (!sLastGeckoMenuBarPainted) 1.427 + return 0; 1.428 + 1.429 + nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(sLastGeckoMenuBarPainted->mDocument)); 1.430 + if (!domDoc) 1.431 + return 0; 1.432 + 1.433 + NS_ConvertASCIItoUTF16 shortcutIDStr((const char *)shortcutID); 1.434 + nsCOMPtr<nsIDOMElement> shortcutElement; 1.435 + domDoc->GetElementById(shortcutIDStr, getter_AddRefs(shortcutElement)); 1.436 + nsCOMPtr<nsIContent> shortcutContent = do_QueryInterface(shortcutElement); 1.437 + if (!shortcutContent) 1.438 + return 0; 1.439 + 1.440 + nsAutoString key; 1.441 + shortcutContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key); 1.442 + NS_LossyConvertUTF16toASCII keyASC(key.get()); 1.443 + const char *keyASCPtr = keyASC.get(); 1.444 + if (!keyASCPtr) 1.445 + return 0; 1.446 + // If keyID's 'key' attribute isn't exactly one character long, it's not 1.447 + // what we're looking for. 1.448 + if (strlen(keyASCPtr) != sizeof(char)) 1.449 + return 0; 1.450 + // Make sure retval is lower case. 1.451 + char retval = tolower(keyASCPtr[0]); 1.452 + 1.453 + return retval; 1.454 +} 1.455 + 1.456 +// Hide the item in the menu by setting the 'hidden' attribute. Returns it in |outHiddenNode| so 1.457 +// the caller can hang onto it if they so choose. It is acceptable to pass nsull 1.458 +// for |outHiddenNode| if the caller doesn't care about the hidden node. 1.459 +void nsMenuBarX::HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode) 1.460 +{ 1.461 + nsCOMPtr<nsIDOMElement> menuItem; 1.462 + inDoc->GetElementById(inID, getter_AddRefs(menuItem)); 1.463 + nsCOMPtr<nsIContent> menuContent(do_QueryInterface(menuItem)); 1.464 + if (menuContent) { 1.465 + menuContent->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, NS_LITERAL_STRING("true"), false); 1.466 + if (outHiddenNode) { 1.467 + *outHiddenNode = menuContent.get(); 1.468 + NS_IF_ADDREF(*outHiddenNode); 1.469 + } 1.470 + } 1.471 +} 1.472 + 1.473 +// Do what is necessary to conform to the Aqua guidelines for menus. 1.474 +void nsMenuBarX::AquifyMenuBar() 1.475 +{ 1.476 + nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mContent->GetDocument())); 1.477 + if (domDoc) { 1.478 + // remove the "About..." item and its separator 1.479 + HideItem(domDoc, NS_LITERAL_STRING("aboutSeparator"), nullptr); 1.480 + HideItem(domDoc, NS_LITERAL_STRING("aboutName"), getter_AddRefs(mAboutItemContent)); 1.481 + if (!sAboutItemContent) 1.482 + sAboutItemContent = mAboutItemContent; 1.483 + 1.484 + // Hide the software update menu item, since it belongs in the application 1.485 + // menu on Mac OS X. 1.486 + HideItem(domDoc, NS_LITERAL_STRING("updateSeparator"), nullptr); 1.487 + HideItem(domDoc, NS_LITERAL_STRING("checkForUpdates"), getter_AddRefs(mUpdateItemContent)); 1.488 + if (!sUpdateItemContent) 1.489 + sUpdateItemContent = mUpdateItemContent; 1.490 + 1.491 + // remove quit item and its separator 1.492 + HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitSeparator"), nullptr); 1.493 + HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitItem"), getter_AddRefs(mQuitItemContent)); 1.494 + if (!sQuitItemContent) 1.495 + sQuitItemContent = mQuitItemContent; 1.496 + 1.497 + // remove prefs item and its separator, but save off the pref content node 1.498 + // so we can invoke its command later. 1.499 + HideItem(domDoc, NS_LITERAL_STRING("menu_PrefsSeparator"), nullptr); 1.500 + HideItem(domDoc, NS_LITERAL_STRING("menu_preferences"), getter_AddRefs(mPrefItemContent)); 1.501 + if (!sPrefItemContent) 1.502 + sPrefItemContent = mPrefItemContent; 1.503 + 1.504 + // hide items that we use for the Application menu 1.505 + HideItem(domDoc, NS_LITERAL_STRING("menu_mac_services"), nullptr); 1.506 + HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_app"), nullptr); 1.507 + HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_others"), nullptr); 1.508 + HideItem(domDoc, NS_LITERAL_STRING("menu_mac_show_all"), nullptr); 1.509 + } 1.510 +} 1.511 + 1.512 +// for creating menu items destined for the Application menu 1.513 +NSMenuItem* nsMenuBarX::CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action, 1.514 + int tag, NativeMenuItemTarget* target) 1.515 +{ 1.516 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.517 + 1.518 + nsCOMPtr<nsIDocument> doc = inMenu->Content()->GetDocument(); 1.519 + if (!doc) 1.520 + return nil; 1.521 + 1.522 + nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(doc)); 1.523 + if (!domdoc) 1.524 + return nil; 1.525 + 1.526 + // Get information from the gecko menu item 1.527 + nsAutoString label; 1.528 + nsAutoString modifiers; 1.529 + nsAutoString key; 1.530 + nsCOMPtr<nsIDOMElement> menuItem; 1.531 + domdoc->GetElementById(nodeID, getter_AddRefs(menuItem)); 1.532 + if (menuItem) { 1.533 + menuItem->GetAttribute(NS_LITERAL_STRING("label"), label); 1.534 + menuItem->GetAttribute(NS_LITERAL_STRING("modifiers"), modifiers); 1.535 + menuItem->GetAttribute(NS_LITERAL_STRING("key"), key); 1.536 + } 1.537 + else { 1.538 + return nil; 1.539 + } 1.540 + 1.541 + // Get more information about the key equivalent. Start by 1.542 + // finding the key node we need. 1.543 + NSString* keyEquiv = nil; 1.544 + unsigned int macKeyModifiers = 0; 1.545 + if (!key.IsEmpty()) { 1.546 + nsCOMPtr<nsIDOMElement> keyElement; 1.547 + domdoc->GetElementById(key, getter_AddRefs(keyElement)); 1.548 + if (keyElement) { 1.549 + nsCOMPtr<nsIContent> keyContent (do_QueryInterface(keyElement)); 1.550 + // first grab the key equivalent character 1.551 + nsAutoString keyChar(NS_LITERAL_STRING(" ")); 1.552 + keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar); 1.553 + if (!keyChar.EqualsLiteral(" ")) { 1.554 + keyEquiv = [[NSString stringWithCharacters:reinterpret_cast<const unichar*>(keyChar.get()) 1.555 + length:keyChar.Length()] lowercaseString]; 1.556 + } 1.557 + // now grab the key equivalent modifiers 1.558 + nsAutoString modifiersStr; 1.559 + keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr); 1.560 + uint8_t geckoModifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr); 1.561 + macKeyModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(geckoModifiers); 1.562 + } 1.563 + } 1.564 + // get the label into NSString-form 1.565 + NSString* labelString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(label.get()) 1.566 + length:label.Length()]; 1.567 + 1.568 + if (!labelString) 1.569 + labelString = @""; 1.570 + if (!keyEquiv) 1.571 + keyEquiv = @""; 1.572 + 1.573 + // put together the actual NSMenuItem 1.574 + NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:labelString action:action keyEquivalent:keyEquiv]; 1.575 + 1.576 + [newMenuItem setTag:tag]; 1.577 + [newMenuItem setTarget:target]; 1.578 + [newMenuItem setKeyEquivalentModifierMask:macKeyModifiers]; 1.579 + 1.580 + MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:this]; 1.581 + [newMenuItem setRepresentedObject:info]; 1.582 + [info release]; 1.583 + 1.584 + return newMenuItem; 1.585 + 1.586 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.587 +} 1.588 + 1.589 +// build the Application menu shared by all menu bars 1.590 +nsresult nsMenuBarX::CreateApplicationMenu(nsMenuX* inMenu) 1.591 +{ 1.592 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.593 + 1.594 + // At this point, the application menu is the application menu from 1.595 + // the nib in cocoa widgets. We do not have a way to create an application 1.596 + // menu manually, so we grab the one from the nib and use that. 1.597 + sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain]; 1.598 + 1.599 +/* 1.600 + We support the following menu items here: 1.601 + 1.602 + Menu Item DOM Node ID Notes 1.603 + 1.604 + ======================== 1.605 + = About This App = <- aboutName 1.606 + = Check for Updates... = <- checkForUpdates 1.607 + ======================== 1.608 + = Preferences... = <- menu_preferences 1.609 + ======================== 1.610 + = Services > = <- menu_mac_services <- (do not define key equivalent) 1.611 + ======================== 1.612 + = Hide App = <- menu_mac_hide_app 1.613 + = Hide Others = <- menu_mac_hide_others 1.614 + = Show All = <- menu_mac_show_all 1.615 + ======================== 1.616 + = Quit = <- menu_FileQuitItem 1.617 + ======================== 1.618 + 1.619 + If any of them are ommitted from the application's DOM, we just don't add 1.620 + them. We always add a "Quit" item, but if an app developer does not provide a 1.621 + DOM node with the right ID for the Quit item, we add it in English. App 1.622 + developers need only add each node with a label and a key equivalent (if they 1.623 + want one). Other attributes are optional. Like so: 1.624 + 1.625 + <menuitem id="menu_preferences" 1.626 + label="&preferencesCmdMac.label;" 1.627 + key="open_prefs_key"/> 1.628 + 1.629 + We need to use this system for localization purposes, until we have a better way 1.630 + to define the Application menu to be used on Mac OS X. 1.631 +*/ 1.632 + 1.633 + if (sApplicationMenu) { 1.634 + // This code reads attributes we are going to care about from the DOM elements 1.635 + 1.636 + NSMenuItem *itemBeingAdded = nil; 1.637 + BOOL addAboutSeparator = FALSE; 1.638 + 1.639 + // Add the About menu item 1.640 + itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("aboutName"), @selector(menuItemHit:), 1.641 + eCommand_ID_About, nsMenuBarX::sNativeEventTarget); 1.642 + if (itemBeingAdded) { 1.643 + [sApplicationMenu addItem:itemBeingAdded]; 1.644 + [itemBeingAdded release]; 1.645 + itemBeingAdded = nil; 1.646 + 1.647 + addAboutSeparator = TRUE; 1.648 + } 1.649 + 1.650 + // Add the software update menu item 1.651 + itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("checkForUpdates"), @selector(menuItemHit:), 1.652 + eCommand_ID_Update, nsMenuBarX::sNativeEventTarget); 1.653 + if (itemBeingAdded) { 1.654 + [sApplicationMenu addItem:itemBeingAdded]; 1.655 + [itemBeingAdded release]; 1.656 + itemBeingAdded = nil; 1.657 + 1.658 + addAboutSeparator = TRUE; 1.659 + } 1.660 + 1.661 + // Add separator if either the About item or software update item exists 1.662 + if (addAboutSeparator) 1.663 + [sApplicationMenu addItem:[NSMenuItem separatorItem]]; 1.664 + 1.665 + // Add the Preferences menu item 1.666 + itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_preferences"), @selector(menuItemHit:), 1.667 + eCommand_ID_Prefs, nsMenuBarX::sNativeEventTarget); 1.668 + if (itemBeingAdded) { 1.669 + [sApplicationMenu addItem:itemBeingAdded]; 1.670 + [itemBeingAdded release]; 1.671 + itemBeingAdded = nil; 1.672 + 1.673 + // Add separator after Preferences menu 1.674 + [sApplicationMenu addItem:[NSMenuItem separatorItem]]; 1.675 + } 1.676 + 1.677 + // Add Services menu item 1.678 + itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_services"), nil, 1.679 + 0, nil); 1.680 + if (itemBeingAdded) { 1.681 + [sApplicationMenu addItem:itemBeingAdded]; 1.682 + 1.683 + // set this menu item up as the Mac OS X Services menu 1.684 + NSMenu* servicesMenu = [[GeckoServicesNSMenu alloc] initWithTitle:@""]; 1.685 + [itemBeingAdded setSubmenu:servicesMenu]; 1.686 + [NSApp setServicesMenu:servicesMenu]; 1.687 + 1.688 + [itemBeingAdded release]; 1.689 + itemBeingAdded = nil; 1.690 + 1.691 + // Add separator after Services menu 1.692 + [sApplicationMenu addItem:[NSMenuItem separatorItem]]; 1.693 + } 1.694 + 1.695 + BOOL addHideShowSeparator = FALSE; 1.696 + 1.697 + // Add menu item to hide this application 1.698 + itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_app"), @selector(menuItemHit:), 1.699 + eCommand_ID_HideApp, nsMenuBarX::sNativeEventTarget); 1.700 + if (itemBeingAdded) { 1.701 + [sApplicationMenu addItem:itemBeingAdded]; 1.702 + [itemBeingAdded release]; 1.703 + itemBeingAdded = nil; 1.704 + 1.705 + addHideShowSeparator = TRUE; 1.706 + } 1.707 + 1.708 + // Add menu item to hide other applications 1.709 + itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_others"), @selector(menuItemHit:), 1.710 + eCommand_ID_HideOthers, nsMenuBarX::sNativeEventTarget); 1.711 + if (itemBeingAdded) { 1.712 + [sApplicationMenu addItem:itemBeingAdded]; 1.713 + [itemBeingAdded release]; 1.714 + itemBeingAdded = nil; 1.715 + 1.716 + addHideShowSeparator = TRUE; 1.717 + } 1.718 + 1.719 + // Add menu item to show all applications 1.720 + itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_show_all"), @selector(menuItemHit:), 1.721 + eCommand_ID_ShowAll, nsMenuBarX::sNativeEventTarget); 1.722 + if (itemBeingAdded) { 1.723 + [sApplicationMenu addItem:itemBeingAdded]; 1.724 + [itemBeingAdded release]; 1.725 + itemBeingAdded = nil; 1.726 + 1.727 + addHideShowSeparator = TRUE; 1.728 + } 1.729 + 1.730 + // Add a separator after the hide/show menus if at least one exists 1.731 + if (addHideShowSeparator) 1.732 + [sApplicationMenu addItem:[NSMenuItem separatorItem]]; 1.733 + 1.734 + // Add quit menu item 1.735 + itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_FileQuitItem"), @selector(menuItemHit:), 1.736 + eCommand_ID_Quit, nsMenuBarX::sNativeEventTarget); 1.737 + if (itemBeingAdded) { 1.738 + [sApplicationMenu addItem:itemBeingAdded]; 1.739 + [itemBeingAdded release]; 1.740 + itemBeingAdded = nil; 1.741 + } 1.742 + else { 1.743 + // the current application does not have a DOM node for "Quit". Add one 1.744 + // anyway, in English. 1.745 + NSMenuItem* defaultQuitItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(menuItemHit:) 1.746 + keyEquivalent:@"q"] autorelease]; 1.747 + [defaultQuitItem setTarget:nsMenuBarX::sNativeEventTarget]; 1.748 + [defaultQuitItem setTag:eCommand_ID_Quit]; 1.749 + [sApplicationMenu addItem:defaultQuitItem]; 1.750 + } 1.751 + } 1.752 + 1.753 + return (sApplicationMenu) ? NS_OK : NS_ERROR_FAILURE; 1.754 + 1.755 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.756 +} 1.757 + 1.758 +void nsMenuBarX::SetParent(nsIWidget* aParent) 1.759 +{ 1.760 + mParentWindow = aParent; 1.761 +} 1.762 + 1.763 + 1.764 +// 1.765 +// Objective-C class used to allow us to have keyboard commands 1.766 +// look like they are doing something but actually do nothing. 1.767 +// We allow mouse actions to work normally. 1.768 +// 1.769 + 1.770 +// Controls whether or not native menu items should invoke their commands. 1.771 +static BOOL gMenuItemsExecuteCommands = YES; 1.772 + 1.773 +@implementation GeckoNSMenu 1.774 + 1.775 +- (id)initWithTitle:(NSString *)aTitle 1.776 +{ 1.777 + if (self = [super initWithTitle:aTitle]) { 1.778 + mMenuBarOwner = nullptr; 1.779 + mDelayResignMainMenu = false; 1.780 + } 1.781 + return self; 1.782 +} 1.783 + 1.784 +- (id)initWithTitle:(NSString *)aTitle andMenuBarOwner:(nsMenuBarX *)aMenuBarOwner 1.785 +{ 1.786 + if (self = [super initWithTitle:aTitle]) { 1.787 + mMenuBarOwner = aMenuBarOwner; 1.788 + mDelayResignMainMenu = false; 1.789 + } 1.790 + return self; 1.791 +} 1.792 + 1.793 +- (void)resetMenuBarOwner 1.794 +{ 1.795 + mMenuBarOwner = nil; 1.796 +} 1.797 + 1.798 +- (bool)delayResignMainMenu 1.799 +{ 1.800 + return mDelayResignMainMenu; 1.801 +} 1.802 + 1.803 +- (void)setDelayResignMainMenu:(bool)aShouldDelay 1.804 +{ 1.805 + mDelayResignMainMenu = aShouldDelay; 1.806 +} 1.807 + 1.808 +// Used to delay a call to nsMenuBarX::Paint(). Needed to work around 1.809 +// bug 722676. 1.810 +- (void)delayedPaintMenuBar:(id)unused 1.811 +{ 1.812 + if (mMenuBarOwner) { 1.813 + if (mMenuBarOwner == nsMenuBarX::sCurrentPaintDelayedMenuBar) { 1.814 + mMenuBarOwner->Paint(true); 1.815 + nsMenuBarX::sCurrentPaintDelayedMenuBar = nullptr; 1.816 + } else { 1.817 + mMenuBarOwner->ResetAwaitingDelayedPaint(); 1.818 + } 1.819 + } 1.820 + [self release]; 1.821 +} 1.822 + 1.823 +// Undocumented method, present unchanged since OS X 10.6, used to temporarily 1.824 +// highlight a top-level menu item when an appropriate Cmd+key combination is 1.825 +// pressed. 1.826 +- (void)_performActionWithHighlightingForItemAtIndex:(NSInteger)index; 1.827 +{ 1.828 + NSMenu *mainMenu = [NSApp mainMenu]; 1.829 + if ([mainMenu isKindOfClass:[GeckoNSMenu class]]) { 1.830 + [(GeckoNSMenu *)mainMenu setDelayResignMainMenu:true]; 1.831 + } 1.832 + [super _performActionWithHighlightingForItemAtIndex:index]; 1.833 +} 1.834 + 1.835 +// Keyboard commands should not cause menu items to invoke their 1.836 +// commands when there is a key window because we'd rather send 1.837 +// the keyboard command to the window. We still have the menus 1.838 +// go through the mechanics so they'll give the proper visual 1.839 +// feedback. 1.840 +- (BOOL)performKeyEquivalent:(NSEvent *)theEvent 1.841 +{ 1.842 + // We've noticed that Mac OS X expects this check in subclasses before 1.843 + // calling NSMenu's "performKeyEquivalent:". 1.844 + // 1.845 + // There is no case in which we'd need to do anything or return YES 1.846 + // when we have no items so we can just do this check first. 1.847 + if ([self numberOfItems] <= 0) { 1.848 + return NO; 1.849 + } 1.850 + 1.851 + NSWindow *keyWindow = [NSApp keyWindow]; 1.852 + 1.853 + // If there is no key window then just behave normally. This 1.854 + // probably means that this menu is associated with Gecko's 1.855 + // hidden window. 1.856 + if (!keyWindow) { 1.857 + return [super performKeyEquivalent:theEvent]; 1.858 + } 1.859 + 1.860 + // Plugins normally eat all keyboard commands, this hack mitigates 1.861 + // the problem. 1.862 + BOOL handleForPluginHack = NO; 1.863 + NSResponder *firstResponder = [keyWindow firstResponder]; 1.864 + if (firstResponder && 1.865 + [firstResponder isKindOfClass:[ChildView class]] && 1.866 + [(ChildView*)firstResponder isPluginView]) { 1.867 + handleForPluginHack = YES; 1.868 + // Maintain a list of cmd+key combinations that we never act on (in the 1.869 + // browser) when the keyboard focus is in a plugin. What a particular 1.870 + // cmd+key combo means here (to the browser) is governed by browser.dtd, 1.871 + // which "contains the browser main menu items". 1.872 + UInt32 modifierFlags = [theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask; 1.873 + if (modifierFlags == NSCommandKeyMask) { 1.874 + NSString *unmodchars = [theEvent charactersIgnoringModifiers]; 1.875 + if ([unmodchars length] == 1) { 1.876 + if ([unmodchars characterAtIndex:0] == nsMenuBarX::GetLocalizedAccelKey("key_selectAll")) { 1.877 + handleForPluginHack = NO; 1.878 + } 1.879 + } 1.880 + } 1.881 + } 1.882 + 1.883 + gMenuItemsExecuteCommands = handleForPluginHack; 1.884 + [super performKeyEquivalent:theEvent]; 1.885 + gMenuItemsExecuteCommands = YES; // return to default 1.886 + 1.887 + // Return YES if we invoked a command and there is now no key window or we changed 1.888 + // the first responder. In this case we do not want to propagate the event because 1.889 + // we don't want it handled again. 1.890 + if (handleForPluginHack) { 1.891 + if (![NSApp keyWindow] || [[NSApp keyWindow] firstResponder] != firstResponder) { 1.892 + return YES; 1.893 + } 1.894 + } 1.895 + 1.896 + // Return NO so that we can handle the event via NSView's "keyDown:". 1.897 + return NO; 1.898 +} 1.899 + 1.900 +@end 1.901 + 1.902 +// 1.903 +// Objective-C class used as action target for menu items 1.904 +// 1.905 + 1.906 +@implementation NativeMenuItemTarget 1.907 + 1.908 +// called when some menu item in this menu gets hit 1.909 +-(IBAction)menuItemHit:(id)sender 1.910 +{ 1.911 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.912 + 1.913 + // menuGroupOwner below is an nsMenuBarX object, which we sometimes access 1.914 + // after it's been deleted, causing crashes (see bug 704866 and bug 670914). 1.915 + // To fix this "correctly", in nsMenuBarX::~nsMenuBarX() we'd need to 1.916 + // iterate through every NSMenuItem in nsMenuBarX::mNativeMenu and its 1.917 + // submenus, which might be quite time consuming. (For every NSMenuItem 1.918 + // that has a "representedObject" that's a MenuItemInfo object, we'd need 1.919 + // need to null out its "menuGroupOwner" if it's the same as the nsMenuBarX 1.920 + // object being destroyed.) But if the nsMenuBarX object being destroyed 1.921 + // corresponds to the currently focused window, it's likely that the 1.922 + // nsMenuBarX destructor will null out sLastGeckoMenuBarPainted. So we can 1.923 + // probably eliminate most of these crashes if we use this variable being 1.924 + // null as an indicator that we're likely to crash below when we dereference 1.925 + // menuGroupOwner. 1.926 + if (!nsMenuBarX::sLastGeckoMenuBarPainted) { 1.927 + return; 1.928 + } 1.929 + 1.930 + if (!gMenuItemsExecuteCommands) { 1.931 + return; 1.932 + } 1.933 + 1.934 + int tag = [sender tag]; 1.935 + 1.936 + MenuItemInfo* info = [sender representedObject]; 1.937 + if (!info) 1.938 + return; 1.939 + 1.940 + nsMenuGroupOwnerX* menuGroupOwner = [info menuGroupOwner]; 1.941 + if (!menuGroupOwner) 1.942 + return; 1.943 + 1.944 + nsMenuBarX* menuBar = nullptr; 1.945 + if (menuGroupOwner->MenuObjectType() == eMenuBarObjectType) 1.946 + menuBar = static_cast<nsMenuBarX*>(menuGroupOwner); 1.947 + 1.948 + // Do special processing if this is for an app-global command. 1.949 + if (tag == eCommand_ID_About) { 1.950 + nsIContent* mostSpecificContent = sAboutItemContent; 1.951 + if (menuBar && menuBar->mAboutItemContent) 1.952 + mostSpecificContent = menuBar->mAboutItemContent; 1.953 + nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); 1.954 + return; 1.955 + } 1.956 + else if (tag == eCommand_ID_Update) { 1.957 + nsIContent* mostSpecificContent = sUpdateItemContent; 1.958 + if (menuBar && menuBar->mUpdateItemContent) 1.959 + mostSpecificContent = menuBar->mUpdateItemContent; 1.960 + nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); 1.961 + } 1.962 + else if (tag == eCommand_ID_Prefs) { 1.963 + nsIContent* mostSpecificContent = sPrefItemContent; 1.964 + if (menuBar && menuBar->mPrefItemContent) 1.965 + mostSpecificContent = menuBar->mPrefItemContent; 1.966 + nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); 1.967 + return; 1.968 + } 1.969 + else if (tag == eCommand_ID_HideApp) { 1.970 + [NSApp hide:sender]; 1.971 + return; 1.972 + } 1.973 + else if (tag == eCommand_ID_HideOthers) { 1.974 + [NSApp hideOtherApplications:sender]; 1.975 + return; 1.976 + } 1.977 + else if (tag == eCommand_ID_ShowAll) { 1.978 + [NSApp unhideAllApplications:sender]; 1.979 + return; 1.980 + } 1.981 + else if (tag == eCommand_ID_Quit) { 1.982 + nsIContent* mostSpecificContent = sQuitItemContent; 1.983 + if (menuBar && menuBar->mQuitItemContent) 1.984 + mostSpecificContent = menuBar->mQuitItemContent; 1.985 + // If we have some content for quit we execute it. Otherwise we send a native app terminate 1.986 + // message. If you want to stop a quit from happening, provide quit content and return 1.987 + // the event as unhandled. 1.988 + if (mostSpecificContent) { 1.989 + nsMenuUtilsX::DispatchCommandTo(mostSpecificContent); 1.990 + } 1.991 + else { 1.992 + [NSApp terminate:nil]; 1.993 + } 1.994 + return; 1.995 + } 1.996 + 1.997 + // given the commandID, look it up in our hashtable and dispatch to 1.998 + // that menu item. 1.999 + if (menuGroupOwner) { 1.1000 + nsMenuItemX* menuItem = menuGroupOwner->GetMenuItemForCommandID(static_cast<uint32_t>(tag)); 1.1001 + if (menuItem) 1.1002 + menuItem->DoCommand(); 1.1003 + } 1.1004 + 1.1005 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.1006 +} 1.1007 + 1.1008 +@end 1.1009 + 1.1010 +// Objective-C class used for menu items on the Services menu to allow Gecko 1.1011 +// to override their standard behavior in order to stop key equivalents from 1.1012 +// firing in certain instances. When gMenuItemsExecuteCommands is NO, we return 1.1013 +// a dummy target and action instead of the actual target and action. 1.1014 + 1.1015 +@implementation GeckoServicesNSMenuItem 1.1016 + 1.1017 +- (id) target 1.1018 +{ 1.1019 + id realTarget = [super target]; 1.1020 + if (gMenuItemsExecuteCommands) 1.1021 + return realTarget; 1.1022 + else 1.1023 + return realTarget ? self : nil; 1.1024 +} 1.1025 + 1.1026 +- (SEL) action 1.1027 +{ 1.1028 + SEL realAction = [super action]; 1.1029 + if (gMenuItemsExecuteCommands) 1.1030 + return realAction; 1.1031 + else 1.1032 + return realAction ? @selector(_doNothing:) : NULL; 1.1033 +} 1.1034 + 1.1035 +- (void) _doNothing:(id)sender 1.1036 +{ 1.1037 +} 1.1038 + 1.1039 +@end 1.1040 + 1.1041 +// Objective-C class used as the Services menu so that Gecko can override the 1.1042 +// standard behavior of the Services menu in order to stop key equivalents 1.1043 +// from firing in certain instances. 1.1044 + 1.1045 +@implementation GeckoServicesNSMenu 1.1046 + 1.1047 +- (void)addItem:(NSMenuItem *)newItem 1.1048 +{ 1.1049 + [self _overrideClassOfMenuItem:newItem]; 1.1050 + [super addItem:newItem]; 1.1051 +} 1.1052 + 1.1053 +- (NSMenuItem *)addItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv 1.1054 +{ 1.1055 + NSMenuItem * newItem = [super addItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv]; 1.1056 + [self _overrideClassOfMenuItem:newItem]; 1.1057 + return newItem; 1.1058 +} 1.1059 + 1.1060 +- (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index 1.1061 +{ 1.1062 + [self _overrideClassOfMenuItem:newItem]; 1.1063 + [super insertItem:newItem atIndex:index]; 1.1064 +} 1.1065 + 1.1066 +- (NSMenuItem *)insertItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv atIndex:(NSInteger)index 1.1067 +{ 1.1068 + NSMenuItem * newItem = [super insertItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv atIndex:index]; 1.1069 + [self _overrideClassOfMenuItem:newItem]; 1.1070 + return newItem; 1.1071 +} 1.1072 + 1.1073 +- (void) _overrideClassOfMenuItem:(NSMenuItem *)menuItem 1.1074 +{ 1.1075 + if ([menuItem class] == [NSMenuItem class]) 1.1076 + object_setClass(menuItem, [GeckoServicesNSMenuItem class]); 1.1077 +} 1.1078 + 1.1079 +@end