1#![expect(unsafe_code)]
10
11use std::cell::RefCell;
12use std::ffi::c_void;
13use std::ptr;
14use std::rc::Rc;
15
16use js::context::{JSContext, RawJSContext};
17use js::jsapi::{
18 CallArgs, GetErrorType, GetFunctionNativeReserved, Heap, JS_GetFunctionObject, JSExnType,
19 JSObject, JSScript, 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, GetModuleNamespace, GetModuleRequestType, IsPromiseObject,
27 JS_GetScriptPrivate, LoadRequestedModules1, ModuleEvaluate, ModuleLink,
28 NewFunctionWithReserved,
29};
30use net_traits::blob_url_store::UrlWithBlobClaim;
31use net_traits::request::{Destination, Referrer, RequestClient};
32use script_bindings::cell::DomRefCell;
33use script_bindings::settings_stack::run_a_callback;
34
35use crate::DomTypeHolder;
36use crate::dom::bindings::error::Error;
37use crate::dom::bindings::root::DomRoot;
38use crate::dom::bindings::trace::RootedTraceableBox;
39use crate::dom::globalscope::GlobalScope;
40use crate::dom::promise::Promise;
41use crate::dom::promise::promisenativehandler::{Callback, PromiseNativeHandler};
42use crate::modules::script_module::{
43 ModuleHandler, ModuleObject, ModuleTree, RethrowError, ScriptFetchOptions,
44 fetch_a_single_module_script, gen_type_error, module_script_from_reference_private,
45};
46use crate::realms::enter_auto_realm;
47use crate::runtime::script_runtime::IntroductionType;
48use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
49
50#[derive(JSTraceable, MallocSizeOf)]
51struct OnRejectedHandler {
52 #[conditional_malloc_size_of]
53 promise: Rc<Promise>,
54}
55
56impl Callback for OnRejectedHandler {
57 fn callback(&self, cx: &mut CurrentRealm, v: Handle<JSVal>) {
58 self.promise.reject(cx, v);
60 }
61}
62
63#[derive(JSTraceable, MallocSizeOf)]
64pub(crate) struct LoadState {
65 #[ignore_malloc_size_of = "mozjs"]
66 pub(crate) error_to_rethrow: RefCell<Option<RethrowError>>,
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 unsafe { GetErrorType(error.as_ref(cx)) } == JSExnType::JSEXN_SYNTAXERR {
161 module_script.set_rethrow_error(RethrowError::new(Heap::boxed(*error.as_ref(cx))));
162 on_complete(cx, Some(module_script));
163 return true;
164 }
165
166 let error_to_rethrow = state.error_to_rethrow.borrow().as_ref().cloned();
169 if let Some(error) = error_to_rethrow {
170 module_script.set_rethrow_error(error);
171 on_complete(cx, Some(module_script));
172 } else {
173 on_complete(cx, None);
175 }
176
177 true
178}
179
180struct ImportRequest {
181 referrer: RootedTraceableBox<Heap<*mut JSScript>>,
182 module_request: RootedTraceableBox<Heap<*mut JSObject>>,
183 payload: RootedTraceableBox<Heap<JSVal>>,
184 host_defined: RootedTraceableBox<Heap<JSVal>>,
185}
186
187fn load_state_from_handle_value<'a>(reference_private: Handle<'a, JSVal>) -> Option<&'a LoadState> {
188 if reference_private.get().is_undefined() {
189 return None;
190 }
191 unsafe { (reference_private.get().to_private() as *const LoadState).as_ref() }
192}
193
194pub(crate) fn load_requested_modules(
195 cx: &mut CurrentRealm,
196 module: Handle<*mut JSObject>,
197 load_state: Box<LoadState>,
198) {
199 rooted!(&in(cx) let host_defined = PrivateValue(Box::into_raw(load_state) as *const _ as *const c_void));
200
201 unsafe {
202 let on_resolved = NewFunctionWithReserved(
203 cx,
204 Some(on_load_requested_modules_resolved),
205 0,
206 0,
207 ptr::null(),
208 );
209 let on_rejected = NewFunctionWithReserved(
210 cx,
211 Some(on_load_requested_modules_rejected),
212 1,
213 0,
214 ptr::null(),
215 );
216
217 rooted!(&in(cx) let resolved_function_object = JS_GetFunctionObject(on_resolved));
218 SetFunctionNativeReserved(
219 resolved_function_object.get(),
220 LOAD_REACTION_HOST_DEFINED_SLOT,
221 host_defined.handle().as_ref(cx),
222 );
223
224 rooted!(&in(cx) let rejected_function_object = JS_GetFunctionObject(on_rejected));
225 SetFunctionNativeReserved(
226 rejected_function_object.get(),
227 LOAD_REACTION_HOST_DEFINED_SLOT,
228 host_defined.handle().as_ref(cx),
229 );
230
231 rooted!(&in(cx) let mut promise_obj = ptr::null_mut::<JSObject>());
232 assert!(LoadRequestedModules1(
233 cx,
234 module,
235 host_defined.handle(),
236 promise_obj.handle_mut(),
237 ));
238
239 AddPromiseReactions(
240 cx,
241 promise_obj.handle(),
242 resolved_function_object.handle(),
243 rejected_function_object.handle(),
244 );
245 }
246}
247
248fn finish_loading_imported_module(
250 cx: &mut CurrentRealm,
251 referrer: Handle<*mut JSScript>,
252 module_request: Handle<*mut JSObject>,
253 payload: Handle<JSVal>,
254 result: Result<Rc<ModuleTree>, RethrowError>,
255) {
256 match result {
257 Ok(module_tree) => {
258 let module_handle = module_tree
259 .get_record()
260 .map(|module| module.handle())
261 .unwrap();
262
263 if payload.is_object() {
264 rooted!(&in(cx) let object = payload.to_object());
265 let is_promise = unsafe { IsPromiseObject(object.handle()) };
266
267 if is_promise {
268 unsafe {
269 FinishLoadingDynamicImportedModule(
270 cx,
271 referrer,
272 module_request,
273 payload,
274 module_handle,
275 )
276 };
277 let promise = Promise::new_with_js_promise(cx, object.handle());
278 let record = ModuleObject::new(module_handle);
279 return continue_dynamic_import(cx, promise, record);
280 }
281 }
282
283 assert!(unsafe {
284 FinishLoadingImportedModule(
285 cx,
286 referrer,
287 module_request,
288 payload,
289 module_handle,
290 true,
291 )
292 });
293 },
294 Err(error) => {
295 unsafe { FinishLoadingImportedModuleFailed(cx, payload, error.handle()) };
296 },
297 }
298}
299
300fn continue_dynamic_import(realm: &mut CurrentRealm, promise: Rc<Promise>, module: ModuleObject) {
302 let global = GlobalScope::from_current_realm(realm);
308
309 rooted!(&in(*realm) let host_defined = UndefinedValue());
312 rooted!(&in(*realm) let mut promise_obj = ptr::null_mut::<JSObject>());
313
314 unsafe {
316 LoadRequestedModules1(
317 realm,
318 module.handle(),
319 host_defined.handle(),
320 promise_obj.handle_mut(),
321 )
322 };
323
324 let load_promise = Promise::new_with_js_promise(realm, promise_obj.handle());
325
326 let global_scope = global.clone();
332 let inner_promise = promise.clone();
333 let fulfilled_promise = promise.clone();
334
335 let link_and_evaluate = ModuleHandler::new_boxed(Box::new(
339 task!(link_and_evaluate: |cx, global_scope: DomRoot<GlobalScope>, inner_promise: Rc<Promise>, module: ModuleObject| {
340 let mut realm = enter_auto_realm(cx, &*global_scope);
341 let cx = &mut realm.current_realm();
342
343 let link = unsafe { ModuleLink(cx, module.handle()) };
345
346 if !link {
348 let exception = RethrowError::from_pending_exception(cx);
350 inner_promise.reject(cx, exception.handle());
351
352 return;
354 }
355
356 rooted!(&in(cx) let mut rval = UndefinedValue());
357
358 assert!(unsafe { ModuleEvaluate(cx, module.handle(), rval.handle_mut()) });
360
361 if !rval.is_object() {
362 let error = RethrowError::from_pending_exception(cx);
363 return inner_promise.reject(cx, error.handle());
364 }
365
366 rooted!(&in(cx) let evaluate_promise = rval.to_object());
367 let evaluate_promise = Promise::new_with_js_promise(cx, evaluate_promise.handle());
368
369 let on_fulfilled = ModuleHandler::new_boxed(Box::new(
373 task!(on_fulfilled: |cx, fulfilled_promise: Rc<Promise>, module: ModuleObject| {
374
375 rooted!(&in(cx) let rval = unsafe { GetModuleNamespace(cx, module.handle()) });
377 rooted!(&in(cx) let namespace = ObjectValue(rval.get()));
378
379 fulfilled_promise.resolve(cx, namespace.handle());
381
382 })));
384
385 let handler = PromiseNativeHandler::new(
387 cx,
388 &global_scope,
389 Some(on_fulfilled),
390 Some(Box::new(OnRejectedHandler { promise: inner_promise }))
391 );
392 evaluate_promise.append_native_handler(cx, &handler);
393
394 }),
396 ));
397
398 run_a_callback::<DomTypeHolder, _>(&*global, || {
399 let handler = PromiseNativeHandler::new(
401 realm,
402 &global,
403 Some(link_and_evaluate),
404 Some(Box::new(OnRejectedHandler { promise })),
405 );
406 load_promise.append_native_handler(realm, &handler);
407 });
408 }
410
411pub(crate) fn host_load_imported_module(
413 cx: &mut CurrentRealm,
414 referrer: Handle<*mut JSScript>,
415 module_request: Handle<*mut JSObject>,
416 specifier: String,
417 host_defined: Handle<JSVal>,
418 payload: Handle<JSVal>,
419) {
420 let mut realm = CurrentRealm::assert(cx);
422 let mut global_scope = GlobalScope::from_current_realm(&mut realm);
423
424 let load_state = load_state_from_handle_value(host_defined);
425
426 rooted!(&in(cx) let mut script_private = UndefinedValue());
430
431 unsafe { JS_GetScriptPrivate(*referrer.as_ref(cx), script_private.handle_mut()) };
433 let referencing_script =
434 unsafe { module_script_from_reference_private(script_private.handle()) };
435
436 let (original_fetch_options, fetch_referrer) = match referencing_script {
438 Some(module) => (
439 module.options.clone(),
441 Referrer::ReferrerUrl(module.base_url.clone()),
443 ),
444 None => (
445 ScriptFetchOptions::default_classic_script(),
447 global_scope.get_referrer(),
449 ),
450 };
451
452 let script_owner = referencing_script.and_then(|script| script.owner.clone());
454
455 if let Some(ref owner) = script_owner {
457 global_scope = owner.root();
458 }
459
460 let global = &global_scope.clone();
461
462 let module_type = unsafe { GetModuleRequestType(cx, module_request) };
470
471 if let ModuleType::Unknown = module_type {
473 let error = gen_type_error(
475 cx,
476 global,
477 Error::Type(c"Found invalid module type attribute".to_owned()),
478 );
479
480 if let Some(load_state) = load_state {
483 load_state
484 .error_to_rethrow
485 .borrow_mut()
486 .get_or_insert(error.clone());
487 }
488
489 finish_loading_imported_module(cx, referrer, module_request, payload, Err(error));
491
492 return;
494 }
495
496 let url = ModuleTree::resolve_module_specifier(global, referencing_script, specifier);
499
500 if let Err(error) = url {
502 let resolution_error = gen_type_error(cx, &global_scope, error);
503
504 load_state.as_ref().inspect(|load_state| {
507 load_state
508 .error_to_rethrow
509 .borrow_mut()
510 .get_or_insert(resolution_error.clone());
511 });
512
513 finish_loading_imported_module(
515 cx,
516 referrer,
517 module_request,
518 payload,
519 Err(resolution_error),
520 );
521
522 return;
524 };
525
526 let url = ensure_blob_referenced_by_url_is_kept_alive(global, url.unwrap());
527
528 let fetch_options = original_fetch_options.descendant_fetch_options(&url.url(), &global_scope);
531
532 let (destination, fetch_client) = match load_state {
535 Some(load_state) => (load_state.destination, load_state.fetch_client.clone()),
538 None => (
539 Destination::Script,
541 global_scope.request_client(Some(cx.no_gc())),
543 ),
544 };
545
546 let request = ImportRequest {
547 referrer: RootedTraceableBox::from_box(Heap::boxed(*referrer.as_ref(cx))),
548 module_request: RootedTraceableBox::from_box(Heap::boxed(*module_request.as_ref(cx))),
549 payload: RootedTraceableBox::from_box(Heap::boxed(*payload.as_ref(cx))),
550 host_defined: RootedTraceableBox::from_box(Heap::boxed(*host_defined.as_ref(cx))),
551 };
552
553 let on_single_fetch_complete =
554 move |cx: &mut JSContext, module_tree: Option<Rc<ModuleTree>>| {
555 let mut realm = CurrentRealm::assert(cx);
556 let cx = &mut realm;
557
558 let completion = match module_tree {
560 None => Err(gen_type_error(
562 cx,
563 &global_scope,
564 Error::Type(c"Module fetching failed".to_owned()),
565 )),
566 Some(module_tree) => {
567 if let Some(parse_error) = module_tree.get_parse_error() {
570 let load_state =
573 load_state_from_handle_value(request.host_defined.handle());
574 load_state.inspect(|load_state| {
575 load_state
576 .error_to_rethrow
577 .borrow_mut()
578 .get_or_insert(parse_error.clone());
579 });
580
581 Err(parse_error.clone())
583 } else {
584 Ok(module_tree)
586 }
587 },
588 };
589
590 finish_loading_imported_module(
592 cx,
593 request.referrer.handle(),
594 request.module_request.handle(),
595 request.payload.handle(),
596 completion,
597 );
598 };
599
600 fetch_a_single_imported_module_script(
605 cx,
606 url,
607 fetch_client,
608 global,
609 destination,
610 fetch_options,
611 fetch_referrer,
612 module_type,
613 on_single_fetch_complete,
614 );
615}
616
617#[expect(clippy::too_many_arguments)]
619fn fetch_a_single_imported_module_script(
620 cx: &mut JSContext,
621 url: UrlWithBlobClaim,
622 fetch_client: RequestClient,
623 global: &GlobalScope,
624 destination: Destination,
625 options: ScriptFetchOptions,
626 referrer: Referrer,
627 module_type: ModuleType,
628 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
629) {
630 match module_type {
638 ModuleType::Unknown | ModuleType::Bytes | ModuleType::Text | ModuleType::CSS => {
639 return on_complete(cx, None);
640 },
641 ModuleType::JavaScript | ModuleType::JSON => (),
642 }
643
644 fetch_a_single_module_script(
647 cx,
648 url,
649 fetch_client,
650 global,
651 destination,
652 options,
653 referrer,
654 Some(module_type),
655 false,
656 Some(IntroductionType::IMPORTED_MODULE),
657 on_complete,
658 );
659}