Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <string>
      6 
      7 #include "base/base_paths.h"
      8 #include "base/command_line.h"
      9 #include "base/logging.h"
     10 #include "base/utf_string_conversions.h"
     11 #include "chrome/app/chrome_command_ids.h"
     12 #include "chrome/browser/background_application_list_model.h"
     13 #include "chrome/browser/background_mode_manager.h"
     14 #include "chrome/browser/extensions/extension_service.h"
     15 #include "chrome/browser/metrics/user_metrics.h"
     16 #include "chrome/browser/profiles/profile.h"
     17 #include "chrome/browser/status_icons/status_icon.h"
     18 #include "chrome/browser/status_icons/status_tray.h"
     19 #include "chrome/browser/ui/browser_list.h"
     20 #include "chrome/common/chrome_switches.h"
     21 #include "chrome/common/extensions/extension.h"
     22 #include "chrome/common/pref_names.h"
     23 #include "content/common/notification_service.h"
     24 #include "content/common/notification_type.h"
     25 #include "grit/chromium_strings.h"
     26 #include "grit/generated_resources.h"
     27 #include "grit/theme_resources.h"
     28 #include "ui/base/l10n/l10n_util.h"
     29 #include "ui/base/resource/resource_bundle.h"
     30 
     31 void BackgroundModeManager::OnApplicationDataChanged(
     32     const Extension* extension) {
     33   UpdateContextMenuEntryIcon(extension);
     34 }
     35 
     36 void BackgroundModeManager::OnApplicationListChanged() {
     37   UpdateStatusTrayIconContextMenu();
     38 }
     39 
     40 BackgroundModeManager::BackgroundModeManager(Profile* profile,
     41                                              CommandLine* command_line)
     42     : profile_(profile),
     43       applications_(profile),
     44       background_app_count_(0),
     45       context_menu_(NULL),
     46       context_menu_application_offset_(0),
     47       in_background_mode_(false),
     48       keep_alive_for_startup_(false),
     49       status_tray_(NULL),
     50       status_icon_(NULL) {
     51   // If background mode is disabled, just exit - don't listen for any
     52   // notifications.
     53   if (!IsBackgroundModeEnabled(command_line))
     54     return;
     55 
     56   // Keep the browser alive until extensions are done loading - this is needed
     57   // by the --no-startup-window flag. We want to stay alive until we load
     58   // extensions, at which point we should either run in background mode (if
     59   // there are background apps) or exit if there are none.
     60   if (command_line->HasSwitch(switches::kNoStartupWindow)) {
     61     keep_alive_for_startup_ = true;
     62     BrowserList::StartKeepAlive();
     63   }
     64 
     65   // If the -keep-alive-for-test flag is passed, then always keep chrome running
     66   // in the background until the user explicitly terminates it, by acting as if
     67   // we loaded a background app.
     68   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKeepAliveForTest))
     69     OnBackgroundAppLoaded();
     70 
     71   // Listen for when extensions are loaded/unloaded so we can track the
     72   // number of background apps and modify our keep-alive and launch-on-startup
     73   // state appropriately.
     74   registrar_.Add(this, NotificationType::EXTENSION_LOADED,
     75                  Source<Profile>(profile));
     76   registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
     77                  Source<Profile>(profile));
     78 
     79   // Check for the presence of background apps after all extensions have been
     80   // loaded, to handle the case where an extension has been manually removed
     81   // while Chrome was not running.
     82   registrar_.Add(this, NotificationType::EXTENSIONS_READY,
     83                  Source<Profile>(profile));
     84 
     85   // Listen for the application shutting down so we can decrement our KeepAlive
     86   // count.
     87   registrar_.Add(this, NotificationType::APP_TERMINATING,
     88                  NotificationService::AllSources());
     89 
     90   applications_.AddObserver(this);
     91 }
     92 
     93 BackgroundModeManager::~BackgroundModeManager() {
     94   applications_.RemoveObserver(this);
     95 
     96   // We're going away, so exit background mode (does nothing if we aren't in
     97   // background mode currently). This is primarily needed for unit tests,
     98   // because in an actual running system we'd get an APP_TERMINATING
     99   // notification before being destroyed.
    100   EndBackgroundMode();
    101 }
    102 
    103 void BackgroundModeManager::Observe(NotificationType type,
    104                                     const NotificationSource& source,
    105                                     const NotificationDetails& details) {
    106   switch (type.value) {
    107     case NotificationType::EXTENSIONS_READY:
    108       // Extensions are loaded, so we don't need to manually keep the browser
    109       // process alive any more when running in no-startup-window mode.
    110       EndKeepAliveForStartup();
    111 
    112       // On a Mac, we use 'login items' mechanism which has user-facing UI so we
    113       // don't want to stomp on user choice every time we start and load
    114       // registered extensions. This means that if a background app is removed
    115       // or added while Chrome is not running, we could leave Chrome in the
    116       // wrong state, but this is better than constantly forcing Chrome to
    117       // launch on startup even after the user removes the LoginItem manually.
    118 #if !defined(OS_MACOSX)
    119       EnableLaunchOnStartup(background_app_count_ > 0);
    120 #endif
    121       break;
    122     case NotificationType::EXTENSION_LOADED: {
    123         Extension* extension = Details<Extension>(details).ptr();
    124         if (BackgroundApplicationListModel::IsBackgroundApp(*extension)) {
    125           // Extensions loaded after the ExtensionsService is ready should be
    126           // treated as new installs.
    127           if (profile_->GetExtensionService()->is_ready())
    128             OnBackgroundAppInstalled(extension);
    129           OnBackgroundAppLoaded();
    130         }
    131       }
    132       break;
    133     case NotificationType::EXTENSION_UNLOADED:
    134       if (BackgroundApplicationListModel::IsBackgroundApp(
    135               *Details<UnloadedExtensionInfo>(details)->extension)) {
    136         Details<UnloadedExtensionInfo> info =
    137             Details<UnloadedExtensionInfo>(details);
    138         // If we already got an unload notification when it was disabled, ignore
    139         // this one.
    140         // TODO(atwilson): Change BackgroundModeManager to use
    141         // BackgroundApplicationListModel instead of tracking the count here.
    142         if (info->already_disabled)
    143           return;
    144         OnBackgroundAppUnloaded();
    145         OnBackgroundAppUninstalled();
    146       }
    147       break;
    148     case NotificationType::APP_TERMINATING:
    149       // Make sure we aren't still keeping the app alive (only happens if we
    150       // don't receive an EXTENSIONS_READY notification for some reason).
    151       EndKeepAliveForStartup();
    152       // Performing an explicit shutdown, so exit background mode (does nothing
    153       // if we aren't in background mode currently).
    154       EndBackgroundMode();
    155       // Shutting down, so don't listen for any more notifications so we don't
    156       // try to re-enter/exit background mode again.
    157       registrar_.RemoveAll();
    158       break;
    159     default:
    160       NOTREACHED();
    161       break;
    162   }
    163 }
    164 
    165 void BackgroundModeManager::EndKeepAliveForStartup() {
    166   if (keep_alive_for_startup_) {
    167     keep_alive_for_startup_ = false;
    168     // We call this via the message queue to make sure we don't try to end
    169     // keep-alive (which can shutdown Chrome) before the message loop has
    170     // started.
    171     MessageLoop::current()->PostTask(
    172         FROM_HERE, NewRunnableFunction(BrowserList::EndKeepAlive));
    173   }
    174 }
    175 
    176 void BackgroundModeManager::OnBackgroundAppLoaded() {
    177   // When a background app loads, increment our count and also enable
    178   // KeepAlive mode if the preference is set.
    179   background_app_count_++;
    180   if (background_app_count_ == 1)
    181     StartBackgroundMode();
    182 }
    183 
    184 void BackgroundModeManager::StartBackgroundMode() {
    185   // Don't bother putting ourselves in background mode if we're already there.
    186   if (in_background_mode_)
    187     return;
    188 
    189   // Mark ourselves as running in background mode.
    190   in_background_mode_ = true;
    191 
    192   // Put ourselves in KeepAlive mode and create a status tray icon.
    193   BrowserList::StartKeepAlive();
    194 
    195   // Display a status icon to exit Chrome.
    196   CreateStatusTrayIcon();
    197 }
    198 
    199 void BackgroundModeManager::OnBackgroundAppUnloaded() {
    200   // When a background app unloads, decrement our count and also end
    201   // KeepAlive mode if appropriate.
    202   background_app_count_--;
    203   DCHECK(background_app_count_ >= 0);
    204   if (background_app_count_ == 0)
    205     EndBackgroundMode();
    206 }
    207 
    208 void BackgroundModeManager::EndBackgroundMode() {
    209   if (!in_background_mode_)
    210     return;
    211   in_background_mode_ = false;
    212 
    213   // End KeepAlive mode and blow away our status tray icon.
    214   BrowserList::EndKeepAlive();
    215   RemoveStatusTrayIcon();
    216 }
    217 
    218 void BackgroundModeManager::OnBackgroundAppInstalled(
    219     const Extension* extension) {
    220   // We're installing a background app. If this is the first background app
    221   // being installed, make sure we are set to launch on startup.
    222   if (background_app_count_ == 0)
    223     EnableLaunchOnStartup(true);
    224 
    225   // Notify the user that a background app has been installed.
    226   if (extension)  // NULL when called by unit tests.
    227     DisplayAppInstalledNotification(extension);
    228 }
    229 
    230 void BackgroundModeManager::OnBackgroundAppUninstalled() {
    231   // When uninstalling a background app, disable launch on startup if
    232   // we have no more background apps.
    233   if (background_app_count_ == 0)
    234     EnableLaunchOnStartup(false);
    235 }
    236 
    237 void BackgroundModeManager::CreateStatusTrayIcon() {
    238   // Only need status icons on windows/linux. ChromeOS doesn't allow exiting
    239   // Chrome and Mac can use the dock icon instead.
    240 #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
    241   if (!status_tray_)
    242     status_tray_ = profile_->GetStatusTray();
    243 #endif
    244 
    245   // If the platform doesn't support status icons, or we've already created
    246   // our status icon, just return.
    247   if (!status_tray_ || status_icon_)
    248     return;
    249   status_icon_ = status_tray_->CreateStatusIcon();
    250   if (!status_icon_)
    251     return;
    252 
    253   // Set the image and add ourselves as a click observer on it
    254   SkBitmap* bitmap = ResourceBundle::GetSharedInstance().GetBitmapNamed(
    255       IDR_STATUS_TRAY_ICON);
    256   status_icon_->SetImage(*bitmap);
    257   status_icon_->SetToolTip(l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
    258   UpdateStatusTrayIconContextMenu();
    259 }
    260 
    261 void BackgroundModeManager::UpdateContextMenuEntryIcon(
    262     const Extension* extension) {
    263   if (!context_menu_)
    264     return;
    265   context_menu_->SetIcon(
    266       context_menu_application_offset_ + applications_.GetPosition(extension),
    267       *(applications_.GetIcon(extension)));
    268   status_icon_->SetContextMenu(context_menu_);  // for Update effect
    269 }
    270 
    271 void BackgroundModeManager::UpdateStatusTrayIconContextMenu() {
    272   if (!status_icon_)
    273     return;
    274 
    275   // Create a context menu item for Chrome.
    276   ui::SimpleMenuModel* menu = new ui::SimpleMenuModel(this);
    277   // Add About item
    278   menu->AddItem(IDC_ABOUT, l10n_util::GetStringFUTF16(IDS_ABOUT,
    279       l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
    280   menu->AddItem(IDC_OPTIONS, GetPreferencesMenuLabel());
    281   menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
    282   menu->AddSeparator();
    283   int position = 0;
    284   context_menu_application_offset_ = menu->GetItemCount();
    285   for (ExtensionList::const_iterator cursor = applications_.begin();
    286        cursor != applications_.end();
    287        ++cursor, ++position) {
    288     const SkBitmap* icon = applications_.GetIcon(*cursor);
    289     DCHECK(position == applications_.GetPosition(*cursor));
    290     const std::string& name = (*cursor)->name();
    291     menu->AddItem(position, UTF8ToUTF16(name));
    292     if (icon)
    293       menu->SetIcon(menu->GetItemCount() - 1, *icon);
    294   }
    295   if (applications_.size() > 0)
    296     menu->AddSeparator();
    297   menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT);
    298   context_menu_ = menu;
    299   status_icon_->SetContextMenu(menu);
    300 }
    301 
    302 bool BackgroundModeManager::IsCommandIdChecked(int command_id) const {
    303   return false;
    304 }
    305 
    306 bool BackgroundModeManager::IsCommandIdEnabled(int command_id) const {
    307   // For now, we do not support disabled items.
    308   return true;
    309 }
    310 
    311 bool BackgroundModeManager::GetAcceleratorForCommandId(
    312     int command_id,
    313     ui::Accelerator* accelerator) {
    314   // No accelerators for status icon context menus.
    315   return false;
    316 }
    317 
    318 void BackgroundModeManager::RemoveStatusTrayIcon() {
    319   if (status_icon_)
    320     status_tray_->RemoveStatusIcon(status_icon_);
    321   status_icon_ = NULL;
    322   context_menu_ = NULL;  // Do not delete, points within status_icon_
    323 }
    324 
    325 void BackgroundModeManager::ExecuteApplication(int item) {
    326   DCHECK(item >= 0 && item < static_cast<int>(applications_.size()));
    327   Browser* browser = BrowserList::GetLastActive();
    328   if (!browser) {
    329     Browser::OpenEmptyWindow(profile_);
    330     browser = BrowserList::GetLastActive();
    331   }
    332   const Extension* extension = applications_.GetExtension(item);
    333   browser->OpenApplicationTab(profile_, extension, NULL);
    334 }
    335 
    336 void BackgroundModeManager::ExecuteCommand(int item) {
    337   switch (item) {
    338     case IDC_ABOUT:
    339       GetBrowserWindow()->OpenAboutChromeDialog();
    340       break;
    341     case IDC_EXIT:
    342       UserMetrics::RecordAction(UserMetricsAction("Exit"), profile_);
    343       BrowserList::CloseAllBrowsersAndExit();
    344       break;
    345     case IDC_OPTIONS:
    346       GetBrowserWindow()->OpenOptionsDialog();
    347       break;
    348     case IDC_TASK_MANAGER:
    349       GetBrowserWindow()->OpenTaskManager(true);
    350       break;
    351     default:
    352       ExecuteApplication(item);
    353       break;
    354   }
    355 }
    356 
    357 Browser* BackgroundModeManager::GetBrowserWindow() {
    358   Browser* browser = BrowserList::GetLastActive();
    359   if (!browser) {
    360     Browser::OpenEmptyWindow(profile_);
    361     browser = BrowserList::GetLastActive();
    362   }
    363   return browser;
    364 }
    365 
    366 // static
    367 bool BackgroundModeManager::IsBackgroundModeEnabled(
    368     const CommandLine* command_line) {
    369 
    370   // Background mode is disabled if the appropriate flag is passed, or if
    371   // extensions are disabled. It's always disabled on chromeos since chrome
    372   // is always running on that platform, making it superfluous.
    373 #if defined(OS_CHROMEOS)
    374   return false;
    375 #else
    376   bool background_mode_enabled =
    377       !command_line->HasSwitch(switches::kDisableBackgroundMode) &&
    378       !command_line->HasSwitch(switches::kDisableExtensions);
    379   return background_mode_enabled;
    380 #endif
    381 }
    382 
    383 // static
    384 void BackgroundModeManager::RegisterPrefs(PrefService* prefs) {
    385   prefs->RegisterBooleanPref(prefs::kUserCreatedLoginItem, false);
    386 }
    387