px_wsdom_core/
operations.rs

1use crate::{
2    js::{object::JsObject, value::JsValue},
3    js_cast::JsCast,
4    link::{Browser, Error, RpcCell},
5    protocol::{ERR, GET, IMPORT, SET},
6    retrieve::RetrieveFuture,
7    serialize::{ToJs, UseInJsCode, UseInJsCodeWriter},
8    Endpoint, RpcHandle,
9};
10use alloc::{borrow::ToOwned, sync::Arc};
11use core::{
12    fmt::Write,
13    marker::PhantomData,
14    // sync::{Arc, Mutex},
15    task::Waker,
16};
17use futures_util::task::noop_waker_ref;
18use sha3::Digest;
19use spin::Mutex;
20
21impl Browser {
22    /// Creates a new RPC endpoint
23    pub fn new_rpc<T, C>(&self, a: &str) -> crate::RpcHandle<Endpoint<T, C>> {
24        let mut lock = self.0.lock();
25        let a = lock
26            .rpc_state
27            .entry(a.to_owned())
28            .or_insert_with(|| {
29                crate::RpcCellAM(Arc::new(Mutex::new(RpcCell {
30                    waker: noop_waker_ref().clone(),
31                    queue: Default::default(),
32                })))
33            })
34            .clone();
35        return RpcHandle {
36            browser: self.clone(),
37            recv: a,
38            data: Endpoint {
39                phantom: PhantomData,
40            },
41        };
42    }
43    /// Call a standalone JavaScript function.
44    ///
45    /// ```rust
46    /// # use wsdom_core::Browser;
47    /// fn example(browser: Browser) {
48    ///     let _return_value = browser.call_function(
49    ///         "alert",
50    ///         [&"hello world" as &_],
51    ///         false
52    ///     );
53    /// }
54    /// ```
55    ///
56    /// This method is "low-level" and you shouldn't need to use it.
57    /// Instead, use the `wsdom` crate which provides mostly type-safe wrappers to the Web API.
58    ///
59    /// If you still want to use `call_function`,
60    /// be aware that the first argument (`function_name`) is NOT escaped.
61    /// Do NOT allow user-supplied function name.
62    pub fn call_function<'a>(
63        &'a self,
64        function_name: &'a str,
65        args: impl IntoIterator<Item = &'a dyn UseInJsCode>,
66        last_arg_variadic: bool,
67    ) -> JsValue {
68        self.call_function_inner(&format_args!("{}", function_name), args, last_arg_variadic)
69    }
70
71    /// Call constructor for a class.
72    ///
73    /// ```rust
74    /// # use wsdom_core::Browser;
75    /// fn example(browser: Browser) {
76    ///     let _regexp_object = browser.call_constructor(
77    ///         "RegExp",
78    ///         [&"hello" as &_],
79    ///         false
80    ///     );
81    /// }
82    /// ```
83    ///
84    /// This method is "low-level" and you shouldn't need to use it.
85    /// Instead, use the `wsdom` crate which provides mostly type-safe wrappers to the Web API.
86    ///
87    /// If you still want to use `call_constructor`,
88    /// be aware that the first argument (`class_name`) is NOT escaped.
89    /// Do NOT allow user-supplied class name.
90    pub fn call_constructor<'a>(
91        &'a self,
92        class_name: &'a str,
93        args: impl IntoIterator<Item = &'a dyn UseInJsCode>,
94        last_arg_variadic: bool,
95    ) -> JsValue {
96        self.call_function_inner(&format_args!("new {}", class_name), args, last_arg_variadic)
97    }
98
99    fn call_function_inner<'a>(
100        &'a self,
101        function: &core::fmt::Arguments<'_>,
102        args: impl IntoIterator<Item = &'a dyn UseInJsCode>,
103        last_arg_variadic: bool,
104    ) -> JsValue {
105        let id = {
106            let mut link = self.0.lock();
107            let out_id = link.get_new_id();
108            write!(link.raw_commands_buf(), "try{{{SET}({out_id},{function}(").unwrap();
109            let mut iter = args.into_iter().peekable();
110            while let Some(arg) = iter.next() {
111                let arg = UseInJsCodeWriter(arg);
112                let res = if last_arg_variadic && iter.peek().is_none() {
113                    write!(link.raw_commands_buf(), "...{arg},")
114                } else {
115                    write!(link.raw_commands_buf(), "{arg},")
116                };
117                if let Err(e) = res {
118                    link.kill(Error::CommandSerialize(e));
119                }
120            }
121            write!(
122                link.raw_commands_buf(),
123                "))}}catch($){{{ERR}({out_id},$)}};\n"
124            )
125            .unwrap();
126            link.wake_outgoing();
127            out_id
128        };
129        JsValue {
130            id,
131            browser: self.clone(),
132        }
133    }
134
135    /// Get a field in an object.
136    ///
137    /// This returns the value of `base_obj[property]`.
138    pub fn get_field(&self, base_obj: &dyn UseInJsCode, property: &dyn UseInJsCode) -> JsValue {
139        let browser = self.clone();
140        let id = {
141            let mut link = browser.0.lock();
142            let out_id = link.get_new_id();
143            let base_obj = UseInJsCodeWriter(base_obj);
144            let property = UseInJsCodeWriter(property);
145            if let Err(e) = writeln!(
146                link.raw_commands_buf(),
147                "try{{{SET}({out_id},({base_obj})[{property}])}}catch($){{{ERR}({out_id},$)}};"
148            ) {
149                link.kill(Error::CommandSerialize(e));
150            }
151            link.wake_outgoing_lazy();
152            out_id
153        };
154        JsValue { id, browser }
155    }
156
157    /// Set a field in an object.
158    ///
159    /// This executes the JavaScript code `base_obj[property]=value;`
160    pub fn set_field(
161        &self,
162        base_obj: &dyn UseInJsCode,
163        property: &dyn UseInJsCode,
164        value: &dyn UseInJsCode,
165    ) {
166        let mut link = self.0.lock();
167        let (base_obj, property, value) = (
168            UseInJsCodeWriter(base_obj),
169            UseInJsCodeWriter(property),
170            UseInJsCodeWriter(value),
171        );
172        if let Err(e) = writeln!(link.raw_commands_buf(), "({base_obj})[{property}]={value};") {
173            link.kill(Error::CommandSerialize(e));
174        }
175        link.wake_outgoing();
176    }
177
178    /// Create a new value on the JavaScript side from a [ToJs] type.
179    pub fn new_value<'a, T: JsCast>(&'a self, value: &'a dyn ToJs<T>) -> T {
180        let val = self.value_from_raw_code(format_args!("{}", UseInJsCodeWriter(value)));
181        JsCast::unchecked_from_js(val)
182    }
183
184    /// Executes arbitrary JavaScript code.
185    ///
186    /// Don't use this unless you really have to.
187    pub fn run_raw_code<'a>(&'a self, code: core::fmt::Arguments<'a>) {
188        let mut link = self.0.lock();
189        if let Err(e) = writeln!(link.raw_commands_buf(), "{{ {code} }}") {
190            link.kill(Error::CommandSerialize(e));
191        }
192        link.wake_outgoing();
193    }
194
195    /// Executes arbitrary JavaScript expression and return the result.
196    ///
197    /// Don't use this unless you really have to.
198    pub fn value_from_raw_code<'a>(&'a self, code: core::fmt::Arguments<'a>) -> JsValue {
199        let mut link = self.0.lock();
200        let out_id = link.get_new_id();
201        writeln!(
202            link.raw_commands_buf(),
203            "try{{{SET}({out_id},{code})}}catch($){{{ERR}({out_id},$)}};"
204        )
205        .unwrap();
206        link.wake_outgoing();
207        JsValue {
208            id: out_id,
209            browser: self.to_owned(),
210        }
211    }
212
213    /// Executesand caches  arbitrary JavaScript expression and return the result.
214    ///
215    /// Don't use this unless you really have to.
216    pub fn value_from_pure_raw_code(&self, x: &str) -> JsValue {
217        let mut link = self.0.lock();
218        let a = match link.pure_values.get(x).cloned() {
219            None => {
220                let out = self.value_from_raw_code(format_args!("{x}"));
221                link.pure_values.insert(x.to_owned(), out.clone());
222                out
223            }
224            Some(a) => a,
225        };
226        return a;
227    }
228
229    /// Gets an import from the available ones
230    pub fn import(&self, name: &str) -> JsValue {
231        let mut link = self.0.lock();
232        let a = match link.imports.get(name).cloned() {
233            None => {
234                let out_id = link.get_new_id();
235                writeln!(
236                    link.raw_commands_buf(),
237                    "try{{{SET}({out_id},{IMPORT}._{})}}catch($){{{ERR}({out_id},$)}};",
238                    hex::encode(&sha3::Sha3_256::digest(name.as_bytes()))
239                )
240                .unwrap();
241                link.wake_outgoing();
242                let out = JsValue {
243                    id: out_id,
244                    browser: self.to_owned(),
245                };
246                link.imports.insert(name.to_owned(), out.clone());
247                out
248            }
249            Some(a) => a,
250        };
251        return a;
252    }
253}
254
255impl JsValue {
256    pub(crate) fn retrieve_and_deserialize<U: serde::de::DeserializeOwned>(
257        &self,
258    ) -> RetrieveFuture<'_, U> {
259        RetrieveFuture::new(self.id, &self.browser)
260    }
261    /// Retrive this value from the JS side to the Rust side.
262    /// Returns Future whose output is a [serde_json::Value].
263    ///
264    /// ```rust
265    /// # use wsdom::Browser;
266    /// # use wsdom::dom::HTMLInputElement;
267    /// async fn example(input: &HTMLInputElement) {
268    ///     let _val = input.get_value().retrieve_json().await;
269    /// }
270    /// ```
271    pub fn retrieve_json(&self) -> RetrieveFuture<'_, serde_json::Value> {
272        self.retrieve_and_deserialize()
273    }
274}
275impl JsObject {
276    /// Get a field value of in this object.
277    ///
278    /// WSDOM provides built-in getters so you should use that instead when possible.
279    ///
280    /// Use `js_get_field` only when needed
281    ///
282    /// ```rust
283    /// # use wsdom_core::Browser;
284    /// # use wsdom_core::js_types::*;
285    /// fn example(browser: Browser) {
286    ///     // you can get `window["location"]["href"]` like this
287    ///     let href: JsValue = wsdom::dom::location(&browser).js_get_field(&"href");
288    ///
289    ///     // but you should use built-in getters instead
290    ///     let href: JsString = wsdom::dom::location(&browser).get_href();
291    /// }
292    /// ```
293    pub fn js_get_field(&self, property: &dyn UseInJsCode) -> JsValue {
294        let browser = self.browser.clone();
295        let id = {
296            let mut link = browser.0.lock();
297            let out_id = link.get_new_id();
298            let self_id = self.id;
299            let property = UseInJsCodeWriter(property);
300            if let Err(e) = writeln!(
301                link.raw_commands_buf(),
302                "try{{{SET}({out_id},{GET}({self_id})[{property}])}}catch($){{{ERR}({out_id},$)}};"
303            ) {
304                link.kill(Error::CommandSerialize(e));
305            }
306            link.wake_outgoing_lazy();
307            out_id
308        };
309        JsValue { id, browser }
310    }
311    /// Set a field value of in this object.
312    ///
313    /// WSDOM provides built-in setters so you should use that instead when possible.
314    ///
315    /// Use `js_set_field` only when needed
316    ///
317    /// ```rust
318    /// # use wsdom_core::Browser;
319    /// # use wsdom_core::js_types::*;
320    /// fn example(browser: Browser) {
321    ///     // you can set `window["location"]["href"]` like this
322    ///     wsdom::dom::location(&browser).js_set_field(&"href", &"https://example.com/");
323    ///
324    ///     // but you should use built-in setters instead
325    ///     wsdom::dom::location(&browser).set_href(&"https://example.com");
326    /// }
327    /// ```
328    pub fn js_set_field(&self, property: &dyn UseInJsCode, value: &dyn UseInJsCode) {
329        let self_id = self.id;
330        let mut link = self.browser.0.lock();
331        let (property, value) = (UseInJsCodeWriter(property), UseInJsCodeWriter(value));
332        if let Err(e) = writeln!(
333            link.raw_commands_buf(),
334            "{GET}({self_id})[{property}]={value};"
335        ) {
336            link.kill(Error::CommandSerialize(e));
337        }
338        link.wake_outgoing();
339    }
340
341    /// Call a method on this object.
342    ///
343    /// Most types in WSDOM already come with safe Rust wrappers for their methods, so you should use those instead.
344    ///
345    /// ```rust
346    /// # use wsdom_core::Browser;
347    /// fn example(browser: &Browser) {
348    ///     let console = wsdom::dom::console(browser);
349    ///     // you can call console.log like this
350    ///     console.js_call_method("log", [&"hello" as &_], false);
351    ///     
352    ///     // but the better way is to use
353    ///     wsdom::dom::console(&browser).log(&[&"Hello" as &_]);
354    /// }
355    /// ```
356    ///
357    /// Be aware that the first argument (`method_name`) is NOT escaped.
358    ///
359    /// Set `last_arg_variadic` to `true` if you want to "spread" the last argument as `obj.method(arg1, arg2, ...arg3)`.
360    pub fn js_call_method<'a>(
361        &'a self,
362        method_name: &'a str,
363        args: impl IntoIterator<Item = &'a dyn UseInJsCode>,
364        last_arg_variadic: bool,
365    ) -> JsValue {
366        let self_id = self.id;
367        self.browser.call_function_inner(
368            &format_args!("{GET}({self_id}).{method_name}"),
369            args,
370            last_arg_variadic,
371        )
372    }
373    /// Call this object: `obj()`.
374    ///
375    /// Most types in WSDOM already come with safe Rust wrappers for their methods, so you should use those instead.
376    pub fn js_call_self<'a>(
377        &'a self,
378        args: impl IntoIterator<Item = &'a dyn UseInJsCode>,
379        last_arg_variadic: bool,
380    ) -> JsValue {
381        let self_id = self.id;
382        self.browser.call_function_inner(
383            &format_args!("({GET}({self_id}))"),
384            args,
385            last_arg_variadic,
386        )
387    }
388}
389
390struct CommandSerializeFailed;
391
392impl core::fmt::Display for CommandSerializeFailed {
393    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
394        core::fmt::Debug::fmt(self, f)
395    }
396}
397impl core::fmt::Debug for CommandSerializeFailed {
398    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
399        f.debug_struct("CommandSerializeFailed").finish()
400    }
401}
402
403impl core::error::Error for CommandSerializeFailed {}