LibWeb: Don't clip descendants outside stacking context root rect

Skia allows you to pass a bounding rect to its saveLayer() function as
an optimization when you know that you won't paint outside those bounds.
Unfortunately, we were passing a too-small rectangle that didn't take
into account transformed descendants, etc.

For now, simply pass null instead of a bounding rect. This way, Skia
figures it out internally. It may allocate larger temporary bitmaps than
needed this way, but at least we get more correct results. I've left
re-enabling the optimization as a FIXME in the code.

This fixes unwanted clipping in various parts of the Discord UI.
This commit is contained in:
Andreas Kling 2025-07-24 12:36:47 +02:00 committed by Alexander Kalenik
commit b4435bd50c
Notes: github-actions[bot] 2025-07-24 15:17:25 +00:00
7 changed files with 29 additions and 11 deletions

View file

@ -130,15 +130,12 @@ struct PushStackingContext {
float opacity;
Gfx::CompositingAndBlendingOperator compositing_and_blending_operator;
bool isolate;
// The bounding box of the source paintable (pre-transform).
Gfx::IntRect source_paintable_rect;
// A translation to be applied after the stacking context has been transformed.
StackingContextTransform transform;
Optional<Gfx::Path> clip_path = {};
void translate_by(Gfx::IntPoint const& offset)
{
source_paintable_rect.translate_by(offset);
transform.origin.translate_by(offset.to_type<float>());
if (clip_path.has_value()) {
clip_path.value().transform(Gfx::AffineTransform().translate(offset.to_type<float>()));

View file

@ -211,14 +211,13 @@ void DisplayListPlayerSkia::push_stacking_context(PushStackingContext const& com
auto matrix = to_skia_matrix(new_transform);
if (command.opacity < 1 || command.compositing_and_blending_operator != Gfx::CompositingAndBlendingOperator::Normal || command.isolate) {
auto source_paintable_rect = to_skia_rect(command.source_paintable_rect);
SkRect dest;
matrix.mapRect(&dest, source_paintable_rect);
SkPaint paint;
paint.setAlphaf(command.opacity);
paint.setBlender(Gfx::to_skia_blender(command.compositing_and_blending_operator));
canvas.saveLayer(&dest, &paint);
// FIXME: If we knew the bounds of the stacking context including any transformed descendants etc,
// we could use saveLayer with a bounds rect. For now, we pass nullptr and let Skia figure it out.
canvas.saveLayer(nullptr, &paint);
} else {
canvas.save();
}

View file

@ -330,7 +330,6 @@ void DisplayListRecorder::push_stacking_context(PushStackingContextParams params
.opacity = params.opacity,
.compositing_and_blending_operator = params.compositing_and_blending_operator,
.isolate = params.isolate,
.source_paintable_rect = params.source_paintable_rect,
.transform = {
.origin = params.transform.origin,
.matrix = params.transform.matrix,

View file

@ -124,7 +124,6 @@ public:
Gfx::CompositingAndBlendingOperator compositing_and_blending_operator;
bool isolate;
bool is_fixed_position;
Gfx::IntRect source_paintable_rect;
StackingContextTransform transform;
Optional<Gfx::Path> clip_path = {};
};

View file

@ -324,7 +324,6 @@ void StackingContext::paint(PaintContext& context) const
.compositing_and_blending_operator = compositing_and_blending_operator,
.isolate = paintable_box().computed_values().isolation() == CSS::Isolation::Isolate,
.is_fixed_position = paintable_box().is_fixed_position(),
.source_paintable_rect = source_paintable_rect,
.transform = {
.origin = transform_origin.scaled(to_device_pixels_scale),
.matrix = matrix_with_scaled_translation(transform_matrix, to_device_pixels_scale),

View file

@ -0,0 +1,11 @@
<!doctype html><style>
html {
background-color: white;
}
body {
background-color: red;
width: 60px;
height: 60px;
translate: 30px 30px;
}
</style><body>

View file

@ -0,0 +1,14 @@
<!doctype html>
<link rel="match" href="../../expected/css/stacking-context-with-unclipped-transformed-descendant-ref.html" />
<style>
html {
isolation: isolate;
background-color: white;
}
body {
background-color: red;
width: 60px;
height: 60px;
translate: 30px 30px;
}
</style><body>