Coverage Report

Created: 2026-02-16 07:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2022-2023, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <AK/GenericShorthands.h>
9
#include <LibGfx/Font/ScaledFont.h>
10
#include <LibGfx/Painter.h>
11
#include <LibUnicode/CharacterTypes.h>
12
#include <LibWeb/CSS/SystemColor.h>
13
#include <LibWeb/DOM/Document.h>
14
#include <LibWeb/DOM/Range.h>
15
#include <LibWeb/HTML/HTMLHtmlElement.h>
16
#include <LibWeb/HTML/Window.h>
17
#include <LibWeb/Layout/BlockContainer.h>
18
#include <LibWeb/Layout/Viewport.h>
19
#include <LibWeb/Painting/BackgroundPainting.h>
20
#include <LibWeb/Painting/FilterPainting.h>
21
#include <LibWeb/Painting/PaintableBox.h>
22
#include <LibWeb/Painting/SVGPaintable.h>
23
#include <LibWeb/Painting/SVGSVGPaintable.h>
24
#include <LibWeb/Painting/StackingContext.h>
25
#include <LibWeb/Painting/TableBordersPainting.h>
26
#include <LibWeb/Painting/TextPaintable.h>
27
#include <LibWeb/Painting/ViewportPaintable.h>
28
#include <LibWeb/Platform/FontPlugin.h>
29
#include <LibWeb/Selection/Selection.h>
30
31
namespace Web::Painting {
32
33
JS::NonnullGCPtr<PaintableWithLines> PaintableWithLines::create(Layout::BlockContainer const& block_container)
34
0
{
35
0
    return block_container.heap().allocate_without_realm<PaintableWithLines>(block_container);
36
0
}
37
38
JS::NonnullGCPtr<PaintableBox> PaintableBox::create(Layout::Box const& layout_box)
39
0
{
40
0
    return layout_box.heap().allocate_without_realm<PaintableBox>(layout_box);
41
0
}
42
43
PaintableBox::PaintableBox(Layout::Box const& layout_box)
44
0
    : Paintable(layout_box)
45
0
{
46
0
}
47
48
PaintableBox::~PaintableBox()
49
0
{
50
0
}
51
52
PaintableWithLines::PaintableWithLines(Layout::BlockContainer const& layout_box)
53
0
    : PaintableBox(layout_box)
54
0
{
55
0
}
56
57
PaintableWithLines::~PaintableWithLines()
58
0
{
59
0
}
60
61
CSSPixelPoint PaintableBox::scroll_offset() const
62
0
{
63
0
    if (is_viewport()) {
64
0
        auto navigable = document().navigable();
65
0
        VERIFY(navigable);
66
0
        return navigable->viewport_scroll_offset();
67
0
    }
68
69
0
    auto const& node = layout_node();
70
0
    if (node.is_generated_for_before_pseudo_element())
71
0
        return node.pseudo_element_generator()->scroll_offset(DOM::Element::ScrollOffsetFor::PseudoBefore);
72
0
    if (node.is_generated_for_after_pseudo_element())
73
0
        return node.pseudo_element_generator()->scroll_offset(DOM::Element::ScrollOffsetFor::PseudoAfter);
74
75
0
    if (!(dom_node() && is<DOM::Element>(*dom_node())))
76
0
        return {};
77
78
0
    return static_cast<DOM::Element const*>(dom_node())->scroll_offset(DOM::Element::ScrollOffsetFor::Self);
79
0
}
80
81
void PaintableBox::set_scroll_offset(CSSPixelPoint offset)
82
0
{
83
0
    auto scrollable_overflow_rect = this->scrollable_overflow_rect();
84
0
    if (!scrollable_overflow_rect.has_value())
85
0
        return;
86
87
0
    document().set_needs_to_refresh_clip_state(true);
88
0
    document().set_needs_to_refresh_scroll_state(true);
89
90
0
    auto padding_rect = absolute_padding_box_rect();
91
0
    auto max_x_offset = max(scrollable_overflow_rect->width() - padding_rect.width(), 0);
92
0
    auto max_y_offset = max(scrollable_overflow_rect->height() - padding_rect.height(), 0);
93
94
0
    offset.set_x(clamp(offset.x(), 0, max_x_offset));
95
0
    offset.set_y(clamp(offset.y(), 0, max_y_offset));
96
97
    // FIXME: If there is horizontal and vertical scroll ignore only part of the new offset
98
0
    if (offset.y() < 0 || scroll_offset() == offset)
99
0
        return;
100
101
0
    auto& node = layout_node();
102
0
    if (node.is_generated_for_before_pseudo_element()) {
103
0
        node.pseudo_element_generator()->set_scroll_offset(DOM::Element::ScrollOffsetFor::PseudoBefore, offset);
104
0
    } else if (node.is_generated_for_after_pseudo_element()) {
105
0
        node.pseudo_element_generator()->set_scroll_offset(DOM::Element::ScrollOffsetFor::PseudoAfter, offset);
106
0
    } else if (is<DOM::Element>(*dom_node())) {
107
0
        static_cast<DOM::Element*>(dom_node())->set_scroll_offset(DOM::Element::ScrollOffsetFor::Self, offset);
108
0
    } else {
109
0
        return;
110
0
    }
111
112
    // https://drafts.csswg.org/cssom-view-1/#scrolling-events
113
    // Whenever an element gets scrolled (whether in response to user interaction or by an API),
114
    // the user agent must run these steps:
115
116
    // 1. Let doc be the element’s node document.
117
0
    auto& document = layout_box().document();
118
119
    // FIXME: 2. If the element is a snap container, run the steps to update snapchanging targets for the element with
120
    //           the element’s eventual snap target in the block axis as newBlockTarget and the element’s eventual snap
121
    //           target in the inline axis as newInlineTarget.
122
123
0
    JS::NonnullGCPtr<DOM::EventTarget> const event_target = *dom_node();
124
125
    // 3. If the element is already in doc’s pending scroll event targets, abort these steps.
126
0
    if (document.pending_scroll_event_targets().contains_slow(event_target))
127
0
        return;
128
129
    // 4. Append the element to doc’s pending scroll event targets.
130
0
    document.pending_scroll_event_targets().append(*layout_box().dom_node());
131
132
0
    set_needs_display();
133
0
}
134
135
void PaintableBox::scroll_by(int delta_x, int delta_y)
136
0
{
137
0
    set_scroll_offset(scroll_offset().translated(delta_x, delta_y));
138
0
}
139
140
void PaintableBox::set_offset(CSSPixelPoint offset)
141
0
{
142
0
    m_offset = offset;
143
0
}
144
145
void PaintableBox::set_content_size(CSSPixelSize size)
146
0
{
147
0
    m_content_size = size;
148
0
    layout_box().did_set_content_size();
149
0
}
150
151
CSSPixelPoint PaintableBox::offset() const
152
0
{
153
0
    return m_offset;
154
0
}
155
156
CSSPixelRect PaintableBox::compute_absolute_rect() const
157
0
{
158
0
    CSSPixelRect rect { offset(), content_size() };
159
0
    for (auto const* block = containing_block(); block; block = block->containing_block())
160
0
        rect.translate_by(block->offset());
161
0
    return rect;
162
0
}
163
164
CSSPixelRect PaintableBox::compute_absolute_padding_rect_with_css_transform_applied() const
165
0
{
166
0
    auto rect = absolute_rect();
167
0
    auto scroll_offset = this->enclosing_scroll_frame_offset();
168
0
    if (scroll_offset.has_value())
169
0
        rect.translate_by(scroll_offset.value());
170
0
    rect.translate_by(combined_css_transform().translation().to_type<CSSPixels>());
171
172
0
    CSSPixelRect padding_rect;
173
0
    padding_rect.set_x(rect.x() - box_model().padding.left);
174
0
    padding_rect.set_width(content_width() + box_model().padding.left + box_model().padding.right);
175
0
    padding_rect.set_y(rect.y() - box_model().padding.top);
176
0
    padding_rect.set_height(content_height() + box_model().padding.top + box_model().padding.bottom);
177
0
    return padding_rect;
178
0
}
179
180
CSSPixelRect PaintableBox::absolute_rect() const
181
0
{
182
0
    if (!m_absolute_rect.has_value())
183
0
        m_absolute_rect = compute_absolute_rect();
184
0
    return *m_absolute_rect;
185
0
}
186
187
CSSPixelRect PaintableBox::compute_absolute_paint_rect() const
188
0
{
189
    // FIXME: This likely incomplete:
190
0
    auto rect = absolute_border_box_rect();
191
0
    if (has_scrollable_overflow()) {
192
0
        auto scrollable_overflow_rect = this->scrollable_overflow_rect().value();
193
0
        if (computed_values().overflow_x() == CSS::Overflow::Visible)
194
0
            rect.unite_horizontally(scrollable_overflow_rect);
195
0
        if (computed_values().overflow_y() == CSS::Overflow::Visible)
196
0
            rect.unite_vertically(scrollable_overflow_rect);
197
0
    }
198
0
    for (auto const& shadow : box_shadow_data()) {
199
0
        if (shadow.placement == ShadowPlacement::Inner)
200
0
            continue;
201
0
        auto inflate = shadow.spread_distance + shadow.blur_radius;
202
0
        auto shadow_rect = rect.inflated(inflate, inflate, inflate, inflate).translated(shadow.offset_x, shadow.offset_y);
203
0
        rect = rect.united(shadow_rect);
204
0
    }
205
0
    return rect;
206
0
}
207
208
CSSPixelRect PaintableBox::absolute_paint_rect() const
209
0
{
210
0
    if (!m_absolute_paint_rect.has_value())
211
0
        m_absolute_paint_rect = compute_absolute_paint_rect();
212
0
    return *m_absolute_paint_rect;
213
0
}
214
215
Optional<CSSPixelRect> PaintableBox::get_clip_rect() const
216
0
{
217
0
    auto clip = computed_values().clip();
218
0
    if (clip.is_rect() && layout_box().is_absolutely_positioned()) {
219
0
        auto border_box = absolute_border_box_rect();
220
0
        return clip.to_rect().resolved(layout_node(), border_box);
221
0
    }
222
0
    return {};
223
0
}
224
225
bool PaintableBox::wants_mouse_events() const
226
0
{
227
0
    if (scroll_thumb_rect(ScrollDirection::Vertical).has_value())
228
0
        return true;
229
0
    if (scroll_thumb_rect(ScrollDirection::Horizontal).has_value())
230
0
        return true;
231
0
    return false;
232
0
}
233
234
void PaintableBox::before_paint(PaintContext& context, [[maybe_unused]] PaintPhase phase) const
235
0
{
236
0
    if (!is_visible())
237
0
        return;
238
239
0
    apply_clip_overflow_rect(context, phase);
240
0
    apply_scroll_offset(context, phase);
241
0
}
242
243
void PaintableBox::after_paint(PaintContext& context, [[maybe_unused]] PaintPhase phase) const
244
0
{
245
0
    if (!is_visible())
246
0
        return;
247
248
0
    reset_scroll_offset(context, phase);
249
0
    clear_clip_overflow_rect(context, phase);
250
0
}
251
252
bool PaintableBox::is_scrollable(ScrollDirection direction) const
253
0
{
254
0
    auto overflow = direction == ScrollDirection::Horizontal ? computed_values().overflow_x() : computed_values().overflow_y();
255
0
    auto scrollable_overflow_size = direction == ScrollDirection::Horizontal ? scrollable_overflow_rect()->width() : scrollable_overflow_rect()->height();
256
0
    auto scrollport_size = direction == ScrollDirection::Horizontal ? absolute_padding_box_rect().width() : absolute_padding_box_rect().height();
257
0
    if (is_viewport() || overflow == CSS::Overflow::Auto)
258
0
        return scrollable_overflow_size > scrollport_size;
259
0
    return overflow == CSS::Overflow::Scroll;
260
0
}
261
262
static constexpr CSSPixels scrollbar_thumb_thickness = 8;
263
264
Optional<CSSPixelRect> PaintableBox::scroll_thumb_rect(ScrollDirection direction) const
265
0
{
266
0
    if (!is_scrollable(direction))
267
0
        return {};
268
269
0
    auto padding_rect = absolute_padding_box_rect();
270
0
    auto scrollable_overflow_rect = this->scrollable_overflow_rect().value();
271
0
    auto scroll_overflow_size = direction == ScrollDirection::Horizontal ? scrollable_overflow_rect.width() : scrollable_overflow_rect.height();
272
0
    auto scrollport_size = direction == ScrollDirection::Horizontal ? padding_rect.width() : padding_rect.height();
273
0
    auto scroll_offset = direction == ScrollDirection::Horizontal ? this->scroll_offset().x() : this->scroll_offset().y();
274
0
    if (scroll_overflow_size == 0)
275
0
        return {};
276
277
0
    auto thumb_size = scrollport_size * (scrollport_size / scroll_overflow_size);
278
0
    CSSPixels thumb_position = 0;
279
0
    if (scroll_overflow_size > scrollport_size)
280
0
        thumb_position = scroll_offset * (scrollport_size - thumb_size) / (scroll_overflow_size - scrollport_size);
281
282
0
    CSSPixelRect thumb_rect;
283
0
    if (direction == ScrollDirection::Horizontal) {
284
0
        thumb_rect = {
285
0
            padding_rect.left() + thumb_position,
286
0
            padding_rect.bottom() - scrollbar_thumb_thickness,
287
0
            thumb_size,
288
0
            scrollbar_thumb_thickness
289
0
        };
290
0
    } else {
291
0
        thumb_rect = {
292
0
            padding_rect.right() - scrollbar_thumb_thickness,
293
0
            padding_rect.top() + thumb_position,
294
0
            scrollbar_thumb_thickness,
295
0
            thumb_size
296
0
        };
297
0
    }
298
299
0
    if (is_viewport())
300
0
        thumb_rect.translate_by(this->scroll_offset());
301
302
0
    return thumb_rect;
303
0
}
304
305
void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
306
0
{
307
0
    if (!is_visible())
308
0
        return;
309
310
0
    if (phase == PaintPhase::Background) {
311
0
        paint_backdrop_filter(context);
312
0
        paint_background(context);
313
0
        paint_box_shadow(context);
314
0
    }
315
316
0
    auto const is_table_with_collapsed_borders = display().is_table_inside() && computed_values().border_collapse() == CSS::BorderCollapse::Collapse;
317
0
    if (!display().is_table_cell() && !is_table_with_collapsed_borders && phase == PaintPhase::Border) {
318
0
        paint_border(context);
319
0
    }
320
321
0
    if ((display().is_table_inside() || computed_values().border_collapse() == CSS::BorderCollapse::Collapse) && phase == PaintPhase::TableCollapsedBorder) {
322
0
        paint_table_borders(context, *this);
323
0
    }
324
325
0
    if (phase == PaintPhase::Outline) {
326
0
        auto const& outline_data = this->outline_data();
327
0
        if (outline_data.has_value()) {
328
0
            auto outline_offset = this->outline_offset();
329
0
            auto border_radius_data = normalized_border_radii_data(ShrinkRadiiForBorders::No);
330
0
            auto borders_rect = absolute_border_box_rect();
331
332
0
            auto outline_offset_x = outline_offset;
333
0
            auto outline_offset_y = outline_offset;
334
            // "Both the height and the width of the outside of the shape drawn by the outline should not
335
            // become smaller than twice the computed value of the outline-width property to make sure
336
            // that an outline can be rendered even with large negative values."
337
            // https://www.w3.org/TR/css-ui-4/#outline-offset
338
            // So, if the horizontal outline offset is > half the borders_rect's width then we set it to that.
339
            // (And the same for y)
340
0
            if ((borders_rect.width() / 2) + outline_offset_x < 0)
341
0
                outline_offset_x = -borders_rect.width() / 2;
342
0
            if ((borders_rect.height() / 2) + outline_offset_y < 0)
343
0
                outline_offset_y = -borders_rect.height() / 2;
344
345
0
            border_radius_data.inflate(outline_data->top.width + outline_offset_y, outline_data->right.width + outline_offset_x, outline_data->bottom.width + outline_offset_y, outline_data->left.width + outline_offset_x);
346
0
            borders_rect.inflate(outline_data->top.width + outline_offset_y, outline_data->right.width + outline_offset_x, outline_data->bottom.width + outline_offset_y, outline_data->left.width + outline_offset_x);
347
348
0
            paint_all_borders(context.display_list_recorder(), context.rounded_device_rect(borders_rect), border_radius_data.as_corners(context), outline_data->to_device_pixels(context));
349
0
        }
350
0
    }
351
352
0
    auto scrollbar_width = computed_values().scrollbar_width();
353
0
    if (phase == PaintPhase::Overlay && scrollbar_width != CSS::ScrollbarWidth::None) {
354
0
        auto color = Color(Color::NamedColor::DarkGray).with_alpha(128);
355
0
        auto border_color = Color(Color::NamedColor::LightGray).with_alpha(128);
356
0
        auto borders_data = BordersDataDevicePixels {
357
0
            .top = BorderDataDevicePixels { border_color, CSS::LineStyle::Solid, 1 },
358
0
            .right = BorderDataDevicePixels { border_color, CSS::LineStyle::Solid, 1 },
359
0
            .bottom = BorderDataDevicePixels { border_color, CSS::LineStyle::Solid, 1 },
360
0
            .left = BorderDataDevicePixels { border_color, CSS::LineStyle::Solid, 1 },
361
0
        };
362
0
        int thumb_corner_radius = static_cast<int>(context.rounded_device_pixels(scrollbar_thumb_thickness / 2));
363
0
        CornerRadii corner_radii = {
364
0
            .top_left = Gfx::CornerRadius { thumb_corner_radius, thumb_corner_radius },
365
0
            .top_right = Gfx::CornerRadius { thumb_corner_radius, thumb_corner_radius },
366
0
            .bottom_right = Gfx::CornerRadius { thumb_corner_radius, thumb_corner_radius },
367
0
            .bottom_left = Gfx::CornerRadius { thumb_corner_radius, thumb_corner_radius },
368
0
        };
369
0
        if (auto thumb_rect = scroll_thumb_rect(ScrollDirection::Horizontal); thumb_rect.has_value()) {
370
0
            auto thumb_device_rect = context.enclosing_device_rect(thumb_rect.value());
371
0
            paint_all_borders(context.display_list_recorder(), thumb_device_rect, corner_radii, borders_data);
372
0
            context.display_list_recorder().fill_rect_with_rounded_corners(thumb_device_rect.to_type<int>(), color, thumb_corner_radius);
373
0
        }
374
0
        if (auto thumb_rect = scroll_thumb_rect(ScrollDirection::Vertical); thumb_rect.has_value()) {
375
0
            auto thumb_device_rect = context.enclosing_device_rect(thumb_rect.value());
376
0
            paint_all_borders(context.display_list_recorder(), thumb_device_rect, corner_radii, borders_data);
377
0
            context.display_list_recorder().fill_rect_with_rounded_corners(thumb_device_rect.to_type<int>(), color, thumb_corner_radius);
378
0
        }
379
0
    }
380
381
0
    if (phase == PaintPhase::Overlay && layout_box().document().inspected_layout_node() == &layout_box()) {
382
0
        auto content_rect = absolute_rect();
383
384
0
        auto margin_box = box_model().margin_box();
385
0
        CSSPixelRect margin_rect;
386
0
        margin_rect.set_x(absolute_x() - margin_box.left);
387
0
        margin_rect.set_width(content_width() + margin_box.left + margin_box.right);
388
0
        margin_rect.set_y(absolute_y() - margin_box.top);
389
0
        margin_rect.set_height(content_height() + margin_box.top + margin_box.bottom);
390
391
0
        auto border_rect = absolute_border_box_rect();
392
0
        auto padding_rect = absolute_padding_box_rect();
393
394
0
        auto paint_inspector_rect = [&](CSSPixelRect const& rect, Color color) {
395
0
            auto device_rect = context.enclosing_device_rect(rect).to_type<int>();
396
0
            context.display_list_recorder().fill_rect(device_rect, Color(color).with_alpha(100));
397
0
            context.display_list_recorder().draw_rect(device_rect, Color(color));
398
0
        };
399
400
0
        paint_inspector_rect(margin_rect, Color::Yellow);
401
0
        paint_inspector_rect(padding_rect, Color::Cyan);
402
0
        paint_inspector_rect(border_rect, Color::Green);
403
0
        paint_inspector_rect(content_rect, Color::Magenta);
404
405
0
        auto& font = Platform::FontPlugin::the().default_font();
406
407
0
        StringBuilder builder;
408
0
        if (layout_box().dom_node())
409
0
            builder.append(layout_box().dom_node()->debug_description());
410
0
        else
411
0
            builder.append(layout_box().debug_description());
412
0
        builder.appendff(" {}x{} @ {},{}", border_rect.width(), border_rect.height(), border_rect.x(), border_rect.y());
413
0
        auto size_text = MUST(builder.to_string());
414
0
        auto size_text_rect = border_rect;
415
0
        size_text_rect.set_y(border_rect.y() + border_rect.height());
416
0
        size_text_rect.set_top(size_text_rect.top());
417
0
        size_text_rect.set_width(CSSPixels::nearest_value_for(font.width(size_text)) + 4);
418
0
        size_text_rect.set_height(CSSPixels::nearest_value_for(font.pixel_size()) + 4);
419
0
        auto size_text_device_rect = context.enclosing_device_rect(size_text_rect).to_type<int>();
420
0
        context.display_list_recorder().fill_rect(size_text_device_rect, context.palette().color(Gfx::ColorRole::Tooltip));
421
0
        context.display_list_recorder().draw_rect(size_text_device_rect, context.palette().threed_shadow1());
422
0
        context.display_list_recorder().draw_text(size_text_device_rect, size_text, font.with_size(font.point_size() * static_cast<float>(context.device_pixels_per_css_pixel())), Gfx::TextAlignment::Center, context.palette().color(Gfx::ColorRole::TooltipText));
423
0
    }
424
0
}
425
426
BordersData PaintableBox::remove_element_kind_from_borders_data(PaintableBox::BordersDataWithElementKind borders_data)
427
0
{
428
0
    return {
429
0
        .top = borders_data.top.border_data,
430
0
        .right = borders_data.right.border_data,
431
0
        .bottom = borders_data.bottom.border_data,
432
0
        .left = borders_data.left.border_data,
433
0
    };
434
0
}
435
436
void PaintableBox::paint_border(PaintContext& context) const
437
0
{
438
0
    auto borders_data = m_override_borders_data.has_value() ? remove_element_kind_from_borders_data(m_override_borders_data.value()) : BordersData {
439
0
        .top = box_model().border.top == 0 ? CSS::BorderData() : computed_values().border_top(),
440
0
        .right = box_model().border.right == 0 ? CSS::BorderData() : computed_values().border_right(),
441
0
        .bottom = box_model().border.bottom == 0 ? CSS::BorderData() : computed_values().border_bottom(),
442
0
        .left = box_model().border.left == 0 ? CSS::BorderData() : computed_values().border_left(),
443
0
    };
444
0
    paint_all_borders(context.display_list_recorder(), context.rounded_device_rect(absolute_border_box_rect()), normalized_border_radii_data().as_corners(context), borders_data.to_device_pixels(context));
445
0
}
446
447
void PaintableBox::paint_backdrop_filter(PaintContext& context) const
448
0
{
449
0
    auto& backdrop_filter = computed_values().backdrop_filter();
450
0
    if (!backdrop_filter.is_none())
451
0
        apply_backdrop_filter(context, absolute_border_box_rect(), normalized_border_radii_data(), backdrop_filter);
452
0
}
453
454
void PaintableBox::paint_background(PaintContext& context) const
455
0
{
456
    // If the body's background properties were propagated to the root element, do no re-paint the body's background.
457
0
    if (layout_box().is_body() && document().html_element()->should_use_body_background_properties())
458
0
        return;
459
460
0
    CSSPixelRect background_rect;
461
0
    Color background_color = computed_values().background_color();
462
0
    auto* background_layers = &computed_values().background_layers();
463
464
0
    if (layout_box().is_root_element()) {
465
        // CSS 2.1 Appendix E.2: If the element is a root element, paint the background over the entire canvas.
466
0
        background_rect = context.css_viewport_rect();
467
468
        // Section 2.11.2: If the computed value of background-image on the root element is none and its background-color is transparent,
469
        // user agents must instead propagate the computed values of the background properties from that element’s first HTML BODY child element.
470
0
        if (document().html_element()->should_use_body_background_properties()) {
471
0
            background_layers = document().background_layers();
472
0
            background_color = document().background_color();
473
0
        }
474
0
    } else {
475
0
        background_rect = absolute_padding_box_rect();
476
0
    }
477
478
    // HACK: If the Box has a border, use the bordered_rect to paint the background.
479
    //       This way if we have a border-radius there will be no gap between the filling and actual border.
480
0
    if (computed_values().border_top().width != 0 || computed_values().border_right().width != 0 || computed_values().border_bottom().width != 0 || computed_values().border_left().width != 0)
481
0
        background_rect = absolute_border_box_rect();
482
483
0
    Painting::paint_background(context, layout_box(), background_rect, background_color, computed_values().image_rendering(), background_layers, normalized_border_radii_data());
484
0
}
485
486
void PaintableBox::paint_box_shadow(PaintContext& context) const
487
0
{
488
0
    auto const& resolved_box_shadow_data = box_shadow_data();
489
0
    if (resolved_box_shadow_data.is_empty())
490
0
        return;
491
0
    auto borders_data = BordersData {
492
0
        .top = computed_values().border_top(),
493
0
        .right = computed_values().border_right(),
494
0
        .bottom = computed_values().border_bottom(),
495
0
        .left = computed_values().border_left(),
496
0
    };
497
0
    Painting::paint_box_shadow(context, absolute_border_box_rect(), absolute_padding_box_rect(),
498
0
        borders_data, normalized_border_radii_data(), resolved_box_shadow_data);
499
0
}
500
501
BorderRadiiData PaintableBox::normalized_border_radii_data(ShrinkRadiiForBorders shrink) const
502
0
{
503
0
    auto border_radii_data = this->border_radii_data();
504
0
    if (shrink == ShrinkRadiiForBorders::Yes)
505
0
        border_radii_data.shrink(computed_values().border_top().width, computed_values().border_right().width, computed_values().border_bottom().width, computed_values().border_left().width);
506
0
    return border_radii_data;
507
0
}
508
509
void PaintableBox::apply_scroll_offset(PaintContext& context, PaintPhase) const
510
0
{
511
0
    if (scroll_frame_id().has_value()) {
512
0
        context.display_list_recorder().save();
513
0
        context.display_list_recorder().set_scroll_frame_id(scroll_frame_id().value());
514
0
    }
515
0
}
516
517
void PaintableBox::reset_scroll_offset(PaintContext& context, PaintPhase) const
518
0
{
519
0
    if (scroll_frame_id().has_value())
520
0
        context.display_list_recorder().restore();
521
0
}
522
523
void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase phase) const
524
0
{
525
0
    if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::TableCollapsedBorder, PaintPhase::Foreground, PaintPhase::Outline))
