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