// Copyright 2021 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/safe_browsing/extension_telemetry/extension_telemetry_service.h"

#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/files/file_util.h"
#include "base/i18n/time_formatting.h"
#include "base/json/values_util.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/sequence_bound.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/types/optional_ref.h"
#include "chrome/browser/enterprise/browser_management/management_service_factory.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/extension_telemetry/activity_log_ingester.h"
#include "chrome/browser/safe_browsing/extension_telemetry/cookies_get_all_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/cookies_get_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/declarative_net_request_action_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/declarative_net_request_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/dom_access_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_js_callstacks.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_signal.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_config_manager.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_file_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_persister.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service_factory.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_uploader.h"
#include "chrome/browser/safe_browsing/extension_telemetry/potential_password_theft_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/remote_host_contacted_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/script_injection_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/search_hijacking_detector.h"
#include "chrome/browser/safe_browsing/extension_telemetry/tabs_api_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/tabs_execute_script_signal_processor.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/common/pref_names.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/policy/core/common/management/management_service.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/safe_browsing/core/browser/sync/safe_browsing_primary_account_token_fetcher.h"
#include "components/safe_browsing/core/browser/sync/sync_utils.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/proto/csd.pb.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/blocklist_extension_prefs.h"
#include "extensions/browser/blocklist_state.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/install_prefs_helper.h"
#include "extensions/browser/managed_installation_mode.h"
#include "extensions/browser/path_util.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/file_util.h"
#include "extensions/common/mojom/manifest.mojom-shared.h"
#include "extensions/common/switches.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"

#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
#include "chrome/browser/enterprise/connectors/connectors_service.h"
#include "chrome/browser/enterprise/connectors/reporting/extension_telemetry_event_router.h"
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)

