blob: 87546c753a44ed88fca7ec8a29de96c35a0ff3b2 [file] [log] [blame]
// 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.
#include "extensions/browser/user_script_world_configuration_manager.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_registry_factory.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/renderer_startup_helper.h"
namespace extensions {
namespace {
constexpr char kDefaultUserScriptWorldKey[] = "_default";
constexpr char kUserScriptWorldMessagingKey[] = "messaging";
constexpr char kUserScriptWorldCspKey[] = "csp";
class UserScriptWorldConfigurationManagerFactory
: public BrowserContextKeyedServiceFactory {
public:
UserScriptWorldConfigurationManagerFactory()
: BrowserContextKeyedServiceFactory(
"UserScriptWorldConfigurationManager",
BrowserContextDependencyManager::GetInstance()) {
DependsOn(ExtensionPrefsFactory::GetInstance());
DependsOn(ExtensionRegistryFactory::GetInstance());
DependsOn(RendererStartupHelperFactory::GetInstance());
}
UserScriptWorldConfigurationManagerFactory(
const UserScriptWorldConfigurationManagerFactory&) = delete;
UserScriptWorldConfigurationManagerFactory& operator=(
const UserScriptWorldConfigurationManagerFactory&) = delete;
~UserScriptWorldConfigurationManagerFactory() override = default;
UserScriptWorldConfigurationManager* GetForBrowserContext(
content::BrowserContext* context) {
return static_cast<UserScriptWorldConfigurationManager*>(
GetServiceForBrowserContext(context, /*create=*/true));
}
private:
// BrowserContextKeyedServiceFactory:
content::BrowserContext* GetBrowserContextToUse(
content::BrowserContext* context) const override {
// TODO(devlin): I wonder if it would make sense for this to have its own
// instance in incognito. That would allow split-mode extensions to have
// incognito-only world specifications and have them cleaned up when the
// profile is destroyed.
return ExtensionsBrowserClient::Get()->GetContextRedirectedToOriginal(
context);
}
std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
content::BrowserContext* context) const override {
return std::make_unique<UserScriptWorldConfigurationManager>(context);
}
};
// Returns the key entry in the user script world dictionary to use for the
// provided `world_id`.
std::string_view GetUserScriptWorldKeyForWorldId(
const std::optional<std::string>& world_id) {
return world_id ? world_id->c_str() : kDefaultUserScriptWorldKey;
}
// Attempts to parse the given `dict` into a mojom::UserScriptWorldInfo. If
// `dict` is null, returns a default specification for a
// mojom::UserScriptWorldInfo.
mojom::UserScriptWorldInfoPtr ParseUserScriptWorldInfo(
const ExtensionId& extension_id,
const std::optional<std::string>& world_id,
const base::Value::Dict* dict) {
bool enable_messaging = false;
std::optional<std::string> csp;
if (dict) {
enable_messaging =
dict->FindBool(kUserScriptWorldMessagingKey).value_or(false);
const std::string* csp_pref = dict->FindString(kUserScriptWorldCspKey);
csp = csp_pref ? std::make_optional(*csp_pref) : std::nullopt;
}
return mojom::UserScriptWorldInfo::New(extension_id, world_id, csp,
enable_messaging);
}
} // namespace
UserScriptWorldConfigurationManager::UserScriptWorldConfigurationManager(
content::BrowserContext* browser_context)
: extension_prefs_(ExtensionPrefs::Get(browser_context)),
renderer_helper_(
RendererStartupHelperFactory::GetForBrowserContext(browser_context)) {
registry_observation_.Observe(ExtensionRegistry::Get(browser_context));
}
UserScriptWorldConfigurationManager::~UserScriptWorldConfigurationManager() =
default;
void UserScriptWorldConfigurationManager::SetUserScriptWorldInfo(
const Extension& extension,
mojom::UserScriptWorldInfoPtr world_info) {
CHECK(!world_info->world_id || !world_info->world_id->empty());
// Persist world configuratation in ExtensionPrefs.
ExtensionPrefs::ScopedDictionaryUpdate update(
extension_prefs_, extension.id(), kUserScriptsWorldsConfiguration.name);
std::unique_ptr<prefs::DictionaryValueUpdate> update_dict = update.Get();
if (!update_dict) {
update_dict = update.Create();
}
base::Value::Dict world_info_dict;
world_info_dict.Set(kUserScriptWorldMessagingKey,
world_info->enable_messaging);
if (world_info->csp.has_value()) {
world_info_dict.Set(kUserScriptWorldCspKey, *world_info->csp);
}
update_dict->SetKey(GetUserScriptWorldKeyForWorldId(world_info->world_id),
base::Value(std::move(world_info_dict)));
renderer_helper_->SetUserScriptWorldProperties(extension,
std::move(world_info));
}
void UserScriptWorldConfigurationManager::ClearUserScriptWorldInfo(
const Extension& extension,
const std::optional<std::string>& world_id) {
CHECK(!world_id || !world_id->empty());
ExtensionPrefs::ScopedDictionaryUpdate update(
extension_prefs_, extension.id(), kUserScriptsWorldsConfiguration.name);
std::unique_ptr<prefs::DictionaryValueUpdate> update_dict = update.Get();
if (!update_dict) {
return; // No configs.
}
std::string_view world_key = GetUserScriptWorldKeyForWorldId(world_id);
if (!update_dict->HasKey(world_key)) {
return; // No config for this world ID.
}
update_dict->Remove(world_key);
renderer_helper_->ClearUserScriptWorldProperties(extension, world_id);
}
mojom::UserScriptWorldInfoPtr
UserScriptWorldConfigurationManager::GetUserScriptWorldInfo(
const ExtensionId& extension_id,
const std::optional<std::string>& world_id) {
CHECK(!world_id || !world_id->empty());
const base::Value::Dict* worlds_configuration =
extension_prefs_->ReadPrefAsDictionary(extension_id,
kUserScriptsWorldsConfiguration);
const base::Value::Dict* world_info =
worlds_configuration ? worlds_configuration->FindDict(
GetUserScriptWorldKeyForWorldId(world_id))
: nullptr;
return ParseUserScriptWorldInfo(extension_id, world_id, world_info);
}
std::vector<mojom::UserScriptWorldInfoPtr>
UserScriptWorldConfigurationManager::GetAllUserScriptWorlds(
const ExtensionId& extension_id) {
const base::Value::Dict* worlds_configuration =
extension_prefs_->ReadPrefAsDictionary(extension_id,
kUserScriptsWorldsConfiguration);
if (!worlds_configuration) {
return {};
}
std::vector<mojom::UserScriptWorldInfoPtr> result;
for (auto [world_id_key, world_value] : *worlds_configuration) {
if (world_id_key.length() < 1) {
continue; // Invalid key. Ignore.
}
if (!world_value.is_dict()) {
continue; // Invalid value. Ignore.
}
std::optional<std::string> world_id;
if (world_id_key[0] == '_') {
if (world_id_key != kDefaultUserScriptWorldKey) {
continue; // Invalid key. Ignore.
}
} else {
// Otherwise, the world ID is the key in the dictionary.
world_id = world_id_key;
}
mojom::UserScriptWorldInfoPtr parsed_world = ParseUserScriptWorldInfo(
extension_id, world_id, &world_value.GetDict());
if (!parsed_world) {
continue; // Failed to parse. Ignore.
}
result.push_back(std::move(parsed_world));
}
return result;
}
// static
BrowserContextKeyedServiceFactory&
UserScriptWorldConfigurationManager::GetFactory() {
static base::NoDestructor<UserScriptWorldConfigurationManagerFactory>
g_factory;
return *g_factory;
}
// static
UserScriptWorldConfigurationManager* UserScriptWorldConfigurationManager::Get(
content::BrowserContext* browser_context) {
auto& factory =
static_cast<UserScriptWorldConfigurationManagerFactory&>(GetFactory());
return factory.GetForBrowserContext(browser_context);
}
void UserScriptWorldConfigurationManager::OnExtensionWillBeInstalled(
content::BrowserContext* browser_context,
const Extension* extension,
bool is_update,
const std::string& old_name) {
if (!is_update) {
return;
}
extension_prefs_->UpdateExtensionPref(
extension->id(), kUserScriptsWorldsConfiguration.name, std::nullopt);
}
} // namespace extensions