From 05ef650a5981bf3537ca8c51e2e5a589fc5dfb43 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Sat, 5 Jul 2025 17:27:32 +0100 Subject: [PATCH] LibWeb: Respect presentation attributes that apply to not all elements Some SVG presentation attributes are only supported on certain elements. We now support these special cases for attributes and elements that we currently have implemented. --- Libraries/LibWeb/SVG/SVGElement.cpp | 112 +++++++------ .../svg-foreign-object-with-block-element.txt | 4 +- .../presentation-attributes-relevant.txt | 16 +- .../presentation-attributes-special-cases.txt | 32 ++++ ...presentation-attributes-special-cases.html | 149 ++++++++++++++++++ 5 files changed, 256 insertions(+), 57 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-special-cases.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/svg/styling/presentation-attributes-special-cases.html diff --git a/Libraries/LibWeb/SVG/SVGElement.cpp b/Libraries/LibWeb/SVG/SVGElement.cpp index 7e9aa1d5622..b5b7b1224d5 100644 --- a/Libraries/LibWeb/SVG/SVGElement.cpp +++ b/Libraries/LibWeb/SVG/SVGElement.cpp @@ -17,6 +17,7 @@ #include #include #include +#include namespace Web::SVG { @@ -32,75 +33,92 @@ void SVGElement::initialize(JS::Realm& realm) } struct NamedPropertyID { - NamedPropertyID(CSS::PropertyID property_id) + NamedPropertyID(CSS::PropertyID property_id, Vector supported_elements = {}) : id(property_id) , name(CSS::string_from_property_id(property_id)) + , supported_elements(move(supported_elements)) { } CSS::PropertyID id; - StringView name; + FlyString name; + Vector supported_elements; }; -static Array const attribute_style_properties { - // FIXME: The `fill` attribute and CSS `fill` property are not the same! But our support is limited enough that they are equivalent for now. - NamedPropertyID(CSS::PropertyID::Fill), - // FIXME: The `stroke` attribute and CSS `stroke` property are not the same! But our support is limited enough that they are equivalent for now. - NamedPropertyID(CSS::PropertyID::ClipPath), - NamedPropertyID(CSS::PropertyID::ClipRule), - NamedPropertyID(CSS::PropertyID::Color), - NamedPropertyID(CSS::PropertyID::Cursor), - NamedPropertyID(CSS::PropertyID::Direction), - NamedPropertyID(CSS::PropertyID::Display), - NamedPropertyID(CSS::PropertyID::FillOpacity), - NamedPropertyID(CSS::PropertyID::FillRule), - NamedPropertyID(CSS::PropertyID::FontFamily), - NamedPropertyID(CSS::PropertyID::FontSize), - NamedPropertyID(CSS::PropertyID::FontStyle), - NamedPropertyID(CSS::PropertyID::FontWeight), - NamedPropertyID(CSS::PropertyID::ImageRendering), - NamedPropertyID(CSS::PropertyID::LetterSpacing), - NamedPropertyID(CSS::PropertyID::Mask), - NamedPropertyID(CSS::PropertyID::MaskType), - NamedPropertyID(CSS::PropertyID::Opacity), - NamedPropertyID(CSS::PropertyID::Overflow), - NamedPropertyID(CSS::PropertyID::PointerEvents), - NamedPropertyID(CSS::PropertyID::StopColor), - NamedPropertyID(CSS::PropertyID::StopOpacity), - NamedPropertyID(CSS::PropertyID::Stroke), - NamedPropertyID(CSS::PropertyID::StrokeDasharray), - NamedPropertyID(CSS::PropertyID::StrokeDashoffset), - NamedPropertyID(CSS::PropertyID::StrokeLinecap), - NamedPropertyID(CSS::PropertyID::StrokeLinejoin), - NamedPropertyID(CSS::PropertyID::StrokeMiterlimit), - NamedPropertyID(CSS::PropertyID::StrokeOpacity), - NamedPropertyID(CSS::PropertyID::StrokeWidth), - NamedPropertyID(CSS::PropertyID::TextAnchor), - NamedPropertyID(CSS::PropertyID::TextRendering), - NamedPropertyID(CSS::PropertyID::TextOverflow), - NamedPropertyID(CSS::PropertyID::TransformOrigin), - NamedPropertyID(CSS::PropertyID::UnicodeBidi), - NamedPropertyID(CSS::PropertyID::Visibility), - NamedPropertyID(CSS::PropertyID::WhiteSpace), - NamedPropertyID(CSS::PropertyID::WordSpacing), - NamedPropertyID(CSS::PropertyID::WritingMode), -}; +static ReadonlySpan attribute_style_properties() +{ + static Array const properties = { + // FIXME: The `fill` attribute and CSS `fill` property are not the same! But our support is limited enough that they are equivalent for now. + NamedPropertyID(CSS::PropertyID::Fill), + // FIXME: The `stroke` attribute and CSS `stroke` property are not the same! But our support is limited enough that they are equivalent for now. + NamedPropertyID(CSS::PropertyID::ClipPath), + NamedPropertyID(CSS::PropertyID::ClipRule), + NamedPropertyID(CSS::PropertyID::Color), + NamedPropertyID(CSS::PropertyID::Cursor), + NamedPropertyID(CSS::PropertyID::Cx, { SVG::TagNames::circle, SVG::TagNames::ellipse }), + NamedPropertyID(CSS::PropertyID::Cy, { SVG::TagNames::circle, SVG::TagNames::ellipse }), + NamedPropertyID(CSS::PropertyID::Direction), + NamedPropertyID(CSS::PropertyID::Display), + NamedPropertyID(CSS::PropertyID::FillOpacity), + NamedPropertyID(CSS::PropertyID::FillRule), + NamedPropertyID(CSS::PropertyID::FontFamily), + NamedPropertyID(CSS::PropertyID::FontSize), + NamedPropertyID(CSS::PropertyID::FontStyle), + NamedPropertyID(CSS::PropertyID::FontWeight), + NamedPropertyID(CSS::PropertyID::Height, { SVG::TagNames::foreignObject, SVG::TagNames::image, SVG::TagNames::rect, SVG::TagNames::svg, SVG::TagNames::symbol, SVG::TagNames::use }), + NamedPropertyID(CSS::PropertyID::ImageRendering), + NamedPropertyID(CSS::PropertyID::LetterSpacing), + NamedPropertyID(CSS::PropertyID::Mask), + NamedPropertyID(CSS::PropertyID::MaskType), + NamedPropertyID(CSS::PropertyID::Opacity), + NamedPropertyID(CSS::PropertyID::Overflow), + NamedPropertyID(CSS::PropertyID::PointerEvents), + NamedPropertyID(CSS::PropertyID::R, { SVG::TagNames::circle }), + NamedPropertyID(CSS::PropertyID::Rx, { SVG::TagNames::ellipse, SVG::TagNames::rect }), + NamedPropertyID(CSS::PropertyID::Ry, { SVG::TagNames::ellipse, SVG::TagNames::rect }), + NamedPropertyID(CSS::PropertyID::StopColor), + NamedPropertyID(CSS::PropertyID::StopOpacity), + NamedPropertyID(CSS::PropertyID::Stroke), + NamedPropertyID(CSS::PropertyID::StrokeDasharray), + NamedPropertyID(CSS::PropertyID::StrokeDashoffset), + NamedPropertyID(CSS::PropertyID::StrokeLinecap), + NamedPropertyID(CSS::PropertyID::StrokeLinejoin), + NamedPropertyID(CSS::PropertyID::StrokeMiterlimit), + NamedPropertyID(CSS::PropertyID::StrokeOpacity), + NamedPropertyID(CSS::PropertyID::StrokeWidth), + NamedPropertyID(CSS::PropertyID::TextAnchor), + NamedPropertyID(CSS::PropertyID::TextRendering), + NamedPropertyID(CSS::PropertyID::TextOverflow), + NamedPropertyID(CSS::PropertyID::TransformOrigin), + NamedPropertyID(CSS::PropertyID::UnicodeBidi), + NamedPropertyID(CSS::PropertyID::Visibility), + NamedPropertyID(CSS::PropertyID::WhiteSpace), + NamedPropertyID(CSS::PropertyID::Width, { SVG::TagNames::foreignObject, SVG::TagNames::image, SVG::TagNames::rect, SVG::TagNames::svg, SVG::TagNames::symbol, SVG::TagNames::use }), + NamedPropertyID(CSS::PropertyID::WordSpacing), + NamedPropertyID(CSS::PropertyID::WritingMode), + NamedPropertyID(CSS::PropertyID::X, { SVG::TagNames::foreignObject, SVG::TagNames::image, SVG::TagNames::rect, SVG::TagNames::svg, SVG::TagNames::symbol, SVG::TagNames::use }), + NamedPropertyID(CSS::PropertyID::Y, { SVG::TagNames::foreignObject, SVG::TagNames::image, SVG::TagNames::rect, SVG::TagNames::svg, SVG::TagNames::symbol, SVG::TagNames::use }), + }; + return properties; +} bool SVGElement::is_presentational_hint(FlyString const& name) const { if (Base::is_presentational_hint(name)) return true; - return any_of(attribute_style_properties, [&](auto& property) { return name.equals_ignoring_ascii_case(property.name); }); + return any_of(attribute_style_properties(), [&](auto& property) { return name.equals_ignoring_ascii_case(property.name); }); } void SVGElement::apply_presentational_hints(GC::Ref cascaded_properties) const { CSS::Parser::ParsingParams parsing_context { document(), CSS::Parser::ParsingMode::SVGPresentationAttribute }; for_each_attribute([&](auto& name, auto& value) { - for (auto property : attribute_style_properties) { + for (auto& property : attribute_style_properties()) { if (!name.equals_ignoring_ascii_case(property.name)) continue; + if (!property.supported_elements.is_empty() && !property.supported_elements.contains_slow(local_name())) + continue; if (property.id == CSS::PropertyID::Mask) { // Mask is a shorthand property in CSS, but parse_css_value does not take that into account. For now, // just parse as 'mask-image' as anything else is currently not supported. diff --git a/Tests/LibWeb/Layout/expected/svg/svg-foreign-object-with-block-element.txt b/Tests/LibWeb/Layout/expected/svg/svg-foreign-object-with-block-element.txt index 572b24dfc6d..d65a1af20d5 100644 --- a/Tests/LibWeb/Layout/expected/svg/svg-foreign-object-with-block-element.txt +++ b/Tests/LibWeb/Layout/expected/svg/svg-foreign-object-with-block-element.txt @@ -3,7 +3,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline BlockContainer at (8,8) content-size 784x100 children: inline frag 0 from SVGSVGBox start: 0, length: 0, rect: [8,8 100x100] baseline: 100 SVGSVGBox at (8,8) content-size 100x100 [SVG] children: inline - SVGForeignObjectBox at (8,8) content-size 0x0 children: not-inline + SVGForeignObjectBox at (8,8) content-size 100x100 children: not-inline BlockContainer
at (8,8) content-size 100x18 children: inline frag 0 from TextNode start: 0, length: 3, rect: [8,8 27.15625x18] baseline: 13.796875 "foo" @@ -13,6 +13,6 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600] PaintableWithLines (BlockContainer) [0,0 800x116] PaintableWithLines (BlockContainer) [8,8 784x100] SVGSVGPaintable (SVGSVGBox) [8,8 100x100] - SVGForeignObjectPaintable (SVGForeignObjectBox) [8,8 0x0] overflow: [8,8 100x18] + SVGForeignObjectPaintable (SVGForeignObjectBox) [8,8 100x100] PaintableWithLines (BlockContainer
) [8,8 100x18] TextPaintable (TextNode<#text>) diff --git a/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-relevant.txt b/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-relevant.txt index 64f473ffefd..1737cbbb440 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-relevant.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-relevant.txt @@ -2,8 +2,8 @@ Harness status: OK Found 53 tests -42 Pass -11 Fail +48 Pass +5 Fail Pass clip-path presentation attribute supported on a relevant element Pass clip-rule presentation attribute supported on a relevant element Pass color presentation attribute supported on a relevant element @@ -22,7 +22,7 @@ Fail font-stretch presentation attribute supported on a relevant element Pass font-style presentation attribute supported on a relevant element Fail font-variant presentation attribute supported on a relevant element Pass font-weight presentation attribute supported on a relevant element -Fail height presentation attribute supported on a relevant element +Pass height presentation attribute supported on a relevant element Pass image-rendering presentation attribute supported on a relevant element Pass letter-spacing presentation attribute supported on a relevant element Pass mask-type presentation attribute supported on a relevant element @@ -31,8 +31,8 @@ Pass opacity presentation attribute supported on a relevant element Pass overflow presentation attribute supported on a relevant element Pass pointer-events presentation attribute supported on a relevant element Pass r presentation attribute supported on a relevant element -Fail rx presentation attribute supported on a relevant element -Fail ry presentation attribute supported on a relevant element +Pass rx presentation attribute supported on a relevant element +Pass ry presentation attribute supported on a relevant element Pass stop-color presentation attribute supported on a relevant element Pass stop-opacity presentation attribute supported on a relevant element Pass stroke presentation attribute supported on a relevant element @@ -52,8 +52,8 @@ Fail transform presentation attribute supported on a relevant element Pass unicode-bidi presentation attribute supported on a relevant element Pass visibility presentation attribute supported on a relevant element Pass white-space presentation attribute supported on a relevant element -Fail width presentation attribute supported on a relevant element +Pass width presentation attribute supported on a relevant element Pass word-spacing presentation attribute supported on a relevant element Pass writing-mode presentation attribute supported on a relevant element -Fail x presentation attribute supported on a relevant element -Fail y presentation attribute supported on a relevant element \ No newline at end of file +Pass x presentation attribute supported on a relevant element +Pass y presentation attribute supported on a relevant element \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-special-cases.txt b/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-special-cases.txt new file mode 100644 index 00000000000..c0ac13eff99 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-special-cases.txt @@ -0,0 +1,32 @@ +Harness status: OK + +Found 26 tests + +17 Pass +9 Fail +Pass cx and cy presentation attributes supported on circle element +Pass cx and cy presentation attributes supported on ellipse element +Pass x, y, width, and height presentation attributes supported on foreignObject element +Pass x, y, width, and height presentation attributes supported on image element +Pass x, y, width, and height presentation attributes supported on rect element +Pass x, y, width, and height presentation attributes supported on svg element +Pass x, y, width, and height presentation attributes supported on symbol element +Fail x, y, width, and height presentation attributes supported on use element +Pass r presentation attribute supported on circle element +Pass rx and ry presentation attributes supported on ellipse element +Pass rx and ry presentation attributes supported on rect element +Pass cx and cy presentation attributes not supported on other elements +Pass x, y, width, and height presentation attributes not supported on other elements +Pass r presentation attribute not supported on other elements +Pass rx and ry presentation attributes not supported on other elements +Fail fill presentation attribute not supported on animate +Fail fill presentation attribute not supported on animateMotion +Fail fill presentation attribute not supported on animateTransform +Fail fill presentation attribute not supported on set +Fail transform presentation attribute supported on g +Fail patternTransform presentation attribute supported on pattern +Fail gradientTransform presentation attribute supported on linearGradient +Fail gradientTransform presentation attribute supported on radialGradient +Pass transform presentation attribute not supported on pattern or gradient elements +Pass patternTransform presentation attribute not supported on g or gradient elements +Pass gradientTransform presentation attribute not supported on g or pattern elements \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/svg/styling/presentation-attributes-special-cases.html b/Tests/LibWeb/Text/input/wpt-import/svg/styling/presentation-attributes-special-cases.html new file mode 100644 index 00000000000..b398731cef5 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/svg/styling/presentation-attributes-special-cases.html @@ -0,0 +1,149 @@ + + +SVG presentation attributes - special cases + + + + + +