/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script_bindings/utils.rs
882 строки
30 KB
Gae24
script: Drop unsafe `ToJSValConvertible::to_jsval` and wrapper trait `SafeToJSValConvertible` (#46890)
31 июл 2026, 10:20
Не верифицирован
31 июл 2026, 10:20
559e0a5
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::ffi::CStr; use std::os::raw::{c_char, c_void}; use std::ptr::{self, NonNull}; use std::slice; use js::context::{JSContext, RawJSContext}; use js::conversions::{ToJSValConvertible, jsstr_to_string}; use js::gc::Handle; use js::glue::{AppendToIdVector, JS_GetReservedSlot, RUST_FUNCTION_VALUE_TO_JITINFO}; use js::jsapi::{ AtomToLinearString, CallArgs, ExceptionStackBehavior, GetLinearStringCharAt, GetLinearStringLength, GetNonCCWObjectGlobal, HandleId as RawHandleId, HandleObject as RawHandleObject, Heap, JS_AtomizeStringN, JS_DeprecatedStringHasLatin1Chars, JS_GetLatin1StringCharsAndLength, JS_IsGlobalObject, JS_MayResolveStandardClass, JS_NewEnumerateStandardClasses, JS_ResolveStandardClass, JSAtom, JSAtomState, JSJitInfo, JSObject, JSPROP_ENUMERATE, JSTracer, MutableHandleIdVector as RawMutableHandleIdVector, MutableHandleValue as RawMutableHandleValue, PropertyKey, StringIsArrayIndex, jsid, }; use js::jsid::StringId; use js::jsval::{JSVal, UndefinedValue}; use js::rust::wrappers2::{ CallJitGetterOp, CallJitMethodOp, CallJitSetterOp, CallOriginalPromiseReject, JS_ClearPendingException, JS_DefineProperty, JS_ForwardGetPropertyTo, JS_FreezeObject, JS_GetPendingException, JS_GetProperty, JS_GetPrototype, JS_HasOwnProperty, JS_HasProperty, JS_HasPropertyById, JS_IsExceptionPending, JS_SetPendingException, JS_SetProperty, }; use js::rust::{ HandleId, HandleObject, HandleValue, MutableHandleValue, Runtime, ToString, get_object_class, }; use js::{JS_CALLEE, rooted}; use malloc_size_of::MallocSizeOfOps; use crate::DomTypes; use crate::codegen::Globals::Globals; use crate::codegen::InheritTypes::TopTypeId; use crate::codegen::PrototypeList::{self, MAX_PROTO_CHAIN_LENGTH, PROTO_OR_IFACE_LENGTH}; use crate::conversions::{PrototypeCheck, private_from_proto_check}; use crate::error::throw_invalid_this; use crate::interfaces::DomHelpers; use crate::proxyhandler::{ is_cross_origin_object, is_platform_object_same_origin, report_cross_origin_denial, }; use crate::str::DOMString; use crate::trace::trace_object; /// The struct that holds inheritance information for DOM object reflectors. #[derive(Clone, Copy)] pub struct DOMClass { /// A list of interfaces that this object implements, in order of decreasing /// derivedness. pub interface_chain: [PrototypeList::ID; MAX_PROTO_CHAIN_LENGTH], /// The last valid index of `interface_chain`. pub depth: u8, /// The type ID of that interface. pub type_id: TopTypeId, /// The MallocSizeOf function wrapper for that interface. pub malloc_size_of: unsafe fn(ops: &mut MallocSizeOfOps, *const c_void) -> usize, /// The `Globals` flag for this global interface, if any. pub global: Globals, } unsafe impl Sync for DOMClass {} /// The JSClass used for DOM object reflectors. #[derive(Copy)] #[repr(C)] pub struct DOMJSClass { /// The actual JSClass. pub base: js::jsapi::JSClass, /// Associated data for DOM object reflectors. pub dom_class: DOMClass, } impl Clone for DOMJSClass { fn clone(&self) -> DOMJSClass { *self } } unsafe impl Sync for DOMJSClass {} /// The index of the slot where the object holder of that interface's /// unforgeable members are defined. pub(crate) const DOM_PROTO_UNFORGEABLE_HOLDER_SLOT: u32 = 0; /// The index of the slot that contains a reference to the ProtoOrIfaceArray. // All DOM globals must have a slot at DOM_PROTOTYPE_SLOT. pub(crate) const DOM_PROTOTYPE_SLOT: u32 = js::JSCLASS_GLOBAL_SLOT_COUNT; /// The flag set on the `JSClass`es for DOM global objects. // NOTE: This is baked into the Ion JIT as 0 in codegen for LGetDOMProperty and // LSetDOMProperty. Those constants need to be changed accordingly if this value // changes. pub(crate) const JSCLASS_DOM_GLOBAL: u32 = js::JSCLASS_USERBIT1; /// Returns the ProtoOrIfaceArray for the given global object. /// Fails if `global` is not a DOM global object. /// /// # Safety /// `global` must point to a valid, non-null JS object. pub(crate) unsafe fn get_proto_or_iface_array(global: *mut JSObject) -> *mut ProtoOrIfaceArray { assert_ne!(((*get_object_class(global)).flags & JSCLASS_DOM_GLOBAL), 0); let mut slot = UndefinedValue(); JS_GetReservedSlot(global, DOM_PROTOTYPE_SLOT, &mut slot); slot.to_private() as *mut ProtoOrIfaceArray } /// An array of *mut JSObject of size PROTO_OR_IFACE_LENGTH. pub type ProtoOrIfaceArray = [*mut JSObject; PROTO_OR_IFACE_LENGTH]; /// Gets the property `id` on `proxy`'s prototype. If it exists, `*found` is /// set to true and `*vp` to the value, otherwise `*found` is set to false. /// /// Returns false on JSAPI failure. pub(crate) fn get_property_on_prototype( cx: &mut JSContext, proxy: HandleObject, receiver: HandleValue, id: HandleId, found: &mut bool, vp: MutableHandleValue, ) -> bool { rooted!(&in(cx) let mut proto = ptr::null_mut::<JSObject>()); if unsafe { !JS_GetPrototype(cx, proxy, proto.handle_mut()) || proto.is_null() } { *found = false; return true; } let mut has_property = false; if unsafe { !JS_HasPropertyById(cx, proto.handle(), id, &mut has_property) } { return false; } *found = has_property; if !has_property { return true; } unsafe { JS_ForwardGetPropertyTo(cx, proto.handle(), id, receiver, vp) } } /// Get an array index from the given `jsid`. Returns `None` if the given /// `jsid` is not an integer. pub fn get_array_index_from_id(id: HandleId) -> Option<u32> { let raw_id = *id; if raw_id.is_int() { return Some(raw_id.to_int() as u32); } if raw_id.is_void() || !raw_id.is_string() { return None; } unsafe { let atom = raw_id.to_string() as *mut JSAtom; let s = AtomToLinearString(atom); if GetLinearStringLength(s) == 0 { return None; } let chars = [GetLinearStringCharAt(s, 0)]; let first_char = char::decode_utf16(chars.iter().cloned()) .next() .map_or('\0', |r| r.unwrap_or('\0')); if first_char.is_ascii_lowercase() { return None; } let mut i = 0; if StringIsArrayIndex(s, &mut i) { Some(i) } else { None } } /*let s = jsstr_to_string(cx, RUST_JSID_TO_STRING(raw_id)); if s.len() == 0 { return None; } let first = s.chars().next().unwrap(); if first.is_ascii_lowercase() { return None; } let mut i: u32 = 0; let is_array = if s.is_ascii() { let chars = s.as_bytes(); StringIsArrayIndex1(chars.as_ptr() as *const _, chars.len() as u32, &mut i) } else { let chars = s.encode_utf16().collect::<Vec<u16>>(); let slice = chars.as_slice(); StringIsArrayIndex2(slice.as_ptr(), chars.len() as u32, &mut i) }; if is_array { Some(i) } else { None }*/ } /// Find the enum equivelent of a string given by `v` in `pairs`. /// Returns `Err(())` on JSAPI failure (there is a pending exception), and /// `Ok((None, value))` if there was no matching string. pub(crate) fn find_enum_value<'a, T>( cx: &mut JSContext, v: HandleValue, pairs: &'a [(&'static str, T)], ) -> Result<(Option<&'a T>, DOMString), ()> { match NonNull::new(unsafe { ToString(cx, v) }) { Some(jsstr) => { let search = unsafe { jsstr_to_string(cx, jsstr) }.into(); Ok(( pairs .iter() .find(|&&(key, _)| search == key) .map(|(_, ev)| ev), search, )) }, None => Err(()), } } /// Get the property with name `property` from `object`. /// Returns `Err(())` on JSAPI failure (there is a pending exception), and /// `Ok(false)` if there was no property with the given name. pub(crate) fn get_dictionary_property( cx: &mut JSContext, object: HandleObject, property: &CStr, rval: MutableHandleValue, ) -> Result<bool, ()> { if object.get().is_null() { return Ok(false); } let mut found = false; if unsafe { !JS_HasProperty(cx, object, property.as_ptr(), &mut found) } { return Err(()); } if !found { return Ok(false); } if unsafe { !JS_GetProperty(cx, object, property.as_ptr(), rval) } { return Err(()); } Ok(true) } /// Set the property with name `property` from `object`. /// Returns `Err(())` on JSAPI failure, or null object, /// and Ok(()) otherwise #[expect(clippy::result_unit_err)] pub fn set_dictionary_property( cx: &mut JSContext, object: HandleObject, property: &CStr, value: HandleValue, ) -> Result<(), ()> { if object.get().is_null() { return Err(()); } if unsafe { !JS_SetProperty(cx, object, property.as_ptr(), value) } { return Err(()); } Ok(()) } /// Define an own enumerable data property with name `property` on `object`. /// Returns `Err(())` on JSAPI failure, or null object, /// and Ok(()) otherwise. #[expect(clippy::result_unit_err)] pub fn define_dictionary_property( cx: &mut JSContext, object: HandleObject, property: &CStr, value: HandleValue, ) -> Result<(), ()> { if object.get().is_null() { return Err(()); } if unsafe { !JS_DefineProperty( cx, object, property.as_ptr(), value, JSPROP_ENUMERATE as u32, ) } { return Err(()); } Ok(()) } /// Checks whether `object` has an own property named `property`. /// Returns `Err(())` on JSAPI failure (there is a pending exception), /// and `Ok(false)` for null objects or when the property is not own. #[expect(clippy::result_unit_err)] pub fn has_own_property( cx: &mut JSContext, object: HandleObject, property: &CStr, ) -> Result<bool, ()> { if object.get().is_null() { return Ok(false); } let mut found = false; if unsafe { !JS_HasOwnProperty(cx, object, property.as_ptr(), &mut found) } { return Err(()); } Ok(found) } /// Computes whether `proxy` has a property `id` on its prototype and stores /// the result in `found`. /// /// Returns a boolean indicating whether the check succeeded. /// If `false` is returned then the value of `found` is unspecified. pub fn has_property_on_prototype( cx: &mut JSContext, proxy: HandleObject, id: HandleId, found: &mut bool, ) -> bool { rooted!(&in(cx) let mut proto = ptr::null_mut::<JSObject>()); if unsafe { !JS_GetPrototype(cx, proxy, proto.handle_mut()) } { return false; } assert!(!proto.is_null()); unsafe { JS_HasPropertyById(cx, proto.handle(), id, found) } } pub trait CallPolicy { const INFO: CallPolicyInfo; } pub mod call_policies { use super::*; pub struct Normal; pub struct TargetClassMaybeCrossOrigin; pub struct LenientThis; pub struct LenientThisTargetClassMaybeCrossOrigin; pub struct CrossOriginCallable; impl CallPolicy for Normal { const INFO: CallPolicyInfo = CallPolicyInfo { lenient_this: false, needs_security_check_on_interface_match: false, }; } impl CallPolicy for TargetClassMaybeCrossOrigin { const INFO: CallPolicyInfo = CallPolicyInfo { lenient_this: false, needs_security_check_on_interface_match: true, }; } impl CallPolicy for LenientThis { const INFO: CallPolicyInfo = CallPolicyInfo { lenient_this: true, needs_security_check_on_interface_match: false, }; } impl CallPolicy for LenientThisTargetClassMaybeCrossOrigin { const INFO: CallPolicyInfo = CallPolicyInfo { lenient_this: true, needs_security_check_on_interface_match: true, }; } impl CallPolicy for CrossOriginCallable { const INFO: CallPolicyInfo = CallPolicyInfo { lenient_this: false, needs_security_check_on_interface_match: false, }; } } /// Controls various details of an IDL operation, such as whether a /// `[`[`LegacyLenientThis`][1]`]` attribute is specified and preconditions that /// affect the outcome of the "[perform a security check][2]" steps. /// /// [1]: https://heycam.github.io/webidl/#LegacyLenientThis /// [2]: https://html.spec.whatwg.org/multipage/#integration-with-idl #[derive(Clone, Copy, Eq, PartialEq)] pub struct CallPolicyInfo { /// Specifies whether a `[LegacyLenientThis]` attribute is specified on the /// interface member this operation is associated with. pub lenient_this: bool, /// Indicates whether a [security check][1] is required if the target object /// implements this operation's interface. /// /// Regardless of this value, performing a security check is always /// necessary if the target object doesn't implement this operation's /// interface. /// /// This field is `false` iff any of the following are true: /// /// - The operation is not implemented by any cross-origin objects (i.e., /// any `Window` or `Location` objects). /// /// - The operation is defined as cross origin (i.e., it's included in /// [`CrossOriginProperties`][2]`(obj)`, given `obj` implementing the /// operation's interface). /// /// [1]: https://html.spec.whatwg.org/multipage/#integration-with-idl /// [2]: https://html.spec.whatwg.org/multipage/#crossoriginproperties-(-o-) pub needs_security_check_on_interface_match: bool, } unsafe fn generic_call<D: DomTypes, const EXCEPTION_TO_REJECTION: bool>( cx: &mut JSContext, argc: libc::c_uint, vp: *mut JSVal, CallPolicyInfo { lenient_this, needs_security_check_on_interface_match, }: CallPolicyInfo, call: unsafe fn( *const JSJitInfo, &mut JSContext, HandleObject, *mut libc::c_void, u32, *mut JSVal, ) -> bool, ) -> bool { let args = CallArgs::from_vp(vp, argc); let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx.raw_cx_no_gc(), vp)); let proto_id = (*info).__bindgen_anon_2.protoID; // <https://heycam.github.io/webidl/#es-operations> // // > To create an operation function, given an operation `op`, a namespace // > or interface `target`, and a Realm `realm`: // > // > 2. Let `steps` be the following series of steps, [...] // > // > 2.1.2.1. Let `esValue` be the `this` value, if it is not `null` or // > `undefined`, or `realm`’s global object otherwise. [...] // > // > 2.1.2.2. If `esValue` is a platform object, then perform a security // > check, passing `esValue`, `id`, and "method". // > // > 2.1.2.3. If `esValue` does not implement the interface `target`, throw // > a `TypeError`. [...] // // <https://html.spec.whatwg.org/multipage/#integration-with-idl> // // > When perform a security check is invoked, with a `platformObject`, // > `identifier`, and `type`, run these steps: // > // > 1. If `platformObject` is not a `Window` or `Location` object, then // > return. // > // > 2. For each `e` of `! CrossOriginProperties(platformObject)`: // > // > 2.1. If `SameValue(e.[[Property]], identifier)` is true, then: // > // > 2.1.1. If type is "method" and `e` has neither `[[NeedsGet]]` nor // > `[[NeedsSet]]`, then return. [... ditto for other types] // > // > 3. If `! IsPlatformObjectSameOrigin(platformObject)` is false, then // > throw a "SecurityError" `DOMException`. // // According to the above steps, the outcome of an IDL operation is // determined be the following boolean variables: // // - `this_same_origin`: The current principals object subsumes that of // `thisobj` // - `this_class_cross_origin`: `thisobj`'s class provides a cross-origin // member // - `cross_origin_operation`: The tuple `(operation_name, operation_type)` // (e.g., `("focus", "method")`) is a member of // `CrossOriginProperties(thisobj)` // - `this_implements_operation`: `thisobj`'s class implements the // current operation. // // The Karnaugh-esque map of the expected outcome is shown below: // // this_same_origin // ,-------, // ,---+---+---+---, // | T | T | o | o | // ,-+---+---+---+---+ // | | S | T | o | S | // this_class_cross_origin | +---+---+---+---+-, // | | T | T | o | o | | // '-+---+---+---+---+ | cross_origin_operation // | T | T | | | | // '---+---+---+---+-' // '-------' // this_implements_operation // // T: TypeError (generated by WebIDL opration function step 2.1.2.3) // S: SecurityError (generated by HTML security check step 3) // o: OK // blank: don't-care (impossible cases) // // Under some circumstances, we can rule out some cases from this map. // E.g., if the operation is known to be not implemented by any cross-origin // objects, `this_implements_operation → ¬this_class_cross_origin`, so in // this case, we don't have to perform the security check at all if // `thisobj`'s class implements the expected interface. `CallPolicyInfo:: // needs_security_check_on_interface_match` indicates whether this applies. let thisobj = args.thisv(); if !thisobj.get().is_null_or_undefined() && !thisobj.get().is_object() { // `thisobj` is not a platform object, so the security check is not // invoked in this case throw_invalid_this(cx, proto_id); return if EXCEPTION_TO_REJECTION { exception_to_promise(cx, args.rval()) } else { false }; } rooted!(&in(cx) let obj = if thisobj.get().is_object() { thisobj.get().to_object() } else { GetNonCCWObjectGlobal(JS_CALLEE(cx.raw_cx_no_gc(), vp).to_object_or_null()) }); let depth = (*info).__bindgen_anon_3.depth as usize; let proto_check = PrototypeCheck::Depth { depth, proto_id }; let this = match private_from_proto_check(cx, obj.get(), proto_check) { Ok(val) => val, Err(()) => { // [this_implements_operation == false] // // For now, We don't check the conditions for `SecurityError` in // this case, following WebKit's behavior. // // FIXME: Implement a different browser or the specification's // behavior? (They all differ subtly.) Some behavior is more // challenging to implement - for example, implementing the // specification's behavior requires `generic_call`'s code to // have access to the current IDL operation's name and type // and the target object's `CrossOriginProperties`. if lenient_this { debug_assert!(!JS_IsExceptionPending(cx)); *vp = UndefinedValue(); return true; } else { throw_invalid_this(cx, proto_id); return if EXCEPTION_TO_REJECTION { exception_to_promise(cx, args.rval()) } else { false }; } }, }; // [this_implements_operation == true] if needs_security_check_on_interface_match { let mut realm = js::realm::CurrentRealm::assert(cx); // [cross_origin_operation == false] if is_cross_origin_object::<D>(&mut realm, obj.handle()) && !is_platform_object_same_origin(&realm, obj.handle()) { // [this_class_cross_origin == true && this_same_origin == false] // Throw a `SecurityError` `DOMException`. // FIXME: `Handle<jsid>` could have a default constructor // like `Handle<Value>::null` rooted!(&in(*realm) let mut void_jsid: jsid); let result = report_cross_origin_denial::<D>(&mut realm, void_jsid.handle(), "call"); return if EXCEPTION_TO_REJECTION { exception_to_promise(cx, args.rval()) } else { result }; } } else { // [(cross_origin_operation == true && this_class_cross_origin == true) // || cross_origin_operation == false && this_class_cross_origin == false] } call(info, cx, obj.handle(), this as *mut libc::c_void, argc, vp) } /// Generic method of IDL interface. /// /// # Safety /// `cx` must point to a valid, non-null JSContext. /// `vp` must point to a VALID, non-null JSVal. pub(crate) unsafe extern "C" fn generic_method< D: DomTypes, Policy: CallPolicy, const EXCEPTION_TO_REJECTION: bool, >( cx: *mut RawJSContext, argc: libc::c_uint, vp: *mut JSVal, ) -> bool { // SAFETY: it is safe to construct a JSContext from engine hook. let mut cx = JSContext::from_ptr(NonNull::new(cx).unwrap()); let cx = &mut cx; generic_call::<D, EXCEPTION_TO_REJECTION>(cx, argc, vp, Policy::INFO, CallJitMethodOp) } /// Generic getter of IDL interface. /// /// # Safety /// `cx` must point to a valid, non-null JSContext. /// `vp` must point to a VALID, non-null JSVal. pub(crate) unsafe extern "C" fn generic_getter< D: DomTypes, Policy: CallPolicy, const EXCEPTION_TO_REJECTION: bool, >( cx: *mut RawJSContext, argc: libc::c_uint, vp: *mut JSVal, ) -> bool { // SAFETY: it is safe to construct a JSContext from engine hook. let mut cx = JSContext::from_ptr(NonNull::new(cx).unwrap()); let cx = &mut cx; generic_call::<D, EXCEPTION_TO_REJECTION>(cx, argc, vp, Policy::INFO, CallJitGetterOp) } unsafe fn call_setter( info: *const JSJitInfo, cx: &mut JSContext, handle: HandleObject, this: *mut libc::c_void, argc: u32, vp: *mut JSVal, ) -> bool { if !CallJitSetterOp(info, cx, handle, this, argc, vp) { return false; } *vp = UndefinedValue(); true } /// Generic setter of IDL interface. /// /// # Safety /// `cx` must point to a valid, non-null JSContext. /// `vp` must point to a VALID, non-null JSVal. pub(crate) unsafe extern "C" fn generic_setter<D: DomTypes, Policy: CallPolicy>( cx: *mut RawJSContext, argc: libc::c_uint, vp: *mut JSVal, ) -> bool { // SAFETY: it is safe to construct a JSContext from engine hook. let mut cx = JSContext::from_ptr(NonNull::new(cx).unwrap()); let cx = &mut cx; generic_call::<D, false>(cx, argc, vp, Policy::INFO, call_setter) } /// <https://searchfox.org/mozilla-central/rev/7279a1df13a819be254fd4649e07c4ff93e4bd45/dom/bindings/BindingUtils.cpp#3300> /// # Safety /// /// `cx` must point to a valid, non-null JSContext. /// `vp` must point to a VALID, non-null JSVal. pub(crate) unsafe extern "C" fn generic_static_promise_method( cx: *mut RawJSContext, argc: libc::c_uint, vp: *mut JSVal, ) -> bool { // SAFETY: it is safe to construct a JSContext from engine hook. let mut cx = JSContext::from_ptr(NonNull::new(cx).unwrap()); let cx = &mut cx; let args = CallArgs::from_vp(vp, argc); let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx.raw_cx(), vp)); assert!(!info.is_null()); // TODO: we need safe wrappers for this in mozjs! // assert_eq!((*info)._bitfield_1, JSJitInfo_OpType::StaticMethod as u8) let static_fn = (*info).__bindgen_anon_1.staticMethod.unwrap(); if static_fn(cx.raw_cx(), argc, vp) { return true; } exception_to_promise(cx, args.rval()) } /// Coverts exception to promise rejection /// /// <https://searchfox.org/mozilla-central/rev/b220e40ff2ee3d10ce68e07d8a8a577d5558e2a2/dom/bindings/BindingUtils.cpp#3315> pub(crate) fn exception_to_promise(cx: &mut JSContext, rval: RawMutableHandleValue) -> bool { unsafe { rooted!(&in(cx) let mut exception = UndefinedValue()); if !JS_GetPendingException(cx, exception.handle_mut()) { return false; } JS_ClearPendingException(cx); if let Some(promise) = NonNull::new(CallOriginalPromiseReject(cx, exception.handle())) { promise.safe_to_jsval(cx, MutableHandleValue::from_raw(rval)); true } else { // We just give up. Put the exception back. JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture); false } } } /// Trace the resources held by reserved slots of a global object /// /// # Safety /// `tracer` must point to a valid, non-null JSTracer. /// `obj` must point to a valid, non-null JSObject. pub(crate) unsafe fn trace_global(tracer: *mut JSTracer, obj: *mut JSObject) { let array = get_proto_or_iface_array(obj); for proto in (*array).iter() { if !proto.is_null() { trace_object( tracer, "prototype", &*(proto as *const *mut JSObject as *const Heap<*mut JSObject>), ); } } } /// Enumerate lazy properties of a global object. /// Modeled after <https://github.com/mozilla/gecko-dev/blob/3fd619f47/dom/bindings/BindingUtils.cpp#L2814> pub(crate) unsafe extern "C" fn enumerate_global( cx: *mut RawJSContext, obj: RawHandleObject, props: RawMutableHandleIdVector, enumerable_only: bool, ) -> bool { assert!(JS_IsGlobalObject(obj.get())); JS_NewEnumerateStandardClasses(cx, obj, props, enumerable_only) } /// Enumerate lazy properties of a global object that is a Window. /// <https://github.com/mozilla/gecko-dev/blob/3fd619f47/dom/base/nsGlobalWindowInner.cpp#3297> pub(crate) unsafe extern "C" fn enumerate_window<D: DomTypes>( cx: *mut RawJSContext, obj: RawHandleObject, props: RawMutableHandleIdVector, enumerable_only: bool, ) -> bool { let mut cx = JSContext::from_ptr(NonNull::new(cx).unwrap()); if !enumerate_global(cx.raw_cx(), obj, props, enumerable_only) { return false; } if enumerable_only { // All WebIDL interface names are defined as non-enumerable, so there's // no point in checking them if we're only returning enumerable names. return true; } let obj = Handle::from_raw(obj); for (name, interface) in <D as DomHelpers<D>>::interface_map() { if !(interface.enabled)(&mut cx, obj) { continue; } let s = JS_AtomizeStringN(cx.raw_cx(), name.as_ptr() as *const c_char, name.len()); rooted!(&in(cx) let id = StringId(s)); if s.is_null() || !AppendToIdVector(props, id.handle().into()) { return false; } } true } /// Returns true if the resolve hook for this global may resolve the provided id. /// <https://searchfox.org/mozilla-central/rev/f3c8c63a097b61bb1f01e13629b9514e09395947/dom/bindings/BindingUtils.cpp#2809> /// <https://searchfox.org/mozilla-central/rev/f3c8c63a097b61bb1f01e13629b9514e09395947/js/public/Class.h#283-291> pub(crate) unsafe extern "C" fn may_resolve_global( names: *const JSAtomState, id: PropertyKey, maybe_obj: *mut JSObject, ) -> bool { JS_MayResolveStandardClass(names, id, maybe_obj) } /// Returns true if the resolve hook for this window may resolve the provided id. /// <https://searchfox.org/mozilla-central/rev/f3c8c63a097b61bb1f01e13629b9514e09395947/dom/base/nsGlobalWindowInner.cpp#3275> /// <https://searchfox.org/mozilla-central/rev/f3c8c63a097b61bb1f01e13629b9514e09395947/js/public/Class.h#283-291> pub(crate) unsafe extern "C" fn may_resolve_window<D: DomTypes>( names: *const JSAtomState, id: PropertyKey, maybe_obj: *mut JSObject, ) -> bool { if may_resolve_global(names, id, maybe_obj) { return true; } let cx = Runtime::get() .expect("There must be a JSContext active") .as_ptr(); let Ok(bytes) = latin1_bytes_from_id(cx, id) else { return false; }; <D as DomHelpers<D>>::interface_map().contains_key(bytes) } /// Resolve a lazy global property, for interface objects and named constructors. pub(crate) unsafe extern "C" fn resolve_global( cx: *mut RawJSContext, obj: RawHandleObject, id: RawHandleId, rval: *mut bool, ) -> bool { assert!(JS_IsGlobalObject(obj.get())); JS_ResolveStandardClass(cx, obj, id, rval) } /// Resolve a lazy global property for a Window global. pub(crate) unsafe extern "C" fn resolve_window<D: DomTypes>( cx: *mut RawJSContext, obj: RawHandleObject, id: RawHandleId, rval: *mut bool, ) -> bool { let mut cx = JSContext::from_ptr(NonNull::new(cx).unwrap()); if !resolve_global(cx.raw_cx(), obj, id, rval) { return false; } if *rval { return true; } let Ok(bytes) = latin1_bytes_from_id(cx.raw_cx(), *id) else { *rval = false; return true; }; if let Some(interface) = <D as DomHelpers<D>>::interface_map().get(bytes) { (interface.define)(&mut cx, Handle::from_raw(obj)); *rval = true; } else { *rval = false; } true } /// Returns a slice of bytes corresponding to the bytes in the provided string id. /// Returns an error if the id is not a string, or the string contains non-latin1 characters. /// # Safety /// The slice is only valid as long as the original id is not garbage collected. unsafe fn latin1_bytes_from_id(cx: *mut RawJSContext, id: jsid) -> Result<&'static [u8], ()> { if !id.is_string() { return Err(()); } let string = id.to_string(); if !JS_DeprecatedStringHasLatin1Chars(string) { return Err(()); } let mut length = 0; let ptr = JS_GetLatin1StringCharsAndLength(cx, ptr::null(), string, &mut length); assert!(!ptr.is_null()); Ok(slice::from_raw_parts(ptr, length)) } /// Returns a JSVal representing the frozen JavaScript array pub fn to_frozen_array<T: ToJSValConvertible>( cx: &mut JSContext, convertibles: &[T], mut rval: MutableHandleValue, ) { convertibles.safe_to_jsval(cx, rval.reborrow()); rooted!(&in(cx) let obj = rval.to_object()); unsafe { JS_FreezeObject(cx, obj.handle()) }; }