LibWeb/CSS: Parse the paint-order property

This commit is contained in:
Tim Ledbetter 2025-08-24 15:00:53 +01:00 committed by Sam Atkins
commit a87a9156d5
Notes: github-actions[bot] 2025-08-28 09:32:32 +00:00
25 changed files with 376 additions and 17 deletions

View file

@ -1951,6 +1951,47 @@ ShapeRendering ComputedProperties::shape_rendering() const
return keyword_to_shape_rendering(value.to_keyword()).release_value(); return keyword_to_shape_rendering(value.to_keyword()).release_value();
} }
PaintOrderList ComputedProperties::paint_order() const
{
auto const& value = property(PropertyID::PaintOrder);
if (value.is_keyword()) {
auto keyword = value.as_keyword().keyword();
if (keyword == Keyword::Normal)
return InitialValues::paint_order();
auto paint_order_keyword = keyword_to_paint_order(keyword);
VERIFY(paint_order_keyword.has_value());
switch (*paint_order_keyword) {
case PaintOrder::Fill:
return InitialValues::paint_order();
case PaintOrder::Stroke:
return PaintOrderList { PaintOrder::Stroke, PaintOrder::Fill, PaintOrder::Markers };
case PaintOrder::Markers:
return PaintOrderList { PaintOrder::Markers, PaintOrder::Fill, PaintOrder::Stroke };
}
}
VERIFY(value.is_value_list());
auto const& value_list = value.as_value_list();
// The list must contain 2 values at this point, since the third value is omitted during parsing due to the
// shortest-serialization principle.
VERIFY(value_list.size() == 2);
PaintOrderList paint_order_list {};
// We use the sum of the keyword values to infer what the missing keyword is. Since each keyword can only appear in
// the list once, the sum of their values will always be 3.
auto sum = 0;
for (auto i = 0; i < 2; i++) {
auto keyword = value_list.value_at(i, false)->as_keyword().keyword();
auto paint_order_keyword = keyword_to_paint_order(keyword);
VERIFY(paint_order_keyword.has_value());
sum += to_underlying(*paint_order_keyword);
paint_order_list[i] = *paint_order_keyword;
}
VERIFY(sum <= 3);
paint_order_list[2] = static_cast<PaintOrder>(3 - sum);
return paint_order_list;
}
WillChange ComputedProperties::will_change() const WillChange ComputedProperties::will_change() const
{ {
auto const& value = property(PropertyID::WillChange); auto const& value = property(PropertyID::WillChange);

View file

@ -199,6 +199,7 @@ public:
ClipRule clip_rule() const; ClipRule clip_rule() const;
float flood_opacity() const; float flood_opacity() const;
CSS::ShapeRendering shape_rendering() const; CSS::ShapeRendering shape_rendering() const;
PaintOrderList paint_order() const;
WillChange will_change() const; WillChange will_change() const;

View file

@ -118,6 +118,8 @@ using CursorData = Variant<NonnullRefPtr<CursorStyleValue const>, CursorPredefin
using ListStyleType = Variant<CounterStyleNameKeyword, String>; using ListStyleType = Variant<CounterStyleNameKeyword, String>;
using PaintOrderList = Array<PaintOrder, 3>;
class InitialValues { class InitialValues {
public: public:
static AspectRatio aspect_ratio() { return AspectRatio { true, {} }; } static AspectRatio aspect_ratio() { return AspectRatio { true, {} }; }
@ -259,6 +261,7 @@ public:
} }
static CSS::ScrollbarWidth scrollbar_width() { return CSS::ScrollbarWidth::Auto; } static CSS::ScrollbarWidth scrollbar_width() { return CSS::ScrollbarWidth::Auto; }
static CSS::ShapeRendering shape_rendering() { return CSS::ShapeRendering::Auto; } static CSS::ShapeRendering shape_rendering() { return CSS::ShapeRendering::Auto; }
static PaintOrderList paint_order() { return { PaintOrder::Fill, PaintOrder::Stroke, PaintOrder::Markers }; }
static WillChange will_change() { return WillChange::make_auto(); } static WillChange will_change() { return WillChange::make_auto(); }
}; };
@ -590,6 +593,7 @@ public:
CSS::ClipRule clip_rule() const { return m_inherited.clip_rule; } CSS::ClipRule clip_rule() const { return m_inherited.clip_rule; }
Color flood_color() const { return m_noninherited.flood_color; } Color flood_color() const { return m_noninherited.flood_color; }
float flood_opacity() const { return m_noninherited.flood_opacity; } float flood_opacity() const { return m_noninherited.flood_opacity; }
PaintOrderList paint_order() const { return m_inherited.paint_order; }
LengthPercentage const& cx() const { return m_noninherited.cx; } LengthPercentage const& cx() const { return m_noninherited.cx; }
LengthPercentage const& cy() const { return m_noninherited.cy; } LengthPercentage const& cy() const { return m_noninherited.cy; }
@ -702,6 +706,7 @@ protected:
CSS::FillRule fill_rule { InitialValues::fill_rule() }; CSS::FillRule fill_rule { InitialValues::fill_rule() };
Optional<SVGPaint> stroke; Optional<SVGPaint> stroke;
float fill_opacity { InitialValues::fill_opacity() }; float fill_opacity { InitialValues::fill_opacity() };
PaintOrderList paint_order { InitialValues::paint_order() };
Vector<Variant<LengthPercentage, NumberOrCalculated>> stroke_dasharray; Vector<Variant<LengthPercentage, NumberOrCalculated>> stroke_dasharray;
LengthPercentage stroke_dashoffset { InitialValues::stroke_dashoffset() }; LengthPercentage stroke_dashoffset { InitialValues::stroke_dashoffset() };
CSS::StrokeLinecap stroke_linecap { InitialValues::stroke_linecap() }; CSS::StrokeLinecap stroke_linecap { InitialValues::stroke_linecap() };
@ -1032,6 +1037,7 @@ public:
void set_flood_color(Color value) { m_noninherited.flood_color = value; } void set_flood_color(Color value) { m_noninherited.flood_color = value; }
void set_flood_opacity(float value) { m_noninherited.flood_opacity = value; } void set_flood_opacity(float value) { m_noninherited.flood_opacity = value; }
void set_shape_rendering(CSS::ShapeRendering value) { m_noninherited.shape_rendering = value; } void set_shape_rendering(CSS::ShapeRendering value) { m_noninherited.shape_rendering = value; }
void set_paint_order(PaintOrderList value) { m_inherited.paint_order = value; }
void set_cx(LengthPercentage cx) { m_noninherited.cx = move(cx); } void set_cx(LengthPercentage cx) { m_noninherited.cx = move(cx); }
void set_cy(LengthPercentage cy) { m_noninherited.cy = move(cy); } void set_cy(LengthPercentage cy) { m_noninherited.cy = move(cy); }

