diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp index 7c2a298faaa..8006c40e511 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp @@ -1172,22 +1172,96 @@ String CSSStyleProperties::serialized() const if (already_serialized.contains(property)) continue; - // FIXME: 3. If property maps to one or more shorthand properties, let shorthands be an array of those shorthand properties, in preferred order. + // 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); - // FIXME: 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: ... + // 4. Shorthand loop: For each shorthand in shorthands, follow these substeps: + for (auto shorthand : shorthands) { + // 1. Let longhands be an array consisting of all CSS declarations in declaration block’s declarations + // that that are not in already serialized and have a property name that maps to one of the shorthand + // properties in shorthands. + Vector longhands; - // 5. Let value be the result of invoking serialize a CSS value of declaration. - auto value = declaration.value->to_string(Web::CSS::SerializationMode::Normal); + for (auto const& longhand_declaration : m_properties) { + // FIXME: Some of the ad-hoc ShorthandStyleValue::to_string cases don't account for the possibility + // of subproperty values pending substitution, to avoid crashing we don't include those here + if (!already_serialized.contains(longhand_declaration.property_id) && shorthands_for_longhand(longhand_declaration.property_id).contains_slow(shorthand) && !longhand_declaration.value->is_pending_substitution()) + longhands.append(longhand_declaration); + } - // 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value, - // and the important flag set if declaration has its important flag set. - auto serialized_declaration = serialize_a_css_declaration(string_from_property_id(property), move(value), declaration.important); + // 2. If all properties that map to shorthand are not present in longhands, continue with the steps labeled shorthand loop. + if (longhands.is_empty()) + continue; - // 7. Append serialized declaration to list. - list.append(move(serialized_declaration)); + // 3. Let current longhands be an empty array. + Vector current_longhands; - // 8. Append property to already serialized. - already_serialized.set(property); + // 4. Append all CSS declarations in longhands that have a property name that maps to shorthand to current longhands. + for (auto const& longhand : longhands) { + if (shorthands_for_longhand(longhand.property_id).contains_slow(shorthand)) + current_longhands.append(longhand); + } + + // 5. If there is one or more CSS declarations in current longhands have their important flag set and + // one or more with it unset, continue with the steps labeled shorthand loop. + auto all_declarations_have_same_important_flag = true; + + for (size_t i = 1; i < current_longhands.size(); ++i) { + if (current_longhands[i].important != current_longhands[0].important) { + all_declarations_have_same_important_flag = false; + break; + } + } + + if (!all_declarations_have_same_important_flag) + continue; + + // FIXME: 6. If there’s any declaration in declaration block in between the first and the last longhand + // in current longhands which belongs to the same logical property group, but has a different + // mapping logic as any of the longhands in current longhands, and is not in current + // longhands, continue with the steps labeled shorthand loop. + + // 7. Let value be the result of invoking serialize a CSS value of current longhands. + auto value = serialize_a_css_value(current_longhands); + + // 8. If value is the empty string, continue with the steps labeled shorthand loop. + if (value.is_empty()) + continue; + + // 9. Let serialized declaration be the result of invoking serialize a CSS declaration with property + // name shorthand, value value, and the important flag set if the CSS declarations in current + // longhands have their important flag set. + auto serialized_declaration = serialize_a_css_declaration(string_from_property_id(shorthand), move(value), current_longhands.first().important); + + // 10. Append serialized declaration to list. + list.append(move(serialized_declaration)); + + // 11. Append the property names of all items of current longhands to already serialized. + for (auto const& longhand : current_longhands) + already_serialized.set(longhand.property_id); + + // 12. Continue with the steps labeled declaration loop. + } + } + + // FIXME: File spec issue that this should only be run if we haven't serialized this declaration in the above shorthand loop. + if (!already_serialized.contains(declaration.property_id)) { + // 5. Let value be the result of invoking serialize a CSS value of declaration. + auto value = serialize_a_css_value(declaration); + + // 6. Let serialized declaration be the result of invoking serialize a CSS declaration with property name property, value value, + // and the important flag set if declaration has its important flag set. + auto serialized_declaration = serialize_a_css_declaration(string_from_property_id(property), move(value), declaration.important); + + // 7. Append serialized declaration to list. + list.append(move(serialized_declaration)); + + // 8. Append property to already serialized. + already_serialized.set(property); + } } // 4. Return list joined with " " (U+0020). @@ -1196,6 +1270,71 @@ String CSSStyleProperties::serialized() const return MUST(builder.to_string()); } +// https://www.w3.org/TR/cssom/#serialize-a-css-value +String CSSStyleProperties::serialize_a_css_value(StyleProperty const& declaration) const +{ + // 1. If If this algorithm is invoked with a list list: + // NOTE: This is handled in other other overload of this method + + // 2. Represent the value of the declaration as a list of CSS component values components that, when parsed + // according to the property’s grammar, would represent that value. Additionally: + // - If certain component values can appear in any order without changing the meaning of the value (a pattern + // typically represented by a double bar || in the value syntax), reorder the component values to use the + // canonical order of component values as given in the property definition table. + // - If component values can be omitted or replaced with a shorter representation without changing the meaning + // of the value, omit/replace them. + // - If either of the above syntactic translations would be less backwards-compatible, do not perform them. + + // Spec Note: The rules described here outlines the general principles of serialization. For legacy reasons, some + // properties serialize in a different manner, which is intentionally undefined here due to lack of + // resources. Please consult your local reverse-engineer for details. + + // 3. Remove any s from components. + // 4. Replace each component value in components with the result of invoking serialize a CSS component value. + // 5. Join the items of components into a single string, inserting " " (U+0020 SPACE) between each pair of items + // unless the second item is a "," (U+002C COMMA) Return the result. + + // AD-HOC: As the spec is vague we don't follow it exactly here. + return declaration.value->to_string(Web::CSS::SerializationMode::Normal); +} + +// https://www.w3.org/TR/cssom/#serialize-a-css-value +String CSSStyleProperties::serialize_a_css_value(Vector list) const +{ + if (list.is_empty()) + 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); + + // 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. + if (longhands_for_potential_shorthand.size() != list.size()) + return false; + + // b. All longhand properties in the list are contained in the list of longhands for the potential shorthand. + return all_of(longhands_for_potential_shorthand, [&](auto longhand) { return any_of(list, [&](auto const& declaration) { return declaration.property_id == longhand; }); }); + }); + + // 2. If there is no such shorthand or shorthand cannot exactly represent the values of all the properties in list, return the empty string. + if (!shorthand.has_value()) + 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. + // 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); +} + // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext WebIDL::ExceptionOr CSSStyleProperties::set_css_text(StringView css_text) { diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.h b/Libraries/LibWeb/CSS/CSSStyleProperties.h index 69326048a28..628b56e793e 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.h +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.h @@ -52,6 +52,8 @@ public: WebIDL::ExceptionOr set_css_float(StringView); virtual String serialized() const final override; + String serialize_a_css_value(StyleProperty const&) const; + String serialize_a_css_value(Vector) const; virtual WebIDL::ExceptionOr set_css_text(StringView) override; void set_declarations_from_text(StringView); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp index 05d0d6a8cfd..e17f0f5f203 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp @@ -268,6 +268,8 @@ bool property_accepts_time(PropertyID, Time const&); bool property_is_shorthand(PropertyID); Vector longhands_for_shorthand(PropertyID); +bool property_maps_to_shorthand(PropertyID); +Vector shorthands_for_longhand(PropertyID); size_t property_maximum_value_count(PropertyID); @@ -1094,6 +1096,78 @@ Vector longhands_for_shorthand(PropertyID property_id) return { }; } } +)~~~"); + + HashMap> shorthands_for_longhand_map; + + 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(); + for (auto& longhand : longhand_values.values()) { + VERIFY(longhand.is_string()); + auto& longhand_name = longhand.as_string(); + shorthands_for_longhand_map.ensure(longhand_name).append(name); + } + } + }); + + generator.append(R"~~~( +bool property_maps_to_shorthand(PropertyID property_id) +{ + switch (property_id) { +)~~~"); + for (auto const& longhand : shorthands_for_longhand_map.keys()) { + auto property_generator = generator.fork(); + property_generator.set("name:titlecase", title_casify(longhand)); + property_generator.append(R"~~~( + case PropertyID::@name:titlecase@: +)~~~"); + } + + generator.append(R"~~~( + return true; + default: + return false; + } +} +)~~~"); + + generator.append(R"~~~( +Vector shorthands_for_longhand(PropertyID property_id) +{ + switch (property_id) { +)~~~"); + + 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 + builder.append(", "sv); + builder.appendff("PropertyID::{}", title_casify(shorthand)); + } + property_generator.set("shorthands", builder.to_byte_string()); + property_generator.append(R"~~~( + case PropertyID::@name:titlecase@: + return { @shorthands@ }; +)~~~"); + } + + generator.append(R"~~~( + default: + return { }; + } +} )~~~"); generator.append(R"~~~( diff --git a/Tests/LibWeb/Text/expected/Editing/execCommand-preserveWhitespace.txt b/Tests/LibWeb/Text/expected/Editing/execCommand-preserveWhitespace.txt index eb8dac2b8bb..b6db7c81e10 100644 --- a/Tests/LibWeb/Text/expected/Editing/execCommand-preserveWhitespace.txt +++ b/Tests/LibWeb/Text/expected/Editing/execCommand-preserveWhitespace.txt @@ -1,2 +1,2 @@ Before: foo
bar
-After: foobar +After: foobar diff --git a/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt b/Tests/LibWeb/Text/expected/HTML/background-shorthand.txt index 8f3128cc429..452d55bda50 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: 0%; background-position-y: 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: 0% 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/wpt-import/css/css-align/gaps/gap-parsing-002.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-align/gaps/gap-parsing-002.txt index 06c51e32299..3b04d95d4cb 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-align/gaps/gap-parsing-002.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-align/gaps/gap-parsing-002.txt @@ -2,8 +2,7 @@ Harness status: OK Found 16 tests -12 Pass -4 Fail +16 Pass Pass e.style['gap'] = "normal" should set the property value Pass e.style['gap'] = "10px" should set the property value Pass e.style['gap'] = "normal normal" should set the property value @@ -12,11 +11,11 @@ Pass e.style['column-gap'] = "normal" should set the property value Pass e.style['column-gap'] = "10px" should set the property value Pass e.style['row-gap'] = "normal" should set the property value Pass e.style['row-gap'] = "10px" should set the property value -Fail 'row-gap: normal; column-gap: normal;' is serialized to 'gap: normal;' +Pass 'row-gap: normal; column-gap: normal;' is serialized to 'gap: normal;' Pass getPropertyValue for 'row-gap: normal; column-gap: normal;' returns 'normal' -Fail 'row-gap: 10px; column-gap: 10px;' is serialized to 'gap: 10px;' +Pass 'row-gap: 10px; column-gap: 10px;' is serialized to 'gap: 10px;' Pass getPropertyValue for 'row-gap: 10px; column-gap: 10px;' returns '10px' -Fail 'row-gap: 10px; column-gap: normal;' is serialized to 'gap: 10px normal;' +Pass 'row-gap: 10px; column-gap: normal;' is serialized to 'gap: 10px normal;' Pass getPropertyValue for 'row-gap: 10px; column-gap: normal;' returns '10px normal' -Fail 'column-gap: normal; row-gap: 10px;' is serialized to 'gap: 10px normal;' +Pass 'column-gap: normal; row-gap: 10px;' is serialized to 'gap: 10px normal;' Pass getPropertyValue for 'column-gap: normal; row-gap: 10px;' returns '10px normal' \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/overflow-serialization.txt b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/overflow-serialization.txt index a253529e2eb..9c05487cceb 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/overflow-serialization.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/overflow-serialization.txt @@ -2,13 +2,12 @@ Harness status: OK Found 10 tests -7 Pass -3 Fail +10 Pass Pass Single value overflow with CSS-wide keyword should serialize correctly. Pass Single value overflow with non-CSS-wide keyword should serialize correctly. -Fail Overflow-x/y longhands with same CSS-wide keyword should serialize correctly. -Fail Overflow-x/y longhands with same non-CSS-wide keyword should serialize correctly. -Fail Overflow-x/y longhands with different keywords should serialize correctly. +Pass Overflow-x/y longhands with same CSS-wide keyword should serialize correctly. +Pass Overflow-x/y longhands with same non-CSS-wide keyword should serialize correctly. +Pass Overflow-x/y longhands with different keywords should serialize correctly. Pass Single value overflow on element with CSS-wide keyword should serialize correctly. Pass Single value overflow on element with non-CSS-wide keyword should serialize correctly. Pass Overflow-x/y longhands on element with same CSS-wide keyword should serialize correctly. diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-serialization.txt b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-serialization.txt index f010a207ff4..2f86b51a5e1 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-serialization.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-serialization.txt @@ -2,12 +2,12 @@ Harness status: OK Found 7 tests -3 Pass -4 Fail +6 Pass +1 Fail Pass Shorthand serialization with shorthand and longhands mixed. -Fail Shorthand serialization with just longhands. +Pass Shorthand serialization with just longhands. Fail Shorthand serialization with variable and variable from other shorthand. -Fail Shorthand serialization after setting -Fail Shorthand serialization with 'initial' value. +Pass Shorthand serialization after setting +Pass Shorthand serialization with 'initial' value. Pass Shorthand serialization with 'initial' value, one longhand with important flag. Pass Shorthand serialization with 'initial' value, longhands set individually, one with important flag. \ 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 24dc10fd165..dad3ba22eba 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,8 +2,8 @@ Harness status: OK Found 20 tests -2 Pass -18 Fail +7 Pass +13 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. @@ -15,12 +15,12 @@ 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. Fail The serialization of border: solid; border-style: dotted should be canonical. -Fail The serialization of border-width: 1px; should be canonical. -Fail The serialization of overflow-x: scroll; overflow-y: hidden; should be canonical. -Fail The serialization of overflow-x: scroll; overflow-y: scroll; should be canonical. +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. -Fail The serialization of margin-top: 1px; margin-right: 2px; margin-bottom: 3px; margin-left: 4px; 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: 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. -Fail The serialization of padding-top: 1px; padding-right: 2px; padding-bottom: 3px; padding-left: 4px; should be canonical. \ No newline at end of file +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