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_NewStringCopyN, JSContext, JSObject,
17};
18use js::jsval::{ObjectValue, StringValue, UndefinedValue};
19use js::rust::wrappers2::{
20 IsArrayObject, JS_GetLatin1StringCharsAndLength, JS_GetTwoByteStringCharsAndLength,
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 mut obj: *mut JSObject,
289 cx: *mut JSContext,
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>(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()>
333where
334 T: DomObject + IDLInterface,
335{
336 unsafe {
337 private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives))
338 .map(|ptr| ptr as *const T)
339 }
340}
341
342#[allow(clippy::result_unit_err)]
353pub unsafe fn root_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<DomRoot<T>, ()>
354where
355 T: DomObject + IDLInterface,
356{
357 native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
358}
359
360#[allow(clippy::result_unit_err)]
366pub fn root_from_handlevalue<T>(
367 cx: &mut js::context::JSContext,
368 v: HandleValue,
369) -> Result<DomRoot<T>, ()>
370where
371 T: DomObject + IDLInterface,
372{
373 if !v.get().is_object() {
374 return Err(());
375 }
376 #[expect(unsafe_code)]
377 unsafe {
378 root_from_object(v.get().to_object(), cx.raw_cx())
379 }
380}
381
382pub fn jsid_to_string(cx: &js::context::JSContext, id: HandleId) -> Option<DOMString> {
387 let id_raw = *id;
388 if id_raw.is_string() {
389 let jsstr = ptr::NonNull::new(id_raw.to_string()).unwrap();
390 return Some(unsafe { jsstr_to_string(cx, jsstr) }.into());
391 }
392
393 if id_raw.is_int() {
394 return Some(id_raw.to_int().to_string().into());
395 }
396
397 None
398}
399
400impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
401 #[inline]
402 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
403 let value = **self;
404 value.to_jsval(cx, rval);
405 }
406}
407
408impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
409 type Config = ();
410
411 fn safe_from_jsval(
412 cx: &mut js::context::JSContext,
413 value: HandleValue,
414 option: (),
415 ) -> Result<ConversionResult<Finite<T>>, ()> {
416 let result = match FromJSValConvertible::safe_from_jsval(cx, value, option)? {
417 ConversionResult::Success(v) => v,
418 ConversionResult::Failure(error) => {
419 unsafe { throw_type_error(cx.raw_cx(), &error) };
421 return Err(());
422 },
423 };
424 match Finite::new(result) {
425 Some(v) => Ok(ConversionResult::Success(v)),
426 None => {
427 unsafe {
428 throw_type_error(
429 cx.raw_cx(),
430 c"this argument is not a finite floating-point value",
431 )
432 };
433 Err(())
434 },
435 }
436 }
437}
438
439#[inline]
445#[allow(clippy::result_unit_err)]
446unsafe fn private_from_proto_check_static(
447 obj: *mut JSObject,
448 proto_check: fn(&'static DOMClass) -> bool,
449) -> Result<*const libc::c_void, ()> {
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#[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 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>(
480 cx: &mut js::context::JSContext,
481 v: HandleValue,
482) -> Result<*const T, ()>
483where
484 T: DomObject + IDLInterface,
485{
486 if !v.get().is_object() {
487 return Err(());
488 }
489
490 #[expect(unsafe_code)]
491 unsafe {
492 native_from_object(v.get().to_object(), cx.raw_cx())
493 }
494}
495
496impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
497 #[inline]
498 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
499 let value = &**self;
500 value.to_jsval(cx, rval);
501 }
502}
503
504impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
505where
506 T: FromJSValConvertible + js::rust::GCMethods + Copy,
507 Heap<T>: JSTraceable + Default,
508{
509 type Config = T::Config;
510
511 fn safe_from_jsval(
512 cx: &mut js::context::JSContext,
513 value: HandleValue,
514 config: Self::Config,
515 ) -> Result<ConversionResult<Self>, ()> {
516 T::safe_from_jsval(cx, value, config).map(|result| match result {
517 ConversionResult::Success(inner) => {
518 ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
519 },
520 ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
521 })
522 }
523}
524
525pub fn is_array_like<D: crate::DomTypes>(
529 cx: &mut js::context::JSContext,
530 value: HandleValue,
531) -> bool {
532 let mut is_array = false;
533 assert!(unsafe { IsArrayObject(cx, value, &mut is_array) });
534 if is_array {
535 return true;
536 }
537
538 let object: *mut JSObject = match FromJSValConvertible::safe_from_jsval(cx, value, ()).unwrap()
539 {
540 ConversionResult::Success(object) => object,
541 _ => return false,
542 };
543
544 unsafe {
545 if root_from_object::<D::DOMTokenList>(object, cx.raw_cx()).is_ok() {
547 return true;
548 }
549 if root_from_object::<D::FileList>(object, cx.raw_cx()).is_ok() {
550 return true;
551 }
552 if root_from_object::<D::HTMLCollection>(object, cx.raw_cx()).is_ok() {
553 return true;
554 }
555 if root_from_object::<D::HTMLFormControlsCollection>(object, cx.raw_cx()).is_ok() {
556 return true;
557 }
558 if root_from_object::<D::HTMLOptionsCollection>(object, cx.raw_cx()).is_ok() {
559 return true;
560 }
561 if root_from_object::<D::NodeList>(object, cx.raw_cx()).is_ok() {
562 return true;
563 }
564 }
565
566 false
567}
568
569pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
572 v: HandleValue,
573) -> Result<DomRoot<D::WindowProxy>, ()> {
574 if !v.get().is_object() {
575 return Err(());
576 }
577 let object = v.get().to_object();
578 if !IsWindowProxy(object) {
579 return Err(());
580 }
581 let mut value = UndefinedValue();
582 GetProxyReservedSlot(object, 0, &mut value);
583 let ptr = value.to_private() as *const D::WindowProxy;
584 Ok(DomRoot::from_ref(&*ptr))
585}
586
587#[allow(deprecated)]
588impl<D: crate::DomTypes> EventModifierInit<D> {
589 pub fn modifiers(&self) -> Modifiers {
590 let mut modifiers = Modifiers::empty();
591 if self.altKey {
592 modifiers.insert(Modifiers::ALT);
593 }
594 if self.ctrlKey {
595 modifiers.insert(Modifiers::CONTROL);
596 }
597 if self.shiftKey {
598 modifiers.insert(Modifiers::SHIFT);
599 }
600 if self.metaKey {
601 modifiers.insert(Modifiers::META);
602 }
603 if self.keyModifierStateAltGraph {
604 modifiers.insert(Modifiers::ALT_GRAPH);
605 }
606 if self.keyModifierStateCapsLock {
607 modifiers.insert(Modifiers::CAPS_LOCK);
608 }
609 if self.keyModifierStateFn {
610 modifiers.insert(Modifiers::FN);
611 }
612 if self.keyModifierStateFnLock {
613 modifiers.insert(Modifiers::FN_LOCK);
614 }
615 if self.keyModifierStateHyper {
616 modifiers.insert(Modifiers::HYPER);
617 }
618 if self.keyModifierStateNumLock {
619 modifiers.insert(Modifiers::NUM_LOCK);
620 }
621 if self.keyModifierStateScrollLock {
622 modifiers.insert(Modifiers::SCROLL_LOCK);
623 }
624 if self.keyModifierStateSuper {
625 modifiers.insert(Modifiers::SUPER);
626 }
627 if self.keyModifierStateSymbol {
628 modifiers.insert(Modifiers::SYMBOL);
629 }
630 if self.keyModifierStateSymbolLock {
631 modifiers.insert(Modifiers::SYMBOL_LOCK);
632 }
633 modifiers
634 }
635}