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;