1use std::{ptr, slice};
6
7use js::context::JSContext;
8use js::conversions::{
9 ConversionResult, FromJSValConvertible, ToJSValConvertible, jsstr_to_string,
10};
11use js::error::throw_type_error;
12use js::glue::{
13 GetProxyHandlerExtra, GetProxyReservedSlot, IsProxyHandlerFamily, IsWrapper, JS_GetReservedSlot,
14};
15use js::jsapi::{Heap, IsWindowProxy, JS_DeprecatedStringHasLatin1Chars, JSObject};
16use js::jsval::{ObjectValue, StringValue, UndefinedValue};
17use js::rust::wrappers2::{
18 IsArrayObject, JS_GetLatin1StringCharsAndLength, JS_GetTwoByteStringCharsAndLength,
19 JS_NewStringCopyN, UnwrapObjectDynamic,
20};
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::str::{ByteString, DOMString, USVString};
35use crate::trace::RootedTraceableBox;
36use crate::utils::{DOMClass, DOMJSClass};
37
38pub trait IDLInterface {
40 fn derives(_: &'static DOMClass) -> bool;
42
43 const PROTO_FIRST: u16 = 0;
45 const PROTO_LAST: u16 = u16::MAX;
47}
48
49pub trait DerivedFrom<T: Castable>: Castable {}
51
52impl ToJSValConvertible for USVString {
54 fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
55 self.0.to_jsval(cx, rval);
56 }
57}
58
59#[derive(Clone, PartialEq)]
61pub enum StringificationBehavior {
62 Default,
64 Empty,
66}
67
68impl FromJSValConvertible for DOMString {
70 type Config = StringificationBehavior;
71
72 fn from_jsval(
73 cx: &mut JSContext,
74 value: HandleValue,
75 null_behavior: StringificationBehavior,
76 ) -> Result<ConversionResult<DOMString>, ()> {
77 if null_behavior == StringificationBehavior::Empty && value.get().is_null() {
78 Ok(ConversionResult::Success(DOMString::new()))
79 } else {
80 match DOMString::from_js_string(cx, value) {
81 Ok(domstring) => Ok(ConversionResult::Success(domstring)),
82 Err(_) => Err(()),
83 }
84 }
85 }
86}
87
88impl FromJSValConvertible for USVString {
90 type Config = ();
91
92 fn from_jsval(
93 cx: &mut JSContext,
94 value: HandleValue,
95 _: (),
96 ) -> Result<ConversionResult<USVString>, ()> {
97 let Some(jsstr) = ptr::NonNull::new(unsafe { ToString(cx, value) }) else {
98 debug!("ToString failed");
99 return Err(());
100 };
101
102 Ok(ConversionResult::Success(USVString(unsafe {
104 jsstr_to_string(cx, jsstr)
105 })))
106 }
107}
108
109impl ToJSValConvertible for ByteString {
111 fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
112 let jsstr = unsafe {
113 JS_NewStringCopyN(
114 cx,
115 self.as_ptr() as *const libc::c_char,
116 self.len() as libc::size_t,
117 )
118 };
119 if jsstr.is_null() {
120 panic!("JS_NewStringCopyN failed");
121 }
122 unsafe { rval.set(StringValue(&*jsstr)) };
123 }
124}
125
126impl FromJSValConvertible for ByteString {
128 type Config = ();
129
130 fn from_jsval(
131 cx: &mut JSContext,
132 value: HandleValue,
133 _option: (),
134 ) -> Result<ConversionResult<ByteString>, ()> {
135 unsafe {
136 let string = ToString(cx, value);
137 if string.is_null() {
138 debug!("ToString failed");
139 return Err(());
140 }
141
142 let latin1 = JS_DeprecatedStringHasLatin1Chars(string);
143 if latin1 {
144 let mut length = 0;
145 let chars = JS_GetLatin1StringCharsAndLength(cx, string, &mut length);
146 assert!(!chars.is_null());
147
148 let char_slice = slice::from_raw_parts(chars as *mut u8, length);
149 return Ok(ConversionResult::Success(ByteString::new(
150 char_slice.to_vec(),
151 )));
152 }
153
154 let mut length = 0;
155 let chars = JS_GetTwoByteStringCharsAndLength(cx, string, &mut length);
156 let char_vec = slice::from_raw_parts(chars, length);
157
158 if char_vec.iter().any(|&c| c > 0xFF) {
159 throw_type_error(cx, c"Invalid ByteString");
160 Err(())
161 } else {
162 Ok(ConversionResult::Success(ByteString::new(
163 char_vec.iter().map(|&c| c as u8).collect(),
164 )))
165 }
166 }
167 }
168}
169
170impl<T> ToJSValConvertible for Reflector<T> {
171 fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
172 let obj = self.get_jsobject().get();
173 assert!(!obj.is_null());
174 rval.set(ObjectValue(obj));
175 maybe_wrap_value(cx, rval);
176 }
177}
178
179impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> {
180 type Config = ();
181
182 fn from_jsval(
183 cx: &mut JSContext,
184 value: HandleValue,
185 _config: Self::Config,
186 ) -> Result<ConversionResult<DomRoot<T>>, ()> {
187 Ok(match root_from_handlevalue(cx, value) {
188 Ok(result) => ConversionResult::Success(result),
189 Err(()) => ConversionResult::Failure(c"value is not an object".into()),
190 })
191 }
192}
193
194impl<T: DomObject> ToJSValConvertible for DomRoot<T> {
195 fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
196 self.reflector().to_jsval(cx, rval);
197 }
198}
199
200#[allow(clippy::result_unit_err)]
205pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> {
206 unsafe {
207 let clasp = get_object_class(obj);
208 if is_dom_class(&*clasp) {
209 trace!("plain old dom object");
210 let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
211 return Ok(&(*domjsclass).dom_class);
212 }
213 if is_dom_proxy(obj) {
214 trace!("proxy dom object");
215 let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
216 if dom_class.is_null() {
217 return Err(());
218 }
219 return Ok(&*dom_class);
220 }
221 }
222 trace!("not a dom object");
223 Err(())
224}
225
226pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool {
231 unsafe {
232 let clasp = get_object_class(obj);
233 ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj)
234 }
235}
236
237pub const DOM_OBJECT_SLOT: u32 = 0;
242
243pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
248 let mut value = UndefinedValue();
249 unsafe {
250 if is_dom_object(obj) {
251 JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
252 } else {
253 debug_assert!(is_dom_proxy(obj));
254 GetProxyReservedSlot(obj, 0, &mut value);
255 };
256 }
257 if value.is_undefined() {
258 ptr::null()
259 } else {
260 value.to_private()
261 }
262}
263
264pub enum PrototypeCheck {
265 Derive(fn(&'static DOMClass) -> bool),
266 Depth { depth: usize, proto_id: u16 },
267}
268
269#[inline]
280#[allow(clippy::result_unit_err)]
281pub unsafe fn private_from_proto_check(
282 cx: &mut JSContext,
283 mut obj: *mut JSObject,
284 proto_check: PrototypeCheck,
285) -> Result<*const libc::c_void, ()> {
286 let dom_class = unsafe {
287 get_dom_class(obj).or_else(|_| {
288 if IsWrapper(obj) {
289 trace!("found wrapper");
290 obj = UnwrapObjectDynamic(obj, cx, false);
291 if obj.is_null() {
292 trace!("unwrapping security wrapper failed");
293 Err(())
294 } else {
295 assert!(!IsWrapper(obj));
296 trace!("unwrapped successfully");
297 get_dom_class(obj)
298 }
299 } else {
300 trace!("not a dom wrapper");
301 Err(())
302 }
303 })?
304 };
305
306 let prototype_matches = match proto_check {
307 PrototypeCheck::Derive(f) => (f)(dom_class),
308 PrototypeCheck::Depth { depth, proto_id } => {
309 dom_class.interface_chain[depth] as u16 == proto_id
310 },
311 };
312
313 if prototype_matches {
314 trace!("good prototype");
315 Ok(unsafe { private_from_object(obj) })
316 } else {
317 trace!("bad prototype");
318 Err(())
319 }
320}
321
322#[allow(clippy::result_unit_err)]
328pub unsafe fn native_from_object<T>(cx: &mut JSContext, obj: *mut JSObject) -> Result<*const T, ()>
329where
330 T: DomObject + IDLInterface,
331{
332 unsafe {
333 private_from_proto_check(cx, obj, PrototypeCheck::Derive(T::derives))
334 .map(|ptr| ptr as *const T)
335 }
336}
337
338#[allow(clippy::result_unit_err)]
349pub unsafe fn root_from_object<T>(cx: &mut JSContext, obj: *mut JSObject) -> Result<DomRoot<T>, ()>
350where
351 T: DomObject + IDLInterface,
352{
353 unsafe { native_from_object(cx, obj).map(|ptr| DomRoot::from_ref(&*ptr)) }
354}
355
356#[allow(clippy::result_unit_err)]
362pub fn root_from_handlevalue<T>(cx: &mut JSContext, v: HandleValue) -> Result<DomRoot<T>, ()>
363where
364 T: DomObject + IDLInterface,
365{
366 if !v.get().is_object() {
367 return Err(());
368 }
369 #[expect(unsafe_code)]
370 unsafe {
371 root_from_object(cx, v.get().to_object())
372 }
373}
374
375pub fn jsid_to_string(cx: &js::context::JSContext, id: HandleId) -> Option<DOMString> {
380 let id_raw = *id;
381 if id_raw.is_string() {
382 let jsstr = ptr::NonNull::new(id_raw.to_string()).unwrap();
383 return Some(unsafe { jsstr_to_string(cx, jsstr) }.into());
384 }
385
386 if id_raw.is_int() {
387 return Some(id_raw.to_int().to_string().into());
388 }
389
390 None
391}
392
393impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
394 #[inline]
395 fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
396 let value = **self;
397 value.to_jsval(cx, rval);
398 }
399}
400
401impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
402 type Config = ();
403
404 fn from_jsval(
405 cx: &mut JSContext,
406 value: HandleValue,
407 option: (),
408 ) -> Result<ConversionResult<Finite<T>>, ()> {
409 let result = match FromJSValConvertible::from_jsval(cx, value, option)? {
410 ConversionResult::Success(v) => v,
411 ConversionResult::Failure(error) => {
412 throw_type_error(cx, &error);
414 return Err(());
415 },
416 };
417 match Finite::new(result) {
418 Some(v) => Ok(ConversionResult::Success(v)),
419 None => {
420 throw_type_error(cx, c"this argument is not a finite floating-point value");
421 Err(())
422 },
423 }
424 }
425}
426
427#[inline]
433#[allow(clippy::result_unit_err)]
434unsafe fn private_from_proto_check_static(
435 obj: *mut JSObject,
436 proto_check: fn(&'static DOMClass) -> bool,
437) -> Result<*const libc::c_void, ()> {
438 unsafe {
439 let dom_class = get_dom_class(obj).map_err(|_| ())?;
440 if proto_check(dom_class) {
441 trace!("good prototype");
442 Ok(private_from_object(obj))
443 } else {
444 trace!("bad prototype");
445 Err(())
446 }
447 }
448}
449
450#[allow(clippy::result_unit_err)]
456pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
457where
458 T: DomObject + IDLInterface,
459{
460 unsafe { private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T) }
461}
462
463#[allow(clippy::result_unit_err)]
469pub fn native_from_handlevalue<T>(cx: &mut JSContext, v: HandleValue) -> Result<*const T, ()>
470where
471 T: DomObject + IDLInterface,
472{
473 if !v.get().is_object() {
474 return Err(());
475 }
476
477 #[expect(unsafe_code)]
478 unsafe {
479 native_from_object(cx, v.get().to_object())
480 }
481}
482
483impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
484 #[inline]
485 fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
486 let value = &**self;
487 value.to_jsval(cx, rval);
488 }
489}
490
491impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
492where
493 T: FromJSValConvertible + js::rust::GCMethods + Copy,
494 Heap<T>: JSTraceable + Default,
495{
496 type Config = T::Config;
497
498 fn from_jsval(
499 cx: &mut JSContext,
500 value: HandleValue,
501 config: Self::Config,
502 ) -> Result<ConversionResult<Self>, ()> {
503 T::from_jsval(cx, value, config).map(|result| match result {
504 ConversionResult::Success(inner) => {
505 ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
506 },
507 ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
508 })
509 }
510}
511
512pub fn is_array_like<D: crate::DomTypes>(cx: &mut JSContext, value: HandleValue) -> bool {
516 let mut is_array = false;
517 assert!(unsafe { IsArrayObject(cx, value, &mut is_array) });
518 if is_array {
519 return true;
520 }
521
522 let object: *mut JSObject = match FromJSValConvertible::from_jsval(cx, value, ()).unwrap() {
523 ConversionResult::Success(object) => object,
524 _ => return false,
525 };
526
527 unsafe {
528 if root_from_object::<D::DOMTokenList>(cx, object).is_ok() {
530 return true;
531 }
532 if root_from_object::<D::FileList>(cx, object).is_ok() {
533 return true;
534 }
535 if root_from_object::<D::HTMLCollection>(cx, object).is_ok() {
536 return true;
537 }
538 if root_from_object::<D::HTMLFormControlsCollection>(cx, object).is_ok() {
539 return true;
540 }
541 if root_from_object::<D::HTMLOptionsCollection>(cx, object).is_ok() {
542 return true;
543 }
544 if root_from_object::<D::NodeList>(cx, object).is_ok() {
545 return true;
546 }
547 }
548
549 false
550}
551
552pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
555 v: HandleValue,
556) -> Result<DomRoot<D::WindowProxy>, ()> {
557 if !v.get().is_object() {
558 return Err(());
559 }
560 let object = v.get().to_object();
561 unsafe {
562 if !IsWindowProxy(object) {
563 return Err(());
564 }
565 let mut value = UndefinedValue();
566 GetProxyReservedSlot(object, 0, &mut value);
567 let ptr = value.to_private() as *const D::WindowProxy;
568 Ok(DomRoot::from_ref(&*ptr))
569 }
570}
571
572#[allow(deprecated)]
573impl<D: crate::DomTypes> EventModifierInit<D> {
574 pub fn modifiers(&self) -> Modifiers {
575 let mut modifiers = Modifiers::empty();
576 if self.altKey {
577 modifiers.insert(Modifiers::ALT);
578 }
579 if self.ctrlKey {
580 modifiers.insert(Modifiers::CONTROL);
581 }
582 if self.shiftKey {
583 modifiers.insert(Modifiers::SHIFT);
584 }
585 if self.metaKey {
586 modifiers.insert(Modifiers::META);
587 }
588 if self.keyModifierStateAltGraph {
589 modifiers.insert(Modifiers::ALT_GRAPH);
590 }
591 if self.keyModifierStateCapsLock {
592 modifiers.insert(Modifiers::CAPS_LOCK);
593 }
594 if self.keyModifierStateFn {
595 modifiers.insert(Modifiers::FN);
596 }
597 if self.keyModifierStateFnLock {
598 modifiers.insert(Modifiers::FN_LOCK);
599 }
600 if self.keyModifierStateHyper {
601 modifiers.insert(Modifiers::HYPER);
602 }
603 if self.keyModifierStateNumLock {
604 modifiers.insert(Modifiers::NUM_LOCK);
605 }
606 if self.keyModifierStateScrollLock {
607 modifiers.insert(Modifiers::SCROLL_LOCK);
608 }
609 if self.keyModifierStateSuper {
610 modifiers.insert(Modifiers::SUPER);
611 }
612 if self.keyModifierStateSymbol {
613 modifiers.insert(Modifiers::SYMBOL);
614 }
615 if self.keyModifierStateSymbolLock {
616 modifiers.insert(Modifiers::SYMBOL_LOCK);
617 }
618 modifiers
619 }
620}