Skip to main content

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