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::realms::enter_auto_realm;
97use crate::runtime::job_queue::{JobQueue, job_queue_clear};
98use crate::scripting::script_module::EnsureModuleHooksInitialized;
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 global = target.global();
518                    let reason_string = run_a_script::<DomTypeHolder, _, _>(
519                        cx,
520                        &global,
521                        |cx: &mut JSContext| stringify_handle_value(cx, reason.handle()),
522                    );
523                    Console::internal_error(
524                        cx,
525                        &global,
526                        format!("Unhandled promise rejection: {reason_string}")
527                    );
528                }
529
530                // Step 4.1.4 If p.[[PromiseIsHandled]] is false, then append p to global's outstanding
531                // rejected promises weak set.
532                if !promise.get_promise_is_handled() {
533                    target.global().add_consumed_rejection(promise.reflector().get_jsobject().into_handle());
534                }
535            }
536        })
537    );
538}
539
540/// Data that is sent to SpiderMonkey runtime callbacks as a pointer, which allows access
541/// to the `Runtime` state.
542#[derive(Default, JSTraceable, MallocSizeOf)]
543struct RuntimeCallbackData {
544    script_event_loop_sender: Option<ScriptEventLoopSender>,
545    #[no_trace]
546    #[ignore_malloc_size_of = "ScriptThread measures its own memory itself."]
547    script_thread: Option<Weak<ScriptThread>>,
548}
549
550#[derive(JSTraceable, MallocSizeOf)]
551pub(crate) struct Runtime {
552    #[ignore_malloc_size_of = "Type from mozjs"]
553    rt: RustRuntime,
554    job_queue: JobQueue,
555    /// The data that is set on the SpiderMonkey runtime callbacks as a pointer.
556    runtime_callback_data: Box<RuntimeCallbackData>,
557}
558
559impl Runtime {
560    /// Create a new runtime, optionally with the given [`SendableTaskSource`] for networking.
561    ///
562    /// # Safety
563    ///
564    /// If panicking does not abort the program, any threads with child runtimes will continue
565    /// executing after the thread with the parent runtime panics, but they will be in an
566    /// invalid and undefined state.
567    ///
568    /// This, like many calls to SpiderMoney API, is unsafe.
569    #[expect(unsafe_code)]
570    pub(crate) fn new(main_thread_sender: Option<ScriptEventLoopSender>) -> Runtime {
571        unsafe { Self::new_with_parent(None, main_thread_sender) }
572    }
573
574    #[allow(unsafe_code)]
575    /// ## Safety
576    /// - only one `JSContext` can exist on the thread at a time (see note in [JSContext::from_ptr])
577    /// - the `JSContext` must not outlive the `Runtime`
578    pub(crate) unsafe fn cx(&self) -> JSContext {
579        unsafe { JSContext::from_ptr(RustRuntime::get().unwrap()) }
580    }
581
582    /// Create a new runtime, optionally with the given [`ParentRuntime`] and [`SendableTaskSource`]
583    /// for networking.
584    ///
585    /// # Safety
586    ///
587    /// If panicking does not abort the program, any threads with child runtimes will continue
588    /// executing after the thread with the parent runtime panics, but they will be in an
589    /// invalid and undefined state.
590    ///
591    /// The `parent` pointer in the [`ParentRuntime`] argument must point to a valid object in memory.
592    ///
593    /// This, like many calls to the SpiderMoney API, is unsafe.
594    #[expect(unsafe_code)]
595    pub(crate) unsafe fn new_with_parent(
596        parent: Option<ParentRuntime>,
597        script_event_loop_sender: Option<ScriptEventLoopSender>,
598    ) -> Runtime {
599        let mut runtime = if let Some(parent) = parent {
600            unsafe { RustRuntime::create_with_parent(parent) }
601        } else {
602            RustRuntime::new(current_js_engine_handle())
603        };
604        let cx = runtime.cx();
605
606        let have_event_loop_sender = script_event_loop_sender.is_some();
607        let runtime_callback_data = Box::new(RuntimeCallbackData {
608            script_event_loop_sender,
609            script_thread: None,
610        });
611        let runtime_callback_data = Box::into_raw(runtime_callback_data);
612
613        unsafe {
614            JS_AddExtraGCRootsTracer(
615                cx,
616                Some(trace_rust_roots),
617                runtime_callback_data as *mut c_void,
618            );
619
620            JS_SetSecurityCallbacks(cx, &SECURITY_CALLBACKS);
621
622            JS_InitDestroyPrincipalsCallback(cx, Some(principals::destroy_servo_jsprincipal));
623            JS_InitReadPrincipalsCallback(cx, Some(principals::read_jsprincipal));
624
625            // Needed for debug assertions about whether GC is running.
626            if cfg!(debug_assertions) {
627                JS_SetGCCallback(cx, Some(debug_gc_callback), ptr::null_mut());
628            }
629
630            if opts::get()
631                .debug
632                .is_enabled(DiagnosticsLoggingOption::GcProfile)
633            {
634                SetGCSliceCallback(cx, Some(gc_slice_callback));
635            }
636        }
637
638        unsafe extern "C" fn empty_wrapper_callback(_: *mut RawJSContext, _: HandleObject) -> bool {
639            true
640        }
641        unsafe extern "C" fn empty_has_released_callback(_: HandleObject) -> bool {
642            // fixme: return true when the Drop impl for a DOM object has been invoked
643            false
644        }
645
646        unsafe {
647            SetDOMCallbacks(cx, &DOM_CALLBACKS);
648            SetPreserveWrapperCallbacks(
649                cx,
650                Some(empty_wrapper_callback),
651                Some(empty_has_released_callback),
652            );
653        }
654
655        unsafe extern "C" fn dispatch_to_event_loop(
656            data: *mut c_void,
657            dispatchable: *mut DispatchablePointer,
658        ) -> bool {
659            let runtime_callback_data: &RuntimeCallbackData =
660                unsafe { &*(data as *mut RuntimeCallbackData) };
661            let Some(script_event_loop_sender) =
662                runtime_callback_data.script_event_loop_sender.as_ref()
663            else {
664                return false;
665            };
666
667            let runnable = Runnable(dispatchable);
668            let task = task!(dispatch_to_event_loop_message: move |cx| {
669                runnable.run(cx, Dispatchable_MaybeShuttingDown::NotShuttingDown);
670            });
671
672            script_event_loop_sender
673                .send(CommonScriptMsg::Task(
674                    ScriptThreadEventCategory::NetworkEvent,
675                    Box::new(task),
676                    None, /* pipeline_id */
677                    TaskSourceName::Networking,
678                ))
679                .is_ok()
680        }
681
682        if have_event_loop_sender {
683            unsafe {
684                SetUpEventLoopDispatch(
685                    cx,
686                    Some(dispatch_to_event_loop),
687                    None,
688                    None,
689                    runtime_callback_data as *mut c_void,
690                );
691            }
692        }
693
694        unsafe {
695            InitConsumeStreamCallback(cx, Some(consume_stream), Some(report_stream_error));
696        }
697
698        let cx_opts;
699        let job_queue;
700        unsafe {
701            let cx = runtime.cx();
702            job_queue = JobQueue::new();
703            job_queue.set_on_context(cx);
704            SetPromiseRejectionTrackerCallback(
705                cx,
706                Some(promise_rejection_tracker),
707                ptr::null_mut(),
708            );
709
710            RegisterScriptEnvironmentPreparer(
711                cx.raw_cx(),
712                Some(invoke_script_environment_preparer),
713            );
714
715            EnsureModuleHooksInitialized(runtime.rt());
716
717            let cx = runtime.cx();
718
719            set_gc_zeal_options(cx.raw_cx());
720
721            // Enable or disable the JITs.
722            cx_opts = &mut *ContextOptionsRef(cx);
723            JS_SetGlobalJitCompilerOption(
724                cx,
725                JSJitCompilerOption::JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE,
726                pref!(js_baseline_interpreter_enabled) as u32,
727            );
728            JS_SetGlobalJitCompilerOption(
729                cx,
730                JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE,
731                pref!(js_baseline_jit_enabled) as u32,
732            );
733            JS_SetGlobalJitCompilerOption(
734                cx,
735                JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE,
736                pref!(js_ion_enabled) as u32,
737            );
738        }
739        cx_opts.compileOptions_.asmJSOption_ = if pref!(js_asmjs_enabled) {
740            AsmJSOption::Enabled
741        } else {
742            AsmJSOption::DisabledByAsmJSPref
743        };
744        let wasm_enabled = pref!(js_wasm_enabled);
745        cx_opts.set_wasm_(wasm_enabled);
746        if wasm_enabled {
747            // If WASM is enabled without setting the buildIdOp,
748            // initializing a module will report an out of memory error.
749            // https://dxr.mozilla.org/mozilla-central/source/js/src/wasm/WasmTypes.cpp#458
750            unsafe { SetProcessBuildIdOp(Some(servo_build_id)) };
751        }
752        cx_opts.set_wasmBaseline_(pref!(js_wasm_baseline_enabled));
753        cx_opts.set_wasmIon_(pref!(js_wasm_ion_enabled));
754
755        unsafe {
756            let cx = runtime.cx();
757            // TODO: handle js.throw_on_asmjs_validation_failure (needs new Spidermonkey)
758            JS_SetGlobalJitCompilerOption(
759                cx,
760                JSJitCompilerOption::JSJITCOMPILER_NATIVE_REGEXP_ENABLE,
761                pref!(js_native_regex_enabled) as u32,
762            );
763            JS_SetOffthreadIonCompilationEnabled(cx, pref!(js_offthread_compilation_enabled));
764            JS_SetGlobalJitCompilerOption(
765                cx,
766                JSJitCompilerOption::JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
767                if pref!(js_baseline_jit_unsafe_eager_compilation_enabled) {
768                    0
769                } else {
770                    u32::MAX
771                },
772            );
773            JS_SetGlobalJitCompilerOption(
774                cx,
775                JSJitCompilerOption::JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER,
776                if pref!(js_ion_unsafe_eager_compilation_enabled) {
777                    0
778                } else {
779                    u32::MAX
780                },
781            );
782            // TODO: handle js.discard_system_source.enabled
783            // TODO: handle js.asyncstack.enabled (needs new Spidermonkey)
784            // TODO: handle js.throw_on_debugee_would_run (needs new Spidermonkey)
785            // TODO: handle js.dump_stack_on_debugee_would_run (needs new Spidermonkey)
786            // TODO: handle js.shared_memory.enabled
787            JS_SetGCParameter(
788                cx,
789                JSGCParamKey::JSGC_MAX_BYTES,
790                in_range(pref!(js_mem_max), 1, 0x100)
791                    .map(|val| (val * 1024 * 1024) as u32)
792                    .unwrap_or(u32::MAX),
793            );
794
795            // Pre-barriers aren't implemented correctly at the moment, so this preference
796            // defaults to false.
797            JS_SetGCParameter(
798                cx,
799                JSGCParamKey::JSGC_INCREMENTAL_GC_ENABLED,
800                pref!(js_mem_gc_incremental_enabled) as u32,
801            );
802
803            JS_SetGCParameter(
804                cx,
805                JSGCParamKey::JSGC_PER_ZONE_GC_ENABLED,
806                pref!(js_mem_gc_per_zone_enabled) as u32,
807            );
808            if let Some(val) = in_range(pref!(js_mem_gc_incremental_slice_ms), 0, 100_000) {
809                JS_SetGCParameter(cx, JSGCParamKey::JSGC_SLICE_TIME_BUDGET_MS, val as u32);
810            }
811            JS_SetGCParameter(
812                cx,
813                JSGCParamKey::JSGC_COMPACTING_ENABLED,
814                pref!(js_mem_gc_compacting_enabled) as u32,
815            );
816
817            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_time_limit_ms), 0, 10_000) {
818                JS_SetGCParameter(cx, JSGCParamKey::JSGC_HIGH_FREQUENCY_TIME_LIMIT, val as u32);
819            }
820            if let Some(val) = in_range(pref!(js_mem_gc_low_frequency_heap_growth), 0, 10_000) {
821                JS_SetGCParameter(cx, JSGCParamKey::JSGC_LOW_FREQUENCY_HEAP_GROWTH, val as u32);
822            }
823            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_heap_growth_min), 0, 10_000)
824            {
825                JS_SetGCParameter(
826                    cx,
827                    JSGCParamKey::JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH,
828                    val as u32,
829                );
830            }
831            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_heap_growth_max), 0, 10_000)
832            {
833                JS_SetGCParameter(
834                    cx,
835                    JSGCParamKey::JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH,
836                    val as u32,
837                );
838            }
839            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_low_limit_mb), 0, 10_000) {
840                JS_SetGCParameter(cx, JSGCParamKey::JSGC_SMALL_HEAP_SIZE_MAX, val as u32);
841            }
842            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_high_limit_mb), 0, 10_000) {
843                JS_SetGCParameter(cx, JSGCParamKey::JSGC_LARGE_HEAP_SIZE_MIN, val as u32);
844            }
845            if let Some(val) = in_range(pref!(js_mem_gc_empty_chunk_count_min), 0, 10_000) {
846                JS_SetGCParameter(cx, JSGCParamKey::JSGC_MIN_EMPTY_CHUNK_COUNT, val as u32);
847            }
848            if let Some(val) = in_range(pref!(js_mem_gc_malloc_threshold_base_mb), 0, 10_000) {
849                JS_SetGCParameter(cx, JSGCParamKey::JSGC_MALLOC_THRESHOLD_BASE, val as u32);
850            }
851            if let Some(val) = in_range(pref!(js_mem_gc_urgent_threshold_mb), 0, 10_000) {
852                JS_SetGCParameter(cx, JSGCParamKey::JSGC_URGENT_THRESHOLD_MB, val as u32);
853            }
854        }
855        Runtime {
856            rt: runtime,
857            job_queue,
858            runtime_callback_data: unsafe { Box::from_raw(runtime_callback_data) },
859        }
860    }
861
862    pub(crate) fn set_script_thread(&mut self, script_thread: Weak<ScriptThread>) {
863        self.runtime_callback_data
864            .script_thread
865            .replace(script_thread);
866    }
867
868    pub(crate) fn thread_safe_js_context(&self) -> ThreadSafeJSContext {
869        self.rt.thread_safe_js_context()
870    }
871}
872
873impl Drop for Runtime {
874    fn drop(&mut self) {
875        // Clear our main microtask_queue.
876        job_queue_clear(self.rt.cx_no_gc());
877
878        LivePromiseReferences::destruct();
879        script_bindings::refcounted::LiveDOMReferences::destruct();
880        mark_runtime_dead();
881    }
882}
883
884impl Deref for Runtime {
885    type Target = RustRuntime;
886    fn deref(&self) -> &RustRuntime {
887        &self.rt
888    }
889}
890
891impl DerefMut for Runtime {
892    fn deref_mut(&mut self) -> &mut RustRuntime {
893        &mut self.rt
894    }
895}
896
897fn in_range<T: PartialOrd + Copy>(val: T, min: T, max: T) -> Option<T> {
898    if val < min || val >= max {
899        None
900    } else {
901        Some(val)
902    }
903}
904
905thread_local!(static MALLOC_SIZE_OF_OPS: Cell<*mut MallocSizeOfOps> = const { Cell::new(ptr::null_mut()) });
906
907#[derive(Default)]
908struct InterfaceSizeData {
909    /// How many live instances of this interface exist.
910    count: usize,
911    /// The total number of bytes allocated for instances of this interface.
912    bytes: usize,
913}
914
915struct GlobalSizeData {
916    /// The URL associated with this global.
917    url: ServoUrl,
918    /// A map of WebIDL interface names to size information.
919    interface_sizes: HashMap<&'static str, InterfaceSizeData>,
920    /// Is this global considered dead?
921    is_zombie: bool,
922}
923
924#[derive(Default)]
925/// A map of globals to size information for those globals.
926/// The key is the global's JS reflector pointer as an integer.
927pub(crate) struct PerGlobalInterfaceSizes(HashMap<usize, GlobalSizeData>);
928
929thread_local!(
930    static DOM_OBJECT_SIZES: LazyCell<RefCell<PerGlobalInterfaceSizes>> = const {
931        LazyCell::new(Default::default)
932    }
933);
934
935#[expect(unsafe_code)]
936unsafe extern "C" fn get_size(obj: *mut JSObject) -> usize {
937    let ops = MALLOC_SIZE_OF_OPS.get();
938    ALREADY_COMPUTED_OBJECTS.with(|objects| {
939        let ignored = objects.borrow();
940        DOM_OBJECT_SIZES.with(|dom_sizes| {
941            let mut per_global_interface_sizes = dom_sizes.borrow_mut();
942            compute_size(
943                obj,
944                unsafe { &mut *ops },
945                &ignored,
946                Some(&mut per_global_interface_sizes),
947            )
948        })
949    })
950}
951
952thread_local!(static GC_CYCLE_START: Cell<Option<Instant>> = const { Cell::new(None) });
953thread_local!(static GC_SLICE_START: Cell<Option<Instant>> = const { Cell::new(None) });
954
955#[expect(unsafe_code)]
956unsafe extern "C" fn gc_slice_callback(
957    _cx: *mut RawJSContext,
958    progress: GCProgress,
959    desc: *const GCDescription,
960) {
961    match progress {
962        GCProgress::GC_CYCLE_BEGIN => GC_CYCLE_START.with(|start| {
963            start.set(Some(Instant::now()));
964            println!("GC cycle began");
965        }),
966        GCProgress::GC_SLICE_BEGIN => GC_SLICE_START.with(|start| {
967            start.set(Some(Instant::now()));
968            println!("GC slice began");
969        }),
970        GCProgress::GC_SLICE_END => GC_SLICE_START.with(|start| {
971            let duration = start.get().unwrap().elapsed();
972            start.set(None);
973            println!("GC slice ended: duration={:?}", duration);
974        }),
975        GCProgress::GC_CYCLE_END => GC_CYCLE_START.with(|start| {
976            let duration = start.get().unwrap().elapsed();
977            start.set(None);
978            println!("GC cycle ended: duration={:?}", duration);
979        }),
980    };
981    if !desc.is_null() {
982        let desc: &GCDescription = unsafe { &*desc };
983        let options = match desc.options_ {
984            GCOptions::Normal => "Normal",
985            GCOptions::Shrink => "Shrink",
986            GCOptions::Shutdown => "Shutdown",
987        };
988        println!("  isZone={}, options={}", desc.isZone_, options);
989    }
990    let _ = stdout().flush();
991}
992
993#[expect(unsafe_code)]
994unsafe extern "C" fn debug_gc_callback(
995    _cx: *mut RawJSContext,
996    status: JSGCStatus,
997    _reason: GCReason,
998    _data: *mut os::raw::c_void,
999) {
1000    match status {
1001        JSGCStatus::JSGC_BEGIN => thread_state::enter(ThreadState::IN_GC),
1002        JSGCStatus::JSGC_END => thread_state::exit(ThreadState::IN_GC),
1003    }
1004}
1005
1006#[expect(unsafe_code)]
1007unsafe extern "C" fn trace_rust_roots(tr: *mut JSTracer, data: *mut os::raw::c_void) {
1008    if !runtime_is_alive() {
1009        return;
1010    }
1011    trace!("starting custom root handler");
1012
1013    let runtime_callback_data = unsafe { &*(data as *const RuntimeCallbackData) };
1014    if let Some(script_thread) = runtime_callback_data
1015        .script_thread
1016        .as_ref()
1017        .and_then(Weak::upgrade)
1018    {
1019        trace!("tracing fields of ScriptThread");
1020        unsafe { script_thread.trace(tr) };
1021    };
1022
1023    unsafe {
1024        trace_roots(tr);
1025        trace_refcounted_objects(tr);
1026        settings_stack::trace(tr);
1027    }
1028    trace!("done custom root handler");
1029}
1030
1031#[expect(unsafe_code)]
1032unsafe extern "C" fn servo_build_id(build_id: *mut BuildIdCharVector) -> bool {
1033    let servo_id = b"Servo\0";
1034    unsafe { SetBuildId(build_id, servo_id[0] as *const c_char, servo_id.len()) }
1035}
1036
1037#[expect(unsafe_code)]
1038#[cfg(feature = "debugmozjs")]
1039unsafe fn set_gc_zeal_options(cx: *mut RawJSContext) {
1040    use js::jsapi::SetGCZeal;
1041
1042    let level = match pref!(js_mem_gc_zeal_level) {
1043        level @ 0..=14 => level as u8,
1044        _ => return,
1045    };
1046    let frequency = match pref!(js_mem_gc_zeal_frequency) {
1047        frequency if frequency >= 0 => frequency as u32,
1048        // https://searchfox.org/mozilla-esr128/source/js/public/GCAPI.h#1392
1049        _ => 5000,
1050    };
1051    unsafe {
1052        SetGCZeal(cx, level, frequency);
1053    }
1054}
1055
1056#[expect(unsafe_code)]
1057#[cfg(not(feature = "debugmozjs"))]
1058unsafe fn set_gc_zeal_options(_: *mut RawJSContext) {}
1059
1060thread_local!(static ALREADY_COMPUTED_OBJECTS: LazyCell<RefCell<HashSet<*const JSObject>>> = const {
1061    LazyCell::new(Default::default)
1062});
1063
1064#[expect(unsafe_code)]
1065pub(crate) fn compute_size(
1066    obj: *mut JSObject,
1067    ops: &mut MallocSizeOfOps,
1068    ignored: &HashSet<*const JSObject>,
1069    per_global_interface_sizes: Option<&mut PerGlobalInterfaceSizes>,
1070) -> usize {
1071    if ignored.contains(&(obj as *const JSObject)) {
1072        return 0;
1073    }
1074
1075    match unsafe { get_dom_class(obj) } {
1076        Ok(v) => {
1077            let dom_object = unsafe { private_from_object(obj) as *const c_void };
1078
1079            if dom_object.is_null() {
1080                return 0;
1081            }
1082            let size = unsafe { (v.malloc_size_of)(&mut *ops, dom_object) };
1083
1084            let Some(per_global_interface_sizes) = per_global_interface_sizes else {
1085                return size;
1086            };
1087
1088            let global = unsafe { js::jsapi::GetNonCCWObjectGlobal(obj) };
1089            let interface = v.interface_chain[v.depth as usize];
1090            let interface_size = per_global_interface_sizes
1091                .0
1092                .entry(global as usize)
1093                .or_insert_with(|| {
1094                    let global = unsafe { GlobalScope::from_object(obj) };
1095                    GlobalSizeData {
1096                        url: global.get_url(),
1097                        interface_sizes: HashMap::new(),
1098                        is_zombie: global
1099                            .downcast::<Window>()
1100                            .is_some_and(|window| !window.is_alive()),
1101                    }
1102                })
1103                .interface_sizes
1104                .entry(interface.into())
1105                .or_default();
1106            interface_size.count += 1;
1107            interface_size.bytes += size;
1108            // Always report a size of zero, since we report this value
1109            // in a separate tree from the main JS heap.
1110            0
1111        },
1112        Err(_e) => 0,
1113    }
1114}
1115
1116#[expect(unsafe_code)]
1117pub(crate) fn get_reports(
1118    cx: &mut JSContext,
1119    path_seg: String,
1120    ops: &mut MallocSizeOfOps,
1121    already_computed_objects: HashSet<*const JSObject>,
1122) -> Vec<Report> {
1123    MALLOC_SIZE_OF_OPS.with(|ops_tls| ops_tls.set(ops));
1124    ALREADY_COMPUTED_OBJECTS.with(|objects| {
1125        *objects.borrow_mut() = already_computed_objects;
1126    });
1127
1128    let stats = unsafe {
1129        let mut stats = ::std::mem::zeroed();
1130        if !CollectServoSizes(cx, &mut stats, Some(get_size)) {
1131            return vec![];
1132        }
1133        stats
1134    };
1135    MALLOC_SIZE_OF_OPS.with(|ops| ops.set(ptr::null_mut()));
1136    ALREADY_COMPUTED_OBJECTS.with(|objects| {
1137        let mut objects = objects.borrow_mut();
1138        objects.clear();
1139        objects.shrink_to_fit();
1140    });
1141
1142    let mut reports = vec![];
1143    let mut report = |mut path_suffix, kind, size| {
1144        let mut path = path![path_seg, "js"];
1145        path.append(&mut path_suffix);
1146        reports.push(Report { path, kind, size })
1147    };
1148
1149    DOM_OBJECT_SIZES.with(|sizes| {
1150        let mut sizes = sizes.borrow_mut();
1151        let mut known_globals = HashMap::new();
1152        for global_size_data in sizes.0.values() {
1153            let url = global_size_data.url.as_str();
1154            let suffix = if global_size_data.is_zombie {
1155                "-zombie"
1156            } else {
1157                ""
1158            };
1159            let index = known_globals.entry(url).or_insert(0);
1160            *index += 1;
1161            for (interface, interface_data) in &global_size_data.interface_sizes {
1162                report(
1163                    path![
1164                        "dom",
1165                        "out-of-tree",
1166                        format!("url({url}){suffix}-{}", *index),
1167                        format!("{interface} [{}]", interface_data.count)
1168                    ],
1169                    ReportKind::ExplicitJemallocHeapSize,
1170                    interface_data.bytes,
1171                );
1172            }
1173        }
1174        sizes.0.clear();
1175    });
1176
1177    // A note about possibly confusing terminology: the JS GC "heap" is allocated via
1178    // mmap/VirtualAlloc, which means it's not on the malloc "heap", so we use
1179    // `ExplicitNonHeapSize` as its kind.
1180    report(
1181        path!["gc-heap", "used"],
1182        ReportKind::ExplicitNonHeapSize,
1183        stats.gcHeapUsed,
1184    );
1185
1186    report(
1187        path!["gc-heap", "unused"],
1188        ReportKind::ExplicitNonHeapSize,
1189        stats.gcHeapUnused,
1190    );
1191
1192    report(
1193        path!["gc-heap", "admin"],
1194        ReportKind::ExplicitNonHeapSize,
1195        stats.gcHeapAdmin,
1196    );
1197
1198    report(
1199        path!["gc-heap", "decommitted"],
1200        ReportKind::ExplicitNonHeapSize,
1201        stats.gcHeapDecommitted,
1202    );
1203
1204    // SpiderMonkey uses the system heap, not jemalloc.
1205    report(
1206        path!["malloc-heap"],
1207        ReportKind::ExplicitSystemHeapSize,
1208        stats.mallocHeap,
1209    );
1210
1211    report(
1212        path!["non-heap"],
1213        ReportKind::ExplicitNonHeapSize,
1214        stats.nonHeap,
1215    );
1216    reports
1217}
1218
1219pub(crate) struct StreamConsumer(*mut JSStreamConsumer);
1220
1221#[expect(unsafe_code)]
1222impl StreamConsumer {
1223    pub(crate) fn consume_chunk(&self, stream: &[u8]) -> bool {
1224        unsafe {
1225            let stream_ptr = stream.as_ptr();
1226            StreamConsumerConsumeChunk(self.0, stream_ptr, stream.len())
1227        }
1228    }
1229
1230    pub(crate) fn stream_end(&self) {
1231        unsafe {
1232            StreamConsumerStreamEnd(self.0);
1233        }
1234    }
1235
1236    pub(crate) fn stream_error(&self, error_code: usize) {
1237        unsafe {
1238            StreamConsumerStreamError(self.0, error_code);
1239        }
1240    }
1241
1242    pub(crate) fn note_response_urls(
1243        &self,
1244        maybe_url: Option<String>,
1245        maybe_source_map_url: Option<String>,
1246    ) {
1247        unsafe {
1248            let maybe_url = maybe_url.map(|url| CString::new(url).unwrap());
1249            let maybe_source_map_url = maybe_source_map_url.map(|url| CString::new(url).unwrap());
1250
1251            let maybe_url_param = match maybe_url.as_ref() {
1252                Some(url) => url.as_ptr(),
1253                None => ptr::null(),
1254            };
1255            let maybe_source_map_url_param = match maybe_source_map_url.as_ref() {
1256                Some(url) => url.as_ptr(),
1257                None => ptr::null(),
1258            };
1259
1260            StreamConsumerNoteResponseURLs(self.0, maybe_url_param, maybe_source_map_url_param);
1261        }
1262    }
1263}
1264
1265/// Implements the steps to compile webassembly response mentioned here
1266/// <https://webassembly.github.io/spec/web-api/#compile-a-potential-webassembly-response>
1267#[expect(unsafe_code)]
1268unsafe extern "C" fn consume_stream(
1269    cx: *mut RawJSContext,
1270    obj: HandleObject,
1271    _mime_type: MimeType,
1272    _consumer: *mut JSStreamConsumer,
1273) -> bool {
1274    let mut cx = unsafe {
1275        // SAFETY: We are in SM hook
1276        JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
1277    };
1278    let cx = &mut cx;
1279    let mut realm = CurrentRealm::assert(cx);
1280    let global = GlobalScope::from_current_realm(&mut realm);
1281
1282    // Step 2.1 Upon fulfillment of source, store the Response with value unwrappedSource.
1283    if let Ok(unwrapped_source) =
1284        unsafe { root_from_handleobject::<Response>(cx, RustHandleObject::from_raw(obj)) }
1285    {
1286        // Step 2.2 Let mimeType be the result of extracting a MIME type from response’s header list.
1287        let mimetype = unwrapped_source.Headers(cx).extract_mime_type();
1288
1289        // Step 2.3 If mimeType is not `application/wasm`, return with a TypeError and abort these substeps.
1290        if !&mimetype[..].eq_ignore_ascii_case(b"application/wasm") {
1291            throw_dom_exception(
1292                cx,
1293                &global,
1294                Error::Type(c"Response has unsupported MIME type".to_owned()),
1295            );
1296            return false;
1297        }
1298
1299        // Step 2.4 If response is not CORS-same-origin, return with a TypeError and abort these substeps.
1300        match unwrapped_source.Type() {
1301            DOMResponseType::Basic | DOMResponseType::Cors | DOMResponseType::Default => {},
1302            _ => {
1303                throw_dom_exception(
1304                    cx,
1305                    &global,
1306                    Error::Type(c"Response.type must be 'basic', 'cors' or 'default'".to_owned()),
1307                );
1308                return false;
1309            },
1310        }
1311
1312        // Step 2.5 If response’s status is not an ok status, return with a TypeError and abort these substeps.
1313        if !unwrapped_source.Ok() {
1314            throw_dom_exception(
1315                cx,
1316                &global,
1317                Error::Type(c"Response does not have ok status".to_owned()),
1318            );
1319            return false;
1320        }
1321
1322        // Step 2.6.1 If response body is locked, return with a TypeError and abort these substeps.
1323        if unwrapped_source.is_locked() {
1324            throw_dom_exception(
1325                cx,
1326                &global,
1327                Error::Type(c"There was an error consuming the Response".to_owned()),
1328            );
1329            return false;
1330        }
1331
1332        // Step 2.6.2 If response body is alreaady consumed, return with a TypeError and abort these substeps.
1333        if unwrapped_source.is_disturbed() {
1334            throw_dom_exception(
1335                cx,
1336                &global,
1337                Error::Type(c"Response already consumed".to_owned()),
1338            );
1339            return false;
1340        }
1341        unwrapped_source.set_stream_consumer(Some(StreamConsumer(_consumer)));
1342    } else {
1343        // Step 3 Upon rejection of source, return with reason.
1344        throw_dom_exception(
1345            cx,
1346            &global,
1347            Error::Type(c"expected Response or Promise resolving to Response".to_owned()),
1348        );
1349        return false;
1350    }
1351    true
1352}
1353
1354#[expect(unsafe_code)]
1355unsafe extern "C" fn report_stream_error(_cx: *mut RawJSContext, error_code: usize) {
1356    error!("Error initializing StreamConsumer: {:?}", unsafe {
1357        RUST_js_GetErrorMessage(ptr::null_mut(), error_code as u32)
1358    });
1359}
1360
1361#[expect(unsafe_code)]
1362unsafe extern "C" fn invoke_script_environment_preparer(
1363    global: HandleObject,
1364    closure: *mut ScriptEnvironmentPreparer_Closure,
1365) {
1366    // SAFETY: always safe from a JS engine hook.
1367    let mut cx = unsafe { temp_cx() };
1368    let global = unsafe { GlobalScope::from_object(global.get()) };
1369    let mut realm = enter_auto_realm(&mut cx, &*global);
1370    let cx = &mut realm.current_realm();
1371
1372    run_a_script::<DomTypeHolder, _, _>(cx, &global, |cx| {
1373        if unsafe { !RunScriptEnvironmentPreparerClosure(cx.raw_cx(), closure) } {
1374            report_pending_exception(cx);
1375        };
1376    });
1377}
1378
1379pub(crate) struct Runnable(*mut DispatchablePointer);
1380
1381#[expect(unsafe_code)]
1382unsafe impl Sync for Runnable {}
1383#[expect(unsafe_code)]
1384unsafe impl Send for Runnable {}
1385
1386#[expect(unsafe_code)]
1387impl Runnable {
1388    fn run(&self, cx: &mut JSContext, maybe_shutting_down: Dispatchable_MaybeShuttingDown) {
1389        unsafe {
1390            DispatchableRun(cx, self.0, maybe_shutting_down);
1391        }
1392    }
1393}
1394
1395/// `introductionType` values in SpiderMonkey TransitiveCompileOptions.
1396///
1397/// Value definitions are based on the SpiderMonkey Debugger API docs:
1398/// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Source.html#introductiontype>
1399// TODO: squish `scriptElement` <https://searchfox.org/mozilla-central/rev/202069c4c5113a1a9052d84fa4679d4c1b22113e/devtools/server/actors/source.js#199-201>
1400pub(crate) struct IntroductionType;
1401impl IntroductionType {
1402    /// `introductionType` for code passed to `eval`.
1403    pub const EVAL: &CStr = c"eval";
1404    pub const EVAL_STR: &str = "eval";
1405
1406    /// `introductionType` for code evaluated by debugger.
1407    /// This includes code run via the devtools repl, even if the thread is not paused.
1408    pub const DEBUGGER_EVAL: &CStr = c"debugger eval";
1409    pub const DEBUGGER_EVAL_STR: &str = "debugger eval";
1410
1411    /// `introductionType` for code passed to the `Function` constructor.
1412    pub const FUNCTION: &CStr = c"Function";
1413    pub const FUNCTION_STR: &str = "Function";
1414
1415    /// `introductionType` for code loaded by worklet.
1416    pub const WORKLET: &CStr = c"Worklet";
1417    pub const WORKLET_STR: &str = "Worklet";
1418
1419    /// `introductionType` for code assigned to DOM elements’ event handler IDL attributes as a string.
1420    pub const EVENT_HANDLER: &CStr = c"eventHandler";
1421    pub const EVENT_HANDLER_STR: &str = "eventHandler";
1422
1423    /// `introductionType` for code belonging to `<script src="file.js">` elements.
1424    /// This includes `<script type="module" src="...">`.
1425    pub const SRC_SCRIPT: &CStr = c"srcScript";
1426    pub const SRC_SCRIPT_STR: &str = "srcScript";
1427
1428    /// `introductionType` for code belonging to `<script>code;</script>` elements.
1429    /// This includes `<script type="module" src="...">`.
1430    pub const INLINE_SCRIPT: &CStr = c"inlineScript";
1431    pub const INLINE_SCRIPT_STR: &str = "inlineScript";
1432
1433    /// `introductionType` for code belonging to scripts that *would* be `"inlineScript"` except that they were not
1434    /// part of the initial file itself.
1435    /// For example, scripts created via:
1436    /// - `document.write("<script>code;</script>")`
1437    /// - `var s = document.createElement("script"); s.text = "code";`
1438    pub const INJECTED_SCRIPT: &CStr = c"injectedScript";
1439    pub const INJECTED_SCRIPT_STR: &str = "injectedScript";
1440
1441    /// `introductionType` for code that was loaded indirectly by being imported by another script
1442    /// using ESM static or dynamic imports.
1443    pub const IMPORTED_MODULE: &CStr = c"importedModule";
1444    pub const IMPORTED_MODULE_STR: &str = "importedModule";
1445
1446    /// `introductionType` for code presented in `javascript:` URLs.
1447    pub const JAVASCRIPT_URL: &CStr = c"javascriptURL";
1448    pub const JAVASCRIPT_URL_STR: &str = "javascriptURL";
1449
1450    /// `introductionType` for code passed to `setTimeout`/`setInterval` as a string.
1451    pub const DOM_TIMER: &CStr = c"domTimer";
1452    pub const DOM_TIMER_STR: &str = "domTimer";
1453
1454    /// `introductionType` for web workers.
1455    /// FIXME: only documented in older(?) devtools user docs
1456    /// <https://firefox-source-docs.mozilla.org/devtools-user/debugger-api/debugger.source/index.html>
1457    pub const WORKER: &CStr = c"Worker";
1458    pub const WORKER_STR: &str = "Worker";
1459}