fix(svg): reflect animated integer attributes

This commit is contained in:
ldm0
2026-09-28 01:47:00 +08:00
parent 304e02c233
commit 1086b8d3ab
8 changed files with 592 additions and 0 deletions
@@ -1822,6 +1822,7 @@ pub(crate) fn finish_context_bootstrap(
("SVGLengthList", "SVGLengthList"),
("SVGAnimatedLengthList", "SVGAnimatedLengthList"),
("SVGAnimatedNumber", "SVGAnimatedNumber"),
("SVGAnimatedInteger", "SVGAnimatedInteger"),
("SVGNumberList", "SVGNumberList"),
("SVGStringList", "SVGStringList"),
("SVGAnimatedNumberList", "SVGAnimatedNumberList"),
@@ -124,6 +124,10 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
interface: web_api_interfaces::SVGAnimatedNumber::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::SVGAnimatedInteger::DESCRIPTOR,
kind: ConstructorKind::Illegal,
},
ConstructorSpec {
interface: web_api_interfaces::SVGNumberList::DESCRIPTOR,
kind: ConstructorKind::Illegal,
@@ -1000,6 +1000,25 @@ struct SvgAnimatedNumberTemplateAccessorsDeclaration {
anim_val: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGAnimatedInteger, enumerable)]
struct SvgAnimatedIntegerTemplateAccessorsDeclaration {
#[webapi(
accessor_property = "baseVal",
getter = svg_animated_integer_getter,
setter = svg_animated_integer_setter,
data = callback_data_index_value(scope, 0)
)]
base_val: (),
#[webapi(
accessor_property = "animVal",
getter = svg_animated_integer_getter,
data = callback_data_index_value(scope, 1)
)]
anim_val: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGAnimatedNumberList, enumerable)]
struct SvgAnimatedNumberListTemplateAccessorsDeclaration {
@@ -1373,6 +1392,34 @@ struct SvgFeCompositeElementPrototypeAccessorsDeclaration {
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGFEConvolveMatrixElement, enumerable)]
struct SvgFeConvolveMatrixElementPrototypeAccessorsDeclaration {
#[webapi(
accessor_property = "orderX",
getter = svg_element_animated_integer_getter,
data = callback_data_index_value(scope, 0)
)]
order_x: (),
#[webapi(
accessor_property = "orderY",
getter = svg_element_animated_integer_getter,
data = callback_data_index_value(scope, 1)
)]
order_y: (),
#[webapi(
accessor_property = "targetX",
getter = svg_element_animated_integer_getter,
data = callback_data_index_value(scope, 2)
)]
target_x: (),
#[webapi(
accessor_property = "targetY",
getter = svg_element_animated_integer_getter,
data = callback_data_index_value(scope, 3)
)]
target_y: (),
#[webapi(
accessor_property = "edgeMode",
getter = svg_element_animated_enumeration_getter,
@@ -1419,6 +1466,13 @@ struct SvgFeMorphologyElementPrototypeAccessorsDeclaration {
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGFETurbulenceElement, enumerable)]
struct SvgFeTurbulenceElementPrototypeAccessorsDeclaration {
#[webapi(
accessor_property = "numOctaves",
getter = svg_element_animated_integer_getter,
data = callback_data_index_value(scope, 4)
)]
num_octaves: (),
#[webapi(
accessor_property = "stitchTiles",
getter = svg_element_animated_enumeration_getter,
@@ -1690,6 +1744,14 @@ pub(super) fn install_svg_animated_number_bindings<'s>(
SvgAnimatedNumberTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto);
}
pub(super) fn install_svg_animated_integer_bindings<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
template: v8::Local<'s, v8::FunctionTemplate>,
) {
let proto = template.prototype_template(scope);
SvgAnimatedIntegerTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto);
}
pub(super) fn install_svg_animated_number_list_bindings<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
template: v8::Local<'s, v8::FunctionTemplate>,
@@ -40,6 +40,18 @@ struct SvgAnimatedNumberObjectDeclaration {
anim_val: f64,
}
#[derive(WebApiObject)]
#[webapi(
interface = web_api_interfaces::SVGAnimatedInteger,
own_to_string_tag = "SVGAnimatedInteger"
)]
struct SvgAnimatedIntegerObjectDeclaration {
#[webapi(slot = SVG_ANIMATED_INTEGER_BASE_VAL_SLOT)]
base_val: i32,
#[webapi(slot = SVG_ANIMATED_INTEGER_ANIM_VAL_SLOT)]
anim_val: i32,
}
#[derive(WebApiObject)]
#[webapi(interface = web_api_interfaces::SVGNumber, own_to_string_tag = "SVGNumber")]
struct SvgNumberObjectDeclaration {
@@ -516,6 +528,163 @@ pub(super) fn build_svg_animated_number_for_attribute<'s>(
object
}
pub(super) fn build_svg_animated_integer<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: i32,
) -> v8::Local<'s, v8::Object> {
SvgAnimatedIntegerObjectDeclaration::new(value, value)
.bind(scope)
.expect("SVGAnimatedInteger declaration should bind")
}
pub(super) fn build_svg_animated_integer_for_property<'s>(
scope: &mut v8::PinScope<'s, '_>,
owner: v8::Local<'s, v8::Object>,
property: SvgAnimatedIntegerProperty,
) -> v8::Local<'s, v8::Object> {
let object = build_svg_animated_integer(scope, property.initial_value);
set_private_value(
scope,
object,
SVG_ANIMATED_INTEGER_OWNER_ELEMENT_SLOT,
owner.into(),
);
let index = u32::try_from(property.index).expect("SVG integer property index exceeds u32");
let index = v8::Integer::new_from_unsigned(scope, index);
set_private_value(
scope,
object,
SVG_ANIMATED_INTEGER_PROPERTY_INDEX_SLOT,
index.into(),
);
sync_svg_animated_integer_from_owner_attribute(scope, object);
object
}
pub(super) fn svg_animated_integer_property<'s>(
scope: &mut v8::PinScope<'s, '_>,
animated: v8::Local<'s, v8::Object>,
) -> Option<SvgAnimatedIntegerProperty> {
let index = get_private_value(scope, animated, SVG_ANIMATED_INTEGER_PROPERTY_INDEX_SLOT)?
.uint32_value(scope)?;
SVG_ANIMATED_INTEGER_PROPERTIES.get(index as usize).copied()
}
pub(super) fn sync_svg_animated_integer_from_owner_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
animated: v8::Local<'s, v8::Object>,
) {
let Some(property) = svg_animated_integer_property(scope, animated) else {
return;
};
let Some(owner) = get_private_value(scope, animated, SVG_ANIMATED_INTEGER_OWNER_ELEMENT_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
else {
return;
};
let value = svg_owner_attribute_value(scope, owner, property.attribute)
.as_deref()
.and_then(|raw| parse_svg_animated_integer(property, raw))
.unwrap_or(property.initial_value);
set_svg_animated_integer_values(scope, animated, value);
}
pub(super) fn reflect_svg_animated_integer_to_owner_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
animated: v8::Local<'s, v8::Object>,
) {
let Some(property) = svg_animated_integer_property(scope, animated) else {
return;
};
let Some(owner) = get_private_value(scope, animated, SVG_ANIMATED_INTEGER_OWNER_ELEMENT_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
else {
return;
};
let value = get_private_value(scope, animated, SVG_ANIMATED_INTEGER_BASE_VAL_SLOT)
.and_then(|value| value.int32_value(scope))
.unwrap_or(property.initial_value);
let serialized = match property.component {
SvgAnimatedIntegerComponent::Scalar => value.to_string(),
SvgAnimatedIntegerComponent::PairFirst | SvgAnimatedIntegerComponent::PairSecondOrFirst => {
let (mut first, mut second) =
svg_owner_attribute_value(scope, owner, property.attribute)
.as_deref()
.and_then(parse_svg_integer_pair)
.unwrap_or((property.initial_value, property.initial_value));
match property.component {
SvgAnimatedIntegerComponent::PairFirst => first = value,
SvgAnimatedIntegerComponent::PairSecondOrFirst => second = value,
SvgAnimatedIntegerComponent::Scalar => unreachable!(),
}
format!("{first} {second}")
}
};
let Ok((runtime_ptr, handle)) =
crate::native_bridge::node_runtime_and_handle_from_object(scope, owner)
else {
return;
};
let runtime = unsafe { &mut *runtime_ptr };
let _ = runtime.set_attribute(scope, runtime_ptr, handle, property.attribute, &serialized);
}
pub(super) fn set_svg_animated_integer_values(
scope: &mut v8::PinScope<'_, '_>,
animated: v8::Local<'_, v8::Object>,
value: i32,
) {
let value = v8::Integer::new(scope, value);
set_private_value(
scope,
animated,
SVG_ANIMATED_INTEGER_BASE_VAL_SLOT,
value.into(),
);
set_private_value(
scope,
animated,
SVG_ANIMATED_INTEGER_ANIM_VAL_SLOT,
value.into(),
);
}
pub(super) fn parse_svg_animated_integer(
property: SvgAnimatedIntegerProperty,
value: &str,
) -> Option<i32> {
match property.component {
SvgAnimatedIntegerComponent::Scalar => parse_svg_integer(value),
SvgAnimatedIntegerComponent::PairFirst => {
parse_svg_integer_pair(value).map(|(first, _)| first)
}
SvgAnimatedIntegerComponent::PairSecondOrFirst => {
parse_svg_integer_pair(value).map(|(_, second)| second)
}
}
}
fn parse_svg_integer(value: &str) -> Option<i32> {
value.trim().parse().ok()
}
fn parse_svg_integer_pair(value: &str) -> Option<(i32, i32)> {
let value = value.trim();
if let Some((first, second)) = value.split_once(',') {
if second.contains(',') {
return None;
}
return Some((parse_svg_integer(first)?, parse_svg_integer(second)?));
}
let mut values = value.split_ascii_whitespace();
let first = parse_svg_integer(values.next()?)?;
let second = match values.next() {
Some(value) => parse_svg_integer(value)?,
None => first,
};
values.next().is_none().then_some((first, second))
}
pub(super) fn build_svg_number<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: f64,
@@ -691,6 +691,60 @@ pub(super) fn svg_element_animated_enumeration_getter<'s>(
rv.set(value.into());
}
pub(super) fn svg_element_animated_integer_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(property) = callback_data_item(
scope,
&args,
SVG_ANIMATED_INTEGER_PROPERTIES,
"SVG animated integer properties",
) else {
rv.set_undefined();
return;
};
let holder = args.this();
let Ok((runtime_ptr, handle)) =
crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, holder)
else {
webidl::throw_type_error(
scope,
&format!(
"{}.{} called on incompatible receiver.",
property.interface, property.name
),
);
return;
};
let is_expected_element = unsafe { &*runtime_ptr }
.dom_host()
.node(handle)
.and_then(|node| node.as_element())
.is_some_and(|element| element.is_svg_element(property.local_name));
if !is_expected_element {
webidl::throw_type_error(
scope,
&format!(
"{}.{} called on incompatible receiver.",
property.interface, property.name
),
);
return;
}
if let Some(value) = get_private_value(scope, holder, property.cache_slot) {
if let Ok(animated) = v8::Local::<v8::Object>::try_from(value) {
sync_svg_animated_integer_from_owner_attribute(scope, animated);
}
rv.set(value);
return;
}
let value = build_svg_animated_integer_for_property(scope, holder, property);
set_private_value(scope, holder, property.cache_slot, value.into());
rv.set(value.into());
}
pub(super) fn svg_text_positioning_list_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -1257,6 +1311,47 @@ pub(super) fn svg_animated_number_getter<'s>(
rv.set(v8::Number::new(scope, value).into());
}
pub(super) fn svg_animated_integer_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(name) = callback_data_item(
scope,
&args,
SVG_ANIMATED_ACCESSOR_NAMES,
"SVGAnimatedInteger attributes",
) else {
rv.set_undefined();
return;
};
if !require_svg_receiver(
scope,
args.this(),
SVG_ANIMATED_INTEGER_BASE_VAL_SLOT,
"SVGAnimatedInteger",
&format!("{name} getter"),
) {
return;
}
let slot = match name {
"baseVal" => SVG_ANIMATED_INTEGER_BASE_VAL_SLOT,
"animVal" => SVG_ANIMATED_INTEGER_ANIM_VAL_SLOT,
_ => {
rv.set_undefined();
return;
}
};
sync_svg_animated_integer_from_owner_attribute(scope, args.this());
let initial_value = svg_animated_integer_property(scope, args.this())
.map(|property| property.initial_value)
.unwrap_or_default();
let value = get_private_value(scope, args.this(), slot)
.and_then(|value| value.int32_value(scope))
.unwrap_or(initial_value);
rv.set(v8::Integer::new(scope, value).into());
}
pub(super) fn svg_animated_number_list_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -1427,6 +1522,44 @@ pub(super) fn svg_animated_number_setter<'s>(
reflect_svg_animated_number_to_owner_attribute(scope, args.this());
}
pub(super) fn svg_animated_integer_setter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(name) = callback_data_item(
scope,
&args,
SVG_ANIMATED_ACCESSOR_NAMES,
"SVGAnimatedInteger attributes",
) else {
return;
};
if !require_svg_receiver(
scope,
args.this(),
SVG_ANIMATED_INTEGER_BASE_VAL_SLOT,
"SVGAnimatedInteger",
&format!("{name} setter"),
) || name != "baseVal"
{
return;
}
let value = match webidl::convert::<webidl::Long>(
scope,
args.get(0),
webidl::Context::member("SVGAnimatedInteger", "baseVal"),
) {
Ok(value) => value.0,
Err(error) => {
webidl::throw_error(scope, &error);
return;
}
};
set_svg_animated_integer_values(scope, args.this(), value);
reflect_svg_animated_integer_to_owner_attribute(scope, args.this());
}
pub(super) fn svg_animated_transform_list_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -63,6 +63,10 @@ const SVG_ANIMATED_NUMBER_OWNER_ELEMENT_SLOT: &str = "__moliSvgAnimatedNumberOwn
const SVG_ANIMATED_NUMBER_OWNER_ATTRIBUTE_SLOT: &str = "__moliSvgAnimatedNumberOwnerAttribute";
const SVG_ANIMATED_NUMBER_BASE_VAL_SLOT: &str = "__moliSvgAnimatedNumberBaseVal";
const SVG_ANIMATED_NUMBER_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedNumberAnimVal";
const SVG_ANIMATED_INTEGER_BASE_VAL_SLOT: &str = "__moliSvgAnimatedIntegerBaseVal";
const SVG_ANIMATED_INTEGER_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedIntegerAnimVal";
const SVG_ANIMATED_INTEGER_OWNER_ELEMENT_SLOT: &str = "__moliSvgAnimatedIntegerOwnerElement";
const SVG_ANIMATED_INTEGER_PROPERTY_INDEX_SLOT: &str = "__moliSvgAnimatedIntegerPropertyIndex";
const SVG_ANIMATED_NUMBER_LIST_BASE_VAL_SLOT: &str = "__moliSvgAnimatedNumberListBaseVal";
const SVG_ANIMATED_NUMBER_LIST_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedNumberListAnimVal";
const SVG_NUMBER_LIST_ITEMS_SLOT: &str = "__moliSvgNumberListItems";
@@ -96,11 +100,16 @@ const SVG_FE_BLEND_MODE_SLOT: &str = "__moliSvgFeBlendMode";
const SVG_FE_COLOR_MATRIX_TYPE_SLOT: &str = "__moliSvgFeColorMatrixType";
const SVG_FE_COMPOSITE_OPERATOR_SLOT: &str = "__moliSvgFeCompositeOperator";
const SVG_FE_CONVOLVE_MATRIX_EDGE_MODE_SLOT: &str = "__moliSvgFeConvolveMatrixEdgeMode";
const SVG_FE_CONVOLVE_MATRIX_ORDER_X_SLOT: &str = "__moliSvgFeConvolveMatrixOrderX";
const SVG_FE_CONVOLVE_MATRIX_ORDER_Y_SLOT: &str = "__moliSvgFeConvolveMatrixOrderY";
const SVG_FE_CONVOLVE_MATRIX_TARGET_X_SLOT: &str = "__moliSvgFeConvolveMatrixTargetX";
const SVG_FE_CONVOLVE_MATRIX_TARGET_Y_SLOT: &str = "__moliSvgFeConvolveMatrixTargetY";
const SVG_FE_DISPLACEMENT_MAP_X_CHANNEL_SLOT: &str = "__moliSvgFeDisplacementMapXChannel";
const SVG_FE_DISPLACEMENT_MAP_Y_CHANNEL_SLOT: &str = "__moliSvgFeDisplacementMapYChannel";
const SVG_FE_MORPHOLOGY_OPERATOR_SLOT: &str = "__moliSvgFeMorphologyOperator";
const SVG_FE_TURBULENCE_STITCH_TILES_SLOT: &str = "__moliSvgFeTurbulenceStitchTiles";
const SVG_FE_TURBULENCE_TYPE_SLOT: &str = "__moliSvgFeTurbulenceType";
const SVG_FE_TURBULENCE_NUM_OCTAVES_SLOT: &str = "__moliSvgFeTurbulenceNumOctaves";
const SVG_TEXT_PATH_METHOD_SLOT: &str = "__moliSvgTextPathMethod";
const SVG_TEXT_PATH_SPACING_SLOT: &str = "__moliSvgTextPathSpacing";
const SVG_TEXT_PATH_SIDE_SLOT: &str = "__moliSvgTextPathSide";
@@ -155,6 +164,25 @@ struct SvgAnimatedEnumerationProperty {
kind: SvgAnimatedEnumerationKind,
}
#[derive(Clone, Copy)]
enum SvgAnimatedIntegerComponent {
Scalar,
PairFirst,
PairSecondOrFirst,
}
#[derive(Clone, Copy)]
struct SvgAnimatedIntegerProperty {
index: usize,
interface: &'static str,
local_name: &'static str,
name: &'static str,
attribute: &'static str,
cache_slot: &'static str,
initial_value: i32,
component: SvgAnimatedIntegerComponent,
}
#[derive(webidl::WebIdlArgs)]
#[webidl(prefix = "SVG list item")]
struct SvgListItemArgs<'s> {
@@ -707,6 +735,59 @@ const SVG_ANIMATED_ENUMERATION_PROPERTIES: &[SvgAnimatedEnumerationProperty] = &
},
];
const SVG_ANIMATED_INTEGER_PROPERTIES: &[SvgAnimatedIntegerProperty] = &[
SvgAnimatedIntegerProperty {
index: 0,
interface: "SVGFEConvolveMatrixElement",
local_name: "feConvolveMatrix",
name: "orderX",
attribute: "order",
cache_slot: SVG_FE_CONVOLVE_MATRIX_ORDER_X_SLOT,
initial_value: 3,
component: SvgAnimatedIntegerComponent::PairFirst,
},
SvgAnimatedIntegerProperty {
index: 1,
interface: "SVGFEConvolveMatrixElement",
local_name: "feConvolveMatrix",
name: "orderY",
attribute: "order",
cache_slot: SVG_FE_CONVOLVE_MATRIX_ORDER_Y_SLOT,
initial_value: 3,
component: SvgAnimatedIntegerComponent::PairSecondOrFirst,
},
SvgAnimatedIntegerProperty {
index: 2,
interface: "SVGFEConvolveMatrixElement",
local_name: "feConvolveMatrix",
name: "targetX",
attribute: "targetX",
cache_slot: SVG_FE_CONVOLVE_MATRIX_TARGET_X_SLOT,
initial_value: 0,
component: SvgAnimatedIntegerComponent::Scalar,
},
SvgAnimatedIntegerProperty {
index: 3,
interface: "SVGFEConvolveMatrixElement",
local_name: "feConvolveMatrix",
name: "targetY",
attribute: "targetY",
cache_slot: SVG_FE_CONVOLVE_MATRIX_TARGET_Y_SLOT,
initial_value: 0,
component: SvgAnimatedIntegerComponent::Scalar,
},
SvgAnimatedIntegerProperty {
index: 4,
interface: "SVGFETurbulenceElement",
local_name: "feTurbulence",
name: "numOctaves",
attribute: "numOctaves",
cache_slot: SVG_FE_TURBULENCE_NUM_OCTAVES_SLOT,
initial_value: 1,
component: SvgAnimatedIntegerComponent::Scalar,
},
];
pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
template: v8::Local<'s, v8::FunctionTemplate>,
@@ -728,6 +809,7 @@ pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>(
bindings::install_svg_animated_length_list_bindings(scope, template)
}
"SVGAnimatedNumber" => bindings::install_svg_animated_number_bindings(scope, template),
"SVGAnimatedInteger" => bindings::install_svg_animated_integer_bindings(scope, template),
"SVGNumberList" => bindings::install_svg_number_list_bindings(scope, template),
"SVGStringList" => bindings::install_svg_string_list_bindings(scope, template),
"SVGAnimatedNumberList" => {
@@ -624,6 +624,146 @@ fn svg_filter_and_text_path_enumerations_reflect_typed_content_attributes() {
assert_eq!(result, "ok");
}
#[test]
fn svg_animated_integers_reflect_filter_content_attributes() {
let mut vm = new_parsed_test_vm(
"https://svg-animated-integer.test/",
"<!doctype html><html><body></body></html>",
);
let result = vm
.eval(
r#"
(() => {
const assert = (condition, message) => {
if (!condition) throw new Error(message);
};
const ns = "http://www.w3.org/2000/svg";
const convolve = document.createElementNS(ns, "feConvolveMatrix");
const turbulence = document.createElementNS(ns, "feTurbulence");
assert(typeof SVGAnimatedInteger === "function", "constructor exposed");
let illegalConstructor = false;
try {
new SVGAnimatedInteger();
} catch (error) {
illegalConstructor = error instanceof TypeError;
}
assert(illegalConstructor, "illegal constructor");
const baseDescriptor = Object.getOwnPropertyDescriptor(
SVGAnimatedInteger.prototype,
"baseVal",
);
const animDescriptor = Object.getOwnPropertyDescriptor(
SVGAnimatedInteger.prototype,
"animVal",
);
assert(typeof baseDescriptor.get === "function" &&
typeof baseDescriptor.set === "function", "baseVal descriptor");
assert(typeof animDescriptor.get === "function" && animDescriptor.set === undefined,
"animVal descriptor");
assert(baseDescriptor.enumerable && baseDescriptor.configurable &&
animDescriptor.enumerable && animDescriptor.configurable,
"animated integer descriptor flags");
const orderX = convolve.orderX;
const orderY = convolve.orderY;
const targetX = convolve.targetX;
const targetY = convolve.targetY;
const numOctaves = turbulence.numOctaves;
for (const [owner, property, value, initial] of [
[convolve, "orderX", orderX, 3],
[convolve, "orderY", orderY, 3],
[convolve, "targetX", targetX, 0],
[convolve, "targetY", targetY, 0],
[turbulence, "numOctaves", numOctaves, 1],
]) {
assert(value instanceof SVGAnimatedInteger, `${property} interface`);
assert(Object.prototype.toString.call(value) === "[object SVGAnimatedInteger]",
`${property} tag`);
assert(owner[property] === value, `${property} SameObject`);
assert(value.baseVal === initial && value.animVal === initial,
`${property} initial value`);
const descriptor = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(owner),
property,
);
assert(typeof descriptor.get === "function" && descriptor.set === undefined,
`${property} element descriptor`);
assert(descriptor.enumerable && descriptor.configurable,
`${property} element descriptor flags`);
}
convolve.setAttribute("order", "5");
assert(orderX.baseVal === 5 && orderY.baseVal === 5, "single order value");
convolve.setAttribute("order", "5 7");
assert(orderX.baseVal === 5 && orderY.baseVal === 7, "paired order values");
convolve.setAttribute("order", "5, 7");
assert(orderX.baseVal === 5 && orderY.baseVal === 7, "comma order values");
convolve.setAttribute("order", "-1.5");
assert(orderX.baseVal === 3 && orderY.baseVal === 3, "invalid order value");
convolve.removeAttribute("order");
assert(orderX.baseVal === 3 && orderY.baseVal === 3, "removed order value");
convolve.setAttribute("targetX", "42");
convolve.setAttribute("targetY", "-4");
turbulence.setAttribute("numOctaves", "6");
assert(targetX.baseVal === 42 && targetY.baseVal === -4,
"target content attributes");
assert(numOctaves.baseVal === 6, "numOctaves content attribute");
turbulence.setAttribute("numOctaves", "invalid");
assert(numOctaves.baseVal === 1, "invalid numOctaves value");
targetX.baseVal = -1;
assert(convolve.getAttribute("targetX") === "-1", "negative reflection");
assert(targetX.baseVal === -1 && targetX.animVal === -1,
"negative reflected values");
targetX.baseVal = 300;
assert(targetX.baseVal === 300, "positive assignment");
targetX.baseVal = "aString";
assert(targetX.baseVal === 0 && convolve.getAttribute("targetX") === "0",
"WebIDL string conversion");
targetX.baseVal = convolve;
assert(targetX.baseVal === 0, "WebIDL object conversion");
orderX.baseVal = -2;
assert(orderX.baseVal === -2 && orderY.baseVal === 3,
"orderX reflected independently");
assert(convolve.getAttribute("order") === "-2 3", "orderX serialization");
orderY.baseVal = 8;
assert(orderX.baseVal === -2 && orderY.baseVal === 8,
"orderY reflected independently");
assert(convolve.getAttribute("order") === "-2 8", "orderY serialization");
let incompatibleAnimatedReceiver = false;
try {
baseDescriptor.get.call({});
} catch (error) {
incompatibleAnimatedReceiver = error instanceof TypeError;
}
assert(incompatibleAnimatedReceiver, "animated integer receiver brand");
const orderDescriptor = Object.getOwnPropertyDescriptor(
SVGFEConvolveMatrixElement.prototype,
"orderX",
);
let incompatibleElementReceiver = false;
try {
orderDescriptor.get.call(turbulence);
} catch (error) {
incompatibleElementReceiver = error instanceof TypeError;
}
assert(incompatibleElementReceiver, "element receiver brand");
return "ok";
})()
"#,
)
.expect("SVG animated integer probe should evaluate");
assert_eq!(result, "ok");
}
#[test]
fn svg_animated_boolean_reflects_preserve_alpha() {
let mut vm = new_parsed_test_vm(
@@ -375,6 +375,7 @@ interfaces! {
SVGAngle;
SVGAnimatedAngle;
SVGMarkerElement: SVGElement;
SVGAnimatedInteger;
SVGAnimatedNumber;
SVGAnimatedNumberList;
SVGAnimatedTransformList;