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, _window: &mut Window,
cx: &mut Context<Self>, 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 { let Some((tab_id, requested_url)) = self.active_external_tab_target() else {
return; 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) { if self.type_text_in_external_web_viewport(tab_id, requested_url, text, cx) {
cx.stop_propagation(); 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)> { 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; let modifiers = &event.keystroke.modifiers;
if modifiers.control || modifiers.platform || modifiers.function { if modifiers.control || modifiers.platform || modifiers.function {
return None; return None;
@@ -52,17 +58,39 @@ fn typed_text_from_key_down(event: &KeyDownEvent) -> Option<&str> {
Some(text) 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)] #[cfg(test)]
mod tests { mod tests {
use gpui::{KeyDownEvent, Keystroke, Modifiers}; use gpui::{KeyDownEvent, Keystroke, Modifiers};
use super::typed_text_from_key_down; use super::{printable_text_from_key_down, special_key_text};
#[test] #[test]
fn typed_text_uses_printable_key_char() { fn typed_text_uses_printable_key_char() {
let event = key_down("e", Some("e"), Modifiers::none()); 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] #[test]
@@ -71,7 +99,7 @@ mod tests {
modifiers.shift = true; modifiers.shift = true;
let event = key_down("1", Some("!"), modifiers); 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] #[test]
@@ -80,7 +108,36 @@ mod tests {
modifiers.platform = true; modifiers.platform = true;
let event = key_down("l", None, modifiers); 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 { 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 { match character {
'a' | 'A' => Code::KeyA, 'a' | 'A' => Code::KeyA,
'b' | 'B' => Code::KeyB, 'b' | 'B' => Code::KeyB,
+3 -4
View File
@@ -1,10 +1,10 @@
use servo::{ use servo::{
DevicePoint, InputEvent, Key, KeyState, KeyboardEvent, Location, Modifiers, MouseButton, DevicePoint, InputEvent, KeyState, KeyboardEvent, Location, Modifiers, MouseButton,
MouseButtonAction, MouseButtonEvent, MouseMoveEvent, TouchEvent, TouchEventType, TouchId, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, TouchEvent, TouchEventType, TouchId,
WebView, WebViewPoint, 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) { pub(super) fn send_mouse_click(webview: &WebView, x: u32, y: u32) {
let point = point(x, y); 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) { pub(super) fn send_keyboard_text(webview: &WebView, text: &str) {
for character in text.chars() { for character in text.chars() {
let key = Key::Character(character.to_string()); let (key, code) = key_and_code_for_character(character);
let code = keyboard_code_for_character(character);
webview.notify_input_event(InputEvent::Keyboard(KeyboardEvent::new_without_event( webview.notify_input_event(InputEvent::Keyboard(KeyboardEvent::new_without_event(
KeyState::Down, KeyState::Down,
key.clone(), key.clone(),