Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / widget / src / cocoa / nsMenuBarX.mm
blob4d40905a0c61e12bb82676007ce8ab9af73f1237
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is mozilla.org code.
16  *
17  * The Initial Developer of the Original Code is
18  * Netscape Communications Corporation.
19  * Portions created by the Initial Developer are Copyright (C) 1998
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *   Josh Aas <josh@mozilla.com>
24  *
25  * Alternatively, the contents of this file may be used under the terms of
26  * either the GNU General Public License Version 2 or later (the "GPL"), or
27  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28  * in which case the provisions of the GPL or the LGPL are applicable instead
29  * of those above. If you wish to allow use of your version of this file only
30  * under the terms of either the GPL or the LGPL, and not to allow others to
31  * use your version of this file under the terms of the MPL, indicate your
32  * decision by deleting the provisions above and replace them with the notice
33  * and other provisions required by the GPL or the LGPL. If you do not delete
34  * the provisions above, a recipient may use your version of this file under
35  * the terms of any one of the MPL, the GPL or the LGPL.
36  *
37  * ***** END LICENSE BLOCK ***** */
39 #include "nsMenuBarX.h"
40 #include "nsMenuX.h"
41 #include "nsMenuItemX.h"
42 #include "nsMenuUtilsX.h"
43 #include "nsCocoaUtils.h"
44 #include "nsCocoaWindow.h"
46 #include "nsCOMPtr.h"
47 #include "nsString.h"
48 #include "nsWidgetAtoms.h"
49 #include "nsGUIEvent.h"
50 #include "nsObjCExceptions.h"
51 #include "nsHashtable.h"
52 #include "nsThreadUtils.h"
54 #include "nsIContent.h"
55 #include "nsIWidget.h"
56 #include "nsIDocument.h"
57 #include "nsIDOMDocument.h"
58 #include "nsIDOMElement.h"
60 NS_IMPL_ISUPPORTS1(nsMenuBarX, nsIMutationObserver)
62 NativeMenuItemTarget* nsMenuBarX::sNativeEventTarget = nil;
63 nsMenuBarX* nsMenuBarX::sLastGeckoMenuBarPainted = nsnull;
64 NSMenu* sApplicationMenu = nil;
65 BOOL gSomeMenuBarPainted = NO;
67 // We keep references to the first quit and pref item content nodes we find, which
68 // will be from the hidden window. We use these when the document for the current
69 // window does not have a quit or pref item. We don't need strong refs here because
70 // these items are always strong ref'd by their owning menu bar (instance variable).
71 static nsIContent* sAboutItemContent = nsnull;
72 static nsIContent* sPrefItemContent  = nsnull;
73 static nsIContent* sQuitItemContent  = nsnull;
75 // Special command IDs that we know Mac OS X does not use for anything else. We use
76 // these in place of carbon's IDs for these commands in order to stop Carbon from
77 // messing with our event handlers. See bug 346883.
78 enum {
79   eCommand_ID_About = 1,
80   eCommand_ID_Prefs = 2,
81   eCommand_ID_Quit  = 3,
82   eCommand_ID_Last  = 4
86 NS_IMPL_ISUPPORTS1(nsNativeMenuServiceX, nsINativeMenuService)
88 NS_IMETHODIMP nsNativeMenuServiceX::CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode)
90   NS_ASSERTION(NS_IsMainThread(), "Attempting to create native menu bar on wrong thread!");
92   nsRefPtr<nsMenuBarX> mb = new nsMenuBarX();
93   if (!mb)
94     return NS_ERROR_OUT_OF_MEMORY;
96   return mb->Create(aParent, aMenuBarNode);
100 nsMenuBarX::nsMenuBarX()
101 : mParentWindow(nsnull),
102   mCurrentCommandID(eCommand_ID_Last),
103   mDocument(nsnull)
105   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
107   mNativeMenu = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
109   NS_OBJC_END_TRY_ABORT_BLOCK;
113 nsMenuBarX::~nsMenuBarX()
115   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
117   if (nsMenuBarX::sLastGeckoMenuBarPainted == this)
118     nsMenuBarX::sLastGeckoMenuBarPainted = nsnull;
120   // the quit/pref items of a random window might have been used if there was no
121   // hidden window, thus we need to invalidate the weak references.
122   if (sAboutItemContent == mAboutItemContent)
123     sAboutItemContent = nsnull;
124   if (sQuitItemContent == mQuitItemContent)
125     sQuitItemContent = nsnull;
126   if (sPrefItemContent == mPrefItemContent)
127     sPrefItemContent = nsnull;
129   // make sure we unregister ourselves as a document observer
130   if (mDocument)
131     mDocument->RemoveMutationObserver(this);
133   // We have to manually clear the array here because clearing causes menu items
134   // to call back into the menu bar to unregister themselves. We don't want to
135   // depend on member variable ordering to ensure that the array gets cleared
136   // before the registration hash table is destroyed.
137   mMenuArray.Clear();
139   [mNativeMenu release];
141   NS_OBJC_END_TRY_ABORT_BLOCK;
145 nsresult nsMenuBarX::Create(nsIWidget* aParent, nsIContent* aContent)
147   if (!aParent || !aContent)
148     return NS_ERROR_INVALID_ARG;
150   mParentWindow = aParent;
151   mContent = aContent;
153   AquifyMenuBar();
155   nsIDocument* doc = aContent->GetOwnerDoc();
156   if (!doc)
157     return NS_ERROR_FAILURE;
158   doc->AddMutationObserver(this);
159   mDocument = doc;
161   ConstructNativeMenus();
163   // Give this to the parent window. The parent takes ownership.
164   return mParentWindow->SetMenuBar(this);
168 void nsMenuBarX::ConstructNativeMenus()
170   PRUint32 count = mContent->GetChildCount();
171   for (PRUint32 i = 0; i < count; i++) { 
172     nsIContent *menuContent = mContent->GetChildAt(i);
173     if (menuContent &&
174         menuContent->Tag() == nsWidgetAtoms::menu &&
175         menuContent->IsNodeOfType(nsINode::eXUL)) {
176       nsMenuX* newMenu = new nsMenuX();
177       if (newMenu) {
178         nsresult rv = newMenu->Create(this, this, menuContent);
179         if (NS_SUCCEEDED(rv))
180           InsertMenuAtIndex(newMenu, GetMenuCount());
181         else
182           delete newMenu;
183       }
184     }
185   }  
189 PRUint32 nsMenuBarX::GetMenuCount()
191   return mMenuArray.Length();
195 bool nsMenuBarX::MenuContainsAppMenu()
197   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
199   return ([mNativeMenu numberOfItems] > 0 &&
200           [[mNativeMenu itemAtIndex:0] submenu] == sApplicationMenu);
202   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
206 nsresult nsMenuBarX::InsertMenuAtIndex(nsMenuX* aMenu, PRUint32 aIndex)
208   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
210   // If we haven't created a global Application menu yet, do it.
211   if (!sApplicationMenu) {
212     nsresult rv = NS_OK; // avoid warning about rv being unused
213     rv = CreateApplicationMenu(aMenu);
214     NS_ASSERTION(NS_SUCCEEDED(rv), "Can't create Application menu");
216     // Hook the new Application menu up to the menu bar.
217     NSMenu* mainMenu = [NSApp mainMenu];
218     NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
219     [[mainMenu itemAtIndex:0] setSubmenu:sApplicationMenu];
220   }
222   // add menu to array that owns our menus
223   mMenuArray.InsertElementAt(aIndex, aMenu);
225   // hook up submenus
226   nsIContent* menuContent = aMenu->Content();
227   if (menuContent->GetChildCount() > 0 &&
228       !nsMenuUtilsX::NodeIsHiddenOrCollapsed(menuContent)) {
229     PRUint32 insertAfter = 0;
230     nsresult rv = nsMenuUtilsX::CountVisibleBefore(this, aMenu, &insertAfter);
231     NS_ASSERTION(NS_SUCCEEDED(rv), "nsMenuUtilsX::CountVisibleBefore failed!\n");
232     if (NS_FAILED(rv))
233       return rv;
234     if (MenuContainsAppMenu())
235       insertAfter++;
236     [mNativeMenu insertItem:aMenu->NativeMenuItem() atIndex:insertAfter];
237   }
239   return NS_OK;
241   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
245 void nsMenuBarX::RemoveMenuAtIndex(PRUint32 aIndex)
247   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
249   NS_ASSERTION(aIndex < mMenuArray.Length(), "Attempting submenu removal with bad index!");
251   // Our native menu and our internal menu object array might be out of sync.
252   // This happens, for example, when a submenu is hidden. Because of this we
253   // should not assume that a native submenu is hooked up.
254   NSMenuItem* nativeMenuItem = mMenuArray[aIndex]->NativeMenuItem();
255   int nativeMenuItemIndex = [mNativeMenu indexOfItem:nativeMenuItem];
256   if (nativeMenuItemIndex != -1)
257     [mNativeMenu removeItemAtIndex:nativeMenuItemIndex];
259   mMenuArray.RemoveElementAt(aIndex);
261   NS_OBJC_END_TRY_ABORT_BLOCK;
265 void nsMenuBarX::ForceUpdateNativeMenuAt(const nsAString& indexString)
267   NSString* locationString = [NSString stringWithCharacters:indexString.BeginReading() length:indexString.Length()];
268   NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
269   unsigned int indexCount = [indexes count];
270   if (indexCount == 0)
271     return;
273   nsMenuX* currentMenu = NULL;
274   int targetIndex = [[indexes objectAtIndex:0] intValue];
275   int visible = 0;
276   PRUint32 length = mMenuArray.Length();
277   // first find a menu in the menu bar
278   for (unsigned int i = 0; i < length; i++) {
279     nsMenuX* menu = mMenuArray[i];
280     if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) {
281       visible++;
282       if (visible == (targetIndex + 1)) {
283         currentMenu = menu;
284         break;
285       }
286     }
287   }
289   if (!currentMenu)
290     return;
292   // fake open/close to cause lazy update to happen so submenus populate
293   nsMenuEvent menuEvent(PR_TRUE, NS_MENU_SELECTED, nsnull);
294   menuEvent.time = PR_IntervalNow();
295   menuEvent.mCommand = (PRUint32)_NSGetCarbonMenu(static_cast<NSMenu*>(currentMenu->NativeData()));
296   currentMenu->MenuOpened(menuEvent);
297   currentMenu->MenuClosed(menuEvent);
299   // now find the correct submenu
300   for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
301     targetIndex = [[indexes objectAtIndex:i] intValue];
302     visible = 0;
303     length = currentMenu->GetItemCount();
304     for (unsigned int j = 0; j < length; j++) {
305       nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
306       if (!targetMenu)
307         return;
308       if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
309         visible++;
310         if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
311           currentMenu = static_cast<nsMenuX*>(targetMenu);
312           // fake open/close to cause lazy update to happen
313           nsMenuEvent menuEvent(PR_TRUE, NS_MENU_SELECTED, nsnull);
314           menuEvent.time = PR_IntervalNow();
315           menuEvent.mCommand = (PRUint32)_NSGetCarbonMenu(static_cast<NSMenu*>(currentMenu->NativeData()));
316           currentMenu->MenuOpened(menuEvent);
317           currentMenu->MenuClosed(menuEvent);
318           break;
319         }
320       }
321     }
322   }
326 // Calling this forces a full reload of the menu system, reloading all native
327 // menus and their items.
328 // Without this testing is hard because changes to the DOM affect the native
329 // menu system lazily.
330 void nsMenuBarX::ForceNativeMenuReload()
332   // tear down everything
333   while (GetMenuCount() > 0)
334     RemoveMenuAtIndex(0);
336   // construct everything
337   ConstructNativeMenus();
341 nsMenuX* nsMenuBarX::GetMenuAt(PRUint32 aIndex)
343   if (mMenuArray.Length() <= aIndex) {
344     NS_ERROR("Requesting menu at invalid index!");
345     return NULL;
346   }
347   return mMenuArray[aIndex];
351 nsresult nsMenuBarX::Paint()
353   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
355   // Don't try to optimize anything in this painting by checking
356   // sLastGeckoMenuBarPainted because the menubar can be manipulated by
357   // native dialogs and sheet code and other things besides this paint method.
359   // We have to keep the same menu item for the Application menu so we keep
360   // passing it along.
361   NSMenu* outgoingMenu = [NSApp mainMenu];
362   NS_ASSERTION([outgoingMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
364   NSMenuItem* appMenuItem = [[outgoingMenu itemAtIndex:0] retain];
365   [outgoingMenu removeItemAtIndex:0];
366   [mNativeMenu insertItem:appMenuItem atIndex:0];
367   [appMenuItem release];
369   // Set menu bar and event target.
370   [NSApp setMainMenu:mNativeMenu];
371   nsMenuBarX::sLastGeckoMenuBarPainted = this;
373   gSomeMenuBarPainted = YES;
375   return NS_OK;
377   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
381 // Hide the item in the menu by setting the 'hidden' attribute. Returns it in |outHiddenNode| so
382 // the caller can hang onto it if they so choose. It is acceptable to pass nsull
383 // for |outHiddenNode| if the caller doesn't care about the hidden node.
384 void nsMenuBarX::HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode)
386   nsCOMPtr<nsIDOMElement> menuItem;
387   inDoc->GetElementById(inID, getter_AddRefs(menuItem));  
388   nsCOMPtr<nsIContent> menuContent(do_QueryInterface(menuItem));
389   if (menuContent) {
390     menuContent->SetAttr(kNameSpaceID_None, nsWidgetAtoms::hidden, NS_LITERAL_STRING("true"), PR_FALSE);
391     if (outHiddenNode) {
392       *outHiddenNode = menuContent.get();
393       NS_IF_ADDREF(*outHiddenNode);
394     }
395   }
399 // Do what is necessary to conform to the Aqua guidelines for menus.
400 void nsMenuBarX::AquifyMenuBar()
402   nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mContent->GetDocument()));
403   if (domDoc) {
404     // remove the "About..." item and its separator
405     HideItem(domDoc, NS_LITERAL_STRING("aboutSeparator"), nsnull);
406     HideItem(domDoc, NS_LITERAL_STRING("aboutName"), getter_AddRefs(mAboutItemContent));
407     if (!sAboutItemContent)
408       sAboutItemContent = mAboutItemContent;
410     // remove quit item and its separator
411     HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitSeparator"), nsnull);
412     HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitItem"), getter_AddRefs(mQuitItemContent));
413     if (!sQuitItemContent)
414       sQuitItemContent = mQuitItemContent;
415     
416     // remove prefs item and its separator, but save off the pref content node
417     // so we can invoke its command later.
418     HideItem(domDoc, NS_LITERAL_STRING("menu_PrefsSeparator"), nsnull);
419     HideItem(domDoc, NS_LITERAL_STRING("menu_preferences"), getter_AddRefs(mPrefItemContent));
420     if (!sPrefItemContent)
421       sPrefItemContent = mPrefItemContent;
422     
423     // hide items that we use for the Application menu
424     HideItem(domDoc, NS_LITERAL_STRING("menu_mac_services"), nsnull);
425     HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_app"), nsnull);
426     HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_others"), nsnull);
427     HideItem(domDoc, NS_LITERAL_STRING("menu_mac_show_all"), nsnull);
428   }
432 // for creating menu items destined for the Application menu
433 NSMenuItem* nsMenuBarX::CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action,
434                                                 int tag, NativeMenuItemTarget* target)
436   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
438   nsCOMPtr<nsIDocument> doc = inMenu->Content()->GetDocument();
439   if (!doc)
440     return nil;
442   nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(doc));
443   if (!domdoc)
444     return nil;
446   // Get information from the gecko menu item
447   nsAutoString label;
448   nsAutoString modifiers;
449   nsAutoString key;
450   nsCOMPtr<nsIDOMElement> menuItem;
451   domdoc->GetElementById(nodeID, getter_AddRefs(menuItem));
452   if (menuItem) {
453     menuItem->GetAttribute(NS_LITERAL_STRING("label"), label);
454     menuItem->GetAttribute(NS_LITERAL_STRING("modifiers"), modifiers);
455     menuItem->GetAttribute(NS_LITERAL_STRING("key"), key);
456   }
457   else {
458     return nil;
459   }
461   // Get more information about the key equivalent. Start by
462   // finding the key node we need.
463   NSString* keyEquiv = nil;
464   unsigned int macKeyModifiers = 0;
465   if (!key.IsEmpty()) {
466     nsCOMPtr<nsIDOMElement> keyElement;
467     domdoc->GetElementById(key, getter_AddRefs(keyElement));
468     if (keyElement) {
469       nsCOMPtr<nsIContent> keyContent (do_QueryInterface(keyElement));
470       // first grab the key equivalent character
471       nsAutoString keyChar(NS_LITERAL_STRING(" "));
472       keyContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::key, keyChar);
473       if (!keyChar.EqualsLiteral(" ")) {
474         keyEquiv = [[NSString stringWithCharacters:keyChar.get() length:keyChar.Length()] lowercaseString];
475       }
476       // now grab the key equivalent modifiers
477       nsAutoString modifiersStr;
478       keyContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::modifiers, modifiersStr);
479       PRUint8 geckoModifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
480       macKeyModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(geckoModifiers);
481     }
482   }
483   // get the label into NSString-form
484   NSString* labelString = [NSString stringWithCharacters:label.get() length:label.Length()];
485   
486   if (!labelString)
487     labelString = @"";
488   if (!keyEquiv)
489     keyEquiv = @"";
491   // put together the actual NSMenuItem
492   NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:labelString action:action keyEquivalent:keyEquiv];
493   
494   [newMenuItem setTag:tag];
495   [newMenuItem setTarget:target];
496   [newMenuItem setKeyEquivalentModifierMask:macKeyModifiers];
497   
498   return newMenuItem;
500   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
504 // build the Application menu shared by all menu bars
505 nsresult nsMenuBarX::CreateApplicationMenu(nsMenuX* inMenu)
507   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
509   // At this point, the application menu is the application menu from
510   // the nib in cocoa widgets. We do not have a way to create an application
511   // menu manually, so we grab the one from the nib and use that.
512   sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
513   
515   We support the following menu items here:
517   Menu Item             DOM Node ID             Notes
518   
519   ==================
520   = About This App = <- aboutName
521   ==================
522   = Preferences... = <- menu_preferences
523   ==================
524   = Services     > = <- menu_mac_services    <- (do not define key equivalent)
525   ==================
526   = Hide App       = <- menu_mac_hide_app
527   = Hide Others    = <- menu_mac_hide_others
528   = Show All       = <- menu_mac_show_all
529   ==================
530   = Quit           = <- menu_FileQuitItem
531   ==================
532   
533   If any of them are ommitted from the application's DOM, we just don't add
534   them. We always add a "Quit" item, but if an app developer does not provide a
535   DOM node with the right ID for the Quit item, we add it in English. App
536   developers need only add each node with a label and a key equivalent (if they
537   want one). Other attributes are optional. Like so:
538   
539   <menuitem id="menu_preferences"
540          label="&preferencesCmdMac.label;"
541            key="open_prefs_key"/>
542   
543   We need to use this system for localization purposes, until we have a better way
544   to define the Application menu to be used on Mac OS X.
547   if (sApplicationMenu) {
548     // This code reads attributes we are going to care about from the DOM elements
549     
550     NSMenuItem *itemBeingAdded = nil;
551     
552     // Add the About menu item
553     itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("aboutName"), @selector(menuItemHit:),
554                                              eCommand_ID_About, nsMenuBarX::sNativeEventTarget);
555     if (itemBeingAdded) {
556       [sApplicationMenu addItem:itemBeingAdded];
557       [itemBeingAdded release];
558       itemBeingAdded = nil;
559       
560       // Add separator after About menu
561       [sApplicationMenu addItem:[NSMenuItem separatorItem]];      
562     }
563     
564     // Add the Preferences menu item
565     itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_preferences"), @selector(menuItemHit:),
566                                              eCommand_ID_Prefs, nsMenuBarX::sNativeEventTarget);
567     if (itemBeingAdded) {
568       [sApplicationMenu addItem:itemBeingAdded];
569       [itemBeingAdded release];
570       itemBeingAdded = nil;
571       
572       // Add separator after Preferences menu
573       [sApplicationMenu addItem:[NSMenuItem separatorItem]];      
574     }
576     // Add Services menu item
577     itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_services"), nil,
578                                              0, nil);
579     if (itemBeingAdded) {
580       [sApplicationMenu addItem:itemBeingAdded];
581       
582       // set this menu item up as the Mac OS X Services menu
583       NSMenu* servicesMenu = [[NSMenu alloc] initWithTitle:@""];
584       [itemBeingAdded setSubmenu:servicesMenu];
585       [NSApp setServicesMenu:servicesMenu];
586       
587       [itemBeingAdded release];
588       itemBeingAdded = nil;
589       
590       // Add separator after Services menu
591       [sApplicationMenu addItem:[NSMenuItem separatorItem]];      
592     }
593     
594     BOOL addHideShowSeparator = FALSE;
595     
596     // Add menu item to hide this application
597     itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_app"), @selector(hide:),
598                                              0, NSApp);
599     if (itemBeingAdded) {
600       [sApplicationMenu addItem:itemBeingAdded];
601       [itemBeingAdded release];
602       itemBeingAdded = nil;
603       
604       addHideShowSeparator = TRUE;
605     }
606     
607     // Add menu item to hide other applications
608     itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_others"), @selector(hideOtherApplications:),
609                                              0, NSApp);
610     if (itemBeingAdded) {
611       [sApplicationMenu addItem:itemBeingAdded];
612       [itemBeingAdded release];
613       itemBeingAdded = nil;
614       
615       addHideShowSeparator = TRUE;
616     }
617     
618     // Add menu item to show all applications
619     itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_show_all"), @selector(unhideAllApplications:),
620                                              0, NSApp);
621     if (itemBeingAdded) {
622       [sApplicationMenu addItem:itemBeingAdded];
623       [itemBeingAdded release];
624       itemBeingAdded = nil;
625       
626       addHideShowSeparator = TRUE;
627     }
628     
629     // Add a separator after the hide/show menus if at least one exists
630     if (addHideShowSeparator)
631       [sApplicationMenu addItem:[NSMenuItem separatorItem]];
632     
633     // Add quit menu item
634     itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_FileQuitItem"), @selector(menuItemHit:),
635                                              eCommand_ID_Quit, nsMenuBarX::sNativeEventTarget);
636     if (itemBeingAdded) {
637       [sApplicationMenu addItem:itemBeingAdded];
638       [itemBeingAdded release];
639       itemBeingAdded = nil;
640     }
641     else {
642       // the current application does not have a DOM node for "Quit". Add one
643       // anyway, in English.
644       NSMenuItem* defaultQuitItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(menuItemHit:)
645                                                          keyEquivalent:@"q"] autorelease];
646       [defaultQuitItem setTarget:nsMenuBarX::sNativeEventTarget];
647       [defaultQuitItem setTag:eCommand_ID_Quit];
648       [sApplicationMenu addItem:defaultQuitItem];
649     }
650   }
651   
652   return (sApplicationMenu) ? NS_OK : NS_ERROR_FAILURE;
654   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
658 void nsMenuBarX::SetParent(nsIWidget* aParent)
660   mParentWindow = aParent;
665 // nsIMutationObserver
669 void nsMenuBarX::CharacterDataWillChange(nsIDocument* aDocument,
670                                          nsIContent* aContent,
671                                          CharacterDataChangeInfo* aInfo)
676 void nsMenuBarX::CharacterDataChanged(nsIDocument* aDocument,
677                                       nsIContent* aContent,
678                                       CharacterDataChangeInfo* aInfo)
683 void nsMenuBarX::ContentAppended(nsIDocument* aDocument, nsIContent* aContainer,
684                                  PRInt32 aNewIndexInContainer)
686   PRUint32 childCount = aContainer->GetChildCount();
687   while ((PRUint32)aNewIndexInContainer < childCount) {
688     nsIContent *child = aContainer->GetChildAt(aNewIndexInContainer);
689     ContentInserted(aDocument, aContainer, child, aNewIndexInContainer);
690     aNewIndexInContainer++;
691   }
695 void nsMenuBarX::NodeWillBeDestroyed(const nsINode * aNode)
697   // our menu bar node is being destroyed
698   mDocument = nsnull;
702 void nsMenuBarX::AttributeChanged(nsIDocument * aDocument, nsIContent * aContent,
703                                   PRInt32 aNameSpaceID, nsIAtom * aAttribute,
704                                   PRInt32 aModType, PRUint32 aStateMask)
706   nsChangeObserver* obs = LookupContentChangeObserver(aContent);
707   if (obs)
708     obs->ObserveAttributeChanged(aDocument, aContent, aAttribute);
712 void nsMenuBarX::ContentRemoved(nsIDocument * aDocument, nsIContent * aContainer,
713                                 nsIContent * aChild, PRInt32 aIndexInContainer)
715   if (aContainer == mContent) {
716     RemoveMenuAtIndex(aIndexInContainer);
717   }
718   else {
719     nsChangeObserver* obs = LookupContentChangeObserver(aContainer);
720     if (obs) {
721       obs->ObserveContentRemoved(aDocument, aChild, aIndexInContainer);
722     }
723     else {
724       // We do a lookup on the parent container in case things were removed
725       // under a "menupopup" item. That is basically a wrapper for the contents
726       // of a "menu" node.
727       nsCOMPtr<nsIContent> parent = aContainer->GetParent();
728       if (parent) {
729         obs = LookupContentChangeObserver(parent);
730         if (obs)
731           obs->ObserveContentRemoved(aDocument, aChild, aIndexInContainer);
732       }
733     }
734   }
738 void nsMenuBarX::ContentInserted(nsIDocument * aDocument, nsIContent * aContainer,
739                                  nsIContent * aChild, PRInt32 aIndexInContainer)
741   if (aContainer == mContent) {
742     nsMenuX* newMenu = new nsMenuX();
743     if (newMenu) {
744       nsresult rv = newMenu->Create(this, this, aChild);
745       if (NS_SUCCEEDED(rv))
746         InsertMenuAtIndex(newMenu, aIndexInContainer);
747       else
748         delete newMenu;
749     }
750   }
751   else {
752     nsChangeObserver* obs = LookupContentChangeObserver(aContainer);
753     if (obs)
754       obs->ObserveContentInserted(aDocument, aChild, aIndexInContainer);
755     else {
756       // We do a lookup on the parent container in case things were removed
757       // under a "menupopup" item. That is basically a wrapper for the contents
758       // of a "menu" node.
759       nsCOMPtr<nsIContent> parent = aContainer->GetParent();
760       if (parent) {
761         obs = LookupContentChangeObserver(parent);
762         if (obs)
763           obs->ObserveContentInserted(aDocument, aChild, aIndexInContainer);
764       }
765     }
766   }
770 void nsMenuBarX::ParentChainChanged(nsIContent *aContent)
775 // For change management, we don't use a |nsSupportsHashtable| because we know that the
776 // lifetime of all these items is bounded by the lifetime of the menubar. No need to add
777 // any more strong refs to the picture because the containment hierarchy already uses
778 // strong refs.
779 void nsMenuBarX::RegisterForContentChanges(nsIContent *aContent, nsChangeObserver *aMenuObject)
781   nsVoidKey key(aContent);
782   mObserverTable.Put(&key, aMenuObject);
786 void nsMenuBarX::UnregisterForContentChanges(nsIContent *aContent)
788   nsVoidKey key(aContent);
789   mObserverTable.Remove(&key);
793 nsChangeObserver* nsMenuBarX::LookupContentChangeObserver(nsIContent* aContent)
795   nsVoidKey key(aContent);
796   return reinterpret_cast<nsChangeObserver*>(mObserverTable.Get(&key));
800 // Given a menu item, creates a unique 4-character command ID and
801 // maps it to the item. Returns the id for use by the client.
802 PRUint32 nsMenuBarX::RegisterForCommand(nsMenuItemX* inMenuItem)
804   // no real need to check for uniqueness. We always start afresh with each
805   // window at 1. Even if we did get close to the reserved Apple command id's,
806   // those don't start until at least '    ', which is integer 538976288. If
807   // we have that many menu items in one window, I think we have other problems.
809   // make id unique
810   ++mCurrentCommandID;
812   // put it in the table, set out param for client
813   nsPRUint32Key key(mCurrentCommandID);
814   mObserverTable.Put(&key, inMenuItem);
816   return mCurrentCommandID;
820 // Removes the mapping between the given 4-character command ID
821 // and its associated menu item.
822 void nsMenuBarX::UnregisterCommand(PRUint32 inCommandID)
824   nsPRUint32Key key(inCommandID);
825   mObserverTable.Remove(&key);
829 nsMenuItemX* nsMenuBarX::GetMenuItemForCommandID(PRUint32 inCommandID)
831   nsPRUint32Key key(inCommandID);
832   return reinterpret_cast<nsMenuItemX*>(mObserverTable.Get(&key));
837 // Objective-C class used to allow us to have keyboard commands
838 // look like they are doing something but actually do nothing.
839 // We allow mouse actions to work normally.
843 // This tells us whether or not pKE is on the stack from a GeckoNSMenu. If it
844 // is nil, it is not on the stack. The non-nil value is the object that put it
845 // on the stack first.
846 static GeckoNSMenu* gPerformKeyEquivOnStack = nil;
847 // If this is YES, act on key equivs.
848 static BOOL gActOnKeyEquiv = NO;
849 // When this variable is set to NO, don't do special command processing.
850 static BOOL gActOnSpecialCommands = YES;
852 @implementation GeckoNSMenu
854 - (BOOL)performKeyEquivalent:(NSEvent *)theEvent
856   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
858   NS_ASSERTION(gPerformKeyEquivOnStack != self, "GeckoNSMenu pKE re-entering for the same object!");
860   // Don't bother doing this if we don't have any items. It appears as though
861   // the OS will sometimes expect this sort of check.
862   if ([self numberOfItems] <= 0)
863     return NO;
865   if (!gPerformKeyEquivOnStack)
866     gPerformKeyEquivOnStack = self;
867   BOOL rv = [super performKeyEquivalent:theEvent];
868   if (gPerformKeyEquivOnStack == self)
869     gPerformKeyEquivOnStack = nil;
870   return rv;
872   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
876 -(void)actOnKeyEquivalent:(NSEvent *)theEvent
878   gActOnKeyEquiv = YES;
879   [self performKeyEquivalent:theEvent];
880   gActOnKeyEquiv = NO;
884 - (void)performMenuUserInterfaceEffectsForEvent:(NSEvent*)theEvent
886   gActOnSpecialCommands = NO;
887   [self performKeyEquivalent:theEvent];
888   gActOnSpecialCommands = YES;
891 @end
895 // Objective-C class used as action target for menu items
899 @implementation NativeMenuItemTarget
901 // called when some menu item in this menu gets hit
902 -(IBAction)menuItemHit:(id)sender
904   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
906   int tag = [sender tag];
907   nsMenuBarX* menuBar = nsMenuBarX::sLastGeckoMenuBarPainted;
908   if (!menuBar)
909     return;
911   // We want to avoid processing app-global commands when we are asked to
912   // perform native menu effects only. This avoids sending events twice,
913   // which can lead to major problems.
914   if (gActOnSpecialCommands) {
915     // Do special processing if this is for an app-global command.
916     if (tag == eCommand_ID_About) {
917       nsIContent* mostSpecificContent = sAboutItemContent;
918       if (menuBar && menuBar->mAboutItemContent)
919         mostSpecificContent = menuBar->mAboutItemContent;
920       nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
921     }
922     else if (tag == eCommand_ID_Prefs) {
923       nsIContent* mostSpecificContent = sPrefItemContent;
924       if (menuBar && menuBar->mPrefItemContent)
925         mostSpecificContent = menuBar->mPrefItemContent;
926       nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
927     }
928     else if (tag == eCommand_ID_Quit) {
929       nsIContent* mostSpecificContent = sQuitItemContent;
930       if (menuBar && menuBar->mQuitItemContent)
931         mostSpecificContent = menuBar->mQuitItemContent;
932       // If we have some content for quit we execute it. Otherwise we send a native app terminate
933       // message. If you want to stop a quit from happening, provide quit content and return
934       // the event as unhandled.
935       if (mostSpecificContent) {
936         nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
937       }
938       else {
939         [NSApp terminate:nil];
940         return;
941       }
942     }
943     // Quit now if the "active" menu bar has changed (as the result of
944     // processing an app-global command above).  This resolves bmo bug
945     // 430506.
946     if (menuBar != nsMenuBarX::sLastGeckoMenuBarPainted)
947       return;
948   }
950   // Don't do anything unless this is not a keyboard command and
951   // this isn't for the hidden window menu. We assume that if there
952   // is no main window then the hidden window menu bar is up, even
953   // if that isn't true for some reason we better play it safe if
954   // there is no main window.
955   if (gPerformKeyEquivOnStack && !gActOnKeyEquiv && [NSApp mainWindow])
956     return;
958   // given the commandID, look it up in our hashtable and dispatch to
959   // that menu item.
960   if (menuBar) {
961     nsMenuItemX* menuItem = menuBar->GetMenuItemForCommandID(static_cast<PRUint32>(tag));
962     if (menuItem)
963       menuItem->DoCommand();
964   }
966   NS_OBJC_END_TRY_ABORT_BLOCK;
969 @end