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();
}
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
{
auto const& value = property(PropertyID::WillChange);

View file

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

View file

@ -118,6 +118,8 @@ using CursorData = Variant<NonnullRefPtr<CursorStyleValue const>, CursorPredefin
using ListStyleType = Variant<CounterStyleNameKeyword, String>;
using PaintOrderList = Array<PaintOrder, 3>;
class InitialValues {
public:
static AspectRatio aspect_ratio() { return AspectRatio { true, {} }; }
@ -259,6 +261,7 @@ public:
}
static CSS::ScrollbarWidth scrollbar_width() { return CSS::ScrollbarWidth::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(); }
};
@ -590,6 +593,7 @@ public:
CSS::ClipRule clip_rule() const { return m_inherited.clip_rule; }
Color flood_color() const { return m_noninherited.flood_color; }
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& cy() const { return m_noninherited.cy; }
@ -702,6 +706,7 @@ protected:
CSS::FillRule fill_rule { InitialValues::fill_rule() };
Optional<SVGPaint> stroke;
float fill_opacity { InitialValues::fill_opacity() };
PaintOrderList paint_order { InitialValues::paint_order() };
Vector<Variant<LengthPercentage, NumberOrCalculated>> stroke_dasharray;
LengthPercentage stroke_dashoffset { InitialValues::stroke_dashoffset() };
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_opacity(float value) { m_noninherited.flood_opacity = 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_cy(LengthPercentage cy) { m_noninherited.cy = move(cy); }

View file

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

View file

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

View file

@ -454,6 +454,7 @@ private:
RefPtr<StyleValue const> parse_math_depth_value(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_paint_order_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_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())
return parsed_value.release_nonnull();
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:
if (auto parsed_value = parse_place_content_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
@ -3856,6 +3860,84 @@ RefPtr<StyleValue const> Parser::parse_overflow_value(TokenStream<ComponentValue
{ *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)
{
auto transaction = tokens.begin_transaction();

View file

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

View file

@ -854,6 +854,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
else if (stroke_width.is_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_paint_order(computed_style.paint_order());
auto const& mask_image = computed_style.property(CSS::PropertyID::MaskImage);
if (mask_image.is_url()) {

View file

@ -77,6 +77,7 @@ static ReadonlySpan<NamedPropertyID> attribute_style_properties()
NamedPropertyID(CSS::PropertyID::MaskType),
NamedPropertyID(CSS::PropertyID::Opacity),
NamedPropertyID(CSS::PropertyID::Overflow),
NamedPropertyID(CSS::PropertyID::PaintOrder),
NamedPropertyID(CSS::PropertyID::PointerEvents),
NamedPropertyID(CSS::PropertyID::R, { SVG::TagNames::circle }),
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-shift",
"math-style",
"paint-order",
"pointer-events",
"quotes",
"shape-rendering",

View file

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

View file

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

View file

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

View file

@ -2,15 +2,15 @@ Harness status: OK
Found 20 tests
15 Pass
5 Fail
14 Pass
6 Fail
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 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; 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: 1px !important; 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
30 Pass
8 Fail
32 Pass
6 Fail
Pass Property fill has initial value rgb(0, 0, 0)
Pass Property fill inherits
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-end has initial value none
Fail Property marker-end inherits
Fail Property paint-order has initial value normal
Fail Property paint-order inherits
Pass Property paint-order has initial value normal
Pass Property paint-order inherits
Pass Property color-interpolation has initial value srgb
Pass Property color-interpolation inherits
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
Found 47 tests
Found 48 tests
44 Pass
45 Pass
3 Fail
Pass clip-path 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 opacity 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 shape-rendering 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
Found 57 tests
Found 58 tests
53 Pass
54 Pass
4 Fail
Pass clip-path 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 opacity 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 r 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
Found 47 tests
Found 48 tests
44 Pass
45 Pass
3 Fail
Pass clip-path 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 opacity 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 shape-rendering 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