blob: ac0f43af2f9c6d97189142cd11eb45929a61dedf [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/extension_registrar.h"
#include "base/check_is_test.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/debug/alias.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/stl_util.h"
#include "build/chromeos_buildflags.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "extensions/browser/blocklist_extension_prefs.h"
#include "extensions/browser/delayed_install_manager.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registrar_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/lazy_context_id.h"
#include "extensions/browser/lazy_context_task_queue.h"
#include "extensions/browser/management_policy.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/renderer_startup_helper.h"
#include "extensions/browser/service_worker/service_worker_task_queue.h"
#include "extensions/browser/task_queue_util.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/manifest_handlers/shared_module_info.h"
#include "extensions/common/permissions/permissions_data.h"
#include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
using content::DevToolsAgentHost;
using extensions::mojom::ManifestLocation;
namespace extensions {
namespace {
BASE_FEATURE(kExtensionUpdatesImmediatelyUnregisterWorker,
base::FEATURE_ENABLED_BY_DEFAULT);
bool g_disable_lazy_context_spinup_for_test = false;
} // namespace
ExtensionRegistrar::ExtensionRegistrar(content::BrowserContext* browser_context)
: browser_context_(browser_context),
extension_system_(ExtensionSystem::Get(browser_context)),
extension_prefs_(ExtensionPrefs::Get(browser_context)),
registry_(ExtensionRegistry::Get(browser_context)),
renderer_helper_(
RendererStartupHelperFactory::GetForBrowserContext(browser_context)) {
// ExtensionRegistrar is created by ExtensionSystem via ExtensionService, and
// ChromeExtensionSystemFactory depends on ProcessManager, so this should be
// safe.
auto* process_manager = ProcessManager::Get(browser_context_);
DCHECK(process_manager);
process_manager_observation_.Observe(process_manager);
}
ExtensionRegistrar::~ExtensionRegistrar() = default;
// static
ExtensionRegistrar* ExtensionRegistrar::Get(content::BrowserContext* context) {
return ExtensionRegistrarFactory::GetForBrowserContext(context);
}
void ExtensionRegistrar::Init(
Delegate* delegate,
bool extensions_enabled,
const base::CommandLine* command_line,
const base::FilePath& install_directory,
const base::FilePath& unpacked_install_directory) {
delegate_ = delegate;
// Figure out if extension installation should be enabled.
if (ExtensionsBrowserClient::Get()->AreExtensionsDisabled(*command_line,
browser_context_)) {
extensions_enabled = false;
}
extensions_enabled_ = extensions_enabled;
install_directory_ = install_directory;
unpacked_install_directory_ = unpacked_install_directory;
// TODO(https://crbug.com/410635478): We can't put this in ctor because
// there's a KeyedService cycle between DelayedInstallManager and
// ExtensionRegistrar.
delayed_install_manager_ = DelayedInstallManager::Get(browser_context_);
}
bool ExtensionRegistrar::IsInitialized() const {
// The registrar is initialized if a delegate has been assigned.
return !!delegate_;
}
base::WeakPtr<ExtensionRegistrar> ExtensionRegistrar::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void ExtensionRegistrar::Shutdown() {
// Setting to `nullptr`, because this raw pointer may become dangling once
// the `ExtensionSystem` keyed service is destroyed.
extension_system_ = nullptr;
delegate_ = nullptr;
delayed_install_manager_ = nullptr;
}
void ExtensionRegistrar::AddExtension(
scoped_refptr<const Extension> extension) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!Manifest::IsValidLocation(extension->location())) {
// TODO(devlin): We should *never* add an extension with an invalid
// location, but some bugs (e.g. crbug.com/692069) seem to indicate we do.
// Track down the cases when this can happen, and remove this
// DumpWithoutCrashing() (possibly replacing it with a CHECK).
DEBUG_ALIAS_FOR_CSTR(extension_id_copy, extension->id().c_str(), 33);
ManifestLocation location = extension->location();
int creation_flags = extension->creation_flags();
Manifest::Type type = extension->manifest()->type();
base::debug::Alias(&location);
base::debug::Alias(&creation_flags);
base::debug::Alias(&type);
NOTREACHED();
}
if (!CanAddExtension(extension.get())) {
return;
}
bool is_extension_loaded = false;
const Extension* old = registry_->GetInstalledExtension(extension->id());
if (old) {
is_extension_loaded = true;
int version_compare_result = extension->version().CompareTo(old->version());
// Other than for unpacked extensions, we should not be downgrading.
if (!Manifest::IsUnpackedLocation(extension->location()) &&
version_compare_result < 0) {
// TODO(crbug.com/41369768): It would be awfully nice to CHECK this,
// but that's caused problems. There are apparently times when this
// happens that we aren't accounting for. We should track those down and
// fix them, but it can be tricky.
DUMP_WILL_BE_NOTREACHED()
<< "Attempted to downgrade extension." << "\nID: " << extension->id()
<< "\nOld Version: " << old->version()
<< "\nNew Version: " << extension->version()
<< "\nLocation: " << extension->location();
return;
}
}
// If the extension was disabled for a reload, we will enable it.
bool was_reloading = reloading_extensions_.erase(extension->id()) > 0;
// 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());
// Notify the delegate we will add the extension.
CHECK(delegate_);
delegate_->PreAddExtension(extension.get(), old);
if (was_reloading) {
failed_to_reload_unpacked_extensions_.erase(extension->path());
ReplaceReloadedExtension(extension);
} else {
if (is_extension_loaded) {
// To upgrade an extension in place, remove the old one and then activate
// the new one. ReloadExtension disables the extension, which is
// sufficient.
RemoveExtension(extension->id(), UnloadedExtensionReason::UPDATE);
UnregisterServiceWorkerWithRootScope(extension.get());
}
AddNewExtension(extension);
}
if (registry_->disabled_extensions().Contains(extension->id())) {
// Show the extension disabled error if a permissions increase or a remote
// installation is the reason it was disabled, and no other reasons exist.
DisableReasonSet reasons =
extension_prefs_->GetDisableReasons(extension->id());
const DisableReasonSet error_reasons = {
disable_reason::DISABLE_PERMISSIONS_INCREASE,
disable_reason::DISABLE_REMOTE_INSTALL};
DisableReasonSet other_reasons =
base::STLSetDifference<DisableReasonSet>(reasons, error_reasons);
if (!reasons.empty() && other_reasons.empty()) {
delegate_->ShowExtensionDisabledError(
extension.get(),
extension_prefs_->HasDisableReason(
extension->id(), disable_reason::DISABLE_REMOTE_INSTALL));
}
}
}
void ExtensionRegistrar::AddNewExtension(
scoped_refptr<const Extension> extension) {
if (blocklist_prefs::IsExtensionBlocklisted(extension->id(),
extension_prefs_)) {
DCHECK(!Manifest::IsComponentLocation(extension->location()));
// Only prefs is checked for the blocklist. We rely on callers to check the
// blocklist before calling into here, e.g. CrxInstaller checks before
// installation then threads through the install and pending install flow
// of this class, and ExtensionService checks when loading installed
// extensions.
registry_->AddBlocklisted(extension);
} else if (ShouldBlockExtension(extension.get())) {
DCHECK(!Manifest::IsComponentLocation(extension->location()));
registry_->AddBlocked(extension);
} else if (extension_prefs_->IsExtensionDisabled(extension->id())) {
registry_->AddDisabled(extension);
} else { // Extension should be enabled.
registry_->AddEnabled(extension);
ActivateExtension(extension.get(), true);
}
}
void ExtensionRegistrar::AddNewOrUpdatedExtension(
const Extension* extension,
const base::flat_set<int>& disable_reasons,
int install_flags,
const syncer::StringOrdinal& page_ordinal,
const std::string& install_parameter,
base::Value::Dict ruleset_install_prefs) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
extension_prefs_->OnExtensionInstalled(
extension, disable_reasons, page_ordinal, install_flags,
install_parameter, std::move(ruleset_install_prefs));
delayed_install_manager_->Remove(extension->id());
delegate_->OnAddNewOrUpdatedExtension(extension);
FinishInstallation(extension);
}
void ExtensionRegistrar::OnExtensionInstalled(
const Extension* extension,
const syncer::StringOrdinal& page_ordinal,
int install_flags,
base::Value::Dict ruleset_install_prefs) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
delegate_->OnExtensionInstalled(extension, page_ordinal, install_flags,
std::move(ruleset_install_prefs));
}
void ExtensionRegistrar::RemoveExtension(const ExtensionId& extension_id,
UnloadedExtensionReason reason) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
int include_mask = ExtensionRegistry::ENABLED | ExtensionRegistry::DISABLED |
ExtensionRegistry::TERMINATED;
scoped_refptr<const Extension> extension(
registry_->GetExtensionById(extension_id, include_mask));
// If the extension is blocked/blocklisted, no need to notify again.
if (!extension) {
return;
}
if (registry_->terminated_extensions().Contains(extension_id)) {
// The extension was already deactivated from the call to
// TerminateExtension(), which also should have added it to
// unloaded_extension_paths_ if necessary.
registry_->RemoveTerminated(extension->id());
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();
// Stop tracking whether the extension was meant to be enabled after a reload.
reloading_extensions_.erase(extension->id());
if (registry_->enabled_extensions().Contains(extension_id)) {
// Put the pending removal extension in disabled set because underlying
// code of `DeactivateExtension` needs to access it.
// See https://crbug.com/443038597
registry_->AddDisabled(extension);
registry_->RemoveEnabled(extension_id);
DeactivateExtension(extension.get(), reason);
registry_->RemoveDisabled(extension_id);
} else {
// The extension was already deactivated from the call to
// DisableExtension().
bool removed = registry_->RemoveDisabled(extension->id());
DCHECK(removed);
}
}
void ExtensionRegistrar::EnableExtension(const ExtensionId& extension_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// If the extension is currently reloading, it will be enabled once the reload
// is complete.
if (reloading_extensions_.count(extension_id) > 0)
return;
// First, check that the extension can be enabled.
if (IsExtensionEnabled(extension_id) ||
blocklist_prefs::IsExtensionBlocklisted(extension_id, extension_prefs_) ||
registry_->blocked_extensions().Contains(extension_id)) {
return;
}
const Extension* extension =
registry_->disabled_extensions().GetByID(extension_id);
if (extension && !delegate_->CanEnableExtension(extension))
return;
// Now that we know the extension can be enabled, update the prefs.
extension_prefs_->ClearDisableReasons(extension_id);
// This can happen if sync enables an extension that is not installed yet.
if (!extension)
return;
// Actually enable the extension.
registry_->AddEnabled(extension);
registry_->RemoveDisabled(extension->id());
ActivateExtension(extension, false);
}
void ExtensionRegistrar::DisableExtension(
const ExtensionId& extension_id,
const DisableReasonSet& disable_reasons) {
auto passkey = ExtensionPrefs::DisableReasonRawManipulationPasskey();
DisableExtensionWithRawReasons(passkey, extension_id,
DisableReasonSetToIntegerSet(disable_reasons));
}
void ExtensionRegistrar::DisableExtensionWithRawReasons(
ExtensionPrefs::DisableReasonRawManipulationPasskey,
const ExtensionId& extension_id,
base::flat_set<int> disable_reasons) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!disable_reasons.empty());
scoped_refptr<const Extension> extension =
registry_->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
CHECK(delegate_);
bool is_controlled_extension =
!delegate_->CanDisableExtension(extension.get());
if (is_controlled_extension) {
// Remove disallowed disable reasons.
// Certain disable reasons are always allowed, since they are more internal
// to the browser (rather than the user choosing to disable the extension).
base::flat_set<int> internal_disable_reasons = {
extensions::disable_reason::DISABLE_RELOAD,
extensions::disable_reason::DISABLE_CORRUPTED,
extensions::disable_reason::DISABLE_UPDATE_REQUIRED_BY_POLICY,
extensions::disable_reason::
DISABLE_PUBLISHED_IN_STORE_REQUIRED_BY_POLICY,
extensions::disable_reason::DISABLE_BLOCKED_BY_POLICY,
extensions::disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED,
extensions::disable_reason::DISABLE_REINSTALL,
extensions::disable_reason::DISABLE_UNSUPPORTED_MANIFEST_VERSION,
extensions::disable_reason::DISABLE_NOT_VERIFIED,
extensions::disable_reason::DISABLE_UNSUPPORTED_DEVELOPER_EXTENSION,
};
disable_reasons = base::STLSetIntersection<base::flat_set<int>>(
disable_reasons, internal_disable_reasons);
if (disable_reasons.empty()) {
return;
}
}
auto passkey = ExtensionPrefs::DisableReasonRawManipulationPasskey();
// The extension may have been disabled already. Just add the disable reasons.
if (!IsExtensionEnabled(extension_id)) {
extension_prefs_->AddRawDisableReasons(passkey, extension_id,
disable_reasons);
return;
}
extension_prefs_->ReplaceRawDisableReasons(passkey, extension_id,
disable_reasons);
int include_mask =
ExtensionRegistry::EVERYTHING & ~ExtensionRegistry::DISABLED;
extension = registry_->GetExtensionById(extension_id, include_mask);
if (!extension)
return;
// The extension is either enabled or terminated.
DCHECK(registry_->enabled_extensions().Contains(extension->id()) ||
registry_->terminated_extensions().Contains(extension->id()));
// Move the extension to the disabled list.
registry_->AddDisabled(extension);
if (registry_->enabled_extensions().Contains(extension->id())) {
registry_->RemoveEnabled(extension->id());
DeactivateExtension(extension.get(), UnloadedExtensionReason::DISABLE);
} else {
// The extension must have been terminated. Don't send additional
// notifications for it being disabled.
bool removed = registry_->RemoveTerminated(extension->id());
DCHECK(removed);
}
}
void ExtensionRegistrar::DisableExtensionWithSource(
const Extension* source_extension,
const ExtensionId& extension_id,
disable_reason::DisableReason disable_reason) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(disable_reason == disable_reason::DISABLE_USER_ACTION ||
disable_reason == disable_reason::DISABLE_BLOCKED_BY_POLICY);
if (disable_reason == disable_reason::DISABLE_BLOCKED_BY_POLICY) {
DCHECK(Manifest::IsPolicyLocation(source_extension->location()) ||
Manifest::IsComponentLocation(source_extension->location()));
}
const Extension* extension =
registry_->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
CHECK(extension_system_->management_policy()->ExtensionMayModifySettings(
source_extension, extension, nullptr));
DisableExtension(extension_id, {disable_reason});
}
void ExtensionRegistrar::EnabledReloadableExtensions() {
std::vector<std::string> extensions_to_enable;
for (const auto& e : registry_->disabled_extensions()) {
if (extension_prefs_->HasOnlyDisableReason(
e->id(), disable_reason::DISABLE_RELOAD)) {
extensions_to_enable.push_back(e->id());
}
}
for (const std::string& extension : extensions_to_enable) {
EnableExtension(extension);
}
}
base::flat_set<int> ExtensionRegistrar::GetDisableReasonsOnInstalled(
const Extension* extension) {
bool is_update_from_same_type = false;
{
const Extension* existing_extension =
registry_->GetInstalledExtension(extension->id());
is_update_from_same_type =
existing_extension &&
existing_extension->manifest()->type() == extension->manifest()->type();
}
disable_reason::DisableReason disable_reason = disable_reason::DISABLE_NONE;
// Extensions disabled by management policy should always be disabled, even
// if it's force-installed.
if (extension_system_->management_policy()->MustRemainDisabled(
extension, &disable_reason)) {
// A specified reason is required to disable the extension.
DCHECK(disable_reason != disable_reason::DISABLE_NONE);
return {disable_reason};
}
// Extensions installed by policy can't be disabled. So even if a previous
// installation disabled the extension, make sure it is now enabled.
if (extension_system_->management_policy()->MustRemainEnabled(extension,
nullptr)) {
return {};
}
// An already disabled extension should inherit the disable reasons and
// remain disabled. We must get the raw reasons to retain unknown reasons.
if (extension_prefs_->IsExtensionDisabled(extension->id())) {
auto passkey = ExtensionPrefs::DisableReasonRawManipulationPasskey();
base::flat_set<int> disable_reasons =
extension_prefs_->GetRawDisableReasons(passkey, extension->id());
// If an extension was disabled without specified reason, presume it's
// disabled by user.
return disable_reasons.empty()
? base::flat_set<int>({disable_reason::DISABLE_USER_ACTION})
: disable_reasons;
}
if (util::IsPromptingEnabled()) {
// 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). We also don't prompt for
// extensions updating; this is because the extension will be disabled from
// the initial install if it is supposed to be, and this allows us to turn
// this on for other platforms without disabling already-installed
// extensions.
if (extension->GetType() != Manifest::Type::kHostedApp &&
Manifest::IsExternalLocation(extension->location()) &&
!extension_prefs_->IsExternalExtensionAcknowledged(extension->id()) &&
!is_update_from_same_type) {
return {disable_reason::DISABLE_EXTERNAL_EXTENSION};
}
}
return {};
}
void ExtensionRegistrar::AddComponentExtension(const Extension* extension) {
extension_prefs_->ClearInapplicableDisableReasonsForComponentExtension(
extension->id());
const std::string old_version_string(
extension_prefs_->GetVersionString(extension->id()));
const base::Version old_version(old_version_string);
VLOG(1) << "AddComponentExtension " << extension->name();
if (!old_version.IsValid() || old_version != extension->version()) {
VLOG(1) << "Component extension " << extension->name() << " ("
<< extension->id() << ") installing/upgrading from '"
<< old_version_string << "' to "
<< extension->version().GetString();
// If there was a previous installation, we need to clear the extension
// service worker. This is a workaround to ensure component extension
// updates are applied. See crbug.com/425464855.
if (old_version.IsValid()) {
UnregisterServiceWorkerWithRootScope(extension);
}
// TODO(crbug.com/40508457): If needed, add support for Declarative Net
// Request to component extensions and pass the ruleset install prefs here.
AddNewOrUpdatedExtension(extension, {}, kInstallFlagNone,
syncer::StringOrdinal(), std::string(),
/*ruleset_install_prefs=*/{});
return;
}
AddExtension(extension);
}
void ExtensionRegistrar::RemoveComponentExtension(
const std::string& extension_id) {
scoped_refptr<const Extension> extension(
registry_->enabled_extensions().GetByID(extension_id));
RemoveExtension(extension_id, UnloadedExtensionReason::UNINSTALL);
if (extension.get()) {
registry_->TriggerOnUninstalled(extension.get(),
UNINSTALL_REASON_COMPONENT_REMOVED);
}
}
void ExtensionRegistrar::RemoveDisableReasonAndMaybeEnable(
const std::string& extension_id,
disable_reason::DisableReason reason_to_remove) {
DisableReasonSet disable_reasons =
extension_prefs_->GetDisableReasons(extension_id);
if (!disable_reasons.contains(reason_to_remove)) {
return;
}
extension_prefs_->RemoveDisableReason(extension_id, reason_to_remove);
if (disable_reasons.size() == 1) {
EnableExtension(extension_id);
}
}
namespace {
std::vector<scoped_refptr<DevToolsAgentHost>> GetDevToolsAgentHostsFor(
ProcessManager* process_manager,
const Extension* extension) {
std::vector<scoped_refptr<DevToolsAgentHost>> result;
if (!BackgroundInfo::IsServiceWorkerBased(extension)) {
ExtensionHost* host =
process_manager->GetBackgroundHostForExtension(extension->id());
if (host) {
content::WebContents* const wc = host->host_contents();
if (auto tab_host = content::DevToolsAgentHost::GetForTab(wc)) {
result.push_back(tab_host);
}
if (content::DevToolsAgentHost::HasFor(wc)) {
result.push_back(content::DevToolsAgentHost::GetOrCreateFor(wc));
}
}
} else {
content::ServiceWorkerContext* context =
util::GetServiceWorkerContextForExtensionId(
extension->id(), process_manager->browser_context());
std::vector<WorkerId> service_worker_ids =
process_manager->GetServiceWorkersForExtension(extension->id());
for (const auto& worker_id : service_worker_ids) {
auto devtools_host =
DevToolsAgentHost::GetForServiceWorker(context, worker_id.version_id);
if (devtools_host)
result.push_back(std::move(devtools_host));
}
}
return result;
}
} // namespace
void ExtensionRegistrar::ReloadExtension(const ExtensionId& extension_id) {
DoReloadExtension(extension_id, LoadErrorBehavior::kNoisy);
}
void ExtensionRegistrar::ReloadExtensionWithQuietFailure(
const ExtensionId& extension_id) {
DoReloadExtension(extension_id, LoadErrorBehavior::kQuiet);
}
bool ExtensionRegistrar::UninstallExtension(
// "transient" because the process of uninstalling may cause the reference
// to become invalid. Instead, use |extension->id()|.
const std::string& transient_extension_id,
UninstallReason reason,
std::u16string* error,
base::OnceClosure done_callback) {
CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
scoped_refptr<const Extension> extension =
registry_->GetInstalledExtension(transient_extension_id);
// Callers should not send us nonexistent extensions.
CHECK(extension.get());
ManagementPolicy* by_policy = extension_system_->management_policy();
// Policy change which triggers an uninstall will always set
// |external_uninstall| to true so this is the only way to uninstall
// managed extensions.
// Shared modules being uninstalled will also set |external_uninstall| to true
// so that we can guarantee users don't uninstall a shared module.
// (crbug.com/273300)
// TODO(rdevlin.cronin): This is probably not right. We should do something
// else, like include an enum IS_INTERNAL_UNINSTALL or IS_USER_UNINSTALL so
// we don't do this.
bool external_uninstall =
(reason == UNINSTALL_REASON_INTERNAL_MANAGEMENT) ||
(reason == UNINSTALL_REASON_COMPONENT_REMOVED) ||
(reason == UNINSTALL_REASON_MIGRATED) ||
(reason == UNINSTALL_REASON_REINSTALL) ||
(reason == UNINSTALL_REASON_ORPHANED_EXTERNAL_EXTENSION) ||
(reason == UNINSTALL_REASON_ORPHANED_SHARED_MODULE);
if (!external_uninstall &&
(!by_policy->UserMayModifySettings(extension.get(), error) ||
by_policy->MustRemainInstalled(extension.get(), error))) {
registry_->TriggerOnUninstallationDenied(extension.get());
return false;
}
// Prepare to uninstall the extension.
delegate_->PreUninstallExtension(extension.get());
UMA_HISTOGRAM_ENUMERATION("Extensions.UninstallType", extension->GetType(),
100);
// Unload before doing more cleanup to ensure that nothing is hanging on to
// any of these resources.
RemoveExtension(extension->id(), UnloadedExtensionReason::UNINSTALL);
// `UnloadExtension` ignores extensions that are `BLOCKLISTED` or `BLOCKED`
if (registry_->blocklisted_extensions().Contains(extension->id())) {
registry_->RemoveBlocklisted(extension->id());
}
if (registry_->blocked_extensions().Contains(extension->id())) {
registry_->RemoveBlocked(extension->id());
}
// Perform the necessary clean up after the extension is un-registered.
delegate_->PostUninstallExtension(extension, std::move(done_callback));
UntrackTerminatedExtension(extension->id());
// Notify interested parties that we've uninstalled this extension.
registry_->TriggerOnUninstalled(extension.get(), reason);
// Perform the necessary clean up work after extension un-installation event
// has been notified to all observers.
delayed_install_manager_->Remove(extension->id());
extension_prefs_->OnExtensionUninstalled(
extension->id(), extension->location(), external_uninstall);
return true;
}
void ExtensionRegistrar::UninstallMigratedExtensions(
base::span<const char* const> migrated_ids) {
const ExtensionSet installed_extensions =
registry_->GenerateInstalledExtensionsSet();
for (const auto* extension_id : migrated_ids) {
auto* extension = installed_extensions.GetByID(extension_id);
if (extension) {
UninstallExtension(extension_id, UNINSTALL_REASON_COMPONENT_REMOVED,
nullptr);
extension_prefs_->MarkObsoleteComponentExtensionAsRemoved(
extension->id(), extension->location());
}
}
}
void ExtensionRegistrar::FinishInstallation(const Extension* extension) {
const Extension* existing_extension =
registry_->GetInstalledExtension(extension->id());
bool is_update = false;
std::string old_name;
if (existing_extension) {
is_update = true;
old_name = existing_extension->name();
}
registry_->TriggerOnWillBeInstalled(extension, is_update, old_name);
// 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);
// Notify observers that need to know when an installation is complete.
registry_->TriggerOnInstalled(extension, is_update);
// Check extensions that may have been delayed only because this shared module
// was not available.
if (SharedModuleInfo::IsSharedModule(extension)) {
delayed_install_manager_->MaybeFinishDelayedInstallations();
}
}
bool ExtensionRegistrar::CanBlockExtension(const Extension* extension) const {
DCHECK(extension);
return extension->location() != ManifestLocation::kComponent &&
extension->location() != ManifestLocation::kExternalComponent &&
!extension_system_->management_policy()->MustRemainEnabled(extension,
nullptr);
}
// Extensions that are not locked, components or forced by policy should be
// locked. Extensions are no longer considered enabled or disabled. Blocklisted
// extensions are now considered both blocklisted and locked.
void ExtensionRegistrar::BlockAllExtensions() {
if (block_extensions_) {
return;
}
block_extensions_ = true;
// Blocklisted extensions are already unloaded, need not be blocked.
const ExtensionSet extensions = registry_->GenerateInstalledExtensionsSet(
ExtensionRegistry::ENABLED | ExtensionRegistry::DISABLED |
ExtensionRegistry::TERMINATED);
for (const auto& extension : extensions) {
const std::string& id = extension->id();
if (!CanBlockExtension(extension.get())) {
continue;
}
registry_->AddBlocked(extension.get());
RemoveExtension(id, UnloadedExtensionReason::LOCK_ALL);
}
}
// All locked extensions should revert to being either enabled or disabled
// as appropriate.
void ExtensionRegistrar::UnblockAllExtensions() {
if (!block_extensions_) {
return;
}
block_extensions_ = false;
const ExtensionSet to_unblock =
registry_->GenerateInstalledExtensionsSet(ExtensionRegistry::BLOCKED);
for (const auto& extension : to_unblock) {
registry_->RemoveBlocked(extension->id());
AddExtension(extension.get());
}
// While extensions are blocked, we won't display any external install
// warnings. Now that they are unblocked, we should update the error.
delegate_->UpdateExternalExtensionAlert();
}
void ExtensionRegistrar::OnBlocklistStateRemoved(
const std::string& extension_id) {
if (blocklist_prefs::IsExtensionBlocklisted(extension_id, extension_prefs_)) {
return;
}
// Clear acknowledged state.
blocklist_prefs::RemoveAcknowledgedBlocklistState(
extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
extension_prefs_);
scoped_refptr<const Extension> extension =
registry_->blocklisted_extensions().GetByID(extension_id);
DCHECK(extension);
registry_->RemoveBlocklisted(extension_id);
AddExtension(extension.get());
}
void ExtensionRegistrar::OnBlocklistStateAdded(
const std::string& extension_id) {
DCHECK(
blocklist_prefs::IsExtensionBlocklisted(extension_id, extension_prefs_));
// The extension was already acknowledged by the user, it should already be in
// the unloaded state.
if (blocklist_prefs::HasAcknowledgedBlocklistState(
extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
extension_prefs_)) {
DCHECK(base::Contains(registry_->blocklisted_extensions().GetIDs(),
extension_id));
return;
}
scoped_refptr<const Extension> extension =
registry_->GetInstalledExtension(extension_id);
registry_->AddBlocklisted(extension);
RemoveExtension(extension_id, UnloadedExtensionReason::BLOCKLIST);
}
void ExtensionRegistrar::OnGreylistStateRemoved(
const std::string& extension_id) {
bool is_on_sb_list = (blocklist_prefs::GetSafeBrowsingExtensionBlocklistState(
extension_id, extension_prefs_) !=
BitMapBlocklistState::NOT_BLOCKLISTED);
bool is_on_omaha_list =
blocklist_prefs::HasAnyOmahaGreylistState(extension_id, extension_prefs_);
if (is_on_sb_list || is_on_omaha_list) {
return;
}
// Clear all acknowledged states so the extension will still get disabled if
// it is added to the greylist again.
blocklist_prefs::ClearAcknowledgedGreylistStates(extension_id,
extension_prefs_);
RemoveDisableReasonAndMaybeEnable(extension_id,
disable_reason::DISABLE_GREYLIST);
// A user can enable and disable a force-installed extension while it is
// greylisted. If a user disables an extension while greylisted, the
// extension gets a DISABLE_USER_ACTION disable reason assigned to it. So
// remove the DISABLE_USER_ACTION disable reason as well when a
// force-installed extension gets "un-greylisted" to allow the extension
// to be re-enabled.
const Extension* extension = registry_->GetInstalledExtension(extension_id);
if (extension && extension_system_->management_policy()->MustRemainEnabled(
extension, nullptr)) {
RemoveDisableReasonAndMaybeEnable(extension_id,
disable_reason::DISABLE_USER_ACTION);
}
}
void ExtensionRegistrar::OnGreylistStateAdded(const std::string& extension_id,
BitMapBlocklistState new_state) {
#if DCHECK_IS_ON()
bool has_new_state_on_sb_list =
(blocklist_prefs::GetSafeBrowsingExtensionBlocklistState(
extension_id, extension_prefs_) == new_state);
bool has_new_state_on_omaha_list = blocklist_prefs::HasOmahaBlocklistState(
extension_id, new_state, extension_prefs_);
DCHECK(has_new_state_on_sb_list || has_new_state_on_omaha_list);
#endif
if (blocklist_prefs::HasAcknowledgedBlocklistState(extension_id, new_state,
extension_prefs_)) {
// If the extension is already acknowledged, don't disable it again
// because it can be already re-enabled by the user. This could happen if
// the extension is added to the SafeBrowsing blocklist, and then
// subsequently marked by Omaha. In this case, we don't want to disable the
// extension twice.
return;
}
// Set the current greylist states to acknowledge immediately because the
// extension is disabled silently. Clear the other acknowledged state because
// when the state changes to another greylist state in the future, we'd like
// to disable the extension again.
blocklist_prefs::UpdateCurrentGreylistStatesAsAcknowledged(extension_id,
extension_prefs_);
DisableExtension(extension_id, {disable_reason::DISABLE_GREYLIST});
}
void ExtensionRegistrar::BlocklistExtensionForTest(
const std::string& extension_id) {
blocklist_prefs::SetSafeBrowsingExtensionBlocklistState(
extension_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
extension_prefs_);
OnBlocklistStateAdded(extension_id);
}
void ExtensionRegistrar::GreylistExtensionForTest(
const std::string& extension_id,
const BitMapBlocklistState& state) {
blocklist_prefs::SetSafeBrowsingExtensionBlocklistState(extension_id, state,
extension_prefs_);
if (state == BitMapBlocklistState::NOT_BLOCKLISTED) {
OnGreylistStateRemoved(extension_id);
} else {
OnGreylistStateAdded(extension_id, state);
}
}
// static
base::AutoReset<bool> ExtensionRegistrar::DisableLazyContextSpinupForTest() {
CHECK_IS_TEST();
return base::AutoReset<bool>(&g_disable_lazy_context_spinup_for_test, true);
}
void ExtensionRegistrar::OnUnpackedExtensionReloadFailed(
const base::FilePath& path) {
failed_to_reload_unpacked_extensions_.insert(path);
}
void ExtensionRegistrar::GrantPermissionsAndEnableExtension(
const Extension& extension) {
delegate_->GrantActivePermissions(&extension);
EnableExtension(extension.id());
}
void ExtensionRegistrar::AddDisableFlagExemptedExtension(
const ExtensionId& extension_id) {
disable_flag_exempted_extensions_.insert(extension_id);
}
void ExtensionRegistrar::TerminateExtension(const ExtensionId& extension_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
scoped_refptr<const Extension> extension =
registry_->enabled_extensions().GetByID(extension_id);
if (!extension)
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();
DCHECK(!base::Contains(reloading_extensions_, extension->id()))
<< "Enabled extension shouldn't be marked for reloading";
registry_->AddTerminated(extension);
registry_->RemoveEnabled(extension_id);
DeactivateExtension(extension.get(), UnloadedExtensionReason::TERMINATE);
}
void ExtensionRegistrar::UntrackTerminatedExtension(
const ExtensionId& extension_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
scoped_refptr<const Extension> extension =
registry_->terminated_extensions().GetByID(extension_id);
if (!extension)
return;
registry_->RemoveTerminated(extension_id);
}
bool ExtensionRegistrar::IsExtensionEnabled(
const ExtensionId& extension_id) const {
if (registry_->enabled_extensions().Contains(extension_id) ||
registry_->terminated_extensions().Contains(extension_id)) {
return true;
}
if (registry_->disabled_extensions().Contains(extension_id) ||
registry_->blocklisted_extensions().Contains(extension_id) ||
registry_->blocked_extensions().Contains(extension_id)) {
return false;
}
if (ShouldBlockExtension(nullptr)) {
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) &&
!blocklist_prefs::IsExtensionBlocklisted(extension_id,
extension_prefs_) &&
!extension_prefs_->IsExternalExtensionUninstalled(extension_id);
}
void ExtensionRegistrar::DidCreateMainFrameForBackgroundPage(
ExtensionHost* host) {
auto iter = orphaned_dev_tools_.find(host->extension_id());
if (iter == orphaned_dev_tools_.end())
return;
// Keepalive count is reset on extension reload. This re-establishes the
// keepalive that was added when the DevTools agent was initially attached.
ProcessManager::Get(browser_context_)
->IncrementLazyKeepaliveCount(host->extension(), Activity::DEV_TOOLS,
std::string());
// TODO(caseq): do we need to handle the case when the extension changed
// from SW-based to WC-based during reload?
for (auto& dev_tools_host : iter->second) {
dev_tools_host->ConnectWebContents(host->host_contents());
}
orphaned_dev_tools_.erase(iter);
}
void ExtensionRegistrar::ActivateExtension(const Extension* extension,
bool is_newly_added) {
// Activate the extension before calling
// RendererStartupHelper::OnExtensionLoaded() below, so that we have
// activation information ready while we send ExtensionMsg_Load IPC.
//
// TODO(lazyboy): We should move all logic that is required to start up an
// extension to a separate class, instead of calling adhoc methods like
// service worker ones below.
ActivateTaskQueueForExtension(browser_context_, extension);
renderer_helper_->OnExtensionLoaded(*extension);
// Tell subsystems that use the ExtensionRegistryObserver::OnExtensionLoaded
// 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
// ExtensionRegistryObserver::OnExtensionLoaded the renderer is guaranteed to
// know about it.
registry_->TriggerOnLoaded(extension);
delegate_->PostActivateExtension(extension);
// When an extension is activated, and it is either event page-based or
// service worker-based, it may be necessary to spin up its context.
if (BackgroundInfo::HasLazyContext(extension))
MaybeSpinUpLazyContext(extension, is_newly_added);
registry_->AddReady(extension);
if (registry_->enabled_extensions().Contains(extension->id())) {
registry_->TriggerOnReady(extension);
}
}
void ExtensionRegistrar::DeactivateExtension(const Extension* extension,
UnloadedExtensionReason reason) {
// NOTE: Call `TriggerOnUnloaded` before `DeactivateTaskQueueForExtension`.
// If an extension service worker is running, this stops it, which triggers a
// synchronous notification. This notification updates the
// `ServiceWorkerState` and untracks the worker from `ProcessManager`.
// `ServiceWorkerTaskQueue` can then operate in a consistent state, safely
// assuming the worker is no longer active.
registry_->TriggerOnUnloaded(extension, reason);
renderer_helper_->OnExtensionUnloaded(*extension);
DeactivateTaskQueueForExtension(browser_context_, extension);
delegate_->PostDeactivateExtension(extension);
}
void ExtensionRegistrar::DoReloadExtension(
ExtensionId extension_id,
LoadErrorBehavior load_error_behavior) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::FilePath path;
const Extension* disabled_extension =
registry_->disabled_extensions().GetByID(extension_id);
if (disabled_extension) {
path = disabled_extension->path();
}
// If the extension is already reloading, don't reload again.
if (extension_prefs_->HasDisableReason(extension_id,
disable_reason::DISABLE_RELOAD)) {
DCHECK(disabled_extension);
// If an unpacked extension previously failed to reload, it will still be
// marked as disabled, but we can try to reload it again - the developer
// may have fixed the issue.
if (failed_to_reload_unpacked_extensions_.count(path) == 0) {
return;
}
failed_to_reload_unpacked_extensions_.erase(path);
}
// Ignore attempts to reload a blocklisted or blocked extension. Sometimes
// this can happen in a convoluted reload sequence triggered by the
// termination of a blocklisted or blocked extension and a naive attempt to
// reload it. For an example see http://crbug.com/373842.
if (registry_->blocklisted_extensions().Contains(extension_id) ||
registry_->blocked_extensions().Contains(extension_id)) {
return;
}
const Extension* enabled_extension =
registry_->enabled_extensions().GetByID(extension_id);
// Disable the extension if it's loaded. It might not be loaded if it crashed.
if (enabled_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!
ProcessManager* manager = ProcessManager::Get(browser_context_);
auto agent_hosts = GetDevToolsAgentHostsFor(manager, enabled_extension);
if (!agent_hosts.empty()) {
for (auto& host : agent_hosts) {
// Let DevTools know we'll be back once extension is reloaded.
host->DisconnectWebContents();
}
// Retain DevToolsAgentHosts for the extension being reloaded to prevent
// client disconnecting. We will re-attach later, when the extension is
// loaded.
// TODO(crbug.com/40196582): clean up upon failure to reload.
orphaned_dev_tools_[extension_id] = std::move(agent_hosts);
}
path = enabled_extension->path();
DisableExtension(extension_id, {disable_reason::DISABLE_RELOAD});
DCHECK(registry_->disabled_extensions().Contains(extension_id));
reloading_extensions_.insert(extension_id);
} else if (!disabled_extension) {
std::map<ExtensionId, base::FilePath>::const_iterator iter =
unloaded_extension_paths_.find(extension_id);
if (iter == unloaded_extension_paths_.end()) {
return;
}
path = unloaded_extension_paths_[extension_id];
}
if (delayed_install_manager_->Contains(extension_id) &&
delayed_install_manager_->FinishDelayedInstallationIfReady(
extension_id, true /*install_immediately*/)) {
return;
}
if (load_error_behavior == LoadErrorBehavior::kQuiet) {
delegate_->LoadExtensionForReloadWithQuietFailure(extension_id, path);
} else {
delegate_->LoadExtensionForReload(extension_id, path);
}
}
void ExtensionRegistrar::UnregisterServiceWorkerWithRootScope(
const Extension* new_extension) {
// Only cleanup the old service worker if the new extension is
// service-worker-based.
if (!BackgroundInfo::IsServiceWorkerBased(new_extension)) {
return;
}
// Non service-worker based extensions could register root-scope service
// workers using regular web APIs. These service workers are not tracked by
// extension ServiceWorkerTaskQueue and would prevent newer service worker
// version from installing (crbug/1340341).
content::ServiceWorkerContext* context =
util::GetServiceWorkerContextForExtensionId(new_extension->id(),
browser_context_);
bool worker_previously_registered =
ServiceWorkerTaskQueue::Get(browser_context_)
->IsWorkerRegistered(new_extension->id());
// Even though the unregistration process for a service worker is
// asynchronous, we begin the process before the new extension is added, so
// the old worker will be unregistered before the new one is registered.
if (base::FeatureList::IsEnabled(
kExtensionUpdatesImmediatelyUnregisterWorker)) {
context->UnregisterServiceWorkerImmediately(
new_extension->url(),
blink::StorageKey::CreateFirstParty(new_extension->origin()),
base::BindOnce(&ExtensionRegistrar::NotifyServiceWorkerUnregistered,
weak_factory_.GetWeakPtr(), new_extension->id(),
worker_previously_registered));
} else {
context->UnregisterServiceWorker(
new_extension->url(),
blink::StorageKey::CreateFirstParty(new_extension->origin()),
base::BindOnce(&ExtensionRegistrar::NotifyServiceWorkerUnregistered,
weak_factory_.GetWeakPtr(), new_extension->id(),
worker_previously_registered));
}
}
void ExtensionRegistrar::NotifyServiceWorkerUnregistered(
const ExtensionId& extension_id,
bool worker_previously_registered,
blink::ServiceWorkerStatusCode status) {
bool success =
ServiceWorkerTaskQueue::Get(browser_context_)
->IsWorkerUnregistrationSuccess(status, worker_previously_registered);
base::UmaHistogramBoolean(
"Extensions.ServiceWorkerBackground.WorkerUnregistrationState", success);
base::UmaHistogramBoolean(
"Extensions.ServiceWorkerBackground.WorkerUnregistrationState_"
"AddExtension",
success);
if (!success) {
// TODO(crbug.com/346732739): Handle this case.
LOG(ERROR) << "Failed to unregister service worker for extension "
<< extension_id;
base::UmaHistogramEnumeration(
"Extensions.ServiceWorkerBackground.WorkerUnregistrationFailureStatus",
status);
base::UmaHistogramEnumeration(
"Extensions.ServiceWorkerBackground.WorkerUnregistrationFailureStatus_"
"AddExtension",
status);
}
}
bool ExtensionRegistrar::ReplaceReloadedExtension(
scoped_refptr<const Extension> extension) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// The extension must already be disabled, and the original extension has
// been unloaded.
CHECK(registry_->disabled_extensions().Contains(extension->id()));
if (!delegate_->CanEnableExtension(extension.get()))
return false;
// TODO(michaelpg): Other disable reasons might have been added after the
// reload started. We may want to keep the extension disabled and just remove
// the DISABLE_RELOAD reason in that case.
extension_prefs_->ClearDisableReasons(extension->id());
// Move it over to the enabled list.
CHECK(registry_->RemoveDisabled(extension->id()));
CHECK(registry_->AddEnabled(extension));
ActivateExtension(extension.get(), false);
return true;
}
void ExtensionRegistrar::MaybeSpinUpLazyContext(const Extension* extension,
bool is_newly_added) {
DCHECK(BackgroundInfo::HasLazyContext(extension));
if (g_disable_lazy_context_spinup_for_test) {
return;
}
// For orphaned devtools, we will reconnect devtools to it later in
// DidCreateMainFrameForBackgroundPage().
bool has_orphaned_dev_tools =
base::Contains(orphaned_dev_tools_, extension->id());
// Reloading component extension does not trigger install, so RuntimeAPI won't
// be able to detect its loading. Therefore, we need to spin up its lazy
// background page.
bool is_component_extension =
Manifest::IsComponentLocation(extension->location());
// TODO(crbug.com/40107353): This is either a workaround or something
// that will be part of the permanent solution for service worker-
// based extensions.
// We spin up extensions with the webRequest permission so their
// listeners are reconstructed on load.
// Event page-based extension cannot have the webRequest permission, but
// a bug allowed them to specify it in optional permissions, so filter
// out those extensions. See crbug.com/40912377.
bool needs_spinup_for_web_request =
extension->permissions_data()->HasAPIPermission(
mojom::APIPermissionID::kWebRequest) &&
BackgroundInfo::IsServiceWorkerBased(extension);
// If there aren't any special cases, we're done.
if (!has_orphaned_dev_tools && !is_component_extension &&
!needs_spinup_for_web_request) {
return;
}
// If the extension's not being reloaded (|is_newly_added| = true),
// only wake it up if it has the webRequest permission.
if (is_newly_added && !needs_spinup_for_web_request)
return;
// Wake up the extension by posting a dummy task. In the case of a service
// worker-based extension with the webRequest permission that's being newly
// installed, this will result in a no-op task that's not necessary, since
// this is really only needed for a previously-installed extension. However,
// that cost is minimal, since the worker is already active.
const auto context_id =
LazyContextId::ForExtension(browser_context_, extension);
context_id.GetTaskQueue()->AddPendingTask(context_id, base::DoNothing());
}
void ExtensionRegistrar::OnStartedTrackingServiceWorkerInstance(
const WorkerId& worker_id) {
// Just release the host. We only get here when the new worker has been
// attached and resumed by the DevTools, and all we need in case of service
// worker-based extensions is to keep the host around for the target
// auto-attacher to do its job.
orphaned_dev_tools_.erase(worker_id.extension_id);
}
bool ExtensionRegistrar::CanAddExtension(const Extension* extension) const {
// 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() &&
!Manifest::ShouldAlwaysLoadExtension(extension->location(),
extension->is_theme()) &&
disable_flag_exempted_extensions_.count(extension->id()) == 0) {
return false;
}
return true;
}
bool ExtensionRegistrar::ShouldBlockExtension(
const Extension* extension) const {
if (!block_extensions_) {
return false;
}
// Blocked extensions aren't marked as such in prefs, thus if
// `block_extensions_` is true then CanBlockExtension() must be called with an
// Extension object. If `extension` is not loaded, assume it should be
// blocked.
return !extension || CanBlockExtension(extension);
}
} // namespace extensions