wsdom_core/
operations.rs

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