diff --git a/moli-renderer-v8/src/native_bridge/context_host/service_workers.rs b/moli-renderer-v8/src/native_bridge/context_host/service_workers.rs index 4ce8bd03a3..59243ea4e8 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/service_workers.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/service_workers.rs @@ -117,6 +117,63 @@ impl JsContextHost { self.service_worker_task_tx.clone() } + fn ensure_service_worker_client_for_request_context( + &mut self, + request_context: &ServiceWorkerWindowRequestContext, + ) -> ServiceWorkerClientId { + let dispatch_scope = request_context.owner().dispatch_scope(); + let client_id = match dispatch_scope { + OwnerDispatchScope::Top => self.service_worker_client_id, + OwnerDispatchScope::Child(handle) => { + if let Some(client_id) = self + .child_browsing_contexts + .get(&handle) + .and_then(|entry| entry.service_worker_client_id()) + { + client_id + } else { + let client_id = self + .browser_context_runtime + .allocate_service_worker_client_id(); + if let Some(entry) = self.child_browsing_contexts.get_mut(&handle) { + entry.set_service_worker_client_id(client_id); + } + self.set_frame_owner_child_service_worker_client_id(handle, Some(client_id)); + client_id + } + } + OwnerDispatchScope::LightweightPopup(popup_id) => { + if let Some(client_id) = self.service_worker_popup_clients.get(&popup_id).copied() { + client_id + } else { + let client_id = self + .browser_context_runtime + .allocate_service_worker_client_id(); + self.service_worker_popup_clients + .insert(popup_id, client_id); + client_id + } + } + }; + let frame_type = match dispatch_scope { + OwnerDispatchScope::Child(_) => ServiceWorkerClientFrameType::Nested, + OwnerDispatchScope::Top | OwnerDispatchScope::LightweightPopup(_) => { + ServiceWorkerClientFrameType::TopLevel + } + }; + let document_owner = Some(request_context.owner().window_document_owner()); + self.browser_context_runtime + .register_allocated_service_worker_client( + client_id, + request_context.document_url().clone(), + request_context.serialized_storage_key(), + frame_type, + document_owner, + self.service_worker_task_sender(), + ); + client_id + } + fn service_worker_window_owner_for_dispatch_scope( &self, dispatch_scope: OwnerDispatchScope, @@ -571,6 +628,7 @@ impl JsContextHost { register_document_owner: WindowDocumentOwner, register_completion_tx: crate::page_task_queue::RendererPageServiceWorkerTaskSender, ) { + self.ensure_service_worker_client_for_request_context(request_context); let creator_secure_context = moli_url::is_potentially_trustworthy_url(request_context.document_url()); let network_policy = WorkerNetworkPolicy { @@ -616,6 +674,7 @@ impl JsContextHost { let requests = self.pending_service_worker_ready_requests(); let mut attached_any = false; for (request_id, request_context, completion_tx) in requests { + self.ensure_service_worker_client_for_request_context(&request_context); if self .browser_context_runtime .watch_service_worker_ready_registration( @@ -990,7 +1049,11 @@ impl JsContextHost { &mut self, document_url: &Url, ) -> Option { - if !matches!(document_url.scheme(), "http" | "https") { + if !matches!(document_url.scheme(), "http" | "https") + || !self + .browser_context_runtime + .ensure_service_worker_runtime_for_navigation() + { return None; } let storage_key = service_worker_first_party_storage_key(document_url); @@ -1157,6 +1220,13 @@ impl JsContextHost { self.clear_pending_service_worker_child_client(handle); return; } + if !self + .browser_context_runtime + .ensure_service_worker_runtime_for_navigation() + { + self.clear_pending_service_worker_child_client(handle); + return; + } if !self.child_browsing_contexts.contains_key(&handle) { return; } @@ -1537,10 +1607,11 @@ impl JsContextHost { } pub(crate) fn service_worker_registration_for_client( - &self, + &mut self, request_context: &ServiceWorkerWindowRequestContext, client_url: &Url, ) -> Option { + self.ensure_service_worker_client_for_request_context(request_context); self.browser_context_runtime .service_worker_registration_for_client( client_url, @@ -1549,9 +1620,10 @@ impl JsContextHost { } pub(crate) fn service_worker_registrations( - &self, + &mut self, request_context: &ServiceWorkerWindowRequestContext, ) -> Vec { + self.ensure_service_worker_client_for_request_context(request_context); self.browser_context_runtime.service_worker_registrations( request_context.document_url(), &request_context.serialized_storage_key(), diff --git a/moli-renderer-v8/src/runtime/browser_context_runtime.rs b/moli-renderer-v8/src/runtime/browser_context_runtime.rs index 51dc765237..83874a1e2d 100644 --- a/moli-renderer-v8/src/runtime/browser_context_runtime.rs +++ b/moli-renderer-v8/src/runtime/browser_context_runtime.rs @@ -174,8 +174,8 @@ struct RendererBrowserContextRuntimeInner { message_port_registry: crate::message_port_runtime::SharedMessagePortRegistry, broadcast_channel_registry: crate::broadcast_channel_runtime::SharedBroadcastChannelRegistry, browser_resource_runtime: crate::network::BrowserResourceRuntimeBinding, - shared_worker_runtime: crate::shared_worker_runtime::SharedWorkerRuntimeService, - service_worker_runtime: crate::service_worker_runtime::ServiceWorkerRuntimeService, + shared_worker_runtime: shared_workers::LazySharedWorkerRuntime, + service_worker_runtime: service_worker_runtime::LazyServiceWorkerRuntime, storage_partition_identity: RendererStoragePartitionIdentity, next_web_storage_opaque_context_nonce: AtomicU64, next_child_document_loader_id: AtomicU64, @@ -314,12 +314,12 @@ fn terminate_browser_context_resource_producers(inner: &RendererBrowserContextRu for handle in dedicated_worker_handles.into_values() { let _ = handle.terminate_for_devtools(); } - inner - .shared_worker_runtime - .terminate_all_for_context_shutdown(); - inner - .service_worker_runtime - .terminate_all_for_context_shutdown(); + if let Some(shared_worker_runtime) = inner.shared_worker_runtime.get() { + shared_worker_runtime.terminate_all_for_context_shutdown(); + } + if let Some(service_worker_runtime) = inner.service_worker_runtime.get() { + service_worker_runtime.terminate_all_for_context_shutdown(); + } } impl Default for RendererBrowserContextRuntimeOwner { @@ -379,7 +379,7 @@ impl RendererBrowserContextRuntime { Self::from_parts( message_port_registry, broadcast_channel_registry, - crate::shared_worker_runtime::new_shared_worker_runtime_service(), + None, service_worker_resource_store, browser_resource_runtime, ) @@ -399,7 +399,7 @@ impl RendererBrowserContextRuntime { let runtime = Self::from_parts( message_port_registry, broadcast_channel_registry, - crate::shared_worker_runtime::new_shared_worker_runtime_service(), + None, crate::new_shared_service_worker_resource_store(), browser_resource_runtime, ); @@ -425,7 +425,7 @@ impl RendererBrowserContextRuntime { let runtime = Self::from_parts( message_port_registry, broadcast_channel_registry, - shared_worker_runtime, + Some(shared_worker_runtime), crate::new_shared_service_worker_resource_store(), browser_resource_runtime, ); @@ -449,7 +449,7 @@ impl RendererBrowserContextRuntime { crate::network::BrowserResourceRuntimeOwnerRoot::new(browser_resource_runtime_owner); let runtime = Self::from_parts_with_worker_context_runtime( restored_worker_context_runtime, - crate::shared_worker_runtime::new_shared_worker_runtime_service(), + None, service_worker_resource_store, browser_resource_runtime, ); @@ -463,7 +463,7 @@ impl RendererBrowserContextRuntime { fn from_parts( message_port_registry: crate::message_port_runtime::SharedMessagePortRegistry, broadcast_channel_registry: crate::broadcast_channel_runtime::SharedBroadcastChannelRegistry, - shared_worker_runtime: crate::shared_worker_runtime::SharedWorkerRuntimeService, + shared_worker_runtime: Option, service_worker_resource_store: crate::SharedServiceWorkerResourceStore, browser_resource_runtime: crate::network::BrowserResourceRuntimeBinding, ) -> Self { @@ -483,7 +483,7 @@ impl RendererBrowserContextRuntime { fn from_parts_with_worker_context_runtime( service_worker_context_runtime: RendererWorkerContextRuntime, - shared_worker_runtime: crate::shared_worker_runtime::SharedWorkerRuntimeService, + shared_worker_runtime: Option, service_worker_resource_store: crate::SharedServiceWorkerResourceStore, browser_resource_runtime: crate::network::BrowserResourceRuntimeBinding, ) -> Self { @@ -496,17 +496,24 @@ impl RendererBrowserContextRuntime { NEXT_RENDERER_BROWSER_CONTEXT_RUNTIME_ID.fetch_add(1, Ordering::Relaxed), ); let renderer_output_transport_tx = RendererOutputTransportSenderSlot::default(); - shared_worker_runtime - .configure_target_output_streams(id, renderer_output_transport_tx.clone()); - let service_worker_runtime = - crate::service_worker_runtime:: - new_service_worker_runtime_service_with_resource_store_and_browser_resource_runtime_binding( - service_worker_resource_store, - service_worker_context_runtime, - browser_resource_runtime.clone(), - id, - renderer_output_transport_tx.clone(), - ); + let shared_worker_runtime = match shared_worker_runtime { + Some(service) => shared_workers::LazySharedWorkerRuntime::from_service( + service, + id, + renderer_output_transport_tx.clone(), + ), + None => shared_workers::LazySharedWorkerRuntime::new( + id, + renderer_output_transport_tx.clone(), + ), + }; + let service_worker_runtime = service_worker_runtime::LazyServiceWorkerRuntime::new( + service_worker_resource_store, + service_worker_context_runtime, + browser_resource_runtime.clone(), + id, + renderer_output_transport_tx.clone(), + ); Self { inner: Arc::new(RendererBrowserContextRuntimeInner { id, @@ -551,12 +558,12 @@ impl RendererBrowserContextRuntime { sender: super::RendererOutputTransportSender, ) { self.inner.renderer_output_transport_tx.set(sender.clone()); - self.inner - .shared_worker_runtime - .bind_target_output_transport(sender.clone()); - self.inner - .service_worker_runtime - .bind_target_output_transport(sender); + if let Some(shared_worker_runtime) = self.inner.shared_worker_runtime.get() { + shared_worker_runtime.bind_target_output_transport(sender.clone()); + } + if let Some(service_worker_runtime) = self.inner.service_worker_runtime.get() { + service_worker_runtime.bind_target_output_transport(sender); + } } pub(crate) fn renderer_output_transport_sender( @@ -570,15 +577,42 @@ impl RendererBrowserContextRuntime { } pub fn moli_memory_diagnostics(&self) -> Value { - let service_worker_diagnostics = self.inner.service_worker_runtime.diagnostics_snapshot(); + let shared_worker_runtime_initialized = self.inner.shared_worker_runtime.is_initialized(); + let mut shared_worker_diagnostics = self.inner.shared_worker_runtime.get().map_or_else( + || { + json!({ + "matchingEntryCount": 0, + "loadingInstanceCount": 0, + "runningInstanceCount": 0, + "clientCount": 0, + "loadingHostCount": 0, + "runningWorkerIsolateCount": 0, + "pendingServiceLaneEventCount": 0, + }) + }, + |runtime| runtime.moli_memory_diagnostics(), + ); + shared_worker_diagnostics["runtimeInitialized"] = shared_worker_runtime_initialized.into(); + let service_worker_runtime_initialized = self.inner.service_worker_runtime.is_initialized(); + let deferred_service_worker_window_client_count = self + .inner + .service_worker_runtime + .deferred_window_client_count(); + let service_worker_diagnostics = self + .inner + .service_worker_runtime + .get() + .map_or_else(Default::default, |runtime| runtime.diagnostics_snapshot()); json!({ "rendererOutputTransport": self .inner .renderer_output_transport_tx .sender() .map(|sender| sender.diagnostics()), - "sharedWorker": self.inner.shared_worker_runtime.moli_memory_diagnostics(), + "sharedWorker": shared_worker_diagnostics, "serviceWorker": { + "runtimeInitialized": service_worker_runtime_initialized, + "deferredWindowClients": deferred_service_worker_window_client_count, "registrations": service_worker_diagnostics.registration_count, "runtimeRegistrations": service_worker_diagnostics.registration_count, "versions": service_worker_diagnostics.version_count, @@ -607,7 +641,12 @@ impl RendererBrowserContextRuntime { pub fn shared_worker_runtime_diagnostics_for_diagnostics( &self, ) -> RendererSharedWorkerRuntimeDiagnostics { - self.inner.shared_worker_runtime.diagnostics_snapshot() + self.inner + .shared_worker_runtime + .get() + .map_or_else(RendererSharedWorkerRuntimeDiagnostics::default, |runtime| { + runtime.diagnostics_snapshot() + }) } pub(crate) fn message_port_registry( @@ -633,7 +672,20 @@ impl RendererBrowserContextRuntime { pub(crate) fn service_worker_runtime( &self, ) -> crate::service_worker_runtime::ServiceWorkerRuntimeService { - self.inner.service_worker_runtime.clone() + self.inner.service_worker_runtime.get_or_init() + } + + pub(crate) fn service_worker_runtime_if_initialized( + &self, + ) -> Option { + self.inner.service_worker_runtime.get() + } + + pub(crate) fn ensure_service_worker_runtime_for_navigation(&self) -> bool { + self.inner + .service_worker_runtime + .get_or_init_for_navigation() + .is_some() } pub(crate) fn next_web_storage_opaque_context_nonce( @@ -927,6 +979,76 @@ mod tests { assert!(runtime.shares_state_with(&clone)); } + #[test] + fn worker_services_stay_deferred_until_first_use() { + let runtime = RendererBrowserContextRuntime::new(); + + assert_eq!( + runtime.moli_memory_diagnostics()["sharedWorker"]["runtimeInitialized"], + false + ); + assert_eq!( + runtime.moli_memory_diagnostics()["serviceWorker"]["runtimeInitialized"], + false + ); + + let _ = runtime.next_shared_worker_client_owner_id(); + let document_url = url::Url::parse("https://deferred-client.test/").unwrap(); + let client_queue = crate::page_task_queue::RendererPageServiceWorkerTestHarness::new(); + runtime.register_service_worker_client( + document_url.clone(), + moli_storage_key::MoliStorageKey::first_party_from_url(&document_url, None) + .serialized_storage_key(), + crate::service_worker_runtime::ServiceWorkerClientFrameType::TopLevel, + Some(crate::native_bridge::WindowDocumentOwner::for_test(1)), + client_queue.sender(), + ); + let (shared_wake_tx, _shared_wake_rx) = + crate::shared_worker_runtime::shared_worker_owner_wake_channel(); + runtime.add_shared_worker_owner_wake_sender(shared_wake_tx); + let (service_wake_tx, _service_wake_rx) = + crate::service_worker_runtime::service_worker_owner_wake_channel(); + runtime.add_service_worker_owner_wake_sender(service_wake_tx); + runtime.set_service_worker_pause_on_start_for_devtools(true); + + assert!(!runtime.ensure_service_worker_runtime_for_navigation()); + assert_eq!( + runtime.moli_memory_diagnostics()["sharedWorker"]["runtimeInitialized"], + false + ); + assert_eq!( + runtime.moli_memory_diagnostics()["serviceWorker"]["runtimeInitialized"], + false + ); + assert_eq!( + runtime.moli_memory_diagnostics()["serviceWorker"]["deferredWindowClients"], + 1 + ); + + runtime.inner.shared_worker_runtime.get_or_init(); + runtime.service_worker_runtime(); + assert_eq!( + runtime.moli_memory_diagnostics()["sharedWorker"]["runtimeInitialized"], + true + ); + assert_eq!( + runtime.moli_memory_diagnostics()["serviceWorker"]["runtimeInitialized"], + true + ); + assert_eq!( + runtime.moli_memory_diagnostics()["serviceWorker"]["deferredWindowClients"], + 0 + ); + assert_eq!( + runtime + .service_worker_runtime() + .diagnostics_snapshot() + .live_client_count, + 1 + ); + assert!(runtime.service_worker_pause_on_start_for_devtools()); + } + #[test] fn cloned_runtime_shares_child_document_loader_id_sequence() { let runtime = RendererBrowserContextRuntime::new(); diff --git a/moli-renderer-v8/src/runtime/browser_context_runtime/service_worker_runtime.rs b/moli-renderer-v8/src/runtime/browser_context_runtime/service_worker_runtime.rs index 92d1657337..a3a018f934 100644 --- a/moli-renderer-v8/src/runtime/browser_context_runtime/service_worker_runtime.rs +++ b/moli-renderer-v8/src/runtime/browser_context_runtime/service_worker_runtime.rs @@ -1,13 +1,365 @@ +use std::collections::{HashMap, hash_map::Entry}; + +use parking_lot::Mutex; +use url::Url; + use crate::{ + page_task_queue::RendererPageServiceWorkerTaskSender, runtime::{RendererRuntimeInspectorMessage, RendererRuntimeInspectorResponseSender}, service_worker_runtime::{ - ServiceWorkerRegistrationId, ServiceWorkerRuntimeOwnerWakeSender, ServiceWorkerVersionId, + ServiceWorkerClientFrameType, ServiceWorkerClientId, ServiceWorkerRegistrationId, + ServiceWorkerRuntimeOwnerWakeSender, ServiceWorkerVersionId, }, + window_document_identity::WindowDocumentOwner, }; use super::RendererBrowserContextRuntime; +/// Keeps the full Service Worker registry dormant while retaining the small +/// amount of browser-context state that must exist before first use. +pub(super) struct LazyServiceWorkerRuntime { + state: Mutex, + resource_store: crate::SharedServiceWorkerResourceStore, + restored_worker_context_runtime: super::RendererWorkerContextRuntime, + browser_resource_runtime: crate::network::BrowserResourceRuntimeBinding, + client_id_allocator: crate::service_worker_runtime::ServiceWorkerClientIdAllocator, + browser_context_runtime_id: crate::runtime::RendererBrowserContextRuntimeId, + output_transport: crate::runtime::RendererOutputTransportSenderSlot, +} + +enum LazyServiceWorkerRuntimeState { + Deferred { + owner_wake_senders: Vec, + window_clients: HashMap, + force_update_on_page_load: bool, + pause_new_workers_on_start: bool, + related_pause_on_start_policies: Vec<(u64, u64, String, String)>, + }, + Live(crate::service_worker_runtime::ServiceWorkerRuntimeService), +} + +struct DeferredServiceWorkerWindowClient { + document_url: Url, + storage_key: String, + frame_type: ServiceWorkerClientFrameType, + document_owner: Option, + completion_tx: RendererPageServiceWorkerTaskSender, +} + +impl std::fmt::Debug for LazyServiceWorkerRuntime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LazyServiceWorkerRuntime") + .field("initialized", &self.is_initialized()) + .finish() + } +} + +impl LazyServiceWorkerRuntime { + pub(super) fn new( + resource_store: crate::SharedServiceWorkerResourceStore, + restored_worker_context_runtime: super::RendererWorkerContextRuntime, + browser_resource_runtime: crate::network::BrowserResourceRuntimeBinding, + browser_context_runtime_id: crate::runtime::RendererBrowserContextRuntimeId, + output_transport: crate::runtime::RendererOutputTransportSenderSlot, + ) -> Self { + Self { + state: Mutex::new(LazyServiceWorkerRuntimeState::Deferred { + owner_wake_senders: Vec::new(), + window_clients: HashMap::new(), + force_update_on_page_load: false, + pause_new_workers_on_start: false, + related_pause_on_start_policies: Vec::new(), + }), + resource_store, + restored_worker_context_runtime, + browser_resource_runtime, + client_id_allocator: Default::default(), + browser_context_runtime_id, + output_transport, + } + } + + pub(super) fn get_or_init(&self) -> crate::service_worker_runtime::ServiceWorkerRuntimeService { + let mut state = self.state.lock(); + if let LazyServiceWorkerRuntimeState::Live(service) = &*state { + return service.clone(); + } + let LazyServiceWorkerRuntimeState::Deferred { + owner_wake_senders, + window_clients, + force_update_on_page_load, + pause_new_workers_on_start, + related_pause_on_start_policies, + } = &mut *state + else { + unreachable!(); + }; + let owner_wake_senders = std::mem::take(owner_wake_senders); + let window_clients = std::mem::take(window_clients); + let force_update_on_page_load = *force_update_on_page_load; + let pause_new_workers_on_start = *pause_new_workers_on_start; + let related_pause_on_start_policies = std::mem::take(related_pause_on_start_policies); + let service = crate::service_worker_runtime:: + new_service_worker_runtime_service_with_resource_store_and_browser_resource_runtime_binding( + self.resource_store.clone(), + self.restored_worker_context_runtime.clone(), + self.browser_resource_runtime.clone(), + self.client_id_allocator.clone(), + self.browser_context_runtime_id, + self.output_transport.clone(), + ); + for sender in owner_wake_senders { + service.add_owner_wake_sender(sender); + } + service.set_force_update_on_page_load_for_devtools(force_update_on_page_load); + service.set_pause_new_workers_on_start_for_devtools(pause_new_workers_on_start); + service.set_related_pause_on_start_policies_for_devtools(related_pause_on_start_policies); + for (client_id, client) in window_clients { + let inserted = service.register_allocated_client_with_storage_key( + client_id, + client.document_url, + client.storage_key, + client.frame_type, + client.document_owner, + client.completion_tx, + ); + debug_assert!(inserted, "deferred Service Worker client id must be unique"); + } + *state = LazyServiceWorkerRuntimeState::Live(service.clone()); + service + } + + pub(super) fn get(&self) -> Option { + let state = self.state.lock(); + let LazyServiceWorkerRuntimeState::Live(service) = &*state else { + return None; + }; + Some(service.clone()) + } + + /// Returns a live runtime when one already exists or persisted + /// registrations may control the navigation. An empty store stays lazy. + pub(super) fn get_or_init_for_navigation( + &self, + ) -> Option { + if let Some(service) = self.get() { + return Some(service); + } + if self.resource_store.lock().is_empty() { + return None; + } + Some(self.get_or_init()) + } + + pub(super) fn is_initialized(&self) -> bool { + matches!(*self.state.lock(), LazyServiceWorkerRuntimeState::Live(_)) + } + + pub(super) fn allocate_client_id( + &self, + ) -> crate::service_worker_runtime::ServiceWorkerClientId { + self.client_id_allocator.allocate() + } + + pub(super) fn register_window_client( + &self, + client_id: ServiceWorkerClientId, + document_url: Url, + storage_key: String, + frame_type: ServiceWorkerClientFrameType, + document_owner: Option, + completion_tx: RendererPageServiceWorkerTaskSender, + ) -> bool { + let service = { + let mut state = self.state.lock(); + match &mut *state { + LazyServiceWorkerRuntimeState::Deferred { window_clients, .. } => { + let client = DeferredServiceWorkerWindowClient { + document_url, + storage_key, + frame_type, + document_owner, + completion_tx, + }; + return match window_clients.entry(client_id) { + Entry::Vacant(entry) => { + entry.insert(client); + true + } + Entry::Occupied(_) => false, + }; + } + LazyServiceWorkerRuntimeState::Live(service) => service.clone(), + } + }; + service.register_allocated_client_with_storage_key( + client_id, + document_url, + storage_key, + frame_type, + document_owner, + completion_tx, + ) + } + + pub(super) fn update_window_client( + &self, + client_id: ServiceWorkerClientId, + document_url: Url, + storage_key: String, + frame_type: ServiceWorkerClientFrameType, + document_owner: Option, + completion_tx: Option, + ) -> bool { + let service = { + let mut state = self.state.lock(); + match &mut *state { + LazyServiceWorkerRuntimeState::Deferred { window_clients, .. } => { + let Some(client) = window_clients.get_mut(&client_id) else { + return false; + }; + client.document_url = document_url; + client.storage_key = storage_key; + client.frame_type = frame_type; + client.document_owner = document_owner; + if let Some(completion_tx) = completion_tx { + client.completion_tx = completion_tx; + } + return true; + } + LazyServiceWorkerRuntimeState::Live(service) => service.clone(), + } + }; + if let Some(completion_tx) = completion_tx { + service.update_client_document_with_storage_key_and_completion_sender( + client_id, + document_url, + storage_key, + frame_type, + document_owner, + completion_tx, + ) + } else { + service.update_client_document_with_storage_key( + client_id, + document_url, + storage_key, + frame_type, + document_owner, + ) + } + } + + pub(super) fn unregister_client(&self, client_id: ServiceWorkerClientId) { + let service = { + let mut state = self.state.lock(); + match &mut *state { + LazyServiceWorkerRuntimeState::Deferred { window_clients, .. } => { + window_clients.remove(&client_id); + return; + } + LazyServiceWorkerRuntimeState::Live(service) => service.clone(), + } + }; + service.unregister_client(client_id); + } + + pub(super) fn deferred_window_client_count(&self) -> usize { + let state = self.state.lock(); + match &*state { + LazyServiceWorkerRuntimeState::Deferred { window_clients, .. } => window_clients.len(), + LazyServiceWorkerRuntimeState::Live(_) => 0, + } + } + + pub(super) fn add_owner_wake_sender(&self, sender: ServiceWorkerRuntimeOwnerWakeSender) { + let mut state = self.state.lock(); + match &mut *state { + LazyServiceWorkerRuntimeState::Deferred { + owner_wake_senders, .. + } => owner_wake_senders.push(sender), + LazyServiceWorkerRuntimeState::Live(service) => service.add_owner_wake_sender(sender), + } + } + + pub(super) fn set_force_update_on_page_load(&self, force_update: bool) { + let mut state = self.state.lock(); + match &mut *state { + LazyServiceWorkerRuntimeState::Deferred { + force_update_on_page_load, + .. + } => *force_update_on_page_load = force_update, + LazyServiceWorkerRuntimeState::Live(service) => { + service.set_force_update_on_page_load_for_devtools(force_update) + } + } + } + + pub(super) fn force_update_on_page_load(&self) -> bool { + let state = self.state.lock(); + match &*state { + LazyServiceWorkerRuntimeState::Deferred { + force_update_on_page_load, + .. + } => *force_update_on_page_load, + LazyServiceWorkerRuntimeState::Live(service) => { + service.force_update_on_page_load_for_devtools() + } + } + } + + pub(super) fn set_pause_new_workers_on_start(&self, pause: bool) { + let mut state = self.state.lock(); + match &mut *state { + LazyServiceWorkerRuntimeState::Deferred { + pause_new_workers_on_start, + .. + } => *pause_new_workers_on_start = pause, + LazyServiceWorkerRuntimeState::Live(service) => { + service.set_pause_new_workers_on_start_for_devtools(pause) + } + } + } + + pub(super) fn pause_new_workers_on_start(&self) -> bool { + let state = self.state.lock(); + match &*state { + LazyServiceWorkerRuntimeState::Deferred { + pause_new_workers_on_start, + .. + } => *pause_new_workers_on_start, + LazyServiceWorkerRuntimeState::Live(service) => { + service.pause_new_workers_on_start_for_devtools() + } + } + } + + pub(super) fn set_related_pause_on_start_policies( + &self, + policies: Vec<(u64, u64, String, String)>, + ) { + let mut state = self.state.lock(); + match &mut *state { + LazyServiceWorkerRuntimeState::Deferred { + related_pause_on_start_policies, + .. + } => *related_pause_on_start_policies = policies, + LazyServiceWorkerRuntimeState::Live(service) => { + service.set_related_pause_on_start_policies_for_devtools(policies) + } + } + } +} + impl RendererBrowserContextRuntime { + fn service_worker_runtime_for_existing_registration( + &self, + ) -> Option { + self.inner + .service_worker_runtime + .get_or_init_for_navigation() + } + pub(crate) fn add_service_worker_owner_wake_sender( &self, sender: ServiceWorkerRuntimeOwnerWakeSender, @@ -18,7 +370,10 @@ impl RendererBrowserContextRuntime { } pub(crate) fn drain_service_worker_service_lane(&self) -> usize { - self.inner.service_worker_runtime.drain_service_lane() + self.inner + .service_worker_runtime + .get() + .map_or(0, |runtime| runtime.drain_service_lane()) } pub async fn dispatch_service_worker_runtime_protocol_message( @@ -27,8 +382,10 @@ impl RendererBrowserContextRuntime { inspector_session_id: Option, raw_json: String, ) -> Result, String> { - self.inner - .service_worker_runtime + let Some(runtime) = self.service_worker_runtime_for_existing_registration() else { + return Err("ServiceWorkerRuntimeUnavailable".to_owned()); + }; + runtime .dispatch_runtime_protocol_message( ServiceWorkerVersionId::from_u64_for_binding(version_id), inspector_session_id, @@ -44,8 +401,10 @@ impl RendererBrowserContextRuntime { raw_json: String, deferred_response: RendererRuntimeInspectorResponseSender, ) -> Result, String> { - self.inner - .service_worker_runtime + let Some(runtime) = self.service_worker_runtime_for_existing_registration() else { + return Err("ServiceWorkerRuntimeUnavailable".to_owned()); + }; + runtime .dispatch_runtime_protocol_message_with_deferred_response( ServiceWorkerVersionId::from_u64_for_binding(version_id), inspector_session_id, @@ -60,69 +419,76 @@ impl RendererBrowserContextRuntime { version_id: u64, inspector_session_id: Option, ) -> bool { - self.inner - .service_worker_runtime - .detach_runtime_inspector_session( - ServiceWorkerVersionId::from_u64_for_binding(version_id), - inspector_session_id, - ) + self.service_worker_runtime_for_existing_registration() + .is_some_and(|runtime| { + runtime.detach_runtime_inspector_session( + ServiceWorkerVersionId::from_u64_for_binding(version_id), + inspector_session_id, + ) + }) } pub fn unregister_service_worker_scope_for_devtools( &self, scope_url: &url::Url, ) -> Result { - self.inner - .service_worker_runtime - .devtools_unregister_scope(scope_url) + self.service_worker_runtime_for_existing_registration() + .map_or(Ok(false), |runtime| { + runtime.devtools_unregister_scope(scope_url) + }) } pub fn start_service_worker_for_devtools(&self, scope_url: &url::Url) -> Result { - self.inner - .service_worker_runtime - .devtools_start_worker_for_scope(scope_url) + self.service_worker_runtime_for_existing_registration() + .map_or(Ok(false), |runtime| { + runtime.devtools_start_worker_for_scope(scope_url) + }) } pub fn stop_service_worker_for_devtools(&self, version_id: u64) -> Result { - self.inner - .service_worker_runtime - .devtools_stop_worker_version(ServiceWorkerVersionId::from_u64_for_binding(version_id)) + self.service_worker_runtime_for_existing_registration() + .map_or(Ok(false), |runtime| { + runtime.devtools_stop_worker_version(ServiceWorkerVersionId::from_u64_for_binding( + version_id, + )) + }) } pub fn stop_all_service_workers_for_devtools(&self) -> Result { - self.inner - .service_worker_runtime - .devtools_stop_all_workers() + self.service_worker_runtime_for_existing_registration() + .map_or(Ok(0), |runtime| runtime.devtools_stop_all_workers()) } pub fn skip_waiting_service_worker_for_devtools( &self, scope_url: &url::Url, ) -> Result { - self.inner - .service_worker_runtime - .devtools_skip_waiting_for_scope(scope_url) + self.service_worker_runtime_for_existing_registration() + .map_or(Ok(false), |runtime| { + runtime.devtools_skip_waiting_for_scope(scope_url) + }) } pub fn update_service_worker_registration_for_devtools( &self, scope_url: &url::Url, ) -> Result { - self.inner - .service_worker_runtime - .devtools_update_registration_for_scope(scope_url, self.clone()) + self.service_worker_runtime_for_existing_registration() + .map_or(Ok(false), |runtime| { + runtime.devtools_update_registration_for_scope(scope_url, self.clone()) + }) } pub fn set_service_worker_force_update_on_page_load_for_devtools(&self, force_update: bool) { self.inner .service_worker_runtime - .set_force_update_on_page_load_for_devtools(force_update); + .set_force_update_on_page_load(force_update); } pub fn service_worker_force_update_on_page_load_for_devtools(&self) -> bool { self.inner .service_worker_runtime - .force_update_on_page_load_for_devtools() + .force_update_on_page_load() } pub fn controlled_service_worker_window_client_urls_for_devtools( @@ -130,12 +496,13 @@ impl RendererBrowserContextRuntime { registration_id: u64, version_id: u64, ) -> Vec { - self.inner - .service_worker_runtime - .controlled_window_client_urls_for_version_for_devtools( - ServiceWorkerRegistrationId::from_u64_for_binding(registration_id), - ServiceWorkerVersionId::from_u64_for_binding(version_id), - ) + self.service_worker_runtime_for_existing_registration() + .map_or_else(Vec::new, |runtime| { + runtime.controlled_window_client_urls_for_version_for_devtools( + ServiceWorkerRegistrationId::from_u64_for_binding(registration_id), + ServiceWorkerVersionId::from_u64_for_binding(version_id), + ) + }) } pub fn controlled_service_worker_window_client_ids_for_devtools( @@ -143,18 +510,19 @@ impl RendererBrowserContextRuntime { registration_id: u64, version_id: u64, ) -> Vec { - self.inner - .service_worker_runtime - .controlled_window_client_ids_for_version_for_devtools( - ServiceWorkerRegistrationId::from_u64_for_binding(registration_id), - ServiceWorkerVersionId::from_u64_for_binding(version_id), - ) + self.service_worker_runtime_for_existing_registration() + .map_or_else(Vec::new, |runtime| { + runtime.controlled_window_client_ids_for_version_for_devtools( + ServiceWorkerRegistrationId::from_u64_for_binding(registration_id), + ServiceWorkerVersionId::from_u64_for_binding(version_id), + ) + }) } pub fn set_service_worker_pause_on_start_for_devtools(&self, pause: bool) { self.inner .service_worker_runtime - .set_pause_new_workers_on_start_for_devtools(pause); + .set_pause_new_workers_on_start(pause); } pub fn set_service_worker_related_pause_on_start_policies_for_devtools( @@ -163,7 +531,7 @@ impl RendererBrowserContextRuntime { ) { self.inner .service_worker_runtime - .set_related_pause_on_start_policies_for_devtools(policies); + .set_related_pause_on_start_policies(policies); } pub fn set_service_worker_pause_on_start_for_version_for_devtools( @@ -171,41 +539,44 @@ impl RendererBrowserContextRuntime { version_id: u64, pause: bool, ) -> bool { - self.inner - .service_worker_runtime - .set_pause_on_start_for_version_for_devtools( - ServiceWorkerVersionId::from_u64_for_binding(version_id), - pause, - ) + self.service_worker_runtime_for_existing_registration() + .is_some_and(|runtime| { + runtime.set_pause_on_start_for_version_for_devtools( + ServiceWorkerVersionId::from_u64_for_binding(version_id), + pause, + ) + }) } pub fn service_worker_pause_on_start_for_devtools(&self) -> bool { self.inner .service_worker_runtime - .pause_new_workers_on_start_for_devtools() + .pause_new_workers_on_start() } pub fn set_service_worker_devtools_attached(&self, version_id: u64, attached: bool) { - self.inner - .service_worker_runtime - .set_devtools_attached_for_version( + if let Some(runtime) = self.service_worker_runtime_for_existing_registration() { + runtime.set_devtools_attached_for_version( ServiceWorkerVersionId::from_u64_for_binding(version_id), attached, ); + } } pub fn run_service_worker_if_waiting_for_debugger_for_devtools(&self, version_id: u64) -> bool { - self.inner - .service_worker_runtime - .devtools_run_if_waiting_for_debugger(ServiceWorkerVersionId::from_u64_for_binding( - version_id, - )) + self.service_worker_runtime_for_existing_registration() + .is_some_and(|runtime| { + runtime.devtools_run_if_waiting_for_debugger( + ServiceWorkerVersionId::from_u64_for_binding(version_id), + ) + }) } pub fn release_all_service_workers_waiting_for_debugger_for_devtools(&self) -> usize { - self.inner - .service_worker_runtime - .devtools_release_all_workers_waiting_for_debugger() + self.service_worker_runtime_for_existing_registration() + .map_or(0, |runtime| { + runtime.devtools_release_all_workers_waiting_for_debugger() + }) } pub fn deliver_push_message_for_devtools( @@ -214,13 +585,14 @@ impl RendererBrowserContextRuntime { registration_id: u64, data: Option>, ) -> Result { - self.inner - .service_worker_runtime - .devtools_deliver_push_message( - origin, - ServiceWorkerRegistrationId::from_u64_for_binding(registration_id), - data, - ) + self.service_worker_runtime_for_existing_registration() + .map_or(Ok(false), |runtime| { + runtime.devtools_deliver_push_message( + origin, + ServiceWorkerRegistrationId::from_u64_for_binding(registration_id), + data, + ) + }) } pub fn dispatch_sync_event_for_devtools( @@ -230,14 +602,15 @@ impl RendererBrowserContextRuntime { tag: String, last_chance: bool, ) -> Result { - self.inner - .service_worker_runtime - .devtools_dispatch_sync_event( - origin, - ServiceWorkerRegistrationId::from_u64_for_binding(registration_id), - tag, - last_chance, - ) + self.service_worker_runtime_for_existing_registration() + .map_or(Ok(false), |runtime| { + runtime.devtools_dispatch_sync_event( + origin, + ServiceWorkerRegistrationId::from_u64_for_binding(registration_id), + tag, + last_chance, + ) + }) } pub fn dispatch_periodic_sync_event_for_devtools( @@ -246,12 +619,13 @@ impl RendererBrowserContextRuntime { registration_id: u64, tag: String, ) -> Result { - self.inner - .service_worker_runtime - .devtools_dispatch_periodic_sync_event( - origin, - ServiceWorkerRegistrationId::from_u64_for_binding(registration_id), - tag, - ) + self.service_worker_runtime_for_existing_registration() + .map_or(Ok(false), |runtime| { + runtime.devtools_dispatch_periodic_sync_event( + origin, + ServiceWorkerRegistrationId::from_u64_for_binding(registration_id), + tag, + ) + }) } } diff --git a/moli-renderer-v8/src/runtime/browser_context_runtime/service_workers.rs b/moli-renderer-v8/src/runtime/browser_context_runtime/service_workers.rs index fe533bffb4..596c5f7995 100644 --- a/moli-renderer-v8/src/runtime/browser_context_runtime/service_workers.rs +++ b/moli-renderer-v8/src/runtime/browser_context_runtime/service_workers.rs @@ -78,6 +78,17 @@ impl RendererBrowserContextRuntime { response: None, }); } + if self + .inner + .service_worker_runtime + .get_or_init_for_navigation() + .is_none() + { + return Ok(RendererServiceWorkerMainResourceFetch { + reserved_client: None, + response: None, + }); + } let storage_key = moli_storage_key::MoliStorageKey::first_party_from_url(&request.url, None) @@ -340,8 +351,7 @@ impl RendererBrowserContextRuntime { scope_url: &Url, ) -> Option> { let (_, receiver) = self - .inner - .service_worker_runtime + .service_worker_runtime() .devtools_force_update_registration_for_page_load(scope_url, self.clone()); receiver } @@ -354,8 +364,7 @@ impl RendererBrowserContextRuntime { resource_task_runner: RendererResourceTaskRunner, destination: ServiceWorkerRequestDestination, ) -> Result> { - self.inner - .service_worker_runtime + self.service_worker_runtime() .fetch_main_resource_for_worker_client( client_id, request, @@ -376,15 +385,40 @@ impl RendererBrowserContextRuntime { document_owner: Option, completion_tx: crate::page_task_queue::RendererPageServiceWorkerTaskSender, ) -> ServiceWorkerClientId { - self.inner - .service_worker_runtime - .register_client_with_storage_key( - document_url, - storage_key, - frame_type, - document_owner, - completion_tx, - ) + let client_id = self.inner.service_worker_runtime.allocate_client_id(); + let inserted = self.inner.service_worker_runtime.register_window_client( + client_id, + document_url, + storage_key, + frame_type, + document_owner, + completion_tx, + ); + debug_assert!(inserted, "new Service Worker client id must be unused"); + client_id + } + + pub(crate) fn register_allocated_service_worker_client( + &self, + client_id: ServiceWorkerClientId, + document_url: Url, + storage_key: String, + frame_type: ServiceWorkerClientFrameType, + document_owner: Option, + completion_tx: crate::page_task_queue::RendererPageServiceWorkerTaskSender, + ) -> bool { + self.inner.service_worker_runtime.register_window_client( + client_id, + document_url, + storage_key, + frame_type, + document_owner, + completion_tx, + ) + } + + pub(crate) fn allocate_service_worker_client_id(&self) -> ServiceWorkerClientId { + self.inner.service_worker_runtime.allocate_client_id() } pub(crate) fn register_reserved_service_worker_client( @@ -394,8 +428,7 @@ impl RendererBrowserContextRuntime { frame_type: ServiceWorkerClientFrameType, document_owner: Option, ) -> ServiceWorkerClientId { - self.inner - .service_worker_runtime + self.service_worker_runtime() .register_reserved_client_with_storage_key( document_url, storage_key, @@ -411,8 +444,7 @@ impl RendererBrowserContextRuntime { frame_type: ServiceWorkerClientFrameType, document_owner: Option, ) -> ServiceWorkerClientId { - self.inner - .service_worker_runtime + self.service_worker_runtime() .register_reserved_client_with_storage_key_bypassing_service_worker( document_url, storage_key, @@ -428,8 +460,7 @@ impl RendererBrowserContextRuntime { client_type: ServiceWorkerClientType, secure_context: bool, ) -> ServiceWorkerClientId { - self.inner - .service_worker_runtime + self.service_worker_runtime() .register_reserved_worker_client_with_storage_key( script_url, storage_key, @@ -446,8 +477,7 @@ impl RendererBrowserContextRuntime { secure_context: bool, parent_client_id: ServiceWorkerClientId, ) -> Option { - self.inner - .service_worker_runtime + self.service_worker_runtime_if_initialized()? .register_reserved_worker_client_inheriting_controller_from_client( script_url, storage_key, @@ -471,15 +501,14 @@ impl RendererBrowserContextRuntime { frame_type: ServiceWorkerClientFrameType, document_owner: Option, ) -> bool { - self.inner - .service_worker_runtime - .update_client_document_with_storage_key( - client_id, - document_url, - storage_key, - frame_type, - document_owner, - ) + self.inner.service_worker_runtime.update_window_client( + client_id, + document_url, + storage_key, + frame_type, + document_owner, + None, + ) } pub(crate) fn update_service_worker_client_document_and_page_endpoint( @@ -491,16 +520,14 @@ impl RendererBrowserContextRuntime { document_owner: Option, completion_tx: crate::page_task_queue::RendererPageServiceWorkerTaskSender, ) -> bool { - self.inner - .service_worker_runtime - .update_client_document_with_storage_key_and_completion_sender( - client_id, - document_url, - storage_key, - frame_type, - document_owner, - completion_tx, - ) + self.inner.service_worker_runtime.update_window_client( + client_id, + document_url, + storage_key, + frame_type, + document_owner, + Some(completion_tx), + ) } pub(crate) fn unregister_service_worker_scope( @@ -511,8 +538,7 @@ impl RendererBrowserContextRuntime { document_owner: crate::window_document_identity::WindowDocumentOwner, completion_tx: crate::page_task_queue::RendererPageServiceWorkerTaskSender, ) -> ServiceWorkerUnregisterStart { - self.inner - .service_worker_runtime + self.service_worker_runtime() .start_unregistration_with_storage_key( scope_url, storage_key, @@ -527,8 +553,7 @@ impl RendererBrowserContextRuntime { client_url: &Url, storage_key: &str, ) -> Option { - self.inner - .service_worker_runtime + self.service_worker_runtime() .matching_registration_for_client_with_storage_key(client_url, storage_key) } @@ -537,8 +562,7 @@ impl RendererBrowserContextRuntime { document_url: &Url, storage_key: &str, ) -> Vec { - self.inner - .service_worker_runtime + self.service_worker_runtime() .all_registrations_with_storage_key(document_url, storage_key) } @@ -546,8 +570,7 @@ impl RendererBrowserContextRuntime { &self, scope_url: &Url, ) -> Option { - self.inner - .service_worker_runtime + self.service_worker_runtime() .navigation_preload_state_for_scope(scope_url) } @@ -556,8 +579,7 @@ impl RendererBrowserContextRuntime { scope_url: &Url, enabled: bool, ) -> Result<(), ServiceWorkerNavigationPreloadStateError> { - self.inner - .service_worker_runtime + self.service_worker_runtime() .set_navigation_preload_enabled_for_scope(scope_url, enabled) } @@ -566,8 +588,7 @@ impl RendererBrowserContextRuntime { scope_url: &Url, header_value: String, ) -> Result<(), ServiceWorkerNavigationPreloadStateError> { - self.inner - .service_worker_runtime + self.service_worker_runtime() .set_navigation_preload_header_value_for_scope(scope_url, header_value) } @@ -579,8 +600,7 @@ impl RendererBrowserContextRuntime { document_owner: crate::window_document_identity::WindowDocumentOwner, completion_tx: crate::page_task_queue::RendererPageServiceWorkerTaskSender, ) -> bool { - self.inner - .service_worker_runtime + self.service_worker_runtime() .watch_ready_registration_with_storage_key( document_url, storage_key, @@ -597,18 +617,20 @@ impl RendererBrowserContextRuntime { document_owner: crate::window_document_identity::WindowDocumentOwner, completion_tx: crate::page_task_queue::RendererPageServiceWorkerTaskSender, ) { - self.inner - .service_worker_runtime - .watch_registration_lifecycle(scope_url, storage_key, document_owner, completion_tx) + self.service_worker_runtime().watch_registration_lifecycle( + scope_url, + storage_key, + document_owner, + completion_tx, + ) } pub(crate) fn service_worker_controller_for_client( &self, client_id: ServiceWorkerClientId, ) -> Option { - self.inner - .service_worker_runtime - .matching_controller_for_client(client_id) + self.service_worker_runtime_if_initialized() + .and_then(|runtime| runtime.matching_controller_for_client(client_id)) } pub(crate) fn service_worker_controller_for_fetch( @@ -616,24 +638,23 @@ impl RendererBrowserContextRuntime { client_id: ServiceWorkerClientId, request_url: &Url, ) -> Option { - self.inner - .service_worker_runtime - .matching_controller_for_client_fetch(client_id, request_url) + self.service_worker_runtime_if_initialized() + .and_then(|runtime| { + runtime.matching_controller_for_client_fetch(client_id, request_url) + }) } pub(crate) fn dispatch_service_worker_fetch( &self, dispatch: ServiceWorkerFetchDispatch, ) -> bool { - self.inner - .service_worker_runtime - .dispatch_controlled_fetch(dispatch) + self.service_worker_runtime_if_initialized() + .is_some_and(|runtime| runtime.dispatch_controlled_fetch(dispatch)) } pub(crate) fn abort_service_worker_fetch(&self, internal_id: u64) -> bool { - self.inner - .service_worker_runtime - .abort_controlled_fetch(internal_id) + self.service_worker_runtime_if_initialized() + .is_some_and(|runtime| runtime.abort_controlled_fetch(internal_id)) } pub(crate) fn abort_service_worker_fetch_with_reason( @@ -641,9 +662,8 @@ impl RendererBrowserContextRuntime { internal_id: u64, reason: Option, ) -> bool { - self.inner - .service_worker_runtime - .abort_controlled_fetch_with_reason(internal_id, reason) + self.service_worker_runtime_if_initialized() + .is_some_and(|runtime| runtime.abort_controlled_fetch_with_reason(internal_id, reason)) } pub(crate) fn dispatch_service_worker_message( @@ -653,9 +673,15 @@ impl RendererBrowserContextRuntime { source_origin: Option, payload: V8StructuredClonePayload, ) -> bool { - self.inner - .service_worker_runtime - .dispatch_message_to_version(version_id, source_client_id, source_origin, payload) + self.service_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.dispatch_message_to_version( + version_id, + source_client_id, + source_origin, + payload, + ) + }) } pub fn dispatch_service_worker_notification_click( @@ -664,8 +690,7 @@ impl RendererBrowserContextRuntime { title: impl Into, action: impl Into, ) -> bool { - self.inner - .service_worker_runtime + self.service_worker_runtime() .dispatch_notification_click_for_scope(scope_url, title.into(), action.into()) } @@ -674,33 +699,27 @@ impl RendererBrowserContextRuntime { scope_url: &Url, title: impl Into, ) -> bool { - self.inner - .service_worker_runtime + self.service_worker_runtime() .dispatch_notification_close_for_scope(scope_url, title.into()) } pub fn dispatch_service_worker_push(&self, scope_url: &Url, data: Option>) -> bool { - self.inner - .service_worker_runtime + self.service_worker_runtime() .dispatch_push_for_scope(scope_url, data) } pub fn dispatch_service_worker_periodic_sync(&self, scope_url: &Url, tag: &str) -> bool { - self.inner - .service_worker_runtime + self.service_worker_runtime() .dispatch_periodic_sync_for_scope(scope_url, tag) } pub(crate) fn register_service_worker_sync(&self, scope_url: &Url, tag: String) -> bool { - self.inner - .service_worker_runtime + self.service_worker_runtime() .register_sync_for_scope(scope_url, tag) } pub(crate) fn service_worker_sync_tags(&self, scope_url: &Url) -> Vec { - self.inner - .service_worker_runtime - .sync_tags_for_scope(scope_url) + self.service_worker_runtime().sync_tags_for_scope(scope_url) } pub(crate) fn register_service_worker_periodic_sync( @@ -709,14 +728,12 @@ impl RendererBrowserContextRuntime { tag: String, min_interval_ms: u64, ) -> bool { - self.inner - .service_worker_runtime + self.service_worker_runtime() .register_periodic_sync_for_scope(scope_url, tag, min_interval_ms) } pub(crate) fn service_worker_periodic_sync_tags(&self, scope_url: &Url) -> Vec { - self.inner - .service_worker_runtime + self.service_worker_runtime() .periodic_sync_tags_for_scope(scope_url) } @@ -725,8 +742,7 @@ impl RendererBrowserContextRuntime { scope_url: &Url, tag: &str, ) -> bool { - self.inner - .service_worker_runtime + self.service_worker_runtime() .unregister_periodic_sync_for_scope(scope_url, tag) } @@ -735,8 +751,7 @@ impl RendererBrowserContextRuntime { scope_url: &Url, user_visible_only: bool, ) -> Option { - self.inner - .service_worker_runtime + self.service_worker_runtime() .subscribe_push_for_scope(scope_url, user_visible_only) } @@ -744,14 +759,12 @@ impl RendererBrowserContextRuntime { &self, scope_url: &Url, ) -> Option { - self.inner - .service_worker_runtime + self.service_worker_runtime() .push_subscription_for_scope(scope_url) } pub(crate) fn unsubscribe_service_worker_push(&self, scope_url: &Url) -> bool { - self.inner - .service_worker_runtime + self.service_worker_runtime() .unsubscribe_push_for_scope(scope_url) } @@ -764,16 +777,14 @@ impl RendererBrowserContextRuntime { actions: Vec, data: V8StructuredClonePayload, ) -> bool { - self.inner - .service_worker_runtime - .show_notification_for_scope( - scope_url, - title.into(), - tag.into(), - metadata, - actions, - data, - ) + self.service_worker_runtime().show_notification_for_scope( + scope_url, + title.into(), + tag.into(), + metadata, + actions, + data, + ) } pub(crate) fn service_worker_notifications( @@ -781,8 +792,7 @@ impl RendererBrowserContextRuntime { scope_url: &Url, tag: Option<&str>, ) -> Vec { - self.inner - .service_worker_runtime + self.service_worker_runtime() .notifications_for_scope(scope_url, tag) } @@ -791,15 +801,13 @@ impl RendererBrowserContextRuntime { registration_id: crate::runtime::ServiceWorkerRegistrationId, notification_id: u64, ) -> bool { - self.inner - .service_worker_runtime + self.service_worker_runtime() .close_notification(registration_id, notification_id) } #[cfg(test)] pub(crate) fn stop_service_worker_hosts_for_test(&self) { - self.inner - .service_worker_runtime + self.service_worker_runtime() .stop_all_running_hosts_for_test(); } } diff --git a/moli-renderer-v8/src/runtime/browser_context_runtime/shared_workers.rs b/moli-renderer-v8/src/runtime/browser_context_runtime/shared_workers.rs index 212fa6732d..7806dc2c9a 100644 --- a/moli-renderer-v8/src/runtime/browser_context_runtime/shared_workers.rs +++ b/moli-renderer-v8/src/runtime/browser_context_runtime/shared_workers.rs @@ -6,6 +6,7 @@ use crate::{ use moli_shared_worker::{ SharedWorkerClientId, SharedWorkerClientOwnerId, SharedWorkerDescriptor, SharedWorkerInstanceId, }; +use parking_lot::Mutex; use super::RendererBrowserContextRuntime; use crate::runtime::{ @@ -13,7 +14,142 @@ use crate::runtime::{ RendererRuntimeInspectorResponseSender, }; +/// Defers the browser-context SharedWorker registry until the first actual +/// `connect_shared_worker` call. ID allocation and owner routing do not require +/// the registry. +pub(super) struct LazySharedWorkerRuntime { + state: Mutex, + client_owner_id_allocator: crate::shared_worker_runtime::SharedWorkerClientOwnerIdAllocator, + browser_context_runtime_id: crate::runtime::RendererBrowserContextRuntimeId, + output_transport: crate::runtime::RendererOutputTransportSenderSlot, +} + +enum LazySharedWorkerRuntimeState { + Deferred { + owner_wake_senders: Vec, + owner_local_host_id: Option, + }, + Live(crate::shared_worker_runtime::SharedWorkerRuntimeService), +} + +impl std::fmt::Debug for LazySharedWorkerRuntime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LazySharedWorkerRuntime") + .field("initialized", &self.is_initialized()) + .finish() + } +} + +impl LazySharedWorkerRuntime { + pub(super) fn new( + browser_context_runtime_id: crate::runtime::RendererBrowserContextRuntimeId, + output_transport: crate::runtime::RendererOutputTransportSenderSlot, + ) -> Self { + Self { + state: Mutex::new(LazySharedWorkerRuntimeState::Deferred { + owner_wake_senders: Vec::new(), + owner_local_host_id: None, + }), + client_owner_id_allocator: Default::default(), + browser_context_runtime_id, + output_transport, + } + } + + pub(super) fn from_service( + service: crate::shared_worker_runtime::SharedWorkerRuntimeService, + browser_context_runtime_id: crate::runtime::RendererBrowserContextRuntimeId, + output_transport: crate::runtime::RendererOutputTransportSenderSlot, + ) -> Self { + service + .configure_target_output_streams(browser_context_runtime_id, output_transport.clone()); + Self { + client_owner_id_allocator: service.client_owner_id_allocator(), + state: Mutex::new(LazySharedWorkerRuntimeState::Live(service)), + browser_context_runtime_id, + output_transport, + } + } + + pub(super) fn get_or_init(&self) -> crate::shared_worker_runtime::SharedWorkerRuntimeService { + let mut state = self.state.lock(); + if let LazySharedWorkerRuntimeState::Live(service) = &*state { + return service.clone(); + } + let LazySharedWorkerRuntimeState::Deferred { + owner_wake_senders, + owner_local_host_id, + } = &mut *state + else { + unreachable!(); + }; + let owner_wake_senders = std::mem::take(owner_wake_senders); + let owner_local_host_id = *owner_local_host_id; + let service = crate::shared_worker_runtime:: + new_shared_worker_runtime_service_with_client_owner_id_allocator( + self.client_owner_id_allocator.clone(), + ); + service.configure_target_output_streams( + self.browser_context_runtime_id, + self.output_transport.clone(), + ); + for sender in owner_wake_senders { + service.add_owner_wake_sender(sender); + } + if let Some(owner_local_host_id) = owner_local_host_id { + service.set_owner_local_host_id(owner_local_host_id); + } + *state = LazySharedWorkerRuntimeState::Live(service.clone()); + service + } + + pub(super) fn get(&self) -> Option { + let state = self.state.lock(); + let LazySharedWorkerRuntimeState::Live(service) = &*state else { + return None; + }; + Some(service.clone()) + } + + pub(super) fn is_initialized(&self) -> bool { + matches!(*self.state.lock(), LazySharedWorkerRuntimeState::Live(_)) + } + + pub(super) fn allocate_client_owner_id(&self) -> SharedWorkerClientOwnerId { + self.client_owner_id_allocator.allocate() + } + + pub(super) fn add_owner_wake_sender(&self, sender: SharedWorkerRuntimeOwnerWakeSender) { + let mut state = self.state.lock(); + match &mut *state { + LazySharedWorkerRuntimeState::Deferred { + owner_wake_senders, .. + } => owner_wake_senders.push(sender), + LazySharedWorkerRuntimeState::Live(service) => service.add_owner_wake_sender(sender), + } + } + + pub(super) fn set_owner_local_host_id(&self, owner_local_host_id: RendererOwnerLocalHostId) { + let mut state = self.state.lock(); + match &mut *state { + LazySharedWorkerRuntimeState::Deferred { + owner_local_host_id: slot, + .. + } => *slot = Some(owner_local_host_id), + LazySharedWorkerRuntimeState::Live(service) => { + service.set_owner_local_host_id(owner_local_host_id) + } + } + } +} + impl RendererBrowserContextRuntime { + fn shared_worker_runtime_if_initialized( + &self, + ) -> Option { + self.inner.shared_worker_runtime.get() + } + pub(crate) fn add_shared_worker_owner_wake_sender( &self, sender: SharedWorkerRuntimeOwnerWakeSender, @@ -37,28 +173,35 @@ impl RendererBrowserContextRuntime { descriptor: SharedWorkerDescriptor, params: SharedWorkerLaunchParams, ) -> SharedWorkerClientId { - self.inner.shared_worker_runtime.connect(descriptor, params) + self.inner + .shared_worker_runtime + .get_or_init() + .connect(descriptor, params) } pub(crate) fn next_shared_worker_client_owner_id(&self) -> SharedWorkerClientOwnerId { - self.inner.shared_worker_runtime.next_client_owner_id() + self.inner.shared_worker_runtime.allocate_client_owner_id() } pub(crate) fn drain_shared_worker_service_lane(&self) -> usize { - self.inner.shared_worker_runtime.drain_service_lane() + self.inner + .shared_worker_runtime + .get() + .map_or(0, |runtime| runtime.drain_service_lane()) } pub fn close_shared_worker_for_target_close( &self, instance_id: SharedWorkerInstanceId, ) -> bool { - self.inner - .shared_worker_runtime - .close_instance_for_devtools_target_close(instance_id) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| runtime.close_instance_for_devtools_target_close(instance_id)) } pub(crate) fn remove_shared_worker_client(&self, client_id: SharedWorkerClientId) { - self.inner.shared_worker_runtime.remove_client(client_id); + if let Some(runtime) = self.shared_worker_runtime_if_initialized() { + runtime.remove_client(client_id); + } } pub(crate) fn continue_shared_worker_fetch( @@ -66,9 +209,8 @@ impl RendererBrowserContextRuntime { instance_id: SharedWorkerInstanceId, request: WorkerPendingFetchContinue, ) -> bool { - self.inner - .shared_worker_runtime - .continue_pending_fetch(instance_id, request) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| runtime.continue_pending_fetch(instance_id, request)) } pub(crate) fn continue_shared_worker_xhr( @@ -76,9 +218,8 @@ impl RendererBrowserContextRuntime { instance_id: SharedWorkerInstanceId, request: WorkerPendingXhrContinue, ) -> bool { - self.inner - .shared_worker_runtime - .continue_pending_xhr(instance_id, request) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| runtime.continue_pending_xhr(instance_id, request)) } pub(crate) fn continue_shared_worker_csp_report( @@ -86,9 +227,8 @@ impl RendererBrowserContextRuntime { instance_id: SharedWorkerInstanceId, request: WorkerPendingFetchContinue, ) -> bool { - self.inner - .shared_worker_runtime - .continue_pending_csp_report(instance_id, request) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| runtime.continue_pending_csp_report(instance_id, request)) } pub(crate) fn continue_shared_worker_fetch_response( @@ -98,9 +238,15 @@ impl RendererBrowserContextRuntime { response_code: Option, response_headers: Option>, ) -> bool { - self.inner - .shared_worker_runtime - .continue_pending_fetch_response(instance_id, request, response_code, response_headers) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.continue_pending_fetch_response( + instance_id, + request, + response_code, + response_headers, + ) + }) } pub(crate) fn continue_shared_worker_xhr_response( @@ -110,9 +256,15 @@ impl RendererBrowserContextRuntime { response_code: Option, response_headers: Option>, ) -> bool { - self.inner - .shared_worker_runtime - .continue_pending_xhr_response(instance_id, request, response_code, response_headers) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.continue_pending_xhr_response( + instance_id, + request, + response_code, + response_headers, + ) + }) } pub(crate) fn fail_shared_worker_fetch( @@ -121,9 +273,8 @@ impl RendererBrowserContextRuntime { request: WorkerPendingFetchContinue, error_text: String, ) -> bool { - self.inner - .shared_worker_runtime - .fail_pending_fetch(instance_id, request, error_text) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| runtime.fail_pending_fetch(instance_id, request, error_text)) } pub(crate) fn fail_shared_worker_xhr( @@ -132,9 +283,8 @@ impl RendererBrowserContextRuntime { request: WorkerPendingXhrContinue, error_text: String, ) -> bool { - self.inner - .shared_worker_runtime - .fail_pending_xhr(instance_id, request, error_text) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| runtime.fail_pending_xhr(instance_id, request, error_text)) } pub(crate) fn fail_shared_worker_csp_report( @@ -143,9 +293,10 @@ impl RendererBrowserContextRuntime { request: WorkerPendingFetchContinue, error_text: String, ) -> bool { - self.inner - .shared_worker_runtime - .fail_pending_csp_report(instance_id, request, error_text) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.fail_pending_csp_report(instance_id, request, error_text) + }) } pub(crate) fn fail_shared_worker_fetch_auth( @@ -154,9 +305,10 @@ impl RendererBrowserContextRuntime { request: WorkerPendingFetchContinue, error_text: String, ) -> bool { - self.inner - .shared_worker_runtime - .fail_pending_fetch_auth(instance_id, request, error_text) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.fail_pending_fetch_auth(instance_id, request, error_text) + }) } pub(crate) fn fail_shared_worker_xhr_auth( @@ -165,9 +317,8 @@ impl RendererBrowserContextRuntime { request: WorkerPendingXhrContinue, error_text: String, ) -> bool { - self.inner - .shared_worker_runtime - .fail_pending_xhr_auth(instance_id, request, error_text) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| runtime.fail_pending_xhr_auth(instance_id, request, error_text)) } pub(crate) fn fail_shared_worker_fetch_response( @@ -176,9 +327,10 @@ impl RendererBrowserContextRuntime { request: WorkerPendingFetchContinue, error_text: String, ) -> bool { - self.inner - .shared_worker_runtime - .fail_pending_fetch_response(instance_id, request, error_text) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.fail_pending_fetch_response(instance_id, request, error_text) + }) } pub(crate) fn fail_shared_worker_xhr_response( @@ -187,9 +339,10 @@ impl RendererBrowserContextRuntime { request: WorkerPendingXhrContinue, error_text: String, ) -> bool { - self.inner - .shared_worker_runtime - .fail_pending_xhr_response(instance_id, request, error_text) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.fail_pending_xhr_response(instance_id, request, error_text) + }) } pub(crate) fn fulfill_shared_worker_fetch( @@ -200,13 +353,16 @@ impl RendererBrowserContextRuntime { response_headers: Vec<(String, String)>, response_body: RendererSyntheticResponseBody, ) -> bool { - self.inner.shared_worker_runtime.fulfill_pending_fetch( - instance_id, - request, - response_code, - response_headers, - response_body, - ) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.fulfill_pending_fetch( + instance_id, + request, + response_code, + response_headers, + response_body, + ) + }) } pub(crate) fn fulfill_shared_worker_xhr( @@ -217,13 +373,16 @@ impl RendererBrowserContextRuntime { response_headers: Vec<(String, String)>, response_body: RendererSyntheticResponseBody, ) -> bool { - self.inner.shared_worker_runtime.fulfill_pending_xhr( - instance_id, - request, - response_code, - response_headers, - response_body, - ) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.fulfill_pending_xhr( + instance_id, + request, + response_code, + response_headers, + response_body, + ) + }) } pub(crate) fn fulfill_shared_worker_csp_report( @@ -234,13 +393,16 @@ impl RendererBrowserContextRuntime { response_headers: Vec<(String, String)>, response_body: RendererSyntheticResponseBody, ) -> bool { - self.inner.shared_worker_runtime.fulfill_pending_csp_report( - instance_id, - request, - response_code, - response_headers, - response_body, - ) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.fulfill_pending_csp_report( + instance_id, + request, + response_code, + response_headers, + response_body, + ) + }) } pub(crate) fn fulfill_shared_worker_fetch_response( @@ -251,15 +413,16 @@ impl RendererBrowserContextRuntime { response_headers: Vec<(String, String)>, response_body: RendererSyntheticResponseBody, ) -> bool { - self.inner - .shared_worker_runtime - .fulfill_pending_fetch_response( - instance_id, - request, - response_code, - response_headers, - response_body, - ) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.fulfill_pending_fetch_response( + instance_id, + request, + response_code, + response_headers, + response_body, + ) + }) } pub(crate) fn fulfill_shared_worker_xhr_response( @@ -270,15 +433,16 @@ impl RendererBrowserContextRuntime { response_headers: Vec<(String, String)>, response_body: RendererSyntheticResponseBody, ) -> bool { - self.inner - .shared_worker_runtime - .fulfill_pending_xhr_response( - instance_id, - request, - response_code, - response_headers, - response_body, - ) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.fulfill_pending_xhr_response( + instance_id, + request, + response_code, + response_headers, + response_body, + ) + }) } pub async fn dispatch_shared_worker_runtime_protocol_message( @@ -287,8 +451,10 @@ impl RendererBrowserContextRuntime { inspector_session_id: Option, raw_json: String, ) -> Result, String> { - self.inner - .shared_worker_runtime + let Some(runtime) = self.shared_worker_runtime_if_initialized() else { + return Err("SharedWorkerRuntimeUnavailable".to_owned()); + }; + runtime .dispatch_runtime_protocol_message(instance_id, inspector_session_id, raw_json) .await } @@ -300,8 +466,10 @@ impl RendererBrowserContextRuntime { raw_json: String, deferred_response: RendererRuntimeInspectorResponseSender, ) -> Result, String> { - self.inner - .shared_worker_runtime + let Some(runtime) = self.shared_worker_runtime_if_initialized() else { + return Err("SharedWorkerRuntimeUnavailable".to_owned()); + }; + runtime .dispatch_runtime_protocol_message_with_deferred_response( instance_id, inspector_session_id, @@ -316,8 +484,9 @@ impl RendererBrowserContextRuntime { instance_id: SharedWorkerInstanceId, inspector_session_id: Option, ) -> bool { - self.inner - .shared_worker_runtime - .detach_runtime_inspector_session(instance_id, inspector_session_id) + self.shared_worker_runtime_if_initialized() + .is_some_and(|runtime| { + runtime.detach_runtime_inspector_session(instance_id, inspector_session_id) + }) } } diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/lifecycle.rs b/moli-renderer-v8/src/runtime/page_vm/tests/lifecycle.rs index b9b62ae3b7..26b6b05939 100644 --- a/moli-renderer-v8/src/runtime/page_vm/tests/lifecycle.rs +++ b/moli-renderer-v8/src/runtime/page_vm/tests/lifecycle.rs @@ -3478,12 +3478,24 @@ fn top_level_http_location_navigation_reserves_service_worker_client_until_commi .await; let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default()).expect("loader"); - let page_vm = test_page_vm_with_loader_and_document_url( + let browser_context_owner = crate::runtime::RendererBrowserContextRuntime::new(); + let browser_context_runtime = browser_context_owner.handle(); + browser_context_runtime.service_worker_runtime(); + let (owner_wake_tx, _owner_wake_rx) = tokio::sync::mpsc::unbounded_channel(); + let runtime_hooks = + PageVmRuntimeHooks::standalone_with_owner_wake_and_browser_context_without_owner_reservation_for_test( + crate::page_task_queue::RendererOwnerWakeSender::new( + owner_wake_tx, + crate::runtime::RendererPageToken::new_for_testing(PageId::new_for_testing(1)), + ), + browser_context_runtime.clone(), + ); + let page_vm = test_page_vm_with_loader_document_url_and_hooks( &loader, Vec::new(), Url::parse(&format!("{base_url}/start.html")).unwrap(), + runtime_hooks, ); - let browser_context_runtime = page_vm.runtime_hooks.browser_context_runtime.clone(); let browser_context_runtime_after_drop = browser_context_runtime.clone(); let local_executor = page_vm.local_executor.clone(); let next_url = format!("{base_url}/next.html"); @@ -3579,9 +3591,44 @@ fn top_level_http_location_navigation_reserves_service_worker_client_until_commi server .await .expect("top-level reserved navigation response server should finish"); + drop(browser_context_owner); }); } +#[test] +fn top_level_http_location_navigation_without_registrations_keeps_service_worker_runtime_deferred() +{ + let loader = + crate::network::ResourceRequestClient::new(&FetchConfig::default()).expect("loader"); + let mut page_vm = test_page_vm_with_loader_and_document_url( + &loader, + Vec::new(), + Url::parse("https://deferred-navigation.test/start.html").unwrap(), + ); + let browser_context_runtime = page_vm.runtime_hooks.browser_context_runtime.clone(); + + assert_eq!( + browser_context_runtime.moli_memory_diagnostics()["serviceWorker"]["runtimeInitialized"], + false + ); + page_vm + .vm_mut() + .eval("location.href = 'https://deferred-navigation.test/next.html'; 'queued'") + .expect("location navigation should queue"); + assert!(page_vm.vm().has_pending_location_navigation()); + assert_eq!( + browser_context_runtime.moli_memory_diagnostics()["serviceWorker"]["runtimeInitialized"], + false, + "an empty Service Worker store must not make navigation materialize the runtime" + ); + + drop(page_vm); + assert_eq!( + browser_context_runtime.moli_memory_diagnostics()["serviceWorker"]["runtimeInitialized"], + false + ); +} + #[test] fn runtime_evaluate_without_enable_uses_inspector_default_context() { let mut page_vm = test_page_vm(); diff --git a/moli-renderer-v8/src/service_worker_runtime.rs b/moli-renderer-v8/src/service_worker_runtime.rs index e2669d7a17..27b1e73796 100644 --- a/moli-renderer-v8/src/service_worker_runtime.rs +++ b/moli-renderer-v8/src/service_worker_runtime.rs @@ -63,8 +63,8 @@ pub(crate) use events::{ service_worker_fetch_request_metadata, service_worker_opaque_response_rejection, }; pub(crate) use ids::{ - ServiceWorkerClientId, ServiceWorkerEventId, ServiceWorkerRegistrationId, - ServiceWorkerVersionId, + ServiceWorkerClientId, ServiceWorkerClientIdAllocator, ServiceWorkerEventId, + ServiceWorkerRegistrationId, ServiceWorkerVersionId, }; pub(crate) use jobs::ServiceWorkerUnregisterStart; pub(crate) use owner_wake::{ diff --git a/moli-renderer-v8/src/service_worker_runtime/ids.rs b/moli-renderer-v8/src/service_worker_runtime/ids.rs index f6efa60595..5741a4aaaf 100644 --- a/moli-renderer-v8/src/service_worker_runtime/ids.rs +++ b/moli-renderer-v8/src/service_worker_runtime/ids.rs @@ -1,3 +1,8 @@ +use std::sync::{ + Arc, + atomic::{AtomicU64, Ordering}, +}; + #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub(crate) struct ServiceWorkerRegistrationId(pub(super) u64); @@ -52,6 +57,25 @@ impl ServiceWorkerClientId { } } +#[derive(Clone, Debug)] +pub(crate) struct ServiceWorkerClientIdAllocator { + next: Arc, +} + +impl Default for ServiceWorkerClientIdAllocator { + fn default() -> Self { + Self { + next: Arc::new(AtomicU64::new(1)), + } + } +} + +impl ServiceWorkerClientIdAllocator { + pub(crate) fn allocate(&self) -> ServiceWorkerClientId { + ServiceWorkerClientId(self.next.fetch_add(1, Ordering::Relaxed)) + } +} + #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub(crate) struct ServiceWorkerEventId(pub(super) u64); diff --git a/moli-renderer-v8/src/service_worker_runtime/resource_store.rs b/moli-renderer-v8/src/service_worker_runtime/resource_store.rs index 4e09166c6f..a4a080d510 100644 --- a/moli-renderer-v8/src/service_worker_runtime/resource_store.rs +++ b/moli-renderer-v8/src/service_worker_runtime/resource_store.rs @@ -170,6 +170,13 @@ impl ServiceWorkerResourceStore { .collect() } + pub(crate) fn is_empty(&self) -> bool { + match &self.backend { + ServiceWorkerResourceStoreBackend::Memory(registrations) => registrations.is_empty(), + ServiceWorkerResourceStoreBackend::Json(json) => json.registrations.is_empty(), + } + } + pub(super) fn delete_registration(&mut self, key: &ServiceWorkerRegistrationKey) -> Result<()> { let mut last_error = None; for _ in 0..=SERVICE_WORKER_RESOURCE_STORE_MUTATION_RETRY_COUNT { diff --git a/moli-renderer-v8/src/service_worker_runtime/service.rs b/moli-renderer-v8/src/service_worker_runtime/service.rs index 49c93b19d2..d422a4ed9a 100644 --- a/moli-renderer-v8/src/service_worker_runtime/service.rs +++ b/moli-renderer-v8/src/service_worker_runtime/service.rs @@ -299,6 +299,7 @@ pub(crate) fn new_service_worker_runtime_service_with_resource_store_and_browser resource_store: SharedServiceWorkerResourceStore, restored_worker_context_runtime: RendererWorkerContextRuntime, browser_resource_runtime: crate::network::BrowserResourceRuntimeBinding, + client_id_allocator: super::ids::ServiceWorkerClientIdAllocator, browser_context_runtime_id: crate::runtime::RendererBrowserContextRuntimeId, output_transport: crate::runtime::RendererOutputTransportSenderSlot, ) -> ServiceWorkerRuntimeService { @@ -308,6 +309,7 @@ pub(crate) fn new_service_worker_runtime_service_with_resource_store_and_browser resource_store, restored_worker_context_runtime, browser_resource_runtime, + client_id_allocator, browser_context_runtime_id, output_transport, )), @@ -801,9 +803,7 @@ mod tests { let controlled_client_ids = controlled_client_documents .into_iter() .map(|document_url| { - let client_id = ServiceWorkerClientId( - service.inner.next_client_id.fetch_add(1, Ordering::Relaxed), - ); + let client_id = service.inner.client_id_allocator.allocate(); let current_document_url = service_worker_current_url_for_creation_url(&document_url); let storage_key = @@ -1143,8 +1143,7 @@ mod tests { worker_tx, ); } - let client_id = - ServiceWorkerClientId(service.inner.next_client_id.fetch_add(1, Ordering::Relaxed)); + let client_id = service.inner.client_id_allocator.allocate(); let document_url = service_worker_current_url_for_creation_url(&creation_url); let storage_key = ServiceWorkerRegistrationKey::first_party_storage_key_for_url(&document_url); diff --git a/moli-renderer-v8/src/service_worker_runtime/service/client_registry.rs b/moli-renderer-v8/src/service_worker_runtime/service/client_registry.rs index ac139d0b56..f0197d84ce 100644 --- a/moli-renderer-v8/src/service_worker_runtime/service/client_registry.rs +++ b/moli-renderer-v8/src/service_worker_runtime/service/client_registry.rs @@ -23,6 +23,7 @@ impl ServiceWorkerRuntimeService { ) } + #[cfg(test)] pub(crate) fn register_client_with_storage_key( &self, document_url: Url, @@ -31,17 +32,43 @@ impl ServiceWorkerRuntimeService { document_owner: Option, completion_tx: RendererPageServiceWorkerTaskSender, ) -> ServiceWorkerClientId { - let client_id = self.register_window_client_with_storage_key( + let client_id = self.inner.client_id_allocator.allocate(); + let inserted = self.register_window_client_with_storage_key( + client_id, document_url, storage_key, frame_type, document_owner, ServiceWorkerClientEndpoint::Page(completion_tx), ); + debug_assert!(inserted, "new ServiceWorker client id must be unused"); self.mark_client_execution_ready(client_id); client_id } + pub(crate) fn register_allocated_client_with_storage_key( + &self, + client_id: ServiceWorkerClientId, + document_url: Url, + storage_key: String, + frame_type: ServiceWorkerClientFrameType, + document_owner: Option, + completion_tx: RendererPageServiceWorkerTaskSender, + ) -> bool { + if !self.register_window_client_with_storage_key( + client_id, + document_url, + storage_key, + frame_type, + document_owner, + ServiceWorkerClientEndpoint::Page(completion_tx), + ) { + return false; + } + self.mark_client_execution_ready(client_id); + true + } + pub(crate) fn register_reserved_client_with_storage_key( &self, document_url: Url, @@ -82,7 +109,9 @@ impl ServiceWorkerRuntimeService { document_owner: Option, bypass_service_worker: bool, ) -> ServiceWorkerClientId { - self.register_window_client_with_storage_key( + let client_id = self.inner.client_id_allocator.allocate(); + let inserted = self.register_window_client_with_storage_key( + client_id, document_url, storage_key, frame_type, @@ -90,7 +119,12 @@ impl ServiceWorkerRuntimeService { ServiceWorkerClientEndpoint::ReservedPage { bypass_service_worker, }, - ) + ); + debug_assert!( + inserted, + "new reserved ServiceWorker client id must be unused" + ); + client_id } pub(crate) fn register_worker_client_with_storage_key( @@ -105,8 +139,7 @@ impl ServiceWorkerRuntimeService { client_type, ServiceWorkerClientType::DedicatedWorker | ServiceWorkerClientType::SharedWorker )); - let client_id = - ServiceWorkerClientId(self.inner.next_client_id.fetch_add(1, Ordering::Relaxed)); + let client_id = self.inner.client_id_allocator.allocate(); let current_script_url = service_worker_current_url_for_creation_url(&script_url); { let mut state = self.inner.state.lock(); @@ -159,8 +192,7 @@ impl ServiceWorkerRuntimeService { client_type, ServiceWorkerClientType::DedicatedWorker | ServiceWorkerClientType::SharedWorker )); - let client_id = - ServiceWorkerClientId(self.inner.next_client_id.fetch_add(1, Ordering::Relaxed)); + let client_id = self.inner.client_id_allocator.allocate(); let current_script_url = service_worker_current_url_for_creation_url(&script_url); { let mut state = self.inner.state.lock(); @@ -250,8 +282,7 @@ impl ServiceWorkerRuntimeService { .map(|registration| registration.id) }) .flatten(); - let client_id = - ServiceWorkerClientId(self.inner.next_client_id.fetch_add(1, Ordering::Relaxed)); + let client_id = self.inner.client_id_allocator.allocate(); state.live_clients.insert( client_id, ServiceWorkerClient { @@ -304,14 +335,13 @@ impl ServiceWorkerRuntimeService { fn register_window_client_with_storage_key( &self, + client_id: ServiceWorkerClientId, document_url: Url, storage_key: String, frame_type: ServiceWorkerClientFrameType, document_owner: Option, endpoint: ServiceWorkerClientEndpoint, - ) -> ServiceWorkerClientId { - let client_id = - ServiceWorkerClientId(self.inner.next_client_id.fetch_add(1, Ordering::Relaxed)); + ) -> bool { let current_document_url = service_worker_current_url_for_creation_url(&document_url); let bypass_service_worker = matches!( &endpoint, @@ -321,6 +351,9 @@ impl ServiceWorkerRuntimeService { ); { let mut state = self.inner.state.lock(); + if state.live_clients.contains_key(&client_id) { + return false; + } self.restore_stored_registrations_for_document_url_locked( &mut state, ¤t_document_url, @@ -356,7 +389,7 @@ impl ServiceWorkerRuntimeService { state.record_target_version_updated(version_id); } } - client_id + true } fn mark_client_execution_ready(&self, client_id: ServiceWorkerClientId) { diff --git a/moli-renderer-v8/src/service_worker_runtime/state.rs b/moli-renderer-v8/src/service_worker_runtime/state.rs index 13c1e334bf..ae9560b691 100644 --- a/moli-renderer-v8/src/service_worker_runtime/state.rs +++ b/moli-renderer-v8/src/service_worker_runtime/state.rs @@ -122,7 +122,7 @@ fn target_status_for_lifecycle_state( pub(super) struct ServiceWorkerRuntimeInner { pub(super) next_registration_id: AtomicU64, pub(super) next_version_id: AtomicU64, - pub(super) next_client_id: AtomicU64, + pub(super) client_id_allocator: super::ids::ServiceWorkerClientIdAllocator, pub(super) next_event_id: AtomicU64, pub(super) next_force_update_page_load_waiter_id: AtomicU64, pub(super) next_notification_id: AtomicU64, @@ -146,13 +146,14 @@ impl ServiceWorkerRuntimeInner { resource_store: SharedServiceWorkerResourceStore, restored_worker_context_runtime: RendererWorkerContextRuntime, browser_resource_runtime: crate::network::BrowserResourceRuntimeBinding, + client_id_allocator: super::ids::ServiceWorkerClientIdAllocator, browser_context_runtime_id: crate::runtime::RendererBrowserContextRuntimeId, output_transport: crate::runtime::RendererOutputTransportSenderSlot, ) -> Self { Self { next_registration_id: AtomicU64::new(1), next_version_id: AtomicU64::new(1), - next_client_id: AtomicU64::new(1), + client_id_allocator, next_event_id: AtomicU64::new(1), next_force_update_page_load_waiter_id: AtomicU64::new(1), next_notification_id: AtomicU64::new(1), diff --git a/moli-renderer-v8/src/shared_worker_runtime.rs b/moli-renderer-v8/src/shared_worker_runtime.rs index 2b45c23c22..f547a9673e 100644 --- a/moli-renderer-v8/src/shared_worker_runtime.rs +++ b/moli-renderer-v8/src/shared_worker_runtime.rs @@ -52,8 +52,11 @@ pub(crate) use loading::{ SharedWorkerExecutionPolicy, SharedWorkerLaunchContext, SharedWorkerLaunchParams, SharedWorkerScriptLoad, SharedWorkerScriptRequestPolicy, }; +pub(crate) use matching::SharedWorkerClientOwnerIdAllocator; pub(crate) use owner_wake::{ SharedWorkerRuntimeOwnerWake, SharedWorkerRuntimeOwnerWakeSender, shared_worker_owner_wake_channel, }; -pub(crate) use service::{SharedWorkerRuntimeService, new_shared_worker_runtime_service}; +pub(crate) use service::{ + SharedWorkerRuntimeService, new_shared_worker_runtime_service_with_client_owner_id_allocator, +}; diff --git a/moli-renderer-v8/src/shared_worker_runtime/matching.rs b/moli-renderer-v8/src/shared_worker_runtime/matching.rs index 297f6a017c..334d02d9f2 100644 --- a/moli-renderer-v8/src/shared_worker_runtime/matching.rs +++ b/moli-renderer-v8/src/shared_worker_runtime/matching.rs @@ -1,4 +1,7 @@ -use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{ + Arc, + atomic::{AtomicU64, Ordering}, +}; use moli_shared_worker::{ SharedWorkerClientId, SharedWorkerClientOwnerId, SharedWorkerClientRemoval, @@ -12,14 +15,47 @@ use super::{ host::SharedRendererSharedWorkerHost, }; +#[derive(Clone, Debug)] +pub(crate) struct SharedWorkerClientOwnerIdAllocator { + next: Arc, +} + +impl Default for SharedWorkerClientOwnerIdAllocator { + fn default() -> Self { + Self { + next: Arc::new(AtomicU64::new(0)), + } + } +} + +impl SharedWorkerClientOwnerIdAllocator { + pub(crate) fn allocate(&self) -> SharedWorkerClientOwnerId { + let id = self.next.fetch_add(1, Ordering::Relaxed).saturating_add(1); + SharedWorkerClientOwnerId::from_u64(id) + } +} + #[derive(Default)] pub(super) struct SharedWorkerMatchingStore { registry: SharedWorkerRegistry, client_owner_lifecycle: SharedWorkerClientOwnerLifecycleStore, - next_client_owner_id: AtomicU64, + client_owner_id_allocator: SharedWorkerClientOwnerIdAllocator, } impl SharedWorkerMatchingStore { + pub(super) fn with_client_owner_id_allocator( + client_owner_id_allocator: SharedWorkerClientOwnerIdAllocator, + ) -> Self { + Self { + client_owner_id_allocator, + ..Self::default() + } + } + + pub(super) fn client_owner_id_allocator(&self) -> SharedWorkerClientOwnerIdAllocator { + self.client_owner_id_allocator.clone() + } + pub(super) fn connect( &self, key: SharedWorkerKey, @@ -100,12 +136,9 @@ impl SharedWorkerMatchingStore { self.registry.loading_clients_for_instance(instance_id) } + #[cfg(test)] pub(super) fn next_client_owner_id(&self) -> SharedWorkerClientOwnerId { - let id = self - .next_client_owner_id - .fetch_add(1, Ordering::Relaxed) - .saturating_add(1); - SharedWorkerClientOwnerId::from_u64(id) + self.client_owner_id_allocator.allocate() } fn consume_observed_action(&self, observed: SharedWorkerObservedAction) -> T { diff --git a/moli-renderer-v8/src/shared_worker_runtime/service.rs b/moli-renderer-v8/src/shared_worker_runtime/service.rs index 4b077b7a76..4b89de9583 100644 --- a/moli-renderer-v8/src/shared_worker_runtime/service.rs +++ b/moli-renderer-v8/src/shared_worker_runtime/service.rs @@ -17,7 +17,7 @@ use crate::runtime::RendererOwnerLocalHostId; use super::{ host::SharedRendererSharedWorkerHost, instances::SharedWorkerHostStore, - matching::SharedWorkerMatchingStore, + matching::{SharedWorkerClientOwnerIdAllocator, SharedWorkerMatchingStore}, owner_wake::{ SharedWorkerOwnerWake, SharedWorkerRuntimeOwnerWake, SharedWorkerRuntimeOwnerWakeSender, }, @@ -25,8 +25,17 @@ use super::{ target_output_streams::SharedWorkerTargetOutputStreams, }; -pub(crate) fn new_shared_worker_runtime_service() -> SharedWorkerRuntimeService { - SharedWorkerRuntimeService::default() +pub(crate) fn new_shared_worker_runtime_service_with_client_owner_id_allocator( + client_owner_id_allocator: SharedWorkerClientOwnerIdAllocator, +) -> SharedWorkerRuntimeService { + SharedWorkerRuntimeService { + inner: Arc::new(SharedWorkerRuntimeInner { + matching: Arc::new(SharedWorkerMatchingStore::with_client_owner_id_allocator( + client_owner_id_allocator, + )), + ..SharedWorkerRuntimeInner::default() + }), + } } #[derive(Clone)] @@ -50,6 +59,10 @@ struct SharedWorkerRuntimeInner { } impl SharedWorkerRuntimeService { + pub(crate) fn client_owner_id_allocator(&self) -> SharedWorkerClientOwnerIdAllocator { + self.inner.matching.client_owner_id_allocator() + } + pub(crate) fn configure_target_output_streams( &self, browser_context_runtime_id: crate::runtime::RendererBrowserContextRuntimeId, @@ -136,6 +149,7 @@ impl SharedWorkerRuntimeService { self.inner.owner_wake.signal_service_lane_wake() } + #[cfg(test)] pub(crate) fn next_client_owner_id(&self) -> SharedWorkerClientOwnerId { self.inner.matching.next_client_owner_id() }