namespace safe_browsing {

namespace {

using ::extensions::ExtensionManagement;
using ::extensions::ManagedInstallationMode;
using ::extensions::mojom::ManifestLocation;
using ::google::protobuf::RepeatedPtrField;
using ExtensionInfo =
    ::safe_browsing::ExtensionTelemetryReportRequest_ExtensionInfo;
using OffstoreExtensionVerdict =
    ::safe_browsing::ExtensionTelemetryReportResponse_OffstoreExtensionVerdict;
using SearchHijackingSignal =
    ::safe_browsing::ExtensionTelemetryReportRequest_SearchHijackingSignal;

// The ExtensionTelemetryService saves offstore extensions file data such as
// filenames and hashes in Prefs. This information is stored in the following
// dictionary format:
// {
//  ...
//  kExtensionTelemetryFileData : {
//    <extension_id_1> : {
//      "last_processed_timestamp" : <timestamp>,
//      "file_data" : {
//        <file_path_1> : <file_hash_1>,
//        <file_path_2> : <file_hash_2>,
//        ...
//        <manifest.json> : <file_contents>
//      }
//    },
//    <extension_id_2> : {
//      "last_processed_timestamp" : <timestamp>,
//      "file_data" : {
//        <file_path_1> : <file_hash_1>,
//        <file_path_2> : <file_hash_2>,
//        ...
//        <manifest.json> : <file_contents>
//      }
//    },
//    ...
//  },
//  ...
// }

constexpr char kFileDataProcessTimestampPref[] = "last_processed_timestamp";
constexpr char kFileDataDictPref[] = "file_data";
constexpr char kManifestFile[] = "manifest.json";

// Specifies the default for `num_checks_per_upload_interval_`
// NOTE: With a value of 1, the telemetry service will perform checks at the
// upload interval itself and will only write a report to disk if the upload
// fails.
constexpr int kNumChecksPerUploadInterval = 1;

// Specifies the upload interval for ESB telemetry reports.
base::TimeDelta kUploadIntervalSeconds = base::Seconds(3600);

// Delay before the Telemetry Service checks its last upload time.
base::TimeDelta kStartupUploadCheckDelaySeconds = base::Seconds(15);

// Interval for extension telemetry to start another off-store extension
// file data collection process.
base::TimeDelta kOffstoreFileDataCollectionIntervalSeconds =
    base::Seconds(7200);

// Initial delay for extension telemetry to start collecting
// off-store extension file data.
base::TimeDelta kOffstoreFileDataCollectionStartupDelaySeconds =
    base::Seconds(300);

// Limit the off-store file data collection duration.
base::TimeDelta kOffstoreFileDataCollectionDurationLimitSeconds =
    base::Seconds(60);

#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
// Interval for generating and send extension telemetry for enterprise
base::TimeDelta kExtensionTelemetryEnterpriseReportingIntervalSeconds =
    base::Seconds(300);
#endif

void RecordWhenFileWasPersisted(bool persisted_at_write_interval) {
  base::UmaHistogramBoolean(
      "SafeBrowsing.ExtensionTelemetry.FilePersistedAtWriteInterval",
      persisted_at_write_interval);
}

void RecordSignalUploadSize(size_t size, ExtensionSignalType signal_type) {
  switch (signal_type) {
    case ExtensionSignalType::kTabsExecuteScript:
      base::UmaHistogramCounts1M(
          "SafeBrowsing.ExtensionTelemetry.UploadSize.TabsExecuteScript", size);
      break;
    case ExtensionSignalType::kRemoteHostContacted:
      base::UmaHistogramCounts1M(
          "SafeBrowsing.ExtensionTelemetry.UploadSize.RemoteHostContacted",
          size);
      break;
    case ExtensionSignalType::kCookiesGetAll:
      base::UmaHistogramCounts1M(
          "SafeBrowsing.ExtensionTelemetry.UploadSize.CookiesGetAll", size);
      break;
    case ExtensionSignalType::kPotentialPasswordTheft:
      base::UmaHistogramCounts1M(
          "SafeBrowsing.ExtensionTelemetry.UploadSize.PotentialPasswordTheft",
          size);
      break;
    case ExtensionSignalType::kCookiesGet:
      base::UmaHistogramCounts1M(
          "SafeBrowsing.ExtensionTelemetry.UploadSize.CookiesGet", size);
      break;
    case ExtensionSignalType::kDeclarativeNetRequest:
      base::UmaHistogramCounts1M(
          "SafeBrowsing.ExtensionTelemetry.UploadSize.DeclarativeNetRequest",
          size);
      break;
    case ExtensionSignalType::kTabsApi:
      base::UmaHistogramCounts1M(
          "SafeBrowsing.ExtensionTelemetry.UploadSize.TabsApi", size);
      break;
    case ExtensionSignalType::kDeclarativeNetRequestAction:
      base::UmaHistogramCounts1M(
          "SafeBrowsing.ExtensionTelemetry.UploadSize."
          "DeclarativeNetRequestAction",
          size);
      break;
    case ExtensionSignalType::kPasswordReuse:
    // This signal is used to construct the potential password theft signal and
    // is not itself included in a telemetry report.
    default:
      break;
  }
}

void RecordNumOffstoreExtensions(int num_extensions) {
  base::UmaHistogramCounts100(
      "SafeBrowsing.ExtensionTelemetry.FileData.NumOffstoreExtensions",
      num_extensions);
}

void RecordCollectionDuration(base::TimeDelta duration) {
  base::UmaHistogramMediumTimes(
      "SafeBrowsing.ExtensionTelemetry.FileData.CollectionDuration", duration);
}

void RecordSignalTypeForEnterprise(ExtensionSignalType signal_type) {
  base::UmaHistogramEnumeration(
      "SafeBrowsing.ExtensionTelemetry.Enterprise.Signals.SignalType",
      signal_type);
}

void RecordEnterpriseReportSize(size_t size) {
  base::UmaHistogramCounts1M(
      "SafeBrowsing.ExtensionTelemetry.Enterprise.ReportSize", size);
}

static_assert(std::to_underlying(extensions::Manifest::Type::kNumLoadTypes) ==
                  10,
              "ExtensionTelemetryReportRequest::ExtensionInfo::Type "
              "needs to match extensions::Manifest::Type.");
ExtensionInfo::Type GetType(extensions::Manifest::Type type) {
  switch (type) {
    case extensions::Manifest::Type::kUnknown:
      return ExtensionInfo::UNKNOWN_TYPE;
    case extensions::Manifest::Type::kExtension:
      return ExtensionInfo::EXTENSION;
    case extensions::Manifest::Type::kTheme:
      return ExtensionInfo::THEME;
    case extensions::Manifest::Type::kUserScript:
      return ExtensionInfo::USER_SCRIPT;
    case extensions::Manifest::Type::kHostedApp:
      return ExtensionInfo::HOSTED_APP;
    case extensions::Manifest::Type::kLegacyPackagedApp:
      return ExtensionInfo::LEGACY_PACKAGED_APP;
    case extensions::Manifest::Type::kPlatformApp:
      return ExtensionInfo::PLATFORM_APP;
    case extensions::Manifest::Type::kSharedModule:
      return ExtensionInfo::SHARED_MODULE;
    case extensions::Manifest::Type::kLoginScreenExtension:
      return ExtensionInfo::LOGIN_SCREEN_EXTENSION;
    case extensions::Manifest::Type::kChromeOSSystemExtension:
      // TODO(mgawad): introduce new CHROMEOS_SYSTEM_EXTENSION type.
      return ExtensionInfo::EXTENSION;
    default:
      return ExtensionInfo::UNKNOWN_TYPE;
  }
}

static_assert(static_cast<int>(ManifestLocation::kMaxValue) ==
                  static_cast<int>(ExtensionInfo::EXTERNAL_COMPONENT),
              "ExtensionTelemetryReportRequest::ExtensionInfo::InstallLocation "
              "needs to match extensions::mojom::ManifestLocation.");
ExtensionInfo::InstallLocation GetInstallLocation(ManifestLocation location) {
  switch (location) {
    case ManifestLocation::kInvalidLocation:
      return ExtensionInfo::UNKNOWN_LOCATION;
    case ManifestLocation::kInternal:
      return ExtensionInfo::INTERNAL;
    case ManifestLocation::kExternalPref:
      return ExtensionInfo::EXTERNAL_PREF;
    case ManifestLocation::kExternalRegistry:
      return ExtensionInfo::EXTERNAL_REGISTRY;
    case ManifestLocation::kUnpacked:
      return ExtensionInfo::UNPACKED;
    case ManifestLocation::kComponent:
      return ExtensionInfo::COMPONENT;
    case ManifestLocation::kExternalPrefDownload:
      return ExtensionInfo::EXTERNAL_PREF_DOWNLOAD;
    case ManifestLocation::kExternalPolicyDownload:
      return ExtensionInfo::EXTERNAL_POLICY_DOWNLOAD;
    case ManifestLocation::kCommandLine:
      return ExtensionInfo::COMMAND_LINE;
    case ManifestLocation::kExternalPolicy:
      return ExtensionInfo::EXTERNAL_POLICY;
    case ManifestLocation::kExternalComponent:
      return ExtensionInfo::EXTERNAL_COMPONENT;
  }
  return ExtensionInfo::UNKNOWN_LOCATION;
}

// Converts a policy::ManagementAuthorityTrustworthiness to
// ExtensionTelemetryReportRequest::ManagementAuthority.
ExtensionTelemetryReportRequest::ManagementAuthority GetManagementAuthority(
    policy::ManagementAuthorityTrustworthiness
        management_authority_trustworthiness) {
  switch (management_authority_trustworthiness) {
    case policy::ManagementAuthorityTrustworthiness::NONE:
      return ExtensionTelemetryReportRequest::MANAGEMENT_AUTHORITY_NONE;
    case policy::ManagementAuthorityTrustworthiness::LOW:
      return ExtensionTelemetryReportRequest::MANAGEMENT_AUTHORITY_LOW;
    case policy::ManagementAuthorityTrustworthiness::TRUSTED:
      return ExtensionTelemetryReportRequest::MANAGEMENT_AUTHORITY_TRUSTED;
    case policy::ManagementAuthorityTrustworthiness::FULLY_TRUSTED:
      return ExtensionTelemetryReportRequest::
          MANAGEMENT_AUTHORITY_FULLY_TRUSTED;
    default:
      return ExtensionTelemetryReportRequest::MANAGEMENT_AUTHORITY_UNSPECIFIED;
  }
}

ExtensionInfo::InstallationPolicy
ExtensionManagementInstallationModeToExtensionInfoInstallationPolicy(
    const ManagedInstallationMode& installation_mode) {
  switch (installation_mode) {
    case ManagedInstallationMode::kAllowed:
      return ExtensionInfo::INSTALLATION_ALLOWED;
    case ManagedInstallationMode::kBlocked:
      return ExtensionInfo::INSTALLATION_BLOCKED;
    case ManagedInstallationMode::kForced:
      return ExtensionInfo::INSTALLATION_FORCED;
    case ManagedInstallationMode::kRecommended:
      return ExtensionInfo::INSTALLATION_RECOMMENDED;
    case ManagedInstallationMode::kRemoved:
      return ExtensionInfo::INSTALLATION_REMOVED;
    default:
      return ExtensionInfo::NO_POLICY;
  }
}

ExtensionInfo::BlocklistState BitMapBlocklistStateToExtensionInfoBlocklistState(
    const extensions::BitMapBlocklistState& state) {
  switch (state) {
    case extensions::BitMapBlocklistState::NOT_BLOCKLISTED:
      return ExtensionInfo::NOT_BLOCKLISTED;
    case extensions::BitMapBlocklistState::BLOCKLISTED_MALWARE:
      return ExtensionInfo::BLOCKLISTED_MALWARE;
    case extensions::BitMapBlocklistState::BLOCKLISTED_SECURITY_VULNERABILITY:
      return ExtensionInfo::BLOCKLISTED_SECURITY_VULNERABILITY;
    case extensions::BitMapBlocklistState::BLOCKLISTED_CWS_POLICY_VIOLATION:
      return ExtensionInfo::BLOCKLISTED_CWS_POLICY_VIOLATION;
    case extensions::BitMapBlocklistState::BLOCKLISTED_POTENTIALLY_UNWANTED:
      return ExtensionInfo::BLOCKLISTED_POTENTIALLY_UNWANTED;
    default:
      return ExtensionInfo::BLOCKLISTED_UNKNOWN;
  }
}

ExtensionInfo::BlocklistState GetBlocklistState(
    const extensions::ExtensionId extension_id,
    extensions::ExtensionPrefs* extension_prefs) {
  extensions::BitMapBlocklistState state =
      extensions::blocklist_prefs::GetExtensionBlocklistState(extension_id,
                                                              extension_prefs);
  return BitMapBlocklistStateToExtensionInfoBlocklistState(state);
}

ExtensionInfo::BlocklistState GetExtensionTelemetryServiceBlocklistState(
    const extensions::ExtensionId extension_id,
    extensions::ExtensionPrefs* extension_prefs) {
  extensions::BitMapBlocklistState state =
      extensions::blocklist_prefs::GetExtensionTelemetryServiceBlocklistState(
          extension_id, extension_prefs);
  return BitMapBlocklistStateToExtensionInfoBlocklistState(state);
}

extensions::BlocklistState ConvertTelemetryResponseVerdictToBlocklistState(
    OffstoreExtensionVerdict::OffstoreExtensionVerdictType type) {
  switch (type) {
    case OffstoreExtensionVerdict::NONE:
      return extensions::BlocklistState::NOT_BLOCKLISTED;
    case OffstoreExtensionVerdict::MALWARE:
      return extensions::BlocklistState::BLOCKLISTED_MALWARE;
    default:
      return extensions::BlocklistState::BLOCKLISTED_UNKNOWN;
  }
}

// Checks for extensions specified in the -load-extension commandline switch.
// Creates and returns a set of extension objects for such extensions.
// NOTE: These extensions are not installed - the information is only collected
// for telemetry purposes.
// This function is executed on a separate thread from the UI thread since it
// involves reading the manifest files from disk.
extensions::ExtensionSet CollectCommandLineExtensionInfo() {
  extensions::ExtensionSet commandline_extensions;

  // If there are no commandline extensions, return an empty set.
  base::CommandLine* cmdline(base::CommandLine::ForCurrentProcess());
  if (!cmdline || !cmdline->HasSwitch(extensions::switches::kLoadExtension)) {
    return commandline_extensions;
  }

  // Otherwise, create an extension object for each extension specified in the
  // commandline. Note that the extension is not installed.
  base::CommandLine::StringType path_list =
      cmdline->GetSwitchValueNative(extensions::switches::kLoadExtension);
  base::StringTokenizerT<base::CommandLine::StringType,
                         base::CommandLine::StringType::const_iterator>
      t(path_list, FILE_PATH_LITERAL(","));

  while (t.GetNext()) {
    auto tmp_path = extensions::path_util::ResolveHomeDirectory(
        base::FilePath(t.token_piece()));
    auto extension_path = base::MakeAbsoluteFilePath(tmp_path);
    std::u16string error;
    // Use default creation flags. Since we are not installing the extension,
    // it doesn't really mattter.
    int flags = extensions::Extension::FOLLOW_SYMLINKS_ANYWHERE |
                extensions::Extension::ALLOW_FILE_ACCESS |
                extensions::Extension::REQUIRE_MODERN_MANIFEST_VERSION;
    scoped_refptr<extensions::Extension> extension =
        extensions::file_util::LoadExtension(
            extension_path, ManifestLocation::kCommandLine, flags, &error);
    if (error.empty()) {
      commandline_extensions.Insert(extension);
    }
  }

  return commandline_extensions;
}

#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
// Retrieves the ExtensionTelemetryEventRouter associated with the profile.
enterprise_connectors::ExtensionTelemetryEventRouter*
GetExtensionTelemetryEventRouter(Profile* profile) {
  return enterprise_connectors::ExtensionTelemetryEventRouter::Get(profile);
}
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)

}  // namespace

// Adds extension installation mode and managed status to extension telemetry
// reports.
BASE_FEATURE(kExtensionTelemetryIncludePolicyData,
             "SafeBrowsingExtensionTelemetryIncludePolicyData",
             base::FEATURE_ENABLED_BY_DEFAULT);

ExtensionTelemetryService::~ExtensionTelemetryService() = default;

// static
ExtensionTelemetryService* ExtensionTelemetryService::Get(Profile* profile) {
  return ExtensionTelemetryServiceFactory::GetInstance()->GetForProfile(
      profile);
}

ExtensionTelemetryService::ExtensionTelemetryService(
    Profile* profile,
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
    : profile_(profile),
      extension_registry_(extensions::ExtensionRegistry::Get(profile)),
      extension_prefs_(extensions::ExtensionPrefs::Get(profile)),
      offstore_file_data_collection_duration_limit_(
          kOffstoreFileDataCollectionDurationLimitSeconds),
      current_reporting_interval_(kUploadIntervalSeconds),
      url_loader_factory_(url_loader_factory),
      num_checks_per_upload_interval_(kNumChecksPerUploadInterval) {
  // Register for SB preference change notifications.
  pref_service_ = profile_->GetPrefs();
  pref_change_registrar_.Init(pref_service_);
  pref_change_registrar_.Add(
      prefs::kSafeBrowsingEnhanced,
      base::BindRepeating(&ExtensionTelemetryService::OnESBPrefChanged,
                          base::Unretained(this)));
  // Set initial enable/disable state for ESB.
  SetEnabledForESB(IsEnhancedProtectionEnabled(*pref_service_));

#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
    // Register for enterprise policy changes.
    auto* connector_service =
        enterprise_connectors::ConnectorsServiceFactory::GetForBrowserContext(
            profile);
    connector_service->ObserveTelemetryReporting(base::BindRepeating(
        &ExtensionTelemetryService::OnEnterprisePolicyChanged,
        base::Unretained(this)));

    // Set initial enable/disable state for enterprise.
    SetEnabledForEnterprise(
        GetExtensionTelemetryEventRouter(profile_)->IsPolicyEnabled());
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)

    UpdateDOMActivityLoggingState();
}

