1#![expect(unsafe_code)]
6
7use std::ffi::c_void;
8use std::ptr;
9use std::rc::Rc;
10
11use js::context::{JSContext, RawJSContext};
12use js::error::throw_type_error;
13use js::jsapi::{
14 CallArgs, GetFunctionNativeReserved, Heap, JS_GetFunctionObject, JS_GetGlobalFromScript,
15 JSObject, JSScript, ModuleType, SetFunctionNativeReserved,
16};
17use js::jsval::{JSVal, ObjectValue, PrivateValue, UndefinedValue};
18use js::realm::CurrentRealm;
19use js::rust::Handle;
20use js::rust::wrappers2::{
21 AddPromiseReactions, FinishLoadingDynamicImportedModule, FinishLoadingImportedModule,
22 FinishLoadingImportedModuleFailed, FinishLoadingImportedModuleFailedWithPendingException,
23 GetModuleNamespace, GetModuleRequestType, IsPromiseObject, JS_GetScriptPrivate,
24 LoadRequestedModules1, ModuleEvaluate, ModuleLink, NewFunctionWithReserved,
25};
26use net_traits::blob_url_store::UrlWithBlobClaim;
27use net_traits::request::{Destination, Referrer, RequestClient};
28use script_bindings::cell::DomRefCell;
29use script_bindings::inheritance::Castable;
30use script_bindings::settings_stack::run_a_callback;
31
32use crate::DomTypeHolder;
33use crate::dom::bindings::error::throw_dom_exception;
34use crate::dom::bindings::root::DomRoot;
35use crate::dom::bindings::trace::RootedTraceableBox;
36use crate::dom::globalscope::GlobalScope;
37use crate::dom::promise::promisenativehandler::{Callback, PromiseNativeHandler};
38use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
39use crate::dom::window::Window;
40use crate::realms::enter_auto_realm;
41use crate::runtime::script_runtime::IntroductionType;
42use crate::scripting::fetching_options::ScriptFetchOptions;
43use crate::scripting::import_map::{ImportMap, ReferringScript};
44use crate::scripting::script_module::{
45 ModuleHandler, ModuleObject, ModuleTree, RethrowError, fetch_a_single_module_script,
46 module_script_from_reference_private,
47};
48use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
49
50#[derive(JSTraceable, MallocSizeOf)]
51#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
52struct OnRejectedHandler {
53 promise: TracedPromise,
54}
55
56impl js::gc::Rootable for OnRejectedHandler {}
57
58impl Callback for OnRejectedHandler {
59 fn callback(&self, cx: &mut CurrentRealm, v: Handle<JSVal>) {
60 self.promise.reject(cx, v);
62 }
63}
64
65#[derive(JSTraceable, MallocSizeOf)]
66pub(crate) struct LoadState {
67 #[no_trace]
68 pub(crate) destination: Destination,
69 #[no_trace]
70 pub(crate) fetch_client: RequestClient,
71 #[conditional_malloc_size_of]
72 pub(crate) module_script: DomRefCell<Option<Rc<ModuleTree>>>,
73 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
74 #[no_trace]
75 #[allow(clippy::type_complexity)]
76 pub(crate) on_complete:
77 DomRefCell<Option<Box<dyn FnOnce(&mut JSContext, Option<Rc<ModuleTree>>)>>>,
78}
79
80const LOAD_REACTION_HOST_DEFINED_SLOT: usize = 0;
81
82fn take_state_from_reserved_slot(cx: &mut JSContext, args: &CallArgs) -> Box<LoadState> {
83 rooted!(&in(cx) let host_defined = unsafe { *GetFunctionNativeReserved(args.callee(), LOAD_REACTION_HOST_DEFINED_SLOT) });
84 unsafe {
85 SetFunctionNativeReserved(
86 args.callee(),
87 LOAD_REACTION_HOST_DEFINED_SLOT,
88 &UndefinedValue(),
89 )
90 };
91 assert!(!host_defined.get().is_undefined());
92 unsafe { Box::from_raw((*host_defined).to_private() as *mut LoadState) }
93}
94
95unsafe extern "C" fn on_load_requested_modules_resolved(
96 cx: *mut RawJSContext,
97 argc: u32,
98 vp: *mut JSVal,
99) -> bool {
100 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
102 let mut realm = CurrentRealm::assert(&mut cx);
103 let cx = &mut realm;
104
105 let args = unsafe { CallArgs::from_vp(vp, argc) };
106
107 let state = take_state_from_reserved_slot(cx, &args);
108
109 let on_complete = state.on_complete.safe_borrow_mut(cx).take().unwrap();
110 let module_script = state.module_script.safe_borrow_mut(cx).take().unwrap();
111
112 let record = module_script.record().unwrap();
113
114 let link = unsafe { ModuleLink(cx, record.handle()) };
119
120 if !link {
122 let exception = RethrowError::from_pending_exception(cx);
123 module_script.set_rethrow_error(exception.handle());
124 }
125
126 on_complete(cx, Some(module_script));
128
129 true
130}
131
132unsafe extern "C" fn on_load_requested_modules_rejected(
133 cx: *mut RawJSContext,
134 argc: u32,
135 vp: *mut JSVal,
136) -> bool {
137 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
139 let mut realm = CurrentRealm::assert(&mut cx);
140 let cx = &mut realm;
141
142 let args = unsafe { CallArgs::from_vp(vp, argc) };
143
144 let state = take_state_from_reserved_slot(cx, &args);
145
146 let error = unsafe { Handle::from_raw(args.get(0)) };
147
148 let on_complete = state.on_complete.safe_borrow_mut(cx).take().unwrap();
149 let module_script = state.module_script.safe_borrow_mut(cx).take().unwrap();
150
151 if !error.is_undefined() {
157 module_script.set_rethrow_error(error);
158 on_complete(cx, Some(module_script));
159 } else {
160 on_complete(cx, None);
162 }
163
164 true
165}
166
167struct ImportRequest {
168 referrer: RootedTraceableBox<Heap<*mut JSScript>>,
169 module_request: RootedTraceableBox<Heap<*mut JSObject>>,
170 payload: RootedTraceableBox<Heap<JSVal>>,
171}
172
173fn load_state_from_handle_value<'a>(reference_private: Handle<'a, JSVal>) -> Option<&'a LoadState> {
174 if reference_private.get().is_undefined() {
175 return None;
176 }
177 unsafe { (reference_private.get().to_private() as *const LoadState).as_ref() }
178}
179
180pub(crate) fn load_requested_modules(
181 cx: &mut CurrentRealm,
182 module: Handle<*mut JSObject>,
183 load_state: Box<LoadState>,
184) {
185 rooted!(&in(cx) let host_defined = PrivateValue(Box::into_raw(load_state) as *const _ as *const c_void));
186
187 unsafe {
188 rooted!(&in(cx) let on_resolved = NewFunctionWithReserved(
189 cx,
190 Some(on_load_requested_modules_resolved),
191 0,
192 0,
193 ptr::null(),
194 ));
195 rooted!(&in(cx) let on_rejected = NewFunctionWithReserved(
196 cx,
197 Some(on_load_requested_modules_rejected),
198 1,
199 0,
200 ptr::null(),
201 ));
202
203 rooted!(&in(cx) let resolved_function_object = JS_GetFunctionObject(on_resolved.get()));
204 SetFunctionNativeReserved(
205 resolved_function_object.get(),
206 LOAD_REACTION_HOST_DEFINED_SLOT,
207 host_defined.handle().as_ref(cx),
208 );
209
210 rooted!(&in(cx) let rejected_function_object = JS_GetFunctionObject(on_rejected.get()));
211 SetFunctionNativeReserved(
212 rejected_function_object.get(),
213 LOAD_REACTION_HOST_DEFINED_SLOT,
214 host_defined.handle().as_ref(cx),
215 );
216
217 rooted!(&in(cx) let mut promise_obj = ptr::null_mut::<JSObject>());
218 assert!(LoadRequestedModules1(
219 cx,
220 module,
221 host_defined.handle(),
222 promise_obj.handle_mut(),
223 ));
224
225 AddPromiseReactions(
226 cx,
227 promise_obj.handle(),
228 resolved_function_object.handle(),
229 rejected_function_object.handle(),
230 );
231 }
232}
233
234fn finish_loading_imported_module(
236 cx: &mut CurrentRealm,
237 referrer: Handle<*mut JSScript>,
238 module_request: Handle<*mut JSObject>,
239 payload: Handle<JSVal>,
240 module_record: Handle<*mut JSObject>,
241) {
242 rooted!(&in(cx) let object = payload.to_object());
243 let is_promise = unsafe { IsPromiseObject(object.handle()) };
244
245 if is_promise {
246 unsafe {
247 FinishLoadingDynamicImportedModule(cx, referrer, module_request, payload, module_record)
248 };
249 let promise = Promise::new_with_js_promise(cx, object.handle());
250 return continue_dynamic_import(cx, promise, ModuleObject::new(module_record));
251 }
252
253 assert!(unsafe {
254 FinishLoadingImportedModule(cx, referrer, module_request, payload, module_record, true)
255 });
256}
257
258fn continue_dynamic_import(realm: &mut CurrentRealm, promise: RootedPromise, module: ModuleObject) {
260 let global = GlobalScope::from_current_realm(realm);
266
267 rooted!(&in(*realm) let host_defined = UndefinedValue());
270 rooted!(&in(*realm) let mut promise_obj = ptr::null_mut::<JSObject>());
271
272 unsafe {
274 LoadRequestedModules1(
275 realm,
276 module.handle(),
277 host_defined.handle(),
278 promise_obj.handle_mut(),
279 )
280 };
281
282 let load_promise = Promise::new_with_js_promise(realm, promise_obj.handle());
283
284 let global_scope = global.clone();
290 let inner_promise = promise.clone();
291 let fulfilled_promise = promise.clone();
292
293 let link_and_evaluate = ModuleHandler::new_boxed(Box::new(
297 task!(link_and_evaluate: |cx, global_scope: DomRoot<GlobalScope>, inner_promise: RootedPromise, module: ModuleObject| {
298 let mut realm = enter_auto_realm(cx, &*global_scope);
299 let cx = &mut realm.current_realm();
300
301 let link = unsafe { ModuleLink(cx, module.handle()) };
303
304 if !link {
306 let exception = RethrowError::from_pending_exception(cx);
308 inner_promise.reject(cx, exception.handle());
309
310 return;
312 }
313
314 rooted!(&in(cx) let mut rval = UndefinedValue());
315
316 assert!(unsafe { ModuleEvaluate(cx, module.handle(), rval.handle_mut()) });
318
319 if !rval.is_object() {
320 let error = RethrowError::from_pending_exception(cx);
321 return inner_promise.reject(cx, error.handle());
322 }
323
324 rooted!(&in(cx) let evaluate_promise = rval.to_object());
325 let evaluate_promise = Promise::new_with_js_promise(cx, evaluate_promise.handle());
326
327 let on_fulfilled = ModuleHandler::new_boxed(Box::new(
331 task!(on_fulfilled: |cx, fulfilled_promise: RootedPromise, module: ModuleObject| {
332
333 rooted!(&in(cx) let rval = unsafe { GetModuleNamespace(cx, module.handle()) });
335 rooted!(&in(cx) let namespace = ObjectValue(rval.get()));
336
337 fulfilled_promise.resolve(cx, namespace.handle());
339
340 })));
342
343 rooted!(&in(cx) let mut rejected_handler = Some(OnRejectedHandler { promise: inner_promise.to_traced() }));
344
345 let handler = PromiseNativeHandler::new(
347 cx,
348 &global_scope,
349 Some(on_fulfilled),
350 rejected_handler.take().map(|h| Box::new(h) as Box<dyn Callback>),
351 );
352 evaluate_promise.append_native_handler(cx, &handler);
353
354 }),
356 ));
357
358 run_a_callback::<DomTypeHolder, _>(&*global, || {
359 let handler = PromiseNativeHandler::new(
361 realm,
362 &global,
363 Some(link_and_evaluate),
364 Some(Box::new(OnRejectedHandler {
365 promise: promise.to_traced(),
366 })),
367 );
368 load_promise.append_native_handler(realm, &handler);
369 });
370 }
372
373pub(crate) fn host_load_imported_module(
375 cx: &mut CurrentRealm,
376 referrer: Handle<*mut JSScript>,
377 module_request: Handle<*mut JSObject>,
378 specifier: String,
379 host_defined: Handle<JSVal>,
380 payload: Handle<JSVal>,
381) {
382 let mut realm = CurrentRealm::assert(cx);
384 let mut global_scope = GlobalScope::from_current_realm(&mut realm);
385
386 let load_state = load_state_from_handle_value(host_defined);
387
388 rooted!(&in(cx) let mut script_private = UndefinedValue());
392
393 let mut original_fetch_options = ScriptFetchOptions::default_classic_script();
395
396 let mut fetch_referrer = global_scope.get_referrer();
398
399 unsafe { JS_GetScriptPrivate(*referrer.as_ref(cx), script_private.handle_mut()) };
403 let referencing_script = unsafe {
404 module_script_from_reference_private(script_private.handle())
405 }
406 .map(|script| ReferringScript {
407 script,
408 settings_object: unsafe {
409 GlobalScope::from_object(JS_GetGlobalFromScript(referrer.get()))
410 },
411 });
412 if let Some(referencing_script) = &referencing_script {
413 global_scope = referencing_script.settings_object.clone();
415 fetch_referrer = Referrer::ReferrerUrl(referencing_script.script.base_url.clone());
417 original_fetch_options = referencing_script.script.options.clone();
419 }
420
421 let module_type = unsafe { GetModuleRequestType(cx, module_request) };
430
431 if !module_type_allowed(&global_scope, module_type) {
433 throw_type_error(cx, c"Found invalid module type attribute");
435
436 unsafe { FinishLoadingImportedModuleFailedWithPendingException(cx, payload) };
442
443 return;
445 }
446
447 let url =
450 ImportMap::resolve_module_specifier(&global_scope, referencing_script.as_ref(), specifier);
451
452 if let Err(resolution_error) = url {
454 throw_dom_exception(cx, &global_scope, resolution_error);
455
456 unsafe { FinishLoadingImportedModuleFailedWithPendingException(cx, payload) };
462
463 return;
465 };
466
467 let url = ensure_blob_referenced_by_url_is_kept_alive(&global_scope, url.unwrap());
468
469 let fetch_options = original_fetch_options.descendant_fetch_options(&url.url(), &global_scope);
472
473 let (destination, fetch_client) = match load_state {
476 Some(load_state) => (load_state.destination, load_state.fetch_client.clone()),
479 None => (
480 Destination::Script,
482 global_scope.request_client(Some(cx.no_gc())),
484 ),
485 };
486
487 let is_dynamic_import = load_state.is_none();
488 let request = ImportRequest {
489 referrer: RootedTraceableBox::from_box(Heap::boxed(*referrer.as_ref(cx))),
490 module_request: RootedTraceableBox::from_box(Heap::boxed(*module_request.as_ref(cx))),
491 payload: RootedTraceableBox::from_box(Heap::boxed(*payload.as_ref(cx))),
492 };
493
494 let on_single_fetch_complete =
495 move |cx: &mut JSContext, module_tree: Option<Rc<ModuleTree>>| {
496 let mut realm = CurrentRealm::assert(cx);
497 let cx = &mut realm;
498
499 match module_tree {
502 None => {
504 if is_dynamic_import {
505 throw_type_error(cx, c"Module fetching failed");
506 unsafe {
507 FinishLoadingImportedModuleFailedWithPendingException(
508 cx,
509 request.payload.handle(),
510 )
511 };
512 } else {
513 rooted!(&in(cx) let error = UndefinedValue());
517 unsafe {
518 FinishLoadingImportedModuleFailed(
519 cx,
520 request.payload.handle(),
521 error.handle(),
522 )
523 };
524 }
525 },
526 Some(module_tree) => {
527 if let Some(parse_error) = module_tree.parse_error() {
530 unsafe {
537 FinishLoadingImportedModuleFailed(
538 cx,
539 request.payload.handle(),
540 parse_error.handle(),
541 )
542 };
543 } else {
544 let completion = module_tree.record().unwrap();
546
547 finish_loading_imported_module(
549 cx,
550 request.referrer.handle(),
551 request.module_request.handle(),
552 request.payload.handle(),
553 completion.handle(),
554 );
555 }
556 },
557 }
558 };
559
560 fetch_a_single_imported_module_script(
565 cx,
566 url,
567 fetch_client,
568 &global_scope,
569 destination,
570 fetch_options,
571 fetch_referrer,
572 module_type,
573 on_single_fetch_complete,
574 );
575}
576
577fn module_type_allowed(global: &GlobalScope, module_type: ModuleType) -> bool {
579 match module_type {
580 ModuleType::Unknown | ModuleType::Bytes => false,
583 ModuleType::CSS => global.is::<Window>(),
586 ModuleType::JavaScript | ModuleType::JSON | ModuleType::Text => true,
588 }
589}
590
591#[expect(clippy::too_many_arguments)]
593fn fetch_a_single_imported_module_script(
594 cx: &mut JSContext,
595 url: UrlWithBlobClaim,
596 fetch_client: RequestClient,
597 global: &GlobalScope,
598 destination: Destination,
599 options: ScriptFetchOptions,
600 referrer: Referrer,
601 module_type: ModuleType,
602 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
603) {
604 match module_type {
612 ModuleType::Unknown | ModuleType::Bytes => {
613 return on_complete(cx, None);
614 },
615 ModuleType::JavaScript | ModuleType::JSON | ModuleType::CSS | ModuleType::Text => (),
616 }
617
618 fetch_a_single_module_script(
621 cx,
622 url,
623 fetch_client,
624 global,
625 destination,
626 options,
627 referrer,
628 Some(module_type),
629 false,
630 Some(IntroductionType::IMPORTED_MODULE),
631 on_complete,
632 );
633}