From 279924242de22bd97a27a52a63469728e1f40653 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Fri, 26 May 2023 23:16:43 +0330 Subject: [PATCH] LibWeb: Add CSS CompositeStyleValue This represents the value of properties assigned via their shorthands, and is expanded when computing actual property values. --- .../LibWeb/GenerateCSSPropertyID.cpp | 42 +++++++++- Userland/Libraries/LibWeb/CMakeLists.txt | 1 + .../Libraries/LibWeb/CSS/Parser/Parser.cpp | 79 ++++++++++++++++--- .../Libraries/LibWeb/CSS/StyleComputer.cpp | 13 +++ Userland/Libraries/LibWeb/CSS/StyleValue.cpp | 7 ++ Userland/Libraries/LibWeb/CSS/StyleValue.h | 3 + .../CSS/StyleValues/CompositeStyleValue.cpp | 38 +++++++++ .../CSS/StyleValues/CompositeStyleValue.h | 38 +++++++++ Userland/Libraries/LibWeb/Forward.h | 1 + 9 files changed, 209 insertions(+), 13 deletions(-) create mode 100644 Userland/Libraries/LibWeb/CSS/StyleValues/CompositeStyleValue.cpp create mode 100644 Userland/Libraries/LibWeb/CSS/StyleValues/CompositeStyleValue.h diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp index c542b6540d..8b14b4227a 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp @@ -170,6 +170,8 @@ enum class ValueType { }; bool property_accepts_type(PropertyID, ValueType); bool property_accepts_identifier(PropertyID, ValueID); +Vector longhands_for_shorthand(PropertyID); + size_t property_maximum_value_count(PropertyID); bool property_affects_layout(PropertyID); @@ -216,6 +218,7 @@ ErrorOr generate_implementation_file(JsonObject& properties, Core::File& f #include #include #include +#include #include namespace Web::CSS { @@ -617,8 +620,45 @@ size_t property_maximum_value_count(PropertyID property_id) } } -} // namespace Web::CSS +Vector 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())); diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 6727d24d10..a03cd83ffe 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -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 diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 0bdc0c5b56..407fcfaff8 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -7167,22 +7168,76 @@ Parser::ParseErrorOr> 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 ) 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 ) 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, Vector>> 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 longhand_properties; + longhand_properties.ensure_capacity(assigned_values.size()); + for (auto& it : assigned_values) + longhand_properties.unchecked_append(static_cast(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 } diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index acad231baf..305d69360a 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -25,11 +25,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -47,8 +49,11 @@ #include #include #include +#include #include #include +#include +#include #include #include #include @@ -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]); diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp index a97fa95d2b..6a1bd6b180 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -132,6 +133,12 @@ ColorStyleValue const& StyleValue::as_color() const return static_cast(*this); } +CompositeStyleValue const& StyleValue::as_composite() const +{ + VERIFY(is_composite()); + return static_cast(*this); +} + ConicGradientStyleValue const& StyleValue::as_conic_gradient() const { VERIFY(is_conic_gradient()); diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h index 87472265bf..9fe07caa69 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h @@ -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; diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CompositeStyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/CompositeStyleValue.cpp new file mode 100644 index 0000000000..c7ff0f2656 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CompositeStyleValue.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "CompositeStyleValue.h" +#include + +namespace Web::CSS { + +CompositeStyleValue::CompositeStyleValue(Vector sub_properties, Vector> 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 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(); +} + +} diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CompositeStyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValues/CompositeStyleValue.h new file mode 100644 index 0000000000..dd6bed05e6 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CompositeStyleValue.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::CSS { + +class CompositeStyleValue final : public StyleValueWithDefaultOperators { +public: + static ErrorOr> create(Vector sub_properties, Vector> values) + { + return adopt_nonnull_ref_or_enomem(new CompositeStyleValue(move(sub_properties), move(values))); + } + virtual ~CompositeStyleValue() override; + + Vector const& sub_properties() const { return m_properties.sub_properties; } + Vector> const& values() const { return m_properties.values; } + + virtual ErrorOr to_string() const override; + + bool properties_equal(CompositeStyleValue const& other) const { return m_properties == other.m_properties; } + +private: + CompositeStyleValue(Vector sub_properties, Vector> values); + + struct Properties { + Vector sub_properties; + Vector> values; + bool operator==(Properties const&) const = default; + } m_properties; +}; + +} diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 68980477b7..d072d50821 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -71,6 +71,7 @@ class BorderStyleValue; class CalculatedStyleValue; class Clip; class ColorStyleValue; +class CompositeStyleValue; class ConicGradientStyleValue; class ContentStyleValue; class CSSConditionRule;