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