mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01: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
|
@ -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,25 @@ 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
|
||||
let raw_key = action.value.chars().next().unwrap();
|
||||
let key_input_state = self.get_key_input_state_mut(source_id);
|
||||
fn dispatch_keydown_action(&self, source_id: &str, action: &KeyDownAction) {
|
||||
let session = self.session().unwrap();
|
||||
|
||||
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);
|
||||
|
||||
// Step 12
|
||||
|
@ -271,12 +350,25 @@ 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
|
||||
let raw_key = action.value.chars().next().unwrap();
|
||||
let key_input_state = self.get_key_input_state_mut(source_id);
|
||||
fn dispatch_keyup_action(&self, source_id: &str, action: &KeyUpAction) {
|
||||
let session = self.session().unwrap();
|
||||
|
||||
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) {
|
||||
// Step 12
|
||||
let cmd_msg = WebDriverCommandMsg::KeyboardAction(
|
||||
|
@ -289,44 +381,49 @@ 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() {
|
||||
/// <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();
|
||||
|
||||
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!(),
|
||||
};
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
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(
|
||||
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 +431,48 @@ 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);
|
||||
|
||||
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(
|
||||
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 +481,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 +494,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 +540,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 +549,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 +587,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 +615,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 +680,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 +690,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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue