web_thread/post.rs
1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::{JsValue, js_sys};
5
6/// Objects that can be sent via `postMessage`. A type that is `Post`
7/// supports being serialized into a JavaScript object that can be
8/// sent using `postMessage`, and also getting an array of subobjects
9/// that must be
10/// [transferred](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects).
11pub trait Post: AsJs {
12 /// Get a list of the objects that must be
13 /// transferred when calling `postMessage`.
14 ///
15 /// The default implementation returns an empty array.
16 fn transferables(&self) -> js_sys::Array {
17 js_sys::Array::new()
18 }
19}
20
21/// Convenience trait for something that can have messages posted to
22/// it, including
23/// [transferables](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects).
24pub trait PostExt {
25 /// Send a value, transferring subobjects as necessary.
26 ///
27 /// This function consumes `message`, as in general it may leave
28 /// the object in an incoherent state.
29 ///
30 /// # Errors
31 ///
32 /// If the message could not be sent.
33 fn post(&self, message: impl Post) -> Result<(), JsValue>;
34}
35
36impl PostExt for web_sys::MessagePort {
37 fn post(&self, message: impl Post) -> Result<(), JsValue> {
38 // While not syntactically consumed, the use of `postMessage`
39 // here may leave `Context` in an invalid state (setting
40 // transferred JavaScript values to `undefined`).
41 #![allow(clippy::needless_pass_by_value)]
42
43 self.post_message_with_transferable(&message.to_js()?, &message.transferables())
44 }
45}
46
47impl PostExt for web_sys::Worker {
48 fn post(&self, message: impl Post) -> Result<(), JsValue> {
49 // While not syntactically consumed, the use of `postMessage`
50 // here may leave `Context` in an invalid state (setting
51 // transferred JavaScript values to `undefined`).
52 #![allow(clippy::needless_pass_by_value)]
53
54 self.post_message_with_transfer(&message.to_js()?, &message.transferables())
55 }
56}
57
58/// A serializable (JS-friendly) representation of a message plus its
59/// transferables.
60#[derive(serde::Serialize)]
61pub struct Postable {
62 #[serde(with = "serde_wasm_bindgen::preserve")]
63 message: JsValue,
64 #[serde(with = "serde_wasm_bindgen::preserve")]
65 transfer: js_sys::Array,
66}
67
68impl Postable {
69 pub fn new(message: impl Post) -> Result<Self, JsValue> {
70 // While not syntactically consumed, the use of `postMessage`
71 // may leave `Context` in an invalid state (setting
72 // transferred JavaScript values to `undefined`).
73 #![allow(clippy::needless_pass_by_value)]
74
75 Ok(Self {
76 message: message.to_js()?,
77 transfer: message.transferables(),
78 })
79 }
80}
81
82/// An object-safe version of
83/// `std::convert::TryInto`/`std::convert::TryFrom`, relying on the
84/// JavaScript GC.
85pub trait AsJs {
86 /// Retrieve the JavaScript representation of a value.
87 ///
88 /// # Errors
89 ///
90 /// If the value could not be represented as a JavaScript value.
91 fn to_js(&self) -> Result<JsValue, JsValue>;
92 /// Construct the value from its JavaScript representation.
93 /// Unlike [`to_js`] this function consumes its argument, as the
94 /// value or its subvalues may be transferred into the resulting
95 /// Rust object.
96 ///
97 /// # Errors
98 ///
99 /// If the value could not be reconstructed from the provided
100 /// JavaScript value.
101 ///
102 /// [`to_js`]: AsJs::to_js()
103 fn from_js(js_value: JsValue) -> Result<Self, JsValue>
104 where
105 Self: Sized;
106}
107
108impl<T: serde::Serialize + serde::de::DeserializeOwned> AsJs for T {
109 fn to_js(&self) -> Result<JsValue, JsValue> {
110 Ok(serde_wasm_bindgen::to_value(self)?)
111 }
112
113 fn from_js(value: JsValue) -> Result<Self, JsValue>
114 where
115 Self: Sized,
116 {
117 Ok(serde_wasm_bindgen::from_value(value)?)
118 }
119}
120
121impl Post for () {}
122impl Post for u8 {}
123impl Post for u16 {}
124impl Post for u32 {}
125impl Post for u64 {}
126impl Post for u128 {}
127impl Post for i8 {}
128impl Post for i16 {}
129impl Post for i32 {}
130impl Post for i64 {}
131impl Post for i128 {}
132impl Post for String {}
133
134impl<T: Post, E: Post> Post for Result<T, E>
135where
136 Result<T, E>: AsJs,
137{
138 fn transferables(&self) -> js_sys::Array {
139 match self {
140 Ok(x) => x.transferables(),
141 Err(e) => e.transferables(),
142 }
143 }
144}
145
146impl<T: Post, U: Post> Post for (T, U)
147where
148 (T, U): AsJs,
149{
150 fn transferables(&self) -> js_sys::Array {
151 let mut array = js_sys::Array::new();
152 array = array.concat(&self.0.transferables());
153 array = array.concat(&self.1.transferables());
154 array
155 }
156}
157
158impl<T: Post> Post for Vec<T>
159where
160 Vec<T>: AsJs,
161{
162 fn transferables(&self) -> js_sys::Array {
163 let mut array = js_sys::Array::new();
164 for x in self {
165 array = array.concat(&x.transferables());
166 }
167 array
168 }
169}