1 // Copyright (c) 2011 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.
7 #include "base/base_paths.h"
8 #include "base/command_line.h"
9 #include "base/logging.h"
10 #include "base/utf_string_conversions.h"
11 #include "chrome/app/chrome_command_ids.h"
12 #include "chrome/browser/background_application_list_model.h"
13 #include "chrome/browser/background_mode_manager.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/status_icons/status_icon.h"
18 #include "chrome/browser/status_icons/status_tray.h"
19 #include "chrome/browser/ui/browser_list.h"
20 #include "chrome/common/chrome_switches.h"
21 #include "chrome/common/extensions/extension.h"
22 #include "chrome/common/pref_names.h"
23 #include "content/browser/user_metrics.h"
24 #include "content/common/notification_service.h"
25 #include "content/common/notification_type.h"
26 #include "grit/chromium_strings.h"
27 #include "grit/generated_resources.h"
28 #include "grit/theme_resources.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/base/resource/resource_bundle.h"
33 BackgroundModeManager::BackgroundModeData::BackgroundModeData(
35 BackgroundModeManager
* background_mode_manager
)
36 : applications_(new BackgroundApplicationListModel(profile
)),
39 context_menu_application_offset_(0),
41 background_mode_manager_(background_mode_manager
) {
44 BackgroundModeManager::BackgroundModeData::~BackgroundModeData() {
47 ///////////////////////////////////////////////////////////////////////////////
48 // BackgroundModeManager::BackgroundModeData, ui::SimpleMenuModel overrides
49 bool BackgroundModeManager::BackgroundModeData::IsCommandIdChecked(
50 int command_id
) const {
51 DCHECK(command_id
== IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND
);
55 bool BackgroundModeManager::BackgroundModeData::IsCommandIdEnabled(
56 int command_id
) const {
57 // For now, we do not support disabled items.
61 bool BackgroundModeManager::BackgroundModeData::GetAcceleratorForCommandId(
62 int command_id
, ui::Accelerator
* accelerator
) {
63 // No accelerators for status icon context menus.
67 void BackgroundModeManager::BackgroundModeData::ExecuteCommand(int item
) {
70 GetBrowserWindow()->OpenAboutChromeDialog();
73 UserMetrics::RecordAction(UserMetricsAction("Exit"));
74 BrowserList::CloseAllBrowsersAndExit();
77 GetBrowserWindow()->OpenOptionsDialog();
79 case IDC_TASK_MANAGER
:
80 GetBrowserWindow()->OpenTaskManager(true);
82 case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND
: {
83 // Background mode must already be enabled (as otherwise this menu would
85 DCHECK(background_mode_manager_
->IsBackgroundModePrefEnabled());
86 DCHECK(BrowserList::WillKeepAlive());
88 // Set the background mode pref to "disabled" - the resulting notification
89 // will result in a call to DisableBackgroundMode().
90 PrefService
* service
= g_browser_process
->local_state();
92 service
->SetBoolean(prefs::kBackgroundModeEnabled
, false);
96 ExecuteApplication(item
);
101 void BackgroundModeManager::BackgroundModeData::ExecuteApplication(
103 Browser
* browser
= GetBrowserWindow();
104 const Extension
* extension
= applications_
->GetExtension(item
);
105 browser
->OpenApplicationTab(profile_
, extension
, NEW_FOREGROUND_TAB
);
108 Browser
* BackgroundModeManager::BackgroundModeData::GetBrowserWindow() {
109 Browser
* browser
= BrowserList::GetLastActiveWithProfile(profile_
);
111 Browser::OpenEmptyWindow(profile_
);
112 browser
= BrowserList::GetLastActiveWithProfile(profile_
);
117 void BackgroundModeManager::BackgroundModeData::UpdateContextMenuEntryIcon(
118 const Extension
* extension
) {
121 context_menu_
->SetIcon(
122 context_menu_application_offset_
+
123 applications_
->GetPosition(extension
),
124 *(applications_
->GetIcon(extension
)));
126 status_icon_
->SetContextMenu(context_menu_
); // for Update effect
129 bool BackgroundModeManager::BackgroundModeData::HasBackgroundApp() {
130 return (applications_
->size() > 0);
133 ///////////////////////////////////////////////////////////////////////////////
134 // BackgroundModeManager, public
135 BackgroundModeManager::BackgroundModeManager(CommandLine
* command_line
)
136 : status_tray_(NULL
),
137 background_app_count_(0),
138 in_background_mode_(false),
139 keep_alive_for_startup_(false) {
140 // If background mode is currently disabled, just exit - don't listen for any
142 if (IsBackgroundModePermanentlyDisabled(command_line
))
145 // Listen for the background mode preference changing.
146 if (g_browser_process
->local_state()) { // Skip for unit tests
147 pref_registrar_
.Init(g_browser_process
->local_state());
148 pref_registrar_
.Add(prefs::kBackgroundModeEnabled
, this);
151 // Keep the browser alive until extensions are done loading - this is needed
152 // by the --no-startup-window flag. We want to stay alive until we load
153 // extensions, at which point we should either run in background mode (if
154 // there are background apps) or exit if there are none.
155 if (command_line
->HasSwitch(switches::kNoStartupWindow
)) {
156 keep_alive_for_startup_
= true;
157 BrowserList::StartKeepAlive();
160 // If the -keep-alive-for-test flag is passed, then always keep chrome running
161 // in the background until the user explicitly terminates it, by acting as if
162 // we loaded a background app.
163 if (command_line
->HasSwitch(switches::kKeepAliveForTest
))
164 OnBackgroundAppLoaded();
166 // Listen for the application shutting down so we can decrement our KeepAlive
168 registrar_
.Add(this, NotificationType::APP_TERMINATING
,
169 NotificationService::AllSources());
172 BackgroundModeManager::~BackgroundModeManager() {
173 for (std::map
<Profile
*, BackgroundModeInfo
>::iterator it
=
174 background_mode_data_
.begin();
175 it
!= background_mode_data_
.end();
177 it
->second
->applications_
->RemoveObserver(this);
180 // We're going away, so exit background mode (does nothing if we aren't in
181 // background mode currently). This is primarily needed for unit tests,
182 // because in an actual running system we'd get an APP_TERMINATING
183 // notification before being destroyed.
188 void BackgroundModeManager::RegisterPrefs(PrefService
* prefs
) {
189 prefs
->RegisterBooleanPref(prefs::kUserCreatedLoginItem
, false);
190 prefs
->RegisterBooleanPref(prefs::kBackgroundModeEnabled
, true);
194 void BackgroundModeManager::RegisterProfile(Profile
* profile
) {
195 // We don't want to register multiple times for one profile.
196 DCHECK(background_mode_data_
.find(profile
) == background_mode_data_
.end());
197 BackgroundModeInfo
bmd(new BackgroundModeData(profile
, this));
198 background_mode_data_
[profile
] = bmd
;
200 // Listen for when extensions are loaded/unloaded so we can track the
201 // number of background apps and modify our keep-alive and launch-on-startup
202 // state appropriately.
203 registrar_
.Add(this, NotificationType::EXTENSION_LOADED
,
204 Source
<Profile
>(profile
));
205 registrar_
.Add(this, NotificationType::EXTENSION_UNLOADED
,
206 Source
<Profile
>(profile
));
208 // Check for the presence of background apps after all extensions have been
209 // loaded, to handle the case where an extension has been manually removed
210 // while Chrome was not running.
211 registrar_
.Add(this, NotificationType::EXTENSIONS_READY
,
212 Source
<Profile
>(profile
));
214 background_mode_data_
[profile
]->applications_
->AddObserver(this);
217 ///////////////////////////////////////////////////////////////////////////////
218 // BackgroundModeManager, NotificationObserver overrides
219 void BackgroundModeManager::Observe(NotificationType type
,
220 const NotificationSource
& source
,
221 const NotificationDetails
& details
) {
222 switch (type
.value
) {
223 case NotificationType::PREF_CHANGED
:
224 DCHECK(*Details
<std::string
>(details
).ptr() ==
225 prefs::kBackgroundModeEnabled
);
226 if (IsBackgroundModePrefEnabled())
227 EnableBackgroundMode();
229 DisableBackgroundMode();
231 case NotificationType::EXTENSIONS_READY
:
232 // Extensions are loaded, so we don't need to manually keep the browser
233 // process alive any more when running in no-startup-window mode.
234 EndKeepAliveForStartup();
236 // On a Mac, we use 'login items' mechanism which has user-facing UI so we
237 // don't want to stomp on user choice every time we start and load
238 // registered extensions. This means that if a background app is removed
239 // or added while Chrome is not running, we could leave Chrome in the
240 // wrong state, but this is better than constantly forcing Chrome to
241 // launch on startup even after the user removes the LoginItem manually.
242 #if !defined(OS_MACOSX)
243 EnableLaunchOnStartup(background_app_count_
> 0);
246 case NotificationType::EXTENSION_LOADED
: {
247 Extension
* extension
= Details
<Extension
>(details
).ptr();
248 if (BackgroundApplicationListModel::IsBackgroundApp(*extension
)) {
249 // Extensions loaded after the ExtensionsService is ready should be
250 // treated as new installs.
251 Profile
* profile
= Source
<Profile
>(source
).ptr();
252 if (profile
->GetExtensionService()->is_ready())
253 OnBackgroundAppInstalled(extension
, profile
);
254 OnBackgroundAppLoaded();
258 case NotificationType::EXTENSION_UNLOADED
:
259 if (BackgroundApplicationListModel::IsBackgroundApp(
260 *Details
<UnloadedExtensionInfo
>(details
)->extension
)) {
261 Details
<UnloadedExtensionInfo
> info
=
262 Details
<UnloadedExtensionInfo
>(details
);
263 // If we already got an unload notification when it was disabled, ignore
265 // TODO(atwilson): Change BackgroundModeManager to use
266 // BackgroundApplicationListModel instead of tracking the count here.
267 if (info
->already_disabled
)
269 OnBackgroundAppUnloaded();
270 Profile
* profile
= Source
<Profile
>(source
).ptr();
271 OnBackgroundAppUninstalled(profile
);
274 case NotificationType::APP_TERMINATING
:
275 // Make sure we aren't still keeping the app alive (only happens if we
276 // don't receive an EXTENSIONS_READY notification for some reason).
277 EndKeepAliveForStartup();
278 // Performing an explicit shutdown, so exit background mode (does nothing
279 // if we aren't in background mode currently).
281 // Shutting down, so don't listen for any more notifications so we don't
282 // try to re-enter/exit background mode again.
283 registrar_
.RemoveAll();
291 ///////////////////////////////////////////////////////////////////////////////
292 // BackgroundModeManager, BackgroundApplicationListModel::Observer overrides
293 void BackgroundModeManager::OnApplicationDataChanged(
294 const Extension
* extension
, Profile
* profile
) {
295 UpdateContextMenuEntryIcon(extension
, profile
);
298 void BackgroundModeManager::OnApplicationListChanged(Profile
* profile
) {
299 UpdateStatusTrayIconContextMenu(profile
);
303 ///////////////////////////////////////////////////////////////////////////////
304 // BackgroundModeManager, private
305 void BackgroundModeManager::EndKeepAliveForStartup() {
306 if (keep_alive_for_startup_
) {
307 keep_alive_for_startup_
= false;
308 // We call this via the message queue to make sure we don't try to end
309 // keep-alive (which can shutdown Chrome) before the message loop has
311 MessageLoop::current()->PostTask(
312 FROM_HERE
, NewRunnableFunction(BrowserList::EndKeepAlive
));
316 void BackgroundModeManager::OnBackgroundAppLoaded() {
317 // When a background app loads, increment our count and also enable
318 // KeepAlive mode if the preference is set.
319 // The count here is across all profiles since we must have background
320 // mode if there is even one.
321 background_app_count_
++;
322 if (background_app_count_
== 1)
323 StartBackgroundMode();
326 void BackgroundModeManager::StartBackgroundMode() {
327 // Don't bother putting ourselves in background mode if we're already there
328 // or if background mode is disabled.
329 if (in_background_mode_
|| !IsBackgroundModePrefEnabled())
332 // Mark ourselves as running in background mode.
333 in_background_mode_
= true;
335 // Put ourselves in KeepAlive mode and create a status tray icon.
336 BrowserList::StartKeepAlive();
338 // Display a status icon to exit Chrome.
339 InitStatusTrayIcons();
342 void BackgroundModeManager::InitStatusTrayIcons() {
343 // Only initialize status tray icons for those profiles which actually
344 // have a background app running.
345 for (std::map
<Profile
*, BackgroundModeInfo
>::iterator it
=
346 background_mode_data_
.begin();
347 it
!= background_mode_data_
.end();
349 if (it
->second
->HasBackgroundApp())
350 CreateStatusTrayIcon(it
->first
);
354 void BackgroundModeManager::OnBackgroundAppUnloaded() {
355 // When a background app unloads, decrement our count and also end
356 // KeepAlive mode if appropriate.
357 background_app_count_
--;
358 DCHECK_GE(background_app_count_
, 0);
359 if (background_app_count_
== 0)
363 void BackgroundModeManager::EndBackgroundMode() {
364 if (!in_background_mode_
)
366 in_background_mode_
= false;
368 // End KeepAlive mode and blow away our status tray icon.
369 BrowserList::EndKeepAlive();
370 // There is a status tray icon for each profile. Blow them all away.
371 for (std::map
<Profile
*, BackgroundModeInfo
>::iterator it
=
372 background_mode_data_
.begin();
373 it
!= background_mode_data_
.end();
375 RemoveStatusTrayIcon(it
->first
);
379 void BackgroundModeManager::EnableBackgroundMode() {
380 DCHECK(IsBackgroundModePrefEnabled());
381 // If background mode should be enabled, but isn't, turn it on.
382 if (background_app_count_
> 0 && !in_background_mode_
) {
383 StartBackgroundMode();
384 EnableLaunchOnStartup(true);
388 void BackgroundModeManager::DisableBackgroundMode() {
389 DCHECK(!IsBackgroundModePrefEnabled());
390 // If background mode is currently enabled, turn it off.
391 if (in_background_mode_
) {
393 EnableLaunchOnStartup(false);
397 void BackgroundModeManager::OnBackgroundAppInstalled(
398 const Extension
* extension
, Profile
* profile
) {
399 // Background mode is disabled - don't do anything.
400 if (!IsBackgroundModePrefEnabled())
403 // We're installing a background app. If this is the first background app
404 // being installed, make sure we are set to launch on startup.
405 if (background_app_count_
== 0)
406 EnableLaunchOnStartup(true);
408 // Check if we need a status tray icon and make one if we do.
409 CreateStatusTrayIcon(profile
);
411 // Notify the user that a background app has been installed.
412 if (extension
) // NULL when called by unit tests.
413 DisplayAppInstalledNotification(extension
, profile
);
416 void BackgroundModeManager::OnBackgroundAppUninstalled(Profile
* profile
) {
417 // Check if we need to remove the status tray icon if there are no
418 // more background apps.
419 BackgroundModeInfo bmd
= GetBackgroundModeInfo(profile
);
421 // If there are still background apps for this profile, don't remove
422 // the status tray icon.
423 if (!bmd
->HasBackgroundApp())
424 RemoveStatusTrayIcon(profile
);
426 // When uninstalling a background app, disable launch on startup if
427 // we have no more background apps.
428 if (background_app_count_
== 0)
429 EnableLaunchOnStartup(false);
432 void BackgroundModeManager::CreateStatusTrayIcon(Profile
* profile
) {
433 // Only need status icons on windows/linux. ChromeOS doesn't allow exiting
434 // Chrome and Mac can use the dock icon instead.
436 // Since there are multiple profiles which share the status tray, we now
437 // use the browser process to keep track of it.
438 #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
440 status_tray_
= g_browser_process
->status_tray();
443 // If the platform doesn't support status icons, or we've already created
444 // our status icon, just return.
445 BackgroundModeInfo bmd
= GetBackgroundModeInfo(profile
);
446 if (!status_tray_
|| bmd
->status_icon_
)
449 bmd
->status_icon_
= status_tray_
->CreateStatusIcon();
450 if (!bmd
->status_icon_
)
453 // Set the image and add ourselves as a click observer on it.
454 // TODO(rlp): Status tray icon should have submenus for each profile.
455 SkBitmap
* bitmap
= ResourceBundle::GetSharedInstance().GetBitmapNamed(
456 IDR_STATUS_TRAY_ICON
);
457 bmd
->status_icon_
->SetImage(*bitmap
);
458 bmd
->status_icon_
->SetToolTip(l10n_util::GetStringUTF16(IDS_PRODUCT_NAME
));
459 UpdateStatusTrayIconContextMenu(profile
);
462 void BackgroundModeManager::UpdateContextMenuEntryIcon(
463 const Extension
* extension
, Profile
* profile
) {
464 BackgroundModeInfo bmd
= GetBackgroundModeInfo(profile
);
466 bmd
->UpdateContextMenuEntryIcon(extension
);
469 void BackgroundModeManager::UpdateStatusTrayIconContextMenu(Profile
* profile
) {
470 BackgroundModeInfo bmd
= GetBackgroundModeInfo(profile
);
472 // If no status icon exists, it's either because one wasn't created when
473 // it should have been which can happen when extensions load after the
474 // profile has already been registered with the background mode manager.
475 if (in_background_mode_
&& !bmd
->status_icon_
)
476 CreateStatusTrayIcon(profile
);
478 // If we don't have a status icon or one could not be created succesfully,
479 // then no need to continue the update.
480 if (!bmd
->status_icon_
)
483 // TODO(rlp): Add current profile color.
484 // Create a context menu item for Chrome.
485 ui::SimpleMenuModel
* menu
= new ui::SimpleMenuModel(bmd
.get());
487 menu
->AddItem(IDC_ABOUT
, l10n_util::GetStringFUTF16(IDS_ABOUT
,
488 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME
)));
489 menu
->AddItem(IDC_OPTIONS
, GetPreferencesMenuLabel());
490 menu
->AddItemWithStringId(IDC_TASK_MANAGER
, IDS_TASK_MANAGER
);
491 menu
->AddSeparator();
493 bmd
->context_menu_application_offset_
= menu
->GetItemCount();
494 for (ExtensionList::const_iterator cursor
= bmd
->applications_
->begin();
495 cursor
!= bmd
->applications_
->end();
496 ++cursor
, ++position
) {
497 const SkBitmap
* icon
= bmd
->applications_
->GetIcon(*cursor
);
498 DCHECK(position
== bmd
->applications_
->GetPosition(*cursor
));
499 const std::string
& name
= (*cursor
)->name();
500 menu
->AddItem(position
, UTF8ToUTF16(name
));
502 menu
->SetIcon(menu
->GetItemCount() - 1, *icon
);
504 if (bmd
->applications_
->size() > 0)
505 menu
->AddSeparator();
506 menu
->AddCheckItemWithStringId(
507 IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND
,
508 IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND
);
509 menu
->AddItemWithStringId(IDC_EXIT
, IDS_EXIT
);
510 bmd
->context_menu_
= menu
;
511 bmd
->status_icon_
->SetContextMenu(menu
);
514 void BackgroundModeManager::RemoveStatusTrayIcon(Profile
* profile
) {
515 BackgroundModeInfo bmd
= GetBackgroundModeInfo(profile
);
518 if (bmd
->status_icon_
)
519 status_tray_
->RemoveStatusIcon(bmd
->status_icon_
);
520 bmd
->status_icon_
= NULL
;
521 bmd
->context_menu_
= NULL
; // Do not delete, points within |status_icon_|.
524 BackgroundModeManager::BackgroundModeInfo
525 BackgroundModeManager::GetBackgroundModeInfo(Profile
* profile
) {
526 DCHECK(background_mode_data_
.find(profile
) != background_mode_data_
.end());
527 return background_mode_data_
[profile
];
531 bool BackgroundModeManager::IsBackgroundModePermanentlyDisabled(
532 const CommandLine
* command_line
) {
534 // Background mode is disabled if the appropriate flag is passed, or if
535 // extensions are disabled, or if the associated preference is unset. It's
536 // always disabled on chromeos since chrome is always running on that
537 // platform, making it superfluous.
538 #if defined(OS_CHROMEOS)
541 bool background_mode_disabled
=
542 command_line
->HasSwitch(switches::kDisableBackgroundMode
) ||
543 command_line
->HasSwitch(switches::kDisableExtensions
);
544 return background_mode_disabled
;
548 bool BackgroundModeManager::IsBackgroundModePrefEnabled() {
549 PrefService
* service
= g_browser_process
->local_state();
551 return service
->GetBoolean(prefs::kBackgroundModeEnabled
);