From 84150f972f9df908c5cac5175ac165b117055cea Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Fri, 29 Nov 2024 22:44:14 +1100 Subject: [PATCH] LibWeb: Properly serialize position/edge style values --- Libraries/LibWeb/CSS/Parser/Parser.cpp | 59 ++- .../CSS/ResolvedCSSStyleDeclaration.cpp | 27 -- Libraries/LibWeb/CSS/StyleProperties.cpp | 4 +- .../CSS/StyleValues/BasicShapeStyleValue.cpp | 22 +- .../CSS/StyleValues/BasicShapeStyleValue.h | 12 +- .../LibWeb/CSS/StyleValues/EdgeStyleValue.cpp | 35 +- .../LibWeb/CSS/StyleValues/EdgeStyleValue.h | 30 +- .../CSS/StyleValues/PositionStyleValue.cpp | 6 +- .../CSS/StyleValues/PositionStyleValue.h | 4 +- .../CSS/StyleValues/ShorthandStyleValue.cpp | 39 +- Libraries/LibWeb/Layout/Node.cpp | 4 +- .../expected/HTML/background-shorthand.txt | 2 +- .../Text/expected/background-position-xy.txt | 4 +- ...upported-properties-and-default-values.txt | 18 +- .../Text/expected/css/calc-coverage.txt | 8 +- .../css/getComputedStyle-print-all.txt | 6 +- .../Text/expected/position-serialization.txt | 18 + .../parsing/background-position-valid.txt | 36 ++ .../wpt-import/css/cssom/serialize-values.txt | 342 +++++++++--------- .../Text/input/position-serialization.html | 25 ++ .../parsing/background-position-valid.html | 48 +++ 21 files changed, 461 insertions(+), 288 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/position-serialization.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/background-position-valid.txt create mode 100644 Tests/LibWeb/Text/input/position-serialization.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/background-position-valid.html diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index 55f0988eb7d..ca7932a8cd6 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -3913,12 +3913,6 @@ RefPtr Parser::parse_position_value(TokenStream NonnullRefPtr { - if (position_edge == PositionEdge::Center) - return EdgeStyleValue::create(is_horizontal ? PositionEdge::Left : PositionEdge::Top, Percentage { 50 }); - return EdgeStyleValue::create(position_edge, Length::make_px(0)); - }; - // = [ // [ left | center | right | top | bottom | ] // | @@ -3945,21 +3939,21 @@ RefPtr Parser::parse_position_value(TokenStream ] if (auto maybe_percentage = parse_length_percentage(token); maybe_percentage.has_value()) { transaction.commit(); - return PositionStyleValue::create(EdgeStyleValue::create(PositionEdge::Left, *maybe_percentage), make_edge_style_value(PositionEdge::Center, false)); + return PositionStyleValue::create(EdgeStyleValue::create({}, *maybe_percentage), EdgeStyleValue::create(PositionEdge::Center, {})); } return nullptr; @@ -3994,7 +3988,7 @@ RefPtr Parser::parse_position_value(TokenStream Parser::parse_position_value(TokenStream ] @@ -4157,15 +4151,15 @@ RefPtr Parser::parse_position_value(TokenStream NonnullRefPtr { + auto to_style_value = [&](PositionAndMaybeLength const& group) -> NonnullRefPtr { if (group.position == PositionEdge::Center) - return EdgeStyleValue::create(is_horizontal ? PositionEdge::Left : PositionEdge::Top, Percentage { 50 }); + return EdgeStyleValue::create(PositionEdge::Center, {}); - return EdgeStyleValue::create(group.position, group.length.value_or(Length::make_px(0))); + return EdgeStyleValue::create(group.position, group.length); }; transaction.commit(); - return PositionStyleValue::create(to_style_value(group1, true), to_style_value(group2, false)); + return PositionStyleValue::create(to_style_value(group1), to_style_value(group2)); }; // Note: The alternatives must be attempted in this order since shorter alternatives can match a prefix of longer ones. @@ -4527,16 +4521,7 @@ static Optional style_value_to_length_percentage(auto value) RefPtr Parser::parse_single_background_position_x_or_y_value(TokenStream& tokens, PropertyID property) { - PositionEdge relative_edge {}; - if (property == PropertyID::BackgroundPositionX) { - // [ center | [ [ left | right | x-start | x-end ]? ? ]! ]# - relative_edge = PositionEdge::Left; - } else if (property == PropertyID::BackgroundPositionY) { - // [ center | [ [ top | bottom | y-start | y-end ]? ? ]! ]# - relative_edge = PositionEdge::Top; - } else { - VERIFY_NOT_REACHED(); - } + Optional relative_edge {}; auto transaction = tokens.begin_transaction(); if (!tokens.has_next_token()) @@ -4550,10 +4535,10 @@ RefPtr Parser::parse_single_background_position_x_or_y_value(Toke auto keyword = value->to_keyword(); if (keyword == Keyword::Center) { transaction.commit(); - return EdgeStyleValue::create(relative_edge, Percentage { 50 }); + return EdgeStyleValue::create(PositionEdge::Center, {}); } if (auto edge = keyword_to_position_edge(keyword); edge.has_value()) { - relative_edge = *edge; + relative_edge = edge; } else { return nullptr; } @@ -4561,7 +4546,7 @@ RefPtr Parser::parse_single_background_position_x_or_y_value(Toke value = parse_css_value_for_property(property, tokens); if (!value) { transaction.commit(); - return EdgeStyleValue::create(relative_edge, Length::make_px(0)); + return EdgeStyleValue::create(relative_edge, {}); } } } @@ -4572,9 +4557,21 @@ RefPtr Parser::parse_single_background_position_x_or_y_value(Toke return EdgeStyleValue::create(relative_edge, *offset); } + if (!relative_edge.has_value()) { + if (property == PropertyID::BackgroundPositionX) { + // [ center | [ [ left | right | x-start | x-end ]? ? ]! ]# + relative_edge = PositionEdge::Left; + } else if (property == PropertyID::BackgroundPositionY) { + // [ center | [ [ top | bottom | y-start | y-end ]? ? ]! ]# + relative_edge = PositionEdge::Top; + } else { + VERIFY_NOT_REACHED(); + } + } + // If no offset is provided create this element but with an offset of default value of zero transaction.commit(); - return EdgeStyleValue::create(relative_edge, Length::make_px(0)); + return EdgeStyleValue::create(relative_edge, {}); } RefPtr Parser::parse_single_background_repeat_value(TokenStream& tokens) diff --git a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp index 2e0dfdf94d7..c2ffff6d0b9 100644 --- a/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp @@ -82,20 +82,6 @@ String ResolvedCSSStyleDeclaration::item(size_t index) const return string_from_property_id(property_id).to_string(); } -static NonnullRefPtr style_value_for_background_property(Layout::NodeWithStyle const& layout_node, Function(BackgroundLayerData const&)> callback, Function()> default_value) -{ - auto const& background_layers = layout_node.background_layers(); - if (background_layers.is_empty()) - return default_value(); - if (background_layers.size() == 1) - return callback(background_layers.first()); - StyleValueVector values; - values.ensure_capacity(background_layers.size()); - for (auto const& layer : background_layers) - values.unchecked_append(callback(layer)); - return StyleValueList::create(move(values), StyleValueList::Separator::Comma); -} - static NonnullRefPtr style_value_for_length_percentage(LengthPercentage const& length_percentage) { if (length_percentage.is_auto()) @@ -453,19 +439,6 @@ RefPtr ResolvedCSSStyleDeclaration::style_value_for_propert // NOTE: This is handled inside the `default` case. // NOTE: Everything below is a shorthand that requires some manual construction. - case PropertyID::BackgroundPosition: - return style_value_for_background_property( - layout_node, - [](auto& layer) -> NonnullRefPtr { - return PositionStyleValue::create( - EdgeStyleValue::create(layer.position_edge_x, layer.position_offset_x), - EdgeStyleValue::create(layer.position_edge_y, layer.position_offset_y)); - }, - []() -> NonnullRefPtr { - return PositionStyleValue::create( - EdgeStyleValue::create(PositionEdge::Left, Percentage(0)), - EdgeStyleValue::create(PositionEdge::Top, Percentage(0))); - }); case PropertyID::Border: { auto width = style_value_for_property(layout_node, PropertyID::BorderWidth); auto style = style_value_for_property(layout_node, PropertyID::BorderStyle); diff --git a/Libraries/LibWeb/CSS/StyleProperties.cpp b/Libraries/LibWeb/CSS/StyleProperties.cpp index 2e452988afc..b67f09e9cde 100644 --- a/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -1325,12 +1325,12 @@ CSS::ObjectPosition StyleProperties::object_position() const auto const& edge_y = position.edge_y(); if (edge_x->is_edge()) { auto const& edge = edge_x->as_edge(); - object_position.edge_x = edge.edge(); + object_position.edge_x = edge.edge().value_or(PositionEdge::Left); object_position.offset_x = edge.offset(); } if (edge_y->is_edge()) { auto const& edge = edge_y->as_edge(); - object_position.edge_y = edge.edge(); + object_position.edge_y = edge.edge().value_or(PositionEdge::Top); object_position.offset_y = edge.offset(); } return object_position; diff --git a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp index f496b59038c..f64171bf940 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp @@ -34,7 +34,7 @@ Gfx::Path Inset::to_path(CSSPixelRect reference_box, Layout::Node const& node) c return path_from_resolved_rect(top, right, bottom, left); } -String Inset::to_string() const +String Inset::to_string(CSSStyleValue::SerializationMode) const { return MUST(String::formatted("inset({} {} {} {})", inset_box.top(), inset_box.right(), inset_box.bottom(), inset_box.left())); } @@ -49,7 +49,7 @@ Gfx::Path Xywh::to_path(CSSPixelRect reference_box, Layout::Node const& node) co return path_from_resolved_rect(top, right, bottom, left); } -String Xywh::to_string() const +String Xywh::to_string(CSSStyleValue::SerializationMode) const { return MUST(String::formatted("xywh({} {} {} {})", x, y, width, height)); } @@ -68,7 +68,7 @@ Gfx::Path Rect::to_path(CSSPixelRect reference_box, Layout::Node const& node) co return path_from_resolved_rect(top, max(right, left), max(bottom, top), left); } -String Rect::to_string() const +String Rect::to_string(CSSStyleValue::SerializationMode) const { return MUST(String::formatted("rect({} {} {} {})", box.top(), box.right(), box.bottom(), box.left())); } @@ -123,9 +123,9 @@ Gfx::Path Circle::to_path(CSSPixelRect reference_box, Layout::Node const& node) return path; } -String Circle::to_string() const +String Circle::to_string(CSSStyleValue::SerializationMode mode) const { - return MUST(String::formatted("circle({} at {})", radius_to_string(radius), position->to_string(CSSStyleValue::SerializationMode::Normal))); + return MUST(String::formatted("circle({} at {})", radius_to_string(radius), position->to_string(mode))); } Gfx::Path Ellipse::to_path(CSSPixelRect reference_box, Layout::Node const& node) const @@ -168,9 +168,9 @@ Gfx::Path Ellipse::to_path(CSSPixelRect reference_box, Layout::Node const& node) return path; } -String Ellipse::to_string() const +String Ellipse::to_string(CSSStyleValue::SerializationMode mode) const { - return MUST(String::formatted("ellipse({} {} at {})", radius_to_string(radius_x), radius_to_string(radius_y), position->to_string(CSSStyleValue::SerializationMode::Normal))); + return MUST(String::formatted("ellipse({} {} at {})", radius_to_string(radius_x), radius_to_string(radius_y), position->to_string(mode))); } Gfx::Path Polygon::to_path(CSSPixelRect reference_box, Layout::Node const& node) const @@ -193,7 +193,7 @@ Gfx::Path Polygon::to_path(CSSPixelRect reference_box, Layout::Node const& node) return path; } -String Polygon::to_string() const +String Polygon::to_string(CSSStyleValue::SerializationMode) const { StringBuilder builder; builder.append("polygon("sv); @@ -220,10 +220,10 @@ Gfx::Path BasicShapeStyleValue::to_path(CSSPixelRect reference_box, Layout::Node }); } -String BasicShapeStyleValue::to_string(SerializationMode) const +String BasicShapeStyleValue::to_string(SerializationMode mode) const { - return m_basic_shape.visit([](auto const& shape) { - return shape.to_string(); + return m_basic_shape.visit([mode](auto const& shape) { + return shape.to_string(mode); }); } diff --git a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h index 529a1820194..e9ec143b8a7 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h @@ -17,7 +17,7 @@ namespace Web::CSS { struct Inset { Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Inset const&) const = default; @@ -26,7 +26,7 @@ struct Inset { struct Xywh { Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Xywh const&) const = default; @@ -38,7 +38,7 @@ struct Xywh { struct Rect { Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Rect const&) const = default; @@ -54,7 +54,7 @@ using ShapeRadius = Variant; struct Circle { Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Circle const&) const = default; @@ -64,7 +64,7 @@ struct Circle { struct Ellipse { Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Ellipse const&) const = default; @@ -81,7 +81,7 @@ struct Polygon { }; Gfx::Path to_path(CSSPixelRect reference_box, Layout::Node const&) const; - String to_string() const; + String to_string(CSSStyleValue::SerializationMode) const; bool operator==(Polygon const&) const = default; diff --git a/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.cpp index 50345f10b49..8b3af477386 100644 --- a/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.cpp @@ -8,9 +8,40 @@ namespace Web::CSS { -String EdgeStyleValue::to_string(SerializationMode) const +String EdgeStyleValue::to_string(SerializationMode mode) const { - return MUST(String::formatted("{} {}", CSS::to_string(m_properties.edge), m_properties.offset.to_string())); + if (mode == CSSStyleValue::SerializationMode::ResolvedValue) { + if (edge() == PositionEdge::Right || edge() == PositionEdge::Bottom) { + if (offset().is_percentage()) { + auto flipped_percentage = 100 - offset().percentage().value(); + return Percentage(flipped_percentage).to_string(); + } + Vector> sum_parts; + sum_parts.append(NumericCalculationNode::create(Percentage(100))); + if (offset().is_length()) { + sum_parts.append(NegateCalculationNode::create(NumericCalculationNode::create(offset().length()))); + } else { + // FIXME: Flip calculated offsets (convert CSSMathValue to CalculationNode, then negate and append) + return to_string(CSSStyleValue::SerializationMode::Normal); + } + auto flipped_absolute = CSSMathValue::create(SumCalculationNode::create(move(sum_parts)), CSSNumericType(CSSNumericType::BaseType::Length, 1)); + return flipped_absolute->to_string(mode); + } + return offset().to_string(); + } + + StringBuilder builder; + + if (m_properties.edge.has_value()) + builder.append(CSS::to_string(m_properties.edge.value())); + + if (m_properties.edge.has_value() && m_properties.offset.has_value()) + builder.append(' '); + + if (m_properties.offset.has_value()) + builder.append(m_properties.offset->to_string()); + + return builder.to_string_without_validation(); } } diff --git a/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h index 5e81d6ea705..fa9bdc3d833 100644 --- a/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h @@ -14,31 +14,45 @@ namespace Web::CSS { class EdgeStyleValue final : public StyleValueWithDefaultOperators { public: - static ValueComparingNonnullRefPtr create(PositionEdge edge, LengthPercentage const& offset) + static ValueComparingNonnullRefPtr create(Optional edge, Optional const& offset) { - VERIFY(edge != PositionEdge::Center); return adopt_ref(*new (nothrow) EdgeStyleValue(edge, offset)); } virtual ~EdgeStyleValue() override = default; - // NOTE: `center` is converted to `left 50%` or `top 50%` in parsing, so is never returned here. - PositionEdge edge() const { return m_properties.edge; } - LengthPercentage const& offset() const { return m_properties.offset; } + Optional edge() const + { + if (m_properties.edge == PositionEdge::Center) + return {}; + + return m_properties.edge; + } + + LengthPercentage offset() const + { + if (m_properties.edge == PositionEdge::Center) + return Percentage(50); + + if (!m_properties.offset.has_value()) + return Percentage(0); + + return m_properties.offset.value(); + } virtual String to_string(SerializationMode) const override; bool properties_equal(EdgeStyleValue const& other) const { return m_properties == other.m_properties; } private: - EdgeStyleValue(PositionEdge edge, LengthPercentage const& offset) + EdgeStyleValue(Optional edge, Optional const& offset) : StyleValueWithDefaultOperators(Type::Edge) , m_properties { .edge = edge, .offset = offset } { } struct Properties { - PositionEdge edge; - LengthPercentage offset; + Optional edge; + Optional offset; bool operator==(Properties const&) const = default; } m_properties; }; diff --git a/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.cpp index 250ee6e5b16..f470aa04603 100644 --- a/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.cpp @@ -13,10 +13,8 @@ namespace Web::CSS { bool PositionStyleValue::is_center() const { - return (edge_x()->edge() == PositionEdge::Left - && edge_x()->offset().is_percentage() && edge_x()->offset().percentage() == Percentage { 50 }) - && (edge_y()->edge() == PositionEdge::Top - && edge_y()->offset().is_percentage() && edge_y()->offset().percentage() == Percentage { 50 }); + return (edge_x()->offset().is_percentage() && edge_x()->offset().percentage() == Percentage { 50 }) + && (edge_y()->offset().is_percentage() && edge_y()->offset().percentage() == Percentage { 50 }); } CSSPixelPoint PositionStyleValue::resolved(Layout::Node const& node, CSSPixelRect const& rect) const diff --git a/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.h index cb212326711..dcd3c10b280 100644 --- a/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/PositionStyleValue.h @@ -25,8 +25,8 @@ public: static ValueComparingNonnullRefPtr create_center() { return adopt_ref(*new (nothrow) PositionStyleValue( - EdgeStyleValue::create(PositionEdge::Left, Percentage { 50 }), - EdgeStyleValue::create(PositionEdge::Top, Percentage { 50 }))); + EdgeStyleValue::create(PositionEdge::Center, {}), + EdgeStyleValue::create(PositionEdge::Center, {}))); } virtual ~PositionStyleValue() override = default; diff --git a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp index 9f87276b578..5f9963e2240 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp @@ -65,6 +65,8 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const auto color = longhand(PropertyID::BackgroundColor); auto image = longhand(PropertyID::BackgroundImage); auto position = longhand(PropertyID::BackgroundPosition); + auto position_x = position->as_shorthand().longhand(PropertyID::BackgroundPositionX); + auto position_y = position->as_shorthand().longhand(PropertyID::BackgroundPositionY); auto size = longhand(PropertyID::BackgroundSize); auto repeat = longhand(PropertyID::BackgroundRepeat); auto attachment = longhand(PropertyID::BackgroundAttachment); @@ -75,10 +77,10 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const return style_value->is_value_list() ? style_value->as_value_list().size() : 1; }; - auto layer_count = max(get_layer_count(image), max(get_layer_count(position), max(get_layer_count(size), max(get_layer_count(repeat), max(get_layer_count(attachment), max(get_layer_count(origin), get_layer_count(clip))))))); + auto layer_count = max(get_layer_count(image), max(get_layer_count(position_x), max(get_layer_count(position_y), max(get_layer_count(size), max(get_layer_count(repeat), max(get_layer_count(attachment), max(get_layer_count(origin), get_layer_count(clip)))))))); if (layer_count == 1) { - return MUST(String::formatted("{} {} {} {} {} {} {} {}", color->to_string(mode), image->to_string(mode), position->to_string(mode), size->to_string(mode), repeat->to_string(mode), attachment->to_string(mode), origin->to_string(mode), clip->to_string(mode))); + return MUST(String::formatted("{} {} {} {} {} {} {} {} {}", color->to_string(mode), image->to_string(mode), position_x->to_string(mode), position_y->to_string(mode), size->to_string(mode), repeat->to_string(mode), attachment->to_string(mode), origin->to_string(mode), clip->to_string(mode))); } auto get_layer_value_string = [mode](ValueComparingRefPtr const& style_value, size_t index) { @@ -93,7 +95,38 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const builder.append(", "sv); if (i == layer_count - 1) builder.appendff("{} ", color->to_string(mode)); - builder.appendff("{} {} {} {} {} {} {}", get_layer_value_string(image, i), get_layer_value_string(position, i), get_layer_value_string(size, i), get_layer_value_string(repeat, i), get_layer_value_string(attachment, i), get_layer_value_string(origin, i), get_layer_value_string(clip, i)); + builder.appendff("{} {} {} {} {} {} {} {}", get_layer_value_string(image, i), get_layer_value_string(position_x, i), get_layer_value_string(position_y, i), get_layer_value_string(size, i), get_layer_value_string(repeat, i), get_layer_value_string(attachment, i), get_layer_value_string(origin, i), get_layer_value_string(clip, i)); + } + + return MUST(builder.to_string()); + } + case Web::CSS::PropertyID::BackgroundPosition: { + auto x_edges = longhand(PropertyID::BackgroundPositionX); + auto y_edges = longhand(PropertyID::BackgroundPositionY); + + auto get_layer_count = [](auto style_value) -> size_t { + return style_value->is_value_list() ? style_value->as_value_list().size() : 1; + }; + + // FIXME: The spec is unclear about how differing layer counts should be handled + auto layer_count = max(get_layer_count(x_edges), get_layer_count(y_edges)); + + if (layer_count == 1) { + return MUST(String::formatted("{} {}", x_edges->to_string(mode), y_edges->to_string(mode))); + } + + auto get_layer_value_string = [mode](ValueComparingRefPtr const& style_value, size_t index) { + if (style_value->is_value_list()) + return style_value->as_value_list().value_at(index, true)->to_string(mode); + return style_value->to_string(mode); + }; + + StringBuilder builder; + for (size_t i = 0; i < layer_count; i++) { + if (i) + builder.append(", "sv); + + builder.appendff("{} {}", get_layer_value_string(x_edges, i), get_layer_value_string(y_edges, i)); } return MUST(builder.to_string()); diff --git a/Libraries/LibWeb/Layout/Node.cpp b/Libraries/LibWeb/Layout/Node.cpp index ec54f02dd4c..a92d2329466 100644 --- a/Libraries/LibWeb/Layout/Node.cpp +++ b/Libraries/LibWeb/Layout/Node.cpp @@ -428,13 +428,13 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style) if (auto position_value = value_for_layer(x_positions, layer_index); position_value && position_value->is_edge()) { auto& position = position_value->as_edge(); - layer.position_edge_x = position.edge(); + layer.position_edge_x = position.edge().value_or(CSS::PositionEdge::Left); layer.position_offset_x = position.offset(); } if (auto position_value = value_for_layer(y_positions, layer_index); position_value && position_value->is_edge()) { auto& position = position_value->as_edge(); - layer.position_edge_y = position.edge(); + layer.position_edge_y = position.edge().value_or(CSS::PositionEdge::Top); layer.position_offset_y = position.offset(); }; diff --git a/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt b/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt index 5100c84795b..8f3128cc429 100644 --- a/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt +++ b/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt @@ -1,4 +1,4 @@ -style.cssText = background-color: yellow; background-image: none; background-position-x: left 0%; background-position-y: top 0%; background-size: auto auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box; +style.cssText = background-color: yellow; background-image: none; background-position-x: 0%; background-position-y: 0%; background-size: auto auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box; style.length = 9 style[] = 1. background-color diff --git a/Tests/LibWeb/Text/expected/background-position-xy.txt b/Tests/LibWeb/Text/expected/background-position-xy.txt index dd4e27cb9fb..077355b134d 100644 --- a/Tests/LibWeb/Text/expected/background-position-xy.txt +++ b/Tests/LibWeb/Text/expected/background-position-xy.txt @@ -1,2 +1,2 @@ -right 0px -bottom 0px +100% +100% diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt index 7bf960ade92..b4a657217fe 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-all-supported-properties-and-default-values.txt @@ -159,7 +159,7 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'aspect-ratio': 'auto' 'backdropFilter': 'none' 'backdrop-filter': 'none' -'background': 'rgba(0, 0, 0, 0) none left 0% top 0% auto auto repeat scroll padding-box border-box' +'background': 'rgba(0, 0, 0, 0) none 0% 0% auto auto repeat scroll padding-box border-box' 'backgroundAttachment': 'scroll' 'background-attachment': 'scroll' 'backgroundClip': 'border-box' @@ -170,12 +170,12 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'background-image': 'none' 'backgroundOrigin': 'padding-box' 'background-origin': 'padding-box' -'backgroundPosition': 'left 0% top 0%' -'background-position': 'left 0% top 0%' -'backgroundPositionX': 'left 0%' -'background-position-x': 'left 0%' -'backgroundPositionY': 'top 0%' -'background-position-y': 'top 0%' +'backgroundPosition': '0% 0%' +'background-position': '0% 0%' +'backgroundPositionX': '0%' +'background-position-x': '0%' +'backgroundPositionY': '0%' +'background-position-y': '0%' 'backgroundRepeat': 'repeat' 'background-repeat': 'repeat' 'backgroundSize': 'auto auto' @@ -430,8 +430,8 @@ All supported properties and their default values exposed from CSSStyleDeclarati 'min-width': 'auto' 'objectFit': 'fill' 'object-fit': 'fill' -'objectPosition': 'left 50% top 50%' -'object-position': 'left 50% top 50%' +'objectPosition': '50% 50%' +'object-position': '50% 50%' 'opacity': '1' 'order': '0' 'outline': 'rgb(0, 0, 0) none medium' diff --git a/Tests/LibWeb/Text/expected/css/calc-coverage.txt b/Tests/LibWeb/Text/expected/css/calc-coverage.txt index fc2f6a083a2..0e254b85def 100644 --- a/Tests/LibWeb/Text/expected/css/calc-coverage.txt +++ b/Tests/LibWeb/Text/expected/css/calc-coverage.txt @@ -8,10 +8,10 @@ backdrop-filter: 'grayscale(calc(2%))' -> 'grayscale(calc(2%))' backdrop-filter: 'grayscale(calc(2% * var(--n)))' -> 'grayscale(calc(2% * 2))' backdrop-filter: 'grayscale(calc(0.02))' -> 'grayscale(calc(0.02))' backdrop-filter: 'grayscale(calc(0.02 * var(--n)))' -> 'grayscale(calc(0.02 * 2))' -background-position-x: 'calc(2px)' -> 'left calc(2px)' -background-position-x: 'calc(2px * var(--n))' -> 'left calc(2px * 2)' -background-position-y: 'calc(2%)' -> 'top calc(2%)' -background-position-y: 'calc(2% * var(--n))' -> 'top 4%' +background-position-x: 'calc(2px)' -> 'calc(2px)' +background-position-x: 'calc(2px * var(--n))' -> 'calc(2px * 2)' +background-position-y: 'calc(2%)' -> 'calc(2%)' +background-position-y: 'calc(2% * var(--n))' -> '4%' background-size: 'calc(2px * var(--n)) calc(2%)' -> 'calc(2px * 2) 2%' background-size: 'calc(2px * var(--n)) calc(2% * var(--n))' -> 'calc(2px * 2) 4%' border-bottom-left-radius: 'calc(2px)' -> 'calc(2px)' diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt index 175a3646fcc..dce83098311 100644 --- a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt +++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt @@ -71,8 +71,8 @@ background-clip: border-box background-color: rgba(0, 0, 0, 0) background-image: none background-origin: padding-box -background-position-x: left 0% -background-position-y: top 0% +background-position-x: 0% +background-position-y: 0% background-repeat: repeat background-size: auto auto border-bottom-color: rgb(0, 0, 0) @@ -154,7 +154,7 @@ min-height: auto min-inline-size: 0px min-width: auto object-fit: fill -object-position: left 50% top 50% +object-position: 50% 50% opacity: 1 order: 0 outline-color: rgb(0, 0, 0) diff --git a/Tests/LibWeb/Text/expected/position-serialization.txt b/Tests/LibWeb/Text/expected/position-serialization.txt new file mode 100644 index 00000000000..cb505b6ee40 --- /dev/null +++ b/Tests/LibWeb/Text/expected/position-serialization.txt @@ -0,0 +1,18 @@ +inline: center center +computed: 50% 50% +inline: left bottom +computed: 0% 100% +inline: center top +computed: 50% 0% +inline: center top 20% +computed: 50% 20% +inline: left 10px top 20% +computed: 10px 20% +inline: 10px top +computed: 10px 0% +inline: right 10px bottom 20% +computed: calc(100% + (0 - 10px)) 80% +inline: center center, left bottom +computed: 50% 50%, 0% 100% +inline: left 10px bottom 20%, right 10px top 20% +computed: 10px 80%, calc(100% + (0 - 10px)) 20% diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/background-position-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/background-position-valid.txt new file mode 100644 index 00000000000..1927ba08ce4 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/background-position-valid.txt @@ -0,0 +1,36 @@ +Harness status: OK + +Found 31 tests + +31 Pass +Pass e.style['background-position'] = "1px" should set the property value +Pass e.style['background-position'] = "1px center" should set the property value +Pass e.style['background-position'] = "-2% -3%" should set the property value +Pass e.style['background-position'] = "5% top" should set the property value +Pass e.style['background-position'] = "center" should set the property value +Pass e.style['background-position'] = "center center" should set the property value +Pass e.style['background-position'] = "center 6px" should set the property value +Pass e.style['background-position'] = "center left" should set the property value +Pass e.style['background-position'] = "center right 7%" should set the property value +Pass e.style['background-position'] = "center bottom" should set the property value +Pass e.style['background-position'] = "center top 8px" should set the property value +Pass e.style['background-position'] = "left" should set the property value +Pass e.style['background-position'] = "right 9%" should set the property value +Pass e.style['background-position'] = "left 10px center" should set the property value +Pass e.style['background-position'] = "right 11% bottom" should set the property value +Pass e.style['background-position'] = "left 12px top 13px" should set the property value +Pass e.style['background-position'] = "right center" should set the property value +Pass e.style['background-position'] = "left bottom" should set the property value +Pass e.style['background-position'] = "right top 14%" should set the property value +Pass e.style['background-position'] = "bottom" should set the property value +Pass e.style['background-position'] = "top 15px center" should set the property value +Pass e.style['background-position'] = "bottom 16% left" should set the property value +Pass e.style['background-position'] = "top 17px right 18px" should set the property value +Pass e.style['background-position'] = "bottom center" should set the property value +Pass e.style['background-position'] = "top left" should set the property value +Pass e.style['background-position'] = "bottom right 19%" should set the property value +Pass e.style['background-position'] = "20% 0%" should set the property value +Pass e.style['background-position'] = "0% 0%" should set the property value +Pass e.style['background-position'] = "0%" should set the property value +Pass e.style['background-position'] = "0% center" should set the property value +Pass e.style['background-position'] = "center 0%" should set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-values.txt b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-values.txt index a7b5f000635..702473bde52 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-values.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/serialize-values.txt @@ -2,8 +2,8 @@ Harness status: OK Found 687 tests -485 Pass -202 Fail +654 Pass +33 Fail Pass background-attachment: scroll Pass background-attachment: fixed Pass background-attachment: inherit @@ -17,175 +17,175 @@ Pass background-image: url("http://localhost/") Pass background-image: url(http://localhost/) Pass background-image: none Pass background-image: inherit -Fail background-position: 5% 5% -Fail background-position: 5% .5% -Fail background-position: 5% -5% -Fail background-position: 5% -.5% -Fail background-position: 5% 0px -Fail background-position: 5% 1px -Fail background-position: 5% .1em -Fail background-position: 5% -0px -Fail background-position: 5% -1px -Fail background-position: 5% -.1em -Fail background-position: 5% top -Fail background-position: 5% center -Fail background-position: 5% bottom -Fail background-position: .5% 5% -Fail background-position: .5% .5% -Fail background-position: .5% -5% -Fail background-position: .5% -.5% -Fail background-position: .5% 0px -Fail background-position: .5% 1px -Fail background-position: .5% .1em -Fail background-position: .5% -0px -Fail background-position: .5% -1px -Fail background-position: .5% -.1em -Fail background-position: .5% top -Fail background-position: .5% center -Fail background-position: .5% bottom -Fail background-position: -5% 5% -Fail background-position: -5% .5% -Fail background-position: -5% -5% -Fail background-position: -5% -.5% -Fail background-position: -5% 0px -Fail background-position: -5% 1px -Fail background-position: -5% .1em -Fail background-position: -5% -0px -Fail background-position: -5% -1px -Fail background-position: -5% -.1em -Fail background-position: -5% top -Fail background-position: -5% center -Fail background-position: -5% bottom -Fail background-position: -.5% 5% -Fail background-position: -.5% .5% -Fail background-position: -.5% -5% -Fail background-position: -.5% -.5% -Fail background-position: -.5% 0px -Fail background-position: -.5% 1px -Fail background-position: -.5% .1em -Fail background-position: -.5% -0px -Fail background-position: -.5% -1px -Fail background-position: -.5% -.1em -Fail background-position: -.5% top -Fail background-position: -.5% center -Fail background-position: -.5% bottom -Fail background-position: 0px 5% -Fail background-position: 0px .5% -Fail background-position: 0px -5% -Fail background-position: 0px -.5% -Fail background-position: 0px 0px -Fail background-position: 0px 1px -Fail background-position: 0px .1em -Fail background-position: 0px -0px -Fail background-position: 0px -1px -Fail background-position: 0px -.1em -Fail background-position: 0px top -Fail background-position: 0px center -Fail background-position: 0px bottom -Fail background-position: 1px 5% -Fail background-position: 1px .5% -Fail background-position: 1px -5% -Fail background-position: 1px -.5% -Fail background-position: 1px 0px -Fail background-position: 1px 1px -Fail background-position: 1px .1em -Fail background-position: 1px -0px -Fail background-position: 1px -1px -Fail background-position: 1px -.1em -Fail background-position: 1px top -Fail background-position: 1px center -Fail background-position: 1px bottom -Fail background-position: .1em 5% -Fail background-position: .1em .5% -Fail background-position: .1em -5% -Fail background-position: .1em -.5% -Fail background-position: .1em 0px -Fail background-position: .1em 1px -Fail background-position: .1em .1em -Fail background-position: .1em -0px -Fail background-position: .1em -1px -Fail background-position: .1em -.1em -Fail background-position: .1em top -Fail background-position: .1em center -Fail background-position: .1em bottom -Fail background-position: -0px 5% -Fail background-position: -0px .5% -Fail background-position: -0px -5% -Fail background-position: -0px -.5% -Fail background-position: -0px 0px -Fail background-position: -0px 1px -Fail background-position: -0px .1em -Fail background-position: -0px -0px -Fail background-position: -0px -1px -Fail background-position: -0px -.1em -Fail background-position: -0px top -Fail background-position: -0px center -Fail background-position: -0px bottom -Fail background-position: -1px 5% -Fail background-position: -1px .5% -Fail background-position: -1px -5% -Fail background-position: -1px -.5% -Fail background-position: -1px 0px -Fail background-position: -1px 1px -Fail background-position: -1px .1em -Fail background-position: -1px -0px -Fail background-position: -1px -1px -Fail background-position: -1px -.1em -Fail background-position: -1px top -Fail background-position: -1px center -Fail background-position: -1px bottom -Fail background-position: -.1em 5% -Fail background-position: -.1em .5% -Fail background-position: -.1em -5% -Fail background-position: -.1em -.5% -Fail background-position: -.1em 0px -Fail background-position: -.1em 1px -Fail background-position: -.1em .1em -Fail background-position: -.1em -0px -Fail background-position: -.1em -1px -Fail background-position: -.1em -.1em -Fail background-position: -.1em top -Fail background-position: -.1em center -Fail background-position: -.1em bottom -Fail background-position: left 5% -Fail background-position: left .5% -Fail background-position: left -5% -Fail background-position: left -.5% -Fail background-position: left 0px -Fail background-position: left 1px -Fail background-position: left .1em -Fail background-position: left -0px -Fail background-position: left -1px -Fail background-position: left -.1em -Fail background-position: left top -Fail background-position: left center -Fail background-position: left bottom -Fail background-position: center 5% -Fail background-position: center .5% -Fail background-position: center -5% -Fail background-position: center -.5% -Fail background-position: center 0px -Fail background-position: center 1px -Fail background-position: center .1em -Fail background-position: center -0px -Fail background-position: center -1px -Fail background-position: center -.1em -Fail background-position: center top -Fail background-position: center center -Fail background-position: center bottom -Fail background-position: right 5% -Fail background-position: right .5% -Fail background-position: right -5% -Fail background-position: right -.5% -Fail background-position: right 0px -Fail background-position: right 1px -Fail background-position: right .1em -Fail background-position: right -0px -Fail background-position: right -1px -Fail background-position: right -.1em -Fail background-position: right top -Fail background-position: right center -Fail background-position: right bottom +Pass background-position: 5% 5% +Pass background-position: 5% .5% +Pass background-position: 5% -5% +Pass background-position: 5% -.5% +Pass background-position: 5% 0px +Pass background-position: 5% 1px +Pass background-position: 5% .1em +Pass background-position: 5% -0px +Pass background-position: 5% -1px +Pass background-position: 5% -.1em +Pass background-position: 5% top +Pass background-position: 5% center +Pass background-position: 5% bottom +Pass background-position: .5% 5% +Pass background-position: .5% .5% +Pass background-position: .5% -5% +Pass background-position: .5% -.5% +Pass background-position: .5% 0px +Pass background-position: .5% 1px +Pass background-position: .5% .1em +Pass background-position: .5% -0px +Pass background-position: .5% -1px +Pass background-position: .5% -.1em +Pass background-position: .5% top +Pass background-position: .5% center +Pass background-position: .5% bottom +Pass background-position: -5% 5% +Pass background-position: -5% .5% +Pass background-position: -5% -5% +Pass background-position: -5% -.5% +Pass background-position: -5% 0px +Pass background-position: -5% 1px +Pass background-position: -5% .1em +Pass background-position: -5% -0px +Pass background-position: -5% -1px +Pass background-position: -5% -.1em +Pass background-position: -5% top +Pass background-position: -5% center +Pass background-position: -5% bottom +Pass background-position: -.5% 5% +Pass background-position: -.5% .5% +Pass background-position: -.5% -5% +Pass background-position: -.5% -.5% +Pass background-position: -.5% 0px +Pass background-position: -.5% 1px +Pass background-position: -.5% .1em +Pass background-position: -.5% -0px +Pass background-position: -.5% -1px +Pass background-position: -.5% -.1em +Pass background-position: -.5% top +Pass background-position: -.5% center +Pass background-position: -.5% bottom +Pass background-position: 0px 5% +Pass background-position: 0px .5% +Pass background-position: 0px -5% +Pass background-position: 0px -.5% +Pass background-position: 0px 0px +Pass background-position: 0px 1px +Pass background-position: 0px .1em +Pass background-position: 0px -0px +Pass background-position: 0px -1px +Pass background-position: 0px -.1em +Pass background-position: 0px top +Pass background-position: 0px center +Pass background-position: 0px bottom +Pass background-position: 1px 5% +Pass background-position: 1px .5% +Pass background-position: 1px -5% +Pass background-position: 1px -.5% +Pass background-position: 1px 0px +Pass background-position: 1px 1px +Pass background-position: 1px .1em +Pass background-position: 1px -0px +Pass background-position: 1px -1px +Pass background-position: 1px -.1em +Pass background-position: 1px top +Pass background-position: 1px center +Pass background-position: 1px bottom +Pass background-position: .1em 5% +Pass background-position: .1em .5% +Pass background-position: .1em -5% +Pass background-position: .1em -.5% +Pass background-position: .1em 0px +Pass background-position: .1em 1px +Pass background-position: .1em .1em +Pass background-position: .1em -0px +Pass background-position: .1em -1px +Pass background-position: .1em -.1em +Pass background-position: .1em top +Pass background-position: .1em center +Pass background-position: .1em bottom +Pass background-position: -0px 5% +Pass background-position: -0px .5% +Pass background-position: -0px -5% +Pass background-position: -0px -.5% +Pass background-position: -0px 0px +Pass background-position: -0px 1px +Pass background-position: -0px .1em +Pass background-position: -0px -0px +Pass background-position: -0px -1px +Pass background-position: -0px -.1em +Pass background-position: -0px top +Pass background-position: -0px center +Pass background-position: -0px bottom +Pass background-position: -1px 5% +Pass background-position: -1px .5% +Pass background-position: -1px -5% +Pass background-position: -1px -.5% +Pass background-position: -1px 0px +Pass background-position: -1px 1px +Pass background-position: -1px .1em +Pass background-position: -1px -0px +Pass background-position: -1px -1px +Pass background-position: -1px -.1em +Pass background-position: -1px top +Pass background-position: -1px center +Pass background-position: -1px bottom +Pass background-position: -.1em 5% +Pass background-position: -.1em .5% +Pass background-position: -.1em -5% +Pass background-position: -.1em -.5% +Pass background-position: -.1em 0px +Pass background-position: -.1em 1px +Pass background-position: -.1em .1em +Pass background-position: -.1em -0px +Pass background-position: -.1em -1px +Pass background-position: -.1em -.1em +Pass background-position: -.1em top +Pass background-position: -.1em center +Pass background-position: -.1em bottom +Pass background-position: left 5% +Pass background-position: left .5% +Pass background-position: left -5% +Pass background-position: left -.5% +Pass background-position: left 0px +Pass background-position: left 1px +Pass background-position: left .1em +Pass background-position: left -0px +Pass background-position: left -1px +Pass background-position: left -.1em +Pass background-position: left top +Pass background-position: left center +Pass background-position: left bottom +Pass background-position: center 5% +Pass background-position: center .5% +Pass background-position: center -5% +Pass background-position: center -.5% +Pass background-position: center 0px +Pass background-position: center 1px +Pass background-position: center .1em +Pass background-position: center -0px +Pass background-position: center -1px +Pass background-position: center -.1em +Pass background-position: center top +Pass background-position: center center +Pass background-position: center bottom +Pass background-position: right 5% +Pass background-position: right .5% +Pass background-position: right -5% +Pass background-position: right -.5% +Pass background-position: right 0px +Pass background-position: right 1px +Pass background-position: right .1em +Pass background-position: right -0px +Pass background-position: right -1px +Pass background-position: right -.1em +Pass background-position: right top +Pass background-position: right center +Pass background-position: right bottom Pass background-position: inherit Pass background-repeat: repeat Pass background-repeat: repeat-x diff --git a/Tests/LibWeb/Text/input/position-serialization.html b/Tests/LibWeb/Text/input/position-serialization.html new file mode 100644 index 00000000000..9a2f17ac9c0 --- /dev/null +++ b/Tests/LibWeb/Text/input/position-serialization.html @@ -0,0 +1,25 @@ + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/background-position-valid.html b/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/background-position-valid.html new file mode 100644 index 00000000000..17060f6607a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/background-position-valid.html @@ -0,0 +1,48 @@ + + + + +CSS Backgrounds and Borders Module Level 3: parsing background-position with valid values + + + + + + + + + + +