diff options
author | Josh Matthews <josh@joshmatthews.net> | 2025-03-29 04:11:27 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-29 08:11:27 +0000 |
commit | c30ad5a30e10b9aabe5842624db24b9846c3d5d4 (patch) | |
tree | c879104dc5ef15647d975da14a6b703d4099df4d /components/script_bindings/utils.rs | |
parent | 2c9411095255f804a43c2abc05c5aaa2a0becca7 (diff) | |
download | servo-c30ad5a30e10b9aabe5842624db24b9846c3d5d4.tar.gz servo-c30ad5a30e10b9aabe5842624db24b9846c3d5d4.zip |
Miscellaneous script splitting preparation changes (#36216)
* script: Move num module to script_bindings.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* script: Make JS reflector creation generic over DOM trait.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* script: Move bindings-specific lock to script_bindings.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* script: Move DOM proto array code to script_bindings.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* script: Move finalizer implementations to script_bindings.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* script: Move some error routines to script_bindings.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* script: Move some DOM interface conversion routines to script_bindings.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* script: Make is_array_like generic over DOM trait.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* script: Use generic interfaces for conditional exposure functions.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* script: Move a bunch of routines used by codegen to script_bindings.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* Formatting.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* Fix clippy warnings.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
---------
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
Diffstat (limited to 'components/script_bindings/utils.rs')
-rw-r--r-- | components/script_bindings/utils.rs | 482 |
1 files changed, 481 insertions, 1 deletions
diff --git a/components/script_bindings/utils.rs b/components/script_bindings/utils.rs index fd307fd5ab3..2317f80dbfa 100644 --- a/components/script_bindings/utils.rs +++ b/components/script_bindings/utils.rs @@ -2,13 +2,40 @@ * 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::CString; use std::os::raw::c_void; +use std::ptr::{self, NonNull}; +use js::conversions::ToJSValConvertible; +use js::glue::{ + CallJitGetterOp, CallJitMethodOp, CallJitSetterOp, JS_GetReservedSlot, + RUST_FUNCTION_VALUE_TO_JITINFO, +}; +use js::jsapi::{ + AtomToLinearString, CallArgs, ExceptionStackBehavior, GetLinearStringCharAt, + GetLinearStringLength, GetNonCCWObjectGlobal, HandleObject as RawHandleObject, + JS_ClearPendingException, JS_IsExceptionPending, JSAtom, JSContext, JSJitInfo, JSObject, + MutableHandleValue as RawMutableHandleValue, ObjectOpResult, StringIsArrayIndex, +}; +use js::jsval::{JSVal, UndefinedValue}; +use js::rust::wrappers::{ + CallOriginalPromiseReject, JS_DeletePropertyById, JS_ForwardGetPropertyTo, + JS_GetPendingException, JS_GetProperty, JS_GetPrototype, JS_HasProperty, JS_HasPropertyById, + JS_SetPendingException, JS_SetProperty, +}; +use js::rust::{ + HandleId, HandleObject, HandleValue, MutableHandleValue, ToString, get_object_class, +}; +use js::{JS_CALLEE, rooted}; use malloc_size_of::MallocSizeOfOps; use crate::codegen::Globals::Globals; use crate::codegen::InheritTypes::TopTypeId; -use crate::codegen::PrototypeList::{self, MAX_PROTO_CHAIN_LENGTH}; +use crate::codegen::PrototypeList::{self, MAX_PROTO_CHAIN_LENGTH, PROTO_OR_IFACE_LENGTH}; +use crate::conversions::{PrototypeCheck, jsstring_to_str, private_from_proto_check}; +use crate::error::throw_invalid_this; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; +use crate::str::DOMString; /// The struct that holds inheritance information for DOM object reflectors. #[derive(Clone, Copy)] @@ -46,3 +73,456 @@ impl Clone for DOMJSClass { } } unsafe impl Sync for DOMJSClass {} + +/// The index of the slot where the object holder of that interface's +/// unforgeable members are defined. +pub 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 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 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 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. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +/// `found` must point to a valid, non-null bool. +pub unsafe 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 !JS_GetPrototype(cx, proxy, proto.handle_mut()) || proto.is_null() { + *found = false; + return true; + } + let mut has_property = false; + if !JS_HasPropertyById(cx, proto.handle(), id, &mut has_property) { + return false; + } + *found = has_property; + if !has_property { + return true; + } + + 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. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +#[allow(clippy::result_unit_err)] +pub unsafe fn find_enum_value<'a, T>( + cx: *mut JSContext, + v: HandleValue, + pairs: &'a [(&'static str, T)], +) -> Result<(Option<&'a T>, DOMString), ()> { + match ptr::NonNull::new(ToString(cx, v)) { + Some(jsstr) => { + let search = jsstring_to_str(cx, jsstr); + 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. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +#[allow(clippy::result_unit_err)] +pub unsafe fn get_dictionary_property( + cx: *mut JSContext, + object: HandleObject, + property: &str, + rval: MutableHandleValue, + _can_gc: CanGc, +) -> Result<bool, ()> { + unsafe fn has_property( + cx: *mut JSContext, + object: HandleObject, + property: &CString, + found: &mut bool, + ) -> bool { + JS_HasProperty(cx, object, property.as_ptr(), found) + } + unsafe fn get_property( + cx: *mut JSContext, + object: HandleObject, + property: &CString, + value: MutableHandleValue, + ) -> bool { + JS_GetProperty(cx, object, property.as_ptr(), value) + } + + let property = CString::new(property).unwrap(); + if object.get().is_null() { + return Ok(false); + } + + let mut found = false; + if !has_property(cx, object, &property, &mut found) { + return Err(()); + } + + if !found { + return Ok(false); + } + + if !get_property(cx, object, &property, rval) { + return Err(()); + } + + Ok(true) +} + +/// Set the property with name `property` from `object`. +/// Returns `Err(())` on JSAPI failure, or null object, +/// and Ok(()) otherwise +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +#[allow(clippy::result_unit_err)] +pub unsafe fn set_dictionary_property( + cx: *mut JSContext, + object: HandleObject, + property: &str, + value: HandleValue, +) -> Result<(), ()> { + if object.get().is_null() { + return Err(()); + } + + let property = CString::new(property).unwrap(); + if !JS_SetProperty(cx, object, property.as_ptr(), value) { + return Err(()); + } + + Ok(()) +} + +/// Returns whether `proxy` has a property `id` on its prototype. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +pub unsafe 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 !JS_GetPrototype(cx, proxy, proto.handle_mut()) { + return false; + } + assert!(!proto.is_null()); + JS_HasPropertyById(cx, proto.handle(), id, found) +} + +/// Deletes the property `id` from `object`. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +pub unsafe fn delete_property_by_id( + cx: *mut JSContext, + object: HandleObject, + id: HandleId, + bp: *mut ObjectOpResult, +) -> bool { + JS_DeletePropertyById(cx, object, id, bp) +} + +unsafe fn generic_call<const EXCEPTION_TO_REJECTION: bool>( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, + is_lenient: bool, + call: unsafe extern "C" fn( + *const JSJitInfo, + *mut JSContext, + RawHandleObject, + *mut libc::c_void, + u32, + *mut JSVal, + ) -> bool, + can_gc: CanGc, +) -> bool { + let args = CallArgs::from_vp(vp, argc); + + let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); + let proto_id = (*info).__bindgen_anon_2.protoID; + let cx = SafeJSContext::from_ptr(cx); + + let thisobj = args.thisv(); + if !thisobj.get().is_null_or_undefined() && !thisobj.get().is_object() { + throw_invalid_this(cx, proto_id); + return if EXCEPTION_TO_REJECTION { + exception_to_promise(*cx, args.rval(), can_gc) + } else { + false + }; + } + + rooted!(in(*cx) let obj = if thisobj.get().is_object() { + thisobj.get().to_object() + } else { + GetNonCCWObjectGlobal(JS_CALLEE(*cx, 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(obj.get(), *cx, proto_check) { + Ok(val) => val, + Err(()) => { + if is_lenient { + 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(), can_gc) + } else { + false + }; + } + }, + }; + call( + info, + *cx, + obj.handle().into(), + 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 unsafe extern "C" fn generic_method<const EXCEPTION_TO_REJECTION: bool>( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, false, CallJitMethodOp, CanGc::note()) +} + +/// 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 unsafe extern "C" fn generic_getter<const EXCEPTION_TO_REJECTION: bool>( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, false, CallJitGetterOp, CanGc::note()) +} + +/// Generic lenient getter of IDL interface. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +/// `vp` must point to a VALID, non-null JSVal. +pub unsafe extern "C" fn generic_lenient_getter<const EXCEPTION_TO_REJECTION: bool>( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + generic_call::<EXCEPTION_TO_REJECTION>(cx, argc, vp, true, CallJitGetterOp, CanGc::note()) +} + +unsafe extern "C" fn call_setter( + info: *const JSJitInfo, + cx: *mut JSContext, + handle: RawHandleObject, + 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 unsafe extern "C" fn generic_setter( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + generic_call::<false>(cx, argc, vp, false, call_setter, CanGc::note()) +} + +/// Generic lenient setter of IDL interface. +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +/// `vp` must point to a VALID, non-null JSVal. +pub unsafe extern "C" fn generic_lenient_setter( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + generic_call::<false>(cx, argc, vp, true, call_setter, CanGc::note()) +} + +/// <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 unsafe extern "C" fn generic_static_promise_method( + cx: *mut JSContext, + argc: libc::c_uint, + vp: *mut JSVal, +) -> bool { + let args = CallArgs::from_vp(vp, argc); + + let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(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, argc, vp) { + return true; + } + exception_to_promise(cx, args.rval(), CanGc::note()) +} + +/// Coverts exception to promise rejection +/// +/// <https://searchfox.org/mozilla-central/rev/b220e40ff2ee3d10ce68e07d8a8a577d5558e2a2/dom/bindings/BindingUtils.cpp#3315> +/// +/// # Safety +/// `cx` must point to a valid, non-null JSContext. +pub unsafe fn exception_to_promise( + cx: *mut JSContext, + rval: RawMutableHandleValue, + _can_gc: CanGc, +) -> bool { + 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.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 + } +} |