fix(service-worker): retain Window client messages until the queue is enabled

This commit is contained in:
ldm0
2026-11-12 07:18:25 +08:00
parent 2d20fee937
commit 71ff1009f7
15 changed files with 379 additions and 9 deletions
@@ -22,6 +22,7 @@ use crate::worker::WorkerScriptKind;
use moli_webapi_declare::{WebApiFunctionTemplate, WebApiObject, WebApiObjectDeclaration};
const SERVICE_WORKER_REGISTRATION_SCOPE_SLOT: &str = "__moliServiceWorkerRegistrationScope";
const SERVICE_WORKER_WINDOW_ID_SLOT: &str = "__moliServiceWorkerWindowIdentity";
const SERVICE_WORKER_REGISTRATION_ID_SLOT: &str = "__moliServiceWorkerRegistrationId";
const SERVICE_WORKER_REGISTRATION_WORKER_SLOT: &str = "__moliServiceWorkerRegistrationWorker";
const SERVICE_WORKER_REGISTRATION_WORKERS_SLOT: &str = "__moliServiceWorkerRegistrationWorkers";
@@ -993,6 +994,7 @@ pub(in crate::context_bootstrap) fn navigator_service_worker_message_handler_set
SERVICE_WORKER_CONTAINER_ONMESSAGE_SLOT,
args.get(0),
);
enable_service_worker_client_message_queue(scope, args.this());
}
pub(in crate::context_bootstrap) fn navigator_service_worker_messageerror_handler_getter_callback<
@@ -2765,13 +2767,35 @@ pub(in crate::context_bootstrap) fn navigator_service_worker_ready_getter_callba
}
}
pub(in crate::context_bootstrap) fn navigator_service_worker_start_messages_callback(
_scope: &mut v8::PinScope<'_, '_>,
_args: v8::FunctionCallbackArguments<'_>,
_rv: v8::ReturnValue<'_, v8::Value>,
pub(in crate::context_bootstrap) fn navigator_service_worker_start_messages_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'s, v8::Value>,
) {
// The current native client-message queue is enabled when the container is
// created. Calling startMessages again leaves that enabled state unchanged.
enable_service_worker_client_message_queue(scope, args.this());
}
fn enable_service_worker_client_message_queue<'s>(
scope: &mut v8::PinScope<'s, '_>,
container: v8::Local<'s, v8::Object>,
) {
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
// Worker client messages become runnable after worker bootstrap.
return;
};
let owner = service_worker_container_owner_scope(scope, container);
let Some(identity) = get_private_value(scope, container, SERVICE_WORKER_WINDOW_ID_SLOT)
.and_then(|value| v8::Local::<v8::BigInt>::try_from(value).ok())
else {
return;
};
let (identity, lossless) = identity.u64_value();
let host = unsafe { &mut *host_ptr };
// A container retained from a replaced Window must not enable the new
// Window's queue. document.open() can retain the same Window and container.
if lossless && host.service_worker_window_id_for_dispatch_scope(owner) == Some(identity) {
host.enable_service_worker_client_message_queue(owner);
}
}
fn mark_service_worker_registration_event_target<'s>(
@@ -2850,6 +2874,22 @@ pub(in crate::context_bootstrap) fn service_worker_object_set_owner_scope<'s>(
) {
let owner_token = service_worker_owner_token_value(scope, owner);
set_private_value(scope, object, SERVICE_WORKER_OWNER_TOKEN_SLOT, owner_token);
if let Some(identity) = context_host_ptr_from_global_bridge(scope).and_then(|host| {
let host = unsafe { &*host };
// A retained Navigator can materialize this object after navigation.
// Its creation realm must still belong to this current Window; a
// dispatch address alone may already refer to a replacement Window.
host.current_runtime_window_execution_context_identity_for_dispatch_scope(scope, owner)?;
host.service_worker_window_id_for_dispatch_scope(owner)
}) {
let identity = v8::BigInt::new_from_u64(scope, identity);
set_private_value(
scope,
object,
SERVICE_WORKER_WINDOW_ID_SLOT,
identity.into(),
);
}
}
fn service_worker_worker_set_owner_scope<'s>(
@@ -795,6 +795,10 @@ impl JsContextHost {
)
});
record_performance_dom_content_loaded_event_end(scope);
self.enable_service_worker_client_message_queue_for_document(
super::super::OwnerDispatchScope::Child(child_handle),
crate::native_bridge::WindowDocumentOwner::Frame(action.owner()),
);
if self
.frame_owner_store
.child_document_task_owner_is_current(child_handle, action.owner())
@@ -458,6 +458,7 @@ impl JsContextHost {
pending_service_worker_registers: HashMap::new(),
pending_service_worker_unregisters: HashMap::new(),
pending_service_worker_ready: HashMap::new(),
service_worker_client_message_queues: HashMap::new(),
service_worker_registration_watchers: Vec::new(),
service_worker_lifecycle_watched_scopes: HashSet::new(),
service_worker_popup_clients: HashMap::new(),
@@ -996,6 +996,8 @@ pub(crate) struct JsContextHost {
pending_service_worker_unregisters:
HashMap<u64, service_workers::PendingServiceWorkerUnregister>,
pending_service_worker_ready: HashMap<u64, service_workers::PendingServiceWorkerReady>,
service_worker_client_message_queues:
HashMap<WindowDocumentOwner, service_workers::ServiceWorkerClientMessageQueue>,
service_worker_registration_watchers: Vec<service_workers::ServiceWorkerRegistrationWatcher>,
service_worker_lifecycle_watched_scopes: HashSet<(Url, String, WindowDocumentOwner)>,
service_worker_popup_clients: HashMap<u64, ServiceWorkerClientId>,
@@ -5400,6 +5400,10 @@ impl JsContextHost {
return;
}
document.dom_content_loaded = PopupDomContentLoadedState::Dispatched;
self.enable_service_worker_client_message_queue_for_document(
OwnerDispatchScope::LightweightPopup(popup_id),
crate::native_bridge::WindowDocumentOwner::LightweightPopup(window.document_owner),
);
self.queue_lightweight_popup_post_parse_autofocus(popup_id);
self.publish_lightweight_popup_load_event_if_ready(popup_id);
}
@@ -16,6 +16,9 @@ use crate::worker::{WorkerNetworkPolicy, WorkerScriptKind, worker_secure_context
use moli_storage_key::MoliStorageKey;
use url::Url;
mod client_messages;
pub(super) use client_messages::ServiceWorkerClientMessageQueue;
pub(super) fn service_worker_first_party_storage_key(document_url: &Url) -> String {
moli_storage_key::MoliStorageKey::first_party_from_url(document_url, None)
.serialized_storage_key()
@@ -378,6 +381,8 @@ impl JsContextHost {
retired_owner: WindowDocumentOwner,
) {
self.retire_web_mcp_document(retired_owner);
self.service_worker_client_message_queues
.remove(&retired_owner);
let register_count = self.pending_service_worker_registers.len();
self.pending_service_worker_registers
.retain(|_, pending| pending.owner.window_document_owner() != retired_owner);
@@ -409,6 +414,8 @@ impl JsContextHost {
transition: crate::frame_owner_model::MainDocumentOwnerTransition,
) {
let retired_owner = transition.retired_owner();
self.service_worker_client_message_queues
.remove(&WindowDocumentOwner::Frame(retired_owner));
self.retire_web_mcp_document(WindowDocumentOwner::Frame(retired_owner));
let register_count = self.pending_service_worker_registers.len();
self.pending_service_worker_registers
@@ -0,0 +1,138 @@
use std::collections::VecDeque;
use super::{JsContextHost, OwnerDispatchScope, ServiceWorkerWindowOwner, WindowDocumentOwner};
use crate::types::ServiceWorkerClientMessageCompletion;
#[derive(Default)]
pub(in crate::native_bridge::context_host) struct ServiceWorkerClientMessageQueue {
enabled: bool,
pending: VecDeque<ServiceWorkerClientMessageCompletion>,
scheduled: usize,
}
impl JsContextHost {
pub(crate) fn service_worker_window_id_for_dispatch_scope(
&self,
scope: OwnerDispatchScope,
) -> Option<u64> {
use super::super::WindowExecutionContextOwner;
match self.current_window_execution_context_owner(scope)? {
WindowExecutionContextOwner::Frame(id) => Some(id.0),
WindowExecutionContextOwner::LightweightPopup {
local_window_id, ..
} => Some(local_window_id.as_u64()),
}
}
fn service_worker_client_queue_initially_enabled(&self, scope: OwnerDispatchScope) -> bool {
let document = match scope {
OwnerDispatchScope::Top => Some(self.document_handle()),
OwnerDispatchScope::Child(child) => self.child_browsing_context_document_handle(child),
OwnerDispatchScope::LightweightPopup(popup) => {
self.lightweight_popup_document_handle(popup)
}
};
// Initial about:blank documents can already be complete without a
// parser lifecycle task. Interactive Documents are enabled explicitly
// by their real DOMContentLoaded transition.
document
.is_some_and(|document| self.document_ready_state_for_handle(document) == "complete")
}
pub(crate) fn enable_service_worker_client_message_queue(&mut self, scope: OwnerDispatchScope) {
let Some(owner) = self.service_worker_window_owner_for_dispatch_scope(scope) else {
return;
};
self.enable_service_worker_client_message_queue_for_document(
scope,
owner.window_document_owner(),
);
}
pub(crate) fn enable_service_worker_client_message_queue_for_document(
&mut self,
scope: OwnerDispatchScope,
document: WindowDocumentOwner,
) {
if self
.service_worker_window_owner_for_dispatch_scope(scope)
.is_none_or(|owner| owner.window_document_owner() != document)
{
return;
}
let queue = self
.service_worker_client_message_queues
.entry(document)
.or_default();
if queue.enabled {
return;
}
queue.enabled = true;
let pending = std::mem::take(&mut queue.pending);
for completion in pending {
self.schedule_service_worker_client_queue_delivery(document, completion);
}
}
fn schedule_service_worker_client_queue_delivery(
&mut self,
document: WindowDocumentOwner,
completion: ServiceWorkerClientMessageCompletion,
) {
let queue = self
.service_worker_client_message_queues
.get_mut(&document)
.expect("a queued client message retains its current Document queue");
queue.scheduled = queue
.scheduled
.checked_add(1)
.expect("ServiceWorker client message queue size exhausted");
if self
.service_worker_task_tx
.send_service_worker_client_queue_delivery(completion)
.is_err()
{
self.service_worker_client_message_queues
.get_mut(&document)
.expect("sending a Page task cannot retire its Document queue")
.scheduled -= 1;
}
}
pub(crate) fn admit_service_worker_client_message(
&mut self,
owner: ServiceWorkerWindowOwner,
completion: ServiceWorkerClientMessageCompletion,
client_queue_delivery: bool,
) -> Option<ServiceWorkerClientMessageCompletion> {
assert!(self.service_worker_window_owner_is_current(owner));
let document = owner.window_document_owner();
let enabled = self.service_worker_client_queue_initially_enabled(owner.dispatch_scope());
let queue = self
.service_worker_client_message_queues
.entry(document)
.or_insert_with(|| ServiceWorkerClientMessageQueue {
enabled,
..Default::default()
});
if client_queue_delivery {
queue.scheduled = queue
.scheduled
.checked_sub(1)
.expect("a resumed client message must own a scheduled queue delivery");
return Some(completion);
}
if !queue.enabled {
queue.pending.push_back(completion);
return None;
}
if queue.scheduled != 0 {
// New incoming Page tasks can precede messages resumed by
// startMessages(). Put them behind that older backlog, rather
// than dispatching them ahead of it or dispatching synchronously.
self.schedule_service_worker_client_queue_delivery(document, completion);
return None;
}
Some(completion)
}
}
@@ -38,12 +38,14 @@ impl RendererPageServiceWorkerClientMessageOwner {
pub(crate) struct RendererPageServiceWorkerClientMessageTask {
owner: RendererPageServiceWorkerClientMessageOwner,
completion: ServiceWorkerClientMessageCompletion,
client_queue_delivery: bool,
}
impl RendererPageServiceWorkerClientMessageTask {
fn new(
root_document: RendererDocumentToken,
completion: ServiceWorkerClientMessageCompletion,
client_queue_delivery: bool,
) -> Self {
Self {
owner: RendererPageServiceWorkerClientMessageOwner::new(
@@ -51,6 +53,7 @@ impl RendererPageServiceWorkerClientMessageTask {
completion.target,
),
completion,
client_queue_delivery,
}
}
@@ -61,6 +64,10 @@ impl RendererPageServiceWorkerClientMessageTask {
pub(crate) fn into_completion(self) -> ServiceWorkerClientMessageCompletion {
self.completion
}
pub(crate) const fn is_client_queue_delivery(&self) -> bool {
self.client_queue_delivery
}
}
#[derive(Clone, Debug)]
@@ -103,10 +110,29 @@ impl RendererPageServiceWorkerClientMessageSender {
pub(crate) fn send(
&self,
completion: ServiceWorkerClientMessageCompletion,
) -> Result<(), RendererPageServiceWorkerClientMessageRouteClosed> {
self.send_with_client_queue_state(completion, false)
}
pub(crate) fn send_client_queue_delivery(
&self,
completion: ServiceWorkerClientMessageCompletion,
) -> Result<(), RendererPageServiceWorkerClientMessageRouteClosed> {
self.send_with_client_queue_state(completion, true)
}
fn send_with_client_queue_state(
&self,
completion: ServiceWorkerClientMessageCompletion,
client_queue_delivery: bool,
) -> Result<(), RendererPageServiceWorkerClientMessageRouteClosed> {
self.task_route
.send_and_signal_if_newly_ready(ReadyPageTask::new(
RendererPageServiceWorkerClientMessageTask::new(self.root_document, completion),
RendererPageServiceWorkerClientMessageTask::new(
self.root_document,
completion,
client_queue_delivery,
),
))
.map_err(|_| RendererPageServiceWorkerClientMessageRouteClosed)
}
@@ -194,6 +220,9 @@ pub(crate) enum ServiceWorkerClientMessageCallbackEffect {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PageServiceWorkerClientMessageTargetEffect {
/// The exact current Document retained the message for asynchronous FIFO
/// delivery on its client message queue.
QueuedForClientMessageQueue,
/// A `message` or `messageerror` event was dispatched in the exact
/// current Window client. `callback_effect` determines whether selected
/// completion performs callback reconciliation or checkpoint only.
@@ -80,6 +80,15 @@ impl RendererPageServiceWorkerTaskSender {
.map_err(|_| RendererPageServiceWorkerTaskRouteClosed)
}
pub(crate) fn send_service_worker_client_queue_delivery(
&self,
completion: ServiceWorkerClientMessageCompletion,
) -> Result<(), RendererPageServiceWorkerTaskRouteClosed> {
self.client_message
.send_client_queue_delivery(completion)
.map_err(|_| RendererPageServiceWorkerTaskRouteClosed)
}
pub(crate) fn send_service_worker_client_navigate_request(
&self,
completion: ServiceWorkerClientNavigateRequestCompletion,
@@ -24,6 +24,9 @@ use super::{IntoPageTaskCompletion, PageTaskCompletion, PageVm};
impl From<ServiceWorkerClientMessageBodyEffect> for PageServiceWorkerClientMessageTargetEffect {
fn from(effect: ServiceWorkerClientMessageBodyEffect) -> Self {
match effect {
ServiceWorkerClientMessageBodyEffect::QueuedForClientMessageQueue => {
Self::QueuedForClientMessageQueue
}
ServiceWorkerClientMessageBodyEffect::EventDispatched {
event_kind,
callback_effect,
@@ -63,7 +66,8 @@ impl IntoPageTaskCompletion for PageServiceWorkerClientMessageTurnAction {
callback_effect: ServiceWorkerClientMessageCallbackEffect::CurrentTargetHadNoCallback,
..
}
| PageServiceWorkerClientMessageTargetEffect::CurrentTargetProducedNoDispatchableEvent => {
| PageServiceWorkerClientMessageTargetEffect::CurrentTargetProducedNoDispatchableEvent
| PageServiceWorkerClientMessageTargetEffect::QueuedForClientMessageQueue => {
PageTaskCompletion::CheckpointOnly
}
PageServiceWorkerClientMessageTargetEffect::DiscardedStaleTarget
@@ -10,6 +10,104 @@ use crate::{
types::ServiceWorkerClientMessageCompletion,
};
#[tokio::test(flavor = "current_thread")]
async fn service_worker_client_queue_enables_asynchronously_and_keeps_older_messages_first() {
run_page_vm_async_test(async move {
let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default())?;
for enable in [
"navigator.serviceWorker.startMessages()",
"navigator.serviceWorker.onmessage = () => {}",
"navigator.serviceWorker.onmessage = null",
"navigator.serviceWorker.onmessage = {}",
] {
let (mut page, _resources, _wakes) = page_vm_with_bound_task_sources_and_owner_wake(
&loader,
Url::parse("https://service-worker-client-message.test/queue")?,
);
page.vm_mut().set_document_ready_state(crate::dom::native::DocumentReadyState::Loading)?;
page.vm_mut().eval(r#"
globalThis.received = [];
navigator.serviceWorker.addEventListener('message', event => {
received.push(event.data);
Promise.resolve().then(() => received.push('after:' + event.data));
});
navigator.serviceWorker.onmessageerror = () => {};
document.dispatchEvent(new Event('DOMContentLoaded'));
void 0;
"#)?;
let target = page.vm().service_worker_client_message_target_for_test(
crate::native_bridge::OwnerDispatchScope::Top,
)?;
let root = page.document_lifecycle.identity().document;
let sender = page.service_worker_task_sender_for_root_for_test(root);
let first = page.vm_mut().service_worker_client_message_payload_for_test("first")?;
sender.send_service_worker_client_message(service_worker_client_message(target, first)).unwrap();
let task = page.take_service_worker_client_message_body_task_for_test().unwrap();
let result = page.apply_selected_page_service_worker_client_message_turn(task)?;
assert_eq!(result.action.target_effect, PageServiceWorkerClientMessageTargetEffect::QueuedForClientMessageQueue);
page.finish_selected_page_task_completion(result.action.into_page_task_completion(), &loader).await?;
assert_eq!(page.vm_mut().eval("received.join('|')")?, "");
assert!(page.take_service_worker_client_message_body_task_for_test().is_none());
// This incoming message is already resident before the older
// disabled message is rescheduled by the enabling call.
let second = page.vm_mut().service_worker_client_message_payload_for_test("second")?;
sender.send_service_worker_client_message(service_worker_client_message(target, second)).unwrap();
page.vm_mut().eval(&format!("{enable}; Promise.resolve().then(() => received.push('enabled-microtask')); void 0;"))?;
assert_eq!(page.vm_mut().eval("received.join('|')")?, "enabled-microtask", "{enable} must not dispatch synchronously");
let newer = page.take_service_worker_client_message_body_task_for_test().unwrap();
let result = page.apply_selected_page_service_worker_client_message_turn(newer)?;
assert_eq!(result.action.target_effect, PageServiceWorkerClientMessageTargetEffect::QueuedForClientMessageQueue);
page.finish_selected_page_task_completion(result.action.into_page_task_completion(), &loader).await?;
for expected in ["enabled-microtask|first|after:first", "enabled-microtask|first|after:first|second|after:second"] {
let task = page.take_service_worker_client_message_body_task_for_test().unwrap();
assert!(task.is_client_queue_delivery());
let result = page.apply_selected_page_service_worker_client_message_turn(task)?;
assert!(matches!(result.action.target_effect, PageServiceWorkerClientMessageTargetEffect::EventDispatchedToCurrentTarget { .. }));
page.finish_selected_page_task_completion(result.action.into_page_task_completion(), &loader).await?;
assert_eq!(page.vm_mut().eval("received.join('|')")?, expected, "{enable}");
}
page.vm_mut().eval("navigator.serviceWorker.startMessages(); navigator.serviceWorker.startMessages();")?;
assert!(page.take_service_worker_client_message_body_task_for_test().is_none());
}
anyhow::Ok(())
}).await.unwrap();
}
#[tokio::test(flavor = "current_thread")]
async fn service_worker_client_queue_discards_pending_messages_when_document_open_replaces_document()
{
run_page_vm_async_test(async move {
let loader = crate::network::ResourceRequestClient::new(&FetchConfig::default())?;
let (mut page, _resources, _wakes) = page_vm_with_bound_task_sources_and_owner_wake(
&loader,
Url::parse("https://service-worker-client-message.test/open-queue")?,
);
page.vm_mut().set_document_ready_state(crate::dom::native::DocumentReadyState::Loading)?;
page.vm_mut().eval("globalThis.received=[]; globalThis.savedContainer=navigator.serviceWorker; savedContainer.addEventListener('message', event => received.push(event.data));")?;
let root = page.document_lifecycle.identity().document;
let old = page.vm().service_worker_client_message_target_for_test(crate::native_bridge::OwnerDispatchScope::Top)?;
let payload = page.vm_mut().service_worker_client_message_payload_for_test("retired")?;
page.service_worker_task_sender_for_root_for_test(root).send_service_worker_client_message(service_worker_client_message(old, payload)).unwrap();
let task = page.take_service_worker_client_message_body_task_for_test().unwrap();
let result = page.apply_selected_page_service_worker_client_message_turn(task)?;
assert_eq!(result.action.target_effect, PageServiceWorkerClientMessageTargetEffect::QueuedForClientMessageQueue);
page.finish_selected_page_task_completion(result.action.into_page_task_completion(), &loader).await?;
page.vm_mut().eval("document.open(); savedContainer.startMessages();")?;
let current = page.vm().service_worker_client_message_target_for_test(crate::native_bridge::OwnerDispatchScope::Top)?;
assert_ne!(old.document_owner, current.document_owner);
assert!(page.take_service_worker_client_message_body_task_for_test().is_none());
let payload = page.vm_mut().service_worker_client_message_payload_for_test("current")?;
page.service_worker_task_sender_for_root_for_test(root).send_service_worker_client_message(service_worker_client_message(current, payload)).unwrap();
let task = page.take_service_worker_client_message_body_task_for_test().unwrap();
let result = page.apply_selected_page_service_worker_client_message_turn(task)?;
assert!(matches!(result.action.target_effect, PageServiceWorkerClientMessageTargetEffect::EventDispatchedToCurrentTarget { .. }));
page.finish_selected_page_task_completion(result.action.into_page_task_completion(), &loader).await?;
assert_eq!(page.vm_mut().eval("received.join('|')")?, "current");
anyhow::Ok(())
}).await.unwrap();
}
fn service_worker_client_message(
target: crate::types::ServiceWorkerWindowClientTarget,
payload: crate::structured_clone::V8StructuredClonePayload,
@@ -128,6 +226,7 @@ navigator.serviceWorker.onmessageerror = event => {
__serviceWorkerClientMessageErrorEvents.push("microtask");
});
};
navigator.serviceWorker.startMessages();
"listener-ready"
"#,
)?;
@@ -193,6 +292,7 @@ async fn current_service_worker_client_without_a_dispatchable_container_is_check
)?;
page_vm.vm_mut().eval(
r#"
navigator.serviceWorker.startMessages();
Object.defineProperty(navigator, "serviceWorker", {
configurable: true,
value: null,
@@ -248,6 +348,9 @@ async fn service_worker_client_message_without_listener_is_checkpoint_only() {
Url::parse("https://service-worker-client-message.test/no-listener").unwrap();
let (mut page_vm, _resource_source, _owner_wake_rx) =
page_vm_with_bound_task_sources_and_owner_wake(&loader, document_url);
page_vm
.vm_mut()
.eval("navigator.serviceWorker.startMessages();")?;
let target = page_vm.vm().service_worker_client_message_target_for_test(
crate::native_bridge::OwnerDispatchScope::Top,
)?;
@@ -143,6 +143,7 @@ impl ScriptVm {
&mut self,
event_type: &str,
) -> Result<()> {
let document_owner = self.current_main_document_task_owner();
let watchdog = self
.renderer_document_isolate
.with_renderer_document_isolate_mut(|isolate| {
@@ -178,6 +179,16 @@ impl ScriptVm {
watchdog_timeout
);
}
if event_type == "DOMContentLoaded"
&& let Some(owner) = document_owner
{
self._context_host
.borrow_mut()
.enable_service_worker_client_message_queue_for_document(
crate::native_bridge::OwnerDispatchScope::Top,
crate::native_bridge::WindowDocumentOwner::Frame(owner),
);
}
Ok(())
}
@@ -18,6 +18,7 @@ use crate::{
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ServiceWorkerClientMessageBodyEffect {
QueuedForClientMessageQueue,
EventDispatched {
event_kind: ServiceWorkerClientMessageBodyEventKind,
callback_effect: ServiceWorkerClientMessageBodyCallbackEffect,
@@ -100,7 +101,18 @@ impl ScriptVm {
.service_worker_window_owner_is_current(window_owner),
"authorized ServiceWorker client-message owner changed inside one Page turn"
);
let completion = task.into_completion();
let client_queue_delivery = task.is_client_queue_delivery();
let Some(completion) = self
._context_host
.borrow_mut()
.admit_service_worker_client_message(
window_owner,
task.into_completion(),
client_queue_delivery,
)
else {
return Ok(ServiceWorkerClientMessageBodyEffect::QueuedForClientMessageQueue);
};
self.with_default_context_scope(move |scope, _host_ptr| {
Ok(
crate::context_bootstrap::dispatch_service_worker_client_message_body(
@@ -323,6 +323,8 @@ async fn navigator_service_worker_post_message_routes_to_waiting_and_active_vers
globalThis.__serviceWorkerWaitingPostMessageProbe = "pending";
(async () => {
const sw = navigator.serviceWorker;
// This API fixture does not run a parser-driven DOMContentLoaded.
sw.startMessages();
const registration = await sw.register("worker.js", { scope: "./" });
await __moliTestActivatedServiceWorker(sw);
const updated = await sw.register("worker.js", { scope: "./" });
@@ -435,6 +437,7 @@ async fn navigator_service_worker_post_message_routes_to_installing_version() {
globalThis.__serviceWorkerInstallingPostMessageProbe = "pending";
(async () => {
const sw = navigator.serviceWorker;
sw.startMessages();
const registration = await sw.register("worker.js", { scope: "./" });
await __moliTestActivatedServiceWorker(sw);
let installingWorker = null;
@@ -1330,6 +1333,7 @@ async fn navigator_service_worker_post_message_to_redundant_worker_is_dropped()
globalThis.__serviceWorkerRedundantPostMessageProbe = "pending";
(async () => {
const sw = navigator.serviceWorker;
sw.startMessages();
const registration = await sw.register("worker-v1.js", { scope: "./" });
await __moliTestActivatedServiceWorker(sw);
const firstWorker = registration.active;
@@ -149,6 +149,8 @@ const WORKER_PARENT_PROBE: &str = r#"
if (config.mode === 'service') {
const listener = event => { navigator.serviceWorker.removeEventListener('message', listener); resolve(event.data); };
navigator.serviceWorker.addEventListener('message', listener);
// This API fixture does not run a parser-driven DOMContentLoaded.
navigator.serviceWorker.startMessages();
registration = await navigator.serviceWorker.register('/handler-sw.js?case=' + encodeURIComponent(config.name), {scope:'./'});
const active = registration.active || registration.installing || registration.waiting;
if (active.state !== 'activated') await new Promise(done => active.addEventListener('statechange', () => { if (active.state === 'activated') done(); }));