Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Libraries/LibWeb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ set(SOURCES
CSS/StyleSheet.cpp
CSS/StyleSheetIdentifier.cpp
CSS/StyleSheetList.cpp
CSS/StyleValues/AnchorSizeStyleValue.cpp
CSS/StyleValues/AngleStyleValue.cpp
CSS/StyleValues/BackgroundRepeatStyleValue.cpp
CSS/StyleValues/BackgroundSizeStyleValue.cpp
Expand Down
7 changes: 7 additions & 0 deletions Libraries/LibWeb/CSS/CSSStyleValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <LibWeb/CSS/CSSStyleValue.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleValues/AbstractImageStyleValue.h>
#include <LibWeb/CSS/StyleValues/AnchorSizeStyleValue.h>
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
#include <LibWeb/CSS/StyleValues/BackgroundRepeatStyleValue.h>
#include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h>
Expand Down Expand Up @@ -83,6 +84,12 @@ AbstractImageStyleValue const& CSSStyleValue::as_abstract_image() const
return static_cast<AbstractImageStyleValue const&>(*this);
}

AnchorSizeStyleValue const& CSSStyleValue::as_anchor_size() const
{
VERIFY(is_anchor_size());
return static_cast<AnchorSizeStyleValue const&>(*this);
}

AngleStyleValue const& CSSStyleValue::as_angle() const
{
VERIFY(is_angle());
Expand Down
5 changes: 5 additions & 0 deletions Libraries/LibWeb/CSS/CSSStyleValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class CSSStyleValue : public RefCounted<CSSStyleValue> {
virtual ~CSSStyleValue() = default;

enum class Type {
AnchorSize,
Angle,
BackgroundRepeat,
BackgroundSize,
Expand Down Expand Up @@ -154,6 +155,10 @@ class CSSStyleValue : public RefCounted<CSSStyleValue> {
AbstractImageStyleValue const& as_abstract_image() const;
AbstractImageStyleValue& as_abstract_image() { return const_cast<AbstractImageStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_abstract_image()); }

bool is_anchor_size() const { return type() == Type::AnchorSize; }
AnchorSizeStyleValue const& as_anchor_size() const;
AnchorSizeStyleValue& as_anchor_size() { return const_cast<AnchorSizeStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_anchor_size()); }

bool is_angle() const { return type() == Type::Angle; }
AngleStyleValue const& as_angle() const;
AngleStyleValue& as_angle() { return const_cast<AngleStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_angle()); }
Expand Down
5 changes: 4 additions & 1 deletion Libraries/LibWeb/CSS/ComputedProperties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ Size ComputedProperties::size_value(PropertyID id) const
return Size::make_length(length);
}

// FIXME: Support `fit-content(<length>)`
// FIXME: Support `anchor-size(..)`
if (value.is_anchor_size())
return Size::make_none();

