blob: 66ad7b9abaa21302d2c970aba3ac112134633b16 [file] [log] [blame] [edit]
// Copyright 2023 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/renderer/isolated_world_manager.h"
#include <algorithm>
#include <map>
#include <string>
#include "base/check.h"
#include "base/no_destructor.h"
#include "extensions/common/mojom/execution_world.mojom.h"
#include "extensions/renderer/extensions_renderer_client.h"
#include "extensions/renderer/injection_host.h"
#include "third_party/blink/public/platform/web_isolated_world_info.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url.h"
namespace extensions {
IsolatedWorldManager::IsolatedWorldInfo::IsolatedWorldInfo() = default;
IsolatedWorldManager::IsolatedWorldInfo::~IsolatedWorldInfo() = default;
IsolatedWorldManager::IsolatedWorldInfo::IsolatedWorldInfo(
IsolatedWorldInfo&&) = default;
IsolatedWorldManager::PendingWorldInfo::PendingWorldInfo() = default;
IsolatedWorldManager::PendingWorldInfo::~PendingWorldInfo() = default;
IsolatedWorldManager::PendingWorldInfo::PendingWorldInfo(PendingWorldInfo&&) =
default;
IsolatedWorldManager::IsolatedWorldManager() = default;
IsolatedWorldManager::~IsolatedWorldManager() = default;
IsolatedWorldManager& IsolatedWorldManager::GetInstance() {
static base::NoDestructor<IsolatedWorldManager> g_instance;
return *g_instance;
}
std::string IsolatedWorldManager::GetHostIdForIsolatedWorld(int world_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto iter = isolated_worlds_.find(world_id);
return iter != isolated_worlds_.end() ? iter->second.host_id : std::string();
}
std::optional<mojom::ExecutionWorld>
IsolatedWorldManager::GetExecutionWorldForIsolatedWorld(int world_id) {
auto iter = isolated_worlds_.find(world_id);
return iter != isolated_worlds_.end() ? std::optional<mojom::ExecutionWorld>(
iter->second.execution_world)
: std::nullopt;
}
void IsolatedWorldManager::RemoveIsolatedWorlds(const std::string& host_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::erase_if(isolated_worlds_, [&host_id](const auto& entry) {
return entry.second.host_id == host_id;
});
}
void IsolatedWorldManager::SetUserScriptWorldProperties(
const std::string& host_id,
const std::optional<std::string>& world_id,
std::optional<std::string> csp,
bool enable_messaging) {
PendingWorldInfo& pending_info =
pending_worlds_info_[std::make_pair(host_id, world_id)];
pending_info.csp = std::move(csp);
pending_info.enable_messaging = enable_messaging;
// Check if there are currently isolated worlds associated with the host. If
// there are, we need to manually update them. This *won't* update already-
// created worlds (CSPs are cached on the LocalDomWindow), but is necessary
// to allow newly-created worlds to get the proper CSP before any injection
// happens. Otherwise, new windows may eagerly create CSP for isolated worlds
// (before the scripts have a chance to inject), resulting in stale values
// even though the CSP should be updated.
if (!pending_info.csp) {
return;
}
for (auto& [blink_world_id, isolated_world] : isolated_worlds_) {
if (isolated_world.host_id == host_id &&
isolated_world.execution_world == mojom::ExecutionWorld::kUserScript &&
isolated_world.world_id == world_id) {
isolated_world.enable_messaging = enable_messaging;
isolated_world.csp = pending_info.csp;
blink::WebIsolatedWorldInfo info;
info.security_origin =
blink::WebSecurityOrigin::Create(isolated_world.url);
info.human_readable_name =
blink::WebString::FromUTF8(isolated_world.name);
info.stable_id = blink::WebString::FromUTF8(host_id);
info.content_security_policy =
blink::WebString::FromUTF8(*pending_info.csp);
blink::SetIsolatedWorldInfo(blink_world_id, info);
}
}
}
void IsolatedWorldManager::ClearUserScriptWorldProperties(
const std::string& host_id,
const std::optional<std::string>& world_id) {
// Clear the pending world registration.
pending_worlds_info_.erase(std::make_pair(host_id, world_id));
// Determine if there's already an IsolatedWorldInfo for this world. If there
// is, reset it to the default values from the user script configuration. This
// ensures future worlds created from this config will have the proper state.
IsolatedWorldInfo* world_info = FindIsolatedWorldInfo(
host_id, mojom::ExecutionWorld::kUserScript, world_id);
if (world_info) {
world_info->csp = std::nullopt;
world_info->enable_messaging = false;
}
}
bool IsolatedWorldManager::IsMessagingEnabledInUserScriptWorld(
int blink_world_id) {
auto iter = isolated_worlds_.find(blink_world_id);
return iter != isolated_worlds_.end() && iter->second.enable_messaging;
}
int IsolatedWorldManager::GetOrCreateIsolatedWorldForHost(
const InjectionHost& injection_host,
mojom::ExecutionWorld execution_world,
const std::optional<std::string>& world_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
static_assert(
static_cast<int>(mojom::ExecutionWorld::kMaxValue) == 2,
"You've added a new execution world! Does this code need to be updated?");
CHECK_NE(mojom::ExecutionWorld::kMain, execution_world);
static int g_next_blink_world_id =
ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
const std::string& host_id = injection_host.id().id;
IsolatedWorldInfo* world_info =
FindIsolatedWorldInfo(host_id, execution_world, world_id);
if (!world_info) {
int blink_world_id = g_next_blink_world_id++;
// This map will tend to pile up over time, but realistically, you're never
// going to have enough injection hosts for it to matter.
// TODO(crbug.com/40262660): Are we sure about that? Processes can stick
// around awhile.... (and this could be affected by introducing user script
// worlds).
world_info = &isolated_worlds_[blink_world_id];
world_info->host_id = host_id;
world_info->execution_world = execution_world;
world_info->url = injection_host.url();
world_info->name = injection_host.name();
world_info->world_id = world_id;
world_info->blink_world_id = blink_world_id;
}
// Initialize CSP and messaging for the new world. First, check if we have a
// dedicated user script world configuration.
const std::string* csp = nullptr;
// The default for messaging depends on whether this is a user script world or
// a content script world.
world_info->enable_messaging =
execution_world == mojom::ExecutionWorld::kIsolated;
if (execution_world == mojom::ExecutionWorld::kUserScript) {
PendingWorldInfo* pending_world_info =
FindPendingWorldInfo(host_id, world_id);
if (pending_world_info) {
world_info->enable_messaging = pending_world_info->enable_messaging;
if (pending_world_info->csp) {
csp = &(*pending_world_info->csp);
}
}
}
// If we don't have a dedicated user script world, check the injection host's
// default CSP.
if (!csp) {
csp = injection_host.GetContentSecurityPolicy();
}
// If we found a CSP, apply it to the world. Otherwise, explicitly clear out
// any old CSP.
world_info->csp = csp ? std::optional<std::string>(*csp) : std::nullopt;
// Even though there may be an existing world for this `injection_host`'s id,
// the properties may have changed (e.g. due to an extension update).
// Overwrite any existing entries.
UpdateBlinkIsolatedWorldInfo(world_info->blink_world_id, *world_info);
return world_info->blink_world_id;
}
// static
bool IsolatedWorldManager::IsExtensionIsolatedWorld(int blink_world_id) {
// Extension worlds are any that are higher than the lowest isolated world
// value (values below that are reserved for other purposes).
return blink_world_id >=
ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
}
IsolatedWorldManager::IsolatedWorldInfo*
IsolatedWorldManager::FindIsolatedWorldInfo(
const std::string& host_id,
mojom::ExecutionWorld execution_world,
const std::optional<std::string>& world_id) {
auto iter = std::ranges::find_if(
isolated_worlds_,
[host_id, execution_world, world_id](const auto& entry) {
return entry.second.host_id == host_id &&
entry.second.execution_world == execution_world &&
entry.second.world_id == world_id;
});
return iter != isolated_worlds_.end() ? &iter->second : nullptr;
}
IsolatedWorldManager::PendingWorldInfo*
IsolatedWorldManager::FindPendingWorldInfo(
const std::string& host_id,
const std::optional<std::string>& world_id) {
auto iter = pending_worlds_info_.find(std::make_pair(host_id, world_id));
return iter != pending_worlds_info_.end() ? &(iter->second) : nullptr;
}
void IsolatedWorldManager::UpdateBlinkIsolatedWorldInfo(
int world_id,
const IsolatedWorldInfo& world_info) {
blink::WebIsolatedWorldInfo blink_info;
blink_info.security_origin = blink::WebSecurityOrigin::Create(world_info.url);
blink_info.human_readable_name = blink::WebString::FromUTF8(world_info.name);
blink_info.stable_id = blink::WebString::FromUTF8(world_info.host_id);
if (world_info.csp) {
blink_info.content_security_policy =
blink::WebString::FromUTF8(*world_info.csp);
}
blink::SetIsolatedWorldInfo(world_id, blink_info);
}
} // namespace extensions