aboutsummaryrefslogtreecommitdiffstats
path: root/components/script_bindings
diff options
context:
space:
mode:
Diffstat (limited to 'components/script_bindings')
-rw-r--r--components/script_bindings/Cargo.toml10
-rw-r--r--components/script_bindings/codegen/CodegenRust.py4
-rw-r--r--components/script_bindings/conversions.rs25
-rw-r--r--components/script_bindings/lib.rs3
-rw-r--r--components/script_bindings/record.rs209
-rw-r--r--components/script_bindings/trace.rs255
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);
+ }
+}