瀏覽代碼

LibWeb/Painting: Apply clip and mask operations that are off-screen

These operations should still apply even if they are off screen, because
they affect painting of things outside of their bounding rectangles.

This commit makes us always apply these, regardless of if they are in
the visible region. However, if they are outside that region, we
replace them with simple clip-rect commands, which have the same
effect (not painting anything) but are cheaper than computing a full
mask bitmap.
Sam Atkins 8 月之前
父節點
當前提交
a6822986bb

+ 4 - 0
Libraries/LibWeb/Painting/Command.h

@@ -105,6 +105,8 @@ struct Translate {
 struct AddClipRect {
     Gfx::IntRect rect;
 
+    [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
+    bool is_clip_or_mask() const { return true; }
     void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); }
 };
 
@@ -349,6 +351,7 @@ struct AddRoundedRectClip {
     CornerClip corner_clip;
 
     [[nodiscard]] Gfx::IntRect bounding_rect() const { return border_rect; }
+    bool is_clip_or_mask() const { return true; }
 
     void translate_by(Gfx::IntPoint const& offset) { border_rect.translate_by(offset); }
 };
@@ -358,6 +361,7 @@ struct AddMask {
     Gfx::IntRect rect;
 
     [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
+    bool is_clip_or_mask() const { return true; }
 
     void translate_by(Gfx::IntPoint const& offset)
     {

+ 20 - 0
Libraries/LibWeb/Painting/DisplayList.cpp

@@ -24,6 +24,17 @@ static Optional<Gfx::IntRect> command_bounding_rectangle(Command const& command)
         });
 }
 
+static bool command_is_clip_or_mask(Command const& command)
+{
+    return command.visit(
+        [&](auto const& command) -> bool {
+            if constexpr (requires { command.is_clip_or_mask(); })
+                return command.is_clip_or_mask();
+            else
+                return false;
+        });
+}
+
 void DisplayListPlayer::execute(DisplayList& display_list)
 {
     auto const& commands = display_list.commands();
@@ -60,6 +71,15 @@ void DisplayListPlayer::execute(DisplayList& display_list)
 
         auto bounding_rect = command_bounding_rectangle(command);
         if (bounding_rect.has_value() && (bounding_rect->is_empty() || would_be_fully_clipped_by_painter(*bounding_rect))) {
+            // Any clip or mask that's located outside of the visible region is equivalent to a simple clip-rect,
+            // so replace it with one to avoid doing unnecessary work.
+            if (command_is_clip_or_mask(command)) {
+                if (command.has<AddClipRect>()) {
+                    add_clip_rect(command.get<AddClipRect>());
+                } else {
+                    add_clip_rect({ bounding_rect.release_value() });
+                }
+            }
             continue;
         }
 

+ 2 - 0
Tests/LibWeb/Ref/expected/clip-offscreen-ref.html

@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+(This space intentionally left blank)

+ 57 - 0
Tests/LibWeb/Ref/input/clip-offscreen.html

@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="match" href="../expected/clip-offscreen-ref.html" />
+<style>
+    .wrapper {
+        border: 1px solid black;
+        height: 50px;
+        position: absolute;
+        top: -100px;
+        overflow: auto;
+    }
+</style>
+<div class="wrapper">
+    <div>
+        01) Overflow<br/>
+        02) Overflow<br/>
+        03) Overflow<br/>
+        04) Overflow<br/>
+        05) Overflow<br/>
+        06) Overflow<br/>
+        07) Overflow<br/>
+        08) Overflow<br/>
+        09) Overflow<br/>
+        10) Overflow<br/>
+    </div>
+</div>
+
+<div class="wrapper" style="left: 200px; border-radius: 10px">
+    <div>
+        01) Border-radius overflow<br/>
+        02) Border-radius overflow<br/>
+        03) Border-radius overflow<br/>
+        04) Border-radius overflow<br/>
+        05) Border-radius overflow<br/>
+        06) Border-radius overflow<br/>
+        07) Border-radius overflow<br/>
+        08) Border-radius overflow<br/>
+        09) Border-radius overflow<br/>
+        10) Border-radius overflow<br/>
+    </div>
+</div>
+
+<div class="wrapper" style="left: 400px; clip-path: polygon(65px 0px, 35px 80px, 105px 30px, 25px 30px, 95px 80px)">
+    <div>
+        01) Masked overflow<br/>
+        02) Masked overflow<br/>
+        03) Masked overflow<br/>
+        04) Masked overflow<br/>
+        05) Masked overflow<br/>
+        06) Masked overflow<br/>
+        07) Masked overflow<br/>
+        08) Masked overflow<br/>
+        09) Masked overflow<br/>
+        10) Masked overflow<br/>
+    </div>
+</div>
+
+(This space intentionally left blank)