// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/extension_service.h"

#include <stddef.h>

#include <algorithm>
#include <iterator>
#include <memory>
#include <optional>
#include <set>
#include <utility>

#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/debug/alias.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/one_shot_event.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/syslog_logging.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/chrome_extension_registrar_delegate.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/extensions/corrupted_extension_reinstaller.h"
#include "chrome/browser/extensions/cws_info_service_factory.h"
#include "chrome/browser/extensions/extension_action_storage_manager.h"
#include "chrome/browser/extensions/extension_allowlist_factory.h"
#include "chrome/browser/extensions/extension_disabled_ui.h"
#include "chrome/browser/extensions/extension_error_controller.h"
#include "chrome/browser/extensions/extension_special_storage_policy.h"
#include "chrome/browser/extensions/external_install_manager.h"
#include "chrome/browser/extensions/external_provider_impl.h"
#include "chrome/browser/extensions/external_provider_manager.h"
#include "chrome/browser/extensions/install_verifier_factory.h"
#include "chrome/browser/extensions/installed_loader.h"
#include "chrome/browser/extensions/omaha_attributes_handler.h"
#include "chrome/browser/extensions/profile_util.h"
#include "chrome/browser/extensions/sync/extension_sync_service.h"
#include "chrome/browser/extensions/updater/chrome_extension_downloader_factory.h"
#include "chrome/browser/extensions/updater/extension_updater.h"
#include "chrome/browser/google/google_brand.h"
#include "chrome/browser/lifetime/termination_notification.h"
#include "chrome/browser/policy/cloud/extension_install_policy_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/crx_file/id_util.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/supervised_user/core/browser/supervised_user_preferences.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/child_process_id.h"
#include "extensions/browser/blocklist_extension_prefs.h"
#include "extensions/browser/blocklist_state.h"
#include "extensions/browser/cws_info_service.h"
#include "extensions/browser/delayed_install_manager.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_allowlist.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_registrar.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/external_install_info.h"
#include "extensions/browser/forced_extensions/install_stage_tracker.h"
#include "extensions/browser/install_flag.h"
#include "extensions/browser/install_verifier.h"
#include "extensions/browser/management_policy.h"
#include "extensions/browser/manifest_v2_handler.h"
#include "extensions/browser/pending_extension_manager.h"
#include "extensions/browser/permissions/permissions_updater.h"
#include "extensions/browser/pref_names.h"
#include "extensions/browser/process_map.h"
#include "extensions/browser/renderer_startup_helper.h"
#include "extensions/browser/uninstall_reason.h"
#include "extensions/browser/unloaded_extension_reason.h"
#include "extensions/browser/unpacked_installer.h"
#include "extensions/browser/updater/extension_cache.h"
#include "extensions/browser/updater/extension_downloader.h"
#include "extensions/browser/updater/manifest_fetch_data.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/features/feature_developer_mode_only.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/manifest_url_handlers.h"
#include "extensions/common/manifest_handlers/shared_module_info.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/switches.h"

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
#include "chrome/browser/extensions/policy_dse_ntp_override_metrics_reporter.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "base/system/sys_info.h"
#include "chrome/browser/ash/extensions/install_limiter.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chromeos/constants/chromeos_features.h"
#endif

#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/upgrade_detector/upgrade_detector.h"
#endif

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

using content::BrowserContext;
using content::BrowserThread;
using extensions::mojom::ManifestLocation;

