feat(webidl): add frame integer, dictionary sequence and row conversions

This commit is contained in:
ldm0
2026-10-11 18:06:49 +08:00
parent ca924d812a
commit 4fb35a340e
7 changed files with 195 additions and 5 deletions
+9
View File
@@ -18,6 +18,7 @@ pub(crate) enum ConverterKind {
Octet,
Long,
EnforceRangeLong,
LongLong,
EnforceRangeLongLong,
Short,
UnsignedShort,
@@ -56,6 +57,7 @@ impl ConverterKind {
"octet" => Ok(Self::Octet),
"long" => Ok(Self::Long),
"enforce_range_long" => Ok(Self::EnforceRangeLong),
"long_long" => Ok(Self::LongLong),
"enforce_range_long_long" => Ok(Self::EnforceRangeLongLong),
"short" => Ok(Self::Short),
"unsigned_short" => Ok(Self::UnsignedShort),
@@ -71,6 +73,7 @@ impl ConverterKind {
"unrestricted_double" => Ok(Self::UnrestrictedDouble),
"boolean" => Ok(Self::Boolean),
"enum" => Ok(Self::Enum),
"dictionary" => Ok(Self::Dictionary),
"raw" => Ok(Self::Raw),
_ => Err(Error::new(value.span(), "unsupported converter kind")),
}
@@ -91,6 +94,7 @@ impl ConverterKind {
Self::Octet => quote!(::moli_webidl::Octet),
Self::Long => quote!(::moli_webidl::Long),
Self::EnforceRangeLong => quote!(::moli_webidl::EnforceRangeLong),
Self::LongLong => quote!(::moli_webidl::LongLong),
Self::EnforceRangeLongLong => quote!(::moli_webidl::EnforceRangeLongLong),
Self::Short => quote!(::moli_webidl::Short),
Self::UnsignedShort => quote!(::moli_webidl::UnsignedShort),
@@ -137,6 +141,7 @@ impl ConverterKind {
Self::Octet => quote!(::moli_webidl::Octet(#expr)),
Self::Long => quote!(::moli_webidl::Long(#expr)),
Self::EnforceRangeLong => quote!(::moli_webidl::EnforceRangeLong(#expr)),
Self::LongLong => quote!(::moli_webidl::LongLong(#expr)),
Self::EnforceRangeLongLong => quote!(::moli_webidl::EnforceRangeLongLong(#expr)),
Self::Short => quote!(::moli_webidl::Short(#expr)),
Self::UnsignedShort => quote!(::moli_webidl::UnsignedShort(#expr)),
@@ -176,6 +181,7 @@ impl ConverterKind {
| Self::Octet
| Self::Long
| Self::EnforceRangeLong
| Self::LongLong
| Self::EnforceRangeLongLong
| Self::Short
| Self::UnsignedShort
@@ -404,6 +410,9 @@ fn infer_converter_kind(ty: &Type) -> Option<ConverterKind> {
if is_type_ident(ty, "i16") {
return Some(ConverterKind::Short);
}
if is_type_ident(ty, "i64") {
return Some(ConverterKind::LongLong);
}
if is_type_ident(ty, "u64") {
return Some(ConverterKind::UnsignedLongLong);
}
+1
View File
@@ -20,6 +20,7 @@ use syn::{DeriveInput, parse_macro_input};
/// values parse an empty dictionary; optional fields skip missing/undefined.
/// `#[webidl(sequence)]` converts one iterable to `Vec<T>`, using inferred or
/// explicit item conversion, including `interface` and `brand_check` metadata.
/// `sequence, converter = "dictionary"` converts each item as a dictionary.
/// Uint8Array fields support `allow_shared` for fixed shared backing storage.
#[proc_macro_derive(WebIdlArgs, attributes(webidl))]
pub fn derive_webidl_args(input: TokenStream) -> TokenStream {
+15 -2
View File
@@ -165,7 +165,20 @@ impl AllowSharedBufferSource<'_> {
/// Copy into the native view's byte range, preserving surrounding bytes.
pub fn write_bytes(&self, scope: &mut v8::PinScope<'_, '_>, bytes: &[u8]) -> bool {
if bytes.len() > self.byte_length() {
self.write_bytes_at(scope, 0, bytes)
}
/// Copy a row without touching plane padding or other shared-memory bytes.
pub fn write_bytes_at(
&self,
scope: &mut v8::PinScope<'_, '_>,
start: usize,
bytes: &[u8],
) -> bool {
if start
.checked_add(bytes.len())
.is_none_or(|end| end > self.byte_length())
{
return false;
}
if bytes.is_empty() {
@@ -174,7 +187,7 @@ impl AllowSharedBufferSource<'_> {
let backing = self
.backing_store(scope)
.expect("BufferSource has native backing");
let offset = self.byte_offset();
let offset = self.byte_offset() + start;
if backing.is_shared() {
// SAFETY: fixed shared storage is retained and V8 validated the
// view range; byte stores are unordered as required for BufferSource.
+13
View File
@@ -346,6 +346,19 @@ impl<'s> WebIdlConverter<'s> for crate::ClampedUnsignedLong {
}
}
impl<'s> WebIdlConverter<'s> for crate::LongLong {
type Options = ();
fn convert(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
context: Context,
_: &(),
) -> Result<Self, WebIdlError> {
number_value(scope, value, context, "long long")
.map(|value| Self(unsigned_long_long(value) as i64))
}
}
impl<'s> WebIdlConverter<'s> for EnforceRangeLongLong {
type Options = ();
+3 -3
View File
@@ -101,9 +101,9 @@ pub use types::{
Boolean, BufferSource, ByteString, ClampedUnsignedLong, ClampedUnsignedShort, Dictionary,
DomString, DomString16, Double, EnforceRangeLong, EnforceRangeLongLong,
EnforceRangeUnsignedLong, EnforceRangeUnsignedLongLong, EnumValue, EventListenerOptions,
InterfaceObject, InterfaceOptions, Long, Octet, Record, Sequence, Short, StringOptions,
Uint8ArrayOptions, UnrestrictedDouble, UnrestrictedFloat, UnsignedLong, UnsignedLongLong,
UnsignedShort, UsvString,
InterfaceObject, InterfaceOptions, Long, LongLong, Octet, Record, Sequence, Short,
StringOptions, Uint8ArrayOptions, UnrestrictedDouble, UnrestrictedFloat, UnsignedLong,
UnsignedLongLong, UnsignedShort, UsvString,
};
/// Restricted WebIDL float represented as a finite binary32 value.
+9
View File
@@ -320,6 +320,15 @@ impl From<ClampedUnsignedShort> for u16 {
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct LongLong(pub i64);
impl From<LongLong> for i64 {
fn from(value: LongLong) -> Self {
value.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EnforceRangeLongLong(pub i64);
impl From<EnforceRangeLongLong> for i64 {
+145
View File
@@ -0,0 +1,145 @@
use moli_webidl::{Context, WebIdlDictionary};
#[derive(WebIdlDictionary)]
#[webidl(prefix = "LongLongInit")]
struct Init {
#[webidl(default = 0)]
value: i64,
#[webidl(converter = "long_long", default = 0)]
explicit: i64,
}
#[test]
fn long_long_derive_wraps_and_preserves_conversion_errors() {
moli_v8_test_util::ensure_v8();
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
v8::scope!(let scope, &mut isolate);
let context = v8::Context::new(scope, Default::default());
let scope = &mut v8::ContextScope::new(scope, context);
for (expression, expected) in [
("-17.9", -17),
("NaN", 0),
("Infinity", 0),
("undefined", 0),
("2**63", i64::MIN),
("2**64", 0),
("-(2**63)-2048", i64::MAX - 2047),
] {
let source = v8::String::new(
scope,
&format!("({{value:{expression},explicit:{expression}}})"),
)
.unwrap();
let value = v8::Script::compile(scope, source, None)
.unwrap()
.run(scope)
.unwrap();
let init =
moli_webidl::parse_dictionary::<Init>(scope, value, Context::argument("Test", 1))
.unwrap()
.unwrap();
assert_eq!(init.value, expected, "{expression}");
assert_eq!(init.explicit, expected, "{expression}");
}
for source in [
"({value:Symbol(),explicit:0})",
"({value:0,explicit:1n})",
"({get explicit(){throw 42},value:0})",
] {
v8::tc_scope!(let scope, scope);
let source = v8::String::new(scope, source).unwrap();
let value = v8::Script::compile(scope, source, None)
.unwrap()
.run(scope)
.unwrap();
assert!(
moli_webidl::parse_dictionary::<Init>(scope, value, Context::argument("Test", 1))
.is_err()
);
}
}
#[test]
fn buffer_source_row_writes_preserve_padding_and_reject_out_of_bounds() {
moli_v8_test_util::ensure_v8();
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
v8::scope!(let scope, &mut isolate);
let context = v8::Context::new(scope, Default::default());
let scope = &mut v8::ContextScope::new(scope, context);
for source in [
"new DataView(new ArrayBuffer(8),2,4)",
"new DataView(new SharedArrayBuffer(8),2,4)",
] {
let source = v8::String::new(scope, source).unwrap();
let value = v8::Script::compile(scope, source, None)
.unwrap()
.run(scope)
.unwrap();
let buffer = moli_webidl::convert::<moli_webidl::AllowSharedBufferSource>(
scope,
value,
Context::argument("Test", 1),
)
.unwrap();
assert!(buffer.write_bytes(scope, &[7, 7, 7, 7]));
assert!(buffer.write_bytes_at(scope, 1, &[1, 2]));
assert_eq!(buffer.to_vec(scope), vec![7, 1, 2, 7]);
assert!(!buffer.write_bytes_at(scope, 3, &[1, 2]));
assert!(!buffer.write_bytes_at(scope, usize::MAX, &[1]));
assert!(buffer.write_bytes_at(scope, 4, &[]));
assert_eq!(buffer.to_vec(scope), vec![7, 1, 2, 7]);
}
}
#[derive(WebIdlDictionary)]
#[webidl(prefix = "Plane")]
struct Plane {
#[webidl(required)]
offset: u32,
}
#[derive(WebIdlDictionary)]
#[webidl(prefix = "Planes")]
struct Planes {
#[webidl(sequence, converter = "dictionary")]
layout: Option<Vec<Plane>>,
}
#[test]
fn sequence_dictionary_conversion_observes_iteration_and_required_members() {
moli_v8_test_util::ensure_v8();
let mut isolate = v8::Isolate::new(Default::default());
v8::scope!(let scope, &mut isolate);
let context = v8::Context::new(scope, Default::default());
let scope = &mut v8::ContextScope::new(scope, context);
for (source, expected) in [
("({layout:[{offset:3},{offset:7}]})", Some(vec![3, 7])),
("({layout:[]})", Some(vec![])),
("({})", None),
] {
let source = v8::String::new(scope, source).unwrap();
let value = v8::Script::compile(scope, source, None)
.unwrap()
.run(scope)
.unwrap();
let planes =
moli_webidl::parse_dictionary::<Planes>(scope, value, Context::argument("Test", 1))
.unwrap()
.unwrap();
assert_eq!(
planes
.layout
.map(|list| list.into_iter().map(|p| p.offset).collect::<Vec<_>>()),
expected
);
}
for source in ["({layout:[null]})", "({layout:[{}]})", "({layout:{}})"] {
let source = v8::String::new(scope, source).unwrap();
let value = v8::Script::compile(scope, source, None)
.unwrap()
.run(scope)
.unwrap();
assert!(
moli_webidl::parse_dictionary::<Planes>(scope, value, Context::argument("Test", 1))
.is_err()
);
}
}