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
75impl ToJSValConvertible for DOMString {
77 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
78 (**self).to_jsval(cx, rval);
79 }
80}
81
82pub trait SafeFromJSValConvertible: Sized {
84 type Config;
85
86 #[allow(clippy::result_unit_err)] fn safe_from_jsval(
88 cx: SafeJSContext,
89 value: HandleValue,
90 option: Self::Config,
91 ) -> Result<ConversionResult<Self>, ()>;
92}
93
94impl<T: FromJSValConvertible> SafeFromJSValConvertible for T {
95 type Config = <T as FromJSValConvertible>::Config;
96
97 fn safe_from_jsval(
98 cx: SafeJSContext,
99 value: HandleValue,
100 option: Self::Config,
101 ) -> Result<ConversionResult<Self>, ()> {
102 unsafe { T::from_jsval(*cx, value, option) }
103 }
104}
105
106impl FromJSValConvertible for DOMString {
108 type Config = StringificationBehavior;
109 unsafe fn from_jsval(
110 cx: *mut JSContext,
111 value: HandleValue,
112 null_behavior: StringificationBehavior,
113 ) -> Result<ConversionResult<DOMString>, ()> {
114 if null_behavior == StringificationBehavior::Empty && value.get().is_null() {
115 Ok(ConversionResult::Success(DOMString::new()))
116 } else {
117 match ptr::NonNull::new(ToString(cx, value)) {
118 Some(jsstr) => Ok(ConversionResult::Success(DOMString::from_string(
119 jsstr_to_string(cx, jsstr),
120 ))),
121 None => {
122 debug!("ToString failed");
123 Err(())
124 },
125 }
126 }
127 }
128}
129
130impl FromJSValConvertible for USVString {
132 type Config = ();
133 unsafe fn from_jsval(
134 cx: *mut JSContext,
135 value: HandleValue,
136 _: (),
137 ) -> Result<ConversionResult<USVString>, ()> {
138 let Some(jsstr) = ptr::NonNull::new(ToString(cx, value)) else {
139 debug!("ToString failed");
140 return Err(());
141 };
142 let latin1 = JS_DeprecatedStringHasLatin1Chars(jsstr.as_ptr());
143 if latin1 {
144 return Ok(ConversionResult::Success(USVString(jsstr_to_string(
146 cx, jsstr,
147 ))));
148 }
149 let mut length = 0;
150 let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr.as_ptr(), &mut length);
151 assert!(!chars.is_null());
152 let char_vec = slice::from_raw_parts(chars, length);
153 Ok(ConversionResult::Success(USVString(
154 String::from_utf16_lossy(char_vec),
155 )))
156 }
157}
158
159impl ToJSValConvertible for ByteString {
161 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
162 let jsstr = JS_NewStringCopyN(
163 cx,
164 self.as_ptr() as *const libc::c_char,
165 self.len() as libc::size_t,
166 );
167 if jsstr.is_null() {
168 panic!("JS_NewStringCopyN failed");
169 }
170 rval.set(StringValue(&*jsstr));
171 }
172}
173
174impl FromJSValConvertible for ByteString {
176 type Config = ();
177 unsafe fn from_jsval(
178 cx: *mut JSContext,
179 value: HandleValue,
180 _option: (),
181 ) -> Result<ConversionResult<ByteString>, ()> {
182 let string = ToString(cx, value);
183 if string.is_null() {
184 debug!("ToString failed");
185 return Err(());
186 }
187
188 let latin1 = JS_DeprecatedStringHasLatin1Chars(string);
189 if latin1 {
190 let mut length = 0;
191 let chars = JS_GetLatin1StringCharsAndLength(cx, ptr::null(), string, &mut length);
192 assert!(!chars.is_null());
193
194 let char_slice = slice::from_raw_parts(chars as *mut u8, length);
195 return Ok(ConversionResult::Success(ByteString::new(
196 char_slice.to_vec(),
197 )));
198 }
199
200 let mut length = 0;
201 let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), string, &mut length);
202 let char_vec = slice::from_raw_parts(chars, length);
203
204 if char_vec.iter().any(|&c| c > 0xFF) {
205 throw_type_error(cx, "Invalid ByteString");
206 Err(())
207 } else {
208 Ok(ConversionResult::Success(ByteString::new(
209 char_vec.iter().map(|&c| c as u8).collect(),
210 )))
211 }
212 }
213}
214
215impl ToJSValConvertible for Reflector {
216 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
217 let obj = self.get_jsobject().get();
218 assert!(!obj.is_null());
219 rval.set(ObjectValue(obj));
220 maybe_wrap_value(cx, rval);
221 }
222}
223
224impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> {
225 type Config = ();
226
227 unsafe fn from_jsval(
228 cx: *mut JSContext,
229 value: HandleValue,
230 _config: Self::Config,
231 ) -> Result<ConversionResult<DomRoot<T>>, ()> {
232 Ok(
233 match root_from_handlevalue(value, SafeJSContext::from_ptr(cx)) {
234 Ok(result) => ConversionResult::Success(result),
235 Err(()) => ConversionResult::Failure("value is not an object".into()),
236 },
237 )
238 }
239}
240
241impl<T: DomObject> ToJSValConvertible for DomRoot<T> {
242 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
243 self.reflector().to_jsval(cx, rval);
244 }
245}
246
247#[allow(clippy::result_unit_err)]
252pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> {
253 let clasp = get_object_class(obj);
254 if is_dom_class(&*clasp) {
255 trace!("plain old dom object");
256 let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
257 return Ok(&(*domjsclass).dom_class);
258 }
259 if is_dom_proxy(obj) {
260 trace!("proxy dom object");
261 let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
262 if dom_class.is_null() {
263 return Err(());
264 }
265 return Ok(&*dom_class);
266 }
267 trace!("not a dom object");
268 Err(())
269}
270
271pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool {
276 unsafe {
277 let clasp = get_object_class(obj);
278 ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj)
279 }
280}
281
282pub const DOM_OBJECT_SLOT: u32 = 0;
287
288pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
293 let mut value = UndefinedValue();
294 if is_dom_object(obj) {
295 JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
296 } else {
297 debug_assert!(is_dom_proxy(obj));
298 GetProxyReservedSlot(obj, 0, &mut value);
299 };
300 if value.is_undefined() {
301 ptr::null()
302 } else {
303 value.to_private()
304 }
305}
306
307pub enum PrototypeCheck {
308 Derive(fn(&'static DOMClass) -> bool),
309 Depth { depth: usize, proto_id: u16 },
310}
311
312#[inline]
323#[allow(clippy::result_unit_err)]
324pub unsafe fn private_from_proto_check(
325 mut obj: *mut JSObject,
326 cx: *mut JSContext,
327 proto_check: PrototypeCheck,
328) -> Result<*const libc::c_void, ()> {
329 let dom_class = get_dom_class(obj).or_else(|_| {
330 if IsWrapper(obj) {
331 trace!("found wrapper");
332 obj = UnwrapObjectDynamic(obj, cx, false);
333 if obj.is_null() {
334 trace!("unwrapping security wrapper failed");
335 Err(())
336 } else {
337 assert!(!IsWrapper(obj));
338 trace!("unwrapped successfully");
339 get_dom_class(obj)
340 }
341 } else {
342 trace!("not a dom wrapper");
343 Err(())
344 }
345 })?;
346
347 let prototype_matches = match proto_check {
348 PrototypeCheck::Derive(f) => (f)(dom_class),
349 PrototypeCheck::Depth { depth, proto_id } => {
350 dom_class.interface_chain[depth] as u16 == proto_id
351 },
352 };
353
354 if prototype_matches {
355 trace!("good prototype");
356 Ok(private_from_object(obj))
357 } else {
358 trace!("bad prototype");
359 Err(())
360 }
361}
362
363#[allow(clippy::result_unit_err)]
369pub unsafe fn native_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()>
370where
371 T: DomObject + IDLInterface,
372{
373 unsafe {
374 private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives))
375 .map(|ptr| ptr as *const T)
376 }
377}
378
379#[allow(clippy::result_unit_err)]
390pub unsafe fn root_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<DomRoot<T>, ()>
391where
392 T: DomObject + IDLInterface,
393{
394 native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
395}
396
397#[allow(clippy::result_unit_err)]
403pub fn root_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<DomRoot<T>, ()>
404where
405 T: DomObject + IDLInterface,
406{
407 if !v.get().is_object() {
408 return Err(());
409 }
410 #[allow(unsafe_code)]
411 unsafe {
412 root_from_object(v.get().to_object(), *cx)
413 }
414}
415
416pub unsafe fn jsid_to_string(cx: *mut JSContext, id: HandleId) -> Option<DOMString> {
424 let id_raw = *id;
425 if id_raw.is_string() {
426 let jsstr = std::ptr::NonNull::new(id_raw.to_string()).unwrap();
427 return Some(DOMString::from_string(jsstr_to_string(cx, jsstr)));
428 }
429
430 if id_raw.is_int() {
431 return Some(id_raw.to_int().to_string().into());
432 }
433
434 None
435}
436
437impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
438 #[inline]
439 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
440 let value = **self;
441 value.to_jsval(cx, rval);
442 }
443}
444
445impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
446 type Config = ();
447
448 unsafe fn from_jsval(
449 cx: *mut JSContext,
450 value: HandleValue,
451 option: (),
452 ) -> Result<ConversionResult<Finite<T>>, ()> {
453 let result = match FromJSValConvertible::from_jsval(cx, value, option)? {
454 ConversionResult::Success(v) => v,
455 ConversionResult::Failure(error) => {
456 throw_type_error(cx, &error);
458 return Err(());
459 },
460 };
461 match Finite::new(result) {
462 Some(v) => Ok(ConversionResult::Success(v)),
463 None => {
464 throw_type_error(cx, "this argument is not a finite floating-point value");
465 Err(())
466 },
467 }
468 }
469}
470
471#[inline]
477#[allow(clippy::result_unit_err)]
478unsafe fn private_from_proto_check_static(
479 obj: *mut JSObject,
480 proto_check: fn(&'static DOMClass) -> bool,
481) -> Result<*const libc::c_void, ()> {
482 let dom_class = get_dom_class(obj).map_err(|_| ())?;
483 if proto_check(dom_class) {
484 trace!("good prototype");
485 Ok(private_from_object(obj))
486 } else {
487 trace!("bad prototype");
488 Err(())
489 }
490}
491
492#[allow(clippy::result_unit_err)]
498pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
499where
500 T: DomObject + IDLInterface,
501{
502 private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T)
503}
504
505#[allow(clippy::result_unit_err)]
511pub fn native_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<*const T, ()>
512where
513 T: DomObject + IDLInterface,
514{
515 if !v.get().is_object() {
516 return Err(());
517 }
518
519 #[allow(unsafe_code)]
520 unsafe {
521 native_from_object(v.get().to_object(), *cx)
522 }
523}
524
525impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
526 #[inline]
527 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
528 let value = &**self;
529 value.to_jsval(cx, rval);
530 }
531}
532
533impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
534where
535 T: FromJSValConvertible + js::rust::GCMethods + Copy,
536 Heap<T>: JSTraceable + Default,
537{
538 type Config = T::Config;
539
540 unsafe fn from_jsval(
541 cx: *mut JSContext,
542 value: HandleValue,
543 config: Self::Config,
544 ) -> Result<ConversionResult<Self>, ()> {
545 T::from_jsval(cx, value, config).map(|result| match result {
546 ConversionResult::Success(inner) => {
547 ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
548 },
549 ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
550 })
551 }
552}
553
554pub unsafe fn is_array_like<D: crate::DomTypes>(cx: *mut JSContext, value: HandleValue) -> bool {
561 let mut is_array = false;
562 assert!(IsArrayObject(cx, value, &mut is_array));
563 if is_array {
564 return true;
565 }
566
567 let object: *mut JSObject = match FromJSValConvertible::from_jsval(cx, value, ()).unwrap() {
568 ConversionResult::Success(object) => object,
569 _ => return false,
570 };
571
572 if root_from_object::<D::DOMTokenList>(object, cx).is_ok() {
574 return true;
575 }
576 if root_from_object::<D::FileList>(object, cx).is_ok() {
577 return true;
578 }
579 if root_from_object::<D::HTMLCollection>(object, cx).is_ok() {
580 return true;
581 }
582 if root_from_object::<D::HTMLFormControlsCollection>(object, cx).is_ok() {
583 return true;
584 }
585 if root_from_object::<D::HTMLOptionsCollection>(object, cx).is_ok() {
586 return true;
587 }
588 if root_from_object::<D::NodeList>(object, cx).is_ok() {
589 return true;
590 }
591
592 false
593}
594
595pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
598 v: HandleValue,
599 _cx: SafeJSContext,
600) -> Result<DomRoot<D::WindowProxy>, ()> {
601 if !v.get().is_object() {
602 return Err(());
603 }
604 let object = v.get().to_object();
605 if !IsWindowProxy(object) {
606 return Err(());
607 }
608 let mut value = UndefinedValue();
609 GetProxyReservedSlot(object, 0, &mut value);
610 let ptr = value.to_private() as *const D::WindowProxy;
611 Ok(DomRoot::from_ref(&*ptr))
612}
613
614#[allow(deprecated)]
615impl<D: crate::DomTypes> EventModifierInit<D> {
616 pub fn modifiers(&self) -> Modifiers {
617 let mut modifiers = Modifiers::empty();
618 if self.altKey {
619 modifiers.insert(Modifiers::ALT);
620 }
621 if self.ctrlKey {
622 modifiers.insert(Modifiers::CONTROL);
623 }
624 if self.shiftKey {
625 modifiers.insert(Modifiers::SHIFT);
626 }
627 if self.metaKey {
628 modifiers.insert(Modifiers::META);
629 }
630 if self.keyModifierStateAltGraph {
631 modifiers.insert(Modifiers::ALT_GRAPH);
632 }
633 if self.keyModifierStateCapsLock {
634 modifiers.insert(Modifiers::CAPS_LOCK);
635 }
636 if self.keyModifierStateFn {
637 modifiers.insert(Modifiers::FN);
638 }
639 if self.keyModifierStateFnLock {
640 modifiers.insert(Modifiers::FN_LOCK);
641 }
642 if self.keyModifierStateHyper {
643 modifiers.insert(Modifiers::HYPER);
644 }
645 if self.keyModifierStateNumLock {
646 modifiers.insert(Modifiers::NUM_LOCK);
647 }
648 if self.keyModifierStateScrollLock {
649 modifiers.insert(Modifiers::SCROLL_LOCK);
650 }
651 if self.keyModifierStateSuper {
652 modifiers.insert(Modifiers::SUPER);
653 }
654 if self.keyModifierStateSymbol {
655 modifiers.insert(Modifiers::SYMBOL);
656 }
657 if self.keyModifierStateSymbolLock {
658 modifiers.insert(Modifiers::SYMBOL_LOCK);
659 }
660 modifiers
661 }
662}