script/
script_runtime.rs

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