void ExtensionTelemetryService::RecordSignalType(
    ExtensionSignalType signal_type) {
  base::UmaHistogramEnumeration(
      "SafeBrowsing.ExtensionTelemetry.Signals.SignalType", signal_type);
}

void ExtensionTelemetryService::RecordSignalDiscarded(
    ExtensionSignalType signal_type) {
  base::UmaHistogramEnumeration(
      "SafeBrowsing.ExtensionTelemetry.Signals.Discarded", signal_type);
}

void ExtensionTelemetryService::OnESBPrefChanged() {
  SetEnabledForESB(IsEnhancedProtectionEnabled(*pref_service_));
}

void ExtensionTelemetryService::OnEnterprisePolicyChanged() {
  if (is_shutdown_) {
    return;
  }

#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
  SetEnabledForEnterprise(
      GetExtensionTelemetryEventRouter(profile_)->IsPolicyEnabled());
#else
  SetEnabledForEnterprise(false);
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
}

// Telemetry features for ESB include:
// - ESB signals
// - Search hijacking signal
// - Off-store data collection
// - Command line extensions file data
// - Telemetry Configuration
// - Persister
void ExtensionTelemetryService::SetEnabledForESB(bool enable) {
  // Make call idempotent.
  if (esb_enabled_ == enable) {
    return;
  }

  esb_enabled_ = enable;
  if (esb_enabled_) {
    SetUpSignalProcessorsAndSubscribersForESB();
    SetUpOffstoreFileDataCollection();

    if (base::FeatureList::IsEnabled(
            kExtensionTelemetrySearchHijackingSignal)) {
      auto* template_url_service =
          TemplateURLServiceFactory::GetForProfile(profile_);
      search_hijacking_detector_ = std::make_unique<SearchHijackingDetector>(
          pref_service_, template_url_service);
      search_hijacking_detector_->SetHeuristicCheckInterval(base::Seconds(
          kExtensionTelemetrySearchHijackingSignalHeuristicCheckIntervalSeconds
              .Get()));
      search_hijacking_detector_->SetHeuristicThreshold(
          kExtensionTelemetrySearchHijackingSignalHeuristicThreshold.Get());
    }

    // File data for Command Line extensions.
    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
        base::BindOnce(CollectCommandLineExtensionInfo),
        base::BindOnce(
            &ExtensionTelemetryService::OnCommandLineExtensionsInfoCollected,
            weak_factory_.GetWeakPtr()));

    // Telemetry Configuration
    if (base::FeatureList::IsEnabled(kExtensionTelemetryConfiguration)) {
      config_manager_ =
          std::make_unique<ExtensionTelemetryConfigManager>(pref_service_);
      config_manager_->LoadConfig();
    }

    if (current_reporting_interval_.is_positive()) {
      int max_files_supported =
          ExtensionTelemetryPersister::MaxFilesSupported();
      int checks_per_interval =
          std::min(max_files_supported, num_checks_per_upload_interval_);
      // Configure persister for the maximum number of reports to be persisted
      // on disk. This number is the sum of reports written at every
      // write interval plus an additional allocation (3) for files written at
      // browser/profile shutdown.
      int max_reports_to_persist =
          std::min(checks_per_interval + 3, max_files_supported);
      // Instantiate persister which is used to read/write telemetry reports to
      // disk and start timer for sending periodic telemetry reports.
      persister_ = base::SequenceBound<ExtensionTelemetryPersister>(
          base::ThreadPool::CreateSequencedTaskRunner(
              {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
               base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}),
          max_reports_to_persist, profile_->GetPath());
      persister_.AsyncCall(&ExtensionTelemetryPersister::PersisterInit);
      timer_.Start(FROM_HERE, current_reporting_interval_ / checks_per_interval,
                   this, &ExtensionTelemetryService::PersistOrUploadData);
    }
    // Post this task with a delay to avoid running right at Chrome startup
    // when a lot of other startup tasks are running.
    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&ExtensionTelemetryService::StartUploadCheck,
                       weak_factory_.GetWeakPtr()),
        kStartupUploadCheckDelaySeconds);
  } else {
    // Stop timer for periodic telemetry reports.
    timer_.Stop();
    // Clear all data stored by the service.
    extension_store_.clear();
    // Destruct signal subscribers.
    signal_subscribers_.clear();
    // Destruct signal processors.
    signal_processors_.clear();
    // Destruct search hijacking detector.
    if (search_hijacking_detector_) {
      // Clear all persisted data.
      search_hijacking_detector_->ClearAllDataFromPrefs();
      search_hijacking_detector_.reset();
    }
    // Delete persisted files.
    if (!persister_.is_null()) {
      persister_.AsyncCall(&ExtensionTelemetryPersister::ClearPersistedFiles);
    }
    StopOffstoreFileDataCollection();
  }
}

// Telemetry features for enterprise include:
// - Enterprise signals
// - Off-store data collection
void ExtensionTelemetryService::SetEnabledForEnterprise(bool enable) {
  bool was_enabled = enterprise_enabled_;
  enterprise_enabled_ = enable;

  // Only do setup/teardown when the enterprise reporting state
  // actually changes.
  if (was_enabled != enable) {
    if (enable) {
      SetUpSignalProcessorsAndSubscribersForEnterprise();
      SetUpOffstoreFileDataCollection();

#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
      base::TimeDelta interval =
          base::FeatureList::IsEnabled(
              kExtensionTelemetryEnterpriseShortReportingInterval)
              ? base::Seconds(30)
              : kExtensionTelemetryEnterpriseReportingIntervalSeconds;
      enterprise_timer_.Start(
          FROM_HERE, interval, this,
          &ExtensionTelemetryService::CreateAndSendEnterpriseReport);
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
    } else {
      // Cancel any pending enterprise telemetry reports.
      enterprise_timer_.Stop();
      // Clear enterprise extension store.
      enterprise_extension_store_.clear();
      // Destruct enterprise signal subscribers.
      enterprise_signal_subscribers_.clear();
      // Destruct signal processors.
      enterprise_signal_processors_.clear();
      StopOffstoreFileDataCollection();
    }
  }

  // Always update the DOM activity logging state, as its policy can change
  // independently of the main enterprise reporting setting.
  UpdateDOMActivityLoggingState();
}

bool ExtensionTelemetryService::enabled() const {
  return esb_enabled_ || enterprise_enabled_;
}

void ExtensionTelemetryService::Shutdown() {
  is_shutdown_ = true;
  if (esb_enabled_ && SignalDataPresent() && !persister_.is_null()) {
    // Saving data to disk.
    active_report_ = CreateReport();
    std::string write_string;
    active_report_->SerializeToString(&write_string);
    persister_.AsyncCall(&ExtensionTelemetryPersister::WriteReport)
        .WithArgs(std::move(write_string),
                  ExtensionTelemetryPersister::WriteReportTrigger::kAtShutdown);

    RecordWhenFileWasPersisted(/*persisted_at_write_interval=*/false);
  }
  timer_.Stop();
  enterprise_timer_.Stop();
  pref_change_registrar_.RemoveAll();
  StopOffstoreFileDataCollection();
  activity_log_ingester_.reset();
}

bool ExtensionTelemetryService::SignalDataPresent() const {
  return (extension_store_.size() > 0);
}

bool ExtensionTelemetryService::IsSignalEnabled(
    const extensions::ExtensionId& extension_id,
    ExtensionSignalType signal_type) const {
  return config_manager_->IsSignalEnabled(extension_id, signal_type);
}

void ExtensionTelemetryService::AddSignal(
    std::unique_ptr<ExtensionSignal> signal) {
  ExtensionSignalType signal_type = signal->GetType();

  if (esb_enabled_ && signal_subscribers_.contains(signal_type)) {
    RecordSignalType(signal_type);
    AddSignalHelper(*signal, extension_store_, signal_subscribers_);
  }

  if (enterprise_enabled_ &&
      enterprise_signal_subscribers_.contains(signal_type)) {
    RecordSignalTypeForEnterprise(signal_type);
    AddSignalHelper(*signal, enterprise_extension_store_,
                    enterprise_signal_subscribers_);
  }
}

void ExtensionTelemetryService::OnOmniboxSearch(
    const AutocompleteMatch& match) {
  if (search_hijacking_detector_) {
    search_hijacking_detector_->OnOmniboxSearch(match);
  }
}

void ExtensionTelemetryService::AddSignalHelper(
    const ExtensionSignal& signal,
    ExtensionStore& store,
    SignalSubscribers& subscribers) {
  ExtensionSignalType signal_type = signal.GetType();
  DCHECK(subscribers.contains(signal_type));

  if (!store.contains(signal.extension_id())) {
    // This is the first signal triggered by this extension since the last
    // time a report was generated for it. Store its information.
    // Note: The extension information is cached at signal addition time
    // instead of being calculated at report generation. This approach handles
    // the case where the extension is uninstalled after generating the signal
    // but before a report is generated. The extension information is also
    // cleared after each telemetry report is sent to keep the data fresh.
    const extensions::Extension* extension =
        extension_registry_->GetInstalledExtension(signal.extension_id());
    // Do a sanity check on the returned extension object and abort if it is
    // invalid.
    if (!extension) {
      RecordSignalDiscarded(signal_type);
      return;
    }

    store.emplace(signal.extension_id(), GetExtensionInfoForReport(*extension));
  }

  for (safe_browsing::ExtensionSignalProcessor* processor :
       subscribers[signal_type]) {
    processor->ProcessSignal(signal);
  }
}

