1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 18:07:34 +00:00

LibWeb: Implement and use FlexStyleValue

This is not just moving the code from StyleResolver to Parser. The logic
has changed to allow for the `flex-basis` to come before or after the
`flex-grow/shrink` values, as well as handle the special one-value
cases.

Also added test cases to flex.html to check the parsing. It does parse
correctly, but elements with `flex-basis: auto` do not calculate their
width correctly.
This commit is contained in:
Sam Atkins 2021-08-04 17:48:08 +01:00 committed by Andreas Kling
parent 44a082391b
commit 2644d2c221
5 changed files with 208 additions and 77 deletions

View file

@ -154,6 +154,68 @@
<div class="box" style="flex-shrink: 1;">2 I shrink</div> <div class="box" style="flex-shrink: 1;">2 I shrink</div>
<div class="box" style="flex-shrink: 0;">3 I don't</div> <div class="box" style="flex-shrink: 0;">3 I don't</div>
</div> </div>
<h2>Flex shorthand</h2>
<p>flex: initial;</p>
<div class="my-container" style="width: 500px;">
<div class="box" style="flex: initial;">1</div>
<div class="box" style="flex: initial;">2</div>
<div class="box" style="flex: initial;">3</div>
</div>
<p>flex: auto;</p>
<div class="my-container" style="width: 500px;">
<div class="box" style="flex: auto;">1</div>
<div class="box" style="flex: auto;">2</div>
<div class="box" style="flex: auto;">3</div>
</div>
<p>flex: none;</p>
<div class="my-container" style="width: 500px;">
<div class="box" style="flex: none;">1</div>
<div class="box" style="flex: none;">2</div>
<div class="box" style="flex: none;">3</div>
</div>
<p>flex: 1/2/3;</p>
<div class="my-container" style="width: 500px;">
<div class="box" style="flex: 1;">1</div>
<div class="box" style="flex: 2;">2</div>
<div class="box" style="flex: 3;">3</div>
</div>
<p>flex: 0 0 0;</p>
<div class="my-container" style="width: 500px;">
<div class="box" style="flex: 0 0 0;">1</div>
<div class="box" style="flex: 0 0 0;">2</div>
<div class="box" style="flex: 0 0 0;">3</div>
</div>
<p>flex: 1 2 0;</p>
<div class="my-container" style="width: 500px;">
<div class="box" style="flex: 1 2 0;">1</div>
<div class="box" style="flex: 1 2 0;">2</div>
<div class="box" style="flex: 1 2 0;">3</div>
</div>
<p>flex: 0 1 2; <span style="color: red;">(Invalid)</span></p>
<div class="my-container" style="width: 500px;">
<div class="box" style="flex: 0 1 2;">1</div>
<div class="box" style="flex: 0 1 2;">2</div>
<div class="box" style="flex: 0 1 2;">3</div>
</div>
<p>flex: 4/1/0 0 50px;</p>
<div class="my-container" style="width: 500px;">
<div class="box" style="flex: 4 0 50px;">1</div>
<div class="box" style="flex: 1 0 50px;">2</div>
<div class="box" style="flex: 0 0 50px;">3</div>
</div>
<p>flex: 80% 0 4/1/0;</p>
<div class="my-container" style="width: 500px;">
<div class="box" style="flex: 80% 0 4;">1</div>
<div class="box" style="flex: 80% 0 1;">2</div>
<div class="box" style="flex: 80% 0 0;">3</div>
</div>
<p>flex: auto 0 4/1/0;</p>
<div class="my-container" style="width: 500px;">
<div class="box" style="flex: auto 0 4;">1</div>
<div class="box" style="flex: auto 0 1;">2</div>
<div class="box" style="flex: auto 0 0;">3</div>
</div>
</body> </body>
</html> </html>

View file

