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    /// <https://w3c.github.io/uievents/#widl-KeyboardEvent-initKeyboardEvent>
152    #[expect(clippy::too_many_arguments)]
153    pub(crate) fn init_event(
154        &self,
155        event_type: Atom,
156        can_bubble_arg: bool,
157        cancelable_arg: bool,
158        view_arg: Option<&Window>,
159        key_arg: DOMString,
160        location_arg: u32,
161        repeat: bool,
162    ) {
163        if self.upcast::<Event>().dispatching() {
164            return;
165        }
166
167        self.upcast::<UIEvent>().init_event(
168            event_type,
169            can_bubble_arg,
170            cancelable_arg,
171            view_arg,
172            0,
173        );
174        *self.key.borrow_mut() = key_arg;
175        self.location.set(location_arg);
176        self.repeat.set(repeat);
177    }
178}
179
180impl KeyboardEventMethods<crate::DomTypeHolder> for KeyboardEvent {
181    /// <https://w3c.github.io/uievents/#dom-keyboardevent-keyboardevent>
182    fn Constructor(
183        cx: &mut JSContext,
184        window: &Window,
185        proto: Option<HandleObject>,
186        event_type: DOMString,
187        init: &KeyboardEventBinding::KeyboardEventInit,
188    ) -> Fallible<DomRoot<KeyboardEvent>> {
189        let mut modifiers = Modifiers::empty();
190        modifiers.set(Modifiers::CONTROL, init.parent.ctrlKey);
191        modifiers.set(Modifiers::ALT, init.parent.altKey);
192        modifiers.set(Modifiers::SHIFT, init.parent.shiftKey);
193        modifiers.set(Modifiers::META, init.parent.metaKey);
194        let event = KeyboardEvent::new_with_proto(
195            cx,
196            window,
197            proto,
198            event_type.into(),
199            init.parent.parent.parent.bubbles,
200            init.parent.parent.parent.cancelable,
201            init.parent.parent.view.as_deref(),
202            init.parent.parent.detail,
203            Key::Named(NamedKey::Unidentified),
204            init.code.clone(),
205            Code::from_str(&init.code.str()).ok(),
206            init.location,
207            init.repeat,
208            init.isComposing,
209            modifiers,
210            init.charCode,
211            init.keyCode,
212        );
213        *event.key.safe_borrow_mut(cx.no_gc()) = init.key.clone();
214        Ok(event)
215    }
216
217    /// <https://w3c.github.io/uievents/#widl-KeyboardEvent-initKeyboardEvent>
218    fn InitKeyboardEvent(
219        &self,
220        event_type: DOMString,
221        can_bubble_arg: bool,
222        cancelable_arg: bool,
223        view_arg: Option<&Window>,
224        key_arg: DOMString,
225        location_arg: u32,
226        _modifiers_list_arg: DOMString,
227        repeat: bool,
228        _locale: DOMString,
229    ) {
230        self.init_event(
231            event_type.into(),
232            can_bubble_arg,
233            cancelable_arg,
234            view_arg,
235            key_arg,
236            location_arg,
237            repeat,
238        );
239    }
240
241    /// <https://w3c.github.io/uievents/#dom-keyboardevent-initkeyboardevent>
242    fn Key(&self) -> DOMString {
243        self.key.borrow().clone()
244    }
245
246    /// <https://w3c.github.io/uievents/#dom-keyboardevent-code>
247    fn Code(&self) -> DOMString {
248        self.code.borrow().clone()
249    }
250
251    /// <https://w3c.github.io/uievents/#dom-keyboardevent-location>
252    fn Location(&self) -> u32 {
253        self.location.get()
254    }
255
256    /// <https://w3c.github.io/uievents/#dom-keyboardevent-ctrlkey>
257    fn CtrlKey(&self) -> bool {
258        self.modifiers.get().contains(Modifiers::CONTROL)
259    }
260
261    /// <https://w3c.github.io/uievents/#dom-keyboardevent-shiftkey>
262    fn ShiftKey(&self) -> bool {
263        self.modifiers.get().contains(Modifiers::SHIFT)
264    }
265
266    /// <https://w3c.github.io/uievents/#dom-keyboardevent-altkey>
267    fn AltKey(&self) -> bool {
268        self.modifiers.get().contains(Modifiers::ALT)
269    }
270
271    /// <https://w3c.github.io/uievents/#dom-keyboardevent-metakey>
272    fn MetaKey(&self) -> bool {
273        self.modifiers.get().contains(Modifiers::META)
274    }
275
276    /// <https://w3c.github.io/uievents/#dom-keyboardevent-repeat>
277    fn Repeat(&self) -> bool {
278        self.repeat.get()
279    }
280
281    /// <https://w3c.github.io/uievents/#dom-keyboardevent-iscomposing>
282    fn IsComposing(&self) -> bool {
283        self.is_composing.get()
284    }
285
286    /// <https://w3c.github.io/uievents/#dom-keyboardevent-getmodifierstate>
287    fn GetModifierState(&self, key_arg: DOMString) -> bool {
288        self.modifiers.get().contains(match &*key_arg.str() {
289            "Alt" => Modifiers::ALT,
290            "AltGraph" => Modifiers::ALT_GRAPH,
291            "CapsLock" => Modifiers::CAPS_LOCK,
292            "Control" => Modifiers::CONTROL,
293            "Fn" => Modifiers::FN,
294            "FnLock" => Modifiers::FN_LOCK,
295            "Meta" => Modifiers::META,
296            "NumLock" => Modifiers::NUM_LOCK,
297            "ScrollLock" => Modifiers::SCROLL_LOCK,
298            "Shift" => Modifiers::SHIFT,
299            "Symbol" => Modifiers::SYMBOL,
300            "SymbolLock" => Modifiers::SYMBOL_LOCK,
301            _ => return false,
302        })
303    }
304
305    /// <https://w3c.github.io/uievents/#dom-keyboardevent-charcode>
306    fn CharCode(&self) -> u32 {
307        self.char_code.get()
308    }
309
310    /// <https://w3c.github.io/uievents/#dom-keyboardevent-keycode>
311    fn KeyCode(&self) -> u32 {
312        self.key_code.get()
313    }
314
315    /// <https://dom.spec.whatwg.org/#dom-event-istrusted>
316    fn IsTrusted(&self) -> bool {
317        self.uievent.IsTrusted()
318    }
319}
320
321fn legacy_keycode_for_keyboard_event(keyboard_event: &keyboard_types::KeyboardEvent) -> u32 {
322    match keyboard_event.code {
323        Code::Backquote => 192,
324        Code::MetaLeft | Code::MetaRight => 224,
325        Code::ContextMenu => 93,
326        Code::Insert => 45,
327        Code::NumLock => 144,
328        Code::PrintScreen => 4,
329        Code::ScrollLock => 145,
330        Code::Pause => 19,
331        Code::F1 => 112,
332        Code::F2 => 113,
333        Code::F3 => 114,
334        Code::F4 => 115,
335        Code::F5 => 116,
336        Code::F6 => 117,
337        Code::F7 => 118,
338        Code::F8 => 119,
339        Code::F9 => 120,
340        Code::F10 => 121,
341        Code::F11 => 122,
342        Code::F12 => 123,
343        Code::F13 => 124,
344        Code::F14 => 125,
345        Code::F15 => 126,
346        Code::F16 => 127,
347        Code::F17 => 128,
348        Code::F18 => 129,
349        Code::F19 => 130,
350        Code::F20 => 131,
351        Code::F21 => 132,
352        Code::F22 => 133,
353        Code::F23 => 134,
354        Code::F24 => 135,
355        _ => keyboard_event.key.legacy_keycode(),
356    }
357}