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