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}