webdriver_server/
lib.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
5#![crate_name = "webdriver_server"]
6#![crate_type = "rlib"]
7#![deny(unsafe_code)]
8
9mod actions;
10mod capabilities;
11mod script_argument_extraction;
12mod session;
13mod timeout;
14mod user_prompt;
15
16use std::borrow::ToOwned;
17use std::cell::{Cell, LazyCell, RefCell};
18use std::collections::{BTreeMap, HashMap};
19use std::io::Cursor;
20use std::net::{SocketAddr, SocketAddrV4};
21use std::thread::sleep;
22use std::time::{Duration, Instant};
23use std::{env, fmt, process, thread};
24
25use base::generic_channel::{self, GenericSender, RoutedReceiver};
26use base::id::{BrowsingContextId, WebViewId};
27use base64::Engine;
28use capabilities::ServoCapabilities;
29use cookie::{CookieBuilder, Expiration, SameSite};
30use crossbeam_channel::{Receiver, RecvTimeoutError, Sender, after, select, unbounded};
31use embedder_traits::{
32    EventLoopWaker, ImeEvent, InputEvent, JSValue, JavaScriptEvaluationError,
33    JavaScriptEvaluationResultSerializationError, MouseButton, WebDriverCommandMsg,
34    WebDriverFrameId, WebDriverJSResult, WebDriverLoadStatus, WebDriverScriptCommand,
35};
36use euclid::{Point2D, Rect, Size2D};
37use http::method::Method;
38use image::{DynamicImage, ImageFormat};
39use ipc_channel::ipc::{self, IpcReceiver, TryRecvError};
40use keyboard_types::webdriver::{Event as DispatchStringEvent, KeyInputState, send_keys};
41use keyboard_types::{Code, Key, KeyState, KeyboardEvent, Location, NamedKey};
42use log::{debug, error, info};
43use serde::de::{Deserializer, MapAccess, Visitor};
44use serde::ser::Serializer;
45use serde::{Deserialize, Serialize};
46use serde_json::{Value, json};
47use servo_config::prefs::{self, PrefValue, Preferences};
48use servo_geometry::DeviceIndependentIntRect;
49use servo_url::ServoUrl;
50use style_traits::CSSPixel;
51use time::OffsetDateTime;
52use uuid::Uuid;
53use webdriver::actions::{
54    ActionSequence, ActionsType, KeyAction, KeyActionItem, KeyDownAction, KeyUpAction,
55    PointerAction, PointerActionItem, PointerActionParameters, PointerDownAction,
56    PointerMoveAction, PointerOrigin, PointerType, PointerUpAction,
57};
58use webdriver::capabilities::CapabilitiesMatching;
59use webdriver::command::{
60    ActionsParameters, AddCookieParameters, GetParameters, JavascriptCommandParameters,
61    LocatorParameters, NewSessionParameters, NewWindowParameters, SendKeysParameters,
62    SwitchToFrameParameters, SwitchToWindowParameters, TimeoutsParameters, WebDriverCommand,
63    WebDriverExtensionCommand, WebDriverMessage, WindowRectParameters,
64};
65use webdriver::common::{
66    Cookie, Date, LocatorStrategy, Parameters, ShadowRoot, WebElement, WebFrame, WebWindow,
67};
68use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
69use webdriver::httpapi::WebDriverExtensionRoute;
70use webdriver::response::{
71    CloseWindowResponse, CookieResponse, CookiesResponse, ElementRectResponse, NewSessionResponse,
72    NewWindowResponse, TimeoutsResponse, ValueResponse, WebDriverResponse, WindowRectResponse,
73};
74use webdriver::server::{self, Session, SessionTeardownKind, WebDriverHandler};
75
76use crate::actions::{InputSourceState, PointerInputState};
77use crate::session::{PageLoadStrategy, WebDriverSession};
78use crate::timeout::{DEFAULT_PAGE_LOAD_TIMEOUT, SCREENSHOT_TIMEOUT};
79
80fn extension_routes() -> Vec<(Method, &'static str, ServoExtensionRoute)> {
81    vec![
82        (
83            Method::GET,
84            "/session/{sessionId}/servo/prefs/get",
85            ServoExtensionRoute::GetPrefs,
86        ),
87        (
88            Method::POST,
89            "/session/{sessionId}/servo/prefs/set",
90            ServoExtensionRoute::SetPrefs,
91        ),
92        (
93            Method::POST,
94            "/session/{sessionId}/servo/prefs/reset",
95            ServoExtensionRoute::ResetPrefs,
96        ),
97        (
98            Method::DELETE,
99            "/session/{sessionId}/servo/shutdown",
100            ServoExtensionRoute::Shutdown,
101        ),
102    ]
103}
104
105fn cookie_msg_to_cookie(cookie: cookie::Cookie) -> Cookie {
106    Cookie {
107        name: cookie.name().to_owned(),
108        value: cookie.value().to_owned(),
109        path: cookie.path().map(|s| s.to_owned()),
110        domain: cookie.domain().map(|s| s.to_owned()),
111        expiry: cookie.expires().and_then(|expiration| match expiration {
112            Expiration::DateTime(date_time) => Some(Date(date_time.unix_timestamp() as u64)),
113            Expiration::Session => None,
114        }),
115        secure: cookie.secure().unwrap_or(false),
116        http_only: cookie.http_only().unwrap_or(false),
117        same_site: cookie.same_site().map(|s| s.to_string()),
118    }
119}
120
121pub fn start_server(
122    port: u16,
123    embedder_sender: Sender<WebDriverCommandMsg>,
124    event_loop_waker: Box<dyn EventLoopWaker>,
125) {
126    let handler = Handler::new(embedder_sender, event_loop_waker);
127
128    thread::Builder::new()
129        .name("WebDriverHttpServer".to_owned())
130        .spawn(move || {
131            let address = SocketAddrV4::new("0.0.0.0".parse().unwrap(), port);
132            match server::start(
133                SocketAddr::V4(address),
134                vec![],
135                vec![],
136                handler,
137                extension_routes(),
138            ) {
139                Ok(listening) => info!("WebDriver server listening on {}", listening.socket),
140                Err(e) => panic!("Unable to start WebDriver HTTP server {e:?}"),
141            }
142        })
143        .expect("Thread spawning failed");
144}
145
146struct Handler {
147    /// The threaded receiver on which we can block for a load-status.
148    /// It will receive messages sent on the load_status_sender,
149    /// and forwarded by the IPC router.
150    load_status_receiver: RoutedReceiver<WebDriverLoadStatus>,
151    /// The IPC sender which we can clone and pass along to the constellation,
152    /// for it to send us a load-status. Messages sent on it
153    /// will be forwarded to the load_status_receiver.
154    load_status_sender: GenericSender<WebDriverLoadStatus>,
155
156    session: Option<WebDriverSession>,
157
158    /// A [`Sender`] that sends messages to the embedder that this `WebDriver instance controls.
159    /// In addition to sending a message, we must always wake up the embedder's event loop so it
160    /// knows that more messages are available for processing.
161    embedder_sender: Sender<WebDriverCommandMsg>,
162
163    /// An [`EventLoopWaker`] which is used to wake up the embedder event loop.
164    event_loop_waker: Box<dyn EventLoopWaker>,
165
166    /// A list of [`Receiver`]s that are used to track when input events are handled in the DOM.
167    /// Once these receivers receive a response, we know that the event has been handled.
168    ///
169    /// TODO: Once we upgrade crossbeam-channel this can be replaced with a `WaitGroup`.
170    pending_input_event_receivers: RefCell<Vec<Receiver<()>>>,
171
172    /// Number of pending actions of which WebDriver is waiting for responses.
173    num_pending_actions: Cell<u32>,
174}
175
176#[derive(Clone, Copy, Debug, PartialEq)]
177#[allow(clippy::enum_variant_names)]
178enum ServoExtensionRoute {
179    GetPrefs,
180    SetPrefs,
181    ResetPrefs,
182    /// TODO: Shutdown does not actually use sessionId.
183    /// But the webdriver crate always checks existence of sessionID
184    /// except for WebDriverCommand::Status.
185    /// We have to either use our own fork, or relies on the current workaround:
186    /// passing any dummy sessionID.
187    Shutdown,
188}
189
190impl WebDriverExtensionRoute for ServoExtensionRoute {
191    type Command = ServoExtensionCommand;
192
193    fn command(
194        &self,
195        _parameters: &Parameters,
196        body_data: &Value,
197    ) -> WebDriverResult<WebDriverCommand<ServoExtensionCommand>> {
198        let command = match *self {
199            ServoExtensionRoute::GetPrefs => {
200                let parameters: GetPrefsParameters = serde_json::from_value(body_data.clone())?;
201                ServoExtensionCommand::GetPrefs(parameters)
202            },
203            ServoExtensionRoute::SetPrefs => {
204                let parameters: SetPrefsParameters = serde_json::from_value(body_data.clone())?;
205                ServoExtensionCommand::SetPrefs(parameters)
206            },
207            ServoExtensionRoute::ResetPrefs => {
208                let parameters: GetPrefsParameters = serde_json::from_value(body_data.clone())?;
209                ServoExtensionCommand::ResetPrefs(parameters)
210            },
211            ServoExtensionRoute::Shutdown => ServoExtensionCommand::Shutdown,
212        };
213        Ok(WebDriverCommand::Extension(command))
214    }
215}
216
217#[derive(Clone, Debug)]
218#[allow(clippy::enum_variant_names)]
219enum ServoExtensionCommand {
220    GetPrefs(GetPrefsParameters),
221    SetPrefs(SetPrefsParameters),
222    ResetPrefs(GetPrefsParameters),
223    Shutdown,
224}
225
226impl WebDriverExtensionCommand for ServoExtensionCommand {
227    fn parameters_json(&self) -> Option<Value> {
228        match *self {
229            ServoExtensionCommand::GetPrefs(ref x) => serde_json::to_value(x).ok(),
230            ServoExtensionCommand::SetPrefs(ref x) => serde_json::to_value(x).ok(),
231            ServoExtensionCommand::ResetPrefs(ref x) => serde_json::to_value(x).ok(),
232            ServoExtensionCommand::Shutdown => None,
233        }
234    }
235}
236
237#[derive(Clone)]
238struct SendableJSValue(JSValue);
239
240impl Serialize for SendableJSValue {
241    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
242    where
243        S: Serializer,
244    {
245        match self.0 {
246            JSValue::Undefined | JSValue::Null => serializer.serialize_unit(),
247            JSValue::Boolean(x) => serializer.serialize_bool(x),
248            JSValue::Number(x) => {
249                if x.fract() == 0.0 {
250                    serializer.serialize_i64(x as i64)
251                } else {
252                    serializer.serialize_f64(x)
253                }
254            },
255            JSValue::String(ref x) => serializer.serialize_str(x),
256            JSValue::Element(ref x) => WebElement(x.clone()).serialize(serializer),
257            JSValue::ShadowRoot(ref x) => ShadowRoot(x.clone()).serialize(serializer),
258            JSValue::Frame(ref x) => WebFrame(x.clone()).serialize(serializer),
259            JSValue::Window(ref x) => WebWindow(x.clone()).serialize(serializer),
260            JSValue::Array(ref x) => x
261                .iter()
262                .map(|element| SendableJSValue(element.clone()))
263                .collect::<Vec<SendableJSValue>>()
264                .serialize(serializer),
265            JSValue::Object(ref x) => x
266                .iter()
267                .map(|(k, v)| (k.clone(), SendableJSValue(v.clone())))
268                .collect::<HashMap<String, SendableJSValue>>()
269                .serialize(serializer),
270        }
271    }
272}
273
274#[derive(Clone, Debug, PartialEq)]
275struct WebDriverPrefValue(PrefValue);
276
277impl Serialize for WebDriverPrefValue {
278    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
279    where
280        S: Serializer,
281    {
282        match self.0 {
283            PrefValue::Bool(b) => serializer.serialize_bool(b),
284            PrefValue::Str(ref s) => serializer.serialize_str(s),
285            PrefValue::Float(f) => serializer.serialize_f64(f),
286            PrefValue::Int(i) => serializer.serialize_i64(i),
287            PrefValue::Array(ref v) => v
288                .iter()
289                .map(|value| WebDriverPrefValue(value.clone()))
290                .collect::<Vec<WebDriverPrefValue>>()
291                .serialize(serializer),
292        }
293    }
294}
295
296impl<'de> Deserialize<'de> for WebDriverPrefValue {
297    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
298    where
299        D: Deserializer<'de>,
300    {
301        struct Visitor;
302
303        impl ::serde::de::Visitor<'_> for Visitor {
304            type Value = WebDriverPrefValue;
305
306            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
307                formatter.write_str("preference value")
308            }
309
310            fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
311            where
312                E: ::serde::de::Error,
313            {
314                Ok(WebDriverPrefValue(PrefValue::Float(value)))
315            }
316
317            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
318            where
319                E: ::serde::de::Error,
320            {
321                Ok(WebDriverPrefValue(PrefValue::Int(value)))
322            }
323
324            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
325            where
326                E: ::serde::de::Error,
327            {
328                Ok(WebDriverPrefValue(PrefValue::Int(value as i64)))
329            }
330
331            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
332            where
333                E: ::serde::de::Error,
334            {
335                Ok(WebDriverPrefValue(PrefValue::Str(String::from(value))))
336            }
337
338            fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
339            where
340                E: ::serde::de::Error,
341            {
342                Ok(WebDriverPrefValue(PrefValue::Bool(value)))
343            }
344        }
345
346        deserializer.deserialize_any(Visitor)
347    }
348}
349
350#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
351struct GetPrefsParameters {
352    prefs: Vec<String>,
353}
354
355#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
356struct SetPrefsParameters {
357    #[serde(deserialize_with = "map_to_vec")]
358    prefs: Vec<(String, WebDriverPrefValue)>,
359}
360
361fn map_to_vec<'de, D>(de: D) -> Result<Vec<(String, WebDriverPrefValue)>, D::Error>
362where
363    D: Deserializer<'de>,
364{
365    de.deserialize_map(TupleVecMapVisitor)
366}
367
368struct TupleVecMapVisitor;
369
370impl<'de> Visitor<'de> for TupleVecMapVisitor {
371    type Value = Vec<(String, WebDriverPrefValue)>;
372
373    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
374        formatter.write_str("a map")
375    }
376
377    #[inline]
378    fn visit_unit<E>(self) -> Result<Self::Value, E> {
379        Ok(Vec::new())
380    }
381
382    #[inline]
383    fn visit_map<T>(self, mut access: T) -> Result<Self::Value, T::Error>
384    where
385        T: MapAccess<'de>,
386    {
387        let mut values = Vec::new();
388
389        while let Some((key, value)) = access.next_entry()? {
390            values.push((key, value));
391        }
392
393        Ok(values)
394    }
395}
396
397enum VerifyBrowsingContextIsOpen {
398    Yes,
399    No,
400}
401
402impl Handler {
403    fn new(
404        embedder_sender: Sender<WebDriverCommandMsg>,
405        event_loop_waker: Box<dyn EventLoopWaker>,
406    ) -> Handler {
407        // Create a pair of both an IPC and a threaded channel,
408        // keep the IPC sender to clone and pass to the constellation for each load,
409        // and keep a threaded receiver to block on an incoming load-status.
410        // Pass the others to the IPC router so that IPC messages are forwarded to the threaded receiver.
411        // We need to use the router because IPC does not come with a timeout on receive/select.
412        let (load_status_sender, receiver) = generic_channel::channel().unwrap();
413        let load_status_receiver = receiver.route_preserving_errors();
414
415        Handler {
416            load_status_sender,
417            load_status_receiver,
418            session: None,
419            embedder_sender,
420            event_loop_waker,
421            pending_input_event_receivers: Default::default(),
422            num_pending_actions: Cell::new(0),
423        }
424    }
425
426    fn browsing_context_id(&self) -> WebDriverResult<BrowsingContextId> {
427        self.session()?
428            .current_browsing_context_id()
429            .ok_or_else(|| {
430                WebDriverError::new(ErrorStatus::UnknownError, "No browsing context available")
431            })
432    }
433
434    fn webview_id(&self) -> WebDriverResult<WebViewId> {
435        self.session()?
436            .current_webview_id()
437            .ok_or_else(|| WebDriverError::new(ErrorStatus::UnknownError, "No webview available"))
438    }
439
440    fn send_input_event_to_embedder(&self, input_event: InputEvent) {
441        let _ = self.send_message_to_embedder(WebDriverCommandMsg::InputEvent(
442            self.verified_webview_id(),
443            input_event,
444            None,
445        ));
446    }
447
448    fn send_blocking_input_event_to_embedder(&self, input_event: InputEvent) {
449        let (result_sender, result_receiver) = unbounded();
450        if self
451            .send_message_to_embedder(WebDriverCommandMsg::InputEvent(
452                self.verified_webview_id(),
453                input_event,
454                Some(result_sender),
455            ))
456            .is_ok()
457        {
458            self.pending_input_event_receivers
459                .borrow_mut()
460                .push(result_receiver);
461        }
462    }
463
464    fn send_message_to_embedder(&self, msg: WebDriverCommandMsg) -> WebDriverResult<()> {
465        self.embedder_sender.send(msg).map_err(|_| {
466            WebDriverError::new(
467                ErrorStatus::UnknownError,
468                "Failed to send message to embedder",
469            )
470        })?;
471        self.event_loop_waker.wake();
472        Ok(())
473    }
474
475    fn add_load_status_sender(&self) -> WebDriverResult<()> {
476        self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
477            self.browsing_context_id()?,
478            WebDriverScriptCommand::AddLoadStatusSender(
479                self.webview_id()?,
480                self.load_status_sender.clone(),
481            ),
482        ))
483    }
484
485    fn clear_load_status_sender(&self) -> WebDriverResult<()> {
486        self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
487            self.browsing_context_id()?,
488            WebDriverScriptCommand::RemoveLoadStatusSender(self.webview_id()?),
489        ))
490    }
491
492    // This function is called only if session and webview are verified.
493    fn verified_webview_id(&self) -> WebViewId {
494        self.session().unwrap().current_webview_id().unwrap()
495    }
496
497    fn focused_webview_id(&self) -> WebDriverResult<Option<WebViewId>> {
498        let (sender, receiver) = ipc::channel().unwrap();
499        self.send_message_to_embedder(WebDriverCommandMsg::GetFocusedWebView(sender.clone()))?;
500        // Wait until the document is ready before returning the top-level browsing context id.
501        wait_for_ipc_response(receiver)
502    }
503
504    fn session(&self) -> WebDriverResult<&WebDriverSession> {
505        match self.session {
506            Some(ref x) => Ok(x),
507            // https://w3c.github.io/webdriver/#ref-for-dfn-invalid-session-id-1
508            None => Err(WebDriverError::new(
509                ErrorStatus::InvalidSessionId,
510                "Session not created",
511            )),
512        }
513    }
514
515    fn session_mut(&mut self) -> WebDriverResult<&mut WebDriverSession> {
516        match self.session {
517            Some(ref mut x) => Ok(x),
518            // https://w3c.github.io/webdriver/#ref-for-dfn-invalid-session-id-1
519            None => Err(WebDriverError::new(
520                ErrorStatus::InvalidSessionId,
521                "Session not created",
522            )),
523        }
524    }
525
526    /// <https://w3c.github.io/webdriver/#new-session>
527    fn handle_new_session(
528        &mut self,
529        parameters: &NewSessionParameters,
530    ) -> WebDriverResult<WebDriverResponse> {
531        if let Ok(value) = env::var("DELAY_AFTER_ACCEPT") {
532            let seconds = value.parse::<u64>().unwrap_or_default();
533            println!("Waiting for {} seconds...", seconds);
534            println!("lldb -p {}", process::id());
535            thread::sleep(Duration::from_secs(seconds));
536        }
537
538        // Step 1. If the list of active HTTP sessions is not empty
539        // return error with error code session not created.
540        if self.session.is_some() {
541            return Err(WebDriverError::new(
542                ErrorStatus::SessionNotCreated,
543                "Session already created",
544            ));
545        }
546
547        // Step 2. Skip because the step is only applied to an intermediary node.
548        // Step 3. Skip since all sessions are http for now.
549
550        // Step 4. Let capabilities be the result of trying to process capabilities
551        let mut servo_capabilities = ServoCapabilities::new();
552        let processed_capabilities = parameters.match_browser(&mut servo_capabilities)?;
553
554        // Step 5. If capabilities's is null, return error with error code session not created.
555        let mut capabilities = match processed_capabilities {
556            Some(capabilities) => capabilities,
557            None => {
558                return Err(WebDriverError::new(
559                    ErrorStatus::SessionNotCreated,
560                    "Session not created due to invalid capabilities",
561                ));
562            },
563        };
564
565        // Step 6. Create a session
566        let session_id = self.create_session(&mut capabilities, &servo_capabilities)?;
567
568        // Step 7. Let response be a JSON Object initialized with session's session ID and capabilities
569        let response = NewSessionResponse::new(session_id.to_string(), Value::Object(capabilities));
570
571        // Step 8. Set session' current top-level browsing context
572        match self.focused_webview_id()? {
573            Some(webview_id) => {
574                self.session_mut()?.set_webview_id(webview_id);
575                self.session_mut()?
576                    .set_browsing_context_id(BrowsingContextId::from(webview_id));
577            },
578            None => {
579                // This happens when there is no open webview.
580                // We need to create a new one. See https://github.com/servo/servo/issues/37408
581                let (sender, receiver) = ipc::channel().unwrap();
582
583                self.send_message_to_embedder(WebDriverCommandMsg::NewWebView(
584                    sender,
585                    Some(self.load_status_sender.clone()),
586                ))?;
587                let webview_id = receiver
588                    .recv()
589                    .expect("IPC failure when creating new webview for new session");
590                self.focus_webview(webview_id)?;
591                self.session_mut()?.set_webview_id(webview_id);
592                self.session_mut()?
593                    .set_browsing_context_id(BrowsingContextId::from(webview_id));
594                let _ = self.wait_document_ready(Some(3000));
595            },
596        };
597
598        // Step 9. Set the request queue to a new queue.
599        // Skip here because the requests are handled in the external crate.
600
601        // Step 10. Return success with data body
602        Ok(WebDriverResponse::NewSession(response))
603    }
604
605    /// <https://w3c.github.io/webdriver/#dfn-delete-session>
606    fn handle_delete_session(&mut self) -> WebDriverResult<WebDriverResponse> {
607        // Step 1. If session is http, close the session
608        self.session = None;
609
610        // Step 2. Return success with data null
611        Ok(WebDriverResponse::DeleteSession)
612    }
613
614    /// <https://w3c.github.io/webdriver/#status>
615    fn handle_status(&self) -> WebDriverResult<WebDriverResponse> {
616        Ok(WebDriverResponse::Generic(ValueResponse(
617            if self.session.is_none() {
618                json!({ "ready": true, "message": "Ready for a new session" })
619            } else {
620                json!({ "ready": false, "message": "Not ready for a new session" })
621            },
622        )))
623    }
624
625    /// Send command to Script Thread with session's current browsing context.
626    /// If `verify` is [`VerifyBrowsingContextIsOpen::Yes`],
627    /// it would verify the existence of browsing context before sending.
628    fn browsing_context_script_command(
629        &self,
630        cmd_msg: WebDriverScriptCommand,
631        verify: VerifyBrowsingContextIsOpen,
632    ) -> WebDriverResult<()> {
633        let browsing_context_id = self.browsing_context_id()?;
634        if let VerifyBrowsingContextIsOpen::Yes = verify {
635            self.verify_browsing_context_is_open(browsing_context_id)?;
636        }
637        self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
638            browsing_context_id,
639            cmd_msg,
640        ))?;
641        Ok(())
642    }
643
644    /// Send command to Script Thread with session's current top-level browsing context.
645    /// If `verify` is [`VerifyBrowsingContextIsOpen::Yes`],
646    /// it would verify the existence of top-level browsing context before sending.
647    fn top_level_script_command(
648        &self,
649        cmd_msg: WebDriverScriptCommand,
650        verify: VerifyBrowsingContextIsOpen,
651    ) -> WebDriverResult<()> {
652        let webview_id = self.webview_id()?;
653        if let VerifyBrowsingContextIsOpen::Yes = verify {
654            self.verify_top_level_browsing_context_is_open(webview_id)?;
655        }
656        let browsing_context_id = BrowsingContextId::from(webview_id);
657        self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
658            browsing_context_id,
659            cmd_msg,
660        ))?;
661        Ok(())
662    }
663
664    /// <https://w3c.github.io/webdriver/#navigate-to>
665    fn handle_get(&mut self, parameters: &GetParameters) -> WebDriverResult<WebDriverResponse> {
666        let webview_id = self.webview_id()?;
667        // Step 2. If session's current top-level browsing context is no longer open,
668        // return error with error code no such window.
669        self.verify_top_level_browsing_context_is_open(webview_id)?;
670        // Step 3. If URL is not an absolute URL or is not an absolute URL with fragment
671        // or not a local scheme, return error with error code invalid argument.
672        let url = ServoUrl::parse(&parameters.url)
673            .map(|url| url.into_url())
674            .map_err(|_| WebDriverError::new(ErrorStatus::InvalidArgument, "Invalid URL"))?;
675
676        // Step 4. Handle any user prompt.
677        self.handle_any_user_prompts(webview_id)?;
678
679        let cmd_msg =
680            WebDriverCommandMsg::LoadUrl(webview_id, url, self.load_status_sender.clone());
681        self.send_message_to_embedder(cmd_msg)?;
682
683        // Step 8.2.1: try to wait for navigation to complete.
684        self.wait_for_navigation_complete()?;
685
686        // Step 8.3. Set current browsing context with session and current top browsing context
687        self.session_mut()?
688            .set_browsing_context_id(BrowsingContextId::from(webview_id));
689
690        Ok(WebDriverResponse::Void)
691    }
692
693    fn wait_document_ready(&self, timeout: Option<u64>) -> WebDriverResult<WebDriverResponse> {
694        let timeout_channel = match timeout {
695            Some(timeout) => after(Duration::from_millis(timeout)),
696            None => crossbeam_channel::never(),
697        };
698
699        select! {
700            recv(self.load_status_receiver) -> res => {
701                match res {
702                    // If the navigation is navigation to IFrame, no document state event is fired.
703                    Ok(Ok(WebDriverLoadStatus::Blocked)) => {
704                        // TODO: evaluate the correctness later
705                        // Load status is block means an user prompt is shown.
706                        // Alot of tests expect this to return success
707                        // then the user prompt is handled in the next command.
708                        // If user prompt can't be handler, next command returns
709                        // an error anyway.
710                        Ok(WebDriverResponse::Void)
711                    },
712                    Ok(Ok(WebDriverLoadStatus::Complete)) |
713                    Ok(Ok(WebDriverLoadStatus::NavigationStop)) =>
714                        Ok(WebDriverResponse::Void)
715                    ,
716                    _ => Err(WebDriverError::new(
717                        ErrorStatus::UnknownError,
718                        "Unexpected load status received while waiting for document ready state",
719                    )),
720                }
721            },
722            recv(timeout_channel) -> _ => Err(
723                WebDriverError::new(ErrorStatus::Timeout, "Load timed out")
724            ),
725        }
726    }
727
728    /// <https://w3c.github.io/webdriver/#dfn-wait-for-navigation-to-complete>
729    fn wait_for_navigation_complete(&self) -> WebDriverResult<WebDriverResponse> {
730        debug!("waiting for load");
731
732        let session = self.session()?;
733
734        // Step 1. If session's page loading strategy is "none",
735        // return success with data null.
736        if session.page_loading_strategy() == PageLoadStrategy::None {
737            return Ok(WebDriverResponse::Void);
738        }
739
740        // Step 2. If session's current browsing context is no longer open,
741        // return success with data null.
742        if self
743            .verify_browsing_context_is_open(self.browsing_context_id()?)
744            .is_err()
745        {
746            return Ok(WebDriverResponse::Void);
747        }
748
749        // Step 3. let timeout be the session's page load timeout.
750        let timeout = session.session_timeouts().page_load;
751
752        // TODO: Step 4. Implement timer parameter
753
754        let result = self.wait_document_ready(timeout);
755        debug!("finished waiting for load with {:?}", result);
756        result
757    }
758
759    /// <https://w3c.github.io/webdriver/#dfn-wait-for-navigation-to-complete>
760    fn wait_for_navigation(&self) -> WebDriverResult<WebDriverResponse> {
761        let navigation_status = match self.load_status_receiver.try_recv() {
762            Ok(Ok(status)) => status,
763            // Empty channel means no navigation started. Nothing to wait for.
764            Err(crossbeam_channel::TryRecvError::Empty) => {
765                return Ok(WebDriverResponse::Void);
766            },
767            Err(crossbeam_channel::TryRecvError::Disconnected) => {
768                return Err(WebDriverError::new(
769                    ErrorStatus::UnknownError,
770                    "Load status channel disconnected",
771                ));
772            },
773            Ok(Err(ipc_error)) => {
774                return Err(WebDriverError::new(
775                    ErrorStatus::UnknownError,
776                    format!("Load status channel ipc error: {ipc_error}"),
777                ));
778            },
779        };
780
781        match navigation_status {
782            WebDriverLoadStatus::NavigationStart => self.wait_for_navigation_complete(),
783            // If the load status is timeout, return an error
784            WebDriverLoadStatus::Timeout => Err(WebDriverError::new(
785                ErrorStatus::Timeout,
786                "Navigation timed out",
787            )),
788            // If the load status is blocked, it means a user prompt is shown.
789            // We should handle the user prompt in the next command.
790            WebDriverLoadStatus::Blocked => Ok(WebDriverResponse::Void),
791            WebDriverLoadStatus::NavigationStop | WebDriverLoadStatus::Complete => {
792                unreachable!("Unexpected load status received")
793            },
794        }
795    }
796
797    /// <https://w3c.github.io/webdriver/#dfn-get-current-url>
798    fn handle_current_url(&self) -> WebDriverResult<WebDriverResponse> {
799        let webview_id = self.webview_id()?;
800
801        // Step 1. If session's current top-level browsing context is no longer open,
802        // return error with error code no such window.
803        self.verify_top_level_browsing_context_is_open(webview_id)?;
804
805        // Step 2. Handle any user prompt.
806        self.handle_any_user_prompts(webview_id)?;
807
808        let (sender, receiver) = ipc::channel().unwrap();
809        self.top_level_script_command(
810            WebDriverScriptCommand::GetUrl(sender),
811            VerifyBrowsingContextIsOpen::No,
812        )?;
813
814        let url = wait_for_ipc_response(receiver)?;
815
816        Ok(WebDriverResponse::Generic(ValueResponse(
817            serde_json::to_value(url)?,
818        )))
819    }
820
821    /// <https://w3c.github.io/webdriver/#get-window-rect>
822    fn handle_window_rect(
823        &self,
824        verify: VerifyBrowsingContextIsOpen,
825    ) -> WebDriverResult<WebDriverResponse> {
826        let (sender, receiver) = ipc::channel().unwrap();
827        let webview_id = self.webview_id()?;
828        // Step 1. If session's current top-level browsing context is no longer open,
829        // return error with error code no such window.
830        if let VerifyBrowsingContextIsOpen::Yes = verify {
831            self.verify_top_level_browsing_context_is_open(webview_id)?;
832        }
833
834        // Step 2. Handle any user prompt.
835        self.handle_any_user_prompts(webview_id)?;
836
837        self.send_message_to_embedder(WebDriverCommandMsg::GetWindowRect(webview_id, sender))?;
838
839        let window_rect = wait_for_ipc_response(receiver)?;
840        let window_size_response = WindowRectResponse {
841            x: window_rect.min.x,
842            y: window_rect.min.y,
843            width: window_rect.width(),
844            height: window_rect.height(),
845        };
846        Ok(WebDriverResponse::WindowRect(window_size_response))
847    }
848
849    /// <https://w3c.github.io/webdriver/#set-window-rect>
850    fn handle_set_window_rect(
851        &self,
852        params: &WindowRectParameters,
853    ) -> WebDriverResult<WebDriverResponse> {
854        // Step 9 - 10. Input Validation. Already done when deserialize.
855
856        // Step 11. In case the Set Window Rect command is partially supported
857        // (i.e. some combinations of arguments are supported but not others),
858        // the implmentation is expected to continue with the remaining steps.
859        // DO NOT return "unsupported operation".
860
861        let webview_id = self.webview_id()?;
862        // Step 12. If session's current top-level browsing context is no longer open,
863        // return error with error code no such window.
864        self.verify_top_level_browsing_context_is_open(webview_id)?;
865
866        // Step 13. Handle any user prompt.
867        self.handle_any_user_prompts(webview_id)?;
868
869        // (TODO) Step 14. Fully exit fullscreen.
870        // (TODO) Step 15. Restore the window.
871
872        let current = LazyCell::new(|| {
873            let WebDriverResponse::WindowRect(current) = self
874                .handle_window_rect(VerifyBrowsingContextIsOpen::No)
875                .unwrap()
876            else {
877                unreachable!("handle_window_size() must return WindowRect");
878            };
879            current
880        });
881
882        let (x, y, width, height) = (
883            params.x.unwrap_or_else(|| current.x),
884            params.y.unwrap_or_else(|| current.y),
885            params.width.unwrap_or_else(|| current.width),
886            params.height.unwrap_or_else(|| current.height),
887        );
888        let (sender, receiver) = ipc::channel().unwrap();
889        // Step 16 - 17. Set the width/height in CSS pixels.
890        // This should be done as long as one of width/height is not null.
891
892        // Step 18 - 19. Set the screen x/y in CSS pixels.
893        // This should be done as long as one of width/height is not null.
894        self.send_message_to_embedder(WebDriverCommandMsg::SetWindowRect(
895            webview_id,
896            DeviceIndependentIntRect::from_origin_and_size(
897                Point2D::new(x, y),
898                Size2D::new(width, height),
899            ),
900            sender,
901        ))?;
902
903        let window_rect = wait_for_ipc_response(receiver)?;
904        debug!("Result window_rect: {window_rect:?}");
905        let window_size_response = WindowRectResponse {
906            x: window_rect.min.x,
907            y: window_rect.min.y,
908            width: window_rect.width(),
909            height: window_rect.height(),
910        };
911        Ok(WebDriverResponse::WindowRect(window_size_response))
912    }
913
914    /// <https://w3c.github.io/webdriver/#maximize-window>
915    fn handle_maximize_window(&mut self) -> WebDriverResult<WebDriverResponse> {
916        // Step 1. If the remote end does not support the Maximize Window command for session's
917        // current top-level browsing context for any reason,
918        // return error with error code unsupported operation.
919        let webview_id = self.webview_id()?;
920        // Step 2. If session's current top-level browsing context is no longer open,
921        // return error with error code no such window.
922        self.verify_top_level_browsing_context_is_open(webview_id)?;
923
924        // Step 3. Try to handle any user prompts with session.
925        self.handle_any_user_prompts(self.webview_id()?)?;
926
927        // Step 4. (TODO) Fully exit fullscreen.
928
929        // Step 5. (TODO) Restore the window.
930
931        // Step 6. Maximize the window of session's current top-level browsing context.
932        let (sender, receiver) = ipc::channel().unwrap();
933        self.send_message_to_embedder(WebDriverCommandMsg::MaximizeWebView(webview_id, sender))?;
934
935        let window_rect = wait_for_ipc_response(receiver)?;
936        debug!("Result window_rect: {window_rect:?}");
937        let window_size_response = WindowRectResponse {
938            x: window_rect.min.x,
939            y: window_rect.min.y,
940            width: window_rect.width(),
941            height: window_rect.height(),
942        };
943        Ok(WebDriverResponse::WindowRect(window_size_response))
944    }
945
946    fn handle_is_enabled(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
947        // Step 1. If session's current browsing context is no longer open,
948        // return error with error code no such window.
949        let browsing_context = self.browsing_context_id()?;
950        self.verify_browsing_context_is_open(browsing_context)?;
951
952        // Step 2. Try to handle any user prompts with session.
953        let webview_id = self.webview_id()?;
954        self.handle_any_user_prompts(webview_id)?;
955
956        let (sender, receiver) = ipc::channel().unwrap();
957        self.browsing_context_script_command(
958            WebDriverScriptCommand::IsEnabled(element.to_string(), sender),
959            VerifyBrowsingContextIsOpen::No,
960        )?;
961
962        Ok(WebDriverResponse::Generic(ValueResponse(
963            serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
964        )))
965    }
966
967    fn handle_is_selected(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
968        // Step 1. If session's current browsing context is no longer open,
969        // return error with error code no such window.
970        let browsing_context = self.browsing_context_id()?;
971        self.verify_browsing_context_is_open(browsing_context)?;
972
973        // Step 2. Try to handle any user prompts with session.
974        let webview_id = self.webview_id()?;
975        self.handle_any_user_prompts(webview_id)?;
976
977        let (sender, receiver) = ipc::channel().unwrap();
978        self.browsing_context_script_command(
979            WebDriverScriptCommand::IsSelected(element.to_string(), sender),
980            VerifyBrowsingContextIsOpen::No,
981        )?;
982
983        Ok(WebDriverResponse::Generic(ValueResponse(
984            serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
985        )))
986    }
987
988    /// <https://w3c.github.io/webdriver/#back>
989    fn handle_go_back(&self) -> WebDriverResult<WebDriverResponse> {
990        let webview_id = self.webview_id()?;
991        // Step 1. If session's current top-level browsing context is no longer open,
992        // return error with error code no such window.
993        self.verify_top_level_browsing_context_is_open(webview_id)?;
994
995        // Step 2. Handle any user prompt.
996        self.handle_any_user_prompts(webview_id)?;
997
998        self.send_message_to_embedder(WebDriverCommandMsg::GoBack(
999            webview_id,
1000            self.load_status_sender.clone(),
1001        ))?;
1002        self.wait_for_navigation_complete()
1003    }
1004
1005    /// <https://w3c.github.io/webdriver/#forward>
1006    fn handle_go_forward(&self) -> WebDriverResult<WebDriverResponse> {
1007        let webview_id = self.webview_id()?;
1008        // Step 1. If session's current top-level browsing context is no longer open,
1009        // return error with error code no such window.
1010        self.verify_top_level_browsing_context_is_open(webview_id)?;
1011
1012        // Step 2. Handle any user prompt.
1013        self.handle_any_user_prompts(webview_id)?;
1014
1015        self.send_message_to_embedder(WebDriverCommandMsg::GoForward(
1016            webview_id,
1017            self.load_status_sender.clone(),
1018        ))?;
1019        self.wait_for_navigation_complete()
1020    }
1021
1022    /// <https://w3c.github.io/webdriver/#refresh>
1023    fn handle_refresh(&mut self) -> WebDriverResult<WebDriverResponse> {
1024        let webview_id = self.webview_id()?;
1025        // Step 1. If session's current top-level browsing context is no longer open,
1026        // return error with error code no such window.
1027        self.verify_top_level_browsing_context_is_open(webview_id)?;
1028
1029        // Step 2. Handle any user prompt.
1030        self.handle_any_user_prompts(webview_id)?;
1031
1032        let cmd_msg = WebDriverCommandMsg::Refresh(webview_id, self.load_status_sender.clone());
1033        self.send_message_to_embedder(cmd_msg)?;
1034
1035        // Step 4.1: Try to wait for navigation to complete.
1036        self.wait_for_navigation_complete()?;
1037
1038        // Step 5. Set current browsing context with session and current top browsing context.
1039        self.session_mut()?
1040            .set_browsing_context_id(BrowsingContextId::from(webview_id));
1041
1042        Ok(WebDriverResponse::Void)
1043    }
1044
1045    /// <https://w3c.github.io/webdriver/#get-title>
1046    fn handle_title(&self) -> WebDriverResult<WebDriverResponse> {
1047        let webview_id = self.webview_id()?;
1048
1049        // Step 1. If session's current top-level browsing context is no longer open,
1050        // return error with error code no such window.
1051        self.verify_top_level_browsing_context_is_open(webview_id)?;
1052        // Step 2. Handle any user prompt.
1053        self.handle_any_user_prompts(webview_id)?;
1054
1055        let (sender, receiver) = ipc::channel().unwrap();
1056
1057        self.top_level_script_command(
1058            WebDriverScriptCommand::GetTitle(sender),
1059            VerifyBrowsingContextIsOpen::No,
1060        )?;
1061
1062        // Step 3. Let title be the session's current top-level
1063        // browsing context's active document's title.
1064        let title = wait_for_ipc_response(receiver)?;
1065        Ok(WebDriverResponse::Generic(ValueResponse(
1066            serde_json::to_value(title)?,
1067        )))
1068    }
1069
1070    /// <https://w3c.github.io/webdriver/#get-window-handle>
1071    fn handle_window_handle(&mut self) -> WebDriverResult<WebDriverResponse> {
1072        let webview_id = self.webview_id()?;
1073
1074        // Step 1. If session's current top-level browsing context is no longer open,
1075        // return error with error code no such window.
1076        self.verify_top_level_browsing_context_is_open(webview_id)?;
1077
1078        // Step 2. Return success with the window handle.
1079        let handle = self
1080            .get_window_handle(webview_id)
1081            .expect("Failed to get window handle of an existing webview");
1082
1083        Ok(WebDriverResponse::Generic(ValueResponse(
1084            serde_json::to_value(handle)?,
1085        )))
1086    }
1087
1088    /// <https://w3c.github.io/webdriver/#get-window-handles>
1089    fn handle_window_handles(&mut self) -> WebDriverResult<WebDriverResponse> {
1090        let mut handles = self.get_window_handles();
1091        handles.sort_unstable();
1092
1093        Ok(WebDriverResponse::Generic(ValueResponse(
1094            serde_json::to_value(handles)?,
1095        )))
1096    }
1097
1098    fn get_window_handle(&mut self, webview_id: WebViewId) -> Option<String> {
1099        self.get_window_handles()
1100            .iter()
1101            .find(|id| id == &&webview_id.to_string())
1102            .cloned()
1103    }
1104
1105    fn get_window_handles(&self) -> Vec<String> {
1106        self.get_all_webview_ids()
1107            .into_iter()
1108            .map(|id| id.to_string())
1109            .collect()
1110    }
1111
1112    fn get_all_webview_ids(&self) -> Vec<WebViewId> {
1113        let (sender, receiver) = ipc::channel().unwrap();
1114        self.send_message_to_embedder(WebDriverCommandMsg::GetAllWebViews(sender))
1115            .unwrap();
1116        wait_for_ipc_response(receiver).unwrap_or_default()
1117    }
1118
1119    /// <https://w3c.github.io/webdriver/#close-window>
1120    fn handle_close_window(&mut self) -> WebDriverResult<WebDriverResponse> {
1121        let webview_id = self.webview_id()?;
1122        // Step 1. If session's current top-level browsing context is no longer open,
1123        // return error with error code no such window.
1124        self.verify_top_level_browsing_context_is_open(webview_id)?;
1125
1126        // Step 2. Handle any user prompt.
1127        self.handle_any_user_prompts(webview_id)?;
1128
1129        // Step 3. Close session's current top-level browsing context.
1130        let (sender, receiver) = ipc::channel().unwrap();
1131
1132        let cmd_msg = WebDriverCommandMsg::CloseWebView(webview_id, sender);
1133        self.send_message_to_embedder(cmd_msg)?;
1134
1135        wait_for_ipc_response(receiver)?;
1136
1137        // Step 4. If there are no more open top-level browsing contexts, try to close the session.
1138        let window_handles = self.get_window_handles();
1139
1140        if window_handles.is_empty() {
1141            self.session = None;
1142        }
1143
1144        // Step 5. Return the result of running the remote end steps for the Get Window Handles command
1145        Ok(WebDriverResponse::CloseWindow(CloseWindowResponse(
1146            window_handles,
1147        )))
1148    }
1149
1150    /// <https://w3c.github.io/webdriver/#new-window>
1151    fn handle_new_window(
1152        &mut self,
1153        _parameters: &NewWindowParameters,
1154    ) -> WebDriverResult<WebDriverResponse> {
1155        let (sender, receiver) = ipc::channel().unwrap();
1156
1157        let webview_id = self.webview_id()?;
1158
1159        // Step 2. If session's current top-level browsing context is no longer open,
1160        // return error with error code no such window.
1161        self.verify_top_level_browsing_context_is_open(webview_id)?;
1162
1163        // Step 3. Handle any user prompt.
1164        self.handle_any_user_prompts(webview_id)?;
1165
1166        let cmd_msg =
1167            WebDriverCommandMsg::NewWebView(sender, Some(self.load_status_sender.clone()));
1168        // Step 5. Create a new top-level browsing context by running the window open steps.
1169        // This MUST be done without invoking the focusing steps.
1170        self.send_message_to_embedder(cmd_msg)?;
1171
1172        if let Ok(webview_id) = receiver.recv() {
1173            let _ = self.wait_for_navigation_complete();
1174            let handle = self
1175                .get_window_handle(webview_id)
1176                .expect("Failed to get window handle of an existing webview");
1177
1178            Ok(WebDriverResponse::NewWindow(NewWindowResponse {
1179                handle,
1180                typ: "tab".to_string(),
1181            }))
1182        } else {
1183            Err(WebDriverError::new(
1184                ErrorStatus::UnknownError,
1185                "No webview ID received",
1186            ))
1187        }
1188    }
1189
1190    /// <https://w3c.github.io/webdriver/#dfn-switch-to-frame>
1191    fn handle_switch_to_frame(
1192        &mut self,
1193        parameters: &SwitchToFrameParameters,
1194    ) -> WebDriverResult<WebDriverResponse> {
1195        use webdriver::common::FrameId;
1196        let frame_id = match parameters.id {
1197            // id is null
1198            FrameId::Top => {
1199                let webview_id = self.webview_id()?;
1200                // Step 1. If session's current top-level browsing context is no longer open,
1201                // return error with error code no such window.
1202                self.verify_top_level_browsing_context_is_open(webview_id)?;
1203                // Step 2. Try to handle any user prompts with session.
1204                self.handle_any_user_prompts(webview_id)?;
1205                // Step 3. Set the current browsing context with session and
1206                // session's current top-level browsing context.
1207                let browsing_context_id = BrowsingContextId::from(webview_id);
1208                self.session_mut()?
1209                    .set_browsing_context_id(browsing_context_id);
1210
1211                // Step 4. Update any implementation-specific state that would result from
1212                // the user selecting session's current browsing context for interaction,
1213                // without altering OS-level focus.
1214                self.focus_browsing_context(browsing_context_id)?;
1215                return Ok(WebDriverResponse::Void);
1216            },
1217            // id is a Number object
1218            FrameId::Short(ref x) => {
1219                // (Already handled when deserializing in webdriver-crate)
1220                // Step 1. If id is less than 0 or greater than 2^16 – 1,
1221                // return error with error code invalid argument.
1222                WebDriverFrameId::Short(*x)
1223            },
1224            FrameId::Element(ref x) => WebDriverFrameId::Element(x.to_string()),
1225        };
1226
1227        self.switch_to_frame(frame_id)
1228    }
1229
1230    /// <https://w3c.github.io/webdriver/#switch-to-parent-frame>
1231    fn handle_switch_to_parent_frame(&mut self) -> WebDriverResult<WebDriverResponse> {
1232        let webview_id = self.webview_id()?;
1233        let browsing_context = self.browsing_context_id()?;
1234
1235        // Step 1. If session's current browsing context is already the top-level browsing context:
1236        if browsing_context == webview_id {
1237            // Step 1.1. If session's current browsing context is no longer open,
1238            // return error with error code no such window.
1239            self.verify_browsing_context_is_open(browsing_context)?;
1240            // Step 1.2. Return success with data null.
1241            return Ok(WebDriverResponse::Void);
1242        }
1243
1244        // Step 2. If session's current parent browsing context is no longer open,
1245        // return error with error code no such window.
1246        let (sender, receiver) = ipc::channel().unwrap();
1247        let cmd = WebDriverScriptCommand::GetParentFrameId(sender);
1248        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::Yes)?;
1249
1250        // Step 3. Handle any user prompt.
1251        self.handle_any_user_prompts(webview_id)?;
1252
1253        // Step 4. If session's current parent browsing context is not null,
1254        // set the current browsing context with session and current parent browsing context.
1255        let browsing_context_id = wait_for_ipc_response_flatten(receiver)?;
1256        self.session_mut()?
1257            .set_browsing_context_id(browsing_context_id);
1258        // Step 5. Update any implementation-specific state that would result from
1259        // the user selecting session's current browsing context for interaction,
1260        // without altering OS-level focus.
1261        self.focus_browsing_context(browsing_context_id)?;
1262        Ok(WebDriverResponse::Void)
1263    }
1264
1265    /// <https://w3c.github.io/webdriver/#switch-to-window>
1266    fn handle_switch_to_window(
1267        &mut self,
1268        parameters: &SwitchToWindowParameters,
1269    ) -> WebDriverResult<WebDriverResponse> {
1270        let Some(webview_id) = self
1271            .get_all_webview_ids()
1272            .into_iter()
1273            .find(|id| id.to_string() == parameters.handle)
1274        else {
1275            return Err(WebDriverError::new(
1276                ErrorStatus::NoSuchWindow,
1277                "No such window while switching to window",
1278            ));
1279        };
1280
1281        let session = self.session_mut()?;
1282        session.set_webview_id(webview_id);
1283        session.set_browsing_context_id(BrowsingContextId::from(webview_id));
1284
1285        // Step 5. Update any implementation-specific state that would result
1286        // from the user selecting session's current browsing context for interaction,
1287        // without altering OS-level focus.
1288        self.focus_webview(webview_id)?;
1289
1290        Ok(WebDriverResponse::Void)
1291    }
1292
1293    fn switch_to_frame(
1294        &mut self,
1295        frame_id: WebDriverFrameId,
1296    ) -> WebDriverResult<WebDriverResponse> {
1297        let (sender, receiver) = ipc::channel().unwrap();
1298        let cmd = WebDriverScriptCommand::GetBrowsingContextId(frame_id, sender);
1299        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::Yes)?;
1300        self.handle_any_user_prompts(self.webview_id()?)?;
1301
1302        let browsing_context_id = wait_for_ipc_response_flatten(receiver)?;
1303        self.session_mut()?
1304            .set_browsing_context_id(browsing_context_id);
1305        // Step 4. Update any implementation-specific state that would result from
1306        // the user selecting session's current browsing context for interaction,
1307        // without altering OS-level focus.
1308        self.focus_browsing_context(browsing_context_id)?;
1309        Ok(WebDriverResponse::Void)
1310    }
1311
1312    /// <https://w3c.github.io/webdriver/#find-element>
1313    fn handle_find_element(
1314        &self,
1315        parameters: &LocatorParameters,
1316    ) -> WebDriverResult<WebDriverResponse> {
1317        // Step 1 - 9.
1318        let res = self.handle_find_elements(parameters)?;
1319        // Step 10. If result is empty, return error with error code no such element.
1320        // Otherwise, return the first element of result.
1321        unwrap_first_element_response(res)
1322    }
1323
1324    fn implicit_wait<T>(
1325        &self,
1326        callback: impl Fn() -> Result<Vec<T>, WebDriverError>,
1327    ) -> Result<Vec<T>, WebDriverError>
1328    where
1329        T: for<'de> Deserialize<'de> + Serialize,
1330    {
1331        let now = Instant::now();
1332        let (implicit_wait, sleep_interval) = {
1333            let timeouts = self.session()?.session_timeouts();
1334            (
1335                Duration::from_millis(timeouts.implicit_wait.unwrap_or(0)),
1336                Duration::from_millis(timeouts.sleep_interval),
1337            )
1338        };
1339
1340        loop {
1341            match callback() {
1342                Ok(value) if !value.is_empty() || now.elapsed() > implicit_wait => {
1343                    return Ok(value);
1344                },
1345                Ok(_) => {},
1346                Err(error) => return Err(error),
1347            }
1348            sleep(sleep_interval);
1349        }
1350    }
1351
1352    /// <https://w3c.github.io/webdriver/#find-elements>
1353    fn handle_find_elements(
1354        &self,
1355        parameters: &LocatorParameters,
1356    ) -> WebDriverResult<WebDriverResponse> {
1357        // Step 4. If selector is undefined, return error with error code invalid argument.
1358        if parameters.value.is_empty() {
1359            return Err(WebDriverError::new(ErrorStatus::InvalidArgument, ""));
1360        }
1361        // Step 5. If session's current browsing context is no longer open,
1362        // return error with error code no such window.
1363        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1364
1365        // Step 6. Handle any user prompt.
1366        self.handle_any_user_prompts(self.webview_id()?)?;
1367
1368        self.implicit_wait(|| {
1369            let (sender, receiver) = ipc::channel().unwrap();
1370            match parameters.using {
1371                LocatorStrategy::CSSSelector => {
1372                    let cmd = WebDriverScriptCommand::FindElementsCSSSelector(
1373                        parameters.value.clone(),
1374                        sender,
1375                    );
1376                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1377                },
1378                LocatorStrategy::LinkText | LocatorStrategy::PartialLinkText => {
1379                    let cmd = WebDriverScriptCommand::FindElementsLinkText(
1380                        parameters.value.clone(),
1381                        parameters.using == LocatorStrategy::PartialLinkText,
1382                        sender,
1383                    );
1384                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1385                },
1386                LocatorStrategy::TagName => {
1387                    let cmd = WebDriverScriptCommand::FindElementsTagName(
1388                        parameters.value.clone(),
1389                        sender,
1390                    );
1391                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1392                },
1393                LocatorStrategy::XPath => {
1394                    let cmd = WebDriverScriptCommand::FindElementsXpathSelector(
1395                        parameters.value.clone(),
1396                        sender,
1397                    );
1398                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1399                },
1400            }
1401            wait_for_ipc_response_flatten(receiver)
1402        })
1403        .and_then(|response| {
1404            let resp_value: Vec<WebElement> = response.into_iter().map(WebElement).collect();
1405            Ok(WebDriverResponse::Generic(ValueResponse(
1406                serde_json::to_value(resp_value)?,
1407            )))
1408        })
1409    }
1410
1411    /// <https://w3c.github.io/webdriver/#find-element-from-element>
1412    fn handle_find_element_from_element(
1413        &self,
1414        element: &WebElement,
1415        parameters: &LocatorParameters,
1416    ) -> WebDriverResult<WebDriverResponse> {
1417        // Step 1 - 8.
1418        let res = self.handle_find_elements_from_element(element, parameters)?;
1419        // Step 9. If result is empty, return error with error code no such element.
1420        // Otherwise, return the first element of result.
1421        unwrap_first_element_response(res)
1422    }
1423
1424    /// <https://w3c.github.io/webdriver/#find-elements-from-element>
1425    fn handle_find_elements_from_element(
1426        &self,
1427        element: &WebElement,
1428        parameters: &LocatorParameters,
1429    ) -> WebDriverResult<WebDriverResponse> {
1430        // Step 4. If selector is undefined, return error with error code invalid argument.
1431        if parameters.value.is_empty() {
1432            return Err(WebDriverError::new(ErrorStatus::InvalidArgument, ""));
1433        }
1434        // Step 5. If session's current browsing context is no longer open,
1435        // return error with error code no such window.
1436        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1437
1438        // Step 6. Handle any user prompt.
1439        self.handle_any_user_prompts(self.webview_id()?)?;
1440
1441        self.implicit_wait(|| {
1442            let (sender, receiver) = ipc::channel().unwrap();
1443
1444            match parameters.using {
1445                LocatorStrategy::CSSSelector => {
1446                    let cmd = WebDriverScriptCommand::FindElementElementsCSSSelector(
1447                        parameters.value.clone(),
1448                        element.to_string(),
1449                        sender,
1450                    );
1451                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1452                },
1453                LocatorStrategy::LinkText | LocatorStrategy::PartialLinkText => {
1454                    let cmd = WebDriverScriptCommand::FindElementElementsLinkText(
1455                        parameters.value.clone(),
1456                        element.to_string(),
1457                        parameters.using == LocatorStrategy::PartialLinkText,
1458                        sender,
1459                    );
1460                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1461                },
1462                LocatorStrategy::TagName => {
1463                    let cmd = WebDriverScriptCommand::FindElementElementsTagName(
1464                        parameters.value.clone(),
1465                        element.to_string(),
1466                        sender,
1467                    );
1468                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1469                },
1470                LocatorStrategy::XPath => {
1471                    let cmd = WebDriverScriptCommand::FindElementElementsXPathSelector(
1472                        parameters.value.clone(),
1473                        element.to_string(),
1474                        sender,
1475                    );
1476                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1477                },
1478            }
1479
1480            wait_for_ipc_response_flatten(receiver)
1481        })
1482        .and_then(|response| {
1483            let resp_value: Vec<Value> = response
1484                .into_iter()
1485                .map(|x| serde_json::to_value(WebElement(x)).unwrap())
1486                .collect();
1487            Ok(WebDriverResponse::Generic(ValueResponse(
1488                serde_json::to_value(resp_value)?,
1489            )))
1490        })
1491    }
1492
1493    /// <https://w3c.github.io/webdriver/#find-elements-from-shadow-root>
1494    fn handle_find_elements_from_shadow_root(
1495        &self,
1496        shadow_root: &ShadowRoot,
1497        parameters: &LocatorParameters,
1498    ) -> WebDriverResult<WebDriverResponse> {
1499        // Step 4. If selector is undefined, return error with error code invalid argument.
1500        if parameters.value.is_empty() {
1501            return Err(WebDriverError::new(ErrorStatus::InvalidArgument, ""));
1502        }
1503
1504        // Step 5. If session's current browsing context is no longer open,
1505        // return error with error code no such window.
1506        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1507
1508        // Step 6. Handle any user prompt.
1509        self.handle_any_user_prompts(self.webview_id()?)?;
1510
1511        self.implicit_wait(|| {
1512            let (sender, receiver) = ipc::channel().unwrap();
1513
1514            match parameters.using {
1515                LocatorStrategy::CSSSelector => {
1516                    let cmd = WebDriverScriptCommand::FindShadowElementsCSSSelector(
1517                        parameters.value.clone(),
1518                        shadow_root.to_string(),
1519                        sender,
1520                    );
1521                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1522                },
1523                LocatorStrategy::LinkText | LocatorStrategy::PartialLinkText => {
1524                    let cmd = WebDriverScriptCommand::FindShadowElementsLinkText(
1525                        parameters.value.clone(),
1526                        shadow_root.to_string(),
1527                        parameters.using == LocatorStrategy::PartialLinkText,
1528                        sender,
1529                    );
1530                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1531                },
1532                LocatorStrategy::TagName => {
1533                    let cmd = WebDriverScriptCommand::FindShadowElementsTagName(
1534                        parameters.value.clone(),
1535                        shadow_root.to_string(),
1536                        sender,
1537                    );
1538                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1539                },
1540                LocatorStrategy::XPath => {
1541                    let cmd = WebDriverScriptCommand::FindShadowElementsXPathSelector(
1542                        parameters.value.clone(),
1543                        shadow_root.to_string(),
1544                        sender,
1545                    );
1546                    self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1547                },
1548            }
1549
1550            wait_for_ipc_response_flatten(receiver)
1551        })
1552        .and_then(|response| {
1553            let resp_value: Vec<Value> = response
1554                .into_iter()
1555                .map(|x| serde_json::to_value(WebElement(x)).unwrap())
1556                .collect();
1557            Ok(WebDriverResponse::Generic(ValueResponse(
1558                serde_json::to_value(resp_value)?,
1559            )))
1560        })
1561    }
1562
1563    /// <https://w3c.github.io/webdriver/#find-element-from-shadow-root>
1564    fn handle_find_element_from_shadow_root(
1565        &self,
1566        shadow_root: &ShadowRoot,
1567        parameters: &LocatorParameters,
1568    ) -> WebDriverResult<WebDriverResponse> {
1569        // Step 1 - 8.
1570        let res = self.handle_find_elements_from_shadow_root(shadow_root, parameters)?;
1571        // Step 9. If result is empty, return error with error code no such element.
1572        // Otherwise, return the first element of result.
1573        unwrap_first_element_response(res)
1574    }
1575
1576    /// <https://w3c.github.io/webdriver/#get-element-shadow-root>
1577    fn handle_get_shadow_root(&self, element: WebElement) -> WebDriverResult<WebDriverResponse> {
1578        // Step 1. If session's current browsing context is no longer open,
1579        // return error with error code no such window.
1580        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1581        // Step 2. Handle any user prompt.
1582        self.handle_any_user_prompts(self.webview_id()?)?;
1583        let (sender, receiver) = ipc::channel().unwrap();
1584        let cmd = WebDriverScriptCommand::GetElementShadowRoot(element.to_string(), sender);
1585        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1586        // Step 5. If shadow root is null, return error with error code no such shadow root.
1587        let Some(value) = wait_for_ipc_response_flatten(receiver)? else {
1588            return Err(WebDriverError::new(ErrorStatus::NoSuchShadowRoot, ""));
1589        };
1590        Ok(WebDriverResponse::Generic(ValueResponse(
1591            serde_json::to_value(ShadowRoot(value))?,
1592        )))
1593    }
1594
1595    /// <https://w3c.github.io/webdriver/#get-element-rect>
1596    fn handle_element_rect(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
1597        // Step 1. If session's current browsing context is no longer open,
1598        // return error with error code no such window.
1599        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1600        // Step 2. Handle any user prompt.
1601        self.handle_any_user_prompts(self.webview_id()?)?;
1602        let (sender, receiver) = ipc::channel().unwrap();
1603        let cmd = WebDriverScriptCommand::GetElementRect(element.to_string(), sender);
1604        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1605        let rect = wait_for_ipc_response_flatten(receiver)?;
1606        let response = ElementRectResponse {
1607            x: rect.origin.x,
1608            y: rect.origin.y,
1609            width: rect.size.width,
1610            height: rect.size.height,
1611        };
1612        Ok(WebDriverResponse::ElementRect(response))
1613    }
1614
1615    /// <https://w3c.github.io/webdriver/#dfn-get-element-text>
1616    fn handle_element_text(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
1617        // Step 1. If session's current browsing context is no longer open,
1618        // return error with error code no such window.
1619        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1620        // Step 2. Handle any user prompt.
1621        self.handle_any_user_prompts(self.webview_id()?)?;
1622        let (sender, receiver) = ipc::channel().unwrap();
1623        let cmd = WebDriverScriptCommand::GetElementText(element.to_string(), sender);
1624        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1625        Ok(WebDriverResponse::Generic(ValueResponse(
1626            serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
1627        )))
1628    }
1629
1630    ///<https://w3c.github.io/webdriver/#get-active-element>
1631    fn handle_active_element(&self) -> WebDriverResult<WebDriverResponse> {
1632        // Step 1. If session's current browsing context is no longer open,
1633        // return error with error code no such window.
1634        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1635        // Step 2. Handle any user prompt.
1636        self.handle_any_user_prompts(self.webview_id()?)?;
1637        let (sender, receiver) = ipc::channel().unwrap();
1638        let cmd = WebDriverScriptCommand::GetActiveElement(sender);
1639        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1640        let value =
1641            wait_for_ipc_response(receiver)?.map(|x| serde_json::to_value(WebElement(x)).unwrap());
1642        // Step 4. If active element is a non-null element, return success
1643        // with data set to web element reference object for session and active element.
1644        // Otherwise, return error with error code no such element.
1645        if value.is_some() {
1646            Ok(WebDriverResponse::Generic(ValueResponse(
1647                serde_json::to_value(value)?,
1648            )))
1649        } else {
1650            Err(WebDriverError::new(
1651                ErrorStatus::NoSuchElement,
1652                "No active element found",
1653            ))
1654        }
1655    }
1656
1657    /// <https://w3c.github.io/webdriver/#get-computed-role>
1658    fn handle_computed_role(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
1659        // Step 1. If session's current browsing context is no longer open,
1660        // return error with error code no such window.
1661        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1662        // Step 2. Handle any user prompt.
1663        self.handle_any_user_prompts(self.webview_id()?)?;
1664        let (sender, receiver) = ipc::channel().unwrap();
1665        let cmd = WebDriverScriptCommand::GetComputedRole(element.to_string(), sender);
1666        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1667        Ok(WebDriverResponse::Generic(ValueResponse(
1668            serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
1669        )))
1670    }
1671
1672    /// <https://w3c.github.io/webdriver/#get-element-tag-name>
1673    fn handle_element_tag_name(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
1674        // Step 1. If session's current browsing context is no longer open,
1675        // return error with error code no such window.
1676        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1677        // Step 2. Handle any user prompt.
1678        self.handle_any_user_prompts(self.webview_id()?)?;
1679        let (sender, receiver) = ipc::channel().unwrap();
1680        let cmd = WebDriverScriptCommand::GetElementTagName(element.to_string(), sender);
1681        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1682        Ok(WebDriverResponse::Generic(ValueResponse(
1683            serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
1684        )))
1685    }
1686
1687    /// <https://w3c.github.io/webdriver/#get-element-attribute>
1688    fn handle_element_attribute(
1689        &self,
1690        element: &WebElement,
1691        name: &str,
1692    ) -> WebDriverResult<WebDriverResponse> {
1693        // Step 1. If session's current browsing context is no longer open,
1694        // return error with error code no such window.
1695        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1696        // Step 2. Handle any user prompt.
1697        self.handle_any_user_prompts(self.webview_id()?)?;
1698        let (sender, receiver) = ipc::channel().unwrap();
1699        let cmd = WebDriverScriptCommand::GetElementAttribute(
1700            element.to_string(),
1701            name.to_owned(),
1702            sender,
1703        );
1704        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1705        Ok(WebDriverResponse::Generic(ValueResponse(
1706            serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
1707        )))
1708    }
1709
1710    /// <https://w3c.github.io/webdriver/#get-element-property>
1711    fn handle_element_property(
1712        &self,
1713        element: &WebElement,
1714        name: &str,
1715    ) -> WebDriverResult<WebDriverResponse> {
1716        // Step 1. If session's current browsing context is no longer open,
1717        // return error with error code no such window.
1718        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1719        // Step 2. Handle any user prompt.
1720        self.handle_any_user_prompts(self.webview_id()?)?;
1721        let (sender, receiver) = ipc::channel().unwrap();
1722
1723        let cmd = WebDriverScriptCommand::GetElementProperty(
1724            element.to_string(),
1725            name.to_owned(),
1726            sender,
1727        );
1728        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1729
1730        Ok(WebDriverResponse::Generic(ValueResponse(
1731            serde_json::to_value(SendableJSValue(wait_for_ipc_response_flatten(receiver)?))?,
1732        )))
1733    }
1734
1735    /// <https://w3c.github.io/webdriver/#get-element-css-value>
1736    fn handle_element_css(
1737        &self,
1738        element: &WebElement,
1739        name: &str,
1740    ) -> WebDriverResult<WebDriverResponse> {
1741        // Step 1. If session's current browsing context is no longer open,
1742        // return error with error code no such window.
1743        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1744        // Step 2. Handle any user prompt.
1745        self.handle_any_user_prompts(self.webview_id()?)?;
1746        let (sender, receiver) = ipc::channel().unwrap();
1747        let cmd =
1748            WebDriverScriptCommand::GetElementCSS(element.to_string(), name.to_owned(), sender);
1749        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1750        Ok(WebDriverResponse::Generic(ValueResponse(
1751            serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
1752        )))
1753    }
1754
1755    /// <https://w3c.github.io/webdriver/#get-all-cookies>
1756    fn handle_get_cookies(&self) -> WebDriverResult<WebDriverResponse> {
1757        // Step 1. If session's current browsing context is no longer open,
1758        // return error with error code no such window.
1759        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1760        // Step 2. Handle any user prompt.
1761        self.handle_any_user_prompts(self.webview_id()?)?;
1762        let (sender, receiver) = ipc::channel().unwrap();
1763        let cmd = WebDriverScriptCommand::GetCookies(sender);
1764        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1765        let cookies = wait_for_ipc_response_flatten(receiver)?;
1766        let response = cookies
1767            .into_iter()
1768            .map(|cookie| cookie_msg_to_cookie(cookie.into_inner()))
1769            .collect::<Vec<Cookie>>();
1770        Ok(WebDriverResponse::Cookies(CookiesResponse(response)))
1771    }
1772
1773    /// <https://w3c.github.io/webdriver/#get-named-cookie>
1774    fn handle_get_cookie(&self, name: String) -> WebDriverResult<WebDriverResponse> {
1775        // Step 1. If session's current browsing context is no longer open,
1776        // return error with error code no such window.
1777        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1778        // Step 2. Handle any user prompt.
1779        self.handle_any_user_prompts(self.webview_id()?)?;
1780        let (sender, receiver) = ipc::channel().unwrap();
1781        let cmd = WebDriverScriptCommand::GetCookie(name, sender);
1782        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1783        let cookies = wait_for_ipc_response_flatten(receiver)?;
1784        let Some(response) = cookies
1785            .into_iter()
1786            .map(|cookie| cookie_msg_to_cookie(cookie.into_inner()))
1787            .next()
1788        else {
1789            return Err(WebDriverError::new(ErrorStatus::NoSuchCookie, ""));
1790        };
1791        Ok(WebDriverResponse::Cookie(CookieResponse(response)))
1792    }
1793
1794    /// <https://w3c.github.io/webdriver/#add-cookie>
1795    fn handle_add_cookie(
1796        &self,
1797        params: &AddCookieParameters,
1798    ) -> WebDriverResult<WebDriverResponse> {
1799        // Step 1. If session's current browsing context is no longer open,
1800        // return error with error code no such window.
1801        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1802        // Step 2. Handle any user prompt.
1803        self.handle_any_user_prompts(self.webview_id()?)?;
1804        let (sender, receiver) = ipc::channel().unwrap();
1805
1806        let mut cookie_builder =
1807            CookieBuilder::new(params.name.to_owned(), params.value.to_owned())
1808                .secure(params.secure)
1809                .http_only(params.httpOnly);
1810        if let Some(ref domain) = params.domain {
1811            cookie_builder = cookie_builder.domain(domain.clone());
1812        }
1813        if let Some(ref path) = params.path {
1814            cookie_builder = cookie_builder.path(path.clone());
1815        }
1816        if let Some(ref expiry) = params.expiry {
1817            if let Ok(datetime) = OffsetDateTime::from_unix_timestamp(expiry.0 as i64) {
1818                cookie_builder = cookie_builder.expires(datetime);
1819            }
1820        }
1821        if let Some(ref same_site) = params.sameSite {
1822            cookie_builder = match same_site.as_str() {
1823                "None" => Ok(cookie_builder.same_site(SameSite::None)),
1824                "Lax" => Ok(cookie_builder.same_site(SameSite::Lax)),
1825                "Strict" => Ok(cookie_builder.same_site(SameSite::Strict)),
1826                _ => Err(WebDriverError::new(
1827                    ErrorStatus::InvalidArgument,
1828                    "invalid argument",
1829                )),
1830            }?;
1831        }
1832
1833        let cmd = WebDriverScriptCommand::AddCookie(cookie_builder.build(), sender);
1834        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1835        wait_for_ipc_response_flatten(receiver)?;
1836        Ok(WebDriverResponse::Void)
1837    }
1838
1839    /// <https://w3c.github.io/webdriver/#delete-cookie>
1840    fn handle_delete_cookie(&self, name: String) -> WebDriverResult<WebDriverResponse> {
1841        // Step 1. If session's current browsing context is no longer open,
1842        // return error with error code no such window.
1843        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1844        // Step 2. Handle any user prompt.
1845        self.handle_any_user_prompts(self.webview_id()?)?;
1846        let (sender, receiver) = ipc::channel().unwrap();
1847        let cmd = WebDriverScriptCommand::DeleteCookie(name, sender);
1848        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1849        wait_for_ipc_response_flatten(receiver)?;
1850        Ok(WebDriverResponse::Void)
1851    }
1852
1853    /// <https://w3c.github.io/webdriver/#delete-all-cookies>
1854    fn handle_delete_cookies(&self) -> WebDriverResult<WebDriverResponse> {
1855        // Step 1. If session's current browsing context is no longer open,
1856        // return error with error code no such window.
1857        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1858        // Step 2. Handle any user prompt.
1859        self.handle_any_user_prompts(self.webview_id()?)?;
1860        let (sender, receiver) = ipc::channel().unwrap();
1861        let cmd = WebDriverScriptCommand::DeleteCookies(sender);
1862        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::Yes)?;
1863        wait_for_ipc_response_flatten(receiver)?;
1864        Ok(WebDriverResponse::Void)
1865    }
1866
1867    /// <https://w3c.github.io/webdriver/#get-timeouts>
1868    fn handle_get_timeouts(&mut self) -> WebDriverResult<WebDriverResponse> {
1869        let timeouts = self.session()?.session_timeouts();
1870
1871        // FIXME: The specification says that all of these values can be `null`, but the `webdriver` crate
1872        // only supports setting `script` as null. When set to null, report these values as being the
1873        // default ones for now.
1874        let timeouts = TimeoutsResponse {
1875            script: timeouts.script,
1876            page_load: timeouts.page_load.unwrap_or(DEFAULT_PAGE_LOAD_TIMEOUT),
1877            implicit: timeouts.implicit_wait.unwrap_or(0),
1878        };
1879
1880        Ok(WebDriverResponse::Timeouts(timeouts))
1881    }
1882
1883    /// <https://w3c.github.io/webdriver/#set-timeouts>
1884    fn handle_set_timeouts(
1885        &mut self,
1886        parameters: &TimeoutsParameters,
1887    ) -> WebDriverResult<WebDriverResponse> {
1888        let session = self.session_mut()?;
1889
1890        if let Some(timeout) = parameters.script {
1891            session.session_timeouts_mut().script = timeout;
1892        }
1893        if let Some(timeout) = parameters.page_load {
1894            session.session_timeouts_mut().page_load = Some(timeout);
1895        }
1896        if let Some(timeout) = parameters.implicit {
1897            session.session_timeouts_mut().implicit_wait = Some(timeout);
1898        }
1899
1900        Ok(WebDriverResponse::Void)
1901    }
1902
1903    /// <https://w3c.github.io/webdriver/#dfn-get-page-source>
1904    fn handle_get_page_source(&self) -> WebDriverResult<WebDriverResponse> {
1905        // Step 1. If session's current browsing context is no longer open,
1906        // return error with error code no such window.
1907        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1908        // Step 2. Handle any user prompt.
1909        self.handle_any_user_prompts(self.webview_id()?)?;
1910        let (sender, receiver) = ipc::channel().unwrap();
1911
1912        let cmd = WebDriverScriptCommand::GetPageSource(sender);
1913        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
1914
1915        Ok(WebDriverResponse::Generic(ValueResponse(
1916            serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
1917        )))
1918    }
1919
1920    /// <https://w3c.github.io/webdriver/#perform-actions>
1921    fn handle_perform_actions(
1922        &mut self,
1923        parameters: ActionsParameters,
1924    ) -> WebDriverResult<WebDriverResponse> {
1925        let browsing_context = self.browsing_context_id()?;
1926        // Step 1. If session's current browsing context is no longer open,
1927        // return error with error code no such window.
1928        self.verify_browsing_context_is_open(browsing_context)?;
1929
1930        // Step 2. Handle any user prompt.
1931        self.handle_any_user_prompts(self.webview_id()?)?;
1932
1933        // Step 5. Let actions by tick be the result of trying to extract an action sequence
1934        let actions_by_tick = self.extract_an_action_sequence(parameters);
1935
1936        // Step 6. Dispatch actions with current browsing context
1937        match self.dispatch_actions(actions_by_tick, browsing_context) {
1938            Ok(_) => Ok(WebDriverResponse::Void),
1939            Err(error) => Err(WebDriverError::new(error, "")),
1940        }
1941    }
1942
1943    /// <https://w3c.github.io/webdriver/#dfn-release-actions>
1944    fn handle_release_actions(&mut self) -> WebDriverResult<WebDriverResponse> {
1945        let browsing_context_id = self.browsing_context_id()?;
1946        // Step 1. If session's current browsing context is no longer open,
1947        // return error with error code no such window.
1948        self.verify_browsing_context_is_open(browsing_context_id)?;
1949
1950        // Step 2. User prompts.
1951        self.handle_any_user_prompts(self.webview_id()?)?;
1952
1953        // TODO: Step 4. Actions options are not used yet.
1954
1955        // Step 5. Not needed because "In a session that is only a HTTP session
1956        // only one command can run at a time, so this will never block."
1957
1958        // Step 6. Let undo actions be input cancel list in reverse order.
1959        let undo_actions = self
1960            .session_mut()?
1961            .input_cancel_list
1962            .drain(..)
1963            .rev()
1964            .map(|(id, action_item)| Vec::from([(id, action_item)]))
1965            .collect();
1966        // Step 7. Dispatch undo actions with current browsing context.
1967        if let Err(err) = self.dispatch_actions(undo_actions, browsing_context_id) {
1968            return Err(WebDriverError::new(err, "Failed to dispatch undo actions"));
1969        }
1970
1971        // Step 8. Reset the input state of session's current top-level browsing context.
1972        self.session_mut()?.input_state_table.clear();
1973
1974        Ok(WebDriverResponse::Void)
1975    }
1976
1977    /// <https://w3c.github.io/webdriver/#dfn-execute-script>
1978    fn handle_execute_script(
1979        &self,
1980        parameters: JavascriptCommandParameters,
1981    ) -> WebDriverResult<WebDriverResponse> {
1982        // Step 1. Let body and arguments be the result of trying to extract the script arguments
1983        // from a request with argument parameters.
1984        let (func_body, args_string) = self.extract_script_arguments(parameters)?;
1985        // This is pretty ugly; we really want something that acts like
1986        // new Function() and then takes the resulting function and executes
1987        // it with a vec of arguments.
1988        let script = format!(
1989            "(function() {{ {}\n }})({})",
1990            func_body,
1991            args_string.join(", ")
1992        );
1993        debug!("{}", script);
1994
1995        // Step 2. If session's current browsing context is no longer open,
1996        // return error with error code no such window.
1997        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
1998
1999        // Step 3. Handle any user prompt.
2000        self.handle_any_user_prompts(self.webview_id()?)?;
2001
2002        let (sender, receiver) = ipc::channel().unwrap();
2003        let cmd = WebDriverScriptCommand::ExecuteScript(script, sender);
2004        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
2005
2006        let timeout_duration = self
2007            .session()?
2008            .session_timeouts()
2009            .script
2010            .map(Duration::from_millis);
2011        let result = wait_for_script_ipc_response_with_timeout(receiver, timeout_duration)?;
2012
2013        self.javascript_evaluation_result_to_webdriver_response(result)
2014    }
2015
2016    fn handle_execute_async_script(
2017        &self,
2018        parameters: JavascriptCommandParameters,
2019    ) -> WebDriverResult<WebDriverResponse> {
2020        // Step 1. Let body and arguments be the result of trying to extract the script arguments
2021        // from a request with argument parameters.
2022        let (function_body, mut args_string) = self.extract_script_arguments(parameters)?;
2023        args_string.push("resolve".to_string());
2024
2025        let joined_args = args_string.join(", ");
2026        let script = format!(
2027            r#"(function() {{
2028              let webdriverPromise = new Promise(function(resolve, reject) {{
2029                  (async function() {{
2030                    {function_body}
2031                  }})({joined_args})
2032                    .then((v) => {{}}, (err) => reject(err))
2033              }})
2034              .then((v) => window.webdriverCallback(v), (r) => window.webdriverException(r))
2035              .catch((r) => window.webdriverException(r));
2036            }})();"#,
2037        );
2038        debug!("{}", script);
2039
2040        // Step 2. If session's current browsing context is no longer open,
2041        // return error with error code no such window.
2042        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
2043
2044        // Step 3. Handle any user prompt.
2045        self.handle_any_user_prompts(self.webview_id()?)?;
2046
2047        let (sender, receiver) = ipc::channel().unwrap();
2048        self.browsing_context_script_command(
2049            WebDriverScriptCommand::ExecuteAsyncScript(script, sender),
2050            VerifyBrowsingContextIsOpen::No,
2051        )?;
2052
2053        let timeout_duration = self
2054            .session()?
2055            .session_timeouts()
2056            .script
2057            .map(Duration::from_millis);
2058        let result = wait_for_script_ipc_response_with_timeout(receiver, timeout_duration)?;
2059
2060        self.javascript_evaluation_result_to_webdriver_response(result)
2061    }
2062
2063    fn javascript_evaluation_result_to_webdriver_response(
2064        &self,
2065        result: WebDriverJSResult,
2066    ) -> WebDriverResult<WebDriverResponse> {
2067        match result {
2068            Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse(
2069                serde_json::to_value(SendableJSValue(value))?,
2070            ))),
2071            Err(error) => {
2072                let message = format!("{error:?}");
2073                let status = match error {
2074                    JavaScriptEvaluationError::DocumentNotFound => ErrorStatus::NoSuchWindow,
2075                    JavaScriptEvaluationError::CompilationFailure => ErrorStatus::JavascriptError,
2076                    JavaScriptEvaluationError::EvaluationFailure(Some(error_info)) => {
2077                        return Err(WebDriverError::new_with_data(
2078                            ErrorStatus::JavascriptError,
2079                            error_info.message,
2080                            None,
2081                            error_info.stack,
2082                        ));
2083                    },
2084                    JavaScriptEvaluationError::EvaluationFailure(None) => {
2085                        ErrorStatus::JavascriptError
2086                    },
2087                    JavaScriptEvaluationError::InternalError => ErrorStatus::JavascriptError,
2088                    JavaScriptEvaluationError::SerializationError(serialization_error) => {
2089                        match serialization_error {
2090                            JavaScriptEvaluationResultSerializationError::DetachedShadowRoot => {
2091                                ErrorStatus::DetachedShadowRoot
2092                            },
2093                            JavaScriptEvaluationResultSerializationError::OtherJavaScriptError => {
2094                                ErrorStatus::JavascriptError
2095                            },
2096                            JavaScriptEvaluationResultSerializationError::StaleElementReference => {
2097                                ErrorStatus::StaleElementReference
2098                            },
2099                            JavaScriptEvaluationResultSerializationError::UnknownType => {
2100                                ErrorStatus::UnsupportedOperation
2101                            },
2102                        }
2103                    },
2104                    JavaScriptEvaluationError::WebViewNotReady => ErrorStatus::NoSuchWindow,
2105                };
2106                Err(WebDriverError::new(status, message))
2107            },
2108        }
2109    }
2110
2111    /// <https://w3c.github.io/webdriver/#dfn-element-send-keys>
2112    fn handle_element_send_keys(
2113        &mut self,
2114        element: &WebElement,
2115        keys: &SendKeysParameters,
2116    ) -> WebDriverResult<WebDriverResponse> {
2117        // Step 3. If session's current browsing context is no longer open,
2118        // return error with error code no such window.
2119        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
2120        // Step 4. Handle any user prompt.
2121        self.handle_any_user_prompts(self.webview_id()?)?;
2122
2123        let (sender, receiver) = ipc::channel().unwrap();
2124        let cmd = WebDriverScriptCommand::WillSendKeys(
2125            element.to_string(),
2126            keys.text.to_string(),
2127            self.session()?.strict_file_interactability(),
2128            sender,
2129        );
2130        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
2131
2132        // File input and non-typeable form control should have
2133        // been handled in `webdriver_handler.rs`.
2134        if !wait_for_ipc_response_flatten(receiver)? {
2135            return Ok(WebDriverResponse::Void);
2136        }
2137
2138        // Step 10. Let input id be a the result of generating a UUID.
2139        let id = Uuid::new_v4().to_string();
2140
2141        // Step 12. Add an input source
2142        self.session_mut()?
2143            .input_state_table
2144            .insert(id.clone(), InputSourceState::Key(KeyInputState::new()));
2145
2146        // Step 13. dispatch actions for a string
2147        // https://w3c.github.io/webdriver/#dfn-dispatch-actions-for-a-string
2148        let input_events = send_keys(&keys.text);
2149
2150        for event in input_events {
2151            match event {
2152                DispatchStringEvent::Keyboard(event) => {
2153                    let raw_string = convert_keyboard_event_to_string(&event);
2154                    let key_action = match event.state {
2155                        KeyState::Down => KeyAction::Down(KeyDownAction { value: raw_string }),
2156                        KeyState::Up => KeyAction::Up(KeyUpAction { value: raw_string }),
2157                    };
2158                    let action_sequence = ActionSequence {
2159                        id: id.clone(),
2160                        actions: ActionsType::Key {
2161                            actions: vec![KeyActionItem::Key(key_action)],
2162                        },
2163                    };
2164
2165                    let actions_by_tick = self.actions_by_tick_from_sequence(vec![action_sequence]);
2166                    if let Err(e) =
2167                        self.dispatch_actions(actions_by_tick, self.browsing_context_id()?)
2168                    {
2169                        log::error!("handle_element_send_keys: dispatch_actions failed: {:?}", e);
2170                    }
2171                },
2172                DispatchStringEvent::Composition(event) => {
2173                    self.send_input_event_to_embedder(InputEvent::Ime(ImeEvent::Composition(
2174                        event,
2175                    )));
2176                },
2177            }
2178        }
2179
2180        // Step 14. Remove an input source with input state and input id.
2181        // It is possible that we only dispatched keydown.
2182        // In that case, we cannot remove the id from input state table.
2183        // This is a bug in spec: https://github.com/servo/servo/issues/37579#issuecomment-2990762713
2184        if self
2185            .session()?
2186            .input_cancel_list
2187            .iter()
2188            .all(|(cancel_item_id, _)| &id != cancel_item_id)
2189        {
2190            self.session_mut()?.input_state_table.remove(&id);
2191        }
2192
2193        Ok(WebDriverResponse::Void)
2194    }
2195
2196    /// <https://w3c.github.io/webdriver/#element-clear>
2197    fn handle_element_clear(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
2198        // Step 1. If session's current browsing context is no longer open,
2199        // return ErrorStatus::NoSuchWindow.
2200        self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
2201
2202        // Step 2. Try to handle any user prompt.
2203        self.handle_any_user_prompts(self.webview_id()?)?;
2204
2205        // Step 3-11 handled in script thread.
2206        let (sender, receiver) = ipc::channel().unwrap();
2207        let cmd = WebDriverScriptCommand::ElementClear(element.to_string(), sender);
2208        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
2209
2210        wait_for_ipc_response_flatten(receiver)?;
2211        Ok(WebDriverResponse::Void)
2212    }
2213
2214    /// <https://w3c.github.io/webdriver/#element-click>
2215    fn handle_element_click(&mut self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
2216        // Step 1. If session's current browsing context is no longer open,
2217        // return error with error code no such window.
2218        let browsing_context_id = self.browsing_context_id()?;
2219        self.verify_browsing_context_is_open(browsing_context_id)?;
2220
2221        // Step 2. Handle any user prompts.
2222        self.handle_any_user_prompts(self.webview_id()?)?;
2223
2224        let (sender, receiver) = ipc::channel().unwrap();
2225
2226        // Steps 3-7 + Step 8 for <option> are handled in script thread.
2227        let cmd = WebDriverScriptCommand::ElementClick(element.to_string(), sender);
2228        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
2229
2230        match wait_for_ipc_response_flatten(receiver)? {
2231            Some(element_id) => {
2232                // Load status sender should be set up before we dispatch actions
2233                // to ensure webdriver can capture any navigation events.
2234                self.add_load_status_sender()?;
2235
2236                self.perform_element_click(element_id)?;
2237
2238                // Step 11. Try to wait for navigation to complete with session.
2239                // Check if there is a navigation with script
2240                let res = self.wait_for_navigation()?;
2241
2242                // Clear the load status sender
2243                self.clear_load_status_sender()?;
2244
2245                Ok(res)
2246            },
2247            // Step 13
2248            None => Ok(WebDriverResponse::Void),
2249        }
2250    }
2251
2252    /// <https://w3c.github.io/webdriver/#element-click>
2253    /// Step 8 for elements other than <option>
2254    fn perform_element_click(&mut self, element: String) -> WebDriverResult<WebDriverResponse> {
2255        // Step 8.1 - 8.4: Create UUID, create input source "pointer".
2256        let id = Uuid::new_v4().to_string();
2257
2258        let pointer_ids = self.session()?.pointer_ids();
2259        self.session_mut()?.input_state_table.insert(
2260            id.clone(),
2261            InputSourceState::Pointer(PointerInputState::new(PointerType::Mouse, pointer_ids)),
2262        );
2263
2264        // Step 8.7. Construct a pointer move action.
2265        // Step 8.8. Set a property x to 0 on pointer move action.
2266        // Step 8.9. Set a property y to 0 on pointer move action.
2267        // Step 8.10. Set a property origin to element on pointer move action.
2268        let pointer_move_action = PointerMoveAction {
2269            duration: None,
2270            origin: PointerOrigin::Element(WebElement(element)),
2271            x: 0.0,
2272            y: 0.0,
2273            ..Default::default()
2274        };
2275
2276        // Step 8.11. Construct pointer down action.
2277        // Step 8.12. Set a property button to 0 on pointer down action.
2278        let pointer_down_action = PointerDownAction {
2279            button: i16::from(MouseButton::Left) as u64,
2280            ..Default::default()
2281        };
2282
2283        // Step 8.13. Construct pointer up action.
2284        // Step 8.14. Set a property button to 0 on pointer up action.
2285        let pointer_up_action = PointerUpAction {
2286            button: i16::from(MouseButton::Left) as u64,
2287            ..Default::default()
2288        };
2289
2290        let action_sequence = ActionSequence {
2291            id: id.clone(),
2292            actions: ActionsType::Pointer {
2293                parameters: PointerActionParameters {
2294                    pointer_type: PointerType::Mouse,
2295                },
2296                actions: vec![
2297                    PointerActionItem::Pointer(PointerAction::Move(pointer_move_action)),
2298                    PointerActionItem::Pointer(PointerAction::Down(pointer_down_action)),
2299                    PointerActionItem::Pointer(PointerAction::Up(pointer_up_action)),
2300                ],
2301            },
2302        };
2303
2304        // Step 8.16. Dispatch a list of actions with session's current browsing context
2305        let actions_by_tick = self.actions_by_tick_from_sequence(vec![action_sequence]);
2306        if let Err(e) = self.dispatch_actions(actions_by_tick, self.browsing_context_id()?) {
2307            log::error!("handle_element_click: dispatch_actions failed: {:?}", e);
2308        }
2309
2310        // Step 8.17 Remove an input source with input state and input id.
2311        self.session_mut()?.input_state_table.remove(&id);
2312
2313        Ok(WebDriverResponse::Void)
2314    }
2315
2316    fn take_screenshot(&self, rect: Option<Rect<f32, CSSPixel>>) -> WebDriverResult<String> {
2317        // Spec: Take screenshot after running the animation frame callbacks.
2318        let _ = self.handle_execute_async_script(JavascriptCommandParameters {
2319            script: "requestAnimationFrame(() => arguments[0]());".to_string(),
2320            args: None,
2321        });
2322        if rect.as_ref().is_some_and(Rect::is_empty) {
2323            return Err(WebDriverError::new(
2324                ErrorStatus::UnknownError,
2325                "The requested `rect` has zero width and/or height",
2326            ));
2327        }
2328
2329        let webview_id = self.webview_id()?;
2330        let (sender, receiver) = crossbeam_channel::unbounded();
2331        self.send_message_to_embedder(WebDriverCommandMsg::TakeScreenshot(
2332            webview_id, rect, sender,
2333        ))?;
2334
2335        let result = match receiver.recv_timeout(SCREENSHOT_TIMEOUT) {
2336            Ok(result) => Ok(result),
2337            Err(RecvTimeoutError::Timeout) => Err(WebDriverError::new(
2338                ErrorStatus::Timeout,
2339                "Timed out waiting to take screenshot. Test likely didn't finish.",
2340            )),
2341            Err(RecvTimeoutError::Disconnected) => Err(WebDriverError::new(
2342                ErrorStatus::UnknownError,
2343                "Could not take screenshot because channel disconnected.",
2344            )),
2345        }?;
2346
2347        let image = result.map_err(|error| {
2348            WebDriverError::new(
2349                ErrorStatus::UnknownError,
2350                format!("Failed to take screenshot: {error:?}"),
2351            )
2352        })?;
2353
2354        let mut png_data = Cursor::new(Vec::new());
2355        DynamicImage::ImageRgba8(image)
2356            .write_to(&mut png_data, ImageFormat::Png)
2357            .unwrap();
2358
2359        Ok(base64::engine::general_purpose::STANDARD.encode(png_data.get_ref()))
2360    }
2361
2362    fn handle_take_screenshot(&self) -> WebDriverResult<WebDriverResponse> {
2363        // Step 1. If session's current top-level browsing context is no longer open,
2364        // return error with error code no such window.
2365        let webview_id = self.webview_id()?;
2366        self.verify_top_level_browsing_context_is_open(webview_id)?;
2367
2368        self.handle_any_user_prompts(webview_id)?;
2369
2370        // Step 2
2371        let encoded = self.take_screenshot(None)?;
2372
2373        Ok(WebDriverResponse::Generic(ValueResponse(
2374            serde_json::to_value(encoded)?,
2375        )))
2376    }
2377
2378    fn handle_take_element_screenshot(
2379        &self,
2380        element: &WebElement,
2381    ) -> WebDriverResult<WebDriverResponse> {
2382        // Step 1. If session's current top-level browsing context is no longer open,
2383        // return error with error code no such window.
2384        let webview_id = self.webview_id()?;
2385        self.verify_top_level_browsing_context_is_open(webview_id)?;
2386
2387        // Step 2. Try to handle any user prompts with session.
2388        self.handle_any_user_prompts(webview_id)?;
2389
2390        // Step 3. Trying to get element.
2391        // Step 4. Scroll into view into element.
2392        let (sender, receiver) = ipc::channel().unwrap();
2393        let cmd =
2394            WebDriverScriptCommand::ScrollAndGetBoundingClientRect(element.to_string(), sender);
2395        self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::Yes)?;
2396
2397        let rect = wait_for_ipc_response_flatten(receiver)?;
2398
2399        // Step 5
2400        let encoded = self.take_screenshot(Some(Rect::from_untyped(&rect)))?;
2401
2402        // Step 6 return success with data encoded string.
2403        Ok(WebDriverResponse::Generic(ValueResponse(
2404            serde_json::to_value(encoded)?,
2405        )))
2406    }
2407
2408    fn handle_get_prefs(
2409        &self,
2410        parameters: &GetPrefsParameters,
2411    ) -> WebDriverResult<WebDriverResponse> {
2412        let prefs = parameters
2413            .prefs
2414            .iter()
2415            .map(|item| {
2416                (
2417                    item.clone(),
2418                    serde_json::to_value(prefs::get().get_value(item)).unwrap(),
2419                )
2420            })
2421            .collect::<BTreeMap<_, _>>();
2422
2423        Ok(WebDriverResponse::Generic(ValueResponse(
2424            serde_json::to_value(prefs)?,
2425        )))
2426    }
2427
2428    fn handle_set_prefs(
2429        &self,
2430        parameters: &SetPrefsParameters,
2431    ) -> WebDriverResult<WebDriverResponse> {
2432        let mut current_preferences = prefs::get().clone();
2433        for (key, value) in parameters.prefs.iter() {
2434            current_preferences.set_value(key, value.0.clone());
2435        }
2436        prefs::set(current_preferences);
2437
2438        Ok(WebDriverResponse::Void)
2439    }
2440
2441    fn handle_reset_prefs(
2442        &self,
2443        parameters: &GetPrefsParameters,
2444    ) -> WebDriverResult<WebDriverResponse> {
2445        let (new_preferences, map) = if parameters.prefs.is_empty() {
2446            (Preferences::default(), BTreeMap::new())
2447        } else {
2448            // If we only want to reset some of the preferences.
2449            let mut new_preferences = prefs::get().clone();
2450            let default_preferences = Preferences::default();
2451            for key in parameters.prefs.iter() {
2452                new_preferences.set_value(key, default_preferences.get_value(key))
2453            }
2454
2455            let map = parameters
2456                .prefs
2457                .iter()
2458                .map(|item| (item.clone(), new_preferences.get_value(item)))
2459                .collect::<BTreeMap<_, _>>();
2460
2461            (new_preferences, map)
2462        };
2463
2464        prefs::set(new_preferences);
2465
2466        Ok(WebDriverResponse::Generic(ValueResponse(
2467            serde_json::to_value(map)?,
2468        )))
2469    }
2470
2471    fn handle_shutdown(&self) -> WebDriverResult<WebDriverResponse> {
2472        self.send_message_to_embedder(WebDriverCommandMsg::Shutdown)?;
2473        Ok(WebDriverResponse::Void)
2474    }
2475
2476    fn verify_top_level_browsing_context_is_open(
2477        &self,
2478        webview_id: WebViewId,
2479    ) -> Result<(), WebDriverError> {
2480        let (sender, receiver) = ipc::channel().unwrap();
2481        self.send_message_to_embedder(WebDriverCommandMsg::IsWebViewOpen(webview_id, sender))?;
2482        if wait_for_ipc_response(receiver)? {
2483            Ok(())
2484        } else {
2485            Err(WebDriverError::new(ErrorStatus::NoSuchWindow, ""))
2486        }
2487    }
2488
2489    fn verify_browsing_context_is_open(
2490        &self,
2491        browsing_context_id: BrowsingContextId,
2492    ) -> Result<(), WebDriverError> {
2493        let (sender, receiver) = ipc::channel().unwrap();
2494        self.send_message_to_embedder(WebDriverCommandMsg::IsBrowsingContextOpen(
2495            browsing_context_id,
2496            sender,
2497        ))?;
2498        if !receiver.recv().unwrap_or(false) {
2499            Err(WebDriverError::new(
2500                ErrorStatus::NoSuchWindow,
2501                "No such window",
2502            ))
2503        } else {
2504            Ok(())
2505        }
2506    }
2507
2508    fn focus_webview(&self, webview_id: WebViewId) -> WebDriverResult<()> {
2509        self.send_message_to_embedder(WebDriverCommandMsg::FocusWebView(webview_id))
2510    }
2511
2512    fn focus_browsing_context(&self, browsing_cotext_id: BrowsingContextId) -> WebDriverResult<()> {
2513        self.send_message_to_embedder(WebDriverCommandMsg::FocusBrowsingContext(
2514            browsing_cotext_id,
2515        ))
2516    }
2517}
2518
2519impl WebDriverHandler<ServoExtensionRoute> for Handler {
2520    fn handle_command(
2521        &mut self,
2522        _session: &Option<Session>,
2523        msg: WebDriverMessage<ServoExtensionRoute>,
2524    ) -> WebDriverResult<WebDriverResponse> {
2525        info!("{:?}", msg.command);
2526
2527        // Drain the load status receiver to avoid incorrect status handling
2528        while self.load_status_receiver.try_recv().is_ok() {}
2529
2530        // Unless we are trying to create/delete a new session, check status, or shutdown Servo,
2531        // we need to ensure that a session has previously been created.
2532        match msg.command {
2533            WebDriverCommand::NewSession(_) |
2534            WebDriverCommand::Status |
2535            WebDriverCommand::DeleteSession |
2536            WebDriverCommand::Extension(ServoExtensionCommand::Shutdown) => {},
2537            _ => {
2538                self.session()?;
2539            },
2540        }
2541
2542        match msg.command {
2543            WebDriverCommand::NewSession(ref parameters) => self.handle_new_session(parameters),
2544            WebDriverCommand::DeleteSession => self.handle_delete_session(),
2545            WebDriverCommand::Status => self.handle_status(),
2546            WebDriverCommand::AddCookie(ref parameters) => self.handle_add_cookie(parameters),
2547            WebDriverCommand::Get(ref parameters) => self.handle_get(parameters),
2548            WebDriverCommand::GetCurrentUrl => self.handle_current_url(),
2549            WebDriverCommand::GetWindowRect => {
2550                self.handle_window_rect(VerifyBrowsingContextIsOpen::Yes)
2551            },
2552            WebDriverCommand::SetWindowRect(ref size) => self.handle_set_window_rect(size),
2553            WebDriverCommand::IsEnabled(ref element) => self.handle_is_enabled(element),
2554            WebDriverCommand::IsSelected(ref element) => self.handle_is_selected(element),
2555            WebDriverCommand::GoBack => self.handle_go_back(),
2556            WebDriverCommand::GoForward => self.handle_go_forward(),
2557            WebDriverCommand::Refresh => self.handle_refresh(),
2558            WebDriverCommand::GetTitle => self.handle_title(),
2559            WebDriverCommand::GetWindowHandle => self.handle_window_handle(),
2560            WebDriverCommand::GetWindowHandles => self.handle_window_handles(),
2561            WebDriverCommand::NewWindow(ref parameters) => self.handle_new_window(parameters),
2562            WebDriverCommand::CloseWindow => self.handle_close_window(),
2563            WebDriverCommand::MaximizeWindow => self.handle_maximize_window(),
2564            WebDriverCommand::SwitchToFrame(ref parameters) => {
2565                self.handle_switch_to_frame(parameters)
2566            },
2567            WebDriverCommand::SwitchToParentFrame => self.handle_switch_to_parent_frame(),
2568            WebDriverCommand::SwitchToWindow(ref parameters) => {
2569                self.handle_switch_to_window(parameters)
2570            },
2571            WebDriverCommand::FindElement(ref parameters) => self.handle_find_element(parameters),
2572            WebDriverCommand::FindElements(ref parameters) => self.handle_find_elements(parameters),
2573            WebDriverCommand::FindElementElement(ref element, ref parameters) => {
2574                self.handle_find_element_from_element(element, parameters)
2575            },
2576            WebDriverCommand::FindElementElements(ref element, ref parameters) => {
2577                self.handle_find_elements_from_element(element, parameters)
2578            },
2579            WebDriverCommand::FindShadowRootElements(ref shadow_root, ref parameters) => {
2580                self.handle_find_elements_from_shadow_root(shadow_root, parameters)
2581            },
2582            WebDriverCommand::FindShadowRootElement(ref shadow_root, ref parameters) => {
2583                self.handle_find_element_from_shadow_root(shadow_root, parameters)
2584            },
2585            WebDriverCommand::GetShadowRoot(element) => self.handle_get_shadow_root(element),
2586            WebDriverCommand::GetNamedCookie(name) => self.handle_get_cookie(name),
2587            WebDriverCommand::GetCookies => self.handle_get_cookies(),
2588            WebDriverCommand::GetActiveElement => self.handle_active_element(),
2589            WebDriverCommand::GetComputedRole(ref element) => self.handle_computed_role(element),
2590            WebDriverCommand::GetElementRect(ref element) => self.handle_element_rect(element),
2591            WebDriverCommand::GetElementText(ref element) => self.handle_element_text(element),
2592            WebDriverCommand::GetElementTagName(ref element) => {
2593                self.handle_element_tag_name(element)
2594            },
2595            WebDriverCommand::GetElementAttribute(ref element, ref name) => {
2596                self.handle_element_attribute(element, name)
2597            },
2598            WebDriverCommand::GetElementProperty(ref element, ref name) => {
2599                self.handle_element_property(element, name)
2600            },
2601            WebDriverCommand::GetCSSValue(ref element, ref name) => {
2602                self.handle_element_css(element, name)
2603            },
2604            WebDriverCommand::GetPageSource => self.handle_get_page_source(),
2605            WebDriverCommand::PerformActions(actions_parameters) => {
2606                self.handle_perform_actions(actions_parameters)
2607            },
2608            WebDriverCommand::ReleaseActions => self.handle_release_actions(),
2609            WebDriverCommand::ExecuteScript(x) => self.handle_execute_script(x),
2610            WebDriverCommand::ExecuteAsyncScript(x) => self.handle_execute_async_script(x),
2611            WebDriverCommand::ElementSendKeys(ref element, ref keys) => {
2612                self.handle_element_send_keys(element, keys)
2613            },
2614            WebDriverCommand::ElementClear(ref element) => self.handle_element_clear(element),
2615            WebDriverCommand::ElementClick(ref element) => self.handle_element_click(element),
2616            WebDriverCommand::DismissAlert => self.handle_dismiss_alert(),
2617            WebDriverCommand::AcceptAlert => self.handle_accept_alert(),
2618            WebDriverCommand::GetAlertText => self.handle_get_alert_text(),
2619            WebDriverCommand::SendAlertText(text) => self.handle_send_alert_text(text.text),
2620            WebDriverCommand::DeleteCookies => self.handle_delete_cookies(),
2621            WebDriverCommand::DeleteCookie(name) => self.handle_delete_cookie(name),
2622            WebDriverCommand::GetTimeouts => self.handle_get_timeouts(),
2623            WebDriverCommand::SetTimeouts(ref x) => self.handle_set_timeouts(x),
2624            WebDriverCommand::TakeScreenshot => self.handle_take_screenshot(),
2625            WebDriverCommand::TakeElementScreenshot(ref x) => {
2626                self.handle_take_element_screenshot(x)
2627            },
2628            WebDriverCommand::Extension(extension) => match extension {
2629                ServoExtensionCommand::GetPrefs(ref x) => self.handle_get_prefs(x),
2630                ServoExtensionCommand::SetPrefs(ref x) => self.handle_set_prefs(x),
2631                ServoExtensionCommand::ResetPrefs(ref x) => self.handle_reset_prefs(x),
2632                ServoExtensionCommand::Shutdown => self.handle_shutdown(),
2633            },
2634            _ => Err(WebDriverError::new(
2635                ErrorStatus::UnsupportedOperation,
2636                format!("Command not implemented: {:?}", msg.command),
2637            )),
2638        }
2639    }
2640
2641    fn teardown_session(&mut self, _session: SessionTeardownKind) {
2642        self.session = None;
2643    }
2644}
2645
2646fn wait_for_ipc_response<T>(receiver: IpcReceiver<T>) -> Result<T, WebDriverError>
2647where
2648    T: for<'de> Deserialize<'de> + Serialize,
2649{
2650    receiver
2651        .recv()
2652        .map_err(|_| WebDriverError::new(ErrorStatus::NoSuchWindow, ""))
2653}
2654
2655/// This function is like `wait_for_ipc_response`, but works on a channel that
2656/// returns a `Result<T, ErrorStatus>`, mapping all errors into `WebDriverError`.
2657fn wait_for_ipc_response_flatten<T>(
2658    receiver: IpcReceiver<Result<T, ErrorStatus>>,
2659) -> Result<T, WebDriverError>
2660where
2661    T: for<'de> Deserialize<'de> + Serialize,
2662{
2663    match receiver.recv() {
2664        Ok(Ok(value)) => Ok(value),
2665        Ok(Err(error_status)) => Err(WebDriverError::new(error_status, "")),
2666        Err(_) => Err(WebDriverError::new(ErrorStatus::NoSuchWindow, "")),
2667    }
2668}
2669
2670fn wait_for_script_ipc_response_with_timeout<T>(
2671    receiver: IpcReceiver<T>,
2672    timeout: Option<Duration>,
2673) -> Result<T, WebDriverError>
2674where
2675    T: for<'de> Deserialize<'de> + Serialize,
2676{
2677    let Some(timeout) = timeout else {
2678        return wait_for_ipc_response(receiver);
2679    };
2680    receiver
2681        .try_recv_timeout(timeout)
2682        .map_err(|error| match error {
2683            TryRecvError::IpcError(_) => WebDriverError::new(ErrorStatus::NoSuchWindow, ""),
2684            TryRecvError::Empty => WebDriverError::new(ErrorStatus::ScriptTimeout, ""),
2685        })
2686}
2687
2688fn unwrap_first_element_response(res: WebDriverResponse) -> WebDriverResult<WebDriverResponse> {
2689    if let WebDriverResponse::Generic(ValueResponse(values)) = res {
2690        let arr = values.as_array().unwrap();
2691        if let Some(first) = arr.first() {
2692            Ok(WebDriverResponse::Generic(ValueResponse(first.clone())))
2693        } else {
2694            Err(WebDriverError::new(ErrorStatus::NoSuchElement, ""))
2695        }
2696    } else {
2697        unreachable!()
2698    }
2699}
2700
2701fn convert_keyboard_event_to_string(event: &KeyboardEvent) -> String {
2702    let key = &event.key;
2703    let named_key = match key {
2704        Key::Character(s) => return s.to_string(),
2705        Key::Named(named_key) => named_key,
2706    };
2707
2708    match event.location {
2709        Location::Left | Location::Standard => match named_key {
2710            NamedKey::Unidentified => '\u{E000}'.to_string(),
2711            NamedKey::Cancel => '\u{E001}'.to_string(),
2712            NamedKey::Help => '\u{E002}'.to_string(),
2713            NamedKey::Backspace => '\u{E003}'.to_string(),
2714            NamedKey::Tab => '\u{E004}'.to_string(),
2715            NamedKey::Clear => '\u{E005}'.to_string(),
2716            NamedKey::Enter => match event.code {
2717                Code::NumpadEnter => '\u{E007}'.to_string(),
2718                _ => '\u{E006}'.to_string(),
2719            },
2720            NamedKey::Shift => '\u{E008}'.to_string(),
2721            NamedKey::Control => '\u{E009}'.to_string(),
2722            NamedKey::Alt => '\u{E00A}'.to_string(),
2723            NamedKey::Pause => '\u{E00B}'.to_string(),
2724            NamedKey::Escape => '\u{E00C}'.to_string(),
2725            NamedKey::PageUp => '\u{E00E}'.to_string(),
2726            NamedKey::PageDown => '\u{E00F}'.to_string(),
2727            NamedKey::End => '\u{E010}'.to_string(),
2728            NamedKey::Home => '\u{E011}'.to_string(),
2729            NamedKey::ArrowLeft => '\u{E012}'.to_string(),
2730            NamedKey::ArrowUp => '\u{E013}'.to_string(),
2731            NamedKey::ArrowRight => '\u{E014}'.to_string(),
2732            NamedKey::ArrowDown => '\u{E015}'.to_string(),
2733            NamedKey::Insert => '\u{E016}'.to_string(),
2734            NamedKey::Delete => '\u{E017}'.to_string(),
2735            NamedKey::F1 => '\u{E031}'.to_string(),
2736            NamedKey::F2 => '\u{E032}'.to_string(),
2737            NamedKey::F3 => '\u{E033}'.to_string(),
2738            NamedKey::F4 => '\u{E034}'.to_string(),
2739            NamedKey::F5 => '\u{E035}'.to_string(),
2740            NamedKey::F6 => '\u{E036}'.to_string(),
2741            NamedKey::F7 => '\u{E037}'.to_string(),
2742            NamedKey::F8 => '\u{E038}'.to_string(),
2743            NamedKey::F9 => '\u{E039}'.to_string(),
2744            NamedKey::F10 => '\u{E03A}'.to_string(),
2745            NamedKey::F11 => '\u{E03B}'.to_string(),
2746            NamedKey::F12 => '\u{E03C}'.to_string(),
2747            NamedKey::Meta => '\u{E03D}'.to_string(),
2748            NamedKey::ZenkakuHankaku => '\u{E040}'.to_string(),
2749            _ => {
2750                error!("Unexpected NamedKey on send_keys");
2751                '\u{E000}'.to_string()
2752            },
2753        },
2754        Location::Right | Location::Numpad => match named_key {
2755            NamedKey::Shift => '\u{E050}'.to_string(),
2756            NamedKey::Control => '\u{E051}'.to_string(),
2757            NamedKey::Alt => '\u{E052}'.to_string(),
2758            NamedKey::Meta => '\u{E053}'.to_string(),
2759            NamedKey::PageUp => '\u{E054}'.to_string(),
2760            NamedKey::PageDown => '\u{E055}'.to_string(),
2761            NamedKey::End => '\u{E056}'.to_string(),
2762            NamedKey::Home => '\u{E057}'.to_string(),
2763            NamedKey::ArrowLeft => '\u{E058}'.to_string(),
2764            NamedKey::ArrowUp => '\u{E059}'.to_string(),
2765            NamedKey::ArrowRight => '\u{E05A}'.to_string(),
2766            NamedKey::ArrowDown => '\u{E05B}'.to_string(),
2767            NamedKey::Insert => '\u{E05C}'.to_string(),
2768            NamedKey::Delete => '\u{E05D}'.to_string(),
2769            _ => {
2770                error!("Unexpected NamedKey on send_keys");
2771                '\u{E000}'.to_string()
2772            },
2773        },
2774    }
2775}