Skip to main content

boa_engine/context/
hooks.rs

1use crate::{
2    Context, JsResult, JsString, JsValue,
3    builtins::{Promise, promise::OperationType},
4    context::intrinsics::Intrinsics,
5    job::JobCallback,
6    object::{JsFunction, JsObject},
7    realm::Realm,
8};
9use time::{OffsetDateTime, UtcOffset};
10
11/// [`Host Hooks`] customizable by the host code or engine.
12///
13/// Every hook contains on its `Requirements` section the spec requirements
14/// that the hook must abide to for spec compliance.
15///
16/// # Usage
17///
18/// Implement the trait for a custom struct (maybe with additional state), overriding the methods that
19/// need to be redefined:
20///
21/// ```
22/// use boa_engine::{
23///     JsNativeError, JsResult, JsString, Source,
24///     context::{Context, ContextBuilder, HostHooks},
25///     realm::Realm,
26/// };
27/// use std::rc::Rc;
28///
29/// struct Hooks;
30///
31/// impl HostHooks for Hooks {
32///     fn ensure_can_compile_strings(
33///         &self,
34///         _realm: Realm,
35///         _parameters: &[JsString],
36///         _body: &JsString,
37///         _direct: bool,
38///         _context: &mut Context,
39///     ) -> JsResult<()> {
40///         Err(JsNativeError::typ()
41///             .with_message("eval calls not available")
42///             .into())
43///     }
44/// }
45///
46/// let context = &mut ContextBuilder::new()
47///     .host_hooks(Rc::new(Hooks))
48///     .build()
49///     .unwrap();
50/// let result = context.eval(Source::from_bytes(r#"eval("let a = 5")"#));
51/// assert!(
52///     result
53///         .unwrap_err()
54///         .to_string()
55///         .starts_with("TypeError: eval calls not available")
56/// );
57/// ```
58///
59/// [`Host Hooks`]: https://tc39.es/ecma262/#sec-host-hooks-summary
60pub trait HostHooks {
61    /// [`HostMakeJobCallback ( callback )`][spec]
62    ///
63    /// # Requirements
64    ///
65    /// - It must return a `JobCallback` Record whose `[[Callback]]` field is `callback`.
66    ///
67    /// [spec]: https://tc39.es/ecma262/#sec-hostmakejobcallback
68    fn make_job_callback(&self, callback: JsFunction, _context: &mut Context) -> JobCallback {
69        // The default implementation of HostMakeJobCallback performs the following steps when called:
70
71        // 1. Return the JobCallback Record { [[Callback]]: callback, [[HostDefined]]: empty }.
72        JobCallback::new(callback, ())
73    }
74
75    /// [`HostCallJobCallback ( jobCallback, V, argumentsList )`][spec]
76    ///
77    /// # Requirements
78    ///
79    /// - It must perform and return the result of `Call(jobCallback.[[Callback]], V, argumentsList)`.
80    ///
81    /// [spec]: https://tc39.es/ecma262/#sec-hostcalljobcallback
82    #[cfg_attr(feature = "native-backtrace", track_caller)]
83    fn call_job_callback(
84        &self,
85        job: &JobCallback,
86        this: &JsValue,
87        args: &[JsValue],
88        context: &mut Context,
89    ) -> JsResult<JsValue> {
90        // The default implementation of HostCallJobCallback performs the following steps when called:
91
92        // 1. Assert: IsCallable(jobCallback.[[Callback]]) is true.
93        // already asserted by `Call`.
94        // 2. Return ? Call(jobCallback.[[Callback]], V, argumentsList).
95        job.callback().call(this, args, context)
96    }
97
98    /// [`HostPromiseRejectionTracker ( promise, operation )`][spec]
99    ///
100    /// # Requirements
101    ///
102    /// - It must complete normally (i.e. not return an abrupt completion). This is already
103    ///   ensured by the return type.
104    ///
105    /// [spec]: https://tc39.es/ecma262/#sec-host-promise-rejection-tracker
106    fn promise_rejection_tracker(
107        &self,
108        _promise: &JsObject<Promise>,
109        _operation: OperationType,
110        _context: &mut Context,
111    ) {
112        // The default implementation of HostPromiseRejectionTracker is to return unused.
113    }
114
115    /// [`HostEnsureCanCompileStrings ( calleeRealm, parameterStrings, bodyString, direct )`][spec]
116    ///
117    /// # Requirements
118    ///
119    /// - If the returned Completion Record is a normal completion, it must be a normal completion
120    ///   containing unused. This is already ensured by the return type.
121    ///
122    /// [spec]: https://tc39.es/ecma262/#sec-hostensurecancompilestrings
123    fn ensure_can_compile_strings(
124        &self,
125        _realm: Realm,
126        _parameters: &[JsString],
127        _body: &JsString,
128        _direct: bool,
129        _context: &mut Context,
130    ) -> JsResult<()> {
131        // The default implementation of HostEnsureCanCompileStrings is to return NormalCompletion(unused).
132        Ok(())
133    }
134
135    /// [`HostHasSourceTextAvailable ( func )`][spec]
136    ///
137    /// # Requirements
138    ///
139    /// - It must be deterministic with respect to its parameters. Each time it is called with a
140    ///   specific `func` as its argument, it must return the same result.
141    ///
142    /// [spec]: https://tc39.es/ecma262/#sec-hosthassourcetextavailable
143    fn has_source_text_available(&self, _function: &JsFunction, _context: &mut Context) -> bool {
144        // The default implementation of HostHasSourceTextAvailable is to return true.
145        true
146    }
147
148    /// [`HostEnsureCanAddPrivateElement ( O )`][spec]
149    ///
150    /// # Requirements
151    ///
152    /// - If `O` is not a host-defined exotic object, this abstract operation must return
153    ///   `NormalCompletion(unused)` and perform no other steps.
154    /// - Any two calls of this abstract operation with the same argument must return the same kind
155    ///   of *Completion Record*.
156    /// - This abstract operation should only be overridden by ECMAScript hosts that are web browsers.
157    ///
158    /// [spec]: https://tc39.es/ecma262/#sec-hostensurecanaddprivateelement
159    fn ensure_can_add_private_element(
160        &self,
161        _o: &JsObject,
162        _context: &mut Context,
163    ) -> JsResult<()> {
164        Ok(())
165    }
166
167    /// Creates the global object of a new [`Context`] from the initial intrinsics.
168    ///
169    /// Equivalent to the step 7 of [`InitializeHostDefinedRealm ( )`][ihdr].
170    ///
171    /// [ihdr]: https://tc39.es/ecma262/#sec-initializehostdefinedrealm
172    fn create_global_object(&self, intrinsics: &Intrinsics) -> JsObject {
173        JsObject::with_object_proto(intrinsics)
174    }
175
176    /// Creates the global `this` of a new [`Context`] from the initial intrinsics.
177    ///
178    /// Equivalent to the step 8 of [`InitializeHostDefinedRealm ( )`][ihdr].
179    ///
180    /// [ihdr]: https://tc39.es/ecma262/#sec-initializehostdefinedrealm
181    fn create_global_this(&self, _intrinsics: &Intrinsics) -> Option<JsObject> {
182        None
183    }
184
185    /// Gets the current UTC time of the host, in milliseconds since epoch.
186    ///
187    /// Defaults to using [`OffsetDateTime::now_utc`] on all targets,
188    /// which can cause panics if the target doesn't support [`SystemTime::now`][time].
189    ///
190    /// [time]: std::time::SystemTime::now
191    #[deprecated(
192        since = "0.21.0",
193        note = "Use `context.clock().now().millis_since_epoch()` instead"
194    )]
195    fn utc_now(&self) -> i64 {
196        let now = OffsetDateTime::now_utc();
197        now.unix_timestamp() * 1000 + i64::from(now.millisecond())
198    }
199
200    /// Returns the offset of the local timezone to the `utc` timezone in seconds.
201    fn local_timezone_offset_seconds(&self, unix_time_seconds: i64) -> i32 {
202        OffsetDateTime::from_unix_timestamp(unix_time_seconds)
203            .ok()
204            .and_then(|t| UtcOffset::local_offset_at(t).ok())
205            .map_or(0, UtcOffset::whole_seconds)
206    }
207
208    /// Gets the maximum size in bits that can be allocated for an `ArrayBuffer` or a
209    /// `SharedArrayBuffer`.
210    ///
211    /// This hook will be called before any buffer allocation, which allows to dynamically change
212    /// the maximum size at runtime. By default, this is set to 1.5GiB per the recommendations of the
213    /// [specification]:
214    ///
215    /// > If a host is multi-tenanted (i.e. it runs many ECMAScript applications simultaneously),
216    /// > such as a web browser, and its implementations choose to implement in-place growth by reserving
217    /// > virtual memory, we recommend that both 32-bit and 64-bit implementations throw for values of
218    /// > "`maxByteLength`" ≥ 1GiB to 1.5GiB. This is to reduce the likelihood a single application can
219    /// > exhaust the virtual memory address space and to reduce interoperability risk.
220    ///
221    ///
222    /// [specification]: https://tc39.es/ecma262/#sec-resizable-arraybuffer-guidelines
223    fn max_buffer_size(&self, _context: &mut Context) -> u64 {
224        1_610_612_736 // 1.5 GiB
225    }
226}
227
228/// Default implementation of [`HostHooks`], which doesn't carry any state.
229#[derive(Debug, Clone, Copy)]
230pub struct DefaultHooks;
231
232impl HostHooks for DefaultHooks {}