LibWeb: Add CSS CompositeStyleValue

This represents the value of properties assigned via their shorthands,
and is expanded when computing actual property values.
This commit is contained in:
Ali Mohammad Pur 2023-05-26 23:16:43 +03:30 committed by Andreas Kling
parent 49bb04a6ba
commit 279924242d
Notes: sideshowbarker 2024-07-17 04:01:41 +09:00
9 changed files with 209 additions and 13 deletions

View file

@ -170,6 +170,8 @@ enum class ValueType {
};
bool property_accepts_type(PropertyID, ValueType);
bool property_accepts_identifier(PropertyID, ValueID);
Vector<PropertyID> longhands_for_shorthand(PropertyID);
size_t property_maximum_value_count(PropertyID);
bool property_affects_layout(PropertyID);
@ -216,6 +218,7 @@ ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& f
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/CSS/StyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
#include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
#include <LibWeb/Infra/Strings.h>
namespace Web::CSS {
@ -617,8 +620,45 @@ size_t property_maximum_value_count(PropertyID property_id)
}
}
} // namespace Web::CSS
Vector<PropertyID> longhands_for_shorthand(PropertyID property_id)
{
switch (property_id) {
)~~~");
properties.for_each_member([&](auto& name, auto& value) {
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
builder.append(", "sv);
builder.appendff("PropertyID::{}", title_casify(longhand.to_deprecated_string()));
return IterationDecision::Continue;
});
property_generator.set("longhands", builder.to_deprecated_string());
property_generator.append(R"~~~(
case PropertyID::@name:titlecase@:
return { @longhands@ };
)~~~");
}
});
generator.append(R"~~~(
default:
return { };
}
}
)~~~");
generator.append(R"~~~(
} // namespace Web::CSS
)~~~");
TRY(file.write_until_depleted(generator.as_string_view().bytes()));

View file

