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, JS_GetReservedSlot,
13};
14use js::jsapi::{
15 Heap, IsWindowProxy, JS_DeprecatedStringHasLatin1Chars, JS_NewStringCopyN, JSContext, JSObject,
16};
17use js::jsval::{ObjectValue, StringValue, UndefinedValue};
18use js::rust::wrappers2::{
19 IsArrayObject, JS_GetLatin1StringCharsAndLength, JS_GetTwoByteStringCharsAndLength,
20 UnwrapObjectDynamic,
21};
22use js::rust::{
23 HandleId, HandleValue, MutableHandleValue, ToString, get_object_class, is_dom_class,
24 is_dom_object, maybe_wrap_value,
25};
26use keyboard_types::Modifiers;
27use num_traits::Float;
28
29use crate::JSTraceable;
30use crate::codegen::GenericBindings::EventModifierInitBinding::EventModifierInit;
31use crate::inheritance::Castable;
32use crate::num::Finite;
33use crate::reflector::{DomObject, Reflector};
34use crate::root::DomRoot;
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: &mut js::context::JSContext, rval: MutableHandleValue);
42}
43
44impl<T: ToJSValConvertible + ?Sized> SafeToJSValConvertible for T {
45 fn safe_to_jsval(&self, cx: &mut js::context::JSContext, rval: MutableHandleValue) {
46 ToJSValConvertible::safe_to_jsval(self, cx, rval);
47 }
48}
49
50pub trait IDLInterface {
52 fn derives(_: &'static DOMClass) -> bool;
54
55 const PROTO_FIRST: u16 = 0;
57 const PROTO_LAST: u16 = u16::MAX;
59}
60
61pub trait DerivedFrom<T: Castable>: Castable {}
63
64impl ToJSValConvertible for USVString {
66 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
67 self.0.to_jsval(cx, rval);
68 }
69}
70
71#[derive(Clone, PartialEq)]
73pub enum StringificationBehavior {
74 Default,
76 Empty,
78}
79
80impl FromJSValConvertible for DOMString {
82 type Config = StringificationBehavior;
83
84 fn safe_from_jsval(
85 cx: &mut js::context::JSContext,
86 value: HandleValue,
87 null_behavior: StringificationBehavior,
88 ) -> Result<ConversionResult<DOMString>, ()> {
89 if null_behavior == StringificationBehavior::Empty && value.get().is_null() {
90 Ok(ConversionResult::Success(DOMString::new()))
91 } else {
92 match DOMString::from_js_string(cx, value) {
93 Ok(domstring) => Ok(ConversionResult::Success(domstring)),
94 Err(_) => Err(()),
95 }
96 }
97 }
98}
99
100impl FromJSValConvertible for USVString {
102 type Config = ();
103
104 fn safe_from_jsval(
105 cx: &mut js::context::JSContext,
106 value: HandleValue,
107 _: (),
108 ) -> Result<ConversionResult<USVString>, ()> {
109 let Some(jsstr) = ptr::NonNull::new(unsafe { ToString(cx, value) }) else {
110 debug!("ToString failed");
111 return Err(());
112 };
113
114 Ok(ConversionResult::Success(USVString(unsafe {
116 jsstr_to_string(cx, jsstr)
117 })))
118 }
119}
120
121impl ToJSValConvertible for ByteString {
123 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
124 let jsstr = JS_NewStringCopyN(
125 cx,
126 self.as_ptr() as *const libc::c_char,
127 self.len() as libc::size_t,
128 );
129 if jsstr.is_null() {
130 panic!("JS_NewStringCopyN failed");
131 }
132 rval.set(StringValue(&*jsstr));
133 }
134}
135
136impl FromJSValConvertible for ByteString {
138 type Config = ();
139
140 fn safe_from_jsval(
141 cx: &mut js::context::JSContext,
142 value: HandleValue,
143 _option: (),
144 ) -> Result<ConversionResult<ByteString>, ()> {
145 unsafe {
146 let string = ToString(cx, value);
147 if string.is_null() {
148 debug!("ToString failed");
149 return Err(());
150 }
151
152 let latin1 = JS_DeprecatedStringHasLatin1Chars(string);
153 if latin1 {
154 let mut length = 0;
155 let chars = JS_GetLatin1StringCharsAndLength(cx, string, &mut length);
156 assert!(!chars.is_null());
157
158 let char_slice = slice::from_raw_parts(chars as *mut u8, length);
159 return Ok(ConversionResult::Success(ByteString::new(
160 char_slice.to_vec(),
161 )));
162 }
163
164 let mut length = 0;
165 let chars = JS_GetTwoByteStringCharsAndLength(cx, string, &mut length);
166 let char_vec = slice::from_raw_parts(chars, length);
167
168 if char_vec.iter().any(|&c| c > 0xFF) {
169 throw_type_error(cx.raw_cx(), c"Invalid ByteString");
170 Err(())
171 } else {
172 Ok(ConversionResult::Success(ByteString::new(
173 char_vec.iter().map(|&c| c as u8).collect(),
174 )))
175 }
176 }
177 }
178}
179
180impl<T> ToJSValConvertible for Reflector<T> {
181 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
182 let obj = self.get_jsobject().get();
183 assert!(!obj.is_null());
184 rval.set(ObjectValue(obj));
185 maybe_wrap_value(cx, rval);
186 }
187}
188
189impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> {
190 type Config = ();
191
192 fn safe_from_jsval(
193 cx: &mut js::context::JSContext,
194 value: HandleValue,
195 _config: Self::Config,
196 ) -> Result<ConversionResult<DomRoot<T>>, ()> {
197 Ok(match root_from_handlevalue(cx, value) {
198 Ok(result) => ConversionResult::Success(result),
199 Err(()) => ConversionResult::Failure(c"value is not an object".into()),
200 })
201 }
202}
203
204impl<T: DomObject> ToJSValConvertible for DomRoot<T> {
205 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
206 self.reflector().to_jsval(cx, rval);
207 }
208}
209
210#[allow(clippy::result_unit_err)]
215pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> {
216 let clasp = get_object_class(obj);
217 if is_dom_class(&*clasp) {
218 trace!("plain old dom object");
219 let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
220 return Ok(&(*domjsclass).dom_class);
221 }
222 if is_dom_proxy(obj) {
223 trace!("proxy dom object");
224 let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
225 if dom_class.is_null() {
226 return Err(());
227 }
228 return Ok(&*dom_class);
229 }
230 trace!("not a dom object");
231 Err(())
232}
233
234pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool {
239 unsafe {
240 let clasp = get_object_class(obj);
241 ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj)
242 }
243}
244
245pub const DOM_OBJECT_SLOT: u32 = 0;
250
251pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
256 let mut value = UndefinedValue();
257 if is_dom_object(obj) {
258 JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
259 } else {
260 debug_assert!(is_dom_proxy(obj));
261 GetProxyReservedSlot(obj, 0, &mut value);
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 js::context::JSContext,
289 mut obj: *mut JSObject,
290 proto_check: PrototypeCheck,
291) -> Result<*const libc::c_void, ()> {
292 let dom_class = 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 let prototype_matches = match proto_check {
311 PrototypeCheck::Derive(f) => (f)(dom_class),
312 PrototypeCheck::Depth { depth, proto_id } => {
313 dom_class.interface_chain[depth] as u16 == proto_id
314 },
315 };
316
317 if prototype_matches {
318 trace!("good prototype");
319 Ok(private_from_object(obj))
320 } else {
321 trace!("bad prototype");
322 Err(())
323 }
324}
325
326#[allow(clippy::result_unit_err)]
332pub unsafe fn native_from_object<T>(
333 cx: &mut js::context::JSContext,
334 obj: *mut JSObject,
335) -> Result<*const T, ()>
336where
337 T: DomObject + IDLInterface,
338{
339 unsafe {
340 private_from_proto_check(cx, obj, PrototypeCheck::Derive(T::derives))
341 .map(|ptr| ptr as *const T)
342 }
343}
344
345#[allow(clippy::result_unit_err)]
356pub unsafe fn root_from_object<T>(
357 cx: &mut js::context::JSContext,
358 obj: *mut JSObject,
359) -> Result<DomRoot<T>, ()>
360where
361 T: DomObject + IDLInterface,
362{
363 native_from_object(cx, obj).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
364}
365
366#[allow(clippy::result_unit_err)]
372pub fn root_from_handlevalue<T>(
373 cx: &mut js::context::JSContext,
374 v: HandleValue,
375) -> Result<DomRoot<T>, ()>
376where
377 T: DomObject + IDLInterface,
378{
379 if !v.get().is_object() {
380 return Err(());
381 }
382 #[expect(unsafe_code)]
383 unsafe {
384 root_from_object(cx, v.get().to_object())
385 }
386}
387
388pub fn jsid_to_string(cx: &js::context::JSContext, id: HandleId) -> Option<DOMString> {
393 let id_raw = *id;
394 if id_raw.is_string() {
395 let jsstr = ptr::NonNull::new(id_raw.to_string()).unwrap();
396 return Some(unsafe { jsstr_to_string(cx, jsstr) }.into());
397 }
398
399 if id_raw.is_int() {
400 return Some(id_raw.to_int().to_string().into());
401 }
402
403 None
404}
405
406impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
407 #[inline]
408 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
409 let value = **self;
410 value.to_jsval(cx, rval);
411 }
412}
413
414impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
415 type Config = ();
416
417 fn safe_from_jsval(
418 cx: &mut js::context::JSContext,
419 value: HandleValue,
420 option: (),
421 ) -> Result<ConversionResult<Finite<T>>, ()> {
422 let result = match FromJSValConvertible::safe_from_jsval(cx, value, option)? {
423 ConversionResult::Success(v) => v,
424 ConversionResult::Failure(error) => {
425 unsafe { throw_type_error(cx.raw_cx(), &error) };
427 return Err(());
428 },
429 };
430 match Finite::new(result) {
431 Some(v) => Ok(ConversionResult::Success(v)),
432 None => {
433 unsafe {
434 throw_type_error(
435 cx.raw_cx(),
436 c"this argument is not a finite floating-point value",
437 )
438 };
439 Err(())
440 },
441 }
442 }
443}
444
445#[inline]
451#[allow(clippy::result_unit_err)]
452unsafe fn private_from_proto_check_static(
453 obj: *mut JSObject,
454 proto_check: fn(&'static DOMClass) -> bool,
455) -> Result<*const libc::c_void, ()> {
456 let dom_class = get_dom_class(obj).map_err(|_| ())?;
457 if proto_check(dom_class) {
458 trace!("good prototype");
459 Ok(private_from_object(obj))
460 } else {
461 trace!("bad prototype");
462 Err(())
463 }
464}
465
466#[allow(clippy::result_unit_err)]
472pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
473where
474 T: DomObject + IDLInterface,
475{
476 private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T)
477}
478
479#[allow(clippy::result_unit_err)]
485pub fn native_from_handlevalue<T>(
486 cx: &mut js::context::JSContext,
487 v: HandleValue,
488) -> Result<*const T, ()>
489where
490 T: DomObject + IDLInterface,
491{
492 if !v.get().is_object() {
493 return Err(());
494 }
495
496 #[expect(unsafe_code)]
497 unsafe {
498 native_from_object(cx, v.get().to_object())
499 }
500}
501
502impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
503 #[inline]
504 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
505 let value = &**self;
506 value.to_jsval(cx, rval);
507 }
508}
509
510impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
511where
512 T: FromJSValConvertible + js::rust::GCMethods + Copy,
513 Heap<T>: JSTraceable + Default,
514{
515 type Config = T::Config;
516
517 fn safe_from_jsval(
518 cx: &mut js::context::JSContext,
519 value: HandleValue,
520 config: Self::Config,
521 ) -> Result<ConversionResult<Self>, ()> {
522 T::safe_from_jsval(cx, value, config).map(|result| match result {
523 ConversionResult::Success(inner) => {
524 ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
525 },
526 ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
527 })
528 }
529}
530
531pub fn is_array_like<D: crate::DomTypes>(
535 cx: &mut js::context::JSContext,
536 value: HandleValue,
537) -> bool {
538 let mut is_array = false;
539 assert!(unsafe { IsArrayObject(cx, value, &mut is_array) });
540 if is_array {
541 return true;
542 }
543
544 let object: *mut JSObject = match FromJSValConvertible::safe_from_jsval(cx, value, ()).unwrap()
545 {
546 ConversionResult::Success(object) => object,
547 _ => return false,
548 };
549
550 unsafe {
551 if root_from_object::<D::DOMTokenList>(cx, object).is_ok() {
553 return true;
554 }
555 if root_from_object::<D::FileList>(cx, object).is_ok() {
556 return true;
557 }
558 if root_from_object::<D::HTMLCollection>(cx, object).is_ok() {
559 return true;
560 }
561 if root_from_object::<D::HTMLFormControlsCollection>(cx, object).is_ok() {
562 return true;
563 }
564 if root_from_object::<D::HTMLOptionsCollection>(cx, object).is_ok() {
565 return true;
566 }
567 if root_from_object::<D::NodeList>(cx, object).is_ok() {
568 return true;
569 }
570 }
571
572 false
573}
574
575pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
578 v: HandleValue,
579) -> Result<DomRoot<D::WindowProxy>, ()> {
580 if !v.get().is_object() {
581 return Err(());
582 }
583 let object = v.get().to_object();
584 if !IsWindowProxy(object) {
585 return Err(());
586 }
587 let mut value = UndefinedValue();
588 GetProxyReservedSlot(object, 0, &mut value);
589 let ptr = value.to_private() as *const D::WindowProxy;
590 Ok(DomRoot::from_ref(&*ptr))
591}
592
593#[allow(deprecated)]
594impl<D: crate::DomTypes> EventModifierInit<D> {
595 pub fn modifiers(&self) -> Modifiers {
596 let mut modifiers = Modifiers::empty();
597 if self.altKey {
598 modifiers.insert(Modifiers::ALT);
599 }
600 if self.ctrlKey {
601 modifiers.insert(Modifiers::CONTROL);
602 }
603 if self.shiftKey {
604 modifiers.insert(Modifiers::SHIFT);
605 }
606 if self.metaKey {
607 modifiers.insert(Modifiers::META);
608 }
609 if self.keyModifierStateAltGraph {
610 modifiers.insert(Modifiers::ALT_GRAPH);
611 }
612 if self.keyModifierStateCapsLock {
613 modifiers.insert(Modifiers::CAPS_LOCK);
614 }
615 if self.keyModifierStateFn {
616 modifiers.insert(Modifiers::FN);
617 }
618 if self.keyModifierStateFnLock {
619 modifiers.insert(Modifiers::FN_LOCK);
620 }
621 if self.keyModifierStateHyper {
622 modifiers.insert(Modifiers::HYPER);
623 }
624 if self.keyModifierStateNumLock {
625 modifiers.insert(Modifiers::NUM_LOCK);
626 }
627 if self.keyModifierStateScrollLock {
628 modifiers.insert(Modifiers::SCROLL_LOCK);
629 }
630 if self.keyModifierStateSuper {
631 modifiers.insert(Modifiers::SUPER);
632 }
633 if self.keyModifierStateSymbol {
634 modifiers.insert(Modifiers::SYMBOL);
635 }
636 if self.keyModifierStateSymbolLock {
637 modifiers.insert(Modifiers::SYMBOL_LOCK);
638 }
639 modifiers
640 }
641}