widget/cocoa/nsMenuBarX.mm

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial