Bug 1292527: Make OSX application menu localizable at runtime. r=mstange
authorStephen A Pohl <spohl.mozilla.bugs@gmail.com>
Tue, 22 Nov 2016 11:18:48 -0500
changeset 323823 3d7142a4f06d94aa5701e570faf90751aa3d7469
parent 323822 6d906f5908d1014d3eb6f5a38d2d00aaaa2cc45e
child 323824 4690b433667e2aec6fe45989bccde1644f87f448
push id30983
push userphilringnalda@gmail.com
push dateWed, 23 Nov 2016 04:03:17 +0000
treeherdermozilla-central@0ddfec7126ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1292527
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1292527: Make OSX application menu localizable at runtime. r=mstange
widget/cocoa/nsMenuBarX.h
widget/cocoa/nsMenuBarX.mm
widget/cocoa/nsMenuX.mm
--- a/widget/cocoa/nsMenuBarX.h
+++ b/widget/cocoa/nsMenuBarX.h
@@ -10,20 +10,29 @@
 
 #include "mozilla/UniquePtr.h"
 #include "nsMenuBaseX.h"
 #include "nsMenuGroupOwnerX.h"
 #include "nsChangeObserver.h"
 #include "nsINativeMenuService.h"
 #include "nsString.h"
 
+class nsMenuBarX;
 class nsMenuX;
 class nsIWidget;
 class nsIContent;
 
+// ApplicationMenuDelegate is used to receive Cocoa notifications.
+@interface ApplicationMenuDelegate : NSObject<NSMenuDelegate>
+{
+  nsMenuBarX* mApplicationMenu; // weak ref
+}
+- (id)initWithApplicationMenu:(nsMenuBarX*)aApplicationMenu;
+@end
+
 // The native menu service for creating native menu bars.
 class nsNativeMenuServiceX : public nsINativeMenuService
 {
 public:
   NS_DECL_ISUPPORTS
 
   nsNativeMenuServiceX() {}
 
@@ -103,26 +112,30 @@ public:
   nsMenuX*          GetMenuAt(uint32_t aIndex);
   nsMenuX*          GetXULHelpMenu();
   void              SetSystemHelpMenu();
   nsresult          Paint();
   void              ForceUpdateNativeMenuAt(const nsAString& indexString);
   void              ForceNativeMenuReload(); // used for testing
   static char       GetLocalizedAccelKey(const char *shortcutID);
   static void       ResetNativeApplicationMenu();
+  void              SetNeedsRebuild();
+  void              ApplicationMenuOpened();
 
 protected:
   void              ConstructNativeMenus();
   void              ConstructFallbackNativeMenus();
   nsresult          InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex);
   void              RemoveMenuAtIndex(uint32_t aIndex);
   void              HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode);
   void              AquifyMenuBar();
   NSMenuItem*       CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action,
                                             int tag, NativeMenuItemTarget* target);
   nsresult          CreateApplicationMenu(nsMenuX* inMenu);
 
   nsTArray<mozilla::UniquePtr<nsMenuX>> mMenuArray;
   nsIWidget*         mParentWindow;        // [weak]
   GeckoNSMenu*       mNativeMenu;            // root menu, representing entire menu bar
+  bool               mNeedsRebuild;
+  ApplicationMenuDelegate* mApplicationMenuDelegate;
 };
 
 #endif // nsMenuBarX_h_
--- a/widget/cocoa/nsMenuBarX.mm
+++ b/widget/cocoa/nsMenuBarX.mm
@@ -50,22 +50,53 @@ NS_IMETHODIMP nsNativeMenuServiceX::Crea
 
   RefPtr<nsMenuBarX> mb = new nsMenuBarX();
   if (!mb)
     return NS_ERROR_OUT_OF_MEMORY;
 
   return mb->Create(aParent, aMenuBarNode);
 }
 
+//
+// ApplicationMenuDelegate Objective-C class
+//
+
+@implementation ApplicationMenuDelegate
+
+- (id)initWithApplicationMenu:(nsMenuBarX*)aApplicationMenu
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+  if ((self = [super init])) {
+    mApplicationMenu = aApplicationMenu;
+  }
+  return self;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)menuWillOpen:(NSMenu*)menu
+{
+  mApplicationMenu->ApplicationMenuOpened();
+}
+
+- (void)menuDidClose:(NSMenu*)menu
+{
+}
+
+@end
+
 nsMenuBarX::nsMenuBarX()
-: nsMenuGroupOwnerX(), mParentWindow(nullptr)
+: nsMenuGroupOwnerX(), mParentWindow(nullptr), mNeedsRebuild(false)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   mNativeMenu = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
+  mApplicationMenuDelegate =
+    [[ApplicationMenuDelegate alloc] initWithApplicationMenu:this];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 nsMenuBarX::~nsMenuBarX()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
@@ -87,16 +118,21 @@ nsMenuBarX::~nsMenuBarX()
   }
 
   // We have to manually clear the array here because clearing causes menu items
   // to call back into the menu bar to unregister themselves. We don't want to
   // depend on member variable ordering to ensure that the array gets cleared
   // before the registration hash table is destroyed.
   mMenuArray.Clear();
 
