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::{CanGc, 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, can_gc: CanGc);
42}
43
44impl<T: ToJSValConvertible + ?Sized> SafeToJSValConvertible for T {
45 fn safe_to_jsval(&self, cx: SafeJSContext, rval: MutableHandleValue, _can_gc: CanGc) {
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 DOMString::from_js_string(unsafe { SafeJSContext::from_ptr(cx) }, value) {
111 Ok(domstring) => Ok(ConversionResult::Success(domstring)),
112 Err(_) => Err(()),
113 }
114 }
115 }
116}
117
118impl FromJSValConvertible for USVString {
120 type Config = ();
121 unsafe fn from_jsval(
122 cx: *mut JSContext,
123 value: HandleValue,
124 _: (),
125 ) -> Result<ConversionResult<USVString>, ()> {
126 let Some(jsstr) = ptr::NonNull::new(ToString(cx, value)) else {
127 debug!("ToString failed");
128 return Err(());
129 };
130 let latin1 = JS_DeprecatedStringHasLatin1Chars(jsstr.as_ptr());
131 if latin1 {
132 return Ok(ConversionResult::Success(USVString(jsstr_to_string(
134 cx, jsstr,
135 ))));
136 }
137 let mut length = 0;
138 let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr.as_ptr(), &mut length);
139 assert!(!chars.is_null());
140 let char_vec = slice::from_raw_parts(chars, length);
141 Ok(ConversionResult::Success(USVString(
142 String::from_utf16_lossy(char_vec),
143 )))
144 }
145}
146
147impl ToJSValConvertible for ByteString {
149 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
150 let jsstr = JS_NewStringCopyN(
151 cx,
152 self.as_ptr() as *const libc::c_char,
153 self.len() as libc::size_t,
154 );
155 if jsstr.is_null() {
156 panic!("JS_NewStringCopyN failed");
157 }
158 rval.set(StringValue(&*jsstr));
159 }
160}
161
162impl FromJSValConvertible for ByteString {
164 type Config = ();
165 unsafe fn from_jsval(
166 cx: *mut JSContext,
167 value: HandleValue,
168 _option: (),
169 ) -> Result<ConversionResult<ByteString>, ()> {
170 let string = ToString(cx, value);
171 if string.is_null() {
172 debug!("ToString failed");
173 return Err(());
174 }
175
176 let latin1 = JS_DeprecatedStringHasLatin1Chars(string);
177 if latin1 {
178 let mut length = 0;
179 let chars = JS_GetLatin1StringCharsAndLength(cx, ptr::null(), string, &mut length);
180 assert!(!chars.is_null());
181
182 let char_slice = slice::from_raw_parts(chars as *mut u8, length);
183 return Ok(ConversionResult::Success(ByteString::new(
184 char_slice.to_vec(),
185 )));
186 }
187
188 let mut length = 0;
189 let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), string, &mut length);
190 let char_vec = slice::from_raw_parts(chars, length);
191
192 if char_vec.iter().any(|&c| c > 0xFF) {
193 throw_type_error(cx, "Invalid ByteString");
194 Err(())
195 } else {
196 Ok(ConversionResult::Success(ByteString::new(
197 char_vec.iter().map(|&c| c as u8).collect(),
198 )))
199 }
200 }
201}
202
203impl ToJSValConvertible for Reflector {
204 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
205 let obj = self.get_jsobject().get();
206 assert!(!obj.is_null());
207 rval.set(ObjectValue(obj));
208 maybe_wrap_value(cx, rval);
209 }
210}
211
212impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> {
213 type Config = ();
214
215 unsafe fn from_jsval(
216 cx: *mut JSContext,
217 value: HandleValue,
218 _config: Self::Config,
219 ) -> Result<ConversionResult<DomRoot<T>>, ()> {
220 Ok(
221 match root_from_handlevalue(value, SafeJSContext::from_ptr(cx)) {
222 Ok(result) => ConversionResult::Success(result),
223 Err(()) => ConversionResult::Failure("value is not an object".into()),
224 },
225 )
226 }
227}
228
229impl<T: DomObject> ToJSValConvertible for DomRoot<T> {
230 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
231 self.reflector().to_jsval(cx, rval);
232 }
233}
234
235#[allow(clippy::result_unit_err)]
240pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> {
241 let clasp = get_object_class(obj);
242 if is_dom_class(&*clasp) {
243 trace!("plain old dom object");
244 let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
245 return Ok(&(*domjsclass).dom_class);
246 }
247 if is_dom_proxy(obj) {
248 trace!("proxy dom object");
249 let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
250 if dom_class.is_null() {
251 return Err(());
252 }
253 return Ok(&*dom_class);
254 }
255 trace!("not a dom object");
256 Err(())
257}
258
259pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool {
264 unsafe {
265 let clasp = get_object_class(obj);
266 ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj)
267 }
268}
269
270pub const DOM_OBJECT_SLOT: u32 = 0;
275
276pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
281 let mut value = UndefinedValue();
282 if is_dom_object(obj) {
283 JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
284 } else {
285 debug_assert!(is_dom_proxy(obj));
286 GetProxyReservedSlot(obj, 0, &mut value);
287 };
288 if value.is_undefined() {
289 ptr::null()
290 } else {
291 value.to_private()
292 }
293}
294
295pub enum PrototypeCheck {
296 Derive(fn(&'static DOMClass) -> bool),
297 Depth { depth: usize, proto_id: u16 },
298}
299
300#[inline]
311#[allow(clippy::result_unit_err)]
312pub unsafe fn private_from_proto_check(
313 mut obj: *mut JSObject,
314 cx: *mut JSContext,
315 proto_check: PrototypeCheck,
316) -> Result<*const libc::c_void, ()> {
317 let dom_class = get_dom_class(obj).or_else(|_| {
318 if IsWrapper(obj) {
319 trace!("found wrapper");
320 obj = UnwrapObjectDynamic(obj, cx, false);
321 if obj.is_null() {
322 trace!("unwrapping security wrapper failed");
323 Err(())
324 } else {
325 assert!(!IsWrapper(obj));
326 trace!("unwrapped successfully");
327 get_dom_class(obj)
328 }
329 } else {
330 trace!("not a dom wrapper");
331 Err(())
332 }
333 })?;
334
335 let prototype_matches = match proto_check {
336 PrototypeCheck::Derive(f) => (f)(dom_class),
337 PrototypeCheck::Depth { depth, proto_id } => {
338 dom_class.interface_chain[depth] as u16 == proto_id
339 },
340 };
341
342 if prototype_matches {
343 trace!("good prototype");
344 Ok(private_from_object(obj))
345 } else {
346 trace!("bad prototype");
347 Err(())
348 }
349}
350
351#[allow(clippy::result_unit_err)]
357pub unsafe fn native_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()>
358where
359 T: DomObject + IDLInterface,
360{
361 unsafe {
362 private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives))
363 .map(|ptr| ptr as *const T)
364 }
365}
366
367#[allow(clippy::result_unit_err)]
378pub unsafe fn root_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<DomRoot<T>, ()>
379where
380 T: DomObject + IDLInterface,
381{
382 native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
383}
384
385#[allow(clippy::result_unit_err)]
391pub fn root_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<DomRoot<T>, ()>
392where
393 T: DomObject + IDLInterface,
394{
395 if !v.get().is_object() {
396 return Err(());
397 }
398 #[allow(unsafe_code)]
399 unsafe {
400 root_from_object(v.get().to_object(), *cx)
401 }
402}
403
404pub unsafe fn jsid_to_string(cx: *mut JSContext, id: HandleId) -> Option<DOMString> {
412 let id_raw = *id;
413 if id_raw.is_string() {
414 let jsstr = std::ptr::NonNull::new(id_raw.to_string()).unwrap();
415 return Some(DOMString::from_string(jsstr_to_string(cx, jsstr)));
416 }
417
418 if id_raw.is_int() {
419 return Some(id_raw.to_int().to_string().into());
420 }
421
422 None
423}
424
425impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
426 #[inline]
427 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
428 let value = **self;
429 value.to_jsval(cx, rval);
430 }
431}
432
433impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
434 type Config = ();
435
436 unsafe fn from_jsval(
437 cx: *mut JSContext,
438 value: HandleValue,
439 option: (),
440 ) -> Result<ConversionResult<Finite<T>>, ()> {
441 let result = match FromJSValConvertible::from_jsval(cx, value, option)? {
442 ConversionResult::Success(v) => v,
443 ConversionResult::Failure(error) => {
444 throw_type_error(cx, &error);
446 return Err(());
447 },
448 };
449 match Finite::new(result) {
450 Some(v) => Ok(ConversionResult::Success(v)),
451 None => {
452 throw_type_error(cx, "this argument is not a finite floating-point value");
453 Err(())
454 },
455 }
456 }
457}
458
459#[inline]
465#[allow(clippy::result_unit_err)]
466unsafe fn private_from_proto_check_static(
467 obj: *mut JSObject,
468 proto_check: fn(&'static DOMClass) -> bool,
469) -> Result<*const libc::c_void, ()> {
470 let dom_class = get_dom_class(obj).map_err(|_| ())?;
471 if proto_check(dom_class) {
472 trace!("good prototype");
473 Ok(private_from_object(obj))
474 } else {
475 trace!("bad prototype");
476 Err(())
477 }
478}
479
480#[allow(clippy::result_unit_err)]
486pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
487where
488 T: DomObject + IDLInterface,
489{
490 private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T)
491}
492
493#[allow(clippy::result_unit_err)]
499pub fn native_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<*const T, ()>
500where
501 T: DomObject + IDLInterface,
502{
503 if !v.get().is_object() {
504 return Err(());
505 }
506
507 #[allow(unsafe_code)]
508 unsafe {
509 native_from_object(v.get().to_object(), *cx)
510 }
511}
512
513impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
514 #[inline]
515 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
516 let value = &**self;
517 value.to_jsval(cx, rval);
518 }
519}
520
521impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
522where
523 T: FromJSValConvertible + js::rust::GCMethods + Copy,
524 Heap<T>: JSTraceable + Default,
525{
526 type Config = T::Config;
527
528 unsafe fn from_jsval(
529 cx: *mut JSContext,
530 value: HandleValue,
531 config: Self::Config,
532 ) -> Result<ConversionResult<Self>, ()> {
533 T::from_jsval(cx, value, config).map(|result| match result {
534 ConversionResult::Success(inner) => {
535 ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
536 },
537 ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
538 })
539 }
540}
541
542pub unsafe fn is_array_like<D: crate::DomTypes>(cx: *mut JSContext, value: HandleValue) -> bool {
549 let mut is_array = false;
550 assert!(IsArrayObject(cx, value, &mut is_array));
551 if is_array {
552 return true;
553 }
554
555 let object: *mut JSObject = match FromJSValConvertible::from_jsval(cx, value, ()).unwrap() {
556 ConversionResult::Success(object) => object,
557 _ => return false,
558 };
559
560 if root_from_object::<D::DOMTokenList>(object, cx).is_ok() {
562 return true;
563 }
564 if root_from_object::<D::FileList>(object, cx).is_ok() {
565 return true;
566 }
567 if root_from_object::<D::HTMLCollection>(object, cx).is_ok() {
568 return true;
569 }
570 if root_from_object::<D::HTMLFormControlsCollection>(object, cx).is_ok() {
571 return true;
572 }
573 if root_from_object::<D::HTMLOptionsCollection>(object, cx).is_ok() {
574 return true;
575 }
576 if root_from_object::<D::NodeList>(object, cx).is_ok() {
577 return true;
578 }
579
580 false
581}
582
583pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
586 v: HandleValue,
587 _cx: SafeJSContext,
588) -> Result<DomRoot<D::WindowProxy>, ()> {
589 if !v.get().is_object() {
590 return Err(());
591 }
592 let object = v.get().to_object();
593 if !IsWindowProxy(object) {
594 return Err(());
595 }
596 let mut value = UndefinedValue();
597 GetProxyReservedSlot(object, 0, &mut value);
598 let ptr = value.to_private() as *const D::WindowProxy;
599 Ok(DomRoot::from_ref(&*ptr))
600}
601
602#[allow(deprecated)]
603impl<D: crate::DomTypes> EventModifierInit<D> {
604 pub fn modifiers(&self) -> Modifiers {
605 let mut modifiers = Modifiers::empty();
606 if self.altKey {
607 modifiers.insert(Modifiers::ALT);
608 }
609 if self.ctrlKey {
610 modifiers.insert(Modifiers::CONTROL);
611 }
612 if self.shiftKey {
613 modifiers.insert(Modifiers::SHIFT);
614 }
615 if self.metaKey {
616 modifiers.insert(Modifiers::META);
617 }
618 if self.keyModifierStateAltGraph {
619 modifiers.insert(Modifiers::ALT_GRAPH);
620 }
621 if self.keyModifierStateCapsLock {
622 modifiers.insert(Modifiers::CAPS_LOCK);
623 }
624 if self.keyModifierStateFn {
625 modifiers.insert(Modifiers::FN);
626 }
627 if self.keyModifierStateFnLock {
628 modifiers.insert(Modifiers::FN_LOCK);
629 }
630 if self.keyModifierStateHyper {
631 modifiers.insert(Modifiers::HYPER);
632 }
633 if self.keyModifierStateNumLock {
634 modifiers.insert(Modifiers::NUM_LOCK);
635 }
636 if self.keyModifierStateScrollLock {
637 modifiers.insert(Modifiers::SCROLL_LOCK);
638 }
639 if self.keyModifierStateSuper {
640 modifiers.insert(Modifiers::SUPER);
641 }
642 if self.keyModifierStateSymbol {
643 modifiers.insert(Modifiers::SYMBOL);
644 }
645 if self.keyModifierStateSymbolLock {
646 modifiers.insert(Modifiers::SYMBOL_LOCK);
647 }
648 modifiers
649 }
650}