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_GetLatin1StringCharsAndLength,
17 JS_GetTwoByteStringCharsAndLength, JS_NewStringCopyN, JSContext, JSObject,
18};
19use js::jsval::{ObjectValue, StringValue, UndefinedValue};
20use js::rust::wrappers::IsArrayObject;
21use js::rust::{
22 HandleId, HandleValue, MutableHandleValue, ToString, get_object_class, is_dom_class,
23 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::inheritance::Castable;
31use crate::num::Finite;
32use crate::reflector::{DomObject, Reflector};
33use crate::root::DomRoot;
34use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
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: SafeJSContext, rval: MutableHandleValue, can_gc: CanGc);
42}
43
44impl<T: ToJSValConvertible + ?Sized> SafeToJSValConvertible for T {
45 fn safe_to_jsval(&self, cx: SafeJSContext, rval: MutableHandleValue, _can_gc: CanGc) {
46 unsafe { self.to_jsval(*cx, rval) };
47 }
48}
49
50pub trait IDLInterface {
52 fn derives(_: &'static DOMClass) -> bool;
54}
55
56pub trait DerivedFrom<T: Castable>: Castable {}
58
59impl ToJSValConvertible for USVString {
61 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
62 self.0.to_jsval(cx, rval);
63 }
64}
65
66#[derive(Clone, PartialEq)]
68pub enum StringificationBehavior {
69 Default,
71 Empty,
73}
74
75pub trait SafeFromJSValConvertible: Sized {
77 type Config;
78
79 #[allow(clippy::result_unit_err)] fn safe_from_jsval(
81 cx: SafeJSContext,
82 value: HandleValue,
83 option: Self::Config,
84 _can_gc: CanGc,
85 ) -> Result<ConversionResult<Self>, ()>;
86}
87
88impl<T: FromJSValConvertible> SafeFromJSValConvertible for T {
89 type Config = <T as FromJSValConvertible>::Config;
90
91 fn safe_from_jsval(
92 cx: SafeJSContext,
93 value: HandleValue,
94 option: Self::Config,
95 _can_gc: CanGc,
96 ) -> Result<ConversionResult<Self>, ()> {
97 unsafe { T::from_jsval(*cx, value, option) }
98 }
99}
100
101impl FromJSValConvertible for DOMString {
103 type Config = StringificationBehavior;
104 unsafe fn from_jsval(
105 cx: *mut JSContext,
106 value: HandleValue,
107 null_behavior: StringificationBehavior,
108 ) -> Result<ConversionResult<DOMString>, ()> {
109 if null_behavior == StringificationBehavior::Empty && value.get().is_null() {
110 Ok(ConversionResult::Success(DOMString::new()))
111 } else {
112 match DOMString::from_js_string(unsafe { SafeJSContext::from_ptr(cx) }, value) {
113 Ok(domstring) => Ok(ConversionResult::Success(domstring)),
114 Err(_) => Err(()),
115 }
116 }
117 }
118}
119
120impl FromJSValConvertible for USVString {
122 type Config = ();
123 unsafe fn from_jsval(
124 cx: *mut JSContext,
125 value: HandleValue,
126 _: (),
127 ) -> Result<ConversionResult<USVString>, ()> {
128 let Some(jsstr) = ptr::NonNull::new(ToString(cx, value)) else {
129 debug!("ToString failed");
130 return Err(());
131 };
132 let latin1 = JS_DeprecatedStringHasLatin1Chars(jsstr.as_ptr());
133 if latin1 {
134 return Ok(ConversionResult::Success(USVString(jsstr_to_string(
136 cx, jsstr,
137 ))));
138 }
139 let mut length = 0;
140 let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr.as_ptr(), &mut length);
141 assert!(!chars.is_null());
142 let char_vec = slice::from_raw_parts(chars, length);
143 Ok(ConversionResult::Success(USVString(
144 String::from_utf16_lossy(char_vec),
145 )))
146 }
147}
148
149impl ToJSValConvertible for ByteString {
151 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
152 let jsstr = JS_NewStringCopyN(
153 cx,
154 self.as_ptr() as *const libc::c_char,
155 self.len() as libc::size_t,
156 );
157 if jsstr.is_null() {
158 panic!("JS_NewStringCopyN failed");
159 }
160 rval.set(StringValue(&*jsstr));
161 }
162}
163
164impl FromJSValConvertible for ByteString {
166 type Config = ();
167 unsafe fn from_jsval(
168 cx: *mut JSContext,
169 value: HandleValue,
170 _option: (),
171 ) -> Result<ConversionResult<ByteString>, ()> {
172 let string = ToString(cx, value);
173 if string.is_null() {
174 debug!("ToString failed");
175 return Err(());
176 }
177
178 let latin1 = JS_DeprecatedStringHasLatin1Chars(string);
179 if latin1 {
180 let mut length = 0;
181 let chars = JS_GetLatin1StringCharsAndLength(cx, ptr::null(), string, &mut length);
182 assert!(!chars.is_null());
183
184 let char_slice = slice::from_raw_parts(chars as *mut u8, length);
185 return Ok(ConversionResult::Success(ByteString::new(
186 char_slice.to_vec(),
187 )));
188 }
189
190 let mut length = 0;
191 let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), string, &mut length);
192 let char_vec = slice::from_raw_parts(chars, length);
193
194 if char_vec.iter().any(|&c| c > 0xFF) {
195 throw_type_error(cx, "Invalid ByteString");
196 Err(())
197 } else {
198 Ok(ConversionResult::Success(ByteString::new(
199 char_vec.iter().map(|&c| c as u8).collect(),
200 )))
201 }
202 }
203}
204
205impl ToJSValConvertible for Reflector {
206 unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
207 let obj = self.get_jsobject().get();
208 assert!(!obj.is_null());
209 rval.set(ObjectValue(obj));
210 maybe_wrap_value(cx, rval);
211 }
212}
213
214impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> {
215 type Config = ();
216
217 unsafe fn from_jsval(
218 cx: *mut JSContext,
219 value: HandleValue,
220 _config: Self::Config,
221 ) -> Result<ConversionResult<DomRoot<T>>, ()> {
222 Ok(
223 match root_from_handlevalue(value, SafeJSContext::from_ptr(cx)) {
224 Ok(result) => ConversionResult::Success(result),
225 Err(()) => ConversionResult::Failure("value is not an object".into()),
226 },
227 )
228 }
229}
230
231impl<T: DomObject> ToJSValConvertible for DomRoot<T> {
232 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
233 self.reflector().to_jsval(cx, rval);
234 }
235}
236
237#[allow(clippy::result_unit_err)]
242pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> {
243 let clasp = get_object_class(obj);
244 if is_dom_class(&*clasp) {
245 trace!("plain old dom object");
246 let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
247 return Ok(&(*domjsclass).dom_class);
248 }
249 if is_dom_proxy(obj) {
250 trace!("proxy dom object");
251 let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
252 if dom_class.is_null() {
253 return Err(());
254 }
255 return Ok(&*dom_class);
256 }
257 trace!("not a dom object");
258 Err(())
259}
260
261pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool {
266 unsafe {
267 let clasp = get_object_class(obj);
268 ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj)
269 }
270}
271
272pub const DOM_OBJECT_SLOT: u32 = 0;
277
278pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
283 let mut value = UndefinedValue();
284 if is_dom_object(obj) {
285 JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
286 } else {
287 debug_assert!(is_dom_proxy(obj));
288 GetProxyReservedSlot(obj, 0, &mut value);
289 };
290 if value.is_undefined() {
291 ptr::null()
292 } else {
293 value.to_private()
294 }
295}
296
297pub enum PrototypeCheck {
298 Derive(fn(&'static DOMClass) -> bool),
299 Depth { depth: usize, proto_id: u16 },
300}
301
302#[inline]
313#[allow(clippy::result_unit_err)]
314pub unsafe fn private_from_proto_check(
315 mut obj: *mut JSObject,
316 cx: *mut JSContext,
317 proto_check: PrototypeCheck,
318) -> Result<*const libc::c_void, ()> {
319 let dom_class = get_dom_class(obj).or_else(|_| {
320 if IsWrapper(obj) {
321 trace!("found wrapper");
322 obj = UnwrapObjectDynamic(obj, cx, false);
323 if obj.is_null() {
324 trace!("unwrapping security wrapper failed");
325 Err(())
326 } else {
327 assert!(!IsWrapper(obj));
328 trace!("unwrapped successfully");
329 get_dom_class(obj)
330 }
331 } else {
332 trace!("not a dom wrapper");
333 Err(())
334 }
335 })?;
336
337 let prototype_matches = match proto_check {
338 PrototypeCheck::Derive(f) => (f)(dom_class),
339 PrototypeCheck::Depth { depth, proto_id } => {
340 dom_class.interface_chain[depth] as u16 == proto_id
341 },
342 };
343
344 if prototype_matches {
345 trace!("good prototype");
346 Ok(private_from_object(obj))
347 } else {
348 trace!("bad prototype");
349 Err(())
350 }
351}
352
353#[allow(clippy::result_unit_err)]
359pub unsafe fn native_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()>
360where
361 T: DomObject + IDLInterface,
362{
363 unsafe {
364 private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives))
365 .map(|ptr| ptr as *const T)
366 }
367}
368
369#[allow(clippy::result_unit_err)]
380pub unsafe fn root_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<DomRoot<T>, ()>
381where
382 T: DomObject + IDLInterface,
383{
384 native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
385}
386
387#[allow(clippy::result_unit_err)]
393pub fn root_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<DomRoot<T>, ()>
394where
395 T: DomObject + IDLInterface,
396{
397 if !v.get().is_object() {
398 return Err(());
399 }
400 #[expect(unsafe_code)]
401 unsafe {
402 root_from_object(v.get().to_object(), *cx)
403 }
404}
405
406pub unsafe fn jsid_to_string(cx: *mut JSContext, id: HandleId) -> Option<DOMString> {
414 let id_raw = *id;
415 if id_raw.is_string() {
416 let jsstr = std::ptr::NonNull::new(id_raw.to_string()).unwrap();
417 return Some(DOMString::from_string(jsstr_to_string(cx, jsstr)));
418 }
419
420 if id_raw.is_int() {
421 return Some(id_raw.to_int().to_string().into());
422 }
423
424 None
425}
426
427impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
428 #[inline]
429 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
430 let value = **self;
431 value.to_jsval(cx, rval);
432 }
433}
434
435impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
436 type Config = ();
437
438 unsafe fn from_jsval(
439 cx: *mut JSContext,
440 value: HandleValue,
441 option: (),
442 ) -> Result<ConversionResult<Finite<T>>, ()> {
443 let result = match FromJSValConvertible::from_jsval(cx, value, option)? {
444 ConversionResult::Success(v) => v,
445 ConversionResult::Failure(error) => {
446 throw_type_error(cx, &error);
448 return Err(());
449 },
450 };
451 match Finite::new(result) {
452 Some(v) => Ok(ConversionResult::Success(v)),
453 None => {
454 throw_type_error(cx, "this argument is not a finite floating-point value");
455 Err(())
456 },
457 }
458 }
459}
460
461#[inline]
467#[allow(clippy::result_unit_err)]
468unsafe fn private_from_proto_check_static(
469 obj: *mut JSObject,
470 proto_check: fn(&'static DOMClass) -> bool,
471) -> Result<*const libc::c_void, ()> {
472 let dom_class = get_dom_class(obj).map_err(|_| ())?;
473 if proto_check(dom_class) {
474 trace!("good prototype");
475 Ok(private_from_object(obj))
476 } else {
477 trace!("bad prototype");
478 Err(())
479 }
480}
481
482#[allow(clippy::result_unit_err)]
488pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
489where
490 T: DomObject + IDLInterface,
491{
492 private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T)
493}
494
495#[allow(clippy::result_unit_err)]
501pub fn native_from_handlevalue<T>(v: HandleValue, cx: SafeJSContext) -> Result<*const T, ()>
502where
503 T: DomObject + IDLInterface,
504{
505 if !v.get().is_object() {
506 return Err(());
507 }
508
509 #[expect(unsafe_code)]
510 unsafe {
511 native_from_object(v.get().to_object(), *cx)
512 }
513}
514
515impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
516 #[inline]
517 unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
518 let value = &**self;
519 value.to_jsval(cx, rval);
520 }
521}
522
523impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
524where
525 T: FromJSValConvertible + js::rust::GCMethods + Copy,
526 Heap<T>: JSTraceable + Default,
527{
528 type Config = T::Config;
529
530 unsafe fn from_jsval(
531 cx: *mut JSContext,
532 value: HandleValue,
533 config: Self::Config,
534 ) -> Result<ConversionResult<Self>, ()> {
535 T::from_jsval(cx, value, config).map(|result| match result {
536 ConversionResult::Success(inner) => {
537 ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
538 },
539 ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
540 })
541 }
542}
543
544pub unsafe fn is_array_like<D: crate::DomTypes>(cx: *mut JSContext, value: HandleValue) -> bool {
551 let mut is_array = false;
552 assert!(IsArrayObject(cx, value, &mut is_array));
553 if is_array {
554 return true;
555 }
556
557 let object: *mut JSObject = match FromJSValConvertible::from_jsval(cx, value, ()).unwrap() {
558 ConversionResult::Success(object) => object,
559 _ => return false,
560 };
561
562 if root_from_object::<D::DOMTokenList>(object, cx).is_ok() {
564 return true;
565 }
566 if root_from_object::<D::FileList>(object, cx).is_ok() {
567 return true;
568 }
569 if root_from_object::<D::HTMLCollection>(object, cx).is_ok() {
570 return true;
571 }
572 if root_from_object::<D::HTMLFormControlsCollection>(object, cx).is_ok() {
573 return true;
574 }
575 if root_from_object::<D::HTMLOptionsCollection>(object, cx).is_ok() {
576 return true;
577 }
578 if root_from_object::<D::NodeList>(object, cx).is_ok() {
579 return true;
580 }
581
582 false
583}
584
585pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
588 v: HandleValue,
589 _cx: SafeJSContext,
590) -> Result<DomRoot<D::WindowProxy>, ()> {
591 if !v.get().is_object() {
592 return Err(());
593 }
594 let object = v.get().to_object();
595 if !IsWindowProxy(object) {
596 return Err(());
597 }
598 let mut value = UndefinedValue();
599 GetProxyReservedSlot(object, 0, &mut value);
600 let ptr = value.to_private() as *const D::WindowProxy;
601 Ok(DomRoot::from_ref(&*ptr))
602}
603
604#[allow(deprecated)]
605impl<D: crate::DomTypes> EventModifierInit<D> {
606 pub fn modifiers(&self) -> Modifiers {
607 let mut modifiers = Modifiers::empty();
608 if self.altKey {
609 modifiers.insert(Modifiers::ALT);
610 }
611 if self.ctrlKey {
612 modifiers.insert(Modifiers::CONTROL);
613 }
614 if self.shiftKey {
615 modifiers.insert(Modifiers::SHIFT);
616 }
617 if self.metaKey {
618 modifiers.insert(Modifiers::META);
619 }
620 if self.keyModifierStateAltGraph {
621 modifiers.insert(Modifiers::ALT_GRAPH);
622 }
623 if self.keyModifierStateCapsLock {
624 modifiers.insert(Modifiers::CAPS_LOCK);
625 }
626 if self.keyModifierStateFn {
627 modifiers.insert(Modifiers::FN);
628 }
629 if self.keyModifierStateFnLock {
630 modifiers.insert(Modifiers::FN_LOCK);
631 }
632 if self.keyModifierStateHyper {
633 modifiers.insert(Modifiers::HYPER);
634 }
635 if self.keyModifierStateNumLock {
636 modifiers.insert(Modifiers::NUM_LOCK);
637 }
638 if self.keyModifierStateScrollLock {
639 modifiers.insert(Modifiers::SCROLL_LOCK);
640 }
641 if self.keyModifierStateSuper {
642 modifiers.insert(Modifiers::SUPER);
643 }
644 if self.keyModifierStateSymbol {
645 modifiers.insert(Modifiers::SYMBOL);
646 }
647 if self.keyModifierStateSymbolLock {
648 modifiers.insert(Modifiers::SYMBOL_LOCK);
649 }
650 modifiers
651 }
652}