summaryrefslogtreecommitdiff
path: root/widget/cocoa/nsMenuX.mm
diff options
context:
space:
mode:
Diffstat (limited to 'widget/cocoa/nsMenuX.mm')
-rw-r--r--widget/cocoa/nsMenuX.mm1051
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