Bug 1292527: Make OSX application menu localizable at runtime. r=mstange
authorStephen A Pohl <spohl.mozilla.bugs@gmail.com>
Tue, 29 Nov 2016 21:22:13 -0500
changeset 324766 896204eba38856f07ab347ee70511c88040a101f
parent 324765 66c6b9f68e500b51fbfdf31768757ce49e0bc365
child 324767 cceff8069b0b623ee59e63f77fbe74ef4f97aa79
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersmstange
bugs1292527
milestone53.0a1
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,18 +50,48 @@ 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),
+  mApplicationMenuDelegate(nil)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   mNativeMenu = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
@@ -87,16 +117,20 @@ 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 (mApplicationMenuDelegate) {
+    [mApplicationMenuDelegate release];
+  }
+
   [mNativeMenu release];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 nsresult nsMenuBarX::Create(nsIWidget* aParent, nsIContent* aContent)
 {
   if (!aParent)
@@ -173,16 +207,21 @@ 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];
+  if (!mApplicationMenuDelegate) {
+    mApplicationMenuDelegate =
+      [[ApplicationMenuDelegate alloc] initWithApplicationMenu:this];
+  }
+  [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 +506,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 +694,22 @@ 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) {
+    if (!mApplicationMenuDelegate) {
+      mApplicationMenuDelegate =
+        [[ApplicationMenuDelegate alloc] initWithApplicationMenu:this];
+    }
+    [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];