Commit aa7d46e9 authored by Andrey Zaytsev's avatar Andrey Zaytsev Committed by Commit Bot

Safety check: added extensions check

Bug: 1015841
Change-Id: I32a0a2622c45838df1a7cc51009d88e5adb8bde5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2089775
Commit-Queue: Andrey Zaytsev <andzaytsev@google.com>
Reviewed-by: default avatarFinnur Thorarinsson <finnur@chromium.org>
Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#749594}
parent 5fbe2348
......@@ -572,8 +572,7 @@ bool ExtensionService::UpdateExtension(const CRXFileInfo& file,
ExtensionPrefs::Get(profile_)->HasDisableReason(
id, disable_reason::DISABLE_PERMISSIONS_INCREASE);
const base::Version& expected_version = pending_extension_info->version();
if (has_permissions_increase ||
pending_extension_info->remote_install() ||
if (has_permissions_increase || pending_extension_info->remote_install() ||
!expected_version.IsValid()) {
installer->set_grant_permissions(false);
} else {
......@@ -724,8 +723,8 @@ bool ExtensionService::UninstallExtension(
InstallVerifier::Get(GetBrowserContext())->Remove(extension->id());
UMA_HISTOGRAM_ENUMERATION("Extensions.UninstallType",
extension->GetType(), 100);
UMA_HISTOGRAM_ENUMERATION("Extensions.UninstallType", extension->GetType(),
100);
RecordPermissionMessagesHistogram(extension.get(), "Uninstall");
// Unload before doing more cleanup to ensure that nothing is hanging on to
......@@ -749,8 +748,8 @@ bool ExtensionService::UninstallExtension(
extension_registrar_.UntrackTerminatedExtension(extension->id());
// Notify interested parties that we've uninstalled this extension.
ExtensionRegistry::Get(profile_)
->TriggerOnUninstalled(extension.get(), reason);
ExtensionRegistry::Get(profile_)->TriggerOnUninstalled(extension.get(),
reason);
delayed_installs_.Remove(extension->id());
......@@ -901,10 +900,8 @@ void ExtensionService::RecordPermissionMessagesHistogram(
// Since this is called from multiple sources, and since the histogram macros
// use statics, we need to manually lookup the histogram ourselves.
base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
base::StringPrintf("Extensions.Permissions_%s3", histogram),
1,
APIPermission::kEnumBoundary,
APIPermission::kEnumBoundary + 1,
base::StringPrintf("Extensions.Permissions_%s3", histogram), 1,
APIPermission::kEnumBoundary, APIPermission::kEnumBoundary + 1,
base::HistogramBase::kUmaTargetedHistogramFlag);
base::HistogramBase* counter_has_any = base::BooleanHistogram::FactoryGet(
......@@ -1522,21 +1519,20 @@ void ExtensionService::OnExtensionInstalled(
// showing the install dialogue).
extension_prefs_->AcknowledgeBlacklistedExtension(id);
UMA_HISTOGRAM_ENUMERATION("ExtensionBlacklist.SilentInstall",
extension->location(),
Manifest::NUM_LOCATIONS);
extension->location(), Manifest::NUM_LOCATIONS);
}
if (!registry_->GetInstalledExtension(extension->id())) {
UMA_HISTOGRAM_ENUMERATION("Extensions.InstallType",
extension->GetType(), 100);
UMA_HISTOGRAM_ENUMERATION("Extensions.InstallSource",
extension->location(), Manifest::NUM_LOCATIONS);
UMA_HISTOGRAM_ENUMERATION("Extensions.InstallType", extension->GetType(),
100);
UMA_HISTOGRAM_ENUMERATION("Extensions.InstallSource", extension->location(),
Manifest::NUM_LOCATIONS);
RecordPermissionMessagesHistogram(extension, "Install");
} else {
UMA_HISTOGRAM_ENUMERATION("Extensions.UpdateType",
extension->GetType(), 100);
UMA_HISTOGRAM_ENUMERATION("Extensions.UpdateSource",
extension->location(), Manifest::NUM_LOCATIONS);
UMA_HISTOGRAM_ENUMERATION("Extensions.UpdateType", extension->GetType(),
100);
UMA_HISTOGRAM_ENUMERATION("Extensions.UpdateSource", extension->location(),
Manifest::NUM_LOCATIONS);
}
const Extension::State initial_state =
......@@ -1659,8 +1655,7 @@ bool ExtensionService::FinishDelayedInstallationIfReady(
return true;
}
void ExtensionService::FinishInstallation(
const Extension* extension) {
void ExtensionService::FinishInstallation(const Extension* extension) {
const Extension* existing_extension =
registry_->GetInstalledExtension(extension->id());
bool is_update = false;
......@@ -1669,8 +1664,7 @@ void ExtensionService::FinishInstallation(
is_update = true;
old_name = existing_extension->name();
}
registry_->TriggerOnWillBeInstalled(
extension, is_update, old_name);
registry_->TriggerOnWillBeInstalled(extension, is_update, old_name);
// Unpacked extensions default to allowing file access, but if that has been
// overridden, don't reset the value.
......@@ -1845,7 +1839,7 @@ void ExtensionService::Observe(int type,
Profile* host_profile =
Profile::FromBrowserContext(process->GetBrowserContext());
if (!profile_->IsSameProfile(host_profile->GetOriginalProfile()))
break;
break;
ProcessMap* process_map = ProcessMap::Get(profile_);
if (process_map->Contains(process->GetID())) {
......@@ -2093,7 +2087,7 @@ void Partition(const ExtensionIdSet& before,
const ExtensionIdSet& unchanged,
ExtensionIdSet* no_longer,
ExtensionIdSet* not_yet) {
*not_yet = base::STLSetDifference<ExtensionIdSet>(after, before);
*not_yet = base::STLSetDifference<ExtensionIdSet>(after, before);
*no_longer = base::STLSetDifference<ExtensionIdSet>(before, after);
*no_longer = base::STLSetDifference<ExtensionIdSet>(*no_longer, unchanged);
}
......@@ -2120,8 +2114,7 @@ void ExtensionService::UpdateBlacklistedExtensions(
NOT_BLACKLISTED);
AddExtension(extension.get());
UMA_HISTOGRAM_ENUMERATION("ExtensionBlacklist.UnblacklistInstalled",
extension->location(),
Manifest::NUM_LOCATIONS);
extension->location(), Manifest::NUM_LOCATIONS);
}
for (auto it = not_yet_blocked.begin(); it != not_yet_blocked.end(); ++it) {
......@@ -2147,9 +2140,8 @@ void ExtensionService::UpdateGreylistedExtensions(
const ExtensionIdSet& unchanged,
const Blacklist::BlacklistStateMap& state_map) {
ExtensionIdSet not_yet_greylisted, no_longer_greylisted;
Partition(greylist_.GetIDs(),
greylist, unchanged,
&no_longer_greylisted, &not_yet_greylisted);
Partition(greylist_.GetIDs(), greylist, unchanged, &no_longer_greylisted,
&not_yet_greylisted);
for (auto it = no_longer_greylisted.begin(); it != no_longer_greylisted.end();
++it) {
......@@ -2210,6 +2202,12 @@ void ExtensionService::UnregisterInstallGate(InstallGate* install_delayer) {
}
}
bool ExtensionService::UserCanDisableInstalledExtension(
const std::string& extension_id) {
const Extension* extension = registry_->GetInstalledExtension(extension_id);
return CanDisableExtension(extension);
}
// Used only by test code.
void ExtensionService::UnloadAllExtensionsInternal() {
profile_->GetExtensionSpecialStoragePolicy()->RevokeRightsForAllExtensions();
......
......@@ -56,7 +56,7 @@ class Profile;
namespace base {
class CommandLine;
class OneShotEvent;
}
} // namespace base
FORWARD_DECLARE_TEST(BlacklistedExtensionSyncServiceTest,
SyncBlacklistedExtension);
......@@ -144,6 +144,10 @@ class ExtensionServiceInterface
// Whether the extension service is ready.
virtual bool is_ready() = 0;
// Whether a user is able to disable a given extension.
virtual bool UserCanDisableInstalledExtension(
const std::string& extension_id) = 0;
};
// Manages installed and running Chromium extensions. An instance is shared
......@@ -339,6 +343,11 @@ class ExtensionService : public ExtensionServiceInterface,
InstallGate* install_delayer);
void UnregisterInstallGate(InstallGate* install_delayer);
// Returns whether a user is able to disable a given extension or if that is
// not possible (for instance, extension was enabled by policy).
bool UserCanDisableInstalledExtension(
const std::string& extension_id) override;
//////////////////////////////////////////////////////////////////////////////
// Simple Accessors
......@@ -682,15 +691,12 @@ class ExtensionService : public ExtensionServiceInterface,
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
WillNotLoadBlacklistedExtensionsFromDirectory);
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest, ReloadBlacklistedExtension);
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
RemoveExtensionFromBlacklist);
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest, RemoveExtensionFromBlacklist);
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest, BlacklistedInPrefsFromStartup);
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
GreylistedExtensionDisabled);
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest, GreylistedExtensionDisabled);
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
GreylistDontEnableManuallyDisabled);
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
GreylistUnknownDontChange);
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest, GreylistUnknownDontChange);
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
ManagementPolicyProhibitsEnableOnInstalled);
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
......
......@@ -2,8 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/test_extension_service.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "testing/gtest/include/gtest/gtest.h"
using extensions::Extension;
......@@ -74,3 +75,9 @@ void TestExtensionService::RemoveComponentExtension(
const std::string& extension_id) {
ADD_FAILURE();
}
bool TestExtensionService::UserCanDisableInstalledExtension(
const std::string& extension_id) {
ADD_FAILURE();
return false;
}
......@@ -13,9 +13,9 @@
namespace extensions {
class CrxInstaller;
class Extension;
}
} // namespace extensions
// Implemention of ExtensionServiceInterface with default
// Implementation of ExtensionServiceInterface with default
// implementations for methods that add failures. You should subclass
// this and override the methods you care about.
class TestExtensionService : public extensions::ExtensionServiceInterface {
......@@ -45,6 +45,9 @@ class TestExtensionService : public extensions::ExtensionServiceInterface {
void UnloadExtension(const std::string& extension_id,
extensions::UnloadedExtensionReason reason) override;
void RemoveComponentExtension(const std::string& extension_id) override;
bool UserCanDisableInstalledExtension(
const std::string& extension_id) override;
};
#endif // CHROME_BROWSER_EXTENSIONS_TEST_EXTENSION_SERVICE_H_
......@@ -17,6 +17,9 @@
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension_id.h"
#include "ui/base/l10n/l10n_util.h"
#if defined(OS_CHROMEOS)
......@@ -30,10 +33,13 @@ constexpr char kUpdatesEvent[] = "safety-check-updates-status-changed";
constexpr char kPasswordsEvent[] = "safety-check-passwords-status-changed";
constexpr char kSafeBrowsingEvent[] =
"safety-check-safe-browsing-status-changed";
constexpr char kExtensionsEvent[] = "safety-check-extensions-status-changed";
constexpr char kPerformSafetyCheck[] = "performSafetyCheck";
constexpr char kNewState[] = "newState";
constexpr char kDisplayString[] = "displayString";
constexpr char kPasswordsCompromised[] = "passwordsCompromised";
constexpr char kExtensionsReenabledByUser[] = "extensionsReenabledByUser";
constexpr char kExtensionsReenabledByAdmin[] = "extensionsReenabledByAdmin";
// Converts the VersionUpdater::Status to the UpdateStatus enum to be passed
// to the safety check frontend. Note: if the VersionUpdater::Status gets
......@@ -71,27 +77,46 @@ SafetyCheckHandler::~SafetyCheckHandler() = default;
void SafetyCheckHandler::PerformSafetyCheck() {
AllowJavascript();
base::RecordAction(base::UserMetricsAction("SafetyCheck.Started"));
if (!version_updater_) {
version_updater_.reset(VersionUpdater::Create(web_ui()->GetWebContents()));
}
CheckUpdates();
CheckSafeBrowsing();
if (!leak_service_) {
leak_service_ = BulkLeakCheckServiceFactory::GetForProfile(
Profile::FromWebUI(web_ui()));
}
DCHECK(leak_service_);
CheckPasswords();
if (!extension_prefs_) {
extension_prefs_ = extensions::ExtensionPrefsFactory::GetForBrowserContext(
Profile::FromWebUI(web_ui()));
}
DCHECK(extension_prefs_);
if (!extension_service_) {
extension_service_ =
extensions::ExtensionSystem::Get(Profile::FromWebUI(web_ui()))
->extension_service();
}
DCHECK(extension_service_);
CheckExtensions();
}
SafetyCheckHandler::SafetyCheckHandler(
std::unique_ptr<VersionUpdater> version_updater,
password_manager::BulkLeakCheckService* leak_service)
password_manager::BulkLeakCheckService* leak_service,
extensions::ExtensionPrefs* extension_prefs,
extensions::ExtensionServiceInterface* extension_service)
: version_updater_(std::move(version_updater)),
leak_service_(leak_service) {}
leak_service_(leak_service),
extension_prefs_(extension_prefs),
extension_service_(extension_service) {}
void SafetyCheckHandler::HandlePerformSafetyCheck(
const base::ListValue* /*args*/) {
void SafetyCheckHandler::HandlePerformSafetyCheck(const base::ListValue* args) {
PerformSafetyCheck();
}
......@@ -131,6 +156,60 @@ void SafetyCheckHandler::CheckPasswords() {
// crrev.com/c/2072742 and follow up CLs).
}
void SafetyCheckHandler::CheckExtensions() {
extensions::ExtensionIdList extensions;
extension_prefs_->GetExtensions(&extensions);
int blocklisted = 0;
int reenabled_by_user = 0;
int reenabled_by_admin = 0;
for (auto extension_id : extensions) {
extensions::BlacklistState state =
extension_prefs_->GetExtensionBlacklistState(extension_id);
if (state == extensions::BLACKLISTED_UNKNOWN) {
// If any of the extensions are in the unknown blacklist state, that means
// there was an error the last time the blacklist was fetched. That means
// the results cannot be relied upon.
OnExtensionsCheckResult(ExtensionsStatus::kError, Blocklisted(0),
ReenabledUser(0), ReenabledAdmin(0));
return;
}
if (state == extensions::NOT_BLACKLISTED) {
continue;
}
++blocklisted;
if (!extension_service_->IsExtensionEnabled(extension_id)) {
continue;
}
if (extension_service_->UserCanDisableInstalledExtension(extension_id)) {
++reenabled_by_user;
} else {
++reenabled_by_admin;
}
}
if (blocklisted == 0) {
OnExtensionsCheckResult(ExtensionsStatus::kNoneBlocklisted, Blocklisted(0),
ReenabledUser(0), ReenabledAdmin(0));
} else if (reenabled_by_user == 0 && reenabled_by_admin == 0) {
OnExtensionsCheckResult(ExtensionsStatus::kBlocklistedAllDisabled,
Blocklisted(blocklisted), ReenabledUser(0),
ReenabledAdmin(0));
} else if (reenabled_by_user > 0 && reenabled_by_admin == 0) {
OnExtensionsCheckResult(ExtensionsStatus::kBlocklistedReenabledAllByUser,
Blocklisted(blocklisted),
ReenabledUser(reenabled_by_user),
ReenabledAdmin(0));
} else if (reenabled_by_admin > 0 && reenabled_by_user == 0) {
OnExtensionsCheckResult(ExtensionsStatus::kBlocklistedReenabledAllByAdmin,
Blocklisted(blocklisted), ReenabledUser(0),
ReenabledAdmin(reenabled_by_admin));
} else {
OnExtensionsCheckResult(ExtensionsStatus::kBlocklistedReenabledSomeByUser,
Blocklisted(blocklisted),
ReenabledUser(reenabled_by_user),
ReenabledAdmin(reenabled_by_admin));
}
}
void SafetyCheckHandler::OnUpdateCheckResult(VersionUpdater::Status status,
int progress,
bool rollback,
......@@ -164,6 +243,27 @@ void SafetyCheckHandler::OnPasswordsCheckResult(PasswordsStatus status,
FireWebUIListener(kPasswordsEvent, event);
}
void SafetyCheckHandler::OnExtensionsCheckResult(
ExtensionsStatus status,
Blocklisted blocklisted,
ReenabledUser reenabled_user,
ReenabledAdmin reenabled_admin) {
base::DictionaryValue event;
event.SetIntKey(kNewState, static_cast<int>(status));
if (status == ExtensionsStatus::kBlocklistedReenabledAllByUser ||
status == ExtensionsStatus::kBlocklistedReenabledSomeByUser) {
event.SetIntKey(kExtensionsReenabledByUser, reenabled_user.value());
}
if (status == ExtensionsStatus::kBlocklistedReenabledAllByAdmin ||
status == ExtensionsStatus::kBlocklistedReenabledSomeByUser) {
event.SetIntKey(kExtensionsReenabledByAdmin, reenabled_admin.value());
}
event.SetStringKey(kDisplayString,
GetStringForExtensions(status, Blocklisted(blocklisted),
reenabled_user, reenabled_admin));
FireWebUIListener(kExtensionsEvent, event);
}
base::string16 SafetyCheckHandler::GetStringForUpdates(UpdateStatus status) {
switch (status) {
case UpdateStatus::kChecking:
......@@ -246,6 +346,46 @@ base::string16 SafetyCheckHandler::GetStringForPasswords(PasswordsStatus status,
}
}
base::string16 SafetyCheckHandler::GetStringForExtensions(
ExtensionsStatus status,
Blocklisted blocklisted,
ReenabledUser reenabled_user,
ReenabledAdmin reenabled_admin) {
switch (status) {
case ExtensionsStatus::kChecking:
return l10n_util::GetStringUTF16(IDS_SETTINGS_SAFETY_CHECK_RUNNING);
case ExtensionsStatus::kError:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_ERROR);
case ExtensionsStatus::kNoneBlocklisted:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_SAFE);
case ExtensionsStatus::kBlocklistedAllDisabled:
return l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_OFF,
blocklisted.value());
case ExtensionsStatus::kBlocklistedReenabledAllByUser:
return l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_USER,
reenabled_user.value());
case ExtensionsStatus::kBlocklistedReenabledSomeByUser:
// TODO(crbug/1060625): Make string concatenation with a period
// internationalized (see go/i18n-concatenation).
return l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_USER,
reenabled_user.value()) +
base::ASCIIToUTF16(". ") +
l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_ADMIN,
reenabled_admin.value()) +
base::ASCIIToUTF16(".");
case ExtensionsStatus::kBlocklistedReenabledAllByAdmin:
return l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_ADMIN,
reenabled_admin.value());
}
}
void SafetyCheckHandler::OnStateChanged(
password_manager::BulkLeakCheckService::State state) {
using password_manager::BulkLeakCheckService;
......
......@@ -14,9 +14,13 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observer.h"
#include "base/util/type_safety/strong_alias.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/ui/webui/help/version_updater.h"
#include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
#include "components/password_manager/core/browser/bulk_leak_check_service.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
// Settings page UI handler that checks four areas of browser safety:
// browser updates, password leaks, malicious extensions, and unwanted
......@@ -55,6 +59,16 @@ class SafetyCheckHandler
kTooManyPasswords,
kError,
};
enum class ExtensionsStatus {
kChecking,
kError,
kNoneBlocklisted,
kBlocklistedAllDisabled,
kBlocklistedReenabledAllByUser,
// In this case, at least one of the extensions was re-enabled by admin.
kBlocklistedReenabledSomeByUser,
kBlocklistedReenabledAllByAdmin,
};
SafetyCheckHandler();
~SafetyCheckHandler() override;
......@@ -66,9 +80,17 @@ class SafetyCheckHandler
protected:
SafetyCheckHandler(std::unique_ptr<VersionUpdater> version_updater,
password_manager::BulkLeakCheckService* leak_service);
password_manager::BulkLeakCheckService* leak_service,
extensions::ExtensionPrefs* extension_prefs,
extensions::ExtensionServiceInterface* extension_service);
private:
// These ensure integers are passed in the correct possitions in the extension
// check methods.
using Blocklisted = util::StrongAlias<class BlocklistedTag, int>;
using ReenabledUser = util::StrongAlias<class ReenabledUserTag, int>;
using ReenabledAdmin = util::StrongAlias<class ReenabledAdminTag, int>;
// Handles triggering the safety check from the frontend (by user pressing a
// button).
void HandlePerformSafetyCheck(const base::ListValue* args);
......@@ -85,6 +107,10 @@ class SafetyCheckHandler
// results are available.
void CheckPasswords();
// Checks if any of the installed extensions are blocklisted, and in
// that case, if any of those were re-enabled.
void CheckExtensions();
// Callbacks that get triggered when each check completes.
void OnUpdateCheckResult(VersionUpdater::Status status,
int progress,
......@@ -94,6 +120,10 @@ class SafetyCheckHandler
const base::string16& message);
void OnSafeBrowsingCheckResult(SafeBrowsingStatus status);
void OnPasswordsCheckResult(PasswordsStatus status, int num_compromised);
void OnExtensionsCheckResult(ExtensionsStatus status,
Blocklisted blocklisted,
ReenabledUser reenabled_user,
ReenabledAdmin reenabled_admin);
// Methods for building user-visible strings based on the safety check
// state.
......@@ -101,6 +131,10 @@ class SafetyCheckHandler
base::string16 GetStringForSafeBrowsing(SafeBrowsingStatus status);
base::string16 GetStringForPasswords(PasswordsStatus status,
int num_compromised);
base::string16 GetStringForExtensions(ExtensionsStatus status,
Blocklisted blocklisted,
ReenabledUser reenabled_user,
ReenabledAdmin reenabled_admin);
// BulkLeakCheckService::Observer implementation.
void OnStateChanged(
......@@ -117,6 +151,8 @@ class SafetyCheckHandler
std::unique_ptr<VersionUpdater> version_updater_;
password_manager::BulkLeakCheckService* leak_service_ = nullptr;
extensions::ExtensionPrefs* extension_prefs_ = nullptr;
extensions::ExtensionServiceInterface* extension_service_ = nullptr;
ScopedObserver<password_manager::BulkLeakCheckService,
password_manager::BulkLeakCheckService::Observer>
observed_leak_check_{this};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment