obeli_sk_boa_runtime/store/mod.rs
1//! Module containing the types related to the [`JsValueStore`].
2use boa_engine::bigint::RawBigInt;
3use boa_engine::builtins::array_buffer::{AlignedVec, SharedArrayBuffer};
4use boa_engine::builtins::error::ErrorKind;
5use boa_engine::builtins::typed_array::TypedArrayKind;
6use boa_engine::value::TryIntoJs;
7use boa_engine::{Context, JsError, JsResult, JsString, JsValue, JsVariant, js_error};
8use rustc_hash::FxHashSet;
9use std::sync::Arc;
10
11mod from;
12mod to;
13
14/// Convenience method to avoid copy-pasting the same message.
15#[inline]
16fn unsupported_type() -> JsError {
17 js_error!(Error: "DataCloneError: unsupported type for structured data")
18}
19
20#[inline]
21fn unsupported_transfer() -> JsError {
22 js_error!(TypeError: "Found an invalid value in transferList")
23}
24
25/// A type to help store [`JsString`]. Because [`JsString`] relies on [`std::rc::Rc`],
26/// it cannot be `Send`, which is a necessary contract for the Store. The [`StringStore`]
27/// can be transformed from and into `JsString`, but owns its data. It is _not_ copy-on-
28/// write.
29#[derive(Debug, Eq, PartialEq, Hash)]
30struct StringStore(Vec<u16>);
31
32impl StringStore {
33 fn to_js_string(&self) -> JsString {
34 JsString::from(self.0.as_slice())
35 }
36}
37
38impl From<JsString> for StringStore {
39 fn from(value: JsString) -> Self {
40 Self(value.to_vec())
41 }
42}
43
44impl From<StringStore> for JsString {
45 fn from(value: StringStore) -> Self {
46 JsString::from(value.0.as_slice())
47 }
48}
49
50/// Inner value for [`JsValueStore`].
51#[derive(Debug)]
52enum ValueStoreInner {
53 /// An Empty value that will be filled later. This is only used during
54 /// construction, and if encountered at other points will result
55 /// in an error.
56 Empty,
57
58 /// Primitive values - `null`.
59 Null,
60
61 /// Primitive values - `undefined`.
62 Undefined,
63
64 /// Primitive values - `Boolean`.
65 Boolean(bool),
66
67 /// Primitive values - `float64`. No need to store integers separately,
68 /// they'll be checked when recreating the `JsValue`.
69 Float(f64),
70
71 /// [`JsString`]s are context-free, but not `Send`. Since we want to be
72 /// `Send`, we'll have to make a copy of the data.
73 String(StringStore),
74
75 /// [`boa_engine::JsBigInt`]s are context-free but not `Send`. The Raw version
76 /// of it is, though.
77 BigInt(RawBigInt),
78
79 /// A dictionary of strings to values which should be reconstructed into
80 /// a `JsObject`. Note: the prototype and constructor are not maintained,
81 /// and during reconstruction the default `Object` prototype will be used.
82 Object(Vec<(StringStore, JsValueStore)>),
83
84 /// A `Map()` object in JavaScript.
85 Map(Vec<(JsValueStore, JsValueStore)>),
86
87 /// A `Set()` object in JavaScript. The elements are already unique at
88 /// construction.
89 Set(Vec<JsValueStore>),
90
91 /// An `Array` object in JavaScript.
92 Array(Vec<Option<JsValueStore>>),
93
94 /// A `Date` object in JavaScript. Although this can be marshaled, it uses
95 /// the system's datetime library to be reconstructed and may diverge.
96 Date(f64),
97
98 /// Allowed error types (see the structured clone algorithm page).
99 #[expect(unused)]
100 Error {
101 kind: ErrorKind,
102 name: StringStore,
103 message: StringStore,
104 stack: StringStore,
105 cause: StringStore,
106 },
107
108 /// Regular expression. We store the expression and its flags. Everything else
109 /// is reset. These are extracted as `String`, so we don't need to use the
110 /// [`StringStore`] type.
111 RegExp { source: String, flags: String },
112
113 /// Array Buffer.
114 ArrayBuffer(AlignedVec<u8>),
115
116 /// Shared Array Buffer.
117 SharedArrayBuffer(SharedArrayBuffer),
118
119 /// Dataview.
120 #[expect(unused)]
121 DataView {
122 buffer: JsValueStore,
123 byte_length: u64,
124 byte_offset: u64,
125 },
126
127 /// Typed Array, including its kind and data.
128 TypedArray {
129 kind: TypedArrayKind,
130 buffer: JsValueStore,
131 },
132}
133
134/// A [`JsValue`]-like structure that can rebuild its value given any [`Context`].
135/// It essentially stores the value itself and its original type. During
136/// reconstruction, the constructors of the new [`Context`] will be used.
137///
138/// This follows the rules of the [structured clone algorithm][sca], but does not
139/// require a [`Context`] to copy/move, and can be [`Send`] between threads.
140///
141/// It is not serializable as it allows recursive values.
142///
143/// To transform a [`JsValue`] into a [`JsValueStore`], the application MUST
144/// pass in the context of the initial value. To transform it back to a
145/// [`JsValue`], the application MUST pass the context that will contain
146/// all prototypes for the new types (e.g. Object).
147///
148/// [sca]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
149#[derive(Debug, Clone)]
150pub struct JsValueStore(Arc<ValueStoreInner>);
151
152impl TryIntoJs for JsValueStore {
153 fn try_into_js(&self, context: &mut Context) -> JsResult<JsValue> {
154 let mut seen = to::ReverseSeenMap::default();
155 to::try_value_into_js(self, &mut seen, context)
156 }
157}
158
159impl JsValueStore {
160 /// Replace the inner content with a new inner. This is necessary as the inner
161 /// content holder must be allocated before its own inner content is created
162 /// (to allow for recursive data). Therefore, the pattern is to create the
163 /// store with an empty inner, then create the sub-content, and replace the
164 /// empty inner with the new inner.
165 ///
166 /// # SAFETY
167 /// This should only be done if the inner content is [`ValueStoreInner::Empty`],
168 /// and only by the creator of the current [`JsValueStore`]. We enforce the first
169 /// rule at runtime (and will panic), and the second rule by requiring a mutable
170 /// reference. This is still unsafe and relies on unsafe pointer access.
171 unsafe fn replace(&mut self, other: ValueStoreInner) {
172 let ptr = Arc::as_ptr(&self.0).cast_mut();
173
174 assert!(!ptr.is_null());
175 unsafe {
176 assert!(
177 matches!(*ptr, ValueStoreInner::Empty),
178 "ValueStoreInner must be empty."
179 );
180
181 *ptr = other;
182 }
183 }
184
185 /// A still-being-constructed value.
186 fn empty() -> Self {
187 Self(Arc::new(ValueStoreInner::Empty))
188 }
189
190 fn new(inner: ValueStoreInner) -> Self {
191 Self(Arc::new(inner))
192 }
193
194 /// Create a context-free [`JsValue`] equivalent from an existing `JsValue` and the
195 /// [`Context`] that was used to create it. The `transfer` argument allows for
196 /// transferring ownership of the inner data to the context-free value instead of
197 /// cloning it. By default, if a value isn't in the transfer vector, it is cloned.
198 ///
199 /// # Errors
200 /// Any errors related to transferring or cloning a value's inner data.
201 pub fn try_from_js(
202 value: &JsValue,
203 context: &mut Context,
204 transfer: Vec<JsValue>,
205 ) -> JsResult<Self> {
206 let mut seen = from::SeenMap::default();
207 // Verify the validity of the transfer list and make it a set.
208 let transfer = transfer
209 .into_iter()
210 .map(|v| match v.variant() {
211 JsVariant::Object(o) if from::is_transferable(&o) => Ok(o),
212 _ => Err(unsupported_transfer()),
213 })
214 .collect::<Result<FxHashSet<_>, _>>()?;
215
216 let v = from::try_from_js_value(value, &transfer, &mut seen, context)?;
217 Ok(v)
218 }
219}