blob: efb348592403b6d71ec5bb26df9e8a22228c3c9a [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.h"
#include "base/command_line.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/apps/app_shim/extension_app_shim_handler_mac.h"
#include "chrome/browser/apps/app_window_registry_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#import "chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/grit/generated_resources.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/common/extension.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_mac.h"
using extensions::Extension;
namespace {
// When an app window loses main status, AppKit may make another app window main
// instead. Rather than trying to predict what AppKit will do (which is hard),
// just protect against changes in the event queue that will clobber each other.
int g_window_cycle_sequence_number = 0;
// Whether Custom Cmd+` window cycling is enabled for apps.
bool IsAppWindowCyclingEnabled() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kDisableAppWindowCycling))
return false;
if (command_line->HasSwitch(switches::kEnableAppWindowCycling))
return true;
return false; // Current default.
}
// Gets an item from the main menu given the tag of the top level item
// |menu_tag| and the tag of the item |item_tag|.
NSMenuItem* GetItemByTag(NSInteger menu_tag, NSInteger item_tag) {
return [[[[NSApp mainMenu] itemWithTag:menu_tag] submenu]
itemWithTag:item_tag];
}
// Finds a top level menu item using |menu_tag| and creates a new NSMenuItem
// with the same title.
NSMenuItem* NewTopLevelItemFrom(NSInteger menu_tag) {
NSMenuItem* original = [[NSApp mainMenu] itemWithTag:menu_tag];
base::scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc]
initWithTitle:[original title]
action:nil
keyEquivalent:@""]);
DCHECK([original hasSubmenu]);
base::scoped_nsobject<NSMenu> sub_menu([[NSMenu alloc]
initWithTitle:[[original submenu] title]]);
[item setSubmenu:sub_menu];
return item.autorelease();
}
// Finds an item using |menu_tag| and |item_tag| and adds a duplicate of it to
// the submenu of |top_level_item|.
void AddDuplicateItem(NSMenuItem* top_level_item,
NSInteger menu_tag,
NSInteger item_tag) {
base::scoped_nsobject<NSMenuItem> item(
[GetItemByTag(menu_tag, item_tag) copy]);
DCHECK(item);
[[top_level_item submenu] addItem:item];
}
// Finds an item with |item_tag| and removes it from the submenu of
// |top_level_item|.
void RemoveMenuItemWithTag(NSMenuItem* top_level_item,
NSInteger item_tag,
bool remove_following_separator) {
NSMenu* submenu = [top_level_item submenu];
NSInteger index = [submenu indexOfItemWithTag:item_tag];
if (index < 0)
return;
[submenu removeItemAtIndex:index];
if (!remove_following_separator || index == [submenu numberOfItems])
return;
NSMenuItem* nextItem = [submenu itemAtIndex:index];
if ([nextItem isSeparatorItem])
[submenu removeItem:nextItem];
}
// Sets the menu item with |item_tag| in |top_level_item| visible.
// If |has_alternate| is true, the item immediately following |item_tag| is
// assumed to be its (only) alternate. Since AppKit is unable to hide items
// with alternates, |has_alternate| will cause -[NSMenuItem alternate] to be
// removed when hiding and restored when showing.
void SetItemWithTagVisible(NSMenuItem* top_level_item,
NSInteger item_tag,
bool visible,
bool has_alternate) {
NSMenu* submenu = [top_level_item submenu];
NSMenuItem* menu_item = [submenu itemWithTag:item_tag];
DCHECK(menu_item);
if (visible != [menu_item isHidden])
return;
if (!has_alternate) {
[menu_item setHidden:!visible];
return;
}
NSInteger next_index = [submenu indexOfItem:menu_item] + 1;
DCHECK_LT(next_index, [submenu numberOfItems]);
NSMenuItem* alternate_item = [submenu itemAtIndex:next_index];
if (!visible) {
// When hiding (only), we can verify the assumption that the item following
// |item_tag| is actually an alternate.
DCHECK([alternate_item isAlternate]);
}
// The alternate item visibility should always be in sync.
DCHECK_EQ([alternate_item isHidden], [menu_item isHidden]);
[alternate_item setAlternate:visible];
[alternate_item setHidden:!visible];
[menu_item setHidden:!visible];
}
// Return the Extension (if any) associated with the given window. If it is not
// a platform app nor hosted app, but it is a browser, |is_browser| will be set
// to true (otherwise false).
const Extension* GetExtensionForNSWindow(NSWindow* window, bool* is_browser) {
const Extension* extension = nullptr;
Browser* browser = nullptr;
extensions::AppWindow* app_window =
AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(window);
if (app_window) {
extension = app_window->GetExtension();
} else {
// If there is no corresponding AppWindow, this could be a hosted app, so
// check for a browser.
browser = chrome::FindBrowserWithWindow(window);
extension = apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
}
*is_browser = extension == nullptr && browser != nullptr;
return extension;
}
// Sets or clears NSWindowCollectionBehaviorIgnoresCycle for |window|. Does not
// change NSWindowCollectionBehaviorParticipatesInCycle. That exists, e.g, for
// an NSPanel to override its default behavior, but this should only ever be
// called for Browser windows and App windows (which are not panels).
bool SetWindowParticipatesInCycle(NSWindow* window, bool participates) {
const NSWindowCollectionBehavior past_behavior = [window collectionBehavior];
NSWindowCollectionBehavior behavior = past_behavior;
if (participates)
behavior &= ~NSWindowCollectionBehaviorIgnoresCycle;
else
behavior |= NSWindowCollectionBehaviorIgnoresCycle;
// Often, there is no change. AppKit has no early exit since the value is
// derived partially from styleMask and other things, so do our own.
if (behavior == past_behavior)
return false;
[window setCollectionBehavior:behavior];
return true;
}
// Sets the window cycle list to |app_id|'s windows only.
void SetAppCyclesWindows(const std::string& app_id, int sequence_number) {
if (g_window_cycle_sequence_number != sequence_number)
return;
bool any_change = false;
for (NSWindow* window : [NSApp windows]) {
bool is_browser;
const Extension* extension = GetExtensionForNSWindow(window, &is_browser);
if (extension && extension->id() == app_id)
any_change |= SetWindowParticipatesInCycle(window, true);
else if (extension || is_browser)
any_change |= SetWindowParticipatesInCycle(window, false);
}
// Without the following, -[NSApplication _getLockedWindowListForCycle] will
// happily return windows that were just set to ignore window cycling. Doing
// this seems to trick AppKit into updating the window cycle list. But it is a
// bit scary, so avoid it when there is no change. These attempts were based
// on the observation that clicking a window twice to switch focus would
// always work. Also tried (without luck):
// - [NSApp setWindowsNeedUpdate:YES],
// - Creating a deferred NSWindow and immediately releasing it,
// - Calling private methods like [NSApp _unlockWindowListForCycle],
// - [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined...
// (an attempt to tickle AppKit into an update of some kind),
// - Calling synchronously (i.e. not via PostTask) <- this was actually the
// initial attempt. Then, switching to PostTask didn't help with this
// quirk, but was useful for the sequence number stuff, and
// - Re-ordering collection behavior changes to ensure one window was always
// participating (i.e. all 'adds' before any 'removes').
if (any_change)
[[NSApp keyWindow] makeKeyAndOrderFront:nil];
}
// Sets the window cycle list to Chrome browser windows only.
void SetChromeCyclesWindows(int sequence_number) {
if (g_window_cycle_sequence_number != sequence_number)
return;
bool any_change = false;
for (NSWindow* window : [NSApp windows]) {
bool is_browser;
const Extension* extension = GetExtensionForNSWindow(window, &is_browser);
if (extension || is_browser)
any_change |= SetWindowParticipatesInCycle(window, is_browser);
}
if (any_change)
[[NSApp keyWindow] makeKeyAndOrderFront:nil];
}
} // namespace
// Used by AppShimMenuController to manage menu items that are a copy of a
// Chrome menu item but with a different action. This manages unsetting and
// restoring the original item's key equivalent, so that we can use the same
// key equivalent in the copied item with a different action. If |resourceId_|
// is non-zero, this will also update the title to include the app name.
// If the copy (menuItem) has no key equivalent, and the title does not have the
// app name, then enableForApp and disable do not need to be called. I.e. the
// doppelganger just copies the item and sets a new action.
@interface DoppelgangerMenuItem : NSObject {
@private
base::scoped_nsobject<NSMenuItem> menuItem_;
base::scoped_nsobject<NSMenuItem> sourceItem_;
base::scoped_nsobject<NSString> sourceKeyEquivalent_;
int resourceId_;
}
@property(readonly, nonatomic) NSMenuItem* menuItem;
// Get the source item using the tags and create the menu item.
- (id)initWithController:(AppShimMenuController*)controller
menuTag:(NSInteger)menuTag
itemTag:(NSInteger)itemTag
resourceId:(int)resourceId
action:(SEL)action
keyEquivalent:(NSString*)keyEquivalent;
// Retain the source item given |menuTag| and |sourceItemTag|. Copy
// the menu item given |menuTag| and |targetItemTag|.
// This is useful when we want a doppelganger with a different source item.
// For example, if there are conflicting key equivalents.
- (id)initWithMenuTag:(NSInteger)menuTag
sourceItemTag:(NSInteger)sourceItemTag
targetItemTag:(NSInteger)targetItemTag
keyEquivalent:(NSString*)keyEquivalent;
// Set the title using |resourceId_| and unset the source item's key equivalent.
- (void)enableForApp:(const Extension*)app;
// Restore the source item's key equivalent.
- (void)disable;
@end
@implementation DoppelgangerMenuItem
- (NSMenuItem*)menuItem {
return menuItem_;
}
- (id)initWithController:(AppShimMenuController*)controller
menuTag:(NSInteger)menuTag
itemTag:(NSInteger)itemTag
resourceId:(int)resourceId
action:(SEL)action
keyEquivalent:(NSString*)keyEquivalent {
if ((self = [super init])) {
sourceItem_.reset([GetItemByTag(menuTag, itemTag) retain]);
DCHECK(sourceItem_);
sourceKeyEquivalent_.reset([[sourceItem_ keyEquivalent] copy]);
menuItem_.reset([[NSMenuItem alloc]
initWithTitle:[sourceItem_ title]
action:action
keyEquivalent:keyEquivalent]);
[menuItem_ setTarget:controller];
[menuItem_ setTag:itemTag];
resourceId_ = resourceId;
}
return self;
}
- (id)initWithMenuTag:(NSInteger)menuTag
sourceItemTag:(NSInteger)sourceItemTag
targetItemTag:(NSInteger)targetItemTag
keyEquivalent:(NSString*)keyEquivalent {
if ((self = [super init])) {
menuItem_.reset([GetItemByTag(menuTag, targetItemTag) copy]);
sourceItem_.reset([GetItemByTag(menuTag, sourceItemTag) retain]);
DCHECK(menuItem_);
DCHECK(sourceItem_);
sourceKeyEquivalent_.reset([[sourceItem_ keyEquivalent] copy]);
}
return self;
}
- (void)enableForApp:(const Extension*)app {
// It seems that two menu items that have the same key equivalent must also
// have the same action for the keyboard shortcut to work. (This refers to the
// original keyboard shortcut, regardless of any overrides set in OSX).
// In order to let the app menu items have a different action, we remove the
// key equivalent of the original items and restore them later.
[sourceItem_ setKeyEquivalent:@""];
if (!resourceId_)
return;
[menuItem_ setTitle:l10n_util::GetNSStringF(resourceId_,
base::UTF8ToUTF16(app->name()))];
}
- (void)disable {
// Restore the keyboard shortcut to Chrome. This just needs to be set back to
// the original keyboard shortcut, regardless of any overrides in OSX. The
// overrides still work as they are based on the title of the menu item.
[sourceItem_ setKeyEquivalent:sourceKeyEquivalent_];
}
@end
@interface AppShimMenuController ()
// Construct the NSMenuItems for apps.
- (void)buildAppMenuItems;
// Register for NSWindow notifications.
- (void)registerEventHandlers;
// If the window is an app window, add or remove menu items.
- (void)windowMainStatusChanged:(NSNotification*)notification;
// Called when |app| becomes the main window in the Chrome process.
- (void)appBecameMain:(const Extension*)app;
// Called when there is no main window, or if the main window is not an app.
- (void)chromeBecameMain;
// Add menu items for an app and hide Chrome menu items.
- (void)addMenuItems:(const Extension*)app;
// If the window belongs to the currently focused app, remove the menu items and
// unhide Chrome menu items.
- (void)removeMenuItems;
// If the currently focused window belongs to a platform app, quit the app.
- (void)quitCurrentPlatformApp;
// If the currently focused window belongs to a platform app, hide the app.
- (void)hideCurrentPlatformApp;
// If the currently focused window belongs to a platform app, focus the app.
- (void)focusCurrentPlatformApp;
@end
@implementation AppShimMenuController
- (id)init {
if ((self = [super init])) {
[self buildAppMenuItems];
[self registerEventHandlers];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)buildAppMenuItems {
aboutDoppelganger_.reset([[DoppelgangerMenuItem alloc]
initWithController:self
menuTag:IDC_CHROME_MENU
itemTag:IDC_ABOUT
resourceId:IDS_ABOUT_MAC
action:nil
keyEquivalent:@""]);
hideDoppelganger_.reset([[DoppelgangerMenuItem alloc]
initWithController:self
menuTag:IDC_CHROME_MENU
itemTag:IDC_HIDE_APP
resourceId:IDS_HIDE_APP_MAC
action:@selector(hideCurrentPlatformApp)
keyEquivalent:@"h"]);
quitDoppelganger_.reset([[DoppelgangerMenuItem alloc]
initWithController:self
menuTag:IDC_CHROME_MENU
itemTag:IDC_EXIT
resourceId:IDS_EXIT_MAC
action:@selector(quitCurrentPlatformApp)
keyEquivalent:@"q"]);
newDoppelganger_.reset([[DoppelgangerMenuItem alloc]
initWithController:self
menuTag:IDC_FILE_MENU
itemTag:IDC_NEW_WINDOW
resourceId:0
action:nil
keyEquivalent:@"n"]);
// Since the "Close Window" menu item will have the same shortcut as "Close
// Tab" on the Chrome menu, we need to create a doppelganger.
closeWindowDoppelganger_.reset([[DoppelgangerMenuItem alloc]
initWithMenuTag:IDC_FILE_MENU
sourceItemTag:IDC_CLOSE_TAB
targetItemTag:IDC_CLOSE_WINDOW
keyEquivalent:@"w"]);
// For apps, the "Window" part of "New Window" is dropped to match the default
// menu set given to Cocoa Apps.
[[newDoppelganger_ menuItem] setTitle:l10n_util::GetNSString(IDS_NEW_MAC)];
openDoppelganger_.reset([[DoppelgangerMenuItem alloc]
initWithController:self
menuTag:IDC_FILE_MENU
itemTag:IDC_OPEN_FILE
resourceId:0
action:nil
keyEquivalent:@"o"]);
allToFrontDoppelganger_.reset([[DoppelgangerMenuItem alloc]
initWithController:self
menuTag:IDC_WINDOW_MENU
itemTag:IDC_ALL_WINDOWS_FRONT
resourceId:0
action:@selector(focusCurrentPlatformApp)
keyEquivalent:@""]);
// The app's menu.
appMenuItem_.reset([[NSMenuItem alloc] initWithTitle:@""
action:nil
keyEquivalent:@""]);
base::scoped_nsobject<NSMenu> appMenu([[NSMenu alloc] initWithTitle:@""]);
[appMenuItem_ setSubmenu:appMenu];
[appMenu setAutoenablesItems:NO];
[appMenu addItem:[aboutDoppelganger_ menuItem]];
[[aboutDoppelganger_ menuItem] setEnabled:NO]; // Not implemented yet.
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItem:[hideDoppelganger_ menuItem]];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItem:[quitDoppelganger_ menuItem]];
// File menu.
fileMenuItem_.reset([NewTopLevelItemFrom(IDC_FILE_MENU) retain]);
[[fileMenuItem_ submenu] addItem:[newDoppelganger_ menuItem]];
[[fileMenuItem_ submenu] addItem:[openDoppelganger_ menuItem]];
[[fileMenuItem_ submenu] addItem:[NSMenuItem separatorItem]];
[[fileMenuItem_ submenu] addItem:[closeWindowDoppelganger_ menuItem]];
// Edit menu. We copy the menu because the last two items, "Start Dictation"
// and "Special Characters" are added by OSX, so we can't copy them
// explicitly.
editMenuItem_.reset([[[NSApp mainMenu] itemWithTag:IDC_EDIT_MENU] copy]);
// View menu. Remove "Always Show Bookmark Bar" and separator.
viewMenuItem_.reset([[[NSApp mainMenu] itemWithTag:IDC_VIEW_MENU] copy]);
RemoveMenuItemWithTag(viewMenuItem_, IDC_SHOW_BOOKMARK_BAR, YES);
// History menu.
historyMenuItem_.reset([NewTopLevelItemFrom(IDC_HISTORY_MENU) retain]);
AddDuplicateItem(historyMenuItem_, IDC_HISTORY_MENU, IDC_BACK);
AddDuplicateItem(historyMenuItem_, IDC_HISTORY_MENU, IDC_FORWARD);
// Window menu.
windowMenuItem_.reset([NewTopLevelItemFrom(IDC_WINDOW_MENU) retain]);
AddDuplicateItem(windowMenuItem_, IDC_WINDOW_MENU, IDC_MINIMIZE_WINDOW);
AddDuplicateItem(windowMenuItem_, IDC_WINDOW_MENU, IDC_MAXIMIZE_WINDOW);
[[windowMenuItem_ submenu] addItem:[NSMenuItem separatorItem]];
[[windowMenuItem_ submenu] addItem:[allToFrontDoppelganger_ menuItem]];
}
- (void)registerEventHandlers {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(windowMainStatusChanged:)
name:NSWindowDidBecomeMainNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(windowMainStatusChanged:)
name:NSWindowDidResignMainNotification
object:nil];
}
- (void)windowMainStatusChanged:(NSNotification*)notification {
// A Yosemite AppKit bug causes this notification to be sent during the
// -dealloc for a specific NSWindow. Any autoreleases sent to that window
// must be drained before the window finishes -dealloc. In this method, an
// autorelease is sent by the invocation of [NSApp windows].
// http://crbug.com/406944.
base::mac::ScopedNSAutoreleasePool pool;
NSString* name = [notification name];
if ([name isEqualToString:NSWindowDidBecomeMainNotification]) {
id window = [notification object];
bool is_browser;
const Extension* extension = GetExtensionForNSWindow(window, &is_browser);
// Ignore is_browser: if a window becomes main that does not belong to an
// extension or browser, treat it the same as switching to a browser.
if (extension)
[self appBecameMain:extension];
else
[self chromeBecameMain];
} else if ([name isEqualToString:NSWindowDidResignMainNotification]) {
// When a window resigns main status, reset back to the Chrome menu.
// In the past we've tried:
// - Only doing this when a window closes, but this would not be triggered
// when an app becomes hidden (Cmd+h), and there are no Chrome windows to
// become main.
// - Scanning [NSApp windows] to predict whether we could
// expect another Chrome window to become main, and skip the reset. However,
// panels need to do strange things during window close to ensure panels
// never get chosen for key status over a browser window (which is likely
// because they are given an elevated [NSWindow level]). Trying to handle
// this case is not robust.
//
// Unfortunately, resetting the menu to Chrome
// unconditionally means that if another packaged app window becomes key,
// the menu will flicker. TODO(tapted): Investigate restoring the logic when
// the panel code is removed.
[self chromeBecameMain];
} else {
NOTREACHED();
}
}
- (void)appBecameMain:(const Extension*)app {
if (appId_ == app->id())
return;
if (!appId_.empty())
[self removeMenuItems];
appId_ = app->id();
[self addMenuItems:app];
if (IsAppWindowCyclingEnabled()) {
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(&SetAppCyclesWindows, appId_,
++g_window_cycle_sequence_number));
}
}
- (void)chromeBecameMain {
if (appId_.empty())
return;
appId_.clear();
[self removeMenuItems];
if (IsAppWindowCyclingEnabled()) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&SetChromeCyclesWindows, ++g_window_cycle_sequence_number));
}
}
- (void)addMenuItems:(const Extension*)app {
DCHECK_EQ(appId_, app->id());
NSString* title = base::SysUTF8ToNSString(app->name());
// Hide Chrome menu items.
NSMenu* mainMenu = [NSApp mainMenu];
for (NSMenuItem* item in [mainMenu itemArray])
[item setHidden:YES];
[aboutDoppelganger_ enableForApp:app];
[hideDoppelganger_ enableForApp:app];
[quitDoppelganger_ enableForApp:app];
[newDoppelganger_ enableForApp:app];
[openDoppelganger_ enableForApp:app];
[closeWindowDoppelganger_ enableForApp:app];
[appMenuItem_ setTitle:base::SysUTF8ToNSString(appId_)];
[[appMenuItem_ submenu] setTitle:title];
[mainMenu addItem:appMenuItem_];
[mainMenu addItem:fileMenuItem_];
SetItemWithTagVisible(editMenuItem_,
IDC_CONTENT_CONTEXT_PASTE_AND_MATCH_STYLE,
app->is_hosted_app(), true);
SetItemWithTagVisible(editMenuItem_, IDC_FIND_MENU, app->is_hosted_app(),
false);
[mainMenu addItem:editMenuItem_];
if (app->is_hosted_app()) {
[mainMenu addItem:viewMenuItem_];
[mainMenu addItem:historyMenuItem_];
}
[mainMenu addItem:windowMenuItem_];
}
- (void)removeMenuItems {
NSMenu* mainMenu = [NSApp mainMenu];
[mainMenu removeItem:appMenuItem_];
[mainMenu removeItem:fileMenuItem_];
if ([mainMenu indexOfItem:viewMenuItem_] >= 0)
[mainMenu removeItem:viewMenuItem_];
if ([mainMenu indexOfItem:historyMenuItem_] >= 0)
[mainMenu removeItem:historyMenuItem_];
[mainMenu removeItem:editMenuItem_];
[mainMenu removeItem:windowMenuItem_];
// Restore the Chrome main menu bar.
for (NSMenuItem* item in [mainMenu itemArray])
[item setHidden:NO];
[aboutDoppelganger_ disable];
[hideDoppelganger_ disable];
[quitDoppelganger_ disable];
[newDoppelganger_ disable];
[openDoppelganger_ disable];
[closeWindowDoppelganger_ disable];
}
- (void)quitCurrentPlatformApp {
extensions::AppWindow* appWindow =
AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(
[NSApp keyWindow]);
if (appWindow) {
apps::ExtensionAppShimHandler::QuitAppForWindow(appWindow);
} else {
Browser* browser = chrome::FindBrowserWithWindow([NSApp keyWindow]);
const Extension* extension =
apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
if (extension)
apps::ExtensionAppShimHandler::QuitHostedAppForWindow(browser->profile(),
extension->id());
}
}
- (void)hideCurrentPlatformApp {
extensions::AppWindow* appWindow =
AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(
[NSApp keyWindow]);
if (appWindow) {
apps::ExtensionAppShimHandler::HideAppForWindow(appWindow);
} else {
Browser* browser = chrome::FindBrowserWithWindow([NSApp keyWindow]);
const Extension* extension =
apps::ExtensionAppShimHandler::MaybeGetAppForBrowser(browser);
if (extension)
apps::ExtensionAppShimHandler::HideHostedApp(browser->profile(),
extension->id());
}
}
- (void)focusCurrentPlatformApp {
extensions::AppWindow* appWindow =
AppWindowRegistryUtil::GetAppWindowForNativeWindowAnyProfile(
[NSApp keyWindow]);
if (appWindow)
apps::ExtensionAppShimHandler::FocusAppForWindow(appWindow);
}
@end