aboutsummaryrefslogtreecommitdiffstats
path: root/components/webdriver_server
diff options
context:
space:
mode:
Diffstat (limited to 'components/webdriver_server')
-rw-r--r--components/webdriver_server/actions.rs194
-rw-r--r--components/webdriver_server/lib.rs114
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(
&parameters.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)