blob: 5b1d737e695f0d4eb441007e1c69d3ee455fcb48 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/background/background_mode_manager.h"
#include <stddef.h>
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "base/base_paths.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/background/background_application_list_model.h"
#include "chrome/browser/background/background_mode_optimizer.h"
#include "chrome/browser/background/background_trigger.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/status_icons/status_icon.h"
#include "chrome/browser/status_icons/status_tray.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/extensions/app_launch_params.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/browser/ui/user_manager.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/chrome_unscaled_resources.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/startup_metric_utils/browser/startup_metric_utils.h"
#include "content/public/browser/notification_service.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/options_page_info.h"
#include "extensions/common/one_shot_event.h"
#include "extensions/common/permissions/permission_set.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#if defined(OS_WIN)
#include "chrome/browser/win/app_icon.h"
#endif
using base::UserMetricsAction;
using extensions::Extension;
namespace {
// Enum for recording menu item clicks in UMA.
// NOTE: Do not renumber these as that would confuse interpretation of
// previously logged data. When making changes, also update histograms.xml.
enum MenuItem {
MENU_ITEM_ABOUT = 0,
MENU_ITEM_TASK_MANAGER = 1,
MENU_ITEM_BACKGROUND_CLIENT = 2,
MENU_ITEM_KEEP_RUNNING = 3,
MENU_ITEM_EXIT = 4,
MENU_ITEM_NUM_STATES
};
void RecordMenuItemClick(MenuItem item) {
UMA_HISTOGRAM_ENUMERATION("BackgroundMode.MenuItemClick", item,
MENU_ITEM_NUM_STATES);
}
} // namespace
// static
bool BackgroundModeManager::should_restart_in_background_ = false;
BackgroundModeManager::BackgroundModeData::BackgroundModeData(
Profile* profile,
CommandIdHandlerVector* command_id_handler_vector)
: applications_(std::make_unique<BackgroundApplicationListModel>(profile)),
profile_(profile),
command_id_handler_vector_(command_id_handler_vector) {}
BackgroundModeManager::BackgroundModeData::~BackgroundModeData() {
}
///////////////////////////////////////////////////////////////////////////////
// BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
void BackgroundModeManager::BackgroundModeData::ExecuteCommand(
int command_id,
int event_flags) {
switch (command_id) {
case IDC_MinimumLabelValue:
// Do nothing. This is just a label.
break;
default:
DCHECK(!command_id_handler_vector_->at(command_id).is_null());
RecordMenuItemClick(MENU_ITEM_BACKGROUND_CLIENT);
command_id_handler_vector_->at(command_id).Run();
break;
}
}
Browser* BackgroundModeManager::BackgroundModeData::GetBrowserWindow() {
return BackgroundModeManager::GetBrowserWindowForProfile(profile_);
}
bool BackgroundModeManager::BackgroundModeData::HasBackgroundClient() const {
return applications_->size() > 0 || !registered_triggers_.empty();
}
void BackgroundModeManager::BackgroundModeData::BuildProfileMenu(
StatusIconMenuModel* menu,
StatusIconMenuModel* containing_menu) {
if (HasBackgroundClient()) {
// Add a menu item for each application (extension).
for (const auto& application : *applications_) {
const gfx::ImageSkia* icon = applications_->GetIcon(application.get());
const std::string& name = application->name();
int command_id = command_id_handler_vector_->size();
// Check that the command ID is within the dynamic range.
DCHECK_LT(command_id, IDC_MinimumLabelValue);
command_id_handler_vector_->push_back(base::BindRepeating(
&BackgroundModeManager::LaunchBackgroundApplication, profile_,
base::RetainedRef(application)));
menu->AddItem(command_id, base::UTF8ToUTF16(name));
if (icon)
menu->SetIcon(menu->GetItemCount() - 1, gfx::Image(*icon));
// Component extensions with background that do not have an options page
// will cause this menu item to go to the extensions page with an
// absent component extension.
//
// Ideally, we would remove this item, but this conflicts with the user
// model where this menu shows the extensions with background.
//
// The compromise is to disable the item, avoiding the non-actionable
// navigate to the extensions page and preserving the user model.
if (application->location() == extensions::Manifest::COMPONENT) {
GURL options_page =
extensions::OptionsPageInfo::GetOptionsPage(application.get());
if (!options_page.is_valid())
menu->SetCommandIdEnabled(command_id, false);
}
}
// Add a menu item for each trigger.
for (auto* trigger : registered_triggers_) {
const gfx::ImageSkia* icon = trigger->GetIcon();
const base::string16& name = trigger->GetName();
int command_id = command_id_handler_vector_->size();
// Check that the command ID is within the dynamic range.
DCHECK_LT(command_id, IDC_MinimumLabelValue);
command_id_handler_vector_->push_back(base::BindRepeating(
&BackgroundTrigger::OnMenuClick, base::Unretained(trigger)));
menu->AddItem(command_id, name);
if (icon)
menu->SetIcon(menu->GetItemCount() - 1, gfx::Image(*icon));
}
} else {
// When there are no background clients, we want to display just a label
// stating that none are running.
menu->AddItemWithStringId(IDC_MinimumLabelValue,
IDS_BACKGROUND_APP_NOT_INSTALLED);
menu->SetCommandIdEnabled(IDC_MinimumLabelValue, false);
}
if (containing_menu) {
int menu_command_id = command_id_handler_vector_->size();
// Check that the command ID is within the dynamic range.
DCHECK_LT(menu_command_id, IDC_MinimumLabelValue);
command_id_handler_vector_->push_back(base::DoNothing());
containing_menu->AddSubMenu(menu_command_id, name_, menu);
}
}
void BackgroundModeManager::BackgroundModeData::SetName(
const base::string16& new_profile_name) {
name_ = new_profile_name;
}
base::string16 BackgroundModeManager::BackgroundModeData::name() {
return name_;
}
std::set<const extensions::Extension*>
BackgroundModeManager::BackgroundModeData::GetNewBackgroundApps() {
std::set<const extensions::Extension*> new_apps;
// Copy all current extensions into our list of |current_extensions_|.
for (const auto& application : *applications_) {
const extensions::ExtensionId& id = application->id();
if (current_extensions_.count(id) == 0) {
// Not found in our set yet - add it and maybe return as a previously
// unseen extension.
current_extensions_.insert(id);
// If this application has been newly loaded after the initial startup,
// notify the user.
if (applications_->is_ready())
new_apps.insert(application.get());
}
}
return new_apps;
}
// static
bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare(
const BackgroundModeData* bmd1,
const BackgroundModeData* bmd2) {
return bmd1->name_ < bmd2->name_;
}
void BackgroundModeManager::BackgroundModeData::AddPendingTrigger(
BackgroundTrigger* trigger, bool should_notify_user) {
if (HasTrigger(trigger))
return;
pending_trigger_data_[trigger] = should_notify_user;
}
BackgroundModeManager::PendingTriggerData
BackgroundModeManager::BackgroundModeData::TakePendingTriggerData() {
return std::move(pending_trigger_data_);
}
bool BackgroundModeManager::BackgroundModeData::HasPendingTrigger() const {
return !pending_trigger_data_.empty();
}
void BackgroundModeManager::BackgroundModeData::RegisterTrigger(
BackgroundTrigger* trigger) {
if (HasTrigger(trigger))
return;
registered_triggers_.insert(trigger);
}
void BackgroundModeManager::BackgroundModeData::UnregisterTrigger(
BackgroundTrigger* trigger) {
registered_triggers_.erase(trigger);
pending_trigger_data_.erase(trigger);
}
bool BackgroundModeManager::BackgroundModeData::HasTrigger(
BackgroundTrigger* trigger) {
return base::ContainsKey(registered_triggers_, trigger) ||
base::ContainsKey(pending_trigger_data_, trigger);
}
///////////////////////////////////////////////////////////////////////////////
// BackgroundModeManager, public
BackgroundModeManager::BackgroundModeManager(
const base::CommandLine& command_line,
ProfileAttributesStorage* profile_storage)
: profile_storage_(profile_storage),
task_runner_(CreateTaskRunner()),
weak_factory_(this) {
// We should never start up if there is no browser process or if we are
// currently quitting.
CHECK(g_browser_process);
CHECK(!browser_shutdown::IsTryingToQuit());
// Add self as an observer for the ProfileAttributesStorage so we know when
// profiles are deleted and their names change.
profile_storage_->AddObserver(this);
UMA_HISTOGRAM_BOOLEAN("BackgroundMode.OnStartup.AutoLaunchState",
command_line.HasSwitch(switches::kNoStartupWindow));
UMA_HISTOGRAM_BOOLEAN("BackgroundMode.OnStartup.IsBackgroundModePrefEnabled",
IsBackgroundModePrefEnabled());
// Listen for the background mode preference changing.
if (g_browser_process->local_state()) { // Skip for unit tests
pref_registrar_.Init(g_browser_process->local_state());
pref_registrar_.Add(
prefs::kBackgroundModeEnabled,
base::BindRepeating(
&BackgroundModeManager::OnBackgroundModeEnabledPrefChanged,
base::Unretained(this)));
}
// Keep the browser alive until extensions are done loading - this is needed
// by the --no-startup-window flag. We want to stay alive until we load
// extensions, at which point we should either run in background mode (if
// there are background apps) or exit if there are none.
if (command_line.HasSwitch(switches::kNoStartupWindow)) {
keep_alive_for_startup_.reset(
new ScopedKeepAlive(KeepAliveOrigin::BACKGROUND_MODE_MANAGER_STARTUP,
KeepAliveRestartOption::DISABLED));
} else {
// Otherwise, start with background mode suspended in case we're launching
// in a mode that doesn't open a browser window. It will be resumed when the
// first browser window is opened.
SuspendBackgroundMode();
optimizer_ = BackgroundModeOptimizer::Create();
}
// If the -keep-alive-for-test flag is passed, then always keep chrome running
// in the background until the user explicitly terminates it.
if (command_line.HasSwitch(switches::kKeepAliveForTest))
keep_alive_for_test_ = true;
if (ShouldBeInBackgroundMode())
StartBackgroundMode();
// Listen for the application shutting down so we can release our KeepAlive.
registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
content::NotificationService::AllSources());
BrowserList::AddObserver(this);
}
BackgroundModeManager::~BackgroundModeManager() {
// Remove ourselves from the application observer list (only needed by unit
// tests since APP_TERMINATING is what does this in a real running system).
for (const auto& it : background_mode_data_)
it.second->applications()->RemoveObserver(this);
BrowserList::RemoveObserver(this);
// We're going away, so exit background mode (does nothing if we aren't in
// background mode currently). This is primarily needed for unit tests,
// because in an actual running system we'd get an APP_TERMINATING
// notification before being destroyed.
EndBackgroundMode();
}
// static
void BackgroundModeManager::RegisterPrefs(PrefRegistrySimple* registry) {
#if defined(OS_MACOSX)
registry->RegisterBooleanPref(prefs::kUserRemovedLoginItem, false);
registry->RegisterBooleanPref(prefs::kChromeCreatedLoginItem, false);
registry->RegisterBooleanPref(prefs::kMigratedLoginItemPref, false);
#endif
registry->RegisterBooleanPref(prefs::kBackgroundModeEnabled, true);
}
void BackgroundModeManager::RegisterProfile(Profile* profile) {
// We don't want to register multiple times for one profile.
DCHECK(!base::ContainsKey(background_mode_data_, profile));
auto bmd = std::make_unique<BackgroundModeData>(profile,
&command_id_handler_vector_);
BackgroundModeData* bmd_ptr = bmd.get();
background_mode_data_[profile] = std::move(bmd);
// Initially set the name for this background mode data.
base::string16 name = l10n_util::GetStringUTF16(IDS_PROFILES_DEFAULT_NAME);
ProfileAttributesEntry* entry;
if (profile_storage_->GetProfileAttributesWithPath(
profile->GetPath(), &entry)) {
name = entry->GetName();
}
bmd_ptr->SetName(name);
// Check for the presence of background apps after all extensions have been
// loaded, to handle the case where an extension has been manually removed
// while Chrome was not running.
extensions::ExtensionSystem::Get(profile)->ready().Post(
FROM_HERE,
base::Bind(&BackgroundModeManager::OnExtensionsReady,
weak_factory_.GetWeakPtr(), profile));
bmd_ptr->applications()->AddObserver(this);
// If we're adding a new profile and running in multi-profile mode, this new
// profile should be added to the status icon if one currently exists.
if (in_background_mode_ && status_icon_)
UpdateStatusTrayIconContextMenu();
}
// static
void BackgroundModeManager::LaunchBackgroundApplication(
Profile* profile,
const Extension* extension) {
OpenApplication(CreateAppLaunchParamsUserContainer(
profile, extension, WindowOpenDisposition::NEW_FOREGROUND_TAB,
extensions::SOURCE_BACKGROUND));
}
// static
Browser* BackgroundModeManager::GetBrowserWindowForProfile(Profile* profile) {
Browser* browser = chrome::FindLastActiveWithProfile(profile);
return browser ? browser : chrome::OpenEmptyWindow(profile);
}
bool BackgroundModeManager::IsBackgroundModeActive() {
return in_background_mode_;
}
bool BackgroundModeManager::IsBackgroundWithoutWindows() const {
return KeepAliveRegistry::GetInstance()->WouldRestartWithout({
// Transient startup related KeepAlives, not related to any UI.
KeepAliveOrigin::SESSION_RESTORE,
KeepAliveOrigin::BACKGROUND_MODE_MANAGER_STARTUP,
// Notification KeepAlives are not dependent on the Chrome UI being
// loaded, and can be registered when we were in pure background mode.
// They just block it to avoid issues. Ignore them when determining if we
// are in that mode.
KeepAliveOrigin::NOTIFICATION,
KeepAliveOrigin::PENDING_NOTIFICATION_CLICK_EVENT,
KeepAliveOrigin::IN_FLIGHT_PUSH_MESSAGE,
});
}
size_t BackgroundModeManager::NumberOfBackgroundModeData() {
return background_mode_data_.size();
}
void BackgroundModeManager::RegisterTrigger(const Profile* profile,
BackgroundTrigger* trigger,
bool should_notify_user) {
BackgroundModeManager::BackgroundModeData* bmd =
GetBackgroundModeData(profile);
if (!bmd)
return;
// Only proceed if we don't have this trigger yet.
if (bmd->HasTrigger(trigger))
return;
// If the background pref is disabled, store it as a pending trigger that may
// be registered later if the pref gets enabled.
if (!IsBackgroundModePrefEnabled()) {
bmd->AddPendingTrigger(trigger, should_notify_user);
return;
}
bmd->RegisterTrigger(trigger);
std::vector<base::string16> new_client_names;
if (should_notify_user)
new_client_names.push_back(trigger->GetName());
OnClientsChanged(profile, new_client_names);
}
void BackgroundModeManager::UnregisterTrigger(const Profile* profile,
BackgroundTrigger* trigger) {
if (!IsBackgroundModePrefEnabled())
return;
BackgroundModeManager::BackgroundModeData* bmd =
GetBackgroundModeData(profile);
if (!bmd)
return;
// Only proceed if this is a known trigger.
if (!bmd->HasTrigger(trigger))
return;
bmd->UnregisterTrigger(trigger);
std::vector<base::string16> new_client_names;
OnClientsChanged(profile, new_client_names);
}
///////////////////////////////////////////////////////////////////////////////
// BackgroundModeManager, content::NotificationObserver overrides
void BackgroundModeManager::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
// Make sure we aren't still keeping the app alive (only happens if we
// don't receive an EXTENSIONS_READY notification for some reason).
ReleaseStartupKeepAlive();
// Performing an explicit shutdown, so exit background mode (does nothing
// if we aren't in background mode currently).
EndBackgroundMode();
// Shutting down, so don't listen for any more notifications so we don't
// try to re-enter/exit background mode again.
registrar_.RemoveAll();
for (const auto& it : background_mode_data_)
it.second->applications()->RemoveObserver(this);
}
void BackgroundModeManager::OnExtensionsReady(const Profile* profile) {
BackgroundModeManager::BackgroundModeData* bmd =
GetBackgroundModeData(profile);
if (bmd) {
UMA_HISTOGRAM_COUNTS_100("BackgroundMode.BackgroundApplicationsCount",
bmd->applications()->size());
}
// Extensions are loaded, so we don't need to manually keep the browser
// process alive any more when running in no-startup-window mode.
ReleaseStartupKeepAlive();
}
void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() {
bool enabled = IsBackgroundModePrefEnabled();
UMA_HISTOGRAM_BOOLEAN("BackgroundMode.BackgroundModeEnabledPrefChanged",
enabled);
if (enabled)
EnableBackgroundMode();
else
DisableBackgroundMode();
}
///////////////////////////////////////////////////////////////////////////////
// BackgroundModeManager, BackgroundApplicationListModel::Observer overrides
void BackgroundModeManager::OnApplicationDataChanged() {
UpdateStatusTrayIconContextMenu();
}
void BackgroundModeManager::OnApplicationListChanged(const Profile* profile) {
if (!IsBackgroundModePrefEnabled())
return;
BackgroundModeManager::BackgroundModeData* bmd =
GetBackgroundModeData(profile);
if (!bmd)
return;
// Get the new apps (if any) and process them.
std::set<const extensions::Extension*> new_apps = bmd->GetNewBackgroundApps();
std::vector<base::string16> new_names;
for (auto* app : new_apps)
new_names.push_back(base::UTF8ToUTF16(app->name()));
OnClientsChanged(profile, new_names);
}
///////////////////////////////////////////////////////////////////////////////
// BackgroundModeManager, ProfileAttributesStorage::Observer overrides
void BackgroundModeManager::OnProfileAdded(const base::FilePath& profile_path) {
ProfileAttributesEntry* entry;
bool success =
profile_storage_->GetProfileAttributesWithPath(profile_path, &entry);
DCHECK(success);
base::string16 profile_name = entry->GetName();
// At this point, the profile should be registered with the background mode
// manager, but when it's actually added to the ProfileAttributesStorage is
// when its name is set so we need up to update that with the
// background_mode_data.
for (const auto& it : background_mode_data_) {
if (it.first->GetPath() == profile_path) {
it.second->SetName(profile_name);
UpdateStatusTrayIconContextMenu();
return;
}
}
}
void BackgroundModeManager::OnProfileWillBeRemoved(
const base::FilePath& profile_path) {
ProfileAttributesEntry* entry;
bool success =
profile_storage_->GetProfileAttributesWithPath(profile_path, &entry);
DCHECK(success);
base::string16 profile_name = entry->GetName();
// Remove the profile from our map of profiles.
BackgroundModeInfoMap::iterator it =
GetBackgroundModeIterator(profile_name);
// If a profile isn't running a background app, it may not be in the map.
if (it != background_mode_data_.end()) {
it->second->applications()->RemoveObserver(this);
background_mode_data_.erase(it);
// If there are no background mode profiles any longer, then turn off
// background mode.
if (!ShouldBeInBackgroundMode()) {
EnableLaunchOnStartup(false);
EndBackgroundMode();
}
UpdateStatusTrayIconContextMenu();
}
}
void BackgroundModeManager::OnProfileNameChanged(
const base::FilePath& profile_path,
const base::string16& old_profile_name) {
ProfileAttributesEntry* entry;
bool success =
profile_storage_->GetProfileAttributesWithPath(profile_path, &entry);
DCHECK(success);
base::string16 new_profile_name = entry->GetName();
BackgroundModeInfoMap::const_iterator it =
GetBackgroundModeIterator(old_profile_name);
// We check that the returned iterator is valid due to unittests, but really
// this should only be called on profiles already known by the background
// mode manager.
if (it != background_mode_data_.end()) {
it->second->SetName(new_profile_name);
UpdateStatusTrayIconContextMenu();
}
}
BackgroundModeManager::BackgroundModeData*
BackgroundModeManager::GetBackgroundModeDataForLastProfile() const {
Profile* most_recent_profile = g_browser_process->profile_manager()->
GetLastUsedProfileAllowedByPolicy();
BackgroundModeInfoMap::const_iterator profile_background_data =
background_mode_data_.find(most_recent_profile);
if (profile_background_data == background_mode_data_.end())
return nullptr;
// Do not permit a locked profile to be used to open a browser.
ProfileAttributesEntry* entry;
bool success = profile_storage_->GetProfileAttributesWithPath(
profile_background_data->first->GetPath(), &entry);
DCHECK(success);
if (entry->IsSigninRequired())
return nullptr;
return profile_background_data->second.get();
}
///////////////////////////////////////////////////////////////////////////////
// BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides
void BackgroundModeManager::ExecuteCommand(int command_id, int event_flags) {
BackgroundModeData* bmd = GetBackgroundModeDataForLastProfile();
switch (command_id) {
case IDC_ABOUT:
RecordMenuItemClick(MENU_ITEM_ABOUT);
if (bmd) {
chrome::ShowAboutChrome(bmd->GetBrowserWindow());
} else {
UserManager::Show(base::FilePath(),
profiles::USER_MANAGER_SELECT_PROFILE_ABOUT_CHROME);
}
break;
case IDC_TASK_MANAGER:
RecordMenuItemClick(MENU_ITEM_TASK_MANAGER);
if (bmd) {
chrome::OpenTaskManager(bmd->GetBrowserWindow());
} else {
UserManager::Show(base::FilePath(),
profiles::USER_MANAGER_SELECT_PROFILE_TASK_MANAGER);
}
break;
case IDC_EXIT:
RecordMenuItemClick(MENU_ITEM_EXIT);
base::RecordAction(UserMetricsAction("Exit"));
chrome::CloseAllBrowsers();
break;
case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND: {
// Background mode must already be enabled (as otherwise this menu would
// not be visible).
DCHECK(IsBackgroundModePrefEnabled());
DCHECK(KeepAliveRegistry::GetInstance()->IsKeepingAlive());
RecordMenuItemClick(MENU_ITEM_KEEP_RUNNING);
// Set the background mode pref to "disabled" - the resulting notification
// will result in a call to DisableBackgroundMode().
PrefService* service = g_browser_process->local_state();
DCHECK(service);
service->SetBoolean(prefs::kBackgroundModeEnabled, false);
break;
}
default:
if (bmd) {
bmd->ExecuteCommand(command_id, event_flags);
} else {
UserManager::Show(base::FilePath(),
profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION);
}
break;
}
}
///////////////////////////////////////////////////////////////////////////////
// BackgroundModeManager, private
void BackgroundModeManager::ReleaseStartupKeepAliveCallback() {
keep_alive_for_startup_.reset();
optimizer_ = BackgroundModeOptimizer::Create();
}
void BackgroundModeManager::ReleaseStartupKeepAlive() {
if (keep_alive_for_startup_) {
// We call this via the message queue to make sure we don't try to end
// keep-alive (which can shutdown Chrome) before the message loop has
// started. This object reference is safe because it's going to be kept
// alive by the browser process until after the callback is called.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&BackgroundModeManager::ReleaseStartupKeepAliveCallback,
base::Unretained(this)));
}
}
void BackgroundModeManager::StartBackgroundMode() {
DCHECK(ShouldBeInBackgroundMode());
// Don't bother putting ourselves in background mode if we're already there
// or if background mode is disabled.
if (in_background_mode_)
return;
startup_metric_utils::SetBackgroundModeEnabled();
// Mark ourselves as running in background mode.
in_background_mode_ = true;
UpdateKeepAliveAndTrayIcon();
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
content::Source<BackgroundModeManager>(this),
content::Details<bool>(&in_background_mode_));
}
void BackgroundModeManager::EndBackgroundMode() {
if (!in_background_mode_)
return;
in_background_mode_ = false;
UpdateKeepAliveAndTrayIcon();
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED,
content::Source<BackgroundModeManager>(this),
content::Details<bool>(&in_background_mode_));
}
void BackgroundModeManager::EnableBackgroundMode() {
DCHECK(IsBackgroundModePrefEnabled());
// If background mode should be enabled, but isn't, turn it on.
if (!in_background_mode_ && ShouldBeInBackgroundMode()) {
StartBackgroundMode();
// Register pending triggers.
for (const auto& bmd_iterator : background_mode_data_) {
PendingTriggerData pending_trigger_data =
bmd_iterator.second->TakePendingTriggerData();
for (const auto& trigger_data_iterator : pending_trigger_data) {
const Profile* profile = bmd_iterator.first;
BackgroundTrigger* trigger = trigger_data_iterator.first;
bool should_notify_user = trigger_data_iterator.second;
RegisterTrigger(profile, trigger, should_notify_user);
}
}
EnableLaunchOnStartup(true);
}
}
void BackgroundModeManager::DisableBackgroundMode() {
DCHECK(!IsBackgroundModePrefEnabled());
// If background mode is currently enabled, turn it off.
if (in_background_mode_) {
EndBackgroundMode();
EnableLaunchOnStartup(false);
}
}
void BackgroundModeManager::SuspendBackgroundMode() {
background_mode_suspended_ = true;
UpdateKeepAliveAndTrayIcon();
}
void BackgroundModeManager::ResumeBackgroundMode() {
background_mode_suspended_ = false;
UpdateKeepAliveAndTrayIcon();
}
void BackgroundModeManager::UpdateKeepAliveAndTrayIcon() {
if (in_background_mode_ && !background_mode_suspended_) {
if (!keep_alive_) {
keep_alive_ = std::make_unique<ScopedKeepAlive>(
KeepAliveOrigin::BACKGROUND_MODE_MANAGER,
KeepAliveRestartOption::ENABLED);
}
CreateStatusTrayIcon();
return;
}
RemoveStatusTrayIcon();
keep_alive_.reset();
}
void BackgroundModeManager::OnBrowserAdded(Browser* browser) {
ResumeBackgroundMode();
}
void BackgroundModeManager::OnClientsChanged(
const Profile* profile,
const std::vector<base::string16>& new_client_names) {
DCHECK(IsBackgroundModePrefEnabled());
// Update the ProfileAttributesStorage with the fact whether background
// clients are running for this profile.
ProfileAttributesEntry* entry;
if (profile_storage_->
GetProfileAttributesWithPath(profile->GetPath(), &entry)) {
entry->SetBackgroundStatus(HasBackgroundClientForProfile(profile));
}
if (!ShouldBeInBackgroundMode()) {
// We've uninstalled our last background client, make sure we exit
// background mode and no longer launch on startup.
EnableLaunchOnStartup(false);
EndBackgroundMode();
} else {
// We have at least one background client - make sure we're in background
// mode.
if (!in_background_mode_) {
// We're entering background mode - make sure we have launch-on-startup
// enabled. On Mac, the platform-specific code tracks whether the user
// has deleted a login item in the past, and if so, no login item will
// be created (to avoid overriding the specific user action).
EnableLaunchOnStartup(true);
StartBackgroundMode();
}
// List of clients changed so update the UI.
UpdateStatusTrayIconContextMenu();
// Notify the user about any new clients.
for (const auto& name : new_client_names)
OnBackgroundClientInstalled(name);
}
}
bool BackgroundModeManager::HasBackgroundClient() const {
for (const auto& it : background_mode_data_) {
if (it.second->HasBackgroundClient())
return true;
}
return false;
}
bool BackgroundModeManager::HasBackgroundClientForProfile(
const Profile* profile) const {
BackgroundModeManager::BackgroundModeData* bmd =
GetBackgroundModeData(profile);
return bmd && bmd->HasBackgroundClient();
}
bool BackgroundModeManager::HasPendingTrigger() const {
for (const auto& it : background_mode_data_) {
if (it.second->HasPendingTrigger())
return true;
}
return false;
}
bool BackgroundModeManager::ShouldBeInBackgroundMode() const {
return IsBackgroundModePrefEnabled() &&
(HasBackgroundClient() || HasPendingTrigger() || keep_alive_for_test_);
}
void BackgroundModeManager::OnBackgroundClientInstalled(
const base::string16& name) {
// Background mode is disabled - don't do anything.
if (!IsBackgroundModePrefEnabled())
return;
// Ensure we have a tray icon (needed so we can display the app-installed
// notification below).
EnableBackgroundMode();
ResumeBackgroundMode();
// Notify the user that a background client has been installed.
DisplayClientInstalledNotification(name);
}
// Gets the image for the status tray icon, at the correct size for the current
// platform and display settings.
gfx::ImageSkia GetStatusTrayIcon() {
#if defined(OS_WIN)
// On Windows, use GetSmallAppIconSize to get the correct image size. The
// user's "text size" setting in Windows determines how large the system tray
// icon should be.
gfx::Size size = GetSmallAppIconSize();
// This loads all of the icon images, which is a bit wasteful because we're
// going to pick one and throw the rest away, but that is the price of using
// the ImageFamily abstraction. Note: We could just use the LoadImage function
// from the Windows API, but that does a *terrible* job scaling images.
// Therefore, we fetch the images and do our own high-quality scaling.
std::unique_ptr<gfx::ImageFamily> family = GetAppIconImageFamily();
DCHECK(family);
if (!family)
return gfx::ImageSkia();
return family->CreateExact(size).AsImageSkia();
#else
// On other platforms, just get a static resource image.
return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_STATUS_TRAY_ICON);
#endif
}
void BackgroundModeManager::CreateStatusTrayIcon() {
// Only need status icons on windows/linux. ChromeOS doesn't allow exiting
// Chrome and Mac can use the dock icon instead.
// Since there are multiple profiles which share the status tray, we now
// use the browser process to keep track of it.
#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
if (!status_tray_)
status_tray_ = g_browser_process->status_tray();
#endif
// If the platform doesn't support status icons, or we've already created
// our status icon, just return.
if (!status_tray_ || status_icon_)
return;
status_icon_ = status_tray_->CreateStatusIcon(
StatusTray::BACKGROUND_MODE_ICON, GetStatusTrayIcon(),
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
if (!status_icon_)
return;
UpdateStatusTrayIconContextMenu();
}
void BackgroundModeManager::UpdateStatusTrayIconContextMenu() {
// Ensure we have a tray icon if appropriate.
UpdateKeepAliveAndTrayIcon();
// If we don't have a status icon or one could not be created succesfully,
// then no need to continue the update.
if (!status_icon_)
return;
// We should only get here if we have a profile loaded, or if we're running
// in test mode.
if (background_mode_data_.empty()) {
DCHECK(keep_alive_for_test_);
return;
}
command_id_handler_vector_.clear();
submenus.clear();
std::unique_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
menu->AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT));
menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
menu->AddSeparator(ui::NORMAL_SEPARATOR);
// If there are multiple profiles they each get a submenu.
if (profile_storage_->GetNumberOfProfiles() > 1) {
std::vector<BackgroundModeData*> bmd_vector;
for (const auto& it : background_mode_data_)
bmd_vector.push_back(it.second.get());
std::sort(bmd_vector.begin(), bmd_vector.end(),
&BackgroundModeData::BackgroundModeDataCompare);
int profiles_using_background_mode = 0;
for (auto* bmd : bmd_vector) {
// We should only display the profile in the status icon if it has at
// least one background app.
if (bmd->HasBackgroundClient()) {
// The submenu constructor caller owns the lifetime of the submenu.
// The containing menu does not handle the lifetime.
submenus.push_back(std::make_unique<StatusIconMenuModel>(bmd));
bmd->BuildProfileMenu(submenus.back().get(), menu.get());
profiles_using_background_mode++;
}
}
// We should only be displaying the status tray icon if there is at least
// one profile using background mode. If |keep_alive_for_test_| is set,
// there may not be any profiles and that is okay.
DCHECK(profiles_using_background_mode > 0 || keep_alive_for_test_);
} else {
// We should only have one profile in the ProfileAttributesStorage if we are
// not using multi-profiles. If |keep_alive_for_test_| is set, then we may
// not have any profiles in the ProfileAttributesStorage.
DCHECK(profile_storage_->GetNumberOfProfiles() == size_t(1) ||
keep_alive_for_test_);
background_mode_data_.begin()->second->BuildProfileMenu(menu.get(),
nullptr);
}
menu->AddSeparator(ui::NORMAL_SEPARATOR);
menu->AddCheckItemWithStringId(
IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND);
menu->SetCommandIdChecked(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
true);
PrefService* service = g_browser_process->local_state();
DCHECK(service);
bool enabled =
service->IsUserModifiablePreference(prefs::kBackgroundModeEnabled);
menu->SetCommandIdEnabled(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
enabled);
menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT);
context_menu_ = menu.get();
status_icon_->SetContextMenu(std::move(menu));
}
void BackgroundModeManager::RemoveStatusTrayIcon() {
if (status_icon_)
status_tray_->RemoveStatusIcon(status_icon_);
status_icon_ = nullptr;
context_menu_ = nullptr;
}
BackgroundModeManager::BackgroundModeData*
BackgroundModeManager::GetBackgroundModeData(const Profile* profile) const {
// Profiles are shut down and destroyed asynchronously after
// OnProfileWillBeRemoved is called, so we may have dropped anything
// associated with the profile already.
auto it = background_mode_data_.find(profile);
return it != background_mode_data_.end() ? it->second.get() : nullptr;
}
BackgroundModeManager::BackgroundModeInfoMap::iterator
BackgroundModeManager::GetBackgroundModeIterator(
const base::string16& profile_name) {
BackgroundModeInfoMap::iterator profile_it =
background_mode_data_.end();
for (BackgroundModeInfoMap::iterator it =
background_mode_data_.begin();
it != background_mode_data_.end();
++it) {
if (it->second->name() == profile_name) {
profile_it = it;
}
}
return profile_it;
}
bool BackgroundModeManager::IsBackgroundModePrefEnabled() const {
PrefService* service = g_browser_process->local_state();
DCHECK(service);
return service->GetBoolean(prefs::kBackgroundModeEnabled);
}