diff options
author | Josh Matthews <josh@joshmatthews.net> | 2025-03-16 10:08:22 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-16 14:08:22 +0000 |
commit | c8d878795966f0b685385e6961e0d69df4268734 (patch) | |
tree | 3560dee84d5ed70506b1de90910d361868b868dd /components/script_bindings | |
parent | d35da38a2fd6f093967e74f704612391b4988e69 (diff) | |
download | servo-c8d878795966f0b685385e6961e0d69df4268734.tar.gz servo-c8d878795966f0b685385e6961e0d69df4268734.zip |
Move CustomTraceable to script_bindings. (#35988)
* script: Move CustomTraceable to script_bindings.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* script: Move record binding support to script_bindings.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
* Address clippy warnings.
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
---------
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
Diffstat (limited to 'components/script_bindings')
-rw-r--r-- | components/script_bindings/Cargo.toml | 10 | ||||
-rw-r--r-- | components/script_bindings/codegen/CodegenRust.py | 4 | ||||
-rw-r--r-- | components/script_bindings/conversions.rs | 25 | ||||
-rw-r--r-- | components/script_bindings/lib.rs | 3 | ||||
-rw-r--r-- | components/script_bindings/record.rs | 209 | ||||
-rw-r--r-- | components/script_bindings/trace.rs | 255 |
6 files changed, 501 insertions, 5 deletions
diff --git a/components/script_bindings/Cargo.toml b/components/script_bindings/Cargo.toml index 3f022f74480..c911a0d4730 100644 --- a/components/script_bindings/Cargo.toml +++ b/components/script_bindings/Cargo.toml @@ -22,8 +22,10 @@ serde_json = { workspace = true } [dependencies] bitflags = { workspace = true } +crossbeam-channel = { workspace = true } cssparser = { workspace = true } html5ever = { workspace = true } +indexmap = { workspace = true } js = { workspace = true } jstraceable_derive = { path = "../jstraceable_derive" } libc = { workspace = true } @@ -31,15 +33,21 @@ log = { workspace = true } malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } num-traits = { workspace = true } +parking_lot = { workspace = true } regex = { workspace = true } +servo_arc = { workspace = true } +smallvec = { workspace = true } stylo_atoms = { workspace = true } servo_config = { path = "../config" } style = { workspace = true } +tendril = { version = "0.4.1", features = ["encoding_rs"] } +webxr-api = { workspace = true, optional = true } +xml5ever = { workspace = true } [features] bluetooth = [] webgpu = [] -webxr = [] +webxr = ["webxr-api"] [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(crown)'] } diff --git a/components/script_bindings/codegen/CodegenRust.py b/components/script_bindings/codegen/CodegenRust.py index e77709918f4..6b5c77c2c20 100644 --- a/components/script_bindings/codegen/CodegenRust.py +++ b/components/script_bindings/codegen/CodegenRust.py @@ -2287,7 +2287,7 @@ class CGImports(CGWrapper): extras += [descriptor.path, descriptor.bindingPath] parentName = descriptor.getParentName() elif t.isType() and t.isRecord(): - extras += ['crate::dom::bindings::record::Record'] + extras += ['script_bindings::record::Record'] elif isinstance(t, IDLPromiseType): extras += ['crate::dom::promise::Promise'] else: @@ -2643,7 +2643,7 @@ def UnionTypes(descriptors, dictionaries, callbacks, typedefs, config): 'crate::dom::bindings::import::base::*', 'crate::dom::bindings::codegen::DomTypes::DomTypes', 'crate::dom::bindings::conversions::windowproxy_from_handlevalue', - 'crate::dom::bindings::record::Record', + 'script_bindings::record::Record', 'crate::dom::types::*', 'crate::dom::windowproxy::WindowProxy', 'js::typedarray', diff --git a/components/script_bindings/conversions.rs b/components/script_bindings/conversions.rs index 98998e170da..6e0ce7adee0 100644 --- a/components/script_bindings/conversions.rs +++ b/components/script_bindings/conversions.rs @@ -18,8 +18,8 @@ use js::jsapi::{ }; use js::jsval::{ObjectValue, StringValue, UndefinedValue}; use js::rust::{ - HandleValue, MutableHandleValue, ToString, get_object_class, is_dom_class, is_dom_object, - maybe_wrap_value, + HandleId, HandleValue, MutableHandleValue, ToString, get_object_class, is_dom_class, + is_dom_object, maybe_wrap_value, }; use crate::inheritance::Castable; @@ -387,3 +387,24 @@ where } root_from_object(v.get().to_object(), cx) } + +/// 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. +/// +/// # Safety +/// - cx must point to a non-null, valid JSContext instance. +pub unsafe fn jsid_to_string(cx: *mut JSContext, id: HandleId) -> Option<DOMString> { + let id_raw = *id; + if id_raw.is_string() { + let jsstr = std::ptr::NonNull::new(id_raw.to_string()).unwrap(); + return Some(jsstring_to_str(cx, jsstr)); + } + + if id_raw.is_int() { + return Some(id_raw.to_int().to_string().into()); + } + + None +} diff --git a/components/script_bindings/lib.rs b/components/script_bindings/lib.rs index 8090144b73b..e857a2289f8 100644 --- a/components/script_bindings/lib.rs +++ b/components/script_bindings/lib.rs @@ -24,6 +24,7 @@ pub mod error; pub mod inheritance; pub mod iterable; pub mod like; +pub mod record; pub mod reflector; pub mod root; pub mod script_runtime; @@ -51,3 +52,5 @@ pub mod codegen { // Since they are used in derive macros, // it is useful that they are accessible at the root of the crate. pub(crate) use js::gc::Traceable as JSTraceable; + +pub(crate) use crate::trace::CustomTraceable; diff --git a/components/script_bindings/record.rs b/components/script_bindings/record.rs new file mode 100644 index 00000000000..2668a84f42c --- /dev/null +++ b/components/script_bindings/record.rs @@ -0,0 +1,209 @@ +/* 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/. */ + +//! The `Record` (open-ended dictionary) type. + +use std::cmp::Eq; +use std::hash::Hash; +use std::marker::Sized; +use std::ops::Deref; + +use indexmap::IndexMap; +use js::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible}; +use js::jsapi::glue::JS_GetOwnPropertyDescriptorById; +use js::jsapi::{ + HandleId as RawHandleId, JS_NewPlainObject, JSContext, JSITER_HIDDEN, JSITER_OWNONLY, + JSITER_SYMBOLS, JSPROP_ENUMERATE, PropertyDescriptor, +}; +use js::jsval::{ObjectValue, UndefinedValue}; +use js::rooted; +use js::rust::wrappers::{GetPropertyKeys, JS_DefineUCProperty2, JS_GetPropertyById, JS_IdToValue}; +use js::rust::{HandleId, HandleValue, IdVector, MutableHandleValue}; + +use crate::conversions::jsid_to_string; +use crate::str::{ByteString, DOMString, USVString}; + +pub trait RecordKey: Eq + Hash + Sized { + fn to_utf16_vec(&self) -> Vec<u16>; + + /// Attempt to extract a key from a JS id. + /// # Safety + /// - cx must point to a non-null, valid JSContext. + #[allow(clippy::result_unit_err)] + unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()>; +} + +impl RecordKey for DOMString { + fn to_utf16_vec(&self) -> Vec<u16> { + self.encode_utf16().collect::<Vec<_>>() + } + + unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> { + match jsid_to_string(cx, id) { + Some(s) => Ok(ConversionResult::Success(s)), + None => Ok(ConversionResult::Failure("Failed to get DOMString".into())), + } + } +} + +impl RecordKey for USVString { + fn to_utf16_vec(&self) -> Vec<u16> { + self.0.encode_utf16().collect::<Vec<_>>() + } + + unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> { + rooted!(in(cx) let mut jsid_value = UndefinedValue()); + let raw_id: RawHandleId = id.into(); + JS_IdToValue(cx, *raw_id.ptr, jsid_value.handle_mut()); + + USVString::from_jsval(cx, jsid_value.handle(), ()) + } +} + +impl RecordKey for ByteString { + fn to_utf16_vec(&self) -> Vec<u16> { + self.iter().map(|&x| x as u16).collect::<Vec<u16>>() + } + + unsafe fn from_id(cx: *mut JSContext, id: HandleId) -> Result<ConversionResult<Self>, ()> { + rooted!(in(cx) let mut jsid_value = UndefinedValue()); + let raw_id: RawHandleId = id.into(); + JS_IdToValue(cx, *raw_id.ptr, jsid_value.handle_mut()); + + ByteString::from_jsval(cx, jsid_value.handle(), ()) + } +} + +/// The `Record` (open-ended dictionary) type. +#[derive(Clone, JSTraceable)] +pub struct Record<K: RecordKey, V> { + #[custom_trace] + map: IndexMap<K, V>, +} + +impl<K: RecordKey, V> Record<K, V> { + /// Create an empty `Record`. + pub fn new() -> Self { + Record { + map: IndexMap::new(), + } + } +} + +impl<K: RecordKey, V> Deref for Record<K, V> { + type Target = IndexMap<K, V>; + + fn deref(&self) -> &IndexMap<K, V> { + &self.map + } +} + +impl<K, V, C> FromJSValConvertible for Record<K, V> +where + K: RecordKey, + V: FromJSValConvertible<Config = C>, + C: Clone, +{ + type Config = C; + unsafe fn from_jsval( + cx: *mut JSContext, + value: HandleValue, + config: C, + ) -> Result<ConversionResult<Self>, ()> { + if !value.is_object() { + return Ok(ConversionResult::Failure( + "Record value was not an object".into(), + )); + } + + rooted!(in(cx) let object = value.to_object()); + let mut ids = IdVector::new(cx); + if !GetPropertyKeys( + cx, + object.handle(), + JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, + ids.handle_mut(), + ) { + return Err(()); + } + + let mut map = IndexMap::new(); + for id in &*ids { + rooted!(in(cx) let id = *id); + rooted!(in(cx) let mut desc = PropertyDescriptor::default()); + + let mut is_none = false; + if !JS_GetOwnPropertyDescriptorById( + cx, + object.handle().into(), + id.handle().into(), + desc.handle_mut().into(), + &mut is_none, + ) { + return Err(()); + } + + if !desc.enumerable_() { + continue; + } + + let key = match K::from_id(cx, id.handle())? { + ConversionResult::Success(key) => key, + ConversionResult::Failure(message) => { + return Ok(ConversionResult::Failure(message)); + }, + }; + + rooted!(in(cx) let mut property = UndefinedValue()); + if !JS_GetPropertyById(cx, object.handle(), id.handle(), property.handle_mut()) { + return Err(()); + } + + let property = match V::from_jsval(cx, property.handle(), config.clone())? { + ConversionResult::Success(property) => property, + ConversionResult::Failure(message) => { + return Ok(ConversionResult::Failure(message)); + }, + }; + map.insert(key, property); + } + + Ok(ConversionResult::Success(Record { map })) + } +} + +impl<K, V> ToJSValConvertible for Record<K, V> +where + K: RecordKey, + V: ToJSValConvertible, +{ + #[inline] + unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) { + rooted!(in(cx) let js_object = JS_NewPlainObject(cx)); + assert!(!js_object.handle().is_null()); + + rooted!(in(cx) let mut js_value = UndefinedValue()); + for (key, value) in &self.map { + let key = key.to_utf16_vec(); + value.to_jsval(cx, js_value.handle_mut()); + + assert!(JS_DefineUCProperty2( + cx, + js_object.handle(), + key.as_ptr(), + key.len(), + js_value.handle(), + JSPROP_ENUMERATE as u32 + )); + } + + rval.set(ObjectValue(js_object.handle().get())); + } +} + +impl<K: RecordKey, V> Default for Record<K, V> { + fn default() -> Self { + Self::new() + } +} diff --git a/components/script_bindings/trace.rs b/components/script_bindings/trace.rs index 04819803d33..3248fbfc67c 100644 --- a/components/script_bindings/trace.rs +++ b/components/script_bindings/trace.rs @@ -2,9 +2,32 @@ * 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::cell::OnceCell; +use std::hash::{BuildHasher, Hash}; +use std::marker::PhantomData; + +use crossbeam_channel::Sender; +use html5ever::interface::{Tracer as HtmlTracer, TreeSink}; +use html5ever::tokenizer::{TokenSink, Tokenizer}; +use html5ever::tree_builder::TreeBuilder; +use indexmap::IndexMap; use js::glue::CallObjectTracer; use js::jsapi::{GCTraceKindToAscii, Heap, JSObject, JSTracer, TraceKind}; +use parking_lot::RwLock; +use servo_arc::Arc as ServoArc; +use smallvec::SmallVec; +use style::author_styles::AuthorStyles; +use style::stylesheet_set::{AuthorStylesheetSet, DocumentStylesheetSet}; +use tendril::TendrilSink; +use tendril::fmt::UTF8; +use tendril::stream::LossyDecoder; +#[cfg(feature = "webxr")] +use webxr_api::{Finger, Hand}; +use xml5ever::interface::TreeSink as XmlTreeSink; +use xml5ever::tokenizer::XmlTokenizer; +use xml5ever::tree_builder::{Tracer as XmlTracer, XmlTreeBuilder}; +use crate::JSTraceable; use crate::error::Error; use crate::reflector::Reflector; use crate::str::{DOMString, USVString}; @@ -53,3 +76,235 @@ macro_rules! unsafe_no_jsmanaged_fields( unsafe_no_jsmanaged_fields!(DOMString); unsafe_no_jsmanaged_fields!(USVString); unsafe_no_jsmanaged_fields!(Error); + +/// A trait to allow tracing only DOM sub-objects. +/// +/// # Safety +/// +/// This trait is unsafe; if it is implemented incorrectly, the GC may end up collecting objects +/// that are still reachable. +pub unsafe trait CustomTraceable { + /// Trace `self`. + /// + /// # Safety + /// + /// The `JSTracer` argument must point to a valid `JSTracer` in memory. In addition, + /// implementors of this method must ensure that all active objects are properly traced + /// or else the garbage collector may end up collecting objects that are still reachable. + unsafe fn trace(&self, trc: *mut JSTracer); +} + +unsafe impl<T: CustomTraceable> CustomTraceable for Box<T> { + #[inline] + unsafe fn trace(&self, trc: *mut JSTracer) { + (**self).trace(trc); + } +} + +unsafe impl<T: JSTraceable> CustomTraceable for OnceCell<T> { + unsafe fn trace(&self, tracer: *mut JSTracer) { + if let Some(value) = self.get() { + value.trace(tracer) + } + } +} + +unsafe impl<T> CustomTraceable for Sender<T> { + unsafe fn trace(&self, _: *mut JSTracer) {} +} + +unsafe impl<T: JSTraceable> CustomTraceable for ServoArc<T> { + unsafe fn trace(&self, trc: *mut JSTracer) { + (**self).trace(trc) + } +} + +unsafe impl<T: JSTraceable> CustomTraceable for RwLock<T> { + unsafe fn trace(&self, trc: *mut JSTracer) { + self.read().trace(trc) + } +} + +unsafe impl<T: JSTraceable + Eq + Hash> CustomTraceable for indexmap::IndexSet<T> { + #[inline] + unsafe fn trace(&self, trc: *mut JSTracer) { + for e in self.iter() { + e.trace(trc); + } + } +} + +// XXXManishearth Check if the following three are optimized to no-ops +// if e.trace() is a no-op (e.g it is an unsafe_no_jsmanaged_fields type) +unsafe impl<T: JSTraceable + 'static> CustomTraceable for SmallVec<[T; 1]> { + #[inline] + unsafe fn trace(&self, trc: *mut JSTracer) { + for e in self.iter() { + e.trace(trc); + } + } +} + +unsafe impl<K, V, S> CustomTraceable for IndexMap<K, V, S> +where + K: Hash + Eq + JSTraceable, + V: JSTraceable, + S: BuildHasher, +{ + #[inline] + unsafe fn trace(&self, trc: *mut JSTracer) { + for (k, v) in self { + k.trace(trc); + v.trace(trc); + } + } +} + +unsafe impl<S> CustomTraceable for DocumentStylesheetSet<S> +where + S: JSTraceable + ::style::stylesheets::StylesheetInDocument + PartialEq + 'static, +{ + unsafe fn trace(&self, tracer: *mut JSTracer) { + for (s, _origin) in self.iter() { + s.trace(tracer) + } + } +} + +unsafe impl<S> CustomTraceable for AuthorStylesheetSet<S> +where + S: JSTraceable + ::style::stylesheets::StylesheetInDocument + PartialEq + 'static, +{ + unsafe fn trace(&self, tracer: *mut JSTracer) { + for s in self.iter() { + s.trace(tracer) + } + } +} + +unsafe impl<S> CustomTraceable for AuthorStyles<S> +where + S: JSTraceable + ::style::stylesheets::StylesheetInDocument + PartialEq + 'static, +{ + unsafe fn trace(&self, tracer: *mut JSTracer) { + self.stylesheets.trace(tracer) + } +} + +unsafe impl<Sink> CustomTraceable for LossyDecoder<Sink> +where + Sink: JSTraceable + TendrilSink<UTF8>, +{ + unsafe fn trace(&self, tracer: *mut JSTracer) { + self.inner_sink().trace(tracer); + } +} + +#[cfg(feature = "webxr")] +unsafe impl<J> CustomTraceable for Hand<J> +where + J: JSTraceable, +{ + #[inline] + unsafe fn trace(&self, trc: *mut JSTracer) { + // exhaustive match so we don't miss new fields + let Hand { + ref wrist, + ref thumb_metacarpal, + ref thumb_phalanx_proximal, + ref thumb_phalanx_distal, + ref thumb_phalanx_tip, + ref index, + ref middle, + ref ring, + ref little, + } = *self; + wrist.trace(trc); + thumb_metacarpal.trace(trc); + thumb_phalanx_proximal.trace(trc); + thumb_phalanx_distal.trace(trc); + thumb_phalanx_tip.trace(trc); + index.trace(trc); + middle.trace(trc); + ring.trace(trc); + little.trace(trc); + } +} + +#[cfg(feature = "webxr")] +unsafe impl<J> CustomTraceable for Finger<J> +where + J: JSTraceable, +{ + #[inline] + unsafe fn trace(&self, trc: *mut JSTracer) { + // exhaustive match so we don't miss new fields + let Finger { + ref metacarpal, + ref phalanx_proximal, + ref phalanx_intermediate, + ref phalanx_distal, + ref phalanx_tip, + } = *self; + metacarpal.trace(trc); + phalanx_proximal.trace(trc); + phalanx_intermediate.trace(trc); + phalanx_distal.trace(trc); + phalanx_tip.trace(trc); + } +} + +unsafe impl<Handle: JSTraceable + Clone, Sink: TreeSink<Handle = Handle> + JSTraceable> + CustomTraceable for TreeBuilder<Handle, Sink> +{ + unsafe fn trace(&self, trc: *mut JSTracer) { + struct Tracer<Handle>(*mut JSTracer, PhantomData<Handle>); + let tracer = Tracer::<Handle>(trc, PhantomData); + + impl<Handle: JSTraceable> HtmlTracer for Tracer<Handle> { + type Handle = Handle; + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + fn trace_handle(&self, node: &Handle) { + unsafe { + node.trace(self.0); + } + } + } + + self.trace_handles(&tracer); + self.sink.trace(trc); + } +} + +#[allow(unsafe_code)] +unsafe impl<Handle: JSTraceable + Clone, Sink: TokenSink<Handle = Handle> + CustomTraceable> + CustomTraceable for Tokenizer<Sink> +{ + unsafe fn trace(&self, trc: *mut JSTracer) { + self.sink.trace(trc); + } +} + +#[allow(unsafe_code)] +unsafe impl<Handle: JSTraceable + Clone, Sink: JSTraceable + XmlTreeSink<Handle = Handle>> + CustomTraceable for XmlTokenizer<XmlTreeBuilder<Handle, Sink>> +{ + unsafe fn trace(&self, trc: *mut JSTracer) { + struct Tracer<Handle>(*mut JSTracer, PhantomData<Handle>); + let tracer = Tracer(trc, PhantomData); + + impl<Handle: JSTraceable> XmlTracer for Tracer<Handle> { + type Handle = Handle; + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + fn trace_handle(&self, node: &Handle) { + unsafe { + node.trace(self.0); + } + } + } + + let tree_builder = &self.sink; + tree_builder.trace_handles(&tracer); + tree_builder.sink.trace(trc); + } +} |