fix(script): share the service-worker loader for child classics

This commit is contained in:
ldm0
2026-09-30 00:59:48 +08:00
parent d92d4da858
commit e1820f2b6b
2 changed files with 72 additions and 2 deletions
+52
View File
@@ -335,6 +335,11 @@ fn fixture_response(
);
("200 OK", javascript.to_owned(), body)
}
"/child-classic.html" => (
"200 OK",
"Content-Type: text/html\r\n".to_owned(),
"<!doctype html><body><script>globalThis.sriExecutions=0</script><script src='/sw-default.js?parser'></script>".to_owned(),
),
"/page.html" | "/parser.html" | "/cookie-page.html" | "/csp-page.html" => {
let mut html = "<!doctype html><body><script>globalThis.sriEvents = {}; globalThis.sriExecutions = 0;</script>".to_owned();
if path == "/csp-page.html" {
@@ -792,6 +797,53 @@ async fn manifest_roundtrip_checks_intermediate_and_final_cors_responses() -> Re
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn child_classic_scripts_use_the_controlling_service_worker() -> Result<()> {
let servers = IntegrityServers::spawn().await?;
let browser = Browser::new(AppConfig::default())?;
let mut page = browser
.fetch(&format!("{}/page.html", servers.origin))
.await?;
let result = tokio::time::timeout(
Duration::from_secs(15),
page.evaluate_runtime_expression_with_await_async(
r#"(async () => {
const controlled = navigator.serviceWorker.controller ? Promise.resolve() :
new Promise(resolve => navigator.serviceWorker.addEventListener('controllerchange', resolve, {once:true}));
await navigator.serviceWorker.register('/worker.js', {scope:'/'});
await navigator.serviceWorker.ready;
await controlled;
const child = document.createElement('iframe');
child.src='/child-classic.html';
await new Promise(resolve => {child.onload=resolve;document.body.append(child);});
const other = child.contentWindow;
if (other.sriExecutions !== 1) throw new Error('parser script did not execute');
const script = other.document.createElement('script');
script.src='/sw-default.js?dynamic';
await new Promise((resolve,reject) => {
script.onload=resolve;
script.onerror=() => reject(new Error('dynamic script failed'));
other.document.body.append(script);
});
const executions = other.sriExecutions;
child.remove();
return executions;
})()"#,
true,
),
).await??;
assert_eq!(result["value"], 2, "{result}");
assert!(
!servers
.requests
.lock()
.iter()
.any(|request| request.path.starts_with("/sw-default.js")),
"both child script paths must be served by the controlling service worker"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn service_worker_script_integrity_preserves_response_filter() -> Result<()> {
let servers = IntegrityServers::spawn().await?;
@@ -295,14 +295,23 @@ impl JsContextHost {
.unwrap_or_else(|| self.document_character_set().to_owned());
let completion_tx = self.resource_completion_tx.clone();
let task_loader = loader.clone();
let browser_context_runtime = self.browser_context_runtime();
let client_id = self.service_worker_client_id_for_subresource_owner(
crate::native_bridge::context_host::OwnerDispatchScope::Child(handle),
);
let document_url = network_attribution.document_url.clone();
loader.spawn_resource_task(async move {
let outcome =
crate::planning::load_prepared_script_source_outcome_with_document_character_set(
crate::planning::load_service_worker_aware_external_script_source_outcome(
&script_for_load,
&task_loader.fetch_context().request_origin(),
task_loader.request_client(),
task_loader.task_runner(),
Some(&document_character_set),
None,
browser_context_runtime,
client_id,
document_url,
)
.await;
let _ = completion_tx.send_child_classic_script(ChildClassicScriptLoadCompletion {
@@ -708,14 +717,23 @@ impl JsContextHost {
return;
}
let task_loader = loader.clone();
let browser_context_runtime = self.browser_context_runtime();
let client_id = self.service_worker_client_id_for_subresource_owner(
crate::native_bridge::context_host::OwnerDispatchScope::Child(child_handle),
);
let document_url = network_attribution.document_url.clone();
loader.spawn_resource_task(async move {
let outcome =
crate::planning::load_prepared_script_source_outcome_with_document_character_set(
crate::planning::load_service_worker_aware_external_script_source_outcome(
&script_for_load,
&task_loader.fetch_context().request_origin(),
task_loader.request_client(),
task_loader.task_runner(),
Some(&document_character_set),
None,
browser_context_runtime,
client_id,
document_url,
)
.await;
let _ = completion_tx.send_child_classic_script(ChildClassicScriptLoadCompletion {