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