// 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/command_line.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 "base/version_info/version_info.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_pref_names.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/pref_types.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 "extensions/common/switches.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);

// The browser version at the time a component extension was last added.
// Component extensions are packaged with the browser, so their code can
// change whenever the browser updates, even if the version in their manifest
// does not.
constexpr PrefMap kLastLoadedBrowserVersion = {"last_loaded_browser_version",
                                               PrefType::kString,
                                               PrefScope::kExtensionSpecific};

// Overrides the browser version in CheckAndUpdateLastLoadedBrowserVersion() for
// tests.
const char* g_browser_version_for_testing = nullptr;

}  // namespace

// Returns an empty set to clear disable reasons when a new extension version
// is installed. Override this method to preserve specific disable reasons
// across extension updates.
base::flat_set<int> ExtensionRegistrar::Delegate::GetDisableReasonsOnInstalled(
    const Extension* extension,
    int install_flags) {
  return {};
}

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_);
  delayed_install_manager_observation_.Reset();
  delayed_install_manager_observation_.Observe(delayed_install_manager_);
}

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_observation_.Reset();
  delayed_install_manager_ = nullptr;
}

void ExtensionRegistrar::OnDelayedInstallFinished(
    scoped_refptr<const Extension> extension) {
  ExtensionPrefs::DelayedInstallInfo info =
      extension_prefs_->GetDelayedInstallInfo(extension->id());

  AddNewOrUpdatedExtension(extension.get(), info.install_flags,
                           info.page_ordinal, info.install_parameter,
                           std::move(info.ruleset_install_prefs));
}

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/41301792) 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_installed = false;
  const Extension* old = registry_->GetInstalledExtension(extension->id());
  if (old) {
    is_extension_installed = 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_installed) {
      // 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,
    int install_flags,
    const syncer::StringOrdinal& page_ordinal,
    const std::string& install_parameter,
    base::DictValue ruleset_install_prefs) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  base::flat_set<int> disable_reasons =
      delegate_->GetDisableReasonsOnInstalled(extension, install_flags);
  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::DictValue 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);

  // Since this can be called from WebUI, this disable request might race with
  // an extension that has already been uninstalled (e.g. via a non-UI
  // automated means). If so, we can just return early since the extension is
  // already uninstalled.
  if (!extension) {
    return;
  }

  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_all_but_disabled =
      ExtensionRegistry::EVERYTHING & ~ExtensionRegistry::DISABLED;
  extension =
      registry_->GetExtensionById(extension_id, include_all_but_disabled);
  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);
  const bool browser_updated =
      CheckAndUpdateLastLoadedBrowserVersion(extension->id());

  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;
  }

  // A browser update may have changed the extension's code even though the
  // extension version did not change. The service worker runs the script
  // persisted by the //content layer at registration time, not the
  // extension's files on disk, so a registered worker must be unregistered
  // for the activation below to register it afresh from the updated files.
  // See crbug.com/521490632.
  bool sw_registered =
      ServiceWorkerTaskQueue::Get(browser_context_)
          ->RetrieveRegisteredServiceWorkerVersion(extension->id())
          .IsValid();
  bool force_refresh = base::CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kRefreshComponentExtensionServiceWorkers);
  if ((browser_updated || force_refresh) && sw_registered) {
    UnregisterServiceWorkerWithRootScope(extension);
  }

  AddExtension(extension);
}

// static
base::AutoReset<const char*>
ExtensionRegistrar::OverrideBrowserVersionForTesting(const char* version) {
  return base::AutoReset<const char*>(&g_browser_version_for_testing, version);
}

bool ExtensionRegistrar::CheckAndUpdateLastLoadedBrowserVersion(
    const ExtensionId& extension_id) {
  std::string current_version(version_info::GetVersionNumber());
  if (g_browser_version_for_testing) {
    current_version = g_browser_version_for_testing;
  }

  std::string last_version;
  if (extension_prefs_->ReadPrefAsString(
          extension_id, kLastLoadedBrowserVersion, &last_version) &&
      last_version == current_version) {
    return false;
  }
  extension_prefs_->SetStringPref(extension_id, kLastLoadedBrowserVersion,
                                  current_version);
  return true;
}

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/40329049)
  // 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(registry_->blocklisted_extensions().GetIDs().contains(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);
  }
}

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(!reloading_extensions_.contains(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.
  bool extension_enabled =
      registry_->enabled_extensions().Contains(extension->id());
  if (extension_enabled && BackgroundInfo::HasLazyContext(extension)) {
    MaybeSpinUpLazyContext(extension, is_newly_added);
  }

  registry_->AddReady(extension);
  if (extension_enabled) {
    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/41107702.
  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_);
  ServiceWorkerTaskQueue* task_queue =
      ServiceWorkerTaskQueue::Get(browser_context_);
  bool worker_previously_registered =
      task_queue->IsWorkerRegistered(new_extension->id());
  // We clear the worker registration record immediately, rather than waiting
  // for the async unregistration to finish. This ensures that if the extension
  // is activated again quickly, it knows to register a new worker instead of
  // relying on the one being removed. This is safe because the content layer
  // queues registration jobs behind unregistration jobs for the same scope,
  // guaranteeing the old worker is fully gone before the new one is registered.
  task_queue->RemoveRegisteredServiceWorkerInfo(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;
  }

  // We want to keep the extension disabled if there are other disable reasons.
  extension_prefs_->RemoveDisableReason(extension->id(),
                                        disable_reason::DISABLE_RELOAD);
  if (!extension_prefs_->GetDisableReasons(extension->id()).empty()) {
    return false;
  }

  // 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));

  // For orphaned devtools, we will reconnect devtools to it later in
  // DidCreateMainFrameForBackgroundPage().
  bool has_orphaned_dev_tools = orphaned_dev_tools_.contains(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());

  // It's possible that the worker has been registered but hasn't fully run yet,
  // as in the case where a service worker may have been interrupted during it's
  // startup flow. In that case, start it to ensure that it runs at least once.
  bool needs_spinup_because_hasnt_started = false;
  if (BackgroundInfo::IsServiceWorkerBased(extension)) {
    bool has_started = false;
    extension_prefs_->ReadPrefAsBoolean(
        extension->id(), kPrefHasStartedServiceWorker, &has_started);
    needs_spinup_because_hasnt_started = !has_started;
  }

  // If there aren't any special cases, we're done.
  if (!has_orphaned_dev_tools && !is_component_extension &&
      !needs_spinup_because_hasnt_started) {
    return;
  }

  // If the extension's not being reloaded (`is_newly_added` == true),
  // only wake it up if it hasn't fully started.
  if (is_newly_added && !needs_spinup_because_hasnt_started) {
    return;
  }

  // Wake up the extension by posting a no-op task.
  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
