blob: 06766289626283acd417d8e7eef1217d58305be0 [file] [log] [blame]
// Copyright (c) 2010 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 "app/l10n_util.h"
#include "app/resource_bundle.h"
#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/logging.h"
#include "base/path_service.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/browser_list.h"
#include "chrome/browser/extensions/extensions_service.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/status_icons/status_icon.h"
#include "chrome/browser/status_icons/status_tray.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/notification_type.h"
#include "chrome/common/pref_names.h"
#include "grit/browser_resources.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#if defined(OS_LINUX)
#include <unistd.h>
#include "base/environment.h"
#include "base/file_util.h"
#include "base/nix/xdg_util.h"
#include "base/task.h"
#include "base/utf_string_conversions.h"
#include "chrome/common/chrome_version_info.h"
#endif
#if defined(OS_MACOSX)
#include "base/mac_util.h"
#endif
#if defined(TOOLKIT_GTK)
#include "chrome/browser/gtk/gtk_util.h"
#endif
#if defined(OS_LINUX)
static const FilePath::CharType kAutostart[] = "autostart";
static const FilePath::CharType kConfig[] = ".config";
static const char kXdgConfigHome[] = "XDG_CONFIG_HOME";
#endif
#if defined(OS_WIN)
#include "base/win/registry.h"
const HKEY kBackgroundModeRegistryRootKey = HKEY_CURRENT_USER;
const wchar_t* kBackgroundModeRegistrySubkey =
L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
const wchar_t* kBackgroundModeRegistryKeyName = L"chromium";
#endif
#if defined(OS_LINUX)
namespace {
FilePath GetAutostartDirectory(base::Environment* environment) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
FilePath result =
base::nix::GetXDGDirectory(environment, kXdgConfigHome, kConfig);
result = result.Append(kAutostart);
return result;
}
FilePath GetAutostartFilename(base::Environment* environment) {
FilePath directory = GetAutostartDirectory(environment);
return directory.Append(ShellIntegration::GetDesktopName(environment));
}
} // namespace
class DisableLaunchOnStartupTask : public Task {
public:
virtual void Run() {
scoped_ptr<base::Environment> environment(base::Environment::Create());
if (!file_util::Delete(GetAutostartFilename(environment.get()), false)) {
LOG(WARNING) << "Failed to deregister launch on login.";
}
}
};
// TODO(rickcam): Bug 56280: Share implementation with ShellIntegration
class EnableLaunchOnStartupTask : public Task {
public:
virtual void Run() {
scoped_ptr<base::Environment> environment(base::Environment::Create());
scoped_ptr<chrome::VersionInfo> version_info(new chrome::VersionInfo());
FilePath autostart_directory = GetAutostartDirectory(environment.get());
FilePath autostart_file = GetAutostartFilename(environment.get());
if (!file_util::DirectoryExists(autostart_directory) &&
!file_util::CreateDirectory(autostart_directory)) {
LOG(WARNING)
<< "Failed to register launch on login. No autostart directory.";
return;
}
std::string wrapper_script;
if (!environment->GetVar("CHROME_WRAPPER", &wrapper_script)) {
LOG(WARNING)
<< "Failed to register launch on login. CHROME_WRAPPER not set.";
return;
}
std::string autostart_file_contents =
"[Desktop Entry]\n"
"Type=Application\n"
"Terminal=false\n"
"Exec=" + wrapper_script +
" --enable-background-mode --no-startup-window\n"
"Name=" + version_info->Name() + "\n";
std::string::size_type content_length = autostart_file_contents.length();
if (file_util::WriteFile(autostart_file, autostart_file_contents.c_str(),
content_length) !=
static_cast<int>(content_length)) {
LOG(WARNING) << "Failed to register launch on login. Failed to write "
<< autostart_file.value();
file_util::Delete(GetAutostartFilename(environment.get()), false);
}
}
};
#endif // defined(OS_LINUX)
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();
// When an extension is installed, make sure launch on startup is properly
// set if appropriate. Likewise, turn off launch on startup when the last
// background app is uninstalled.
registrar_.Add(this, NotificationType::EXTENSION_INSTALLED,
Source<Profile>(profile));
registrar_.Add(this, NotificationType::EXTENSION_UNINSTALLED,
Source<Profile>(profile));
// Listen for when extensions are loaded/unloaded so we can track the
// number of background apps.
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();
}
bool BackgroundModeManager::IsLaunchOnStartupResetAllowed() {
return profile_->GetPrefs()->GetBoolean(prefs::kLaunchOnStartupResetAllowed);
}
void BackgroundModeManager::SetLaunchOnStartupResetAllowed(bool allowed) {
profile_->GetPrefs()->SetBoolean(prefs::kLaunchOnStartupResetAllowed,
allowed);
}
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.
#if !defined(OS_MACOSX)
EnableLaunchOnStartup(background_app_count_ > 0);
#endif
break;
case NotificationType::EXTENSION_LOADED:
if (BackgroundApplicationListModel::IsBackgroundApp(
*Details<Extension>(details).ptr())) {
OnBackgroundAppLoaded();
}
break;
case NotificationType::EXTENSION_UNLOADED:
if (BackgroundApplicationListModel::IsBackgroundApp(
*Details<Extension>(details).ptr())) {
OnBackgroundAppUnloaded();
}
break;
case NotificationType::EXTENSION_INSTALLED:
if (BackgroundApplicationListModel::IsBackgroundApp(
*Details<Extension>(details).ptr())) {
OnBackgroundAppInstalled();
}
break;
case NotificationType::EXTENSION_UNINSTALLED:
if (Extension::HasApiPermission(
Details<UninstalledExtensionInfo>(details).ptr()->
extension_api_permissions,
Extension::kBackgroundPermission)) {
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() {
// 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);
}
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::EnableLaunchOnStartup(bool should_launch) {
// This functionality is only defined for default profile, currently.
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUserDataDir))
return;
#if defined(OS_LINUX)
if (should_launch)
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
new EnableLaunchOnStartupTask());
else
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
new DisableLaunchOnStartupTask());
#elif defined(OS_MACOSX)
if (should_launch) {
// Return if Chrome is already a Login Item (avoid overriding user choice).
if (mac_util::CheckLoginItemStatus(NULL))
return;
mac_util::AddToLoginItems(true); // Hide on startup.
// Remember we set Login Item, not the user - so we can reset it later.
SetLaunchOnStartupResetAllowed(true);
} else {
// If we didn't set Login Item, don't mess with it.
if (!IsLaunchOnStartupResetAllowed())
return;
SetLaunchOnStartupResetAllowed(false);
// Check if Chrome is not a login Item, or is a Login Item but w/o 'hidden'
// flag - most likely user has modified the setting, don't override it.
bool is_hidden = false;
if (!mac_util::CheckLoginItemStatus(&is_hidden) || !is_hidden)
return;
mac_util::RemoveFromLoginItems();
}
#elif defined(OS_WIN)
// TODO(rickcam): Bug 53597: Make RegKey mockable.
// TODO(rickcam): Bug 53600: Use distinct registry keys per flavor+profile.
const wchar_t* key_name = kBackgroundModeRegistryKeyName;
base::win::RegKey read_key(kBackgroundModeRegistryRootKey,
kBackgroundModeRegistrySubkey, KEY_READ);
base::win::RegKey write_key(kBackgroundModeRegistryRootKey,
kBackgroundModeRegistrySubkey, KEY_WRITE);
if (should_launch) {
FilePath executable;
if (!PathService::Get(base::FILE_EXE, &executable))
return;
std::wstring new_value = executable.value() +
L" --enable-background-mode --no-startup-window";
if (read_key.ValueExists(key_name)) {
std::wstring current_value;
if (read_key.ReadValue(key_name, &current_value) &&
(current_value == new_value))
return;
}
if (!write_key.WriteValue(key_name, new_value.c_str()))
LOG(WARNING) << "Failed to register launch on login.";
} else {
if (read_key.ValueExists(key_name) && !write_key.DeleteValue(key_name))
LOG(WARNING) << "Failed to deregister launch on login.";
}
#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.
#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.
menus::SimpleMenuModel* menu = new menus::SimpleMenuModel(this);
// Add About item
menu->AddItem(IDC_ABOUT, l10n_util::GetStringFUTF16(IDS_ABOUT,
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
// Add Preferences item
#if defined(OS_CHROMEOS)
menu->AddItemWithStringId(IDC_OPTIONS, IDS_SETTINGS);
#elif defined(TOOLKIT_GTK)
string16 preferences = gtk_util::GetStockPreferencesMenuLabel();
if (!preferences.empty())
menu->AddItem(IDC_OPTIONS, preferences);
else
menu->AddItemWithStringId(IDC_OPTIONS, IDS_PREFERENCES);
#else
menu->AddItemWithStringId(IDC_OPTIONS, IDS_OPTIONS);
#endif
menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
menu->AddSeparator();
int application_position = 0;
context_menu_application_offset_ = menu->GetItemCount();
for (ExtensionList::const_iterator cursor = applications_.begin();
cursor != applications_.end();
++cursor, ++application_position) {
const SkBitmap* icon = applications_.GetIcon(*cursor);
int sort_position = applications_.GetPosition(*cursor);
DCHECK(sort_position == application_position);
const std::string& name = (*cursor)->name();
menu->AddItem(sort_position, ASCIIToUTF16(name));
if (icon)
menu->SetIcon(menu->GetItemCount() - 1, *icon);
}
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,
menus::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;
}
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();
break;
default:
ExecuteApplication(item);
break;
}
}
Browser* BackgroundModeManager::GetBrowserWindow() {
Browser* browser = BrowserList::GetLastActive();
if (!browser) {
Browser::OpenEmptyWindow(profile_);
browser = BrowserList::GetLastActive();
}
return browser;
}
// static
void BackgroundModeManager::RegisterUserPrefs(PrefService* prefs) {
prefs->RegisterBooleanPref(prefs::kLaunchOnStartupResetAllowed, false);
}
// static
bool BackgroundModeManager::IsBackgroundModeEnabled(
const CommandLine* command_line) {
// Background mode is disabled if the appropriate flag is passed, or if
// extensions are disabled.
bool background_mode_enabled =
!command_line->HasSwitch(switches::kDisableBackgroundMode) &&
!command_line->HasSwitch(switches::kDisableExtensions);
// Only enable BackgroundMode if the associated flag is enabled.
background_mode_enabled = background_mode_enabled &&
command_line->HasSwitch(switches::kEnableBackgroundMode);
return background_mode_enabled;
}