fix(service-worker): implement registration update jobs

This commit is contained in:
ldm0
2026-09-23 00:14:07 +08:00
parent b66ea67b7b
commit 4d0db1a658
25 changed files with 1475 additions and 120 deletions
@@ -15,6 +15,7 @@ use crate::worker::WorkerScriptKind;
use moli_webapi_declare::{ObjectLiteralDeclaration, WebApiObject, WebApiObjectDeclaration};
const SERVICE_WORKER_REGISTRATION_SCOPE_SLOT: &str = "__moliServiceWorkerRegistrationScope";
const SERVICE_WORKER_REGISTRATION_ID_SLOT: &str = "__moliServiceWorkerRegistrationId";
const SERVICE_WORKER_REGISTRATION_EVENTS_SLOT: &str = "__moliServiceWorkerRegistrationEvents";
const SERVICE_WORKER_REGISTRATION_WORKER_SLOT: &str = "__moliServiceWorkerRegistrationWorker";
const SERVICE_WORKER_REGISTRATION_WORKERS_SLOT: &str = "__moliServiceWorkerRegistrationWorkers";
@@ -64,6 +65,9 @@ struct ServiceWorkerRegistrationObjectDeclaration<'scope> {
#[webapi(method, callback = navigator_service_worker_unregister_callback, length = 0)]
unregister: (),
#[webapi(method, callback = service_worker_registration_update_callback, length = 0)]
update: (),
#[webapi(
method = "showNotification",
callback = service_worker_registration_show_notification_callback,
@@ -513,6 +517,7 @@ pub(in crate::context_bootstrap) fn navigator_service_worker_register_callback<'
scope,
scope_url.clone(),
resolver,
None,
request_context.owner(),
);
host.start_service_worker_runtime(
@@ -532,6 +537,7 @@ pub(in crate::context_bootstrap) fn navigator_service_worker_register_callback<'
pub(crate) fn settle_service_worker_register_completion<'s>(
scope: &mut v8::PinScope<'s, '_>,
resolver: v8::Local<'s, v8::PromiseResolver>,
update_registration: Option<v8::Local<'s, v8::Object>>,
owner: OwnerDispatchScope,
scope_url: &url::Url,
result: std::result::Result<
@@ -547,6 +553,16 @@ pub(crate) fn settle_service_worker_register_completion<'s>(
.or_else(|| state.installing())
.map(|version| version.script_url().as_str())
.unwrap_or_else(|| state.scope_url().as_str());
if let Some(registration) = update_registration {
update_service_worker_registration_object(
scope,
registration,
script_url,
ServiceWorkerRegistrationPhase::Snapshot(&state),
);
let _ = resolver.resolve(scope, registration.into());
return;
}
let registration =
if let Some(container) = service_worker_container_for_owner(scope, owner) {
build_service_worker_registration_object_for_container(
@@ -673,6 +689,11 @@ pub(crate) fn dispatch_service_worker_lifecycle_notification(
notification.document_owner,
);
for (owner, registration) in registrations {
if service_worker_registration_id_from_object(scope, registration)
!= Some(notification.registration.registration_id())
{
continue;
}
let previous_owner_context = owner.enter(scope);
update_service_worker_registration_object(
scope,
@@ -1010,12 +1031,20 @@ fn remember_service_worker_container_registration<'s>(
return;
};
let scope_url = scope_url.to_rust_string_lossy(scope);
if service_worker_container_cached_registration_for_scope(scope, container, &scope_url)
.is_some()
{
return;
}
let cache = service_worker_container_registration_cache(scope, container);
if let Some(previous) =
service_worker_container_cached_registration_for_scope(scope, container, &scope_url)
{
for index in 0..cache.length() {
if cache
.get_index(scope, index)
.is_some_and(|value| value.strict_equals(previous.into()))
{
let _ = cache.set_index(scope, index, registration.into());
return;
}
}
}
array_push_value(scope, cache, registration.into());
}
@@ -1310,6 +1339,95 @@ fn service_worker_container_owner_scope<'s>(
service_worker_owner_scope_from_object(scope, container)
}
fn service_worker_registration_id_from_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
registration: v8::Local<'s, v8::Object>,
) -> Option<crate::service_worker_runtime::ServiceWorkerRegistrationId> {
let value = get_private_value(scope, registration, SERVICE_WORKER_REGISTRATION_ID_SLOT)?;
let value = v8::Local::<v8::BigInt>::try_from(value).ok()?;
let (value, lossless) = value.u64_value();
lossless.then(|| {
crate::service_worker_runtime::ServiceWorkerRegistrationId::from_u64_for_binding(value)
})
}
fn service_worker_registration_update_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s, v8::Value>,
) {
let Some(resolver) = v8::PromiseResolver::new(scope) else {
return;
};
rv.set(resolver.get_promise(scope).into());
let Some(registration_id) = service_worker_registration_id_from_object(scope, args.this())
else {
reject_service_worker_promise_with_type_error(
scope,
resolver,
"Illegal invocation of ServiceWorkerRegistration.update",
);
return;
};
let Some(scope_url) = service_worker_registration_scope_from_this(scope, args.this()) else {
reject_service_worker_promise_with_dom_exception(
scope,
resolver,
"The registration is unavailable.",
"InvalidStateError",
);
return;
};
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
reject_service_worker_promise_with_dom_exception(
scope,
resolver,
"The registration owner is unavailable.",
"InvalidStateError",
);
return;
};
let host = unsafe { &mut *host_ptr };
let owner = service_worker_owner_scope_from_object(scope, args.this());
let Some(request_context) = host.service_worker_window_request_context(owner) else {
reject_service_worker_promise_with_dom_exception(
scope,
resolver,
"The registration Document is no longer active.",
"InvalidStateError",
);
return;
};
let Some(request_client) = host
.document_resource_loader_for_window_owner(request_context.owner().window_document_owner())
.map(|loader| loader.request_client().clone())
else {
reject_service_worker_promise_with_dom_exception(
scope,
resolver,
"The registration Document is no longer active.",
"InvalidStateError",
);
return;
};
let (request_id, document_owner, completion_tx) = host
.register_pending_service_worker_register(
scope,
scope_url,
resolver,
Some(args.this()),
request_context.owner(),
);
host.start_service_worker_registration_update(
registration_id,
&request_context,
request_client,
request_id,
document_owner,
completion_tx,
);
}
fn navigator_service_worker_unregister_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -2364,6 +2482,13 @@ fn build_service_worker_registration_object<'s>(
navigation_preload,
);
let registration = bind_declared_service_worker_object(scope, &declaration);
let id = v8::BigInt::new_from_u64(scope, resolved.registration_id.as_u64());
set_private_value(
scope,
registration,
SERVICE_WORKER_REGISTRATION_ID_SLOT,
id.into(),
);
mark_service_worker_registration_event_target(scope, registration);
if let Some(scope_value) = v8_string(scope, scope_url) {
define_non_enumerable_value_property(
@@ -2408,6 +2533,12 @@ fn build_service_worker_registration_object_for_container<'s>(
let owner = service_worker_container_owner_scope(scope, container);
if let Some(registration) =
service_worker_container_cached_registration_for_scope(scope, container, scope_url)
&& match &phase {
ServiceWorkerRegistrationPhase::Snapshot(snapshot) => {
service_worker_registration_id_from_object(scope, registration)
== Some(snapshot.registration_id())
}
}
{
service_worker_worker_set_owner_scope(scope, registration, owner);
update_service_worker_registration_object(scope, registration, script_url, phase);
@@ -2441,6 +2572,7 @@ fn watch_service_worker_registration_object_lifecycle<'s>(
}
struct ResolvedServiceWorkerRegistrationPhase<'s> {
registration_id: crate::service_worker_runtime::ServiceWorkerRegistrationId,
installing: v8::Local<'s, v8::Value>,
waiting: v8::Local<'s, v8::Value>,
active: v8::Local<'s, v8::Value>,
@@ -2496,6 +2628,7 @@ fn resolve_service_worker_registration_phase<'s>(
})
.unwrap_or_else(|| build_service_worker_object(scope, script_url, "redundant"));
ResolvedServiceWorkerRegistrationPhase {
registration_id: snapshot.registration_id(),
installing,
waiting,
active,
@@ -2533,6 +2666,11 @@ fn update_service_worker_registration_object<'s>(
) {
let resolved =
resolve_service_worker_registration_phase(scope, Some(registration), script_url, phase);
if service_worker_registration_id_from_object(scope, registration)
!= Some(resolved.registration_id)
{
return;
}
set_service_worker_registration_worker_values(
scope,
registration,
@@ -70,6 +70,7 @@ pub(crate) struct PendingServiceWorkerRegister {
pub(crate) scope_url: Url,
pub(crate) context: v8::Global<v8::Context>,
pub(crate) resolver: v8::Global<v8::PromiseResolver>,
pub(crate) update_registration: Option<v8::Global<v8::Object>>,
}
pub(crate) struct PendingServiceWorkerReady {
@@ -444,6 +445,7 @@ impl JsContextHost {
scope: &mut v8::PinScope<'_, '_>,
scope_url: Url,
resolver: v8::Local<'_, v8::PromiseResolver>,
update_registration: Option<v8::Local<'_, v8::Object>>,
owner: ServiceWorkerWindowOwner,
) -> (
u64,
@@ -458,6 +460,8 @@ impl JsContextHost {
scope_url,
context: v8::Global::new(scope, scope.get_current_context()),
resolver: v8::Global::new(scope, resolver),
update_registration: update_registration
.map(|registration| v8::Global::new(scope, registration)),
},
);
tracing::debug!(
@@ -673,6 +677,54 @@ impl JsContextHost {
self.watch_pending_service_worker_ready();
}
pub(crate) fn start_service_worker_registration_update(
&mut self,
registration_id: crate::service_worker_runtime::ServiceWorkerRegistrationId,
request_context: &ServiceWorkerWindowRequestContext,
request_client: crate::network::ResourceRequestClient,
request_id: u64,
document_owner: WindowDocumentOwner,
completion_tx: crate::page_task_queue::RendererPageServiceWorkerTaskSender,
) {
self.ensure_service_worker_client_for_request_context(request_context);
self.browser_context_runtime
.service_worker_runtime()
.start_registration_update(
crate::service_worker_runtime::ServiceWorkerRegistrationUpdate {
registration_id,
caller_version_id: None,
storage_key: request_context.serialized_storage_key(),
document_url: request_context.document_url().clone(),
request_client,
network_policy: WorkerNetworkPolicy {
secure_context: moli_url::is_potentially_trustworthy_url(
request_context.document_url(),
),
permission_overrides: self.permission_overrides().to_vec(),
extra_http_headers: self.extra_http_headers().to_vec(),
network_offline: self.network_offline(),
blocked_url_patterns: self.blocked_url_patterns().to_vec(),
network_partition_key: None,
fetch_subresource_interception_enabled: self
.fetch_subresource_interception_enabled(),
fetch_subresource_interception_resource_type: self
.fetch_subresource_interception_resource_type(),
},
worker_context_runtime: self.browser_context_runtime.worker_context_runtime(),
broadcast_channel_top_level_site: Some(
request_context.storage_key_top_level_site(),
),
indexed_db_manager: self.indexed_db_manager(),
storage_bucket_store: Some(self.storage_bucket_store()),
completion: crate::service_worker_runtime::ServiceWorkerRegisterJob::Page {
request_id,
document_owner,
completion_tx,
},
},
);
}
pub(crate) fn watch_pending_service_worker_ready(&mut self) -> bool {
let requests = self.pending_service_worker_ready_requests();
let mut attached_any = false;
@@ -829,16 +881,17 @@ impl JsContextHost {
let owner = request_context.owner();
let storage_key = request_context.serialized_storage_key();
self.compact_service_worker_registration_watchers();
if self
.service_worker_registration_watchers
.iter()
.any(|watcher| {
watcher.owner == owner
&& watcher.scope_url == scope_url
&& watcher.storage_key == storage_key
&& !watcher.registration.is_empty()
})
if let Some(watcher) =
self.service_worker_registration_watchers
.iter_mut()
.find(|watcher| {
watcher.owner == owner
&& watcher.scope_url == scope_url
&& watcher.storage_key == storage_key
&& !watcher.registration.is_empty()
})
{
watcher.registration = v8::Weak::new(scope, registration);
return;
}
self.service_worker_registration_watchers
+14 -4
View File
@@ -291,7 +291,10 @@ impl ResourceRequestClient {
cancel_handle: FetchCancelHandle,
) -> Result<NetworkFetchResult<Response>> {
let request = self.apply_network_policy(request)?;
if request.auth_requires_buffered_transport() || !request.follow_redirects {
if request.auth_requires_buffered_transport()
|| (!request.follow_redirects
&& request.redirect_mode != moli_fetch::RequestRedirectMode::Error)
{
return self
.resource_runtime
.client()
@@ -714,12 +717,16 @@ impl ResourceRequestClient {
request: Request,
cancel_handle: FetchCancelHandle,
) -> Result<Response> {
if request.auth_requires_buffered_transport() || !request.follow_redirects {
if request.auth_requires_buffered_transport()
|| (!request.follow_redirects
&& request.redirect_mode != moli_fetch::RequestRedirectMode::Error)
{
// Challenge-response schemes still need libcurl's buffered auth
// retry behavior until the streaming collector models
// intermediate authentication challenges explicitly. Manual
// redirect callers need the intermediate 3xx response before raw
// streaming starts.
// streaming starts. Error-mode script fetches can use the raw
// stream and its HTTP cache; their caller rejects any 3xx status.
return self
.resource_runtime
.client()
@@ -999,7 +1006,10 @@ impl ResourceRequestClient {
cancel_handle: Option<FetchCancelHandle>,
) -> Result<Response> {
let request = self.apply_network_policy(request)?;
if request.auth_requires_buffered_transport() || !request.follow_redirects {
if request.auth_requires_buffered_transport()
|| (!request.follow_redirects
&& request.redirect_mode != moli_fetch::RequestRedirectMode::Error)
{
// Digest auth retries are still completed inside libcurl on the
// buffered path. Keep auth requests there until the streaming
// collector can distinguish intermediate auth challenges from
@@ -45,12 +45,17 @@ impl ScriptVm {
let context = pending.context;
let resolver = pending.resolver;
let scope_url = pending.scope_url;
let update_registration = pending.update_registration;
let context_ptr: *const v8::Global<v8::Context> = &context;
self.with_context_scope_by_ptr(context_ptr, move |scope, _host_ptr| {
let resolver = v8::Local::new(scope, &resolver);
let update_registration = update_registration
.as_ref()
.map(|value| v8::Local::new(scope, value));
crate::context_bootstrap::settle_service_worker_register_completion(
scope,
resolver,
update_registration,
owner.dispatch_scope(),
&scope_url,
completion.result,
@@ -15655,7 +15655,7 @@ async fn navigator_service_worker_intercepts_connected_stylesheet_link() {
}
#[tokio::test]
async fn navigator_service_worker_update_check_failure_rejects_register_with_type_error() {
async fn navigator_service_worker_update_check_preserves_mime_security_error() {
let (base_url, server) = spawn_service_worker_response_server_with_headers(vec![
(
"/app/worker.js",
@@ -15717,7 +15717,7 @@ async fn navigator_service_worker_update_check_failure_rejects_register_with_typ
&browser_context_runtime,
&loader,
"String(globalThis.__serviceWorkerUpdateFailureProbe)",
r#"{"name":"TypeError","isTypeError":true,"isDomException":false,"messageIncludesNosniff":true}"#,
r#"{"name":"SecurityError","isTypeError":false,"isDomException":true,"messageIncludesNosniff":true}"#,
)
.await;
@@ -18888,6 +18888,263 @@ async fn navigator_service_worker_update_via_cache_option_reflects_registration(
.expect("service worker updateViaCache script server should finish");
}
#[tokio::test]
async fn navigator_service_worker_update_preserves_identity_and_rejects_stale_receivers() {
let (base_url, server) = spawn_service_worker_response_server(vec![
("/app/workers/sw.js", "text/javascript", "// first"),
("/app/workers/sw.js", "text/javascript", "// second"),
("/app/workers/sw.js", "text/javascript", "// second"),
(
"/app/workers/sw.js",
"text/javascript",
"// new registration",
),
("/app/workers/sw.js", "text/javascript", "// new update"),
])
.await;
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
let (mut vm, runtime) = new_service_worker_page_test_vm_with_loader_and_browser_context_runtime(
&format!("{base_url}/app/page.html"),
&loader,
);
vm.eval(r#"
globalThis.__updateIdentity = 'pending';
(async () => {
const activated = async r => {
const w = r.installing || r.waiting || r.active;
if (w.state !== 'activated') await new Promise(resolve => w.addEventListener('statechange', () => {
if (w.state === 'activated') resolve();
}));
};
const errorName = async p => { try { await p; return 'success'; } catch(e) { return e.name; } };
const register = () => navigator.serviceWorker.register('workers/sw.js', {scope:'workers/'});
const r = await register();
let found = 0;
r.addEventListener('updatefound', () => found++);
await activated(r);
const initial = found;
const receivers = [];
for (const fake of [{}, Object.create(r), new Proxy(r, {}), null]) {
const p = Reflect.apply(r.update, fake, []);
receivers.push(p instanceof Promise && await errorName(p) === 'TypeError');
}
const before = r.active;
const order = [];
r.addEventListener('updatefound', () => order.push('event'), {once:true});
const updated = await r.update(); order.push('promise');
await activated(r);
const changed = before !== r.active;
const current = r.active;
const unchanged = await r.update({get unused(){throw new Error('argument read');}});
const stable = unchanged === r && current === r.active && found === 2;
await r.unregister();
const removed = await errorName(r.update());
const replacement = await register();
await activated(replacement);
const stale = await errorName(r.update());
const again = await replacement.update();
await activated(replacement);
const lookup = await navigator.serviceWorker.getRegistration('workers/client');
globalThis.__updateIdentity = [r.update.name, r.update.length, initial,
receivers.every(Boolean), updated === r, changed, order.join(','), stable,
removed, stale, replacement !== r, again === replacement, lookup === replacement].join('|');
await replacement.unregister();
})().catch(e => globalThis.__updateIdentity = 'error:' + e);
"#).expect("update identity probe");
drain_service_worker_test_until_eval_equals(&mut vm, &runtime, &loader,
"String(globalThis.__updateIdentity)",
"update|0|1|true|true|true|promise,event|true|InvalidStateError|InvalidStateError|true|true|true",
).await;
server.await.expect("update identity server");
}
#[tokio::test]
async fn navigator_service_worker_update_reuses_failed_import_responses() {
const MAIN: &str = "importScripts('a.js', 'z.js');";
let (base_url, server) = spawn_service_worker_response_server(vec![
("/app/workers/sw.js", "text/javascript", MAIN),
("/app/workers/a.js", "text/javascript", "// a"),
("/app/workers/z.js", "text/javascript", "// z1"),
("/app/workers/sw.js", "text/javascript", MAIN),
("/app/workers/a.js", "text/html", "missing import"),
("/app/workers/z.js", "text/javascript", "// z2"),
(
"/app/workers/sw.js",
"text/javascript",
"importScripts('z.js');",
),
("/app/workers/z.js", "text/javascript", "// z3"),
])
.await;
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
let (mut vm, runtime) = new_service_worker_page_test_vm_with_loader_and_browser_context_runtime(
&format!("{base_url}/app/page.html"),
&loader,
);
vm.eval(r#"
globalThis.__updateImports = 'pending';
(async () => {
const activated = async r => {
const w = r.installing || r.waiting || r.active;
if (w.state !== 'activated') await new Promise(resolve => w.addEventListener('statechange', () => {
if (w.state === 'activated') resolve();
}));
};
const r = await navigator.serviceWorker.register('workers/sw.js', {scope:'workers/'});
await activated(r);
const before = r.active;
let found = 0;
r.addEventListener('updatefound', () => found++);
let error = 'success';
try { await r.update(); } catch(e) { error = e.name; }
const failed = error === 'TypeError' && found === 0 && r.active === before && r.installing === null;
const updated = await r.update();
await activated(r);
globalThis.__updateImports = [failed, updated === r, r.active !== before, found].join('|');
await r.unregister();
})().catch(e => globalThis.__updateImports = 'error:' + e);
"#).expect("update import response probe");
drain_service_worker_test_until_eval_equals(
&mut vm,
&runtime,
&loader,
"String(globalThis.__updateImports)",
"true|true|true|1",
)
.await;
server
.await
.expect("update import server should not refetch failed responses");
}
#[tokio::test]
async fn navigator_service_worker_update_discards_prefetched_but_unused_imports_after_install() {
const MAIN: &str = r#"
importScripts('flag.js');
if (!self.skipOld) importScripts('unused.js');
onmessage = e => {
try { importScripts('unused.js'); e.ports[0].postMessage('success'); }
catch(error) { e.ports[0].postMessage(error.name); }
};
"#;
let (base_url, server) = spawn_service_worker_response_server(vec![
("/app/workers/sw.js", "text/javascript", MAIN),
(
"/app/workers/flag.js",
"text/javascript",
"self.skipOld = false;",
),
("/app/workers/unused.js", "text/javascript", "// unused"),
("/app/workers/sw.js", "text/javascript", MAIN),
(
"/app/workers/flag.js",
"text/javascript",
"self.skipOld = true;",
),
("/app/workers/unused.js", "text/javascript", "// unused"),
])
.await;
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
let (mut vm, runtime) = new_service_worker_page_test_vm_with_loader_and_browser_context_runtime(
&format!("{base_url}/app/page.html"),
&loader,
);
vm.eval(r#"
globalThis.__unusedUpdateImport = 'pending';
(async () => {
const activated = async r => {
const w = r.installing || r.waiting || r.active;
if (w.state !== 'activated') await new Promise(resolve => w.addEventListener('statechange', () => {
if (w.state === 'activated') resolve();
}));
};
const r = await navigator.serviceWorker.register('workers/sw.js', {scope:'workers/'});
await activated(r);
await r.update(); await activated(r);
const value = await new Promise(resolve => {
const c = new MessageChannel();
c.port1.onmessage = e => { c.port1.close(); resolve(e.data); };
r.active.postMessage('probe', [c.port2]);
});
globalThis.__unusedUpdateImport = value;
await r.unregister();
})().catch(e => globalThis.__unusedUpdateImport = 'error:' + e);
"#).expect("unused update import probe");
drain_service_worker_test_until_eval_equals(
&mut vm,
&runtime,
&loader,
"String(globalThis.__unusedUpdateImport)",
"NetworkError",
)
.await;
server
.await
.expect("prefetched imports should not be fetched again");
}
#[tokio::test]
async fn navigator_service_worker_update_in_worker_preserves_events_and_install_rejection() {
let (base_url, server) = spawn_service_worker_response_server(vec![
("/app/workers/sw.js", "text/javascript", r#"
const original = registration;
const seen = [];
let installError;
original.addEventListener('updatefound', () => seen.push('before'));
original.onupdatefound = function(e) {
seen.push([this === original, e.target === original, e.currentTarget === original,
e instanceof Event, e.isTrusted, e.bubbles, e.cancelable].join(':'));
};
original.addEventListener('updatefound', () => seen.push('after'));
oninstall = e => e.waitUntil(original.update().then(
() => installError = 'success', e => installError = e.name));
onactivate = () => seen.push('activate');
onmessage = e => {
if (e.data === 'sample') e.ports[0].postMessage(seen.join(',') + '|' + installError);
else e.waitUntil(original.update().then(r => e.ports[0].postMessage(r === original)));
};
Object.defineProperty(self, 'registration', {get(){throw new Error('public registration read');}});
"#),
("/app/workers/sw.js", "text/javascript", "// updated"),
]).await;
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
let (mut vm, runtime) = new_service_worker_page_test_vm_with_loader_and_browser_context_runtime(
&format!("{base_url}/app/page.html"),
&loader,
);
vm.eval(
r#"
globalThis.__workerUpdate = 'pending';
(async () => {
const r = await navigator.serviceWorker.register('workers/sw.js', {scope:'workers/'});
const w = r.installing;
await new Promise(resolve => w.addEventListener('statechange', () => {
if (w.state === 'activated') resolve();
}));
const message = data => new Promise(resolve => {
const c = new MessageChannel();
c.port1.onmessage = e => { c.port1.close(); resolve(e.data); };
w.postMessage(data, [c.port2]);
});
const first = await message('sample');
const same = await message('update');
globalThis.__workerUpdate = first + '|' + same;
await r.unregister();
})().catch(e => globalThis.__workerUpdate = 'error:' + e);
"#,
)
.expect("worker update probe");
drain_service_worker_test_until_eval_equals(
&mut vm,
&runtime,
&loader,
"String(globalThis.__workerUpdate)",
"before,true:true:true:true:true:false:false,after,activate|InvalidStateError|true",
)
.await;
server.await.expect("worker update server");
}
#[tokio::test]
async fn navigator_service_worker_update_via_cache_all_uses_fresh_main_script_cache() {
let cache_dir = service_worker_http_cache_test_root("update-via-cache-all");
@@ -18907,12 +19164,33 @@ async fn navigator_service_worker_update_via_cache_all_uses_fresh_main_script_ca
.await;
let mut fetch_config = moli_fetch::FetchConfig::default();
fetch_config.set_http_cache_dir(Some(cache_dir.display().to_string()));
let loader = ResourceRequestClient::new(&fetch_config).expect("loader");
let (mut vm, browser_context_runtime) =
new_service_worker_page_test_vm_with_loader_and_browser_context_runtime(
&format!("{base_url}/app/page.html"),
&loader,
);
let browser_context_runtime = crate::runtime::RendererBrowserContextRuntime::new();
let resource_runtime = browser_context_runtime
.replace_browser_resource_runtime(crate::network::BrowserResourceRuntimeOwner::new(
&fetch_config,
moli_cookie_jar::new_shared_browser_cookie_store(),
))
.expect("cache-enabled browser resource runtime");
let loader = ResourceRequestClient::from_browser_resource_runtime(resource_runtime);
let mut vm = crate::runtime::PageVmTaskExecutorTestHarness::new_with_browser_context_runtime(
url::Url::parse(&format!("{base_url}/app/page.html")).unwrap(),
&loader,
browser_context_runtime.handle(),
);
// Execute register and update with the same Document transport, including
// its browser-site context used to partition the HTTP cache.
let loader = vm
._context_host
.borrow()
.current_main_document_resource_loader()
.expect("current Document loader")
.request_client()
.clone();
assert!(
loader
.browser_resource_runtime()
.matches_fetch_config(&fetch_config)
);
vm.eval(
r#"
@@ -18933,6 +19211,8 @@ async fn navigator_service_worker_update_via_cache_all_uses_fresh_main_script_ca
scope: "./",
updateViaCache: "all"
});
const updated = await registration.update();
if (updated !== registration) throw new Error('update identity changed');
const all = await sw.getRegistrations();
globalThis.__serviceWorkerUpdateViaCacheAllProbe = [
updatefoundCount,
@@ -66,7 +66,9 @@ pub(crate) use ids::{
ServiceWorkerClientId, ServiceWorkerClientIdAllocator, ServiceWorkerEventId,
ServiceWorkerRegistrationId, ServiceWorkerVersionId,
};
pub(crate) use jobs::ServiceWorkerUnregisterStart;
pub(crate) use jobs::{
ServiceWorkerRegisterJob, ServiceWorkerRegistrationUpdate, ServiceWorkerUnregisterStart,
};
pub(crate) use owner_wake::{
ServiceWorkerRuntimeOwnerWake, ServiceWorkerRuntimeOwnerWakeSender,
service_worker_owner_wake_channel,
@@ -2,6 +2,7 @@
pub(crate) enum ServiceWorkerRegistrationErrorKind {
Abort,
Install,
InvalidState,
Network,
NotFound,
ScriptEvaluateFailed,
@@ -15,6 +16,7 @@ impl ServiceWorkerRegistrationErrorKind {
match self {
Self::Abort | Self::Install | Self::ScriptEvaluateFailed => "AbortError",
Self::Network => "NetworkError",
Self::InvalidState => "InvalidStateError",
Self::NotFound => "NotFoundError",
Self::Security => "SecurityError",
Self::Type => unreachable!("type errors are not DOMExceptions"),
@@ -227,6 +227,16 @@ impl RendererServiceWorkerHost {
true
}
pub(super) fn dispatch_registration_update_found(&self) {
let state = self.state.lock();
if let RendererServiceWorkerHostState::Running {
handle: Some(handle),
} = &*state
{
handle.dispatch_service_worker_registration_update_found();
}
}
pub(super) fn dispatch_fetch_event(&self, event: ServiceWorkerFetchEvent) -> bool {
let state = self.state.lock();
let RendererServiceWorkerHostState::Running {
@@ -991,6 +1001,7 @@ fn spawn_service_worker(
script_map.imported_scripts,
script_map.can_import_new_scripts,
)
.with_service_worker_updated_script_resources(script.updated_imports)
.with_global_kind(crate::worker::WorkerGlobalKind::Service {
registration_id: params.registration_id,
version_id: params.run_owner.version_id(),
@@ -6,7 +6,7 @@ use url::Url;
use crate::{
network::{BrowserResourceRuntimeBinding, ResourceRequestClient},
page_task_queue::RendererPageServiceWorkerTaskSender,
runtime::{RendererBrowserContextRuntime, RendererWorkerContextRuntime},
runtime::RendererWorkerContextRuntime,
types::{ServiceWorkerRegisterCompletion, ServiceWorkerUnregisterCompletion},
worker::{WorkerNetworkPolicy, WorkerScriptKind},
};
@@ -39,10 +39,16 @@ pub(crate) struct ServiceWorkerLaunchParams {
}
#[derive(Clone, Debug)]
pub(super) struct ServiceWorkerRegisterJob {
pub(super) request_id: u64,
pub(super) document_owner: crate::window_document_identity::WindowDocumentOwner,
pub(super) completion_tx: RendererPageServiceWorkerTaskSender,
pub(crate) enum ServiceWorkerRegisterJob {
Page {
request_id: u64,
document_owner: crate::window_document_identity::WindowDocumentOwner,
completion_tx: RendererPageServiceWorkerTaskSender,
},
Worker {
request_id: u64,
completion_tx: tokio::sync::mpsc::UnboundedSender<crate::worker::WorkerMessage>,
},
}
impl ServiceWorkerRegisterJob {
@@ -53,13 +59,30 @@ impl ServiceWorkerRegisterJob {
ServiceWorkerRegistrationError,
>,
) {
let _ = self
.completion_tx
.send_service_worker_register(ServiceWorkerRegisterCompletion {
request_id: self.request_id,
document_owner: self.document_owner,
result,
});
match self {
Self::Page {
request_id,
document_owner,
completion_tx,
} => {
let _ =
completion_tx.send_service_worker_register(ServiceWorkerRegisterCompletion {
request_id,
document_owner,
result,
});
}
Self::Worker {
request_id,
completion_tx,
} => {
let _ =
completion_tx.send(crate::worker::WorkerMessage::ServiceWorkerUpdateResult {
request_id,
result: Box::new(result),
});
}
}
}
pub(super) fn send_all(
@@ -75,6 +98,20 @@ impl ServiceWorkerRegisterJob {
}
}
pub(crate) struct ServiceWorkerRegistrationUpdate {
pub(crate) registration_id: ServiceWorkerRegistrationId,
pub(crate) caller_version_id: Option<ServiceWorkerVersionId>,
pub(crate) storage_key: String,
pub(crate) document_url: Url,
pub(crate) request_client: ResourceRequestClient,
pub(crate) network_policy: WorkerNetworkPolicy,
pub(crate) worker_context_runtime: RendererWorkerContextRuntime,
pub(crate) broadcast_channel_top_level_site: Option<String>,
pub(crate) indexed_db_manager: Option<crate::context_bootstrap::WeakIndexedDbManager>,
pub(crate) storage_bucket_store: Option<crate::context_bootstrap::SharedStorageBucketStore>,
pub(crate) completion: ServiceWorkerRegisterJob,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum ServiceWorkerRegisterJobPhase {
Initial,
@@ -89,6 +126,7 @@ pub(super) enum ServiceWorkerRegisterJobPhase {
#[derive(Clone, Debug)]
pub(super) struct ServiceWorkerPendingRegisterJob {
pub(super) is_update: bool,
phase: ServiceWorkerRegisterJobPhase,
skip_waiting_after_install: bool,
callbacks: Vec<ServiceWorkerRegisterJob>,
@@ -109,6 +147,7 @@ impl ServiceWorkerPendingRegisterJob {
) -> Self {
Self {
phase: ServiceWorkerRegisterJobPhase::Initial,
is_update: false,
skip_waiting_after_install,
callbacks,
resolved_result: None,
@@ -250,7 +289,7 @@ mod tests {
#[test]
fn pending_register_job_resolves_when_install_starts() {
let queue = crate::page_task_queue::RendererPageServiceWorkerTestHarness::new();
let mut job = ServiceWorkerPendingRegisterJob::new(vec![ServiceWorkerRegisterJob {
let mut job = ServiceWorkerPendingRegisterJob::new(vec![ServiceWorkerRegisterJob::Page {
request_id: 1,
document_owner: crate::window_document_identity::WindowDocumentOwner::for_test(1),
completion_tx: queue.sender(),
@@ -353,6 +392,9 @@ impl ServiceWorkerQueuedUnregisterJob {
#[derive(Clone)]
pub(super) struct ServiceWorkerQueuedRegisterJob {
/// Web API update jobs retain their registration identity and cannot
/// create a new registration or clear a pending unregistration.
pub(super) update_registration_id: Option<ServiceWorkerRegistrationId>,
pub(super) script_url: Url,
pub(super) scope_url: Url,
pub(super) document_url: Url,
@@ -365,7 +407,7 @@ pub(super) struct ServiceWorkerQueuedRegisterJob {
pub(super) force_update_page_load_waiter_ids: Vec<u64>,
pub(super) request_client: ResourceRequestClient,
pub(super) network_policy: WorkerNetworkPolicy,
pub(super) browser_context_runtime: RendererBrowserContextRuntime,
pub(super) worker_context_runtime: RendererWorkerContextRuntime,
pub(super) broadcast_channel_top_level_site: Option<String>,
pub(super) indexed_db_manager: Option<crate::context_bootstrap::WeakIndexedDbManager>,
pub(super) storage_bucket_store: Option<crate::context_bootstrap::SharedStorageBucketStore>,
@@ -381,7 +423,8 @@ impl ServiceWorkerQueuedRegisterJob {
}
pub(super) fn matches_registration_job(&self, other: &Self) -> bool {
self.scope_url == other.scope_url
self.update_registration_id == other.update_registration_id
&& self.scope_url == other.scope_url
&& self.storage_key == other.storage_key
&& self.script_url == other.script_url
&& self.script_kind == other.script_kind
@@ -461,6 +504,7 @@ pub(super) enum ServiceWorkerMainScriptUpdateCheckStart {
impl std::fmt::Debug for ServiceWorkerQueuedRegisterJob {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ServiceWorkerQueuedRegisterJob")
.field("update_registration_id", &self.update_registration_id)
.field("script_url", &self.script_url)
.field("scope_url", &self.scope_url)
.field("document_url", &self.document_url)
@@ -718,7 +762,7 @@ impl ServiceWorkerVersionLaunchConfig {
document_url: job.document_url.clone(),
request_client: ServiceWorkerRequestClientSource::Captured(job.request_client.clone()),
network_policy: job.network_policy.clone(),
worker_context_runtime: job.browser_context_runtime.worker_context_runtime(),
worker_context_runtime: job.worker_context_runtime.clone(),
broadcast_channel_top_level_site: job.broadcast_channel_top_level_site.clone(),
indexed_db_manager: job.indexed_db_manager.clone(),
storage_bucket_store: job.storage_bucket_store.clone(),
@@ -2,8 +2,8 @@ use std::time::Instant;
use moli_crypto::sha256_hex;
use moli_fetch::{
FetchCancelHandle, Request, RequestCacheMode, RequestCredentialsMode, ResponseHead,
ScriptFetchRequestMetadata,
FetchCancelHandle, Request, RequestCacheMode, RequestCredentialsMode, RequestMode,
RequestRedirectMode, ResponseHead, ScriptFetchRequestMetadata,
};
use url::Url;
@@ -12,6 +12,7 @@ use crate::network::ResourceRequestClient;
use crate::worker::WorkerScriptResourceKind;
use super::{
errors::{ServiceWorkerRegistrationError, ServiceWorkerRegistrationErrorKind},
jobs::ServiceWorkerLaunchParams,
path_restriction::{
service_worker_allowed_header_value, verify_service_worker_script_path_restriction,
@@ -41,6 +42,7 @@ impl ServiceWorkerScriptLoadParams {
#[derive(Clone)]
pub(super) struct ServiceWorkerScriptUpdateCheckParams {
pub(super) script_kind: crate::worker::WorkerScriptKind,
pub(super) main_script: ServiceWorkerScriptLoadParams,
pub(super) newest_main_body_sha256: String,
pub(super) imported_scripts: Vec<ServiceWorkerScriptResource>,
@@ -125,6 +127,7 @@ pub(super) struct ServiceWorkerScriptMapSnapshot {
}
pub(super) struct LoadedServiceWorkerScript {
pub(super) updated_imports: crate::worker::WorkerScriptUpdateResources,
pub(super) resource: ServiceWorkerScriptResource,
pub(super) source: String,
pub(super) response_referrer_policy: Option<String>,
@@ -147,6 +150,7 @@ impl LoadedServiceWorkerScript {
);
let response_content_security_reporting_endpoints = crate::content_security_policy::content_security_policy_reporting_endpoints_from_headers(&resource.headers, &resource.final_url);
Some(Self {
updated_imports: Default::default(),
resource,
source,
response_referrer_policy,
@@ -173,6 +177,7 @@ pub(super) enum ServiceWorkerScriptUpdateCheckChange {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum ServiceWorkerScriptUpdateCheckFailureStatus {
ScriptLoadFailed,
Security,
Internal,
Stale,
}
@@ -181,6 +186,7 @@ impl ServiceWorkerScriptUpdateCheckFailureStatus {
pub(super) fn as_str(self) -> &'static str {
match self {
Self::ScriptLoadFailed => "script-load-failed",
Self::Security => "security",
Self::Internal => "internal",
Self::Stale => "stale",
}
@@ -229,18 +235,21 @@ pub(super) fn load_service_worker_script_source(
load_service_worker_script_source_for_params(
&ServiceWorkerScriptLoadParams::from_launch_params(params),
)
.map_err(|error| error.message)
}
pub(super) fn load_service_worker_script_source_for_params(
params: &ServiceWorkerScriptLoadParams,
) -> Result<LoadedServiceWorkerScript, String> {
) -> Result<LoadedServiceWorkerScript, ServiceWorkerRegistrationError> {
let request_client = &params.request_client;
let mut request_url = params.script_url.clone();
request_url.set_fragment(None);
let response_started_at = Instant::now();
let request = Request::new("GET", request_url.as_str(), None, vec![])
.map_err(|error| error.to_string())?
.map_err(|error| ServiceWorkerRegistrationError::network(error.to_string()))?
.with_credentials_mode(RequestCredentialsMode::SameOrigin)
.with_request_mode(RequestMode::SameOrigin)
.with_redirect_mode(RequestRedirectMode::Error)
.with_cache_mode(params.cache_mode)
.with_page_network_policy()
.with_initiator_url(&params.document_url)
@@ -251,7 +260,9 @@ pub(super) fn load_service_worker_script_source_for_params(
let response = request_client
.fetch_text_for_worker_blocking_boundary_with_cancel(request, FetchCancelHandle::new())
.map_err(|error| {
format!("Failed to load service worker script `{request_url}`: {error}")
ServiceWorkerRegistrationError::network(format!(
"Failed to load service worker script `{request_url}`: {error}"
))
})?;
let response_time_ms = response_started_at
.elapsed()
@@ -263,21 +274,30 @@ pub(super) fn load_service_worker_script_source_for_params(
&response.final_url,
)
.map_err(|message| {
format!("Failed to load service worker script `{request_url}`: {message}")
ServiceWorkerRegistrationError::new(
ServiceWorkerRegistrationErrorKind::Security,
format!("Failed to load service worker script `{request_url}`: {message}"),
)
})?;
moli_fetch::ensure_http_status_success(response.final_url.as_str(), response.status, false)
.map_err(|error| error.to_string())?;
.map_err(|error| ServiceWorkerRegistrationError::network(error.to_string()))?;
crate::worker::ensure_worker_script_mime_acceptable(
&response.final_url,
&response.headers,
response.body_bytes(),
)?;
)
.map_err(|message| {
ServiceWorkerRegistrationError::new(ServiceWorkerRegistrationErrorKind::Security, message)
})?;
let service_worker_allowed_header = service_worker_allowed_header_value(&response.headers);
verify_service_worker_script_path_restriction(
&params.scope_url,
&response.final_url,
service_worker_allowed_header.as_deref(),
)?;
)
.map_err(|message| {
ServiceWorkerRegistrationError::new(ServiceWorkerRegistrationErrorKind::Security, message)
})?;
let response_referrer_policy =
crate::referrer_policy::response_referrer_policy_from_headers(&response.headers);
let response_content_security_policies =
@@ -311,6 +331,7 @@ pub(super) fn load_service_worker_script_source_for_params(
final_url.set_fragment(params.script_url.fragment());
resource.final_url = final_url;
Ok(LoadedServiceWorkerScript {
updated_imports: Default::default(),
resource,
source: body,
response_referrer_policy,
@@ -323,8 +344,14 @@ pub(super) fn load_service_worker_script_source_for_params(
pub(super) fn load_service_worker_script_update_check(
params: &ServiceWorkerScriptUpdateCheckParams,
) -> ServiceWorkerScriptUpdateCheckCompletion {
let main_script = load_service_worker_script_source_for_params(&params.main_script)
.map_err(ServiceWorkerScriptUpdateCheckFailure::script_load)?;
let mut main_script = load_service_worker_script_source_for_params(&params.main_script)
.map_err(|error| {
let mut failure = ServiceWorkerScriptUpdateCheckFailure::script_load(error.message);
if error.kind == ServiceWorkerRegistrationErrorKind::Security {
failure.status = ServiceWorkerScriptUpdateCheckFailureStatus::Security;
}
failure
})?;
if params.skip_script_comparison {
return Ok(ServiceWorkerScriptUpdateCheckResult {
main_script,
@@ -338,22 +365,26 @@ pub(super) fn load_service_worker_script_update_check(
});
}
let request_client = &params.main_script.request_client;
let mut change = ServiceWorkerScriptUpdateCheckChange::Identical;
for imported_script in &params.imported_scripts {
if !matches!(imported_script.request_url.scheme(), "http" | "https") {
continue;
}
let result = load_imported_script_resource_for_update_check(
request_client,
&imported_script.request_url,
&main_script.resource.final_url,
params.imported_script_cache_mode,
imported_script.kind,
params.script_kind == crate::worker::WorkerScriptKind::Classic,
);
match result {
match &result {
Ok(updated_resource) if updated_resource.body_sha256 != imported_script.body_sha256 => {
return Ok(ServiceWorkerScriptUpdateCheckResult {
main_script,
change: ServiceWorkerScriptUpdateCheckChange::ImportedScriptDifferent {
if change == ServiceWorkerScriptUpdateCheckChange::Identical {
change = ServiceWorkerScriptUpdateCheckChange::ImportedScriptDifferent {
script_url: imported_script.request_url.clone(),
},
});
};
}
}
Ok(_) => {}
Err(message) => {
@@ -364,10 +395,16 @@ pub(super) fn load_service_worker_script_update_check(
);
}
}
// The candidate worker must consume these exact responses, including
// failures, rather than fetching a third version during evaluation.
main_script.updated_imports.insert(
imported_script.request_url.clone(),
result.map(|resource| resource.to_worker_script_resource()),
);
}
Ok(ServiceWorkerScriptUpdateCheckResult {
main_script,
change: ServiceWorkerScriptUpdateCheckChange::Identical,
change,
})
}
@@ -377,12 +414,18 @@ fn load_imported_script_resource_for_update_check(
initiator_url: &Url,
cache_mode: RequestCacheMode,
kind: WorkerScriptResourceKind,
classic: bool,
) -> Result<ServiceWorkerScriptResource, String> {
let mut request_url_without_fragment = request_url.clone();
request_url_without_fragment.set_fragment(None);
let request = Request::new("GET", request_url_without_fragment.as_str(), None, vec![])
.map_err(|error| error.to_string())?
.with_credentials_mode(RequestCredentialsMode::SameOrigin)
.with_request_mode(if classic {
RequestMode::NoCors
} else {
RequestMode::Cors
})
.with_cache_mode(cache_mode)
.with_page_network_policy()
.with_initiator_url(initiator_url)
@@ -402,7 +445,8 @@ fn load_imported_script_resource_for_update_check(
.elapsed()
.as_millis()
.min(u64::MAX as u128) as u64;
crate::worker::ensure_worker_script_redirect_chain_same_origin(
if !classic {
crate::worker::ensure_worker_script_redirect_chain_same_origin(
initiator_url,
&response.redirect_chain,
&response.final_url,
@@ -412,19 +456,38 @@ fn load_imported_script_resource_for_update_check(
"Failed to load service worker imported script `{request_url_without_fragment}`: {message}"
)
})?;
}
moli_fetch::ensure_http_status_success(response.final_url.as_str(), response.status, false)
.map_err(|error| error.to_string())?;
ensure_imported_script_resource_mime(kind, &response)?;
let (head, body_bytes) = response.into_byte_parts();
let (head, body, body_bytes) = response.into_parts();
let mut resource = ServiceWorkerScriptResource::from_response_parts(
request_url_without_fragment,
request_url.clone(),
&head,
&body_bytes,
response_time_ms,
);
resource.kind = kind;
if classic {
resource.classic_script = Some(crate::worker::WorkerStoredClassicScript {
source: body.into(),
muted_errors: moli_url::WebOrigin::from_url(initiator_url)
!= moli_url::WebOrigin::from_url(&head.final_url)
|| head.redirect_chain.iter().any(|redirect| {
moli_url::WebOrigin::from_url(initiator_url)
!= moli_url::WebOrigin::from_url(&redirect.to_url)
}),
redirect_urls: head
.redirect_chain
.iter()
.map(|redirect| redirect.to_url.clone())
.collect(),
});
}
let mut final_url = head.final_url;
final_url.set_fragment(request_url.fragment());
if !classic {
final_url.set_fragment(request_url.fragment());
}
resource.final_url = final_url;
Ok(resource)
}
@@ -535,6 +598,7 @@ mod tests {
let result =
load_service_worker_script_update_check(&ServiceWorkerScriptUpdateCheckParams {
script_kind: crate::worker::WorkerScriptKind::Classic,
main_script: ServiceWorkerScriptLoadParams {
script_url: script_url.clone(),
scope_url: Url::parse(&format!("{base_url}/app/")).unwrap(),
@@ -576,6 +640,7 @@ mod tests {
let result =
load_service_worker_script_update_check(&ServiceWorkerScriptUpdateCheckParams {
script_kind: crate::worker::WorkerScriptKind::Classic,
main_script: ServiceWorkerScriptLoadParams {
script_url: script_url.clone(),
scope_url: Url::parse(&format!("{base_url}/app/")).unwrap(),
@@ -623,6 +688,7 @@ mod tests {
let result =
load_service_worker_script_update_check(&ServiceWorkerScriptUpdateCheckParams {
script_kind: crate::worker::WorkerScriptKind::Module,
main_script: ServiceWorkerScriptLoadParams {
script_url,
scope_url: Url::parse(&format!("{base_url}/app/")).unwrap(),
@@ -676,6 +742,7 @@ mod tests {
let result =
load_service_worker_script_update_check(&ServiceWorkerScriptUpdateCheckParams {
script_kind: crate::worker::WorkerScriptKind::Module,
main_script: ServiceWorkerScriptLoadParams {
script_url,
scope_url: Url::parse(&format!("{base_url}/app/")).unwrap(),
@@ -729,6 +796,7 @@ mod tests {
let result =
load_service_worker_script_update_check(&ServiceWorkerScriptUpdateCheckParams {
script_kind: crate::worker::WorkerScriptKind::Module,
main_script: ServiceWorkerScriptLoadParams {
script_url,
scope_url: Url::parse(&format!("{base_url}/app/")).unwrap(),
@@ -782,6 +850,7 @@ mod tests {
let result =
load_service_worker_script_update_check(&ServiceWorkerScriptUpdateCheckParams {
script_kind: crate::worker::WorkerScriptKind::Module,
main_script: ServiceWorkerScriptLoadParams {
script_url,
scope_url: Url::parse(&format!("{base_url}/app/")).unwrap(),
@@ -834,6 +903,7 @@ mod tests {
let result =
load_service_worker_script_update_check(&ServiceWorkerScriptUpdateCheckParams {
script_kind: crate::worker::WorkerScriptKind::Classic,
main_script: ServiceWorkerScriptLoadParams {
script_url: Url::parse(&format!("{base_url}/app/sw.js")).unwrap(),
scope_url: Url::parse(&format!("{base_url}/app/")).unwrap(),
@@ -770,7 +770,6 @@ mod tests {
ServiceWorkerQueuedLaunch {
params,
host: new_loading_test_host(version_id, &run),
lifecycle_notifications: Vec::new(),
preloaded_script: None,
}
}
@@ -1349,6 +1348,7 @@ mod tests {
fn test_loaded_script(script_url: &Url, source: &str) -> LoadedServiceWorkerScript {
LoadedServiceWorkerScript {
updated_imports: Default::default(),
resource: test_script_resource(script_url),
source: source.to_owned(),
response_referrer_policy: None,
@@ -1381,6 +1381,7 @@ mod tests {
scope_url: Url,
) -> ServiceWorkerQueuedRegisterJob {
ServiceWorkerQueuedRegisterJob {
update_registration_id: None,
script_url,
document_url: scope_url.join("page.html").expect("document url"),
storage_key: ServiceWorkerRegistrationKey::storage_key_for_scope_url(&scope_url),
@@ -1393,11 +1394,11 @@ mod tests {
force_update_page_load_waiter_ids: Vec::new(),
request_client: test_request_client(service),
network_policy: WorkerNetworkPolicy::default(),
browser_context_runtime: service.browser_context_runtime(),
worker_context_runtime: service.browser_context_runtime().worker_context_runtime(),
broadcast_channel_top_level_site: None,
indexed_db_manager: None,
storage_bucket_store: None,
callbacks: vec![ServiceWorkerRegisterJob {
callbacks: vec![ServiceWorkerRegisterJob::Page {
request_id: 1,
document_owner: crate::native_bridge::WindowDocumentOwner::for_test(1),
completion_tx: test_completion_sender(),
@@ -1417,6 +1418,7 @@ mod tests {
let document_url = scope_url.join("page.html").expect("document url");
let browser_context_runtime = service.browser_context_runtime();
let queued_job = ServiceWorkerQueuedRegisterJob {
update_registration_id: None,
script_url: script_url.clone(),
scope_url: scope_url.clone(),
document_url,
@@ -1429,11 +1431,11 @@ mod tests {
force_update_page_load_waiter_ids: Vec::new(),
request_client: test_request_client(service),
network_policy: WorkerNetworkPolicy::default(),
browser_context_runtime,
worker_context_runtime: browser_context_runtime.worker_context_runtime(),
broadcast_channel_top_level_site: None,
indexed_db_manager: None,
storage_bucket_store: None,
callbacks: vec![ServiceWorkerRegisterJob {
callbacks: vec![ServiceWorkerRegisterJob::Page {
request_id,
document_owner: crate::native_bridge::WindowDocumentOwner::for_test(1),
completion_tx,
@@ -1610,7 +1612,7 @@ mod tests {
registration.scope_url = scope_url.clone();
registration.installing_version_id = Some(version_id);
let mut pending_register_job =
ServiceWorkerPendingRegisterJob::new(vec![ServiceWorkerRegisterJob {
ServiceWorkerPendingRegisterJob::new(vec![ServiceWorkerRegisterJob::Page {
request_id,
document_owner: crate::native_bridge::WindowDocumentOwner::for_test(1),
completion_tx,
@@ -1665,6 +1667,7 @@ mod tests {
state.job_coordinator.enqueue_register(
registration_key.clone(),
ServiceWorkerQueuedRegisterJob {
update_registration_id: None,
script_url,
scope_url: scope_url.clone(),
document_url: scope_url.join("page.html").expect("document url"),
@@ -1677,11 +1680,11 @@ mod tests {
force_update_page_load_waiter_ids: Vec::new(),
request_client: test_request_client(service),
network_policy: WorkerNetworkPolicy::default(),
browser_context_runtime: service.browser_context_runtime(),
worker_context_runtime: service.browser_context_runtime().worker_context_runtime(),
broadcast_channel_top_level_site: None,
indexed_db_manager: None,
storage_bucket_store: None,
callbacks: vec![ServiceWorkerRegisterJob {
callbacks: vec![ServiceWorkerRegisterJob::Page {
request_id,
document_owner: crate::native_bridge::WindowDocumentOwner::for_test(1),
completion_tx,
@@ -5297,6 +5300,191 @@ self.addEventListener("message", event => {
);
}
#[test]
fn registration_update_rejects_wrong_identity_partition_and_installing_worker() {
use crate::service_worker_runtime::{
ServiceWorkerRegistrationErrorKind, ServiceWorkerRegistrationUpdate,
};
for invalid in ["identity", "partition", "installing"] {
let service = new_service_worker_runtime_service();
let (registration_id, version_id) = insert_running_installing_version(&service);
let mut queue = crate::page_task_queue::RendererPageServiceWorkerTestHarness::new();
let scope_url = url("https://example.test/app/");
let mut request = ServiceWorkerRegistrationUpdate {
registration_id,
caller_version_id: None,
storage_key: ServiceWorkerRegistrationKey::storage_key_for_scope_url(&scope_url),
document_url: scope_url.join("page.html").unwrap(),
request_client: test_request_client(&service),
network_policy: WorkerNetworkPolicy::default(),
worker_context_runtime: service.browser_context_runtime().worker_context_runtime(),
broadcast_channel_top_level_site: None,
indexed_db_manager: None,
storage_bucket_store: None,
completion: ServiceWorkerRegisterJob::Page {
request_id: 7,
document_owner: crate::native_bridge::WindowDocumentOwner::for_test(1),
completion_tx: queue.sender(),
},
};
match invalid {
"identity" => request.registration_id = ServiceWorkerRegistrationId(999),
"partition" => request.storage_key = "another-partition".into(),
"installing" => request.caller_version_id = Some(version_id),
_ => unreachable!(),
}
service.start_registration_update(request);
let completion = pop_register_completion(&mut queue);
assert_eq!(completion.request_id, 7);
assert_eq!(
completion.result.err().unwrap().kind,
ServiceWorkerRegistrationErrorKind::InvalidState,
"{invalid}"
);
let diagnostics = service.diagnostics_snapshot();
assert_eq!(diagnostics.registration_count, 1);
assert_eq!(diagnostics.queued_register_job_count, 0);
}
}
#[test]
fn queued_registration_update_revalidates_after_installation() {
for unregister in [false, true] {
let service = new_service_worker_runtime_service();
let (registration_id, version_id) = insert_running_installing_version(&service);
let mut queue = crate::page_task_queue::RendererPageServiceWorkerTestHarness::new();
let mut job = test_queued_register_job(
&service,
url("https://example.test/app/sw.js"),
url("https://example.test/app/"),
);
job.update_registration_id = Some(registration_id);
job.callbacks = vec![ServiceWorkerRegisterJob::Page {
request_id: 3,
document_owner: crate::native_bridge::WindowDocumentOwner::for_test(1),
completion_tx: queue.sender(),
}];
service.start_queued_register_job(job);
assert!(!queue.has_ready_task());
assert_eq!(service.diagnostics_snapshot().queued_register_job_count, 1);
let progress = {
let mut state = service.inner.state.lock();
let registration = state.registrations.get_mut(&registration_id).unwrap();
registration.installing_version_id = None;
registration.active_version_id = Some(version_id);
registration.pending_unregistration = unregister;
let version = state.versions.get_mut(&version_id).unwrap();
version.lifecycle_state = ServiceWorkerVersionLifecycleState::Activated;
if !unregister {
version.script_url = url("https://example.test/app/replaced.js");
}
service.advance_registration_job_queue_locked(&mut state, registration_id)
};
for action in progress {
service.run_lifecycle_progress(action);
}
let error = pop_register_completion(&mut queue).result.err().unwrap();
assert_eq!(
error.kind,
crate::service_worker_runtime::ServiceWorkerRegistrationErrorKind::Type
);
assert_eq!(service.diagnostics_snapshot().queued_register_job_count, 0);
assert_eq!(service.diagnostics_snapshot().version_count, 1);
}
}
#[test]
fn register_queues_behind_an_installing_update_job() {
let service = new_service_worker_runtime_service();
let script_url = url("https://example.test/app/sw.js");
let scope_url = url("https://example.test/app/");
let registration_id = ServiceWorkerRegistrationId(1);
let version_id = ServiceWorkerVersionId(1);
insert_starting_version_with_register_job(
&service,
registration_id,
version_id,
script_url.clone(),
scope_url.clone(),
1,
test_completion_sender(),
);
service
.inner
.state
.lock()
.registrations
.get_mut(&registration_id)
.unwrap()
.pending_register_jobs
.get_mut(&version_id)
.unwrap()
.is_update = true;
let mut queue = crate::page_task_queue::RendererPageServiceWorkerTestHarness::new();
let mut job = test_queued_register_job(&service, script_url, scope_url);
job.callbacks = vec![ServiceWorkerRegisterJob::Page {
request_id: 2,
document_owner: crate::native_bridge::WindowDocumentOwner::for_test(1),
completion_tx: queue.sender(),
}];
service.start_queued_register_job(job);
assert_eq!(service.diagnostics_snapshot().queued_register_job_count, 1);
assert!(!queue.has_ready_task());
let progress = {
let mut state = service.inner.state.lock();
let registration = state.registrations.get_mut(&registration_id).unwrap();
registration.installing_version_id = None;
registration.active_version_id = Some(version_id);
service.advance_registration_job_queue_locked(&mut state, registration_id)
};
for action in progress {
service.run_lifecycle_progress(action);
}
assert_eq!(pop_register_completion(&mut queue).request_id, 2);
assert_eq!(service.diagnostics_snapshot().queued_register_job_count, 0);
assert_eq!(
service
.diagnostics_snapshot()
.pending_main_script_update_check_count,
0
);
service.terminate_all_for_context_shutdown();
}
#[test]
fn queued_forced_update_keeps_the_script_check() {
let service = new_service_worker_runtime_service();
let registration_id = ServiceWorkerRegistrationId(42);
let version_id = ServiceWorkerVersionId(17);
let script_url = url("https://example.test/app/sw.js");
let scope_url = url("https://example.test/app/");
insert_registered_version(
&service,
registration_id,
version_id,
script_url.clone(),
scope_url.clone(),
[],
);
make_version_persistable(&service, version_id);
let mut job = test_queued_register_job(&service, script_url, scope_url);
job.force_bypass_cache = true;
job.skip_script_comparison = true;
let mut state = service.inner.state.lock();
state
.job_coordinator
.enqueue_register(job.registration_key(), job);
let progress = service.advance_registration_job_queue_locked(&mut state, registration_id);
let [LifecycleProgress::StartMainScriptUpdateCheck(check)] = progress.as_slice() else {
panic!("queued forced update must not take the register shortcut");
};
assert!(check.1.skip_script_comparison);
assert_eq!(
check.1.main_script.cache_mode,
moli_fetch::RequestCacheMode::Validate
);
}
#[test]
fn start_registration_queues_same_scope_job_while_installing() {
let service = new_service_worker_runtime_service();
@@ -334,7 +334,6 @@ impl ServiceWorkerRuntimeService {
Some(ServiceWorkerQueuedLaunch {
params,
host,
lifecycle_notifications: Vec::new(),
preloaded_script: None,
})
}
@@ -481,6 +480,7 @@ impl ServiceWorkerRuntimeService {
};
let request_client = newest_version.launch_config.request_client();
ServiceWorkerQueuedRegisterJob {
update_registration_id: None,
script_url: newest_version.script_url.clone(),
scope_url: registration.scope_url.clone(),
document_url: newest_version.launch_config.document_url.clone(),
@@ -493,7 +493,7 @@ impl ServiceWorkerRuntimeService {
force_update_page_load_waiter_ids,
request_client,
network_policy: newest_version.launch_config.network_policy.clone(),
browser_context_runtime,
worker_context_runtime: browser_context_runtime.worker_context_runtime(),
broadcast_channel_top_level_site: newest_version
.launch_config
.broadcast_channel_top_level_site
@@ -181,9 +181,6 @@ impl ServiceWorkerRuntimeService {
&mut launch,
debugger_release_consumed,
);
for notification in launch.lifecycle_notifications {
notification.send();
}
launch
.host
.start_loading(self.clone(), launch.params, launch.preloaded_script);
@@ -420,7 +417,6 @@ impl ServiceWorkerRuntimeService {
Some(ServiceWorkerQueuedLaunch {
params,
host,
lifecycle_notifications: Vec::new(),
preloaded_script: None,
}),
None,
@@ -363,7 +363,6 @@ impl ServiceWorkerRuntimeService {
ServiceWorkerQueuedLaunch {
params,
host,
lifecycle_notifications: Vec::new(),
preloaded_script: None,
},
)))
@@ -430,7 +429,6 @@ impl ServiceWorkerRuntimeService {
ServiceWorkerMessageStart::Start(Box::new(ServiceWorkerQueuedLaunch {
params,
host,
lifecycle_notifications: Vec::new(),
preloaded_script: None,
}))
}
@@ -492,7 +490,6 @@ impl ServiceWorkerRuntimeService {
ServiceWorkerNotificationStart::Start(Box::new(ServiceWorkerQueuedLaunch {
params,
host,
lifecycle_notifications: Vec::new(),
preloaded_script: None,
}))
}
@@ -556,7 +553,6 @@ impl ServiceWorkerRuntimeService {
ServiceWorkerPushStart::Start(Box::new(ServiceWorkerQueuedLaunch {
params,
host,
lifecycle_notifications: Vec::new(),
preloaded_script: None,
}))
}
@@ -618,7 +614,6 @@ impl ServiceWorkerRuntimeService {
ServiceWorkerSyncStart::Start(Box::new(ServiceWorkerQueuedLaunch {
params,
host,
lifecycle_notifications: Vec::new(),
preloaded_script: None,
}))
}
@@ -680,7 +675,6 @@ impl ServiceWorkerRuntimeService {
ServiceWorkerPeriodicSyncStart::Start(Box::new(ServiceWorkerQueuedLaunch {
params,
host,
lifecycle_notifications: Vec::new(),
preloaded_script: None,
}))
}
@@ -1,6 +1,57 @@
use super::*;
use crate::service_worker_runtime::{
ServiceWorkerRegistrationErrorKind, ServiceWorkerRegistrationUpdate,
};
use moli_fetch::RequestCacheMode;
fn newest_exposed_version_id(
state: &ServiceWorkerRuntimeState,
registration: &ServiceWorkerRegistration,
) -> Option<ServiceWorkerVersionId> {
let checking = state
.pending_main_script_update_checks
.get(&registration.id)
.map(|check| check.new_version_id);
registration
.installing_version_id
.filter(|version_id| Some(*version_id) != checking)
.or(registration.waiting_version_id)
.or(registration.active_version_id)
}
fn validate_queued_update(
state: &ServiceWorkerRuntimeState,
job: &ServiceWorkerQueuedRegisterJob,
) -> Result<(), ServiceWorkerRegistrationError> {
let Some(registration_id) = job.update_registration_id else {
return Ok(());
};
let registration = state
.registrations
.get(&registration_id)
.filter(|registration| {
!registration.pending_unregistration && registration.key() == job.registration_key()
})
.ok_or_else(|| {
ServiceWorkerRegistrationError::type_error(
"The ServiceWorker registration was unregistered.",
)
})?;
let newest = newest_exposed_version_id(state, registration)
.and_then(|id| state.versions.get(&id))
.ok_or_else(|| {
ServiceWorkerRegistrationError::type_error(
"The ServiceWorker registration has no worker.",
)
})?;
if newest.script_url != job.script_url {
return Err(ServiceWorkerRegistrationError::type_error(
"The newest ServiceWorker script URL changed before update().",
));
}
Ok(())
}
fn same_registration_options_fast_path_snapshot(
state: &ServiceWorkerRuntimeState,
registration: &ServiceWorkerRegistration,
@@ -28,6 +79,77 @@ fn same_registration_options_fast_path_snapshot(
}
impl ServiceWorkerRuntimeService {
pub(crate) fn start_registration_update(&self, request: ServiceWorkerRegistrationUpdate) {
let newest = {
let state = self.inner.state.lock();
(|| {
let registration = state
.registrations
.get(&request.registration_id)
.filter(|registration| registration.storage_key == request.storage_key)
.ok_or_else(|| {
ServiceWorkerRegistrationError::new(
ServiceWorkerRegistrationErrorKind::InvalidState,
"The ServiceWorker registration has no worker.",
)
})?;
let newest = newest_exposed_version_id(&state, registration)
.and_then(|id| state.versions.get(&id))
.ok_or_else(|| {
ServiceWorkerRegistrationError::new(
ServiceWorkerRegistrationErrorKind::InvalidState,
"The ServiceWorker registration has no worker.",
)
})?;
if request
.caller_version_id
.and_then(|id| state.versions.get(&id))
.is_some_and(|caller| {
caller.lifecycle_state == ServiceWorkerVersionLifecycleState::Installing
})
{
return Err(ServiceWorkerRegistrationError::new(
ServiceWorkerRegistrationErrorKind::InvalidState,
"An installing ServiceWorker cannot call update().",
));
}
Ok((
registration.scope_url.clone(),
newest.script_url.clone(),
newest.script_kind,
registration.update_via_cache,
))
})()
};
let (scope_url, script_url, script_kind, update_via_cache) = match newest {
Ok(newest) => newest,
Err(error) => {
request.completion.send(Err(error));
return;
}
};
self.start_queued_register_job(ServiceWorkerQueuedRegisterJob {
update_registration_id: Some(request.registration_id),
script_url,
scope_url,
document_url: request.document_url,
storage_key: request.storage_key,
script_kind,
update_via_cache,
force_bypass_cache: false,
skip_script_comparison: false,
skip_waiting_after_install: false,
force_update_page_load_waiter_ids: Vec::new(),
request_client: request.request_client,
network_policy: request.network_policy,
worker_context_runtime: request.worker_context_runtime,
broadcast_channel_top_level_site: request.broadcast_channel_top_level_site,
indexed_db_manager: request.indexed_db_manager,
storage_bucket_store: request.storage_bucket_store,
callbacks: vec![request.completion],
});
}
fn record_force_update_page_load_devtools_message_locked(
&self,
state: &mut ServiceWorkerRuntimeState,
@@ -109,6 +231,7 @@ impl ServiceWorkerRuntimeService {
register_completion_tx: RendererPageServiceWorkerTaskSender,
) {
let queued_job = ServiceWorkerQueuedRegisterJob {
update_registration_id: None,
script_url,
scope_url,
document_url,
@@ -121,11 +244,11 @@ impl ServiceWorkerRuntimeService {
force_update_page_load_waiter_ids: Vec::new(),
request_client,
network_policy,
browser_context_runtime,
worker_context_runtime: browser_context_runtime.worker_context_runtime(),
broadcast_channel_top_level_site,
indexed_db_manager,
storage_bucket_store,
callbacks: vec![ServiceWorkerRegisterJob {
callbacks: vec![ServiceWorkerRegisterJob::Page {
request_id: register_request_id,
document_owner: register_document_owner,
completion_tx: register_completion_tx,
@@ -138,6 +261,11 @@ impl ServiceWorkerRuntimeService {
let (launch, update_check, completed_register_callbacks) = {
let mut state = self.inner.state.lock();
let registration_key = queued_job.registration_key();
if let Err(error) = validate_queued_update(&state, &queued_job) {
drop(state);
ServiceWorkerRegisterJob::send_all(queued_job.callbacks, Err(error));
return;
}
self.restore_stored_registration_for_queued_job_locked(&mut state, &queued_job);
let registration_id = state
.registrations
@@ -190,14 +318,19 @@ impl ServiceWorkerRuntimeService {
{
registration.pending_unregistration = false;
}
let can_coalesce_with_installing = installing_version_id
.and_then(|version_id| state.versions.get(&version_id))
.is_some_and(|version| {
version.script_url == queued_job.script_url
&& version.script_kind == queued_job.script_kind
&& registration_scope_url == queued_job.scope_url
&& registration_update_via_cache == queued_job.update_via_cache
});
let can_coalesce_with_installing = queued_job.update_registration_id.is_none()
&& installing_version_id
.and_then(|version_id| state.versions.get(&version_id))
.is_some_and(|version| {
version.script_url == queued_job.script_url
&& version.script_kind == queued_job.script_kind
&& registration_scope_url == queued_job.scope_url
&& registration_update_via_cache == queued_job.update_via_cache
&& !state.registrations[&registration_id]
.pending_register_jobs
.get(&version.id)
.is_some_and(|job| job.is_update)
});
let pending_update_check_matches = state
.pending_main_script_update_checks
.get(&registration_id)
@@ -514,6 +647,7 @@ impl ServiceWorkerRuntimeService {
),
};
let update_check_params = ServiceWorkerScriptUpdateCheckParams {
script_kind: queued_job.script_kind,
main_script: load_params,
newest_main_body_sha256: newest_body_sha256.clone(),
imported_scripts,
@@ -616,16 +750,12 @@ impl ServiceWorkerRuntimeService {
register_callbacks,
queued_job.skip_waiting_after_install,
);
pending_register_job.is_update = queued_job.update_registration_id.is_some();
pending_register_job.start_current_moli_job();
let registration = state.registrations.get_mut(&registration_id)?;
registration
.pending_register_jobs
.insert(version_id, pending_register_job);
let lifecycle_notifications = lifecycle_notifications_for_registration_locked(
state,
registration_id,
vec![ServiceWorkerLifecycleClientEvent::UpdateFound],
);
let request_client = launch_config.request_client();
Some(ServiceWorkerQueuedLaunch {
params: ServiceWorkerLaunchParams {
@@ -645,7 +775,6 @@ impl ServiceWorkerRuntimeService {
pause_evaluation_until_debugger: false,
},
host,
lifecycle_notifications,
preloaded_script,
})
}
@@ -681,6 +810,7 @@ impl ServiceWorkerRuntimeService {
register_callbacks,
queued_job.skip_waiting_after_install,
);
pending_register_job.is_update = queued_job.update_registration_id.is_some();
pending_register_job.start_current_moli_job();
registration
.pending_register_jobs
@@ -691,11 +821,6 @@ impl ServiceWorkerRuntimeService {
host.run_identity(),
&queued_job,
);
let lifecycle_notifications = lifecycle_notifications_for_registration_locked(
state,
registration_id,
vec![ServiceWorkerLifecycleClientEvent::UpdateFound],
);
let request_client = launch_config.request_client();
Some(ServiceWorkerQueuedLaunch {
params: ServiceWorkerLaunchParams {
@@ -715,7 +840,6 @@ impl ServiceWorkerRuntimeService {
pause_evaluation_until_debugger: false,
},
host,
lifecycle_notifications,
preloaded_script: Some(preloaded_script),
})
}
@@ -837,6 +961,39 @@ impl ServiceWorkerRuntimeService {
match queued_job {
ServiceWorkerQueuedJob::Register(queued_job) => {
let queued_job = *queued_job;
if let Err(error) = validate_queued_update(state, &queued_job) {
progress.push(LifecycleProgress::RegisterFailed((
queued_job.callbacks,
error,
)));
continue;
}
// A register queued behind update must wait for that job,
// then reuse the matching registration without another fetch.
if queued_job.update_registration_id.is_none()
&& !queued_job.force_bypass_cache
&& !queued_job.skip_script_comparison
&& let Some(snapshot) =
state
.registrations
.get(&registration_id)
.and_then(|registration| {
same_registration_options_fast_path_snapshot(
state,
registration,
&queued_job.script_url,
queued_job.script_kind,
queued_job.update_via_cache,
false,
)
})
{
progress.push(LifecycleProgress::RegisterCompleted(Box::new((
queued_job.callbacks,
snapshot,
))));
continue;
}
if let Some(update_check) = self.start_main_script_update_check_locked(
state,
registration_id,
@@ -66,7 +66,7 @@ impl ServiceWorkerRuntimeService {
return;
};
let loaded_main_script_resource = script_resource.is_some();
let (lifecycle_start, register_completion, pending_events) = {
let (lifecycle_start, register_completion, notifications, worker_hosts, pending_events) = {
let mut state = self.inner.state.lock();
let (registration_id, lifecycle_state, pending_events, run) = {
let Some(version) = state.versions.get_mut(&version_id) else {
@@ -149,19 +149,90 @@ impl ServiceWorkerRuntimeService {
} else {
None
};
let (mut notifications, worker_hosts) = if lifecycle_start.is_some() {
let notifications = lifecycle_notifications_for_registration_locked(
&state,
registration_id,
vec![ServiceWorkerLifecycleClientEvent::UpdateFound],
);
let hosts = state
.versions
.values()
.filter_map(|version| {
if version.registration_id != registration_id {
return None;
}
match &version.running_state {
ServiceWorkerVersionRunningState::Running { host } => {
Some(host.clone())
}
_ => None,
}
})
.collect::<Vec<_>>();
(notifications, hosts)
} else {
(Vec::new(), Vec::new())
};
// A first register() creates its JS registration when the completion is
// delivered. Queue updatefound for that document after the completion,
// even though it cannot have a registration watcher yet.
if let Some((callbacks, snapshot)) = &register_completion {
for callback in callbacks {
let ServiceWorkerRegisterJob::Page {
document_owner,
completion_tx,
..
} = callback
else {
continue;
};
if notifications
.iter()
.any(|notification| notification.watcher.document_owner == *document_owner)
{
continue;
}
notifications.push(ServiceWorkerLifecycleNotificationDelivery {
watcher: ServiceWorkerLifecycleWatcher {
scope_url: snapshot.scope_url().clone(),
storage_key: state.registrations[&registration_id].storage_key.clone(),
document_owner: *document_owner,
completion_tx: completion_tx.clone(),
},
registration: snapshot.clone(),
events: vec![ServiceWorkerLifecycleClientEvent::UpdateFound],
});
}
}
state.record_target_started(version_id, run);
(lifecycle_start, register_completion, pending_events)
(
lifecycle_start,
register_completion,
notifications,
worker_hosts,
pending_events,
)
};
if let Some(start) = lifecycle_start
&& let Some(progress) = Self::lifecycle_start_to_progress(start)
{
self.run_lifecycle_progress(progress);
}
if let Some((callbacks, snapshot)) = register_completion
&& !callbacks.is_empty()
{
ServiceWorkerRegisterJob::send_all(callbacks, Ok(snapshot));
}
// Script evaluation must succeed before updatefound. Deliver it on each
// existing worker loop before install (and before page listeners can send
// another event to the active worker).
for host in worker_hosts {
host.dispatch_registration_update_found();
}
for notification in notifications {
notification.send();
}
if let Some(start) = lifecycle_start
&& let Some(progress) = Self::lifecycle_start_to_progress(start)
{
self.run_lifecycle_progress(progress);
}
for event in pending_events {
match event {
ServiceWorkerPendingStartEvent::Fetch(event) => {
@@ -981,6 +1052,12 @@ fn registration_error_for_update_check_failure(
failure: ServiceWorkerScriptUpdateCheckFailure,
) -> ServiceWorkerRegistrationError {
match failure.status {
ServiceWorkerScriptUpdateCheckFailureStatus::Security => {
ServiceWorkerRegistrationError::new(
crate::service_worker_runtime::ServiceWorkerRegistrationErrorKind::Security,
failure.message,
)
}
ServiceWorkerScriptUpdateCheckFailureStatus::ScriptLoadFailed => {
registration_error_for_script_load_failure(failure.message)
}
@@ -1002,6 +1079,8 @@ fn registration_error_for_script_load_failure(message: String) -> ServiceWorkerR
);
}
if normalized.contains("cross-origin")
|| normalized.contains("script mime type")
|| normalized.contains("blocked by x-content-type-options nosniff")
|| normalized.contains("service-worker-allowed")
|| normalized.contains("not under the max scope allowed")
|| normalized.contains("disallowed escape")
@@ -132,7 +132,6 @@ impl ServiceWorkerRegistrationSnapshot {
)
}
#[cfg(test)]
pub(crate) fn registration_id(&self) -> ServiceWorkerRegistrationId {
self.registration_id
}
@@ -49,7 +49,7 @@ enum ServiceWorkerRuntimeCompletionKind {
},
MainScriptUpdateCheckCompleted {
registration_id: ServiceWorkerRegistrationId,
result: ServiceWorkerScriptUpdateCheckCompletion,
result: Box<ServiceWorkerScriptUpdateCheckCompletion>,
},
LifecycleEventCompleted {
completion: ServiceWorkerLifecycleCompletion,
@@ -219,7 +219,7 @@ impl ServiceWorkerRuntimeCompletion {
runtime_service,
kind: ServiceWorkerRuntimeCompletionKind::MainScriptUpdateCheckCompleted {
registration_id,
result,
result: Box::new(result),
},
}
}
@@ -637,7 +637,7 @@ impl ServiceWorkerRuntimeCompletion {
result,
} => self
.runtime_service
.finish_main_script_update_check_completed(registration_id, result),
.finish_main_script_update_check_completed(registration_id, *result),
ServiceWorkerRuntimeCompletionKind::LifecycleEventCompleted { completion } => {
self.runtime_service
.finish_lifecycle_event_completed(completion);
@@ -713,7 +713,6 @@ pub(super) enum LifecycleProgress {
pub(super) struct ServiceWorkerQueuedLaunch {
pub(super) params: ServiceWorkerLaunchParams,
pub(super) host: SharedRendererServiceWorkerHost,
pub(super) lifecycle_notifications: Vec<ServiceWorkerLifecycleNotificationDelivery>,
pub(super) preloaded_script: Option<LoadedServiceWorkerScript>,
}
@@ -65,7 +65,7 @@ pub(super) fn materialize_worker_import_source(
csp.check_url(script_url, ContentSecurityPolicyRedirectStatus::NoRedirect)
.map_err(WorkerImportScriptError::network)?;
}
let (service_worker, cached, can_import_new) = {
let (service_worker, cached, updated, can_import_new) = {
let state = state.borrow();
(
matches!(
@@ -76,10 +76,21 @@ pub(super) fn materialize_worker_import_source(
.service_worker_script_resources
.get(script_url)
.cloned(),
state
.service_worker_updated_script_resources
.get(script_url)
.cloned(),
state.service_worker_can_import_new_scripts,
)
};
if service_worker {
let from_update_check = cached.is_none() && updated.is_some();
let cached = match cached.clone() {
Some(resource) => Some(resource),
None => updated
.transpose()
.map_err(WorkerImportScriptError::network)?,
};
if let Some(resource) = &cached
&& let Some(script) = &resource.classic_script
{
@@ -89,6 +100,13 @@ pub(super) fn materialize_worker_import_source(
.map_err(WorkerImportScriptError::network)?;
}
}
if from_update_check {
state
.borrow_mut()
.service_worker_script_resources
.insert(script_url.clone(), resource.clone());
report_service_worker_imported_script_loaded(state, resource.clone());
}
return Ok(WorkerImportScriptSource {
final_url: resource.final_url.clone(),
source: script.source.clone(),
@@ -178,6 +178,10 @@ pub(super) const WORKER_EXCEPTION_LINE_SLOT: &str = "__moliWorkerExceptionLine";
pub(super) const WORKER_EXCEPTION_COLUMN_SLOT: &str = "__moliWorkerExceptionColumn";
const SERVICE_WORKER_REGISTRATION_SCOPE_SLOT: &str = "__moliServiceWorkerRegistrationScope";
const SERVICE_WORKER_REGISTRATION_ID_SLOT: &str = "__moliServiceWorkerRegistrationId";
const SERVICE_WORKER_GLOBAL_REGISTRATION_SLOT: &str = "__moliServiceWorkerGlobalRegistration";
const SERVICE_WORKER_REGISTRATION_EVENTS_SLOT: &str = "__moliServiceWorkerRegistrationEvents";
const SERVICE_WORKER_REGISTRATION_ONUPDATEFOUND_SLOT: &str =
"__moliServiceWorkerRegistrationOnUpdateFound";
const SERVICE_WORKER_VERSION_ID_SLOT: &str = "__moliServiceWorkerVersionId";
const SERVICE_WORKER_WORKER_EVENTS_SLOT: &str = "__moliServiceWorkerWorkerEvents";
const SERVICE_WORKER_NAVIGATION_PRELOAD_MANAGER_SCOPE_SLOT: &str =
@@ -655,6 +659,13 @@ struct ServiceWorkerGlobalRegistrationDeclaration<'scope> {
#[webapi(data_property, readonly)]
scope: String,
#[webapi(
accessor_property = "onupdatefound",
getter = service_worker_registration_onupdatefound_getter,
setter = service_worker_registration_onupdatefound_setter
)]
onupdatefound: (),
#[webapi(
accessor_property = "installing",
getter = service_worker_registration_installing_getter
@@ -676,6 +687,9 @@ struct ServiceWorkerGlobalRegistrationDeclaration<'scope> {
#[webapi(method, callback = service_worker_registration_unregister_callback, length = 0)]
unregister: (),
#[webapi(method, callback = service_worker_registration_update_callback, length = 0)]
update: (),
#[webapi(
method = "showNotification",
callback = service_worker_registration_show_notification_callback,
@@ -986,6 +1000,11 @@ pub(super) struct PendingServiceWorkerShowNotification {
pub(super) resolver: v8::Global<v8::PromiseResolver>,
}
pub(super) struct PendingServiceWorkerUpdate {
pub(super) resolver: v8::Global<v8::PromiseResolver>,
pub(super) registration: v8::Global<v8::Object>,
}
pub(super) struct PendingServiceWorkerGetNotifications {
pub(super) resolver: v8::Global<v8::PromiseResolver>,
}
@@ -1549,6 +1568,7 @@ pub(crate) struct WorkerGlobalState {
/// Responses in this ServiceWorker version's script resource map, keyed by
/// requested URL. Dedicated and Shared Workers leave this map empty.
pub(super) service_worker_script_resources: HashMap<Url, crate::worker::WorkerScriptResource>,
pub(super) service_worker_updated_script_resources: crate::worker::WorkerScriptUpdateResources,
pub(super) service_worker_can_import_new_scripts: bool,
/// Referrer policy parsed from the top-level worker script response.
pub(super) referrer_policy: Option<String>,
@@ -1659,6 +1679,8 @@ pub(crate) struct WorkerGlobalState {
/// In-flight Service Worker `registration.showNotification()` requests keyed by request id.
pub(super) pending_service_worker_show_notifications:
HashMap<u64, PendingServiceWorkerShowNotification>,
pub(super) pending_service_worker_updates: HashMap<u64, PendingServiceWorkerUpdate>,
pub(super) service_worker_update_request_ids: WorkerServiceWorkerRequestIdAllocator,
/// In-flight Service Worker `registration.getNotifications()` requests keyed by request id.
pub(super) pending_service_worker_get_notifications:
HashMap<u64, PendingServiceWorkerGetNotifications>,
@@ -2523,6 +2545,45 @@ pub(super) fn drain_service_worker_clients_open_window_result(
}
}
pub(super) fn drain_service_worker_update_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
request_id: u64,
result: Result<
crate::service_worker_runtime::ServiceWorkerRegistrationSnapshot,
crate::service_worker_runtime::ServiceWorkerRegistrationError,
>,
) {
let Some(pending) = state
.borrow_mut()
.pending_service_worker_updates
.remove(&request_id)
else {
return;
};
let resolver = v8::Local::new(scope, &pending.resolver);
match result {
Ok(_) => {
let registration = v8::Local::new(scope, &pending.registration);
let _ = resolver.resolve(scope, registration.into());
}
Err(error) => {
let exception = if error.kind.rejects_as_type_error_for_update() {
let message = v8_string(scope, &error.message)
.unwrap_or_else(|| v8str(scope, "ServiceWorker update failed"));
v8::Exception::type_error(scope, message)
} else {
crate::context_bootstrap::new_dom_exception_value(
scope,
&error.message,
error.kind.dom_exception_name(),
)
};
let _ = resolver.reject(scope, exception);
}
}
}
pub(super) fn drain_service_worker_show_notification_result(
scope: &mut v8::PinScope<'_, '_>,
state: &Rc<RefCell<WorkerGlobalState>>,
@@ -3265,6 +3326,12 @@ fn install_service_worker_global_runtime<'s>(
) -> Result<()> {
let registration =
build_service_worker_global_registration(scope, registration_id, version_id, scope_url)?;
set_private_value(
scope,
global,
SERVICE_WORKER_GLOBAL_REGISTRATION_SLOT,
registration.into(),
);
let clients = ServiceWorkerClientsDeclaration::default()
.bind(scope)
.map_err(|error| anyhow!("failed to build service worker clients: {error}"))?;
@@ -3303,10 +3370,12 @@ fn build_service_worker_global_registration<'s>(
build_service_worker_global_navigation_preload_manager(scope, scope_url)?;
let registration = ServiceWorkerGlobalRegistrationDeclaration {
scope: scope_url.as_str().to_owned(),
onupdatefound: (),
installing: (),
waiting: (),
active: (),
unregister: (),
update: (),
show_notification: (),
get_notifications: (),
sync: sync_manager,
@@ -3316,6 +3385,13 @@ fn build_service_worker_global_registration<'s>(
}
.bind(scope)
.map_err(|error| anyhow!("failed to build service worker registration: {error:?}"))?;
install_simple_event_target_methods(
scope,
registration,
SERVICE_WORKER_REGISTRATION_EVENTS_SLOT,
false,
);
install_simple_event_target_ordered_handlers(scope, registration);
let scope_value = v8_string(scope, scope_url.as_str())
.ok_or_else(|| anyhow!("failed to allocate service worker registration scope"))?;
set_private_value(
@@ -3341,6 +3417,77 @@ fn build_service_worker_global_registration<'s>(
Ok(registration)
}
fn service_worker_registration_onupdatefound_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(
get_private_value(
scope,
args.this(),
SERVICE_WORKER_REGISTRATION_ONUPDATEFOUND_SLOT,
)
.unwrap_or_else(|| v8::null(scope).into()),
);
}
fn service_worker_registration_onupdatefound_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
let value = args.get(0);
let active = value.is_function();
let value = if active {
value
} else {
v8::null(scope).into()
};
set_private_value(
scope,
args.this(),
SERVICE_WORKER_REGISTRATION_ONUPDATEFOUND_SLOT,
value,
);
simple_object_event_set_ordered_handler(
scope,
args.this(),
SERVICE_WORKER_REGISTRATION_EVENTS_SLOT,
"updatefound",
SERVICE_WORKER_REGISTRATION_ONUPDATEFOUND_SLOT,
active,
);
}
pub(super) fn dispatch_service_worker_registration_update_found<'s>(
scope: &mut v8::PinScope<'s, '_>,
) {
let global = scope.get_current_context().global(scope);
let Some(registration) =
get_private_value(scope, global, SERVICE_WORKER_GLOBAL_REGISTRATION_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
else {
return;
};
let Ok(prototype) =
crate::context_bootstrap::ensure_intrinsic_interface_prototype(scope, "Event")
else {
return;
};
let event = v8::Object::new(scope);
let _ = event.set_prototype(scope, prototype.into());
crate::context_bootstrap::initialize_event_object(scope, event, "updatefound", false, false);
crate::context_bootstrap::mark_event_trusted(scope, event);
crate::context_bootstrap::dispatch_simple_event_target_event(
scope,
registration,
SERVICE_WORKER_REGISTRATION_EVENTS_SLOT,
"updatefound",
event,
);
}
fn build_service_worker_global_navigation_preload_manager<'s>(
scope: &mut v8::PinScope<'s, '_>,
scope_url: &Url,
@@ -3450,6 +3597,98 @@ pub(super) fn build_service_worker_global_service_worker<'s>(
Ok(worker)
}
fn service_worker_registration_update_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s, v8::Value>,
) {
let Some(resolver) = v8::PromiseResolver::new(scope) else {
return;
};
rv.set(resolver.get_promise(scope).into());
let registration_id =
get_private_value(scope, args.this(), SERVICE_WORKER_REGISTRATION_ID_SLOT)
.and_then(|value| v8::Local::<v8::BigInt>::try_from(value).ok())
.map(|value| value.u64_value())
.filter(|(_, lossless)| *lossless)
.map(|(value, _)| {
crate::service_worker_runtime::ServiceWorkerRegistrationId::from_u64_for_binding(
value,
)
});
let Some(registration_id) = registration_id else {
let _ = resolver.reject(
scope,
v8::Exception::type_error(
scope,
v8str(
scope,
"Illegal invocation of ServiceWorkerRegistration.update",
),
),
);
return;
};
let Some(state) = get_worker_state(scope) else {
return;
};
let Some(runtime) = worker_service_worker_runtime(scope) else {
let error = crate::context_bootstrap::new_dom_exception_value(
scope,
"The registration runtime is unavailable.",
"InvalidStateError",
);
let _ = resolver.reject(scope, error);
return;
};
let request = {
let mut state = state.borrow_mut();
let caller_version_id = match state.global_kind {
super::thread::WorkerGlobalKind::Service { version_id, .. } => Some(version_id),
_ => None,
};
let Some(document_url) = state.current_script_url.clone() else {
return;
};
let request_id = state.service_worker_update_request_ids.allocate();
state.pending_service_worker_updates.insert(
request_id,
PendingServiceWorkerUpdate {
resolver: v8::Global::new(scope, resolver),
registration: v8::Global::new(scope, args.this()),
},
);
crate::service_worker_runtime::ServiceWorkerRegistrationUpdate {
registration_id,
caller_version_id,
storage_key: state.storage_key.serialized_storage_key(),
document_url,
request_client: state.loader.request_client().clone(),
network_policy: super::handle::WorkerNetworkPolicy {
secure_context: state.secure_context,
permission_overrides: state.permission_overrides.clone(),
extra_http_headers: state.extra_http_headers.clone(),
network_offline: state.network_offline,
blocked_url_patterns: state.blocked_url_patterns.clone(),
network_partition_key: state.network_partition_key.clone(),
fetch_subresource_interception_enabled: state
.fetch_subresource_interception_enabled,
fetch_subresource_interception_resource_type: state
.fetch_subresource_interception_resource_type,
},
worker_context_runtime: state.worker_context_runtime.clone(),
broadcast_channel_top_level_site: Some(state.storage_key.top_level_site().to_owned()),
indexed_db_manager: state.indexed_db_manager.clone(),
storage_bucket_store: state.storage_bucket_store.clone(),
completion: crate::service_worker_runtime::ServiceWorkerRegisterJob::Worker {
request_id,
completion_tx: state.worker_wake_tx.clone(),
},
}
};
runtime.start_registration_update(request);
}
fn service_worker_registration_unregister_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
_args: v8::FunctionCallbackArguments<'s>,
+18
View File
@@ -109,6 +109,18 @@ pub(crate) enum WorkerMessage {
ServiceWorkerPeriodicSyncEvent(Box<ServiceWorkerPeriodicSyncEvent>),
/// Dispatch `navigator.serviceWorker` `controllerchange` in this worker client.
ServiceWorkerControllerChange,
/// Dispatch updatefound on the original registration in this service worker realm.
ServiceWorkerRegistrationUpdateFound,
/// Settle a registration.update() promise on the requesting worker loop.
ServiceWorkerUpdateResult {
request_id: u64,
result: Box<
Result<
crate::service_worker_runtime::ServiceWorkerRegistrationSnapshot,
crate::service_worker_runtime::ServiceWorkerRegistrationError,
>,
>,
},
/// Resolve a Service Worker `SyncManager.register()` request in the worker.
ServiceWorkerSyncRegistrationResult(ServiceWorkerSyncRegistrationResult),
/// Resolve a Service Worker `SyncManager.getTags()` request in the worker.
@@ -942,6 +954,12 @@ impl WorkerHandle {
.send(WorkerMessage::ServiceWorkerLifecycleEvent(Box::new(event)));
}
pub(crate) fn dispatch_service_worker_registration_update_found(&self) {
let _ = self
.tx
.send(WorkerMessage::ServiceWorkerRegistrationUpdateFound);
}
pub(crate) fn dispatch_service_worker_fetch_event(&self, event: ServiceWorkerFetchEvent) {
let _ = self
.tx
+3
View File
@@ -22,6 +22,9 @@ mod script_mime;
mod thread;
mod timer_callback;
pub(crate) type WorkerScriptUpdateResources =
std::collections::HashMap<url::Url, Result<WorkerScriptResource, String>>;
pub(crate) use thread::perform_callback_cleanup_checkpoint_if_worker;
pub(crate) use data_url::decode_data_url_script_source;
@@ -4090,6 +4090,7 @@ fn maybe_send_service_worker_lifecycle_completion(
let completion = pending.completion.clone();
if completion.kind == ServiceWorkerLifecycleEventKind::Install {
state.service_worker_can_import_new_scripts = false;
state.service_worker_updated_script_resources.clear();
}
state
.pending_service_worker_lifecycle_events
+49
View File
@@ -84,6 +84,7 @@ use super::global_scope::{
drain_service_worker_push_get_subscription_result, drain_service_worker_push_subscribe_result,
drain_service_worker_push_unsubscribe_result, drain_service_worker_show_notification_result,
drain_service_worker_sync_get_tags_result, drain_service_worker_sync_registration_result,
drain_service_worker_update_result,
drain_worker_fetch_completion, drain_worker_opfs_completion, drain_worker_webcrypto_completion,
drain_worker_xhr_event, fail_pending_worker_csp_report, fail_pending_worker_fetch,
fail_pending_worker_fetch_auth, fail_pending_worker_fetch_response, fail_pending_worker_xhr,
@@ -204,6 +205,7 @@ pub(crate) struct WorkerSpawnOptions {
pub(crate) creator_storage_key: Option<MoliStorageKey>,
pub(crate) service_worker_runtime: Option<ServiceWorkerRuntimeService>,
pub(crate) service_worker_script_resources: Vec<WorkerScriptResource>,
pub(crate) service_worker_updated_script_resources: crate::worker::WorkerScriptUpdateResources,
pub(crate) service_worker_can_import_new_scripts: bool,
pub(crate) reserved_service_worker_client_id: Option<ServiceWorkerClientId>,
pub(crate) indexed_db_manager: Option<crate::context_bootstrap::WeakIndexedDbManager>,
@@ -341,6 +343,7 @@ impl WorkerSpawnOptions {
creator_storage_key: None,
service_worker_runtime: None,
service_worker_script_resources: Vec::new(),
service_worker_updated_script_resources: Default::default(),
service_worker_can_import_new_scripts: true,
reserved_service_worker_client_id: None,
indexed_db_manager: None,
@@ -469,6 +472,14 @@ impl WorkerSpawnOptions {
self
}
pub(crate) fn with_service_worker_updated_script_resources(
mut self,
resources: crate::worker::WorkerScriptUpdateResources,
) -> Self {
self.service_worker_updated_script_resources = resources;
self
}
pub(crate) fn with_storage_key_top_level_site(self, top_level_site: Option<String>) -> Self {
self.with_broadcast_channel_top_level_site(top_level_site)
}
@@ -584,6 +595,7 @@ fn worker_has_pending_async(state: &Rc<RefCell<WorkerGlobalState>>) -> bool {
|| !state.pending_service_worker_client_focuses.is_empty()
|| !state.pending_service_worker_clients_open_windows.is_empty()
|| !state.pending_service_worker_show_notifications.is_empty()
|| !state.pending_service_worker_updates.is_empty()
|| !state.pending_service_worker_get_notifications.is_empty()
|| !state.pending_service_worker_sync_registrations.is_empty()
|| !state.pending_service_worker_sync_get_tags.is_empty()
@@ -1364,6 +1376,7 @@ pub(crate) fn spawn_worker_with_options(options: WorkerSpawnOptions) -> WorkerHa
creator_storage_key,
service_worker_runtime,
service_worker_script_resources,
service_worker_updated_script_resources,
service_worker_can_import_new_scripts,
reserved_service_worker_client_id,
indexed_db_manager,
@@ -1418,6 +1431,7 @@ pub(crate) fn spawn_worker_with_options(options: WorkerSpawnOptions) -> WorkerHa
creator_storage_key,
service_worker_runtime,
service_worker_script_resources,
service_worker_updated_script_resources,
service_worker_can_import_new_scripts,
reserved_service_worker_client_id,
indexed_db_manager,
@@ -1554,6 +1568,7 @@ async fn worker_main(
creator_storage_key: Option<MoliStorageKey>,
service_worker_runtime: Option<ServiceWorkerRuntimeService>,
service_worker_script_resources: Vec<WorkerScriptResource>,
service_worker_updated_script_resources: crate::worker::WorkerScriptUpdateResources,
service_worker_can_import_new_scripts: bool,
reserved_service_worker_client_id: Option<ServiceWorkerClientId>,
indexed_db_manager: Option<crate::context_bootstrap::WeakIndexedDbManager>,
@@ -1678,6 +1693,7 @@ async fn worker_main(
.into_iter()
.map(|resource| (resource.request_url.clone(), resource))
.collect(),
service_worker_updated_script_resources,
service_worker_can_import_new_scripts,
referrer_policy,
module_static_import_content_security_policies,
@@ -1735,6 +1751,8 @@ async fn worker_main(
pending_service_worker_client_focuses: std::collections::HashMap::new(),
pending_service_worker_clients_open_windows: std::collections::HashMap::new(),
pending_service_worker_show_notifications: std::collections::HashMap::new(),
pending_service_worker_updates: std::collections::HashMap::new(),
service_worker_update_request_ids: Default::default(),
pending_service_worker_get_notifications: std::collections::HashMap::new(),
pending_service_worker_sync_registrations: std::collections::HashMap::new(),
pending_service_worker_sync_get_tags: std::collections::HashMap::new(),
@@ -2503,6 +2521,37 @@ async fn worker_main(
perform_worker_microtask_checkpoint_and_report_pending_promise_rejections(scope);
drain_worker_dynamic_module_imports(scope, &state, &module_graph_fetch_tx);
}
WorkerLoopWake::Message(Some(WorkerMessage::ServiceWorkerUpdateResult {
request_id,
result,
})) => {
if pending_module_bootstrap.is_some() {
pending_bootstrap_messages
.push_back(WorkerMessage::ServiceWorkerUpdateResult { request_id, result });
continue;
}
let scope = pin!(v8::HandleScope::new(worker_isolate.worker_isolate_mut()));
let scope = &mut scope.init();
let ctx = v8::Local::new(scope, &context);
let scope = &mut v8::ContextScope::new(scope, ctx);
drain_service_worker_update_result(scope, &state, request_id, *result);
perform_worker_microtask_checkpoint_and_report_pending_promise_rejections(scope);
drain_worker_dynamic_module_imports(scope, &state, &module_graph_fetch_tx);
}
WorkerLoopWake::Message(Some(WorkerMessage::ServiceWorkerRegistrationUpdateFound)) => {
if pending_module_bootstrap.is_some() {
pending_bootstrap_messages
.push_back(WorkerMessage::ServiceWorkerRegistrationUpdateFound);
continue;
}
let scope = pin!(v8::HandleScope::new(worker_isolate.worker_isolate_mut()));
let scope = &mut scope.init();
let ctx = v8::Local::new(scope, &context);
let scope = &mut v8::ContextScope::new(scope, ctx);
super::global_scope::dispatch_service_worker_registration_update_found(scope);
perform_worker_microtask_checkpoint_and_report_pending_promise_rejections(scope);
drain_worker_dynamic_module_imports(scope, &state, &module_graph_fetch_tx);
}
WorkerLoopWake::Message(Some(WorkerMessage::ServiceWorkerShowNotificationResult(
result,
))) => {