@ -1889,6 +1889,98 @@ RefPtr<StyleValue> Parser::parse_box_shadow_value(ParsingContext const& context,
return BoxShadowStyleValue::create(offset_x, offset_y, blur_radius, color); return BoxShadowStyleValue::create(offset_x, offset_y, blur_radius, color);
} }
RefPtr<StyleValue> Parser::parse_flex_value(ParsingContext const& context, Vector<StyleComponentValueRule> const& component_values)
{
auto is_flex_grow_or_shrink = [](StyleValue const& value) -> bool {
if (value.is_numeric())
return true;
return false;
};
auto is_flex_basis = [](StyleValue const& value) -> bool {
if (value.is_length())
return true;
switch (value.to_identifier()) {
case ValueID::Auto:
case ValueID::Content:
return true;
default:
return false;
}
};
if (component_values.size() == 1) {
auto value = parse_css_value(context, PropertyID::Flex, component_values[0]);
if (!value)
return nullptr;
switch (value->to_identifier()) {
case ValueID::Auto: {
auto one = NumericStyleValue::create(1);
return FlexStyleValue::create(one, one, IdentifierStyleValue::create(ValueID::Auto));
}
case ValueID::None: {
auto zero = NumericStyleValue::create(0);
return FlexStyleValue::create(zero, zero, IdentifierStyleValue::create(ValueID::Auto));
}
default:
break;
}
}
RefPtr<StyleValue> flex_grow;
RefPtr<StyleValue> flex_shrink;
RefPtr<StyleValue> flex_basis;
for (size_t i = 0; i < component_values.size(); ++i) {
auto value = parse_css_value(context, PropertyID::Flex, component_values[i]);
if (!value)
return nullptr;
// Zero is a valid value for basis, but only if grow and shrink are already specified.
if (value->is_numeric() && static_cast<NumericStyleValue&>(*value).value() == 0) {
if (flex_grow && flex_shrink && !flex_basis) {
flex_basis = LengthStyleValue::create(Length(0, Length::Type::Px));
continue;
}
}
if (is_flex_grow_or_shrink(*value)) {
if (flex_grow)
return nullptr;
flex_grow = value.release_nonnull();
// Flex-shrink may optionally follow directly after.
if (i + 1 < component_values.size()) {
auto second_value = parse_css_value(context, PropertyID::Flex, component_values[i + 1]);
if (second_value && is_flex_grow_or_shrink(*second_value)) {
flex_shrink = second_value.release_nonnull();
i++;
}
}
continue;
}
if (is_flex_basis(*value)) {
if (flex_basis)
return nullptr;
flex_basis = value.release_nonnull();
continue;
}
return nullptr;
}
if (!flex_grow)
flex_grow = NumericStyleValue::create(0);
if (!flex_shrink)
flex_shrink = NumericStyleValue::create(1);
if (!flex_basis)
flex_basis = IdentifierStyleValue::create(ValueID::Auto);
return FlexStyleValue::create(flex_grow.release_nonnull(), flex_shrink.release_nonnull(), flex_basis.release_nonnull());
}
RefPtr<StyleValue> Parser::parse_font_value(ParsingContext const& context, Vector<StyleComponentValueRule> const& component_values) RefPtr<StyleValue> Parser::parse_font_value(ParsingContext const& context, Vector<StyleComponentValueRule> const& component_values)
{ {
auto is_font_size = [](StyleValue const& value) -> bool { auto is_font_size = [](StyleValue const& value) -> bool {
@ -2285,6 +2377,10 @@ RefPtr<StyleValue> Parser::parse_css_value(PropertyID property_id, TokenStream<S
if (auto parsed_box_shadow = parse_box_shadow_value(m_context, component_values)) if (auto parsed_box_shadow = parse_box_shadow_value(m_context, component_values))
return parsed_box_shadow; return parsed_box_shadow;
break; break;
case PropertyID::Flex:
if (auto parsed_value = parse_flex_value(m_context, component_values))
return parsed_value;
break;
case PropertyID::Font: case PropertyID::Font:
if (auto parsed_value = parse_font_value(m_context, component_values)) if (auto parsed_value = parse_font_value(m_context, component_values))
return parsed_value; return parsed_value;

View file

@ -177,6 +177,7 @@ private:
static RefPtr<StyleValue> parse_image_value(ParsingContext const&, StyleComponentValueRule const&); static RefPtr<StyleValue> parse_image_value(ParsingContext const&, StyleComponentValueRule const&);
static RefPtr<StyleValue> parse_background_value(ParsingContext const&, Vector<StyleComponentValueRule> const&); static RefPtr<StyleValue> parse_background_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_box_shadow_value(ParsingContext const&, Vector<StyleComponentValueRule> const&); static RefPtr<StyleValue> parse_box_shadow_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_flex_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_font_value(ParsingContext const&, Vector<StyleComponentValueRule> const&); static RefPtr<StyleValue> parse_font_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_list_style_value(ParsingContext const&, Vector<StyleComponentValueRule> const&); static RefPtr<StyleValue> parse_list_style_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_text_decoration_value(ParsingContext const&, Vector<StyleComponentValueRule> const&); static RefPtr<StyleValue> parse_text_decoration_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);

View file

@ -250,31 +250,6 @@ static inline bool is_flex_wrap(StyleValue const& value)
} }
} }
static inline bool is_flex_grow_or_shrink(StyleValue const& value)
{
if (value.is_builtin_or_dynamic())
return true;
if (value.is_numeric())
return true;
return false;
}
static inline bool is_flex_basis(StyleValue const& value)
{
if (value.is_builtin_or_dynamic())
return true;
if (value.is_length())
return true;
if (value.is_identifier() && value.to_identifier() == ValueID::Content)
return true;
return false;
}
static inline bool is_font_family(StyleValue const& value) static inline bool is_font_family(StyleValue const& value)
{ {
if (value.is_builtin_or_dynamic()) if (value.is_builtin_or_dynamic())
@ -801,61 +776,19 @@ static void set_property_expanding_shorthands(StyleProperties& style, CSS::Prope
} }
if (property_id == CSS::PropertyID::Flex) { if (property_id == CSS::PropertyID::Flex) {
if (value.is_length() || (value.is_identifier() && value.to_identifier() == CSS::ValueID::Content)) { if (value.is_flex()) {
auto& flex = static_cast<CSS::FlexStyleValue const&>(value);
style.set_property(CSS::PropertyID::FlexGrow, flex.grow());
style.set_property(CSS::PropertyID::FlexShrink, flex.shrink());
style.set_property(CSS::PropertyID::FlexBasis, flex.basis());
return;
}
if (value.is_builtin()) {
style.set_property(CSS::PropertyID::FlexGrow, value);
style.set_property(CSS::PropertyID::FlexShrink, value);
style.set_property(CSS::PropertyID::FlexBasis, value); style.set_property(CSS::PropertyID::FlexBasis, value);
return; return;
} }
if (value.is_component_value_list()) {
auto parts = static_cast<CSS::ValueListStyleValue const&>(value).values();
if (parts.size() == 1) {
auto value = Parser::parse_css_value(context, property_id, parts[0]);
if (!value)
return;
if (is_flex_basis(*value)) {
style.set_property(CSS::PropertyID::FlexBasis, *value);
} else if (is_flex_grow_or_shrink(*value)) {
style.set_property(CSS::PropertyID::FlexGrow, *value);
}
return;
}
if (parts.size() == 2) {
auto flex_grow = Parser::parse_css_value(context, property_id, parts[0]);
auto second_value = Parser::parse_css_value(context, property_id, parts[1]);
if (!flex_grow || !second_value)
return;
style.set_property(CSS::PropertyID::FlexGrow, *flex_grow);
if (is_flex_basis(*second_value)) {
style.set_property(CSS::PropertyID::FlexBasis, *second_value);
} else if (is_flex_grow_or_shrink(*second_value)) {
style.set_property(CSS::PropertyID::FlexShrink, *second_value);
}
return;
}
if (parts.size() == 3) {
auto flex_grow = Parser::parse_css_value(context, property_id, parts[0]);
auto flex_shrink = Parser::parse_css_value(context, property_id, parts[1]);
auto flex_basis = Parser::parse_css_value(context, property_id, parts[2]);
if (!flex_grow || !flex_shrink || !flex_basis)
return;
style.set_property(CSS::PropertyID::FlexGrow, *flex_grow);
style.set_property(CSS::PropertyID::FlexShrink, *flex_shrink);
if (is_flex_basis(*flex_basis))
style.set_property(CSS::PropertyID::FlexBasis, *flex_basis);
return;
}
return;
}
dbgln("Unsure what to do with CSS flex value '{}'", value.to_string());
return; return;
} }

View file

@ -231,6 +231,7 @@ public:
Calculated, Calculated,
Background, Background,
BoxShadow, BoxShadow,
Flex,
Font, Font,
ListStyle, ListStyle,
TextDecoration, TextDecoration,
@ -252,6 +253,7 @@ public:
bool is_calculated() const { return type() == Type::Calculated; } bool is_calculated() const { return type() == Type::Calculated; }
bool is_background() const { return type() == Type::Background; } bool is_background() const { return type() == Type::Background; }
bool is_box_shadow() const { return type() == Type::BoxShadow; } bool is_box_shadow() const { return type() == Type::BoxShadow; }
bool is_flex() const { return type() == Type::Flex; }
bool is_font() const { return type() == Type::Font; } bool is_font() const { return type() == Type::Font; }
bool is_list_style() const { return type() == Type::ListStyle; } bool is_list_style() const { return type() == Type::ListStyle; }
bool is_text_decoration() const { return type() == Type::TextDecoration; } bool is_text_decoration() const { return type() == Type::TextDecoration; }
@ -677,6 +679,43 @@ private:
// FIXME: background-origin // FIXME: background-origin
}; };
class FlexStyleValue final : public StyleValue {
public:
static NonnullRefPtr<FlexStyleValue> create(
NonnullRefPtr<StyleValue> grow,
NonnullRefPtr<StyleValue> shrink,
NonnullRefPtr<StyleValue> basis)
{
return adopt_ref(*new FlexStyleValue(grow, shrink, basis));
}
virtual ~FlexStyleValue() override { }
NonnullRefPtr<StyleValue> grow() const { return m_grow; }
NonnullRefPtr<StyleValue> shrink() const { return m_shrink; }
NonnullRefPtr<StyleValue> basis() const { return m_basis; }
virtual String to_string() const override
{
return String::formatted("Flex grow: {}, shrink: {}, basis: {}", m_grow->to_string(), m_shrink->to_string(), m_basis->to_string());
}
private:
FlexStyleValue(
NonnullRefPtr<StyleValue> grow,
NonnullRefPtr<StyleValue> shrink,
NonnullRefPtr<StyleValue> basis)
: StyleValue(Type::Flex)
, m_grow(grow)
, m_shrink(shrink)
, m_basis(basis)
{
}
NonnullRefPtr<StyleValue> m_grow;
NonnullRefPtr<StyleValue> m_shrink;
NonnullRefPtr<StyleValue> m_basis;
};
class FontStyleValue final : public StyleValue { class FontStyleValue final : public StyleValue {
public: public:
static NonnullRefPtr<FontStyleValue> create(NonnullRefPtr<StyleValue> font_style, NonnullRefPtr<StyleValue> font_weight, NonnullRefPtr<StyleValue> font_size, NonnullRefPtr<StyleValue> line_height, NonnullRefPtrVector<StyleValue>&& font_families) { return adopt_ref(*new FontStyleValue(font_style, font_weight, font_size, line_height, move(font_families))); } static NonnullRefPtr<FontStyleValue> create(NonnullRefPtr<StyleValue> font_style, NonnullRefPtr<StyleValue> font_weight, NonnullRefPtr<StyleValue> font_size, NonnullRefPtr<StyleValue> line_height, NonnullRefPtrVector<StyleValue>&& font_families) { return adopt_ref(*new FontStyleValue(font_style, font_weight, font_size, line_height, move(font_families))); }