fix(timers): preserve the initiating script nonce for string handlers

This commit is contained in:
ldm0
2026-09-14 15:06:20 +08:00
parent 152f25bf6c
commit 59eef217f9
5 changed files with 116 additions and 4 deletions
@@ -2972,8 +2972,6 @@ html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-worker
html/semantics/scripting-1/the-script-element/module/crossorigin.html
html/semantics/scripting-1/the-script-element/module/dynamic-import/alpha/base-url-worker-importScripts.html
html/semantics/scripting-1/the-script-element/module/dynamic-import/code-cache-base-url.html
html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html
html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html
html/semantics/scripting-1/the-script-element/module/inline-async-execorder.html
html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-1.html
html/semantics/scripting-1/the-script-element/moving-between-documents/ordering/delay-load-event-2.html
@@ -6508,6 +6508,8 @@ html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compi
html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-base-url-inline-module.html
html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-classic.html
html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-module.html
html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-classic.html
html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-nonce-module.html
html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-of-promise-result.html
html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-other-document.html
html/semantics/scripting-1/the-script-element/module/dynamic-import/v8-code-cache.html
+14 -2
View File
@@ -18,7 +18,8 @@ use crate::{
page_task_queue::RendererPageTimerSelection,
script_provenance::CompiledStringProvenance,
util::{
context_host_ptr_from_global_bridge, create_script_origin_with_base_url, get_private_value,
context_host_ptr_from_global_bridge, create_script_origin_with_base_url_and_nonce,
get_private_value, script_nonce_from_host_defined_options,
},
};
use moli_time::{
@@ -44,6 +45,8 @@ struct ScheduledTimerSource {
use_target_context: bool,
source: String,
provenance: CompiledStringProvenance,
// A snapshot of the initiating script, not the eventual timer caller/realm.
script_nonce: Option<String>,
}
struct ScheduledTimerFunction {
@@ -493,6 +496,9 @@ impl HostTimeoutScheduler {
owner: HostTimerOwner,
extra_args: Vec<v8::Global<v8::Value>>,
) -> u32 {
let script_nonce = scope
.get_current_host_defined_options()
.and_then(|options| script_nonce_from_host_defined_options(scope, options));
let Some(owner) = scheduled_timer_owner_for_target(scope, owner, Some(receiver), context)
else {
return 0;
@@ -519,6 +525,7 @@ impl HostTimeoutScheduler {
use_target_context,
source,
provenance,
script_nonce,
}),
owner,
is_interval: false,
@@ -541,6 +548,9 @@ impl HostTimeoutScheduler {
owner: HostTimerOwner,
extra_args: Vec<v8::Global<v8::Value>>,
) -> u32 {
let script_nonce = scope
.get_current_host_defined_options()
.and_then(|options| script_nonce_from_host_defined_options(scope, options));
let Some(owner) = scheduled_timer_owner_for_target(scope, owner, Some(receiver), context)
else {
return 0;
@@ -567,6 +577,7 @@ impl HostTimeoutScheduler {
use_target_context,
source,
provenance,
script_nonce,
}),
owner,
is_interval: true,
@@ -1268,11 +1279,12 @@ fn run_window_timer_source(
exception: None,
}));
};
let origin = create_script_origin_with_base_url(
let origin = create_script_origin_with_base_url_and_nonce(
&mut scope,
source.provenance.source_url().as_str(),
0,
Some(source.provenance.module_base_url()),
source.script_nonce.as_deref(),
);
let Some(script) = v8::Script::compile(&scope, source_value, Some(&origin)) else {
let exception = scope.exception();
@@ -15622,6 +15622,7 @@ mod queue_microtask;
mod rendering_update;
mod script_terminal_completion;
mod streams;
mod string_timers;
mod url_components;
mod webidl_collections;
mod webidl_fetch;
@@ -0,0 +1,99 @@
use super::*;
use crate::script_provenance::CompiledStringProvenance;
fn execute_nonce_script(vm: &mut ScriptVm, source: &str, url: &Url, nonce: Option<&str>) {
vm.exec_in_enclosing_script_turn_with_provenance(
source,
&CompiledStringProvenance::at_url(url.clone()),
0,
nonce,
true,
)
.map_err(|error| error.into_anyhow())
.expect("the initiating script should execute");
}
fn consume_string_timer(vm: &mut ScriptVm) {
assert!(matches!(
vm.run_next_timeout_for_test()
.expect("string timer should execute"),
crate::host::HostTimeoutRunResult::Consumed
));
}
fn assert_import_nonce(vm: &mut ScriptVm, base_url: &Url, nonce: Option<&str>) {
let request = vm
.document_runtime
.take_next_native_dynamic_module_import()
.expect("the timer should queue a dynamic import")
.into_dynamic_import_request();
assert_eq!(request.specifier(), "./dependency.mjs");
assert_eq!(request.base_url(), base_url);
assert_eq!(request.fetch_metadata().nonce.as_deref(), nonce);
assert!(!request.fetch_metadata().parser_inserted);
assert!(request.fetch_metadata().integrity.is_none());
}
#[test]
fn string_timers_preserve_nonce_for_timeout_interval_and_nested_source() {
for (source, timer_turns) in [
(r#"setTimeout("import('./dependency.mjs')", 0);"#, 1),
(
r#"globalThis.__nonceInterval = setInterval("clearInterval(__nonceInterval); import('./dependency.mjs')", 0);"#,
1,
),
(
r#"setTimeout("setTimeout(\"import('./dependency.mjs')\", 0)", 0);"#,
2,
),
] {
let mut vm = new_storage_test_vm("https://timer-nonce.test/page.html");
let script_url = Url::parse("https://timer-nonce.test/scripts/initiator.js").unwrap();
execute_nonce_script(&mut vm, source, &script_url, Some("initiating-nonce"));
for _ in 0..timer_turns {
consume_string_timer(&mut vm);
}
assert_import_nonce(&mut vm, &script_url, Some("initiating-nonce"));
}
}
#[test]
fn string_timers_keep_nonce_snapshots_separate_for_identical_source_and_url() {
let mut vm = new_storage_test_vm("https://timer-nonce.test/page.html");
let script_url = Url::parse("https://timer-nonce.test/scripts/same.js").unwrap();
let nonces = [Some("first-nonce"), None, Some("last-nonce")];
for nonce in nonces {
execute_nonce_script(
&mut vm,
r#"setTimeout("import('./dependency.mjs')", 0);"#,
&script_url,
nonce,
);
}
for nonce in nonces {
consume_string_timer(&mut vm);
assert_import_nonce(&mut vm, &script_url, nonce);
}
}
#[test]
fn string_timers_use_the_active_function_nonce_without_borrowing_the_callers_nonce() {
for initiating_nonce in [Some("function-nonce"), None] {
let mut vm = new_storage_test_vm("https://timer-nonce.test/page.html");
let script_url = Url::parse("https://timer-nonce.test/scripts/function.js").unwrap();
execute_nonce_script(
&mut vm,
r#"globalThis.scheduleFromInitiator = () => setTimeout("import('./dependency.mjs')", 0);"#,
&script_url,
initiating_nonce,
);
execute_nonce_script(
&mut vm,
"scheduleFromInitiator();",
&Url::parse("https://timer-nonce.test/other/caller.js").unwrap(),
Some("caller-nonce"),
);
consume_string_timer(&mut vm);
assert_import_nonce(&mut vm, &script_url, initiating_nonce);
}
}