widget/cocoa/nsMenuX.mm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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 <dlfcn.h>
     8 #include "nsMenuX.h"
     9 #include "nsMenuItemX.h"
    10 #include "nsMenuUtilsX.h"
    11 #include "nsMenuItemIconX.h"
    12 #include "nsStandaloneNativeMenu.h"
    14 #include "nsObjCExceptions.h"
    16 #include "nsToolkit.h"
    17 #include "nsCocoaFeatures.h"
    18 #include "nsCocoaUtils.h"
    19 #include "nsCOMPtr.h"
    20 #include "prinrval.h"
    21 #include "nsString.h"
    22 #include "nsReadableUtils.h"
    23 #include "nsUnicharUtils.h"
    24 #include "plstr.h"
    25 #include "nsGkAtoms.h"
    26 #include "nsCRT.h"
    27 #include "nsBaseWidget.h"
    29 #include "nsIDocument.h"
    30 #include "nsIContent.h"
    31 #include "nsIDOMDocument.h"
    32 #include "nsIDocumentObserver.h"
    33 #include "nsIComponentManager.h"
    34 #include "nsIRollupListener.h"
    35 #include "nsIDOMElement.h"
    36 #include "nsBindingManager.h"
    37 #include "nsIServiceManager.h"
    38 #include "nsXULPopupManager.h"
    39 #include "nsCxPusher.h"
    41 #include "jsapi.h"
    42 #include "nsIScriptGlobalObject.h"
    43 #include "nsIScriptContext.h"
    44 #include "nsIXPConnect.h"
    46 #include "mozilla/MouseEvents.h"
    48 using namespace mozilla;
    50 static bool gConstructingMenu = false;
    51 static bool gMenuMethodsSwizzled = false;
    53 int32_t nsMenuX::sIndexingMenuLevel = 0;
    54 using mozilla::AutoPushJSContext;
    57 //
    58 // Objective-C class used for representedObject
    59 //
    61 @implementation MenuItemInfo
    63 - (id) initWithMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner
    64 {
    65   if ((self = [super init]) != nil) {
    66     mMenuGroupOwner = nullptr;
    67     [self setMenuGroupOwner:aMenuGroupOwner];
    68   }
    69   return self;
    70 }
    72 - (void) dealloc
    73 {
    74   [self setMenuGroupOwner:nullptr];
    75   [super dealloc];
    76 }
    78 - (nsMenuGroupOwnerX *) menuGroupOwner
    79 {
    80   return mMenuGroupOwner;
    81 }
    83 - (void) setMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner
    84 {
    85   // weak reference as the nsMenuGroupOwnerX owns all of its sub-objects
    86   mMenuGroupOwner = aMenuGroupOwner;
    87 }
    89 @end
    92 //
    93 // nsMenuX
    94 //
    96 nsMenuX::nsMenuX()
    97 : mVisibleItemsCount(0), mParent(nullptr), mMenuGroupOwner(nullptr),
    98   mNativeMenu(nil), mNativeMenuItem(nil), mIsEnabled(true),
    99   mDestroyHandlerCalled(false), mNeedsRebuild(true),
   100   mConstructed(false), mVisible(true), mXBLAttached(false)
   101 {
   102   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   104   if (!gMenuMethodsSwizzled) {
   105     nsToolkit::SwizzleMethods([NSMenu class], @selector(_addItem:toTable:),
   106                               @selector(nsMenuX_NSMenu_addItem:toTable:), true);
   107     nsToolkit::SwizzleMethods([NSMenu class], @selector(_removeItem:fromTable:),
   108                               @selector(nsMenuX_NSMenu_removeItem:fromTable:), true);
   109     // On SnowLeopard the Shortcut framework (which contains the
   110     // SCTGRLIndex class) is loaded on demand, whenever the user first opens
   111     // a menu (which normally hasn't happened yet).  So we need to load it
   112     // here explicitly.
   113     dlopen("/System/Library/PrivateFrameworks/Shortcut.framework/Shortcut", RTLD_LAZY);
   114     Class SCTGRLIndexClass = ::NSClassFromString(@"SCTGRLIndex");
   115     nsToolkit::SwizzleMethods(SCTGRLIndexClass, @selector(indexMenuBarDynamically),
   116                               @selector(nsMenuX_SCTGRLIndex_indexMenuBarDynamically));
   118     gMenuMethodsSwizzled = true;
   119   }
   121   mMenuDelegate = [[MenuDelegate alloc] initWithGeckoMenu:this];
   123   if (!nsMenuBarX::sNativeEventTarget)
   124     nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init];
   126   MOZ_COUNT_CTOR(nsMenuX);
   128   NS_OBJC_END_TRY_ABORT_BLOCK;
   129 }
   131 nsMenuX::~nsMenuX()
   132 {
   133   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   135   // Prevent the icon object from outliving us.
   136   if (mIcon)
   137     mIcon->Destroy();
   139   RemoveAll();
   141   [mNativeMenu setDelegate:nil];
   142   [mNativeMenu release];
   143   [mMenuDelegate release];
   144   // autorelease the native menu item so that anything else happening to this
   145   // object happens before the native menu item actually dies
   146   [mNativeMenuItem autorelease];
   148   // alert the change notifier we don't care no more
   149   if (mContent)
   150     mMenuGroupOwner->UnregisterForContentChanges(mContent);
   152   MOZ_COUNT_DTOR(nsMenuX);
   154   NS_OBJC_END_TRY_ABORT_BLOCK;
   155 }
   157 nsresult nsMenuX::Create(nsMenuObjectX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode)
   158 {
   159   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   161   mContent = aNode;
   162   mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel);
   163   mNativeMenu = CreateMenuWithGeckoString(mLabel);
   165   // register this menu to be notified when changes are made to our content object
   166   mMenuGroupOwner = aMenuGroupOwner; // weak ref
   167   NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one");
   168   mMenuGroupOwner->RegisterForContentChanges(mContent, this);
   170   mParent = aParent;
   171   // our parent could be either a menu bar (if we're toplevel) or a menu (if we're a submenu)
   173 #ifdef DEBUG
   174   nsMenuObjectTypeX parentType =
   175 #endif
   176     mParent->MenuObjectType();
   177   NS_ASSERTION((parentType == eMenuBarObjectType || parentType == eSubmenuObjectType || parentType == eStandaloneNativeMenuObjectType),
   178                "Menu parent not a menu bar, menu, or native menu!");
   180   if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent))
   181     mVisible = false;
   182   if (mContent->GetChildCount() == 0)
   183     mVisible = false;
   185   NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel);
   186   mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""];
   187   [mNativeMenuItem setSubmenu:mNativeMenu];
   189   SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
   190                                     nsGkAtoms::_true, eCaseMatters));
   192   // We call MenuConstruct here because keyboard commands are dependent upon
   193   // native menu items being created. If we only call MenuConstruct when a menu
   194   // is actually selected, then we can't access keyboard commands until the
   195   // menu gets selected, which is bad.
   196   MenuConstruct();
   198   mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
   200   return NS_OK;
   202   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   203 }
   205 nsresult nsMenuX::AddMenuItem(nsMenuItemX* aMenuItem)
   206 {
   207   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   209   if (!aMenuItem)
   210     return NS_ERROR_INVALID_ARG;
   212   mMenuObjectsArray.AppendElement(aMenuItem);
   213   if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(aMenuItem->Content()))
   214     return NS_OK;
   215   ++mVisibleItemsCount;
   217   NSMenuItem* newNativeMenuItem = (NSMenuItem*)aMenuItem->NativeData();
   219   // add the menu item to this menu
   220   [mNativeMenu addItem:newNativeMenuItem];
   222   // set up target/action
   223   [newNativeMenuItem setTarget:nsMenuBarX::sNativeEventTarget];
   224   [newNativeMenuItem setAction:@selector(menuItemHit:)];
   226   // set its command. we get the unique command id from the menubar
   227   [newNativeMenuItem setTag:mMenuGroupOwner->RegisterForCommand(aMenuItem)];
   228   MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:mMenuGroupOwner];
   229   [newNativeMenuItem setRepresentedObject:info];
   230   [info release];
   232   return NS_OK;
   234   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   235 }
   237 nsresult nsMenuX::AddMenu(nsMenuX* aMenu)
   238 {
   239   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   241   // Add a submenu
   242   if (!aMenu)
   243     return NS_ERROR_NULL_POINTER;
   245   mMenuObjectsArray.AppendElement(aMenu);
   246   if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(aMenu->Content()))
   247     return NS_OK;
   248   ++mVisibleItemsCount;
   250   // We have to add a menu item and then associate the menu with it
   251   NSMenuItem* newNativeMenuItem = aMenu->NativeMenuItem();
   252   if (!newNativeMenuItem)
   253     return NS_ERROR_FAILURE;
   254   [mNativeMenu addItem:newNativeMenuItem];
   256   [newNativeMenuItem setSubmenu:(NSMenu*)aMenu->NativeData()];
   258   return NS_OK;
   260   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   261 }
   263 // Includes all items, including hidden/collapsed ones
   264 uint32_t nsMenuX::GetItemCount()
   265 {
   266   return mMenuObjectsArray.Length();
   267 }
   269 // Includes all items, including hidden/collapsed ones
   270 nsMenuObjectX* nsMenuX::GetItemAt(uint32_t aPos)
   271 {
   272   if (aPos >= (uint32_t)mMenuObjectsArray.Length())
   273     return NULL;
   275   return mMenuObjectsArray[aPos];
   276 }
   278 // Only includes visible items
   279 nsresult nsMenuX::GetVisibleItemCount(uint32_t &aCount)
   280 {
   281   aCount = mVisibleItemsCount;
   282   return NS_OK;
   283 }
   285 // Only includes visible items. Note that this is provides O(N) access
   286 // If you need to iterate or search, consider using GetItemAt and doing your own filtering
   287 nsMenuObjectX* nsMenuX::GetVisibleItemAt(uint32_t aPos)
   288 {
   290   uint32_t count = mMenuObjectsArray.Length();
   291   if (aPos >= mVisibleItemsCount || aPos >= count)
   292     return NULL;
   294   // If there are no invisible items, can provide direct access
   295   if (mVisibleItemsCount == count)
   296     return mMenuObjectsArray[aPos];
   298   // Otherwise, traverse the array until we find the the item we're looking for.
   299   nsMenuObjectX* item;
   300   uint32_t visibleNodeIndex = 0;
   301   for (uint32_t i = 0; i < count; i++) {
   302     item = mMenuObjectsArray[i];
   303     if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(item->Content())) {
   304       if (aPos == visibleNodeIndex) {
   305         // we found the visible node we're looking for, return it
   306         return item;
   307       }
   308       visibleNodeIndex++;
   309     }
   310   }
   312   return NULL;
   313 }
   315 nsresult nsMenuX::RemoveAll()
   316 {
   317   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   319   if (mNativeMenu) {
   320     // clear command id's
   321     int itemCount = [mNativeMenu numberOfItems];
   322     for (int i = 0; i < itemCount; i++)
   323       mMenuGroupOwner->UnregisterCommand((uint32_t)[[mNativeMenu itemAtIndex:i] tag]);
   324     // get rid of Cocoa menu items
   325     for (int i = [mNativeMenu numberOfItems] - 1; i >= 0; i--)
   326       [mNativeMenu removeItemAtIndex:i];
   327   }
   329   mMenuObjectsArray.Clear();
   330   mVisibleItemsCount = 0;
   332   return NS_OK;
   334   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   335 }
   337 nsEventStatus nsMenuX::MenuOpened()
   338 {
   339   // Open the node.
   340   mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), true);
   342   // Fire a handler. If we're told to stop, don't build the menu at all
   343   bool keepProcessing = OnOpen();
   345   if (!mNeedsRebuild || !keepProcessing)
   346     return nsEventStatus_eConsumeNoDefault;
   348   if (!mConstructed || mNeedsRebuild) {
   349     if (mNeedsRebuild)
   350       RemoveAll();
   352     MenuConstruct();
   353     mConstructed = true;
   354   }
   356   nsEventStatus status = nsEventStatus_eIgnore;
   357   WidgetMouseEvent event(true, NS_XUL_POPUP_SHOWN, nullptr,
   358                          WidgetMouseEvent::eReal);
   360   nsCOMPtr<nsIContent> popupContent;
   361   GetMenuPopupContent(getter_AddRefs(popupContent));
   362   nsIContent* dispatchTo = popupContent ? popupContent : mContent;
   363   dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
   365   return nsEventStatus_eConsumeNoDefault;
   366 }
   368 void nsMenuX::MenuClosed()
   369 {
   370   if (mConstructed) {
   371     // Don't close if a handler tells us to stop.
   372     if (!OnClose())
   373       return;
   375     if (mNeedsRebuild)
   376       mConstructed = false;
   378     mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, true);
   380     nsEventStatus status = nsEventStatus_eIgnore;
   381     WidgetMouseEvent event(true, NS_XUL_POPUP_HIDDEN, nullptr,
   382                            WidgetMouseEvent::eReal);
   384     nsCOMPtr<nsIContent> popupContent;
   385     GetMenuPopupContent(getter_AddRefs(popupContent));
   386     nsIContent* dispatchTo = popupContent ? popupContent : mContent;
   387     dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
   389     mDestroyHandlerCalled = true;
   390     mConstructed = false;
   391   }
   392 }
   394 void nsMenuX::MenuConstruct()
   395 {
   396   mConstructed = false;
   397   gConstructingMenu = true;
   399   // reset destroy handler flag so that we'll know to fire it next time this menu goes away.
   400   mDestroyHandlerCalled = false;
   402   //printf("nsMenuX::MenuConstruct called for %s = %d \n", NS_LossyConvertUTF16toASCII(mLabel).get(), mNativeMenu);
   404   // Retrieve our menupopup.
   405   nsCOMPtr<nsIContent> menuPopup;
   406   GetMenuPopupContent(getter_AddRefs(menuPopup));
   407   if (!menuPopup) {
   408     gConstructingMenu = false;
   409     return;
   410   }
   412   // bug 365405: Manually wrap the menupopup node to make sure it's bounded
   413   if (!mXBLAttached) {
   414     nsresult rv;
   415     nsCOMPtr<nsIXPConnect> xpconnect =
   416       do_GetService(nsIXPConnect::GetCID(), &rv);
   417     if (NS_SUCCEEDED(rv)) {
   418       nsIDocument* ownerDoc = menuPopup->OwnerDoc();
   419       nsCOMPtr<nsIScriptGlobalObject> sgo;
   420       if (ownerDoc && (sgo = do_QueryInterface(ownerDoc->GetWindow()))) {
   421         nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
   422         JSObject* global = sgo->GetGlobalJSObject();
   423         if (scriptContext && global) {
   424           AutoPushJSContext cx(scriptContext->GetNativeContext());
   425           if (cx) {
   426             nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
   427             xpconnect->WrapNative(cx, global,
   428                                   menuPopup, NS_GET_IID(nsISupports),
   429                                   getter_AddRefs(wrapper));
   430             mXBLAttached = true;
   431           }
   432         }
   433       } 
   434     }
   435   }
   437   // Iterate over the kids
   438   uint32_t count = menuPopup->GetChildCount();
   439   for (uint32_t i = 0; i < count; i++) {
   440     nsIContent *child = menuPopup->GetChildAt(i);
   441     if (child) {
   442       // depending on the type, create a menu item, separator, or submenu
   443       nsIAtom *tag = child->Tag();
   444       if (tag == nsGkAtoms::menuitem || tag == nsGkAtoms::menuseparator)
   445         LoadMenuItem(child);
   446       else if (tag == nsGkAtoms::menu)
   447         LoadSubMenu(child);
   448     }
   449   } // for each menu item
   451   gConstructingMenu = false;
   452   mNeedsRebuild = false;
   453   // printf("Done building, mMenuObjectsArray.Count() = %d \n", mMenuObjectsArray.Count());
   454 }
   456 void nsMenuX::SetRebuild(bool aNeedsRebuild)
   457 {
   458   if (!gConstructingMenu)
   459     mNeedsRebuild = aNeedsRebuild;
   460 }
   462 nsresult nsMenuX::SetEnabled(bool aIsEnabled)
   463 {
   464   if (aIsEnabled != mIsEnabled) {
   465     // we always want to rebuild when this changes
   466     mIsEnabled = aIsEnabled;
   467     [mNativeMenuItem setEnabled:(BOOL)mIsEnabled];
   468   }
   469   return NS_OK;
   470 }
   472 nsresult nsMenuX::GetEnabled(bool* aIsEnabled)
   473 {
   474   NS_ENSURE_ARG_POINTER(aIsEnabled);
   475   *aIsEnabled = mIsEnabled;
   476   return NS_OK;
   477 }
   479 GeckoNSMenu* nsMenuX::CreateMenuWithGeckoString(nsString& menuTitle)
   480 {
   481   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   483   NSString* title = [NSString stringWithCharacters:(UniChar*)menuTitle.get() length:menuTitle.Length()];
   484   GeckoNSMenu* myMenu = [[GeckoNSMenu alloc] initWithTitle:title];
   485   [myMenu setDelegate:mMenuDelegate];
   487   // We don't want this menu to auto-enable menu items because then Cocoa
   488   // overrides our decisions and things get incorrectly enabled/disabled.
   489   [myMenu setAutoenablesItems:NO];
   491   // we used to install Carbon event handlers here, but since NSMenu* doesn't
   492   // create its underlying MenuRef until just before display, we delay until
   493   // that happens. Now we install the event handlers when Cocoa notifies
   494   // us that a menu is about to display - see the Cocoa MenuDelegate class.
   496   return myMenu;
   498   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   499 }
   501 void nsMenuX::LoadMenuItem(nsIContent* inMenuItemContent)
   502 {
   503   if (!inMenuItemContent)
   504     return;
   506   nsAutoString menuitemName;
   507   inMenuItemContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, menuitemName);
   509   // printf("menuitem %s \n", NS_LossyConvertUTF16toASCII(menuitemName).get());
   511   EMenuItemType itemType = eRegularMenuItemType;
   512   if (inMenuItemContent->Tag() == nsGkAtoms::menuseparator) {
   513     itemType = eSeparatorMenuItemType;
   514   }
   515   else {
   516     static nsIContent::AttrValuesArray strings[] =
   517   {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr};
   518     switch (inMenuItemContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
   519                                                strings, eCaseMatters)) {
   520       case 0: itemType = eCheckboxMenuItemType; break;
   521       case 1: itemType = eRadioMenuItemType; break;
   522     }
   523   }
   525   // Create the item.
   526   nsMenuItemX* menuItem = new nsMenuItemX();
   527   if (!menuItem)
   528     return;
   530   nsresult rv = menuItem->Create(this, menuitemName, itemType, mMenuGroupOwner, inMenuItemContent);
   531   if (NS_FAILED(rv)) {
   532     delete menuItem;
   533     return;
   534   }
   536   AddMenuItem(menuItem);
   538   // This needs to happen after the nsIMenuItem object is inserted into
   539   // our item array in AddMenuItem()
   540   menuItem->SetupIcon();
   541 }
   543 void nsMenuX::LoadSubMenu(nsIContent* inMenuContent)
   544 {
   545   nsAutoPtr<nsMenuX> menu(new nsMenuX());
   546   if (!menu)
   547     return;
   549   nsresult rv = menu->Create(this, mMenuGroupOwner, inMenuContent);
   550   if (NS_FAILED(rv))
   551     return;
   553   AddMenu(menu);
   555   // This needs to happen after the nsIMenu object is inserted into
   556   // our item array in AddMenu()
   557   menu->SetupIcon();
   559   menu.forget();
   560 }
   562 // This menu is about to open. Returns TRUE if we should keep processing the event,
   563 // FALSE if the handler wants to stop the opening of the menu.
   564 bool nsMenuX::OnOpen()
   565 {
   566   nsEventStatus status = nsEventStatus_eIgnore;
   567   WidgetMouseEvent event(true, NS_XUL_POPUP_SHOWING, nullptr,
   568                          WidgetMouseEvent::eReal);
   570   nsCOMPtr<nsIContent> popupContent;
   571   GetMenuPopupContent(getter_AddRefs(popupContent));
   573   nsresult rv = NS_OK;
   574   nsIContent* dispatchTo = popupContent ? popupContent : mContent;
   575   rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
   576   if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault)
   577     return false;
   579   // If the open is going to succeed we need to walk our menu items, checking to
   580   // see if any of them have a command attribute. If so, several attributes
   581   // must potentially be updated.
   583   // Get new popup content first since it might have changed as a result of the
   584   // NS_XUL_POPUP_SHOWING event above.
   585   GetMenuPopupContent(getter_AddRefs(popupContent));
   586   if (!popupContent)
   587     return true;
   589   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
   590   if (pm) {
   591     pm->UpdateMenuItems(popupContent);
   592   }
   594   return true;
   595 }
   597 // Returns TRUE if we should keep processing the event, FALSE if the handler
   598 // wants to stop the closing of the menu.
   599 bool nsMenuX::OnClose()
   600 {
   601   if (mDestroyHandlerCalled)
   602     return true;
   604   nsEventStatus status = nsEventStatus_eIgnore;
   605   WidgetMouseEvent event(true, NS_XUL_POPUP_HIDING, nullptr,
   606                          WidgetMouseEvent::eReal);
   608   nsCOMPtr<nsIContent> popupContent;
   609   GetMenuPopupContent(getter_AddRefs(popupContent));
   611   nsresult rv = NS_OK;
   612   nsIContent* dispatchTo = popupContent ? popupContent : mContent;
   613   rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
   615   mDestroyHandlerCalled = true;
   617   if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault)
   618     return false;
   620   return true;
   621 }
   623 // Find the |menupopup| child in the |popup| representing this menu. It should be one
   624 // of a very few children so we won't be iterating over a bazillion menu items to find
   625 // it (so the strcmp won't kill us).
   626 void nsMenuX::GetMenuPopupContent(nsIContent** aResult)
   627 {
   628   if (!aResult)
   629     return;
   630   *aResult = nullptr;
   632   // Check to see if we are a "menupopup" node (if we are a native menu).
   633   {
   634     int32_t dummy;
   635     nsCOMPtr<nsIAtom> tag = mContent->OwnerDoc()->BindingManager()->ResolveTag(mContent, &dummy);
   636     if (tag == nsGkAtoms::menupopup) {
   637       *aResult = mContent;
   638       NS_ADDREF(*aResult);
   639       return;
   640     }
   641   }
   643   // Otherwise check our child nodes.
   645   uint32_t count = mContent->GetChildCount();
   647   for (uint32_t i = 0; i < count; i++) {
   648     int32_t dummy;
   649     nsIContent *child = mContent->GetChildAt(i);
   650     nsCOMPtr<nsIAtom> tag = child->OwnerDoc()->BindingManager()->ResolveTag(child, &dummy);
   651     if (tag == nsGkAtoms::menupopup) {
   652       *aResult = child;
   653       NS_ADDREF(*aResult);
   654       return;
   655     }
   656   }
   657 }
   659 NSMenuItem* nsMenuX::NativeMenuItem()
   660 {
   661   return mNativeMenuItem;
   662 }
   664 bool nsMenuX::IsXULHelpMenu(nsIContent* aMenuContent)
   665 {
   666   bool retval = false;
   667   if (aMenuContent) {
   668     nsAutoString id;
   669     aMenuContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
   670     if (id.Equals(NS_LITERAL_STRING("helpMenu")))
   671       retval = true;
   672   }
   673   return retval;
   674 }
   676 //
   677 // nsChangeObserver
   678 //
   680 void nsMenuX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent,
   681                                       nsIAtom *aAttribute)
   682 {
   683   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   685   // ignore the |open| attribute, which is by far the most common
   686   if (gConstructingMenu || (aAttribute == nsGkAtoms::open))
   687     return;
   689   nsMenuObjectTypeX parentType = mParent->MenuObjectType();
   691   if (aAttribute == nsGkAtoms::disabled) {
   692     SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
   693                                       nsGkAtoms::_true, eCaseMatters));
   694   }
   695   else if (aAttribute == nsGkAtoms::label) {
   696     mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel);
   698     // invalidate my parent. If we're a submenu parent, we have to rebuild
   699     // the parent menu in order for the changes to be picked up. If we're
   700     // a regular menu, just change the title and redraw the menubar.
   701     if (parentType == eMenuBarObjectType) {
   702       // reuse the existing menu, to avoid rebuilding the root menu bar.
   703       NS_ASSERTION(mNativeMenu, "nsMenuX::AttributeChanged: invalid menu handle.");
   704       NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel);
   705       [mNativeMenu setTitle:newCocoaLabelString];
   706     }
   707     else if (parentType == eSubmenuObjectType) {
   708       static_cast<nsMenuX*>(mParent)->SetRebuild(true);
   709     }    
   710     else if (parentType == eStandaloneNativeMenuObjectType) {
   711       static_cast<nsStandaloneNativeMenu*>(mParent)->GetMenuXObject()->SetRebuild(true);
   712     }
   713   }
   714   else if (aAttribute == nsGkAtoms::hidden || aAttribute == nsGkAtoms::collapsed) {
   715     SetRebuild(true);
   717     bool contentIsHiddenOrCollapsed = nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent);
   719     // don't do anything if the state is correct already
   720     if (contentIsHiddenOrCollapsed != mVisible)
   721       return;
   723     if (contentIsHiddenOrCollapsed) {
   724       if (parentType == eMenuBarObjectType ||
   725           parentType == eSubmenuObjectType ||
   726           parentType == eStandaloneNativeMenuObjectType) {
   727         NSMenu* parentMenu = (NSMenu*)mParent->NativeData();
   728         // An exception will get thrown if we try to remove an item that isn't
   729         // in the menu.
   730         if ([parentMenu indexOfItem:mNativeMenuItem] != -1)
   731           [parentMenu removeItem:mNativeMenuItem];
   732         mVisible = false;
   733       }
   734     }
   735     else {
   736       if (parentType == eMenuBarObjectType ||
   737           parentType == eSubmenuObjectType ||
   738           parentType == eStandaloneNativeMenuObjectType) {
   739         int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(mParent, this);
   740         if (parentType == eMenuBarObjectType) {
   741           // Before inserting we need to figure out if we should take the native
   742           // application menu into account.
   743           nsMenuBarX* mb = static_cast<nsMenuBarX*>(mParent);
   744           if (mb->MenuContainsAppMenu())
   745             insertionIndex++;
   746         }
   747         NSMenu* parentMenu = (NSMenu*)mParent->NativeData();
   748         [parentMenu insertItem:mNativeMenuItem atIndex:insertionIndex];
   749         [mNativeMenuItem setSubmenu:mNativeMenu];
   750         mVisible = true;
   751       }
   752     }
   753   }
   754   else if (aAttribute == nsGkAtoms::image) {
   755     SetupIcon();
   756   }
   758   NS_OBJC_END_TRY_ABORT_BLOCK;
   759 }
   761 void nsMenuX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild,
   762                                     int32_t aIndexInContainer)
   763 {
   764   if (gConstructingMenu)
   765     return;
   767   SetRebuild(true);
   768   mMenuGroupOwner->UnregisterForContentChanges(aChild);
   769 }
   771 void nsMenuX::ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
   772                                      nsIContent *aChild)
   773 {
   774   if (gConstructingMenu)
   775     return;
   777   SetRebuild(true);
   778 }
   780 nsresult nsMenuX::SetupIcon()
   781 {
   782   // In addition to out-of-memory, menus that are children of the menu bar
   783   // will not have mIcon set.
   784   if (!mIcon)
   785     return NS_ERROR_OUT_OF_MEMORY;
   787   return mIcon->SetupIcon();
   788 }
   790 //
   791 // MenuDelegate Objective-C class, used to set up Carbon events
   792 //
   794 @implementation MenuDelegate
   796 - (id)initWithGeckoMenu:(nsMenuX*)geckoMenu
   797 {
   798   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   800   if ((self = [super init])) {
   801     NS_ASSERTION(geckoMenu, "Cannot initialize native menu delegate with NULL gecko menu! Will crash!");
   802     mGeckoMenu = geckoMenu;
   803   }
   804   return self;
   806   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   807 }
   809 - (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item
   810 {
   811   if (!menu || !item || !mGeckoMenu)
   812     return;
   814   nsMenuObjectX* target = mGeckoMenu->GetVisibleItemAt((uint32_t)[menu indexOfItem:item]);
   815   if (target && (target->MenuObjectType() == eMenuItemObjectType)) {
   816     nsMenuItemX* targetMenuItem = static_cast<nsMenuItemX*>(target);
   817     bool handlerCalledPreventDefault; // but we don't actually care
   818     targetMenuItem->DispatchDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), &handlerCalledPreventDefault);
   819   }
   820 }
   822 - (void)menuWillOpen:(NSMenu *)menu
   823 {
   824   if (!mGeckoMenu)
   825     return;
   827   // Don't do anything while the OS is (re)indexing our menus (on Leopard and
   828   // higher).  This stops the Help menu from being able to search in our
   829   // menus, but it also resolves many other problems.
   830   if (nsMenuX::sIndexingMenuLevel > 0)
   831     return;
   833   nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
   834   if (rollupListener) {
   835     nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
   836     if (rollupWidget) {
   837       rollupListener->Rollup(0, nullptr, nullptr);
   838       [menu cancelTracking];
   839       return;
   840     }
   841   }
   842   mGeckoMenu->MenuOpened();
   843 }
   845 - (void)menuDidClose:(NSMenu *)menu
   846 {
   847   if (!mGeckoMenu)
   848     return;
   850   // Don't do anything while the OS is (re)indexing our menus (on Leopard and
   851   // higher).  This stops the Help menu from being able to search in our
   852   // menus, but it also resolves many other problems.
   853   if (nsMenuX::sIndexingMenuLevel > 0)
   854     return;
   856   mGeckoMenu->MenuClosed();
   857 }
   859 @end
   861 // OS X Leopard (at least as of 10.5.2) has an obscure bug triggered by some
   862 // behavior that's present in Mozilla.org browsers but not (as best I can
   863 // tell) in Apple products like Safari.  (It's not yet clear exactly what this
   864 // behavior is.)
   865 //
   866 // The bug is that sometimes you crash on quit in nsMenuX::RemoveAll(), on a
   867 // call to [NSMenu removeItemAtIndex:].  The crash is caused by trying to
   868 // access a deleted NSMenuItem object (sometimes (perhaps always?) by trying
   869 // to send it a _setChangedFlags: message).  Though this object was deleted
   870 // some time ago, it remains registered as a potential target for a particular
   871 // key equivalent.  So when [NSMenu removeItemAtIndex:] removes the current
   872 // target for that same key equivalent, the OS tries to "activate" the
   873 // previous target.
   874 //
   875 // The underlying reason appears to be that NSMenu's _addItem:toTable: and
   876 // _removeItem:fromTable: methods (which are used to keep a hashtable of
   877 // registered key equivalents) don't properly "retain" and "release"
   878 // NSMenuItem objects as they are added to and removed from the hashtable.
   879 //
   880 // Our (hackish) workaround is to shadow the OS's hashtable with another
   881 // hastable of our own (gShadowKeyEquivDB), and use it to "retain" and
   882 // "release" NSMenuItem objects as needed.  This resolves bmo bugs 422287 and
   883 // 423669.  When (if) Apple fixes this bug, we can remove this workaround.
   885 static NSMutableDictionary *gShadowKeyEquivDB = nil;
   887 // Class for values in gShadowKeyEquivDB.
   889 @interface KeyEquivDBItem : NSObject
   890 {
   891   NSMenuItem *mItem;
   892   NSMutableSet *mTables;
   893 }
   895 - (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable;
   896 - (BOOL)hasTable:(NSMapTable *)aTable;
   897 - (int)addTable:(NSMapTable *)aTable;
   898 - (int)removeTable:(NSMapTable *)aTable;
   900 @end
   902 @implementation KeyEquivDBItem
   904 - (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable
   905 {
   906   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   908   if (!gShadowKeyEquivDB)
   909     gShadowKeyEquivDB = [[NSMutableDictionary alloc] init];
   910   self = [super init];
   911   if (aItem && aTable) {
   912     mTables = [[NSMutableSet alloc] init];
   913     mItem = [aItem retain];
   914     [mTables addObject:[NSValue valueWithPointer:aTable]];
   915   } else {
   916     mTables = nil;
   917     mItem = nil;
   918   }
   919   return self;
   921   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   922 }
   924 - (void)dealloc
   925 {
   926   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   928   if (mTables)
   929     [mTables release];
   930   if (mItem)
   931     [mItem release];
   932   [super dealloc];
   934   NS_OBJC_END_TRY_ABORT_BLOCK;
   935 }
   937 - (BOOL)hasTable:(NSMapTable *)aTable
   938 {
   939   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
   941   return [mTables member:[NSValue valueWithPointer:aTable]] ? YES : NO;
   943   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
   944 }
   946 // Does nothing if aTable (its index value) is already present in mTables.
   947 - (int)addTable:(NSMapTable *)aTable
   948 {
   949   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
   951   if (aTable)
   952     [mTables addObject:[NSValue valueWithPointer:aTable]];
   953   return [mTables count];
   955   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
   956 }
   958 - (int)removeTable:(NSMapTable *)aTable
   959 {
   960   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
   962   if (aTable) {
   963     NSValue *objectToRemove =
   964       [mTables member:[NSValue valueWithPointer:aTable]];
   965     if (objectToRemove)
   966       [mTables removeObject:objectToRemove];
   967   }
   968   return [mTables count];
   970   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
   971 }
   973 @end
   975 @interface NSMenu (MethodSwizzling)
   976 + (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable;
   977 + (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable;
   978 @end
   980 @implementation NSMenu (MethodSwizzling)
   982 + (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable
   983 {
   984   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   986   if (aItem && aTable) {
   987     NSValue *key = [NSValue valueWithPointer:aItem];
   988     KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key];
   989     if (shadowItem) {
   990       [shadowItem addTable:aTable];
   991     } else {
   992       shadowItem = [[KeyEquivDBItem alloc] initWithItem:aItem table:aTable];
   993       [gShadowKeyEquivDB setObject:shadowItem forKey:key];
   994       // Release after [NSMutableDictionary setObject:forKey:] retains it (so
   995       // that it will get dealloced when removeObjectForKey: is called).
   996       [shadowItem release];
   997     }
   998   }
  1000   NS_OBJC_END_TRY_ABORT_BLOCK;
  1002   [self nsMenuX_NSMenu_addItem:aItem toTable:aTable];
  1005 + (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable
  1007   [self nsMenuX_NSMenu_removeItem:aItem fromTable:aTable];
  1009   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
  1011   if (aItem && aTable) {
  1012     NSValue *key = [NSValue valueWithPointer:aItem];
  1013     KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key];
  1014     if (shadowItem && [shadowItem hasTable:aTable]) {
  1015       if (![shadowItem removeTable:aTable])
  1016         [gShadowKeyEquivDB removeObjectForKey:key];
  1020   NS_OBJC_END_TRY_ABORT_BLOCK;
  1023 @end
  1025 // This class is needed to keep track of when the OS is (re)indexing all of
  1026 // our menus.  This appears to only happen on Leopard and higher, and can
  1027 // be triggered by opening the Help menu.  Some operations are unsafe while
  1028 // this is happening -- notably the calls to [[NSImage alloc]
  1029 // initWithSize:imageRect.size] and [newImage lockFocus] in nsMenuItemIconX::
  1030 // OnStopFrame().  But we don't yet have a complete list, and Apple doesn't
  1031 // yet have any documentation on this subject.  (Apple also doesn't yet have
  1032 // any documented way to find the information we seek here.)  The "original"
  1033 // of this class (the one whose indexMenuBarDynamically method we hook) is
  1034 // defined in the Shortcut framework in /System/Library/PrivateFrameworks.
  1035 @interface NSObject (SCTGRLIndexMethodSwizzling)
  1036 - (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically;
  1037 @end
  1039 @implementation NSObject (SCTGRLIndexMethodSwizzling)
  1041 - (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically
  1043   // This method appears to be called (once) whenever the OS (re)indexes our
  1044   // menus.  sIndexingMenuLevel is a int32_t just in case it might be
  1045   // reentered.  As it's running, it spawns calls to two undocumented
  1046   // HIToolbox methods (_SimulateMenuOpening() and _SimulateMenuClosed()),
  1047   // which "simulate" the opening and closing of our menus without actually
  1048   // displaying them.
  1049   ++nsMenuX::sIndexingMenuLevel;
  1050   [self nsMenuX_SCTGRLIndex_indexMenuBarDynamically];
  1051   --nsMenuX::sIndexingMenuLevel;
  1054 @end

mercurial