@ -77,6 +77,7 @@ set(SOURCES
CSS/StyleValues/BorderStyleValue.cpp
CSS/StyleValues/CalculatedStyleValue.cpp
CSS/StyleValues/ColorStyleValue.cpp
CSS/StyleValues/CompositeStyleValue.cpp
CSS/StyleValues/ConicGradientStyleValue.cpp
CSS/StyleValues/ContentStyleValue.cpp
CSS/StyleValues/DisplayStyleValue.cpp

View file

@ -39,6 +39,7 @@
#include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
#include <LibWeb/CSS/StyleValues/BorderStyleValue.h>
#include <LibWeb/CSS/StyleValues/ColorStyleValue.h>
#include <LibWeb/CSS/StyleValues/CompositeStyleValue.h>
#include <LibWeb/CSS/StyleValues/ConicGradientStyleValue.h>
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
@ -7167,22 +7168,76 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue>> Parser::parse_css_value(Property
}
// Multiple ComponentValues will usually produce multiple StyleValues, so make a StyleValueList.
StyleValueVector parsed_values;
auto stream = TokenStream { component_values };
while (auto parsed_value = FIXME_TRY(parse_css_value_for_property(property_id, stream))) {
FIXME_TRY(parsed_values.try_append(parsed_value.release_nonnull()));
if (!stream.has_next_token())
break;
{
StyleValueVector parsed_values;
auto stream = TokenStream { component_values };
while (auto parsed_value = FIXME_TRY(parse_css_value_for_property(property_id, stream))) {
FIXME_TRY(parsed_values.try_append(parsed_value.release_nonnull()));
if (!stream.has_next_token())
break;
}
// Some types (such as <ratio>) can be made from multiple ComponentValues, so if we only made 1 StyleValue, return it directly.
if (parsed_values.size() == 1)
return *parsed_values.take_first();
if (!parsed_values.is_empty() && parsed_values.size() <= property_maximum_value_count(property_id))
return FIXME_TRY(StyleValueList::create(move(parsed_values), StyleValueList::Separator::Space));
}
// Some types (such as <ratio>) can be made from multiple ComponentValues, so if we only made 1 StyleValue, return it directly.
if (parsed_values.size() == 1)
return *parsed_values.take_first();
// We have multiple values, but the property claims to accept only a single one, check if it's a shorthand property.
auto unassigned_properties = longhands_for_shorthand(property_id);
if (unassigned_properties.is_empty())
return ParseError::SyntaxError;
if (!parsed_values.is_empty() && parsed_values.size() <= property_maximum_value_count(property_id))
return FIXME_TRY(StyleValueList::create(move(parsed_values), StyleValueList::Separator::Space));
auto stream = TokenStream { component_values };
return ParseError::SyntaxError;
HashMap<UnderlyingType<PropertyID>, Vector<ValueComparingNonnullRefPtr<StyleValue const>>> assigned_values;
while (stream.has_next_token() && !unassigned_properties.is_empty()) {
auto property_and_value = parse_css_value_for_properties(unassigned_properties, stream);
if (!property_and_value.is_error() && property_and_value.value().style_value) {
auto property = property_and_value.value().property;
auto value = property_and_value.release_value().style_value;
auto& values = assigned_values.ensure(to_underlying(property));
if (values.size() + 1 == property_maximum_value_count(property)) {
// We're done with this property, move on to the next one.
unassigned_properties.remove_first_matching([&](auto& unassigned_property) { return unassigned_property == property; });
}
values.append(value.release_nonnull());
continue;
}
// No property matched, so we're done.
dbgln("No property (from {} properties) matched {}", unassigned_properties.size(), stream.peek_token().to_debug_string());
for (auto id : unassigned_properties)
dbgln(" {}", string_from_property_id(id));
break;
}
for (auto& property : unassigned_properties)
assigned_values.ensure(to_underlying(property)).append(FIXME_TRY(property_initial_value(m_context.realm(), property)));
stream.skip_whitespace();
if (stream.has_next_token())
return ParseError::SyntaxError;
Vector<PropertyID> longhand_properties;
longhand_properties.ensure_capacity(assigned_values.size());
for (auto& it : assigned_values)
longhand_properties.unchecked_append(static_cast<PropertyID>(it.key));
StyleValueVector longhand_values;
longhand_values.ensure_capacity(assigned_values.size());
for (auto& it : assigned_values) {
if (it.value.size() == 1)
longhand_values.unchecked_append(it.value.take_first());
else
longhand_values.unchecked_append(FIXME_TRY(StyleValueList::create(move(it.value), StyleValueList::Separator::Space)));
}
return { FIXME_TRY(CompositeStyleValue::create(move(longhand_properties), move(longhand_values))) };
#undef FIXME_TRY
}

View file

@ -25,11 +25,13 @@
#include <LibWeb/CSS/SelectorEngine.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleSheet.h>
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
#include <LibWeb/CSS/StyleValues/BackgroundStyleValue.h>
#include <LibWeb/CSS/StyleValues/BorderRadiusShorthandStyleValue.h>
#include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
#include <LibWeb/CSS/StyleValues/BorderStyleValue.h>
#include <LibWeb/CSS/StyleValues/ColorStyleValue.h>
#include <LibWeb/CSS/StyleValues/CompositeStyleValue.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/CSS/StyleValues/FilterValueListStyleValue.h>
#include <LibWeb/CSS/StyleValues/FlexFlowStyleValue.h>
@ -47,8 +49,11 @@
#include <LibWeb/CSS/StyleValues/OverflowStyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
#include <LibWeb/CSS/StyleValues/TextDecorationStyleValue.h>
#include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
#include <LibWeb/CSS/StyleValues/UnresolvedStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
@ -274,6 +279,14 @@ static bool contains(Edge a, Edge b)
static void set_property_expanding_shorthands(StyleProperties& style, CSS::PropertyID property_id, StyleValue const& value, DOM::Document& document)
{
if (value.is_composite()) {
auto& composite_value = value.as_composite();
auto& properties = composite_value.sub_properties();
auto& values = composite_value.values();
for (size_t i = 0; i < properties.size(); ++i)
set_property_expanding_shorthands(style, properties[i], values[i], document);
}
auto assign_edge_values = [&style](PropertyID top_property, PropertyID right_property, PropertyID bottom_property, PropertyID left_property, auto const& values) {
if (values.size() == 4) {
style.set_property(top_property, values[0]);

View file

@ -19,6 +19,7 @@
#include <LibWeb/CSS/StyleValues/BorderStyleValue.h>
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
#include <LibWeb/CSS/StyleValues/ColorStyleValue.h>
#include <LibWeb/CSS/StyleValues/CompositeStyleValue.h>
#include <LibWeb/CSS/StyleValues/ConicGradientStyleValue.h>
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
@ -132,6 +133,12 @@ ColorStyleValue const& StyleValue::as_color() const
return static_cast<ColorStyleValue const&>(*this);
}
CompositeStyleValue const& StyleValue::as_composite() const
{
VERIFY(is_composite());
return static_cast<CompositeStyleValue const&>(*this);
}
ConicGradientStyleValue const& StyleValue::as_conic_gradient() const
{
VERIFY(is_conic_gradient());

View file

@ -96,6 +96,7 @@ public:
BorderRadiusShorthand,
Calculated,
Color,
Composite,
ConicGradient,
Content,
CustomIdent,
@ -149,6 +150,7 @@ public:
bool is_border_radius_shorthand() const { return type() == Type::BorderRadiusShorthand; }
bool is_calculated() const { return type() == Type::Calculated; }
bool is_color() const { return type() == Type::Color; }
bool is_composite() const { return type() == Type::Composite; }
bool is_conic_gradient() const { return type() == Type::ConicGradient; }
bool is_content() const { return type() == Type::Content; }
bool is_custom_ident() const { return type() == Type::CustomIdent; }
@ -201,6 +203,7 @@ public:
BorderStyleValue const& as_border() const;
CalculatedStyleValue const& as_calculated() const;
ColorStyleValue const& as_color() const;
CompositeStyleValue const& as_composite() const;
ConicGradientStyleValue const& as_conic_gradient() const;
ContentStyleValue const& as_content() const;
CustomIdentStyleValue const& as_custom_ident() const;

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CompositeStyleValue.h"
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
namespace Web::CSS {
CompositeStyleValue::CompositeStyleValue(Vector<PropertyID> sub_properties, Vector<ValueComparingNonnullRefPtr<StyleValue const>> values)
: StyleValueWithDefaultOperators(Type::Composite)
, m_properties { move(sub_properties), move(values) }
{
if (m_properties.sub_properties.size() != m_properties.values.size()) {
dbgln("CompositeStyleValue: sub_properties and values must be the same size! {} != {}", m_properties.sub_properties.size(), m_properties.values.size());
VERIFY_NOT_REACHED();
}
}
CompositeStyleValue::~CompositeStyleValue() = default;
ErrorOr<String> CompositeStyleValue::to_string() const
{
StringBuilder builder;
auto first = true;
for (auto& value : m_properties.values) {
if (first)
first = false;
else
builder.append(' ');
builder.append(TRY(value->to_string()));
}
return builder.to_string();
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/StyleValue.h>
namespace Web::CSS {
class CompositeStyleValue final : public StyleValueWithDefaultOperators<CompositeStyleValue> {
public:
static ErrorOr<ValueComparingNonnullRefPtr<CompositeStyleValue>> create(Vector<PropertyID> sub_properties, Vector<ValueComparingNonnullRefPtr<StyleValue const>> values)
{
return adopt_nonnull_ref_or_enomem(new CompositeStyleValue(move(sub_properties), move(values)));
}
virtual ~CompositeStyleValue() override;
Vector<PropertyID> const& sub_properties() const { return m_properties.sub_properties; }
Vector<ValueComparingNonnullRefPtr<StyleValue const>> const& values() const { return m_properties.values; }
virtual ErrorOr<String> to_string() const override;
bool properties_equal(CompositeStyleValue const& other) const { return m_properties == other.m_properties; }
private:
CompositeStyleValue(Vector<PropertyID> sub_properties, Vector<ValueComparingNonnullRefPtr<StyleValue const>> values);
struct Properties {
Vector<PropertyID> sub_properties;
Vector<ValueComparingNonnullRefPtr<StyleValue const>> values;
bool operator==(Properties const&) const = default;
} m_properties;
};
}

View file

@ -71,6 +71,7 @@ class BorderStyleValue;
class CalculatedStyleValue;
class Clip;
class ColorStyleValue;
class CompositeStyleValue;
class ConicGradientStyleValue;
class ContentStyleValue;
class CSSConditionRule;