Skip to main content

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