dbgln("FIXME: Unsupported size value: `{}`, treating as `auto`", value.to_string(SerializationMode::Normal));
return Size::make_auto();
}
Expand Down
8 changes: 8 additions & 0 deletions Libraries/LibWeb/CSS/Enums.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@
"stretch",
"unsafe"
],
"anchor-size": [
"block",
"height",
"inline",
"self-block",
"self-inline",
"width"
],
"animation-direction": [
"alternate",
"alternate-reverse",
Expand Down
4 changes: 4 additions & 0 deletions Libraries/LibWeb/CSS/Keywords.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
"grid",
"groove",
"hard-light",
"height",
"help",
"hide",
"hidden",
Expand Down Expand Up @@ -447,7 +448,9 @@
"searchfield",
"selecteditem",
"selecteditemtext",
"self-block",
"self-end",
"self-inline",
"self-start",
"semi-condensed",
"semi-expanded",
Expand Down Expand Up @@ -550,6 +553,7 @@
"w-resize",
"wait",
"wavy",
"width",
"window",
"windowframe",
"windowtext",
Expand Down
2 changes: 2 additions & 0 deletions Libraries/LibWeb/CSS/Parser/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ class Parser {
RefPtr<CSSStyleValue const> parse_calculated_value(ComponentValue const&);
Optional<FlyString> parse_custom_ident(TokenStream<ComponentValue>&, ReadonlySpan<StringView> blacklist);
RefPtr<CustomIdentStyleValue const> parse_custom_ident_value(TokenStream<ComponentValue>&, ReadonlySpan<StringView> blacklist);
Optional<FlyString> parse_dashed_ident(TokenStream<ComponentValue>&);
// NOTE: Implemented in generated code. (GenerateCSSMathFunctions.cpp)
RefPtr<CalculationNode const> parse_math_function(Function const&, CalculationContext const&);
RefPtr<CalculationNode const> parse_a_calc_function_node(Function const&, CalculationContext const&);
Expand Down Expand Up @@ -395,6 +396,7 @@ class Parser {
RefPtr<StringStyleValue const> parse_opentype_tag_value(TokenStream<ComponentValue>&);
RefPtr<FontSourceStyleValue const> parse_font_source_value(TokenStream<ComponentValue>&);

RefPtr<CSSStyleValue const> parse_anchor_size(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue const> parse_angle_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue const> parse_angle_percentage_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue const> parse_flex_value(TokenStream<ComponentValue>&);
Expand Down
4 changes: 2 additions & 2 deletions Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
auto transaction = tokens.begin_transaction();
if (property_accepts_type(*property, ValueType::Percentage)) {
if (auto value = parse_length_percentage_value(tokens)) {
if (value->is_calculated()) {
if (value->is_calculated() || value->is_anchor_size()) {
transaction.commit();
return PropertyAndValue { *property, value };
}
Expand All @@ -308,7 +308,7 @@ Optional<Parser::PropertyAndValue> Parser::parse_css_value_for_properties(Readon
}
}
if (auto value = parse_length_value(tokens)) {
if (value->is_calculated()) {
if (value->is_calculated() || value->is_anchor_size()) {
transaction.commit();
return PropertyAndValue { *property, value };
}
Expand Down
131 changes: 131 additions & 0 deletions Libraries/LibWeb/CSS/Parser/ValueParsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
Expand All @@ -21,6 +22,7 @@
#include <LibWeb/CSS/FontFace.h>
#include <LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleValues/AnchorSizeStyleValue.h>
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
#include <LibWeb/CSS/StyleValues/BackgroundRepeatStyleValue.h>
#include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h>
Expand Down Expand Up @@ -266,6 +268,7 @@ Optional<LengthOrCalculated> Parser::parse_length(TokenStream<ComponentValue>& t
return value->as_length().length();
if (value->is_calculated())
return LengthOrCalculated { value->as_calculated() };
// FIXME: Deal with ->is_anchor_size()
}
return {};
}
Expand All @@ -279,6 +282,7 @@ Optional<LengthPercentage> Parser::parse_length_percentage(TokenStream<Component
return value->as_percentage().percentage();
if (value->is_calculated())
return LengthPercentage { value->as_calculated() };
// FIXME: Deal with ->is_anchor_size()
}
return {};
}
Expand Down Expand Up @@ -791,6 +795,112 @@ RefPtr<CSSStyleValue const> Parser::parse_percentage_value(TokenStream<Component
return nullptr;
}

// https://drafts.csswg.org/css-anchor-position-1/#sizing
RefPtr<CSSStyleValue const> Parser::parse_anchor_size(TokenStream<ComponentValue>& tokens)
{
// anchor-size() = anchor-size( [ <anchor-name> || <anchor-size> ]? , <length-percentage>? )

auto transaction = tokens.begin_transaction();
auto const& function_token = tokens.consume_a_token();
if (!function_token.is_function("anchor-size"sv))
return {};

// It is only allowed in the accepted @position-try properties (and is otherwise invalid).
static Array allowed_property_ids = {
// inset properties
PropertyID::Inset,
PropertyID::Top, PropertyID::Right, PropertyID::Bottom, PropertyID::Left,
PropertyID::InsetBlock, PropertyID::InsetBlockStart, PropertyID::InsetBlockEnd,
PropertyID::InsetInline, PropertyID::InsetInlineStart, PropertyID::InsetInlineEnd,
// margin properties
PropertyID::Margin,
PropertyID::MarginTop, PropertyID::MarginRight, PropertyID::MarginBottom, PropertyID::MarginLeft,
PropertyID::MarginBlock, PropertyID::MarginBlockStart, PropertyID::MarginBlockEnd,
PropertyID::MarginInline, PropertyID::MarginInlineStart, PropertyID::MarginInlineEnd,
// sizing properties
PropertyID::Width, PropertyID::MinWidth, PropertyID::MaxWidth,
PropertyID::Height, PropertyID::MinHeight, PropertyID::MaxHeight,
PropertyID::BlockSize, PropertyID::MinBlockSize, PropertyID::MaxBlockSize,
PropertyID::InlineSize, PropertyID::MinInlineSize, PropertyID::MaxInlineSize,
// self-alignment properties
PropertyID::AlignSelf, PropertyID::JustifySelf, PropertyID::PlaceSelf,
// FIXME: position-anchor
// FIXME: position-area
};
bool valid_property_context = false;
for (auto& value_context : m_value_context) {
if (!value_context.has<PropertyID>())
continue;
if (!allowed_property_ids.contains_slow(value_context.get<PropertyID>())) {
valid_property_context = false;
break;
}
valid_property_context = true;
}
if (!valid_property_context)
return {};

auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_token.function().name });
auto argument_tokens = TokenStream { function_token.function().value };

Optional<FlyString> anchor_name;
Optional<AnchorSize> anchor_size;
ValueComparingRefPtr<CSSStyleValue const> fallback_value;

// Parse optional anchor name and anchor size in arbitrary order.
for (auto i = 0; i < 2; ++i) {
argument_tokens.discard_whitespace();
auto const& peek_token = argument_tokens.next_token();
if (!peek_token.is(Token::Type::Ident))
break;

// <anchor-name> = <dashed-ident>
if (auto dashed_ident = parse_dashed_ident(argument_tokens); dashed_ident.has_value()) {
if (anchor_name.has_value())
return {};
anchor_name = dashed_ident.value();
continue;
}

// <anchor-size> = width | height | block | inline | self-block | self-inline
auto keyword = keyword_from_string(peek_token.token().ident());
if (!keyword.has_value())
return {};
auto maybe_anchor_size = keyword_to_anchor_size(keyword.value());
if (!maybe_anchor_size.has_value() || anchor_size.has_value())
return {};
argument_tokens.discard_a_token();
anchor_size = maybe_anchor_size.release_value();
}

argument_tokens.discard_whitespace();
auto has_name_or_size = anchor_name.has_value() || anchor_size.has_value();
auto comma_present = false;
if (argument_tokens.next_token().is(Token::Type::Comma)) {
if (!has_name_or_size)
return {};
comma_present = true;
argument_tokens.discard_a_token();
argument_tokens.discard_whitespace();
}

// FIXME: Nested anchor sizes should actually be handled by parse_length_percentage()
if (auto nested_anchor_size = parse_anchor_size(argument_tokens))
fallback_value = nested_anchor_size.release_nonnull();
else if (auto length_percentage = parse_length_percentage_value(argument_tokens))
fallback_value = length_percentage.release_nonnull();

if (!fallback_value && comma_present)
return {};
if (fallback_value && !comma_present && has_name_or_size)
return {};
if (argument_tokens.has_next_token())
return {};

transaction.commit();
return AnchorSizeStyleValue::create(anchor_name, anchor_size, fallback_value);
}

RefPtr<CSSStyleValue const> Parser::parse_angle_value(TokenStream<ComponentValue>& tokens)
{
if (tokens.next_token().is(Token::Type::Dimension)) {
Expand Down Expand Up @@ -959,6 +1069,9 @@ RefPtr<CSSStyleValue const> Parser::parse_length_value(TokenStream<ComponentValu
}
}

if (tokens.next_token().is_function("anchor-size"sv))
return parse_anchor_size(tokens);

auto transaction = tokens.begin_transaction();
if (auto calc = parse_calculated_value(tokens.consume_a_token()); calc
&& (calc->is_length() || (calc->is_calculated() && calc->as_calculated().resolves_to_length()))) {
Expand Down Expand Up @@ -1006,6 +1119,9 @@ RefPtr<CSSStyleValue const> Parser::parse_length_percentage_value(TokenStream<Co
}
}

if (tokens.next_token().is_function("anchor-size"sv))
return parse_anchor_size(tokens);

auto transaction = tokens.begin_transaction();
if (auto calc = parse_calculated_value(tokens.consume_a_token()); calc
&& (calc->is_length() || calc->is_percentage() || (calc->is_calculated() && calc->as_calculated().resolves_to_length_percentage()))) {
Expand Down Expand Up @@ -3307,6 +3423,19 @@ RefPtr<CustomIdentStyleValue const> Parser::parse_custom_ident_value(TokenStream
return nullptr;
}

// https://drafts.csswg.org/css-values-4/#typedef-dashed-ident
Optional<FlyString> Parser::parse_dashed_ident(TokenStream<ComponentValue>& tokens)
{
// The <dashed-ident> production is a <custom-ident>, with all the case-sensitivity that implies, with the
// additional restriction that it must start with two dashes (U+002D HYPHEN-MINUS).
auto transaction = tokens.begin_transaction();
auto custom_ident = parse_custom_ident(tokens, {});
if (!custom_ident.has_value() || !custom_ident->starts_with_bytes("--"sv))
return {};
transaction.commit();
return custom_ident;
}

// https://www.w3.org/TR/css-grid-2/#typedef-track-breadth
Optional<GridSize> Parser::parse_grid_track_breadth(TokenStream<ComponentValue>& tokens)
{
Expand Down Expand Up @@ -4378,6 +4507,8 @@ NonnullRefPtr<CSSStyleValue const> Parser::resolve_unresolved_style_value(DOM::A
RefPtr<CSSStyleValue const> Parser::parse_value(ValueType value_type, TokenStream<ComponentValue>& tokens)
{
switch (value_type) {
case ValueType::AnchorSize:
return parse_anchor_size(tokens);
case ValueType::Angle:
return parse_angle_value(tokens);
case ValueType::BackgroundPosition:
Expand Down
50 changes: 50 additions & 0 deletions Libraries/LibWeb/CSS/StyleValues/AnchorSizeStyleValue.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <LibWeb/CSS/StyleValues/AnchorSizeStyleValue.h>

namespace Web::CSS {

ValueComparingNonnullRefPtr<AnchorSizeStyleValue const> AnchorSizeStyleValue::create(
Optional<FlyString> const& anchor_name, Optional<AnchorSize> const& anchor_size,
ValueComparingRefPtr<CSSStyleValue const> const& fallback_value)
{
return adopt_ref(*new (nothrow) AnchorSizeStyleValue(anchor_name, anchor_size, fallback_value));
}

AnchorSizeStyleValue::AnchorSizeStyleValue(Optional<FlyString> const& anchor_name, Optional<AnchorSize> const& anchor_size,
ValueComparingRefPtr<CSSStyleValue const> const& fallback_value)
: StyleValueWithDefaultOperators(Type::AnchorSize)
, m_properties { .anchor_name = anchor_name, .anchor_size = anchor_size, .fallback_value = fallback_value }
{
}

String AnchorSizeStyleValue::to_string(SerializationMode serialization_mode) const
{
// FIXME: Handle SerializationMode.
StringBuilder builder;
builder.append("anchor-size("sv);

if (anchor_name().has_value())
builder.append(anchor_name().value());

if (anchor_size().has_value()) {
if (anchor_name().has_value())
builder.append(' ');
builder.append(CSS::to_string(anchor_size().value()));
}

if (fallback_value()) {
if (anchor_name().has_value() || anchor_size().has_value())
builder.append(", "sv);
builder.append(fallback_value()->to_string(serialization_mode));
}

builder.append(')');
return MUST(builder.to_string());
}

}
Loading
Loading