blob: 902cf488c7d84923365c4197a70b06e61e3af038 [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 "extensions/browser/api/protocol_handlers/protocol_handlers_manager.h"
#include "base/lazy_instance.h"
#include "base/one_shot_event.h"
#include "components/custom_handlers/protocol_handler.h"
#include "components/custom_handlers/protocol_handler_registry.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/manifest_handlers/protocol_handler_info.h"
#include "third_party/blink/public/common/security/protocol_handler_security_level.h"
using content::BrowserThread;
namespace extensions {
namespace {
static void RegisterHandlersIfNeeded(
const ExtensionId& id,
const ProtocolHandlersInfo& info,
custom_handlers::ProtocolHandlerRegistry& registry) {
for (const auto& handler_info : info) {
custom_handlers::ProtocolHandler handler =
custom_handlers::ProtocolHandler::CreateExtensionProtocolHandler(
handler_info.protocol, handler_info.url, id);
DCHECK(handler.IsValid());
// This function is called whenever the extension is loaded (including
// every startup), so the handler is most likely already registered. In
// that case, the registration will be (safely) silently ignored.
if (registry.SilentlyHandleRegisterHandlerRequest(handler)) {
continue;
}
// TODO(crbug.com/40482153#comment71): As defined in the design document,
// the runtime permissions approach for extensions must be followed here.
// Hence, we need a new "registration status" that mark the custom handler
// as "pending" and ask the user only when used.
registry.OnAcceptRegisterProtocolHandler(handler);
}
}
static void UnregisterHandlersIfNeeded(
const ExtensionId& id,
const ProtocolHandlersInfo& info,
custom_handlers::ProtocolHandlerRegistry& registry) {
for (const auto& handler_info : info) {
custom_handlers::ProtocolHandler handler =
custom_handlers::ProtocolHandler::CreateExtensionProtocolHandler(
handler_info.protocol, handler_info.url, id);
DCHECK(handler.IsValid());
registry.RemoveHandler(handler);
}
}
} // namespace
ProtocolHandlersManager::ProtocolHandlersManager(
content::BrowserContext* context)
: browser_context_(context) {
DCHECK(browser_context_);
ExtensionRegistry::Get(browser_context_)->AddObserver(this);
ExtensionSystem::Get(browser_context_)
->ready()
.Post(
FROM_HERE,
base::BindOnce(&ProtocolHandlersManager::ProtocolHandlersSanityCheck,
weak_ptr_factory_.GetWeakPtr()));
}
ProtocolHandlersManager::~ProtocolHandlersManager() = default;
void ProtocolHandlersManager::Shutdown() {
ExtensionRegistry::Get(browser_context_)->RemoveObserver(this);
}
// static
BrowserContextKeyedAPIFactory<ProtocolHandlersManager>*
ProtocolHandlersManager::GetFactoryInstance() {
static base::NoDestructor<
BrowserContextKeyedAPIFactory<ProtocolHandlersManager>>
instance;
return instance.get();
}
void ProtocolHandlersManager::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
const ProtocolHandlersInfo* info =
ProtocolHandlers::GetProtocolHandlers(*extension);
if (!info) {
return;
}
auto* registry = ExtensionsBrowserClient::Get()->GetProtocolHandlerRegistry(
browser_context);
CHECK(registry);
RegisterHandlersIfNeeded(extension->id(), *info, *registry);
}
void ProtocolHandlersManager::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionReason reason) {
const ProtocolHandlersInfo* info =
ProtocolHandlers::GetProtocolHandlers(*extension);
if (!info) {
return;
}
auto* registry = ExtensionsBrowserClient::Get()->GetProtocolHandlerRegistry(
browser_context);
CHECK(registry);
UnregisterHandlersIfNeeded(extension->id(), *info, *registry);
}
void ProtocolHandlersManager::ProtocolHandlersSanityCheck() {
const auto* ext_registry = ExtensionRegistry::Get(browser_context_);
ExtensionIdSet enabled_ids = ext_registry->enabled_extensions().GetIDs();
auto* ph_registry =
ExtensionsBrowserClient::Get()->GetProtocolHandlerRegistry(
browser_context_);
DCHECK(ph_registry);
for (const auto& handler : ph_registry->GetExtensionProtocolHandlers()) {
DCHECK(handler.extension_id());
if (!enabled_ids.contains(*handler.extension_id())) {
ph_registry->RemoveHandler(handler);
}
}
}
} // namespace extensions