Fix WebDriverSession::input_cancel_list related logic (#37010)

- Remove incorrect addition to `input_cancel_list` in
`dispatch_keyup_action`
- For `KeyDown` and `PointerDown`, delay the addition to
`input_cancel_list` until "Dispatching action algorithm" is done to
match the spec. Previously the addition is done before notifying
constellation. Moreover, this makes sure that `pointerUp` is appended
even if `dispatch_pointerdown_action` returns early, so that [Release
Actions](https://w3c.github.io/webdriver/#release-actions) always have
the correct order.
- Remove incorrect addition to `input_cancel_list` in
`dispatch_pointerup_action`. This wrongly added "pointerdown" in
[Release Actions](https://w3c.github.io/webdriver/#release-actions)
- Reduce code duplication
- Add TODO for PointerInputState::subtype and pointerID

Testing: `./mach test-wpt -r --log-raw "D:\servo test
log\perform-actions.txt"
tests\wpt\tests\webdriver\tests\classic\perform_actions --product
servodriver` has no new failures so no regression. There are a lot more
passing tests, but I think mostly are because there is no CI for
webdriver.

cc @xiaochengh @jdm @PotatoCP @longvatrong111

---------

Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
This commit is contained in:
Euclid Ye 2025-05-19 16:34:04 +08:00 committed by GitHub
parent e2424fcec7
commit 7ac302f255
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -33,6 +33,9 @@ pub(crate) enum InputSourceState {
}
// https://w3c.github.io/webdriver/#dfn-pointer-input-source
// TODO: subtype is used for https://w3c.github.io/webdriver/#dfn-get-a-pointer-id
// Need to add pointer-id to the following struct
#[allow(dead_code)]
pub(crate) struct PointerInputState {
subtype: PointerType,
pressed: HashSet<u64>,
@ -142,7 +145,22 @@ impl Handler {
.or_insert(InputSourceState::Key(KeyInputState::new()));
match action {
KeyAction::Down(action) => {
self.dispatch_keydown_action(source_id, action)
self.dispatch_keydown_action(source_id, action);
// 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(
ActionSequence {
id: source_id.into(),
actions: ActionsType::Key {
actions: vec![KeyActionItem::Key(KeyAction::Up(
KeyUpAction {
value: action.value.clone(),
},
))],
},
},
);
},
KeyAction::Up(action) => {
self.dispatch_keyup_action(source_id, action)
@ -172,7 +190,27 @@ impl Handler {
match action {
PointerAction::Cancel => (),
PointerAction::Down(action) => {
self.dispatch_pointerdown_action(source_id, action)
self.dispatch_pointerdown_action(source_id, action);
// 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(
ActionSequence {
id: source_id.into(),
actions: ActionsType::Pointer {
parameters: PointerActionParameters {
pointer_type: parameters.pointer_type,
},
actions: vec![PointerActionItem::Pointer(
PointerAction::Up(PointerUpAction {
button: action.button,
..Default::default()
}),
)],
},
},
);
},
PointerAction::Move(action) => self.dispatch_pointermove_action(
source_id,
@ -215,26 +253,18 @@ impl Handler {
// https://w3c.github.io/webdriver/#dfn-dispatch-a-keydown-action
fn dispatch_keydown_action(&mut self, source_id: &str, action: &KeyDownAction) {
let session = self.session.as_mut().unwrap();
// Step 1
let raw_key = action.value.chars().next().unwrap();
let key_input_state = match session.input_state_table.get_mut(source_id).unwrap() {
InputSourceState::Key(key_input_state) => key_input_state,
_ => unreachable!(),
};
session.input_cancel_list.push(ActionSequence {
id: source_id.into(),
actions: ActionsType::Key {
actions: vec![KeyActionItem::Key(KeyAction::Up(KeyUpAction {
value: action.value.clone(),
}))],
},
});
let key_input_state = self.get_key_input_state_mut(source_id);
// Step 2 - 11. Done by `keyboard-types` crate.
let keyboard_event = key_input_state.dispatch_keydown(raw_key);
let cmd_msg =
WebDriverCommandMsg::KeyboardAction(session.browsing_context_id, keyboard_event);
// Step 12
let cmd_msg = WebDriverCommandMsg::KeyboardAction(
self.session().unwrap().browsing_context_id,
keyboard_event,
);
self.constellation_chan
.send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg))
.unwrap();
@ -242,71 +272,57 @@ impl Handler {
// https://w3c.github.io/webdriver/#dfn-dispatch-a-keyup-action
fn dispatch_keyup_action(&mut self, source_id: &str, action: &KeyUpAction) {
let session = self.session.as_mut().unwrap();
// Step 1
let raw_key = action.value.chars().next().unwrap();
let key_input_state = match session.input_state_table.get_mut(source_id).unwrap() {
InputSourceState::Key(key_input_state) => key_input_state,
_ => unreachable!(),
};
session.input_cancel_list.push(ActionSequence {
id: source_id.into(),
actions: ActionsType::Key {
actions: vec![KeyActionItem::Key(KeyAction::Up(KeyUpAction {
value: action.value.clone(),
}))],
},
});
let key_input_state = self.get_key_input_state_mut(source_id);
// Step 2 - 11. Done by `keyboard-types` crate.
if let Some(keyboard_event) = key_input_state.dispatch_keyup(raw_key) {
let cmd_msg =
WebDriverCommandMsg::KeyboardAction(session.browsing_context_id, keyboard_event);
// Step 12
let cmd_msg = WebDriverCommandMsg::KeyboardAction(
self.session().unwrap().browsing_context_id,
keyboard_event,
);
self.constellation_chan
.send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg))
.unwrap();
}
}
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
}
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 session = self.session.as_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!(),
};
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.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 cmd_msg = WebDriverCommandMsg::MouseButtonAction(
session.webview_id,
webview_id,
MouseButtonAction::Down,
action.button.into(),
pointer_input_state.x as f32,
@ -319,39 +335,16 @@ 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 session = self.session.as_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!(),
};
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.remove(&action.button);
session.input_cancel_list.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 cmd_msg = WebDriverCommandMsg::MouseButtonAction(
session.webview_id,
webview_id,
MouseButtonAction::Up,
action.button.into(),
pointer_input_state.x as f32,
@ -432,12 +425,9 @@ impl Handler {
target_y: f64,
tick_start: Instant,
) {
let session = self.session.as_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!(),
};
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);
loop {
// Step 1
let time_delta = tick_start.elapsed().as_millis();
@ -469,10 +459,9 @@ impl Handler {
// Step 7
if x != current_x || y != current_y {
// Step 7.2
let cmd_msg =
WebDriverCommandMsg::MouseMoveAction(session.webview_id, x as f32, y as f32);
let cmd_msg = WebDriverCommandMsg::MouseMoveAction(webview_id, x as f32, y as f32);
//TODO: Need Synchronization here before updating `pointer_input_state`
self.constellation_chan
constellation_chan
.send(EmbedderToConstellationMessage::WebDriverCommand(cmd_msg))
.unwrap();
// Step 7.3
@ -567,7 +556,7 @@ impl Handler {
mut curr_delta_y: i64,
tick_start: Instant,
) {
let session = self.session.as_mut().unwrap();
let session = self.session_mut().unwrap();
// Step 1
let time_delta = tick_start.elapsed().as_millis();