blob: 164bdd363b5b1a61a365fb6f2d8dd644e8e10860 [file] [log] [blame]
// Copyright 2014 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/renderer_startup_helper.h"
#include <string>
#include <utility>
#include <vector>
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/bad_message.h"
#include "extensions/browser/extension_function_dispatcher.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/guest_view/web_view/web_view_guest.h"
#include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
#include "extensions/browser/l10n_file_util.h"
#include "extensions/browser/network_permissions_updater.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_manager_factory.h"
#include "extensions/browser/service_worker/service_worker_task_queue.h"
#include "extensions/browser/user_script_world_configuration_manager.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_l10n_util.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/features/feature_developer_mode_only.h"
#include "extensions/common/features/feature_session_type.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/manifest_handlers/default_locale_handler.h"
#include "extensions/common/manifest_handlers/shared_module_info.h"
#include "extensions/common/message_bundle.h"
#include "extensions/common/permissions/permissions_data.h"
#include "ipc/ipc_channel_proxy.h"
#include "ui/base/webui/web_ui_util.h"
#include "url/origin.h"
namespace extensions {
namespace {
using ::content::BrowserContext;
// Returns the current activation sequence of |extension| if the extension is
// Service Worker-based, otherwise returns std::nullopt.
std::optional<base::UnguessableToken> GetWorkerActivationToken(
BrowserContext* browser_context,
const Extension& extension) {
if (BackgroundInfo::IsServiceWorkerBased(&extension)) {
return ServiceWorkerTaskQueue::Get(browser_context)
->GetCurrentActivationToken(extension.id());
}
return std::nullopt;
}
PermissionSet CreatePermissionSet(const PermissionSet& set) {
return PermissionSet(set.apis().Clone(), set.manifest_permissions().Clone(),
set.explicit_hosts().Clone(),
set.scriptable_hosts().Clone());
}
mojom::ExtensionLoadedParamsPtr CreateExtensionLoadedParams(
const Extension& extension,
bool include_tab_permissions,
BrowserContext* browser_context) {
const PermissionsData* permissions_data = extension.permissions_data();
base::flat_map<int, PermissionSet> tab_specific_permissions;
if (include_tab_permissions) {
for (const auto& pair : permissions_data->tab_specific_permissions()) {
tab_specific_permissions[pair.first] = CreatePermissionSet(*pair.second);
}
}
return mojom::ExtensionLoadedParams::New(
extension.manifest()->value()->Clone(), extension.location(),
extension.path(),
CreatePermissionSet(permissions_data->active_permissions()),
CreatePermissionSet(permissions_data->withheld_permissions()),
std::move(tab_specific_permissions),
permissions_data->policy_blocked_hosts(),
permissions_data->policy_allowed_hosts(),
permissions_data->UsesDefaultPolicyHostRestrictions(), extension.id(),
GetWorkerActivationToken(browser_context, extension),
extension.creation_flags(), extension.guid());
}
base::flat_map<std::string, std::string> ToFlatMap(
const std::map<std::string, std::string>& map) {
return {map.begin(), map.end()};
}
} // namespace
RendererStartupHelper::RendererStartupHelper(BrowserContext* browser_context)
: browser_context_(browser_context) {
DCHECK(browser_context);
}
RendererStartupHelper::~RendererStartupHelper() {
for (auto& process_entry : process_mojo_map_)
process_entry.first->RemoveObserver(this);
}
void RendererStartupHelper::OnRenderProcessHostCreated(
content::RenderProcessHost* host) {
InitializeProcess(host);
}
void RendererStartupHelper::RenderProcessExited(
content::RenderProcessHost* host,
const content::ChildProcessTerminationInfo& info) {
UntrackProcess(host);
}
void RendererStartupHelper::RenderProcessHostDestroyed(
content::RenderProcessHost* host) {
UntrackProcess(host);
}
void RendererStartupHelper::InitializeProcess(
content::RenderProcessHost* process) {
ExtensionsBrowserClient* client = ExtensionsBrowserClient::Get();
if (!client->IsSameContext(browser_context_, process->GetBrowserContext()))
return;
mojom::Renderer* renderer =
process_mojo_map_.emplace(process, BindNewRendererRemote(process))
.first->second.get();
process->AddObserver(this);
bool activity_logging_enabled =
client->IsActivityLoggingEnabled(process->GetBrowserContext());
// We only send the ActivityLoggingEnabled message if it is enabled; otherwise
// the default (not enabled) is correct.
if (activity_logging_enabled)
renderer->SetActivityLoggingEnabled(activity_logging_enabled);
// extensions need to know the developer mode value for api restrictions.
renderer->SetDeveloperMode(
GetCurrentDeveloperMode(util::GetBrowserContextId(browser_context_)));
// Extensions need to know the channel and the session type for API
// restrictions. The values are sent to all renderers, as the non-extension
// renderers may have content scripts.
bool is_lock_screen_context =
client->IsLockScreenContext(process->GetBrowserContext());
renderer->SetSessionInfo(GetCurrentChannel(), GetCurrentFeatureSessionType(),
is_lock_screen_context);
// Platform apps need to know the system font.
// TODO(dbeam): this is not the system font in all cases.
renderer->SetSystemFont(webui::GetFontFamily(), webui::GetFontSize());
// Scripting allowlist. This is modified by tests and must be communicated
// to renderers.
renderer->SetScriptingAllowlist(
ExtensionsClient::Get()->GetScriptingAllowlist());
// If the new render process is a WebView guest process, propagate the WebView
// partition ID to it.
if (WebViewRendererState::GetInstance()->IsGuest(process->GetID())) {
std::string webview_partition_id = WebViewGuest::GetPartitionID(process);
renderer->SetWebViewPartitionID(webview_partition_id);
}
BrowserContext* renderer_context = process->GetBrowserContext();
// Load default policy_blocked_hosts and policy_allowed_hosts settings, part
// of the ExtensionSettings policy.
int context_id = util::GetBrowserContextId(renderer_context);
renderer->UpdateDefaultPolicyHostRestrictions(
PermissionsData::GetDefaultPolicyBlockedHosts(context_id),
PermissionsData::GetDefaultPolicyAllowedHosts(context_id));
renderer->UpdateUserHostRestrictions(
PermissionsData::GetUserBlockedHosts(context_id),
PermissionsData::GetUserAllowedHosts(context_id));
// Loaded extensions.
std::vector<mojom::ExtensionLoadedParamsPtr> loaded_extensions;
const ExtensionSet& extensions =
ExtensionRegistry::Get(browser_context_)->enabled_extensions();
for (const auto& ext : extensions) {
// OnExtensionLoaded should have already been called for the extension.
DCHECK(base::Contains(extension_process_map_, ext->id()));
DCHECK(!base::Contains(extension_process_map_[ext->id()], process));
if (!util::IsExtensionVisibleToContext(*ext, renderer_context))
continue;
// TODO(kalman): Only include tab specific permissions for extension
// processes, no other process needs it, so it's mildly wasteful.
// I am not sure this is possible to know this here, at such a low
// level of the stack. Perhaps site isolation can help.
loaded_extensions.push_back(CreateExtensionLoadedParams(
*ext, true /* include tab permissions*/, renderer_context));
extension_process_map_[ext->id()].insert(process);
// Each extension needs to know its user script world configurations.
std::vector<mojom::UserScriptWorldInfoPtr> worlds_info =
UserScriptWorldConfigurationManager::Get(browser_context_)
->GetAllUserScriptWorlds(ext->id());
renderer->UpdateUserScriptWorlds(std::move(worlds_info));
}
// Activate pending extensions.
renderer->LoadExtensions(std::move(loaded_extensions));
auto iter = pending_active_extensions_.find(process);
if (iter != pending_active_extensions_.end()) {
for (const ExtensionId& id : iter->second) {
// The extension should be loaded in the process.
DCHECK(extensions.Contains(id));
DCHECK(base::Contains(extension_process_map_, id));
DCHECK(base::Contains(extension_process_map_[id], process));
renderer->ActivateExtension(id);
}
}
pending_active_extensions_.erase(process);
}
void RendererStartupHelper::UntrackProcess(
content::RenderProcessHost* process) {
if (!ExtensionsBrowserClient::Get()->IsSameContext(
browser_context_, process->GetBrowserContext())) {
return;
}
process->RemoveObserver(this);
process_mojo_map_.erase(process);
pending_active_extensions_.erase(process);
for (auto& extension_process_pair : extension_process_map_)
extension_process_pair.second.erase(process);
}
void RendererStartupHelper::ActivateExtensionInProcess(
const Extension& extension,
content::RenderProcessHost* process) {
// The extension should have been loaded already. Dump without crashing to
// debug crbug.com/528026.
if (!base::Contains(extension_process_map_, extension.id())) {
#if DCHECK_IS_ON()
NOTREACHED() << "Extension " << extension.id()
<< " activated before loading";
#else
base::debug::DumpWithoutCrashing();
return;
#endif
}
if (!util::IsExtensionVisibleToContext(extension,
process->GetBrowserContext()))
return;
// Populate NetworkContext's OriginAccessList for this extension.
//
// Doing it in ActivateExtensionInProcess rather than in OnExtensionLoaded
// ensures that we cover both the regular profile and incognito profiles. See
// also https://crbug.com/1197798.
//
// This is guaranteed to happen before the extension can make any network
// requests (so there is no race) because ActivateExtensionInProcess will
// always be called before creating URLLoaderFactory for any extension frames
// that might be eventually hosted inside the renderer `process` (this
// Browser-side ordering will be replicated within the NetworkService because
// `SetCorsOriginAccessListsForOrigin()`, which is used in
// NetworkPermissionsUpdater, and `CreateURLLoaderFactory()` are 2 methods
// of the same mojom::NetworkContext interface).
NetworkPermissionsUpdater::UpdateExtension(
*process->GetBrowserContext(), extension,
NetworkPermissionsUpdater::ContextSet::kCurrentContextOnly,
base::DoNothing());
auto remote = process_mojo_map_.find(process);
if (remote != process_mojo_map_.end()) {
DCHECK(base::Contains(extension_process_map_[extension.id()], process));
remote->second->ActivateExtension(extension.id());
} else {
pending_active_extensions_[process].insert(extension.id());
}
}
void RendererStartupHelper::OnExtensionLoaded(const Extension& extension) {
DCHECK(!base::Contains(extension_process_map_, extension.id()));
// Mark the extension as loaded.
std::set<raw_ptr<content::RenderProcessHost, SetExperimental>>&
loaded_process_set = extension_process_map_[extension.id()];
// util::IsExtensionVisibleToContext() would filter out themes, but we choose
// to return early for performance reasons.
if (extension.is_theme())
return;
for (auto& process_entry : process_mojo_map_) {
content::RenderProcessHost* process = process_entry.first;
if (!util::IsExtensionVisibleToContext(extension,
process->GetBrowserContext()))
continue;
// We don't need to include tab permissions here, since the extension
// was just loaded.
// Uninitialized renderers will be informed of the extension load during the
// first batch of messages.
std::vector<mojom::ExtensionLoadedParamsPtr> params;
params.emplace_back(CreateExtensionLoadedParams(
extension, false /* no tab permissions */, browser_context_));
mojom::Renderer* renderer = GetRenderer(process);
if (renderer)
renderer->LoadExtensions(std::move(params));
loaded_process_set.insert(process);
}
}
void RendererStartupHelper::OnExtensionUnloaded(const Extension& extension) {
DCHECK(base::Contains(extension_process_map_, extension.id()));
const std::set<raw_ptr<content::RenderProcessHost, SetExperimental>>&
loaded_process_set = extension_process_map_[extension.id()];
for (content::RenderProcessHost* process : loaded_process_set) {
mojom::Renderer* renderer = GetRenderer(process);
if (renderer)
renderer->UnloadExtension(extension.id());
}
// Resets registered origin access lists in the BrowserContext asynchronously.
NetworkPermissionsUpdater::ResetOriginAccessForExtension(*browser_context_,
extension);
for (auto& process_extensions_pair : pending_active_extensions_)
process_extensions_pair.second.erase(extension.id());
// Mark the extension as unloaded.
extension_process_map_.erase(extension.id());
}
void RendererStartupHelper::OnDeveloperModeChanged(bool in_developer_mode) {
for (auto& process_entry : process_mojo_map_) {
content::RenderProcessHost* process = process_entry.first;
mojom::Renderer* renderer = GetRenderer(process);
if (renderer)
renderer->SetDeveloperMode(in_developer_mode);
}
}
void RendererStartupHelper::SetUserScriptWorldProperties(
const Extension& extension,
std::optional<std::string> world_id,
std::optional<std::string> csp,
bool enable_messaging) {
mojom::UserScriptWorldInfoPtr info = mojom::UserScriptWorldInfo::New(
extension.id(), std::move(world_id), std::move(csp), enable_messaging);
for (auto& process_entry : process_mojo_map_) {
content::RenderProcessHost* process = process_entry.first;
mojom::Renderer* renderer = GetRenderer(process);
if (!renderer) {
continue;
}
if (!util::IsExtensionVisibleToContext(extension,
process->GetBrowserContext())) {
continue;
}
std::vector<mojom::UserScriptWorldInfoPtr> worlds_info;
worlds_info.push_back(info.Clone());
renderer->UpdateUserScriptWorlds(std::move(worlds_info));
}
}
void RendererStartupHelper::ClearUserScriptWorldProperties(
const Extension& extension,
const std::optional<std::string>& world_id) {
for (auto& process_entry : process_mojo_map_) {
content::RenderProcessHost* process = process_entry.first;
mojom::Renderer* renderer = GetRenderer(process);
if (!renderer) {
continue;
}
if (!util::IsExtensionVisibleToContext(extension,
process->GetBrowserContext())) {
continue;
}
renderer->ClearUserScriptWorldConfig(extension.id(), world_id);
}
}
mojo::PendingAssociatedRemote<mojom::Renderer>
RendererStartupHelper::BindNewRendererRemote(
content::RenderProcessHost* process) {
mojo::AssociatedRemote<mojom::Renderer> renderer_interface;
process->GetChannel()->GetRemoteAssociatedInterface(&renderer_interface);
return renderer_interface.Unbind();
}
mojom::Renderer* RendererStartupHelper::GetRenderer(
content::RenderProcessHost* process) {
auto it = process_mojo_map_.find(process);
if (it == process_mojo_map_.end())
return nullptr;
return it->second.get();
}
BrowserContext* RendererStartupHelper::GetRendererBrowserContext() {
// `browser_context_` is redirected to remove incognito mode. This method
// returns the original browser context associated with the renderer.
auto* host = content::RenderProcessHost::FromID(receivers_.current_context());
if (!host) {
return nullptr;
}
return host->GetBrowserContext();
}
void RendererStartupHelper::AddAPIActionToActivityLog(
const ExtensionId& extension_id,
const std::string& call_name,
base::Value::List args,
const std::string& extra) {
auto* browser_context = GetRendererBrowserContext();
if (!browser_context) {
return;
}
ExtensionsBrowserClient::Get()->AddAPIActionToActivityLog(
browser_context, extension_id, call_name, std::move(args), extra);
}
void RendererStartupHelper::AddEventToActivityLog(
const ExtensionId& extension_id,
const std::string& call_name,
base::Value::List args,
const std::string& extra) {
auto* browser_context = GetRendererBrowserContext();
if (!browser_context) {
return;
}
ExtensionsBrowserClient::Get()->AddEventToActivityLog(
browser_context, extension_id, call_name, std::move(args), extra);
}
void RendererStartupHelper::AddDOMActionToActivityLog(
const ExtensionId& extension_id,
const std::string& call_name,
base::Value::List args,
const GURL& url,
const std::u16string& url_title,
int32_t call_type) {
auto* browser_context = GetRendererBrowserContext();
if (!browser_context) {
return;
}
ExtensionsBrowserClient::Get()->AddDOMActionToActivityLog(
browser_context, extension_id, call_name, std::move(args), url, url_title,
call_type);
}
// static
void RendererStartupHelper::BindForRenderer(
int process_id,
mojo::PendingAssociatedReceiver<mojom::RendererHost> receiver) {
auto* host = content::RenderProcessHost::FromID(process_id);
if (!host) {
return;
}
auto* renderer_startup_helper =
RendererStartupHelperFactory::GetForBrowserContext(
host->GetBrowserContext());
renderer_startup_helper->receivers_.Add(renderer_startup_helper,
std::move(receiver), process_id);
}
void RendererStartupHelper::WakeEventPage(const ExtensionId& extension_id,
WakeEventPageCallback callback) {
auto* browser_context = GetRendererBrowserContext();
if (!browser_context) {
std::move(callback).Run(false);
return;
}
const Extension* extension = ExtensionRegistry::Get(browser_context)
->enabled_extensions()
.GetByID(extension_id);
if (!extension) {
// Don't kill the renderer, it might just be some context which hasn't
// caught up to extension having been uninstalled.
std::move(callback).Run(false);
return;
}
ProcessManager* process_manager = ProcessManager::Get(browser_context);
if (BackgroundInfo::HasLazyBackgroundPage(extension)) {
// Wake the event page if it's asleep, or immediately repond with success
// if it's already awake.
if (process_manager->IsEventPageSuspended(extension_id)) {
process_manager->WakeEventPage(extension_id, std::move(callback));
} else {
std::move(callback).Run(true);
}
return;
}
if (BackgroundInfo::HasPersistentBackgroundPage(extension)) {
// No point in trying to wake a persistent background page. If it's open,
// immediately return and call it a success. If it's closed, fail.
std::move(callback).Run(process_manager->GetBackgroundHostForExtension(
extension_id) != nullptr);
return;
}
// The extension has no background page, so there is nothing to wake.
std::move(callback).Run(false);
}
void RendererStartupHelper::GetMessageBundle(
const ExtensionId& extension_id,
GetMessageBundleCallback callback) {
auto* browser_context = GetRendererBrowserContext();
if (!browser_context) {
std::move(callback).Run({});
return;
}
const ExtensionSet& extension_set =
ExtensionRegistry::Get(browser_context)->enabled_extensions();
const Extension* extension = extension_set.GetByID(extension_id);
if (!extension) { // The extension has gone.
std::move(callback).Run({});
return;
}
const std::string& default_locale = LocaleInfo::GetDefaultLocale(extension);
if (default_locale.empty()) {
// A little optimization: send the answer here to avoid an extra thread hop.
std::unique_ptr<MessageBundle::SubstitutionMap> dictionary_map(
l10n_file_util::LoadNonLocalizedMessageBundleSubstitutionMap(
extension_id));
std::move(callback).Run(ToFlatMap(*dictionary_map));
return;
}
std::vector<base::FilePath> paths_to_load;
paths_to_load.push_back(extension->path());
auto imports = SharedModuleInfo::GetImports(extension);
// Iterate through the imports in reverse. This will allow later imported
// modules to override earlier imported modules, as the list order is
// maintained from the definition in manifest.json of the imports.
for (const SharedModuleInfo::ImportInfo& import : base::Reversed(imports)) {
const Extension* imported_extension =
extension_set.GetByID(import.extension_id);
if (!imported_extension) {
NOTREACHED() << "Missing shared module " << import.extension_id;
continue;
}
paths_to_load.push_back(imported_extension->path());
}
// This blocks tab loading. Priority is inherited from the calling context.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(
[](const std::vector<base::FilePath>& extension_paths,
const ExtensionId& main_extension_id,
const std::string& default_locale,
extension_l10n_util::GzippedMessagesPermission gzip_permission) {
return base::WrapUnique<MessageBundle::SubstitutionMap>(
l10n_file_util::LoadMessageBundleSubstitutionMapFromPaths(
extension_paths, main_extension_id, default_locale,
gzip_permission));
},
paths_to_load, extension_id, default_locale,
extension_l10n_util::GetGzippedMessagesPermissionForExtension(
extension)),
base::BindOnce(
[](GetMessageBundleCallback callback,
std::unique_ptr<MessageBundle::SubstitutionMap> dictionary_map) {
std::move(callback).Run(ToFlatMap(*dictionary_map));
},
std::move(callback)));
}
//////////////////////////////////////////////////////////////////////////////
// static
RendererStartupHelper* RendererStartupHelperFactory::GetForBrowserContext(
BrowserContext* context) {
return static_cast<RendererStartupHelper*>(
GetInstance()->GetServiceForBrowserContext(context, true));
}
// static
RendererStartupHelperFactory* RendererStartupHelperFactory::GetInstance() {
return base::Singleton<RendererStartupHelperFactory>::get();
}
RendererStartupHelperFactory::RendererStartupHelperFactory()
: BrowserContextKeyedServiceFactory(
"RendererStartupHelper",
BrowserContextDependencyManager::GetInstance()) {
DependsOn(ProcessManagerFactory::GetInstance());
}
RendererStartupHelperFactory::~RendererStartupHelperFactory() = default;
std::unique_ptr<KeyedService>
RendererStartupHelperFactory::BuildServiceInstanceForBrowserContext(
BrowserContext* context) const {
return std::make_unique<RendererStartupHelper>(context);
}
BrowserContext* RendererStartupHelperFactory::GetBrowserContextToUse(
BrowserContext* context) const {
// Redirected in incognito.
return ExtensionsBrowserClient::Get()->GetContextRedirectedToOriginal(
context, /*force_guest_profile=*/true);
}
bool RendererStartupHelperFactory::ServiceIsCreatedWithBrowserContext() const {
return true;
}
} // namespace extensions