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;
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))
39 if (command_line->HasSwitch(switches::kEnableAppWindowCycling))
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]
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,
72 base::scoped_nsobject<NSMenuItem> item(
73 [GetItemByTag(menu_tag, item_tag) copy]);
75 [[top_level_item submenu] addItem:item];
78 // Finds an item with |item_tag| and removes it from the submenu of
80 void RemoveMenuItemWithTag(NSMenuItem* top_level_item,
82 bool remove_following_separator) {
83 NSMenu* submenu = [top_level_item submenu];
84 NSInteger index = [submenu indexOfItemWithTag:item_tag];
88 [submenu removeItemAtIndex:index];
90 if (!remove_following_separator || index == [submenu numberOfItems])
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,
106 bool has_alternate) {
107 NSMenu* submenu = [top_level_item submenu];
108 NSMenuItem* menu_item = [submenu itemWithTag:item_tag];
111 if (visible != [menu_item isHidden])
114 if (!has_alternate) {
115 [menu_item setHidden:!visible];
119 NSInteger next_index = [submenu indexOfItem:menu_item] + 1;
120 DCHECK_LT(next_index, [submenu numberOfItems]);
122 NSMenuItem* alternate_item = [submenu itemAtIndex:next_index];
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]);
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);
146 extension = app_window->GetExtension();
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);
154 *is_browser = extension == nullptr && browser != nullptr;
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;
166 behavior &= ~NSWindowCollectionBehaviorIgnoresCycle;
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)
175 [window setCollectionBehavior:behavior];
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)
184 bool any_change = false;
185 for (NSWindow* window : [NSApp windows]) {
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);
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').
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)
219 bool any_change = false;
220 for (NSWindow* window : [NSApp windows]) {
222 const Extension* extension = GetExtensionForNSWindow(window, &is_browser);
223 if (extension || is_browser)
224 any_change |= SetWindowParticipatesInCycle(window, is_browser);
227 [[NSApp keyWindow] makeKeyAndOrderFront:nil];
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 {
242 base::scoped_nsobject<NSMenuItem> menuItem_;
243 base::scoped_nsobject<NSMenuItem> sourceItem_;
244 base::scoped_nsobject<NSString> sourceKeyEquivalent_;
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
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.
271 @implementation DoppelgangerMenuItem
273 - (NSMenuItem*)menuItem {
277 - (id)initWithController:(AppShimMenuController*)controller
278 menuTag:(NSInteger)menuTag
279 itemTag:(NSInteger)itemTag
280 resourceId:(int)resourceId
282 keyEquivalent:(NSString*)keyEquivalent {
283 if ((self = [super init])) {
284 sourceItem_.reset([GetItemByTag(menuTag, itemTag) retain]);
286 sourceKeyEquivalent_.reset([[sourceItem_ keyEquivalent] copy]);
287 menuItem_.reset([[NSMenuItem alloc]
288 initWithTitle:[sourceItem_ title]
290 keyEquivalent:keyEquivalent]);
291 [menuItem_ setTarget:controller];
292 [menuItem_ setTag:itemTag];
293 resourceId_ = resourceId;
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]);
307 sourceKeyEquivalent_.reset([[sourceItem_ keyEquivalent] copy]);
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:@""];
322 [menuItem_ setTitle:l10n_util::GetNSStringF(resourceId_,
323 base::UTF8ToUTF16(app->name()))];
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_];
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;
359 @implementation AppShimMenuController
362 if ((self = [super init])) {
363 [self buildAppMenuItems];
364 [self registerEventHandlers];
370 [[NSNotificationCenter defaultCenter] removeObserver:self];
374 - (void)buildAppMenuItems {
375 aboutDoppelganger_.reset([[DoppelgangerMenuItem alloc]
376 initWithController:self
377 menuTag:IDC_CHROME_MENU
379 resourceId:IDS_ABOUT_MAC
382 hideDoppelganger_.reset([[DoppelgangerMenuItem alloc]
383 initWithController:self
384 menuTag:IDC_CHROME_MENU
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
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
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
419 keyEquivalent:@"o"]);
420 allToFrontDoppelganger_.reset([[DoppelgangerMenuItem alloc]
421 initWithController:self
422 menuTag:IDC_WINDOW_MENU
423 itemTag:IDC_ALL_WINDOWS_FRONT
425 action:@selector(focusCurrentPlatformApp)
429 appMenuItem_.reset([[NSMenuItem alloc] initWithTitle:@""
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]];
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
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);
460 historyMenuItem_.reset([NewTopLevelItemFrom(IDC_HISTORY_MENU) retain]);
461 AddDuplicateItem(historyMenuItem_, IDC_HISTORY_MENU, IDC_BACK);
462 AddDuplicateItem(historyMenuItem_, IDC_HISTORY_MENU, IDC_FORWARD);
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]
475 selector:@selector(windowMainStatusChanged:)
476 name:NSWindowDidBecomeMainNotification
479 [[NSNotificationCenter defaultCenter]
481 selector:@selector(windowMainStatusChanged:)
482 name:NSWindowDidResignMainNotification
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];
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.
502 [self appBecameMain:extension];
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
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.
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];
528 - (void)appBecameMain:(const Extension*)app {
529 if (appId_ == app->id())
533 [self removeMenuItems];
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));
544 - (void)chromeBecameMain {
549 [self removeMenuItems];
550 if (IsAppWindowCyclingEnabled()) {
551 base::MessageLoop::current()->PostTask(
553 base::Bind(&SetChromeCyclesWindows, ++g_window_cycle_sequence_number));
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(),
584 [mainMenu addItem:editMenuItem_];
586 if (app->is_hosted_app()) {
587 [mainMenu addItem:viewMenuItem_];
588 [mainMenu addItem:historyMenuItem_];
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])
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(
621 apps::ExtensionAppShimHandler::QuitAppForWindow(appWindow);
623 Browser* browser = chrome::FindBrowserWithWindow([NSApp keyWindow]);
624 const Extension* extension =
625 apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
627 apps::ExtensionAppShimHandler::QuitHostedAppForWindow(browser->profile(),
632 - (void)hideCurrentPlatformApp {
633 extensions::AppWindow* appWindow =
634 AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(
637 apps::ExtensionAppShimHandler::HideAppForWindow(appWindow);
639 Browser* browser = chrome::FindBrowserWithWindow([NSApp keyWindow]);
640 const Extension* extension =
641 apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
643 apps::ExtensionAppShimHandler::HideHostedApp(browser->profile(),
648 - (void)focusCurrentPlatformApp {
649 extensions::AppWindow* appWindow =
650 AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(
653 apps::ExtensionAppShimHandler::FocusAppForWindow(appWindow);