Print Preview: Changing displayed error message when PDF Viewer is missing.
[chromium-blink-merge.git] / chrome / browser / background_mode_manager.cc
blob4e19b21a548e7c2b3b2705bc46630c85795b6e82
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.
5 #include <string>
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/browser_process.h"
15 #include "chrome/browser/extensions/extension_service.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/browser/user_metrics.h"
24 #include "content/common/notification_service.h"
25 #include "content/common/notification_type.h"
26 #include "grit/chromium_strings.h"
27 #include "grit/generated_resources.h"
28 #include "grit/theme_resources.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/base/resource/resource_bundle.h"
33 BackgroundModeManager::BackgroundModeData::BackgroundModeData(
34 Profile* profile,
35 BackgroundModeManager* background_mode_manager)
36 : applications_(new BackgroundApplicationListModel(profile)),
37 status_icon_(NULL),
38 context_menu_(NULL),
39 context_menu_application_offset_(0),
40 profile_(profile),
41 background_mode_manager_(background_mode_manager) {
44 BackgroundModeManager::BackgroundModeData::~BackgroundModeData() {
47 ///////////////////////////////////////////////////////////////////////////////
48 // BackgroundModeManager::BackgroundModeData, ui::SimpleMenuModel overrides
49 bool BackgroundModeManager::BackgroundModeData::IsCommandIdChecked(
50 int command_id) const {
51 DCHECK(command_id == IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND);
52 return true;
55 bool BackgroundModeManager::BackgroundModeData::IsCommandIdEnabled(
56 int command_id) const {
57 // For now, we do not support disabled items.
58 return true;
61 bool BackgroundModeManager::BackgroundModeData::GetAcceleratorForCommandId(
62 int command_id, ui::Accelerator* accelerator) {
63 // No accelerators for status icon context menus.
64 return false;
67 void BackgroundModeManager::BackgroundModeData::ExecuteCommand(int item) {
68 switch (item) {
69 case IDC_ABOUT:
70 GetBrowserWindow()->OpenAboutChromeDialog();
71 break;
72 case IDC_EXIT:
73 UserMetrics::RecordAction(UserMetricsAction("Exit"));
74 BrowserList::CloseAllBrowsersAndExit();
75 break;
76 case IDC_OPTIONS:
77 GetBrowserWindow()->OpenOptionsDialog();
78 break;
79 case IDC_TASK_MANAGER:
80 GetBrowserWindow()->OpenTaskManager(true);
81 break;
82 case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND: {
83 // Background mode must already be enabled (as otherwise this menu would
84 // not be visible).
85 DCHECK(background_mode_manager_->IsBackgroundModePrefEnabled());
86 DCHECK(BrowserList::WillKeepAlive());
88 // Set the background mode pref to "disabled" - the resulting notification
89 // will result in a call to DisableBackgroundMode().
90 PrefService* service = g_browser_process->local_state();
91 DCHECK(service);
92 service->SetBoolean(prefs::kBackgroundModeEnabled, false);
93 break;
95 default:
96 ExecuteApplication(item);
97 break;
101 void BackgroundModeManager::BackgroundModeData::ExecuteApplication(
102 int item) {
103 Browser* browser = GetBrowserWindow();
104 const Extension* extension = applications_->GetExtension(item);
105 browser->OpenApplicationTab(profile_, extension, NEW_FOREGROUND_TAB);
108 Browser* BackgroundModeManager::BackgroundModeData::GetBrowserWindow() {
109 Browser* browser = BrowserList::GetLastActiveWithProfile(profile_);
110 if (!browser) {
111 Browser::OpenEmptyWindow(profile_);
112 browser = BrowserList::GetLastActiveWithProfile(profile_);
114 return browser;
117 void BackgroundModeManager::BackgroundModeData::UpdateContextMenuEntryIcon(
118 const Extension* extension) {
119 if (!context_menu_)
120 return;
121 context_menu_->SetIcon(
122 context_menu_application_offset_ +
123 applications_->GetPosition(extension),
124 *(applications_->GetIcon(extension)));
126 status_icon_->SetContextMenu(context_menu_); // for Update effect
129 bool BackgroundModeManager::BackgroundModeData::HasBackgroundApp() {
130 return (applications_->size() > 0);
133 ///////////////////////////////////////////////////////////////////////////////
134 // BackgroundModeManager, public
135 BackgroundModeManager::BackgroundModeManager(CommandLine* command_line)
136 : status_tray_(NULL),
137 background_app_count_(0),
138 in_background_mode_(false),
139 keep_alive_for_startup_(false) {
140 // If background mode is currently disabled, just exit - don't listen for any
141 // notifications.
142 if (IsBackgroundModePermanentlyDisabled(command_line))
143 return;
145 // Listen for the background mode preference changing.
146 if (g_browser_process->local_state()) { // Skip for unit tests
147 pref_registrar_.Init(g_browser_process->local_state());
148 pref_registrar_.Add(prefs::kBackgroundModeEnabled, this);
151 // Keep the browser alive until extensions are done loading - this is needed
152 // by the --no-startup-window flag. We want to stay alive until we load
153 // extensions, at which point we should either run in background mode (if
154 // there are background apps) or exit if there are none.
155 if (command_line->HasSwitch(switches::kNoStartupWindow)) {
156 keep_alive_for_startup_ = true;
157 BrowserList::StartKeepAlive();
160 // If the -keep-alive-for-test flag is passed, then always keep chrome running
161 // in the background until the user explicitly terminates it, by acting as if
162 // we loaded a background app.
163 if (command_line->HasSwitch(switches::kKeepAliveForTest))
164 OnBackgroundAppLoaded();
166 // Listen for the application shutting down so we can decrement our KeepAlive
167 // count.
168 registrar_.Add(this, NotificationType::APP_TERMINATING,
169 NotificationService::AllSources());
172 BackgroundModeManager::~BackgroundModeManager() {
173 for (std::map<Profile*, BackgroundModeInfo>::iterator it =
174 background_mode_data_.begin();
175 it != background_mode_data_.end();
176 ++it) {
177 it->second->applications_->RemoveObserver(this);
180 // We're going away, so exit background mode (does nothing if we aren't in
181 // background mode currently). This is primarily needed for unit tests,
182 // because in an actual running system we'd get an APP_TERMINATING
183 // notification before being destroyed.
184 EndBackgroundMode();
187 // static
188 void BackgroundModeManager::RegisterPrefs(PrefService* prefs) {
189 prefs->RegisterBooleanPref(prefs::kUserCreatedLoginItem, false);
190 prefs->RegisterBooleanPref(prefs::kBackgroundModeEnabled, true);
194 void BackgroundModeManager::RegisterProfile(Profile* profile) {
195 // We don't want to register multiple times for one profile.
196 DCHECK(background_mode_data_.find(profile) == background_mode_data_.end());
197 BackgroundModeInfo bmd(new BackgroundModeData(profile, this));
198 background_mode_data_[profile] = bmd;
200 // Listen for when extensions are loaded/unloaded so we can track the
201 // number of background apps and modify our keep-alive and launch-on-startup
202 // state appropriately.
203 registrar_.Add(this, NotificationType::EXTENSION_LOADED,
204 Source<Profile>(profile));
205 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
206 Source<Profile>(profile));
208 // Check for the presence of background apps after all extensions have been
209 // loaded, to handle the case where an extension has been manually removed
210 // while Chrome was not running.
211 registrar_.Add(this, NotificationType::EXTENSIONS_READY,
212 Source<Profile>(profile));
214 background_mode_data_[profile]->applications_->AddObserver(this);
217 ///////////////////////////////////////////////////////////////////////////////
218 // BackgroundModeManager, NotificationObserver overrides
219 void BackgroundModeManager::Observe(NotificationType type,
220 const NotificationSource& source,
221 const NotificationDetails& details) {
222 switch (type.value) {
223 case NotificationType::PREF_CHANGED:
224 DCHECK(*Details<std::string>(details).ptr() ==
225 prefs::kBackgroundModeEnabled);
226 if (IsBackgroundModePrefEnabled())
227 EnableBackgroundMode();
228 else
229 DisableBackgroundMode();
230 break;
231 case NotificationType::EXTENSIONS_READY:
232 // Extensions are loaded, so we don't need to manually keep the browser
233 // process alive any more when running in no-startup-window mode.
234 EndKeepAliveForStartup();
236 // On a Mac, we use 'login items' mechanism which has user-facing UI so we
237 // don't want to stomp on user choice every time we start and load
238 // registered extensions. This means that if a background app is removed
239 // or added while Chrome is not running, we could leave Chrome in the
240 // wrong state, but this is better than constantly forcing Chrome to
241 // launch on startup even after the user removes the LoginItem manually.
242 #if !defined(OS_MACOSX)
243 EnableLaunchOnStartup(background_app_count_ > 0);
244 #endif
245 break;
246 case NotificationType::EXTENSION_LOADED: {
247 Extension* extension = Details<Extension>(details).ptr();
248 if (BackgroundApplicationListModel::IsBackgroundApp(*extension)) {
249 // Extensions loaded after the ExtensionsService is ready should be
250 // treated as new installs.
251 Profile* profile = Source<Profile>(source).ptr();
252 if (profile->GetExtensionService()->is_ready())
253 OnBackgroundAppInstalled(extension, profile);
254 OnBackgroundAppLoaded();
257 break;
258 case NotificationType::EXTENSION_UNLOADED:
259 if (BackgroundApplicationListModel::IsBackgroundApp(
260 *Details<UnloadedExtensionInfo>(details)->extension)) {
261 Details<UnloadedExtensionInfo> info =
262 Details<UnloadedExtensionInfo>(details);
263 // If we already got an unload notification when it was disabled, ignore
264 // this one.
265 // TODO(atwilson): Change BackgroundModeManager to use
266 // BackgroundApplicationListModel instead of tracking the count here.
267 if (info->already_disabled)
268 return;
269 OnBackgroundAppUnloaded();
270 Profile* profile = Source<Profile>(source).ptr();
271 OnBackgroundAppUninstalled(profile);
273 break;
274 case NotificationType::APP_TERMINATING:
275 // Make sure we aren't still keeping the app alive (only happens if we
276 // don't receive an EXTENSIONS_READY notification for some reason).
277 EndKeepAliveForStartup();
278 // Performing an explicit shutdown, so exit background mode (does nothing
279 // if we aren't in background mode currently).
280 EndBackgroundMode();
281 // Shutting down, so don't listen for any more notifications so we don't
282 // try to re-enter/exit background mode again.
283 registrar_.RemoveAll();
284 break;
285 default:
286 NOTREACHED();
287 break;
291 ///////////////////////////////////////////////////////////////////////////////
292 // BackgroundModeManager, BackgroundApplicationListModel::Observer overrides
293 void BackgroundModeManager::OnApplicationDataChanged(
294 const Extension* extension, Profile* profile) {
295 UpdateContextMenuEntryIcon(extension, profile);
298 void BackgroundModeManager::OnApplicationListChanged(Profile* profile) {
299 UpdateStatusTrayIconContextMenu(profile);
303 ///////////////////////////////////////////////////////////////////////////////
304 // BackgroundModeManager, private
305 void BackgroundModeManager::EndKeepAliveForStartup() {
306 if (keep_alive_for_startup_) {
307 keep_alive_for_startup_ = false;
308 // We call this via the message queue to make sure we don't try to end
309 // keep-alive (which can shutdown Chrome) before the message loop has
310 // started.
311 MessageLoop::current()->PostTask(
312 FROM_HERE, NewRunnableFunction(BrowserList::EndKeepAlive));
316 void BackgroundModeManager::OnBackgroundAppLoaded() {
317 // When a background app loads, increment our count and also enable
318 // KeepAlive mode if the preference is set.
319 // The count here is across all profiles since we must have background
320 // mode if there is even one.
321 background_app_count_++;
322 if (background_app_count_ == 1)
323 StartBackgroundMode();
326 void BackgroundModeManager::StartBackgroundMode() {
327 // Don't bother putting ourselves in background mode if we're already there
328 // or if background mode is disabled.
329 if (in_background_mode_ || !IsBackgroundModePrefEnabled())
330 return;
332 // Mark ourselves as running in background mode.
333 in_background_mode_ = true;
335 // Put ourselves in KeepAlive mode and create a status tray icon.
336 BrowserList::StartKeepAlive();
338 // Display a status icon to exit Chrome.
339 InitStatusTrayIcons();
342 void BackgroundModeManager::InitStatusTrayIcons() {
343 // Only initialize status tray icons for those profiles which actually
344 // have a background app running.
345 for (std::map<Profile*, BackgroundModeInfo>::iterator it =
346 background_mode_data_.begin();
347 it != background_mode_data_.end();
348 ++it) {
349 if (it->second->HasBackgroundApp())
350 CreateStatusTrayIcon(it->first);
354 void BackgroundModeManager::OnBackgroundAppUnloaded() {
355 // When a background app unloads, decrement our count and also end
356 // KeepAlive mode if appropriate.
357 background_app_count_--;
358 DCHECK_GE(background_app_count_, 0);
359 if (background_app_count_ == 0)
360 EndBackgroundMode();
363 void BackgroundModeManager::EndBackgroundMode() {
364 if (!in_background_mode_)
365 return;
366 in_background_mode_ = false;
368 // End KeepAlive mode and blow away our status tray icon.
369 BrowserList::EndKeepAlive();
370 // There is a status tray icon for each profile. Blow them all away.
371 for (std::map<Profile*, BackgroundModeInfo>::iterator it =
372 background_mode_data_.begin();
373 it != background_mode_data_.end();
374 ++it) {
375 RemoveStatusTrayIcon(it->first);
379 void BackgroundModeManager::EnableBackgroundMode() {
380 DCHECK(IsBackgroundModePrefEnabled());
381 // If background mode should be enabled, but isn't, turn it on.
382 if (background_app_count_ > 0 && !in_background_mode_) {
383 StartBackgroundMode();
384 EnableLaunchOnStartup(true);
388 void BackgroundModeManager::DisableBackgroundMode() {
389 DCHECK(!IsBackgroundModePrefEnabled());
390 // If background mode is currently enabled, turn it off.
391 if (in_background_mode_) {
392 EndBackgroundMode();
393 EnableLaunchOnStartup(false);
397 void BackgroundModeManager::OnBackgroundAppInstalled(
398 const Extension* extension, Profile* profile) {
399 // Background mode is disabled - don't do anything.
400 if (!IsBackgroundModePrefEnabled())
401 return;
403 // We're installing a background app. If this is the first background app
404 // being installed, make sure we are set to launch on startup.
405 if (background_app_count_ == 0)
406 EnableLaunchOnStartup(true);
408 // Check if we need a status tray icon and make one if we do.
409 CreateStatusTrayIcon(profile);
411 // Notify the user that a background app has been installed.
412 if (extension) // NULL when called by unit tests.
413 DisplayAppInstalledNotification(extension, profile);
416 void BackgroundModeManager::OnBackgroundAppUninstalled(Profile* profile) {
417 // Check if we need to remove the status tray icon if there are no
418 // more background apps.
419 BackgroundModeInfo bmd = GetBackgroundModeInfo(profile);
420 DCHECK(bmd.get());
421 // If there are still background apps for this profile, don't remove
422 // the status tray icon.
423 if (!bmd->HasBackgroundApp())
424 RemoveStatusTrayIcon(profile);
426 // When uninstalling a background app, disable launch on startup if
427 // we have no more background apps.
428 if (background_app_count_ == 0)
429 EnableLaunchOnStartup(false);
432 void BackgroundModeManager::CreateStatusTrayIcon(Profile* profile) {
433 // Only need status icons on windows/linux. ChromeOS doesn't allow exiting
434 // Chrome and Mac can use the dock icon instead.
436 // Since there are multiple profiles which share the status tray, we now
437 // use the browser process to keep track of it.
438 #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
439 if (!status_tray_)
440 status_tray_ = g_browser_process->status_tray();
441 #endif
443 // If the platform doesn't support status icons, or we've already created
444 // our status icon, just return.
445 BackgroundModeInfo bmd = GetBackgroundModeInfo(profile);
446 if (!status_tray_ || bmd->status_icon_)
447 return;
449 bmd->status_icon_ = status_tray_->CreateStatusIcon();
450 if (!bmd->status_icon_)
451 return;
453 // Set the image and add ourselves as a click observer on it.
454 // TODO(rlp): Status tray icon should have submenus for each profile.
455 SkBitmap* bitmap = ResourceBundle::GetSharedInstance().GetBitmapNamed(
456 IDR_STATUS_TRAY_ICON);
457 bmd->status_icon_->SetImage(*bitmap);
458 bmd->status_icon_->SetToolTip(l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
459 UpdateStatusTrayIconContextMenu(profile);
462 void BackgroundModeManager::UpdateContextMenuEntryIcon(
463 const Extension* extension, Profile* profile) {
464 BackgroundModeInfo bmd = GetBackgroundModeInfo(profile);
465 DCHECK(bmd.get());
466 bmd->UpdateContextMenuEntryIcon(extension);
469 void BackgroundModeManager::UpdateStatusTrayIconContextMenu(Profile* profile) {
470 BackgroundModeInfo bmd = GetBackgroundModeInfo(profile);
471 DCHECK(bmd.get());
472 // If no status icon exists, it's either because one wasn't created when
473 // it should have been which can happen when extensions load after the
474 // profile has already been registered with the background mode manager.
475 if (in_background_mode_ && !bmd->status_icon_)
476 CreateStatusTrayIcon(profile);
478 // If we don't have a status icon or one could not be created succesfully,
479 // then no need to continue the update.
480 if (!bmd->status_icon_)
481 return;
483 // TODO(rlp): Add current profile color.
484 // Create a context menu item for Chrome.
485 ui::SimpleMenuModel* menu = new ui::SimpleMenuModel(bmd.get());
486 // Add About item
487 menu->AddItem(IDC_ABOUT, l10n_util::GetStringFUTF16(IDS_ABOUT,
488 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
489 menu->AddItem(IDC_OPTIONS, GetPreferencesMenuLabel());
490 menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
491 menu->AddSeparator();
492 int position = 0;
493 bmd->context_menu_application_offset_ = menu->GetItemCount();
494 for (ExtensionList::const_iterator cursor = bmd->applications_->begin();
495 cursor != bmd->applications_->end();
496 ++cursor, ++position) {
497 const SkBitmap* icon = bmd->applications_->GetIcon(*cursor);
498 DCHECK(position == bmd->applications_->GetPosition(*cursor));
499 const std::string& name = (*cursor)->name();
500 menu->AddItem(position, UTF8ToUTF16(name));
501 if (icon)
502 menu->SetIcon(menu->GetItemCount() - 1, *icon);
504 if (bmd->applications_->size() > 0)
505 menu->AddSeparator();
506 menu->AddCheckItemWithStringId(
507 IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND,
508 IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND);
509 menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT);
510 bmd->context_menu_ = menu;
511 bmd->status_icon_->SetContextMenu(menu);
514 void BackgroundModeManager::RemoveStatusTrayIcon(Profile* profile) {
515 BackgroundModeInfo bmd = GetBackgroundModeInfo(profile);
516 DCHECK(bmd.get());
518 if (bmd->status_icon_)
519 status_tray_->RemoveStatusIcon(bmd->status_icon_);
520 bmd->status_icon_ = NULL;
521 bmd->context_menu_ = NULL; // Do not delete, points within |status_icon_|.
524 BackgroundModeManager::BackgroundModeInfo
525 BackgroundModeManager::GetBackgroundModeInfo(Profile* profile) {
526 DCHECK(background_mode_data_.find(profile) != background_mode_data_.end());
527 return background_mode_data_[profile];
530 // static
531 bool BackgroundModeManager::IsBackgroundModePermanentlyDisabled(
532 const CommandLine* command_line) {
534 // Background mode is disabled if the appropriate flag is passed, or if
535 // extensions are disabled, or if the associated preference is unset. It's
536 // always disabled on chromeos since chrome is always running on that
537 // platform, making it superfluous.
538 #if defined(OS_CHROMEOS)
539 return true;
540 #else
541 bool background_mode_disabled =
542 command_line->HasSwitch(switches::kDisableBackgroundMode) ||
543 command_line->HasSwitch(switches::kDisableExtensions);
544 return background_mode_disabled;
545 #endif
548 bool BackgroundModeManager::IsBackgroundModePrefEnabled() {
549 PrefService* service = g_browser_process->local_state();
550 DCHECK(service);
551 return service->GetBoolean(prefs::kBackgroundModeEnabled);