std::unique_ptr<ExtensionTelemetryReportRequest>
ExtensionTelemetryService::CreateReportWithCommonFieldsPopulated() {
  auto telemetry_report_pb =
      std::make_unique<ExtensionTelemetryReportRequest>();
  telemetry_report_pb->set_developer_mode_enabled(
      profile_->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode));
  telemetry_report_pb->set_creation_timestamp_msec(
      base::Time::Now().InMillisecondsSinceUnixEpoch());

  if (search_hijacking_detector_) {
    std::unique_ptr<SearchHijackingSignal> signal =
        search_hijacking_detector_->GetSignalForReport();
    if (signal) {
      telemetry_report_pb->set_allocated_search_hijacking_signal(
          signal.release());
    }
  }

  // Only collect if `is_shutdown_` is false, since BrowserContextHelper and
  // UserManager are ChromeOS platform-level singletons destroyed during
  // PostMainMessageLoopRun prior to ProfileManager teardown. KeyedService
  // dependency ordering between ProfileKeyedServiceFactory instances cannot
  // prevent their destruction before service shutdown.
  if (base::FeatureList::IsEnabled(kExtensionTelemetryIncludePolicyData) &&
      !is_shutdown_) {
    // The highest level of ManagementAuthorityTrustworthiness of either
    // platform or browser are taken into account.
    policy::ManagementAuthorityTrustworthiness platform_trustworthiness =
        policy::ManagementServiceFactory::GetForPlatform()
            ->GetManagementAuthorityTrustworthiness();
    policy::ManagementAuthorityTrustworthiness browser_trustworthiness =
        policy::ManagementServiceFactory::GetForProfile(profile_)
            ->GetManagementAuthorityTrustworthiness();
    policy::ManagementAuthorityTrustworthiness highest_trustworthiness =
        std::max(platform_trustworthiness, browser_trustworthiness);
    telemetry_report_pb->set_management_authority(
        GetManagementAuthority(highest_trustworthiness));
  }

  return telemetry_report_pb;
}

void ExtensionTelemetryService::CreateAndUploadReport() {
  if (!esb_enabled_) {
    return;
  }

  active_report_ = CreateReport();
  if (!active_report_) {
    return;
  }

  auto upload_data = std::make_unique<std::string>();
  if (!active_report_->SerializeToString(upload_data.get())) {
    active_report_.reset();
    return;
  }
  UploadReport(std::move(upload_data));
}

void ExtensionTelemetryService::CreateAndSendEnterpriseReport() {
  if (!enterprise_enabled_) {
    return;
  }

  std::unique_ptr<ExtensionTelemetryReportRequest> enterprise_report =
      CreateReportForEnterprise();
  if (enterprise_report) {
    RecordEnterpriseReportSize(enterprise_report->ByteSizeLong());
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
    GetExtensionTelemetryEventRouter(profile_)->UploadTelemetryReport(
        std::move(enterprise_report));
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
  } else {
    DLOG(WARNING) << "Upload skipped due to empty enterprise report.";
  }
}

void ExtensionTelemetryService::OnUploadComplete(
    bool success,
    base::optional_ref<std::string> response_data) {
  // TODO(crbug.com/40253384): Add `config_manager_` implementation
  // to check server response and update config.
  if (success) {
    SetLastUploadTimeForExtensionTelemetry(*pref_service_, base::Time::Now());

    ExtensionTelemetryReportResponse response;
    if (response.ParseFromString(*response_data)) {
      ProcessOffstoreExtensionVerdicts(response);
    }
  }
  if (esb_enabled_ && !persister_.is_null()) {
    // Upload saved report(s) if there are any.
    if (success) {
      // Bind the callback to our current thread.
      auto read_callback =
          base::BindOnce(&ExtensionTelemetryService::UploadPersistedFile,
                         weak_factory_.GetWeakPtr());
      persister_.AsyncCall(&ExtensionTelemetryPersister::ReadReport)
          .Then(std::move(read_callback));
    } else {
      // Save report to disk on a failed upload.
      std::string write_string;
      active_report_->SerializeToString(&write_string);
      persister_.AsyncCall(&ExtensionTelemetryPersister::WriteReport)
          .WithArgs(std::move(write_string),
                    ExtensionTelemetryPersister::WriteReportTrigger::
                        kAtWriteInterval);
      RecordWhenFileWasPersisted(/*persisted_at_write_interval=*/true);
    }
  }
  active_report_.reset();
  active_uploader_.reset();
}

void ExtensionTelemetryService::UploadPersistedFile(std::string report) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!report.empty()) {
    auto upload_data = std::make_unique<std::string>(report);
    // Ensure that `upload_data` is a properly formatted protobuf. If it
    // isn't, skip the upload and attempt to upload the next persisted file.
    active_report_ = std::make_unique<ExtensionTelemetryReportRequest>();
    if (active_report_->ParseFromString(*upload_data.get())) {
      UploadReport(std::move(upload_data));
    } else {
      active_report_.reset();
      auto read_callback =
          base::BindOnce(&ExtensionTelemetryService::UploadPersistedFile,
                         weak_factory_.GetWeakPtr());
      persister_.AsyncCall(&ExtensionTelemetryPersister::ReadReport)
          .Then(std::move(read_callback));
    }
  }
}

void ExtensionTelemetryService::UploadReport(
    std::unique_ptr<std::string> report) {
  auto callback = base::BindOnce(&ExtensionTelemetryService::OnUploadComplete,
                                 weak_factory_.GetWeakPtr());
  active_uploader_ = std::make_unique<ExtensionTelemetryUploader>(
      std::move(callback), url_loader_factory_, std::move(report),
      GetTokenFetcher());
  active_uploader_->Start();
}

void ExtensionTelemetryService::StartUploadCheck() {
  // This check is performed as a delayed task after enabling the service. The
  // service may become disabled between the time this task is scheduled and it
  // actually runs. So make sure service is enabled before performing the check.
  if (!esb_enabled_) {
    return;
  }

  if ((GetLastUploadTimeForExtensionTelemetry(*pref_service_) +
       current_reporting_interval_) <= base::Time::Now()) {
    CreateAndUploadReport();
  }
}

void ExtensionTelemetryService::PersistOrUploadData() {
  if (search_hijacking_detector_) {
    search_hijacking_detector_->MaybeCheckForHeuristicMatch();
  }
  if (GetLastUploadTimeForExtensionTelemetry(*pref_service_) +
          current_reporting_interval_ <=
      base::Time::Now()) {
    CreateAndUploadReport();
  } else {
    // Otherwise persist data gathered so far.
    active_report_ = CreateReport();
    if (!active_report_) {
      return;
    }
    std::string write_string;
    if (!active_report_->SerializeToString(&write_string)) {
      active_report_.reset();
      return;
    }
    persister_.AsyncCall(&ExtensionTelemetryPersister::WriteReport)
        .WithArgs(
            std::move(write_string),
            ExtensionTelemetryPersister::WriteReportTrigger::kAtWriteInterval);
    RecordWhenFileWasPersisted(/*persisted_at_write_interval=*/true);
  }
}

std::unique_ptr<ExtensionTelemetryReportRequest>
ExtensionTelemetryService::CreateReport() {
  if (!esb_enabled_) {
    return nullptr;
  }

  // Don't create a telemetry report if there were no signals generated (i.e.,
  // extension store is empty) AND there are no installed or command-line
  // extensions present.
  extensions::ExtensionSet extensions_to_report =
      extension_registry_->GenerateInstalledExtensionsSet();
  extensions_to_report.InsertAll(commandline_extensions_);
  if (extension_store_.empty() && extensions_to_report.empty()) {
    return nullptr;
  }

  std::unique_ptr<ExtensionTelemetryReportRequest> telemetry_report_pb =
      CreateReportWithCommonFieldsPopulated();
  RepeatedPtrField<ExtensionTelemetryReportRequest_Report>* reports_pb =
      telemetry_report_pb->mutable_reports();

  // Create per-extension reports for all the extensions in the extension store.
  // These represent extensions that have signal information to report.
  for (auto& extension_store_it : extension_store_) {
    auto report_entry_pb =
        std::make_unique<ExtensionTelemetryReportRequest_Report>();

    // Populate all signal info for the extension by querying the signal
    // processors.
    RepeatedPtrField<ExtensionTelemetryReportRequest_SignalInfo>* signals_pb =
        report_entry_pb->mutable_signals();
    for (auto& processor_it : signal_processors_) {
      std::unique_ptr<ExtensionTelemetryReportRequest_SignalInfo>
          signal_info_pb = processor_it.second->GetSignalInfoForReport(
              extension_store_it.first);
      if (signal_info_pb) {
        RecordSignalUploadSize(signal_info_pb->ByteSizeLong(),
                               processor_it.first);
        signals_pb->AddAllocated(signal_info_pb.release());
      }
    }

    report_entry_pb->set_allocated_extension(
        extension_store_it.second.release());
    reports_pb->AddAllocated(report_entry_pb.release());
  }

  // Create per-extension reports for the remaining extensions, i.e., exclude
  // extension store extensions since reports have already been created for
  // them. Note that these remaining extension reports will only contain
  // extension information (and no signal data).
  for (const auto& entry : extension_store_) {
    extensions_to_report.Remove(/*extension_id=*/entry.first);
  }

  for (const scoped_refptr<const extensions::Extension>& installed_entry :
       extensions_to_report) {
    auto report_entry_pb =
        std::make_unique<ExtensionTelemetryReportRequest_Report>();

    report_entry_pb->set_allocated_extension(
        GetExtensionInfoForReport(*installed_entry.get()).release());
    reports_pb->AddAllocated(report_entry_pb.release());
  }

  DCHECK(!reports_pb->empty());

  // Clear out the extension store data to ensure that:
  // - extension info is refreshed for every telemetry report.
  // - no stale extension entry is left over in the extension store.
  extension_store_.clear();

  return telemetry_report_pb;
}

