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