BookmarkManager: Fix 'new folder text field size changes on clicking it' issue.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / apps / app_shim_menu_controller_mac.mm
blobefb348592403b6d71ec5bb26df9e8a22228c3c9a
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.h"
7 #include "base/command_line.h"
8 #include "base/mac/scoped_nsautorelease_pool.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/app/chrome_command_ids.h"
12 #include "chrome/browser/apps/app_shim/extension_app_shim_handler_mac.h"
13 #include "chrome/browser/apps/app_window_registry_util.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_finder.h"
17 #import "chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h"
18 #include "chrome/common/chrome_switches.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "extensions/browser/app_window/app_window.h"
21 #include "extensions/common/extension.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/base/l10n/l10n_util_mac.h"
25 using extensions::Extension;
27 namespace {
29 // When an app window loses main status, AppKit may make another app window main
30 // instead. Rather than trying to predict what AppKit will do (which is hard),
31 // just protect against changes in the event queue that will clobber each other.
32 int g_window_cycle_sequence_number = 0;
34 // Whether Custom Cmd+` window cycling is enabled for apps.
35 bool IsAppWindowCyclingEnabled() {
36   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
37   if (command_line->HasSwitch(switches::kDisableAppWindowCycling))
38     return false;
39   if (command_line->HasSwitch(switches::kEnableAppWindowCycling))
40     return true;
42   return false;  // Current default.
45 // Gets an item from the main menu given the tag of the top level item
46 // |menu_tag| and the tag of the item |item_tag|.
47 NSMenuItem* GetItemByTag(NSInteger menu_tag, NSInteger item_tag) {
48   return [[[[NSApp mainMenu] itemWithTag:menu_tag] submenu]
49       itemWithTag:item_tag];
52 // Finds a top level menu item using |menu_tag| and creates a new NSMenuItem
53 // with the same title.
54 NSMenuItem* NewTopLevelItemFrom(NSInteger menu_tag) {
55   NSMenuItem* original = [[NSApp mainMenu] itemWithTag:menu_tag];
56   base::scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc]
57       initWithTitle:[original title]
58              action:nil
59       keyEquivalent:@""]);
60   DCHECK([original hasSubmenu]);
61   base::scoped_nsobject<NSMenu> sub_menu([[NSMenu alloc]
62       initWithTitle:[[original submenu] title]]);
63   [item setSubmenu:sub_menu];
64   return item.autorelease();
67 // Finds an item using |menu_tag| and |item_tag| and adds a duplicate of it to
68 // the submenu of |top_level_item|.
69 void AddDuplicateItem(NSMenuItem* top_level_item,
70                       NSInteger menu_tag,
71                       NSInteger item_tag) {
72   base::scoped_nsobject<NSMenuItem> item(
73       [GetItemByTag(menu_tag, item_tag) copy]);
74   DCHECK(item);
75   [[top_level_item submenu] addItem:item];
78 // Finds an item with |item_tag| and removes it from the submenu of
79 // |top_level_item|.
80 void RemoveMenuItemWithTag(NSMenuItem* top_level_item,
81                            NSInteger item_tag,
82                            bool remove_following_separator) {
83   NSMenu* submenu = [top_level_item submenu];
84   NSInteger index = [submenu indexOfItemWithTag:item_tag];
85   if (index < 0)
86     return;
88   [submenu removeItemAtIndex:index];
90   if (!remove_following_separator || index == [submenu numberOfItems])
91     return;
93   NSMenuItem* nextItem = [submenu itemAtIndex:index];
94   if ([nextItem isSeparatorItem])
95     [submenu removeItem:nextItem];
98 // Sets the menu item with |item_tag| in |top_level_item| visible.
99 // If |has_alternate| is true, the item immediately following |item_tag| is
100 // assumed to be its (only) alternate. Since AppKit is unable to hide items
101 // with alternates, |has_alternate| will cause -[NSMenuItem alternate] to be
102 // removed when hiding and restored when showing.
103 void SetItemWithTagVisible(NSMenuItem* top_level_item,
104                            NSInteger item_tag,
105                            bool visible,
106                            bool has_alternate) {
107   NSMenu* submenu = [top_level_item submenu];
108   NSMenuItem* menu_item = [submenu itemWithTag:item_tag];
109   DCHECK(menu_item);
111   if (visible != [menu_item isHidden])
112     return;
114   if (!has_alternate) {
115     [menu_item setHidden:!visible];
116     return;
117   }
119   NSInteger next_index = [submenu indexOfItem:menu_item] + 1;
120   DCHECK_LT(next_index, [submenu numberOfItems]);
122   NSMenuItem* alternate_item = [submenu itemAtIndex:next_index];
123   if (!visible) {
124     // When hiding (only), we can verify the assumption that the item following
125     // |item_tag| is actually an alternate.
126     DCHECK([alternate_item isAlternate]);
127   }
129   // The alternate item visibility should always be in sync.
130   DCHECK_EQ([alternate_item isHidden], [menu_item isHidden]);
131   [alternate_item setAlternate:visible];
132   [alternate_item setHidden:!visible];
133   [menu_item setHidden:!visible];
136 // Return the Extension (if any) associated with the given window. If it is not
137 // a platform app nor hosted app, but it is a browser, |is_browser| will be set
138 // to true (otherwise false).
139 const Extension* GetExtensionForNSWindow(NSWindow* window, bool* is_browser) {
140   const Extension* extension = nullptr;
141   Browser* browser = nullptr;
143   extensions::AppWindow* app_window =
144       AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(window);
145   if (app_window) {
146     extension = app_window->GetExtension();
147   } else {
148     // If there is no corresponding AppWindow, this could be a hosted app, so
149     // check for a browser.
150     browser = chrome::FindBrowserWithWindow(window);
151     extension = apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
152   }
154   *is_browser = extension == nullptr && browser != nullptr;
155   return extension;
158 // Sets or clears NSWindowCollectionBehaviorIgnoresCycle for |window|. Does not
159 // change NSWindowCollectionBehaviorParticipatesInCycle. That exists, e.g, for
160 // an NSPanel to override its default behavior, but this should only ever be
161 // called for Browser windows and App windows (which are not panels).
162 bool SetWindowParticipatesInCycle(NSWindow* window, bool participates) {
163   const NSWindowCollectionBehavior past_behavior = [window collectionBehavior];
164   NSWindowCollectionBehavior behavior = past_behavior;
165   if (participates)
166     behavior &= ~NSWindowCollectionBehaviorIgnoresCycle;
167   else
168     behavior |= NSWindowCollectionBehaviorIgnoresCycle;
170   // Often, there is no change. AppKit has no early exit since the value is
171   // derived partially from styleMask and other things, so do our own.
172   if (behavior == past_behavior)
173     return false;
175   [window setCollectionBehavior:behavior];
176   return true;
179 // Sets the window cycle list to |app_id|'s windows only.
180 void SetAppCyclesWindows(const std::string& app_id, int sequence_number) {
181   if (g_window_cycle_sequence_number != sequence_number)
182     return;
184   bool any_change = false;
185   for (NSWindow* window : [NSApp windows]) {
186     bool is_browser;
187     const Extension* extension = GetExtensionForNSWindow(window, &is_browser);
188     if (extension && extension->id() == app_id)
189       any_change |= SetWindowParticipatesInCycle(window, true);
190     else if (extension || is_browser)
191       any_change |= SetWindowParticipatesInCycle(window, false);
192   }
194   // Without the following, -[NSApplication _getLockedWindowListForCycle] will
195   // happily return windows that were just set to ignore window cycling. Doing
196   // this seems to trick AppKit into updating the window cycle list. But it is a
197   // bit scary, so avoid it when there is no change. These attempts were based
198   // on the observation that clicking a window twice to switch focus would
199   // always work. Also tried (without luck):
200   //  - [NSApp setWindowsNeedUpdate:YES],
201   //  - Creating a deferred NSWindow and immediately releasing it,
202   //  - Calling private methods like [NSApp _unlockWindowListForCycle],
203   //  - [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined...
204   //      (an attempt to tickle AppKit into an update of some kind),
205   //  - Calling synchronously (i.e. not via PostTask) <- this was actually the
206   //      initial attempt. Then, switching to PostTask didn't help with this
207   //      quirk, but was useful for the sequence number stuff, and
208   //  - Re-ordering collection behavior changes to ensure one window was always
209   //      participating (i.e. all 'adds' before any 'removes').
210   if (any_change)
211     [[NSApp keyWindow] makeKeyAndOrderFront:nil];
214 // Sets the window cycle list to Chrome browser windows only.
215 void SetChromeCyclesWindows(int sequence_number) {
216   if (g_window_cycle_sequence_number != sequence_number)
217     return;
219   bool any_change = false;
220   for (NSWindow* window : [NSApp windows]) {
221     bool is_browser;
222     const Extension* extension = GetExtensionForNSWindow(window, &is_browser);
223     if (extension || is_browser)
224       any_change |= SetWindowParticipatesInCycle(window, is_browser);
225   }
226   if (any_change)
227     [[NSApp keyWindow] makeKeyAndOrderFront:nil];
230 }  // namespace
232 // Used by AppShimMenuController to manage menu items that are a copy of a
233 // Chrome menu item but with a different action. This manages unsetting and
234 // restoring the original item's key equivalent, so that we can use the same
235 // key equivalent in the copied item with a different action. If |resourceId_|
236 // is non-zero, this will also update the title to include the app name.
237 // If the copy (menuItem) has no key equivalent, and the title does not have the
238 // app name, then enableForApp and disable do not need to be called. I.e. the
239 // doppelganger just copies the item and sets a new action.
240 @interface DoppelgangerMenuItem : NSObject {
241  @private
242   base::scoped_nsobject<NSMenuItem> menuItem_;
243   base::scoped_nsobject<NSMenuItem> sourceItem_;
244   base::scoped_nsobject<NSString> sourceKeyEquivalent_;
245   int resourceId_;
248 @property(readonly, nonatomic) NSMenuItem* menuItem;
250 // Get the source item using the tags and create the menu item.
251 - (id)initWithController:(AppShimMenuController*)controller
252                  menuTag:(NSInteger)menuTag
253                  itemTag:(NSInteger)itemTag
254               resourceId:(int)resourceId
255                   action:(SEL)action
256            keyEquivalent:(NSString*)keyEquivalent;
257 // Retain the source item given |menuTag| and |sourceItemTag|. Copy
258 // the menu item given |menuTag| and |targetItemTag|.
259 // This is useful when we want a doppelganger with a different source item.
260 // For example, if there are conflicting key equivalents.
261 - (id)initWithMenuTag:(NSInteger)menuTag
262         sourceItemTag:(NSInteger)sourceItemTag
263         targetItemTag:(NSInteger)targetItemTag
264         keyEquivalent:(NSString*)keyEquivalent;
265 // Set the title using |resourceId_| and unset the source item's key equivalent.
266 - (void)enableForApp:(const Extension*)app;
267 // Restore the source item's key equivalent.
268 - (void)disable;
269 @end
271 @implementation DoppelgangerMenuItem
273 - (NSMenuItem*)menuItem {
274   return menuItem_;
277 - (id)initWithController:(AppShimMenuController*)controller
278                  menuTag:(NSInteger)menuTag
279                  itemTag:(NSInteger)itemTag
280               resourceId:(int)resourceId
281                   action:(SEL)action
282            keyEquivalent:(NSString*)keyEquivalent {
283   if ((self = [super init])) {
284     sourceItem_.reset([GetItemByTag(menuTag, itemTag) retain]);
285     DCHECK(sourceItem_);
286     sourceKeyEquivalent_.reset([[sourceItem_ keyEquivalent] copy]);
287     menuItem_.reset([[NSMenuItem alloc]
288         initWithTitle:[sourceItem_ title]
289                action:action
290         keyEquivalent:keyEquivalent]);
291     [menuItem_ setTarget:controller];
292     [menuItem_ setTag:itemTag];
293     resourceId_ = resourceId;
294   }
295   return self;
298 - (id)initWithMenuTag:(NSInteger)menuTag
299         sourceItemTag:(NSInteger)sourceItemTag
300         targetItemTag:(NSInteger)targetItemTag
301         keyEquivalent:(NSString*)keyEquivalent {
302   if ((self = [super init])) {
303     menuItem_.reset([GetItemByTag(menuTag, targetItemTag) copy]);
304     sourceItem_.reset([GetItemByTag(menuTag, sourceItemTag) retain]);
305     DCHECK(menuItem_);
306     DCHECK(sourceItem_);
307     sourceKeyEquivalent_.reset([[sourceItem_ keyEquivalent] copy]);
308   }
309   return self;
312 - (void)enableForApp:(const Extension*)app {
313   // It seems that two menu items that have the same key equivalent must also
314   // have the same action for the keyboard shortcut to work. (This refers to the
315   // original keyboard shortcut, regardless of any overrides set in OSX).
316   // In order to let the app menu items have a different action, we remove the
317   // key equivalent of the original items and restore them later.
318   [sourceItem_ setKeyEquivalent:@""];
319   if (!resourceId_)
320     return;
322   [menuItem_ setTitle:l10n_util::GetNSStringF(resourceId_,
323                                               base::UTF8ToUTF16(app->name()))];
326 - (void)disable {
327   // Restore the keyboard shortcut to Chrome. This just needs to be set back to
328   // the original keyboard shortcut, regardless of any overrides in OSX. The
329   // overrides still work as they are based on the title of the menu item.
330   [sourceItem_ setKeyEquivalent:sourceKeyEquivalent_];
333 @end
335 @interface AppShimMenuController ()
336 // Construct the NSMenuItems for apps.
337 - (void)buildAppMenuItems;
338 // Register for NSWindow notifications.
339 - (void)registerEventHandlers;
340 // If the window is an app window, add or remove menu items.
341 - (void)windowMainStatusChanged:(NSNotification*)notification;
342 // Called when |app| becomes the main window in the Chrome process.
343 - (void)appBecameMain:(const Extension*)app;
344 // Called when there is no main window, or if the main window is not an app.
345 - (void)chromeBecameMain;
346 // Add menu items for an app and hide Chrome menu items.
347 - (void)addMenuItems:(const Extension*)app;
348 // If the window belongs to the currently focused app, remove the menu items and
349 // unhide Chrome menu items.
350 - (void)removeMenuItems;
351 // If the currently focused window belongs to a platform app, quit the app.
352 - (void)quitCurrentPlatformApp;
353 // If the currently focused window belongs to a platform app, hide the app.
354 - (void)hideCurrentPlatformApp;
355 // If the currently focused window belongs to a platform app, focus the app.
356 - (void)focusCurrentPlatformApp;
357 @end
359 @implementation AppShimMenuController
361 - (id)init {
362   if ((self = [super init])) {
363     [self buildAppMenuItems];
364     [self registerEventHandlers];
365   }
366   return self;
369 - (void)dealloc {
370   [[NSNotificationCenter defaultCenter] removeObserver:self];
371   [super dealloc];
374 - (void)buildAppMenuItems {
375   aboutDoppelganger_.reset([[DoppelgangerMenuItem alloc]
376       initWithController:self
377                  menuTag:IDC_CHROME_MENU
378                  itemTag:IDC_ABOUT
379               resourceId:IDS_ABOUT_MAC
380                   action:nil
381            keyEquivalent:@""]);
382   hideDoppelganger_.reset([[DoppelgangerMenuItem alloc]
383       initWithController:self
384                  menuTag:IDC_CHROME_MENU
385                  itemTag:IDC_HIDE_APP
386               resourceId:IDS_HIDE_APP_MAC
387                   action:@selector(hideCurrentPlatformApp)
388            keyEquivalent:@"h"]);
389   quitDoppelganger_.reset([[DoppelgangerMenuItem alloc]
390       initWithController:self
391                  menuTag:IDC_CHROME_MENU
392                  itemTag:IDC_EXIT
393               resourceId:IDS_EXIT_MAC
394                   action:@selector(quitCurrentPlatformApp)
395            keyEquivalent:@"q"]);
396   newDoppelganger_.reset([[DoppelgangerMenuItem alloc]
397       initWithController:self
398                  menuTag:IDC_FILE_MENU
399                  itemTag:IDC_NEW_WINDOW
400               resourceId:0
401                   action:nil
402            keyEquivalent:@"n"]);
403   // Since the "Close Window" menu item will have the same shortcut as "Close
404   // Tab" on the Chrome menu, we need to create a doppelganger.
405   closeWindowDoppelganger_.reset([[DoppelgangerMenuItem alloc]
406                 initWithMenuTag:IDC_FILE_MENU
407                   sourceItemTag:IDC_CLOSE_TAB
408                   targetItemTag:IDC_CLOSE_WINDOW
409                   keyEquivalent:@"w"]);
410   // For apps, the "Window" part of "New Window" is dropped to match the default
411   // menu set given to Cocoa Apps.
412   [[newDoppelganger_ menuItem] setTitle:l10n_util::GetNSString(IDS_NEW_MAC)];
413   openDoppelganger_.reset([[DoppelgangerMenuItem alloc]
414       initWithController:self
415                  menuTag:IDC_FILE_MENU
416                  itemTag:IDC_OPEN_FILE
417               resourceId:0
418                   action:nil
419            keyEquivalent:@"o"]);
420   allToFrontDoppelganger_.reset([[DoppelgangerMenuItem alloc]
421       initWithController:self
422                  menuTag:IDC_WINDOW_MENU
423                  itemTag:IDC_ALL_WINDOWS_FRONT
424               resourceId:0
425                   action:@selector(focusCurrentPlatformApp)
426            keyEquivalent:@""]);
428   // The app's menu.
429   appMenuItem_.reset([[NSMenuItem alloc] initWithTitle:@""
430                                                 action:nil
431                                          keyEquivalent:@""]);
432   base::scoped_nsobject<NSMenu> appMenu([[NSMenu alloc] initWithTitle:@""]);
433   [appMenuItem_ setSubmenu:appMenu];
434   [appMenu setAutoenablesItems:NO];
436   [appMenu addItem:[aboutDoppelganger_ menuItem]];
437   [[aboutDoppelganger_ menuItem] setEnabled:NO];  // Not implemented yet.
438   [appMenu addItem:[NSMenuItem separatorItem]];
439   [appMenu addItem:[hideDoppelganger_ menuItem]];
440   [appMenu addItem:[NSMenuItem separatorItem]];
441   [appMenu addItem:[quitDoppelganger_ menuItem]];
443   // File menu.
444   fileMenuItem_.reset([NewTopLevelItemFrom(IDC_FILE_MENU) retain]);
445   [[fileMenuItem_ submenu] addItem:[newDoppelganger_ menuItem]];
446   [[fileMenuItem_ submenu] addItem:[openDoppelganger_ menuItem]];
447   [[fileMenuItem_ submenu] addItem:[NSMenuItem separatorItem]];
448   [[fileMenuItem_ submenu] addItem:[closeWindowDoppelganger_ menuItem]];
450   // Edit menu. We copy the menu because the last two items, "Start Dictation"
451   // and "Special Characters" are added by OSX, so we can't copy them
452   // explicitly.
453   editMenuItem_.reset([[[NSApp mainMenu] itemWithTag:IDC_EDIT_MENU] copy]);
455   // View menu. Remove "Always Show Bookmark Bar" and separator.
456   viewMenuItem_.reset([[[NSApp mainMenu] itemWithTag:IDC_VIEW_MENU] copy]);
457   RemoveMenuItemWithTag(viewMenuItem_, IDC_SHOW_BOOKMARK_BAR, YES);
459   // History menu.
460   historyMenuItem_.reset([NewTopLevelItemFrom(IDC_HISTORY_MENU) retain]);
461   AddDuplicateItem(historyMenuItem_, IDC_HISTORY_MENU, IDC_BACK);
462   AddDuplicateItem(historyMenuItem_, IDC_HISTORY_MENU, IDC_FORWARD);
464   // Window menu.
465   windowMenuItem_.reset([NewTopLevelItemFrom(IDC_WINDOW_MENU) retain]);
466   AddDuplicateItem(windowMenuItem_, IDC_WINDOW_MENU, IDC_MINIMIZE_WINDOW);
467   AddDuplicateItem(windowMenuItem_, IDC_WINDOW_MENU, IDC_MAXIMIZE_WINDOW);
468   [[windowMenuItem_ submenu] addItem:[NSMenuItem separatorItem]];
469   [[windowMenuItem_ submenu] addItem:[allToFrontDoppelganger_ menuItem]];
472 - (void)registerEventHandlers {
473   [[NSNotificationCenter defaultCenter]
474       addObserver:self
475          selector:@selector(windowMainStatusChanged:)
476              name:NSWindowDidBecomeMainNotification
477            object:nil];
479   [[NSNotificationCenter defaultCenter]
480       addObserver:self
481          selector:@selector(windowMainStatusChanged:)
482              name:NSWindowDidResignMainNotification
483            object:nil];
486 - (void)windowMainStatusChanged:(NSNotification*)notification {
487   // A Yosemite AppKit bug causes this notification to be sent during the
488   // -dealloc for a specific NSWindow. Any autoreleases sent to that window
489   // must be drained before the window finishes -dealloc. In this method, an
490   // autorelease is sent by the invocation of [NSApp windows].
491   // http://crbug.com/406944.
492   base::mac::ScopedNSAutoreleasePool pool;
494   NSString* name = [notification name];
495   if ([name isEqualToString:NSWindowDidBecomeMainNotification]) {
496     id window = [notification object];
497     bool is_browser;
498     const Extension* extension = GetExtensionForNSWindow(window, &is_browser);
499     // Ignore is_browser: if a window becomes main that does not belong to an
500     // extension or browser, treat it the same as switching to a browser.
501     if (extension)
502       [self appBecameMain:extension];
503     else
504       [self chromeBecameMain];
505   } else if ([name isEqualToString:NSWindowDidResignMainNotification]) {
506     // When a window resigns main status, reset back to the Chrome menu.
507     // In the past we've tried:
508     // - Only doing this when a window closes, but this would not be triggered
509     // when an app becomes hidden (Cmd+h), and there are no Chrome windows to
510     // become main.
511     // - Scanning [NSApp windows] to predict whether we could
512     // expect another Chrome window to become main, and skip the reset. However,
513     // panels need to do strange things during window close to ensure panels
514     // never get chosen for key status over a browser window (which is likely
515     // because they are given an elevated [NSWindow level]). Trying to handle
516     // this case is not robust.
517     //
518     // Unfortunately, resetting the menu to Chrome
519     // unconditionally means that if another packaged app window becomes key,
520     // the menu will flicker. TODO(tapted): Investigate restoring the logic when
521     // the panel code is removed.
522     [self chromeBecameMain];
523   } else {
524     NOTREACHED();
525   }
528 - (void)appBecameMain:(const Extension*)app {
529   if (appId_ == app->id())
530     return;
532   if (!appId_.empty())
533     [self removeMenuItems];
535   appId_ = app->id();
536   [self addMenuItems:app];
537   if (IsAppWindowCyclingEnabled()) {
538     base::MessageLoop::current()->PostTask(
539         FROM_HERE, base::Bind(&SetAppCyclesWindows, appId_,
540                               ++g_window_cycle_sequence_number));
541   }
544 - (void)chromeBecameMain {
545   if (appId_.empty())
546     return;
548   appId_.clear();
549   [self removeMenuItems];
550   if (IsAppWindowCyclingEnabled()) {
551     base::MessageLoop::current()->PostTask(
552         FROM_HERE,
553         base::Bind(&SetChromeCyclesWindows, ++g_window_cycle_sequence_number));
554   }
557 - (void)addMenuItems:(const Extension*)app {
558   DCHECK_EQ(appId_, app->id());
559   NSString* title = base::SysUTF8ToNSString(app->name());
561   // Hide Chrome menu items.
562   NSMenu* mainMenu = [NSApp mainMenu];
563   for (NSMenuItem* item in [mainMenu itemArray])
564     [item setHidden:YES];
566   [aboutDoppelganger_ enableForApp:app];
567   [hideDoppelganger_ enableForApp:app];
568   [quitDoppelganger_ enableForApp:app];
569   [newDoppelganger_ enableForApp:app];
570   [openDoppelganger_ enableForApp:app];
571   [closeWindowDoppelganger_ enableForApp:app];
573   [appMenuItem_ setTitle:base::SysUTF8ToNSString(appId_)];
574   [[appMenuItem_ submenu] setTitle:title];
576   [mainMenu addItem:appMenuItem_];
577   [mainMenu addItem:fileMenuItem_];
579   SetItemWithTagVisible(editMenuItem_,
580                         IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE,
581                         app->is_hosted_app(), true);
582   SetItemWithTagVisible(editMenuItem_, IDC_FIND_MENU, app->is_hosted_app(),
583                         false);
584   [mainMenu addItem:editMenuItem_];
586   if (app->is_hosted_app()) {
587     [mainMenu addItem:viewMenuItem_];
588     [mainMenu addItem:historyMenuItem_];
589   }
590   [mainMenu addItem:windowMenuItem_];
593 - (void)removeMenuItems {
594   NSMenu* mainMenu = [NSApp mainMenu];
595   [mainMenu removeItem:appMenuItem_];
596   [mainMenu removeItem:fileMenuItem_];
597   if ([mainMenu indexOfItem:viewMenuItem_] >= 0)
598     [mainMenu removeItem:viewMenuItem_];
599   if ([mainMenu indexOfItem:historyMenuItem_] >= 0)
600     [mainMenu removeItem:historyMenuItem_];
601   [mainMenu removeItem:editMenuItem_];
602   [mainMenu removeItem:windowMenuItem_];
604   // Restore the Chrome main menu bar.
605   for (NSMenuItem* item in [mainMenu itemArray])
606     [item setHidden:NO];
608   [aboutDoppelganger_ disable];
609   [hideDoppelganger_ disable];
610   [quitDoppelganger_ disable];
611   [newDoppelganger_ disable];
612   [openDoppelganger_ disable];
613   [closeWindowDoppelganger_ disable];
616 - (void)quitCurrentPlatformApp {
617   extensions::AppWindow* appWindow =
618       AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(
619           [NSApp keyWindow]);
620   if (appWindow) {
621     apps::ExtensionAppShimHandler::QuitAppForWindow(appWindow);
622   } else {
623     Browser* browser = chrome::FindBrowserWithWindow([NSApp keyWindow]);
624     const Extension* extension =
625         apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
626     if (extension)
627       apps::ExtensionAppShimHandler::QuitHostedAppForWindow(browser->profile(),
628                                                             extension->id());
629   }
632 - (void)hideCurrentPlatformApp {
633   extensions::AppWindow* appWindow =
634       AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(
635           [NSApp keyWindow]);
636   if (appWindow) {
637     apps::ExtensionAppShimHandler::HideAppForWindow(appWindow);
638   } else {
639     Browser* browser = chrome::FindBrowserWithWindow([NSApp keyWindow]);
640     const Extension* extension =
641         apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
642     if (extension)
643       apps::ExtensionAppShimHandler::HideHostedApp(browser->profile(),
644                                                    extension->id());
645   }
648 - (void)focusCurrentPlatformApp {
649   extensions::AppWindow* appWindow =
650       AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(
651           [NSApp keyWindow]);
652   if (appWindow)
653     apps::ExtensionAppShimHandler::FocusAppForWindow(appWindow);
656 @end