// Copyright (c) 2011 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 <string> #include "base/base_paths.h" #include "base/command_line.h" #include "base/logging.h" #include "base/utf_string_conversions.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/background_application_list_model.h" #include "chrome/browser/background_mode_manager.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/metrics/user_metrics.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/status_icons/status_icon.h" #include "chrome/browser/status_icons/status_tray.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/pref_names.h" #include "content/common/notification_service.h" #include "content/common/notification_type.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" void BackgroundModeManager::OnApplicationDataChanged( const Extension* extension) { UpdateContextMenuEntryIcon(extension); } void BackgroundModeManager::OnApplicationListChanged() { UpdateStatusTrayIconContextMenu(); } BackgroundModeManager::BackgroundModeManager(Profile* profile, CommandLine* command_line) : profile_(profile), applications_(profile), background_app_count_(0), context_menu_(NULL), context_menu_application_offset_(0), in_background_mode_(false), keep_alive_for_startup_(false), status_tray_(NULL), status_icon_(NULL) { // If background mode is disabled, just exit - don't listen for any // notifications. if (!IsBackgroundModeEnabled(command_line)) return; // 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; BrowserList::StartKeepAlive(); } // If the -keep-alive-for-test flag is passed, then always keep chrome running // in the background until the user explicitly terminates it, by acting as if // we loaded a background app. if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKeepAliveForTest)) OnBackgroundAppLoaded(); // Listen for when extensions are loaded/unloaded so we can track the // number of background apps and modify our keep-alive and launch-on-startup // state appropriately. registrar_.Add(this, NotificationType::EXTENSION_LOADED, Source<Profile>(profile)); registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, Source<Profile>(profile)); // 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. registrar_.Add(this, NotificationType::EXTENSIONS_READY, Source<Profile>(profile)); // Listen for the application shutting down so we can decrement our KeepAlive // count. registrar_.Add(this, NotificationType::APP_TERMINATING, NotificationService::AllSources()); applications_.AddObserver(this); } BackgroundModeManager::~BackgroundModeManager() { applications_.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(); } void BackgroundModeManager::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::EXTENSIONS_READY: // Extensions are loaded, so we don't need to manually keep the browser // process alive any more when running in no-startup-window mode. EndKeepAliveForStartup(); // On a Mac, we use 'login items' mechanism which has user-facing UI so we // don't want to stomp on user choice every time we start and load // registered extensions. This means that if a background app is removed // or added while Chrome is not running, we could leave Chrome in the // wrong state, but this is better than constantly forcing Chrome to // launch on startup even after the user removes the LoginItem manually. #if !defined(OS_MACOSX) EnableLaunchOnStartup(background_app_count_ > 0); #endif break; case NotificationType::EXTENSION_LOADED: { Extension* extension = Details<Extension>(details).ptr(); if (BackgroundApplicationListModel::IsBackgroundApp(*extension)) { // Extensions loaded after the ExtensionsService is ready should be // treated as new installs. if (profile_->GetExtensionService()->is_ready()) OnBackgroundAppInstalled(extension); OnBackgroundAppLoaded(); } } break; case NotificationType::EXTENSION_UNLOADED: if (BackgroundApplicationListModel::IsBackgroundApp( *Details<UnloadedExtensionInfo>(details)->extension)) { Details<UnloadedExtensionInfo> info = Details<UnloadedExtensionInfo>(details); // If we already got an unload notification when it was disabled, ignore // this one. // TODO(atwilson): Change BackgroundModeManager to use // BackgroundApplicationListModel instead of tracking the count here. if (info->already_disabled) return; OnBackgroundAppUnloaded(); OnBackgroundAppUninstalled(); } break; case NotificationType::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). EndKeepAliveForStartup(); // 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(); break; default: NOTREACHED(); break; } } void BackgroundModeManager::EndKeepAliveForStartup() { 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. MessageLoop::current()->PostTask( FROM_HERE, NewRunnableFunction(BrowserList::EndKeepAlive)); } } void BackgroundModeManager::OnBackgroundAppLoaded() { // When a background app loads, increment our count and also enable // KeepAlive mode if the preference is set. background_app_count_++; if (background_app_count_ == 1) StartBackgroundMode(); } void BackgroundModeManager::StartBackgroundMode() { // Don't bother putting ourselves in background mode if we're already there. if (in_background_mode_) return; // Mark ourselves as running in background mode. in_background_mode_ = true; // Put ourselves in KeepAlive mode and create a status tray icon. BrowserList::StartKeepAlive(); // Display a status icon to exit Chrome. CreateStatusTrayIcon(); } void BackgroundModeManager::OnBackgroundAppUnloaded() { // When a background app unloads, decrement our count and also end // KeepAlive mode if appropriate. background_app_count_--; DCHECK(background_app_count_ >= 0); if (background_app_count_ == 0) EndBackgroundMode(); } void BackgroundModeManager::EndBackgroundMode() { if (!in_background_mode_) return; in_background_mode_ = false; // End KeepAlive mode and blow away our status tray icon. BrowserList::EndKeepAlive(); RemoveStatusTrayIcon(); } void BackgroundModeManager::OnBackgroundAppInstalled( const Extension* extension) { // We're installing a background app. If this is the first background app // being installed, make sure we are set to launch on startup. if (background_app_count_ == 0) EnableLaunchOnStartup(true); // Notify the user that a background app has been installed. if (extension) // NULL when called by unit tests. DisplayAppInstalledNotification(extension); } void BackgroundModeManager::OnBackgroundAppUninstalled() { // When uninstalling a background app, disable launch on startup if // we have no more background apps. if (background_app_count_ == 0) EnableLaunchOnStartup(false); } void BackgroundModeManager::CreateStatusTrayIcon() { // Only need status icons on windows/linux. ChromeOS doesn't allow exiting // Chrome and Mac can use the dock icon instead. #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) if (!status_tray_) status_tray_ = profile_->GetStatusTray(); #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(); if (!status_icon_) return; // Set the image and add ourselves as a click observer on it SkBitmap* bitmap = ResourceBundle::GetSharedInstance().GetBitmapNamed( IDR_STATUS_TRAY_ICON); status_icon_->SetImage(*bitmap); status_icon_->SetToolTip(l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); UpdateStatusTrayIconContextMenu(); } void BackgroundModeManager::UpdateContextMenuEntryIcon( const Extension* extension) { if (!context_menu_) return; context_menu_->SetIcon( context_menu_application_offset_ + applications_.GetPosition(extension), *(applications_.GetIcon(extension))); status_icon_->SetContextMenu(context_menu_); // for Update effect } void BackgroundModeManager::UpdateStatusTrayIconContextMenu() { if (!status_icon_) return; // Create a context menu item for Chrome. ui::SimpleMenuModel* menu = new ui::SimpleMenuModel(this); // Add About item menu->AddItem(IDC_ABOUT, l10n_util::GetStringFUTF16(IDS_ABOUT, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); menu->AddItem(IDC_OPTIONS, GetPreferencesMenuLabel()); menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER); menu->AddSeparator(); int position = 0; context_menu_application_offset_ = menu->GetItemCount(); for (ExtensionList::const_iterator cursor = applications_.begin(); cursor != applications_.end(); ++cursor, ++position) { const SkBitmap* icon = applications_.GetIcon(*cursor); DCHECK(position == applications_.GetPosition(*cursor)); const std::string& name = (*cursor)->name(); menu->AddItem(position, UTF8ToUTF16(name)); if (icon) menu->SetIcon(menu->GetItemCount() - 1, *icon); } if (applications_.size() > 0) menu->AddSeparator(); menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT); context_menu_ = menu; status_icon_->SetContextMenu(menu); } bool BackgroundModeManager::IsCommandIdChecked(int command_id) const { return false; } bool BackgroundModeManager::IsCommandIdEnabled(int command_id) const { // For now, we do not support disabled items. return true; } bool BackgroundModeManager::GetAcceleratorForCommandId( int command_id, ui::Accelerator* accelerator) { // No accelerators for status icon context menus. return false; } void BackgroundModeManager::RemoveStatusTrayIcon() { if (status_icon_) status_tray_->RemoveStatusIcon(status_icon_); status_icon_ = NULL; context_menu_ = NULL; // Do not delete, points within status_icon_ } void BackgroundModeManager::ExecuteApplication(int item) { DCHECK(item >= 0 && item < static_cast<int>(applications_.size())); Browser* browser = BrowserList::GetLastActive(); if (!browser) { Browser::OpenEmptyWindow(profile_); browser = BrowserList::GetLastActive(); } const Extension* extension = applications_.GetExtension(item); browser->OpenApplicationTab(profile_, extension, NULL); } void BackgroundModeManager::ExecuteCommand(int item) { switch (item) { case IDC_ABOUT: GetBrowserWindow()->OpenAboutChromeDialog(); break; case IDC_EXIT: UserMetrics::RecordAction(UserMetricsAction("Exit"), profile_); BrowserList::CloseAllBrowsersAndExit(); break; case IDC_OPTIONS: GetBrowserWindow()->OpenOptionsDialog(); break; case IDC_TASK_MANAGER: GetBrowserWindow()->OpenTaskManager(true); break; default: ExecuteApplication(item); break; } } Browser* BackgroundModeManager::GetBrowserWindow() { Browser* browser = BrowserList::GetLastActive(); if (!browser) { Browser::OpenEmptyWindow(profile_); browser = BrowserList::GetLastActive(); } return browser; } // static bool BackgroundModeManager::IsBackgroundModeEnabled( const CommandLine* command_line) { // Background mode is disabled if the appropriate flag is passed, or if // extensions are disabled. It's always disabled on chromeos since chrome // is always running on that platform, making it superfluous. #if defined(OS_CHROMEOS) return false; #else bool background_mode_enabled = !command_line->HasSwitch(switches::kDisableBackgroundMode) && !command_line->HasSwitch(switches::kDisableExtensions); return background_mode_enabled; #endif } // static void BackgroundModeManager::RegisterPrefs(PrefService* prefs) { prefs->RegisterBooleanPref(prefs::kUserCreatedLoginItem, false); }