| // Copyright (c) 2013 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/extension_service.h" |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <set> |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/file_util.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "base/version.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/api/extension_action/extension_action_api.h" |
| #include "chrome/browser/extensions/api/storage/settings_frontend.h" |
| #include "chrome/browser/extensions/component_loader.h" |
| #include "chrome/browser/extensions/crx_installer.h" |
| #include "chrome/browser/extensions/data_deleter.h" |
| #include "chrome/browser/extensions/extension_disabled_ui.h" |
| #include "chrome/browser/extensions/extension_error_reporter.h" |
| #include "chrome/browser/extensions/extension_error_ui.h" |
| #include "chrome/browser/extensions/extension_host.h" |
| #include "chrome/browser/extensions/extension_install_ui.h" |
| #include "chrome/browser/extensions/extension_special_storage_policy.h" |
| #include "chrome/browser/extensions/extension_sync_service.h" |
| #include "chrome/browser/extensions/extension_system.h" |
| #include "chrome/browser/extensions/external_install_ui.h" |
| #include "chrome/browser/extensions/external_provider_impl.h" |
| #include "chrome/browser/extensions/install_verifier.h" |
| #include "chrome/browser/extensions/installed_loader.h" |
| #include "chrome/browser/extensions/permissions_updater.h" |
| #include "chrome/browser/extensions/unpacked_installer.h" |
| #include "chrome/browser/extensions/updater/extension_updater.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/webui/extensions/extension_icon_source.h" |
| #include "chrome/browser/ui/webui/favicon_source.h" |
| #include "chrome/browser/ui/webui/ntp/thumbnail_source.h" |
| #include "chrome/browser/ui/webui/theme_source.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/crash_keys.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "chrome/common/extensions/extension_file_util.h" |
| #include "chrome/common/extensions/extension_messages.h" |
| #include "chrome/common/extensions/features/feature_channel.h" |
| #include "chrome/common/extensions/manifest_handlers/app_isolation_info.h" |
| #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" |
| #include "chrome/common/extensions/manifest_url_handler.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/url_constants.h" |
| #include "components/startup_metric_utils/startup_metric_utils.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/url_data_source.h" |
| #include "extensions/browser/app_sorting.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/extensions_browser_client.h" |
| #include "extensions/browser/external_provider_interface.h" |
| #include "extensions/browser/management_policy.h" |
| #include "extensions/browser/pending_extension_manager.h" |
| #include "extensions/browser/process_manager.h" |
| #include "extensions/browser/update_observer.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/error_utils.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extensions_client.h" |
| #include "extensions/common/feature_switch.h" |
| #include "extensions/common/manifest.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "extensions/common/manifest_handlers/background_info.h" |
| #include "extensions/common/manifest_handlers/incognito_info.h" |
| #include "extensions/common/manifest_handlers/shared_module_info.h" |
| #include "extensions/common/permissions/permission_message_provider.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "grit/generated_resources.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "ui/base/webui/web_ui_util.h" |
| #include "url/gurl.h" |
| #include "webkit/browser/database/database_tracker.h" |
| #include "webkit/browser/database/database_util.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "chrome/browser/chromeos/extensions/install_limiter.h" |
| #include "webkit/browser/fileapi/file_system_backend.h" |
| #include "webkit/browser/fileapi/file_system_context.h" |
| #endif |
| |
| using content::BrowserContext; |
| using content::BrowserThread; |
| using content::DevToolsAgentHost; |
| using extensions::CrxInstaller; |
| using extensions::Extension; |
| using extensions::ExtensionIdSet; |
| using extensions::ExtensionInfo; |
| using extensions::FeatureSwitch; |
| using extensions::InstallVerifier; |
| using extensions::ManagementPolicy; |
| using extensions::Manifest; |
| using extensions::PermissionMessage; |
| using extensions::PermissionMessages; |
| using extensions::PermissionSet; |
| using extensions::SharedModuleInfo; |
| using extensions::UnloadedExtensionInfo; |
| |
| namespace errors = extensions::manifest_errors; |
| |
| namespace { |
| |
| // Histogram values for logging events related to externally installed |
| // extensions. |
| enum ExternalExtensionEvent { |
| EXTERNAL_EXTENSION_INSTALLED = 0, |
| EXTERNAL_EXTENSION_IGNORED, |
| EXTERNAL_EXTENSION_REENABLED, |
| EXTERNAL_EXTENSION_UNINSTALLED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY, |
| }; |
| |
| // Prompt the user this many times before considering an extension acknowledged. |
| static const int kMaxExtensionAcknowledgePromptCount = 3; |
| |
| // Wait this many seconds after an extensions becomes idle before updating it. |
| static const int kUpdateIdleDelay = 5; |
| |
| // Wait this many seconds before trying to garbage collect extensions again. |
| static const int kGarbageCollectRetryDelay = 30; |
| |
| // Wait this many seconds after startup to see if there are any extensions |
| // which can be garbage collected. |
| static const int kGarbageCollectStartupDelay = 30; |
| |
| static bool IsSharedModule(const Extension* extension) { |
| return SharedModuleInfo::IsSharedModule(extension); |
| } |
| |
| static bool IsCWSSharedModule(const Extension* extension) { |
| return extension->from_webstore() && IsSharedModule(extension); |
| } |
| |
| class SharedModuleProvider : public extensions::ManagementPolicy::Provider { |
| public: |
| SharedModuleProvider() {} |
| virtual ~SharedModuleProvider() {} |
| |
| virtual std::string GetDebugPolicyProviderName() const OVERRIDE { |
| return "SharedModuleProvider"; |
| } |
| |
| virtual bool UserMayModifySettings(const Extension* extension, |
| base::string16* error) const OVERRIDE { |
| return !IsCWSSharedModule(extension); |
| } |
| |
| virtual bool MustRemainEnabled(const Extension* extension, |
| base::string16* error) const OVERRIDE { |
| return IsCWSSharedModule(extension); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SharedModuleProvider); |
| }; |
| |
| |
| } // namespace |
| |
| ExtensionService::ExtensionRuntimeData::ExtensionRuntimeData() |
| : background_page_ready(false), |
| being_upgraded(false), |
| has_used_webrequest(false) { |
| } |
| |
| ExtensionService::ExtensionRuntimeData::~ExtensionRuntimeData() { |
| } |
| |
| // ExtensionService. |
| |
| void ExtensionService::CheckExternalUninstall(const std::string& id) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Check if the providers know about this extension. |
| extensions::ProviderCollection::const_iterator i; |
| for (i = external_extension_providers_.begin(); |
| i != external_extension_providers_.end(); ++i) { |
| DCHECK(i->get()->IsReady()); |
| if (i->get()->HasExtension(id)) |
| return; // Yup, known extension, don't uninstall. |
| } |
| |
| // We get the list of external extensions to check from preferences. |
| // It is possible that an extension has preferences but is not loaded. |
| // For example, an extension that requires experimental permissions |
| // will not be loaded if the experimental command line flag is not used. |
| // In this case, do not uninstall. |
| if (!GetInstalledExtension(id)) { |
| // We can't call UninstallExtension with an unloaded/invalid |
| // extension ID. |
| LOG(WARNING) << "Attempted uninstallation of unloaded/invalid extension " |
| << "with id: " << id; |
| return; |
| } |
| UninstallExtension(id, true, NULL); |
| } |
| |
| void ExtensionService::SetFileTaskRunnerForTesting( |
| base::SequencedTaskRunner* task_runner) { |
| file_task_runner_ = task_runner; |
| } |
| |
| void ExtensionService::ClearProvidersForTesting() { |
| external_extension_providers_.clear(); |
| } |
| |
| void ExtensionService::AddProviderForTesting( |
| extensions::ExternalProviderInterface* test_provider) { |
| CHECK(test_provider); |
| external_extension_providers_.push_back( |
| linked_ptr<extensions::ExternalProviderInterface>(test_provider)); |
| } |
| |
| bool ExtensionService::OnExternalExtensionUpdateUrlFound( |
| const std::string& id, |
| const GURL& update_url, |
| Manifest::Location location, |
| int creation_flags, |
| bool mark_acknowledged) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| CHECK(Extension::IdIsValid(id)); |
| |
| const Extension* extension = GetExtensionById(id, true); |
| if (extension) { |
| // Already installed. Skip this install if the current location has |
| // higher priority than |location|. |
| Manifest::Location current = extension->location(); |
| if (current == Manifest::GetHigherPriorityLocation(current, location)) |
| return false; |
| // Otherwise, overwrite the current installation. |
| } |
| |
| // Add |id| to the set of pending extensions. If it can not be added, |
| // then there is already a pending record from a higher-priority install |
| // source. In this case, signal that this extension will not be |
| // installed by returning false. |
| if (!pending_extension_manager()->AddFromExternalUpdateUrl( |
| id, update_url, location, creation_flags, mark_acknowledged)) { |
| return false; |
| } |
| |
| update_once_all_providers_are_ready_ = true; |
| return true; |
| } |
| |
| const Extension* ExtensionService::GetInstalledExtensionByUrl( |
| const GURL& url) const { |
| return extensions_.GetExtensionOrAppByURL(url); |
| } |
| |
| const Extension* ExtensionService::GetInstalledApp(const GURL& url) const { |
| const Extension* extension = GetInstalledExtensionByUrl(url); |
| return (extension && extension->is_app()) ? extension : NULL; |
| } |
| |
| bool ExtensionService::IsInstalledApp(const GURL& url) const { |
| return !!GetInstalledApp(url); |
| } |
| |
| // static |
| // This function is used to implement the command-line switch |
| // --uninstall-extension, and to uninstall an extension via sync. The LOG |
| // statements within this function are used to inform the user if the uninstall |
| // cannot be done. |
| bool ExtensionService::UninstallExtensionHelper( |
| ExtensionService* extensions_service, |
| const std::string& extension_id) { |
| // We can't call UninstallExtension with an invalid extension ID. |
| if (!extensions_service->GetInstalledExtension(extension_id)) { |
| LOG(WARNING) << "Attempted uninstallation of non-existent extension with " |
| << "id: " << extension_id; |
| return false; |
| } |
| |
| // The following call to UninstallExtension will not allow an uninstall of a |
| // policy-controlled extension. |
| base::string16 error; |
| if (!extensions_service->UninstallExtension(extension_id, false, &error)) { |
| LOG(WARNING) << "Cannot uninstall extension with id " << extension_id |
| << ": " << error; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| ExtensionService::ExtensionService(Profile* profile, |
| const CommandLine* command_line, |
| const base::FilePath& install_directory, |
| extensions::ExtensionPrefs* extension_prefs, |
| extensions::Blacklist* blacklist, |
| bool autoupdate_enabled, |
| bool extensions_enabled, |
| extensions::OneShotEvent* ready) |
| : extensions::Blacklist::Observer(blacklist), |
| profile_(profile), |
| system_(extensions::ExtensionSystem::Get(profile)), |
| extension_prefs_(extension_prefs), |
| blacklist_(blacklist), |
| settings_frontend_(extensions::SettingsFrontend::Create(profile)), |
| extension_sync_service_(NULL), |
| pending_extension_manager_(*this), |
| install_directory_(install_directory), |
| extensions_enabled_(extensions_enabled), |
| show_extensions_prompts_(true), |
| install_updates_when_idle_(true), |
| ready_(ready), |
| update_once_all_providers_are_ready_(false), |
| browser_terminating_(false), |
| installs_delayed_for_gc_(false), |
| is_first_run_(false) { |
| #if defined(OS_CHROMEOS) |
| disable_garbage_collection_ = false; |
| #endif |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Figure out if extension installation should be enabled. |
| if (extensions::ExtensionsBrowserClient::Get()->AreExtensionsDisabled( |
| *command_line, profile)) |
| extensions_enabled_ = false; |
| |
| registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| registrar_.Add(this, chrome::NOTIFICATION_UPGRADE_RECOMMENDED, |
| content::NotificationService::AllBrowserContextsAndSources()); |
| pref_change_registrar_.Init(profile->GetPrefs()); |
| base::Closure callback = |
| base::Bind(&ExtensionService::OnExtensionInstallPrefChanged, |
| base::Unretained(this)); |
| pref_change_registrar_.Add(prefs::kExtensionInstallAllowList, callback); |
| pref_change_registrar_.Add(prefs::kExtensionInstallDenyList, callback); |
| pref_change_registrar_.Add(prefs::kExtensionAllowedTypes, callback); |
| |
| // Set up the ExtensionUpdater |
| if (autoupdate_enabled) { |
| int update_frequency = extensions::kDefaultUpdateFrequencySeconds; |
| if (command_line->HasSwitch(switches::kExtensionsUpdateFrequency)) { |
| base::StringToInt(command_line->GetSwitchValueASCII( |
| switches::kExtensionsUpdateFrequency), |
| &update_frequency); |
| } |
| updater_.reset(new extensions::ExtensionUpdater(this, |
| extension_prefs, |
| profile->GetPrefs(), |
| profile, |
| update_frequency)); |
| } |
| |
| component_loader_.reset( |
| new extensions::ComponentLoader(this, |
| profile->GetPrefs(), |
| g_browser_process->local_state())); |
| |
| if (extensions_enabled_) { |
| extensions::ExternalProviderImpl::CreateExternalProviders( |
| this, profile_, &external_extension_providers_); |
| } |
| |
| // Set this as the ExtensionService for app sorting to ensure it causes syncs |
| // if required. |
| is_first_run_ = !extension_prefs_->SetAlertSystemFirstRun(); |
| |
| #if defined(ENABLE_EXTENSIONS) |
| extension_action_storage_manager_.reset( |
| new extensions::ExtensionActionStorageManager(profile_)); |
| #endif |
| |
| shared_module_policy_provider_.reset(new SharedModuleProvider); |
| |
| // How long is the path to the Extensions directory? |
| UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ExtensionRootPathLength", |
| install_directory_.value().length(), 0, 500, 100); |
| } |
| |
| const ExtensionSet* ExtensionService::extensions() const { |
| return &extensions_; |
| } |
| |
| const ExtensionSet* ExtensionService::disabled_extensions() const { |
| return &disabled_extensions_; |
| } |
| |
| const ExtensionSet* ExtensionService::terminated_extensions() const { |
| return &terminated_extensions_; |
| } |
| |
| const ExtensionSet* ExtensionService::blacklisted_extensions() const { |
| return &blacklisted_extensions_; |
| } |
| |
| const ExtensionSet* ExtensionService::delayed_installs() const { |
| return &delayed_installs_; |
| } |
| |
| scoped_ptr<ExtensionSet> |
| ExtensionService::GenerateInstalledExtensionsSet() const { |
| scoped_ptr<ExtensionSet> installed_extensions(new ExtensionSet()); |
| installed_extensions->InsertAll(extensions_); |
| installed_extensions->InsertAll(disabled_extensions_); |
| installed_extensions->InsertAll(terminated_extensions_); |
| installed_extensions->InsertAll(blacklisted_extensions_); |
| return installed_extensions.PassAs<ExtensionSet>(); |
| } |
| |
| extensions::PendingExtensionManager* |
| ExtensionService::pending_extension_manager() { |
| return &pending_extension_manager_; |
| } |
| |
| ExtensionService::~ExtensionService() { |
| // No need to unload extensions here because they are profile-scoped, and the |
| // profile is in the process of being deleted. |
| |
| extensions::ProviderCollection::const_iterator i; |
| for (i = external_extension_providers_.begin(); |
| i != external_extension_providers_.end(); ++i) { |
| extensions::ExternalProviderInterface* provider = i->get(); |
| provider->ServiceShutdown(); |
| } |
| } |
| |
| void ExtensionService::Shutdown() { |
| system_->management_policy()->UnregisterProvider( |
| shared_module_policy_provider_.get()); |
| } |
| |
| const Extension* ExtensionService::GetExtensionById( |
| const std::string& id, bool include_disabled) const { |
| int include_mask = INCLUDE_ENABLED; |
| if (include_disabled) { |
| // Include blacklisted extensions here because there are hundreds of |
| // callers of this function, and many might assume that this includes those |
| // that have been disabled due to blacklisting. |
| include_mask |= INCLUDE_DISABLED | INCLUDE_BLACKLISTED; |
| } |
| return GetExtensionById(id, include_mask); |
| } |
| |
| GURL ExtensionService::GetSiteForExtensionId(const std::string& extension_id) { |
| return content::SiteInstance::GetSiteForURL( |
| profile_, |
| Extension::GetBaseURLFromExtensionId(extension_id)); |
| } |
| |
| const Extension* ExtensionService::GetExtensionById( |
| const std::string& id, int include_mask) const { |
| std::string lowercase_id = StringToLowerASCII(id); |
| if (include_mask & INCLUDE_ENABLED) { |
| const Extension* extension = extensions_.GetByID(lowercase_id); |
| if (extension) |
| return extension; |
| } |
| if (include_mask & INCLUDE_DISABLED) { |
| const Extension* extension = disabled_extensions_.GetByID(lowercase_id); |
| if (extension) |
| return extension; |
| } |
| if (include_mask & INCLUDE_TERMINATED) { |
| const Extension* extension = terminated_extensions_.GetByID(lowercase_id); |
| if (extension) |
| return extension; |
| } |
| if (include_mask & INCLUDE_BLACKLISTED) { |
| const Extension* extension = blacklisted_extensions_.GetByID(lowercase_id); |
| if (extension) |
| return extension; |
| } |
| return NULL; |
| } |
| |
| void ExtensionService::Init() { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| base::Time begin_time = base::Time::Now(); |
| |
| DCHECK(!is_ready()); // Can't redo init. |
| DCHECK_EQ(extensions_.size(), 0u); |
| |
| const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); |
| if (cmd_line->HasSwitch(switches::kInstallFromWebstore) || |
| cmd_line->HasSwitch(switches::kLimitedInstallFromWebstore)) { |
| // The sole purpose of this launch is to install a new extension from CWS |
| // and immediately terminate: loading already installed extensions is |
| // unnecessary and may interfere with the inline install dialog (e.g. if an |
| // extension listens to onStartup and opens a window). |
| SetReadyAndNotifyListeners(); |
| } else { |
| // LoadAllExtensions() calls OnLoadedInstalledExtensions(). |
| component_loader_->LoadAll(); |
| extensions::InstalledLoader(this).LoadAllExtensions(); |
| |
| ReconcileKnownDisabled(); |
| |
| // Attempt to re-enable extensions whose only disable reason is reloading. |
| std::vector<std::string> extensions_to_enable; |
| for (ExtensionSet::const_iterator iter = disabled_extensions_.begin(); |
| iter != disabled_extensions_.end(); ++iter) { |
| const Extension* e = iter->get(); |
| if (extension_prefs_->GetDisableReasons(e->id()) == |
| Extension::DISABLE_RELOAD) { |
| extensions_to_enable.push_back(e->id()); |
| } |
| } |
| for (std::vector<std::string>::iterator it = extensions_to_enable.begin(); |
| it != extensions_to_enable.end(); ++it) { |
| EnableExtension(*it); |
| } |
| |
| // Finish install (if possible) of extensions that were still delayed while |
| // the browser was shut down. |
| scoped_ptr<extensions::ExtensionPrefs::ExtensionsInfo> delayed_info( |
| extension_prefs_->GetAllDelayedInstallInfo()); |
| for (size_t i = 0; i < delayed_info->size(); ++i) { |
| ExtensionInfo* info = delayed_info->at(i).get(); |
| scoped_refptr<const Extension> extension(NULL); |
| if (info->extension_manifest) { |
| std::string error; |
| extension = Extension::Create( |
| info->extension_path, |
| info->extension_location, |
| *info->extension_manifest, |
| extension_prefs_->GetDelayedInstallCreationFlags( |
| info->extension_id), |
| info->extension_id, |
| &error); |
| if (extension.get()) |
| delayed_installs_.Insert(extension); |
| } |
| } |
| MaybeFinishDelayedInstallations(); |
| |
| scoped_ptr<extensions::ExtensionPrefs::ExtensionsInfo> delayed_info2( |
| extension_prefs_->GetAllDelayedInstallInfo()); |
| UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateOnLoad", |
| delayed_info2->size() - delayed_info->size()); |
| |
| SetReadyAndNotifyListeners(); |
| |
| // TODO(erikkay) this should probably be deferred to a future point |
| // rather than running immediately at startup. |
| CheckForExternalUpdates(); |
| |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&ExtensionService::GarbageCollectExtensions, AsWeakPtr()), |
| base::TimeDelta::FromSeconds(kGarbageCollectStartupDelay)); |
| |
| if (extension_prefs_->NeedsStorageGarbageCollection()) { |
| GarbageCollectIsolatedStorage(); |
| extension_prefs_->SetNeedsStorageGarbageCollection(false); |
| } |
| system_->management_policy()->RegisterProvider( |
| shared_module_policy_provider_.get()); |
| } |
| |
| UMA_HISTOGRAM_TIMES("Extensions.ExtensionServiceInitTime", |
| base::Time::Now() - begin_time); |
| } |
| |
| bool ExtensionService::UpdateExtension(const std::string& id, |
| const base::FilePath& extension_path, |
| const GURL& download_url, |
| CrxInstaller** out_crx_installer) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (browser_terminating_) { |
| LOG(WARNING) << "Skipping UpdateExtension due to browser shutdown"; |
| // Leak the temp file at extension_path. We don't want to add to the disk |
| // I/O burden at shutdown, we can't rely on the I/O completing anyway, and |
| // the file is in the OS temp directory which should be cleaned up for us. |
| return false; |
| } |
| |
| const extensions::PendingExtensionInfo* pending_extension_info = |
| pending_extension_manager()->GetById(id); |
| |
| const Extension* extension = GetInstalledExtension(id); |
| if (!pending_extension_info && !extension) { |
| 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. |
| if (!GetFileTaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind( |
| &extension_file_util::DeleteFile, extension_path, false))) |
| NOTREACHED(); |
| |
| return false; |
| } |
| |
| // We want a silent install only for non-pending extensions and |
| // pending extensions that have install_silently set. |
| scoped_ptr<ExtensionInstallPrompt> client; |
| if (pending_extension_info && !pending_extension_info->install_silently()) |
| client.reset(ExtensionInstallUI::CreateInstallPromptWithProfile(profile_)); |
| |
| scoped_refptr<CrxInstaller> installer( |
| CrxInstaller::Create(this, client.Pass())); |
| installer->set_expected_id(id); |
| int creation_flags = Extension::NO_FLAGS; |
| if (pending_extension_info) { |
| installer->set_install_source(pending_extension_info->install_source()); |
| if (pending_extension_info->install_silently()) |
| installer->set_allow_silent_install(true); |
| creation_flags = pending_extension_info->creation_flags(); |
| if (pending_extension_info->mark_acknowledged()) |
| AcknowledgeExternalExtension(id); |
| } else if (extension) { |
| installer->set_install_source(extension->location()); |
| } |
| // If the extension was installed from or has migrated to the webstore, or |
| // its auto-update URL is from the webstore, treat it as a webstore install. |
| // Note that we ignore some older extensions with blank auto-update URLs |
| // because we are mostly concerned with restrictions on NaCl extensions, |
| // which are newer. |
| if ((extension && extension->from_webstore()) || |
| (extension && extensions::ManifestURL::UpdatesFromGallery(extension)) || |
| (!extension && extension_urls::IsWebstoreUpdateUrl( |
| pending_extension_info->update_url()))) { |
| creation_flags |= Extension::FROM_WEBSTORE; |
| } |
| |
| // Bookmark apps being updated is kind of a contradiction, but that's because |
| // we mark the default apps as bookmark apps, and they're hosted in the web |
| // store, thus they can get updated. See http://crbug.com/101605 for more |
| // details. |
| if (extension && extension->from_bookmark()) |
| creation_flags |= Extension::FROM_BOOKMARK; |
| |
| if (extension && extension->was_installed_by_default()) |
| creation_flags |= Extension::WAS_INSTALLED_BY_DEFAULT; |
| |
| installer->set_creation_flags(creation_flags); |
| |
| installer->set_delete_source(true); |
| installer->set_download_url(download_url); |
| installer->set_install_cause(extension_misc::INSTALL_CAUSE_UPDATE); |
| installer->InstallCrx(extension_path); |
| |
| if (out_crx_installer) |
| *out_crx_installer = installer.get(); |
| |
| return true; |
| } |
| |
| void ExtensionService::ReloadExtension(const std::string extension_id) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // If the extension is already reloading, don't reload again. |
| if (extension_prefs_->GetDisableReasons(extension_id) & |
| Extension::DISABLE_RELOAD) { |
| return; |
| } |
| |
| base::FilePath path; |
| const Extension* current_extension = GetExtensionById(extension_id, false); |
| |
| // Disable 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. |
| // TODO(yoz): this is not incognito-safe! |
| extensions::ProcessManager* manager = system_->process_manager(); |
| extensions::ExtensionHost* host = |
| manager->GetBackgroundHostForExtension(extension_id); |
| if (host && DevToolsAgentHost::HasFor(host->render_view_host())) { |
| // Look for an open inspector for the background page. |
| scoped_refptr<DevToolsAgentHost> agent_host = |
| DevToolsAgentHost::GetOrCreateFor(host->render_view_host()); |
| agent_host->DisconnectRenderViewHost(); |
| orphaned_dev_tools_[extension_id] = agent_host; |
| } |
| |
| path = current_extension->path(); |
| // BeingUpgraded is set back to false when the extension is added. |
| SetBeingUpgraded(current_extension, true); |
| DisableExtension(extension_id, Extension::DISABLE_RELOAD); |
| reloading_extensions_.insert(extension_id); |
| } else { |
| path = unloaded_extension_paths_[extension_id]; |
| } |
| |
| if (delayed_installs_.Contains(extension_id)) { |
| FinishDelayedInstallation(extension_id); |
| return; |
| } |
| |
| // If we're reloading a component extension, use the component extension |
| // loader's reloader. |
| if (component_loader_->Exists(extension_id)) { |
| SetBeingReloaded(extension_id, true); |
| component_loader_->Reload(extension_id); |
| SetBeingReloaded(extension_id, false); |
| return; |
| } |
| |
| // Check the installed extensions to see if what we're reloading was already |
| // installed. |
| SetBeingReloaded(extension_id, true); |
| scoped_ptr<ExtensionInfo> installed_extension( |
| extension_prefs_->GetInstalledExtensionInfo(extension_id)); |
| if (installed_extension.get() && |
| installed_extension->extension_manifest.get()) { |
| extensions::InstalledLoader(this).Load(*installed_extension, false); |
| } else { |
| // Otherwise, the extension is unpacked (location LOAD). |
| // 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()); |
| extensions::UnpackedInstaller::Create(this)->Load(path); |
| } |
| // When reloading is done, mark this extension as done reloading. |
| SetBeingReloaded(extension_id, false); |
| } |
| |
| bool ExtensionService::UninstallExtension( |
| std::string extension_id, |
| bool external_uninstall, |
| base::string16* error) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| scoped_refptr<const Extension> extension(GetInstalledExtension(extension_id)); |
| |
| // Callers should not send us nonexistent extensions. |
| CHECK(extension.get()); |
| |
| // Policy change which triggers an uninstall will always set |
| // |external_uninstall| to true so this is the only way to uninstall |
| // managed extensions. |
| if (!external_uninstall && |
| !system_->management_policy()->UserMayModifySettings( |
| extension.get(), error)) { |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_UNINSTALL_NOT_ALLOWED, |
| content::Source<Profile>(profile_), |
| content::Details<const Extension>(extension.get())); |
| return false; |
| } |
| |
| syncer::SyncChange sync_change; |
| if (extension_sync_service_) { |
| sync_change = extension_sync_service_->PrepareToSyncUninstallExtension( |
| extension.get(), is_ready()); |
| } |
| |
| extensions::ExtensionSystem::Get(profile_)->install_verifier()->Remove( |
| extension->id()); |
| |
| if (IsUnacknowledgedExternalExtension(extension.get())) { |
| UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent", |
| EXTERNAL_EXTENSION_UNINSTALLED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| if (extensions::ManifestURL::UpdatesFromGallery(extension.get())) { |
| UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore", |
| EXTERNAL_EXTENSION_UNINSTALLED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore", |
| EXTERNAL_EXTENSION_UNINSTALLED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| } |
| } |
| UMA_HISTOGRAM_ENUMERATION("Extensions.UninstallType", |
| extension->GetType(), 100); |
| RecordPermissionMessagesHistogram(extension.get(), |
| "Extensions.Permissions_Uninstall"); |
| |
| // Unload before doing more cleanup to ensure that nothing is hanging on to |
| // any of these resources. |
| UnloadExtension(extension_id, UnloadedExtensionInfo::REASON_UNINSTALL); |
| |
| // Tell the backend to start deleting installed extensions on the file thread. |
| if (!Manifest::IsUnpackedLocation(extension->location())) { |
| if (!GetFileTaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind( |
| &extension_file_util::UninstallExtension, |
| install_directory_, |
| extension_id))) |
| NOTREACHED(); |
| } |
| |
| GURL launch_web_url_origin( |
| extensions::AppLaunchInfo::GetLaunchWebURL(extension.get()).GetOrigin()); |
| bool is_storage_isolated = |
| extensions::AppIsolationInfo::HasIsolatedStorage(extension.get()); |
| |
| if (is_storage_isolated) { |
| BrowserContext::AsyncObliterateStoragePartition( |
| profile_, |
| GetSiteForExtensionId(extension_id), |
| base::Bind(&ExtensionService::OnNeedsToGarbageCollectIsolatedStorage, |
| AsWeakPtr())); |
| } else { |
| if (extension->is_hosted_app() && |
| !profile_->GetExtensionSpecialStoragePolicy()-> |
| IsStorageProtected(launch_web_url_origin)) { |
| extensions::DataDeleter::StartDeleting( |
| profile_, extension_id, launch_web_url_origin); |
| } |
| extensions::DataDeleter::StartDeleting(profile_, extension_id, |
| extension->url()); |
| } |
| |
| UntrackTerminatedExtension(extension_id); |
| |
| // Notify interested parties that we've uninstalled this extension. |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_UNINSTALLED, |
| content::Source<Profile>(profile_), |
| content::Details<const Extension>(extension.get())); |
| |
| if (extension_sync_service_) { |
| extension_sync_service_->ProcessSyncUninstallExtension(extension_id, |
| sync_change); |
| } |
| |
| delayed_installs_.Remove(extension_id); |
| |
| PruneSharedModulesOnUninstall(extension.get()); |
| |
| extension_prefs_->OnExtensionUninstalled(extension_id, extension->location(), |
| external_uninstall); |
| |
| // Track the uninstallation. |
| UMA_HISTOGRAM_ENUMERATION("Extensions.ExtensionUninstalled", 1, 2); |
| |
| return true; |
| } |
| |
| bool ExtensionService::IsExtensionEnabled( |
| const std::string& extension_id) const { |
| if (extensions_.Contains(extension_id) || |
| terminated_extensions_.Contains(extension_id)) { |
| return true; |
| } |
| |
| if (disabled_extensions_.Contains(extension_id) || |
| blacklisted_extensions_.Contains(extension_id)) { |
| return false; |
| } |
| |
| // If the extension hasn't been loaded yet, check the prefs for it. Assume |
| // enabled unless otherwise noted. |
| return !extension_prefs_->IsExtensionDisabled(extension_id) && |
| !extension_prefs_->IsExternalExtensionUninstalled(extension_id); |
| } |
| |
| bool ExtensionService::IsExternalExtensionUninstalled( |
| const std::string& extension_id) const { |
| return extension_prefs_->IsExternalExtensionUninstalled(extension_id); |
| } |
| |
| void ExtensionService::EnableExtension(const std::string& extension_id) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (IsExtensionEnabled(extension_id)) |
| return; |
| const Extension* extension = disabled_extensions_.GetByID(extension_id); |
| |
| ManagementPolicy* policy = system_->management_policy(); |
| if (extension && policy->MustRemainDisabled(extension, NULL, NULL)) { |
| UMA_HISTOGRAM_COUNTS_100("Extensions.EnableDeniedByPolicy", 1); |
| return; |
| } |
| |
| extension_prefs_->SetExtensionState(extension_id, Extension::ENABLED); |
| extension_prefs_->ClearDisableReasons(extension_id); |
| |
| // This can happen if sync enables an extension that is not |
| // installed yet. |
| if (!extension) |
| return; |
| |
| if (IsUnacknowledgedExternalExtension(extension)) { |
| UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent", |
| EXTERNAL_EXTENSION_REENABLED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| if (extensions::ManifestURL::UpdatesFromGallery(extension)) { |
| UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore", |
| EXTERNAL_EXTENSION_REENABLED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore", |
| EXTERNAL_EXTENSION_REENABLED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| } |
| AcknowledgeExternalExtension(extension->id()); |
| } |
| |
| // Move it over to the enabled list. |
| extensions_.Insert(make_scoped_refptr(extension)); |
| disabled_extensions_.Remove(extension->id()); |
| |
| NotifyExtensionLoaded(extension); |
| |
| // Notify listeners that the extension was enabled. |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_ENABLED, |
| content::Source<Profile>(profile_), |
| content::Details<const Extension>(extension)); |
| |
| if (extension_sync_service_) |
| extension_sync_service_->SyncEnableExtension(*extension); |
| } |
| |
| void ExtensionService::DisableExtension( |
| const std::string& extension_id, |
| Extension::DisableReason disable_reason) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // The extension may have been disabled already. |
| if (!IsExtensionEnabled(extension_id)) |
| return; |
| |
| const Extension* extension = GetInstalledExtension(extension_id); |
| // |extension| can be NULL if sync disables an extension that is not |
| // installed yet. |
| if (extension && |
| disable_reason != Extension::DISABLE_RELOAD && |
| !system_->management_policy()->UserMayModifySettings(extension, NULL)) { |
| return; |
| } |
| |
| extension_prefs_->SetExtensionState(extension_id, Extension::DISABLED); |
| extension_prefs_->AddDisableReason(extension_id, disable_reason); |
| |
| int include_mask = INCLUDE_EVERYTHING & ~INCLUDE_DISABLED; |
| extension = GetExtensionById(extension_id, include_mask); |
| if (!extension) |
| return; |
| |
| // Reset the background_page_ready flag |
| if (extensions::BackgroundInfo::HasBackgroundPage(extension)) |
| extension_runtime_data_[extension->id()].background_page_ready = false; |
| |
| // Move it over to the disabled list. Don't send a second unload notification |
| // for terminated extensions being disabled. |
| disabled_extensions_.Insert(make_scoped_refptr(extension)); |
| if (extensions_.Contains(extension->id())) { |
| extensions_.Remove(extension->id()); |
| NotifyExtensionUnloaded(extension, UnloadedExtensionInfo::REASON_DISABLE); |
| } else { |
| terminated_extensions_.Remove(extension->id()); |
| } |
| |
| if (extension_sync_service_) |
| extension_sync_service_->SyncDisableExtension(*extension); |
| } |
| |
| void ExtensionService::DisableUserExtensions( |
| const std::vector<std::string>& except_ids) { |
| extensions::ManagementPolicy* management_policy = |
| system_->management_policy(); |
| extensions::ExtensionList to_disable; |
| |
| for (ExtensionSet::const_iterator extension = extensions_.begin(); |
| extension != extensions_.end(); ++extension) { |
| if (management_policy->UserMayModifySettings(extension->get(), NULL)) |
| to_disable.push_back(*extension); |
| } |
| for (ExtensionSet::const_iterator extension = terminated_extensions_.begin(); |
| extension != terminated_extensions_.end(); ++extension) { |
| if (management_policy->UserMayModifySettings(extension->get(), NULL)) |
| to_disable.push_back(*extension); |
| } |
| |
| for (extensions::ExtensionList::const_iterator extension = to_disable.begin(); |
| extension != to_disable.end(); ++extension) { |
| if ((*extension)->was_installed_by_default() && |
| extension_urls::IsWebstoreUpdateUrl( |
| extensions::ManifestURL::GetUpdateURL(*extension))) |
| continue; |
| const std::string& id = (*extension)->id(); |
| if (except_ids.end() == std::find(except_ids.begin(), except_ids.end(), id)) |
| DisableExtension(id, extensions::Extension::DISABLE_USER_ACTION); |
| } |
| } |
| |
| void ExtensionService::GrantPermissionsAndEnableExtension( |
| const Extension* extension) { |
| GrantPermissions(extension); |
| RecordPermissionMessagesHistogram( |
| extension, "Extensions.Permissions_ReEnable"); |
| extension_prefs_->SetDidExtensionEscalatePermissions(extension, false); |
| EnableExtension(extension->id()); |
| } |
| |
| void ExtensionService::GrantPermissions(const Extension* extension) { |
| CHECK(extension); |
| extensions::PermissionsUpdater perms_updater(profile()); |
| perms_updater.GrantActivePermissions(extension); |
| } |
| |
| // static |
| void ExtensionService::RecordPermissionMessagesHistogram( |
| const Extension* extension, const char* histogram) { |
| // Since this is called from multiple sources, and since the histogram macros |
| // use statics, we need to manually lookup the histogram ourselves. |
| base::HistogramBase* counter = base::LinearHistogram::FactoryGet( |
| histogram, |
| 1, |
| PermissionMessage::kEnumBoundary, |
| PermissionMessage::kEnumBoundary + 1, |
| base::HistogramBase::kUmaTargetedHistogramFlag); |
| |
| PermissionMessages permissions = |
| extensions::PermissionsData::GetPermissionMessages(extension); |
| if (permissions.empty()) { |
| counter->Add(PermissionMessage::kNone); |
| } else { |
| for (PermissionMessages::iterator it = permissions.begin(); |
| it != permissions.end(); ++it) |
| counter->Add(it->id()); |
| } |
| } |
| |
| void ExtensionService::NotifyExtensionLoaded(const 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. |
| system_->RegisterExtensionWithRequestContexts(extension); |
| |
| // Tell renderers about the new extension, unless it's a theme (renderers |
| // don't need to know about themes). |
| if (!extension->is_theme()) { |
| for (content::RenderProcessHost::iterator i( |
| content::RenderProcessHost::AllHostsIterator()); |
| !i.IsAtEnd(); i.Advance()) { |
| content::RenderProcessHost* host = i.GetCurrentValue(); |
| Profile* host_profile = |
| Profile::FromBrowserContext(host->GetBrowserContext()); |
| if (host_profile->GetOriginalProfile() == |
| profile_->GetOriginalProfile()) { |
| std::vector<ExtensionMsg_Loaded_Params> loaded_extensions( |
| 1, ExtensionMsg_Loaded_Params(extension)); |
| host->Send( |
| new ExtensionMsg_Loaded(loaded_extensions)); |
| } |
| } |
| } |
| |
| // Tell subsystems that use the EXTENSION_LOADED notification about the new |
| // extension. |
| // |
| // NOTE: It is important that this happen after notifying the renderers about |
| // the new extensions so that if we navigate to an extension URL in |
| // NOTIFICATION_EXTENSION_LOADED, the renderer is guaranteed to know about it. |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_LOADED, |
| content::Source<Profile>(profile_), |
| content::Details<const Extension>(extension)); |
| |
| // Tell a random-ass collection of other subsystems about the new extension. |
| // TODO(aa): What should we do with all this goop? Can it move into the |
| // relevant objects via EXTENSION_LOADED? |
| |
| profile_->GetExtensionSpecialStoragePolicy()-> |
| GrantRightsForExtension(extension); |
| |
| UpdateActiveExtensionsInCrashReporter(); |
| |
| // If the extension has permission to load chrome://favicon/ resources we need |
| // to make sure that the FaviconSource is registered with the |
| // ChromeURLDataManager. |
| if (extensions::PermissionsData::HasHostPermission( |
| extension, GURL(chrome::kChromeUIFaviconURL))) { |
| FaviconSource* favicon_source = new FaviconSource(profile_, |
| FaviconSource::FAVICON); |
| content::URLDataSource::Add(profile_, favicon_source); |
| } |
| |
| #if !defined(OS_ANDROID) |
| // Same for chrome://theme/ resources. |
| if (extensions::PermissionsData::HasHostPermission( |
| extension, GURL(chrome::kChromeUIThemeURL))) { |
| ThemeSource* theme_source = new ThemeSource(profile_); |
| content::URLDataSource::Add(profile_, theme_source); |
| } |
| #endif |
| |
| // Same for chrome://thumb/ resources. |
| if (extensions::PermissionsData::HasHostPermission( |
| extension, GURL(chrome::kChromeUIThumbnailURL))) { |
| ThumbnailSource* thumbnail_source = new ThumbnailSource(profile_, false); |
| content::URLDataSource::Add(profile_, thumbnail_source); |
| } |
| } |
| |
| void ExtensionService::NotifyExtensionUnloaded( |
| const Extension* extension, |
| UnloadedExtensionInfo::Reason reason) { |
| UnloadedExtensionInfo details(extension, reason); |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_UNLOADED, |
| content::Source<Profile>(profile_), |
| content::Details<UnloadedExtensionInfo>(&details)); |
| |
| for (content::RenderProcessHost::iterator i( |
| content::RenderProcessHost::AllHostsIterator()); |
| !i.IsAtEnd(); i.Advance()) { |
| content::RenderProcessHost* host = i.GetCurrentValue(); |
| Profile* host_profile = |
| Profile::FromBrowserContext(host->GetBrowserContext()); |
| if (host_profile->GetOriginalProfile() == profile_->GetOriginalProfile()) |
| host->Send(new ExtensionMsg_Unloaded(extension->id())); |
| } |
| |
| system_->UnregisterExtensionWithRequestContexts(extension->id(), reason); |
| profile_->GetExtensionSpecialStoragePolicy()-> |
| RevokeRightsForExtension(extension); |
| |
| #if defined(OS_CHROMEOS) |
| // Revoke external file access for the extension from its file system context. |
| // It is safe to access the extension's storage partition at this point. The |
| // storage partition may get destroyed only after the extension gets unloaded. |
| GURL site = extensions::ExtensionSystem::Get(profile_)->extension_service()-> |
| GetSiteForExtensionId(extension->id()); |
| fileapi::FileSystemContext* filesystem_context = |
| BrowserContext::GetStoragePartitionForSite(profile_, site)-> |
| GetFileSystemContext(); |
| if (filesystem_context && filesystem_context->external_backend()) { |
| filesystem_context->external_backend()-> |
| RevokeAccessForExtension(extension->id()); |
| } |
| #endif |
| |
| UpdateActiveExtensionsInCrashReporter(); |
| } |
| |
| Profile* ExtensionService::profile() { |
| return profile_; |
| } |
| |
| extensions::ExtensionPrefs* ExtensionService::extension_prefs() { |
| return extension_prefs_; |
| } |
| |
| const extensions::ExtensionPrefs* ExtensionService::extension_prefs() const { |
| return extension_prefs_; |
| } |
| |
| extensions::SettingsFrontend* ExtensionService::settings_frontend() { |
| return settings_frontend_.get(); |
| } |
| |
| extensions::ContentSettingsStore* ExtensionService::GetContentSettingsStore() { |
| return extension_prefs()->content_settings_store(); |
| } |
| |
| bool ExtensionService::is_ready() { |
| return ready_->is_signaled(); |
| } |
| |
| base::SequencedTaskRunner* ExtensionService::GetFileTaskRunner() { |
| if (file_task_runner_.get()) |
| return file_task_runner_.get(); |
| |
| // We should be able to interrupt any part of extension install process during |
| // shutdown. SKIP_ON_SHUTDOWN ensures that not started extension install tasks |
| // will be ignored/deleted while we will block on started tasks. |
| std::string token("ext_install-"); |
| token.append(profile_->GetPath().AsUTF8Unsafe()); |
| file_task_runner_ = BrowserThread::GetBlockingPool()-> |
| GetSequencedTaskRunnerWithShutdownBehavior( |
| BrowserThread::GetBlockingPool()->GetNamedSequenceToken(token), |
| base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); |
| return file_task_runner_.get(); |
| } |
| |
| extensions::ExtensionUpdater* ExtensionService::updater() { |
| return updater_.get(); |
| } |
| |
| void ExtensionService::CheckManagementPolicy() { |
| std::vector<std::string> to_unload; |
| std::map<std::string, Extension::DisableReason> to_disable; |
| |
| // Loop through the extensions list, finding extensions we need to unload or |
| // disable. |
| for (ExtensionSet::const_iterator iter = extensions_.begin(); |
| iter != extensions_.end(); ++iter) { |
| const Extension* extension = (iter->get()); |
| if (!system_->management_policy()->UserMayLoad(extension, NULL)) |
| to_unload.push_back(extension->id()); |
| Extension::DisableReason disable_reason = Extension::DISABLE_NONE; |
| if (system_->management_policy()->MustRemainDisabled( |
| extension, &disable_reason, NULL)) |
| to_disable[extension->id()] = disable_reason; |
| } |
| |
| for (size_t i = 0; i < to_unload.size(); ++i) |
| UnloadExtension(to_unload[i], UnloadedExtensionInfo::REASON_DISABLE); |
| |
| for (std::map<std::string, Extension::DisableReason>::const_iterator i = |
| to_disable.begin(); i != to_disable.end(); ++i) |
| DisableExtension(i->first, i->second); |
| } |
| |
| void ExtensionService::CheckForUpdatesSoon() { |
| if (updater()) { |
| if (AreAllExternalProvidersReady()) { |
| updater()->CheckSoon(); |
| } else { |
| // Sync can start updating before all the external providers are ready |
| // during startup. Start the update as soon as those providers are ready, |
| // but not before. |
| update_once_all_providers_are_ready_ = true; |
| } |
| } else { |
| LOG(WARNING) << "CheckForUpdatesSoon() called with auto-update turned off"; |
| } |
| } |
| |
| void ExtensionService::OnExtensionMoved( |
| const std::string& moved_extension_id, |
| const std::string& predecessor_extension_id, |
| const std::string& successor_extension_id) { |
| extension_prefs_->app_sorting()->OnExtensionMoved( |
| moved_extension_id, |
| predecessor_extension_id, |
| successor_extension_id); |
| |
| const Extension* extension = GetInstalledExtension(moved_extension_id); |
| if (extension_sync_service_ && extension) { |
| extension_sync_service_->SyncExtensionChangeIfNeeded(*extension); |
| } |
| } |
| |
| // 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. |
| // Errors are reported through ExtensionErrorReporter. Succcess is not |
| // reported. |
| void ExtensionService::CheckForExternalUpdates() { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // 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. |
| |
| // Ask each external extension provider to give us a call back for each |
| // extension they know about. See OnExternalExtension(File|UpdateUrl)Found. |
| extensions::ProviderCollection::const_iterator i; |
| for (i = external_extension_providers_.begin(); |
| i != external_extension_providers_.end(); ++i) { |
| extensions::ExternalProviderInterface* provider = i->get(); |
| provider->VisitRegisteredExtension(); |
| } |
| |
| // Do any required work that we would have done after completion of all |
| // providers. |
| if (external_extension_providers_.empty()) { |
| OnAllExternalProvidersReady(); |
| } |
| } |
| |
| void ExtensionService::OnExternalProviderReady( |
| const extensions::ExternalProviderInterface* provider) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| CHECK(provider->IsReady()); |
| |
| // An external provider has finished loading. We only take action |
| // if all of them are finished. So we check them first. |
| if (AreAllExternalProvidersReady()) |
| OnAllExternalProvidersReady(); |
| } |
| |
| bool ExtensionService::AreAllExternalProvidersReady() const { |
| extensions::ProviderCollection::const_iterator i; |
| for (i = external_extension_providers_.begin(); |
| i != external_extension_providers_.end(); ++i) { |
| if (!i->get()->IsReady()) |
| return false; |
| } |
| return true; |
| } |
| |
| void ExtensionService::OnAllExternalProvidersReady() { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| base::TimeDelta elapsed = base::Time::Now() - profile_->GetStartTime(); |
| UMA_HISTOGRAM_TIMES("Extension.ExternalProvidersReadyAfter", elapsed); |
| |
| // Install any pending extensions. |
| if (update_once_all_providers_are_ready_ && updater()) { |
| update_once_all_providers_are_ready_ = false; |
| updater()->CheckNow(extensions::ExtensionUpdater::CheckParams()); |
| } |
| |
| // Uninstall all the unclaimed extensions. |
| scoped_ptr<extensions::ExtensionPrefs::ExtensionsInfo> extensions_info( |
| extension_prefs_->GetInstalledExtensionsInfo()); |
| for (size_t i = 0; i < extensions_info->size(); ++i) { |
| ExtensionInfo* info = extensions_info->at(i).get(); |
| if (Manifest::IsExternalLocation(info->extension_location)) |
| CheckExternalUninstall(info->extension_id); |
| } |
| IdentifyAlertableExtensions(); |
| } |
| |
| void ExtensionService::IdentifyAlertableExtensions() { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Build up the lists of extensions that require acknowledgment. If this is |
| // the first time, grandfather extensions that would have caused |
| // notification. |
| extension_error_ui_.reset(ExtensionErrorUI::Create(this)); |
| |
| bool did_show_alert = false; |
| if (PopulateExtensionErrorUI(extension_error_ui_.get())) { |
| if (!is_first_run_) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| did_show_alert = extension_error_ui_->ShowErrorInBubbleView(); |
| } else { |
| // First run. Just acknowledge all the extensions, silently, by |
| // shortcutting the display of the UI and going straight to the |
| // callback for pressing the Accept button. |
| HandleExtensionAlertAccept(); |
| } |
| } |
| |
| UpdateExternalExtensionAlert(); |
| |
| if (!did_show_alert) |
| extension_error_ui_.reset(); |
| } |
| |
| bool ExtensionService::PopulateExtensionErrorUI( |
| ExtensionErrorUI* extension_error_ui) { |
| bool needs_alert = false; |
| |
| // Extensions that are blacklisted. |
| for (ExtensionSet::const_iterator it = blacklisted_extensions_.begin(); |
| it != blacklisted_extensions_.end(); ++it) { |
| std::string id = (*it)->id(); |
| if (!extension_prefs_->IsBlacklistedExtensionAcknowledged(id)) { |
| extension_error_ui->AddBlacklistedExtension(id); |
| needs_alert = true; |
| } |
| } |
| |
| for (ExtensionSet::const_iterator iter = extensions_.begin(); |
| iter != extensions_.end(); ++iter) { |
| const Extension* e = iter->get(); |
| |
| // Extensions disabled by policy. Note: this no longer includes blacklisted |
| // extensions, though we still show the same UI. |
| if (!system_->management_policy()->UserMayLoad(e, NULL)) { |
| if (!extension_prefs_->IsBlacklistedExtensionAcknowledged(e->id())) { |
| extension_error_ui->AddBlacklistedExtension(e->id()); |
| needs_alert = true; |
| } |
| } |
| } |
| |
| return needs_alert; |
| } |
| |
| void ExtensionService::HandleExtensionAlertClosed() { |
| const ExtensionIdSet* extension_ids = |
| extension_error_ui_->get_blacklisted_extension_ids(); |
| for (ExtensionIdSet::const_iterator iter = extension_ids->begin(); |
| iter != extension_ids->end(); ++iter) { |
| extension_prefs_->AcknowledgeBlacklistedExtension(*iter); |
| } |
| extension_error_ui_.reset(); |
| } |
| |
| void ExtensionService::HandleExtensionAlertAccept() { |
| extension_error_ui_->Close(); |
| } |
| |
| void ExtensionService::AcknowledgeExternalExtension(const std::string& id) { |
| extension_prefs_->AcknowledgeExternalExtension(id); |
| UpdateExternalExtensionAlert(); |
| } |
| |
| bool ExtensionService::IsUnacknowledgedExternalExtension( |
| const Extension* extension) { |
| if (!FeatureSwitch::prompt_for_external_extensions()->IsEnabled()) |
| return false; |
| |
| return (Manifest::IsExternalLocation(extension->location()) && |
| !extension_prefs_->IsExternalExtensionAcknowledged(extension->id()) && |
| !(extension_prefs_->GetDisableReasons(extension->id()) & |
| Extension::DISABLE_SIDELOAD_WIPEOUT)); |
| } |
| |
| void ExtensionService::ReconcileKnownDisabled() { |
| ExtensionIdSet known_disabled_ids; |
| if (!extension_prefs_->GetKnownDisabled(&known_disabled_ids)) { |
| extension_prefs_->SetKnownDisabled(disabled_extensions_.GetIDs()); |
| UMA_HISTOGRAM_BOOLEAN("Extensions.KnownDisabledInitialized", true); |
| return; |
| } |
| |
| // Both |known_disabled_ids| and |extensions_| are ordered (by definition |
| // of std::map and std::set). Iterate forward over both sets in parallel |
| // to find matching IDs and disable the corresponding extensions. |
| ExtensionSet::const_iterator extensions_it = extensions_.begin(); |
| ExtensionIdSet::const_iterator known_disabled_ids_it = |
| known_disabled_ids.begin(); |
| int known_disabled_count = 0; |
| while (extensions_it != extensions_.end() && |
| known_disabled_ids_it != known_disabled_ids.end()) { |
| const std::string& extension_id = extensions_it->get()->id(); |
| const int comparison = extension_id.compare(*known_disabled_ids_it); |
| if (comparison < 0) { |
| ++extensions_it; |
| } else if (comparison > 0) { |
| ++known_disabled_ids_it; |
| } else { |
| ++known_disabled_count; |
| // Advance |extensions_it| immediately as it will be invalidated upon |
| // disabling the extension it points to. |
| ++extensions_it; |
| ++known_disabled_ids_it; |
| DisableExtension(extension_id, Extension::DISABLE_KNOWN_DISABLED); |
| } |
| } |
| UMA_HISTOGRAM_COUNTS_100("Extensions.KnownDisabledReDisabled", |
| known_disabled_count); |
| |
| // Update the list of known disabled to reflect every change to |
| // |disabled_extensions_| from this point forward. |
| disabled_extensions_.set_modification_callback( |
| base::Bind(&extensions::ExtensionPrefs::SetKnownDisabled, |
| base::Unretained(extension_prefs_))); |
| } |
| |
| void ExtensionService::HandleExtensionAlertDetails() { |
| extension_error_ui_->ShowExtensions(); |
| // ShowExtensions may cause the error UI to close synchronously, e.g. if it |
| // causes a navigation. |
| if (extension_error_ui_) |
| extension_error_ui_->Close(); |
| } |
| |
| void ExtensionService::UpdateExternalExtensionAlert() { |
| if (!FeatureSwitch::prompt_for_external_extensions()->IsEnabled()) |
| return; |
| |
| const Extension* extension = NULL; |
| for (ExtensionSet::const_iterator iter = disabled_extensions_.begin(); |
| iter != disabled_extensions_.end(); ++iter) { |
| const Extension* e = iter->get(); |
| if (IsUnacknowledgedExternalExtension(e)) { |
| extension = e; |
| break; |
| } |
| } |
| |
| if (extension) { |
| if (!extensions::HasExternalInstallError(this)) { |
| if (extension_prefs_->IncrementAcknowledgePromptCount(extension->id()) > |
| kMaxExtensionAcknowledgePromptCount) { |
| // Stop prompting for this extension, and check if there's another |
| // one that needs prompting. |
| extension_prefs_->AcknowledgeExternalExtension(extension->id()); |
| UpdateExternalExtensionAlert(); |
| UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent", |
| EXTERNAL_EXTENSION_IGNORED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| if (extensions::ManifestURL::UpdatesFromGallery(extension)) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Extensions.ExternalExtensionEventWebstore", |
| EXTERNAL_EXTENSION_IGNORED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Extensions.ExternalExtensionEventNonWebstore", |
| EXTERNAL_EXTENSION_IGNORED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| } |
| return; |
| } |
| if (is_first_run_) |
| extension_prefs_->SetExternalInstallFirstRun(extension->id()); |
| // first_run is true if the extension was installed during a first run |
| // (even if it's post-first run now). |
| bool first_run = extension_prefs_->IsExternalInstallFirstRun( |
| extension->id()); |
| extensions::AddExternalInstallError(this, extension, first_run); |
| } |
| } else { |
| extensions::RemoveExternalInstallError(this); |
| } |
| } |
| |
| void ExtensionService::UnloadExtension( |
| const std::string& extension_id, |
| UnloadedExtensionInfo::Reason reason) { |
| // Make sure the extension gets deleted after we return from this function. |
| int include_mask = INCLUDE_EVERYTHING & ~INCLUDE_TERMINATED; |
| scoped_refptr<const Extension> extension( |
| GetExtensionById(extension_id, include_mask)); |
| |
| // This method can be called via PostTask, so the extension may have been |
| // unloaded by the time this runs. |
| if (!extension.get()) { |
| // In case the extension may have crashed/uninstalled. Allow the profile to |
| // clean up its RequestContexts. |
| system_->UnregisterExtensionWithRequestContexts(extension_id, reason); |
| return; |
| } |
| |
| // 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(); |
| |
| // Clean up if the extension is meant to be enabled after a reload. |
| reloading_extensions_.erase(extension->id()); |
| |
| // Clean up runtime data. |
| extension_runtime_data_.erase(extension_id); |
| |
| if (disabled_extensions_.Contains(extension->id())) { |
| disabled_extensions_.Remove(extension->id()); |
| // Make sure the profile cleans up its RequestContexts when an already |
| // disabled extension is unloaded (since they are also tracking the disabled |
| // extensions). |
| system_->UnregisterExtensionWithRequestContexts(extension_id, reason); |
| } else { |
| // Remove the extension from our list. |
| extensions_.Remove(extension->id()); |
| NotifyExtensionUnloaded(extension.get(), reason); |
| } |
| |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_REMOVED, |
| content::Source<Profile>(profile_), |
| content::Details<const Extension>(extension.get())); |
| } |
| |
| void ExtensionService::RemoveComponentExtension( |
| const std::string& extension_id) { |
| scoped_refptr<const Extension> extension( |
| GetExtensionById(extension_id, false)); |
| UnloadExtension(extension_id, UnloadedExtensionInfo::REASON_UNINSTALL); |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_UNINSTALLED, |
| content::Source<Profile>(profile_), |
| content::Details<const Extension>(extension.get())); |
| } |
| |
| void ExtensionService::UnloadAllExtensions() { |
| profile_->GetExtensionSpecialStoragePolicy()->RevokeRightsForAllExtensions(); |
| |
| extensions_.Clear(); |
| disabled_extensions_.Clear(); |
| terminated_extensions_.Clear(); |
| extension_runtime_data_.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 ExtensionService::ReloadExtensions() { |
| UnloadAllExtensions(); |
| component_loader_->LoadAll(); |
| extensions::InstalledLoader(this).LoadAllExtensions(); |
| // Don't call SetReadyAndNotifyListeners() since tests call this multiple |
| // times. |
| } |
| |
| void ExtensionService::GarbageCollectExtensions() { |
| #if defined(OS_CHROMEOS) |
| if (disable_garbage_collection_) |
| return; |
| #endif |
| |
| if (extension_prefs_->pref_service()->ReadOnly()) |
| return; |
| |
| if (pending_extension_manager()->HasPendingExtensions()) { |
| // Don't garbage collect while there are pending installations, which may |
| // be using the temporary installation directory. Try to garbage collect |
| // again later. |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&ExtensionService::GarbageCollectExtensions, AsWeakPtr()), |
| base::TimeDelta::FromSeconds(kGarbageCollectRetryDelay)); |
| return; |
| } |
| |
| scoped_ptr<extensions::ExtensionPrefs::ExtensionsInfo> info( |
| extension_prefs_->GetInstalledExtensionsInfo()); |
| |
| std::multimap<std::string, base::FilePath> extension_paths; |
| for (size_t i = 0; i < info->size(); ++i) |
| extension_paths.insert(std::make_pair(info->at(i)->extension_id, |
| info->at(i)->extension_path)); |
| |
| info = extension_prefs_->GetAllDelayedInstallInfo(); |
| for (size_t i = 0; i < info->size(); ++i) |
| extension_paths.insert(std::make_pair(info->at(i)->extension_id, |
| info->at(i)->extension_path)); |
| |
| if (!GetFileTaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind( |
| &extension_file_util::GarbageCollectExtensions, |
| install_directory_, |
| extension_paths))) { |
| NOTREACHED(); |
| } |
| } |
| |
| void ExtensionService::SetReadyAndNotifyListeners() { |
| ready_->Signal(); |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSIONS_READY, |
| content::Source<Profile>(profile_), |
| content::NotificationService::NoDetails()); |
| } |
| |
| void ExtensionService::OnLoadedInstalledExtensions() { |
| if (updater_) |
| updater_->Start(); |
| |
| OnBlacklistUpdated(); |
| } |
| |
| void ExtensionService::AddExtension(const Extension* extension) { |
| // TODO(jstritar): We may be able to get rid of this branch by overriding the |
| // default extension state to DISABLED when the --disable-extensions flag |
| // is set (http://crbug.com/29067). |
| if (!extensions_enabled() && |
| !extension->is_theme() && |
| extension->location() != Manifest::COMPONENT && |
| !Manifest::IsExternalLocation(extension->location())) { |
| return; |
| } |
| |
| bool is_extension_upgrade = false; |
| bool is_extension_installed = false; |
| const Extension* old = GetInstalledExtension(extension->id()); |
| if (old) { |
| is_extension_installed = true; |
| int version_compare_result = |
| extension->version()->CompareTo(*(old->version())); |
| is_extension_upgrade = version_compare_result > 0; |
| // Other than for unpacked extensions, CrxInstaller should have guaranteed |
| // that we aren't downgrading. |
| if (!Manifest::IsUnpackedLocation(extension->location())) |
| CHECK_GE(version_compare_result, 0); |
| } |
| SetBeingUpgraded(extension, is_extension_upgrade); |
| |
| // The extension is now loaded, remove its data from unloaded extension map. |
| unloaded_extension_paths_.erase(extension->id()); |
| |
| // If a terminated extension is loaded, remove it from the terminated list. |
| UntrackTerminatedExtension(extension->id()); |
| |
| // If the extension was disabled for a reload, then enable it. |
| bool reloading = reloading_extensions_.erase(extension->id()) > 0; |
| |
| // Check if the extension's privileges have changed and mark the |
| // extension disabled if necessary. |
| CheckPermissionsIncrease(extension, is_extension_installed); |
| |
| if (is_extension_installed && !reloading) { |
| // To upgrade an extension in place, unload the old one and then load the |
| // new one. ReloadExtension disables the extension, which is sufficient. |
| UnloadExtension(extension->id(), UnloadedExtensionInfo::REASON_UPDATE); |
| } |
| |
| if (extension_prefs_->IsExtensionBlacklisted(extension->id())) { |
| // Only prefs is checked for the blacklist. We rely on callers to check the |
| // blacklist before calling into here, e.g. CrxInstaller checks before |
| // installation then threads through the install and pending install flow |
| // of this class, and we check when loading installed extensions. |
| blacklisted_extensions_.Insert(extension); |
| } else if (!reloading && |
| extension_prefs_->IsExtensionDisabled(extension->id())) { |
| disabled_extensions_.Insert(extension); |
| if (extension_sync_service_) |
| extension_sync_service_->SyncExtensionChangeIfNeeded(*extension); |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED, |
| content::Source<Profile>(profile_), |
| content::Details<const Extension>(extension)); |
| |
| // Show the extension disabled error if a permissions increase was the |
| // only reason it was disabled. |
| if (extension_prefs_->GetDisableReasons(extension->id()) == |
| Extension::DISABLE_PERMISSIONS_INCREASE) { |
| extensions::AddExtensionDisabledError(this, extension); |
| } |
| } else if (reloading) { |
| // Replace the old extension with the new version. |
| CHECK(!disabled_extensions_.Insert(extension)); |
| EnableExtension(extension->id()); |
| } else { |
| // All apps that are displayed in the launcher are ordered by their ordinals |
| // so we must ensure they have valid ordinals. |
| if (extension->RequiresSortOrdinal()) { |
| if (!extension->ShouldDisplayInNewTabPage()) { |
| extension_prefs_->app_sorting()->MarkExtensionAsHidden(extension->id()); |
| } |
| extension_prefs_->app_sorting()->EnsureValidOrdinals( |
| extension->id(), syncer::StringOrdinal()); |
| } |
| |
| extensions_.Insert(extension); |
| if (extension_sync_service_) |
| extension_sync_service_->SyncExtensionChangeIfNeeded(*extension); |
| NotifyExtensionLoaded(extension); |
| } |
| SetBeingUpgraded(extension, false); |
| } |
| |
| void ExtensionService::AddComponentExtension(const Extension* extension) { |
| const std::string old_version_string( |
| extension_prefs_->GetVersionString(extension->id())); |
| const Version old_version(old_version_string); |
| |
| VLOG(1) << "AddComponentExtension " << extension->name(); |
| if (!old_version.IsValid() || !old_version.Equals(*extension->version())) { |
| VLOG(1) << "Component extension " << extension->name() << " (" |
| << extension->id() << ") installing/upgrading from '" |
| << old_version_string << "' to " << extension->version()->GetString(); |
| |
| AddNewOrUpdatedExtension(extension, |
| Extension::ENABLED_COMPONENT, |
| extensions::Blacklist::NOT_BLACKLISTED, |
| syncer::StringOrdinal()); |
| return; |
| } |
| |
| AddExtension(extension); |
| } |
| |
| void ExtensionService::UpdateActivePermissions(const Extension* extension) { |
| // If the extension has used the optional permissions API, it will have a |
| // custom set of active permissions defined in the extension prefs. Here, |
| // we update the extension's active permissions based on the prefs. |
| scoped_refptr<PermissionSet> active_permissions = |
| extension_prefs()->GetActivePermissions(extension->id()); |
| |
| if (active_permissions.get()) { |
| // We restrict the active permissions to be within the bounds defined in the |
| // extension's manifest. |
| // a) active permissions must be a subset of optional + default permissions |
| // b) active permissions must contains all default permissions |
| scoped_refptr<PermissionSet> total_permissions = |
| PermissionSet::CreateUnion( |
| extensions::PermissionsData::GetRequiredPermissions(extension), |
| extensions::PermissionsData::GetOptionalPermissions(extension)); |
| |
| // Make sure the active permissions contain no more than optional + default. |
| scoped_refptr<PermissionSet> adjusted_active = |
| PermissionSet::CreateIntersection( |
| total_permissions.get(), active_permissions.get()); |
| |
| // Make sure the active permissions contain the default permissions. |
| adjusted_active = PermissionSet::CreateUnion( |
| extensions::PermissionsData::GetRequiredPermissions(extension), |
| adjusted_active.get()); |
| |
| extensions::PermissionsUpdater perms_updater(profile()); |
| perms_updater.UpdateActivePermissions(extension, adjusted_active.get()); |
| } |
| } |
| |
| void ExtensionService::CheckPermissionsIncrease(const Extension* extension, |
| bool is_extension_installed) { |
| UpdateActivePermissions(extension); |
| |
| // We keep track of all permissions the user has granted each extension. |
| // This allows extensions to gracefully support backwards compatibility |
| // by including unknown permissions in their manifests. When the user |
| // installs the extension, only the recognized permissions are recorded. |
| // When the unknown permissions become recognized (e.g., through browser |
| // upgrade), we can prompt the user to accept these new permissions. |
| // Extensions can also silently upgrade to less permissions, and then |
| // silently upgrade to a version that adds these permissions back. |
| // |
| // For example, pretend that Chrome 10 includes a permission "omnibox" |
| // for an API that adds suggestions to the omnibox. An extension can |
| // maintain backwards compatibility while still having "omnibox" in the |
| // manifest. If a user installs the extension on Chrome 9, the browser |
| // will record the permissions it recognized, not including "omnibox." |
| // When upgrading to Chrome 10, "omnibox" will be recognized and Chrome |
| // will disable the extension and prompt the user to approve the increase |
| // in privileges. The extension could then release a new version that |
| // removes the "omnibox" permission. When the user upgrades, Chrome will |
| // still remember that "omnibox" had been granted, so that if the |
| // extension once again includes "omnibox" in an upgrade, the extension |
| // can upgrade without requiring this user's approval. |
| int disable_reasons = extension_prefs_->GetDisableReasons(extension->id()); |
| |
| bool auto_grant_permission = |
| (!is_extension_installed && extension->was_installed_by_default()) || |
| extensions::ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode(); |
| // Silently grant all active permissions to default apps only on install. |
| // After install they should behave like other apps. |
| // Silently grant all active permissions to apps install in kiosk mode on both |
| // install and update. |
| if (auto_grant_permission) |
| GrantPermissions(extension); |
| |
| bool is_privilege_increase = false; |
| // We only need to compare the granted permissions to the current permissions |
| // if the extension is not allowed to silently increase its permissions. |
| if (!extensions::PermissionsData::CanSilentlyIncreasePermissions(extension) && |
| !auto_grant_permission) { |
| // Add all the recognized permissions if the granted permissions list |
| // hasn't been initialized yet. |
| scoped_refptr<PermissionSet> granted_permissions = |
| extension_prefs_->GetGrantedPermissions(extension->id()); |
| CHECK(granted_permissions.get()); |
| |
| // Here, we check if an extension's privileges have increased in a manner |
| // that requires the user's approval. This could occur because the browser |
| // upgraded and recognized additional privileges, or an extension upgrades |
| // to a version that requires additional privileges. |
| is_privilege_increase = |
| extensions::PermissionMessageProvider::Get()->IsPrivilegeIncrease( |
| granted_permissions, |
| extension->GetActivePermissions().get(), |
| extension->GetType()); |
| } |
| |
| if (is_extension_installed) { |
| // If the extension was already disabled, suppress any alerts for becoming |
| // disabled on permissions increase. |
| bool previously_disabled = |
| extension_prefs_->IsExtensionDisabled(extension->id()); |
| // Legacy disabled extensions do not have a disable reason. Infer that if |
| // there was no permission increase, it was likely disabled by the user. |
| if (previously_disabled && disable_reasons == Extension::DISABLE_NONE && |
| !extension_prefs_->DidExtensionEscalatePermissions(extension->id())) { |
| disable_reasons |= Extension::DISABLE_USER_ACTION; |
| } |
| // Extensions that came to us disabled from sync need a similar inference, |
| // except based on the new version's permissions. |
| if (previously_disabled && |
| disable_reasons == Extension::DISABLE_UNKNOWN_FROM_SYNC) { |
| // Remove the DISABLE_UNKNOWN_FROM_SYNC reason. |
| extension_prefs_->ClearDisableReasons(extension->id()); |
| if (!is_privilege_increase) |
| disable_reasons |= Extension::DISABLE_USER_ACTION; |
| } |
| disable_reasons &= ~Extension::DISABLE_UNKNOWN_FROM_SYNC; |
| } |
| |
| // Extension has changed permissions significantly. Disable it. A |
| // notification should be sent by the caller. |
| if (is_privilege_increase) { |
| disable_reasons |= Extension::DISABLE_PERMISSIONS_INCREASE; |
| if (!extension_prefs_->DidExtensionEscalatePermissions(extension->id())) { |
| RecordPermissionMessagesHistogram( |
| extension, "Extensions.Permissions_AutoDisable"); |
| } |
| extension_prefs_->SetExtensionState(extension->id(), Extension::DISABLED); |
| extension_prefs_->SetDidExtensionEscalatePermissions(extension, true); |
| } |
| if (disable_reasons != Extension::DISABLE_NONE) { |
| extension_prefs_->AddDisableReason( |
| extension->id(), |
| static_cast<Extension::DisableReason>(disable_reasons)); |
| } |
| } |
| |
| void ExtensionService::UpdateActiveExtensionsInCrashReporter() { |
| std::set<std::string> extension_ids; |
| for (ExtensionSet::const_iterator iter = extensions_.begin(); |
| iter != extensions_.end(); ++iter) { |
| const Extension* extension = iter->get(); |
| if (!extension->is_theme() && extension->location() != Manifest::COMPONENT) |
| extension_ids.insert(extension->id()); |
| } |
| |
| crash_keys::SetActiveExtensions(extension_ids); |
| } |
| |
| ExtensionService::ImportStatus ExtensionService::CheckImports( |
| const extensions::Extension* extension, |
| std::list<SharedModuleInfo::ImportInfo>* missing_modules, |
| std::list<SharedModuleInfo::ImportInfo>* outdated_modules) { |
| DCHECK(extension); |
| DCHECK(missing_modules && missing_modules->empty()); |
| DCHECK(outdated_modules && outdated_modules->empty()); |
| ImportStatus status = IMPORT_STATUS_OK; |
| if (SharedModuleInfo::ImportsModules(extension)) { |
| const std::vector<SharedModuleInfo::ImportInfo>& imports = |
| SharedModuleInfo::GetImports(extension); |
| std::vector<SharedModuleInfo::ImportInfo>::const_iterator i; |
| for (i = imports.begin(); i != imports.end(); ++i) { |
| Version version_required(i->minimum_version); |
| const Extension* imported_module = |
| GetExtensionById(i->extension_id, true); |
| if (!imported_module) { |
| if (extension->from_webstore()) { |
| status = IMPORT_STATUS_UNSATISFIED; |
| missing_modules->push_back(*i); |
| } else { |
| return IMPORT_STATUS_UNRECOVERABLE; |
| } |
| } else if (!SharedModuleInfo::IsSharedModule(imported_module)) { |
| return IMPORT_STATUS_UNRECOVERABLE; |
| } else if (version_required.IsValid() && |
| imported_module->version()->CompareTo(version_required) < 0) { |
| if (imported_module->from_webstore()) { |
| outdated_modules->push_back(*i); |
| status = IMPORT_STATUS_UNSATISFIED; |
| } else { |
| return IMPORT_STATUS_UNRECOVERABLE; |
| } |
| } |
| } |
| } |
| return status; |
| } |
| |
| ExtensionService::ImportStatus ExtensionService::SatisfyImports( |
| const Extension* extension) { |
| std::list<SharedModuleInfo::ImportInfo> noinstalled; |
| std::list<SharedModuleInfo::ImportInfo> outdated; |
| ImportStatus status = CheckImports(extension, &noinstalled, &outdated); |
| if (status == IMPORT_STATUS_UNRECOVERABLE) |
| return status; |
| if (status == IMPORT_STATUS_UNSATISFIED) { |
| std::list<SharedModuleInfo::ImportInfo>::const_iterator iter; |
| for (iter = noinstalled.begin(); iter != noinstalled.end(); ++iter) { |
| pending_extension_manager()->AddFromExtensionImport( |
| iter->extension_id, |
| extension_urls::GetWebstoreUpdateUrl(), |
| IsSharedModule); |
| } |
| CheckForUpdatesSoon(); |
| } |
| return status; |
| } |
| |
| scoped_ptr<const ExtensionSet> |
| ExtensionService::GetDependentExtensions(const Extension* extension) { |
| scoped_ptr<ExtensionSet> dependents(new ExtensionSet()); |
| scoped_ptr<ExtensionSet> set_to_check(new ExtensionSet()); |
| if (SharedModuleInfo::IsSharedModule(extension)) { |
| set_to_check->InsertAll(disabled_extensions_); |
| set_to_check->InsertAll(delayed_installs_); |
| set_to_check->InsertAll(extensions_); |
| for (ExtensionSet::const_iterator iter = set_to_check->begin(); |
| iter != set_to_check->end(); ++iter) { |
| if (SharedModuleInfo::ImportsExtensionById(iter->get(), |
| extension->id())) { |
| dependents->Insert(*iter); |
| } |
| } |
| } |
| return dependents.PassAs<const ExtensionSet>(); |
| } |
| |
| void ExtensionService::PruneSharedModulesOnUninstall( |
| const Extension* extension) { |
| if (SharedModuleInfo::ImportsModules(extension)) { |
| const std::vector<SharedModuleInfo::ImportInfo>& imports = |
| SharedModuleInfo::GetImports(extension); |
| std::vector<SharedModuleInfo::ImportInfo>::const_iterator i; |
| for (i = imports.begin(); i != imports.end(); ++i) { |
| const Extension* imported_module = |
| GetExtensionById(i->extension_id, true); |
| if (imported_module && imported_module->from_webstore()) { |
| scoped_ptr<const ExtensionSet> dependents = |
| GetDependentExtensions(imported_module); |
| if (dependents->size() == 0) { |
| UninstallExtension(i->extension_id, true, NULL); |
| } |
| } |
| } |
| } |
| } |
| |
| void ExtensionService::OnExtensionInstalled( |
| const Extension* extension, |
| const syncer::StringOrdinal& page_ordinal, |
| bool has_requirement_errors, |
| extensions::Blacklist::BlacklistState blacklist_state, |
| bool wait_for_idle) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| const std::string& id = extension->id(); |
| bool initial_enable = ShouldEnableOnInstall(extension); |
| const extensions::PendingExtensionInfo* pending_extension_info = NULL; |
| if ((pending_extension_info = pending_extension_manager()->GetById(id))) { |
| if (!pending_extension_info->ShouldAllowInstall(extension)) { |
| pending_extension_manager()->Remove(id); |
| |
| LOG(WARNING) << "ShouldAllowInstall() returned false for " |
| << id << " of type " << extension->GetType() |
| << " and update URL " |
| << extensions::ManifestURL::GetUpdateURL(extension).spec() |
| << "; not installing"; |
| |
| // Delete the extension directory since we're not going to |
| // load it. |
| if (!GetFileTaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind(&extension_file_util::DeleteFile, |
| extension->path(), true))) { |
| NOTREACHED(); |
| } |
| return; |
| } |
| |
| pending_extension_manager()->Remove(id); |
| } else { |
| // We explicitly want to re-enable an uninstalled external |
| // extension; if we're here, that means the user is manually |
| // installing the extension. |
| if (IsExternalExtensionUninstalled(id)) { |
| initial_enable = true; |
| } |
| } |
| |
| // Unsupported requirements overrides the management policy. |
| if (has_requirement_errors) { |
| initial_enable = false; |
| extension_prefs_->AddDisableReason( |
| id, Extension::DISABLE_UNSUPPORTED_REQUIREMENT); |
| // If the extension was disabled because of unsupported requirements but |
| // now supports all requirements after an update and there are not other |
| // disable reasons, enable it. |
| } else if (extension_prefs_->GetDisableReasons(id) == |
| Extension::DISABLE_UNSUPPORTED_REQUIREMENT) { |
| initial_enable = true; |
| extension_prefs_->ClearDisableReasons(id); |
| } |
| |
| if (blacklist_state == extensions::Blacklist::BLACKLISTED_MALWARE) { |
| // Installation of a blacklisted extension can happen from sync, policy, |
| // etc, where to maintain consistency we need to install it, just never |
| // load it (see AddExtension). Usually it should be the job of callers to |
| // incercept blacklisted extension earlier (e.g. CrxInstaller, before even |
| // showing the install dialogue). |
| extension_prefs()->AcknowledgeBlacklistedExtension(id); |
| UMA_HISTOGRAM_ENUMERATION("ExtensionBlacklist.SilentInstall", |
| extension->location(), |
| Manifest::NUM_LOCATIONS); |
| } |
| |
| if (!GetInstalledExtension(extension->id())) { |
| UMA_HISTOGRAM_ENUMERATION("Extensions.InstallType", |
| extension->GetType(), 100); |
| UMA_HISTOGRAM_ENUMERATION("Extensions.InstallSource", |
| extension->location(), Manifest::NUM_LOCATIONS); |
| RecordPermissionMessagesHistogram( |
| extension, "Extensions.Permissions_Install"); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION("Extensions.UpdateType", |
| extension->GetType(), 100); |
| UMA_HISTOGRAM_ENUMERATION("Extensions.UpdateSource", |
| extension->location(), Manifest::NUM_LOCATIONS); |
| } |
| |
| // Certain extension locations are specific enough that we can |
| // auto-acknowledge any extension that came from one of them. |
| if (Manifest::IsPolicyLocation(extension->location())) |
| AcknowledgeExternalExtension(extension->id()); |
| const Extension::State initial_state = |
| initial_enable ? Extension::ENABLED : Extension::DISABLED; |
| const bool blacklisted_for_malware = |
| blacklist_state == extensions::Blacklist::BLACKLISTED_MALWARE; |
| if (ShouldDelayExtensionUpdate(id, wait_for_idle)) { |
| extension_prefs_->SetDelayedInstallInfo( |
| extension, |
| initial_state, |
| blacklisted_for_malware, |
| extensions::ExtensionPrefs::DELAY_REASON_WAIT_FOR_IDLE, |
| page_ordinal); |
| |
| // Transfer ownership of |extension|. |
| delayed_installs_.Insert(extension); |
| |
| // Notify observers that app update is available. |
| FOR_EACH_OBSERVER(extensions::UpdateObserver, update_observers_, |
| OnAppUpdateAvailable(extension)); |
| return; |
| } |
| |
| ImportStatus status = SatisfyImports(extension); |
| if (installs_delayed_for_gc()) { |
| extension_prefs_->SetDelayedInstallInfo( |
| extension, |
| initial_state, |
| blacklisted_for_malware, |
| extensions::ExtensionPrefs::DELAY_REASON_GC, |
| page_ordinal); |
| delayed_installs_.Insert(extension); |
| } else if (status != IMPORT_STATUS_OK) { |
| if (status == IMPORT_STATUS_UNSATISFIED) { |
| extension_prefs_->SetDelayedInstallInfo( |
| extension, |
| initial_state, |
| blacklisted_for_malware, |
| extensions::ExtensionPrefs::DELAY_REASON_WAIT_FOR_IMPORTS, |
| page_ordinal); |
| delayed_installs_.Insert(extension); |
| } |
| } else { |
| AddNewOrUpdatedExtension(extension, |
| initial_state, |
| blacklist_state, |
| page_ordinal); |
| } |
| } |
| |
| void ExtensionService::AddNewOrUpdatedExtension( |
| const Extension* extension, |
| Extension::State initial_state, |
| extensions::Blacklist::BlacklistState blacklist_state, |
| const syncer::StringOrdinal& page_ordinal) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| const bool blacklisted_for_malware = |
| blacklist_state == extensions::Blacklist::BLACKLISTED_MALWARE; |
| extension_prefs_->OnExtensionInstalled(extension, |
| initial_state, |
| blacklisted_for_malware, |
| page_ordinal); |
| delayed_installs_.Remove(extension->id()); |
| if (extensions::ManifestURL::UpdatesFromGallery(extension)) { |
| extensions::ExtensionSystem::Get(profile_)->install_verifier()->Add( |
| extension->id(), InstallVerifier::AddResultCallback()); |
| } |
| FinishInstallation(extension); |
| } |
| |
| void ExtensionService::MaybeFinishDelayedInstallation( |
| const std::string& extension_id) { |
| // Check if the extension already got installed. |
| if (!delayed_installs_.Contains(extension_id)) |
| return; |
| extensions::ExtensionPrefs::DelayReason reason = |
| extension_prefs_->GetDelayedInstallReason(extension_id); |
| |
| // Check if the extension is idle. DELAY_REASON_NONE is used for older |
| // preferences files that will not have set this field but it was previously |
| // only used for idle updates. |
| if ((reason == extensions::ExtensionPrefs::DELAY_REASON_WAIT_FOR_IDLE || |
| reason == extensions::ExtensionPrefs::DELAY_REASON_NONE) && |
| is_ready() && !IsExtensionIdle(extension_id)) |
| return; |
| |
| const Extension* extension = delayed_installs_.GetByID(extension_id); |
| if (reason == extensions::ExtensionPrefs::DELAY_REASON_WAIT_FOR_IMPORTS) { |
| ImportStatus status = SatisfyImports(extension); |
| if (status != IMPORT_STATUS_OK) { |
| if (status == IMPORT_STATUS_UNRECOVERABLE) { |
| delayed_installs_.Remove(extension_id); |
| // Make sure no version of the extension is actually installed, (i.e., |
| // that this delayed install was not an update). |
| CHECK(!extension_prefs_->GetInstalledExtensionInfo(extension_id).get()); |
| extension_prefs_->DeleteExtensionPrefs(extension_id); |
| } |
| return; |
| } |
| } |
| |
| FinishDelayedInstallation(extension_id); |
| } |
| |
| void ExtensionService::FinishDelayedInstallation( |
| const std::string& extension_id) { |
| scoped_refptr<const Extension> extension( |
| GetPendingExtensionUpdate(extension_id)); |
| CHECK(extension.get()); |
| delayed_installs_.Remove(extension_id); |
| |
| if (!extension_prefs_->FinishDelayedInstallInfo(extension_id)) |
| NOTREACHED(); |
| |
| FinishInstallation(extension.get()); |
| } |
| |
| void ExtensionService::FinishInstallation(const Extension* extension) { |
| const extensions::Extension* existing_extension = |
| GetInstalledExtension(extension->id()); |
| bool is_update = false; |
| std::string old_name; |
| if (existing_extension) { |
| is_update = true; |
| old_name = existing_extension->name(); |
| } |
| extensions::InstalledExtensionInfo details(extension, is_update, old_name); |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_INSTALLED, |
| content::Source<Profile>(profile_), |
| content::Details<const extensions::InstalledExtensionInfo>(&details)); |
| |
| bool unacknowledged_external = IsUnacknowledgedExternalExtension(extension); |
| |
| // Unpacked extensions default to allowing file access, but if that has been |
| // overridden, don't reset the value. |
| if (Manifest::ShouldAlwaysAllowFileAccess(extension->location()) && |
| !extension_prefs_->HasAllowFileAccessSetting(extension->id())) { |
| extension_prefs_->SetAllowFileAccess(extension->id(), true); |
| } |
| |
| AddExtension(extension); |
| |
| // If this is a new external extension that was disabled, alert the user |
| // so he can reenable it. We do this last so that it has already been |
| // added to our list of extensions. |
| if (unacknowledged_external && !is_update) { |
| UpdateExternalExtensionAlert(); |
| UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent", |
| EXTERNAL_EXTENSION_INSTALLED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| if (extensions::ManifestURL::UpdatesFromGallery(extension)) { |
| UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore", |
| EXTERNAL_EXTENSION_INSTALLED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore", |
| EXTERNAL_EXTENSION_INSTALLED, |
| EXTERNAL_EXTENSION_BUCKET_BOUNDARY); |
| } |
| } |
| |
| // Check extensions that may have been delayed only because this shared module |
| // was not available. |
| if (SharedModuleInfo::IsSharedModule(extension)) { |
| MaybeFinishDelayedInstallations(); |
| } |
| } |
| |
| const Extension* ExtensionService::GetPendingExtensionUpdate( |
| const std::string& id) const { |
| return delayed_installs_.GetByID(id); |
| } |
| |
| void ExtensionService::TrackTerminatedExtension(const Extension* extension) { |
| if (!terminated_extensions_.Contains(extension->id())) |
| terminated_extensions_.Insert(make_scoped_refptr(extension)); |
| |
| UnloadExtension(extension->id(), UnloadedExtensionInfo::REASON_TERMINATE); |
| } |
| |
| void ExtensionService::UntrackTerminatedExtension(const std::string& id) { |
| std::string lowercase_id = StringToLowerASCII(id); |
| const Extension* extension = terminated_extensions_.GetByID(lowercase_id); |
| terminated_extensions_.Remove(lowercase_id); |
| if (extension) { |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_REMOVED, |
| content::Source<Profile>(profile_), |
| content::Details<const Extension>(extension)); |
| } |
| } |
| |
| const Extension* ExtensionService::GetTerminatedExtension( |
| const std::string& id) const { |
| return GetExtensionById(id, INCLUDE_TERMINATED); |
| } |
| |
| const Extension* ExtensionService::GetInstalledExtension( |
| const std::string& id) const { |
| int include_mask = INCLUDE_ENABLED | |
| INCLUDE_DISABLED | |
| INCLUDE_TERMINATED | |
| INCLUDE_BLACKLISTED; |
| return GetExtensionById(id, include_mask); |
| } |
| |
| bool ExtensionService::ExtensionBindingsAllowed(const GURL& url) { |
| // Allow bindings for all packaged extensions and component hosted apps. |
| const Extension* extension = extensions_.GetExtensionOrAppByURL(url); |
| return extension && (!extension->is_hosted_app() || |
| extension->location() == Manifest::COMPONENT); |
| } |
| |
| bool ExtensionService::ShouldBlockUrlInBrowserTab(GURL* url) { |
| const Extension* extension = extensions_.GetExtensionOrAppByURL(*url); |
| if (extension && extension->is_platform_app()) { |
| *url = GURL(chrome::kExtensionInvalidRequestURL); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ExtensionService::OnExternalExtensionFileFound( |
| const std::string& id, |
| const Version* version, |
| const base::FilePath& path, |
| Manifest::Location location, |
| int creation_flags, |
| bool mark_acknowledged) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| CHECK(Extension::IdIsValid(id)); |
| if (extension_prefs_->IsExternalExtensionUninstalled(id)) |
| return false; |
| |
| // 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. |
| const Extension* existing = GetExtensionById(id, true); |
| |
| if (existing) { |
| // The default apps will have the location set as INTERNAL. Since older |
| // default apps are installed as EXTERNAL, we override them. However, if the |
| // app is already installed as internal, then do the version check. |
| // TODO(grv) : Remove after Q1-2013. |
| bool is_default_apps_migration = |
| (location == Manifest::INTERNAL && |
| Manifest::IsExternalLocation(existing->location())); |
| |
| if (!is_default_apps_migration) { |
| DCHECK(version); |
| |
| switch (existing->version()->CompareTo(*version)) { |
| case -1: // existing version is older, we should upgrade |
| break; |
| case 0: // existing version is same, do nothing |
| return false; |
| 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->GetString() |
| << ". Keeping current version."; |
| return false; |
| } |
| } |
| } |
| |
| // If the extension is already pending, don't start an install. |
| if (!pending_extension_manager()->AddFromExternalFile( |
| id, location, *version, creation_flags, mark_acknowledged)) { |
| return false; |
| } |
| |
| // no client (silent install) |
| scoped_refptr<CrxInstaller> installer(CrxInstaller::CreateSilent(this)); |
| installer->set_install_source(location); |
| installer->set_expected_id(id); |
| installer->set_expected_version(*version); |
| installer->set_install_cause(extension_misc::INSTALL_CAUSE_EXTERNAL_FILE); |
| installer->set_creation_flags(creation_flags); |
| #if defined(OS_CHROMEOS) |
| extensions::InstallLimiter::Get(profile_)->Add(installer, path); |
| #else |
| installer->InstallCrx(path); |
| #endif |
| |
| // Depending on the source, a new external extension might not need a user |
| // notification on installation. For such extensions, mark them acknowledged |
| // now to suppress the notification. |
| if (mark_acknowledged) |
| AcknowledgeExternalExtension(id); |
| |
| return true; |
| } |
| |
| scoped_ptr<DictionaryValue> ExtensionService::GetExtensionInfo( |
| const std::string& extension_id) const { |
| scoped_ptr<DictionaryValue> dictionary(new DictionaryValue); |
| const extensions::Extension* extension = extensions_.GetByID(extension_id); |
| if (extension) { |
| GURL icon = extensions::ExtensionIconSource::GetIconURL( |
| extension, extension_misc::EXTENSION_ICON_SMALLISH, |
| ExtensionIconSet::MATCH_BIGGER, false, NULL); |
| dictionary->SetString("id", extension_id); |
| dictionary->SetString("name", extension->name()); |
| dictionary->SetString("icon", icon.spec()); |
| } |
| return dictionary.Pass(); |
| } |
| |
| void ExtensionService::ReportExtensionLoadError( |
| const base::FilePath& extension_path, |
| const std::string &error, |
| bool be_noisy) { |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_LOAD_ERROR, |
| content::Source<Profile>(profile_), |
| content::Details<const std::string>(&error)); |
| |
| std::string path_str = UTF16ToUTF8(extension_path.LossyDisplayName()); |
| base::string16 message = UTF8ToUTF16(base::StringPrintf( |
| "Could not load extension from '%s'. %s", |
| path_str.c_str(), error.c_str())); |
| ExtensionErrorReporter::GetInstance()->ReportError(message, be_noisy); |
| } |
| |
| void ExtensionService::DidCreateRenderViewForBackgroundPage( |
| extensions::ExtensionHost* host) { |
| OrphanedDevTools::iterator iter = |
| orphaned_dev_tools_.find(host->extension_id()); |
| if (iter == orphaned_dev_tools_.end()) |
| return; |
| |
| iter->second->ConnectRenderViewHost(host->render_view_host()); |
| orphaned_dev_tools_.erase(iter); |
| } |
| |
| void ExtensionService::Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| switch (type) { |
| case chrome::NOTIFICATION_APP_TERMINATING: |
| // Shutdown has started. Don't start any more extension installs. |
| // (We cannot use ExtensionService::Shutdown() for this because it |
| // happens too late in browser teardown.) |
| browser_terminating_ = true; |
| break; |
| case chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED: { |
| if (profile_ != |
| content::Source<Profile>(source).ptr()->GetOriginalProfile()) { |
| break; |
| } |
| |
| extensions::ExtensionHost* host = |
| content::Details<extensions::ExtensionHost>(details).ptr(); |
| |
| // Mark the extension as terminated and Unload it. We want it to |
| // be in a consistent state: either fully working or not loaded |
| // at all, but never half-crashed. We do it in a PostTask so |
| // that other handlers of this notification will still have |
| // access to the Extension and ExtensionHost. |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind( |
| &ExtensionService::TrackTerminatedExtension, |
| AsWeakPtr(), |
| host->extension())); |
| break; |
| } |
| case content::NOTIFICATION_RENDERER_PROCESS_CREATED: { |
| content::RenderProcessHost* process = |
| content::Source<content::RenderProcessHost>(source).ptr(); |
| Profile* host_profile = |
| Profile::FromBrowserContext(process->GetBrowserContext()); |
| if (!profile_->IsSameProfile(host_profile->GetOriginalProfile())) |
| break; |
| |
| // Extensions need to know the channel for API restrictions. |
| process->Send(new ExtensionMsg_SetChannel( |
| extensions::GetCurrentChannel())); |
| |
| // Platform apps need to know the system font. |
| scoped_ptr<base::DictionaryValue> fonts(new base::DictionaryValue); |
| webui::SetFontAndTextDirection(fonts.get()); |
| std::string font_family, font_size; |
| fonts->GetString("fontfamily", &font_family); |
| fonts->GetString("fontsize", &font_size); |
| process->Send(new ExtensionMsg_SetSystemFont( |
| font_family, font_size)); |
| |
| // Valid extension function names, used to setup bindings in renderer. |
| std::vector<std::string> function_names; |
| ExtensionFunctionDispatcher::GetAllFunctionNames(&function_names); |
| process->Send(new ExtensionMsg_SetFunctionNames(function_names)); |
| |
| // Scripting whitelist. This is modified by tests and must be communicated |
| // to renderers. |
| process->Send(new ExtensionMsg_SetScriptingWhitelist( |
| extensions::ExtensionsClient::Get()->GetScriptingWhitelist())); |
| |
| // Loaded extensions. |
| std::vector<ExtensionMsg_Loaded_Params> loaded_extensions; |
| for (ExtensionSet::const_iterator iter = extensions_.begin(); |
| iter != extensions_.end(); ++iter) { |
| // Renderers don't need to know about themes. |
| if (!(*iter)->is_theme()) |
| loaded_extensions.push_back(ExtensionMsg_Loaded_Params(iter->get())); |
| } |
| process->Send(new ExtensionMsg_Loaded(loaded_extensions)); |
| break; |
| } |
| case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: { |
| content::RenderProcessHost* process = |
| content::Source<content::RenderProcessHost>(source).ptr(); |
| Profile* host_profile = |
| Profile::FromBrowserContext(process->GetBrowserContext()); |
| if (!profile_->IsSameProfile(host_profile->GetOriginalProfile())) |
| break; |
| |
| if (process_map_.Contains(process->GetID())) { |
| // An extension process was terminated, this might have resulted in an |
| // app or extension becoming idle. |
| std::set<std::string> extension_ids = |
| process_map_.GetExtensionsInProcess(process->GetID()); |
| for (std::set<std::string>::const_iterator it = extension_ids.begin(); |
| it != extension_ids.end(); ++it) { |
| if (delayed_installs_.Contains(*it)) { |
| base::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&ExtensionService::MaybeFinishDelayedInstallation, |
| AsWeakPtr(), *it), |
| base::TimeDelta::FromSeconds(kUpdateIdleDelay)); |
| } |
| } |
| } |
| |
| process_map_.RemoveAllFromProcess(process->GetID()); |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&extensions::InfoMap::UnregisterAllExtensionsInProcess, |
| system_->info_map(), |
| process->GetID())); |
| break; |
| } |
| case chrome::NOTIFICATION_UPGRADE_RECOMMENDED: { |
| // Notify observers that chrome update is available. |
| FOR_EACH_OBSERVER(extensions::UpdateObserver, update_observers_, |
| OnChromeUpdateAvailable()); |
| break; |
| } |
| |
| default: |
| NOTREACHED() << "Unexpected notification type."; |
| } |
| } |
| |
| void ExtensionService::OnExtensionInstallPrefChanged() { |
| IdentifyAlertableExtensions(); |
| CheckManagementPolicy(); |
| } |
| |
| bool ExtensionService::HasApps() const { |
| return !GetAppIds().empty(); |
| } |
| |
| ExtensionIdSet ExtensionService::GetAppIds() const { |
| ExtensionIdSet result; |
| for (ExtensionSet::const_iterator it = extensions_.begin(); |
| it != extensions_.end(); ++it) { |
| if ((*it)->is_app() && (*it)->location() != Manifest::COMPONENT) |
| result.insert((*it)->id()); |
| } |
| |
| return result; |
| } |
| |
| bool ExtensionService::IsBackgroundPageReady(const Extension* extension) const { |
| if (!extensions::BackgroundInfo::HasPersistentBackgroundPage(extension)) |
| return true; |
| ExtensionRuntimeDataMap::const_iterator it = |
| extension_runtime_data_.find(extension->id()); |
| return it == extension_runtime_data_.end() ? false : |
| it->second.background_page_ready; |
| } |
| |
| void ExtensionService::SetBackgroundPageReady(const Extension* extension) { |
| DCHECK(extensions::BackgroundInfo::HasBackgroundPage(extension)); |
| extension_runtime_data_[extension->id()].background_page_ready = true; |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY, |
| content::Source<const Extension>(extension), |
| content::NotificationService::NoDetails()); |
| } |
| |
| bool ExtensionService::IsBeingUpgraded(const Extension* extension) const { |
| ExtensionRuntimeDataMap::const_iterator it = |
| extension_runtime_data_.find(extension->id()); |
| return it == extension_runtime_data_.end() ? false : |
| it->second.being_upgraded; |
| } |
| |
| void ExtensionService::SetBeingUpgraded(const Extension* extension, |
| bool value) { |
| extension_runtime_data_[extension->id()].being_upgraded = value; |
| } |
| |
| bool ExtensionService::IsBeingReloaded( |
| const std::string& extension_id) const { |
| return ContainsKey(extensions_being_reloaded_, extension_id); |
| } |
| |
| void ExtensionService::SetBeingReloaded(const std::string& extension_id, |
| bool isBeingReloaded) { |
| if (isBeingReloaded) |
| extensions_being_reloaded_.insert(extension_id); |
| else |
| extensions_being_reloaded_.erase(extension_id); |
| } |
| |
| bool ExtensionService::HasUsedWebRequest(const Extension* extension) const { |
| ExtensionRuntimeDataMap::const_iterator it = |
| extension_runtime_data_.find(extension->id()); |
| return it == extension_runtime_data_.end() ? false : |
| it->second.has_used_webrequest; |
| } |
| |
| void ExtensionService::SetHasUsedWebRequest(const Extension* extension, |
| bool value) { |
| extension_runtime_data_[extension->id()].has_used_webrequest = value; |
| } |
| |
| bool ExtensionService::ShouldEnableOnInstall(const Extension* extension) { |
| // Extensions installed by policy can't be disabled. So even if a previous |
| // installation disabled the extension, make sure it is now enabled. |
| if (system_->management_policy()->MustRemainEnabled(extension, NULL)) |
| return true; |
| |
| if (extension_prefs_->IsExtensionDisabled(extension->id())) |
| return false; |
| |
| if (FeatureSwitch::prompt_for_external_extensions()->IsEnabled()) { |
| // External extensions are initially disabled. We prompt the user before |
| // enabling them. Hosted apps are excepted because they are not dangerous |
| // (they need to be launched by the user anyway). |
| if (extension->GetType() != Manifest::TYPE_HOSTED_APP && |
| Manifest::IsExternalLocation(extension->location()) && |
| !extension_prefs_->IsExternalExtensionAcknowledged(extension->id())) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ExtensionService::IsExtensionIdle(const std::string& extension_id) const { |
| extensions::ProcessManager* process_manager = system_->process_manager(); |
| DCHECK(process_manager); |
| extensions::ExtensionHost* host = |
| process_manager->GetBackgroundHostForExtension(extension_id); |
| if (host) |
| return false; |
| |
| content::SiteInstance* site_instance = process_manager->GetSiteInstanceForURL( |
| Extension::GetBaseURLFromExtensionId(extension_id)); |
| if (site_instance && site_instance->HasProcess()) { |
| return false; |
| } |
| |
| return process_manager->GetRenderViewHostsForExtension(extension_id).empty(); |
| } |
| |
| bool ExtensionService::ShouldDelayExtensionUpdate( |
| const std::string& extension_id, |
| bool wait_for_idle) const { |
| const char kOnUpdateAvailableEvent[] = "runtime.onUpdateAvailable"; |
| |
| // If delayed updates are globally disabled, or just for this extension, |
| // don't delay. |
| if (!install_updates_when_idle_ || !wait_for_idle) |
| return false; |
| |
| const Extension* old = GetInstalledExtension(extension_id); |
| // If there is no old extension, this is not an update, so don't delay. |
| if (!old) |
| return false; |
| |
| if (extensions::BackgroundInfo::HasPersistentBackgroundPage(old)) { |
| // Delay installation if the extension listens for the onUpdateAvailable |
| // event. |
| return system_->event_router()->ExtensionHasEventListener( |
| extension_id, kOnUpdateAvailableEvent); |
| } else { |
| // Delay installation if the extension is not idle. |
| return !IsExtensionIdle(extension_id); |
| } |
| } |
| |
| void ExtensionService::GarbageCollectIsolatedStorage() { |
| scoped_ptr<base::hash_set<base::FilePath> > active_paths( |
| new base::hash_set<base::FilePath>()); |
| for (ExtensionSet::const_iterator it = extensions_.begin(); |
| it != extensions_.end(); ++it) { |
| if (extensions::AppIsolationInfo::HasIsolatedStorage(it->get())) { |
| active_paths->insert(BrowserContext::GetStoragePartitionForSite( |
| profile_, GetSiteForExtensionId((*it)->id()))->GetPath()); |
| } |
| } |
| |
| DCHECK(!installs_delayed_for_gc()); |
| set_installs_delayed_for_gc(true); |
| BrowserContext::GarbageCollectStoragePartitions( |
| profile_, active_paths.Pass(), |
| base::Bind(&ExtensionService::OnGarbageCollectIsolatedStorageFinished, |
| AsWeakPtr())); |
| } |
| |
| void ExtensionService::OnGarbageCollectIsolatedStorageFinished() { |
| set_installs_delayed_for_gc(false); |
| MaybeFinishDelayedInstallations(); |
| } |
| |
| void ExtensionService::MaybeFinishDelayedInstallations() { |
| std::vector<std::string> to_be_installed; |
| for (ExtensionSet::const_iterator it = delayed_installs_.begin(); |
| it != delayed_installs_.end(); |
| ++it) { |
| to_be_installed.push_back((*it)->id()); |
| } |
| for (std::vector<std::string>::const_iterator it = to_be_installed.begin(); |
| it != to_be_installed.end(); |
| ++it) { |
| MaybeFinishDelayedInstallation(*it); |
| } |
| } |
| |
| void ExtensionService::OnNeedsToGarbageCollectIsolatedStorage() { |
| extension_prefs_->SetNeedsStorageGarbageCollection(true); |
| } |
| |
| void ExtensionService::OnBlacklistUpdated() { |
| blacklist_->GetMalwareIDs( |
| GenerateInstalledExtensionsSet()->GetIDs(), |
| base::Bind(&ExtensionService::ManageBlacklist, AsWeakPtr())); |
| } |
| |
| void ExtensionService::ManageBlacklist(const std::set<std::string>& updated) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| std::set<std::string> before = blacklisted_extensions_.GetIDs(); |
| std::set<std::string> no_longer_blacklisted = |
| base::STLSetDifference<std::set<std::string> >(before, updated); |
| std::set<std::string> not_yet_blacklisted = |
| base::STLSetDifference<std::set<std::string> >(updated, before); |
| |
| for (std::set<std::string>::iterator it = no_longer_blacklisted.begin(); |
| it != no_longer_blacklisted.end(); ++it) { |
| scoped_refptr<const Extension> extension = |
| blacklisted_extensions_.GetByID(*it); |
| if (!extension.get()) { |
| NOTREACHED() << "Extension " << *it << " no longer blacklisted, " |
| << "but it was never blacklisted."; |
| continue; |
| } |
| blacklisted_extensions_.Remove(*it); |
| extension_prefs_->SetExtensionBlacklisted(extension->id(), false); |
| AddExtension(extension.get()); |
| UMA_HISTOGRAM_ENUMERATION("ExtensionBlacklist.UnblacklistInstalled", |
| extension->location(), |
| Manifest::NUM_LOCATIONS); |
| } |
| |
| for (std::set<std::string>::iterator it = not_yet_blacklisted.begin(); |
| it != not_yet_blacklisted.end(); ++it) { |
| scoped_refptr<const Extension> extension = GetInstalledExtension(*it); |
| if (!extension.get()) { |
| NOTREACHED() << "Extension " << *it << " needs to be " |
| << "blacklisted, but it's not installed."; |
| continue; |
| } |
| blacklisted_extensions_.Insert(extension); |
| extension_prefs_->SetExtensionBlacklisted(extension->id(), true); |
| UnloadExtension(*it, UnloadedExtensionInfo::REASON_BLACKLIST); |
| UMA_HISTOGRAM_ENUMERATION("ExtensionBlacklist.BlacklistInstalled", |
| extension->location(), Manifest::NUM_LOCATIONS); |
| } |
| |
| IdentifyAlertableExtensions(); |
| } |
| |
| void ExtensionService::AddUpdateObserver(extensions::UpdateObserver* observer) { |
| update_observers_.AddObserver(observer); |
| } |
| |
| void ExtensionService::RemoveUpdateObserver( |
| extensions::UpdateObserver* observer) { |
| update_observers_.RemoveObserver(observer); |
| } |