mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Synchronize dispatch_actions
in WebDriver (#36932)
Implement missing synchronization in `dispatch_actions` of `WebDriver`. https://w3c.github.io/webdriver/#dispatching-actions > The user agent event loop has spun enough times to process the DOM events generated by the last invocation of the >[dispatch tick actions](https://w3c.github.io/webdriver/#dfn-dispatch-tick-actions) steps. - Add a way for `ScriptThread` to notify `WebDriver` about the completion of input commands. - Add a `webdriver_id` field for `InputEvent`. `ScriptThread` uses it to distinguish WebDriver events and sends notification. Tests: `./mach test-wpt --product servodriver -r tests\wpt\tests\webdriver\tests\classic\element_click\events.py` pass if `hit_testing` pass. Check [issue](https://github.com/servo/servo/issues/36676#issuecomment-2882917136) cc: @xiaochengh --------- Signed-off-by: batu_hoang <longvatrong111@gmail.com> Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
3a527d784b
commit
f52fa9b672
13 changed files with 471 additions and 140 deletions
|
@ -620,29 +620,37 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
CompositorMsg::WebDriverMouseButtonEvent(webview_id, action, button, x, y) => {
|
CompositorMsg::WebDriverMouseButtonEvent(
|
||||||
|
webview_id,
|
||||||
|
action,
|
||||||
|
button,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
message_id,
|
||||||
|
) => {
|
||||||
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
|
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
|
||||||
warn!("Handling input event for unknown webview: {webview_id}");
|
warn!("Handling input event for unknown webview: {webview_id}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let dppx = webview_renderer.device_pixels_per_page_pixel();
|
let dppx = webview_renderer.device_pixels_per_page_pixel();
|
||||||
let point = dppx.transform_point(Point2D::new(x, y));
|
let point = dppx.transform_point(Point2D::new(x, y));
|
||||||
webview_renderer.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent {
|
webview_renderer.dispatch_input_event(
|
||||||
point,
|
InputEvent::MouseButton(MouseButtonEvent::new(action, button, point))
|
||||||
action,
|
.with_webdriver_message_id(Some(message_id)),
|
||||||
button,
|
);
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y) => {
|
CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y, message_id) => {
|
||||||
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
|
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
|
||||||
warn!("Handling input event for unknown webview: {webview_id}");
|
warn!("Handling input event for unknown webview: {webview_id}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let dppx = webview_renderer.device_pixels_per_page_pixel();
|
let dppx = webview_renderer.device_pixels_per_page_pixel();
|
||||||
let point = dppx.transform_point(Point2D::new(x, y));
|
let point = dppx.transform_point(Point2D::new(x, y));
|
||||||
webview_renderer
|
webview_renderer.dispatch_input_event(
|
||||||
.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point }));
|
InputEvent::MouseMove(MouseMoveEvent::new(point))
|
||||||
|
.with_webdriver_message_id(Some(message_id)),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
CompositorMsg::WebDriverWheelScrollEvent(webview_id, x, y, delta_x, delta_y) => {
|
CompositorMsg::WebDriverWheelScrollEvent(webview_id, x, y, delta_x, delta_y) => {
|
||||||
|
|
|
@ -687,17 +687,17 @@ impl WebViewRenderer {
|
||||||
/// <http://w3c.github.io/touch-events/#mouse-events>
|
/// <http://w3c.github.io/touch-events/#mouse-events>
|
||||||
fn simulate_mouse_click(&mut self, point: DevicePoint) {
|
fn simulate_mouse_click(&mut self, point: DevicePoint) {
|
||||||
let button = MouseButton::Left;
|
let button = MouseButton::Left;
|
||||||
self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point }));
|
self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point)));
|
||||||
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent {
|
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
|
||||||
|
MouseButtonAction::Down,
|
||||||
button,
|
button,
|
||||||
action: MouseButtonAction::Down,
|
|
||||||
point,
|
point,
|
||||||
}));
|
)));
|
||||||
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent {
|
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
|
||||||
|
MouseButtonAction::Up,
|
||||||
button,
|
button,
|
||||||
action: MouseButtonAction::Up,
|
|
||||||
point,
|
point,
|
||||||
}));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn notify_scroll_event(
|
pub(crate) fn notify_scroll_event(
|
||||||
|
|
|
@ -132,7 +132,7 @@ use embedder_traits::{
|
||||||
FocusSequenceNumber, ImeEvent, InputEvent, JSValue, JavaScriptEvaluationError,
|
FocusSequenceNumber, ImeEvent, InputEvent, JSValue, JavaScriptEvaluationError,
|
||||||
JavaScriptEvaluationId, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState,
|
JavaScriptEvaluationId, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState,
|
||||||
MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg,
|
MouseButton, MouseButtonAction, MouseButtonEvent, Theme, ViewportDetails, WebDriverCommandMsg,
|
||||||
WebDriverLoadStatus,
|
WebDriverCommandResponse, WebDriverLoadStatus,
|
||||||
};
|
};
|
||||||
use euclid::Size2D;
|
use euclid::Size2D;
|
||||||
use euclid::default::Size2D as UntypedSize2D;
|
use euclid::default::Size2D as UntypedSize2D;
|
||||||
|
@ -532,6 +532,8 @@ pub struct InitialConstellationState {
|
||||||
struct WebDriverData {
|
struct WebDriverData {
|
||||||
load_channel: Option<(PipelineId, IpcSender<WebDriverLoadStatus>)>,
|
load_channel: Option<(PipelineId, IpcSender<WebDriverLoadStatus>)>,
|
||||||
resize_channel: Option<IpcSender<Size2D<f32, CSSPixel>>>,
|
resize_channel: Option<IpcSender<Size2D<f32, CSSPixel>>>,
|
||||||
|
// Forward responses from the script thread to the webdriver server.
|
||||||
|
input_command_response_sender: Option<IpcSender<WebDriverCommandResponse>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebDriverData {
|
impl WebDriverData {
|
||||||
|
@ -539,6 +541,7 @@ impl WebDriverData {
|
||||||
WebDriverData {
|
WebDriverData {
|
||||||
load_channel: None,
|
load_channel: None,
|
||||||
resize_channel: None,
|
resize_channel: None,
|
||||||
|
input_command_response_sender: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1867,6 +1870,18 @@ where
|
||||||
ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result) => {
|
ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result) => {
|
||||||
self.handle_finish_javascript_evaluation(evaluation_id, result)
|
self.handle_finish_javascript_evaluation(evaluation_id, result)
|
||||||
},
|
},
|
||||||
|
ScriptToConstellationMessage::WebDriverInputComplete(msg_id) => {
|
||||||
|
if let Some(ref reply_sender) = self.webdriver.input_command_response_sender {
|
||||||
|
reply_sender
|
||||||
|
.send(WebDriverCommandResponse { id: msg_id })
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
warn!("Failed to send WebDriverInputComplete {:?}", msg_id);
|
||||||
|
self.webdriver.input_command_response_sender = None;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
warn!("No WebDriver input_command_response_sender");
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4836,7 +4851,11 @@ where
|
||||||
mouse_button,
|
mouse_button,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
msg_id,
|
||||||
|
response_sender,
|
||||||
) => {
|
) => {
|
||||||
|
self.webdriver.input_command_response_sender = Some(response_sender);
|
||||||
|
|
||||||
self.compositor_proxy
|
self.compositor_proxy
|
||||||
.send(CompositorMsg::WebDriverMouseButtonEvent(
|
.send(CompositorMsg::WebDriverMouseButtonEvent(
|
||||||
webview_id,
|
webview_id,
|
||||||
|
@ -4844,11 +4863,16 @@ where
|
||||||
mouse_button,
|
mouse_button,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
msg_id,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
WebDriverCommandMsg::MouseMoveAction(webview_id, x, y) => {
|
WebDriverCommandMsg::MouseMoveAction(webview_id, x, y, msg_id, response_sender) => {
|
||||||
|
self.webdriver.input_command_response_sender = Some(response_sender);
|
||||||
|
|
||||||
self.compositor_proxy
|
self.compositor_proxy
|
||||||
.send(CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y));
|
.send(CompositorMsg::WebDriverMouseMoveEvent(
|
||||||
|
webview_id, x, y, msg_id,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
WebDriverCommandMsg::WheelScrollAction(webview, x, y, delta_x, delta_y) => {
|
WebDriverCommandMsg::WheelScrollAction(webview, x, y, delta_x, delta_y) => {
|
||||||
self.compositor_proxy
|
self.compositor_proxy
|
||||||
|
|
|
@ -177,6 +177,7 @@ mod from_script {
|
||||||
Self::TitleChanged(..) => target!("TitleChanged"),
|
Self::TitleChanged(..) => target!("TitleChanged"),
|
||||||
Self::IFrameSizes(..) => target!("IFrameSizes"),
|
Self::IFrameSizes(..) => target!("IFrameSizes"),
|
||||||
Self::ReportMemory(..) => target!("ReportMemory"),
|
Self::ReportMemory(..) => target!("ReportMemory"),
|
||||||
|
Self::WebDriverInputComplete(..) => target!("WebDriverInputComplete"),
|
||||||
Self::FinishJavaScriptEvaluation(..) => target!("FinishJavaScriptEvaluation"),
|
Self::FinishJavaScriptEvaluation(..) => target!("FinishJavaScriptEvaluation"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1081,6 +1081,10 @@ impl ScriptThread {
|
||||||
for event in document.take_pending_input_events().into_iter() {
|
for event in document.take_pending_input_events().into_iter() {
|
||||||
document.update_active_keyboard_modifiers(event.active_keyboard_modifiers);
|
document.update_active_keyboard_modifiers(event.active_keyboard_modifiers);
|
||||||
|
|
||||||
|
// We do this now, because the event is consumed below, but the order doesn't really
|
||||||
|
// matter as the event will be handled before any new ScriptThread messages are processed.
|
||||||
|
self.notify_webdriver_input_event_completed(pipeline_id, &event.event);
|
||||||
|
|
||||||
match event.event {
|
match event.event {
|
||||||
InputEvent::MouseButton(mouse_button_event) => {
|
InputEvent::MouseButton(mouse_button_event) => {
|
||||||
document.handle_mouse_button_event(
|
document.handle_mouse_button_event(
|
||||||
|
@ -1144,6 +1148,19 @@ impl ScriptThread {
|
||||||
ScriptThread::set_user_interacting(false);
|
ScriptThread::set_user_interacting(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn notify_webdriver_input_event_completed(&self, pipeline_id: PipelineId, event: &InputEvent) {
|
||||||
|
let Some(id) = event.webdriver_message_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(error) = self.senders.pipeline_to_constellation_sender.send((
|
||||||
|
pipeline_id,
|
||||||
|
ScriptToConstellationMessage::WebDriverInputComplete(id),
|
||||||
|
)) {
|
||||||
|
warn!("ScriptThread failed to send WebDriverInputComplete {id:?}: {error:?}",);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#update-the-rendering>
|
/// <https://html.spec.whatwg.org/multipage/#update-the-rendering>
|
||||||
///
|
///
|
||||||
/// Attempt to update the rendering and then do a microtask checkpoint if rendering was actually
|
/// Attempt to update the rendering and then do a microtask checkpoint if rendering was actually
|
||||||
|
@ -3420,7 +3437,7 @@ impl ScriptThread {
|
||||||
// the pointer, when the user presses down and releases the primary pointer button"
|
// the pointer, when the user presses down and releases the primary pointer button"
|
||||||
|
|
||||||
// Servo-specific: Trigger if within 10px of the down point
|
// Servo-specific: Trigger if within 10px of the down point
|
||||||
if let InputEvent::MouseButton(mouse_button_event) = event.event {
|
if let InputEvent::MouseButton(mouse_button_event) = &event.event {
|
||||||
if let MouseButton::Left = mouse_button_event.button {
|
if let MouseButton::Left = mouse_button_event.button {
|
||||||
match mouse_button_event.action {
|
match mouse_button_event.action {
|
||||||
MouseButtonAction::Up => {
|
MouseButtonAction::Up => {
|
||||||
|
@ -3429,16 +3446,23 @@ impl ScriptThread {
|
||||||
let pixel_dist =
|
let pixel_dist =
|
||||||
(pixel_dist.x * pixel_dist.x + pixel_dist.y * pixel_dist.y).sqrt();
|
(pixel_dist.x * pixel_dist.x + pixel_dist.y * pixel_dist.y).sqrt();
|
||||||
if pixel_dist < 10.0 * document.window().device_pixel_ratio().get() {
|
if pixel_dist < 10.0 * document.window().device_pixel_ratio().get() {
|
||||||
document.note_pending_input_event(event.clone());
|
// Pass webdriver_id to the newly generated click event
|
||||||
|
document.note_pending_input_event(ConstellationInputEvent {
|
||||||
|
hit_test_result: event.hit_test_result.clone(),
|
||||||
|
pressed_mouse_buttons: event.pressed_mouse_buttons,
|
||||||
|
active_keyboard_modifiers: event.active_keyboard_modifiers,
|
||||||
|
event: event.event.clone().with_webdriver_message_id(None),
|
||||||
|
});
|
||||||
document.note_pending_input_event(ConstellationInputEvent {
|
document.note_pending_input_event(ConstellationInputEvent {
|
||||||
hit_test_result: event.hit_test_result,
|
hit_test_result: event.hit_test_result,
|
||||||
pressed_mouse_buttons: event.pressed_mouse_buttons,
|
pressed_mouse_buttons: event.pressed_mouse_buttons,
|
||||||
active_keyboard_modifiers: event.active_keyboard_modifiers,
|
active_keyboard_modifiers: event.active_keyboard_modifiers,
|
||||||
event: InputEvent::MouseButton(MouseButtonEvent {
|
event: InputEvent::MouseButton(MouseButtonEvent::new(
|
||||||
action: MouseButtonAction::Click,
|
MouseButtonAction::Click,
|
||||||
button: mouse_button_event.button,
|
mouse_button_event.button,
|
||||||
point: mouse_button_event.point,
|
mouse_button_event.point,
|
||||||
}),
|
))
|
||||||
|
.with_webdriver_message_id(event.event.webdriver_message_id()),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use base::id::{PipelineId, WebViewId};
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use embedder_traits::{
|
use embedder_traits::{
|
||||||
AnimationState, EventLoopWaker, MouseButton, MouseButtonAction, TouchEventResult,
|
AnimationState, EventLoopWaker, MouseButton, MouseButtonAction, TouchEventResult,
|
||||||
|
WebDriverMessageId,
|
||||||
};
|
};
|
||||||
use euclid::Rect;
|
use euclid::Rect;
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::IpcSender;
|
||||||
|
@ -101,9 +102,16 @@ pub enum CompositorMsg {
|
||||||
/// The load of a page has completed
|
/// The load of a page has completed
|
||||||
LoadComplete(WebViewId),
|
LoadComplete(WebViewId),
|
||||||
/// WebDriver mouse button event
|
/// WebDriver mouse button event
|
||||||
WebDriverMouseButtonEvent(WebViewId, MouseButtonAction, MouseButton, f32, f32),
|
WebDriverMouseButtonEvent(
|
||||||
|
WebViewId,
|
||||||
|
MouseButtonAction,
|
||||||
|
MouseButton,
|
||||||
|
f32,
|
||||||
|
f32,
|
||||||
|
WebDriverMessageId,
|
||||||
|
),
|
||||||
/// WebDriver mouse move event
|
/// WebDriver mouse move event
|
||||||
WebDriverMouseMoveEvent(WebViewId, f32, f32),
|
WebDriverMouseMoveEvent(WebViewId, f32, f32, WebDriverMessageId),
|
||||||
// Webdriver wheel scroll event
|
// Webdriver wheel scroll event
|
||||||
WebDriverWheelScrollEvent(WebViewId, f32, f32, f64, f64),
|
WebDriverWheelScrollEvent(WebViewId, f32, f32, f64, f64),
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, Worke
|
||||||
use embedder_traits::{
|
use embedder_traits::{
|
||||||
AnimationState, EmbedderMsg, FocusSequenceNumber, JSValue, JavaScriptEvaluationError,
|
AnimationState, EmbedderMsg, FocusSequenceNumber, JSValue, JavaScriptEvaluationError,
|
||||||
JavaScriptEvaluationId, MediaSessionEvent, TouchEventResult, ViewportDetails,
|
JavaScriptEvaluationId, MediaSessionEvent, TouchEventResult, ViewportDetails,
|
||||||
|
WebDriverMessageId,
|
||||||
};
|
};
|
||||||
use euclid::default::Size2D as UntypedSize2D;
|
use euclid::default::Size2D as UntypedSize2D;
|
||||||
use http::{HeaderMap, Method};
|
use http::{HeaderMap, Method};
|
||||||
|
@ -652,6 +653,8 @@ pub enum ScriptToConstellationMessage {
|
||||||
JavaScriptEvaluationId,
|
JavaScriptEvaluationId,
|
||||||
Result<JSValue, JavaScriptEvaluationError>,
|
Result<JSValue, JavaScriptEvaluationError>,
|
||||||
),
|
),
|
||||||
|
/// Notify the completion of a webdriver command.
|
||||||
|
WebDriverInputComplete(WebDriverMessageId),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for ScriptToConstellationMessage {
|
impl fmt::Debug for ScriptToConstellationMessage {
|
||||||
|
|
|
@ -8,6 +8,8 @@ use malloc_size_of_derive::MallocSizeOf;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use webrender_api::units::DevicePoint;
|
use webrender_api::units::DevicePoint;
|
||||||
|
|
||||||
|
use crate::WebDriverMessageId;
|
||||||
|
|
||||||
/// An input event that is sent from the embedder to Servo.
|
/// An input event that is sent from the embedder to Servo.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub enum InputEvent {
|
pub enum InputEvent {
|
||||||
|
@ -42,6 +44,38 @@ impl InputEvent {
|
||||||
InputEvent::Wheel(event) => Some(event.point),
|
InputEvent::Wheel(event) => Some(event.point),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn webdriver_message_id(&self) -> Option<WebDriverMessageId> {
|
||||||
|
match self {
|
||||||
|
InputEvent::EditingAction(..) => None,
|
||||||
|
InputEvent::Gamepad(..) => None,
|
||||||
|
InputEvent::Ime(..) => None,
|
||||||
|
InputEvent::Keyboard(..) => None,
|
||||||
|
InputEvent::MouseButton(event) => event.webdriver_id,
|
||||||
|
InputEvent::MouseMove(event) => event.webdriver_id,
|
||||||
|
InputEvent::Touch(..) => None,
|
||||||
|
InputEvent::Wheel(..) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_webdriver_message_id(self, webdriver_id: Option<WebDriverMessageId>) -> Self {
|
||||||
|
match self {
|
||||||
|
InputEvent::EditingAction(..) => {},
|
||||||
|
InputEvent::Gamepad(..) => {},
|
||||||
|
InputEvent::Ime(..) => {},
|
||||||
|
InputEvent::Keyboard(..) => {},
|
||||||
|
InputEvent::MouseButton(mut event) => {
|
||||||
|
event.webdriver_id = webdriver_id;
|
||||||
|
},
|
||||||
|
InputEvent::MouseMove(mut event) => {
|
||||||
|
event.webdriver_id = webdriver_id;
|
||||||
|
},
|
||||||
|
InputEvent::Touch(..) => {},
|
||||||
|
InputEvent::Wheel(..) => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
|
@ -49,6 +83,18 @@ pub struct MouseButtonEvent {
|
||||||
pub action: MouseButtonAction,
|
pub action: MouseButtonAction,
|
||||||
pub button: MouseButton,
|
pub button: MouseButton,
|
||||||
pub point: DevicePoint,
|
pub point: DevicePoint,
|
||||||
|
webdriver_id: Option<WebDriverMessageId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MouseButtonEvent {
|
||||||
|
pub fn new(action: MouseButtonAction, button: MouseButton, point: DevicePoint) -> Self {
|
||||||
|
Self {
|
||||||
|
action,
|
||||||
|
button,
|
||||||
|
point,
|
||||||
|
webdriver_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
|
@ -102,6 +148,16 @@ pub enum MouseButtonAction {
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
pub struct MouseMoveEvent {
|
pub struct MouseMoveEvent {
|
||||||
pub point: DevicePoint,
|
pub point: DevicePoint,
|
||||||
|
webdriver_id: Option<WebDriverMessageId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MouseMoveEvent {
|
||||||
|
pub fn new(point: DevicePoint) -> Self {
|
||||||
|
Self {
|
||||||
|
point,
|
||||||
|
webdriver_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of input represented by a multi-touch event.
|
/// The type of input represented by a multi-touch event.
|
||||||
|
|
|
@ -24,6 +24,9 @@ use webrender_api::units::DeviceIntSize;
|
||||||
|
|
||||||
use crate::{MouseButton, MouseButtonAction};
|
use crate::{MouseButton, MouseButtonAction};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
||||||
|
pub struct WebDriverMessageId(pub usize);
|
||||||
|
|
||||||
/// Messages to the constellation originating from the WebDriver server.
|
/// Messages to the constellation originating from the WebDriver server.
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub enum WebDriverCommandMsg {
|
pub enum WebDriverCommandMsg {
|
||||||
|
@ -41,9 +44,23 @@ pub enum WebDriverCommandMsg {
|
||||||
/// Act as if keys were pressed or release in the browsing context with the given ID.
|
/// Act as if keys were pressed or release in the browsing context with the given ID.
|
||||||
KeyboardAction(BrowsingContextId, KeyboardEvent),
|
KeyboardAction(BrowsingContextId, KeyboardEvent),
|
||||||
/// Act as if the mouse was clicked in the browsing context with the given ID.
|
/// Act as if the mouse was clicked in the browsing context with the given ID.
|
||||||
MouseButtonAction(WebViewId, MouseButtonAction, MouseButton, f32, f32),
|
MouseButtonAction(
|
||||||
|
WebViewId,
|
||||||
|
MouseButtonAction,
|
||||||
|
MouseButton,
|
||||||
|
f32,
|
||||||
|
f32,
|
||||||
|
WebDriverMessageId,
|
||||||
|
IpcSender<WebDriverCommandResponse>,
|
||||||
|
),
|
||||||
/// Act as if the mouse was moved in the browsing context with the given ID.
|
/// Act as if the mouse was moved in the browsing context with the given ID.
|
||||||
MouseMoveAction(WebViewId, f32, f32),
|
MouseMoveAction(
|
||||||
|
WebViewId,
|
||||||
|
f32,
|
||||||
|
f32,
|
||||||
|
WebDriverMessageId,
|
||||||
|
IpcSender<WebDriverCommandResponse>,
|
||||||
|
),
|
||||||
/// Act as if the mouse wheel is scrolled in the browsing context given the given ID.
|
/// Act as if the mouse wheel is scrolled in the browsing context given the given ID.
|
||||||
WheelScrollAction(WebViewId, f32, f32, f64, f64),
|
WheelScrollAction(WebViewId, f32, f32, f64, f64),
|
||||||
/// Set the window size.
|
/// Set the window size.
|
||||||
|
@ -188,6 +205,11 @@ pub enum WebDriverFrameId {
|
||||||
Parent,
|
Parent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct WebDriverCommandResponse {
|
||||||
|
pub id: WebDriverMessageId,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub enum WebDriverLoadStatus {
|
pub enum WebDriverLoadStatus {
|
||||||
Complete,
|
Complete,
|
||||||
|
|
|
@ -98,20 +98,83 @@ fn compute_tick_duration(tick_actions: &ActionSequence) -> u64 {
|
||||||
impl Handler {
|
impl Handler {
|
||||||
// https://w3c.github.io/webdriver/#dfn-dispatch-actions
|
// https://w3c.github.io/webdriver/#dfn-dispatch-actions
|
||||||
pub(crate) fn dispatch_actions(
|
pub(crate) fn dispatch_actions(
|
||||||
&mut self,
|
&self,
|
||||||
actions_by_tick: &[ActionSequence],
|
actions_by_tick: &[ActionSequence],
|
||||||
) -> Result<(), ErrorStatus> {
|
) -> 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() {
|
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);
|
let tick_duration = compute_tick_duration(tick_actions);
|
||||||
|
|
||||||
|
// Step 1.3. Try to dispatch tick actions
|
||||||
self.dispatch_tick_actions(tick_actions, tick_duration)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_general_action(&mut self, source_id: &str) {
|
fn dispatch_general_action(&self, source_id: &str) {
|
||||||
self.session_mut()
|
self.session()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.input_state_table
|
.input_state_table
|
||||||
|
.borrow_mut()
|
||||||
.entry(source_id.to_string())
|
.entry(source_id.to_string())
|
||||||
.or_insert(InputSourceState::Null);
|
.or_insert(InputSourceState::Null);
|
||||||
// https://w3c.github.io/webdriver/#dfn-dispatch-a-pause-action
|
// 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
|
// https://w3c.github.io/webdriver/#dfn-dispatch-tick-actions
|
||||||
fn dispatch_tick_actions(
|
fn dispatch_tick_actions(
|
||||||
&mut self,
|
&self,
|
||||||
tick_actions: &ActionSequence,
|
tick_actions: &ActionSequence,
|
||||||
tick_duration: u64,
|
tick_duration: u64,
|
||||||
) -> Result<(), ErrorStatus> {
|
) -> Result<(), ErrorStatus> {
|
||||||
|
@ -138,9 +201,10 @@ impl Handler {
|
||||||
self.dispatch_general_action(source_id);
|
self.dispatch_general_action(source_id);
|
||||||
},
|
},
|
||||||
KeyActionItem::Key(action) => {
|
KeyActionItem::Key(action) => {
|
||||||
self.session_mut()
|
self.session()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.input_state_table
|
.input_state_table
|
||||||
|
.borrow_mut()
|
||||||
.entry(source_id.to_string())
|
.entry(source_id.to_string())
|
||||||
.or_insert(InputSourceState::Key(KeyInputState::new()));
|
.or_insert(InputSourceState::Key(KeyInputState::new()));
|
||||||
match action {
|
match action {
|
||||||
|
@ -149,7 +213,7 @@ impl Handler {
|
||||||
// Step 9. If subtype is "keyDown", append a copy of action
|
// Step 9. If subtype is "keyDown", append a copy of action
|
||||||
// object with the subtype property changed to "keyUp" to
|
// object with the subtype property changed to "keyUp" to
|
||||||
// input state's input cancel list.
|
// input state's input cancel list.
|
||||||
self.session_mut().unwrap().input_cancel_list.push(
|
self.session().unwrap().input_cancel_list.borrow_mut().push(
|
||||||
ActionSequence {
|
ActionSequence {
|
||||||
id: source_id.into(),
|
id: source_id.into(),
|
||||||
actions: ActionsType::Key {
|
actions: ActionsType::Key {
|
||||||
|
@ -180,9 +244,10 @@ impl Handler {
|
||||||
self.dispatch_general_action(source_id);
|
self.dispatch_general_action(source_id);
|
||||||
},
|
},
|
||||||
PointerActionItem::Pointer(action) => {
|
PointerActionItem::Pointer(action) => {
|
||||||
self.session_mut()
|
self.session()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.input_state_table
|
.input_state_table
|
||||||
|
.borrow_mut()
|
||||||
.entry(source_id.to_string())
|
.entry(source_id.to_string())
|
||||||
.or_insert(InputSourceState::Pointer(PointerInputState::new(
|
.or_insert(InputSourceState::Pointer(PointerInputState::new(
|
||||||
¶meters.pointer_type,
|
¶meters.pointer_type,
|
||||||
|
@ -195,7 +260,7 @@ impl Handler {
|
||||||
// Step 10. If subtype is "pointerDown", append a copy of action
|
// Step 10. If subtype is "pointerDown", append a copy of action
|
||||||
// object with the subtype property changed to "pointerUp" to
|
// object with the subtype property changed to "pointerUp" to
|
||||||
// input state's input cancel list.
|
// input state's input cancel list.
|
||||||
self.session_mut().unwrap().input_cancel_list.push(
|
self.session().unwrap().input_cancel_list.borrow_mut().push(
|
||||||
ActionSequence {
|
ActionSequence {
|
||||||
id: source_id.into(),
|
id: source_id.into(),
|
||||||
actions: ActionsType::Pointer {
|
actions: ActionsType::Pointer {
|
||||||
|
@ -232,9 +297,10 @@ impl Handler {
|
||||||
self.dispatch_general_action(source_id)
|
self.dispatch_general_action(source_id)
|
||||||
},
|
},
|
||||||
WheelActionItem::Wheel(action) => {
|
WheelActionItem::Wheel(action) => {
|
||||||
self.session_mut()
|
self.session()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.input_state_table
|
.input_state_table
|
||||||
|
.borrow_mut()
|
||||||
.entry(source_id.to_string())
|
.entry(source_id.to_string())
|
||||||
.or_insert(InputSourceState::Wheel);
|
.or_insert(InputSourceState::Wheel);
|
||||||
match action {
|
match action {
|
||||||
|
@ -252,12 +318,25 @@ impl Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/webdriver/#dfn-dispatch-a-keydown-action
|
// https://w3c.github.io/webdriver/#dfn-dispatch-a-keydown-action
|
||||||
fn dispatch_keydown_action(&mut self, source_id: &str, action: &KeyDownAction) {
|
fn dispatch_keydown_action(&self, source_id: &str, action: &KeyDownAction) {
|
||||||
// Step 1
|
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 raw_key = action.value.chars().next().unwrap();
|
||||||
|
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!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
session.input_cancel_list.borrow_mut().push(ActionSequence {
|
||||||
|
id: source_id.into(),
|
||||||
|
actions: ActionsType::Key {
|
||||||
|
actions: vec![KeyActionItem::Key(KeyAction::Up(KeyUpAction {
|
||||||
|
value: action.value.clone(),
|
||||||
|
}))],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Step 2 - 11. Done by `keyboard-types` crate.
|
|
||||||
let keyboard_event = key_input_state.dispatch_keydown(raw_key);
|
let keyboard_event = key_input_state.dispatch_keydown(raw_key);
|
||||||
|
|
||||||
// Step 12
|
// Step 12
|
||||||
|
@ -271,12 +350,25 @@ impl Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/webdriver/#dfn-dispatch-a-keyup-action
|
// https://w3c.github.io/webdriver/#dfn-dispatch-a-keyup-action
|
||||||
fn dispatch_keyup_action(&mut self, source_id: &str, action: &KeyUpAction) {
|
fn dispatch_keyup_action(&self, source_id: &str, action: &KeyUpAction) {
|
||||||
// Step 1
|
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 raw_key = action.value.chars().next().unwrap();
|
||||||
|
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!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
session.input_cancel_list.borrow_mut().push(ActionSequence {
|
||||||
|
id: source_id.into(),
|
||||||
|
actions: ActionsType::Key {
|
||||||
|
actions: vec![KeyActionItem::Key(KeyAction::Up(KeyUpAction {
|
||||||
|
value: action.value.clone(),
|
||||||
|
}))],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Step 2 - 11. Done by `keyboard-types` crate.
|
|
||||||
if let Some(keyboard_event) = key_input_state.dispatch_keyup(raw_key) {
|
if let Some(keyboard_event) = key_input_state.dispatch_keyup(raw_key) {
|
||||||
// Step 12
|
// Step 12
|
||||||
let cmd_msg = WebDriverCommandMsg::KeyboardAction(
|
let cmd_msg = WebDriverCommandMsg::KeyboardAction(
|
||||||
|
@ -289,44 +381,49 @@ impl Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_pointer_input_state_mut(&mut self, source_id: &str) -> &mut PointerInputState {
|
/// <https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerdown-action>
|
||||||
let session = self.session_mut().unwrap();
|
pub(crate) fn dispatch_pointerdown_action(&self, source_id: &str, action: &PointerDownAction) {
|
||||||
let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() {
|
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,
|
InputSourceState::Pointer(pointer_input_state) => pointer_input_state,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
pointer_input_state
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
_ => 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) {
|
if pointer_input_state.pressed.contains(&action.button) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pointer_input_state.pressed.insert(action.button);
|
pointer_input_state.pressed.insert(action.button);
|
||||||
|
|
||||||
|
session.input_cancel_list.borrow_mut().push(ActionSequence {
|
||||||
|
id: source_id.into(),
|
||||||
|
actions: ActionsType::Pointer {
|
||||||
|
parameters: PointerActionParameters {
|
||||||
|
pointer_type: match pointer_input_state.subtype {
|
||||||
|
PointerType::Mouse => PointerType::Mouse,
|
||||||
|
PointerType::Pen => PointerType::Pen,
|
||||||
|
PointerType::Touch => PointerType::Touch,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: vec![PointerActionItem::Pointer(PointerAction::Up(
|
||||||
|
PointerUpAction {
|
||||||
|
button: action.button,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
))],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let msg_id = self.current_action_id.get().unwrap();
|
||||||
let cmd_msg = WebDriverCommandMsg::MouseButtonAction(
|
let cmd_msg = WebDriverCommandMsg::MouseButtonAction(
|
||||||
webview_id,
|
session.webview_id,
|
||||||
MouseButtonAction::Down,
|
MouseButtonAction::Down,
|
||||||
action.button.into(),
|
action.button.into(),
|
||||||
pointer_input_state.x as f32,
|
pointer_input_state.x as f32,
|
||||||
pointer_input_state.y as f32,
|
pointer_input_state.y as f32,
|
||||||
|
msg_id,
|
||||||
|
self.constellation_sender.clone(),
|
||||||
);
|
);
|
||||||
self.constellation_chan
|
self.constellation_chan
|
||||||
.send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg))
|
.send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg))
|
||||||
|
@ -334,21 +431,48 @@ impl Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerup-action
|
// https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerup-action
|
||||||
pub(crate) fn dispatch_pointerup_action(&mut self, source_id: &str, action: &PointerUpAction) {
|
pub(crate) fn dispatch_pointerup_action(&self, source_id: &str, action: &PointerUpAction) {
|
||||||
let webview_id = self.session().unwrap().webview_id;
|
let session = self.session().unwrap();
|
||||||
let pointer_input_state = self.get_pointer_input_state_mut(source_id);
|
|
||||||
|
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) {
|
if !pointer_input_state.pressed.contains(&action.button) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pointer_input_state.pressed.remove(&action.button);
|
pointer_input_state.pressed.remove(&action.button);
|
||||||
|
|
||||||
|
session.input_cancel_list.borrow_mut().push(ActionSequence {
|
||||||
|
id: source_id.into(),
|
||||||
|
actions: ActionsType::Pointer {
|
||||||
|
parameters: PointerActionParameters {
|
||||||
|
pointer_type: match pointer_input_state.subtype {
|
||||||
|
PointerType::Mouse => PointerType::Mouse,
|
||||||
|
PointerType::Pen => PointerType::Pen,
|
||||||
|
PointerType::Touch => PointerType::Touch,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: vec![PointerActionItem::Pointer(PointerAction::Down(
|
||||||
|
PointerDownAction {
|
||||||
|
button: action.button,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
))],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let msg_id = self.current_action_id.get().unwrap();
|
||||||
let cmd_msg = WebDriverCommandMsg::MouseButtonAction(
|
let cmd_msg = WebDriverCommandMsg::MouseButtonAction(
|
||||||
webview_id,
|
session.webview_id,
|
||||||
MouseButtonAction::Up,
|
MouseButtonAction::Up,
|
||||||
action.button.into(),
|
action.button.into(),
|
||||||
pointer_input_state.x as f32,
|
pointer_input_state.x as f32,
|
||||||
pointer_input_state.y as f32,
|
pointer_input_state.y as f32,
|
||||||
|
msg_id,
|
||||||
|
self.constellation_sender.clone(),
|
||||||
);
|
);
|
||||||
self.constellation_chan
|
self.constellation_chan
|
||||||
.send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg))
|
.send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg))
|
||||||
|
@ -357,7 +481,7 @@ impl Handler {
|
||||||
|
|
||||||
// https://w3c.github.io/webdriver/#dfn-dispatch-a-pointermove-action
|
// https://w3c.github.io/webdriver/#dfn-dispatch-a-pointermove-action
|
||||||
pub(crate) fn dispatch_pointermove_action(
|
pub(crate) fn dispatch_pointermove_action(
|
||||||
&mut self,
|
&self,
|
||||||
source_id: &str,
|
source_id: &str,
|
||||||
action: &PointerMoveAction,
|
action: &PointerMoveAction,
|
||||||
tick_duration: u64,
|
tick_duration: u64,
|
||||||
|
@ -370,10 +494,10 @@ impl Handler {
|
||||||
|
|
||||||
// Steps 3 - 4
|
// Steps 3 - 4
|
||||||
let (start_x, start_y) = match self
|
let (start_x, start_y) = match self
|
||||||
.session
|
.session()
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.input_state_table
|
.input_state_table
|
||||||
|
.borrow_mut()
|
||||||
.get(source_id)
|
.get(source_id)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
{
|
{
|
||||||
|
@ -416,7 +540,7 @@ impl Handler {
|
||||||
/// <https://w3c.github.io/webdriver/#dfn-perform-a-pointer-move>
|
/// <https://w3c.github.io/webdriver/#dfn-perform-a-pointer-move>
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn perform_pointer_move(
|
fn perform_pointer_move(
|
||||||
&mut self,
|
&self,
|
||||||
source_id: &str,
|
source_id: &str,
|
||||||
duration: u64,
|
duration: u64,
|
||||||
start_x: f64,
|
start_x: f64,
|
||||||
|
@ -425,9 +549,13 @@ impl Handler {
|
||||||
target_y: f64,
|
target_y: f64,
|
||||||
tick_start: Instant,
|
tick_start: Instant,
|
||||||
) {
|
) {
|
||||||
let webview_id = self.session().unwrap().webview_id;
|
let session = self.session().unwrap();
|
||||||
let constellation_chan = self.constellation_chan.clone();
|
let mut input_state_table = session.input_state_table.borrow_mut();
|
||||||
let pointer_input_state = self.get_pointer_input_state_mut(source_id);
|
let pointer_input_state = match input_state_table.get_mut(source_id).unwrap() {
|
||||||
|
InputSourceState::Pointer(pointer_input_state) => pointer_input_state,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Step 1
|
// Step 1
|
||||||
let time_delta = tick_start.elapsed().as_millis();
|
let time_delta = tick_start.elapsed().as_millis();
|
||||||
|
@ -459,9 +587,15 @@ impl Handler {
|
||||||
// Step 7
|
// Step 7
|
||||||
if x != current_x || y != current_y {
|
if x != current_x || y != current_y {
|
||||||
// Step 7.2
|
// Step 7.2
|
||||||
let cmd_msg = WebDriverCommandMsg::MouseMoveAction(webview_id, x as f32, y as f32);
|
let msg_id = self.current_action_id.get().unwrap();
|
||||||
//TODO: Need Synchronization here before updating `pointer_input_state`
|
let cmd_msg = WebDriverCommandMsg::MouseMoveAction(
|
||||||
constellation_chan
|
session.webview_id,
|
||||||
|
x as f32,
|
||||||
|
y as f32,
|
||||||
|
msg_id,
|
||||||
|
self.constellation_sender.clone(),
|
||||||
|
);
|
||||||
|
self.constellation_chan
|
||||||
.send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg))
|
.send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Step 7.3
|
// Step 7.3
|
||||||
|
@ -481,7 +615,7 @@ impl Handler {
|
||||||
|
|
||||||
/// <https://w3c.github.io/webdriver/#dfn-dispatch-a-scroll-action>
|
/// <https://w3c.github.io/webdriver/#dfn-dispatch-a-scroll-action>
|
||||||
fn dispatch_scroll_action(
|
fn dispatch_scroll_action(
|
||||||
&mut self,
|
&self,
|
||||||
action: &WheelScrollAction,
|
action: &WheelScrollAction,
|
||||||
tick_duration: u64,
|
tick_duration: u64,
|
||||||
) -> Result<(), ErrorStatus> {
|
) -> Result<(), ErrorStatus> {
|
||||||
|
@ -546,7 +680,7 @@ impl Handler {
|
||||||
/// <https://w3c.github.io/webdriver/#dfn-perform-a-scroll>
|
/// <https://w3c.github.io/webdriver/#dfn-perform-a-scroll>
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn perform_scroll(
|
fn perform_scroll(
|
||||||
&mut self,
|
&self,
|
||||||
duration: u64,
|
duration: u64,
|
||||||
x: i64,
|
x: i64,
|
||||||
y: i64,
|
y: i64,
|
||||||
|
@ -556,7 +690,7 @@ impl Handler {
|
||||||
mut curr_delta_y: i64,
|
mut curr_delta_y: i64,
|
||||||
tick_start: Instant,
|
tick_start: Instant,
|
||||||
) {
|
) {
|
||||||
let session = self.session_mut().unwrap();
|
let session = self.session().unwrap();
|
||||||
|
|
||||||
// Step 1
|
// Step 1
|
||||||
let time_delta = tick_start.elapsed().as_millis();
|
let time_delta = tick_start.elapsed().as_millis();
|
||||||
|
|
|
@ -10,11 +10,12 @@ mod actions;
|
||||||
mod capabilities;
|
mod capabilities;
|
||||||
|
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::net::{SocketAddr, SocketAddrV4};
|
use std::net::{SocketAddr, SocketAddrV4};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{env, fmt, mem, process, thread};
|
use std::{env, fmt, process, thread};
|
||||||
|
|
||||||
use base::id::{BrowsingContextId, WebViewId};
|
use base::id::{BrowsingContextId, WebViewId};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
@ -23,8 +24,9 @@ use constellation_traits::{EmbedderToConstellationMessage, TraversalDirection};
|
||||||
use cookie::{CookieBuilder, Expiration};
|
use cookie::{CookieBuilder, Expiration};
|
||||||
use crossbeam_channel::{Receiver, Sender, after, select, unbounded};
|
use crossbeam_channel::{Receiver, Sender, after, select, unbounded};
|
||||||
use embedder_traits::{
|
use embedder_traits::{
|
||||||
MouseButton, WebDriverCommandMsg, WebDriverCookieError, WebDriverFrameId, WebDriverJSError,
|
MouseButton, WebDriverCommandMsg, WebDriverCommandResponse, WebDriverCookieError,
|
||||||
WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, WebDriverScriptCommand,
|
WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus,
|
||||||
|
WebDriverMessageId, WebDriverScriptCommand,
|
||||||
};
|
};
|
||||||
use euclid::{Rect, Size2D};
|
use euclid::{Rect, Size2D};
|
||||||
use http::method::Method;
|
use http::method::Method;
|
||||||
|
@ -43,8 +45,8 @@ use servo_url::ServoUrl;
|
||||||
use style_traits::CSSPixel;
|
use style_traits::CSSPixel;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use webdriver::actions::{
|
use webdriver::actions::{
|
||||||
ActionSequence, PointerDownAction, PointerMoveAction, PointerOrigin, PointerType,
|
ActionSequence, ActionsType, PointerAction, PointerActionItem, PointerActionParameters,
|
||||||
PointerUpAction,
|
PointerDownAction, PointerMoveAction, PointerOrigin, PointerType, PointerUpAction,
|
||||||
};
|
};
|
||||||
use webdriver::capabilities::CapabilitiesMatching;
|
use webdriver::capabilities::CapabilitiesMatching;
|
||||||
use webdriver::command::{
|
use webdriver::command::{
|
||||||
|
@ -64,6 +66,26 @@ use webdriver::server::{self, Session, SessionTeardownKind, WebDriverHandler};
|
||||||
|
|
||||||
use crate::actions::{InputSourceState, PointerInputState};
|
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)> {
|
fn extension_routes() -> Vec<(Method, &'static str, ServoExtensionRoute)> {
|
||||||
vec![
|
vec![
|
||||||
(
|
(
|
||||||
|
@ -145,10 +167,11 @@ pub struct WebDriverSession {
|
||||||
|
|
||||||
unhandled_prompt_behavior: String,
|
unhandled_prompt_behavior: String,
|
||||||
|
|
||||||
// https://w3c.github.io/webdriver/#dfn-input-state-table
|
/// <https://w3c.github.io/webdriver/#dfn-input-state-map>
|
||||||
input_state_table: HashMap<String, InputSourceState>,
|
input_state_table: RefCell<HashMap<String, InputSourceState>>,
|
||||||
// https://w3c.github.io/webdriver/#dfn-input-cancel-list
|
|
||||||
input_cancel_list: Vec<ActionSequence>,
|
/// <https://w3c.github.io/webdriver/#dfn-input-cancel-list>
|
||||||
|
input_cancel_list: RefCell<Vec<ActionSequence>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebDriverSession {
|
impl WebDriverSession {
|
||||||
|
@ -172,8 +195,8 @@ impl WebDriverSession {
|
||||||
strict_file_interactability: false,
|
strict_file_interactability: false,
|
||||||
unhandled_prompt_behavior: "dismiss and notify".to_string(),
|
unhandled_prompt_behavior: "dismiss and notify".to_string(),
|
||||||
|
|
||||||
input_state_table: HashMap::new(),
|
input_state_table: RefCell::new(HashMap::new()),
|
||||||
input_cancel_list: Vec::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
|
/// for it to send us a load-status. Messages sent on it
|
||||||
/// will be forwarded to the load_status_receiver.
|
/// will be forwarded to the load_status_receiver.
|
||||||
load_status_sender: IpcSender<WebDriverLoadStatus>,
|
load_status_sender: IpcSender<WebDriverLoadStatus>,
|
||||||
|
|
||||||
session: Option<WebDriverSession>,
|
session: Option<WebDriverSession>,
|
||||||
|
|
||||||
|
/// The channel for sending Webdriver messages to the constellation.
|
||||||
constellation_chan: Sender<EmbedderToConstellationMessage>,
|
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,
|
resize_timeout: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,11 +446,18 @@ impl Handler {
|
||||||
let (load_status_sender, receiver) = ipc::channel().unwrap();
|
let (load_status_sender, receiver) = ipc::channel().unwrap();
|
||||||
let (sender, load_status_receiver) = unbounded();
|
let (sender, load_status_receiver) = unbounded();
|
||||||
ROUTER.route_ipc_receiver_to_crossbeam_sender(receiver, sender);
|
ROUTER.route_ipc_receiver_to_crossbeam_sender(receiver, sender);
|
||||||
|
|
||||||
|
let (constellation_sender, constellation_receiver) = ipc::channel().unwrap();
|
||||||
|
|
||||||
Handler {
|
Handler {
|
||||||
load_status_sender,
|
load_status_sender,
|
||||||
load_status_receiver,
|
load_status_receiver,
|
||||||
session: None,
|
session: None,
|
||||||
constellation_chan,
|
constellation_chan,
|
||||||
|
constellation_sender,
|
||||||
|
constellation_receiver,
|
||||||
|
id_generator: WebDriverMessageIdGenerator::new(),
|
||||||
|
current_action_id: Cell::new(None),
|
||||||
resize_timeout: 500,
|
resize_timeout: 500,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1445,18 +1489,13 @@ impl Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_release_actions(&mut self) -> WebDriverResult<WebDriverResponse> {
|
fn handle_release_actions(&mut self) -> WebDriverResult<WebDriverResponse> {
|
||||||
let input_cancel_list = {
|
let input_cancel_list = self.session().unwrap().input_cancel_list.borrow();
|
||||||
let session = self.session_mut()?;
|
|
||||||
session.input_cancel_list.reverse();
|
|
||||||
mem::take(&mut session.input_cancel_list)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(error) = self.dispatch_actions(&input_cancel_list) {
|
if let Err(error) = self.dispatch_actions(&input_cancel_list) {
|
||||||
return Err(WebDriverError::new(error, ""));
|
return Err(WebDriverError::new(error, ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
let session = self.session_mut()?;
|
let session = self.session()?;
|
||||||
session.input_state_table = HashMap::new();
|
session.input_state_table.borrow_mut().clear();
|
||||||
|
|
||||||
Ok(WebDriverResponse::Void)
|
Ok(WebDriverResponse::Void)
|
||||||
}
|
}
|
||||||
|
@ -1614,7 +1653,7 @@ impl Handler {
|
||||||
let id = Uuid::new_v4().to_string();
|
let id = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
// Step 8.1
|
// Step 8.1
|
||||||
self.session_mut()?.input_state_table.insert(
|
self.session_mut()?.input_state_table.borrow_mut().insert(
|
||||||
id.clone(),
|
id.clone(),
|
||||||
InputSourceState::Pointer(PointerInputState::new(&PointerType::Mouse)),
|
InputSourceState::Pointer(PointerInputState::new(&PointerType::Mouse)),
|
||||||
);
|
);
|
||||||
|
@ -1645,19 +1684,31 @@ impl Handler {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 8.16 Dispatch a list of actions with input state,
|
let action_sequence = ActionSequence {
|
||||||
// actions, session's current browsing context, and actions options.
|
id: id.clone(),
|
||||||
if let Err(error) =
|
actions: ActionsType::Pointer {
|
||||||
self.dispatch_pointermove_action(&id, &pointer_move_action, 0)
|
parameters: PointerActionParameters {
|
||||||
{
|
pointer_type: PointerType::Mouse,
|
||||||
return Err(WebDriverError::new(error, ""));
|
},
|
||||||
}
|
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);
|
let _ = self.dispatch_actions(&[action_sequence]);
|
||||||
self.dispatch_pointerup_action(&id, &pointer_up_action);
|
|
||||||
|
|
||||||
// Step 8.17 Remove an input source with input state and input id.
|
// 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
|
// Step 13
|
||||||
Ok(WebDriverResponse::Void)
|
Ok(WebDriverResponse::Void)
|
||||||
|
|
|
@ -262,11 +262,11 @@ impl Window {
|
||||||
ElementState::Released => MouseButtonAction::Up,
|
ElementState::Released => MouseButtonAction::Up,
|
||||||
};
|
};
|
||||||
|
|
||||||
webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent {
|
webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
|
||||||
action,
|
action,
|
||||||
button: mouse_button,
|
mouse_button,
|
||||||
point,
|
point,
|
||||||
}));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle key events before sending them to Servo.
|
/// Handle key events before sending them to Servo.
|
||||||
|
@ -563,7 +563,7 @@ impl WindowPortsMethods for Window {
|
||||||
point.y -= (self.toolbar_height() * self.hidpi_scale_factor()).0;
|
point.y -= (self.toolbar_height() * self.hidpi_scale_factor()).0;
|
||||||
|
|
||||||
self.webview_relative_mouse_point.set(point);
|
self.webview_relative_mouse_point.set(point);
|
||||||
webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent { point }));
|
webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point)));
|
||||||
},
|
},
|
||||||
WindowEvent::MouseWheel { delta, phase, .. } => {
|
WindowEvent::MouseWheel { delta, phase, .. } => {
|
||||||
let (mut dx, mut dy, mode) = match delta {
|
let (mut dx, mut dy, mode) = match delta {
|
||||||
|
|
|
@ -537,31 +537,31 @@ impl RunningAppState {
|
||||||
/// Register a mouse movement.
|
/// Register a mouse movement.
|
||||||
pub fn mouse_move(&self, x: f32, y: f32) {
|
pub fn mouse_move(&self, x: f32, y: f32) {
|
||||||
self.active_webview()
|
self.active_webview()
|
||||||
.notify_input_event(InputEvent::MouseMove(MouseMoveEvent {
|
.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(Point2D::new(
|
||||||
point: Point2D::new(x, y),
|
x, y,
|
||||||
}));
|
))));
|
||||||
self.perform_updates();
|
self.perform_updates();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a mouse button press.
|
/// Register a mouse button press.
|
||||||
pub fn mouse_down(&self, x: f32, y: f32, button: MouseButton) {
|
pub fn mouse_down(&self, x: f32, y: f32, button: MouseButton) {
|
||||||
self.active_webview()
|
self.active_webview()
|
||||||
.notify_input_event(InputEvent::MouseButton(MouseButtonEvent {
|
.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
|
||||||
action: MouseButtonAction::Down,
|
MouseButtonAction::Down,
|
||||||
button,
|
button,
|
||||||
point: Point2D::new(x, y),
|
Point2D::new(x, y),
|
||||||
}));
|
)));
|
||||||
self.perform_updates();
|
self.perform_updates();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a mouse button release.
|
/// Register a mouse button release.
|
||||||
pub fn mouse_up(&self, x: f32, y: f32, button: MouseButton) {
|
pub fn mouse_up(&self, x: f32, y: f32, button: MouseButton) {
|
||||||
self.active_webview()
|
self.active_webview()
|
||||||
.notify_input_event(InputEvent::MouseButton(MouseButtonEvent {
|
.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
|
||||||
action: MouseButtonAction::Up,
|
MouseButtonAction::Up,
|
||||||
button,
|
button,
|
||||||
point: Point2D::new(x, y),
|
Point2D::new(x, y),
|
||||||
}));
|
)));
|
||||||
self.perform_updates();
|
self.perform_updates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,11 +589,11 @@ impl RunningAppState {
|
||||||
/// Perform a click.
|
/// Perform a click.
|
||||||
pub fn click(&self, x: f32, y: f32) {
|
pub fn click(&self, x: f32, y: f32) {
|
||||||
self.active_webview()
|
self.active_webview()
|
||||||
.notify_input_event(InputEvent::MouseButton(MouseButtonEvent {
|
.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
|
||||||
action: MouseButtonAction::Click,
|
MouseButtonAction::Click,
|
||||||
button: MouseButton::Left,
|
MouseButton::Left,
|
||||||
point: Point2D::new(x, y),
|
Point2D::new(x, y),
|
||||||
}));
|
)));
|
||||||
self.perform_updates();
|
self.perform_updates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue