summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjackhou@chromium.org <jackhou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-24 08:44:30 +0000
committerjackhou@chromium.org <jackhou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-24 08:44:30 +0000
commit7ea6f8301c1b47e3669344b878dfa1edb8e7be66 (patch)
treee44ace6a3cc46df487b2ea50c97ad2f0db3614fe
parent46e5ecd93ab07120a2a0429191d8e02e2aabdf23 (diff)
downloadchromium_src-7ea6f8301c1b47e3669344b878dfa1edb8e7be66.zip
chromium_src-7ea6f8301c1b47e3669344b878dfa1edb8e7be66.tar.gz
chromium_src-7ea6f8301c1b47e3669344b878dfa1edb8e7be66.tar.bz2
Prevent Chrome from quitting when apps are open. (Mac)
This is added behind --apps-keep-chrome-alive. When Chrome is quit, a notification is shown to let the user know that Chrome will continue running. The browser session should be shut down like on Windows, but that is not yet implemented. Similarly, quitting all the apps should quit Chrome. These will be added in followup CLs. BUG=333429 Review URL: https://codereview.chromium.org/220373003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@265889 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--apps/app_window_registry.cc18
-rw-r--r--apps/app_window_registry.h3
-rw-r--r--chrome/app/chromium_strings.grd7
-rw-r--r--chrome/app/generated_resources.grd22
-rw-r--r--chrome/app/google_chrome_strings.grd7
-rw-r--r--chrome/browser/about_flags.cc9
-rw-r--r--chrome/browser/app_controller_mac.h5
-rw-r--r--chrome/browser/app_controller_mac.mm11
-rw-r--r--chrome/browser/prefs/browser_prefs.cc2
-rw-r--r--chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.cc150
-rw-r--r--chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h49
-rw-r--r--chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac_interactive_uitest.cc127
-rw-r--r--chrome/chrome_browser_ui.gypi2
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/chrome_switches.cc3
-rw-r--r--chrome/common/chrome_switches.h1
-rw-r--r--chrome/common/pref_names.cc5
-rw-r--r--chrome/common/pref_names.h1
18 files changed, 423 insertions, 0 deletions
diff --git a/apps/app_window_registry.cc b/apps/app_window_registry.cc
index 2be3c78..b362a5b 100644
--- a/apps/app_window_registry.cc
+++ b/apps/app_window_registry.cc
@@ -228,6 +228,24 @@ bool AppWindowRegistry::IsAppWindowRegisteredInAnyProfile(
return false;
}
+// static
+void AppWindowRegistry::CloseAllAppWindows() {
+ std::vector<content::BrowserContext*> contexts =
+ AppsClient::Get()->GetLoadedBrowserContexts();
+ for (std::vector<content::BrowserContext*>::const_iterator i =
+ contexts.begin();
+ i != contexts.end();
+ ++i) {
+ AppWindowRegistry* registry =
+ Factory::GetForBrowserContext(*i, false /* create */);
+ if (!registry)
+ continue;
+
+ while (!registry->app_windows().empty())
+ registry->app_windows().front()->GetBaseWindow()->Close();
+ }
+}
+
void AppWindowRegistry::OnDevToolsStateChanged(
content::DevToolsAgentHost* agent_host,
bool attached) {
diff --git a/apps/app_window_registry.h b/apps/app_window_registry.h
index 1bce91d..cc3ee35 100644
--- a/apps/app_window_registry.h
+++ b/apps/app_window_registry.h
@@ -100,6 +100,9 @@ class AppWindowRegistry : public KeyedService {
// AppWindow::WindowType, or 0 for any window type.
static bool IsAppWindowRegisteredInAnyProfile(int window_type_mask);
+ // Close all app windows in all profiles.
+ static void CloseAllAppWindows();
+
class Factory : public BrowserContextKeyedServiceFactory {
public:
static AppWindowRegistry* GetForBrowserContext(
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index 2f40467..7ca657d 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -657,6 +657,13 @@ Chromium is unable to recover your settings.
Yes, exit Chromium
</message>
+ <!-- Quit all apps confirmation dialog -->
+ <if expr="is_macosx">
+ <message name="IDS_QUIT_WITH_APPS_TITLE" desc="Title for a notification explaining that Chrome is running in the background.">
+ Chromium is running in the background.
+ </message>
+ </if>
+
<!-- Autolaunch infobar -->
<message name="IDS_AUTO_LAUNCH_INFOBAR_TEXT" desc="The text to show in the infobar when Chromium was automatically launched on startup">
Chromium is configured to automatically launch when you start your computer.
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 4969065..464091e 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -2174,6 +2174,22 @@ Even if you have downloaded files from this website before, the website might ha
Open as tab
</message>
+ <!-- Quit all apps confirmation dialog -->
+ <if expr="is_macosx">
+ <message name="IDS_QUIT_WITH_APPS_NOTIFICATION_DISPLAY_SOURCE" desc="Display source for the notification when quitting with apps open.">
+ Quit
+ </message>
+ <message name="IDS_QUIT_WITH_APPS_EXPLANATION" desc="Text displayed in a notification explaining that Chrome will continue running in the background as long as there are Chrome Apps open.">
+ Chrome will keep running while Chrome Apps are open.
+ </message>
+ <message name="IDS_QUIT_WITH_APPS_QUIT_LABEL" desc="Button text to quit all running Chrome apps.">
+ Quit all apps
+ </message>
+ <message name="IDS_QUIT_WITH_APPS_SUPPRESSION_LABEL" desc="Button text to prevent a notification from showing in future.">
+ Don't show this again
+ </message>
+ </if>
+
<!-- "Create application shortcuts" menu item -->
<if expr="not use_titlecase">
<message name="IDS_CREATE_SHORTCUTS" desc="Default installation menu label">
@@ -6530,6 +6546,12 @@ Keep your key file in a safe place. You will need it to create new versions of y
Enables directory support for sync filesystem.
</message>
<if expr="is_macosx">
+ <message name="IDS_FLAGS_APPS_KEEP_CHROME_ALIVE_NAME" desc="Title for the flag to prevent Chrome from quitting when Chrome Apps are open.">
+ Apps keep Chrome alive.
+ </message>
+ <message name="IDS_FLAGS_APPS_KEEP_CHROME_ALIVE_DESCRIPTION" desc="Description for the flag to prevent Chrome from quitting when Chrome Apps are open.">
+ Prevent Chrome from quitting when Chrome Apps are open.
+ </message>
<message name="IDS_FLAGS_DISABLE_APP_SHIMS_NAME" desc="Title for the flag to disable shortcuts for packaged apps from being added to Applications and from appearing in the dock.">
Disable packaged app shortcuts.
</message>
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index 75740aed..778d96b 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -581,6 +581,13 @@ Google Chrome is unable to recover your settings.
Yes, exit Chrome
</message>
+ <!-- Quit all apps confirmation dialog -->
+ <if expr="is_macosx">
+ <message name="IDS_QUIT_WITH_APPS_TITLE" desc="Title for a notification explaining that Chrome is running in the background.">
+ Google Chrome is running in the background.
+ </message>
+ </if>
+
<!-- Autolaunch infobar -->
<message name="IDS_AUTO_LAUNCH_INFOBAR_TEXT" desc="The text to show in the infobar when Chrome was automatically launched on startup">
Google Chrome is configured to automatically launch when you start your computer.
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 8559d1c..a963a01 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1778,6 +1778,15 @@ const Experiment kExperiments[] = {
SINGLE_VALUE_TYPE(chromeos::switches::kEnableFileManagerMTP)
},
#endif
+#if defined(OS_MACOSX)
+ {
+ "apps-keep-chrome-alive",
+ IDS_FLAGS_APPS_KEEP_CHROME_ALIVE_NAME,
+ IDS_FLAGS_APPS_KEEP_CHROME_ALIVE_DESCRIPTION,
+ kOsMac,
+ SINGLE_VALUE_TYPE(switches::kAppsKeepChromeAlive)
+ }
+#endif
};
const Experiment* experiments = kExperiments;
diff --git a/chrome/browser/app_controller_mac.h b/chrome/browser/app_controller_mac.h
index efb0783..f148c13 100644
--- a/chrome/browser/app_controller_mac.h
+++ b/chrome/browser/app_controller_mac.h
@@ -25,6 +25,8 @@ class GURL;
class HistoryMenuBridge;
class Profile;
@class ProfileMenuController;
+class QuitWithAppsController;
+
namespace ui {
class WorkAreaWatcherObserver;
}
@@ -87,6 +89,9 @@ class WorkAreaWatcherObserver;
scoped_ptr<PrefChangeRegistrar> profilePrefRegistrar_;
PrefChangeRegistrar localPrefRegistrar_;
+
+ // Displays a notification when quitting while apps are running.
+ scoped_refptr<QuitWithAppsController> quitWithAppsController_;
}
@property(readonly, nonatomic) BOOL startupComplete;
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index c8a6a16..2464c0d 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -52,6 +52,7 @@
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#import "chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.h"
+#include "chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
#import "chrome/browser/ui/cocoa/browser_window_cocoa.h"
#import "chrome/browser/ui/cocoa/browser_window_controller.h"
@@ -371,6 +372,12 @@ class AppControllerProfileObserver : public ProfileInfoCacheObserver {
![self shouldQuitWithInProgressDownloads])
return NO;
+ // Check for active apps, and prompt the user if they really want to quit
+ // (and also quit the apps).
+ if (!browser_shutdown::IsTryingToQuit() &&
+ quitWithAppsController_.get() && !quitWithAppsController_->ShouldQuit())
+ return NO;
+
// TODO(viettrungluu): Remove Apple Event handlers here? (It's safe to leave
// them in, but I'm not sure about UX; we'd also want to disable other things
// though.) http://crbug.com/40861
@@ -695,6 +702,10 @@ class AppControllerProfileObserver : public ProfileInfoCacheObserver {
// main menu item titles are not yet initialized in awakeFromNib.
[self initAppShimMenuController];
+ // If enabled, keep Chrome alive when apps are open instead of quitting all
+ // apps.
+ quitWithAppsController_ = new QuitWithAppsController();
+
// Build up the encoding menu, the order of the items differs based on the
// current locale (see http://crbug.com/7647 for details).
// We need a valid g_browser_process to get the profile which is why we can't
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index fd2bd10..4053fca 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -172,6 +172,7 @@
#endif
#if defined(OS_MACOSX)
+#include "chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h"
#include "chrome/browser/ui/cocoa/confirm_quit.h"
#include "chrome/browser/ui/cocoa/extensions/browser_actions_controller_prefs.h"
#endif
@@ -315,6 +316,7 @@ void RegisterLocalState(PrefRegistrySimple* registry) {
#if defined(OS_MACOSX)
confirm_quit::RegisterLocalState(registry);
+ QuitWithAppsController::RegisterPrefs(registry);
#endif
#if defined(OS_WIN)
diff --git a/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.cc b/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.cc
new file mode 100644
index 0000000..9bdb3cf
--- /dev/null
+++ b/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.cc
@@ -0,0 +1,150 @@
+// Copyright 2014 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.
+
+#include "chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h"
+
+#include "apps/app_window.h"
+#include "apps/app_window_registry.h"
+#include "apps/ui/native_app_window.h"
+#include "base/command_line.h"
+#include "base/i18n/number_formatting.h"
+#include "base/prefs/pref_registry_simple.h"
+#include "base/prefs/pref_service.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/notifications/notification.h"
+#include "chrome/browser/notifications/notification_ui_manager.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser_iterator.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "extensions/common/extension.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/google_chrome_strings.h"
+#include "grit/theme_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+#include "ui/base/resource/resource_bundle.h"
+
+const char kQuitWithAppsOriginUrl[] = "chrome://quit-with-apps";
+const int kQuitAllAppsButtonIndex = 0;
+const int kDontShowAgainButtonIndex = 1;
+
+const char QuitWithAppsController::kQuitWithAppsNotificationID[] =
+ "quit-with-apps";
+
+QuitWithAppsController::QuitWithAppsController()
+ : suppress_for_session_(false) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ // There is only ever one notification to replace, so use the same replace_id
+ // each time.
+ base::string16 replace_id = base::UTF8ToUTF16(id());
+
+ message_center::ButtonInfo quit_apps_button_info(
+ l10n_util::GetStringUTF16(IDS_QUIT_WITH_APPS_QUIT_LABEL));
+ message_center::ButtonInfo suppression_button_info(
+ l10n_util::GetStringUTF16(IDS_QUIT_WITH_APPS_SUPPRESSION_LABEL));
+ message_center::RichNotificationData rich_notification_data;
+ rich_notification_data.buttons.push_back(quit_apps_button_info);
+ rich_notification_data.buttons.push_back(suppression_button_info);
+
+ notification_.reset(new Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ GURL(kQuitWithAppsOriginUrl),
+ l10n_util::GetStringUTF16(IDS_QUIT_WITH_APPS_TITLE),
+ l10n_util::GetStringUTF16(IDS_QUIT_WITH_APPS_EXPLANATION),
+ ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ IDR_APP_DEFAULT_ICON),
+ blink::WebTextDirectionDefault,
+ message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT,
+ kQuitWithAppsNotificationID),
+ l10n_util::GetStringUTF16(IDS_QUIT_WITH_APPS_NOTIFICATION_DISPLAY_SOURCE),
+ replace_id,
+ rich_notification_data,
+ this));
+}
+
+QuitWithAppsController::~QuitWithAppsController() {}
+
+void QuitWithAppsController::Display() {}
+
+void QuitWithAppsController::Error() {}
+
+void QuitWithAppsController::Close(bool by_user) {
+ if (by_user)
+ suppress_for_session_ = true;
+}
+
+void QuitWithAppsController::Click() {
+ g_browser_process->notification_ui_manager()->CancelById(id());
+}
+
+void QuitWithAppsController::ButtonClick(int button_index) {
+ typedef apps::AppWindowRegistry::AppWindowList AppWindowList;
+
+ g_browser_process->notification_ui_manager()->CancelById(id());
+ if (button_index == kQuitAllAppsButtonIndex) {
+ apps::AppWindowRegistry::CloseAllAppWindows();
+ } else if (button_index == kDontShowAgainButtonIndex) {
+ g_browser_process->local_state()->SetBoolean(
+ prefs::kNotifyWhenAppsKeepChromeAlive, false);
+ }
+}
+
+content::WebContents* QuitWithAppsController::GetWebContents() const {
+ return NULL;
+}
+
+std::string QuitWithAppsController::id() const {
+ return kQuitWithAppsNotificationID;
+}
+
+bool QuitWithAppsController::ShouldQuit() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAppsKeepChromeAlive)) {
+ g_browser_process->local_state()->ClearPref(
+ prefs::kNotifyWhenAppsKeepChromeAlive);
+ return true;
+ }
+
+ // Quit immediately if there are no windows or the confirmation has been
+ // suppressed.
+ if (!apps::AppWindowRegistry::IsAppWindowRegisteredInAnyProfile(0))
+ return true;
+
+ // If there are browser windows, and this notification has been suppressed for
+ // this session or permanently, then just return false to prevent Chrome from
+ // quitting. If there are no browser windows, always show the notification.
+ bool suppress_always = !g_browser_process->local_state()->GetBoolean(
+ prefs::kNotifyWhenAppsKeepChromeAlive);
+ if (!chrome::BrowserIterator().done() &&
+ (suppress_for_session_ || suppress_always)) {
+ return false;
+ }
+
+ ProfileManager* profile_manager = g_browser_process->profile_manager();
+ DCHECK(profile_manager);
+
+ std::vector<Profile*> profiles(profile_manager->GetLoadedProfiles());
+ DCHECK(profiles.size());
+
+ // Delete any existing notification to ensure this one is shown.
+ g_browser_process->notification_ui_manager()->CancelById(id());
+ g_browser_process->notification_ui_manager()->Add(*notification_,
+ profiles[0]);
+
+ // Always return false, the notification UI can be used to quit all apps which
+ // will cause Chrome to quit.
+ return false;
+}
+
+// static
+void QuitWithAppsController::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterBooleanPref(prefs::kNotifyWhenAppsKeepChromeAlive, true);
+}
diff --git a/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h b/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h
new file mode 100644
index 0000000..f8b32fa
--- /dev/null
+++ b/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h
@@ -0,0 +1,49 @@
+// Copyright 2014 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.
+
+#ifndef CHROME_BROWSER_UI_COCOA_APPS_QUIT_WITH_APPS_CONTROLLER_MAC_H_
+#define CHROME_BROWSER_UI_COCOA_APPS_QUIT_WITH_APPS_CONTROLLER_MAC_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/notifications/notification.h"
+
+class PrefRegistrySimple;
+
+// QuitWithAppsController checks whether any apps are running and shows a
+// notification to quit all of them.
+class QuitWithAppsController : public NotificationDelegate {
+ public:
+ static const char kQuitWithAppsNotificationID[];
+
+ QuitWithAppsController();
+
+ // NotificationDelegate interface.
+ virtual void Display() OVERRIDE;
+ virtual void Error() OVERRIDE;
+ virtual void Close(bool by_user) OVERRIDE;
+ virtual void Click() OVERRIDE;
+ virtual void ButtonClick(int button_index) OVERRIDE;
+ virtual content::WebContents* GetWebContents() const OVERRIDE;
+ virtual std::string id() const OVERRIDE;
+
+ // Attempt to quit Chrome. This will display a notification and return false
+ // if there are apps running.
+ bool ShouldQuit();
+
+ // Register prefs used by QuitWithAppsController.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+ virtual ~QuitWithAppsController();
+
+ scoped_ptr<Notification> notification_;
+
+ // Whether to suppress showing the notification for the rest of the session.
+ bool suppress_for_session_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuitWithAppsController);
+};
+
+#endif // CHROME_BROWSER_UI_COCOA_APPS_QUIT_WITH_APPS_CONTROLLER_MAC_H_
diff --git a/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac_interactive_uitest.cc b/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac_interactive_uitest.cc
new file mode 100644
index 0000000..de3d15f
--- /dev/null
+++ b/chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac_interactive_uitest.cc
@@ -0,0 +1,127 @@
+// Copyright 2014 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.
+
+#include "chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h"
+
+#include "apps/app_window_registry.h"
+#include "apps/ui/native_app_window.h"
+#include "base/command_line.h"
+#include "chrome/browser/apps/app_browsertest_util.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_test_message_listener.h"
+#include "chrome/browser/lifetime/application_lifetime.h"
+#include "chrome/browser/notifications/message_center_notification_manager.h"
+#include "chrome/browser/notifications/notification_ui_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser_iterator.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/common/extension.h"
+#include "ui/message_center/message_center.h"
+
+typedef apps::AppWindowRegistry::AppWindowList AppWindowList;
+
+namespace {
+
+class QuitWithAppsControllerInteractiveTest
+ : public extensions::PlatformAppBrowserTest {
+ protected:
+ QuitWithAppsControllerInteractiveTest() : app_(NULL) {}
+
+ virtual ~QuitWithAppsControllerInteractiveTest() {}
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ PlatformAppBrowserTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitch(switches::kAppsKeepChromeAlive);
+ }
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ ExtensionBrowserTest::SetUpOnMainThread();
+
+ ExtensionTestMessageListener listener("Launched", false);
+ app_ = InstallAndLaunchPlatformApp("minimal_id");
+ ASSERT_TRUE(listener.WaitUntilSatisfied());
+ }
+
+ const extensions::Extension* app_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuitWithAppsControllerInteractiveTest);
+};
+
+} // namespace
+
+// Test that quitting while apps are open shows a notification instead.
+IN_PROC_BROWSER_TEST_F(QuitWithAppsControllerInteractiveTest, QuitBehavior) {
+ scoped_refptr<QuitWithAppsController> controller =
+ new QuitWithAppsController();
+ const Notification* notification;
+ message_center::MessageCenter* message_center =
+ message_center::MessageCenter::Get();
+ // One browser and one app window at this point.
+ EXPECT_FALSE(chrome::BrowserIterator().done());
+ EXPECT_EQ(1u, GetAppWindowCount());
+
+ // On the first quit, show notification.
+ EXPECT_FALSE(controller->ShouldQuit());
+ EXPECT_EQ(1u, GetAppWindowCount());
+ notification = g_browser_process->notification_ui_manager()->FindById(
+ QuitWithAppsController::kQuitWithAppsNotificationID);
+ ASSERT_TRUE(notification);
+
+ // If notification was dismissed by click, show again on next quit.
+ notification->delegate()->Click();
+ message_center->RemoveAllNotifications(false);
+ EXPECT_FALSE(controller->ShouldQuit());
+ EXPECT_EQ(1u, GetAppWindowCount());
+ notification = g_browser_process->notification_ui_manager()->FindById(
+ QuitWithAppsController::kQuitWithAppsNotificationID);
+ ASSERT_TRUE(notification);
+
+ EXPECT_FALSE(chrome::BrowserIterator().done());
+ EXPECT_EQ(1u, GetAppWindowCount());
+
+ // If notification is closed by user, don't show it next time.
+ notification->delegate()->Close(true);
+ message_center->RemoveAllNotifications(false);
+ EXPECT_FALSE(controller->ShouldQuit());
+ EXPECT_EQ(1u, GetAppWindowCount());
+ notification = g_browser_process->notification_ui_manager()->FindById(
+ QuitWithAppsController::kQuitWithAppsNotificationID);
+ EXPECT_EQ(NULL, notification);
+
+ EXPECT_FALSE(chrome::BrowserIterator().done());
+ EXPECT_EQ(1u, GetAppWindowCount());
+
+ // Normally, quitting would close all browsers, but since we're just
+ // simulating a quit, close it here.
+ content::WindowedNotificationObserver observer(
+ chrome::NOTIFICATION_BROWSER_CLOSED,
+ content::NotificationService::AllSources());
+ chrome::BrowserIterator()->window()->Close();
+ observer.Wait();
+
+ EXPECT_TRUE(chrome::BrowserIterator().done());
+ EXPECT_EQ(1u, GetAppWindowCount());
+
+ // Trying to quit while there are no browsers always shows notification.
+ EXPECT_FALSE(controller->ShouldQuit());
+ notification = g_browser_process->notification_ui_manager()->FindById(
+ QuitWithAppsController::kQuitWithAppsNotificationID);
+ ASSERT_TRUE(notification);
+
+ // Clicking "Quit All Apps." button closes all app windows.
+ notification->delegate()->ButtonClick(0);
+ message_center->RemoveAllNotifications(false);
+ EXPECT_EQ(0u, GetAppWindowCount());
+
+ // With no app windows open, ShouldQuit returns true.
+ EXPECT_TRUE(controller->ShouldQuit());
+ notification = g_browser_process->notification_ui_manager()->FindById(
+ QuitWithAppsController::kQuitWithAppsNotificationID);
+ EXPECT_EQ(NULL, notification);
+}
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index 485db76..bcadf96 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -534,6 +534,8 @@
'browser/ui/cocoa/apps/chrome_app_window_delegate_cocoa.mm',
'browser/ui/cocoa/apps/native_app_window_cocoa.h',
'browser/ui/cocoa/apps/native_app_window_cocoa.mm',
+ 'browser/ui/cocoa/apps/quit_with_apps_controller_mac.cc',
+ 'browser/ui/cocoa/apps/quit_with_apps_controller_mac.h',
'browser/ui/cocoa/autofill/autofill_account_chooser.h',
'browser/ui/cocoa/autofill/autofill_account_chooser.mm',
'browser/ui/cocoa/autofill/autofill_details_container.h',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 14e3b1a..2f57d31 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -81,6 +81,7 @@
'browser/ui/app_list/app_list_service_mac_interactive_uitest.mm',
'browser/ui/autofill/autofill_popup_controller_interactive_uitest.cc',
'browser/ui/browser_focus_uitest.cc',
+ 'browser/ui/cocoa/apps/quit_with_apps_controller_mac_interactive_uitest.cc',
'browser/ui/cocoa/panels/panel_cocoa_browsertest.mm',
'browser/ui/find_bar/find_bar_host_interactive_uitest.cc',
'browser/ui/fullscreen/fullscreen_controller_interactive_browsertest.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 87168e7..7c93680 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -1359,6 +1359,9 @@ const char kEnableSpeechDispatcher[] = "enable-speech-dispatcher";
#endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
#if defined(OS_MACOSX)
+// Prevents Chrome from quitting when Chrome Apps are open.
+const char kAppsKeepChromeAlive[] = "apps-keep-chrome-alive";
+
// Disables the creation and launch of app shims for platform apps.
const char kDisableAppShims[] = "disable-app-shims";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 5512234b..4217a55 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -382,6 +382,7 @@ extern const char kMigrateDataDirForSxS[];
#endif
#if defined(OS_MACOSX)
+extern const char kAppsKeepChromeAlive[];
extern const char kDisableAppShims[];
extern const char kDisableSystemFullscreenForTesting[];
extern const char kEnableSimplifiedFullscreen[];
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 823d46b..308d4bb 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -2240,6 +2240,11 @@ const char kChromeCreatedLoginItem[] =
// time.
const char kMigratedLoginItemPref[] =
"background_mode.migrated_login_item_pref";
+
+// A boolean that tracks whether to show a notification when trying to quit
+// while there are apps running.
+const char kNotifyWhenAppsKeepChromeAlive[] =
+ "apps.notify-when-apps-keep-chrome-alive";
#endif
// Set to true if background mode is enabled on this browser.
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 561ac67..d5bed91 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -755,6 +755,7 @@ extern const char kCustomHandlersEnabled[];
extern const char kUserRemovedLoginItem[];
extern const char kChromeCreatedLoginItem[];
extern const char kMigratedLoginItemPref[];
+extern const char kNotifyWhenAppsKeepChromeAlive[];
#endif
extern const char kBackgroundModeEnabled[];