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