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