std::unique_ptr<ExtensionTelemetryReportRequest>
ExtensionTelemetryService::CreateReportForEnterprise() {
  // Don't create a telemetry report if there were no signals generated (i.e.,
  // enterprise extension store is empty).
  if (!enterprise_enabled_ || enterprise_extension_store_.empty()) {
    return nullptr;
  }

  std::unique_ptr<ExtensionTelemetryReportRequest> telemetry_report_pb =
      CreateReportWithCommonFieldsPopulated();
  RepeatedPtrField<ExtensionTelemetryReportRequest_Report>* reports_pb =
      telemetry_report_pb->mutable_reports();

  // Create per-extension reports for all the extensions in the enterprise
  // extension store. These represent extensions that have signal information to
  // report.
  for (auto& extension_store_it : enterprise_extension_store_) {
    auto report_entry_pb =
        std::make_unique<ExtensionTelemetryReportRequest_Report>();

    // Populate all signal info for the extension by querying the signal
    // processors.
    RepeatedPtrField<ExtensionTelemetryReportRequest_SignalInfo>* signals_pb =
        report_entry_pb->mutable_signals();
    for (auto& processor_it : enterprise_signal_processors_) {
      std::unique_ptr<ExtensionTelemetryReportRequest_SignalInfo>
          signal_info_pb = processor_it.second->GetSignalInfoForReport(
              extension_store_it.first);
      if (signal_info_pb) {
        signals_pb->AddAllocated(signal_info_pb.release());
      }
    }

    report_entry_pb->set_allocated_extension(
        extension_store_it.second.release());
    reports_pb->AddAllocated(report_entry_pb.release());
  }

  DCHECK(!reports_pb->empty());

  // Clear out the enterprise extension store data.
  enterprise_extension_store_.clear();

  return telemetry_report_pb;
}

std::unique_ptr<SafeBrowsingTokenFetcher>
ExtensionTelemetryService::GetTokenFetcher() {
  DCHECK(!profile_->IsOffTheRecord() &&
         IsEnhancedProtectionEnabled(*profile_->GetPrefs()));
  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForProfile(profile_);
  if (identity_manager &&
      safe_browsing::SyncUtils::IsPrimaryAccountSignedIn(identity_manager)) {
    return std::make_unique<SafeBrowsingPrimaryAccountTokenFetcher>(
        identity_manager);
  }
  return nullptr;
}

void ExtensionTelemetryService::DumpReportForTesting(
    const ExtensionTelemetryReportRequest& report) {
  base::Time creation_time = base::Time::FromMillisecondsSinceUnixEpoch(
      report.creation_timestamp_msec());
  std::stringstream ss;
  ss << "Report creation time: "
     << base::UTF16ToUTF8(TimeFormatShortDateAndTimeWithTimeZone(creation_time))
     << "\n";
  ss << "Developer mode enabled: " << report.developer_mode_enabled() << "\n";
  ss << "Management authority: " << report.management_authority() << "\n";

  const RepeatedPtrField<ExtensionTelemetryReportRequest_Report>& reports =
      report.reports();

  for (const auto& report_pb : reports) {
    const auto& extension_pb = report_pb.extension();
    base::Time install_time = base::Time::FromMillisecondsSinceUnixEpoch(
        extension_pb.install_timestamp_msec());
    ss << "\nExtensionId: " << extension_pb.id() << "\n"
       << "  Name: " << extension_pb.name() << "\n"
       << "  Version: " << extension_pb.version() << "\n"
       << "  InstallTime: "
       << base::UTF16ToUTF8(
              TimeFormatShortDateAndTimeWithTimeZone(install_time))
       << "\n"
       << "  InstalledByDefault: "
       << (extension_pb.is_default_installed() ? "Y" : "N")
       << "  InstalledByOEM: " << (extension_pb.is_oem_installed() ? "Y" : "N")
       << "\n"
       << "  InstalledFromCWS: " << (extension_pb.is_from_store() ? "Y" : "N")
       << "  UpdatesFromCWS: "
       << (extension_pb.updates_from_store() ? "Y" : "N") << "\n"
       << "  ConvertedFromUserScript: "
       << (extension_pb.is_converted_from_user_script() ? "Y" : "N") << "\n"
       << "  Type: " << extension_pb.type()
       << "  InstallLocation: " << extension_pb.install_location()
       << "  BlocklistState: " << extension_pb.blocklist_state() << "\n"
       << "  DisableReasons: 0x" << std::hex << extension_pb.disable_reasons()
       << "\n"
       << "  InstallationPolicy: " << extension_pb.installation_policy()
       << "\n";

    if (extension_pb.has_manifest_json()) {
      ss << "  ManifestJSON: " << extension_pb.manifest_json() << "\n";
    }

    const RepeatedPtrField<
        ExtensionTelemetryReportRequest_ExtensionInfo_FileInfo>& file_infos =
        extension_pb.file_infos();
    if (!file_infos.empty()) {
      ss << "  FileInfos: \n";
      for (const auto& file_info : file_infos) {
        ss << "    File name: " << file_info.name()
           << " File hash: " << file_info.hash() << "\n";
      }
    }

    const RepeatedPtrField<ExtensionTelemetryReportRequest_SignalInfo>&
        signals = report_pb.signals();
    for (const auto& signal_pb : signals) {
      // Tabs Api
      if (signal_pb.has_tabs_api_info()) {
        const auto& tabs_api_info_pb = signal_pb.tabs_api_info();
        const RepeatedPtrField<
            ExtensionTelemetryReportRequest_SignalInfo_TabsApiInfo_CallDetails>&
            call_details = tabs_api_info_pb.call_details();
        if (!call_details.empty()) {
          ss << "  Signal: TabsApi\n";
          for (const auto& entry : call_details) {
            ss << "    Call Details:\n"
               << "      API method: "
               << base::NumberToString(static_cast<int>(entry.method())) << "\n"
               << "      current URL: " << entry.current_url() << "\n"
               << "      new URL: " << entry.new_url() << "\n"
               << "      count: " << entry.count() << "\n";
            const auto& js_callstacks = entry.js_callstacks();
            int stack_idx = 0;
            for (const auto& stack : js_callstacks) {
              ss << "      JS callstack " << stack_idx++ << " :";
              ss << ExtensionJSCallStacks::SignalInfoJSCallStackAsString(stack);
            }
          }
        }
        continue;
      }

      // Tabs Execute Script
      if (signal_pb.has_tabs_execute_script_info()) {
        const auto& tabs_execute_script_info_pb =
            signal_pb.tabs_execute_script_info();
        const RepeatedPtrField<
            ExtensionTelemetryReportRequest_SignalInfo_TabsExecuteScriptInfo_ScriptInfo>&
            scripts = tabs_execute_script_info_pb.scripts();
        if (!scripts.empty()) {
          ss << "  Signal: TabsExecuteScript\n";
          for (const auto& script_pb : scripts) {
            ss << "    Script hash: " << base::HexEncode(script_pb.hash())
               << " count: " << script_pb.execution_count() << "\n";
          }
        }
        continue;
      }

      // Remote Host Contacted
      if (signal_pb.has_remote_host_contacted_info()) {
        const auto& remote_host_contacted_info_pb =
            signal_pb.remote_host_contacted_info();
        const RepeatedPtrField<
            ExtensionTelemetryReportRequest_SignalInfo_RemoteHostContactedInfo_RemoteHostInfo>&
            remote_host_infos = remote_host_contacted_info_pb.remote_host();
        if (!remote_host_infos.empty()) {
          ss << "  Signal: RemoteHostContacted\n";
          for (const auto& remote_host_info_pb : remote_host_infos) {
            ss << "    RemoteHostInfo:\n"
               << "      URL: " << remote_host_info_pb.url() << "\n"
               << "      ConnectionProtocol: "
               << remote_host_info_pb.connection_protocol() << "\n"
               << "      ContactedBy: " << remote_host_info_pb.contacted_by()
               << "\n"
               << "      count: " << remote_host_info_pb.contact_count()
               << "\n";
          }
        }
        continue;
      }

      // Cookies Get All
      if (signal_pb.has_cookies_get_all_info()) {
        const auto& cookies_get_all_info_pb = signal_pb.cookies_get_all_info();
        const RepeatedPtrField<
            ExtensionTelemetryReportRequest_SignalInfo_CookiesGetAllInfo_GetAllArgsInfo>&
            get_all_args_infos = cookies_get_all_info_pb.get_all_args_info();
        if (!get_all_args_infos.empty()) {
          ss << "  Signal: CookiesGetAll\n";
          for (const auto& get_all_args_pb : get_all_args_infos) {
            ss << "    GetAllArgsInfo:\n"
               << "      Domain: " << get_all_args_pb.domain() << "\n"
               << "      Name: " << get_all_args_pb.name() << "\n"
               << "      Path: " << get_all_args_pb.path() << "\n"
               << "      Secure: " << (get_all_args_pb.secure() ? "Y" : "N")
               << "\n"
               << "      StoreId: " << get_all_args_pb.store_id() << "\n"
               << "      URL: " << get_all_args_pb.url() << "\n"
               << "      IsSession: "
               << (get_all_args_pb.is_session() ? "Y" : "N") << "\n"
               << "      count: " << get_all_args_pb.count() << "\n";
            const auto& js_callstacks = get_all_args_pb.js_callstacks();
            int stack_idx = 0;
            for (const auto& stack : js_callstacks) {
              ss << "      JS callstack " << stack_idx++ << " :";
              ss << ExtensionJSCallStacks::SignalInfoJSCallStackAsString(stack);
            }
          }
        }
        continue;
      }

      // Cookies Get
      if (signal_pb.has_cookies_get_info()) {
        const auto& cookies_get_info_pb = signal_pb.cookies_get_info();
        const RepeatedPtrField<
            ExtensionTelemetryReportRequest_SignalInfo_CookiesGetInfo_GetArgsInfo>&
            get_args_infos = cookies_get_info_pb.get_args_info();
        if (!get_args_infos.empty()) {
          ss << "  Signal: CookiesGet\n";
          for (const auto& get_args_pb : get_args_infos) {
            ss << "    GetArgsInfo:\n"
               << "      Name: " << get_args_pb.name() << "\n"
               << "      URL: " << get_args_pb.url() << "\n"
               << "      StoreId: " << get_args_pb.store_id() << "\n"
               << "      count: " << get_args_pb.count() << "\n";
            const auto& js_callstacks = get_args_pb.js_callstacks();
            int stack_idx = 0;
            for (const auto& stack : js_callstacks) {
              ss << "      JS callstack " << stack_idx++ << " :";
              ss << ExtensionJSCallStacks::SignalInfoJSCallStackAsString(stack);
            }
          }
        }
        continue;
      }

      // Potential Password Theft
      if (signal_pb.has_potential_password_theft_info()) {
        const auto& potential_password_theft_info_pb =
            signal_pb.potential_password_theft_info();
        const RepeatedPtrField<
            ExtensionTelemetryReportRequest_SignalInfo_PotentialPasswordTheftInfo_PasswordReuseInfo>&
            reused_password_infos =
                potential_password_theft_info_pb.reused_password_infos();
        const RepeatedPtrField<
            ExtensionTelemetryReportRequest_SignalInfo_PotentialPasswordTheftInfo_RemoteHostData>&
            remote_hosts_data =
                potential_password_theft_info_pb.remote_hosts_data();
        if (!reused_password_infos.empty() && !remote_hosts_data.empty()) {
          ss << "  Signal: PotentialPasswordTheft\n";
          for (const auto& remote_hosts_data_pb : remote_hosts_data) {
            ss << "    RemoteHostData:\n"
               << "      URL: " << remote_hosts_data_pb.remote_host_url()
               << "\n"
               << "      count: " << remote_hosts_data_pb.count() << "\n";
          }
          for (const auto& reused_password_infos_pb : reused_password_infos) {
            ss << "    PasswordReuseInfo:\n";
            ss << "      DomainsMatchingPassword:\n";
            for (const std::string& matching_domain :
                 reused_password_infos_pb.domains_matching_password()) {
              ss << "        " << matching_domain << "\n";
            }
            ss << "      IsChromeSigninPassword: "
               << (reused_password_infos_pb.is_chrome_signin_password() ? "Y"
                                                                        : "N")
               << "\n";
            ss << "      ReusedPasswordAccountType:\n";
            ss << "        IsAccountSyncing: "
               << (reused_password_infos_pb.reused_password_account_type()
                           .is_account_syncing()
                       ? "Y"
                       : "N")
               << "\n";
            ss << "        AccountType: "
               << reused_password_infos_pb.reused_password_account_type()
                      .account_type()
               << "\n";
            ss << "      ReuseCount: " << reused_password_infos_pb.count()
               << "\n";
          }
        }
      }

      // Declarative Net Request Api
      if (signal_pb.has_declarative_net_request_info()) {
        const auto& declarative_net_request_info_pb =
            signal_pb.declarative_net_request_info();
        const RepeatedPtrField<std::string>& rules =
            declarative_net_request_info_pb.rules();
        if (!rules.empty()) {
          ss << "  Signal: DeclarativeNetRequest\n";
          for (const auto& rule : rules) {
            ss << "    Rule:" << rule << "\n";
          }
          ss << "    MaxExceededRulesCount:"
             << declarative_net_request_info_pb.max_exceeded_rules_count()
             << "\n";
        }
        continue;
      }

      // Declarative Net Request Action
      if (signal_pb.has_declarative_net_request_action_info()) {
        const auto& dnr_action_info_pb =
            signal_pb.declarative_net_request_action_info();
        const RepeatedPtrField<
            ExtensionTelemetryReportRequest_SignalInfo_DeclarativeNetRequestActionInfo_ActionDetails>&
            action_details = dnr_action_info_pb.action_details();
        if (!action_details.empty()) {
          ss << "  Signal: DeclarativeNetRequestAction\n";
          for (const auto& entry : action_details) {
            ss << "    Action Details:\n"
               << "      action type: "
               << base::NumberToString(static_cast<int>(entry.type())) << "\n"
               << "      request URL: " << entry.request_url() << "\n"
               << "      redirect URL: " << entry.redirect_url() << "\n"
               << "      count: " << entry.count() << "\n";
          }
        }
        continue;
      }

      // DOM Access
      if (signal_pb.has_dom_access_info()) {
        const auto& dom_access_info_pb = signal_pb.dom_access_info();
        const RepeatedPtrField<
            ExtensionTelemetryReportRequest_SignalInfo_DOMAccessInfo_DOMAccess>&
            dom_accesses = dom_access_info_pb.dom_accesses();
        if (!dom_accesses.empty()) {
          ss << "  Signal: DOMAccess\n";
          for (const auto& entry : dom_accesses) {
            ss << "    DOM Access Details:\n"
               << "      api_name: " << entry.api_name() << "\n"
               << "      url: " << entry.url() << "\n"
               << "      access_type: "
               << base::NumberToString(static_cast<int>(entry.access_type()))
               << "\n"
               << "      count: " << entry.count() << "\n";
          }
        }
        continue;
      }

      // Script Injection
      if (signal_pb.has_script_injection_info()) {
        const auto& script_injection_info_pb =
            signal_pb.script_injection_info();
        const RepeatedPtrField<
            ExtensionTelemetryReportRequest_SignalInfo_ScriptInjectionInfo_ScriptInjection>&
            script_injections = script_injection_info_pb.script_injections();
        if (!script_injections.empty()) {
          ss << "  Signal: ScriptInjection\n";
          for (const auto& entry : script_injections) {
            ss << "    Script Injection Details:\n"
               << "      api_name: " << entry.api_name() << "\n"
               << "      url: " << entry.url() << "\n"
               << "      arg_url: " << entry.arg_url() << "\n";
            for (const auto& arg : entry.args_list()) {
              ss << "      arg: " << arg << "\n";
            }
            ss << "      count: " << entry.count() << "\n";
          }
        }
        continue;
      }
    }
  }

  DVLOG(1) << "Telemetry Report: " << ss.str();
}

