1use std::{ffi, ptr, slice};
6
7use js::context::JSContext;
8use js::conversions::{
9 ConversionResult, FromJSValConvertible, ToJSValConvertible, jsstr_to_string,
10};
11use js::error::throw_type_error;
12use js::glue::{
13 GetProxyHandlerExtra, GetProxyReservedSlot, IsProxyHandlerFamily, IsWrapper, JS_GetReservedSlot,
14};
15use js::jsapi::{Heap, IsWindowProxy, JS_DeprecatedStringHasLatin1Chars, JSObject};
16use js::jsval::{ObjectValue, StringValue, UndefinedValue};
17use js::rust::wrappers2::{
18 IsArrayObject, JS_GetLatin1StringCharsAndLength, JS_GetProperty,
19 JS_GetTwoByteStringCharsAndLength, JS_NewStringCopyN, UnwrapObjectDynamic,
20};
21use js::rust::{
22 HandleId, HandleObject, HandleValue, MutableHandleValue, ToString, get_object_class,
23 is_dom_class, 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::codegen::PrototypeList;
31use crate::error::{Error, Fallible};
32use crate::inheritance::Castable;
33use crate::num::Finite;
34use crate::reflector::{DomObject, Reflector};
35use crate::root::DomRoot;
36use crate::str::{ByteString, DOMString, USVString};
37use crate::trace::RootedTraceableBox;
38use crate::utils::{DOMClass, DOMJSClass};
39
40pub trait IDLInterface {
42 fn derives(_: &'static DOMClass) -> bool;
44
45 const PROTO_ID: PrototypeList::ID;
47
48 const PROTO_FIRST: u16 = 0;
50 const PROTO_LAST: u16 = u16::MAX;
52}
53
54pub trait DerivedFrom<T: Castable>: Castable {}
56
57impl ToJSValConvertible for USVString {
59 fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
60 self.0.to_jsval(cx, rval);
61 }
62}
63
64#[derive(Clone, PartialEq)]
66pub enum StringificationBehavior {
67 Default,
69 Empty,
71}
72
73impl FromJSValConvertible for DOMString {
75 type Config = StringificationBehavior;
76
77 fn from_jsval(
78 cx: &mut JSContext,
79 value: HandleValue,
80 null_behavior: StringificationBehavior,
81 ) -> Result<ConversionResult<DOMString>, ()> {
82 if null_behavior == StringificationBehavior::Empty && value.get().is_null() {
83 Ok(ConversionResult::Success(DOMString::new()))
84 } else {
85 match DOMString::from_js_string(cx, value) {
86 Ok(domstring) => Ok(ConversionResult::Success(domstring)),
87 Err(_) => Err(()),
88 }
89 }
90 }
91}
92
93impl FromJSValConvertible for USVString {
95 type Config = ();
96
97 fn from_jsval(
98 cx: &mut JSContext,
99 value: HandleValue,
100 _: (),
101 ) -> Result<ConversionResult<USVString>, ()> {
102 let Some(jsstr) = ptr::NonNull::new(unsafe { ToString(cx, value) }) else {
103 debug!("ToString failed");
104 return Err(());
105 };
106
107 Ok(ConversionResult::Success(USVString(unsafe {
109 jsstr_to_string(cx, jsstr)
110 })))
111 }
112}
113
114impl ToJSValConvertible for ByteString {
116 fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
117 let jsstr = unsafe {
118 JS_NewStringCopyN(
119 cx,
120 self.as_ptr() as *const libc::c_char,
121 self.len() as libc::size_t,
122 )
123 };
124 if jsstr.is_null() {
125 panic!("JS_NewStringCopyN failed");
126 }
127 unsafe { rval.set(StringValue(&*jsstr)) };
128 }
129}
130
131impl FromJSValConvertible for ByteString {
133 type Config = ();
134
135 fn from_jsval(
136 cx: &mut JSContext,
137 value: HandleValue,
138 _option: (),
139 ) -> Result<ConversionResult<ByteString>, ()> {
140 unsafe {
141 let string = ToString(cx, value);
142 if string.is_null() {
143 debug!("ToString failed");
144 return Err(());
145 }
146
147 let latin1 = JS_DeprecatedStringHasLatin1Chars(string);
148 if latin1 {
149 let mut length = 0;
150 let chars = JS_GetLatin1StringCharsAndLength(cx, string, &mut length);
151 assert!(!chars.is_null());
152
153 let char_slice = slice::from_raw_parts(chars as *mut u8, length);
154 return Ok(ConversionResult::Success(ByteString::new(
155 char_slice.to_vec(),
156 )));
157 }
158
159 let mut length = 0;
160 let chars = JS_GetTwoByteStringCharsAndLength(cx, string, &mut length);
161 let char_vec = slice::from_raw_parts(chars, length);
162
163 if char_vec.iter().any(|&c| c > 0xFF) {
164 throw_type_error(cx, c"Invalid ByteString");
165 Err(())
166 } else {
167 Ok(ConversionResult::Success(ByteString::new(
168 char_vec.iter().map(|&c| c as u8).collect(),
169 )))
170 }
171 }
172 }
173}
174
175impl<T> ToJSValConvertible for Reflector<T> {
176 fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
177 let obj = self.get_jsobject().get();
178 assert!(!obj.is_null());
179 rval.set(ObjectValue(obj));
180 maybe_wrap_value(cx, rval);
181 }
182}
183
184impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> {
185 type Config = ();
186
187 fn from_jsval(
188 cx: &mut JSContext,
189 value: HandleValue,
190 _config: Self::Config,
191 ) -> Result<ConversionResult<DomRoot<T>>, ()> {
192 Ok(match root_from_handlevalue(cx, value) {
193 Ok(result) => ConversionResult::Success(result),
194 Err(()) => ConversionResult::Failure(c"value is not an object".into()),
195 })
196 }
197}
198
199impl<T: DomObject> ToJSValConvertible for DomRoot<T> {
200 fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
201 self.reflector().to_jsval(cx, rval);
202 }
203}
204
205#[allow(clippy::result_unit_err)]
210pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> {
211 unsafe {
212 let clasp = get_object_class(obj);
213 if is_dom_class(&*clasp) {
214 trace!("plain old dom object");
215 let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
216 return Ok(&(*domjsclass).dom_class);
217 }
218 if is_dom_proxy(obj) {
219 trace!("proxy dom object");
220 let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
221 if dom_class.is_null() {
222 return Err(());
223 }
224 return Ok(&*dom_class);
225 }
226 }
227 trace!("not a dom object");
228 Err(())
229}
230
231pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool {
236 unsafe {
237 let clasp = get_object_class(obj);
238 ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj)
239 }
240}
241
242pub const DOM_OBJECT_SLOT: u32 = 0;
247
248pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
253 let mut value = UndefinedValue();
254 unsafe {
255 if is_dom_object(obj) {
256 JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
257 } else {
258 debug_assert!(is_dom_proxy(obj));
259 GetProxyReservedSlot(obj, 0, &mut value);
260 };
261 }
262 if value.is_undefined() {
263 ptr::null()
264 } else {
265 value.to_private()
266 }
267}
268
269pub enum PrototypeCheck {
270 Derive(fn(&'static DOMClass) -> bool),
271 Depth { depth: usize, proto_id: u16 },
272}
273
274#[inline]
285#[allow(clippy::result_unit_err)]
286pub unsafe fn private_from_proto_check(
287 cx: &mut JSContext,
288 mut obj: *mut JSObject,
289 proto_check: PrototypeCheck,
290) -> Result<*const libc::c_void, ()> {
291 let dom_class = unsafe {
292 get_dom_class(obj).or_else(|_| {
293 if IsWrapper(obj) {
294 trace!("found wrapper");
295 obj = UnwrapObjectDynamic(obj, cx, false);
296 if obj.is_null() {
297 trace!("unwrapping security wrapper failed");
298 Err(())
299 } else {
300 assert!(!IsWrapper(obj));
301 trace!("unwrapped successfully");
302 get_dom_class(obj)
303 }
304 } else {
305 trace!("not a dom wrapper");
306 Err(())
307 }
308 })?
309 };
310
311 let prototype_matches = match proto_check {
312 PrototypeCheck::Derive(f) => (f)(dom_class),
313 PrototypeCheck::Depth { depth, proto_id } => {
314 dom_class.interface_chain[depth] as u16 == proto_id
315 },
316 };
317
318 if prototype_matches {
319 trace!("good prototype");
320 Ok(unsafe { private_from_object(obj) })
321 } else {
322 trace!("bad prototype");
323 Err(())
324 }
325}
326
327#[allow(clippy::result_unit_err)]
333pub unsafe fn native_from_object<T>(cx: &mut JSContext, obj: *mut JSObject) -> Result<*const T, ()>
334where
335 T: DomObject + IDLInterface,
336{
337 unsafe {
338 private_from_proto_check(cx, obj, PrototypeCheck::Derive(T::derives))
339 .map(|ptr| ptr as *const T)
340 }
341}
342
343#[allow(clippy::result_unit_err)]
354pub unsafe fn root_from_object<T>(cx: &mut JSContext, obj: *mut JSObject) -> Result<DomRoot<T>, ()>
355where
356 T: DomObject + IDLInterface,
357{
358 unsafe { native_from_object(cx, obj).map(|ptr| DomRoot::from_ref(&*ptr)) }
359}
360
361#[allow(clippy::result_unit_err)]
367pub fn root_from_handlevalue<T>(cx: &mut JSContext, v: HandleValue) -> Result<DomRoot<T>, ()>
368where
369 T: DomObject + IDLInterface,
370{
371 if !v.get().is_object() {
372 return Err(());
373 }
374 #[expect(unsafe_code)]
375 unsafe {
376 root_from_object(cx, v.get().to_object())
377 }
378}
379
380pub fn jsid_to_string(cx: &js::context::JSContext, id: HandleId) -> Option<DOMString> {
385 let id_raw = *id;
386 if id_raw.is_string() {
387 let jsstr = id_raw.to_string();
388 return Some(unsafe {
389 DOMString::from_raw_js_string(cx, jsstr).expect(
391 "Should always be able to convert a property id that is a string to a DOMString",
392 )
393 });
394 }
395
396 if id_raw.is_int() {
397 return Some(id_raw.to_int().to_string().into());
398 }
399
400 None
401}
402
403impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
404 #[inline]
405 fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
406 let value = **self;
407 value.to_jsval(cx, rval);
408 }
409}
410
411impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
412 type Config = ();
413
414 fn from_jsval(
415 cx: &mut JSContext,
416 value: HandleValue,
417 option: (),
418 ) -> Result<ConversionResult<Finite<T>>, ()> {
419 let result = match FromJSValConvertible::from_jsval(cx, value, option)? {
420 ConversionResult::Success(v) => v,
421 ConversionResult::Failure(error) => {
422 throw_type_error(cx, &error);
424 return Err(());
425 },
426 };
427 match Finite::new(result) {
428 Some(v) => Ok(ConversionResult::Success(v)),
429 None => {
430 throw_type_error(cx, c"this argument is not a finite floating-point value");
431 Err(())
432 },
433 }
434 }
435}
436
437#[inline]
443#[allow(clippy::result_unit_err)]
444unsafe fn private_from_proto_check_static(
445 obj: *mut JSObject,
446 proto_check: fn(&'static DOMClass) -> bool,
447) -> Result<*const libc::c_void, ()> {
448 unsafe {
449 let dom_class = get_dom_class(obj).map_err(|_| ())?;
450 if proto_check(dom_class) {
451 trace!("good prototype");
452 Ok(private_from_object(obj))
453 } else {
454 trace!("bad prototype");
455 Err(())
456 }
457 }
458}
459
460#[allow(clippy::result_unit_err)]
466pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
467where
468 T: DomObject + IDLInterface,
469{
470 unsafe { private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T) }
471}
472
473#[allow(clippy::result_unit_err)]
479pub fn native_from_handlevalue<T>(cx: &mut JSContext, v: HandleValue) -> Result<*const T, ()>
480where
481 T: DomObject + IDLInterface,
482{
483 if !v.get().is_object() {
484 return Err(());
485 }
486
487 #[expect(unsafe_code)]
488 unsafe {
489 native_from_object(cx, v.get().to_object())
490 }
491}
492
493impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
494 #[inline]
495 fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
496 let value = &**self;
497 value.to_jsval(cx, rval);
498 }
499}
500
501impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
502where
503 T: FromJSValConvertible + js::rust::GCMethods + Copy,
504 Heap<T>: JSTraceable + Default,
505{
506 type Config = T::Config;
507
508 fn from_jsval(
509 cx: &mut JSContext,
510 value: HandleValue,
511 config: Self::Config,
512 ) -> Result<ConversionResult<Self>, ()> {
513 T::from_jsval(cx, value, config).map(|result| match result {
514 ConversionResult::Success(inner) => {
515 ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
516 },
517 ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
518 })
519 }
520}
521
522pub fn is_array_like<D: crate::DomTypes>(cx: &mut JSContext, value: HandleValue) -> bool {
526 let mut is_array = false;
527 assert!(unsafe { IsArrayObject(cx, value, &mut is_array) });
528 if is_array {
529 return true;
530 }
531
532 let object: *mut JSObject = match FromJSValConvertible::from_jsval(cx, value, ()).unwrap() {
533 ConversionResult::Success(object) => object,
534 _ => return false,
535 };
536
537 unsafe {
538 if root_from_object::<D::DOMTokenList>(cx, object).is_ok() {
540 return true;
541 }
542 if root_from_object::<D::FileList>(cx, object).is_ok() {
543 return true;
544 }
545 if root_from_object::<D::HTMLCollection>(cx, object).is_ok() {
546 return true;
547 }
548 if root_from_object::<D::HTMLFormControlsCollection>(cx, object).is_ok() {
549 return true;
550 }
551 if root_from_object::<D::HTMLOptionsCollection>(cx, object).is_ok() {
552 return true;
553 }
554 if root_from_object::<D::NodeList>(cx, object).is_ok() {
555 return true;
556 }
557 }
558
559 false
560}
561
562pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
565 v: HandleValue,
566) -> Result<DomRoot<D::WindowProxy>, ()> {
567 if !v.get().is_object() {
568 return Err(());
569 }
570 let object = v.get().to_object();
571 unsafe {
572 if !IsWindowProxy(object) {
573 return Err(());
574 }
575 let mut value = UndefinedValue();
576 GetProxyReservedSlot(object, 0, &mut value);
577 let ptr = value.to_private() as *const D::WindowProxy;
578 Ok(DomRoot::from_ref(&*ptr))
579 }
580}
581
582#[allow(deprecated)]
583impl<D: crate::DomTypes> EventModifierInit<D> {
584 pub fn modifiers(&self) -> Modifiers {
585 let mut modifiers = Modifiers::empty();
586 if self.altKey {
587 modifiers.insert(Modifiers::ALT);
588 }
589 if self.ctrlKey {
590 modifiers.insert(Modifiers::CONTROL);
591 }
592 if self.shiftKey {
593 modifiers.insert(Modifiers::SHIFT);
594 }
595 if self.metaKey {
596 modifiers.insert(Modifiers::META);
597 }
598 if self.keyModifierStateAltGraph {
599 modifiers.insert(Modifiers::ALT_GRAPH);
600 }
601 if self.keyModifierStateCapsLock {
602 modifiers.insert(Modifiers::CAPS_LOCK);
603 }
604 if self.keyModifierStateFn {
605 modifiers.insert(Modifiers::FN);
606 }
607 if self.keyModifierStateFnLock {
608 modifiers.insert(Modifiers::FN_LOCK);
609 }
610 if self.keyModifierStateHyper {
611 modifiers.insert(Modifiers::HYPER);
612 }
613 if self.keyModifierStateNumLock {
614 modifiers.insert(Modifiers::NUM_LOCK);
615 }
616 if self.keyModifierStateScrollLock {
617 modifiers.insert(Modifiers::SCROLL_LOCK);
618 }
619 if self.keyModifierStateSuper {
620 modifiers.insert(Modifiers::SUPER);
621 }
622 if self.keyModifierStateSymbol {
623 modifiers.insert(Modifiers::SYMBOL);
624 }
625 if self.keyModifierStateSymbolLock {
626 modifiers.insert(Modifiers::SYMBOL_LOCK);
627 }
628 modifiers
629 }
630}
631
632pub fn get_property_jsval(
634 cx: &mut JSContext,
635 object: HandleObject,
636 name: &ffi::CStr,
637 rval: MutableHandleValue,
638) -> Fallible<()> {
639 if unsafe { !JS_GetProperty(cx, object, name.as_ptr(), rval) } {
640 return Err(Error::JSFailed);
641 }
642
643 Ok(())
644}
645
646pub fn get_property<T>(
648 cx: &mut JSContext,
649 object: HandleObject,
650 name: &ffi::CStr,
651 option: T::Config,
652) -> Fallible<Option<T>>
653where
654 T: FromJSValConvertible,
655{
656 rooted!(&in(cx) let mut result = UndefinedValue());
657 get_property_jsval(cx, object, name, result.handle_mut())?;
658
659 if result.is_undefined() {
660 return Ok(None);
661 }
662
663 let value = T::from_jsval(cx, result.handle(), option);
664 match value {
665 Ok(ConversionResult::Success(value)) => Ok(Some(value)),
666 Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())),
667 Err(()) => Err(Error::JSFailed),
668 }
669}