From e442d0eee2e1ead411b417d2ccde282a20b15d9a Mon Sep 17 00:00:00 2001 From: ldm0 Date: Fri, 2 Oct 2026 01:37:16 +0800 Subject: [PATCH] refactor(worker): split global scope by responsibility --- .../webidl_callback_source_boundary_tests.rs | 2 +- .../src/worker/global_scope/async_tasks.rs | 326 ++ .../src/worker/global_scope/bootstrap.rs | 522 ++ .../src/worker/global_scope/console.rs | 186 + .../global_scope/content_security_policy.rs | 131 +- .../src/worker/global_scope/event_handlers.rs | 747 +++ .../worker/global_scope/extendable_events.rs | 560 ++ .../src/worker/global_scope/import_scripts.rs | 212 +- .../src/worker/global_scope/messaging.rs | 236 + .../src/worker/global_scope/mod.rs | 5114 +---------------- .../src/worker/global_scope/nested_workers.rs | 153 + .../src/worker/global_scope/network_state.rs | 265 + .../src/worker/global_scope/origin.rs | 73 + .../src/worker/global_scope/performance.rs | 62 + .../worker/global_scope/service_worker_api.rs | 316 + .../global_scope/service_worker_results.rs | 908 +++ .../src/worker/global_scope/state.rs | 323 ++ .../src/worker/global_scope/timers.rs | 76 + 18 files changed, 5201 insertions(+), 5011 deletions(-) create mode 100644 moli-renderer-v8/src/worker/global_scope/async_tasks.rs create mode 100644 moli-renderer-v8/src/worker/global_scope/bootstrap.rs create mode 100644 moli-renderer-v8/src/worker/global_scope/console.rs create mode 100644 moli-renderer-v8/src/worker/global_scope/event_handlers.rs create mode 100644 moli-renderer-v8/src/worker/global_scope/extendable_events.rs create mode 100644 moli-renderer-v8/src/worker/global_scope/messaging.rs create mode 100644 moli-renderer-v8/src/worker/global_scope/nested_workers.rs create mode 100644 moli-renderer-v8/src/worker/global_scope/network_state.rs create mode 100644 moli-renderer-v8/src/worker/global_scope/origin.rs create mode 100644 moli-renderer-v8/src/worker/global_scope/performance.rs create mode 100644 moli-renderer-v8/src/worker/global_scope/service_worker_results.rs create mode 100644 moli-renderer-v8/src/worker/global_scope/state.rs diff --git a/moli-renderer-v8/src/webidl_callback_source_boundary_tests.rs b/moli-renderer-v8/src/webidl_callback_source_boundary_tests.rs index 86cfa36ca8..fe8e9d4da5 100644 --- a/moli-renderer-v8/src/webidl_callback_source_boundary_tests.rs +++ b/moli-renderer-v8/src/webidl_callback_source_boundary_tests.rs @@ -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, ), diff --git a/moli-renderer-v8/src/worker/global_scope/async_tasks.rs b/moli-renderer-v8/src/worker/global_scope/async_tasks.rs new file mode 100644 index 0000000000..ade37d7027 --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/async_tasks.rs @@ -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, +} + +/// 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, +} + +pub(in crate::worker) struct PendingWorkerOpfsTask { + pub(in crate::worker) locator: moli_storage_service::StorageBucketLocator, + pub(in crate::worker) handle_access: Option, + pub(in crate::worker) settlement: crate::opfs_owner_tasks::OpfsTaskSettlement, +} + +pub(in crate::worker) struct WorkerOpfsOwnerState { + next_task_id: u64, + pending_tasks: HashMap, + 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, + ) -> (u64, mpsc::UnboundedSender) { + 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 { + 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, + settlement: crate::opfs_owner_tasks::OpfsTaskSettlement, + ) -> (u64, mpsc::UnboundedSender) { + 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 { + 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)> { + 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, +) -> Option<(u64, mpsc::UnboundedSender)> { + 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, + handle_access: Option, +) -> Option<(u64, mpsc::UnboundedSender)> { + 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, +) -> Option<(u64, mpsc::UnboundedSender)> { + 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 { + 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 { + 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 { + 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 { + 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>, + 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>, + 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, + ®istry, + iterator_id, + &pending.locator, + handle_access.as_ref(), + completion.result, + ); + drop(keep_alive); + } + } +} diff --git a/moli-renderer-v8/src/worker/global_scope/bootstrap.rs b/moli-renderer-v8/src/worker/global_scope/bootstrap.rs new file mode 100644 index 0000000000..046c5b1c0d --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/bootstrap.rs @@ -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>` +/// 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>, + 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) }; + 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, + worker: v8::Global, + specific_worker: v8::Global, +} + +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 { + 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> { + 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> { + let prototype_key = v8str(scope, "prototype"); + let prototype = constructor + .get(scope, prototype_key.into()) + .and_then(|value| v8::Local::::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}`")) +} diff --git a/moli-renderer-v8/src/worker/global_scope/console.rs b/moli-renderer-v8/src/worker/global_scope/console.rs new file mode 100644 index 0000000000..ab6121fcff --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/console.rs @@ -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 { + 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::::try_from(value).ok()) + else { + return; + }; + let Some(method) = original_console + .get(scope, v8str(scope, method_name).into()) + .and_then(|value| v8::Local::::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); +} diff --git a/moli-renderer-v8/src/worker/global_scope/content_security_policy.rs b/moli-renderer-v8/src/worker/global_scope/content_security_policy.rs index 8efcc05086..7d7b322183 100644 --- a/moli-renderer-v8/src/worker/global_scope/content_security_policy.rs +++ b/moli-renderer-v8/src/worker/global_scope/content_security_policy.rs @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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>, + violation: &crate::content_security_policy::ContentSecurityPolicyUrlViolation, +) { + self::dispatch_worker_content_security_policy_violation_event_for_state( + scope, state, violation, + ); +} diff --git a/moli-renderer-v8/src/worker/global_scope/event_handlers.rs b/moli-renderer-v8/src/worker/global_scope/event_handlers.rs new file mode 100644 index 0000000000..c73da2ab54 --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/event_handlers.rs @@ -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::(); + 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() +} diff --git a/moli-renderer-v8/src/worker/global_scope/extendable_events.rs b/moli-renderer-v8/src/worker/global_scope/extendable_events.rs new file mode 100644 index 0000000000..0a63e56bd7 --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/extendable_events.rs @@ -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>, + pub(in crate::worker) request_signal_id: Option, + pub(in crate::worker) request_mode: RequestMode, + pub(in crate::worker) request_destination: ServiceWorkerRequestDestination, + pub(in crate::worker) pending_respond_with_response: + Option, + pub(in crate::worker) pending_respond_with_stream_body_source_id: Option, + pub(in crate::worker) pending_respond_with_stream_cancel_handle: Option>, + 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, + pub(in crate::worker) resolver: Option>, + pub(in crate::worker) body_source_id: Option, +} + +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 { + if args.length() == 0 { + throw_type_error( + scope, + &format!("Failed to construct '{constructor_name}': 1 argument required."), + ); + return None; + } + webidl::argument::( + 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> { + if args.length() <= 1 { + return None; + } + let value = args.get(1); + if value.is_null_or_undefined() || !value.is_object() { + None + } else { + v8::Local::::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>, + prefix: &'static str, + key: &'static str, +) -> Option { + 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::(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>, +) -> Option> { + 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>, + key: &'static str, + default: &'static str, +) -> Option { + 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::( + 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>, +) -> Option> { + 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::::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>, +) -> Option> { + 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::::try_from(value) else { + throw_type_error( + scope, + "Failed to construct 'ExtendableMessageEvent': member ports is not a sequence.", + ); + 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::::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); +} diff --git a/moli-renderer-v8/src/worker/global_scope/import_scripts.rs b/moli-renderer-v8/src/worker/global_scope/import_scripts.rs index a8cf664f6a..fe14d3a744 100644 --- a/moli-renderer-v8/src/worker/global_scope/import_scripts.rs +++ b/moli-renderer-v8/src/worker/global_scope/import_scripts.rs @@ -7,7 +7,7 @@ pub(super) struct WorkerImportScriptSource { resource: Option, } -pub(super) fn resolve_import_script_url( +fn resolve_import_script_url( state: Rc>, input: &str, ) -> Result { @@ -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>, 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>, 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), +} + +impl WorkerImportScriptError { + fn syntax(message: impl Into) -> Self { + Self::DomException { + name: "SyntaxError", + message: message.into(), + } + } + + fn network(message: impl Into) -> Self { + Self::DomException { + name: "NetworkError", + message: message.into(), + } + } + + fn error<'s>(scope: &mut v8::PinScope<'s, '_>, message: impl Into) -> 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, + muted_errors: bool, +} + +fn annotate_worker_exception_location<'s>( + scope: &mut v8::PinScope<'s, '_>, + exception: v8::Local<'s, v8::Value>, + message: Option>, +) { + let Ok(object) = v8::Local::::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, + ) +} diff --git a/moli-renderer-v8/src/worker/global_scope/messaging.rs b/moli-renderer-v8/src/worker/global_scope/messaging.rs new file mode 100644 index 0000000000..bed0c49d4a --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/messaging.rs @@ -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, +} + +pub(crate) fn worker_message_port_wake_sender( + scope: &mut v8::PinScope<'_, '_>, +) -> Option> { + Some(get_worker_state(scope)?.borrow().worker_wake_tx.clone()) +} + +pub(crate) fn worker_message_port_registry( + scope: &mut v8::PinScope<'_, '_>, +) -> Option { + Some( + get_worker_state(scope)? + .borrow() + .message_port_registry + .clone(), + ) +} + +pub(crate) fn worker_broadcast_channel_wake_sender( + scope: &mut v8::PinScope<'_, '_>, +) -> Option> { + Some(get_worker_state(scope)?.borrow().worker_wake_tx.clone()) +} + +pub(crate) fn worker_broadcast_channel_registry( + scope: &mut v8::PinScope<'_, '_>, +) -> Option { + Some( + get_worker_state(scope)? + .borrow() + .broadcast_channel_registry + .clone(), + ) +} + +pub(crate) fn worker_broadcast_channel_storage_key( + scope: &mut v8::PinScope<'_, '_>, +) -> Option { + 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> { + 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> { + 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>) { + let (registry, port_ids): (SharedMessagePortRegistry, Vec) = { + 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>, +) { + let (registry, channel_ids): (SharedBroadcastChannelRegistry, Vec) = { + 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(); + } +} diff --git a/moli-renderer-v8/src/worker/global_scope/mod.rs b/moli-renderer-v8/src/worker/global_scope/mod.rs index e82c45afc0..2050ece39a 100644 --- a/moli-renderer-v8/src/worker/global_scope/mod.rs +++ b/moli-renderer-v8/src/worker/global_scope/mod.rs @@ -1,15 +1,7 @@ -//! Bootstrap the `DedicatedWorkerGlobalScope` — the JS global environment -//! visible inside a Web Worker. +//! WorkerGlobalScope bindings for dedicated, shared and service workers. //! -//! Workers have no DOM access. The global exposes: -//! - `self` (the global itself) -//! - `postMessage(data)` — send to parent -//! - `onmessage` / `onmessageerror` and functional service worker handlers -//! - `close()` — request worker shutdown -//! - `console` (log/warn/error/info/profile/profileEnd) -//! - `setTimeout` / `clearTimeout` / `setInterval` / `clearInterval` -//! - `requestAnimationFrame` / `cancelAnimationFrame` -//! - `globalThis` +//! Initialization, runtime state and API implementations live in the child modules. +//! This module retains the existing worker-facing exports and shared imports. use crate::web_api_interfaces; use std::cell::RefCell; @@ -124,12 +116,25 @@ use crate::worker::abort::{ worker_abort_signal_id, worker_abort_signal_reason, worker_dom_exception_value, }; +mod async_tasks; +mod bootstrap; +mod console; mod content_security_policy; +mod event_handlers; +mod extendable_events; mod fetch; mod import_scripts; +mod messaging; +mod nested_workers; +mod network_state; +mod origin; +mod performance; mod service_worker_api; +mod service_worker_results; +mod state; mod timers; mod xhr; + use self::service_worker_api::*; pub(super) use self::service_worker_api::{ build_service_worker_client_object, build_service_worker_client_object_from_snapshot, @@ -149,4997 +154,96 @@ use import_scripts::*; use timers::*; pub(crate) use xhr::*; -pub(super) fn dispatch_worker_csp_violation_event<'s>( - scope: &mut v8::PinScope<'s, '_>, - loader: &crate::network::context::WorkerResourceLoader, - violation: &crate::content_security_policy::ContentSecurityPolicyUrlViolation, -) { - content_security_policy::dispatch_worker_content_security_policy_violation_event( - scope, loader, violation, - ); -} - -pub(super) const WORKER_GLOBAL_LISTENERS_SLOT: &str = "__moliWorkerGlobalListeners"; -pub(crate) const WORKER_STATE_SLOT: &str = "__workerState"; -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"; -pub(super) const WORKER_EXCEPTION_SOURCE_SLOT: &str = "__moliWorkerExceptionSource"; -pub(super) const WORKER_EXCEPTION_LINE_SLOT: &str = "__moliWorkerExceptionLine"; -pub(super) const WORKER_EXCEPTION_COLUMN_SLOT: &str = "__moliWorkerExceptionColumn"; -const SERVICE_WORKER_REGISTRATION_SCOPE_SLOT: &str = "__moliServiceWorkerRegistrationScope"; -const SERVICE_WORKER_REGISTRATION_ID_SLOT: &str = "__moliServiceWorkerRegistrationId"; -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"; -const SERVICE_WORKER_CLIENT_ID_SLOT: &str = "__lmServiceWorkerClientId"; -const WORKER_ORIGINAL_CONSOLE_SLOT: &str = "__moliWorkerOriginalConsole"; -const WORKER_GLOBAL_ORIGIN_SLOT: &str = "__moliWorkerGlobalOrigin"; - -#[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(WebApiObject)] -#[webapi(plain)] -struct WorkerGlobalOriginDeclaration { - #[webapi(slot = WORKER_GLOBAL_ORIGIN_SLOT)] - origin: String, -} - -#[derive(Default, WebApiObject)] -#[webapi(fragment, prototype = "WorkerGlobalScope", enumerable)] -struct WorkerGlobalOriginPrototypeDeclaration { - #[webapi( - accessor_property, - getter = worker_global_origin_getter, - setter = worker_global_origin_setter - )] - origin: (), -} - -#[derive(WebApiObject)] -#[webapi(plain)] -struct WorkerGlobalConsoleDeclaration<'scope> { - #[webapi(data_property)] - console: v8::Local<'scope, v8::Object>, -} - -#[derive(WebApiObject)] -#[webapi(plain)] -struct WorkerGlobalPerformanceDeclaration<'scope> { - #[webapi(data_property)] - performance: 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: (), -} - -#[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: (), -} - -#[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(fragment, prototype = "DedicatedWorkerGlobalScope", enumerable)] -struct DedicatedWorkerGlobalMethodsDeclaration { - #[webapi(method, callback = worker_close_callback, length = 0)] - close: (), -} - -#[derive(Default, WebApiObject)] -#[webapi(plain)] -struct DedicatedWorkerGlobalPostMessageDeclaration { - #[webapi(method = "postMessage", callback = worker_post_message_callback, length = 1)] - post_message: (), -} - -#[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 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(Default, WebApiObject)] -#[webapi(plain)] -struct WorkerGlobalCreateImageBitmapDeclaration { - #[webapi( - method = "createImageBitmap", - callback = worker_create_image_bitmap_callback, - length = 1 - )] - create_image_bitmap: (), -} - -#[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: (), -} - -#[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(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 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 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(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, -} - -#[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::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>, -} - -#[derive(WebApiObject)] -#[webapi(interface = web_api_interfaces::Client)] -pub(super) 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(super) 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: (), -} - -pub(super) struct PendingServiceWorkerClientQuery { - pub(super) resolver: v8::Global, - pub(super) query_type: PendingServiceWorkerClientQueryType, -} - -pub(super) struct PendingServiceWorkerClientNavigate { - pub(super) resolver: v8::Global, -} - -pub(super) struct PendingServiceWorkerClientFocus { - pub(super) resolver: v8::Global, -} - -pub(super) struct PendingServiceWorkerClientsOpenWindow { - pub(super) resolver: v8::Global, -} - -pub(super) struct PendingServiceWorkerShowNotification { - pub(super) resolver: v8::Global, -} - -pub(super) struct PendingServiceWorkerGetNotifications { - pub(super) resolver: v8::Global, -} - -pub(super) struct PendingServiceWorkerSyncRegistration { - pub(super) resolver: v8::Global, -} - -pub(super) struct PendingServiceWorkerSyncGetTags { - pub(super) resolver: v8::Global, -} - -pub(super) struct PendingServiceWorkerPeriodicSyncRegistration { - pub(super) resolver: v8::Global, -} - -pub(super) struct PendingServiceWorkerPeriodicSyncGetTags { - pub(super) resolver: v8::Global, -} - -pub(super) struct PendingServiceWorkerPeriodicSyncUnregistration { - pub(super) resolver: v8::Global, -} - -pub(super) struct PendingServiceWorkerPushSubscribe { - pub(super) resolver: v8::Global, -} - -#[derive(Default, webidl::WebIdlDictionary)] -#[webidl(prefix = "BackgroundSyncOptions")] -struct BackgroundSyncOptions { - #[webidl( - name = "minInterval", - converter = "enforce_range_unsigned_long_long", - default = 0 - )] - min_interval: u64, -} - -pub(super) struct PendingServiceWorkerPushGetSubscription { - pub(super) resolver: v8::Global, -} - -pub(super) struct PendingServiceWorkerPushUnsubscribe { - pub(super) resolver: v8::Global, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub(super) enum PendingServiceWorkerClientQueryType { - Get, - MatchAll, -} - -/// 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(super) struct PendingWorkerWebCryptoTask { - pub(super) resolver: v8::Global, -} - -/// 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, -} - -pub(super) struct PendingWorkerOpfsTask { - pub(super) locator: moli_storage_service::StorageBucketLocator, - pub(super) handle_access: Option, - pub(super) settlement: crate::opfs_owner_tasks::OpfsTaskSettlement, -} - -pub(super) struct WorkerOpfsOwnerState { - next_task_id: u64, - pending_tasks: HashMap, - 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(super) fn has_pending_tasks(&self) -> bool { - !self.pending_tasks.is_empty() - } -} - -pub(crate) struct WorkerOpfsCompletion { - pub(crate) task_id: u64, - pub(crate) result: OpfsTaskResult, -} - -pub(super) struct PendingWorkerFetch { - pub(super) resolver: v8::Global, - pub(super) document_url: Url, - pub(super) credentials_mode: RequestCredentialsMode, - pub(super) request_mode: moli_fetch::RequestMode, - pub(super) redirect_mode: RequestRedirectMode, - pub(super) request_priority: Option, - pub(super) request_metadata: crate::service_worker_runtime::ServiceWorkerFetchRequestMetadata, - pub(super) policy_context: crate::types::SubresourcePolicyContext, - pub(super) signal_id: Option, - pub(super) load: ResourceLoadLease, - pub(super) request_url: Url, - pub(super) request_method: String, - pub(super) request_headers: moli_fetch::RequestHeaders, - pub(super) request_body: Option, - pub(super) network_request_handle: Option, - pub(super) network_record: Option, - pub(super) paused_response: Option, - pub(super) streaming_body_source_id: Option, -} - -pub(super) enum WorkerFetchEvent { - Completion(Box), - StreamingStarted(WorkerFetchStreamingStarted), - StreamingChunk(WorkerFetchStreamingChunk), - StreamingFinished(WorkerFetchStreamingFinished), -} - -pub(super) struct WorkerFetchCompletion { - fetch_id: u32, - network_request_headers: Option>, - result: Result, -} - -pub(super) struct WorkerFetchStreamingStarted { - fetch_id: u32, - body_source_id: NetworkBodySourceId, - head: ResponseHead, - network_request_headers: Option>, -} - -pub(super) struct WorkerFetchStreamingChunk { - body_source_id: NetworkBodySourceId, - bytes: Vec, -} - -pub(super) struct WorkerFetchStreamingFinished { - fetch_id: u32, - body_source_id: NetworkBodySourceId, - head: ResponseHead, - result: Result, -} - -pub(super) enum WorkerFetchResponse { - Materialized(Box), - Streamed { - head: Box, - body: SubresourceResponseBody, - }, -} - -impl WorkerFetchResponse { - fn head(&self) -> ResponseHead { - match self { - Self::Materialized(response) => response.head(), - Self::Streamed { head, .. } => head.as_ref().clone(), - } - } - - fn subresource_response_body(&self) -> SubresourceResponseBody { - match self { - Self::Materialized(response) => SubresourceResponseBody::from_fetch_response(response), - Self::Streamed { body, .. } => body.clone(), - } - } - - 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 } - } - } - } -} - -enum WorkerFetchResponseParts { - Materialized { - head: ResponseHead, - body: Box, - }, - Subresource { - head: ResponseHead, - body: SubresourceResponseBody, - }, -} - -#[derive(Clone)] -pub(super) struct PendingWorkerFetchNetworkRecord { - pub(crate) redirect_headers: Option, - pub(super) internal_id: u64, - pub(super) url: Url, - pub(super) method: String, - pub(super) request_headers: moli_fetch::RequestHeaders, - pub(super) request_body: Option, - pub(super) initial_network_request_headers: Option>, - pub(super) intercept_response: bool, - pub(super) handle_auth_requests: bool, -} - -impl PendingWorkerFetchNetworkRecord { - 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(super) struct PendingWorkerXhr { - pub(super) xhr: v8::Global, - pub(super) document_url: Url, - pub(super) credentials_mode: RequestCredentialsMode, - pub(super) load: ResourceLoadLease, - pub(super) request_paused: bool, - pub(super) request_url: Url, - pub(super) request_method: String, - pub(super) request_headers: moli_fetch::RequestHeaders, - pub(super) request_body: Option, - pub(super) network_request_handle: Option, - pub(super) network_record: Option, - pub(super) paused_response: Option, -} - -pub(super) struct PendingWorkerCspReport { - pub(super) load: ResourceLoadLease, - pub(super) document_url: Url, - pub(super) request: Request, - pub(super) request_body: Option, - pub(super) policy_context: crate::types::SubresourcePolicyContext, - pub(super) service_worker_runtime: - Option, - pub(super) service_worker_client_id: - Option, -} - -pub(super) struct PausedWorkerSubresourceResponse { - pub(super) head: ResponseHead, - pub(super) body: SubresourceResponseBody, -} - -pub(super) struct WorkerXhrCompletion { - pub(super) xhr_id: u32, - pub(super) network_request_headers: Option>, - pub(super) result: Result, -} - -pub(super) enum WorkerXhrResponse { - Materialized(Box), - Streamed { - head: Box, - body: SubresourceResponseBody, - }, -} - -impl WorkerXhrResponse { - fn head(&self) -> ResponseHead { - match self { - Self::Materialized(response) => response.head(), - Self::Streamed { head, .. } => head.as_ref().clone(), - } - } - - fn subresource_response_body(&self) -> SubresourceResponseBody { - match self { - Self::Materialized(response) => SubresourceResponseBody::from_fetch_response(response), - Self::Streamed { body, .. } => body.clone(), - } - } - - 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(super) struct WorkerWebSocketState { - pub(super) wrapper: v8::Global, - pub(super) connection: WebSocketConnectionHandle, - pub(super) document_url: Url, - pub(super) url: Url, - pub(super) loader: crate::network::context::WorkerResourceLoader, - pub(super) load: Option, - pub(super) opened: bool, - pub(super) network_recorded: bool, -} - -pub(super) struct PendingServiceWorkerLifecycleEvent { - pub(super) completion: ServiceWorkerLifecycleCompletion, - pub(super) pending_wait_until_count: usize, - pub(super) dispatch_finished: bool, -} - -pub(super) struct PendingServiceWorkerMessageEvent { - pub(super) completion: ServiceWorkerMessageCompletion, - pub(super) pending_wait_until_count: usize, - pub(super) dispatch_finished: bool, - pub(super) window_interaction_allowed: bool, -} - -pub(super) struct PendingServiceWorkerNotificationEvent { - pub(super) completion: crate::runtime::ServiceWorkerNotificationCompletion, - pub(super) pending_wait_until_count: usize, - pub(super) dispatch_finished: bool, - pub(super) window_interaction_allowed: bool, -} - -pub(super) struct PendingServiceWorkerPushEvent { - pub(super) completion: crate::runtime::ServiceWorkerPushCompletion, - pub(super) pending_wait_until_count: usize, - pub(super) dispatch_finished: bool, -} - -pub(super) struct PendingServiceWorkerSyncEvent { - pub(super) completion: crate::runtime::ServiceWorkerSyncCompletion, - pub(super) pending_wait_until_count: usize, - pub(super) dispatch_finished: bool, -} - -pub(super) struct PendingServiceWorkerPeriodicSyncEvent { - pub(super) completion: crate::runtime::ServiceWorkerPeriodicSyncCompletion, - pub(super) pending_wait_until_count: usize, - pub(super) dispatch_finished: bool, -} - -pub(super) struct PendingServiceWorkerFetchEvent { - pub(super) completion: ServiceWorkerFetchCompletion, - pub(super) handled_resolver: Option>, - pub(super) request_signal_id: Option, - pub(super) request_mode: RequestMode, - pub(super) request_destination: ServiceWorkerRequestDestination, - pub(super) pending_respond_with_response: Option, - pub(super) pending_respond_with_stream_body_source_id: Option, - pub(super) pending_respond_with_stream_cancel_handle: Option>, - pub(super) respond_with_called: bool, - pub(super) pending_respond_with: bool, - pub(super) pending_wait_until_count: usize, - pub(super) dispatch_finished: bool, -} - -impl PendingServiceWorkerFetchEvent { - pub(super) 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(super) struct PendingServiceWorkerNavigationPreload { - pub(super) owner: crate::service_worker_runtime::ServiceWorkerRunOwner, - pub(super) _promise: v8::Global, - pub(super) resolver: Option>, - pub(super) body_source_id: Option, -} - -enum WorkerImportScriptError { - DomException { name: &'static str, message: String }, - Exception(v8::Global), -} - -impl WorkerImportScriptError { - fn syntax(message: impl Into) -> Self { - Self::DomException { - name: "SyntaxError", - message: message.into(), - } - } - - fn network(message: impl Into) -> Self { - Self::DomException { - name: "NetworkError", - message: message.into(), - } - } - - fn error<'s>(scope: &mut v8::PinScope<'s, '_>, message: impl Into) -> 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, - muted_errors: bool, -} - -fn annotate_worker_exception_location<'s>( - scope: &mut v8::PinScope<'s, '_>, - exception: v8::Local<'s, v8::Value>, - message: Option>, -) { - let Ok(object) = v8::Local::::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); - } -} - -/// Mutable state accessible from V8 callbacks inside the worker isolate. -pub(super) struct WorkerMessagePortWrapperEntry { - wrapper: v8::Global, -} - -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(super) parent_tx: mpsc::UnboundedSender, - /// Internal wake channel used by worker-owned async runtime surfaces. - pub(crate) worker_wake_tx: mpsc::UnboundedSender, - /// 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, - /// Whether `close()` has been called. - pub(super) closed: bool, - /// HTML's per-global guard against recursively reporting an error event. - pub(super) in_error_reporting_mode: bool, - /// Timer id counter. - pub(super) 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(super) loader: crate::network::context::WorkerResourceLoader, - /// Whether this runtime is a dedicated or shared worker global. - pub(super) global_kind: super::thread::WorkerGlobalKind, - /// Whether this worker was constructed as a classic or module worker. - pub(super) script_kind: super::thread::WorkerScriptKind, - /// Base URL used to resolve relative worker script fetches. - pub(super) current_script_url: Option, - /// Referrer policy parsed from the top-level worker script response. - pub(super) referrer_policy: Option, - /// CSP policies from outside settings used for module static imports. - pub(super) module_static_import_content_security_policies: Vec, - /// Enforce CSP policies parsed from the top-level worker script response. - pub(super) content_security_policies: Vec, - /// Report-only CSP policies parsed from the top-level worker script response. - pub(super) content_security_report_only_policies: Vec, - /// Reporting API endpoints parsed from the top-level worker script response. - pub(super) content_security_reporting_endpoints: ContentSecurityPolicyReportingEndpoints, - /// Whether this worker global is a secure context for `[SecureContext]` APIs. - pub(super) secure_context: bool, - /// Network.setExtraHTTPHeaders headers inherited from the owning page/CDP session. - pub(super) extra_http_headers: moli_fetch::RequestHeaders, - /// Permission overrides inherited from the owning page/CDP session. - pub(super) permission_overrides: Vec, - /// Network.emulateNetworkConditions offline state inherited from the owning page/CDP session. - pub(super) network_offline: bool, - /// Network.setBlockedURLs patterns inherited from the owning page/CDP session. - pub(super) blocked_url_patterns: Vec, - /// Current document network/cache partition key inherited by worker-owned requests. - pub(super) network_partition_key: Option, - /// COEP/DIP policy container state inherited by worker-owned subresource requests. - pub(super) policy_context: crate::types::SubresourcePolicyContext, - /// Fetch domain subresource interception inherited from the owning page/CDP session. - pub(super) fetch_subresource_interception_enabled: bool, - pub(super) fetch_subresource_interception_resource_type: Option, - /// Async fetch completions routed back onto the worker event loop. - pub(super) fetch_completion_tx: mpsc::UnboundedSender, - /// Promise resolvers pending async fetch completion. - pub(super) pending_fetches: HashMap, - /// Worker-local pending body sources for headers-first worker fetch. - pub(crate) pending_network_body_sources: - HashMap, - pub(crate) pending_network_body_clones: HashMap>, - /// Fetch id counter. - pub(super) next_fetch_id: u32, - /// Async XHR completions routed back onto the worker event loop. - pub(super) xhr_completion_tx: mpsc::UnboundedSender, - /// In-flight worker XHR requests keyed by internal id. - pub(super) pending_xhrs: HashMap, - /// Worker XHR id counter. - pub(super) next_xhr_id: u32, - /// Worker-owned CSP report requests paused for Fetch domain request-stage interception. - pub(super) pending_csp_reports: HashMap, - /// 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(super) abort: Rc>, - /// Worker-thread view of the creator's browser-context/partition runtime. - pub(super) worker_context_runtime: crate::runtime::RendererWorkerContextRuntime, - /// Browser-context Service Worker owner inherited by real worker clients. - pub(super) service_worker_runtime: - Option, - /// Live Service Worker client id for DedicatedWorker/SharedWorker globals. - pub(super) service_worker_client_id: - Option, - /// Owner-scoped MessagePort state shared with page and nested worker contexts. - pub(super) message_port_registry: SharedMessagePortRegistry, - /// Worker-owned MessagePort wrappers keyed by runtime port id. - pub(super) message_port_wrappers: HashMap, - /// SharedWorker connect-event ports whose lifetime is owned by the shared worker runtime. - pub(super) shared_worker_connection_ports: HashSet, - /// Worker-owned BroadcastChannel wrappers keyed by runtime channel id. - pub(super) broadcast_channel_registry: SharedBroadcastChannelRegistry, - pub(super) broadcast_channel_storage_key: MoliStorageKey, - pub(super) storage_key: MoliStorageKey, - pub(super) broadcast_channel_wrappers: HashMap>, - /// IndexedDB storage manager inherited from the owning browser context. - pub(super) indexed_db_manager: Option, - /// Storage Buckets registry inherited from the owning browser context. - pub(super) storage_bucket_store: Option, - /// Async WebSocket transport events routed back onto this worker loop. - pub(super) websocket_event_tx: tokio::sync::mpsc::Sender, - /// Worker-owned classic WebSocket connections keyed by socket id. - pub(super) websockets: HashMap, - /// Worker-local WebSocket id counter. - pub(super) next_websocket_id: u64, - /// Worker-local dedicated workers created through `new Worker(...)`. - pub(super) next_nested_worker_id: u64, - pub(super) nested_worker_wrappers: HashMap>, - /// Heavyweight WebCrypto completions routed back onto the worker event loop. - pub(super) webcrypto_completion_tx: mpsc::UnboundedSender, - /// In-flight worker WebCrypto blocking tasks keyed by task id. - pub(super) pending_webcrypto: HashMap, - /// Worker WebCrypto task id counter (never zero so 0 can mean "unset"). - pub(super) next_webcrypto_task_id: u64, - /// OPFS completions routed from the partition-owned storage IO sequence. - pub(super) opfs_completion_tx: mpsc::UnboundedSender, - pub(super) opfs_owner_state: Option, - /// In-flight Service Worker `periodicsync` events keyed by runtime event id. - pub(super) pending_service_worker_periodic_sync_events: - HashMap, - /// In-flight Service Worker `clients.get()` / `clients.matchAll()` queries keyed by request id. - pub(super) pending_service_worker_client_queries: HashMap, - /// In-flight Service Worker `WindowClient.navigate()` requests keyed by request id. - pub(super) pending_service_worker_client_navigates: - HashMap, - /// In-flight Service Worker `WindowClient.focus()` requests keyed by request id. - pub(super) pending_service_worker_client_focuses: HashMap, - /// In-flight Service Worker `clients.openWindow()` requests keyed by request id. - pub(super) pending_service_worker_clients_open_windows: - HashMap, - /// In-flight Service Worker `registration.showNotification()` requests keyed by request id. - pub(super) pending_service_worker_show_notifications: - HashMap, - /// In-flight Service Worker `registration.getNotifications()` requests keyed by request id. - pub(super) pending_service_worker_get_notifications: - HashMap, - /// In-flight Service Worker `registration.sync.register()` requests keyed by request id. - pub(super) pending_service_worker_sync_registrations: - HashMap, - /// In-flight Service Worker `registration.sync.getTags()` requests keyed by request id. - pub(super) pending_service_worker_sync_get_tags: HashMap, - /// In-flight Service Worker `registration.periodicSync.register()` requests keyed by request id. - pub(super) pending_service_worker_periodic_sync_registrations: - HashMap, - /// In-flight Service Worker `registration.periodicSync.getTags()` requests keyed by request id. - pub(super) pending_service_worker_periodic_sync_get_tags: - HashMap, - /// In-flight Service Worker `registration.periodicSync.unregister()` requests keyed by request id. - pub(super) pending_service_worker_periodic_sync_unregistrations: - HashMap, - /// In-flight Service Worker `registration.pushManager.subscribe()` requests keyed by request id. - pub(super) pending_service_worker_push_subscriptions: - HashMap, - /// In-flight Service Worker `registration.pushManager.getSubscription()` requests keyed by request id. - pub(super) pending_service_worker_push_get_subscriptions: - HashMap, - /// In-flight Service Worker `PushSubscription.unsubscribe()` requests keyed by request id. - pub(super) pending_service_worker_push_unsubscriptions: - HashMap, - pub(super) service_worker_client_query_request_ids: WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_client_navigate_request_ids: WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_client_focus_request_ids: WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_clients_open_window_request_ids: - WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_show_notification_request_ids: WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_get_notifications_request_ids: WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_sync_registration_request_ids: WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_sync_get_tags_request_ids: WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_periodic_sync_registration_request_ids: - WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_periodic_sync_get_tags_request_ids: - WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_periodic_sync_unregistration_request_ids: - WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_push_subscription_request_ids: WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_push_get_subscription_request_ids: - WorkerServiceWorkerRequestIdAllocator, - pub(super) service_worker_push_unsubscription_request_ids: - WorkerServiceWorkerRequestIdAllocator, - /// Count of active Service Worker events that allow window interaction. - pub(super) service_worker_window_interaction_allowed_count: usize, - /// Service Worker lifecycle events currently waiting for dispatch and - /// `ExtendableEvent.waitUntil()` promises to finish. - pub(super) pending_service_worker_lifecycle_events: - HashMap, - /// Service Worker fetch events currently waiting for dispatch, `respondWith()`, - /// and minimal `waitUntil()` promises to finish. - pub(super) pending_service_worker_fetch_events: - HashMap, - /// Navigation preload promises and response bodies stay alive until preload complete/error. - pub(super) pending_service_worker_navigation_preloads: - HashMap, - /// Service Worker message events currently waiting for dispatch and - /// `ExtendableEvent.waitUntil()` promises to finish. - pub(super) pending_service_worker_message_events: - HashMap, - /// Service Worker notification events currently waiting for dispatch and - /// `ExtendableEvent.waitUntil()` promises to finish. - pub(super) pending_service_worker_notification_events: - HashMap, - /// Service Worker push events currently waiting for dispatch and - /// `ExtendableEvent.waitUntil()` promises to finish. - pub(super) pending_service_worker_push_events: - HashMap, - /// Service Worker sync events currently waiting for dispatch and - /// `ExtendableEvent.waitUntil()` promises to finish. - pub(super) pending_service_worker_sync_events: - HashMap, -} - -/// 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(super) 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 - } -} - -#[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(); - } -} - -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(super) fn register_pending_webcrypto_task( - &mut self, - resolver: v8::Global, - ) -> (u64, mpsc::UnboundedSender) { - 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(super) fn take_pending_webcrypto_task( - &mut self, - task_id: u64, - ) -> Option { - self.pending_webcrypto.remove(&task_id) - } - - pub(super) fn register_pending_opfs_task( - &mut self, - locator: moli_storage_service::StorageBucketLocator, - handle_access: Option, - settlement: crate::opfs_owner_tasks::OpfsTaskSettlement, - ) -> (u64, mpsc::UnboundedSender) { - 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(super) fn take_pending_opfs_task(&mut self, task_id: u64) -> Option { - self.opfs_owner_state - .as_mut()? - .pending_tasks - .remove(&task_id) - } - - pub(super) fn register_pending_service_worker_client_query( - &mut self, - resolver: v8::Global, - 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(super) fn take_pending_service_worker_client_query( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_client_queries - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_client_navigate( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_client_navigate( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_client_navigates - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_client_focus( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_client_focus( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_client_focuses - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_clients_open_window( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_clients_open_window( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_clients_open_windows - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_show_notification( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_show_notification( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_show_notifications - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_get_notifications( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_get_notifications( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_get_notifications - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_sync_registration( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_sync_registration( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_sync_registrations - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_sync_get_tags( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_sync_get_tags( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_sync_get_tags - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_periodic_sync_registration( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_periodic_sync_registration( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_periodic_sync_registrations - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_periodic_sync_get_tags( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_periodic_sync_get_tags( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_periodic_sync_get_tags - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_periodic_sync_unregistration( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_periodic_sync_unregistration( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_periodic_sync_unregistrations - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_push_subscribe( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_push_subscribe( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_push_subscriptions - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_push_get_subscription( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_push_get_subscription( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_push_get_subscriptions - .remove(&request_id) - } - - pub(super) fn register_pending_service_worker_push_unsubscribe( - &mut self, - resolver: v8::Global, - ) -> 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(super) fn take_pending_service_worker_push_unsubscribe( - &mut self, - request_id: u64, - ) -> Option { - self.pending_service_worker_push_unsubscriptions - .remove(&request_id) - } - - pub(super) 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 - } -} - -/// 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)> { - 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, -) -> Option<(u64, mpsc::UnboundedSender)> { - 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, - handle_access: Option, -) -> Option<(u64, mpsc::UnboundedSender)> { - 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, -) -> Option<(u64, mpsc::UnboundedSender)> { - 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 { - 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 { - 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 { - 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 { - 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(super) fn drain_worker_webcrypto_completion( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_worker_opfs_completion( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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, - ®istry, - iterator_id, - &pending.locator, - handle_access.as_ref(), - completion.result, - ); - drop(keep_alive); - } - } -} - -pub(super) fn drain_service_worker_client_query_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_client_navigate_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_client_focus_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_clients_open_window_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_show_notification_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_get_notifications_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_sync_registration_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_sync_get_tags_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_periodic_sync_registration_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_periodic_sync_get_tags_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_periodic_sync_unregistration_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_push_subscribe_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_push_get_subscription_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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(super) fn drain_service_worker_push_unsubscribe_result( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - 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); - } - } -} - -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, - pub(crate) service_worker_client_id: - Option, - pub(crate) storage_key_top_level_site: String, - pub(crate) creator_storage_key: MoliStorageKey, - pub(crate) indexed_db_manager: Option, - pub(crate) storage_bucket_store: Option, - pub(crate) module_static_import_content_security_policies: Vec, - pub(crate) require_trusted_types_for_script: bool, - pub(crate) network_policy: super::handle::WorkerNetworkPolicy, - pub(crate) policy_context: crate::types::SubresourcePolicyContext, - pub(crate) wake_tx: mpsc::UnboundedSender, -} - -pub(crate) fn reserve_nested_worker_context( - scope: &mut v8::PinScope<'_, '_>, - worker: v8::Local<'_, v8::Object>, -) -> Option { - 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: super::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(crate) fn worker_service_worker_control_state( - scope: &mut v8::PinScope<'_, '_>, -) -> Option { - 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) -} - -pub(super) struct NestedWorkerUnhandledError { - pub(super) message: String, - pub(super) filename: String, - pub(super) lineno: u32, - pub(super) colno: u32, - pub(super) event_kind: super::handle::WorkerParentErrorEventKind, -} - -pub(super) struct NestedWorkerDispatchResult { - pub(super) dispatched: bool, - pub(super) unhandled_error: Option, -} - -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(super) fn dispatch_nested_worker_event( - scope: &mut v8::PinScope<'_, '_>, - state: &Rc>, - worker_id: DedicatedWorkerId, - message: &super::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 { - super::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, - } - } - super::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 == super::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, - }, - } -} - -/// 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>` -/// outlives the V8 context. -pub(super) fn install_worker_global_scope<'s>( - scope: &mut v8::PinScope<'s, '_>, - global: v8::Local<'s, v8::Object>, - state: Rc>, - 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) }; - 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, super::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 { - super::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}"))?; - } - super::thread::WorkerGlobalKind::Shared { name, .. } => { - set_worker_global_name_prop(scope, global, name)?; - } - super::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 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 => {} - } -} - -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_global_event_handler_accessors<'s>( - scope: &mut v8::PinScope<'s, '_>, - global: v8::Local<'s, v8::Object>, - global_kind: &super::thread::WorkerGlobalKind, -) -> Result<()> { - if matches!( - global_kind, - super::thread::WorkerGlobalKind::Dedicated { .. } - ) { - DedicatedWorkerGlobalEventHandlersDeclaration::default().initialize(scope, global)?; - DedicatedWorkerGlobalEventHandlerStateDeclaration::default().initialize(scope, global)?; - } - if matches!(global_kind, super::thread::WorkerGlobalKind::Shared { .. }) { - SharedWorkerGlobalEventHandlersDeclaration::default().initialize(scope, global)?; - SharedWorkerGlobalEventHandlerStateDeclaration::default().initialize(scope, global)?; - } - if matches!(global_kind, super::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"), - ); -} - -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(super) struct PreparedWorkerGlobalScopeTemplates { - event_target: v8::Global, - worker: v8::Global, - specific_worker: v8::Global, -} - -impl PreparedWorkerGlobalScopeTemplates { - pub(super) 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: &super::thread::WorkerGlobalKind, -) -> ( - &'static str, - crate::context_bootstrap::exposed_interfaces::RealmKind, -) { - use crate::context_bootstrap::exposed_interfaces::RealmKind; - match global_kind { - super::thread::WorkerGlobalKind::Dedicated { .. } => { - ("DedicatedWorkerGlobalScope", RealmKind::DedicatedWorker) - } - super::thread::WorkerGlobalKind::Shared { .. } => { - ("SharedWorkerGlobalScope", RealmKind::SharedWorker) - } - super::thread::WorkerGlobalKind::Service { .. } => { - ("ServiceWorkerGlobalScope", RealmKind::ServiceWorker) - } - } -} - -pub(super) fn prepare_worker_global_scope_templates<'s>( - scope: &mut v8::PinScope<'s, '_, ()>, - global_kind: &super::thread::WorkerGlobalKind, -) -> Result { - 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: &super::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 { - super::thread::WorkerGlobalKind::Dedicated { .. } => { - DedicatedWorkerGlobalMethodsDeclaration::default().initialize(scope, specific_proto)?; - DedicatedWorkerGlobalScopeConstructorGlobalDeclaration::new(specific_ctor) - .initialize(scope, global)?; - } - super::thread::WorkerGlobalKind::Shared { .. } => { - SharedWorkerGlobalMethodsDeclaration::default().initialize(scope, specific_proto)?; - SharedWorkerGlobalScopeConstructorGlobalDeclaration::new(specific_ctor) - .initialize(scope, global)?; - } - super::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(()) -} - -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 { - if args.length() == 0 { - throw_type_error( - scope, - &format!("Failed to construct '{constructor_name}': 1 argument required."), - ); - return None; - } - webidl::argument::( - 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> { - if args.length() <= 1 { - return None; - } - let value = args.get(1); - if value.is_null_or_undefined() || !value.is_object() { - None - } else { - v8::Local::::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>, - prefix: &'static str, - key: &'static str, -) -> Option { - 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::(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>, -) -> Option> { - 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>, - key: &'static str, - default: &'static str, -) -> Option { - 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::( - 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>, -) -> Option> { - 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::::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>, -) -> Option> { - 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::::try_from(value) else { - throw_type_error( - scope, - "Failed to construct 'ExtendableMessageEvent': member ports is not a sequence.", - ); - 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::::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); -} - -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> { - 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) -} - -fn constructor_prototype<'s>( - scope: &mut v8::PinScope<'s, '_>, - constructor: v8::Local<'s, v8::Function>, - name: &'static str, -) -> Result> { - let prototype_key = v8str(scope, "prototype"); - let prototype = constructor - .get(scope, prototype_key.into()) - .and_then(|value| v8::Local::::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) -} - -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."); -} - -/// Retrieve the `WorkerGlobalState` from a callback scope. -pub(crate) fn get_worker_state<'s>( - scope: &mut v8::PinScope<'s, '_>, -) -> Option>> { - let global = scope.get_current_context().global(scope); - let val = get_private_value(scope, global, WORKER_STATE_SLOT)?; - let external = v8::Local::::try_from(val).ok()?; - let ptr = external.value() as *const RefCell; - // 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(super) 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::(); - 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() -} - -pub(crate) fn worker_message_port_wake_sender( - scope: &mut v8::PinScope<'_, '_>, -) -> Option> { - Some(get_worker_state(scope)?.borrow().worker_wake_tx.clone()) -} - -pub(crate) fn worker_message_port_registry( - scope: &mut v8::PinScope<'_, '_>, -) -> Option { - Some( - get_worker_state(scope)? - .borrow() - .message_port_registry - .clone(), - ) -} - -pub(crate) fn worker_broadcast_channel_wake_sender( - scope: &mut v8::PinScope<'_, '_>, -) -> Option> { - Some(get_worker_state(scope)?.borrow().worker_wake_tx.clone()) -} - -pub(crate) fn worker_broadcast_channel_registry( - scope: &mut v8::PinScope<'_, '_>, -) -> Option { - Some( - get_worker_state(scope)? - .borrow() - .broadcast_channel_registry - .clone(), - ) -} - -pub(crate) fn worker_broadcast_channel_storage_key( - scope: &mut v8::PinScope<'_, '_>, -) -> Option { - Some( - get_worker_state(scope)? - .borrow() - .broadcast_channel_storage_key - .clone(), - ) -} - -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 { - Some(get_worker_state(scope)?.borrow().storage_key.clone()) -} - -pub(crate) fn worker_storage_partition_identity( - scope: &mut v8::PinScope<'_, '_>, -) -> Option { - Some( - get_worker_state(scope)? - .borrow() - .worker_context_runtime - .storage_partition_identity(), - ) -} - -pub(crate) fn worker_current_script_url(scope: &mut v8::PinScope<'_, '_>) -> Option { - get_worker_state(scope)?.borrow().current_script_url.clone() -} - -pub(crate) fn worker_global_origin( - scope: &mut v8::PinScope<'_, '_>, -) -> Option { - let global = scope.get_current_context().global(scope); - let origin = get_private_value(scope, global, WORKER_GLOBAL_ORIGIN_SLOT)?; - let origin = v8::Local::::try_from(origin).ok()?; - Some(moli_url::WebOrigin::from_serialized( - &origin.to_rust_string_lossy(scope), - )) -} - -pub(crate) fn worker_allows_trusted_type_policy_name( - scope: &mut v8::PinScope<'_, '_>, - name: &str, -) -> Option { - 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 { - 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 { - 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; - }; - content_security_policy::dispatch_worker_trusted_types_sink_violation_event_for_state( - scope, &state, sink, sample, - ); -} - -pub(crate) fn worker_exception_report_target( - scope: &mut v8::PinScope<'_, '_>, -) -> Option<(mpsc::UnboundedSender, 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, - super::thread::WorkerGlobalKind::Shared { .. } - ) - }) -} - -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> { - 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> { - 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(super) fn close_worker_owned_message_ports(state: &Rc>) { - let (registry, port_ids): (SharedMessagePortRegistry, Vec) = { - 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(super) fn close_worker_owned_broadcast_channels(state: &Rc>) { - let (registry, channel_ids): (SharedBroadcastChannelRegistry, Vec) = { - 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 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 -} - -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 -} - -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 -} - -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)) -} - -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 -} - -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)); -} - -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(); - } -} - -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 == super::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; - } - } -} - -// ─── close ────────────────────────────────────────────────────────────────── - -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); - } -} - -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 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) -} - -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 -} - -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 { - 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::::try_from(value).ok()) - else { - return; - }; - let Some(method) = original_console - .get(scope, v8str(scope, method_name).into()) - .and_then(|value| v8::Local::::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); -} - -// ─── timers (minimal stubs) ───────────────────────────────────────────────── - -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, - ) -} - -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}`")) -} - -// ─── timer info (used by the event loop in thread.rs) ─────────────────────── - -#[derive(Clone, Default)] -pub(super) struct WorkerIsolateTimerQueues { - pending: Rc>>, - cancelled: Rc>>, -} - -impl WorkerIsolateTimerQueues { - pub(super) fn push_pending(&self, timer: TimerInfo) { - self.pending.borrow_mut().push(timer); - } - - pub(super) 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(super) fn cancel_active(&self, timer_id: u32) { - self.cancelled.borrow_mut().push(timer_id); - } - - pub(super) fn drain_pending(&self) -> Vec { - self.pending.borrow_mut().drain(..).collect() - } - - pub(super) fn drain_cancelled(&self) -> Vec { - self.cancelled.borrow_mut().drain(..).collect() - } -} - -pub(super) fn worker_isolate_timer_queues( - scope: &mut v8::PinScope<'_, '_>, -) -> Option { - scope.get_slot::().cloned() -} - -pub(super) struct TimerInfo { - pub(super) id: u32, - pub(super) callback: super::timer_callback::WorkerTimerCallback, - pub(super) delay_ms: u64, - pub(super) is_interval: bool, - pub(super) extra_args: Vec>, -} - -pub(crate) fn worker_allows_eval_code_generation_by_csp( - scope: &mut v8::PinScope<'_, '_>, - allow_trusted_types_eval: bool, - source: Option<&str>, -) -> Option { - 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 { - 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(), - content_security_policy::worker_compilation_content_security_policy_violations( - &state, - protected_url, - kind, - source, - crate::content_security_policy::ContentSecurityPolicyDisposition::Report, - ), - content_security_policy::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( - super::handle::WorkerMessage::DispatchContentSecurityPolicyViolation(Box::new( - violation, - )), - ); - } - Some(allowed) -} - -pub(super) fn dispatch_worker_csp_violation_event_for_state<'s>( - scope: &mut v8::PinScope<'s, '_>, - state: &Rc>, - violation: &crate::content_security_policy::ContentSecurityPolicyUrlViolation, -) { - content_security_policy::dispatch_worker_content_security_policy_violation_event_for_state( - scope, state, violation, - ); -} +pub(super) use async_tasks::{ + PendingWorkerWebCryptoTask, WorkerOpfsOwnerState, drain_worker_opfs_completion, + drain_worker_webcrypto_completion, +}; +pub(crate) use async_tasks::{ + WorkerOpfsCompletion, WorkerWebCryptoCompletion, cancel_worker_opfs_task, + ensure_worker_opfs_directory_iterator_registry, ensure_worker_opfs_handle_registry, + register_worker_opfs_iterator_task, register_worker_opfs_move_task, register_worker_opfs_task, + register_worker_webcrypto_task, worker_opfs_directory_iterator_registry, + worker_opfs_handle_registry, +}; +use bootstrap::{ + constructor_prototype, ensure_worker_interface_constructor, set_worker_to_string_tag, +}; +pub(super) use bootstrap::{install_worker_global_scope, prepare_worker_global_scope_templates}; +use console::install_console; +pub(super) use content_security_policy::{ + dispatch_worker_csp_violation_event, dispatch_worker_csp_violation_event_for_state, +}; +pub(crate) use content_security_policy::{ + dispatch_worker_trusted_types_sink_violation_event, worker_allows_eval_code_generation_by_csp, + worker_allows_trusted_type_policy_name, worker_allows_trusted_types_eval, + worker_requires_trusted_types_for_script, +}; +use event_handlers::install_worker_global_event_handler_accessors; +pub(super) use event_handlers::{WORKER_GLOBAL_LISTENERS_SLOT, service_worker_fetch_handler_type}; +use extendable_events::install_service_worker_extendable_event_constructors; +pub(super) use extendable_events::{ + PendingServiceWorkerFetchEvent, PendingServiceWorkerLifecycleEvent, + PendingServiceWorkerMessageEvent, PendingServiceWorkerNavigationPreload, + PendingServiceWorkerNotificationEvent, PendingServiceWorkerPeriodicSyncEvent, + PendingServiceWorkerPushEvent, PendingServiceWorkerSyncEvent, +}; +pub(super) use import_scripts::{ + WORKER_EXCEPTION_COLUMN_SLOT, WORKER_EXCEPTION_LINE_SLOT, WORKER_EXCEPTION_SOURCE_SLOT, +}; +use messaging::{DedicatedWorkerGlobalPostMessageDeclaration, worker_structured_clone_callback}; +pub(super) use messaging::{ + WorkerMessagePortWrapperEntry, close_worker_owned_broadcast_channels, + close_worker_owned_message_ports, +}; +pub(crate) use messaging::{ + forget_worker_broadcast_channel_wrapper, forget_worker_message_port_wrapper, + register_shared_worker_connection_port, register_worker_broadcast_channel_wrapper, + register_worker_message_port_wrapper, worker_broadcast_channel_registry, + worker_broadcast_channel_storage_key, worker_broadcast_channel_wake_sender, + worker_broadcast_channel_wrapper, worker_message_port_registry, + worker_message_port_wake_sender, worker_message_port_wrapper, +}; +pub(super) use nested_workers::dispatch_nested_worker_event; +pub(crate) use nested_workers::{ + NestedWorkerContext, forget_nested_worker_context, reserve_nested_worker_context, +}; +pub(super) use network_state::{ + PausedWorkerSubresourceResponse, PendingWorkerCspReport, PendingWorkerFetch, + PendingWorkerFetchNetworkRecord, PendingWorkerXhr, WorkerFetchCompletion, WorkerFetchEvent, + WorkerFetchResponse, WorkerFetchStreamingChunk, WorkerFetchStreamingFinished, + WorkerFetchStreamingStarted, WorkerWebSocketState, WorkerXhrCompletion, WorkerXhrResponse, +}; +use network_state::{ + WorkerFetchResponseParts, merge_worker_request_headers, next_fetch_id, next_websocket_id, + next_xhr_id, worker_url_blocked, +}; +pub(crate) use origin::worker_global_origin; +use origin::{WorkerGlobalOriginDeclaration, WorkerGlobalOriginPrototypeDeclaration}; +use performance::{install_worker_performance, monotonic_unix_epoch_millis}; +pub(super) use service_worker_results::{ + PendingServiceWorkerClientFocus, PendingServiceWorkerClientNavigate, + PendingServiceWorkerClientQuery, PendingServiceWorkerClientQueryType, + PendingServiceWorkerClientsOpenWindow, PendingServiceWorkerGetNotifications, + PendingServiceWorkerPeriodicSyncGetTags, PendingServiceWorkerPeriodicSyncRegistration, + PendingServiceWorkerPeriodicSyncUnregistration, PendingServiceWorkerPushGetSubscription, + PendingServiceWorkerPushSubscribe, PendingServiceWorkerPushUnsubscribe, + PendingServiceWorkerShowNotification, PendingServiceWorkerSyncGetTags, + PendingServiceWorkerSyncRegistration, WorkerServiceWorkerRequestIdAllocator, + drain_service_worker_client_focus_result, drain_service_worker_client_navigate_result, + drain_service_worker_client_query_result, drain_service_worker_clients_open_window_result, + drain_service_worker_get_notifications_result, + drain_service_worker_periodic_sync_get_tags_result, + drain_service_worker_periodic_sync_registration_result, + drain_service_worker_periodic_sync_unregistration_result, + drain_service_worker_push_get_subscription_result, drain_service_worker_push_subscribe_result, + drain_service_worker_push_unsubscribe_result, drain_service_worker_show_notification_result, + drain_service_worker_sync_get_tags_result, drain_service_worker_sync_registration_result, +}; +use state::worker_close_callback; +pub(crate) use state::{ + WORKER_STATE_SLOT, WorkerGlobalState, get_worker_state, worker_current_script_url, + worker_exception_report_target, worker_global_is_closed, worker_service_worker_control_state, + worker_storage_key, worker_storage_partition_identity, worker_termination_requested, + worker_uses_shared_worker_agent_cluster, +}; +pub(super) use timers::{TimerInfo, WorkerIsolateTimerQueues, worker_isolate_timer_queues}; diff --git a/moli-renderer-v8/src/worker/global_scope/nested_workers.rs b/moli-renderer-v8/src/worker/global_scope/nested_workers.rs new file mode 100644 index 0000000000..f581efe1eb --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/nested_workers.rs @@ -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, + pub(crate) service_worker_client_id: + Option, + pub(crate) storage_key_top_level_site: String, + pub(crate) creator_storage_key: MoliStorageKey, + pub(crate) indexed_db_manager: Option, + pub(crate) storage_bucket_store: Option, + pub(crate) module_static_import_content_security_policies: Vec, + 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, +} + +pub(crate) fn reserve_nested_worker_context( + scope: &mut v8::PinScope<'_, '_>, + worker: v8::Local<'_, v8::Object>, +) -> Option { + 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, +} + +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>, + 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, + }, + } +} diff --git a/moli-renderer-v8/src/worker/global_scope/network_state.rs b/moli-renderer-v8/src/worker/global_scope/network_state.rs new file mode 100644 index 0000000000..0eab8e2c57 --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/network_state.rs @@ -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, + 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, + 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, + 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, + pub(in crate::worker) network_request_handle: Option, + pub(in crate::worker) network_record: Option, + pub(in crate::worker) paused_response: Option, + pub(in crate::worker) streaming_body_source_id: Option, +} + +pub(in crate::worker) enum WorkerFetchEvent { + Completion(Box), + StreamingStarted(WorkerFetchStreamingStarted), + StreamingChunk(WorkerFetchStreamingChunk), + StreamingFinished(WorkerFetchStreamingFinished), +} + +pub(in crate::worker) struct WorkerFetchCompletion { + pub(super) fetch_id: u32, + pub(super) network_request_headers: Option>, + pub(super) result: Result, +} + +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>, +} + +pub(in crate::worker) struct WorkerFetchStreamingChunk { + pub(super) body_source_id: NetworkBodySourceId, + pub(super) bytes: Vec, +} + +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, +} + +pub(in crate::worker) enum WorkerFetchResponse { + Materialized(Box), + Streamed { + head: Box, + 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, + }, + Subresource { + head: ResponseHead, + body: SubresourceResponseBody, + }, +} + +#[derive(Clone)] +pub(in crate::worker) struct PendingWorkerFetchNetworkRecord { + pub(crate) redirect_headers: Option, + 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, + pub(in crate::worker) initial_network_request_headers: Option>, + 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, + 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, + pub(in crate::worker) network_request_handle: Option, + pub(in crate::worker) network_record: Option, + pub(in crate::worker) paused_response: Option, +} + +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, + pub(in crate::worker) policy_context: crate::types::SubresourcePolicyContext, + pub(in crate::worker) service_worker_runtime: + Option, + pub(in crate::worker) service_worker_client_id: + Option, +} + +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>, + pub(in crate::worker) result: Result, +} + +pub(in crate::worker) enum WorkerXhrResponse { + Materialized(Box), + Streamed { + head: Box, + 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, + 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, + 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 +} diff --git a/moli-renderer-v8/src/worker/global_scope/origin.rs b/moli-renderer-v8/src/worker/global_scope/origin.rs new file mode 100644 index 0000000000..44f885f211 --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/origin.rs @@ -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 { + let global = scope.get_current_context().global(scope); + let origin = get_private_value(scope, global, WORKER_GLOBAL_ORIGIN_SLOT)?; + let origin = v8::Local::::try_from(origin).ok()?; + Some(moli_url::WebOrigin::from_serialized( + &origin.to_rust_string_lossy(scope), + )) +} diff --git a/moli-renderer-v8/src/worker/global_scope/performance.rs b/moli-renderer-v8/src/worker/global_scope/performance.rs new file mode 100644 index 0000000000..a0dd296a11 --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/performance.rs @@ -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 +} diff --git a/moli-renderer-v8/src/worker/global_scope/service_worker_api.rs b/moli-renderer-v8/src/worker/global_scope/service_worker_api.rs index d6894e5ff7..d9f27e4041 100644 --- a/moli-renderer-v8/src/worker/global_scope/service_worker_api.rs +++ b/moli-renderer-v8/src/worker/global_scope/service_worker_api.rs @@ -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, +} diff --git a/moli-renderer-v8/src/worker/global_scope/service_worker_results.rs b/moli-renderer-v8/src/worker/global_scope/service_worker_results.rs new file mode 100644 index 0000000000..091681a726 --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/service_worker_results.rs @@ -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, + pub(in crate::worker) query_type: PendingServiceWorkerClientQueryType, +} + +pub(in crate::worker) struct PendingServiceWorkerClientNavigate { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerClientFocus { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerClientsOpenWindow { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerShowNotification { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerGetNotifications { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerSyncRegistration { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerSyncGetTags { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerPeriodicSyncRegistration { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerPeriodicSyncGetTags { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerPeriodicSyncUnregistration { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerPushSubscribe { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerPushGetSubscription { + pub(in crate::worker) resolver: v8::Global, +} + +pub(in crate::worker) struct PendingServiceWorkerPushUnsubscribe { + pub(in crate::worker) resolver: v8::Global, +} + +#[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, + 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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, + ) -> 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 { + 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>, + 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>, + 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>, + 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>, + 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>, + 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>, + 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>, + 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>, + 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>, + 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>, + 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>, + 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>, + 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>, + 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>, + 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(); + } +} diff --git a/moli-renderer-v8/src/worker/global_scope/state.rs b/moli-renderer-v8/src/worker/global_scope/state.rs new file mode 100644 index 0000000000..ff8ed68817 --- /dev/null +++ b/moli-renderer-v8/src/worker/global_scope/state.rs @@ -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, + /// Internal wake channel used by worker-owned async runtime surfaces. + pub(crate) worker_wake_tx: mpsc::UnboundedSender, + /// 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, + /// 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, + /// Referrer policy parsed from the top-level worker script response. + pub(in crate::worker) referrer_policy: Option, + /// CSP policies from outside settings used for module static imports. + pub(in crate::worker) module_static_import_content_security_policies: Vec, + /// Enforce CSP policies parsed from the top-level worker script response. + pub(in crate::worker) content_security_policies: Vec, + /// Report-only CSP policies parsed from the top-level worker script response. + pub(in crate::worker) content_security_report_only_policies: Vec, + /// 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, + /// 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, + /// Current document network/cache partition key inherited by worker-owned requests. + pub(in crate::worker) network_partition_key: Option, + /// 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, + /// Async fetch completions routed back onto the worker event loop. + pub(in crate::worker) fetch_completion_tx: mpsc::UnboundedSender, + /// Promise resolvers pending async fetch completion. + pub(in crate::worker) pending_fetches: HashMap, + /// Worker-local pending body sources for headers-first worker fetch. + pub(crate) pending_network_body_sources: + HashMap, + pub(crate) pending_network_body_clones: HashMap>, + /// 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, + /// In-flight worker XHR requests keyed by internal id. + pub(in crate::worker) pending_xhrs: HashMap, + /// 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, + /// 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>, + /// 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, + /// Live Service Worker client id for DedicatedWorker/SharedWorker globals. + pub(in crate::worker) service_worker_client_id: + Option, + /// 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, + /// SharedWorker connect-event ports whose lifetime is owned by the shared worker runtime. + pub(in crate::worker) shared_worker_connection_ports: HashSet, + /// 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>, + /// IndexedDB storage manager inherited from the owning browser context. + pub(in crate::worker) indexed_db_manager: + Option, + /// Storage Buckets registry inherited from the owning browser context. + pub(in crate::worker) storage_bucket_store: + Option, + /// Async WebSocket transport events routed back onto this worker loop. + pub(in crate::worker) websocket_event_tx: tokio::sync::mpsc::Sender, + /// Worker-owned classic WebSocket connections keyed by socket id. + pub(in crate::worker) websockets: HashMap, + /// 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>, + /// Heavyweight WebCrypto completions routed back onto the worker event loop. + pub(in crate::worker) webcrypto_completion_tx: mpsc::UnboundedSender, + /// In-flight worker WebCrypto blocking tasks keyed by task id. + pub(in crate::worker) pending_webcrypto: HashMap, + /// 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, + pub(in crate::worker) opfs_owner_state: Option, + /// In-flight Service Worker `periodicsync` events keyed by runtime event id. + pub(in crate::worker) pending_service_worker_periodic_sync_events: + HashMap, + /// In-flight Service Worker `clients.get()` / `clients.matchAll()` queries keyed by request id. + pub(in crate::worker) pending_service_worker_client_queries: + HashMap, + /// In-flight Service Worker `WindowClient.navigate()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_client_navigates: + HashMap, + /// In-flight Service Worker `WindowClient.focus()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_client_focuses: + HashMap, + /// In-flight Service Worker `clients.openWindow()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_clients_open_windows: + HashMap, + /// In-flight Service Worker `registration.showNotification()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_show_notifications: + HashMap, + /// In-flight Service Worker `registration.getNotifications()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_get_notifications: + HashMap, + /// In-flight Service Worker `registration.sync.register()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_sync_registrations: + HashMap, + /// In-flight Service Worker `registration.sync.getTags()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_sync_get_tags: + HashMap, + /// In-flight Service Worker `registration.periodicSync.register()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_periodic_sync_registrations: + HashMap, + /// In-flight Service Worker `registration.periodicSync.getTags()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_periodic_sync_get_tags: + HashMap, + /// In-flight Service Worker `registration.periodicSync.unregister()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_periodic_sync_unregistrations: + HashMap, + /// In-flight Service Worker `registration.pushManager.subscribe()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_push_subscriptions: + HashMap, + /// In-flight Service Worker `registration.pushManager.getSubscription()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_push_get_subscriptions: + HashMap, + /// In-flight Service Worker `PushSubscription.unsubscribe()` requests keyed by request id. + pub(in crate::worker) pending_service_worker_push_unsubscriptions: + HashMap, + 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, + /// 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, + /// Navigation preload promises and response bodies stay alive until preload complete/error. + pub(in crate::worker) pending_service_worker_navigation_preloads: + HashMap, + /// Service Worker message events currently waiting for dispatch and + /// `ExtendableEvent.waitUntil()` promises to finish. + pub(in crate::worker) pending_service_worker_message_events: + HashMap, + /// Service Worker notification events currently waiting for dispatch and + /// `ExtendableEvent.waitUntil()` promises to finish. + pub(in crate::worker) pending_service_worker_notification_events: + HashMap, + /// Service Worker push events currently waiting for dispatch and + /// `ExtendableEvent.waitUntil()` promises to finish. + pub(in crate::worker) pending_service_worker_push_events: + HashMap, + /// Service Worker sync events currently waiting for dispatch and + /// `ExtendableEvent.waitUntil()` promises to finish. + pub(in crate::worker) pending_service_worker_sync_events: + HashMap, +} + +pub(crate) fn worker_service_worker_control_state( + scope: &mut v8::PinScope<'_, '_>, +) -> Option { + 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>> { + let global = scope.get_current_context().global(scope); + let val = get_private_value(scope, global, WORKER_STATE_SLOT)?; + let external = v8::Local::::try_from(val).ok()?; + let ptr = external.value() as *const RefCell; + // 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 { + Some(get_worker_state(scope)?.borrow().storage_key.clone()) +} + +pub(crate) fn worker_storage_partition_identity( + scope: &mut v8::PinScope<'_, '_>, +) -> Option { + Some( + get_worker_state(scope)? + .borrow() + .worker_context_runtime + .storage_partition_identity(), + ) +} + +pub(crate) fn worker_current_script_url(scope: &mut v8::PinScope<'_, '_>) -> Option { + get_worker_state(scope)?.borrow().current_script_url.clone() +} + +pub(crate) fn worker_exception_report_target( + scope: &mut v8::PinScope<'_, '_>, +) -> Option<(mpsc::UnboundedSender, 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); + } +} diff --git a/moli-renderer-v8/src/worker/global_scope/timers.rs b/moli-renderer-v8/src/worker/global_scope/timers.rs index 45ae5ed668..79ea38f633 100644 --- a/moli-renderer-v8/src/worker/global_scope/timers.rs +++ b/moli-renderer-v8/src/worker/global_scope/timers.rs @@ -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>>, + cancelled: Rc>>, +} + +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 { + self.pending.borrow_mut().drain(..).collect() + } + + pub(in crate::worker) fn drain_cancelled(&self) -> Vec { + self.cancelled.borrow_mut().drain(..).collect() + } +} + +pub(in crate::worker) fn worker_isolate_timer_queues( + scope: &mut v8::PinScope<'_, '_>, +) -> Option { + scope.get_slot::().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>, +}