blob: 29315aa3a6de2c1609be90cd74a24a758ae4b760 [file] [log] [blame]
// 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/browser/service_worker/service_worker_host.h"
#include <vector>
#include "base/containers/unique_ptr_adapters.h"
#include "base/trace_event/typed_macros.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/service_worker_external_request_result.h"
#include "content/public/browser/storage_partition.h"
#include "extensions/browser/bad_message.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_function_dispatcher.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/message_service_api.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/browser/process_map.h"
#include "extensions/browser/service_worker/service_worker_task_queue.h"
#include "extensions/common/api/messaging/port_context.h"
#include "extensions/common/constants.h"
#include "extensions/common/mojom/frame.mojom.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/trace_util.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/tokens/tokens.h"
namespace extensions {
using perfetto::protos::pbzero::ChromeTrackEvent;
namespace {
const void* const kUserDataKey = &kUserDataKey;
class ServiceWorkerHostList : public base::SupportsUserData::Data {
public:
std::vector<std::unique_ptr<ServiceWorkerHost>> list;
static ServiceWorkerHostList* Get(
content::RenderProcessHost* render_process_host,
bool create_if_not_exists) {
auto* service_worker_host_list = static_cast<ServiceWorkerHostList*>(
render_process_host->GetUserData(kUserDataKey));
if (!service_worker_host_list && !create_if_not_exists) {
return nullptr;
}
if (!service_worker_host_list) {
auto new_host_list = std::make_unique<ServiceWorkerHostList>();
service_worker_host_list = new_host_list.get();
render_process_host->SetUserData(kUserDataKey, std::move(new_host_list));
}
return service_worker_host_list;
}
};
} // namespace
ServiceWorkerHost::ServiceWorkerHost(
content::RenderProcessHost* render_process_host,
mojo::PendingAssociatedReceiver<mojom::ServiceWorkerHost> receiver)
: render_process_host_(render_process_host) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
dispatcher_ =
std::make_unique<ExtensionFunctionDispatcher>(GetBrowserContext());
receiver_.Bind(std::move(receiver));
receiver_.set_disconnect_handler(base::BindOnce(
&ServiceWorkerHost::RemoteDisconnected, base::Unretained(this)));
render_process_host_->AddObserver(this);
}
ServiceWorkerHost::~ServiceWorkerHost() {
render_process_host_->RemoveObserver(this);
}
// static
void ServiceWorkerHost::BindReceiver(
int render_process_id,
mojo::PendingAssociatedReceiver<mojom::ServiceWorkerHost> receiver) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto* render_process_host =
content::RenderProcessHost::FromID(render_process_id);
if (!render_process_host) {
return;
}
auto* service_worker_host_list = ServiceWorkerHostList::Get(
render_process_host, /*create_if_not_exists=*/true);
service_worker_host_list->list.push_back(std::make_unique<ServiceWorkerHost>(
render_process_host, std::move(receiver)));
}
// static
std::vector<ServiceWorkerHost*> ServiceWorkerHost::GetServiceWorkerHostList(
content::RenderProcessHost* rph) {
ServiceWorkerHostList* host_list =
ServiceWorkerHostList::Get(rph, /*create_if_not_exists=*/false);
if (!host_list) {
return {};
}
// Copy the vector of unique pointers to a vector of raw pointers so we're
// not handing out our owned memory to the caller.
std::vector<ServiceWorkerHost*> hosts;
hosts.reserve(host_list->list.size());
for (const std::unique_ptr<ServiceWorkerHost>& host : host_list->list) {
hosts.push_back(host.get());
}
return hosts;
}
// static
ServiceWorkerHost* ServiceWorkerHost::GetWorkerFor(const WorkerId& worker_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto* render_process_host =
content::RenderProcessHost::FromID(worker_id.render_process_id);
if (!render_process_host) {
return nullptr;
}
auto* service_worker_host_list = ServiceWorkerHostList::Get(
render_process_host, /*create_if_not_exists=*/false);
if (!service_worker_host_list) {
return nullptr;
}
for (auto& worker : service_worker_host_list->list) {
if (worker->worker_id_ == worker_id) {
return worker.get();
}
}
return nullptr;
}
void ServiceWorkerHost::RemoteDisconnected() {
receiver_.reset();
Destroy();
// This instance has now been destroyed.
}
void ServiceWorkerHost::DidInitializeServiceWorkerContext(
const ExtensionId& extension_id,
int64_t service_worker_version_id,
int worker_thread_id,
const blink::ServiceWorkerToken& service_worker_token,
mojo::PendingAssociatedRemote<mojom::EventDispatcher> event_dispatcher) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserContext* browser_context = GetBrowserContext();
if (!browser_context) {
return;
}
// Ensure the worker thread ID is not the main thread ID as that is
// invalid input.
if (worker_thread_id == kMainThreadId) {
bad_message::ReceivedBadMessage(render_process_host_,
bad_message::SWH_BAD_WORKER_THREAD_ID);
return;
}
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context);
DCHECK(registry);
if (!registry->enabled_extensions().GetByID(extension_id)) {
// This can happen if the extension is unloaded at this point. Just
// checking the extension process (as below) is insufficient because
// tearing down processes is async and happens after extension unload.
return;
}
int render_process_id = render_process_host_->GetDeprecatedID();
auto* process_map = ProcessMap::Get(browser_context);
if (!process_map || !process_map->Contains(extension_id, render_process_id)) {
// We check the process in addition to the registry to guard against
// situations in which an extension may still be enabled, but no longer
// running in a given process.
return;
}
worker_id_.extension_id = extension_id;
worker_id_.version_id = service_worker_version_id;
worker_id_.render_process_id = render_process_id;
worker_id_.thread_id = worker_thread_id;
ServiceWorkerTaskQueue::Get(browser_context)
->RendererDidInitializeServiceWorkerContext(
render_process_id, extension_id, service_worker_version_id,
worker_thread_id, service_worker_token);
EventRouter::Get(browser_context)
->BindServiceWorkerEventDispatcher(render_process_id, worker_thread_id,
std::move(event_dispatcher));
}
void ServiceWorkerHost::DidStartServiceWorkerContext(
const ExtensionId& extension_id,
const base::UnguessableToken& activation_token,
const GURL& service_worker_scope,
int64_t service_worker_version_id,
int worker_thread_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserContext* browser_context = GetBrowserContext();
if (!browser_context) {
return;
}
DCHECK_NE(kMainThreadId, worker_thread_id);
int render_process_id = render_process_host_->GetDeprecatedID();
auto* process_map = ProcessMap::Get(browser_context);
if (!process_map || !process_map->Contains(extension_id, render_process_id)) {
// We can legitimately get here if the extension was already unloaded.
return;
}
CHECK(service_worker_scope.SchemeIs(kExtensionScheme) &&
extension_id == service_worker_scope.host_piece());
ServiceWorkerTaskQueue::Get(browser_context)
->RendererDidStartServiceWorkerContext(
render_process_id, extension_id, activation_token,
service_worker_scope, service_worker_version_id, worker_thread_id);
}
void ServiceWorkerHost::DidStopServiceWorkerContext(
const ExtensionId& extension_id,
const base::UnguessableToken& activation_token,
const GURL& service_worker_scope,
int64_t service_worker_version_id,
int worker_thread_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserContext* browser_context = GetBrowserContext();
if (!browser_context) {
return;
}
DCHECK_NE(kMainThreadId, worker_thread_id);
int render_process_id = render_process_host_->GetDeprecatedID();
auto* process_map = ProcessMap::Get(browser_context);
if (!process_map || !process_map->Contains(extension_id, render_process_id)) {
// We can legitimately get here if the extension was already unloaded.
return;
}
CHECK(service_worker_scope.SchemeIs(kExtensionScheme) &&
extension_id == service_worker_scope.host_piece());
CHECK_NE(blink::mojom::kInvalidServiceWorkerVersionId,
service_worker_version_id);
ServiceWorkerTaskQueue::Get(browser_context)
->RendererDidStopServiceWorkerContext(
render_process_id, extension_id, activation_token,
service_worker_scope, service_worker_version_id, worker_thread_id);
}
void ServiceWorkerHost::RequestWorker(mojom::RequestParamsPtr params,
RequestWorkerCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!GetBrowserContext()) {
std::move(callback).Run(/*kFailed=*/true, base::Value::List(),
"No browser context", nullptr);
return;
}
dispatcher_->DispatchForServiceWorker(std::move(params),
render_process_host_->GetDeprecatedID(),
std::move(callback));
}
void ServiceWorkerHost::WorkerResponseAck(const base::Uuid& request_uuid) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!GetBrowserContext()) {
return;
}
dispatcher_->ProcessResponseAck(request_uuid);
}
content::BrowserContext* ServiceWorkerHost::GetBrowserContext() {
return render_process_host_->GetBrowserContext();
}
mojom::ServiceWorker* ServiceWorkerHost::GetServiceWorker() {
if (!remote_.is_bound()) {
content::ServiceWorkerContext* context =
util::GetServiceWorkerContextForExtensionId(worker_id_.extension_id,
GetBrowserContext());
CHECK(context);
if (!context->IsLiveRunningServiceWorker(worker_id_.version_id)) {
return nullptr;
}
context->GetRemoteAssociatedInterfaces(worker_id_.version_id)
.GetInterface(&remote_);
}
return remote_.get();
}
void ServiceWorkerHost::UpdateExtensionPermissions(
const Extension& extension,
const PermissionSet& permissions) {
if (extension.id() != worker_id_.extension_id) {
return;
}
content::BrowserContext* browser_context = GetBrowserContext();
if (!browser_context) {
return;
}
content::ServiceWorkerContext* context =
util::GetServiceWorkerContextForExtensionId(worker_id_.extension_id,
browser_context);
CHECK(context);
auto* service_worker_remote = GetServiceWorker();
if (!service_worker_remote) {
return;
}
const PermissionsData* permissions_data = extension.permissions_data();
service_worker_remote->UpdatePermissions(
std::move(*permissions_data->active_permissions().Clone()),
std::move(*permissions_data->withheld_permissions().Clone()));
}
void ServiceWorkerHost::OpenChannelToExtension(
extensions::mojom::ExternalConnectionInfoPtr info,
extensions::mojom::ChannelType channel_type,
const std::string& channel_name,
const PortId& port_id,
mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,
mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
port_host) {
TRACE_EVENT("extensions", "ServiceWorkerHost::OpenChannelToExtension",
ChromeTrackEvent::kRenderProcessHost, *render_process_host_);
content::BrowserContext* browser_context = GetBrowserContext();
if (!browser_context) {
return;
}
MessageServiceApi::GetMessageService()->OpenChannelToExtension(
browser_context, worker_id_, port_id, *info, channel_type, channel_name,
std::move(port), std::move(port_host));
}
void ServiceWorkerHost::OpenChannelToNativeApp(
const std::string& native_app_name,
const PortId& port_id,
mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,
mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
port_host) {
TRACE_EVENT("extensions", "ServiceWorkerHost::OnOpenChannelToNativeApp",
ChromeTrackEvent::kRenderProcessHost, *render_process_host_);
content::BrowserContext* browser_context = GetBrowserContext();
if (!browser_context) {
return;
}
MessageServiceApi::GetMessageService()->OpenChannelToNativeApp(
browser_context, worker_id_, port_id, native_app_name, std::move(port),
std::move(port_host));
}
void ServiceWorkerHost::OpenChannelToTab(
int32_t tab_id,
int32_t frame_id,
const std::optional<std::string>& document_id,
extensions::mojom::ChannelType channel_type,
const std::string& channel_name,
const PortId& port_id,
mojo::PendingAssociatedRemote<extensions::mojom::MessagePort> port,
mojo::PendingAssociatedReceiver<extensions::mojom::MessagePortHost>
port_host) {
TRACE_EVENT("extensions", "ServiceWorkerHost::OpenChannelToTab",
ChromeTrackEvent::kRenderProcessHost, *render_process_host_);
content::BrowserContext* browser_context = GetBrowserContext();
if (!browser_context) {
return;
}
MessageServiceApi::GetMessageService()->OpenChannelToTab(
browser_context, worker_id_, port_id, tab_id, frame_id,
document_id ? *document_id : std::string(), channel_type, channel_name,
std::move(port), std::move(port_host));
}
void ServiceWorkerHost::Destroy() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto* service_worker_host_list = ServiceWorkerHostList::Get(
render_process_host_, /*create_if_not_exists=*/false);
CHECK(service_worker_host_list);
// std::erase_if will lead to a call to the destructor for this object.
std::erase_if(service_worker_host_list->list, base::MatchesUniquePtr(this));
}
void ServiceWorkerHost::RenderProcessExited(
content::RenderProcessHost* host,
const content::ChildProcessTerminationInfo& info) {
CHECK_EQ(host, render_process_host_);
// TODO(crbug.com/40062641): Investigate clearing the user data from
// RenderProcessHostImpl::Cleanup.
Destroy();
// This instance has now been deleted.
}
} // namespace extensions