namespace extensions {

namespace {

// Wait this long after an extensions becomes idle before updating it.
constexpr base::TimeDelta kUpdateIdleDelay = base::Seconds(5);

// IDs of component extensions that have been obsoleted and need to be
// uninstalled.
// Note: We preserve at least one entry here for continued testing coverage.
const char* const kObsoleteComponentExtensionIds[] = {
    // The Video Player chrome app became obsolete in m93, but is preserved for
    // continued test coverage.
    "jcgeabjmjgoblfofpppfkcoakmfobdko",  // Video Player
};

// ExtensionUnpublishedAvailability policy default value.
constexpr int kAllowUnpublishedExtensions = 0;

#if !BUILDFLAG(GOOGLE_CHROME_BRANDING) || BUILDFLAG(IS_CHROMEOS)
const char kBlockLoadCommandline[] = "command_line";

bool ShouldBlockCommandLineExtension(Profile& profile) {
  const base::ListValue& list =
      profile.GetPrefs()->GetList(pref_names::kExtensionInstallTypeBlocklist);
  for (const auto& val : list) {
    if (val.is_string() && val.GetString() == kBlockLoadCommandline) {
      return true;
    }
  }

  return false;
}
#endif

}  // namespace

// ExtensionService.

void ExtensionService::BlocklistExtensionForTest(
    const std::string& extension_id) {
  extension_registrar_->BlocklistExtensionForTest(extension_id);  // IN-TEST
}

void ExtensionService::GreylistExtensionForTest(
    const std::string& extension_id,
    const BitMapBlocklistState& state) {
  extension_registrar_->GreylistExtensionForTest(extension_id,
                                                 state);  // IN-TEST
}

ExtensionService::ExtensionService(
    Profile* profile,
    const base::CommandLine* command_line,
    const base::FilePath& install_directory,
    const base::FilePath& unpacked_install_directory,
    ExtensionPrefs* extension_prefs,
    Blocklist* blocklist,
    ExtensionErrorController* error_controller,
    bool autoupdate_enabled,
    bool extensions_enabled,
    base::OneShotEvent* ready)
    : Blocklist::Observer(blocklist),
      command_line_(command_line),
      profile_(profile),
      system_(ExtensionSystem::Get(profile)),
      extension_prefs_(extension_prefs),
      blocklist_(blocklist),
      allowlist_(ExtensionAllowlistFactory::GetForBrowserContext(profile)),
      registry_(ExtensionRegistry::Get(profile)),
      pending_extension_manager_(PendingExtensionManager::Get(profile)),
      external_provider_manager_(ExternalProviderManager::Get(profile)),
      ready_(ready),
      updater_(ExtensionUpdater::Get(profile)),
      component_loader_(ComponentLoader::Get(profile_)),
      error_controller_(error_controller),
      external_install_manager_(ExternalInstallManager::Get(profile)),
      extension_registrar_delegate_(
          std::make_unique<ChromeExtensionRegistrarDelegate>(profile_)),
      extension_registrar_(ExtensionRegistrar::Get(profile)),
      safe_browsing_verdict_handler_(extension_prefs,
                                     registry_,
                                     extension_registrar_),
      extension_telemetry_service_verdict_handler_(extension_prefs,
                                                   registry_,
                                                   extension_registrar_),
      omaha_attributes_handler_(extension_prefs,
                                registry_,
                                extension_registrar_),
      force_installed_tracker_(registry_, profile_),
      force_installed_metrics_(registry_, profile_, &force_installed_tracker_),
      corrupted_extension_reinstaller_(
          CorruptedExtensionReinstaller::Get(profile_)),
      delayed_install_manager_(DelayedInstallManager::Get(profile_)) {
  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  TRACE_EVENT0("browser,startup", "ExtensionService::ExtensionService::ctor");

  extension_registrar_delegate_->Init(extension_registrar_);
  extension_registrar_->Init(extension_registrar_delegate_.get(),
                             extensions_enabled, command_line_,
                             install_directory, unpacked_install_directory);

  host_registry_observation_.Observe(ExtensionHostRegistry::Get(profile));

  // The ProfileManager may be null in unit tests.
  if (g_browser_process->profile_manager()) {
    profile_manager_observation_.Observe(g_browser_process->profile_manager());
  }

#if !BUILDFLAG(IS_ANDROID)
  // TODO(crbug.com/413455412): Find another way to report Chrome updates to
  // extensions on Android, which uses the Play Store for updates.
  UpgradeDetector::GetInstance()->AddObserver(this);
#endif

  cws_info_service_observation_.Observe(
      CWSInfoServiceFactory::GetForProfile(profile_));

  ExtensionManagementFactory::GetForBrowserContext(profile_)->AddObserver(this);

  if (auto* extension_install_policy_service =
          policy::ExtensionInstallPolicyServiceFactory::GetForBrowserContext(
              profile_)) {
    extension_install_policy_observation_.Observe(
        extension_install_policy_service);
  }

  if (autoupdate_enabled) {
    // Initialize and enable the ExtensionUpdater.
    updater_->InitAndEnable(
        extension_prefs, profile->GetPrefs(), kDefaultUpdateFrequency,
        ExtensionsBrowserClient::Get()->GetExtensionCache(),
        base::BindRepeating(ChromeExtensionDownloaderFactory::CreateForProfile,
                            profile));
  }

  if (extension_registrar_->extensions_enabled()) {
    external_provider_manager_->CreateExternalProviders();
  }

  // Set this as the ExtensionService for app sorting to ensure it causes syncs
  // if required.
  is_first_run_ = !extension_prefs_->SetAlertSystemFirstRun();

  error_controller_->set_is_first_run(is_first_run_);
  external_install_manager_->set_is_first_run(is_first_run_);

  extension_action_storage_manager_ =
      std::make_unique<ExtensionActionStorageManager>(profile_);

  SetCurrentDeveloperMode(
      util::GetBrowserContextId(profile),
      profile->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode));

  pref_change_registrar_.Init(profile_->GetPrefs());
  pref_change_registrar_.Add(
      prefs::kExtensionsUIDeveloperMode,
      base::BindRepeating(&ExtensionService::OnDeveloperModePrefChanged,
                          base::Unretained(this)));
}

base::WeakPtr<ExtensionServiceInterface> ExtensionService::AsWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

ExtensionService::~ExtensionService() {
#if !BUILDFLAG(IS_ANDROID)
  UpgradeDetector::GetInstance()->RemoveObserver(this);
#endif
}

void ExtensionService::Shutdown() {
  delayed_install_manager_ = nullptr;
  cws_info_service_observation_.Reset();
  extension_install_policy_observation_.Reset();
  ExtensionManagementFactory::GetForBrowserContext(profile())->RemoveObserver(
      this);
  external_install_manager_->Shutdown();
  corrupted_extension_reinstaller_ = nullptr;
  extension_registrar_->Shutdown();
  extension_registrar_delegate_->Shutdown();
  external_provider_manager_->Shutdown();
  pref_change_registrar_.Reset();
  weak_ptr_factory_.InvalidateWeakPtrs();
  // Avoid dangling pointers.
  command_line_ = nullptr;
  system_ = nullptr;
  extension_prefs_ = nullptr;
  blocklist_ = nullptr;
  registry_ = nullptr;
  pending_extension_manager_ = nullptr;
  external_provider_manager_ = nullptr;
  error_controller_ = nullptr;
  allowlist_ = nullptr;
  external_install_manager_ = nullptr;
  updater_ = nullptr;
  component_loader_ = nullptr;
  host_observation_.RemoveAllObservations();
  is_shut_down_executed_ = true;
}

void ExtensionService::Init() {
  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  TRACE_EVENT0("browser,startup", "ExtensionService::Init");

  DCHECK(!system_->is_ready());  // Can't redo init.
  DCHECK_EQ(registry_->enabled_extensions().size(), 0u);

  component_loader_->LoadAll();
  bool load_saved_extensions = true;
  bool load_command_line_extensions =
      extension_registrar_->extensions_enabled();
#if BUILDFLAG(IS_CHROMEOS)
  if (!ash::ProfileHelper::IsUserProfile(profile_)) {
    load_saved_extensions = false;
    load_command_line_extensions = false;
  }

  const bool load_autotest_ext =
      command_line_->HasSwitch(switches::kLoadSigninProfileTestExtension);
  const bool is_signin_profile = ash::ProfileHelper::IsSigninProfile(profile_);
  if (load_autotest_ext && is_signin_profile) {
    LoadSigninProfileTestExtension(command_line_->GetSwitchValueASCII(
        switches::kLoadSigninProfileTestExtension));
  }
#endif
  if (load_saved_extensions) {
    InstalledLoader(profile_).LoadAllExtensions();
  }

  CheckManagementPolicy();
  OnInstalledExtensionsLoaded();

  LoadExtensionsFromCommandLineFlag(switches::kDisableExtensionsExcept);
  if (load_command_line_extensions) {
    LoadExtensionsFromCommandLineFlag(switches::kLoadExtension);
  }
  EnabledReloadableExtensions();
  delayed_install_manager_->FinishInstallationsDelayedByShutdown();
  SetReadyAndNotifyListeners();

  extension_registrar_->UninstallMigratedExtensions(
      kObsoleteComponentExtensionIds);

  // TODO(erikkay): this should probably be deferred to a future point
  // rather than running immediately at startup.
  external_provider_manager_->CheckForExternalUpdates();

  LogExtensionsOnChromeUrlsSwitchWarningIfNeeded();

  safe_browsing_verdict_handler_.Init();

  // Must be called after extensions are loaded.
  allowlist_->Init();

  // Check for updates especially for corrupted user installed extension from
  // the webstore. This will do nothing if an extension update check was
  // triggered before and is still running.
  if (corrupted_extension_reinstaller_->HasAnyReinstallForCorruption()) {
    CheckForUpdatesSoon();
  }
}

void ExtensionService::EnabledReloadableExtensions() {
  TRACE_EVENT0("browser,startup",
               "ExtensionService::EnabledReloadableExtensions");
  extension_registrar_->EnabledReloadableExtensions();
}

void ExtensionService::LoadExtensionsFromCommandLineFlag(
    const char* switch_name) {
  CHECK(switch_name == switches::kLoadExtension ||
        switch_name == switches::kDisableExtensionsExcept);
  if (!command_line_->HasSwitch(switch_name)) {
    return;
  }

  // Check that --load-extension is allowed.
  if (switch_name == switches::kLoadExtension) {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && !BUILDFLAG(IS_CHROMEOS)
    LOG(WARNING)
        << "--load-extension is not allowed in Google Chrome, ignoring.";
    return;
#else   // BUILDFLAG(GOOGLE_CHROME_BRANDING) && !BUILDFLAG(IS_CHROMEOS)
    if (safe_browsing::IsEnhancedProtectionEnabled(*profile_->GetPrefs())) {
      VLOG(1) << "--load-extension is not allowed for users opted into "
              << "Enhanced Safe Browsing, ignoring.";
      return;
    }
    if (ShouldBlockCommandLineExtension(*profile_)) {
      // TODO(crbug.com/401529219): Deprecate this restriction once
      // --load-extension removal on Chrome builds is fully launched.
      VLOG(1)
          << "--load-extension is not allowed for users that have the policy "
          << "ExtensionInstallTypeBlocklist::command_line, ignoring.";
      return;
    }
#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING) && !BUILDFLAG(IS_CHROMEOS)
  } else if (base::FeatureList::IsEnabled(
                 extensions_features::
                     kDisableDisableExtensionsExceptCommandLineSwitch)) {
    DCHECK_EQ(switch_name, switches::kDisableExtensionsExcept);
    LOG(WARNING) << "--disable-extensions-except is not allowed in Google "
                    "Chrome, ignoring.";
    return;
  }

  base::CommandLine::StringType path_list =
      command_line_->GetSwitchValueNative(switch_name);
  base::StringTokenizerT<base::CommandLine::StringType,
                         base::CommandLine::StringType::const_iterator>
      t(path_list, FILE_PATH_LITERAL(","));
  while (t.GetNext()) {
    std::string extension_id;
    UnpackedInstaller::Create(profile_)->LoadFromCommandLine(
        base::FilePath(t.token_piece()), &extension_id,
        /*only-allow-apps=*/false);
    if (switch_name == switches::kDisableExtensionsExcept) {
      extension_registrar_->AddDisableFlagExemptedExtension(extension_id);
    }
  }

  base::UmaHistogramEnumeration(
      "Extensions.LoadingFromCommandLine",
      switch_name == switches::kLoadExtension
          ? ExtensionService::LoadExtensionFlag::kLoadExtension
          : ExtensionService::LoadExtensionFlag::kDisableExtensionsExcept);
}

#if BUILDFLAG(IS_CHROMEOS)
void ExtensionService::LoadSigninProfileTestExtension(const std::string& path) {
  base::SysInfo::CrashIfChromeOSNonTestImage();
  std::string extension_id;
  const bool installing =
      UnpackedInstaller::Create(profile_)->LoadFromCommandLine(
          base::FilePath(path), &extension_id, false /*only-allow-apps*/);
  CHECK(installing);
  CHECK_EQ(extension_id, extension_misc::kSigninProfileTestExtensionId)
      << extension_id
      << " extension not allowed to load from the command line in the "
         "signin profile";
}
#endif

void ExtensionService::PerformActionBasedOnOmahaAttributes(
    const std::string& extension_id,
    const base::DictValue& attributes) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  omaha_attributes_handler_.PerformActionBasedOnOmahaAttributes(extension_id,
                                                                attributes);
  allowlist_->PerformActionBasedOnOmahaAttributes(extension_id, attributes);
  // Show an error for the newly blocklisted extension.
  error_controller_->ShowErrorIfNeeded();
}

void ExtensionService::PerformActionBasedOnExtensionTelemetryServiceVerdicts(
    const Blocklist::BlocklistStateMap& blocklist_state_map) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  extension_telemetry_service_verdict_handler_.PerformActionBasedOnVerdicts(
      blocklist_state_map);
  error_controller_->ShowErrorIfNeeded();
}

void ExtensionService::DisableUserExtensionsExcept(
    const std::vector<std::string>& except_ids) {
  ManagementPolicy* management_policy = system_->management_policy();
  ExtensionList to_disable;

  for (const auto& extension : registry_->enabled_extensions()) {
    if (management_policy->UserMayModifySettings(extension.get(), nullptr)) {
      to_disable.push_back(extension);
    }
  }

  for (const auto& extension : registry_->terminated_extensions()) {
    if (management_policy->UserMayModifySettings(extension.get(), nullptr)) {
      to_disable.push_back(extension);
    }
  }

  ExtensionManagement* extension_management =
      ExtensionManagementFactory::GetForBrowserContext(profile());
  for (const auto& extension : to_disable) {
    if (extension->was_installed_by_default() &&
        extension_management->UpdatesFromWebstore(*extension)) {
      continue;
    }
    const std::string& id = extension->id();
    if (!std::ranges::contains(except_ids, id)) {
      extension_registrar_->DisableExtension(
          id, {disable_reason::DISABLE_USER_ACTION});
    }
  }
}

content::BrowserContext* ExtensionService::GetBrowserContext() const {
  // Implemented in the .cc file to avoid adding a profile.h dependency to
  // extension_service.h.
  return profile_;
}

void ExtensionService::CheckManagementPolicy() {
  std::map<std::string, disable_reason::DisableReason> to_disable;
  std::vector<std::string> to_enable;

  // Loop through the extensions list, finding extensions we need to disable.
  for (const auto& extension : registry_->enabled_extensions()) {
    disable_reason::DisableReason disable_reason = disable_reason::DISABLE_NONE;
    if (system_->management_policy()->MustRemainDisabled(extension.get(),
                                                         &disable_reason)) {
      DCHECK_NE(disable_reason, disable_reason::DISABLE_NONE);
      to_disable[extension->id()] = disable_reason;
    }
  }

  ExtensionManagement* management =
      ExtensionManagementFactory::GetForBrowserContext(profile());

  PermissionsUpdater(profile()).SetDefaultPolicyHostRestrictions(
      management->GetDefaultPolicyBlockedHosts(),
      management->GetDefaultPolicyAllowedHosts());

  for (const auto& extension : registry_->enabled_extensions()) {
    PermissionsUpdater(profile()).ApplyPolicyHostRestrictions(*extension);
  }

  ManifestV2Handler* mv2_handler = ManifestV2Handler::Get(profile_);

  // Loop through the disabled extension list, find extensions to re-enable
  // automatically. These extensions are exclusive from the |to_disable| list
  // constructed above, since disabled_extensions() and enabled_extensions() are
  // supposed to be mutually exclusive.
  for (const auto& extension : registry_->disabled_extensions()) {
    DisableReasonSet to_add;
    DisableReasonSet to_remove;

    // Find all extensions disabled due to minimum version requirement and
    // management policy but now satisfying it.
    if (management->CheckMinimumVersion(extension.get(), nullptr)) {
      to_remove.insert(disable_reason::DISABLE_UPDATE_REQUIRED_BY_POLICY);
    }

    // Check published-in-store status against policy requirement and update
    // the disable reasons accordingly.
    if (management->IsAllowedByUnpublishedAvailabilityPolicy(extension.get())) {
      to_remove.insert(
          disable_reason::DISABLE_PUBLISHED_IN_STORE_REQUIRED_BY_POLICY);
    } else {
      to_add.insert(
          disable_reason::DISABLE_PUBLISHED_IN_STORE_REQUIRED_BY_POLICY);
    }

    if (management->IsAllowedByUnpackedDeveloperModePolicy(*extension)) {
      to_remove.insert(disable_reason::DISABLE_UNSUPPORTED_DEVELOPER_EXTENSION);
    } else {
      to_add.insert(disable_reason::DISABLE_UNSUPPORTED_DEVELOPER_EXTENSION);
    }

    // Check if the `DISABLE_NOT_VERIFIED` reason is still applicable. This
    // disable reason is used in multiple disabling flows (e.g., by
    // InstallVerifier and StandardManagementPolicyProvider). Only clear the
    // disable reason if none of these flows require the extension to be
    // disabled.
    // TODO(crbug.com/362756477): Refactor duplicated disable reason logic
    // between CheckManagementPolicy() and policy providers.
    disable_reason::DisableReason install_verifier_disable_reason =
        disable_reason::DISABLE_NONE;
    InstallVerifierFactory::GetForBrowserContext(GetBrowserContext())
        ->MustRemainDisabled(extension.get(), &install_verifier_disable_reason);
    if (install_verifier_disable_reason == disable_reason::DISABLE_NONE &&
        !management->ShouldBlockForceInstalledOffstoreExtension(*extension)) {
      to_remove.insert(disable_reason::DISABLE_NOT_VERIFIED);
    }

    if (!system_->management_policy()->MustRemainDisabled(extension.get(),
                                                          nullptr)) {
      to_remove.insert(disable_reason::DISABLE_BLOCKED_BY_POLICY);
    }

    // Note: `mv2_handler` may be null for certain types of profiles
    // (such as the sign-in profile). We can ignore this check in this case,
    // since users can't install extensions in these profiles.
    // TODO(https://crbug.com/362756477): As above, this is effectively
    // fragmenting logic between the policy provider and here to ensure that
    // the extension gets properly re-enabled when appropriate.
    if (mv2_handler && !mv2_handler->ShouldBlockExtensionEnable(*extension)) {
      to_remove.insert(disable_reason::DISABLE_UNSUPPORTED_MANIFEST_VERSION);
    }

    // If this profile is not supervised, then remove any supervised user
    // related disable reasons.
    bool is_supervised = profile() && profile()->IsChild();
    if (!is_supervised) {
      to_remove.insert(disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED);
    }

    if (system_->management_policy()->MustRemainEnabled(extension.get(),
                                                        nullptr)) {
      // Extensions installed from the Windows Registry should re-enable when
      // they become force-installed. Normally this is handled in
      // OnExternalExtensionUpdateUrlFound(), but already-broken browsers (from
      // previous Chromium versions) also need to be fixed here.
      //
      // TODO(crbug.com/40144051): This won't be needed after a few milestones.
      // It should be safe to remove in M107.
      to_remove.insert(disable_reason::DISABLE_EXTERNAL_EXTENSION);
    }

    DisableReasonSet shared_disable_reasons =
        base::STLSetIntersection<DisableReasonSet>(to_remove, to_add);
    CHECK(shared_disable_reasons.empty())
        << "Found a disable reason in both `to_add` and `to_remove`: "
        << static_cast<int>(*shared_disable_reasons.begin());

    if (!to_add.empty()) {
      extension_prefs_->AddDisableReasons(extension->id(), to_add);
    }

    if (!to_remove.empty()) {
      extension_prefs_->RemoveDisableReasons(extension->id(), to_remove);
    }

    if (extension_prefs_->GetDisableReasons(extension->id()).empty()) {
      to_enable.push_back(extension->id());
    }
  }

  for (const auto& i : to_disable) {
    extension_registrar_->DisableExtension(i.first, {i.second});
  }

  // No extension is getting re-enabled here after disabling because |to_enable|
  // is mutually exclusive to |to_disable|.
  for (const std::string& id : to_enable) {
    extension_registrar_->EnableExtension(id);
  }

  if (updater_ && updater_->enabled()) {
    // Find all extensions disabled due to minimum version requirement from
    // policy (including the ones that got disabled just now), and check
    // for update.
    ExtensionUpdater::CheckParams to_recheck;
    for (const auto& extension : registry_->disabled_extensions()) {
      if (extension_prefs_->HasOnlyDisableReason(
              extension->id(),
              disable_reason::DISABLE_UPDATE_REQUIRED_BY_POLICY)) {
        // The minimum version check is the only thing holding this extension
        // back, so check if it can be updated to fix that.
        to_recheck.ids.push_back(extension->id());
      }
    }
    if (!to_recheck.ids.empty()) {
      updater_->CheckNow(std::move(to_recheck));
    }
  }

  // Check the disabled extensions to see if any should be force uninstalled.
  std::vector<ExtensionId> remove_list;
  for (const auto& extension : registry_->disabled_extensions()) {
    if (system_->management_policy()->ShouldForceUninstall(extension.get(),
                                                           nullptr /*error*/)) {
      remove_list.push_back(extension->id());
    }
  }
  for (const auto& extension_id : remove_list) {
    std::u16string error;
    if (!extension_registrar_->UninstallExtension(
            extension_id, UNINSTALL_REASON_INTERNAL_MANAGEMENT, &error)) {
      SYSLOG(WARNING) << "Extension with id " << extension_id
                      << " failed to be uninstalled via policy: " << error;
    }
  }
}

void ExtensionService::CheckForUpdatesSoon() {
  // This can legitimately happen in unit tests.
  if (!updater_ || !updater_->enabled()) {
    return;
  }

  // Avoid scheduling a redundant check if a full update check is in progress.
  if (updater_->HasFullCheckInProgress()) {
    return;
  }

  updater_->CheckSoon();
}

void ExtensionService::UnloadAllExtensionsForTest() {
  UnloadAllExtensionsInternal();
}

void ExtensionService::ReloadExtensionsForTest() {
  // Calling UnloadAllExtensionsForTest here triggers a false-positive presubmit
  // warning about calling test code in production.
  UnloadAllExtensionsInternal();
  component_loader_->LoadAll();
  InstalledLoader(profile_).LoadAllExtensions();
  OnInstalledExtensionsLoaded();
  // Don't call SetReadyAndNotifyListeners() since tests call this multiple
  // times.
}

void ExtensionService::UninstallMigratedExtensionsForTest() {
  extension_registrar_->UninstallMigratedExtensions(
      kObsoleteComponentExtensionIds);
}

void ExtensionService::SetReadyAndNotifyListeners() {
  TRACE_EVENT0("browser,startup",
               "ExtensionService::SetReadyAndNotifyListeners");
  ready_->Signal();
}

void ExtensionService::OnExtensionManagementSettingsChanged() {
  error_controller_->ShowErrorIfNeeded();

  // Revokes blocked permissions from active_permissions for all extensions.
  ExtensionManagement* settings =
      ExtensionManagementFactory::GetForBrowserContext(profile());
  CHECK(settings);
  const ExtensionSet all_extensions =
      registry_->GenerateInstalledExtensionsSet();
  for (const auto& extension : all_extensions) {
    if (!settings->IsPermissionSetAllowed(
            extension.get(),
            extension->permissions_data()->active_permissions()) &&
        extension_registrar_->CanBlockExtension(extension.get())) {
      PermissionsUpdater(profile()).RemovePermissionsUnsafe(
          extension.get(), *settings->GetBlockedPermissions(extension.get()));
    }
  }

  CheckManagementPolicy();

  // Request an out-of-cycle update of extension metadata information from CWS
  // if the ExtensionUnpublishedAvailability policy setting is such that
  // unpublished extensions should not be enabled. This update allows
  // unpublished extensions to be disabled sooner rather than waiting till the
  // next regularly scheduled fetch.
  if (profile_->GetPrefs()->GetInteger(
          pref_names::kExtensionUnpublishedAvailability) !=
      kAllowUnpublishedExtensions) {
    CWSInfoServiceFactory::GetForProfile(profile_)->CheckAndMaybeFetchInfo();
  }
}

void ExtensionService::OnExtensionInstallPolicyUpdated() {
  CheckManagementPolicy();
}

bool ExtensionService::FinishDelayedInstallationIfReady(
    const std::string& extension_id,
    bool install_immediately) {
  return delayed_install_manager_->FinishDelayedInstallationIfReady(
      extension_id, install_immediately);
}

const Extension* ExtensionService::GetPendingExtensionUpdate(
    const std::string& id) const {
  return delayed_install_manager_->GetPendingExtensionUpdate(id);
}

void ExtensionService::OnExtensionHostRenderProcessGone(
    content::BrowserContext* browser_context,
    ExtensionHost* extension_host) {
  DCHECK(
      profile_->IsSameOrParent(Profile::FromBrowserContext(browser_context)));

  // Mark the extension as terminated and deactivated. We want it to
  // be in a consistent state: either fully working or not loaded
  // at all, but never half-crashed.  We do it in a PostTask so
  // that other handlers of this notification will still have
  // access to the Extension and ExtensionHost.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ExtensionRegistrar::TerminateExtension,
                                extension_registrar_->GetWeakPtr(),
                                extension_host->extension_id()));
}

void ExtensionService::OnRenderProcessHostCreated(
    content::RenderProcessHost* host) {
  if (!host_observation_.IsObservingSource(host)) {
    host_observation_.AddObservation(host);
  }
}

void ExtensionService::RenderProcessHostDestroyed(
    content::RenderProcessHost* host) {
  // If you hit this from a KeyedService you might be missing a DependsOn()
  // for ChromeExtensionSystemFactory.
  CHECK(registry_) << "ExtensionService used after Shutdown()";

  host_observation_.RemoveObservation(host);

  Profile* host_profile =
      Profile::FromBrowserContext(host->GetBrowserContext());
  if (!profile_->IsSameOrParent(host_profile->GetOriginalProfile())) {
    return;
  }

  ProcessMap* process_map = ProcessMap::Get(profile_);

  // An extension process was terminated, this might have resulted in an
  // app or extension becoming idle.
  if (std::optional<std::string> extension_id =
          process_map->GetExtensionIdForProcess(host->GetID())) {
    // The extension running in this process might also be referencing a shared
    // module which is waiting for idle to update. Check all imports of this
    // extension too.
    std::set<std::string> affected_ids;
    affected_ids.insert(*extension_id);

    if (auto* extension = registry_->GetExtensionById(
            *extension_id, ExtensionRegistry::EVERYTHING)) {
      const std::vector<SharedModuleInfo::ImportInfo>& imports =
          SharedModuleInfo::GetImports(extension);
      for (const auto& import_info : imports) {
        affected_ids.insert(import_info.extension_id);
      }
    }

    for (const auto& id : affected_ids) {
      if (delayed_install_manager_->Contains(id)) {
        base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
            FROM_HERE,
            base::BindOnce(
                base::IgnoreResult(
                    &ExtensionService::FinishDelayedInstallationIfReady),
                AsExtensionServiceWeakPtr(), id, false /*install_immediately*/),
            kUpdateIdleDelay);
      }
    }
  }
  process_map->Remove(host->GetID());
}

void ExtensionService::OnBlocklistUpdated() {
  blocklist_->GetBlocklistedIDs(
      registry_->GenerateInstalledExtensionsSet().GetIDs(),
      base::BindOnce(&ExtensionService::ManageBlocklist,
                     AsExtensionServiceWeakPtr()));
}

void ExtensionService::OnCWSInfoChanged() {
  CheckManagementPolicy();
}

void ExtensionService::OnUpgradeRecommended() {
  // Notify observers that chrome update is available.
  ExtensionUpdater::Get(profile_)->NotifyChromeUpdateAvailable();
}

void ExtensionService::OnProfileMarkedForPermanentDeletion(Profile* profile) {
  if (profile != profile_) {
    return;
  }

  ExtensionIdSet ids_to_unload = registry_->enabled_extensions().GetIDs();
  for (const auto& id : ids_to_unload) {
    extension_registrar_->RemoveExtension(
        id, UnloadedExtensionReason::PROFILE_SHUTDOWN);
  }
}

void ExtensionService::ManageBlocklist(
    const Blocklist::BlocklistStateMap& state_map) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  safe_browsing_verdict_handler_.ManageBlocklist(state_map);
  error_controller_->ShowErrorIfNeeded();
}

bool ExtensionService::UserCanDisableInstalledExtension(
    const std::string& extension_id) {
  const Extension* extension = registry_->GetInstalledExtension(extension_id);
  return extension_registrar_delegate_->CanDisableExtension(extension);
}

// Used only by test code.
void ExtensionService::UnloadAllExtensionsInternal() {
  profile_->GetExtensionSpecialStoragePolicy()->RevokeRightsForAllExtensions();

  const ExtensionSet extensions = registry_->GenerateInstalledExtensionsSet(
      ExtensionRegistry::ENABLED | ExtensionRegistry::DISABLED |
      ExtensionRegistry::TERMINATED);

  for (const auto& extension : extensions) {
    extension_registrar_->RemoveExtension(extension->id(),
                                          UnloadedExtensionReason::UNINSTALL);
  }

  // TODO(erikkay) should there be a notification for this?  We can't use
  // EXTENSION_UNLOADED since that implies that the extension has
  // been disabled or uninstalled.
}

void ExtensionService::OnInstalledExtensionsLoaded() {
  if (updater_ && updater_->enabled()) {
    updater_->Start();
  }

  // Enable any Shared Modules that incorrectly got disabled previously.
  // This is temporary code to fix incorrect behavior from previous versions of
  // Chrome and can be removed after several releases (perhaps M60).
  ExtensionList to_enable;
  for (const auto& extension : registry_->disabled_extensions()) {
    if (SharedModuleInfo::IsSharedModule(extension.get())) {
      to_enable.push_back(extension);
    }
  }
  for (const auto& extension : to_enable) {
    extension_registrar_->EnableExtension(extension->id());
  }

  // Check installed extensions against the blocklist if and only if the
  // database is ready; otherwise, the database is effectively empty and we'll
  // re-enable all blocked extensions.

  blocklist_->IsDatabaseReady(base::BindOnce(
      [](base::WeakPtr<ExtensionService> service, bool is_ready) {
        DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
        if (!service || !is_ready) {
          // Either the service was torn down or the database isn't
          // ready yet (and is effectively empty). Either way, no need
          // to update the blocklisted extensions.
          return;
        }
        service->OnBlocklistUpdated();
      },
      AsExtensionServiceWeakPtr()));

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
  PolicyDseNtpOverrideMetricsReporter::ReportMetrics(profile_);
#endif
}

void ExtensionService::OnDeveloperModePrefChanged() {
  CheckManagementPolicy();
}

void ExtensionService::LogExtensionsOnChromeUrlsSwitchWarningIfNeeded() {
  bool allow_on_chrome_urls = base::CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kExtensionsOnChromeURLs);

  if (allow_on_chrome_urls &&
      base::FeatureList::IsEnabled(
          extensions_features::kDisableExtensionsOnChromeUrlsSwitch)) {
    LOG(WARNING) << "--extensions-on-chrome-urls is not allowed in Google "
                    "Chrome, ignoring. "
                    "Use --extensions-on-extension-urls instead to allow for "
                    "extensions to run on extension URLs.";
  }
}

}  // namespace extensions
