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