/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 | | } |