Skip to main content

script/runtime/
script_runtime.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! The script runtime contains common traits and structs commonly used by the
6//! script thread, the dom, and the worker threads.
7
8#![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::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    DispatchablePointer, RUST_js_GetErrorMessage, RegisterScriptEnvironmentPreparer,
28    RunScriptEnvironmentPreparerClosure, SetBuildId, StreamConsumerConsumeChunk,
29    StreamConsumerNoteResponseURLs, StreamConsumerStreamEnd, StreamConsumerStreamError,
30};
31use js::jsapi::{
32    AsmJSOption, BuildIdCharVector, CompilationType, Dispatchable_MaybeShuttingDown, GCDescription,
33    GCOptions, GCProgress, GCReason, Handle as RawHandle, HandleObject, HandleString,
34    HandleValue as RawHandleValue, Heap, JSContext as RawJSContext, JSGCParamKey, JSGCStatus,
35    JSJitCompilerOption, JSObject, JSSecurityCallbacks, JSString, JSTracer, MimeType,
36    MutableHandleString, PromiseRejectionHandlingState, RuntimeCode,
37    ScriptEnvironmentPreparer_Closure, SetProcessBuildIdOp, StreamConsumer as JSStreamConsumer,
38};
39use js::jsval::{JSVal, UndefinedValue};
40use js::panic::wrap_panic;
41use js::realm::CurrentRealm;
42pub(crate) use js::rust::ThreadSafeJSContext;
43use js::rust::wrappers2::{
44    CollectServoSizes, ContextOptionsRef, DispatchableRun, InitConsumeStreamCallback,
45    JS_AddExtraGCRootsTracer, JS_GetPromiseResult, JS_InitDestroyPrincipalsCallback,
46    JS_InitReadPrincipalsCallback, JS_NewStringCopyUTF8N, JS_SetGCCallback, JS_SetGCParameter,
47    JS_SetGlobalJitCompilerOption, JS_SetOffthreadIonCompilationEnabled, JS_SetSecurityCallbacks,
48    SetDOMCallbacks, SetGCSliceCallback, SetPreserveWrapperCallbacks,
49    SetPromiseRejectionTrackerCallback, SetUpEventLoopDispatch,
50};
51use js::rust::{
52    Handle, HandleObject as RustHandleObject, HandleValue, IntoHandle, ParentRuntime,
53    Runtime as RustRuntime, Trace,
54};
55use malloc_size_of::MallocSizeOfOps;
56use malloc_size_of_derive::MallocSizeOf;
57use profile_traits::mem::{Report, ReportKind};
58use profile_traits::path;
59use profile_traits::time::ProfilerCategory;
60use script_bindings::reflector::DomObject;
61use script_bindings::script_runtime::{mark_runtime_dead, runtime_is_alive, temp_cx};
62use script_bindings::settings_stack::run_a_script;
63use servo_config::opts::{self, DiagnosticsLoggingOption};
64use servo_config::pref;
65use servo_url::ServoUrl;
66use style::thread_state::{self, ThreadState};
67
68use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
69use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
70use crate::dom::bindings::codegen::UnionTypes::TrustedScriptOrString;
71use crate::dom::bindings::conversions::{
72    get_dom_class, private_from_object, root_from_handleobject, root_from_object,
73};
74use crate::dom::bindings::error::{Error, report_pending_exception, throw_dom_exception};
75use crate::dom::bindings::inheritance::Castable;
76use crate::dom::bindings::refcounted::{
77    LivePromiseReferences, Trusted, TrustedPromise, trace_refcounted_objects,
78};
79use crate::dom::bindings::reflector::DomGlobal;
80use crate::dom::bindings::root::trace_roots;
81use crate::dom::bindings::str::DOMString;
82use crate::dom::bindings::utils::DOM_CALLBACKS;
83use crate::dom::bindings::{principals, settings_stack};
84use crate::dom::console::{Console, stringify_handle_value};
85use crate::dom::csp::CspReporting;
86use crate::dom::event::{Event, EventBubbles, EventCancelable};
87use crate::dom::eventtarget::EventTarget;
88use crate::dom::globalscope::GlobalScope;
89use crate::dom::promise::Promise;
90use crate::dom::promiserejectionevent::PromiseRejectionEvent;
91use crate::dom::response::Response;
92use crate::dom::trustedtypes::trustedscript::TrustedScript;
93use crate::dom::window::Window;
94use crate::engine::handle::current_js_engine_handle;
95use crate::messaging::{CommonScriptMsg, ScriptEventLoopSender};
96use crate::modules::script_module::EnsureModuleHooksInitialized;
97use crate::realms::enter_auto_realm;
98use crate::runtime::job_queue::{JobQueue, job_queue_clear};
99use crate::tasks::task_source::TaskSourceName;
100use crate::{DomTypeHolder, ScriptThread};
101
102static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks {
103    contentSecurityPolicyAllows: Some(content_security_policy_allows),
104    codeForEvalGets: Some(code_for_eval_gets),
105    subsumes: Some(principals::subsumes),
106};
107
108#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
109pub(crate) enum ScriptThreadEventCategory {
110    SpawnPipeline,
111    ConstellationMsg,
112    DatabaseAccessEvent,
113    DevtoolsMsg,
114    DocumentEvent,
115    FileRead,
116    FontLoading,
117    FormPlannedNavigation,
118    GeolocationEvent,
119    ImageCacheMsg,
120    InputEvent,
121    NavigationAndTraversalEvent,
122    NetworkEvent,
123    PortMessage,
124    Rendering,
125    Resize,
126    ScriptEvent,
127    SetScrollState,
128    SetViewport,
129    StylesheetLoad,
130    TimerEvent,
131    UpdateReplacedElement,
132    WebSocketEvent,
133    WorkerEvent,
134    WorkletEvent,
135    ServiceWorkerEvent,
136    EnterFullscreen,
137    ExitFullscreen,
138    PerformanceTimelineTask,
139    #[cfg(feature = "webgpu")]
140    WebGPUMsg,
141}
142
143impl From<ScriptThreadEventCategory> for ProfilerCategory {
144    fn from(category: ScriptThreadEventCategory) -> Self {
145        match category {
146            ScriptThreadEventCategory::SpawnPipeline => ProfilerCategory::ScriptSpawnPipeline,
147            ScriptThreadEventCategory::ConstellationMsg => ProfilerCategory::ScriptConstellationMsg,
148            ScriptThreadEventCategory::DatabaseAccessEvent => {
149                ProfilerCategory::ScriptDatabaseAccessEvent
150            },
151            ScriptThreadEventCategory::DevtoolsMsg => ProfilerCategory::ScriptDevtoolsMsg,
152            ScriptThreadEventCategory::DocumentEvent => ProfilerCategory::ScriptDocumentEvent,
153            ScriptThreadEventCategory::EnterFullscreen => ProfilerCategory::ScriptEnterFullscreen,
154            ScriptThreadEventCategory::ExitFullscreen => ProfilerCategory::ScriptExitFullscreen,
155            ScriptThreadEventCategory::FileRead => ProfilerCategory::ScriptFileRead,
156            ScriptThreadEventCategory::FontLoading => ProfilerCategory::ScriptFontLoading,
157            ScriptThreadEventCategory::FormPlannedNavigation => {
158                ProfilerCategory::ScriptPlannedNavigation
159            },
160            ScriptThreadEventCategory::GeolocationEvent => ProfilerCategory::ScriptGeolocationEvent,
161            ScriptThreadEventCategory::NavigationAndTraversalEvent => {
162                ProfilerCategory::ScriptNavigationAndTraversalEvent
163            },
164            ScriptThreadEventCategory::ImageCacheMsg => ProfilerCategory::ScriptImageCacheMsg,
165            ScriptThreadEventCategory::InputEvent => ProfilerCategory::ScriptInputEvent,
166            ScriptThreadEventCategory::NetworkEvent => ProfilerCategory::ScriptNetworkEvent,
167            ScriptThreadEventCategory::PerformanceTimelineTask => {
168                ProfilerCategory::ScriptPerformanceEvent
169            },
170            ScriptThreadEventCategory::PortMessage => ProfilerCategory::ScriptPortMessage,
171            ScriptThreadEventCategory::Resize => ProfilerCategory::ScriptResize,
172            ScriptThreadEventCategory::Rendering => ProfilerCategory::ScriptRendering,
173            ScriptThreadEventCategory::ScriptEvent => ProfilerCategory::ScriptEvent,
174            ScriptThreadEventCategory::ServiceWorkerEvent => {
175                ProfilerCategory::ScriptServiceWorkerEvent
176            },
177            ScriptThreadEventCategory::SetScrollState => ProfilerCategory::ScriptSetScrollState,
178            ScriptThreadEventCategory::SetViewport => ProfilerCategory::ScriptSetViewport,
179            ScriptThreadEventCategory::StylesheetLoad => ProfilerCategory::ScriptStylesheetLoad,
180            ScriptThreadEventCategory::TimerEvent => ProfilerCategory::ScriptTimerEvent,
181            ScriptThreadEventCategory::UpdateReplacedElement => {
182                ProfilerCategory::ScriptUpdateReplacedElement
183            },
184            ScriptThreadEventCategory::WebSocketEvent => ProfilerCategory::ScriptWebSocketEvent,
185            ScriptThreadEventCategory::WorkerEvent => ProfilerCategory::ScriptWorkerEvent,
186            ScriptThreadEventCategory::WorkletEvent => ProfilerCategory::ScriptWorkletEvent,
187            #[cfg(feature = "webgpu")]
188            ScriptThreadEventCategory::WebGPUMsg => ProfilerCategory::ScriptWebGPUMsg,
189        }
190    }
191}
192
193impl From<ScriptThreadEventCategory> for ScriptHangAnnotation {
194    fn from(category: ScriptThreadEventCategory) -> Self {
195        match category {
196            ScriptThreadEventCategory::SpawnPipeline => ScriptHangAnnotation::SpawnPipeline,
197            ScriptThreadEventCategory::ConstellationMsg => ScriptHangAnnotation::ConstellationMsg,
198            ScriptThreadEventCategory::DatabaseAccessEvent => {
199                ScriptHangAnnotation::DatabaseAccessEvent
200            },
201            ScriptThreadEventCategory::DevtoolsMsg => ScriptHangAnnotation::DevtoolsMsg,
202            ScriptThreadEventCategory::DocumentEvent => ScriptHangAnnotation::DocumentEvent,
203            ScriptThreadEventCategory::InputEvent => ScriptHangAnnotation::InputEvent,
204            ScriptThreadEventCategory::FileRead => ScriptHangAnnotation::FileRead,
205            ScriptThreadEventCategory::FontLoading => ScriptHangAnnotation::FontLoading,
206            ScriptThreadEventCategory::FormPlannedNavigation => {
207                ScriptHangAnnotation::FormPlannedNavigation
208            },
209            ScriptThreadEventCategory::GeolocationEvent => ScriptHangAnnotation::GeolocationEvent,
210            ScriptThreadEventCategory::NavigationAndTraversalEvent => {
211                ScriptHangAnnotation::NavigationAndTraversalEvent
212            },
213            ScriptThreadEventCategory::ImageCacheMsg => ScriptHangAnnotation::ImageCacheMsg,
214            ScriptThreadEventCategory::NetworkEvent => ScriptHangAnnotation::NetworkEvent,
215            ScriptThreadEventCategory::Rendering => ScriptHangAnnotation::Rendering,
216            ScriptThreadEventCategory::Resize => ScriptHangAnnotation::Resize,
217            ScriptThreadEventCategory::ScriptEvent => ScriptHangAnnotation::ScriptEvent,
218            ScriptThreadEventCategory::SetScrollState => ScriptHangAnnotation::SetScrollState,
219            ScriptThreadEventCategory::SetViewport => ScriptHangAnnotation::SetViewport,
220            ScriptThreadEventCategory::StylesheetLoad => ScriptHangAnnotation::StylesheetLoad,
221            ScriptThreadEventCategory::TimerEvent => ScriptHangAnnotation::TimerEvent,
222            ScriptThreadEventCategory::UpdateReplacedElement => {
223                ScriptHangAnnotation::UpdateReplacedElement
224            },
225            ScriptThreadEventCategory::WebSocketEvent => ScriptHangAnnotation::WebSocketEvent,
226            ScriptThreadEventCategory::WorkerEvent => ScriptHangAnnotation::WorkerEvent,
227            ScriptThreadEventCategory::WorkletEvent => ScriptHangAnnotation::WorkletEvent,
228            ScriptThreadEventCategory::ServiceWorkerEvent => {
229                ScriptHangAnnotation::ServiceWorkerEvent
230            },
231            ScriptThreadEventCategory::EnterFullscreen => ScriptHangAnnotation::EnterFullscreen,
232            ScriptThreadEventCategory::ExitFullscreen => ScriptHangAnnotation::ExitFullscreen,
233            ScriptThreadEventCategory::PerformanceTimelineTask => {
234                ScriptHangAnnotation::PerformanceTimelineTask
235            },
236            ScriptThreadEventCategory::PortMessage => ScriptHangAnnotation::PortMessage,
237            #[cfg(feature = "webgpu")]
238            ScriptThreadEventCategory::WebGPUMsg => ScriptHangAnnotation::WebGPUMsg,
239        }
240    }
241}
242
243#[expect(unsafe_code)]
244/// <https://html.spec.whatwg.org/multipage/#the-hostpromiserejectiontracker-implementation>
245unsafe extern "C" fn promise_rejection_tracker(
246    cx: *mut RawJSContext,
247    muted_errors: bool,
248    promise: HandleObject,
249    state: PromiseRejectionHandlingState,
250    _data: *mut c_void,
251) {
252    // Step 1. Let script be the running script.
253    // Step 2. If script is a classic script and script's muted errors is true, then return.
254    if muted_errors {
255        return;
256    }
257
258    // Step 3.
259    // SAFETY: it is safe to construct a JSContext from engine hook.
260    let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
261    let mut realm = CurrentRealm::assert(&mut cx);
262
263    let global = GlobalScope::from_current_realm(&mut realm);
264    let cx = &mut realm;
265
266    wrap_panic(&mut || {
267        match state {
268            // Step 4.
269            PromiseRejectionHandlingState::Unhandled => {
270                global.add_uncaught_rejection(promise);
271            },
272            // Step 5.
273            PromiseRejectionHandlingState::Handled => {
274                // Step 5-1.
275                if global
276                    .get_uncaught_rejections()
277                    .borrow()
278                    .contains(&Heap::boxed(promise.get()))
279                {
280                    global.remove_uncaught_rejection(promise);
281                    return;
282                }
283
284                // Step 5-2.
285                if !global
286                    .get_consumed_rejections()
287                    .borrow()
288                    .contains(&Heap::boxed(promise.get()))
289                {
290                    return;
291                }
292
293                // Step 5-3.
294                global.remove_consumed_rejection(promise);
295
296                let target = Trusted::new(global.upcast::<EventTarget>());
297                let promise =
298                    Promise::new_with_js_promise(cx, unsafe { Handle::from_raw(promise) });
299                let trusted_promise = TrustedPromise::from(&promise);
300
301                // Step 5-4.
302                global.task_manager().dom_manipulation_task_source().queue(
303                task!(rejection_handled_event: move |cx| {
304                    let target = target.root();
305                    let root_promise = trusted_promise.root(cx);
306
307                    rooted!(&in(cx) let mut reason = UndefinedValue());
308                    unsafe {
309                        JS_GetPromiseResult(root_promise.reflector().get_jsobject(), reason.handle_mut());
310                    }
311
312                    let event = PromiseRejectionEvent::new(
313                        cx,
314                        &target.global(),
315                        atom!("rejectionhandled"),
316                        EventBubbles::DoesNotBubble,
317                        EventCancelable::Cancelable,
318                        &root_promise,
319                        reason.handle(),
320                    );
321
322                    event.upcast::<Event>().fire(cx, &target);
323                })
324                );
325            },
326        };
327    })
328}
329
330#[expect(unsafe_code)]
331fn safely_convert_null_to_string(cx: &JSContext, str_: HandleString) -> DOMString {
332    DOMString::from(match std::ptr::NonNull::new(*str_) {
333        None => String::new(),
334        Some(str_) => unsafe { jsstr_to_string(cx, str_) },
335    })
336}
337
338#[expect(unsafe_code)]
339unsafe extern "C" fn code_for_eval_gets(
340    cx: *mut RawJSContext,
341    code: HandleObject,
342    code_for_eval: MutableHandleString,
343) -> bool {
344    // SAFETY: We are in SM hook
345    let (mut cx, code) = unsafe {
346        (
347            JSContext::from_ptr(NonNull::new(cx).unwrap()),
348            RustHandleObject::from_raw(code),
349        )
350    };
351    let cx = &mut cx;
352    if let Ok(trusted_script) = root_from_handleobject::<TrustedScript>(cx, code) {
353        let script_str = trusted_script.data().str();
354        let s = js::conversions::Utf8Chars::from(&*script_str);
355        let new_string = unsafe { JS_NewStringCopyUTF8N(cx, &*s as *const _) };
356        code_for_eval.set(new_string);
357    }
358    true
359}
360
361#[expect(unsafe_code)]
362unsafe extern "C" fn content_security_policy_allows(
363    cx: *mut RawJSContext,
364    runtime_code: RuntimeCode,
365    code_string: HandleString,
366    compilation_type: CompilationType,
367    parameter_strings: RawHandle<StackGCVector<*mut JSString>>,
368    body_string: HandleString,
369    parameter_args: RawHandle<StackGCVector<JSVal>>,
370    body_arg: RawHandleValue,
371    can_compile_strings: *mut bool,
372) -> bool {
373    let mut allowed = false;
374    // SAFETY: We are in SM hook
375    let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
376    let cx = &mut cx;
377    wrap_panic(&mut || {
378        // SpiderMonkey provides null pointer when executing webassembly.
379        let mut realm = CurrentRealm::assert(cx);
380        let global = GlobalScope::from_current_realm(&mut realm);
381        let csp_list = global.get_csp_list();
382
383        // If we don't have any CSP checks to run, short-circuit all logic here
384        allowed = csp_list.is_none() ||
385            match runtime_code {
386                RuntimeCode::JS => {
387                    let parameter_strings = unsafe { Handle::from_raw(parameter_strings) };
388                    let parameter_strings_length = parameter_strings.len();
389                    let mut parameter_strings_vec =
390                        Vec::with_capacity(parameter_strings_length as usize);
391
392                    for i in 0..parameter_strings_length {
393                        let Some(str_) = parameter_strings.at(i) else {
394                            unreachable!();
395                        };
396                        parameter_strings_vec.push(safely_convert_null_to_string(cx, str_.into()));
397                    }
398
399                    let parameter_args = unsafe { Handle::from_raw(parameter_args) };
400                    let parameter_args_length = parameter_args.len();
401                    let mut parameter_args_vec = Vec::with_capacity(parameter_args_length as usize);
402
403                    for i in 0..parameter_args_length {
404                        let Some(arg) = parameter_args.at(i) else {
405                            unreachable!();
406                        };
407                        let value = arg.into_handle().get();
408                        if value.is_object() {
409                            if let Ok(trusted_script) =
410                                unsafe { root_from_object::<TrustedScript>(cx, value.to_object()) }
411                            {
412                                parameter_args_vec
413                                    .push(TrustedScriptOrString::TrustedScript(trusted_script));
414                            } else {
415                                // It's not a trusted script but a different object. Treat it
416                                // as if it is a string, since we don't need the actual contents
417                                // of the object.
418                                parameter_args_vec
419                                    .push(TrustedScriptOrString::String(DOMString::new()));
420                            }
421                        } else if value.is_string() {
422                            // We don't need to know the specific string, only that it is untrusted
423                            parameter_args_vec
424                                .push(TrustedScriptOrString::String(DOMString::new()));
425                        } else {
426                            unreachable!();
427                        }
428                    }
429
430                    let code_string = safely_convert_null_to_string(cx, code_string);
431                    let body_string = safely_convert_null_to_string(cx, body_string);
432
433                    TrustedScript::can_compile_string_with_trusted_type(
434                        cx,
435                        &global,
436                        code_string,
437                        compilation_type,
438                        parameter_strings_vec,
439                        body_string,
440                        parameter_args_vec,
441                        unsafe { HandleValue::from_raw(body_arg) },
442                    )
443                },
444                RuntimeCode::WASM => global
445                    .get_csp_list()
446                    .is_wasm_evaluation_allowed(cx, &global),
447            };
448    });
449    unsafe { *can_compile_strings = allowed };
450    true
451}
452
453#[expect(unsafe_code)]
454/// <https://html.spec.whatwg.org/multipage/#notify-about-rejected-promises>
455pub(crate) fn notify_about_rejected_promises(cx: &mut JSContext, global: &GlobalScope) {
456    // Step 1. Let list be a clone of global's about-to-be-notified rejected promises list.
457    let uncaught_rejections: Vec<TrustedPromise> = {
458        global
459            .get_uncaught_rejections()
460            .borrow()
461            .iter()
462            .map(|promise| {
463                let promise =
464                    Promise::new_with_js_promise(cx, unsafe { Handle::from_raw(promise.handle()) });
465
466                TrustedPromise::from(&promise)
467            })
468            .collect()
469    };
470    global.get_uncaught_rejections().safe_borrow_mut(cx).clear();
471
472    // Step 2. If list is empty, then return.
473    if uncaught_rejections.is_empty() {
474        return;
475    }
476
477    // Step 3. Empty global's about-to-be-notified rejected promises list.
478    // NOTE: We did this as part of Step 1. using the "drain(..)" call.
479
480    // Step 4. Queue a global task on the DOM manipulation task source given global to run the following step:
481    let target = Trusted::new(global.upcast::<EventTarget>());
482    global.task_manager().dom_manipulation_task_source().queue(
483        task!(unhandled_rejection_event: move |cx| {
484            let target = target.root();
485
486            // Step 4.1 For each promise p of list:
487            for promise in uncaught_rejections {
488                let promise = promise.root(cx);
489
490                // 4.1.1 If p.[[PromiseIsHandled]] is true, then continue.
491                if promise.get_promise_is_handled() {
492                    continue;
493                }
494
495                // Step 4.1.2 Let notCanceled be the result of firing an event named unhandledrejection at global,
496                // using PromiseRejectionEvent, with the cancelable attribute initialized to true,
497                // the promise attribute initialized to p, and the reason attribute initialized to p.[[PromiseResult]].
498                rooted!(&in(cx) let mut reason = UndefinedValue());
499                unsafe {
500                    JS_GetPromiseResult(promise.reflector().get_jsobject(), reason.handle_mut());
501                }
502
503                let event = PromiseRejectionEvent::new(
504                    cx,
505                    &target.global(),
506                    atom!("unhandledrejection"),
507                    EventBubbles::DoesNotBubble,
508                    EventCancelable::Cancelable,
509                    &promise,
510                    reason.handle(),
511                );
512                let not_canceled = event.upcast::<Event>().fire(cx, &target);
513
514                // Step 4.1.3 If notCanceled is true, then the user agent may report
515                // p.[[PromiseResult]] to a developer console.
516                if not_canceled {
517                    let message = format!(
518                        "Unhandled promise rejection: {}",
519                        stringify_handle_value(cx, reason.handle())
520                    );
521                    Console::internal_error(cx, &target.global(), message);
522                }
523
524                // Step 4.1.4 If p.[[PromiseIsHandled]] is false, then append p to global's outstanding
525                // rejected promises weak set.
526                if !promise.get_promise_is_handled() {
527                    target.global().add_consumed_rejection(promise.reflector().get_jsobject().into_handle());
528                }
529            }
530        })
531    );
532}
533
534/// Data that is sent to SpiderMonkey runtime callbacks as a pointer, which allows access
535/// to the `Runtime` state.
536#[derive(Default, JSTraceable, MallocSizeOf)]
537struct RuntimeCallbackData {
538    script_event_loop_sender: Option<ScriptEventLoopSender>,
539    #[no_trace]
540    #[ignore_malloc_size_of = "ScriptThread measures its own memory itself."]
541    script_thread: Option<Weak<ScriptThread>>,
542}
543
544#[derive(JSTraceable, MallocSizeOf)]
545pub(crate) struct Runtime {
546    #[ignore_malloc_size_of = "Type from mozjs"]
547    rt: RustRuntime,
548    job_queue: JobQueue,
549    /// The data that is set on the SpiderMonkey runtime callbacks as a pointer.
550    runtime_callback_data: Box<RuntimeCallbackData>,
551}
552
553impl Runtime {
554    /// Create a new runtime, optionally with the given [`SendableTaskSource`] for networking.
555    ///
556    /// # Safety
557    ///
558    /// If panicking does not abort the program, any threads with child runtimes will continue
559    /// executing after the thread with the parent runtime panics, but they will be in an
560    /// invalid and undefined state.
561    ///
562    /// This, like many calls to SpiderMoney API, is unsafe.
563    #[expect(unsafe_code)]
564    pub(crate) fn new(main_thread_sender: Option<ScriptEventLoopSender>) -> Runtime {
565        unsafe { Self::new_with_parent(None, main_thread_sender) }
566    }
567
568    #[allow(unsafe_code)]
569    /// ## Safety
570    /// - only one `JSContext` can exist on the thread at a time (see note in [JSContext::from_ptr])
571    /// - the `JSContext` must not outlive the `Runtime`
572    pub(crate) unsafe fn cx(&self) -> JSContext {
573        unsafe { JSContext::from_ptr(RustRuntime::get().unwrap()) }
574    }
575
576    /// Create a new runtime, optionally with the given [`ParentRuntime`] and [`SendableTaskSource`]
577    /// for networking.
578    ///
579    /// # Safety
580    ///
581    /// If panicking does not abort the program, any threads with child runtimes will continue
582    /// executing after the thread with the parent runtime panics, but they will be in an
583    /// invalid and undefined state.
584    ///
585    /// The `parent` pointer in the [`ParentRuntime`] argument must point to a valid object in memory.
586    ///
587    /// This, like many calls to the SpiderMoney API, is unsafe.
588    #[expect(unsafe_code)]
589    pub(crate) unsafe fn new_with_parent(
590        parent: Option<ParentRuntime>,
591        script_event_loop_sender: Option<ScriptEventLoopSender>,
592    ) -> Runtime {
593        let mut runtime = if let Some(parent) = parent {
594            unsafe { RustRuntime::create_with_parent(parent) }
595        } else {
596            RustRuntime::new(current_js_engine_handle())
597        };
598        let cx = runtime.cx();
599
600        let have_event_loop_sender = script_event_loop_sender.is_some();
601        let runtime_callback_data = Box::new(RuntimeCallbackData {
602            script_event_loop_sender,
603            script_thread: None,
604        });
605        let runtime_callback_data = Box::into_raw(runtime_callback_data);
606
607        unsafe {
608            JS_AddExtraGCRootsTracer(
609                cx,
610                Some(trace_rust_roots),
611                runtime_callback_data as *mut c_void,
612            );
613
614            JS_SetSecurityCallbacks(cx, &SECURITY_CALLBACKS);
615
616            JS_InitDestroyPrincipalsCallback(cx, Some(principals::destroy_servo_jsprincipal));
617            JS_InitReadPrincipalsCallback(cx, Some(principals::read_jsprincipal));
618
619            // Needed for debug assertions about whether GC is running.
620            if cfg!(debug_assertions) {
621                JS_SetGCCallback(cx, Some(debug_gc_callback), ptr::null_mut());
622            }
623
624            if opts::get()
625                .debug
626                .is_enabled(DiagnosticsLoggingOption::GcProfile)
627            {
628                SetGCSliceCallback(cx, Some(gc_slice_callback));
629            }
630        }
631
632        unsafe extern "C" fn empty_wrapper_callback(_: *mut RawJSContext, _: HandleObject) -> bool {
633            true
634        }
635        unsafe extern "C" fn empty_has_released_callback(_: HandleObject) -> bool {
636            // fixme: return true when the Drop impl for a DOM object has been invoked
637            false
638        }
639
640        unsafe {
641            SetDOMCallbacks(cx, &DOM_CALLBACKS);
642            SetPreserveWrapperCallbacks(
643                cx,
644                Some(empty_wrapper_callback),
645                Some(empty_has_released_callback),
646            );
647        }
648
649        unsafe extern "C" fn dispatch_to_event_loop(
650            data: *mut c_void,
651            dispatchable: *mut DispatchablePointer,
652        ) -> bool {
653            let runtime_callback_data: &RuntimeCallbackData =
654                unsafe { &*(data as *mut RuntimeCallbackData) };
655            let Some(script_event_loop_sender) =
656                runtime_callback_data.script_event_loop_sender.as_ref()
657            else {
658                return false;
659            };
660
661            let runnable = Runnable(dispatchable);
662            let task = task!(dispatch_to_event_loop_message: move |cx| {
663                runnable.run(cx, Dispatchable_MaybeShuttingDown::NotShuttingDown);
664            });
665
666            script_event_loop_sender
667                .send(CommonScriptMsg::Task(
668                    ScriptThreadEventCategory::NetworkEvent,
669                    Box::new(task),
670                    None, /* pipeline_id */
671                    TaskSourceName::Networking,
672                ))
673                .is_ok()
674        }
675
676        if have_event_loop_sender {
677            unsafe {
678                SetUpEventLoopDispatch(
679                    cx,
680                    Some(dispatch_to_event_loop),
681                    None,
682                    None,
683                    runtime_callback_data as *mut c_void,
684                );
685            }
686        }
687
688        unsafe {
689            InitConsumeStreamCallback(cx, Some(consume_stream), Some(report_stream_error));
690        }
691
692        let cx_opts;
693        let job_queue;
694        unsafe {
695            let cx = runtime.cx();
696            job_queue = JobQueue::new();
697            job_queue.set_on_context(cx);
698            SetPromiseRejectionTrackerCallback(
699                cx,
700                Some(promise_rejection_tracker),
701                ptr::null_mut(),
702            );
703
704            RegisterScriptEnvironmentPreparer(
705                cx.raw_cx(),
706                Some(invoke_script_environment_preparer),
707            );
708
709            EnsureModuleHooksInitialized(runtime.rt());
710
711            let cx = runtime.cx();
712
713            set_gc_zeal_options(cx.raw_cx());
714
715            // Enable or disable the JITs.
716            cx_opts = &mut *ContextOptionsRef(cx);
717            JS_SetGlobalJitCompilerOption(
718                cx,
719                JSJitCompilerOption::JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE,
720                pref!(js_baseline_interpreter_enabled) as u32,
721            );
722            JS_SetGlobalJitCompilerOption(
723                cx,
724                JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE,
725                pref!(js_baseline_jit_enabled) as u32,
726            );
727            JS_SetGlobalJitCompilerOption(
728                cx,
729                JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE,
730                pref!(js_ion_enabled) as u32,
731            );
732        }
733        cx_opts.compileOptions_.asmJSOption_ = if pref!(js_asmjs_enabled) {
734            AsmJSOption::Enabled
735        } else {
736            AsmJSOption::DisabledByAsmJSPref
737        };
738        let wasm_enabled = pref!(js_wasm_enabled);
739        cx_opts.set_wasm_(wasm_enabled);
740        if wasm_enabled {
741            // If WASM is enabled without setting the buildIdOp,
742            // initializing a module will report an out of memory error.
743            // https://dxr.mozilla.org/mozilla-central/source/js/src/wasm/WasmTypes.cpp#458
744            unsafe { SetProcessBuildIdOp(Some(servo_build_id)) };
745        }
746        cx_opts.set_wasmBaseline_(pref!(js_wasm_baseline_enabled));
747        cx_opts.set_wasmIon_(pref!(js_wasm_ion_enabled));
748
749        unsafe {
750            let cx = runtime.cx();
751            // TODO: handle js.throw_on_asmjs_validation_failure (needs new Spidermonkey)
752            JS_SetGlobalJitCompilerOption(
753                cx,
754                JSJitCompilerOption::JSJITCOMPILER_NATIVE_REGEXP_ENABLE,
755                pref!(js_native_regex_enabled) as u32,
756            );
757            JS_SetOffthreadIonCompilationEnabled(cx, pref!(js_offthread_compilation_enabled));
758            JS_SetGlobalJitCompilerOption(
759                cx,
760                JSJitCompilerOption::JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
761                if pref!(js_baseline_jit_unsafe_eager_compilation_enabled) {
762                    0
763                } else {
764                    u32::MAX
765                },
766            );
767            JS_SetGlobalJitCompilerOption(
768                cx,
769                JSJitCompilerOption::JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER,
770                if pref!(js_ion_unsafe_eager_compilation_enabled) {
771                    0
772                } else {
773                    u32::MAX
774                },
775            );
776            // TODO: handle js.discard_system_source.enabled
777            // TODO: handle js.asyncstack.enabled (needs new Spidermonkey)
778            // TODO: handle js.throw_on_debugee_would_run (needs new Spidermonkey)
779            // TODO: handle js.dump_stack_on_debugee_would_run (needs new Spidermonkey)
780            // TODO: handle js.shared_memory.enabled
781            JS_SetGCParameter(
782                cx,
783                JSGCParamKey::JSGC_MAX_BYTES,
784                in_range(pref!(js_mem_max), 1, 0x100)
785                    .map(|val| (val * 1024 * 1024) as u32)
786                    .unwrap_or(u32::MAX),
787            );
788
789            // Pre-barriers aren't implemented correctly at the moment, so this preference
790            // defaults to false.
791            JS_SetGCParameter(
792                cx,
793                JSGCParamKey::JSGC_INCREMENTAL_GC_ENABLED,
794                pref!(js_mem_gc_incremental_enabled) as u32,
795            );
796
797            JS_SetGCParameter(
798                cx,
799                JSGCParamKey::JSGC_PER_ZONE_GC_ENABLED,
800                pref!(js_mem_gc_per_zone_enabled) as u32,
801            );
802            if let Some(val) = in_range(pref!(js_mem_gc_incremental_slice_ms), 0, 100_000) {
803                JS_SetGCParameter(cx, JSGCParamKey::JSGC_SLICE_TIME_BUDGET_MS, val as u32);
804            }
805            JS_SetGCParameter(
806                cx,
807                JSGCParamKey::JSGC_COMPACTING_ENABLED,
808                pref!(js_mem_gc_compacting_enabled) as u32,
809            );
810
811            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_time_limit_ms), 0, 10_000) {
812                JS_SetGCParameter(cx, JSGCParamKey::JSGC_HIGH_FREQUENCY_TIME_LIMIT, val as u32);
813            }
814            if let Some(val) = in_range(pref!(js_mem_gc_low_frequency_heap_growth), 0, 10_000) {
815                JS_SetGCParameter(cx, JSGCParamKey::JSGC_LOW_FREQUENCY_HEAP_GROWTH, val as u32);
816            }
817            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_heap_growth_min), 0, 10_000)
818            {
819                JS_SetGCParameter(
820                    cx,
821                    JSGCParamKey::JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH,
822                    val as u32,
823                );
824            }
825            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_heap_growth_max), 0, 10_000)
826            {
827                JS_SetGCParameter(
828                    cx,
829                    JSGCParamKey::JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH,
830                    val as u32,
831                );
832            }
833            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_low_limit_mb), 0, 10_000) {
834                JS_SetGCParameter(cx, JSGCParamKey::JSGC_SMALL_HEAP_SIZE_MAX, val as u32);
835            }
836            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_high_limit_mb), 0, 10_000) {
837                JS_SetGCParameter(cx, JSGCParamKey::JSGC_LARGE_HEAP_SIZE_MIN, val as u32);
838            }
839            if let Some(val) = in_range(pref!(js_mem_gc_empty_chunk_count_min), 0, 10_000) {
840                JS_SetGCParameter(cx, JSGCParamKey::JSGC_MIN_EMPTY_CHUNK_COUNT, val as u32);
841            }
842            if let Some(val) = in_range(pref!(js_mem_gc_malloc_threshold_base_mb), 0, 10_000) {
843                JS_SetGCParameter(cx, JSGCParamKey::JSGC_MALLOC_THRESHOLD_BASE, val as u32);
844            }
845            if let Some(val) = in_range(pref!(js_mem_gc_urgent_threshold_mb), 0, 10_000) {
846                JS_SetGCParameter(cx, JSGCParamKey::JSGC_URGENT_THRESHOLD_MB, val as u32);
847            }
848        }
849        Runtime {
850            rt: runtime,
851            job_queue,
852            runtime_callback_data: unsafe { Box::from_raw(runtime_callback_data) },
853        }
854    }
855
856    pub(crate) fn set_script_thread(&mut self, script_thread: Weak<ScriptThread>) {
857        self.runtime_callback_data
858            .script_thread
859            .replace(script_thread);
860    }
861
862    pub(crate) fn thread_safe_js_context(&self) -> ThreadSafeJSContext {
863        self.rt.thread_safe_js_context()
864    }
865}
866
867impl Drop for Runtime {
868    fn drop(&mut self) {
869        // Clear our main microtask_queue.
870        job_queue_clear(self.rt.cx_no_gc());
871
872        LivePromiseReferences::destruct();
873        script_bindings::refcounted::LiveDOMReferences::destruct();
874        mark_runtime_dead();
875    }
876}
877
878impl Deref for Runtime {
879    type Target = RustRuntime;
880    fn deref(&self) -> &RustRuntime {
881        &self.rt
882    }
883}
884
885impl DerefMut for Runtime {
886    fn deref_mut(&mut self) -> &mut RustRuntime {
887        &mut self.rt
888    }
889}
890
891fn in_range<T: PartialOrd + Copy>(val: T, min: T, max: T) -> Option<T> {
892    if val < min || val >= max {
893        None
894    } else {
895        Some(val)
896    }
897}
898
899thread_local!(static MALLOC_SIZE_OF_OPS: Cell<*mut MallocSizeOfOps> = const { Cell::new(ptr::null_mut()) });
900
901#[derive(Default)]
902struct InterfaceSizeData {
903    /// How many live instances of this interface exist.
904    count: usize,
905    /// The total number of bytes allocated for instances of this interface.
906    bytes: usize,
907}
908
909struct GlobalSizeData {
910    /// The URL associated with this global.
911    url: ServoUrl,
912    /// A map of WebIDL interface names to size information.
913    interface_sizes: HashMap<&'static str, InterfaceSizeData>,
914    /// Is this global considered dead?
915    is_zombie: bool,
916}
917
918#[derive(Default)]
919/// A map of globals to size information for those globals.
920/// The key is the global's JS reflector pointer as an integer.
921pub(crate) struct PerGlobalInterfaceSizes(HashMap<usize, GlobalSizeData>);
922
923thread_local!(
924    static DOM_OBJECT_SIZES: LazyCell<RefCell<PerGlobalInterfaceSizes>> = const {
925        LazyCell::new(Default::default)
926    }
927);
928
929#[expect(unsafe_code)]
930unsafe extern "C" fn get_size(obj: *mut JSObject) -> usize {
931    let ops = MALLOC_SIZE_OF_OPS.get();
932    ALREADY_COMPUTED_OBJECTS.with(|objects| {
933        let ignored = objects.borrow();
934        DOM_OBJECT_SIZES.with(|dom_sizes| {
935            let mut per_global_interface_sizes = dom_sizes.borrow_mut();
936            compute_size(
937                obj,
938                unsafe { &mut *ops },
939                &ignored,
940                Some(&mut per_global_interface_sizes),
941            )
942        })
943    })
944}
945
946thread_local!(static GC_CYCLE_START: Cell<Option<Instant>> = const { Cell::new(None) });
947thread_local!(static GC_SLICE_START: Cell<Option<Instant>> = const { Cell::new(None) });
948
949#[expect(unsafe_code)]
950unsafe extern "C" fn gc_slice_callback(
951    _cx: *mut RawJSContext,
952    progress: GCProgress,
953    desc: *const GCDescription,
954) {
955    match progress {
956        GCProgress::GC_CYCLE_BEGIN => GC_CYCLE_START.with(|start| {
957            start.set(Some(Instant::now()));
958            println!("GC cycle began");
959        }),
960        GCProgress::GC_SLICE_BEGIN => GC_SLICE_START.with(|start| {
961            start.set(Some(Instant::now()));
962            println!("GC slice began");
963        }),
964        GCProgress::GC_SLICE_END => GC_SLICE_START.with(|start| {
965            let duration = start.get().unwrap().elapsed();
966            start.set(None);
967            println!("GC slice ended: duration={:?}", duration);
968        }),
969        GCProgress::GC_CYCLE_END => GC_CYCLE_START.with(|start| {
970            let duration = start.get().unwrap().elapsed();
971            start.set(None);
972            println!("GC cycle ended: duration={:?}", duration);
973        }),
974    };
975    if !desc.is_null() {
976        let desc: &GCDescription = unsafe { &*desc };
977        let options = match desc.options_ {
978            GCOptions::Normal => "Normal",
979            GCOptions::Shrink => "Shrink",
980            GCOptions::Shutdown => "Shutdown",
981        };
982        println!("  isZone={}, options={}", desc.isZone_, options);
983    }
984    let _ = stdout().flush();
985}
986
987#[expect(unsafe_code)]
988unsafe extern "C" fn debug_gc_callback(
989    _cx: *mut RawJSContext,
990    status: JSGCStatus,
991    _reason: GCReason,
992    _data: *mut os::raw::c_void,
993) {
994    match status {
995        JSGCStatus::JSGC_BEGIN => thread_state::enter(ThreadState::IN_GC),
996        JSGCStatus::JSGC_END => thread_state::exit(ThreadState::IN_GC),
997    }
998}
999
1000#[expect(unsafe_code)]
1001unsafe extern "C" fn trace_rust_roots(tr: *mut JSTracer, data: *mut os::raw::c_void) {
1002    if !runtime_is_alive() {
1003        return;
1004    }
1005    trace!("starting custom root handler");
1006
1007    let runtime_callback_data = unsafe { &*(data as *const RuntimeCallbackData) };
1008    if let Some(script_thread) = runtime_callback_data
1009        .script_thread
1010        .as_ref()
1011        .and_then(Weak::upgrade)
1012    {
1013        trace!("tracing fields of ScriptThread");
1014        unsafe { script_thread.trace(tr) };
1015    };
1016
1017    unsafe {
1018        trace_roots(tr);
1019        trace_refcounted_objects(tr);
1020        settings_stack::trace(tr);
1021    }
1022    trace!("done custom root handler");
1023}
1024
1025#[expect(unsafe_code)]
1026unsafe extern "C" fn servo_build_id(build_id: *mut BuildIdCharVector) -> bool {
1027    let servo_id = b"Servo\0";
1028    unsafe { SetBuildId(build_id, servo_id[0] as *const c_char, servo_id.len()) }
1029}
1030
1031#[expect(unsafe_code)]
1032#[cfg(feature = "debugmozjs")]
1033unsafe fn set_gc_zeal_options(cx: *mut RawJSContext) {
1034    use js::jsapi::SetGCZeal;
1035
1036    let level = match pref!(js_mem_gc_zeal_level) {
1037        level @ 0..=14 => level as u8,
1038        _ => return,
1039    };
1040    let frequency = match pref!(js_mem_gc_zeal_frequency) {
1041        frequency if frequency >= 0 => frequency as u32,
1042        // https://searchfox.org/mozilla-esr128/source/js/public/GCAPI.h#1392
1043        _ => 5000,
1044    };
1045    unsafe {
1046        SetGCZeal(cx, level, frequency);
1047    }
1048}
1049
1050#[expect(unsafe_code)]
1051#[cfg(not(feature = "debugmozjs"))]
1052unsafe fn set_gc_zeal_options(_: *mut RawJSContext) {}
1053
1054thread_local!(static ALREADY_COMPUTED_OBJECTS: LazyCell<RefCell<HashSet<*const JSObject>>> = const {
1055    LazyCell::new(Default::default)
1056});
1057
1058#[expect(unsafe_code)]
1059pub(crate) fn compute_size(
1060    obj: *mut JSObject,
1061    ops: &mut MallocSizeOfOps,
1062    ignored: &HashSet<*const JSObject>,
1063    per_global_interface_sizes: Option<&mut PerGlobalInterfaceSizes>,
1064) -> usize {
1065    if ignored.contains(&(obj as *const JSObject)) {
1066        return 0;
1067    }
1068
1069    match unsafe { get_dom_class(obj) } {
1070        Ok(v) => {
1071            let dom_object = unsafe { private_from_object(obj) as *const c_void };
1072
1073            if dom_object.is_null() {
1074                return 0;
1075            }
1076            let size = unsafe { (v.malloc_size_of)(&mut *ops, dom_object) };
1077
1078            let Some(per_global_interface_sizes) = per_global_interface_sizes else {
1079                return size;
1080            };
1081
1082            let global = unsafe { js::jsapi::GetNonCCWObjectGlobal(obj) };
1083            let interface = v.interface_chain[v.depth as usize];
1084            let interface_size = per_global_interface_sizes
1085                .0
1086                .entry(global as usize)
1087                .or_insert_with(|| {
1088                    let global = unsafe { GlobalScope::from_object(obj) };
1089                    GlobalSizeData {
1090                        url: global.get_url(),
1091                        interface_sizes: HashMap::new(),
1092                        is_zombie: global
1093                            .downcast::<Window>()
1094                            .is_some_and(|window| !window.is_alive()),
1095                    }
1096                })
1097                .interface_sizes
1098                .entry(interface.into())
1099                .or_default();
1100            interface_size.count += 1;
1101            interface_size.bytes += size;
1102            // Always report a size of zero, since we report this value
1103            // in a separate tree from the main JS heap.
1104            0
1105        },
1106        Err(_e) => 0,
1107    }
1108}
1109
1110#[expect(unsafe_code)]
1111pub(crate) fn get_reports(
1112    cx: &mut JSContext,
1113    path_seg: String,
1114    ops: &mut MallocSizeOfOps,
1115    already_computed_objects: HashSet<*const JSObject>,
1116) -> Vec<Report> {
1117    MALLOC_SIZE_OF_OPS.with(|ops_tls| ops_tls.set(ops));
1118    ALREADY_COMPUTED_OBJECTS.with(|objects| {
1119        *objects.borrow_mut() = already_computed_objects;
1120    });
1121
1122    let stats = unsafe {
1123        let mut stats = ::std::mem::zeroed();
1124        if !CollectServoSizes(cx, &mut stats, Some(get_size)) {
1125            return vec![];
1126        }
1127        stats
1128    };
1129    MALLOC_SIZE_OF_OPS.with(|ops| ops.set(ptr::null_mut()));
1130    ALREADY_COMPUTED_OBJECTS.with(|objects| {
1131        let mut objects = objects.borrow_mut();
1132        objects.clear();
1133        objects.shrink_to_fit();
1134    });
1135
1136    let mut reports = vec![];
1137    let mut report = |mut path_suffix, kind, size| {
1138        let mut path = path![path_seg, "js"];
1139        path.append(&mut path_suffix);
1140        reports.push(Report { path, kind, size })
1141    };
1142
1143    DOM_OBJECT_SIZES.with(|sizes| {
1144        let mut sizes = sizes.borrow_mut();
1145        let mut known_globals = HashMap::new();
1146        for global_size_data in sizes.0.values() {
1147            let url = global_size_data.url.as_str();
1148            let suffix = if global_size_data.is_zombie {
1149                "-zombie"
1150            } else {
1151                ""
1152            };
1153            let index = known_globals.entry(url).or_insert(0);
1154            *index += 1;
1155            for (interface, interface_data) in &global_size_data.interface_sizes {
1156                report(
1157                    path![
1158                        "dom",
1159                        "out-of-tree",
1160                        format!("url({url}){suffix}-{}", *index),
1161                        format!("{interface} [{}]", interface_data.count)
1162                    ],
1163                    ReportKind::ExplicitJemallocHeapSize,
1164                    interface_data.bytes,
1165                );
1166            }
1167        }
1168        sizes.0.clear();
1169    });
1170
1171    // A note about possibly confusing terminology: the JS GC "heap" is allocated via
1172    // mmap/VirtualAlloc, which means it's not on the malloc "heap", so we use
1173    // `ExplicitNonHeapSize` as its kind.
1174    report(
1175        path!["gc-heap", "used"],
1176        ReportKind::ExplicitNonHeapSize,
1177        stats.gcHeapUsed,
1178    );
1179
1180    report(
1181        path!["gc-heap", "unused"],
1182        ReportKind::ExplicitNonHeapSize,
1183        stats.gcHeapUnused,
1184    );
1185
1186    report(
1187        path!["gc-heap", "admin"],
1188        ReportKind::ExplicitNonHeapSize,
1189        stats.gcHeapAdmin,
1190    );
1191
1192    report(
1193        path!["gc-heap", "decommitted"],
1194        ReportKind::ExplicitNonHeapSize,
1195        stats.gcHeapDecommitted,
1196    );
1197
1198    // SpiderMonkey uses the system heap, not jemalloc.
1199    report(
1200        path!["malloc-heap"],
1201        ReportKind::ExplicitSystemHeapSize,
1202        stats.mallocHeap,
1203    );
1204
1205    report(
1206        path!["non-heap"],
1207        ReportKind::ExplicitNonHeapSize,
1208        stats.nonHeap,
1209    );
1210    reports
1211}
1212
1213pub(crate) struct StreamConsumer(*mut JSStreamConsumer);
1214
1215#[expect(unsafe_code)]
1216impl StreamConsumer {
1217    pub(crate) fn consume_chunk(&self, stream: &[u8]) -> bool {
1218        unsafe {
1219            let stream_ptr = stream.as_ptr();
1220            StreamConsumerConsumeChunk(self.0, stream_ptr, stream.len())
1221        }
1222    }
1223
1224    pub(crate) fn stream_end(&self) {
1225        unsafe {
1226            StreamConsumerStreamEnd(self.0);
1227        }
1228    }
1229
1230    pub(crate) fn stream_error(&self, error_code: usize) {
1231        unsafe {
1232            StreamConsumerStreamError(self.0, error_code);
1233        }
1234    }
1235
1236    pub(crate) fn note_response_urls(
1237        &self,
1238        maybe_url: Option<String>,
1239        maybe_source_map_url: Option<String>,
1240    ) {
1241        unsafe {
1242            let maybe_url = maybe_url.map(|url| CString::new(url).unwrap());
1243            let maybe_source_map_url = maybe_source_map_url.map(|url| CString::new(url).unwrap());
1244
1245            let maybe_url_param = match maybe_url.as_ref() {
1246                Some(url) => url.as_ptr(),
1247                None => ptr::null(),
1248            };
1249            let maybe_source_map_url_param = match maybe_source_map_url.as_ref() {
1250                Some(url) => url.as_ptr(),
1251                None => ptr::null(),
1252            };
1253
1254            StreamConsumerNoteResponseURLs(self.0, maybe_url_param, maybe_source_map_url_param);
1255        }
1256    }
1257}
1258
1259/// Implements the steps to compile webassembly response mentioned here
1260/// <https://webassembly.github.io/spec/web-api/#compile-a-potential-webassembly-response>
1261#[expect(unsafe_code)]
1262unsafe extern "C" fn consume_stream(
1263    cx: *mut RawJSContext,
1264    obj: HandleObject,
1265    _mime_type: MimeType,
1266    _consumer: *mut JSStreamConsumer,
1267) -> bool {
1268    let mut cx = unsafe {
1269        // SAFETY: We are in SM hook
1270        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1271    };
1272    let cx = &mut cx;
1273    let mut realm = CurrentRealm::assert(cx);
1274    let global = GlobalScope::from_current_realm(&mut realm);
1275
1276    // Step 2.1 Upon fulfillment of source, store the Response with value unwrappedSource.
1277    if let Ok(unwrapped_source) =
1278        unsafe { root_from_handleobject::<Response>(cx, RustHandleObject::from_raw(obj)) }
1279    {
1280        // Step 2.2 Let mimeType be the result of extracting a MIME type from response’s header list.
1281        let mimetype = unwrapped_source.Headers(cx).extract_mime_type();
1282
1283        // Step 2.3 If mimeType is not `application/wasm`, return with a TypeError and abort these substeps.
1284        if !&mimetype[..].eq_ignore_ascii_case(b"application/wasm") {
1285            throw_dom_exception(
1286                cx,
1287                &global,
1288                Error::Type(c"Response has unsupported MIME type".to_owned()),
1289            );
1290            return false;
1291        }
1292
1293        // Step 2.4 If response is not CORS-same-origin, return with a TypeError and abort these substeps.
1294        match unwrapped_source.Type() {
1295            DOMResponseType::Basic | DOMResponseType::Cors | DOMResponseType::Default => {},
1296            _ => {
1297                throw_dom_exception(
1298                    cx,
1299                    &global,
1300                    Error::Type(c"Response.type must be 'basic', 'cors' or 'default'".to_owned()),
1301                );
1302                return false;
1303            },
1304        }
1305
1306        // Step 2.5 If response’s status is not an ok status, return with a TypeError and abort these substeps.
1307        if !unwrapped_source.Ok() {
1308            throw_dom_exception(
1309                cx,
1310                &global,
1311                Error::Type(c"Response does not have ok status".to_owned()),
1312            );
1313            return false;
1314        }
1315
1316        // Step 2.6.1 If response body is locked, return with a TypeError and abort these substeps.
1317        if unwrapped_source.is_locked() {
1318            throw_dom_exception(
1319                cx,
1320                &global,
1321                Error::Type(c"There was an error consuming the Response".to_owned()),
1322            );
1323            return false;
1324        }
1325
1326        // Step 2.6.2 If response body is alreaady consumed, return with a TypeError and abort these substeps.
1327        if unwrapped_source.is_disturbed() {
1328            throw_dom_exception(
1329                cx,
1330                &global,
1331                Error::Type(c"Response already consumed".to_owned()),
1332            );
1333            return false;
1334        }
1335        unwrapped_source.set_stream_consumer(Some(StreamConsumer(_consumer)));
1336    } else {
1337        // Step 3 Upon rejection of source, return with reason.
1338        throw_dom_exception(
1339            cx,
1340            &global,
1341            Error::Type(c"expected Response or Promise resolving to Response".to_owned()),
1342        );
1343        return false;
1344    }
1345    true
1346}
1347
1348#[expect(unsafe_code)]
1349unsafe extern "C" fn report_stream_error(_cx: *mut RawJSContext, error_code: usize) {
1350    error!("Error initializing StreamConsumer: {:?}", unsafe {
1351        RUST_js_GetErrorMessage(ptr::null_mut(), error_code as u32)
1352    });
1353}
1354
1355#[expect(unsafe_code)]
1356unsafe extern "C" fn invoke_script_environment_preparer(
1357    global: HandleObject,
1358    closure: *mut ScriptEnvironmentPreparer_Closure,
1359) {
1360    // SAFETY: always safe from a JS engine hook.
1361    let mut cx = unsafe { temp_cx() };
1362    let global = unsafe { GlobalScope::from_object(global.get()) };
1363    let mut realm = enter_auto_realm(&mut cx, &*global);
1364    let cx = &mut realm.current_realm();
1365
1366    run_a_script::<DomTypeHolder, _, _>(cx, &global, |cx| {
1367        if unsafe { !RunScriptEnvironmentPreparerClosure(cx.raw_cx(), closure) } {
1368            report_pending_exception(cx);
1369        };
1370    });
1371}
1372
1373pub(crate) struct Runnable(*mut DispatchablePointer);
1374
1375#[expect(unsafe_code)]
1376unsafe impl Sync for Runnable {}
1377#[expect(unsafe_code)]
1378unsafe impl Send for Runnable {}
1379
1380#[expect(unsafe_code)]
1381impl Runnable {
1382    fn run(&self, cx: &mut JSContext, maybe_shutting_down: Dispatchable_MaybeShuttingDown) {
1383        unsafe {
1384            DispatchableRun(cx, self.0, maybe_shutting_down);
1385        }
1386    }
1387}
1388
1389/// `introductionType` values in SpiderMonkey TransitiveCompileOptions.
1390///
1391/// Value definitions are based on the SpiderMonkey Debugger API docs:
1392/// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Source.html#introductiontype>
1393// TODO: squish `scriptElement` <https://searchfox.org/mozilla-central/rev/202069c4c5113a1a9052d84fa4679d4c1b22113e/devtools/server/actors/source.js#199-201>
1394pub(crate) struct IntroductionType;
1395impl IntroductionType {
1396    /// `introductionType` for code passed to `eval`.
1397    pub const EVAL: &CStr = c"eval";
1398    pub const EVAL_STR: &str = "eval";
1399
1400    /// `introductionType` for code evaluated by debugger.
1401    /// This includes code run via the devtools repl, even if the thread is not paused.
1402    pub const DEBUGGER_EVAL: &CStr = c"debugger eval";
1403    pub const DEBUGGER_EVAL_STR: &str = "debugger eval";
1404
1405    /// `introductionType` for code passed to the `Function` constructor.
1406    pub const FUNCTION: &CStr = c"Function";
1407    pub const FUNCTION_STR: &str = "Function";
1408
1409    /// `introductionType` for code loaded by worklet.
1410    pub const WORKLET: &CStr = c"Worklet";
1411    pub const WORKLET_STR: &str = "Worklet";
1412
1413    /// `introductionType` for code assigned to DOM elements’ event handler IDL attributes as a string.
1414    pub const EVENT_HANDLER: &CStr = c"eventHandler";
1415    pub const EVENT_HANDLER_STR: &str = "eventHandler";
1416
1417    /// `introductionType` for code belonging to `<script src="file.js">` elements.
1418    /// This includes `<script type="module" src="...">`.
1419    pub const SRC_SCRIPT: &CStr = c"srcScript";
1420    pub const SRC_SCRIPT_STR: &str = "srcScript";
1421
1422    /// `introductionType` for code belonging to `<script>code;</script>` elements.
1423    /// This includes `<script type="module" src="...">`.
1424    pub const INLINE_SCRIPT: &CStr = c"inlineScript";
1425    pub const INLINE_SCRIPT_STR: &str = "inlineScript";
1426
1427    /// `introductionType` for code belonging to scripts that *would* be `"inlineScript"` except that they were not
1428    /// part of the initial file itself.
1429    /// For example, scripts created via:
1430    /// - `document.write("<script>code;</script>")`
1431    /// - `var s = document.createElement("script"); s.text = "code";`
1432    pub const INJECTED_SCRIPT: &CStr = c"injectedScript";
1433    pub const INJECTED_SCRIPT_STR: &str = "injectedScript";
1434
1435    /// `introductionType` for code that was loaded indirectly by being imported by another script
1436    /// using ESM static or dynamic imports.
1437    pub const IMPORTED_MODULE: &CStr = c"importedModule";
1438    pub const IMPORTED_MODULE_STR: &str = "importedModule";
1439
1440    /// `introductionType` for code presented in `javascript:` URLs.
1441    pub const JAVASCRIPT_URL: &CStr = c"javascriptURL";
1442    pub const JAVASCRIPT_URL_STR: &str = "javascriptURL";
1443
1444    /// `introductionType` for code passed to `setTimeout`/`setInterval` as a string.
1445    pub const DOM_TIMER: &CStr = c"domTimer";
1446    pub const DOM_TIMER_STR: &str = "domTimer";
1447
1448    /// `introductionType` for web workers.
1449    /// FIXME: only documented in older(?) devtools user docs
1450    /// <https://firefox-source-docs.mozilla.org/devtools-user/debugger-api/debugger.source/index.html>
1451    pub const WORKER: &CStr = c"Worker";
1452    pub const WORKER_STR: &str = "Worker";
1453}