blob: ff63a3eac596d6b2c01e1c949bf4e039120a1089 [file]
// Copyright 2016 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/service_worker_data.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/single_thread_task_runner.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/renderer/dispatcher.h"
#include "extensions/renderer/extension_interaction_provider.h"
#include "extensions/renderer/native_extension_bindings_system.h"
#include "extensions/renderer/renderer_extension_registry.h"
#include "extensions/renderer/worker_script_context_set.h"
#include "extensions/renderer/worker_thread_dispatcher.h"
#include "extensions/renderer/worker_thread_util.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
#include "third_party/blink/public/common/tokens/tokens.h"
namespace extensions {
ServiceWorkerData::ServiceWorkerData(
blink::WebServiceWorkerContextProxy* proxy,
int64_t service_worker_version_id,
const std::optional<base::UnguessableToken>& activation_sequence,
const blink::ServiceWorkerToken& service_worker_token,
ScriptContext* context,
std::unique_ptr<NativeExtensionBindingsSystem> bindings_system)
: proxy_(proxy),
service_worker_version_id_(service_worker_version_id),
activation_sequence_(std::move(activation_sequence)),
service_worker_token_(service_worker_token),
context_(context),
v8_schema_registry_(new V8SchemaRegistry),
bindings_system_(std::move(bindings_system)) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(bindings_system_);
proxy_->GetAssociatedInterfaceRegistry().AddInterface<mojom::ServiceWorker>(
base::BindRepeating(&ServiceWorkerData::OnServiceWorkerRequest,
weak_ptr_factory_.GetWeakPtr()));
}
ServiceWorkerData::~ServiceWorkerData() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}
void ServiceWorkerData::OnServiceWorkerRequest(
mojo::PendingAssociatedReceiver<mojom::ServiceWorker> receiver) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(bindings_system_);
receiver_.reset();
receiver_.Bind(std::move(receiver));
}
void ServiceWorkerData::UpdatePermissions(PermissionSet active_permissions,
PermissionSet withheld_permissions) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(worker_thread_util::IsWorkerThread());
CHECK(bindings_system_);
const ExtensionId& extension_id = context_->GetExtensionID();
const Extension* extension =
RendererExtensionRegistry::Get()->GetByID(extension_id);
if (!extension) {
return;
}
extension->permissions_data()->SetPermissions(
std::make_unique<const PermissionSet>(std::move(active_permissions)),
std::make_unique<const PermissionSet>(std::move(withheld_permissions)));
bindings_system_->UpdateBindings(extension_id, /*permissions_changed=*/true,
Dispatcher::GetWorkerScriptContextSet());
}
mojom::ServiceWorkerHost* ServiceWorkerData::GetServiceWorkerHost() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(bindings_system_);
if (!service_worker_host_.is_bound()) {
proxy_->GetRemoteAssociatedInterface(
service_worker_host_.BindNewEndpointAndPassReceiver());
}
return service_worker_host_.get();
}
mojom::EventRouter* ServiceWorkerData::GetEventRouter() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(bindings_system_);
if (!event_router_remote_.is_bound()) {
proxy_->GetRemoteAssociatedInterface(
event_router_remote_.BindNewEndpointAndPassReceiver());
}
return event_router_remote_.get();
}
mojom::RendererAutomationRegistry* ServiceWorkerData::GetAutomationRegistry() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(bindings_system_);
if (!renderer_automation_registry_remote_.is_bound()) {
proxy_->GetRemoteAssociatedInterface(
renderer_automation_registry_remote_.BindNewEndpointAndPassReceiver());
}
return renderer_automation_registry_remote_.get();
}
mojom::RendererHost* ServiceWorkerData::GetRendererHost() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// We allow access to mojom::RendererHost without a `bindings_system_`.
if (!renderer_host_.is_bound()) {
proxy_->GetRemoteAssociatedInterface(
renderer_host_.BindNewEndpointAndPassReceiver());
}
return renderer_host_.get();
}
mojom::WebRequestHost* ServiceWorkerData::GetWebRequestHost() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(bindings_system_);
if (!web_request_host_.is_bound()) {
proxy_->GetRemoteAssociatedInterface(
web_request_host_.BindNewEndpointAndPassReceiver());
}
return web_request_host_.get();
}
void ServiceWorkerData::Init() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(bindings_system_);
const int thread_id = content::WorkerThread::GetCurrentId();
const ExtensionId& extension_id = context_->GetExtensionID();
CHECK(!extension_id.empty());
CHECK(activation_sequence_.has_value());
// Set `in_listener_registration_phase_` before binding
// `event_dispatcher_receiver_` so incoming events are properly queued.
in_listener_registration_phase_ =
BackgroundInfo::HasAsyncListenerRegistration(context_->extension());
GetServiceWorkerHost()->DidInitializeServiceWorkerContext(
extension_id, *activation_sequence_, service_worker_version_id_,
thread_id, service_worker_token_,
event_dispatcher_receiver_.BindNewEndpointAndPassRemote());
}
bool ServiceWorkerData::MarkListenerRegistrationComplete() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!in_listener_registration_phase_) {
return false;
}
in_listener_registration_phase_ = false;
// Flush in a separate task. This method runs synchronously during
// `runtime.markListenerRegistrationComplete()`, so flushing immediately would
// run listeners reentrantly before the call returns.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&ServiceWorkerData::FlushQueuedEvents,
weak_ptr_factory_.GetWeakPtr()));
return true;
}
void ServiceWorkerData::DispatchEvent(
mojom::DispatchEventParamsPtr params,
const scoped_refptr<const EventArgs>& event_args,
DispatchEventCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(event_args);
// Queue events while registration is in progress or earlier events are
// waiting to flush, preserving FIFO order.
if (in_listener_registration_phase_ || !queued_events_.empty()) {
// TODO(crbug.com/509627729): Bound the queue. When full, drop the oldest
// event while still completing its webRequest bookkeeping
// (`DidDispatchEvent()`).
queued_events_.push_back(QueuedEvent{std::move(params), event_args});
} else {
DispatchEventToListeners(*params, event_args->data);
}
// Queued events do not keep the worker alive: they have no browser keepalive
// and are acked at queue time. If the worker stops at the idle timeout
// before registration completes, the phase aborts and the queued events are
// lost.
std::move(callback).Run(
/*event_will_run_in_lazy_background_page_script=*/false);
}
void ServiceWorkerData::DispatchEventToListeners(
const mojom::DispatchEventParams& params,
const base::ListValue& event_args) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
ScriptContext* script_context = context();
// Note |scoped_extension_interaction| requires a HandleScope.
v8::Isolate* isolate = script_context->isolate();
v8::HandleScope handle_scope(isolate);
std::unique_ptr<InteractionProvider::Scope> scoped_extension_interaction;
if (params.is_user_gesture) {
scoped_extension_interaction =
ExtensionInteractionProvider::Scope::ForWorker(
script_context->v8_context());
}
bindings_system()->DispatchEventInContext(params.event_name, event_args,
params.filtering_info, context());
// The worker has a single context, so one dispatch notifies every listener.
bindings_system()->DidDispatchEvent(*params.host_id, params.event_name,
event_args);
}
void ServiceWorkerData::FlushQueuedEvents() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
CHECK(!in_listener_registration_phase_);
base::UmaHistogramCounts10000(
"Extensions.ServiceWorkerBackground.AsyncListenerRegistration."
"QueuedEvents",
queued_events_.size());
// Flush queued events in FIFO order. Because this runs in a single task,
// new Mojo events cannot interleave. Events without matching JS listeners
// are discarded downstream in `APIEventHandler::FireEventInContext()`.
base::WeakPtr<ServiceWorkerData> weak_this = weak_ptr_factory_.GetWeakPtr();
while (!queued_events_.empty()) {
if (!context_->is_valid()) {
queued_events_.clear();
break;
}
QueuedEvent event = std::move(queued_events_.front());
queued_events_.pop_front();
DispatchEventToListeners(*event.params, event.event_args->data);
if (!weak_this) {
return;
}
}
}
void ServiceWorkerData::DispatchOnConnect(
const PortId& port_id,
extensions::mojom::ChannelType channel_type,
const std::string& channel_name,
extensions::mojom::TabConnectionInfoPtr tab_info,
extensions::mojom::ExternalConnectionInfoPtr external_connection_info,
mojo::PendingAssociatedReceiver<extensions::mojom::MessagePort> port,
mojo::PendingAssociatedRemote<extensions::mojom::MessagePortHost> port_host,
DispatchOnConnectCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
WorkerThreadDispatcher::GetBindingsSystem()
->messaging_service()
->DispatchOnConnect(Dispatcher::GetWorkerScriptContextSet(), port_id,
channel_type, channel_name, *tab_info,
*external_connection_info, std::move(port),
std::move(port_host),
// Render frames do not matter.
nullptr, std::move(callback));
}
} // namespace extensions