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    /// The receiver of the native constructor running as a plain call (its first argument,
874    /// new.target, is then `undefined`). `None` outside such a call.
875    ///
876    /// For an embedder whose constructor accepts the ES5 subclass pattern
877    /// `Base.call(this)`, which a native constructor cannot otherwise see.
878    #[must_use]
879    pub fn native_constructor_receiver(&self) -> Option<&JsValue> {
880        self.vm.native_constructor_receiver.as_ref()
881    }
882
883    /// Get `active function object`
884    ///
885    /// More information:
886    ///  - [ECMAScript reference][spec]
887    ///
888    /// [spec]: https://tc39.es/ecma262/#active-function-object
889    pub(crate) fn active_function_object(&self) -> Option<JsObject> {
890        if self.vm.native_active_function.is_some() {
891            return self.vm.native_active_function.clone();
892        }
893
894        self.vm.stack.get_function(self.vm.frame())
895    }
896
897    /// Creates all globals required to evaluate `codeblock`.
898    ///
899    /// This is the common path of the instantiations:
900    /// - `EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict )`
901    /// - `GlobalDeclarationInstantiation ( script, env )`
902    fn create_globals(
903        &mut self,
904        codeblock: &CodeBlock,
905        configurable_globals: bool,
906    ) -> JsResult<()> {
907        // 8. For each element d of varDeclarations, in reverse List order, do
908        //    a. If d is not either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
909        //       i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
910        //       ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
911        //       iii. Let fn be the sole element of the BoundNames of d.
912        for fun in codeblock.global_fns.iter().rev() {
913            // ...
914            // 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn).
915            // 2. If fnDefinable is false, throw a TypeError exception.
916            let name = codeblock.constant_string(fun.name_index as usize);
917            if !self.can_declare_global_function(&name)? {
918                return Err(js_error!(TypeError: "cannot declare global function"));
919            }
920        }
921
922        // 10. For each element d of varDeclarations, do
923        for global_var in &codeblock.global_vars {
924            // ...
925            // a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn).
926            // b. If vnDefinable is false, throw a TypeError exception.
927            let name = codeblock.constant_string(*global_var as usize);
928            if !self.can_declare_global_var(&name)? {
929                return Err(js_error!(TypeError: "cannot declare global variable"));
930            }
931        }
932
933        // 16. For each Parse Node f of functionsToInitialize, do
934        for fun in &codeblock.global_fns {
935            // ...
936            // c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false).
937            let function = create_function_object_fast(
938                codeblock.constant_function(fun.function_index as usize),
939                self,
940            );
941            let name = codeblock.constant_string(fun.name_index as usize);
942            self.create_global_function_binding(name, function, configurable_globals)?;
943        }
944
945        // 17. For each String vn of declaredVarNames, do
946        for global_declared_var in &codeblock.global_vars {
947            // a. Perform ? env.CreateGlobalVarBinding(vn, false).
948            let name = codeblock.constant_string(*global_declared_var as usize);
949            self.create_global_var_binding(name, configurable_globals)?;
950        }
951
952        Ok(())
953    }
954
955    /// `GlobalDeclarationInstantiation ( script, env )`
956    ///
957    /// More information:
958    ///  - [ECMAScript reference][spec]
959    ///
960    /// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
961    pub(crate) fn global_declaration_instantiation(
962        &mut self,
963        codeblock: &CodeBlock,
964    ) -> JsResult<()> {
965        // 3. For each element name of lexNames, do
966        for global_lex in &codeblock.global_lexs {
967            // c. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name).
968            // d. If hasRestrictedGlobal is true, throw a SyntaxError exception.
969            let name = codeblock.constant_string(*global_lex as usize);
970            if self.has_restricted_global_property(&name)? {
971                return Err(
972                    js_error!(SyntaxError: "cannot redefine non-configurable global property"),
973                );
974            }
975        }
976
977        self.create_globals(codeblock, false)
978    }
979
980    /// `EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict )`
981    ///
982    /// More information:
983    ///  - [ECMAScript reference][spec]
984    ///
985    /// [spec]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation
986    pub(crate) fn eval_declaration_instantiation(&mut self, codeblock: &CodeBlock) -> JsResult<()> {
987        self.create_globals(codeblock, true)
988    }
989}
990
991impl Context {
992    /// Creates a `ContextCleanupGuard` that executes some cleanup after being dropped.
993    pub(crate) fn guard<F>(&mut self, cleanup: F) -> ContextCleanupGuard<'_, F>
994    where
995        F: FnOnce(&mut Context) + 'static,
996    {
997        ContextCleanupGuard::new(self, cleanup)
998    }
999
1000    /// Get the Intl data provider.
1001    #[cfg(feature = "intl")]
1002    pub(crate) const fn intl_provider(&self) -> &icu::IntlProvider {
1003        &self.intl_provider
1004    }
1005
1006    /// Get the Time Zone Provider
1007    #[cfg(feature = "temporal")]
1008    pub(crate) fn timezone_provider(&self) -> &dyn TimeZoneProvider {
1009        self.timezone_provider.as_ref()
1010    }
1011}
1012
1013/// Builder for the [`Context`] type.
1014///
1015/// This builder allows custom initialization of the [`Interner`] within
1016/// the context.
1017#[derive(Default)]
1018pub struct ContextBuilder {
1019    interner: Option<Interner>,
1020    host_hooks: Option<Rc<dyn HostHooks>>,
1021    clock: Option<Rc<dyn Clock>>,
1022    job_executor: Option<Rc<dyn JobExecutor>>,
1023    module_loader: Option<Rc<dyn DynModuleLoader>>,
1024    can_block: bool,
1025    #[cfg(feature = "intl")]
1026    icu: Option<icu::IntlProvider>,
1027    #[cfg(feature = "temporal")]
1028    timezone_provider: Option<Box<dyn TimeZoneProvider>>,
1029    #[cfg(feature = "fuzz")]
1030    instructions_remaining: usize,
1031}
1032
1033impl std::fmt::Debug for ContextBuilder {
1034    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1035        #[derive(Clone, Copy, Debug)]
1036        struct JobExecutor;
1037        #[derive(Clone, Copy, Debug)]
1038        struct HostHooks;
1039        #[derive(Clone, Copy, Debug)]
1040        struct Clock;
1041        #[derive(Clone, Copy, Debug)]
1042        struct ModuleLoader;
1043
1044        let mut out = f.debug_struct("ContextBuilder");
1045
1046        out.field("interner", &self.interner)
1047            .field("host_hooks", &self.host_hooks.as_ref().map(|_| HostHooks))
1048            .field("clock", &self.clock.as_ref().map(|_| Clock))
1049            .field(
1050                "job_executor",
1051                &self.job_executor.as_ref().map(|_| JobExecutor),
1052            )
1053            .field(
1054                "module_loader",
1055                &self.module_loader.as_ref().map(|_| ModuleLoader),
1056            )
1057            .field("can_block", &self.can_block);
1058
1059        #[cfg(feature = "intl")]
1060        out.field("icu", &self.icu);
1061
1062        #[cfg(feature = "temporal")]
1063        out.field(
1064            "timezone_provider",
1065            &self.timezone_provider.as_ref().map(|_| "TimeZoneProvider"),
1066        );
1067
1068        #[cfg(feature = "fuzz")]
1069        out.field("instructions_remaining", &self.instructions_remaining);
1070
1071        out.finish()
1072    }
1073}
1074
1075impl ContextBuilder {
1076    /// Creates a new [`ContextBuilder`] with a default empty [`Interner`]
1077    /// and a default `BoaProvider` if the `intl` feature is enabled.
1078    #[must_use]
1079    pub fn new() -> Self {
1080        Self::default()
1081    }
1082
1083    /// Initializes the context [`Interner`] to the provided interner.
1084    ///
1085    /// This is useful when you want to initialize an [`Interner`] with
1086    /// a collection of words before parsing.
1087    #[must_use]
1088    #[allow(clippy::missing_const_for_fn)]
1089    pub fn interner(mut self, interner: Interner) -> Self {
1090        self.interner = Some(interner);
1091        self
1092    }
1093
1094    /// Provides a [`BufferProvider`] data provider to the [`Context`].
1095    ///
1096    /// This function is only available if the `intl` feature is enabled.
1097    ///
1098    /// # Additional considerations
1099    ///
1100    /// If the data was generated using `icu_datagen`, make sure that the deduplication strategy is
1101    /// not set to [`Maximal`]. Otherwise, `icu_datagen` will delete base locales such as "en" from
1102    /// the list of supported locales if the required data for "en" is the same as "und".
1103    /// We recommend [`RetainBaseLanguages`] as a nice default, which will only deduplicate locales
1104    /// if the deduplication target is not "und".
1105    ///
1106    /// # Errors
1107    ///
1108    /// This returns `Err` if the provided provider doesn't have the required locale information
1109    /// to construct common tools used through `Intl`. Note that this doesn't
1110    /// mean that the provider will successfully construct all `Intl` services; that check is made
1111    /// until the creation of an instance of a service.
1112    ///
1113    /// [`Maximal`]: https://docs.rs/icu_datagen/latest/icu_datagen/enum.DeduplicationStrategy.html#variant.Maximal
1114    /// [`RetainBaseLanguages`]: https://docs.rs/icu_datagen/latest/icu_datagen/enum.DeduplicationStrategy.html#variant.RetainBaseLanguages
1115    /// [`BufferProvider`]: icu_provider::buf::BufferProvider
1116    #[cfg(feature = "intl")]
1117    pub fn icu_buffer_provider<
1118        T: icu_provider::prelude::DynamicDryDataProvider<icu_provider::prelude::BufferMarker>
1119            + 'static,
1120    >(
1121        mut self,
1122        provider: T,
1123    ) -> Result<Self, IcuError> {
1124        self.icu = Some(icu::IntlProvider::try_new_buffer(provider));
1125        Ok(self)
1126    }
1127
1128    /// Set the [`timezone_provider::provider::TimeZoneProvider`] that should be used to source
1129    /// time zone data.
1130    ///
1131    /// ## Default
1132    ///
1133    /// If no time zone provider is provided, a compiled time zone provider will be used
1134    /// which includes the time zone data in the binary. This may increase binary sizes
1135    /// by up to 200 Kb.
1136    #[cfg(feature = "temporal")]
1137    #[must_use]
1138    pub fn timezone_provider<T: TimeZoneProvider + 'static>(mut self, provider: T) -> Self {
1139        self.timezone_provider = Some(Box::new(provider));
1140        self
1141    }
1142
1143    /// Initializes the [`HostHooks`] for the context.
1144    ///
1145    /// [`Host Hooks`]: https://tc39.es/ecma262/#sec-host-hooks-summary
1146    #[must_use]
1147    pub fn host_hooks<H: HostHooks + 'static>(mut self, host_hooks: Rc<H>) -> Self {
1148        self.host_hooks = Some(host_hooks);
1149        self
1150    }
1151
1152    /// Initializes the [`Clock`] for the context.
1153    #[must_use]
1154    pub fn clock<C: Clock + 'static>(mut self, clock: Rc<C>) -> Self {
1155        self.clock = Some(clock);
1156        self
1157    }
1158
1159    /// Initializes the [`JobExecutor`] for the context.
1160    #[must_use]
1161    pub fn job_executor<Q: JobExecutor + 'static>(mut self, job_executor: Rc<Q>) -> Self {
1162        self.job_executor = Some(job_executor);
1163        self
1164    }
1165
1166    /// Initializes the [`ModuleLoader`] for the context.
1167    #[must_use]
1168    pub fn module_loader<M: ModuleLoader + 'static>(mut self, module_loader: Rc<M>) -> Self {
1169        self.module_loader = Some(module_loader);
1170        self
1171    }
1172
1173    /// [`AgentCanSuspend ( )`][spec] aka `[[CanBlock]]`
1174    ///
1175    /// Defines if this context can be suspended by calls to the [`Atomics.wait`][wait] function.
1176    ///
1177    /// # Note
1178    ///
1179    /// By the specification, multiple agents cannot share the same thread if any of them has its
1180    /// `[[CanBlock]]` field set to true. The builder will verify at build time that all contexts on
1181    /// the current thread fulfill this requisite.
1182    ///
1183    /// [spec]: https://tc39.es/ecma262/#sec-agentcansuspend
1184    /// [wait]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
1185    #[must_use]
1186    pub const fn can_block(mut self, can_block: bool) -> Self {
1187        self.can_block = can_block;
1188        self
1189    }
1190
1191    /// Specifies the number of instructions remaining to the [`Context`].
1192    ///
1193    /// This function is only available if the `fuzz` feature is enabled.
1194    #[cfg(feature = "fuzz")]
1195    #[must_use]
1196    pub const fn instructions_remaining(mut self, instructions_remaining: usize) -> Self {
1197        self.instructions_remaining = instructions_remaining;
1198        self
1199    }
1200
1201    /// Builds a new [`Context`] with the provided parameters, and defaults
1202    /// all missing parameters to their default values.
1203    // TODO: try to use a custom error here, since most of the `JsError` APIs
1204    // require having a `Context` in the first place.
1205    pub fn build(self) -> JsResult<Context> {
1206        if self.can_block {
1207            if CANNOT_BLOCK_COUNTER.get() > 0 {
1208                return Err(JsNativeError::typ()
1209                    .with_message(
1210                        "a context that can block must be the only active context in its current thread",
1211                    )
1212                    .into());
1213            }
1214        } else {
1215            CANNOT_BLOCK_COUNTER.set(CANNOT_BLOCK_COUNTER.get() + 1);
1216        }
1217
1218        let root_shape = RootShape::default();
1219
1220        let host_hooks = self.host_hooks.unwrap_or(Rc::new(DefaultHooks));
1221        let clock = self.clock.unwrap_or_else(|| Rc::new(StdClock::new()));
1222        let realm = Realm::create(host_hooks.as_ref(), &root_shape)?;
1223        let vm = Vm::new(realm);
1224
1225        let module_loader: Rc<dyn DynModuleLoader> = if let Some(loader) = self.module_loader {
1226            loader
1227        } else if let Ok(loader) = SimpleModuleLoader::new(Path::new(".")) {
1228            Rc::new(loader)
1229        } else {
1230            Rc::new(IdleModuleLoader)
1231        };
1232
1233        let job_executor = self
1234            .job_executor
1235            .unwrap_or_else(|| Rc::new(SimpleJobExecutor::new()));
1236
1237        let mut context = Context {
1238            interner: self.interner.unwrap_or_default(),
1239            vm,
1240            strict: false,
1241            #[cfg(feature = "temporal")]
1242            timezone_provider: if let Some(provider) = self.timezone_provider {
1243                provider
1244            } else {
1245                Box::new(ZeroCompiledTzdbProvider::default())
1246            },
1247            #[cfg(feature = "intl")]
1248            intl_provider: if let Some(icu) = self.icu {
1249                icu
1250            } else {
1251                cfg_if::cfg_if! {
1252                    if #[cfg(feature = "intl_bundled")] {
1253                        icu::IntlProvider::try_new_buffer(boa_icu_provider::buffer())
1254                    } else {
1255                        return Err(JsNativeError::typ()
1256                            .with_message("missing Intl provider for context")
1257                            .into()
1258                        );
1259                    }
1260                }
1261            },
1262            #[cfg(feature = "fuzz")]
1263            instructions_remaining: self.instructions_remaining,
1264            kept_alive: Vec::new(),
1265            host_hooks,
1266            clock,
1267            job_executor,
1268            module_loader,
1269            optimizer_options: OptimizerOptions::OPTIMIZE_ALL,
1270            root_shape,
1271            parser_identifier: 0,
1272            can_block: self.can_block,
1273            data: HostDefined::default(),
1274        };
1275
1276        builtins::set_default_global_bindings(&mut context)?;
1277
1278        Ok(context)
1279    }
1280}
1281
1282/// A cleanup guard for a [`Context`] that is executed when dropped.
1283#[derive(Debug)]
1284pub(crate) struct ContextCleanupGuard<'a, F>
1285where
1286    F: FnOnce(&mut Context) + 'static,
1287{
1288    context: &'a mut Context,
1289    cleanup: Option<F>,
1290}
1291
1292impl<'a, F> ContextCleanupGuard<'a, F>
1293where
1294    F: FnOnce(&mut Context) + 'static,
1295{
1296    /// Creates a new `ContextCleanupGuard` from the current context and its cleanup operation.
1297    pub(crate) fn new(context: &'a mut Context, cleanup: F) -> Self {
1298        Self {
1299            context,
1300            cleanup: Some(cleanup),
1301        }
1302    }
1303}
1304
1305impl<F> std::ops::Deref for ContextCleanupGuard<'_, F>
1306where
1307    F: FnOnce(&mut Context) + 'static,
1308{
1309    type Target = Context;
1310
1311    fn deref(&self) -> &Self::Target {
1312        self.context
1313    }
1314}
1315
1316impl<F> std::ops::DerefMut for ContextCleanupGuard<'_, F>
1317where
1318    F: FnOnce(&mut Context) + 'static,
1319{
1320    fn deref_mut(&mut self) -> &mut Self::Target {
1321        self.context
1322    }
1323}
1324
1325impl<F> Drop for ContextCleanupGuard<'_, F>
1326where
1327    F: FnOnce(&mut Context) + 'static,
1328{
1329    fn drop(&mut self) {
1330        if let Some(cleanup) = self.cleanup.take() {
1331            cleanup(self.context);
1332        }
1333    }
1334}