View file

@ -575,6 +575,11 @@
"legal", "legal",
"ledger" "ledger"
], ],
"paint-order": [
"fill",
"stroke",
"markers"
],
"pointer-events": [ "pointer-events": [
"auto", "auto",
"all", "all",

View file

@ -319,6 +319,7 @@
"luminosity", "luminosity",
"manipulation", "manipulation",
"mark", "mark",
"markers",
"marktext", "marktext",
"match-parent", "match-parent",
"match-source", "match-source",
@ -498,6 +499,7 @@
"strict", "strict",
"strict-origin", "strict-origin",
"strict-origin-when-cross-origin", "strict-origin-when-cross-origin",
"stroke",
"stroke-box", "stroke-box",
"style", "style",
"sub", "sub",

View file

@ -454,6 +454,7 @@ private:
RefPtr<StyleValue const> parse_math_depth_value(TokenStream<ComponentValue>&); RefPtr<StyleValue const> parse_math_depth_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_opacity_value(PropertyID property_id, TokenStream<ComponentValue>&); RefPtr<StyleValue const> parse_opacity_value(PropertyID property_id, TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_overflow_value(TokenStream<ComponentValue>&); RefPtr<StyleValue const> parse_overflow_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_paint_order_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_place_content_value(TokenStream<ComponentValue>&); RefPtr<StyleValue const> parse_place_content_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_place_items_value(TokenStream<ComponentValue>&); RefPtr<StyleValue const> parse_place_items_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_place_self_value(TokenStream<ComponentValue>&); RefPtr<StyleValue const> parse_place_self_value(TokenStream<ComponentValue>&);

View file

@ -701,6 +701,10 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue const>> Parser::parse_css_value(Pr
if (auto parsed_value = parse_overflow_value(tokens); parsed_value && !tokens.has_next_token()) if (auto parsed_value = parse_overflow_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull(); return parsed_value.release_nonnull();
return ParseError::SyntaxError; return ParseError::SyntaxError;
case PropertyID::PaintOrder:
if (auto parsed_value = parse_paint_order_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::PlaceContent: case PropertyID::PlaceContent:
if (auto parsed_value = parse_place_content_value(tokens); parsed_value && !tokens.has_next_token()) if (auto parsed_value = parse_place_content_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull(); return parsed_value.release_nonnull();
@ -3856,6 +3860,84 @@ RefPtr<StyleValue const> Parser::parse_overflow_value(TokenStream<ComponentValue
{ *maybe_x_value, *maybe_x_value }); { *maybe_x_value, *maybe_x_value });
} }
RefPtr<StyleValue const> Parser::parse_paint_order_value(TokenStream<ComponentValue>& tokens)
{
if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal))
return normal;
bool has_fill = false;
bool has_stroke = false;
bool has_markers = false;
auto parse_paint_order_keyword = [&](auto& inner_tokens) -> RefPtr<StyleValue const> {
auto maybe_value = parse_keyword_value(inner_tokens);
if (!maybe_value)
return nullptr;
switch (maybe_value->to_keyword()) {
case Keyword::Fill:
if (has_fill)
return nullptr;
has_fill = true;
break;
case Keyword::Markers:
if (has_markers)
return nullptr;
has_markers = true;
break;
case Keyword::Stroke:
if (has_stroke)
return nullptr;
has_stroke = true;
break;
default:
return nullptr;
}
return maybe_value.release_nonnull();
};
tokens.discard_whitespace();
if (tokens.is_empty())
return nullptr;
auto transaction = tokens.begin_transaction();
auto first_keyword_value = parse_paint_order_keyword(tokens);
if (!first_keyword_value)
return nullptr;
tokens.discard_whitespace();
if (!tokens.has_next_token()) {
transaction.commit();
return first_keyword_value;
}
auto second_keyword_value = parse_paint_order_keyword(tokens);
if (!second_keyword_value)
return nullptr;
tokens.discard_whitespace();
if (tokens.has_next_token()) {
// The third keyword is parsed to ensure it is valid, but it doesn't get added to the list. This follows the
// shortest-serialization principle, as the value can always be inferred.
if (auto third_keyword_value = parse_paint_order_keyword(tokens); !third_keyword_value)
return nullptr;
tokens.discard_whitespace();
if (tokens.has_next_token())
return nullptr;
}
transaction.commit();
auto expected_second_keyword = Keyword::Fill;
if (first_keyword_value->to_keyword() == Keyword::Fill)
expected_second_keyword = Keyword::Stroke;
if (expected_second_keyword == second_keyword_value->to_keyword())
return first_keyword_value;
return StyleValueList::create({ first_keyword_value.release_nonnull(), second_keyword_value.release_nonnull() }, StyleValueList::Separator::Space);
}
RefPtr<StyleValue const> Parser::parse_place_content_value(TokenStream<ComponentValue>& tokens) RefPtr<StyleValue const> Parser::parse_place_content_value(TokenStream<ComponentValue>& tokens)
{ {
auto transaction = tokens.begin_transaction(); auto transaction = tokens.begin_transaction();

View file

@ -2889,6 +2889,17 @@
"unitless-length" "unitless-length"
] ]
}, },
"paint-order": {
"animation-type": "discrete",
"inherited": true,
"initial": "normal",
"valid-identifiers": [
"normal"
],
"valid-types": [
"paint-order"
]
},
"place-content": { "place-content": {
"inherited": false, "inherited": false,
"initial": "normal", "initial": "normal",

View file

@ -854,6 +854,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
else if (stroke_width.is_percentage()) else if (stroke_width.is_percentage())
computed_values.set_stroke_width(CSS::LengthPercentage { stroke_width.as_percentage().percentage() }); computed_values.set_stroke_width(CSS::LengthPercentage { stroke_width.as_percentage().percentage() });
computed_values.set_shape_rendering(computed_style.shape_rendering()); computed_values.set_shape_rendering(computed_style.shape_rendering());
computed_values.set_paint_order(computed_style.paint_order());
auto const& mask_image = computed_style.property(CSS::PropertyID::MaskImage); auto const& mask_image = computed_style.property(CSS::PropertyID::MaskImage);
if (mask_image.is_url()) { if (mask_image.is_url()) {

View file

@ -77,6 +77,7 @@ static ReadonlySpan<NamedPropertyID> attribute_style_properties()
NamedPropertyID(CSS::PropertyID::MaskType), NamedPropertyID(CSS::PropertyID::MaskType),
NamedPropertyID(CSS::PropertyID::Opacity), NamedPropertyID(CSS::PropertyID::Opacity),
NamedPropertyID(CSS::PropertyID::Overflow), NamedPropertyID(CSS::PropertyID::Overflow),
NamedPropertyID(CSS::PropertyID::PaintOrder),
NamedPropertyID(CSS::PropertyID::PointerEvents), NamedPropertyID(CSS::PropertyID::PointerEvents),
NamedPropertyID(CSS::PropertyID::R, { SVG::TagNames::circle }), NamedPropertyID(CSS::PropertyID::R, { SVG::TagNames::circle }),
NamedPropertyID(CSS::PropertyID::Rx, { SVG::TagNames::ellipse, SVG::TagNames::rect }), NamedPropertyID(CSS::PropertyID::Rx, { SVG::TagNames::ellipse, SVG::TagNames::rect }),

View file

@ -41,6 +41,7 @@ All properties associated with getComputedStyle(document.body):
"math-depth", "math-depth",
"math-shift", "math-shift",
"math-style", "math-style",
"paint-order",
"pointer-events", "pointer-events",
"quotes", "quotes",
"shape-rendering", "shape-rendering",

View file

@ -610,6 +610,8 @@ All supported properties and their default values exposed from CSSStylePropertie
'padding-right': '0px' 'padding-right': '0px'
'paddingTop': '0px' 'paddingTop': '0px'
'padding-top': '0px' 'padding-top': '0px'
'paintOrder': 'normal'
'paint-order': 'normal'
'placeContent': 'normal' 'placeContent': 'normal'
'place-content': 'normal' 'place-content': 'normal'
'placeItems': 'normal legacy' 'placeItems': 'normal legacy'

View file

@ -39,6 +39,7 @@ list-style-type: disc
math-depth: 0 math-depth: 0
math-shift: normal math-shift: normal
math-style: normal math-style: normal
paint-order: normal
pointer-events: auto pointer-events: auto
quotes: auto quotes: auto
shape-rendering: auto shape-rendering: auto
@ -91,7 +92,7 @@ background-position-x: 0%
background-position-y: 0% background-position-y: 0%
background-repeat: repeat background-repeat: repeat
background-size: auto background-size: auto
block-size: 1395px block-size: 1410px
border-block-end-color: rgb(0, 0, 0) border-block-end-color: rgb(0, 0, 0)
border-block-end-style: none border-block-end-style: none
border-block-end-width: 0px border-block-end-width: 0px
@ -167,7 +168,7 @@ grid-row-start: auto
grid-template-areas: none grid-template-areas: none
grid-template-columns: none grid-template-columns: none
grid-template-rows: none grid-template-rows: none
height: 2535px height: 2550px
inline-size: 784px inline-size: 784px
inset-block-end: auto inset-block-end: auto
inset-block-start: auto inset-block-start: auto

View file

@ -1,8 +1,8 @@
Harness status: OK Harness status: OK
Found 257 tests Found 258 tests
250 Pass 251 Pass
7 Fail 7 Fail
Pass accent-color Pass accent-color
Pass border-collapse Pass border-collapse
@ -43,6 +43,7 @@ Pass list-style-type
Pass math-depth Pass math-depth
Pass math-shift Pass math-shift
Pass math-style Pass math-style
Pass paint-order
Pass pointer-events Pass pointer-events
Pass quotes Pass quotes
Pass shape-rendering Pass shape-rendering

View file

@ -2,15 +2,15 @@ Harness status: OK
Found 20 tests Found 20 tests
15 Pass 14 Pass
5 Fail 6 Fail
Pass The serialization of border: 1px; border-top: 1px; should be canonical. Pass The serialization of border: 1px; border-top: 1px; should be canonical.
Pass The serialization of border: 1px solid red; should be canonical. Pass The serialization of border: 1px solid red; should be canonical.
Pass The serialization of border: 1px red; should be canonical. Pass The serialization of border: 1px red; should be canonical.
Pass The serialization of border: red; should be canonical. Pass The serialization of border: red; should be canonical.
Fail The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; border-image: none; should be canonical. Fail The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; border-image: none; should be canonical.
Fail The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; should be canonical. Fail The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; should be canonical.
Pass The serialization of border-top: 1px; border-right: 2px; border-bottom: 3px; border-left: 4px; should be canonical. Fail The serialization of border-top: 1px; border-right: 2px; border-bottom: 3px; border-left: 4px; should be canonical.
Fail The serialization of border: 1px; border-top: 2px; should be canonical. Fail The serialization of border: 1px; border-top: 2px; should be canonical.
Fail The serialization of border: 1px; border-top: 1px !important; should be canonical. Fail The serialization of border: 1px; border-top: 1px !important; should be canonical.
Fail The serialization of border: 1px; border-top-color: red; should be canonical. Fail The serialization of border: 1px; border-top-color: red; should be canonical.

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 38 tests Found 38 tests
30 Pass 32 Pass
8 Fail 6 Fail
Pass Property fill has initial value rgb(0, 0, 0) Pass Property fill has initial value rgb(0, 0, 0)
Pass Property fill inherits Pass Property fill inherits
Pass Property fill-rule has initial value nonzero Pass Property fill-rule has initial value nonzero
@ -32,8 +32,8 @@ Fail Property marker-mid has initial value none
Fail Property marker-mid inherits Fail Property marker-mid inherits
Fail Property marker-end has initial value none Fail Property marker-end has initial value none
Fail Property marker-end inherits Fail Property marker-end inherits
Fail Property paint-order has initial value normal Pass Property paint-order has initial value normal
Fail Property paint-order inherits Pass Property paint-order inherits
Pass Property color-interpolation has initial value srgb Pass Property color-interpolation has initial value srgb
Pass Property color-interpolation inherits Pass Property color-interpolation inherits
Pass Property shape-rendering has initial value auto Pass Property shape-rendering has initial value auto

View file

@ -0,0 +1,8 @@
Harness status: OK
Found 3 tests
3 Pass
Pass e.style['paint-order'] = "normal stroke" should not set the property value
Pass e.style['paint-order'] = "fill fill" should not set the property value
Pass e.style['paint-order'] = "markers normal" should not set the property value

View file

@ -0,0 +1,21 @@
Harness status: OK
Found 16 tests
16 Pass
Pass e.style['paint-order'] = "normal" should set the property value
Pass e.style['paint-order'] = "fill" should set the property value
Pass e.style['paint-order'] = "stroke" should set the property value
Pass e.style['paint-order'] = "markers" should set the property value
Pass e.style['paint-order'] = "fill stroke" should set the property value
Pass e.style['paint-order'] = "fill markers" should set the property value
Pass e.style['paint-order'] = "stroke fill" should set the property value
Pass e.style['paint-order'] = "stroke markers" should set the property value
Pass e.style['paint-order'] = "markers fill" should set the property value
Pass e.style['paint-order'] = "markers stroke" should set the property value
Pass e.style['paint-order'] = "fill stroke markers" should set the property value
Pass e.style['paint-order'] = "fill markers stroke" should set the property value
Pass e.style['paint-order'] = "stroke fill markers" should set the property value
Pass e.style['paint-order'] = "stroke markers fill" should set the property value
Pass e.style['paint-order'] = "markers fill stroke" should set the property value
Pass e.style['paint-order'] = "markers stroke fill" should set the property value

View file

@ -0,0 +1,49 @@
Harness status: OK
Found 44 tests
44 Pass
Pass 'paint-order' computed style serialization, "normal" => "normal" (property)
Pass 'paint-order' computed style serialization, "fill" => "fill" (property)
Pass 'paint-order' computed style serialization, "stroke" => "stroke" (property)
Pass 'paint-order' computed style serialization, "markers" => "markers" (property)
Pass 'paint-order' computed style serialization, "fill stroke" => "fill" (property)
Pass 'paint-order' computed style serialization, "fill markers" => "fill markers" (property)
Pass 'paint-order' computed style serialization, "stroke fill" => "stroke" (property)
Pass 'paint-order' computed style serialization, "stroke markers" => "stroke markers" (property)
Pass 'paint-order' computed style serialization, "markers fill" => "markers" (property)
Pass 'paint-order' computed style serialization, "markers stroke" => "markers stroke" (property)
Pass 'paint-order' computed style serialization, "fill stroke markers" => "fill" (property)
Pass 'paint-order' computed style serialization, "fill markers stroke" => "fill markers" (property)
Pass 'paint-order' computed style serialization, "stroke fill markers" => "stroke" (property)
Pass 'paint-order' computed style serialization, "stroke markers fill" => "stroke markers" (property)
Pass 'paint-order' computed style serialization, "markers fill stroke" => "markers" (property)
Pass 'paint-order' computed style serialization, "markers stroke fill" => "markers stroke" (property)
Pass 'paint-order' computed style serialization, "foo" => "normal" (property)
Pass 'paint-order' computed style serialization, "fill foo" => "normal" (property)
Pass 'paint-order' computed style serialization, "stroke foo" => "normal" (property)
Pass 'paint-order' computed style serialization, "markers foo" => "normal" (property)
Pass 'paint-order' computed style serialization, "normal foo" => "normal" (property)
Pass 'paint-order' computed style serialization, "fill markers stroke foo" => "normal" (property)
Pass 'paint-order' computed style serialization, "normal" => "normal" (presentation attribute)
Pass 'paint-order' computed style serialization, "fill" => "fill" (presentation attribute)
Pass 'paint-order' computed style serialization, "stroke" => "stroke" (presentation attribute)
Pass 'paint-order' computed style serialization, "markers" => "markers" (presentation attribute)
Pass 'paint-order' computed style serialization, "fill stroke" => "fill" (presentation attribute)
Pass 'paint-order' computed style serialization, "fill markers" => "fill markers" (presentation attribute)
Pass 'paint-order' computed style serialization, "stroke fill" => "stroke" (presentation attribute)
Pass 'paint-order' computed style serialization, "stroke markers" => "stroke markers" (presentation attribute)
Pass 'paint-order' computed style serialization, "markers fill" => "markers" (presentation attribute)
Pass 'paint-order' computed style serialization, "markers stroke" => "markers stroke" (presentation attribute)
Pass 'paint-order' computed style serialization, "fill stroke markers" => "fill" (presentation attribute)
Pass 'paint-order' computed style serialization, "fill markers stroke" => "fill markers" (presentation attribute)
Pass 'paint-order' computed style serialization, "stroke fill markers" => "stroke" (presentation attribute)
Pass 'paint-order' computed style serialization, "stroke markers fill" => "stroke markers" (presentation attribute)
Pass 'paint-order' computed style serialization, "markers fill stroke" => "markers" (presentation attribute)
Pass 'paint-order' computed style serialization, "markers stroke fill" => "markers stroke" (presentation attribute)
Pass 'paint-order' computed style serialization, "foo" => "normal" (presentation attribute)
Pass 'paint-order' computed style serialization, "fill foo" => "normal" (presentation attribute)
Pass 'paint-order' computed style serialization, "stroke foo" => "normal" (presentation attribute)
Pass 'paint-order' computed style serialization, "markers foo" => "normal" (presentation attribute)
Pass 'paint-order' computed style serialization, "normal foo" => "normal" (presentation attribute)
Pass 'paint-order' computed style serialization, "fill markers stroke foo" => "normal" (presentation attribute)

View file

@ -1,8 +1,8 @@
Harness status: OK Harness status: OK
Found 47 tests Found 48 tests
44 Pass 45 Pass
3 Fail 3 Fail
Pass clip-path presentation attribute supported on an irrelevant element Pass clip-path presentation attribute supported on an irrelevant element
Pass clip-rule presentation attribute supported on an irrelevant element Pass clip-rule presentation attribute supported on an irrelevant element
@ -29,6 +29,7 @@ Pass mask-type presentation attribute supported on an irrelevant element
Pass mask presentation attribute supported on an irrelevant element Pass mask presentation attribute supported on an irrelevant element
Pass opacity presentation attribute supported on an irrelevant element Pass opacity presentation attribute supported on an irrelevant element
Pass overflow presentation attribute supported on an irrelevant element Pass overflow presentation attribute supported on an irrelevant element
Pass paint-order presentation attribute supported on an irrelevant element
Pass pointer-events presentation attribute supported on an irrelevant element Pass pointer-events presentation attribute supported on an irrelevant element
Pass shape-rendering presentation attribute supported on an irrelevant element Pass shape-rendering presentation attribute supported on an irrelevant element
Pass stop-color presentation attribute supported on an irrelevant element Pass stop-color presentation attribute supported on an irrelevant element

View file

@ -1,8 +1,8 @@
Harness status: OK Harness status: OK
Found 57 tests Found 58 tests
53 Pass 54 Pass
4 Fail 4 Fail
Pass clip-path presentation attribute supported on a relevant element Pass clip-path presentation attribute supported on a relevant element
Pass clip-rule presentation attribute supported on a relevant element Pass clip-rule presentation attribute supported on a relevant element
@ -32,6 +32,7 @@ Pass mask-type presentation attribute supported on a relevant element
Pass mask presentation attribute supported on a relevant element Pass mask presentation attribute supported on a relevant element
Pass opacity presentation attribute supported on a relevant element Pass opacity presentation attribute supported on a relevant element
Pass overflow presentation attribute supported on a relevant element Pass overflow presentation attribute supported on a relevant element
Pass paint-order presentation attribute supported on a relevant element
Pass pointer-events 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 Pass r presentation attribute supported on a relevant element
Pass rx presentation attribute supported on a relevant element Pass rx presentation attribute supported on a relevant element

View file

@ -1,8 +1,8 @@
Harness status: OK Harness status: OK
Found 47 tests Found 48 tests
44 Pass 45 Pass
3 Fail 3 Fail
Pass clip-path presentation attribute supported on an unknown SVG element Pass clip-path presentation attribute supported on an unknown SVG element
Pass clip-rule presentation attribute supported on an unknown SVG element Pass clip-rule presentation attribute supported on an unknown SVG element
@ -29,6 +29,7 @@ Pass mask-type presentation attribute supported on an unknown SVG element
Pass mask presentation attribute supported on an unknown SVG element Pass mask presentation attribute supported on an unknown SVG element
Pass opacity presentation attribute supported on an unknown SVG element Pass opacity presentation attribute supported on an unknown SVG element
Pass overflow presentation attribute supported on an unknown SVG element Pass overflow presentation attribute supported on an unknown SVG element
Pass paint-order presentation attribute supported on an unknown SVG element
Pass pointer-events presentation attribute supported on an unknown SVG element Pass pointer-events presentation attribute supported on an unknown SVG element
Pass shape-rendering presentation attribute supported on an unknown SVG element Pass shape-rendering presentation attribute supported on an unknown SVG element
Pass stop-color presentation attribute supported on an unknown SVG element Pass stop-color presentation attribute supported on an unknown SVG element

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:h="http://www.w3.org/1999/xhtml"
width="800px" height="800px">
<title>SVG Painting: parsing paint-order with invalid values</title>
<metadata>
<h:link rel="help" href="https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty"/>
<h:meta name="assert" content="paint-order supports only the grammar 'normal | [ fill || stroke || markers ]'."/>
<h:meta name="assert" content="paint-order uses the shortest serialization."/>
</metadata>
<g id="target"></g>
<h:script src="../../../resources/testharness.js"/>
<h:script src="../../../resources/testharnessreport.js"/>
<h:script src="../../../css/support/parsing-testcommon.js"/>
<script><![CDATA[
test_invalid_value("paint-order", "normal stroke");
test_invalid_value("paint-order", "fill fill");
test_invalid_value("paint-order", "markers normal");
]]></script>
</svg>

After

Width:  |  Height:  |  Size: 947 B

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:h="http://www.w3.org/1999/xhtml"
width="800px" height="800px">
<title>SVG Painting: parsing paint-order with valid values</title>
<metadata>
<h:link rel="help" href="https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty"/>
<h:meta name="assert" content="paint-order supports the full grammar 'normal | [ fill || stroke || markers ]'."/>
<h:meta name="assert" content="paint-order uses the shortest serialization."/>
</metadata>
<g id="target"></g>
<h:script src="../../../resources/testharness.js"/>
<h:script src="../../../resources/testharnessreport.js"/>
<h:script src="../../../css/support/parsing-testcommon.js"/>
<script><![CDATA[
test_valid_value("paint-order", "normal");
test_valid_value("paint-order", "fill");
test_valid_value("paint-order", "stroke");
test_valid_value("paint-order", "markers");
test_valid_value("paint-order", "fill stroke", "fill");
test_valid_value("paint-order", "fill markers");
test_valid_value("paint-order", "stroke fill", "stroke");
test_valid_value("paint-order", "stroke markers");
test_valid_value("paint-order", "markers fill", "markers");
test_valid_value("paint-order", "markers stroke");
test_valid_value("paint-order", "fill stroke markers", "fill");
test_valid_value("paint-order", "fill markers stroke", "fill markers");
test_valid_value("paint-order", "stroke fill markers", "stroke");
test_valid_value("paint-order", "stroke markers fill", "stroke markers");
test_valid_value("paint-order", "markers fill stroke", "markers");
test_valid_value("paint-order", "markers stroke fill", "markers stroke");
]]></script>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,62 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:h="http://www.w3.org/1999/xhtml">
<title>'paint-order' computed style serialization</title>
<h:script src="../../../resources/testharness.js"/>
<h:script src="../../../resources/testharnessreport.js"/>
<text id="text" x="100" y="100"/>
<script><![CDATA[
'use strict';
function make_tests(check, type) {
let tests = [
// Single keyword
["normal", "normal"],
["fill", "fill"],
["stroke", "stroke"],
["markers", "markers"],
// Two keywords
["fill stroke", "fill"],
["fill markers", "fill markers"],
["stroke fill", "stroke"],
["stroke markers", "stroke markers"],
["markers fill", "markers"],
["markers stroke", "markers stroke"],
// Three keywords
["fill stroke markers", "fill"],
["fill markers stroke", "fill markers"],
["stroke fill markers", "stroke"],
["stroke markers fill", "stroke markers"],
["markers fill stroke", "markers"],
["markers stroke fill", "markers stroke"],
// Invalid
["foo", "normal"],
["fill foo", "normal"],
["stroke foo", "normal"],
["markers foo", "normal"],
["normal foo", "normal"],
["fill markers stroke foo", "normal"],
];
for (let [value, expected] of tests) {
test(() => {
check(value, expected);
}, `${document.title}, "${value}" => "${expected}" (${type})`);
}
}
const text = document.getElementById("text");
make_tests((value, expected) => {
text.setAttribute("style", "paint-order: " + value);
let actual = getComputedStyle(text).paintOrder;
text.removeAttribute("style");
assert_equals(actual, expected, value);
}, "property");
make_tests((value, expected) => {
text.setAttribute("paint-order", value);
let actual = getComputedStyle(text).paintOrder;
text.removeAttribute("paint-order");
assert_equals(actual, expected, value);
}, "presentation attribute");
]]>
</script>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB