Skip to main content

script/dom/event/
keyboardevent.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::str::FromStr;
7
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use js::rust::HandleObject;
11use keyboard_types::{Code, Key, Modifiers, NamedKey};
12use script_bindings::cell::DomRefCell;
13use script_bindings::reflector::reflect_dom_object_with_proto;
14use style::Atom;
15
16use crate::dom::bindings::codegen::Bindings::KeyboardEventBinding;
17use crate::dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods;
18use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods;
19use crate::dom::bindings::error::Fallible;
20use crate::dom::bindings::inheritance::Castable;
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::bindings::str::DOMString;
23use crate::dom::event::Event;
24use crate::dom::uievent::UIEvent;
25use crate::dom::window::Window;
26
27#[dom_struct]
28pub(crate) struct KeyboardEvent {
29    uievent: UIEvent,
30    key: DomRefCell<DOMString>,
31    #[no_trace]
32    typed_key: DomRefCell<Key>,
33    code: DomRefCell<DOMString>,
34    #[no_trace]
35    original_code: DomRefCell<Option<Code>>,
36    location: Cell<u32>,
37    #[no_trace]
38    modifiers: Cell<Modifiers>,
39    repeat: Cell<bool>,
40    is_composing: Cell<bool>,
41    char_code: Cell<u32>,
42    key_code: Cell<u32>,
43}
44
45impl KeyboardEvent {
46    fn new_inherited() -> Self {
47        Self {
48            uievent: UIEvent::new_inherited(),
49            key: Default::default(),
50            typed_key: Default::default(),
51            code: Default::default(),
52            original_code: Default::default(),
53            location: Default::default(),
54            modifiers: Default::default(),
55            repeat: Default::default(),
56            is_composing: Default::default(),
57            char_code: Default::default(),
58            key_code: Default::default(),
59        }
60    }
61
62    pub(crate) fn new_uninitialized(cx: &mut JSContext, window: &Window) -> DomRoot<KeyboardEvent> {
63        Self::new_uninitialized_with_proto(cx, window, None)
64    }
65
66    fn new_uninitialized_with_proto(
67        cx: &mut JSContext,
68        window: &Window,
69        proto: Option<HandleObject>,
70    ) -> DomRoot<KeyboardEvent> {
71        reflect_dom_object_with_proto(cx, Box::new(KeyboardEvent::new_inherited()), window, proto)
72    }
73
74    pub(crate) fn new_with_platform_keyboard_event(
75        cx: &mut JSContext,
76        window: &Window,
77        event_type: Atom,
78        keyboard_event: &keyboard_types::KeyboardEvent,
79    ) -> DomRoot<KeyboardEvent> {
80        let keycode = legacy_keycode_for_keyboard_event(keyboard_event);
81        Self::new_with_proto(
82            cx,
83            window,
84            None,
85            event_type,
86            true,         /* can_bubble */
87            true,         /* cancelable */
88            Some(window), /* view */
89            0,            /* detail */
90            keyboard_event.key.clone(),
91            DOMString::from(keyboard_event.code.to_string()),
92            Some(keyboard_event.code),
93            keyboard_event.location as u32,
94            keyboard_event.repeat,
95            keyboard_event.is_composing,
96            keyboard_event.modifiers,
97            0, /* char_code */
98            keycode,
99        )
100    }
101
102    #[expect(clippy::too_many_arguments)]
103    fn new_with_proto(
104        cx: &mut JSContext,
105        window: &Window,
106        proto: Option<HandleObject>,
107        event_type: Atom,
108        can_bubble: bool,
109        cancelable: bool,
110        view: Option<&Window>,
111        _detail: i32,
112        key: Key,
113        code: DOMString,
114        original_code: Option<Code>,
115        location: u32,
116        repeat: bool,
117        is_composing: bool,
118        modifiers: Modifiers,
119        char_code: u32,
120        key_code: u32,
121    ) -> DomRoot<KeyboardEvent> {
122        let event = KeyboardEvent::new_uninitialized_with_proto(cx, window, proto);
123        event.init_event(
124            event_type,
125            can_bubble,
126            cancelable,
127            view,
128            DOMString::from(key.to_string()),
129            location,
130            repeat,
131        );
132        *event.typed_key.safe_borrow_mut(cx.no_gc()) = key;
133        *event.code.safe_borrow_mut(cx.no_gc()) = code;
134        *event.original_code.safe_borrow_mut(cx.no_gc()) = original_code;
135        event.modifiers.set(modifiers);
136        event.is_composing.set(is_composing);
137        event.char_code.set(char_code);
138        event.key_code.set(key_code);
139        event.uievent.set_which(key_code);
140        event
141    }
142
143    pub(crate) fn key(&self) -> Key {
144        self.typed_key.borrow().clone()
145    }
146
147    pub(crate) fn original_code(&self) -> Option<Code> {
148        *self.original_code.borrow()
149    }
150
151    pub(crate) fn modifiers(&self) -> Modifiers {
152        self.modifiers.get()
153    }
154
155    /// <https://w3c.github.io/uievents/#widl-KeyboardEvent-initKeyboardEvent>
156    #[expect(clippy::too_many_arguments)]
157    pub(crate) fn init_event(
158        &self,
159        event_type: Atom,
160        can_bubble_arg: bool,
161        cancelable_arg: bool,
162        view_arg: Option<&Window>,
163        key_arg: DOMString,
164        location_arg: u32,
165        repeat: bool,
166    ) {
167        if self.upcast::<Event>().dispatching() {
168            return;
169        }
170
171        self.upcast::<UIEvent>().init_event(
172            event_type,
173            can_bubble_arg,
174            cancelable_arg,
175            view_arg,
176            0,
177        );
178        *self.key.borrow_mut() = key_arg;
179        self.location.set(location_arg);
180        self.repeat.set(repeat);
181    }
182}
183
184impl KeyboardEventMethods<crate::DomTypeHolder> for KeyboardEvent {
185    /// <https://w3c.github.io/uievents/#dom-keyboardevent-keyboardevent>
186    fn Constructor(
187        cx: &mut JSContext,
188        window: &Window,
189        proto: Option<HandleObject>,
190        event_type: DOMString,
191        init: &KeyboardEventBinding::KeyboardEventInit,
192    ) -> Fallible<DomRoot<KeyboardEvent>> {
193        let mut modifiers = Modifiers::empty();
194        modifiers.set(Modifiers::CONTROL, init.parent.ctrlKey);
195        modifiers.set(Modifiers::ALT, init.parent.altKey);
196        modifiers.set(Modifiers::SHIFT, init.parent.shiftKey);
197        modifiers.set(Modifiers::META, init.parent.metaKey);
198        let event = KeyboardEvent::new_with_proto(
199            cx,
200            window,
201            proto,
202            event_type.into(),
203            init.parent.parent.parent.bubbles,
204            init.parent.parent.parent.cancelable,
205            init.parent.parent.view.as_deref(),
206            init.parent.parent.detail,
207            Key::Named(NamedKey::Unidentified),
208            init.code.clone(),
209            Code::from_str(&init.code.str()).ok(),
210            init.location,
211            init.repeat,
212            init.isComposing,
213            modifiers,
214            init.charCode,
215            init.keyCode,
216        );
217        *event.key.safe_borrow_mut(cx.no_gc()) = init.key.clone();
218        Ok(event)
219    }
220
221    /// <https://w3c.github.io/uievents/#widl-KeyboardEvent-initKeyboardEvent>
222    fn InitKeyboardEvent(
223        &self,
224        event_type: DOMString,
225        can_bubble_arg: bool,
226        cancelable_arg: bool,
227        view_arg: Option<&Window>,
228        key_arg: DOMString,
229        location_arg: u32,
230        _modifiers_list_arg: DOMString,
231        repeat: bool,
232        _locale: DOMString,
233    ) {
234        self.init_event(
235            event_type.into(),
236            can_bubble_arg,
237            cancelable_arg,
238            view_arg,
239            key_arg,
240            location_arg,
241            repeat,
242        );
243    }
244
245    /// <https://w3c.github.io/uievents/#dom-keyboardevent-initkeyboardevent>
246    fn Key(&self) -> DOMString {
247        self.key.borrow().clone()
248    }
249
250    /// <https://w3c.github.io/uievents/#dom-keyboardevent-code>
251    fn Code(&self) -> DOMString {
252        self.code.borrow().clone()
253    }
254
255    /// <https://w3c.github.io/uievents/#dom-keyboardevent-location>
256    fn Location(&self) -> u32 {
257        self.location.get()
258    }
259
260    /// <https://w3c.github.io/uievents/#dom-keyboardevent-ctrlkey>
261    fn CtrlKey(&self) -> bool {
262        self.modifiers.get().contains(Modifiers::CONTROL)
263    }
264
265    /// <https://w3c.github.io/uievents/#dom-keyboardevent-shiftkey>
266    fn ShiftKey(&self) -> bool {
267        self.modifiers.get().contains(Modifiers::SHIFT)
268    }
269
270    /// <https://w3c.github.io/uievents/#dom-keyboardevent-altkey>
271    fn AltKey(&self) -> bool {
272        self.modifiers.get().contains(Modifiers::ALT)
273    }
274
275    /// <https://w3c.github.io/uievents/#dom-keyboardevent-metakey>
276    fn MetaKey(&self) -> bool {
277        self.modifiers.get().contains(Modifiers::META)
278    }
279
280    /// <https://w3c.github.io/uievents/#dom-keyboardevent-repeat>
281    fn Repeat(&self) -> bool {
282        self.repeat.get()
283    }
284
285    /// <https://w3c.github.io/uievents/#dom-keyboardevent-iscomposing>
286    fn IsComposing(&self) -> bool {
287        self.is_composing.get()
288    }
289
290    /// <https://w3c.github.io/uievents/#dom-keyboardevent-getmodifierstate>
291    fn GetModifierState(&self, key_arg: DOMString) -> bool {
292        self.modifiers.get().contains(match &*key_arg.str() {
293            "Alt" => Modifiers::ALT,
294            "AltGraph" => Modifiers::ALT_GRAPH,
295            "CapsLock" => Modifiers::CAPS_LOCK,
296            "Control" => Modifiers::CONTROL,
297            "Fn" => Modifiers::FN,
298            "FnLock" => Modifiers::FN_LOCK,
299            "Meta" => Modifiers::META,
300            "NumLock" => Modifiers::NUM_LOCK,
301            "ScrollLock" => Modifiers::SCROLL_LOCK,
302            "Shift" => Modifiers::SHIFT,
303            "Symbol" => Modifiers::SYMBOL,
304            "SymbolLock" => Modifiers::SYMBOL_LOCK,
305            _ => return false,
306        })
307    }
308
309    /// <https://w3c.github.io/uievents/#dom-keyboardevent-charcode>
310    fn CharCode(&self) -> u32 {
311        self.char_code.get()
312    }
313
314    /// <https://w3c.github.io/uievents/#dom-keyboardevent-keycode>
315    fn KeyCode(&self) -> u32 {
316        self.key_code.get()
317    }
318
319    /// <https://dom.spec.whatwg.org/#dom-event-istrusted>
320    fn IsTrusted(&self) -> bool {
321        self.uievent.IsTrusted()
322    }
323}
324
325fn legacy_keycode_for_keyboard_event(keyboard_event: &keyboard_types::KeyboardEvent) -> u32 {
326    match keyboard_event.code {
327        Code::Backquote => 192,
328        Code::MetaLeft | Code::MetaRight => 224,
329        Code::ContextMenu => 93,
330        Code::Insert => 45,
331        Code::NumLock => 144,
332        Code::PrintScreen => 4,
333        Code::ScrollLock => 145,
334        Code::Pause => 19,
335        Code::F1 => 112,
336        Code::F2 => 113,
337        Code::F3 => 114,
338        Code::F4 => 115,
339        Code::F5 => 116,
340        Code::F6 => 117,
341        Code::F7 => 118,
342        Code::F8 => 119,
343        Code::F9 => 120,
344        Code::F10 => 121,
345        Code::F11 => 122,
346        Code::F12 => 123,
347        Code::F13 => 124,
348        Code::F14 => 125,
349        Code::F15 => 126,
350        Code::F16 => 127,
351        Code::F17 => 128,
352        Code::F18 => 129,
353        Code::F19 => 130,
354        Code::F20 => 131,
355        Code::F21 => 132,
356        Code::F22 => 133,
357        Code::F23 => 134,
358        Code::F24 => 135,
359        _ => keyboard_event.key.legacy_keycode(),
360    }
361}