blob: b068340c27834744506bdd6ccc0a322cfcfb240f [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/paint/paint_layer_clipper.h"
#include "build/build_config.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/web_test_support.h"
namespace blink {
class PaintLayerClipperTest : public RenderingTest {
public:
PaintLayerClipperTest() : RenderingTest(EmptyLocalFrameClient::Create()) {}
void SetUp() override {
WebTestSupport::SetMockThemeEnabledForTest(true);
RenderingTest::SetUp();
}
void TearDown() override {
WebTestSupport::SetMockThemeEnabledForTest(false);
RenderingTest::TearDown();
}
};
TEST_F(PaintLayerClipperTest, ParentBackgroundClipRectSubpixelAccumulation) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<div style="overflow: hidden; width: 300px;">
<div id=target style='position: relative; width: 200px; height: 300px'>
</div>
)HTML");
Element* target = GetDocument().getElementById("target");
PaintLayer* target_paint_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
ClipRectsContext context(
GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip,
LayoutSize(FloatSize(0.25, 0.35)));
ClipRect background_rect_gm;
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateBackgroundClipRect(context, background_rect_gm);
EXPECT_EQ(LayoutRect(FloatRect(8.25, 8.34375, 300, 300)),
background_rect_gm.Rect());
ClipRect background_rect_nogm;
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateBackgroundClipRect(context, background_rect_nogm);
EXPECT_EQ(LayoutRect(FloatRect(8.25, 8.34375, 300, 300)),
background_rect_nogm.Rect());
}
TEST_F(PaintLayerClipperTest, BackgroundClipRectSubpixelAccumulation) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<div id=target width=200 height=300 style='position: relative'>
)HTML");
Element* target = GetDocument().getElementById("target");
PaintLayer* target_paint_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
ClipRectsContext context(
GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip,
LayoutSize(FloatSize(0.25, 0.35)));
ClipRect background_rect_gm;
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateBackgroundClipRect(context, background_rect_gm);
EXPECT_GE(background_rect_gm.Rect().Size().Width().ToInt(), 33554422);
EXPECT_GE(background_rect_gm.Rect().Size().Height().ToInt(), 33554422);
ClipRect background_rect_nogm;
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateBackgroundClipRect(context, background_rect_nogm);
EXPECT_GE(background_rect_nogm.Rect().Size().Width().ToInt(), 33554422);
EXPECT_GE(background_rect_nogm.Rect().Size().Height().ToInt(), 33554422);
}
TEST_F(PaintLayerClipperTest, SVGBackgroundClipRectSubpixelAccumulation) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<svg id=target width=200 height=300 style='position: relative'>
<rect width=400 height=500 fill='blue'/>
</svg>
)HTML");
Element* target = GetDocument().getElementById("target");
PaintLayer* target_paint_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
ClipRectsContext context(
GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip,
LayoutSize(FloatSize(0.25, 0.35)));
ClipRect background_rect_gm;
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateBackgroundClipRect(context, background_rect_gm);
EXPECT_GE(background_rect_gm.Rect().Size().Width().ToInt(), 33554422);
EXPECT_GE(background_rect_gm.Rect().Size().Height().ToInt(), 33554422);
ClipRect background_rect_nogm;
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateBackgroundClipRect(context, background_rect_nogm);
EXPECT_GE(background_rect_nogm.Rect().Size().Width().ToInt(), 33554422);
EXPECT_GE(background_rect_nogm.Rect().Size().Height().ToInt(), 33554422);
}
TEST_F(PaintLayerClipperTest, LayoutSVGRoot) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<svg id=target width=200 height=300 style='position: relative'>
<rect width=400 height=500 fill='blue'/>
</svg>
)HTML");
Element* target = GetDocument().getElementById("target");
PaintLayer* target_paint_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
// When RLS is enabled, the LayoutView will have a composited scrolling layer,
// so don't apply an overflow clip.
ClipRectsContext context(
GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip,
LayoutSize(FloatSize(0.25, 0.35)));
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context,
&target_paint_layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_EQ(LayoutRect(FloatRect(8.25, 8.35, 200, 300)),
background_rect.Rect());
EXPECT_EQ(LayoutRect(FloatRect(8.25, 8.35, 200, 300)),
foreground_rect.Rect());
EXPECT_EQ(LayoutRect(FloatRect(8.25, 8.35, 200, 300)), layer_bounds);
}
TEST_F(PaintLayerClipperTest, ControlClip) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<input id=target style='position:absolute; width: 200px; height: 300px'
type=button>
)HTML");
Element* target = GetDocument().getElementById("target");
PaintLayer* target_paint_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
// When RLS is enabled, the LayoutView will have a composited scrolling layer,
// so don't apply an overflow clip.
ClipRectsContext context(
GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip);
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context,
&target_paint_layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
#if defined(OS_MACOSX)
// If the PaintLayer clips overflow, the background rect is intersected with
// the PaintLayer bounds...
EXPECT_EQ(LayoutRect(3, 4, 210, 28), background_rect.Rect());
// and the foreground rect is intersected with the control clip in this case.
EXPECT_EQ(LayoutRect(8, 8, 200, 18), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(8, 8, 200, 18), layer_bounds);
#else
// If the PaintLayer clips overflow, the background rect is intersected with
// the PaintLayer bounds...
EXPECT_EQ(LayoutRect(8, 8, 200, 300), background_rect.Rect());
// and the foreground rect is intersected with the control clip in this case.
EXPECT_EQ(LayoutRect(10, 10, 196, 296), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(8, 8, 200, 300), layer_bounds);
#endif
}
TEST_F(PaintLayerClipperTest, RoundedClip) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<div id='target' style='position:absolute; width: 200px; height: 300px;
overflow: hidden; border-radius: 1px'>
</div>
)HTML");
Element* target = GetDocument().getElementById("target");
PaintLayer* target_paint_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
ClipRectsContext context(
GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip);
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context,
&target_paint_layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
// Only the foreground rect gets hasRadius set for overflow clipping
// of descendants.
EXPECT_EQ(LayoutRect(8, 8, 200, 300), background_rect.Rect());
EXPECT_FALSE(background_rect.HasRadius());
EXPECT_EQ(LayoutRect(8, 8, 200, 300), foreground_rect.Rect());
EXPECT_TRUE(foreground_rect.HasRadius());
EXPECT_EQ(LayoutRect(8, 8, 200, 300), layer_bounds);
}
TEST_F(PaintLayerClipperTest, RoundedClipNested) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<div id='parent' style='position:absolute; width: 200px; height: 300px;
overflow: hidden; border-radius: 1px'>
<div id='child' style='position: relative; width: 500px;
height: 500px'>
</div>
</div>
)HTML");
Element* parent = GetDocument().getElementById("parent");
PaintLayer* parent_paint_layer =
ToLayoutBoxModelObject(parent->GetLayoutObject())->Layer();
Element* child = GetDocument().getElementById("child");
PaintLayer* child_paint_layer =
ToLayoutBoxModelObject(child->GetLayoutObject())->Layer();
ClipRectsContext context(
parent_paint_layer,
&parent_paint_layer->GetLayoutObject().FirstFragment(),
kUncachedClipRects);
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
child_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context,
&child_paint_layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_EQ(LayoutRect(0, 0, 200, 300), background_rect.Rect());
EXPECT_TRUE(background_rect.HasRadius());
EXPECT_EQ(LayoutRect(0, 0, 200, 300), foreground_rect.Rect());
EXPECT_TRUE(foreground_rect.HasRadius());
EXPECT_EQ(LayoutRect(0, 0, 500, 500), layer_bounds);
}
// TODO(https://crbug.com/795645): This test is failing on the ChromeOS bot.
#if defined(OS_CHROMEOS)
TEST_F(PaintLayerClipperTest, DISABLED_ControlClipSelect) {
#else
TEST_F(PaintLayerClipperTest, ControlClipSelect) {
#endif
SetBodyInnerHTML(R"HTML(
<select id='target' style='position: relative; width: 100px;
background: none; border: none; padding: 0px 15px 0px 5px;'>
<option>
Test long texttttttttttttttttttttttttttttttt
</option>
</select>
)HTML");
Element* target = GetDocument().getElementById("target");
PaintLayer* target_paint_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
ClipRectsContext context(
GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip);
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context,
&target_paint_layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
// The control clip for a select excludes the area for the down arrow.
#if defined(OS_MACOSX)
EXPECT_EQ(LayoutRect(16, 9, 79, 13), foreground_rect.Rect());
#elif defined(OS_WIN)
EXPECT_EQ(LayoutRect(17, 9, 60, 16), foreground_rect.Rect());
#else
EXPECT_EQ(LayoutRect(17, 9, 60, 15), foreground_rect.Rect());
#endif
}
TEST_F(PaintLayerClipperTest, LayoutSVGRootChild) {
SetBodyInnerHTML(R"HTML(
<svg width=200 height=300 style='position: relative'>
<foreignObject width=400 height=500>
<div id=target xmlns='http://www.w3.org/1999/xhtml'
style='position: relative'></div>
</foreignObject>
</svg>
)HTML");
Element* target = GetDocument().getElementById("target");
PaintLayer* target_paint_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
ClipRectsContext context(GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(),
kUncachedClipRects);
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context,
&target_paint_layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_EQ(LayoutRect(8, 8, 200, 300), background_rect.Rect());
EXPECT_EQ(LayoutRect(8, 8, 200, 300), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(8, 8, 400, 0), layer_bounds);
}
TEST_F(PaintLayerClipperTest, ContainPaintClip) {
SetBodyInnerHTML(R"HTML(
<div id='target'
style='contain: paint; width: 200px; height: 200px; overflow: auto'>
<div style='height: 400px'></div>
</div>
)HTML");
PaintLayer* layer =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
ClipRectsContext context(layer, &layer->GetLayoutObject().FirstFragment(),
kPaintingClipRectsIgnoringOverflowClip,
kIgnorePlatformOverlayScrollbarSize,
kIgnoreOverflowClip);
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
layer->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context, &layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_GE(background_rect.Rect().Size().Width().ToInt(), 33554422);
EXPECT_GE(background_rect.Rect().Size().Height().ToInt(), 33554422);
EXPECT_EQ(background_rect.Rect(), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 200, 200), layer_bounds);
ClipRectsContext context_clip(
layer, &layer->GetLayoutObject().FirstFragment(), kUncachedClipRects);
layer->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context_clip, &layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_EQ(LayoutRect(0, 0, 200, 200), background_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 200, 200), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 200, 200), layer_bounds);
}
TEST_F(PaintLayerClipperTest, NestedContainPaintClip) {
SetBodyInnerHTML(R"HTML(
<div style='contain: paint; width: 200px; height: 200px; overflow:
auto'>
<div id='target' style='contain: paint; height: 400px'>
</div>
</div>
)HTML");
PaintLayer* layer =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
ClipRectsContext context(
layer->Parent(), &layer->Parent()->GetLayoutObject().FirstFragment(),
kPaintingClipRectsIgnoringOverflowClip,
kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip);
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
layer->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context, &layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_EQ(LayoutRect(0, 0, 200, 400), background_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 200, 400), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 200, 400), layer_bounds);
ClipRectsContext context_clip(
layer->Parent(), &layer->Parent()->GetLayoutObject().FirstFragment(),
kUncachedClipRects);
layer->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context_clip, &layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_EQ(LayoutRect(0, 0, 200, 200), background_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 200, 200), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 200, 400), layer_bounds);
}
TEST_F(PaintLayerClipperTest, LocalClipRectFixedUnderTransform) {
SetBodyInnerHTML(R"HTML(
<div id='transformed'
style='will-change: transform; width: 100px; height: 100px;
overflow: hidden'>
<div id='fixed'
style='position: fixed; width: 100px; height: 100px;
top: -50px'>
</div>
</div>
)HTML");
PaintLayer* transformed =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("transformed"))
->Layer();
PaintLayer* fixed =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("fixed"))->Layer();
EXPECT_EQ(
LayoutRect(0, 0, 100, 100),
transformed->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.LocalClipRect(*transformed));
EXPECT_EQ(LayoutRect(0, 50, 100, 100),
fixed->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.LocalClipRect(*transformed));
}
TEST_F(PaintLayerClipperTest, ClearClipRectsRecursive) {
// CAP will re-use a global GeometryMapper, so this
// logic does not apply.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
SetBodyInnerHTML(R"HTML(
<style>
div {
width: 5px; height: 5px; background: blue; overflow: hidden;
position: relative;
}
</style>
<div id='parent'>
<div id='child'>
<div id='grandchild'></div>
</div>
</div>
)HTML");
PaintLayer* parent =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("parent"))->Layer();
PaintLayer* child =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("child"))->Layer();
EXPECT_TRUE(parent->GetClipRectsCache());
EXPECT_TRUE(child->GetClipRectsCache());
parent->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.ClearClipRectsIncludingDescendants();
EXPECT_FALSE(parent->GetClipRectsCache());
EXPECT_FALSE(child->GetClipRectsCache());
}
TEST_F(PaintLayerClipperTest, ClearClipRectsRecursiveChild) {
// CAP will re-use a global GeometryMapper, so this
// logic does not apply.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
SetBodyInnerHTML(R"HTML(
<style>
div {
width: 5px; height: 5px; background: blue;
position: relative;
}
</style>
<div id='parent'>
<div id='child'>
<div id='grandchild'></div>
</div>
</div>
)HTML");
PaintLayer* parent =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("parent"))->Layer();
PaintLayer* child =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("child"))->Layer();
EXPECT_TRUE(parent->GetClipRectsCache());
EXPECT_TRUE(child->GetClipRectsCache());
child->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.ClearClipRectsIncludingDescendants();
EXPECT_TRUE(parent->GetClipRectsCache());
EXPECT_FALSE(child->GetClipRectsCache());
}
TEST_F(PaintLayerClipperTest, CSSClip) {
SetBodyInnerHTML(R"HTML(
<style>
#target {
width: 400px; height: 400px; position: absolute;
clip: rect(0, 50px, 100px, 0);
clip-path: inset(0%);
}
</style>
<div id='target'></div>
)HTML");
PaintLayer* target =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
ClipRectsContext context(target, &target->GetLayoutObject().FirstFragment(),
kUncachedClipRects);
LayoutRect infinite_rect(LayoutRect::InfiniteIntRect());
LayoutRect layer_bounds(infinite_rect);
ClipRect background_rect(infinite_rect);
ClipRect foreground_rect(infinite_rect);
target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context, &target->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_EQ(LayoutRect(0, 0, 50, 100), background_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 50, 100), foreground_rect.Rect());
}
TEST_F(PaintLayerClipperTest, Filter) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0 }
#target {
filter: drop-shadow(0 3px 4px #333); overflow: hidden;
width: 100px; height: 200px; border: 40px solid blue; margin: 50px;
}
</style>
<div id='target'></div>
)HTML");
PaintLayer* target =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
// First test clip rects in the target layer itself.
ClipRectsContext context(target, &target->GetLayoutObject().FirstFragment(),
kUncachedClipRects);
LayoutRect infinite_rect(LayoutRect::InfiniteIntRect());
LayoutRect layer_bounds(infinite_rect);
ClipRect background_rect(infinite_rect);
ClipRect foreground_rect(infinite_rect);
target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context, &target->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
// The background rect is used to clip stacking context (layer) output.
// In this case, nothing is above us, thus the infinite rect. However we do
// clip to the layer's after-filter visual rect as an optimization.
EXPECT_EQ(LayoutRect(-12, -9, 204, 304), background_rect.Rect());
// The foreground rect is used to clip the normal flow contents of the
// stacking context (layer) thus including the overflow clip.
EXPECT_EQ(LayoutRect(40, 40, 100, 200), foreground_rect.Rect());
// Test without GeometryMapper.
background_rect = infinite_rect;
foreground_rect = infinite_rect;
target->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateRects(context, nullptr, nullptr, layer_bounds, background_rect,
foreground_rect);
// The non-GeometryMapper path applies the immediate filter effect in
// background rect.
EXPECT_EQ(LayoutRect(-12, -9, 204, 304), background_rect.Rect());
EXPECT_EQ(LayoutRect(40, 40, 100, 200), foreground_rect.Rect());
// Test mapping to the root layer.
ClipRectsContext root_context(GetLayoutView().Layer(),
&GetLayoutView().FirstFragment(),
kUncachedClipRects);
background_rect = infinite_rect;
foreground_rect = infinite_rect;
target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(root_context, &target->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
// This includes the filter effect because it's applied before mapping the
// background rect to the root layer.
EXPECT_EQ(LayoutRect(38, 41, 204, 304), background_rect.Rect());
EXPECT_EQ(LayoutRect(90, 90, 100, 200), foreground_rect.Rect());
// Test mapping to the root layer without GeometryMapper.
background_rect = infinite_rect;
foreground_rect = infinite_rect;
target->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateRects(root_context, nullptr, nullptr, layer_bounds,
background_rect, foreground_rect);
EXPECT_EQ(LayoutRect(38, 41, 204, 304), background_rect.Rect());
EXPECT_EQ(LayoutRect(90, 90, 100, 200), foreground_rect.Rect());
}
// Computed infinite clip rects may not match LayoutRect::InfiniteIntRect()
// due to floating point errors.
static bool IsInfinite(const LayoutRect& rect) {
return rect.X().Round() < -10000000 && rect.MaxX().Round() > 10000000
&& rect.Y().Round() < -10000000 && rect.MaxY().Round() > 10000000;
}
TEST_F(PaintLayerClipperTest, IgnoreRootLayerClipWithCSSClip) {
SetBodyInnerHTML(R"HTML(
<style>
#root {
width: 400px; height: 400px;
position: absolute; clip: rect(0, 50px, 100px, 0);
}
#target {
position: relative;
}
</style>
<div id='root'>
<div id='target'></div>
</div>
)HTML");
PaintLayer* root =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("root"))->Layer();
PaintLayer* target =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
ClipRectsContext context(root, &root->GetLayoutObject().FirstFragment(),
kPaintingClipRectsIgnoringOverflowClip,
kIgnorePlatformOverlayScrollbarSize,
kIgnoreOverflowClip);
LayoutRect infinite_rect(LayoutRect::InfiniteIntRect());
LayoutRect layer_bounds(infinite_rect);
ClipRect background_rect(infinite_rect);
ClipRect foreground_rect(infinite_rect);
target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context, &target->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_TRUE(IsInfinite(background_rect.Rect()));
EXPECT_TRUE(IsInfinite(foreground_rect.Rect()));
}
TEST_F(PaintLayerClipperTest, IgnoreRootLayerClipWithOverflowClip) {
SetBodyInnerHTML(R"HTML(
<style>
#root {
width: 400px; height: 400px;
overflow: hidden;
}
#target {
position: relative;
}
</style>
<div id='root'>
<div id='target'></div>
</div>
)HTML");
PaintLayer* root =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("root"))->Layer();
PaintLayer* target =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
ClipRectsContext context(root, &root->GetLayoutObject().FirstFragment(),
kPaintingClipRectsIgnoringOverflowClip,
kIgnorePlatformOverlayScrollbarSize,
kIgnoreOverflowClip);
LayoutRect infinite_rect(LayoutRect::InfiniteIntRect());
LayoutRect layer_bounds(infinite_rect);
ClipRect background_rect(infinite_rect);
ClipRect foreground_rect(infinite_rect);
target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context, &target->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_TRUE(IsInfinite(background_rect.Rect()));
EXPECT_TRUE(IsInfinite(foreground_rect.Rect()));
}
TEST_F(PaintLayerClipperTest, IgnoreRootLayerClipWithBothClip) {
SetBodyInnerHTML(R"HTML(
<style>
#root {
width: 400px; height: 400px;
position: absolute; clip: rect(0, 50px, 100px, 0);
overflow: hidden;
}
#target {
position: relative;
}
</style>
<div id='root'>
<div id='target'></div>
</div>
)HTML");
PaintLayer* root =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("root"))->Layer();
PaintLayer* target =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
ClipRectsContext context(root, &root->GetLayoutObject().FirstFragment(),
kPaintingClipRectsIgnoringOverflowClip,
kIgnorePlatformOverlayScrollbarSize,
kIgnoreOverflowClip);
LayoutRect infinite_rect(LayoutRect::InfiniteIntRect());
LayoutRect layer_bounds(infinite_rect);
ClipRect background_rect(infinite_rect);
ClipRect foreground_rect(infinite_rect);
target->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context, &target->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_TRUE(IsInfinite(background_rect.Rect()));
EXPECT_TRUE(IsInfinite(foreground_rect.Rect()));
}
TEST_F(PaintLayerClipperTest, Fragmentation) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<div id=root style='width: 200px; height: 100px; columns: 2;
column-gap: 0'>
<div id=target style='width: 100px; height: 200px;
background: lightblue; position: relative'>
</div
</div>
)HTML");
Element* root = GetDocument().getElementById("root");
PaintLayer* root_paint_layer =
ToLayoutBoxModelObject(root->GetLayoutObject())->Layer();
ClipRectsContext context(
root_paint_layer, &root_paint_layer->GetLayoutObject().FirstFragment(),
kUncachedClipRects, kIgnorePlatformOverlayScrollbarSize);
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
Element* target = GetDocument().getElementById("target");
PaintLayer* target_paint_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
EXPECT_TRUE(
target_paint_layer->GetLayoutObject().FirstFragment().NextFragment());
EXPECT_FALSE(target_paint_layer->GetLayoutObject()
.FirstFragment()
.NextFragment()
->NextFragment());
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context,
&target_paint_layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_EQ(LayoutRect(FloatRect(-1.0e6, -1.0e6, 2.0000e6, 1.0001e6)),
background_rect.Rect());
EXPECT_EQ(LayoutRect(FloatRect(-1.0e6, -1.0e6, 2.0000e6, 1.0001e6)),
foreground_rect.Rect());
EXPECT_EQ(LayoutRect(FloatRect(0, 0, 100, 200)), layer_bounds);
target_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(
context,
target_paint_layer->GetLayoutObject().FirstFragment().NextFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
EXPECT_EQ(LayoutRect(FloatRect(-999900, 0, 2000000, 999900)),
background_rect.Rect());
EXPECT_EQ(LayoutRect(FloatRect(-999900, 0, 2000000, 999900)),
foreground_rect.Rect());
// Layer bounds adjusted for pagination offset of second fragment.
EXPECT_EQ(LayoutRect(FloatRect(100, -100, 100, 200)), layer_bounds);
}
TEST_F(PaintLayerClipperTest, ScrollbarClipBehaviorChild) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<div id='parent' style='position:absolute; width: 200px; height: 300px;
overflow: scroll;'>
<div id='child' style='position: relative; width: 500px;
height: 500px'>
</div>
</div>
)HTML");
Element* parent = GetDocument().getElementById("parent");
PaintLayer* parent_paint_layer =
ToLayoutBoxModelObject(parent->GetLayoutObject())->Layer();
Element* child = GetDocument().getElementById("child");
PaintLayer* child_paint_layer =
ToLayoutBoxModelObject(child->GetLayoutObject())->Layer();
ClipRectsContext context(
parent_paint_layer,
&parent_paint_layer->GetLayoutObject().FirstFragment(),
kUncachedClipRects, kExcludeOverlayScrollbarSizeForHitTesting);
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
child_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context,
&child_paint_layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
// The background and foreground rect are clipped by the scrollbar size.
EXPECT_EQ(LayoutRect(0, 0, 193, 293), background_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 193, 293), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 500, 500), layer_bounds);
child_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateRects(context, nullptr, nullptr, layer_bounds, background_rect,
foreground_rect);
// The background and foreground rect are clipped by the scrollbar size.
EXPECT_EQ(LayoutRect(0, 0, 193, 293), background_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 193, 293), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 500, 500), layer_bounds);
}
TEST_F(PaintLayerClipperTest, ScrollbarClipBehaviorChildScrollBetween) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<div id='parent' style='position:absolute; width: 200px; height: 300px;
overflow: scroll;'>
<div id='child' style='position: relative; width: 500px;
height: 500px'>
</div>
</div>
)HTML");
Element* parent = GetDocument().getElementById("parent");
PaintLayer* root_paint_layer = parent->GetLayoutObject()->View()->Layer();
Element* child = GetDocument().getElementById("child");
PaintLayer* child_paint_layer =
ToLayoutBoxModelObject(child->GetLayoutObject())->Layer();
ClipRectsContext context(
root_paint_layer, &root_paint_layer->GetLayoutObject().FirstFragment(),
kUncachedClipRects, kExcludeOverlayScrollbarSizeForHitTesting);
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
child_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context,
&child_paint_layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
// The background and foreground rect are clipped by the scrollbar size.
EXPECT_EQ(LayoutRect(8, 8, 193, 293), background_rect.Rect());
EXPECT_EQ(LayoutRect(8, 8, 193, 293), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(8, 8, 500, 500), layer_bounds);
child_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateRects(context, nullptr, nullptr, layer_bounds, background_rect,
foreground_rect);
// The background and foreground rect are clipped by the scrollbar size.
EXPECT_EQ(LayoutRect(8, 8, 193, 293), background_rect.Rect());
EXPECT_EQ(LayoutRect(8, 8, 193, 293), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(8, 8, 500, 500), layer_bounds);
}
TEST_F(PaintLayerClipperTest, ScrollbarClipBehaviorParent) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<div id='parent' style='position:absolute; width: 200px; height: 300px;
overflow: scroll;'>
<div id='child' style='position: relative; width: 500px;
height: 500px'>
</div>
</div>
)HTML");
Element* parent = GetDocument().getElementById("parent");
PaintLayer* parent_paint_layer =
ToLayoutBoxModelObject(parent->GetLayoutObject())->Layer();
ClipRectsContext context(
parent_paint_layer,
&parent_paint_layer->GetLayoutObject().FirstFragment(),
kUncachedClipRects, kExcludeOverlayScrollbarSizeForHitTesting);
LayoutRect layer_bounds;
ClipRect background_rect, foreground_rect;
parent_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kUseGeometryMapper)
.CalculateRects(context,
&parent_paint_layer->GetLayoutObject().FirstFragment(),
nullptr, layer_bounds, background_rect, foreground_rect);
// Only the foreground is clipped by the scrollbar size, because we
// called CalculateRects on the root layer.
EXPECT_EQ(LayoutRect(0, 0, 200, 300), background_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 193, 293), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 200, 300), layer_bounds);
parent_paint_layer
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateRects(context, nullptr, nullptr, layer_bounds, background_rect,
foreground_rect);
// Only the foreground is clipped by the scrollbar size, because we
// called CalculateRects on the root layer.
EXPECT_EQ(LayoutRect(0, 0, 200, 300), background_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 193, 293), foreground_rect.Rect());
EXPECT_EQ(LayoutRect(0, 0, 200, 300), layer_bounds);
}
TEST_F(PaintLayerClipperTest, FixedLayerClipRectInDocumentSpace) {
SetBodyInnerHTML(R"HTML(
<div style="position:fixed; left:100px; top:200px; width:300px; height:400px; overflow:hidden;">
<div id="target" style="position:relative;"></div>
</div>
<div style="height:3000px;"></div>
)HTML");
Element* target = GetDocument().getElementById("target");
PaintLayer* target_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
GetDocument().domWindow()->scrollTo(0, 50);
GetDocument()
.GetLayoutView()
->Layer()
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.ClearClipRectsIncludingDescendants();
{
ClipRect clip_rect;
target_layer
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateBackgroundClipRect(
ClipRectsContext(GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(),
kAbsoluteClipRectsIgnoringViewportClip,
kIgnorePlatformOverlayScrollbarSize,
kIgnoreOverflowClipAndScroll),
clip_rect);
EXPECT_EQ(LayoutRect(100, 250, 300, 400), clip_rect.Rect());
}
GetDocument().domWindow()->scrollTo(0, 100);
{
ClipRect clip_rect;
target_layer
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateBackgroundClipRect(
ClipRectsContext(GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(),
kAbsoluteClipRectsIgnoringViewportClip,
kIgnorePlatformOverlayScrollbarSize,
kIgnoreOverflowClipAndScroll),
clip_rect);
EXPECT_EQ(LayoutRect(100, 300, 300, 400), clip_rect.Rect());
}
}
TEST_F(PaintLayerClipperTest,
FixedLayerClipRectInDocumentSpaceWithNestedScroller) {
SetBodyInnerHTML(R"HTML(
<div style="position:fixed; left:100px; top:200px; width:300px; height:400px; overflow:scroll;">
<div style="width:200px; height:300px; overflow:hidden;">
<div id="target" style="position:relative;"></div>
</div>
<div style="height:3000px;"></div>
</div>
<div style="height:3000px;"></div>
)HTML");
Element* target = GetDocument().getElementById("target");
PaintLayer* target_layer =
ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
GetDocument().domWindow()->scrollTo(0, 50);
GetDocument()
.GetLayoutView()
->Layer()
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.ClearClipRectsIncludingDescendants();
{
ClipRect clip_rect;
target_layer
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateBackgroundClipRect(
ClipRectsContext(GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(),
kAbsoluteClipRectsIgnoringViewportClip,
kIgnorePlatformOverlayScrollbarSize,
kIgnoreOverflowClipAndScroll),
clip_rect);
EXPECT_EQ(LayoutRect(100, 250, 200, 300), clip_rect.Rect());
}
GetDocument().domWindow()->scrollTo(0, 100);
{
ClipRect clip_rect;
target_layer
->Clipper(PaintLayer::GeometryMapperOption::kDoNotUseGeometryMapper)
.CalculateBackgroundClipRect(
ClipRectsContext(GetDocument().GetLayoutView()->Layer(),
&GetDocument().GetLayoutView()->FirstFragment(),
kAbsoluteClipRectsIgnoringViewportClip,
kIgnorePlatformOverlayScrollbarSize,
kIgnoreOverflowClipAndScroll),
clip_rect);
EXPECT_EQ(LayoutRect(100, 300, 200, 300), clip_rect.Rect());
}
}
} // namespace blink