LibWeb: Add dedicated overflow-clip-margin style value
Store overflow-clip-margin as a structured style value instead of collapsing it into generic keyword/length lists during parsing. Keep omitted visual-box as a distinct state so painting can apply the spec default per element type (content-box for replaced, padding-box otherwise), while preserving canonical serialization.
This commit is contained in:
parent
a3e420bb9e
commit
b9ed64d1ce
11 changed files with 180 additions and 27 deletions
|
|
@ -290,6 +290,7 @@ set(SOURCES
|
|||
CSS/StyleValues/NumberStyleValue.cpp
|
||||
CSS/StyleValues/OpacityValueStyleValue.cpp
|
||||
CSS/StyleValues/OpenTypeTaggedStyleValue.cpp
|
||||
CSS/StyleValues/OverflowClipMarginStyleValue.cpp
|
||||
CSS/StyleValues/PositionStyleValue.cpp
|
||||
CSS/StyleValues/RadialGradientStyleValue.cpp
|
||||
CSS/StyleValues/RadialSizeStyleValue.cpp
|
||||
|
|
|
|||
|
|
@ -146,6 +146,22 @@ private:
|
|||
|
||||
using CursorData = Variant<NonnullRefPtr<CursorStyleValue const>, CursorPredefined>;
|
||||
|
||||
struct OverflowClipMarginSide {
|
||||
Optional<BackgroundBox> visual_box {};
|
||||
Length offset { Length::make_px(0) };
|
||||
|
||||
bool operator==(OverflowClipMarginSide const&) const = default;
|
||||
};
|
||||
|
||||
struct OverflowClipMarginData {
|
||||
OverflowClipMarginSide left;
|
||||
OverflowClipMarginSide top;
|
||||
OverflowClipMarginSide right;
|
||||
OverflowClipMarginSide bottom;
|
||||
|
||||
bool operator==(OverflowClipMarginData const&) const = default;
|
||||
};
|
||||
|
||||
using ListStyleType = Variant<Empty, RefPtr<CounterStyle const>, String>;
|
||||
|
||||
class InitialValues {
|
||||
|
|
@ -228,7 +244,7 @@ public:
|
|||
static LengthBox inset() { return {}; }
|
||||
static LengthBox margin() { return { Length::make_px(0), Length::make_px(0), Length::make_px(0), Length::make_px(0) }; }
|
||||
static LengthBox padding() { return { Length::make_px(0), Length::make_px(0), Length::make_px(0), Length::make_px(0) }; }
|
||||
static LengthBox overflow_clip_margin() { return { Length::make_px(0), Length::make_px(0), Length::make_px(0), Length::make_px(0) }; }
|
||||
static OverflowClipMarginData overflow_clip_margin() { return {}; }
|
||||
static Size width() { return Size::make_auto(); }
|
||||
static Size min_width() { return Size::make_auto(); }
|
||||
static Size max_width() { return Size::make_none(); }
|
||||
|
|
@ -667,7 +683,7 @@ public:
|
|||
LengthBox const& inset() const { return m_noninherited.inset; }
|
||||
LengthBox const& margin() const { return m_noninherited.margin; }
|
||||
LengthBox const& padding() const { return m_noninherited.padding; }
|
||||
LengthBox const& overflow_clip_margin() const { return m_noninherited.overflow_clip_margin; }
|
||||
OverflowClipMarginData const& overflow_clip_margin() const { return m_noninherited.overflow_clip_margin; }
|
||||
|
||||
BorderData const& border_left() const { return m_noninherited.border_left; }
|
||||
BorderData const& border_top() const { return m_noninherited.border_top; }
|
||||
|
|
@ -863,7 +879,7 @@ protected:
|
|||
LengthBox inset { InitialValues::inset() };
|
||||
LengthBox margin { InitialValues::margin() };
|
||||
LengthBox padding { InitialValues::padding() };
|
||||
LengthBox overflow_clip_margin { InitialValues::overflow_clip_margin() };
|
||||
OverflowClipMarginData overflow_clip_margin { InitialValues::overflow_clip_margin() };
|
||||
Filter backdrop_filter { InitialValues::backdrop_filter() };
|
||||
Filter filter { InitialValues::filter() };
|
||||
BorderData border_left;
|
||||
|
|
@ -1051,7 +1067,7 @@ public:
|
|||
void set_inset(LengthBox const& inset) { m_noninherited.inset = inset; }
|
||||
void set_margin(LengthBox const& margin) { m_noninherited.margin = margin; }
|
||||
void set_padding(LengthBox const& padding) { m_noninherited.padding = padding; }
|
||||
void set_overflow_clip_margin(LengthBox const& overflow_clip_margin) { m_noninherited.overflow_clip_margin = overflow_clip_margin; }
|
||||
void set_overflow_clip_margin(OverflowClipMarginData const& overflow_clip_margin) { m_noninherited.overflow_clip_margin = overflow_clip_margin; }
|
||||
void set_overflow_x(Overflow value) { m_noninherited.overflow_x = value; }
|
||||
void set_overflow_y(Overflow value) { m_noninherited.overflow_y = value; }
|
||||
void set_list_style_type(ListStyleType value) { m_inherited.list_style_type = move(value); }
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/OpenTypeTaggedStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/OverflowClipMarginStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/RepeatStyleStyleValue.h>
|
||||
|
|
@ -3330,22 +3331,29 @@ RefPtr<StyleValue const> Parser::parse_math_depth_value(TokenStream<ComponentVal
|
|||
RefPtr<StyleValue const> Parser::parse_overflow_clip_margin_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <visual-box> || <length>
|
||||
auto parse_visual_box = [this](auto& tokens) -> Optional<Keyword> {
|
||||
auto parse_visual_box = [this](auto& tokens) -> Optional<BackgroundBox> {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
auto maybe_visual_box = parse_keyword_value(tokens);
|
||||
if (!maybe_visual_box)
|
||||
return {};
|
||||
|
||||
auto keyword = maybe_visual_box->to_keyword();
|
||||
if (keyword != Keyword::ContentBox && keyword != Keyword::PaddingBox && keyword != Keyword::BorderBox)
|
||||
Optional<BackgroundBox> box;
|
||||
if (keyword == Keyword::ContentBox)
|
||||
box = BackgroundBox::ContentBox;
|
||||
else if (keyword == Keyword::PaddingBox)
|
||||
box = BackgroundBox::PaddingBox;
|
||||
else if (keyword == Keyword::BorderBox)
|
||||
box = BackgroundBox::BorderBox;
|
||||
else
|
||||
return {};
|
||||
|
||||
transaction.commit();
|
||||
return keyword;
|
||||
return box;
|
||||
};
|
||||
|
||||
RefPtr<StyleValue const> length;
|
||||
Optional<Keyword> visual_box;
|
||||
Optional<BackgroundBox> visual_box;
|
||||
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
tokens.discard_whitespace();
|
||||
|
|
@ -3360,7 +3368,7 @@ RefPtr<StyleValue const> Parser::parse_overflow_clip_margin_value(TokenStream<Co
|
|||
}
|
||||
|
||||
if (!length) {
|
||||
if (auto maybe_length = parse_length_value(tokens, infinite_range)) {
|
||||
if (auto maybe_length = parse_length_value(tokens, non_negative_range)) {
|
||||
length = maybe_length.release_nonnull();
|
||||
continue;
|
||||
}
|
||||
|
|
@ -3376,13 +3384,7 @@ RefPtr<StyleValue const> Parser::parse_overflow_clip_margin_value(TokenStream<Co
|
|||
if (!length)
|
||||
length = LengthStyleValue::create(Length::make_px(0));
|
||||
|
||||
if (!visual_box.has_value() || *visual_box == Keyword::PaddingBox)
|
||||
return length;
|
||||
|
||||
if (length->is_length() && length->as_length().length().raw_value() == 0)
|
||||
return KeywordStyleValue::create(*visual_box);
|
||||
|
||||
return StyleValueList::create({ KeywordStyleValue::create(*visual_box), length.release_nonnull() }, StyleValueList::Separator::Space);
|
||||
return OverflowClipMarginStyleValue::create(visual_box, length.release_nonnull());
|
||||
}
|
||||
|
||||
RefPtr<StyleValue const> Parser::parse_overflow_clip_margin_shorthand(PropertyID property_id, TokenStream<ComponentValue>& tokens)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2026, mikiubo <michele.uboldi@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "OverflowClipMarginStyleValue.h"
|
||||
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
ValueComparingNonnullRefPtr<OverflowClipMarginStyleValue const> OverflowClipMarginStyleValue::create(Optional<BackgroundBox> visual_box, NonnullRefPtr<StyleValue const> offset)
|
||||
{
|
||||
return adopt_ref(*new (nothrow) OverflowClipMarginStyleValue(visual_box, move(offset)));
|
||||
}
|
||||
|
||||
OverflowClipMarginStyleValue::OverflowClipMarginStyleValue(Optional<BackgroundBox> visual_box, NonnullRefPtr<StyleValue const> offset)
|
||||
: StyleValueWithDefaultOperators(Type::OverflowClipMargin)
|
||||
, m_visual_box(visual_box)
|
||||
, m_offset(move(offset))
|
||||
{
|
||||
}
|
||||
|
||||
OverflowClipMarginStyleValue::~OverflowClipMarginStyleValue() = default;
|
||||
|
||||
// https://drafts.csswg.org/css-overflow-4/#overflow-clip-margin
|
||||
void OverflowClipMarginStyleValue::serialize(StringBuilder& builder, SerializationMode mode) const
|
||||
{
|
||||
bool has_explicit_box = m_visual_box.has_value();
|
||||
bool is_default_box = has_explicit_box && *m_visual_box == BackgroundBox::PaddingBox;
|
||||
bool is_zero_offset = m_offset->is_length() && m_offset->as_length().length().raw_value() == 0;
|
||||
|
||||
if (!has_explicit_box || is_default_box) {
|
||||
m_offset->serialize(builder, mode);
|
||||
} else if (is_zero_offset) {
|
||||
builder.append(CSS::to_string(*m_visual_box));
|
||||
} else {
|
||||
builder.append(CSS::to_string(*m_visual_box));
|
||||
builder.append(' ');
|
||||
m_offset->serialize(builder, mode);
|
||||
}
|
||||
}
|
||||
|
||||
ValueComparingNonnullRefPtr<StyleValue const> OverflowClipMarginStyleValue::absolutized(ComputationContext const& context) const
|
||||
{
|
||||
auto new_offset = m_offset->absolutized(context);
|
||||
if (new_offset->equals(m_offset))
|
||||
return *this;
|
||||
return create(m_visual_box, move(new_offset));
|
||||
}
|
||||
|
||||
bool OverflowClipMarginStyleValue::properties_equal(OverflowClipMarginStyleValue const& other) const
|
||||
{
|
||||
return m_visual_box == other.m_visual_box
|
||||
&& m_offset == other.m_offset;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2026, mikiubo <michele.uboldi@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <LibWeb/CSS/Enums.h>
|
||||
#include <LibWeb/CSS/StyleValues/StyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
class OverflowClipMarginStyleValue final : public StyleValueWithDefaultOperators<OverflowClipMarginStyleValue> {
|
||||
public:
|
||||
static ValueComparingNonnullRefPtr<OverflowClipMarginStyleValue const> create(Optional<BackgroundBox> visual_box, NonnullRefPtr<StyleValue const> offset);
|
||||
virtual ~OverflowClipMarginStyleValue() override;
|
||||
|
||||
Optional<BackgroundBox> visual_box() const { return m_visual_box; }
|
||||
StyleValue const& offset() const { return m_offset; }
|
||||
|
||||
virtual void serialize(StringBuilder&, SerializationMode) const override;
|
||||
virtual ValueComparingNonnullRefPtr<StyleValue const> absolutized(ComputationContext const&) const override;
|
||||
bool properties_equal(OverflowClipMarginStyleValue const&) const;
|
||||
|
||||
virtual bool is_computationally_independent() const override { return m_offset->is_computationally_independent(); }
|
||||
|
||||
private:
|
||||
OverflowClipMarginStyleValue(Optional<BackgroundBox> visual_box, NonnullRefPtr<StyleValue const> offset);
|
||||
|
||||
Optional<BackgroundBox> m_visual_box;
|
||||
ValueComparingNonnullRefPtr<StyleValue const> m_offset;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -181,8 +181,10 @@ void ShorthandStyleValue::serialize(StringBuilder& builder, SerializationMode mo
|
|||
m_properties.values.first()->serialize(builder, mode);
|
||||
};
|
||||
|
||||
// Then special cases
|
||||
// FIXME: overflow-clip-margin needs a special case here for when its longhands aren't identical.
|
||||
// Ref: https://github.com/w3c/csswg-drafts/issues/8381
|
||||
|
||||
// Then special cases
|
||||
switch (m_properties.shorthand_property) {
|
||||
case PropertyID::All: {
|
||||
// NOTE: 'all' can only be serialized in the case all sub-properties share the same CSS-wide keyword, this is
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/OpacityValueStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/OpenTypeTaggedStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/OverflowClipMarginStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PendingSubstitutionStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ namespace Web::CSS {
|
|||
__ENUMERATE_CSS_STYLE_VALUE_TYPE(Number, number, NumberStyleValue) \
|
||||
__ENUMERATE_CSS_STYLE_VALUE_TYPE(OpacityValue, opacity_value, OpacityValueStyleValue) \
|
||||
__ENUMERATE_CSS_STYLE_VALUE_TYPE(OpenTypeTagged, open_type_tagged, OpenTypeTaggedStyleValue) \
|
||||
__ENUMERATE_CSS_STYLE_VALUE_TYPE(OverflowClipMargin, overflow_clip_margin, OverflowClipMarginStyleValue) \
|
||||
__ENUMERATE_CSS_STYLE_VALUE_TYPE(PendingSubstitution, pending_substitution, PendingSubstitutionStyleValue) \
|
||||
__ENUMERATE_CSS_STYLE_VALUE_TYPE(Percentage, percentage, PercentageStyleValue) \
|
||||
__ENUMERATE_CSS_STYLE_VALUE_TYPE(Position, position, PositionStyleValue) \
|
||||
|
|
|
|||
|
|
@ -356,6 +356,7 @@ class NumberStyleValue;
|
|||
class NumericType;
|
||||
class OpacityValueStyleValue;
|
||||
class OpenTypeTaggedStyleValue;
|
||||
class OverflowClipMarginStyleValue;
|
||||
class ParsedFontFace;
|
||||
class PendingSubstitutionStyleValue;
|
||||
class Percentage;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/OverflowClipMarginStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
|
||||
|
|
@ -828,7 +829,25 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
|
|||
computed_values.set_inset(computed_style.length_box(CSS::PropertyID::Left, CSS::PropertyID::Top, CSS::PropertyID::Right, CSS::PropertyID::Bottom, CSS::LengthPercentageOrAuto::make_auto()));
|
||||
computed_values.set_margin(computed_style.length_box(CSS::PropertyID::MarginLeft, CSS::PropertyID::MarginTop, CSS::PropertyID::MarginRight, CSS::PropertyID::MarginBottom, CSS::Length::make_px(0)));
|
||||
computed_values.set_padding(computed_style.length_box(CSS::PropertyID::PaddingLeft, CSS::PropertyID::PaddingTop, CSS::PropertyID::PaddingRight, CSS::PropertyID::PaddingBottom, CSS::Length::make_px(0)));
|
||||
computed_values.set_overflow_clip_margin(computed_style.length_box(CSS::PropertyID::OverflowClipMarginLeft, CSS::PropertyID::OverflowClipMarginTop, CSS::PropertyID::OverflowClipMarginRight, CSS::PropertyID::OverflowClipMarginBottom, CSS::Length::make_px(0)));
|
||||
{
|
||||
auto extract_side = [&](CSS::PropertyID property_id) -> CSS::OverflowClipMarginSide {
|
||||
auto const& value = computed_style.property(property_id);
|
||||
if (value.is_overflow_clip_margin()) {
|
||||
auto const& overflow_clip_margin = value.as_overflow_clip_margin();
|
||||
CSS::Length offset = CSS::Length::make_px(0);
|
||||
if (overflow_clip_margin.offset().is_length())
|
||||
offset = overflow_clip_margin.offset().as_length().length();
|
||||
return { overflow_clip_margin.visual_box(), offset };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
CSS::OverflowClipMarginData data;
|
||||
data.left = extract_side(CSS::PropertyID::OverflowClipMarginLeft);
|
||||
data.top = extract_side(CSS::PropertyID::OverflowClipMarginTop);
|
||||
data.right = extract_side(CSS::PropertyID::OverflowClipMarginRight);
|
||||
data.bottom = extract_side(CSS::PropertyID::OverflowClipMarginBottom);
|
||||
computed_values.set_overflow_clip_margin(data);
|
||||
}
|
||||
|
||||
computed_values.set_box_shadow(computed_style.box_shadow(*this));
|
||||
|
||||
|
|
|
|||
|
|
@ -363,20 +363,36 @@ CSSPixelRect PaintableBox::overflow_clip_edge_rect() const
|
|||
// '<visual-box>'
|
||||
// Specifies the box edge to use as the overflow clip edge origin, i.e. when the specified offset is zero.
|
||||
// If omitted, defaults to 'padding-box' on non-replaced elements, or 'content-box' on replaced elements.
|
||||
// FIXME: We can't parse this yet so it's always omitted for now.
|
||||
auto overflow_clip_edge = absolute_padding_box_rect();
|
||||
if (layout_node().is_replaced_box()) {
|
||||
overflow_clip_edge = absolute_rect();
|
||||
}
|
||||
auto const& overflow_clip_margin = computed_values().overflow_clip_margin();
|
||||
auto resolve_box_edge = [&](CSS::OverflowClipMarginSide const& side) -> CSSPixelRect {
|
||||
auto box = side.visual_box;
|
||||
if (!box.has_value()) {
|
||||
if (layout_node().is_replaced_box())
|
||||
box = CSS::BackgroundBox::ContentBox;
|
||||
else
|
||||
box = CSS::BackgroundBox::PaddingBox;
|
||||
}
|
||||
switch (*box) {
|
||||
case CSS::BackgroundBox::ContentBox:
|
||||
return absolute_rect();
|
||||
case CSS::BackgroundBox::BorderBox:
|
||||
return absolute_border_box_rect();
|
||||
case CSS::BackgroundBox::PaddingBox:
|
||||
default:
|
||||
return absolute_padding_box_rect();
|
||||
}
|
||||
};
|
||||
|
||||
auto overflow_clip_edge = resolve_box_edge(overflow_clip_margin.top);
|
||||
|
||||
// '<length [0,∞]>'
|
||||
// The specified offset dictates how much the overflow clip edge is expanded from the specified box edge
|
||||
// Negative values are invalid. Defaults to zero if omitted.
|
||||
overflow_clip_edge.inflate(
|
||||
computed_values().overflow_clip_margin().top().length().absolute_length_to_px(),
|
||||
computed_values().overflow_clip_margin().right().length().absolute_length_to_px(),
|
||||
computed_values().overflow_clip_margin().bottom().length().absolute_length_to_px(),
|
||||
computed_values().overflow_clip_margin().left().length().absolute_length_to_px());
|
||||
overflow_clip_margin.top.offset.absolute_length_to_px(),
|
||||
overflow_clip_margin.right.offset.absolute_length_to_px(),
|
||||
overflow_clip_margin.bottom.offset.absolute_length_to_px(),
|
||||
overflow_clip_margin.left.offset.absolute_length_to_px());
|
||||
return overflow_clip_edge;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue