// Copyright 2012 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/installed_loader.h"

#include <stddef.h>

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notimplemented.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/corrupted_extension_reinstaller.h"
#include "chrome/browser/extensions/extension_allowlist_factory.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/profile_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/webui_url_constants.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/allowlist_state.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_allowlist.h"
#include "extensions/browser/extension_prefs.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/install_prefs_helper.h"
#include "extensions/browser/load_error_reporter.h"
#include "extensions/browser/management_policy.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/browser/pref_types.h"
#include "extensions/browser/ui_util.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_l10n_util.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/features/feature_developer_mode_only.h"
#include "extensions/common/file_util.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/manifest_handlers/chrome_url_overrides_handler.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/permission_message_provider.h"
#include "extensions/common/permissions/permissions_data.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "components/user_manager/user.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

using content::BrowserThread;

namespace extensions {

namespace {

// Used in histogram Extensions.BackgroundPageType.
enum class BackgroundPageType {
  kNone = 0,
  kPersistent,
  kEventPage,
  kServiceWorker,

  // New enum values must go above here.
  kMaxValue = kServiceWorker
};

// Used in histogram Extensions.ExternalItemState.
enum class ExternalItemState {
  kDeprecated_Disabled = 0,
  kDeprecated_Enabled,
  kWebstoreDisabled,
  kWebstoreEnabled,
  kNonwebstoreDisabled,
  kNonwebstoreEnabled,
  kWebstoreUninstalled,
  kNonwebstoreUninstalled,

