diff options
Diffstat (limited to 'widget/cocoa/nsMenuX.mm')
-rw-r--r-- | widget/cocoa/nsMenuX.mm | 1051 |
1 files changed, 0 insertions, 1051 deletions
diff --git a/widget/cocoa/nsMenuX.mm b/widget/cocoa/nsMenuX.mm deleted file mode 100644 index 757221eac6..0000000000 --- a/widget/cocoa/nsMenuX.mm +++ /dev/null @@ -1,1051 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include <dlfcn.h> - -#include "nsMenuX.h" -#include "nsMenuItemX.h" -#include "nsMenuUtilsX.h" -#include "nsMenuItemIconX.h" -#include "nsStandaloneNativeMenu.h" - -#include "nsObjCExceptions.h" - -#include "nsToolkit.h" -#include "nsCocoaUtils.h" -#include "nsCOMPtr.h" -#include "prinrval.h" -#include "nsString.h" -#include "nsReadableUtils.h" -#include "nsUnicharUtils.h" -#include "plstr.h" -#include "nsGkAtoms.h" -#include "nsCRT.h" -#include "nsBaseWidget.h" - -#include "nsIDocument.h" -#include "nsIContent.h" -#include "nsIDOMDocument.h" -#include "nsIDocumentObserver.h" -#include "nsIComponentManager.h" -#include "nsIRollupListener.h" -#include "nsIDOMElement.h" -#include "nsBindingManager.h" -#include "nsIServiceManager.h" -#include "nsXULPopupManager.h" -#include "mozilla/dom/ScriptSettings.h" - -#include "jsapi.h" -#include "nsIScriptGlobalObject.h" -#include "nsIScriptContext.h" -#include "nsIXPConnect.h" - -#include "mozilla/MouseEvents.h" - -using namespace mozilla; - -static bool gConstructingMenu = false; -static bool gMenuMethodsSwizzled = false; - -int32_t nsMenuX::sIndexingMenuLevel = 0; - - -// -// Objective-C class used for representedObject -// - -@implementation MenuItemInfo - -- (id) initWithMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner -{ - if ((self = [super init]) != nil) { - [self setMenuGroupOwner:aMenuGroupOwner]; - } - return self; -} - -- (void) dealloc -{ - [self setMenuGroupOwner:nullptr]; - [super dealloc]; -} - -- (nsMenuGroupOwnerX *) menuGroupOwner -{ - return mMenuGroupOwner; -} - -- (void) setMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner -{ - // weak reference as the nsMenuGroupOwnerX owns all of its sub-objects - mMenuGroupOwner = aMenuGroupOwner; - if (aMenuGroupOwner) { - aMenuGroupOwner->AddMenuItemInfoToSet(self); - } -} - -@end - - -// -// nsMenuX -// - -nsMenuX::nsMenuX() -: mVisibleItemsCount(0), mParent(nullptr), mMenuGroupOwner(nullptr), - mNativeMenu(nil), mNativeMenuItem(nil), mIsEnabled(true), - mDestroyHandlerCalled(false), mNeedsRebuild(true), - mConstructed(false), mVisible(true), mXBLAttached(false) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - if (!gMenuMethodsSwizzled) { - nsToolkit::SwizzleMethods([NSMenu class], @selector(_addItem:toTable:), - @selector(nsMenuX_NSMenu_addItem:toTable:), true); - nsToolkit::SwizzleMethods([NSMenu class], @selector(_removeItem:fromTable:), - @selector(nsMenuX_NSMenu_removeItem:fromTable:), true); - // On SnowLeopard the Shortcut framework (which contains the - // SCTGRLIndex class) is loaded on demand, whenever the user first opens - // a menu (which normally hasn't happened yet). So we need to load it - // here explicitly. - dlopen("/System/Library/PrivateFrameworks/Shortcut.framework/Shortcut", RTLD_LAZY); - Class SCTGRLIndexClass = ::NSClassFromString(@"SCTGRLIndex"); - nsToolkit::SwizzleMethods(SCTGRLIndexClass, @selector(indexMenuBarDynamically), - @selector(nsMenuX_SCTGRLIndex_indexMenuBarDynamically)); - - gMenuMethodsSwizzled = true; - } - - mMenuDelegate = [[MenuDelegate alloc] initWithGeckoMenu:this]; - - if (!nsMenuBarX::sNativeEventTarget) - nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init]; - - MOZ_COUNT_CTOR(nsMenuX); - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -nsMenuX::~nsMenuX() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - // Prevent the icon object from outliving us. - if (mIcon) - mIcon->Destroy(); - - RemoveAll(); - - [mNativeMenu setDelegate:nil]; - [mNativeMenu release]; - [mMenuDelegate release]; - // autorelease the native menu item so that anything else happening to this - // object happens before the native menu item actually dies - [mNativeMenuItem autorelease]; - - // alert the change notifier we don't care no more - if (mContent) - mMenuGroupOwner->UnregisterForContentChanges(mContent); - - MOZ_COUNT_DTOR(nsMenuX); - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -nsresult nsMenuX::Create(nsMenuObjectX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; - - mContent = aNode; - mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel); - mNativeMenu = CreateMenuWithGeckoString(mLabel); - - // register this menu to be notified when changes are made to our content object - mMenuGroupOwner = aMenuGroupOwner; // weak ref - NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one"); - mMenuGroupOwner->RegisterForContentChanges(mContent, this); - - mParent = aParent; - // our parent could be either a menu bar (if we're toplevel) or a menu (if we're a submenu) - -#ifdef DEBUG - nsMenuObjectTypeX parentType = -#endif - mParent->MenuObjectType(); - NS_ASSERTION((parentType == eMenuBarObjectType || parentType == eSubmenuObjectType || parentType == eStandaloneNativeMenuObjectType), - "Menu parent not a menu bar, menu, or native menu!"); - - if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent)) - mVisible = false; - if (mContent->GetChildCount() == 0) - mVisible = false; - - NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel); - mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""]; - [mNativeMenuItem setSubmenu:mNativeMenu]; - - SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, - nsGkAtoms::_true, eCaseMatters)); - - // We call MenuConstruct here because keyboard commands are dependent upon - // native menu items being created. If we only call MenuConstruct when a menu - // is actually selected, then we can't access keyboard commands until the - // menu gets selected, which is bad. - MenuConstruct(); - - mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem); - - return NS_OK; - - NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; -} - -nsresult nsMenuX::AddMenuItem(nsMenuItemX* aMenuItem) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; - - if (!aMenuItem) - return NS_ERROR_INVALID_ARG; - - mMenuObjectsArray.AppendElement(aMenuItem); - if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(aMenuItem->Content())) - return NS_OK; - ++mVisibleItemsCount; - - NSMenuItem* newNativeMenuItem = (NSMenuItem*)aMenuItem->NativeData(); - - // add the menu item to this menu - [mNativeMenu addItem:newNativeMenuItem]; - - // set up target/action - [newNativeMenuItem setTarget:nsMenuBarX::sNativeEventTarget]; - [newNativeMenuItem setAction:@selector(menuItemHit:)]; - - // set its command. we get the unique command id from the menubar - [newNativeMenuItem setTag:mMenuGroupOwner->RegisterForCommand(aMenuItem)]; - MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:mMenuGroupOwner]; - [newNativeMenuItem setRepresentedObject:info]; - [info release]; - - return NS_OK; - - NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; -} - -nsMenuX* nsMenuX::AddMenu(UniquePtr<nsMenuX> aMenu) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - // aMenu transfers ownership to mMenuObjectsArray and becomes nullptr, so - // we need to keep a raw pointer to access it conveniently. - nsMenuX* menu = aMenu.get(); - mMenuObjectsArray.AppendElement(Move(aMenu)); - - if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) { - return menu; - } - - ++mVisibleItemsCount; - - // We have to add a menu item and then associate the menu with it - NSMenuItem* newNativeMenuItem = menu->NativeMenuItem(); - if (newNativeMenuItem) { - [mNativeMenu addItem:newNativeMenuItem]; - [newNativeMenuItem setSubmenu:(NSMenu*)menu->NativeData()]; - } - - return menu; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr); -} - -// Includes all items, including hidden/collapsed ones -uint32_t nsMenuX::GetItemCount() -{ - return mMenuObjectsArray.Length(); -} - -// Includes all items, including hidden/collapsed ones -nsMenuObjectX* nsMenuX::GetItemAt(uint32_t aPos) -{ - if (aPos >= (uint32_t)mMenuObjectsArray.Length()) - return NULL; - - return mMenuObjectsArray[aPos].get(); -} - -// Only includes visible items -nsresult nsMenuX::GetVisibleItemCount(uint32_t &aCount) -{ - aCount = mVisibleItemsCount; - return NS_OK; -} - -// Only includes visible items. Note that this is provides O(N) access -// If you need to iterate or search, consider using GetItemAt and doing your own filtering -nsMenuObjectX* nsMenuX::GetVisibleItemAt(uint32_t aPos) -{ - - uint32_t count = mMenuObjectsArray.Length(); - if (aPos >= mVisibleItemsCount || aPos >= count) - return NULL; - - // If there are no invisible items, can provide direct access - if (mVisibleItemsCount == count) - return mMenuObjectsArray[aPos].get(); - - // Otherwise, traverse the array until we find the the item we're looking for. - nsMenuObjectX* item; - uint32_t visibleNodeIndex = 0; - for (uint32_t i = 0; i < count; i++) { - item = mMenuObjectsArray[i].get(); - if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(item->Content())) { - if (aPos == visibleNodeIndex) { - // we found the visible node we're looking for, return it - return item; - } - visibleNodeIndex++; - } - } - - return NULL; -} - -nsresult nsMenuX::RemoveAll() -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; - - if (mNativeMenu) { - // clear command id's - int itemCount = [mNativeMenu numberOfItems]; - for (int i = 0; i < itemCount; i++) - mMenuGroupOwner->UnregisterCommand((uint32_t)[[mNativeMenu itemAtIndex:i] tag]); - // get rid of Cocoa menu items - for (int i = [mNativeMenu numberOfItems] - 1; i >= 0; i--) - [mNativeMenu removeItemAtIndex:i]; - } - - mMenuObjectsArray.Clear(); - mVisibleItemsCount = 0; - - return NS_OK; - - NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; -} - -nsEventStatus nsMenuX::MenuOpened() -{ - // Open the node. - mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), true); - - // Fire a handler. If we're told to stop, don't build the menu at all - bool keepProcessing = OnOpen(); - - if (!mNeedsRebuild || !keepProcessing) - return nsEventStatus_eConsumeNoDefault; - - if (!mConstructed || mNeedsRebuild) { - if (mNeedsRebuild) - RemoveAll(); - - MenuConstruct(); - mConstructed = true; - } - - nsEventStatus status = nsEventStatus_eIgnore; - WidgetMouseEvent event(true, eXULPopupShown, nullptr, - WidgetMouseEvent::eReal); - - nsCOMPtr<nsIContent> popupContent; - GetMenuPopupContent(getter_AddRefs(popupContent)); - nsIContent* dispatchTo = popupContent ? popupContent : mContent; - dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status); - - return nsEventStatus_eConsumeNoDefault; -} - -void nsMenuX::MenuClosed() -{ - if (mConstructed) { - // Don't close if a handler tells us to stop. - if (!OnClose()) - return; - - if (mNeedsRebuild) - mConstructed = false; - - mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, true); - - nsEventStatus status = nsEventStatus_eIgnore; - WidgetMouseEvent event(true, eXULPopupHidden, nullptr, - WidgetMouseEvent::eReal); - - nsCOMPtr<nsIContent> popupContent; - GetMenuPopupContent(getter_AddRefs(popupContent)); - nsIContent* dispatchTo = popupContent ? popupContent : mContent; - dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status); - - mDestroyHandlerCalled = true; - mConstructed = false; - } -} - -void nsMenuX::MenuConstruct() -{ - mConstructed = false; - gConstructingMenu = true; - - // reset destroy handler flag so that we'll know to fire it next time this menu goes away. - mDestroyHandlerCalled = false; - - //printf("nsMenuX::MenuConstruct called for %s = %d \n", NS_LossyConvertUTF16toASCII(mLabel).get(), mNativeMenu); - - // Retrieve our menupopup. - nsCOMPtr<nsIContent> menuPopup; - GetMenuPopupContent(getter_AddRefs(menuPopup)); - if (!menuPopup) { - gConstructingMenu = false; - return; - } - - // bug 365405: Manually wrap the menupopup node to make sure it's bounded - if (!mXBLAttached) { - nsresult rv; - nsCOMPtr<nsIXPConnect> xpconnect = - do_GetService(nsIXPConnect::GetCID(), &rv); - if (NS_SUCCEEDED(rv)) { - nsIDocument* ownerDoc = menuPopup->OwnerDoc(); - dom::AutoJSAPI jsapi; - if (ownerDoc && jsapi.Init(ownerDoc->GetInnerWindow())) { - JSContext* cx = jsapi.cx(); - JS::RootedObject ignoredObj(cx); - xpconnect->WrapNative(cx, JS::CurrentGlobalOrNull(cx), menuPopup, - NS_GET_IID(nsISupports), ignoredObj.address()); - mXBLAttached = true; - } - } - } - - // Iterate over the kids - uint32_t count = menuPopup->GetChildCount(); - for (uint32_t i = 0; i < count; i++) { - nsIContent *child = menuPopup->GetChildAt(i); - if (child) { - // depending on the type, create a menu item, separator, or submenu - if (child->IsAnyOfXULElements(nsGkAtoms::menuitem, - nsGkAtoms::menuseparator)) { - LoadMenuItem(child); - } else if (child->IsXULElement(nsGkAtoms::menu)) { - LoadSubMenu(child); - } - } - } // for each menu item - - gConstructingMenu = false; - mNeedsRebuild = false; - // printf("Done building, mMenuObjectsArray.Count() = %d \n", mMenuObjectsArray.Count()); -} - -void nsMenuX::SetRebuild(bool aNeedsRebuild) -{ - if (!gConstructingMenu) - mNeedsRebuild = aNeedsRebuild; -} - -nsresult nsMenuX::SetEnabled(bool aIsEnabled) -{ - if (aIsEnabled != mIsEnabled) { - // we always want to rebuild when this changes - mIsEnabled = aIsEnabled; - [mNativeMenuItem setEnabled:(BOOL)mIsEnabled]; - } - return NS_OK; -} - -nsresult nsMenuX::GetEnabled(bool* aIsEnabled) -{ - NS_ENSURE_ARG_POINTER(aIsEnabled); - *aIsEnabled = mIsEnabled; - return NS_OK; -} - -GeckoNSMenu* nsMenuX::CreateMenuWithGeckoString(nsString& menuTitle) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - NSString* title = [NSString stringWithCharacters:(UniChar*)menuTitle.get() length:menuTitle.Length()]; - GeckoNSMenu* myMenu = [[GeckoNSMenu alloc] initWithTitle:title]; - [myMenu setDelegate:mMenuDelegate]; - - // We don't want this menu to auto-enable menu items because then Cocoa - // overrides our decisions and things get incorrectly enabled/disabled. - [myMenu setAutoenablesItems:NO]; - - // we used to install Carbon event handlers here, but since NSMenu* doesn't - // create its underlying MenuRef until just before display, we delay until - // that happens. Now we install the event handlers when Cocoa notifies - // us that a menu is about to display - see the Cocoa MenuDelegate class. - - return myMenu; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - -void nsMenuX::LoadMenuItem(nsIContent* inMenuItemContent) -{ - if (!inMenuItemContent) - return; - - nsAutoString menuitemName; - inMenuItemContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, menuitemName); - - // printf("menuitem %s \n", NS_LossyConvertUTF16toASCII(menuitemName).get()); - - EMenuItemType itemType = eRegularMenuItemType; - if (inMenuItemContent->IsXULElement(nsGkAtoms::menuseparator)) { - itemType = eSeparatorMenuItemType; - } - else { - static nsIContent::AttrValuesArray strings[] = - {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr}; - switch (inMenuItemContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, - strings, eCaseMatters)) { - case 0: itemType = eCheckboxMenuItemType; break; - case 1: itemType = eRadioMenuItemType; break; - } - } - - // Create the item. - nsMenuItemX* menuItem = new nsMenuItemX(); - if (!menuItem) - return; - - nsresult rv = menuItem->Create(this, menuitemName, itemType, mMenuGroupOwner, inMenuItemContent); - if (NS_FAILED(rv)) { - delete menuItem; - return; - } - - AddMenuItem(menuItem); - - // This needs to happen after the nsIMenuItem object is inserted into - // our item array in AddMenuItem() - menuItem->SetupIcon(); -} - -void nsMenuX::LoadSubMenu(nsIContent* inMenuContent) -{ - auto menu = MakeUnique<nsMenuX>(); - if (!menu) - return; - - nsresult rv = menu->Create(this, mMenuGroupOwner, inMenuContent); - if (NS_FAILED(rv)) - return; - - // |menu|'s ownership is transfer to AddMenu but, if it is successfully - // added, we can access it via the returned raw pointer. - nsMenuX* menu_ptr = AddMenu(Move(menu)); - - // This needs to happen after the nsIMenu object is inserted into - // our item array in AddMenu() - if (menu_ptr) { - menu_ptr->SetupIcon(); - } -} - -// This menu is about to open. Returns TRUE if we should keep processing the event, -// FALSE if the handler wants to stop the opening of the menu. -bool nsMenuX::OnOpen() -{ - nsEventStatus status = nsEventStatus_eIgnore; - WidgetMouseEvent event(true, eXULPopupShowing, nullptr, - WidgetMouseEvent::eReal); - - nsCOMPtr<nsIContent> popupContent; - GetMenuPopupContent(getter_AddRefs(popupContent)); - - nsresult rv = NS_OK; - nsIContent* dispatchTo = popupContent ? popupContent : mContent; - rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status); - if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault) - return false; - - // If the open is going to succeed we need to walk our menu items, checking to - // see if any of them have a command attribute. If so, several attributes - // must potentially be updated. - - // Get new popup content first since it might have changed as a result of the - // eXULPopupShowing event above. - GetMenuPopupContent(getter_AddRefs(popupContent)); - if (!popupContent) - return true; - - nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); - if (pm) { - pm->UpdateMenuItems(popupContent); - } - - return true; -} - -// Returns TRUE if we should keep processing the event, FALSE if the handler -// wants to stop the closing of the menu. -bool nsMenuX::OnClose() -{ - if (mDestroyHandlerCalled) - return true; - - nsEventStatus status = nsEventStatus_eIgnore; - WidgetMouseEvent event(true, eXULPopupHiding, nullptr, - WidgetMouseEvent::eReal); - - nsCOMPtr<nsIContent> popupContent; - GetMenuPopupContent(getter_AddRefs(popupContent)); - - nsresult rv = NS_OK; - nsIContent* dispatchTo = popupContent ? popupContent : mContent; - rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status); - - mDestroyHandlerCalled = true; - - if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault) - return false; - - return true; -} - -// Find the |menupopup| child in the |popup| representing this menu. It should be one -// of a very few children so we won't be iterating over a bazillion menu items to find -// it (so the strcmp won't kill us). -void nsMenuX::GetMenuPopupContent(nsIContent** aResult) -{ - if (!aResult) - return; - *aResult = nullptr; - - // Check to see if we are a "menupopup" node (if we are a native menu). - { - int32_t dummy; - nsCOMPtr<nsIAtom> tag = mContent->OwnerDoc()->BindingManager()->ResolveTag(mContent, &dummy); - if (tag == nsGkAtoms::menupopup) { - *aResult = mContent; - NS_ADDREF(*aResult); - return; - } - } - - // Otherwise check our child nodes. - - uint32_t count = mContent->GetChildCount(); - - for (uint32_t i = 0; i < count; i++) { - int32_t dummy; - nsIContent *child = mContent->GetChildAt(i); - nsCOMPtr<nsIAtom> tag = child->OwnerDoc()->BindingManager()->ResolveTag(child, &dummy); - if (tag == nsGkAtoms::menupopup) { - *aResult = child; - NS_ADDREF(*aResult); - return; - } - } -} - -NSMenuItem* nsMenuX::NativeMenuItem() -{ - return mNativeMenuItem; -} - -bool nsMenuX::IsXULHelpMenu(nsIContent* aMenuContent) -{ - bool retval = false; - if (aMenuContent) { - nsAutoString id; - aMenuContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id); - if (id.Equals(NS_LITERAL_STRING("helpMenu"))) - retval = true; - } - return retval; -} - -// -// nsChangeObserver -// - -void nsMenuX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, - nsIAtom *aAttribute) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - // ignore the |open| attribute, which is by far the most common - if (gConstructingMenu || (aAttribute == nsGkAtoms::open)) - return; - - nsMenuObjectTypeX parentType = mParent->MenuObjectType(); - - if (aAttribute == nsGkAtoms::disabled) { - SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, - nsGkAtoms::_true, eCaseMatters)); - } - else if (aAttribute == nsGkAtoms::label) { - mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel); - - // invalidate my parent. If we're a submenu parent, we have to rebuild - // the parent menu in order for the changes to be picked up. If we're - // a regular menu, just change the title and redraw the menubar. - if (parentType == eMenuBarObjectType) { - // reuse the existing menu, to avoid rebuilding the root menu bar. - NS_ASSERTION(mNativeMenu, "nsMenuX::AttributeChanged: invalid menu handle."); - NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel); - [mNativeMenu setTitle:newCocoaLabelString]; - } - else if (parentType == eSubmenuObjectType) { - static_cast<nsMenuX*>(mParent)->SetRebuild(true); - } - else if (parentType == eStandaloneNativeMenuObjectType) { - static_cast<nsStandaloneNativeMenu*>(mParent)->GetMenuXObject()->SetRebuild(true); - } - } - else if (aAttribute == nsGkAtoms::hidden || aAttribute == nsGkAtoms::collapsed) { - SetRebuild(true); - - bool contentIsHiddenOrCollapsed = nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent); - - // don't do anything if the state is correct already - if (contentIsHiddenOrCollapsed != mVisible) - return; - - if (contentIsHiddenOrCollapsed) { - if (parentType == eMenuBarObjectType || - parentType == eSubmenuObjectType || - parentType == eStandaloneNativeMenuObjectType) { - NSMenu* parentMenu = (NSMenu*)mParent->NativeData(); - // An exception will get thrown if we try to remove an item that isn't - // in the menu. - if ([parentMenu indexOfItem:mNativeMenuItem] != -1) - [parentMenu removeItem:mNativeMenuItem]; - mVisible = false; - } - } - else { - if (parentType == eMenuBarObjectType || - parentType == eSubmenuObjectType || - parentType == eStandaloneNativeMenuObjectType) { - int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(mParent, this); - if (parentType == eMenuBarObjectType) { - // Before inserting we need to figure out if we should take the native - // application menu into account. - nsMenuBarX* mb = static_cast<nsMenuBarX*>(mParent); - if (mb->MenuContainsAppMenu()) - insertionIndex++; - } - NSMenu* parentMenu = (NSMenu*)mParent->NativeData(); - [parentMenu insertItem:mNativeMenuItem atIndex:insertionIndex]; - [mNativeMenuItem setSubmenu:mNativeMenu]; - mVisible = true; - } - } - } - else if (aAttribute == nsGkAtoms::image) { - SetupIcon(); - } - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -void nsMenuX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild, - int32_t aIndexInContainer) -{ - if (gConstructingMenu) - return; - - SetRebuild(true); - mMenuGroupOwner->UnregisterForContentChanges(aChild); -} - -void nsMenuX::ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer, - nsIContent *aChild) -{ - if (gConstructingMenu) - return; - - SetRebuild(true); -} - -nsresult nsMenuX::SetupIcon() -{ - // In addition to out-of-memory, menus that are children of the menu bar - // will not have mIcon set. - if (!mIcon) - return NS_ERROR_OUT_OF_MEMORY; - - return mIcon->SetupIcon(); -} - -// -// MenuDelegate Objective-C class, used to set up Carbon events -// - -@implementation MenuDelegate - -- (id)initWithGeckoMenu:(nsMenuX*)geckoMenu -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - if ((self = [super init])) { - NS_ASSERTION(geckoMenu, "Cannot initialize native menu delegate with NULL gecko menu! Will crash!"); - mGeckoMenu = geckoMenu; - } - return self; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - -- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item -{ - if (!menu || !item || !mGeckoMenu) - return; - - nsMenuObjectX* target = mGeckoMenu->GetVisibleItemAt((uint32_t)[menu indexOfItem:item]); - if (target && (target->MenuObjectType() == eMenuItemObjectType)) { - nsMenuItemX* targetMenuItem = static_cast<nsMenuItemX*>(target); - bool handlerCalledPreventDefault; // but we don't actually care - targetMenuItem->DispatchDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), &handlerCalledPreventDefault); - } -} - -- (void)menuWillOpen:(NSMenu *)menu -{ - if (!mGeckoMenu) - return; - - // Don't do anything while the OS is (re)indexing our menus (on Leopard and - // higher). This stops the Help menu from being able to search in our - // menus, but it also resolves many other problems. - if (nsMenuX::sIndexingMenuLevel > 0) - return; - - nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); - if (rollupListener) { - nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget(); - if (rollupWidget) { - rollupListener->Rollup(0, true, nullptr, nullptr); - [menu cancelTracking]; - return; - } - } - mGeckoMenu->MenuOpened(); -} - -- (void)menuDidClose:(NSMenu *)menu -{ - if (!mGeckoMenu) - return; - - // Don't do anything while the OS is (re)indexing our menus (on Leopard and - // higher). This stops the Help menu from being able to search in our - // menus, but it also resolves many other problems. - if (nsMenuX::sIndexingMenuLevel > 0) - return; - - mGeckoMenu->MenuClosed(); -} - -@end - -// OS X Leopard (at least as of 10.5.2) has an obscure bug triggered by some -// behavior that's present in Mozilla.org browsers but not (as best I can -// tell) in Apple products like Safari. (It's not yet clear exactly what this -// behavior is.) -// -// The bug is that sometimes you crash on quit in nsMenuX::RemoveAll(), on a -// call to [NSMenu removeItemAtIndex:]. The crash is caused by trying to -// access a deleted NSMenuItem object (sometimes (perhaps always?) by trying -// to send it a _setChangedFlags: message). Though this object was deleted -// some time ago, it remains registered as a potential target for a particular -// key equivalent. So when [NSMenu removeItemAtIndex:] removes the current -// target for that same key equivalent, the OS tries to "activate" the -// previous target. -// -// The underlying reason appears to be that NSMenu's _addItem:toTable: and -// _removeItem:fromTable: methods (which are used to keep a hashtable of -// registered key equivalents) don't properly "retain" and "release" -// NSMenuItem objects as they are added to and removed from the hashtable. -// -// Our (hackish) workaround is to shadow the OS's hashtable with another -// hastable of our own (gShadowKeyEquivDB), and use it to "retain" and -// "release" NSMenuItem objects as needed. This resolves bmo bugs 422287 and -// 423669. When (if) Apple fixes this bug, we can remove this workaround. - -static NSMutableDictionary *gShadowKeyEquivDB = nil; - -// Class for values in gShadowKeyEquivDB. - -@interface KeyEquivDBItem : NSObject -{ - NSMenuItem *mItem; - NSMutableSet *mTables; -} - -- (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable; -- (BOOL)hasTable:(NSMapTable *)aTable; -- (int)addTable:(NSMapTable *)aTable; -- (int)removeTable:(NSMapTable *)aTable; - -@end - -@implementation KeyEquivDBItem - -- (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - if (!gShadowKeyEquivDB) - gShadowKeyEquivDB = [[NSMutableDictionary alloc] init]; - self = [super init]; - if (aItem && aTable) { - mTables = [[NSMutableSet alloc] init]; - mItem = [aItem retain]; - [mTables addObject:[NSValue valueWithPointer:aTable]]; - } else { - mTables = nil; - mItem = nil; - } - return self; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - -- (void)dealloc -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - if (mTables) - [mTables release]; - if (mItem) - [mItem release]; - [super dealloc]; - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -- (BOOL)hasTable:(NSMapTable *)aTable -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - return [mTables member:[NSValue valueWithPointer:aTable]] ? YES : NO; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); -} - -// Does nothing if aTable (its index value) is already present in mTables. -- (int)addTable:(NSMapTable *)aTable -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - if (aTable) - [mTables addObject:[NSValue valueWithPointer:aTable]]; - return [mTables count]; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); -} - -- (int)removeTable:(NSMapTable *)aTable -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; - - if (aTable) { - NSValue *objectToRemove = - [mTables member:[NSValue valueWithPointer:aTable]]; - if (objectToRemove) - [mTables removeObject:objectToRemove]; - } - return [mTables count]; - - NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); -} - -@end - -@interface NSMenu (MethodSwizzling) -+ (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable; -+ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable; -@end - -@implementation NSMenu (MethodSwizzling) - -+ (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - if (aItem && aTable) { - NSValue *key = [NSValue valueWithPointer:aItem]; - KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key]; - if (shadowItem) { - [shadowItem addTable:aTable]; - } else { - shadowItem = [[KeyEquivDBItem alloc] initWithItem:aItem table:aTable]; - [gShadowKeyEquivDB setObject:shadowItem forKey:key]; - // Release after [NSMutableDictionary setObject:forKey:] retains it (so - // that it will get dealloced when removeObjectForKey: is called). - [shadowItem release]; - } - } - - NS_OBJC_END_TRY_ABORT_BLOCK; - - [self nsMenuX_NSMenu_addItem:aItem toTable:aTable]; -} - -+ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable -{ - [self nsMenuX_NSMenu_removeItem:aItem fromTable:aTable]; - - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - if (aItem && aTable) { - NSValue *key = [NSValue valueWithPointer:aItem]; - KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key]; - if (shadowItem && [shadowItem hasTable:aTable]) { - if (![shadowItem removeTable:aTable]) - [gShadowKeyEquivDB removeObjectForKey:key]; - } - } - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -@end - -// This class is needed to keep track of when the OS is (re)indexing all of -// our menus. This appears to only happen on Leopard and higher, and can -// be triggered by opening the Help menu. Some operations are unsafe while -// this is happening -- notably the calls to [[NSImage alloc] -// initWithSize:imageRect.size] and [newImage lockFocus] in nsMenuItemIconX:: -// OnStopFrame(). But we don't yet have a complete list, and Apple doesn't -// yet have any documentation on this subject. (Apple also doesn't yet have -// any documented way to find the information we seek here.) The "original" -// of this class (the one whose indexMenuBarDynamically method we hook) is -// defined in the Shortcut framework in /System/Library/PrivateFrameworks. -@interface NSObject (SCTGRLIndexMethodSwizzling) -- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically; -@end - -@implementation NSObject (SCTGRLIndexMethodSwizzling) - -- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically -{ - // This method appears to be called (once) whenever the OS (re)indexes our - // menus. sIndexingMenuLevel is a int32_t just in case it might be - // reentered. As it's running, it spawns calls to two undocumented - // HIToolbox methods (_SimulateMenuOpening() and _SimulateMenuClosed()), - // which "simulate" the opening and closing of our menus without actually - // displaying them. - ++nsMenuX::sIndexingMenuLevel; - [self nsMenuX_SCTGRLIndex_indexMenuBarDynamically]; - --nsMenuX::sIndexingMenuLevel; -} - -@end |