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}