ExtensionTelemetryService::OffstoreExtensionFileData::
    OffstoreExtensionFileData() = default;
ExtensionTelemetryService::OffstoreExtensionFileData::
    ~OffstoreExtensionFileData() = default;
ExtensionTelemetryService::OffstoreExtensionFileData::
    OffstoreExtensionFileData::OffstoreExtensionFileData(
        const OffstoreExtensionFileData& src) = default;

std::optional<ExtensionTelemetryService::OffstoreExtensionFileData>
ExtensionTelemetryService::RetrieveOffstoreFileDataForReport(
    const extensions::ExtensionId& extension_id) {
  const auto& pref_dict = GetExtensionTelemetryFileData(*pref_service_);
  const base::DictValue* extension_dict = pref_dict.FindDict(extension_id);
  if (!extension_dict) {
    return std::nullopt;
  }

  const base::DictValue* file_data_dict =
      extension_dict->FindDict(kFileDataDictPref);
  if (!file_data_dict || file_data_dict->empty()) {
    return std::nullopt;
  }

  OffstoreExtensionFileData offstore_extension_file_data;
  base::DictValue dict = file_data_dict->Clone();
  std::optional<base::Value> manifest_value = dict.Extract(kManifestFile);
  if (manifest_value.has_value()) {
    offstore_extension_file_data.manifest =
        std::move(manifest_value.value().GetString());
  }

  for (auto&& [file_name, file_hash] : dict) {
    ExtensionTelemetryReportRequest_ExtensionInfo_FileInfo file_info;
    file_info.set_name(std::move(file_name));
    file_info.set_hash(std::move(file_hash.GetString()));
    offstore_extension_file_data.file_infos.emplace_back(std::move(file_info));
  }
  return std::make_optional(offstore_extension_file_data);
}

