1#![expect(unsafe_code)]
10
11use std::ffi::c_void;
12use std::ptr;
13use std::rc::Rc;
14
15use js::context::{JSContext, RawJSContext};
16use js::error::throw_type_error;
17use js::jsapi::{
18 CallArgs, GetFunctionNativeReserved, Heap, JS_GetFunctionObject, JSObject, JSScript,
19 ModuleType, SetFunctionNativeReserved,
20};
21use js::jsval::{JSVal, ObjectValue, PrivateValue, UndefinedValue};
22use js::realm::CurrentRealm;
23use js::rust::Handle;
24use js::rust::wrappers2::{
25 AddPromiseReactions, FinishLoadingDynamicImportedModule, FinishLoadingImportedModule,
26 FinishLoadingImportedModuleFailed, FinishLoadingImportedModuleFailedWithPendingException,
27 GetModuleNamespace, GetModuleRequestType, IsPromiseObject, JS_GetScriptPrivate,
28 LoadRequestedModules1, ModuleEvaluate, ModuleLink, NewFunctionWithReserved,
29};
30use net_traits::blob_url_store::UrlWithBlobClaim;
31use net_traits::request::{Destination, Referrer, RequestClient};
32use script_bindings::cell::DomRefCell;
33use script_bindings::inheritance::Castable;
34use script_bindings::settings_stack::run_a_callback;
35
36use crate::DomTypeHolder;
37use crate::dom::bindings::error::throw_dom_exception;
38use crate::dom::bindings::root::DomRoot;
39use crate::dom::bindings::trace::RootedTraceableBox;
40use crate::dom::globalscope::GlobalScope;
41use crate::dom::promise::promisenativehandler::{Callback, PromiseNativeHandler};
42use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
43use crate::dom::window::Window;
44use crate::modules::script_module::{
45 ModuleHandler, ModuleObject, ModuleTree, RethrowError, ScriptFetchOptions,
46 fetch_a_single_module_script, module_script_from_reference_private,
47};
48use crate::realms::enter_auto_realm;
49use crate::runtime::script_runtime::IntroductionType;
50use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
51
52#[derive(JSTraceable, MallocSizeOf)]
53#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
54struct OnRejectedHandler {
55 promise: TracedPromise,
56}
57
58impl js::gc::Rootable for OnRejectedHandler {}
59
60impl Callback for OnRejectedHandler {
61 fn callback(&self, cx: &mut CurrentRealm, v: Handle<JSVal>) {
62 self.promise.reject(cx, v);
64 }
65}
66
67#[derive(JSTraceable, MallocSizeOf)]
68pub(crate) struct LoadState {
69 #[no_trace]
70 pub(crate) destination: Destination,
71 #[no_trace]
72 pub(crate) fetch_client: RequestClient,
73 #[conditional_malloc_size_of]
74 pub(crate) module_script: DomRefCell<Option<Rc<ModuleTree>>>,
75 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
76 #[no_trace]
77 #[allow(clippy::type_complexity)]
78 pub(crate) on_complete:
79 DomRefCell<Option<Box<dyn FnOnce(&mut JSContext, Option<Rc<ModuleTree>>)>>>,
80}
81
82const LOAD_REACTION_HOST_DEFINED_SLOT: usize = 0;
83
84fn take_state_from_reserved_slot(cx: &mut JSContext, args: &CallArgs) -> Box<LoadState> {
85 rooted!(&in(cx) let host_defined = unsafe { *GetFunctionNativeReserved(args.callee(), LOAD_REACTION_HOST_DEFINED_SLOT) });
86 unsafe {
87 SetFunctionNativeReserved(
88 args.callee(),
89 LOAD_REACTION_HOST_DEFINED_SLOT,
90 &UndefinedValue(),
91 )
92 };
93 assert!(!host_defined.get().is_undefined());
94 unsafe { Box::from_raw((*host_defined).to_private() as *mut LoadState) }
95}
96
97unsafe extern "C" fn on_load_requested_modules_resolved(
98 cx: *mut RawJSContext,
99 argc: u32,
100 vp: *mut JSVal,
101) -> bool {
102 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
104 let mut realm = CurrentRealm::assert(&mut cx);
105 let cx = &mut realm;
106
107 let args = unsafe { CallArgs::from_vp(vp, argc) };
108
109 let state = take_state_from_reserved_slot(cx, &args);
110
111 let on_complete = state.on_complete.safe_borrow_mut(cx).take().unwrap();
112 let module_script = state.module_script.safe_borrow_mut(cx).take().unwrap();
113
114 let record = module_script
115 .get_record()
116 .map(|module| module.handle())
117 .unwrap();
118
119 let link = unsafe { ModuleLink(cx, record) };
124
125 if !link {
127 let exception = RethrowError::from_pending_exception(cx);
128 module_script.set_rethrow_error(exception);
129 }
130
131 on_complete(cx, Some(module_script));
133
134 true
135}
136
137unsafe extern "C" fn on_load_requested_modules_rejected(
138 cx: *mut RawJSContext,
139 argc: u32,
140 vp: *mut JSVal,
141) -> bool {
142 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
144 let mut realm = CurrentRealm::assert(&mut cx);
145 let cx = &mut realm;
146
147 let args = unsafe { CallArgs::from_vp(vp, argc) };
148
149 let state = take_state_from_reserved_slot(cx, &args);
150
151 let error = unsafe { Handle::from_raw(args.get(0)) };
152
153 let on_complete = state.on_complete.safe_borrow_mut(cx).take().unwrap();
154 let module_script = state.module_script.safe_borrow_mut(cx).take().unwrap();
155
156 if !error.is_undefined() {
162 module_script.set_rethrow_error(RethrowError::new(Heap::boxed(*error.as_ref(cx))));
163 on_complete(cx, Some(module_script));
164 } else {
165 on_complete(cx, None);
167 }
168
169 true
170}
171
172struct ImportRequest {
173 referrer: RootedTraceableBox<Heap<*mut JSScript>>,
174 module_request: RootedTraceableBox<Heap<*mut JSObject>>,
175 payload: RootedTraceableBox<Heap<JSVal>>,
176}
177
178fn load_state_from_handle_value<'a>(reference_private: Handle<'a, JSVal>) -> Option<&'a LoadState> {
179 if reference_private.get().is_undefined() {
180 return None;
181 }
182 unsafe { (reference_private.get().to_private() as *const LoadState).as_ref() }
183}
184
185pub(crate) fn load_requested_modules(
186 cx: &mut CurrentRealm,
187 module: Handle<*mut JSObject>,
188 load_state: Box<LoadState>,
189) {
190 rooted!(&in(cx) let host_defined = PrivateValue(Box::into_raw(load_state) as *const _ as *const c_void));
191
192 unsafe {
193 rooted!(&in(cx) let on_resolved = NewFunctionWithReserved(
194 cx,
195 Some(on_load_requested_modules_resolved),
196 0,
197 0,
198 ptr::null(),
199 ));
200 rooted!(&in(cx) let on_rejected = NewFunctionWithReserved(
201 cx,
202 Some(on_load_requested_modules_rejected),
203 1,
204 0,
205 ptr::null(),
206 ));
207
208 rooted!(&in(cx) let resolved_function_object = JS_GetFunctionObject(on_resolved.get()));
209 SetFunctionNativeReserved(
210 resolved_function_object.get(),
211 LOAD_REACTION_HOST_DEFINED_SLOT,
212 host_defined.handle().as_ref(cx),
213 );
214
215 rooted!(&in(cx) let rejected_function_object = JS_GetFunctionObject(on_rejected.get()));
216 SetFunctionNativeReserved(
217 rejected_function_object.get(),
218 LOAD_REACTION_HOST_DEFINED_SLOT,
219 host_defined.handle().as_ref(cx),
220 );
221
222 rooted!(&in(cx) let mut promise_obj = ptr::null_mut::<JSObject>());
223 assert!(LoadRequestedModules1(
224 cx,
225 module,
226 host_defined.handle(),
227 promise_obj.handle_mut(),
228 ));
229
230 AddPromiseReactions(
231 cx,
232 promise_obj.handle(),
233 resolved_function_object.handle(),
234 rejected_function_object.handle(),
235 );
236 }
237}
238
239fn finish_loading_imported_module(
241 cx: &mut CurrentRealm,
242 referrer: Handle<*mut JSScript>,
243 module_request: Handle<*mut JSObject>,
244 payload: Handle<JSVal>,
245 module_record: Handle<*mut JSObject>,
246) {
247 rooted!(&in(cx) let object = payload.to_object());
248 let is_promise = unsafe { IsPromiseObject(object.handle()) };
249
250 if is_promise {
251 unsafe {
252 FinishLoadingDynamicImportedModule(cx, referrer, module_request, payload, module_record)
253 };
254 let promise = Promise::new_with_js_promise(cx, object.handle());
255 return continue_dynamic_import(cx, promise, ModuleObject::new(module_record));
256 }
257
258 assert!(unsafe {
259 FinishLoadingImportedModule(cx, referrer, module_request, payload, module_record, true)
260 });
261}
262
263fn continue_dynamic_import(realm: &mut CurrentRealm, promise: RootedPromise, module: ModuleObject) {
265 let global = GlobalScope::from_current_realm(realm);
271
272 rooted!(&in(*realm) let host_defined = UndefinedValue());
275 rooted!(&in(*realm) let mut promise_obj = ptr::null_mut::<JSObject>());
276
277 unsafe {
279 LoadRequestedModules1(
280 realm,
281 module.handle(),
282 host_defined.handle(),
283 promise_obj.handle_mut(),
284 )
285 };
286
287 let load_promise = Promise::new_with_js_promise(realm, promise_obj.handle());
288
289 let global_scope = global.clone();
295 let inner_promise = promise.clone();
296 let fulfilled_promise = promise.clone();
297
298 let link_and_evaluate = ModuleHandler::new_boxed(Box::new(
302 task!(link_and_evaluate: |cx, global_scope: DomRoot<GlobalScope>, inner_promise: RootedPromise, module: ModuleObject| {
303 let mut realm = enter_auto_realm(cx, &*global_scope);
304 let cx = &mut realm.current_realm();
305
306 let link = unsafe { ModuleLink(cx, module.handle()) };
308
309 if !link {
311 let exception = RethrowError::from_pending_exception(cx);
313 inner_promise.reject(cx, exception.handle());
314
315 return;
317 }
318
319 rooted!(&in(cx) let mut rval = UndefinedValue());
320
321 assert!(unsafe { ModuleEvaluate(cx, module.handle(), rval.handle_mut()) });
323
324 if !rval.is_object() {
325 let error = RethrowError::from_pending_exception(cx);
326 return inner_promise.reject(cx, error.handle());
327 }
328
329 rooted!(&in(cx) let evaluate_promise = rval.to_object());
330 let evaluate_promise = Promise::new_with_js_promise(cx, evaluate_promise.handle());
331
332 let on_fulfilled = ModuleHandler::new_boxed(Box::new(
336 task!(on_fulfilled: |cx, fulfilled_promise: RootedPromise, module: ModuleObject| {
337
338 rooted!(&in(cx) let rval = unsafe { GetModuleNamespace(cx, module.handle()) });
340 rooted!(&in(cx) let namespace = ObjectValue(rval.get()));
341
342 fulfilled_promise.resolve(cx, namespace.handle());
344
345 })));
347
348 rooted!(&in(cx) let mut rejected_handler = Some(OnRejectedHandler { promise: inner_promise.to_traced() }));
349
350 let handler = PromiseNativeHandler::new(
352 cx,
353 &global_scope,
354 Some(on_fulfilled),
355 rejected_handler.take().map(|h| Box::new(h) as Box<dyn Callback>),
356 );
357 evaluate_promise.append_native_handler(cx, &handler);
358
359 }),
361 ));
362
363 run_a_callback::<DomTypeHolder, _>(&*global, || {
364 let handler = PromiseNativeHandler::new(
366 realm,
367 &global,
368 Some(link_and_evaluate),
369 Some(Box::new(OnRejectedHandler {
370 promise: promise.to_traced(),
371 })),
372 );
373 load_promise.append_native_handler(realm, &handler);
374 });
375 }
377
378pub(crate) fn host_load_imported_module(
380 cx: &mut CurrentRealm,
381 referrer: Handle<*mut JSScript>,
382 module_request: Handle<*mut JSObject>,
383 specifier: String,
384 host_defined: Handle<JSVal>,
385 payload: Handle<JSVal>,
386) {
387 let mut realm = CurrentRealm::assert(cx);
389 let mut global_scope = GlobalScope::from_current_realm(&mut realm);
390
391 let load_state = load_state_from_handle_value(host_defined);
392
393 rooted!(&in(cx) let mut script_private = UndefinedValue());
397
398 unsafe { JS_GetScriptPrivate(*referrer.as_ref(cx), script_private.handle_mut()) };
400 let referencing_script =
401 unsafe { module_script_from_reference_private(script_private.handle()) };
402
403 let (original_fetch_options, fetch_referrer) = match referencing_script {
405 Some(module) => (
406 module.options.clone(),
408 Referrer::ReferrerUrl(module.base_url.clone()),
410 ),
411 None => (
412 ScriptFetchOptions::default_classic_script(),
414 global_scope.get_referrer(),
416 ),
417 };
418
419 let script_owner = referencing_script.and_then(|script| script.owner.clone());
420
421 if let Some(ref owner) = script_owner {
423 global_scope = owner.root();
424 }
425
426 let module_type = unsafe { GetModuleRequestType(cx, module_request) };
434
435 if !module_type_allowed(&global_scope, module_type) {
437 throw_type_error(cx, c"Found invalid module type attribute");
439
440 unsafe { FinishLoadingImportedModuleFailedWithPendingException(cx, payload) };
446
447 return;
449 }
450
451 let url = ModuleTree::resolve_module_specifier(&global_scope, referencing_script, specifier);
454
455 if let Err(resolution_error) = url {
457 throw_dom_exception(cx, &global_scope, resolution_error);
458
459 unsafe { FinishLoadingImportedModuleFailedWithPendingException(cx, payload) };
465
466 return;
468 };
469
470 let url = ensure_blob_referenced_by_url_is_kept_alive(&global_scope, url.unwrap());
471
472 let fetch_options = original_fetch_options.descendant_fetch_options(&url.url(), &global_scope);
475
476 let (destination, fetch_client) = match load_state {
479 Some(load_state) => (load_state.destination, load_state.fetch_client.clone()),
482 None => (
483 Destination::Script,
485 global_scope.request_client(Some(cx.no_gc())),
487 ),
488 };
489
490 let is_dynamic_import = load_state.is_none();
491 let request = ImportRequest {
492 referrer: RootedTraceableBox::from_box(Heap::boxed(*referrer.as_ref(cx))),
493 module_request: RootedTraceableBox::from_box(Heap::boxed(*module_request.as_ref(cx))),
494 payload: RootedTraceableBox::from_box(Heap::boxed(*payload.as_ref(cx))),
495 };
496
497 let on_single_fetch_complete =
498 move |cx: &mut JSContext, module_tree: Option<Rc<ModuleTree>>| {
499 let mut realm = CurrentRealm::assert(cx);
500 let cx = &mut realm;
501
502 match module_tree {
505 None => {
507 if is_dynamic_import {
508 throw_type_error(cx, c"Module fetching failed");
509 unsafe {
510 FinishLoadingImportedModuleFailedWithPendingException(
511 cx,
512 request.payload.handle(),
513 )
514 };
515 } else {
516 rooted!(&in(cx) let error = UndefinedValue());
520 unsafe {
521 FinishLoadingImportedModuleFailed(
522 cx,
523 request.payload.handle(),
524 error.handle(),
525 )
526 };
527 }
528 },
529 Some(module_tree) => {
530 if let Some(parse_error) = module_tree.get_parse_error() {
533 unsafe {
540 FinishLoadingImportedModuleFailed(
541 cx,
542 request.payload.handle(),
543 parse_error.handle(),
544 )
545 };
546 } else {
547 let completion = module_tree
549 .get_record()
550 .map(|module| module.handle())
551 .unwrap();
552
553 finish_loading_imported_module(
555 cx,
556 request.referrer.handle(),
557 request.module_request.handle(),
558 request.payload.handle(),
559 completion,
560 );
561 }
562 },
563 }
564 };
565
566 fetch_a_single_imported_module_script(
571 cx,
572 url,
573 fetch_client,
574 &global_scope,
575 destination,
576 fetch_options,
577 fetch_referrer,
578 module_type,
579 on_single_fetch_complete,
580 );
581}
582
583fn module_type_allowed(global: &GlobalScope, module_type: ModuleType) -> bool {
585 match module_type {
586 ModuleType::Unknown | ModuleType::Bytes => false,
589 ModuleType::CSS => global.is::<Window>(),
592 ModuleType::JavaScript | ModuleType::JSON | ModuleType::Text => true,
594 }
595}
596
597#[expect(clippy::too_many_arguments)]
599fn fetch_a_single_imported_module_script(
600 cx: &mut JSContext,
601 url: UrlWithBlobClaim,
602 fetch_client: RequestClient,
603 global: &GlobalScope,
604 destination: Destination,
605 options: ScriptFetchOptions,
606 referrer: Referrer,
607 module_type: ModuleType,
608 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
609) {
610 match module_type {
618 ModuleType::Unknown | ModuleType::Bytes => {
619 return on_complete(cx, None);
620 },
621 ModuleType::JavaScript | ModuleType::JSON | ModuleType::CSS | ModuleType::Text => (),
622 }
623
624 fetch_a_single_module_script(
627 cx,
628 url,
629 fetch_client,
630 global,
631 destination,
632 options,
633 referrer,
634 Some(module_type),
635 false,
636 Some(IntroductionType::IMPORTED_MODULE),
637 on_complete,
638 );
639}