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::script_runtime::{CanGc, JSContext as SafeJSContext};
36use crate::str::{ByteString, DOMString, USVString};
37use crate::trace::RootedTraceableBox;
38use crate::utils::{DOMClass, DOMJSClass};
39
40pub trait SafeToJSValConvertible {
42 fn safe_to_jsval(&self, cx: SafeJSContext, rval: MutableHandleValue, can_gc: CanGc);
43}
44
45impl<T: ToJSValConvertible + ?Sized> SafeToJSValConvertible for T {
46 fn safe_to_jsval(&self, cx: SafeJSContext, rval: MutableHandleValue, _can_gc: CanGc) {
47 unsafe { self.to_jsval(*cx, rval) };
48 }
49}
50
51pub trait IDLInterface {
53 fn derives(_: &'static DOMClass) -> bool;
55
56 const PROTO_FIRST: u16 = 0;
58 const PROTO_LAST: u16 = u16::MAX;
60}
61
62pub trait DerivedFrom<T: Castable>: Castable {}
64
65impl ToJSValConvertible for USVString {
67 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
68 self.0.to_jsval(cx, rval);
69 }
70}
71
72#[derive(Clone, PartialEq)]
74pub enum StringificationBehavior {
75 Default,
77 Empty,
79}
80
81impl FromJSValConvertible for DOMString {
83 type Config = StringificationBehavior;
84 unsafe fn from_jsval(
85 _cx: *mut JSContext,
86 value: HandleValue,
87 null_behavior: StringificationBehavior,
88 ) -> Result<ConversionResult<DOMString>, ()> {
89 let mut cx = unsafe { crate::script_runtime::temp_cx() };
91 FromJSValConvertible::safe_from_jsval(&mut cx, value, null_behavior)
92 }
93
94 fn safe_from_jsval(
95 cx: &mut js::context::JSContext,
96 value: HandleValue,
97 null_behavior: StringificationBehavior,
98 ) -> Result<ConversionResult<DOMString>, ()> {
99 if null_behavior == StringificationBehavior::Empty && value.get().is_null() {
100 Ok(ConversionResult::Success(DOMString::new()))
101 } else {
102 match DOMString::from_js_string(cx, value) {
103 Ok(domstring) => Ok(ConversionResult::Success(domstring)),
104 Err(_) => Err(()),
105 }
106 }
107 }
108}
109
110impl FromJSValConvertible for USVString {
112 type Config = ();
113 unsafe fn from_jsval(
114 _cx: *mut JSContext,
115 value: HandleValue,
116 _: (),
117 ) -> Result<ConversionResult<USVString>, ()> {
118 let mut cx = unsafe { crate::script_runtime::temp_cx() };
120 FromJSValConvertible::safe_from_jsval(&mut cx, value, ())
121 }
122
123 fn safe_from_jsval(
124 cx: &mut js::context::JSContext,
125 value: HandleValue,
126 _: (),
127 ) -> Result<ConversionResult<USVString>, ()> {
128 let Some(jsstr) = ptr::NonNull::new(unsafe { ToString(cx, value) }) else {
129 debug!("ToString failed");
130 return Err(());
131 };
132
133 Ok(ConversionResult::Success(USVString(unsafe {
135 jsstr_to_string(cx, jsstr)
136 })))
137 }
138}
139
140impl ToJSValConvertible for ByteString {
142 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
143 let jsstr = JS_NewStringCopyN(
144 cx,
145 self.as_ptr() as *const libc::c_char,
146 self.len() as libc::size_t,
147 );
148 if jsstr.is_null() {
149 panic!("JS_NewStringCopyN failed");
150 }
151 rval.set(StringValue(&*jsstr));
152 }
153}
154
155impl FromJSValConvertible for ByteString {
157 type Config = ();
158 unsafe fn from_jsval(
159 _cx: *mut JSContext,
160 value: HandleValue,
161 _option: (),
162 ) -> Result<ConversionResult<ByteString>, ()> {
163 let mut cx = unsafe { crate::script_runtime::temp_cx() };
165 FromJSValConvertible::safe_from_jsval(&mut cx, value, ())
166 }
167
168 fn safe_from_jsval(
169 cx: &mut js::context::JSContext,
170 value: HandleValue,
171 _option: (),
172 ) -> Result<ConversionResult<ByteString>, ()> {
173 unsafe {
174 let string = ToString(cx, value);
175 if string.is_null() {
176 debug!("ToString failed");
177 return Err(());
178 }
179
180 let latin1 = JS_DeprecatedStringHasLatin1Chars(string);
181 if latin1 {
182 let mut length = 0;
183 let chars = JS_GetLatin1StringCharsAndLength(cx, string, &mut length);
184 assert!(!chars.is_null());
185
186 let char_slice = slice::from_raw_parts(chars as *mut u8, length);
187 return Ok(ConversionResult::Success(ByteString::new(
188 char_slice.to_vec(),
189 )));
190 }
191
192 let mut length = 0;
193 let chars = JS_GetTwoByteStringCharsAndLength(cx, string, &mut length);
194 let char_vec = slice::from_raw_parts(chars, length);
195
196 if char_vec.iter().any(|&c| c > 0xFF) {
197 throw_type_error(cx.raw_cx(), c"Invalid ByteString");
198 Err(())
199 } else {
200 Ok(ConversionResult::Success(ByteString::new(
201 char_vec.iter().map(|&c| c as u8).collect(),
202 )))
203 }
204 }
205 }
206}
207
208impl<T> ToJSValConvertible for Reflector<T> {
209 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
210 let obj = self.get_jsobject().get();
211 assert!(!obj.is_null());
212 rval.set(ObjectValue(obj));
213 maybe_wrap_value(cx, rval);
214 }
215}
216
217impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> {
218 type Config = ();
219
220 unsafe fn from_jsval(
221 _cx: *mut JSContext,
222 value: HandleValue,
223 _config: Self::Config,
224 ) -> Result<ConversionResult<DomRoot<T>>, ()> {
225 let mut cx = unsafe { crate::script_runtime::temp_cx() };
227 FromJSValConvertible::safe_from_jsval(&mut cx, value, ())
228 }
229
230 fn safe_from_jsval(
231 cx: &mut js::context::JSContext,
232 value: HandleValue,
233 _config: Self::Config,
234 ) -> Result<ConversionResult<DomRoot<T>>, ()> {
235 Ok(match root_from_handlevalue(value, cx.into()) {
236 Ok(result) => ConversionResult::Success(result),
237 Err(()) => ConversionResult::Failure(c"value is not an object".into()),
238 })
239 }
240}
241
242impl<T: DomObject> ToJSValConvertible for DomRoot<T> {
243 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
244 self.reflector().to_jsval(cx, rval);
245 }
246}
247
248#[allow(clippy::result_unit_err)]
253pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> {
254 let clasp = get_object_class(obj);
255 if is_dom_class(&*clasp) {
256 trace!("plain old dom object");
257 let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
258 return Ok(&(*domjsclass).dom_class);
259 }
260 if is_dom_proxy(obj) {
261 trace!("proxy dom object");
262 let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
263 if dom_class.is_null() {
264 return Err(());
265 }
266 return Ok(&*dom_class);
267 }
268 trace!("not a dom object");
269 Err(())
270}
271
272pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool {
277 unsafe {
278 let clasp = get_object_class(obj);
279 ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj)
280 }
281}
282
283pub const DOM_OBJECT_SLOT: u32 = 0;
288
289pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
294 let mut value = UndefinedValue();
295 if is_dom_object(obj) {
296 JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
297 } else {
298 debug_assert!(is_dom_proxy(obj));
299 GetProxyReservedSlot(obj, 0, &mut value);
300 };
301 if value.is_undefined() {
302 ptr::null()
303 } else {
304 value.to_private()
305 }
306}
307
308pub enum PrototypeCheck {
309 Derive(fn(&'static DOMClass) -> bool),
310 Depth { depth: usize, proto_id: u16 },
311}
312
313#[inline]
324#[allow(clippy::result_unit_err)]
325pub unsafe fn private_from_proto_check(
326 mut obj: *mut JSObject,
327 cx: *mut JSContext,
328 proto_check: PrototypeCheck,
329) -> Result<*const libc::c_void, ()> {
330 let dom_class = get_dom_class(obj).or_else(|_| {
331 if IsWrapper(obj) {
332 trace!("found wrapper");
333 obj = UnwrapObjectDynamic(obj, cx, false);
334 if obj.is_null() {
335 trace!("unwrapping security wrapper failed");
336 Err(())
337 } else {
338 assert!(!IsWrapper(obj));
339 trace!("unwrapped successfully");
340 get_dom_class(obj)
341 }
342 } else {
343 trace!("not a dom wrapper");
344 Err(())
345 }
346 })?;
347
348 let prototype_matches = match proto_check {
349 PrototypeCheck::Derive(f) => (f)(dom_class),
350 PrototypeCheck::Depth { depth, proto_id } => {
351 dom_class.interface_chain[depth] as u16 == proto_id
352 },
353 };
354
355 if prototype_matches {
356 trace!("good prototype");
357 Ok(private_from_object(obj))
358 } else {
359 trace!("bad prototype");
360 Err(())
361 }
362}
363
364#[allow(clippy::result_unit_err)]
370pub unsafe fn native_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()>
371where
372 T: DomObject + IDLInterface,
373{
374 unsafe {
375 private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives))
376 .map(|ptr| ptr as *const T)
377 }
378}
379
380#[allow(clippy::result_unit_err)]
391pub unsafe fn root_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<DomRoot<T>, ()>
392where
393 T: DomObject + IDLInterface,
394{
395 native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
396}
397
398#[allow(clippy::result_unit_err)]
404pub fn root_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<DomRoot<T>, ()>
405where
406 T: DomObject + IDLInterface,
407{
408 if !v.get().is_object() {
409 return Err(());
410 }
411 #[expect(unsafe_code)]
412 unsafe {
413 root_from_object(v.get().to_object(), *cx)
414 }
415}
416
417pub fn jsid_to_string(cx: &js::context::JSContext, id: HandleId) -> Option<DOMString> {
422 let id_raw = *id;
423 if id_raw.is_string() {
424 let jsstr = ptr::NonNull::new(id_raw.to_string()).unwrap();
425 return Some(unsafe { jsstr_to_string(cx, jsstr) }.into());
426 }
427
428 if id_raw.is_int() {
429 return Some(id_raw.to_int().to_string().into());
430 }
431
432 None
433}
434
435impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
436 #[inline]
437 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
438 let value = **self;
439 value.to_jsval(cx, rval);
440 }
441}
442
443impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
444 type Config = ();
445
446 unsafe fn from_jsval(
447 _cx: *mut JSContext,
448 value: HandleValue,
449 option: (),
450 ) -> Result<ConversionResult<Finite<T>>, ()> {
451 let mut cx = unsafe { crate::script_runtime::temp_cx() };
453 FromJSValConvertible::safe_from_jsval(&mut cx, value, option)
454 }
455
456 fn safe_from_jsval(
457 cx: &mut js::context::JSContext,
458 value: HandleValue,
459 option: (),
460 ) -> Result<ConversionResult<Finite<T>>, ()> {
461 let result = match FromJSValConvertible::safe_from_jsval(cx, value, option)? {
462 ConversionResult::Success(v) => v,
463 ConversionResult::Failure(error) => {
464 unsafe { throw_type_error(cx.raw_cx(), &error) };
466 return Err(());
467 },
468 };
469 match Finite::new(result) {
470 Some(v) => Ok(ConversionResult::Success(v)),
471 None => {
472 unsafe {
473 throw_type_error(
474 cx.raw_cx(),
475 c"this argument is not a finite floating-point value",
476 )
477 };
478 Err(())
479 },
480 }
481 }
482}
483
484#[inline]
490#[allow(clippy::result_unit_err)]
491unsafe fn private_from_proto_check_static(
492 obj: *mut JSObject,
493 proto_check: fn(&'static DOMClass) -> bool,
494) -> Result<*const libc::c_void, ()> {
495 let dom_class = get_dom_class(obj).map_err(|_| ())?;
496 if proto_check(dom_class) {
497 trace!("good prototype");
498 Ok(private_from_object(obj))
499 } else {
500 trace!("bad prototype");
501 Err(())
502 }
503}
504
505#[allow(clippy::result_unit_err)]
511pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
512where
513 T: DomObject + IDLInterface,
514{
515 private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T)
516}
517
518#[allow(clippy::result_unit_err)]
524pub fn native_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<*const T, ()>
525where
526 T: DomObject + IDLInterface,
527{
528 if !v.get().is_object() {
529 return Err(());
530 }
531
532 #[expect(unsafe_code)]
533 unsafe {
534 native_from_object(v.get().to_object(), *cx)
535 }
536}
537
538impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
539 #[inline]
540 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
541 let value = &**self;
542 value.to_jsval(cx, rval);
543 }
544}
545
546impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
547where
548 T: FromJSValConvertible + js::rust::GCMethods + Copy,
549 Heap<T>: JSTraceable + Default,
550{
551 type Config = T::Config;
552
553 unsafe fn from_jsval(
554 _cx: *mut JSContext,
555 value: HandleValue,
556 config: Self::Config,
557 ) -> Result<ConversionResult<Self>, ()> {
558 let mut cx = unsafe { crate::script_runtime::temp_cx() };
560 FromJSValConvertible::safe_from_jsval(&mut cx, value, config)
561 }
562
563 fn safe_from_jsval(
564 cx: &mut js::context::JSContext,
565 value: HandleValue,
566 config: Self::Config,
567 ) -> Result<ConversionResult<Self>, ()> {
568 T::safe_from_jsval(cx, value, config).map(|result| match result {
569 ConversionResult::Success(inner) => {
570 ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
571 },
572 ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
573 })
574 }
575}
576
577pub fn is_array_like<D: crate::DomTypes>(
581 cx: &mut js::context::JSContext,
582 value: HandleValue,
583) -> bool {
584 let mut is_array = false;
585 assert!(unsafe { IsArrayObject(cx, value, &mut is_array) });
586 if is_array {
587 return true;
588 }
589
590 let object: *mut JSObject = match FromJSValConvertible::safe_from_jsval(cx, value, ()).unwrap()
591 {
592 ConversionResult::Success(object) => object,
593 _ => return false,
594 };
595
596 unsafe {
597 if root_from_object::<D::DOMTokenList>(object, cx.raw_cx()).is_ok() {
599 return true;
600 }
601 if root_from_object::<D::FileList>(object, cx.raw_cx()).is_ok() {
602 return true;
603 }
604 if root_from_object::<D::HTMLCollection>(object, cx.raw_cx()).is_ok() {
605 return true;
606 }
607 if root_from_object::<D::HTMLFormControlsCollection>(object, cx.raw_cx()).is_ok() {
608 return true;
609 }
610 if root_from_object::<D::HTMLOptionsCollection>(object, cx.raw_cx()).is_ok() {
611 return true;
612 }
613 if root_from_object::<D::NodeList>(object, cx.raw_cx()).is_ok() {
614 return true;
615 }
616 }
617
618 false
619}
620
621pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
624 v: HandleValue,
625 _cx: SafeJSContext,
626) -> Result<DomRoot<D::WindowProxy>, ()> {
627 if !v.get().is_object() {
628 return Err(());
629 }
630 let object = v.get().to_object();
631 if !IsWindowProxy(object) {
632 return Err(());
633 }
634 let mut value = UndefinedValue();
635 GetProxyReservedSlot(object, 0, &mut value);
636 let ptr = value.to_private() as *const D::WindowProxy;
637 Ok(DomRoot::from_ref(&*ptr))
638}
639
640#[allow(deprecated)]
641impl<D: crate::DomTypes> EventModifierInit<D> {
642 pub fn modifiers(&self) -> Modifiers {
643 let mut modifiers = Modifiers::empty();
644 if self.altKey {
645 modifiers.insert(Modifiers::ALT);
646 }
647 if self.ctrlKey {
648 modifiers.insert(Modifiers::CONTROL);
649 }
650 if self.shiftKey {
651 modifiers.insert(Modifiers::SHIFT);
652 }
653 if self.metaKey {
654 modifiers.insert(Modifiers::META);
655 }
656 if self.keyModifierStateAltGraph {
657 modifiers.insert(Modifiers::ALT_GRAPH);
658 }
659 if self.keyModifierStateCapsLock {
660 modifiers.insert(Modifiers::CAPS_LOCK);
661 }
662 if self.keyModifierStateFn {
663 modifiers.insert(Modifiers::FN);
664 }
665 if self.keyModifierStateFnLock {
666 modifiers.insert(Modifiers::FN_LOCK);
667 }
668 if self.keyModifierStateHyper {
669 modifiers.insert(Modifiers::HYPER);
670 }
671 if self.keyModifierStateNumLock {
672 modifiers.insert(Modifiers::NUM_LOCK);
673 }
674 if self.keyModifierStateScrollLock {
675 modifiers.insert(Modifiers::SCROLL_LOCK);
676 }
677 if self.keyModifierStateSuper {
678 modifiers.insert(Modifiers::SUPER);
679 }
680 if self.keyModifierStateSymbol {
681 modifiers.insert(Modifiers::SYMBOL);
682 }
683 if self.keyModifierStateSymbolLock {
684 modifiers.insert(Modifiers::SYMBOL_LOCK);
685 }
686 modifiers
687 }
688}