浏览代码

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
Aliaksandr Kalenik 1 年之前
父节点
当前提交
6b79508c08

+ 73 - 0
Tests/LibWeb/Ref/positioned-elements-in-scroll-container.html

@@ -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>

+ 71 - 0
Tests/LibWeb/Ref/reference/positioned-elements-in-scroll-container-ref.html

@@ -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>

+ 11 - 0
Userland/Libraries/LibWeb/Painting/PaintableBox.cpp

@@ -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;
+}
+
 }

+ 2 - 0
Userland/Libraries/LibWeb/Painting/PaintableBox.h

@@ -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&);
 

+ 10 - 0
Userland/Libraries/LibWeb/Painting/StackingContext.cpp

@@ -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;
     });