feat(webidl): support lossless DOMString16 in derive bindings

This commit is contained in:
ldm0
2026-10-12 14:45:32 +08:00
committed by Donough Liu
parent 99c24fd8de
commit dcf8cb94ee
2 changed files with 75 additions and 3 deletions
+8 -1
View File
@@ -8,6 +8,7 @@ use crate::attrs::FieldAttrs;
pub(crate) enum ConverterKind {
#[default]
DomString,
DomString16,
UsvString,
ByteString,
BufferSource,
@@ -45,6 +46,7 @@ impl ConverterKind {
pub(crate) fn from_lit(value: &LitStr) -> Result<Self, Error> {
match value.value().as_str() {
"dom_string" => Ok(Self::DomString),
"dom_string16" => Ok(Self::DomString16),
"usv_string" => Ok(Self::UsvString),
"byte_string" => Ok(Self::ByteString),
"buffer_source" => Ok(Self::BufferSource),
@@ -80,6 +82,7 @@ impl ConverterKind {
pub(crate) fn wrapper_type(self, ty: &Type) -> proc_macro2::TokenStream {
match self {
Self::DomString => quote!(::moli_webidl::DomString),
Self::DomString16 => quote!(::moli_webidl::DomString16),
Self::UsvString => quote!(::moli_webidl::UsvString),
Self::ByteString => quote!(::moli_webidl::ByteString),
Self::BufferSource => quote!(::moli_webidl::BufferSource),
@@ -120,6 +123,9 @@ impl ConverterKind {
Self::DomString => {
quote!(::moli_webidl::DomString(::std::convert::Into::into(#expr)))
}
Self::DomString16 => {
quote!(::moli_webidl::DomString16(::std::convert::Into::into(#expr)))
}
Self::UsvString => {
quote!(::moli_webidl::UsvString(::std::convert::Into::into(#expr)))
}
@@ -165,6 +171,7 @@ impl ConverterKind {
Self::BufferSource => quote!(::std::convert::Into::into(#value)),
Self::CallbackFunction | Self::CallbackInterface => value,
Self::DomString
| Self::DomString16
| Self::UsvString
| Self::Octet
| Self::Long
@@ -213,7 +220,7 @@ impl ConverterKind {
}
if attrs.treat_null_as_empty_string {
match self {
Self::DomString | Self::UsvString | Self::ByteString => {
Self::DomString | Self::DomString16 | Self::UsvString | Self::ByteString => {
return Ok(quote!(::moli_webidl::StringOptions {
treat_null_as_empty_string: true,
}));
+67 -2
View File
@@ -1,5 +1,49 @@
use moli_webidl::{Context, DomString, DomString16, StringOptions, UsvString};
#[derive(moli_webidl::WebIdlArgs)]
#[webidl(prefix = "derivedLossless")]
struct DerivedLosslessArgs {
#[webidl(required, converter = "dom_string16")]
value: Vec<u16>,
#[webidl(nullable, converter = "dom_string16")]
optional: Option<Vec<u16>>,
#[webidl(converter = "dom_string16", default = vec![0xdfff])]
defaulted: Vec<u16>,
#[webidl(converter = "dom_string16", treat_null_as_empty_string, default = Vec::new())]
null_as_empty: Vec<u16>,
#[webidl(variadic, converter = "dom_string16")]
tail: Vec<Vec<u16>>,
}
fn derived_lossless<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s>,
) {
let Some(parsed) = moli_webidl::parse_args::<DerivedLosslessArgs>(scope, &args) else {
return;
};
let mut values = Vec::new();
for units in [
Some(parsed.value),
parsed.optional,
Some(parsed.defaulted),
Some(parsed.null_as_empty),
]
.into_iter()
.chain(parsed.tail.into_iter().map(Some))
{
let value = match units {
Some(units) => v8::String::new_from_two_byte(scope, &units, v8::NewStringType::Normal)
.unwrap()
.into(),
None => v8::null(scope).into(),
};
values.push(value);
}
rv.set(v8::Array::new_with_elements(scope, &values).into());
}
fn lossless<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -47,13 +91,19 @@ fn dom_string16_preserves_code_units_and_webidl_conversion_semantics() {
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
let scope = &mut scope.init();
let callee = v8::Context::new(scope, Default::default());
let (lossless_function, utf8_function, type_error) = {
let (lossless_function, utf8_function, derived_function, type_error) = {
let scope = &mut v8::ContextScope::new(scope, callee);
let lossless_function = v8::Function::new(scope, lossless).unwrap();
let utf8_function = v8::Function::new(scope, utf8_boundary).unwrap();
let derived_function = v8::Function::new(scope, derived_lossless).unwrap();
let key = v8::String::new(scope, "TypeError").unwrap();
let type_error = callee.global(scope).get(scope, key.into()).unwrap();
(lossless_function, utf8_function, type_error)
(
lossless_function,
utf8_function,
derived_function,
type_error,
)
};
let caller = v8::Context::new(scope, Default::default());
let scope = &mut v8::ContextScope::new(scope, caller);
@@ -61,6 +111,7 @@ fn dom_string16_preserves_code_units_and_webidl_conversion_semantics() {
for (name, value) in [
("lossless", lossless_function.into()),
("utf8Boundary", utf8_function.into()),
("derivedLossless", derived_function.into()),
("CalleeTypeError", type_error),
] {
let key = v8::String::new(scope, name).unwrap();
@@ -97,6 +148,20 @@ fn dom_string16_preserves_code_units_and_webidl_conversion_semantics() {
assert(utf8Boundary('\ud800x\udfff') === '\ufffdx\ufffd', 'existing Rust String boundary stays lossy');
assert(utf8Boundary('\ud800x\udfff', true) === '\ufffdx\ufffd', 'USVString applies scalar replacement');
assert(utf8Boundary('\ud83d\ude00') === '\ud83d\ude00', 'UTF-8 boundary preserves valid pairs');
const same = (actual, expected) => actual.length === expected.length && actual.every((value, i) => value === expected[i]);
for (const value of ['\ud800', '\udfff', '\ud800x\udfff', '\ud83d\ude00', '\0']) {
assert(same(derivedLossless(value, value, value, value, value, value), Array(6).fill(value)), 'derived required/optional/variadic UTF-16');
}
assert(same(derivedLossless(null, null, undefined, null), ['null', null, '\udfff', '']), 'derived nullable/default/null-as-empty');
assert(same(derivedLossless(undefined, undefined, undefined, undefined), ['undefined', null, '\udfff', '']), 'optional undefined uses defaults');
assert(thrown(() => derivedLossless()) instanceof CalleeTypeError, 'required arity');
const derivedConversions = [];
const convert = label => ({toString() { derivedConversions.push(label); return '\ud800' + label; }});
const derived = derivedLossless(convert('one'), null, undefined, null, convert('two'), convert('three'));
assert(same(derivedConversions, ['one', 'two', 'three']) && same(derived, ['\ud800one', null, '\udfff', '', '\ud800two', '\ud800three']), 'derived variadic conversion ordering');
derivedConversions.length = 0;
assert(thrown(() => derivedLossless('x', null, undefined, null, convert('one'), {toString() {throw sentinel; }}, convert('last'))) === sentinel && same(derivedConversions, ['one']), 'derived original exception stops remaining conversions');
assert(thrown(() => derivedLossless('x', null, undefined, null, Symbol())) instanceof CalleeTypeError, 'derived conversion TypeError uses callee realm');
return true;
})()
"#).unwrap();