summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorjackhou@chromium.org <jackhou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-29 01:51:44 +0000
committerjackhou@chromium.org <jackhou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-29 01:51:44 +0000
commit425f7a0afb038772bf0d1bfb007a1ccb13dfae35 (patch)
treeb2105f30f45bbd4b7315922b5f1cb262bfc90df4 /chrome/browser
parentac296392d7ab509cda68f9465e0f3f23bc383879 (diff)
downloadchromium_src-425f7a0afb038772bf0d1bfb007a1ccb13dfae35.zip
chromium_src-425f7a0afb038772bf0d1bfb007a1ccb13dfae35.tar.gz
chromium_src-425f7a0afb038772bf0d1bfb007a1ccb13dfae35.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 Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=265889 Review URL: https://codereview.chromium.org/220373003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@266731 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-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
7 files changed, 353 insertions, 0 deletions
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 311d3ba..461655c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1789,6 +1789,15 @@ const Experiment kExperiments[] = {
kOsAll,
SINGLE_VALUE_TYPE(switches::kAllowInsecureWebSocketFromHttpsOrigin)
},
+#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..51a8acb
--- /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_TRUE(apps::AppWindowRegistry::IsAppWindowRegisteredInAnyProfile(0));
+
+ // On the first quit, show notification.
+ EXPECT_FALSE(controller->ShouldQuit());
+ EXPECT_TRUE(apps::AppWindowRegistry::IsAppWindowRegisteredInAnyProfile(0));
+ 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_TRUE(apps::AppWindowRegistry::IsAppWindowRegisteredInAnyProfile(0));
+ notification = g_browser_process->notification_ui_manager()->FindById(
+ QuitWithAppsController::kQuitWithAppsNotificationID);
+ ASSERT_TRUE(notification);
+
+ EXPECT_FALSE(chrome::BrowserIterator().done());
+ EXPECT_TRUE(apps::AppWindowRegistry::IsAppWindowRegisteredInAnyProfile(0));
+
+ // 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_TRUE(apps::AppWindowRegistry::IsAppWindowRegisteredInAnyProfile(0));
+ notification = g_browser_process->notification_ui_manager()->FindById(
+ QuitWithAppsController::kQuitWithAppsNotificationID);
+ EXPECT_EQ(NULL, notification);
+
+ EXPECT_FALSE(chrome::BrowserIterator().done());
+ EXPECT_TRUE(apps::AppWindowRegistry::IsAppWindowRegisteredInAnyProfile(0));
+
+ // 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_TRUE(apps::AppWindowRegistry::IsAppWindowRegisteredInAnyProfile(0));
+
+ // 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_FALSE(apps::AppWindowRegistry::IsAppWindowRegisteredInAnyProfile(0));
+
+ // 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);
+}