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