std::unique_ptr<ExtensionInfo>
ExtensionTelemetryService::GetExtensionInfoForReport(
    const extensions::Extension& extension) {
  ExtensionManagement* extension_management =
      extensions::ExtensionManagementFactory::GetForBrowserContext(profile_);
  auto extension_info = std::make_unique<ExtensionInfo>();
  extension_info->set_id(extension.id());
  extension_info->set_name(extension.name());
  extension_info->set_version(extension.version().GetString());
  if (extension.location() == ManifestLocation::kCommandLine) {
    // Set the install timestamp to 0 explicitly for a command-line extension
    // to indicate that it is not actually installed. This is required
    // because the extension may still have associated extension prefs
    // from the last time it was installed (eg. when ESB was disabled).
    extension_info->set_install_timestamp_msec(0);
  } else {
    extension_info->set_install_timestamp_msec(
        GetLastUpdateTime(extension_prefs_, extension.id())
            .InMillisecondsSinceUnixEpoch());
  }
  extension_info->set_is_default_installed(
      extension.was_installed_by_default());
  extension_info->set_is_oem_installed(extension.was_installed_by_oem());
  extension_info->set_is_from_store(extension.from_webstore());
  extension_info->set_updates_from_store(
      extension_management->UpdatesFromWebstore(extension));
  extension_info->set_is_converted_from_user_script(
      extension.converted_from_user_script());
  extension_info->set_type(GetType(extension.GetType()));
  extension_info->set_install_location(
      GetInstallLocation(extension.location()));
  extension_info->set_blocklist_state(
      GetBlocklistState(extension.id(), extension_prefs_));
  extension_info->set_telemetry_blocklist_state(
      GetExtensionTelemetryServiceBlocklistState(extension.id(),
                                                 extension_prefs_));

  // Use the GetRawDisableReasons() getter here as we want all the disable
  // reasons (known and unknown).
  extensions::ExtensionPrefs::DisableReasonRawManipulationPasskey passkey;
  base::flat_set<int> disable_reasons =
      extension_prefs_->GetRawDisableReasons(passkey, extension.id());

  for (int reason : disable_reasons) {
    extension_info->add_disable_reasons_list(reason);
  }

  // TODO(crbug.com/372186532): Remove this code and deprecate the
  // disable_reasons field in the proto after the Safe Browsing service is
  // migrated to use disable_reasons_list.
  const int disable_reasons_bitflag =
      extensions::IntegerSetToBitflag(disable_reasons);
  extension_info->set_disable_reasons(disable_reasons_bitflag);

  if (base::FeatureList::IsEnabled(kExtensionTelemetryIncludePolicyData)) {
    bool installation_managed =
        extension_management->IsInstallationExplicitlyAllowed(extension.id()) ||
        extension_management->IsInstallationExplicitlyBlocked(extension.id());
    ExtensionInfo::InstallationPolicy installation_policy =
        installation_managed
            ? ExtensionManagementInstallationModeToExtensionInfoInstallationPolicy(
                  extension_management->GetInstallationMode(&extension))
            : ExtensionInfo::NO_POLICY;
    extension_info->set_installation_policy(installation_policy);
  }

  std::optional<OffstoreExtensionFileData> offstore_file_data =
      RetrieveOffstoreFileDataForReport(extension.id());

  if (offstore_file_data.has_value()) {
    extension_info->set_manifest_json(
        std::move(offstore_file_data.value().manifest));
    for (auto& file_info : offstore_file_data.value().file_infos) {
      extension_info->mutable_file_infos()->Add(std::move(file_info));
    }
  }

  return extension_info;
}

void ExtensionTelemetryService::SetUpSignalProcessorsAndSubscribersForESB() {
  // Create signal processors.
  // Map the processors to the signals they output reports for.
  signal_processors_.emplace(ExtensionSignalType::kCookiesGet,
                             std::make_unique<CookiesGetSignalProcessor>());
  signal_processors_.emplace(ExtensionSignalType::kCookiesGetAll,
                             std::make_unique<CookiesGetAllSignalProcessor>());
  signal_processors_.emplace(
      ExtensionSignalType::kDeclarativeNetRequestAction,
      std::make_unique<DeclarativeNetRequestActionSignalProcessor>());
  signal_processors_.emplace(
      ExtensionSignalType::kDeclarativeNetRequest,
      std::make_unique<DeclarativeNetRequestSignalProcessor>());
  signal_processors_.emplace(
      ExtensionSignalType::kPotentialPasswordTheft,
      std::make_unique<PotentialPasswordTheftSignalProcessor>());
  signal_processors_.emplace(
      ExtensionSignalType::kRemoteHostContacted,
      std::make_unique<RemoteHostContactedSignalProcessor>());
  signal_processors_.emplace(ExtensionSignalType::kTabsApi,
                             std::make_unique<TabsApiSignalProcessor>());
  signal_processors_.emplace(
      ExtensionSignalType::kTabsExecuteScript,
      std::make_unique<TabsExecuteScriptSignalProcessor>());

  // Create subscriber lists for each telemetry signal type.
  // Map the signal processors to the signals that they consume.
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      subscribers_for_cookies_get = {
          signal_processors_[ExtensionSignalType::kCookiesGet].get()};
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      subscribers_for_cookies_get_all = {
          signal_processors_[ExtensionSignalType::kCookiesGetAll].get()};
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      subscribers_for_declarative_net_request_action = {
          signal_processors_[ExtensionSignalType::kDeclarativeNetRequestAction]
              .get()};
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      subscribers_for_declarative_net_request = {
          signal_processors_[ExtensionSignalType::kDeclarativeNetRequest]
              .get()};
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      subscribers_for_password_reuse = {
          signal_processors_[ExtensionSignalType::kPotentialPasswordTheft]
              .get()};
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      subscribers_for_remote_host_contacted = {
          signal_processors_[ExtensionSignalType::kRemoteHostContacted].get(),
          signal_processors_[ExtensionSignalType::kPotentialPasswordTheft]
              .get()};

  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      subscribers_for_tabs_api = {
          signal_processors_[ExtensionSignalType::kTabsApi].get()};
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      subscribers_for_tabs_execute_script = {
          signal_processors_[ExtensionSignalType::kTabsExecuteScript].get()};

  signal_subscribers_.emplace(ExtensionSignalType::kCookiesGet,
                              std::move(subscribers_for_cookies_get));
  signal_subscribers_.emplace(ExtensionSignalType::kCookiesGetAll,
                              std::move(subscribers_for_cookies_get_all));
  signal_subscribers_.emplace(
      ExtensionSignalType::kDeclarativeNetRequestAction,
      std::move(subscribers_for_declarative_net_request_action));
  signal_subscribers_.emplace(
      ExtensionSignalType::kDeclarativeNetRequest,
      std::move(subscribers_for_declarative_net_request));
  signal_subscribers_.emplace(ExtensionSignalType::kPasswordReuse,
                              std::move(subscribers_for_password_reuse));
  signal_subscribers_.emplace(ExtensionSignalType::kRemoteHostContacted,
                              std::move(subscribers_for_remote_host_contacted));
  signal_subscribers_.emplace(ExtensionSignalType::kTabsApi,
                              std::move(subscribers_for_tabs_api));
  signal_subscribers_.emplace(ExtensionSignalType::kTabsExecuteScript,
                              std::move(subscribers_for_tabs_execute_script));
}

void ExtensionTelemetryService::
    SetUpSignalProcessorsAndSubscribersForEnterprise() {
  // Create signal processors.
  // Map the processors to the signals they output reports for.
  enterprise_signal_processors_.emplace(
      ExtensionSignalType::kCookiesGet,
      std::make_unique<CookiesGetSignalProcessor>());
  enterprise_signal_processors_.emplace(
      ExtensionSignalType::kCookiesGetAll,
      std::make_unique<CookiesGetAllSignalProcessor>());
  enterprise_signal_processors_.emplace(
      ExtensionSignalType::kRemoteHostContacted,
      std::make_unique<RemoteHostContactedSignalProcessor>());
  enterprise_signal_processors_.emplace(
      ExtensionSignalType::kTabsApi,
      std::make_unique<TabsApiSignalProcessor>());
  enterprise_signal_processors_.emplace(
      ExtensionSignalType::kDOMAccess,
      std::make_unique<DOMAccessSignalProcessor>());
  enterprise_signal_processors_.emplace(
      ExtensionSignalType::kScriptInjection,
      std::make_unique<ScriptInjectionSignalProcessor>());

  // Create subscriber lists for each telemetry signal type.
  // Map the signal processors to the signals that they consume.
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      enterprise_subscribers_for_cookies_get = {
          enterprise_signal_processors_[ExtensionSignalType::kCookiesGet]
              .get()};
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      enterprise_subscribers_for_cookies_get_all = {
          enterprise_signal_processors_[ExtensionSignalType::kCookiesGetAll]
              .get()};
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      enterprise_subscribers_for_remote_host_contacted = {
          enterprise_signal_processors_
              [ExtensionSignalType::kRemoteHostContacted]
                  .get()};
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      enterprise_subscribers_for_tabs_api = {
          enterprise_signal_processors_[ExtensionSignalType::kTabsApi].get()};
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      enterprise_subscribers_for_dom_access = {
          enterprise_signal_processors_[ExtensionSignalType::kDOMAccess].get()};
  std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
      enterprise_subscribers_for_script_injection = {
          enterprise_signal_processors_[ExtensionSignalType::kScriptInjection]
              .get()};

  enterprise_signal_subscribers_.emplace(
      ExtensionSignalType::kCookiesGet,
      std::move(enterprise_subscribers_for_cookies_get));
  enterprise_signal_subscribers_.emplace(
      ExtensionSignalType::kCookiesGetAll,
      std::move(enterprise_subscribers_for_cookies_get_all));
  enterprise_signal_subscribers_.emplace(
      ExtensionSignalType::kRemoteHostContacted,
      std::move(enterprise_subscribers_for_remote_host_contacted));
  enterprise_signal_subscribers_.emplace(
      ExtensionSignalType::kTabsApi,
      std::move(enterprise_subscribers_for_tabs_api));
  enterprise_signal_subscribers_.emplace(
      ExtensionSignalType::kDOMAccess,
      std::move(enterprise_subscribers_for_dom_access));
  enterprise_signal_subscribers_.emplace(
      ExtensionSignalType::kScriptInjection,
      std::move(enterprise_subscribers_for_script_injection));
}

ExtensionTelemetryService::OffstoreExtensionFileDataContext::
    OffstoreExtensionFileDataContext(
        const extensions::ExtensionId& extension_id,
        const base::FilePath& root_dir)
    : extension_id(extension_id), root_dir(root_dir) {}

ExtensionTelemetryService::OffstoreExtensionFileDataContext::
    OffstoreExtensionFileDataContext(
        const extensions::ExtensionId& extension_id,
        const base::FilePath& root_dir,
        const base::Time& last_processed_time)
    : extension_id(extension_id),
      root_dir(root_dir),
      last_processed_time(last_processed_time) {}

