webdriver_server/
session.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 std::collections::HashMap;
6
7use base::id::{BrowsingContextId, WebViewId};
8use rustc_hash::FxHashSet;
9use serde_json::{Map, Value, json};
10use uuid::Uuid;
11use webdriver::error::WebDriverResult;
12
13use crate::Handler;
14use crate::actions::{ActionItem, InputSourceState};
15use crate::capabilities::ServoCapabilities;
16use crate::timeout::{
17    TimeoutsConfiguration, deserialize_as_timeouts_configuration, serialize_timeouts_configuration,
18};
19use crate::user_prompt::{
20    UserPromptHandler, default_unhandled_prompt_behavior, deserialize_unhandled_prompt_behaviour,
21};
22
23#[derive(Debug, Clone, PartialEq, serde::Serialize)]
24pub(crate) enum PageLoadStrategy {
25    None,
26    Eager,
27    Normal,
28}
29
30// Need a different implementation for ToString than Display
31#[allow(clippy::to_string_trait_impl)]
32impl ToString for PageLoadStrategy {
33    fn to_string(&self) -> String {
34        match self {
35            PageLoadStrategy::None => String::from("none"),
36            PageLoadStrategy::Eager => String::from("eager"),
37            PageLoadStrategy::Normal => String::from("normal"),
38        }
39    }
40}
41
42/// Represents the current WebDriver session and holds relevant session state.
43/// Currently, only 1 webview is supported per session.
44/// So only there is only 1 InputState.
45pub(crate) struct WebDriverSession {
46    /// <https://www.w3.org/TR/webdriver2/#dfn-session-id>
47    id: Uuid,
48
49    /// <https://www.w3.org/TR/webdriver2/#dfn-current-top-level-browsing-context>
50    /// The id of the current top-level browsing context
51    webview_id: Option<WebViewId>,
52
53    /// <https://www.w3.org/TR/webdriver2/#dfn-current-browsing-context>
54    /// The id of the current browsing context
55    browsing_context_id: Option<BrowsingContextId>,
56
57    timeouts: TimeoutsConfiguration,
58
59    page_loading_strategy: PageLoadStrategy,
60
61    strict_file_interactability: bool,
62
63    /// <https://w3c.github.io/webdriver/#dfn-user-prompt-handler>
64    user_prompt_handler: UserPromptHandler,
65
66    /// <https://w3c.github.io/webdriver/#dfn-input-state-map>
67    pub(crate) input_state_table: HashMap<String, InputSourceState>,
68
69    /// <https://w3c.github.io/webdriver/#dfn-input-cancel-list>
70    pub(crate) input_cancel_list: Vec<(String, ActionItem)>,
71}
72
73impl WebDriverSession {
74    pub(crate) fn new() -> WebDriverSession {
75        WebDriverSession {
76            id: Uuid::new_v4(),
77            webview_id: None,
78            browsing_context_id: None,
79            timeouts: TimeoutsConfiguration::default(),
80            page_loading_strategy: PageLoadStrategy::Normal,
81            strict_file_interactability: false,
82            user_prompt_handler: UserPromptHandler::new(),
83            input_state_table: Default::default(),
84            input_cancel_list: Default::default(),
85        }
86    }
87
88    pub(crate) fn set_webview_id(&mut self, webview_id: WebViewId) {
89        self.webview_id = Some(webview_id);
90    }
91
92    pub(crate) fn set_browsing_context_id(&mut self, browsing_context_id: BrowsingContextId) {
93        self.browsing_context_id = Some(browsing_context_id);
94    }
95
96    pub(crate) fn current_webview_id(&self) -> Option<WebViewId> {
97        self.webview_id
98    }
99
100    pub(crate) fn current_browsing_context_id(&self) -> Option<BrowsingContextId> {
101        self.browsing_context_id
102    }
103
104    pub(crate) fn session_timeouts(&self) -> &TimeoutsConfiguration {
105        &self.timeouts
106    }
107
108    pub(crate) fn session_timeouts_mut(&mut self) -> &mut TimeoutsConfiguration {
109        &mut self.timeouts
110    }
111
112    pub(crate) fn page_loading_strategy(&self) -> PageLoadStrategy {
113        self.page_loading_strategy.clone()
114    }
115
116    pub(crate) fn strict_file_interactability(&self) -> bool {
117        self.strict_file_interactability
118    }
119
120    pub(crate) fn user_prompt_handler(&self) -> &UserPromptHandler {
121        &self.user_prompt_handler
122    }
123
124    pub(crate) fn pointer_ids(&self) -> FxHashSet<u32> {
125        self.input_state_table
126            .values()
127            .filter_map(|source| match source {
128                InputSourceState::Pointer(pointer_state) => Some(pointer_state.pointer_id),
129                _ => None,
130            })
131            .collect()
132    }
133}
134
135impl Handler {
136    /// <https://w3c.github.io/webdriver/#dfn-create-a-session>
137    pub(crate) fn create_session(
138        &mut self,
139        capabilities: &mut Map<String, Value>,
140        servo_capabilities: &ServoCapabilities,
141    ) -> WebDriverResult<Uuid> {
142        // Step 2. Let session be a new session
143        let mut session = WebDriverSession::new();
144
145        // Step 3. Let proxy be the result of getting property "proxy" from capabilities
146        match capabilities.get("proxy") {
147            // Proxy is a proxy configuration object
148            Some(_) => {
149                // TODO:
150                // Take implementation-defined steps to set the user agent proxy
151                // using the extracted proxy configuration.
152                // If the defined proxy cannot be configured return error with error code
153                // session not created. Otherwise set the has proxy configuration flag to true.
154            },
155            // Otherwise, set a property of capabilities with name "proxy"
156            // and a value that is a new JSON Object.
157            None => {
158                capabilities.insert(String::from("proxy"), json!({}));
159            },
160        }
161
162        // Step 4. If capabilites has a property named "acceptInsecureCerts"
163        match capabilities.get("acceptInsecureCerts") {
164            Some(_accept_insecure_certs) => {
165                // TODO: Set the endpoint node's accept insecure TLS flag
166            },
167            None => {
168                capabilities.insert(String::from("acceptInsecureCerts"), json!(false));
169            },
170        }
171
172        // Step 5. Let user prompt handler capability be the result of
173        // getting property "unhandledPromptBehavior" from capabilities
174        match capabilities.get("unhandledPromptBehavior") {
175            // Step 6. If user prompt handler capability is not undefined
176            Some(unhandled_prompt_behavior) => {
177                session.user_prompt_handler =
178                    deserialize_unhandled_prompt_behaviour(unhandled_prompt_behavior.clone())?;
179            },
180            // Step 7. Let serialized user prompt handler be serialize the user prompt handler.
181            // Step 8. Set a property on capabilities with the name "unhandledPromptBehavior",
182            // and the value serialized user prompt handler.
183            // Ignore because the user prompt handler is already in the capabilities object
184            None => {
185                capabilities.insert(
186                    String::from("unhandledPromptBehavior"),
187                    json!(default_unhandled_prompt_behavior()),
188                );
189            },
190        }
191
192        // TODO: flag is http by default for now
193        // Step 9. If flags contains "http"
194        // Step 9.1. Let strategy be the result of getting property "pageLoadStrategy" from capabilities.
195        match capabilities.get("pageLoadStrategy") {
196            // If strategy is a string, set the session's page loading strategy to strategy.
197            Some(strategy) => match strategy.to_string().as_str() {
198                "none" => session.page_loading_strategy = PageLoadStrategy::None,
199                "eager" => session.page_loading_strategy = PageLoadStrategy::Eager,
200                _ => session.page_loading_strategy = PageLoadStrategy::Normal,
201            },
202            // Otherwise, set the page loading strategy to normal and set a property of capabilities
203            // with name "pageLoadStrategy" and value "normal".
204            None => {
205                capabilities.insert(
206                    String::from("pageLoadStrategy"),
207                    json!(session.page_loading_strategy.to_string()),
208                );
209                session.page_loading_strategy = PageLoadStrategy::Normal;
210            },
211        }
212
213        // Step 9.2. Let strictFileInteractability be the result of getting property
214        // "strictFileInteractability" from capabilities
215        if let Some(Value::Bool(strict_file_interactability)) =
216            capabilities.get("strictFileInteractability")
217        {
218            session.strict_file_interactability = *strict_file_interactability;
219        } else {
220            capabilities.insert(String::from("strictFileInteractability"), json!(false));
221        }
222
223        // Step 9.3. Let timeouts be the result of getting a property "timeouts" from capabilities.
224        // If timeouts is not undefined, set session's session timeouts to timeouts.
225        if let Some(timeouts) = capabilities.get("timeouts") {
226            session.timeouts = deserialize_as_timeouts_configuration(timeouts)?;
227        }
228
229        // Step 9.4 Set a property on capabilities with name "timeouts"
230        // and value serialize the timeouts configuration with session's session timeouts.
231        capabilities.insert(
232            "timeouts".to_string(),
233            json!(serialize_timeouts_configuration(&session.timeouts)),
234        );
235
236        // Step 10. Process any extension capabilities in capabilities in an implementation-defined manner
237        // Nothing to processed
238
239        // Step 11. Run any WebDriver new session algorithm defined in external specifications
240        capabilities.insert(
241            "browserName".to_string(),
242            json!(servo_capabilities.browser_name),
243        );
244        capabilities.insert(
245            "browserVersion".to_string(),
246            json!(servo_capabilities.browser_version),
247        );
248        capabilities.insert(
249            "platformName".to_string(),
250            json!(
251                servo_capabilities
252                    .platform_name
253                    .clone()
254                    .unwrap_or("unknown".to_string())
255            ),
256        );
257        capabilities.insert(
258            "setWindowRect".to_string(),
259            json!(servo_capabilities.set_window_rect),
260        );
261        capabilities.insert(
262            "userAgent".to_string(),
263            servo_config::pref!(user_agent).into(),
264        );
265
266        // Step 12. Append session to active sessions
267        let id = session.id;
268        self.session = Some(session);
269
270        Ok(id)
271    }
272}