+  if (sApplicationMenu) {
+    ResetNativeApplicationMenu();
+  }
+  [mApplicationMenuDelegate release];
+
   [mNativeMenu release];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 nsresult nsMenuBarX::Create(nsIWidget* aParent, nsIContent* aContent)
 {
   if (!aParent)
@@ -173,16 +209,17 @@ void nsMenuBarX::ConstructFallbackNative
   NSString* keyStr= [NSString stringWithUTF8String:
                      NS_ConvertUTF16toUTF8(keyUTF16).get()];
 
   if (!nsMenuBarX::sNativeEventTarget) {
     nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init];
   }
 
   sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
+  [sApplicationMenu setDelegate:mApplicationMenuDelegate];
   NSMenuItem* quitMenuItem = [[[NSMenuItem alloc] initWithTitle:labelStr
                                                   action:@selector(menuItemHit:)
                                                   keyEquivalent:keyStr] autorelease];
   [quitMenuItem setTarget:nsMenuBarX::sNativeEventTarget];
   [quitMenuItem setTag:eCommand_ID_Quit];
   [sApplicationMenu addItem:quitMenuItem];
   sApplicationMenuIsFallback = YES;
 }
@@ -467,16 +504,32 @@ char nsMenuBarX::GetLocalizedAccelKey(co
 void nsMenuBarX::ResetNativeApplicationMenu()
 {
   [sApplicationMenu removeAllItems];
   [sApplicationMenu release];
   sApplicationMenu = nil;
   sApplicationMenuIsFallback = NO;
 }
 
+void nsMenuBarX::SetNeedsRebuild()
+{
+  mNeedsRebuild = true;
+}
+
+void nsMenuBarX::ApplicationMenuOpened()
+{
+  if (mNeedsRebuild) {
+    if (!mMenuArray.IsEmpty()) {
+      ResetNativeApplicationMenu();
+      CreateApplicationMenu(mMenuArray[0].get());
+    }
+    mNeedsRebuild = false;
+  }
+}
+
 // Hide the item in the menu by setting the 'hidden' attribute. Returns it in |outHiddenNode| so
 // the caller can hang onto it if they so choose. It is acceptable to pass nsull
 // for |outHiddenNode| if the caller doesn't care about the hidden node.
 void nsMenuBarX::HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode)
 {
   nsCOMPtr<nsIDOMElement> menuItem;
   inDoc->GetElementById(inID, getter_AddRefs(menuItem));
   nsCOMPtr<nsIContent> menuContent(do_QueryInterface(menuItem));
@@ -639,16 +692,18 @@ nsresult nsMenuBarX::CreateApplicationMe
          label="&preferencesCmdMac.label;"
            key="open_prefs_key"/>
 
   We need to use this system for localization purposes, until we have a better way
   to define the Application menu to be used on Mac OS X.
 */
 
   if (sApplicationMenu) {
+    [sApplicationMenu setDelegate:mApplicationMenuDelegate];
+
     // This code reads attributes we are going to care about from the DOM elements
 
     NSMenuItem *itemBeingAdded = nil;
     BOOL addAboutSeparator = FALSE;
 
     // Add the About menu item
     itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("aboutName"), @selector(menuItemHit:),
                                              eCommand_ID_About, nsMenuBarX::sNativeEventTarget);
--- a/widget/cocoa/nsMenuX.mm
+++ b/widget/cocoa/nsMenuX.mm
@@ -445,18 +445,23 @@ void nsMenuX::MenuConstruct()
 
   gConstructingMenu = false;
   mNeedsRebuild = false;
   // printf("Done building, mMenuObjectsArray.Count() = %d \n", mMenuObjectsArray.Count());
 }
 
 void nsMenuX::SetRebuild(bool aNeedsRebuild)
 {
-  if (!gConstructingMenu)
+  if (!gConstructingMenu) {
     mNeedsRebuild = aNeedsRebuild;
+    if (mParent->MenuObjectType() == eMenuBarObjectType) {
+      nsMenuBarX* mb = static_cast<nsMenuBarX*>(mParent);
+      mb->SetNeedsRebuild();
+    }
+  }
 }
 
 nsresult nsMenuX::SetEnabled(bool aIsEnabled)
 {
   if (aIsEnabled != mIsEnabled) {
     // we always want to rebuild when this changes
     mIsEnabled = aIsEnabled;
     [mNativeMenuItem setEnabled:(BOOL)mIsEnabled];