fix(svg): reflect animated number attributes

This commit is contained in:
ldm0
2026-09-28 01:47:00 +08:00
parent 7bad525c7d
commit 712d26e79f
9 changed files with 1147 additions and 31 deletions
@@ -100,12 +100,20 @@ pub fn svg_element_interface_name(local_name: &str) -> &'static str {
"feColorMatrix" => "SVGFEColorMatrixElement",
"feComposite" => "SVGFECompositeElement",
"feConvolveMatrix" => "SVGFEConvolveMatrixElement",
"feDiffuseLighting" => "SVGFEDiffuseLightingElement",
"feDisplacementMap" => "SVGFEDisplacementMapElement",
"feDistantLight" => "SVGFEDistantLightElement",
"feDropShadow" => "SVGFEDropShadowElement",
"feFuncA" => "SVGFEFuncAElement",
"feFuncB" => "SVGFEFuncBElement",
"feFuncG" => "SVGFEFuncGElement",
"feFuncR" => "SVGFEFuncRElement",
"feGaussianBlur" => "SVGFEGaussianBlurElement",
"feMorphology" => "SVGFEMorphologyElement",
"feOffset" => "SVGFEOffsetElement",
"fePointLight" => "SVGFEPointLightElement",
"feSpecularLighting" => "SVGFESpecularLightingElement",
"feSpotLight" => "SVGFESpotLightElement",
"feTurbulence" => "SVGFETurbulenceElement",
"filter" => "SVGFilterElement",
"foreignObject" => "SVGForeignObjectElement",
@@ -123,6 +131,7 @@ pub fn svg_element_interface_name(local_name: &str) -> &'static str {
"radialGradient" => "SVGRadialGradientElement",
"rect" => "SVGRectElement",
"script" => "SVGScriptElement",
"stop" => "SVGStopElement",
"svg" => "SVGSVGElement",
"style" => "SVGStyleElement",
"symbol" => "SVGSymbolElement",
+30
View File
@@ -273,18 +273,47 @@ fn svg_element_interface_name_specializes_standard_elements() {
svg_element_interface_name("feConvolveMatrix"),
"SVGFEConvolveMatrixElement"
);
assert_eq!(
svg_element_interface_name("feDiffuseLighting"),
"SVGFEDiffuseLightingElement"
);
assert_eq!(
svg_element_interface_name("feDisplacementMap"),
"SVGFEDisplacementMapElement"
);
assert_eq!(
svg_element_interface_name("feDistantLight"),
"SVGFEDistantLightElement"
);
assert_eq!(
svg_element_interface_name("feDropShadow"),
"SVGFEDropShadowElement"
);
assert_eq!(svg_element_interface_name("feFuncA"), "SVGFEFuncAElement");
assert_eq!(svg_element_interface_name("feFuncB"), "SVGFEFuncBElement");
assert_eq!(svg_element_interface_name("feFuncG"), "SVGFEFuncGElement");
assert_eq!(svg_element_interface_name("feFuncR"), "SVGFEFuncRElement");
assert_eq!(
svg_element_interface_name("feGaussianBlur"),
"SVGFEGaussianBlurElement"
);
assert_eq!(
svg_element_interface_name("feMorphology"),
"SVGFEMorphologyElement"
);
assert_eq!(svg_element_interface_name("feOffset"), "SVGFEOffsetElement");
assert_eq!(
svg_element_interface_name("fePointLight"),
"SVGFEPointLightElement"
);
assert_eq!(
svg_element_interface_name("feSpecularLighting"),
"SVGFESpecularLightingElement"
);
assert_eq!(
svg_element_interface_name("feSpotLight"),
"SVGFESpotLightElement"
);
assert_eq!(
svg_element_interface_name("feTurbulence"),
"SVGFETurbulenceElement"
@@ -314,6 +343,7 @@ fn svg_element_interface_name_specializes_standard_elements() {
);
assert_eq!(svg_element_interface_name("rect"), "SVGRectElement");
assert_eq!(svg_element_interface_name("script"), "SVGScriptElement");
assert_eq!(svg_element_interface_name("stop"), "SVGStopElement");
assert_eq!(svg_element_interface_name("svg"), "SVGSVGElement");
assert_eq!(svg_element_interface_name("symbol"), "SVGSymbolElement");
assert_eq!(svg_element_interface_name("text"), "SVGTextElement");
@@ -161,11 +161,31 @@ const NODE_BRIDGE_DESCRIPTORS: &[BridgeDescriptor] = &[
web_api_interfaces::SVGFEConvolveMatrixElement::DESCRIPTOR,
ELEMENT_GROUPS,
),
descriptor(
web_api_interfaces::SVGFEDiffuseLightingElement::DESCRIPTOR,
ELEMENT_GROUPS,
),
descriptor(
web_api_interfaces::SVGFEDisplacementMapElement::DESCRIPTOR,
ELEMENT_GROUPS,
),
descriptor(
web_api_interfaces::SVGFEDistantLightElement::DESCRIPTOR,
ELEMENT_GROUPS,
),
descriptor(web_api_interfaces::SVGFEDropShadowElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(
web_api_interfaces::SVGFEGaussianBlurElement::DESCRIPTOR,
ELEMENT_GROUPS,
),
descriptor(web_api_interfaces::SVGFEMorphologyElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(web_api_interfaces::SVGFEOffsetElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(web_api_interfaces::SVGFEPointLightElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(
web_api_interfaces::SVGFESpecularLightingElement::DESCRIPTOR,
ELEMENT_GROUPS,
),
descriptor(web_api_interfaces::SVGFESpotLightElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(web_api_interfaces::SVGFETurbulenceElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(
web_api_interfaces::SVGForeignObjectElement::DESCRIPTOR,
@@ -198,20 +218,12 @@ const NODE_BRIDGE_DESCRIPTORS: &[BridgeDescriptor] = &[
svg_gradient_transform: true,
},
),
descriptor(
web_api_interfaces::SVGMetadataElement::DESCRIPTOR,
ELEMENT_GROUPS,
),
descriptor(
web_api_interfaces::SVGScriptElement::DESCRIPTOR,
ELEMENT_GROUPS,
),
descriptor(
web_api_interfaces::SVGStyleElement::DESCRIPTOR,
ELEMENT_GROUPS,
),
descriptor(web_api_interfaces::SVGMaskElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(web_api_interfaces::SVGMarkerElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(web_api_interfaces::SVGMaskElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(web_api_interfaces::SVGMetadataElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(web_api_interfaces::SVGScriptElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(web_api_interfaces::SVGStopElement::DESCRIPTOR, ELEMENT_GROUPS),
descriptor(web_api_interfaces::SVGStyleElement::DESCRIPTOR, ELEMENT_GROUPS),
specialized_descriptor(
web_api_interfaces::SVGPathElement::DESCRIPTOR,
ELEMENT_GROUPS,
@@ -1301,6 +1301,152 @@ struct SvgMatrixTemplateAccessorsDeclaration {
f: (),
}
macro_rules! define_svg_animated_number_accessors {
(
$declaration:ident,
$interface:literal,
$(($field:ident, $name:literal, $index:literal)),+ $(,)?
) => {
#[derive(WebApiFunctionTemplate)]
#[webapi(name = $interface, enumerable)]
struct $declaration {
$(
#[webapi(
accessor_property = $name,
getter = svg_element_animated_number_getter,
data = callback_data_index_value(scope, $index)
)]
$field: (),
)+
}
};
}
define_svg_animated_number_accessors!(
SvgComponentTransferFunctionAnimatedNumberAccessorsDeclaration,
"SVGComponentTransferFunctionElement",
(slope, "slope", 0),
(intercept, "intercept", 1),
(amplitude, "amplitude", 2),
(exponent, "exponent", 3),
(offset, "offset", 4),
);
define_svg_animated_number_accessors!(
SvgFeCompositeAnimatedNumberAccessorsDeclaration,
"SVGFECompositeElement",
(k1, "k1", 5),
(k2, "k2", 6),
(k3, "k3", 7),
(k4, "k4", 8),
);
define_svg_animated_number_accessors!(
SvgFeConvolveMatrixAnimatedNumberAccessorsDeclaration,
"SVGFEConvolveMatrixElement",
(divisor, "divisor", 9),
(bias, "bias", 10),
(kernel_unit_length_x, "kernelUnitLengthX", 11),
(kernel_unit_length_y, "kernelUnitLengthY", 12),
);
define_svg_animated_number_accessors!(
SvgFeDiffuseLightingAnimatedNumberAccessorsDeclaration,
"SVGFEDiffuseLightingElement",
(surface_scale, "surfaceScale", 13),
(diffuse_constant, "diffuseConstant", 14),
(kernel_unit_length_x, "kernelUnitLengthX", 15),
(kernel_unit_length_y, "kernelUnitLengthY", 16),
);
define_svg_animated_number_accessors!(
SvgFeDisplacementMapAnimatedNumberAccessorsDeclaration,
"SVGFEDisplacementMapElement",
(scale, "scale", 17),
);
define_svg_animated_number_accessors!(
SvgFeDistantLightAnimatedNumberAccessorsDeclaration,
"SVGFEDistantLightElement",
(azimuth, "azimuth", 18),
(elevation, "elevation", 19),
);
define_svg_animated_number_accessors!(
SvgFeDropShadowAnimatedNumberAccessorsDeclaration,
"SVGFEDropShadowElement",
(dx, "dx", 20),
(dy, "dy", 21),
(std_deviation_x, "stdDeviationX", 22),
(std_deviation_y, "stdDeviationY", 23),
);
define_svg_animated_number_accessors!(
SvgFeGaussianBlurAnimatedNumberAccessorsDeclaration,
"SVGFEGaussianBlurElement",
(std_deviation_x, "stdDeviationX", 24),
(std_deviation_y, "stdDeviationY", 25),
);
define_svg_animated_number_accessors!(
SvgFeMorphologyAnimatedNumberAccessorsDeclaration,
"SVGFEMorphologyElement",
(radius_x, "radiusX", 26),
(radius_y, "radiusY", 27),
);
define_svg_animated_number_accessors!(
SvgFeOffsetAnimatedNumberAccessorsDeclaration,
"SVGFEOffsetElement",
(dx, "dx", 28),
(dy, "dy", 29),
);
define_svg_animated_number_accessors!(
SvgFePointLightAnimatedNumberAccessorsDeclaration,
"SVGFEPointLightElement",
(x, "x", 30),
(y, "y", 31),
(z, "z", 32),
);
define_svg_animated_number_accessors!(
SvgFeSpecularLightingAnimatedNumberAccessorsDeclaration,
"SVGFESpecularLightingElement",
(surface_scale, "surfaceScale", 33),
(specular_constant, "specularConstant", 34),
(specular_exponent, "specularExponent", 35),
(kernel_unit_length_x, "kernelUnitLengthX", 36),
(kernel_unit_length_y, "kernelUnitLengthY", 37),
);
define_svg_animated_number_accessors!(
SvgFeSpotLightAnimatedNumberAccessorsDeclaration,
"SVGFESpotLightElement",
(x, "x", 38),
(y, "y", 39),
(z, "z", 40),
(points_at_x, "pointsAtX", 41),
(points_at_y, "pointsAtY", 42),
(points_at_z, "pointsAtZ", 43),
(specular_exponent, "specularExponent", 44),
(limiting_cone_angle, "limitingConeAngle", 45),
);
define_svg_animated_number_accessors!(
SvgFeTurbulenceAnimatedNumberAccessorsDeclaration,
"SVGFETurbulenceElement",
(base_frequency_x, "baseFrequencyX", 46),
(base_frequency_y, "baseFrequencyY", 47),
(seed, "seed", 48),
);
define_svg_animated_number_accessors!(
SvgStopAnimatedNumberAccessorsDeclaration,
"SVGStopElement",
(offset, "offset", 49),
);
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGElement, enumerable, receiver)]
struct SvgElementPrototypeAccessorsDeclaration {
@@ -2220,12 +2366,73 @@ pub(super) fn install_svg_svg_element_bindings(
SvgSvgElementTemplateMethodsDeclaration::initialize_prototype_template(scope, proto);
}
fn install_svg_animated_number_element_bindings<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
prototype: v8::Local<'s, v8::ObjectTemplate>,
interface_name: &str,
) {
macro_rules! install_accessors {
($declaration:ty) => {
<$declaration>::initialize_prototype_template(scope, prototype)
};
}
match interface_name {
"SVGComponentTransferFunctionElement" => {
install_accessors!(SvgComponentTransferFunctionAnimatedNumberAccessorsDeclaration);
}
"SVGFECompositeElement" => {
install_accessors!(SvgFeCompositeAnimatedNumberAccessorsDeclaration);
}
"SVGFEConvolveMatrixElement" => {
install_accessors!(SvgFeConvolveMatrixAnimatedNumberAccessorsDeclaration);
}
"SVGFEDiffuseLightingElement" => {
install_accessors!(SvgFeDiffuseLightingAnimatedNumberAccessorsDeclaration);
}
"SVGFEDisplacementMapElement" => {
install_accessors!(SvgFeDisplacementMapAnimatedNumberAccessorsDeclaration);
}
"SVGFEDistantLightElement" => {
install_accessors!(SvgFeDistantLightAnimatedNumberAccessorsDeclaration);
}
"SVGFEDropShadowElement" => {
install_accessors!(SvgFeDropShadowAnimatedNumberAccessorsDeclaration);
}
"SVGFEGaussianBlurElement" => {
install_accessors!(SvgFeGaussianBlurAnimatedNumberAccessorsDeclaration);
}
"SVGFEMorphologyElement" => {
install_accessors!(SvgFeMorphologyAnimatedNumberAccessorsDeclaration);
}
"SVGFEOffsetElement" => {
install_accessors!(SvgFeOffsetAnimatedNumberAccessorsDeclaration);
}
"SVGFEPointLightElement" => {
install_accessors!(SvgFePointLightAnimatedNumberAccessorsDeclaration);
}
"SVGFESpecularLightingElement" => {
install_accessors!(SvgFeSpecularLightingAnimatedNumberAccessorsDeclaration);
}
"SVGFESpotLightElement" => {
install_accessors!(SvgFeSpotLightAnimatedNumberAccessorsDeclaration);
}
"SVGFETurbulenceElement" => {
install_accessors!(SvgFeTurbulenceAnimatedNumberAccessorsDeclaration);
}
"SVGStopElement" => {
install_accessors!(SvgStopAnimatedNumberAccessorsDeclaration);
}
_ => {}
}
}
pub(super) fn install_svg_element_accessor_bindings<'s>(
scope: &mut v8::PinScope<'s, '_, ()>,
template: v8::Local<'s, v8::FunctionTemplate>,
interface_name: &str,
) {
let prototype = template.prototype_template(scope);
install_svg_animated_number_element_bindings(scope, prototype, interface_name);
if matches!(
interface_name,
"SVGSVGElement"
@@ -2244,8 +2451,13 @@ pub(super) fn install_svg_element_accessor_bindings<'s>(
| "SVGFEColorMatrixElement"
| "SVGFECompositeElement"
| "SVGFEConvolveMatrixElement"
| "SVGFEDiffuseLightingElement"
| "SVGFEDisplacementMapElement"
| "SVGFEDropShadowElement"
| "SVGFEGaussianBlurElement"
| "SVGFEMorphologyElement"
| "SVGFEOffsetElement"
| "SVGFESpecularLightingElement"
| "SVGFETurbulenceElement"
) {
SvgFilterPrimitiveStandardAttributesPrototypeAccessorsDeclaration::initialize_prototype_template(
@@ -572,6 +572,38 @@ pub(super) fn build_svg_animated_number_for_attribute<'s>(
object
}
pub(super) fn build_svg_animated_number_for_property<'s>(
scope: &mut v8::PinScope<'s, '_>,
owner: v8::Local<'s, v8::Object>,
property: SvgAnimatedNumberProperty,
) -> v8::Local<'s, v8::Object> {
let object = build_svg_animated_number(scope, property.initial_value);
set_svg_animated_number_owner_attribute(scope, object, owner, property.attribute);
let index = u32::try_from(property.index).expect("SVG number property index exceeds u32");
let index = v8::Integer::new_from_unsigned(scope, index);
set_private_value(
scope,
object,
SVG_ANIMATED_NUMBER_PROPERTY_INDEX_SLOT,
index.into(),
);
sync_svg_animated_number_from_property(scope, object);
object
}
pub(super) fn svg_animated_number_property_for_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
animated: v8::Local<'s, v8::Object>,
) -> Option<SvgAnimatedNumberProperty> {
let index = get_private_value(scope, animated, SVG_ANIMATED_NUMBER_PROPERTY_INDEX_SLOT)?
.uint32_value(scope)?;
SVG_ANIMATED_NUMBER_PROPERTIES.get(index as usize).copied()
}
pub(super) fn svg_animated_number_cache_slot(property: SvgAnimatedNumberProperty) -> String {
format!("__moliSvgAnimatedNumberProperty{}", property.index)
}
pub(super) fn build_svg_animated_integer<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: i32,
@@ -3264,6 +3296,57 @@ pub(super) fn sync_svg_animated_number_from_owner_attribute<'s>(
.as_deref()
.and_then(parse_svg_number_value)
.unwrap_or(0.0);
set_svg_animated_number_values(scope, animated, value);
}
pub(super) fn sync_svg_animated_number_from_stored_owner_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
animated: v8::Local<'s, v8::Object>,
) {
if svg_animated_number_property_for_object(scope, animated).is_some() {
sync_svg_animated_number_from_property(scope, animated);
return;
}
let Some(owner) = get_private_value(scope, animated, SVG_ANIMATED_NUMBER_OWNER_ELEMENT_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
else {
return;
};
let Some(attribute) =
get_private_value(scope, animated, SVG_ANIMATED_NUMBER_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;
};
sync_svg_animated_number_from_owner_attribute(scope, animated, owner, &attribute);
}
pub(super) fn sync_svg_animated_number_from_property<'s>(
scope: &mut v8::PinScope<'s, '_>,
animated: v8::Local<'s, v8::Object>,
) {
let Some(property) = svg_animated_number_property_for_object(scope, animated) else {
return;
};
let Some(owner) = get_private_value(scope, animated, SVG_ANIMATED_NUMBER_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_number(property, raw))
.unwrap_or(property.initial_value);
set_svg_animated_number_values(scope, animated, value);
}
pub(super) fn set_svg_animated_number_values(
scope: &mut v8::PinScope<'_, '_>,
animated: v8::Local<'_, v8::Object>,
value: f64,
) {
let value = v8::Number::new(scope, value);
set_private_value(
scope,
@@ -3283,6 +3366,10 @@ pub(super) fn reflect_svg_animated_number_to_owner_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
animated: v8::Local<'s, v8::Object>,
) {
if svg_animated_number_property_for_object(scope, animated).is_some() {
reflect_svg_animated_number_property_to_owner_attribute(scope, animated);
return;
}
let Some(owner) = get_private_value(scope, animated, SVG_ANIMATED_NUMBER_OWNER_ELEMENT_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
else {
@@ -3307,6 +3394,85 @@ pub(super) fn reflect_svg_animated_number_to_owner_attribute<'s>(
let _ = runtime.set_attribute(scope, runtime_ptr, handle, &attribute, &value);
}
pub(super) fn reflect_svg_animated_number_property_to_owner_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
animated: v8::Local<'s, v8::Object>,
) {
let Some(property) = svg_animated_number_property_for_object(scope, animated) else {
return;
};
let Some(owner) = get_private_value(scope, animated, SVG_ANIMATED_NUMBER_OWNER_ELEMENT_SLOT)
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
else {
return;
};
let value = svg_number_slot(scope, animated, SVG_ANIMATED_NUMBER_BASE_VAL_SLOT)
.unwrap_or(property.initial_value);
let serialized = match property.component {
SvgAnimatedNumberComponent::Scalar | SvgAnimatedNumberComponent::NumberOrPercentage => {
svg_geometry::serialize_number(value)
}
SvgAnimatedNumberComponent::PairFirst | SvgAnimatedNumberComponent::PairSecondOrFirst => {
let (mut first, mut second) =
svg_owner_attribute_value(scope, owner, property.attribute)
.as_deref()
.and_then(parse_svg_number_pair)
.unwrap_or((property.initial_value, property.initial_value));
match property.component {
SvgAnimatedNumberComponent::PairFirst => first = value,
SvgAnimatedNumberComponent::PairSecondOrFirst => second = value,
SvgAnimatedNumberComponent::Scalar
| SvgAnimatedNumberComponent::NumberOrPercentage => unreachable!(),
}
format!(
"{} {}",
svg_geometry::serialize_number(first),
svg_geometry::serialize_number(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 parse_svg_animated_number(
property: SvgAnimatedNumberProperty,
value: &str,
) -> Option<f64> {
match property.component {
SvgAnimatedNumberComponent::Scalar => parse_svg_number_value(value),
SvgAnimatedNumberComponent::PairFirst => {
parse_svg_number_pair(value).map(|(first, _)| first)
}
SvgAnimatedNumberComponent::PairSecondOrFirst => {
parse_svg_number_pair(value).map(|(_, second)| second)
}
SvgAnimatedNumberComponent::NumberOrPercentage => parse_svg_number_or_percentage(value),
}
}
fn parse_svg_number_pair(value: &str) -> Option<(f64, f64)> {
let values = svg_geometry::parse_number_list(value)?;
match values.as_slice() {
[first] => Some((*first, *first)),
[first, second] => Some((*first, *second)),
_ => None,
}
}
fn parse_svg_number_or_percentage(value: &str) -> Option<f64> {
let value = value.trim();
match value.strip_suffix('%') {
Some(percentage) => parse_svg_number_value(percentage).map(|value| value / 100.0),
None => parse_svg_number_value(value),
}
}
pub(super) fn set_svg_length_owner_attribute<'s>(
scope: &mut v8::PinScope<'s, '_>,
length: v8::Local<'s, v8::Object>,
@@ -837,13 +837,22 @@ fn require_svg_graphics_element_receiver<'s>(
scope: &mut v8::PinScope<'s, '_>,
receiver: v8::Local<'s, v8::Object>,
member: &str,
) -> bool {
require_svg_interface_receiver(scope, receiver, "SVGGraphicsElement", member)
}
fn require_svg_interface_receiver<'s>(
scope: &mut v8::PinScope<'s, '_>,
receiver: v8::Local<'s, v8::Object>,
interface: &str,
member: &str,
) -> bool {
let Ok((runtime_ptr, handle)) =
crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, receiver)
else {
webidl::throw_type_error(
scope,
&format!("SVGGraphicsElement.{member} called on incompatible receiver."),
&format!("{interface}.{member} called on incompatible receiver."),
);
return false;
};
@@ -856,12 +865,12 @@ fn require_svg_graphics_element_receiver<'s>(
else {
webidl::throw_type_error(
scope,
&format!("SVGGraphicsElement.{member} called on incompatible receiver."),
&format!("{interface}.{member} called on incompatible receiver."),
);
return false;
};
loop {
if interface_name == "SVGGraphicsElement" {
if interface_name == interface {
return true;
}
let Some(parent) =
@@ -874,7 +883,7 @@ fn require_svg_graphics_element_receiver<'s>(
}
webidl::throw_type_error(
scope,
&format!("SVGGraphicsElement.{member} called on incompatible receiver."),
&format!("{interface}.{member} called on incompatible receiver."),
);
false
}
@@ -980,6 +989,37 @@ pub(super) fn svg_element_animated_enumeration_getter<'s>(
rv.set(value.into());
}
pub(super) fn svg_element_animated_number_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_NUMBER_PROPERTIES,
"SVG animated number properties",
) else {
rv.set_undefined();
return;
};
let holder = args.this();
if !require_svg_interface_receiver(scope, holder, property.interface, property.name) {
return;
}
let cache_slot = svg_animated_number_cache_slot(property);
if let Some(value) = get_private_value(scope, holder, &cache_slot) {
if let Ok(animated) = v8::Local::<v8::Object>::try_from(value) {
sync_svg_animated_number_from_property(scope, animated);
}
rv.set(value);
return;
}
let value = build_svg_animated_number_for_property(scope, holder, property);
set_private_value(scope, holder, &cache_slot, value.into());
rv.set(value.into());
}
pub(super) fn svg_element_animated_integer_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
@@ -1785,7 +1825,11 @@ pub(super) fn svg_animated_number_getter<'s>(
return;
}
};
let value = svg_number_slot(scope, args.this(), slot).unwrap_or(0.0);
sync_svg_animated_number_from_stored_owner_attribute(scope, args.this());
let initial_value = svg_animated_number_property_for_object(scope, args.this())
.map(|property| property.initial_value)
.unwrap_or_default();
let value = svg_number_slot(scope, args.this(), slot).unwrap_or(initial_value);
rv.set(v8::Number::new(scope, value).into());
}
@@ -1974,7 +2018,7 @@ pub(super) fn svg_animated_number_setter<'s>(
{
return;
}
let value = match webidl::convert::<webidl::UnrestrictedDouble>(
let value = match webidl::convert::<webidl::Double>(
scope,
args.get(0),
webidl::Context::member("SVGAnimatedNumber", "baseVal"),
@@ -1985,18 +2029,7 @@ pub(super) fn svg_animated_number_setter<'s>(
return;
}
};
set_private_value(
scope,
args.this(),
SVG_ANIMATED_NUMBER_BASE_VAL_SLOT,
v8::Number::new(scope, value).into(),
);
set_private_value(
scope,
args.this(),
SVG_ANIMATED_NUMBER_ANIM_VAL_SLOT,
v8::Number::new(scope, value).into(),
);
set_svg_animated_number_values(scope, args.this(), value);
reflect_svg_animated_number_to_owner_attribute(scope, args.this());
}
@@ -75,6 +75,7 @@ const SVG_LENGTH_VALUE_AS_STRING_SLOT: &str = "__moliSvgLengthValueAsString";
const SVG_NUMBER_VALUE_SLOT: &str = "__moliSvgNumberValue";
const SVG_ANIMATED_NUMBER_OWNER_ELEMENT_SLOT: &str = "__moliSvgAnimatedNumberOwnerElement";
const SVG_ANIMATED_NUMBER_OWNER_ATTRIBUTE_SLOT: &str = "__moliSvgAnimatedNumberOwnerAttribute";
const SVG_ANIMATED_NUMBER_PROPERTY_INDEX_SLOT: &str = "__moliSvgAnimatedNumberPropertyIndex";
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";
@@ -198,6 +199,24 @@ struct SvgAnimatedIntegerProperty {
component: SvgAnimatedIntegerComponent,
}
#[derive(Clone, Copy)]
enum SvgAnimatedNumberComponent {
Scalar,
PairFirst,
PairSecondOrFirst,
NumberOrPercentage,
}
#[derive(Clone, Copy)]
struct SvgAnimatedNumberProperty {
index: usize,
interface: &'static str,
name: &'static str,
attribute: &'static str,
initial_value: f64,
component: SvgAnimatedNumberComponent,
}
#[derive(webidl::WebIdlArgs)]
#[webidl(prefix = "SVG list item")]
struct SvgListItemArgs<'s> {
@@ -766,6 +785,427 @@ const SVG_ANIMATED_ENUMERATION_PROPERTIES: &[SvgAnimatedEnumerationProperty] = &
},
];
const fn svg_animated_number_property(
index: usize,
interface: &'static str,
name: &'static str,
attribute: &'static str,
initial_value: f64,
component: SvgAnimatedNumberComponent,
) -> SvgAnimatedNumberProperty {
SvgAnimatedNumberProperty {
index,
interface,
name,
attribute,
initial_value,
component,
}
}
const SVG_ANIMATED_NUMBER_PROPERTIES: &[SvgAnimatedNumberProperty] = &[
svg_animated_number_property(
0,
"SVGComponentTransferFunctionElement",
"slope",
"slope",
1.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
1,
"SVGComponentTransferFunctionElement",
"intercept",
"intercept",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
2,
"SVGComponentTransferFunctionElement",
"amplitude",
"amplitude",
1.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
3,
"SVGComponentTransferFunctionElement",
"exponent",
"exponent",
1.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
4,
"SVGComponentTransferFunctionElement",
"offset",
"offset",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
5,
"SVGFECompositeElement",
"k1",
"k1",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
6,
"SVGFECompositeElement",
"k2",
"k2",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
7,
"SVGFECompositeElement",
"k3",
"k3",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
8,
"SVGFECompositeElement",
"k4",
"k4",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
9,
"SVGFEConvolveMatrixElement",
"divisor",
"divisor",
1.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
10,
"SVGFEConvolveMatrixElement",
"bias",
"bias",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
11,
"SVGFEConvolveMatrixElement",
"kernelUnitLengthX",
"kernelUnitLength",
0.0,
SvgAnimatedNumberComponent::PairFirst,
),
svg_animated_number_property(
12,
"SVGFEConvolveMatrixElement",
"kernelUnitLengthY",
"kernelUnitLength",
0.0,
SvgAnimatedNumberComponent::PairSecondOrFirst,
),
svg_animated_number_property(
13,
"SVGFEDiffuseLightingElement",
"surfaceScale",
"surfaceScale",
1.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
14,
"SVGFEDiffuseLightingElement",
"diffuseConstant",
"diffuseConstant",
1.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
15,
"SVGFEDiffuseLightingElement",
"kernelUnitLengthX",
"kernelUnitLength",
0.0,
SvgAnimatedNumberComponent::PairFirst,
),
svg_animated_number_property(
16,
"SVGFEDiffuseLightingElement",
"kernelUnitLengthY",
"kernelUnitLength",
0.0,
SvgAnimatedNumberComponent::PairSecondOrFirst,
),
svg_animated_number_property(
17,
"SVGFEDisplacementMapElement",
"scale",
"scale",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
18,
"SVGFEDistantLightElement",
"azimuth",
"azimuth",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
19,
"SVGFEDistantLightElement",
"elevation",
"elevation",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
20,
"SVGFEDropShadowElement",
"dx",
"dx",
2.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
21,
"SVGFEDropShadowElement",
"dy",
"dy",
2.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
22,
"SVGFEDropShadowElement",
"stdDeviationX",
"stdDeviation",
2.0,
SvgAnimatedNumberComponent::PairFirst,
),
svg_animated_number_property(
23,
"SVGFEDropShadowElement",
"stdDeviationY",
"stdDeviation",
2.0,
SvgAnimatedNumberComponent::PairSecondOrFirst,
),
svg_animated_number_property(
24,
"SVGFEGaussianBlurElement",
"stdDeviationX",
"stdDeviation",
0.0,
SvgAnimatedNumberComponent::PairFirst,
),
svg_animated_number_property(
25,
"SVGFEGaussianBlurElement",
"stdDeviationY",
"stdDeviation",
0.0,
SvgAnimatedNumberComponent::PairSecondOrFirst,
),
svg_animated_number_property(
26,
"SVGFEMorphologyElement",
"radiusX",
"radius",
0.0,
SvgAnimatedNumberComponent::PairFirst,
),
svg_animated_number_property(
27,
"SVGFEMorphologyElement",
"radiusY",
"radius",
0.0,
SvgAnimatedNumberComponent::PairSecondOrFirst,
),
svg_animated_number_property(
28,
"SVGFEOffsetElement",
"dx",
"dx",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
29,
"SVGFEOffsetElement",
"dy",
"dy",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
30,
"SVGFEPointLightElement",
"x",
"x",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
31,
"SVGFEPointLightElement",
"y",
"y",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
32,
"SVGFEPointLightElement",
"z",
"z",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
33,
"SVGFESpecularLightingElement",
"surfaceScale",
"surfaceScale",
1.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
34,
"SVGFESpecularLightingElement",
"specularConstant",
"specularConstant",
1.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
35,
"SVGFESpecularLightingElement",
"specularExponent",
"specularExponent",
1.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
36,
"SVGFESpecularLightingElement",
"kernelUnitLengthX",
"kernelUnitLength",
0.0,
SvgAnimatedNumberComponent::PairFirst,
),
svg_animated_number_property(
37,
"SVGFESpecularLightingElement",
"kernelUnitLengthY",
"kernelUnitLength",
0.0,
SvgAnimatedNumberComponent::PairSecondOrFirst,
),
svg_animated_number_property(
38,
"SVGFESpotLightElement",
"x",
"x",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
39,
"SVGFESpotLightElement",
"y",
"y",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
40,
"SVGFESpotLightElement",
"z",
"z",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
41,
"SVGFESpotLightElement",
"pointsAtX",
"pointsAtX",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
42,
"SVGFESpotLightElement",
"pointsAtY",
"pointsAtY",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
43,
"SVGFESpotLightElement",
"pointsAtZ",
"pointsAtZ",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
44,
"SVGFESpotLightElement",
"specularExponent",
"specularExponent",
1.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
45,
"SVGFESpotLightElement",
"limitingConeAngle",
"limitingConeAngle",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
46,
"SVGFETurbulenceElement",
"baseFrequencyX",
"baseFrequency",
0.0,
SvgAnimatedNumberComponent::PairFirst,
),
svg_animated_number_property(
47,
"SVGFETurbulenceElement",
"baseFrequencyY",
"baseFrequency",
0.0,
SvgAnimatedNumberComponent::PairSecondOrFirst,
),
svg_animated_number_property(
48,
"SVGFETurbulenceElement",
"seed",
"seed",
0.0,
SvgAnimatedNumberComponent::Scalar,
),
svg_animated_number_property(
49,
"SVGStopElement",
"offset",
"offset",
0.0,
SvgAnimatedNumberComponent::NumberOrPercentage,
),
];
const SVG_ANIMATED_INTEGER_PROPERTIES: &[SvgAnimatedIntegerProperty] = &[
SvgAnimatedIntegerProperty {
index: 0,
@@ -1144,6 +1144,211 @@ fn svg_animated_integers_reflect_filter_content_attributes() {
assert_eq!(result, "ok");
}
#[test]
fn svg_animated_numbers_reflect_filter_and_stop_content_attributes() {
let mut vm = new_parsed_test_vm(
"https://svg-animated-number.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 cases = [
["feFuncA", SVGFEFuncAElement, SVGComponentTransferFunctionElement, [
["slope", "slope", 1], ["intercept", "intercept", 0],
["amplitude", "amplitude", 1], ["exponent", "exponent", 1],
["offset", "offset", 0],
]],
["feComposite", SVGFECompositeElement, SVGFECompositeElement, [
["k1", "k1", 0], ["k2", "k2", 0],
["k3", "k3", 0], ["k4", "k4", 0],
]],
["feConvolveMatrix", SVGFEConvolveMatrixElement,
SVGFEConvolveMatrixElement, [
["divisor", "divisor", 1], ["bias", "bias", 0],
["kernelUnitLengthX", "kernelUnitLength", 0],
["kernelUnitLengthY", "kernelUnitLength", 0],
]],
["feDiffuseLighting", SVGFEDiffuseLightingElement,
SVGFEDiffuseLightingElement, [
["surfaceScale", "surfaceScale", 1],
["diffuseConstant", "diffuseConstant", 1],
["kernelUnitLengthX", "kernelUnitLength", 0],
["kernelUnitLengthY", "kernelUnitLength", 0],
]],
["feDisplacementMap", SVGFEDisplacementMapElement,
SVGFEDisplacementMapElement, [["scale", "scale", 0]]],
["feDistantLight", SVGFEDistantLightElement,
SVGFEDistantLightElement, [
["azimuth", "azimuth", 0], ["elevation", "elevation", 0],
]],
["feDropShadow", SVGFEDropShadowElement, SVGFEDropShadowElement, [
["dx", "dx", 2], ["dy", "dy", 2],
["stdDeviationX", "stdDeviation", 2],
["stdDeviationY", "stdDeviation", 2],
]],
["feGaussianBlur", SVGFEGaussianBlurElement,
SVGFEGaussianBlurElement, [
["stdDeviationX", "stdDeviation", 0],
["stdDeviationY", "stdDeviation", 0],
]],
["feMorphology", SVGFEMorphologyElement, SVGFEMorphologyElement, [
["radiusX", "radius", 0], ["radiusY", "radius", 0],
]],
["feOffset", SVGFEOffsetElement, SVGFEOffsetElement, [
["dx", "dx", 0], ["dy", "dy", 0],
]],
["fePointLight", SVGFEPointLightElement, SVGFEPointLightElement, [
["x", "x", 0], ["y", "y", 0], ["z", "z", 0],
]],
["feSpecularLighting", SVGFESpecularLightingElement,
SVGFESpecularLightingElement, [
["surfaceScale", "surfaceScale", 1],
["specularConstant", "specularConstant", 1],
["specularExponent", "specularExponent", 1],
["kernelUnitLengthX", "kernelUnitLength", 0],
["kernelUnitLengthY", "kernelUnitLength", 0],
]],
["feSpotLight", SVGFESpotLightElement, SVGFESpotLightElement, [
["x", "x", 0], ["y", "y", 0], ["z", "z", 0],
["pointsAtX", "pointsAtX", 0], ["pointsAtY", "pointsAtY", 0],
["pointsAtZ", "pointsAtZ", 0],
["specularExponent", "specularExponent", 1],
["limitingConeAngle", "limitingConeAngle", 0],
]],
["feTurbulence", SVGFETurbulenceElement, SVGFETurbulenceElement, [
["baseFrequencyX", "baseFrequency", 0],
["baseFrequencyY", "baseFrequency", 0], ["seed", "seed", 0],
]],
["stop", SVGStopElement, SVGStopElement, [["offset", "offset", 0]]],
];
assert(typeof SVGAnimatedNumber === "function", "constructor exposed");
let illegalConstructor = false;
try {
new SVGAnimatedNumber();
} catch (error) {
illegalConstructor = error instanceof TypeError;
}
assert(illegalConstructor, "illegal constructor");
const baseDescriptor = Object.getOwnPropertyDescriptor(
SVGAnimatedNumber.prototype,
"baseVal",
);
const animDescriptor = Object.getOwnPropertyDescriptor(
SVGAnimatedNumber.prototype,
"animVal",
);
assert(typeof baseDescriptor.get === "function" &&
typeof baseDescriptor.set === "function", "baseVal descriptor");
assert(typeof animDescriptor.get === "function" && animDescriptor.set === undefined,
"animVal descriptor");
for (const [tag, constructor, owner, properties] of cases) {
const element = document.createElementNS(ns, tag);
assert(element instanceof constructor, `${tag} interface`);
for (const [property, attribute, initial] of properties) {
const descriptor = Object.getOwnPropertyDescriptor(owner.prototype, property);
assert(typeof descriptor.get === "function" && descriptor.set === undefined,
`${owner.name}.${property} descriptor`);
assert(descriptor.enumerable && descriptor.configurable,
`${owner.name}.${property} descriptor flags`);
const animated = element[property];
assert(animated instanceof SVGAnimatedNumber, `${tag}.${property} interface`);
assert(element[property] === animated, `${tag}.${property} SameObject`);
assert(animated.baseVal === initial && animated.animVal === initial,
`${tag}.${property} initial value`);
element.setAttribute(attribute, "42");
assert(animated.baseVal === 42 && animated.animVal === 42,
`${tag}.${property} live content update`);
element.setAttribute(attribute, "foobar");
assert(animated.baseVal === initial && animated.animVal === initial,
`${tag}.${property} invalid fallback`);
element.removeAttribute(attribute);
assert(animated.baseVal === initial && animated.animVal === initial,
`${tag}.${property} removed fallback`);
}
}
const gaussian = document.createElementNS(ns, "feGaussianBlur");
const deviationX = gaussian.stdDeviationX;
const deviationY = gaussian.stdDeviationY;
gaussian.setAttribute("stdDeviation", "5");
assert(deviationX.baseVal === 5 && deviationY.baseVal === 5,
"single optional-number value");
gaussian.setAttribute("stdDeviation", "5, 7");
assert(deviationX.baseVal === 5 && deviationY.baseVal === 7,
"paired optional-number values");
deviationX.baseVal = 3;
assert(gaussian.getAttribute("stdDeviation") === "3 7",
"first pair component reflection");
deviationY.baseVal = 9;
assert(gaussian.getAttribute("stdDeviation") === "3 9" &&
deviationX.baseVal === 3 && deviationY.animVal === 9,
"second pair component reflection");
const specular = document.createElementNS(ns, "feSpecularLighting");
const surfaceScale = specular.surfaceScale;
surfaceScale.baseVal = -1;
assert(surfaceScale.baseVal === -1 && surfaceScale.animVal === -1 &&
specular.getAttribute("surfaceScale") === "-1", "scalar reflection");
surfaceScale.baseVal = 300;
for (const invalid of ["aString", NaN, Infinity, specular]) {
let rejected = false;
try {
surfaceScale.baseVal = invalid;
} catch (error) {
rejected = error instanceof TypeError;
}
assert(rejected && surfaceScale.baseVal === 300,
"restricted float rejects invalid values");
}
const stop = document.createElementNS(ns, "stop");
const stopOffset = stop.offset;
stop.setAttribute("offset", "50%");
assert(stopOffset.baseVal === 0.5 && stopOffset.animVal === 0.5,
"stop percentage content value");
stopOffset.baseVal = 0.25;
assert(stop.getAttribute("offset") === "0.25", "stop number reflection");
let incompatibleAnimatedReceiver = false;
try {
baseDescriptor.get.call({});
} catch (error) {
incompatibleAnimatedReceiver = error instanceof TypeError;
}
assert(incompatibleAnimatedReceiver, "animated number receiver brand");
const k1Descriptor = Object.getOwnPropertyDescriptor(
SVGFECompositeElement.prototype,
"k1",
);
let incompatibleElementReceiver = false;
try {
k1Descriptor.get.call(specular);
} catch (error) {
incompatibleElementReceiver = error instanceof TypeError;
}
assert(incompatibleElementReceiver, "element receiver brand");
return "ok";
})()
"#,
)
.expect("SVG animated number probe should evaluate");
assert_eq!(result, "ok");
}
#[test]
fn svg_animated_boolean_reflects_preserve_alpha() {
let mut vm = new_parsed_test_vm(
@@ -401,6 +401,15 @@ interfaces! {
SVGFEMorphologyElement: SVGElement;
SVGFETurbulenceElement: SVGElement;
SVGTextPathElement: SVGTextContentElement;
SVGFEDiffuseLightingElement: SVGElement;
SVGFEDistantLightElement: SVGElement;
SVGFEDropShadowElement: SVGElement;
SVGFEGaussianBlurElement: SVGElement;
SVGFEOffsetElement: SVGElement;
SVGFEPointLightElement: SVGElement;
SVGFESpecularLightingElement: SVGElement;
SVGFESpotLightElement: SVGElement;
SVGStopElement: SVGElement;
SVGElement: Element;
SVGEllipseElement: SVGGeometryElement;
SVGForeignObjectElement: SVGGraphicsElement;