diff --git a/moli-renderer-v8/src/host/timers.rs b/moli-renderer-v8/src/host/timers.rs index fe931ed7cf..6b7f443e25 100644 --- a/moli-renderer-v8/src/host/timers.rs +++ b/moli-renderer-v8/src/host/timers.rs @@ -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::{TimerId, TimerReadyAllowance, TimerScheduler}; @@ -42,6 +43,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, } struct ScheduledTimerFunction { @@ -491,6 +494,9 @@ impl HostTimeoutScheduler { owner: HostTimerOwner, extra_args: Vec>, ) -> 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; @@ -517,6 +523,7 @@ impl HostTimeoutScheduler { use_target_context, source, provenance, + script_nonce, }), owner, is_interval: false, @@ -539,6 +546,9 @@ impl HostTimeoutScheduler { owner: HostTimerOwner, extra_args: Vec>, ) -> 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; @@ -565,6 +575,7 @@ impl HostTimeoutScheduler { use_target_context, source, provenance, + script_nonce, }), owner, is_interval: true, @@ -1235,11 +1246,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(); diff --git a/moli-renderer-v8/src/script_vm/tests/mod.rs b/moli-renderer-v8/src/script_vm/tests/mod.rs index b3dc8a8386..3ade5223b5 100644 --- a/moli-renderer-v8/src/script_vm/tests/mod.rs +++ b/moli-renderer-v8/src/script_vm/tests/mod.rs @@ -15219,3 +15219,5 @@ mod webidl_receivers; mod webidl_trusted_types; mod websocket; mod window_execution_context; + +mod string_timers; diff --git a/moli-renderer-v8/src/script_vm/tests/string_timers.rs b/moli-renderer-v8/src/script_vm/tests/string_timers.rs new file mode 100644 index 0000000000..4f8aee4120 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/string_timers.rs @@ -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); + } +}