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