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
81pub trait SafeFromJSValConvertible: Sized {
83 type Config;
84
85 #[allow(clippy::result_unit_err)] fn safe_from_jsval(
87 cx: SafeJSContext,
88 value: HandleValue,
89 option: Self::Config,
90 _can_gc: CanGc,
91 ) -> Result<ConversionResult<Self>, ()>;
92}
93
94impl<T: FromJSValConvertible> SafeFromJSValConvertible for T {
95 type Config = <T as FromJSValConvertible>::Config;
96
97 fn safe_from_jsval(
98 cx: SafeJSContext,
99 value: HandleValue,
100 option: Self::Config,
101 _can_gc: CanGc,
102 ) -> Result<ConversionResult<Self>, ()> {
103 unsafe { T::from_jsval(*cx, value, option) }
104 }
105}
106
107impl FromJSValConvertible for DOMString {
109 type Config = StringificationBehavior;
110 unsafe fn from_jsval(
111 _cx: *mut JSContext,
112 value: HandleValue,
113 null_behavior: StringificationBehavior,
114 ) -> Result<ConversionResult<DOMString>, ()> {
115 let mut cx = unsafe { crate::script_runtime::temp_cx() };
117 FromJSValConvertible::safe_from_jsval(&mut cx, value, null_behavior)
118 }
119
120 fn safe_from_jsval(
121 cx: &mut js::context::JSContext,
122 value: HandleValue,
123 null_behavior: StringificationBehavior,
124 ) -> Result<ConversionResult<DOMString>, ()> {
125 if null_behavior == StringificationBehavior::Empty && value.get().is_null() {
126 Ok(ConversionResult::Success(DOMString::new()))
127 } else {
128 match DOMString::from_js_string(cx, value) {
129 Ok(domstring) => Ok(ConversionResult::Success(domstring)),
130 Err(_) => Err(()),
131 }
132 }
133 }
134}
135
136impl FromJSValConvertible for USVString {
138 type Config = ();
139 unsafe fn from_jsval(
140 _cx: *mut JSContext,
141 value: HandleValue,
142 _: (),
143 ) -> Result<ConversionResult<USVString>, ()> {
144 let mut cx = unsafe { crate::script_runtime::temp_cx() };
146 FromJSValConvertible::safe_from_jsval(&mut cx, value, ())
147 }
148
149 fn safe_from_jsval(
150 cx: &mut js::context::JSContext,
151 value: HandleValue,
152 _: (),
153 ) -> Result<ConversionResult<USVString>, ()> {
154 let Some(jsstr) = ptr::NonNull::new(unsafe { ToString(cx.raw_cx(), value) }) else {
155 debug!("ToString failed");
156 return Err(());
157 };
158
159 Ok(ConversionResult::Success(USVString(unsafe {
161 jsstr_to_string(cx.raw_cx(), jsstr)
162 })))
163 }
164}
165
166impl ToJSValConvertible for ByteString {
168 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
169 let jsstr = JS_NewStringCopyN(
170 cx,
171 self.as_ptr() as *const libc::c_char,
172 self.len() as libc::size_t,
173 );
174 if jsstr.is_null() {
175 panic!("JS_NewStringCopyN failed");
176 }
177 rval.set(StringValue(&*jsstr));
178 }
179}
180
181impl FromJSValConvertible for ByteString {
183 type Config = ();
184 unsafe fn from_jsval(
185 _cx: *mut JSContext,
186 value: HandleValue,
187 _option: (),
188 ) -> Result<ConversionResult<ByteString>, ()> {
189 let mut cx = unsafe { crate::script_runtime::temp_cx() };
191 FromJSValConvertible::safe_from_jsval(&mut cx, value, ())
192 }
193
194 fn safe_from_jsval(
195 cx: &mut js::context::JSContext,
196 value: HandleValue,
197 _option: (),
198 ) -> Result<ConversionResult<ByteString>, ()> {
199 unsafe {
200 let string = ToString(cx.raw_cx(), value);
201 if string.is_null() {
202 debug!("ToString failed");
203 return Err(());
204 }
205
206 let latin1 = JS_DeprecatedStringHasLatin1Chars(string);
207 if latin1 {
208 let mut length = 0;
209 let chars = JS_GetLatin1StringCharsAndLength(cx, string, &mut length);
210 assert!(!chars.is_null());
211
212 let char_slice = slice::from_raw_parts(chars as *mut u8, length);
213 return Ok(ConversionResult::Success(ByteString::new(
214 char_slice.to_vec(),
215 )));
216 }
217
218 let mut length = 0;
219 let chars = JS_GetTwoByteStringCharsAndLength(cx, string, &mut length);
220 let char_vec = slice::from_raw_parts(chars, length);
221
222 if char_vec.iter().any(|&c| c > 0xFF) {
223 throw_type_error(cx.raw_cx(), c"Invalid ByteString");
224 Err(())
225 } else {
226 Ok(ConversionResult::Success(ByteString::new(
227 char_vec.iter().map(|&c| c as u8).collect(),
228 )))
229 }
230 }
231 }
232}
233
234impl<T> ToJSValConvertible for Reflector<T> {
235 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
236 let obj = self.get_jsobject().get();
237 assert!(!obj.is_null());
238 rval.set(ObjectValue(obj));
239 maybe_wrap_value(cx, rval);
240 }
241}
242
243impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> {
244 type Config = ();
245
246 unsafe fn from_jsval(
247 _cx: *mut JSContext,
248 value: HandleValue,
249 _config: Self::Config,
250 ) -> Result<ConversionResult<DomRoot<T>>, ()> {
251 let mut cx = unsafe { crate::script_runtime::temp_cx() };
253 FromJSValConvertible::safe_from_jsval(&mut cx, value, ())
254 }
255
256 fn safe_from_jsval(
257 cx: &mut js::context::JSContext,
258 value: HandleValue,
259 _config: Self::Config,
260 ) -> Result<ConversionResult<DomRoot<T>>, ()> {
261 Ok(match root_from_handlevalue(value, cx.into()) {
262 Ok(result) => ConversionResult::Success(result),
263 Err(()) => ConversionResult::Failure(c"value is not an object".into()),
264 })
265 }
266}
267
268impl<T: DomObject> ToJSValConvertible for DomRoot<T> {
269 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
270 self.reflector().to_jsval(cx, rval);
271 }
272}
273
274#[allow(clippy::result_unit_err)]
279pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> {
280 let clasp = get_object_class(obj);
281 if is_dom_class(&*clasp) {
282 trace!("plain old dom object");
283 let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
284 return Ok(&(*domjsclass).dom_class);
285 }
286 if is_dom_proxy(obj) {
287 trace!("proxy dom object");
288 let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
289 if dom_class.is_null() {
290 return Err(());
291 }
292 return Ok(&*dom_class);
293 }
294 trace!("not a dom object");
295 Err(())
296}
297
298pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool {
303 unsafe {
304 let clasp = get_object_class(obj);
305 ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj)
306 }
307}
308
309pub const DOM_OBJECT_SLOT: u32 = 0;
314
315pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
320 let mut value = UndefinedValue();
321 if is_dom_object(obj) {
322 JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
323 } else {
324 debug_assert!(is_dom_proxy(obj));
325 GetProxyReservedSlot(obj, 0, &mut value);
326 };
327 if value.is_undefined() {
328 ptr::null()
329 } else {
330 value.to_private()
331 }
332}
333
334pub enum PrototypeCheck {
335 Derive(fn(&'static DOMClass) -> bool),
336 Depth { depth: usize, proto_id: u16 },
337}
338
339#[inline]
350#[allow(clippy::result_unit_err)]
351pub unsafe fn private_from_proto_check(
352 mut obj: *mut JSObject,
353 cx: *mut JSContext,
354 proto_check: PrototypeCheck,
355) -> Result<*const libc::c_void, ()> {
356 let dom_class = get_dom_class(obj).or_else(|_| {
357 if IsWrapper(obj) {
358 trace!("found wrapper");
359 obj = UnwrapObjectDynamic(obj, cx, false);
360 if obj.is_null() {
361 trace!("unwrapping security wrapper failed");
362 Err(())
363 } else {
364 assert!(!IsWrapper(obj));
365 trace!("unwrapped successfully");
366 get_dom_class(obj)
367 }
368 } else {
369 trace!("not a dom wrapper");
370 Err(())
371 }
372 })?;
373
374 let prototype_matches = match proto_check {
375 PrototypeCheck::Derive(f) => (f)(dom_class),
376 PrototypeCheck::Depth { depth, proto_id } => {
377 dom_class.interface_chain[depth] as u16 == proto_id
378 },
379 };
380
381 if prototype_matches {
382 trace!("good prototype");
383 Ok(private_from_object(obj))
384 } else {
385 trace!("bad prototype");
386 Err(())
387 }
388}
389
390#[allow(clippy::result_unit_err)]
396pub unsafe fn native_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()>
397where
398 T: DomObject + IDLInterface,
399{
400 unsafe {
401 private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives))
402 .map(|ptr| ptr as *const T)
403 }
404}
405
406#[allow(clippy::result_unit_err)]
417pub unsafe fn root_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<DomRoot<T>, ()>
418where
419 T: DomObject + IDLInterface,
420{
421 native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
422}
423
424#[allow(clippy::result_unit_err)]
430pub fn root_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<DomRoot<T>, ()>
431where
432 T: DomObject + IDLInterface,
433{
434 if !v.get().is_object() {
435 return Err(());
436 }
437 #[expect(unsafe_code)]
438 unsafe {
439 root_from_object(v.get().to_object(), *cx)
440 }
441}
442
443pub fn jsid_to_string(cx: &mut js::context::JSContext, id: HandleId) -> Option<DOMString> {
451 let id_raw = *id;
452 if id_raw.is_string() {
453 let jsstr = ptr::NonNull::new(id_raw.to_string()).unwrap();
454 return Some(unsafe { jsstr_to_string(cx.raw_cx(), jsstr) }.into());
455 }
456
457 if id_raw.is_int() {
458 return Some(id_raw.to_int().to_string().into());
459 }
460
461 None
462}
463
464impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
465 #[inline]
466 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
467 let value = **self;
468 value.to_jsval(cx, rval);
469 }
470}
471
472impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
473 type Config = ();
474
475 unsafe fn from_jsval(
476 _cx: *mut JSContext,
477 value: HandleValue,
478 option: (),
479 ) -> Result<ConversionResult<Finite<T>>, ()> {
480 let mut cx = unsafe { crate::script_runtime::temp_cx() };
482 FromJSValConvertible::safe_from_jsval(&mut cx, value, option)
483 }
484
485 fn safe_from_jsval(
486 cx: &mut js::context::JSContext,
487 value: HandleValue,
488 option: (),
489 ) -> Result<ConversionResult<Finite<T>>, ()> {
490 let result = match FromJSValConvertible::safe_from_jsval(cx, value, option)? {
491 ConversionResult::Success(v) => v,
492 ConversionResult::Failure(error) => {
493 unsafe { throw_type_error(cx.raw_cx(), &error) };
495 return Err(());
496 },
497 };
498 match Finite::new(result) {
499 Some(v) => Ok(ConversionResult::Success(v)),
500 None => {
501 unsafe {
502 throw_type_error(
503 cx.raw_cx(),
504 c"this argument is not a finite floating-point value",
505 )
506 };
507 Err(())
508 },
509 }
510 }
511}
512
513#[inline]
519#[allow(clippy::result_unit_err)]
520unsafe fn private_from_proto_check_static(
521 obj: *mut JSObject,
522 proto_check: fn(&'static DOMClass) -> bool,
523) -> Result<*const libc::c_void, ()> {
524 let dom_class = get_dom_class(obj).map_err(|_| ())?;
525 if proto_check(dom_class) {
526 trace!("good prototype");
527 Ok(private_from_object(obj))
528 } else {
529 trace!("bad prototype");
530 Err(())
531 }
532}
533
534#[allow(clippy::result_unit_err)]
540pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
541where
542 T: DomObject + IDLInterface,
543{
544 private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T)
545}
546
547#[allow(clippy::result_unit_err)]
553pub fn native_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<*const T, ()>
554where
555 T: DomObject + IDLInterface,
556{
557 if !v.get().is_object() {
558 return Err(());
559 }
560
561 #[expect(unsafe_code)]
562 unsafe {
563 native_from_object(v.get().to_object(), *cx)
564 }
565}
566
567impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
568 #[inline]
569 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
570 let value = &**self;
571 value.to_jsval(cx, rval);
572 }
573}
574
575impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
576where
577 T: FromJSValConvertible + js::rust::GCMethods + Copy,
578 Heap<T>: JSTraceable + Default,
579{
580 type Config = T::Config;
581
582 unsafe fn from_jsval(
583 _cx: *mut JSContext,
584 value: HandleValue,
585 config: Self::Config,
586 ) -> Result<ConversionResult<Self>, ()> {
587 let mut cx = unsafe { crate::script_runtime::temp_cx() };
589 FromJSValConvertible::safe_from_jsval(&mut cx, value, config)
590 }
591
592 fn safe_from_jsval(
593 cx: &mut js::context::JSContext,
594 value: HandleValue,
595 config: Self::Config,
596 ) -> Result<ConversionResult<Self>, ()> {
597 T::safe_from_jsval(cx, value, config).map(|result| match result {
598 ConversionResult::Success(inner) => {
599 ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
600 },
601 ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
602 })
603 }
604}
605
606pub fn is_array_like<D: crate::DomTypes>(
610 cx: &mut js::context::JSContext,
611 value: HandleValue,
612) -> bool {
613 let mut is_array = false;
614 assert!(unsafe { IsArrayObject(cx, value, &mut is_array) });
615 if is_array {
616 return true;
617 }
618
619 let object: *mut JSObject = match FromJSValConvertible::safe_from_jsval(cx, value, ()).unwrap()
620 {
621 ConversionResult::Success(object) => object,
622 _ => return false,
623 };
624
625 unsafe {
626 if root_from_object::<D::DOMTokenList>(object, cx.raw_cx()).is_ok() {
628 return true;
629 }
630 if root_from_object::<D::FileList>(object, cx.raw_cx()).is_ok() {
631 return true;
632 }
633 if root_from_object::<D::HTMLCollection>(object, cx.raw_cx()).is_ok() {
634 return true;
635 }
636 if root_from_object::<D::HTMLFormControlsCollection>(object, cx.raw_cx()).is_ok() {
637 return true;
638 }
639 if root_from_object::<D::HTMLOptionsCollection>(object, cx.raw_cx()).is_ok() {
640 return true;
641 }
642 if root_from_object::<D::NodeList>(object, cx.raw_cx()).is_ok() {
643 return true;
644 }
645 }
646
647 false
648}
649
650pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
653 v: HandleValue,
654 _cx: SafeJSContext,
655) -> Result<DomRoot<D::WindowProxy>, ()> {
656 if !v.get().is_object() {
657 return Err(());
658 }
659 let object = v.get().to_object();
660 if !IsWindowProxy(object) {
661 return Err(());
662 }
663 let mut value = UndefinedValue();
664 GetProxyReservedSlot(object, 0, &mut value);
665 let ptr = value.to_private() as *const D::WindowProxy;
666 Ok(DomRoot::from_ref(&*ptr))
667}
668
669#[allow(deprecated)]
670impl<D: crate::DomTypes> EventModifierInit<D> {
671 pub fn modifiers(&self) -> Modifiers {
672 let mut modifiers = Modifiers::empty();
673 if self.altKey {
674 modifiers.insert(Modifiers::ALT);
675 }
676 if self.ctrlKey {
677 modifiers.insert(Modifiers::CONTROL);
678 }
679 if self.shiftKey {
680 modifiers.insert(Modifiers::SHIFT);
681 }
682 if self.metaKey {
683 modifiers.insert(Modifiers::META);
684 }
685 if self.keyModifierStateAltGraph {
686 modifiers.insert(Modifiers::ALT_GRAPH);
687 }
688 if self.keyModifierStateCapsLock {
689 modifiers.insert(Modifiers::CAPS_LOCK);
690 }
691 if self.keyModifierStateFn {
692 modifiers.insert(Modifiers::FN);
693 }
694 if self.keyModifierStateFnLock {
695 modifiers.insert(Modifiers::FN_LOCK);
696 }
697 if self.keyModifierStateHyper {
698 modifiers.insert(Modifiers::HYPER);
699 }
700 if self.keyModifierStateNumLock {
701 modifiers.insert(Modifiers::NUM_LOCK);
702 }
703 if self.keyModifierStateScrollLock {
704 modifiers.insert(Modifiers::SCROLL_LOCK);
705 }
706 if self.keyModifierStateSuper {
707 modifiers.insert(Modifiers::SUPER);
708 }
709 if self.keyModifierStateSymbol {
710 modifiers.insert(Modifiers::SYMBOL);
711 }
712 if self.keyModifierStateSymbolLock {
713 modifiers.insert(Modifiers::SYMBOL_LOCK);
714 }
715 modifiers
716 }
717}