From 9fe45344123851664d986b23ed0cdc32cc19f7d5 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Tue, 8 Sep 2026 17:42:54 +0800 Subject: [PATCH] fix(modules): bind synthetic text sources to their owning records Resolve JSON and CSS evaluation data by exact module identity in the compiling realm, with weak context entries owned by compiled records. Remove the main-Document source lookup that broke child imports. Cover static and deferred dynamic child imports, realm-local caches, parse errors, identity collisions and record disposal. Mark both JSON/CSS cross-origin WPT cases passing after CLI/CDP verification. --- .../wpt-cross-current/failed-cases.txt | 2 - .../wpt-cross-current/passed-cases.txt | 2 + moli-core/tests/scripts.rs | 3 + .../tests/scripts/child_synthetic_modules.rs | 153 ++++++++++++++++++ .../script_scheduling/module_facade.rs | 9 -- moli-renderer-v8/src/host/scripts.rs | 7 - moli-renderer-v8/src/module_runtime.rs | 9 +- .../src/module_runtime/modulator.rs | 8 - moli-renderer-v8/src/module_runtime/record.rs | 12 ++ .../src/module_runtime/synthetic_text.rs | 153 ++++++++++++++++++ .../context_host/resource_loading.rs | 13 +- .../src/script_vm/native_module.rs | 118 ++++++-------- 12 files changed, 371 insertions(+), 118 deletions(-) create mode 100644 moli-core/tests/scripts/child_synthetic_modules.rs create mode 100644 moli-renderer-v8/src/module_runtime/synthetic_text.rs diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index 00aece7268..407517cf5f 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -3147,12 +3147,10 @@ html/semantics/popovers/popover-shadow-dom.html html/semantics/popovers/popover-stacking.html html/semantics/popovers/popover-types-with-hints.html html/semantics/scripting-1/the-script-element/css-module/content-type-checking.html -html/semantics/scripting-1/the-script-element/css-module/cors-crossorigin-requests.html html/semantics/scripting-1/the-script-element/execution-timing/083.html html/semantics/scripting-1/the-script-element/execution-timing/085.html html/semantics/scripting-1/the-script-element/execution-timing/128.html html/semantics/scripting-1/the-script-element/execution-timing/137.html -html/semantics/scripting-1/the-script-element/json-module/cors-crossorigin-requests.html html/semantics/scripting-1/the-script-element/json-module/json-module-service-worker-test.https.html html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-window-onerror-module.html html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-window-onerror.html diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index 3976a2be71..049259e05b 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -6612,6 +6612,7 @@ html/semantics/scripting-1/the-script-element/async_011.htm html/semantics/scripting-1/the-script-element/css-module/charset-2.html html/semantics/scripting-1/the-script-element/css-module/charset-bom.html html/semantics/scripting-1/the-script-element/css-module/charset.html +html/semantics/scripting-1/the-script-element/css-module/cors-crossorigin-requests.html html/semantics/scripting-1/the-script-element/css-module/css-module-parse-error.html html/semantics/scripting-1/the-script-element/css-module/import-css-module-basic.html html/semantics/scripting-1/the-script-element/css-module/import-css-module-dynamic.html @@ -6778,6 +6779,7 @@ html/semantics/scripting-1/the-script-element/import-attributes/invalid-type-att html/semantics/scripting-1/the-script-element/import-attributes/unsupported-attribute.html html/semantics/scripting-1/the-script-element/json-module/charset-2.html html/semantics/scripting-1/the-script-element/json-module/charset.html +html/semantics/scripting-1/the-script-element/json-module/cors-crossorigin-requests.html html/semantics/scripting-1/the-script-element/json-module/integrity.html html/semantics/scripting-1/the-script-element/json-module/module.html html/semantics/scripting-1/the-script-element/json-module/parse-error.html diff --git a/moli-core/tests/scripts.rs b/moli-core/tests/scripts.rs index e7d93a2344..711129235e 100644 --- a/moli-core/tests/scripts.rs +++ b/moli-core/tests/scripts.rs @@ -13,6 +13,9 @@ use moli_core::{ use support::FixtureServer; use tokio::time::Duration; +#[path = "scripts/child_synthetic_modules.rs"] +mod child_synthetic_modules; + fn diagnostic_global<'a>( page: &'a moli_core::page::Page, name: &str, diff --git a/moli-core/tests/scripts/child_synthetic_modules.rs b/moli-core/tests/scripts/child_synthetic_modules.rs new file mode 100644 index 0000000000..e6cb8a16c7 --- /dev/null +++ b/moli-core/tests/scripts/child_synthetic_modules.rs @@ -0,0 +1,153 @@ +use super::*; + +async fn evaluate_child_synthetic_expression(expression: &str) -> Result { + let server = FixtureServer::spawn().await?; + let browser = Browser::new(AppConfig::default())?; + let mut page = browser.fetch(&server.url("/static")).await?; + let result = page + .evaluate_runtime_expression_with_await_async(expression, true) + .await?; + server.shutdown().await; + Ok(result) +} + +async fn child_synthetic_probe(source: &str) -> Result { + let html = format!( + r#""# + ); + let expression = format!( + r#"new Promise(resolve => {{ + const frame = document.createElement('iframe'); + frame.onload = () => resolve(frame.contentWindow.result); + frame.srcdoc = {}; + document.body.appendChild(frame); + }})"#, + serde_json::to_string(&html)? + ); + evaluate_child_synthetic_expression(&expression).await +} + +#[tokio::test(flavor = "multi_thread")] +async fn child_synthetic_json_module_uses_its_own_document_source() -> Result<()> { + let result = child_synthetic_probe( + r#"import value from 'data:application/json,{"answer":42}' with { type: 'json' }; + window.result = value.answer + ':' + (Object.getPrototypeOf(value) === Object.prototype);"#, + ) + .await?; + assert_eq!(result["value"], "42:true", "{result:?}"); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn child_synthetic_css_module_uses_its_own_document_source() -> Result<()> { + let result = child_synthetic_probe( + r#"import sheet from 'data:text/css,body%20%7B%20color%3A%20red%3B%20%7D' with { type: 'css' }; + window.result = (sheet instanceof CSSStyleSheet) + ':' + sheet.cssRules.length;"#, + ) + .await?; + assert_eq!(result["value"], "true:1", "{result:?}"); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn child_synthetic_json_parse_error_is_not_a_missing_source_error() -> Result<()> { + let result = child_synthetic_probe( + r#"import value from 'data:application/json,not-json' with { type: 'json' }; + window.result = 'must not execute';"#, + ) + .await?; + assert!( + result["value"] + .as_str() + .is_some_and(|value| value.starts_with("SyntaxError: ")), + "{result:?}" + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn child_synthetic_dynamic_import_after_load_uses_its_realm() -> Result<()> { + let result = evaluate_child_synthetic_expression( + r#"(async () => { + const frame = document.createElement('iframe'); + const loaded = new Promise(resolve => frame.onload = resolve); + frame.srcdoc = ``; + document.body.appendChild(frame); + await loaded; + return await frame.contentWindow.probe(); + })()"#, + ) + .await?; + assert_eq!( + result["value"], "true:true:true:true:true:true", + "{result:?}" + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn child_synthetic_module_values_are_cached_per_realm_not_per_url() -> Result<()> { + let result = evaluate_child_synthetic_expression( + r#"(async () => { + const jsonUrl = 'data:application/json,{"answer":42}'; + const cssUrl = 'data:text/css,body%7Bcolor%3Ared%7D'; + const rootJson = (await import(jsonUrl, {with: {type: 'json'}})).default; + const rootSheet = (await import(cssUrl, {with: {type: 'css'}})).default; + const makeFrame = async () => { + const frame = document.createElement('iframe'); + const loaded = new Promise(resolve => frame.onload = resolve); + frame.srcdoc = ``; + document.body.appendChild(frame); + await loaded; + return frame.contentWindow; + }; + const [left, right] = await Promise.all([makeFrame(), makeFrame()]); + left.value.answer = 7; + left.sheet.insertRule('p { color: blue; }', 1); + return [rootJson.answer === 42, right.value.answer === 42, + left.value.answer === 7, rootSheet.cssRules.length === 1, + right.sheet.cssRules.length === 1, left.sheet.cssRules.length === 2, + left.value !== right.value, left.value !== rootJson, + left.sheet !== right.sheet, left.sheet !== rootSheet, + Object.getPrototypeOf(left.value) === left.Object.prototype, + Object.getPrototypeOf(right.value) === right.Object.prototype, + left.sheet instanceof left.CSSStyleSheet, + right.sheet instanceof right.CSSStyleSheet, + await left.readAgain(), await right.readAgain()].join(':'); + })()"#, + ) + .await?; + assert_eq!(result["value"], ["true"; 16].join(":"), "{result:?}"); + Ok(()) +} diff --git a/moli-renderer-v8/src/document_runtime/script_scheduling/module_facade.rs b/moli-renderer-v8/src/document_runtime/script_scheduling/module_facade.rs index 7ee40424dc..7f76029dde 100644 --- a/moli-renderer-v8/src/document_runtime/script_scheduling/module_facade.rs +++ b/moli-renderer-v8/src/document_runtime/script_scheduling/module_facade.rs @@ -198,15 +198,6 @@ impl DocumentRuntime { .native_module_source(entry_id) } - pub(crate) fn native_module_source_for( - &self, - module: v8::Local<'_, v8::Module>, - ) -> Option<(ModuleMapKey, ModuleSource)> { - self.script_lifecycle - .scripts() - .native_module_source_for(module) - } - pub(crate) fn native_module_wasm_record_for( &self, module: v8::Local<'_, v8::Module>, diff --git a/moli-renderer-v8/src/host/scripts.rs b/moli-renderer-v8/src/host/scripts.rs index ad6dfd02a4..3e5475efc3 100644 --- a/moli-renderer-v8/src/host/scripts.rs +++ b/moli-renderer-v8/src/host/scripts.rs @@ -601,13 +601,6 @@ impl HostScriptScheduler { self.module_owner.native_module_source(entry_id) } - pub(crate) fn native_module_source_for( - &self, - module: v8::Local<'_, v8::Module>, - ) -> Option<(ModuleMapKey, ModuleSource)> { - self.module_owner.native_module_source_for(module) - } - pub(crate) fn native_module_wasm_record_for( &self, module: v8::Local<'_, v8::Module>, diff --git a/moli-renderer-v8/src/module_runtime.rs b/moli-renderer-v8/src/module_runtime.rs index 9b1278a455..54e6452bc0 100644 --- a/moli-renderer-v8/src/module_runtime.rs +++ b/moli-renderer-v8/src/module_runtime.rs @@ -12,6 +12,7 @@ mod record; mod record_resolver; mod resolver; mod single_module_fetch; +mod synthetic_text; #[cfg(test)] mod tests; mod tree_adapter; @@ -51,6 +52,7 @@ pub(crate) use self::resolver::{ ResolverScopeGuard, resolve_static_module_callback, resolve_static_source_callback, }; pub(crate) use self::single_module_fetch::NativeModuleSingleFetchRequest; +pub(crate) use self::synthetic_text::SyntheticTextModuleSource; pub(crate) use self::tree_owner::{ NativeModuleTreeDocumentOwnerAdapter, NativeModuleTreeFrameDocumentOwner, }; @@ -486,13 +488,6 @@ impl ModuleOwnerState { self.document_modulator.entry(entry_id).source().cloned() } - pub(crate) fn native_module_source_for( - &self, - module: v8::Local<'_, v8::Module>, - ) -> Option<(ModuleMapKey, ModuleSource)> { - self.document_modulator.module_source_for(module) - } - pub(crate) fn native_module_wasm_record_for( &self, module: v8::Local<'_, v8::Module>, diff --git a/moli-renderer-v8/src/module_runtime/modulator.rs b/moli-renderer-v8/src/module_runtime/modulator.rs index 45c8f31cc5..05e75121d0 100644 --- a/moli-renderer-v8/src/module_runtime/modulator.rs +++ b/moli-renderer-v8/src/module_runtime/modulator.rs @@ -655,14 +655,6 @@ impl NativeDocumentModulator { .set_record_state_for_entry(self.core.entry(entry_id), ModuleRecordState::Evaluated); } - pub(crate) fn module_source_for( - &self, - module: v8::Local<'_, v8::Module>, - ) -> Option<(ModuleMapKey, ModuleSource)> { - let entry = self.entry(self.module_entry_id_for(module)?); - Some((entry.effective_key().clone(), entry.source()?.clone())) - } - pub(crate) fn module_wasm_record_for( &self, module: v8::Local<'_, v8::Module>, diff --git a/moli-renderer-v8/src/module_runtime/record.rs b/moli-renderer-v8/src/module_runtime/record.rs index 335fd32ad8..aacf68cebe 100644 --- a/moli-renderer-v8/src/module_runtime/record.rs +++ b/moli-renderer-v8/src/module_runtime/record.rs @@ -19,6 +19,8 @@ pub(crate) struct ModuleRecordEntry { compiled_module: v8::Global, requests: Vec, wasm_module: Option, + // Own the evaluation data; the Context's lookup only keeps a weak reference. + synthetic_text_module_source: Option>, state: ModuleRecordState, } @@ -33,6 +35,7 @@ impl ModuleRecordEntry { compiled_module, requests, wasm_module: None, + synthetic_text_module_source: None, state: ModuleRecordState::Compiled, } } @@ -48,6 +51,7 @@ impl ModuleRecordEntry { compiled_module, requests, wasm_module: Some(wasm_module), + synthetic_text_module_source: None, state: ModuleRecordState::Compiled, } } @@ -56,6 +60,14 @@ impl ModuleRecordEntry { &self.key } + pub(crate) fn with_synthetic_text_module_source( + mut self, + source: Rc, + ) -> Self { + self.synthetic_text_module_source = Some(source); + self + } + pub(crate) fn compiled_module(&self) -> &v8::Global { &self.compiled_module } diff --git a/moli-renderer-v8/src/module_runtime/synthetic_text.rs b/moli-renderer-v8/src/module_runtime/synthetic_text.rs new file mode 100644 index 0000000000..2906395af0 --- /dev/null +++ b/moli-renderer-v8/src/module_runtime/synthetic_text.rs @@ -0,0 +1,153 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::{Rc, Weak}; + +use super::{ModuleIdentityHash, ModuleMapKey, module_identity_hash_from_v8_module}; + +/// Evaluation data belongs to the compiled module record, not the page's +/// default Document. The Context only holds weak references, so this lookup +/// does not keep a disposed realm or its compiled modules alive. +#[derive(Debug)] +pub(crate) struct SyntheticTextModuleSource { + module: v8::Global, + key: ModuleMapKey, + source: String, +} + +#[derive(Default)] +struct SyntheticTextModuleSources { + entries: RefCell>>>, +} + +impl SyntheticTextModuleSource { + pub(crate) fn register( + scope: &mut v8::PinScope<'_, '_>, + module: v8::Local<'_, v8::Module>, + key: ModuleMapKey, + source: &str, + ) -> Rc { + let context = scope.get_current_context(); + let sources = context + .get_slot::() + .unwrap_or_else(|| { + let sources = Rc::new(SyntheticTextModuleSources::default()); + context.set_slot(sources.clone()); + sources + }); + let record = Rc::new(Self { + module: v8::Global::new(scope, module), + key, + source: source.to_owned(), + }); + let mut entries = sources.entries.borrow_mut(); + let candidates = entries + .entry(module_identity_hash_from_v8_module(module)) + .or_default(); + candidates.retain(|candidate| candidate.strong_count() != 0); + candidates.push(Rc::downgrade(&record)); + record + } + + pub(crate) fn for_module( + context: v8::Local<'_, v8::Context>, + module: v8::Local<'_, v8::Module>, + ) -> Option> { + let sources = context.get_slot::()?; + let entries = sources.entries.borrow(); + entries + .get(&module_identity_hash_from_v8_module(module))? + .iter() + .filter_map(Weak::upgrade) + // V8 identity hashes are not unique, even within one Context. + .find(|candidate| module == candidate.module) + } + + pub(crate) fn key(&self) -> &ModuleMapKey { + &self.key + } + + pub(crate) fn source(&self) -> &str { + &self.source + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::module_runtime::{ModuleAttributesKey, ModuleRecordEntry}; + use url::Url; + + fn unused_evaluation_steps<'s>( + context: v8::Local<'s, v8::Context>, + _module: v8::Local<'s, v8::Module>, + ) -> Option> { + v8::callback_scope!(unsafe scope, context); + Some(v8::undefined(scope).into()) + } + + #[test] + fn synthetic_text_module_lookup_checks_context_and_exact_identity() { + crate::ensure_v8_for_test(); + let mut isolate = v8::Isolate::new(Default::default()); + let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate)); + let scope = &mut scope.init(); + let context = v8::Context::new(scope, Default::default()); + let scope = &mut v8::ContextScope::new(scope, context); + let name = v8::String::new(scope, "https://example.test/shared.json").unwrap(); + let first = v8::Module::create_synthetic_module(scope, name, &[], unused_evaluation_steps); + let second = v8::Module::create_synthetic_module(scope, name, &[], unused_evaluation_steps); + let key = ModuleMapKey::json_with_attributes( + Url::parse("https://example.test/shared.json").unwrap(), + ModuleAttributesKey::empty(), + ); + let first_source = SyntheticTextModuleSource::register(scope, first, key.clone(), "1"); + let second_source = SyntheticTextModuleSource::register(scope, second, key, "2"); + + // Inject a conflicting candidate deterministically instead of waiting + // for a random V8 identity-hash collision. + let sources = context.get_slot::().unwrap(); + sources + .entries + .borrow_mut() + .get_mut(&module_identity_hash_from_v8_module(second)) + .unwrap() + .insert(0, Rc::downgrade(&first_source)); + assert!(Rc::ptr_eq( + &SyntheticTextModuleSource::for_module(context, second).unwrap(), + &second_source, + )); + let foreign = v8::Context::new(scope, Default::default()); + assert!(SyntheticTextModuleSource::for_module(foreign, first).is_none()); + } + + #[test] + fn synthetic_text_module_lookup_does_not_keep_disposed_records_alive() { + crate::ensure_v8_for_test(); + let mut isolate = v8::Isolate::new(Default::default()); + let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate)); + let scope = &mut scope.init(); + let context = v8::Context::new(scope, Default::default()); + let scope = &mut v8::ContextScope::new(scope, context); + let name = v8::String::new(scope, "https://example.test/value.json").unwrap(); + let module = v8::Module::create_synthetic_module(scope, name, &[], unused_evaluation_steps); + let key = ModuleMapKey::json_with_attributes( + Url::parse("https://example.test/value.json").unwrap(), + ModuleAttributesKey::empty(), + ); + let source = SyntheticTextModuleSource::register(scope, module, key.clone(), "42"); + let weak = Rc::downgrade(&source); + let record = ModuleRecordEntry::new(key, v8::Global::new(scope, module), Vec::new()) + .with_synthetic_text_module_source(source); + let clone = record.clone(); + drop(record); + assert_eq!( + SyntheticTextModuleSource::for_module(context, module) + .unwrap() + .source(), + "42" + ); + drop(clone); + assert!(weak.upgrade().is_none()); + assert!(SyntheticTextModuleSource::for_module(context, module).is_none()); + } +} diff --git a/moli-renderer-v8/src/native_bridge/context_host/resource_loading.rs b/moli-renderer-v8/src/native_bridge/context_host/resource_loading.rs index 638c8798e3..bc781f4620 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/resource_loading.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/resource_loading.rs @@ -2,10 +2,7 @@ use super::{JsContextHost, OwnerDispatchScope}; use crate::network::loads::{ResourceLoadDisposition, ResourceLoadKind, ResourceLoadLease}; use crate::types::DedicatedWorkerId; use crate::{ - module_runtime::{ - ModuleAttributesKey, ModuleMapKey, ModuleSource, PendingDynamicModuleImport, - WasmModuleRecord, - }, + module_runtime::{ModuleAttributesKey, PendingDynamicModuleImport, WasmModuleRecord}, page_task_queue::RendererResourceCompletionSender, renderer_resource_scheduler::RendererResourceScheduler, types::{ @@ -86,14 +83,6 @@ impl JsContextHost { unsafe { &mut *self.runtime }.resolve_module_specifier(specifier, base_url) } - pub(crate) fn native_module_source_for( - &self, - module: v8::Local<'_, v8::Module>, - ) -> Option<(ModuleMapKey, ModuleSource)> { - // SAFETY: JsContextHost is owned by the ScriptVm that owns this DocumentRuntime. - unsafe { &*self.runtime }.native_module_source_for(module) - } - pub(crate) fn native_module_wasm_record_for( &self, module: v8::Local<'_, v8::Module>, diff --git a/moli-renderer-v8/src/script_vm/native_module.rs b/moli-renderer-v8/src/script_vm/native_module.rs index d7d30eed34..dba03d0d8d 100644 --- a/moli-renderer-v8/src/script_vm/native_module.rs +++ b/moli-renderer-v8/src/script_vm/native_module.rs @@ -3433,22 +3433,6 @@ impl ScriptVm { }) } - fn compile_synthetic_module_record_in_context( - &mut self, - context_ptr: *const v8::Global, - key: ModuleMapKey, - _source: &str, - source_url: &Url, - ) -> std::result::Result<(ModuleRecordEntry, ModuleIdentityHash), ModuleLoadError> { - self.compile_synthetic_module_record_with_exports_in_context( - context_ptr, - key, - source_url, - &["default"], - None, - ) - } - fn compile_wasm_module_record_in_context( &mut self, context_ptr: *const v8::Global, @@ -3499,7 +3483,7 @@ impl ScriptVm { &scope, module_name, &export_names, - synthetic_module_evaluation_steps, + wasm_synthetic_module_evaluation_steps, ); let identity = module_identity_hash_from_v8_module(module); let compiled_module = v8::Global::new(scope.as_ref(), module); @@ -3519,13 +3503,12 @@ impl ScriptVm { }) } - fn compile_synthetic_module_record_with_exports_in_context( + fn compile_synthetic_module_record_in_context( &mut self, context_ptr: *const v8::Global, key: ModuleMapKey, + source: &str, source_url: &Url, - export_names: &[&str], - wasm_record: Option<(Vec, WasmModuleRecord)>, ) -> std::result::Result<(ModuleRecordEntry, ModuleIdentityHash), ModuleLoadError> { self.renderer_document_isolate .with_entered_renderer_document_isolate(|isolate| { @@ -3534,36 +3517,29 @@ impl ScriptVm { let context = unsafe { v8::Local::new(scope, &*context_ptr) }; let scope = &mut v8::ContextScope::new(scope, context); let try_catch = pin!(v8::TryCatch::new(scope)); - let scope = try_catch.init(); + let mut scope = try_catch.init(); let module_name = v8_string(&scope, source_url.as_str()).ok_or_else(|| { anyhow::anyhow!("failed to allocate v8 synthetic module name") })?; - let export_names = export_names - .iter() - .map(|name| { - v8_string(&scope, name).ok_or_else(|| { - anyhow::anyhow!("failed to allocate synthetic export name") - }) - }) - .collect::>>()?; + let default_export = v8_string(&scope, "default") + .ok_or_else(|| anyhow::anyhow!("failed to allocate synthetic export name"))?; let module = v8::Module::create_synthetic_module( &scope, module_name, - &export_names, - synthetic_module_evaluation_steps, + &[default_export], + synthetic_text_module_evaluation_steps, ); let identity = module_identity_hash_from_v8_module(module); + let evaluation_source = crate::module_runtime::SyntheticTextModuleSource::register( + &mut scope, + module, + key.clone(), + source, + ); let compiled_module = v8::Global::new(scope.as_ref(), module); - let entry = match wasm_record { - Some((requests, wasm_module)) => ModuleRecordEntry::new_with_wasm_module( - key, - compiled_module, - requests, - wasm_module, - ), - None => ModuleRecordEntry::new(key, compiled_module, Vec::new()), - }; + let entry = ModuleRecordEntry::new(key, compiled_module, Vec::new()) + .with_synthetic_text_module_source(evaluation_source); Ok((entry, identity)) }) .map_err(|error| ModuleLoadError::new(ModuleLoadStage::Compile, error.to_string())) @@ -4639,7 +4615,27 @@ fn get_i64_reaction_data_slot<'s>( lossless.then_some(value) } -fn synthetic_module_evaluation_steps<'s>( +fn synthetic_text_module_evaluation_steps<'s>( + context: v8::Local<'s, v8::Context>, + module: v8::Local<'s, v8::Module>, +) -> Option> { + v8::callback_scope!(unsafe scope, context); + let Some(record) = + crate::module_runtime::SyntheticTextModuleSource::for_module(context, module) + else { + return throw_synthetic_module_error(scope, "synthetic module source is not available"); + }; + let source = record.source(); + match record.key().kind() { + ModuleKind::Json => evaluate_json_synthetic_module(scope, module, source), + ModuleKind::Css => { + evaluate_css_synthetic_module(scope, module, record.key().url().as_str(), source) + } + _ => throw_synthetic_module_error(scope, "unexpected synthetic text module kind"), + } +} + +fn wasm_synthetic_module_evaluation_steps<'s>( context: v8::Local<'s, v8::Context>, module: v8::Local<'s, v8::Module>, ) -> Option> { @@ -4647,39 +4643,15 @@ fn synthetic_module_evaluation_steps<'s>( let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else { return throw_synthetic_module_error(scope, "synthetic module host is not available"); }; - let Some((key, source)) = (unsafe { &*host_ptr }).native_module_source_for(module) else { - return throw_synthetic_module_error(scope, "synthetic module source is not available"); - }; - match key.kind() { - ModuleKind::Json => { - let Some(source) = source.text_source() else { - return throw_synthetic_module_error(scope, "JSON module source is not text"); - }; - evaluate_json_synthetic_module(scope, module, source) - } - ModuleKind::Css => { - let Some(source) = source.text_source() else { - return throw_synthetic_module_error(scope, "CSS module source is not text"); - }; - evaluate_css_synthetic_module(scope, module, key.url().as_str(), source) - } - ModuleKind::WebAssembly => { - let Some(wasm_record) = (unsafe { &*host_ptr }).native_module_wasm_record_for(module) - else { - return throw_synthetic_module_error( - scope, - "WebAssembly synthetic module record is not available", - ); - }; - evaluate_wasm_synthetic_module(scope, module, &wasm_record, |scope, import| { - wasm_import_value(scope, module, import) - }) - } - ModuleKind::JavaScript | ModuleKind::ModulePreloadText => throw_synthetic_module_error( + let Some(wasm_record) = (unsafe { &*host_ptr }).native_module_wasm_record_for(module) else { + return throw_synthetic_module_error( scope, - "non-synthetic module reached synthetic module evaluation", - ), - } + "WebAssembly synthetic module record is not available", + ); + }; + evaluate_wasm_synthetic_module(scope, module, &wasm_record, |scope, import| { + wasm_import_value(scope, module, import) + }) } fn evaluate_json_synthetic_module<'s>(