526
0
        return;
527
528
0
    if (clip_rect().has_value()) {
529
0
        auto overflow_clip_rect = clip_rect().value();
530
0
        m_clipping_overflow = true;
531
0
        context.display_list_recorder().save();
532
0
        context.display_list_recorder().add_clip_rect(context.enclosing_device_rect(overflow_clip_rect).to_type<int>());
533
0
        auto const& border_radii_clips = this->border_radii_clips();
534
0
        m_corner_clipper_ids.resize(border_radii_clips.size());
535
0
        auto const& combined_transform = combined_css_transform();
536
0
        for (size_t corner_clip_index = 0; corner_clip_index < border_radii_clips.size(); ++corner_clip_index) {
537
0
            auto const& corner_clip = border_radii_clips[corner_clip_index];
538
0
            auto corners = corner_clip.radii.as_corners(context);
539
0
            if (!corners.has_any_radius())
540
0
                continue;
541
0
            auto corner_clipper_id = context.allocate_corner_clipper_id();
542
0
            m_corner_clipper_ids[corner_clip_index] = corner_clipper_id;
543
0
            auto rect = corner_clip.rect.translated(-combined_transform.translation().to_type<CSSPixels>());
544
0
            context.display_list_recorder().sample_under_corners(corner_clipper_id, corner_clip.radii.as_corners(context), context.rounded_device_rect(rect).to_type<int>(), CornerClip::Outside);
545
0
        }
546
0
    }
