script/
script_runtime.rs

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