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::Promise;
42use crate::dom::promise::promisenativehandler::{Callback, PromiseNativeHandler};
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)]
53struct OnRejectedHandler {
54 #[conditional_malloc_size_of]
55 promise: Rc<Promise>,
56}
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: Rc<Promise>, 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: Rc<Promise>, 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: Rc<Promise>, 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 let handler = PromiseNativeHandler::new(
348 cx,
349 &global_scope,
350 Some(on_fulfilled),
351 Some(Box::new(OnRejectedHandler { promise: inner_promise }))
352 );
353 evaluate_promise.append_native_handler(cx, &handler);
354
355 }),
357 ));
358
359 run_a_callback::<DomTypeHolder, _>(&*global, || {
360 let handler = PromiseNativeHandler::new(
362 realm,
363 &global,
364 Some(link_and_evaluate),
365 Some(Box::new(OnRejectedHandler { promise })),
366 );
367 load_promise.append_native_handler(realm, &handler);
368 });
369 }
371
372pub(crate) fn host_load_imported_module(
374 cx: &mut CurrentRealm,
375 referrer: Handle<*mut JSScript>,
376 module_request: Handle<*mut JSObject>,
377 specifier: String,
378 host_defined: Handle<JSVal>,
379 payload: Handle<JSVal>,
380) {
381 let mut realm = CurrentRealm::assert(cx);
383 let mut global_scope = GlobalScope::from_current_realm(&mut realm);
384
385 let load_state = load_state_from_handle_value(host_defined);
386
387 rooted!(&in(cx) let mut script_private = UndefinedValue());
391
392 unsafe { JS_GetScriptPrivate(*referrer.as_ref(cx), script_private.handle_mut()) };
394 let referencing_script =
395 unsafe { module_script_from_reference_private(script_private.handle()) };
396
397 let (original_fetch_options, fetch_referrer) = match referencing_script {
399 Some(module) => (
400 module.options.clone(),
402 Referrer::ReferrerUrl(module.base_url.clone()),
404 ),
405 None => (
406 ScriptFetchOptions::default_classic_script(),
408 global_scope.get_referrer(),
410 ),
411 };
412
413 let script_owner = referencing_script.and_then(|script| script.owner.clone());
415
416 if let Some(ref owner) = script_owner {
418 global_scope = owner.root();
419 }
420
421 let module_type = unsafe { GetModuleRequestType(cx, module_request) };
429
430 if !module_type_allowed(&global_scope, module_type) {
432 throw_type_error(cx, c"Found invalid module type attribute");
434
435 unsafe { FinishLoadingImportedModuleFailedWithPendingException(cx, payload) };
441
442 return;
444 }
445
446 let url = ModuleTree::resolve_module_specifier(&global_scope, referencing_script, specifier);
449
450 if let Err(resolution_error) = url {
452 throw_dom_exception(cx, &global_scope, resolution_error);
453
454 unsafe { FinishLoadingImportedModuleFailedWithPendingException(cx, payload) };
460
461 return;
463 };
464
465 let url = ensure_blob_referenced_by_url_is_kept_alive(&global_scope, url.unwrap());
466
467 let fetch_options = original_fetch_options.descendant_fetch_options(&url.url(), &global_scope);
470
471 let (destination, fetch_client) = match load_state {
474 Some(load_state) => (load_state.destination, load_state.fetch_client.clone()),
477 None => (
478 Destination::Script,
480 global_scope.request_client(Some(cx.no_gc())),
482 ),
483 };
484
485 let is_dynamic_import = load_state.is_none();
486 let request = ImportRequest {
487 referrer: RootedTraceableBox::from_box(Heap::boxed(*referrer.as_ref(cx))),
488 module_request: RootedTraceableBox::from_box(Heap::boxed(*module_request.as_ref(cx))),
489 payload: RootedTraceableBox::from_box(Heap::boxed(*payload.as_ref(cx))),
490 };
491
492 let on_single_fetch_complete =
493 move |cx: &mut JSContext, module_tree: Option<Rc<ModuleTree>>| {
494 let mut realm = CurrentRealm::assert(cx);
495 let cx = &mut realm;
496
497 match module_tree {
500 None => {
502 if is_dynamic_import {
503 throw_type_error(cx, c"Module fetching failed");
504 unsafe {
505 FinishLoadingImportedModuleFailedWithPendingException(
506 cx,
507 request.payload.handle(),
508 )
509 };
510 } else {
511 rooted!(&in(cx) let error = UndefinedValue());
515 unsafe {
516 FinishLoadingImportedModuleFailed(
517 cx,
518 request.payload.handle(),
519 error.handle(),
520 )
521 };
522 }
523 },
524 Some(module_tree) => {
525 if let Some(parse_error) = module_tree.get_parse_error() {
528 unsafe {
535 FinishLoadingImportedModuleFailed(
536 cx,
537 request.payload.handle(),
538 parse_error.handle(),
539 )
540 };
541 } else {
542 let completion = module_tree
544 .get_record()
545 .map(|module| module.handle())
546 .unwrap();
547
548 finish_loading_imported_module(
550 cx,
551 request.referrer.handle(),
552 request.module_request.handle(),
553 request.payload.handle(),
554 completion,
555 );
556 }
557 },
558 }
559 };
560
561 fetch_a_single_imported_module_script(
566 cx,
567 url,
568 fetch_client,
569 &global_scope,
570 destination,
571 fetch_options,
572 fetch_referrer,
573 module_type,
574 on_single_fetch_complete,
575 );
576}
577
578fn module_type_allowed(global: &GlobalScope, module_type: ModuleType) -> bool {
580 match module_type {
581 ModuleType::Unknown | ModuleType::Bytes => false,
584 ModuleType::CSS => global.is::<Window>(),
587 ModuleType::JavaScript | ModuleType::JSON | ModuleType::Text => true,
589 }
590}
591
592#[expect(clippy::too_many_arguments)]
594fn fetch_a_single_imported_module_script(
595 cx: &mut JSContext,
596 url: UrlWithBlobClaim,
597 fetch_client: RequestClient,
598 global: &GlobalScope,
599 destination: Destination,
600 options: ScriptFetchOptions,
601 referrer: Referrer,
602 module_type: ModuleType,
603 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
604) {
605 match module_type {
613 ModuleType::Unknown | ModuleType::Bytes => {
614 return on_complete(cx, None);
615 },
616 ModuleType::JavaScript | ModuleType::JSON | ModuleType::CSS | ModuleType::Text => (),
617 }
618
619 fetch_a_single_module_script(
622 cx,
623 url,
624 fetch_client,
625 global,
626 destination,
627 options,
628 referrer,
629 Some(module_type),
630 false,
631 Some(IntroductionType::IMPORTED_MODULE),
632 on_complete,
633 );
634}