• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
OnApplicationDataChanged(const Extension * extension)31 void BackgroundModeManager::OnApplicationDataChanged(
32     const Extension* extension) {
33   UpdateContextMenuEntryIcon(extension);
34 }
35 
OnApplicationListChanged()36 void BackgroundModeManager::OnApplicationListChanged() {
37   UpdateStatusTrayIconContextMenu();
38 }
39 
BackgroundModeManager(Profile * profile,CommandLine * command_line)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 
~BackgroundModeManager()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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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 
EndKeepAliveForStartup()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 
OnBackgroundAppLoaded()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 
StartBackgroundMode()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 
OnBackgroundAppUnloaded()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 
EndBackgroundMode()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 
OnBackgroundAppInstalled(const Extension * extension)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 
OnBackgroundAppUninstalled()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 
CreateStatusTrayIcon()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 
UpdateContextMenuEntryIcon(const Extension * extension)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 
UpdateStatusTrayIconContextMenu()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 
IsCommandIdChecked(int command_id) const302 bool BackgroundModeManager::IsCommandIdChecked(int command_id) const {
303   return false;
304 }
305 
IsCommandIdEnabled(int command_id) const306 bool BackgroundModeManager::IsCommandIdEnabled(int command_id) const {
307   // For now, we do not support disabled items.
308   return true;
309 }
310 
GetAcceleratorForCommandId(int command_id,ui::Accelerator * accelerator)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 
RemoveStatusTrayIcon()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 
ExecuteApplication(int item)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 
ExecuteCommand(int item)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 
GetBrowserWindow()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
IsBackgroundModeEnabled(const CommandLine * command_line)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
RegisterPrefs(PrefService * prefs)384 void BackgroundModeManager::RegisterPrefs(PrefService* prefs) {
385   prefs->RegisterBooleanPref(prefs::kUserCreatedLoginItem, false);
386 }
387