diff --git a/moli-dom/src/native/element/interface_names.rs b/moli-dom/src/native/element/interface_names.rs index e302756eb7..64bd99c641 100644 --- a/moli-dom/src/native/element/interface_names.rs +++ b/moli-dom/src/native/element/interface_names.rs @@ -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", diff --git a/moli-dom/src/native/element/tests.rs b/moli-dom/src/native/element/tests.rs index ba2bad6839..0c0a888f58 100644 --- a/moli-dom/src/native/element/tests.rs +++ b/moli-dom/src/native/element/tests.rs @@ -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"); diff --git a/moli-renderer-v8/src/context_bootstrap/bridge_descriptor/descriptors.rs b/moli-renderer-v8/src/context_bootstrap/bridge_descriptor/descriptors.rs index c340f30192..5e1a60d711 100644 --- a/moli-renderer-v8/src/context_bootstrap/bridge_descriptor/descriptors.rs +++ b/moli-renderer-v8/src/context_bootstrap/bridge_descriptor/descriptors.rs @@ -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, 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 5aed84ab1f..db4602befc 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs @@ -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( 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 1c4a054bcf..9bc8dfccac 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs @@ -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 { + 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::::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::::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::::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::::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 { + 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 { + 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>, 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 6fb70aec41..3777e51ef7 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs @@ -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::::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::( + let value = match webidl::convert::( 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()); } diff --git a/moli-renderer-v8/src/context_bootstrap/svg_runtime/mod.rs b/moli-renderer-v8/src/context_bootstrap/svg_runtime/mod.rs index a13db6352f..d4c047751f 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/mod.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/mod.rs @@ -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, diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document/geometry_and_svg.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document/geometry_and_svg.rs index 8bba204554..aefd02b6b5 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document/geometry_and_svg.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document/geometry_and_svg.rs @@ -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/", + "", + ); + + 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( diff --git a/moli-renderer-v8/src/web_api_interfaces.rs b/moli-renderer-v8/src/web_api_interfaces.rs index cbe8bb0dd6..fc82cbba9f 100644 --- a/moli-renderer-v8/src/web_api_interfaces.rs +++ b/moli-renderer-v8/src/web_api_interfaces.rs @@ -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;