refactor(worker): split global scope by responsibility

This commit is contained in:
ldm0
2026-10-04 01:38:57 +08:00
committed by Donough Liu
parent 82540f8d5c
commit e442d0eee2
18 changed files with 5201 additions and 5011 deletions
@@ -247,7 +247,7 @@ const DIRECT_V8_CALL_ALLOWLIST: &[AllowedDirectCallFile] = &[
DirectCallOwner::NativeForwardingOrScript,
),
allowed(
"worker/global_scope/mod.rs",
"worker/global_scope/console.rs",
1,
DirectCallOwner::NativeForwardingOrScript,
),
@@ -0,0 +1,326 @@
//! Worker-owned WebCrypto and OPFS tasks and their promise completions.
use super::*;
/// A WebCrypto primitive dispatched off the worker event loop to the blocking
/// pool. The worker owns the resolver and matches the completion back by the
/// task id allocated for this worker lifetime.
pub(in crate::worker) struct PendingWorkerWebCryptoTask {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
/// Completion of a worker WebCrypto blocking task, routed back onto the worker
/// event loop. The worker sink captures these ids at registration just as the
/// page-side typed producer captures its exact Page/Window owner.
pub(crate) struct WorkerWebCryptoCompletion {
pub(crate) task_id: u64,
pub(crate) result: Result<WebCryptoTaskResult, WebCryptoRejection>,
}
pub(in crate::worker) struct PendingWorkerOpfsTask {
pub(in crate::worker) locator: moli_storage_service::StorageBucketLocator,
pub(in crate::worker) handle_access: Option<crate::opfs_owner_tasks::OpfsHandleAccessContext>,
pub(in crate::worker) settlement: crate::opfs_owner_tasks::OpfsTaskSettlement,
}
pub(in crate::worker) struct WorkerOpfsOwnerState {
next_task_id: u64,
pending_tasks: HashMap<u64, PendingWorkerOpfsTask>,
handles: crate::opfs_owner_tasks::OpfsHandleRegistry,
directory_iterators: crate::opfs_owner_tasks::OpfsDirectoryIteratorRegistry,
}
impl Default for WorkerOpfsOwnerState {
fn default() -> Self {
Self {
next_task_id: 1,
pending_tasks: HashMap::new(),
handles: crate::opfs_owner_tasks::OpfsHandleRegistry::default(),
directory_iterators: crate::opfs_owner_tasks::OpfsDirectoryIteratorRegistry::default(),
}
}
}
impl WorkerOpfsOwnerState {
pub(in crate::worker) fn has_pending_tasks(&self) -> bool {
!self.pending_tasks.is_empty()
}
}
pub(crate) struct WorkerOpfsCompletion {
pub(crate) task_id: u64,
pub(crate) result: OpfsTaskResult,
}
impl WorkerGlobalState {
/// Register a pending worker WebCrypto task and return the routing tuple the
/// blocking task needs to report completion. Runs synchronously inside the
/// V8 callback before control returns to the worker event loop.
pub(in crate::worker) fn register_pending_webcrypto_task(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> (u64, mpsc::UnboundedSender<WorkerWebCryptoCompletion>) {
let task_id = self.next_webcrypto_task_id;
self.next_webcrypto_task_id = task_id
.checked_add(1)
.expect("worker WebCrypto task id exhausted");
self.pending_webcrypto
.insert(task_id, PendingWorkerWebCryptoTask { resolver });
(task_id, self.webcrypto_completion_tx.clone())
}
pub(in crate::worker) fn take_pending_webcrypto_task(
&mut self,
task_id: u64,
) -> Option<PendingWorkerWebCryptoTask> {
self.pending_webcrypto.remove(&task_id)
}
pub(in crate::worker) fn register_pending_opfs_task(
&mut self,
locator: moli_storage_service::StorageBucketLocator,
handle_access: Option<crate::opfs_owner_tasks::OpfsHandleAccessContext>,
settlement: crate::opfs_owner_tasks::OpfsTaskSettlement,
) -> (u64, mpsc::UnboundedSender<WorkerOpfsCompletion>) {
let state = self
.opfs_owner_state
.get_or_insert_with(WorkerOpfsOwnerState::default);
let task_id = state.next_task_id;
state.next_task_id = task_id
.checked_add(1)
.expect("worker OPFS task id exhausted");
state.pending_tasks.insert(
task_id,
PendingWorkerOpfsTask {
locator,
handle_access,
settlement,
},
);
(task_id, self.opfs_completion_tx.clone())
}
pub(in crate::worker) fn take_pending_opfs_task(
&mut self,
task_id: u64,
) -> Option<PendingWorkerOpfsTask> {
self.opfs_owner_state
.as_mut()?
.pending_tasks
.remove(&task_id)
}
}
/// Register a pending worker WebCrypto task from a callback scope.
///
/// Returns the routing tuple the blocking task needs to report completion, or
/// `None` when the current scope is not a worker global (e.g. the page runtime
/// or a bare unit-test context). This is the worker-lane analog of the page
/// `register_webcrypto_task`.
pub(crate) fn register_worker_webcrypto_task(
scope: &mut v8::PinScope<'_, '_>,
resolver: v8::Local<'_, v8::PromiseResolver>,
) -> Option<(u64, mpsc::UnboundedSender<WorkerWebCryptoCompletion>)> {
let state = get_worker_state(scope)?;
let resolver = v8::Global::new(scope, resolver);
let mut state = state.borrow_mut();
Some(state.register_pending_webcrypto_task(resolver))
}
pub(crate) fn register_worker_opfs_task(
scope: &mut v8::PinScope<'_, '_>,
resolver: v8::Local<'_, v8::PromiseResolver>,
locator: moli_storage_service::StorageBucketLocator,
handle_access: Option<crate::opfs_owner_tasks::OpfsHandleAccessContext>,
) -> Option<(u64, mpsc::UnboundedSender<WorkerOpfsCompletion>)> {
let state = get_worker_state(scope)?;
let resolver = v8::Global::new(scope, resolver);
let mut state = state.borrow_mut();
Some(state.register_pending_opfs_task(
locator,
handle_access,
crate::opfs_owner_tasks::OpfsTaskSettlement::Promise(resolver),
))
}
pub(crate) fn register_worker_opfs_iterator_task(
scope: &mut v8::PinScope<'_, '_>,
locator: moli_storage_service::StorageBucketLocator,
registry: crate::opfs_owner_tasks::OpfsDirectoryIteratorRegistry,
iterator_id: u32,
keep_alive: v8::Global<v8::Object>,
handle_access: Option<crate::opfs_owner_tasks::OpfsHandleAccessContext>,
) -> Option<(u64, mpsc::UnboundedSender<WorkerOpfsCompletion>)> {
let state = get_worker_state(scope)?;
let mut state = state.borrow_mut();
Some(state.register_pending_opfs_task(
locator,
handle_access,
crate::opfs_owner_tasks::OpfsTaskSettlement::DirectoryIterator {
registry,
iterator_id,
keep_alive,
},
))
}
pub(crate) fn register_worker_opfs_move_task(
scope: &mut v8::PinScope<'_, '_>,
resolver: v8::Local<'_, v8::PromiseResolver>,
handle: v8::Local<'_, v8::Object>,
mutation: crate::opfs_owner_tasks::OpfsHandleMutationGuard,
locator: moli_storage_service::StorageBucketLocator,
handle_access: Option<crate::opfs_owner_tasks::OpfsHandleAccessContext>,
) -> Option<(u64, mpsc::UnboundedSender<WorkerOpfsCompletion>)> {
let state = get_worker_state(scope)?;
let mut state = state.borrow_mut();
Some(state.register_pending_opfs_task(
locator,
handle_access,
crate::opfs_owner_tasks::OpfsTaskSettlement::Move {
resolver: v8::Global::new(scope, resolver),
handle: v8::Global::new(scope, handle),
mutation,
},
))
}
pub(crate) fn worker_opfs_handle_registry(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<crate::opfs_owner_tasks::OpfsHandleRegistry> {
Some(
get_worker_state(scope)?
.borrow()
.opfs_owner_state
.as_ref()?
.handles
.clone(),
)
}
pub(crate) fn ensure_worker_opfs_handle_registry(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<crate::opfs_owner_tasks::OpfsHandleRegistry> {
Some(
get_worker_state(scope)?
.borrow_mut()
.opfs_owner_state
.get_or_insert_with(WorkerOpfsOwnerState::default)
.handles
.clone(),
)
}
pub(crate) fn worker_opfs_directory_iterator_registry(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<crate::opfs_owner_tasks::OpfsDirectoryIteratorRegistry> {
Some(
get_worker_state(scope)?
.borrow()
.opfs_owner_state
.as_ref()?
.directory_iterators
.clone(),
)
}
pub(crate) fn ensure_worker_opfs_directory_iterator_registry(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<crate::opfs_owner_tasks::OpfsDirectoryIteratorRegistry> {
Some(
get_worker_state(scope)?
.borrow_mut()
.opfs_owner_state
.get_or_insert_with(WorkerOpfsOwnerState::default)
.directory_iterators
.clone(),
)
}
pub(crate) fn cancel_worker_opfs_task(scope: &mut v8::PinScope<'_, '_>, task_id: u64) {
if let Some(state) = get_worker_state(scope) {
state.borrow_mut().take_pending_opfs_task(task_id);
}
}
/// Settle a worker WebCrypto promise once its blocking task reports back on the
/// worker event loop. Terminating a worker drops this state and its completion
/// receiver together, so a separate reset generation is unnecessary.
pub(in crate::worker) fn drain_worker_webcrypto_completion(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
completion: WorkerWebCryptoCompletion,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) = state.take_pending_webcrypto_task(completion.task_id) else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
crate::script_vm::webcrypto_tasks::settle_webcrypto_task_result(
scope,
resolver,
completion.result,
);
}
pub(in crate::worker) fn drain_worker_opfs_completion(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
completion: WorkerOpfsCompletion,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) = state.take_pending_opfs_task(completion.task_id) else {
return;
};
pending
};
let handle_access = pending.handle_access;
match pending.settlement {
crate::opfs_owner_tasks::OpfsTaskSettlement::Promise(resolver) => {
let resolver = v8::Local::new(scope, &resolver);
crate::context_bootstrap::settle_opfs_task_result(
scope,
resolver,
&pending.locator,
handle_access.as_ref(),
completion.result,
);
}
crate::opfs_owner_tasks::OpfsTaskSettlement::Move {
resolver,
handle,
mutation,
} => {
let resolver = v8::Local::new(scope, &resolver);
let handle = v8::Local::new(scope, &handle);
crate::context_bootstrap::settle_opfs_move_task_result(
scope,
resolver,
handle,
&pending.locator,
handle_access.as_ref(),
completion.result,
);
drop(mutation);
}
crate::opfs_owner_tasks::OpfsTaskSettlement::DirectoryIterator {
registry,
iterator_id,
keep_alive,
} => {
crate::context_bootstrap::settle_opfs_directory_iterator_task_result(
scope,
&registry,
iterator_id,
&pending.locator,
handle_access.as_ref(),
completion.result,
);
drop(keep_alive);
}
}
}
@@ -0,0 +1,522 @@
//! Global initialization and constructors for dedicated, shared and service workers.
use super::*;
#[derive(WebApiObject)]
#[webapi(plain)]
struct WorkerGlobalBootstrapPropertiesDeclaration<'scope> {
#[webapi(slot = WORKER_STATE_SLOT)]
worker_state: v8::Local<'scope, v8::External>,
#[webapi(data_property = "self", readonly)]
self_value: v8::Local<'scope, v8::Object>,
#[webapi(data_property = "crossOriginIsolated")]
cross_origin_isolated: bool,
#[webapi(data_property = "isSecureContext", readonly)]
is_secure_context: bool,
#[webapi(data_property = "globalThis")]
global_this: v8::Local<'scope, v8::Object>,
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct WorkerGlobalNameDeclaration {
#[webapi(data_property, enumerable)]
name: String,
}
#[derive(Default, WebApiObject)]
#[webapi(fragment, prototype = "DedicatedWorkerGlobalScope", enumerable)]
struct DedicatedWorkerGlobalMethodsDeclaration {
#[webapi(method, callback = worker_close_callback, length = 0)]
close: (),
}
#[derive(Default, WebApiObject)]
#[webapi(fragment, prototype = "SharedWorkerGlobalScope", enumerable)]
struct SharedWorkerGlobalMethodsDeclaration {
#[webapi(method, callback = worker_close_callback, length = 0)]
close: (),
}
#[derive(Default, WebApiObject)]
#[webapi(plain)]
struct WorkerGlobalCommonOperationsDeclaration {
#[webapi(
method = "structuredClone",
callback = worker_structured_clone_callback,
length = 1
)]
structured_clone: (),
#[webapi(method, callback = worker_fetch_callback, length = 1)]
fetch: (),
#[webapi(method = "importScripts", callback = worker_import_scripts_callback, length = 0)]
import_scripts: (),
}
#[derive(Default, WebApiObject)]
#[webapi(plain)]
struct WorkerGlobalCreateImageBitmapDeclaration {
#[webapi(
method = "createImageBitmap",
callback = worker_create_image_bitmap_callback,
length = 1
)]
create_image_bitmap: (),
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct WorkerGlobalScopeConstructorGlobalDeclaration<'scope> {
#[webapi(data_property = "WorkerGlobalScope")]
constructor: v8::Local<'scope, v8::Function>,
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct DedicatedWorkerGlobalScopeConstructorGlobalDeclaration<'scope> {
#[webapi(data_property = "DedicatedWorkerGlobalScope")]
constructor: v8::Local<'scope, v8::Function>,
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct SharedWorkerGlobalScopeConstructorGlobalDeclaration<'scope> {
#[webapi(data_property = "SharedWorkerGlobalScope")]
constructor: v8::Local<'scope, v8::Function>,
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct ServiceWorkerGlobalScopeConstructorGlobalDeclaration<'scope> {
#[webapi(data_property = "ServiceWorkerGlobalScope")]
constructor: v8::Local<'scope, v8::Function>,
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct WorkerScopePrototypeConstructorDeclaration<'scope> {
#[webapi(data_property = "constructor")]
constructor: v8::Local<'scope, v8::Function>,
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct WorkerPrototypeTagDeclaration {
#[webapi(to_string_tag, readonly)]
tag: &'static str,
}
/// Install the `DedicatedWorkerGlobalScope` APIs on the given V8 global object.
///
/// The `state_ptr` is stored as an external in V8 so callbacks can access
/// `WorkerGlobalState`. Callers must ensure the `Rc<RefCell<WorkerGlobalState>>`
/// outlives the V8 context.
pub(in crate::worker) fn install_worker_global_scope<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
state: Rc<RefCell<WorkerGlobalState>>,
worker_templates: Option<&PreparedWorkerGlobalScopeTemplates>,
) -> Result<()> {
// Store state pointer as an external on the global so callbacks can find it.
let state_ptr = Rc::into_raw(state.clone()) as *mut c_void;
let external = v8::External::new(scope, state_ptr);
// Prevent leak: the Rc was consumed by into_raw, but we still hold `state`.
// We reconstruct it so the ref-count is correct. The raw pointer stored in
// the external is valid as long as the Rc (held by the caller) is alive.
unsafe { Rc::from_raw(state_ptr as *const RefCell<WorkerGlobalState>) };
let global_kind = state.borrow().global_kind.clone();
let (secure_context, cross_origin_isolated) = {
let state = state.borrow();
(
state.secure_context,
state.secure_context && state.policy_context.cross_origin_isolated,
)
};
WorkerGlobalBootstrapPropertiesDeclaration::new(
external,
global,
cross_origin_isolated,
secure_context,
global,
)
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize worker global bootstrap properties: {error}"))?;
install_worker_performance(scope, global)?;
install_worker_global_scope_constructors(scope, global, &global_kind, worker_templates)?;
let (_, realm_kind) = worker_global_scope_interface(&global_kind);
crate::context_bootstrap::install_worker_lazy_exposed_interfaces(
scope,
global,
realm_kind,
secure_context,
)?;
crate::context_bootstrap::install_trusted_types_runtime_state(scope, global)?;
let require_trusted_types_for_script =
crate::content_security_policy::content_security_policy_requires_trusted_types_for_script(
&state.borrow().content_security_policies,
);
if require_trusted_types_for_script {
scope
.get_current_context()
.set_allow_generation_from_strings(false);
crate::context_bootstrap::install_trusted_types_eval_runtime_state(scope, global)?;
}
crate::context_bootstrap::install_webassembly_runtime_state(scope, global)?;
if matches!(
global_kind,
crate::worker::thread::WorkerGlobalKind::Service { .. }
) {
install_service_worker_extendable_event_constructors(scope, global)?;
}
crate::context_bootstrap::initialize_worker_fetch_realm_state(scope, global)?;
let subtle_crypto_available = secure_context;
crate::context_bootstrap::initialize_worker_crypto_realm_state(
scope,
global,
subtle_crypto_available,
)?;
crate::context_bootstrap::initialize_worker_file_realm_state(scope, global)?;
install_worker_create_image_bitmap(scope, global)?;
let identity = state
.borrow()
.loader
.request_client()
.browser_identity()
.clone();
crate::context_bootstrap::install_worker_navigator_runtime_state(
scope,
global,
secure_context,
&identity,
)?;
crate::context_bootstrap::install_worker_indexed_db_runtime_state(scope, global)?;
crate::context_bootstrap::install_worker_base64_runtime_state(scope, global)?;
install_simple_event_target_methods(scope, global, WORKER_GLOBAL_LISTENERS_SLOT, false);
install_simple_event_target_ordered_handlers(scope, global);
let script_url = state.borrow().current_script_url.clone();
let origin = script_url
.as_ref()
.map(moli_url::origin_ascii_serialization)
.unwrap_or_else(|| "null".to_owned());
WorkerGlobalOriginDeclaration::new(origin)
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize worker global origin: {error}"))?;
let worker_prototype = global_constructor_prototype(scope, "WorkerGlobalScope")
.ok_or_else(|| anyhow!("WorkerGlobalScope prototype missing"))?;
WorkerGlobalOriginPrototypeDeclaration::default()
.initialize(scope, worker_prototype)
.map_err(|error| anyhow!("failed to initialize WorkerGlobalScope origin: {error}"))?;
if let Some(script_url) = script_url {
crate::context_bootstrap::install_worker_location_runtime_state(
scope,
global,
&script_url,
)?;
crate::context_bootstrap::install_worker_script_url_runtime_state(
scope,
global,
&script_url,
)?;
}
match &global_kind {
crate::worker::thread::WorkerGlobalKind::Dedicated { name } => {
set_worker_global_name_prop(scope, global, name)?;
DedicatedWorkerGlobalPostMessageDeclaration::default()
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize worker postMessage: {error}"))?;
}
crate::worker::thread::WorkerGlobalKind::Shared { name, .. } => {
set_worker_global_name_prop(scope, global, name)?;
}
crate::worker::thread::WorkerGlobalKind::Service {
registration_id,
version_id,
scope_url,
} => {
set_worker_global_name_prop(scope, global, "")?;
install_service_worker_global_runtime(
scope,
global,
*registration_id,
*version_id,
scope_url,
)?;
}
}
WorkerGlobalCommonOperationsDeclaration::default()
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize worker global operations: {error}"))?;
install_worker_global_event_handler_accessors(scope, global, &global_kind)?;
// console
install_console(scope, global)?;
WorkerGlobalTimerOperationsDeclaration::default()
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize worker timers: {error}"))?;
crate::context_bootstrap::exposed_interfaces::capture_eager_intrinsic_interfaces(
scope, global, realm_kind,
)?;
Ok(())
}
fn set_worker_global_name_prop<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
name: &str,
) -> Result<()> {
// WorkerGlobalScope.name is [Replaceable] readonly in IDL. Browsers expose
// an own global property whose assignment becomes a normal enumerable data
// property, which WPT observes through Object.getOwnPropertyDescriptor.
WorkerGlobalNameDeclaration::new(name.to_owned())
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize worker global `name`: {error}"))
}
fn install_worker_create_image_bitmap<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
) -> Result<()> {
WorkerGlobalCreateImageBitmapDeclaration::default()
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize createImageBitmap: {error}"))
}
fn worker_create_image_bitmap_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
_args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(resolver) = v8::PromiseResolver::new(scope) else {
rv.set_undefined();
return;
};
let promise = resolver.get_promise(scope);
let reason = worker_dom_exception_value(
scope,
"The source image could not be decoded.",
"InvalidStateError",
);
let _ = resolver.reject(scope, reason);
rv.set(promise.into());
}
pub(in crate::worker) struct PreparedWorkerGlobalScopeTemplates {
event_target: v8::Global<v8::FunctionTemplate>,
worker: v8::Global<v8::FunctionTemplate>,
specific_worker: v8::Global<v8::FunctionTemplate>,
}
impl PreparedWorkerGlobalScopeTemplates {
pub(in crate::worker) fn global_template<'s>(
&self,
scope: &mut v8::PinScope<'s, '_, ()>,
) -> v8::Local<'s, v8::ObjectTemplate> {
v8::Local::new(scope, &self.specific_worker).instance_template(scope)
}
}
fn worker_global_scope_interface(
global_kind: &crate::worker::thread::WorkerGlobalKind,
) -> (
&'static str,
crate::context_bootstrap::exposed_interfaces::RealmKind,
) {
use crate::context_bootstrap::exposed_interfaces::RealmKind;
match global_kind {
crate::worker::thread::WorkerGlobalKind::Dedicated { .. } => {
("DedicatedWorkerGlobalScope", RealmKind::DedicatedWorker)
}
crate::worker::thread::WorkerGlobalKind::Shared { .. } => {
("SharedWorkerGlobalScope", RealmKind::SharedWorker)
}
crate::worker::thread::WorkerGlobalKind::Service { .. } => {
("ServiceWorkerGlobalScope", RealmKind::ServiceWorker)
}
}
}
pub(in crate::worker) fn prepare_worker_global_scope_templates<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
global_kind: &crate::worker::thread::WorkerGlobalKind,
) -> Result<PreparedWorkerGlobalScopeTemplates> {
let (interface, realm_kind) = worker_global_scope_interface(global_kind);
let event_target =
crate::context_bootstrap::prepare_worker_event_target_template(scope, realm_kind)?;
// Every object in a [Global] object's prototype chain has an immutable
// prototype. Install the inherited templates before creating the context.
event_target.prototype_template(scope).set_immutable_proto();
let worker = worker_global_scope_template(scope, "WorkerGlobalScope");
worker.inherit(event_target);
let specific_worker = worker_global_scope_template(scope, interface);
specific_worker.inherit(worker);
specific_worker
.instance_template(scope)
.set_immutable_proto();
Ok(PreparedWorkerGlobalScopeTemplates {
event_target: v8::Global::new(scope, event_target),
worker: v8::Global::new(scope, worker),
specific_worker: v8::Global::new(scope, specific_worker),
})
}
fn worker_global_scope_template<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
name: &'static str,
) -> v8::Local<'s, v8::FunctionTemplate> {
let template =
v8::FunctionTemplate::builder(worker_global_scope_constructor_callback).build(scope);
template.set_class_name(v8str(scope, name));
template.prototype_template(scope).set_immutable_proto();
template
}
fn install_worker_global_scope_constructors<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
global_kind: &crate::worker::thread::WorkerGlobalKind,
worker_templates: Option<&PreparedWorkerGlobalScopeTemplates>,
) -> Result<()> {
let (interface, _) = worker_global_scope_interface(global_kind);
web_api_interfaces::initialize(scope, global, interface)?;
let templates = worker_templates.ok_or_else(|| {
anyhow!("worker global scope templates were not prepared before context creation")
})?;
let event_target_template = v8::Local::new(scope, &templates.event_target);
let event_target_ctor = event_target_template
.get_function(scope)
.ok_or_else(|| anyhow!("failed to instantiate worker EventTarget constructor"))?;
let worker_template = v8::Local::new(scope, &templates.worker);
let worker_ctor = worker_template
.get_function(scope)
.ok_or_else(|| anyhow!("failed to instantiate WorkerGlobalScope constructor"))?;
worker_ctor
.set_prototype(scope, event_target_ctor.into())
.unwrap_or(false)
.then_some(())
.ok_or_else(|| anyhow!("failed to inherit worker EventTarget constructor"))?;
let worker_proto = constructor_prototype(scope, worker_ctor, "WorkerGlobalScope")?;
set_worker_to_string_tag(scope, worker_proto, "WorkerGlobalScope");
WorkerGlobalScopeConstructorGlobalDeclaration::new(worker_ctor)
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize WorkerGlobalScope global: {error}"))?;
let specific_template = v8::Local::new(scope, &templates.specific_worker);
let specific_ctor = specific_template
.get_function(scope)
.ok_or_else(|| anyhow!("failed to instantiate {interface} constructor"))?;
specific_ctor
.set_prototype(scope, worker_ctor.into())
.unwrap_or(false)
.then_some(())
.ok_or_else(|| anyhow!("failed to inherit {interface} constructor"))?;
let specific_proto = constructor_prototype(scope, specific_ctor, interface)?;
set_worker_to_string_tag(scope, specific_proto, interface);
match global_kind {
crate::worker::thread::WorkerGlobalKind::Dedicated { .. } => {
DedicatedWorkerGlobalMethodsDeclaration::default().initialize(scope, specific_proto)?;
DedicatedWorkerGlobalScopeConstructorGlobalDeclaration::new(specific_ctor)
.initialize(scope, global)?;
}
crate::worker::thread::WorkerGlobalKind::Shared { .. } => {
SharedWorkerGlobalMethodsDeclaration::default().initialize(scope, specific_proto)?;
SharedWorkerGlobalScopeConstructorGlobalDeclaration::new(specific_ctor)
.initialize(scope, global)?;
}
crate::worker::thread::WorkerGlobalKind::Service { .. } => {
ServiceWorkerGlobalScopeConstructorGlobalDeclaration::new(specific_ctor)
.initialize(scope, global)?;
ensure_worker_interface_constructor(scope, "NavigationPreloadManager")?;
}
}
if !global
.get_prototype(scope)
.is_some_and(|prototype| prototype.strict_equals(specific_proto.into()))
{
return Err(anyhow!(
"worker global template did not install {interface}.prototype"
));
}
Ok(())
}
pub(super) fn ensure_worker_interface_constructor<'s>(
scope: &mut v8::PinScope<'s, '_>,
name: &'static str,
) -> Result<()> {
let global = scope.get_current_context().global(scope);
if global
.get(scope, v8str(scope, name).into())
.is_some_and(|value| !value.is_undefined())
{
return Ok(());
}
let constructor = worker_scope_constructor(scope, name)?;
let prototype = constructor_prototype(scope, constructor, name)?;
set_worker_to_string_tag(scope, prototype, name);
set_prop(scope, global, name, constructor.into())
}
fn worker_scope_constructor<'s>(
scope: &mut v8::PinScope<'s, '_>,
name: &'static str,
) -> Result<v8::Local<'s, v8::Function>> {
let constructor = v8::Function::builder(worker_global_scope_constructor_callback)
.build(scope)
.ok_or_else(|| anyhow!("failed to build {name} constructor"))?;
constructor.set_name(v8str(scope, name));
Ok(constructor)
}
pub(super) fn constructor_prototype<'s>(
scope: &mut v8::PinScope<'s, '_>,
constructor: v8::Local<'s, v8::Function>,
name: &'static str,
) -> Result<v8::Local<'s, v8::Object>> {
let prototype_key = v8str(scope, "prototype");
let prototype = constructor
.get(scope, prototype_key.into())
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
.ok_or_else(|| anyhow!("{name} constructor prototype missing"))?;
WorkerScopePrototypeConstructorDeclaration::new(constructor)
.initialize(scope, prototype)
.map_err(|error| anyhow!("failed to initialize {name}.prototype.constructor: {error}"))?;
Ok(prototype)
}
pub(super) fn set_worker_to_string_tag<'s>(
scope: &mut v8::PinScope<'s, '_>,
prototype: v8::Local<'s, v8::Object>,
tag: &'static str,
) {
let _ = WorkerPrototypeTagDeclaration::new(tag).initialize(scope, prototype);
}
fn worker_global_scope_constructor_callback(
scope: &mut v8::PinScope<'_, '_>,
_args: v8::FunctionCallbackArguments<'_>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
throw_type_error(scope, "Illegal constructor.");
}
fn set_prop(
scope: &mut v8::PinScope<'_, '_>,
obj: v8::Local<'_, v8::Object>,
name: &str,
value: v8::Local<'_, v8::Value>,
) -> Result<()> {
let key =
v8_string(scope, name).ok_or_else(|| anyhow!("failed to allocate worker key `{name}`"))?;
obj.define_own_property(scope, key.into(), value, v8::PropertyAttribute::DONT_ENUM)
.unwrap_or(false)
.then_some(())
.ok_or_else(|| anyhow!("failed to set worker global `{name}`"))
}
@@ -0,0 +1,186 @@
//! Worker console bindings and forwarding to captured native console methods.
use super::*;
const WORKER_ORIGINAL_CONSOLE_SLOT: &str = "__moliWorkerOriginalConsole";
#[derive(WebApiObject)]
#[webapi(plain)]
struct WorkerGlobalConsoleDeclaration<'scope> {
#[webapi(data_property)]
console: v8::Local<'scope, v8::Object>,
}
#[derive(Default, WebApiObject)]
#[webapi(plain)]
struct WorkerConsoleObjectDeclaration {
#[webapi(method, enumerable, callback = console_log_callback, data = v8str(scope, "log"))]
log: (),
#[webapi(method, enumerable, callback = console_log_callback, data = v8str(scope, "info"))]
info: (),
#[webapi(method, enumerable, callback = console_log_callback, data = v8str(scope, "warn"))]
warn: (),
#[webapi(method, enumerable, callback = console_log_callback, data = v8str(scope, "error"))]
error: (),
#[webapi(method, enumerable, callback = console_log_callback, data = v8str(scope, "debug"))]
debug: (),
#[webapi(method, enumerable, callback = console_log_callback, data = v8str(scope, "trace"))]
trace: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
time: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
time_log: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
time_end: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
count: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
count_reset: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
dir: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
dirxml: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
table: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
group: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
group_end: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
group_collapsed: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
clear: (),
#[webapi(method, enumerable, callback = console_noop_callback)]
assert: (),
#[webapi(method, enumerable, callback = console_profile_callback)]
profile: (),
#[webapi(method, enumerable, callback = console_profile_end_callback)]
profile_end: (),
}
pub(super) fn install_console<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
) -> Result<()> {
let console_key = v8str(scope, "console");
if let Some(original_console) = global.get(scope, console_key.into()) {
set_private_value(
scope,
global,
WORKER_ORIGINAL_CONSOLE_SLOT,
original_console,
);
}
let _ = global.delete(scope, console_key.into());
let console = WorkerConsoleObjectDeclaration::default()
.bind(scope)
.map_err(|error| anyhow!("failed to create worker console: {error}"))?;
WorkerGlobalConsoleDeclaration::new(console)
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize worker console: {error}"))
}
fn format_console_args(
scope: &mut v8::PinScope<'_, '_>,
args: &v8::FunctionCallbackArguments<'_>,
) -> String {
let mut parts = Vec::new();
for i in 0..args.length() {
let val = args.get(i);
if let Some(s) = val.to_detail_string(scope) {
parts.push(s.to_rust_string_lossy(scope));
}
}
parts.join(" ")
}
fn console_arg_snapshots_json(
scope: &mut v8::PinScope<'_, '_>,
args: &v8::FunctionCallbackArguments<'_>,
) -> Vec<serde_json::Value> {
let mut snapshots = Vec::with_capacity(args.length().max(0) as usize);
for index in 0..args.length() {
snapshots.push(crate::context_bootstrap::console_arg_remote_object_json(
scope,
args.get(index),
));
}
snapshots
}
fn console_log_callback(
scope: &mut v8::PinScope<'_, '_>,
args: v8::FunctionCallbackArguments<'_>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
let level = args
.data()
.to_string(scope)
.map(|value| value.to_rust_string_lossy(scope))
.unwrap_or_else(|| "log".to_owned());
let text = format_console_args(scope, &args);
let message = format!("{level}: {text}");
let arg_snapshots = console_arg_snapshots_json(scope, &args);
let stack = crate::context_bootstrap::current_console_stack(scope);
tracing::trace!("{}", message);
if let Some(state) = get_worker_state(scope) {
let parent_tx = state.borrow().parent_tx.clone();
let _ = parent_tx.send(WorkerToParentMessage::Console(WorkerConsoleMessage {
message,
args: arg_snapshots,
stack,
}));
}
}
fn console_noop_callback(
_scope: &mut v8::PinScope<'_, '_>,
_args: v8::FunctionCallbackArguments<'_>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
}
fn console_profile_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
call_original_worker_console_method(scope, &args, "profile");
rv.set_undefined();
}
fn console_profile_end_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
call_original_worker_console_method(scope, &args, "profileEnd");
rv.set_undefined();
}
fn call_original_worker_console_method<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: &v8::FunctionCallbackArguments<'s>,
method_name: &'static str,
) {
let global = scope.get_current_context().global(scope);
let Some(original_console) = get_private_value(scope, global, WORKER_ORIGINAL_CONSOLE_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
else {
return;
};
let Some(method) = original_console
.get(scope, v8str(scope, method_name).into())
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
else {
return;
};
let mut forwarded_args = Vec::with_capacity(args.length().max(0) as usize);
for index in 0..args.length() {
forwarded_args.push(args.get(index));
}
let _ = method.call(scope, original_console.into(), &forwarded_args);
}
@@ -32,8 +32,8 @@ use moli_fetch::{
};
use super::{
PendingWorkerCspReport, WORKER_GLOBAL_LISTENERS_SLOT, WorkerGlobalState, next_fetch_id,
record_worker_subresource_failure_with_handle, request_body_text,
PendingWorkerCspReport, WORKER_GLOBAL_LISTENERS_SLOT, WorkerGlobalState, get_worker_state,
next_fetch_id, record_worker_subresource_failure_with_handle, request_body_text,
};
pub(super) fn dispatch_worker_content_security_policy_violation_event<'s>(
@@ -961,6 +961,55 @@ pub(super) fn worker_content_security_policy_error_message(
)
}
pub(in crate::worker) fn dispatch_worker_csp_violation_event<'s>(
scope: &mut v8::PinScope<'s, '_>,
loader: &crate::network::context::WorkerResourceLoader,
violation: &crate::content_security_policy::ContentSecurityPolicyUrlViolation,
) {
self::dispatch_worker_content_security_policy_violation_event(scope, loader, violation);
}
pub(crate) fn worker_allows_trusted_type_policy_name(
scope: &mut v8::PinScope<'_, '_>,
name: &str,
) -> Option<bool> {
Some(
crate::content_security_policy::content_security_policy_allows_trusted_type_policy_name(
&get_worker_state(scope)?.borrow().content_security_policies,
name,
),
)
}
pub(crate) fn worker_requires_trusted_types_for_script(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<bool> {
Some(
crate::content_security_policy::content_security_policy_requires_trusted_types_for_script(
&get_worker_state(scope)?.borrow().content_security_policies,
),
)
}
pub(crate) fn worker_allows_trusted_types_eval(scope: &mut v8::PinScope<'_, '_>) -> Option<bool> {
Some(
crate::content_security_policy::content_security_policy_allows_trusted_types_eval(
&get_worker_state(scope)?.borrow().content_security_policies,
),
)
}
pub(crate) fn dispatch_worker_trusted_types_sink_violation_event(
scope: &mut v8::PinScope<'_, '_>,
sink: &str,
sample: &str,
) {
let Some(state) = get_worker_state(scope) else {
return;
};
self::dispatch_worker_trusted_types_sink_violation_event_for_state(scope, &state, sink, sample);
}
pub(super) fn worker_compilation_content_security_policy_violations(
state: &WorkerGlobalState,
protected_url: &Url,
@@ -986,3 +1035,81 @@ pub(super) fn worker_compilation_content_security_policy_violations(
})
.collect()
}
pub(crate) fn worker_allows_eval_code_generation_by_csp(
scope: &mut v8::PinScope<'_, '_>,
allow_trusted_types_eval: bool,
source: Option<&str>,
) -> Option<bool> {
use crate::content_security_policy::ContentSecurityPolicyNonUrlKind;
let kind = if allow_trusted_types_eval {
ContentSecurityPolicyNonUrlKind::TrustedTypesEval
} else {
ContentSecurityPolicyNonUrlKind::Eval
};
worker_allows_compilation_by_csp(scope, kind, source)
}
fn worker_allows_compilation_by_csp(
scope: &mut v8::PinScope<'_, '_>,
kind: crate::content_security_policy::ContentSecurityPolicyNonUrlKind,
source: Option<&str>,
) -> Option<bool> {
let state = get_worker_state(scope)?;
let (wake_tx, mut report_only_violations, mut enforce_violations) = {
let state = state.borrow();
let Some(protected_url) = state.current_script_url.as_ref() else {
return Some(true);
};
(
state.worker_wake_tx.clone(),
worker_compilation_content_security_policy_violations(
&state,
protected_url,
kind,
source,
crate::content_security_policy::ContentSecurityPolicyDisposition::Report,
),
worker_compilation_content_security_policy_violations(
&state,
protected_url,
kind,
source,
crate::content_security_policy::ContentSecurityPolicyDisposition::Enforce,
),
)
};
if kind != crate::content_security_policy::ContentSecurityPolicyNonUrlKind::WasmEval
&& (!report_only_violations.is_empty() || !enforce_violations.is_empty())
&& let Some((source_file, line_number, column_number)) =
crate::content_security_policy::current_script_violation_location(scope)
{
for violation in [&mut report_only_violations, &mut enforce_violations]
.into_iter()
.flatten()
{
violation.source_file.clone_from(&source_file);
violation.line_number = line_number;
violation.column_number = column_number;
}
}
let allowed = enforce_violations.is_empty();
for violation in report_only_violations.into_iter().chain(enforce_violations) {
let _ = wake_tx.send(
crate::worker::handle::WorkerMessage::DispatchContentSecurityPolicyViolation(Box::new(
violation,
)),
);
}
Some(allowed)
}
pub(in crate::worker) fn dispatch_worker_csp_violation_event_for_state<'s>(
scope: &mut v8::PinScope<'s, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
violation: &crate::content_security_policy::ContentSecurityPolicyUrlViolation,
) {
self::dispatch_worker_content_security_policy_violation_event_for_state(
scope, state, violation,
);
}
@@ -0,0 +1,747 @@
//! Worker event-handler properties, private slots and fetch-handler classification.
use super::*;
pub(in crate::worker) const WORKER_GLOBAL_LISTENERS_SLOT: &str = "__moliWorkerGlobalListeners";
const WORKER_GLOBAL_ONMESSAGE_SLOT: &str = "__moliWorkerGlobalOnMessage";
const WORKER_GLOBAL_ONMESSAGEERROR_SLOT: &str = "__moliWorkerGlobalOnMessageError";
const WORKER_GLOBAL_ONERROR_SLOT: &str = "__moliWorkerGlobalOnError";
const WORKER_GLOBAL_ONCONNECT_SLOT: &str = "__moliWorkerGlobalOnConnect";
const WORKER_GLOBAL_ONINSTALL_SLOT: &str = "__moliWorkerGlobalOnInstall";
const WORKER_GLOBAL_ONACTIVATE_SLOT: &str = "__moliWorkerGlobalOnActivate";
const WORKER_GLOBAL_ONFETCH_SLOT: &str = "__moliWorkerGlobalOnFetch";
const WORKER_GLOBAL_ONPUSH_SLOT: &str = "__moliWorkerGlobalOnPush";
const WORKER_GLOBAL_ONSYNC_SLOT: &str = "__moliWorkerGlobalOnSync";
const WORKER_GLOBAL_ONPERIODICSYNC_SLOT: &str = "__moliWorkerGlobalOnPeriodicSync";
const WORKER_GLOBAL_ONNOTIFICATIONCLICK_SLOT: &str = "__moliWorkerGlobalOnNotificationClick";
const WORKER_GLOBAL_ONNOTIFICATIONCLOSE_SLOT: &str = "__moliWorkerGlobalOnNotificationClose";
const WORKER_GLOBAL_ONOFFLINE_SLOT: &str = "__moliWorkerGlobalOnOffline";
const WORKER_GLOBAL_ONONLINE_SLOT: &str = "__moliWorkerGlobalOnOnline";
const WORKER_GLOBAL_ONUNHANDLEDREJECTION_SLOT: &str = "__moliWorkerGlobalOnUnhandledRejection";
const WORKER_GLOBAL_ONREJECTIONHANDLED_SLOT: &str = "__moliWorkerGlobalOnRejectionHandled";
#[derive(Default, WebApiObject)]
#[webapi(plain, enumerable)]
struct WorkerGlobalCommonEventHandlersDeclaration {
#[webapi(
accessor_property = "onerror",
getter = worker_global_onerror_getter,
setter = worker_global_onerror_setter
)]
onerror: (),
#[webapi(
accessor_property = "onoffline",
getter = worker_global_onoffline_getter,
setter = worker_global_onoffline_setter
)]
onoffline: (),
#[webapi(
accessor_property = "ononline",
getter = worker_global_ononline_getter,
setter = worker_global_ononline_setter
)]
ononline: (),
#[webapi(
accessor_property = "onunhandledrejection",
getter = worker_global_onunhandledrejection_getter,
setter = worker_global_onunhandledrejection_setter
)]
onunhandledrejection: (),
#[webapi(
accessor_property = "onrejectionhandled",
getter = worker_global_onrejectionhandled_getter,
setter = worker_global_onrejectionhandled_setter
)]
onrejectionhandled: (),
}
#[derive(Default, WebApiObject)]
#[webapi(plain, enumerable)]
struct DedicatedWorkerGlobalEventHandlersDeclaration {
#[webapi(
accessor_property = "onmessage",
getter = worker_global_onmessage_getter,
setter = worker_global_onmessage_setter
)]
onmessage: (),
#[webapi(
accessor_property = "onmessageerror",
getter = worker_global_onmessageerror_getter,
setter = worker_global_onmessageerror_setter
)]
onmessageerror: (),
}
#[derive(Default, WebApiObject)]
#[webapi(plain, enumerable)]
struct SharedWorkerGlobalEventHandlersDeclaration {
#[webapi(
accessor_property = "onconnect",
getter = worker_global_onconnect_getter,
setter = worker_global_onconnect_setter
)]
onconnect: (),
}
#[derive(Default, WebApiObject)]
#[webapi(plain)]
struct WorkerGlobalCommonEventHandlerStateDeclaration {
#[webapi(slot = WORKER_GLOBAL_ONERROR_SLOT, init = "null")]
onerror: (),
#[webapi(slot = WORKER_GLOBAL_ONOFFLINE_SLOT, init = "null")]
onoffline: (),
#[webapi(slot = WORKER_GLOBAL_ONONLINE_SLOT, init = "null")]
ononline: (),
#[webapi(slot = WORKER_GLOBAL_ONUNHANDLEDREJECTION_SLOT, init = "null")]
onunhandledrejection: (),
#[webapi(slot = WORKER_GLOBAL_ONREJECTIONHANDLED_SLOT, init = "null")]
onrejectionhandled: (),
}
#[derive(Default, WebApiObject)]
#[webapi(plain)]
struct DedicatedWorkerGlobalEventHandlerStateDeclaration {
#[webapi(slot = WORKER_GLOBAL_ONMESSAGE_SLOT, init = "null")]
onmessage: (),
#[webapi(slot = WORKER_GLOBAL_ONMESSAGEERROR_SLOT, init = "null")]
onmessageerror: (),
}
#[derive(Default, WebApiObject)]
#[webapi(plain)]
struct SharedWorkerGlobalEventHandlerStateDeclaration {
#[webapi(slot = WORKER_GLOBAL_ONCONNECT_SLOT, init = "null")]
onconnect: (),
}
#[derive(Default, WebApiObject)]
#[webapi(plain, enumerable)]
struct ServiceWorkerGlobalEventHandlersDeclaration {
#[webapi(
accessor_property = "oninstall",
getter = worker_global_oninstall_getter,
setter = worker_global_oninstall_setter
)]
oninstall: (),
#[webapi(
accessor_property = "onactivate",
getter = worker_global_onactivate_getter,
setter = worker_global_onactivate_setter
)]
onactivate: (),
#[webapi(
accessor_property = "onfetch",
getter = worker_global_onfetch_getter,
setter = worker_global_onfetch_setter
)]
onfetch: (),
#[webapi(
accessor_property = "onpush",
getter = worker_global_onpush_getter,
setter = worker_global_onpush_setter
)]
onpush: (),
#[webapi(
accessor_property = "onsync",
getter = worker_global_onsync_getter,
setter = worker_global_onsync_setter
)]
onsync: (),
#[webapi(
accessor_property = "onperiodicsync",
getter = worker_global_onperiodicsync_getter,
setter = worker_global_onperiodicsync_setter
)]
onperiodicsync: (),
#[webapi(
accessor_property = "onmessage",
getter = worker_global_onmessage_getter,
setter = worker_global_onmessage_setter
)]
onmessage: (),
#[webapi(
accessor_property = "onmessageerror",
getter = worker_global_onmessageerror_getter,
setter = worker_global_onmessageerror_setter
)]
onmessageerror: (),
#[webapi(
accessor_property = "onnotificationclick",
getter = worker_global_onnotificationclick_getter,
setter = worker_global_onnotificationclick_setter
)]
onnotificationclick: (),
#[webapi(
accessor_property = "onnotificationclose",
getter = worker_global_onnotificationclose_getter,
setter = worker_global_onnotificationclose_setter
)]
onnotificationclose: (),
}
#[derive(Default, WebApiObject)]
#[webapi(plain)]
struct ServiceWorkerGlobalEventHandlerStateDeclaration {
#[webapi(slot = WORKER_GLOBAL_ONINSTALL_SLOT, init = "null")]
oninstall: (),
#[webapi(slot = WORKER_GLOBAL_ONACTIVATE_SLOT, init = "null")]
onactivate: (),
#[webapi(slot = WORKER_GLOBAL_ONFETCH_SLOT, init = "null")]
onfetch: (),
#[webapi(slot = WORKER_GLOBAL_ONPUSH_SLOT, init = "null")]
onpush: (),
#[webapi(slot = WORKER_GLOBAL_ONSYNC_SLOT, init = "null")]
onsync: (),
#[webapi(slot = WORKER_GLOBAL_ONPERIODICSYNC_SLOT, init = "null")]
onperiodicsync: (),
#[webapi(slot = WORKER_GLOBAL_ONMESSAGE_SLOT, init = "null")]
onmessage: (),
#[webapi(slot = WORKER_GLOBAL_ONMESSAGEERROR_SLOT, init = "null")]
onmessageerror: (),
#[webapi(slot = WORKER_GLOBAL_ONNOTIFICATIONCLICK_SLOT, init = "null")]
onnotificationclick: (),
#[webapi(slot = WORKER_GLOBAL_ONNOTIFICATIONCLOSE_SLOT, init = "null")]
onnotificationclose: (),
}
pub(super) fn install_worker_global_event_handler_accessors<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
global_kind: &crate::worker::thread::WorkerGlobalKind,
) -> Result<()> {
if matches!(
global_kind,
crate::worker::thread::WorkerGlobalKind::Dedicated { .. }
) {
DedicatedWorkerGlobalEventHandlersDeclaration::default().initialize(scope, global)?;
DedicatedWorkerGlobalEventHandlerStateDeclaration::default().initialize(scope, global)?;
}
if matches!(
global_kind,
crate::worker::thread::WorkerGlobalKind::Shared { .. }
) {
SharedWorkerGlobalEventHandlersDeclaration::default().initialize(scope, global)?;
SharedWorkerGlobalEventHandlerStateDeclaration::default().initialize(scope, global)?;
}
if matches!(
global_kind,
crate::worker::thread::WorkerGlobalKind::Service { .. }
) {
ServiceWorkerGlobalEventHandlersDeclaration::default().initialize(scope, global)?;
ServiceWorkerGlobalEventHandlerStateDeclaration::default().initialize(scope, global)?;
}
WorkerGlobalCommonEventHandlersDeclaration::default().initialize(scope, global)?;
WorkerGlobalCommonEventHandlerStateDeclaration::default().initialize(scope, global)?;
Ok(())
}
fn worker_global_event_handler_value<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
slot_name: &str,
) -> v8::Local<'s, v8::Value> {
get_private_value(scope, global, slot_name).unwrap_or_else(|| v8::null(scope).into())
}
fn set_worker_global_event_handler<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
value: v8::Local<'s, v8::Value>,
slot_name: &'static str,
event_type: Option<&str>,
) {
let stored = if value.is_object() {
value
} else {
v8::null(scope).into()
};
let active = stored.is_object();
set_private_value(scope, global, slot_name, stored);
if let Some(event_type) = event_type {
simple_object_event_set_ordered_handler(
scope,
global,
WORKER_GLOBAL_LISTENERS_SLOT,
event_type,
slot_name,
active,
);
}
}
fn worker_global_onmessage_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONMESSAGE_SLOT,
));
}
fn worker_global_onmessage_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONMESSAGE_SLOT,
Some("message"),
);
}
fn worker_global_onmessageerror_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONMESSAGEERROR_SLOT,
));
}
fn worker_global_onmessageerror_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONMESSAGEERROR_SLOT,
Some("messageerror"),
);
}
fn worker_global_oninstall_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONINSTALL_SLOT,
));
}
fn worker_global_oninstall_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONINSTALL_SLOT,
Some("install"),
);
}
fn worker_global_onactivate_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONACTIVATE_SLOT,
));
}
fn worker_global_onactivate_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONACTIVATE_SLOT,
Some("activate"),
);
}
fn worker_global_onfetch_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONFETCH_SLOT,
));
}
fn worker_global_onfetch_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONFETCH_SLOT,
Some("fetch"),
);
}
fn worker_global_onpush_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONPUSH_SLOT,
));
}
fn worker_global_onpush_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONPUSH_SLOT,
Some("push"),
);
}
fn worker_global_onsync_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONSYNC_SLOT,
));
}
fn worker_global_onsync_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONSYNC_SLOT,
Some("sync"),
);
}
fn worker_global_onperiodicsync_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONPERIODICSYNC_SLOT,
));
}
fn worker_global_onperiodicsync_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONPERIODICSYNC_SLOT,
Some("periodicsync"),
);
}
fn worker_global_onnotificationclick_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONNOTIFICATIONCLICK_SLOT,
));
}
fn worker_global_onnotificationclick_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONNOTIFICATIONCLICK_SLOT,
Some("notificationclick"),
);
}
fn worker_global_onnotificationclose_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONNOTIFICATIONCLOSE_SLOT,
));
}
fn worker_global_onnotificationclose_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONNOTIFICATIONCLOSE_SLOT,
Some("notificationclose"),
);
}
fn worker_global_onerror_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONERROR_SLOT,
));
}
fn worker_global_onerror_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONERROR_SLOT,
None,
);
}
fn worker_global_onconnect_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONCONNECT_SLOT,
));
}
fn worker_global_onconnect_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONCONNECT_SLOT,
Some("connect"),
);
}
fn worker_global_onoffline_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONOFFLINE_SLOT,
));
}
fn worker_global_onoffline_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONOFFLINE_SLOT,
Some("offline"),
);
}
fn worker_global_ononline_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONONLINE_SLOT,
));
}
fn worker_global_ononline_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONONLINE_SLOT,
Some("online"),
);
}
fn worker_global_onunhandledrejection_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONUNHANDLEDREJECTION_SLOT,
));
}
fn worker_global_onunhandledrejection_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONUNHANDLEDREJECTION_SLOT,
Some("unhandledrejection"),
);
}
fn worker_global_onrejectionhandled_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(worker_global_event_handler_value(
scope,
args.this(),
WORKER_GLOBAL_ONREJECTIONHANDLED_SLOT,
));
}
fn worker_global_onrejectionhandled_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
set_worker_global_event_handler(
scope,
args.this(),
args.get(0),
WORKER_GLOBAL_ONREJECTIONHANDLED_SLOT,
Some("rejectionhandled"),
);
}
pub(in crate::worker) fn service_worker_fetch_handler_type<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
) -> WorkerFetchHandlerType {
let listeners = simple_object_event_listeners_snapshot(
scope,
global,
WORKER_GLOBAL_LISTENERS_SLOT,
"fetch",
);
if listeners.is_empty() {
return WorkerFetchHandlerType::NoHandler;
}
if listeners.iter().all(|listener| {
listener
.callable_function()
.is_some_and(|callback| function_source_has_empty_body(scope, callback))
}) {
WorkerFetchHandlerType::EmptyFetchHandler
} else {
WorkerFetchHandlerType::NotSkippable
}
}
fn function_source_has_empty_body<'s>(
scope: &mut v8::PinScope<'s, '_>,
function: v8::Local<'s, v8::Function>,
) -> bool {
let Some(source) = function.to_string(scope) else {
return false;
};
let normalized = source
.to_rust_string_lossy(scope)
.chars()
.filter(|ch| !ch.is_whitespace())
.collect::<String>();
let Some(open_brace) = normalized.find('{') else {
return false;
};
let Some(close_brace) = normalized.rfind('}') else {
return false;
};
open_brace < close_brace && normalized[open_brace + 1..close_brace].is_empty()
}
@@ -0,0 +1,560 @@
//! Service Worker extendable events and their pending dispatch state.
use super::*;
#[derive(WebApiFunctionTemplate)]
#[webapi(
interface = web_api_interfaces::ExtendableEvent,
constructor_callback = extendable_event_constructor_callback,
constructor_length = 1
)]
struct ExtendableEventTemplateDeclaration {
#[webapi(method = "waitUntil", callback = extendable_event_wait_until_callback, length = 1)]
wait_until: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(
interface = web_api_interfaces::ExtendableMessageEvent,
constructor_callback = extendable_message_event_constructor_callback,
constructor_length = 1
)]
struct ExtendableMessageEventTemplateDeclaration {}
#[derive(WebApiObject)]
#[webapi(plain)]
struct ExtendableEventConstructorGlobalDeclaration<'scope> {
#[webapi(data_property = "ExtendableEvent")]
extendable_event: v8::Local<'scope, v8::Function>,
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct ExtendableMessageEventConstructorGlobalDeclaration<'scope> {
#[webapi(data_property = "ExtendableMessageEvent")]
extendable_message_event: v8::Local<'scope, v8::Function>,
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::ExtendableEvent, prototype = "Object")]
struct ExtendableEventStateDeclaration {
#[webapi(data_property, enumerable)]
composed: bool,
}
#[derive(WebApiObject)]
#[webapi(
interface = web_api_interfaces::ExtendableMessageEvent,
prototype = "Object"
)]
struct ExtendableMessageEventStateDeclaration<'scope> {
#[webapi(data_property, enumerable)]
data: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
origin: String,
#[webapi(data_property = "lastEventId", enumerable)]
last_event_id: String,
#[webapi(data_property, enumerable)]
source: v8::Local<'scope, v8::Value>,
#[webapi(data_property, enumerable)]
ports: v8::Local<'scope, v8::Array>,
}
pub(in crate::worker) struct PendingServiceWorkerLifecycleEvent {
pub(in crate::worker) completion: ServiceWorkerLifecycleCompletion,
pub(in crate::worker) pending_wait_until_count: usize,
pub(in crate::worker) dispatch_finished: bool,
}
pub(in crate::worker) struct PendingServiceWorkerMessageEvent {
pub(in crate::worker) completion: ServiceWorkerMessageCompletion,
pub(in crate::worker) pending_wait_until_count: usize,
pub(in crate::worker) dispatch_finished: bool,
pub(in crate::worker) window_interaction_allowed: bool,
}
pub(in crate::worker) struct PendingServiceWorkerNotificationEvent {
pub(in crate::worker) completion: crate::runtime::ServiceWorkerNotificationCompletion,
pub(in crate::worker) pending_wait_until_count: usize,
pub(in crate::worker) dispatch_finished: bool,
pub(in crate::worker) window_interaction_allowed: bool,
}
pub(in crate::worker) struct PendingServiceWorkerPushEvent {
pub(in crate::worker) completion: crate::runtime::ServiceWorkerPushCompletion,
pub(in crate::worker) pending_wait_until_count: usize,
pub(in crate::worker) dispatch_finished: bool,
}
pub(in crate::worker) struct PendingServiceWorkerSyncEvent {
pub(in crate::worker) completion: crate::runtime::ServiceWorkerSyncCompletion,
pub(in crate::worker) pending_wait_until_count: usize,
pub(in crate::worker) dispatch_finished: bool,
}
pub(in crate::worker) struct PendingServiceWorkerPeriodicSyncEvent {
pub(in crate::worker) completion: crate::runtime::ServiceWorkerPeriodicSyncCompletion,
pub(in crate::worker) pending_wait_until_count: usize,
pub(in crate::worker) dispatch_finished: bool,
}
pub(in crate::worker) struct PendingServiceWorkerFetchEvent {
pub(in crate::worker) completion: ServiceWorkerFetchCompletion,
pub(in crate::worker) handled_resolver: Option<v8::Global<v8::PromiseResolver>>,
pub(in crate::worker) request_signal_id: Option<u32>,
pub(in crate::worker) request_mode: RequestMode,
pub(in crate::worker) request_destination: ServiceWorkerRequestDestination,
pub(in crate::worker) pending_respond_with_response:
Option<crate::network_host::MaterializedResponseHead>,
pub(in crate::worker) pending_respond_with_stream_body_source_id: Option<NetworkBodySourceId>,
pub(in crate::worker) pending_respond_with_stream_cancel_handle: Option<v8::Global<v8::Object>>,
pub(in crate::worker) respond_with_called: bool,
pub(in crate::worker) pending_respond_with: bool,
pub(in crate::worker) pending_wait_until_count: usize,
pub(in crate::worker) dispatch_finished: bool,
}
impl PendingServiceWorkerFetchEvent {
pub(in crate::worker) fn fallback(
completion: ServiceWorkerFetchCompletion,
request_mode: RequestMode,
request_destination: ServiceWorkerRequestDestination,
) -> Self {
Self {
completion: ServiceWorkerFetchCompletion {
result: ServiceWorkerFetchResult::Fallback,
..completion
},
handled_resolver: None,
request_signal_id: None,
request_mode,
request_destination,
pending_respond_with_response: None,
pending_respond_with_stream_body_source_id: None,
pending_respond_with_stream_cancel_handle: None,
respond_with_called: false,
pending_respond_with: false,
pending_wait_until_count: 0,
dispatch_finished: false,
}
}
}
pub(in crate::worker) struct PendingServiceWorkerNavigationPreload {
pub(in crate::worker) owner: crate::service_worker_runtime::ServiceWorkerRunOwner,
pub(in crate::worker) _promise: v8::Global<v8::Promise>,
pub(in crate::worker) resolver: Option<v8::Global<v8::PromiseResolver>>,
pub(in crate::worker) body_source_id: Option<NetworkBodySourceId>,
}
pub(super) fn install_service_worker_extendable_event_constructors<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
) -> Result<()> {
let extendable_template = ExtendableEventTemplateDeclaration::build(scope);
let extendable_ctor = extendable_template
.get_function(scope)
.ok_or_else(|| anyhow!("failed to build ExtendableEvent constructor"))?;
let extendable_proto = constructor_prototype(scope, extendable_ctor, "ExtendableEvent")?;
set_worker_to_string_tag(scope, extendable_proto, "ExtendableEvent");
if let Some(event_ctor) = global_constructor_object(scope, "Event") {
let _ = extendable_ctor.set_prototype(scope, event_ctor.into());
}
if let Some(event_proto) = global_constructor_prototype(scope, "Event") {
let _ = extendable_proto.set_prototype(scope, event_proto.into());
}
ExtendableEventConstructorGlobalDeclaration::new(extendable_ctor)
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize ExtendableEvent global: {error}"))?;
let message_template = ExtendableMessageEventTemplateDeclaration::build(scope);
let message_ctor = message_template
.get_function(scope)
.ok_or_else(|| anyhow!("failed to build ExtendableMessageEvent constructor"))?;
let message_proto = constructor_prototype(scope, message_ctor, "ExtendableMessageEvent")?;
set_worker_to_string_tag(scope, message_proto, "ExtendableMessageEvent");
let _ = message_ctor.set_prototype(scope, extendable_ctor.into());
let _ = message_proto.set_prototype(scope, extendable_proto.into());
ExtendableMessageEventConstructorGlobalDeclaration::new(message_ctor)
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize ExtendableMessageEvent global: {error}"))?;
ensure_worker_interface_constructor(scope, "ServiceWorker")?;
web_api_interfaces::WindowClient::DESCRIPTOR.register(scope)?;
ensure_worker_interface_constructor(scope, "Client")?;
ensure_worker_interface_constructor(scope, "WindowClient")?;
if let Some(client_ctor) = global_constructor_object(scope, "Client")
&& let Some(window_ctor) = global_constructor_object(scope, "WindowClient")
{
let _ = window_ctor.set_prototype(scope, client_ctor.into());
}
if let Some(client_proto) = global_constructor_prototype(scope, "Client")
&& let Some(window_proto) = global_constructor_prototype(scope, "WindowClient")
{
let _ = window_proto.set_prototype(scope, client_proto.into());
}
Ok(())
}
fn extendable_event_constructor_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s, v8::Value>,
) {
if !args.is_construct_call() {
throw_type_error(
scope,
"Failed to construct 'ExtendableEvent': Please use the 'new' operator.",
);
return;
}
let Some(event_type) = extendable_event_type_argument(scope, &args, "ExtendableEvent") else {
return;
};
let Some((bubbles, cancelable, composed)) =
extendable_event_init_flags(scope, &args, "ExtendableEvent")
else {
return;
};
initialize_extendable_event_object(
scope,
args.this(),
&event_type,
bubbles,
cancelable,
composed,
);
rv.set(args.this().into());
}
fn extendable_message_event_constructor_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s, v8::Value>,
) {
if !args.is_construct_call() {
throw_type_error(
scope,
"Failed to construct 'ExtendableMessageEvent': Please use the 'new' operator.",
);
return;
}
let Some(event_type) = extendable_event_type_argument(scope, &args, "ExtendableMessageEvent")
else {
return;
};
let init = extendable_event_init_object(&args);
let Some((bubbles, cancelable, composed)) =
extendable_event_init_flags(scope, &args, "ExtendableMessageEvent")
else {
return;
};
let Some(data) = extendable_message_event_data(scope, init) else {
return;
};
let Some(origin) = extendable_message_event_string_member(scope, init, "origin", "") else {
return;
};
let Some(last_event_id) =
extendable_message_event_string_member(scope, init, "lastEventId", "")
else {
return;
};
let Some(source) = extendable_message_event_source(scope, init) else {
return;
};
let Some(ports) = extendable_message_event_ports(scope, init) else {
return;
};
let event = args.this();
initialize_extendable_event_object(scope, event, &event_type, bubbles, cancelable, composed);
let _ = ExtendableMessageEventStateDeclaration::new(data, origin, last_event_id, source, ports)
.initialize(scope, event);
rv.set(event.into());
}
fn extendable_event_type_argument<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: &v8::FunctionCallbackArguments<'s>,
constructor_name: &'static str,
) -> Option<String> {
if args.length() == 0 {
throw_type_error(
scope,
&format!("Failed to construct '{constructor_name}': 1 argument required."),
);
return None;
}
webidl::argument::<webidl::DomString>(
scope,
args,
0,
webidl::Context::argument(constructor_name, 1),
)
.map(Into::into)
.map_or_else(
|error| {
webidl::throw_error(scope, &error);
None
},
Some,
)
}
fn extendable_event_init_object<'s>(
args: &v8::FunctionCallbackArguments<'s>,
) -> Option<v8::Local<'s, v8::Object>> {
if args.length() <= 1 {
return None;
}
let value = args.get(1);
if value.is_null_or_undefined() || !value.is_object() {
None
} else {
v8::Local::<v8::Object>::try_from(value).ok()
}
}
fn extendable_event_init_flags<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: &v8::FunctionCallbackArguments<'s>,
constructor_name: &'static str,
) -> Option<(bool, bool, bool)> {
let init = extendable_event_init_object(args);
Some((
extendable_event_bool_member(scope, init, constructor_name, "bubbles")?,
extendable_event_bool_member(scope, init, constructor_name, "cancelable")?,
extendable_event_bool_member(scope, init, constructor_name, "composed")?,
))
}
fn extendable_event_bool_member<'s>(
scope: &mut v8::PinScope<'s, '_>,
init: Option<v8::Local<'s, v8::Object>>,
prefix: &'static str,
key: &'static str,
) -> Option<bool> {
let Some(init) = init else {
return Some(false);
};
let value =
match webidl::property_result(scope, init, key, webidl::Context::member(prefix, key)) {
Ok(Some(value)) if !value.is_undefined() => value,
Ok(_) => return Some(false),
Err(error) => {
webidl::throw_error(scope, &error);
return None;
}
};
webidl::convert::<webidl::Boolean>(scope, value, webidl::Context::member(prefix, key))
.map(Into::into)
.map_or_else(
|error| {
webidl::throw_error(scope, &error);
None
},
Some,
)
}
fn initialize_extendable_event_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
event: v8::Local<'s, v8::Object>,
event_type: &str,
bubbles: bool,
cancelable: bool,
composed: bool,
) {
// EventTarget dispatch and inherited Event methods require the base
// event's internal state, in addition to its visible properties.
crate::context_bootstrap::initialize_event_object(
scope, event, event_type, bubbles, cancelable,
);
ExtendableEventStateDeclaration::new(composed)
.initialize(scope, event)
.expect("extendable event state should initialize");
}
fn extendable_message_event_data<'s>(
scope: &mut v8::PinScope<'s, '_>,
init: Option<v8::Local<'s, v8::Object>>,
) -> Option<v8::Local<'s, v8::Value>> {
let Some(init) = init else {
return Some(v8::null(scope).into());
};
match webidl::property_result(
scope,
init,
"data",
webidl::Context::member("ExtendableMessageEvent", "data"),
) {
Ok(Some(value)) if !value.is_undefined() => Some(value),
Ok(_) => Some(v8::null(scope).into()),
Err(error) => {
webidl::throw_error(scope, &error);
None
}
}
}
fn extendable_message_event_string_member<'s>(
scope: &mut v8::PinScope<'s, '_>,
init: Option<v8::Local<'s, v8::Object>>,
key: &'static str,
default: &'static str,
) -> Option<String> {
let Some(init) = init else {
return Some(default.to_owned());
};
let value = match webidl::property_result(
scope,
init,
key,
webidl::Context::member("ExtendableMessageEvent", key),
) {
Ok(Some(value)) if !value.is_undefined() => value,
Ok(_) => return Some(default.to_owned()),
Err(error) => {
webidl::throw_error(scope, &error);
return None;
}
};
webidl::convert::<webidl::DomString>(
scope,
value,
webidl::Context::member("ExtendableMessageEvent", key),
)
.map(Into::into)
.map_or_else(
|error| {
webidl::throw_error(scope, &error);
None
},
Some,
)
}
fn extendable_message_event_source<'s>(
scope: &mut v8::PinScope<'s, '_>,
init: Option<v8::Local<'s, v8::Object>>,
) -> Option<v8::Local<'s, v8::Value>> {
let Some(init) = init else {
return Some(v8::null(scope).into());
};
let value = match webidl::property_result(
scope,
init,
"source",
webidl::Context::member("ExtendableMessageEvent", "source"),
) {
Ok(Some(value)) if !value.is_null_or_undefined() => value,
Ok(_) => return Some(v8::null(scope).into()),
Err(error) => {
webidl::throw_error(scope, &error);
return None;
}
};
let Ok(object) = v8::Local::<v8::Object>::try_from(value) else {
throw_type_error(
scope,
"Failed to construct 'ExtendableMessageEvent': member source is not of type Client, ServiceWorker, or MessagePort.",
);
return None;
};
if extendable_message_event_source_object_is_valid(scope, object) {
Some(value)
} else {
throw_type_error(
scope,
"Failed to construct 'ExtendableMessageEvent': member source is not of type Client, ServiceWorker, or MessagePort.",
);
None
}
}
fn extendable_message_event_source_object_is_valid<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
) -> bool {
crate::context_bootstrap::message_port_id_from_object(scope, object).is_some()
|| get_private_value(scope, object, SERVICE_WORKER_CLIENT_ID_SLOT).is_some()
|| (get_private_value(scope, object, SERVICE_WORKER_VERSION_ID_SLOT).is_some()
&& object
.get(scope, v8str(scope, "scriptURL").into())
.is_some_and(|value| value.is_string()))
}
fn extendable_message_event_ports<'s>(
scope: &mut v8::PinScope<'s, '_>,
init: Option<v8::Local<'s, v8::Object>>,
) -> Option<v8::Local<'s, v8::Array>> {
let Some(init) = init else {
return Some(frozen_empty_worker_array(scope));
};
let value = match webidl::property_result(
scope,
init,
"ports",
webidl::Context::member("ExtendableMessageEvent", "ports"),
) {
Ok(Some(value)) if !value.is_undefined() => value,
Ok(_) => return Some(frozen_empty_worker_array(scope)),
Err(error) => {
webidl::throw_error(scope, &error);
return None;
}
};
let Ok(source) = v8::Local::<v8::Array>::try_from(value) else {
throw_type_error(
scope,
"Failed to construct 'ExtendableMessageEvent': member ports is not a sequence<MessagePort>.",
);
return None;
};
let ports = v8::Array::new(scope, source.length() as i32);
for index in 0..source.length() {
let port = source.get_index(scope, index)?;
let Ok(port_object) = v8::Local::<v8::Object>::try_from(port) else {
throw_type_error(
scope,
"Failed to construct 'ExtendableMessageEvent': member ports contains a non-MessagePort value.",
);
return None;
};
if crate::context_bootstrap::message_port_id_from_object(scope, port_object).is_none() {
throw_type_error(
scope,
"Failed to construct 'ExtendableMessageEvent': member ports contains a non-MessagePort value.",
);
return None;
}
let _ = ports.set_index(scope, index, port);
}
let _ = ports.set_integrity_level(scope, v8::IntegrityLevel::Frozen);
Some(ports)
}
fn frozen_empty_worker_array<'s>(scope: &mut v8::PinScope<'s, '_>) -> v8::Local<'s, v8::Array> {
let array = v8::Array::new(scope, 0);
let _ = array.set_integrity_level(scope, v8::IntegrityLevel::Frozen);
array
}
fn extendable_event_wait_until_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
_args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
let exception = worker_dom_exception_value(
scope,
"ExtendableEvent.waitUntil() was called outside an active event dispatch.",
"InvalidStateError",
);
scope.throw_exception(exception);
}
@@ -7,7 +7,7 @@ pub(super) struct WorkerImportScriptSource {
resource: Option<crate::worker::WorkerScriptResource>,
}
pub(super) fn resolve_import_script_url(
fn resolve_import_script_url(
state: Rc<RefCell<WorkerGlobalState>>,
input: &str,
) -> Result<Url, WorkerImportScriptError> {
@@ -36,7 +36,7 @@ pub(super) fn resolve_import_script_url(
Ok(url)
}
pub(super) fn materialize_worker_import_source(
fn materialize_worker_import_source(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
script_url: &Url,
@@ -244,7 +244,7 @@ fn report_service_worker_imported_script_loaded(
});
}
pub(super) fn evaluate_worker_script(
fn evaluate_worker_script(
scope: &mut v8::PinScope<'_, '_>,
state: Rc<RefCell<WorkerGlobalState>>,
script_url: &Url,
@@ -317,3 +317,209 @@ pub(super) fn evaluate_worker_script(
}
// ─── console ────────────────────────────────────────────────────────────────
pub(in crate::worker) const WORKER_EXCEPTION_SOURCE_SLOT: &str = "__moliWorkerExceptionSource";
pub(in crate::worker) const WORKER_EXCEPTION_LINE_SLOT: &str = "__moliWorkerExceptionLine";
pub(in crate::worker) const WORKER_EXCEPTION_COLUMN_SLOT: &str = "__moliWorkerExceptionColumn";
enum WorkerImportScriptError {
DomException { name: &'static str, message: String },
Exception(v8::Global<v8::Value>),
}
impl WorkerImportScriptError {
fn syntax(message: impl Into<String>) -> Self {
Self::DomException {
name: "SyntaxError",
message: message.into(),
}
}
fn network(message: impl Into<String>) -> Self {
Self::DomException {
name: "NetworkError",
message: message.into(),
}
}
fn error<'s>(scope: &mut v8::PinScope<'s, '_>, message: impl Into<String>) -> Self {
let message = message.into();
let value = v8_string(scope, &message)
.map(|value| v8::Exception::error(scope, value))
.unwrap_or_else(|| v8::Exception::error(scope, v8::String::empty(scope)));
Self::Exception(v8::Global::new(scope, value))
}
fn throw(self, scope: &mut v8::PinScope<'_, '_>) {
match self {
Self::DomException { name, message } => {
let exception = worker_dom_exception_value(scope, &message, name);
scope.throw_exception(exception);
}
Self::Exception(value) => {
let value = v8::Local::new(scope, value);
scope.throw_exception(value);
}
}
}
}
struct PreparedWorkerImportScript {
final_url: Url,
source: Option<String>,
muted_errors: bool,
}
fn annotate_worker_exception_location<'s>(
scope: &mut v8::PinScope<'s, '_>,
exception: v8::Local<'s, v8::Value>,
message: Option<v8::Local<'s, v8::Message>>,
) {
let Ok(object) = v8::Local::<v8::Object>::try_from(exception) else {
return;
};
let source = message
.and_then(|message| message.get_script_resource_name(scope))
.and_then(|value| value.to_string(scope))
.map(|value| value.to_rust_string_lossy(scope))
.filter(|value| !value.is_empty());
if let Some(source) = source
&& let Some(value) = v8_string(scope, &source)
{
set_worker_exception_location_if_missing(
scope,
object,
WORKER_EXCEPTION_SOURCE_SLOT,
value.into(),
);
}
if let Some(line) = message.and_then(|message| message.get_line_number(scope)) {
let value = v8::Number::new(scope, line as f64);
set_worker_exception_location_if_missing(
scope,
object,
WORKER_EXCEPTION_LINE_SLOT,
value.into(),
);
}
if let Some(column) = message.and_then(|message| message.get_start_column().checked_add(1)) {
let value = v8::Number::new(scope, column as f64);
set_worker_exception_location_if_missing(
scope,
object,
WORKER_EXCEPTION_COLUMN_SLOT,
value.into(),
);
}
}
fn set_worker_exception_location_if_missing<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
slot: &str,
value: v8::Local<'s, v8::Value>,
) {
if get_private_value(scope, object, slot).is_none() {
set_private_value(scope, object, slot, value);
}
}
pub(super) fn worker_import_scripts_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(state) = get_worker_state(scope) else {
return;
};
if state.borrow().script_kind == crate::worker::thread::WorkerScriptKind::Module {
throw_type_error(scope, "Module scripts don't support importScripts().");
return;
}
let require_trusted_types_for_script =
crate::content_security_policy::content_security_policy_requires_trusted_types_for_script(
&state.borrow().content_security_policies,
);
let mut prepared = Vec::with_capacity(args.length() as usize);
for i in 0..args.length() {
let Some(specifier) = crate::context_bootstrap::trusted_script_url_string_or_throw(
scope,
args.get(i),
crate::content_security_policy::TrustedTypesForScriptRequirements::enforced_only(
require_trusted_types_for_script,
),
"WorkerGlobalScope importScripts",
"importScripts",
) else {
return;
};
let resolved_url = match resolve_import_script_url(state.clone(), &specifier) {
Ok(url) => url,
Err(error) => {
error.throw(scope);
return;
}
};
let source = if matches!(resolved_url.scheme(), "data" | "blob") {
match materialize_worker_import_source(scope, &state, &resolved_url) {
Ok(import_source) => Some(import_source.source),
Err(error) => {
error.throw(scope);
return;
}
}
} else {
None
};
prepared.push(PreparedWorkerImportScript {
final_url: resolved_url,
source,
muted_errors: false,
});
}
for mut script in prepared {
if script.source.is_none() {
let import_source =
match materialize_worker_import_source(scope, &state, &script.final_url) {
Ok(result) => result,
Err(error) => {
error.throw(scope);
return;
}
};
script.final_url = import_source.final_url;
script.source = Some(import_source.source);
script.muted_errors = import_source.muted_errors;
}
let source = script.source.as_deref().unwrap_or_default();
if let Err(error) = evaluate_worker_script(
scope,
state.clone(),
&script.final_url,
source,
script.muted_errors,
) {
error.throw(scope);
return;
}
}
}
fn create_script_origin<'s>(scope: &mut v8::PinScope<'s, '_>, url: &str) -> v8::ScriptOrigin<'s> {
let name = v8::String::new(scope, url).expect("worker script origin");
v8::ScriptOrigin::new(
scope,
name.into(),
0,
0,
false,
-1,
None,
false,
false,
false,
None,
)
}
@@ -0,0 +1,236 @@
//! Worker postMessage, structured cloning, MessagePorts and BroadcastChannels.
use super::*;
#[derive(Default, WebApiObject)]
#[webapi(plain)]
pub(super) struct DedicatedWorkerGlobalPostMessageDeclaration {
#[webapi(method = "postMessage", callback = worker_post_message_callback, length = 1)]
post_message: (),
}
/// Mutable state accessible from V8 callbacks inside the worker isolate.
pub(in crate::worker) struct WorkerMessagePortWrapperEntry {
wrapper: v8::Global<v8::Object>,
}
pub(crate) fn worker_message_port_wake_sender(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<mpsc::UnboundedSender<crate::worker::handle::WorkerMessage>> {
Some(get_worker_state(scope)?.borrow().worker_wake_tx.clone())
}
pub(crate) fn worker_message_port_registry(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<SharedMessagePortRegistry> {
Some(
get_worker_state(scope)?
.borrow()
.message_port_registry
.clone(),
)
}
pub(crate) fn worker_broadcast_channel_wake_sender(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<mpsc::UnboundedSender<crate::worker::handle::WorkerMessage>> {
Some(get_worker_state(scope)?.borrow().worker_wake_tx.clone())
}
pub(crate) fn worker_broadcast_channel_registry(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<SharedBroadcastChannelRegistry> {
Some(
get_worker_state(scope)?
.borrow()
.broadcast_channel_registry
.clone(),
)
}
pub(crate) fn worker_broadcast_channel_storage_key(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<MoliStorageKey> {
Some(
get_worker_state(scope)?
.borrow()
.broadcast_channel_storage_key
.clone(),
)
}
pub(crate) fn register_worker_message_port_wrapper(
scope: &mut v8::PinScope<'_, '_>,
port_id: MessagePortId,
port: v8::Local<'_, v8::Object>,
) {
let Some(state) = get_worker_state(scope) else {
return;
};
state.borrow_mut().message_port_wrappers.insert(
port_id,
WorkerMessagePortWrapperEntry {
wrapper: v8::Global::new(scope, port),
},
);
}
pub(crate) fn register_shared_worker_connection_port(
scope: &mut v8::PinScope<'_, '_>,
port_id: MessagePortId,
) {
let Some(state) = get_worker_state(scope) else {
return;
};
state
.borrow_mut()
.shared_worker_connection_ports
.insert(port_id);
}
pub(crate) fn forget_worker_message_port_wrapper(
scope: &mut v8::PinScope<'_, '_>,
port_id: MessagePortId,
) {
if let Some(state) = get_worker_state(scope) {
state.borrow_mut().message_port_wrappers.remove(&port_id);
}
}
pub(crate) fn worker_message_port_wrapper<'s>(
scope: &mut v8::PinScope<'s, '_>,
port_id: MessagePortId,
) -> Option<v8::Local<'s, v8::Object>> {
let state = get_worker_state(scope)?;
let state = state.borrow();
state
.message_port_wrappers
.get(&port_id)
.map(|entry| v8::Local::new(scope, &entry.wrapper))
}
pub(crate) fn register_worker_broadcast_channel_wrapper(
scope: &mut v8::PinScope<'_, '_>,
channel_id: BroadcastChannelId,
channel: v8::Local<'_, v8::Object>,
) {
let Some(state) = get_worker_state(scope) else {
return;
};
state
.borrow_mut()
.broadcast_channel_wrappers
.insert(channel_id, v8::Global::new(scope, channel));
}
pub(crate) fn forget_worker_broadcast_channel_wrapper(
scope: &mut v8::PinScope<'_, '_>,
channel_id: BroadcastChannelId,
) {
let Some(state) = get_worker_state(scope) else {
return;
};
state
.borrow_mut()
.broadcast_channel_wrappers
.remove(&channel_id);
}
pub(crate) fn worker_broadcast_channel_wrapper<'s>(
scope: &mut v8::PinScope<'s, '_>,
channel_id: BroadcastChannelId,
) -> Option<v8::Local<'s, v8::Object>> {
let state = get_worker_state(scope)?;
let state = state.borrow();
state
.broadcast_channel_wrappers
.get(&channel_id)
.map(|handle| v8::Local::new(scope, handle))
}
pub(in crate::worker) fn close_worker_owned_message_ports(state: &Rc<RefCell<WorkerGlobalState>>) {
let (registry, port_ids): (SharedMessagePortRegistry, Vec<MessagePortId>) = {
let mut state = state.borrow_mut();
let shared_worker_connection_ports =
std::mem::take(&mut state.shared_worker_connection_ports);
(
state.message_port_registry.clone(),
state
.message_port_wrappers
.drain()
.filter_map(|(port_id, _)| {
(!shared_worker_connection_ports.contains(&port_id)).then_some(port_id)
})
.collect(),
)
};
for port_id in port_ids {
registry.close_message_port(port_id);
}
}
pub(in crate::worker) fn close_worker_owned_broadcast_channels(
state: &Rc<RefCell<WorkerGlobalState>>,
) {
let (registry, channel_ids): (SharedBroadcastChannelRegistry, Vec<BroadcastChannelId>) = {
let mut state = state.borrow_mut();
(
state.broadcast_channel_registry.clone(),
state
.broadcast_channel_wrappers
.drain()
.map(|(channel_id, _)| channel_id)
.collect(),
)
};
for channel_id in channel_ids {
registry.close_broadcast_channel(channel_id);
}
}
fn worker_post_message_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(state) = get_worker_state(scope) else {
return;
};
if args.length() == 0 {
throw_type_error(
scope,
"Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope': 1 argument required, but only 0 present.",
);
return;
}
let val = args.get(0);
let transfer_arg = (args.length() > 1).then(|| args.get(1));
let Some(data) = crate::context_bootstrap::structured_serialize_value_for_post_message(
scope,
val,
transfer_arg,
"DedicatedWorkerGlobalScope",
) else {
return;
};
let _ = state
.borrow()
.parent_tx
.send(WorkerToParentMessage::Post(data));
}
pub(super) fn worker_structured_clone_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if let Some(value) = crate::context_bootstrap::structured_clone_value_with_options(
scope,
args.get(0),
args.get(1),
) {
rv.set(value);
} else {
rv.set_undefined();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,153 @@
//! Nested worker ownership, creation snapshots and parent event delivery.
use super::*;
pub(crate) struct NestedWorkerContext {
pub(crate) worker_id: DedicatedWorkerId,
pub(crate) base_url: Url,
pub(crate) loader: crate::network::context::WorkerResourceLoader,
pub(crate) worker_context_runtime: crate::runtime::RendererWorkerContextRuntime,
pub(crate) service_worker_runtime:
Option<crate::service_worker_runtime::ServiceWorkerRuntimeService>,
pub(crate) service_worker_client_id:
Option<crate::service_worker_runtime::ServiceWorkerClientId>,
pub(crate) storage_key_top_level_site: String,
pub(crate) creator_storage_key: MoliStorageKey,
pub(crate) indexed_db_manager: Option<crate::context_bootstrap::WeakIndexedDbManager>,
pub(crate) storage_bucket_store: Option<crate::context_bootstrap::SharedStorageBucketStore>,
pub(crate) module_static_import_content_security_policies: Vec<String>,
pub(crate) require_trusted_types_for_script: bool,
pub(crate) network_policy: crate::worker::handle::WorkerNetworkPolicy,
pub(crate) policy_context: crate::types::SubresourcePolicyContext,
pub(crate) wake_tx: mpsc::UnboundedSender<crate::worker::handle::WorkerMessage>,
}
pub(crate) fn reserve_nested_worker_context(
scope: &mut v8::PinScope<'_, '_>,
worker: v8::Local<'_, v8::Object>,
) -> Option<NestedWorkerContext> {
let state = get_worker_state(scope)?;
let mut state = state.borrow_mut();
let base_url = state.current_script_url.clone()?;
let worker_id = DedicatedWorkerId::new(state.next_nested_worker_id);
state.next_nested_worker_id = state
.next_nested_worker_id
.checked_add(1)
.expect("nested worker id space exhausted");
state
.nested_worker_wrappers
.insert(worker_id, v8::Global::new(scope, worker));
Some(NestedWorkerContext {
worker_id,
base_url,
loader: state.loader.clone(),
worker_context_runtime: state.worker_context_runtime.clone(),
service_worker_runtime: state.service_worker_runtime.clone(),
service_worker_client_id: state.service_worker_client_id,
storage_key_top_level_site: state.storage_key.top_level_site().to_owned(),
creator_storage_key: state.storage_key.clone(),
indexed_db_manager: state.indexed_db_manager.clone(),
storage_bucket_store: state.storage_bucket_store.clone(),
module_static_import_content_security_policies: state.content_security_policies.clone(),
require_trusted_types_for_script:
crate::content_security_policy::content_security_policy_requires_trusted_types_for_script(
&state.content_security_policies,
),
network_policy: crate::worker::handle::WorkerNetworkPolicy {
secure_context: state.secure_context,
permission_overrides: state.permission_overrides.clone(),
extra_http_headers: state.extra_http_headers.clone(),
network_offline: state.network_offline,
blocked_url_patterns: state.blocked_url_patterns.clone(),
network_partition_key: state.network_partition_key.clone(),
fetch_subresource_interception_enabled: state.fetch_subresource_interception_enabled,
fetch_subresource_interception_resource_type: state
.fetch_subresource_interception_resource_type,
},
policy_context: state.policy_context,
wake_tx: state.worker_wake_tx.clone(),
})
}
pub(in crate::worker) struct NestedWorkerUnhandledError {
pub(in crate::worker) message: String,
pub(in crate::worker) filename: String,
pub(in crate::worker) lineno: u32,
pub(in crate::worker) colno: u32,
pub(in crate::worker) event_kind: crate::worker::handle::WorkerParentErrorEventKind,
}
pub(in crate::worker) struct NestedWorkerDispatchResult {
pub(in crate::worker) dispatched: bool,
pub(in crate::worker) unhandled_error: Option<NestedWorkerUnhandledError>,
}
pub(crate) fn forget_nested_worker_context(
scope: &mut v8::PinScope<'_, '_>,
worker_id: DedicatedWorkerId,
) -> bool {
let Some(state) = get_worker_state(scope) else {
return false;
};
let mut state = state.borrow_mut();
state.nested_worker_wrappers.remove(&worker_id).is_some()
}
pub(in crate::worker) fn dispatch_nested_worker_event(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
worker_id: DedicatedWorkerId,
message: &crate::worker::handle::WorkerToParentMessage,
) -> NestedWorkerDispatchResult {
let Some(worker) = state
.borrow()
.nested_worker_wrappers
.get(&worker_id)
.map(|worker| v8::Local::new(scope, worker))
else {
return NestedWorkerDispatchResult {
dispatched: false,
unhandled_error: None,
};
};
match message {
crate::worker::handle::WorkerToParentMessage::Post(_) => {
let has_message_delivery_listener =
crate::context_bootstrap::worker_has_message_delivery_listener(scope, worker);
let _ = crate::context_bootstrap::dispatch_worker_event(scope, worker, message);
NestedWorkerDispatchResult {
dispatched: has_message_delivery_listener,
unhandled_error: None,
}
}
crate::worker::handle::WorkerToParentMessage::Error {
message: error_message,
filename,
lineno,
colno,
event_kind,
phase,
..
} => {
let unhandled = crate::context_bootstrap::dispatch_worker_event(scope, worker, message);
// Bootstrap failures only fire an Event at the child Worker. Only
// uncanceled runtime errors propagate to its owner's global scope.
let propagate = unhandled && *phase == crate::worker::handle::WorkerErrorPhase::Runtime;
NestedWorkerDispatchResult {
dispatched: true,
unhandled_error: propagate.then(|| NestedWorkerUnhandledError {
message: error_message.clone(),
filename: filename.clone(),
lineno: *lineno,
colno: *colno,
event_kind: *event_kind,
}),
}
}
_ => NestedWorkerDispatchResult {
dispatched: crate::context_bootstrap::dispatch_worker_event(scope, worker, message),
unhandled_error: None,
},
}
}
@@ -0,0 +1,265 @@
//! Pending worker network requests, response snapshots and shared request helpers.
use super::*;
pub(in crate::worker) struct PendingWorkerFetch {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
pub(in crate::worker) document_url: Url,
pub(in crate::worker) credentials_mode: RequestCredentialsMode,
pub(in crate::worker) request_mode: moli_fetch::RequestMode,
pub(in crate::worker) redirect_mode: RequestRedirectMode,
pub(in crate::worker) request_priority: Option<moli_fetch::FetchPriorityHint>,
pub(in crate::worker) request_metadata:
crate::service_worker_runtime::ServiceWorkerFetchRequestMetadata,
pub(in crate::worker) policy_context: crate::types::SubresourcePolicyContext,
pub(in crate::worker) signal_id: Option<u32>,
pub(in crate::worker) load: ResourceLoadLease,
pub(in crate::worker) request_url: Url,
pub(in crate::worker) request_method: String,
pub(in crate::worker) request_headers: moli_fetch::RequestHeaders,
pub(in crate::worker) request_body: Option<String>,
pub(in crate::worker) network_request_handle: Option<SubresourceNetworkRequestHandle>,
pub(in crate::worker) network_record: Option<PendingWorkerFetchNetworkRecord>,
pub(in crate::worker) paused_response: Option<PausedWorkerSubresourceResponse>,
pub(in crate::worker) streaming_body_source_id: Option<NetworkBodySourceId>,
}
pub(in crate::worker) enum WorkerFetchEvent {
Completion(Box<WorkerFetchCompletion>),
StreamingStarted(WorkerFetchStreamingStarted),
StreamingChunk(WorkerFetchStreamingChunk),
StreamingFinished(WorkerFetchStreamingFinished),
}
pub(in crate::worker) struct WorkerFetchCompletion {
pub(super) fetch_id: u32,
pub(super) network_request_headers: Option<Vec<(String, String)>>,
pub(super) result: Result<WorkerFetchResponse, String>,
}
pub(in crate::worker) struct WorkerFetchStreamingStarted {
pub(super) fetch_id: u32,
pub(super) body_source_id: NetworkBodySourceId,
pub(super) head: ResponseHead,
pub(super) network_request_headers: Option<Vec<(String, String)>>,
}
pub(in crate::worker) struct WorkerFetchStreamingChunk {
pub(super) body_source_id: NetworkBodySourceId,
pub(super) bytes: Vec<u8>,
}
pub(in crate::worker) struct WorkerFetchStreamingFinished {
pub(super) fetch_id: u32,
pub(super) body_source_id: NetworkBodySourceId,
pub(super) head: ResponseHead,
pub(super) result: Result<SubresourceResponseBody, String>,
}
pub(in crate::worker) enum WorkerFetchResponse {
Materialized(Box<Response>),
Streamed {
head: Box<ResponseHead>,
body: SubresourceResponseBody,
},
}
impl WorkerFetchResponse {
pub(super) fn head(&self) -> ResponseHead {
match self {
Self::Materialized(response) => response.head(),
Self::Streamed { head, .. } => head.as_ref().clone(),
}
}
pub(super) fn subresource_response_body(&self) -> SubresourceResponseBody {
match self {
Self::Materialized(response) => SubresourceResponseBody::from_fetch_response(response),
Self::Streamed { body, .. } => body.clone(),
}
}
pub(super) fn into_fetch_parts(self) -> WorkerFetchResponseParts {
match self {
Self::Materialized(response) => {
let (head, body) = response.into_body();
WorkerFetchResponseParts::Materialized {
head,
body: Box::new(body),
}
}
Self::Streamed { head, body } => {
WorkerFetchResponseParts::Subresource { head: *head, body }
}
}
}
}
pub(super) enum WorkerFetchResponseParts {
Materialized {
head: ResponseHead,
body: Box<ResponseBody>,
},
Subresource {
head: ResponseHead,
body: SubresourceResponseBody,
},
}
#[derive(Clone)]
pub(in crate::worker) struct PendingWorkerFetchNetworkRecord {
pub(crate) redirect_headers: Option<moli_fetch::RequestHeaders>,
pub(in crate::worker) internal_id: u64,
pub(in crate::worker) url: Url,
pub(in crate::worker) method: String,
pub(in crate::worker) request_headers: moli_fetch::RequestHeaders,
pub(in crate::worker) request_body: Option<String>,
pub(in crate::worker) initial_network_request_headers: Option<Vec<(String, String)>>,
pub(in crate::worker) intercept_response: bool,
pub(in crate::worker) handle_auth_requests: bool,
}
impl PendingWorkerFetchNetworkRecord {
pub(super) fn follow_redirects(&mut self, head: &ResponseHead) {
if head.redirect_chain.is_empty() {
return;
}
let mut request = Request::get_with_url(self.url.clone())
.with_redirect_headers(self.redirect_headers.take());
request.method = std::mem::take(&mut self.method);
request.body = self.request_body.clone().map(String::into_bytes);
request.request_headers = std::mem::take(&mut self.request_headers);
for redirect in &head.redirect_chain {
request.apply_redirect_status(redirect.status);
}
self.url = head.final_url.clone();
self.method = request.method;
self.request_headers = request.request_headers;
if request.body.is_none() {
self.request_body = None;
}
}
}
pub(in crate::worker) struct PendingWorkerXhr {
pub(in crate::worker) xhr: v8::Global<v8::Object>,
pub(in crate::worker) document_url: Url,
pub(in crate::worker) credentials_mode: RequestCredentialsMode,
pub(in crate::worker) load: ResourceLoadLease,
pub(in crate::worker) request_paused: bool,
pub(in crate::worker) request_url: Url,
pub(in crate::worker) request_method: String,
pub(in crate::worker) request_headers: moli_fetch::RequestHeaders,
pub(in crate::worker) request_body: Option<String>,
pub(in crate::worker) network_request_handle: Option<SubresourceNetworkRequestHandle>,
pub(in crate::worker) network_record: Option<PendingWorkerFetchNetworkRecord>,
pub(in crate::worker) paused_response: Option<PausedWorkerSubresourceResponse>,
}
pub(in crate::worker) struct PendingWorkerCspReport {
pub(in crate::worker) load: ResourceLoadLease,
pub(in crate::worker) document_url: Url,
pub(in crate::worker) request: Request,
pub(in crate::worker) request_body: Option<String>,
pub(in crate::worker) policy_context: crate::types::SubresourcePolicyContext,
pub(in crate::worker) service_worker_runtime:
Option<crate::service_worker_runtime::ServiceWorkerRuntimeService>,
pub(in crate::worker) service_worker_client_id:
Option<crate::service_worker_runtime::ServiceWorkerClientId>,
}
pub(in crate::worker) struct PausedWorkerSubresourceResponse {
pub(in crate::worker) head: ResponseHead,
pub(in crate::worker) body: SubresourceResponseBody,
}
pub(in crate::worker) struct WorkerXhrCompletion {
pub(in crate::worker) xhr_id: u32,
pub(in crate::worker) network_request_headers: Option<Vec<(String, String)>>,
pub(in crate::worker) result: Result<WorkerXhrResponse, String>,
}
pub(in crate::worker) enum WorkerXhrResponse {
Materialized(Box<Response>),
Streamed {
head: Box<ResponseHead>,
body: SubresourceResponseBody,
},
}
impl WorkerXhrResponse {
pub(super) fn head(&self) -> ResponseHead {
match self {
Self::Materialized(response) => response.head(),
Self::Streamed { head, .. } => head.as_ref().clone(),
}
}
pub(super) fn subresource_response_body(&self) -> SubresourceResponseBody {
match self {
Self::Materialized(response) => SubresourceResponseBody::from_fetch_response(response),
Self::Streamed { body, .. } => body.clone(),
}
}
pub(super) fn into_body_source(self) -> Result<(ResponseHead, ResponseBody), String> {
match self {
Self::Materialized(response) => Ok(response.into_body()),
Self::Streamed { head, body } => body
.materialize_bytes()
.map(|bytes| (*head, ResponseBody::materialized_bytes(bytes)))
.map_err(|error| format!("failed to materialize worker XHR body: {error}")),
}
}
}
pub(in crate::worker) struct WorkerWebSocketState {
pub(in crate::worker) wrapper: v8::Global<v8::Object>,
pub(in crate::worker) connection: WebSocketConnectionHandle,
pub(in crate::worker) document_url: Url,
pub(in crate::worker) url: Url,
pub(in crate::worker) loader: crate::network::context::WorkerResourceLoader,
pub(in crate::worker) load: Option<ResourceLoadLease>,
pub(in crate::worker) opened: bool,
pub(in crate::worker) network_recorded: bool,
}
pub(super) fn next_fetch_id(state: &mut WorkerGlobalState) -> u32 {
state.next_fetch_id = state
.next_fetch_id
.checked_add(1)
.expect("worker fetch id space exhausted");
state.next_fetch_id
}
pub(super) fn next_xhr_id(state: &mut WorkerGlobalState) -> u32 {
state.next_xhr_id = state
.next_xhr_id
.checked_add(1)
.expect("worker XHR id space exhausted");
state.next_xhr_id
}
pub(super) fn next_websocket_id(state: &mut WorkerGlobalState) -> u64 {
state.next_websocket_id = state
.next_websocket_id
.checked_add(1)
.expect("worker WebSocket id space exhausted");
state.next_websocket_id
}
pub(super) fn worker_url_blocked(patterns: &[String], url: &Url) -> bool {
let value = url.as_str();
patterns
.iter()
.any(|pattern| moli_fetch::url_pattern_matches(pattern, value))
}
pub(super) fn merge_worker_request_headers(
context_headers: &moli_fetch::RequestHeaders,
request_headers: &moli_fetch::RequestHeaders,
) -> moli_fetch::RequestHeaders {
let mut headers = context_headers.clone();
headers.overlay(request_headers.clone());
headers
}
@@ -0,0 +1,73 @@
//! Captured worker origin and the replaceable public origin property.
use super::*;
const WORKER_GLOBAL_ORIGIN_SLOT: &str = "__moliWorkerGlobalOrigin";
#[derive(WebApiObject)]
#[webapi(plain)]
pub(super) struct WorkerGlobalOriginDeclaration {
#[webapi(slot = WORKER_GLOBAL_ORIGIN_SLOT)]
origin: String,
}
#[derive(Default, WebApiObject)]
#[webapi(fragment, prototype = "WorkerGlobalScope", enumerable)]
pub(super) struct WorkerGlobalOriginPrototypeDeclaration {
#[webapi(
accessor_property,
getter = worker_global_origin_getter,
setter = worker_global_origin_setter
)]
origin: (),
}
fn worker_global_origin_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s, v8::Value>,
) {
let global = scope.get_current_context().global(scope);
if !args.this().strict_equals(global.into()) {
throw_type_error(scope, "Illegal invocation");
return;
}
let origin = get_private_value(scope, global, WORKER_GLOBAL_ORIGIN_SLOT)
.unwrap_or_else(|| v8str(scope, "null").into());
rv.set(origin);
}
fn worker_global_origin_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'s, v8::Value>,
) {
let global = scope.get_current_context().global(scope);
if !args.this().strict_equals(global.into()) {
throw_type_error(scope, "Illegal invocation");
return;
}
// [Replaceable] creates an own data property without converting the value
// or changing the origin returned by the original getter.
match global.define_own_property(
scope,
v8str(scope, "origin").into(),
args.get(0),
v8::PropertyAttribute::NONE,
) {
Some(true) => {}
Some(false) => throw_type_error(scope, "Cannot redefine WorkerGlobalScope.origin"),
None => {}
}
}
pub(crate) fn worker_global_origin(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<moli_url::WebOrigin> {
let global = scope.get_current_context().global(scope);
let origin = get_private_value(scope, global, WORKER_GLOBAL_ORIGIN_SLOT)?;
let origin = v8::Local::<v8::String>::try_from(origin).ok()?;
Some(moli_url::WebOrigin::from_serialized(
&origin.to_rust_string_lossy(scope),
))
}
@@ -0,0 +1,62 @@
//! Worker performance clock and time origin.
use super::*;
#[derive(WebApiObject)]
#[webapi(plain)]
struct WorkerGlobalPerformanceDeclaration<'scope> {
#[webapi(data_property)]
performance: v8::Local<'scope, v8::Object>,
}
#[derive(WebApiObject)]
#[webapi(prototype = "Object", interface = web_api_interfaces::Performance)]
struct WorkerPerformanceObjectDeclaration {
#[webapi(data_property, readonly)]
time_origin: f64,
#[webapi(method, callback = worker_performance_now_callback, data = self.time_origin)]
now: (),
}
pub(super) fn install_worker_performance<'s>(
scope: &mut v8::PinScope<'s, '_>,
global: v8::Local<'s, v8::Object>,
) -> Result<()> {
let time_origin = monotonic_unix_epoch_millis();
let performance = WorkerPerformanceObjectDeclaration::new(time_origin)
.bind(scope)
.map_err(|error| anyhow!("failed to create worker performance: {error}"))?;
WorkerGlobalPerformanceDeclaration::new(performance)
.initialize(scope, global)
.map_err(|error| anyhow!("failed to initialize worker performance: {error}"))
}
fn worker_performance_now_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let time_origin = args.data().number_value(scope).unwrap_or(0.0);
rv.set(
v8::Number::new(
scope,
moli_time::coarsened_dom_time_millis(
(monotonic_unix_epoch_millis() - time_origin).max(0.0),
),
)
.into(),
);
}
fn unix_epoch_millis() -> f64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_secs_f64() * 1000.0)
.unwrap_or(0.0)
}
pub(super) fn monotonic_unix_epoch_millis() -> f64 {
static BASE: OnceLock<(f64, Instant)> = OnceLock::new();
let (epoch_millis, instant) = BASE.get_or_init(|| (unix_epoch_millis(), Instant::now()));
epoch_millis + instant.elapsed().as_secs_f64() * 1000.0
}
@@ -1827,3 +1827,319 @@ pub(super) fn service_worker_window_client_navigate_callback<'s>(
fn service_worker_window_client_can_display_url(url: &Url) -> bool {
matches!(url.scheme(), "http" | "https")
}
const SERVICE_WORKER_REGISTRATION_SCOPE_SLOT: &str = "__moliServiceWorkerRegistrationScope";
const SERVICE_WORKER_REGISTRATION_ID_SLOT: &str = "__moliServiceWorkerRegistrationId";
pub(super) const SERVICE_WORKER_VERSION_ID_SLOT: &str = "__moliServiceWorkerVersionId";
const SERVICE_WORKER_WORKER_EVENTS_SLOT: &str = "__moliServiceWorkerWorkerEvents";
const SERVICE_WORKER_NAVIGATION_PRELOAD_MANAGER_SCOPE_SLOT: &str =
"__moliServiceWorkerNavigationPreloadManagerScope";
pub(super) const SERVICE_WORKER_CLIENT_ID_SLOT: &str = "__lmServiceWorkerClientId";
#[derive(Default, WebApiObject)]
#[webapi(interface = web_api_interfaces::Clients, prototype = "Object")]
struct ServiceWorkerClientsDeclaration {
#[webapi(method, callback = service_worker_clients_claim_callback, length = 0)]
claim: (),
#[webapi(method, callback = service_worker_clients_get_callback, length = 1)]
get: (),
#[webapi(
method = "matchAll",
callback = service_worker_clients_match_all_callback,
length = 0
)]
match_all: (),
#[webapi(
method = "openWindow",
callback = service_worker_clients_open_window_callback,
length = 1
)]
open_window: (),
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct ServiceWorkerGlobalRuntimeDeclaration<'scope> {
#[webapi(data_property, readonly)]
registration: v8::Local<'scope, v8::Object>,
#[webapi(data_property, readonly)]
clients: v8::Local<'scope, v8::Object>,
#[webapi(method = "skipWaiting", callback = service_worker_skip_waiting_callback, length = 0)]
skip_waiting: (),
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::ServiceWorkerRegistration,)]
struct ServiceWorkerGlobalRegistrationDeclaration<'scope> {
#[webapi(data_property, readonly)]
scope: String,
#[webapi(
accessor_property = "installing",
getter = service_worker_registration_installing_getter
)]
installing: (),
#[webapi(
accessor_property = "waiting",
getter = service_worker_registration_waiting_getter
)]
waiting: (),
#[webapi(
accessor_property = "active",
getter = service_worker_registration_active_getter
)]
active: (),
#[webapi(method, callback = service_worker_registration_unregister_callback, length = 0)]
unregister: (),
#[webapi(
method = "showNotification",
callback = service_worker_registration_show_notification_callback,
length = 1
)]
show_notification: (),
#[webapi(
method = "getNotifications",
callback = service_worker_registration_get_notifications_callback,
length = 0
)]
get_notifications: (),
#[webapi(data_property, readonly)]
sync: v8::Local<'scope, v8::Object>,
#[webapi(data_property = "periodicSync", readonly)]
periodic_sync: v8::Local<'scope, v8::Object>,
#[webapi(data_property = "pushManager", readonly)]
push_manager: v8::Local<'scope, v8::Object>,
#[webapi(data_property = "navigationPreload", readonly)]
navigation_preload: v8::Local<'scope, v8::Object>,
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::ServiceWorker,)]
struct ServiceWorkerGlobalServiceWorkerDeclaration {
#[webapi(data_property = "scriptURL", readonly)]
script_url: String,
#[webapi(data_property, readonly)]
state: String,
#[webapi(method = "postMessage", callback = service_worker_worker_post_message_callback, length = 1)]
post_message: (),
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::SyncManager)]
struct ServiceWorkerGlobalSyncManagerDeclaration {
#[webapi(
method = "register",
callback = service_worker_sync_manager_register_callback,
length = 1
)]
register: (),
#[webapi(
method = "getTags",
callback = service_worker_sync_manager_get_tags_callback,
length = 0
)]
get_tags: (),
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::PeriodicSyncManager)]
struct ServiceWorkerGlobalPeriodicSyncManagerDeclaration {
#[webapi(
method = "register",
callback = service_worker_periodic_sync_manager_register_callback,
length = 1
)]
register: (),
#[webapi(
method = "getTags",
callback = service_worker_periodic_sync_manager_get_tags_callback,
length = 0
)]
get_tags: (),
#[webapi(
method = "unregister",
callback = service_worker_periodic_sync_manager_unregister_callback,
length = 1
)]
unregister: (),
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::PushManager)]
struct ServiceWorkerGlobalPushManagerDeclaration {
#[webapi(
method = "subscribe",
callback = service_worker_push_manager_subscribe_callback,
length = 0
)]
subscribe: (),
#[webapi(
method = "getSubscription",
callback = service_worker_push_manager_get_subscription_callback,
length = 0
)]
get_subscription: (),
#[webapi(
method = "permissionState",
callback = service_worker_push_manager_permission_state_callback,
length = 0
)]
permission_state: (),
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::NavigationPreloadManager)]
struct ServiceWorkerGlobalNavigationPreloadManagerDeclaration {
#[webapi(
method,
callback = service_worker_navigation_preload_manager_enable_callback,
length = 0
)]
enable: (),
#[webapi(
method,
callback = service_worker_navigation_preload_manager_disable_callback,
length = 0
)]
disable: (),
#[webapi(
method = "setHeaderValue",
callback = service_worker_navigation_preload_manager_set_header_value_callback,
length = 1
)]
set_header_value: (),
#[webapi(
method = "getState",
callback = service_worker_navigation_preload_manager_get_state_callback,
length = 0
)]
get_state: (),
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::PushSubscription)]
struct ServiceWorkerPushSubscriptionDeclaration<'scope> {
#[webapi(data_property, readonly)]
endpoint: String,
#[webapi(data_property = "expirationTime", readonly)]
expiration_time: v8::Local<'scope, v8::Value>,
#[webapi(data_property, readonly)]
options: v8::Local<'scope, v8::Object>,
#[webapi(method, callback = service_worker_push_subscription_unsubscribe_callback, length = 0)]
unsubscribe: (),
#[webapi(
method = "toJSON",
callback = service_worker_push_subscription_to_json_callback,
length = 0
)]
to_json: (),
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::PushSubscriptionOptions, prototype = "Object")]
struct ServiceWorkerPushSubscriptionOptionsDeclaration<'scope> {
#[webapi(data_property = "userVisibleOnly", readonly)]
user_visible_only: bool,
#[webapi(data_property = "applicationServerKey", readonly)]
application_server_key: v8::Local<'scope, v8::Value>,
}
#[derive(WebApiObject)]
#[webapi(plain)]
struct WorkerNavigationPreloadStateDeclaration {
#[webapi(data_property, enumerable)]
enabled: bool,
#[webapi(data_property = "headerValue", enumerable)]
header_value: String,
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::Client)]
pub(in crate::worker) struct ServiceWorkerBaseClientDeclaration<'scope> {
#[webapi(data_property, readonly)]
id: String,
#[webapi(data_property, readonly)]
url: v8::Local<'scope, v8::String>,
#[webapi(data_property = "type", readonly)]
client_type: &'static str,
#[webapi(method = "postMessage", callback = service_worker_client_post_message_callback, length = 1)]
post_message: (),
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::WindowClient)]
pub(in crate::worker) struct ServiceWorkerWindowClientDeclaration<'scope> {
#[webapi(data_property, readonly)]
id: String,
#[webapi(data_property, readonly)]
url: v8::Local<'scope, v8::String>,
#[webapi(data_property = "type", readonly)]
client_type: &'static str,
#[webapi(data_property = "frameType", readonly)]
frame_type: &'static str,
#[webapi(data_property = "lifecycleState", readonly)]
lifecycle_state: &'static str,
#[webapi(data_property = "visibilityState", readonly)]
visibility_state: &'static str,
#[webapi(data_property, readonly)]
focused: bool,
#[webapi(method = "postMessage", callback = service_worker_client_post_message_callback, length = 1)]
post_message: (),
#[webapi(method, callback = service_worker_window_client_focus_callback, length = 0)]
focus: (),
#[webapi(method, callback = service_worker_window_client_navigate_callback, length = 1)]
navigate: (),
}
#[derive(Default, webidl::WebIdlDictionary)]
#[webidl(prefix = "BackgroundSyncOptions")]
struct BackgroundSyncOptions {
#[webidl(
name = "minInterval",
converter = "enforce_range_unsigned_long_long",
default = 0
)]
min_interval: u64,
}
@@ -0,0 +1,908 @@
//! Service Worker API request ids, pending promises and result delivery.
use super::*;
pub(in crate::worker) struct PendingServiceWorkerClientQuery {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
pub(in crate::worker) query_type: PendingServiceWorkerClientQueryType,
}
pub(in crate::worker) struct PendingServiceWorkerClientNavigate {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerClientFocus {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerClientsOpenWindow {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerShowNotification {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerGetNotifications {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerSyncRegistration {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerSyncGetTags {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerPeriodicSyncRegistration {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerPeriodicSyncGetTags {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerPeriodicSyncUnregistration {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerPushSubscribe {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerPushGetSubscription {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
pub(in crate::worker) struct PendingServiceWorkerPushUnsubscribe {
pub(in crate::worker) resolver: v8::Global<v8::PromiseResolver>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::worker) enum PendingServiceWorkerClientQueryType {
Get,
MatchAll,
}
/// Checked allocator for one operation-local Service Worker request namespace.
///
/// The parent/worker protocol intentionally gives each operation its own
/// namespace. Keeping that shape avoids coupling unrelated APIs while making
/// exhaustion fail instead of overwriting a live pending resolver.
#[derive(Debug)]
pub(in crate::worker) struct WorkerServiceWorkerRequestIdAllocator {
next: u64,
}
impl Default for WorkerServiceWorkerRequestIdAllocator {
fn default() -> Self {
Self { next: 1 }
}
}
impl WorkerServiceWorkerRequestIdAllocator {
fn allocate(&mut self) -> u64 {
let request_id = self.next;
self.next = request_id
.checked_add(1)
.expect("worker Service Worker request id space exhausted");
request_id
}
}
impl WorkerGlobalState {
pub(in crate::worker) fn register_pending_service_worker_client_query(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
query_type: PendingServiceWorkerClientQueryType,
) -> u64 {
let request_id = self.service_worker_client_query_request_ids.allocate();
self.pending_service_worker_client_queries.insert(
request_id,
PendingServiceWorkerClientQuery {
resolver,
query_type,
},
);
request_id
}
pub(in crate::worker) fn take_pending_service_worker_client_query(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerClientQuery> {
self.pending_service_worker_client_queries
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_client_navigate(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self.service_worker_client_navigate_request_ids.allocate();
self.pending_service_worker_client_navigates
.insert(request_id, PendingServiceWorkerClientNavigate { resolver });
request_id
}
pub(in crate::worker) fn take_pending_service_worker_client_navigate(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerClientNavigate> {
self.pending_service_worker_client_navigates
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_client_focus(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self.service_worker_client_focus_request_ids.allocate();
self.pending_service_worker_client_focuses
.insert(request_id, PendingServiceWorkerClientFocus { resolver });
request_id
}
pub(in crate::worker) fn take_pending_service_worker_client_focus(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerClientFocus> {
self.pending_service_worker_client_focuses
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_clients_open_window(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self
.service_worker_clients_open_window_request_ids
.allocate();
self.pending_service_worker_clients_open_windows.insert(
request_id,
PendingServiceWorkerClientsOpenWindow { resolver },
);
request_id
}
pub(in crate::worker) fn take_pending_service_worker_clients_open_window(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerClientsOpenWindow> {
self.pending_service_worker_clients_open_windows
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_show_notification(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self.service_worker_show_notification_request_ids.allocate();
self.pending_service_worker_show_notifications.insert(
request_id,
PendingServiceWorkerShowNotification { resolver },
);
request_id
}
pub(in crate::worker) fn take_pending_service_worker_show_notification(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerShowNotification> {
self.pending_service_worker_show_notifications
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_get_notifications(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self.service_worker_get_notifications_request_ids.allocate();
self.pending_service_worker_get_notifications.insert(
request_id,
PendingServiceWorkerGetNotifications { resolver },
);
request_id
}
pub(in crate::worker) fn take_pending_service_worker_get_notifications(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerGetNotifications> {
self.pending_service_worker_get_notifications
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_sync_registration(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self.service_worker_sync_registration_request_ids.allocate();
self.pending_service_worker_sync_registrations.insert(
request_id,
PendingServiceWorkerSyncRegistration { resolver },
);
request_id
}
pub(in crate::worker) fn take_pending_service_worker_sync_registration(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerSyncRegistration> {
self.pending_service_worker_sync_registrations
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_sync_get_tags(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self.service_worker_sync_get_tags_request_ids.allocate();
self.pending_service_worker_sync_get_tags
.insert(request_id, PendingServiceWorkerSyncGetTags { resolver });
request_id
}
pub(in crate::worker) fn take_pending_service_worker_sync_get_tags(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerSyncGetTags> {
self.pending_service_worker_sync_get_tags
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_periodic_sync_registration(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self
.service_worker_periodic_sync_registration_request_ids
.allocate();
self.pending_service_worker_periodic_sync_registrations
.insert(
request_id,
PendingServiceWorkerPeriodicSyncRegistration { resolver },
);
request_id
}
pub(in crate::worker) fn take_pending_service_worker_periodic_sync_registration(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerPeriodicSyncRegistration> {
self.pending_service_worker_periodic_sync_registrations
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_periodic_sync_get_tags(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self
.service_worker_periodic_sync_get_tags_request_ids
.allocate();
self.pending_service_worker_periodic_sync_get_tags.insert(
request_id,
PendingServiceWorkerPeriodicSyncGetTags { resolver },
);
request_id
}
pub(in crate::worker) fn take_pending_service_worker_periodic_sync_get_tags(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerPeriodicSyncGetTags> {
self.pending_service_worker_periodic_sync_get_tags
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_periodic_sync_unregistration(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self
.service_worker_periodic_sync_unregistration_request_ids
.allocate();
self.pending_service_worker_periodic_sync_unregistrations
.insert(
request_id,
PendingServiceWorkerPeriodicSyncUnregistration { resolver },
);
request_id
}
pub(in crate::worker) fn take_pending_service_worker_periodic_sync_unregistration(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerPeriodicSyncUnregistration> {
self.pending_service_worker_periodic_sync_unregistrations
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_push_subscribe(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self.service_worker_push_subscription_request_ids.allocate();
self.pending_service_worker_push_subscriptions
.insert(request_id, PendingServiceWorkerPushSubscribe { resolver });
request_id
}
pub(in crate::worker) fn take_pending_service_worker_push_subscribe(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerPushSubscribe> {
self.pending_service_worker_push_subscriptions
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_push_get_subscription(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self
.service_worker_push_get_subscription_request_ids
.allocate();
self.pending_service_worker_push_get_subscriptions.insert(
request_id,
PendingServiceWorkerPushGetSubscription { resolver },
);
request_id
}
pub(in crate::worker) fn take_pending_service_worker_push_get_subscription(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerPushGetSubscription> {
self.pending_service_worker_push_get_subscriptions
.remove(&request_id)
}
pub(in crate::worker) fn register_pending_service_worker_push_unsubscribe(
&mut self,
resolver: v8::Global<v8::PromiseResolver>,
) -> u64 {
let request_id = self
.service_worker_push_unsubscription_request_ids
.allocate();
self.pending_service_worker_push_unsubscriptions
.insert(request_id, PendingServiceWorkerPushUnsubscribe { resolver });
request_id
}
pub(in crate::worker) fn take_pending_service_worker_push_unsubscribe(
&mut self,
request_id: u64,
) -> Option<PendingServiceWorkerPushUnsubscribe> {
self.pending_service_worker_push_unsubscriptions
.remove(&request_id)
}
pub(in crate::worker) fn consume_service_worker_window_interaction(&mut self) -> bool {
if self.service_worker_window_interaction_allowed_count == 0 {
return false;
}
self.service_worker_window_interaction_allowed_count = self
.service_worker_window_interaction_allowed_count
.saturating_sub(1);
if let Some((_, pending)) = self
.pending_service_worker_message_events
.iter_mut()
.find(|(_, pending)| pending.window_interaction_allowed)
{
pending.window_interaction_allowed = false;
return true;
}
if let Some((_, pending)) = self
.pending_service_worker_notification_events
.iter_mut()
.find(|(_, pending)| pending.window_interaction_allowed)
{
pending.window_interaction_allowed = false;
}
true
}
}
pub(in crate::worker) fn drain_service_worker_client_query_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: ServiceWorkerClientQueryResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) = state.take_pending_service_worker_client_query(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match pending.query_type {
PendingServiceWorkerClientQueryType::Get => {
let value = result
.clients
.into_iter()
.next()
.and_then(|client| build_service_worker_client_object_from_snapshot(scope, &client))
.map(v8::Local::into)
.unwrap_or_else(|| v8::undefined(scope).into());
let _ = resolver.resolve(scope, value);
}
PendingServiceWorkerClientQueryType::MatchAll => {
let array = v8::Array::new(scope, result.clients.len() as i32);
for (index, client) in result.clients.iter().enumerate() {
if let Some(object) =
build_service_worker_client_object_from_snapshot(scope, client)
{
let _ = array.set_index(scope, index as u32, object.into());
}
}
let _ = resolver.resolve(scope, array.into());
}
}
}
pub(in crate::worker) fn drain_service_worker_client_navigate_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: ServiceWorkerClientNavigateResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) = state.take_pending_service_worker_client_navigate(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(Some(client)) => {
let value = build_service_worker_client_object_from_snapshot(scope, &client)
.map(v8::Local::into)
.unwrap_or_else(|| v8::null(scope).into());
let _ = resolver.resolve(scope, value);
}
Ok(None) => {
let _ = resolver.resolve(scope, v8::null(scope).into());
}
Err(error) => match error {
ServiceWorkerClientNavigateError::TypeError(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
}
},
}
}
pub(in crate::worker) fn drain_service_worker_client_focus_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: crate::runtime::ServiceWorkerClientFocusResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) = state.take_pending_service_worker_client_focus(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(client) => {
let value = build_service_worker_client_object_from_snapshot(scope, &client)
.map(v8::Local::into)
.unwrap_or_else(|| v8::null(scope).into());
let _ = resolver.resolve(scope, value);
}
Err(error) => {
reject_service_worker_client_focus_error(scope, resolver, error);
}
}
}
fn reject_service_worker_client_focus_error<'s>(
scope: &mut v8::PinScope<'s, '_>,
resolver: v8::Local<'s, v8::PromiseResolver>,
error: ServiceWorkerClientFocusError,
) {
match error {
ServiceWorkerClientFocusError::DomException { name, message } => {
let reason = worker_dom_exception_value(scope, &message, name);
let _ = resolver.reject(scope, reason);
}
ServiceWorkerClientFocusError::TypeError(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let reason = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, reason);
}
}
}
pub(in crate::worker) fn drain_service_worker_clients_open_window_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: ServiceWorkerClientsOpenWindowResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) =
state.take_pending_service_worker_clients_open_window(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(Some(client)) => {
let value = build_service_worker_client_object_from_snapshot(scope, &client)
.map(v8::Local::into)
.unwrap_or_else(|| v8::null(scope).into());
let _ = resolver.resolve(scope, value);
}
Ok(None) => {
let _ = resolver.resolve(scope, v8::null(scope).into());
}
Err(error) => match error {
ServiceWorkerClientsOpenWindowError::TypeError(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
}
},
}
}
pub(in crate::worker) fn drain_service_worker_show_notification_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: ServiceWorkerShowNotificationResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) = state.take_pending_service_worker_show_notification(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(()) => {
let _ = resolver.resolve(scope, v8::undefined(scope).into());
}
Err(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
}
}
}
pub(in crate::worker) fn drain_service_worker_get_notifications_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: ServiceWorkerGetNotificationsResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) = state.take_pending_service_worker_get_notifications(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
let notifications = match result.result {
Ok(notifications) => notifications,
Err(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
return;
}
};
let array = v8::Array::new(scope, notifications.len() as i32);
for (index, notification) in notifications.iter().enumerate() {
if let Some(object) =
crate::context_bootstrap::build_notification_object_from_snapshot(scope, notification)
{
let _ = array.set_index(scope, index as u32, object.into());
}
}
let _ = resolver.resolve(scope, array.into());
}
pub(in crate::worker) fn drain_service_worker_sync_registration_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: ServiceWorkerSyncRegistrationResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) = state.take_pending_service_worker_sync_registration(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(()) => {
let _ = resolver.resolve(scope, v8::undefined(scope).into());
}
Err(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
}
}
}
pub(in crate::worker) fn drain_service_worker_sync_get_tags_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: ServiceWorkerSyncGetTagsResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) = state.take_pending_service_worker_sync_get_tags(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(tags) => {
let array = v8::Array::new(scope, tags.len() as i32);
for (index, tag) in tags.iter().enumerate() {
if let Some(value) = v8_string(scope, tag) {
let _ = array.set_index(scope, index as u32, value.into());
}
}
let _ = resolver.resolve(scope, array.into());
}
Err(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
}
}
}
pub(in crate::worker) fn drain_service_worker_periodic_sync_registration_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: crate::runtime::ServiceWorkerPeriodicSyncRegistrationResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) =
state.take_pending_service_worker_periodic_sync_registration(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(()) => {
let _ = resolver.resolve(scope, v8::undefined(scope).into());
}
Err(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
}
}
}
pub(in crate::worker) fn drain_service_worker_periodic_sync_get_tags_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: crate::runtime::ServiceWorkerPeriodicSyncGetTagsResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) =
state.take_pending_service_worker_periodic_sync_get_tags(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(tags) => {
let array = v8::Array::new(scope, tags.len() as i32);
for (index, tag) in tags.iter().enumerate() {
if let Some(value) = v8_string(scope, tag) {
let _ = array.set_index(scope, index as u32, value.into());
}
}
let _ = resolver.resolve(scope, array.into());
}
Err(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
}
}
}
pub(in crate::worker) fn drain_service_worker_periodic_sync_unregistration_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: crate::runtime::ServiceWorkerPeriodicSyncUnregistrationResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) =
state.take_pending_service_worker_periodic_sync_unregistration(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(()) => {
let _ = resolver.resolve(scope, v8::undefined(scope).into());
}
Err(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
}
}
}
pub(in crate::worker) fn drain_service_worker_push_subscribe_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: ServiceWorkerPushSubscribeResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) = state.take_pending_service_worker_push_subscribe(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(subscription) => {
let value = build_service_worker_push_subscription_object(scope, &subscription)
.map(v8::Local::into)
.unwrap_or_else(|| v8::null(scope).into());
let _ = resolver.resolve(scope, value);
}
Err(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
}
}
}
pub(in crate::worker) fn drain_service_worker_push_get_subscription_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: ServiceWorkerPushGetSubscriptionResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) =
state.take_pending_service_worker_push_get_subscription(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(Some(subscription)) => {
let value = build_service_worker_push_subscription_object(scope, &subscription)
.map(v8::Local::into)
.unwrap_or_else(|| v8::null(scope).into());
let _ = resolver.resolve(scope, value);
}
Ok(None) => {
let _ = resolver.resolve(scope, v8::null(scope).into());
}
Err(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
}
}
}
pub(in crate::worker) fn drain_service_worker_push_unsubscribe_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
result: ServiceWorkerPushUnsubscribeResult,
) {
let pending = {
let mut state = state.borrow_mut();
let Some(pending) = state.take_pending_service_worker_push_unsubscribe(result.request_id)
else {
return;
};
pending
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result.result {
Ok(unsubscribed) => {
let _ = resolver.resolve(scope, v8::Boolean::new(scope, unsubscribed).into());
}
Err(message) => {
let Some(message) = v8_string(scope, &message) else {
let _ = resolver.reject(scope, v8::undefined(scope).into());
return;
};
let error = v8::Exception::type_error(scope, message);
let _ = resolver.reject(scope, error);
}
}
}
#[cfg(test)]
mod service_worker_request_id_allocator_tests {
use super::WorkerServiceWorkerRequestIdAllocator;
#[test]
#[should_panic(expected = "worker Service Worker request id space exhausted")]
fn operation_local_request_ids_never_wrap() {
let mut ids = WorkerServiceWorkerRequestIdAllocator { next: u64::MAX };
let _ = ids.allocate();
}
}
@@ -0,0 +1,323 @@
//! WorkerGlobalScope runtime state and native queries for the current worker.
use super::*;
pub(crate) const WORKER_STATE_SLOT: &str = "__workerState";
pub(crate) struct WorkerGlobalState {
/// Weak handles and native cleanups owned by this worker isolate. Worker
/// teardown clears this registry before `OwnedIsolate` is destroyed.
pub(crate) v8_finalizers: crate::v8_finalizer::V8FinalizerRegistry,
/// Channel to send messages back to the parent.
pub(in crate::worker) parent_tx: mpsc::UnboundedSender<WorkerToParentMessage>,
/// Internal wake channel used by worker-owned async runtime surfaces.
pub(crate) worker_wake_tx: mpsc::UnboundedSender<crate::worker::handle::WorkerMessage>,
/// Shared lifecycle bit published by `Worker.terminate()` before V8 is
/// interrupted. Worker-side task delivery must not rely on cross-thread
/// timing between that interrupt and already-selected work.
pub(crate) termination_requested: Arc<AtomicBool>,
/// Whether `close()` has been called.
pub(in crate::worker) closed: bool,
/// HTML's per-global guard against recursively reporting an error event.
pub(in crate::worker) in_error_reporting_mode: bool,
/// Timer id counter.
pub(in crate::worker) next_timer_id: u32,
/// Inside-settings resource authority for every request owned by this
/// WorkerGlobalScope. Even data/blob workers retain the creator's browser
/// backend so later fetch/XHR/module/WebSocket work has an exact owner.
pub(in crate::worker) loader: crate::network::context::WorkerResourceLoader,
/// Whether this runtime is a dedicated or shared worker global.
pub(in crate::worker) global_kind: crate::worker::thread::WorkerGlobalKind,
/// Whether this worker was constructed as a classic or module worker.
pub(in crate::worker) script_kind: crate::worker::thread::WorkerScriptKind,
/// Base URL used to resolve relative worker script fetches.
pub(in crate::worker) current_script_url: Option<Url>,
/// Referrer policy parsed from the top-level worker script response.
pub(in crate::worker) referrer_policy: Option<String>,
/// CSP policies from outside settings used for module static imports.
pub(in crate::worker) module_static_import_content_security_policies: Vec<String>,
/// Enforce CSP policies parsed from the top-level worker script response.
pub(in crate::worker) content_security_policies: Vec<String>,
/// Report-only CSP policies parsed from the top-level worker script response.
pub(in crate::worker) content_security_report_only_policies: Vec<String>,
/// Reporting API endpoints parsed from the top-level worker script response.
pub(in crate::worker) content_security_reporting_endpoints:
ContentSecurityPolicyReportingEndpoints,
/// Whether this worker global is a secure context for `[SecureContext]` APIs.
pub(in crate::worker) secure_context: bool,
/// Network.setExtraHTTPHeaders headers inherited from the owning page/CDP session.
pub(in crate::worker) extra_http_headers: moli_fetch::RequestHeaders,
/// Permission overrides inherited from the owning page/CDP session.
pub(in crate::worker) permission_overrides:
Vec<crate::protocol_types::PermissionOverrideRegistration>,
/// Network.emulateNetworkConditions offline state inherited from the owning page/CDP session.
pub(in crate::worker) network_offline: bool,
/// Network.setBlockedURLs patterns inherited from the owning page/CDP session.
pub(in crate::worker) blocked_url_patterns: Vec<String>,
/// Current document network/cache partition key inherited by worker-owned requests.
pub(in crate::worker) network_partition_key: Option<String>,
/// COEP/DIP policy container state inherited by worker-owned subresource requests.
pub(in crate::worker) policy_context: crate::types::SubresourcePolicyContext,
/// Fetch domain subresource interception inherited from the owning page/CDP session.
pub(in crate::worker) fetch_subresource_interception_enabled: bool,
pub(in crate::worker) fetch_subresource_interception_resource_type:
Option<SubresourceResourceType>,
/// Async fetch completions routed back onto the worker event loop.
pub(in crate::worker) fetch_completion_tx: mpsc::UnboundedSender<WorkerFetchEvent>,
/// Promise resolvers pending async fetch completion.
pub(in crate::worker) pending_fetches: HashMap<u32, PendingWorkerFetch>,
/// Worker-local pending body sources for headers-first worker fetch.
pub(crate) pending_network_body_sources:
HashMap<NetworkBodySourceId, crate::network_host::PendingNetworkBodySourceState>,
pub(crate) pending_network_body_clones: HashMap<NetworkBodySourceId, Vec<NetworkBodySourceId>>,
/// Fetch id counter.
pub(in crate::worker) next_fetch_id: u32,
/// Async XHR completions routed back onto the worker event loop.
pub(in crate::worker) xhr_completion_tx: mpsc::UnboundedSender<WorkerXhrCompletion>,
/// In-flight worker XHR requests keyed by internal id.
pub(in crate::worker) pending_xhrs: HashMap<u32, PendingWorkerXhr>,
/// Worker XHR id counter.
pub(in crate::worker) next_xhr_id: u32,
/// Worker-owned CSP report requests paused for Fetch domain request-stage interception.
pub(in crate::worker) pending_csp_reports: HashMap<u32, PendingWorkerCspReport>,
/// Worker-local TextDecoder state. TextEncoder is stateless, but TextDecoder
/// can stream and needs decoder state tied to this worker's isolate.
pub(crate) text_codecs: TextCodecStore,
/// Worker-local AbortController/AbortSignal runtime state.
pub(in crate::worker) abort: Rc<RefCell<WorkerAbortStore>>,
/// Worker-thread view of the creator's browser-context/partition runtime.
pub(in crate::worker) worker_context_runtime: crate::runtime::RendererWorkerContextRuntime,
/// Browser-context Service Worker owner inherited by real worker clients.
pub(in crate::worker) service_worker_runtime:
Option<crate::service_worker_runtime::ServiceWorkerRuntimeService>,
/// Live Service Worker client id for DedicatedWorker/SharedWorker globals.
pub(in crate::worker) service_worker_client_id:
Option<crate::service_worker_runtime::ServiceWorkerClientId>,
/// Owner-scoped MessagePort state shared with page and nested worker contexts.
pub(in crate::worker) message_port_registry: SharedMessagePortRegistry,
/// Worker-owned MessagePort wrappers keyed by runtime port id.
pub(in crate::worker) message_port_wrappers:
HashMap<MessagePortId, WorkerMessagePortWrapperEntry>,
/// SharedWorker connect-event ports whose lifetime is owned by the shared worker runtime.
pub(in crate::worker) shared_worker_connection_ports: HashSet<MessagePortId>,
/// Worker-owned BroadcastChannel wrappers keyed by runtime channel id.
pub(in crate::worker) broadcast_channel_registry: SharedBroadcastChannelRegistry,
pub(in crate::worker) broadcast_channel_storage_key: MoliStorageKey,
pub(in crate::worker) storage_key: MoliStorageKey,
pub(in crate::worker) broadcast_channel_wrappers:
HashMap<BroadcastChannelId, v8::Global<v8::Object>>,
/// IndexedDB storage manager inherited from the owning browser context.
pub(in crate::worker) indexed_db_manager:
Option<crate::context_bootstrap::WeakIndexedDbManager>,
/// Storage Buckets registry inherited from the owning browser context.
pub(in crate::worker) storage_bucket_store:
Option<crate::context_bootstrap::SharedStorageBucketStore>,
/// Async WebSocket transport events routed back onto this worker loop.
pub(in crate::worker) websocket_event_tx: tokio::sync::mpsc::Sender<WebSocketEvent>,
/// Worker-owned classic WebSocket connections keyed by socket id.
pub(in crate::worker) websockets: HashMap<u64, WorkerWebSocketState>,
/// Worker-local WebSocket id counter.
pub(in crate::worker) next_websocket_id: u64,
/// Worker-local dedicated workers created through `new Worker(...)`.
pub(in crate::worker) next_nested_worker_id: u64,
pub(in crate::worker) nested_worker_wrappers:
HashMap<DedicatedWorkerId, v8::Global<v8::Object>>,
/// Heavyweight WebCrypto completions routed back onto the worker event loop.
pub(in crate::worker) webcrypto_completion_tx: mpsc::UnboundedSender<WorkerWebCryptoCompletion>,
/// In-flight worker WebCrypto blocking tasks keyed by task id.
pub(in crate::worker) pending_webcrypto: HashMap<u64, PendingWorkerWebCryptoTask>,
/// Worker WebCrypto task id counter (never zero so 0 can mean "unset").
pub(in crate::worker) next_webcrypto_task_id: u64,
/// OPFS completions routed from the partition-owned storage IO sequence.
pub(in crate::worker) opfs_completion_tx: mpsc::UnboundedSender<WorkerOpfsCompletion>,
pub(in crate::worker) opfs_owner_state: Option<WorkerOpfsOwnerState>,
/// In-flight Service Worker `periodicsync` events keyed by runtime event id.
pub(in crate::worker) pending_service_worker_periodic_sync_events:
HashMap<ServiceWorkerEventId, PendingServiceWorkerPeriodicSyncEvent>,
/// In-flight Service Worker `clients.get()` / `clients.matchAll()` queries keyed by request id.
pub(in crate::worker) pending_service_worker_client_queries:
HashMap<u64, PendingServiceWorkerClientQuery>,
/// In-flight Service Worker `WindowClient.navigate()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_client_navigates:
HashMap<u64, PendingServiceWorkerClientNavigate>,
/// In-flight Service Worker `WindowClient.focus()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_client_focuses:
HashMap<u64, PendingServiceWorkerClientFocus>,
/// In-flight Service Worker `clients.openWindow()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_clients_open_windows:
HashMap<u64, PendingServiceWorkerClientsOpenWindow>,
/// In-flight Service Worker `registration.showNotification()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_show_notifications:
HashMap<u64, PendingServiceWorkerShowNotification>,
/// In-flight Service Worker `registration.getNotifications()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_get_notifications:
HashMap<u64, PendingServiceWorkerGetNotifications>,
/// In-flight Service Worker `registration.sync.register()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_sync_registrations:
HashMap<u64, PendingServiceWorkerSyncRegistration>,
/// In-flight Service Worker `registration.sync.getTags()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_sync_get_tags:
HashMap<u64, PendingServiceWorkerSyncGetTags>,
/// In-flight Service Worker `registration.periodicSync.register()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_periodic_sync_registrations:
HashMap<u64, PendingServiceWorkerPeriodicSyncRegistration>,
/// In-flight Service Worker `registration.periodicSync.getTags()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_periodic_sync_get_tags:
HashMap<u64, PendingServiceWorkerPeriodicSyncGetTags>,
/// In-flight Service Worker `registration.periodicSync.unregister()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_periodic_sync_unregistrations:
HashMap<u64, PendingServiceWorkerPeriodicSyncUnregistration>,
/// In-flight Service Worker `registration.pushManager.subscribe()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_push_subscriptions:
HashMap<u64, PendingServiceWorkerPushSubscribe>,
/// In-flight Service Worker `registration.pushManager.getSubscription()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_push_get_subscriptions:
HashMap<u64, PendingServiceWorkerPushGetSubscription>,
/// In-flight Service Worker `PushSubscription.unsubscribe()` requests keyed by request id.
pub(in crate::worker) pending_service_worker_push_unsubscriptions:
HashMap<u64, PendingServiceWorkerPushUnsubscribe>,
pub(in crate::worker) service_worker_client_query_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_client_navigate_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_client_focus_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_clients_open_window_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_show_notification_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_get_notifications_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_sync_registration_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_sync_get_tags_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_periodic_sync_registration_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_periodic_sync_get_tags_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_periodic_sync_unregistration_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_push_subscription_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_push_get_subscription_request_ids:
WorkerServiceWorkerRequestIdAllocator,
pub(in crate::worker) service_worker_push_unsubscription_request_ids:
WorkerServiceWorkerRequestIdAllocator,
/// Count of active Service Worker events that allow window interaction.
pub(in crate::worker) service_worker_window_interaction_allowed_count: usize,
/// Service Worker lifecycle events currently waiting for dispatch and
/// `ExtendableEvent.waitUntil()` promises to finish.
pub(in crate::worker) pending_service_worker_lifecycle_events:
HashMap<ServiceWorkerEventId, PendingServiceWorkerLifecycleEvent>,
/// Service Worker fetch events currently waiting for dispatch, `respondWith()`,
/// and minimal `waitUntil()` promises to finish.
pub(in crate::worker) pending_service_worker_fetch_events:
HashMap<ServiceWorkerEventId, PendingServiceWorkerFetchEvent>,
/// Navigation preload promises and response bodies stay alive until preload complete/error.
pub(in crate::worker) pending_service_worker_navigation_preloads:
HashMap<ServiceWorkerEventId, PendingServiceWorkerNavigationPreload>,
/// Service Worker message events currently waiting for dispatch and
/// `ExtendableEvent.waitUntil()` promises to finish.
pub(in crate::worker) pending_service_worker_message_events:
HashMap<ServiceWorkerEventId, PendingServiceWorkerMessageEvent>,
/// Service Worker notification events currently waiting for dispatch and
/// `ExtendableEvent.waitUntil()` promises to finish.
pub(in crate::worker) pending_service_worker_notification_events:
HashMap<ServiceWorkerEventId, PendingServiceWorkerNotificationEvent>,
/// Service Worker push events currently waiting for dispatch and
/// `ExtendableEvent.waitUntil()` promises to finish.
pub(in crate::worker) pending_service_worker_push_events:
HashMap<ServiceWorkerEventId, PendingServiceWorkerPushEvent>,
/// Service Worker sync events currently waiting for dispatch and
/// `ExtendableEvent.waitUntil()` promises to finish.
pub(in crate::worker) pending_service_worker_sync_events:
HashMap<ServiceWorkerEventId, PendingServiceWorkerSyncEvent>,
}
pub(crate) fn worker_service_worker_control_state(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<crate::runtime::ServiceWorkerControlState> {
let state = get_worker_state(scope)?;
let state = state.borrow();
let runtime = state.service_worker_runtime.as_ref()?;
let client_id = state.service_worker_client_id?;
runtime.matching_controller_for_client(client_id)
}
/// Retrieve the `WorkerGlobalState` from a callback scope.
pub(crate) fn get_worker_state<'s>(
scope: &mut v8::PinScope<'s, '_>,
) -> Option<Rc<RefCell<WorkerGlobalState>>> {
let global = scope.get_current_context().global(scope);
let val = get_private_value(scope, global, WORKER_STATE_SLOT)?;
let external = v8::Local::<v8::External>::try_from(val).ok()?;
let ptr = external.value() as *const RefCell<WorkerGlobalState>;
// Safety: the Rc is kept alive by the worker thread for the lifetime of
// the context. We increment the ref-count here so we get our own Rc.
unsafe {
Rc::increment_strong_count(ptr);
Some(Rc::from_raw(ptr))
}
}
pub(crate) fn worker_global_is_closed(scope: &mut v8::PinScope<'_, '_>) -> bool {
get_worker_state(scope).is_some_and(|state| state.borrow().closed)
}
pub(crate) fn worker_termination_requested(scope: &mut v8::PinScope<'_, '_>) -> bool {
get_worker_state(scope)
.is_some_and(|state| state.borrow().termination_requested.load(Ordering::Acquire))
}
pub(crate) fn worker_storage_key(scope: &mut v8::PinScope<'_, '_>) -> Option<MoliStorageKey> {
Some(get_worker_state(scope)?.borrow().storage_key.clone())
}
pub(crate) fn worker_storage_partition_identity(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<crate::runtime::RendererStoragePartitionIdentity> {
Some(
get_worker_state(scope)?
.borrow()
.worker_context_runtime
.storage_partition_identity(),
)
}
pub(crate) fn worker_current_script_url(scope: &mut v8::PinScope<'_, '_>) -> Option<Url> {
get_worker_state(scope)?.borrow().current_script_url.clone()
}
pub(crate) fn worker_exception_report_target(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<(mpsc::UnboundedSender<WorkerToParentMessage>, String)> {
let state = get_worker_state(scope)?;
let state = state.borrow();
let script_url = state
.current_script_url
.as_ref()
.map(ToString::to_string)
.unwrap_or_default();
Some((state.parent_tx.clone(), script_url))
}
pub(crate) fn worker_uses_shared_worker_agent_cluster(scope: &mut v8::PinScope<'_, '_>) -> bool {
get_worker_state(scope).is_some_and(|state| {
matches!(
state.borrow().global_kind,
crate::worker::thread::WorkerGlobalKind::Shared { .. }
)
})
}
pub(super) fn worker_close_callback(
scope: &mut v8::PinScope<'_, '_>,
_args: v8::FunctionCallbackArguments<'_>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
if let Some(state) = get_worker_state(scope) {
state.borrow_mut().closed = true;
close_worker_owned_broadcast_channels(&state);
}
}
@@ -262,3 +262,79 @@ pub(super) fn worker_request_animation_frame_callback<'s>(
}
rv.set_uint32(timer_id);
}
#[derive(Default, WebApiObject)]
#[webapi(plain)]
pub(super) struct WorkerGlobalTimerOperationsDeclaration {
#[webapi(method = "setTimeout", callback = worker_set_timeout_callback, length = 1)]
set_timeout: (),
#[webapi(method = "clearTimeout", callback = worker_clear_timeout_callback, length = 1)]
clear_timeout: (),
#[webapi(method = "setInterval", callback = worker_set_interval_callback, length = 1)]
set_interval: (),
#[webapi(method = "clearInterval", callback = worker_clear_interval_callback, length = 1)]
clear_interval: (),
#[webapi(
method = "requestAnimationFrame",
callback = worker_request_animation_frame_callback,
length = 1
)]
request_animation_frame: (),
#[webapi(
method = "cancelAnimationFrame",
callback = worker_clear_timeout_callback,
length = 1
)]
cancel_animation_frame: (),
#[webapi(
method = "queueMicrotask",
callback = worker_queue_microtask_callback,
length = 1
)]
queue_microtask: (),
}
#[derive(Clone, Default)]
pub(in crate::worker) struct WorkerIsolateTimerQueues {
pending: Rc<RefCell<Vec<TimerInfo>>>,
cancelled: Rc<RefCell<Vec<u32>>>,
}
impl WorkerIsolateTimerQueues {
pub(in crate::worker) fn push_pending(&self, timer: TimerInfo) {
self.pending.borrow_mut().push(timer);
}
pub(in crate::worker) fn clear_pending_and_active(&self, timer_id: u32) {
self.pending
.borrow_mut()
.retain(|timer| timer.id != timer_id);
self.cancel_active(timer_id);
}
pub(in crate::worker) fn cancel_active(&self, timer_id: u32) {
self.cancelled.borrow_mut().push(timer_id);
}
pub(in crate::worker) fn drain_pending(&self) -> Vec<TimerInfo> {
self.pending.borrow_mut().drain(..).collect()
}
pub(in crate::worker) fn drain_cancelled(&self) -> Vec<u32> {
self.cancelled.borrow_mut().drain(..).collect()
}
}
pub(in crate::worker) fn worker_isolate_timer_queues(
scope: &mut v8::PinScope<'_, '_>,
) -> Option<WorkerIsolateTimerQueues> {
scope.get_slot::<WorkerIsolateTimerQueues>().cloned()
}
pub(in crate::worker) struct TimerInfo {
pub(in crate::worker) id: u32,
pub(in crate::worker) callback: crate::worker::timer_callback::WorkerTimerCallback,
pub(in crate::worker) delay_ms: u64,
pub(in crate::worker) is_interval: bool,
pub(in crate::worker) extra_args: Vec<v8::Global<v8::Value>>,
}