Skip to main content

script_bindings/
conversions.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::{ptr, slice};
6
7use js::conversions::{
8    ConversionResult, FromJSValConvertible, ToJSValConvertible, jsstr_to_string,
9};
10use js::error::throw_type_error;
11use js::glue::{
12    GetProxyHandlerExtra, GetProxyReservedSlot, IsProxyHandlerFamily, IsWrapper,
13    JS_GetReservedSlot, UnwrapObjectDynamic,
14};
15use js::jsapi::{
16    Heap, IsWindowProxy, JS_DeprecatedStringHasLatin1Chars, JS_NewStringCopyN, JSContext, JSObject,
17};
18use js::jsval::{ObjectValue, StringValue, UndefinedValue};
19use js::rust::wrappers2::{
20    IsArrayObject, JS_GetLatin1StringCharsAndLength, JS_GetTwoByteStringCharsAndLength,
21};
22use js::rust::{
23    HandleId, HandleValue, MutableHandleValue, ToString, get_object_class, is_dom_class,
24    is_dom_object, maybe_wrap_value,
25};
26use keyboard_types::Modifiers;
27use num_traits::Float;
28
29use crate::JSTraceable;
30use crate::codegen::GenericBindings::EventModifierInitBinding::EventModifierInit;
31use crate::inheritance::Castable;
32use crate::num::Finite;
33use crate::reflector::{DomObject, Reflector};
34use crate::root::DomRoot;
35use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
36use crate::str::{ByteString, DOMString, USVString};
37use crate::trace::RootedTraceableBox;
38use crate::utils::{DOMClass, DOMJSClass};
39
40/// A safe wrapper for `ToJSValConvertible`.
41pub trait SafeToJSValConvertible {
42    fn safe_to_jsval(&self, cx: SafeJSContext, rval: MutableHandleValue, can_gc: CanGc);
43}
44
45impl<T: ToJSValConvertible + ?Sized> SafeToJSValConvertible for T {
46    fn safe_to_jsval(&self, cx: SafeJSContext, rval: MutableHandleValue, _can_gc: CanGc) {
47        unsafe { self.to_jsval(*cx, rval) };
48    }
49}
50
51/// A trait to check whether a given `JSObject` implements an IDL interface.
52pub trait IDLInterface {
53    /// Returns whether the given DOM class derives that interface.
54    fn derives(_: &'static DOMClass) -> bool;
55
56    /// First prototype ID in the DFS-ordered range for this interface and its descendants.
57    const PROTO_FIRST: u16 = 0;
58    /// Last prototype ID in the DFS-ordered range for this interface and its descendants.
59    const PROTO_LAST: u16 = u16::MAX;
60}
61
62/// A trait to mark an IDL interface as deriving from another one.
63pub trait DerivedFrom<T: Castable>: Castable {}
64
65// http://heycam.github.io/webidl/#es-USVString
66impl ToJSValConvertible for USVString {
67    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
68        self.0.to_jsval(cx, rval);
69    }
70}
71
72/// Behavior for stringification of `JSVal`s.
73#[derive(Clone, PartialEq)]
74pub enum StringificationBehavior {
75    /// Convert `null` to the string `"null"`.
76    Default,
77    /// Convert `null` to the empty string.
78    Empty,
79}
80
81// https://heycam.github.io/webidl/#es-DOMString
82impl FromJSValConvertible for DOMString {
83    type Config = StringificationBehavior;
84    unsafe fn from_jsval(
85        _cx: *mut JSContext,
86        value: HandleValue,
87        null_behavior: StringificationBehavior,
88    ) -> Result<ConversionResult<DOMString>, ()> {
89        // TODO https://github.com/servo/mozjs/issues/749
90        let mut cx = unsafe { crate::script_runtime::temp_cx() };
91        FromJSValConvertible::safe_from_jsval(&mut cx, value, null_behavior)
92    }
93
94    fn safe_from_jsval(
95        cx: &mut js::context::JSContext,
96        value: HandleValue,
97        null_behavior: StringificationBehavior,
98    ) -> Result<ConversionResult<DOMString>, ()> {
99        if null_behavior == StringificationBehavior::Empty && value.get().is_null() {
100            Ok(ConversionResult::Success(DOMString::new()))
101        } else {
102            match DOMString::from_js_string(cx, value) {
103                Ok(domstring) => Ok(ConversionResult::Success(domstring)),
104                Err(_) => Err(()),
105            }
106        }
107    }
108}
109
110// http://heycam.github.io/webidl/#es-USVString
111impl FromJSValConvertible for USVString {
112    type Config = ();
113    unsafe fn from_jsval(
114        _cx: *mut JSContext,
115        value: HandleValue,
116        _: (),
117    ) -> Result<ConversionResult<USVString>, ()> {
118        // TODO https://github.com/servo/mozjs/issues/749
119        let mut cx = unsafe { crate::script_runtime::temp_cx() };
120        FromJSValConvertible::safe_from_jsval(&mut cx, value, ())
121    }
122
123    fn safe_from_jsval(
124        cx: &mut js::context::JSContext,
125        value: HandleValue,
126        _: (),
127    ) -> Result<ConversionResult<USVString>, ()> {
128        let Some(jsstr) = ptr::NonNull::new(unsafe { ToString(cx, value) }) else {
129            debug!("ToString failed");
130            return Err(());
131        };
132
133        // FIXME(ajeffrey): Convert directly from DOMString to USVString
134        Ok(ConversionResult::Success(USVString(unsafe {
135            jsstr_to_string(cx, jsstr)
136        })))
137    }
138}
139
140// http://heycam.github.io/webidl/#es-ByteString
141impl ToJSValConvertible for ByteString {
142    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
143        let jsstr = JS_NewStringCopyN(
144            cx,
145            self.as_ptr() as *const libc::c_char,
146            self.len() as libc::size_t,
147        );
148        if jsstr.is_null() {
149            panic!("JS_NewStringCopyN failed");
150        }
151        rval.set(StringValue(&*jsstr));
152    }
153}
154
155// http://heycam.github.io/webidl/#es-ByteString
156impl FromJSValConvertible for ByteString {
157    type Config = ();
158    unsafe fn from_jsval(
159        _cx: *mut JSContext,
160        value: HandleValue,
161        _option: (),
162    ) -> Result<ConversionResult<ByteString>, ()> {
163        // TODO https://github.com/servo/mozjs/issues/749
164        let mut cx = unsafe { crate::script_runtime::temp_cx() };
165        FromJSValConvertible::safe_from_jsval(&mut cx, value, ())
166    }
167
168    fn safe_from_jsval(
169        cx: &mut js::context::JSContext,
170        value: HandleValue,
171        _option: (),
172    ) -> Result<ConversionResult<ByteString>, ()> {
173        unsafe {
174            let string = ToString(cx, value);
175            if string.is_null() {
176                debug!("ToString failed");
177                return Err(());
178            }
179
180            let latin1 = JS_DeprecatedStringHasLatin1Chars(string);
181            if latin1 {
182                let mut length = 0;
183                let chars = JS_GetLatin1StringCharsAndLength(cx, string, &mut length);
184                assert!(!chars.is_null());
185
186                let char_slice = slice::from_raw_parts(chars as *mut u8, length);
187                return Ok(ConversionResult::Success(ByteString::new(
188                    char_slice.to_vec(),
189                )));
190            }
191
192            let mut length = 0;
193            let chars = JS_GetTwoByteStringCharsAndLength(cx, string, &mut length);
194            let char_vec = slice::from_raw_parts(chars, length);
195
196            if char_vec.iter().any(|&c| c > 0xFF) {
197                throw_type_error(cx.raw_cx(), c"Invalid ByteString");
198                Err(())
199            } else {
200                Ok(ConversionResult::Success(ByteString::new(
201                    char_vec.iter().map(|&c| c as u8).collect(),
202                )))
203            }
204        }
205    }
206}
207
208impl<T> ToJSValConvertible for Reflector<T> {
209    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
210        let obj = self.get_jsobject().get();
211        assert!(!obj.is_null());
212        rval.set(ObjectValue(obj));
213        maybe_wrap_value(cx, rval);
214    }
215}
216
217impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> {
218    type Config = ();
219
220    unsafe fn from_jsval(
221        _cx: *mut JSContext,
222        value: HandleValue,
223        _config: Self::Config,
224    ) -> Result<ConversionResult<DomRoot<T>>, ()> {
225        // TODO https://github.com/servo/mozjs/issues/749
226        let mut cx = unsafe { crate::script_runtime::temp_cx() };
227        FromJSValConvertible::safe_from_jsval(&mut cx, value, ())
228    }
229
230    fn safe_from_jsval(
231        cx: &mut js::context::JSContext,
232        value: HandleValue,
233        _config: Self::Config,
234    ) -> Result<ConversionResult<DomRoot<T>>, ()> {
235        Ok(match root_from_handlevalue(value, cx.into()) {
236            Ok(result) => ConversionResult::Success(result),
237            Err(()) => ConversionResult::Failure(c"value is not an object".into()),
238        })
239    }
240}
241
242impl<T: DomObject> ToJSValConvertible for DomRoot<T> {
243    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
244        self.reflector().to_jsval(cx, rval);
245    }
246}
247
248/// Get the `DOMClass` from `obj`, or `Err(())` if `obj` is not a DOM object.
249///
250/// # Safety
251/// obj must point to a valid, non-null JS object.
252#[allow(clippy::result_unit_err)]
253pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> {
254    let clasp = get_object_class(obj);
255    if is_dom_class(&*clasp) {
256        trace!("plain old dom object");
257        let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
258        return Ok(&(*domjsclass).dom_class);
259    }
260    if is_dom_proxy(obj) {
261        trace!("proxy dom object");
262        let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
263        if dom_class.is_null() {
264            return Err(());
265        }
266        return Ok(&*dom_class);
267    }
268    trace!("not a dom object");
269    Err(())
270}
271
272/// Returns whether `obj` is a DOM object implemented as a proxy.
273///
274/// # Safety
275/// obj must point to a valid, non-null JS object.
276pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool {
277    unsafe {
278        let clasp = get_object_class(obj);
279        ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj)
280    }
281}
282
283/// The index of the slot wherein a pointer to the reflected DOM object is
284/// stored for non-proxy bindings.
285// We use slot 0 for holding the raw object.  This is safe for both
286// globals and non-globals.
287pub const DOM_OBJECT_SLOT: u32 = 0;
288
289/// Get the private pointer of a DOM object from a given reflector.
290///
291/// # Safety
292/// obj must point to a valid non-null JS object.
293pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
294    let mut value = UndefinedValue();
295    if is_dom_object(obj) {
296        JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
297    } else {
298        debug_assert!(is_dom_proxy(obj));
299        GetProxyReservedSlot(obj, 0, &mut value);
300    };
301    if value.is_undefined() {
302        ptr::null()
303    } else {
304        value.to_private()
305    }
306}
307
308pub enum PrototypeCheck {
309    Derive(fn(&'static DOMClass) -> bool),
310    Depth { depth: usize, proto_id: u16 },
311}
312
313/// Get a `*const libc::c_void` for the given DOM object, unwrapping any
314/// wrapper around it first, and checking if the object is of the correct type.
315///
316/// Returns Err(()) if `obj` is an opaque security wrapper or if the object is
317/// not an object for a DOM object of the given type (as defined by the
318/// proto_id and proto_depth).
319///
320/// # Safety
321/// obj must point to a valid, non-null JS object.
322/// cx must point to a valid, non-null JS context.
323#[inline]
324#[allow(clippy::result_unit_err)]
325pub unsafe fn private_from_proto_check(
326    mut obj: *mut JSObject,
327    cx: *mut JSContext,
328    proto_check: PrototypeCheck,
329) -> Result<*const libc::c_void, ()> {
330    let dom_class = get_dom_class(obj).or_else(|_| {
331        if IsWrapper(obj) {
332            trace!("found wrapper");
333            obj = UnwrapObjectDynamic(obj, cx, /* stopAtWindowProxy = */ false);
334            if obj.is_null() {
335                trace!("unwrapping security wrapper failed");
336                Err(())
337            } else {
338                assert!(!IsWrapper(obj));
339                trace!("unwrapped successfully");
340                get_dom_class(obj)
341            }
342        } else {
343            trace!("not a dom wrapper");
344            Err(())
345        }
346    })?;
347
348    let prototype_matches = match proto_check {
349        PrototypeCheck::Derive(f) => (f)(dom_class),
350        PrototypeCheck::Depth { depth, proto_id } => {
351            dom_class.interface_chain[depth] as u16 == proto_id
352        },
353    };
354
355    if prototype_matches {
356        trace!("good prototype");
357        Ok(private_from_object(obj))
358    } else {
359        trace!("bad prototype");
360        Err(())
361    }
362}
363
364/// Get a `*const T` for a DOM object accessible from a `JSObject`.
365///
366/// # Safety
367/// obj must point to a valid, non-null JS object.
368/// cx must point to a valid, non-null JS context.
369#[allow(clippy::result_unit_err)]
370pub unsafe fn native_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()>
371where
372    T: DomObject + IDLInterface,
373{
374    unsafe {
375        private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives))
376            .map(|ptr| ptr as *const T)
377    }
378}
379
380/// Get a `DomRoot<T>` for the given DOM object, unwrapping any wrapper
381/// around it first, and checking if the object is of the correct type.
382///
383/// Returns Err(()) if `obj` is an opaque security wrapper or if the object is
384/// not a reflector for a DOM object of the given type (as defined by the
385/// proto_id and proto_depth).
386///
387/// # Safety
388/// obj must point to a valid, non-null JS object.
389/// cx must point to a valid, non-null JS context.
390#[allow(clippy::result_unit_err)]
391pub unsafe fn root_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<DomRoot<T>, ()>
392where
393    T: DomObject + IDLInterface,
394{
395    native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
396}
397
398/// Get a `DomRoot<T>` for a DOM object accessible from a `HandleValue`.
399/// Caller is responsible for throwing a JS exception if needed in case of error.
400///
401/// # Safety
402/// cx must point to a valid, non-null JS context.
403#[allow(clippy::result_unit_err)]
404pub fn root_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<DomRoot<T>, ()>
405where
406    T: DomObject + IDLInterface,
407{
408    if !v.get().is_object() {
409        return Err(());
410    }
411    #[expect(unsafe_code)]
412    unsafe {
413        root_from_object(v.get().to_object(), *cx)
414    }
415}
416
417/// Convert `id` to a `DOMString`. Returns `None` if `id` is not a string or
418/// integer.
419///
420/// Handling of invalid UTF-16 in strings depends on the relevant option.
421pub fn jsid_to_string(cx: &js::context::JSContext, id: HandleId) -> Option<DOMString> {
422    let id_raw = *id;
423    if id_raw.is_string() {
424        let jsstr = ptr::NonNull::new(id_raw.to_string()).unwrap();
425        return Some(unsafe { jsstr_to_string(cx, jsstr) }.into());
426    }
427
428    if id_raw.is_int() {
429        return Some(id_raw.to_int().to_string().into());
430    }
431
432    None
433}
434
435impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
436    #[inline]
437    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
438        let value = **self;
439        value.to_jsval(cx, rval);
440    }
441}
442
443impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
444    type Config = ();
445
446    unsafe fn from_jsval(
447        _cx: *mut JSContext,
448        value: HandleValue,
449        option: (),
450    ) -> Result<ConversionResult<Finite<T>>, ()> {
451        // TODO https://github.com/servo/mozjs/issues/749
452        let mut cx = unsafe { crate::script_runtime::temp_cx() };
453        FromJSValConvertible::safe_from_jsval(&mut cx, value, option)
454    }
455
456    fn safe_from_jsval(
457        cx: &mut js::context::JSContext,
458        value: HandleValue,
459        option: (),
460    ) -> Result<ConversionResult<Finite<T>>, ()> {
461        let result = match FromJSValConvertible::safe_from_jsval(cx, value, option)? {
462            ConversionResult::Success(v) => v,
463            ConversionResult::Failure(error) => {
464                // FIXME(emilio): Why throwing instead of propagating the error?
465                unsafe { throw_type_error(cx.raw_cx(), &error) };
466                return Err(());
467            },
468        };
469        match Finite::new(result) {
470            Some(v) => Ok(ConversionResult::Success(v)),
471            None => {
472                unsafe {
473                    throw_type_error(
474                        cx.raw_cx(),
475                        c"this argument is not a finite floating-point value",
476                    )
477                };
478                Err(())
479            },
480        }
481    }
482}
483
484/// Get a `*const libc::c_void` for the given DOM object, unless it is a DOM
485/// wrapper, and checking if the object is of the correct type.
486///
487/// Returns Err(()) if `obj` is a wrapper or if the object is not an object
488/// for a DOM object of the given type (as defined by the proto_id and proto_depth).
489#[inline]
490#[allow(clippy::result_unit_err)]
491unsafe fn private_from_proto_check_static(
492    obj: *mut JSObject,
493    proto_check: fn(&'static DOMClass) -> bool,
494) -> Result<*const libc::c_void, ()> {
495    let dom_class = get_dom_class(obj).map_err(|_| ())?;
496    if proto_check(dom_class) {
497        trace!("good prototype");
498        Ok(private_from_object(obj))
499    } else {
500        trace!("bad prototype");
501        Err(())
502    }
503}
504
505/// Get a `*const T` for a DOM object accessible from a `JSObject`, where the DOM object
506/// is guaranteed not to be a wrapper.
507///
508/// # Safety
509/// `obj` must point to a valid, non-null JSObject.
510#[allow(clippy::result_unit_err)]
511pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
512where
513    T: DomObject + IDLInterface,
514{
515    private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T)
516}
517
518/// Get a `*const T` for a DOM object accessible from a `HandleValue`.
519/// Caller is responsible for throwing a JS exception if needed in case of error.
520///
521/// # Safety
522/// `cx` must point to a valid, non-null JSContext.
523#[allow(clippy::result_unit_err)]
524pub fn native_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<*const T, ()>
525where
526    T: DomObject + IDLInterface,
527{
528    if !v.get().is_object() {
529        return Err(());
530    }
531
532    #[expect(unsafe_code)]
533    unsafe {
534        native_from_object(v.get().to_object(), *cx)
535    }
536}
537
538impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
539    #[inline]
540    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
541        let value = &**self;
542        value.to_jsval(cx, rval);
543    }
544}
545
546impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
547where
548    T: FromJSValConvertible + js::rust::GCMethods + Copy,
549    Heap<T>: JSTraceable + Default,
550{
551    type Config = T::Config;
552
553    unsafe fn from_jsval(
554        _cx: *mut JSContext,
555        value: HandleValue,
556        config: Self::Config,
557    ) -> Result<ConversionResult<Self>, ()> {
558        // TODO https://github.com/servo/mozjs/issues/749
559        let mut cx = unsafe { crate::script_runtime::temp_cx() };
560        FromJSValConvertible::safe_from_jsval(&mut cx, value, config)
561    }
562
563    fn safe_from_jsval(
564        cx: &mut js::context::JSContext,
565        value: HandleValue,
566        config: Self::Config,
567    ) -> Result<ConversionResult<Self>, ()> {
568        T::safe_from_jsval(cx, value, config).map(|result| match result {
569            ConversionResult::Success(inner) => {
570                ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
571            },
572            ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
573        })
574    }
575}
576
577/// Returns whether `value` is an array-like object (Array, FileList,
578/// HTMLCollection, HTMLFormControlsCollection, HTMLOptionsCollection,
579/// NodeList, DOMTokenList).
580pub fn is_array_like<D: crate::DomTypes>(
581    cx: &mut js::context::JSContext,
582    value: HandleValue,
583) -> bool {
584    let mut is_array = false;
585    assert!(unsafe { IsArrayObject(cx, value, &mut is_array) });
586    if is_array {
587        return true;
588    }
589
590    let object: *mut JSObject = match FromJSValConvertible::safe_from_jsval(cx, value, ()).unwrap()
591    {
592        ConversionResult::Success(object) => object,
593        _ => return false,
594    };
595
596    unsafe {
597        // TODO: HTMLAllCollection
598        if root_from_object::<D::DOMTokenList>(object, cx.raw_cx()).is_ok() {
599            return true;
600        }
601        if root_from_object::<D::FileList>(object, cx.raw_cx()).is_ok() {
602            return true;
603        }
604        if root_from_object::<D::HTMLCollection>(object, cx.raw_cx()).is_ok() {
605            return true;
606        }
607        if root_from_object::<D::HTMLFormControlsCollection>(object, cx.raw_cx()).is_ok() {
608            return true;
609        }
610        if root_from_object::<D::HTMLOptionsCollection>(object, cx.raw_cx()).is_ok() {
611            return true;
612        }
613        if root_from_object::<D::NodeList>(object, cx.raw_cx()).is_ok() {
614            return true;
615        }
616    }
617
618    false
619}
620
621/// Get a `DomRoot<T>` for a WindowProxy accessible from a `HandleValue`.
622/// Caller is responsible for throwing a JS exception if needed in case of error.
623pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
624    v: HandleValue,
625    _cx: SafeJSContext,
626) -> Result<DomRoot<D::WindowProxy>, ()> {
627    if !v.get().is_object() {
628        return Err(());
629    }
630    let object = v.get().to_object();
631    if !IsWindowProxy(object) {
632        return Err(());
633    }
634    let mut value = UndefinedValue();
635    GetProxyReservedSlot(object, 0, &mut value);
636    let ptr = value.to_private() as *const D::WindowProxy;
637    Ok(DomRoot::from_ref(&*ptr))
638}
639
640#[allow(deprecated)]
641impl<D: crate::DomTypes> EventModifierInit<D> {
642    pub fn modifiers(&self) -> Modifiers {
643        let mut modifiers = Modifiers::empty();
644        if self.altKey {
645            modifiers.insert(Modifiers::ALT);
646        }
647        if self.ctrlKey {
648            modifiers.insert(Modifiers::CONTROL);
649        }
650        if self.shiftKey {
651            modifiers.insert(Modifiers::SHIFT);
652        }
653        if self.metaKey {
654            modifiers.insert(Modifiers::META);
655        }
656        if self.keyModifierStateAltGraph {
657            modifiers.insert(Modifiers::ALT_GRAPH);
658        }
659        if self.keyModifierStateCapsLock {
660            modifiers.insert(Modifiers::CAPS_LOCK);
661        }
662        if self.keyModifierStateFn {
663            modifiers.insert(Modifiers::FN);
664        }
665        if self.keyModifierStateFnLock {
666            modifiers.insert(Modifiers::FN_LOCK);
667        }
668        if self.keyModifierStateHyper {
669            modifiers.insert(Modifiers::HYPER);
670        }
671        if self.keyModifierStateNumLock {
672            modifiers.insert(Modifiers::NUM_LOCK);
673        }
674        if self.keyModifierStateScrollLock {
675            modifiers.insert(Modifiers::SCROLL_LOCK);
676        }
677        if self.keyModifierStateSuper {
678            modifiers.insert(Modifiers::SUPER);
679        }
680        if self.keyModifierStateSymbol {
681            modifiers.insert(Modifiers::SYMBOL);
682        }
683        if self.keyModifierStateSymbolLock {
684            modifiers.insert(Modifiers::SYMBOL_LOCK);
685        }
686        modifiers
687    }
688}