mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 19:59:17 +00:00
LibWeb: Support nested shorthands when serializing CSS declaration
This commit is contained in:
parent
0a53aaa3b6
commit
048a0c9106
Notes:
github-actions[bot]
2025-06-09 09:45:04 +00:00
Author: https://github.com/Calme1709
Commit: 048a0c9106
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4990
Reviewed-by: https://github.com/AtkinsSJ ✅
Reviewed-by: https://github.com/konradekk
5 changed files with 141 additions and 44 deletions
|
@ -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<CSSStyleValue const> 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<PropertyID> 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<PropertyID> longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> expanded_longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> 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<ValueType> 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)` |
|
||||
|
|
|
@ -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<StyleProperty> 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<PropertyID> 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<StyleProperty> 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<ValueComparingNonnullRefPtr<ShorthandStyleValue const>(PropertyID)> make_shorthand_value = [&](PropertyID shorthand_id) {
|
||||
auto longhand_ids = longhands_for_shorthand(shorthand_id);
|
||||
Vector<ValueComparingNonnullRefPtr<CSSStyleValue const>> 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<PropertyID> longhand_ids;
|
||||
Vector<ValueComparingNonnullRefPtr<CSSStyleValue const>> 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
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "GeneratorUtil.h"
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/GenericShorthands.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <AK/SourceGenerator.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibCore/ArgsParser.h>
|
||||
|
@ -268,6 +269,7 @@ bool property_accepts_time(PropertyID, Time const&);
|
|||
|
||||
bool property_is_shorthand(PropertyID);
|
||||
Vector<PropertyID> longhands_for_shorthand(PropertyID);
|
||||
Vector<PropertyID> expanded_longhands_for_shorthand(PropertyID);
|
||||
bool property_maps_to_shorthand(PropertyID);
|
||||
Vector<PropertyID> shorthands_for_longhand(PropertyID);
|
||||
|
||||
|
@ -1063,26 +1065,35 @@ Vector<PropertyID> longhands_for_shorthand(PropertyID property_id)
|
|||
{
|
||||
switch (property_id) {
|
||||
)~~~");
|
||||
Function<Vector<String>(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<String> 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<PropertyID> longhands_for_shorthand(PropertyID property_id)
|
|||
return { };
|
||||
}
|
||||
}
|
||||
)~~~");
|
||||
|
||||
generator.append(R"~~~(
|
||||
Vector<PropertyID> expanded_longhands_for_shorthand(PropertyID property_id)
|
||||
{
|
||||
switch (property_id) {
|
||||
)~~~");
|
||||
|
||||
Function<Vector<String>(String const&)> get_expanded_longhands = [&](String const& property_id) {
|
||||
Vector<String> 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<String, Vector<String>> shorthands_for_longhand_map;
|
||||
|
@ -1143,16 +1206,47 @@ Vector<PropertyID> shorthands_for_longhand(PropertyID property_id)
|
|||
switch (property_id) {
|
||||
)~~~");
|
||||
|
||||
Function<Vector<String>(String)> get_shorthands_for_longhand = [&](auto const& longhand) {
|
||||
Vector<String> 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<PropertyID> shorthands_for_longhand(PropertyID property_id)
|
|||
}
|
||||
|
||||
generator.append(R"~~~(
|
||||
default:
|
||||
return { };
|
||||
}
|
||||
default:
|
||||
return { };
|
||||
}
|
||||
}
|
||||
)~~~");
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
Loading…
Add table
Add a link
Reference in a new issue