From 1df5279884bc81440ad66b60ca5fa17eb4d2de5d Mon Sep 17 00:00:00 2001 From: ldm0 Date: Tue, 1 Sep 2026 10:05:27 +0800 Subject: [PATCH] fix(svg): reflect animated length attributes --- .../context_bootstrap/svg_runtime/bindings.rs | 161 ++++++++++++- .../context_bootstrap/svg_runtime/builders.rs | 45 ++-- .../svg_runtime/callbacks.rs | 223 ++++++++++++++++-- .../tests/dom_elements/live_document.rs | 91 +++++++ 4 files changed, 473 insertions(+), 47 deletions(-) diff --git a/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs b/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs index ad296eb5d2..0de1361e6a 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs @@ -1298,6 +1298,18 @@ struct SvgTextPositioningElementPrototypeAccessorsDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::SVGPatternElement, enumerable)] struct SvgPatternElementPrototypeAccessorsDeclaration { + #[webapi(accessor_property = "x", getter = svg_pattern_animated_length_getter, data = callback_data_index_value(scope, 0))] + x: (), + + #[webapi(accessor_property = "y", getter = svg_pattern_animated_length_getter, data = callback_data_index_value(scope, 1))] + y: (), + + #[webapi(accessor_property = "width", getter = svg_pattern_animated_length_getter, data = callback_data_index_value(scope, 2))] + width: (), + + #[webapi(accessor_property = "height", getter = svg_pattern_animated_length_getter, data = callback_data_index_value(scope, 3))] + height: (), + #[webapi( accessor_property = "patternUnits", getter = svg_element_animated_enumeration_getter, @@ -1337,6 +1349,60 @@ struct SvgGradientElementPrototypeAccessorsDeclaration { gradient_transform: (), } +#[derive(WebApiFunctionTemplate)] +#[webapi(interface = web_api_interfaces::SVGLinearGradientElement, enumerable)] +struct SvgLinearGradientElementPrototypeAccessorsDeclaration { + #[webapi(accessor_property = "x1", getter = svg_linear_gradient_animated_length_getter, data = callback_data_index_value(scope, 0))] + x1: (), + + #[webapi(accessor_property = "y1", getter = svg_linear_gradient_animated_length_getter, data = callback_data_index_value(scope, 1))] + y1: (), + + #[webapi(accessor_property = "x2", getter = svg_linear_gradient_animated_length_getter, data = callback_data_index_value(scope, 2))] + x2: (), + + #[webapi(accessor_property = "y2", getter = svg_linear_gradient_animated_length_getter, data = callback_data_index_value(scope, 3))] + y2: (), +} + +#[derive(WebApiFunctionTemplate)] +#[webapi(interface = web_api_interfaces::SVGRadialGradientElement, enumerable)] +struct SvgRadialGradientElementPrototypeAccessorsDeclaration { + #[webapi(accessor_property = "cx", getter = svg_radial_gradient_animated_length_getter, data = callback_data_index_value(scope, 0))] + cx: (), + + #[webapi(accessor_property = "cy", getter = svg_radial_gradient_animated_length_getter, data = callback_data_index_value(scope, 1))] + cy: (), + + #[webapi(accessor_property = "r", getter = svg_radial_gradient_animated_length_getter, data = callback_data_index_value(scope, 2))] + r: (), + + #[webapi(accessor_property = "fx", getter = svg_radial_gradient_animated_length_getter, data = callback_data_index_value(scope, 3))] + fx: (), + + #[webapi(accessor_property = "fy", getter = svg_radial_gradient_animated_length_getter, data = callback_data_index_value(scope, 4))] + fy: (), + + #[webapi(accessor_property = "fr", getter = svg_radial_gradient_animated_length_getter, data = callback_data_index_value(scope, 5))] + fr: (), +} + +#[derive(WebApiFunctionTemplate)] +#[webapi(interface = web_api_interfaces::SVGSVGElement, enumerable)] +struct SvgSvgElementPrototypeAccessorsDeclaration { + #[webapi(accessor_property = "x", getter = svg_svg_animated_length_getter, data = callback_data_index_value(scope, 0))] + x: (), + + #[webapi(accessor_property = "y", getter = svg_svg_animated_length_getter, data = callback_data_index_value(scope, 1))] + y: (), + + #[webapi(accessor_property = "width", getter = svg_svg_animated_length_getter, data = callback_data_index_value(scope, 2))] + width: (), + + #[webapi(accessor_property = "height", getter = svg_svg_animated_length_getter, data = callback_data_index_value(scope, 3))] + height: (), +} + #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::SVGClipPathElement, enumerable)] struct SvgClipPathElementPrototypeAccessorsDeclaration { @@ -1351,6 +1417,18 @@ struct SvgClipPathElementPrototypeAccessorsDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::SVGFilterElement, enumerable)] struct SvgFilterElementPrototypeAccessorsDeclaration { + #[webapi(accessor_property = "x", getter = svg_filter_animated_length_getter, data = callback_data_index_value(scope, 0))] + x: (), + + #[webapi(accessor_property = "y", getter = svg_filter_animated_length_getter, data = callback_data_index_value(scope, 1))] + y: (), + + #[webapi(accessor_property = "width", getter = svg_filter_animated_length_getter, data = callback_data_index_value(scope, 2))] + width: (), + + #[webapi(accessor_property = "height", getter = svg_filter_animated_length_getter, data = callback_data_index_value(scope, 3))] + height: (), + #[webapi( accessor_property = "filterUnits", getter = svg_element_animated_enumeration_getter, @@ -1366,6 +1444,22 @@ struct SvgFilterElementPrototypeAccessorsDeclaration { primitive_units: (), } +#[derive(WebApiFunctionTemplate)] +#[webapi(name = "SVGFilterPrimitiveStandardAttributes", enumerable)] +struct SvgFilterPrimitiveStandardAttributesPrototypeAccessorsDeclaration { + #[webapi(accessor_property = "x", getter = svg_filter_primitive_animated_length_getter, data = callback_data_index_value(scope, 0))] + x: (), + + #[webapi(accessor_property = "y", getter = svg_filter_primitive_animated_length_getter, data = callback_data_index_value(scope, 1))] + y: (), + + #[webapi(accessor_property = "width", getter = svg_filter_primitive_animated_length_getter, data = callback_data_index_value(scope, 2))] + width: (), + + #[webapi(accessor_property = "height", getter = svg_filter_primitive_animated_length_getter, data = callback_data_index_value(scope, 3))] + height: (), +} + #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::SVGComponentTransferFunctionElement, enumerable)] struct SvgComponentTransferFunctionElementPrototypeAccessorsDeclaration { @@ -1512,6 +1606,9 @@ struct SvgFeTurbulenceElementPrototypeAccessorsDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::SVGTextPathElement, enumerable)] struct SvgTextPathElementPrototypeAccessorsDeclaration { + #[webapi(accessor_property = "startOffset", getter = svg_text_path_animated_length_getter, data = callback_data_index_value(scope, 0))] + start_offset: (), + #[webapi( accessor_property = "method", getter = svg_element_animated_enumeration_getter, @@ -1537,6 +1634,18 @@ struct SvgTextPathElementPrototypeAccessorsDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::SVGMaskElement, enumerable)] struct SvgMaskElementPrototypeAccessorsDeclaration { + #[webapi(accessor_property = "x", getter = svg_mask_animated_length_getter, data = callback_data_index_value(scope, 0))] + x: (), + + #[webapi(accessor_property = "y", getter = svg_mask_animated_length_getter, data = callback_data_index_value(scope, 1))] + y: (), + + #[webapi(accessor_property = "width", getter = svg_mask_animated_length_getter, data = callback_data_index_value(scope, 2))] + width: (), + + #[webapi(accessor_property = "height", getter = svg_mask_animated_length_getter, data = callback_data_index_value(scope, 3))] + height: (), + #[webapi( accessor_property = "maskUnits", getter = svg_element_animated_enumeration_getter, @@ -1638,6 +1747,18 @@ struct SvgGraphicsBoxElementPrototypeAccessorsDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::SVGMarkerElement, enumerable)] struct SvgMarkerElementPrototypeAccessorsDeclaration { + #[webapi(accessor_property = "refX", getter = svg_marker_animated_length_getter, data = callback_data_index_value(scope, 0))] + ref_x: (), + + #[webapi(accessor_property = "refY", getter = svg_marker_animated_length_getter, data = callback_data_index_value(scope, 1))] + ref_y: (), + + #[webapi(accessor_property = "markerWidth", getter = svg_marker_animated_length_getter, data = callback_data_index_value(scope, 2))] + marker_width: (), + + #[webapi(accessor_property = "markerHeight", getter = svg_marker_animated_length_getter, data = callback_data_index_value(scope, 3))] + marker_height: (), + #[webapi( accessor_property = "markerUnits", getter = svg_element_animated_enumeration_getter, @@ -1971,6 +2092,20 @@ pub(super) fn install_svg_element_accessor_bindings<'s>( interface_name: &str, ) { let prototype = template.prototype_template(scope); + if matches!( + interface_name, + "SVGFEBlendElement" + | "SVGFEColorMatrixElement" + | "SVGFECompositeElement" + | "SVGFEConvolveMatrixElement" + | "SVGFEDisplacementMapElement" + | "SVGFEMorphologyElement" + | "SVGFETurbulenceElement" + ) { + SvgFilterPrimitiveStandardAttributesPrototypeAccessorsDeclaration::initialize_prototype_template( + scope, prototype, + ); + } match interface_name { "SVGElement" => { SvgElementPrototypeAccessorsDeclaration::initialize_prototype_template( @@ -2073,6 +2208,27 @@ pub(super) fn install_svg_element_accessor_bindings<'s>( scope, prototype, ); } + "SVGLinearGradientElement" => { + SvgLinearGradientElementPrototypeAccessorsDeclaration::initialize_prototype_template( + scope, prototype, + ); + SvgUriReferencePrototypeAccessorsDeclaration::initialize_prototype_template( + scope, prototype, + ); + } + "SVGRadialGradientElement" => { + SvgRadialGradientElementPrototypeAccessorsDeclaration::initialize_prototype_template( + scope, prototype, + ); + SvgUriReferencePrototypeAccessorsDeclaration::initialize_prototype_template( + scope, prototype, + ); + } + "SVGSVGElement" => { + SvgSvgElementPrototypeAccessorsDeclaration::initialize_prototype_template( + scope, prototype, + ); + } "SVGRectElement" => { SvgRectElementPrototypeAccessorsDeclaration::initialize_prototype_template( scope, prototype, @@ -2103,10 +2259,7 @@ pub(super) fn install_svg_element_accessor_bindings<'s>( scope, prototype, ); } - "SVGAElement" - | "SVGLinearGradientElement" - | "SVGRadialGradientElement" - | "SVGScriptElement" => { + "SVGAElement" | "SVGScriptElement" => { SvgUriReferencePrototypeAccessorsDeclaration::initialize_prototype_template( scope, prototype, ); diff --git a/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs b/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs index ffba045e6e..f269675dd8 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs @@ -1095,26 +1095,13 @@ pub(super) fn build_svg_matrix<'s>( .expect("SVGMatrix declaration should bind") } -pub(super) fn build_svg_animated_length<'s>( - scope: &mut v8::PinScope<'s, '_>, - value: f64, -) -> v8::Local<'s, v8::Object> { - let base_val = build_svg_length(scope, value); - let anim_val = build_svg_length(scope, value); - SvgAnimatedLengthObjectDeclaration::new(base_val, anim_val) - .bind(scope) - .expect("SVGAnimatedLength declaration should bind") -} - pub(super) fn build_svg_animated_length_for_attribute<'s>( scope: &mut v8::PinScope<'s, '_>, owner: v8::Local<'s, v8::Object>, attribute: &str, + initial_value: &str, ) -> v8::Local<'s, v8::Object> { - let parsed = svg_owner_attribute_value(scope, owner, attribute) - .as_deref() - .and_then(parse_svg_length_value) - .unwrap_or_default(); + let parsed = svg_animated_length_attribute_value(scope, owner, attribute, initial_value); let base_val = build_svg_length_from_parsed(scope, parsed); set_svg_length_owner_attribute(scope, base_val, owner, attribute); let anim_val = build_svg_length_from_parsed(scope, parsed); @@ -2427,6 +2414,15 @@ pub(super) fn svg_animated_length_attribute_slot(name: &str) -> &'static str { "y1" => "__moliSvgAnimatedY1", "x2" => "__moliSvgAnimatedX2", "y2" => "__moliSvgAnimatedY2", + "fx" => "__moliSvgAnimatedFx", + "fy" => "__moliSvgAnimatedFy", + "fr" => "__moliSvgAnimatedFr", + "refX" => "__moliSvgAnimatedRefX", + "refY" => "__moliSvgAnimatedRefY", + "markerWidth" => "__moliSvgAnimatedMarkerWidth", + "markerHeight" => "__moliSvgAnimatedMarkerHeight", + "textLength" => SVG_TEXT_CONTENT_TEXT_LENGTH_SLOT, + "startOffset" => "__moliSvgAnimatedStartOffset", _ => "__moliSvgAnimatedUnknown", } } @@ -2833,11 +2829,9 @@ pub(super) fn sync_svg_animated_length_from_owner_attribute<'s>( animated: v8::Local<'s, v8::Object>, owner: v8::Local<'s, v8::Object>, attribute: &str, + initial_value: &str, ) { - let parsed = svg_owner_attribute_value(scope, owner, attribute) - .as_deref() - .and_then(parse_svg_length_value) - .unwrap_or_default(); + let parsed = svg_animated_length_attribute_value(scope, owner, attribute, initial_value); if let Some(base_val) = get_private_value(scope, animated, SVG_ANIMATED_LENGTH_BASE_VAL_SLOT) .and_then(|value| v8::Local::::try_from(value).ok()) { @@ -2851,6 +2845,19 @@ pub(super) fn sync_svg_animated_length_from_owner_attribute<'s>( } } +fn svg_animated_length_attribute_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + owner: v8::Local<'s, v8::Object>, + attribute: &str, + initial_value: &str, +) -> SvgParsedLength { + svg_owner_attribute_value(scope, owner, attribute) + .as_deref() + .and_then(parse_svg_length_value) + .or_else(|| parse_svg_length_value(initial_value)) + .unwrap_or_default() +} + pub(super) fn set_svg_animated_string_owner_attribute<'s>( scope: &mut v8::PinScope<'s, '_>, animated: v8::Local<'s, v8::Object>, diff --git a/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs b/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs index d3ae731a2c..73a4f57b7e 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs @@ -4,11 +4,57 @@ use crate::util::serialize_v8_array; use crate::web_api_interfaces; use moli_webapi_declare::DataPropertyDescriptorDeclaration; -const SVG_RECT_ANIMATED_LENGTH_ATTRIBUTES: &[&str] = &["x", "y", "width", "height", "rx", "ry"]; -const SVG_CIRCLE_ANIMATED_LENGTH_ATTRIBUTES: &[&str] = &["cx", "cy", "r"]; -const SVG_ELLIPSE_ANIMATED_LENGTH_ATTRIBUTES: &[&str] = &["cx", "cy", "rx", "ry"]; -const SVG_LINE_ANIMATED_LENGTH_ATTRIBUTES: &[&str] = &["x1", "y1", "x2", "y2"]; -const SVG_BOX_ANIMATED_LENGTH_ATTRIBUTES: &[&str] = &["x", "y", "width", "height"]; +const SVG_RECT_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = &[ + ("x", "0"), + ("y", "0"), + ("width", "0"), + ("height", "0"), + ("rx", "0"), + ("ry", "0"), +]; +const SVG_CIRCLE_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = + &[("cx", "0"), ("cy", "0"), ("r", "0")]; +const SVG_ELLIPSE_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = + &[("cx", "0"), ("cy", "0"), ("rx", "0"), ("ry", "0")]; +const SVG_LINE_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = + &[("x1", "0"), ("y1", "0"), ("x2", "0"), ("y2", "0")]; +const SVG_BOX_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = + &[("x", "0"), ("y", "0"), ("width", "0"), ("height", "0")]; +const SVG_FILTER_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = &[ + ("x", "-10%"), + ("y", "-10%"), + ("width", "120%"), + ("height", "120%"), +]; +const SVG_FILTER_PRIMITIVE_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = &[ + ("x", "0%"), + ("y", "0%"), + ("width", "100%"), + ("height", "100%"), +]; +const SVG_LINEAR_GRADIENT_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = + &[("x1", "0%"), ("y1", "0%"), ("x2", "100%"), ("y2", "0%")]; +const SVG_RADIAL_GRADIENT_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = &[ + ("cx", "50%"), + ("cy", "50%"), + ("r", "50%"), + ("fx", "50%"), + ("fy", "50%"), + ("fr", "0%"), +]; +const SVG_MARKER_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = &[ + ("refX", "0"), + ("refY", "0"), + ("markerWidth", "3"), + ("markerHeight", "3"), +]; +const SVG_SVG_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = &[ + ("x", "0"), + ("y", "0"), + ("width", "100%"), + ("height", "100%"), +]; +const SVG_TEXT_PATH_ANIMATED_LENGTH_ATTRIBUTES: &[(&str, &str)] = &[("startOffset", "0")]; const SVG_LENGTH_ACCESSOR_NAMES: &[&str] = &[ "unitType", "value", @@ -378,27 +424,168 @@ pub(super) fn svg_box_animated_length_getter<'s>( ); } +pub(super) fn svg_filter_animated_length_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + svg_element_animated_length_getter( + scope, + &args, + rv, + SVG_FILTER_ANIMATED_LENGTH_ATTRIBUTES, + "SVGFilterElement animated length attributes", + ); +} + +pub(super) fn svg_filter_primitive_animated_length_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + svg_element_animated_length_getter( + scope, + &args, + rv, + SVG_FILTER_PRIMITIVE_ANIMATED_LENGTH_ATTRIBUTES, + "SVGFilterPrimitiveStandardAttributes animated length attributes", + ); +} + +pub(super) fn svg_pattern_animated_length_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + svg_element_animated_length_getter( + scope, + &args, + rv, + SVG_BOX_ANIMATED_LENGTH_ATTRIBUTES, + "SVGPatternElement animated length attributes", + ); +} + +pub(super) fn svg_linear_gradient_animated_length_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + svg_element_animated_length_getter( + scope, + &args, + rv, + SVG_LINEAR_GRADIENT_ANIMATED_LENGTH_ATTRIBUTES, + "SVGLinearGradientElement animated length attributes", + ); +} + +pub(super) fn svg_radial_gradient_animated_length_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + svg_element_animated_length_getter( + scope, + &args, + rv, + SVG_RADIAL_GRADIENT_ANIMATED_LENGTH_ATTRIBUTES, + "SVGRadialGradientElement animated length attributes", + ); +} + +pub(super) fn svg_marker_animated_length_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + svg_element_animated_length_getter( + scope, + &args, + rv, + SVG_MARKER_ANIMATED_LENGTH_ATTRIBUTES, + "SVGMarkerElement animated length attributes", + ); +} + +pub(super) fn svg_mask_animated_length_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + svg_element_animated_length_getter( + scope, + &args, + rv, + SVG_FILTER_ANIMATED_LENGTH_ATTRIBUTES, + "SVGMaskElement animated length attributes", + ); +} + +pub(super) fn svg_svg_animated_length_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + svg_element_animated_length_getter( + scope, + &args, + rv, + SVG_SVG_ANIMATED_LENGTH_ATTRIBUTES, + "SVGSVGElement animated length attributes", + ); +} + +pub(super) fn svg_text_path_animated_length_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + svg_element_animated_length_getter( + scope, + &args, + rv, + SVG_TEXT_PATH_ANIMATED_LENGTH_ATTRIBUTES, + "SVGTextPathElement animated length attributes", + ); +} + fn svg_element_animated_length_getter<'s>( scope: &mut v8::PinScope<'s, '_>, args: &v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, - attributes: &'static [&'static str], + attributes: &'static [(&'static str, &'static str)], label: &'static str, ) { - let Some(name) = callback_data_item(scope, args, attributes, label) else { + let Some((name, initial_value)) = callback_data_item(scope, args, attributes, label) else { rv.set_undefined(); return; }; + svg_animated_length_attribute_getter(scope, args.this(), rv, name, initial_value); +} + +fn svg_animated_length_attribute_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + owner: v8::Local<'s, v8::Object>, + mut rv: v8::ReturnValue<'_, v8::Value>, + name: &'static str, + initial_value: &'static str, +) { let slot = svg_animated_length_attribute_slot(name); - let owner = args.this(); if let Some(value) = get_private_value(scope, owner, slot) { if let Ok(object) = v8::Local::::try_from(value) { - sync_svg_animated_length_from_owner_attribute(scope, object, owner, name); + sync_svg_animated_length_from_owner_attribute( + scope, + object, + owner, + name, + initial_value, + ); } rv.set(value); return; } - let value = build_svg_animated_length_for_attribute(scope, owner, name); + let value = build_svg_animated_length_for_attribute(scope, owner, name, initial_value); set_private_value(scope, owner, slot, value.into()); rv.set(value.into()); } @@ -662,21 +849,9 @@ pub(super) fn svg_geometry_path_length_getter<'s>( pub(super) fn svg_text_content_text_length_getter<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, - mut rv: v8::ReturnValue<'_, v8::Value>, + rv: v8::ReturnValue<'_, v8::Value>, ) { - let holder = args.this(); - if let Some(value) = get_private_value(scope, holder, SVG_TEXT_CONTENT_TEXT_LENGTH_SLOT) { - rv.set(value); - return; - } - let value = build_svg_animated_length(scope, 0.0); - set_private_value( - scope, - holder, - SVG_TEXT_CONTENT_TEXT_LENGTH_SLOT, - value.into(), - ); - rv.set(value.into()); + svg_animated_length_attribute_getter(scope, args.this(), rv, "textLength", "0"); } pub(super) fn svg_element_animated_enumeration_getter<'s>( diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs index a8a5f11503..13ec1617b8 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs @@ -1649,6 +1649,97 @@ fn svg_owner_svg_element_tracks_the_nearest_svg_fragment_root() { assert_eq!(result, "ok"); } +#[test] +fn svg_animated_lengths_reflect_initial_and_content_attribute_values() { + let mut vm = new_parsed_test_vm( + "https://svg-animated-length-reflection.test/", + "", + ); + + 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 = [ + ["filter", SVGFilterElement, [ + ["x", "-10%"], ["y", "-10%"], + ["width", "120%"], ["height", "120%"], + ]], + ["feBlend", SVGFEBlendElement, [ + ["x", "0%"], ["y", "0%"], + ["width", "100%"], ["height", "100%"], + ]], + ["linearGradient", SVGLinearGradientElement, [ + ["x1", "0%"], ["y1", "0%"], ["x2", "100%"], ["y2", "0%"], + ]], + ["marker", SVGMarkerElement, [ + ["refX", "0"], ["refY", "0"], + ["markerWidth", "3"], ["markerHeight", "3"], + ]], + ["mask", SVGMaskElement, [ + ["x", "-10%"], ["y", "-10%"], + ["width", "120%"], ["height", "120%"], + ]], + ["pattern", SVGPatternElement, [ + ["x", "0"], ["y", "0"], ["width", "0"], ["height", "0"], + ]], + ["radialGradient", SVGRadialGradientElement, [ + ["cx", "50%"], ["cy", "50%"], ["r", "50%"], + ["fx", "50%"], ["fy", "50%"], ["fr", "0%"], + ]], + ["svg", SVGSVGElement, [ + ["x", "0"], ["y", "0"], ["width", "100%"], ["height", "100%"], + ]], + ["text", SVGTextContentElement, [["textLength", "0"]]], + ["textPath", SVGTextPathElement, [["startOffset", "0"]]], + ]; + + for (const [tag, owner, attributes] of cases) { + const element = document.createElementNS(ns, tag); + for (const [name, initial] of attributes) { + const descriptor = Object.getOwnPropertyDescriptor(owner.prototype, name); + assert(typeof descriptor?.get === "function", `${owner.name}.${name} getter`); + assert(descriptor.set === undefined, `${owner.name}.${name} setter`); + assert(descriptor.enumerable && descriptor.configurable, + `${owner.name}.${name} flags`); + + const animated = element[name]; + assert(animated instanceof SVGAnimatedLength, `${tag}.${name} interface`); + assert(element[name] === animated, `${tag}.${name} SameObject`); + assert(animated.baseVal.valueAsString === initial, + `${tag}.${name} initial baseVal`); + assert(animated.animVal.valueAsString === initial, + `${tag}.${name} initial animVal`); + + element.setAttribute(name, "42"); + assert(element[name] === animated, `${tag}.${name} SameObject after set`); + assert(animated.baseVal.valueAsString === "42" && + animated.animVal.valueAsString === "42", `${tag}.${name} content update`); + + element.setAttribute(name, "foobar"); + assert(element[name] === animated, `${tag}.${name} SameObject after invalid`); + assert(animated.baseVal.valueAsString === initial && + animated.animVal.valueAsString === initial, `${tag}.${name} invalid fallback`); + + element.removeAttribute(name); + assert(element[name] === animated, `${tag}.${name} SameObject after remove`); + assert(animated.baseVal.valueAsString === initial && + animated.animVal.valueAsString === initial, `${tag}.${name} removed fallback`); + } + } + return "ok"; + })() + "#, + ) + .expect("SVG animated length reflection probe should evaluate"); + + assert_eq!(result, "ok"); +} + #[test] fn svg_historical_interfaces_keep_current_element_constructors_only() { let mut vm = new_parsed_test_vm(