// 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 "chrome/browser/extensions/extensions_service.h" #include "base/basictypes.h" #include "base/command_line.h" #include "base/file_util.h" #include "base/histogram.h" #include "base/string16.h" #include "base/string_util.h" #include "base/time.h" #include "base/values.h" #include "base/version.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_thread.h" #include "chrome/browser/debugger/devtools_manager.h" #include "chrome/browser/extensions/crx_installer.h" #include "chrome/browser/extensions/extension_accessibility_api.h" #include "chrome/browser/extensions/extension_bookmarks_module.h" #include "chrome/browser/extensions/extension_browser_event_router.h" #include "chrome/browser/extensions/extension_data_deleter.h" #include "chrome/browser/extensions/extension_dom_ui.h" #include "chrome/browser/extensions/extension_error_reporter.h" #include "chrome/browser/extensions/extension_history_api.h" #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extension_updater.h" #include "chrome/browser/extensions/external_extension_provider.h" #include "chrome/browser/extensions/external_pref_extension_provider.h" #include "chrome/browser/pref_service.h" #include "chrome/browser/profile.h" #include "chrome/browser/net/chrome_url_request_context.h" #include "chrome/common/child_process_logging.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_file_util.h" #include "chrome/common/extensions/extension_l10n_util.h" #include "chrome/common/notification_service.h" #include "chrome/common/notification_type.h" #include "chrome/common/json_value_serializer.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "googleurl/src/gurl.h" #include "webkit/database/database_tracker.h" #include "webkit/database/database_util.h" #if defined(OS_WIN) #include "chrome/browser/extensions/external_registry_extension_provider_win.h" #endif using base::Time; namespace errors = extension_manifest_errors; namespace { static bool ShouldReloadExtensionManifest(const ExtensionInfo& info) { // Always reload LOAD extension manifests, because they can change on disk // independent of the manifest in our prefs. if (info.extension_location == Extension::LOAD) return true; // Otherwise, reload the manifest it needs to be relocalized. return extension_l10n_util::ShouldRelocalizeManifest(info); } } // namespace PendingExtensionInfo::PendingExtensionInfo(const GURL& update_url, const Version& version, bool is_theme, bool install_silently) : update_url(update_url), version(version), is_theme(is_theme), install_silently(install_silently) {} PendingExtensionInfo::PendingExtensionInfo() : update_url(), version(), is_theme(false), install_silently(false) {} // ExtensionsService. const char* ExtensionsService::kInstallDirectoryName = "Extensions"; const char* ExtensionsService::kCurrentVersionFileName = "Current Version"; // static bool ExtensionsService::IsDownloadFromGallery(const GURL& download_url, const GURL& referrer_url) { if (StartsWithASCII(download_url.spec(), extension_urls::kMiniGalleryDownloadPrefix, false) && StartsWithASCII(referrer_url.spec(), extension_urls::kMiniGalleryBrowsePrefix, false)) { return true; } if (StartsWithASCII(download_url.spec(), extension_urls::kGalleryDownloadPrefix, false) && StartsWithASCII(referrer_url.spec(), extension_urls::kGalleryBrowsePrefix, false)) { return true; } // Allow command line gallery url to be referrer for the gallery downloads. std::string command_line_gallery_url = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( switches::kAppsGalleryURL); if (!command_line_gallery_url.empty() && StartsWithASCII(download_url.spec(), extension_urls::kGalleryDownloadPrefix, false) && StartsWithASCII(referrer_url.spec(), command_line_gallery_url, false)) { return true; } return false; } bool ExtensionsService::IsDownloadFromMiniGallery(const GURL& download_url) { return StartsWithASCII(download_url.spec(), extension_urls::kMiniGalleryDownloadPrefix, false); // case_sensitive } ExtensionsService::ExtensionsService(Profile* profile, const CommandLine* command_line, PrefService* prefs, const FilePath& install_directory, bool autoupdate_enabled) : profile_(profile), extension_prefs_(new ExtensionPrefs(prefs, install_directory)), install_directory_(install_directory), extensions_enabled_(true), show_extensions_prompts_(true), ready_(false), ALLOW_THIS_IN_INITIALIZER_LIST(toolbar_model_(this)) { // Figure out if extension installation should be enabled. if (command_line->HasSwitch(switches::kDisableExtensions)) { extensions_enabled_ = false; } else if (profile->GetPrefs()->GetBoolean(prefs::kDisableExtensions)) { extensions_enabled_ = false; } registrar_.Add(this, NotificationType::EXTENSION_HOST_DID_STOP_LOADING, NotificationService::AllSources()); registrar_.Add(this, NotificationType::EXTENSION_PROCESS_TERMINATED, Source(profile_)); // Set up the ExtensionUpdater if (autoupdate_enabled) { int update_frequency = kDefaultUpdateFrequencySeconds; if (command_line->HasSwitch(switches::kExtensionsUpdateFrequency)) { update_frequency = StringToInt(command_line->GetSwitchValueASCII( switches::kExtensionsUpdateFrequency)); } updater_ = new ExtensionUpdater(this, prefs, update_frequency); } backend_ = new ExtensionsServiceBackend(install_directory_); } ExtensionsService::~ExtensionsService() { UnloadAllExtensions(); if (updater_.get()) { updater_->Stop(); } } void ExtensionsService::InitEventRouters() { ExtensionHistoryEventRouter::GetInstance()->ObserveProfile(profile_); ExtensionAccessibilityEventRouter::GetInstance()->ObserveProfile(profile_); ExtensionBrowserEventRouter::GetInstance()->Init(); ExtensionBookmarkEventRouter::GetSingleton()->Observe( profile_->GetBookmarkModel()); } void ExtensionsService::Init() { DCHECK(!ready_); DCHECK_EQ(extensions_.size(), 0u); // Hack: we need to ensure the ResourceDispatcherHost is ready before we load // the first extension, because its members listen for loaded notifications. g_browser_process->resource_dispatcher_host(); LoadAllExtensions(); // TODO(erikkay) this should probably be deferred to a future point // rather than running immediately at startup. CheckForExternalUpdates(); // TODO(erikkay) this should probably be deferred as well. GarbageCollectExtensions(); } void ExtensionsService::InstallExtension(const FilePath& extension_path) { scoped_refptr installer( new CrxInstaller(install_directory_, this, // frontend NULL)); // no client (silent install) installer->set_allow_privilege_increase(true); installer->InstallCrx(extension_path); } namespace { // TODO(akalin): Put this somewhere where both crx_installer.cc and // this file can use it. void DeleteFileHelper(const FilePath& path, bool recursive) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); file_util::Delete(path, recursive); } } // namespace void ExtensionsService::UpdateExtension(const std::string& id, const FilePath& extension_path, const GURL& download_url) { PendingExtensionMap::const_iterator it = pending_extensions_.find(id); if ((it == pending_extensions_.end()) && !GetExtensionByIdInternal(id, true, true)) { LOG(WARNING) << "Will not update extension " << id << " because it is not installed or pending"; // Delete extension_path since we're not creating a CrxInstaller // that would do it for us. ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableFunction(&DeleteFileHelper, extension_path, false)); return; } // We want a silent install only for non-pending extensions and // pending extensions that have install_silently set. ExtensionInstallUI* client = ((it == pending_extensions_.end()) || it->second.install_silently) ? NULL : new ExtensionInstallUI(profile_); scoped_refptr installer( new CrxInstaller(install_directory_, this, // frontend client)); installer->set_expected_id(id); installer->set_delete_source(true); installer->set_force_web_origin_to_download_url(true); installer->set_original_url(download_url); installer->InstallCrx(extension_path); } void ExtensionsService::AddPendingExtension( const std::string& id, const GURL& update_url, const Version& version, bool is_theme, bool install_silently) { if (GetExtensionByIdInternal(id, true, true)) { LOG(DFATAL) << "Trying to add pending extension " << id << " which already exists"; return; } AddPendingExtensionInternal(id, update_url, version, is_theme, install_silently); } void ExtensionsService::AddPendingExtensionInternal( const std::string& id, const GURL& update_url, const Version& version, bool is_theme, bool install_silently) { pending_extensions_[id] = PendingExtensionInfo(update_url, version, is_theme, install_silently); } void ExtensionsService::ReloadExtension(const std::string& extension_id) { FilePath path; Extension* current_extension = GetExtensionById(extension_id, false); // Unload the extension if it's loaded. It might not be loaded if it crashed. if (current_extension) { // If the extension has an inspector open for its background page, detach // the inspector and hang onto a cookie for it, so that we can reattach // later. ExtensionProcessManager* manager = profile_->GetExtensionProcessManager(); ExtensionHost* host = manager->GetBackgroundHostForExtension( current_extension); if (host) { // Look for an open inspector for the background page. int devtools_cookie = DevToolsManager::GetInstance()->DetachClientHost( host->render_view_host()); if (devtools_cookie >= 0) orphaned_dev_tools_[extension_id] = devtools_cookie; } path = current_extension->path(); UnloadExtension(extension_id); } else { path = unloaded_extension_paths_[extension_id]; } // Check the installed extensions to see if what we're reloading was already // installed. scoped_ptr installed_extension( extension_prefs_->GetInstalledExtensionInfo(extension_id)); if (installed_extension.get() && installed_extension->extension_manifest.get()) { LoadInstalledExtension(*installed_extension, false); } else { // We should always be able to remember the extension's path. If it's not in // the map, someone failed to update |unloaded_extension_paths_|. CHECK(!path.empty()); LoadExtension(path); } } void ExtensionsService::UninstallExtension(const std::string& extension_id, bool external_uninstall) { Extension* extension = GetExtensionByIdInternal(extension_id, true, true); // Callers should not send us nonexistant extensions. DCHECK(extension); // Get hold of information we need after unloading, since the extension // pointer will be invalid then. GURL extension_url(extension->url()); Extension::Location location(extension->location()); // Also copy the extension identifier since the reference might have been // obtained via Extension::id(). std::string extension_id_copy(extension_id); // Unload before doing more cleanup to ensure that nothing is hanging on to // any of these resources. UnloadExtension(extension_id); extension_prefs_->OnExtensionUninstalled(extension_id_copy, location, external_uninstall); // Tell the backend to start deleting installed extensions on the file thread. if (Extension::LOAD != location) { ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableFunction( &extension_file_util::UninstallExtension, install_directory_, extension_id_copy)); } ClearExtensionData(extension_url); } void ExtensionsService::ClearExtensionData(const GURL& extension_url) { scoped_refptr deleter( new ExtensionDataDeleter(profile_, extension_url)); deleter->StartDeleting(); } void ExtensionsService::EnableExtension(const std::string& extension_id) { Extension* extension = GetExtensionByIdInternal(extension_id, false, true); if (!extension) { NOTREACHED() << "Trying to enable an extension that isn't disabled."; return; } extension_prefs_->SetExtensionState(extension, Extension::ENABLED); // Move it over to the enabled list. extensions_.push_back(extension); ExtensionList::iterator iter = std::find(disabled_extensions_.begin(), disabled_extensions_.end(), extension); disabled_extensions_.erase(iter); ExtensionDOMUI::RegisterChromeURLOverrides(profile_, extension->GetChromeURLOverrides()); NotifyExtensionLoaded(extension); UpdateActiveExtensionsInCrashReporter(); } void ExtensionsService::DisableExtension(const std::string& extension_id) { Extension* extension = GetExtensionByIdInternal(extension_id, true, false); // The extension may have been disabled already. if (!extension) return; extension_prefs_->SetExtensionState(extension, Extension::DISABLED); // Move it over to the disabled list. disabled_extensions_.push_back(extension); ExtensionList::iterator iter = std::find(extensions_.begin(), extensions_.end(), extension); extensions_.erase(iter); ExtensionDOMUI::UnregisterChromeURLOverrides(profile_, extension->GetChromeURLOverrides()); NotifyExtensionUnloaded(extension); UpdateActiveExtensionsInCrashReporter(); } void ExtensionsService::LoadExtension(const FilePath& extension_path) { ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableMethod( backend_.get(), &ExtensionsServiceBackend::LoadSingleExtension, extension_path, scoped_refptr(this))); } void ExtensionsService::LoadComponentExtensions() { for (RegisteredComponentExtensions::iterator it = component_extension_manifests_.begin(); it != component_extension_manifests_.end(); ++it) { JSONStringValueSerializer serializer(it->manifest); scoped_ptr manifest(serializer.Deserialize(NULL, NULL)); if (!manifest.get()) { DLOG(ERROR) << "Failed to retrieve manifest for extension"; continue; } scoped_ptr extension(new Extension(it->root_directory)); extension->set_location(Extension::COMPONENT); std::string error; if (!extension->InitFromValue( *static_cast(manifest.get()), true, // require key &error)) { NOTREACHED(); return; } OnExtensionLoaded(extension.release(), false); // Don't allow privilege // increase. } } void ExtensionsService::LoadAllExtensions() { base::TimeTicks start_time = base::TimeTicks::Now(); // Load any component extensions. LoadComponentExtensions(); // Load the previously installed extensions. scoped_ptr info( extension_prefs_->GetInstalledExtensionsInfo()); // If any extensions need localization, we bounce them all to the file thread // for re-reading and localization. for (size_t i = 0; i < info->size(); ++i) { if (ShouldReloadExtensionManifest(*info->at(i))) { ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableMethod( backend_.get(), &ExtensionsServiceBackend::ReloadExtensionManifests, info.release(), // Callee takes ownership of the memory. start_time, scoped_refptr(this))); return; } } // Don't update prefs. // Callee takes ownership of the memory. ContinueLoadAllExtensions(info.release(), start_time, false); } void ExtensionsService::ContinueLoadAllExtensions( ExtensionPrefs::ExtensionsInfo* extensions_info, base::TimeTicks start_time, bool write_to_prefs) { scoped_ptr info(extensions_info); for (size_t i = 0; i < info->size(); ++i) { LoadInstalledExtension(*info->at(i), write_to_prefs); } OnLoadedInstalledExtensions(); UMA_HISTOGRAM_COUNTS_100("Extensions.LoadAll", extensions_.size()); UMA_HISTOGRAM_COUNTS_100("Extensions.Disabled", disabled_extensions_.size()); UMA_HISTOGRAM_TIMES("Extensions.LoadAllTime", base::TimeTicks::Now() - start_time); int user_script_count = 0; int extension_count = 0; int theme_count = 0; int external_count = 0; int page_action_count = 0; int browser_action_count = 0; ExtensionList::iterator ex; for (ex = extensions_.begin(); ex != extensions_.end(); ++ex) { // Don't count component extensions, since they are only extensions as an // implementation detail. if ((*ex)->location() == Extension::COMPONENT) continue; // Don't count unpacked extensions, since they're a developer-specific // feature. if ((*ex)->location() == Extension::LOAD) continue; if ((*ex)->IsTheme()) { theme_count++; } else if ((*ex)->converted_from_user_script()) { user_script_count++; } else { extension_count++; } if (Extension::IsExternalLocation((*ex)->location())) { external_count++; } if ((*ex)->page_action() != NULL) { page_action_count++; } if ((*ex)->browser_action() != NULL) { browser_action_count++; } } UMA_HISTOGRAM_COUNTS_100("Extensions.LoadExtension", extension_count); UMA_HISTOGRAM_COUNTS_100("Extensions.LoadUserScript", user_script_count); UMA_HISTOGRAM_COUNTS_100("Extensions.LoadTheme", theme_count); UMA_HISTOGRAM_COUNTS_100("Extensions.LoadExternal", external_count); UMA_HISTOGRAM_COUNTS_100("Extensions.LoadPageAction", page_action_count); UMA_HISTOGRAM_COUNTS_100("Extensions.LoadBrowserAction", browser_action_count); } void ExtensionsService::LoadInstalledExtension(const ExtensionInfo& info, bool write_to_prefs) { std::string error; Extension* extension = NULL; if (info.extension_manifest.get()) { scoped_ptr tmp(new Extension(info.extension_path)); bool require_key = info.extension_location != Extension::LOAD; if (tmp->InitFromValue(*info.extension_manifest, require_key, &error)) extension = tmp.release(); } else { error = errors::kManifestUnreadable; } if (!extension) { ReportExtensionLoadError(info.extension_path, error, NotificationType::EXTENSION_INSTALL_ERROR, false); return; } extension->set_location(info.extension_location); if (write_to_prefs) extension_prefs_->UpdateManifest(extension); OnExtensionLoaded(extension, true); if (info.extension_location == Extension::EXTERNAL_PREF || info.extension_location == Extension::EXTERNAL_REGISTRY) { ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableMethod( backend_.get(), &ExtensionsServiceBackend::CheckExternalUninstall, scoped_refptr(this), info.extension_id, info.extension_location)); } } void ExtensionsService::NotifyExtensionLoaded(Extension* extension) { // The ChromeURLRequestContexts need to be first to know that the extension // was loaded, otherwise a race can arise where a renderer that is created // for the extension may try to load an extension URL with an extension id // that the request context doesn't yet know about. The profile is responsible // for ensuring its URLRequestContexts appropriately discover the loaded // extension. if (profile_) { profile_->RegisterExtensionWithRequestContexts(extension); // Check if this permission requires unlimited storage quota if (extension->HasApiPermission(Extension::kUnlimitedStoragePermission)) { string16 origin_identifier = webkit_database::DatabaseUtil::GetOriginIdentifier(extension->url()); ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableMethod( profile_->GetDatabaseTracker(), &webkit_database::DatabaseTracker::SetOriginQuotaInMemory, origin_identifier, kint64max)); } } LOG(INFO) << "Sending EXTENSION_LOADED"; NotificationService::current()->Notify( NotificationType::EXTENSION_LOADED, Source(profile_), Details(extension)); } void ExtensionsService::NotifyExtensionUnloaded(Extension* extension) { LOG(INFO) << "Sending EXTENSION_UNLOADED"; NotificationService::current()->Notify( NotificationType::EXTENSION_UNLOADED, Source(profile_), Details(extension)); if (profile_) { profile_->UnregisterExtensionWithRequestContexts(extension); // Check if this permission required unlimited storage quota, reset its // in-memory quota. if (extension->HasApiPermission(Extension::kUnlimitedStoragePermission)) { string16 origin_identifier = webkit_database::DatabaseUtil::GetOriginIdentifier(extension->url()); ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableMethod( profile_->GetDatabaseTracker(), &webkit_database::DatabaseTracker::ResetOriginQuotaInMemory, origin_identifier)); } } } void ExtensionsService::UpdateExtensionBlacklist( const std::vector& blacklist) { // Use this set to indicate if an extension in the blacklist has been used. std::set blacklist_set; for (unsigned int i = 0; i < blacklist.size(); ++i) { if (Extension::IdIsValid(blacklist[i])) { blacklist_set.insert(blacklist[i]); } } extension_prefs_->UpdateBlacklist(blacklist_set); std::vector to_be_removed; // Loop current extensions, unload installed extensions. for (ExtensionList::const_iterator iter = extensions_.begin(); iter != extensions_.end(); ++iter) { Extension* extension = (*iter); if (blacklist_set.find(extension->id()) != blacklist_set.end()) { to_be_removed.push_back(extension->id()); } } // UnloadExtension will change the extensions_ list. So, we should // call it outside the iterator loop. for (unsigned int i = 0; i < to_be_removed.size(); ++i) { UnloadExtension(to_be_removed[i]); } } bool ExtensionsService::IsIncognitoEnabled(const Extension* extension) { // If this is a component extension we always allow it to work in incognito // mode. if (extension->location() == Extension::COMPONENT) return true; // Check the prefs. return extension_prefs_->IsIncognitoEnabled(extension->id()); } void ExtensionsService::SetIsIncognitoEnabled(Extension* extension, bool enabled) { extension_prefs_->SetIsIncognitoEnabled(extension->id(), enabled); // Broadcast unloaded and loaded events to update browser state. NotifyExtensionUnloaded(extension); NotifyExtensionLoaded(extension); } void ExtensionsService::CheckForExternalUpdates() { // This installs or updates externally provided extensions. // TODO(aa): Why pass this list into the provider, why not just filter it // later? std::set killed_extensions; extension_prefs_->GetKilledExtensionIds(&killed_extensions); ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableMethod( backend_.get(), &ExtensionsServiceBackend::CheckForExternalUpdates, killed_extensions, scoped_refptr(this))); } void ExtensionsService::UnloadExtension(const std::string& extension_id) { // Make sure the extension gets deleted after we return from this function. scoped_ptr extension( GetExtensionByIdInternal(extension_id, true, true)); // Callers should not send us nonexistant extensions. CHECK(extension.get()); // Keep information about the extension so that we can reload it later // even if it's not permanently installed. unloaded_extension_paths_[extension->id()] = extension->path(); ExtensionDOMUI::UnregisterChromeURLOverrides(profile_, extension->GetChromeURLOverrides()); ExtensionList::iterator iter = std::find(disabled_extensions_.begin(), disabled_extensions_.end(), extension.get()); if (iter != disabled_extensions_.end()) { disabled_extensions_.erase(iter); NotificationService::current()->Notify( NotificationType::EXTENSION_UNLOADED_DISABLED, Source(profile_), Details(extension.get())); return; } iter = std::find(extensions_.begin(), extensions_.end(), extension.get()); // Remove the extension from our list. extensions_.erase(iter); NotifyExtensionUnloaded(extension.get()); UpdateActiveExtensionsInCrashReporter(); } void ExtensionsService::UnloadAllExtensions() { ExtensionList::iterator iter; for (iter = extensions_.begin(); iter != extensions_.end(); ++iter) delete *iter; extensions_.clear(); // TODO(erikkay) should there be a notification for this? We can't use // EXTENSION_UNLOADED since that implies that the extension has been disabled // or uninstalled, and UnloadAll is just part of shutdown. } void ExtensionsService::ReloadExtensions() { UnloadAllExtensions(); LoadAllExtensions(); } void ExtensionsService::GarbageCollectExtensions() { if (extension_prefs_->pref_service()->read_only()) return; scoped_ptr info( extension_prefs_->GetInstalledExtensionsInfo()); std::map extension_paths; for (size_t i = 0; i < info->size(); ++i) extension_paths[info->at(i)->extension_id] = info->at(i)->extension_path; ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableFunction( &extension_file_util::GarbageCollectExtensions, install_directory_, extension_paths)); } void ExtensionsService::OnLoadedInstalledExtensions() { ready_ = true; if (updater_.get()) { updater_->Start(); } NotificationService::current()->Notify( NotificationType::EXTENSIONS_READY, Source(profile_), NotificationService::NoDetails()); } void ExtensionsService::OnExtensionLoaded(Extension* extension, bool allow_privilege_increase) { // Ensure extension is deleted unless we transfer ownership. scoped_ptr scoped_extension(extension); // The extension is now loaded, remove its data from unloaded extension map. unloaded_extension_paths_.erase(extension->id()); // TODO(aa): Need to re-evaluate this branch. Does this still make sense now // that extensions are enabled by default? if (extensions_enabled() || extension->IsTheme() || extension->location() == Extension::LOAD || Extension::IsExternalLocation(extension->location())) { Extension* old = GetExtensionByIdInternal(extension->id(), true, true); if (old) { // CrxInstaller should have guaranteed that we aren't downgrading. CHECK(extension->version()->CompareTo(*(old->version())) >= 0); bool allow_silent_upgrade = allow_privilege_increase || !Extension::IsPrivilegeIncrease( old, extension); // Extensions get upgraded if silent upgrades are allowed, otherwise // they get disabled. if (allow_silent_upgrade) { old->set_being_upgraded(true); extension->set_being_upgraded(true); } // To upgrade an extension in place, unload the old one and // then load the new one. UnloadExtension(old->id()); old = NULL; if (!allow_silent_upgrade) { // Extension has changed permissions significantly. Disable it. We // send a notification below. extension_prefs_->SetExtensionState(extension, Extension::DISABLED); extension_prefs_->SetDidExtensionEscalatePermissions(extension, true); } } switch (extension_prefs_->GetExtensionState(extension->id())) { case Extension::ENABLED: extensions_.push_back(scoped_extension.release()); NotifyExtensionLoaded(extension); ExtensionDOMUI::RegisterChromeURLOverrides(profile_, extension->GetChromeURLOverrides()); break; case Extension::DISABLED: disabled_extensions_.push_back(scoped_extension.release()); NotificationService::current()->Notify( NotificationType::EXTENSION_UPDATE_DISABLED, Source(profile_), Details(extension)); break; default: NOTREACHED(); break; } } extension->set_being_upgraded(false); UpdateActiveExtensionsInCrashReporter(); } void ExtensionsService::UpdateActiveExtensionsInCrashReporter() { std::set extension_ids; for (size_t i = 0; i < extensions_.size(); ++i) { if (!extensions_[i]->IsTheme()) extension_ids.insert(extensions_[i]->id()); } child_process_logging::SetActiveExtensions(extension_ids); } void ExtensionsService::OnExtensionInstalled(Extension* extension, bool allow_privilege_increase) { PendingExtensionMap::iterator it = pending_extensions_.find(extension->id()); if (it != pending_extensions_.end() && (it->second.is_theme != extension->IsTheme())) { LOG(WARNING) << "Not installing pending extension " << extension->id() << " with is_theme = " << extension->IsTheme() << "; expected is_theme = " << it->second.is_theme; // Delete the extension directory since we're not going to load // it. ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableFunction(&DeleteFileHelper, extension->path(), true)); delete extension; return; } extension_prefs_->OnExtensionInstalled(extension); // If the extension is a theme, tell the profile (and therefore ThemeProvider) // to apply it. if (extension->IsTheme()) { NotificationService::current()->Notify( NotificationType::THEME_INSTALLED, Source(profile_), Details(extension)); } else { NotificationService::current()->Notify( NotificationType::EXTENSION_INSTALLED, Source(profile_), Details(extension)); } // Also load the extension. OnExtensionLoaded(extension, allow_privilege_increase); // Erase any pending extension. if (it != pending_extensions_.end()) { pending_extensions_.erase(it); } } Extension* ExtensionsService::GetExtensionByIdInternal(const std::string& id, bool include_enabled, bool include_disabled) { std::string lowercase_id = StringToLowerASCII(id); if (include_enabled) { for (ExtensionList::const_iterator iter = extensions_.begin(); iter != extensions_.end(); ++iter) { if ((*iter)->id() == lowercase_id) return *iter; } } if (include_disabled) { for (ExtensionList::const_iterator iter = disabled_extensions_.begin(); iter != disabled_extensions_.end(); ++iter) { if ((*iter)->id() == lowercase_id) return *iter; } } return NULL; } Extension* ExtensionsService::GetExtensionByURL(const GURL& url) { return url.scheme() != chrome::kExtensionScheme ? NULL : GetExtensionById(url.host(), false); } Extension* ExtensionsService::GetExtensionByWebExtent(const GURL& url) { for (size_t i = 0; i < extensions_.size(); ++i) { if (extensions_[i]->web_extent().ContainsURL(url)) return extensions_[i]; } return NULL; } void ExtensionsService::ClearProvidersForTesting() { ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableMethod( backend_.get(), &ExtensionsServiceBackend::ClearProvidersForTesting)); } void ExtensionsService::SetProviderForTesting( Extension::Location location, ExternalExtensionProvider* test_provider) { ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableMethod( backend_.get(), &ExtensionsServiceBackend::SetProviderForTesting, location, test_provider)); } void ExtensionsService::OnExternalExtensionFound(const std::string& id, const std::string& version, const FilePath& path, Extension::Location location) { // Before even bothering to unpack, check and see if we already have this // version. This is important because these extensions are going to get // installed on every startup. Extension* existing = GetExtensionById(id, true); scoped_ptr other(Version::GetVersionFromString(version)); if (existing) { switch (existing->version()->CompareTo(*other)) { case -1: // existing version is older, we should upgrade break; case 0: // existing version is same, do nothing return; case 1: // existing version is newer, uh-oh LOG(WARNING) << "Found external version of extension " << id << "that is older than current version. Current version " << "is: " << existing->VersionString() << ". New version " << "is: " << version << ". Keeping current version."; return; } } scoped_refptr installer( new CrxInstaller(install_directory_, this, // frontend NULL)); // no client (silent install) installer->set_install_source(location); installer->set_expected_id(id); installer->set_allow_privilege_increase(true); installer->InstallCrx(path); } void ExtensionsService::ReportExtensionLoadError( const FilePath& extension_path, const std::string &error, NotificationType type, bool be_noisy) { NotificationService* service = NotificationService::current(); service->Notify(type, Source(profile_), Details(&error)); // TODO(port): note that this isn't guaranteed to work properly on Linux. std::string path_str = WideToUTF8(extension_path.ToWStringHack()); std::string message = StringPrintf("Could not load extension from '%s'. %s", path_str.c_str(), error.c_str()); ExtensionErrorReporter::GetInstance()->ReportError(message, be_noisy); } void ExtensionsService::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::EXTENSION_HOST_DID_STOP_LOADING: { ExtensionHost* host = Details(details).ptr(); OrphanedDevTools::iterator iter = orphaned_dev_tools_.find(host->extension()->id()); if (iter == orphaned_dev_tools_.end()) return; DevToolsManager::GetInstance()->AttachClientHost( iter->second, host->render_view_host()); orphaned_dev_tools_.erase(iter); break; } case NotificationType::EXTENSION_PROCESS_TERMINATED: { DCHECK_EQ(profile_, Source(source).ptr()); // Unload the entire extension. We want it to be in a consistent state: // either fully working or not loaded at all, but never half-crashed. UnloadExtension(Details(details).ptr()->extension()->id()); break; } default: NOTREACHED() << "Unexpected notification type."; } } // ExtensionsServicesBackend ExtensionsServiceBackend::ExtensionsServiceBackend( const FilePath& install_directory) : frontend_(NULL), install_directory_(install_directory), alert_on_error_(false) { // TODO(aa): This ends up doing blocking IO on the UI thread because it reads // pref data in the ctor and that is called on the UI thread. Would be better // to re-read data each time we list external extensions, anyway. external_extension_providers_[Extension::EXTERNAL_PREF] = linked_ptr( new ExternalPrefExtensionProvider()); #if defined(OS_WIN) external_extension_providers_[Extension::EXTERNAL_REGISTRY] = linked_ptr( new ExternalRegistryExtensionProvider()); #endif } ExtensionsServiceBackend::~ExtensionsServiceBackend() { } void ExtensionsServiceBackend::LoadSingleExtension( const FilePath& path_in, scoped_refptr frontend) { frontend_ = frontend; // Explicit UI loads are always noisy. alert_on_error_ = true; FilePath extension_path = path_in; file_util::AbsolutePath(&extension_path); LOG(INFO) << "Loading single extension from " << extension_path.BaseName().value(); std::string error; Extension* extension = extension_file_util::LoadExtension( extension_path, false, // Don't require id &error); if (!extension) { ReportExtensionLoadError(extension_path, error); return; } extension->set_location(Extension::LOAD); // Report this as an installed extension so that it gets remembered in the // prefs. ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(frontend_, &ExtensionsService::OnExtensionInstalled, extension, true)); } void ExtensionsServiceBackend::ReportExtensionLoadError( const FilePath& extension_path, const std::string &error) { ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod( frontend_, &ExtensionsService::ReportExtensionLoadError, extension_path, error, NotificationType::EXTENSION_INSTALL_ERROR, alert_on_error_)); } bool ExtensionsServiceBackend::LookupExternalExtension( const std::string& id, Version** version, Extension::Location* location) { scoped_ptr extension_version; for (ProviderMap::const_iterator i = external_extension_providers_.begin(); i != external_extension_providers_.end(); ++i) { const ExternalExtensionProvider* provider = i->second.get(); extension_version.reset(provider->RegisteredVersion(id, location)); if (extension_version.get()) { if (version) *version = extension_version.release(); return true; } } return false; } // Some extensions will autoupdate themselves externally from Chrome. These // are typically part of some larger client application package. To support // these, the extension will register its location in the the preferences file // (and also, on Windows, in the registry) and this code will periodically // check that location for a .crx file, which it will then install locally if // a new version is available. void ExtensionsServiceBackend::CheckForExternalUpdates( std::set ids_to_ignore, scoped_refptr frontend) { // Note that this installation is intentionally silent (since it didn't // go through the front-end). Extensions that are registered in this // way are effectively considered 'pre-bundled', and so implicitly // trusted. In general, if something has HKLM or filesystem access, // they could install an extension manually themselves anyway. alert_on_error_ = false; frontend_ = frontend; // Ask each external extension provider to give us a call back for each // extension they know about. See OnExternalExtensionFound. for (ProviderMap::const_iterator i = external_extension_providers_.begin(); i != external_extension_providers_.end(); ++i) { ExternalExtensionProvider* provider = i->second.get(); provider->VisitRegisteredExtension(this, ids_to_ignore); } } void ExtensionsServiceBackend::CheckExternalUninstall( scoped_refptr frontend, const std::string& id, Extension::Location location) { // Check if the providers know about this extension. ProviderMap::const_iterator i = external_extension_providers_.find(location); if (i == external_extension_providers_.end()) { NOTREACHED() << "CheckExternalUninstall called for non-external extension " << location; return; } scoped_ptr version; version.reset(i->second->RegisteredVersion(id, NULL)); if (version.get()) return; // Yup, known extension, don't uninstall. // This is an external extension that we don't have registered. Uninstall. ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod( frontend.get(), &ExtensionsService::UninstallExtension, id, true)); } void ExtensionsServiceBackend::ClearProvidersForTesting() { external_extension_providers_.clear(); } void ExtensionsServiceBackend::SetProviderForTesting( Extension::Location location, ExternalExtensionProvider* test_provider) { DCHECK(test_provider); external_extension_providers_[location] = linked_ptr(test_provider); } void ExtensionsServiceBackend::OnExternalExtensionFound( const std::string& id, const Version* version, const FilePath& path, Extension::Location location) { ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod( frontend_, &ExtensionsService::OnExternalExtensionFound, id, version->GetString(), path, location)); } void ExtensionsServiceBackend::ReloadExtensionManifests( ExtensionPrefs::ExtensionsInfo* extensions_to_reload, base::TimeTicks start_time, scoped_refptr frontend) { frontend_ = frontend; for (size_t i = 0; i < extensions_to_reload->size(); ++i) { ExtensionInfo* info = extensions_to_reload->at(i).get(); if (!ShouldReloadExtensionManifest(*info)) continue; // We need to reload original manifest in order to localize properly. std::string error; scoped_ptr extension(extension_file_util::LoadExtension( info->extension_path, false, &error)); if (extension.get()) extensions_to_reload->at(i)->extension_manifest.reset( static_cast( extension->manifest_value()->DeepCopy())); } // Finish installing on UI thread. ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod( frontend_, &ExtensionsService::ContinueLoadAllExtensions, extensions_to_reload, start_time, true)); }