blob: a5cfb94f79d466a4c164fbfdf523291a687c1bc2 [file] [log] [blame]
// Copyright 2024 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/manifest_v2_experiment_manager.h"
#include "base/auto_reset.h"
#include "base/functional/callback_forward.h"
#include "base/metrics/histogram_functions.h"
#include "base/one_shot_event.h"
#include "base/strings/stringprintf.h"
#include "base/types/pass_key.h"
#include "chrome/browser/extensions/chrome_extension_system_factory.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/extensions/mv2_experiment_stage.h"
#include "chrome/browser/extensions/profile_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_keyed_service_factory.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_factory.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/pref_names.h"
#include "extensions/browser/pref_types.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_features.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
namespace extensions {
namespace {
// Whether to override the MV2 deprecation for testing purposes.
bool g_allow_mv2_for_testing = false;
// Returns the suffix to use for histograms related to the manifest location
// grouping.
const char* GetHistogramManifestLocation(mojom::ManifestLocation location) {
switch (location) {
case mojom::ManifestLocation::kComponent:
case mojom::ManifestLocation::kExternalComponent:
return "Component";
case mojom::ManifestLocation::kExternalPolicy:
case mojom::ManifestLocation::kExternalPolicyDownload:
return "Policy";
case mojom::ManifestLocation::kCommandLine:
case mojom::ManifestLocation::kUnpacked:
return "Unpacked";
case mojom::ManifestLocation::kExternalRegistry:
case mojom::ManifestLocation::kExternalPref:
case mojom::ManifestLocation::kExternalPrefDownload:
return "External";
case mojom::ManifestLocation::kInternal:
return "Internal";
case mojom::ManifestLocation::kInvalidLocation:
NOTREACHED();
}
}
// Stores the bit for whether the user has acknowledged the MV2 deprecation
// notice for a given extension in the warning stage.
constexpr PrefMap kMV2DeprecationExtensionWarningAcknowledgedPref = {
"mv2_deprecation_warning_ack", PrefType::kBool,
PrefScope::kExtensionSpecific};
// Stores the bit for whether the user has acknowledged the MV2 deprecation
// notice for a given extension in the disabled stage.
constexpr PrefMap kMV2DeprecationExtensionDisabledAcknowledgedPref = {
"mv2_deprecation_disabled_ack", PrefType::kBool,
PrefScope::kExtensionSpecific};
// Stores a bit for whether the extension has been disabled as part of the
// MV2 deprecation.
constexpr PrefMap kMV2DeprecationDidDisablePref = {
"mv2_deprecation_did_disable", PrefType::kBool,
PrefScope::kExtensionSpecific};
// Stores a bit for whether the extension was re-enabled after being previously
// disabled as part of the MV2 deprecation.
// We store this separately from kMV2DeprecationDidDisablePref (as opposed to
// relying on an enum or a single bool) since there are multiple phases and
// causes for extensions to be disabled and re-enabled, and we only want to
// record that a user re-enables it if it was explicitly disabled by this phase
// of the experiment.
constexpr PrefMap kMV2DeprecationUserReEnabledPref = {
"mv2_deprecation_user_re_enabled", PrefType::kBool,
PrefScope::kExtensionSpecific};
class ManifestV2ExperimentManagerFactory : public ProfileKeyedServiceFactory {
public:
ManifestV2ExperimentManagerFactory();
ManifestV2ExperimentManagerFactory(
const ManifestV2ExperimentManagerFactory&) = delete;
ManifestV2ExperimentManagerFactory& operator=(
const ManifestV2ExperimentManagerFactory&) = delete;
~ManifestV2ExperimentManagerFactory() override = default;
ManifestV2ExperimentManager* GetForBrowserContext(
content::BrowserContext* context);
private:
// BrowserContextKeyedServiceFactory:
std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
content::BrowserContext* context) const override;
bool ServiceIsCreatedWithBrowserContext() const override;
};
ManifestV2ExperimentManagerFactory::ManifestV2ExperimentManagerFactory()
: ProfileKeyedServiceFactory(
"ManifestV2ExperimentManager",
ProfileSelections::Builder()
.WithRegular(ProfileSelection::kRedirectedToOriginal)
.WithGuest(ProfileSelection::kRedirectedToOriginal)
.WithAshInternals(ProfileSelection::kNone)
.Build()) {
DependsOn(ExtensionManagementFactory::GetInstance());
DependsOn(ExtensionPrefsFactory::GetInstance());
DependsOn(ChromeExtensionSystemFactory::GetInstance());
DependsOn(ExtensionRegistryFactory::GetInstance());
}
ManifestV2ExperimentManager*
ManifestV2ExperimentManagerFactory::GetForBrowserContext(
content::BrowserContext* browser_context) {
return static_cast<ManifestV2ExperimentManager*>(
GetServiceForBrowserContext(browser_context, /*create=*/true));
}
std::unique_ptr<KeyedService>
ManifestV2ExperimentManagerFactory::BuildServiceInstanceForBrowserContext(
content::BrowserContext* context) const {
return std::make_unique<ManifestV2ExperimentManager>(context);
}
bool ManifestV2ExperimentManagerFactory::ServiceIsCreatedWithBrowserContext()
const {
return true;
}
// Determines the current stage of the MV2 deprecation experiments.
MV2ExperimentStage CalculateCurrentExperimentStage() {
// Return the "highest" stage that is currently active for the user.
if (base::FeatureList::IsEnabled(
extensions_features::kExtensionManifestV2Unsupported)) {
return MV2ExperimentStage::kUnsupported;
}
if (base::FeatureList::IsEnabled(
extensions_features::kExtensionManifestV2Disabled)) {
return MV2ExperimentStage::kDisableWithReEnable;
}
return MV2ExperimentStage::kWarning;
}
// Returns the pref that stores whether the user has acknowledged the MV2
// deprecation notice for a given extension in `experiment_stage`.
PrefMap GetExtensionAcknowledgedPrefFor(MV2ExperimentStage experiment_stage) {
switch (experiment_stage) {
case MV2ExperimentStage::kWarning:
return kMV2DeprecationExtensionWarningAcknowledgedPref;
case MV2ExperimentStage::kDisableWithReEnable:
return kMV2DeprecationExtensionDisabledAcknowledgedPref;
case MV2ExperimentStage::kUnsupported:
NOTREACHED();
}
}
// Returns the pref that stores whether the user has acknowledged the MV2
// deprecation global notice in `experiment_stage`.
PrefMap GetGlobalNoticeAcknowledgedPrefFor(
MV2ExperimentStage experiment_stage) {
switch (experiment_stage) {
case MV2ExperimentStage::kWarning:
return kMV2DeprecationWarningAcknowledgedGloballyPref;
case MV2ExperimentStage::kDisableWithReEnable:
return kMV2DeprecationDisabledAcknowledgedGloballyPref;
case MV2ExperimentStage::kUnsupported:
return kMV2DeprecationUnsupportedAcknowledgedGloballyPref;
}
}
// Returns true if legacy extensions should be disabled, looking at both
// experiment stage and global state.
bool ShouldDisableLegacyExtensions(MV2ExperimentStage stage) {
if (g_allow_mv2_for_testing) {
// We allow legacy MV2 extensions for testing purposes.
return false;
}
switch (stage) {
case MV2ExperimentStage::kWarning:
return false;
case MV2ExperimentStage::kDisableWithReEnable:
case MV2ExperimentStage::kUnsupported:
return true;
}
}
// Returns true if the given `stage` is one in which extension enablement should
// potentially be blocked.
bool ShouldBlockLegacyExtensionEnableForStage(MV2ExperimentStage stage) {
// The times in which we block extension enablement are a strict subset of
// those when we disable legacy extensions.
if (!ShouldDisableLegacyExtensions(stage)) {
return false;
}
// We only block extension enablement in the `kUnsupported` phase.
// (We use a switch just to ensure compile errors if we ever add a new phase.)
switch (stage) {
case MV2ExperimentStage::kWarning:
case MV2ExperimentStage::kDisableWithReEnable:
return false;
case MV2ExperimentStage::kUnsupported:
return true;
}
}
// Returns true if unpacked extensions should be blocked in the given experiment
// `stage`. This is true only for cases where MV2 extensions are fully
// unsupported.
bool ShouldBlockUnpackedExtensions(MV2ExperimentStage stage) {
if (!ShouldBlockLegacyExtensionEnableForStage(stage)) {
return false;
}
// If the developer has flipped the explicit flag to allow legacy MV2
// extensions, we allow the unpacked extension to be loaded.
if (base::FeatureList::IsEnabled(
extensions_features::kAllowLegacyMV2Extensions)) {
return false;
}
return true;
}
// Returns true if the user is allowed to re-enable disabled extensions in the
// given experiment `stage`.
bool UserCanReEnableExtensionsForStage(MV2ExperimentStage stage) {
switch (stage) {
case MV2ExperimentStage::kWarning:
case MV2ExperimentStage::kDisableWithReEnable:
return true;
case MV2ExperimentStage::kUnsupported:
return false;
}
}
} // namespace
ManifestV2ExperimentManager::ManifestV2ExperimentManager(
content::BrowserContext* browser_context)
: experiment_stage_(CalculateCurrentExperimentStage()),
// Note: passing `ExtensionManagement` is safe and guaranteed to outlive
// the `impact_checker_` because this class is a KeyedService that depends
// on `ExtensionManagement`.
impact_checker_(
ExtensionManagementFactory::GetForBrowserContext(browser_context)),
browser_context_(browser_context) {
registry_observation_.Observe(ExtensionRegistry::Get(browser_context));
ExtensionSystem::Get(browser_context)
->ready()
.Post(FROM_HERE,
base::BindOnce(&ManifestV2ExperimentManager::OnExtensionSystemReady,
weak_factory_.GetWeakPtr()));
// Listen to management policy changes. `Unretained` is safe since the
// `pref_change_registrar` is owned by this class.
pref_change_registrar_.Init(
Profile::FromBrowserContext(browser_context_)->GetPrefs());
pref_change_registrar_.Add(
pref_names::kManifestV2Availability,
base::BindRepeating(
&ManifestV2ExperimentManager::OnManagementPolicyChanged,
base::Unretained(this)));
}
ManifestV2ExperimentManager::~ManifestV2ExperimentManager() = default;
// static
ManifestV2ExperimentManager* ManifestV2ExperimentManager::Get(
content::BrowserContext* browser_context) {
return static_cast<ManifestV2ExperimentManagerFactory*>(GetFactory())
->GetForBrowserContext(browser_context);
}
// static
BrowserContextKeyedServiceFactory* ManifestV2ExperimentManager::GetFactory() {
static base::NoDestructor<ManifestV2ExperimentManagerFactory> g_factory;
return g_factory.get();
}
MV2ExperimentStage ManifestV2ExperimentManager::GetCurrentExperimentStage() {
return experiment_stage_;
}
bool ManifestV2ExperimentManager::IsExtensionAffected(
const Extension& extension) {
return impact_checker_.IsExtensionAffected(extension);
}
bool ManifestV2ExperimentManager::ShouldBlockExtensionInstallation(
const ExtensionId& extension_id,
int manifest_version,
Manifest::Type manifest_type,
mojom::ManifestLocation manifest_location,
const HashedExtensionId& hashed_id) {
// Only block extension installation during phases in which legacy extensions
// are automatically disabled.
if (!ShouldDisableLegacyExtensions(experiment_stage_)) {
return false;
}
// Unpacked extensions are special-cased (since developers need to be able to
// continue supporting them). Check if unpacked extensions are blocked, either
// by the experiment phase or by enterprise policy.
ExtensionManagement* extension_management =
ExtensionManagementFactory::GetForBrowserContext(browser_context_);
if (Manifest::IsUnpackedLocation(manifest_location) &&
!ShouldBlockUnpackedExtensions(experiment_stage_) &&
extension_management->IsAllowedManifestVersion(
manifest_version, extension_id, manifest_type)) {
return false;
}
// Otherwise, if the extension is affected by the deprecation, it should be
// blocked.
return impact_checker_.IsExtensionAffected(extension_id, manifest_version,
manifest_type, manifest_location,
hashed_id);
}
bool ManifestV2ExperimentManager::ShouldBlockExtensionEnable(
const Extension& extension) {
if (!ShouldBlockLegacyExtensionEnableForStage(experiment_stage_)) {
return false;
}
// Block unpacked extension enablement only if unpacked extensions are blocked
// in this stage.
if (Manifest::IsUnpackedLocation(extension.location()) &&
!ShouldBlockUnpackedExtensions(experiment_stage_)) {
return false;
}
return impact_checker_.IsExtensionAffected(
extension.id(), extension.manifest_version(), extension.GetType(),
extension.location(), extension.hashed_id());
}
bool ManifestV2ExperimentManager::DidUserAcknowledgeNotice(
const ExtensionId& extension_id) {
// The notice cannot be acknowledged in kUnsupported stage.
if (experiment_stage_ == MV2ExperimentStage::kUnsupported) {
return false;
}
bool acknowledged = false;
PrefMap pref = GetExtensionAcknowledgedPrefFor(experiment_stage_);
return extension_prefs()->ReadPrefAsBoolean(extension_id, pref,
&acknowledged) &&
acknowledged;
}
void ManifestV2ExperimentManager::MarkNoticeAsAcknowledged(
const ExtensionId& extension_id) {
// The notice cannot be acknowledged in kUnsupported stage.
if (experiment_stage_ == MV2ExperimentStage::kUnsupported) {
return;
}
PrefMap pref = GetExtensionAcknowledgedPrefFor(experiment_stage_);
extension_prefs()->SetBooleanPref(extension_id, pref, true);
}
bool ManifestV2ExperimentManager::DidUserAcknowledgeNoticeGlobally() {
PrefMap pref = GetGlobalNoticeAcknowledgedPrefFor(experiment_stage_);
return extension_prefs()->GetPrefAsBoolean(pref);
}
void ManifestV2ExperimentManager::MarkNoticeAsAcknowledgedGlobally() {
PrefMap pref = GetGlobalNoticeAcknowledgedPrefFor(experiment_stage_);
extension_prefs()->SetBooleanPref(pref, true);
}
ExtensionPrefs* ManifestV2ExperimentManager::extension_prefs() {
if (!extension_prefs_) {
extension_prefs_ = ExtensionPrefs::Get(browser_context_);
}
return extension_prefs_;
}
void ManifestV2ExperimentManager::OnExtensionSystemReady() {
CheckDisabledExtensions();
DisableAffectedExtensions();
EmitMetricsForProfileReady();
is_manager_ready_ = true;
on_manager_ready_callback_list_.Notify();
}
base::CallbackListSubscription
ManifestV2ExperimentManager::RegisterOnManagerReadyCallback(
base::RepeatingClosure callback) {
CHECK(!is_manager_ready_);
return on_manager_ready_callback_list_.Add(std::move(callback));
}
void ManifestV2ExperimentManager::SetHasTriggeredDisabledDialog(
bool has_triggered) {
has_triggered_disabled_dialog_ = has_triggered;
}
void ManifestV2ExperimentManager::DisableAffectedExtensions() {
if (!ShouldDisableLegacyExtensions(experiment_stage_)) {
return;
}
ExtensionRegistry* extension_registry =
ExtensionRegistry::Get(browser_context_);
std::set<scoped_refptr<const Extension>> extensions_to_disable;
// Whether the user is allowed to re-enable disabled extensions in the given
// experiment phase.
bool user_reenable_allowed =
UserCanReEnableExtensionsForStage(experiment_stage_);
// Whether unpacked extensions are exempt from being disabled.
// Note that this is distinct from `ShouldBlockUnpackedExtensions()`, since
// unpacked extensions are only blocked in "unsupported" phase, but they may
// be *disabled* in other phases.
bool unpacked_extensions_are_exempt = base::FeatureList::IsEnabled(
extensions_features::kAllowLegacyMV2Extensions);
for (const auto& extension : extension_registry->enabled_extensions()) {
if (!impact_checker_.IsExtensionAffected(*extension)) {
continue;
}
if (user_reenable_allowed && DidUserReEnableExtension(extension->id())) {
// The user explicitly chose to re-enable the extension after it was
// disabled, and that's allowed in this experiment stage. Allow it to
// remain enabled.
continue;
}
if (Manifest::IsUnpackedLocation(extension->location()) &&
unpacked_extensions_are_exempt) {
continue;
}
extensions_to_disable.insert(extension);
}
auto* registrar = ExtensionRegistrar::Get(browser_context_);
for (const auto& extension : extensions_to_disable) {
registrar->DisableExtension(
extension->id(),
{disable_reason::DISABLE_UNSUPPORTED_MANIFEST_VERSION});
extension_prefs()->SetBooleanPref(extension->id(),
kMV2DeprecationDidDisablePref, true);
}
}
void ManifestV2ExperimentManager::CheckDisabledExtensions() {
ExtensionRegistry* extension_registry =
ExtensionRegistry::Get(browser_context_);
ExtensionSet disabled_extensions;
// Loop through all disabled extensions. For each, check if they should be
// re-enabled (e.g. because they've updated to MV3 or a change in policy
// settings).
// Use a copy of the set to avoid changing the set while iterating.
disabled_extensions.InsertAll(extension_registry->disabled_extensions());
for (const auto& extension : disabled_extensions) {
MaybeReEnableExtension(*extension);
}
}
void ManifestV2ExperimentManager::MaybeReEnableExtension(
const Extension& extension) {
if (!extension_prefs()->HasDisableReason(
extension.id(),
disable_reason::DISABLE_UNSUPPORTED_MANIFEST_VERSION)) {
// We only care about extensions that were disabled for this reason.
return;
}
// Check if the extension is still affected *and* whether the environment is
// still one in which extensions should be disabled. It's possible the user
// moved from a later experiment stage to an earlier one or set a feature
// flag, in which case extensions should be re-enabled.
if (impact_checker_.IsExtensionAffected(extension) &&
ShouldDisableLegacyExtensions(experiment_stage_)) {
return;
}
// Remove the bit that the extension was disabled by the MV2 deprecation,
// since it no longer is. This also ensures we don't count it as user-
// re-enabled, if it gets re-enabled below.
extension_prefs()->SetBooleanPref(extension.id(),
kMV2DeprecationDidDisablePref, false);
// Remove the disable reason (possibly re-enabling the extension).
ExtensionRegistrar::Get(browser_context_)
->RemoveDisableReasonAndMaybeEnable(
extension.id(), disable_reason::DISABLE_UNSUPPORTED_MANIFEST_VERSION);
}
bool ManifestV2ExperimentManager::DidUserReEnableExtension(
const ExtensionId& extension_id) {
bool acknowledged = false;
return extension_prefs()->ReadPrefAsBoolean(
extension_id, kMV2DeprecationUserReEnabledPref, &acknowledged) &&
acknowledged;
}
void ManifestV2ExperimentManager::EmitMetricsForProfileReady() {
if (!ShouldDisableLegacyExtensions(experiment_stage_)) {
// Don't bother reporting MV2-specific metrics if the user isn't in an
// environment in which extensions could be disabled.
return;
}
if (!profile_util::ProfileCanUseNonComponentExtensions(
Profile::FromBrowserContext(browser_context_))) {
// Don't report metrics if the user can't install extensions in this
// profile.
return;
}
ExtensionRegistry* extension_registry =
ExtensionRegistry::Get(browser_context_);
auto emit_state_for_mv2_extension = [this](const Extension& extension,
bool is_enabled) {
if (extension.manifest_version() != 2) {
return;
}
if (extension.GetType() != Manifest::TYPE_EXTENSION &&
extension.GetType() != Manifest::TYPE_LOGIN_SCREEN_EXTENSION) {
return;
}
if (Manifest::IsComponentLocation(extension.location())) {
return;
}
bool user_reenabled = DidUserReEnableExtension(extension.id());
MV2ExtensionState extension_state = MV2ExtensionState::kUnaffected;
if (!impact_checker_.IsExtensionAffected(extension)) {
extension_state = MV2ExtensionState::kUnaffected;
} else if (is_enabled && user_reenabled) {
extension_state = MV2ExtensionState::kUserReEnabled;
} else if (extension_prefs()->HasDisableReason(
extension.id(),
disable_reason::DISABLE_UNSUPPORTED_MANIFEST_VERSION)) {
CHECK(!is_enabled);
CHECK(experiment_stage_ == MV2ExperimentStage::kUnsupported ||
experiment_stage_ == MV2ExperimentStage::kDisableWithReEnable);
extension_state = experiment_stage_ == MV2ExperimentStage::kUnsupported
? MV2ExtensionState::kHardDisabled
: MV2ExtensionState::kSoftDisabled;
} else {
extension_state = MV2ExtensionState::kOther;
}
std::string histogram_name =
base::StringPrintf("Extensions.MV2Deprecation.MV2ExtensionState.%s",
GetHistogramManifestLocation(extension.location()));
base::UmaHistogramEnumeration(histogram_name, extension_state);
};
for (const auto& extension : extension_registry->enabled_extensions()) {
emit_state_for_mv2_extension(*extension, /*is_enabled=*/true);
}
for (const auto& extension : extension_registry->disabled_extensions()) {
emit_state_for_mv2_extension(*extension, /*is_enabled=*/false);
}
}
void ManifestV2ExperimentManager::RecordUkmForExtension(
const GURL& extension_url,
ExtensionMV2DeprecationAction action) {
if (experiment_stage_ != MV2ExperimentStage::kDisableWithReEnable) {
// The UKM is only emitted for the "disable with re-enable" phase. We do
// not need UKM for the "hard disable" phase (as the only action available
// to the user is to remove or find alternatives).
return;
}
ukm::builders::Extensions_MV2ExtensionHandledInSoftDisable(
ukm::UkmRecorder::GetSourceIdForExtensionUrl(
base::PassKey<ManifestV2ExperimentManager>(), extension_url))
.SetAction(static_cast<int64_t>(action))
.Record(ukm::UkmRecorder::Get());
}
void ManifestV2ExperimentManager::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
bool was_extension_disabled_by_mv2_deprecation = false;
extension_prefs()->ReadPrefAsBoolean(
extension->id(), kMV2DeprecationDidDisablePref,
&was_extension_disabled_by_mv2_deprecation);
if (!was_extension_disabled_by_mv2_deprecation) {
return;
}
extension_prefs()->SetBooleanPref(extension->id(),
kMV2DeprecationUserReEnabledPref, true);
RecordUkmForExtension(extension->url(),
ExtensionMV2DeprecationAction::kReEnabled);
}
void ManifestV2ExperimentManager::OnExtensionInstalled(
content::BrowserContext* browser_context,
const Extension* extension,
bool is_update) {
if (!is_update) {
// We would only ever re-enable a disabled extension if it was already
// installed. No need to look at new installs.
return;
}
MaybeReEnableExtension(*extension);
}
void ManifestV2ExperimentManager::OnExtensionUninstalled(
content::BrowserContext* browser_context,
const Extension* extension,
UninstallReason uninstall_reason) {
// We only care about user uninstallations...
if (uninstall_reason != UNINSTALL_REASON_USER_INITIATED) {
return;
}
// ... of extensions that were disabled by the MV2 deprecation.
bool was_extension_disabled_by_mv2_deprecation = false;
extension_prefs()->ReadPrefAsBoolean(
extension->id(), kMV2DeprecationDidDisablePref,
&was_extension_disabled_by_mv2_deprecation);
if (!was_extension_disabled_by_mv2_deprecation) {
return;
}
RecordUkmForExtension(extension->url(),
ExtensionMV2DeprecationAction::kRemoved);
}
void ManifestV2ExperimentManager::OnManagementPolicyChanged() {
// The management policy has changed. Go through all disabled extensions to
// check if any should be re-enabled, and go through all enabled extensions
// to see if any should be disabled (if the experiment is active).
CheckDisabledExtensions();
DisableAffectedExtensions();
}
bool ManifestV2ExperimentManager::DidUserReEnableExtensionForTesting(
const ExtensionId& extension_id) {
return DidUserReEnableExtension(extension_id);
}
void ManifestV2ExperimentManager::DisableAffectedExtensionsForTesting() {
DisableAffectedExtensions();
}
void ManifestV2ExperimentManager::EmitMetricsForProfileReadyForTesting() {
EmitMetricsForProfileReady();
}
base::AutoReset<bool> ManifestV2ExperimentManager::AllowMV2ExtensionsForTesting(
base::PassKey<ScopedTestMV2Enabler> pass_key) {
return base::AutoReset<bool>(&g_allow_mv2_for_testing, true);
}
} // namespace extensions