Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / background / background_mode_manager.cc
blob167cd2b764e99e255a7631244f039ec5828ca418
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"
7 #include <algorithm>
8 #include <string>
9 #include <vector>
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"
64 #if defined(OS_WIN)
65 #include "components/browser_watcher/exit_funnel_win.h"
66 #endif
68 using base::UserMetricsAction;
69 using extensions::Extension;
70 using extensions::UpdatedExtensionPermissionsInfo;
72 namespace {
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 {
78 AUTO_LAUNCH_NONE = 0,
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;
94 } else {
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.
112 enum MenuItem {
113 MENU_ITEM_ABOUT = 0,
114 MENU_ITEM_TASK_MANAGER = 1,
115 MENU_ITEM_BACKGROUND_CLIENT = 2,
116 MENU_ITEM_KEEP_RUNNING = 3,
117 MENU_ITEM_EXIT = 4,
118 MENU_ITEM_NUM_STATES
121 void RecordMenuItemClick(MenuItem item) {
122 UMA_HISTOGRAM_ENUMERATION("BackgroundMode.MenuItemClick", item,
123 MENU_ITEM_NUM_STATES);
125 } // namespace
127 BackgroundModeManager::BackgroundModeData::BackgroundModeData(
128 Profile* profile,
129 CommandIdHandlerVector* command_id_handler_vector)
130 : applications_(new BackgroundApplicationListModel(profile)),
131 profile_(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(
141 int command_id,
142 int event_flags) {
143 switch (command_id) {
144 case IDC_MinimumLabelValue:
145 // Do nothing. This is just a label.
146 break;
147 default:
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();
151 break;
155 Browser* BackgroundModeManager::BackgroundModeData::GetBrowserWindow() {
156 return BackgroundModeManager::GetBrowserWindowForProfile(profile_);
159 int BackgroundModeManager::BackgroundModeData::GetBackgroundClientCount()
160 const {
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);
173 } else {
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));
185 if (icon)
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) {
198 GURL options_page =
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);
215 if (icon)
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() {
234 return 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 (extensions::ExtensionList::const_iterator it = applications_->begin();
243 it != applications_->end(); ++it) {
244 const extensions::ExtensionId& id = (*it)->id();
245 if (current_extensions_.count(id) == 0) {
246 // Not found in our set yet - add it and maybe return as a previously
247 // unseen extension.
248 current_extensions_.insert(id);
249 // If this application has been newly loaded after the initial startup,
250 // notify the user.
251 if (applications_->is_ready()) {
252 const extensions::Extension* extension = (*it).get();
253 new_apps.insert(extension);
257 return new_apps;
260 // static
261 bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare(
262 const BackgroundModeData* bmd1,
263 const BackgroundModeData* bmd2) {
264 return bmd1->name_ < bmd2->name_;
267 void BackgroundModeManager::BackgroundModeData::AddPendingTrigger(
268 BackgroundTrigger* trigger, bool should_notify_user) {
269 if (HasTrigger(trigger))
270 return;
271 pending_trigger_data_[trigger] = should_notify_user;
274 BackgroundModeManager::PendingTriggerData
275 BackgroundModeManager::BackgroundModeData::GetPendingTriggerData() const {
276 return pending_trigger_data_;
279 void BackgroundModeManager::BackgroundModeData::ClearPendingTriggerData() {
280 pending_trigger_data_.clear();
283 int BackgroundModeManager::BackgroundModeData::GetPendingTriggerCount() const {
284 return pending_trigger_data_.size();
287 void BackgroundModeManager::BackgroundModeData::RegisterTrigger(
288 BackgroundTrigger* trigger) {
289 if (HasTrigger(trigger))
290 return;
291 registered_triggers_.insert(trigger);
294 void BackgroundModeManager::BackgroundModeData::UnregisterTrigger(
295 BackgroundTrigger* trigger) {
296 registered_triggers_.erase(trigger);
297 pending_trigger_data_.erase(trigger);
300 bool BackgroundModeManager::BackgroundModeData::HasTrigger(
301 BackgroundTrigger* trigger) {
302 if (registered_triggers_.find(trigger) != registered_triggers_.end())
303 return true;
304 return pending_trigger_data_.find(trigger) != pending_trigger_data_.end();
307 ///////////////////////////////////////////////////////////////////////////////
308 // BackgroundModeManager, public
309 BackgroundModeManager::BackgroundModeManager(
310 const base::CommandLine& command_line,
311 ProfileInfoCache* profile_cache)
312 : profile_cache_(profile_cache),
313 status_tray_(NULL),
314 status_icon_(NULL),
315 context_menu_(NULL),
316 in_background_mode_(false),
317 keep_alive_for_startup_(false),
318 keep_alive_for_test_(false),
319 background_mode_suspended_(false),
320 keeping_alive_(false),
321 weak_factory_(this) {
322 // We should never start up if there is no browser process or if we are
323 // currently quitting.
324 CHECK(g_browser_process != NULL);
325 CHECK(!browser_shutdown::IsTryingToQuit());
327 // Add self as an observer for the profile info cache so we know when profiles
328 // are deleted and their names change.
329 profile_cache_->AddObserver(this);
331 RecordAutoLaunchState(command_line);
332 UMA_HISTOGRAM_BOOLEAN("BackgroundMode.OnStartup.IsBackgroundModePrefEnabled",
333 IsBackgroundModePrefEnabled());
335 // Listen for the background mode preference changing.
336 if (g_browser_process->local_state()) { // Skip for unit tests
337 pref_registrar_.Init(g_browser_process->local_state());
338 pref_registrar_.Add(
339 prefs::kBackgroundModeEnabled,
340 base::Bind(&BackgroundModeManager::OnBackgroundModeEnabledPrefChanged,
341 base::Unretained(this)));
344 // Keep the browser alive until extensions are done loading - this is needed
345 // by the --no-startup-window flag. We want to stay alive until we load
346 // extensions, at which point we should either run in background mode (if
347 // there are background apps) or exit if there are none.
348 if (command_line.HasSwitch(switches::kNoStartupWindow)) {
349 keep_alive_for_startup_ = true;
350 chrome::IncrementKeepAliveCount();
351 } else {
352 // Otherwise, start with background mode suspended in case we're launching
353 // in a mode that doesn't open a browser window. It will be resumed when the
354 // first browser window is opened.
355 SuspendBackgroundMode();
358 // If the -keep-alive-for-test flag is passed, then always keep chrome running
359 // in the background until the user explicitly terminates it.
360 if (command_line.HasSwitch(switches::kKeepAliveForTest))
361 keep_alive_for_test_ = true;
363 if (ShouldBeInBackgroundMode())
364 StartBackgroundMode();
366 // Listen for the application shutting down so we can decrement our KeepAlive
367 // count.
368 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
369 content::NotificationService::AllSources());
370 BrowserList::AddObserver(this);
373 BackgroundModeManager::~BackgroundModeManager() {
374 // Remove ourselves from the application observer list (only needed by unit
375 // tests since APP_TERMINATING is what does this in a real running system).
376 for (BackgroundModeInfoMap::iterator it =
377 background_mode_data_.begin();
378 it != background_mode_data_.end();
379 ++it) {
380 it->second->applications_->RemoveObserver(this);
382 BrowserList::RemoveObserver(this);
384 // We're going away, so exit background mode (does nothing if we aren't in
385 // background mode currently). This is primarily needed for unit tests,
386 // because in an actual running system we'd get an APP_TERMINATING
387 // notification before being destroyed.
388 EndBackgroundMode();
391 // static
392 void BackgroundModeManager::RegisterPrefs(PrefRegistrySimple* registry) {
393 #if defined(OS_MACOSX)
394 registry->RegisterBooleanPref(prefs::kUserRemovedLoginItem, false);
395 registry->RegisterBooleanPref(prefs::kChromeCreatedLoginItem, false);
396 registry->RegisterBooleanPref(prefs::kMigratedLoginItemPref, false);
397 #endif
398 registry->RegisterBooleanPref(prefs::kBackgroundModeEnabled, true);
401 void BackgroundModeManager::RegisterProfile(Profile* profile) {
402 // We don't want to register multiple times for one profile.
403 DCHECK(background_mode_data_.find(profile) == background_mode_data_.end());
404 BackgroundModeInfo bmd(
405 new BackgroundModeData(profile, &command_id_handler_vector_));
406 background_mode_data_[profile] = bmd;
408 // Initially set the name for this background mode data.
409 size_t index = profile_cache_->GetIndexOfProfileWithPath(profile->GetPath());
410 base::string16 name = l10n_util::GetStringUTF16(IDS_PROFILES_DEFAULT_NAME);
411 if (index != std::string::npos)
412 name = profile_cache_->GetNameOfProfileAtIndex(index);
413 bmd->SetName(name);
415 // Check for the presence of background apps after all extensions have been
416 // loaded, to handle the case where an extension has been manually removed
417 // while Chrome was not running.
418 extensions::ExtensionSystem::Get(profile)->ready().Post(
419 FROM_HERE,
420 base::Bind(&BackgroundModeManager::OnExtensionsReady,
421 weak_factory_.GetWeakPtr(), profile));
423 bmd->applications_->AddObserver(this);
425 // If we're adding a new profile and running in multi-profile mode, this new
426 // profile should be added to the status icon if one currently exists.
427 if (in_background_mode_ && status_icon_)
428 UpdateStatusTrayIconContextMenu();
431 // static
432 void BackgroundModeManager::LaunchBackgroundApplication(
433 Profile* profile,
434 const Extension* extension) {
435 OpenApplication(AppLaunchParams(profile, extension, NEW_FOREGROUND_TAB,
436 extensions::SOURCE_BACKGROUND));
439 // static
440 Browser* BackgroundModeManager::GetBrowserWindowForProfile(Profile* profile) {
441 chrome::HostDesktopType host_desktop_type = chrome::GetActiveDesktop();
442 Browser* browser =
443 chrome::FindLastActiveWithProfile(profile, host_desktop_type);
444 return browser ? browser
445 : chrome::OpenEmptyWindow(profile, host_desktop_type);
448 bool BackgroundModeManager::IsBackgroundModeActive() {
449 return in_background_mode_;
452 int BackgroundModeManager::NumberOfBackgroundModeData() {
453 return background_mode_data_.size();
456 void BackgroundModeManager::RegisterTrigger(Profile* profile,
457 BackgroundTrigger* trigger,
458 bool should_notify_user) {
459 BackgroundModeManager::BackgroundModeData* bmd =
460 GetBackgroundModeData(profile);
461 if (!bmd)
462 return;
464 // Only proceed if we don't have this trigger yet.
465 if (bmd->HasTrigger(trigger))
466 return;
468 // If the background pref is disabled, store it as a pending trigger that may
469 // be registered later if the pref gets enabled.
470 if (!IsBackgroundModePrefEnabled()) {
471 bmd->AddPendingTrigger(trigger, should_notify_user);
472 return;
475 bmd->RegisterTrigger(trigger);
476 std::vector<base::string16> new_client_names;
477 if (should_notify_user)
478 new_client_names.push_back(trigger->GetName());
479 OnClientsChanged(profile, new_client_names);
482 void BackgroundModeManager::UnregisterTrigger(Profile* profile,
483 BackgroundTrigger* trigger) {
484 if (!IsBackgroundModePrefEnabled())
485 return;
487 BackgroundModeManager::BackgroundModeData* bmd =
488 GetBackgroundModeData(profile);
489 if (!bmd)
490 return;
492 // Only proceed if this is a known trigger.
493 if (!bmd->HasTrigger(trigger))
494 return;
495 bmd->UnregisterTrigger(trigger);
497 std::vector<base::string16> new_client_names;
498 OnClientsChanged(profile, new_client_names);
501 ///////////////////////////////////////////////////////////////////////////////
502 // BackgroundModeManager, content::NotificationObserver overrides
503 void BackgroundModeManager::Observe(
504 int type,
505 const content::NotificationSource& source,
506 const content::NotificationDetails& details) {
507 switch (type) {
508 case chrome::NOTIFICATION_APP_TERMINATING:
509 // Make sure we aren't still keeping the app alive (only happens if we
510 // don't receive an EXTENSIONS_READY notification for some reason).
511 DecrementKeepAliveCountForStartup();
512 // Performing an explicit shutdown, so exit background mode (does nothing
513 // if we aren't in background mode currently).
514 EndBackgroundMode();
515 // Shutting down, so don't listen for any more notifications so we don't
516 // try to re-enter/exit background mode again.
517 registrar_.RemoveAll();
518 for (BackgroundModeInfoMap::iterator it =
519 background_mode_data_.begin();
520 it != background_mode_data_.end();
521 ++it) {
522 it->second->applications_->RemoveObserver(this);
524 break;
525 default:
526 NOTREACHED();
527 break;
531 void BackgroundModeManager::OnExtensionsReady(Profile* profile) {
532 BackgroundModeManager::BackgroundModeData* bmd =
533 GetBackgroundModeData(profile);
534 if (bmd) {
535 UMA_HISTOGRAM_COUNTS_100("BackgroundMode.BackgroundApplicationsCount",
536 bmd->applications_->size());
538 // Extensions are loaded, so we don't need to manually keep the browser
539 // process alive any more when running in no-startup-window mode.
540 DecrementKeepAliveCountForStartup();
543 void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() {
544 bool enabled = IsBackgroundModePrefEnabled();
545 UMA_HISTOGRAM_BOOLEAN("BackgroundMode.BackgroundModeEnabledPrefChanged",
546 enabled);
547 if (enabled)
548 EnableBackgroundMode();
549 else
550 DisableBackgroundMode();
553 ///////////////////////////////////////////////////////////////////////////////
554 // BackgroundModeManager, BackgroundApplicationListModel::Observer overrides
555 void BackgroundModeManager::OnApplicationDataChanged(
556 const Extension* extension, Profile* profile) {
557 UpdateStatusTrayIconContextMenu();
560 void BackgroundModeManager::OnApplicationListChanged(Profile* profile) {
561 if (!IsBackgroundModePrefEnabled())
562 return;
564 BackgroundModeManager::BackgroundModeData* bmd =
565 GetBackgroundModeData(profile);
566 if (!bmd)
567 return;
569 // Get the new apps (if any) and process them.
570 std::set<const extensions::Extension*> new_apps = bmd->GetNewBackgroundApps();
571 std::vector<base::string16> new_names;
572 for (const auto& app : new_apps)
573 new_names.push_back(base::UTF8ToUTF16(app->name()));
574 OnClientsChanged(profile, new_names);
577 ///////////////////////////////////////////////////////////////////////////////
578 // BackgroundModeManager, ProfileInfoCacheObserver overrides
579 void BackgroundModeManager::OnProfileAdded(const base::FilePath& profile_path) {
580 ProfileInfoCache& cache =
581 g_browser_process->profile_manager()->GetProfileInfoCache();
582 base::string16 profile_name = cache.GetNameOfProfileAtIndex(
583 cache.GetIndexOfProfileWithPath(profile_path));
584 // At this point, the profile should be registered with the background mode
585 // manager, but when it's actually added to the cache is when its name is
586 // set so we need up to update that with the background_mode_data.
587 for (BackgroundModeInfoMap::const_iterator it =
588 background_mode_data_.begin();
589 it != background_mode_data_.end();
590 ++it) {
591 if (it->first->GetPath() == profile_path) {
592 it->second->SetName(profile_name);
593 UpdateStatusTrayIconContextMenu();
594 return;
599 void BackgroundModeManager::OnProfileWillBeRemoved(
600 const base::FilePath& profile_path) {
601 ProfileInfoCache& cache =
602 g_browser_process->profile_manager()->GetProfileInfoCache();
603 base::string16 profile_name = cache.GetNameOfProfileAtIndex(
604 cache.GetIndexOfProfileWithPath(profile_path));
605 // Remove the profile from our map of profiles.
606 BackgroundModeInfoMap::iterator it =
607 GetBackgroundModeIterator(profile_name);
608 // If a profile isn't running a background app, it may not be in the map.
609 if (it != background_mode_data_.end()) {
610 it->second->applications_->RemoveObserver(this);
611 background_mode_data_.erase(it);
612 // If there are no background mode profiles any longer, then turn off
613 // background mode.
614 if (!ShouldBeInBackgroundMode()) {
615 EnableLaunchOnStartup(false);
616 EndBackgroundMode();
618 UpdateStatusTrayIconContextMenu();
622 void BackgroundModeManager::OnProfileNameChanged(
623 const base::FilePath& profile_path,
624 const base::string16& old_profile_name) {
625 ProfileInfoCache& cache =
626 g_browser_process->profile_manager()->GetProfileInfoCache();
627 base::string16 new_profile_name = cache.GetNameOfProfileAtIndex(
628 cache.GetIndexOfProfileWithPath(profile_path));
629 BackgroundModeInfoMap::const_iterator it =
630 GetBackgroundModeIterator(old_profile_name);
631 // We check that the returned iterator is valid due to unittests, but really
632 // this should only be called on profiles already known by the background
633 // mode manager.
634 if (it != background_mode_data_.end()) {
635 it->second->SetName(new_profile_name);
636 UpdateStatusTrayIconContextMenu();
640 BackgroundModeManager::BackgroundModeData*
641 BackgroundModeManager::GetBackgroundModeDataForLastProfile() const {
642 Profile* most_recent_profile = g_browser_process->profile_manager()->
643 GetLastUsedProfileAllowedByPolicy();
644 BackgroundModeInfoMap::const_iterator profile_background_data =
645 background_mode_data_.find(most_recent_profile);
647 if (profile_background_data == background_mode_data_.end())
648 return NULL;
650 // Do not permit a locked profile to be used to open a browser.
651 ProfileInfoCache& cache =
652 g_browser_process->profile_manager()->GetProfileInfoCache();
653 if (cache.ProfileIsSigninRequiredAtIndex(cache.GetIndexOfProfileWithPath(
654 profile_background_data->first->GetPath())))
655 return NULL;
657 return profile_background_data->second.get();
660 ///////////////////////////////////////////////////////////////////////////////
661 // BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
662 void BackgroundModeManager::ExecuteCommand(int command_id, int event_flags) {
663 BackgroundModeData* bmd = GetBackgroundModeDataForLastProfile();
664 switch (command_id) {
665 case IDC_ABOUT:
666 RecordMenuItemClick(MENU_ITEM_ABOUT);
667 if (bmd) {
668 chrome::ShowAboutChrome(bmd->GetBrowserWindow());
669 } else {
670 UserManager::Show(base::FilePath(),
671 profiles::USER_MANAGER_NO_TUTORIAL,
672 profiles::USER_MANAGER_SELECT_PROFILE_ABOUT_CHROME);
674 break;
675 case IDC_TASK_MANAGER:
676 RecordMenuItemClick(MENU_ITEM_TASK_MANAGER);
677 if (bmd) {
678 chrome::OpenTaskManager(bmd->GetBrowserWindow());
679 } else {
680 UserManager::Show(base::FilePath(),
681 profiles::USER_MANAGER_NO_TUTORIAL,
682 profiles::USER_MANAGER_SELECT_PROFILE_TASK_MANAGER);
684 break;
685 case IDC_EXIT:
686 RecordMenuItemClick(MENU_ITEM_EXIT);
687 #if defined(OS_WIN)
688 browser_watcher::ExitFunnel::RecordSingleEvent(
689 chrome::kBrowserExitCodesRegistryPath, L"TraybarExit");
690 #endif
691 content::RecordAction(UserMetricsAction("Exit"));
692 chrome::CloseAllBrowsers();
693 break;
694 case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND: {
695 // Background mode must already be enabled (as otherwise this menu would
696 // not be visible).
697 DCHECK(IsBackgroundModePrefEnabled());
698 DCHECK(chrome::WillKeepAlive());
700 RecordMenuItemClick(MENU_ITEM_KEEP_RUNNING);
702 // Set the background mode pref to "disabled" - the resulting notification
703 // will result in a call to DisableBackgroundMode().
704 PrefService* service = g_browser_process->local_state();
705 DCHECK(service);
706 service->SetBoolean(prefs::kBackgroundModeEnabled, false);
707 break;
709 default:
710 if (bmd) {
711 bmd->ExecuteCommand(command_id, event_flags);
712 } else {
713 UserManager::Show(base::FilePath(),
714 profiles::USER_MANAGER_NO_TUTORIAL,
715 profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
717 break;
722 ///////////////////////////////////////////////////////////////////////////////
723 // BackgroundModeManager, private
724 void BackgroundModeManager::DecrementKeepAliveCountForStartup() {
725 if (keep_alive_for_startup_) {
726 keep_alive_for_startup_ = false;
727 // We call this via the message queue to make sure we don't try to end
728 // keep-alive (which can shutdown Chrome) before the message loop has
729 // started.
730 base::ThreadTaskRunnerHandle::Get()->PostTask(
731 FROM_HERE, base::Bind(&chrome::DecrementKeepAliveCount));
735 void BackgroundModeManager::StartBackgroundMode() {
736 DCHECK(ShouldBeInBackgroundMode());
737 // Don't bother putting ourselves in background mode if we're already there
738 // or if background mode is disabled.
739 if (in_background_mode_)
740 return;
742 // Mark ourselves as running in background mode.
743 in_background_mode_ = true;
745 UpdateKeepAliveAndTrayIcon();
747 content::NotificationService::current()->Notify(
748 chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
749 content::Source<BackgroundModeManager>(this),
750 content::Details<bool>(&in_background_mode_));
753 void BackgroundModeManager::EndBackgroundMode() {
754 if (!in_background_mode_)
755 return;
756 in_background_mode_ = false;
758 UpdateKeepAliveAndTrayIcon();
760 content::NotificationService::current()->Notify(
761 chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
762 content::Source<BackgroundModeManager>(this),
763 content::Details<bool>(&in_background_mode_));
766 void BackgroundModeManager::EnableBackgroundMode() {
767 DCHECK(IsBackgroundModePrefEnabled());
768 // If background mode should be enabled, but isn't, turn it on.
769 if (!in_background_mode_ && ShouldBeInBackgroundMode()) {
770 StartBackgroundMode();
772 // Register pending triggers.
773 for (const auto& bmd_iterator : background_mode_data_) {
774 PendingTriggerData pending_trigger_data =
775 bmd_iterator.second->GetPendingTriggerData();
776 bmd_iterator.second->ClearPendingTriggerData();
777 for (const auto& trigger_data_iterator : pending_trigger_data) {
778 Profile* profile = bmd_iterator.first;
779 BackgroundTrigger* trigger = trigger_data_iterator.first;
780 bool should_notify_user = trigger_data_iterator.second;
781 RegisterTrigger(profile, trigger, should_notify_user);
785 EnableLaunchOnStartup(true);
789 void BackgroundModeManager::DisableBackgroundMode() {
790 DCHECK(!IsBackgroundModePrefEnabled());
791 // If background mode is currently enabled, turn it off.
792 if (in_background_mode_) {
793 EndBackgroundMode();
794 EnableLaunchOnStartup(false);
798 void BackgroundModeManager::SuspendBackgroundMode() {
799 background_mode_suspended_ = true;
800 UpdateKeepAliveAndTrayIcon();
803 void BackgroundModeManager::ResumeBackgroundMode() {
804 background_mode_suspended_ = false;
805 UpdateKeepAliveAndTrayIcon();
808 void BackgroundModeManager::UpdateKeepAliveAndTrayIcon() {
809 if (in_background_mode_ && !background_mode_suspended_) {
810 if (!keeping_alive_) {
811 keeping_alive_ = true;
812 chrome::IncrementKeepAliveCount();
814 #if defined(OS_WIN)
815 browser_watcher::ExitFunnel::RecordSingleEvent(
816 chrome::kBrowserExitCodesRegistryPath, L"BackgroundOn");
817 #endif
819 CreateStatusTrayIcon();
820 return;
823 RemoveStatusTrayIcon();
824 if (keeping_alive_) {
825 keeping_alive_ = false;
826 chrome::DecrementKeepAliveCount();
828 #if defined(OS_WIN)
829 browser_watcher::ExitFunnel::RecordSingleEvent(
830 chrome::kBrowserExitCodesRegistryPath, L"BackgroundOff");
831 #endif
835 void BackgroundModeManager::OnBrowserAdded(Browser* browser) {
836 ResumeBackgroundMode();
839 void BackgroundModeManager::OnClientsChanged(
840 Profile* profile,
841 const std::vector<base::string16>& new_client_names) {
842 DCHECK(IsBackgroundModePrefEnabled());
844 // Update the profile cache with the fact whether background clients are
845 // running for this profile.
846 size_t profile_index =
847 profile_cache_->GetIndexOfProfileWithPath(profile->GetPath());
848 if (profile_index != std::string::npos) {
849 profile_cache_->SetBackgroundStatusOfProfileAtIndex(
850 profile_index, GetBackgroundClientCountForProfile(profile) != 0);
853 if (!ShouldBeInBackgroundMode()) {
854 // We've uninstalled our last background client, make sure we exit
855 // background mode and no longer launch on startup.
856 EnableLaunchOnStartup(false);
857 EndBackgroundMode();
858 } else {
859 // We have at least one background client - make sure we're in background
860 // mode.
861 if (!in_background_mode_) {
862 // We're entering background mode - make sure we have launch-on-startup
863 // enabled. On Mac, the platform-specific code tracks whether the user
864 // has deleted a login item in the past, and if so, no login item will
865 // be created (to avoid overriding the specific user action).
866 EnableLaunchOnStartup(true);
867 StartBackgroundMode();
870 // List of clients changed so update the UI.
871 UpdateStatusTrayIconContextMenu();
873 // Notify the user about any new clients.
874 for (const auto& name : new_client_names)
875 OnBackgroundClientInstalled(name);
879 int BackgroundModeManager::GetBackgroundClientCount() const {
880 int count = 0;
881 // Walk the BackgroundModeData for all profiles and count the number of
882 // clients.
883 for (BackgroundModeInfoMap::const_iterator it =
884 background_mode_data_.begin();
885 it != background_mode_data_.end();
886 ++it) {
887 count += it->second->GetBackgroundClientCount();
889 DCHECK(count >= 0);
890 return count;
893 int BackgroundModeManager::GetBackgroundClientCountForProfile(
894 Profile* const profile) const {
895 BackgroundModeManager::BackgroundModeData* bmd =
896 GetBackgroundModeData(profile);
897 if (!bmd)
898 return 0;
899 return bmd->GetBackgroundClientCount();
902 int BackgroundModeManager::GetPendingTriggerCount() const {
903 int count = 0;
904 for (const auto& it : background_mode_data_)
905 count += it.second->GetPendingTriggerCount();
906 return count;
909 bool BackgroundModeManager::ShouldBeInBackgroundMode() const {
910 return IsBackgroundModePrefEnabled() &&
911 (GetBackgroundClientCount() > 0 || GetPendingTriggerCount() > 0 ||
912 keep_alive_for_test_);
915 void BackgroundModeManager::OnBackgroundClientInstalled(
916 const base::string16& name) {
917 // Background mode is disabled - don't do anything.
918 if (!IsBackgroundModePrefEnabled())
919 return;
921 // Ensure we have a tray icon (needed so we can display the app-installed
922 // notification below).
923 EnableBackgroundMode();
924 ResumeBackgroundMode();
926 // Notify the user that a background client has been installed.
927 DisplayClientInstalledNotification(name);
930 void BackgroundModeManager::CreateStatusTrayIcon() {
931 // Only need status icons on windows/linux. ChromeOS doesn't allow exiting
932 // Chrome and Mac can use the dock icon instead.
934 // Since there are multiple profiles which share the status tray, we now
935 // use the browser process to keep track of it.
936 #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
937 if (!status_tray_)
938 status_tray_ = g_browser_process->status_tray();
939 #endif
941 // If the platform doesn't support status icons, or we've already created
942 // our status icon, just return.
943 if (!status_tray_ || status_icon_)
944 return;
946 // TODO(rlp): Status tray icon should have submenus for each profile.
947 gfx::ImageSkia* image_skia = ui::ResourceBundle::GetSharedInstance().
948 GetImageSkiaNamed(IDR_STATUS_TRAY_ICON);
950 status_icon_ = status_tray_->CreateStatusIcon(
951 StatusTray::BACKGROUND_MODE_ICON,
952 *image_skia,
953 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
954 if (!status_icon_)
955 return;
956 UpdateStatusTrayIconContextMenu();
959 void BackgroundModeManager::UpdateStatusTrayIconContextMenu() {
960 // Ensure we have a tray icon if appropriate.
961 UpdateKeepAliveAndTrayIcon();
963 // If we don't have a status icon or one could not be created succesfully,
964 // then no need to continue the update.
965 if (!status_icon_)
966 return;
968 // We should only get here if we have a profile loaded, or if we're running
969 // in test mode.
970 if (background_mode_data_.empty()) {
971 DCHECK(keep_alive_for_test_);
972 return;
975 // We are building a new menu. Reset the Command IDs.
976 command_id_handler_vector_.clear();
978 // Clear the submenus too since we will be creating new ones.
979 submenus.clear();
981 // TODO(rlp): Add current profile color or indicator.
982 // Create a context menu item for Chrome.
983 scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
984 // Add About item
985 menu->AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
986 menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
987 menu->AddSeparator(ui::NORMAL_SEPARATOR);
989 if (profile_cache_->GetNumberOfProfiles() > 1) {
990 std::vector<BackgroundModeData*> bmd_vector;
991 for (BackgroundModeInfoMap::iterator it =
992 background_mode_data_.begin();
993 it != background_mode_data_.end();
994 ++it) {
995 bmd_vector.push_back(it->second.get());
997 std::sort(bmd_vector.begin(), bmd_vector.end(),
998 &BackgroundModeData::BackgroundModeDataCompare);
999 int profiles_using_background_mode = 0;
1000 for (std::vector<BackgroundModeData*>::const_iterator bmd_it =
1001 bmd_vector.begin();
1002 bmd_it != bmd_vector.end();
1003 ++bmd_it) {
1004 BackgroundModeData* bmd = *bmd_it;
1005 // We should only display the profile in the status icon if it has at
1006 // least one background app.
1007 if (bmd->GetBackgroundClientCount() > 0) {
1008 StatusIconMenuModel* submenu = new StatusIconMenuModel(bmd);
1009 // The submenu constructor caller owns the lifetime of the submenu.
1010 // The containing menu does not handle the lifetime.
1011 submenus.push_back(submenu);
1012 bmd->BuildProfileMenu(submenu, menu.get());
1013 profiles_using_background_mode++;
1016 // We should only be displaying the status tray icon if there is at least
1017 // one profile using background mode.
1018 DCHECK_GT(profiles_using_background_mode, 0);
1019 } else {
1020 // We should only have one profile in the cache if we are not
1021 // using multi-profiles. If keep_alive_for_test_ is set, then we may not
1022 // have any profiles in the cache.
1023 DCHECK(profile_cache_->GetNumberOfProfiles() == size_t(1) ||
1024 keep_alive_for_test_);
1025 background_mode_data_.begin()->second->BuildProfileMenu(menu.get(), NULL);
1028 menu->AddSeparator(ui::NORMAL_SEPARATOR);
1029 menu->AddCheckItemWithStringId(
1030 IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
1031 IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND);
1032 menu->SetCommandIdChecked(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
1033 true);
1035 PrefService* service = g_browser_process->local_state();
1036 DCHECK(service);
1037 bool enabled =
1038 service->IsUserModifiablePreference(prefs::kBackgroundModeEnabled);
1039 menu->SetCommandIdEnabled(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
1040 enabled);
1042 menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT);
1044 context_menu_ = menu.get();
1045 status_icon_->SetContextMenu(menu.Pass());
1048 void BackgroundModeManager::RemoveStatusTrayIcon() {
1049 if (status_icon_)
1050 status_tray_->RemoveStatusIcon(status_icon_);
1051 status_icon_ = NULL;
1052 context_menu_ = NULL;
1055 BackgroundModeManager::BackgroundModeData*
1056 BackgroundModeManager::GetBackgroundModeData(Profile* const profile) const {
1057 // Profiles are shut down and destroyed asynchronously after
1058 // OnProfileWillBeRemoved is called, so we may have dropped anything
1059 // associated with the profile already.
1060 if (background_mode_data_.find(profile) == background_mode_data_.end())
1061 return NULL;
1062 return background_mode_data_.find(profile)->second.get();
1065 BackgroundModeManager::BackgroundModeInfoMap::iterator
1066 BackgroundModeManager::GetBackgroundModeIterator(
1067 const base::string16& profile_name) {
1068 BackgroundModeInfoMap::iterator profile_it =
1069 background_mode_data_.end();
1070 for (BackgroundModeInfoMap::iterator it =
1071 background_mode_data_.begin();
1072 it != background_mode_data_.end();
1073 ++it) {
1074 if (it->second->name() == profile_name) {
1075 profile_it = it;
1078 return profile_it;
1081 bool BackgroundModeManager::IsBackgroundModePrefEnabled() const {
1082 PrefService* service = g_browser_process->local_state();
1083 DCHECK(service);
1084 return service->GetBoolean(prefs::kBackgroundModeEnabled);