Skip to main content

boa_engine/context/
mod.rs

1//! The ECMAScript context.
2
3use std::any::Any;
4use std::{cell::Cell, path::Path, rc::Rc};
5
6use boa_ast::StatementList;
7use boa_interner::Interner;
8use boa_parser::source::ReadChar;
9pub use hooks::{DefaultHooks, HostHooks};
10#[cfg(feature = "intl")]
11pub use icu::IcuError;
12use intrinsics::Intrinsics;
13#[cfg(any(feature = "temporal", feature = "intl"))]
14use temporal_rs::provider::TimeZoneProvider;
15#[cfg(any(feature = "temporal", feature = "intl"))]
16use timezone_provider::experimental_tzif::ZeroCompiledTzdbProvider;
17
18use crate::job::Job;
19use crate::js_error;
20use crate::module::DynModuleLoader;
21use crate::vm::{CodeBlock, RuntimeLimits, create_function_object_fast};
22use crate::{
23    HostDefined, JsNativeError, JsResult, JsString, JsValue, Source, builtins,
24    class::{Class, ClassBuilder},
25    job::{JobExecutor, SimpleJobExecutor},
26    js_string,
27    module::{IdleModuleLoader, ModuleLoader, SimpleModuleLoader},
28    native_function::NativeFunction,
29    object::{FunctionObjectBuilder, JsObject, shape::RootShape},
30    optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics},
31    property::{Attribute, PropertyDescriptor, PropertyKey},
32    realm::Realm,
33    script::Script,
34    vm::{ActiveRunnable, CallFrame, Vm},
35};
36
37use self::intrinsics::StandardConstructor;
38
39pub mod time;
40use crate::context::time::StdClock;
41pub use time::Clock;
42
43mod hooks;
44#[cfg(feature = "intl")]
45pub(crate) mod icu;
46pub mod intrinsics;
47
48thread_local! {
49    static CANNOT_BLOCK_COUNTER: Cell<u64> = const { Cell::new(0) };
50}
51
52/// ECMAScript context. It is the primary way to interact with the runtime.
53///
54/// `Context`s constructed in a thread share the same runtime, therefore it
55/// is possible to share objects from one context to another context, but they
56/// have to be in the same thread.
57///
58/// # Examples
59///
60/// ## Execute Function of Script File
61///
62/// ```rust
63/// use boa_engine::{
64///     Context, Source, js_string,
65///     object::ObjectInitializer,
66///     property::{Attribute, PropertyDescriptor},
67/// };
68///
69/// let script = r#"
70///     function test(arg1) {
71///         if(arg1 != null) {
72///             return arg1.x;
73///         }
74///         return 112233;
75///     }
76/// "#;
77///
78/// let mut context = Context::default();
79///
80/// // Populate the script definition to the context.
81/// context.eval(Source::from_bytes(script)).unwrap();
82///
83/// // Create an object that can be used in eval calls.
84/// let arg = ObjectInitializer::new(&mut context)
85///     .property(js_string!("x"), 12, Attribute::READONLY)
86///     .build();
87/// context
88///     .register_global_property(js_string!("arg"), arg, Attribute::all())
89///     .expect("property shouldn't exist");
90///
91/// let value = context.eval(Source::from_bytes("test(arg)")).unwrap();
92///
93/// assert_eq!(value.as_number(), Some(12.0))
94/// ```
95pub struct Context {
96    /// String interner in the context.
97    interner: Interner,
98
99    /// Execute in strict mode,
100    strict: bool,
101
102    /// Number of instructions remaining before a forced exit
103    #[cfg(feature = "fuzz")]
104    pub(crate) instructions_remaining: usize,
105
106    pub(crate) vm: Vm,
107
108    pub(crate) kept_alive: Vec<JsObject>,
109
110    can_block: bool,
111
112    #[cfg(any(feature = "temporal", feature = "intl"))]
113    timezone_provider: Box<dyn TimeZoneProvider>,
114
115    /// Intl data provider.
116    #[cfg(feature = "intl")]
117    intl_provider: icu::IntlProvider,
118
119    host_hooks: Rc<dyn HostHooks>,
120
121    clock: Rc<dyn Clock>,
122
123    job_executor: Rc<dyn JobExecutor>,
124
125    module_loader: Rc<dyn DynModuleLoader>,
126
127    optimizer_options: OptimizerOptions,
128    root_shape: RootShape,
129
130    /// Unique identifier for each parser instance used during the context lifetime.
131    parser_identifier: u32,
132
133    data: HostDefined<dyn Any>,
134}
135
136impl std::fmt::Debug for Context {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        let mut debug = f.debug_struct("Context");
139
140        debug
141            .field("realm", &self.vm.frame().realm)
142            .field("interner", &self.interner)
143            .field("vm", &self.vm)
144            .field("strict", &self.strict)
145            .field("job_executor", &"JobExecutor")
146            .field("hooks", &"HostHooks")
147            .field("clock", &"Clock")
148            .field("module_loader", &"ModuleLoader")
149            .field("optimizer_options", &self.optimizer_options);
150
151        #[cfg(feature = "intl")]
152        debug.field("intl_provider", &self.intl_provider);
153
154        // TODO: Support TimeZoneProvider debug names
155        #[cfg(feature = "temporal")]
156        debug.field("timezone_provider", &"TimeZoneProvider");
157
158        debug.finish_non_exhaustive()
159    }
160}
161
162impl Drop for Context {
163    fn drop(&mut self) {
164        if !self.can_block {
165            CANNOT_BLOCK_COUNTER.set(CANNOT_BLOCK_COUNTER.get() - 1);
166        }
167    }
168}
169
170impl Default for Context {
171    fn default() -> Self {
172        ContextBuilder::default()
173            .build()
174            .expect("Building the default context should not fail")
175    }
176}
177
178// ==== Public API ====
179impl Context {
180    /// Create a new [`ContextBuilder`] to specify the [`Interner`] and/or
181    /// the icu data provider.
182    #[must_use]
183    pub fn builder() -> ContextBuilder {
184        ContextBuilder::default()
185    }
186
187    /// Evaluates the given source by compiling down to bytecode, then interpreting the
188    /// bytecode into a value.
189    ///
190    /// # Examples
191    /// ```
192    /// # use boa_engine::{Context, Source};
193    /// let mut context = Context::default();
194    ///
195    /// let source = Source::from_bytes("1 + 3");
196    /// let value = context.eval(source).unwrap();
197    ///
198    /// assert!(value.is_number());
199    /// assert_eq!(value.as_number().unwrap(), 4.0);
200    /// ```
201    ///
202    /// Note that this won't run any scheduled promise jobs; you need to call [`Context::run_jobs`]
203    /// on the context or [`JobExecutor::run_jobs`] on the provided queue to run them.
204    #[allow(clippy::unit_arg, dropping_copy_types)]
205    pub fn eval<R: ReadChar>(&mut self, src: Source<'_, R>) -> JsResult<JsValue> {
206        Script::parse(src, None, self)?.evaluate(self)
207    }
208
209    /// Applies optimizations to the [`StatementList`] inplace.
210    pub fn optimize_statement_list(
211        &mut self,
212        statement_list: &mut StatementList,
213    ) -> OptimizerStatistics {
214        let mut optimizer = Optimizer::new(self);
215        optimizer.apply(statement_list)
216    }
217
218    /// Register a global property.
219    ///
220    /// It will return an error if the property is already defined.
221    ///
222    /// # Example
223    /// ```
224    /// use boa_engine::{
225    ///     Context, js_string,
226    ///     object::ObjectInitializer,
227    ///     property::{Attribute, PropertyDescriptor},
228    /// };
229    ///
230    /// let mut context = Context::default();
231    ///
232    /// context
233    ///     .register_global_property(
234    ///         js_string!("myPrimitiveProperty"),
235    ///         10,
236    ///         Attribute::all(),
237    ///     )
238    ///     .expect("property shouldn't exist");
239    ///
240    /// let object = ObjectInitializer::new(&mut context)
241    ///     .property(js_string!("x"), 0, Attribute::all())
242    ///     .property(js_string!("y"), 1, Attribute::all())
243    ///     .build();
244    /// context
245    ///     .register_global_property(
246    ///         js_string!("myObjectProperty"),
247    ///         object,
248    ///         Attribute::all(),
249    ///     )
250    ///     .expect("property shouldn't exist");
251    /// ```
252    pub fn register_global_property<K, V>(
253        &mut self,
254        key: K,
255        value: V,
256        attribute: Attribute,
257    ) -> JsResult<()>
258    where
259        K: Into<PropertyKey>,
260        V: Into<JsValue>,
261    {
262        self.global_object().define_property_or_throw(
263            key,
264            PropertyDescriptor::builder()
265                .value(value)
266                .writable(attribute.writable())
267                .enumerable(attribute.enumerable())
268                .configurable(attribute.configurable()),
269            self,
270        )?;
271        Ok(())
272    }
273
274    /// Register a global native callable.
275    ///
276    /// The function will be both `constructable` (call with `new <name>()`) and `callable` (call
277    /// with `<name>()`).
278    ///
279    /// The function will be bound to the global object with `writable`, `non-enumerable`
280    /// and `configurable` attributes. The same as when you create a function in JavaScript.
281    ///
282    /// # Note
283    ///
284    /// If you wish to only create the function object without binding it to the global object, you
285    /// can use the [`FunctionObjectBuilder`] API.
286    pub fn register_global_callable(
287        &mut self,
288        name: JsString,
289        length: usize,
290        body: NativeFunction,
291    ) -> JsResult<()> {
292        let function = FunctionObjectBuilder::new(self.realm(), body)
293            .name(name.clone())
294            .length(length)
295            .constructor(true)
296            .build();
297
298        self.global_object().define_property_or_throw(
299            name,
300            PropertyDescriptor::builder()
301                .value(function)
302                .writable(true)
303                .enumerable(false)
304                .configurable(true),
305            self,
306        )?;
307        Ok(())
308    }
309
310    /// Register a global native function that is not a constructor.
311    ///
312    /// The function will be bound to the global object with `writable`, `non-enumerable`
313    /// and `configurable` attributes. The same as when you create a function in JavaScript.
314    ///
315    /// # Note
316    ///
317    /// The difference to [`Context::register_global_callable`] is, that the function will not be
318    /// `constructable`. Usage of the function as a constructor will produce a `TypeError`.
319    pub fn register_global_builtin_callable(
320        &mut self,
321        name: JsString,
322        length: usize,
323        body: NativeFunction,
324    ) -> JsResult<()> {
325        let function = FunctionObjectBuilder::new(self.realm(), body)
326            .name(name.clone())
327            .length(length)
328            .constructor(false)
329            .build();
330
331        self.global_object().define_property_or_throw(
332            name,
333            PropertyDescriptor::builder()
334                .value(function)
335                .writable(true)
336                .enumerable(false)
337                .configurable(true),
338            self,
339        )?;
340        Ok(())
341    }
342
343    /// Registers a global class `C` in the currently active realm.
344    ///
345    /// Errors if the class has already been registered.
346    ///
347    /// # Example
348    /// ```ignore
349    /// #[derive(Debug, Trace, Finalize)]
350    /// struct MyClass;
351    ///
352    /// impl Class for MyClass {
353    ///    // ...
354    /// }
355    ///
356    /// context.register_global_class::<MyClass>()?;
357    /// ```
358    pub fn register_global_class<C: Class>(&mut self) -> JsResult<()> {
359        if self.realm().has_class::<C>() {
360            return Err(JsNativeError::typ()
361                .with_message("cannot register a class twice")
362                .into());
363        }
364
365        let mut class_builder = ClassBuilder::new::<C>(self);
366        C::init(&mut class_builder)?;
367
368        let class = class_builder.build();
369        let property = PropertyDescriptor::builder()
370            .value(class.constructor())
371            .writable(C::ATTRIBUTES.writable())
372            .enumerable(C::ATTRIBUTES.enumerable())
373            .configurable(C::ATTRIBUTES.configurable());
374
375        self.global_object()
376            .define_property_or_throw(js_string!(C::NAME), property, self)?;
377        self.realm().register_class::<C>(class);
378
379        Ok(())
380    }
381
382    /// Removes the global class `C` from the currently active realm, returning the constructor
383    /// and prototype of the class if `C` was registered.
384    ///
385    /// # Note
386    ///
387    /// This makes the constructor return an error on further calls, but note that this won't protect
388    /// static properties from being accessed within variables that stored the constructor before being
389    /// unregistered.  If you need that functionality, you can use a static accessor that first checks
390    /// if the class is registered ([`Context::has_global_class`]) before returning the static value.
391    ///
392    /// # Example
393    /// ```ignore
394    /// #[derive(Debug, Trace, Finalize)]
395    /// struct MyClass;
396    ///
397    /// impl Class for MyClass {
398    ///    // ...
399    /// }
400    ///
401    /// context.register_global_class::<MyClass>()?;
402    /// // ... code
403    /// context.unregister_global_class::<MyClass>()?;
404    /// ```
405    pub fn unregister_global_class<C: Class>(&mut self) -> JsResult<Option<StandardConstructor>> {
406        self.global_object()
407            .delete_property_or_throw(js_string!(C::NAME), self)?;
408        Ok(self.realm().unregister_class::<C>())
409    }
410
411    /// Checks if the currently active realm has the global class `C` registered.
412    #[must_use]
413    pub fn has_global_class<C: Class>(&self) -> bool {
414        self.realm().has_class::<C>()
415    }
416
417    /// Gets the constructor and prototype of the global class `C` if the currently active realm has
418    /// that class registered.
419    #[must_use]
420    pub fn get_global_class<C: Class>(&self) -> Option<StandardConstructor> {
421        self.realm().get_class::<C>()
422    }
423
424    /// Gets the string interner.
425    #[inline]
426    #[must_use]
427    pub const fn interner(&self) -> &Interner {
428        &self.interner
429    }
430
431    /// Gets a mutable reference to the string interner.
432    #[inline]
433    pub fn interner_mut(&mut self) -> &mut Interner {
434        &mut self.interner
435    }
436
437    /// Returns the global object.
438    #[inline]
439    #[must_use]
440    pub fn global_object(&self) -> JsObject {
441        self.vm.frame().realm.global_object().clone()
442    }
443
444    /// Returns the currently active intrinsic constructors and objects.
445    #[inline]
446    #[must_use]
447    pub fn intrinsics(&self) -> &Intrinsics {
448        self.vm.frame().realm.intrinsics()
449    }
450
451    /// Returns the amount of remaining instructions to be executed
452    #[cfg(feature = "fuzz")]
453    #[inline]
454    #[must_use]
455    pub fn instructions_remaining(&self) -> usize {
456        self.instructions_remaining
457    }
458
459    /// Returns the currently active realm.
460    #[inline]
461    #[must_use]
462    pub fn realm(&self) -> &Realm {
463        &self.vm.frame().realm
464    }
465
466    /// Set the value of trace on the context
467    #[cfg(feature = "trace")]
468    #[inline]
469    pub fn set_trace(&mut self, trace: bool) {
470        self.vm.trace = trace;
471    }
472
473    /// Get optimizer options.
474    #[inline]
475    #[must_use]
476    pub const fn optimizer_options(&self) -> OptimizerOptions {
477        self.optimizer_options
478    }
479    /// Enable or disable optimizations
480    #[inline]
481    pub fn set_optimizer_options(&mut self, optimizer_options: OptimizerOptions) {
482        self.optimizer_options = optimizer_options;
483    }
484
485    /// Changes the strictness mode of the context.
486    #[inline]
487    pub fn strict(&mut self, strict: bool) {
488        self.strict = strict;
489    }
490
491    /// Enqueues a [`Job`] on the [`JobExecutor`].
492    #[inline]
493    pub fn enqueue_job(&mut self, job: Job) {
494        self.job_executor().enqueue_job(job, self);
495    }
496
497    /// Runs all the jobs with the provided job executor.
498    #[inline]
499    pub fn run_jobs(&mut self) -> JsResult<()> {
500        self.job_executor().run_jobs(self)
501    }
502
503    /// Abstract operation [`ClearKeptObjects`][clear].
504    ///
505    /// Clears all objects maintained alive by calls to the [`AddToKeptObjects`][add] abstract
506    /// operation, used within the [`WeakRef`][weak] constructor.
507    ///
508    /// [clear]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-clear-kept-objects
509    /// [add]: https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-addtokeptobjects
510    /// [weak]: https://tc39.es/ecma262/multipage/managing-memory.html#sec-weak-ref-objects
511    #[inline]
512    pub fn clear_kept_objects(&mut self) {
513        self.kept_alive.clear();
514    }
515
516    /// Retrieves the current stack trace of the context.
517    ///
518    /// The stack trace is returned ordered with the most recent frames first.
519    #[inline]
520    pub fn stack_trace(&self) -> impl Iterator<Item = &CallFrame> {
521        // The first frame is always a dummy frame (see `Vm` implementation for more details),
522        // so skip the dummy frame and return the reversed list so that the most recent frames are first.
523        self.vm.frames.iter().skip(1).rev()
524    }
525
526    /// Replaces the currently active realm with `realm`, and returns the old realm.
527    #[inline]
528    pub fn enter_realm(&mut self, realm: Realm) -> Realm {
529        std::mem::replace(&mut self.vm.frame_mut().realm, realm)
530    }
531
532    /// Create a new Realm with the default global bindings.
533    pub fn create_realm(&mut self) -> JsResult<Realm> {
534        let realm = Realm::create(self.host_hooks.as_ref(), &self.root_shape)?;
535
536        let old_realm = self.enter_realm(realm);
537
538        builtins::set_default_global_bindings(self)?;
539
540        Ok(self.enter_realm(old_realm))
541    }
542
543    /// Get the [`RootShape`].
544    #[inline]
545    #[must_use]
546    pub const fn root_shape(&self) -> &RootShape {
547        &self.root_shape
548    }
549
550    /// Gets the host hooks.
551    #[inline]
552    #[must_use]
553    pub fn host_hooks(&self) -> Rc<dyn HostHooks> {
554        self.host_hooks.clone()
555    }
556
557    /// Gets the internal clock.
558    #[inline]
559    #[must_use]
560    pub fn clock(&self) -> &dyn Clock {
561        self.clock.as_ref()
562    }
563
564    /// Gets the current job executor, or `None` if the current job executor
565    /// is not a `T`.
566    #[inline]
567    #[must_use]
568    pub fn downcast_job_executor<T: 'static>(&self) -> Option<Rc<T>> {
569        Rc::downcast(self.job_executor.clone()).ok()
570    }
571
572    /// Gets the current module loader, or `None` if the current module loader
573    /// is not a `T`.
574    #[must_use]
575    pub fn downcast_module_loader<T: 'static>(&self) -> Option<Rc<T>> {
576        Rc::downcast(self.module_loader.clone()).ok()
577    }
578
579    /// Get the [`RuntimeLimits`].
580    #[inline]
581    #[must_use]
582    pub const fn runtime_limits(&self) -> RuntimeLimits {
583        self.vm.runtime_limits
584    }
585
586    /// Set the [`RuntimeLimits`].
587    #[inline]
588    pub fn set_runtime_limits(&mut self, runtime_limits: RuntimeLimits) {
589        self.vm.runtime_limits = runtime_limits;
590    }
591
592    /// Get a mutable reference to the [`RuntimeLimits`].
593    #[inline]
594    pub fn runtime_limits_mut(&mut self) -> &mut RuntimeLimits {
595        &mut self.vm.runtime_limits
596    }
597
598    /// Returns `true` if this context can be suspended by an `Atomics.wait` call.
599    #[inline]
600    #[must_use]
601    pub fn can_block(&self) -> bool {
602        self.can_block
603    }
604
605    /// Gets a mutable reference to the inner [`HostDefined`] field.
606    #[inline]
607    pub fn host_defined_mut(&mut self) -> &mut HostDefined<dyn Any> {
608        &mut self.data
609    }
610
611    /// Insert a type into the context-specific [`HostDefined`] field.
612    #[inline]
613    pub fn insert_data<T: Any>(&mut self, value: T) -> Option<Box<T>> {
614        self.data.insert(value)
615    }
616
617    /// Check if the context-specific [`HostDefined`] has type T.
618    #[inline]
619    #[must_use]
620    pub fn has_data<T: Any>(&self) -> bool {
621        self.data.has::<T>()
622    }
623
624    /// Remove type T from the context-specific [`HostDefined`], if it exists.
625    #[inline]
626    pub fn remove_data<T: Any>(&mut self) -> Option<Box<T>> {
627        self.data.remove::<T>()
628    }
629
630    /// Get type T from the context-specific [`HostDefined`], if it exists.
631    #[inline]
632    #[must_use]
633    pub fn get_data<T: Any>(&self) -> Option<&T> {
634        self.data.get::<T>()
635    }
636}
637
638// ==== Private API ====
639
640impl Context {
641    /// Gets the current job executor.
642    pub(crate) fn job_executor(&self) -> Rc<dyn JobExecutor> {
643        self.job_executor.clone()
644    }
645
646    /// Gets the current module loader.
647    pub(crate) fn module_loader(&self) -> Rc<dyn DynModuleLoader> {
648        self.module_loader.clone()
649    }
650
651    /// Swaps the currently active realm with `realm`.
652    pub(crate) fn swap_realm(&mut self, realm: &mut Realm) {
653        std::mem::swap(&mut self.vm.frame_mut().realm, realm);
654    }
655
656    /// Increment and get the parser identifier.
657    pub(crate) fn next_parser_identifier(&mut self) -> u32 {
658        self.parser_identifier += 1;
659        self.parser_identifier
660    }
661
662    /// `CanDeclareGlobalFunction ( N )`
663    ///
664    /// More information:
665    ///  - [ECMAScript reference][spec]
666    ///
667    /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction
668    pub(crate) fn can_declare_global_function(&mut self, name: &JsString) -> JsResult<bool> {
669        // 1. Let ObjRec be envRec.[[ObjectRecord]].
670        // 2. Let globalObject be ObjRec.[[BindingObject]].
671        let global_object = self.realm().global_object().clone();
672
673        // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
674        let name = name.clone().into();
675        let existing_prop = global_object.__get_own_property__(&name, &mut self.into())?;
676
677        // 4. If existingProp is undefined, return ? IsExtensible(globalObject).
678        let Some(existing_prop) = existing_prop else {
679            return global_object.is_extensible(self);
680        };
681
682        // 5. If existingProp.[[Configurable]] is true, return true.
683        if existing_prop.configurable() == Some(true) {
684            return Ok(true);
685        }
686
687        // 6. If IsDataDescriptor(existingProp) is true and existingProp has attribute values { [[Writable]]: true, [[Enumerable]]: true }, return true.
688        if existing_prop.is_data_descriptor()
689            && existing_prop.writable() == Some(true)
690            && existing_prop.enumerable() == Some(true)
691        {
692            return Ok(true);
693        }
694
695        // 7. Return false.
696        Ok(false)
697    }
698
699    /// `CanDeclareGlobalVar ( N )`
700    ///
701    /// More information:
702    ///  - [ECMAScript reference][spec]
703    ///
704    /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar
705    pub(crate) fn can_declare_global_var(&mut self, name: &JsString) -> JsResult<bool> {
706        // 1. Let ObjRec be envRec.[[ObjectRecord]].
707        // 2. Let globalObject be ObjRec.[[BindingObject]].
708        let global_object = self.realm().global_object().clone();
709
710        // 3. Let hasProperty be ? HasOwnProperty(globalObject, N).
711        let has_property = global_object.has_own_property(name.clone(), self)?;
712
713        // 4. If hasProperty is true, return true.
714        if has_property {
715            return Ok(true);
716        }
717
718        // 5. Return ? IsExtensible(globalObject).
719        global_object.is_extensible(self)
720    }
721
722    /// `CreateGlobalVarBinding ( N, D )`
723    ///
724    /// More information:
725    ///  - [ECMAScript reference][spec]
726    ///
727    /// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding
728    pub(crate) fn create_global_var_binding(
729        &mut self,
730        name: JsString,
731        configurable: bool,
732    ) -> JsResult<()> {
733        // 1. Let ObjRec be envRec.[[ObjectRecord]].
734        // 2. Let globalObject be ObjRec.[[BindingObject]].
735        let global_object = self.realm().global_object().clone();
736
737        // 3. Let hasProperty be ? HasOwnProperty(globalObject, N).
738        let has_property = global_object.has_own_property(name.clone(), self)?;
739
740        // 4. Let extensible be ? IsExtensible(globalObject).
741        let extensible = global_object.is_extensible(self)?;
742
743        // 5. If hasProperty is false and extensible is true, then
744        if !has_property && extensible {
745            // a. Perform ? ObjRec.CreateMutableBinding(N, D).
746            // b. Perform ? ObjRec.InitializeBinding(N, undefined).
747            global_object.define_property_or_throw(
748                name,
749                PropertyDescriptor::builder()
750                    .value(JsValue::undefined())
751                    .writable(true)
752                    .enumerable(true)
753                    .configurable(configurable)
754                    .build(),
755                self,
756            )?;
757        }
758
759        // 6. If envRec.[[VarNames]] does not contain N, then
760        //     a. Append N to envRec.[[VarNames]].
761        // 7. Return unused.
762        Ok(())
763    }
764
765    /// `CreateGlobalFunctionBinding ( N, V, D )`
766    ///
767    /// More information:
768    ///  - [ECMAScript reference][spec]
769    ///
770    /// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding
771    pub(crate) fn create_global_function_binding(
772        &mut self,
773        name: JsString,
774        function: JsObject,
775        configurable: bool,
776    ) -> JsResult<()> {
777        // 1. Let ObjRec be envRec.[[ObjectRecord]].
778        // 2. Let globalObject be ObjRec.[[BindingObject]].
779        let global_object = self.realm().global_object().clone();
780
781        // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
782        let existing_prop =
783            global_object.__get_own_property__(&name.clone().into(), &mut self.into())?;
784
785        // 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then
786        let desc = if existing_prop.is_none()
787            || existing_prop.and_then(|p| p.configurable()) == Some(true)
788        {
789            // a. Let desc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }.
790            PropertyDescriptor::builder()
791                .value(function.clone())
792                .writable(true)
793                .enumerable(true)
794                .configurable(configurable)
795                .build()
796        }
797        // 5. Else,
798        else {
799            // a. Let desc be the PropertyDescriptor { [[Value]]: V }.
800            PropertyDescriptor::builder()
801                .value(function.clone())
802                .build()
803        };
804
805        // 6. Perform ? DefinePropertyOrThrow(globalObject, N, desc).
806        global_object.define_property_or_throw(name.clone(), desc, self)?;
807
808        // 7. Perform ? Set(globalObject, N, V, false).
809        global_object.set(name, function, false, self)?;
810
811        // 8. If envRec.[[VarNames]] does not contain N, then
812        //     a. Append N to envRec.[[VarNames]].
813        // 9. Return unused.
814        Ok(())
815    }
816
817    /// `HasRestrictedGlobalProperty ( N )`
818    ///
819    /// More information:
820    ///  - [ECMAScript reference][spec]
821    ///
822    /// [spec]: https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty
823    pub(crate) fn has_restricted_global_property(&mut self, name: &JsString) -> JsResult<bool> {
824        // 1. Let ObjRec be envRec.[[ObjectRecord]].
825        // 2. Let globalObject be ObjRec.[[BindingObject]].
826        let global_object = self.realm().global_object().clone();
827
828        // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
829        let name = name.clone().into();
830        let existing_prop = global_object.__get_own_property__(&name, &mut self.into())?;
831
832        // 4. If existingProp is undefined, return false.
833        let Some(existing_prop) = existing_prop else {
834            return Ok(false);
835        };
836
837        // 5. If existingProp.[[Configurable]] is true, return false.
838        if existing_prop.configurable() == Some(true) {
839            return Ok(false);
840        }
841
842        // 6. Return true.
843        Ok(true)
844    }
845
846    /// Returns `true` if this context is in strict mode.
847    pub(crate) const fn is_strict(&self) -> bool {
848        self.strict
849    }
850
851    /// `9.4.1 GetActiveScriptOrModule ( )`
852    ///
853    /// More information:
854    ///  - [ECMAScript reference][spec]
855    ///
856    /// [spec]: https://tc39.es/ecma262/#sec-getactivescriptormodule
857    #[must_use]
858    pub fn get_active_script_or_module(&self) -> Option<ActiveRunnable> {
859        // 1. If the execution context stack is empty, return null.
860        // 2. Let ec be the topmost execution context on the execution context stack whose ScriptOrModule component is not null.
861        // 3. If no such execution context exists, return null. Otherwise, return ec's ScriptOrModule.
862        if let Some(active_runnable) = &self.vm.frame().active_runnable {
863            return Some(active_runnable.clone());
864        }
865
866        self.vm
867            .frames
868            .iter()
869            .rev()
870            .find_map(|frame| frame.active_runnable.clone())
871    }
872
873    /// Get `active function object`
874    ///
875    /// More information:
876    ///  - [ECMAScript reference][spec]
877    ///
878    /// [spec]: https://tc39.es/ecma262/#active-function-object
879    pub(crate) fn active_function_object(&self) -> Option<JsObject> {
880        if self.vm.native_active_function.is_some() {
881            return self.vm.native_active_function.clone();
882        }
883
884        self.vm.stack.get_function(self.vm.frame())
885    }
886
887    /// Creates all globals required to evaluate `codeblock`.
888    ///
889    /// This is the common path of the instantiations:
890    /// - `EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict )`
891    /// - `GlobalDeclarationInstantiation ( script, env )`
892    fn create_globals(
893        &mut self,
894        codeblock: &CodeBlock,
895        configurable_globals: bool,
896    ) -> JsResult<()> {
897        // 8. For each element d of varDeclarations, in reverse List order, do
898        //    a. If d is not either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
899        //       i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
900        //       ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
901        //       iii. Let fn be the sole element of the BoundNames of d.
902        for fun in codeblock.global_fns.iter().rev() {
903            // ...
904            // 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn).
905            // 2. If fnDefinable is false, throw a TypeError exception.
906            let name = codeblock.constant_string(fun.name_index as usize);
907            if !self.can_declare_global_function(&name)? {
908                return Err(js_error!(TypeError: "cannot declare global function"));
909            }
910        }
911
912        // 10. For each element d of varDeclarations, do
913        for global_var in &codeblock.global_vars {
914            // ...
915            // a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn).
916            // b. If vnDefinable is false, throw a TypeError exception.
917            let name = codeblock.constant_string(*global_var as usize);
918            if !self.can_declare_global_var(&name)? {
919                return Err(js_error!(TypeError: "cannot declare global variable"));
920            }
921        }
922
923        // 16. For each Parse Node f of functionsToInitialize, do
924        for fun in &codeblock.global_fns {
925            // ...
926            // c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false).
927            let function = create_function_object_fast(
928                codeblock.constant_function(fun.function_index as usize),
929                self,
930            );
931            let name = codeblock.constant_string(fun.name_index as usize);
932            self.create_global_function_binding(name, function, configurable_globals)?;
933        }
934
935        // 17. For each String vn of declaredVarNames, do
936        for global_declared_var in &codeblock.global_vars {
937            // a. Perform ? env.CreateGlobalVarBinding(vn, false).
938            let name = codeblock.constant_string(*global_declared_var as usize);
939            self.create_global_var_binding(name, configurable_globals)?;
940        }
941
942        Ok(())
943    }
944
945    /// `GlobalDeclarationInstantiation ( script, env )`
946    ///
947    /// More information:
948    ///  - [ECMAScript reference][spec]
949    ///
950    /// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
951    pub(crate) fn global_declaration_instantiation(
952        &mut self,
953        codeblock: &CodeBlock,
954    ) -> JsResult<()> {
955        // 3. For each element name of lexNames, do
956        for global_lex in &codeblock.global_lexs {
957            // c. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name).
958            // d. If hasRestrictedGlobal is true, throw a SyntaxError exception.
959            let name = codeblock.constant_string(*global_lex as usize);
960            if self.has_restricted_global_property(&name)? {
961                return Err(
962                    js_error!(SyntaxError: "cannot redefine non-configurable global property"),
963                );
964            }
965        }
966
967        self.create_globals(codeblock, false)
968    }
969
970    /// `EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict )`
971    ///
972    /// More information:
973    ///  - [ECMAScript reference][spec]
974    ///
975    /// [spec]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation
976    pub(crate) fn eval_declaration_instantiation(&mut self, codeblock: &CodeBlock) -> JsResult<()> {
977        self.create_globals(codeblock, true)
978    }
979}
980
981impl Context {
982    /// Creates a `ContextCleanupGuard` that executes some cleanup after being dropped.
983    pub(crate) fn guard<F>(&mut self, cleanup: F) -> ContextCleanupGuard<'_, F>
984    where
985        F: FnOnce(&mut Context) + 'static,
986    {
987        ContextCleanupGuard::new(self, cleanup)
988    }
989
990    /// Get the Intl data provider.
991    #[cfg(feature = "intl")]
992    pub(crate) const fn intl_provider(&self) -> &icu::IntlProvider {
993        &self.intl_provider
994    }
995
996    /// Get the Time Zone Provider
997    #[cfg(feature = "temporal")]
998    pub(crate) fn timezone_provider(&self) -> &dyn TimeZoneProvider {
999        self.timezone_provider.as_ref()
1000    }
1001}
1002
1003/// Builder for the [`Context`] type.
1004///
1005/// This builder allows custom initialization of the [`Interner`] within
1006/// the context.
1007#[derive(Default)]
1008pub struct ContextBuilder {
1009    interner: Option<Interner>,
1010    host_hooks: Option<Rc<dyn HostHooks>>,
1011    clock: Option<Rc<dyn Clock>>,
1012    job_executor: Option<Rc<dyn JobExecutor>>,
1013    module_loader: Option<Rc<dyn DynModuleLoader>>,
1014    can_block: bool,
1015    #[cfg(feature = "intl")]
1016    icu: Option<icu::IntlProvider>,
1017    #[cfg(feature = "temporal")]
1018    timezone_provider: Option<Box<dyn TimeZoneProvider>>,
1019    #[cfg(feature = "fuzz")]
1020    instructions_remaining: usize,
1021}
1022
1023impl std::fmt::Debug for ContextBuilder {
1024    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1025        #[derive(Clone, Copy, Debug)]
1026        struct JobExecutor;
1027        #[derive(Clone, Copy, Debug)]
1028        struct HostHooks;
1029        #[derive(Clone, Copy, Debug)]
1030        struct Clock;
1031        #[derive(Clone, Copy, Debug)]
1032        struct ModuleLoader;
1033
1034        let mut out = f.debug_struct("ContextBuilder");
1035
1036        out.field("interner", &self.interner)
1037            .field("host_hooks", &self.host_hooks.as_ref().map(|_| HostHooks))
1038            .field("clock", &self.clock.as_ref().map(|_| Clock))
1039            .field(
1040                "job_executor",
1041                &self.job_executor.as_ref().map(|_| JobExecutor),
1042            )
1043            .field(
1044                "module_loader",
1045                &self.module_loader.as_ref().map(|_| ModuleLoader),
1046            )
1047            .field("can_block", &self.can_block);
1048
1049        #[cfg(feature = "intl")]
1050        out.field("icu", &self.icu);
1051
1052        #[cfg(feature = "temporal")]
1053        out.field(
1054            "timezone_provider",
1055            &self.timezone_provider.as_ref().map(|_| "TimeZoneProvider"),
1056        );
1057
1058        #[cfg(feature = "fuzz")]
1059        out.field("instructions_remaining", &self.instructions_remaining);
1060
1061        out.finish()
1062    }
1063}
1064
1065impl ContextBuilder {
1066    /// Creates a new [`ContextBuilder`] with a default empty [`Interner`]
1067    /// and a default `BoaProvider` if the `intl` feature is enabled.
1068    #[must_use]
1069    pub fn new() -> Self {
1070        Self::default()
1071    }
1072
1073    /// Initializes the context [`Interner`] to the provided interner.
1074    ///
1075    /// This is useful when you want to initialize an [`Interner`] with
1076    /// a collection of words before parsing.
1077    #[must_use]
1078    #[allow(clippy::missing_const_for_fn)]
1079    pub fn interner(mut self, interner: Interner) -> Self {
1080        self.interner = Some(interner);
1081        self
1082    }
1083
1084    /// Provides a [`BufferProvider`] data provider to the [`Context`].
1085    ///
1086    /// This function is only available if the `intl` feature is enabled.
1087    ///
1088    /// # Additional considerations
1089    ///
1090    /// If the data was generated using `icu_datagen`, make sure that the deduplication strategy is
1091    /// not set to [`Maximal`]. Otherwise, `icu_datagen` will delete base locales such as "en" from
1092    /// the list of supported locales if the required data for "en" is the same as "und".
1093    /// We recommend [`RetainBaseLanguages`] as a nice default, which will only deduplicate locales
1094    /// if the deduplication target is not "und".
1095    ///
1096    /// # Errors
1097    ///
1098    /// This returns `Err` if the provided provider doesn't have the required locale information
1099    /// to construct common tools used through `Intl`. Note that this doesn't
1100    /// mean that the provider will successfully construct all `Intl` services; that check is made
1101    /// until the creation of an instance of a service.
1102    ///
1103    /// [`Maximal`]: https://docs.rs/icu_datagen/latest/icu_datagen/enum.DeduplicationStrategy.html#variant.Maximal
1104    /// [`RetainBaseLanguages`]: https://docs.rs/icu_datagen/latest/icu_datagen/enum.DeduplicationStrategy.html#variant.RetainBaseLanguages
1105    /// [`BufferProvider`]: icu_provider::buf::BufferProvider
1106    #[cfg(feature = "intl")]
1107    pub fn icu_buffer_provider<
1108        T: icu_provider::prelude::DynamicDryDataProvider<icu_provider::prelude::BufferMarker>
1109            + 'static,
1110    >(
1111        mut self,
1112        provider: T,
1113    ) -> Result<Self, IcuError> {
1114        self.icu = Some(icu::IntlProvider::try_new_buffer(provider));
1115        Ok(self)
1116    }
1117
1118    /// Set the [`timezone_provider::provider::TimeZoneProvider`] that should be used to source
1119    /// time zone data.
1120    ///
1121    /// ## Default
1122    ///
1123    /// If no time zone provider is provided, a compiled time zone provider will be used
1124    /// which includes the time zone data in the binary. This may increase binary sizes
1125    /// by up to 200 Kb.
1126    #[cfg(feature = "temporal")]
1127    #[must_use]
1128    pub fn timezone_provider<T: TimeZoneProvider + 'static>(mut self, provider: T) -> Self {
1129        self.timezone_provider = Some(Box::new(provider));
1130        self
1131    }
1132
1133    /// Initializes the [`HostHooks`] for the context.
1134    ///
1135    /// [`Host Hooks`]: https://tc39.es/ecma262/#sec-host-hooks-summary
1136    #[must_use]
1137    pub fn host_hooks<H: HostHooks + 'static>(mut self, host_hooks: Rc<H>) -> Self {
1138        self.host_hooks = Some(host_hooks);
1139        self
1140    }
1141
1142    /// Initializes the [`Clock`] for the context.
1143    #[must_use]
1144    pub fn clock<C: Clock + 'static>(mut self, clock: Rc<C>) -> Self {
1145        self.clock = Some(clock);
1146        self
1147    }
1148
1149    /// Initializes the [`JobExecutor`] for the context.
1150    #[must_use]
1151    pub fn job_executor<Q: JobExecutor + 'static>(mut self, job_executor: Rc<Q>) -> Self {
1152        self.job_executor = Some(job_executor);
1153        self
1154    }
1155
1156    /// Initializes the [`ModuleLoader`] for the context.
1157    #[must_use]
1158    pub fn module_loader<M: ModuleLoader + 'static>(mut self, module_loader: Rc<M>) -> Self {
1159        self.module_loader = Some(module_loader);
1160        self
1161    }
1162
1163    /// [`AgentCanSuspend ( )`][spec] aka `[[CanBlock]]`
1164    ///
1165    /// Defines if this context can be suspended by calls to the [`Atomics.wait`][wait] function.
1166    ///
1167    /// # Note
1168    ///
1169    /// By the specification, multiple agents cannot share the same thread if any of them has its
1170    /// `[[CanBlock]]` field set to true. The builder will verify at build time that all contexts on
1171    /// the current thread fulfill this requisite.
1172    ///
1173    /// [spec]: https://tc39.es/ecma262/#sec-agentcansuspend
1174    /// [wait]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
1175    #[must_use]
1176    pub const fn can_block(mut self, can_block: bool) -> Self {
1177        self.can_block = can_block;
1178        self
1179    }
1180
1181    /// Specifies the number of instructions remaining to the [`Context`].
1182    ///
1183    /// This function is only available if the `fuzz` feature is enabled.
1184    #[cfg(feature = "fuzz")]
1185    #[must_use]
1186    pub const fn instructions_remaining(mut self, instructions_remaining: usize) -> Self {
1187        self.instructions_remaining = instructions_remaining;
1188        self
1189    }
1190
1191    /// Builds a new [`Context`] with the provided parameters, and defaults
1192    /// all missing parameters to their default values.
1193    // TODO: try to use a custom error here, since most of the `JsError` APIs
1194    // require having a `Context` in the first place.
1195    pub fn build(self) -> JsResult<Context> {
1196        if self.can_block {
1197            if CANNOT_BLOCK_COUNTER.get() > 0 {
1198                return Err(JsNativeError::typ()
1199                    .with_message(
1200                        "a context that can block must be the only active context in its current thread",
1201                    )
1202                    .into());
1203            }
1204        } else {
1205            CANNOT_BLOCK_COUNTER.set(CANNOT_BLOCK_COUNTER.get() + 1);
1206        }
1207
1208        let root_shape = RootShape::default();
1209
1210        let host_hooks = self.host_hooks.unwrap_or(Rc::new(DefaultHooks));
1211        let clock = self.clock.unwrap_or_else(|| Rc::new(StdClock::new()));
1212        let realm = Realm::create(host_hooks.as_ref(), &root_shape)?;
1213        let vm = Vm::new(realm);
1214
1215        let module_loader: Rc<dyn DynModuleLoader> = if let Some(loader) = self.module_loader {
1216            loader
1217        } else if let Ok(loader) = SimpleModuleLoader::new(Path::new(".")) {
1218            Rc::new(loader)
1219        } else {
1220            Rc::new(IdleModuleLoader)
1221        };
1222
1223        let job_executor = self
1224            .job_executor
1225            .unwrap_or_else(|| Rc::new(SimpleJobExecutor::new()));
1226
1227        let mut context = Context {
1228            interner: self.interner.unwrap_or_default(),
1229            vm,
1230            strict: false,
1231            #[cfg(feature = "temporal")]
1232            timezone_provider: if let Some(provider) = self.timezone_provider {
1233                provider
1234            } else {
1235                Box::new(ZeroCompiledTzdbProvider::default())
1236            },
1237            #[cfg(feature = "intl")]
1238            intl_provider: if let Some(icu) = self.icu {
1239                icu
1240            } else {
1241                cfg_if::cfg_if! {
1242                    if #[cfg(feature = "intl_bundled")] {
1243                        icu::IntlProvider::try_new_buffer(boa_icu_provider::buffer())
1244                    } else {
1245                        return Err(JsNativeError::typ()
1246                            .with_message("missing Intl provider for context")
1247                            .into()
1248                        );
1249                    }
1250                }
1251            },
1252            #[cfg(feature = "fuzz")]
1253            instructions_remaining: self.instructions_remaining,
1254            kept_alive: Vec::new(),
1255            host_hooks,
1256            clock,
1257            job_executor,
1258            module_loader,
1259            optimizer_options: OptimizerOptions::OPTIMIZE_ALL,
1260            root_shape,
1261            parser_identifier: 0,
1262            can_block: self.can_block,
1263            data: HostDefined::default(),
1264        };
1265
1266        builtins::set_default_global_bindings(&mut context)?;
1267
1268        Ok(context)
1269    }
1270}
1271
1272/// A cleanup guard for a [`Context`] that is executed when dropped.
1273#[derive(Debug)]
1274pub(crate) struct ContextCleanupGuard<'a, F>
1275where
1276    F: FnOnce(&mut Context) + 'static,
1277{
1278    context: &'a mut Context,
1279    cleanup: Option<F>,
1280}
1281
1282impl<'a, F> ContextCleanupGuard<'a, F>
1283where
1284    F: FnOnce(&mut Context) + 'static,
1285{
1286    /// Creates a new `ContextCleanupGuard` from the current context and its cleanup operation.
1287    pub(crate) fn new(context: &'a mut Context, cleanup: F) -> Self {
1288        Self {
1289            context,
1290            cleanup: Some(cleanup),
1291        }
1292    }
1293}
1294
1295impl<F> std::ops::Deref for ContextCleanupGuard<'_, F>
1296where
1297    F: FnOnce(&mut Context) + 'static,
1298{
1299    type Target = Context;
1300
1301    fn deref(&self) -> &Self::Target {
1302        self.context
1303    }
1304}
1305
1306impl<F> std::ops::DerefMut for ContextCleanupGuard<'_, F>
1307where
1308    F: FnOnce(&mut Context) + 'static,
1309{
1310    fn deref_mut(&mut self) -> &mut Self::Target {
1311        self.context
1312    }
1313}
1314
1315impl<F> Drop for ContextCleanupGuard<'_, F>
1316where
1317    F: FnOnce(&mut Context) + 'static,
1318{
1319    fn drop(&mut self) {
1320        if let Some(cleanup) = self.cleanup.take() {
1321            cleanup(self.context);
1322        }
1323    }
1324}