mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 16:07:24 +00:00
feat(webidl): add frame integer, dictionary sequence and row conversions
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = ();
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user