mirror of
https://github.com/SerenityOS/serenity
synced 2024-07-09 05:00:46 +00:00
LibWeb: Move clip rect calculation to happen before painting
With this change, clip rectangles for boxes with hidden overflow or the clip property are no longer calculated during the recording of painting commands. Instead, it has moved to the "pre-paint" phase, along with the assignment of scrolling offsets, and works in the following way: 1. The paintable tree is traversed to collect all paintable boxes that have hidden overflow or use the CSS clip property. For each of these boxes, the "final" clip rectangle is calculated by intersecting clip rectangles in the containing block chain for a box. 2. The paintable tree is traversed another time, and a clip rectangle is assigned for each paintable box contained by a node with hidden overflow or the clip property. This way, clipping becomes much easier during the painting commands recording phase, as it only concerns the use of already assigned clip rectangles. The same approach is applied to handle scrolling offsets. Also, clip rectangle calculation is now implemented more correctly, as we no longer stop at the stacking context boundary while intersecting clip rectangles in the containing block chain. Fixes: https://github.com/SerenityOS/serenity/issues/22932 https://github.com/SerenityOS/serenity/issues/22883 https://github.com/SerenityOS/serenity/issues/22679 https://github.com/SerenityOS/serenity/issues/22534
This commit is contained in:
parent
eaf9a56c10
commit
0bf82f748f
29
Tests/LibWeb/Ref/abspos-escapes-scroll-container.html
Normal file
29
Tests/LibWeb/Ref/abspos-escapes-scroll-container.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel="match" href="reference/abspos-escapes-scroll-container-ref.html" />
|
||||
<style>
|
||||
#scrollable-box {
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
overflow: auto;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.content {
|
||||
height: 600px;
|
||||
}
|
||||
.absolute-element {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background-color: yellow;
|
||||
}
|
||||
</style>
|
||||
<div id="scrollable-box">
|
||||
<div class="content"><span><span>Content that makes the container scrollable...</span></span></div>
|
||||
<div class="absolute-element">
|
||||
Abspos Element
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const scrollContainer = document.getElementById("scrollable-box");
|
||||
scrollContainer.scrollTop = 500;
|
||||
</script>
|
23
Tests/LibWeb/Ref/overflow-hidden-3.html
Normal file
23
Tests/LibWeb/Ref/overflow-hidden-3.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<!doctype html>
|
||||
<link rel="match" href="reference/overflow-hidden-3-ref.html" />
|
||||
<style>
|
||||
body {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
#outer {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#inner {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
#green {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: green;
|
||||
}
|
||||
</style><body><div id="outer"><div id="inner"><div id="green">
|
28
Tests/LibWeb/Ref/overflow-hidden-4.html
Normal file
28
Tests/LibWeb/Ref/overflow-hidden-4.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel="match" href="reference/overflow-hidden-4-ref.html" />
|
||||
<style>
|
||||
* {
|
||||
outline: black solid 1px;
|
||||
}
|
||||
.outer {
|
||||
background: pink;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.middle {
|
||||
transform: perspective(1200px);
|
||||
background: orange;
|
||||
}
|
||||
.inner {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: magenta;
|
||||
overflow: hidden;
|
||||
}
|
||||
.red {
|
||||
background: red;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
</style><body><div class="outer"><div class="middle"><div class="inner"><div class="red">
|
30
Tests/LibWeb/Ref/overflow-hidden-5.html
Normal file
30
Tests/LibWeb/Ref/overflow-hidden-5.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<link rel="match" href="reference/overflow-hidden-5-ref.html" />
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
margin-top: 100px;
|
||||
margin-left: 100px;
|
||||
width: 900px;
|
||||
border: 5px solid magenta;
|
||||
}
|
||||
|
||||
.container {
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.box {
|
||||
background-color: darkblue;
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
transform: translate3d(-50px, -50px, 0px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<div class="wrapper"><div class="container"><div class="box"></div></div></div>
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<style>
|
||||
#scrollable-box {
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.absolute-element {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background-color: yellow;
|
||||
}
|
||||
</style>
|
||||
<div id="scrollable-box"></div>
|
||||
<div class="absolute-element">Abspos Element</div>
|
9
Tests/LibWeb/Ref/reference/overflow-hidden-3-ref.html
Normal file
9
Tests/LibWeb/Ref/reference/overflow-hidden-3-ref.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel="match" href="reference/overflow-hidden-3-ref.html" />
|
||||
<style>
|
||||
.green {
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
background: green;
|
||||
}
|
||||
</style><body><div class="green">
|
12
Tests/LibWeb/Ref/reference/overflow-hidden-4-ref.html
Normal file
12
Tests/LibWeb/Ref/reference/overflow-hidden-4-ref.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel="match" href="reference/overflow-hidden-4-ref.html" />
|
||||
<style>
|
||||
* {
|
||||
outline: black solid 1px;
|
||||
}
|
||||
.red {
|
||||
background: red;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style><body><div class="red">
|
24
Tests/LibWeb/Ref/reference/overflow-hidden-5-ref.html
Normal file
24
Tests/LibWeb/Ref/reference/overflow-hidden-5-ref.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<link rel="match" href="reference/overflow-hidden-5-ref.html" />
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
margin-top: 100px;
|
||||
margin-left: 100px;
|
||||
border: 5px solid magenta;
|
||||
width: 900px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.box {
|
||||
background-color: darkblue;
|
||||
width: 206px;
|
||||
height: 206px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<div class="wrapper"><div class="box"></div></div>
|
|
@ -2082,8 +2082,10 @@ void Navigable::paint(Painting::RecordingPainter& recording_painter, PaintConfig
|
|||
context.set_should_paint_overlay(config.paint_overlay);
|
||||
context.set_has_focus(config.has_focus);
|
||||
|
||||
HashMap<Painting::PaintableBox const*, Painting::ViewportPaintable::ScrollFrame> scroll_frames;
|
||||
if (is_traversable()) {
|
||||
document->paintable()->collect_scroll_frames(context);
|
||||
document->paintable()->assign_scroll_frame_ids(scroll_frames);
|
||||
document->paintable()->assign_clip_rectangles(context);
|
||||
}
|
||||
|
||||
document->paintable()->paint_all_phases(context);
|
||||
|
@ -2091,11 +2093,11 @@ void Navigable::paint(Painting::RecordingPainter& recording_painter, PaintConfig
|
|||
// FIXME: Support scrollable frames inside iframes.
|
||||
if (is_traversable()) {
|
||||
Vector<Gfx::IntPoint> scroll_offsets_by_frame_id;
|
||||
scroll_offsets_by_frame_id.resize(context.scroll_frames().size());
|
||||
for (auto [_, scrollable_frame] : context.scroll_frames())
|
||||
scroll_offsets_by_frame_id[scrollable_frame.id] = context.rounded_device_point(
|
||||
scrollable_frame.offset)
|
||||
.to_type<int>();
|
||||
scroll_offsets_by_frame_id.resize(scroll_frames.size());
|
||||
for (auto [_, scrollable_frame] : scroll_frames) {
|
||||
auto scroll_offset = context.rounded_device_point(scrollable_frame.offset).to_type<int>();
|
||||
scroll_offsets_by_frame_id[scrollable_frame.id] = scroll_offset;
|
||||
}
|
||||
recording_painter.apply_scroll_offsets(scroll_offsets_by_frame_id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
namespace Web::Painting {
|
||||
|
||||
Gfx::AntiAliasingPainter::CornerRadius BorderRadiusData::as_corner(PaintContext& context) const
|
||||
Gfx::AntiAliasingPainter::CornerRadius BorderRadiusData::as_corner(PaintContext const& context) const
|
||||
{
|
||||
return Gfx::AntiAliasingPainter::CornerRadius {
|
||||
context.floored_device_pixels(horizontal_radius).value(),
|
||||
|
|
|
@ -17,7 +17,7 @@ struct BorderRadiusData {
|
|||
CSSPixels horizontal_radius { 0 };
|
||||
CSSPixels vertical_radius { 0 };
|
||||
|
||||
Gfx::AntiAliasingPainter::CornerRadius as_corner(PaintContext& context) const;
|
||||
Gfx::AntiAliasingPainter::CornerRadius as_corner(PaintContext const& context) const;
|
||||
|
||||
inline operator bool() const
|
||||
{
|
||||
|
@ -71,7 +71,7 @@ struct BorderRadiiData {
|
|||
shrink(-top, -right, -bottom, -left);
|
||||
}
|
||||
|
||||
inline CornerRadii as_corners(PaintContext& context) const
|
||||
inline CornerRadii as_corners(PaintContext const& context) const
|
||||
{
|
||||
return CornerRadii {
|
||||
top_left.as_corner(context),
|
||||
|
|
|
@ -27,6 +27,26 @@ Layout::InlineNode const& InlinePaintable::layout_node() const
|
|||
return static_cast<Layout::InlineNode const&>(Paintable::layout_node());
|
||||
}
|
||||
|
||||
void InlinePaintable::before_paint(PaintContext& context, PaintPhase) const
|
||||
{
|
||||
if (m_scroll_frame_id.has_value()) {
|
||||
context.recording_painter().save();
|
||||
context.recording_painter().set_scroll_frame_id(m_scroll_frame_id.value());
|
||||
}
|
||||
if (m_clip_rect.has_value()) {
|
||||
context.recording_painter().save();
|
||||
context.recording_painter().add_clip_rect(context.enclosing_device_rect(*m_clip_rect).to_type<int>());
|
||||
}
|
||||
}
|
||||
|
||||
void InlinePaintable::after_paint(PaintContext& context, PaintPhase) const
|
||||
{
|
||||
if (m_clip_rect.has_value())
|
||||
context.recording_painter().restore();
|
||||
if (m_scroll_frame_id.has_value())
|
||||
context.recording_painter().restore();
|
||||
}
|
||||
|
||||
void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const
|
||||
{
|
||||
auto& painter = context.recording_painter();
|
||||
|
|
|
@ -20,6 +20,9 @@ public:
|
|||
|
||||
virtual void paint(PaintContext&, PaintPhase) const override;
|
||||
|
||||
virtual void before_paint(PaintContext& context, PaintPhase) const override;
|
||||
virtual void after_paint(PaintContext& context, PaintPhase) const override;
|
||||
|
||||
Layout::InlineNode const& layout_node() const;
|
||||
auto const& box_model() const { return layout_node().box_model(); }
|
||||
|
||||
|
@ -34,12 +37,18 @@ public:
|
|||
void set_box_shadow_data(Vector<ShadowData>&& box_shadow_data) { m_box_shadow_data = move(box_shadow_data); }
|
||||
Vector<ShadowData> const& box_shadow_data() const { return m_box_shadow_data; }
|
||||
|
||||
void set_scroll_frame_id(int id) { m_scroll_frame_id = id; }
|
||||
void set_clip_rect(Optional<CSSPixelRect> rect) { m_clip_rect = rect; }
|
||||
|
||||
private:
|
||||
InlinePaintable(Layout::InlineNode const&);
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_fragment(Callback) const;
|
||||
|
||||
Optional<int> m_scroll_frame_id;
|
||||
Optional<CSSPixelRect> m_clip_rect;
|
||||
|
||||
Vector<ShadowData> m_box_shadow_data;
|
||||
Vector<PaintableFragment> m_fragments;
|
||||
};
|
||||
|
|
|
@ -74,12 +74,6 @@ public:
|
|||
|
||||
u32 allocate_corner_clipper_id() { return m_next_corner_clipper_id++; }
|
||||
|
||||
struct ScrollFrame {
|
||||
i32 id { -1 };
|
||||
CSSPixelPoint offset;
|
||||
};
|
||||
HashMap<Painting::PaintableBox const*, ScrollFrame>& scroll_frames() { return m_scroll_frames; }
|
||||
|
||||
private:
|
||||
Painting::RecordingPainter& m_recording_painter;
|
||||
Palette m_palette;
|
||||
|
@ -90,7 +84,6 @@ private:
|
|||
bool m_focus { false };
|
||||
Gfx::AffineTransform m_svg_transform;
|
||||
u32 m_next_corner_clipper_id { 0 };
|
||||
HashMap<Painting::PaintableBox const*, ScrollFrame> m_scroll_frames;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -148,19 +148,6 @@ void Paintable::set_needs_display() const
|
|||
});
|
||||
}
|
||||
|
||||
PaintableBox const* Paintable::nearest_scrollable_ancestor_within_stacking_context() const
|
||||
{
|
||||
auto* ancestor = parent();
|
||||
while (ancestor) {
|
||||
if (ancestor->stacking_context())
|
||||
return nullptr;
|
||||
if (ancestor->is_paintable_box() && static_cast<PaintableBox const*>(ancestor)->has_scrollable_overflow())
|
||||
return static_cast<PaintableBox const*>(ancestor);
|
||||
ancestor = ancestor->parent();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CSSPixelPoint Paintable::box_type_agnostic_position() const
|
||||
{
|
||||
if (is_paintable_box())
|
||||
|
|
|
@ -186,8 +186,6 @@ public:
|
|||
DOM::Document const& document() const { return layout_node().document(); }
|
||||
DOM::Document& document() { return layout_node().document(); }
|
||||
|
||||
PaintableBox const* nearest_scrollable_ancestor_within_stacking_context() const;
|
||||
|
||||
CSSPixelPoint box_type_agnostic_position() const;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -121,6 +121,26 @@ CSSPixelRect PaintableBox::compute_absolute_rect() const
|
|||
return rect;
|
||||
}
|
||||
|
||||
CSSPixelRect PaintableBox::compute_absolute_padding_rect_with_css_transform_applied() const
|
||||
{
|
||||
CSSPixelRect rect { offset(), content_size() };
|
||||
for (auto const* block = containing_block(); block; block = block->containing_block()) {
|
||||
auto offset = block->paintable_box()->offset();
|
||||
auto affine_transform = Gfx::extract_2d_affine_transform(block->paintable_box()->transform());
|
||||
offset.translate_by(affine_transform.translation().to_type<CSSPixels>());
|
||||
rect.translate_by(offset);
|
||||
}
|
||||
auto affine_transform = Gfx::extract_2d_affine_transform(transform());
|
||||
rect.translate_by(affine_transform.translation().to_type<CSSPixels>());
|
||||
|
||||
CSSPixelRect padding_rect;
|
||||
padding_rect.set_x(rect.x() - box_model().padding.left);
|
||||
padding_rect.set_width(content_width() + box_model().padding.left + box_model().padding.right);
|
||||
padding_rect.set_y(rect.y() - box_model().padding.top);
|
||||
padding_rect.set_height(content_height() + box_model().padding.top + box_model().padding.bottom);
|
||||
return padding_rect;
|
||||
}
|
||||
|
||||
CSSPixelRect PaintableBox::absolute_rect() const
|
||||
{
|
||||
if (!m_absolute_rect.has_value())
|
||||
|
@ -171,11 +191,8 @@ void PaintableBox::before_paint(PaintContext& context, [[maybe_unused]] PaintPha
|
|||
if (!is_visible())
|
||||
return;
|
||||
|
||||
auto clip_rect = get_clip_rect();
|
||||
if (clip_rect.has_value()) {
|
||||
context.recording_painter().save();
|
||||
context.recording_painter().add_clip_rect(clip_rect->to_type<int>());
|
||||
}
|
||||
apply_scroll_offset(context, phase);
|
||||
apply_clip_overflow_rect(context, phase);
|
||||
}
|
||||
|
||||
void PaintableBox::after_paint(PaintContext& context, [[maybe_unused]] PaintPhase phase) const
|
||||
|
@ -183,8 +200,8 @@ void PaintableBox::after_paint(PaintContext& context, [[maybe_unused]] PaintPhas
|
|||
if (!is_visible())
|
||||
return;
|
||||
|
||||
if (get_clip_rect().has_value())
|
||||
context.recording_painter().restore();
|
||||
clear_clip_overflow_rect(context, phase);
|
||||
reset_scroll_offset(context, phase);
|
||||
}
|
||||
|
||||
void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
|
||||
|
@ -358,110 +375,63 @@ BorderRadiiData PaintableBox::normalized_border_radii_data(ShrinkRadiiForBorders
|
|||
return border_radii_data;
|
||||
}
|
||||
|
||||
Optional<CSSPixelRect> PaintableBox::calculate_overflow_clipped_rect() const
|
||||
{
|
||||
if (layout_node().is_viewport()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!m_clip_rect.has_value()) {
|
||||
// NOTE: stacking context should not be crossed while aggregating rectangle to
|
||||
// clip `overflow: hidden` because intersecting rectangles with different
|
||||
// transforms doesn't make sense
|
||||
// TODO: figure out if there are cases when stacking context should be
|
||||
// crossed to calculate correct clip rect
|
||||
if (!stacking_context() && containing_block() && containing_block()->paintable_box()) {
|
||||
m_clip_rect = containing_block()->paintable_box()->calculate_overflow_clipped_rect();
|
||||
}
|
||||
|
||||
auto overflow_x = computed_values().overflow_x();
|
||||
auto overflow_y = computed_values().overflow_y();
|
||||
|
||||
if (overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible) {
|
||||
if (m_clip_rect.has_value()) {
|
||||
m_clip_rect->intersect(absolute_padding_box_rect());
|
||||
} else {
|
||||
m_clip_rect = absolute_padding_box_rect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m_clip_rect;
|
||||
}
|
||||
|
||||
void PaintableBox::apply_scroll_offset(PaintContext& context, PaintPhase) const
|
||||
{
|
||||
if (context.scroll_frames().contains(this)) {
|
||||
if (m_scroll_frame_id.has_value()) {
|
||||
context.recording_painter().save();
|
||||
context.recording_painter().set_scroll_frame_id(context.scroll_frames().get(this)->id);
|
||||
context.recording_painter().set_scroll_frame_id(m_scroll_frame_id.value());
|
||||
}
|
||||
}
|
||||
|
||||
void PaintableBox::reset_scroll_offset(PaintContext& context, PaintPhase) const
|
||||
{
|
||||
if (context.scroll_frames().contains(this)) {
|
||||
if (m_scroll_frame_id.has_value())
|
||||
context.recording_painter().restore();
|
||||
}
|
||||
}
|
||||
|
||||
void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase phase) const
|
||||
{
|
||||
if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::Foreground))
|
||||
if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::Foreground, PaintPhase::Outline))
|
||||
return;
|
||||
|
||||
// FIXME: Support more overflow variations.
|
||||
auto clip_rect = this->calculate_overflow_clipped_rect();
|
||||
auto overflow_x = computed_values().overflow_x();
|
||||
auto overflow_y = computed_values().overflow_y();
|
||||
if (m_clip_rect.has_value()) {
|
||||
auto overflow_clip_rect = m_clip_rect.value();
|
||||
for (auto const* ancestor = &this->layout_box(); ancestor; ancestor = ancestor->containing_block()) {
|
||||
auto affine_transform = Gfx::extract_2d_affine_transform(ancestor->paintable_box()->transform());
|
||||
if (!affine_transform.is_identity()) {
|
||||
// NOTE: Because the painting command executor applies CSS transform of the nearest stacking context
|
||||
// and the m_clip_rect is determined considering CSS transforms, here transform of the nearest
|
||||
// stacking context need to be compensated.
|
||||
// This adjustment ensures the transform is accounted for just once.
|
||||
overflow_clip_rect.translate_by(-affine_transform.translation().to_type<CSSPixels>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto css_clip_property = get_clip_rect();
|
||||
if (css_clip_property.has_value()) {
|
||||
if (clip_rect.has_value())
|
||||
clip_rect->intersect(css_clip_property.value());
|
||||
else
|
||||
clip_rect = css_clip_property.value();
|
||||
}
|
||||
|
||||
if (!clip_rect.has_value())
|
||||
return;
|
||||
|
||||
if (!m_clipping_overflow) {
|
||||
context.recording_painter().save();
|
||||
context.recording_painter().add_clip_rect(context.enclosing_device_rect(*clip_rect).to_type<int>());
|
||||
m_clipping_overflow = true;
|
||||
}
|
||||
|
||||
if (!clip_rect->is_empty() && overflow_y == CSS::Overflow::Hidden && overflow_x == CSS::Overflow::Hidden) {
|
||||
auto border_radii_data = normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
|
||||
CornerRadii corner_radii {
|
||||
.top_left = border_radii_data.top_left.as_corner(context),
|
||||
.top_right = border_radii_data.top_right.as_corner(context),
|
||||
.bottom_right = border_radii_data.bottom_right.as_corner(context),
|
||||
.bottom_left = border_radii_data.bottom_left.as_corner(context)
|
||||
};
|
||||
if (corner_radii.has_any_radius()) {
|
||||
context.recording_painter().save();
|
||||
context.recording_painter().add_clip_rect(context.enclosing_device_rect(overflow_clip_rect).to_type<int>());
|
||||
if (m_corner_clip_radii.has_value()) {
|
||||
VERIFY(!m_corner_clipper_id.has_value());
|
||||
m_corner_clipper_id = context.allocate_corner_clipper_id();
|
||||
context.recording_painter().sample_under_corners(*m_corner_clipper_id, corner_radii, context.rounded_device_rect(*clip_rect).to_type<int>(), CornerClip::Outside);
|
||||
context.recording_painter().sample_under_corners(*m_corner_clipper_id, *m_corner_clip_radii, context.rounded_device_rect(overflow_clip_rect).to_type<int>(), CornerClip::Outside);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase phase) const
|
||||
{
|
||||
if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::Foreground))
|
||||
if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::Foreground, PaintPhase::Outline))
|
||||
return;
|
||||
|
||||
// FIXME: Support more overflow variations.
|
||||
if (m_clipping_overflow) {
|
||||
context.recording_painter().restore();
|
||||
m_clipping_overflow = false;
|
||||
}
|
||||
if (m_corner_clipper_id.has_value()) {
|
||||
VERIFY(m_corner_clipper_id.has_value());
|
||||
auto clip_rect = this->calculate_overflow_clipped_rect();
|
||||
context.recording_painter().blit_corner_clipping(*m_corner_clipper_id, context.rounded_device_rect(*clip_rect).to_type<int>());
|
||||
m_corner_clipper_id = {};
|
||||
if (m_corner_clip_radii.has_value()) {
|
||||
VERIFY(m_corner_clipper_id.has_value());
|
||||
context.recording_painter().blit_corner_clipping(*m_corner_clipper_id, context.rounded_device_rect(*m_clip_rect).to_type<int>());
|
||||
m_corner_clipper_id = {};
|
||||
}
|
||||
context.recording_painter().restore();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -117,8 +117,6 @@ public:
|
|||
return m_overflow_data->scrollable_overflow_rect;
|
||||
}
|
||||
|
||||
Optional<CSSPixelRect> calculate_overflow_clipped_rect() const;
|
||||
|
||||
void set_overflow_data(OverflowData data) { m_overflow_data = move(data); }
|
||||
|
||||
DOM::Node const* dom_node() const { return layout_box().dom_node(); }
|
||||
|
@ -191,11 +189,17 @@ public:
|
|||
void set_transform_origin(CSSPixelPoint transform_origin) { m_transform_origin = transform_origin; }
|
||||
CSSPixelPoint const& transform_origin() const { return m_transform_origin; }
|
||||
|
||||
protected:
|
||||
explicit PaintableBox(Layout::Box const&);
|
||||
CSSPixelRect compute_absolute_padding_rect_with_css_transform_applied() const;
|
||||
|
||||
Optional<CSSPixelRect> get_clip_rect() const;
|
||||
|
||||
void set_clip_rect(Optional<CSSPixelRect> rect) { m_clip_rect = rect; }
|
||||
void set_scroll_frame_id(int id) { m_scroll_frame_id = id; }
|
||||
void set_corner_clip_radii(CornerRadii const& corner_radii) { m_corner_clip_radii = corner_radii; }
|
||||
|
||||
protected:
|
||||
explicit PaintableBox(Layout::Box const&);
|
||||
|
||||
virtual void paint_border(PaintContext&) const;
|
||||
virtual void paint_backdrop_filter(PaintContext&) const;
|
||||
virtual void paint_background(PaintContext&) const;
|
||||
|
@ -215,11 +219,13 @@ private:
|
|||
Optional<CSSPixelRect> mutable m_absolute_rect;
|
||||
Optional<CSSPixelRect> mutable m_absolute_paint_rect;
|
||||
|
||||
Optional<CSSPixelRect> mutable m_clip_rect;
|
||||
|
||||
mutable bool m_clipping_overflow { false };
|
||||
mutable Optional<u32> m_corner_clipper_id;
|
||||
|
||||
Optional<CSSPixelRect> m_clip_rect;
|
||||
Optional<int> m_scroll_frame_id;
|
||||
Optional<CornerRadii> m_corner_clip_radii;
|
||||
|
||||
Optional<BordersDataWithElementKind> m_override_borders_data;
|
||||
Optional<TableCellCoordinates> m_table_cell_coordinates;
|
||||
|
||||
|
|
|
@ -88,9 +88,7 @@ void StackingContext::paint_node_as_stacking_context(Paintable const& paintable,
|
|||
|
||||
void StackingContext::paint_descendants(PaintContext& context, Paintable const& paintable, StackingContextPaintPhase phase)
|
||||
{
|
||||
paintable.apply_scroll_offset(context, to_paint_phase(phase));
|
||||
paintable.before_children_paint(context, to_paint_phase(phase));
|
||||
paintable.apply_clip_overflow_rect(context, to_paint_phase(phase));
|
||||
|
||||
paintable.for_each_child([&context, phase](auto& child) {
|
||||
auto* stacking_context = child.stacking_context();
|
||||
|
@ -167,9 +165,7 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const&
|
|||
}
|
||||
});
|
||||
|
||||
paintable.clear_clip_overflow_rect(context, to_paint_phase(phase));
|
||||
paintable.after_children_paint(context, to_paint_phase(phase));
|
||||
paintable.reset_scroll_offset(context, to_paint_phase(phase));
|
||||
}
|
||||
|
||||
void StackingContext::paint_child(PaintContext& context, StackingContext const& child)
|
||||
|
@ -178,16 +174,8 @@ void StackingContext::paint_child(PaintContext& context, StackingContext const&
|
|||
if (parent_paintable)
|
||||
parent_paintable->before_children_paint(context, PaintPhase::Foreground);
|
||||
|
||||
PaintableBox const* nearest_scrollable_ancestor = child.paintable().nearest_scrollable_ancestor_within_stacking_context();
|
||||
|
||||
if (nearest_scrollable_ancestor)
|
||||
nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground);
|
||||
|
||||
child.paint(context);
|
||||
|
||||
if (nearest_scrollable_ancestor)
|
||||
nearest_scrollable_ancestor->reset_scroll_offset(context, PaintPhase::Foreground);
|
||||
|
||||
if (parent_paintable)
|
||||
parent_paintable->after_children_paint(context, PaintPhase::Foreground);
|
||||
}
|
||||
|
@ -227,21 +215,12 @@ void StackingContext::paint_internal(PaintContext& context) const
|
|||
: TraversalDecision::Continue;
|
||||
}
|
||||
|
||||
// Apply scroll offset of nearest scrollable ancestor before painting the positioned descendant.
|
||||
PaintableBox const* nearest_scrollable_ancestor = paintable.nearest_scrollable_ancestor_within_stacking_context();
|
||||
if (nearest_scrollable_ancestor)
|
||||
nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground);
|
||||
|
||||
// At this point, `paintable_box` is a positioned descendant with z-index: auto.
|
||||
// FIXME: This is basically duplicating logic found elsewhere in this same function. Find a way to make this more elegant.
|
||||
auto exit_decision = TraversalDecision::Continue;
|
||||
auto* parent_paintable = paintable.parent();
|
||||
if (parent_paintable)
|
||||
parent_paintable->before_children_paint(context, PaintPhase::Foreground);
|
||||
auto containing_block = paintable.containing_block();
|
||||
auto* containing_block_paintable = containing_block ? containing_block->paintable() : nullptr;
|
||||
if (containing_block_paintable)
|
||||
containing_block_paintable->apply_clip_overflow_rect(context, PaintPhase::Foreground);
|
||||
if (auto* child = paintable.stacking_context()) {
|
||||
paint_child(context, *child);
|
||||
exit_decision = TraversalDecision::SkipChildrenAndContinue;
|
||||
|
@ -250,11 +229,6 @@ void StackingContext::paint_internal(PaintContext& context) const
|
|||
}
|
||||
if (parent_paintable)
|
||||
parent_paintable->after_children_paint(context, PaintPhase::Foreground);
|
||||
if (containing_block_paintable)
|
||||
containing_block_paintable->clear_clip_overflow_rect(context, PaintPhase::Foreground);
|
||||
|
||||
if (nearest_scrollable_ancestor)
|
||||
nearest_scrollable_ancestor->reset_scroll_offset(context, PaintPhase::Foreground);
|
||||
|
||||
return exit_decision;
|
||||
});
|
||||
|
@ -263,22 +237,8 @@ void StackingContext::paint_internal(PaintContext& context) const
|
|||
// (smallest first) then tree order. (Step 9)
|
||||
// NOTE: This doesn't check if a descendant is positioned as modern CSS allows for alternative methods to establish stacking contexts.
|
||||
for (auto* child : m_children) {
|
||||
PaintableBox const* nearest_scrollable_ancestor = child->paintable().nearest_scrollable_ancestor_within_stacking_context();
|
||||
|
||||
if (nearest_scrollable_ancestor)
|
||||
nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground);
|
||||
|
||||
auto containing_block = child->paintable().containing_block();
|
||||
auto const* containing_block_paintable = containing_block ? containing_block->paintable() : nullptr;
|
||||
if (containing_block_paintable)
|
||||
containing_block_paintable->apply_clip_overflow_rect(context, PaintPhase::Foreground);
|
||||
if (child->paintable().computed_values().z_index().has_value() && child->paintable().computed_values().z_index().value() >= 1)
|
||||
paint_child(context, *child);
|
||||
if (containing_block_paintable)
|
||||
containing_block_paintable->clear_clip_overflow_rect(context, PaintPhase::Foreground);
|
||||
|
||||
if (nearest_scrollable_ancestor)
|
||||
nearest_scrollable_ancestor->reset_scroll_offset(context, PaintPhase::Foreground);
|
||||
}
|
||||
|
||||
paint_node(paintable(), context, PaintPhase::Outline);
|
||||
|
|
|
@ -56,19 +56,97 @@ void ViewportPaintable::paint_all_phases(PaintContext& context)
|
|||
stacking_context()->paint(context);
|
||||
}
|
||||
|
||||
void ViewportPaintable::collect_scroll_frames(PaintContext& context) const
|
||||
void ViewportPaintable::assign_scroll_frame_ids(HashMap<Painting::PaintableBox const*, ScrollFrame>& scroll_frames) const
|
||||
{
|
||||
i32 next_id = 0;
|
||||
// Collect scroll frames with their offsets (accumulated offset for nested scroll frames).
|
||||
for_each_in_subtree_of_type<PaintableBox>([&](auto const& paintable_box) {
|
||||
if (paintable_box.has_scrollable_overflow()) {
|
||||
auto offset = paintable_box.scroll_offset();
|
||||
auto ancestor = paintable_box.parent();
|
||||
auto ancestor = paintable_box.containing_block();
|
||||
while (ancestor) {
|
||||
if (ancestor->is_paintable_box() && static_cast<PaintableBox const*>(ancestor)->has_scrollable_overflow())
|
||||
offset.translate_by(static_cast<PaintableBox const*>(ancestor)->scroll_offset());
|
||||
ancestor = ancestor->parent();
|
||||
if (ancestor->paintable()->is_paintable_box() && static_cast<PaintableBox const*>(ancestor->paintable())->has_scrollable_overflow())
|
||||
offset.translate_by(static_cast<PaintableBox const*>(ancestor->paintable())->scroll_offset());
|
||||
ancestor = ancestor->containing_block();
|
||||
}
|
||||
scroll_frames.set(&paintable_box, { .id = next_id++, .offset = -offset });
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
||||
// Assign scroll frame id to all paintables contained in a scroll frame.
|
||||
for_each_in_subtree([&](auto const& paintable) {
|
||||
for (auto block = paintable.containing_block(); block; block = block->containing_block()) {
|
||||
auto const& block_paintable_box = *block->paintable_box();
|
||||
if (auto scroll_frame_id = scroll_frames.get(&block_paintable_box); scroll_frame_id.has_value()) {
|
||||
if (paintable.is_paintable_box()) {
|
||||
auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
|
||||
const_cast<PaintableBox&>(paintable_box).set_scroll_frame_id(scroll_frame_id->id);
|
||||
} else if (paintable.is_inline_paintable()) {
|
||||
auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable);
|
||||
const_cast<InlinePaintable&>(inline_paintable).set_scroll_frame_id(scroll_frame_id->id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void ViewportPaintable::assign_clip_rectangles(PaintContext const& context)
|
||||
{
|
||||
HashMap<Paintable const*, CSSPixelRect> clip_rects;
|
||||
// Calculate clip rects for all boxes that either have hidden overflow or a CSS clip property.
|
||||
for_each_in_subtree_of_type<PaintableBox>([&](auto const& paintable_box) {
|
||||
auto overflow_x = paintable_box.computed_values().overflow_x();
|
||||
auto overflow_y = paintable_box.computed_values().overflow_y();
|
||||
// Start from CSS clip property if it exists.
|
||||
Optional<CSSPixelRect> clip_rect = paintable_box.get_clip_rect();
|
||||
// FIXME: Support overflow clip in one direction only.
|
||||
if (overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible) {
|
||||
auto overflow_clip_rect = paintable_box.compute_absolute_padding_rect_with_css_transform_applied();
|
||||
for (auto block = &paintable_box.layout_box(); !block->is_viewport(); block = block->containing_block()) {
|
||||
auto const& block_paintable_box = *block->paintable_box();
|
||||
auto block_overflow_x = block_paintable_box.computed_values().overflow_x();
|
||||
auto block_overflow_y = block_paintable_box.computed_values().overflow_y();
|
||||
if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible)
|
||||
overflow_clip_rect.intersect(block_paintable_box.compute_absolute_padding_rect_with_css_transform_applied());
|
||||
if (auto css_clip_property_rect = block->paintable_box()->get_clip_rect(); css_clip_property_rect.has_value())
|
||||
overflow_clip_rect.intersect(css_clip_property_rect.value());
|
||||
}
|
||||
clip_rect = overflow_clip_rect;
|
||||
}
|
||||
if (clip_rect.has_value())
|
||||
clip_rects.set(&paintable_box, *clip_rect);
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
||||
// Assign clip rects to all paintable boxes contained by a box with a hidden overflow or a CSS clip property.
|
||||
for_each_in_subtree_of_type<PaintableBox>([&](auto const& paintable_box) {
|
||||
Optional<CSSPixelRect> clip_rect = paintable_box.get_clip_rect();
|
||||
for (auto block = paintable_box.containing_block(); block; block = block->containing_block()) {
|
||||
if (auto containing_block_clip_rect = clip_rects.get(block->paintable()); containing_block_clip_rect.has_value()) {
|
||||
auto border_radii_data = block->paintable_box()->normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
|
||||
CornerRadii corner_radii = border_radii_data.as_corners(context);
|
||||
if (corner_radii.has_any_radius()) {
|
||||
// FIXME: Border radii of all boxes in containing block chain should be taken into account instead of just the closest one.
|
||||
const_cast<PaintableBox&>(paintable_box).set_corner_clip_radii(corner_radii);
|
||||
}
|
||||
clip_rect = *containing_block_clip_rect;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const_cast<PaintableBox&>(paintable_box).set_clip_rect(clip_rect);
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
||||
// Assign clip rects to all inline paintables contained by a box with hidden overflow or a CSS clip property.
|
||||
for_each_in_subtree_of_type<InlinePaintable>([&](auto const& paintable_box) {
|
||||
for (auto block = paintable_box.containing_block(); block; block = block->containing_block()) {
|
||||
if (auto clip_rect = clip_rects.get(block->paintable()); clip_rect.has_value()) {
|
||||
const_cast<InlinePaintable&>(paintable_box).set_clip_rect(clip_rect);
|
||||
break;
|
||||
}
|
||||
context.scroll_frames().set(&paintable_box, { .id = next_id++, .offset = -offset });
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
|
|
@ -20,7 +20,12 @@ public:
|
|||
void paint_all_phases(PaintContext&);
|
||||
void build_stacking_context_tree_if_needed();
|
||||
|
||||
void collect_scroll_frames(PaintContext&) const;
|
||||
struct ScrollFrame {
|
||||
i32 id { -1 };
|
||||
CSSPixelPoint offset;
|
||||
};
|
||||
void assign_scroll_frame_ids(HashMap<Painting::PaintableBox const*, ScrollFrame>&) const;
|
||||
void assign_clip_rectangles(PaintContext const&);
|
||||
|
||||
private:
|
||||
void build_stacking_context_tree();
|
||||
|
|
Loading…
Reference in New Issue
Block a user