diff --git a/Documentation/CSSGeneratedFiles.md b/Documentation/CSSGeneratedFiles.md index 1ff682db68d..8ab9f2dc4d8 100644 --- a/Documentation/CSSGeneratedFiles.md +++ b/Documentation/CSSGeneratedFiles.md @@ -28,7 +28,7 @@ Each property will have some set of these fields on it: | `initial` | Yes | | String. The property's initial value if it is not specified. | `NonnullRefPtr property_initial_value(PropertyID)` | | `legacy-alias-for` | No | Nothing | String. The name of a property this is an alias for. See below. | | | `logical-alias-for` | No | Nothing | Array of strings. The name of a property this is an alias for. See below. | | -| `longhands` | No | `[]` | Array of strings. If this is a shorthand, these are the property names that it expands out into. | `Vector longhands_for_shorthand(PropertyID)` | +| `longhands` | No | `[]` | Array of strings. If this is a shorthand, these are the property names that it expands out into. | `Vector longhands_for_shorthand(PropertyID)`
`Vector expanded_longhands_for_shorthand(PropertyID)`
`Vector shorthands_for_longhand(PropertyID)`| | `max-values` | No | `1` | Integer. How many values can be parsed for this property. eg, `margin` can have up to 4 values. | `size_t property_maximum_value_count(PropertyID)` | | `percentages-resolve-to` | No | Nothing | String. What type percentages get resolved to. eg, for `width` percentages are resolved to `length` values. | `Optional property_resolves_percentages_relative_to(PropertyID)` | | `quirks` | No | `[]` | Array of strings. Some properties have special behavior in "quirks mode", which are listed here. See below. | `bool property_has_quirk(PropertyID, Quirk)` | diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp index 5c449927242..a721b6ee43e 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp @@ -1184,9 +1184,7 @@ String CSSStyleProperties::serialized() const continue; // 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order. - // FIXME: We don't properly support nested shorthands (e.g. background) if (property_maps_to_shorthand(property)) { - // FIXME: Sort in the preferred order. https://www.w3.org/TR/cssom/#concept-shorthands-preferred-order auto shorthands = shorthands_for_longhand(property); // 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: @@ -1204,7 +1202,7 @@ String CSSStyleProperties::serialized() const } // 2. If not all properties that map to shorthand are present in longhands, continue with the steps labeled shorthand loop. - if (any_of(longhands_for_shorthand(shorthand), [&](auto longhand_id) { return !any_of(longhands, [&](auto const& longhand_declaration) { return longhand_declaration.property_id == longhand_id; }); })) + if (any_of(expanded_longhands_for_shorthand(shorthand), [&](auto longhand_id) { return !any_of(longhands, [&](auto const& longhand_declaration) { return longhand_declaration.property_id == longhand_id; }); })) continue; // 3. Let current longhands be an empty array. @@ -1316,9 +1314,8 @@ String CSSStyleProperties::serialize_a_css_value(Vector list) con return String {}; // 1. Let shorthand be the first shorthand property, in preferred order, that exactly maps to all of the longhand properties in list. - // FIXME: Sort in the preferred order. https://www.w3.org/TR/cssom/#concept-shorthands-preferred-order Optional shorthand = shorthands_for_longhand(list.first().property_id).first_matching([&](PropertyID shorthand) { - auto longhands_for_potential_shorthand = longhands_for_shorthand(shorthand); + auto longhands_for_potential_shorthand = expanded_longhands_for_shorthand(shorthand); // The potential shorthand exactly maps to all of the longhand properties in list if: // a. The number of longhand properties in the list is equal to the number of longhand properties that the potential shorthand maps to. @@ -1334,16 +1331,22 @@ String CSSStyleProperties::serialize_a_css_value(Vector list) con return String {}; // 3. Otherwise, serialize a CSS value from a hypothetical declaration of the property shorthand with its value representing the combined values of the declarations in list. + Function(PropertyID)> make_shorthand_value = [&](PropertyID shorthand_id) { + auto longhand_ids = longhands_for_shorthand(shorthand_id); + Vector> longhand_values; + + for (auto longhand_id : longhand_ids) { + if (property_is_shorthand(longhand_id)) + longhand_values.append(make_shorthand_value(longhand_id)); + else + longhand_values.append(list.first_matching([&](auto declaration) { return declaration.property_id == longhand_id; })->value); + } + + return ShorthandStyleValue::create(shorthand_id, longhand_ids, longhand_values); + }; + // FIXME: Not all shorthands are represented by ShorthandStyleValue, we still need to add support for those that don't. - Vector longhand_ids; - Vector> longhand_values; - - for (auto const& longhand : list) { - longhand_ids.append(longhand.property_id); - longhand_values.append(longhand.value); - } - - return ShorthandStyleValue::create(shorthand.value(), longhand_ids, longhand_values)->to_string(SerializationMode::Normal); + return make_shorthand_value(shorthand.value())->to_string(SerializationMode::Normal); } // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp index e17f0f5f203..a87e1f332e4 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp @@ -8,6 +8,7 @@ #include "GeneratorUtil.h" #include #include +#include #include #include #include @@ -268,6 +269,7 @@ bool property_accepts_time(PropertyID, Time const&); bool property_is_shorthand(PropertyID); Vector longhands_for_shorthand(PropertyID); +Vector expanded_longhands_for_shorthand(PropertyID); bool property_maps_to_shorthand(PropertyID); Vector shorthands_for_longhand(PropertyID); @@ -1063,26 +1065,35 @@ Vector longhands_for_shorthand(PropertyID property_id) { switch (property_id) { )~~~"); + Function(String const&)> get_longhands = [&](String const& property_id) { + auto object = properties.get_object(property_id); + VERIFY(object.has_value()); + + auto longhands_json_array = object.value().get_array("longhands"sv); + VERIFY(longhands_json_array.has_value()); + + Vector longhands; + + longhands_json_array.value().for_each([&](auto longhand_value) { + longhands.append(longhand_value.as_string()); + }); + + return longhands; + }; + properties.for_each_member([&](auto& name, auto& value) { if (is_legacy_alias(value.as_object())) return; if (value.as_object().has("longhands"sv)) { - auto longhands = value.as_object().get("longhands"sv); - VERIFY(longhands.has_value() && longhands->is_array()); - auto longhand_values = longhands->as_array(); auto property_generator = generator.fork(); property_generator.set("name:titlecase", title_casify(name)); StringBuilder builder; - bool first = true; - longhand_values.for_each([&](auto& longhand) { - if (first) - first = false; - else + for (auto longhand : get_longhands(name)) { + if (!builder.is_empty()) builder.append(", "sv); - builder.appendff("PropertyID::{}", title_casify(longhand.as_string())); - return IterationDecision::Continue; - }); + builder.appendff("PropertyID::{}", title_casify(longhand)); + } property_generator.set("longhands", builder.to_byte_string()); property_generator.append(R"~~~( case PropertyID::@name:titlecase@: @@ -1096,6 +1107,58 @@ Vector longhands_for_shorthand(PropertyID property_id) return { }; } } +)~~~"); + + generator.append(R"~~~( +Vector expanded_longhands_for_shorthand(PropertyID property_id) +{ + switch (property_id) { +)~~~"); + + Function(String const&)> get_expanded_longhands = [&](String const& property_id) { + Vector expanded_longhands; + + for (auto const& longhand_id : get_longhands(property_id)) { + + auto property = properties.get_object(longhand_id); + + VERIFY(property.has_value()); + + if (property->has_array("longhands"sv)) + expanded_longhands.extend(get_expanded_longhands(longhand_id)); + else + expanded_longhands.append(longhand_id); + } + + return expanded_longhands; + }; + + properties.for_each_member([&](auto& name, auto& value) { + if (is_legacy_alias(value.as_object())) + return; + + if (value.as_object().has("longhands"sv)) { + auto property_generator = generator.fork(); + property_generator.set("name:titlecase", title_casify(name)); + StringBuilder builder; + for (auto longhand : get_expanded_longhands(name)) { + if (!builder.is_empty()) + builder.append(", "sv); + builder.appendff("PropertyID::{}", title_casify(longhand)); + } + property_generator.set("longhands", builder.to_byte_string()); + property_generator.append(R"~~~( + case PropertyID::@name:titlecase@: + return { @longhands@ }; +)~~~"); + } + }); + + generator.append(R"~~~( + default: + return { }; + } +} )~~~"); HashMap> shorthands_for_longhand_map; @@ -1143,16 +1206,47 @@ Vector shorthands_for_longhand(PropertyID property_id) switch (property_id) { )~~~"); + Function(String)> get_shorthands_for_longhand = [&](auto const& longhand) { + Vector shorthands; + + for (auto const& immediate_shorthand : shorthands_for_longhand_map.get(longhand).value()) { + shorthands.append(immediate_shorthand); + + if (shorthands_for_longhand_map.get(immediate_shorthand).has_value()) + shorthands.extend(get_shorthands_for_longhand(immediate_shorthand)); + } + + // https://www.w3.org/TR/cssom/#concept-shorthands-preferred-order + // NOTE: The steps are performed in a order different to the spec in order to complete this in a single sort. + AK::quick_sort(shorthands, [&](String a, String b) { + auto shorthand_a_longhands = get_expanded_longhands(a); + auto shorthand_b_longhands = get_expanded_longhands(b); + + // 4. Order shorthands by the number of longhand properties that map to it, with the greatest number first. + if (shorthand_a_longhands.size() != shorthand_b_longhands.size()) + return shorthand_a_longhands.size() > shorthand_b_longhands.size(); + + // 2. Move all items in shorthands that begin with "-" (U+002D) last in the list, retaining their relative order. + if (a.starts_with_bytes("-"sv) != b.starts_with_bytes("-"sv)) + return b.starts_with_bytes("-"sv); + + // 3. Move all items in shorthands that begin with "-" (U+002D) but do not begin with "-webkit-" last in the list, retaining their relative order. + if (a.starts_with_bytes("-webkit-"sv) != b.starts_with_bytes("-webkit-"sv)) + return a.starts_with_bytes("-webkit-"sv); + + // 1. Order shorthands lexicographically. + return a < b; + }); + + return shorthands; + }; + for (auto const& longhand : shorthands_for_longhand_map.keys()) { auto property_generator = generator.fork(); property_generator.set("name:titlecase", title_casify(longhand)); - auto& shorthands = shorthands_for_longhand_map.get(longhand).value(); StringBuilder builder; - bool first = true; - for (auto& shorthand : shorthands) { - if (first) - first = false; - else + for (auto& shorthand : get_shorthands_for_longhand(longhand)) { + if (!builder.is_empty()) builder.append(", "sv); builder.appendff("PropertyID::{}", title_casify(shorthand)); } @@ -1164,9 +1258,9 @@ Vector shorthands_for_longhand(PropertyID property_id) } generator.append(R"~~~( - default: - return { }; - } + default: + return { }; + } } )~~~"); diff --git a/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt b/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt index 452d55bda50..4f85b5e27eb 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: 0% 0%; background-size: auto auto; background-repeat: repeat; background-attachment: scroll; background-origin: padding-box; background-clip: border-box; +style.cssText = background: yellow; style.length = 9 style[] = 1. background-color 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 dad3ba22eba..da20b938dd2 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 -7 Pass -13 Fail +13 Pass +7 Fail Fail The serialization of border: 1px; border-top: 1px; should be canonical. -Fail The serialization of border: 1px solid red; should be canonical. -Fail The serialization of border: 1px red; should be canonical. -Fail 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. +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; 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. @@ -18,9 +18,9 @@ Fail The serialization of border: solid; border-style: dotted should be canonica Pass The serialization of border-width: 1px; should be canonical. Pass The serialization of overflow-x: scroll; overflow-y: hidden; should be canonical. Pass The serialization of overflow-x: scroll; overflow-y: scroll; should be canonical. -Fail The serialization of outline-width: 2px; outline-style: dotted; outline-color: blue; should be canonical. +Pass The serialization of outline-width: 2px; outline-style: dotted; outline-color: blue; should be canonical. Pass The serialization of margin-top: 1px; margin-right: 2px; margin-bottom: 3px; margin-left: 4px; should be canonical. -Fail The serialization of list-style-type: circle; list-style-position: inside; list-style-image: none; should be canonical. +Pass The serialization of list-style-type: circle; list-style-position: inside; list-style-image: none; should be canonical. Pass The serialization of list-style-type: lower-alpha; should be canonical. Pass The serialization of font-family: sans-serif; line-height: 2em; font-size: 3em; font-style: italic; font-weight: bold; should be canonical. Pass The serialization of padding-top: 1px; padding-right: 2px; padding-bottom: 3px; padding-left: 4px; should be canonical. \ No newline at end of file