blob: 5339096a8b41d256d6c45eea20e633eb416c6329 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/extension_registrar.h"
#include "base/logging.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "extensions/browser/app_sorting.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/notification_types.h"
#include "extensions/browser/renderer_startup_helper.h"
namespace extensions {
ExtensionRegistrar::ExtensionRegistrar(content::BrowserContext* browser_context,
Delegate* delegate)
: browser_context_(browser_context),
delegate_(delegate),
extension_system_(ExtensionSystem::Get(browser_context)),
extension_prefs_(ExtensionPrefs::Get(browser_context)),
registry_(ExtensionRegistry::Get(browser_context)),
renderer_helper_(
RendererStartupHelperFactory::GetForBrowserContext(browser_context)),
weak_factory_(this) {}
ExtensionRegistrar::~ExtensionRegistrar() = default;
void ExtensionRegistrar::AddExtension(
scoped_refptr<const Extension> extension) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(nullptr, registry_->GetInstalledExtension(extension->id()));
if (extension_prefs_->IsExtensionBlacklisted(extension->id())) {
// Only prefs is checked for the blacklist. We rely on callers to check the
// blacklist before calling into here, e.g. CrxInstaller checks before
// installation then threads through the install and pending install flow
// of this class, and ExtensionService checks when loading installed
// extensions.
registry_->AddBlacklisted(extension);
} else if (delegate_->ShouldBlockExtension(extension.get())) {
registry_->AddBlocked(extension);
} else if (extension_prefs_->IsExtensionDisabled(extension->id())) {
registry_->AddDisabled(extension);
// Notify that a disabled extension was added or updated.
content::NotificationService::current()->Notify(
extensions::NOTIFICATION_EXTENSION_UPDATE_DISABLED,
content::Source<content::BrowserContext>(browser_context_),
content::Details<const Extension>(extension.get()));
} else { // Extension should be enabled.
// All apps that are displayed in the launcher are ordered by their ordinals
// so we must ensure they have valid ordinals.
if (extension->RequiresSortOrdinal()) {
AppSorting* app_sorting = extension_system_->app_sorting();
app_sorting->SetExtensionVisible(extension->id(),
extension->ShouldDisplayInNewTabPage());
app_sorting->EnsureValidOrdinals(extension->id(),
syncer::StringOrdinal());
}
registry_->AddEnabled(extension);
ActivateExtension(extension.get(), true);
}
}
void ExtensionRegistrar::RemoveExtension(const ExtensionId& extension_id,
UnloadedExtensionReason reason) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
int include_mask =
ExtensionRegistry::EVERYTHING & ~ExtensionRegistry::TERMINATED;
scoped_refptr<const Extension> extension(
registry_->GetExtensionById(extension_id, include_mask));
// If the extension was already removed, just notify of the new unload reason.
// TODO: It's unclear when this needs to be called given that it may be a
// duplicate notification. See crbug.com/708230.
if (!extension) {
extension_system_->UnregisterExtensionWithRequestContexts(extension_id,
reason);
return;
}
if (registry_->disabled_extensions().Contains(extension_id)) {
// The extension is already deactivated.
registry_->RemoveDisabled(extension->id());
extension_system_->UnregisterExtensionWithRequestContexts(extension_id,
reason);
} else {
// TODO(michaelpg): The extension may be blocked or blacklisted, in which
// case it shouldn't need to be "deactivated". Determine whether the removal
// notifications are necessary (crbug.com/708230).
registry_->RemoveEnabled(extension_id);
DeactivateExtension(extension.get(), reason);
}
content::NotificationService::current()->Notify(
extensions::NOTIFICATION_EXTENSION_REMOVED,
content::Source<content::BrowserContext>(browser_context_),
content::Details<const Extension>(extension.get()));
}
void ExtensionRegistrar::EnableExtension(const ExtensionId& extension_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// First, check that the extension can be enabled.
if (IsExtensionEnabled(extension_id) ||
extension_prefs_->IsExtensionBlacklisted(extension_id) ||
registry_->blocked_extensions().Contains(extension_id)) {
return;
}
const Extension* extension =
registry_->disabled_extensions().GetByID(extension_id);
if (extension && !delegate_->CanEnableExtension(extension))
return;
// Now that we know the extension can be enabled, update the prefs.
extension_prefs_->SetExtensionEnabled(extension_id);
// This can happen if sync enables an extension that is not installed yet.
if (!extension)
return;
// Actually enable the extension.
registry_->AddEnabled(extension);
registry_->RemoveDisabled(extension->id());
ActivateExtension(extension, false);
}
void ExtensionRegistrar::DisableExtension(const ExtensionId& extension_id,
int disable_reasons) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_NE(disable_reason::DISABLE_NONE, disable_reasons);
if (extension_prefs_->IsExtensionBlacklisted(extension_id))
return;
// The extension may have been disabled already. Just add the disable reasons.
// TODO(michaelpg): Move this after the policy check, below, to ensure that
// disable reasons disallowed by policy are not added here.
if (!IsExtensionEnabled(extension_id)) {
extension_prefs_->AddDisableReasons(extension_id, disable_reasons);
return;
}
scoped_refptr<const Extension> extension =
registry_->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
bool is_controlled_extension =
!delegate_->CanDisableExtension(extension.get());
if (is_controlled_extension) {
// Remove disallowed disable reasons.
// Certain disable reasons are always allowed, since they are more internal
// to the browser (rather than the user choosing to disable the extension).
int internal_disable_reason_mask =
extensions::disable_reason::DISABLE_RELOAD |
extensions::disable_reason::DISABLE_CORRUPTED |
extensions::disable_reason::DISABLE_UPDATE_REQUIRED_BY_POLICY |
extensions::disable_reason::DISABLE_BLOCKED_BY_POLICY;
disable_reasons &= internal_disable_reason_mask;
if (disable_reasons == disable_reason::DISABLE_NONE)
return;
}
extension_prefs_->SetExtensionDisabled(extension_id, disable_reasons);
int include_mask =
ExtensionRegistry::EVERYTHING & ~ExtensionRegistry::DISABLED;
extension = registry_->GetExtensionById(extension_id, include_mask);
if (!extension)
return;
// The extension is either enabled or terminated.
DCHECK(registry_->enabled_extensions().Contains(extension->id()) ||
registry_->terminated_extensions().Contains(extension->id()));
// Move the extension to the disabled list.
registry_->AddDisabled(extension);
if (registry_->enabled_extensions().Contains(extension->id())) {
registry_->RemoveEnabled(extension->id());
DeactivateExtension(extension.get(), UnloadedExtensionReason::DISABLE);
} else {
// The extension must have been terminated. Don't send additional
// notifications for it being disabled.
bool removed = registry_->RemoveTerminated(extension->id());
DCHECK(removed);
}
}
bool ExtensionRegistrar::ReplaceReloadedExtension(
scoped_refptr<const Extension> extension) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// The extension must already be disabled, and the original extension has
// been unloaded.
CHECK(registry_->disabled_extensions().Contains(extension->id()));
if (!delegate_->CanEnableExtension(extension.get()))
return false;
// TODO(michaelpg): Other disable reasons might have been added after the
// reload started. We may want to keep the extension disabled and just remove
// the DISABLE_RELOAD reason in that case.
extension_prefs_->SetExtensionEnabled(extension->id());
// Move it over to the enabled list.
CHECK(registry_->RemoveDisabled(extension->id()));
CHECK(registry_->AddEnabled(extension));
ActivateExtension(extension.get(), false);
return true;
}
void ExtensionRegistrar::TerminateExtension(const ExtensionId& extension_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
scoped_refptr<const Extension> extension =
registry_->enabled_extensions().GetByID(extension_id);
if (!extension)
return;
registry_->AddTerminated(extension);
registry_->RemoveEnabled(extension_id);
DeactivateExtension(extension.get(), UnloadedExtensionReason::TERMINATE);
}
void ExtensionRegistrar::UntrackTerminatedExtension(
const ExtensionId& extension_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
scoped_refptr<const Extension> extension =
registry_->terminated_extensions().GetByID(extension_id);
if (!extension)
return;
registry_->RemoveTerminated(extension_id);
// TODO(michaelpg): This notification was already sent when the extension was
// unloaded as part of being terminated. But we send it again as observers
// may be tracking the terminated extension. See crbug.com/708230.
content::NotificationService::current()->Notify(
extensions::NOTIFICATION_EXTENSION_REMOVED,
content::Source<content::BrowserContext>(browser_context_),
content::Details<const Extension>(extension.get()));
}
bool ExtensionRegistrar::IsExtensionEnabled(
const ExtensionId& extension_id) const {
if (registry_->enabled_extensions().Contains(extension_id) ||
registry_->terminated_extensions().Contains(extension_id)) {
return true;
}
if (registry_->disabled_extensions().Contains(extension_id) ||
registry_->blacklisted_extensions().Contains(extension_id) ||
registry_->blocked_extensions().Contains(extension_id)) {
return false;
}
if (delegate_->ShouldBlockExtension(nullptr))
return false;
// If the extension hasn't been loaded yet, check the prefs for it. Assume
// enabled unless otherwise noted.
return !extension_prefs_->IsExtensionDisabled(extension_id) &&
!extension_prefs_->IsExtensionBlacklisted(extension_id) &&
!extension_prefs_->IsExternalExtensionUninstalled(extension_id);
}
void ExtensionRegistrar::ActivateExtension(const Extension* extension,
bool is_newly_added) {
// The URLRequestContexts need to be first to know that the extension
// was loaded. Otherwise a race can arise where a renderer that is created
// for the extension may try to load an extension URL with an extension id
// that the request context doesn't yet know about. The BrowserContext should
// ensure its URLRequestContexts appropriately discover the loaded extension.
extension_system_->RegisterExtensionWithRequestContexts(
extension,
base::Bind(&ExtensionRegistrar::OnExtensionRegisteredWithRequestContexts,
weak_factory_.GetWeakPtr(), WrapRefCounted(extension)));
renderer_helper_->OnExtensionLoaded(*extension);
// Tell subsystems that use the ExtensionRegistryObserver::OnExtensionLoaded
// about the new extension.
//
// NOTE: It is important that this happen after notifying the renderers about
// the new extensions so that if we navigate to an extension URL in
// ExtensionRegistryObserver::OnExtensionLoaded the renderer is guaranteed to
// know about it.
registry_->TriggerOnLoaded(extension);
delegate_->PostActivateExtension(extension, is_newly_added);
}
void ExtensionRegistrar::DeactivateExtension(const Extension* extension,
UnloadedExtensionReason reason) {
registry_->TriggerOnUnloaded(extension, reason);
renderer_helper_->OnExtensionUnloaded(*extension);
extension_system_->UnregisterExtensionWithRequestContexts(extension->id(),
reason);
delegate_->PostDeactivateExtension(extension);
}
void ExtensionRegistrar::OnExtensionRegisteredWithRequestContexts(
scoped_refptr<const Extension> extension) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
registry_->AddReady(extension);
if (registry_->enabled_extensions().Contains(extension->id()))
registry_->TriggerOnReady(extension.get());
}
} // namespace extensions