Coverage Report

Created: 2025-08-28 06:26

/src/serenity/Userland/Libraries/LibWeb/Layout/Node.h
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#pragma once
8
9
#include <AK/NonnullRefPtr.h>
10
#include <AK/TypeCasts.h>
11
#include <AK/Vector.h>
12
#include <LibGfx/Rect.h>
13
#include <LibJS/Heap/Cell.h>
14
#include <LibJS/Heap/Handle.h>
15
#include <LibWeb/CSS/ComputedValues.h>
16
#include <LibWeb/CSS/StyleComputer.h>
17
#include <LibWeb/CSS/StyleProperties.h>
18
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
19
#include <LibWeb/DOM/Document.h>
20
#include <LibWeb/Forward.h>
21
#include <LibWeb/Layout/BoxModelMetrics.h>
22
#include <LibWeb/Painting/PaintContext.h>
23
#include <LibWeb/TreeNode.h>
24
25
namespace Web::Layout {
26
27
enum class LayoutMode {
28
    // Normal layout. No min-content or max-content constraints applied.
29
    Normal,
30
31
    // Intrinsic size determination.
32
    // Boxes honor min-content and max-content constraints (set via LayoutState::UsedValues::{width,height}_constraint)
33
    // by considering their containing block to be 0-sized or infinitely large in the relevant axis.
34
    // https://drafts.csswg.org/css-sizing-3/#intrinsic-sizing
35
    IntrinsicSizing,
36
};
37
38
class Node
39
    : public JS::Cell
40
    , public TreeNode<Node> {
41
    JS_CELL(Node, JS::Cell);
42
43
public:
44
    virtual ~Node();
45
46
    bool is_anonymous() const;
47
    DOM::Node const* dom_node() const;
48
    DOM::Node* dom_node();
49
50
    DOM::Element const* pseudo_element_generator() const;
51
    DOM::Element* pseudo_element_generator();
52
53
    enum class GeneratedFor {
54
        NotGenerated,
55
        PseudoBefore,
56
        PseudoAfter
57
    };
58
0
    bool is_generated() const { return m_generated_for != GeneratedFor::NotGenerated; }
59
0
    bool is_generated_for_before_pseudo_element() const { return m_generated_for == GeneratedFor::PseudoBefore; }
60
0
    bool is_generated_for_after_pseudo_element() const { return m_generated_for == GeneratedFor::PseudoAfter; }
61
    void set_generated_for(GeneratedFor type, DOM::Element& element)
62
0
    {
63
0
        m_generated_for = type;
64
0
        m_pseudo_element_generator = &element;
65
0
    }
66
67
0
    Painting::Paintable* paintable() { return m_paintable; }
68
0
    Painting::Paintable const* paintable() const { return m_paintable; }
69
    void set_paintable(JS::GCPtr<Painting::Paintable>);
70
71
    virtual JS::GCPtr<Painting::Paintable> create_paintable() const;
72
73
    DOM::Document& document();
74
    DOM::Document const& document() const;
75
76
    HTML::BrowsingContext const& browsing_context() const;
77
    HTML::BrowsingContext& browsing_context();
78
79
    JS::GCPtr<HTML::Navigable> navigable() const;
80
81
    Viewport const& root() const;
82
    Viewport& root();
83
84
    bool is_root_element() const;
85
86
    String debug_description() const;
87
88
0
    bool has_style() const { return m_has_style; }
89
    bool has_style_or_parent_with_style() const;
90
91
0
    virtual bool can_have_children() const { return true; }
92
93
    CSS::Display display() const;
94
95
    bool is_inline() const;
96
    bool is_inline_block() const;
97
    bool is_inline_table() const;
98
99
    bool is_out_of_flow(FormattingContext const&) const;
100
101
    // These are used to optimize hot is<T> variants for some classes where dynamic_cast is too slow.
102
0
    virtual bool is_box() const { return false; }
103
0
    virtual bool is_block_container() const { return false; }
104
0
    virtual bool is_break_node() const { return false; }
105
0
    virtual bool is_text_node() const { return false; }
106
0
    virtual bool is_viewport() const { return false; }
107
0
    virtual bool is_svg_box() const { return false; }
108
0
    virtual bool is_svg_geometry_box() const { return false; }
109
0
    virtual bool is_svg_mask_box() const { return false; }
110
0
    virtual bool is_svg_svg_box() const { return false; }
111
0
    virtual bool is_label() const { return false; }
112
0
    virtual bool is_replaced_box() const { return false; }
113
0
    virtual bool is_list_item_box() const { return false; }
114
0
    virtual bool is_list_item_marker_box() const { return false; }
115
0
    virtual bool is_table_wrapper() const { return false; }
116
0
    virtual bool is_node_with_style_and_box_model_metrics() const { return false; }
117
118
    template<typename T>
119
    bool fast_is() const = delete;
120
121
    bool is_floating() const;
122
    bool is_positioned() const;
123
    bool is_absolutely_positioned() const;
124
    bool is_fixed_position() const;
125
126
0
    bool is_flex_item() const { return m_is_flex_item; }
127
0
    void set_flex_item(bool b) { m_is_flex_item = b; }
128
129
0
    bool is_grid_item() const { return m_is_grid_item; }
130
0
    void set_grid_item(bool b) { m_is_grid_item = b; }
131
132
    Box const* containing_block() const;
133
0
    Box* containing_block() { return const_cast<Box*>(const_cast<Node const*>(this)->containing_block()); }
134
135
    [[nodiscard]] Box const* static_position_containing_block() const;
136
0
    [[nodiscard]] Box* static_position_containing_block() { return const_cast<Box*>(const_cast<Node const*>(this)->static_position_containing_block()); }
137
138
    // Closest non-anonymous ancestor box, to be used when resolving percentage values.
139
    // Anonymous block boxes are ignored when resolving percentage values that would refer to it:
140
    // the closest non-anonymous ancestor box is used instead.
141
    // https://www.w3.org/TR/CSS22/visuren.html#anonymous-block-level
142
    Box const* non_anonymous_containing_block() const;
143
144
    bool establishes_stacking_context() const;
145
146
    bool can_contain_boxes_with_position_absolute() const;
147
148
    Gfx::Font const& first_available_font() const;
149
    Gfx::Font const& scaled_font(PaintContext&) const;
150
    Gfx::Font const& scaled_font(float scale_factor) const;
151
152
    CSS::ImmutableComputedValues const& computed_values() const;
153
154
    NodeWithStyle* parent();
155
    NodeWithStyle const* parent() const;
156
157
0
    void inserted_into(Node&) { }
158
0
    void removed_from(Node&) { }
159
0
    void children_changed() { }
160
161
0
    bool children_are_inline() const { return m_children_are_inline; }
162
0
    void set_children_are_inline(bool value) { m_children_are_inline = value; }
163
164
0
    u32 initial_quote_nesting_level() const { return m_initial_quote_nesting_level; }
165
0
    void set_initial_quote_nesting_level(u32 value) { m_initial_quote_nesting_level = value; }
166
167
    // An element is called out of flow if it is floated, absolutely positioned, or is the root element.
168
    // https://www.w3.org/TR/CSS22/visuren.html#positioning-scheme
169
0
    bool is_out_of_flow() const { return is_floating() || is_absolutely_positioned(); }
170
171
    // An element is called in-flow if it is not out-of-flow.
172
    // https://www.w3.org/TR/CSS22/visuren.html#positioning-scheme
173
0
    bool is_in_flow() const { return !is_out_of_flow(); }
174
175
protected:
176
    Node(DOM::Document&, DOM::Node*);
177
178
    virtual void visit_edges(Cell::Visitor&) override;
179
180
private:
181
    friend class NodeWithStyle;
182
183
    JS::NonnullGCPtr<DOM::Node> m_dom_node;
184
    JS::GCPtr<Painting::Paintable> m_paintable;
185
186
    JS::NonnullGCPtr<HTML::BrowsingContext> m_browsing_context;
187
188
    JS::GCPtr<DOM::Element> m_pseudo_element_generator;
189
190
    bool m_anonymous { false };
191
    bool m_has_style { false };
192
    bool m_children_are_inline { false };
193
194
    bool m_is_flex_item { false };
195
    bool m_is_grid_item { false };
196
197
    GeneratedFor m_generated_for { GeneratedFor::NotGenerated };
198
199
    u32 m_initial_quote_nesting_level { 0 };
200
};
201
202
class NodeWithStyle : public Node {
203
    JS_CELL(NodeWithStyle, Node);
204
205
public:
206
0
    virtual ~NodeWithStyle() override = default;
207
208
0
    CSS::ImmutableComputedValues const& computed_values() const { return static_cast<CSS::ImmutableComputedValues const&>(*m_computed_values); }
209
0
    CSS::MutableComputedValues& mutable_computed_values() { return static_cast<CSS::MutableComputedValues&>(*m_computed_values); }
210
211
    void apply_style(const CSS::StyleProperties&);
212
213
    Gfx::Font const& first_available_font() const;
214
0
    Vector<CSS::BackgroundLayerData> const& background_layers() const { return computed_values().background_layers(); }
215
0
    const CSS::AbstractImageStyleValue* list_style_image() const { return m_list_style_image; }
216
217
    JS::NonnullGCPtr<NodeWithStyle> create_anonymous_wrapper() const;
218
219
    void transfer_table_box_computed_values_to_wrapper_computed_values(CSS::ComputedValues& wrapper_computed_values);
220
221
    virtual void visit_edges(Cell::Visitor& visitor) override;
222
223
protected:
224
    NodeWithStyle(DOM::Document&, DOM::Node*, NonnullRefPtr<CSS::StyleProperties>);
225
    NodeWithStyle(DOM::Document&, DOM::Node*, NonnullOwnPtr<CSS::ComputedValues>);
226
227
private:
228
    void reset_table_box_computed_values_used_by_wrapper_to_init_values();
229
    void propagate_style_to_anonymous_wrappers();
230
231
    NonnullOwnPtr<CSS::ComputedValues> m_computed_values;
232
    RefPtr<CSS::AbstractImageStyleValue const> m_list_style_image;
233
};
234
235
class NodeWithStyleAndBoxModelMetrics : public NodeWithStyle {
236
    JS_CELL(NodeWithStyleAndBoxModelMetrics, NodeWithStyle);
237
238
public:
239
0
    BoxModelMetrics& box_model() { return m_box_model; }
240
0
    BoxModelMetrics const& box_model() const { return m_box_model; }
241
242
protected:
243
    NodeWithStyleAndBoxModelMetrics(DOM::Document& document, DOM::Node* node, NonnullRefPtr<CSS::StyleProperties> style)
244
0
        : NodeWithStyle(document, node, move(style))
245
0
    {
246
0
    }
247
248
    NodeWithStyleAndBoxModelMetrics(DOM::Document& document, DOM::Node* node, NonnullOwnPtr<CSS::ComputedValues> computed_values)
249
0
        : NodeWithStyle(document, node, move(computed_values))
250
0
    {
251
0
    }
252
253
private:
254
0
    virtual bool is_node_with_style_and_box_model_metrics() const final { return true; }
255
256
    BoxModelMetrics m_box_model;
257
};
258
259
template<>
260
0
inline bool Node::fast_is<NodeWithStyleAndBoxModelMetrics>() const { return is_node_with_style_and_box_model_metrics(); }
261
262
inline bool Node::has_style_or_parent_with_style() const
263
0
{
264
0
    return m_has_style || (parent() != nullptr && parent()->has_style_or_parent_with_style());
265
0
}
266
267
inline Gfx::Font const& Node::first_available_font() const
268
0
{
269
0
    VERIFY(has_style_or_parent_with_style());
270
0
    if (m_has_style)
271
0
        return static_cast<NodeWithStyle const*>(this)->first_available_font();
272
0
    return parent()->first_available_font();
273
0
}
274
275
inline Gfx::Font const& Node::scaled_font(PaintContext& context) const
276
0
{
277
0
    return scaled_font(context.device_pixels_per_css_pixel());
278
0
}
279
280
inline Gfx::Font const& Node::scaled_font(float scale_factor) const
281
0
{
282
0
    auto const& font = first_available_font();
283
0
    return font.with_size(font.point_size() * scale_factor);
284
0
}
285
286
inline const CSS::ImmutableComputedValues& Node::computed_values() const
287
0
{
288
0
    VERIFY(has_style_or_parent_with_style());
289
290
0
    if (m_has_style)
291
0
        return static_cast<NodeWithStyle const*>(this)->computed_values();
292
0
    return parent()->computed_values();
293
0
}
294
295
inline NodeWithStyle const* Node::parent() const
296
0
{
297
0
    return static_cast<NodeWithStyle const*>(TreeNode<Node>::parent());
298
0
}
299
300
inline NodeWithStyle* Node::parent()
301
0
{
302
0
    return static_cast<NodeWithStyle*>(TreeNode<Node>::parent());
303
0
}
304
305
inline Gfx::Font const& NodeWithStyle::first_available_font() const
306
0
{
307
    // https://drafts.csswg.org/css-fonts/#first-available-font
308
    // First font for which the character U+0020 (space) is not excluded by a unicode-range
309
0
    return computed_values().font_list().font_for_code_point(' ');
310
0
}
311
312
}