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, Weak};
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, Trace,
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::ScriptThread;
66use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
67use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
68use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
69use crate::dom::bindings::codegen::UnionTypes::TrustedScriptOrString;
70use crate::dom::bindings::conversions::{
71    get_dom_class, private_from_object, root_from_handleobject, root_from_object,
72};
73use crate::dom::bindings::error::{Error, throw_dom_exception};
74use crate::dom::bindings::inheritance::Castable;
75use crate::dom::bindings::refcounted::{
76    LiveDOMReferences, Trusted, TrustedPromise, trace_refcounted_objects,
77};
78use crate::dom::bindings::reflector::{DomGlobal, DomObject};
79use crate::dom::bindings::root::trace_roots;
80use crate::dom::bindings::str::DOMString;
81use crate::dom::bindings::utils::DOM_CALLBACKS;
82use crate::dom::bindings::{principals, settings_stack};
83use crate::dom::csp::CspReporting;
84use crate::dom::event::{Event, EventBubbles, EventCancelable};
85use crate::dom::eventtarget::EventTarget;
86use crate::dom::globalscope::GlobalScope;
87use crate::dom::promise::Promise;
88use crate::dom::promiserejectionevent::PromiseRejectionEvent;
89use crate::dom::response::Response;
90use crate::dom::trustedscript::TrustedScript;
91use crate::messaging::{CommonScriptMsg, ScriptEventLoopSender};
92use crate::microtask::{EnqueuedPromiseCallback, Microtask, MicrotaskQueue};
93use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
94use crate::script_module::EnsureModuleHooksInitialized;
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/// Data that is sent to SpiderMonkey runtime callbacks as a pointer, which allows access
673/// to the `Runtime` state.
674#[derive(Default, JSTraceable, MallocSizeOf)]
675struct RuntimeCallbackData {
676    script_event_loop_sender: Option<ScriptEventLoopSender>,
677    #[no_trace]
678    #[ignore_malloc_size_of = "ScriptThread measures its own memory itself."]
679    script_thread: Option<Weak<ScriptThread>>,
680}
681
682#[derive(JSTraceable, MallocSizeOf)]
683pub(crate) struct Runtime {
684    #[ignore_malloc_size_of = "Type from mozjs"]
685    rt: RustRuntime,
686    /// Our actual microtask queue, which is preserved and untouched by the debugger when running debugger scripts.
687    #[conditional_malloc_size_of]
688    pub(crate) microtask_queue: Rc<MicrotaskQueue>,
689    #[ignore_malloc_size_of = "Type from mozjs"]
690    job_queue: *mut JobQueue,
691    /// The data that is set on the SpiderMonkey runtime callbacks as a pointer.
692    runtime_callback_data: Box<RuntimeCallbackData>,
693}
694
695impl Runtime {
696    /// Create a new runtime, optionally with the given [`SendableTaskSource`] for networking.
697    ///
698    /// # Safety
699    ///
700    /// If panicking does not abort the program, any threads with child runtimes will continue
701    /// executing after the thread with the parent runtime panics, but they will be in an
702    /// invalid and undefined state.
703    ///
704    /// This, like many calls to SpiderMoney API, is unsafe.
705    #[expect(unsafe_code)]
706    pub(crate) fn new(main_thread_sender: Option<ScriptEventLoopSender>) -> Runtime {
707        unsafe { Self::new_with_parent(None, main_thread_sender) }
708    }
709
710    #[allow(unsafe_code)]
711    /// ## Safety
712    /// - only one `JSContext` can exist on the thread at a time (see note in [js::context::JSContext::from_ptr])
713    /// - the `JSContext` must not outlive the `Runtime`
714    pub(crate) unsafe fn cx(&self) -> js::context::JSContext {
715        unsafe { js::context::JSContext::from_ptr(RustRuntime::get().unwrap()) }
716    }
717
718    /// Create a new runtime, optionally with the given [`ParentRuntime`] and [`SendableTaskSource`]
719    /// for networking.
720    ///
721    /// # Safety
722    ///
723    /// If panicking does not abort the program, any threads with child runtimes will continue
724    /// executing after the thread with the parent runtime panics, but they will be in an
725    /// invalid and undefined state.
726    ///
727    /// The `parent` pointer in the [`ParentRuntime`] argument must point to a valid object in memory.
728    ///
729    /// This, like many calls to the SpiderMoney API, is unsafe.
730    #[expect(unsafe_code)]
731    pub(crate) unsafe fn new_with_parent(
732        parent: Option<ParentRuntime>,
733        script_event_loop_sender: Option<ScriptEventLoopSender>,
734    ) -> Runtime {
735        let mut runtime = if let Some(parent) = parent {
736            unsafe { RustRuntime::create_with_parent(parent) }
737        } else {
738            RustRuntime::new(JS_ENGINE.lock().unwrap().as_ref().unwrap().clone())
739        };
740        let cx = runtime.cx();
741
742        let have_event_loop_sender = script_event_loop_sender.is_some();
743        let runtime_callback_data = Box::new(RuntimeCallbackData {
744            script_event_loop_sender,
745            script_thread: None,
746        });
747        let runtime_callback_data = Box::into_raw(runtime_callback_data);
748
749        unsafe {
750            JS_AddExtraGCRootsTracer(
751                cx,
752                Some(trace_rust_roots),
753                runtime_callback_data as *mut c_void,
754            );
755
756            JS_SetSecurityCallbacks(cx, &SECURITY_CALLBACKS);
757
758            JS_InitDestroyPrincipalsCallback(cx, Some(principals::destroy_servo_jsprincipal));
759            JS_InitReadPrincipalsCallback(cx, Some(principals::read_jsprincipal));
760
761            // Needed for debug assertions about whether GC is running.
762            if cfg!(debug_assertions) {
763                JS_SetGCCallback(cx, Some(debug_gc_callback), ptr::null_mut());
764            }
765
766            if opts::get().debug.gc_profile {
767                SetGCSliceCallback(cx, Some(gc_slice_callback));
768            }
769        }
770
771        unsafe extern "C" fn empty_wrapper_callback(_: *mut RawJSContext, _: HandleObject) -> bool {
772            true
773        }
774        unsafe extern "C" fn empty_has_released_callback(_: HandleObject) -> bool {
775            // fixme: return true when the Drop impl for a DOM object has been invoked
776            false
777        }
778
779        unsafe {
780            SetDOMCallbacks(cx, &DOM_CALLBACKS);
781            SetPreserveWrapperCallbacks(
782                cx,
783                Some(empty_wrapper_callback),
784                Some(empty_has_released_callback),
785            );
786            // Pre barriers aren't working correctly at the moment
787            JS_SetGCParameter(cx, JSGCParamKey::JSGC_INCREMENTAL_GC_ENABLED, 0);
788        }
789
790        unsafe extern "C" fn dispatch_to_event_loop(
791            data: *mut c_void,
792            dispatchable: *mut DispatchablePointer,
793        ) -> bool {
794            let runtime_callback_data: &RuntimeCallbackData =
795                unsafe { &*(data as *mut RuntimeCallbackData) };
796            let Some(script_event_loop_sender) =
797                runtime_callback_data.script_event_loop_sender.as_ref()
798            else {
799                return false;
800            };
801
802            let runnable = Runnable(dispatchable);
803            let task = task!(dispatch_to_event_loop_message: move || {
804                if let Some(cx) = RustRuntime::get() {
805                    runnable.run(cx.as_ptr(), Dispatchable_MaybeShuttingDown::NotShuttingDown);
806                }
807            });
808
809            script_event_loop_sender
810                .send(CommonScriptMsg::Task(
811                    ScriptThreadEventCategory::NetworkEvent,
812                    Box::new(task),
813                    None, /* pipeline_id */
814                    TaskSourceName::Networking,
815                ))
816                .is_ok()
817        }
818
819        if have_event_loop_sender {
820            unsafe {
821                SetUpEventLoopDispatch(
822                    cx,
823                    Some(dispatch_to_event_loop),
824                    runtime_callback_data as *mut c_void,
825                );
826            }
827        }
828
829        unsafe {
830            InitConsumeStreamCallback(cx, Some(consume_stream), Some(report_stream_error));
831        }
832
833        let microtask_queue = Rc::new(MicrotaskQueue::default());
834
835        // Extra queues for debugger scripts (“interrupts”) via AutoDebuggerJobQueueInterruption and saveJobQueue().
836        // Moved indefinitely to mozjs via CreateJobQueue(), borrowed from mozjs via JobQueueTraps, and moved back from
837        // mozjs for dropping via DeleteJobQueue().
838        let interrupt_queues: Box<Vec<Rc<MicrotaskQueue>>> = Box::default();
839
840        let cx_opts;
841        let job_queue;
842        unsafe {
843            let cx = runtime.cx();
844            job_queue = CreateJobQueue(
845                &JOB_QUEUE_TRAPS,
846                &*microtask_queue as *const _ as *const c_void,
847                Box::into_raw(interrupt_queues) as *mut c_void,
848            );
849            SetJobQueue(cx, job_queue);
850            SetPromiseRejectionTrackerCallback(
851                cx,
852                Some(promise_rejection_tracker),
853                ptr::null_mut(),
854            );
855
856            EnsureModuleHooksInitialized(runtime.rt());
857
858            let cx = runtime.cx();
859
860            set_gc_zeal_options(cx.raw_cx());
861
862            // Enable or disable the JITs.
863            cx_opts = &mut *ContextOptionsRef(cx);
864            JS_SetGlobalJitCompilerOption(
865                cx,
866                JSJitCompilerOption::JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE,
867                pref!(js_baseline_interpreter_enabled) as u32,
868            );
869            JS_SetGlobalJitCompilerOption(
870                cx,
871                JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE,
872                pref!(js_baseline_jit_enabled) as u32,
873            );
874            JS_SetGlobalJitCompilerOption(
875                cx,
876                JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE,
877                pref!(js_ion_enabled) as u32,
878            );
879        }
880        cx_opts.compileOptions_.asmJSOption_ = if pref!(js_asmjs_enabled) {
881            AsmJSOption::Enabled
882        } else {
883            AsmJSOption::DisabledByAsmJSPref
884        };
885        cx_opts.compileOptions_.set_importAttributes_(true);
886        let wasm_enabled = pref!(js_wasm_enabled);
887        cx_opts.set_wasm_(wasm_enabled);
888        if wasm_enabled {
889            // If WASM is enabled without setting the buildIdOp,
890            // initializing a module will report an out of memory error.
891            // https://dxr.mozilla.org/mozilla-central/source/js/src/wasm/WasmTypes.cpp#458
892            unsafe { SetProcessBuildIdOp(Some(servo_build_id)) };
893        }
894        cx_opts.set_wasmBaseline_(pref!(js_wasm_baseline_enabled));
895        cx_opts.set_wasmIon_(pref!(js_wasm_ion_enabled));
896
897        unsafe {
898            let cx = runtime.cx();
899            // TODO: handle js.throw_on_asmjs_validation_failure (needs new Spidermonkey)
900            JS_SetGlobalJitCompilerOption(
901                cx,
902                JSJitCompilerOption::JSJITCOMPILER_NATIVE_REGEXP_ENABLE,
903                pref!(js_native_regex_enabled) as u32,
904            );
905            JS_SetOffthreadIonCompilationEnabled(cx, pref!(js_offthread_compilation_enabled));
906            JS_SetGlobalJitCompilerOption(
907                cx,
908                JSJitCompilerOption::JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
909                if pref!(js_baseline_jit_unsafe_eager_compilation_enabled) {
910                    0
911                } else {
912                    u32::MAX
913                },
914            );
915            JS_SetGlobalJitCompilerOption(
916                cx,
917                JSJitCompilerOption::JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER,
918                if pref!(js_ion_unsafe_eager_compilation_enabled) {
919                    0
920                } else {
921                    u32::MAX
922                },
923            );
924            // TODO: handle js.discard_system_source.enabled
925            // TODO: handle js.asyncstack.enabled (needs new Spidermonkey)
926            // TODO: handle js.throw_on_debugee_would_run (needs new Spidermonkey)
927            // TODO: handle js.dump_stack_on_debugee_would_run (needs new Spidermonkey)
928            // TODO: handle js.shared_memory.enabled
929            JS_SetGCParameter(
930                cx,
931                JSGCParamKey::JSGC_MAX_BYTES,
932                in_range(pref!(js_mem_max), 1, 0x100)
933                    .map(|val| (val * 1024 * 1024) as u32)
934                    .unwrap_or(u32::MAX),
935            );
936            // NOTE: This is disabled above, so enabling it here will do nothing for now.
937            JS_SetGCParameter(
938                cx,
939                JSGCParamKey::JSGC_INCREMENTAL_GC_ENABLED,
940                pref!(js_mem_gc_incremental_enabled) as u32,
941            );
942            JS_SetGCParameter(
943                cx,
944                JSGCParamKey::JSGC_PER_ZONE_GC_ENABLED,
945                pref!(js_mem_gc_per_zone_enabled) as u32,
946            );
947            if let Some(val) = in_range(pref!(js_mem_gc_incremental_slice_ms), 0, 100_000) {
948                JS_SetGCParameter(cx, JSGCParamKey::JSGC_SLICE_TIME_BUDGET_MS, val as u32);
949            }
950            JS_SetGCParameter(
951                cx,
952                JSGCParamKey::JSGC_COMPACTING_ENABLED,
953                pref!(js_mem_gc_compacting_enabled) as u32,
954            );
955
956            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_time_limit_ms), 0, 10_000) {
957                JS_SetGCParameter(cx, JSGCParamKey::JSGC_HIGH_FREQUENCY_TIME_LIMIT, val as u32);
958            }
959            if let Some(val) = in_range(pref!(js_mem_gc_low_frequency_heap_growth), 0, 10_000) {
960                JS_SetGCParameter(cx, JSGCParamKey::JSGC_LOW_FREQUENCY_HEAP_GROWTH, val as u32);
961            }
962            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_heap_growth_min), 0, 10_000)
963            {
964                JS_SetGCParameter(
965                    cx,
966                    JSGCParamKey::JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH,
967                    val as u32,
968                );
969            }
970            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_heap_growth_max), 0, 10_000)
971            {
972                JS_SetGCParameter(
973                    cx,
974                    JSGCParamKey::JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH,
975                    val as u32,
976                );
977            }
978            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_low_limit_mb), 0, 10_000) {
979                JS_SetGCParameter(cx, JSGCParamKey::JSGC_SMALL_HEAP_SIZE_MAX, val as u32);
980            }
981            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_high_limit_mb), 0, 10_000) {
982                JS_SetGCParameter(cx, JSGCParamKey::JSGC_LARGE_HEAP_SIZE_MIN, val as u32);
983            }
984            /*if let Some(val) = in_range(pref!(js_mem_gc_allocation_threshold_factor), 0, 10_000) {
985                JS_SetGCParameter(cx, JSGCParamKey::JSGC_NON_INCREMENTAL_FACTOR, val as u32);
986            }*/
987            /*
988                // JSGC_SMALL_HEAP_INCREMENTAL_LIMIT
989                pref("javascript.options.mem.gc_small_heap_incremental_limit", 140);
990
991                // JSGC_LARGE_HEAP_INCREMENTAL_LIMIT
992                pref("javascript.options.mem.gc_large_heap_incremental_limit", 110);
993            */
994            if let Some(val) = in_range(pref!(js_mem_gc_empty_chunk_count_min), 0, 10_000) {
995                JS_SetGCParameter(cx, JSGCParamKey::JSGC_MIN_EMPTY_CHUNK_COUNT, val as u32);
996            }
997        }
998        Runtime {
999            rt: runtime,
1000            microtask_queue,
1001            job_queue,
1002            runtime_callback_data: unsafe { Box::from_raw(runtime_callback_data) },
1003        }
1004    }
1005
1006    pub(crate) fn set_script_thread(&mut self, script_thread: Weak<ScriptThread>) {
1007        self.runtime_callback_data
1008            .script_thread
1009            .replace(script_thread);
1010    }
1011
1012    pub(crate) fn thread_safe_js_context(&self) -> ThreadSafeJSContext {
1013        self.rt.thread_safe_js_context()
1014    }
1015}
1016
1017impl Drop for Runtime {
1018    #[expect(unsafe_code)]
1019    fn drop(&mut self) {
1020        // Clear our main microtask_queue.
1021        self.microtask_queue.clear();
1022
1023        // Delete the RustJobQueue in mozjs, which will destroy our interrupt queues.
1024        unsafe {
1025            DeleteJobQueue(self.job_queue);
1026        }
1027        LiveDOMReferences::destruct();
1028        mark_runtime_dead();
1029    }
1030}
1031
1032impl Deref for Runtime {
1033    type Target = RustRuntime;
1034    fn deref(&self) -> &RustRuntime {
1035        &self.rt
1036    }
1037}
1038
1039impl DerefMut for Runtime {
1040    fn deref_mut(&mut self) -> &mut RustRuntime {
1041        &mut self.rt
1042    }
1043}
1044
1045pub struct JSEngineSetup(JSEngine);
1046
1047impl Default for JSEngineSetup {
1048    fn default() -> Self {
1049        let engine = JSEngine::init().unwrap();
1050        *JS_ENGINE.lock().unwrap() = Some(engine.handle());
1051        Self(engine)
1052    }
1053}
1054
1055impl Drop for JSEngineSetup {
1056    fn drop(&mut self) {
1057        *JS_ENGINE.lock().unwrap() = None;
1058
1059        while !self.0.can_shutdown() {
1060            thread::sleep(Duration::from_millis(50));
1061        }
1062    }
1063}
1064
1065static JS_ENGINE: Mutex<Option<JSEngineHandle>> = Mutex::new(None);
1066
1067fn in_range<T: PartialOrd + Copy>(val: T, min: T, max: T) -> Option<T> {
1068    if val < min || val >= max {
1069        None
1070    } else {
1071        Some(val)
1072    }
1073}
1074
1075thread_local!(static MALLOC_SIZE_OF_OPS: Cell<*mut MallocSizeOfOps> = const { Cell::new(ptr::null_mut()) });
1076
1077#[expect(unsafe_code)]
1078unsafe extern "C" fn get_size(obj: *mut JSObject) -> usize {
1079    match unsafe { get_dom_class(obj) } {
1080        Ok(v) => {
1081            let dom_object = unsafe { private_from_object(obj) as *const c_void };
1082
1083            if dom_object.is_null() {
1084                return 0;
1085            }
1086            let ops = MALLOC_SIZE_OF_OPS.get();
1087            unsafe { (v.malloc_size_of)(&mut *ops, dom_object) }
1088        },
1089        Err(_e) => 0,
1090    }
1091}
1092
1093thread_local!(static GC_CYCLE_START: Cell<Option<Instant>> = const { Cell::new(None) });
1094thread_local!(static GC_SLICE_START: Cell<Option<Instant>> = const { Cell::new(None) });
1095
1096#[expect(unsafe_code)]
1097unsafe extern "C" fn gc_slice_callback(
1098    _cx: *mut RawJSContext,
1099    progress: GCProgress,
1100    desc: *const GCDescription,
1101) {
1102    match progress {
1103        GCProgress::GC_CYCLE_BEGIN => GC_CYCLE_START.with(|start| {
1104            start.set(Some(Instant::now()));
1105            println!("GC cycle began");
1106        }),
1107        GCProgress::GC_SLICE_BEGIN => GC_SLICE_START.with(|start| {
1108            start.set(Some(Instant::now()));
1109            println!("GC slice began");
1110        }),
1111        GCProgress::GC_SLICE_END => GC_SLICE_START.with(|start| {
1112            let duration = start.get().unwrap().elapsed();
1113            start.set(None);
1114            println!("GC slice ended: duration={:?}", duration);
1115        }),
1116        GCProgress::GC_CYCLE_END => GC_CYCLE_START.with(|start| {
1117            let duration = start.get().unwrap().elapsed();
1118            start.set(None);
1119            println!("GC cycle ended: duration={:?}", duration);
1120        }),
1121    };
1122    if !desc.is_null() {
1123        let desc: &GCDescription = unsafe { &*desc };
1124        let options = match desc.options_ {
1125            GCOptions::Normal => "Normal",
1126            GCOptions::Shrink => "Shrink",
1127            GCOptions::Shutdown => "Shutdown",
1128        };
1129        println!("  isZone={}, options={}", desc.isZone_, options);
1130    }
1131    let _ = stdout().flush();
1132}
1133
1134#[expect(unsafe_code)]
1135unsafe extern "C" fn debug_gc_callback(
1136    _cx: *mut RawJSContext,
1137    status: JSGCStatus,
1138    _reason: GCReason,
1139    _data: *mut os::raw::c_void,
1140) {
1141    match status {
1142        JSGCStatus::JSGC_BEGIN => thread_state::enter(ThreadState::IN_GC),
1143        JSGCStatus::JSGC_END => thread_state::exit(ThreadState::IN_GC),
1144    }
1145}
1146
1147#[expect(unsafe_code)]
1148unsafe extern "C" fn trace_rust_roots(tr: *mut JSTracer, data: *mut os::raw::c_void) {
1149    if !runtime_is_alive() {
1150        return;
1151    }
1152    trace!("starting custom root handler");
1153
1154    let runtime_callback_data = unsafe { &*(data as *const RuntimeCallbackData) };
1155    if let Some(script_thread) = runtime_callback_data
1156        .script_thread
1157        .as_ref()
1158        .and_then(Weak::upgrade)
1159    {
1160        trace!("tracing fields of ScriptThread");
1161        unsafe { script_thread.trace(tr) };
1162    };
1163
1164    unsafe {
1165        trace_roots(tr);
1166        trace_refcounted_objects(tr);
1167        settings_stack::trace(tr);
1168    }
1169    trace!("done custom root handler");
1170}
1171
1172#[expect(unsafe_code)]
1173unsafe extern "C" fn servo_build_id(build_id: *mut BuildIdCharVector) -> bool {
1174    let servo_id = b"Servo\0";
1175    unsafe { SetBuildId(build_id, servo_id[0] as *const c_char, servo_id.len()) }
1176}
1177
1178#[expect(unsafe_code)]
1179#[cfg(feature = "debugmozjs")]
1180unsafe fn set_gc_zeal_options(cx: *mut RawJSContext) {
1181    use js::jsapi::SetGCZeal;
1182
1183    let level = match pref!(js_mem_gc_zeal_level) {
1184        level @ 0..=14 => level as u8,
1185        _ => return,
1186    };
1187    let frequency = match pref!(js_mem_gc_zeal_frequency) {
1188        frequency if frequency >= 0 => frequency as u32,
1189        // https://searchfox.org/mozilla-esr128/source/js/public/GCAPI.h#1392
1190        _ => 5000,
1191    };
1192    unsafe {
1193        SetGCZeal(cx, level, frequency);
1194    }
1195}
1196
1197#[expect(unsafe_code)]
1198#[cfg(not(feature = "debugmozjs"))]
1199unsafe fn set_gc_zeal_options(_: *mut RawJSContext) {}
1200
1201pub(crate) use script_bindings::script_runtime::JSContext;
1202
1203/// Extra methods for the JSContext type defined in script_bindings, when
1204/// the methods are only called by code in the script crate.
1205pub(crate) trait JSContextHelper {
1206    fn get_reports(&self, path_seg: String, ops: &mut MallocSizeOfOps) -> Vec<Report>;
1207}
1208
1209impl JSContextHelper for JSContext {
1210    #[expect(unsafe_code)]
1211    fn get_reports(&self, path_seg: String, ops: &mut MallocSizeOfOps) -> Vec<Report> {
1212        MALLOC_SIZE_OF_OPS.with(|ops_tls| ops_tls.set(ops));
1213        let stats = unsafe {
1214            let mut stats = ::std::mem::zeroed();
1215            if !CollectServoSizes(**self, &mut stats, Some(get_size)) {
1216                return vec![];
1217            }
1218            stats
1219        };
1220        MALLOC_SIZE_OF_OPS.with(|ops| ops.set(ptr::null_mut()));
1221
1222        let mut reports = vec![];
1223        let mut report = |mut path_suffix, kind, size| {
1224            let mut path = path![path_seg, "js"];
1225            path.append(&mut path_suffix);
1226            reports.push(Report { path, kind, size })
1227        };
1228
1229        // A note about possibly confusing terminology: the JS GC "heap" is allocated via
1230        // mmap/VirtualAlloc, which means it's not on the malloc "heap", so we use
1231        // `ExplicitNonHeapSize` as its kind.
1232        report(
1233            path!["gc-heap", "used"],
1234            ReportKind::ExplicitNonHeapSize,
1235            stats.gcHeapUsed,
1236        );
1237
1238        report(
1239            path!["gc-heap", "unused"],
1240            ReportKind::ExplicitNonHeapSize,
1241            stats.gcHeapUnused,
1242        );
1243
1244        report(
1245            path!["gc-heap", "admin"],
1246            ReportKind::ExplicitNonHeapSize,
1247            stats.gcHeapAdmin,
1248        );
1249
1250        report(
1251            path!["gc-heap", "decommitted"],
1252            ReportKind::ExplicitNonHeapSize,
1253            stats.gcHeapDecommitted,
1254        );
1255
1256        // SpiderMonkey uses the system heap, not jemalloc.
1257        report(
1258            path!["malloc-heap"],
1259            ReportKind::ExplicitSystemHeapSize,
1260            stats.mallocHeap,
1261        );
1262
1263        report(
1264            path!["non-heap"],
1265            ReportKind::ExplicitNonHeapSize,
1266            stats.nonHeap,
1267        );
1268        reports
1269    }
1270}
1271
1272pub(crate) struct StreamConsumer(*mut JSStreamConsumer);
1273
1274#[expect(unsafe_code)]
1275impl StreamConsumer {
1276    pub(crate) fn consume_chunk(&self, stream: &[u8]) -> bool {
1277        unsafe {
1278            let stream_ptr = stream.as_ptr();
1279            StreamConsumerConsumeChunk(self.0, stream_ptr, stream.len())
1280        }
1281    }
1282
1283    pub(crate) fn stream_end(&self) {
1284        unsafe {
1285            StreamConsumerStreamEnd(self.0);
1286        }
1287    }
1288
1289    pub(crate) fn stream_error(&self, error_code: usize) {
1290        unsafe {
1291            StreamConsumerStreamError(self.0, error_code);
1292        }
1293    }
1294
1295    pub(crate) fn note_response_urls(
1296        &self,
1297        maybe_url: Option<String>,
1298        maybe_source_map_url: Option<String>,
1299    ) {
1300        unsafe {
1301            let maybe_url = maybe_url.map(|url| CString::new(url).unwrap());
1302            let maybe_source_map_url = maybe_source_map_url.map(|url| CString::new(url).unwrap());
1303
1304            let maybe_url_param = match maybe_url.as_ref() {
1305                Some(url) => url.as_ptr(),
1306                None => ptr::null(),
1307            };
1308            let maybe_source_map_url_param = match maybe_source_map_url.as_ref() {
1309                Some(url) => url.as_ptr(),
1310                None => ptr::null(),
1311            };
1312
1313            StreamConsumerNoteResponseURLs(self.0, maybe_url_param, maybe_source_map_url_param);
1314        }
1315    }
1316}
1317
1318/// Implements the steps to compile webassembly response mentioned here
1319/// <https://webassembly.github.io/spec/web-api/#compile-a-potential-webassembly-response>
1320#[expect(unsafe_code)]
1321unsafe extern "C" fn consume_stream(
1322    cx: *mut RawJSContext,
1323    obj: HandleObject,
1324    _mime_type: MimeType,
1325    _consumer: *mut JSStreamConsumer,
1326) -> bool {
1327    let cx = unsafe { JSContext::from_ptr(cx) };
1328    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1329    let global = unsafe { GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)) };
1330
1331    // Step 2.1 Upon fulfillment of source, store the Response with value unwrappedSource.
1332    if let Ok(unwrapped_source) =
1333        root_from_handleobject::<Response>(unsafe { RustHandleObject::from_raw(obj) }, *cx)
1334    {
1335        // Step 2.2 Let mimeType be the result of extracting a MIME type from response’s header list.
1336        let mimetype = unwrapped_source.Headers(CanGc::note()).extract_mime_type();
1337
1338        // Step 2.3 If mimeType is not `application/wasm`, return with a TypeError and abort these substeps.
1339        if !&mimetype[..].eq_ignore_ascii_case(b"application/wasm") {
1340            throw_dom_exception(
1341                cx,
1342                &global,
1343                Error::Type(c"Response has unsupported MIME type".to_owned()),
1344                CanGc::note(),
1345            );
1346            return false;
1347        }
1348
1349        // Step 2.4 If response is not CORS-same-origin, return with a TypeError and abort these substeps.
1350        match unwrapped_source.Type() {
1351            DOMResponseType::Basic | DOMResponseType::Cors | DOMResponseType::Default => {},
1352            _ => {
1353                throw_dom_exception(
1354                    cx,
1355                    &global,
1356                    Error::Type(c"Response.type must be 'basic', 'cors' or 'default'".to_owned()),
1357                    CanGc::note(),
1358                );
1359                return false;
1360            },
1361        }
1362
1363        // Step 2.5 If response’s status is not an ok status, return with a TypeError and abort these substeps.
1364        if !unwrapped_source.Ok() {
1365            throw_dom_exception(
1366                cx,
1367                &global,
1368                Error::Type(c"Response does not have ok status".to_owned()),
1369                CanGc::note(),
1370            );
1371            return false;
1372        }
1373
1374        // Step 2.6.1 If response body is locked, return with a TypeError and abort these substeps.
1375        if unwrapped_source.is_locked() {
1376            throw_dom_exception(
1377                cx,
1378                &global,
1379                Error::Type(c"There was an error consuming the Response".to_owned()),
1380                CanGc::note(),
1381            );
1382            return false;
1383        }
1384
1385        // Step 2.6.2 If response body is alreaady consumed, return with a TypeError and abort these substeps.
1386        if unwrapped_source.is_disturbed() {
1387            throw_dom_exception(
1388                cx,
1389                &global,
1390                Error::Type(c"Response already consumed".to_owned()),
1391                CanGc::note(),
1392            );
1393            return false;
1394        }
1395        unwrapped_source.set_stream_consumer(Some(StreamConsumer(_consumer)));
1396    } else {
1397        // Step 3 Upon rejection of source, return with reason.
1398        throw_dom_exception(
1399            cx,
1400            &global,
1401            Error::Type(c"expected Response or Promise resolving to Response".to_owned()),
1402            CanGc::note(),
1403        );
1404        return false;
1405    }
1406    true
1407}
1408
1409#[expect(unsafe_code)]
1410unsafe extern "C" fn report_stream_error(_cx: *mut RawJSContext, error_code: usize) {
1411    error!("Error initializing StreamConsumer: {:?}", unsafe {
1412        RUST_js_GetErrorMessage(ptr::null_mut(), error_code as u32)
1413    });
1414}
1415
1416pub(crate) struct Runnable(*mut DispatchablePointer);
1417
1418#[expect(unsafe_code)]
1419unsafe impl Sync for Runnable {}
1420#[expect(unsafe_code)]
1421unsafe impl Send for Runnable {}
1422
1423#[expect(unsafe_code)]
1424impl Runnable {
1425    fn run(&self, cx: *mut RawJSContext, maybe_shutting_down: Dispatchable_MaybeShuttingDown) {
1426        unsafe {
1427            DispatchableRun(cx, self.0, maybe_shutting_down);
1428        }
1429    }
1430}
1431
1432pub(crate) use script_bindings::script_runtime::CanGc;
1433
1434/// `introductionType` values in SpiderMonkey TransitiveCompileOptions.
1435///
1436/// Value definitions are based on the SpiderMonkey Debugger API docs:
1437/// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Source.html#introductiontype>
1438// TODO: squish `scriptElement` <https://searchfox.org/mozilla-central/rev/202069c4c5113a1a9052d84fa4679d4c1b22113e/devtools/server/actors/source.js#199-201>
1439pub(crate) struct IntroductionType;
1440impl IntroductionType {
1441    /// `introductionType` for code passed to `eval`.
1442    pub const EVAL: &CStr = c"eval";
1443    pub const EVAL_STR: &str = "eval";
1444
1445    /// `introductionType` for code evaluated by debugger.
1446    /// This includes code run via the devtools repl, even if the thread is not paused.
1447    pub const DEBUGGER_EVAL: &CStr = c"debugger eval";
1448    pub const DEBUGGER_EVAL_STR: &str = "debugger eval";
1449
1450    /// `introductionType` for code passed to the `Function` constructor.
1451    pub const FUNCTION: &CStr = c"Function";
1452    pub const FUNCTION_STR: &str = "Function";
1453
1454    /// `introductionType` for code loaded by worklet.
1455    pub const WORKLET: &CStr = c"Worklet";
1456    pub const WORKLET_STR: &str = "Worklet";
1457
1458    /// `introductionType` for code assigned to DOM elements’ event handler IDL attributes as a string.
1459    pub const EVENT_HANDLER: &CStr = c"eventHandler";
1460    pub const EVENT_HANDLER_STR: &str = "eventHandler";
1461
1462    /// `introductionType` for code belonging to `<script src="file.js">` elements.
1463    /// This includes `<script type="module" src="...">`.
1464    pub const SRC_SCRIPT: &CStr = c"srcScript";
1465    pub const SRC_SCRIPT_STR: &str = "srcScript";
1466
1467    /// `introductionType` for code belonging to `<script>code;</script>` elements.
1468    /// This includes `<script type="module" src="...">`.
1469    pub const INLINE_SCRIPT: &CStr = c"inlineScript";
1470    pub const INLINE_SCRIPT_STR: &str = "inlineScript";
1471
1472    /// `introductionType` for code belonging to scripts that *would* be `"inlineScript"` except that they were not
1473    /// part of the initial file itself.
1474    /// For example, scripts created via:
1475    /// - `document.write("<script>code;</script>")`
1476    /// - `var s = document.createElement("script"); s.text = "code";`
1477    pub const INJECTED_SCRIPT: &CStr = c"injectedScript";
1478    pub const INJECTED_SCRIPT_STR: &str = "injectedScript";
1479
1480    /// `introductionType` for code that was loaded indirectly by being imported by another script
1481    /// using ESM static or dynamic imports.
1482    pub const IMPORTED_MODULE: &CStr = c"importedModule";
1483    pub const IMPORTED_MODULE_STR: &str = "importedModule";
1484
1485    /// `introductionType` for code presented in `javascript:` URLs.
1486    pub const JAVASCRIPT_URL: &CStr = c"javascriptURL";
1487    pub const JAVASCRIPT_URL_STR: &str = "javascriptURL";
1488
1489    /// `introductionType` for code passed to `setTimeout`/`setInterval` as a string.
1490    pub const DOM_TIMER: &CStr = c"domTimer";
1491    pub const DOM_TIMER_STR: &str = "domTimer";
1492
1493    /// `introductionType` for web workers.
1494    /// FIXME: only documented in older(?) devtools user docs
1495    /// <https://firefox-source-docs.mozilla.org/devtools-user/debugger-api/debugger.source/index.html>
1496    pub const WORKER: &CStr = c"Worker";
1497    pub const WORKER_STR: &str = "Worker";
1498}