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