  // New enum values must go above here.
  kMaxValue = kNonwebstoreUninstalled
};

bool IsManifestCorrupt(const base::DictValue& manifest) {
  // Because of bug #272524 sometimes manifests got mangled in the preferences
  // file, one particularly bad case resulting in having both a background page
  // and background scripts values. In those situations we want to reload the
  // manifest from the extension to fix this.
  return manifest.contains(manifest_keys::kBackgroundPage) &&
         manifest.contains(manifest_keys::kBackgroundScripts);
}

bool ShouldReloadExtensionManifest(const ExtensionPrefs::InstallRecord& info) {
  // Always reload manifests of unpacked extensions, because they can change
  // on disk independent of the manifest in our prefs.
  if (Manifest::IsUnpackedLocation(info.extension_location)) {
    return true;
  }

  if (!info.extension_manifest) {
    return false;
  }

  // Reload the manifest if it needs to be relocalized.
  if (extension_l10n_util::ShouldRelocalizeManifest(*info.extension_manifest)) {
    return true;
  }

  // Reload if the copy of the manifest in the preferences is corrupt.
  if (IsManifestCorrupt(*info.extension_manifest)) {
    return true;
  }

  return false;
}

BackgroundPageType GetBackgroundPageType(const Extension* extension) {
  if (!BackgroundInfo::HasBackgroundPage(extension)) {
    return BackgroundPageType::kNone;
  }
  if (BackgroundInfo::HasPersistentBackgroundPage(extension)) {
    return BackgroundPageType::kPersistent;
  }
  if (BackgroundInfo::IsServiceWorkerBased(extension)) {
    return BackgroundPageType::kServiceWorker;
  }
  return BackgroundPageType::kEventPage;
}

// Helper to record a single disable reason histogram value (see
// RecordDisableReasons below).
void RecordDisbleReasonHistogram(int reason) {
  base::UmaHistogramSparse("Extensions.DisableReason2", reason);
}

// Records the disable reasons for a single extension grouped by
// disable_reason::DisableReason.
void RecordDisableReasons(const DisableReasonSet& reasons) {
  // |reasons| is a bitmask with values from ExtensionDisabledReason
  // which are increasing powers of 2.
  if (reasons.empty()) {
    RecordDisbleReasonHistogram(disable_reason::DISABLE_NONE);
    return;
  }
  for (int reason : reasons) {
    RecordDisbleReasonHistogram(reason);
  }
}

// Returns the current access level for the given `extension`.
HostPermissionsAccess GetHostPermissionAccessLevelForExtension(
    const Extension& extension) {
  if (!util::CanWithholdPermissionsFromExtension(extension)) {
    return HostPermissionsAccess::kCannotAffect;
  }

  bool has_active_hosts = !extension.permissions_data()
                               ->active_permissions()
                               .effective_hosts()
                               .is_empty();
  size_t active_hosts_size = extension.permissions_data()
                                 ->active_permissions()
                                 .effective_hosts()
                                 .size();
  bool has_withheld_hosts = !extension.permissions_data()
                                 ->withheld_permissions()
                                 .effective_hosts()
                                 .is_empty();

  if (!has_active_hosts && !has_withheld_hosts) {
    // No hosts are granted or withheld, so none were requested.
    // Check if the extension is using activeTab.
    return extension.permissions_data()->HasAPIPermission(
               mojom::APIPermissionID::kActiveTab)
               ? HostPermissionsAccess::kOnActiveTabOnly
               : HostPermissionsAccess::kNotRequested;
  }

  if (!has_withheld_hosts) {
    // No hosts were withheld; the extension is running all requested sites.
    return HostPermissionsAccess::kOnAllRequestedSites;
  }

  // The extension is running automatically on some of the requested sites.
  // <all_urls> (strangely) includes the chrome://favicon/ permission. Thus,
  // we avoid counting the favicon pattern in the active hosts.
  if (active_hosts_size > 1) {
    return HostPermissionsAccess::kOnSpecificSites;
  }
  if (active_hosts_size == 1) {
    const URLPattern& single_pattern = *extension.permissions_data()
                                            ->active_permissions()
                                            .effective_hosts()
                                            .begin();
    if (single_pattern.scheme() != content::kChromeUIScheme ||
        single_pattern.host() != chrome::kChromeUIFaviconHost) {
      return HostPermissionsAccess::kOnSpecificSites;
    }
  }

  // The extension is not running automatically anywhere. All its hosts were
  // withheld.
  return HostPermissionsAccess::kOnClick;
}

// Emits metrics for the host permissions access that an extension has. Meant to
// be called only for profiles where users can install extensions, specifically
// profiles that can have non-component extensions installed.
void LogHostPermissionsAccess(const Extension& extension) {
  HostPermissionsAccess access_level =
      GetHostPermissionAccessLevelForExtension(extension);
  // Extensions.HostPermissions.GrantedAccess is emitted for every extension.
  base::UmaHistogramEnumeration("Extensions.HostPermissions.GrantedAccess2",
                                access_level);

  const PermissionSet& active_permissions =
      extension.permissions_data()->active_permissions();
  const PermissionSet& withheld_permissions =
      extension.permissions_data()->withheld_permissions();

  // Since we only care about host permissions here, we don't want to look at
  // API permissions that might cause Chrome to warn about all hosts (like
  // debugger or devtools).
  static constexpr bool kIncludeApiPermissions = false;
  if (active_permissions.ShouldWarnAllHosts(kIncludeApiPermissions) ||
      withheld_permissions.ShouldWarnAllHosts(kIncludeApiPermissions)) {
    // Extension requests access to at least one eTLD.
    base::UmaHistogramEnumeration(
        "Extensions.HostPermissions.GrantedAccessForBroadRequests2",
        access_level);
  } else if (!active_permissions.effective_hosts().is_empty() ||
             !withheld_permissions.effective_hosts().is_empty()) {
    // Extension requests access to hosts, but not eTLD.
    base::UmaHistogramEnumeration(
        "Extensions.HostPermissions.GrantedAccessForTargetedRequests2",
        access_level);
  }
}

}  // namespace

InstalledLoader::InstalledLoader(Profile* profile)
    : profile_(profile),
      extension_registry_(ExtensionRegistry::Get(profile_)),
      extension_prefs_(ExtensionPrefs::Get(profile_)),
      extension_management_(
          ExtensionManagementFactory::GetForBrowserContext(profile_)) {}

InstalledLoader::~InstalledLoader() = default;

void InstalledLoader::Load(const ExtensionPrefs::InstallRecord& info,
                           bool write_to_prefs) {
  // TODO(asargent): add a test to confirm that we can't load extensions if
  // their ID in preferences does not match the extension's actual ID.
  if (invalid_extensions_.find(info.extension_path) !=
      invalid_extensions_.end())
    return;

  scoped_refptr<const Extension> extension;
  std::u16string error;
  if (info.extension_manifest) {
    extension = Extension::Create(info.extension_path, info.extension_location,
                                  *info.extension_manifest,
                                  GetCreationFlags(&info), &error);
  } else {
    error = base::UTF8ToUTF16(
        std::string_view(manifest_errors::kManifestUnreadable));
  }

  // Once installed, non-unpacked extensions cannot change their IDs (e.g., by
  // updating the 'key' field in their manifest).
  // TODO(jstritar): migrate preferences when unpacked extensions change IDs.
  if (extension.get() && !Manifest::IsUnpackedLocation(extension->location()) &&
      info.extension_id != extension->id()) {
    error = base::UTF8ToUTF16(
        std::string_view(manifest_errors::kCannotChangeExtensionID));
    extension = nullptr;
  }

  if (!extension.get()) {
    LoadErrorReporter::GetInstance()->ReportLoadError(info.extension_path,
                                                      error, profile_,
                                                      false);  // Be quiet.
    return;
  }

  const ManagementPolicy* policy =
      ExtensionSystem::Get(profile_)->management_policy();

  if (extension_prefs_->IsExtensionDisabled(extension->id())) {
    DisableReasonSet disable_reasons =
        extension_prefs_->GetDisableReasons(extension->id());

    // Update the extension prefs to reflect if the extension is no longer
    // blocked due to admin policy.
    if (disable_reasons.contains(disable_reason::DISABLE_BLOCKED_BY_POLICY) &&
        !policy->MustRemainDisabled(extension.get(), nullptr)) {
      disable_reasons.erase(disable_reason::DISABLE_BLOCKED_BY_POLICY);
      extension_prefs_->RemoveDisableReason(
          extension->id(), disable_reason::DISABLE_BLOCKED_BY_POLICY);
    }

    if ((disable_reasons.contains(disable_reason::DISABLE_CORRUPTED))) {
      HandleCorruptExtension(*extension, *policy);
    }
  } else {
    // Extension is enabled. Check management policy to verify if it should
    // remain so.
    disable_reason::DisableReason disable_reason = disable_reason::DISABLE_NONE;
    if (policy->MustRemainDisabled(extension.get(), &disable_reason)) {
      DCHECK_NE(disable_reason, disable_reason::DISABLE_NONE);
      extension_prefs_->AddDisableReason(extension->id(), disable_reason);
    }
  }

  if (write_to_prefs) {
    extension_prefs_->UpdateManifest(extension.get());
  }

  ExtensionRegistrar::Get(profile_)->AddExtension(extension.get());
}

void InstalledLoader::LoadAllExtensions() {
  LoadAllExtensions(profile_);
}

void InstalledLoader::LoadAllExtensions(Profile* profile) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  TRACE_EVENT("browser,startup", "InstalledLoader::LoadAllExtensions");

