blob: 3ad788b30c557cd842a83a04d50b81d167333a4f [file] [log] [blame]
// Copyright 2025 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/chrome_extension_registrar_delegate.h"
#include <set>
#include <string>
#include "base/barrier_closure.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notimplemented.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/extensions/corrupted_extension_reinstaller.h"
#include "chrome/browser/extensions/data_deleter.h"
#include "chrome/browser/extensions/extension_allowlist.h"
#include "chrome/browser/extensions/extension_assets_manager.h"
#include "chrome/browser/extensions/extension_disabled_ui.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/extensions/extension_special_storage_policy.h"
#include "chrome/browser/extensions/external_install_manager.h"
#include "chrome/browser/extensions/install_verifier_factory.h"
#include "chrome/browser/extensions/installed_loader.h"
#include "chrome/browser/extensions/permissions/permissions_updater.h"
#include "chrome/browser/extensions/profile_util.h"
#include "chrome/browser/extensions/unpacked_installer.h"
#include "chrome/browser/extensions/updater/extension_updater.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/favicon_source.h"
#include "chrome/common/webui_url_constants.h"
#include "components/favicon_base/favicon_url_parser.h"
#include "extensions/browser/delayed_install_manager.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/install_flag.h"
#include "extensions/browser/install_verifier.h"
#include "extensions/browser/pending_extension_manager.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/crash_keys.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/incognito_info.h"
#include "extensions/common/manifest_handlers/shared_module_info.h"
#include "extensions/common/mojom/manifest.mojom-shared.h"
#include "extensions/common/permissions/permission_message_provider.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_data.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/fileapi/file_system_backend.h"
#include "content/public/browser/storage_partition.h"
#include "storage/browser/file_system/file_system_context.h"
#endif
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/extensions/sync/extension_sync_service.h"
#include "chrome/browser/ui/webui/theme_source.h"
#endif
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
using extensions::mojom::ManifestLocation;
namespace extensions {
namespace {
// When uninstalling an extension, determine if the extension's directory
// should be deleted when uninstalling. Returns `true` iff extension is
// unpacked and installed outside the unpacked extensions installations dir.
// Example: packed extensions are always deleted. But unpacked extensions are
// in a folder outside the profile dir are not deleted.
bool SkipDeleteExtensionDir(const Extension& extension,
const base::FilePath& profile_path) {
bool is_unpacked_location =
Manifest::IsUnpackedLocation(extension.location());
bool extension_dir_not_direct_subdir_of_unpacked_extensions_install_dir =
extension.path().DirName() !=
profile_path.AppendASCII(extensions::kUnpackedInstallDirectoryName);
return is_unpacked_location &&
extension_dir_not_direct_subdir_of_unpacked_extensions_install_dir;
}
} // namespace
ChromeExtensionRegistrarDelegate::ChromeExtensionRegistrarDelegate(
Profile* profile)
: profile_(profile),
system_(ExtensionSystem::Get(profile_)),
extension_prefs_(ExtensionPrefs::Get(profile_)),
registry_(ExtensionRegistry::Get(profile_)),
component_loader_(ComponentLoader::Get(profile_)) {}
ChromeExtensionRegistrarDelegate::~ChromeExtensionRegistrarDelegate() = default;
void ChromeExtensionRegistrarDelegate::Init(ExtensionRegistrar* registrar) {
extension_registrar_ = registrar;
}
void ChromeExtensionRegistrarDelegate::Shutdown() {
// Avoid dangling pointers.
profile_ = nullptr;
extension_prefs_ = nullptr;
system_ = nullptr;
registry_ = nullptr;
extension_registrar_ = nullptr;
component_loader_ = nullptr;
}
void ChromeExtensionRegistrarDelegate::PreAddExtension(
const Extension* extension,
const Extension* old_extension) {
// An extension may have updated to no longer support incognito. When this
// is the case, we don't show the toggle in the chrome://extensions page.
// In order to ensure an extension doesn't keep an unrevokable permission,
// reset the stored pref.
if (old_extension && !IncognitoInfo::IsIncognitoAllowed(extension)) {
extension_prefs_->SetIsIncognitoEnabled(extension->id(), false);
}
// Check if the extension's privileges have changed and mark the
// extension disabled if necessary.
CheckPermissionsIncrease(extension, !!old_extension);
}
void ChromeExtensionRegistrarDelegate::OnAddNewOrUpdatedExtension(
const Extension* extension) {
if (InstallVerifier::NeedsVerification(*extension, profile_)) {
InstallVerifierFactory::GetForBrowserContext(profile_)->VerifyExtension(
extension->id());
}
}
void ChromeExtensionRegistrarDelegate::PostActivateExtension(
scoped_refptr<const Extension> extension) {
// Update policy permissions in case they were changed while extension was not
// active.
PermissionsUpdater(profile_).ApplyPolicyHostRestrictions(*extension);
// TODO(kalman): Convert ExtensionSpecialStoragePolicy to a
// BrowserContextKeyedService and use ExtensionRegistryObserver.
auto* special_storage_policy = profile_->GetExtensionSpecialStoragePolicy();
CHECK(special_storage_policy);
special_storage_policy->GrantRightsForExtension(extension.get(), profile_);
// TODO(kalman): This is broken. The crash reporter is process-wide so doesn't
// work properly multi-profile. Besides which, it should be using
// ExtensionRegistryObserver. See http://crbug.com/355029.
UpdateActiveExtensionsInCrashReporter();
const PermissionsData* permissions_data = extension->permissions_data();
// If the extension has permission to load chrome://favicon/ resources we need
// to make sure that the FaviconSource is registered with the
// ChromeURLDataManager.
if (permissions_data->HasHostPermission(GURL(chrome::kChromeUIFaviconURL))) {
content::URLDataSource::Add(
profile_, std::make_unique<FaviconSource>(
profile_, chrome::FaviconUrlFormat::kFaviconLegacy));
}
// Same for chrome://theme/ resources.
if (permissions_data->HasHostPermission(GURL(chrome::kChromeUIThemeURL))) {
#if BUILDFLAG(ENABLE_EXTENSIONS)
content::URLDataSource::Add(profile_,
std::make_unique<ThemeSource>(profile_));
#else
// TODO(crbug.com/408507365): Figure out the theme story on desktop Android
// and port ThemeSource if necessary.
NOTIMPLEMENTED() << "Themes not yet supported on desktop Android.";
#endif
}
}
void ChromeExtensionRegistrarDelegate::PostDeactivateExtension(
scoped_refptr<const Extension> extension) {
// TODO(kalman): Convert ExtensionSpecialStoragePolicy to a
// BrowserContextKeyedService and use ExtensionRegistryObserver.
auto* special_storage_policy = profile_->GetExtensionSpecialStoragePolicy();
CHECK(special_storage_policy);
special_storage_policy->RevokeRightsForExtension(extension.get(), profile_);
#if BUILDFLAG(IS_CHROMEOS)
// Revoke external file access for the extension from its file system context.
// It is safe to access the extension's storage partition at this point. The
// storage partition may get destroyed only after the extension gets unloaded.
storage::FileSystemContext* filesystem_context =
util::GetStoragePartitionForExtensionId(extension->id(), profile_)
->GetFileSystemContext();
if (filesystem_context && ash::FileSystemBackend::Get(*filesystem_context)) {
ash::FileSystemBackend::Get(*filesystem_context)
->RevokeAccessForOrigin(extension->origin());
}
#endif
// TODO(kalman): This is broken. The crash reporter is process-wide so doesn't
// work properly multi-profile. Besides which, it should be using
// ExtensionRegistryObserver::OnExtensionLoaded. See http://crbug.com/355029.
UpdateActiveExtensionsInCrashReporter();
}
void ChromeExtensionRegistrarDelegate::PreUninstallExtension(
scoped_refptr<const Extension> extension) {
InstallVerifierFactory::GetForBrowserContext(profile_)->Remove(
extension->id());
}
void ChromeExtensionRegistrarDelegate::PostUninstallExtension(
scoped_refptr<const Extension> extension,
base::OnceClosure done_callback) {
// Prepare barrier closure for UninstallExtensionOnFileThread() task (if
// applicable) and DataDeleter::StartDeleting().
bool is_unpacked_location =
Manifest::IsUnpackedLocation(extension->location());
base::RepeatingClosure subtask_done_callback = base::DoNothing();
if (!done_callback.is_null()) {
int num_tasks = is_unpacked_location ? 1 : 2;
subtask_done_callback =
base::BarrierClosure(num_tasks, std::move(done_callback));
}
// Delete extensions in profile directory (from webstore, or from .crx), but
// do not delete unpacked in a folder outside the profile directory.
if (!SkipDeleteExtensionDir(*extension, profile_->GetPath())) {
// Extensions installed from webstore or .crx are versioned in subdirs so we
// delete the parent dir. Unpacked (installed from .zip rather than folder)
// are not versioned so we just delete the single installation directory.
base::FilePath extension_dir_to_delete =
is_unpacked_location ? extension->path() : extension->path().DirName();
base::FilePath extensions_install_dir =
is_unpacked_location
? extension_registrar_->unpacked_install_directory()
: extension_registrar_->install_directory();
// Tell the backend to start deleting the installed extension on the file
// thread.
if (!GetExtensionFileTaskRunner()->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&ChromeExtensionRegistrarDelegate::
UninstallExtensionOnFileThread,
extension->id(), profile_->GetProfileUserName(),
std::move(extensions_install_dir),
std::move(extension_dir_to_delete),
profile_->GetPath()),
subtask_done_callback)) {
NOTREACHED();
}
}
DataDeleter::StartDeleting(profile_, extension.get(), subtask_done_callback);
}
void ChromeExtensionRegistrarDelegate::DoLoadExtensionForReload(
const ExtensionId& extension_id,
const base::FilePath& path,
bool load_error_behavior_noisy) {
// If we're reloading a component extension, use the component extension
// loader's reloader.
if (component_loader_->Exists(extension_id)) {
component_loader_->Reload(extension_id);
return;
}
// Check the installed extensions to see if what we're reloading was already
// installed.
std::optional<ExtensionInfo> installed_extension(
extension_prefs_->GetInstalledExtensionInfo(extension_id));
if (installed_extension && installed_extension->extension_manifest.get()) {
InstalledLoader(profile_).Load(*installed_extension, false);
} else {
// Otherwise, the extension is unpacked (location LOAD). We must load it
// from the path.
CHECK(!path.empty()) << "ExtensionRegistrar should never ask to load an "
"unknown extension with no path";
scoped_refptr<UnpackedInstaller> unpacked_installer =
UnpackedInstaller::Create(profile_);
unpacked_installer->set_be_noisy_on_failure(load_error_behavior_noisy);
unpacked_installer->set_completion_callback(base::BindOnce(
&ChromeExtensionRegistrarDelegate::OnUnpackedReloadFailure,
weak_factory_.GetWeakPtr()));
unpacked_installer->Load(path);
}
}
void ChromeExtensionRegistrarDelegate::LoadExtensionForReload(
const ExtensionId& extension_id,
const base::FilePath& path) {
DoLoadExtensionForReload(extension_id, path, true);
}
void ChromeExtensionRegistrarDelegate::LoadExtensionForReloadWithQuietFailure(
const ExtensionId& extension_id,
const base::FilePath& path) {
DoLoadExtensionForReload(extension_id, path, false);
}
void ChromeExtensionRegistrarDelegate::ShowExtensionDisabledError(
const Extension* extension,
bool is_remote_install) {
AddExtensionDisabledError(profile_, extension, is_remote_install);
}
bool ChromeExtensionRegistrarDelegate::CanEnableExtension(
const Extension* extension) {
CHECK(system_->management_policy());
return !system_->management_policy()->MustRemainDisabled(extension, nullptr);
}
bool ChromeExtensionRegistrarDelegate::CanDisableExtension(
const Extension* extension) {
// Some extensions cannot be disabled by users:
// - |extension| can be null if sync disables an extension that is not
// installed yet; allow disablement in this case.
if (!extension) {
return true;
}
// - Shared modules are just resources used by other extensions, and are not
// user-controlled.
if (SharedModuleInfo::IsSharedModule(extension)) {
return false;
}
// - EXTERNAL_COMPONENT extensions are not generally modifiable by users, but
// can be uninstalled by the browser if the user sets extension-specific
// preferences.
if (extension->location() == ManifestLocation::kExternalComponent) {
return true;
}
CHECK(system_->management_policy());
return system_->management_policy()->UserMayModifySettings(extension,
nullptr);
}
void ChromeExtensionRegistrarDelegate::GrantActivePermissions(
const Extension* extension) {
PermissionsUpdater(profile_).GrantActivePermissions(extension);
}
void ChromeExtensionRegistrarDelegate::UpdateExternalExtensionAlert() {
ExternalInstallManager::Get(profile_)->UpdateExternalExtensionAlert();
}
void ChromeExtensionRegistrarDelegate::OnExtensionInstalled(
const Extension* extension,
const syncer::StringOrdinal& page_ordinal,
int install_flags,
base::Value::Dict ruleset_install_prefs) {
const std::string& id = extension->id();
base::flat_set<int> disable_reasons =
extension_registrar_->GetDisableReasonsOnInstalled(extension);
std::string install_parameter;
auto* pending_extension_manager = PendingExtensionManager::Get(profile_);
const PendingExtensionInfo* pending_extension_info =
pending_extension_manager->GetById(id);
auto* corrupted_extension_reinstaller =
CorruptedExtensionReinstaller::Get(profile_);
bool is_reinstall_for_corruption =
corrupted_extension_reinstaller->IsReinstallForCorruptionExpected(id);
if (is_reinstall_for_corruption) {
corrupted_extension_reinstaller->MarkResolved(id);
}
if (pending_extension_info) {
if (!pending_extension_info->ShouldAllowInstall(extension, profile_)) {
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Note: Theme is unsupported on desktop Android.
// Hack for crbug.com/558299, see comment on DeleteThemeDoNotUse.
if (extension->is_theme() && pending_extension_info->is_from_sync()) {
ExtensionSyncService::Get(profile_)->DeleteThemeDoNotUse(*extension);
}
#endif
pending_extension_manager->Remove(id);
ExtensionManagement* management =
ExtensionManagementFactory::GetForBrowserContext(profile_);
LOG(WARNING) << "ShouldAllowInstall() returned false for " << id
<< " of type " << extension->GetType() << " and update URL "
<< management->GetEffectiveUpdateURL(*extension).spec()
<< "; not installing";
// Delete the extension directory since we're not going to
// load it.
if (!GetExtensionFileTaskRunner()->PostTask(
FROM_HERE,
base::GetDeletePathRecursivelyCallback(extension->path()))) {
NOTREACHED();
}
return;
}
install_parameter = pending_extension_info->install_parameter();
pending_extension_manager->Remove(id);
} else if (!is_reinstall_for_corruption) {
// We explicitly want to re-enable an uninstalled external
// extension; if we're here, that means the user is manually
// installing the extension.
if (extension_prefs_->IsExternalExtensionUninstalled(id)) {
disable_reasons.clear();
}
}
// If the old version of the extension was disabled due to corruption, this
// new install may correct the problem.
disable_reasons.erase(disable_reason::DISABLE_CORRUPTED);
// Unsupported requirements overrides the management policy.
if (install_flags & kInstallFlagHasRequirementErrors) {
disable_reasons.insert(disable_reason::DISABLE_UNSUPPORTED_REQUIREMENT);
} else {
// Requirement is supported now, remove the corresponding disable reason
// instead.
disable_reasons.erase(disable_reason::DISABLE_UNSUPPORTED_REQUIREMENT);
}
// Check if the extension was disabled because of the minimum version
// requirements from enterprise policy, and satisfies it now.
if (ExtensionManagementFactory::GetForBrowserContext(profile_)
->CheckMinimumVersion(extension, nullptr)) {
// And remove the corresponding disable reason.
disable_reasons.erase(disable_reason::DISABLE_UPDATE_REQUIRED_BY_POLICY);
}
if (install_flags & kInstallFlagIsBlocklistedForMalware) {
// Installation of a blocklisted extension can happen from sync, policy,
// etc, where to maintain consistency we need to install it, just never
// load it (see AddExtension). Usually it should be the job of callers to
// intercept blocklisted extensions earlier (e.g. CrxInstaller, before even
// showing the install dialogue).
extension_prefs_->AcknowledgeBlocklistedExtension(id);
UMA_HISTOGRAM_ENUMERATION("ExtensionBlacklist.SilentInstall",
extension->location());
}
RecordInstallHistograms(extension);
ExtensionAllowlist::Get(profile_)->OnExtensionInstalled(id, install_flags);
DelayedInstallManager* delayed_install_manager =
DelayedInstallManager::Get(profile_);
ExtensionPrefs::DelayReason delay_reason;
InstallGate::Action action =
delayed_install_manager->ShouldDelayExtensionInstall(
extension, !!(install_flags & kInstallFlagInstallImmediately),
&delay_reason);
switch (action) {
case InstallGate::INSTALL:
extension_registrar_->AddNewOrUpdatedExtension(
extension, disable_reasons, install_flags, page_ordinal,
install_parameter, std::move(ruleset_install_prefs));
return;
case InstallGate::DELAY:
extension_prefs_->SetDelayedInstallInfo(
extension, disable_reasons, install_flags, delay_reason, page_ordinal,
install_parameter, std::move(ruleset_install_prefs));
// Transfer ownership of |extension|.
delayed_install_manager->Insert(extension);
if (delay_reason == ExtensionPrefs::DelayReason::kWaitForIdle) {
ExtensionUpdater::Get(profile_)->NotifyAppUpdateAvailable(*extension);
}
return;
case InstallGate::ABORT:
// Do nothing to abort the install. One such case is the shared module
// service gets IMPORT_STATUS_UNRECOVERABLE status for the pending
// install.
return;
}
NOTREACHED() << "Unknown action for delayed install: " << action;
}
void ChromeExtensionRegistrarDelegate::CheckPermissionsIncrease(
const Extension* extension,
bool is_extension_loaded) {
PermissionsUpdater(profile_).InitializePermissions(extension);
// We keep track of all permissions the user has granted each extension.
// This allows extensions to gracefully support backwards compatibility
// by including unknown permissions in their manifests. When the user
// installs the extension, only the recognized permissions are recorded.
// When the unknown permissions become recognized (e.g., through browser
// upgrade), we can prompt the user to accept these new permissions.
// Extensions can also silently upgrade to less permissions, and then
// silently upgrade to a version that adds these permissions back.
//
// For example, pretend that Chrome 10 includes a permission "omnibox"
// for an API that adds suggestions to the omnibox. An extension can
// maintain backwards compatibility while still having "omnibox" in the
// manifest. If a user installs the extension on Chrome 9, the browser
// will record the permissions it recognized, not including "omnibox."
// When upgrading to Chrome 10, "omnibox" will be recognized and Chrome
// will disable the extension and prompt the user to approve the increase
// in privileges. The extension could then release a new version that
// removes the "omnibox" permission. When the user upgrades, Chrome will
// still remember that "omnibox" had been granted, so that if the
// extension once again includes "omnibox" in an upgrade, the extension
// can upgrade without requiring this user's approval.
// Silently grant all active permissions to pre-installed apps and apps
// installed in kiosk mode.
bool auto_grant_permission =
extension->was_installed_by_default() ||
ExtensionsBrowserClient::Get()->IsRunningInForcedAppMode();
if (auto_grant_permission) {
PermissionsUpdater(profile_).GrantActivePermissions(extension);
}
bool is_privilege_increase = false;
// We only need to compare the granted permissions to the current permissions
// if the extension has not been auto-granted its permissions above and is
// installed internally.
if (extension->location() == ManifestLocation::kInternal &&
!auto_grant_permission) {
// Add all the recognized permissions if the granted permissions list
// hasn't been initialized yet.
std::unique_ptr<const PermissionSet> granted_permissions =
extension_prefs_->GetGrantedPermissions(extension->id());
CHECK(granted_permissions.get());
// We check the union of both granted permissions and runtime granted
// permissions as it is possible for permissions which were withheld during
// installation to have never entered the granted set, but to have later
// been granted as runtime permissions.
std::unique_ptr<const PermissionSet> runtime_granted_permissions =
extension_prefs_->GetRuntimeGrantedPermissions(extension->id());
std::unique_ptr<const PermissionSet> total_permissions =
PermissionSet::CreateUnion(*granted_permissions,
*runtime_granted_permissions);
// Here, we check if an extension's privileges have increased in a manner
// that requires the user's approval. This could occur because the browser
// upgraded and recognized additional privileges, or an extension upgrades
// to a version that requires additional privileges.
is_privilege_increase =
PermissionMessageProvider::Get()->IsPrivilegeIncrease(
*total_permissions,
extension->permissions_data()->active_permissions(),
extension->GetType());
// If there was no privilege increase, the extension might still have new
// permissions (which either don't generate a warning message, or whose
// warning messages are suppressed by existing permissions). Grant the new
// permissions.
if (!is_privilege_increase) {
PermissionsUpdater(profile_).GrantActivePermissions(extension);
}
}
const DisableReasonSet disable_reasons =
extension_prefs_->GetDisableReasons(extension->id());
// If the extension is disabled due to a permissions increase, but does in
// fact have all permissions, remove that disable reason.
if (disable_reasons.contains(disable_reason::DISABLE_PERMISSIONS_INCREASE) &&
!is_privilege_increase) {
extension_prefs_->RemoveDisableReason(
extension->id(), disable_reason::DISABLE_PERMISSIONS_INCREASE);
}
// Extension has changed permissions significantly. Disable it. A
// notification should be sent by the caller. If the extension is already
// disabled because it was installed remotely, don't add another disable
// reason.
if (is_privilege_increase &&
!disable_reasons.contains(disable_reason::DISABLE_REMOTE_INSTALL)) {
extension_prefs_->AddDisableReason(
extension->id(), disable_reason::DISABLE_PERMISSIONS_INCREASE);
}
}
void ChromeExtensionRegistrarDelegate::UpdateActiveExtensionsInCrashReporter() {
std::set<std::string> extension_ids;
for (const auto& extension : registry_->enabled_extensions()) {
if (!extension->is_theme() &&
extension->location() != ManifestLocation::kComponent) {
extension_ids.insert(extension->id());
}
}
// TODO(kalman): This is broken. ExtensionService is per-profile.
// crash_keys::SetActiveExtensions is per-process. See
// http://crbug.com/355029.
crash_keys::SetActiveExtensions(extension_ids);
}
// static
void ChromeExtensionRegistrarDelegate::UninstallExtensionOnFileThread(
const std::string& id,
const std::string& profile_user_name,
const base::FilePath& extensions_install_dir,
const base::FilePath& extension_dir_to_delete,
const base::FilePath& profile_dir) {
ExtensionAssetsManager* assets_manager =
ExtensionAssetsManager::GetInstance();
assets_manager->UninstallExtension(id, profile_user_name,
extensions_install_dir,
extension_dir_to_delete, profile_dir);
}
void ChromeExtensionRegistrarDelegate::OnUnpackedReloadFailure(
const Extension* extension,
const base::FilePath& file_path,
const std::string& error) {
if (!error.empty()) {
extension_registrar_->OnUnpackedExtensionReloadFailed(file_path);
}
}
void ChromeExtensionRegistrarDelegate::RecordInstallHistograms(
const Extension* extension) {
bool is_user_profile =
extensions::profile_util::ProfileCanUseNonComponentExtensions(profile_);
if (!registry_->GetInstalledExtension(extension->id())) {
if (is_user_profile) {
UMA_HISTOGRAM_ENUMERATION("Extensions.InstallType.User",
extension->GetType(), 100);
UMA_HISTOGRAM_ENUMERATION("Extensions.InstallSource.User2",
extension->location(), 100);
InstalledLoader::RecordPermissionMessagesHistogram(extension, "Install",
profile_);
} else {
UMA_HISTOGRAM_ENUMERATION("Extensions.InstallType.NonUser",
extension->GetType(), 100);
UMA_HISTOGRAM_ENUMERATION("Extensions.InstallSource.NonUser2",
extension->location(), 100);
}
}
}
} // namespace extensions