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