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#![allow(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#[cfg_attr(crown, allow(crown::unrooted_must_root))]
412/// <https://html.spec.whatwg.org/multipage/#the-hostpromiserejectiontracker-implementation>
413unsafe extern "C" fn promise_rejection_tracker(
414    cx: *mut RawJSContext,
415    _muted_errors: bool,
416    promise: HandleObject,
417    state: PromiseRejectionHandlingState,
418    _data: *mut c_void,
419) {
420    // TODO: Step 2 - If script's muted errors is true, terminate these steps.
421
422    // Step 3.
423    let cx = unsafe { JSContext::from_ptr(cx) };
424    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
425    let global = unsafe { GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)) };
426
427    wrap_panic(&mut || {
428        match state {
429            // Step 4.
430            PromiseRejectionHandlingState::Unhandled => {
431                global.add_uncaught_rejection(promise);
432            },
433            // Step 5.
434            PromiseRejectionHandlingState::Handled => {
435                // Step 5-1.
436                if global
437                    .get_uncaught_rejections()
438                    .borrow()
439                    .contains(&Heap::boxed(promise.get()))
440                {
441                    global.remove_uncaught_rejection(promise);
442                    return;
443                }
444
445                // Step 5-2.
446                if !global
447                    .get_consumed_rejections()
448                    .borrow()
449                    .contains(&Heap::boxed(promise.get()))
450                {
451                    return;
452                }
453
454                // Step 5-3.
455                global.remove_consumed_rejection(promise);
456
457                let target = Trusted::new(global.upcast::<EventTarget>());
458                let promise =
459                    Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx);
460                let trusted_promise = TrustedPromise::new(promise.clone());
461
462                // Step 5-4.
463                global.task_manager().dom_manipulation_task_source().queue(
464                task!(rejection_handled_event: move || {
465                    let target = target.root();
466                    let cx = GlobalScope::get_cx();
467                    let root_promise = trusted_promise.root();
468
469                    rooted!(in(*cx) let mut reason = UndefinedValue());
470                    unsafe{JS_GetPromiseResult(root_promise.reflector().get_jsobject(), reason.handle_mut())};
471
472                    let event = PromiseRejectionEvent::new(
473                        &target.global(),
474                        atom!("rejectionhandled"),
475                        EventBubbles::DoesNotBubble,
476                        EventCancelable::Cancelable,
477                        root_promise,
478                        reason.handle(),
479                        CanGc::note()
480                    );
481
482                    event.upcast::<Event>().fire(&target, CanGc::note());
483                })
484            );
485            },
486        };
487    })
488}
489
490#[expect(unsafe_code)]
491fn safely_convert_null_to_string(cx: JSContext, str_: HandleString) -> DOMString {
492    DOMString::from(match std::ptr::NonNull::new(*str_) {
493        None => "".to_owned(),
494        Some(str_) => unsafe { jsstr_to_string(*cx, str_) },
495    })
496}
497
498#[expect(unsafe_code)]
499unsafe extern "C" fn code_for_eval_gets(
500    cx: *mut RawJSContext,
501    code: HandleObject,
502    code_for_eval: MutableHandleString,
503) -> bool {
504    let cx = unsafe { JSContext::from_ptr(cx) };
505    if let Ok(trusted_script) = unsafe { root_from_object::<TrustedScript>(code.get(), *cx) } {
506        let script_str = trusted_script.data().str();
507        let s = js::conversions::Utf8Chars::from(&*script_str);
508        let new_string = unsafe { JS_NewStringCopyUTF8N(*cx, &*s as *const _) };
509        code_for_eval.set(new_string);
510    }
511    true
512}
513
514#[expect(unsafe_code)]
515unsafe extern "C" fn content_security_policy_allows(
516    cx: *mut RawJSContext,
517    runtime_code: RuntimeCode,
518    code_string: HandleString,
519    compilation_type: CompilationType,
520    parameter_strings: RawHandle<StackGCVector<*mut JSString>>,
521    body_string: HandleString,
522    parameter_args: RawHandle<StackGCVector<JSVal>>,
523    body_arg: RawHandleValue,
524    can_compile_strings: *mut bool,
525) -> bool {
526    let mut allowed = false;
527    let cx = unsafe { JSContext::from_ptr(cx) };
528    wrap_panic(&mut || {
529        // SpiderMonkey provides null pointer when executing webassembly.
530        let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
531        let global = unsafe { &GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)) };
532        let csp_list = global.get_csp_list();
533
534        // If we don't have any CSP checks to run, short-circuit all logic here
535        allowed = csp_list.is_none() ||
536            match runtime_code {
537                RuntimeCode::JS => {
538                    let parameter_strings = unsafe { Handle::from_raw(parameter_strings) };
539                    let parameter_strings_length = parameter_strings.len();
540                    let mut parameter_strings_vec =
541                        Vec::with_capacity(parameter_strings_length as usize);
542
543                    for i in 0..parameter_strings_length {
544                        let Some(str_) = parameter_strings.at(i) else {
545                            unreachable!();
546                        };
547                        parameter_strings_vec.push(safely_convert_null_to_string(cx, str_.into()));
548                    }
549
550                    let parameter_args = unsafe { Handle::from_raw(parameter_args) };
551                    let parameter_args_length = parameter_args.len();
552                    let mut parameter_args_vec = Vec::with_capacity(parameter_args_length as usize);
553
554                    for i in 0..parameter_args_length {
555                        let Some(arg) = parameter_args.at(i) else {
556                            unreachable!();
557                        };
558                        let value = arg.into_handle().get();
559                        if value.is_object() {
560                            if let Ok(trusted_script) =
561                                unsafe { root_from_object::<TrustedScript>(value.to_object(), *cx) }
562                            {
563                                parameter_args_vec
564                                    .push(TrustedScriptOrString::TrustedScript(trusted_script));
565                            } else {
566                                // It's not a trusted script but a different object. Treat it
567                                // as if it is a string, since we don't need the actual contents
568                                // of the object.
569                                parameter_args_vec
570                                    .push(TrustedScriptOrString::String(DOMString::new()));
571                            }
572                        } else if value.is_string() {
573                            // We don't need to know the specific string, only that it is untrusted
574                            parameter_args_vec
575                                .push(TrustedScriptOrString::String(DOMString::new()));
576                        } else {
577                            unreachable!();
578                        }
579                    }
580
581                    TrustedScript::can_compile_string_with_trusted_type(
582                        cx,
583                        global,
584                        safely_convert_null_to_string(cx, code_string),
585                        compilation_type,
586                        parameter_strings_vec,
587                        safely_convert_null_to_string(cx, body_string),
588                        parameter_args_vec,
589                        unsafe { HandleValue::from_raw(body_arg) },
590                        CanGc::note(),
591                    )
592                },
593                RuntimeCode::WASM => global.get_csp_list().is_wasm_evaluation_allowed(global),
594            };
595    });
596    unsafe { *can_compile_strings = allowed };
597    true
598}
599
600#[expect(unsafe_code)]
601#[cfg_attr(crown, allow(crown::unrooted_must_root))]
602/// <https://html.spec.whatwg.org/multipage/#notify-about-rejected-promises>
603pub(crate) fn notify_about_rejected_promises(global: &GlobalScope) {
604    let cx = GlobalScope::get_cx();
605    unsafe {
606        // Step 2.
607        if !global.get_uncaught_rejections().borrow().is_empty() {
608            // Step 1.
609            let uncaught_rejections: Vec<TrustedPromise> = global
610                .get_uncaught_rejections()
611                .borrow()
612                .iter()
613                .map(|promise| {
614                    let promise =
615                        Promise::new_with_js_promise(Handle::from_raw(promise.handle()), cx);
616
617                    TrustedPromise::new(promise)
618                })
619                .collect();
620
621            // Step 3.
622            global.get_uncaught_rejections().borrow_mut().clear();
623
624            let target = Trusted::new(global.upcast::<EventTarget>());
625
626            // Step 4.
627            global.task_manager().dom_manipulation_task_source().queue(
628                task!(unhandled_rejection_event: move || {
629                    let target = target.root();
630                    let cx = GlobalScope::get_cx();
631
632                    for promise in uncaught_rejections {
633                        let promise = promise.root();
634
635                        // Step 4-1.
636                        let promise_is_handled = GetPromiseIsHandled(promise.reflector().get_jsobject());
637                        if promise_is_handled {
638                            continue;
639                        }
640
641                        // Step 4-2.
642                        rooted!(in(*cx) let mut reason = UndefinedValue());
643                        JS_GetPromiseResult(promise.reflector().get_jsobject(), reason.handle_mut());
644
645                        let event = PromiseRejectionEvent::new(
646                            &target.global(),
647                            atom!("unhandledrejection"),
648                            EventBubbles::DoesNotBubble,
649                            EventCancelable::Cancelable,
650                            promise.clone(),
651                            reason.handle(),
652                            CanGc::note()
653                        );
654
655                        let not_canceled = event.upcast::<Event>().fire(&target, CanGc::note());
656
657                        // Step 4-3. If notCanceled is true, then the user agent
658                        // may report p.[[PromiseResult]] to a developer console.
659                        if not_canceled {
660                            // TODO: The promise rejection is not handled; we need to add it back to the list.
661                        }
662
663                        // Step 4-4.
664                        if !promise_is_handled {
665                            target.global().add_consumed_rejection(promise.reflector().get_jsobject().into_handle());
666                        }
667                    }
668                })
669            );
670        }
671    }
672}
673
674#[derive(JSTraceable, MallocSizeOf)]
675pub(crate) struct Runtime {
676    #[ignore_malloc_size_of = "Type from mozjs"]
677    rt: RustRuntime,
678    /// Our actual microtask queue, which is preserved and untouched by the debugger when running debugger scripts.
679    #[conditional_malloc_size_of]
680    pub(crate) microtask_queue: Rc<MicrotaskQueue>,
681    #[ignore_malloc_size_of = "Type from mozjs"]
682    job_queue: *mut JobQueue,
683    script_event_loop_sender: Option<Box<ScriptEventLoopSender>>,
684}
685
686impl Runtime {
687    /// Create a new runtime, optionally with the given [`SendableTaskSource`] for networking.
688    ///
689    /// # Safety
690    ///
691    /// If panicking does not abort the program, any threads with child runtimes will continue
692    /// executing after the thread with the parent runtime panics, but they will be in an
693    /// invalid and undefined state.
694    ///
695    /// This, like many calls to SpiderMoney API, is unsafe.
696    #[expect(unsafe_code)]
697    pub(crate) fn new(main_thread_sender: Option<ScriptEventLoopSender>) -> Runtime {
698        unsafe { Self::new_with_parent(None, main_thread_sender) }
699    }
700
701    /// Create a new runtime, optionally with the given [`ParentRuntime`] and [`SendableTaskSource`]
702    /// for networking.
703    ///
704    /// # Safety
705    ///
706    /// If panicking does not abort the program, any threads with child runtimes will continue
707    /// executing after the thread with the parent runtime panics, but they will be in an
708    /// invalid and undefined state.
709    ///
710    /// The `parent` pointer in the [`ParentRuntime`] argument must point to a valid object in memory.
711    ///
712    /// This, like many calls to the SpiderMoney API, is unsafe.
713    #[expect(unsafe_code)]
714    pub(crate) unsafe fn new_with_parent(
715        parent: Option<ParentRuntime>,
716        script_event_looper_sender: Option<ScriptEventLoopSender>,
717    ) -> Runtime {
718        let mut runtime = if let Some(parent) = parent {
719            unsafe { RustRuntime::create_with_parent(parent) }
720        } else {
721            RustRuntime::new(JS_ENGINE.lock().unwrap().as_ref().unwrap().clone())
722        };
723        let cx = runtime.cx();
724
725        unsafe {
726            JS_AddExtraGCRootsTracer(cx, Some(trace_rust_roots), ptr::null_mut());
727
728            JS_SetSecurityCallbacks(cx, &SECURITY_CALLBACKS);
729
730            JS_InitDestroyPrincipalsCallback(cx, Some(principals::destroy_servo_jsprincipal));
731            JS_InitReadPrincipalsCallback(cx, Some(principals::read_jsprincipal));
732
733            // Needed for debug assertions about whether GC is running.
734            if cfg!(debug_assertions) {
735                JS_SetGCCallback(cx, Some(debug_gc_callback), ptr::null_mut());
736            }
737
738            if opts::get().debug.gc_profile {
739                SetGCSliceCallback(cx, Some(gc_slice_callback));
740            }
741        }
742
743        unsafe extern "C" fn empty_wrapper_callback(_: *mut RawJSContext, _: HandleObject) -> bool {
744            true
745        }
746        unsafe extern "C" fn empty_has_released_callback(_: HandleObject) -> bool {
747            // fixme: return true when the Drop impl for a DOM object has been invoked
748            false
749        }
750
751        unsafe {
752            SetDOMCallbacks(cx, &DOM_CALLBACKS);
753            SetPreserveWrapperCallbacks(
754                cx,
755                Some(empty_wrapper_callback),
756                Some(empty_has_released_callback),
757            );
758            // Pre barriers aren't working correctly at the moment
759            JS_SetGCParameter(cx, JSGCParamKey::JSGC_INCREMENTAL_GC_ENABLED, 0);
760        }
761
762        unsafe extern "C" fn dispatch_to_event_loop(
763            data: *mut c_void,
764            dispatchable: *mut DispatchablePointer,
765        ) -> bool {
766            let script_event_loop_sender: &ScriptEventLoopSender =
767                unsafe { &*(data as *mut ScriptEventLoopSender) };
768            let runnable = Runnable(dispatchable);
769            let task = task!(dispatch_to_event_loop_message: move || {
770                if let Some(cx) = RustRuntime::get() {
771                    runnable.run(cx.as_ptr(), Dispatchable_MaybeShuttingDown::NotShuttingDown);
772                }
773            });
774
775            script_event_loop_sender
776                .send(CommonScriptMsg::Task(
777                    ScriptThreadEventCategory::NetworkEvent,
778                    Box::new(task),
779                    None, /* pipeline_id */
780                    TaskSourceName::Networking,
781                ))
782                .is_ok()
783        }
784
785        let mut script_event_loop_sender_pointer = std::ptr::null_mut();
786        if let Some(script_event_loop_sender) = script_event_looper_sender {
787            script_event_loop_sender_pointer = Box::into_raw(Box::new(script_event_loop_sender));
788            unsafe {
789                SetUpEventLoopDispatch(
790                    cx,
791                    Some(dispatch_to_event_loop),
792                    script_event_loop_sender_pointer as *mut c_void,
793                );
794            }
795        }
796
797        unsafe {
798            InitConsumeStreamCallback(cx, Some(consume_stream), Some(report_stream_error));
799        }
800
801        let microtask_queue = Rc::new(MicrotaskQueue::default());
802
803        // Extra queues for debugger scripts (“interrupts”) via AutoDebuggerJobQueueInterruption and saveJobQueue().
804        // Moved indefinitely to mozjs via CreateJobQueue(), borrowed from mozjs via JobQueueTraps, and moved back from
805        // mozjs for dropping via DeleteJobQueue().
806        let interrupt_queues: Box<Vec<Rc<MicrotaskQueue>>> = Box::default();
807
808        let cx_opts;
809        let job_queue;
810        unsafe {
811            let cx = runtime.cx();
812            job_queue = CreateJobQueue(
813                &JOB_QUEUE_TRAPS,
814                &*microtask_queue as *const _ as *const c_void,
815                Box::into_raw(interrupt_queues) as *mut c_void,
816            );
817            SetJobQueue(cx, job_queue);
818            SetPromiseRejectionTrackerCallback(
819                cx,
820                Some(promise_rejection_tracker),
821                ptr::null_mut(),
822            );
823
824            EnsureModuleHooksInitialized(runtime.rt());
825
826            let cx = runtime.cx();
827
828            set_gc_zeal_options(cx.raw_cx());
829
830            // Enable or disable the JITs.
831            cx_opts = &mut *ContextOptionsRef(cx);
832            JS_SetGlobalJitCompilerOption(
833                cx,
834                JSJitCompilerOption::JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE,
835                pref!(js_baseline_interpreter_enabled) as u32,
836            );
837            JS_SetGlobalJitCompilerOption(
838                cx,
839                JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE,
840                pref!(js_baseline_jit_enabled) as u32,
841            );
842            JS_SetGlobalJitCompilerOption(
843                cx,
844                JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE,
845                pref!(js_ion_enabled) as u32,
846            );
847        }
848        cx_opts.compileOptions_.asmJSOption_ = if pref!(js_asmjs_enabled) {
849            AsmJSOption::Enabled
850        } else {
851            AsmJSOption::DisabledByAsmJSPref
852        };
853        let wasm_enabled = pref!(js_wasm_enabled);
854        cx_opts.set_wasm_(wasm_enabled);
855        if wasm_enabled {
856            // If WASM is enabled without setting the buildIdOp,
857            // initializing a module will report an out of memory error.
858            // https://dxr.mozilla.org/mozilla-central/source/js/src/wasm/WasmTypes.cpp#458
859            unsafe { SetProcessBuildIdOp(Some(servo_build_id)) };
860        }
861        cx_opts.set_wasmBaseline_(pref!(js_wasm_baseline_enabled));
862        cx_opts.set_wasmIon_(pref!(js_wasm_ion_enabled));
863
864        unsafe {
865            let cx = runtime.cx();
866            // TODO: handle js.throw_on_asmjs_validation_failure (needs new Spidermonkey)
867            JS_SetGlobalJitCompilerOption(
868                cx,
869                JSJitCompilerOption::JSJITCOMPILER_NATIVE_REGEXP_ENABLE,
870                pref!(js_native_regex_enabled) as u32,
871            );
872            JS_SetOffthreadIonCompilationEnabled(cx, pref!(js_offthread_compilation_enabled));
873            JS_SetGlobalJitCompilerOption(
874                cx,
875                JSJitCompilerOption::JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
876                if pref!(js_baseline_jit_unsafe_eager_compilation_enabled) {
877                    0
878                } else {
879                    u32::MAX
880                },
881            );
882            JS_SetGlobalJitCompilerOption(
883                cx,
884                JSJitCompilerOption::JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER,
885                if pref!(js_ion_unsafe_eager_compilation_enabled) {
886                    0
887                } else {
888                    u32::MAX
889                },
890            );
891            // TODO: handle js.discard_system_source.enabled
892            // TODO: handle js.asyncstack.enabled (needs new Spidermonkey)
893            // TODO: handle js.throw_on_debugee_would_run (needs new Spidermonkey)
894            // TODO: handle js.dump_stack_on_debugee_would_run (needs new Spidermonkey)
895            // TODO: handle js.shared_memory.enabled
896            JS_SetGCParameter(
897                cx,
898                JSGCParamKey::JSGC_MAX_BYTES,
899                in_range(pref!(js_mem_max), 1, 0x100)
900                    .map(|val| (val * 1024 * 1024) as u32)
901                    .unwrap_or(u32::MAX),
902            );
903            // NOTE: This is disabled above, so enabling it here will do nothing for now.
904            JS_SetGCParameter(
905                cx,
906                JSGCParamKey::JSGC_INCREMENTAL_GC_ENABLED,
907                pref!(js_mem_gc_incremental_enabled) as u32,
908            );
909            JS_SetGCParameter(
910                cx,
911                JSGCParamKey::JSGC_PER_ZONE_GC_ENABLED,
912                pref!(js_mem_gc_per_zone_enabled) as u32,
913            );
914            if let Some(val) = in_range(pref!(js_mem_gc_incremental_slice_ms), 0, 100_000) {
915                JS_SetGCParameter(cx, JSGCParamKey::JSGC_SLICE_TIME_BUDGET_MS, val as u32);
916            }
917            JS_SetGCParameter(
918                cx,
919                JSGCParamKey::JSGC_COMPACTING_ENABLED,
920                pref!(js_mem_gc_compacting_enabled) as u32,
921            );
922
923            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_time_limit_ms), 0, 10_000) {
924                JS_SetGCParameter(cx, JSGCParamKey::JSGC_HIGH_FREQUENCY_TIME_LIMIT, val as u32);
925            }
926            if let Some(val) = in_range(pref!(js_mem_gc_low_frequency_heap_growth), 0, 10_000) {
927                JS_SetGCParameter(cx, JSGCParamKey::JSGC_LOW_FREQUENCY_HEAP_GROWTH, val as u32);
928            }
929            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_heap_growth_min), 0, 10_000)
930            {
931                JS_SetGCParameter(
932                    cx,
933                    JSGCParamKey::JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH,
934                    val as u32,
935                );
936            }
937            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_heap_growth_max), 0, 10_000)
938            {
939                JS_SetGCParameter(
940                    cx,
941                    JSGCParamKey::JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH,
942                    val as u32,
943                );
944            }
945            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_low_limit_mb), 0, 10_000) {
946                JS_SetGCParameter(cx, JSGCParamKey::JSGC_SMALL_HEAP_SIZE_MAX, val as u32);
947            }
948            if let Some(val) = in_range(pref!(js_mem_gc_high_frequency_high_limit_mb), 0, 10_000) {
949                JS_SetGCParameter(cx, JSGCParamKey::JSGC_LARGE_HEAP_SIZE_MIN, val as u32);
950            }
951            /*if let Some(val) = in_range(pref!(js_mem_gc_allocation_threshold_factor), 0, 10_000) {
952                JS_SetGCParameter(cx, JSGCParamKey::JSGC_NON_INCREMENTAL_FACTOR, val as u32);
953            }*/
954            /*
955                // JSGC_SMALL_HEAP_INCREMENTAL_LIMIT
956                pref("javascript.options.mem.gc_small_heap_incremental_limit", 140);
957
958                // JSGC_LARGE_HEAP_INCREMENTAL_LIMIT
959                pref("javascript.options.mem.gc_large_heap_incremental_limit", 110);
960            */
961            if let Some(val) = in_range(pref!(js_mem_gc_empty_chunk_count_min), 0, 10_000) {
962                JS_SetGCParameter(cx, JSGCParamKey::JSGC_MIN_EMPTY_CHUNK_COUNT, val as u32);
963            }
964        }
965        Runtime {
966            rt: runtime,
967            microtask_queue,
968            job_queue,
969            script_event_loop_sender: (!script_event_loop_sender_pointer.is_null())
970                .then(|| unsafe { Box::from_raw(script_event_loop_sender_pointer) }),
971        }
972    }
973
974    pub(crate) fn thread_safe_js_context(&self) -> ThreadSafeJSContext {
975        self.rt.thread_safe_js_context()
976    }
977}
978
979impl Drop for Runtime {
980    #[expect(unsafe_code)]
981    fn drop(&mut self) {
982        // Clear our main microtask_queue.
983        self.microtask_queue.clear();
984
985        // Delete the RustJobQueue in mozjs, which will destroy our interrupt queues.
986        unsafe {
987            DeleteJobQueue(self.job_queue);
988        }
989        LiveDOMReferences::destruct();
990        mark_runtime_dead();
991    }
992}
993
994impl Deref for Runtime {
995    type Target = RustRuntime;
996    fn deref(&self) -> &RustRuntime {
997        &self.rt
998    }
999}
1000
1001impl DerefMut for Runtime {
1002    fn deref_mut(&mut self) -> &mut RustRuntime {
1003        &mut self.rt
1004    }
1005}
1006
1007pub struct JSEngineSetup(JSEngine);
1008
1009impl Default for JSEngineSetup {
1010    fn default() -> Self {
1011        let engine = JSEngine::init().unwrap();
1012        *JS_ENGINE.lock().unwrap() = Some(engine.handle());
1013        Self(engine)
1014    }
1015}
1016
1017impl Drop for JSEngineSetup {
1018    fn drop(&mut self) {
1019        *JS_ENGINE.lock().unwrap() = None;
1020
1021        while !self.0.can_shutdown() {
1022            thread::sleep(Duration::from_millis(50));
1023        }
1024    }
1025}
1026
1027static JS_ENGINE: Mutex<Option<JSEngineHandle>> = Mutex::new(None);
1028
1029fn in_range<T: PartialOrd + Copy>(val: T, min: T, max: T) -> Option<T> {
1030    if val < min || val >= max {
1031        None
1032    } else {
1033        Some(val)
1034    }
1035}
1036
1037thread_local!(static MALLOC_SIZE_OF_OPS: Cell<*mut MallocSizeOfOps> = const { Cell::new(ptr::null_mut()) });
1038
1039#[expect(unsafe_code)]
1040unsafe extern "C" fn get_size(obj: *mut JSObject) -> usize {
1041    match unsafe { get_dom_class(obj) } {
1042        Ok(v) => {
1043            let dom_object = unsafe { private_from_object(obj) as *const c_void };
1044
1045            if dom_object.is_null() {
1046                return 0;
1047            }
1048            let ops = MALLOC_SIZE_OF_OPS.get();
1049            unsafe { (v.malloc_size_of)(&mut *ops, dom_object) }
1050        },
1051        Err(_e) => 0,
1052    }
1053}
1054
1055thread_local!(static GC_CYCLE_START: Cell<Option<Instant>> = const { Cell::new(None) });
1056thread_local!(static GC_SLICE_START: Cell<Option<Instant>> = const { Cell::new(None) });
1057
1058#[expect(unsafe_code)]
1059unsafe extern "C" fn gc_slice_callback(
1060    _cx: *mut RawJSContext,
1061    progress: GCProgress,
1062    desc: *const GCDescription,
1063) {
1064    match progress {
1065        GCProgress::GC_CYCLE_BEGIN => GC_CYCLE_START.with(|start| {
1066            start.set(Some(Instant::now()));
1067            println!("GC cycle began");
1068        }),
1069        GCProgress::GC_SLICE_BEGIN => GC_SLICE_START.with(|start| {
1070            start.set(Some(Instant::now()));
1071            println!("GC slice began");
1072        }),
1073        GCProgress::GC_SLICE_END => GC_SLICE_START.with(|start| {
1074            let duration = start.get().unwrap().elapsed();
1075            start.set(None);
1076            println!("GC slice ended: duration={:?}", duration);
1077        }),
1078        GCProgress::GC_CYCLE_END => GC_CYCLE_START.with(|start| {
1079            let duration = start.get().unwrap().elapsed();
1080            start.set(None);
1081            println!("GC cycle ended: duration={:?}", duration);
1082        }),
1083    };
1084    if !desc.is_null() {
1085        let desc: &GCDescription = unsafe { &*desc };
1086        let options = match desc.options_ {
1087            GCOptions::Normal => "Normal",
1088            GCOptions::Shrink => "Shrink",
1089            GCOptions::Shutdown => "Shutdown",
1090        };
1091        println!("  isZone={}, options={}", desc.isZone_, options);
1092    }
1093    let _ = stdout().flush();
1094}
1095
1096#[expect(unsafe_code)]
1097unsafe extern "C" fn debug_gc_callback(
1098    _cx: *mut RawJSContext,
1099    status: JSGCStatus,
1100    _reason: GCReason,
1101    _data: *mut os::raw::c_void,
1102) {
1103    match status {
1104        JSGCStatus::JSGC_BEGIN => thread_state::enter(ThreadState::IN_GC),
1105        JSGCStatus::JSGC_END => thread_state::exit(ThreadState::IN_GC),
1106    }
1107}
1108
1109#[expect(unsafe_code)]
1110unsafe extern "C" fn trace_rust_roots(tr: *mut JSTracer, _data: *mut os::raw::c_void) {
1111    if !runtime_is_alive() {
1112        return;
1113    }
1114    trace!("starting custom root handler");
1115    unsafe {
1116        trace_thread(tr);
1117        trace_roots(tr);
1118        trace_refcounted_objects(tr);
1119        settings_stack::trace(tr);
1120    }
1121    trace!("done custom root handler");
1122}
1123
1124#[expect(unsafe_code)]
1125unsafe extern "C" fn servo_build_id(build_id: *mut BuildIdCharVector) -> bool {
1126    let servo_id = b"Servo\0";
1127    unsafe { SetBuildId(build_id, servo_id[0] as *const c_char, servo_id.len()) }
1128}
1129
1130#[expect(unsafe_code)]
1131#[cfg(feature = "debugmozjs")]
1132unsafe fn set_gc_zeal_options(cx: *mut RawJSContext) {
1133    use js::jsapi::SetGCZeal;
1134
1135    let level = match pref!(js_mem_gc_zeal_level) {
1136        level @ 0..=14 => level as u8,
1137        _ => return,
1138    };
1139    let frequency = match pref!(js_mem_gc_zeal_frequency) {
1140        frequency if frequency >= 0 => frequency as u32,
1141        // https://searchfox.org/mozilla-esr128/source/js/public/GCAPI.h#1392
1142        _ => 5000,
1143    };
1144    SetGCZeal(cx, level, frequency);
1145}
1146
1147#[expect(unsafe_code)]
1148#[cfg(not(feature = "debugmozjs"))]
1149unsafe fn set_gc_zeal_options(_: *mut RawJSContext) {}
1150
1151pub(crate) use script_bindings::script_runtime::JSContext;
1152
1153/// Extra methods for the JSContext type defined in script_bindings, when
1154/// the methods are only called by code in the script crate.
1155pub(crate) trait JSContextHelper {
1156    fn get_reports(&self, path_seg: String, ops: &mut MallocSizeOfOps) -> Vec<Report>;
1157}
1158
1159impl JSContextHelper for JSContext {
1160    #[expect(unsafe_code)]
1161    fn get_reports(&self, path_seg: String, ops: &mut MallocSizeOfOps) -> Vec<Report> {
1162        MALLOC_SIZE_OF_OPS.with(|ops_tls| ops_tls.set(ops));
1163        let stats = unsafe {
1164            let mut stats = ::std::mem::zeroed();
1165            if !CollectServoSizes(**self, &mut stats, Some(get_size)) {
1166                return vec![];
1167            }
1168            stats
1169        };
1170        MALLOC_SIZE_OF_OPS.with(|ops| ops.set(ptr::null_mut()));
1171
1172        let mut reports = vec![];
1173        let mut report = |mut path_suffix, kind, size| {
1174            let mut path = path![path_seg, "js"];
1175            path.append(&mut path_suffix);
1176            reports.push(Report { path, kind, size })
1177        };
1178
1179        // A note about possibly confusing terminology: the JS GC "heap" is allocated via
1180        // mmap/VirtualAlloc, which means it's not on the malloc "heap", so we use
1181        // `ExplicitNonHeapSize` as its kind.
1182        report(
1183            path!["gc-heap", "used"],
1184            ReportKind::ExplicitNonHeapSize,
1185            stats.gcHeapUsed,
1186        );
1187
1188        report(
1189            path!["gc-heap", "unused"],
1190            ReportKind::ExplicitNonHeapSize,
1191            stats.gcHeapUnused,
1192        );
1193
1194        report(
1195            path!["gc-heap", "admin"],
1196            ReportKind::ExplicitNonHeapSize,
1197            stats.gcHeapAdmin,
1198        );
1199
1200        report(
1201            path!["gc-heap", "decommitted"],
1202            ReportKind::ExplicitNonHeapSize,
1203            stats.gcHeapDecommitted,
1204        );
1205
1206        // SpiderMonkey uses the system heap, not jemalloc.
1207        report(
1208            path!["malloc-heap"],
1209            ReportKind::ExplicitSystemHeapSize,
1210            stats.mallocHeap,
1211        );
1212
1213        report(
1214            path!["non-heap"],
1215            ReportKind::ExplicitNonHeapSize,
1216            stats.nonHeap,
1217        );
1218        reports
1219    }
1220}
1221
1222pub(crate) struct StreamConsumer(*mut JSStreamConsumer);
1223
1224#[expect(unsafe_code)]
1225impl StreamConsumer {
1226    pub(crate) fn consume_chunk(&self, stream: &[u8]) -> bool {
1227        unsafe {
1228            let stream_ptr = stream.as_ptr();
1229            StreamConsumerConsumeChunk(self.0, stream_ptr, stream.len())
1230        }
1231    }
1232
1233    pub(crate) fn stream_end(&self) {
1234        unsafe {
1235            StreamConsumerStreamEnd(self.0);
1236        }
1237    }
1238
1239    pub(crate) fn stream_error(&self, error_code: usize) {
1240        unsafe {
1241            StreamConsumerStreamError(self.0, error_code);
1242        }
1243    }
1244
1245    pub(crate) fn note_response_urls(
1246        &self,
1247        maybe_url: Option<String>,
1248        maybe_source_map_url: Option<String>,
1249    ) {
1250        unsafe {
1251            let maybe_url = maybe_url.map(|url| CString::new(url).unwrap());
1252            let maybe_source_map_url = maybe_source_map_url.map(|url| CString::new(url).unwrap());
1253
1254            let maybe_url_param = match maybe_url.as_ref() {
1255                Some(url) => url.as_ptr(),
1256                None => ptr::null(),
1257            };
1258            let maybe_source_map_url_param = match maybe_source_map_url.as_ref() {
1259                Some(url) => url.as_ptr(),
1260                None => ptr::null(),
1261            };
1262
1263            StreamConsumerNoteResponseURLs(self.0, maybe_url_param, maybe_source_map_url_param);
1264        }
1265    }
1266}
1267
1268/// Implements the steps to compile webassembly response mentioned here
1269/// <https://webassembly.github.io/spec/web-api/#compile-a-potential-webassembly-response>
1270#[expect(unsafe_code)]
1271unsafe extern "C" fn consume_stream(
1272    cx: *mut RawJSContext,
1273    obj: HandleObject,
1274    _mime_type: MimeType,
1275    _consumer: *mut JSStreamConsumer,
1276) -> bool {
1277    let cx = unsafe { JSContext::from_ptr(cx) };
1278    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1279    let global = unsafe { GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)) };
1280
1281    // Step 2.1 Upon fulfillment of source, store the Response with value unwrappedSource.
1282    if let Ok(unwrapped_source) =
1283        root_from_handleobject::<Response>(unsafe { RustHandleObject::from_raw(obj) }, *cx)
1284    {
1285        // Step 2.2 Let mimeType be the result of extracting a MIME type from response’s header list.
1286        let mimetype = unwrapped_source.Headers(CanGc::note()).extract_mime_type();
1287
1288        // Step 2.3 If mimeType is not `application/wasm`, return with a TypeError and abort these substeps.
1289        if !&mimetype[..].eq_ignore_ascii_case(b"application/wasm") {
1290            throw_dom_exception(
1291                cx,
1292                &global,
1293                Error::Type("Response has unsupported MIME type".to_string()),
1294                CanGc::note(),
1295            );
1296            return false;
1297        }
1298
1299        // Step 2.4 If response is not CORS-same-origin, return with a TypeError and abort these substeps.
1300        match unwrapped_source.Type() {
1301            DOMResponseType::Basic | DOMResponseType::Cors | DOMResponseType::Default => {},
1302            _ => {
1303                throw_dom_exception(
1304                    cx,
1305                    &global,
1306                    Error::Type("Response.type must be 'basic', 'cors' or 'default'".to_string()),
1307                    CanGc::note(),
1308                );
1309                return false;
1310            },
1311        }
1312
1313        // Step 2.5 If response’s status is not an ok status, return with a TypeError and abort these substeps.
1314        if !unwrapped_source.Ok() {
1315            throw_dom_exception(
1316                cx,
1317                &global,
1318                Error::Type("Response does not have ok status".to_string()),
1319                CanGc::note(),
1320            );
1321            return false;
1322        }
1323
1324        // Step 2.6.1 If response body is locked, return with a TypeError and abort these substeps.
1325        if unwrapped_source.is_locked() {
1326            throw_dom_exception(
1327                cx,
1328                &global,
1329                Error::Type("There was an error consuming the Response".to_string()),
1330                CanGc::note(),
1331            );
1332            return false;
1333        }
1334
1335        // Step 2.6.2 If response body is alreaady consumed, return with a TypeError and abort these substeps.
1336        if unwrapped_source.is_disturbed() {
1337            throw_dom_exception(
1338                cx,
1339                &global,
1340                Error::Type("Response already consumed".to_string()),
1341                CanGc::note(),
1342            );
1343            return false;
1344        }
1345        unwrapped_source.set_stream_consumer(Some(StreamConsumer(_consumer)));
1346    } else {
1347        // Step 3 Upon rejection of source, return with reason.
1348        throw_dom_exception(
1349            cx,
1350            &global,
1351            Error::Type("expected Response or Promise resolving to Response".to_string()),
1352            CanGc::note(),
1353        );
1354        return false;
1355    }
1356    true
1357}
1358
1359#[expect(unsafe_code)]
1360unsafe extern "C" fn report_stream_error(_cx: *mut RawJSContext, error_code: usize) {
1361    error!("Error initializing StreamConsumer: {:?}", unsafe {
1362        RUST_js_GetErrorMessage(ptr::null_mut(), error_code as u32)
1363    });
1364}
1365
1366pub(crate) struct Runnable(*mut DispatchablePointer);
1367
1368#[expect(unsafe_code)]
1369unsafe impl Sync for Runnable {}
1370#[expect(unsafe_code)]
1371unsafe impl Send for Runnable {}
1372
1373#[expect(unsafe_code)]
1374impl Runnable {
1375    fn run(&self, cx: *mut RawJSContext, maybe_shutting_down: Dispatchable_MaybeShuttingDown) {
1376        unsafe {
1377            DispatchableRun(cx, self.0, maybe_shutting_down);
1378        }
1379    }
1380}
1381
1382pub(crate) use script_bindings::script_runtime::CanGc;
1383
1384/// `introductionType` values in SpiderMonkey TransitiveCompileOptions.
1385///
1386/// Value definitions are based on the SpiderMonkey Debugger API docs:
1387/// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Source.html#introductiontype>
1388// TODO: squish `scriptElement` <https://searchfox.org/mozilla-central/rev/202069c4c5113a1a9052d84fa4679d4c1b22113e/devtools/server/actors/source.js#199-201>
1389pub(crate) struct IntroductionType;
1390impl IntroductionType {
1391    /// `introductionType` for code passed to `eval`.
1392    pub const EVAL: &CStr = c"eval";
1393    pub const EVAL_STR: &str = "eval";
1394
1395    /// `introductionType` for code evaluated by debugger.
1396    /// This includes code run via the devtools repl, even if the thread is not paused.
1397    pub const DEBUGGER_EVAL: &CStr = c"debugger eval";
1398    pub const DEBUGGER_EVAL_STR: &str = "debugger eval";
1399
1400    /// `introductionType` for code passed to the `Function` constructor.
1401    pub const FUNCTION: &CStr = c"Function";
1402    pub const FUNCTION_STR: &str = "Function";
1403
1404    /// `introductionType` for code loaded by worklet.
1405    pub const WORKLET: &CStr = c"Worklet";
1406    pub const WORKLET_STR: &str = "Worklet";
1407
1408    /// `introductionType` for code assigned to DOM elements’ event handler IDL attributes as a string.
1409    pub const EVENT_HANDLER: &CStr = c"eventHandler";
1410    pub const EVENT_HANDLER_STR: &str = "eventHandler";
1411
1412    /// `introductionType` for code belonging to `<script src="file.js">` elements.
1413    /// This includes `<script type="module" src="...">`.
1414    pub const SRC_SCRIPT: &CStr = c"srcScript";
1415    pub const SRC_SCRIPT_STR: &str = "srcScript";
1416
1417    /// `introductionType` for code belonging to `<script>code;</script>` elements.
1418    /// This includes `<script type="module" src="...">`.
1419    pub const INLINE_SCRIPT: &CStr = c"inlineScript";
1420    pub const INLINE_SCRIPT_STR: &str = "inlineScript";
1421
1422    /// `introductionType` for code belonging to scripts that *would* be `"inlineScript"` except that they were not
1423    /// part of the initial file itself.
1424    /// For example, scripts created via:
1425    /// - `document.write("<script>code;</script>")`
1426    /// - `var s = document.createElement("script"); s.text = "code";`
1427    pub const INJECTED_SCRIPT: &CStr = c"injectedScript";
1428    pub const INJECTED_SCRIPT_STR: &str = "injectedScript";
1429
1430    /// `introductionType` for code that was loaded indirectly by being imported by another script
1431    /// using ESM static or dynamic imports.
1432    pub const IMPORTED_MODULE: &CStr = c"importedModule";
1433    pub const IMPORTED_MODULE_STR: &str = "importedModule";
1434
1435    /// `introductionType` for code presented in `javascript:` URLs.
1436    pub const JAVASCRIPT_URL: &CStr = c"javascriptURL";
1437    pub const JAVASCRIPT_URL_STR: &str = "javascriptURL";
1438
1439    /// `introductionType` for code passed to `setTimeout`/`setInterval` as a string.
1440    pub const DOM_TIMER: &CStr = c"domTimer";
1441    pub const DOM_TIMER_STR: &str = "domTimer";
1442
1443    /// `introductionType` for web workers.
1444    /// FIXME: only documented in older(?) devtools user docs
1445    /// <https://firefox-source-docs.mozilla.org/devtools-user/debugger-api/debugger.source/index.html>
1446    pub const WORKER: &CStr = c"Worker";
1447    pub const WORKER_STR: &str = "Worker";
1448}