Skip to content

Create KeyItem struct for send_keys #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 84 additions & 37 deletions src/webdriver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,46 +373,29 @@ impl KeyInputState {
is_composing: false,
})
}
}

fn clear(&mut self, undo_actions: &mut HashSet<char>, result: &mut Vec<Event>) {
let mut actions: Vec<_> = undo_actions.drain().collect();
actions.sort_unstable();
for action in actions {
result.push(self.dispatch_keyup(action).unwrap().into());
}
assert!(undo_actions.is_empty());
}

fn dispatch_typeable(&mut self, text: &mut String, result: &mut Vec<Event>) {
for character in text.chars() {
let shifted = self.modifiers.contains(Modifiers::SHIFT);
if is_shifted_character(character) && !shifted {
// dispatch left shift down
result.push(self.dispatch_keydown('\u{E008}').into());
}
if !is_shifted_character(character) && shifted {
// dispatch left shift up
result.push(self.dispatch_keyup('\u{E008}').unwrap().into());
}
result.push(self.dispatch_keydown(character).into());
result.push(self.dispatch_keyup(character).unwrap().into());
}
text.clear();
}
/// Give user the key state and char of the key event.
/// User must then send the key event through `dispatch_actions`
#[derive(Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct KeyItem {
state: KeyState,
raw_char: char,
}

/// Either a [`KeyboardEvent`] or a [`CompositionEvent`].
/// Either a [`KeyItem`] or a [`CompositionEvent`].
///
/// Returned by the [`send_keys`] function.
#[derive(Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Event {
Keyboard(KeyboardEvent),
Keyboard(KeyItem),
Comment on lines -410 to +393
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'd like to avoid this breaking change. Why can you not use the character in KeyboardEvent::key's Key::Character(...) variant?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern is that we can't get the raw character for Key::Named. Much smaller changes is maybe to simply add raw_char on the KeyboardEvent. Do you think we should do that instead?

This change does not exactly fix behaviour for this crate, but it makes it simpler for user like web engine to implement Webdriver Element Send Keys command based on spec.

Composition(CompositionEvent),
}

impl From<KeyboardEvent> for Event {
fn from(v: KeyboardEvent) -> Event {
impl From<KeyItem> for Event {
fn from(v: KeyItem) -> Event {
Event::Keyboard(v)
}
}
Expand Down Expand Up @@ -459,25 +442,89 @@ pub fn send_keys(text: &str) -> Vec<Event> {
text.chars().count() == 1
}

fn clear(undo_actions: &mut HashSet<char>, result: &mut Vec<Event>, shifted: &mut bool) {
let mut actions: Vec<_> = undo_actions.drain().collect();
actions.sort_unstable();
for action in actions {
result.push(
KeyItem {
state: KeyState::Up,
raw_char: action,
}
.into(),
);
}
*shifted = false;
assert!(undo_actions.is_empty());
}

fn dispatch_typeable(text: &mut String, result: &mut Vec<Event>, shifted: &mut bool) {
for character in text.chars() {
if is_shifted_character(character) && !(*shifted) {
// dispatch left shift down
result.push(
KeyItem {
state: KeyState::Down,
raw_char: '\u{E008}',
}
.into(),
);
*shifted = true;
}
if !is_shifted_character(character) && *shifted {
// dispatch left shift up
result.push(
KeyItem {
state: KeyState::Up,
raw_char: '\u{E008}',
}
.into(),
);
*shifted = false;
}
result.push(
KeyItem {
state: KeyState::Down,
raw_char: character,
}
.into(),
);
result.push(
KeyItem {
state: KeyState::Up,
raw_char: character,
}
.into(),
);
}
text.clear();
}

let mut result = Vec::new();
let mut typeable_text = String::new();
let mut state = KeyInputState::new();
let mut undo_actions = HashSet::new();
let mut shifted = false;
for cluster in UnicodeSegmentation::graphemes(text, true) {
match cluster {
"\u{E000}" => {
state.dispatch_typeable(&mut typeable_text, &mut result);
state.clear(&mut undo_actions, &mut result);
dispatch_typeable(&mut typeable_text, &mut result, &mut shifted);
clear(&mut undo_actions, &mut result, &mut shifted);
}
s if is_modifier(s) => {
state.dispatch_typeable(&mut typeable_text, &mut result);
dispatch_typeable(&mut typeable_text, &mut result, &mut shifted);
let raw_modifier = first_char(s);
result.push(state.dispatch_keydown(raw_modifier).into());
result.push(
KeyItem {
state: KeyState::Down,
raw_char: raw_modifier,
}
.into(),
);
undo_actions.insert(raw_modifier);
}
s if is_typeable(s) => typeable_text.push_str(s),
s => {
state.dispatch_typeable(&mut typeable_text, &mut result);
dispatch_typeable(&mut typeable_text, &mut result, &mut shifted);
// FIXME: Spec says undefined instead of empty string
result.push(
CompositionEvent {
Expand All @@ -503,7 +550,7 @@ pub fn send_keys(text: &str) -> Vec<Event> {
}
}
}
state.dispatch_typeable(&mut typeable_text, &mut result);
state.clear(&mut undo_actions, &mut result);
dispatch_typeable(&mut typeable_text, &mut result, &mut shifted);
clear(&mut undo_actions, &mut result, &mut shifted);
result
}