mirror of
https://github.com/lexmount/moli.git
synced 2026-10-08 00:01:00 +00:00
fix(svg): enforce value list mutation semantics
This commit is contained in:
@@ -209,6 +209,8 @@ struct SvgAnimatedTransformListObjectDeclaration<'scope> {
|
||||
struct SvgLengthListObjectDeclaration<'scope> {
|
||||
#[webapi(slot = SVG_LENGTH_LIST_ITEMS_SLOT)]
|
||||
items: Vec<v8::Local<'scope, v8::Value>>,
|
||||
#[webapi(slot = SVG_VALUE_LIST_READ_ONLY_SLOT)]
|
||||
read_only: bool,
|
||||
#[webapi(method, callback = svg_length_list_clear_callback, length = 0)]
|
||||
clear: (),
|
||||
#[webapi(method, callback = svg_length_list_initialize_callback, length = 1)]
|
||||
@@ -234,6 +236,8 @@ struct SvgLengthListObjectDeclaration<'scope> {
|
||||
struct SvgNumberListObjectDeclaration<'scope> {
|
||||
#[webapi(slot = SVG_NUMBER_LIST_ITEMS_SLOT)]
|
||||
items: Vec<v8::Local<'scope, v8::Value>>,
|
||||
#[webapi(slot = SVG_VALUE_LIST_READ_ONLY_SLOT)]
|
||||
read_only: bool,
|
||||
#[webapi(method, callback = svg_number_list_clear_callback, length = 0)]
|
||||
clear: (),
|
||||
#[webapi(method, callback = svg_number_list_initialize_callback, length = 1)]
|
||||
@@ -451,8 +455,8 @@ pub(super) fn reflect_svg_animated_boolean_to_owner_attribute<'s>(
|
||||
pub(super) fn build_svg_animated_length_list<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let base_val = build_svg_length_list(scope);
|
||||
let anim_val = build_svg_length_list(scope);
|
||||
let base_val = build_svg_length_list(scope, false);
|
||||
let anim_val = build_svg_length_list(scope, true);
|
||||
SvgAnimatedLengthListObjectDeclaration::new(base_val, anim_val)
|
||||
.bind(scope)
|
||||
.expect("SVGAnimatedLengthList declaration should bind")
|
||||
@@ -461,8 +465,8 @@ pub(super) fn build_svg_animated_length_list<'s>(
|
||||
pub(super) fn build_svg_animated_number_list<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let base_val = build_svg_number_list(scope);
|
||||
let anim_val = build_svg_number_list(scope);
|
||||
let base_val = build_svg_number_list(scope, false);
|
||||
let anim_val = build_svg_number_list(scope, true);
|
||||
SvgAnimatedNumberListObjectDeclaration::new(base_val, anim_val)
|
||||
.bind(scope)
|
||||
.expect("SVGAnimatedNumberList declaration should bind")
|
||||
@@ -847,18 +851,32 @@ pub(super) fn serialize_svg_animated_enumeration(
|
||||
|
||||
pub(super) fn build_svg_length_list<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
read_only: bool,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
SvgLengthListObjectDeclaration::new(Vec::new())
|
||||
.bind(scope)
|
||||
.expect("SVGLengthList declaration should bind")
|
||||
let template = v8::ObjectTemplate::new(scope);
|
||||
configure_svg_value_list_indexed_property_handler(template);
|
||||
let object = template
|
||||
.new_instance(scope)
|
||||
.expect("SVGLengthList object template should instantiate");
|
||||
SvgLengthListObjectDeclaration::new(Vec::new(), read_only)
|
||||
.bind_into(scope, object)
|
||||
.expect("SVGLengthList declaration should bind");
|
||||
object
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_number_list<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
read_only: bool,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
SvgNumberListObjectDeclaration::new(Vec::new())
|
||||
.bind(scope)
|
||||
.expect("SVGNumberList declaration should bind")
|
||||
let template = v8::ObjectTemplate::new(scope);
|
||||
configure_svg_value_list_indexed_property_handler(template);
|
||||
let object = template
|
||||
.new_instance(scope)
|
||||
.expect("SVGNumberList object template should instantiate");
|
||||
SvgNumberListObjectDeclaration::new(Vec::new(), read_only)
|
||||
.bind_into(scope, object)
|
||||
.expect("SVGNumberList declaration should bind");
|
||||
object
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_string_list_for_attribute<'s>(
|
||||
@@ -1724,15 +1742,34 @@ pub(super) fn svg_matrix_value_or_throw<'s>(
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn svg_value_list_item_or_default<'s>(
|
||||
pub(super) fn svg_value_list_item_or_throw<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
kind: SvgListKind,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
v8::Local::<v8::Object>::try_from(value).unwrap_or_else(|_| match kind {
|
||||
SvgListKind::Length => build_svg_length(scope, 0.0),
|
||||
SvgListKind::Number => build_svg_number(scope, 0.0),
|
||||
})
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let (brand_slot, interface) = match kind {
|
||||
SvgListKind::Length => (SVG_LENGTH_VALUE_SLOT, "SVGLength"),
|
||||
SvgListKind::Number => (SVG_NUMBER_VALUE_SLOT, "SVGNumber"),
|
||||
};
|
||||
let object = v8::Local::<v8::Object>::try_from(value).ok();
|
||||
if let Some(object) = object
|
||||
&& get_private_value(scope, object, brand_slot).is_some()
|
||||
{
|
||||
return Some(object);
|
||||
}
|
||||
webidl::throw_type_error(
|
||||
scope,
|
||||
&format!("Argument 1 can not be converted to {interface}"),
|
||||
);
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn svg_value_list_is_read_only<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
get_private_value(scope, object, SVG_VALUE_LIST_READ_ONLY_SLOT)
|
||||
.is_some_and(|value| value.is_true())
|
||||
}
|
||||
|
||||
pub(super) fn svg_value_list_items<'s>(
|
||||
@@ -1865,27 +1902,47 @@ pub(super) fn sync_svg_animated_value_list_from_owner_attribute<'s>(
|
||||
) {
|
||||
let raw = svg_owner_attribute_value(scope, owner, attribute);
|
||||
let raw_value = raw.clone().unwrap_or_default();
|
||||
if let Some(base_val) = svg_animated_value_list_member(scope, animated, "baseVal", kind)
|
||||
&& svg_value_list_synced_attribute_value(scope, base_val)
|
||||
.as_deref()
|
||||
.is_some_and(|synced| synced == raw_value)
|
||||
{
|
||||
set_svg_value_list_owner_attribute(scope, base_val, owner, attribute);
|
||||
return;
|
||||
}
|
||||
let base_items = build_svg_value_list_items_from_attribute(scope, raw.as_deref(), kind);
|
||||
let anim_items = build_svg_value_list_items_from_attribute(scope, raw.as_deref(), kind);
|
||||
if let Some(base_val) = svg_animated_value_list_member(scope, animated, "baseVal", kind) {
|
||||
set_svg_value_list_items(scope, base_val, base_items, kind);
|
||||
set_svg_value_list_owner_attribute(scope, base_val, owner, attribute);
|
||||
set_svg_value_list_synced_attribute_value(scope, base_val, &raw_value);
|
||||
sync_svg_value_list_from_owner_attribute(scope, base_val, kind);
|
||||
}
|
||||
if let Some(anim_val) = svg_animated_value_list_member(scope, animated, "animVal", kind) {
|
||||
if let Some(anim_val) = svg_animated_value_list_member(scope, animated, "animVal", kind)
|
||||
&& svg_value_list_synced_attribute_value(scope, anim_val).as_deref()
|
||||
!= Some(raw_value.as_str())
|
||||
{
|
||||
let anim_items = build_svg_value_list_items_from_attribute(scope, raw.as_deref(), kind);
|
||||
set_svg_value_list_items(scope, anim_val, anim_items, kind);
|
||||
set_svg_value_list_synced_attribute_value(scope, anim_val, &raw_value);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn sync_svg_value_list_from_owner_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
kind: SvgListKind,
|
||||
) {
|
||||
let Some(owner) = get_private_value(scope, list, SVG_VALUE_LIST_OWNER_ELEMENT_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(attribute) = get_private_value(scope, list, SVG_VALUE_LIST_OWNER_ATTRIBUTE_SLOT)
|
||||
.and_then(|value| value.to_string(scope))
|
||||
.map(|value| value.to_rust_string_lossy(scope))
|
||||
.filter(|value| !value.is_empty())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let raw = svg_owner_attribute_value(scope, owner, &attribute);
|
||||
let raw_value = raw.clone().unwrap_or_default();
|
||||
if svg_value_list_synced_attribute_value(scope, list).as_deref() == Some(raw_value.as_str()) {
|
||||
return;
|
||||
}
|
||||
let items = build_svg_value_list_items_from_attribute(scope, raw.as_deref(), kind);
|
||||
set_svg_value_list_items(scope, list, items, kind);
|
||||
set_svg_value_list_synced_attribute_value(scope, list, &raw_value);
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_value_list_items_from_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
raw: Option<&str>,
|
||||
|
||||
@@ -34,6 +34,21 @@ pub(super) fn configure_svg_string_list_indexed_property_handler(
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn configure_svg_value_list_indexed_property_handler(
|
||||
template: v8::Local<'_, v8::ObjectTemplate>,
|
||||
) {
|
||||
template.set_indexed_property_handler(
|
||||
v8::IndexedPropertyHandlerConfiguration::new()
|
||||
.getter(svg_value_list_indexed_getter)
|
||||
.setter(svg_value_list_indexed_setter)
|
||||
.query(svg_value_list_indexed_query)
|
||||
.deleter(svg_value_list_indexed_deleter)
|
||||
.enumerator(svg_value_list_indexed_enumerator)
|
||||
.definer(svg_value_list_indexed_definer)
|
||||
.descriptor(svg_value_list_indexed_descriptor),
|
||||
);
|
||||
}
|
||||
|
||||
fn svg_dom_matrix_2d_init_arg<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
@@ -1946,12 +1961,56 @@ fn svg_string_list_indexed_descriptor<'s>(
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn svg_value_list_metadata(kind: SvgListKind) -> (&'static str, &'static str) {
|
||||
match kind {
|
||||
SvgListKind::Length => (SVG_LENGTH_LIST_ITEMS_SLOT, "SVGLengthList"),
|
||||
SvgListKind::Number => (SVG_NUMBER_LIST_ITEMS_SLOT, "SVGNumberList"),
|
||||
}
|
||||
}
|
||||
|
||||
fn svg_value_list_kind<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
) -> Option<SvgListKind> {
|
||||
if web_api_interfaces::SVGLengthList::is_instance(scope, list) {
|
||||
Some(SvgListKind::Length)
|
||||
} else if web_api_interfaces::SVGNumberList::is_instance(scope, list) {
|
||||
Some(SvgListKind::Number)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn require_svg_value_list_items<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
kind: SvgListKind,
|
||||
member: &str,
|
||||
writable: bool,
|
||||
) -> Option<v8::Local<'s, v8::Array>> {
|
||||
let (_, interface) = svg_value_list_metadata(kind);
|
||||
if !require_svg_receiver(scope, list, interface, member) {
|
||||
return None;
|
||||
}
|
||||
if writable && svg_value_list_is_read_only(scope, list) {
|
||||
throw_dom_exception(
|
||||
scope,
|
||||
"NoModificationAllowedError",
|
||||
7,
|
||||
"The SVG value list is read-only.",
|
||||
);
|
||||
return None;
|
||||
}
|
||||
sync_svg_value_list_from_owner_attribute(scope, list, kind);
|
||||
Some(svg_value_list_items(scope, list, kind))
|
||||
}
|
||||
|
||||
pub(super) fn svg_length_list_length_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
svg_value_list_length_getter(scope, args, rv, SvgListKind::Length, "SVGLengthList");
|
||||
svg_value_list_length_getter(scope, args, rv, SvgListKind::Length);
|
||||
}
|
||||
|
||||
pub(super) fn svg_number_list_length_getter<'s>(
|
||||
@@ -1959,7 +2018,7 @@ pub(super) fn svg_number_list_length_getter<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
svg_value_list_length_getter(scope, args, rv, SvgListKind::Number, "SVGNumberList");
|
||||
svg_value_list_length_getter(scope, args, rv, SvgListKind::Number);
|
||||
}
|
||||
|
||||
pub(super) fn svg_value_list_length_getter<'s>(
|
||||
@@ -1967,13 +2026,13 @@ pub(super) fn svg_value_list_length_getter<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
kind: SvgListKind,
|
||||
interface: &'static str,
|
||||
) {
|
||||
if !require_svg_receiver(scope, args.this(), interface, "length getter") {
|
||||
let Some(items) =
|
||||
require_svg_value_list_items(scope, args.this(), kind, "length getter", false)
|
||||
else {
|
||||
return;
|
||||
}
|
||||
let length = svg_value_list_items(scope, args.this(), kind).length();
|
||||
rv.set(v8::Integer::new_from_unsigned(scope, length).into());
|
||||
};
|
||||
rv.set(v8::Integer::new_from_unsigned(scope, items.length()).into());
|
||||
}
|
||||
|
||||
pub(super) fn svg_length_list_clear_callback<'s>(
|
||||
@@ -1998,6 +2057,9 @@ pub(super) fn svg_value_list_clear_callback<'s>(
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
kind: SvgListKind,
|
||||
) {
|
||||
if require_svg_value_list_items(scope, args.this(), kind, "clear", true).is_none() {
|
||||
return;
|
||||
}
|
||||
set_svg_value_list_items(scope, args.this(), v8::Array::new(scope, 0), kind);
|
||||
reflect_svg_value_list_to_owner_attribute(scope, args.this(), kind);
|
||||
rv.set_undefined();
|
||||
@@ -2025,10 +2087,15 @@ pub(super) fn svg_value_list_initialize_callback<'s>(
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
kind: SvgListKind,
|
||||
) {
|
||||
if require_svg_value_list_items(scope, args.this(), kind, "initialize", true).is_none() {
|
||||
return;
|
||||
}
|
||||
let Some(parsed) = webidl::parse_args::<SvgListItemArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let item = svg_value_list_item_or_default(scope, parsed.item, kind);
|
||||
let Some(item) = svg_value_list_item_or_throw(scope, parsed.item, kind) else {
|
||||
return;
|
||||
};
|
||||
let Some(items) = serialize_v8_array(scope, [item]) else {
|
||||
return;
|
||||
};
|
||||
@@ -2059,10 +2126,13 @@ pub(super) fn svg_value_list_get_item_callback<'s>(
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
kind: SvgListKind,
|
||||
) {
|
||||
let Some(items) = require_svg_value_list_items(scope, args.this(), kind, "getItem", false)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<SvgListIndexArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let items = svg_value_list_items(scope, args.this(), kind);
|
||||
let Some(item) = svg_list_item_or_throw(scope, items, parsed.index) else {
|
||||
return;
|
||||
};
|
||||
@@ -2091,11 +2161,17 @@ pub(super) fn svg_value_list_insert_item_before_callback<'s>(
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
kind: SvgListKind,
|
||||
) {
|
||||
let Some(items) =
|
||||
require_svg_value_list_items(scope, args.this(), kind, "insertItemBefore", true)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<SvgListItemIndexArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let item = svg_value_list_item_or_default(scope, parsed.item, kind);
|
||||
let items = svg_value_list_items(scope, args.this(), kind);
|
||||
let Some(item) = svg_value_list_item_or_throw(scope, parsed.item, kind) else {
|
||||
return;
|
||||
};
|
||||
let length = items.length();
|
||||
let index = parsed.index.min(length);
|
||||
let next = v8::Array::new(scope, (length + 1) as i32);
|
||||
@@ -2137,15 +2213,20 @@ pub(super) fn svg_value_list_replace_item_callback<'s>(
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
kind: SvgListKind,
|
||||
) {
|
||||
let Some(items) = require_svg_value_list_items(scope, args.this(), kind, "replaceItem", true)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<SvgListItemIndexArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let items = svg_value_list_items(scope, args.this(), kind);
|
||||
let Some(item) = svg_value_list_item_or_throw(scope, parsed.item, kind) else {
|
||||
return;
|
||||
};
|
||||
if parsed.index >= items.length() {
|
||||
webidl::throw_index_size_error(scope);
|
||||
return;
|
||||
}
|
||||
let item = svg_value_list_item_or_default(scope, parsed.item, kind);
|
||||
if let Some(replaced) = items
|
||||
.get_index(scope, parsed.index)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
@@ -2182,11 +2263,14 @@ pub(super) fn svg_value_list_remove_item_callback<'s>(
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
kind: SvgListKind,
|
||||
) {
|
||||
let Some(items) = require_svg_value_list_items(scope, args.this(), kind, "removeItem", true)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<SvgListIndexArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let index = parsed.index;
|
||||
let items = svg_value_list_items(scope, args.this(), kind);
|
||||
let length = items.length();
|
||||
if index >= length {
|
||||
webidl::throw_index_size_error(scope);
|
||||
@@ -2236,17 +2320,168 @@ pub(super) fn svg_value_list_append_item_callback<'s>(
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
kind: SvgListKind,
|
||||
) {
|
||||
let Some(items) = require_svg_value_list_items(scope, args.this(), kind, "appendItem", true)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<SvgListItemArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let item = svg_value_list_item_or_default(scope, parsed.item, kind);
|
||||
let items = svg_value_list_items(scope, args.this(), kind);
|
||||
let Some(item) = svg_value_list_item_or_throw(scope, parsed.item, kind) else {
|
||||
return;
|
||||
};
|
||||
set_svg_value_list_item_owner_list(scope, item, args.this());
|
||||
let _ = items.set_index(scope, items.length(), item.into());
|
||||
reflect_svg_value_list_to_owner_attribute(scope, args.this(), kind);
|
||||
rv.set(item.into());
|
||||
}
|
||||
|
||||
fn svg_value_list_intercepted_items<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
) -> Option<(SvgListKind, v8::Local<'s, v8::Array>)> {
|
||||
let kind = svg_value_list_kind(scope, list)?;
|
||||
sync_svg_value_list_from_owner_attribute(scope, list, kind);
|
||||
Some((kind, svg_value_list_items(scope, list, kind)))
|
||||
}
|
||||
|
||||
fn svg_value_list_indexed_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(value) = svg_value_list_intercepted_items(scope, args.holder())
|
||||
.map(|(_, items)| items)
|
||||
.filter(|items| index < items.length())
|
||||
.and_then(|items| items.get_index(scope, index))
|
||||
else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
rv.set(value);
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn svg_value_list_indexed_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Boolean>,
|
||||
) -> v8::Intercepted {
|
||||
let list = args.holder();
|
||||
let Some(kind) = svg_value_list_kind(scope, list) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let Some(items) = require_svg_value_list_items(scope, list, kind, "indexed setter", true)
|
||||
else {
|
||||
return v8::Intercepted::kYes;
|
||||
};
|
||||
let Some(item) = svg_value_list_item_or_throw(scope, value, kind) else {
|
||||
return v8::Intercepted::kYes;
|
||||
};
|
||||
if index >= items.length() {
|
||||
webidl::throw_index_size_error(scope);
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
if let Some(replaced) = items
|
||||
.get_index(scope, index)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
{
|
||||
clear_svg_value_list_item_owner_list(scope, replaced);
|
||||
}
|
||||
let _ = items.set_index(scope, index, item.into());
|
||||
set_svg_value_list_item_owner_list(scope, item, list);
|
||||
reflect_svg_value_list_to_owner_attribute(scope, list, kind);
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn svg_value_list_indexed_query<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Integer>,
|
||||
) -> v8::Intercepted {
|
||||
let Some((_, items)) = svg_value_list_intercepted_items(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
if index >= items.length() {
|
||||
return v8::Intercepted::kNo;
|
||||
}
|
||||
rv.set_int32(v8::PropertyAttribute::NONE.as_u32() as i32);
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn svg_value_list_indexed_deleter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Boolean>,
|
||||
) -> v8::Intercepted {
|
||||
let Some((_, items)) = svg_value_list_intercepted_items(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
if index >= items.length() {
|
||||
return v8::Intercepted::kNo;
|
||||
}
|
||||
rv.set_bool(false);
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn svg_value_list_indexed_definer<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
descriptor: &v8::PropertyDescriptor,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Boolean>,
|
||||
) -> v8::Intercepted {
|
||||
if descriptor.has_get() || descriptor.has_set() {
|
||||
rv.set_bool(false);
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
let value = if descriptor.has_value() {
|
||||
v8::Local::new(scope, descriptor.value())
|
||||
} else {
|
||||
v8::undefined(scope).into()
|
||||
};
|
||||
svg_value_list_indexed_setter(scope, index, value, args, rv)
|
||||
}
|
||||
|
||||
fn svg_value_list_indexed_enumerator<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Array>,
|
||||
) {
|
||||
let length = svg_value_list_intercepted_items(scope, args.holder())
|
||||
.map(|(_, items)| items.length())
|
||||
.unwrap_or(0);
|
||||
let keys = (0..length)
|
||||
.map(|index| v8::Integer::new_from_unsigned(scope, index).into())
|
||||
.collect::<Vec<_>>();
|
||||
rv.set(v8::Array::new_with_elements(scope, &keys));
|
||||
}
|
||||
|
||||
fn svg_value_list_indexed_descriptor<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(value) = svg_value_list_intercepted_items(scope, args.holder())
|
||||
.map(|(_, items)| items)
|
||||
.filter(|items| index < items.length())
|
||||
.and_then(|items| items.get_index(scope, index))
|
||||
else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let Ok(descriptor) = DataPropertyDescriptorDeclaration::new(value, true, true).bind(scope)
|
||||
else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
rv.set(descriptor.into());
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
pub(super) fn svg_transform_list_length_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
|
||||
@@ -79,6 +79,7 @@ const SVG_STRING_LIST_SYNCED_ATTRIBUTE_PRESENT_SLOT: &str =
|
||||
const SVG_VALUE_LIST_OWNER_ELEMENT_SLOT: &str = "__moliSvgValueListOwnerElement";
|
||||
const SVG_VALUE_LIST_OWNER_ATTRIBUTE_SLOT: &str = "__moliSvgValueListOwnerAttribute";
|
||||
const SVG_VALUE_LIST_ITEM_OWNER_LIST_SLOT: &str = "__moliSvgValueListItemOwnerList";
|
||||
const SVG_VALUE_LIST_READ_ONLY_SLOT: &str = "__moliSvgValueListReadOnly";
|
||||
const SVG_VALUE_LIST_SYNCED_ATTRIBUTE_VALUE_SLOT: &str = "__moliSvgValueListSyncedAttributeValue";
|
||||
const SVG_ANIMATED_ENUMERATION_BASE_VAL_SLOT: &str = "__moliSvgAnimatedEnumerationBaseVal";
|
||||
const SVG_ANIMATED_ENUMERATION_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedEnumerationAnimVal";
|
||||
|
||||
+85
@@ -3017,3 +3017,88 @@ fn svg_href_animated_string_prefers_href_and_falls_back_to_xlink_href() {
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svg_value_lists_enforce_item_types_indices_and_read_only_anim_values() {
|
||||
let mut vm = new_storage_test_vm("https://svg-value-list-semantics.test/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
const errorName = callback => {
|
||||
try {
|
||||
callback();
|
||||
return "none";
|
||||
} catch (error) {
|
||||
return error.name;
|
||||
}
|
||||
};
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
const text = document.createElementNS(SVG_NS, "text");
|
||||
const svg = document.createElementNS(SVG_NS, "svg");
|
||||
text.setAttribute("x", "10 20");
|
||||
text.setAttribute("rotate", "15 30");
|
||||
|
||||
const lengths = text.x;
|
||||
const lengthBase = lengths.baseVal;
|
||||
const lengthAnim = lengths.animVal;
|
||||
const numbers = text.rotate;
|
||||
const numberBase = numbers.baseVal;
|
||||
const numberAnim = numbers.animVal;
|
||||
const invalidItems = [30, "invalid", text, null];
|
||||
|
||||
for (const item of invalidItems) {
|
||||
assert(errorName(() => lengthBase.initialize(item)) === "TypeError",
|
||||
"SVGLengthList.initialize item type");
|
||||
assert(errorName(() => lengthBase.insertItemBefore(item, 0)) === "TypeError",
|
||||
"SVGLengthList.insertItemBefore item type");
|
||||
assert(errorName(() => lengthBase.replaceItem(item, 0)) === "TypeError",
|
||||
"SVGLengthList.replaceItem item type");
|
||||
assert(errorName(() => lengthBase.appendItem(item)) === "TypeError",
|
||||
"SVGLengthList.appendItem item type");
|
||||
assert(errorName(() => { lengthBase[0] = item; }) === "TypeError",
|
||||
"SVGLengthList indexed setter item type");
|
||||
}
|
||||
|
||||
const length = svg.createSVGLength();
|
||||
length.value = 42;
|
||||
lengthBase[0] = length;
|
||||
assert(lengthBase[0] === length, "SVGLengthList indexed getter");
|
||||
assert(text.getAttribute("x") === "42 20", "SVGLengthList indexed reflection");
|
||||
|
||||
const number = svg.createSVGNumber();
|
||||
number.value = 7;
|
||||
numberBase[1] = number;
|
||||
assert(numberBase[1] === number, "SVGNumberList indexed getter");
|
||||
assert(text.getAttribute("rotate") === "15 7", "SVGNumberList indexed reflection");
|
||||
assert(errorName(() => lengthBase.appendItem(number)) === "TypeError",
|
||||
"SVGLengthList rejects SVGNumber");
|
||||
assert(errorName(() => numberBase.appendItem(length)) === "TypeError",
|
||||
"SVGNumberList rejects SVGLength");
|
||||
|
||||
text.setAttribute("x", "1 2 3");
|
||||
assert(lengthBase.length === 3 && lengthBase[2].value === 3,
|
||||
"saved baseVal resynchronizes");
|
||||
assert(text.x.animVal.length === 3 && text.x.animVal[2].value === 3,
|
||||
"animVal resynchronizes after direct baseVal access");
|
||||
|
||||
assert(errorName(() => lengthAnim.clear()) === "NoModificationAllowedError",
|
||||
"SVGLengthList animVal clear");
|
||||
assert(errorName(() => { lengthAnim[0] = length; }) === "NoModificationAllowedError",
|
||||
"SVGLengthList animVal indexed setter");
|
||||
assert(errorName(() => numberAnim.appendItem(number)) === "NoModificationAllowedError",
|
||||
"SVGNumberList animVal appendItem");
|
||||
assert(errorName(() => SVGLengthList.prototype.clear.call({})) === "TypeError",
|
||||
"SVGLengthList receiver brand");
|
||||
return "ok";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("SVG value list semantics probe should evaluate");
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user