LibWeb: Use offset of nearest scrollable ancestor for positioned boxes

Because positioned descendants are painted out-of-order we need to
separately apply offset of nearest scrollable box for them.

Fixes https://github.com/SerenityOS/serenity/issues/20554
This commit is contained in:
Aliaksandr Kalenik 2023-12-11 16:57:47 +01:00 committed by Andreas Kling
parent 2952f01e84
commit 6b79508c08
5 changed files with 167 additions and 0 deletions

View file

@ -0,0 +1,73 @@
<html>
<link rel="match" href="reference/positioned-elements-in-scroll-container-ref.html" />
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
height: 100%;
position: relative;
}
body {
height: 100%;
}
article {
overflow-x: scroll;
overflow-y: scroll;
width: 500px;
height: 500px;
border: 1px solid black;
}
.card {
margin: 2rem;
height: 30rem;
background-color: #bbb;
position: relative; /* is not positioned in the reference html */
}
</style>
<main>
<article id="scrollcontainer">
<h1>Fly away to victory!</h1>
<div>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<div class="card">A card!</div>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>
</article>
</main>
<script>
const scrollContainer = document.getElementById("scrollcontainer");
scrollContainer.scrollTop = 100;
</script>
</html>

View file

@ -0,0 +1,71 @@
<html>
<link rel="match" href="reference/positioned-elements-in-scroll-container-ref.html" />
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
height: 100%;
position: relative;
}
body {
height: 100%;
}
article {
overflow-x: scroll;
overflow-y: scroll;
width: 500px;
height: 500px;
border: 1px solid black;
z-index: 1;
}
.card {
margin: 2rem;
height: 30rem;
background-color: #bbb;
}
</style>
<main>
<article id="scrollcontainer">
<h1>Fly away to victory!</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
</p>
<div class="card">A card!</div>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
</p>
</article>
</main>
<script>
const scrollContainer = document.getElementById("scrollcontainer");
scrollContainer.scrollTop = 100;
</script>
</html>

View file

@ -850,4 +850,15 @@ Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, Hit
return {};
}
PaintableBox const* PaintableBox::nearest_scrollable_ancestor() const
{
auto* ancestor = parent();
while (ancestor) {
if (ancestor->is_paintable_box() && static_cast<PaintableBox const*>(ancestor)->has_scrollable_overflow())
return static_cast<PaintableBox const*>(ancestor);
ancestor = ancestor->parent();
}
return nullptr;
}
}

View file

@ -191,6 +191,8 @@ public:
BorderRadiiData const& border_radii_data() const { return m_border_radii_data; }
void set_border_radii_data(BorderRadiiData const& border_radii_data) { m_border_radii_data = border_radii_data; }
PaintableBox const* nearest_scrollable_ancestor() const;
protected:
explicit PaintableBox(Layout::Box const&);

View file

@ -226,6 +226,13 @@ 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 = nullptr;
if (paintable.is_paintable_box())
nearest_scrollable_ancestor = static_cast<PaintableBox const&>(paintable).nearest_scrollable_ancestor();
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;
@ -247,6 +254,9 @@ void StackingContext::paint_internal(PaintContext& context) const
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;
});