// 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 #include #include #include #include #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.h" #include "base/prefs/pref_registry_simple.h" #include "base/prefs/pref_service.h" #include "base/single_thread_task_runner.h" #include "base/strings/utf_string_conversions.h" #include "base/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_trigger.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/browser_shutdown.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/profiles/profile.h" #include "chrome/browser/profiles/profile_info_cache.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/host_desktop.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/chromium_strings.h" #include "chrome/grit/generated_resources.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/user_metrics.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 "grit/chrome_unscaled_resources.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #if defined(OS_WIN) #include "chrome/browser/app_icon_win.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 BackgroundModeManager::BackgroundModeData::BackgroundModeData( Profile* profile, CommandIdHandlerVector* command_id_handler_vector) : applications_(new 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_); } int BackgroundModeManager::BackgroundModeData::GetBackgroundClientCount() const { return applications_->size() + registered_triggers_.size(); } void BackgroundModeManager::BackgroundModeData::BuildProfileMenu( StatusIconMenuModel* menu, StatusIconMenuModel* containing_menu) { // When there are no background clients, we want to display just a label // stating that none are running. if (GetBackgroundClientCount() == 0) { menu->AddItemWithStringId(IDC_MinimumLabelValue, IDS_BACKGROUND_APP_NOT_INSTALLED); menu->SetCommandIdEnabled(IDC_MinimumLabelValue, false); } else { // 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::Bind(&BackgroundModeManager::LaunchBackgroundApplication, profile_, application.get())); 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 (const 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::Bind( &BackgroundTrigger::OnMenuClick, base::Unretained(trigger))); menu->AddItem(command_id, name); if (icon) menu->SetIcon(menu->GetItemCount() - 1, gfx::Image(*icon)); } } 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::Bind(&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 BackgroundModeManager::BackgroundModeData::GetNewBackgroundApps() { std::set 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::GetPendingTriggerData() const { return pending_trigger_data_; } void BackgroundModeManager::BackgroundModeData::ClearPendingTriggerData() { pending_trigger_data_.clear(); } int BackgroundModeManager::BackgroundModeData::GetPendingTriggerCount() const { return pending_trigger_data_.size(); } 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) { if (registered_triggers_.find(trigger) != registered_triggers_.end()) return true; return pending_trigger_data_.find(trigger) != pending_trigger_data_.end(); } /////////////////////////////////////////////////////////////////////////////// // BackgroundModeManager, public BackgroundModeManager::BackgroundModeManager( const base::CommandLine& command_line, ProfileInfoCache* profile_cache) : profile_cache_(profile_cache), status_tray_(NULL), status_icon_(NULL), context_menu_(NULL), in_background_mode_(false), keep_alive_for_startup_(false), keep_alive_for_test_(false), background_mode_suspended_(false), keeping_alive_(false), weak_factory_(this) { // We should never start up if there is no browser process or if we are // currently quitting. CHECK(g_browser_process != NULL); CHECK(!browser_shutdown::IsTryingToQuit()); // Add self as an observer for the profile info cache so we know when profiles // are deleted and their names change. profile_cache_->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::Bind(&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_ = true; chrome::IncrementKeepAliveCount(); } 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(); } // 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 decrement our KeepAlive // count. 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(background_mode_data_.find(profile) == background_mode_data_.end()); BackgroundModeInfo bmd( new BackgroundModeData(profile, &command_id_handler_vector_)); background_mode_data_[profile] = bmd; // Initially set the name for this background mode data. size_t index = profile_cache_->GetIndexOfProfileWithPath(profile->GetPath()); base::string16 name = l10n_util::GetStringUTF16(IDS_PROFILES_DEFAULT_NAME); if (index != std::string::npos) name = profile_cache_->GetNameOfProfileAtIndex(index); bmd->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->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(AppLaunchParams(profile, extension, NEW_FOREGROUND_TAB, extensions::SOURCE_BACKGROUND)); } // static Browser* BackgroundModeManager::GetBrowserWindowForProfile(Profile* profile) { chrome::HostDesktopType host_desktop_type = chrome::GetActiveDesktop(); Browser* browser = chrome::FindLastActiveWithProfile(profile, host_desktop_type); return browser ? browser : chrome::OpenEmptyWindow(profile, host_desktop_type); } bool BackgroundModeManager::IsBackgroundModeActive() { return in_background_mode_; } int BackgroundModeManager::NumberOfBackgroundModeData() { return background_mode_data_.size(); } void BackgroundModeManager::RegisterTrigger(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 new_client_names; if (should_notify_user) new_client_names.push_back(trigger->GetName()); OnClientsChanged(profile, new_client_names); } void BackgroundModeManager::UnregisterTrigger(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 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) { switch (type) { case chrome::NOTIFICATION_APP_TERMINATING: // Make sure we aren't still keeping the app alive (only happens if we // don't receive an EXTENSIONS_READY notification for some reason). DecrementKeepAliveCountForStartup(); // 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); break; default: NOTREACHED(); break; } } 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. DecrementKeepAliveCountForStartup(); } void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() { bool enabled = IsBackgroundModePrefEnabled(); UMA_HISTOGRAM_BOOLEAN("BackgroundMode.BackgroundModeEnabledPrefChanged", enabled); if (enabled) EnableBackgroundMode(); else DisableBackgroundMode(); } /////////////////////////////////////////////////////////////////////////////// // BackgroundModeManager, BackgroundApplicationListModel::Observer overrides void BackgroundModeManager::OnApplicationDataChanged( const Extension* extension, Profile* profile) { UpdateStatusTrayIconContextMenu(); } void BackgroundModeManager::OnApplicationListChanged(Profile* profile) { if (!IsBackgroundModePrefEnabled()) return; BackgroundModeManager::BackgroundModeData* bmd = GetBackgroundModeData(profile); if (!bmd) return; // Get the new apps (if any) and process them. std::set new_apps = bmd->GetNewBackgroundApps(); std::vector new_names; for (const auto& app : new_apps) new_names.push_back(base::UTF8ToUTF16(app->name())); OnClientsChanged(profile, new_names); } /////////////////////////////////////////////////////////////////////////////// // BackgroundModeManager, ProfileInfoCacheObserver overrides void BackgroundModeManager::OnProfileAdded(const base::FilePath& profile_path) { ProfileInfoCache& cache = g_browser_process->profile_manager()->GetProfileInfoCache(); base::string16 profile_name = cache.GetNameOfProfileAtIndex( cache.GetIndexOfProfileWithPath(profile_path)); // At this point, the profile should be registered with the background mode // manager, but when it's actually added to the cache 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) { ProfileInfoCache& cache = g_browser_process->profile_manager()->GetProfileInfoCache(); base::string16 profile_name = cache.GetNameOfProfileAtIndex( cache.GetIndexOfProfileWithPath(profile_path)); // 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) { ProfileInfoCache& cache = g_browser_process->profile_manager()->GetProfileInfoCache(); base::string16 new_profile_name = cache.GetNameOfProfileAtIndex( cache.GetIndexOfProfileWithPath(profile_path)); 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 NULL; // Do not permit a locked profile to be used to open a browser. ProfileInfoCache& cache = g_browser_process->profile_manager()->GetProfileInfoCache(); if (cache.ProfileIsSigninRequiredAtIndex(cache.GetIndexOfProfileWithPath( profile_background_data->first->GetPath()))) return NULL; 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_NO_TUTORIAL, 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_NO_TUTORIAL, profiles::USER_MANAGER_SELECT_PROFILE_TASK_MANAGER); } break; case IDC_EXIT: RecordMenuItemClick(MENU_ITEM_EXIT); content::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(chrome::WillKeepAlive()); 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_NO_TUTORIAL, profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION); } break; } } /////////////////////////////////////////////////////////////////////////////// // BackgroundModeManager, private void BackgroundModeManager::DecrementKeepAliveCountForStartup() { if (keep_alive_for_startup_) { keep_alive_for_startup_ = false; // 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. base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&chrome::DecrementKeepAliveCount)); } } 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; // Mark ourselves as running in background mode. in_background_mode_ = true; UpdateKeepAliveAndTrayIcon(); content::NotificationService::current()->Notify( chrome::NOTIFICATION_BACKGROUND_MODE_CHANGED, content::Source(this), content::Details(&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(this), content::Details(&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->GetPendingTriggerData(); bmd_iterator.second->ClearPendingTriggerData(); for (const auto& trigger_data_iterator : pending_trigger_data) { 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 (!keeping_alive_) { keeping_alive_ = true; chrome::IncrementKeepAliveCount(); } CreateStatusTrayIcon(); return; } RemoveStatusTrayIcon(); if (keeping_alive_) { keeping_alive_ = false; chrome::DecrementKeepAliveCount(); } } void BackgroundModeManager::OnBrowserAdded(Browser* browser) { ResumeBackgroundMode(); } void BackgroundModeManager::OnClientsChanged( Profile* profile, const std::vector& new_client_names) { DCHECK(IsBackgroundModePrefEnabled()); // Update the profile cache with the fact whether background clients are // running for this profile. size_t profile_index = profile_cache_->GetIndexOfProfileWithPath(profile->GetPath()); if (profile_index != std::string::npos) { profile_cache_->SetBackgroundStatusOfProfileAtIndex( profile_index, GetBackgroundClientCountForProfile(profile) != 0); } 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); } } int BackgroundModeManager::GetBackgroundClientCount() const { int count = 0; // Walk the BackgroundModeData for all profiles and count the number of // clients. for (const auto& it : background_mode_data_) count += it.second->GetBackgroundClientCount(); DCHECK(count >= 0); return count; } int BackgroundModeManager::GetBackgroundClientCountForProfile( Profile* const profile) const { BackgroundModeManager::BackgroundModeData* bmd = GetBackgroundModeData(profile); if (!bmd) return 0; return bmd->GetBackgroundClientCount(); } int BackgroundModeManager::GetPendingTriggerCount() const { int count = 0; for (const auto& it : background_mode_data_) count += it.second->GetPendingTriggerCount(); return count; } bool BackgroundModeManager::ShouldBeInBackgroundMode() const { return IsBackgroundModePrefEnabled() && (GetBackgroundClientCount() > 0 || GetPendingTriggerCount() > 0 || 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. scoped_ptr 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(); scoped_ptr 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_cache_->GetNumberOfProfiles() > 1) { std::vector 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 (const auto& bmd : bmd_vector) { // We should only display the profile in the status icon if it has at // least one background app. if (bmd->GetBackgroundClientCount() > 0) { StatusIconMenuModel* submenu = new StatusIconMenuModel(bmd); // The submenu constructor caller owns the lifetime of the submenu. // The containing menu does not handle the lifetime. submenus.push_back(submenu); bmd->BuildProfileMenu(submenu, 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 cache if we are not // using multi-profiles. If |keep_alive_for_test_| is set, then we may not // have any profiles in the cache. DCHECK(profile_cache_->GetNumberOfProfiles() == size_t(1) || keep_alive_for_test_); background_mode_data_.begin()->second->BuildProfileMenu(menu.get(), NULL); } 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_ = NULL; context_menu_ = NULL; } BackgroundModeManager::BackgroundModeData* BackgroundModeManager::GetBackgroundModeData(Profile* const profile) const { // Profiles are shut down and destroyed asynchronously after // OnProfileWillBeRemoved is called, so we may have dropped anything // associated with the profile already. if (background_mode_data_.find(profile) == background_mode_data_.end()) return NULL; return background_mode_data_.find(profile)->second.get(); } 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); }