Add keyboard special key support for web content interaction

Enter, Backspace, Tab, Escape, Delete, arrow keys, Home, End,
PageUp, PageDown now forward to Servo. Previously only printable
characters reached the web engine, making forms unusable.
This commit is contained in:
2026-05-09 15:36:37 -04:00
parent 5d678c274a
commit f3a8671bd5
3 changed files with 91 additions and 16 deletions
@@ -10,16 +10,22 @@ impl ElyShell {
_window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(text) = typed_text_from_key_down(event) else {
return;
};
let Some((tab_id, requested_url)) = self.active_external_tab_target() else {
return;
};
if let Some(text) = printable_text_from_key_down(event) {
if self.type_text_in_external_web_viewport(tab_id, requested_url, text, cx) {
cx.stop_propagation();
}
return;
}
if let Some(text) = special_key_text(event) {
if self.type_text_in_external_web_viewport(tab_id, requested_url, text, cx) {
cx.stop_propagation();
}
}
}
fn active_external_tab_target(&self) -> Option<(TabId, String)> {
@@ -36,7 +42,7 @@ impl ElyShell {
}
}
fn typed_text_from_key_down(event: &KeyDownEvent) -> Option<&str> {
fn printable_text_from_key_down(event: &KeyDownEvent) -> Option<&str> {
let modifiers = &event.keystroke.modifiers;
if modifiers.control || modifiers.platform || modifiers.function {
return None;
@@ -52,17 +58,39 @@ fn typed_text_from_key_down(event: &KeyDownEvent) -> Option<&str> {
Some(text)
}
fn special_key_text(event: &KeyDownEvent) -> Option<&'static str> {
if event.keystroke.modifiers.platform || event.keystroke.modifiers.control {
return None;
}
match event.keystroke.key.as_str() {
"enter" => Some("\n"),
"backspace" => Some("\x08"),
"tab" => Some("\t"),
"escape" => Some("\x1b"),
"delete" => Some("\x7f"),
"left" => Some("\u{F702}"),
"right" => Some("\u{F703}"),
"up" => Some("\u{F700}"),
"down" => Some("\u{F701}"),
"home" => Some("\u{F729}"),
"end" => Some("\u{F72B}"),
"pageup" => Some("\u{F72C}"),
"pagedown" => Some("\u{F72D}"),
_ => None,
}
}
#[cfg(test)]
mod tests {
use gpui::{KeyDownEvent, Keystroke, Modifiers};
use super::typed_text_from_key_down;
use super::{printable_text_from_key_down, special_key_text};
#[test]
fn typed_text_uses_printable_key_char() {
let event = key_down("e", Some("e"), Modifiers::none());
assert_eq!(typed_text_from_key_down(&event), Some("e"));
assert_eq!(printable_text_from_key_down(&event), Some("e"));
}
#[test]
@@ -71,7 +99,7 @@ mod tests {
modifiers.shift = true;
let event = key_down("1", Some("!"), modifiers);
assert_eq!(typed_text_from_key_down(&event), Some("!"));
assert_eq!(printable_text_from_key_down(&event), Some("!"));
}
#[test]
@@ -80,7 +108,36 @@ mod tests {
modifiers.platform = true;
let event = key_down("l", None, modifiers);
assert_eq!(typed_text_from_key_down(&event), None);
assert_eq!(printable_text_from_key_down(&event), None);
}
#[test]
fn special_key_enter() {
let event = key_down("enter", None, Modifiers::none());
assert_eq!(special_key_text(&event), Some("\n"));
}
#[test]
fn special_key_backspace() {
let event = key_down("backspace", None, Modifiers::none());
assert_eq!(special_key_text(&event), Some("\x08"));
}
#[test]
fn special_key_ignored_with_platform_modifier() {
let mut modifiers = Modifiers::none();
modifiers.platform = true;
let event = key_down("enter", None, modifiers);
assert_eq!(special_key_text(&event), None);
}
#[test]
fn special_key_arrows() {
let event = key_down("left", None, Modifiers::none());
assert_eq!(special_key_text(&event), Some("\u{F702}"));
let event = key_down("right", None, Modifiers::none());
assert_eq!(special_key_text(&event), Some("\u{F703}"));
}
fn key_down(key: &str, key_char: Option<&str>, modifiers: Modifiers) -> KeyDownEvent {
+21 -2
View File
@@ -1,6 +1,25 @@
use servo::Code;
use servo::{Code, Key};
pub(crate) fn keyboard_code_for_character(character: char) -> Code {
pub(crate) fn key_and_code_for_character(character: char) -> (Key, Code) {
match character {
'\n' | '\r' => (Key::Enter, Code::Enter),
'\x08' => (Key::Backspace, Code::Backspace),
'\t' => (Key::Tab, Code::Tab),
'\x1b' => (Key::Escape, Code::Escape),
'\x7f' => (Key::Delete, Code::Delete),
'\u{F700}' => (Key::ArrowUp, Code::ArrowUp),
'\u{F701}' => (Key::ArrowDown, Code::ArrowDown),
'\u{F702}' => (Key::ArrowLeft, Code::ArrowLeft),
'\u{F703}' => (Key::ArrowRight, Code::ArrowRight),
'\u{F729}' => (Key::Home, Code::Home),
'\u{F72B}' => (Key::End, Code::End),
'\u{F72C}' => (Key::PageUp, Code::PageUp),
'\u{F72D}' => (Key::PageDown, Code::PageDown),
_ => (Key::Character(character.to_string()), printable_code(character)),
}
}
fn printable_code(character: char) -> Code {
match character {
'a' | 'A' => Code::KeyA,
'b' | 'B' => Code::KeyB,
+3 -4
View File
@@ -1,10 +1,10 @@
use servo::{
DevicePoint, InputEvent, Key, KeyState, KeyboardEvent, Location, Modifiers, MouseButton,
DevicePoint, InputEvent, KeyState, KeyboardEvent, Location, Modifiers, MouseButton,
MouseButtonAction, MouseButtonEvent, MouseMoveEvent, TouchEvent, TouchEventType, TouchId,
WebView, WebViewPoint,
};
use crate::keyboard::keyboard_code_for_character;
use crate::keyboard::key_and_code_for_character;
pub(super) fn send_mouse_click(webview: &WebView, x: u32, y: u32) {
let point = point(x, y);
@@ -32,8 +32,7 @@ pub(super) fn send_touch_tap(webview: &WebView, x: u32, y: u32) {
pub(super) fn send_keyboard_text(webview: &WebView, text: &str) {
for character in text.chars() {
let key = Key::Character(character.to_string());
let code = keyboard_code_for_character(character);
let (key, code) = key_and_code_for_character(character);
webview.notify_input_event(InputEvent::Keyboard(KeyboardEvent::new_without_event(
KeyState::Down,
key.clone(),