1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/cocoa/nsMenuX.mm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1054 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include <dlfcn.h> 1.10 + 1.11 +#include "nsMenuX.h" 1.12 +#include "nsMenuItemX.h" 1.13 +#include "nsMenuUtilsX.h" 1.14 +#include "nsMenuItemIconX.h" 1.15 +#include "nsStandaloneNativeMenu.h" 1.16 + 1.17 +#include "nsObjCExceptions.h" 1.18 + 1.19 +#include "nsToolkit.h" 1.20 +#include "nsCocoaFeatures.h" 1.21 +#include "nsCocoaUtils.h" 1.22 +#include "nsCOMPtr.h" 1.23 +#include "prinrval.h" 1.24 +#include "nsString.h" 1.25 +#include "nsReadableUtils.h" 1.26 +#include "nsUnicharUtils.h" 1.27 +#include "plstr.h" 1.28 +#include "nsGkAtoms.h" 1.29 +#include "nsCRT.h" 1.30 +#include "nsBaseWidget.h" 1.31 + 1.32 +#include "nsIDocument.h" 1.33 +#include "nsIContent.h" 1.34 +#include "nsIDOMDocument.h" 1.35 +#include "nsIDocumentObserver.h" 1.36 +#include "nsIComponentManager.h" 1.37 +#include "nsIRollupListener.h" 1.38 +#include "nsIDOMElement.h" 1.39 +#include "nsBindingManager.h" 1.40 +#include "nsIServiceManager.h" 1.41 +#include "nsXULPopupManager.h" 1.42 +#include "nsCxPusher.h" 1.43 + 1.44 +#include "jsapi.h" 1.45 +#include "nsIScriptGlobalObject.h" 1.46 +#include "nsIScriptContext.h" 1.47 +#include "nsIXPConnect.h" 1.48 + 1.49 +#include "mozilla/MouseEvents.h" 1.50 + 1.51 +using namespace mozilla; 1.52 + 1.53 +static bool gConstructingMenu = false; 1.54 +static bool gMenuMethodsSwizzled = false; 1.55 + 1.56 +int32_t nsMenuX::sIndexingMenuLevel = 0; 1.57 +using mozilla::AutoPushJSContext; 1.58 + 1.59 + 1.60 +// 1.61 +// Objective-C class used for representedObject 1.62 +// 1.63 + 1.64 +@implementation MenuItemInfo 1.65 + 1.66 +- (id) initWithMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner 1.67 +{ 1.68 + if ((self = [super init]) != nil) { 1.69 + mMenuGroupOwner = nullptr; 1.70 + [self setMenuGroupOwner:aMenuGroupOwner]; 1.71 + } 1.72 + return self; 1.73 +} 1.74 + 1.75 +- (void) dealloc 1.76 +{ 1.77 + [self setMenuGroupOwner:nullptr]; 1.78 + [super dealloc]; 1.79 +} 1.80 + 1.81 +- (nsMenuGroupOwnerX *) menuGroupOwner 1.82 +{ 1.83 + return mMenuGroupOwner; 1.84 +} 1.85 + 1.86 +- (void) setMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner 1.87 +{ 1.88 + // weak reference as the nsMenuGroupOwnerX owns all of its sub-objects 1.89 + mMenuGroupOwner = aMenuGroupOwner; 1.90 +} 1.91 + 1.92 +@end 1.93 + 1.94 + 1.95 +// 1.96 +// nsMenuX 1.97 +// 1.98 + 1.99 +nsMenuX::nsMenuX() 1.100 +: mVisibleItemsCount(0), mParent(nullptr), mMenuGroupOwner(nullptr), 1.101 + mNativeMenu(nil), mNativeMenuItem(nil), mIsEnabled(true), 1.102 + mDestroyHandlerCalled(false), mNeedsRebuild(true), 1.103 + mConstructed(false), mVisible(true), mXBLAttached(false) 1.104 +{ 1.105 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.106 + 1.107 + if (!gMenuMethodsSwizzled) { 1.108 + nsToolkit::SwizzleMethods([NSMenu class], @selector(_addItem:toTable:), 1.109 + @selector(nsMenuX_NSMenu_addItem:toTable:), true); 1.110 + nsToolkit::SwizzleMethods([NSMenu class], @selector(_removeItem:fromTable:), 1.111 + @selector(nsMenuX_NSMenu_removeItem:fromTable:), true); 1.112 + // On SnowLeopard the Shortcut framework (which contains the 1.113 + // SCTGRLIndex class) is loaded on demand, whenever the user first opens 1.114 + // a menu (which normally hasn't happened yet). So we need to load it 1.115 + // here explicitly. 1.116 + dlopen("/System/Library/PrivateFrameworks/Shortcut.framework/Shortcut", RTLD_LAZY); 1.117 + Class SCTGRLIndexClass = ::NSClassFromString(@"SCTGRLIndex"); 1.118 + nsToolkit::SwizzleMethods(SCTGRLIndexClass, @selector(indexMenuBarDynamically), 1.119 + @selector(nsMenuX_SCTGRLIndex_indexMenuBarDynamically)); 1.120 + 1.121 + gMenuMethodsSwizzled = true; 1.122 + } 1.123 + 1.124 + mMenuDelegate = [[MenuDelegate alloc] initWithGeckoMenu:this]; 1.125 + 1.126 + if (!nsMenuBarX::sNativeEventTarget) 1.127 + nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init]; 1.128 + 1.129 + MOZ_COUNT_CTOR(nsMenuX); 1.130 + 1.131 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.132 +} 1.133 + 1.134 +nsMenuX::~nsMenuX() 1.135 +{ 1.136 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.137 + 1.138 + // Prevent the icon object from outliving us. 1.139 + if (mIcon) 1.140 + mIcon->Destroy(); 1.141 + 1.142 + RemoveAll(); 1.143 + 1.144 + [mNativeMenu setDelegate:nil]; 1.145 + [mNativeMenu release]; 1.146 + [mMenuDelegate release]; 1.147 + // autorelease the native menu item so that anything else happening to this 1.148 + // object happens before the native menu item actually dies 1.149 + [mNativeMenuItem autorelease]; 1.150 + 1.151 + // alert the change notifier we don't care no more 1.152 + if (mContent) 1.153 + mMenuGroupOwner->UnregisterForContentChanges(mContent); 1.154 + 1.155 + MOZ_COUNT_DTOR(nsMenuX); 1.156 + 1.157 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.158 +} 1.159 + 1.160 +nsresult nsMenuX::Create(nsMenuObjectX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode) 1.161 +{ 1.162 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.163 + 1.164 + mContent = aNode; 1.165 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel); 1.166 + mNativeMenu = CreateMenuWithGeckoString(mLabel); 1.167 + 1.168 + // register this menu to be notified when changes are made to our content object 1.169 + mMenuGroupOwner = aMenuGroupOwner; // weak ref 1.170 + NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one"); 1.171 + mMenuGroupOwner->RegisterForContentChanges(mContent, this); 1.172 + 1.173 + mParent = aParent; 1.174 + // our parent could be either a menu bar (if we're toplevel) or a menu (if we're a submenu) 1.175 + 1.176 +#ifdef DEBUG 1.177 + nsMenuObjectTypeX parentType = 1.178 +#endif 1.179 + mParent->MenuObjectType(); 1.180 + NS_ASSERTION((parentType == eMenuBarObjectType || parentType == eSubmenuObjectType || parentType == eStandaloneNativeMenuObjectType), 1.181 + "Menu parent not a menu bar, menu, or native menu!"); 1.182 + 1.183 + if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent)) 1.184 + mVisible = false; 1.185 + if (mContent->GetChildCount() == 0) 1.186 + mVisible = false; 1.187 + 1.188 + NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel); 1.189 + mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""]; 1.190 + [mNativeMenuItem setSubmenu:mNativeMenu]; 1.191 + 1.192 + SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, 1.193 + nsGkAtoms::_true, eCaseMatters)); 1.194 + 1.195 + // We call MenuConstruct here because keyboard commands are dependent upon 1.196 + // native menu items being created. If we only call MenuConstruct when a menu 1.197 + // is actually selected, then we can't access keyboard commands until the 1.198 + // menu gets selected, which is bad. 1.199 + MenuConstruct(); 1.200 + 1.201 + mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem); 1.202 + 1.203 + return NS_OK; 1.204 + 1.205 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.206 +} 1.207 + 1.208 +nsresult nsMenuX::AddMenuItem(nsMenuItemX* aMenuItem) 1.209 +{ 1.210 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.211 + 1.212 + if (!aMenuItem) 1.213 + return NS_ERROR_INVALID_ARG; 1.214 + 1.215 + mMenuObjectsArray.AppendElement(aMenuItem); 1.216 + if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(aMenuItem->Content())) 1.217 + return NS_OK; 1.218 + ++mVisibleItemsCount; 1.219 + 1.220 + NSMenuItem* newNativeMenuItem = (NSMenuItem*)aMenuItem->NativeData(); 1.221 + 1.222 + // add the menu item to this menu 1.223 + [mNativeMenu addItem:newNativeMenuItem]; 1.224 + 1.225 + // set up target/action 1.226 + [newNativeMenuItem setTarget:nsMenuBarX::sNativeEventTarget]; 1.227 + [newNativeMenuItem setAction:@selector(menuItemHit:)]; 1.228 + 1.229 + // set its command. we get the unique command id from the menubar 1.230 + [newNativeMenuItem setTag:mMenuGroupOwner->RegisterForCommand(aMenuItem)]; 1.231 + MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:mMenuGroupOwner]; 1.232 + [newNativeMenuItem setRepresentedObject:info]; 1.233 + [info release]; 1.234 + 1.235 + return NS_OK; 1.236 + 1.237 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.238 +} 1.239 + 1.240 +nsresult nsMenuX::AddMenu(nsMenuX* aMenu) 1.241 +{ 1.242 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.243 + 1.244 + // Add a submenu 1.245 + if (!aMenu) 1.246 + return NS_ERROR_NULL_POINTER; 1.247 + 1.248 + mMenuObjectsArray.AppendElement(aMenu); 1.249 + if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(aMenu->Content())) 1.250 + return NS_OK; 1.251 + ++mVisibleItemsCount; 1.252 + 1.253 + // We have to add a menu item and then associate the menu with it 1.254 + NSMenuItem* newNativeMenuItem = aMenu->NativeMenuItem(); 1.255 + if (!newNativeMenuItem) 1.256 + return NS_ERROR_FAILURE; 1.257 + [mNativeMenu addItem:newNativeMenuItem]; 1.258 + 1.259 + [newNativeMenuItem setSubmenu:(NSMenu*)aMenu->NativeData()]; 1.260 + 1.261 + return NS_OK; 1.262 + 1.263 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.264 +} 1.265 + 1.266 +// Includes all items, including hidden/collapsed ones 1.267 +uint32_t nsMenuX::GetItemCount() 1.268 +{ 1.269 + return mMenuObjectsArray.Length(); 1.270 +} 1.271 + 1.272 +// Includes all items, including hidden/collapsed ones 1.273 +nsMenuObjectX* nsMenuX::GetItemAt(uint32_t aPos) 1.274 +{ 1.275 + if (aPos >= (uint32_t)mMenuObjectsArray.Length()) 1.276 + return NULL; 1.277 + 1.278 + return mMenuObjectsArray[aPos]; 1.279 +} 1.280 + 1.281 +// Only includes visible items 1.282 +nsresult nsMenuX::GetVisibleItemCount(uint32_t &aCount) 1.283 +{ 1.284 + aCount = mVisibleItemsCount; 1.285 + return NS_OK; 1.286 +} 1.287 + 1.288 +// Only includes visible items. Note that this is provides O(N) access 1.289 +// If you need to iterate or search, consider using GetItemAt and doing your own filtering 1.290 +nsMenuObjectX* nsMenuX::GetVisibleItemAt(uint32_t aPos) 1.291 +{ 1.292 + 1.293 + uint32_t count = mMenuObjectsArray.Length(); 1.294 + if (aPos >= mVisibleItemsCount || aPos >= count) 1.295 + return NULL; 1.296 + 1.297 + // If there are no invisible items, can provide direct access 1.298 + if (mVisibleItemsCount == count) 1.299 + return mMenuObjectsArray[aPos]; 1.300 + 1.301 + // Otherwise, traverse the array until we find the the item we're looking for. 1.302 + nsMenuObjectX* item; 1.303 + uint32_t visibleNodeIndex = 0; 1.304 + for (uint32_t i = 0; i < count; i++) { 1.305 + item = mMenuObjectsArray[i]; 1.306 + if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(item->Content())) { 1.307 + if (aPos == visibleNodeIndex) { 1.308 + // we found the visible node we're looking for, return it 1.309 + return item; 1.310 + } 1.311 + visibleNodeIndex++; 1.312 + } 1.313 + } 1.314 + 1.315 + return NULL; 1.316 +} 1.317 + 1.318 +nsresult nsMenuX::RemoveAll() 1.319 +{ 1.320 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.321 + 1.322 + if (mNativeMenu) { 1.323 + // clear command id's 1.324 + int itemCount = [mNativeMenu numberOfItems]; 1.325 + for (int i = 0; i < itemCount; i++) 1.326 + mMenuGroupOwner->UnregisterCommand((uint32_t)[[mNativeMenu itemAtIndex:i] tag]); 1.327 + // get rid of Cocoa menu items 1.328 + for (int i = [mNativeMenu numberOfItems] - 1; i >= 0; i--) 1.329 + [mNativeMenu removeItemAtIndex:i]; 1.330 + } 1.331 + 1.332 + mMenuObjectsArray.Clear(); 1.333 + mVisibleItemsCount = 0; 1.334 + 1.335 + return NS_OK; 1.336 + 1.337 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.338 +} 1.339 + 1.340 +nsEventStatus nsMenuX::MenuOpened() 1.341 +{ 1.342 + // Open the node. 1.343 + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), true); 1.344 + 1.345 + // Fire a handler. If we're told to stop, don't build the menu at all 1.346 + bool keepProcessing = OnOpen(); 1.347 + 1.348 + if (!mNeedsRebuild || !keepProcessing) 1.349 + return nsEventStatus_eConsumeNoDefault; 1.350 + 1.351 + if (!mConstructed || mNeedsRebuild) { 1.352 + if (mNeedsRebuild) 1.353 + RemoveAll(); 1.354 + 1.355 + MenuConstruct(); 1.356 + mConstructed = true; 1.357 + } 1.358 + 1.359 + nsEventStatus status = nsEventStatus_eIgnore; 1.360 + WidgetMouseEvent event(true, NS_XUL_POPUP_SHOWN, nullptr, 1.361 + WidgetMouseEvent::eReal); 1.362 + 1.363 + nsCOMPtr<nsIContent> popupContent; 1.364 + GetMenuPopupContent(getter_AddRefs(popupContent)); 1.365 + nsIContent* dispatchTo = popupContent ? popupContent : mContent; 1.366 + dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status); 1.367 + 1.368 + return nsEventStatus_eConsumeNoDefault; 1.369 +} 1.370 + 1.371 +void nsMenuX::MenuClosed() 1.372 +{ 1.373 + if (mConstructed) { 1.374 + // Don't close if a handler tells us to stop. 1.375 + if (!OnClose()) 1.376 + return; 1.377 + 1.378 + if (mNeedsRebuild) 1.379 + mConstructed = false; 1.380 + 1.381 + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, true); 1.382 + 1.383 + nsEventStatus status = nsEventStatus_eIgnore; 1.384 + WidgetMouseEvent event(true, NS_XUL_POPUP_HIDDEN, nullptr, 1.385 + WidgetMouseEvent::eReal); 1.386 + 1.387 + nsCOMPtr<nsIContent> popupContent; 1.388 + GetMenuPopupContent(getter_AddRefs(popupContent)); 1.389 + nsIContent* dispatchTo = popupContent ? popupContent : mContent; 1.390 + dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status); 1.391 + 1.392 + mDestroyHandlerCalled = true; 1.393 + mConstructed = false; 1.394 + } 1.395 +} 1.396 + 1.397 +void nsMenuX::MenuConstruct() 1.398 +{ 1.399 + mConstructed = false; 1.400 + gConstructingMenu = true; 1.401 + 1.402 + // reset destroy handler flag so that we'll know to fire it next time this menu goes away. 1.403 + mDestroyHandlerCalled = false; 1.404 + 1.405 + //printf("nsMenuX::MenuConstruct called for %s = %d \n", NS_LossyConvertUTF16toASCII(mLabel).get(), mNativeMenu); 1.406 + 1.407 + // Retrieve our menupopup. 1.408 + nsCOMPtr<nsIContent> menuPopup; 1.409 + GetMenuPopupContent(getter_AddRefs(menuPopup)); 1.410 + if (!menuPopup) { 1.411 + gConstructingMenu = false; 1.412 + return; 1.413 + } 1.414 + 1.415 + // bug 365405: Manually wrap the menupopup node to make sure it's bounded 1.416 + if (!mXBLAttached) { 1.417 + nsresult rv; 1.418 + nsCOMPtr<nsIXPConnect> xpconnect = 1.419 + do_GetService(nsIXPConnect::GetCID(), &rv); 1.420 + if (NS_SUCCEEDED(rv)) { 1.421 + nsIDocument* ownerDoc = menuPopup->OwnerDoc(); 1.422 + nsCOMPtr<nsIScriptGlobalObject> sgo; 1.423 + if (ownerDoc && (sgo = do_QueryInterface(ownerDoc->GetWindow()))) { 1.424 + nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext(); 1.425 + JSObject* global = sgo->GetGlobalJSObject(); 1.426 + if (scriptContext && global) { 1.427 + AutoPushJSContext cx(scriptContext->GetNativeContext()); 1.428 + if (cx) { 1.429 + nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper; 1.430 + xpconnect->WrapNative(cx, global, 1.431 + menuPopup, NS_GET_IID(nsISupports), 1.432 + getter_AddRefs(wrapper)); 1.433 + mXBLAttached = true; 1.434 + } 1.435 + } 1.436 + } 1.437 + } 1.438 + } 1.439 + 1.440 + // Iterate over the kids 1.441 + uint32_t count = menuPopup->GetChildCount(); 1.442 + for (uint32_t i = 0; i < count; i++) { 1.443 + nsIContent *child = menuPopup->GetChildAt(i); 1.444 + if (child) { 1.445 + // depending on the type, create a menu item, separator, or submenu 1.446 + nsIAtom *tag = child->Tag(); 1.447 + if (tag == nsGkAtoms::menuitem || tag == nsGkAtoms::menuseparator) 1.448 + LoadMenuItem(child); 1.449 + else if (tag == nsGkAtoms::menu) 1.450 + LoadSubMenu(child); 1.451 + } 1.452 + } // for each menu item 1.453 + 1.454 + gConstructingMenu = false; 1.455 + mNeedsRebuild = false; 1.456 + // printf("Done building, mMenuObjectsArray.Count() = %d \n", mMenuObjectsArray.Count()); 1.457 +} 1.458 + 1.459 +void nsMenuX::SetRebuild(bool aNeedsRebuild) 1.460 +{ 1.461 + if (!gConstructingMenu) 1.462 + mNeedsRebuild = aNeedsRebuild; 1.463 +} 1.464 + 1.465 +nsresult nsMenuX::SetEnabled(bool aIsEnabled) 1.466 +{ 1.467 + if (aIsEnabled != mIsEnabled) { 1.468 + // we always want to rebuild when this changes 1.469 + mIsEnabled = aIsEnabled; 1.470 + [mNativeMenuItem setEnabled:(BOOL)mIsEnabled]; 1.471 + } 1.472 + return NS_OK; 1.473 +} 1.474 + 1.475 +nsresult nsMenuX::GetEnabled(bool* aIsEnabled) 1.476 +{ 1.477 + NS_ENSURE_ARG_POINTER(aIsEnabled); 1.478 + *aIsEnabled = mIsEnabled; 1.479 + return NS_OK; 1.480 +} 1.481 + 1.482 +GeckoNSMenu* nsMenuX::CreateMenuWithGeckoString(nsString& menuTitle) 1.483 +{ 1.484 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.485 + 1.486 + NSString* title = [NSString stringWithCharacters:(UniChar*)menuTitle.get() length:menuTitle.Length()]; 1.487 + GeckoNSMenu* myMenu = [[GeckoNSMenu alloc] initWithTitle:title]; 1.488 + [myMenu setDelegate:mMenuDelegate]; 1.489 + 1.490 + // We don't want this menu to auto-enable menu items because then Cocoa 1.491 + // overrides our decisions and things get incorrectly enabled/disabled. 1.492 + [myMenu setAutoenablesItems:NO]; 1.493 + 1.494 + // we used to install Carbon event handlers here, but since NSMenu* doesn't 1.495 + // create its underlying MenuRef until just before display, we delay until 1.496 + // that happens. Now we install the event handlers when Cocoa notifies 1.497 + // us that a menu is about to display - see the Cocoa MenuDelegate class. 1.498 + 1.499 + return myMenu; 1.500 + 1.501 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.502 +} 1.503 + 1.504 +void nsMenuX::LoadMenuItem(nsIContent* inMenuItemContent) 1.505 +{ 1.506 + if (!inMenuItemContent) 1.507 + return; 1.508 + 1.509 + nsAutoString menuitemName; 1.510 + inMenuItemContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, menuitemName); 1.511 + 1.512 + // printf("menuitem %s \n", NS_LossyConvertUTF16toASCII(menuitemName).get()); 1.513 + 1.514 + EMenuItemType itemType = eRegularMenuItemType; 1.515 + if (inMenuItemContent->Tag() == nsGkAtoms::menuseparator) { 1.516 + itemType = eSeparatorMenuItemType; 1.517 + } 1.518 + else { 1.519 + static nsIContent::AttrValuesArray strings[] = 1.520 + {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr}; 1.521 + switch (inMenuItemContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, 1.522 + strings, eCaseMatters)) { 1.523 + case 0: itemType = eCheckboxMenuItemType; break; 1.524 + case 1: itemType = eRadioMenuItemType; break; 1.525 + } 1.526 + } 1.527 + 1.528 + // Create the item. 1.529 + nsMenuItemX* menuItem = new nsMenuItemX(); 1.530 + if (!menuItem) 1.531 + return; 1.532 + 1.533 + nsresult rv = menuItem->Create(this, menuitemName, itemType, mMenuGroupOwner, inMenuItemContent); 1.534 + if (NS_FAILED(rv)) { 1.535 + delete menuItem; 1.536 + return; 1.537 + } 1.538 + 1.539 + AddMenuItem(menuItem); 1.540 + 1.541 + // This needs to happen after the nsIMenuItem object is inserted into 1.542 + // our item array in AddMenuItem() 1.543 + menuItem->SetupIcon(); 1.544 +} 1.545 + 1.546 +void nsMenuX::LoadSubMenu(nsIContent* inMenuContent) 1.547 +{ 1.548 + nsAutoPtr<nsMenuX> menu(new nsMenuX()); 1.549 + if (!menu) 1.550 + return; 1.551 + 1.552 + nsresult rv = menu->Create(this, mMenuGroupOwner, inMenuContent); 1.553 + if (NS_FAILED(rv)) 1.554 + return; 1.555 + 1.556 + AddMenu(menu); 1.557 + 1.558 + // This needs to happen after the nsIMenu object is inserted into 1.559 + // our item array in AddMenu() 1.560 + menu->SetupIcon(); 1.561 + 1.562 + menu.forget(); 1.563 +} 1.564 + 1.565 +// This menu is about to open. Returns TRUE if we should keep processing the event, 1.566 +// FALSE if the handler wants to stop the opening of the menu. 1.567 +bool nsMenuX::OnOpen() 1.568 +{ 1.569 + nsEventStatus status = nsEventStatus_eIgnore; 1.570 + WidgetMouseEvent event(true, NS_XUL_POPUP_SHOWING, nullptr, 1.571 + WidgetMouseEvent::eReal); 1.572 + 1.573 + nsCOMPtr<nsIContent> popupContent; 1.574 + GetMenuPopupContent(getter_AddRefs(popupContent)); 1.575 + 1.576 + nsresult rv = NS_OK; 1.577 + nsIContent* dispatchTo = popupContent ? popupContent : mContent; 1.578 + rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status); 1.579 + if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault) 1.580 + return false; 1.581 + 1.582 + // If the open is going to succeed we need to walk our menu items, checking to 1.583 + // see if any of them have a command attribute. If so, several attributes 1.584 + // must potentially be updated. 1.585 + 1.586 + // Get new popup content first since it might have changed as a result of the 1.587 + // NS_XUL_POPUP_SHOWING event above. 1.588 + GetMenuPopupContent(getter_AddRefs(popupContent)); 1.589 + if (!popupContent) 1.590 + return true; 1.591 + 1.592 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.593 + if (pm) { 1.594 + pm->UpdateMenuItems(popupContent); 1.595 + } 1.596 + 1.597 + return true; 1.598 +} 1.599 + 1.600 +// Returns TRUE if we should keep processing the event, FALSE if the handler 1.601 +// wants to stop the closing of the menu. 1.602 +bool nsMenuX::OnClose() 1.603 +{ 1.604 + if (mDestroyHandlerCalled) 1.605 + return true; 1.606 + 1.607 + nsEventStatus status = nsEventStatus_eIgnore; 1.608 + WidgetMouseEvent event(true, NS_XUL_POPUP_HIDING, nullptr, 1.609 + WidgetMouseEvent::eReal); 1.610 + 1.611 + nsCOMPtr<nsIContent> popupContent; 1.612 + GetMenuPopupContent(getter_AddRefs(popupContent)); 1.613 + 1.614 + nsresult rv = NS_OK; 1.615 + nsIContent* dispatchTo = popupContent ? popupContent : mContent; 1.616 + rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status); 1.617 + 1.618 + mDestroyHandlerCalled = true; 1.619 + 1.620 + if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault) 1.621 + return false; 1.622 + 1.623 + return true; 1.624 +} 1.625 + 1.626 +// Find the |menupopup| child in the |popup| representing this menu. It should be one 1.627 +// of a very few children so we won't be iterating over a bazillion menu items to find 1.628 +// it (so the strcmp won't kill us). 1.629 +void nsMenuX::GetMenuPopupContent(nsIContent** aResult) 1.630 +{ 1.631 + if (!aResult) 1.632 + return; 1.633 + *aResult = nullptr; 1.634 + 1.635 + // Check to see if we are a "menupopup" node (if we are a native menu). 1.636 + { 1.637 + int32_t dummy; 1.638 + nsCOMPtr<nsIAtom> tag = mContent->OwnerDoc()->BindingManager()->ResolveTag(mContent, &dummy); 1.639 + if (tag == nsGkAtoms::menupopup) { 1.640 + *aResult = mContent; 1.641 + NS_ADDREF(*aResult); 1.642 + return; 1.643 + } 1.644 + } 1.645 + 1.646 + // Otherwise check our child nodes. 1.647 + 1.648 + uint32_t count = mContent->GetChildCount(); 1.649 + 1.650 + for (uint32_t i = 0; i < count; i++) { 1.651 + int32_t dummy; 1.652 + nsIContent *child = mContent->GetChildAt(i); 1.653 + nsCOMPtr<nsIAtom> tag = child->OwnerDoc()->BindingManager()->ResolveTag(child, &dummy); 1.654 + if (tag == nsGkAtoms::menupopup) { 1.655 + *aResult = child; 1.656 + NS_ADDREF(*aResult); 1.657 + return; 1.658 + } 1.659 + } 1.660 +} 1.661 + 1.662 +NSMenuItem* nsMenuX::NativeMenuItem() 1.663 +{ 1.664 + return mNativeMenuItem; 1.665 +} 1.666 + 1.667 +bool nsMenuX::IsXULHelpMenu(nsIContent* aMenuContent) 1.668 +{ 1.669 + bool retval = false; 1.670 + if (aMenuContent) { 1.671 + nsAutoString id; 1.672 + aMenuContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id); 1.673 + if (id.Equals(NS_LITERAL_STRING("helpMenu"))) 1.674 + retval = true; 1.675 + } 1.676 + return retval; 1.677 +} 1.678 + 1.679 +// 1.680 +// nsChangeObserver 1.681 +// 1.682 + 1.683 +void nsMenuX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, 1.684 + nsIAtom *aAttribute) 1.685 +{ 1.686 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.687 + 1.688 + // ignore the |open| attribute, which is by far the most common 1.689 + if (gConstructingMenu || (aAttribute == nsGkAtoms::open)) 1.690 + return; 1.691 + 1.692 + nsMenuObjectTypeX parentType = mParent->MenuObjectType(); 1.693 + 1.694 + if (aAttribute == nsGkAtoms::disabled) { 1.695 + SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, 1.696 + nsGkAtoms::_true, eCaseMatters)); 1.697 + } 1.698 + else if (aAttribute == nsGkAtoms::label) { 1.699 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel); 1.700 + 1.701 + // invalidate my parent. If we're a submenu parent, we have to rebuild 1.702 + // the parent menu in order for the changes to be picked up. If we're 1.703 + // a regular menu, just change the title and redraw the menubar. 1.704 + if (parentType == eMenuBarObjectType) { 1.705 + // reuse the existing menu, to avoid rebuilding the root menu bar. 1.706 + NS_ASSERTION(mNativeMenu, "nsMenuX::AttributeChanged: invalid menu handle."); 1.707 + NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel); 1.708 + [mNativeMenu setTitle:newCocoaLabelString]; 1.709 + } 1.710 + else if (parentType == eSubmenuObjectType) { 1.711 + static_cast<nsMenuX*>(mParent)->SetRebuild(true); 1.712 + } 1.713 + else if (parentType == eStandaloneNativeMenuObjectType) { 1.714 + static_cast<nsStandaloneNativeMenu*>(mParent)->GetMenuXObject()->SetRebuild(true); 1.715 + } 1.716 + } 1.717 + else if (aAttribute == nsGkAtoms::hidden || aAttribute == nsGkAtoms::collapsed) { 1.718 + SetRebuild(true); 1.719 + 1.720 + bool contentIsHiddenOrCollapsed = nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent); 1.721 + 1.722 + // don't do anything if the state is correct already 1.723 + if (contentIsHiddenOrCollapsed != mVisible) 1.724 + return; 1.725 + 1.726 + if (contentIsHiddenOrCollapsed) { 1.727 + if (parentType == eMenuBarObjectType || 1.728 + parentType == eSubmenuObjectType || 1.729 + parentType == eStandaloneNativeMenuObjectType) { 1.730 + NSMenu* parentMenu = (NSMenu*)mParent->NativeData(); 1.731 + // An exception will get thrown if we try to remove an item that isn't 1.732 + // in the menu. 1.733 + if ([parentMenu indexOfItem:mNativeMenuItem] != -1) 1.734 + [parentMenu removeItem:mNativeMenuItem]; 1.735 + mVisible = false; 1.736 + } 1.737 + } 1.738 + else { 1.739 + if (parentType == eMenuBarObjectType || 1.740 + parentType == eSubmenuObjectType || 1.741 + parentType == eStandaloneNativeMenuObjectType) { 1.742 + int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(mParent, this); 1.743 + if (parentType == eMenuBarObjectType) { 1.744 + // Before inserting we need to figure out if we should take the native 1.745 + // application menu into account. 1.746 + nsMenuBarX* mb = static_cast<nsMenuBarX*>(mParent); 1.747 + if (mb->MenuContainsAppMenu()) 1.748 + insertionIndex++; 1.749 + } 1.750 + NSMenu* parentMenu = (NSMenu*)mParent->NativeData(); 1.751 + [parentMenu insertItem:mNativeMenuItem atIndex:insertionIndex]; 1.752 + [mNativeMenuItem setSubmenu:mNativeMenu]; 1.753 + mVisible = true; 1.754 + } 1.755 + } 1.756 + } 1.757 + else if (aAttribute == nsGkAtoms::image) { 1.758 + SetupIcon(); 1.759 + } 1.760 + 1.761 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.762 +} 1.763 + 1.764 +void nsMenuX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild, 1.765 + int32_t aIndexInContainer) 1.766 +{ 1.767 + if (gConstructingMenu) 1.768 + return; 1.769 + 1.770 + SetRebuild(true); 1.771 + mMenuGroupOwner->UnregisterForContentChanges(aChild); 1.772 +} 1.773 + 1.774 +void nsMenuX::ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer, 1.775 + nsIContent *aChild) 1.776 +{ 1.777 + if (gConstructingMenu) 1.778 + return; 1.779 + 1.780 + SetRebuild(true); 1.781 +} 1.782 + 1.783 +nsresult nsMenuX::SetupIcon() 1.784 +{ 1.785 + // In addition to out-of-memory, menus that are children of the menu bar 1.786 + // will not have mIcon set. 1.787 + if (!mIcon) 1.788 + return NS_ERROR_OUT_OF_MEMORY; 1.789 + 1.790 + return mIcon->SetupIcon(); 1.791 +} 1.792 + 1.793 +// 1.794 +// MenuDelegate Objective-C class, used to set up Carbon events 1.795 +// 1.796 + 1.797 +@implementation MenuDelegate 1.798 + 1.799 +- (id)initWithGeckoMenu:(nsMenuX*)geckoMenu 1.800 +{ 1.801 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.802 + 1.803 + if ((self = [super init])) { 1.804 + NS_ASSERTION(geckoMenu, "Cannot initialize native menu delegate with NULL gecko menu! Will crash!"); 1.805 + mGeckoMenu = geckoMenu; 1.806 + } 1.807 + return self; 1.808 + 1.809 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.810 +} 1.811 + 1.812 +- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item 1.813 +{ 1.814 + if (!menu || !item || !mGeckoMenu) 1.815 + return; 1.816 + 1.817 + nsMenuObjectX* target = mGeckoMenu->GetVisibleItemAt((uint32_t)[menu indexOfItem:item]); 1.818 + if (target && (target->MenuObjectType() == eMenuItemObjectType)) { 1.819 + nsMenuItemX* targetMenuItem = static_cast<nsMenuItemX*>(target); 1.820 + bool handlerCalledPreventDefault; // but we don't actually care 1.821 + targetMenuItem->DispatchDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), &handlerCalledPreventDefault); 1.822 + } 1.823 +} 1.824 + 1.825 +- (void)menuWillOpen:(NSMenu *)menu 1.826 +{ 1.827 + if (!mGeckoMenu) 1.828 + return; 1.829 + 1.830 + // Don't do anything while the OS is (re)indexing our menus (on Leopard and 1.831 + // higher). This stops the Help menu from being able to search in our 1.832 + // menus, but it also resolves many other problems. 1.833 + if (nsMenuX::sIndexingMenuLevel > 0) 1.834 + return; 1.835 + 1.836 + nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); 1.837 + if (rollupListener) { 1.838 + nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget(); 1.839 + if (rollupWidget) { 1.840 + rollupListener->Rollup(0, nullptr, nullptr); 1.841 + [menu cancelTracking]; 1.842 + return; 1.843 + } 1.844 + } 1.845 + mGeckoMenu->MenuOpened(); 1.846 +} 1.847 + 1.848 +- (void)menuDidClose:(NSMenu *)menu 1.849 +{ 1.850 + if (!mGeckoMenu) 1.851 + return; 1.852 + 1.853 + // Don't do anything while the OS is (re)indexing our menus (on Leopard and 1.854 + // higher). This stops the Help menu from being able to search in our 1.855 + // menus, but it also resolves many other problems. 1.856 + if (nsMenuX::sIndexingMenuLevel > 0) 1.857 + return; 1.858 + 1.859 + mGeckoMenu->MenuClosed(); 1.860 +} 1.861 + 1.862 +@end 1.863 + 1.864 +// OS X Leopard (at least as of 10.5.2) has an obscure bug triggered by some 1.865 +// behavior that's present in Mozilla.org browsers but not (as best I can 1.866 +// tell) in Apple products like Safari. (It's not yet clear exactly what this 1.867 +// behavior is.) 1.868 +// 1.869 +// The bug is that sometimes you crash on quit in nsMenuX::RemoveAll(), on a 1.870 +// call to [NSMenu removeItemAtIndex:]. The crash is caused by trying to 1.871 +// access a deleted NSMenuItem object (sometimes (perhaps always?) by trying 1.872 +// to send it a _setChangedFlags: message). Though this object was deleted 1.873 +// some time ago, it remains registered as a potential target for a particular 1.874 +// key equivalent. So when [NSMenu removeItemAtIndex:] removes the current 1.875 +// target for that same key equivalent, the OS tries to "activate" the 1.876 +// previous target. 1.877 +// 1.878 +// The underlying reason appears to be that NSMenu's _addItem:toTable: and 1.879 +// _removeItem:fromTable: methods (which are used to keep a hashtable of 1.880 +// registered key equivalents) don't properly "retain" and "release" 1.881 +// NSMenuItem objects as they are added to and removed from the hashtable. 1.882 +// 1.883 +// Our (hackish) workaround is to shadow the OS's hashtable with another 1.884 +// hastable of our own (gShadowKeyEquivDB), and use it to "retain" and 1.885 +// "release" NSMenuItem objects as needed. This resolves bmo bugs 422287 and 1.886 +// 423669. When (if) Apple fixes this bug, we can remove this workaround. 1.887 + 1.888 +static NSMutableDictionary *gShadowKeyEquivDB = nil; 1.889 + 1.890 +// Class for values in gShadowKeyEquivDB. 1.891 + 1.892 +@interface KeyEquivDBItem : NSObject 1.893 +{ 1.894 + NSMenuItem *mItem; 1.895 + NSMutableSet *mTables; 1.896 +} 1.897 + 1.898 +- (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable; 1.899 +- (BOOL)hasTable:(NSMapTable *)aTable; 1.900 +- (int)addTable:(NSMapTable *)aTable; 1.901 +- (int)removeTable:(NSMapTable *)aTable; 1.902 + 1.903 +@end 1.904 + 1.905 +@implementation KeyEquivDBItem 1.906 + 1.907 +- (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable 1.908 +{ 1.909 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.910 + 1.911 + if (!gShadowKeyEquivDB) 1.912 + gShadowKeyEquivDB = [[NSMutableDictionary alloc] init]; 1.913 + self = [super init]; 1.914 + if (aItem && aTable) { 1.915 + mTables = [[NSMutableSet alloc] init]; 1.916 + mItem = [aItem retain]; 1.917 + [mTables addObject:[NSValue valueWithPointer:aTable]]; 1.918 + } else { 1.919 + mTables = nil; 1.920 + mItem = nil; 1.921 + } 1.922 + return self; 1.923 + 1.924 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.925 +} 1.926 + 1.927 +- (void)dealloc 1.928 +{ 1.929 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.930 + 1.931 + if (mTables) 1.932 + [mTables release]; 1.933 + if (mItem) 1.934 + [mItem release]; 1.935 + [super dealloc]; 1.936 + 1.937 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.938 +} 1.939 + 1.940 +- (BOOL)hasTable:(NSMapTable *)aTable 1.941 +{ 1.942 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.943 + 1.944 + return [mTables member:[NSValue valueWithPointer:aTable]] ? YES : NO; 1.945 + 1.946 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); 1.947 +} 1.948 + 1.949 +// Does nothing if aTable (its index value) is already present in mTables. 1.950 +- (int)addTable:(NSMapTable *)aTable 1.951 +{ 1.952 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.953 + 1.954 + if (aTable) 1.955 + [mTables addObject:[NSValue valueWithPointer:aTable]]; 1.956 + return [mTables count]; 1.957 + 1.958 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); 1.959 +} 1.960 + 1.961 +- (int)removeTable:(NSMapTable *)aTable 1.962 +{ 1.963 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.964 + 1.965 + if (aTable) { 1.966 + NSValue *objectToRemove = 1.967 + [mTables member:[NSValue valueWithPointer:aTable]]; 1.968 + if (objectToRemove) 1.969 + [mTables removeObject:objectToRemove]; 1.970 + } 1.971 + return [mTables count]; 1.972 + 1.973 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); 1.974 +} 1.975 + 1.976 +@end 1.977 + 1.978 +@interface NSMenu (MethodSwizzling) 1.979 ++ (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable; 1.980 ++ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable; 1.981 +@end 1.982 + 1.983 +@implementation NSMenu (MethodSwizzling) 1.984 + 1.985 ++ (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable 1.986 +{ 1.987 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.988 + 1.989 + if (aItem && aTable) { 1.990 + NSValue *key = [NSValue valueWithPointer:aItem]; 1.991 + KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key]; 1.992 + if (shadowItem) { 1.993 + [shadowItem addTable:aTable]; 1.994 + } else { 1.995 + shadowItem = [[KeyEquivDBItem alloc] initWithItem:aItem table:aTable]; 1.996 + [gShadowKeyEquivDB setObject:shadowItem forKey:key]; 1.997 + // Release after [NSMutableDictionary setObject:forKey:] retains it (so 1.998 + // that it will get dealloced when removeObjectForKey: is called). 1.999 + [shadowItem release]; 1.1000 + } 1.1001 + } 1.1002 + 1.1003 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.1004 + 1.1005 + [self nsMenuX_NSMenu_addItem:aItem toTable:aTable]; 1.1006 +} 1.1007 + 1.1008 ++ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable 1.1009 +{ 1.1010 + [self nsMenuX_NSMenu_removeItem:aItem fromTable:aTable]; 1.1011 + 1.1012 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.1013 + 1.1014 + if (aItem && aTable) { 1.1015 + NSValue *key = [NSValue valueWithPointer:aItem]; 1.1016 + KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key]; 1.1017 + if (shadowItem && [shadowItem hasTable:aTable]) { 1.1018 + if (![shadowItem removeTable:aTable]) 1.1019 + [gShadowKeyEquivDB removeObjectForKey:key]; 1.1020 + } 1.1021 + } 1.1022 + 1.1023 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.1024 +} 1.1025 + 1.1026 +@end 1.1027 + 1.1028 +// This class is needed to keep track of when the OS is (re)indexing all of 1.1029 +// our menus. This appears to only happen on Leopard and higher, and can 1.1030 +// be triggered by opening the Help menu. Some operations are unsafe while 1.1031 +// this is happening -- notably the calls to [[NSImage alloc] 1.1032 +// initWithSize:imageRect.size] and [newImage lockFocus] in nsMenuItemIconX:: 1.1033 +// OnStopFrame(). But we don't yet have a complete list, and Apple doesn't 1.1034 +// yet have any documentation on this subject. (Apple also doesn't yet have 1.1035 +// any documented way to find the information we seek here.) The "original" 1.1036 +// of this class (the one whose indexMenuBarDynamically method we hook) is 1.1037 +// defined in the Shortcut framework in /System/Library/PrivateFrameworks. 1.1038 +@interface NSObject (SCTGRLIndexMethodSwizzling) 1.1039 +- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically; 1.1040 +@end 1.1041 + 1.1042 +@implementation NSObject (SCTGRLIndexMethodSwizzling) 1.1043 + 1.1044 +- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically 1.1045 +{ 1.1046 + // This method appears to be called (once) whenever the OS (re)indexes our 1.1047 + // menus. sIndexingMenuLevel is a int32_t just in case it might be 1.1048 + // reentered. As it's running, it spawns calls to two undocumented 1.1049 + // HIToolbox methods (_SimulateMenuOpening() and _SimulateMenuClosed()), 1.1050 + // which "simulate" the opening and closing of our menus without actually 1.1051 + // displaying them. 1.1052 + ++nsMenuX::sIndexingMenuLevel; 1.1053 + [self nsMenuX_SCTGRLIndex_indexMenuBarDynamically]; 1.1054 + --nsMenuX::sIndexingMenuLevel; 1.1055 +} 1.1056 + 1.1057 +@end