1 // Copyright (c) 2012 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 #include "chrome/browser/background/background_mode_manager.h"
11 #include "base/base_paths.h"
12 #include "base/bind.h"
13 #include "base/callback.h"
14 #include "base/command_line.h"
15 #include "base/location.h"
16 #include "base/logging.h"
17 #include "base/metrics/histogram.h"
18 #include "base/prefs/pref_registry_simple.h"
19 #include "base/prefs/pref_service.h"
20 #include "base/single_thread_task_runner.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/thread_task_runner_handle.h"
23 #include "chrome/app/chrome_command_ids.h"
24 #include "chrome/browser/background/background_application_list_model.h"
25 #include "chrome/browser/background/background_trigger.h"
26 #include "chrome/browser/browser_process.h"
27 #include "chrome/browser/browser_shutdown.h"
28 #include "chrome/browser/chrome_notification_types.h"
29 #include "chrome/browser/extensions/extension_service.h"
30 #include "chrome/browser/lifetime/application_lifetime.h"
31 #include "chrome/browser/profiles/profile.h"
32 #include "chrome/browser/profiles/profile_info_cache.h"
33 #include "chrome/browser/profiles/profile_manager.h"
34 #include "chrome/browser/status_icons/status_icon.h"
35 #include "chrome/browser/status_icons/status_tray.h"
36 #include "chrome/browser/ui/browser.h"
37 #include "chrome/browser/ui/browser_commands.h"
38 #include "chrome/browser/ui/browser_dialogs.h"
39 #include "chrome/browser/ui/browser_finder.h"
40 #include "chrome/browser/ui/browser_list.h"
41 #include "chrome/browser/ui/chrome_pages.h"
42 #include "chrome/browser/ui/extensions/app_launch_params.h"
43 #include "chrome/browser/ui/extensions/application_launch.h"
44 #include "chrome/browser/ui/host_desktop.h"
45 #include "chrome/browser/ui/user_manager.h"
46 #include "chrome/common/chrome_constants.h"
47 #include "chrome/common/chrome_switches.h"
48 #include "chrome/common/extensions/extension_constants.h"
49 #include "chrome/common/pref_names.h"
50 #include "chrome/grit/chromium_strings.h"
51 #include "chrome/grit/generated_resources.h"
52 #include "content/public/browser/notification_service.h"
53 #include "content/public/browser/user_metrics.h"
54 #include "extensions/browser/extension_system.h"
55 #include "extensions/common/constants.h"
56 #include "extensions/common/extension.h"
57 #include "extensions/common/manifest_handlers/options_page_info.h"
58 #include "extensions/common/one_shot_event.h"
59 #include "extensions/common/permissions/permission_set.h"
60 #include "grit/chrome_unscaled_resources.h"
61 #include "ui/base/l10n/l10n_util.h"
62 #include "ui/base/resource/resource_bundle.h"
65 #include "components/browser_watcher/exit_funnel_win.h"
68 using base::UserMetricsAction
;
69 using extensions::Extension
;
70 using extensions::UpdatedExtensionPermissionsInfo
;
74 // Records histogram about which auto-launch pattern (if any) was used to launch
75 // the current process based on |command_line|.
76 void RecordAutoLaunchState(const base::CommandLine
& command_line
) {
77 enum AutoLaunchState
{
79 AUTO_LAUNCH_BACKGROUND
= 1,
80 AUTO_LAUNCH_FOREGROUND
= 2,
81 AUTO_LAUNCH_FOREGROUND_USELESS
= 3,
82 AUTO_LAUNCH_NUM_STATES
83 } auto_launch_state
= AUTO_LAUNCH_NONE
;
85 if (command_line
.HasSwitch(switches::kNoStartupWindow
))
86 auto_launch_state
= AUTO_LAUNCH_BACKGROUND
;
88 if (command_line
.HasSwitch(switches::kAutoLaunchAtStartup
)) {
89 // The only purpose of kAutoLaunchAtStartup is to override a background
90 // auto-launch from kNoStartupWindow into a foreground auto-launch. It's a
91 // meaningless switch on its own.
92 if (auto_launch_state
== AUTO_LAUNCH_BACKGROUND
) {
93 auto_launch_state
= AUTO_LAUNCH_FOREGROUND
;
95 auto_launch_state
= AUTO_LAUNCH_FOREGROUND_USELESS
;
99 // Observe the AutoLaunchStates in the wild. According to the platform-
100 // specific implementations of EnableLaunchOnStartup(), we'd expect only Mac
101 // and Windows to have any sort of AutoLaunchState and only Windows should use
102 // FOREGROUND if at all (it was only used by a deprecated experiment and a
103 // master pref which may not be used much). Tighten up auto-launch settings
104 // based on the result of usage in the wild.
105 UMA_HISTOGRAM_ENUMERATION("BackgroundMode.OnStartup.AutoLaunchState",
106 auto_launch_state
, AUTO_LAUNCH_NUM_STATES
);
109 // Enum for recording menu item clicks in UMA.
110 // NOTE: Do not renumber these as that would confuse interpretation of
111 // previously logged data. When making changes, also update histograms.xml.
114 MENU_ITEM_TASK_MANAGER
= 1,
115 MENU_ITEM_BACKGROUND_CLIENT
= 2,
116 MENU_ITEM_KEEP_RUNNING
= 3,
121 void RecordMenuItemClick(MenuItem item
) {
122 UMA_HISTOGRAM_ENUMERATION("BackgroundMode.MenuItemClick", item
,
123 MENU_ITEM_NUM_STATES
);
127 BackgroundModeManager::BackgroundModeData::BackgroundModeData(
129 CommandIdHandlerVector
* command_id_handler_vector
)
130 : applications_(new BackgroundApplicationListModel(profile
)),
132 command_id_handler_vector_(command_id_handler_vector
) {
135 BackgroundModeManager::BackgroundModeData::~BackgroundModeData() {
138 ///////////////////////////////////////////////////////////////////////////////
139 // BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
140 void BackgroundModeManager::BackgroundModeData::ExecuteCommand(
143 switch (command_id
) {
144 case IDC_MinimumLabelValue
:
145 // Do nothing. This is just a label.
148 DCHECK(!command_id_handler_vector_
->at(command_id
).is_null());
149 RecordMenuItemClick(MENU_ITEM_BACKGROUND_CLIENT
);
150 command_id_handler_vector_
->at(command_id
).Run();
155 Browser
* BackgroundModeManager::BackgroundModeData::GetBrowserWindow() {
156 return BackgroundModeManager::GetBrowserWindowForProfile(profile_
);
159 int BackgroundModeManager::BackgroundModeData::GetBackgroundClientCount()
161 return applications_
->size() + registered_triggers_
.size();
164 void BackgroundModeManager::BackgroundModeData::BuildProfileMenu(
165 StatusIconMenuModel
* menu
,
166 StatusIconMenuModel
* containing_menu
) {
167 // When there are no background clients, we want to display just a label
168 // stating that none are running.
169 if (GetBackgroundClientCount() == 0) {
170 menu
->AddItemWithStringId(IDC_MinimumLabelValue
,
171 IDS_BACKGROUND_APP_NOT_INSTALLED
);
172 menu
->SetCommandIdEnabled(IDC_MinimumLabelValue
, false);
174 // Add a menu item for each application (extension).
175 for (const auto& application
: *applications_
) {
176 const gfx::ImageSkia
* icon
= applications_
->GetIcon(application
.get());
177 const std::string
& name
= application
->name();
178 int command_id
= command_id_handler_vector_
->size();
179 // Check that the command ID is within the dynamic range.
180 DCHECK_LT(command_id
, IDC_MinimumLabelValue
);
181 command_id_handler_vector_
->push_back(
182 base::Bind(&BackgroundModeManager::LaunchBackgroundApplication
,
183 profile_
, application
.get()));
184 menu
->AddItem(command_id
, base::UTF8ToUTF16(name
));
186 menu
->SetIcon(menu
->GetItemCount() - 1, gfx::Image(*icon
));
188 // Component extensions with background that do not have an options page
189 // will cause this menu item to go to the extensions page with an
190 // absent component extension.
192 // Ideally, we would remove this item, but this conflicts with the user
193 // model where this menu shows the extensions with background.
195 // The compromise is to disable the item, avoiding the non-actionable
196 // navigate to the extensions page and preserving the user model.
197 if (application
->location() == extensions::Manifest::COMPONENT
) {
199 extensions::OptionsPageInfo::GetOptionsPage(application
.get());
200 if (!options_page
.is_valid())
201 menu
->SetCommandIdEnabled(command_id
, false);
205 // Add a menu item for each trigger.
206 for (const auto& trigger
: registered_triggers_
) {
207 const gfx::ImageSkia
* icon
= trigger
->GetIcon();
208 const base::string16
& name
= trigger
->GetName();
209 int command_id
= command_id_handler_vector_
->size();
210 // Check that the command ID is within the dynamic range.
211 DCHECK_LT(command_id
, IDC_MinimumLabelValue
);
212 command_id_handler_vector_
->push_back(base::Bind(
213 &BackgroundTrigger::OnMenuClick
, base::Unretained(trigger
)));
214 menu
->AddItem(command_id
, name
);
216 menu
->SetIcon(menu
->GetItemCount() - 1, gfx::Image(*icon
));
219 if (containing_menu
) {
220 int menu_command_id
= command_id_handler_vector_
->size();
221 // Check that the command ID is within the dynamic range.
222 DCHECK_LT(menu_command_id
, IDC_MinimumLabelValue
);
223 command_id_handler_vector_
->push_back(base::Bind(&base::DoNothing
));
224 containing_menu
->AddSubMenu(menu_command_id
, name_
, menu
);
228 void BackgroundModeManager::BackgroundModeData::SetName(
229 const base::string16
& new_profile_name
) {
230 name_
= new_profile_name
;
233 base::string16
BackgroundModeManager::BackgroundModeData::name() {
237 std::set
<const extensions::Extension
*>
238 BackgroundModeManager::BackgroundModeData::GetNewBackgroundApps() {
239 std::set
<const extensions::Extension
*> new_apps
;
241 // Copy all current extensions into our list of |current_extensions_|.
242 for (const auto& application
: *applications_
) {
243 const extensions::ExtensionId
& id
= application
->id();
244 if (current_extensions_
.count(id
) == 0) {
245 // Not found in our set yet - add it and maybe return as a previously
247 current_extensions_
.insert(id
);
248 // If this application has been newly loaded after the initial startup,
250 if (applications_
->is_ready())
251 new_apps
.insert(application
.get());
258 bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare(
259 const BackgroundModeData
* bmd1
,
260 const BackgroundModeData
* bmd2
) {
261 return bmd1
->name_
< bmd2
->name_
;
264 void BackgroundModeManager::BackgroundModeData::AddPendingTrigger(
265 BackgroundTrigger
* trigger
, bool should_notify_user
) {
266 if (HasTrigger(trigger
))
268 pending_trigger_data_
[trigger
] = should_notify_user
;
271 BackgroundModeManager::PendingTriggerData
272 BackgroundModeManager::BackgroundModeData::GetPendingTriggerData() const {
273 return pending_trigger_data_
;
276 void BackgroundModeManager::BackgroundModeData::ClearPendingTriggerData() {
277 pending_trigger_data_
.clear();
280 int BackgroundModeManager::BackgroundModeData::GetPendingTriggerCount() const {
281 return pending_trigger_data_
.size();
284 void BackgroundModeManager::BackgroundModeData::RegisterTrigger(
285 BackgroundTrigger
* trigger
) {
286 if (HasTrigger(trigger
))
288 registered_triggers_
.insert(trigger
);
291 void BackgroundModeManager::BackgroundModeData::UnregisterTrigger(
292 BackgroundTrigger
* trigger
) {
293 registered_triggers_
.erase(trigger
);
294 pending_trigger_data_
.erase(trigger
);
297 bool BackgroundModeManager::BackgroundModeData::HasTrigger(
298 BackgroundTrigger
* trigger
) {
299 if (registered_triggers_
.find(trigger
) != registered_triggers_
.end())
301 return pending_trigger_data_
.find(trigger
) != pending_trigger_data_
.end();
304 ///////////////////////////////////////////////////////////////////////////////
305 // BackgroundModeManager, public
306 BackgroundModeManager::BackgroundModeManager(
307 const base::CommandLine
& command_line
,
308 ProfileInfoCache
* profile_cache
)
309 : profile_cache_(profile_cache
),
313 in_background_mode_(false),
314 keep_alive_for_startup_(false),
315 keep_alive_for_test_(false),
316 background_mode_suspended_(false),
317 keeping_alive_(false),
318 weak_factory_(this) {
319 // We should never start up if there is no browser process or if we are
320 // currently quitting.
321 CHECK(g_browser_process
!= NULL
);
322 CHECK(!browser_shutdown::IsTryingToQuit());
324 // Add self as an observer for the profile info cache so we know when profiles
325 // are deleted and their names change.
326 profile_cache_
->AddObserver(this);
328 RecordAutoLaunchState(command_line
);
329 UMA_HISTOGRAM_BOOLEAN("BackgroundMode.OnStartup.IsBackgroundModePrefEnabled",
330 IsBackgroundModePrefEnabled());
332 // Listen for the background mode preference changing.
333 if (g_browser_process
->local_state()) { // Skip for unit tests
334 pref_registrar_
.Init(g_browser_process
->local_state());
336 prefs::kBackgroundModeEnabled
,
337 base::Bind(&BackgroundModeManager::OnBackgroundModeEnabledPrefChanged
,
338 base::Unretained(this)));
341 // Keep the browser alive until extensions are done loading - this is needed
342 // by the --no-startup-window flag. We want to stay alive until we load
343 // extensions, at which point we should either run in background mode (if
344 // there are background apps) or exit if there are none.
345 if (command_line
.HasSwitch(switches::kNoStartupWindow
)) {
346 keep_alive_for_startup_
= true;
347 chrome::IncrementKeepAliveCount();
349 // Otherwise, start with background mode suspended in case we're launching
350 // in a mode that doesn't open a browser window. It will be resumed when the
351 // first browser window is opened.
352 SuspendBackgroundMode();
355 // If the -keep-alive-for-test flag is passed, then always keep chrome running
356 // in the background until the user explicitly terminates it.
357 if (command_line
.HasSwitch(switches::kKeepAliveForTest
))
358 keep_alive_for_test_
= true;
360 if (ShouldBeInBackgroundMode())
361 StartBackgroundMode();
363 // Listen for the application shutting down so we can decrement our KeepAlive
365 registrar_
.Add(this, chrome::NOTIFICATION_APP_TERMINATING
,
366 content::NotificationService::AllSources());
367 BrowserList::AddObserver(this);
370 BackgroundModeManager::~BackgroundModeManager() {
371 // Remove ourselves from the application observer list (only needed by unit
372 // tests since APP_TERMINATING is what does this in a real running system).
373 for (const auto& it
: background_mode_data_
)
374 it
.second
->applications_
->RemoveObserver(this);
375 BrowserList::RemoveObserver(this);
377 // We're going away, so exit background mode (does nothing if we aren't in
378 // background mode currently). This is primarily needed for unit tests,
379 // because in an actual running system we'd get an APP_TERMINATING
380 // notification before being destroyed.
385 void BackgroundModeManager::RegisterPrefs(PrefRegistrySimple
* registry
) {
386 #if defined(OS_MACOSX)
387 registry
->RegisterBooleanPref(prefs::kUserRemovedLoginItem
, false);
388 registry
->RegisterBooleanPref(prefs::kChromeCreatedLoginItem
, false);
389 registry
->RegisterBooleanPref(prefs::kMigratedLoginItemPref
, false);
391 registry
->RegisterBooleanPref(prefs::kBackgroundModeEnabled
, true);
394 void BackgroundModeManager::RegisterProfile(Profile
* profile
) {
395 // We don't want to register multiple times for one profile.
396 DCHECK(background_mode_data_
.find(profile
) == background_mode_data_
.end());
397 BackgroundModeInfo
bmd(
398 new BackgroundModeData(profile
, &command_id_handler_vector_
));
399 background_mode_data_
[profile
] = bmd
;
401 // Initially set the name for this background mode data.
402 size_t index
= profile_cache_
->GetIndexOfProfileWithPath(profile
->GetPath());
403 base::string16 name
= l10n_util::GetStringUTF16(IDS_PROFILES_DEFAULT_NAME
);
404 if (index
!= std::string::npos
)
405 name
= profile_cache_
->GetNameOfProfileAtIndex(index
);
408 // Check for the presence of background apps after all extensions have been
409 // loaded, to handle the case where an extension has been manually removed
410 // while Chrome was not running.
411 extensions::ExtensionSystem::Get(profile
)->ready().Post(
413 base::Bind(&BackgroundModeManager::OnExtensionsReady
,
414 weak_factory_
.GetWeakPtr(), profile
));
416 bmd
->applications_
->AddObserver(this);
418 // If we're adding a new profile and running in multi-profile mode, this new
419 // profile should be added to the status icon if one currently exists.
420 if (in_background_mode_
&& status_icon_
)
421 UpdateStatusTrayIconContextMenu();
425 void BackgroundModeManager::LaunchBackgroundApplication(
427 const Extension
* extension
) {
428 OpenApplication(AppLaunchParams(profile
, extension
, NEW_FOREGROUND_TAB
,
429 extensions::SOURCE_BACKGROUND
));
433 Browser
* BackgroundModeManager::GetBrowserWindowForProfile(Profile
* profile
) {
434 chrome::HostDesktopType host_desktop_type
= chrome::GetActiveDesktop();
436 chrome::FindLastActiveWithProfile(profile
, host_desktop_type
);
437 return browser
? browser
438 : chrome::OpenEmptyWindow(profile
, host_desktop_type
);
441 bool BackgroundModeManager::IsBackgroundModeActive() {
442 return in_background_mode_
;
445 int BackgroundModeManager::NumberOfBackgroundModeData() {
446 return background_mode_data_
.size();
449 void BackgroundModeManager::RegisterTrigger(Profile
* profile
,
450 BackgroundTrigger
* trigger
,
451 bool should_notify_user
) {
452 BackgroundModeManager::BackgroundModeData
* bmd
=
453 GetBackgroundModeData(profile
);
457 // Only proceed if we don't have this trigger yet.
458 if (bmd
->HasTrigger(trigger
))
461 // If the background pref is disabled, store it as a pending trigger that may
462 // be registered later if the pref gets enabled.
463 if (!IsBackgroundModePrefEnabled()) {
464 bmd
->AddPendingTrigger(trigger
, should_notify_user
);
468 bmd
->RegisterTrigger(trigger
);
469 std::vector
<base::string16
> new_client_names
;
470 if (should_notify_user
)
471 new_client_names
.push_back(trigger
->GetName());
472 OnClientsChanged(profile
, new_client_names
);
475 void BackgroundModeManager::UnregisterTrigger(Profile
* profile
,
476 BackgroundTrigger
* trigger
) {
477 if (!IsBackgroundModePrefEnabled())
480 BackgroundModeManager::BackgroundModeData
* bmd
=
481 GetBackgroundModeData(profile
);
485 // Only proceed if this is a known trigger.
486 if (!bmd
->HasTrigger(trigger
))
488 bmd
->UnregisterTrigger(trigger
);
490 std::vector
<base::string16
> new_client_names
;
491 OnClientsChanged(profile
, new_client_names
);
494 ///////////////////////////////////////////////////////////////////////////////
495 // BackgroundModeManager, content::NotificationObserver overrides
496 void BackgroundModeManager::Observe(
498 const content::NotificationSource
& source
,
499 const content::NotificationDetails
& details
) {
501 case chrome::NOTIFICATION_APP_TERMINATING
:
502 // Make sure we aren't still keeping the app alive (only happens if we
503 // don't receive an EXTENSIONS_READY notification for some reason).
504 DecrementKeepAliveCountForStartup();
505 // Performing an explicit shutdown, so exit background mode (does nothing
506 // if we aren't in background mode currently).
508 // Shutting down, so don't listen for any more notifications so we don't
509 // try to re-enter/exit background mode again.
510 registrar_
.RemoveAll();
511 for (const auto& it
: background_mode_data_
)
512 it
.second
->applications_
->RemoveObserver(this);
520 void BackgroundModeManager::OnExtensionsReady(Profile
* profile
) {
521 BackgroundModeManager::BackgroundModeData
* bmd
=
522 GetBackgroundModeData(profile
);
524 UMA_HISTOGRAM_COUNTS_100("BackgroundMode.BackgroundApplicationsCount",
525 bmd
->applications_
->size());
527 // Extensions are loaded, so we don't need to manually keep the browser
528 // process alive any more when running in no-startup-window mode.
529 DecrementKeepAliveCountForStartup();
532 void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() {
533 bool enabled
= IsBackgroundModePrefEnabled();
534 UMA_HISTOGRAM_BOOLEAN("BackgroundMode.BackgroundModeEnabledPrefChanged",
537 EnableBackgroundMode();
539 DisableBackgroundMode();
542 ///////////////////////////////////////////////////////////////////////////////
543 // BackgroundModeManager, BackgroundApplicationListModel::Observer overrides
544 void BackgroundModeManager::OnApplicationDataChanged(
545 const Extension
* extension
, Profile
* profile
) {
546 UpdateStatusTrayIconContextMenu();
549 void BackgroundModeManager::OnApplicationListChanged(Profile
* profile
) {
550 if (!IsBackgroundModePrefEnabled())
553 BackgroundModeManager::BackgroundModeData
* bmd
=
554 GetBackgroundModeData(profile
);
558 // Get the new apps (if any) and process them.
559 std::set
<const extensions::Extension
*> new_apps
= bmd
->GetNewBackgroundApps();
560 std::vector
<base::string16
> new_names
;
561 for (const auto& app
: new_apps
)
562 new_names
.push_back(base::UTF8ToUTF16(app
->name()));
563 OnClientsChanged(profile
, new_names
);
566 ///////////////////////////////////////////////////////////////////////////////
567 // BackgroundModeManager, ProfileInfoCacheObserver overrides
568 void BackgroundModeManager::OnProfileAdded(const base::FilePath
& profile_path
) {
569 ProfileInfoCache
& cache
=
570 g_browser_process
->profile_manager()->GetProfileInfoCache();
571 base::string16 profile_name
= cache
.GetNameOfProfileAtIndex(
572 cache
.GetIndexOfProfileWithPath(profile_path
));
573 // At this point, the profile should be registered with the background mode
574 // manager, but when it's actually added to the cache is when its name is
575 // set so we need up to update that with the background_mode_data.
576 for (const auto& it
: background_mode_data_
) {
577 if (it
.first
->GetPath() == profile_path
) {
578 it
.second
->SetName(profile_name
);
579 UpdateStatusTrayIconContextMenu();
585 void BackgroundModeManager::OnProfileWillBeRemoved(
586 const base::FilePath
& profile_path
) {
587 ProfileInfoCache
& cache
=
588 g_browser_process
->profile_manager()->GetProfileInfoCache();
589 base::string16 profile_name
= cache
.GetNameOfProfileAtIndex(
590 cache
.GetIndexOfProfileWithPath(profile_path
));
591 // Remove the profile from our map of profiles.
592 BackgroundModeInfoMap::iterator it
=
593 GetBackgroundModeIterator(profile_name
);
594 // If a profile isn't running a background app, it may not be in the map.
595 if (it
!= background_mode_data_
.end()) {
596 it
->second
->applications_
->RemoveObserver(this);
597 background_mode_data_
.erase(it
);
598 // If there are no background mode profiles any longer, then turn off
600 if (!ShouldBeInBackgroundMode()) {
601 EnableLaunchOnStartup(false);
604 UpdateStatusTrayIconContextMenu();
608 void BackgroundModeManager::OnProfileNameChanged(
609 const base::FilePath
& profile_path
,
610 const base::string16
& old_profile_name
) {
611 ProfileInfoCache
& cache
=
612 g_browser_process
->profile_manager()->GetProfileInfoCache();
613 base::string16 new_profile_name
= cache
.GetNameOfProfileAtIndex(
614 cache
.GetIndexOfProfileWithPath(profile_path
));
615 BackgroundModeInfoMap::const_iterator it
=
616 GetBackgroundModeIterator(old_profile_name
);
617 // We check that the returned iterator is valid due to unittests, but really
618 // this should only be called on profiles already known by the background
620 if (it
!= background_mode_data_
.end()) {
621 it
->second
->SetName(new_profile_name
);
622 UpdateStatusTrayIconContextMenu();
626 BackgroundModeManager::BackgroundModeData
*
627 BackgroundModeManager::GetBackgroundModeDataForLastProfile() const {
628 Profile
* most_recent_profile
= g_browser_process
->profile_manager()->
629 GetLastUsedProfileAllowedByPolicy();
630 BackgroundModeInfoMap::const_iterator profile_background_data
=
631 background_mode_data_
.find(most_recent_profile
);
633 if (profile_background_data
== background_mode_data_
.end())
636 // Do not permit a locked profile to be used to open a browser.
637 ProfileInfoCache
& cache
=
638 g_browser_process
->profile_manager()->GetProfileInfoCache();
639 if (cache
.ProfileIsSigninRequiredAtIndex(cache
.GetIndexOfProfileWithPath(
640 profile_background_data
->first
->GetPath())))
643 return profile_background_data
->second
.get();
646 ///////////////////////////////////////////////////////////////////////////////
647 // BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
648 void BackgroundModeManager::ExecuteCommand(int command_id
, int event_flags
) {
649 BackgroundModeData
* bmd
= GetBackgroundModeDataForLastProfile();
650 switch (command_id
) {
652 RecordMenuItemClick(MENU_ITEM_ABOUT
);
654 chrome::ShowAboutChrome(bmd
->GetBrowserWindow());
656 UserManager::Show(base::FilePath(),
657 profiles::USER_MANAGER_NO_TUTORIAL
,
658 profiles::USER_MANAGER_SELECT_PROFILE_ABOUT_CHROME
);
661 case IDC_TASK_MANAGER
:
662 RecordMenuItemClick(MENU_ITEM_TASK_MANAGER
);
664 chrome::OpenTaskManager(bmd
->GetBrowserWindow());
666 UserManager::Show(base::FilePath(),
667 profiles::USER_MANAGER_NO_TUTORIAL
,
668 profiles::USER_MANAGER_SELECT_PROFILE_TASK_MANAGER
);
672 RecordMenuItemClick(MENU_ITEM_EXIT
);
674 browser_watcher::ExitFunnel::RecordSingleEvent(
675 chrome::kBrowserExitCodesRegistryPath
, L
"TraybarExit");
677 content::RecordAction(UserMetricsAction("Exit"));
678 chrome::CloseAllBrowsers();
680 case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND
: {
681 // Background mode must already be enabled (as otherwise this menu would
683 DCHECK(IsBackgroundModePrefEnabled());
684 DCHECK(chrome::WillKeepAlive());
686 RecordMenuItemClick(MENU_ITEM_KEEP_RUNNING
);
688 // Set the background mode pref to "disabled" - the resulting notification
689 // will result in a call to DisableBackgroundMode().
690 PrefService
* service
= g_browser_process
->local_state();
692 service
->SetBoolean(prefs::kBackgroundModeEnabled
, false);
697 bmd
->ExecuteCommand(command_id
, event_flags
);
699 UserManager::Show(base::FilePath(),
700 profiles::USER_MANAGER_NO_TUTORIAL
,
701 profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION
);
708 ///////////////////////////////////////////////////////////////////////////////
709 // BackgroundModeManager, private
710 void BackgroundModeManager::DecrementKeepAliveCountForStartup() {
711 if (keep_alive_for_startup_
) {
712 keep_alive_for_startup_
= false;
713 // We call this via the message queue to make sure we don't try to end
714 // keep-alive (which can shutdown Chrome) before the message loop has
716 base::ThreadTaskRunnerHandle::Get()->PostTask(
717 FROM_HERE
, base::Bind(&chrome::DecrementKeepAliveCount
));
721 void BackgroundModeManager::StartBackgroundMode() {
722 DCHECK(ShouldBeInBackgroundMode());
723 // Don't bother putting ourselves in background mode if we're already there
724 // or if background mode is disabled.
725 if (in_background_mode_
)
728 // Mark ourselves as running in background mode.
729 in_background_mode_
= true;
731 UpdateKeepAliveAndTrayIcon();
733 content::NotificationService::current()->Notify(
734 chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED
,
735 content::Source
<BackgroundModeManager
>(this),
736 content::Details
<bool>(&in_background_mode_
));
739 void BackgroundModeManager::EndBackgroundMode() {
740 if (!in_background_mode_
)
742 in_background_mode_
= false;
744 UpdateKeepAliveAndTrayIcon();
746 content::NotificationService::current()->Notify(
747 chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED
,
748 content::Source
<BackgroundModeManager
>(this),
749 content::Details
<bool>(&in_background_mode_
));
752 void BackgroundModeManager::EnableBackgroundMode() {
753 DCHECK(IsBackgroundModePrefEnabled());
754 // If background mode should be enabled, but isn't, turn it on.
755 if (!in_background_mode_
&& ShouldBeInBackgroundMode()) {
756 StartBackgroundMode();
758 // Register pending triggers.
759 for (const auto& bmd_iterator
: background_mode_data_
) {
760 PendingTriggerData pending_trigger_data
=
761 bmd_iterator
.second
->GetPendingTriggerData();
762 bmd_iterator
.second
->ClearPendingTriggerData();
763 for (const auto& trigger_data_iterator
: pending_trigger_data
) {
764 Profile
* profile
= bmd_iterator
.first
;
765 BackgroundTrigger
* trigger
= trigger_data_iterator
.first
;
766 bool should_notify_user
= trigger_data_iterator
.second
;
767 RegisterTrigger(profile
, trigger
, should_notify_user
);
771 EnableLaunchOnStartup(true);
775 void BackgroundModeManager::DisableBackgroundMode() {
776 DCHECK(!IsBackgroundModePrefEnabled());
777 // If background mode is currently enabled, turn it off.
778 if (in_background_mode_
) {
780 EnableLaunchOnStartup(false);
784 void BackgroundModeManager::SuspendBackgroundMode() {
785 background_mode_suspended_
= true;
786 UpdateKeepAliveAndTrayIcon();
789 void BackgroundModeManager::ResumeBackgroundMode() {
790 background_mode_suspended_
= false;
791 UpdateKeepAliveAndTrayIcon();
794 void BackgroundModeManager::UpdateKeepAliveAndTrayIcon() {
795 if (in_background_mode_
&& !background_mode_suspended_
) {
796 if (!keeping_alive_
) {
797 keeping_alive_
= true;
798 chrome::IncrementKeepAliveCount();
801 browser_watcher::ExitFunnel::RecordSingleEvent(
802 chrome::kBrowserExitCodesRegistryPath
, L
"BackgroundOn");
805 CreateStatusTrayIcon();
809 RemoveStatusTrayIcon();
810 if (keeping_alive_
) {
811 keeping_alive_
= false;
812 chrome::DecrementKeepAliveCount();
815 browser_watcher::ExitFunnel::RecordSingleEvent(
816 chrome::kBrowserExitCodesRegistryPath
, L
"BackgroundOff");
821 void BackgroundModeManager::OnBrowserAdded(Browser
* browser
) {
822 ResumeBackgroundMode();
825 void BackgroundModeManager::OnClientsChanged(
827 const std::vector
<base::string16
>& new_client_names
) {
828 DCHECK(IsBackgroundModePrefEnabled());
830 // Update the profile cache with the fact whether background clients are
831 // running for this profile.
832 size_t profile_index
=
833 profile_cache_
->GetIndexOfProfileWithPath(profile
->GetPath());
834 if (profile_index
!= std::string::npos
) {
835 profile_cache_
->SetBackgroundStatusOfProfileAtIndex(
836 profile_index
, GetBackgroundClientCountForProfile(profile
) != 0);
839 if (!ShouldBeInBackgroundMode()) {
840 // We've uninstalled our last background client, make sure we exit
841 // background mode and no longer launch on startup.
842 EnableLaunchOnStartup(false);
845 // We have at least one background client - make sure we're in background
847 if (!in_background_mode_
) {
848 // We're entering background mode - make sure we have launch-on-startup
849 // enabled. On Mac, the platform-specific code tracks whether the user
850 // has deleted a login item in the past, and if so, no login item will
851 // be created (to avoid overriding the specific user action).
852 EnableLaunchOnStartup(true);
853 StartBackgroundMode();
856 // List of clients changed so update the UI.
857 UpdateStatusTrayIconContextMenu();
859 // Notify the user about any new clients.
860 for (const auto& name
: new_client_names
)
861 OnBackgroundClientInstalled(name
);
865 int BackgroundModeManager::GetBackgroundClientCount() const {
867 // Walk the BackgroundModeData for all profiles and count the number of
869 for (const auto& it
: background_mode_data_
)
870 count
+= it
.second
->GetBackgroundClientCount();
875 int BackgroundModeManager::GetBackgroundClientCountForProfile(
876 Profile
* const profile
) const {
877 BackgroundModeManager::BackgroundModeData
* bmd
=
878 GetBackgroundModeData(profile
);
881 return bmd
->GetBackgroundClientCount();
884 int BackgroundModeManager::GetPendingTriggerCount() const {
886 for (const auto& it
: background_mode_data_
)
887 count
+= it
.second
->GetPendingTriggerCount();
891 bool BackgroundModeManager::ShouldBeInBackgroundMode() const {
892 return IsBackgroundModePrefEnabled() &&
893 (GetBackgroundClientCount() > 0 || GetPendingTriggerCount() > 0 ||
894 keep_alive_for_test_
);
897 void BackgroundModeManager::OnBackgroundClientInstalled(
898 const base::string16
& name
) {
899 // Background mode is disabled - don't do anything.
900 if (!IsBackgroundModePrefEnabled())
903 // Ensure we have a tray icon (needed so we can display the app-installed
904 // notification below).
905 EnableBackgroundMode();
906 ResumeBackgroundMode();
908 // Notify the user that a background client has been installed.
909 DisplayClientInstalledNotification(name
);
912 void BackgroundModeManager::CreateStatusTrayIcon() {
913 // Only need status icons on windows/linux. ChromeOS doesn't allow exiting
914 // Chrome and Mac can use the dock icon instead.
916 // Since there are multiple profiles which share the status tray, we now
917 // use the browser process to keep track of it.
918 #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
920 status_tray_
= g_browser_process
->status_tray();
923 // If the platform doesn't support status icons, or we've already created
924 // our status icon, just return.
925 if (!status_tray_
|| status_icon_
)
928 gfx::ImageSkia
* image_skia
= ui::ResourceBundle::GetSharedInstance().
929 GetImageSkiaNamed(IDR_STATUS_TRAY_ICON
);
931 status_icon_
= status_tray_
->CreateStatusIcon(
932 StatusTray::BACKGROUND_MODE_ICON
,
934 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME
));
937 UpdateStatusTrayIconContextMenu();
940 void BackgroundModeManager::UpdateStatusTrayIconContextMenu() {
941 // Ensure we have a tray icon if appropriate.
942 UpdateKeepAliveAndTrayIcon();
944 // If we don't have a status icon or one could not be created succesfully,
945 // then no need to continue the update.
949 // We should only get here if we have a profile loaded, or if we're running
951 if (background_mode_data_
.empty()) {
952 DCHECK(keep_alive_for_test_
);
956 command_id_handler_vector_
.clear();
959 scoped_ptr
<StatusIconMenuModel
> menu(new StatusIconMenuModel(this));
960 menu
->AddItem(IDC_ABOUT
, l10n_util::GetStringUTF16(IDS_ABOUT
));
961 menu
->AddItemWithStringId(IDC_TASK_MANAGER
, IDS_TASK_MANAGER
);
962 menu
->AddSeparator(ui::NORMAL_SEPARATOR
);
964 // If there are multiple profiles they each get a submenu.
965 if (profile_cache_
->GetNumberOfProfiles() > 1) {
966 std::vector
<BackgroundModeData
*> bmd_vector
;
967 for (const auto& it
: background_mode_data_
)
968 bmd_vector
.push_back(it
.second
.get());
969 std::sort(bmd_vector
.begin(), bmd_vector
.end(),
970 &BackgroundModeData::BackgroundModeDataCompare
);
971 int profiles_using_background_mode
= 0;
972 for (const auto& bmd
: bmd_vector
) {
973 // We should only display the profile in the status icon if it has at
974 // least one background app.
975 if (bmd
->GetBackgroundClientCount() > 0) {
976 StatusIconMenuModel
* submenu
= new StatusIconMenuModel(bmd
);
977 // The submenu constructor caller owns the lifetime of the submenu.
978 // The containing menu does not handle the lifetime.
979 submenus
.push_back(submenu
);
980 bmd
->BuildProfileMenu(submenu
, menu
.get());
981 profiles_using_background_mode
++;
984 // We should only be displaying the status tray icon if there is at least
985 // one profile using background mode.
986 DCHECK_GT(profiles_using_background_mode
, 0);
988 // We should only have one profile in the cache if we are not
989 // using multi-profiles. If keep_alive_for_test_ is set, then we may not
990 // have any profiles in the cache.
991 DCHECK(profile_cache_
->GetNumberOfProfiles() == size_t(1) ||
992 keep_alive_for_test_
);
993 background_mode_data_
.begin()->second
->BuildProfileMenu(menu
.get(), NULL
);
996 menu
->AddSeparator(ui::NORMAL_SEPARATOR
);
997 menu
->AddCheckItemWithStringId(
998 IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND
,
999 IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND
);
1000 menu
->SetCommandIdChecked(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND
,
1003 PrefService
* service
= g_browser_process
->local_state();
1006 service
->IsUserModifiablePreference(prefs::kBackgroundModeEnabled
);
1007 menu
->SetCommandIdEnabled(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND
,
1010 menu
->AddItemWithStringId(IDC_EXIT
, IDS_EXIT
);
1012 context_menu_
= menu
.get();
1013 status_icon_
->SetContextMenu(menu
.Pass());
1016 void BackgroundModeManager::RemoveStatusTrayIcon() {
1018 status_tray_
->RemoveStatusIcon(status_icon_
);
1019 status_icon_
= NULL
;
1020 context_menu_
= NULL
;
1023 BackgroundModeManager::BackgroundModeData
*
1024 BackgroundModeManager::GetBackgroundModeData(Profile
* const profile
) const {
1025 // Profiles are shut down and destroyed asynchronously after
1026 // OnProfileWillBeRemoved is called, so we may have dropped anything
1027 // associated with the profile already.
1028 if (background_mode_data_
.find(profile
) == background_mode_data_
.end())
1030 return background_mode_data_
.find(profile
)->second
.get();
1033 BackgroundModeManager::BackgroundModeInfoMap::iterator
1034 BackgroundModeManager::GetBackgroundModeIterator(
1035 const base::string16
& profile_name
) {
1036 BackgroundModeInfoMap::iterator profile_it
=
1037 background_mode_data_
.end();
1038 for (BackgroundModeInfoMap::iterator it
=
1039 background_mode_data_
.begin();
1040 it
!= background_mode_data_
.end();
1042 if (it
->second
->name() == profile_name
) {
1049 bool BackgroundModeManager::IsBackgroundModePrefEnabled() const {
1050 PrefService
* service
= g_browser_process
->local_state();
1052 return service
->GetBoolean(prefs::kBackgroundModeEnabled
);