Move the layout tree from GC allocation to refcounted ownership so removed layout and paint subtrees are destroyed synchronously instead of waiting for the next GC sweep. This dramatically reduces GC memory usage peaks after layout tree churn and makes it easier for memory use to fall back after large document updates. Update layout factories, tree traversal, SVG layout node creation, paintable back-pointers, and pseudo-element layout links to use RefPtr ownership. Make display: contents follow the same shape as Blink and WebKit: the element itself does not create a layout node, and its children are flattened into the nearest layout parent. Wrap direct non-whitespace text in an anonymous inline node when the boxless element contributes inherited style to that text. Use an internal inline wrapper for display: contents pseudo-elements so generated content can still participate in layout, painting, hit testing, and pseudo-element queries. Keep CSSOM reporting the computed display value from the pseudo style, not the internal wrapper. Remove the retained out-of-tree layout node list and its testing hook, since the flattened model does not need a side owner for boxless elements. Add coverage for inherited text style, dynamic insertion order, pseudo-element hit testing, and computed style queries.
274 lines
11 KiB
C++
274 lines
11 KiB
C++
/*
|
|
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/OwnPtr.h>
|
|
#include <LibWeb/Forward.h>
|
|
#include <LibWeb/Layout/AvailableSpace.h>
|
|
#include <LibWeb/Layout/LayoutState.h>
|
|
|
|
namespace Web::Layout {
|
|
|
|
// NOTE: We use a custom clamping function here instead of AK::clamp(), since the AK version
|
|
// will VERIFY(max >= min) and CSS explicitly allows that (see css-values-4.)
|
|
template<typename T>
|
|
[[nodiscard]] constexpr T css_clamp(T const& value, T const& min, T const& max)
|
|
{
|
|
return ::max(min, ::min(value, max));
|
|
}
|
|
|
|
enum class Alignment {
|
|
Baseline,
|
|
Center,
|
|
End,
|
|
Normal,
|
|
Safe,
|
|
SelfEnd,
|
|
SelfStart,
|
|
SpaceAround,
|
|
SpaceBetween,
|
|
SpaceEvenly,
|
|
Start,
|
|
Stretch,
|
|
Unsafe,
|
|
};
|
|
|
|
enum class AbsposAxisMode {
|
|
// Both insets auto: offset = static_position + margin
|
|
StaticPosition,
|
|
// At least one explicit inset: offset = rect.origin + inset + margin
|
|
InsetFromRect,
|
|
};
|
|
|
|
enum class TableWrapperWidthMode {
|
|
ClampToAvailableWidth,
|
|
UseTableUsedWidthIfNotAuto,
|
|
};
|
|
|
|
struct AbsposContainingBlockInfo {
|
|
// Containing block rect in CB Box's content-edge coordinates.
|
|
CSSPixelRect rect;
|
|
AbsposAxisMode horizontal_axis_mode;
|
|
AbsposAxisMode vertical_axis_mode;
|
|
// Grid alignment for axes with auto CSS insets.
|
|
// When set, the base method applies alignment-driven insets after sizing.
|
|
Optional<Alignment> horizontal_alignment;
|
|
Optional<Alignment> vertical_alignment;
|
|
};
|
|
|
|
class FormattingContext {
|
|
#if FORMATTING_CONTEXT_TRACE_DEBUG
|
|
friend class FormattingContextTracer;
|
|
#endif
|
|
|
|
public:
|
|
virtual ~FormattingContext();
|
|
|
|
enum class Type {
|
|
Block,
|
|
Inline,
|
|
Flex,
|
|
Grid,
|
|
Table,
|
|
SVG,
|
|
ReplacedWithChildren,
|
|
InternalReplaced, // Internal hack formatting context for replaced elements. FIXME: Get rid of this.
|
|
InternalDummy, // Internal hack formatting context for unimplemented things. FIXME: Get rid of this.
|
|
};
|
|
|
|
static constexpr StringView type_name(Type type)
|
|
{
|
|
switch (type) {
|
|
case Type::Block:
|
|
return "BFC"sv;
|
|
case Type::Inline:
|
|
return "IFC"sv;
|
|
case Type::Flex:
|
|
return "FFC"sv;
|
|
case Type::Grid:
|
|
return "GFC"sv;
|
|
case Type::Table:
|
|
return "TFC"sv;
|
|
case Type::SVG:
|
|
return "SVG"sv;
|
|
case Type::ReplacedWithChildren:
|
|
return "Replaced, with children"sv;
|
|
case Type::InternalReplaced:
|
|
return "Replaced"sv;
|
|
case Type::InternalDummy:
|
|
return "Dummy"sv;
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
virtual void run(AvailableSpace const&) = 0;
|
|
|
|
// These functions return the automatic content dimensions of the context's root box.
|
|
virtual CSSPixels automatic_content_width() const = 0;
|
|
virtual CSSPixels automatic_content_height() const = 0;
|
|
|
|
Box const& context_box() const { return m_context_box; }
|
|
|
|
FormattingContext* parent() { return m_parent; }
|
|
FormattingContext const* parent() const { return m_parent; }
|
|
|
|
Type type() const { return m_type; }
|
|
|
|
virtual bool inhibits_floating() const { return false; }
|
|
|
|
[[nodiscard]] static Optional<Type> formatting_context_type_created_by_box(Box const&);
|
|
|
|
static bool creates_block_formatting_context(Box const&);
|
|
|
|
CSSPixels compute_table_box_width_inside_table_wrapper(Box const&, AvailableSpace const&,
|
|
Optional<CSSPixels> table_wrapper_containing_block_width = {},
|
|
TableWrapperWidthMode = TableWrapperWidthMode::ClampToAvailableWidth);
|
|
CSSPixels compute_table_box_height_inside_table_wrapper(Box const&, AvailableSpace const&);
|
|
|
|
CSSPixels compute_width_for_replaced_element(Box const&, AvailableSpace const&) const;
|
|
CSSPixels compute_height_for_replaced_element(Box const&, AvailableSpace const&) const;
|
|
|
|
OwnPtr<FormattingContext> create_independent_formatting_context_if_needed(LayoutState&, LayoutMode, Box const& child_box);
|
|
NonnullOwnPtr<FormattingContext> create_independent_formatting_context(LayoutState&, LayoutMode, Box const& child_box);
|
|
|
|
virtual void parent_context_did_dimension_child_root_box() { }
|
|
|
|
CSSPixels calculate_min_content_width(Layout::Box const&) const;
|
|
CSSPixels calculate_max_content_width(Layout::Box const&) const;
|
|
CSSPixels calculate_min_content_height(Layout::Box const&, CSSPixels width) const;
|
|
CSSPixels calculate_max_content_height(Layout::Box const&, CSSPixels width) const;
|
|
|
|
CSSPixels calculate_fit_content_height(Layout::Box const&, AvailableSpace const&) const;
|
|
CSSPixels calculate_fit_content_width(Layout::Box const&, AvailableSpace const&) const;
|
|
|
|
CSSPixels calculate_inner_width(Layout::Box const&, AvailableSize const&, CSS::Size const& width) const;
|
|
[[nodiscard]] CSSPixels calculate_inner_height(Box const&, AvailableSpace const&, CSS::Size const& height) const;
|
|
|
|
virtual CSSPixels greatest_child_width(Box const&) const;
|
|
|
|
[[nodiscard]] CSSPixelRect absolute_content_rect(Box const&) const;
|
|
[[nodiscard]] CSSPixelRect margin_box_rect_in_ancestor_coordinate_space(Box const&, Box const& ancestor_box) const;
|
|
[[nodiscard]] CSSPixelRect margin_box_rect_in_ancestor_coordinate_space(LayoutState::UsedValues const&, Box const& ancestor_box) const;
|
|
[[nodiscard]] CSSPixelRect content_box_rect(Box const&) const;
|
|
[[nodiscard]] CSSPixelRect content_box_rect(LayoutState::UsedValues const&) const;
|
|
[[nodiscard]] CSSPixelRect content_box_rect_in_ancestor_coordinate_space(LayoutState::UsedValues const&, Box const& ancestor_box) const;
|
|
[[nodiscard]] CSSPixels box_baseline(Box const&) const;
|
|
[[nodiscard]] CSSPixels containing_block_width_for(NodeWithStyleAndBoxModelMetrics const&) const;
|
|
|
|
[[nodiscard]] CSSPixels calculate_stretch_fit_width(Box const&, AvailableSize const&) const;
|
|
[[nodiscard]] CSSPixels calculate_stretch_fit_height(Box const&, AvailableSize const&) const;
|
|
|
|
bool can_skip_is_anonymous_text_run(Box&);
|
|
|
|
void compute_inset(NodeWithStyleAndBoxModelMetrics const&, CSSPixelSize containing_block_size);
|
|
|
|
protected:
|
|
FormattingContext(Type, LayoutMode, LayoutState&, Box const&, FormattingContext* parent = nullptr);
|
|
|
|
[[nodiscard]] static bool computed_height_establishes_definite_containing_block_height(CSS::Size const&);
|
|
|
|
[[nodiscard]] bool should_treat_width_as_auto(Box const&, AvailableSpace const&) const;
|
|
[[nodiscard]] bool should_treat_height_as_auto(Box const&, AvailableSpace const&) const;
|
|
|
|
[[nodiscard]] bool should_treat_max_width_as_none(Box const&, AvailableSize const&) const;
|
|
[[nodiscard]] bool should_treat_max_height_as_none(Box const&, AvailableSize const&) const;
|
|
|
|
[[nodiscard]] bool box_is_sized_as_replaced_element(Box const&, AvailableSpace const&) const;
|
|
|
|
OwnPtr<FormattingContext> layout_inside(Box const&, LayoutMode, AvailableSpace const&);
|
|
|
|
struct SpaceUsedByFloats {
|
|
CSSPixels left { 0 };
|
|
CSSPixels right { 0 };
|
|
};
|
|
|
|
struct SpaceUsedAndContainingMarginForFloats {
|
|
// Width for left / right floats, including their own margins.
|
|
CSSPixels left_used_space;
|
|
CSSPixels right_used_space;
|
|
// Left / right total margins from the outermost containing block to the floating element.
|
|
// Each block in the containing chain adds its own margin and we store the total here.
|
|
CSSPixels left_total_containing_margin;
|
|
CSSPixels right_total_containing_margin;
|
|
Box const* matching_left_float_box { nullptr };
|
|
Box const* matching_right_float_box { nullptr };
|
|
};
|
|
|
|
struct ShrinkToFitResult {
|
|
CSSPixels preferred_width { 0 };
|
|
CSSPixels preferred_minimum_width { 0 };
|
|
};
|
|
|
|
CSSPixels tentative_width_for_replaced_element(Box const&, CSS::Size const& computed_width, AvailableSpace const&) const;
|
|
CSSPixels tentative_height_for_replaced_element(Box const&, CSS::Size const& computed_height, AvailableSpace const&) const;
|
|
CSSPixels compute_auto_height_for_block_formatting_context_root(Box const&) const;
|
|
static CSSPixels line_box_physical_width(Box const&, LineBox const&);
|
|
|
|
[[nodiscard]] CSSPixelSize solve_replaced_size_constraint(CSSPixels input_width, CSSPixels input_height, Box const&, AvailableSpace const&) const;
|
|
|
|
ShrinkToFitResult calculate_shrink_to_fit_widths(Box const&);
|
|
|
|
void layout_absolutely_positioned_element(Box&);
|
|
|
|
CSSPixels gap_to_px(Variant<CSS::LengthPercentage, CSS::NormalGap> const& gap, CSSPixels reference_value) const;
|
|
|
|
void layout_absolutely_positioned_children();
|
|
virtual AbsposContainingBlockInfo resolve_abspos_containing_block_info(Box const&);
|
|
void resolve_anchor_insets(Box&) const;
|
|
void compute_width_for_absolutely_positioned_element(Box const&, AvailableSpace const&);
|
|
void compute_width_for_absolutely_positioned_non_replaced_element(Box const&, AvailableSpace const&);
|
|
void compute_width_for_absolutely_positioned_replaced_element(Box const&, AvailableSpace const&);
|
|
|
|
enum class BeforeOrAfterInsideLayout {
|
|
Before,
|
|
After,
|
|
};
|
|
void compute_height_for_absolutely_positioned_element(Box const&, AvailableSpace const&, BeforeOrAfterInsideLayout);
|
|
void compute_height_for_absolutely_positioned_non_replaced_element(Box const&, AvailableSpace const&, BeforeOrAfterInsideLayout);
|
|
void compute_height_for_absolutely_positioned_replaced_element(Box const&, AvailableSpace const&, BeforeOrAfterInsideLayout);
|
|
|
|
[[nodiscard]] Optional<CSSPixels> compute_auto_height_for_absolutely_positioned_element(Box const&, AvailableSpace const&, BeforeOrAfterInsideLayout) const;
|
|
|
|
[[nodiscard]] Box const* box_child_to_derive_baseline_from(Box const&) const;
|
|
|
|
Type m_type {};
|
|
LayoutMode m_layout_mode;
|
|
|
|
FormattingContext* m_parent { nullptr };
|
|
Box const& m_context_box;
|
|
|
|
LayoutState& m_state;
|
|
};
|
|
|
|
#if FORMATTING_CONTEXT_TRACE_DEBUG
|
|
class FormattingContextTracer {
|
|
public:
|
|
FormattingContextTracer(FormattingContext const& fc, AvailableSpace const& available_space)
|
|
{
|
|
StringBuilder indent_builder;
|
|
for (int i = 0; i < s_depth; ++i)
|
|
indent_builder.append("| "sv);
|
|
auto intrinsic_marker = fc.m_layout_mode == LayoutMode::IntrinsicSizing ? " [intrinsic]"sv : ""sv;
|
|
dbgln("{}|- {} <{}> run({}){}", indent_builder.string_view(), FormattingContext::type_name(fc.m_type), fc.m_context_box.debug_description(), available_space, intrinsic_marker);
|
|
++s_depth;
|
|
}
|
|
|
|
~FormattingContextTracer()
|
|
{
|
|
--s_depth;
|
|
}
|
|
|
|
private:
|
|
inline static int s_depth = 0;
|
|
};
|
|
|
|
# define FORMATTING_CONTEXT_TRACE() FormattingContextTracer _formatting_context_tracer(*this, available_space)
|
|
#else
|
|
# define FORMATTING_CONTEXT_TRACE()
|
|
#endif
|
|
|
|
}
|