1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
|
// 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.
#ifndef CHROME_BROWSER_EXTENSIONS_MANIFEST_V2_EXPERIMENT_MANAGER_H_
#define CHROME_BROWSER_EXTENSIONS_MANIFEST_V2_EXPERIMENT_MANAGER_H_
#include "base/auto_reset.h"
#include "base/callback_list.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/types/pass_key.h"
#include "chrome/browser/extensions/mv2_deprecation_impact_checker.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/prefs/pref_change_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension_id.h"
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
class BrowserContextKeyedServiceFactory;
namespace content {
class BrowserContext;
} // namespace content
namespace extensions {
class Extension;
class ExtensionPrefs;
class ScopedTestMV2Enabler;
enum class MV2ExperimentStage;
// The central class responsible for managing experiments related to the MV2
// deprecation.
class ManifestV2ExperimentManager : public KeyedService,
public ExtensionRegistryObserver {
public:
explicit ManifestV2ExperimentManager(
content::BrowserContext* browser_context);
ManifestV2ExperimentManager(const ManifestV2ExperimentManager&) = delete;
ManifestV2ExperimentManager& operator=(const ManifestV2ExperimentManager&) =
delete;
~ManifestV2ExperimentManager() override;
// The possible states for an MV2 extension during the experiments.
// Do not re-order entries, as these are used in histograms.
// Exposed for testing purposes.
enum class MV2ExtensionState {
// Extension is unaffected by the MV2 deprecation (e.g., it's a policy-
// installed extension with the proper enterprise policies set).
kUnaffected = 0,
// The extension was disabled by Chrome, but may be re-enabled by the user.
kSoftDisabled = 1,
// The extension was previously disabled by Chrome, but was re-enabled by
// the user.
kUserReEnabled = 2,
// Any other state. This includes e.g. extensions that are disabled, but for
// other reasons.
kOther = 3,
// The extension is disabled, and may not be re-enabled by the user.
kHardDisabled = 4,
kMaxValue = kHardDisabled,
};
// Possible actions taken by the user on an MV2 extension.
// Do not re-order entries, as these are used in UKM.
// Exposed for testing purposes.
enum class ExtensionMV2DeprecationAction {
kRemoved,
kReEnabled,
};
// Retrieves the ManifestV2ExperimentManager associated with the given
// `browser_context`. Note this instance is shared between on- and off-the-
// record contexts.
static ManifestV2ExperimentManager* Get(
content::BrowserContext* browser_context);
// Returns the singleton instance of the factory for this KeyedService.
static BrowserContextKeyedServiceFactory* GetFactory();
// Returns the current experiment stage for the MV2 experiment. Note: You
// should only use this for determining the experiment stage itself. For
// determining if an extension is affected, use IsExtensionAffected() below.
MV2ExperimentStage GetCurrentExperimentStage();
// Returns true if the given `extension` is affected by the MV2 deprecation.
// This may be false if, e.g., the extension is policy-installed.
bool IsExtensionAffected(const Extension& extension);
// Returns true if a new installation of the given `extension_id` should be
// blocked.
bool ShouldBlockExtensionInstallation(
const ExtensionId& extension_id,
int manifest_version,
Manifest::Type manifest_type,
mojom::ManifestLocation manifest_location,
const HashedExtensionId& hashed_id);
// Returns true if Chrome should disallow enabling the given `extension`.
bool ShouldBlockExtensionEnable(const Extension& extension);
// Returns true if the notice for `extension_id` has been acknowledged by the
// user during the current MV2 deprecation `experiment_stage_`.
bool DidUserAcknowledgeNotice(const ExtensionId& extension_id);
// Called to indicate the user chose to acknowledge the notice for
// `extension_id` during the current MV2 deprecation `experiment_stage_`.
void MarkNoticeAsAcknowledged(const ExtensionId& extension_id);
// Returns true if the user has acknowledge the notice during the current MV2
// deprecation `experiment_stage_`.
bool DidUserAcknowledgeNoticeGlobally();
// Called to indicate the user chose to acknowledge the global notice during
// the current MV2 deprecation `experiment_stage_`..
void MarkNoticeAsAcknowledgedGlobally();
// Registers `callback` to run when this has finished its initialization
// steps. `is_manager_ready_` must be false for this to be called.
base::CallbackListSubscription RegisterOnManagerReadyCallback(
base::RepeatingClosure callback);
// Whether the disabled dialog has been triggered for this `browser_context_`.
bool has_triggered_disabled_dialog() {
return has_triggered_disabled_dialog_;
}
// This should be called when a new window is opened for `browser_context_`.
void SetHasTriggeredDisabledDialog(bool has_triggered);
// Returns whether this has finished its initialization steps.
bool is_manager_ready() { return is_manager_ready_; }
bool DidUserReEnableExtensionForTesting(const ExtensionId& extension_id);
// Helpers to call internal methods directly for testing purposes. These are
// useful to have an extension that's installed in the body of a test get
// disabled, since this normally only happens on startup.
void DisableAffectedExtensionsForTesting();
void EmitMetricsForProfileReadyForTesting();
// See ScopedTestMV2Enabler for details.
static base::AutoReset<bool> AllowMV2ExtensionsForTesting(
base::PassKey<ScopedTestMV2Enabler> pass_key);
private:
// Lazily initialize and access `extension_prefs_`. We do this lazily because:
// - This service is created on Profile creation.
// - A bunch of unit tests override ExtensionPrefs after Profile creation, but
// before the "real" test starts.
// As such, if we instantiated ExtensionPrefs in the constructor, it would be
// the improper ExtensionPrefs object and would trigger raw_ptr violations.
ExtensionPrefs* extension_prefs();
// Called when the extension system has finished its initialization steps.
void OnExtensionSystemReady();
// Disables any Manifest V2 extensions that are affected by the experiment,
// if the user hasn't chosen to re-enable them.
void DisableAffectedExtensions();
// Loops through disabled extensions and checks if any should be re-enabled.
void CheckDisabledExtensions();
// Re-enables the `extension` if it should no longer be disabled by the MV2
// deprecation (e.g., if it updated to MV3).
void MaybeReEnableExtension(const Extension& extension);
// Returns true if a user re-enabled an extension after it was explicitly
// disabled by the MV2 deprecation.
bool DidUserReEnableExtension(const ExtensionId& extension_id);
// Emits metrics about the state of installed extensions related to the
// MV2 deprecation.
void EmitMetricsForProfileReady();
// Emits a UKM record for the extension associated with `extension_url` and
// the corresponding `action`.
void RecordUkmForExtension(const GURL& extension_url,
ExtensionMV2DeprecationAction action);
// ExtensionRegistry:
void OnExtensionLoaded(content::BrowserContext* browser_context,
const Extension* extension) override;
void OnExtensionInstalled(content::BrowserContext* browser_context,
const Extension* extension,
bool is_update) override;
void OnExtensionUninstalled(content::BrowserContext* browser_context,
const Extension* extension,
UninstallReason reason) override;
// Called when the management policy for MV2 is changed.
void OnManagementPolicyChanged();
// The current stage of the MV2 deprecation experiments.
const MV2ExperimentStage experiment_stage_;
// A helper object to determine if a given extension is affected by the
// MV2 deprecation experiments.
MV2DeprecationImpactChecker impact_checker_;
// The associated ExtensionPrefs. Guaranteed to be safe to use since this
// class depends upon them via the KeyedService infrastructure.
raw_ptr<ExtensionPrefs> extension_prefs_;
// The associated BrowserContext. Guaranteed to be safe to use since this is
// a KeyedService for the context.
raw_ptr<content::BrowserContext> browser_context_;
PrefChangeRegistrar pref_change_registrar_;
// Whether the disabled dialog has been triggered for this `browser_context_`.
bool has_triggered_disabled_dialog_ = false;
// Whether this class has finished its initialization steps.
bool is_manager_ready_ = false;
// Callback to be run when this has finished its initialization steps.
base::RepeatingCallbackList<void()> on_manager_ready_callback_list_;
base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
registry_observation_{this};
base::WeakPtrFactory<ManifestV2ExperimentManager> weak_factory_{this};
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_MANIFEST_V2_EXPERIMENT_MANAGER_H_
|