diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index b118b0f7c0a..6d7e9264d28 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -412,6 +412,7 @@ private: RefPtr parse_single_background_repeat_value(TokenStream&); RefPtr parse_single_background_size_value(TokenStream&); RefPtr parse_border_value(PropertyID, TokenStream&); + RefPtr parse_border_image_value(TokenStream&); RefPtr parse_border_image_slice_value(TokenStream&); RefPtr parse_border_radius_value(TokenStream&); RefPtr parse_border_radius_shorthand_value(TokenStream&); diff --git a/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp b/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp index 4c923050cf5..e29e9d2db89 100644 --- a/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp @@ -530,6 +530,10 @@ Parser::ParseErrorOr> Parser::parse_css_value if (auto parsed_value = parse_border_value(property_id, tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); return ParseError::SyntaxError; + case PropertyID::BorderImage: + if (auto parsed_value = parse_border_image_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; case PropertyID::BorderImageSlice: if (auto parsed_value = parse_border_image_slice_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); @@ -1658,6 +1662,133 @@ RefPtr Parser::parse_border_value(PropertyID property_id, T { border_width.release_nonnull(), border_style.release_nonnull(), border_color.release_nonnull() }); } +// https://drafts.csswg.org/css-backgrounds/#border-image +RefPtr Parser::parse_border_image_value(TokenStream& tokens) +{ + // <'border-image-source'> || <'border-image-slice'> [ / <'border-image-width'> | / <'border-image-width'>? / <'border-image-outset'> ]? || <'border-image-repeat'> + auto transaction = tokens.begin_transaction(); + + RefPtr source_value; + RefPtr slice_value; + RefPtr width_value; + RefPtr outset_value; + RefPtr repeat_value; + + auto make_border_image_shorthand = [&]() { + transaction.commit(); + if (!source_value) + source_value = property_initial_value(PropertyID::BorderImageSource); + if (!slice_value) + slice_value = property_initial_value(PropertyID::BorderImageSlice); + if (!width_value) + width_value = property_initial_value(PropertyID::BorderImageWidth); + if (!outset_value) + outset_value = property_initial_value(PropertyID::BorderImageOutset); + if (!repeat_value) + repeat_value = property_initial_value(PropertyID::BorderImageRepeat); + + return ShorthandStyleValue::create(PropertyID::BorderImage, + { PropertyID::BorderImageSource, PropertyID::BorderImageSlice, PropertyID::BorderImageWidth, PropertyID::BorderImageOutset, PropertyID::BorderImageRepeat }, + { source_value.release_nonnull(), slice_value.release_nonnull(), width_value.release_nonnull(), outset_value.release_nonnull(), repeat_value.release_nonnull() }); + }; + + auto parse_value_list = [&](PropertyID property_id, TokenStream& inner_tokens, Optional delimiter = {}) -> RefPtr { + auto inner_transaction = inner_tokens.begin_transaction(); + + auto remaining_values = property_maximum_value_count(property_id); + StyleValueVector values; + while (inner_tokens.has_next_token() && remaining_values > 0) { + if (delimiter.has_value() && inner_tokens.next_token().is_delim(*delimiter)) + break; + auto value = parse_css_value_for_property(property_id, inner_tokens); + if (!value) + break; + values.append(*value); + --remaining_values; + } + if (values.is_empty()) + return nullptr; + + inner_transaction.commit(); + if (values.size() == 1) + return values[0]; + + return StyleValueList::create(move(values), StyleValueList::Separator::Space); + }; + + auto parse_slice_portion = [&]() { + // <'border-image-slice'> [ / <'border-image-width'> | / <'border-image-width'>? / <'border-image-outset'> ]? + auto transaction = tokens.begin_transaction(); + tokens.discard_whitespace(); + if (auto value = parse_border_image_slice_value(tokens)) { + slice_value = move(value); + } else { + return false; + } + tokens.discard_whitespace(); + if (tokens.next_token().is_delim('/')) { + tokens.discard_a_token(); + tokens.discard_whitespace(); + if (auto width = parse_value_list(PropertyID::BorderImageWidth, tokens)) { + width_value = move(width); + tokens.discard_whitespace(); + if (!tokens.next_token().is_delim('/')) { + transaction.commit(); + return true; + } + } + + if (!tokens.next_token().is_delim('/')) + return false; + + tokens.discard_a_token(); + tokens.discard_whitespace(); + if (auto outset = parse_value_list(PropertyID::BorderImageOutset, tokens)) { + outset_value = move(outset); + tokens.discard_whitespace(); + } else { + return false; + } + } + + tokens.discard_whitespace(); + transaction.commit(); + return true; + }; + + auto has_source = false; + auto has_repeat = false; + auto has_slice_portion = false; + while (tokens.has_next_token()) { + if (auto source = parse_css_value_for_property(PropertyID::BorderImageSource, tokens)) { + if (has_source) + return nullptr; + + has_source = true; + source_value = move(source); + continue; + } + if (auto repeat = parse_value_list(PropertyID::BorderImageRepeat, tokens)) { + if (has_repeat) + return nullptr; + + has_repeat = true; + repeat_value = move(repeat); + continue; + } + if (parse_slice_portion()) { + if (has_slice_portion) + return nullptr; + + has_slice_portion = true; + continue; + } + return nullptr; + } + + return make_border_image_shorthand(); +} + // https://drafts.csswg.org/css-backgrounds/#border-image-slice RefPtr Parser::parse_border_image_slice_value(TokenStream& tokens) { diff --git a/Libraries/LibWeb/CSS/Properties.json b/Libraries/LibWeb/CSS/Properties.json index 2a0e6436330..2abee3ee398 100644 --- a/Libraries/LibWeb/CSS/Properties.json +++ b/Libraries/LibWeb/CSS/Properties.json @@ -705,6 +705,17 @@ ], "max-values": 1 }, + "border-image": { + "inherited": false, + "initial": "none 100% 1 0 stretch", + "longhands": [ + "border-image-source", + "border-image-slice", + "border-image-width", + "border-image-outset", + "border-image-repeat" + ] + }, "border-image-outset": { "affects-layout": false, "animation-type": "by-computed-value", diff --git a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp index 65ad7630bca..ebf9c2f48d3 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ShorthandStyleValue.cpp @@ -205,6 +205,19 @@ String ShorthandStyleValue::to_string(SerializationMode mode) const return MUST(builder.to_string()); } + case PropertyID::BorderImage: { + auto source = longhand(PropertyID::BorderImageSource); + auto slice = longhand(PropertyID::BorderImageSlice); + auto width = longhand(PropertyID::BorderImageWidth); + auto outset = longhand(PropertyID::BorderImageOutset); + auto repeat = longhand(PropertyID::BorderImageRepeat); + return MUST(String::formatted("{} {} / {} / {} {}", + source->to_string(mode), + slice->to_string(mode), + width->to_string(mode), + outset->to_string(mode), + repeat->to_string(mode))); + } case PropertyID::BorderRadius: { auto top_left = longhand(PropertyID::BorderTopLeftRadius); auto top_right = longhand(PropertyID::BorderTopRightRadius); diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt b/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt index aca74812477..64842fd40af 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt @@ -243,6 +243,8 @@ All supported properties and their default values exposed from CSSStylePropertie 'border-end-end-radius': '0px' 'borderEndStartRadius': '0px' 'border-end-start-radius': '0px' +'borderImage': 'none 100% / 1 / 0 stretch' +'border-image': 'none 100% / 1 / 0 stretch' 'borderImageOutset': '0' 'border-image-outset': '0' 'borderImageRepeat': 'stretch' diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-image-invalid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-image-invalid.txt new file mode 100644 index 00000000000..1bcbf22bf1a --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-image-invalid.txt @@ -0,0 +1,22 @@ +Harness status: OK + +Found 17 tests + +17 Pass +Pass e.style['border-image'] = "auto" should not set the property value +Pass e.style['border-image'] = "none, url(\"http://www.example.com/\")" should not set the property value +Pass e.style['border-image'] = "stretch repeat round" should not set the property value +Pass e.style['border-image'] = "fill" should not set the property value +Pass e.style['border-image'] = "1 2 3 4 5" should not set the property value +Pass e.style['border-image'] = "1% fill 2%" should not set the property value +Pass e.style['border-image'] = "1 / -2px" should not set the property value +Pass e.style['border-image'] = "-1 / 2px" should not set the property value +Pass e.style['border-image'] = "1 / 1 2 3 4 5" should not set the property value +Pass e.style['border-image'] = "1 2 3 4 5 / / 1px" should not set the property value +Pass e.style['border-image'] = "1 / / auto" should not set the property value +Pass e.style['border-image'] = "1 2% 3 4% / / 1%" should not set the property value +Pass e.style['border-image'] = "1 2% 3 4% fill / / 1 2 3 4 5" should not set the property value +Pass e.style['border-image'] = "1 / none / 1px" should not set the property value +Pass e.style['border-image'] = "1 2% 3 4% / 1 2 3 4 5 / 2" should not set the property value +Pass e.style['border-image'] = "1 2 3 4 5 / 1px / 1px" should not set the property value +Pass e.style['border-image'] = "1 / 1px / auto" should not set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-image-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-image-valid.txt new file mode 100644 index 00000000000..9fbb702c5db --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-backgrounds/parsing/border-image-valid.txt @@ -0,0 +1,36 @@ +Harness status: OK + +Found 30 tests + +29 Pass +1 Fail +Pass e.style['border-image'] = "none" should set the property value +Pass e.style['border-image'] = "stretch" should set the property value +Pass e.style['border-image'] = "none 100% / 1 / 0 stretch" should set the property value +Pass e.style['border-image'] = "url(\"http://www.example.com/\") 1 2 3 4 fill" should set the property value +Pass e.style['border-image'] = "url(\"http://www.example.com/\") 1 2 3 4 fill / 1 / 0 stretch" should set the property value +Pass e.style['border-image'] = "url(\"http://www.example.com/\")" should set the property value +Pass e.style['border-image'] = "repeat round" should set the property value +Pass e.style['border-image'] = "none repeat round" should set the property value +Pass e.style['border-image'] = "space" should set the property value +Fail e.style['border-image'] = "none space space" should set the property value +Pass e.style['border-image'] = "none 100% / 1 / 0 space" should set the property value +Pass e.style['border-image'] = "1" should set the property value +Pass e.style['border-image'] = "none 1 1 1 1" should set the property value +Pass e.style['border-image'] = "none 1 / 1 / 0 stretch" should set the property value +Pass e.style['border-image'] = "url(\"http://www.example.com/\") 1 2% 3 4%" should set the property value +Pass e.style['border-image'] = "url(\"http://www.example.com/\") 1 2% 3 4% fill" should set the property value +Pass e.style['border-image'] = "url(\"http://www.example.com/\") fill 1 2% 3 4%" should set the property value +Pass e.style['border-image'] = "1 / 1px" should set the property value +Pass e.style['border-image'] = "1 2% 3 4% / 2%" should set the property value +Pass e.style['border-image'] = "1 2% 3 4% fill / 3" should set the property value +Pass e.style['border-image'] = "fill 1 2% 3 4% / auto" should set the property value +Pass e.style['border-image'] = "1 / 1px 2% 3 auto" should set the property value +Pass e.style['border-image'] = "1 / / 1px" should set the property value +Pass e.style['border-image'] = "1 2% 3 4% / / 2" should set the property value +Pass e.style['border-image'] = "url(\"http://www.example.com/\") 1 2% 3 4% fill / / 1px 2 3px 4" should set the property value +Pass e.style['border-image'] = "1 / 1px / 1px" should set the property value +Pass e.style['border-image'] = "1 2% 3 4% / 2% / 2" should set the property value +Pass e.style['border-image'] = "1 2% 3 4% fill / 3 / 1px 2 3px 4" should set the property value +Pass e.style['border-image'] = "1 / auto / 1px" should set the property value +Pass e.style['border-image'] = "1 2% 3 4% / 1px 2% 3 auto / 2" should set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-values.txt b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-values.txt index cdccee56f3f..0ccfa5c34a2 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-values.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-values.txt @@ -2,13 +2,13 @@ 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. -Pass 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: 2px; border-bottom: 3px; border-left: 4px; should be canonical. Fail The serialization of border: 1px; border-top: 2px; should be canonical. diff --git a/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/border-image-invalid.html b/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/border-image-invalid.html new file mode 100644 index 00000000000..af9ca040c65 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/border-image-invalid.html @@ -0,0 +1,40 @@ + + + + +CSS Backgrounds and Borders Module Level 3: parsing border-image with invalid values + + + + + + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/border-image-valid.html b/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/border-image-valid.html new file mode 100644 index 00000000000..7fc4280aa05 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/css-backgrounds/parsing/border-image-valid.html @@ -0,0 +1,59 @@ + + + + +CSS Backgrounds and Borders Module Level 3: parsing border-image with valid values + + + + + + + + + + +