Move CSS animation values into a mutable overlay on computed properties and make base computed style data immutable after construction. Base style mutation now goes through a builder that is consumed on publish, so installed styles no longer expose mutation APIs. Build new base style data for inherited style updates instead of cloning and mutating installed computed properties. Element-specific computed style adjustments now run before publication, while animation and transition updates continue to mutate only the animated overlay.
412 lines
17 KiB
C++
412 lines
17 KiB
C++
/*
|
|
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
|
|
* Copyright (c) 2023-2025, Sam Atkins <sam@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/FixedBitmap.h>
|
|
#include <AK/HashMap.h>
|
|
#include <AK/NonnullRefPtr.h>
|
|
#include <AK/RefCounted.h>
|
|
#include <LibGC/Ptr.h>
|
|
#include <LibGfx/Font/Font.h>
|
|
#include <LibGfx/FontCascadeList.h>
|
|
#include <LibGfx/Forward.h>
|
|
#include <LibWeb/CSS/CSSAnimationProperties.h>
|
|
#include <LibWeb/CSS/ComputedValues.h>
|
|
#include <LibWeb/CSS/EasingFunction.h>
|
|
#include <LibWeb/CSS/FontFeatureData.h>
|
|
#include <LibWeb/CSS/LengthBox.h>
|
|
#include <LibWeb/CSS/PropertyID.h>
|
|
#include <LibWeb/CSS/PseudoClass.h>
|
|
#include <LibWeb/CSS/PseudoClassBitmap.h>
|
|
#include <LibWeb/CSS/PseudoElement.h>
|
|
#include <LibWeb/CSS/StyleProperty.h>
|
|
#include <LibWeb/Export.h>
|
|
|
|
namespace Web::CSS {
|
|
|
|
class AnimatedProperties;
|
|
class StyleComputer;
|
|
|
|
}
|
|
|
|
namespace Web::DOM {
|
|
|
|
class Element;
|
|
|
|
}
|
|
|
|
namespace Web::CSS {
|
|
|
|
struct TransitionProperties {
|
|
Vector<PropertyID> properties;
|
|
double duration;
|
|
EasingFunction timing_function;
|
|
double delay;
|
|
TransitionBehavior transition_behavior;
|
|
};
|
|
|
|
enum class AnimatedPropertyResultOfTransition : u8 {
|
|
No,
|
|
Yes
|
|
};
|
|
|
|
class WEB_API ComputedProperties final : public RefCounted<ComputedProperties> {
|
|
public:
|
|
static constexpr double normal_line_height_scale = 1.15;
|
|
|
|
~ComputedProperties();
|
|
|
|
enum class WithAnimationsApplied {
|
|
No,
|
|
Yes,
|
|
};
|
|
|
|
enum class Inherited {
|
|
No,
|
|
Yes
|
|
};
|
|
|
|
private:
|
|
class Data;
|
|
|
|
public:
|
|
class Builder {
|
|
public:
|
|
Builder();
|
|
|
|
ComputedProperties& style() { return *m_style; }
|
|
ComputedProperties const& style() const { return *m_style; }
|
|
NonnullRefPtr<ComputedProperties> build() &&;
|
|
|
|
bool depends_on_viewport_metrics() const { return m_depends_on_viewport_metrics; }
|
|
bool font_metrics_depend_on_viewport_metrics() const { return m_font_metrics_depend_on_viewport_metrics; }
|
|
Display display() const { return style().display(); }
|
|
StyleValue const& property(PropertyID property_id, WithAnimationsApplied with_animations_applied = WithAnimationsApplied::Yes) const { return style().property(property_id, with_animations_applied); }
|
|
[[nodiscard]] CSSPixels line_height() const { return style().line_height(); }
|
|
ValueComparingNonnullRefPtr<Gfx::Font const> first_available_computed_font(FontComputer const& font_computer) const { return style().first_available_computed_font(font_computer); }
|
|
|
|
void set_has_pseudo_element_styles(u64);
|
|
void set_property_important(PropertyID, Important);
|
|
void set_property_inherited(PropertyID, Inherited);
|
|
void set_depends_on_viewport_metrics();
|
|
void set_font_metrics_depend_on_viewport_metrics();
|
|
void set_has_pseudo_element_style(PseudoElement);
|
|
|
|
void set_property(PropertyID, NonnullRefPtr<StyleValue const> value, Inherited = Inherited::No, Important = Important::No);
|
|
void set_property_without_modifying_flags(PropertyID, NonnullRefPtr<StyleValue const> value);
|
|
void revert_property(PropertyID, ComputedProperties const& style_for_revert);
|
|
|
|
void set_display_before_box_type_transformation(Display);
|
|
|
|
HashMap<PropertyID, NonnullRefPtr<StyleValue const>> const& inheritance_dependent_specified_values() const { return m_data->inheritance_dependent_specified_values; }
|
|
void add_inheritance_dependent_specified_value(PropertyID property_id, NonnullRefPtr<StyleValue const> value) { m_data->inheritance_dependent_specified_values.set(property_id, move(value)); }
|
|
|
|
RefPtr<StyleValue const> raw_cascaded_font_size() const { return m_data->raw_cascaded_font_size; }
|
|
void set_raw_cascaded_font_size(NonnullRefPtr<StyleValue const> value) { m_data->raw_cascaded_font_size = move(value); }
|
|
|
|
private:
|
|
friend class ComputedProperties;
|
|
|
|
Builder(ComputedProperties const&);
|
|
Data& data() { return *m_data; }
|
|
Data const& data() const { return *m_data; }
|
|
|
|
NonnullRefPtr<Data> m_data;
|
|
NonnullRefPtr<ComputedProperties> m_style;
|
|
bool m_depends_on_viewport_metrics { false };
|
|
bool m_font_metrics_depend_on_viewport_metrics { false };
|
|
};
|
|
|
|
static NonnullRefPtr<ComputedProperties> create(Builder&&);
|
|
static Builder create_builder();
|
|
static Builder create_builder_with_base_values_from(ComputedProperties const&);
|
|
|
|
template<typename Callback>
|
|
inline void for_each_property(Callback callback) const
|
|
{
|
|
for (size_t i = 0; i < m_data->property_values.size(); ++i) {
|
|
if (m_data->property_values[i])
|
|
callback(static_cast<PropertyID>(i + to_underlying(first_longhand_property_id)), *m_data->property_values[i]);
|
|
}
|
|
}
|
|
|
|
void for_each_anchor_name(Function<void(FlyString const&)>) const;
|
|
|
|
HashMap<PropertyID, NonnullRefPtr<StyleValue const>> const& animated_property_values() const;
|
|
RefPtr<AnimatedProperties const> animated_properties_snapshot() const;
|
|
bool has_animated_property(PropertyID property_id) const;
|
|
void reset_non_inherited_animated_properties(Badge<Animations::KeyframeEffect>);
|
|
|
|
bool is_property_important(PropertyID property_id) const;
|
|
bool is_property_inherited(PropertyID property_id) const;
|
|
bool is_animated_property_inherited(PropertyID property_id) const;
|
|
bool is_animated_property_result_of_transition(PropertyID property_id) const;
|
|
bool depends_on_viewport_metrics() const { return m_depends_on_viewport_metrics; }
|
|
bool font_metrics_depend_on_viewport_metrics() const { return m_font_metrics_depend_on_viewport_metrics; }
|
|
bool has_pseudo_element_style(PseudoElement) const;
|
|
void set_depends_on_viewport_metrics(Badge<StyleComputer>);
|
|
void set_font_metrics_depend_on_viewport_metrics(Badge<StyleComputer>);
|
|
void set_animated_property(Badge<StyleComputer>, PropertyID, NonnullRefPtr<StyleValue const> value, AnimatedPropertyResultOfTransition, Inherited = Inherited::No);
|
|
void set_animated_property(Badge<DOM::Element>, PropertyID, NonnullRefPtr<StyleValue const> value, AnimatedPropertyResultOfTransition, Inherited = Inherited::No);
|
|
void remove_animated_property(Badge<DOM::Element>, PropertyID);
|
|
StyleValue const& property(PropertyID, WithAnimationsApplied = WithAnimationsApplied::Yes) const;
|
|
|
|
Size size_value(PropertyID) const;
|
|
[[nodiscard]] Variant<LengthPercentage, NormalGap> gap_value(PropertyID) const;
|
|
Length length(PropertyID) const;
|
|
LengthBox length_box(PropertyID left_id, PropertyID top_id, PropertyID right_id, PropertyID bottom_id, LengthPercentageOrAuto const& default_value) const;
|
|
Color color(PropertyID, ColorResolutionContext) const;
|
|
HashMap<PropertyID, StyleValueVector> assemble_coordinated_value_list(PropertyID base_property_id, Vector<PropertyID> const& property_ids) const;
|
|
ColorInterpolation color_interpolation() const;
|
|
PreferredColorScheme color_scheme(PreferredColorScheme, Optional<Vector<String> const&> document_supported_schemes) const;
|
|
TextAnchor text_anchor() const;
|
|
Optional<BaselineMetric> dominant_baseline() const;
|
|
TextAlign text_align() const;
|
|
TextJustify text_justify() const;
|
|
TextOverflow text_overflow() const;
|
|
TextRendering text_rendering() const;
|
|
CSSPixels text_underline_offset() const;
|
|
TextUnderlinePosition text_underline_position() const;
|
|
Vector<BackgroundLayerData> background_layers() const;
|
|
Vector<BackgroundLayerData> mask_layers() const;
|
|
BackgroundBox background_color_clip() const;
|
|
Length border_spacing_horizontal() const;
|
|
Length border_spacing_vertical() const;
|
|
CaptionSide caption_side() const;
|
|
Clip clip() const;
|
|
Display display() const;
|
|
Float float_() const;
|
|
Color caret_color(Layout::NodeWithStyle const&) const;
|
|
Clear clear() const;
|
|
ColumnSpan column_span() const;
|
|
struct ContentDataAndQuoteNestingLevel {
|
|
ContentData content_data;
|
|
u32 final_quote_nesting_level { 0 };
|
|
};
|
|
ContentDataAndQuoteNestingLevel content(DOM::AbstractElement&, u32 initial_quote_nesting_level) const;
|
|
ContentVisibility content_visibility() const;
|
|
Vector<CursorData> cursor() const;
|
|
Variant<Length, double> tab_size() const;
|
|
WhiteSpaceCollapse white_space_collapse() const;
|
|
WhiteSpaceTrimData white_space_trim() const;
|
|
WordBreak word_break() const;
|
|
CSSPixels word_spacing() const;
|
|
CSSPixels letter_spacing() const;
|
|
LineStyle line_style(PropertyID) const;
|
|
OutlineStyle outline_style() const;
|
|
Vector<TextDecorationLine> text_decoration_line() const;
|
|
TextDecorationSkipInk text_decoration_skip_ink() const;
|
|
TextDecorationStyle text_decoration_style() const;
|
|
TextDecorationThickness text_decoration_thickness() const;
|
|
TextTransform text_transform() const;
|
|
Vector<ShadowData> text_shadow(Layout::Node const&) const;
|
|
TextIndentData text_indent() const;
|
|
TextWrapMode text_wrap_mode() const;
|
|
ListStyleType list_style_type(StyleScope const&) const;
|
|
ListStylePosition list_style_position() const;
|
|
FlexDirection flex_direction() const;
|
|
FlexWrap flex_wrap() const;
|
|
FlexBasis flex_basis() const;
|
|
float flex_grow() const;
|
|
float flex_shrink() const;
|
|
int order() const;
|
|
Color accent_color(ColorResolutionContext const&) const;
|
|
AlignContent align_content() const;
|
|
AlignItems align_items() const;
|
|
AlignSelf align_self() const;
|
|
Appearance appearance() const;
|
|
Filter backdrop_filter() const;
|
|
Filter filter() const;
|
|
float opacity() const;
|
|
Visibility visibility() const;
|
|
ImageRendering image_rendering() const;
|
|
JustifyContent justify_content() const;
|
|
JustifyItems justify_items() const;
|
|
JustifySelf justify_self() const;
|
|
Overflow overflow_x() const;
|
|
Overflow overflow_y() const;
|
|
Vector<ShadowData> box_shadow(Layout::Node const&) const;
|
|
BoxSizing box_sizing() const;
|
|
PointerEvents pointer_events() const;
|
|
Variant<VerticalAlign, LengthPercentage> vertical_align() const;
|
|
FontFeatureData font_feature_data() const;
|
|
Optional<FontVariantAlternates> font_variant_alternates() const;
|
|
FontVariantCaps font_variant_caps() const;
|
|
Optional<FontVariantEastAsian> font_variant_east_asian() const;
|
|
FontVariantEmoji font_variant_emoji() const;
|
|
Optional<FontVariantLigatures> font_variant_ligatures() const;
|
|
Optional<FontVariantNumeric> font_variant_numeric() const;
|
|
FontVariantPosition font_variant_position() const;
|
|
FontKerning font_kerning() const;
|
|
Optional<FlyString> font_language_override() const;
|
|
HashMap<FlyString, u8> font_feature_settings() const;
|
|
HashMap<FlyString, double> font_variation_settings() const;
|
|
GridTrackSizeList grid_auto_columns() const;
|
|
GridTrackSizeList grid_auto_rows() const;
|
|
GridTrackSizeList grid_template_columns() const;
|
|
GridTrackSizeList grid_template_rows() const;
|
|
[[nodiscard]] GridAutoFlow grid_auto_flow() const;
|
|
GridTrackPlacement grid_column_end() const;
|
|
GridTrackPlacement grid_column_start() const;
|
|
GridTrackPlacement grid_row_end() const;
|
|
GridTrackPlacement grid_row_start() const;
|
|
BorderCollapse border_collapse() const;
|
|
CSS::EmptyCells empty_cells() const;
|
|
GridTemplateAreas grid_template_areas() const;
|
|
ObjectFit object_fit() const;
|
|
Position object_position() const;
|
|
TableLayout table_layout() const;
|
|
Direction direction() const;
|
|
UnicodeBidi unicode_bidi() const;
|
|
WritingMode writing_mode() const;
|
|
UserSelect user_select() const;
|
|
Isolation isolation() const;
|
|
TouchActionData touch_action() const;
|
|
Containment contain() const;
|
|
Vector<FlyString> container_name() const;
|
|
ContainerType container_type() const;
|
|
MixBlendMode mix_blend_mode() const;
|
|
Optional<FlyString> view_transition_name() const;
|
|
Vector<AnimationProperties> animations(DOM::AbstractElement const&) const;
|
|
Vector<TransitionProperties> transitions() const;
|
|
|
|
Display display_before_box_type_transformation() const;
|
|
|
|
static Vector<NonnullRefPtr<TransformationStyleValue const>> transformations_for_style_value(StyleValue const& value);
|
|
Vector<NonnullRefPtr<TransformationStyleValue const>> transformations() const;
|
|
TransformBox transform_box() const;
|
|
TransformOrigin transform_origin() const;
|
|
TransformStyle transform_style() const;
|
|
RefPtr<TransformationStyleValue const> rotate() const;
|
|
RefPtr<TransformationStyleValue const> translate() const;
|
|
RefPtr<TransformationStyleValue const> scale() const;
|
|
Optional<CSSPixels> perspective() const;
|
|
Position perspective_origin() const;
|
|
|
|
MaskType mask_type() const;
|
|
float stop_opacity() const;
|
|
Optional<SVGPaint> fill(ColorResolutionContext const&) const;
|
|
float fill_opacity() const;
|
|
Optional<SVGPaint> stroke(ColorResolutionContext const&) const;
|
|
Vector<Variant<LengthPercentage, float>> stroke_dasharray() const;
|
|
StrokeLinecap stroke_linecap() const;
|
|
StrokeLinejoin stroke_linejoin() const;
|
|
double stroke_miterlimit() const;
|
|
float stroke_opacity() const;
|
|
FillRule fill_rule() const;
|
|
ClipRule clip_rule() const;
|
|
float flood_opacity() const;
|
|
CSS::ShapeRendering shape_rendering() const;
|
|
PaintOrderList paint_order() const;
|
|
|
|
WillChange will_change() const;
|
|
|
|
ValueComparingRefPtr<Gfx::FontCascadeList const> cached_computed_font_list() const { return m_cached_computed_font_list; }
|
|
ValueComparingNonnullRefPtr<Gfx::FontCascadeList const> computed_font_list(FontComputer const&) const;
|
|
ValueComparingNonnullRefPtr<Gfx::Font const> first_available_computed_font(FontComputer const&) const;
|
|
|
|
MathStyle math_style() const;
|
|
int math_depth() const;
|
|
[[nodiscard]] CSSPixels line_height() const;
|
|
[[nodiscard]] CSSPixels font_size() const;
|
|
double font_weight() const;
|
|
Percentage font_width() const;
|
|
int font_slope() const;
|
|
FontOpticalSizing font_optical_sizing() const;
|
|
|
|
bool operator==(ComputedProperties const&) const;
|
|
|
|
Positioning position() const;
|
|
Optional<int> z_index() const;
|
|
|
|
QuotesData quotes() const;
|
|
Vector<CounterData> counter_data(PropertyID) const;
|
|
|
|
ScrollbarColorData scrollbar_color(Layout::NodeWithStyle const& layout_node) const;
|
|
ScrollbarWidth scrollbar_width() const;
|
|
Resize resize() const;
|
|
|
|
static NonnullRefPtr<Gfx::Font const> font_fallback(bool monospace, bool bold, float point_size);
|
|
|
|
HashMap<PropertyID, NonnullRefPtr<StyleValue const>> const& inheritance_dependent_specified_values() const { return m_data->inheritance_dependent_specified_values; }
|
|
|
|
RefPtr<StyleValue const> raw_cascaded_font_size() const { return m_data->raw_cascaded_font_size; }
|
|
|
|
private:
|
|
class Data final : public RefCounted<Data> {
|
|
public:
|
|
Data() = default;
|
|
|
|
Array<RefPtr<StyleValue const>, number_of_longhand_properties> property_values;
|
|
AK::FixedBitmap<number_of_longhand_properties> property_important { false };
|
|
AK::FixedBitmap<number_of_longhand_properties> property_inherited { false };
|
|
|
|
Display display_before_box_type_transformation { InitialValues::display() };
|
|
u64 pseudo_element_styles { 0 };
|
|
|
|
Optional<CSSPixels> line_height;
|
|
|
|
HashMap<PropertyID, NonnullRefPtr<StyleValue const>> inheritance_dependent_specified_values;
|
|
RefPtr<StyleValue const> raw_cascaded_font_size;
|
|
};
|
|
|
|
ComputedProperties(NonnullRefPtr<Data const>, bool depends_on_viewport_metrics, bool font_metrics_depend_on_viewport_metrics);
|
|
|
|
Overflow overflow(PropertyID) const;
|
|
Vector<ShadowData> shadow(PropertyID, Layout::Node const&) const;
|
|
Position position_value(PropertyID) const;
|
|
|
|
Data const& data() const { return *m_data; }
|
|
AnimatedProperties const& animated_properties() const;
|
|
AnimatedProperties& mutable_animated_properties();
|
|
void set_animated_property_internal(PropertyID, NonnullRefPtr<StyleValue const>, AnimatedPropertyResultOfTransition, Inherited);
|
|
|
|
NonnullRefPtr<Data const> m_data;
|
|
RefPtr<AnimatedProperties> m_animated_properties;
|
|
bool m_depends_on_viewport_metrics { false };
|
|
bool m_font_metrics_depend_on_viewport_metrics { false };
|
|
|
|
mutable RefPtr<Gfx::FontCascadeList const> m_cached_computed_font_list;
|
|
mutable RefPtr<Gfx::Font const> m_cached_first_available_computed_font;
|
|
void clear_computed_font_list_cache()
|
|
{
|
|
m_cached_computed_font_list = nullptr;
|
|
m_cached_first_available_computed_font = nullptr;
|
|
}
|
|
};
|
|
|
|
class AnimatedProperties final : public RefCounted<AnimatedProperties> {
|
|
public:
|
|
using PropertyMap = HashMap<PropertyID, NonnullRefPtr<StyleValue const>>;
|
|
|
|
AnimatedProperties() = default;
|
|
AnimatedProperties(AnimatedProperties const&);
|
|
|
|
bool is_empty() const { return m_values.is_empty(); }
|
|
PropertyMap const& values() const { return m_values; }
|
|
|
|
bool has_property(PropertyID) const;
|
|
bool is_property_inherited(PropertyID) const;
|
|
bool is_property_result_of_transition(PropertyID) const;
|
|
StyleValue const& property(PropertyID) const;
|
|
|
|
void set_property(PropertyID, NonnullRefPtr<StyleValue const>, AnimatedPropertyResultOfTransition, ComputedProperties::Inherited);
|
|
void remove_property(PropertyID);
|
|
void reset_non_inherited_properties();
|
|
|
|
private:
|
|
void set_property_inherited(PropertyID, ComputedProperties::Inherited);
|
|
void set_property_result_of_transition(PropertyID, AnimatedPropertyResultOfTransition);
|
|
|
|
AK::FixedBitmap<number_of_longhand_properties> m_has_property { false };
|
|
AK::FixedBitmap<number_of_longhand_properties> m_property_inherited { false };
|
|
AK::FixedBitmap<number_of_longhand_properties> m_property_result_of_transition { false };
|
|
PropertyMap m_values;
|
|
};
|
|
|
|
}
|