  bool is_user_profile =
      profile_util::ProfileCanUseNonComponentExtensions(profile);
  const base::TimeTicks load_start_time = base::TimeTicks::Now();

  ExtensionPrefs::InstallRecords extensions_info =
      extension_prefs_->GetInstalledExtensionsInfo();

  bool should_write_prefs = false;

  for (auto& info : extensions_info) {
    // Skip extensions that were loaded from the command-line because we don't
    // want those to persist across browser restart.
    if (info.extension_location == mojom::ManifestLocation::kCommandLine) {
      continue;
    }

    if (ShouldReloadExtensionManifest(info)) {
      TRACE_EVENT("browser,startup", "Reload Extension Manifest");

      // Reloading an extension reads files from disk.  We do this on the
      // UI thread because reloads should be very rare, and the complexity
      // added by delaying the time when the extensions service knows about
      // all extensions is significant.  See crbug.com/40366546 for details.
      // |allow_blocking| disables tests that file operations run on the file
      // thread.
      base::ScopedAllowBlocking allow_blocking;

      std::u16string error;
      scoped_refptr<const Extension> extension(
          file_util::LoadExtension(info.extension_path, info.extension_location,
                                   GetCreationFlags(&info), &error));

      if (!extension.get() || extension->id() != info.extension_id) {
        invalid_extensions_.insert(info.extension_path);
        LoadErrorReporter::GetInstance()->ReportLoadError(info.extension_path,
                                                          error, profile,
                                                          false);  // Be quiet.
        continue;
      }

      info.extension_manifest = std::make_unique<base::DictValue>(
          extension->manifest()->value()->Clone());
      should_write_prefs = true;
    }
  }

  for (const auto& info : extensions_info) {
    if (info.extension_location != mojom::ManifestLocation::kCommandLine) {
      TRACE_EVENT("browser,startup", "Load Extension");
      Load(info, should_write_prefs);
    }
  }

  const base::TimeDelta load_all_time =
      base::TimeTicks::Now() - load_start_time;
  if (is_user_profile) {
    UMA_HISTOGRAM_COUNTS_100("Extensions.LoadAll2",
                             extension_registry_->enabled_extensions().size());
    UMA_HISTOGRAM_COUNTS_100("Extensions.Disabled2",
                             extension_registry_->disabled_extensions().size());
    UMA_HISTOGRAM_TIMES("Extensions.LoadAllTime2.User", load_all_time);
    RecordExtensionsMetrics(profile);
  } else {
    UMA_HISTOGRAM_TIMES("Extensions.LoadAllTime2.NonUser", load_all_time);
  }
}

// static
void InstalledLoader::RecordPermissionMessagesHistogram(
    const Extension* extension,
    const char* histogram_basename,
    Profile* profile) {
  DCHECK(profile_util::ProfileCanUseNonComponentExtensions(profile));

  PermissionIDSet permissions =
      PermissionMessageProvider::Get()->GetAllPermissionIDs(
          extension->permissions_data()->active_permissions(),
          extension->GetType());

  base::UmaHistogramBoolean(
      base::StringPrintf("Extensions.HasPermissions_%s4", histogram_basename),
      !permissions.empty());

  std::string permissions_histogram_name_incremented =
      base::StringPrintf("Extensions.Permissions_%s4", histogram_basename);
  for (const PermissionID& id : permissions) {
    base::UmaHistogramEnumeration(permissions_histogram_name_incremented,
                                  id.id());
  }
}

void InstalledLoader::RecordExtensionsMetricsForTesting() {
  RecordExtensionsMetrics(profile_);
}

void InstalledLoader::RecordExtensionsIncrementedMetricsForTesting(
    Profile* profile) {
  LoadAllExtensions(profile);
}

// TODO(crbug.com/40739895): Separate out Webstore/Offstore metrics.
void InstalledLoader::RecordExtensionsMetrics(Profile* profile) {
  TRACE_EVENT("browser,startup", "RecordExtensionsMetrics");
  DCHECK(profile_util::ProfileCanUseNonComponentExtensions(profile));

  int app_user_count = 0;
  int app_external_count = 0;
  int hosted_app_count = 0;
  int legacy_packaged_app_count = 0;
  int platform_app_count = 0;
  int extension_user_count = 0;
  int extension_external_count = 0;
  int theme_count = 0;
  int browser_action_count = 0;
  int no_action_count = 0;
  int disabled_for_permissions_count = 0;
  int non_webstore_ntp_override_count = 0;
  int ntp_override_count = 0;
  int homepage_override_count = 0;
  int search_engine_override_count = 0;
  int incognito_allowed_count = 0;
  int incognito_not_allowed_count = 0;
  int file_access_allowed_count = 0;
  int file_access_not_allowed_count = 0;
  int off_store_item_count = 0;
  int web_request_blocking_count = 0;
  int web_request_count = 0;
  int enabled_not_allowlisted_count = 0;
  int disabled_not_allowlisted_count = 0;
  int native_messaging_count = 0;

  struct ManifestVersion2And3Counts {
    int version_2_count = 0;
    int version_3_count = 0;
  };

  ManifestVersion2And3Counts internal_manifest_version_counts;
  ManifestVersion2And3Counts external_manifest_version_counts;
  ManifestVersion2And3Counts policy_manifest_version_counts;
  ManifestVersion2And3Counts component_manifest_version_counts;
  ManifestVersion2And3Counts unpacked_manifest_version_counts;

  bool dev_mode_enabled =
      GetCurrentDeveloperMode(util::GetBrowserContextId(profile));

  const ExtensionSet& extensions = extension_registry_->enabled_extensions();
  for (ExtensionSet::const_iterator iter = extensions.begin();
       iter != extensions.end();
       ++iter) {
    const Extension* extension = iter->get();
    mojom::ManifestLocation location = extension->location();
    Manifest::Type type = extension->GetType();

    // For the first few metrics, include all extensions and apps (component,
    // unpacked, etc). It's good to know these locations, and it doesn't
    // muck up any of the stats. Later, though, we want to omit component and
    // unpacked, as they are less interesting.

    if (extension->is_app()) {
      UMA_HISTOGRAM_ENUMERATION("Extensions.AppLocation2", location);
    } else if (extension->is_extension()) {
      UMA_HISTOGRAM_ENUMERATION("Extensions.ExtensionLocation2", location);
    }

    if (!UpdatesFromWebstore(*extension)) {
      UMA_HISTOGRAM_ENUMERATION("Extensions.NonWebstoreLocation2", location);

      // Check for inconsistencies if the extension was supposedly installed
      // from the webstore.
      enum {
        kBadUpdateUrl = 0,
        // This value was a mistake. Turns out sideloaded extensions can
        // have the from_webstore bit if they update from the webstore.
        kDeprecatedIsExternal = 1,
      };
      if (extension->from_webstore()) {
        UMA_HISTOGRAM_ENUMERATION("Extensions.FromWebstoreInconsistency2",
                                  kBadUpdateUrl, 2);
      } else {
        // Record enabled non-webstore extensions based on developer mode
        // status.
        if (dev_mode_enabled) {
          base::UmaHistogramEnumeration(
              "Extensions.NonWebstoreLocationWithDeveloperModeOn.Enabled3",
              location);
        } else {
          base::UmaHistogramEnumeration(
              "Extensions.NonWebstoreLocationWithDeveloperModeOff.Enabled3",
              location);
        }
      }
    }

    base::UmaHistogramBoolean("Extensions.DeveloperModeEnabled",
                              dev_mode_enabled);

    if (Manifest::IsExternalLocation(location)) {
      // See loop below for DISABLED.
      if (UpdatesFromWebstore(*extension)) {
        base::UmaHistogramEnumeration("Extensions.ExternalItemState2",
                                      ExternalItemState::kWebstoreEnabled);
      } else {
        base::UmaHistogramEnumeration("Extensions.ExternalItemState2",
                                      ExternalItemState::kNonwebstoreEnabled);
      }
    }

    if (extension->permissions_data()->HasAPIPermission(
            mojom::APIPermissionID::kWebRequestBlocking)) {
      web_request_blocking_count++;
    }

    if (extension->permissions_data()->HasAPIPermission(
            mojom::APIPermissionID::kWebRequest)) {
      web_request_count++;
    }

    if (extension->permissions_data()->HasAPIPermission(
            mojom::APIPermissionID::kNativeMessaging)) {
      native_messaging_count++;
    }

    // 10 is arbitrarily chosen.
    static constexpr int kMaxManifestVersion = 10;
    // ManifestVersion split by location for items of type
    // Manifest::Type::kExtension. An ungrouped histogram is below, includes all
    // extension-y types (such as platform apps and hosted apps), and doesn't
    // include unpacked or component locations.
    if (extension->is_extension()) {
      const char* location_histogram_name = nullptr;
      ManifestVersion2And3Counts* manifest_version_counts = nullptr;
      switch (extension->location()) {
        case mojom::ManifestLocation::kInternal:
          location_histogram_name =
              "Extensions.ManifestVersionByLocation.Internal";
          manifest_version_counts = &internal_manifest_version_counts;
          break;
        case mojom::ManifestLocation::kExternalPref:
        case mojom::ManifestLocation::kExternalPrefDownload:
        case mojom::ManifestLocation::kExternalRegistry:
          location_histogram_name =
              "Extensions.ManifestVersionByLocation.External";
          manifest_version_counts = &external_manifest_version_counts;
          break;
        case mojom::ManifestLocation::kComponent:
        case mojom::ManifestLocation::kExternalComponent:
          location_histogram_name =
              "Extensions.ManifestVersionByLocation.Component";
          manifest_version_counts = &component_manifest_version_counts;
          break;
        case mojom::ManifestLocation::kExternalPolicy:
        case mojom::ManifestLocation::kExternalPolicyDownload:
          location_histogram_name =
              "Extensions.ManifestVersionByLocation.Policy";
          manifest_version_counts = &policy_manifest_version_counts;
          break;
        case mojom::ManifestLocation::kCommandLine:
        case mojom::ManifestLocation::kUnpacked:
          location_histogram_name =
              "Extensions.ManifestVersionByLocation.Unpacked";
          manifest_version_counts = &unpacked_manifest_version_counts;
          break;
        case mojom::ManifestLocation::kInvalidLocation:
          NOTREACHED();
      }
      base::UmaHistogramExactLinear(location_histogram_name,
                                    extension->manifest_version(),
                                    kMaxManifestVersion);
      if (extension->manifest_version() == 2) {
        manifest_version_counts->version_2_count++;
      } else if (extension->manifest_version() == 3) {
        manifest_version_counts->version_3_count++;
      }
      // Report the days since the extension was installed.
      base::Time time_since_install =
          GetFirstInstallTime(extension_prefs_, extension->id());
      if (!time_since_install.is_null()) {
        int days_since_install =
            (base::Time::Now() - time_since_install).InDays();
        UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.DaysSinceInstall",
                                    days_since_install, 0, 5000, 91);
      }
      // Report the days since the extension was last updated.
      base::Time time_since_last_update =
          GetLastUpdateTime(extension_prefs_, extension->id());
      if (!time_since_last_update.is_null()) {
        int days_since_updated =
            (base::Time::Now() - time_since_last_update).InDays();
        UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.DaysSinceLastUpdate",
                                    days_since_updated, 0, 5000, 91);
      }
    }

    // From now on, don't count component extensions, since they are only
    // extensions as an implementation detail. Continue to count unpacked
    // extensions for a few metrics.
    if (Manifest::IsComponentLocation(location)) {
      continue;
    }

    // Histogram for extensions overriding the new tab page should include
    // unpacked extensions.
    if (URLOverrides::GetChromeURLOverrides(extension).count("newtab")) {
      ++ntp_override_count;
      if (!extension->from_webstore()) {
        ++non_webstore_ntp_override_count;
      }
    }

    // Histogram for extensions with settings overrides.
    const SettingsOverrides* settings = SettingsOverrides::Get(extension);
    if (settings) {
      if (settings->search_engine) {
        ++search_engine_override_count;
      }
      if (settings->homepage) {
        ++homepage_override_count;
      }
    }

    // Don't count unpacked extensions anymore, either.
    if (Manifest::IsUnpackedLocation(location)) {
      continue;
    }

    UMA_HISTOGRAM_ENUMERATION("Extensions.ManifestVersion2",
                              extension->manifest_version(),
                              kMaxManifestVersion);

    // We might have wanted to count legacy packaged apps here, too, since they
    // are effectively extensions. Unfortunately, it's too late, as we don't
    // want to mess up the existing stats.
    if (type == Manifest::Type::kExtension) {
      base::UmaHistogramEnumeration("Extensions.BackgroundPageType2",
                                    GetBackgroundPageType(extension));
    }

    // Using an enumeration shows us the total installed ratio across all users.
    // Using the totals per user at each startup tells us the distribution of
    // usage for each user (e.g. 40% of users have at least one app installed).
    UMA_HISTOGRAM_ENUMERATION("Extensions.LoadType2", type,
                              Manifest::Type::kNumLoadTypes);
    switch (type) {
      case Manifest::Type::kTheme:
        ++theme_count;
        break;
      case Manifest::Type::kUserScript:
        // No histogram.
        break;
      case Manifest::Type::kHostedApp:
        ++hosted_app_count;
        if (Manifest::IsExternalLocation(location)) {
          ++app_external_count;
        } else {
          ++app_user_count;
        }
        break;
      case Manifest::Type::kLegacyPackagedApp:
        ++legacy_packaged_app_count;
        if (Manifest::IsExternalLocation(location)) {
          ++app_external_count;
        } else {
          ++app_user_count;
        }
        break;
      case Manifest::Type::kPlatformApp:
        ++platform_app_count;
        if (Manifest::IsExternalLocation(location)) {
          ++app_external_count;
        } else {
          ++app_user_count;
        }
        break;
      case Manifest::Type::kExtension:
      default:
        if (Manifest::IsExternalLocation(location)) {
          ++extension_external_count;
        } else {
          ++extension_user_count;
        }
        break;
    }

    // We check the manifest key (instead of the ExtensionActionManager) because
    // we want to know how many extensions have a given type of action as part
    // of their code, rather than as part of the extension action redesign
    // (which gives each extension an action).
    // TODO(devlin): This is wrong -- it's not counting manifest_keys::kAction,
    // which is the most popular (and only allowed option in MV3+).
    if (extension->manifest()->FindKey(manifest_keys::kPageAction)) {
      // No histogram.
    } else if (extension->manifest()->FindKey(manifest_keys::kBrowserAction)) {
      ++browser_action_count;
    } else {
      ++no_action_count;
    }

    RecordPermissionMessagesHistogram(extension, "Load", profile);

    // For incognito and file access, skip anything that doesn't appear in
    // settings. Also, policy-installed (and unpacked of course, checked above)
    // extensions are boring.
    if (ui_util::ShouldDisplayInExtensionSettings(*extension) &&
        !Manifest::IsPolicyLocation(extension->location())) {
      if (util::CanBeIncognitoEnabled(extension)) {
        if (util::IsIncognitoEnabled(extension->id(), profile)) {
          ++incognito_allowed_count;
        } else {
          ++incognito_not_allowed_count;
        }
      }
      if (extension->wants_file_access()) {
        if (util::AllowFileAccess(extension->id(), profile)) {
          ++file_access_allowed_count;
        } else {
          ++file_access_not_allowed_count;
        }
      }
    }

    if (!UpdatesFromWebstore(*extension)) {
      ++off_store_item_count;
    }

    PermissionsManager* permissions_manager = PermissionsManager::Get(profile);
    // NOTE: CanAffectExtension() returns false in all cases when the
    // RuntimeHostPermissions feature is disabled.
    if (permissions_manager->CanAffectExtension(*extension)) {
      bool extension_has_withheld_hosts =
          permissions_manager->HasWithheldHostPermissions(*extension);
      UMA_HISTOGRAM_BOOLEAN(
          "Extensions.RuntimeHostPermissions.ExtensionHasWithheldHosts2",
          extension_has_withheld_hosts);
      if (extension_has_withheld_hosts) {
        // Record the number of granted hosts if and only if the extension
        // has withheld host permissions. This lets us equate "0" granted
        // hosts to "on click only".
        size_t num_granted_hosts = 0;
        for (const auto& pattern : extension->permissions_data()
                                       ->active_permissions()
                                       .effective_hosts()) {
          // Ignore chrome:-scheme patterns (like chrome://favicon); these
          // aren't withheld, and thus shouldn't be considered "granted".
          if (pattern.scheme() != content::kChromeUIScheme) {
            ++num_granted_hosts;
          }
        }
        // TODO(devlin): This only takes into account the granted hosts that
        // were also requested by the extension (because it looks at the active
        // permissions). We could potentially also record the granted hosts that
        // were explicitly not requested.
        UMA_HISTOGRAM_COUNTS_100(
            "Extensions.RuntimeHostPermissions.GrantedHostCount2",
            num_granted_hosts);
      }
    }

    LogHostPermissionsAccess(*extension);
    if (ExtensionAllowlistFactory::GetForBrowserContext(profile)
            ->GetExtensionAllowlistState(extension->id()) ==
        ALLOWLIST_NOT_ALLOWLISTED) {
      // Record the number of not allowlisted enabled extensions.
      ++enabled_not_allowlisted_count;
    }
  }

  const ExtensionSet& disabled_extensions =
      extension_registry_->disabled_extensions();
  for (const scoped_refptr<const Extension>& disabled_extension :
       disabled_extensions) {
    mojom::ManifestLocation disabled_location = disabled_extension->location();
    if (extension_prefs_->DidExtensionEscalatePermissions(
            disabled_extension->id())) {
      ++disabled_for_permissions_count;
    }
    RecordDisableReasons(
        extension_prefs_->GetDisableReasons(disabled_extension->id()));
    if (Manifest::IsExternalLocation(disabled_location)) {
      // See loop above for ENABLED.
      ExternalItemState state = UpdatesFromWebstore(*disabled_extension)
                                    ? ExternalItemState::kWebstoreDisabled
                                    : ExternalItemState::kNonwebstoreDisabled;
      base::UmaHistogramEnumeration("Extensions.ExternalItemState2", state);
    }

    // Record disabled non-webstore extensions based on developer mode status.
    if (!UpdatesFromWebstore(*disabled_extension) &&
        !disabled_extension->from_webstore()) {
      if (dev_mode_enabled) {
        base::UmaHistogramEnumeration(
            "Extensions.NonWebstoreLocationWithDeveloperModeOn.Disabled3",
            disabled_location);
      } else {
        base::UmaHistogramEnumeration(
            "Extensions.NonWebstoreLocationWithDeveloperModeOff.Disabled3",
            disabled_location);
      }
    }

    if (ExtensionAllowlistFactory::GetForBrowserContext(profile)
            ->GetExtensionAllowlistState(disabled_extension->id()) ==
        ALLOWLIST_NOT_ALLOWLISTED) {
      // Record the number of not allowlisted disabled extensions.
      ++disabled_not_allowlisted_count;
    }
  }

  base::UmaHistogramCounts100("Extensions.ManifestVersion2Count.Internal",
                              internal_manifest_version_counts.version_2_count);
  base::UmaHistogramCounts100("Extensions.ManifestVersion3Count.Internal",
                              internal_manifest_version_counts.version_3_count);
  base::UmaHistogramCounts100("Extensions.ManifestVersion2Count.External",
                              external_manifest_version_counts.version_2_count);
  base::UmaHistogramCounts100("Extensions.ManifestVersion3Count.External",
                              external_manifest_version_counts.version_3_count);
  base::UmaHistogramCounts100(
      "Extensions.ManifestVersion2Count.Component",
      component_manifest_version_counts.version_2_count);
  base::UmaHistogramCounts100(
      "Extensions.ManifestVersion3Count.Component",
      component_manifest_version_counts.version_3_count);
  base::UmaHistogramCounts100("Extensions.ManifestVersion2Count.Policy",
                              policy_manifest_version_counts.version_2_count);
  base::UmaHistogramCounts100("Extensions.ManifestVersion3Count.Policy",
                              policy_manifest_version_counts.version_3_count);
  base::UmaHistogramCounts100("Extensions.ManifestVersion2Count.Unpacked",
                              unpacked_manifest_version_counts.version_2_count);
  base::UmaHistogramCounts100("Extensions.ManifestVersion3Count.Unpacked",
                              unpacked_manifest_version_counts.version_3_count);

  base::UmaHistogramCounts100("Extensions.LoadApp2",
                              app_user_count + app_external_count);
  base::UmaHistogramCounts100("Extensions.LoadAppUser2", app_user_count);
  base::UmaHistogramCounts100("Extensions.LoadAppExternal2",
                              app_external_count);
  base::UmaHistogramCounts100("Extensions.LoadHostedApp2", hosted_app_count);
  base::UmaHistogramCounts100("Extensions.LoadPackagedApp2",
                              legacy_packaged_app_count);
  base::UmaHistogramCounts100("Extensions.LoadPlatformApp2",
                              platform_app_count);
  base::UmaHistogramCounts100("Extensions.LoadExtension2",
                              extension_user_count + extension_external_count);
  base::UmaHistogramCounts100("Extensions.LoadExtensionUser2",
                              extension_user_count);
  base::UmaHistogramCounts100("Extensions.LoadExtensionExternal2",
                              extension_external_count);
  base::UmaHistogramCounts100("Extensions.LoadTheme2", theme_count);
  base::UmaHistogramCounts100("Extensions.LoadBrowserAction2",
                              browser_action_count);
  base::UmaHistogramCounts100("Extensions.LoadNoExtensionAction2",
                              no_action_count);
  base::UmaHistogramCounts100("Extensions.DisabledForPermissions2",
                              disabled_for_permissions_count);
  base::UmaHistogramCounts100("Extensions.NonWebStoreNewTabPageOverrides2",
                              non_webstore_ntp_override_count);
  base::UmaHistogramCounts100("Extensions.NewTabPageOverrides2",
                              ntp_override_count);
  base::UmaHistogramCounts100("Extensions.SearchEngineOverrides2",
                              search_engine_override_count);
  base::UmaHistogramCounts100("Extensions.HomepageOverrides2",
                              homepage_override_count);

  if (incognito_allowed_count + incognito_not_allowed_count > 0) {
    base::UmaHistogramCounts100("Extensions.IncognitoAllowed2",
                                incognito_allowed_count);
  }
  if (file_access_allowed_count + file_access_not_allowed_count > 0) {
    base::UmaHistogramCounts100("Extensions.FileAccessAllowed2",
                                file_access_allowed_count);
    base::UmaHistogramCounts100("Extensions.FileAccessNotAllowed2",
                                file_access_not_allowed_count);
  }

  base::UmaHistogramCounts100(
      "Extensions.CorruptExtensionTotalDisables2",
      extension_prefs_->GetPrefAsInteger(kCorruptedDisableCount));
  base::UmaHistogramCounts100("Extensions.LoadOffStoreItems2",
                              off_store_item_count);
  base::UmaHistogramCounts100("Extensions.WebRequestBlockingCount2",
                              web_request_blocking_count);
  base::UmaHistogramCounts100("Extensions.WebRequestCount2", web_request_count);
  base::UmaHistogramCounts100("Extensions.Messaging.NativeMessagingCount",
                              native_messaging_count);
  base::UmaHistogramCounts100("Extensions.NotAllowlistedEnabled2",
                              enabled_not_allowlisted_count);
  base::UmaHistogramCounts100("Extensions.NotAllowlistedDisabled2",
                              disabled_not_allowlisted_count);
  if (safe_browsing::IsEnhancedProtectionEnabled(*profile->GetPrefs())) {
    base::UmaHistogramCounts100("Extensions.NotAllowlistedEnabledAndEsbUser2",
                                enabled_not_allowlisted_count);
    base::UmaHistogramCounts100("Extensions.NotAllowlistedDisabledAndEsbUser2",
                                disabled_not_allowlisted_count);
  }
}

int InstalledLoader::GetCreationFlags(
    const ExtensionPrefs::InstallRecord* info) {
  int flags = extension_prefs_->GetCreationFlags(info->extension_id);
  if (!Manifest::IsUnpackedLocation(info->extension_location)) {
    flags |= Extension::REQUIRE_KEY;
  }
  // Use the AllowFileAccess pref as the source of truth for file access,
  // rather than any previously stored creation flag.
  flags &= ~Extension::ALLOW_FILE_ACCESS;
  if (extension_prefs_->AllowFileAccess(info->extension_id)) {
    flags |= Extension::ALLOW_FILE_ACCESS;
  }
  return flags;
}

void InstalledLoader::HandleCorruptExtension(const Extension& extension,
                                             const ManagementPolicy& policy) {
  CorruptedExtensionReinstaller* corrupted_extension_reinstaller =
      CorruptedExtensionReinstaller::Get(profile_);
  if (policy.MustRemainEnabled(&extension, nullptr)) {
    // This extension must have been disabled due to corruption on a
    // previous run of chrome, and for some reason we weren't successful in
    // auto-reinstalling it. So we want to notify the reinstaller that we'd
    // still like to keep attempt to re-download and reinstall it whenever
    // the ExtensionService checks for external updates.
    LOG(ERROR) << "Expecting reinstall for extension id: " << extension.id()
               << " due to corruption detected in prior session.";
    corrupted_extension_reinstaller->ExpectReinstallForCorruption(
        extension.id(),
        CorruptedExtensionReinstaller::PolicyReinstallReason::
            CORRUPTION_DETECTED_IN_PRIOR_SESSION,
        extension.location());
  } else if (extension.from_webstore()) {
    // Non-policy extensions are repaired on startup. Add any corrupted
    // user-installed extensions to the reinstaller as well.
    corrupted_extension_reinstaller->ExpectReinstallForCorruption(
        extension.id(), std::nullopt, extension.location());
  }
}

bool InstalledLoader::UpdatesFromWebstore(const Extension& extension) {
  return extension_management_->UpdatesFromWebstore(extension);
}

}  // namespace extensions
