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}