1use 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_GetLatin1StringCharsAndLength,
17 JS_GetTwoByteStringCharsAndLength, JS_NewStringCopyN, JSContext, JSObject,
18};
19use js::jsval::{ObjectValue, StringValue, UndefinedValue};
20use js::rust::wrappers::IsArrayObject;
21use js::rust::{
22 HandleId, HandleValue, MutableHandleValue, ToString, get_object_class, is_dom_class,
23 is_dom_object, maybe_wrap_value,
24};
25use keyboard_types::Modifiers;
26use num_traits::Float;
27
28use crate::JSTraceable;
29use crate::codegen::GenericBindings::EventModifierInitBinding::EventModifierInit;
30use crate::inheritance::Castable;
31use crate::num::Finite;
32use crate::reflector::{DomObject, Reflector};
33use crate::root::DomRoot;
34use crate::script_runtime::JSContext as SafeJSContext;
35use crate::str::{ByteString, DOMString, USVString};
36use crate::trace::RootedTraceableBox;
37use crate::utils::{DOMClass, DOMJSClass};
38
39pub trait SafeToJSValConvertible {
41 fn safe_to_jsval(&self, cx: SafeJSContext, rval: MutableHandleValue);
42}
43
44impl<T: ToJSValConvertible + ?Sized> SafeToJSValConvertible for T {
45 fn safe_to_jsval(&self, cx: SafeJSContext, rval: MutableHandleValue) {
46 unsafe { self.to_jsval(*cx, rval) };
47 }
48}
49
50pub trait IDLInterface {
52 fn derives(_: &'static DOMClass) -> bool;
54}
55
56pub trait DerivedFrom<T: Castable>: Castable {}
58
59impl ToJSValConvertible for USVString {
61 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
62 self.0.to_jsval(cx, rval);
63 }
64}
65
66#[derive(Clone, PartialEq)]
68pub enum StringificationBehavior {
69 Default,
71 Empty,
73}
74
75pub trait SafeFromJSValConvertible: Sized {
77 type Config;
78
79 #[allow(clippy::result_unit_err)] fn safe_from_jsval(
81 cx: SafeJSContext,
82 value: HandleValue,
83 option: Self::Config,
84 ) -> Result<ConversionResult<Self>, ()>;
85}
86
87impl<T: FromJSValConvertible> SafeFromJSValConvertible for T {
88 type Config = <T as FromJSValConvertible>::Config;
89
90 fn safe_from_jsval(
91 cx: SafeJSContext,
92 value: HandleValue,
93 option: Self::Config,
94 ) -> Result<ConversionResult<Self>, ()> {
95 unsafe { T::from_jsval(*cx, value, option) }
96 }
97}
98
99impl FromJSValConvertible for DOMString {
101 type Config = StringificationBehavior;
102 unsafe fn from_jsval(
103 cx: *mut JSContext,
104 value: HandleValue,
105 null_behavior: StringificationBehavior,
106 ) -> Result<ConversionResult<DOMString>, ()> {
107 if null_behavior == StringificationBehavior::Empty && value.get().is_null() {
108 Ok(ConversionResult::Success(DOMString::new()))
109 } else {
110 match ptr::NonNull::new(ToString(cx, value)) {
111 Some(jsstr) => Ok(ConversionResult::Success(DOMString::from_string(
112 jsstr_to_string(cx, jsstr),
113 ))),
114 None => {
115 debug!("ToString failed");
116 Err(())
117 },
118 }
119 }
120 }
121}
122
123impl FromJSValConvertible for USVString {
125 type Config = ();
126 unsafe fn from_jsval(
127 cx: *mut JSContext,
128 value: HandleValue,
129 _: (),
130 ) -> Result<ConversionResult<USVString>, ()> {
131 let Some(jsstr) = ptr::NonNull::new(ToString(cx, value)) else {
132 debug!("ToString failed");
133 return Err(());
134 };
135 let latin1 = JS_DeprecatedStringHasLatin1Chars(jsstr.as_ptr());
136 if latin1 {
137 return Ok(ConversionResult::Success(USVString(jsstr_to_string(
139 cx, jsstr,
140 ))));
141 }
142 let mut length = 0;
143 let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr.as_ptr(), &mut length);
144 assert!(!chars.is_null());
145 let char_vec = slice::from_raw_parts(chars, length);
146 Ok(ConversionResult::Success(USVString(
147 String::from_utf16_lossy(char_vec),
148 )))
149 }
150}
151
152impl ToJSValConvertible for ByteString {
154 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
155 let jsstr = JS_NewStringCopyN(
156 cx,
157 self.as_ptr() as *const libc::c_char,
158 self.len() as libc::size_t,
159 );
160 if jsstr.is_null() {
161 panic!("JS_NewStringCopyN failed");
162 }
163 rval.set(StringValue(&*jsstr));
164 }
165}
166
167impl FromJSValConvertible for ByteString {
169 type Config = ();
170 unsafe fn from_jsval(
171 cx: *mut JSContext,
172 value: HandleValue,
173 _option: (),
174 ) -> Result<ConversionResult<ByteString>, ()> {
175 let string = ToString(cx, value);
176 if string.is_null() {
177 debug!("ToString failed");
178 return Err(());
179 }
180
181 let latin1 = JS_DeprecatedStringHasLatin1Chars(string);
182 if latin1 {
183 let mut length = 0;
184 let chars = JS_GetLatin1StringCharsAndLength(cx, ptr::null(), string, &mut length);
185 assert!(!chars.is_null());
186
187 let char_slice = slice::from_raw_parts(chars as *mut u8, length);
188 return Ok(ConversionResult::Success(ByteString::new(
189 char_slice.to_vec(),
190 )));
191 }
192
193 let mut length = 0;
194 let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), string, &mut length);
195 let char_vec = slice::from_raw_parts(chars, length);
196
197 if char_vec.iter().any(|&c| c > 0xFF) {
198 throw_type_error(cx, "Invalid ByteString");
199 Err(())
200 } else {
201 Ok(ConversionResult::Success(ByteString::new(
202 char_vec.iter().map(|&c| c as u8).collect(),
203 )))
204 }
205 }
206}
207
208impl ToJSValConvertible for Reflector {
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 Ok(
226 match root_from_handlevalue(value, SafeJSContext::from_ptr(cx)) {
227 Ok(result) => ConversionResult::Success(result),
228 Err(()) => ConversionResult::Failure("value is not an object".into()),
229 },
230 )
231 }
232}
233
234impl<T: DomObject> ToJSValConvertible for DomRoot<T> {
235 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
236 self.reflector().to_jsval(cx, rval);
237 }
238}
239
240#[allow(clippy::result_unit_err)]
245pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> {
246 let clasp = get_object_class(obj);
247 if is_dom_class(&*clasp) {
248 trace!("plain old dom object");
249 let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
250 return Ok(&(*domjsclass).dom_class);
251 }
252 if is_dom_proxy(obj) {
253 trace!("proxy dom object");
254 let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
255 if dom_class.is_null() {
256 return Err(());
257 }
258 return Ok(&*dom_class);
259 }
260 trace!("not a dom object");
261 Err(())
262}
263
264pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool {
269 unsafe {
270 let clasp = get_object_class(obj);
271 ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj)
272 }
273}
274
275pub const DOM_OBJECT_SLOT: u32 = 0;
280
281pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
286 let mut value = UndefinedValue();
287 if is_dom_object(obj) {
288 JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
289 } else {
290 debug_assert!(is_dom_proxy(obj));
291 GetProxyReservedSlot(obj, 0, &mut value);
292 };
293 if value.is_undefined() {
294 ptr::null()
295 } else {
296 value.to_private()
297 }
298}
299
300pub enum PrototypeCheck {
301 Derive(fn(&'static DOMClass) -> bool),
302 Depth { depth: usize, proto_id: u16 },
303}
304
305#[inline]
316#[allow(clippy::result_unit_err)]
317pub unsafe fn private_from_proto_check(
318 mut obj: *mut JSObject,
319 cx: *mut JSContext,
320 proto_check: PrototypeCheck,
321) -> Result<*const libc::c_void, ()> {
322 let dom_class = get_dom_class(obj).or_else(|_| {
323 if IsWrapper(obj) {
324 trace!("found wrapper");
325 obj = UnwrapObjectDynamic(obj, cx, false);
326 if obj.is_null() {
327 trace!("unwrapping security wrapper failed");
328 Err(())
329 } else {
330 assert!(!IsWrapper(obj));
331 trace!("unwrapped successfully");
332 get_dom_class(obj)
333 }
334 } else {
335 trace!("not a dom wrapper");
336 Err(())
337 }
338 })?;
339
340 let prototype_matches = match proto_check {
341 PrototypeCheck::Derive(f) => (f)(dom_class),
342 PrototypeCheck::Depth { depth, proto_id } => {
343 dom_class.interface_chain[depth] as u16 == proto_id
344 },
345 };
346
347 if prototype_matches {
348 trace!("good prototype");
349 Ok(private_from_object(obj))
350 } else {
351 trace!("bad prototype");
352 Err(())
353 }
354}
355
356#[allow(clippy::result_unit_err)]
362pub unsafe fn native_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()>
363where
364 T: DomObject + IDLInterface,
365{
366 unsafe {
367 private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives))
368 .map(|ptr| ptr as *const T)
369 }
370}
371
372#[allow(clippy::result_unit_err)]
383pub unsafe fn root_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<DomRoot<T>, ()>
384where
385 T: DomObject + IDLInterface,
386{
387 native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
388}
389
390#[allow(clippy::result_unit_err)]
396pub fn root_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<DomRoot<T>, ()>
397where
398 T: DomObject + IDLInterface,
399{
400 if !v.get().is_object() {
401 return Err(());
402 }
403 #[allow(unsafe_code)]
404 unsafe {
405 root_from_object(v.get().to_object(), *cx)
406 }
407}
408
409pub unsafe fn jsid_to_string(cx: *mut JSContext, id: HandleId) -> Option<DOMString> {
417 let id_raw = *id;
418 if id_raw.is_string() {
419 let jsstr = std::ptr::NonNull::new(id_raw.to_string()).unwrap();
420 return Some(DOMString::from_string(jsstr_to_string(cx, jsstr)));
421 }
422
423 if id_raw.is_int() {
424 return Some(id_raw.to_int().to_string().into());
425 }
426
427 None
428}
429
430impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
431 #[inline]
432 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
433 let value = **self;
434 value.to_jsval(cx, rval);
435 }
436}
437
438impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
439 type Config = ();
440
441 unsafe fn from_jsval(
442 cx: *mut JSContext,
443 value: HandleValue,
444 option: (),
445 ) -> Result<ConversionResult<Finite<T>>, ()> {
446 let result = match FromJSValConvertible::from_jsval(cx, value, option)? {
447 ConversionResult::Success(v) => v,
448 ConversionResult::Failure(error) => {
449 throw_type_error(cx, &error);
451 return Err(());
452 },
453 };
454 match Finite::new(result) {
455 Some(v) => Ok(ConversionResult::Success(v)),
456 None => {
457 throw_type_error(cx, "this argument is not a finite floating-point value");
458 Err(())
459 },
460 }
461 }
462}
463
464#[inline]
470#[allow(clippy::result_unit_err)]
471unsafe fn private_from_proto_check_static(
472 obj: *mut JSObject,
473 proto_check: fn(&'static DOMClass) -> bool,
474) -> Result<*const libc::c_void, ()> {
475 let dom_class = get_dom_class(obj).map_err(|_| ())?;
476 if proto_check(dom_class) {
477 trace!("good prototype");
478 Ok(private_from_object(obj))
479 } else {
480 trace!("bad prototype");
481 Err(())
482 }
483}
484
485#[allow(clippy::result_unit_err)]
491pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
492where
493 T: DomObject + IDLInterface,
494{
495 private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T)
496}
497
498#[allow(clippy::result_unit_err)]
504pub fn native_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<*const T, ()>
505where
506 T: DomObject + IDLInterface,
507{
508 if !v.get().is_object() {
509 return Err(());
510 }
511
512 #[allow(unsafe_code)]
513 unsafe {
514 native_from_object(v.get().to_object(), *cx)
515 }
516}
517
518impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
519 #[inline]
520 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
521 let value = &**self;
522 value.to_jsval(cx, rval);
523 }
524}
525
526impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
527where
528 T: FromJSValConvertible + js::rust::GCMethods + Copy,
529 Heap<T>: JSTraceable + Default,
530{
531 type Config = T::Config;
532
533 unsafe fn from_jsval(
534 cx: *mut JSContext,
535 value: HandleValue,
536 config: Self::Config,
537 ) -> Result<ConversionResult<Self>, ()> {
538 T::from_jsval(cx, value, config).map(|result| match result {
539 ConversionResult::Success(inner) => {
540 ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
541 },
542 ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
543 })
544 }
545}
546
547pub unsafe fn is_array_like<D: crate::DomTypes>(cx: *mut JSContext, value: HandleValue) -> bool {
554 let mut is_array = false;
555 assert!(IsArrayObject(cx, value, &mut is_array));
556 if is_array {
557 return true;
558 }
559
560 let object: *mut JSObject = match FromJSValConvertible::from_jsval(cx, value, ()).unwrap() {
561 ConversionResult::Success(object) => object,
562 _ => return false,
563 };
564
565 if root_from_object::<D::DOMTokenList>(object, cx).is_ok() {
567 return true;
568 }
569 if root_from_object::<D::FileList>(object, cx).is_ok() {
570 return true;
571 }
572 if root_from_object::<D::HTMLCollection>(object, cx).is_ok() {
573 return true;
574 }
575 if root_from_object::<D::HTMLFormControlsCollection>(object, cx).is_ok() {
576 return true;
577 }
578 if root_from_object::<D::HTMLOptionsCollection>(object, cx).is_ok() {
579 return true;
580 }
581 if root_from_object::<D::NodeList>(object, cx).is_ok() {
582 return true;
583 }
584
585 false
586}
587
588pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
591 v: HandleValue,
592 _cx: SafeJSContext,
593) -> Result<DomRoot<D::WindowProxy>, ()> {
594 if !v.get().is_object() {
595 return Err(());
596 }
597 let object = v.get().to_object();
598 if !IsWindowProxy(object) {
599 return Err(());
600 }
601 let mut value = UndefinedValue();
602 GetProxyReservedSlot(object, 0, &mut value);
603 let ptr = value.to_private() as *const D::WindowProxy;
604 Ok(DomRoot::from_ref(&*ptr))
605}
606
607#[allow(deprecated)]
608impl<D: crate::DomTypes> EventModifierInit<D> {
609 pub fn modifiers(&self) -> Modifiers {
610 let mut modifiers = Modifiers::empty();
611 if self.altKey {
612 modifiers.insert(Modifiers::ALT);
613 }
614 if self.ctrlKey {
615 modifiers.insert(Modifiers::CONTROL);
616 }
617 if self.shiftKey {
618 modifiers.insert(Modifiers::SHIFT);
619 }
620 if self.metaKey {
621 modifiers.insert(Modifiers::META);
622 }
623 if self.keyModifierStateAltGraph {
624 modifiers.insert(Modifiers::ALT_GRAPH);
625 }
626 if self.keyModifierStateCapsLock {
627 modifiers.insert(Modifiers::CAPS_LOCK);
628 }
629 if self.keyModifierStateFn {
630 modifiers.insert(Modifiers::FN);
631 }
632 if self.keyModifierStateFnLock {
633 modifiers.insert(Modifiers::FN_LOCK);
634 }
635 if self.keyModifierStateHyper {
636 modifiers.insert(Modifiers::HYPER);
637 }
638 if self.keyModifierStateNumLock {
639 modifiers.insert(Modifiers::NUM_LOCK);
640 }
641 if self.keyModifierStateScrollLock {
642 modifiers.insert(Modifiers::SCROLL_LOCK);
643 }
644 if self.keyModifierStateSuper {
645 modifiers.insert(Modifiers::SUPER);
646 }
647 if self.keyModifierStateSymbol {
648 modifiers.insert(Modifiers::SYMBOL);
649 }
650 if self.keyModifierStateSymbolLock {
651 modifiers.insert(Modifiers::SYMBOL_LOCK);
652 }
653 modifiers
654 }
655}