2021-10-26 07:16:41 -03:00
|
|
|
// 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 <memory>
|
|
|
|
#include <string>
|
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include "base/base_paths.h"
|
|
|
|
#include "base/bind.h"
|
|
|
|
#include "base/callback.h"
|
|
|
|
#include "base/callback_helpers.h"
|
|
|
|
#include "base/command_line.h"
|
|
|
|
#include "base/containers/contains.h"
|
|
|
|
#include "base/location.h"
|
|
|
|
#include "base/logging.h"
|
|
|
|
#include "base/metrics/histogram_macros.h"
|
|
|
|
#include "base/metrics/user_metrics.h"
|
|
|
|
#include "base/one_shot_event.h"
|
|
|
|
#include "base/strings/utf_string_conversions.h"
|
|
|
|
#include "base/task/single_thread_task_runner.h"
|
|
|
|
#include "base/threading/thread_task_runner_handle.h"
|
|
|
|
#include "build/build_config.h"
|
|
|
|
#include "build/chromeos_buildflags.h"
|
|
|
|
#include "chrome/app/chrome_command_ids.h"
|
|
|
|
#include "chrome/browser/apps/app_service/app_service_proxy.h"
|
|
|
|
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
|
|
|
|
#include "chrome/browser/apps/app_service/browser_app_launcher.h"
|
|
|
|
#include "chrome/browser/background/background_application_list_model.h"
|
|
|
|
#include "chrome/browser/background/background_mode_optimizer.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/policy/profile_policy_connector.h"
|
2021-12-13 19:13:44 -03:00
|
|
|
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
|
|
|
|
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
|
2021-10-26 07:16:41 -03:00
|
|
|
#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/profile_picker.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/common/webui_url_constants.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/permissions/permission_set.h"
|
|
|
|
#include "ui/base/l10n/l10n_util.h"
|
|
|
|
#include "ui/base/models/image_model.h"
|
|
|
|
#include "ui/base/resource/resource_bundle.h"
|
|
|
|
#include "ui/gfx/image/image_family.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(
|
|
|
|
BackgroundModeManager* manager,
|
|
|
|
Profile* profile,
|
|
|
|
CommandIdHandlerVector* command_id_handler_vector)
|
|
|
|
: manager_(manager),
|
|
|
|
applications_(std::make_unique<BackgroundApplicationListModel>(profile)),
|
|
|
|
profile_(profile),
|
|
|
|
command_id_handler_vector_(command_id_handler_vector) {
|
2021-11-28 18:03:04 -03:00
|
|
|
profile_observation_.Observe(profile_.get());
|
2021-10-26 07:16:41 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
BackgroundModeManager::BackgroundModeData::~BackgroundModeData() = default;
|
|
|
|
|
|
|
|
void BackgroundModeManager::BackgroundModeData::SetTracker(
|
|
|
|
extensions::ForceInstalledTracker* tracker) {
|
|
|
|
force_installed_tracker_observation_.Observe(tracker);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::BackgroundModeData::UpdateProfileKeepAlive() {
|
|
|
|
bool background_mode =
|
|
|
|
(HasPersistentBackgroundClient() && manager_->IsBackgroundModeActive() &&
|
|
|
|
!manager_->background_mode_suspended_);
|
|
|
|
if (!background_mode) {
|
|
|
|
profile_keep_alive_.reset();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (profile_keep_alive_)
|
|
|
|
return;
|
|
|
|
if (!g_browser_process->profile_manager()->IsValidProfile(profile_)) {
|
|
|
|
// ScopedProfileKeepAlive will cause issues if we create it now. Wait for
|
|
|
|
// OnProfileAdded().
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
profile_keep_alive_ = std::make_unique<ScopedProfileKeepAlive>(
|
|
|
|
profile_, ProfileKeepAliveOrigin::kBackgroundMode);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::BackgroundModeData::OnProfileWillBeDestroyed(
|
|
|
|
Profile* profile) {
|
|
|
|
DCHECK_EQ(profile_, profile);
|
|
|
|
profile_observation_.Reset();
|
|
|
|
force_installed_tracker_observation_.Reset();
|
|
|
|
DCHECK(!profile_keep_alive_);
|
|
|
|
profile_ = nullptr;
|
|
|
|
// Remove this Profile* from |background_mode_data|.
|
|
|
|
bool did_unregister = manager_->UnregisterProfile(profile);
|
|
|
|
DCHECK(did_unregister);
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::BackgroundModeData::
|
|
|
|
OnForceInstalledExtensionsReady() {
|
|
|
|
manager_->ReleaseForceInstalledExtensionsKeepAlive();
|
|
|
|
}
|
|
|
|
|
|
|
|
Browser* BackgroundModeManager::BackgroundModeData::GetBrowserWindow() {
|
|
|
|
return BackgroundModeManager::GetBrowserWindowForProfile(profile_);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BackgroundModeManager::BackgroundModeData::HasPersistentBackgroundClient()
|
|
|
|
const {
|
|
|
|
return applications_->HasPersistentBackgroundApps() ||
|
|
|
|
manager_->keep_alive_for_test_;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BackgroundModeManager::BackgroundModeData::HasAnyBackgroundClient() const {
|
|
|
|
return applications_->size() > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::BackgroundModeData::BuildProfileMenu(
|
|
|
|
StatusIconMenuModel* menu,
|
|
|
|
StatusIconMenuModel* containing_menu) {
|
|
|
|
if (HasAnyBackgroundClient()) {
|
|
|
|
// Add a menu item for each application (extension).
|
|
|
|
for (const auto& application : *applications_) {
|
|
|
|
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.isNull())
|
|
|
|
menu->SetIcon(menu->GetItemCount() - 1,
|
|
|
|
ui::ImageModel::FromImageSkia(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::mojom::ManifestLocation::kComponent) {
|
|
|
|
GURL options_page =
|
|
|
|
extensions::OptionsPageInfo::GetOptionsPage(application.get());
|
|
|
|
if (!options_page.is_valid())
|
|
|
|
menu->SetCommandIdEnabled(command_id, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} 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 std::u16string& new_profile_name) {
|
|
|
|
name_ = new_profile_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::u16string 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 and
|
|
|
|
// this is a persistent background app, notify the user.
|
|
|
|
if (applications_->startup_done() &&
|
|
|
|
BackgroundApplicationListModel::IsPersistentBackgroundApp(
|
|
|
|
*application, profile_)) {
|
|
|
|
new_apps.insert(application.get());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new_apps;
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare(
|
|
|
|
const BackgroundModeData* bmd1,
|
|
|
|
const BackgroundModeData* bmd2) {
|
|
|
|
return bmd1->name_ < bmd2->name_;
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// BackgroundModeManager, public
|
|
|
|
BackgroundModeManager::BackgroundModeManager(
|
|
|
|
const base::CommandLine& command_line,
|
|
|
|
ProfileAttributesStorage* profile_storage)
|
|
|
|
: profile_storage_(profile_storage), task_runner_(CreateTaskRunner()) {
|
|
|
|
// 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.
|
|
|
|
// This observer is never unregistered because the BackgroundModeManager
|
|
|
|
// outlives the profile storage.
|
|
|
|
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_ = std::make_unique<ScopedKeepAlive>(
|
|
|
|
KeepAliveOrigin::BACKGROUND_MODE_MANAGER_STARTUP,
|
|
|
|
KeepAliveRestartOption::DISABLED);
|
|
|
|
// Wait for force-installed extensions to install, as well.
|
|
|
|
keep_alive_for_force_installed_extensions_ =
|
|
|
|
std::make_unique<ScopedKeepAlive>(
|
|
|
|
KeepAliveOrigin::BACKGROUND_MODE_MANAGER_FORCE_INSTALLED_EXTENSIONS,
|
|
|
|
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_MAC)
|
|
|
|
registry->RegisterBooleanPref(prefs::kUserRemovedLoginItem, false);
|
|
|
|
registry->RegisterBooleanPref(prefs::kChromeCreatedLoginItem, false);
|
|
|
|
registry->RegisterBooleanPref(prefs::kMigratedLoginItemPref, false);
|
|
|
|
#endif
|
|
|
|
registry->RegisterBooleanPref(prefs::kBackgroundModeEnabled, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::RegisterProfile(Profile* profile) {
|
|
|
|
// We don't want to register multiple times for one profile.
|
|
|
|
DCHECK(!base::Contains(background_mode_data_, profile));
|
|
|
|
auto bmd = std::make_unique<BackgroundModeData>(this, 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.
|
|
|
|
std::u16string name = l10n_util::GetStringUTF16(IDS_PROFILES_DEFAULT_NAME);
|
|
|
|
ProfileAttributesEntry* entry =
|
|
|
|
profile_storage_->GetProfileAttributesWithPath(profile->GetPath());
|
|
|
|
if (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::BindOnce(&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();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BackgroundModeManager::UnregisterProfile(Profile* profile) {
|
|
|
|
// Remove the profile from our map of profiles.
|
|
|
|
auto it = background_mode_data_.find(profile);
|
|
|
|
// If a profile isn't running a background app, it may not be in the map.
|
|
|
|
if (it == background_mode_data_.end())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
it->second->applications()->RemoveObserver(this);
|
|
|
|
background_mode_data_.erase(it);
|
|
|
|
// If there are no background mode profiles any longer, then turn off
|
|
|
|
// background mode.
|
|
|
|
UpdateEnableLaunchOnStartup();
|
|
|
|
if (!ShouldBeInBackgroundMode()) {
|
|
|
|
EndBackgroundMode();
|
|
|
|
}
|
|
|
|
UpdateStatusTrayIconContextMenu();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
void BackgroundModeManager::LaunchBackgroundApplication(
|
|
|
|
Profile* profile,
|
|
|
|
const Extension* extension) {
|
|
|
|
apps::AppServiceProxyFactory::GetForProfile(profile)
|
|
|
|
->BrowserAppLauncher()
|
|
|
|
->LaunchAppWithParams(CreateAppLaunchParamsUserContainer(
|
|
|
|
profile, extension, WindowOpenDisposition::NEW_FOREGROUND_TAB,
|
|
|
|
apps::mojom::LaunchSource::kFromBackgroundMode));
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
|
|
|
KeepAliveOrigin::BACKGROUND_SYNC,
|
|
|
|
|
|
|
|
// 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::PENDING_NOTIFICATION_CLOSE_EVENT,
|
|
|
|
KeepAliveOrigin::IN_FLIGHT_PUSH_MESSAGE,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t BackgroundModeManager::NumberOfBackgroundModeData() {
|
|
|
|
return background_mode_data_.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// 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(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();
|
|
|
|
|
|
|
|
auto* extension_service =
|
|
|
|
extensions::ExtensionSystem::Get(profile)->extension_service();
|
|
|
|
auto* tracker = extension_service->force_installed_tracker();
|
|
|
|
if (tracker->IsReady() || !bmd)
|
|
|
|
ReleaseForceInstalledExtensionsKeepAlive();
|
|
|
|
else
|
|
|
|
bmd->SetTracker(tracker);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() {
|
|
|
|
bool enabled = IsBackgroundModePrefEnabled();
|
|
|
|
UMA_HISTOGRAM_BOOLEAN("BackgroundMode.BackgroundModeEnabledPrefChanged",
|
|
|
|
enabled);
|
|
|
|
UpdateEnableLaunchOnStartup();
|
|
|
|
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<std::u16string> 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 =
|
|
|
|
profile_storage_->GetProfileAttributesWithPath(profile_path);
|
|
|
|
DCHECK(entry);
|
|
|
|
std::u16string 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) {
|
|
|
|
Profile* profile =
|
|
|
|
g_browser_process->profile_manager()->GetProfileByPath(profile_path);
|
|
|
|
DCHECK(profile);
|
|
|
|
UnregisterProfile(profile);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::OnProfileNameChanged(
|
|
|
|
const base::FilePath& profile_path,
|
|
|
|
const std::u16string& old_profile_name) {
|
|
|
|
ProfileAttributesEntry* entry =
|
|
|
|
profile_storage_->GetProfileAttributesWithPath(profile_path);
|
|
|
|
DCHECK(entry);
|
|
|
|
std::u16string 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();
|
|
|
|
auto 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 =
|
|
|
|
profile_storage_->GetProfileAttributesWithPath(
|
|
|
|
profile_background_data->first->GetPath());
|
|
|
|
DCHECK(entry);
|
|
|
|
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 {
|
|
|
|
ProfilePicker::Show(ProfilePicker::EntryPoint::kBackgroundModeManager,
|
|
|
|
GURL(chrome::kChromeUIHelpURL));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case IDC_TASK_MANAGER:
|
|
|
|
RecordMenuItemClick(MENU_ITEM_TASK_MANAGER);
|
|
|
|
if (bmd) {
|
|
|
|
chrome::OpenTaskManager(bmd->GetBrowserWindow());
|
|
|
|
} else {
|
|
|
|
ProfilePicker::Show(ProfilePicker::EntryPoint::kBackgroundModeManager,
|
|
|
|
GURL(ProfilePicker::kTaskManagerUrl));
|
|
|
|
}
|
|
|
|
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 {
|
|
|
|
ProfilePicker::Show(ProfilePicker::EntryPoint::kBackgroundModeManager);
|
|
|
|
}
|
|
|
|
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::ReleaseForceInstalledExtensionsKeepAlive() {
|
|
|
|
if (keep_alive_for_force_installed_extensions_) {
|
|
|
|
// 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(
|
|
|
|
[](std::unique_ptr<ScopedKeepAlive> keep_alive) {
|
|
|
|
// Cleans up unique_ptr when it goes out of scope.
|
|
|
|
},
|
|
|
|
std::move(keep_alive_for_force_installed_extensions_)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::EndBackgroundMode() {
|
|
|
|
if (!in_background_mode_)
|
|
|
|
return;
|
|
|
|
in_background_mode_ = false;
|
|
|
|
|
|
|
|
UpdateKeepAliveAndTrayIcon();
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::EnableBackgroundMode() {
|
|
|
|
DCHECK(IsBackgroundModePrefEnabled());
|
|
|
|
// If background mode should be enabled, but isn't, turn it on.
|
|
|
|
if (!in_background_mode_ && ShouldBeInBackgroundMode()) {
|
|
|
|
StartBackgroundMode();
|
|
|
|
|
|
|
|
UpdateEnableLaunchOnStartup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::DisableBackgroundMode() {
|
|
|
|
DCHECK(!IsBackgroundModePrefEnabled());
|
|
|
|
// If background mode is currently enabled, turn it off.
|
|
|
|
if (in_background_mode_) {
|
|
|
|
EndBackgroundMode();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::SuspendBackgroundMode() {
|
|
|
|
background_mode_suspended_ = true;
|
|
|
|
UpdateKeepAliveAndTrayIcon();
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::ResumeBackgroundMode() {
|
|
|
|
background_mode_suspended_ = false;
|
|
|
|
UpdateKeepAliveAndTrayIcon();
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::UpdateKeepAliveAndTrayIcon() {
|
|
|
|
for (const auto& entry : background_mode_data_)
|
|
|
|
entry.second->UpdateProfileKeepAlive();
|
|
|
|
|
|
|
|
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<std::u16string>& new_client_names) {
|
|
|
|
DCHECK(IsBackgroundModePrefEnabled());
|
|
|
|
|
|
|
|
// Update the ProfileAttributesStorage with the fact whether background
|
|
|
|
// clients are running for this profile.
|
|
|
|
ProfileAttributesEntry* entry =
|
|
|
|
profile_storage_->GetProfileAttributesWithPath(profile->GetPath());
|
|
|
|
if (entry) {
|
|
|
|
entry->SetBackgroundStatus(
|
|
|
|
HasPersistentBackgroundClientForProfile(profile));
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateEnableLaunchOnStartup();
|
|
|
|
if (!ShouldBeInBackgroundMode()) {
|
|
|
|
// We've uninstalled our last background client, make sure we exit
|
|
|
|
// background mode and no longer launch on startup.
|
|
|
|
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).
|
|
|
|
StartBackgroundMode();
|
|
|
|
}
|
|
|
|
|
|
|
|
// List of clients changed so update the UI and keep alive references.
|
|
|
|
UpdateStatusTrayIconContextMenu();
|
|
|
|
|
|
|
|
// Notify the user about any new clients.
|
|
|
|
for (const auto& name : new_client_names)
|
|
|
|
OnBackgroundClientInstalled(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BackgroundModeManager::HasPersistentBackgroundClient() const {
|
|
|
|
for (const auto& it : background_mode_data_) {
|
|
|
|
if (it.second->HasPersistentBackgroundClient())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BackgroundModeManager::HasAnyBackgroundClient() const {
|
|
|
|
for (const auto& it : background_mode_data_) {
|
|
|
|
if (it.second->HasAnyBackgroundClient())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BackgroundModeManager::HasPersistentBackgroundClientForProfile(
|
|
|
|
const Profile* profile) const {
|
|
|
|
BackgroundModeManager::BackgroundModeData* bmd =
|
|
|
|
GetBackgroundModeData(profile);
|
|
|
|
return bmd && bmd->HasPersistentBackgroundClient();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BackgroundModeManager::ShouldBeInBackgroundMode() const {
|
|
|
|
return IsBackgroundModePrefEnabled() &&
|
|
|
|
(HasAnyBackgroundClient() || keep_alive_for_test_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::OnBackgroundClientInstalled(
|
|
|
|
const std::u16string& 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();
|
|
|
|
|
|
|
|
++client_installed_notifications_;
|
|
|
|
// Notify the user that a background client has been installed.
|
|
|
|
DisplayClientInstalledNotification(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BackgroundModeManager::UpdateEnableLaunchOnStartup() {
|
|
|
|
bool new_launch_on_startup =
|
|
|
|
ShouldBeInBackgroundMode() && HasPersistentBackgroundClient();
|
|
|
|
if (launch_on_startup_enabled_ &&
|
|
|
|
new_launch_on_startup == *launch_on_startup_enabled_) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
launch_on_startup_enabled_.emplace(new_launch_on_startup);
|
|
|
|
EnableLaunchOnStartup(*launch_on_startup_enabled_);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
#elif defined(OS_LINUX) || defined(OS_CHROMEOS)
|
|
|
|
return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
|
|
|
|
IDR_PRODUCT_LOGO_128);
|
|
|
|
#elif defined(OS_MAC)
|
|
|
|
return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
|
|
|
|
IDR_STATUS_TRAY_ICON);
|
|
|
|
#else
|
|
|
|
NOTREACHED();
|
|
|
|
return gfx::ImageSkia();
|
|
|
|
#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_MAC) && !BUILDFLAG(IS_CHROMEOS_ASH) && \
|
|
|
|
!BUILDFLAG(IS_CHROMEOS_LACROS)
|
|
|
|
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->HasAnyBackgroundClient()) {
|
|
|
|
// 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 std::u16string& profile_name) {
|
|
|
|
auto profile_it = background_mode_data_.end();
|
|
|
|
for (auto 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);
|
|
|
|
}
|