/src/serenity/Userland/Libraries/LibWeb/Painting/StackingContext.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020-2022, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/Debug.h> |
9 | | #include <AK/QuickSort.h> |
10 | | #include <AK/StringBuilder.h> |
11 | | #include <LibGfx/AffineTransform.h> |
12 | | #include <LibGfx/Matrix4x4.h> |
13 | | #include <LibGfx/Rect.h> |
14 | | #include <LibWeb/CSS/ComputedValues.h> |
15 | | #include <LibWeb/CSS/StyleValues/TransformationStyleValue.h> |
16 | | #include <LibWeb/Layout/Box.h> |
17 | | #include <LibWeb/Layout/ReplacedBox.h> |
18 | | #include <LibWeb/Layout/Viewport.h> |
19 | | #include <LibWeb/Painting/PaintableBox.h> |
20 | | #include <LibWeb/Painting/SVGPaintable.h> |
21 | | #include <LibWeb/Painting/StackingContext.h> |
22 | | #include <LibWeb/SVG/SVGMaskElement.h> |
23 | | |
24 | | namespace Web::Painting { |
25 | | |
26 | | static void paint_node(Paintable const& paintable, PaintContext& context, PaintPhase phase) |
27 | 0 | { |
28 | 0 | paintable.before_paint(context, phase); |
29 | 0 | paintable.paint(context, phase); |
30 | 0 | paintable.after_paint(context, phase); |
31 | 0 | } |
32 | | |
33 | | StackingContext::StackingContext(Paintable& paintable, StackingContext* parent, size_t index_in_tree_order) |
34 | 0 | : m_paintable(paintable) |
35 | 0 | , m_parent(parent) |
36 | 0 | , m_index_in_tree_order(index_in_tree_order) |
37 | 0 | { |
38 | 0 | VERIFY(m_parent != this); |
39 | 0 | if (m_parent) |
40 | 0 | m_parent->m_children.append(this); |
41 | 0 | } |
42 | | |
43 | | void StackingContext::sort() |
44 | 0 | { |
45 | 0 | quick_sort(m_children, [](auto& a, auto& b) { |
46 | 0 | auto a_z_index = a->paintable().computed_values().z_index().value_or(0); |
47 | 0 | auto b_z_index = b->paintable().computed_values().z_index().value_or(0); |
48 | 0 | if (a_z_index == b_z_index) |
49 | 0 | return a->m_index_in_tree_order < b->m_index_in_tree_order; |
50 | 0 | return a_z_index < b_z_index; |
51 | 0 | }); |
52 | |
|
53 | 0 | for (auto* child : m_children) |
54 | 0 | child->sort(); |
55 | 0 | } |
56 | | |
57 | | void StackingContext::set_last_paint_generation_id(u64 generation_id) |
58 | 0 | { |
59 | 0 | if (m_last_paint_generation_id.has_value() && m_last_paint_generation_id.value() >= generation_id) { |
60 | 0 | dbgln("FIXME: Painting commands are recorded twice for stacking context: {}", m_paintable->layout_node().debug_description()); |
61 | 0 | } |
62 | 0 | m_last_paint_generation_id = generation_id; |
63 | 0 | } |
64 | | |
65 | | static PaintPhase to_paint_phase(StackingContext::StackingContextPaintPhase phase) |
66 | 0 | { |
67 | | // There are not a fully correct mapping since some stacking context phases are combined. |
68 | 0 | switch (phase) { |
69 | 0 | case StackingContext::StackingContextPaintPhase::Floats: |
70 | 0 | case StackingContext::StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced: |
71 | 0 | case StackingContext::StackingContextPaintPhase::BackgroundAndBorders: |
72 | 0 | return PaintPhase::Background; |
73 | 0 | case StackingContext::StackingContextPaintPhase::Foreground: |
74 | 0 | return PaintPhase::Foreground; |
75 | 0 | case StackingContext::StackingContextPaintPhase::FocusAndOverlay: |
76 | 0 | return PaintPhase::Overlay; |
77 | 0 | default: |
78 | 0 | VERIFY_NOT_REACHED(); |
79 | 0 | } |
80 | 0 | } |
81 | | |
82 | | void StackingContext::paint_node_as_stacking_context(Paintable const& paintable, PaintContext& context) |
83 | 0 | { |
84 | 0 | paint_node(paintable, context, PaintPhase::Background); |
85 | 0 | paint_node(paintable, context, PaintPhase::Border); |
86 | 0 | paint_descendants(context, paintable, StackingContextPaintPhase::BackgroundAndBorders); |
87 | 0 | paint_descendants(context, paintable, StackingContextPaintPhase::Floats); |
88 | 0 | paint_descendants(context, paintable, StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced); |
89 | 0 | paint_node(paintable, context, PaintPhase::Foreground); |
90 | 0 | paint_descendants(context, paintable, StackingContextPaintPhase::Foreground); |
91 | 0 | paint_node(paintable, context, PaintPhase::Outline); |
92 | 0 | paint_node(paintable, context, PaintPhase::Overlay); |
93 | 0 | paint_descendants(context, paintable, StackingContextPaintPhase::FocusAndOverlay); |
94 | 0 | } |
95 | | |
96 | | void StackingContext::paint_descendants(PaintContext& context, Paintable const& paintable, StackingContextPaintPhase phase) |
97 | 0 | { |
98 | 0 | paintable.before_children_paint(context, to_paint_phase(phase)); |
99 | |
|
100 | 0 | paintable.for_each_child([&context, phase](auto& child) { |
101 | 0 | auto* stacking_context = child.stacking_context(); |
102 | 0 | auto const& z_index = child.computed_values().z_index(); |
103 | | |
104 | | // NOTE: Grid specification https://www.w3.org/TR/css-grid-2/#z-order says that grid items should be treated |
105 | | // the same way as CSS2 defines for inline-blocks: |
106 | | // "For each one of these, treat the element as if it created a new stacking context, but any positioned |
107 | | // descendants and descendants which actually create a new stacking context should be considered part of |
108 | | // the parent stacking context, not this new one." |
109 | 0 | auto should_be_treated_as_stacking_context = child.layout_node().is_grid_item() && !z_index.has_value(); |
110 | 0 | if (should_be_treated_as_stacking_context) { |
111 | | // FIXME: This may not be fully correct with respect to the paint phases. |
112 | 0 | if (phase == StackingContextPaintPhase::Foreground) |
113 | 0 | paint_node_as_stacking_context(child, context); |
114 | 0 | return IterationDecision::Continue; |
115 | 0 | } |
116 | | |
117 | 0 | if (stacking_context && z_index.value_or(0) != 0) |
118 | 0 | return IterationDecision::Continue; |
119 | 0 | if (child.is_positioned() && z_index.value_or(0) == 0) |
120 | 0 | return IterationDecision::Continue; |
121 | | |
122 | 0 | if (stacking_context) { |
123 | | // FIXME: This may not be fully correct with respect to the paint phases. |
124 | 0 | if (phase == StackingContextPaintPhase::Foreground) { |
125 | 0 | paint_child(context, *stacking_context); |
126 | 0 | } |
127 | | // Note: Don't further recurse into descendants as paint_child() will do that. |
128 | 0 | return IterationDecision::Continue; |
129 | 0 | } |
130 | | |
131 | 0 | bool child_is_inline_or_replaced = child.is_inline() || is<Layout::ReplacedBox>(child.layout_node()); |
132 | 0 | switch (phase) { |
133 | 0 | case StackingContextPaintPhase::BackgroundAndBorders: |
134 | 0 | if (!child_is_inline_or_replaced && !child.is_floating()) { |
135 | 0 | paint_node(child, context, PaintPhase::Background); |
136 | 0 | paint_node(child, context, PaintPhase::Border); |
137 | 0 | paint_descendants(context, child, phase); |
138 | 0 | paint_node(child, context, PaintPhase::TableCollapsedBorder); |
139 | 0 | } |
140 | 0 | break; |
141 | 0 | case StackingContextPaintPhase::Floats: |
142 | 0 | if (child.is_floating()) { |
143 | 0 | paint_node(child, context, PaintPhase::Background); |
144 | 0 | paint_node(child, context, PaintPhase::Border); |
145 | 0 | paint_descendants(context, child, StackingContextPaintPhase::BackgroundAndBorders); |
146 | 0 | } |
147 | 0 | paint_descendants(context, child, phase); |
148 | 0 | break; |
149 | 0 | case StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced: |
150 | 0 | if (child_is_inline_or_replaced) { |
151 | 0 | paint_node(child, context, PaintPhase::Background); |
152 | 0 | paint_node(child, context, PaintPhase::Border); |
153 | 0 | paint_node(child, context, PaintPhase::TableCollapsedBorder); |
154 | 0 | paint_descendants(context, child, StackingContextPaintPhase::BackgroundAndBorders); |
155 | 0 | } |
156 | 0 | paint_descendants(context, child, phase); |
157 | 0 | break; |
158 | 0 | case StackingContextPaintPhase::Foreground: |
159 | 0 | paint_node(child, context, PaintPhase::Foreground); |
160 | 0 | paint_descendants(context, child, phase); |
161 | 0 | break; |
162 | 0 | case StackingContextPaintPhase::FocusAndOverlay: |
163 | 0 | paint_node(child, context, PaintPhase::Outline); |
164 | 0 | paint_node(child, context, PaintPhase::Overlay); |
165 | 0 | paint_descendants(context, child, phase); |
166 | 0 | break; |
167 | 0 | } |
168 | | |
169 | 0 | return IterationDecision::Continue; |
170 | 0 | }); |
171 | |
|
172 | 0 | paintable.after_children_paint(context, to_paint_phase(phase)); |
173 | 0 | } |
174 | | |
175 | | void StackingContext::paint_child(PaintContext& context, StackingContext const& child) |
176 | 0 | { |
177 | 0 | const_cast<StackingContext&>(child).set_last_paint_generation_id(context.paint_generation_id()); |
178 | |
|
179 | 0 | auto parent_paintable = child.paintable().parent(); |
180 | 0 | if (parent_paintable) |
181 | 0 | parent_paintable->before_children_paint(context, PaintPhase::Foreground); |
182 | |
|
183 | 0 | child.paint(context); |
184 | |
|
185 | 0 | if (parent_paintable) |
186 | 0 | parent_paintable->after_children_paint(context, PaintPhase::Foreground); |
187 | 0 | } |
188 | | |
189 | | void StackingContext::paint_internal(PaintContext& context) const |
190 | 0 | { |
191 | | // For a more elaborate description of the algorithm, see CSS 2.1 Appendix E |
192 | | // Draw the background and borders for the context root (steps 1, 2) |
193 | 0 | paint_node(paintable(), context, PaintPhase::Background); |
194 | 0 | paint_node(paintable(), context, PaintPhase::Border); |
195 | | |
196 | | // Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order |
197 | | // (most negative first) then tree order. (step 3) |
198 | | // NOTE: This doesn't check if a descendant is positioned as modern CSS allows for alternative methods to establish stacking contexts. |
199 | 0 | for (auto* child : m_children) { |
200 | 0 | if (child->paintable().computed_values().z_index().has_value() && child->paintable().computed_values().z_index().value() < 0) |
201 | 0 | paint_child(context, *child); |
202 | 0 | } |
203 | | |
204 | | // Draw the background and borders for block-level children (step 4) |
205 | 0 | paint_descendants(context, paintable(), StackingContextPaintPhase::BackgroundAndBorders); |
206 | | // Draw the non-positioned floats (step 5) |
207 | 0 | paint_descendants(context, paintable(), StackingContextPaintPhase::Floats); |
208 | | // Draw inline content, replaced content, etc. (steps 6, 7) |
209 | 0 | paint_descendants(context, paintable(), StackingContextPaintPhase::BackgroundAndBordersForInlineLevelAndReplaced); |
210 | 0 | paint_node(paintable(), context, PaintPhase::Foreground); |
211 | 0 | paint_descendants(context, paintable(), StackingContextPaintPhase::Foreground); |
212 | | |
213 | | // Draw positioned descendants with z-index `0` or `auto` in tree order. (step 8) |
214 | | // FIXME: There's more to this step that we have yet to understand and implement. |
215 | 0 | for (auto const& paintable : m_positioned_descendants_with_stack_level_0_and_stacking_contexts) { |
216 | 0 | if (!paintable->is_positioned()) |
217 | 0 | continue; |
218 | | |
219 | | // At this point, `paintable_box` is a positioned descendant with z-index: auto. |
220 | | // FIXME: This is basically duplicating logic found elsewhere in this same function. Find a way to make this more elegant. |
221 | 0 | auto* parent_paintable = paintable->parent(); |
222 | 0 | if (parent_paintable) |
223 | 0 | parent_paintable->before_children_paint(context, PaintPhase::Foreground); |
224 | 0 | if (auto* child = paintable->stacking_context()) { |
225 | 0 | paint_child(context, *child); |
226 | 0 | } else { |
227 | 0 | paint_node_as_stacking_context(paintable, context); |
228 | 0 | } |
229 | 0 | if (parent_paintable) |
230 | 0 | parent_paintable->after_children_paint(context, PaintPhase::Foreground); |
231 | 0 | }; |
232 | | |
233 | | // Stacking contexts formed by positioned descendants with z-indices greater than or equal to 1 in z-index order |
234 | | // (smallest first) then tree order. (Step 9) |
235 | | // NOTE: This doesn't check if a descendant is positioned as modern CSS allows for alternative methods to establish stacking contexts. |
236 | 0 | for (auto* child : m_children) { |
237 | 0 | if (child->paintable().computed_values().z_index().has_value() && child->paintable().computed_values().z_index().value() >= 1) |
238 | 0 | paint_child(context, *child); |
239 | 0 | } |
240 | |
|
241 | 0 | paint_node(paintable(), context, PaintPhase::Outline); |
242 | |
|
243 | 0 | if (context.should_paint_overlay()) { |
244 | 0 | paint_node(paintable(), context, PaintPhase::Overlay); |
245 | 0 | paint_descendants(context, paintable(), StackingContextPaintPhase::FocusAndOverlay); |
246 | 0 | } |
247 | 0 | } |
248 | | |
249 | | // FIXME: This extracts the affine 2D part of the full transformation matrix. |
250 | | // Use the whole matrix when we get better transformation support in LibGfx or use LibGL for drawing the bitmap |
251 | | Gfx::AffineTransform StackingContext::affine_transform_matrix() const |
252 | 0 | { |
253 | 0 | if (paintable().is_paintable_box()) |
254 | 0 | return Gfx::extract_2d_affine_transform(paintable_box().transform()); |
255 | 0 | return Gfx::AffineTransform {}; |
256 | 0 | } |
257 | | |
258 | | static Gfx::FloatMatrix4x4 matrix_with_scaled_translation(Gfx::FloatMatrix4x4 matrix, float scale) |
259 | 0 | { |
260 | 0 | auto* m = matrix.elements(); |
261 | 0 | m[0][3] *= scale; |
262 | 0 | m[1][3] *= scale; |
263 | 0 | m[2][3] *= scale; |
264 | 0 | return matrix; |
265 | 0 | } |
266 | | |
267 | | void StackingContext::paint(PaintContext& context) const |
268 | 0 | { |
269 | 0 | auto opacity = paintable().computed_values().opacity(); |
270 | 0 | if (opacity == 0.0f) |
271 | 0 | return; |
272 | | |
273 | 0 | DisplayListRecorderStateSaver saver(context.display_list_recorder()); |
274 | |
|
275 | 0 | auto to_device_pixels_scale = float(context.device_pixels_per_css_pixel()); |
276 | 0 | Gfx::IntRect source_paintable_rect; |
277 | 0 | if (paintable().is_paintable_box()) { |
278 | 0 | source_paintable_rect = context.enclosing_device_rect(paintable_box().absolute_paint_rect()).to_type<int>(); |
279 | 0 | } else if (paintable().is_inline()) { |
280 | 0 | source_paintable_rect = context.enclosing_device_rect(inline_paintable().bounding_rect()).to_type<int>(); |
281 | 0 | } else { |
282 | 0 | VERIFY_NOT_REACHED(); |
283 | 0 | } |
284 | | |
285 | 0 | auto transform_matrix = Gfx::FloatMatrix4x4::identity(); |
286 | 0 | Gfx::FloatPoint transform_origin; |
287 | 0 | if (paintable().is_paintable_box()) { |
288 | 0 | transform_matrix = paintable_box().transform(); |
289 | 0 | transform_origin = paintable_box().transform_origin().to_type<float>(); |
290 | 0 | } |
291 | |
|
292 | 0 | DisplayListRecorder::PushStackingContextParams push_stacking_context_params { |
293 | 0 | .opacity = opacity, |
294 | 0 | .is_fixed_position = paintable().is_fixed_position(), |
295 | 0 | .source_paintable_rect = source_paintable_rect, |
296 | 0 | .image_rendering = paintable().computed_values().image_rendering(), |
297 | 0 | .transform = { |
298 | 0 | .origin = transform_origin.scaled(to_device_pixels_scale), |
299 | 0 | .matrix = matrix_with_scaled_translation(transform_matrix, to_device_pixels_scale), |
300 | 0 | }, |
301 | 0 | }; |
302 | |
|
303 | 0 | if (paintable().is_paintable_box()) { |
304 | 0 | if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) { |
305 | 0 | if (masking_area->is_empty()) |
306 | 0 | return; |
307 | 0 | auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area); |
308 | 0 | if (mask_bitmap) { |
309 | 0 | auto source_paintable_rect = context.enclosing_device_rect(*masking_area).to_type<int>(); |
310 | 0 | push_stacking_context_params.source_paintable_rect = source_paintable_rect; |
311 | 0 | push_stacking_context_params.mask = StackingContextMask { |
312 | 0 | .mask_bitmap = mask_bitmap.release_nonnull(), |
313 | 0 | .mask_kind = *paintable_box().get_mask_type() |
314 | 0 | }; |
315 | 0 | } |
316 | 0 | } |
317 | 0 | } |
318 | | |
319 | 0 | context.display_list_recorder().save(); |
320 | 0 | if (paintable().is_paintable_box() && paintable_box().scroll_frame_id().has_value()) |
321 | 0 | context.display_list_recorder().set_scroll_frame_id(*paintable_box().scroll_frame_id()); |
322 | 0 | context.display_list_recorder().push_stacking_context(push_stacking_context_params); |
323 | 0 | paint_internal(context); |
324 | 0 | context.display_list_recorder().pop_stacking_context(); |
325 | 0 | context.display_list_recorder().restore(); |
326 | 0 | } |
327 | | |
328 | | TraversalDecision StackingContext::hit_test(CSSPixelPoint position, HitTestType type, Function<TraversalDecision(HitTestResult)> const& callback) const |
329 | 0 | { |
330 | 0 | if (!paintable().is_visible()) |
331 | 0 | return TraversalDecision::Continue; |
332 | | |
333 | 0 | CSSPixelPoint transform_origin { 0, 0 }; |
334 | 0 | if (paintable().is_paintable_box()) |
335 | 0 | transform_origin = paintable_box().transform_origin(); |
336 | | // NOTE: This CSSPixels -> Float -> CSSPixels conversion is because we can't AffineTransform::map() a CSSPixelPoint. |
337 | 0 | Gfx::FloatPoint offset_position { |
338 | 0 | (position.x() - transform_origin.x()).to_float(), |
339 | 0 | (position.y() - transform_origin.y()).to_float() |
340 | 0 | }; |
341 | 0 | auto transformed_position = affine_transform_matrix().inverse().value_or({}).map(offset_position).to_type<CSSPixels>() + transform_origin; |
342 | |
|
343 | 0 | if (paintable().is_fixed_position()) { |
344 | 0 | auto scroll_offset = paintable().document().navigable()->viewport_scroll_offset(); |
345 | 0 | transformed_position.translate_by(-scroll_offset); |
346 | 0 | } |
347 | | |
348 | | // NOTE: Hit testing basically happens in reverse painting order. |
349 | | // https://www.w3.org/TR/CSS22/visuren.html#z-index |
350 | | |
351 | | // 7. the child stacking contexts with positive stack levels (least positive first). |
352 | | // NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed. |
353 | 0 | for (ssize_t i = m_children.size() - 1; i >= 0; --i) { |
354 | 0 | auto const& child = *m_children[i]; |
355 | 0 | if (child.paintable().computed_values().z_index().value_or(0) <= 0) |
356 | 0 | break; |
357 | 0 | if (child.hit_test(transformed_position, type, callback) == TraversalDecision::Break) |
358 | 0 | return TraversalDecision::Break; |
359 | 0 | } |
360 | | |
361 | | // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0. |
362 | 0 | for (auto const& paintable : m_positioned_descendants_with_stack_level_0_and_stacking_contexts.in_reverse()) { |
363 | 0 | if (paintable->stacking_context()) { |
364 | 0 | if (paintable->stacking_context()->hit_test(transformed_position, type, callback) == TraversalDecision::Break) |
365 | 0 | return TraversalDecision::Break; |
366 | 0 | } else { |
367 | 0 | if (paintable->hit_test(transformed_position, type, callback) == TraversalDecision::Break) |
368 | 0 | return TraversalDecision::Break; |
369 | 0 | } |
370 | 0 | } |
371 | | |
372 | | // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks. |
373 | 0 | if (paintable().layout_node().children_are_inline() && is<Layout::BlockContainer>(paintable().layout_node())) { |
374 | 0 | for (auto const* child = paintable().last_child(); child; child = child->previous_sibling()) { |
375 | 0 | if (child->is_inline() && !child->is_absolutely_positioned() && !child->stacking_context()) { |
376 | 0 | if (child->hit_test(transformed_position, type, callback) == TraversalDecision::Break) |
377 | 0 | return TraversalDecision::Break; |
378 | 0 | } |
379 | 0 | } |
380 | 0 | } |
381 | | |
382 | | // 4. the non-positioned floats. |
383 | 0 | for (auto const& paintable : m_non_positioned_floating_descendants.in_reverse()) { |
384 | 0 | if (paintable->hit_test(transformed_position, type, callback) == TraversalDecision::Break) |
385 | 0 | return TraversalDecision::Break; |
386 | 0 | } |
387 | | |
388 | | // 3. the in-flow, non-inline-level, non-positioned descendants. |
389 | 0 | if (!paintable().layout_node().children_are_inline()) { |
390 | 0 | for (auto const* child = paintable().last_child(); child; child = child->previous_sibling()) { |
391 | 0 | if (!child->is_paintable_box()) |
392 | 0 | continue; |
393 | | |
394 | 0 | auto const& paintable_box = verify_cast<PaintableBox>(*child); |
395 | 0 | if (!paintable_box.is_absolutely_positioned() && !paintable_box.is_floating() && !paintable_box.stacking_context()) { |
396 | 0 | if (paintable_box.hit_test(transformed_position, type, callback) == TraversalDecision::Break) |
397 | 0 | return TraversalDecision::Break; |
398 | 0 | } |
399 | 0 | } |
400 | 0 | } |
401 | | |
402 | | // 2. the child stacking contexts with negative stack levels (most negative first). |
403 | | // NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed. |
404 | 0 | for (ssize_t i = m_children.size() - 1; i >= 0; --i) { |
405 | 0 | auto const& child = *m_children[i]; |
406 | 0 | if (child.paintable().computed_values().z_index().value_or(0) >= 0) |
407 | 0 | break; |
408 | 0 | if (child.hit_test(transformed_position, type, callback) == TraversalDecision::Break) |
409 | 0 | return TraversalDecision::Break; |
410 | 0 | } |
411 | | |
412 | | // 1. the background and borders of the element forming the stacking context. |
413 | 0 | if (paintable().is_paintable_box()) { |
414 | 0 | if (paintable_box().absolute_border_box_rect().contains(transformed_position.x(), transformed_position.y())) { |
415 | 0 | auto hit_test_result = HitTestResult { .paintable = const_cast<PaintableBox&>(paintable_box()) }; |
416 | 0 | if (callback(hit_test_result) == TraversalDecision::Break) |
417 | 0 | return TraversalDecision::Break; |
418 | 0 | } |
419 | 0 | } |
420 | | |
421 | 0 | return TraversalDecision::Continue; |
422 | 0 | } |
423 | | |
424 | | void StackingContext::dump(int indent) const |
425 | 0 | { |
426 | 0 | StringBuilder builder; |
427 | 0 | for (int i = 0; i < indent; ++i) |
428 | 0 | builder.append(' '); |
429 | 0 | CSSPixelRect rect; |
430 | 0 | if (paintable().is_paintable_box()) { |
431 | 0 | rect = paintable_box().absolute_rect(); |
432 | 0 | } else if (paintable().is_inline_paintable()) { |
433 | 0 | rect = inline_paintable().bounding_rect(); |
434 | 0 | } else { |
435 | 0 | VERIFY_NOT_REACHED(); |
436 | 0 | } |
437 | 0 | builder.appendff("SC for {} {} [children: {}] (z-index: ", paintable().layout_node().debug_description(), rect, m_children.size()); |
438 | |
|
439 | 0 | if (paintable().computed_values().z_index().has_value()) |
440 | 0 | builder.appendff("{}", paintable().computed_values().z_index().value()); |
441 | 0 | else |
442 | 0 | builder.append("auto"sv); |
443 | 0 | builder.append(')'); |
444 | |
|
445 | 0 | auto affine_transform = affine_transform_matrix(); |
446 | 0 | if (!affine_transform.is_identity()) { |
447 | 0 | builder.appendff(", transform: {}", affine_transform); |
448 | 0 | } |
449 | 0 | dbgln("{}", builder.string_view()); |
450 | 0 | for (auto& child : m_children) |
451 | 0 | child->dump(indent + 1); |
452 | 0 | } |
453 | | |
454 | | } |