widget/cocoa/nsMenuBarX.mm

changeset 0
6474c204b198
     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

mercurial