webdriver_server/
script_argument_extraction.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use base::id::BrowsingContextId;
6use embedder_traits::WebDriverScriptCommand;
7use ipc_channel::ipc;
8use serde_json::Value;
9use webdriver::command::JavascriptCommandParameters;
10use webdriver::common::{ELEMENT_KEY, FRAME_KEY, SHADOW_KEY, WINDOW_KEY};
11use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
12
13use crate::{Handler, VerifyBrowsingContextIsOpen, wait_for_ipc_response_flatten};
14
15impl Handler {
16    /// <https://w3c.github.io/webdriver/#dfn-extract-the-script-arguments-from-a-request>
17    pub(crate) fn extract_script_arguments(
18        &self,
19        parameters: JavascriptCommandParameters,
20    ) -> WebDriverResult<(String, Vec<String>)> {
21        // Step 1. Let script be the result of getting a property named "script" from parameters
22        // Step 2. (Done) If script is not a String, return error with error code invalid argument.
23        let script = parameters.script;
24
25        // Step 3. Let args be the result of getting a property named "args" from parameters.
26        // Step 4. (Done) If args is not an Array return error with error code invalid argument.
27        // Step 5. Let `arguments` be JSON deserialize with session and args.
28        let args: Vec<String> = parameters
29            .args
30            .as_deref()
31            .unwrap_or(&[])
32            .iter()
33            .map(|value| self.json_deserialize(value))
34            .collect::<WebDriverResult<Vec<_>>>()?;
35
36        Ok((script, args))
37    }
38
39    /// <https://w3c.github.io/webdriver/#dfn-deserialize-a-web-element>
40    fn deserialize_web_element(&self, element: &Value) -> WebDriverResult<String> {
41        // Step 2. Let reference be the result of getting the web element identifier property from object.
42        let element_ref = match element {
43            Value::String(string) => string.clone(),
44            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument, "")),
45        };
46
47        // Step 3. Let element be the result of trying to get a known element with session and reference.
48        let (sender, receiver) = ipc::channel().unwrap();
49        self.browsing_context_script_command(
50            WebDriverScriptCommand::GetKnownElement(element_ref.clone(), sender),
51            VerifyBrowsingContextIsOpen::No,
52        )?;
53
54        wait_for_ipc_response_flatten(receiver)?;
55        // Step 4. Return success with data element.
56        Ok(format!("window.webdriverElement(\"{}\")", element_ref))
57    }
58
59    /// <https://w3c.github.io/webdriver/#dfn-deserialize-a-shadow-root>
60    fn deserialize_shadow_root(&self, shadow_root: &Value) -> WebDriverResult<String> {
61        // Step 2. Let reference be the result of getting the shadow root identifier property from object.
62        let shadow_root_ref = match shadow_root {
63            Value::String(string) => string.clone(),
64            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument, "")),
65        };
66
67        // Step 3. Let element be the result of trying to get a known element with session and reference.
68        let (sender, receiver) = ipc::channel().unwrap();
69        self.browsing_context_script_command(
70            WebDriverScriptCommand::GetKnownShadowRoot(shadow_root_ref.clone(), sender),
71            VerifyBrowsingContextIsOpen::No,
72        )?;
73
74        wait_for_ipc_response_flatten(receiver)?;
75        // Step 4. Return success with data element.
76        Ok(format!(
77            "window.webdriverShadowRoot(\"{}\")",
78            shadow_root_ref
79        ))
80    }
81
82    /// <https://w3c.github.io/webdriver/#dfn-deserialize-a-web-frame>
83    fn deserialize_web_frame(&self, frame: &Value) -> WebDriverResult<String> {
84        // Step 2. Let reference be the result of getting the web frame identifier property from object.
85        let frame_ref = match frame {
86            Value::String(string) => string.clone(),
87            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument, "")),
88        };
89
90        // Step 3. Let browsing context be the browsing context whose window handle is reference,
91        // or null if no such browsing context exists.
92        let Some(browsing_context_id) = BrowsingContextId::from_string(&frame_ref) else {
93            // Step 4. If browsing context is null or a top-level browsing context,
94            // return error with error code no such frame.
95            return Err(WebDriverError::new(ErrorStatus::NoSuchFrame, ""));
96        };
97
98        match self.verify_browsing_context_is_open(browsing_context_id) {
99            // Step 5. Return success with data browsing context's associated window.
100            Ok(_) => Ok(format!("window.webdriverFrame(\"{frame_ref}\")")),
101            // Part of Step 4.
102            Err(_) => Err(WebDriverError::new(ErrorStatus::NoSuchFrame, "")),
103        }
104    }
105
106    /// <https://w3c.github.io/webdriver/#dfn-deserialize-a-web-window>
107    fn deserialize_web_window(&self, window: &Value) -> WebDriverResult<String> {
108        // Step 2. Let reference be the result of getting the web window identifier property from object.
109        let window_ref = match window {
110            Value::String(string) => string.clone(),
111            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument, "")),
112        };
113
114        // Step 3. Let browsing context be the browsing context whose window handle is reference,
115        // or null if no such browsing context exists.
116        let (sender, receiver) = ipc::channel().unwrap();
117        self.browsing_context_script_command(
118            WebDriverScriptCommand::GetKnownWindow(window_ref.clone(), sender),
119            VerifyBrowsingContextIsOpen::No,
120        )?;
121
122        // Step 4. If browsing context is null or not a top-level browsing context,
123        // return error with error code no such window.
124        wait_for_ipc_response_flatten(receiver)?;
125        // Step 5. Return success with data browsing context's associated window.
126        Ok(format!("window.webdriverWindow(\"{window_ref}\")"))
127    }
128
129    /// <https://w3c.github.io/webdriver/#dfn-json-deserialize>
130    fn json_deserialize(&self, v: &Value) -> WebDriverResult<String> {
131        let res = match v {
132            Value::Array(list) => {
133                let elems = list
134                    .iter()
135                    .map(|v| self.json_deserialize(v))
136                    .collect::<WebDriverResult<Vec<_>>>()?;
137                format!("[{}]", elems.join(", "))
138            },
139            Value::Object(map) => {
140                if let Some(id) = map.get(ELEMENT_KEY) {
141                    return self.deserialize_web_element(id);
142                }
143                if let Some(id) = map.get(SHADOW_KEY) {
144                    return self.deserialize_shadow_root(id);
145                }
146                if let Some(id) = map.get(FRAME_KEY) {
147                    return self.deserialize_web_frame(id);
148                }
149                if let Some(id) = map.get(WINDOW_KEY) {
150                    return self.deserialize_web_window(id);
151                }
152                let elems = map
153                    .iter()
154                    .map(|(k, v)| {
155                        let key = serde_json::to_string(k)?;
156                        let arg = self.json_deserialize(v)?;
157                        Ok(format!("{key}: {arg}"))
158                    })
159                    .collect::<WebDriverResult<Vec<String>>>()?;
160                format!("{{{}}}", elems.join(", "))
161            },
162            _ => serde_json::to_string(v)?,
163        };
164
165        Ok(res)
166    }
167}