Skip to main content

boa_engine/
realm.rs

1//! Boa's implementation of ECMAScript's `Realm Records`
2//!
3//! Conceptually, a realm consists of a set of intrinsic objects, an ECMAScript global environment,
4//! all of the ECMAScript code that is loaded within the scope of that global environment,
5//! and other associated state and resources.
6//!
7//! A realm is represented in this implementation as a Realm struct with the fields specified from the spec.
8
9use std::any::TypeId;
10use std::cell::Cell;
11
12use crate::{
13    Context, HostDefined, JsNativeError, JsObject, JsResult, JsString,
14    class::Class,
15    context::{
16        HostHooks,
17        intrinsics::{Intrinsics, StandardConstructor},
18    },
19    environments::DeclarativeEnvironment,
20    module::Module,
21    object::shape::RootShape,
22    string::StaticJsStrings,
23};
24use boa_ast::scope::Scope;
25use boa_engine::JsValue;
26use boa_engine::property::{Attribute, PropertyDescriptor, PropertyKey};
27use boa_gc::{Finalize, Gc, GcRef, GcRefCell, GcRefMut, Trace};
28use rustc_hash::FxHashMap;
29
30/// Representation of a Realm.
31///
32/// In the specification these are called Realm Records.
33#[derive(Clone, Trace, Finalize)]
34pub struct Realm {
35    inner: Gc<Inner>,
36}
37
38impl Eq for Realm {}
39
40impl PartialEq for Realm {
41    fn eq(&self, other: &Self) -> bool {
42        Gc::ptr_eq(&self.inner, &other.inner)
43    }
44}
45
46impl std::fmt::Debug for Realm {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_struct("Realm")
49            .field("intrinsics", &self.inner.intrinsics)
50            .field("environment", &self.inner.environment)
51            .field("global_object", &self.inner.global_object)
52            .field("global_this", &self.inner.global_this)
53            .finish()
54    }
55}
56
57#[derive(Trace, Finalize)]
58struct Inner {
59    intrinsics: Intrinsics,
60
61    /// The global declarative environment of this realm.
62    environment: Gc<DeclarativeEnvironment>,
63
64    /// The global scope of this realm.
65    /// This is directly related to the global declarative environment.
66    // Safety: Nothing in `Scope` needs tracing.
67    #[unsafe_ignore_trace]
68    scope: Scope,
69
70    global_object: JsObject,
71    global_this: JsObject,
72    template_map: GcRefCell<FxHashMap<u64, JsObject>>,
73    loaded_modules: GcRefCell<FxHashMap<JsString, Module>>,
74    host_classes: GcRefCell<FxHashMap<TypeId, StandardConstructor>>,
75
76    host_defined: GcRefCell<HostDefined>,
77
78    // Safety: a bool has nothing to trace.
79    #[unsafe_ignore_trace]
80    math_pristine: Cell<bool>,
81}
82
83impl Realm {
84    /// Create a new [`Realm`].
85    #[inline]
86    pub fn create(hooks: &dyn HostHooks, root_shape: &RootShape) -> JsResult<Self> {
87        let intrinsics = Intrinsics::uninit(root_shape).ok_or_else(|| {
88            JsNativeError::typ().with_message("failed to create the realm intrinsics")
89        })?;
90
91        let global_object = hooks.create_global_object(&intrinsics);
92        let global_this = hooks
93            .create_global_this(&intrinsics)
94            .unwrap_or_else(|| global_object.clone());
95        let environment = Gc::new(DeclarativeEnvironment::global());
96        let scope = Scope::new_global();
97
98        let realm = Self {
99            inner: Gc::new(Inner {
100                intrinsics,
101                environment,
102                scope,
103                global_object,
104                global_this,
105                template_map: GcRefCell::default(),
106                loaded_modules: GcRefCell::default(),
107                host_classes: GcRefCell::default(),
108                host_defined: GcRefCell::default(),
109                math_pristine: Cell::new(false),
110            }),
111        };
112
113        realm.initialize();
114
115        Ok(realm)
116    }
117
118    /// Gets the intrinsics of this `Realm`.
119    #[inline]
120    #[must_use]
121    pub fn intrinsics(&self) -> &Intrinsics {
122        &self.inner.intrinsics
123    }
124
125    /// Returns an immutable reference to the [`ECMAScript specification`][spec] defined
126    /// [`\[\[\HostDefined]\]`][`HostDefined`] field of the [`Realm`].
127    ///
128    /// [spec]: https://tc39.es/ecma262/#table-realm-record-fields
129    ///
130    /// # Panics
131    ///
132    /// Panics if [`HostDefined`] field is mutably borrowed.
133    #[inline]
134    #[must_use]
135    pub fn host_defined(&self) -> GcRef<'_, HostDefined> {
136        self.inner.host_defined.borrow()
137    }
138
139    /// Returns a mutable reference to [`ECMAScript specification`][spec] defined
140    /// [`\[\[\HostDefined]\]`][`HostDefined`] field of the [`Realm`].
141    ///
142    /// [spec]: https://tc39.es/ecma262/#table-realm-record-fields
143    ///
144    /// # Panics
145    ///
146    /// Panics if [`HostDefined`] field is borrowed.
147    #[inline]
148    #[must_use]
149    pub fn host_defined_mut(&self) -> GcRefMut<'_, HostDefined> {
150        self.inner.host_defined.borrow_mut()
151    }
152
153    /// Checks if this `Realm` has the class `C` registered into its class map.
154    #[must_use]
155    pub fn has_class<C: Class>(&self) -> bool {
156        self.inner
157            .host_classes
158            .borrow()
159            .contains_key(&TypeId::of::<C>())
160    }
161
162    /// Gets the constructor and prototype of the class `C` if it is registered in the class map.
163    #[must_use]
164    pub fn get_class<C: Class>(&self) -> Option<StandardConstructor> {
165        self.inner
166            .host_classes
167            .borrow()
168            .get(&TypeId::of::<C>())
169            .cloned()
170    }
171
172    pub(crate) fn environment(&self) -> &Gc<DeclarativeEnvironment> {
173        &self.inner.environment
174    }
175
176    /// Returns the scope of this realm.
177    #[must_use]
178    pub fn scope(&self) -> &Scope {
179        &self.inner.scope
180    }
181
182    pub(crate) fn global_object(&self) -> &JsObject {
183        &self.inner.global_object
184    }
185
186    pub(crate) fn global_this(&self) -> &JsObject {
187        &self.inner.global_this
188    }
189
190    /// `true` while this realm's global `Math` is still `%Math%` and the pure
191    /// `Math` methods are still the intrinsic functions.
192    #[inline]
193    pub(crate) fn math_pristine(&self) -> bool {
194        self.inner.math_pristine.get()
195    }
196
197    /// Record that default global bindings installed the intrinsic `Math`.
198    #[inline]
199    pub(crate) fn mark_math_pristine(&self) {
200        self.inner.math_pristine.set(true);
201    }
202
203    #[inline]
204    pub(crate) fn clear_math_pristine(&self) {
205        self.inner.math_pristine.set(false);
206    }
207
208    /// Permanently clear the pristine-`Math` flag if this write can change it.
209    ///
210    /// Unrelated objects miss on one pointer compare against `%Math%`. The global
211    /// object is compared only when `key` is `Math`.
212    #[inline]
213    pub(crate) fn observe_math_write(&self, object: &JsObject, key: &PropertyKey) {
214        if !self.inner.math_pristine.get() {
215            return;
216        }
217        if self.intrinsics().objects().is_math(object)
218            || (matches!(key, PropertyKey::String(name) if *name == StaticJsStrings::MATH)
219                && JsObject::equals(object, self.global_object()))
220        {
221            self.inner.math_pristine.set(false);
222        }
223    }
224
225    pub(crate) fn loaded_modules(&self) -> &GcRefCell<FxHashMap<JsString, Module>> {
226        &self.inner.loaded_modules
227    }
228
229    /// Resizes the number of bindings on the global environment.
230    pub(crate) fn resize_global_env(&self) {
231        let binding_number = self.scope().num_bindings();
232        let env = self
233            .environment()
234            .kind()
235            .as_global()
236            .expect("Realm should only store global environments");
237        let mut bindings = env.bindings().borrow_mut();
238
239        if bindings.len() < binding_number as usize {
240            bindings.resize(binding_number as usize, None);
241        }
242    }
243
244    pub(crate) fn push_template(&self, site: u64, template: JsObject) {
245        self.inner.template_map.borrow_mut().insert(site, template);
246    }
247
248    pub(crate) fn lookup_template(&self, site: u64) -> Option<JsObject> {
249        self.inner.template_map.borrow().get(&site).cloned()
250    }
251
252    /// Register a property on the global object of this realm.
253    ///
254    /// It will return an error if the property is already defined.
255    pub fn register_property<K, V>(
256        &self,
257        key: K,
258        value: V,
259        attribute: Attribute,
260        context: &mut Context,
261    ) -> JsResult<()>
262    where
263        K: Into<PropertyKey>,
264        V: Into<JsValue>,
265    {
266        self.global_object().define_property_or_throw(
267            key,
268            PropertyDescriptor::builder()
269                .value(value)
270                .writable(attribute.writable())
271                .enumerable(attribute.enumerable())
272                .configurable(attribute.configurable()),
273            context,
274        )?;
275        Ok(())
276    }
277
278    /// Register a class `C` in this realm.
279    pub fn register_class<C: Class>(&self, spec: StandardConstructor) {
280        self.inner
281            .host_classes
282            .borrow_mut()
283            .insert(TypeId::of::<C>(), spec);
284    }
285
286    /// Unregister a class `C` in this realm.
287    #[must_use]
288    pub fn unregister_class<C: Class>(&self) -> Option<StandardConstructor> {
289        self.inner
290            .host_classes
291            .borrow_mut()
292            .remove(&TypeId::of::<C>())
293    }
294
295    pub(crate) fn addr(&self) -> *const () {
296        let ptr: *const _ = &raw const *self.inner;
297        ptr.cast()
298    }
299}