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