script_bindings/
utils.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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/// The struct that holds inheritance information for DOM object reflectors.
52#[derive(Clone, Copy)]
53pub struct DOMClass {
54    /// A list of interfaces that this object implements, in order of decreasing
55    /// derivedness.
56    pub interface_chain: [PrototypeList::ID; MAX_PROTO_CHAIN_LENGTH],
57
58    /// The last valid index of `interface_chain`.
59    pub depth: u8,
60
61    /// The type ID of that interface.
62    pub type_id: TopTypeId,
63
64    /// The MallocSizeOf function wrapper for that interface.
65    pub malloc_size_of: unsafe fn(ops: &mut MallocSizeOfOps, *const c_void) -> usize,
66
67    /// The `Globals` flag for this global interface, if any.
68    pub global: Globals,
69}
70unsafe impl Sync for DOMClass {}
71
72/// The JSClass used for DOM object reflectors.
73#[derive(Copy)]
74#[repr(C)]
75pub struct DOMJSClass {
76    /// The actual JSClass.
77    pub base: js::jsapi::JSClass,
78    /// Associated data for DOM object reflectors.
79    pub dom_class: DOMClass,
80}
81impl Clone for DOMJSClass {
82    fn clone(&self) -> DOMJSClass {
83        *self
84    }
85}
86unsafe impl Sync for DOMJSClass {}
87
88/// The index of the slot where the object holder of that interface's
89/// unforgeable members are defined.
90pub(crate) const DOM_PROTO_UNFORGEABLE_HOLDER_SLOT: u32 = 0;
91
92/// The index of the slot that contains a reference to the ProtoOrIfaceArray.
93// All DOM globals must have a slot at DOM_PROTOTYPE_SLOT.
94pub(crate) const DOM_PROTOTYPE_SLOT: u32 = js::JSCLASS_GLOBAL_SLOT_COUNT;
95
96/// The flag set on the `JSClass`es for DOM global objects.
97// NOTE: This is baked into the Ion JIT as 0 in codegen for LGetDOMProperty and
98// LSetDOMProperty. Those constants need to be changed accordingly if this value
99// changes.
100pub(crate) const JSCLASS_DOM_GLOBAL: u32 = js::JSCLASS_USERBIT1;
101
102/// Returns the ProtoOrIfaceArray for the given global object.
103/// Fails if `global` is not a DOM global object.
104///
105/// # Safety
106/// `global` must point to a valid, non-null JS object.
107pub(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
114/// An array of *mut JSObject of size PROTO_OR_IFACE_LENGTH.
115pub type ProtoOrIfaceArray = [*mut JSObject; PROTO_OR_IFACE_LENGTH];
116
117/// Gets the property `id` on  `proxy`'s prototype. If it exists, `*found` is
118/// set to true and `*vp` to the value, otherwise `*found` is set to false.
119///
120/// Returns false on JSAPI failure.
121///
122/// # Safety
123/// `cx` must point to a valid, non-null JSContext.
124/// `found` must point to a valid, non-null bool.
125pub(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
150/// Get an array index from the given `jsid`. Returns `None` if the given
151/// `jsid` is not an integer.
152pub 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    /*let s = jsstr_to_string(cx, RUST_JSID_TO_STRING(raw_id));
186    if s.len() == 0 {
187        return None;
188    }
189
190    let first = s.chars().next().unwrap();
191    if first.is_ascii_lowercase() {
192        return None;
193    }
194
195    let mut i: u32 = 0;
196    let is_array = if s.is_ascii() {
197        let chars = s.as_bytes();
198        StringIsArrayIndex1(chars.as_ptr() as *const _, chars.len() as u32, &mut i)
199    } else {
200        let chars = s.encode_utf16().collect::<Vec<u16>>();
201        let slice = chars.as_slice();
202        StringIsArrayIndex2(slice.as_ptr(), chars.len() as u32, &mut i)
203    };
204
205    if is_array {
206        Some(i)
207    } else {
208        None
209    }*/
210}
211
212/// Find the enum equivelent of a string given by `v` in `pairs`.
213/// Returns `Err(())` on JSAPI failure (there is a pending exception), and
214/// `Ok((None, value))` if there was no matching string.
215///
216/// # Safety
217/// `cx` must point to a valid, non-null JSContext.
218#[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/// Get the property with name `property` from `object`.
240/// Returns `Err(())` on JSAPI failure (there is a pending exception), and
241/// `Ok(false)` if there was no property with the given name.
242///
243/// # Safety
244/// `cx` must point to a valid, non-null JSContext.
245#[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/// Set the property with name `property` from `object`.
291/// Returns `Err(())` on JSAPI failure, or null object,
292/// and Ok(()) otherwise
293#[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/// Define an own enumerable data property with name `property` on `object`.
314/// Returns `Err(())` on JSAPI failure, or null object,
315/// and Ok(()) otherwise.
316#[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/// Checks whether `object` has an own property named `property`.
343/// Returns `Err(())` on JSAPI failure (there is a pending exception),
344/// and `Ok(false)` for null objects or when the property is not own.
345#[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
365/// Computes whether `proxy` has a property `id` on its prototype and stores
366/// the result in `found`.
367///
368/// Returns a boolean indicating whether the check succeeded.
369/// If `false` is returned then the value of `found` is unspecified.
370///
371/// # Safety
372/// `cx` must point to a valid, non-null JSContext.
373pub 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
387/// Deletes the property `id` from `object`.
388///
389/// # Safety
390/// `cx` must point to a valid, non-null JSContext.
391pub(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
465/// Generic method of IDL interface.
466///
467/// # Safety
468/// `cx` must point to a valid, non-null JSContext.
469/// `vp` must point to a VALID, non-null JSVal.
470pub(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
478/// Generic getter of IDL interface.
479///
480/// # Safety
481/// `cx` must point to a valid, non-null JSContext.
482/// `vp` must point to a VALID, non-null JSVal.
483pub(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
491/// Generic lenient getter of IDL interface.
492///
493/// # Safety
494/// `cx` must point to a valid, non-null JSContext.
495/// `vp` must point to a VALID, non-null JSVal.
496pub(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
519/// Generic setter of IDL interface.
520///
521/// # Safety
522/// `cx` must point to a valid, non-null JSContext.
523/// `vp` must point to a VALID, non-null JSVal.
524pub(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
532/// Generic lenient setter of IDL interface.
533///
534/// # Safety
535/// `cx` must point to a valid, non-null JSContext.
536/// `vp` must point to a VALID, non-null JSVal.
537pub(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
545/// <https://searchfox.org/mozilla-central/rev/7279a1df13a819be254fd4649e07c4ff93e4bd45/dom/bindings/BindingUtils.cpp#3300>
546/// # Safety
547///
548/// `cx` must point to a valid, non-null JSContext.
549/// `vp` must point to a VALID, non-null JSVal.
550pub(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    // TODO: we need safe wrappers for this in mozjs!
560    // assert_eq!((*info)._bitfield_1, JSJitInfo_OpType::StaticMethod as u8)
561    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
568/// Coverts exception to promise rejection
569///
570/// <https://searchfox.org/mozilla-central/rev/b220e40ff2ee3d10ce68e07d8a8a577d5558e2a2/dom/bindings/BindingUtils.cpp#3315>
571///
572/// # Safety
573/// `cx` must point to a valid, non-null JSContext.
574pub(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        // We just give up.  Put the exception back.
589        JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
590        false
591    }
592}
593
594/// Trace the resources held by reserved slots of a global object
595///
596/// # Safety
597/// `tracer` must point to a valid, non-null JSTracer.
598/// `obj` must point to a valid, non-null JSObject.
599pub(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
612/// Enumerate lazy properties of a global object.
613/// Modeled after <https://github.com/mozilla/gecko-dev/blob/3fd619f47/dom/bindings/BindingUtils.cpp#L2814>
614pub(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
624/// Enumerate lazy properties of a global object that is a Window.
625/// <https://github.com/mozilla/gecko-dev/blob/3fd619f47/dom/base/nsGlobalWindowInner.cpp#3297>
626pub(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        // All WebIDL interface names are defined as non-enumerable, so there's
639        // no point in checking them if we're only returning enumerable names.
640        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
657/// Returns true if the resolve hook for this global may resolve the provided id.
658/// <https://searchfox.org/mozilla-central/rev/f3c8c63a097b61bb1f01e13629b9514e09395947/dom/bindings/BindingUtils.cpp#2809>
659/// <https://searchfox.org/mozilla-central/rev/f3c8c63a097b61bb1f01e13629b9514e09395947/js/public/Class.h#283-291>
660pub(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
668/// Returns true if the resolve hook for this window may resolve the provided id.
669/// <https://searchfox.org/mozilla-central/rev/f3c8c63a097b61bb1f01e13629b9514e09395947/dom/base/nsGlobalWindowInner.cpp#3275>
670/// <https://searchfox.org/mozilla-central/rev/f3c8c63a097b61bb1f01e13629b9514e09395947/js/public/Class.h#283-291>
671pub(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
690/// Resolve a lazy global property, for interface objects and named constructors.
691pub(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
701/// Resolve a lazy global property for a Window global.
702pub(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
730/// Returns a slice of bytes corresponding to the bytes in the provided string id.
731/// Returns an error if the id is not a string, or the string contains non-latin1 characters.
732/// # Safety
733/// The slice is only valid as long as the original id is not garbage collected.
734unsafe 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}