mirror of
https://github.com/lexmount/moli.git
synced 2026-10-06 00:00:56 +00:00
1572 lines
51 KiB
Rust
1572 lines
51 KiB
Rust
use crate::{
|
|
Boolean, BufferSource, ByteString, ClampedUnsignedShort, Context, DomString, Double,
|
|
EnforceRangeLong, EnforceRangeUnsignedLong, EnforceRangeUnsignedLongLong, EnumValue, Long,
|
|
Record, Sequence, StringOptions, UnrestrictedDouble, UnsignedLong, UnsignedLongLong,
|
|
UnsignedShort, UsvString, WebIdlArguments, WebIdlConverter, WebIdlDictionary, WebIdlEnum,
|
|
WebIdlError, WebIdlErrorKind, dictionary_value, is_nullish, property_result,
|
|
symbol_property_result, throw_error, throw_type_error,
|
|
};
|
|
|
|
impl<'s> WebIdlConverter<'s> for DomString {
|
|
type Options = StringOptions;
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
if value.is_null() && options.treat_null_as_empty_string {
|
|
return Ok(Self(String::new()));
|
|
}
|
|
if value.is_symbol() {
|
|
return Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("DOMString"),
|
|
));
|
|
}
|
|
string_value_utf16(scope, value, context, "DOMString")
|
|
.map(|value| Self(String::from_utf16_lossy(&value)))
|
|
}
|
|
}
|
|
|
|
// String conversion first captures V8's UTF-16 result so `DOMString`,
|
|
// `USVString`, and `ByteString` can apply their different WebIDL post-processing
|
|
// rules. This keeps getter/toString side effects in one V8-facing helper while
|
|
// making the scalar-value and byte restrictions explicit in Rust.
|
|
impl<'s> WebIdlConverter<'s> for UsvString {
|
|
type Options = StringOptions;
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
if value.is_null() && options.treat_null_as_empty_string {
|
|
return Ok(Self(String::new()));
|
|
}
|
|
if value.is_symbol() {
|
|
return Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("USVString"),
|
|
));
|
|
}
|
|
string_value_utf16(scope, value, context, "USVString")
|
|
.map(|value| Self(scalar_value_string_from_utf16(&value)))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for ByteString {
|
|
type Options = StringOptions;
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
let value = DomString::convert(scope, value, context, options)?.0;
|
|
let mut bytes = Vec::with_capacity(value.len());
|
|
for ch in value.chars() {
|
|
let code_point = ch as u32;
|
|
if code_point > 0xff {
|
|
return Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::InvalidByteString,
|
|
));
|
|
}
|
|
bytes.push(code_point as u8);
|
|
}
|
|
Ok(Self(bytes))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for BufferSource {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
if let Ok(view) = v8::Local::<v8::ArrayBufferView>::try_from(value) {
|
|
let mut bytes = vec![0; view.byte_length()];
|
|
let written = view.copy_contents(&mut bytes);
|
|
bytes.truncate(written);
|
|
return Ok(Self(bytes));
|
|
}
|
|
|
|
if let Ok(buffer) = v8::Local::<v8::ArrayBuffer>::try_from(value) {
|
|
let view =
|
|
v8::Uint8Array::new(scope, buffer, 0, buffer.byte_length()).ok_or_else(|| {
|
|
WebIdlError::new(context, WebIdlErrorKind::CannotConvert("BufferSource"))
|
|
})?;
|
|
let mut bytes = vec![0; view.byte_length()];
|
|
let written = view.copy_contents(&mut bytes);
|
|
bytes.truncate(written);
|
|
return Ok(Self(bytes));
|
|
}
|
|
|
|
if let Ok(buffer) = v8::Local::<v8::SharedArrayBuffer>::try_from(value) {
|
|
let backing_store = buffer.get_backing_store();
|
|
let mut bytes = Vec::with_capacity(backing_store.byte_length());
|
|
for index in 0..backing_store.byte_length() {
|
|
bytes.push(backing_store[index].get());
|
|
}
|
|
return Ok(Self(bytes));
|
|
}
|
|
|
|
Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("BufferSource"),
|
|
))
|
|
}
|
|
}
|
|
|
|
// Numeric converters follow WebIDL's default wrapping behavior unless the type
|
|
// name carries an explicit extended attribute such as `[EnforceRange]` or
|
|
// `[Clamp]`. Call sites that need browser rejection for out-of-range values
|
|
// should choose an EnforceRange wrapper instead of checking after conversion.
|
|
impl<'s> WebIdlConverter<'s> for Boolean {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
_context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
Ok(Self(value.boolean_value(scope)))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for UnrestrictedDouble {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
number_value(scope, value, context, "unrestricted double").map(Self)
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for Double {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
let value = number_value(scope, value, context, "double")?;
|
|
if value.is_finite() {
|
|
Ok(Self(value))
|
|
} else {
|
|
Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("double"),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for Long {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
number_value(scope, value, context, "long").map(|value| Self(long(value)))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for EnforceRangeLong {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
let value = number_value(scope, value, context, "[EnforceRange] long")?;
|
|
enforce_range_long(value, context).map(Self)
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for UnsignedLong {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
number_value(scope, value, context, "unsigned long").map(|value| Self(unsigned_long(value)))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for EnforceRangeUnsignedLong {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
let value = number_value(scope, value, context, "[EnforceRange] unsigned long")?;
|
|
enforce_range_unsigned_long(value, context).map(Self)
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for UnsignedShort {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
number_value(scope, value, context, "unsigned short")
|
|
.map(|value| Self(unsigned_short(value)))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for ClampedUnsignedShort {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
number_value(scope, value, context, "[Clamp] unsigned short")
|
|
.map(|value| Self(clamped_unsigned_short(value)))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for UnsignedLongLong {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
number_value(scope, value, context, "unsigned long long")
|
|
.map(|value| Self(unsigned_long_long(value)))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for EnforceRangeUnsignedLongLong {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
let value = number_value(scope, value, context, "[EnforceRange] unsigned long long")?;
|
|
enforce_range_unsigned_long_long(value, context).map(Self)
|
|
}
|
|
}
|
|
|
|
impl<'s, T> WebIdlConverter<'s> for EnumValue<T>
|
|
where
|
|
T: WebIdlEnum,
|
|
{
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
let value = DomString::convert(scope, value, context, &StringOptions::default())?.0;
|
|
T::parse_token(&value)
|
|
.map(Self)
|
|
.ok_or_else(|| WebIdlError::new(context, WebIdlErrorKind::InvalidEnum(T::NAME)))
|
|
}
|
|
}
|
|
|
|
impl<'s, T> WebIdlConverter<'s> for Sequence<T>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
type Options = T::Options;
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
convert_optional_sequence(scope, value, context, options)?
|
|
.ok_or_else(|| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("sequence")))
|
|
}
|
|
}
|
|
|
|
/// Converts an iterable to a WebIDL sequence, or returns `None` when the value
|
|
/// has no `@@iterator` method.
|
|
///
|
|
/// This is the union-discrimination form of sequence conversion. It reads
|
|
/// `@@iterator` exactly once, so callers can fall back to another union member
|
|
/// without probing the property separately and then converting it again.
|
|
pub fn convert_optional_sequence<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
options: &T::Options,
|
|
) -> Result<Option<Sequence<T>>, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
let Ok(object) = v8::Local::<v8::Object>::try_from(value) else {
|
|
return Ok(None);
|
|
};
|
|
let iterator_key = v8::Symbol::get_iterator(scope);
|
|
let Some(iterator_method) = symbol_property_result(scope, object, iterator_key, context)?
|
|
.filter(|value| !value.is_null_or_undefined())
|
|
else {
|
|
return Ok(None);
|
|
};
|
|
let iterator_method = v8::Local::<v8::Function>::try_from(iterator_method)
|
|
.map_err(|_| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("sequence")))?;
|
|
let (iterator, next_method) =
|
|
sequence_iterator_from_method(scope, value, iterator_method, context)?;
|
|
let mut values = Vec::new();
|
|
while let Some(item) = sequence_iterator_next(scope, iterator, next_method, context)? {
|
|
let (converted, caught_exception) = {
|
|
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
|
|
let mut conversion_scope = try_catch.init();
|
|
let converted = T::convert(&mut conversion_scope, item, context, options);
|
|
let caught_exception = conversion_scope
|
|
.has_caught()
|
|
.then(|| conversion_scope.exception())
|
|
.flatten()
|
|
.map(|exception| v8::Global::new(&conversion_scope, exception));
|
|
(converted, caught_exception)
|
|
};
|
|
match converted {
|
|
Ok(value) => values.push(value),
|
|
Err(error) => {
|
|
sequence_iterator_close_ignoring_errors(scope, iterator);
|
|
if let Some(exception) = caught_exception {
|
|
let exception = v8::Local::new(scope, &exception);
|
|
scope.throw_exception(exception);
|
|
}
|
|
return Err(error);
|
|
}
|
|
}
|
|
}
|
|
Ok(Some(Sequence(values)))
|
|
}
|
|
|
|
// Records are converted from own property names and then property values. If two
|
|
// JavaScript keys become the same WebIDL key after key conversion, the later
|
|
// property wins, matching the WebIDL record replacement behavior.
|
|
impl<'s, K, V> WebIdlConverter<'s> for Record<K, V>
|
|
where
|
|
K: WebIdlConverter<'s> + PartialEq,
|
|
K::Options: Default,
|
|
V: WebIdlConverter<'s>,
|
|
V::Options: Default,
|
|
{
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
let object = v8::Local::<v8::Object>::try_from(value)
|
|
.map_err(|_| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("record")))?;
|
|
let properties = own_property_names(scope, object, context)?;
|
|
let mut entries: Vec<(K, V)> = Vec::with_capacity(properties.length() as usize);
|
|
for index in 0..properties.length() {
|
|
let key_value = properties.get_index(scope, index).ok_or_else(|| {
|
|
WebIdlError::new(context, WebIdlErrorKind::CannotConvert("record"))
|
|
})?;
|
|
let key = K::convert(scope, key_value, context, &K::Options::default())?;
|
|
let value = record_property_value(scope, object, key_value, context)?;
|
|
let value = V::convert(scope, value, context, &V::Options::default())?;
|
|
if let Some((_, existing_value)) = entries
|
|
.iter_mut()
|
|
.find(|(existing_key, _)| *existing_key == key)
|
|
{
|
|
*existing_value = value;
|
|
} else {
|
|
entries.push((key, value));
|
|
}
|
|
}
|
|
Ok(Self(entries))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for v8::Local<'s, v8::Value> {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
_scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
_context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
Ok(value)
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for v8::Local<'s, v8::Promise> {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
if let Ok(promise) = v8::Local::<v8::Promise>::try_from(value) {
|
|
return Ok(promise);
|
|
}
|
|
|
|
let resolver = v8::PromiseResolver::new(scope)
|
|
.ok_or_else(|| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("Promise")))?;
|
|
let promise = resolver.get_promise(scope);
|
|
if resolver.resolve(scope, value) == Some(true) {
|
|
Ok(promise)
|
|
} else {
|
|
Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("Promise"),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for v8::Local<'s, v8::Object> {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
_scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
v8::Local::<v8::Object>::try_from(value)
|
|
.map_err(|_| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("object")))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for v8::Local<'s, v8::Function> {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
_scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
v8::Local::<v8::Function>::try_from(value)
|
|
.map_err(|_| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("function")))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for moli_webidl_callback::WebIdlCallbackFunction {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
let callback = v8::Local::<v8::Object>::try_from(value)
|
|
.ok()
|
|
.filter(|callback| callback.is_callable())
|
|
.ok_or_else(|| {
|
|
WebIdlError::new(context, WebIdlErrorKind::CannotConvert("callback function"))
|
|
})?;
|
|
let current_context = scope.get_current_context();
|
|
let relevant_context = callback
|
|
.get_creation_context(scope)
|
|
.unwrap_or(current_context);
|
|
let incumbent_context = scope.get_incumbent_context().unwrap_or(current_context);
|
|
|
|
moli_webidl_callback::WebIdlCallbackFunction::try_new(
|
|
scope,
|
|
callback,
|
|
relevant_context,
|
|
incumbent_context,
|
|
)
|
|
.ok_or_else(|| {
|
|
WebIdlError::new(context, WebIdlErrorKind::CannotConvert("callback function"))
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for moli_webidl_callback::WebIdlCallbackInterface {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
let callback = v8::Local::<v8::Object>::try_from(value).map_err(|_| {
|
|
WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("callback interface"),
|
|
)
|
|
})?;
|
|
let current_context = scope.get_current_context();
|
|
let relevant_context = callback
|
|
.get_creation_context(scope)
|
|
.unwrap_or(current_context);
|
|
let incumbent_context = scope.get_incumbent_context().unwrap_or(current_context);
|
|
|
|
Ok(moli_webidl_callback::WebIdlCallbackInterface::new(
|
|
scope,
|
|
callback,
|
|
relevant_context,
|
|
incumbent_context,
|
|
))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for v8::Local<'s, v8::Array> {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
_scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
v8::Local::<v8::Array>::try_from(value)
|
|
.map_err(|_| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("Array")))
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for v8::Local<'s, v8::Uint8Array> {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
_scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
v8::Local::<v8::Uint8Array>::try_from(value)
|
|
.map_err(|_| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("Uint8Array")))
|
|
}
|
|
}
|
|
|
|
/// Converts one JavaScript value with the default options for `T`.
|
|
pub fn convert<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
) -> Result<T, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
T::convert(scope, value, context, &T::Options::default())
|
|
}
|
|
|
|
/// Converts one JavaScript value with explicit converter options.
|
|
///
|
|
/// This is the entry point used by generated parsers for field attributes such
|
|
/// as `#[webidl(treat_null_as_empty_string)]`.
|
|
pub fn convert_with_options<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
options: &T::Options,
|
|
) -> Result<T, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
T::convert(scope, value, context, options)
|
|
}
|
|
|
|
/// Converts one positional native-binding argument with default converter
|
|
/// options.
|
|
pub fn argument<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
index: i32,
|
|
context: Context,
|
|
) -> Result<T, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
convert(scope, args.get(index), context)
|
|
}
|
|
|
|
/// Converts one positional native-binding argument with explicit converter
|
|
/// options.
|
|
pub fn argument_with_options<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
index: i32,
|
|
context: Context,
|
|
options: &T::Options,
|
|
) -> Result<T, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
convert_with_options(scope, args.get(index), context, options)
|
|
}
|
|
|
|
/// Converts one required positional argument and throws on failure.
|
|
///
|
|
/// New code should generally prefer `parse_args` with a derived
|
|
/// `WebIdlArguments` struct. This helper remains for older hand-written native
|
|
/// binding paths that already own their TypeError text.
|
|
pub fn required_argument<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
index: i32,
|
|
context: Context,
|
|
missing_message: &'static str,
|
|
) -> Option<T>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
if args.length() <= index {
|
|
throw_type_error(scope, missing_message);
|
|
return None;
|
|
}
|
|
argument(scope, args, index, context).map_or_else(
|
|
|error| {
|
|
throw_error(scope, &error);
|
|
None
|
|
},
|
|
Some,
|
|
)
|
|
}
|
|
|
|
/// Converts one optional positional argument, using `default` when missing or
|
|
/// `undefined`.
|
|
pub fn optional_argument_or<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
index: i32,
|
|
context: Context,
|
|
default: T,
|
|
) -> Option<T>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
if args.length() <= index || args.get(index).is_undefined() {
|
|
return Some(default);
|
|
}
|
|
argument(scope, args, index, context).map_or_else(
|
|
|error| {
|
|
throw_error(scope, &error);
|
|
None
|
|
},
|
|
Some,
|
|
)
|
|
}
|
|
|
|
/// Parses a derived `WebIdlArguments` struct and throws conversion errors.
|
|
///
|
|
/// Renderer callbacks normally use this helper and return immediately when it
|
|
/// returns `None`. Use `try_parse_args` when the caller needs to inspect or map
|
|
/// the `WebIdlError` before throwing.
|
|
pub fn parse_args<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
) -> Option<T>
|
|
where
|
|
T: WebIdlArguments<'s>,
|
|
{
|
|
try_parse_args(scope, args).map_or_else(
|
|
|error| {
|
|
throw_error(scope, &error);
|
|
None
|
|
},
|
|
Some,
|
|
)
|
|
}
|
|
|
|
/// Parses a derived `WebIdlArguments` struct without throwing.
|
|
pub fn try_parse_args<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
) -> Result<T, WebIdlError>
|
|
where
|
|
T: WebIdlArguments<'s>,
|
|
{
|
|
T::parse_arguments(scope, args)
|
|
}
|
|
|
|
/// Reads and converts an optional dictionary member.
|
|
///
|
|
/// Missing properties and `undefined` both produce `Ok(None)`. `null` is still
|
|
/// converted normally unless the target converter or a caller-specific helper
|
|
/// treats it specially.
|
|
pub fn optional_member<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
key: &'static str,
|
|
context: Context,
|
|
) -> Result<Option<T>, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
let Some(value) = property_result(scope, object, key, context)? else {
|
|
return Ok(None);
|
|
};
|
|
if value.is_undefined() {
|
|
return Ok(None);
|
|
}
|
|
convert(scope, value, context).map(Some)
|
|
}
|
|
|
|
/// Reads and converts an optional dictionary member with explicit options.
|
|
pub fn optional_member_with_options<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
key: &'static str,
|
|
context: Context,
|
|
options: &T::Options,
|
|
) -> Result<Option<T>, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
let Some(value) = property_result(scope, object, key, context)? else {
|
|
return Ok(None);
|
|
};
|
|
if value.is_undefined() {
|
|
return Ok(None);
|
|
}
|
|
convert_with_options(scope, value, context, options).map(Some)
|
|
}
|
|
|
|
/// Reads and converts an optional dictionary member, returning `default` when
|
|
/// the member is missing or `undefined`.
|
|
pub fn optional_member_or<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
key: &'static str,
|
|
context: Context,
|
|
default: T,
|
|
) -> Result<T, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
optional_member(scope, object, key, context).map(|value| value.unwrap_or(default))
|
|
}
|
|
|
|
/// Reads and converts an optional dictionary member with explicit options,
|
|
/// returning `default` when the member is missing or `undefined`.
|
|
pub fn optional_member_or_with_options<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
key: &'static str,
|
|
context: Context,
|
|
default: T,
|
|
options: &T::Options,
|
|
) -> Result<T, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
optional_member_with_options(scope, object, key, context, options)
|
|
.map(|value| value.unwrap_or(default))
|
|
}
|
|
|
|
/// Reads and converts an optional legacy dictionary member.
|
|
///
|
|
/// This differs from `optional_member`: both `undefined` and `null` count as
|
|
/// missing. Use it only for APIs whose browser behavior historically treats
|
|
/// nullish dictionary members as absent.
|
|
pub fn legacy_optional_member<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
key: &'static str,
|
|
context: Context,
|
|
) -> Result<Option<T>, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
let Some(value) = property_result(scope, object, key, context)? else {
|
|
return Ok(None);
|
|
};
|
|
if is_nullish(value) {
|
|
return Ok(None);
|
|
}
|
|
convert(scope, value, context).map(Some)
|
|
}
|
|
|
|
/// Reads and converts an optional legacy dictionary member with explicit
|
|
/// converter options.
|
|
pub fn legacy_optional_member_with_options<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
key: &'static str,
|
|
context: Context,
|
|
options: &T::Options,
|
|
) -> Result<Option<T>, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
let Some(value) = property_result(scope, object, key, context)? else {
|
|
return Ok(None);
|
|
};
|
|
if is_nullish(value) {
|
|
return Ok(None);
|
|
}
|
|
convert_with_options(scope, value, context, options).map(Some)
|
|
}
|
|
|
|
/// Reads and converts an optional legacy dictionary member, returning `default`
|
|
/// when the member is missing, `undefined`, or `null`.
|
|
pub fn legacy_optional_member_or<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
key: &'static str,
|
|
context: Context,
|
|
default: T,
|
|
) -> Result<T, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
legacy_optional_member(scope, object, key, context).map(|value| value.unwrap_or(default))
|
|
}
|
|
|
|
/// Reads and converts an optional legacy dictionary member with explicit
|
|
/// options, returning `default` when the member is missing, `undefined`, or
|
|
/// `null`.
|
|
pub fn legacy_optional_member_or_with_options<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
key: &'static str,
|
|
context: Context,
|
|
default: T,
|
|
options: &T::Options,
|
|
) -> Result<T, WebIdlError>
|
|
where
|
|
T: WebIdlConverter<'s>,
|
|
{
|
|
legacy_optional_member_with_options(scope, object, key, context, options)
|
|
.map(|value| value.unwrap_or(default))
|
|
}
|
|
|
|
/// Parses a value as an optional WebIDL dictionary.
|
|
///
|
|
/// `null` and `undefined` produce `Ok(None)`. Non-object values fail with a
|
|
/// dictionary conversion error. Object values are delegated to the derived
|
|
/// `WebIdlDictionary` implementation.
|
|
pub fn parse_dictionary<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
) -> Result<Option<T>, WebIdlError>
|
|
where
|
|
T: WebIdlDictionary<'s>,
|
|
{
|
|
dictionary_value(value, context)?
|
|
.map(|object| T::parse_dictionary(scope, object))
|
|
.transpose()
|
|
}
|
|
|
|
/// Parses a value that is already known to be a dictionary object.
|
|
pub fn parse_dictionary_object<'s, T>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
) -> Result<T, WebIdlError>
|
|
where
|
|
T: WebIdlDictionary<'s>,
|
|
{
|
|
T::parse_dictionary(scope, object)
|
|
}
|
|
|
|
/// Legacy convenience helper for boolean dictionary members.
|
|
///
|
|
/// New dictionary code should usually use `#[derive(WebIdlDictionary)]`; this
|
|
/// helper remains for older call sites that intentionally swallow conversion
|
|
/// errors and fall back to a product-compatible default.
|
|
pub fn legacy_bool_member_or<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
prefix: &'static str,
|
|
key: &'static str,
|
|
default: bool,
|
|
) -> bool {
|
|
legacy_optional_member_or::<Boolean>(
|
|
scope,
|
|
object,
|
|
key,
|
|
Context::member(prefix, key),
|
|
Boolean(default),
|
|
)
|
|
.map(|value| value.0)
|
|
.unwrap_or(default)
|
|
}
|
|
|
|
/// Legacy convenience helper for numeric dictionary members.
|
|
pub fn legacy_number_member_or<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
prefix: &'static str,
|
|
key: &'static str,
|
|
default: f64,
|
|
) -> f64 {
|
|
legacy_optional_member_or::<UnrestrictedDouble>(
|
|
scope,
|
|
object,
|
|
key,
|
|
Context::member(prefix, key),
|
|
UnrestrictedDouble(default),
|
|
)
|
|
.map(|value| value.0)
|
|
.ok()
|
|
.filter(|value| !value.is_nan())
|
|
.unwrap_or(default)
|
|
}
|
|
|
|
/// Legacy convenience helper for DOMString dictionary members.
|
|
pub fn legacy_string_member_or<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
prefix: &'static str,
|
|
key: &'static str,
|
|
default: &str,
|
|
) -> String {
|
|
legacy_optional_member_or::<DomString>(
|
|
scope,
|
|
object,
|
|
key,
|
|
Context::member(prefix, key),
|
|
DomString(default.to_owned()),
|
|
)
|
|
.map(Into::into)
|
|
.unwrap_or_else(|_| default.to_owned())
|
|
}
|
|
|
|
/// Legacy convenience helper for positional DOMString arguments.
|
|
pub fn string_arg<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
index: i32,
|
|
) -> Option<String> {
|
|
argument::<DomString>(
|
|
scope,
|
|
args,
|
|
index,
|
|
Context::argument("", (index + 1) as usize),
|
|
)
|
|
.ok()
|
|
.map(Into::into)
|
|
}
|
|
|
|
/// Converts a JavaScript value to an unrestricted number, falling back on
|
|
/// conversion failure.
|
|
pub fn number_or<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
default: f64,
|
|
) -> f64 {
|
|
convert::<UnrestrictedDouble>(scope, value, Context::argument("", 1))
|
|
.map(Into::into)
|
|
.unwrap_or(default)
|
|
}
|
|
|
|
/// Legacy convenience helper for positional unrestricted-number arguments.
|
|
pub fn number_arg_or<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
index: i32,
|
|
prefix: &'static str,
|
|
default: f64,
|
|
) -> f64 {
|
|
argument::<UnrestrictedDouble>(
|
|
scope,
|
|
args,
|
|
index,
|
|
Context::argument(prefix, (index + 1) as usize),
|
|
)
|
|
.map(Into::into)
|
|
.unwrap_or(default)
|
|
}
|
|
|
|
/// Parses timer-like millisecond input where missing, non-finite, and
|
|
/// non-positive values collapse to zero.
|
|
pub fn non_negative_milliseconds_arg<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
index: i32,
|
|
prefix: &'static str,
|
|
) -> u32 {
|
|
if args.length() <= index {
|
|
return 0;
|
|
}
|
|
let value = number_arg_or(scope, args, index, prefix, 0.0);
|
|
if !value.is_finite() || value <= 0.0 {
|
|
return 0;
|
|
}
|
|
value.min(u32::MAX as f64) as u32
|
|
}
|
|
|
|
/// Parses `setTimeout`/`setInterval` style delay input using WebIDL `long`
|
|
/// wrapping before clamping negative values to zero.
|
|
pub fn timer_milliseconds_arg<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
index: i32,
|
|
prefix: &'static str,
|
|
) -> u32 {
|
|
if args.length() <= index {
|
|
return 0;
|
|
}
|
|
let value = number_arg_or(scope, args, index, prefix, 0.0);
|
|
let value = long(value);
|
|
value.max(0) as u32
|
|
}
|
|
|
|
fn string_value_utf16<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
kind: &'static str,
|
|
) -> Result<Vec<u16>, WebIdlError> {
|
|
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
|
|
let mut scope = try_catch.init();
|
|
match value.to_string(&scope) {
|
|
Some(value) => {
|
|
let mut buffer = vec![0; value.length()];
|
|
value.write_v2(&scope, 0, &mut buffer, v8::WriteFlags::empty());
|
|
Ok(buffer)
|
|
}
|
|
None if scope.has_caught() => {
|
|
let _ = scope.rethrow();
|
|
Err(WebIdlError::pending_exception(context))
|
|
}
|
|
None => Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert(kind),
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn scalar_value_string_from_utf16(units: &[u16]) -> String {
|
|
let mut output = String::with_capacity(units.len());
|
|
let mut index = 0;
|
|
while index < units.len() {
|
|
let unit = units[index];
|
|
if (0xD800..=0xDBFF).contains(&unit) {
|
|
if let Some(&trail) = units.get(index + 1)
|
|
&& (0xDC00..=0xDFFF).contains(&trail)
|
|
{
|
|
let high = u32::from(unit) - 0xD800;
|
|
let low = u32::from(trail) - 0xDC00;
|
|
let scalar = 0x10000 + ((high << 10) | low);
|
|
if let Some(ch) = char::from_u32(scalar) {
|
|
output.push(ch);
|
|
}
|
|
index += 2;
|
|
continue;
|
|
}
|
|
output.push('\u{FFFD}');
|
|
} else if (0xDC00..=0xDFFF).contains(&unit) {
|
|
output.push('\u{FFFD}');
|
|
} else if let Some(ch) = char::from_u32(u32::from(unit)) {
|
|
output.push(ch);
|
|
}
|
|
index += 1;
|
|
}
|
|
output
|
|
}
|
|
|
|
fn number_value<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
kind: &'static str,
|
|
) -> Result<f64, WebIdlError> {
|
|
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
|
|
let mut scope = try_catch.init();
|
|
match value.number_value(&scope) {
|
|
Some(value) => Ok(value),
|
|
None if scope.has_caught() => {
|
|
let _ = scope.rethrow();
|
|
Err(WebIdlError::pending_exception(context))
|
|
}
|
|
None => Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert(kind),
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn sequence_iterator_from_method<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
iterator_method: v8::Local<'s, v8::Function>,
|
|
context: Context,
|
|
) -> Result<(v8::Local<'s, v8::Object>, v8::Local<'s, v8::Function>), WebIdlError> {
|
|
let iterator_value = call_sequence_function(scope, iterator_method, value, &[], context)?
|
|
.ok_or_else(|| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("sequence")))?;
|
|
let iterator = v8::Local::<v8::Object>::try_from(iterator_value)
|
|
.map_err(|_| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("sequence")))?;
|
|
let next_method = property_result(scope, iterator, "next", context)?
|
|
.filter(|value| !value.is_null_or_undefined())
|
|
.ok_or_else(|| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("sequence")))?;
|
|
let next_method = v8::Local::<v8::Function>::try_from(next_method)
|
|
.map_err(|_| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("sequence")))?;
|
|
Ok((iterator, next_method))
|
|
}
|
|
|
|
fn sequence_iterator_next<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
iterator: v8::Local<'s, v8::Object>,
|
|
next_method: v8::Local<'s, v8::Function>,
|
|
context: Context,
|
|
) -> Result<Option<v8::Local<'s, v8::Value>>, WebIdlError> {
|
|
let step = call_sequence_function(scope, next_method, iterator.into(), &[], context)?
|
|
.ok_or_else(|| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("sequence")))?;
|
|
let step = v8::Local::<v8::Object>::try_from(step)
|
|
.map_err(|_| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("sequence")))?;
|
|
let done = property_result(scope, step, "done", context)?
|
|
.is_some_and(|value| value.boolean_value(scope));
|
|
if done {
|
|
return Ok(None);
|
|
}
|
|
let value = property_result(scope, step, "value", context)?
|
|
.ok_or_else(|| WebIdlError::new(context, WebIdlErrorKind::CannotConvert("sequence")))?;
|
|
Ok(Some(value))
|
|
}
|
|
|
|
fn sequence_iterator_close_ignoring_errors<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
iterator: v8::Local<'s, v8::Object>,
|
|
) {
|
|
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
|
|
let scope = try_catch.init();
|
|
let Some(return_key) = v8::String::new(&scope, "return") else {
|
|
return;
|
|
};
|
|
let Some(return_method) = iterator.get(&scope, return_key.into()) else {
|
|
return;
|
|
};
|
|
if return_method.is_null_or_undefined() {
|
|
return;
|
|
}
|
|
let Ok(return_method) = v8::Local::<v8::Function>::try_from(return_method) else {
|
|
return;
|
|
};
|
|
let _ = return_method.call(&scope, iterator.into(), &[]);
|
|
}
|
|
|
|
fn call_sequence_function<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
function: v8::Local<'s, v8::Function>,
|
|
receiver: v8::Local<'s, v8::Value>,
|
|
args: &[v8::Local<'s, v8::Value>],
|
|
context: Context,
|
|
) -> Result<Option<v8::Local<'s, v8::Value>>, WebIdlError> {
|
|
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
|
|
let mut scope = try_catch.init();
|
|
match function.call(&scope, receiver, args) {
|
|
Some(value) => Ok(Some(value)),
|
|
None if scope.has_caught() => {
|
|
let _ = scope.rethrow();
|
|
Err(WebIdlError::pending_exception(context))
|
|
}
|
|
None => Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("sequence"),
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn own_property_names<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
context: Context,
|
|
) -> Result<v8::Local<'s, v8::Array>, WebIdlError> {
|
|
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
|
|
let mut scope = try_catch.init();
|
|
match object.get_own_property_names(&scope, v8::GetPropertyNamesArgs::default()) {
|
|
Some(properties) => Ok(properties),
|
|
None if scope.has_caught() => {
|
|
let _ = scope.rethrow();
|
|
Err(WebIdlError::pending_exception(context))
|
|
}
|
|
None => Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("record"),
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn record_property_value<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
key: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
) -> Result<v8::Local<'s, v8::Value>, WebIdlError> {
|
|
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
|
|
let mut scope = try_catch.init();
|
|
match object.get(&scope, key) {
|
|
Some(value) => Ok(value),
|
|
None if scope.has_caught() => {
|
|
let _ = scope.rethrow();
|
|
Err(WebIdlError::pending_exception(context))
|
|
}
|
|
None => Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("record"),
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn unsigned_long(value: f64) -> u32 {
|
|
if !value.is_finite() || value == 0.0 {
|
|
return 0;
|
|
}
|
|
let integer = value.trunc();
|
|
let wrapped = integer.rem_euclid(2f64.powi(32));
|
|
wrapped as u32
|
|
}
|
|
|
|
fn unsigned_short(value: f64) -> u16 {
|
|
if !value.is_finite() || value == 0.0 {
|
|
return 0;
|
|
}
|
|
let integer = value.trunc();
|
|
let wrapped = integer.rem_euclid(2f64.powi(16));
|
|
wrapped as u16
|
|
}
|
|
|
|
fn clamped_unsigned_short(value: f64) -> u16 {
|
|
if value.is_nan() {
|
|
return 0;
|
|
}
|
|
let clamped = value.clamp(0.0, f64::from(u16::MAX));
|
|
round_half_even(clamped) as u16
|
|
}
|
|
|
|
fn round_half_even(value: f64) -> f64 {
|
|
let floor = value.floor();
|
|
let fraction = value - floor;
|
|
if fraction < 0.5 {
|
|
floor
|
|
} else if fraction > 0.5 {
|
|
floor + 1.0
|
|
} else if (floor as u64).is_multiple_of(2) {
|
|
floor
|
|
} else {
|
|
floor + 1.0
|
|
}
|
|
}
|
|
|
|
fn unsigned_long_long(value: f64) -> u64 {
|
|
if !value.is_finite() || value == 0.0 {
|
|
return 0;
|
|
}
|
|
let integer = value.trunc();
|
|
let wrapped = integer.rem_euclid(2f64.powi(64));
|
|
wrapped as u64
|
|
}
|
|
|
|
fn enforce_range_unsigned_long_long(value: f64, context: Context) -> Result<u64, WebIdlError> {
|
|
if !value.is_finite() || value < 0.0 || value >= 2f64.powi(64) {
|
|
return Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("[EnforceRange] unsigned long long"),
|
|
));
|
|
}
|
|
Ok(value.trunc() as u64)
|
|
}
|
|
|
|
fn enforce_range_unsigned_long(value: f64, context: Context) -> Result<u32, WebIdlError> {
|
|
if !value.is_finite() || value < 0.0 || value >= 2f64.powi(32) {
|
|
return Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("[EnforceRange] unsigned long"),
|
|
));
|
|
}
|
|
Ok(value.trunc() as u32)
|
|
}
|
|
|
|
fn enforce_range_long(value: f64, context: Context) -> Result<i32, WebIdlError> {
|
|
if !value.is_finite() || value < -(2f64.powi(31)) || value >= 2f64.powi(31) {
|
|
return Err(WebIdlError::new(
|
|
context,
|
|
WebIdlErrorKind::CannotConvert("[EnforceRange] long"),
|
|
));
|
|
}
|
|
Ok(value.trunc() as i32)
|
|
}
|
|
|
|
fn long(value: f64) -> i32 {
|
|
let value = unsigned_long(value);
|
|
if value >= 2u32.pow(31) {
|
|
(i64::from(value) - 2i64.pow(32)) as i32
|
|
} else {
|
|
value as i32
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{
|
|
clamped_unsigned_short, enforce_range_long, enforce_range_unsigned_long,
|
|
enforce_range_unsigned_long_long, long, scalar_value_string_from_utf16, unsigned_long,
|
|
unsigned_long_long, unsigned_short,
|
|
};
|
|
use crate::{Context, WebIdlCallbackFunction, WebIdlCallbackInterface, WebIdlConverter};
|
|
|
|
fn eval<'s>(scope: &mut v8::PinScope<'s, '_>, source: &str) -> v8::Local<'s, v8::Value> {
|
|
let source = v8::String::new(scope, source).expect("test source");
|
|
let script = v8::Script::compile(scope, source, None).expect("compile test source");
|
|
script.run(scope).expect("run test source")
|
|
}
|
|
|
|
#[test]
|
|
fn unsigned_long_wraps_per_webidl_rules() {
|
|
assert_eq!(unsigned_long(f64::NAN), 0);
|
|
assert_eq!(unsigned_long(f64::INFINITY), 0);
|
|
assert_eq!(unsigned_long(-1.9), u32::MAX);
|
|
assert_eq!(unsigned_long(1.9), 1);
|
|
assert_eq!(unsigned_long(4_294_967_297.0), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn unsigned_short_wraps_per_webidl_rules() {
|
|
assert_eq!(unsigned_short(f64::NAN), 0);
|
|
assert_eq!(unsigned_short(f64::INFINITY), 0);
|
|
assert_eq!(unsigned_short(-1.9), u16::MAX);
|
|
assert_eq!(unsigned_short(1.9), 1);
|
|
assert_eq!(unsigned_short(65_537.0), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn clamped_unsigned_short_clamps_and_rounds_half_even() {
|
|
assert_eq!(clamped_unsigned_short(f64::NAN), 0);
|
|
assert_eq!(clamped_unsigned_short(f64::INFINITY), u16::MAX);
|
|
assert_eq!(clamped_unsigned_short(f64::NEG_INFINITY), 0);
|
|
assert_eq!(clamped_unsigned_short(-1.0), 0);
|
|
assert_eq!(clamped_unsigned_short(65_536.0), u16::MAX);
|
|
assert_eq!(clamped_unsigned_short(1.5), 2);
|
|
assert_eq!(clamped_unsigned_short(2.5), 2);
|
|
assert_eq!(clamped_unsigned_short(3.5), 4);
|
|
}
|
|
|
|
#[test]
|
|
fn long_wraps_per_webidl_rules() {
|
|
assert_eq!(long(f64::NAN), 0);
|
|
assert_eq!(long(f64::INFINITY), 0);
|
|
assert_eq!(long(-1.9), -1);
|
|
assert_eq!(long(1.9), 1);
|
|
assert_eq!(long(2_147_483_648.0), -2_147_483_648);
|
|
assert_eq!(long(4_294_967_296.0), 0);
|
|
assert_eq!(long(4_294_967_297.0), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn unsigned_long_long_wraps_per_webidl_rules() {
|
|
assert_eq!(unsigned_long_long(f64::NAN), 0);
|
|
assert_eq!(unsigned_long_long(f64::INFINITY), 0);
|
|
assert_eq!(unsigned_long_long(-1.0), u64::MAX);
|
|
assert_eq!(unsigned_long_long(1.9), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn enforce_range_unsigned_long_long_rejects_out_of_range_values() {
|
|
let context = Context::argument("IDBFactory.open", 2);
|
|
assert!(enforce_range_unsigned_long_long(f64::NAN, context).is_err());
|
|
assert!(enforce_range_unsigned_long_long(f64::INFINITY, context).is_err());
|
|
assert!(enforce_range_unsigned_long_long(-1.0, context).is_err());
|
|
assert!(enforce_range_unsigned_long_long(2f64.powi(64), context).is_err());
|
|
assert_eq!(enforce_range_unsigned_long_long(1.9, context).unwrap(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn enforce_range_unsigned_long_rejects_out_of_range_values() {
|
|
let context = Context::argument("IDBCursor.advance", 1);
|
|
assert!(enforce_range_unsigned_long(f64::NAN, context).is_err());
|
|
assert!(enforce_range_unsigned_long(f64::INFINITY, context).is_err());
|
|
assert!(enforce_range_unsigned_long(-1.0, context).is_err());
|
|
assert!(enforce_range_unsigned_long(2f64.powi(32), context).is_err());
|
|
assert_eq!(enforce_range_unsigned_long(1.9, context).unwrap(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn enforce_range_long_rejects_out_of_range_values() {
|
|
let context = Context::argument("CanvasRenderingContext2D.getImageData", 1);
|
|
assert!(enforce_range_long(f64::NAN, context).is_err());
|
|
assert!(enforce_range_long(f64::INFINITY, context).is_err());
|
|
assert!(enforce_range_long(-(2f64.powi(31)) - 1.0, context).is_err());
|
|
assert!(enforce_range_long(2f64.powi(31), context).is_err());
|
|
assert_eq!(enforce_range_long(-1.9, context).unwrap(), -1);
|
|
assert_eq!(enforce_range_long(1.9, context).unwrap(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn scalar_value_string_replaces_lone_surrogates() {
|
|
assert_eq!(
|
|
scalar_value_string_from_utf16(&[0xD83E, 0xDD95]),
|
|
"\u{1F995}"
|
|
);
|
|
assert_eq!(scalar_value_string_from_utf16(&[0xD800]), "\u{FFFD}");
|
|
assert_eq!(scalar_value_string_from_utf16(&[0xDC00]), "\u{FFFD}");
|
|
assert_eq!(
|
|
scalar_value_string_from_utf16(&[b'a' as u16, 0xD800, b'b' as u16, 0xDC00]),
|
|
"a\u{FFFD}b\u{FFFD}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn callback_function_conversion_captures_relevant_and_incumbent_contexts() {
|
|
moli_v8_test_util::ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
|
|
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let conversion_context = v8::Context::new(scope, Default::default());
|
|
let relevant_context = v8::Context::new(scope, Default::default());
|
|
let incumbent_context = v8::Context::new(scope, Default::default());
|
|
let callback = {
|
|
let scope = &mut v8::ContextScope::new(scope, relevant_context);
|
|
let callback =
|
|
v8::Local::<v8::Object>::try_from(eval(scope, "(function () { return 42; })"))
|
|
.expect("callback function");
|
|
v8::Global::new(scope, callback)
|
|
};
|
|
let scope = &mut v8::ContextScope::new(scope, conversion_context);
|
|
let callback = v8::Local::new(scope, &callback);
|
|
let incumbent_scope = std::pin::pin!(v8::BackupIncumbentScope::new(incumbent_context));
|
|
let _incumbent_scope = incumbent_scope.init();
|
|
|
|
let converted = WebIdlCallbackFunction::convert(
|
|
scope,
|
|
callback.into(),
|
|
Context::argument("Test.callback", 1),
|
|
&(),
|
|
)
|
|
.expect("callback function conversion");
|
|
let prepared = converted.prepare(scope);
|
|
|
|
assert_eq!(prepared.relevant_context(scope), relevant_context);
|
|
assert_eq!(prepared.incumbent_context(scope), incumbent_context);
|
|
assert!(converted.matches(scope, callback));
|
|
}
|
|
|
|
#[test]
|
|
fn callback_function_conversion_accepts_callable_proxy_and_rejects_object() {
|
|
moli_v8_test_util::ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
|
|
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Context::new(scope, Default::default());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let callable_proxy = eval(scope, "new Proxy(function () {}, {})");
|
|
let non_callable = v8::Object::new(scope);
|
|
|
|
assert!(
|
|
WebIdlCallbackFunction::convert(
|
|
scope,
|
|
callable_proxy,
|
|
Context::argument("Test.callback", 1),
|
|
&(),
|
|
)
|
|
.is_ok()
|
|
);
|
|
assert!(
|
|
WebIdlCallbackFunction::convert(
|
|
scope,
|
|
non_callable.into(),
|
|
Context::argument("Test.callback", 1),
|
|
&(),
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn callback_interface_conversion_captures_contexts_and_callability() {
|
|
moli_v8_test_util::ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
|
|
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let conversion_context = v8::Context::new(scope, Default::default());
|
|
let relevant_context = v8::Context::new(scope, Default::default());
|
|
let incumbent_context = v8::Context::new(scope, Default::default());
|
|
let callback = {
|
|
let scope = &mut v8::ContextScope::new(scope, relevant_context);
|
|
let callback = v8::Local::<v8::Object>::try_from(eval(scope, "({ acceptNode() {} })"))
|
|
.expect("callback-interface object");
|
|
v8::Global::new(scope, callback)
|
|
};
|
|
let scope = &mut v8::ContextScope::new(scope, conversion_context);
|
|
let callback = v8::Local::new(scope, &callback);
|
|
let incumbent_scope = std::pin::pin!(v8::BackupIncumbentScope::new(incumbent_context));
|
|
let _incumbent_scope = incumbent_scope.init();
|
|
|
|
let converted = WebIdlCallbackInterface::convert(
|
|
scope,
|
|
callback.into(),
|
|
Context::argument("Test.callback", 1),
|
|
&(),
|
|
)
|
|
.expect("callback-interface conversion");
|
|
let prepared = converted.prepare(scope);
|
|
|
|
assert_eq!(prepared.relevant_context(scope), relevant_context);
|
|
assert_eq!(prepared.incumbent_context(scope), incumbent_context);
|
|
assert!(!prepared.callable_at_conversion());
|
|
assert!(converted.matches(scope, callback));
|
|
}
|
|
|
|
#[test]
|
|
fn callback_interface_conversion_accepts_callable_proxy_and_rejects_primitive() {
|
|
moli_v8_test_util::ensure_v8();
|
|
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
|
|
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
|
|
let scope = &mut scope.init();
|
|
let context = v8::Context::new(scope, Default::default());
|
|
let scope = &mut v8::ContextScope::new(scope, context);
|
|
let callable_proxy = eval(scope, "new Proxy(function () {}, {})");
|
|
let primitive = v8::Integer::new(scope, 7);
|
|
|
|
let converted = WebIdlCallbackInterface::convert(
|
|
scope,
|
|
callable_proxy,
|
|
Context::argument("Test.callback", 1),
|
|
&(),
|
|
)
|
|
.expect("callable proxy is a callback-interface object");
|
|
assert!(converted.prepare(scope).callable_at_conversion());
|
|
assert!(
|
|
WebIdlCallbackInterface::convert(
|
|
scope,
|
|
primitive.into(),
|
|
Context::argument("Test.callback", 1),
|
|
&(),
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
}
|