1use std::borrow::Cow;
9use std::cell::OnceCell;
10use std::collections::hash_map::Entry;
11use std::ffi::CStr;
12use std::fmt::Debug;
13use std::ptr::NonNull;
14use std::rc::Rc;
15use std::sync::Arc;
16use std::{mem, ptr};
17
18use bytes::{Bytes, BytesMut};
19use encoding_rs::UTF_8;
20use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
21use hyper_serde::Serde;
22use js::context::JSContext;
23use js::conversions::{ToJSValConvertible, jsstr_to_string};
24use js::gc::{HandleObject, MutableHandleValue};
25use js::jsapi::{
26 CallArgs, ColumnNumberOneOrigin, ExceptionStackBehavior, GetFunctionNativeReserved,
27 GetModuleLoadHook, Handle as RawHandle, HandleValue as RawHandleValue, Heap,
28 JS_GetFunctionObject, JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE, JSRuntime,
29 JSScript, ModuleErrorBehaviour, ModuleType, SetFunctionNativeReserved, SetModuleLoadHook,
30 SetModuleMetadataHook, SetModulePrivate, SetScriptPrivateReferenceHooks, Value,
31};
32use js::jsval::{JSVal, ObjectValue, PrivateValue, UndefinedValue};
33use js::realm::{AutoRealm, CurrentRealm};
34use js::rust::wrappers2::{
35 CompileJsonModule1, CompileModule1, CreateDefaultExportSyntheticModule,
36 DefineFunctionWithReserved, GetModuleRequestSpecifier, JS_ClearPendingException,
37 JS_DefineProperty4, JS_GetModulePrivate, JS_GetPendingException, JS_NewStringCopyN,
38 JS_SetPendingException, ModuleEvaluate, ThrowOnModuleEvaluationFailure,
39};
40use js::rust::{Handle, HandleValue, ToString, transform_str_to_source_text};
41use mime::Mime;
42use net_traits::blob_url_store::UrlWithBlobClaim;
43use net_traits::http_status::HttpStatus;
44use net_traits::mime_classifier::MimeClassifier;
45use net_traits::policy_container::PolicyContainer;
46use net_traits::request::{
47 CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder, RequestClient,
48 RequestId, RequestMode,
49};
50use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
51use script_bindings::cell::DomRefCell;
52use script_bindings::error::Fallible;
53use script_bindings::reflector::DomObject;
54use script_bindings::trace::CustomTraceable;
55use servo_config::pref;
56use servo_url::ServoUrl;
57
58use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::{
59 CSSStyleSheetInit, CSSStyleSheetMethods,
60};
61use crate::dom::bindings::codegen::UnionTypes::MediaListOrString;
62use crate::dom::bindings::error::{Error, report_pending_exception, throw_dom_exception};
63use crate::dom::bindings::inheritance::Castable;
64use crate::dom::bindings::refcounted::Trusted;
65use crate::dom::bindings::root::DomRoot;
66use crate::dom::bindings::str::{DOMString, USVString};
67use crate::dom::bindings::trace::RootedTraceableBox;
68use crate::dom::csp::{GlobalCspReporting, Violation};
69use crate::dom::css::cssstylesheet::CSSStyleSheet;
70use crate::dom::globalscope::GlobalScope;
71use crate::dom::globalscope::script_execution::fill_compile_options;
72use crate::dom::html::htmlscriptelement::substitute_with_local_script;
73use crate::dom::performance::performanceresourcetiming::InitiatorType;
74use crate::dom::promisenativehandler::Callback;
75use crate::dom::script_execution::ScriptOptions;
76use crate::dom::types::{
77 DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, WorkerGlobalScope, WorkletGlobalScope,
78};
79use crate::dom::window::Window;
80use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
81use crate::modules::import_map::{ModuleSpecifierMap, resolve_url_like_module_specifier};
82use crate::modules::module_loading::{
83 LoadState, host_load_imported_module, load_requested_modules,
84};
85use crate::realms::enter_auto_realm;
86use crate::runtime::script_runtime::IntroductionType;
87use crate::tasks::task::NonSendTaskBox;
88use crate::unminify::{ScriptSource, unminify_js};
89
90#[derive(JSTraceable)]
91pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
92
93impl ModuleObject {
94 pub(crate) fn new(obj: HandleObject) -> ModuleObject {
95 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
96 }
97
98 pub(crate) fn handle(&'_ self) -> HandleObject<'_> {
99 self.0.handle()
100 }
101}
102
103#[derive(JSTraceable)]
104pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
105
106impl RethrowError {
107 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
108 Self(RootedTraceableBox::from_box(val))
109 }
110
111 #[expect(unsafe_code)]
112 pub(crate) fn from_pending_exception(cx: &mut JSContext) -> Self {
113 rooted!(&in(cx) let mut exception = UndefinedValue());
114 assert!(unsafe { JS_GetPendingException(cx, exception.handle_mut()) });
115 unsafe { JS_ClearPendingException(cx) };
116
117 Self::new(Heap::boxed(exception.get()))
118 }
119
120 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
121 self.0.handle()
122 }
123}
124
125impl Debug for RethrowError {
126 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
127 "RethrowError(...)".fmt(fmt)
128 }
129}
130
131impl Clone for RethrowError {
132 fn clone(&self) -> Self {
133 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
134 }
135}
136
137pub(crate) struct ModuleScript {
138 pub(crate) base_url: ServoUrl,
139 pub(crate) options: ScriptFetchOptions,
140 pub(crate) owner: Option<Trusted<GlobalScope>>,
141}
142
143impl ModuleScript {
144 pub(crate) fn new(
145 base_url: ServoUrl,
146 options: ScriptFetchOptions,
147 owner: Option<Trusted<GlobalScope>>,
148 ) -> Self {
149 ModuleScript {
150 base_url,
151 options,
152 owner,
153 }
154 }
155}
156
157pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
158
159#[derive(JSTraceable)]
160pub(crate) enum ModuleStatus {
161 #[expect(clippy::type_complexity)]
162 Fetching(#[no_trace] Vec<Box<dyn FnOnce(&mut JSContext, Option<Rc<ModuleTree>>)>>),
163 Loaded(Rc<ModuleTree>),
164}
165
166#[derive(Default, JSTraceable, MallocSizeOf)]
167pub(crate) struct ModuleTree {
168 #[ignore_malloc_size_of = "mozjs"]
169 record: OnceCell<ModuleObject>,
170 #[ignore_malloc_size_of = "mozjs"]
171 parse_error: OnceCell<RethrowError>,
172 #[ignore_malloc_size_of = "mozjs"]
173 rethrow_error: DomRefCell<Option<RethrowError>>,
174}
175
176impl ModuleTree {
177 pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
178 self.record.get()
179 }
180
181 pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
182 self.parse_error.get()
183 }
184
185 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
186 &self.rethrow_error
187 }
188
189 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
190 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
191 }
192}
193
194impl ModuleTree {
195 #[expect(unsafe_code)]
196 #[expect(clippy::too_many_arguments)]
197 fn create_a_javascript_module_script(
199 cx: &mut CurrentRealm,
200 source: Cow<'_, str>,
201 global: &GlobalScope,
202 url: &ServoUrl,
203 options: ScriptFetchOptions,
204 external: bool,
205 line_number: u32,
206 introduction_type: Option<&'static CStr>,
207 ) -> Self {
208 let owner = Trusted::new(global);
209
210 let module = ModuleTree::default();
213
214 let compile_options = fill_compile_options(
215 cx,
216 url.as_str(),
217 ScriptOptions::empty(),
218 introduction_type,
219 line_number,
220 );
221
222 let mut module_source = ScriptSource {
223 source,
224 external,
225 url,
226 };
227 if let Some(unminified_js_dir) = global.unminified_js_dir() {
228 unminify_js(&mut module_source, unminified_js_dir);
229 }
230 let mut source = transform_str_to_source_text(&module_source.source);
231
232 unsafe {
233 rooted!(&in(cx) let module_script = CompileModule1(cx, compile_options.ptr, &mut source));
235
236 if module_script.is_null() {
238 warn!("fail to compile module script of {}", url);
239
240 let _ = module
242 .parse_error
243 .set(RethrowError::from_pending_exception(cx));
244
245 return module;
247 }
248
249 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
253
254 SetModulePrivate(
255 module_script.get(),
256 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
257 );
258
259 let _ = module.record.set(ModuleObject::new(module_script.handle()));
261 }
262
263 module
265 }
266
267 #[expect(unsafe_code)]
268 fn create_a_json_module_script(
270 cx: &mut CurrentRealm,
271 source: &str,
272 url: &ServoUrl,
273 introduction_type: Option<&'static CStr>,
274 ) -> Self {
275 let module = ModuleTree::default();
278
279 let compile_options = fill_compile_options(
284 cx,
285 url.as_str(),
286 ScriptOptions::empty(),
287 introduction_type,
288 1, );
290
291 let mut source = transform_str_to_source_text(source);
292
293 rooted!(&in(cx) let module_script = unsafe { CompileJsonModule1(cx, compile_options.ptr, &mut source) });
295
296 if module_script.is_null() {
298 warn!("fail to compile module script of {}", url);
299
300 let _ = module
301 .parse_error
302 .set(RethrowError::from_pending_exception(cx));
303 return module;
304 }
305
306 let _ = module.record.set(ModuleObject::new(module_script.handle()));
308
309 module
311 }
312
313 #[expect(unsafe_code)]
314 fn create_a_css_module_script(
316 cx: &mut CurrentRealm,
317 source: &str,
318 global: &GlobalScope,
319 url: ServoUrl,
320 ) -> Self {
321 let script = ModuleTree::default();
324
325 let dictionary = CSSStyleSheetInit {
332 baseURL: Some(url.into_string().into()),
333 disabled: false,
334 media: MediaListOrString::String(DOMString::new()),
335 };
336 let sheet = CSSStyleSheet::Constructor(cx, global.as_window(), None, &dictionary);
337
338 if let Err(error) = sheet.ReplaceSync(cx, USVString::from(source.to_owned())) {
341 throw_dom_exception(cx, global, error);
344
345 let _ = script
346 .parse_error
347 .set(RethrowError::from_pending_exception(cx));
348 return script;
349 }
350
351 rooted!(&in(cx) let sheet = ObjectValue(sheet.reflector().get_jsobject().get()));
353 rooted!(&in(cx) let module_script = unsafe { CreateDefaultExportSyntheticModule(cx, sheet.handle()) });
354 let _ = script.record.set(ModuleObject::new(module_script.handle()));
355
356 script
358 }
359
360 #[expect(unsafe_code)]
361 fn create_a_text_module_script(cx: &mut CurrentRealm, source: &str) -> Self {
363 let script = ModuleTree::default();
366
367 rooted!(&in(cx) let mut text = UndefinedValue());
373 source.to_jsval(cx, text.handle_mut());
374 rooted!(&in(cx) let result = unsafe { CreateDefaultExportSyntheticModule(cx, text.handle()) });
375
376 let _ = script.record.set(ModuleObject::new(result.handle()));
378
379 script
381 }
382
383 #[expect(unsafe_code)]
386 pub(crate) fn execute_module(
387 &self,
388 cx: &mut JSContext,
389 global: &GlobalScope,
390 module_record: HandleObject,
391 mut eval_result: MutableHandleValue,
392 ) -> Result<(), RethrowError> {
393 let mut realm = AutoRealm::new(
394 cx,
395 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
396 );
397 let cx = &mut *realm;
398
399 unsafe {
400 let ok = ModuleEvaluate(cx, module_record, eval_result.reborrow());
401 assert!(ok, "module evaluation failed");
402
403 rooted!(&in(cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
404 if eval_result.is_object() {
405 evaluation_promise.set(eval_result.to_object());
406 }
407
408 let throw_result = ThrowOnModuleEvaluationFailure(
409 cx,
410 evaluation_promise.handle(),
411 ModuleErrorBehaviour::ThrowModuleErrorsSync,
412 );
413 if !throw_result {
414 warn!("fail to evaluate module");
415
416 Err(RethrowError::from_pending_exception(cx))
417 } else {
418 debug!("module evaluated successfully");
419 Ok(())
420 }
421 }
422 }
423
424 #[expect(unsafe_code)]
425 pub(crate) fn report_error(&self, cx: &mut JSContext, global: &GlobalScope) {
426 let module_error = self.rethrow_error.borrow();
427
428 if let Some(exception) = &*module_error {
429 let mut realm = enter_auto_realm(cx, global);
430 let cx = &mut realm.current_realm();
431
432 unsafe {
433 JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
434 }
435 report_pending_exception(cx);
436 }
437 }
438
439 pub(crate) fn resolve_module_specifier(
441 global: &GlobalScope,
442 script: Option<&ModuleScript>,
443 specifier: String,
444 ) -> Fallible<ServoUrl> {
445 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.root()));
447 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
449 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
453 None => (global, &global.api_base_url()),
458 };
459
460 let import_map = if global.is::<Window>() {
464 Some(global.import_map())
465 } else {
466 None
467 };
468
469 let serialized_base_url = base_url.as_str();
471 let as_url = resolve_url_like_module_specifier(&specifier, base_url);
473 let normalized_specifier = match &as_url {
476 Some(url) => url.as_str(),
477 None => &specifier,
478 };
479
480 let mut result = None;
482 if let Some(map) = import_map {
483 for (prefix, imports) in &map.scopes {
485 let prefix = prefix.as_str();
488 if prefix == serialized_base_url ||
489 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
490 {
491 let scope_imports_match =
494 resolve_imports_match(normalized_specifier, as_url.as_ref(), imports)?;
495
496 if scope_imports_match.is_some() {
498 result = scope_imports_match;
499 break;
500 }
501 }
502 }
503
504 if result.is_none() {
507 result =
508 resolve_imports_match(normalized_specifier, as_url.as_ref(), &map.imports)?;
509 }
510 }
511
512 if result.is_none() {
514 result = as_url.clone();
515 }
516
517 match result {
519 Some(result) => {
520 global.add_module_to_resolved_module_set(
523 serialized_base_url,
524 normalized_specifier,
525 as_url.clone(),
526 );
527 Ok(result)
529 },
530 None => Err(Error::Type(
533 c"Specifier was a bare specifier, but was not remapped to anything by importMap."
534 .to_owned(),
535 )),
536 }
537 }
538}
539
540#[derive(JSTraceable, MallocSizeOf)]
541pub(crate) struct ModuleHandler {
542 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
543 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
544}
545
546impl ModuleHandler {
547 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
548 Box::new(Self {
549 task: DomRefCell::new(Some(task)),
550 })
551 }
552}
553
554impl Callback for ModuleHandler {
555 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
556 let task = self.task.borrow_mut().take().unwrap();
557 task.run_box(cx);
558 }
559}
560
561struct ModuleContext {
563 owner: Trusted<GlobalScope>,
565 data: BytesMut,
567 metadata: Option<Metadata>,
569 module_request: ModuleRequest,
571 options: ScriptFetchOptions,
573 status: Result<(), NetworkError>,
575 introduction_type: Option<&'static CStr>,
577 policy_container: Option<Arc<PolicyContainer>>,
579}
580
581impl FetchResponseListener for ModuleContext {
582 fn process_request_body(&mut self, _: RequestId) {}
584
585 fn process_response(
586 &mut self,
587 _: &mut js::context::JSContext,
588 _: RequestId,
589 metadata: Result<FetchMetadata, NetworkError>,
590 ) {
591 self.metadata = metadata.ok().map(Into::into);
592
593 let status = self
594 .metadata
595 .as_ref()
596 .map(|m| m.status.clone())
597 .unwrap_or_else(HttpStatus::new_error);
598
599 self.status = {
600 if status.is_error() {
601 Err(NetworkError::ResourceLoadError(
602 "No http status code received".to_owned(),
603 ))
604 } else if status.is_success() {
605 Ok(())
606 } else {
607 Err(NetworkError::ResourceLoadError(format!(
608 "HTTP error code {}",
609 status.code()
610 )))
611 }
612 };
613 }
614
615 fn process_response_chunk(
616 &mut self,
617 _: &mut js::context::JSContext,
618 _: RequestId,
619 chunk: Bytes,
620 ) {
621 if self.status.is_ok() {
622 self.data.extend_from_slice(&chunk);
623 }
624 }
625
626 fn process_response_eof(
629 mut self,
630 cx: &mut js::context::JSContext,
631 _: RequestId,
632 response: Result<(), NetworkError>,
633 timing: ResourceFetchTiming,
634 ) {
635 let global = self.owner.root();
636 let (_url, module_type) = &self.module_request;
637
638 if !global.is::<WorkletGlobalScope>() {
639 network_listener::submit_timing(cx, &self, &response, &timing);
640 }
641
642 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
645 error!("Fetching module script failed {:?}", error);
646 let Some(ModuleStatus::Fetching(callbacks)) = global.with_module_map(|module_map| {
649 module_map.safe_borrow_mut(cx).remove(&self.module_request)
650 }) else {
651 return error!("Processing response for a non pending module request");
652 };
653
654 for callback in callbacks {
656 (callback)(cx, None);
657 }
658
659 return;
661 }
662
663 let metadata = self.metadata.take().unwrap();
664
665 if let Some(policy_container) = self.policy_container {
668 let workerscope = global.downcast::<WorkerGlobalScope>().expect(
669 "We only need a policy container when initializing a worker's globalscope.",
670 );
671 workerscope.process_response_for_workerscope(&metadata, &policy_container);
672 }
673
674 let final_url = metadata.final_url;
675
676 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
678
679 let mut module_script = None;
681
682 let referrer_policy = metadata
684 .headers
685 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
686 .into();
687
688 if referrer_policy != ReferrerPolicy::EmptyString {
690 self.options.referrer_policy = referrer_policy;
691 }
692
693 let mut realm = enter_auto_realm(cx, &*global);
694 let cx = &mut realm.current_realm();
695
696 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
701
702 match (module_type, mime_type) {
703 (ModuleType::Text, _) => {
706 let module_tree =
707 Rc::new(ModuleTree::create_a_text_module_script(cx, &source_text));
708 module_script = Some(module_tree);
709 },
710 (ModuleType::JavaScript, Some(mime)) if MimeClassifier::is_javascript(&mime) => {
714 if let Some(window) = global.downcast::<Window>() &&
715 let Some(script_souce) = window.local_script_source()
716 {
717 substitute_with_local_script(script_souce, &mut source_text, &final_url);
718 }
719
720 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
721 cx,
722 source_text,
723 &global,
724 &final_url,
725 self.options,
726 true,
727 1,
728 self.introduction_type,
729 ));
730 module_script = Some(module_tree);
731 },
732 (ModuleType::CSS, Some(mime)) if MimeClassifier::is_css(&mime) => {
736 let module_tree = Rc::new(ModuleTree::create_a_css_module_script(
737 cx,
738 &source_text,
739 &global,
740 final_url.clone(),
741 ));
742 module_script = Some(module_tree);
743 },
744 (ModuleType::JSON, Some(mime)) if MimeClassifier::is_json(&mime) => {
747 let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
748 cx,
749 &source_text,
750 &final_url,
751 self.introduction_type,
752 ));
753 module_script = Some(module_tree);
754 },
755 _ => {},
756 }
757
758 let callbacks = global.with_module_map(|module_map| {
759 match module_map
760 .safe_borrow_mut(cx)
761 .entry(self.module_request.clone())
762 {
763 Entry::Occupied(mut entry) => {
764 let old_value = match module_script.as_ref() {
767 None => entry.remove(),
768 Some(module_script) => {
769 entry.insert(ModuleStatus::Loaded(module_script.clone()))
770 },
771 };
772
773 match old_value {
774 ModuleStatus::Loaded(_) => None,
775 ModuleStatus::Fetching(callbacks) => Some(callbacks),
776 }
777 },
778 Entry::Vacant(_) => None,
779 }
780 });
781
782 if let Some(callbacks) = callbacks {
783 for callback in callbacks {
785 (callback)(cx, module_script.clone());
786 }
787 } else {
788 error!("Processing response for a non pending module request");
789 }
790 }
791
792 fn process_csp_violations(
793 &mut self,
794 cx: &mut js::context::JSContext,
795 _request_id: RequestId,
796 violations: Vec<Violation>,
797 ) {
798 let global = self.owner.root();
799 if let Some(scope) = global.downcast::<DedicatedWorkerGlobalScope>() {
800 scope.report_csp_violations(violations);
801 } else if let Some(scope) = global.downcast::<SharedWorkerGlobalScope>() {
802 scope.report_csp_violations(violations);
803 } else {
804 global.report_csp_violations(cx, violations, None, None);
805 }
806 }
807
808 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
809 self.data.reserve(size.saturating_sub(self.data.len()));
810 }
811}
812
813impl ResourceTimingListener for ModuleContext {
814 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
815 let initiator_type = InitiatorType::LocalName("module".to_string());
816 let (url, _) = &self.module_request;
817 (initiator_type, url.clone())
818 }
819
820 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
821 self.owner.root()
822 }
823}
824
825#[expect(unsafe_code)]
826#[expect(non_snake_case)]
827pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
830 unsafe {
831 if GetModuleLoadHook(rt).is_some() {
832 return;
833 }
834
835 SetModuleLoadHook(rt, Some(HostLoadImportedModule));
836 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
837 SetScriptPrivateReferenceHooks(
838 rt,
839 Some(host_add_ref_top_level_script),
840 Some(host_release_top_level_script),
841 );
842 }
843}
844
845#[expect(unsafe_code)]
846unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
847 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
848 mem::forget(val.clone());
849 mem::forget(val);
850}
851
852#[expect(unsafe_code)]
853unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
854 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
855}
856
857#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
858pub(crate) struct ScriptFetchOptions {
860 pub(crate) integrity_metadata: String,
861 #[no_trace]
862 pub(crate) credentials_mode: CredentialsMode,
863 pub(crate) cryptographic_nonce: String,
864 #[no_trace]
865 pub(crate) parser_metadata: ParserMetadata,
866 #[no_trace]
867 pub(crate) referrer_policy: ReferrerPolicy,
868 pub(crate) render_blocking: bool,
872}
873
874impl ScriptFetchOptions {
875 pub(crate) fn default_classic_script() -> ScriptFetchOptions {
877 Self {
878 cryptographic_nonce: String::new(),
879 integrity_metadata: String::new(),
880 parser_metadata: ParserMetadata::NotParserInserted,
881 credentials_mode: CredentialsMode::CredentialsSameOrigin,
882 referrer_policy: ReferrerPolicy::EmptyString,
883 render_blocking: false,
884 }
885 }
886
887 pub(crate) fn descendant_fetch_options(
889 &self,
890 url: &ServoUrl,
891 global: &GlobalScope,
892 ) -> ScriptFetchOptions {
893 let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
895
896 Self {
899 integrity_metadata: integrity,
901 cryptographic_nonce: self.cryptographic_nonce.clone(),
902 credentials_mode: self.credentials_mode,
903 parser_metadata: self.parser_metadata,
904 referrer_policy: self.referrer_policy,
905 render_blocking: self.render_blocking,
906 }
907 }
908}
909
910#[expect(unsafe_code)]
911pub(crate) unsafe fn module_script_from_reference_private<'a>(
912 reference_private: Handle<'a, JSVal>,
913) -> Option<&'a ModuleScript> {
914 if reference_private.get().is_undefined() {
915 return None;
916 }
917 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
918}
919
920#[expect(unsafe_code)]
921#[expect(non_snake_case)]
922unsafe extern "C" fn HostLoadImportedModule(
925 cx: *mut RawJSContext,
926 referrer: RawHandle<*mut JSScript>,
927 module_request: RawHandle<*mut JSObject>,
928 host_defined: RawHandleValue,
929 payload: RawHandleValue,
930 _line_number: u32,
931 _column_number: ColumnNumberOneOrigin,
932) -> bool {
933 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
935 let mut realm = CurrentRealm::assert(&mut cx);
936 let cx = &mut realm;
937
938 let referrer = unsafe { Handle::from_raw(referrer) };
939 let module_request = unsafe { Handle::from_raw(module_request) };
940 let host_defined = unsafe { Handle::from_raw(host_defined) };
941 let payload = unsafe { Handle::from_raw(payload) };
942
943 let jsstr = unsafe { GetModuleRequestSpecifier(cx, module_request) };
944 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
945
946 host_load_imported_module(
947 cx,
948 referrer,
949 module_request,
950 specifier,
951 host_defined,
952 payload,
953 );
954 true
955}
956
957const MODULE_RECORD_SLOT: usize = 0;
959
960#[expect(unsafe_code)]
961#[expect(non_snake_case)]
962unsafe extern "C" fn HostPopulateImportMeta(
965 cx: *mut RawJSContext,
966 module_record: RawHandle<*mut JSObject>,
967 meta_object: RawHandle<*mut JSObject>,
968) -> bool {
969 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
971 let mut realm = CurrentRealm::assert(&mut cx);
972 let global_scope = GlobalScope::from_current_realm(&mut realm);
973
974 rooted!(&in(cx) let mut module_private: JSVal);
976 unsafe { JS_GetModulePrivate(module_record.get(), module_private.handle_mut()) };
977 let base_url = match unsafe { module_script_from_reference_private(module_private.handle()) } {
978 Some(module_data) => module_data.base_url.clone(),
979 None => global_scope.api_base_url(),
980 };
981
982 unsafe {
983 let url_string = JS_NewStringCopyN(
984 &mut cx,
985 base_url.as_str().as_ptr() as *const _,
986 base_url.as_str().len(),
987 );
988 rooted!(&in(cx) let url_string = url_string);
989
990 if !JS_DefineProperty4(
992 &mut cx,
993 Handle::from_raw(meta_object),
994 c"url".as_ptr(),
995 url_string.handle(),
996 JSPROP_ENUMERATE.into(),
997 ) {
998 return false;
999 }
1000
1001 let resolve_function = DefineFunctionWithReserved(
1003 &mut cx,
1004 meta_object.get(),
1005 c"resolve".as_ptr(),
1006 Some(import_meta_resolve),
1007 1,
1008 JSPROP_ENUMERATE.into(),
1009 );
1010
1011 if resolve_function.is_null() {
1012 return false;
1013 }
1014
1015 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
1016 assert!(!obj.is_null());
1017 SetFunctionNativeReserved(
1018 obj.get(),
1019 MODULE_RECORD_SLOT,
1020 &ObjectValue(module_record.get()),
1021 );
1022 }
1023
1024 true
1025}
1026
1027#[expect(unsafe_code)]
1028unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
1029 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1031 let mut realm = CurrentRealm::assert(&mut cx);
1032 let global_scope = GlobalScope::from_current_realm(&mut realm);
1033
1034 let cx = &mut realm;
1035
1036 let args = unsafe { CallArgs::from_vp(vp, argc) };
1037
1038 rooted!(&in(cx) let module_value = unsafe { *GetFunctionNativeReserved(args.callee(), MODULE_RECORD_SLOT) });
1039 assert!(!module_value.is_undefined());
1040 rooted!(&in(cx) let module_record = module_value.to_object());
1041 rooted!(&in(cx) let mut module_private: JSVal);
1042 unsafe { JS_GetModulePrivate(module_record.get(), module_private.handle_mut()) };
1043 assert!(!module_private.is_undefined());
1044 let module_data = unsafe { module_script_from_reference_private(module_private.handle()) };
1045
1046 let specifier = unsafe {
1050 let value = HandleValue::from_raw(args.get(0));
1051
1052 match NonNull::new(ToString(cx, value)) {
1053 Some(jsstr) => jsstr_to_string(cx, jsstr),
1054 None => return false,
1055 }
1056 };
1057
1058 let url = ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier);
1060
1061 match url {
1062 Ok(url) => {
1063 url.as_str()
1065 .to_jsval(cx, unsafe { MutableHandleValue::from_raw(args.rval()) });
1066 true
1067 },
1068 Err(error) => {
1069 throw_dom_exception(cx, &global_scope, error);
1070 false
1071 },
1072 }
1073}
1074
1075#[expect(clippy::too_many_arguments)]
1076pub(crate) fn fetch_a_module_script_graph(
1079 cx: &mut JSContext,
1080 global: &GlobalScope,
1081 url: UrlWithBlobClaim,
1082 fetch_client: RequestClient,
1083 destination: Destination,
1084 referrer: Referrer,
1085 credentials_mode: CredentialsMode,
1086 introduction_type: Option<&'static CStr>,
1087 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1088) {
1089 let global_scope = DomRoot::from_ref(global);
1090
1091 let options = ScriptFetchOptions {
1096 integrity_metadata: String::new(),
1097 credentials_mode,
1098 cryptographic_nonce: String::new(),
1099 parser_metadata: ParserMetadata::NotParserInserted,
1100 referrer_policy: ReferrerPolicy::EmptyString,
1101 render_blocking: false,
1102 };
1103
1104 fetch_a_single_module_script(
1107 cx,
1108 url,
1109 fetch_client.clone(),
1110 global,
1111 destination,
1112 options,
1113 referrer,
1114 None,
1115 true,
1116 introduction_type,
1117 move |cx, module_tree| {
1118 let Some(module) = module_tree else {
1119 return on_complete(cx, None);
1121 };
1122
1123 fetch_the_descendants_and_link_module_script(
1126 cx,
1127 &global_scope,
1128 module,
1129 fetch_client,
1130 destination,
1131 on_complete,
1132 );
1133 },
1134 );
1135}
1136
1137pub(crate) fn fetch_an_external_module_script(
1139 cx: &mut JSContext,
1140 url: UrlWithBlobClaim,
1141 global: &GlobalScope,
1142 options: ScriptFetchOptions,
1143 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1144) {
1145 let referrer = global.get_referrer();
1146 let fetch_client = global.request_client(Some(cx.no_gc()));
1147 let global_scope = DomRoot::from_ref(global);
1148
1149 fetch_a_single_module_script(
1152 cx,
1153 url,
1154 fetch_client.clone(),
1155 global,
1156 Destination::Script,
1157 options,
1158 referrer,
1159 None,
1160 true,
1161 Some(IntroductionType::SRC_SCRIPT),
1162 move |cx, module_tree| {
1163 let Some(module) = module_tree else {
1164 return on_complete(cx, None);
1166 };
1167
1168 fetch_the_descendants_and_link_module_script(
1170 cx,
1171 &global_scope,
1172 module,
1173 fetch_client,
1174 Destination::Script,
1175 on_complete,
1176 );
1177 },
1178 );
1179}
1180
1181pub(crate) fn fetch_a_modulepreload_module(
1183 cx: &mut JSContext,
1184 url: UrlWithBlobClaim,
1185 destination: Destination,
1186 global: &GlobalScope,
1187 options: ScriptFetchOptions,
1188 on_complete: impl FnOnce(&mut JSContext, bool) + 'static,
1189) {
1190 let referrer = global.get_referrer();
1191 let fetch_client = global.request_client(Some(cx.no_gc()));
1192 let global_scope = DomRoot::from_ref(global);
1193
1194 let module_type = match destination {
1197 Destination::Json => Some(ModuleType::JSON),
1198 Destination::Style => Some(ModuleType::CSS),
1199 Destination::Text => Some(ModuleType::Text),
1200 _ => None,
1201 };
1202
1203 fetch_a_single_module_script(
1206 cx,
1207 url,
1208 fetch_client.clone(),
1209 global,
1210 destination,
1211 options,
1212 referrer,
1213 module_type,
1214 true,
1215 Some(IntroductionType::SRC_SCRIPT),
1216 move |cx, result| {
1217 on_complete(cx, result.is_none());
1219
1220 assert!(global_scope.is::<Window>());
1222
1223 if pref!(dom_allow_preloading_module_descendants) &&
1226 let Some(module) = result
1227 {
1228 fetch_the_descendants_and_link_module_script(
1229 cx,
1230 &global_scope,
1231 module,
1232 fetch_client,
1233 destination,
1234 |_, _| {},
1235 );
1236 }
1237 },
1238 );
1239}
1240
1241#[expect(clippy::too_many_arguments)]
1242pub(crate) fn fetch_inline_module_script(
1244 cx: &mut JSContext,
1245 global: &GlobalScope,
1246 module_script_text: Cow<'_, str>,
1247 url: ServoUrl,
1248 options: ScriptFetchOptions,
1249 line_number: u32,
1250 introduction_type: Option<&'static CStr>,
1251 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1252) {
1253 let mut realm = enter_auto_realm(cx, global);
1254 let cx = &mut realm.current_realm();
1255
1256 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1258 cx,
1259 module_script_text,
1260 global,
1261 &url,
1262 options,
1263 false,
1264 line_number,
1265 introduction_type,
1266 ));
1267 let fetch_client = global.request_client(Some(cx.no_gc()));
1268
1269 fetch_the_descendants_and_link_module_script(
1271 cx,
1272 global,
1273 module_tree,
1274 fetch_client,
1275 Destination::Script,
1276 on_complete,
1277 );
1278}
1279
1280fn fetch_the_descendants_and_link_module_script(
1282 cx: &mut JSContext,
1283 global: &GlobalScope,
1284 module_script: Rc<ModuleTree>,
1285 fetch_client: RequestClient,
1286 destination: Destination,
1287 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1288) {
1289 let Some(record) = module_script.get_record() else {
1292 let parse_error = module_script.get_parse_error().cloned();
1293
1294 module_script.set_rethrow_error(parse_error.unwrap());
1296
1297 on_complete(cx, Some(module_script));
1299
1300 return;
1302 };
1303
1304 let state = Box::new(LoadState {
1307 destination,
1308 fetch_client,
1309 module_script: DomRefCell::new(Some(module_script.clone())),
1310 on_complete: DomRefCell::new(Some(Box::new(on_complete))),
1311 });
1312
1313 let mut realm = enter_auto_realm(cx, global);
1316 let cx = &mut realm.current_realm();
1317
1318 load_requested_modules(cx, record.handle(), state);
1320}
1321
1322#[expect(clippy::too_many_arguments)]
1324pub(crate) fn fetch_a_single_module_script(
1325 cx: &mut JSContext,
1326 url: UrlWithBlobClaim,
1327 fetch_client: RequestClient,
1328 global: &GlobalScope,
1329 destination: Destination,
1330 options: ScriptFetchOptions,
1331 referrer: Referrer,
1332 module_type: Option<ModuleType>,
1333 is_top_level: bool,
1334 introduction_type: Option<&'static CStr>,
1335 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
1336) {
1337 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1341
1342 let module_request = (url.url(), module_type);
1347
1348 let occupied = global.with_module_map(|module_map| {
1350 let mut module_map_borrow = module_map.safe_borrow_mut(cx);
1351
1352 let entry = module_map_borrow.entry(module_request.clone());
1353
1354 match entry {
1355 Entry::Occupied(mut entry) => {
1356 match entry.get_mut() {
1357 ModuleStatus::Loaded(module_tree) => {
1360 let module = module_tree.clone();
1361 drop(module_map_borrow);
1362 on_complete(cx, Some(module));
1363 },
1364 ModuleStatus::Fetching(callbacks) => {
1367 callbacks.push(Box::new(on_complete));
1368 },
1369 }
1370 true
1371 },
1372 Entry::Vacant(entry) => {
1374 entry.insert(ModuleStatus::Fetching(vec![Box::new(on_complete)]));
1375 false
1376 },
1377 }
1378 });
1379
1380 if occupied {
1381 return;
1382 }
1383
1384 let policy_container = (is_top_level && global.is::<WorkerGlobalScope>())
1386 .then(|| fetch_client.policy_container.clone());
1387
1388 let mode = match destination {
1393 Destination::Worker | Destination::SharedWorker if is_top_level => RequestMode::SameOrigin,
1394 _ => RequestMode::CorsMode,
1395 };
1396
1397 let destination = match module_type {
1400 ModuleType::JSON => Destination::Json,
1401 ModuleType::CSS => Destination::Style,
1402 ModuleType::Text => Destination::Text,
1403 ModuleType::Bytes => unreachable!("Not in ESR153"),
1404 ModuleType::JavaScript | ModuleType::Unknown => destination,
1405 };
1406
1407 let request = RequestBuilder::new(global.webview_id(), url, referrer)
1411 .destination(destination)
1412 .parser_metadata(options.parser_metadata)
1413 .integrity_metadata(options.integrity_metadata.clone())
1414 .credentials_mode(options.credentials_mode)
1415 .referrer_policy(options.referrer_policy)
1416 .mode(mode)
1417 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
1418 .client(fetch_client)
1419 .pipeline_id(Some(global.pipeline_id()));
1420
1421 let context = ModuleContext {
1422 owner: Trusted::new(global),
1423 data: BytesMut::new(),
1424 metadata: None,
1425 module_request,
1426 options,
1427 status: Ok(()),
1428 introduction_type,
1429 policy_container,
1430 };
1431
1432 let task_source = global.task_manager().networking_task_source().to_sendable();
1433 global.fetch(request, context, task_source);
1434}
1435
1436#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1438pub(crate) struct ResolvedModule {
1439 pub(crate) base_url: String,
1441 pub(crate) specifier: String,
1443 #[no_trace]
1445 pub(crate) specifier_url: Option<ServoUrl>,
1446}
1447
1448impl ResolvedModule {
1449 pub(crate) fn new(
1450 base_url: String,
1451 specifier: String,
1452 specifier_url: Option<ServoUrl>,
1453 ) -> Self {
1454 Self {
1455 base_url,
1456 specifier,
1457 specifier_url,
1458 }
1459 }
1460}
1461
1462fn resolve_imports_match(
1467 normalized_specifier: &str,
1468 as_url: Option<&ServoUrl>,
1469 specifier_map: &ModuleSpecifierMap,
1470) -> Fallible<Option<ServoUrl>> {
1471 for (specifier_key, resolution_result) in specifier_map {
1473 if specifier_key == normalized_specifier {
1475 if let Some(resolution_result) = resolution_result {
1476 return Ok(Some(resolution_result.clone()));
1480 } else {
1481 return Err(Error::Type(
1483 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
1484 ));
1485 }
1486 }
1487
1488 if specifier_key.ends_with('\u{002f}') &&
1493 normalized_specifier.starts_with(specifier_key) &&
1494 (as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
1495 {
1496 let Some(resolution_result) = resolution_result else {
1499 return Err(Error::Type(
1500 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
1501 ));
1502 };
1503
1504 let after_prefix = normalized_specifier
1506 .strip_prefix(specifier_key)
1507 .expect("specifier_key should be the prefix of normalized_specifier");
1508
1509 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
1511
1512 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
1514
1515 let Ok(url) = url else {
1518 return Err(Error::Type(
1519 c"Resolution of normalizedSpecifier was blocked since
1520 the afterPrefix portion could not be URL-parsed relative to
1521 the resolutionResult mapped to by the specifierKey prefix."
1522 .to_owned(),
1523 ));
1524 };
1525
1526 if !url.as_str().starts_with(resolution_result.as_str()) {
1529 return Err(Error::Type(
1530 c"Resolution of normalizedSpecifier was blocked due to
1531 it backtracking above its prefix specifierKey."
1532 .to_owned(),
1533 ));
1534 }
1535
1536 return Ok(Some(url));
1538 }
1539 }
1540
1541 Ok(None)
1543}