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 {}