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 {}