diff options
Diffstat (limited to 'components/webdriver_server')
-rw-r--r-- | components/webdriver_server/actions.rs | 194 | ||||
-rw-r--r-- | components/webdriver_server/lib.rs | 114 |
2 files changed, 219 insertions, 89 deletions
diff --git a/components/webdriver_server/actions.rs b/components/webdriver_server/actions.rs index 7965120b0fd..f33310ac001 100644 --- a/components/webdriver_server/actions.rs +++ b/components/webdriver_server/actions.rs @@ -98,20 +98,83 @@ fn compute_tick_duration(tick_actions: &ActionSequence) -> u64 { impl Handler { // https://w3c.github.io/webdriver/#dfn-dispatch-actions pub(crate) fn dispatch_actions( - &mut self, + &self, actions_by_tick: &[ActionSequence], ) -> Result<(), ErrorStatus> { + // Step 1. Wait for an action queue token with input state. + let new_token = self.id_generator.next(); + assert!(self.current_action_id.get().is_none()); + self.current_action_id.set(Some(new_token)); + + // Step 2. Let actions result be the result of dispatch actions inner. + let res = self.dispatch_actions_inner(actions_by_tick); + + // Step 3. Dequeue input state's actions queue. + self.current_action_id.set(None); + + // Step 4. Return actions result. + res + } + + // https://w3c.github.io/webdriver/#dfn-dispatch-actions-inner + fn dispatch_actions_inner( + &self, + actions_by_tick: &[ActionSequence], + ) -> Result<(), ErrorStatus> { + // Step 1. For each item tick actions in actions by tick for tick_actions in actions_by_tick.iter() { + // Step 1.2. Let tick duration be the result of + // computing the tick duration with argument tick actions. let tick_duration = compute_tick_duration(tick_actions); + + // Step 1.3. Try to dispatch tick actions self.dispatch_tick_actions(tick_actions, tick_duration)?; + + // Step 1.4. Wait for + // The user agent event loop has spun enough times to process the DOM events + // generated by the last invocation of the dispatch tick actions steps. + // + // To ensure we wait for all events to be processed, only the last event in + // this tick action step holds the message id. + // Whenever a new event is generated, the message id is passed to it. + // + // TO-DO: remove the first match after webdriver_id is implemented in all commands + match tick_actions.actions { + ActionsType::Key { .. } | ActionsType::Wheel { .. } | ActionsType::Null { .. } => { + return Ok(()); + }, + _ => {}, + } + + match self.constellation_receiver.recv() { + Ok(response) => { + let current_waiting_id = self + .current_action_id + .get() + .expect("Current id should be set before dispat_actions_inner is called"); + + if current_waiting_id != response.id { + dbg!("Dispatch actions completed with wrong id in response"); + return Err(ErrorStatus::UnknownError); + } + }, + Err(error) => { + dbg!("Dispatch actions completed with IPC error: {:?}", error); + return Err(ErrorStatus::UnknownError); + }, + }; } + + // Step 2. Return success with data null. + dbg!("Dispatch actions completed successfully"); Ok(()) } - fn dispatch_general_action(&mut self, source_id: &str) { - self.session_mut() + fn dispatch_general_action(&self, source_id: &str) { + self.session() .unwrap() .input_state_table + .borrow_mut() .entry(source_id.to_string()) .or_insert(InputSourceState::Null); // https://w3c.github.io/webdriver/#dfn-dispatch-a-pause-action @@ -120,7 +183,7 @@ impl Handler { // https://w3c.github.io/webdriver/#dfn-dispatch-tick-actions fn dispatch_tick_actions( - &mut self, + &self, tick_actions: &ActionSequence, tick_duration: u64, ) -> Result<(), ErrorStatus> { @@ -138,9 +201,10 @@ impl Handler { self.dispatch_general_action(source_id); }, KeyActionItem::Key(action) => { - self.session_mut() + self.session() .unwrap() .input_state_table + .borrow_mut() .entry(source_id.to_string()) .or_insert(InputSourceState::Key(KeyInputState::new())); match action { @@ -149,7 +213,7 @@ impl Handler { // Step 9. If subtype is "keyDown", append a copy of action // object with the subtype property changed to "keyUp" to // input state's input cancel list. - self.session_mut().unwrap().input_cancel_list.push( + self.session().unwrap().input_cancel_list.borrow_mut().push( ActionSequence { id: source_id.into(), actions: ActionsType::Key { @@ -180,9 +244,10 @@ impl Handler { self.dispatch_general_action(source_id); }, PointerActionItem::Pointer(action) => { - self.session_mut() + self.session() .unwrap() .input_state_table + .borrow_mut() .entry(source_id.to_string()) .or_insert(InputSourceState::Pointer(PointerInputState::new( ¶meters.pointer_type, @@ -195,7 +260,7 @@ impl Handler { // Step 10. If subtype is "pointerDown", append a copy of action // object with the subtype property changed to "pointerUp" to // input state's input cancel list. - self.session_mut().unwrap().input_cancel_list.push( + self.session().unwrap().input_cancel_list.borrow_mut().push( ActionSequence { id: source_id.into(), actions: ActionsType::Pointer { @@ -232,9 +297,10 @@ impl Handler { self.dispatch_general_action(source_id) }, WheelActionItem::Wheel(action) => { - self.session_mut() + self.session() .unwrap() .input_state_table + .borrow_mut() .entry(source_id.to_string()) .or_insert(InputSourceState::Wheel); match action { @@ -252,12 +318,16 @@ impl Handler { } // https://w3c.github.io/webdriver/#dfn-dispatch-a-keydown-action - fn dispatch_keydown_action(&mut self, source_id: &str, action: &KeyDownAction) { - // Step 1 + fn dispatch_keydown_action(&self, source_id: &str, action: &KeyDownAction) { + let session = self.session().unwrap(); + let raw_key = action.value.chars().next().unwrap(); - let key_input_state = self.get_key_input_state_mut(source_id); + let mut input_state_table = session.input_state_table.borrow_mut(); + let key_input_state = match input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Key(key_input_state) => key_input_state, + _ => unreachable!(), + }; - // Step 2 - 11. Done by `keyboard-types` crate. let keyboard_event = key_input_state.dispatch_keydown(raw_key); // Step 12 @@ -271,12 +341,16 @@ impl Handler { } // https://w3c.github.io/webdriver/#dfn-dispatch-a-keyup-action - fn dispatch_keyup_action(&mut self, source_id: &str, action: &KeyUpAction) { - // Step 1 + fn dispatch_keyup_action(&self, source_id: &str, action: &KeyUpAction) { + let session = self.session().unwrap(); + let raw_key = action.value.chars().next().unwrap(); - let key_input_state = self.get_key_input_state_mut(source_id); + let mut input_state_table = session.input_state_table.borrow_mut(); + let key_input_state = match input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Key(key_input_state) => key_input_state, + _ => unreachable!(), + }; - // Step 2 - 11. Done by `keyboard-types` crate. if let Some(keyboard_event) = key_input_state.dispatch_keyup(raw_key) { // Step 12 let cmd_msg = WebDriverCommandMsg::KeyboardAction( @@ -289,44 +363,30 @@ impl Handler { } } - fn get_pointer_input_state_mut(&mut self, source_id: &str) -> &mut PointerInputState { - let session = self.session_mut().unwrap(); - let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Pointer(pointer_input_state) => pointer_input_state, - _ => unreachable!(), - }; - pointer_input_state - } + /// <https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerdown-action> + pub(crate) fn dispatch_pointerdown_action(&self, source_id: &str, action: &PointerDownAction) { + let session = self.session().unwrap(); - fn get_key_input_state_mut(&mut self, source_id: &str) -> &mut KeyInputState { - let session = self.session_mut().unwrap(); - let key_input_state = match session.input_state_table.get_mut(source_id).unwrap() { - InputSourceState::Key(key_input_state) => key_input_state, + let mut input_state_table = session.input_state_table.borrow_mut(); + let pointer_input_state = match input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Pointer(pointer_input_state) => pointer_input_state, _ => unreachable!(), }; - key_input_state - } - - // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerdown-action - pub(crate) fn dispatch_pointerdown_action( - &mut self, - source_id: &str, - action: &PointerDownAction, - ) { - let webview_id = self.session().unwrap().webview_id; - let pointer_input_state = self.get_pointer_input_state_mut(source_id); if pointer_input_state.pressed.contains(&action.button) { return; } pointer_input_state.pressed.insert(action.button); + let msg_id = self.current_action_id.get().unwrap(); let cmd_msg = WebDriverCommandMsg::MouseButtonAction( - webview_id, + session.webview_id, MouseButtonAction::Down, action.button.into(), pointer_input_state.x as f32, pointer_input_state.y as f32, + msg_id, + self.constellation_sender.clone(), ); self.constellation_chan .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) @@ -334,21 +394,29 @@ impl Handler { } // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerup-action - pub(crate) fn dispatch_pointerup_action(&mut self, source_id: &str, action: &PointerUpAction) { - let webview_id = self.session().unwrap().webview_id; - let pointer_input_state = self.get_pointer_input_state_mut(source_id); + pub(crate) fn dispatch_pointerup_action(&self, source_id: &str, action: &PointerUpAction) { + let session = self.session().unwrap(); + + let mut input_state_table = session.input_state_table.borrow_mut(); + let pointer_input_state = match input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Pointer(pointer_input_state) => pointer_input_state, + _ => unreachable!(), + }; if !pointer_input_state.pressed.contains(&action.button) { return; } pointer_input_state.pressed.remove(&action.button); + let msg_id = self.current_action_id.get().unwrap(); let cmd_msg = WebDriverCommandMsg::MouseButtonAction( - webview_id, + session.webview_id, MouseButtonAction::Up, action.button.into(), pointer_input_state.x as f32, pointer_input_state.y as f32, + msg_id, + self.constellation_sender.clone(), ); self.constellation_chan .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) @@ -357,7 +425,7 @@ impl Handler { // https://w3c.github.io/webdriver/#dfn-dispatch-a-pointermove-action pub(crate) fn dispatch_pointermove_action( - &mut self, + &self, source_id: &str, action: &PointerMoveAction, tick_duration: u64, @@ -370,10 +438,10 @@ impl Handler { // Steps 3 - 4 let (start_x, start_y) = match self - .session - .as_ref() + .session() .unwrap() .input_state_table + .borrow_mut() .get(source_id) .unwrap() { @@ -416,7 +484,7 @@ impl Handler { /// <https://w3c.github.io/webdriver/#dfn-perform-a-pointer-move> #[allow(clippy::too_many_arguments)] fn perform_pointer_move( - &mut self, + &self, source_id: &str, duration: u64, start_x: f64, @@ -425,9 +493,13 @@ impl Handler { target_y: f64, tick_start: Instant, ) { - let webview_id = self.session().unwrap().webview_id; - let constellation_chan = self.constellation_chan.clone(); - let pointer_input_state = self.get_pointer_input_state_mut(source_id); + let session = self.session().unwrap(); + let mut input_state_table = session.input_state_table.borrow_mut(); + let pointer_input_state = match input_state_table.get_mut(source_id).unwrap() { + InputSourceState::Pointer(pointer_input_state) => pointer_input_state, + _ => unreachable!(), + }; + loop { // Step 1 let time_delta = tick_start.elapsed().as_millis(); @@ -459,9 +531,15 @@ impl Handler { // Step 7 if x != current_x || y != current_y { // Step 7.2 - let cmd_msg = WebDriverCommandMsg::MouseMoveAction(webview_id, x as f32, y as f32); - //TODO: Need Synchronization here before updating `pointer_input_state` - constellation_chan + let msg_id = self.current_action_id.get().unwrap(); + let cmd_msg = WebDriverCommandMsg::MouseMoveAction( + session.webview_id, + x as f32, + y as f32, + msg_id, + self.constellation_sender.clone(), + ); + self.constellation_chan .send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg)) .unwrap(); // Step 7.3 @@ -481,7 +559,7 @@ impl Handler { /// <https://w3c.github.io/webdriver/#dfn-dispatch-a-scroll-action> fn dispatch_scroll_action( - &mut self, + &self, action: &WheelScrollAction, tick_duration: u64, ) -> Result<(), ErrorStatus> { @@ -546,7 +624,7 @@ impl Handler { /// <https://w3c.github.io/webdriver/#dfn-perform-a-scroll> #[allow(clippy::too_many_arguments)] fn perform_scroll( - &mut self, + &self, duration: u64, x: i64, y: i64, @@ -556,7 +634,7 @@ impl Handler { mut curr_delta_y: i64, tick_start: Instant, ) { - let session = self.session_mut().unwrap(); + let session = self.session().unwrap(); // Step 1 let time_delta = tick_start.elapsed().as_millis(); diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index 5735594b058..0e3fa9058d6 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -10,11 +10,12 @@ mod actions; mod capabilities; use std::borrow::ToOwned; +use std::cell::{Cell, RefCell}; use std::collections::{BTreeMap, HashMap}; use std::io::Cursor; use std::net::{SocketAddr, SocketAddrV4}; use std::time::Duration; -use std::{env, fmt, mem, process, thread}; +use std::{env, fmt, process, thread}; use base::id::{BrowsingContextId, WebViewId}; use base64::Engine; @@ -23,8 +24,9 @@ use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection}; use cookie::{CookieBuilder, Expiration}; use crossbeam_channel::{Receiver, Sender, after, select, unbounded}; use embedder_traits::{ - MouseButton, WebDriverCommandMsg, WebDriverCookieError, WebDriverFrameId, WebDriverJSError, - WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, WebDriverScriptCommand, + MouseButton, WebDriverCommandMsg, WebDriverCommandResponse, WebDriverCookieError, + WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, + WebDriverMessageId, WebDriverScriptCommand, }; use euclid::{Rect, Size2D}; use http::method::Method; @@ -43,8 +45,8 @@ use servo_url::ServoUrl; use style_traits::CSSPixel; use uuid::Uuid; use webdriver::actions::{ - ActionSequence, PointerDownAction, PointerMoveAction, PointerOrigin, PointerType, - PointerUpAction, + ActionSequence, ActionsType, PointerAction, PointerActionItem, PointerActionParameters, + PointerDownAction, PointerMoveAction, PointerOrigin, PointerType, PointerUpAction, }; use webdriver::capabilities::CapabilitiesMatching; use webdriver::command::{ @@ -64,6 +66,26 @@ use webdriver::server::{self, Session, SessionTeardownKind, WebDriverHandler}; use crate::actions::{InputSourceState, PointerInputState}; +#[derive(Default)] +pub struct WebDriverMessageIdGenerator { + counter: Cell<usize>, +} + +impl WebDriverMessageIdGenerator { + pub fn new() -> Self { + Self { + counter: Cell::new(0), + } + } + + /// Returns a unique ID. + pub fn next(&self) -> WebDriverMessageId { + let id = self.counter.get(); + self.counter.set(id + 1); + WebDriverMessageId(id) + } +} + fn extension_routes() -> Vec<(Method, &'static str, ServoExtensionRoute)> { vec![ ( @@ -145,10 +167,11 @@ pub struct WebDriverSession { unhandled_prompt_behavior: String, - // https://w3c.github.io/webdriver/#dfn-input-state-table - input_state_table: HashMap<String, InputSourceState>, - // https://w3c.github.io/webdriver/#dfn-input-cancel-list - input_cancel_list: Vec<ActionSequence>, + /// <https://w3c.github.io/webdriver/#dfn-input-state-map> + input_state_table: RefCell<HashMap<String, InputSourceState>>, + + /// <https://w3c.github.io/webdriver/#dfn-input-cancel-list> + input_cancel_list: RefCell<Vec<ActionSequence>>, } impl WebDriverSession { @@ -172,8 +195,8 @@ impl WebDriverSession { strict_file_interactability: false, unhandled_prompt_behavior: "dismiss and notify".to_string(), - input_state_table: HashMap::new(), - input_cancel_list: Vec::new(), + input_state_table: RefCell::new(HashMap::new()), + input_cancel_list: RefCell::new(Vec::new()), } } } @@ -187,8 +210,22 @@ struct Handler { /// for it to send us a load-status. Messages sent on it /// will be forwarded to the load_status_receiver. load_status_sender: IpcSender<WebDriverLoadStatus>, + session: Option<WebDriverSession>, + + /// The channel for sending Webdriver messages to the constellation. constellation_chan: Sender<EmbedderToConstellationMessage>, + + /// The IPC sender which we can clone and pass along to the constellation + constellation_sender: IpcSender<WebDriverCommandResponse>, + + /// Receiver notification from the constellation when a command is completed + constellation_receiver: IpcReceiver<WebDriverCommandResponse>, + + id_generator: WebDriverMessageIdGenerator, + + current_action_id: Cell<Option<WebDriverMessageId>>, + resize_timeout: u32, } @@ -409,11 +446,18 @@ impl Handler { let (load_status_sender, receiver) = ipc::channel().unwrap(); let (sender, load_status_receiver) = unbounded(); ROUTER.route_ipc_receiver_to_crossbeam_sender(receiver, sender); + + let (constellation_sender, constellation_receiver) = ipc::channel().unwrap(); + Handler { load_status_sender, load_status_receiver, session: None, constellation_chan, + constellation_sender, + constellation_receiver, + id_generator: WebDriverMessageIdGenerator::new(), + current_action_id: Cell::new(None), resize_timeout: 500, } } @@ -1445,18 +1489,13 @@ impl Handler { } fn handle_release_actions(&mut self) -> WebDriverResult<WebDriverResponse> { - let input_cancel_list = { - let session = self.session_mut()?; - session.input_cancel_list.reverse(); - mem::take(&mut session.input_cancel_list) - }; - + let input_cancel_list = self.session().unwrap().input_cancel_list.borrow(); if let Err(error) = self.dispatch_actions(&input_cancel_list) { return Err(WebDriverError::new(error, "")); } - let session = self.session_mut()?; - session.input_state_table = HashMap::new(); + let session = self.session()?; + session.input_state_table.borrow_mut().clear(); Ok(WebDriverResponse::Void) } @@ -1614,7 +1653,7 @@ impl Handler { let id = Uuid::new_v4().to_string(); // Step 8.1 - self.session_mut()?.input_state_table.insert( + self.session_mut()?.input_state_table.borrow_mut().insert( id.clone(), InputSourceState::Pointer(PointerInputState::new(&PointerType::Mouse)), ); @@ -1645,19 +1684,31 @@ impl Handler { ..Default::default() }; - // Step 8.16 Dispatch a list of actions with input state, - // actions, session's current browsing context, and actions options. - if let Err(error) = - self.dispatch_pointermove_action(&id, &pointer_move_action, 0) - { - return Err(WebDriverError::new(error, "")); - } + let action_sequence = ActionSequence { + id: id.clone(), + actions: ActionsType::Pointer { + parameters: PointerActionParameters { + pointer_type: PointerType::Mouse, + }, + actions: vec![ + PointerActionItem::Pointer(PointerAction::Move( + pointer_move_action, + )), + PointerActionItem::Pointer(PointerAction::Down( + pointer_down_action, + )), + PointerActionItem::Pointer(PointerAction::Up(pointer_up_action)), + ], + }, + }; - self.dispatch_pointerdown_action(&id, &pointer_down_action); - self.dispatch_pointerup_action(&id, &pointer_up_action); + let _ = self.dispatch_actions(&[action_sequence]); // Step 8.17 Remove an input source with input state and input id. - self.session_mut()?.input_state_table.remove(&id); + self.session_mut()? + .input_state_table + .borrow_mut() + .remove(&id); // Step 13 Ok(WebDriverResponse::Void) @@ -1709,7 +1760,8 @@ impl Handler { "Unexpected screenshot pixel format" ); - let rgb = RgbaImage::from_raw(img.width, img.height, img.bytes().to_vec()).unwrap(); + let rgb = + RgbaImage::from_raw(img.width, img.height, img.first_frame().bytes.to_vec()).unwrap(); let mut png_data = Cursor::new(Vec::new()); DynamicImage::ImageRgba8(rgb) .write_to(&mut png_data, ImageFormat::Png) |