widget/cocoa/nsMenuX.mm

changeset 0
6474c204b198
     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

mercurial