547
0
}
548
549
void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase phase) const
550
0
{
551
0
    if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::TableCollapsedBorder, PaintPhase::Foreground, PaintPhase::Outline))
552
0
        return;
553
554
0
    if (m_clipping_overflow) {
555
0
        m_clipping_overflow = false;
556
0
        auto const& border_radii_clips = this->border_radii_clips();
557
0
        for (int corner_clip_index = border_radii_clips.size() - 1; corner_clip_index >= 0; --corner_clip_index) {
558
0
            auto const& corner_clip = border_radii_clips[corner_clip_index];
559
0
            auto corners = corner_clip.radii.as_corners(context);
560
0
            if (!corners.has_any_radius())
561
0
                continue;
562
0
            auto corner_clipper_id = m_corner_clipper_ids[corner_clip_index];
563
0
            m_corner_clipper_ids[corner_clip_index] = corner_clipper_id;
564
0
            context.display_list_recorder().blit_corner_clipping(corner_clipper_id);
565
0
        }
566
0
        context.display_list_recorder().restore();
567
0
    }
568
0
}
569
570
void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintable, PaintableFragment const& fragment)
571
0
{
572
0
    auto const& navigable = *paintable.navigable();
573
0
    auto const& document = paintable.document();
574
575
0
    if (!navigable.is_focused())
576
0
        return;
577
578
0
    if (!document.cursor_blink_state())
579
0
        return;
580
581
0
    if (document.cursor_position()->node() != paintable.dom_node())
582
0
        return;
583
584
    // NOTE: This checks if the cursor is before the start or after the end of the fragment. If it is at the end, after all text, it should still be painted.
585
0
    if (document.cursor_position()->offset() < (unsigned)fragment.start() || document.cursor_position()->offset() > (unsigned)(fragment.start() + fragment.length()))
586
0
        return;
587
588
0
    if (!fragment.layout_node().dom_node() || !fragment.layout_node().dom_node()->is_editable())
589
0
        return;
590
591
0
    auto fragment_rect = fragment.absolute_rect();
592
593
0
    auto text = fragment.string_view();
594
0
    CSSPixelRect cursor_rect {
595
0
        fragment_rect.x() + CSSPixels::nearest_value_for(paintable.layout_node().first_available_font().width(text.substring_view(0, document.cursor_position()->offset() - fragment.start()))),
596
0
        fragment_rect.top(),
597
0
        1,
598
0
        fragment_rect.height()
599
0
    };
600
601
0
    auto cursor_device_rect = context.rounded_device_rect(cursor_rect).to_type<int>();
602
603
0
    context.display_list_recorder().draw_rect(cursor_device_rect, paintable.computed_values().color());
604
0
}
605
606
void paint_text_decoration(PaintContext& context, TextPaintable const& paintable, PaintableFragment const& fragment)
607
0
{
608
0
    auto& painter = context.display_list_recorder();
609
0
    auto& font = fragment.layout_node().first_available_font();
610
0
    auto fragment_box = fragment.absolute_rect();
611
0
    CSSPixels glyph_height = CSSPixels::nearest_value_for(font.pixel_size());
612
0
    auto baseline = fragment.baseline();
613
614
0
    auto line_color = paintable.computed_values().text_decoration_color();
615
0
    auto const& text_paintable = static_cast<TextPaintable const&>(fragment.paintable());
616
0
    auto device_line_thickness = context.rounded_device_pixels(text_paintable.text_decoration_thickness());
617
618
0
    auto text_decoration_lines = paintable.computed_values().text_decoration_line();
619
0
    for (auto line : text_decoration_lines) {
620
0
        DevicePixelPoint line_start_point {};
621
0
        DevicePixelPoint line_end_point {};
622
623
0
        switch (line) {
624
0
        case CSS::TextDecorationLine::None:
625
0
            return;
626
0
        case CSS::TextDecorationLine::Underline:
627
0
            line_start_point = context.rounded_device_point(fragment_box.top_left().translated(0, baseline + 2));
628
0
            line_end_point = context.rounded_device_point(fragment_box.top_right().translated(-1, baseline + 2));
629
0
            break;
630
0
        case CSS::TextDecorationLine::Overline:
631
0
            line_start_point = context.rounded_device_point(fragment_box.top_left().translated(0, baseline - glyph_height));
632
0
            line_end_point = context.rounded_device_point(fragment_box.top_right().translated(-1, baseline - glyph_height));
633
0
            break;
634
0
        case CSS::TextDecorationLine::LineThrough: {
635
0
            auto x_height = font.x_height();
636
0
            line_start_point = context.rounded_device_point(fragment_box.top_left().translated(0, baseline - x_height * CSSPixels(0.5f)));
637
0
            line_end_point = context.rounded_device_point(fragment_box.top_right().translated(-1, baseline - x_height * CSSPixels(0.5f)));
638
0
            break;
639
0
        }
640
0
        case CSS::TextDecorationLine::Blink:
641
            // Conforming user agents may simply not blink the text
642
0
            return;
643
0
        }
644
645
0
        switch (paintable.computed_values().text_decoration_style()) {
646
0
        case CSS::TextDecorationStyle::Solid:
647
0
            painter.draw_line(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value(), Gfx::LineStyle::Solid);
648
0
            break;
649
0
        case CSS::TextDecorationStyle::Double:
650
0
            switch (line) {
651
0
            case CSS::TextDecorationLine::Underline:
652
0
                break;
653
0
            case CSS::TextDecorationLine::Overline:
654
0
                line_start_point.translate_by(0, -device_line_thickness - context.rounded_device_pixels(1));
655
0
                line_end_point.translate_by(0, -device_line_thickness - context.rounded_device_pixels(1));
656
0
                break;
657
0
            case CSS::TextDecorationLine::LineThrough:
658
0
                line_start_point.translate_by(0, -device_line_thickness / 2);
659
0
                line_end_point.translate_by(0, -device_line_thickness / 2);
660
0
                break;
661
0
            default:
662
0
                VERIFY_NOT_REACHED();
663
0
            }
664
665
0
            painter.draw_line(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value());
666
0
            painter.draw_line(line_start_point.translated(0, device_line_thickness + 1).to_type<int>(), line_end_point.translated(0, device_line_thickness + 1).to_type<int>(), line_color, device_line_thickness.value());
667
0
            break;
668
0
        case CSS::TextDecorationStyle::Dashed:
669
0
            painter.draw_line(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value(), Gfx::LineStyle::Dashed);
670
0
            break;
671
0
        case CSS::TextDecorationStyle::Dotted:
672
0
            painter.draw_line(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value(), Gfx::LineStyle::Dotted);
673
0
            break;
674
0
        case CSS::TextDecorationStyle::Wavy:
675
0
            painter.draw_triangle_wave(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value() + 1, device_line_thickness.value());
676
0
            break;
677
0
        }
678
0
    }
679
0
}
680
681
void paint_text_fragment(PaintContext& context, TextPaintable const& paintable, PaintableFragment const& fragment, PaintPhase phase)
682
0
{
683
0
    if (!paintable.is_visible())
684
0
        return;
685
686
0
    auto& painter = context.display_list_recorder();
687
688
0
    if (phase == PaintPhase::Foreground) {
689
0
        auto fragment_absolute_rect = fragment.absolute_rect();
690
0
        auto fragment_absolute_device_rect = context.enclosing_device_rect(fragment_absolute_rect);
691
692
0
        if (paintable.document().inspected_layout_node() == &paintable.layout_node())
693
0
            context.display_list_recorder().draw_rect(fragment_absolute_device_rect.to_type<int>(), Color::Magenta);
694
695
0
        auto text = paintable.text_for_rendering();
696
697
0
        auto glyph_run = fragment.glyph_run();
698
0
        if (!glyph_run)
699
0
            return;
700
701
0
        DevicePixelPoint baseline_start { fragment_absolute_device_rect.x(), fragment_absolute_device_rect.y() + context.rounded_device_pixels(fragment.baseline()) };
702
0
        auto scale = context.device_pixels_per_css_pixel();
703
0
        painter.draw_text_run(baseline_start.to_type<int>(), *glyph_run, paintable.computed_values().webkit_text_fill_color(), fragment_absolute_device_rect.to_type<int>(), scale);
704
705
0
        auto selection_rect = context.enclosing_device_rect(fragment.selection_rect(paintable.layout_node().first_available_font())).to_type<int>();
706
0
        if (!selection_rect.is_empty()) {
707
0
            painter.fill_rect(selection_rect, CSS::SystemColor::highlight());
708
0
            DisplayListRecorderStateSaver saver(painter);
709
0
            painter.add_clip_rect(selection_rect);
710
0
            painter.draw_text_run(baseline_start.to_type<int>(), *glyph_run, CSS::SystemColor::highlight_text(), fragment_absolute_device_rect.to_type<int>(), scale);
711
0
        }
712
713
0
        paint_text_decoration(context, paintable, fragment);
714
0
        paint_cursor_if_needed(context, paintable, fragment);
715
0
    }
716
0
}
717
718
void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const
719
0
{
720
0
    if (!is_visible())
721
0
        return;
722
723
0
    PaintableBox::paint(context, phase);
724
725
0
    if (fragments().is_empty())
726
0
        return;
727
728
0
    bool should_clip_overflow = computed_values().overflow_x() != CSS::Overflow::Visible && computed_values().overflow_y() != CSS::Overflow::Visible;
729
0
    Optional<u32> corner_clip_id;
730
731
0
    auto clip_box = absolute_padding_box_rect();
732
0
    if (get_clip_rect().has_value()) {
733
0
        clip_box.intersect(get_clip_rect().value());
734
0
        should_clip_overflow = true;
735
0
    }
736
0
    if (should_clip_overflow) {
737
0
        context.display_list_recorder().save();
738
        // FIXME: Handle overflow-x and overflow-y being different values.
739
0
        auto clip_box_with_enclosing_scroll_frame_offset = clip_box;
740
0
        if (enclosing_scroll_frame_offset().has_value())
741
0
            clip_box_with_enclosing_scroll_frame_offset.translate_by(enclosing_scroll_frame_offset().value());
742
0
        context.display_list_recorder().add_clip_rect(context.rounded_device_rect(clip_box_with_enclosing_scroll_frame_offset).to_type<int>());
743
744
0
        auto border_radii = normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
745
0
        CornerRadii corner_radii {
746
0
            .top_left = border_radii.top_left.as_corner(context),
747
0
            .top_right = border_radii.top_right.as_corner(context),
748
0
            .bottom_right = border_radii.bottom_right.as_corner(context),
749
0
            .bottom_left = border_radii.bottom_left.as_corner(context)
750
0
        };
751
0
        if (corner_radii.has_any_radius()) {
752
0
            corner_clip_id = context.allocate_corner_clipper_id();
753
0
            context.display_list_recorder().sample_under_corners(*corner_clip_id, corner_radii, context.rounded_device_rect(clip_box).to_type<int>(), CornerClip::Outside);
754
0
        }
755
756
0
        context.display_list_recorder().save();
757
0
        auto scroll_offset = context.rounded_device_point(this->scroll_offset());
758
0
        context.display_list_recorder().translate(-scroll_offset.to_type<int>());
759
0
    }
760
761
    // Text shadows
762
    // This is yet another loop, but done here because all shadows should appear under all text.
763
    // So, we paint the shadows before painting any text.
764
    // FIXME: Find a smarter way to do this?
765
0
    if (phase == PaintPhase::Foreground) {
766
0
        for (auto& fragment : fragments()) {
767
0
            paint_text_shadow(context, fragment, fragment.shadows());
768
0
        }
769
0
    }
770
771
0
    for (auto const& fragment : m_fragments) {
772
0
        auto fragment_absolute_rect = fragment.absolute_rect();
773
0
        auto fragment_absolute_device_rect = context.enclosing_device_rect(fragment_absolute_rect);
774
0
        if (context.should_show_line_box_borders()) {
775
0
            context.display_list_recorder().draw_rect(fragment_absolute_device_rect.to_type<int>(), Color::Green);
776
0
            context.display_list_recorder().draw_line(
777
0
                context.rounded_device_point(fragment_absolute_rect.top_left().translated(0, fragment.baseline())).to_type<int>(),
778
0
                context.rounded_device_point(fragment_absolute_rect.top_right().translated(-1, fragment.baseline())).to_type<int>(), Color::Red);
779
0
        }
780
0
        if (is<TextPaintable>(fragment.paintable()))
781
0
            paint_text_fragment(context, static_cast<TextPaintable const&>(fragment.paintable()), fragment, phase);
782
0
    }
783
784
0
    if (should_clip_overflow) {
785
0
        context.display_list_recorder().restore();
786
0
        if (corner_clip_id.has_value()) {
787
0
            context.display_list_recorder().blit_corner_clipping(*corner_clip_id);
788
0
            corner_clip_id = {};
789
0
        }
790
0
        context.display_list_recorder().restore();
791
0
    }
792
0
}
793
794
Paintable::DispatchEventOfSameName PaintableBox::handle_mousedown(Badge<EventHandler>, CSSPixelPoint position, unsigned, unsigned)
795
0
{
796
0
    auto vertical_scroll_thumb_rect = scroll_thumb_rect(ScrollDirection::Vertical);
797
0
    auto horizontal_scroll_thumb_rect = scroll_thumb_rect(ScrollDirection::Horizontal);
798
0
    if (vertical_scroll_thumb_rect.has_value() && vertical_scroll_thumb_rect.value().contains(position)) {
799
0
        if (is_viewport())
800
0
            position.translate_by(-scroll_offset());
801
0
        m_last_mouse_tracking_position = position;
802
0
        m_scroll_thumb_dragging_direction = ScrollDirection::Vertical;
803
0
        const_cast<HTML::Navigable&>(*navigable()).event_handler().set_mouse_event_tracking_paintable(this);
804
0
    } else if (horizontal_scroll_thumb_rect.has_value() && horizontal_scroll_thumb_rect.value().contains(position)) {
805
0
        if (is_viewport())
806
0
            position.translate_by(-scroll_offset());
807
0
        m_last_mouse_tracking_position = position;
808
0
        m_scroll_thumb_dragging_direction = ScrollDirection::Horizontal;
809
0
        const_cast<HTML::Navigable&>(*navigable()).event_handler().set_mouse_event_tracking_paintable(this);
810
0
    }
811
0
    return Paintable::DispatchEventOfSameName::Yes;
812
0
}
813
814
Paintable::DispatchEventOfSameName PaintableBox::handle_mouseup(Badge<EventHandler>, CSSPixelPoint, unsigned, unsigned)
815
0
{
816
0
    if (m_last_mouse_tracking_position.has_value()) {
817
0
        m_last_mouse_tracking_position.clear();
818
0
        m_scroll_thumb_dragging_direction.clear();
819
0
        const_cast<HTML::Navigable&>(*navigable()).event_handler().set_mouse_event_tracking_paintable(nullptr);
820
0
    }
821
0
    return Paintable::DispatchEventOfSameName::Yes;
822
0
}
823
824
Paintable::DispatchEventOfSameName PaintableBox::handle_mousemove(Badge<EventHandler>, CSSPixelPoint position, unsigned, unsigned)
825
0
{
826
0
    if (m_last_mouse_tracking_position.has_value()) {
827
0
        if (is_viewport())
828
0
            position.translate_by(-scroll_offset());
829
830
0
        Gfx::Point<double> scroll_delta;
831
0
        if (m_scroll_thumb_dragging_direction == ScrollDirection::Horizontal)
832
0
            scroll_delta.set_x((position.x() - m_last_mouse_tracking_position->x()).to_double());
833
0
        else
834
0
            scroll_delta.set_y((position.y() - m_last_mouse_tracking_position->y()).to_double());
835
836
0
        auto padding_rect = absolute_padding_box_rect();
837
0
        auto scrollable_overflow_rect = this->scrollable_overflow_rect().value();
838
0
        auto scroll_overflow_size = m_scroll_thumb_dragging_direction == ScrollDirection::Horizontal ? scrollable_overflow_rect.width() : scrollable_overflow_rect.height();
839
0
        auto scrollport_size = m_scroll_thumb_dragging_direction == ScrollDirection::Horizontal ? padding_rect.width() : padding_rect.height();
840
0
        auto scroll_px_per_mouse_position_delta_px = scroll_overflow_size.to_double() / scrollport_size.to_double();
841
0
        scroll_delta *= scroll_px_per_mouse_position_delta_px;
842
843
0
        if (is_viewport()) {
844
0
            document().window()->scroll_by(scroll_delta.x(), scroll_delta.y());
845
0
        } else {
846
0
            scroll_by(scroll_delta.x(), scroll_delta.y());
847
0
        }
848
849
0
        m_last_mouse_tracking_position = position;
850
0
        return Paintable::DispatchEventOfSameName::No;
851
0
    }
852
0
    return Paintable::DispatchEventOfSameName::Yes;
853
0
}
854
855
bool PaintableBox::handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned, unsigned, int wheel_delta_x, int wheel_delta_y)
856
0
{
857
0
    if (!layout_box().is_user_scrollable())
858
0
        return false;
859
860
    // TODO: Vertical and horizontal scroll overflow should be handled separately.
861
0
    if (!has_scrollable_overflow())
862
0
        return false;
863
864
0
    scroll_by(wheel_delta_x, wheel_delta_y);
865
0
    return true;
866
0
}
867
868
Layout::BlockContainer const& PaintableWithLines::layout_box() const
869
0
{
870
0
    return static_cast<Layout::BlockContainer const&>(PaintableBox::layout_box());
871
0
}
872
873
Layout::BlockContainer& PaintableWithLines::layout_box()
874
0
{
875
0
    return static_cast<Layout::BlockContainer&>(PaintableBox::layout_box());
876
0
}
877
878
TraversalDecision PaintableBox::hit_test_scrollbars(CSSPixelPoint position, Function<TraversalDecision(HitTestResult)> const& callback) const
879
0
{
880
0
    auto vertical_scroll_thumb_rect = scroll_thumb_rect(ScrollDirection::Vertical);
881
0
    if (vertical_scroll_thumb_rect.has_value() && vertical_scroll_thumb_rect.value().contains(position))
882
0
        return callback(HitTestResult { const_cast<PaintableBox&>(*this) });
883
0
    auto horizontal_scroll_thumb_rect = scroll_thumb_rect(ScrollDirection::Horizontal);
884
0
    if (horizontal_scroll_thumb_rect.has_value() && horizontal_scroll_thumb_rect.value().contains(position))
885
0
        return callback(HitTestResult { const_cast<PaintableBox&>(*this) });
886
0
    return TraversalDecision::Continue;
887
0
}
888
889
TraversalDecision PaintableBox::hit_test(CSSPixelPoint position, HitTestType type, Function<TraversalDecision(HitTestResult)> const& callback) const
890
0
{
891
0
    if (clip_rect().has_value() && !clip_rect()->contains(position))
892
0
        return TraversalDecision::Continue;
893
894
0
    auto position_adjusted_by_scroll_offset = position;
895
0
    if (enclosing_scroll_frame_offset().has_value())
896
0
        position_adjusted_by_scroll_offset.translate_by(-enclosing_scroll_frame_offset().value());
897
898
0
    if (!is_visible())
899
0
        return TraversalDecision::Continue;
900
901
0
    if (hit_test_scrollbars(position_adjusted_by_scroll_offset, callback) == TraversalDecision::Break)
902
0
        return TraversalDecision::Break;
903
904
0
    if (layout_box().is_viewport()) {
905
0
        auto& viewport_paintable = const_cast<ViewportPaintable&>(static_cast<ViewportPaintable const&>(*this));
906
0
        viewport_paintable.build_stacking_context_tree_if_needed();
907
0
        viewport_paintable.document().update_paint_and_hit_testing_properties_if_needed();
908
0
        viewport_paintable.refresh_scroll_state();
909
0
        viewport_paintable.refresh_clip_state();
910
0
        return stacking_context()->hit_test(position, type, callback);
911
0
    }
912
913
0
    for (auto const* child = last_child(); child; child = child->previous_sibling()) {
914
0
        auto z_index = child->computed_values().z_index();
915
0
        if (child->layout_node().is_positioned() && z_index.value_or(0) == 0)
916
0
            continue;
917
0
        if (child->hit_test(position, type, callback) == TraversalDecision::Break)
918
0
            return TraversalDecision::Break;
919
0
    }
920
921
0
    if (!absolute_border_box_rect().contains(position_adjusted_by_scroll_offset.x(), position_adjusted_by_scroll_offset.y()))
922
0
        return TraversalDecision::Continue;
923
924
0
    if (!visible_for_hit_testing())
925
0
        return TraversalDecision::Continue;
926
927
0
    return callback(HitTestResult { const_cast<PaintableBox&>(*this) });
928
0
}
929
930
Optional<HitTestResult> PaintableBox::hit_test(CSSPixelPoint position, HitTestType type) const
931
0
{
932
0
    Optional<HitTestResult> result;
933
0
    (void)PaintableBox::hit_test(position, type, [&](HitTestResult candidate) {
934
0
        if (candidate.paintable->visible_for_hit_testing()) {
935
0
            if (!result.has_value()
936
0
                || candidate.vertical_distance.value_or(CSSPixels::max_integer_value) < result->vertical_distance.value_or(CSSPixels::max_integer_value)
937
0
                || candidate.horizontal_distance.value_or(CSSPixels::max_integer_value) < result->horizontal_distance.value_or(CSSPixels::max_integer_value)) {
938
0
                result = move(candidate);
939
0
            }
940
0
        }
941
942
0
        if (result.has_value() && (type == HitTestType::Exact || (result->vertical_distance == 0 && result->horizontal_distance == 0)))
943
0
            return TraversalDecision::Break;
944
0
        return TraversalDecision::Continue;
945
0
    });
946
0
    return result;
947
0
}
948
949
TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestType type, Function<TraversalDecision(HitTestResult)> const& callback) const
950
0
{
951
0
    if (clip_rect().has_value() && !clip_rect()->contains(position))
952
0
        return TraversalDecision::Continue;
953
954
0
    auto position_adjusted_by_scroll_offset = position;
955
0
    if (enclosing_scroll_frame_offset().has_value())
956
0
        position_adjusted_by_scroll_offset.translate_by(-enclosing_scroll_frame_offset().value());
957
958
0
    if (!layout_box().children_are_inline() || m_fragments.is_empty()) {
959
0
        return PaintableBox::hit_test(position, type, callback);
960
0
    }
961
962
0
    if (hit_test_scrollbars(position_adjusted_by_scroll_offset, callback) == TraversalDecision::Break)
963
0
        return TraversalDecision::Break;
964
965
0
    for (auto const* child = last_child(); child; child = child->previous_sibling()) {
966
0
        if (child->hit_test(position, type, callback) == TraversalDecision::Break)
967
0
            return TraversalDecision::Break;
968
0
    }
969
970
0
    for (auto const& fragment : fragments()) {
971
0
        if (fragment.paintable().stacking_context())
972
0
            continue;
973
0
        auto fragment_absolute_rect = fragment.absolute_rect();
974
0
        if (fragment_absolute_rect.contains(position_adjusted_by_scroll_offset)) {
975
0
            if (fragment.paintable().hit_test(position, type, callback) == TraversalDecision::Break)
976
0
                return TraversalDecision::Break;
977
0
            HitTestResult hit_test_result { const_cast<Paintable&>(fragment.paintable()), fragment.text_index_at(position_adjusted_by_scroll_offset.x()), 0, 0 };
978
0
            if (callback(hit_test_result) == TraversalDecision::Break)
979
0
                return TraversalDecision::Break;
980
0
        } else if (type == HitTestType::TextCursor) {
981
0
            auto const* common_ancestor_parent = [&]() -> DOM::Node const* {
982
0
                auto selection = document().get_selection();
983
0
                if (!selection)
984
0
                    return nullptr;
985
0
                auto range = selection->range();
986
0
                if (!range)
987
0
                    return nullptr;
988
0
                auto common_ancestor = range->common_ancestor_container();
989
0
                if (common_ancestor->parent())
990
0
                    return common_ancestor->parent();
991
0
                return common_ancestor;
992
0
            }();
993
994
0
            auto const* fragment_dom_node = fragment.layout_node().dom_node();
995
0
            if (common_ancestor_parent && fragment_dom_node && common_ancestor_parent->is_ancestor_of(*fragment_dom_node)) {
996
                // If we reached this point, the position is not within the fragment. However, the fragment start or end might be
997
                // the place to place the cursor. To determine the best place, we first find the closest fragment horizontally to
998
                // the cursor. If we could not find one, then find for the closest vertically above the cursor.
999
                // If we knew the direction of selection, we would look above if selecting upward.
1000
0
                if (fragment_absolute_rect.bottom() - 1 <= position_adjusted_by_scroll_offset.y()) { // fully below the fragment
1001
0
                    HitTestResult hit_test_result {
1002
0
                        .paintable = const_cast<Paintable&>(fragment.paintable()),
1003
0
                        .index_in_node = fragment.start() + fragment.length(),
1004
0
                        .vertical_distance = position_adjusted_by_scroll_offset.y() - fragment_absolute_rect.bottom(),
1005
0
                    };
1006
0
                    if (callback(hit_test_result) == TraversalDecision::Break)
1007
0
                        return TraversalDecision::Break;
1008
0
                } else if (fragment_absolute_rect.top() <= position_adjusted_by_scroll_offset.y()) { // vertically within the fragment
1009
0
                    if (position_adjusted_by_scroll_offset.x() < fragment_absolute_rect.left()) {
1010
0
                        HitTestResult hit_test_result {
1011
0
                            .paintable = const_cast<Paintable&>(fragment.paintable()),
1012
0
                            .index_in_node = fragment.start(),
1013
0
                            .vertical_distance = 0,
1014
0
                            .horizontal_distance = fragment_absolute_rect.left() - position_adjusted_by_scroll_offset.x(),
1015
0
                        };
1016
0
                        if (callback(hit_test_result) == TraversalDecision::Break)
1017
0
                            return TraversalDecision::Break;
1018
0
                    } else if (position_adjusted_by_scroll_offset.x() > fragment_absolute_rect.right()) {
1019
0
                        HitTestResult hit_test_result {
1020
0
                            .paintable = const_cast<Paintable&>(fragment.paintable()),
1021
0
                            .index_in_node = fragment.start() + fragment.length(),
1022
0
                            .vertical_distance = 0,
1023
0
                            .horizontal_distance = position_adjusted_by_scroll_offset.x() - fragment_absolute_rect.right(),
1024
0
                        };
1025
0
                        if (callback(hit_test_result) == TraversalDecision::Break)
1026
0
                            return TraversalDecision::Break;
1027
0
                    }
1028
0
                }
1029
0
            }
1030
0
        }
1031
0
    }
1032
1033
0
    if (!stacking_context() && is_visible() && absolute_border_box_rect().contains(position_adjusted_by_scroll_offset.x(), position_adjusted_by_scroll_offset.y())) {
1034
0
        if (callback(HitTestResult { const_cast<PaintableWithLines&>(*this) }) == TraversalDecision::Break)
1035
0
            return TraversalDecision::Break;
1036
0
    }
1037
1038
0
    return TraversalDecision::Continue;
1039
0
}
1040
1041
void PaintableBox::set_needs_display() const
1042
0
{
1043
0
    if (auto navigable = this->navigable())
1044
0
        navigable->set_needs_display(absolute_rect());
1045
0
}
1046
1047
Optional<CSSPixelRect> PaintableBox::get_masking_area() const
1048
0
{
1049
    // FIXME: Support clip-paths with transforms.
1050
0
    if (!combined_css_transform().is_identity_or_translation())
1051
0
        return {};
1052
0
    auto clip_path = computed_values().clip_path();
1053
    // FIXME: Support other clip sources.
1054
0
    if (!clip_path.has_value() || !clip_path->is_basic_shape())
1055
0
        return {};
1056
    // FIXME: Support other geometry boxes. See: https://drafts.fxtf.org/css-masking/#typedef-geometry-box
1057
0
    return absolute_border_box_rect();
1058
0
}
1059
1060
Optional<Gfx::Bitmap::MaskKind> PaintableBox::get_mask_type() const
1061
0
{
1062
    // Always an alpha mask as only basic shapes are supported right now.
1063
0
    return Gfx::Bitmap::MaskKind::Alpha;
1064
0
}
1065
1066
RefPtr<Gfx::Bitmap> PaintableBox::calculate_mask(PaintContext& context, CSSPixelRect const& masking_area) const
1067
0
{
1068
0
    VERIFY(computed_values().clip_path()->is_basic_shape());
1069
0
    auto const& basic_shape = computed_values().clip_path()->basic_shape();
1070
0
    auto path = basic_shape.to_path(masking_area, layout_node());
1071
0
    auto device_pixel_scale = context.device_pixels_per_css_pixel();
1072
0
    path = path.copy_transformed(Gfx::AffineTransform {}.set_scale(device_pixel_scale, device_pixel_scale));
1073
0
    auto mask_rect = context.enclosing_device_rect(masking_area);
1074
0
    auto maybe_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask_rect.size().to_type<int>());
