/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script_bindings/conversions.rs
616 строк
20 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::{ptr, slice}; use js::context::JSContext; use js::conversions::{ ConversionResult, FromJSValConvertible, ToJSValConvertible, jsstr_to_string, }; use js::error::throw_type_error; use js::glue::{ GetProxyHandlerExtra, GetProxyReservedSlot, IsProxyHandlerFamily, IsWrapper, JS_GetReservedSlot, }; use js::jsapi::{Heap, IsWindowProxy, JS_DeprecatedStringHasLatin1Chars, JSObject}; use js::jsval::{ObjectValue, StringValue, UndefinedValue}; use js::rust::wrappers2::{ IsArrayObject, JS_GetLatin1StringCharsAndLength, JS_GetTwoByteStringCharsAndLength, JS_NewStringCopyN, UnwrapObjectDynamic, }; use js::rust::{ HandleId, HandleValue, MutableHandleValue, ToString, get_object_class, is_dom_class, is_dom_object, maybe_wrap_value, }; use keyboard_types::Modifiers; use num_traits::Float; use crate::JSTraceable; use crate::codegen::GenericBindings::EventModifierInitBinding::EventModifierInit; use crate::inheritance::Castable; use crate::num::Finite; use crate::reflector::{DomObject, Reflector}; use crate::root::DomRoot; use crate::str::{ByteString, DOMString, USVString}; use crate::trace::RootedTraceableBox; use crate::utils::{DOMClass, DOMJSClass}; /// A trait to check whether a given `JSObject` implements an IDL interface. pub trait IDLInterface { /// Returns whether the given DOM class derives that interface. fn derives(_: &'static DOMClass) -> bool; /// First prototype ID in the DFS-ordered range for this interface and its descendants. const PROTO_FIRST: u16 = 0; /// Last prototype ID in the DFS-ordered range for this interface and its descendants. const PROTO_LAST: u16 = u16::MAX; } /// A trait to mark an IDL interface as deriving from another one. pub trait DerivedFrom<T: Castable>: Castable {} // http://heycam.github.io/webidl/#es-USVString impl ToJSValConvertible for USVString { fn safe_to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) { self.0.safe_to_jsval(cx, rval); } } /// Behavior for stringification of `JSVal`s. #[derive(Clone, PartialEq)] pub enum StringificationBehavior { /// Convert `null` to the string `"null"`. Default, /// Convert `null` to the empty string. Empty, } // https://heycam.github.io/webidl/#es-DOMString impl FromJSValConvertible for DOMString { type Config = StringificationBehavior; fn safe_from_jsval( cx: &mut JSContext, value: HandleValue, null_behavior: StringificationBehavior, ) -> Result<ConversionResult<DOMString>, ()> { if null_behavior == StringificationBehavior::Empty && value.get().is_null() { Ok(ConversionResult::Success(DOMString::new())) } else { match DOMString::from_js_string(cx, value) { Ok(domstring) => Ok(ConversionResult::Success(domstring)), Err(_) => Err(()), } } } } // http://heycam.github.io/webidl/#es-USVString impl FromJSValConvertible for USVString { type Config = (); fn safe_from_jsval( cx: &mut JSContext, value: HandleValue, _: (), ) -> Result<ConversionResult<USVString>, ()> { let Some(jsstr) = ptr::NonNull::new(unsafe { ToString(cx, value) }) else { debug!("ToString failed"); return Err(()); }; // FIXME(ajeffrey): Convert directly from DOMString to USVString Ok(ConversionResult::Success(USVString(unsafe { jsstr_to_string(cx, jsstr) }))) } } // http://heycam.github.io/webidl/#es-ByteString impl ToJSValConvertible for ByteString { fn safe_to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) { let jsstr = unsafe { JS_NewStringCopyN( cx, self.as_ptr() as *const libc::c_char, self.len() as libc::size_t, ) }; if jsstr.is_null() { panic!("JS_NewStringCopyN failed"); } unsafe { rval.set(StringValue(&*jsstr)) }; } } // http://heycam.github.io/webidl/#es-ByteString impl FromJSValConvertible for ByteString { type Config = (); fn safe_from_jsval( cx: &mut JSContext, value: HandleValue, _option: (), ) -> Result<ConversionResult<ByteString>, ()> { unsafe { let string = ToString(cx, value); if string.is_null() { debug!("ToString failed"); return Err(()); } let latin1 = JS_DeprecatedStringHasLatin1Chars(string); if latin1 { let mut length = 0; let chars = JS_GetLatin1StringCharsAndLength(cx, string, &mut length); assert!(!chars.is_null()); let char_slice = slice::from_raw_parts(chars as *mut u8, length); return Ok(ConversionResult::Success(ByteString::new( char_slice.to_vec(), ))); } let mut length = 0; let chars = JS_GetTwoByteStringCharsAndLength(cx, string, &mut length); let char_vec = slice::from_raw_parts(chars, length); if char_vec.iter().any(|&c| c > 0xFF) { throw_type_error(cx.raw_cx(), c"Invalid ByteString"); Err(()) } else { Ok(ConversionResult::Success(ByteString::new( char_vec.iter().map(|&c| c as u8).collect(), ))) } } } } impl<T> ToJSValConvertible for Reflector<T> { fn safe_to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) { let obj = self.get_jsobject().get(); assert!(!obj.is_null()); rval.set(ObjectValue(obj)); maybe_wrap_value(cx, rval); } } impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> { type Config = (); fn safe_from_jsval( cx: &mut JSContext, value: HandleValue, _config: Self::Config, ) -> Result<ConversionResult<DomRoot<T>>, ()> { Ok(match root_from_handlevalue(cx, value) { Ok(result) => ConversionResult::Success(result), Err(()) => ConversionResult::Failure(c"value is not an object".into()), }) } } impl<T: DomObject> ToJSValConvertible for DomRoot<T> { fn safe_to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) { self.reflector().safe_to_jsval(cx, rval); } } /// Get the `DOMClass` from `obj`, or `Err(())` if `obj` is not a DOM object. /// /// # Safety /// obj must point to a valid, non-null JS object. #[allow(clippy::result_unit_err)] pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> { let clasp = get_object_class(obj); if is_dom_class(&*clasp) { trace!("plain old dom object"); let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass; return Ok(&(*domjsclass).dom_class); } if is_dom_proxy(obj) { trace!("proxy dom object"); let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass; if dom_class.is_null() { return Err(()); } return Ok(&*dom_class); } trace!("not a dom object"); Err(()) } /// Returns whether `obj` is a DOM object implemented as a proxy. /// /// # Safety /// obj must point to a valid, non-null JS object. pub unsafe fn is_dom_proxy(obj: *mut JSObject) -> bool { unsafe { let clasp = get_object_class(obj); ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj) } } /// The index of the slot wherein a pointer to the reflected DOM object is /// stored for non-proxy bindings. // We use slot 0 for holding the raw object. This is safe for both // globals and non-globals. pub const DOM_OBJECT_SLOT: u32 = 0; /// Get the private pointer of a DOM object from a given reflector. /// /// # Safety /// obj must point to a valid non-null JS object. pub unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void { let mut value = UndefinedValue(); if is_dom_object(obj) { JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value); } else { debug_assert!(is_dom_proxy(obj)); GetProxyReservedSlot(obj, 0, &mut value); }; if value.is_undefined() { ptr::null() } else { value.to_private() } } pub enum PrototypeCheck { Derive(fn(&'static DOMClass) -> bool), Depth { depth: usize, proto_id: u16 }, } /// Get a `*const libc::c_void` for the given DOM object, unwrapping any /// wrapper around it first, and checking if the object is of the correct type. /// /// Returns Err(()) if `obj` is an opaque security wrapper or if the object is /// not an object for a DOM object of the given type (as defined by the /// proto_id and proto_depth). /// /// # Safety /// obj must point to a valid, non-null JS object. /// cx must point to a valid, non-null JS context. #[inline] #[allow(clippy::result_unit_err)] pub unsafe fn private_from_proto_check( cx: &mut JSContext, mut obj: *mut JSObject, proto_check: PrototypeCheck, ) -> Result<*const libc::c_void, ()> { let dom_class = get_dom_class(obj).or_else(|_| { if IsWrapper(obj) { trace!("found wrapper"); obj = UnwrapObjectDynamic(obj, cx, /* stopAtWindowProxy = */ false); if obj.is_null() { trace!("unwrapping security wrapper failed"); Err(()) } else { assert!(!IsWrapper(obj)); trace!("unwrapped successfully"); get_dom_class(obj) } } else { trace!("not a dom wrapper"); Err(()) } })?; let prototype_matches = match proto_check { PrototypeCheck::Derive(f) => (f)(dom_class), PrototypeCheck::Depth { depth, proto_id } => { dom_class.interface_chain[depth] as u16 == proto_id }, }; if prototype_matches { trace!("good prototype"); Ok(private_from_object(obj)) } else { trace!("bad prototype"); Err(()) } } /// Get a `*const T` for a DOM object accessible from a `JSObject`. /// /// # Safety /// obj must point to a valid, non-null JS object. /// cx must point to a valid, non-null JS context. #[allow(clippy::result_unit_err)] pub unsafe fn native_from_object<T>(cx: &mut JSContext, obj: *mut JSObject) -> Result<*const T, ()> where T: DomObject + IDLInterface, { unsafe { private_from_proto_check(cx, obj, PrototypeCheck::Derive(T::derives)) .map(|ptr| ptr as *const T) } } /// Get a `DomRoot<T>` for the given DOM object, unwrapping any wrapper /// around it first, and checking if the object is of the correct type. /// /// Returns Err(()) if `obj` is an opaque security wrapper or if the object is /// not a reflector for a DOM object of the given type (as defined by the /// proto_id and proto_depth). /// /// # Safety /// obj must point to a valid, non-null JS object. /// cx must point to a valid, non-null JS context. #[allow(clippy::result_unit_err)] pub unsafe fn root_from_object<T>(cx: &mut JSContext, obj: *mut JSObject) -> Result<DomRoot<T>, ()> where T: DomObject + IDLInterface, { native_from_object(cx, obj).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) }) } /// Get a `DomRoot<T>` for a DOM object accessible from a `HandleValue`. /// Caller is responsible for throwing a JS exception if needed in case of error. /// /// # Safety /// cx must point to a valid, non-null JS context. #[allow(clippy::result_unit_err)] pub fn root_from_handlevalue<T>(cx: &mut JSContext, v: HandleValue) -> Result<DomRoot<T>, ()> where T: DomObject + IDLInterface, { if !v.get().is_object() { return Err(()); } #[expect(unsafe_code)] unsafe { root_from_object(cx, v.get().to_object()) } } /// Convert `id` to a `DOMString`. Returns `None` if `id` is not a string or /// integer. /// /// Handling of invalid UTF-16 in strings depends on the relevant option. pub fn jsid_to_string(cx: &js::context::JSContext, id: HandleId) -> Option<DOMString> { let id_raw = *id; if id_raw.is_string() { let jsstr = ptr::NonNull::new(id_raw.to_string()).unwrap(); return Some(unsafe { jsstr_to_string(cx, jsstr) }.into()); } if id_raw.is_int() { return Some(id_raw.to_int().to_string().into()); } None } impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> { #[inline] fn safe_to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) { let value = **self; value.safe_to_jsval(cx, rval); } } impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> { type Config = (); fn safe_from_jsval( cx: &mut JSContext, value: HandleValue, option: (), ) -> Result<ConversionResult<Finite<T>>, ()> { let result = match FromJSValConvertible::safe_from_jsval(cx, value, option)? { ConversionResult::Success(v) => v, ConversionResult::Failure(error) => { // FIXME(emilio): Why throwing instead of propagating the error? unsafe { throw_type_error(cx.raw_cx(), &error) }; return Err(()); }, }; match Finite::new(result) { Some(v) => Ok(ConversionResult::Success(v)), None => { unsafe { throw_type_error( cx.raw_cx(), c"this argument is not a finite floating-point value", ) }; Err(()) }, } } } /// Get a `*const libc::c_void` for the given DOM object, unless it is a DOM /// wrapper, and checking if the object is of the correct type. /// /// Returns Err(()) if `obj` is a wrapper or if the object is not an object /// for a DOM object of the given type (as defined by the proto_id and proto_depth). #[inline] #[allow(clippy::result_unit_err)] unsafe fn private_from_proto_check_static( obj: *mut JSObject, proto_check: fn(&'static DOMClass) -> bool, ) -> Result<*const libc::c_void, ()> { let dom_class = get_dom_class(obj).map_err(|_| ())?; if proto_check(dom_class) { trace!("good prototype"); Ok(private_from_object(obj)) } else { trace!("bad prototype"); Err(()) } } /// Get a `*const T` for a DOM object accessible from a `JSObject`, where the DOM object /// is guaranteed not to be a wrapper. /// /// # Safety /// `obj` must point to a valid, non-null JSObject. #[allow(clippy::result_unit_err)] pub unsafe fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()> where T: DomObject + IDLInterface, { private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T) } /// Get a `*const T` for a DOM object accessible from a `HandleValue`. /// Caller is responsible for throwing a JS exception if needed in case of error. /// /// # Safety /// `cx` must point to a valid, non-null JSContext. #[allow(clippy::result_unit_err)] pub fn native_from_handlevalue<T>(cx: &mut JSContext, v: HandleValue) -> Result<*const T, ()> where T: DomObject + IDLInterface, { if !v.get().is_object() { return Err(()); } #[expect(unsafe_code)] unsafe { native_from_object(cx, v.get().to_object()) } } impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> { #[inline] fn safe_to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) { let value = &**self; value.safe_to_jsval(cx, rval); } } impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>> where T: FromJSValConvertible + js::rust::GCMethods + Copy, Heap<T>: JSTraceable + Default, { type Config = T::Config; fn safe_from_jsval( cx: &mut JSContext, value: HandleValue, config: Self::Config, ) -> Result<ConversionResult<Self>, ()> { T::safe_from_jsval(cx, value, config).map(|result| match result { ConversionResult::Success(inner) => { ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner))) }, ConversionResult::Failure(msg) => ConversionResult::Failure(msg), }) } } /// Returns whether `value` is an array-like object (Array, FileList, /// HTMLCollection, HTMLFormControlsCollection, HTMLOptionsCollection, /// NodeList, DOMTokenList). pub fn is_array_like<D: crate::DomTypes>(cx: &mut JSContext, value: HandleValue) -> bool { let mut is_array = false; assert!(unsafe { IsArrayObject(cx, value, &mut is_array) }); if is_array { return true; } let object: *mut JSObject = match FromJSValConvertible::safe_from_jsval(cx, value, ()).unwrap() { ConversionResult::Success(object) => object, _ => return false, }; unsafe { // TODO: HTMLAllCollection if root_from_object::<D::DOMTokenList>(cx, object).is_ok() { return true; } if root_from_object::<D::FileList>(cx, object).is_ok() { return true; } if root_from_object::<D::HTMLCollection>(cx, object).is_ok() { return true; } if root_from_object::<D::HTMLFormControlsCollection>(cx, object).is_ok() { return true; } if root_from_object::<D::HTMLOptionsCollection>(cx, object).is_ok() { return true; } if root_from_object::<D::NodeList>(cx, object).is_ok() { return true; } } false } /// Get a `DomRoot<T>` for a WindowProxy accessible from a `HandleValue`. /// Caller is responsible for throwing a JS exception if needed in case of error. pub(crate) unsafe fn windowproxy_from_handlevalue<D: crate::DomTypes>( v: HandleValue, ) -> Result<DomRoot<D::WindowProxy>, ()> { if !v.get().is_object() { return Err(()); } let object = v.get().to_object(); if !IsWindowProxy(object) { return Err(()); } let mut value = UndefinedValue(); GetProxyReservedSlot(object, 0, &mut value); let ptr = value.to_private() as *const D::WindowProxy; Ok(DomRoot::from_ref(&*ptr)) } #[allow(deprecated)] impl<D: crate::DomTypes> EventModifierInit<D> { pub fn modifiers(&self) -> Modifiers { let mut modifiers = Modifiers::empty(); if self.altKey { modifiers.insert(Modifiers::ALT); } if self.ctrlKey { modifiers.insert(Modifiers::CONTROL); } if self.shiftKey { modifiers.insert(Modifiers::SHIFT); } if self.metaKey { modifiers.insert(Modifiers::META); } if self.keyModifierStateAltGraph { modifiers.insert(Modifiers::ALT_GRAPH); } if self.keyModifierStateCapsLock { modifiers.insert(Modifiers::CAPS_LOCK); } if self.keyModifierStateFn { modifiers.insert(Modifiers::FN); } if self.keyModifierStateFnLock { modifiers.insert(Modifiers::FN_LOCK); } if self.keyModifierStateHyper { modifiers.insert(Modifiers::HYPER); } if self.keyModifierStateNumLock { modifiers.insert(Modifiers::NUM_LOCK); } if self.keyModifierStateScrollLock { modifiers.insert(Modifiers::SCROLL_LOCK); } if self.keyModifierStateSuper { modifiers.insert(Modifiers::SUPER); } if self.keyModifierStateSymbol { modifiers.insert(Modifiers::SYMBOL); } if self.keyModifierStateSymbolLock { modifiers.insert(Modifiers::SYMBOL_LOCK); } modifiers } }