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 safe_to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
55 self.0.safe_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 safe_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 safe_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 safe_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 safe_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.raw_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 safe_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 safe_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 safe_to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
196 self.reflector().safe_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 let clasp = get_object_class(obj);
207 if is_dom_class(&*clasp) {
208 trace!("plain old dom object");
209 let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
210 return Ok(&(*domjsclass).dom_class);
211 }
212 if is_dom_proxy(obj) {
213 trace!("proxy dom object");
214 let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
215 if dom_class.is_null() {
216 return Err(());
217 }
218 return Ok(&*dom_class);
219 }
220 trace!("not a dom object");
221 Err(())
222}
223
224pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool {
229 unsafe {
230 let clasp = get_object_class(obj);
231 ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj)
232 }
233}
234
235pub const DOM_OBJECT_SLOT: u32 = 0;
240
241pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
246 let mut value = UndefinedValue();
247 if is_dom_object(obj) {
248 JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
249 } else {
250 debug_assert!(is_dom_proxy(obj));
251 GetProxyReservedSlot(obj, 0, &mut value);
252 };
253 if value.is_undefined() {
254 ptr::null()
255 } else {
256 value.to_private()
257 }
258}
259
260pub enum PrototypeCheck {
261 Derive(fn(&'static DOMClass) -> bool),
262 Depth { depth: usize, proto_id: u16 },
263}
264
265#[inline]
276#[allow(clippy::result_unit_err)]
277pub unsafe fn private_from_proto_check(
278 cx: &mut JSContext,
279 mut obj: *mut JSObject,
280 proto_check: PrototypeCheck,
281) -> Result<*const libc::c_void, ()> {
282 let dom_class = get_dom_class(obj).or_else(|_| {
283 if IsWrapper(obj) {
284 trace!("found wrapper");
285 obj = UnwrapObjectDynamic(obj, cx, false);
286 if obj.is_null() {
287 trace!("unwrapping security wrapper failed");
288 Err(())
289 } else {
290 assert!(!IsWrapper(obj));
291 trace!("unwrapped successfully");
292 get_dom_class(obj)
293 }
294 } else {
295 trace!("not a dom wrapper");
296 Err(())
297 }
298 })?;
299
300 let prototype_matches = match proto_check {
301 PrototypeCheck::Derive(f) => (f)(dom_class),
302 PrototypeCheck::Depth { depth, proto_id } => {
303 dom_class.interface_chain[depth] as u16 == proto_id
304 },
305 };
306
307 if prototype_matches {
308 trace!("good prototype");
309 Ok(private_from_object(obj))
310 } else {
311 trace!("bad prototype");
312 Err(())
313 }
314}
315
316#[allow(clippy::result_unit_err)]
322pub unsafe fn native_from_object<T>(cx: &mut JSContext, obj: *mut JSObject) -> Result<*const T, ()>
323where
324 T: DomObject + IDLInterface,
325{
326 unsafe {
327 private_from_proto_check(cx, obj, PrototypeCheck::Derive(T::derives))
328 .map(|ptr| ptr as *const T)
329 }
330}
331
332#[allow(clippy::result_unit_err)]
343pub unsafe fn root_from_object<T>(cx: &mut JSContext, obj: *mut JSObject) -> Result<DomRoot<T>, ()>
344where
345 T: DomObject + IDLInterface,
346{
347 native_from_object(cx, obj).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
348}
349
350#[allow(clippy::result_unit_err)]
356pub fn root_from_handlevalue<T>(cx: &mut JSContext, v: HandleValue) -> Result<DomRoot<T>, ()>
357where
358 T: DomObject + IDLInterface,
359{
360 if !v.get().is_object() {
361 return Err(());
362 }
363 #[expect(unsafe_code)]
364 unsafe {
365 root_from_object(cx, v.get().to_object())
366 }
367}
368
369pub fn jsid_to_string(cx: &js::context::JSContext, id: HandleId) -> Option<DOMString> {
374 let id_raw = *id;
375 if id_raw.is_string() {
376 let jsstr = ptr::NonNull::new(id_raw.to_string()).unwrap();
377 return Some(unsafe { jsstr_to_string(cx, jsstr) }.into());
378 }
379
380 if id_raw.is_int() {
381 return Some(id_raw.to_int().to_string().into());
382 }
383
384 None
385}
386
387impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
388 #[inline]
389 fn safe_to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
390 let value = **self;
391 value.safe_to_jsval(cx, rval);
392 }
393}
394
395impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
396 type Config = ();
397
398 fn safe_from_jsval(
399 cx: &mut JSContext,
400 value: HandleValue,
401 option: (),
402 ) -> Result<ConversionResult<Finite<T>>, ()> {
403 let result = match FromJSValConvertible::safe_from_jsval(cx, value, option)? {
404 ConversionResult::Success(v) => v,
405 ConversionResult::Failure(error) => {
406 unsafe { throw_type_error(cx.raw_cx(), &error) };
408 return Err(());
409 },
410 };
411 match Finite::new(result) {
412 Some(v) => Ok(ConversionResult::Success(v)),
413 None => {
414 unsafe {
415 throw_type_error(
416 cx.raw_cx(),
417 c"this argument is not a finite floating-point value",
418 )
419 };
420 Err(())
421 },
422 }
423 }
424}
425
426#[inline]
432#[allow(clippy::result_unit_err)]
433unsafe fn private_from_proto_check_static(
434 obj: *mut JSObject,
435 proto_check: fn(&'static DOMClass) -> bool,
436) -> Result<*const libc::c_void, ()> {
437 let dom_class = get_dom_class(obj).map_err(|_| ())?;
438 if proto_check(dom_class) {
439 trace!("good prototype");
440 Ok(private_from_object(obj))
441 } else {
442 trace!("bad prototype");
443 Err(())
444 }
445}
446
447#[allow(clippy::result_unit_err)]
453pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
454where
455 T: DomObject + IDLInterface,
456{
457 private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T)
458}
459
460#[allow(clippy::result_unit_err)]
466pub fn native_from_handlevalue<T>(cx: &mut JSContext, v: HandleValue) -> Result<*const T, ()>
467where
468 T: DomObject + IDLInterface,
469{
470 if !v.get().is_object() {
471 return Err(());
472 }
473
474 #[expect(unsafe_code)]
475 unsafe {
476 native_from_object(cx, v.get().to_object())
477 }
478}
479
480impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
481 #[inline]
482 fn safe_to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
483 let value = &**self;
484 value.safe_to_jsval(cx, rval);
485 }
486}
487
488impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
489where
490 T: FromJSValConvertible + js::rust::GCMethods + Copy,
491 Heap<T>: JSTraceable + Default,
492{
493 type Config = T::Config;
494
495 fn safe_from_jsval(
496 cx: &mut JSContext,
497 value: HandleValue,
498 config: Self::Config,
499 ) -> Result<ConversionResult<Self>, ()> {
500 T::safe_from_jsval(cx, value, config).map(|result| match result {
501 ConversionResult::Success(inner) => {
502 ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
503 },
504 ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
505 })
506 }
507}
508
509pub fn is_array_like<D: crate::DomTypes>(cx: &mut JSContext, value: HandleValue) -> bool {
513 let mut is_array = false;
514 assert!(unsafe { IsArrayObject(cx, value, &mut is_array) });
515 if is_array {
516 return true;
517 }
518
519 let object: *mut JSObject = match FromJSValConvertible::safe_from_jsval(cx, value, ()).unwrap()
520 {
521 ConversionResult::Success(object) => object,
522 _ => return false,
523 };
524
525 unsafe {
526 if root_from_object::<D::DOMTokenList>(cx, object).is_ok() {
528 return true;
529 }
530 if root_from_object::<D::FileList>(cx, object).is_ok() {
531 return true;
532 }
533 if root_from_object::<D::HTMLCollection>(cx, object).is_ok() {
534 return true;
535 }
536 if root_from_object::<D::HTMLFormControlsCollection>(cx, object).is_ok() {
537 return true;
538 }
539 if root_from_object::<D::HTMLOptionsCollection>(cx, object).is_ok() {
540 return true;
541 }
542 if root_from_object::<D::NodeList>(cx, object).is_ok() {
543 return true;
544 }
545 }
546
547 false
548}
549
550pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>(
553 v: HandleValue,
554) -> Result<DomRoot<D::WindowProxy>, ()> {
555 if !v.get().is_object() {
556 return Err(());
557 }
558 let object = v.get().to_object();
559 if !IsWindowProxy(object) {
560 return Err(());
561 }
562 let mut value = UndefinedValue();
563 GetProxyReservedSlot(object, 0, &mut value);
564 let ptr = value.to_private() as *const D::WindowProxy;
565 Ok(DomRoot::from_ref(&*ptr))
566}
567
568#[allow(deprecated)]
569impl<D: crate::DomTypes> EventModifierInit<D> {
570 pub fn modifiers(&self) -> Modifiers {
571 let mut modifiers = Modifiers::empty();
572 if self.altKey {
573 modifiers.insert(Modifiers::ALT);
574 }
575 if self.ctrlKey {
576 modifiers.insert(Modifiers::CONTROL);
577 }
578 if self.shiftKey {
579 modifiers.insert(Modifiers::SHIFT);
580 }
581 if self.metaKey {
582 modifiers.insert(Modifiers::META);
583 }
584 if self.keyModifierStateAltGraph {
585 modifiers.insert(Modifiers::ALT_GRAPH);
586 }
587 if self.keyModifierStateCapsLock {
588 modifiers.insert(Modifiers::CAPS_LOCK);
589 }
590 if self.keyModifierStateFn {
591 modifiers.insert(Modifiers::FN);
592 }
593 if self.keyModifierStateFnLock {
594 modifiers.insert(Modifiers::FN_LOCK);
595 }
596 if self.keyModifierStateHyper {
597 modifiers.insert(Modifiers::HYPER);
598 }
599 if self.keyModifierStateNumLock {
600 modifiers.insert(Modifiers::NUM_LOCK);
601 }
602 if self.keyModifierStateScrollLock {
603 modifiers.insert(Modifiers::SCROLL_LOCK);
604 }
605 if self.keyModifierStateSuper {
606 modifiers.insert(Modifiers::SUPER);
607 }
608 if self.keyModifierStateSymbol {
609 modifiers.insert(Modifiers::SYMBOL);
610 }
611 if self.keyModifierStateSymbolLock {
612 modifiers.insert(Modifiers::SYMBOL_LOCK);
613 }
614 modifiers
615 }
616}