bool ExtensionTelemetryService::OffstoreExtensionFileDataContext::operator<(
    const OffstoreExtensionFileDataContext& other) const {
  // Use extension_id to break ties.
  return std::tie(last_processed_time, extension_id) <
         std::tie(other.last_processed_time, other.extension_id);
}

void ExtensionTelemetryService::SetUpOffstoreFileDataCollection() {
  // If both is enabled, set up has already been done.
  if (esb_enabled_ && enterprise_enabled_) {
    return;
  }

  // File data collection.
  file_processor_ = base::SequenceBound<ExtensionTelemetryFileProcessor>(
      base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
           base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
  offstore_file_data_collection_timer_.Start(
      FROM_HERE, kOffstoreFileDataCollectionStartupDelaySeconds, this,
      &ExtensionTelemetryService::StartOffstoreFileDataCollection);
}

void ExtensionTelemetryService::StartOffstoreFileDataCollection() {
  if (!enabled()) {
    return;
  }

  offstore_file_data_collection_start_time_ = base::TimeTicks::Now();
  offstore_extension_dirs_.clear();
  offstore_extension_file_data_contexts_.clear();
  GetOffstoreExtensionDirs();
  RemoveStaleExtensionsFileDataFromPref();

  // Gather context to process offstore extensions.
  const auto& pref_dict = GetExtensionTelemetryFileData(*pref_service_);
  for (const auto& [extension_id, root_dir] : offstore_extension_dirs_) {
    const base::DictValue* extension_dict = pref_dict.FindDict(extension_id);
    if (!extension_dict) {
      offstore_extension_file_data_contexts_.emplace(extension_id, root_dir);
      continue;
    }

    const base::Value* timestamp_value =
        extension_dict->Find(kFileDataProcessTimestampPref);
    if (!timestamp_value) {
      offstore_extension_file_data_contexts_.emplace(extension_id, root_dir);
      continue;
    }

    std::optional<base::Time> timestamp = base::ValueToTime(timestamp_value);
    if (!timestamp.has_value()) {
      offstore_extension_file_data_contexts_.emplace(extension_id, root_dir);
    } else if (base::Time::Now() - timestamp.value() > base::Days(1)) {
      offstore_extension_file_data_contexts_.emplace(extension_id, root_dir,
                                                     timestamp.value());
    }
  }

  CollectOffstoreFileData();
}

void ExtensionTelemetryService::OnCommandLineExtensionsInfoCollected(
    extensions::ExtensionSet commandline_extensions) {
  if (esb_enabled_) {
    // Only store this information if the telemetry service is enabled.
    commandline_extensions_ = std::move(commandline_extensions);
  }
}

void ExtensionTelemetryService::GetOffstoreExtensionDirs() {
  const extensions::ExtensionSet installed_extensions =
      extension_registry_->GenerateInstalledExtensionsSet();

  for (const auto& extension : installed_extensions) {
    if (!extension->from_webstore() &&
        !extensions::Manifest::IsComponentLocation(extension->location())) {
      offstore_extension_dirs_[extension->id()] = extension->path();
    }
  }

  // Also add any extensions that were part of the --load-extension commandline
  // switch if applicable. The information about these extensions is collected
  // (only one time) off-thread using `CollectCommandLineExtensionsInfo` when
  // the extension telemetry service is enabled.
  for (const auto& extension : commandline_extensions_) {
    offstore_extension_dirs_[extension->id()] = extension->path();
  }

  RecordNumOffstoreExtensions(offstore_extension_dirs_.size());
}

void ExtensionTelemetryService::RemoveStaleExtensionsFileDataFromPref() {
  ScopedDictPrefUpdate pref_update(pref_service_,
                                   prefs::kExtensionTelemetryFileData);
  base::DictValue& pref_dict = pref_update.Get();

  std::vector<extensions::ExtensionId> stale_extensions;
  for (auto&& offstore : pref_dict) {
    if (offstore_extension_dirs_.find(offstore.first) ==
        offstore_extension_dirs_.end()) {
      stale_extensions.emplace_back(offstore.first);
    }
  }

  for (const auto& extension : stale_extensions) {
    pref_dict.Remove(extension);
  }
}

void ExtensionTelemetryService::CollectOffstoreFileData() {
  if (!enabled()) {
    return;
  }

  // If data for all offstore extensions has been collected or
  // |offstore_file_data_collection_duration_limit_| has been
  // exceeded, start the timer again to schedule the next pass of data
  // collection.
  if (offstore_extension_file_data_contexts_.empty() ||
      (base::TimeTicks::Now() - offstore_file_data_collection_start_time_) >=
          offstore_file_data_collection_duration_limit_) {
    offstore_file_data_collection_timer_.Start(
        FROM_HERE, kOffstoreFileDataCollectionIntervalSeconds, this,
        &ExtensionTelemetryService::StartOffstoreFileDataCollection);

    // Record only if there are off-store extensions installed.
    if (!offstore_extension_dirs_.empty()) {
      RecordCollectionDuration(base::TimeTicks::Now() -
                               offstore_file_data_collection_start_time_);
    }
    return;
  }

  auto context = offstore_extension_file_data_contexts_.begin();
  auto process_extension_callback =
      base::BindOnce(&ExtensionTelemetryService::OnOffstoreFileDataCollected,
                     weak_factory_.GetWeakPtr(), context);
  file_processor_.AsyncCall(&ExtensionTelemetryFileProcessor::ProcessExtension)
      .WithArgs(context->root_dir)
      .Then(std::move(process_extension_callback));
}

void ExtensionTelemetryService::OnOffstoreFileDataCollected(
    base::flat_set<OffstoreExtensionFileDataContext>::iterator context,
    base::DictValue file_data) {
  // Save to Prefs
  base::DictValue extension_dict;
  extension_dict.Set(kFileDataProcessTimestampPref,
                     base::TimeToValue(base::Time::Now()));
  extension_dict.Set(kFileDataDictPref, std::move(file_data));

  ScopedDictPrefUpdate pref_update(pref_service_,
                                   prefs::kExtensionTelemetryFileData);
  pref_update->Set(context->extension_id, std::move(extension_dict));

  // Remove context and repeat.
  offstore_extension_file_data_contexts_.erase(context);
  CollectOffstoreFileData();
}

void ExtensionTelemetryService::UpdateDOMActivityLoggingState() {
  if (is_shutdown_) {
    return;
  }

  bool dom_telemetry_enabled = false;

#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
  dom_telemetry_enabled =
      enterprise_enabled_ &&
      base::FeatureList::IsEnabled(
          extensions_features::kEnterpriseExtensionDOMActivityTelemetry) &&
      GetExtensionTelemetryEventRouter(profile_)
          ->IsDOMActivityTelemetryEnabled();
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)

  if (dom_telemetry_enabled && !activity_log_ingester_) {
    activity_log_ingester_ =
        std::make_unique<ActivityLogIngester>(profile_, this);
  } else if (!dom_telemetry_enabled && activity_log_ingester_) {
    activity_log_ingester_.reset();
  }

  auto* management_service =
      policy::ManagementServiceFactory::GetForProfile(profile_);
  bool is_cloud_managed =
      management_service &&
      (management_service->HasManagementAuthority(
           policy::EnterpriseManagementAuthority::CLOUD_DOMAIN) ||
       management_service->HasManagementAuthority(
           policy::EnterpriseManagementAuthority::CLOUD));

  if (is_cloud_managed) {
    ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(
        "ExtensionDOMActivityTelemetry",
        dom_telemetry_enabled ? "Enabled" : "Disabled",
        variations::SyntheticTrialAnnotationMode::kCurrentLog);
  }
}

void ExtensionTelemetryService::StopOffstoreFileDataCollection() {
  if (file_processor_.is_null() || timer_.IsRunning() ||
      enterprise_timer_.IsRunning()) {
    return;
  }

  offstore_file_data_collection_timer_.Stop();
  offstore_extension_dirs_.clear();
  offstore_extension_file_data_contexts_.clear();
  commandline_extensions_.Clear();
  collected_commandline_extension_info_ = false;
}

void ExtensionTelemetryService::ProcessOffstoreExtensionVerdicts(
    const ExtensionTelemetryReportResponse& response) {
  if (response.offstore_extension_verdicts_size() == 0) {
    return;
  }

  std::map<std::string, extensions::BlocklistState> blocklist_states;
  for (const auto& verdict : response.offstore_extension_verdicts()) {
    const auto& extension_id = verdict.extension_id();
    const extensions::Extension* extension =
        extension_registry_->GetInstalledExtension(extension_id);

    // Ignore the verdict if the extension is not an off-store extension. This
    // should not happen and is just a sanity check.
    if (!extension || extension->from_webstore() ||
        extensions::Manifest::IsComponentLocation(extension->location())) {
      continue;
    }

    blocklist_states[extension_id] =
        ConvertTelemetryResponseVerdictToBlocklistState(verdict.verdict_type());
  }

  auto* extension_system = extensions::ExtensionSystem::Get(profile_);
  if (!extension_system) {
    return;
  }
  extensions::ExtensionService* extension_service =
      extension_system->extension_service();
  if (!extension_service) {
    return;
  }
  extension_service->PerformActionBasedOnExtensionTelemetryServiceVerdicts(
      blocklist_states);
}

base::TimeDelta
ExtensionTelemetryService::GetOffstoreFileDataCollectionStartupDelaySeconds() {
  return kOffstoreFileDataCollectionStartupDelaySeconds;
}

base::TimeDelta
ExtensionTelemetryService::GetOffstoreFileDataCollectionIntervalSeconds() {
  return kOffstoreFileDataCollectionIntervalSeconds;
}

void ExtensionTelemetryService::OnDseSerpLoaded() {
  if (search_hijacking_detector_) {
    search_hijacking_detector_->OnDseSerpLoaded();
  }
}

}  // namespace safe_browsing