1075
0
    if (maybe_bitmap.is_error())
1076
0
        return {};
1077
0
    auto bitmap = maybe_bitmap.release_value();
1078
0
    Gfx::Painter painter(*bitmap);
1079
0
    Gfx::AntiAliasingPainter aa_painter(painter);
1080
0
    aa_painter.fill_path(path, Color::Black);
1081
0
    return bitmap;
1082
0
}
1083
1084
void PaintableBox::resolve_paint_properties()
1085
0
{
1086
0
    auto const& computed_values = this->computed_values();
1087
0
    auto const& layout_node = this->layout_node();
1088
1089
    // Border radii
1090
0
    BorderRadiiData radii_data {};
1091
0
    if (computed_values.has_noninitial_border_radii()) {
1092
0
        CSSPixelRect const border_rect { 0, 0, border_box_width(), border_box_height() };
1093
1094
0
        auto const& border_top_left_radius = computed_values.border_top_left_radius();
1095
0
        auto const& border_top_right_radius = computed_values.border_top_right_radius();
1096
0
        auto const& border_bottom_right_radius = computed_values.border_bottom_right_radius();
1097
0
        auto const& border_bottom_left_radius = computed_values.border_bottom_left_radius();
1098
1099
0
        radii_data = normalize_border_radii_data(layout_node, border_rect, border_top_left_radius,
1100
0
            border_top_right_radius, border_bottom_right_radius,
1101
0
            border_bottom_left_radius);
1102
0
    }
1103
0
    set_border_radii_data(radii_data);
1104
1105
    // Box shadows
1106
0
    auto const& box_shadow_data = computed_values.box_shadow();
1107
0
    Vector<Painting::ShadowData> resolved_box_shadow_data;
1108
0
    resolved_box_shadow_data.ensure_capacity(box_shadow_data.size());
1109
0
    for (auto const& layer : box_shadow_data) {
1110
0
        resolved_box_shadow_data.empend(
1111
0
            layer.color,
1112
0
            layer.offset_x.to_px(layout_node),
1113
0
            layer.offset_y.to_px(layout_node),
1114
0
            layer.blur_radius.to_px(layout_node),
1115
0
            layer.spread_distance.to_px(layout_node),
1116
0
            layer.placement == CSS::ShadowPlacement::Outer ? Painting::ShadowPlacement::Outer
1117
0
                                                           : Painting::ShadowPlacement::Inner);
1118
0
    }
1119
0
    set_box_shadow_data(move(resolved_box_shadow_data));
1120
1121
0
    auto const& transformations = computed_values.transformations();
1122
0
    if (!transformations.is_empty()) {
1123
0
        auto matrix = Gfx::FloatMatrix4x4::identity();
1124
0
        for (auto const& transform : transformations)
1125
0
            matrix = matrix * transform.to_matrix(*this).release_value();
1126
0
        set_transform(matrix);
1127
0
    }
1128
1129
0
    auto const& transform_origin = computed_values.transform_origin();
1130
    // https://www.w3.org/TR/css-transforms-1/#transform-box
1131
0
    auto transform_box = computed_values.transform_box();
1132
    // For SVG elements without associated CSS layout box, the used value for content-box is fill-box and for
1133
    // border-box is stroke-box.
1134
    // FIXME: This currently detects any SVG element except the <svg> one. Is that correct?
1135
    //        And is it correct to use `else` below?
1136
0
    if (is<Painting::SVGPaintable>(*this)) {
1137
0
        switch (transform_box) {
1138
0
        case CSS::TransformBox::ContentBox:
1139
0
            transform_box = CSS::TransformBox::FillBox;
1140
0
            break;
1141
0
        case CSS::TransformBox::BorderBox:
1142
0
            transform_box = CSS::TransformBox::StrokeBox;
1143
0
            break;
1144
0
        default:
1145
0
            break;
1146
0
        }
1147
0
    }
1148
    // For elements with associated CSS layout box, the used value for fill-box is content-box and for
1149
    // stroke-box and view-box is border-box.
1150
0
    else {
1151
0
        switch (transform_box) {
1152
0
        case CSS::TransformBox::FillBox:
1153
0
            transform_box = CSS::TransformBox::ContentBox;
1154
0
            break;
1155
0
        case CSS::TransformBox::StrokeBox:
1156
0
        case CSS::TransformBox::ViewBox:
1157
0
            transform_box = CSS::TransformBox::BorderBox;
1158
0
            break;
1159
0
        default:
1160
0
            break;
1161
0
        }
1162
0
    }
1163
1164
0
    CSSPixelRect reference_box = [&]() {
1165
0
        switch (transform_box) {
1166
0
        case CSS::TransformBox::ContentBox:
1167
            // Uses the content box as reference box.
1168
            // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
1169
0
            return absolute_rect();
1170
0
        case CSS::TransformBox::BorderBox:
1171
            // Uses the border box as reference box.
1172
            // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
1173
0
            return absolute_border_box_rect();
1174
0
        case CSS::TransformBox::FillBox:
1175
            // Uses the object bounding box as reference box.
1176
            // FIXME: For now we're using the content rect as an approximation.
1177
0
            return absolute_rect();
1178
0
        case CSS::TransformBox::StrokeBox:
1179
            // Uses the stroke bounding box as reference box.
1180
            // FIXME: For now we're using the border rect as an approximation.
1181
0
            return absolute_border_box_rect();
1182
0
        case CSS::TransformBox::ViewBox:
1183
            // Uses the nearest SVG viewport as reference box.
1184
            // FIXME: If a viewBox attribute is specified for the SVG viewport creating element:
1185
            //  - The reference box is positioned at the origin of the coordinate system established by the viewBox attribute.
1186
            //  - The dimension of the reference box is set to the width and height values of the viewBox attribute.
1187
0
            auto* svg_paintable = first_ancestor_of_type<Painting::SVGSVGPaintable>();
1188
0
            if (!svg_paintable)
1189
0
                return absolute_border_box_rect();
1190
0
            return svg_paintable->absolute_rect();
1191
0
        }
1192
0
        VERIFY_NOT_REACHED();
1193
0
    }();
1194
0
    auto x = reference_box.left() + transform_origin.x.to_px(layout_node, reference_box.width());
1195
0
    auto y = reference_box.top() + transform_origin.y.to_px(layout_node, reference_box.height());
1196
0
    set_transform_origin({ x, y });
1197
0
    set_transform_origin({ x, y });
1198
1199
    // Outlines
1200
0
    auto outline_width = computed_values.outline_width().to_px(layout_node);
1201
0
    auto outline_data = borders_data_for_outline(layout_node, computed_values.outline_color(), computed_values.outline_style(), outline_width);
1202
0
    auto outline_offset = computed_values.outline_offset().to_px(layout_node);
1203
0
    set_outline_data(outline_data);
1204
0
    set_outline_offset(outline_offset);
1205
1206
0
    auto combined_transform = compute_combined_css_transform();
1207
0
    set_combined_css_transform(combined_transform);
1208
0
}
1209
1210
void PaintableWithLines::resolve_paint_properties()
1211
0
{
1212
0
    PaintableBox::resolve_paint_properties();
1213
1214
0
    auto const& layout_node = this->layout_node();
1215
0
    for (auto const& fragment : fragments()) {
1216
0
        auto const& text_shadow = fragment.m_layout_node->computed_values().text_shadow();
1217
0
        if (!text_shadow.is_empty()) {
1218
0
            Vector<Painting::ShadowData> resolved_shadow_data;
1219
0
            resolved_shadow_data.ensure_capacity(text_shadow.size());
1220
0
            for (auto const& layer : text_shadow) {
1221
0
                resolved_shadow_data.empend(
1222
0
                    layer.color,
1223
0
                    layer.offset_x.to_px(layout_node),
1224
0
                    layer.offset_y.to_px(layout_node),
1225
0
                    layer.blur_radius.to_px(layout_node),
1226
0
                    layer.spread_distance.to_px(layout_node),
1227
0
                    Painting::ShadowPlacement::Outer);
1228
0
            }
1229
0
            const_cast<Painting::PaintableFragment&>(fragment).set_shadows(move(resolved_shadow_data));
1230
0
        }
1231
0
    }
1232
0
}
1233
1234
}