LibWeb: Respect presentation attributes that apply to not all elements
Some checks are pending
CI / macOS, arm64, Sanitizer_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run

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.
This commit is contained in:
Tim Ledbetter 2025-07-05 17:27:32 +01:00 committed by Tim Flynn
parent 2cc8f0821c
commit 05ef650a59
Notes: github-actions[bot] 2025-07-05 23:08:09 +00:00
5 changed files with 256 additions and 57 deletions

View file

@ -17,6 +17,7 @@
#include <LibWeb/SVG/SVGSymbolElement.h>
#include <LibWeb/SVG/SVGTitleElement.h>
#include <LibWeb/SVG/SVGUseElement.h>
#include <LibWeb/SVG/TagNames.h>
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<FlyString> 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<FlyString> 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<NamedPropertyID> 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<CSS::CascadedProperties> 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.