1#![expect(dead_code)]
9
10use core::ffi::c_char;
11use std::cell::{Cell, LazyCell, RefCell};
12use std::collections::{HashMap, HashSet};
13use std::ffi::{CStr, CString};
14use std::io::{Write, stdout};
15use std::ops::{Deref, DerefMut};
16use std::os::raw::c_void;
17use std::ptr::NonNull;
18use std::rc::{Rc, Weak};
19use std::time::Instant;
20use std::{os, ptr};
21
22use background_hang_monitor_api::ScriptHangAnnotation;
23use js::context::JSContext;
24use js::conversions::jsstr_to_string;
25use js::gc::StackGCVector;
26use js::glue::{
27 CreateJobQueue, DeleteJobQueue, DispatchablePointer, JS_GetReservedSlot, JobQueueTraps,
28 RUST_js_GetErrorMessage, RegisterScriptEnvironmentPreparer,
29 RunScriptEnvironmentPreparerClosure, SetBuildId, StreamConsumerConsumeChunk,
30 StreamConsumerNoteResponseURLs, StreamConsumerStreamEnd, StreamConsumerStreamError,
31};
32use js::jsapi::{
33 AsmJSOption, BuildIdCharVector, CompilationType, Dispatchable_MaybeShuttingDown, GCDescription,
34 GCOptions, GCProgress, GCReason, GetPromiseUserInputEventHandlingState, Handle as RawHandle,
35 HandleObject, HandleString, HandleValue as RawHandleValue, Heap, JS_SetReservedSlot,
36 JSCLASS_RESERVED_SLOTS_MASK, JSCLASS_RESERVED_SLOTS_SHIFT, JSClass, JSClassOps,
37 JSContext as RawJSContext, JSGCParamKey, JSGCStatus, JSJitCompilerOption, JSObject,
38 JSSecurityCallbacks, JSString, JSTracer, JobQueue, MimeType, MutableHandleObject,
39 MutableHandleString, PromiseRejectionHandlingState, PromiseUserInputEventHandlingState,
40 RuntimeCode, ScriptEnvironmentPreparer_Closure, SetProcessBuildIdOp,
41 StreamConsumer as JSStreamConsumer,
42};
43use js::jsval::{JSVal, ObjectValue, UndefinedValue};
44use js::panic::wrap_panic;
45use js::realm::CurrentRealm;
46pub(crate) use js::rust::ThreadSafeJSContext;
47use js::rust::wrappers2::{
48 CollectServoSizes, ContextOptionsRef, DispatchableRun, InitConsumeStreamCallback,
49 JS_AddExtraGCRootsTracer, JS_GetPromiseResult, JS_InitDestroyPrincipalsCallback,
50 JS_InitReadPrincipalsCallback, JS_NewObject, JS_NewStringCopyUTF8N, JS_SetGCCallback,
51 JS_SetGCParameter, JS_SetGlobalJitCompilerOption, JS_SetOffthreadIonCompilationEnabled,
52 JS_SetSecurityCallbacks, SetDOMCallbacks, SetGCSliceCallback, SetJobQueue,
53 SetPreserveWrapperCallbacks, SetPromiseRejectionTrackerCallback, SetUpEventLoopDispatch,
54};
55use js::rust::{
56 Handle, HandleObject as RustHandleObject, HandleValue, IntoHandle, ParentRuntime,
57 Runtime as RustRuntime, Trace,
58};
59use malloc_size_of::MallocSizeOfOps;
60use malloc_size_of_derive::MallocSizeOf;
61use profile_traits::mem::{Report, ReportKind};
62use profile_traits::path;
63use profile_traits::time::ProfilerCategory;
64use script_bindings::reflector::DomObject;
65use script_bindings::script_runtime::{mark_runtime_dead, runtime_is_alive, temp_cx};
66use script_bindings::settings_stack::run_a_script;
67use servo_config::opts::{self, DiagnosticsLoggingOption};
68use servo_config::pref;
69use servo_url::ServoUrl;
70use style::thread_state::{self, ThreadState};
71
72use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
73use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
74use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
75use crate::dom::bindings::codegen::UnionTypes::TrustedScriptOrString;
76use crate::dom::bindings::conversions::{
77 get_dom_class, private_from_object, root_from_handleobject, root_from_object,
78};
79use crate::dom::bindings::error::{Error, report_pending_exception, throw_dom_exception};
80use crate::dom::bindings::inheritance::Castable;
81use crate::dom::bindings::refcounted::{
82 LiveDOMReferences, Trusted, TrustedPromise, trace_refcounted_objects,
83};
84use crate::dom::bindings::reflector::DomGlobal;
85use crate::dom::bindings::root::trace_roots;
86use crate::dom::bindings::str::DOMString;
87use crate::dom::bindings::utils::DOM_CALLBACKS;
88use crate::dom::bindings::{principals, settings_stack};
89use crate::dom::console::stringify_handle_value;
90use crate::dom::csp::CspReporting;
91use crate::dom::event::{Event, EventBubbles, EventCancelable};
92use crate::dom::eventtarget::EventTarget;
93use crate::dom::globalscope::GlobalScope;
94use crate::dom::promise::Promise;
95use crate::dom::promiserejectionevent::PromiseRejectionEvent;
96use crate::dom::response::Response;
97use crate::dom::trustedtypes::trustedscript::TrustedScript;
98use crate::engine::handle::current_js_engine_handle;
99use crate::messaging::{CommonScriptMsg, ScriptEventLoopSender};
100use crate::modules::script_module::EnsureModuleHooksInitialized;
101use crate::realms::enter_auto_realm;
102use crate::runtime::microtask::{EnqueuedPromiseCallback, MicrotaskQueue};
103use crate::tasks::task_source::TaskSourceName;
104use crate::{DomTypeHolder, ScriptThread};
105
106static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps {
107 getHostDefinedData: Some(get_host_defined_data),
108 enqueuePromiseJob: Some(enqueue_promise_job),
109 runJobs: Some(run_jobs),
110 empty: Some(empty),
111 pushNewInterruptQueue: Some(push_new_interrupt_queue),
112 popInterruptQueue: Some(pop_interrupt_queue),
113 dropInterruptQueues: Some(drop_interrupt_queues),
114};
115
116static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks {
117 contentSecurityPolicyAllows: Some(content_security_policy_allows),
118 codeForEvalGets: Some(code_for_eval_gets),
119 subsumes: Some(principals::subsumes),
120};
121
122#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
123pub(crate) enum ScriptThreadEventCategory {
124 SpawnPipeline,
125 ConstellationMsg,
126 DatabaseAccessEvent,
127 DevtoolsMsg,
128 DocumentEvent,
129 FileRead,
130 FontLoading,
131 FormPlannedNavigation,
132 GeolocationEvent,
133 ImageCacheMsg,
134 InputEvent,
135 NavigationAndTraversalEvent,
136 NetworkEvent,
137 PortMessage,
138 Rendering,
139 Resize,
140 ScriptEvent,
141 SetScrollState,
142 SetViewport,
143 StylesheetLoad,
144 TimerEvent,
145 UpdateReplacedElement,
146 WebSocketEvent,
147 WorkerEvent,
148 WorkletEvent,
149 ServiceWorkerEvent,
150 EnterFullscreen,
151 ExitFullscreen,
152 PerformanceTimelineTask,
153 #[cfg(feature = "webgpu")]
154 WebGPUMsg,
155}
156
157impl From<ScriptThreadEventCategory> for ProfilerCategory {
158 fn from(category: ScriptThreadEventCategory) -> Self {
159 match category {
160 ScriptThreadEventCategory::SpawnPipeline => ProfilerCategory::ScriptSpawnPipeline,
161 ScriptThreadEventCategory::ConstellationMsg => ProfilerCategory::ScriptConstellationMsg,
162 ScriptThreadEventCategory::DatabaseAccessEvent => {
163 ProfilerCategory::ScriptDatabaseAccessEvent
164 },
165 ScriptThreadEventCategory::DevtoolsMsg => ProfilerCategory::ScriptDevtoolsMsg,
166 ScriptThreadEventCategory::DocumentEvent => ProfilerCategory::ScriptDocumentEvent,
167 ScriptThreadEventCategory::EnterFullscreen => ProfilerCategory::ScriptEnterFullscreen,
168 ScriptThreadEventCategory::ExitFullscreen => ProfilerCategory::ScriptExitFullscreen,
169 ScriptThreadEventCategory::FileRead => ProfilerCategory::ScriptFileRead,
170 ScriptThreadEventCategory::FontLoading => ProfilerCategory::ScriptFontLoading,
171 ScriptThreadEventCategory::FormPlannedNavigation => {
172 ProfilerCategory::ScriptPlannedNavigation
173 },
174 ScriptThreadEventCategory::GeolocationEvent => ProfilerCategory::ScriptGeolocationEvent,
175 ScriptThreadEventCategory::NavigationAndTraversalEvent => {
176 ProfilerCategory::ScriptNavigationAndTraversalEvent
177 },
178 ScriptThreadEventCategory::ImageCacheMsg => ProfilerCategory::ScriptImageCacheMsg,
179 ScriptThreadEventCategory::InputEvent => ProfilerCategory::ScriptInputEvent,
180 ScriptThreadEventCategory::NetworkEvent => ProfilerCategory::ScriptNetworkEvent,
181 ScriptThreadEventCategory::PerformanceTimelineTask => {
182 ProfilerCategory::ScriptPerformanceEvent
183 },
184 ScriptThreadEventCategory::PortMessage => ProfilerCategory::ScriptPortMessage,
185 ScriptThreadEventCategory::Resize => ProfilerCategory::ScriptResize,
186 ScriptThreadEventCategory::Rendering => ProfilerCategory::ScriptRendering,
187 ScriptThreadEventCategory::ScriptEvent => ProfilerCategory::ScriptEvent,
188 ScriptThreadEventCategory::ServiceWorkerEvent => {
189 ProfilerCategory::ScriptServiceWorkerEvent
190 },
191 ScriptThreadEventCategory::SetScrollState => ProfilerCategory::ScriptSetScrollState,
192 ScriptThreadEventCategory::SetViewport => ProfilerCategory::ScriptSetViewport,
193 ScriptThreadEventCategory::StylesheetLoad => ProfilerCategory::ScriptStylesheetLoad,
194 ScriptThreadEventCategory::TimerEvent => ProfilerCategory::ScriptTimerEvent,
195 ScriptThreadEventCategory::UpdateReplacedElement => {
196 ProfilerCategory::ScriptUpdateReplacedElement
197 },
198 ScriptThreadEventCategory::WebSocketEvent => ProfilerCategory::ScriptWebSocketEvent,
199 ScriptThreadEventCategory::WorkerEvent => ProfilerCategory::ScriptWorkerEvent,
200 ScriptThreadEventCategory::WorkletEvent => ProfilerCategory::ScriptWorkletEvent,
201 #[cfg(feature = "webgpu")]
202 ScriptThreadEventCategory::WebGPUMsg => ProfilerCategory::ScriptWebGPUMsg,
203 }
204 }
205}
206
207impl From<ScriptThreadEventCategory> for ScriptHangAnnotation {
208 fn from(category: ScriptThreadEventCategory) -> Self {
209 match category {
210 ScriptThreadEventCategory::SpawnPipeline => ScriptHangAnnotation::SpawnPipeline,
211 ScriptThreadEventCategory::ConstellationMsg => ScriptHangAnnotation::ConstellationMsg,
212 ScriptThreadEventCategory::DatabaseAccessEvent => {
213 ScriptHangAnnotation::DatabaseAccessEvent
214 },
215 ScriptThreadEventCategory::DevtoolsMsg => ScriptHangAnnotation::DevtoolsMsg,
216 ScriptThreadEventCategory::DocumentEvent => ScriptHangAnnotation::DocumentEvent,
217 ScriptThreadEventCategory::InputEvent => ScriptHangAnnotation::InputEvent,
218 ScriptThreadEventCategory::FileRead => ScriptHangAnnotation::FileRead,
219 ScriptThreadEventCategory::FontLoading => ScriptHangAnnotation::FontLoading,
220 ScriptThreadEventCategory::FormPlannedNavigation => {
221 ScriptHangAnnotation::FormPlannedNavigation
222 },
223 ScriptThreadEventCategory::GeolocationEvent => ScriptHangAnnotation::GeolocationEvent,
224 ScriptThreadEventCategory::NavigationAndTraversalEvent => {
225 ScriptHangAnnotation::NavigationAndTraversalEvent
226 },
227 ScriptThreadEventCategory::ImageCacheMsg => ScriptHangAnnotation::ImageCacheMsg,
228 ScriptThreadEventCategory::NetworkEvent => ScriptHangAnnotation::NetworkEvent,
229 ScriptThreadEventCategory::Rendering => ScriptHangAnnotation::Rendering,
230 ScriptThreadEventCategory::Resize => ScriptHangAnnotation::Resize,
231 ScriptThreadEventCategory::ScriptEvent => ScriptHangAnnotation::ScriptEvent,
232 ScriptThreadEventCategory::SetScrollState => ScriptHangAnnotation::SetScrollState,
233 ScriptThreadEventCategory::SetViewport => ScriptHangAnnotation::SetViewport,
234 ScriptThreadEventCategory::StylesheetLoad => ScriptHangAnnotation::StylesheetLoad,
235 ScriptThreadEventCategory::TimerEvent => ScriptHangAnnotation::TimerEvent,
236 ScriptThreadEventCategory::UpdateReplacedElement => {
237 ScriptHangAnnotation::UpdateReplacedElement
238 },
239 ScriptThreadEventCategory::WebSocketEvent => ScriptHangAnnotation::WebSocketEvent,
240 ScriptThreadEventCategory::WorkerEvent => ScriptHangAnnotation::WorkerEvent,
241 ScriptThreadEventCategory::WorkletEvent => ScriptHangAnnotation::WorkletEvent,
242 ScriptThreadEventCategory::ServiceWorkerEvent => {
243 ScriptHangAnnotation::ServiceWorkerEvent
244 },
245 ScriptThreadEventCategory::EnterFullscreen => ScriptHangAnnotation::EnterFullscreen,
246 ScriptThreadEventCategory::ExitFullscreen => ScriptHangAnnotation::ExitFullscreen,
247 ScriptThreadEventCategory::PerformanceTimelineTask => {
248 ScriptHangAnnotation::PerformanceTimelineTask
249 },
250 ScriptThreadEventCategory::PortMessage => ScriptHangAnnotation::PortMessage,
251 #[cfg(feature = "webgpu")]
252 ScriptThreadEventCategory::WebGPUMsg => ScriptHangAnnotation::WebGPUMsg,
253 }
254 }
255}
256
257static HOST_DEFINED_DATA: JSClassOps = JSClassOps {
258 addProperty: None,
259 delProperty: None,
260 enumerate: None,
261 newEnumerate: None,
262 resolve: None,
263 mayResolve: None,
264 finalize: None,
265 call: None,
266 construct: None,
267 trace: None,
268};
269
270static HOST_DEFINED_DATA_CLASS: JSClass = JSClass {
271 name: c"HostDefinedData".as_ptr(),
272 flags: (HOST_DEFINED_DATA_SLOTS & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT,
273 cOps: &HOST_DEFINED_DATA,
274 spec: ptr::null(),
275 ext: ptr::null(),
276 oOps: ptr::null(),
277};
278
279const INCUMBENT_SETTING_SLOT: u32 = 0;
280const HOST_DEFINED_DATA_SLOTS: u32 = 1;
281
282#[expect(unsafe_code)]
284unsafe extern "C" fn get_host_defined_data(
285 _: *const c_void,
286 cx: *mut RawJSContext,
287 data: MutableHandleObject,
288) -> bool {
289 let mut cx = unsafe {
290 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
292 };
293 wrap_panic(&mut || {
294 let Some(incumbent_global) = GlobalScope::incumbent() else {
295 data.set(ptr::null_mut());
296 return;
297 };
298
299 let mut realm = enter_auto_realm(&mut cx, &*incumbent_global);
300 let cx = &mut realm.current_realm();
301
302 rooted!(&in(cx) let result = unsafe { JS_NewObject(cx, &HOST_DEFINED_DATA_CLASS)});
303 assert!(!result.is_null());
304
305 unsafe {
306 JS_SetReservedSlot(
307 *result,
308 INCUMBENT_SETTING_SLOT,
309 &ObjectValue(*incumbent_global.reflector().get_jsobject()),
310 )
311 };
312
313 data.set(result.get());
314 });
315 true
316}
317
318#[expect(unsafe_code)]
319unsafe extern "C" fn run_jobs(microtask_queue: *const c_void, cx: *mut RawJSContext) {
320 let mut cx = unsafe {
321 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
323 };
324 wrap_panic(&mut || {
325 let microtask_queue = unsafe { &*(microtask_queue as *const MicrotaskQueue) };
326 microtask_queue.checkpoint(&mut cx, vec![]);
329 });
330}
331
332#[expect(unsafe_code)]
333unsafe extern "C" fn empty(extra: *const c_void) -> bool {
334 let mut result = false;
335 wrap_panic(&mut || {
336 let microtask_queue = unsafe { &*(extra as *const MicrotaskQueue) };
337 result = microtask_queue.empty()
338 });
339 result
340}
341
342#[expect(unsafe_code)]
343unsafe extern "C" fn push_new_interrupt_queue(interrupt_queues: *mut c_void) -> *const c_void {
344 let mut result = std::ptr::null();
345 wrap_panic(&mut || {
346 let mut interrupt_queues =
347 unsafe { Box::from_raw(interrupt_queues as *mut Vec<Rc<MicrotaskQueue>>) };
348 let new_queue = Rc::new(MicrotaskQueue::default());
349 result = Rc::as_ptr(&new_queue) as *const c_void;
350 interrupt_queues.push(new_queue);
351 std::mem::forget(interrupt_queues);
352 });
353 result
354}
355
356#[expect(unsafe_code)]
357unsafe extern "C" fn pop_interrupt_queue(interrupt_queues: *mut c_void) -> *const c_void {
358 let mut result = std::ptr::null();
359 wrap_panic(&mut || {
360 let mut interrupt_queues =
361 unsafe { Box::from_raw(interrupt_queues as *mut Vec<Rc<MicrotaskQueue>>) };
362 let popped_queue: Rc<MicrotaskQueue> =
363 interrupt_queues.pop().expect("Guaranteed by SpiderMonkey?");
364 result = Rc::as_ptr(&popped_queue) as *const c_void;
366 std::mem::forget(interrupt_queues);
367 });
368 result
369}
370
371#[expect(unsafe_code)]
372unsafe extern "C" fn drop_interrupt_queues(interrupt_queues: *mut c_void) {
373 wrap_panic(&mut || {
374 let interrupt_queues =
375 unsafe { Box::from_raw(interrupt_queues as *mut Vec<Rc<MicrotaskQueue>>) };
376 drop(interrupt_queues);
377 });
378}
379
380#[expect(unsafe_code)]
384unsafe extern "C" fn enqueue_promise_job(
385 extra: *const c_void,
386 cx: *mut RawJSContext,
387 promise: HandleObject,
388 job: HandleObject,
389 _allocation_site: HandleObject,
390 host_defined_data: HandleObject,
391) -> bool {
392 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
394 let cx = &mut cx;
395
396 let mut result = false;
397 wrap_panic(&mut || {
398 let microtask_queue = unsafe { &*(extra as *const MicrotaskQueue) };
399 let global = if !host_defined_data.is_null() {
400 let mut incumbent_global = UndefinedValue();
401 unsafe {
402 JS_GetReservedSlot(
403 host_defined_data.get(),
404 INCUMBENT_SETTING_SLOT,
405 &mut incumbent_global,
406 );
407 GlobalScope::from_object(incumbent_global.to_object())
408 }
409 } else {
410 let mut realm = CurrentRealm::assert(cx);
411 GlobalScope::from_current_realm(&mut realm)
412 };
413 let interaction = if promise.get().is_null() {
414 PromiseUserInputEventHandlingState::DontCare
415 } else {
416 unsafe { GetPromiseUserInputEventHandlingState(promise) }
417 };
418 let is_user_interacting =
419 interaction == PromiseUserInputEventHandlingState::HadUserInteractionAtCreation;
420 microtask_queue.enqueue(
421 cx,
422 Box::new(EnqueuedPromiseCallback {
423 callback: unsafe { PromiseJobCallback::new(cx, job.get()) },
424 global: global.as_traced(),
425 is_user_interacting,
426 }),
427 );
428 result = true
429 });
430 result
431}
432
433#[expect(unsafe_code)]
434unsafe extern "C" fn promise_rejection_tracker(
436 cx: *mut RawJSContext,
437 muted_errors: bool,
438 promise: HandleObject,
439 state: PromiseRejectionHandlingState,
440 _data: *mut c_void,
441) {
442 if muted_errors {
445 return;
446 }
447
448 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
451 let mut realm = CurrentRealm::assert(&mut cx);
452
453 let global = GlobalScope::from_current_realm(&mut realm);
454 let cx = &mut realm;
455
456 wrap_panic(&mut || {
457 match state {
458 PromiseRejectionHandlingState::Unhandled => {
460 global.add_uncaught_rejection(promise);
461 },
462 PromiseRejectionHandlingState::Handled => {
464 if global
466 .get_uncaught_rejections()
467 .borrow()
468 .contains(&Heap::boxed(promise.get()))
469 {
470 global.remove_uncaught_rejection(promise);
471 return;
472 }
473
474 if !global
476 .get_consumed_rejections()
477 .borrow()
478 .contains(&Heap::boxed(promise.get()))
479 {
480 return;
481 }
482
483 global.remove_consumed_rejection(promise);
485
486 let target = Trusted::new(global.upcast::<EventTarget>());
487 let promise =
488 Promise::new_with_js_promise(cx, unsafe { Handle::from_raw(promise) });
489 let trusted_promise = TrustedPromise::new(promise);
490
491 global.task_manager().dom_manipulation_task_source().queue(
493 task!(rejection_handled_event: move |cx| {
494 let target = target.root();
495 let root_promise = trusted_promise.root();
496
497 rooted!(&in(cx) let mut reason = UndefinedValue());
498 unsafe {
499 JS_GetPromiseResult(root_promise.reflector().get_jsobject(), reason.handle_mut());
500 }
501
502 let event = PromiseRejectionEvent::new(
503 cx,
504 &target.global(),
505 atom!("rejectionhandled"),
506 EventBubbles::DoesNotBubble,
507 EventCancelable::Cancelable,
508 root_promise,
509 reason.handle(),
510 );
511
512 event.upcast::<Event>().fire(cx, &target);
513 })
514 );
515 },
516 };
517 })
518}
519
520#[expect(unsafe_code)]
521fn safely_convert_null_to_string(cx: &JSContext, str_: HandleString) -> DOMString {
522 DOMString::from(match std::ptr::NonNull::new(*str_) {
523 None => "".to_owned(),
524 Some(str_) => unsafe { jsstr_to_string(cx, str_) },
525 })
526}
527
528#[expect(unsafe_code)]
529unsafe extern "C" fn code_for_eval_gets(
530 cx: *mut RawJSContext,
531 code: HandleObject,
532 code_for_eval: MutableHandleString,
533) -> bool {
534 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
536 let cx = &mut cx;
537 if let Ok(trusted_script) = unsafe { root_from_object::<TrustedScript>(cx, code.get()) } {
538 let script_str = trusted_script.data().str();
539 let s = js::conversions::Utf8Chars::from(&*script_str);
540 let new_string = unsafe { JS_NewStringCopyUTF8N(cx, &*s as *const _) };
541 code_for_eval.set(new_string);
542 }
543 true
544}
545
546#[expect(unsafe_code)]
547unsafe extern "C" fn content_security_policy_allows(
548 cx: *mut RawJSContext,
549 runtime_code: RuntimeCode,
550 code_string: HandleString,
551 compilation_type: CompilationType,
552 parameter_strings: RawHandle<StackGCVector<*mut JSString>>,
553 body_string: HandleString,
554 parameter_args: RawHandle<StackGCVector<JSVal>>,
555 body_arg: RawHandleValue,
556 can_compile_strings: *mut bool,
557) -> bool {
558 let mut allowed = false;
559 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
561 let cx = &mut cx;
562 wrap_panic(&mut || {
563 let mut realm = CurrentRealm::assert(cx);
565 let global = GlobalScope::from_current_realm(&mut realm);
566 let csp_list = global.get_csp_list();
567
568 allowed = csp_list.is_none() ||
570 match runtime_code {
571 RuntimeCode::JS => {
572 let parameter_strings = unsafe { Handle::from_raw(parameter_strings) };
573 let parameter_strings_length = parameter_strings.len();
574 let mut parameter_strings_vec =
575 Vec::with_capacity(parameter_strings_length as usize);
576
577 for i in 0..parameter_strings_length {
578 let Some(str_) = parameter_strings.at(i) else {
579 unreachable!();
580 };
581 parameter_strings_vec.push(safely_convert_null_to_string(cx, str_.into()));
582 }
583
584 let parameter_args = unsafe { Handle::from_raw(parameter_args) };
585 let parameter_args_length = parameter_args.len();
586 let mut parameter_args_vec = Vec::with_capacity(parameter_args_length as usize);
587
588 for i in 0..parameter_args_length {
589 let Some(arg) = parameter_args.at(i) else {
590 unreachable!();
591 };
592 let value = arg.into_handle().get();
593 if value.is_object() {
594 if let Ok(trusted_script) =
595 unsafe { root_from_object::<TrustedScript>(cx, value.to_object()) }
596 {
597 parameter_args_vec
598 .push(TrustedScriptOrString::TrustedScript(trusted_script));
599 } else {
600 parameter_args_vec
604 .push(TrustedScriptOrString::String(DOMString::new()));
605 }
606 } else if value.is_string() {
607 parameter_args_vec
609 .push(TrustedScriptOrString::String(DOMString::new()));
610 } else {
611 unreachable!();
612 }
613 }
614
615 let code_string = safely_convert_null_to_string(cx, code_string);
616 let body_string = safely_convert_null_to_string(cx, body_string);
617
618 TrustedScript::can_compile_string_with_trusted_type(
619 cx,
620 &global,
621 code_string,
622 compilation_type,
623 parameter_strings_vec,
624 body_string,
625 parameter_args_vec,
626 unsafe { HandleValue::from_raw(body_arg) },
627 )
628 },
629 RuntimeCode::WASM => global
630 .get_csp_list()
631 .is_wasm_evaluation_allowed(cx, &global),
632 };
633 });
634 unsafe { *can_compile_strings = allowed };
635 true
636}
637
638#[expect(unsafe_code)]
639pub(crate) fn notify_about_rejected_promises(cx: &mut JSContext, global: &GlobalScope) {
641 let uncaught_rejections: Vec<TrustedPromise> = {
643 global
644 .get_uncaught_rejections()
645 .borrow()
646 .iter()
647 .map(|promise| {
648 let promise =
649 Promise::new_with_js_promise(cx, unsafe { Handle::from_raw(promise.handle()) });
650
651 TrustedPromise::new(promise)
652 })
653 .collect()
654 };
655 global.get_uncaught_rejections().safe_borrow_mut(cx).clear();
656
657 if uncaught_rejections.is_empty() {
659 return;
660 }
661
662 let target = Trusted::new(global.upcast::<EventTarget>());
667 global.task_manager().dom_manipulation_task_source().queue(
668 task!(unhandled_rejection_event: move |cx| {
669 let target = target.root();
670
671 for promise in uncaught_rejections {
673 let promise = promise.root();
674
675 if promise.get_promise_is_handled() {
677 continue;
678 }
679
680 rooted!(&in(cx) let mut reason = UndefinedValue());
684 unsafe {
685 JS_GetPromiseResult(promise.reflector().get_jsobject(), reason.handle_mut());
686 }
687
688 log::error!(
689 "Unhandled promise rejection: {}",
690 stringify_handle_value( cx, reason.handle())
691 );
692
693 let event = PromiseRejectionEvent::new(
694 cx,
695 &target.global(),
696 atom!("unhandledrejection"),
697 EventBubbles::DoesNotBubble,
698 EventCancelable::Cancelable,
699 promise.clone(),
700 reason.handle(),
701 );
702 event.upcast::<Event>().fire(cx, &target);
703
704 if !promise.get_promise_is_handled() {
710 target.global().add_consumed_rejection(promise.reflector().get_jsobject().into_handle());
711 }
712 }
713 })
714 );
715}
716
717#[derive(Default, JSTraceable, MallocSizeOf)]
720struct RuntimeCallbackData {
721 script_event_loop_sender: Option<ScriptEventLoopSender>,
722 #[no_trace]
723 #[ignore_malloc_size_of = "ScriptThread measures its own memory itself."]
724 script_thread: Option<Weak<ScriptThread>>,
725}
726
727#[derive(JSTraceable, MallocSizeOf)]
728pub(crate) struct Runtime {
729 #[ignore_malloc_size_of = "Type from mozjs"]
730 rt: RustRuntime,
731 #[conditional_malloc_size_of]
733 pub(crate) microtask_queue: Rc<MicrotaskQueue>,
734 #[ignore_malloc_size_of = "Type from mozjs"]
735 job_queue: *mut JobQueue,
736 runtime_callback_data: Box<RuntimeCallbackData>,
738}
739
740impl Runtime {
741 #[expect(unsafe_code)]
751 pub(crate) fn new(main_thread_sender: Option<ScriptEventLoopSender>) -> Runtime {
752 unsafe { Self::new_with_parent(None, main_thread_sender) }
753 }
754
755 #[allow(unsafe_code)]
756 pub(crate) unsafe fn cx(&self) -> JSContext {
760 unsafe { JSContext::from_ptr(RustRuntime::get().unwrap()) }
761 }
762
763 #[expect(unsafe_code)]
776 pub(crate) unsafe fn new_with_parent(
777 parent: Option<ParentRuntime>,
778 script_event_loop_sender: Option<ScriptEventLoopSender>,
779 ) -> Runtime {
780 let mut runtime = if let Some(parent) = parent {
781 unsafe { RustRuntime::create_with_parent(parent) }
782 } else {
783 RustRuntime::new(current_js_engine_handle())
784 };
785 let cx = runtime.cx();
786
787 let have_event_loop_sender = script_event_loop_sender.is_some();
788 let runtime_callback_data = Box::new(RuntimeCallbackData {
789 script_event_loop_sender,
790 script_thread: None,
791 });
792 let runtime_callback_data = Box::into_raw(runtime_callback_data);
793
794 unsafe {
795 JS_AddExtraGCRootsTracer(
796 cx,
797 Some(trace_rust_roots),
798 runtime_callback_data as *mut c_void,
799 );
800
801 JS_SetSecurityCallbacks(cx, &SECURITY_CALLBACKS);
802
803 JS_InitDestroyPrincipalsCallback(cx, Some(principals::destroy_servo_jsprincipal));
804 JS_InitReadPrincipalsCallback(cx, Some(principals::read_jsprincipal));
805
806 if cfg!(debug_assertions) {
808 JS_SetGCCallback(cx, Some(debug_gc_callback), ptr::null_mut());
809 }
810
811 if opts::get()
812 .debug
813 .is_enabled(DiagnosticsLoggingOption::GcProfile)
814 {
815 SetGCSliceCallback(cx, Some(gc_slice_callback));
816 }
817 }
818
819 unsafe extern "C" fn empty_wrapper_callback(_: *mut RawJSContext, _: HandleObject) -> bool {
820 true
821 }
822 unsafe extern "C" fn empty_has_released_callback(_: HandleObject) -> bool {
823 false
825 }
826
827 unsafe {
828 SetDOMCallbacks(cx, &DOM_CALLBACKS);
829 SetPreserveWrapperCallbacks(
830 cx,
831 Some(empty_wrapper_callback),
832 Some(empty_has_released_callback),
833 );
834 }
835
836 unsafe extern "C" fn dispatch_to_event_loop(
837 data: *mut c_void,
838 dispatchable: *mut DispatchablePointer,
839 ) -> bool {
840 let runtime_callback_data: &RuntimeCallbackData =
841 unsafe { &*(data as *mut RuntimeCallbackData) };
842 let Some(script_event_loop_sender) =
843 runtime_callback_data.script_event_loop_sender.as_ref()
844 else {
845 return false;
846 };
847
848 let runnable = Runnable(dispatchable);
849 let task = task!(dispatch_to_event_loop_message: move |cx| {
850 runnable.run(cx, Dispatchable_MaybeShuttingDown::NotShuttingDown);
851 });
852
853 script_event_loop_sender
854 .send(CommonScriptMsg::Task(
855 ScriptThreadEventCategory::NetworkEvent,
856 Box::new(task),
857 None, TaskSourceName::Networking,
859 ))
860 .is_ok()
861 }
862
863 if have_event_loop_sender {
864 unsafe {
865 SetUpEventLoopDispatch(
866 cx,
867 Some(dispatch_to_event_loop),
868 runtime_callback_data as *mut c_void,
869 );
870 }
871 }
872
873 unsafe {
874 InitConsumeStreamCallback(cx, Some(consume_stream), Some(report_stream_error));
875 }
876
877 let microtask_queue = Rc::new(MicrotaskQueue::default());
878
879 let interrupt_queues: Box<Vec<Rc<MicrotaskQueue>>> = Box::default();
883
884 let cx_opts;
885 let job_queue;
886 unsafe {
887 let cx = runtime.cx();
888 job_queue = CreateJobQueue(
889 &JOB_QUEUE_TRAPS,
890 &*microtask_queue as *const _ as *const c_void,
891 Box::into_raw(interrupt_queues) as *mut c_void,
892 );
893 SetJobQueue(cx, job_queue);
894 SetPromiseRejectionTrackerCallback(
895 cx,
896 Some(promise_rejection_tracker),
897 ptr::null_mut(),
898 );
899
900 RegisterScriptEnvironmentPreparer(
901 cx.raw_cx(),
902 Some(invoke_script_environment_preparer),
903 );
904
905 EnsureModuleHooksInitialized(runtime.rt());
906
907 let cx = runtime.cx();
908
909 set_gc_zeal_options(cx.raw_cx());
910
911 cx_opts = &mut *ContextOptionsRef(cx);
913 JS_SetGlobalJitCompilerOption(
914 cx,
915 JSJitCompilerOption::JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE,
916 pref!(js_baseline_interpreter_enabled) as u32,
917 );
918 JS_SetGlobalJitCompilerOption(
919 cx,
920 JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE,
921 pref!(js_baseline_jit_enabled) as u32,
922 );
923 JS_SetGlobalJitCompilerOption(
924 cx,
925 JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE,
926 pref!(js_ion_enabled) as u32,
927 );
928 }
929 cx_opts.compileOptions_.asmJSOption_ = if pref!(js_asmjs_enabled) {
930 AsmJSOption::Enabled
931 } else {
932 AsmJSOption::DisabledByAsmJSPref
933 };
934 cx_opts.compileOptions_.set_importAttributes_(true);
935 let wasm_enabled = pref!(js_wasm_enabled);
936 cx_opts.set_wasm_(wasm_enabled);
937 if wasm_enabled {
938 unsafe { SetProcessBuildIdOp(Some(servo_build_id)) };
942 }
943 cx_opts.set_wasmBaseline_(pref!(js_wasm_baseline_enabled));
944 cx_opts.set_wasmIon_(pref!(js_wasm_ion_enabled));
945
946 unsafe {
947 let cx = runtime.cx();
948 JS_SetGlobalJitCompilerOption(
950 cx,
951 JSJitCompilerOption::JSJITCOMPILER_NATIVE_REGEXP_ENABLE,
952 pref!(js_native_regex_enabled) as u32,
953 );
954 JS_SetOffthreadIonCompilationEnabled(cx, pref!(js_offthread_compilation_enabled));
955 JS_SetGlobalJitCompilerOption(
956 cx,
957 JSJitCompilerOption::JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
958 if pref!(js_baseline_jit_unsafe_eager_compilation_enabled) {
959 0
960 } else {
961 u32::MAX
962 },
963 );
964 JS_SetGlobalJitCompilerOption(
965 cx,
966 JSJitCompilerOption::JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER,
967 if pref!(js_ion_unsafe_eager_compilation_enabled) {
968 0
969 } else {
970 u32::MAX
971 },
972 );
973 JS_SetGCParameter(
979 cx,
980 JSGCParamKey::JSGC_MAX_BYTES,
981 in_range(pref!(js_mem_max), 1, 0x100)
982 .map(|val| (val * 1024 * 1024) as u32)
983 .unwrap_or(u32::MAX),
984 );
985
986 JS_SetGCParameter(
989 cx,
990 JSGCParamKey::JSGC_INCREMENTAL_GC_ENABLED,
991 pref!(js_mem_gc_incremental_enabled) as u32,
992 );
993
994 JS_SetGCParameter(
995 cx,
996 JSGCParamKey::JSGC_PER_ZONE_GC_ENABLED,
997 pref!(js_mem_gc_per_zone_enabled) as u32,
998 );
999 if let Some(val) = in_range(pref!(js_mem_gc_incremental_slice_ms), 0, 100_000) {
1000 JS_SetGCParameter(cx, JSGCParamKey::JSGC_SLICE_TIME_BUDGET_MS, val as u32);
1001 }
1002 JS_SetGCParameter(
1003 cx,
1004 JSGCParamKey::JSGC_COMPACTING_ENABLED,
1005 pref!(js_mem_gc_compacting_enabled) as u32,
1006 );
1007
1008 if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_time_limit_ms), 0, 10_000) {
1009 JS_SetGCParameter(cx, JSGCParamKey::JSGC_HIGH_FREQUENCY_TIME_LIMIT, val as u32);
1010 }
1011 if let Some(val) = in_range(pref!(js_mem_gc_low_frequency_heap_growth), 0, 10_000) {
1012 JS_SetGCParameter(cx, JSGCParamKey::JSGC_LOW_FREQUENCY_HEAP_GROWTH, val as u32);
1013 }
1014 if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_heap_growth_min), 0, 10_000)
1015 {
1016 JS_SetGCParameter(
1017 cx,
1018 JSGCParamKey::JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH,
1019 val as u32,
1020 );
1021 }
1022 if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_heap_growth_max), 0, 10_000)
1023 {
1024 JS_SetGCParameter(
1025 cx,
1026 JSGCParamKey::JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH,
1027 val as u32,
1028 );
1029 }
1030 if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_low_limit_mb), 0, 10_000) {
1031 JS_SetGCParameter(cx, JSGCParamKey::JSGC_SMALL_HEAP_SIZE_MAX, val as u32);
1032 }
1033 if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_high_limit_mb), 0, 10_000) {
1034 JS_SetGCParameter(cx, JSGCParamKey::JSGC_LARGE_HEAP_SIZE_MIN, val as u32);
1035 }
1036 if let Some(val) = in_range(pref!(js_mem_gc_empty_chunk_count_min), 0, 10_000) {
1037 JS_SetGCParameter(cx, JSGCParamKey::JSGC_MIN_EMPTY_CHUNK_COUNT, val as u32);
1038 }
1039 }
1040 Runtime {
1041 rt: runtime,
1042 microtask_queue,
1043 job_queue,
1044 runtime_callback_data: unsafe { Box::from_raw(runtime_callback_data) },
1045 }
1046 }
1047
1048 pub(crate) fn set_script_thread(&mut self, script_thread: Weak<ScriptThread>) {
1049 self.runtime_callback_data
1050 .script_thread
1051 .replace(script_thread);
1052 }
1053
1054 pub(crate) fn thread_safe_js_context(&self) -> ThreadSafeJSContext {
1055 self.rt.thread_safe_js_context()
1056 }
1057}
1058
1059impl Drop for Runtime {
1060 #[expect(unsafe_code)]
1061 fn drop(&mut self) {
1062 self.microtask_queue.clear();
1064
1065 unsafe {
1067 DeleteJobQueue(self.job_queue);
1068 }
1069 LiveDOMReferences::destruct();
1070 mark_runtime_dead();
1071 }
1072}
1073
1074impl Deref for Runtime {
1075 type Target = RustRuntime;
1076 fn deref(&self) -> &RustRuntime {
1077 &self.rt
1078 }
1079}
1080
1081impl DerefMut for Runtime {
1082 fn deref_mut(&mut self) -> &mut RustRuntime {
1083 &mut self.rt
1084 }
1085}
1086
1087fn in_range<T: PartialOrd + Copy>(val: T, min: T, max: T) -> Option<T> {
1088 if val < min || val >= max {
1089 None
1090 } else {
1091 Some(val)
1092 }
1093}
1094
1095thread_local!(static MALLOC_SIZE_OF_OPS: Cell<*mut MallocSizeOfOps> = const { Cell::new(ptr::null_mut()) });
1096
1097#[derive(Default)]
1098struct InterfaceSizeData {
1099 count: usize,
1101 bytes: usize,
1103}
1104
1105struct GlobalSizeData {
1106 url: ServoUrl,
1108 interface_sizes: HashMap<&'static str, InterfaceSizeData>,
1110}
1111
1112#[derive(Default)]
1113pub(crate) struct PerGlobalInterfaceSizes(HashMap<usize, GlobalSizeData>);
1116
1117thread_local!(
1118 static DOM_OBJECT_SIZES: LazyCell<RefCell<PerGlobalInterfaceSizes>> = const {
1119 LazyCell::new(Default::default)
1120 }
1121);
1122
1123#[expect(unsafe_code)]
1124unsafe extern "C" fn get_size(obj: *mut JSObject) -> usize {
1125 let ops = MALLOC_SIZE_OF_OPS.get();
1126 ALREADY_COMPUTED_OBJECTS.with(|objects| {
1127 let ignored = objects.borrow();
1128 DOM_OBJECT_SIZES.with(|dom_sizes| {
1129 let mut per_global_interface_sizes = dom_sizes.borrow_mut();
1130 compute_size(
1131 obj,
1132 unsafe { &mut *ops },
1133 &ignored,
1134 Some(&mut per_global_interface_sizes),
1135 )
1136 })
1137 })
1138}
1139
1140thread_local!(static GC_CYCLE_START: Cell<Option<Instant>> = const { Cell::new(None) });
1141thread_local!(static GC_SLICE_START: Cell<Option<Instant>> = const { Cell::new(None) });
1142
1143#[expect(unsafe_code)]
1144unsafe extern "C" fn gc_slice_callback(
1145 _cx: *mut RawJSContext,
1146 progress: GCProgress,
1147 desc: *const GCDescription,
1148) {
1149 match progress {
1150 GCProgress::GC_CYCLE_BEGIN => GC_CYCLE_START.with(|start| {
1151 start.set(Some(Instant::now()));
1152 println!("GC cycle began");
1153 }),
1154 GCProgress::GC_SLICE_BEGIN => GC_SLICE_START.with(|start| {
1155 start.set(Some(Instant::now()));
1156 println!("GC slice began");
1157 }),
1158 GCProgress::GC_SLICE_END => GC_SLICE_START.with(|start| {
1159 let duration = start.get().unwrap().elapsed();
1160 start.set(None);
1161 println!("GC slice ended: duration={:?}", duration);
1162 }),
1163 GCProgress::GC_CYCLE_END => GC_CYCLE_START.with(|start| {
1164 let duration = start.get().unwrap().elapsed();
1165 start.set(None);
1166 println!("GC cycle ended: duration={:?}", duration);
1167 }),
1168 };
1169 if !desc.is_null() {
1170 let desc: &GCDescription = unsafe { &*desc };
1171 let options = match desc.options_ {
1172 GCOptions::Normal => "Normal",
1173 GCOptions::Shrink => "Shrink",
1174 GCOptions::Shutdown => "Shutdown",
1175 };
1176 println!(" isZone={}, options={}", desc.isZone_, options);
1177 }
1178 let _ = stdout().flush();
1179}
1180
1181#[expect(unsafe_code)]
1182unsafe extern "C" fn debug_gc_callback(
1183 _cx: *mut RawJSContext,
1184 status: JSGCStatus,
1185 _reason: GCReason,
1186 _data: *mut os::raw::c_void,
1187) {
1188 match status {
1189 JSGCStatus::JSGC_BEGIN => thread_state::enter(ThreadState::IN_GC),
1190 JSGCStatus::JSGC_END => thread_state::exit(ThreadState::IN_GC),
1191 }
1192}
1193
1194#[expect(unsafe_code)]
1195unsafe extern "C" fn trace_rust_roots(tr: *mut JSTracer, data: *mut os::raw::c_void) {
1196 if !runtime_is_alive() {
1197 return;
1198 }
1199 trace!("starting custom root handler");
1200
1201 let runtime_callback_data = unsafe { &*(data as *const RuntimeCallbackData) };
1202 if let Some(script_thread) = runtime_callback_data
1203 .script_thread
1204 .as_ref()
1205 .and_then(Weak::upgrade)
1206 {
1207 trace!("tracing fields of ScriptThread");
1208 unsafe { script_thread.trace(tr) };
1209 };
1210
1211 unsafe {
1212 trace_roots(tr);
1213 trace_refcounted_objects(tr);
1214 settings_stack::trace(tr);
1215 }
1216 trace!("done custom root handler");
1217}
1218
1219#[expect(unsafe_code)]
1220unsafe extern "C" fn servo_build_id(build_id: *mut BuildIdCharVector) -> bool {
1221 let servo_id = b"Servo\0";
1222 unsafe { SetBuildId(build_id, servo_id[0] as *const c_char, servo_id.len()) }
1223}
1224
1225#[expect(unsafe_code)]
1226#[cfg(feature = "debugmozjs")]
1227unsafe fn set_gc_zeal_options(cx: *mut RawJSContext) {
1228 use js::jsapi::SetGCZeal;
1229
1230 let level = match pref!(js_mem_gc_zeal_level) {
1231 level @ 0..=14 => level as u8,
1232 _ => return,
1233 };
1234 let frequency = match pref!(js_mem_gc_zeal_frequency) {
1235 frequency if frequency >= 0 => frequency as u32,
1236 _ => 5000,
1238 };
1239 unsafe {
1240 SetGCZeal(cx, level, frequency);
1241 }
1242}
1243
1244#[expect(unsafe_code)]
1245#[cfg(not(feature = "debugmozjs"))]
1246unsafe fn set_gc_zeal_options(_: *mut RawJSContext) {}
1247
1248thread_local!(static ALREADY_COMPUTED_OBJECTS: LazyCell<RefCell<HashSet<*const JSObject>>> = const {
1249 LazyCell::new(Default::default)
1250});
1251
1252#[expect(unsafe_code)]
1253pub(crate) fn compute_size(
1254 obj: *mut JSObject,
1255 ops: &mut MallocSizeOfOps,
1256 ignored: &HashSet<*const JSObject>,
1257 per_global_interface_sizes: Option<&mut PerGlobalInterfaceSizes>,
1258) -> usize {
1259 if ignored.contains(&(obj as *const JSObject)) {
1260 return 0;
1261 }
1262
1263 match unsafe { get_dom_class(obj) } {
1264 Ok(v) => {
1265 let dom_object = unsafe { private_from_object(obj) as *const c_void };
1266
1267 if dom_object.is_null() {
1268 return 0;
1269 }
1270 let size = unsafe { (v.malloc_size_of)(&mut *ops, dom_object) };
1271
1272 let Some(per_global_interface_sizes) = per_global_interface_sizes else {
1273 return size;
1274 };
1275
1276 let global = unsafe { js::jsapi::GetNonCCWObjectGlobal(obj) };
1277 let interface = v.interface_chain[v.depth as usize];
1278 let interface_size = per_global_interface_sizes
1279 .0
1280 .entry(global as usize)
1281 .or_insert_with(|| {
1282 let global = unsafe { GlobalScope::from_object(obj) };
1283 GlobalSizeData {
1284 url: global.get_url(),
1285 interface_sizes: HashMap::new(),
1286 }
1287 })
1288 .interface_sizes
1289 .entry(interface.into())
1290 .or_default();
1291 interface_size.count += 1;
1292 interface_size.bytes += size;
1293 0
1296 },
1297 Err(_e) => 0,
1298 }
1299}
1300
1301#[expect(unsafe_code)]
1302pub(crate) fn get_reports(
1303 cx: &mut JSContext,
1304 path_seg: String,
1305 ops: &mut MallocSizeOfOps,
1306 already_computed_objects: HashSet<*const JSObject>,
1307) -> Vec<Report> {
1308 MALLOC_SIZE_OF_OPS.with(|ops_tls| ops_tls.set(ops));
1309 ALREADY_COMPUTED_OBJECTS.with(|objects| {
1310 *objects.borrow_mut() = already_computed_objects;
1311 });
1312
1313 let stats = unsafe {
1314 let mut stats = ::std::mem::zeroed();
1315 if !CollectServoSizes(cx, &mut stats, Some(get_size)) {
1316 return vec![];
1317 }
1318 stats
1319 };
1320 MALLOC_SIZE_OF_OPS.with(|ops| ops.set(ptr::null_mut()));
1321 ALREADY_COMPUTED_OBJECTS.with(|objects| {
1322 let mut objects = objects.borrow_mut();
1323 objects.clear();
1324 objects.shrink_to_fit();
1325 });
1326
1327 let mut reports = vec![];
1328 let mut report = |mut path_suffix, kind, size| {
1329 let mut path = path![path_seg, "js"];
1330 path.append(&mut path_suffix);
1331 reports.push(Report { path, kind, size })
1332 };
1333
1334 DOM_OBJECT_SIZES.with(|sizes| {
1335 let mut sizes = sizes.borrow_mut();
1336 for global_size_data in sizes.0.values() {
1337 let url = global_size_data.url.as_str();
1338 for (interface, interface_data) in &global_size_data.interface_sizes {
1339 report(
1340 path![
1341 "dom",
1342 "out-of-tree",
1343 format!("url({url})"),
1344 format!("{interface} [{}]", interface_data.count)
1345 ],
1346 ReportKind::ExplicitJemallocHeapSize,
1347 interface_data.bytes,
1348 );
1349 }
1350 }
1351 sizes.0.clear();
1352 });
1353
1354 report(
1358 path!["gc-heap", "used"],
1359 ReportKind::ExplicitNonHeapSize,
1360 stats.gcHeapUsed,
1361 );
1362
1363 report(
1364 path!["gc-heap", "unused"],
1365 ReportKind::ExplicitNonHeapSize,
1366 stats.gcHeapUnused,
1367 );
1368
1369 report(
1370 path!["gc-heap", "admin"],
1371 ReportKind::ExplicitNonHeapSize,
1372 stats.gcHeapAdmin,
1373 );
1374
1375 report(
1376 path!["gc-heap", "decommitted"],
1377 ReportKind::ExplicitNonHeapSize,
1378 stats.gcHeapDecommitted,
1379 );
1380
1381 report(
1383 path!["malloc-heap"],
1384 ReportKind::ExplicitSystemHeapSize,
1385 stats.mallocHeap,
1386 );
1387
1388 report(
1389 path!["non-heap"],
1390 ReportKind::ExplicitNonHeapSize,
1391 stats.nonHeap,
1392 );
1393 reports
1394}
1395
1396pub(crate) struct StreamConsumer(*mut JSStreamConsumer);
1397
1398#[expect(unsafe_code)]
1399impl StreamConsumer {
1400 pub(crate) fn consume_chunk(&self, stream: &[u8]) -> bool {
1401 unsafe {
1402 let stream_ptr = stream.as_ptr();
1403 StreamConsumerConsumeChunk(self.0, stream_ptr, stream.len())
1404 }
1405 }
1406
1407 pub(crate) fn stream_end(&self) {
1408 unsafe {
1409 StreamConsumerStreamEnd(self.0);
1410 }
1411 }
1412
1413 pub(crate) fn stream_error(&self, error_code: usize) {
1414 unsafe {
1415 StreamConsumerStreamError(self.0, error_code);
1416 }
1417 }
1418
1419 pub(crate) fn note_response_urls(
1420 &self,
1421 maybe_url: Option<String>,
1422 maybe_source_map_url: Option<String>,
1423 ) {
1424 unsafe {
1425 let maybe_url = maybe_url.map(|url| CString::new(url).unwrap());
1426 let maybe_source_map_url = maybe_source_map_url.map(|url| CString::new(url).unwrap());
1427
1428 let maybe_url_param = match maybe_url.as_ref() {
1429 Some(url) => url.as_ptr(),
1430 None => ptr::null(),
1431 };
1432 let maybe_source_map_url_param = match maybe_source_map_url.as_ref() {
1433 Some(url) => url.as_ptr(),
1434 None => ptr::null(),
1435 };
1436
1437 StreamConsumerNoteResponseURLs(self.0, maybe_url_param, maybe_source_map_url_param);
1438 }
1439 }
1440}
1441
1442#[expect(unsafe_code)]
1445unsafe extern "C" fn consume_stream(
1446 cx: *mut RawJSContext,
1447 obj: HandleObject,
1448 _mime_type: MimeType,
1449 _consumer: *mut JSStreamConsumer,
1450) -> bool {
1451 let mut cx = unsafe {
1452 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1454 };
1455 let cx = &mut cx;
1456 let mut realm = CurrentRealm::assert(cx);
1457 let global = GlobalScope::from_current_realm(&mut realm);
1458
1459 if let Ok(unwrapped_source) =
1461 unsafe { root_from_handleobject::<Response>(cx, RustHandleObject::from_raw(obj)) }
1462 {
1463 let mimetype = unwrapped_source.Headers(cx).extract_mime_type();
1465
1466 if !&mimetype[..].eq_ignore_ascii_case(b"application/wasm") {
1468 throw_dom_exception(
1469 cx,
1470 &global,
1471 Error::Type(c"Response has unsupported MIME type".to_owned()),
1472 );
1473 return false;
1474 }
1475
1476 match unwrapped_source.Type() {
1478 DOMResponseType::Basic | DOMResponseType::Cors | DOMResponseType::Default => {},
1479 _ => {
1480 throw_dom_exception(
1481 cx,
1482 &global,
1483 Error::Type(c"Response.type must be 'basic', 'cors' or 'default'".to_owned()),
1484 );
1485 return false;
1486 },
1487 }
1488
1489 if !unwrapped_source.Ok() {
1491 throw_dom_exception(
1492 cx,
1493 &global,
1494 Error::Type(c"Response does not have ok status".to_owned()),
1495 );
1496 return false;
1497 }
1498
1499 if unwrapped_source.is_locked() {
1501 throw_dom_exception(
1502 cx,
1503 &global,
1504 Error::Type(c"There was an error consuming the Response".to_owned()),
1505 );
1506 return false;
1507 }
1508
1509 if unwrapped_source.is_disturbed() {
1511 throw_dom_exception(
1512 cx,
1513 &global,
1514 Error::Type(c"Response already consumed".to_owned()),
1515 );
1516 return false;
1517 }
1518 unwrapped_source.set_stream_consumer(Some(StreamConsumer(_consumer)));
1519 } else {
1520 throw_dom_exception(
1522 cx,
1523 &global,
1524 Error::Type(c"expected Response or Promise resolving to Response".to_owned()),
1525 );
1526 return false;
1527 }
1528 true
1529}
1530
1531#[expect(unsafe_code)]
1532unsafe extern "C" fn report_stream_error(_cx: *mut RawJSContext, error_code: usize) {
1533 error!("Error initializing StreamConsumer: {:?}", unsafe {
1534 RUST_js_GetErrorMessage(ptr::null_mut(), error_code as u32)
1535 });
1536}
1537
1538#[expect(unsafe_code)]
1539unsafe extern "C" fn invoke_script_environment_preparer(
1540 global: HandleObject,
1541 closure: *mut ScriptEnvironmentPreparer_Closure,
1542) {
1543 let mut cx = unsafe { temp_cx() };
1545 let global = unsafe { GlobalScope::from_object(global.get()) };
1546 let mut realm = enter_auto_realm(&mut cx, &*global);
1547 let cx = &mut realm.current_realm();
1548
1549 run_a_script::<DomTypeHolder, _, _>(cx, &global, |cx| {
1550 if unsafe { !RunScriptEnvironmentPreparerClosure(cx.raw_cx(), closure) } {
1551 report_pending_exception(cx);
1552 };
1553 });
1554}
1555
1556pub(crate) struct Runnable(*mut DispatchablePointer);
1557
1558#[expect(unsafe_code)]
1559unsafe impl Sync for Runnable {}
1560#[expect(unsafe_code)]
1561unsafe impl Send for Runnable {}
1562
1563#[expect(unsafe_code)]
1564impl Runnable {
1565 fn run(&self, cx: &mut JSContext, maybe_shutting_down: Dispatchable_MaybeShuttingDown) {
1566 unsafe {
1567 DispatchableRun(cx, self.0, maybe_shutting_down);
1568 }
1569 }
1570}
1571
1572pub(crate) struct IntroductionType;
1578impl IntroductionType {
1579 pub const EVAL: &CStr = c"eval";
1581 pub const EVAL_STR: &str = "eval";
1582
1583 pub const DEBUGGER_EVAL: &CStr = c"debugger eval";
1586 pub const DEBUGGER_EVAL_STR: &str = "debugger eval";
1587
1588 pub const FUNCTION: &CStr = c"Function";
1590 pub const FUNCTION_STR: &str = "Function";
1591
1592 pub const WORKLET: &CStr = c"Worklet";
1594 pub const WORKLET_STR: &str = "Worklet";
1595
1596 pub const EVENT_HANDLER: &CStr = c"eventHandler";
1598 pub const EVENT_HANDLER_STR: &str = "eventHandler";
1599
1600 pub const SRC_SCRIPT: &CStr = c"srcScript";
1603 pub const SRC_SCRIPT_STR: &str = "srcScript";
1604
1605 pub const INLINE_SCRIPT: &CStr = c"inlineScript";
1608 pub const INLINE_SCRIPT_STR: &str = "inlineScript";
1609
1610 pub const INJECTED_SCRIPT: &CStr = c"injectedScript";
1616 pub const INJECTED_SCRIPT_STR: &str = "injectedScript";
1617
1618 pub const IMPORTED_MODULE: &CStr = c"importedModule";
1621 pub const IMPORTED_MODULE_STR: &str = "importedModule";
1622
1623 pub const JAVASCRIPT_URL: &CStr = c"javascriptURL";
1625 pub const JAVASCRIPT_URL_STR: &str = "javascriptURL";
1626
1627 pub const DOM_TIMER: &CStr = c"domTimer";
1629 pub const DOM_TIMER_STR: &str = "domTimer";
1630
1631 pub const WORKER: &CStr = c"Worker";
1635 pub const WORKER_STR: &str = "Worker";
1636}