1use std::ffi::CStr;
6use std::os::raw::{c_char, c_void};
7use std::ptr::{self, NonNull};
8use std::slice;
9
10use js::conversions::{ToJSValConvertible, jsstr_to_string};
11use js::gc::Handle;
12use js::glue::{
13 AppendToIdVector, CallJitGetterOp, CallJitMethodOp, CallJitSetterOp, JS_GetReservedSlot,
14 RUST_FUNCTION_VALUE_TO_JITINFO,
15};
16use js::jsapi::{
17 AtomToLinearString, CallArgs, ExceptionStackBehavior, GetLinearStringCharAt,
18 GetLinearStringLength, GetNonCCWObjectGlobal, HandleId as RawHandleId,
19 HandleObject as RawHandleObject, Heap, JS_AtomizeStringN, JS_ClearPendingException,
20 JS_DeprecatedStringHasLatin1Chars, JS_GetLatin1StringCharsAndLength, JS_IsExceptionPending,
21 JS_IsGlobalObject, JS_MayResolveStandardClass, JS_NewEnumerateStandardClasses,
22 JS_ResolveStandardClass, JSAtom, JSAtomState, JSContext, JSJitInfo, JSObject, JSPROP_ENUMERATE,
23 JSTracer, MutableHandleIdVector as RawMutableHandleIdVector,
24 MutableHandleValue as RawMutableHandleValue, ObjectOpResult, PropertyKey, StringIsArrayIndex,
25 jsid,
26};
27use js::jsid::StringId;
28use js::jsval::{JSVal, UndefinedValue};
29use js::rust::wrappers::{
30 CallOriginalPromiseReject, JS_DefineProperty, JS_DeletePropertyById, JS_ForwardGetPropertyTo,
31 JS_GetPendingException, JS_GetProperty, JS_GetPrototype, JS_HasOwnProperty, JS_HasProperty,
32 JS_HasPropertyById, JS_SetPendingException, JS_SetProperty,
33};
34use js::rust::{
35 HandleId, HandleObject, HandleValue, MutableHandleValue, Runtime, ToString, get_object_class,
36};
37use js::{JS_CALLEE, rooted};
38use malloc_size_of::MallocSizeOfOps;
39
40use crate::DomTypes;
41use crate::codegen::Globals::Globals;
42use crate::codegen::InheritTypes::TopTypeId;
43use crate::codegen::PrototypeList::{self, MAX_PROTO_CHAIN_LENGTH, PROTO_OR_IFACE_LENGTH};
44use crate::conversions::{PrototypeCheck, private_from_proto_check};
45use crate::error::throw_invalid_this;
46use crate::interfaces::DomHelpers;
47use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
48use crate::str::DOMString;
49use crate::trace::trace_object;
50
51#[derive(Clone, Copy)]
53pub struct DOMClass {
54 pub interface_chain: [PrototypeList::ID; MAX_PROTO_CHAIN_LENGTH],
57
58 pub depth: u8,
60
61 pub type_id: TopTypeId,
63
64 pub malloc_size_of: unsafe fn(ops: &mut MallocSizeOfOps, *const c_void) -> usize,
66
67 pub global: Globals,
69}
70unsafe impl Sync for DOMClass {}
71
72#[derive(Copy)]
74#[repr(C)]
75pub struct DOMJSClass {
76 pub base: js::jsapi::JSClass,
78 pub dom_class: DOMClass,
80}
81impl Clone for DOMJSClass {
82 fn clone(&self) -> DOMJSClass {
83 *self
84 }
85}
86unsafe impl Sync for DOMJSClass {}
87
88pub(crate) const DOM_PROTO_UNFORGEABLE_HOLDER_SLOT: u32 = 0;
91
92pub(crate) const DOM_PROTOTYPE_SLOT: u32 = js::JSCLASS_GLOBAL_SLOT_COUNT;
95
96pub(crate) const JSCLASS_DOM_GLOBAL: u32 = js::JSCLASS_USERBIT1;
101
102pub(crate) unsafe fn get_proto_or_iface_array(global: *mut JSObject) -> *mut ProtoOrIfaceArray {
108 assert_ne!(((*get_object_class(global)).flags & JSCLASS_DOM_GLOBAL), 0);
109 let mut slot = UndefinedValue();
110 JS_GetReservedSlot(global, DOM_PROTOTYPE_SLOT, &mut slot);
111 slot.to_private() as *mut ProtoOrIfaceArray
112}
113
114pub type ProtoOrIfaceArray = [*mut JSObject; PROTO_OR_IFACE_LENGTH];
116
117pub(crate) unsafe fn get_property_on_prototype(
126 cx: *mut JSContext,
127 proxy: HandleObject,
128 receiver: HandleValue,
129 id: HandleId,
130 found: *mut bool,
131 vp: MutableHandleValue,
132) -> bool {
133 rooted!(in(cx) let mut proto = ptr::null_mut::<JSObject>());
134 if !JS_GetPrototype(cx, proxy, proto.handle_mut()) || proto.is_null() {
135 *found = false;
136 return true;
137 }
138 let mut has_property = false;
139 if !JS_HasPropertyById(cx, proto.handle(), id, &mut has_property) {
140 return false;
141 }
142 *found = has_property;
143 if !has_property {
144 return true;
145 }
146
147 JS_ForwardGetPropertyTo(cx, proto.handle(), id, receiver, vp)
148}
149
150pub fn get_array_index_from_id(id: HandleId) -> Option<u32> {
153 let raw_id = *id;
154 if raw_id.is_int() {
155 return Some(raw_id.to_int() as u32);
156 }
157
158 if raw_id.is_void() || !raw_id.is_string() {
159 return None;
160 }
161
162 unsafe {
163 let atom = raw_id.to_string() as *mut JSAtom;
164 let s = AtomToLinearString(atom);
165 if GetLinearStringLength(s) == 0 {
166 return None;
167 }
168
169 let chars = [GetLinearStringCharAt(s, 0)];
170 let first_char = char::decode_utf16(chars.iter().cloned())
171 .next()
172 .map_or('\0', |r| r.unwrap_or('\0'));
173 if first_char.is_ascii_lowercase() {
174 return None;
175 }
176
177 let mut i = 0;
178 if StringIsArrayIndex(s, &mut i) {
179 Some(i)
180 } else {
181 None
182 }
183 }
184
185 }
211
212#[allow(clippy::result_unit_err)]
219pub(crate) unsafe fn find_enum_value<'a, T>(
220 cx: *mut JSContext,
221 v: HandleValue,
222 pairs: &'a [(&'static str, T)],
223) -> Result<(Option<&'a T>, DOMString), ()> {
224 match ptr::NonNull::new(ToString(cx, v)) {
225 Some(jsstr) => {
226 let search = jsstr_to_string(cx, jsstr).into();
227 Ok((
228 pairs
229 .iter()
230 .find(|&&(key, _)| search == key)
231 .map(|(_, ev)| ev),
232 search,
233 ))
234 },
235 None => Err(()),
236 }
237}
238
239#[allow(clippy::result_unit_err)]
246pub unsafe fn get_dictionary_property(
247 cx: *mut JSContext,
248 object: HandleObject,
249 property: &CStr,
250 rval: MutableHandleValue,
251 _can_gc: CanGc,
252) -> Result<bool, ()> {
253 unsafe fn has_property(
254 cx: *mut JSContext,
255 object: HandleObject,
256 property: &CStr,
257 found: &mut bool,
258 ) -> bool {
259 JS_HasProperty(cx, object, property.as_ptr(), found)
260 }
261 unsafe fn get_property(
262 cx: *mut JSContext,
263 object: HandleObject,
264 property: &CStr,
265 value: MutableHandleValue,
266 ) -> bool {
267 JS_GetProperty(cx, object, property.as_ptr(), value)
268 }
269
270 if object.get().is_null() {
271 return Ok(false);
272 }
273
274 let mut found = false;
275 if !has_property(cx, object, property, &mut found) {
276 return Err(());
277 }
278
279 if !found {
280 return Ok(false);
281 }
282
283 if !get_property(cx, object, property, rval) {
284 return Err(());
285 }
286
287 Ok(true)
288}
289
290#[allow(clippy::result_unit_err)]
294pub fn set_dictionary_property(
295 cx: SafeJSContext,
296 object: HandleObject,
297 property: &CStr,
298 value: HandleValue,
299) -> Result<(), ()> {
300 if object.get().is_null() {
301 return Err(());
302 }
303
304 unsafe {
305 if !JS_SetProperty(*cx, object, property.as_ptr(), value) {
306 return Err(());
307 }
308 }
309
310 Ok(())
311}
312
313#[allow(clippy::result_unit_err)]
317pub fn define_dictionary_property(
318 cx: SafeJSContext,
319 object: HandleObject,
320 property: &CStr,
321 value: HandleValue,
322) -> Result<(), ()> {
323 if object.get().is_null() {
324 return Err(());
325 }
326
327 unsafe {
328 if !JS_DefineProperty(
329 *cx,
330 object,
331 property.as_ptr(),
332 value,
333 JSPROP_ENUMERATE as u32,
334 ) {
335 return Err(());
336 }
337 }
338
339 Ok(())
340}
341
342#[allow(clippy::result_unit_err)]
346pub fn has_own_property(
347 cx: SafeJSContext,
348 object: HandleObject,
349 property: &CStr,
350) -> Result<bool, ()> {
351 if object.get().is_null() {
352 return Ok(false);
353 }
354
355 let mut found = false;
356 unsafe {
357 if !JS_HasOwnProperty(*cx, object, property.as_ptr(), &mut found) {
358 return Err(());
359 }
360 }
361
362 Ok(found)
363}
364
365pub unsafe fn has_property_on_prototype(
374 cx: *mut JSContext,
375 proxy: HandleObject,
376 id: HandleId,
377 found: &mut bool,
378) -> bool {
379 rooted!(in(cx) let mut proto = ptr::null_mut::<JSObject>());
380 if !JS_GetPrototype(cx, proxy, proto.handle_mut()) {
381 return false;
382 }
383 assert!(!proto.is_null());
384 JS_HasPropertyById(cx, proto.handle(), id, found)
385}
386
387pub(crate) unsafe fn delete_property_by_id(
392 cx: *mut JSContext,
393 object: HandleObject,
394 id: HandleId,
395 bp: *mut ObjectOpResult,
396) -> bool {
397 JS_DeletePropertyById(cx, object, id, bp)
398}
399
400unsafe fn generic_call<const EXCEPTION_TO_REJECTION: bool>(
401 cx: *mut JSContext,
402 argc: libc::c_uint,
403 vp: *mut JSVal,
404 is_lenient: bool,
405 call: unsafe extern "C" fn(
406 *const JSJitInfo,
407 *mut JSContext,
408 RawHandleObject,
409 *mut libc::c_void,
410 u32,
411 *mut JSVal,
412 ) -> bool,
413 can_gc: CanGc,
414) -> bool {
415 let args = CallArgs::from_vp(vp, argc);
416
417 let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));
418 let proto_id = (*info).__bindgen_anon_2.protoID;
419 let cx = SafeJSContext::from_ptr(cx);
420
421 let thisobj = args.thisv();
422 if !thisobj.get().is_null_or_undefined() && !thisobj.get().is_object() {
423 throw_invalid_this(cx, proto_id);
424 return if EXCEPTION_TO_REJECTION {
425 exception_to_promise(*cx, args.rval(), can_gc)
426 } else {
427 false
428 };
429 }
430
431 rooted!(in(*cx) let obj = if thisobj.get().is_object() {
432 thisobj.get().to_object()
433 } else {
434 GetNonCCWObjectGlobal(JS_CALLEE(*cx, vp).to_object_or_null())
435 });
436 let depth = (*info).__bindgen_anon_3.depth as usize;
437 let proto_check = PrototypeCheck::Depth { depth, proto_id };
438 let this = match private_from_proto_check(obj.get(), *cx, proto_check) {
439 Ok(val) => val,
440 Err(()) => {
441 if is_lenient {
442 debug_assert!(!JS_IsExceptionPending(*cx));
443 *vp = UndefinedValue();
444 return true;
445 } else {
446 throw_invalid_this(cx, proto_id);
447 return if EXCEPTION_TO_REJECTION {
448 exception_to_promise(*cx, args.rval(), can_gc)
449 } else {
450 false
451 };
452 }
453 },
454 };
455 call(
456 info,
457 *cx,
458 obj.handle().into(),
459 this as *mut libc::c_void,
460 argc,
461 vp,
462 )
463}
464
465pub(crate) unsafe extern "C" fn generic_method<const EXCEPTION_TO_REJECTION: bool>(
471 cx: *mut JSContext,
472 argc: libc::c_uint,
473 vp: *mut JSVal,
474) -> bool {
475 generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, false, CallJitMethodOp, CanGc::note())
476}
477
478pub(crate) unsafe extern "C" fn generic_getter<const EXCEPTION_TO_REJECTION: bool>(
484 cx: *mut JSContext,
485 argc: libc::c_uint,
486 vp: *mut JSVal,
487) -> bool {
488 generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, false, CallJitGetterOp, CanGc::note())
489}
490
491pub(crate) unsafe extern "C" fn generic_lenient_getter<const EXCEPTION_TO_REJECTION: bool>(
497 cx: *mut JSContext,
498 argc: libc::c_uint,
499 vp: *mut JSVal,
500) -> bool {
501 generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, true, CallJitGetterOp, CanGc::note())
502}
503
504unsafe extern "C" fn call_setter(
505 info: *const JSJitInfo,
506 cx: *mut JSContext,
507 handle: RawHandleObject,
508 this: *mut libc::c_void,
509 argc: u32,
510 vp: *mut JSVal,
511) -> bool {
512 if !CallJitSetterOp(info, cx, handle, this, argc, vp) {
513 return false;
514 }
515 *vp = UndefinedValue();
516 true
517}
518
519pub(crate) unsafe extern "C" fn generic_setter(
525 cx: *mut JSContext,
526 argc: libc::c_uint,
527 vp: *mut JSVal,
528) -> bool {
529 generic_call::<false>(cx, argc, vp, false, call_setter, CanGc::note())
530}
531
532pub(crate) unsafe extern "C" fn generic_lenient_setter(
538 cx: *mut JSContext,
539 argc: libc::c_uint,
540 vp: *mut JSVal,
541) -> bool {
542 generic_call::<false>(cx, argc, vp, true, call_setter, CanGc::note())
543}
544
545pub(crate) unsafe extern "C" fn generic_static_promise_method(
551 cx: *mut JSContext,
552 argc: libc::c_uint,
553 vp: *mut JSVal,
554) -> bool {
555 let args = CallArgs::from_vp(vp, argc);
556
557 let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));
558 assert!(!info.is_null());
559 let static_fn = (*info).__bindgen_anon_1.staticMethod.unwrap();
562 if static_fn(cx, argc, vp) {
563 return true;
564 }
565 exception_to_promise(cx, args.rval(), CanGc::note())
566}
567
568pub(crate) unsafe fn exception_to_promise(
575 cx: *mut JSContext,
576 rval: RawMutableHandleValue,
577 _can_gc: CanGc,
578) -> bool {
579 rooted!(in(cx) let mut exception = UndefinedValue());
580 if !JS_GetPendingException(cx, exception.handle_mut()) {
581 return false;
582 }
583 JS_ClearPendingException(cx);
584 if let Some(promise) = NonNull::new(CallOriginalPromiseReject(cx, exception.handle())) {
585 promise.to_jsval(cx, MutableHandleValue::from_raw(rval));
586 true
587 } else {
588 JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
590 false
591 }
592}
593
594pub(crate) unsafe fn trace_global(tracer: *mut JSTracer, obj: *mut JSObject) {
600 let array = get_proto_or_iface_array(obj);
601 for proto in (*array).iter() {
602 if !proto.is_null() {
603 trace_object(
604 tracer,
605 "prototype",
606 &*(proto as *const *mut JSObject as *const Heap<*mut JSObject>),
607 );
608 }
609 }
610}
611
612pub(crate) unsafe extern "C" fn enumerate_global(
615 cx: *mut JSContext,
616 obj: RawHandleObject,
617 props: RawMutableHandleIdVector,
618 enumerable_only: bool,
619) -> bool {
620 assert!(JS_IsGlobalObject(obj.get()));
621 JS_NewEnumerateStandardClasses(cx, obj, props, enumerable_only)
622}
623
624pub(crate) unsafe extern "C" fn enumerate_window<D: DomTypes>(
627 cx: *mut JSContext,
628 obj: RawHandleObject,
629 props: RawMutableHandleIdVector,
630 enumerable_only: bool,
631) -> bool {
632 let mut cx = js::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
633 if !enumerate_global(cx.raw_cx(), obj, props, enumerable_only) {
634 return false;
635 }
636
637 if enumerable_only {
638 return true;
641 }
642
643 let obj = Handle::from_raw(obj);
644 for (name, interface) in <D as DomHelpers<D>>::interface_map() {
645 if !(interface.enabled)(&mut cx, obj) {
646 continue;
647 }
648 let s = JS_AtomizeStringN(cx.raw_cx(), name.as_ptr() as *const c_char, name.len());
649 rooted!(&in(cx) let id = StringId(s));
650 if s.is_null() || !AppendToIdVector(props, id.handle().into()) {
651 return false;
652 }
653 }
654 true
655}
656
657pub(crate) unsafe extern "C" fn may_resolve_global(
661 names: *const JSAtomState,
662 id: PropertyKey,
663 maybe_obj: *mut JSObject,
664) -> bool {
665 JS_MayResolveStandardClass(names, id, maybe_obj)
666}
667
668pub(crate) unsafe extern "C" fn may_resolve_window<D: DomTypes>(
672 names: *const JSAtomState,
673 id: PropertyKey,
674 maybe_obj: *mut JSObject,
675) -> bool {
676 if may_resolve_global(names, id, maybe_obj) {
677 return true;
678 }
679
680 let cx = Runtime::get()
681 .expect("There must be a JSContext active")
682 .as_ptr();
683 let Ok(bytes) = latin1_bytes_from_id(cx, id) else {
684 return false;
685 };
686
687 <D as DomHelpers<D>>::interface_map().contains_key(bytes)
688}
689
690pub(crate) unsafe extern "C" fn resolve_global(
692 cx: *mut JSContext,
693 obj: RawHandleObject,
694 id: RawHandleId,
695 rval: *mut bool,
696) -> bool {
697 assert!(JS_IsGlobalObject(obj.get()));
698 JS_ResolveStandardClass(cx, obj, id, rval)
699}
700
701pub(crate) unsafe extern "C" fn resolve_window<D: DomTypes>(
703 cx: *mut JSContext,
704 obj: RawHandleObject,
705 id: RawHandleId,
706 rval: *mut bool,
707) -> bool {
708 let mut cx = js::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
709 if !resolve_global(cx.raw_cx(), obj, id, rval) {
710 return false;
711 }
712
713 if *rval {
714 return true;
715 }
716 let Ok(bytes) = latin1_bytes_from_id(cx.raw_cx(), *id) else {
717 *rval = false;
718 return true;
719 };
720
721 if let Some(interface) = <D as DomHelpers<D>>::interface_map().get(bytes) {
722 (interface.define)(&mut cx, Handle::from_raw(obj));
723 *rval = true;
724 } else {
725 *rval = false;
726 }
727 true
728}
729
730unsafe fn latin1_bytes_from_id(cx: *mut JSContext, id: jsid) -> Result<&'static [u8], ()> {
735 if !id.is_string() {
736 return Err(());
737 }
738
739 let string = id.to_string();
740 if !JS_DeprecatedStringHasLatin1Chars(string) {
741 return Err(());
742 }
743 let mut length = 0;
744 let ptr = JS_GetLatin1StringCharsAndLength(cx, ptr::null(), string, &mut length);
745 assert!(!ptr.is_null());
746 Ok(slice::from_raw_parts(ptr, length))
747}