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