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, JSObject, JSScript,
15 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;
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
113 .get_record()
114 .map(|module| module.handle())
115 .unwrap();
116
117 let link = unsafe { ModuleLink(cx, record) };
122
123 if !link {
125 let exception = RethrowError::from_pending_exception(cx);
126 module_script.set_rethrow_error(exception);
127 }
128
129 on_complete(cx, Some(module_script));
131
132 true
133}
134
135unsafe extern "C" fn on_load_requested_modules_rejected(
136 cx: *mut RawJSContext,
137 argc: u32,
138 vp: *mut JSVal,
139) -> bool {
140 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
142 let mut realm = CurrentRealm::assert(&mut cx);
143 let cx = &mut realm;
144
145 let args = unsafe { CallArgs::from_vp(vp, argc) };
146
147 let state = take_state_from_reserved_slot(cx, &args);
148
149 let error = unsafe { Handle::from_raw(args.get(0)) };
150
151 let on_complete = state.on_complete.safe_borrow_mut(cx).take().unwrap();
152 let module_script = state.module_script.safe_borrow_mut(cx).take().unwrap();
153
154 if !error.is_undefined() {
160 module_script.set_rethrow_error(RethrowError::new(Heap::boxed(*error.as_ref(cx))));
161 on_complete(cx, Some(module_script));
162 } else {
163 on_complete(cx, None);
165 }
166
167 true
168}
169
170struct ImportRequest {
171 referrer: RootedTraceableBox<Heap<*mut JSScript>>,
172 module_request: RootedTraceableBox<Heap<*mut JSObject>>,
173 payload: RootedTraceableBox<Heap<JSVal>>,
174}
175
176fn load_state_from_handle_value<'a>(reference_private: Handle<'a, JSVal>) -> Option<&'a LoadState> {
177 if reference_private.get().is_undefined() {
178 return None;
179 }
180 unsafe { (reference_private.get().to_private() as *const LoadState).as_ref() }
181}
182
183pub(crate) fn load_requested_modules(
184 cx: &mut CurrentRealm,
185 module: Handle<*mut JSObject>,
186 load_state: Box<LoadState>,
187) {
188 rooted!(&in(cx) let host_defined = PrivateValue(Box::into_raw(load_state) as *const _ as *const c_void));
189
190 unsafe {
191 rooted!(&in(cx) let on_resolved = NewFunctionWithReserved(
192 cx,
193 Some(on_load_requested_modules_resolved),
194 0,
195 0,
196 ptr::null(),
197 ));
198 rooted!(&in(cx) let on_rejected = NewFunctionWithReserved(
199 cx,
200 Some(on_load_requested_modules_rejected),
201 1,
202 0,
203 ptr::null(),
204 ));
205
206 rooted!(&in(cx) let resolved_function_object = JS_GetFunctionObject(on_resolved.get()));
207 SetFunctionNativeReserved(
208 resolved_function_object.get(),
209 LOAD_REACTION_HOST_DEFINED_SLOT,
210 host_defined.handle().as_ref(cx),
211 );
212
213 rooted!(&in(cx) let rejected_function_object = JS_GetFunctionObject(on_rejected.get()));
214 SetFunctionNativeReserved(
215 rejected_function_object.get(),
216 LOAD_REACTION_HOST_DEFINED_SLOT,
217 host_defined.handle().as_ref(cx),
218 );
219
220 rooted!(&in(cx) let mut promise_obj = ptr::null_mut::<JSObject>());
221 assert!(LoadRequestedModules1(
222 cx,
223 module,
224 host_defined.handle(),
225 promise_obj.handle_mut(),
226 ));
227
228 AddPromiseReactions(
229 cx,
230 promise_obj.handle(),
231 resolved_function_object.handle(),
232 rejected_function_object.handle(),
233 );
234 }
235}
236
237fn finish_loading_imported_module(
239 cx: &mut CurrentRealm,
240 referrer: Handle<*mut JSScript>,
241 module_request: Handle<*mut JSObject>,
242 payload: Handle<JSVal>,
243 module_record: Handle<*mut JSObject>,
244) {
245 rooted!(&in(cx) let object = payload.to_object());
246 let is_promise = unsafe { IsPromiseObject(object.handle()) };
247
248 if is_promise {
249 unsafe {
250 FinishLoadingDynamicImportedModule(cx, referrer, module_request, payload, module_record)
251 };
252 let promise = Promise::new_with_js_promise(cx, object.handle());
253 return continue_dynamic_import(cx, promise, ModuleObject::new(module_record));
254 }
255
256 assert!(unsafe {
257 FinishLoadingImportedModule(cx, referrer, module_request, payload, module_record, true)
258 });
259}
260
261fn continue_dynamic_import(realm: &mut CurrentRealm, promise: RootedPromise, module: ModuleObject) {
263 let global = GlobalScope::from_current_realm(realm);
269
270 rooted!(&in(*realm) let host_defined = UndefinedValue());
273 rooted!(&in(*realm) let mut promise_obj = ptr::null_mut::<JSObject>());
274
275 unsafe {
277 LoadRequestedModules1(
278 realm,
279 module.handle(),
280 host_defined.handle(),
281 promise_obj.handle_mut(),
282 )
283 };
284
285 let load_promise = Promise::new_with_js_promise(realm, promise_obj.handle());
286
287 let global_scope = global.clone();
293 let inner_promise = promise.clone();
294 let fulfilled_promise = promise.clone();
295
296 let link_and_evaluate = ModuleHandler::new_boxed(Box::new(
300 task!(link_and_evaluate: |cx, global_scope: DomRoot<GlobalScope>, inner_promise: RootedPromise, module: ModuleObject| {
301 let mut realm = enter_auto_realm(cx, &*global_scope);
302 let cx = &mut realm.current_realm();
303
304 let link = unsafe { ModuleLink(cx, module.handle()) };
306
307 if !link {
309 let exception = RethrowError::from_pending_exception(cx);
311 inner_promise.reject(cx, exception.handle());
312
313 return;
315 }
316
317 rooted!(&in(cx) let mut rval = UndefinedValue());
318
319 assert!(unsafe { ModuleEvaluate(cx, module.handle(), rval.handle_mut()) });
321
322 if !rval.is_object() {
323 let error = RethrowError::from_pending_exception(cx);
324 return inner_promise.reject(cx, error.handle());
325 }
326
327 rooted!(&in(cx) let evaluate_promise = rval.to_object());
328 let evaluate_promise = Promise::new_with_js_promise(cx, evaluate_promise.handle());
329
330 let on_fulfilled = ModuleHandler::new_boxed(Box::new(
334 task!(on_fulfilled: |cx, fulfilled_promise: RootedPromise, module: ModuleObject| {
335
336 rooted!(&in(cx) let rval = unsafe { GetModuleNamespace(cx, module.handle()) });
338 rooted!(&in(cx) let namespace = ObjectValue(rval.get()));
339
340 fulfilled_promise.resolve(cx, namespace.handle());
342
343 })));
345
346 rooted!(&in(cx) let mut rejected_handler = Some(OnRejectedHandler { promise: inner_promise.to_traced() }));
347
348 let handler = PromiseNativeHandler::new(
350 cx,
351 &global_scope,
352 Some(on_fulfilled),
353 rejected_handler.take().map(|h| Box::new(h) as Box<dyn Callback>),
354 );
355 evaluate_promise.append_native_handler(cx, &handler);
356
357 }),
359 ));
360
361 run_a_callback::<DomTypeHolder, _>(&*global, || {
362 let handler = PromiseNativeHandler::new(
364 realm,
365 &global,
366 Some(link_and_evaluate),
367 Some(Box::new(OnRejectedHandler {
368 promise: promise.to_traced(),
369 })),
370 );
371 load_promise.append_native_handler(realm, &handler);
372 });
373 }
375
376pub(crate) fn host_load_imported_module(
378 cx: &mut CurrentRealm,
379 referrer: Handle<*mut JSScript>,
380 module_request: Handle<*mut JSObject>,
381 specifier: String,
382 host_defined: Handle<JSVal>,
383 payload: Handle<JSVal>,
384) {
385 let mut realm = CurrentRealm::assert(cx);
387 let mut global_scope = GlobalScope::from_current_realm(&mut realm);
388
389 let load_state = load_state_from_handle_value(host_defined);
390
391 rooted!(&in(cx) let mut script_private = UndefinedValue());
395
396 unsafe { JS_GetScriptPrivate(*referrer.as_ref(cx), script_private.handle_mut()) };
398 let referencing_script =
399 unsafe { module_script_from_reference_private(script_private.handle()) };
400
401 let (original_fetch_options, fetch_referrer) = match referencing_script {
403 Some(module) => (
404 module.options.clone(),
406 Referrer::ReferrerUrl(module.base_url.clone()),
408 ),
409 None => (
410 ScriptFetchOptions::default_classic_script(),
412 global_scope.get_referrer(),
414 ),
415 };
416
417 let script_owner = referencing_script.and_then(|script| script.owner.clone());
418
419 if let Some(ref owner) = script_owner {
421 global_scope = owner.root();
422 }
423
424 let module_type = unsafe { GetModuleRequestType(cx, module_request) };
432
433 if !module_type_allowed(&global_scope, module_type) {
435 throw_type_error(cx, c"Found invalid module type attribute");
437
438 unsafe { FinishLoadingImportedModuleFailedWithPendingException(cx, payload) };
444
445 return;
447 }
448
449 let url = ImportMap::resolve_module_specifier(&global_scope, referencing_script, specifier);
452
453 if let Err(resolution_error) = url {
455 throw_dom_exception(cx, &global_scope, resolution_error);
456
457 unsafe { FinishLoadingImportedModuleFailedWithPendingException(cx, payload) };
463
464 return;
466 };
467
468 let url = ensure_blob_referenced_by_url_is_kept_alive(&global_scope, url.unwrap());
469
470 let fetch_options = original_fetch_options.descendant_fetch_options(&url.url(), &global_scope);
473
474 let (destination, fetch_client) = match load_state {
477 Some(load_state) => (load_state.destination, load_state.fetch_client.clone()),
480 None => (
481 Destination::Script,
483 global_scope.request_client(Some(cx.no_gc())),
485 ),
486 };
487
488 let is_dynamic_import = load_state.is_none();
489 let request = ImportRequest {
490 referrer: RootedTraceableBox::from_box(Heap::boxed(*referrer.as_ref(cx))),
491 module_request: RootedTraceableBox::from_box(Heap::boxed(*module_request.as_ref(cx))),
492 payload: RootedTraceableBox::from_box(Heap::boxed(*payload.as_ref(cx))),
493 };
494
495 let on_single_fetch_complete =
496 move |cx: &mut JSContext, module_tree: Option<Rc<ModuleTree>>| {
497 let mut realm = CurrentRealm::assert(cx);
498 let cx = &mut realm;
499
500 match module_tree {
503 None => {
505 if is_dynamic_import {
506 throw_type_error(cx, c"Module fetching failed");
507 unsafe {
508 FinishLoadingImportedModuleFailedWithPendingException(
509 cx,
510 request.payload.handle(),
511 )
512 };
513 } else {
514 rooted!(&in(cx) let error = UndefinedValue());
518 unsafe {
519 FinishLoadingImportedModuleFailed(
520 cx,
521 request.payload.handle(),
522 error.handle(),
523 )
524 };
525 }
526 },
527 Some(module_tree) => {
528 if let Some(parse_error) = module_tree.get_parse_error() {
531 unsafe {
538 FinishLoadingImportedModuleFailed(
539 cx,
540 request.payload.handle(),
541 parse_error.handle(),
542 )
543 };
544 } else {
545 let completion = module_tree
547 .get_record()
548 .map(|module| module.handle())
549 .unwrap();
550
551 finish_loading_imported_module(
553 cx,
554 request.referrer.handle(),
555 request.module_request.handle(),
556 request.payload.handle(),
557 completion,
558 );
559 }
560 },
561 }
562 };
563
564 fetch_a_single_imported_module_script(
569 cx,
570 url,
571 fetch_client,
572 &global_scope,
573 destination,
574 fetch_options,
575 fetch_referrer,
576 module_type,
577 on_single_fetch_complete,
578 );
579}
580
581fn module_type_allowed(global: &GlobalScope, module_type: ModuleType) -> bool {
583 match module_type {
584 ModuleType::Unknown | ModuleType::Bytes => false,
587 ModuleType::CSS => global.is::<Window>(),
590 ModuleType::JavaScript | ModuleType::JSON | ModuleType::Text => true,
592 }
593}
594
595#[expect(clippy::too_many_arguments)]
597fn fetch_a_single_imported_module_script(
598 cx: &mut JSContext,
599 url: UrlWithBlobClaim,
600 fetch_client: RequestClient,
601 global: &GlobalScope,
602 destination: Destination,
603 options: ScriptFetchOptions,
604 referrer: Referrer,
605 module_type: ModuleType,
606 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
607) {
608 match module_type {
616 ModuleType::Unknown | ModuleType::Bytes => {
617 return on_complete(cx, None);
618 },
619 ModuleType::JavaScript | ModuleType::JSON | ModuleType::CSS | ModuleType::Text => (),
620 }
621
622 fetch_a_single_module_script(
625 cx,
626 url,
627 fetch_client,
628 global,
629 destination,
630 options,
631 referrer,
632 Some(module_type),
633 false,
634 Some(IntroductionType::IMPORTED_MODULE),
635 on_complete,
636 );
637}