From fbe7d4f6c9fe8ba2a613197c1719ab7ef6a68e95 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Fri, 9 Oct 2026 15:51:32 +0800 Subject: [PATCH] fix(build): reconcile native SVG bindings after main rebase Remove duplicated SVG declarations and stale shims introduced while replaying the topic history onto the native SVG interfaces commit in main. Preserve the later derive, UTF-16 and SMIL implementations and all active tests. Enable main's lone-surrogate SVG animated-string regression now that the native attribute storage supports UTF-16. Validation: cargo fmt --all; cargo clippy --workspace --all-targets --all-features -- -D warnings; cargo nextest run --no-fail-fast (21730 passed, 16 skipped; 32 threads; no automatic retries). --- .../src/native/element/interface_names.rs | 8 +- .../bridge_descriptor/descriptors.rs | 40 +- .../src/context_bootstrap/geometry_runtime.rs | 104 - .../context_bootstrap/svg_runtime/bindings.rs | 60 - .../context_bootstrap/svg_runtime/builders.rs | 38 - .../svg_runtime/callbacks.rs | 20 - .../src/context_bootstrap/svg_runtime/mod.rs | 158 +- moli-renderer-v8/src/native_bridge/element.rs | 12 +- .../native_bridge/element/trusted_types.rs | 85 - .../tests/browser_api/trusted_types.rs | 111 - .../dom_interfaces_and_collections.rs | 233 -- .../live_document/geometry_and_svg.rs | 1954 +---------------- moli-renderer-v8/src/web_api_interfaces.rs | 5 +- 13 files changed, 140 insertions(+), 2688 deletions(-) diff --git a/moli-dom/src/native/element/interface_names.rs b/moli-dom/src/native/element/interface_names.rs index 78544ff908..8efeeb6db3 100644 --- a/moli-dom/src/native/element/interface_names.rs +++ b/moli-dom/src/native/element/interface_names.rs @@ -99,10 +99,16 @@ pub fn svg_element_interface_name(local_name: &str) -> &'static str { "defs" => "SVGDefsElement", "desc" => "SVGDescElement", "ellipse" => "SVGEllipseElement", + "feComponentTransfer" => "SVGFEComponentTransferElement", + "feConvolveMatrix" => "SVGFEConvolveMatrixElement", + "feFlood" => "SVGFEFloodElement", + "feImage" => "SVGFEImageElement", + "feMerge" => "SVGFEMergeElement", + "feMergeNode" => "SVGFEMergeNodeElement", + "feTile" => "SVGFETileElement", "feBlend" => "SVGFEBlendElement", "feColorMatrix" => "SVGFEColorMatrixElement", "feComposite" => "SVGFECompositeElement", - "feConvolveMatrix" => "SVGFEConvolveMatrixElement", "feDiffuseLighting" => "SVGFEDiffuseLightingElement", "feDisplacementMap" => "SVGFEDisplacementMapElement", "feDistantLight" => "SVGFEDistantLightElement", 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 3c94271d96..50cbad088e 100644 --- a/moli-renderer-v8/src/context_bootstrap/bridge_descriptor/descriptors.rs +++ b/moli-renderer-v8/src/context_bootstrap/bridge_descriptor/descriptors.rs @@ -133,18 +133,6 @@ const NODE_BRIDGE_DESCRIPTORS: &[BridgeDescriptor] = &[ web_api_interfaces::SVGClipPathElement::DESCRIPTOR, ELEMENT_GROUPS, ), - descriptor( - web_api_interfaces::SVGFilterElement::DESCRIPTOR, - ELEMENT_GROUPS, - ), - descriptor( - web_api_interfaces::SVGMaskElement::DESCRIPTOR, - ELEMENT_GROUPS, - ), - descriptor( - web_api_interfaces::SVGViewElement::DESCRIPTOR, - ELEMENT_GROUPS, - ), specialized_descriptor( web_api_interfaces::SVGCircleElement::DESCRIPTOR, ELEMENT_GROUPS, @@ -169,6 +157,30 @@ const NODE_BRIDGE_DESCRIPTORS: &[BridgeDescriptor] = &[ SpecializedTemplateInstaller::None, SVG_GEOMETRY_RUNTIME_INSTALL_GROUPS, ), + descriptor( + web_api_interfaces::SVGFEComponentTransferElement::DESCRIPTOR, + ELEMENT_GROUPS, + ), + descriptor( + web_api_interfaces::SVGFEFloodElement::DESCRIPTOR, + ELEMENT_GROUPS, + ), + descriptor( + web_api_interfaces::SVGFEImageElement::DESCRIPTOR, + ELEMENT_GROUPS, + ), + descriptor( + web_api_interfaces::SVGFEMergeElement::DESCRIPTOR, + ELEMENT_GROUPS, + ), + descriptor( + web_api_interfaces::SVGFEMergeNodeElement::DESCRIPTOR, + ELEMENT_GROUPS, + ), + descriptor( + web_api_interfaces::SVGFETileElement::DESCRIPTOR, + ELEMENT_GROUPS, + ), descriptor( web_api_interfaces::SVGComponentTransferFunctionElement::DESCRIPTOR, ELEMENT_GROUPS, @@ -284,6 +296,10 @@ const NODE_BRIDGE_DESCRIPTORS: &[BridgeDescriptor] = &[ web_api_interfaces::SVGMarkerElement::DESCRIPTOR, ELEMENT_GROUPS, ), + descriptor( + web_api_interfaces::SVGMaskElement::DESCRIPTOR, + ELEMENT_GROUPS, + ), descriptor( web_api_interfaces::SVGMetadataElement::DESCRIPTOR, ELEMENT_GROUPS, diff --git a/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs b/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs index 476b5043f6..38986d4a86 100644 --- a/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs +++ b/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs @@ -774,37 +774,6 @@ pub(in crate::context_bootstrap) fn build_dom_point_object<'s>( .expect("DOMPoint declaration should bind") } -pub(super) fn dom_point_init_from_object<'s>( - scope: &mut v8::PinScope<'s, '_>, - object: v8::Local<'s, v8::Object>, -) -> DomPointInit { - DomPointInit { - x: dom_point_slot(scope, object, DOM_POINT_X_SLOT, 0.0), - y: dom_point_slot(scope, object, DOM_POINT_Y_SLOT, 0.0), - z: dom_point_slot(scope, object, DOM_POINT_Z_SLOT, 0.0), - w: dom_point_slot(scope, object, DOM_POINT_W_SLOT, 1.0), - } -} - -pub(super) fn dom_point_clone_data<'s>( - scope: &mut v8::PinScope<'s, '_>, - object: v8::Local<'s, v8::Object>, -) -> Option<(bool, [f64; 4])> { - if !dom_point_receiver_branded(scope, object) { - return None; - } - let mutable = web_api_interfaces::DOMPoint::is_instance(scope, object); - Some(( - mutable, - [ - dom_point_slot(scope, object, DOM_POINT_X_SLOT, 0.0), - dom_point_slot(scope, object, DOM_POINT_Y_SLOT, 0.0), - dom_point_slot(scope, object, DOM_POINT_Z_SLOT, 0.0), - dom_point_slot(scope, object, DOM_POINT_W_SLOT, 1.0), - ], - )) -} - pub(in crate::context_bootstrap) fn build_svg_point_object<'s>( scope: &mut v8::PinScope<'s, '_>, ) -> v8::Local<'s, v8::Object> { @@ -3044,76 +3013,3 @@ const DOM_MATRIX_ATTRIBUTES: &[DomMatrixAttribute] = &[ DOM_MATRIX_READONLY_ATTRIBUTES[0], DOM_MATRIX_READONLY_ATTRIBUTES[1], ]; - -#[derive(Default, webidl::WebIdlDictionary)] -#[webidl(prefix = "DOMMatrix2DInit")] -struct DomMatrix2DInit { - a: Option, - b: Option, - c: Option, - d: Option, - e: Option, - f: Option, - m11: Option, - m12: Option, - m21: Option, - m22: Option, - m41: Option, - m42: Option, -} - -pub(in crate::context_bootstrap) fn dom_matrix_2d_init_arg<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: &v8::FunctionCallbackArguments<'s>, - index: i32, - prefix: &'static str, -) -> Option<[f64; 6]> { - if index >= args.length() || args.get(index).is_undefined() { - return Some([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]); - } - let init = match webidl::parse_dictionary::( - scope, - args.get(index), - webidl::Context::argument(prefix, (index + 1) as usize), - ) { - Ok(Some(init)) => init, - Ok(None) => DomMatrix2DInit::default(), - Err(error) => { - webidl::throw_error(scope, &error); - return None; - } - }; - Some([ - validated_dom_matrix_2d_alias(scope, "DOMMatrix2DInit", "a", init.a, "m11", init.m11, 1.0)?, - validated_dom_matrix_2d_alias(scope, "DOMMatrix2DInit", "b", init.b, "m12", init.m12, 0.0)?, - validated_dom_matrix_2d_alias(scope, "DOMMatrix2DInit", "c", init.c, "m21", init.m21, 0.0)?, - validated_dom_matrix_2d_alias(scope, "DOMMatrix2DInit", "d", init.d, "m22", init.m22, 1.0)?, - validated_dom_matrix_2d_alias(scope, "DOMMatrix2DInit", "e", init.e, "m41", init.m41, 0.0)?, - validated_dom_matrix_2d_alias(scope, "DOMMatrix2DInit", "f", init.f, "m42", init.m42, 0.0)?, - ]) -} - -fn validated_dom_matrix_2d_alias( - scope: &mut v8::PinScope<'_, '_>, - dictionary_name: &'static str, - alias_name: &'static str, - alias: Option, - matrix_name: &'static str, - matrix: Option, - default: f64, -) -> Option { - if let (Some(alias), Some(matrix)) = (alias, matrix) - && !same_dom_matrix_2d_number(alias, matrix) - { - throw_type_error( - scope, - &format!("{dictionary_name} {alias_name} and {matrix_name} values differ."), - ); - return None; - } - Some(matrix.or(alias).unwrap_or(default)) -} - -fn same_dom_matrix_2d_number(lhs: f64, rhs: f64) -> bool { - lhs == rhs || (lhs.is_nan() && rhs.is_nan()) -} 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 d9c5e35758..499ef11b02 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs @@ -840,66 +840,6 @@ struct SvgMarkerElementTemplateMethodsDeclaration { set_orient_to_angle: (), } -#[derive(WebApiFunctionTemplate)] -#[webapi(interface = web_api_interfaces::SVGMarkerElement, enumerable)] -struct SvgMarkerElementTemplateMethodsDeclaration { - #[webapi( - constant = "SVG_MARKERUNITS_UNKNOWN", - value = SVG_MARKER_UNITS_UNKNOWN - )] - marker_units_unknown: (), - - #[webapi( - constant = "SVG_MARKERUNITS_USERSPACEONUSE", - value = SVG_MARKER_UNITS_USER_SPACE_ON_USE - )] - marker_units_user_space_on_use: (), - - #[webapi( - constant = "SVG_MARKERUNITS_STROKEWIDTH", - value = SVG_MARKER_UNITS_STROKE_WIDTH - )] - marker_units_stroke_width: (), - - #[webapi( - constant = "SVG_MARKER_ORIENT_UNKNOWN", - value = SVG_MARKER_ORIENT_UNKNOWN - )] - marker_orient_unknown: (), - - #[webapi( - constant = "SVG_MARKER_ORIENT_AUTO", - value = SVG_MARKER_ORIENT_AUTO - )] - marker_orient_auto: (), - - #[webapi( - constant = "SVG_MARKER_ORIENT_ANGLE", - value = SVG_MARKER_ORIENT_ANGLE - )] - marker_orient_angle: (), - - #[webapi( - constant = "SVG_MARKER_ORIENT_AUTO_START_REVERSE", - value = SVG_MARKER_ORIENT_AUTO_START_REVERSE - )] - marker_orient_auto_start_reverse: (), - - #[webapi( - method = "setOrientToAuto", - length = 0, - callback = svg_marker_set_orient_to_auto_callback - )] - set_orient_to_auto: (), - - #[webapi( - method = "setOrientToAngle", - length = 1, - callback = svg_marker_set_orient_to_angle_callback - )] - set_orient_to_angle: (), -} - #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::SVGSVGElement, enumerable, receiver)] struct SvgSvgElementTemplateMethodsDeclaration { 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 9aef078105..0b8f576f50 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs @@ -1245,44 +1245,6 @@ pub(super) fn build_svg_angle_from_parsed<'s>( .expect("SVGAngle declaration should bind") } -pub(super) fn build_svg_animated_angle_for_attribute<'s>( - scope: &mut v8::PinScope<'s, '_>, - owner: v8::Local<'s, v8::Object>, - attribute: &str, -) -> v8::Local<'s, v8::Object> { - let parsed = svg_owner_attribute_value(scope, owner, attribute) - .as_deref() - .and_then(parse_svg_orient_angle_value) - .unwrap_or_default(); - let base_val = build_svg_angle_from_parsed(scope, &parsed, false); - set_svg_angle_owner_attribute(scope, base_val, owner, attribute); - let anim_val = build_svg_angle_from_parsed(scope, &parsed, true); - set_svg_angle_owner_attribute(scope, anim_val, owner, attribute); - SvgAnimatedAngleObjectDeclaration::new(base_val, anim_val) - .bind(scope) - .expect("SVGAnimatedAngle declaration should bind") -} - -pub(super) fn build_svg_angle<'s>(scope: &mut v8::PinScope<'s, '_>) -> v8::Local<'s, v8::Object> { - build_svg_angle_from_parsed(scope, &SvgParsedAngle::default(), false) -} - -pub(super) fn build_svg_angle_from_parsed<'s>( - scope: &mut v8::PinScope<'s, '_>, - parsed: &SvgParsedAngle, - read_only: bool, -) -> v8::Local<'s, v8::Object> { - SvgAngleObjectDeclaration::new( - parsed.unit_type, - parsed.value, - parsed.value_in_specified_units, - parsed.value_as_string.clone(), - read_only, - ) - .bind(scope) - .expect("SVGAngle declaration should bind") -} - pub(super) fn build_svg_length<'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 1b4630f729..e7b62e4451 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs @@ -3326,26 +3326,6 @@ pub(super) fn svg_svg_element_create_point_callback<'s>( } } -pub(super) fn svg_svg_element_create_angle_callback<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: v8::FunctionCallbackArguments<'s>, - mut rv: v8::ReturnValue<'_, v8::Value>, -) { - if require_svg_svg_element_receiver(scope, args.this(), "createSVGAngle") { - rv.set(build_svg_angle(scope).into()); - } -} - -pub(super) fn svg_svg_element_create_point_callback<'s>( - scope: &mut v8::PinScope<'s, '_>, - args: v8::FunctionCallbackArguments<'s>, - mut rv: v8::ReturnValue<'_, v8::Value>, -) { - if require_svg_svg_element_receiver(scope, args.this(), "createSVGPoint") { - rv.set(super::super::geometry_runtime::build_svg_point_object(scope).into()); - } -} - pub(super) fn svg_svg_element_deselect_all_callback<'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 1ce36a1c23..87082b2c84 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/mod.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/mod.rs @@ -243,58 +243,6 @@ struct SvgAnimatedNumberProperty { component: SvgAnimatedNumberComponent, } -#[derive(Clone, Copy)] -enum SvgAnimatedEnumerationKind { - Keywords(&'static [(&'static str, u32)]), - MarkerOrient, -} - -#[derive(Clone, Copy)] -struct SvgAnimatedEnumerationProperty { - index: usize, - attribute: &'static str, - cache_slot: &'static str, - initial_value: u32, - 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(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> { @@ -1098,111 +1046,6 @@ const SVG_ANIMATED_INTEGER_PROPERTIES: &[SvgAnimatedIntegerProperty] = &[ initial_value: 1, component: SvgAnimatedIntegerComponent::Scalar, }, - SvgAnimatedEnumerationProperty { - index: 10, - attribute: "markerUnits", - cache_slot: SVG_MARKER_UNITS_SLOT, - initial_value: SVG_MARKER_UNITS_STROKE_WIDTH, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_MARKER_UNITS_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 11, - attribute: "orient", - cache_slot: SVG_MARKER_ORIENT_TYPE_SLOT, - initial_value: SVG_MARKER_ORIENT_ANGLE, - kind: SvgAnimatedEnumerationKind::MarkerOrient, - }, - SvgAnimatedEnumerationProperty { - index: 12, - attribute: "type", - cache_slot: SVG_COMPONENT_TRANSFER_TYPE_SLOT, - initial_value: SVG_COMPONENT_TRANSFER_TYPE_IDENTITY, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_COMPONENT_TRANSFER_TYPE_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 13, - attribute: "mode", - cache_slot: SVG_FE_BLEND_MODE_SLOT, - initial_value: SVG_FE_BLEND_MODE_NORMAL, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_FE_BLEND_MODE_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 14, - attribute: "type", - cache_slot: SVG_FE_COLOR_MATRIX_TYPE_SLOT, - initial_value: SVG_FE_COLOR_MATRIX_TYPE_MATRIX, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_FE_COLOR_MATRIX_TYPE_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 15, - attribute: "operator", - cache_slot: SVG_FE_COMPOSITE_OPERATOR_SLOT, - initial_value: SVG_FE_COMPOSITE_OPERATOR_OVER, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_FE_COMPOSITE_OPERATOR_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 16, - attribute: "edgeMode", - cache_slot: SVG_FE_CONVOLVE_MATRIX_EDGE_MODE_SLOT, - initial_value: SVG_EDGE_MODE_DUPLICATE, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_EDGE_MODE_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 17, - attribute: "xChannelSelector", - cache_slot: SVG_FE_DISPLACEMENT_MAP_X_CHANNEL_SLOT, - initial_value: SVG_CHANNEL_A, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_CHANNEL_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 18, - attribute: "yChannelSelector", - cache_slot: SVG_FE_DISPLACEMENT_MAP_Y_CHANNEL_SLOT, - initial_value: SVG_CHANNEL_A, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_CHANNEL_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 19, - attribute: "operator", - cache_slot: SVG_FE_MORPHOLOGY_OPERATOR_SLOT, - initial_value: SVG_MORPHOLOGY_OPERATOR_ERODE, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_MORPHOLOGY_OPERATOR_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 20, - attribute: "stitchTiles", - cache_slot: SVG_FE_TURBULENCE_STITCH_TILES_SLOT, - initial_value: SVG_STITCH_TYPE_NO_STITCH, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_STITCH_TYPE_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 21, - attribute: "type", - cache_slot: SVG_FE_TURBULENCE_TYPE_SLOT, - initial_value: SVG_TURBULENCE_TYPE_TURBULENCE, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_TURBULENCE_TYPE_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 22, - attribute: "method", - cache_slot: SVG_TEXT_PATH_METHOD_SLOT, - initial_value: SVG_TEXT_PATH_METHOD_TYPE_ALIGN, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_TEXT_PATH_METHOD_TYPE_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 23, - attribute: "spacing", - cache_slot: SVG_TEXT_PATH_SPACING_SLOT, - initial_value: SVG_TEXT_PATH_SPACING_TYPE_EXACT, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_TEXT_PATH_SPACING_TYPE_VALUES), - }, - SvgAnimatedEnumerationProperty { - index: 24, - attribute: "side", - cache_slot: SVG_TEXT_PATH_SIDE_SLOT, - initial_value: SVG_TEXT_PATH_SIDE_TYPE_LEFT, - kind: SvgAnimatedEnumerationKind::Keywords(SVG_TEXT_PATH_SIDE_TYPE_VALUES), - }, ]; pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>( @@ -1257,6 +1100,7 @@ pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>( "SVGTextContentElement" => { bindings::install_svg_text_content_element_bindings(scope, template) } + "SVGGradientElement" => bindings::install_svg_gradient_element_bindings(scope, template), "SVGMarkerElement" => bindings::install_svg_marker_element_bindings(scope, template), "SVGSVGElement" => bindings::install_svg_svg_element_bindings(scope, template), _ => {} diff --git a/moli-renderer-v8/src/native_bridge/element.rs b/moli-renderer-v8/src/native_bridge/element.rs index d5f66da97f..3f36a35bd1 100644 --- a/moli-renderer-v8/src/native_bridge/element.rs +++ b/moli-renderer-v8/src/native_bridge/element.rs @@ -272,14 +272,12 @@ pub(crate) use details_dialog::{ use event_handlers::install_global_event_handler_template_bindings as install_global_event_handler_templates_for_owner; use event_handlers::install_node_event_handler_template_bindings; pub(crate) use event_handlers::{ - GlobalEventHandlerOwner, body_or_frameset_reflects_window_event_type, - canonical_event_handler_event_type, compile_body_window_event_attribute, + GlobalEventHandlerOwner, ParserAddedBodyWindowHandlers, + body_or_frameset_reflects_window_event_type, canonical_event_handler_event_type, + compile_body_window_event_attribute, compile_window_event_attribute_handler, event_handler_content_attribute_name, initialize_parser_inserted_body_window_event_handlers, - resolve_window_event_handler_content_attribute, - node_event_handler_getter_function, node_event_handler_setter_function, - legacy_lenient_this_event_handler, - compile_window_event_attribute_handler, - ParserAddedBodyWindowHandlers, + legacy_lenient_this_event_handler, node_event_handler_getter_function, + node_event_handler_setter_function, resolve_window_event_handler_content_attribute, }; use event_handlers::{ HtmlBodyWindowEventHandlersDeclaration, HtmlFrameSetWindowEventHandlersDeclaration, diff --git a/moli-renderer-v8/src/native_bridge/element/trusted_types.rs b/moli-renderer-v8/src/native_bridge/element/trusted_types.rs index 6450562b81..98a24cbb15 100644 --- a/moli-renderer-v8/src/native_bridge/element/trusted_types.rs +++ b/moli-renderer-v8/src/native_bridge/element/trusted_types.rs @@ -451,88 +451,3 @@ pub(crate) fn prepare_trusted_script_text( .set_script_text_internal_slot(handle, &source); Some(source) } - -fn svg_animated_string_attribute_namespace( - runtime: &JsContextHost, - handle: DomHandle, - attribute: &str, -) -> Option<&'static str> { - (attribute == "href" - && !runtime.dom_host().has_attribute_ns(handle, None, attribute) - && runtime.dom_host().has_attribute_ns( - handle, - Some(crate::native_bridge::document::XLINK_NS), - attribute, - )) - .then_some(crate::native_bridge::document::XLINK_NS) -} - -pub(crate) fn set_svg_animated_string_base_value<'s>( - scope: &mut v8::PinScope<'s, '_>, - owner: v8::Local<'s, v8::Object>, - attribute: &str, - value: v8::Local<'s, v8::Value>, -) -> Option { - let (runtime_ptr, handle) = - crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, owner).ok()?; - let script_href = { - let element = unsafe { &*runtime_ptr } - .dom_host() - .node(handle)? - .as_element()?; - element.namespace() == "http://www.w3.org/2000/svg" - && element.local_name() == "script" - && attribute == "href" - }; - let value = if script_href { - let requirements = unsafe { &*runtime_ptr }.trusted_types_for_script_requirements(scope); - crate::context_bootstrap::trusted_script_url_string_or_throw( - scope, - value, - requirements, - "SVGScriptElement href", - "baseVal", - )? - } else { - match crate::webidl::convert::( - scope, - value, - crate::webidl::Context::member("SVGAnimatedString", "baseVal"), - ) { - Ok(value) => value.0, - Err(error) => { - crate::webidl::throw_error(scope, &error); - return None; - } - } - }; - let (runtime_ptr, handle) = - crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, owner).ok()?; - let namespace = - svg_animated_string_attribute_namespace(unsafe { &*runtime_ptr }, handle, attribute); - if attribute == "href" { - let _ = unsafe { &mut *runtime_ptr }.set_attribute_ns( - scope, - runtime_ptr, - handle, - namespace, - namespace.map(|_| "xlink"), - attribute, - if namespace.is_some() { - "xlink:href" - } else { - attribute - }, - &value, - ); - } else { - let _ = unsafe { &mut *runtime_ptr }.set_attribute( - scope, - runtime_ptr, - handle, - attribute, - &value, - ); - } - Some(value) -} diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs index bccabbc483..c0eb36f49c 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs @@ -3142,114 +3142,3 @@ fn service_worker_register_converts_options_before_the_default_policy() { ]) ); } - -#[test] -fn svg_script_href_base_val_uses_the_owner_reflected_trusted_script_url_sink() { - let mut vm = new_storage_test_vm("https://svg-script-href-trusted-types.test/"); - vm.set_response_content_security_policies(&["require-trusted-types-for 'script'".to_owned()]); - - let result = vm - .eval( - r#" -(() => { - const violations = []; - document.addEventListener("securitypolicyviolation", event => { - violations.push({ - blockedURI: event.blockedURI, - effectiveDirective: event.effectiveDirective, - sample: event.sample - }); - }); - globalThis.__svgScriptHrefViolations = violations; - - const svgScript = document.createElementNS("http://www.w3.org/2000/svg", "script"); - const href = svgScript.href; - let illegalConstructor = false; - try { - new SVGAnimatedString(); - } catch (error) { - illegalConstructor = error instanceof TypeError; - } - let plainRejected = false; - try { - href.baseVal = "plain.js"; - } catch (error) { - plainRejected = error instanceof TypeError; - } - const rejectedAttribute = svgScript.getAttribute("href"); - - const explicit = trustedTypes.createPolicy("svg-script-href", { - createScriptURL: value => value - }); - href.baseVal = explicit.createScriptURL("trusted.js"); - const trustedValues = [ - svgScript.getAttribute("href"), - href.baseVal, - href.animVal - ]; - svgScript.setAttribute("href", explicit.createScriptURL("attribute.js")); - const externallySynced = [href.baseVal, href.animVal]; - const className = svgScript.className; - className.baseVal = explicit.createScriptURL("trusted-class"); - const use = document.createElementNS("http://www.w3.org/2000/svg", "use"); - const useHref = use.href; - useHref.baseVal = explicit.createScriptURL("trusted-use"); - - const defaultCalls = []; - trustedTypes.createPolicy("default", { - createScriptURL: (value, type, sink) => { - defaultCalls.push([value, type, sink]); - return `safe-${value}`; - } - }); - href.baseVal = "default-input"; - className.baseVal = "plain-class"; - useHref.baseVal = "plain-use"; - - return JSON.stringify({ - shape: [ - href instanceof SVGAnimatedString, - Object.prototype.toString.call(href), - svgScript.href === href, - illegalConstructor - ], - plainRejected, - rejectedAttribute, - trustedValues, - externallySynced, - ordinaryAnimatedStrings: [ - className instanceof SVGAnimatedString, - svgScript.className === className, - className.baseVal, - className.animVal, - svgScript.getAttribute("class"), - useHref instanceof SVGAnimatedString, - use.href === useHref, - useHref.baseVal, - useHref.animVal, - use.getAttribute("href") - ], - defaulted: [svgScript.getAttribute("href"), href.baseVal, href.animVal], - defaultCalls, - violations - }); -})() -"#, - ) - .expect("SVGScriptElement href Trusted Types sink probe should evaluate"); - - assert_eq!( - result, - r#"{"shape":[true,"[object SVGAnimatedString]",true,true],"plainRejected":true,"rejectedAttribute":null,"trustedValues":["trusted.js","trusted.js","trusted.js"],"externallySynced":["attribute.js","attribute.js"],"ordinaryAnimatedStrings":[true,true,"plain-class","plain-class","plain-class",true,true,"plain-use","plain-use","plain-use"],"defaulted":["safe-default-input","safe-default-input","safe-default-input"],"defaultCalls":[["default-input","TrustedScriptURL","SVGScriptElement href"]],"violations":[]}"# - ); - - assert_eq!( - drain_pre_domcontentloaded_non_script_page_tasks_for_test(&mut vm), - 1 - ); - assert_eq!( - vm.eval("JSON.stringify(globalThis.__svgScriptHrefViolations)") - .expect("queued SVGScriptElement href violation should be observable"), - r#"[{"blockedURI":"trusted-types-sink","effectiveDirective":"require-trusted-types-for","sample":"SVGScriptElement href|plain.js"}]"# - ); -} diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/dom_interfaces_and_collections.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/dom_interfaces_and_collections.rs index dac123e6fd..4e14ce267e 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/dom_interfaces_and_collections.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/dom_surface/extracted/dom_interfaces_and_collections.rs @@ -2734,239 +2734,6 @@ fn detached_plain_document_all_matches_chromium_htmldda_surface() { ); } -#[test] -fn svg_value_lists_enforce_item_types_indices_and_read_only_anim_values() { - let mut vm = new_storage_test_vm("https://svg-value-list-semantics.test/"); - - let result = vm - .eval( - r#" - (() => { - const assert = (condition, message) => { - if (!condition) throw new Error(message); - }; - const errorName = callback => { - try { - callback(); - return "none"; - } catch (error) { - return error.name; - } - }; - const SVG_NS = "http://www.w3.org/2000/svg"; - const text = document.createElementNS(SVG_NS, "text"); - const svg = document.createElementNS(SVG_NS, "svg"); - text.setAttribute("x", "10 20"); - text.setAttribute("rotate", "15 30"); - - const lengths = text.x; - const lengthBase = lengths.baseVal; - const lengthAnim = lengths.animVal; - const numbers = text.rotate; - const numberBase = numbers.baseVal; - const numberAnim = numbers.animVal; - const invalidItems = [30, "invalid", text, null]; - - for (const item of invalidItems) { - assert(errorName(() => lengthBase.initialize(item)) === "TypeError", - "SVGLengthList.initialize item type"); - assert(errorName(() => lengthBase.insertItemBefore(item, 0)) === "TypeError", - "SVGLengthList.insertItemBefore item type"); - assert(errorName(() => lengthBase.replaceItem(item, 0)) === "TypeError", - "SVGLengthList.replaceItem item type"); - assert(errorName(() => lengthBase.appendItem(item)) === "TypeError", - "SVGLengthList.appendItem item type"); - assert(errorName(() => { lengthBase[0] = item; }) === "TypeError", - "SVGLengthList indexed setter item type"); - } - - const length = svg.createSVGLength(); - length.value = 42; - lengthBase[0] = length; - assert(lengthBase[0] === length, "SVGLengthList indexed getter"); - assert(text.getAttribute("x") === "42 20", "SVGLengthList indexed reflection"); - - const number = svg.createSVGNumber(); - number.value = 7; - numberBase[1] = number; - assert(numberBase[1] === number, "SVGNumberList indexed getter"); - assert(text.getAttribute("rotate") === "15 7", "SVGNumberList indexed reflection"); - assert(errorName(() => lengthBase.appendItem(number)) === "TypeError", - "SVGLengthList rejects SVGNumber"); - assert(errorName(() => numberBase.appendItem(length)) === "TypeError", - "SVGNumberList rejects SVGLength"); - - text.setAttribute("x", "1 2 3"); - assert(lengthBase.length === 3 && lengthBase[2].value === 3, - "saved baseVal resynchronizes"); - assert(text.x.animVal.length === 3 && text.x.animVal[2].value === 3, - "animVal resynchronizes after direct baseVal access"); - - assert(errorName(() => lengthAnim.clear()) === "NoModificationAllowedError", - "SVGLengthList animVal clear"); - assert(errorName(() => { lengthAnim[0] = length; }) === "NoModificationAllowedError", - "SVGLengthList animVal indexed setter"); - assert(errorName(() => numberAnim.appendItem(number)) === "NoModificationAllowedError", - "SVGNumberList animVal appendItem"); - assert(errorName(() => SVGLengthList.prototype.clear.call({})) === "TypeError", - "SVGLengthList receiver brand"); - return "ok"; - })() - "#, - ) - .expect("SVG value list semantics probe should evaluate"); - - assert_eq!(result, "ok"); -} - -#[test] -fn svg_point_lists_are_live_mutable_and_clear_invalid_content() { - let mut vm = new_storage_test_vm("https://svg-point-list-semantics.test/"); - - let result = vm - .eval( - r#" - (() => { - const assert = (condition, message) => { - if (!condition) throw new Error(message); - }; - const errorName = callback => { - try { - callback(); - return "none"; - } catch (error) { - return error.name; - } - }; - const ns = "http://www.w3.org/2000/svg"; - const polygon = document.createElementNS(ns, "polygon"); - const polyline = document.createElementNS(ns, "polyline"); - const svg = document.createElementNS(ns, "svg"); - - assert(typeof SVGPointList === "function", "constructor exposed"); - assert(errorName(() => new SVGPointList()) === "TypeError", - "illegal constructor"); - for (const [constructor, element] of [ - [SVGPolygonElement, polygon], - [SVGPolylineElement, polyline], - ]) { - const pointsDescriptor = Object.getOwnPropertyDescriptor( - constructor.prototype, - "points", - ); - const animatedDescriptor = Object.getOwnPropertyDescriptor( - constructor.prototype, - "animatedPoints", - ); - assert(typeof pointsDescriptor.get === "function" && - pointsDescriptor.set === undefined, `${constructor.name}.points descriptor`); - assert(typeof animatedDescriptor.get === "function" && - animatedDescriptor.set === undefined, - `${constructor.name}.animatedPoints descriptor`); - assert(pointsDescriptor.enumerable && pointsDescriptor.configurable && - animatedDescriptor.enumerable && animatedDescriptor.configurable, - `${constructor.name} descriptor flags`); - assert(element.points instanceof SVGPointList && - element.animatedPoints instanceof SVGPointList, - `${constructor.name} point list interfaces`); - assert(element.points === element.points && - element.animatedPoints === element.animatedPoints && - element.points !== element.animatedPoints, - `${constructor.name} SameObject lists`); - } - - polygon.setAttribute("points", "0,0 100,0 100,100 0,100"); - const points = polygon.points; - const animatedPoints = polygon.animatedPoints; - assert(Object.prototype.toString.call(points) === "[object SVGPointList]", - "point list tag"); - assert(points.length === 4 && points.numberOfItems === 4, - "valid content points"); - assert(points.getItem(1) === points[1] && points[1] instanceof DOMPoint && - points[1] instanceof SVGPoint && points[1].x === 100 && points[1].y === 0, - "indexed point identity and values"); - - polygon.setAttribute("points", "0,0 100,0 INVALID"); - assert(points.numberOfItems === 0, - "invalid token clears the whole point list"); - polygon.setAttribute("points", "0,0 100,0 20"); - assert(points.numberOfItems === 2, - "missing final y coordinate truncates the point list"); - polygon.setAttribute("points", "0,0 100,0 20,"); - assert(points.numberOfItems === 2, - "trailing comma with missing y truncates the point list"); - - polygon.setAttribute("points", "0,0 10,20"); - const first = points[0]; - first.x = 2; - assert(polygon.getAttribute("points") === "2 0 10 20", - "point coordinate mutation reflects to content"); - - const point = svg.createSVGPoint(); - point.x = 5; - point.y = 6; - points.clear(); - assert(points.length === 0 && polygon.getAttribute("points") === "", - "clear reflects an empty list"); - assert(points.initialize(point) === point && points[0] === point, - "initialize keeps point identity"); - assert(polygon.getAttribute("points") === "5 6", "initialize reflection"); - point.x = 7; - assert(polygon.getAttribute("points") === "7 6", "owned point stays live"); - - const second = svg.createSVGPoint(); - second.x = 8; - second.y = 9; - assert(points.appendItem(second) === second && points.length === 2, - "appendItem"); - const third = svg.createSVGPoint(); - third.x = 10; - third.y = 11; - assert(points.insertItemBefore(third, 1) === third && points.length === 3, - "insertItemBefore"); - const replacement = svg.createSVGPoint(); - replacement.x = 12; - replacement.y = 13; - assert(points.replaceItem(replacement, 0) === replacement && - points[0] === replacement, - "replaceItem"); - assert(points.removeItem(1) === third && points.length === 2, - "removeItem"); - points[0] = point; - assert(points[0] === point, "indexed setter"); - - for (const invalid of [1, "point", polygon, null]) { - assert(errorName(() => points.appendItem(invalid)) === "TypeError", - "point item type enforcement"); - } - assert(errorName(() => points.getItem(99)) === "IndexSizeError", - "getItem bounds"); - - polygon.setAttribute("points", "1,2 3,4"); - assert(animatedPoints.length === 2 && animatedPoints[1].x === 3, - "animatedPoints live synchronization"); - assert(errorName(() => animatedPoints.clear()) === "NoModificationAllowedError", - "animatedPoints list is read-only"); - assert(errorName(() => { animatedPoints[0].x = 9; }) === - "NoModificationAllowedError", "animated point is read-only"); - assert(errorName(() => SVGPointList.prototype.clear.call({})) === "TypeError", - "point list receiver brand"); - - const pointsDescriptor = Object.getOwnPropertyDescriptor( - SVGPolygonElement.prototype, - "points", - ); - assert(errorName(() => pointsDescriptor.get.call(svg)) === "TypeError", - "animated points receiver brand"); - return "ok"; - })() - "#, - ) - .expect("SVG point list semantics probe should evaluate"); - - assert_eq!(result, "ok"); -} - #[test] fn element_heading_reflections_drive_flat_tree_heading_matching() { let mut vm = new_storage_test_vm("https://heading-offset.test/"); 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 1fb003a62b..a995f60c05 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 @@ -1,113 +1,99 @@ use super::*; #[test] -#[ignore = "requires native DOM attribute UTF-16 storage migration"] -fn svg_animated_string_attributes_preserve_lone_surrogates() { - let mut vm = new_parsed_test_vm("https://svg-utf16.test/", ""); +fn svg_animation_elements_use_native_interfaces_and_event_handlers() { + let mut vm = new_parsed_test_vm( + "https://svg-animation.test/", + "", + ); + vm.eval(include_str!("svg_animation_interfaces.js")) + .unwrap(); assert_eq!( - vm.eval(r#"(() => { - const value = 'before\uD800after'; - for (const [tag, property, attribute] of [['feTile', 'in1', 'in'], ['mpath', 'href', 'href']]) { - const element = document.createElementNS('http://www.w3.org/2000/svg', tag); - const animated = element[property]; - animated.baseVal = value; - if (animated.baseVal !== value || animated.animVal !== value || element.getAttribute(attribute) !== value) return false; + vm.eval("JSON.stringify(__nodeReplacementResults.failures)") + .unwrap(), + "[]" + ); +} + +#[test] +fn svg_animation_time_controls_convert_arguments_without_a_fragment() { + let mut vm = new_parsed_test_vm( + "https://svg-animation-timing.test/", + "", + ); + assert_eq!(vm.eval(r#" + (() => { + const other = document.getElementById('child').contentWindow; + for (const realm of [window, other]) { + const element = document.createElementNS('http://www.w3.org/2000/svg', 'animate'); + for (const name of ['beginElement', 'endElement', 'beginElementAt', 'endElementAt']) { + let conversions = 0; + const method = realm.SVGAnimationElement.prototype[name]; + const result = method.call(element, {valueOf() {conversions++; return 0.25;}}); + if (result !== undefined) throw Error(name + ' detached return value'); + if (conversions !== (name.endsWith('At') ? 1 : 0)) throw Error(name + ' argument conversion'); + if (element.getCurrentTime() !== 0) throw Error('detached animation cannot advance the timeline'); + if (name.endsWith('At')) { + for (const value of [NaN, Infinity, -Infinity, 1e100, Symbol(), 1n]) { + let error; + try { method.call(element, value); } catch (caught) { error = caught; } + if (Object.getPrototypeOf(error) !== realm.TypeError.prototype) throw Error(name + ' invalid float realm'); } - return true; - })()"#).unwrap(), + let error; + try { method.call(element); } catch (caught) { error = caught; } + if (Object.getPrototypeOf(error) !== realm.TypeError.prototype) throw Error(name + ' required argument'); + const sentinel = {}; + try { method.call(element, {valueOf() {throw sentinel;}}); } catch (caught) { error = caught; } + if (error !== sentinel || element.getCurrentTime() !== 0) throw Error(name + ' original conversion exception'); + } + } + } + return true; + })() + "#).unwrap(), "true"); +} + +#[test] +fn namespaced_attribute_values_preserve_utf16_and_namespace_identity() { + let mut vm = new_parsed_test_vm("https://namespace-utf16.test/", ""); + assert_eq!( + vm.eval(include_str!("namespace_attribute_string_values.js")) + .expect("namespaced attribute string fixture should evaluate"), "true" ); } #[test] -fn svg_animated_lengths_reflect_initial_and_content_attribute_values() { - let mut vm = new_parsed_test_vm( - "https://svg-animated-length-reflection.test/", - "", +fn svg_switch_and_mpath_use_native_interfaces_and_live_href() { + let mut vm = new_parsed_test_vm("https://svg-switch-mpath.test/", ""); + assert_eq!( + vm.eval(include_str!("svg_switch_mpath.js")) + .expect("SVG switch and mpath fixture should evaluate"), + "true" ); +} - 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"]]], - ]; +#[test] +fn element_attribute_values_preserve_utf16_across_native_documents() { + let mut vm = new_parsed_test_vm( + "https://attribute-string.test/", + "", + ); + assert_eq!( + vm.eval(include_str!("attribute_string_values.js")) + .expect("attribute string fixture should evaluate"), + "true" + ); +} - 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_animated_values_follow_native_attribute_mutations() { + let mut vm = new_parsed_test_vm("https://svg-values.test/", ""); + assert_eq!( + vm.eval(include_str!("svg_animated_values.js")) + .expect("SVG animated value fixture should evaluate"), + "true" + ); } #[test] @@ -2390,1767 +2376,19 @@ fn element_methods_and_dataset_live_on_owner_prototypes() { } #[test] -fn svg_historical_interfaces_keep_current_element_constructors_only() { - let mut vm = new_parsed_test_vm( - "https://svg-historical-interfaces.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 interfaces = [ - ["clipPath", "SVGClipPathElement"], - ["filter", "SVGFilterElement"], - ["mask", "SVGMaskElement"], - ["view", "SVGViewElement"], - ]; - const removedUnitTypeConstants = [ - "SVG_UNIT_TYPE_UNKNOWN", - "SVG_UNIT_TYPE_USERSPACEONUSE", - "SVG_UNIT_TYPE_OBJECTBOUNDINGBOX", - ]; - - for (const [localName, interfaceName] of interfaces) { - const constructor = globalThis[interfaceName]; - assert(typeof constructor === "function", `${interfaceName} constructor`); - assert(Object.getPrototypeOf(constructor.prototype) === SVGElement.prototype, - `${interfaceName} prototype parent`); - assert(document.createElementNS(ns, localName) instanceof constructor, - `${localName} interface`); - for (const constant of removedUnitTypeConstants) { - assert(!(constant in constructor), `${interfaceName}.${constant} removed`); - } - } - - assert(!("viewTarget" in SVGViewElement.prototype), - "SVGViewElement.prototype.viewTarget removed"); - return "ok"; - })() - "#, - ) - .expect("SVG historical interface probe should evaluate"); - - assert_eq!(result, "ok"); -} - -#[test] -fn svg_animated_enumerations_reflect_typed_content_attributes() { - let mut vm = new_parsed_test_vm( - "https://svg-animated-enumerations.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 unitUser = SVGUnitTypes.SVG_UNIT_TYPE_USERSPACEONUSE; - const unitBox = SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; - const cases = [ - ["clipPath", "SVGClipPathElement", "clipPathUnits", unitUser, unitBox, "objectBoundingBox", 3], - ["filter", "SVGFilterElement", "filterUnits", unitBox, unitUser, "userSpaceOnUse", 3], - ["filter", "SVGFilterElement", "primitiveUnits", unitUser, unitBox, "objectBoundingBox", 3], - ["linearGradient", "SVGGradientElement", "gradientUnits", unitBox, unitUser, "userSpaceOnUse", 3], - ["linearGradient", "SVGGradientElement", "spreadMethod", SVGGradientElement.SVG_SPREADMETHOD_PAD, SVGGradientElement.SVG_SPREADMETHOD_REPEAT, "repeat", 4], - ["mask", "SVGMaskElement", "maskUnits", unitBox, unitUser, "userSpaceOnUse", 3], - ["mask", "SVGMaskElement", "maskContentUnits", unitUser, unitBox, "objectBoundingBox", 3], - ["pattern", "SVGPatternElement", "patternUnits", unitBox, unitUser, "userSpaceOnUse", 3], - ["pattern", "SVGPatternElement", "patternContentUnits", unitUser, unitBox, "objectBoundingBox", 3], - ["text", "SVGTextContentElement", "lengthAdjust", SVGTextContentElement.LENGTHADJUST_SPACING, SVGTextContentElement.LENGTHADJUST_SPACINGANDGLYPHS, "spacingAndGlyphs", 3], - ]; - - assert(typeof SVGUnitTypes === "function", "SVGUnitTypes interface"); - for (const [name, value] of [ - ["SVG_UNIT_TYPE_UNKNOWN", 0], - ["SVG_UNIT_TYPE_USERSPACEONUSE", 1], - ["SVG_UNIT_TYPE_OBJECTBOUNDINGBOX", 2], - ]) { - assert(SVGUnitTypes[name] === value, `SVGUnitTypes.${name}`); - assert(SVGUnitTypes.prototype[name] === value, `SVGUnitTypes.prototype.${name}`); - } - let illegalConstructor = false; - try { - new SVGUnitTypes(); - } catch (error) { - illegalConstructor = error instanceof TypeError; - } - assert(illegalConstructor, "SVGUnitTypes illegal constructor"); - - for (const [tag, interfaceName, property, initial, alternate, serialized, invalid] of cases) { - const element = document.createElementNS(ns, tag); +fn svg_animated_string_attributes_preserve_lone_surrogates() { + let mut vm = new_parsed_test_vm("https://svg-utf16.test/", ""); + assert_eq!( + vm.eval(r#"(() => { + const value = 'before\uD800after'; + for (const [tag, property, attribute] of [['feTile', 'in1', 'in'], ['mpath', 'href', 'href']]) { + const element = document.createElementNS('http://www.w3.org/2000/svg', tag); const animated = element[property]; - assert(animated instanceof SVGAnimatedEnumeration, `${property} interface`); - assert(element[property] === animated, `${property} SameObject`); - assert(animated.baseVal === initial && animated.animVal === initial, - `${property} initial value`); - - element.setAttribute(property, serialized); - assert(animated.baseVal === alternate && animated.animVal === alternate, - `${property} content attribute update`); - element.setAttribute(property, "invalid"); - assert(animated.baseVal === initial, `${property} invalid content attribute`); - element.removeAttribute(property); - assert(animated.baseVal === initial, `${property} removed content attribute`); - - animated.baseVal = alternate; - assert(element.getAttribute(property) === serialized, `${property} reflected value`); - assert(animated.baseVal === alternate && animated.animVal === alternate, - `${property} reflected enumeration`); - let rejected = false; - try { - animated.baseVal = invalid; - } catch (error) { - rejected = error instanceof TypeError; - } - assert(rejected, `${property} invalid IDL value`); - assert(animated.baseVal === alternate, `${property} preserved after rejection`); - - const owner = globalThis[interfaceName].prototype; - const descriptor = Object.getOwnPropertyDescriptor(owner, property); - assert(typeof descriptor.get === "function", `${interfaceName}.${property} getter`); - assert(descriptor.enumerable && descriptor.configurable, - `${interfaceName}.${property} flags`); - } - return "ok"; - })() - "#, - ) - .expect("SVG animated enumeration probe should evaluate"); - - assert_eq!(result, "ok"); -} - -#[test] -fn svg_animated_boolean_reflects_preserve_alpha() { - let mut vm = new_parsed_test_vm( - "https://svg-animated-boolean.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 element = document.createElementNS(ns, "feConvolveMatrix"); - - assert(typeof SVGAnimatedBoolean === "function", "animated boolean constructor"); - assert(typeof SVGFEConvolveMatrixElement === "function", "element constructor"); - assert(element instanceof SVGFEConvolveMatrixElement, "element interface"); - assert(Object.getPrototypeOf(SVGFEConvolveMatrixElement.prototype) === SVGElement.prototype, - "element prototype parent"); - - for (const constructor of [SVGAnimatedBoolean, SVGFEConvolveMatrixElement]) { - let illegalConstructor = false; - try { - new constructor(); - } catch (error) { - illegalConstructor = error instanceof TypeError; - } - assert(illegalConstructor, `${constructor.name} illegal constructor`); - } - - const elementDescriptor = Object.getOwnPropertyDescriptor( - SVGFEConvolveMatrixElement.prototype, - "preserveAlpha", - ); - assert(typeof elementDescriptor.get === "function", "preserveAlpha getter"); - assert(elementDescriptor.set === undefined, "preserveAlpha readonly"); - assert(elementDescriptor.enumerable && elementDescriptor.configurable, - "preserveAlpha descriptor flags"); - - const animated = element.preserveAlpha; - assert(animated instanceof SVGAnimatedBoolean, "animated boolean interface"); - assert(Object.prototype.toString.call(animated) === "[object SVGAnimatedBoolean]", - "animated boolean tag"); - assert(element.preserveAlpha === animated, "preserveAlpha SameObject"); - assert(!element.hasAttribute("preserveAlpha"), "getter does not create attribute"); - assert(animated.baseVal === false && animated.animVal === false, "initial value"); - - const baseDescriptor = Object.getOwnPropertyDescriptor( - SVGAnimatedBoolean.prototype, - "baseVal", - ); - const animDescriptor = Object.getOwnPropertyDescriptor( - SVGAnimatedBoolean.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 boolean descriptor flags"); - - element.setAttribute("preserveAlpha", "true"); - assert(animated.baseVal === true && animated.animVal === true, - "true content attribute"); - element.setAttribute("preserveAlpha", "false"); - assert(animated.baseVal === false && animated.animVal === false, - "false content attribute"); - element.setAttribute("preserveAlpha", "TRUE"); - assert(animated.baseVal === false && animated.animVal === false, - "invalid content attribute uses initial value"); - element.removeAttribute("preserveAlpha"); - assert(animated.baseVal === false && animated.animVal === false, - "removed content attribute uses initial value"); - - animated.baseVal = true; - assert(element.getAttribute("preserveAlpha") === "true", "true reflection"); - assert(animated.baseVal === true && animated.animVal === true, - "true reflected values"); - animated.baseVal = null; - assert(element.getAttribute("preserveAlpha") === "false", "false reflection"); - assert(animated.baseVal === false && animated.animVal === false, - "false reflected values"); - animated.baseVal = {}; - assert(element.getAttribute("preserveAlpha") === "true", "WebIDL ToBoolean"); - - let incompatibleAnimatedReceiver = false; - try { - baseDescriptor.get.call({}); - } catch (error) { - incompatibleAnimatedReceiver = error instanceof TypeError; - } - assert(incompatibleAnimatedReceiver, "animated boolean receiver brand"); - - let incompatibleElementReceiver = false; - try { - elementDescriptor.get.call(document.createElementNS(ns, "rect")); - } catch (error) { - incompatibleElementReceiver = error instanceof TypeError; - } - assert(incompatibleElementReceiver, "element receiver brand"); - return "ok"; - })() - "#, - ) - .expect("SVG animated boolean probe should evaluate"); - - assert_eq!(result, "ok"); -} - -#[test] -fn svg_string_lists_reflect_conditional_processing_attributes() { - let mut vm = new_parsed_test_vm( - "https://svg-string-list.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 text = document.createElementNS(ns, "text"); - - assert(typeof SVGStringList === "function", "SVGStringList constructor"); - let illegalConstructor = false; - try { - new SVGStringList(); - } catch (error) { - illegalConstructor = error instanceof TypeError; - } - assert(illegalConstructor, "SVGStringList illegal constructor"); - - for (const name of ["requiredExtensions", "systemLanguage"]) { - const descriptor = Object.getOwnPropertyDescriptor( - SVGGraphicsElement.prototype, - name, - ); - assert(typeof descriptor.get === "function" && descriptor.set === undefined, - `${name} readonly descriptor`); - assert(descriptor.enumerable && descriptor.configurable, `${name} flags`); - } - for (const name of ["length", "numberOfItems"]) { - const descriptor = Object.getOwnPropertyDescriptor(SVGStringList.prototype, name); - assert(typeof descriptor.get === "function" && descriptor.set === undefined, - `${name} descriptor`); - assert(descriptor.enumerable && descriptor.configurable, `${name} flags`); - } - const methodLengths = { - clear: 0, - initialize: 1, - getItem: 1, - insertItemBefore: 2, - replaceItem: 2, - removeItem: 1, - appendItem: 1, - }; - for (const [name, length] of Object.entries(methodLengths)) { - const descriptor = Object.getOwnPropertyDescriptor(SVGStringList.prototype, name); - assert(typeof descriptor.value === "function", `${name} method`); - assert(descriptor.value.length === length, `${name} arity`); - assert(descriptor.enumerable && descriptor.configurable && descriptor.writable, - `${name} flags`); - } - - const languages = text.systemLanguage; - assert(languages instanceof SVGStringList, "systemLanguage interface"); - assert(Object.prototype.toString.call(languages) === "[object SVGStringList]", - "SVGStringList tag"); - assert(text.systemLanguage === languages, "systemLanguage SameObject"); - assert(languages.length === 0 && languages.numberOfItems === 0, - "absent attribute empty list"); - assert(!text.hasAttribute("systemLanguage"), "getter does not create attribute"); - - const parsingCases = [ - ["en,fr,de", ["en", "fr", "de"]], - ["en, fr, de", ["en", "fr", "de"]], - ["en ,fr ,de", ["en", "fr", "de"]], - ["en , fr , de", ["en", "fr", "de"]], - [" en, fr ", ["en", "fr"]], - [" \t\nen, fr\t\n ", ["en", "fr"]], - ["en", ["en"]], - ["en-US, zh-Hans, pt-BR", ["en-US", "zh-Hans", "pt-BR"]], - ["en,,fr", ["en", "", "fr"]], - ["", [""]], - [",", ["", ""]], - ["123, 456", ["123", "456"]], - ["not-a-lang, ???, @#$", ["not-a-lang", "???", "@#$"]], - ]; - for (const [raw, expected] of parsingCases) { - text.setAttribute("systemLanguage", raw); - assert(text.systemLanguage === languages, `SameObject after ${raw}`); - assert(languages.length === expected.length, `length for ${raw}`); - assert(languages.numberOfItems === expected.length, - `numberOfItems for ${raw}`); - assert(Object.keys(languages).join() === expected.map((_, index) => index).join(), - `supported indices for ${raw}`); - for (let index = 0; index < expected.length; index++) { - assert(languages.getItem(index) === expected[index], - `getItem ${index} for ${raw}`); - assert(languages[index] === expected[index], `index ${index} for ${raw}`); - } - } - - text.removeAttribute("systemLanguage"); - assert(languages.length === 0, "removed attribute empty list"); - const extensions = text.requiredExtensions; - assert(text.requiredExtensions === extensions, "requiredExtensions SameObject"); - text.setAttribute("requiredExtensions", " one\t two\nthree "); - assert(extensions.length === 3 && extensions[0] === "one" && - extensions[1] === "two" && extensions[2] === "three", - "requiredExtensions space-separated parsing"); - - assert(languages.initialize("en") === "en", "initialize return"); - assert(text.getAttribute("systemLanguage") === "en", "initialize reflection"); - assert(languages.appendItem("fr") === "fr", "append return"); - assert(text.getAttribute("systemLanguage") === "en,fr", "append reflection"); - assert(languages.insertItemBefore("de", 1) === "de", "insert return"); - assert(text.getAttribute("systemLanguage") === "en,de,fr", "insert reflection"); - assert(languages.insertItemBefore("it", 99) === "it", "clamped insert return"); - assert(text.getAttribute("systemLanguage") === "en,de,fr,it", - "clamped insert reflection"); - assert(languages.replaceItem("zh", 1) === "zh", "replace return"); - assert(text.getAttribute("systemLanguage") === "en,zh,fr,it", - "replace reflection"); - languages[2] = "pt-BR"; - assert(text.getAttribute("systemLanguage") === "en,zh,pt-BR,it", - "indexed setter reflection"); - Object.defineProperty(languages, "0", {value: "es"}); - assert(text.getAttribute("systemLanguage") === "es,zh,pt-BR,it", - "indexed definer reflection"); - const indexDescriptor = Object.getOwnPropertyDescriptor(languages, "0"); - assert(indexDescriptor.value === "es" && indexDescriptor.writable && - indexDescriptor.enumerable && indexDescriptor.configurable, - "indexed property descriptor"); - assert(delete languages[0] === false, "supported index cannot be deleted"); - assert(languages.removeItem(1) === "zh", "remove return"); - assert(text.getAttribute("systemLanguage") === "es,pt-BR,it", - "remove reflection"); - languages.clear(); - assert(text.getAttribute("systemLanguage") === "" && languages.length === 0, - "clear reflection"); - - extensions.initialize("alpha"); - extensions.appendItem("beta"); - assert(text.getAttribute("requiredExtensions") === "alpha beta", - "requiredExtensions space serialization"); - assert(extensions.appendItem(null) === "null", "DOMString conversion"); - assert(text.getAttribute("requiredExtensions") === "alpha beta null", - "converted string reflection"); - - text.setAttribute("systemLanguage", "en,fr"); - for (const operation of [ - () => languages.getItem(9), - () => languages.replaceItem("x", 9), - () => languages.removeItem(9), - () => { languages[9] = "x"; }, - ]) { - let indexError = false; - try { - operation(); - } catch (error) { - indexError = error instanceof DOMException && error.name === "IndexSizeError"; - } - assert(indexError, "out-of-range operation"); - } - - let incompatibleListReceiver = false; - try { - SVGStringList.prototype.getItem.call({}, 0); - } catch (error) { - incompatibleListReceiver = error instanceof TypeError; - } - assert(incompatibleListReceiver, "SVGStringList receiver brand"); - - const systemLanguageGetter = Object.getOwnPropertyDescriptor( - SVGGraphicsElement.prototype, - "systemLanguage", - ).get; - let incompatibleElementReceiver = false; - try { - systemLanguageGetter.call(document.createElementNS(ns, "filter")); - } catch (error) { - incompatibleElementReceiver = error instanceof TypeError; - } - assert(incompatibleElementReceiver, "SVGGraphicsElement receiver brand"); - return "ok"; - })() - "##, - ) - .expect("SVG string list probe should evaluate"); - - assert_eq!(result, "ok"); -} - -#[test] -fn svg_marker_orient_angle_is_a_live_animated_angle() { - let mut vm = new_parsed_test_vm( - "https://svg-animated-angle.test/", - "", - ); - - let result = vm - .eval( - r#" - (() => { - const assert = (condition, message) => { - if (!condition) throw new Error(message); - }; - const close = (actual, expected) => Math.abs(actual - expected) < 1e-9; - const ns = "http://www.w3.org/2000/svg"; - const marker = document.createElementNS(ns, "marker"); - - for (const constructor of [SVGMarkerElement, SVGAnimatedAngle, SVGAngle]) { - assert(typeof constructor === "function", `${constructor.name} constructor`); - let illegalConstructor = false; - try { - new constructor(); - } catch (error) { - illegalConstructor = error instanceof TypeError; - } - assert(illegalConstructor, `${constructor.name} illegal constructor`); - } - assert(marker instanceof SVGMarkerElement, "marker interface"); - assert(Object.getPrototypeOf(SVGMarkerElement.prototype) === SVGElement.prototype, - "marker prototype parent"); - - const markerDescriptor = Object.getOwnPropertyDescriptor( - SVGMarkerElement.prototype, - "orientAngle", - ); - assert(typeof markerDescriptor.get === "function" && - markerDescriptor.set === undefined, "orientAngle readonly descriptor"); - assert(markerDescriptor.enumerable && markerDescriptor.configurable, - "orientAngle descriptor flags"); - - const animated = marker.orientAngle; - const base = animated.baseVal; - const anim = animated.animVal; - assert(animated instanceof SVGAnimatedAngle, "animated angle interface"); - assert(base instanceof SVGAngle && anim instanceof SVGAngle, "angle interfaces"); - assert(base !== anim, "base and animated values are distinct"); - assert(marker.orientAngle === animated, "orientAngle SameObject"); - assert(base.value === 0 && anim.value === 0, "initial values"); - assert(base.unitType === SVGAngle.SVG_ANGLETYPE_UNSPECIFIED, "initial unit"); - assert(!marker.hasAttribute("orient"), "getter does not create attribute"); - assert(marker.orientType instanceof SVGAnimatedEnumeration, "orientType interface"); - assert(marker.orientType.baseVal === SVGMarkerElement.SVG_MARKER_ORIENT_ANGLE, - "initial orientType"); - assert(marker.markerUnits instanceof SVGAnimatedEnumeration, - "markerUnits interface"); - assert(marker.markerUnits.baseVal === - SVGMarkerElement.SVG_MARKERUNITS_STROKEWIDTH, "initial markerUnits"); - marker.markerUnits.baseVal = SVGMarkerElement.SVG_MARKERUNITS_USERSPACEONUSE; - assert(marker.getAttribute("markerUnits") === "userSpaceOnUse", - "markerUnits reflection"); - - base.value = 100; - assert(base.value === 100 && marker.orientAngle.baseVal.value === 100, - "cached base value is live"); - assert(marker.getAttribute("orient") === "100", "value reflects to orient"); - marker.orientAngle.baseVal = -1; - assert(marker.orientAngle.baseVal === base && base.value === 100, - "baseVal assignment is ignored"); - - marker.setAttribute("orient", "1.5707963267948966rad"); - assert(close(base.value, 90), "content attribute converts to degrees"); - assert(base.unitType === SVGAngle.SVG_ANGLETYPE_RAD, "content attribute unit"); - assert(close(base.valueInSpecifiedUnits, Math.PI / 2), "specified value"); - assert(anim.unitType === SVGAngle.SVG_ANGLETYPE_RAD && close(anim.value, 90), - "animVal tracks content attribute"); - assert(marker.orientType.baseVal === SVGMarkerElement.SVG_MARKER_ORIENT_ANGLE, - "angle orientType"); - - marker.setOrientToAuto(); - assert(marker.getAttribute("orient") === "auto", "setOrientToAuto reflection"); - assert(marker.orientType.baseVal === SVGMarkerElement.SVG_MARKER_ORIENT_AUTO, - "automatic orientType"); - assert(base.value === 0 && base.unitType === SVGAngle.SVG_ANGLETYPE_UNSPECIFIED, - "automatic orient angle"); - - marker.setAttribute("orient", "400grad"); - assert(base.value === 360 && base.unitType === SVGAngle.SVG_ANGLETYPE_GRAD, - "grad content attribute"); - - const svg = document.createElementNS(ns, "svg"); - const standalone = svg.createSVGAngle(); - assert(standalone instanceof SVGAngle, "createSVGAngle result"); - standalone.newValueSpecifiedUnits(SVGAngle.SVG_ANGLETYPE_RAD, Math.PI); - assert(close(standalone.value, 180), "newValueSpecifiedUnits conversion"); - standalone.convertToSpecifiedUnits(SVGAngle.SVG_ANGLETYPE_GRAD); - assert(close(standalone.valueInSpecifiedUnits, 200), "unit conversion"); - assert(standalone.valueAsString === "200grad", "unit serialization"); - marker.setOrientToAngle(standalone); - assert(marker.getAttribute("orient") === "200grad", "setOrientToAngle reflection"); - assert(marker.orientAngle.baseVal.value === 180, "setOrientToAngle value"); - - let readonlyAnim = false; - try { - anim.value = 1; - } catch (error) { - readonlyAnim = error.name === "NoModificationAllowedError"; - } - assert(readonlyAnim, "animVal is read-only"); - - let incompatibleReceiver = false; - try { - markerDescriptor.get.call(document.createElementNS(ns, "rect")); - } catch (error) { - incompatibleReceiver = error instanceof TypeError; - } - assert(incompatibleReceiver, "marker receiver brand"); - return "ok"; - })() - "#, - ) - .expect("SVG animated angle probe should evaluate"); - - assert_eq!(result, "ok"); -} - -#[test] -fn svg_filter_and_text_path_enumerations_reflect_typed_content_attributes() { - let mut vm = new_parsed_test_vm( - "https://svg-filter-enumerations.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 interfaceCases = [ - ["feBlend", SVGFEBlendElement, SVGElement], - ["feColorMatrix", SVGFEColorMatrixElement, SVGElement], - ["feComposite", SVGFECompositeElement, SVGElement], - ["feConvolveMatrix", SVGFEConvolveMatrixElement, SVGElement], - ["feDisplacementMap", SVGFEDisplacementMapElement, SVGElement], - ["feMorphology", SVGFEMorphologyElement, SVGElement], - ["feTurbulence", SVGFETurbulenceElement, SVGElement], - ["feFuncA", SVGFEFuncAElement, SVGComponentTransferFunctionElement], - ["feFuncB", SVGFEFuncBElement, SVGComponentTransferFunctionElement], - ["feFuncG", SVGFEFuncGElement, SVGComponentTransferFunctionElement], - ["feFuncR", SVGFEFuncRElement, SVGComponentTransferFunctionElement], - ["textPath", SVGTextPathElement, SVGTextContentElement], - ]; - - for (const [tag, constructor, parent] of interfaceCases) { - const element = document.createElementNS(ns, tag); - assert(element instanceof constructor, `${tag} interface`); - assert(Object.getPrototypeOf(constructor.prototype) === parent.prototype, - `${constructor.name} parent`); - let illegalConstructor = false; - try { - new constructor(); - } catch (error) { - illegalConstructor = error instanceof TypeError; - } - assert(illegalConstructor, `${constructor.name} illegal constructor`); - } - - const constantGroups = [ - [SVGComponentTransferFunctionElement, [ - ["SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN", 0], - ["SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY", 1], - ["SVG_FECOMPONENTTRANSFER_TYPE_TABLE", 2], - ["SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE", 3], - ["SVG_FECOMPONENTTRANSFER_TYPE_LINEAR", 4], - ["SVG_FECOMPONENTTRANSFER_TYPE_GAMMA", 5], - ]], - [SVGFEBlendElement, [ - ["SVG_FEBLEND_MODE_UNKNOWN", 0], - ["SVG_FEBLEND_MODE_NORMAL", 1], - ["SVG_FEBLEND_MODE_MULTIPLY", 2], - ["SVG_FEBLEND_MODE_SCREEN", 3], - ["SVG_FEBLEND_MODE_DARKEN", 4], - ["SVG_FEBLEND_MODE_LIGHTEN", 5], - ["SVG_FEBLEND_MODE_OVERLAY", 6], - ["SVG_FEBLEND_MODE_COLOR_DODGE", 7], - ["SVG_FEBLEND_MODE_COLOR_BURN", 8], - ["SVG_FEBLEND_MODE_HARD_LIGHT", 9], - ["SVG_FEBLEND_MODE_SOFT_LIGHT", 10], - ["SVG_FEBLEND_MODE_DIFFERENCE", 11], - ["SVG_FEBLEND_MODE_EXCLUSION", 12], - ["SVG_FEBLEND_MODE_HUE", 13], - ["SVG_FEBLEND_MODE_SATURATION", 14], - ["SVG_FEBLEND_MODE_COLOR", 15], - ["SVG_FEBLEND_MODE_LUMINOSITY", 16], - ]], - [SVGFEColorMatrixElement, [ - ["SVG_FECOLORMATRIX_TYPE_UNKNOWN", 0], - ["SVG_FECOLORMATRIX_TYPE_MATRIX", 1], - ["SVG_FECOLORMATRIX_TYPE_SATURATE", 2], - ["SVG_FECOLORMATRIX_TYPE_HUEROTATE", 3], - ["SVG_FECOLORMATRIX_TYPE_LUMINANCETOALPHA", 4], - ]], - [SVGFECompositeElement, [ - ["SVG_FECOMPOSITE_OPERATOR_UNKNOWN", 0], - ["SVG_FECOMPOSITE_OPERATOR_OVER", 1], - ["SVG_FECOMPOSITE_OPERATOR_IN", 2], - ["SVG_FECOMPOSITE_OPERATOR_OUT", 3], - ["SVG_FECOMPOSITE_OPERATOR_ATOP", 4], - ["SVG_FECOMPOSITE_OPERATOR_XOR", 5], - ["SVG_FECOMPOSITE_OPERATOR_LIGHTER", 6], - ["SVG_FECOMPOSITE_OPERATOR_ARITHMETIC", 7], - ]], - [SVGFEConvolveMatrixElement, [ - ["SVG_EDGEMODE_UNKNOWN", 0], - ["SVG_EDGEMODE_DUPLICATE", 1], - ["SVG_EDGEMODE_WRAP", 2], - ["SVG_EDGEMODE_NONE", 3], - ]], - [SVGFEDisplacementMapElement, [ - ["SVG_CHANNEL_UNKNOWN", 0], - ["SVG_CHANNEL_R", 1], - ["SVG_CHANNEL_G", 2], - ["SVG_CHANNEL_B", 3], - ["SVG_CHANNEL_A", 4], - ]], - [SVGFEMorphologyElement, [ - ["SVG_MORPHOLOGY_OPERATOR_UNKNOWN", 0], - ["SVG_MORPHOLOGY_OPERATOR_ERODE", 1], - ["SVG_MORPHOLOGY_OPERATOR_DILATE", 2], - ]], - [SVGFETurbulenceElement, [ - ["SVG_TURBULENCE_TYPE_UNKNOWN", 0], - ["SVG_TURBULENCE_TYPE_FRACTALNOISE", 1], - ["SVG_TURBULENCE_TYPE_TURBULENCE", 2], - ["SVG_STITCHTYPE_UNKNOWN", 0], - ["SVG_STITCHTYPE_STITCH", 1], - ["SVG_STITCHTYPE_NOSTITCH", 2], - ]], - [SVGTextPathElement, [ - ["TEXTPATH_METHODTYPE_UNKNOWN", 0], - ["TEXTPATH_METHODTYPE_ALIGN", 1], - ["TEXTPATH_METHODTYPE_STRETCH", 2], - ["TEXTPATH_SPACINGTYPE_UNKNOWN", 0], - ["TEXTPATH_SPACINGTYPE_AUTO", 1], - ["TEXTPATH_SPACINGTYPE_EXACT", 2], - ["TEXTPATH_SIDETYPE_UNKNOWN", 0], - ["TEXTPATH_SIDETYPE_LEFT", 1], - ["TEXTPATH_SIDETYPE_RIGHT", 2], - ]], - ]; - for (const [constructor, constants] of constantGroups) { - for (const [name, expected] of constants) { - assert(constructor[name] === expected, `${constructor.name}.${name}`); - assert(constructor.prototype[name] === expected, - `${constructor.name}.prototype.${name}`); - } - } - - const blend = document.createElementNS(ns, "feBlend"); - const blendModes = [ - ["normal", 1], ["multiply", 2], ["screen", 3], ["darken", 4], - ["lighten", 5], ["overlay", 6], ["color-dodge", 7], ["color-burn", 8], - ["hard-light", 9], ["soft-light", 10], ["difference", 11], - ["exclusion", 12], ["hue", 13], ["saturation", 14], ["color", 15], - ["luminosity", 16], - ]; - for (const [keyword, value] of blendModes) { - blend.setAttribute("mode", keyword); - assert(blend.mode.baseVal === value, `feBlend parses ${keyword}`); - blend.mode.baseVal = value; - assert(blend.getAttribute("mode") === keyword, `feBlend serializes ${keyword}`); - } - - const cases = [ - ["feFuncR", SVGComponentTransferFunctionElement, "type", 1, 5, "gamma", 6], - ["feBlend", SVGFEBlendElement, "mode", 1, 16, "luminosity", 17], - ["feColorMatrix", SVGFEColorMatrixElement, "type", 1, 4, "luminanceToAlpha", 5], - ["feComposite", SVGFECompositeElement, "operator", 1, 7, "arithmetic", 8], - ["feConvolveMatrix", SVGFEConvolveMatrixElement, "edgeMode", 1, 3, "none", 4], - ["feDisplacementMap", SVGFEDisplacementMapElement, "xChannelSelector", 4, 1, "R", 5], - ["feDisplacementMap", SVGFEDisplacementMapElement, "yChannelSelector", 4, 3, "B", 5], - ["feMorphology", SVGFEMorphologyElement, "operator", 1, 2, "dilate", 3], - ["feTurbulence", SVGFETurbulenceElement, "stitchTiles", 2, 1, "stitch", 3], - ["feTurbulence", SVGFETurbulenceElement, "type", 2, 1, "fractalNoise", 3], - ["textPath", SVGTextPathElement, "method", 1, 2, "stretch", 3], - ["textPath", SVGTextPathElement, "spacing", 2, 1, "auto", 3], - ["textPath", SVGTextPathElement, "side", 1, 2, "right", 3], - ]; - - for (const [tag, owner, property, initial, alternate, serialized, invalid] of cases) { - const element = document.createElementNS(ns, tag); - const animated = element[property]; - assert(animated instanceof SVGAnimatedEnumeration, `${tag}.${property} interface`); - assert(element[property] === animated, `${tag}.${property} SameObject`); - assert(animated.baseVal === initial && animated.animVal === initial, - `${tag}.${property} initial value`); - - element.setAttribute(property, serialized); - assert(animated.baseVal === alternate && animated.animVal === alternate, - `${tag}.${property} content attribute update`); - element.setAttribute(property, "invalid"); - assert(animated.baseVal === initial, `${tag}.${property} invalid content attribute`); - element.removeAttribute(property); - assert(animated.baseVal === initial, `${tag}.${property} removed content attribute`); - - animated.baseVal = alternate; - assert(element.getAttribute(property) === serialized, - `${tag}.${property} reflected value`); - let rejected = false; - try { - animated.baseVal = invalid; - } catch (error) { - rejected = error instanceof TypeError; - } - assert(rejected, `${tag}.${property} invalid IDL value`); - assert(animated.baseVal === alternate, - `${tag}.${property} preserved after rejection`); - - const descriptor = Object.getOwnPropertyDescriptor(owner.prototype, property); - assert(typeof descriptor.get === "function", `${owner.name}.${property} getter`); - assert(descriptor.enumerable && descriptor.configurable, - `${owner.name}.${property} flags`); - } - return "ok"; - })() - "#, - ) - .expect("SVG filter enumeration probe should evaluate"); - - 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_svg_element_value_factories_create_typed_objects() { - let mut vm = new_parsed_test_vm( - "https://svg-value-factories.test/", - "", - ); - - let result = vm - .eval( - r#" - (() => { - const assert = (condition, message) => { - if (!condition) throw new Error(message); - }; - const rejectsTypeError = callback => { - try { - callback(); - return false; - } catch (error) { - return error instanceof TypeError; - } - }; - const rejectsDom = (name, callback) => { - try { - callback(); - return false; - } catch (error) { - return error.name === name; - } - }; - const ns = "http://www.w3.org/2000/svg"; - const svg = document.createElementNS(ns, "svg"); - const wrongReceiver = document.createElementNS(ns, "rect"); - - const factoryNames = [ - "createSVGNumber", - "createSVGLength", - "createSVGAngle", - "createSVGPoint", - "createSVGMatrix", - "createSVGRect", - "createSVGTransform", - "createSVGTransformFromMatrix", - ]; - for (const name of factoryNames) { - const descriptor = Object.getOwnPropertyDescriptor(SVGSVGElement.prototype, name); - assert(typeof descriptor.value === "function", `${name} method`); - assert(descriptor.value.length === 0, `${name} arity`); - assert(descriptor.enumerable && descriptor.configurable && descriptor.writable, - `${name} descriptor flags`); - assert(rejectsTypeError(() => descriptor.value.call(wrongReceiver)), - `${name} receiver brand`); - } - - const number = svg.createSVGNumber(); - const length = svg.createSVGLength(); - const point = svg.createSVGPoint(); - const rect = svg.createSVGRect(); - assert(number instanceof SVGNumber && number.value === 0, "SVGNumber result"); - assert(length instanceof SVGLength && length.value === 0 && - length.unitType === SVGLength.SVG_LENGTHTYPE_NUMBER, "SVGLength result"); - assert(point instanceof DOMPoint && point instanceof SVGPoint, - "SVGPoint result interface"); - assert(point.x === 0 && point.y === 0 && point.z === 0 && point.w === 1, - "SVGPoint defaults"); - assert(rect instanceof SVGRect && !(rect instanceof DOMRect), - "SVGRect result interface"); - assert(rect.x === 0 && rect.y === 0 && rect.width === 0 && rect.height === 0, - "SVGRect defaults"); - assert(svg.createSVGNumber() !== number && svg.createSVGLength() !== length && - svg.createSVGPoint() !== point && svg.createSVGRect() !== rect, - "factories return new objects"); - - number.value = 2; - assert(number.value === 2, "SVGNumber assignment"); - assert(rejectsTypeError(() => { number.value = NaN; }), - "SVGNumber rejects NaN"); - assert(number.value === 2, "SVGNumber preserves rejected assignment"); - - length.valueAsString = "2px"; - assert(length.value === 2 && length.valueAsString === "2px", - "SVGLength assignment"); - assert(rejectsDom("NotSupportedError", () => { - length.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_UNKNOWN); - }), "SVGLength rejects unsupported unit"); - assert(rejectsDom("SyntaxError", () => { length.valueAsString = "10deg"; }), - "SVGLength rejects invalid syntax"); - assert(rejectsTypeError(() => { length.value = NaN; }), - "SVGLength rejects NaN"); - assert(length.value === 2 && length.valueAsString === "2px", - "SVGLength preserves rejected assignments"); - - point.x = 100; - point.y = 200; - assert(point.x === 100 && point.y === 200, "SVGPoint assignment"); - for (const invalid of [point, NaN, Infinity]) { - assert(rejectsTypeError(() => { point.x = invalid; }), - "SVGPoint rejects non-finite values"); - assert(point.x === 100, "SVGPoint preserves rejected assignment"); - } - point.y = null; - assert(point.y === 0, "SVGPoint converts null"); - - Object.assign(rect, { x: 100, y: 200, width: 300, height: 400 }); - assert(rect.x === 100 && rect.y === 200 && rect.width === 300 && - rect.height === 400, "SVGRect assignment"); - for (const [property, invalid] of [ - ["x", rect], - ["y", "aString"], - ["width", svg], - ["height", NaN], - ]) { - assert(rejectsTypeError(() => { rect[property] = invalid; }), - `SVGRect rejects invalid ${property}`); - } - rect.y = null; - assert(rect.y === 0, "SVGRect converts null"); - - const regularPoint = new DOMPoint(); - const regularRect = new DOMRect(); - regularPoint.x = NaN; - regularRect.x = Infinity; - assert(Number.isNaN(regularPoint.x) && regularRect.x === Infinity, - "regular geometry objects remain unrestricted"); - return "ok"; - })() - "#, - ) - .expect("SVG value factory probe should evaluate"); - - assert_eq!(result, "ok"); -} - -#[test] -fn svg_fit_to_view_box_attributes_are_live_and_reflected() { - let mut vm = new_parsed_test_vm( - "https://svg-fit-to-view-box.test/", - "", - ); - - let result = vm - .eval( - r#" - (() => { - const assert = (condition, message) => { - if (!condition) throw new Error(message); - }; - const throws = (callback, expected, message) => { - try { - callback(); - } catch (error) { - assert(error.name === expected, `${message}: ${error.name}`); - return; - } - throw new Error(`${message}: did not throw`); - }; - const accessor = (prototype, name) => { - const descriptor = Object.getOwnPropertyDescriptor(prototype, name); - assert(typeof descriptor?.get === "function", `${prototype.constructor.name}.${name} getter`); - assert(descriptor.set === undefined, `${prototype.constructor.name}.${name} setter`); - assert(descriptor.enumerable && descriptor.configurable, - `${prototype.constructor.name}.${name} flags`); - return descriptor; - }; - const ns = "http://www.w3.org/2000/svg"; - - for (const constructor of [ - SVGAnimatedRect, - SVGPreserveAspectRatio, - SVGAnimatedPreserveAspectRatio, - ]) { - assert(typeof constructor === "function", `${constructor.name} constructor`); - throws(() => new constructor(), "TypeError", `${constructor.name} illegal constructor`); - } - - const fitCases = [ - ["svg", SVGSVGElement], - ["symbol", SVGSymbolElement], - ["marker", SVGMarkerElement], - ["pattern", SVGPatternElement], - ["view", SVGViewElement], - ]; - for (const [localName, constructor] of fitCases) { - accessor(constructor.prototype, "viewBox"); - accessor(constructor.prototype, "preserveAspectRatio"); - const element = document.createElementNS(ns, localName); - const viewBox = element.viewBox; - const aspectRatio = element.preserveAspectRatio; - assert(viewBox instanceof SVGAnimatedRect, `${localName}.viewBox interface`); - assert(aspectRatio instanceof SVGAnimatedPreserveAspectRatio, - `${localName}.preserveAspectRatio interface`); - assert(element.viewBox === viewBox, `${localName}.viewBox SameObject`); - assert(element.preserveAspectRatio === aspectRatio, - `${localName}.preserveAspectRatio SameObject`); - } - - const imageDescriptor = accessor( - SVGImageElement.prototype, - "preserveAspectRatio", - ); - assert(!("viewBox" in SVGImageElement.prototype), "image has no viewBox"); - const image = document.createElementNS(ns, "image"); - assert(image.preserveAspectRatio instanceof SVGAnimatedPreserveAspectRatio, - "image preserveAspectRatio interface"); - assert(image.preserveAspectRatio === image.preserveAspectRatio, - "image preserveAspectRatio SameObject"); - - const svg = document.createElementNS(ns, "svg"); - const animatedRect = svg.viewBox; - const baseRect = animatedRect.baseVal; - const animRect = animatedRect.animVal; - assert(baseRect instanceof SVGRect && animRect instanceof SVGRect, - "viewBox rectangle interfaces"); - for (const rect of [baseRect, animRect]) { - assert(!(rect instanceof DOMRect), "viewBox uses native SVGRect, not DOMRect"); - assert(Object.getPrototypeOf(rect) === SVGRect.prototype, - "viewBox native rectangle prototype"); - assert(Object.prototype.toString.call(rect) === "[object SVGRect]", - "viewBox native rectangle tag"); - } - assert(baseRect !== animRect, "viewBox base and animated rectangles are distinct"); - assert(Object.prototype.toString.call(animatedRect) === "[object SVGAnimatedRect]", - "animated rectangle tag"); - assert(baseRect.x === 0 && baseRect.y === 0 && - baseRect.width === 0 && baseRect.height === 0, "initial viewBox"); - assert(!svg.hasAttribute("viewBox"), "viewBox getter does not create attribute"); - - svg.setAttribute("viewBox", "10 20 30 40"); - assert(baseRect.x === 10 && baseRect.y === 20 && - baseRect.width === 30 && baseRect.height === 40, - "cached base rectangle tracks content attribute"); - assert(animRect.x === 10 && animRect.y === 20 && - animRect.width === 30 && animRect.height === 40, - "cached animated rectangle tracks content attribute"); - baseRect.x = 11; - assert(svg.getAttribute("viewBox") === "11 20 30 40", - "base rectangle mutation reflects to viewBox"); - assert(baseRect.x === 11 && animRect.x === 11, - "viewBox values remain synchronized after mutation"); - throws(() => { animRect.x = 12; }, "NoModificationAllowedError", - "animated rectangle is read-only"); - svg.setAttribute("viewBox", "0 0 -1 2"); - assert(baseRect.x === 0 && baseRect.width === 0, - "invalid viewBox uses the initial value"); - svg.removeAttribute("viewBox"); - assert(baseRect.x === 0 && baseRect.height === 0, - "removed viewBox uses the initial value"); - - const animatedAspectRatio = svg.preserveAspectRatio; - const baseAspectRatio = animatedAspectRatio.baseVal; - const animAspectRatio = animatedAspectRatio.animVal; - assert(baseAspectRatio instanceof SVGPreserveAspectRatio && - animAspectRatio instanceof SVGPreserveAspectRatio, - "preserveAspectRatio value interfaces"); - assert(baseAspectRatio !== animAspectRatio, - "preserveAspectRatio base and animated values are distinct"); - assert(baseAspectRatio.align === - SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID, - "initial preserveAspectRatio align"); - assert(baseAspectRatio.meetOrSlice === - SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, - "initial preserveAspectRatio meetOrSlice"); - assert(!svg.hasAttribute("preserveAspectRatio"), - "preserveAspectRatio getter does not create attribute"); - - svg.setAttribute("preserveAspectRatio", "xMinYMax slice"); - assert(baseAspectRatio.align === - SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMAX, - "cached base aspect ratio tracks content attribute"); - assert(animAspectRatio.meetOrSlice === - SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE, - "cached animated aspect ratio tracks content attribute"); - baseAspectRatio.align = - SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN; - baseAspectRatio.meetOrSlice = SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE; - assert(svg.getAttribute("preserveAspectRatio") === "xMaxYMin slice", - "base aspect ratio mutation reflects canonical syntax"); - assert(animAspectRatio.align === - SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN, - "animated aspect ratio tracks base mutation"); - baseAspectRatio.align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_UNKNOWN; - baseAspectRatio.meetOrSlice = SVGPreserveAspectRatio.SVG_MEETORSLICE_UNKNOWN; - assert(svg.getAttribute("preserveAspectRatio") === "xMaxYMin slice", - "unknown enumeration assignments are ignored"); - throws(() => { animAspectRatio.align = 1; }, "NoModificationAllowedError", - "animated aspect ratio align is read-only"); - throws(() => { animAspectRatio.meetOrSlice = 1; }, - "NoModificationAllowedError", "animated aspect ratio meetOrSlice is read-only"); - svg.setAttribute("preserveAspectRatio", "invalid"); - assert(baseAspectRatio.align === - SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID && - animAspectRatio.meetOrSlice === SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET, - "invalid preserveAspectRatio uses the initial value"); - svg.removeAttribute("preserveAspectRatio"); - assert(baseAspectRatio.align === - SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID, - "removed preserveAspectRatio uses the initial value"); - - const constants = [ - ["SVG_PRESERVEASPECTRATIO_UNKNOWN", 0], - ["SVG_PRESERVEASPECTRATIO_NONE", 1], - ["SVG_PRESERVEASPECTRATIO_XMINYMIN", 2], - ["SVG_PRESERVEASPECTRATIO_XMIDYMIN", 3], - ["SVG_PRESERVEASPECTRATIO_XMAXYMIN", 4], - ["SVG_PRESERVEASPECTRATIO_XMINYMID", 5], - ["SVG_PRESERVEASPECTRATIO_XMIDYMID", 6], - ["SVG_PRESERVEASPECTRATIO_XMAXYMID", 7], - ["SVG_PRESERVEASPECTRATIO_XMINYMAX", 8], - ["SVG_PRESERVEASPECTRATIO_XMIDYMAX", 9], - ["SVG_PRESERVEASPECTRATIO_XMAXYMAX", 10], - ["SVG_MEETORSLICE_UNKNOWN", 0], - ["SVG_MEETORSLICE_MEET", 1], - ["SVG_MEETORSLICE_SLICE", 2], - ]; - for (const [name, value] of constants) { - assert(SVGPreserveAspectRatio[name] === value, `constructor ${name}`); - assert(SVGPreserveAspectRatio.prototype[name] === value, `prototype ${name}`); - } - - const viewBoxDescriptor = Object.getOwnPropertyDescriptor( - SVGSVGElement.prototype, - "viewBox", - ); - throws(() => viewBoxDescriptor.get.call(document.createElementNS(ns, "rect")), - "TypeError", "viewBox receiver brand"); - throws(() => imageDescriptor.get.call(document.createElementNS(ns, "rect")), - "TypeError", "image preserveAspectRatio receiver brand"); - const animatedRectDescriptor = Object.getOwnPropertyDescriptor( - SVGAnimatedRect.prototype, - "baseVal", - ); - throws(() => animatedRectDescriptor.get.call({}), "TypeError", - "animated rectangle receiver brand"); - const alignDescriptor = Object.getOwnPropertyDescriptor( - SVGPreserveAspectRatio.prototype, - "align", - ); - throws(() => alignDescriptor.get.call({}), "TypeError", - "preserveAspectRatio receiver brand"); - return "ok"; - })() - "#, - ) - .expect("SVG fit-to-view-box reflection probe should evaluate"); - - assert_eq!(result, "ok"); -} - -#[test] -fn svg_view_box_rects_preserve_native_float_and_reentrant_setter_semantics() { - let mut vm = new_parsed_test_vm( - "https://svg-view-box-native-rect.test/", - "", - ); - - let result = vm - .eval( - r#" - (() => { - const assert = (condition, message) => { - if (!condition) throw new Error(message); - }; - const throws = (callback, name) => { - try { callback(); } catch (error) { - assert(error.name === name, `expected ${name}, got ${error.name}`); - return; - } - throw new Error(`expected ${name}`); - }; - const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); - const fields = ["x", "y", "width", "height"]; - const values = rect => fields.map(field => rect[field]); - svg.setAttribute("viewBox", "1.2 2.4 3.6 4.8"); - const base = svg.viewBox.baseVal; - const anim = svg.viewBox.animVal; - const assertValues = expected => { - for (const rect of [base, anim]) { - assert(JSON.stringify(values(rect)) === JSON.stringify(expected.map(Math.fround)), - `float values: ${values(rect)}`); - } - }; - assertValues([1.2, 2.4, 3.6, 4.8]); - svg.setAttribute("viewBox", "5.5 6.6 7.7 8.8"); - assertValues([5.5, 6.6, 7.7, 8.8]); - base.x = "1.2"; - base.width = 1e20; - assertValues([1.2, 6.6, 1e20, 8.8]); - const reflected = svg.getAttribute("viewBox"); - assert(JSON.stringify(reflected.split(" ").map(Number).map(Math.fround)) === - JSON.stringify(values(base)), "float serialization round trip"); - for (const invalid of [NaN, Infinity, -Infinity, 1e40, undefined, Symbol()]) { - throws(() => { base.x = invalid; }, "TypeError"); - assert(svg.getAttribute("viewBox") === reflected, - "rejected float assignment preserves the attribute"); - } - throws(() => { anim.width = 1; }, "NoModificationAllowedError"); - const domRectX = Object.getOwnPropertyDescriptor(DOMRect.prototype, "x"); - throws(() => domRectX.get.call(base), "TypeError"); - throws(() => domRectX.set.call(anim, 7), "TypeError"); - assert(svg.getAttribute("viewBox") === reflected, - "read-only and foreign-interface setters preserve the attribute"); - - base.x = { - valueOf() { - svg.setAttribute("viewBox", "10 20 30 40"); - return 9; - } - }; - assert(svg.getAttribute("viewBox") === "9 20 30 40", - "numeric coercion preserves reentrant changes to the other fields"); - assertValues([9, 20, 30, 40]); - svg.setAttribute("viewBox", "0 0 1e40 1"); - assertValues([0, 0, 0, 0]); - return "ok"; - })() - "#, - ) - .expect("native SVG viewBox rectangle probe should evaluate"); - - 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_lengths_preserve_specified_units_and_resolve_live_context() { - let mut vm = new_parsed_test_vm( - "https://svg-length-context.test/", - r#" - - - - - - - - - - - - - - "#, - ); - - let result = vm - .eval( - r#" - (() => { - const assert = (condition, message) => { - if (!condition) throw new Error(message); - }; - const close = (actual, expected, tolerance = 1e-6) => - Math.abs(actual - expected) <= tolerance; - const svg = document.getElementById("svg"); - - const standalone = svg.createSVGLength(); - standalone.valueAsString = "48px"; - standalone.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_CM); - assert(standalone.valueAsString === "1.27cm", - `absolute serialization: ${standalone.valueAsString}`); - assert(close(standalone.valueInSpecifiedUnits, 1.27), - "absolute specified value"); - assert(close(standalone.value, 48), "absolute user value"); - standalone.value = 96; - assert(close(standalone.valueInSpecifiedUnits, 2.54), - "value setter preserves unit"); - standalone.valueInSpecifiedUnits = 1; - assert(close(standalone.value, 96 / 2.54), - "specified setter resolves user value"); - - standalone.valueAsString = "40q"; - assert(standalone.unitType === SVGLength.SVG_LENGTHTYPE_UNKNOWN, - "modern unit type"); - assert(close(standalone.value, 96 / 2.54), "quarter millimeter value"); - standalone.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_MM); - assert(standalone.valueAsString === "10mm", "quarter millimeter conversion"); - - const rect = document.getElementById("rect"); - const x = rect.x.baseVal; - x.valueAsString = "3px"; - x.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PERCENTAGE); - assert(x.valueAsString === "2%" && close(x.value, 3), - `percentage conversion uses SVG viewport: ${x.valueAsString}|${x.value}|${x.valueInSpecifiedUnits}`); - x.valueAsString = "6px"; - x.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_EMS); - assert(x.valueAsString === "0.5em" && close(x.value, 6), - "em conversion uses owner font size"); - - svg.removeAttribute("viewBox"); - x.valueAsString = "50%"; - assert(close(x.value, 75), "initial percentage basis"); - svg.width.baseVal.value = 300; - assert(close(x.value, 150), "live percentage basis"); - - const ch = document.getElementById("text").x.baseVal[0]; - assert(ch.unitType === SVGLength.SVG_LENGTHTYPE_UNKNOWN, - "list modern unit type"); - assert(close(ch.value, 100), "ch resolves from owner font size"); - ch.value = 200; - assert(close(ch.valueInSpecifiedUnits, 20), "ch reverse conversion"); - - for (const [id, expected] of [ - ["cap", 140], - ["ic", 200], - ["lh", 240], - ["rlh", 240], - ["rem", 100], - ]) { - const modern = document.getElementById(id).x.baseVal[0] ?? - document.getElementById(id).x.baseVal; - assert(modern.unitType === SVGLength.SVG_LENGTHTYPE_UNKNOWN, - `${id} unit type`); - assert(close(modern.value, expected), `${id} context value`); - const specified = modern.valueInSpecifiedUnits; - modern.value = expected * 2; - assert(close(modern.valueInSpecifiedUnits, specified * 2), - `${id} reverse conversion`); - } - - const viewport = document.getElementById("viewport").x.baseVal; - const viewportValue = viewport.value; - viewport.value = viewportValue * 2; - assert(close(viewport.valueInSpecifiedUnits, 20), - "viewport reverse conversion"); - return "ok"; - })() - "#, - ) - .expect("SVG length context probe should evaluate"); - - assert_eq!(result, "ok"); -} - -#[test] -fn svg_switch_and_mpath_use_native_interfaces_and_live_href() { - let mut vm = new_parsed_test_vm("https://svg-switch-mpath.test/", ""); - assert_eq!( - vm.eval(include_str!("svg_switch_mpath.js")) - .expect("SVG switch and mpath fixture should evaluate"), - "true" - ); -} - -#[test] -fn svg_animation_elements_use_native_interfaces_and_event_handlers() { - let mut vm = new_parsed_test_vm( - "https://svg-animation.test/", - "", - ); - vm.eval(include_str!("svg_animation_interfaces.js")) - .unwrap(); - assert_eq!( - vm.eval("JSON.stringify(__nodeReplacementResults.failures)") - .unwrap(), - "[]" - ); -} - - -#[test] -fn svg_filter_primitives_expose_native_dom_attributes() { - let mut vm = new_parsed_test_vm("https://svg-filter-dom.test/", ""); - assert_eq!( - vm.eval(include_str!("svg_filter_primitives.js")) - .expect("SVG filter DOM fixture should evaluate"), - "true" - ); -} - -#[test] -fn element_attribute_values_preserve_utf16_across_native_documents() { - let mut vm = new_parsed_test_vm( - "https://attribute-string.test/", - "", - ); - assert_eq!( - vm.eval(include_str!("attribute_string_values.js")) - .expect("attribute string fixture should evaluate"), - "true" - ); -} - - -#[test] -fn svg_animated_values_follow_native_attribute_mutations() { - let mut vm = new_parsed_test_vm("https://svg-values.test/", ""); - assert_eq!( - vm.eval(include_str!("svg_animated_values.js")) - .expect("SVG animated value fixture should evaluate"), - "true" - ); -} - - -#[test] -fn namespaced_attribute_values_preserve_utf16_and_namespace_identity() { - let mut vm = new_parsed_test_vm("https://namespace-utf16.test/", ""); - assert_eq!( - vm.eval(include_str!("namespace_attribute_string_values.js")) - .expect("namespaced attribute string fixture should evaluate"), - "true" - ); -} - - -#[test] -fn svg_animation_time_controls_convert_arguments_without_a_fragment() { - let mut vm = new_parsed_test_vm( - "https://svg-animation-timing.test/", - "", - ); - assert_eq!(vm.eval(r#" - (() => { - const other = document.getElementById('child').contentWindow; - for (const realm of [window, other]) { - const element = document.createElementNS('http://www.w3.org/2000/svg', 'animate'); - for (const name of ['beginElement', 'endElement', 'beginElementAt', 'endElementAt']) { - let conversions = 0; - const method = realm.SVGAnimationElement.prototype[name]; - const result = method.call(element, {valueOf() {conversions++; return 0.25;}}); - if (result !== undefined) throw Error(name + ' detached return value'); - if (conversions !== (name.endsWith('At') ? 1 : 0)) throw Error(name + ' argument conversion'); - if (element.getCurrentTime() !== 0) throw Error('detached animation cannot advance the timeline'); - if (name.endsWith('At')) { - for (const value of [NaN, Infinity, -Infinity, 1e100, Symbol(), 1n]) { - let error; - try { method.call(element, value); } catch (caught) { error = caught; } - if (Object.getPrototypeOf(error) !== realm.TypeError.prototype) throw Error(name + ' invalid float realm'); + animated.baseVal = value; + if (animated.baseVal !== value || animated.animVal !== value || element.getAttribute(attribute) !== value) return false; } - let error; - try { method.call(element); } catch (caught) { error = caught; } - if (Object.getPrototypeOf(error) !== realm.TypeError.prototype) throw Error(name + ' required argument'); - const sentinel = {}; - try { method.call(element, {valueOf() {throw sentinel;}}); } catch (caught) { error = caught; } - if (error !== sentinel || element.getCurrentTime() !== 0) throw Error(name + ' original conversion exception'); - } - } - } - return true; - })() - "#).unwrap(), "true"); + return true; + })()"#).unwrap(), + "true" + ); } - diff --git a/moli-renderer-v8/src/web_api_interfaces.rs b/moli-renderer-v8/src/web_api_interfaces.rs index 664aef3363..1ff3058926 100644 --- a/moli-renderer-v8/src/web_api_interfaces.rs +++ b/moli-renderer-v8/src/web_api_interfaces.rs @@ -552,12 +552,12 @@ interfaces! { SVGAnimatedNumberList; SVGAnimatedTransformList; SVGCircleElement: SVGGeometryElement; + SVGDefsElement: SVGGraphicsElement; + SVGDescElement: SVGElement; SVGClipPathElement: SVGElement; SVGFilterElement: SVGElement; SVGMaskElement: SVGElement; SVGViewElement: SVGElement; - SVGDefsElement: SVGGraphicsElement; - SVGDescElement: SVGElement; SVGComponentTransferFunctionElement: SVGElement; SVGFEFuncAElement: SVGComponentTransferFunctionElement; SVGFEFuncBElement: SVGComponentTransferFunctionElement; @@ -618,6 +618,7 @@ interfaces! { SVGTextElement: SVGTextPositioningElement; SVGTextPositioningElement: SVGTextContentElement; SVGTitleElement: SVGElement; + SVGUnitTypes; SVGPointList; SVGStringList; SVGTransform;