blob: b9b9f148b76a37e2a0f0c4096a335132779d3193 [file] [log] [blame]
// Copyright 2014 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/compositing/composited_layer_mapping.h"
#include "cc/layers/layer.h"
#include "cc/layers/picture_layer.h"
#include "cc/trees/layer_tree_host.h"
#include "cc/trees/property_tree.h"
#include "cc/trees/scroll_node.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
#include "third_party/blink/renderer/core/layout/layout_image.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/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
#include "third_party/blink/renderer/platform/testing/find_cc_layer.h"
#include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
namespace blink {
// TODO(wangxianzhu): Though these tests don't directly apply in
// CompositeAfterPaint, we should ensure the cases are tested in
// CompositeAfterPaint mode if applicable. Some interest rect / cull rect
// tests have been migrated for CompositeAfterPaint into
// PaintLayerPainterTestCAP.
class CompositedLayerMappingTest : public RenderingTest,
public PaintTestConfigurations {
public:
CompositedLayerMappingTest()
: RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()) {}
protected:
bool InterestRectChangedEnoughToRepaint(const IntRect& previous_interest_rect,
const IntRect& new_interest_rect,
const IntSize& layer_size) {
return CompositedLayerMapping::InterestRectChangedEnoughToRepaint(
previous_interest_rect, new_interest_rect, layer_size);
}
gfx::Rect PaintableRegion(const GraphicsLayer* graphics_layer) {
return graphics_layer->PaintableRegion();
}
static const GraphicsLayerPaintInfo* GetSquashedLayer(
const Vector<GraphicsLayerPaintInfo>& squashed_layers,
const PaintLayer& layer) {
for (const auto& squashed_layer : squashed_layers) {
if (squashed_layer.paint_layer == &layer)
return &squashed_layer;
}
return nullptr;
}
const GraphicsLayerPaintInfo* GetNonScrollingSquashedLayer(
const CompositedLayerMapping& mapping,
const PaintLayer& layer) {
return GetSquashedLayer(mapping.non_scrolling_squashed_layers_, layer);
}
const GraphicsLayerPaintInfo* GetSquashedLayerInScrollingContents(
const CompositedLayerMapping& mapping,
const PaintLayer& layer) {
return GetSquashedLayer(mapping.squashed_layers_in_scrolling_contents_,
layer);
}
private:
void SetUp() override {
EnableCompositing();
RenderingTest::SetUp();
}
};
INSTANTIATE_PRE_CAP_TEST_SUITE_P(CompositedLayerMappingTest);
TEST_P(CompositedLayerMappingTest, SubpixelAccumulationChange) {
SetBodyInnerHTML(R"HTML(
<div id='target' style='will-change: opacity; background: lightblue;
position: relative; left: 0.4px; width: 100px; height: 100px'>
<!-- This div would be snapped to a different pixel -->
<div style='position: relative; left: 0.3px; width: 50px; height: 50px;
background: green'></div>
</div>
)HTML");
GetDocument().View()->SetTracksRasterInvalidations(true);
Element* target = GetDocument().getElementById("target");
target->SetInlineStyleProperty(CSSPropertyID::kLeft, "0.6px");
UpdateAllLifecyclePhasesForTest();
// Directly composited layers are not invalidated on subpixel accumulation
// change.
EXPECT_TRUE(target->GetLayoutBox()
->Layer()
->GraphicsLayerBacking()
->GetRasterInvalidationTracking()
->Invalidations()
.IsEmpty());
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(CompositedLayerMappingTest,
SubpixelAccumulationChangeUnderInvalidation) {
ScopedPaintUnderInvalidationCheckingForTest test(true);
SetBodyInnerHTML(R"HTML(
<div id='target' style='will-change: opacity; background: lightblue;
position: relative; left: 0.4px; width: 100px; height: 100px'>
<!-- This div will be snapped to a different pixel -->
<div style='position: relative; left: 0.3px; width: 50px; height: 50px;
background: green'></div>
</div>
)HTML");
GetDocument().View()->SetTracksRasterInvalidations(true);
Element* target = GetDocument().getElementById("target");
target->SetInlineStyleProperty(CSSPropertyID::kLeft, "0.6px");
UpdateAllLifecyclePhasesForTest();
// Invalidate directly composited layers on subpixel accumulation change
// when PaintUnderInvalidationChecking is enabled.
EXPECT_FALSE(target->GetLayoutBox()
->Layer()
->GraphicsLayerBacking()
->GetRasterInvalidationTracking()
->Invalidations()
.IsEmpty());
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(CompositedLayerMappingTest,
SubpixelAccumulationChangeIndirectCompositing) {
SetBodyInnerHTML(R"HTML(
<style>
#target {
background: lightblue;
position: relative;
top: -10px;
left: 0.4px;
width: 100px;
height: 100px;
transform: translateX(0);
opacity: 0.4;
}
#child {
position; relative;
width: 100px;
height: 100px;
background: lightgray;
will-change: transform;
opacity: 0.6;
}
</style>
<div id='target'>
<div id='child'></div>
</div>
)HTML");
GetDocument().View()->SetTracksRasterInvalidations(true);
Element* target = GetDocument().getElementById("target");
target->SetInlineStyleProperty(CSSPropertyID::kLeft, "0.6px");
UpdateAllLifecyclePhasesForTest();
// Invalidate indirectly composited layers on subpixel accumulation change.
EXPECT_FALSE(target->GetLayoutBox()
->Layer()
->GraphicsLayerBacking()
->GetRasterInvalidationTracking()
->Invalidations()
.IsEmpty());
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(CompositedLayerMappingTest, SimpleInterestRect) {
SetBodyInnerHTML(R"HTML(
<div id='target' style='width: 200px; height: 200px;
will-change: transform; background: blue'>
</div>
)HTML");
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
ASSERT_TRUE(paint_layer->GetCompositedLayerMapping());
EXPECT_EQ(gfx::Rect(0, 0, 200, 200),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, TallLayerInterestRect) {
SetBodyInnerHTML(R"HTML(
<div id='target' style='width: 200px; height: 10000px;
will-change: transform; background: blue'>
</div>
)HTML");
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
// Screen-space visible content rect is [8, 8, 200, 600]. Mapping back to
// local, adding 4000px in all directions, then clipping, yields this rect.
EXPECT_EQ(gfx::Rect(0, 0, 200, 4592),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, TallCompositedScrolledLayerInterestRect) {
SetBodyInnerHTML(R"HTML(
<div style='width: 200px; height: 1000px;'></div>
<div id='target' style='width: 200px; height: 10000px;
will-change: transform; background: blue'>
</div>
)HTML");
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 8000), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
EXPECT_EQ(gfx::Rect(0, 2992, 200, 7008),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, TallNonCompositedScrolledLayerInterestRect) {
SetHtmlInnerHTML(R"HTML(
<div style='width: 200px; height: 11000px'></div>
)HTML");
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 8000), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetDocument().GetLayoutView()->Layer();
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
EXPECT_EQ(gfx::Rect(0, 4000, 800, 7016),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, TallLayerWholeDocumentInterestRect) {
SetBodyInnerHTML(R"HTML(
<div id='target' style='width: 200px; height: 10000px;
will-change: transform; background: blue'>
</div>
)HTML");
GetDocument().GetSettings()->SetMainFrameClipsContent(false);
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
ASSERT_TRUE(paint_layer->GetCompositedLayerMapping());
// Clipping is disabled.
EXPECT_EQ(gfx::Rect(0, 0, 200, 10000),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, VerticalRightLeftWritingModeDocument) {
SetBodyInnerHTML(R"HTML(
<style>html,body { margin: 0px } html { -webkit-writing-mode:
vertical-rl}</style> <div id='target' style='width: 10000px; height:
200px;'></div>
)HTML");
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(-5000, 0), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetDocument().GetLayoutView()->Layer();
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
ASSERT_TRUE(paint_layer->GetCompositedLayerMapping());
// A scroll by -5000px is equivalent to a scroll by (10000 - 5000 - 800)px =
// 4200px in non-RTL mode. Expanding the resulting rect by 4000px in each
// direction yields this result.
EXPECT_EQ(gfx::Rect(200, 0, 8800, 600),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, RotatedInterestRect) {
SetBodyInnerHTML(R"HTML(
<div id='target'
style='width: 200px; height: 200px; will-change: transform;
transform: rotateZ(45deg); background: blue'>
</div>
)HTML");
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
EXPECT_EQ(gfx::Rect(0, 0, 200, 200),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, RotatedInterestRectNear90Degrees) {
SetBodyInnerHTML(R"HTML(
<div id='target'
style='width: 10000px; height: 200px; will-change: transform;
transform-origin: 0 0; transform: rotateY(89.9999deg);
background: blue'>
</div>
)HTML");
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
EXPECT_EQ(gfx::Rect(0, 0, 10000, 200),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, LargeScaleInterestRect) {
SetBodyInnerHTML(R"HTML(
<style>
.container {
height: 1080px;
width: 1920px;
transform: scale(0.0859375);
transform-origin: 0 0 0;
background: blue;
will-change: transform;
}
.wrapper {
height: 92px;
width: 165px;
overflow: hidden;
}
.posabs {
position: absolute;
width: 300px;
height: 300px;
top: 5000px;
}
#target {
will-change: transform;
}
</style>
<div class='wrapper'>
<div id='target' class='container'>
<div class='posabs'></div>
<div id='target class='posabs'></div>
</div>
</div>
)HTML");
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
EXPECT_EQ(gfx::Rect(0, 0, 1920, 5300),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, PerspectiveInterestRect) {
SetBodyInnerHTML(R"HTML(<div style='left: 400px; position: absolute;'>
<div id=target style='transform: perspective(1000px) rotateX(-100deg);'>
<div style='width: 1200px; height: 835px; background: lightblue;
border: 1px solid black'></div>
</div>
)HTML");
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
EXPECT_EQ(gfx::Rect(0, 0, 1202, 837),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, RotationInterestRect) {
SetBodyInnerHTML(R"HTML(
<style>
.red_box {
position: fixed;
height: 100px;
width: 100vh; /* height of view, after -90 rot */
right: calc(16px - 50vh); /* 16 pixels above top of view, after -90 */
top: calc(50vh - 16px); /* 16 pixels in from right side, after -90 rot */
transform-origin: top;
transform: rotate(-90deg);
background-color: red;
will-change: transform;
}
.blue_box {
height: 30px;
width: 600px;
background: blue;
}
</style>
<div class="red_box" id=target>
<div class="blue_box"></div>
</div>
)HTML");
GetFrame().View()->Resize(2000, 3000);
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
EXPECT_EQ(gfx::Rect(0, 0, 3000, 100),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, 3D90DegRotatedTallInterestRect) {
// It's rotated 90 degrees about the X axis, which means its visual content
// rect is empty.
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0}</style>
<div id='target'
style='width: 200px; height: 10000px; will-change: transform;
transform: rotateY(90deg); background: blue'>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
// Use the default (-4000, -4000, 8800, 8600) intersected with the layer
// bounds.
EXPECT_EQ(gfx::Rect(0, 0, 200, 4600),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
} else {
// Use the default (-4000, -4000, 8000, 8000) intersected with the layer
// bounds.
EXPECT_EQ(gfx::Rect(0, 0, 200, 4000),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
}
TEST_P(CompositedLayerMappingTest, 3D45DegRotatedTallInterestRect) {
SetBodyInnerHTML(R"HTML(
<div id='target'
style='width: 200px; height: 10000px; will-change: transform;
transform: rotateY(45deg); background: blue'>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
// CullRectUpdate expands the cull rect twice. The first expansion is for
// composited scrolling of the LayoutView, and it's not big enough for
// |target| (as it has a sqrt(2) max scale from screen to local pixels)
// thus the second expansion.
EXPECT_EQ(gfx::Rect(0, 0, 200, 10000),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
} else {
// Interest rect is expanded in both direction by 4000 * sqrt(2) pixels,
// then intersected with the layer bounds.
EXPECT_EQ(gfx::Rect(0, 0, 200, 6226),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
}
TEST_P(CompositedLayerMappingTest, RotatedTallInterestRect) {
SetBodyInnerHTML(R"HTML(
<div id='target'
style='width: 200px; height: 10000px; will-change: transform;
transform: rotateZ(45deg); background: blue'>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
} else {
EXPECT_EQ(gfx::Rect(0, 0, 200, 4000),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
}
TEST_P(CompositedLayerMappingTest, WideLayerInterestRect) {
SetBodyInnerHTML(R"HTML(
<div id='target' style='width: 10000px; height: 200px;
will-change: transform; background: blue'>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
// Screen-space visible content rect is [8, 8, 800, 200] (the screen is
// 800x600). Mapping back to local, adding 4000px in all directions, then
// clipping, yields this rect.
EXPECT_EQ(gfx::Rect(0, 0, 4792, 200),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, FixedPositionInterestRect) {
SetBodyInnerHTML(R"HTML(
<div id='target'
style='width: 300px; height: 400px; will-change: transform;
position: fixed; top: 100px; left: 200px; background: blue'>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
EXPECT_EQ(gfx::Rect(0, 0, 300, 400),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, LayerFarOffscreenInterestRect) {
SetBodyInnerHTML(R"HTML(
<div id='target'
style='width: 200px; height: 200px; position: absolute; top: 9000px;
left: 0px; will-change: transform; background: blue'>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
// CullRectUpdate knows the layer is far away from the viewport.
EXPECT_EQ(gfx::Rect(),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
} else {
// Offscreen layers are painted as usual.
EXPECT_EQ(gfx::Rect(0, 0, 200, 200),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
}
TEST_P(CompositedLayerMappingTest, LayerNearOffscreenInterestRect) {
SetBodyInnerHTML(R"HTML(
<div id='target'
style='width: 200px; height: 200px; position: absolute; top: 3000px;
left: 0px; will-change: transform; background: blue'>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
// Offscreen layers near to the viewport are painted as usual.
EXPECT_EQ(gfx::Rect(0, 0, 200, 200),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, ScrollingLayerInterestRect) {
SetBodyInnerHTML(R"HTML(
<style>
div::-webkit-scrollbar{ width: 5px; }
</style>
<div id='target'
style='width: 200px; height: 200px; will-change: transform;
overflow: scroll; background: blue'>
<div style='width: 100px; height: 10000px'></div>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
// Offscreen layers are painted as usual.
ASSERT_TRUE(
paint_layer->GetCompositedLayerMapping()->ScrollingContentsLayer());
// In screen space, the scroller is (8, 8, 195, 193) (because of overflow clip
// of 'target', scrollbar and root margin).
// Applying the viewport clip of the root has no effect because
// the clip is already small. Mapping it down into the graphics layer
// space yields (0, 0, 195, 193). This is then expanded by 4000px.
EXPECT_EQ(gfx::Rect(0, 0, 195, 4193),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, ClippedBigLayer) {
SetBodyInnerHTML(R"HTML(
<div style='width: 1px; height: 1px; overflow: hidden'>
<div id='target' style='width: 10000px; height: 10000px;
will-change: transform; background: blue'>
</div>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
PaintLayer* paint_layer = GetPaintLayerByElementId("target");
ASSERT_TRUE(paint_layer->GraphicsLayerBacking());
// Offscreen layers are painted as usual.
EXPECT_EQ(gfx::Rect(0, 0, 4001, 4001),
PaintableRegion(paint_layer->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, InterestRectChangedEnoughToRepaintEmpty) {
if (RuntimeEnabledFeatures::CullRectUpdateEnabled())
return;
IntSize layer_size(1000, 1000);
// Both empty means there is nothing to do.
EXPECT_FALSE(
InterestRectChangedEnoughToRepaint(IntRect(), IntRect(), layer_size));
// Going from empty to non-empty means we must re-record because it could be
// the first frame after construction or Clear.
EXPECT_TRUE(InterestRectChangedEnoughToRepaint(IntRect(), IntRect(0, 0, 1, 1),
layer_size));
// Going from non-empty to empty is not special-cased.
EXPECT_FALSE(InterestRectChangedEnoughToRepaint(IntRect(0, 0, 1, 1),
IntRect(), layer_size));
}
TEST_P(CompositedLayerMappingTest,
InterestRectChangedEnoughToRepaintNotBigEnough) {
if (RuntimeEnabledFeatures::CullRectUpdateEnabled())
return;
IntSize layer_size(1000, 1000);
IntRect previous_interest_rect(100, 100, 100, 100);
EXPECT_FALSE(InterestRectChangedEnoughToRepaint(
previous_interest_rect, IntRect(100, 100, 90, 90), layer_size));
EXPECT_FALSE(InterestRectChangedEnoughToRepaint(
previous_interest_rect, IntRect(100, 100, 100, 100), layer_size));
EXPECT_FALSE(InterestRectChangedEnoughToRepaint(
previous_interest_rect, IntRect(1, 1, 200, 200), layer_size));
}
TEST_P(CompositedLayerMappingTest,
InterestRectChangedEnoughToRepaintNotBigEnoughButNewAreaTouchesEdge) {
if (RuntimeEnabledFeatures::CullRectUpdateEnabled())
return;
IntSize layer_size(500, 500);
IntRect previous_interest_rect(100, 100, 100, 100);
// Top edge.
EXPECT_TRUE(InterestRectChangedEnoughToRepaint(
previous_interest_rect, IntRect(100, 0, 100, 200), layer_size));
// Left edge.
EXPECT_TRUE(InterestRectChangedEnoughToRepaint(
previous_interest_rect, IntRect(0, 100, 200, 100), layer_size));
// Bottom edge.
EXPECT_TRUE(InterestRectChangedEnoughToRepaint(
previous_interest_rect, IntRect(100, 100, 100, 400), layer_size));
// Right edge.
EXPECT_TRUE(InterestRectChangedEnoughToRepaint(
previous_interest_rect, IntRect(100, 100, 400, 100), layer_size));
}
// Verifies that having a current viewport that touches a layer edge does not
// force re-recording.
TEST_P(CompositedLayerMappingTest,
InterestRectChangedEnoughToRepaintCurrentViewportTouchesEdge) {
if (RuntimeEnabledFeatures::CullRectUpdateEnabled())
return;
IntSize layer_size(500, 500);
IntRect new_interest_rect(100, 100, 300, 300);
// Top edge.
EXPECT_FALSE(InterestRectChangedEnoughToRepaint(
IntRect(100, 0, 100, 100), new_interest_rect, layer_size));
// Left edge.
EXPECT_FALSE(InterestRectChangedEnoughToRepaint(
IntRect(0, 100, 100, 100), new_interest_rect, layer_size));
// Bottom edge.
EXPECT_FALSE(InterestRectChangedEnoughToRepaint(
IntRect(300, 400, 100, 100), new_interest_rect, layer_size));
// Right edge.
EXPECT_FALSE(InterestRectChangedEnoughToRepaint(
IntRect(400, 300, 100, 100), new_interest_rect, layer_size));
}
TEST_P(CompositedLayerMappingTest,
InterestRectChangedEnoughToRepaintScrollScenarios) {
if (RuntimeEnabledFeatures::CullRectUpdateEnabled())
return;
IntSize layer_size(1000, 1000);
IntRect previous_interest_rect(100, 100, 100, 100);
IntRect new_interest_rect(previous_interest_rect);
new_interest_rect.Move(512, 0);
EXPECT_FALSE(InterestRectChangedEnoughToRepaint(
previous_interest_rect, new_interest_rect, layer_size));
new_interest_rect.Move(0, 512);
EXPECT_FALSE(InterestRectChangedEnoughToRepaint(
previous_interest_rect, new_interest_rect, layer_size));
new_interest_rect.Move(1, 0);
EXPECT_TRUE(InterestRectChangedEnoughToRepaint(
previous_interest_rect, new_interest_rect, layer_size));
new_interest_rect.Move(-1, 1);
EXPECT_TRUE(InterestRectChangedEnoughToRepaint(
previous_interest_rect, new_interest_rect, layer_size));
}
TEST_P(CompositedLayerMappingTest, InterestRectChangeOnViewportScroll) {
SetBodyInnerHTML(R"HTML(
<style>
::-webkit-scrollbar { width: 0; height: 0; }
body { margin: 0; }
</style>
<div id='div' style='width: 100px; height: 10000px'>Text</div>
)HTML");
GraphicsLayer* root_scrolling_layer =
GetDocument().GetLayoutView()->Layer()->GraphicsLayerBacking();
EXPECT_EQ(gfx::Rect(0, 0, 800, 4600), PaintableRegion(root_scrolling_layer));
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 300), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
// Still use the previous interest rect because the recomputed rect hasn't
// changed enough.
EXPECT_EQ(gfx::Rect(0, 0, 800, 4600), PaintableRegion(root_scrolling_layer));
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 600), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
// Use recomputed interest rect because it changed enough.
EXPECT_EQ(gfx::Rect(0, 0, 800, 5200), PaintableRegion(root_scrolling_layer));
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 5400), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(gfx::Rect(0, 1400, 800, 8600),
PaintableRegion(root_scrolling_layer));
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 9000), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
// Still use the previous interest rect because it contains the recomputed
// interest rect.
EXPECT_EQ(gfx::Rect(0, 1400, 800, 8600),
PaintableRegion(root_scrolling_layer));
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 2000), mojom::blink::ScrollType::kProgrammatic);
// Use recomputed interest rect because it changed enough.
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(gfx::Rect(0, 0, 800, 6600), PaintableRegion(root_scrolling_layer));
}
TEST_P(CompositedLayerMappingTest, InterestRectChangeOnShrunkenViewport) {
SetBodyInnerHTML(R"HTML(
<style>
::-webkit-scrollbar { width: 0; height: 0; }
body { margin: 0; }
</style>
<div id='div' style='width: 100px; height: 10000px'>Text</div>
)HTML");
GraphicsLayer* root_scrolling_layer =
GetDocument().GetLayoutView()->Layer()->GraphicsLayerBacking();
EXPECT_EQ(gfx::Rect(0, 0, 800, 4600), PaintableRegion(root_scrolling_layer));
GetDocument().View()->SetFrameRect(IntRect(0, 0, 800, 60));
UpdateAllLifecyclePhasesForTest();
// Repaint required, so interest rect should be updated to shrunken size.
EXPECT_EQ(gfx::Rect(0, 0, 800, 4060), PaintableRegion(root_scrolling_layer));
}
TEST_P(CompositedLayerMappingTest, InterestRectChangeOnScroll) {
GetDocument().GetFrame()->GetSettings()->SetPreferCompositingToLCDTextEnabled(
true);
SetBodyInnerHTML(R"HTML(
<style>
::-webkit-scrollbar { width: 0; height: 0; }
body { margin: 0; }
</style>
<div id='scroller' style='width: 400px; height: 400px; overflow: scroll'>
<div id='content' style='width: 100px; height: 10000px'>Text</div>
</div
)HTML");
Element* scroller = GetDocument().getElementById("scroller");
GraphicsLayer* scrolling_layer =
scroller->GetLayoutBox()->Layer()->GraphicsLayerBacking();
EXPECT_EQ(gfx::Rect(0, 0, 400, 4400), PaintableRegion(scrolling_layer));
scroller->setScrollTop(300);
UpdateAllLifecyclePhasesForTest();
// Still use the previous interest rect because the recomputed rect hasn't
// changed enough.
EXPECT_EQ(gfx::Rect(0, 0, 400, 4400), PaintableRegion(scrolling_layer));
scroller->setScrollTop(600);
UpdateAllLifecyclePhasesForTest();
// Use recomputed interest rect because it changed enough.
EXPECT_EQ(gfx::Rect(0, 0, 400, 5000), PaintableRegion(scrolling_layer));
scroller->setScrollTop(5600);
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(gfx::Rect(0, 1600, 400, 8400), PaintableRegion(scrolling_layer));
scroller->setScrollTop(9000);
UpdateAllLifecyclePhasesForTest();
// Still use the previous interest rect because it contains the recomputed
// interest rect.
EXPECT_EQ(gfx::Rect(0, 1600, 400, 8400), PaintableRegion(scrolling_layer));
scroller->setScrollTop(2000);
// Use recomputed interest rect because it changed enough.
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(gfx::Rect(0, 0, 400, 6400), PaintableRegion(scrolling_layer));
}
TEST_P(CompositedLayerMappingTest,
InterestRectShouldChangeOnPaintInvalidation) {
GetDocument().GetFrame()->GetSettings()->SetPreferCompositingToLCDTextEnabled(
true);
SetBodyInnerHTML(R"HTML(
<style>
::-webkit-scrollbar { width: 0; height: 0; }
body { margin: 0; }
</style>
<div id='scroller' style='width: 400px; height: 400px; overflow:
scroll'>
<div id='content' style='width: 100px; height: 10000px'>Text</div>
</div
)HTML");
UpdateAllLifecyclePhasesForTest();
Element* scroller = GetDocument().getElementById("scroller");
GraphicsLayer* scrolling_layer =
scroller->GetLayoutBox()->Layer()->GraphicsLayerBacking();
scroller->setScrollTop(5800);
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(gfx::Rect(0, 1800, 400, 8200), PaintableRegion(scrolling_layer));
scroller->setScrollTop(9400);
UpdateAllLifecyclePhasesForTest();
// Still use the old cull rect because it contains the new recomputed one.
EXPECT_EQ(gfx::Rect(0, 1800, 400, 8200), PaintableRegion(scrolling_layer));
// Paint invalidation and repaint should change previous paint interest rect.
GetDocument().getElementById("content")->setTextContent("Change");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(gfx::Rect(0, 5400, 400, 4600), PaintableRegion(scrolling_layer));
}
TEST_P(CompositedLayerMappingTest,
InterestRectOfSquashingLayerWithNegativeOverflow) {
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; font-size: 16px; }</style>
<div style='position: absolute; top: -500px; width: 200px; height:
700px; will-change: transform'></div>
<div id='squashed' style='position: absolute; top: 190px;'>
<div id='inside' style='width: 100px; height: 100px; text-indent:
-10000px'>text</div>
</div>
)HTML");
EXPECT_EQ(GetDocument()
.getElementById("inside")
->GetLayoutBox()
->VisualOverflowRect()
.Size()
.Height(),
100);
CompositedLayerMapping* grouped_mapping = GetDocument()
.getElementById("squashed")
->GetLayoutBox()
->Layer()
->GroupedMapping();
// The squashing layer is at (-10000, 190, 10100, 100) in viewport
// coordinates.
// The following rect is at (-4000, 190, 4100, 100) in viewport coordinates.
EXPECT_EQ(gfx::Rect(6000, 0, 4100, 100),
PaintableRegion(grouped_mapping->NonScrollingSquashingLayer()));
}
TEST_P(CompositedLayerMappingTest,
InterestRectOfSquashingLayerWithAncestorClip) {
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div style='overflow: hidden; width: 400px; height: 400px'>
<div style='position: relative; backface-visibility: hidden'>
<div style='position: absolute; top: -500px; width: 200px;
height: 700px; backface-visibility: hidden'></div>
<!-- Above overflow:hidden div and two composited layers make the
squashing layer a child of an ancestor clipping layer. -->
<div id='squashed' style='height: 1000px; width: 10000px; right: 0;
position: absolute'></div>
</div>
</div>
)HTML");
CompositedLayerMapping* grouped_mapping = GetDocument()
.getElementById("squashed")
->GetLayoutBox()
->Layer()
->GroupedMapping();
if (RuntimeEnabledFeatures::CullRectUpdateEnabled()) {
// CullRectUpdate doesn't expand cull rect for layers without directly
// composited transform.
EXPECT_EQ(gfx::Rect(9600, 0, 400, 400),
PaintableRegion(grouped_mapping->NonScrollingSquashingLayer()));
} else {
// The squashing layer is at (-9600, 0, 10000, 1000) in viewport
// coordinates. The following rect is at (-4000, 0, 4400, 1000) in viewport
// coordinates.
EXPECT_EQ(gfx::Rect(5600, 0, 4400, 1000),
PaintableRegion(grouped_mapping->NonScrollingSquashingLayer()));
}
}
TEST_P(CompositedLayerMappingTest, InterestRectOfIframeInScrolledDiv) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div style='width: 200; height: 8000px'></div>
<iframe src='http://test.com' width='500' height='500' frameBorder='0'>
</iframe>
)HTML");
SetChildFrameHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id=target style='width: 200px; height: 200px; will-change: transform;
background: blue'>
</div>
)HTML");
// Scroll 8000 pixels down to move the iframe into view.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0.0, 8000.0), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
Element* target = ChildDocument().getElementById("target");
ASSERT_TRUE(target);
EXPECT_EQ(
gfx::Rect(0, 0, 200, 200),
PaintableRegion(
target->GetLayoutObject()->EnclosingLayer()->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, InterestRectOfScrolledIframe) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
GetDocument().GetFrame()->GetSettings()->SetPreferCompositingToLCDTextEnabled(
true);
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0; }
::-webkit-scrollbar { display: none; }
</style>
<iframe src='http://test.com' width='500' height='500' frameBorder='0'>
</iframe>
)HTML");
SetChildFrameHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id=target style='width: 200px; height: 8000px'></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
// Scroll 7500 pixels down to bring the scrollable area to the bottom.
ChildDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0.0, 7500.0), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
ASSERT_TRUE(ChildDocument().View()->GetLayoutView()->HasLayer());
EXPECT_EQ(gfx::Rect(0, 3500, 500, 4500),
PaintableRegion(ChildDocument()
.View()
->GetLayoutView()
->EnclosingLayer()
->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, InterestRectOfIframeWithContentBoxOffset) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
GetDocument().GetFrame()->GetSettings()->SetPreferCompositingToLCDTextEnabled(
true);
// Set a 10px border in order to have a contentBoxOffset for the iframe
// element.
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0; }
#frame { border: 10px solid black; }
::-webkit-scrollbar { display: none; }
</style>
<iframe src='http://test.com' width='500' height='500' frameBorder='0'>
</iframe>
)HTML");
SetChildFrameHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id=target style='width: 200px; height: 8000px'></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
// Scroll 3000 pixels down to bring the scrollable area to somewhere in the
// middle.
ChildDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0.0, 3000.0), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
ASSERT_TRUE(ChildDocument().View()->GetLayoutView()->HasLayer());
EXPECT_EQ(gfx::Rect(0, 0, 500, 7500),
PaintableRegion(ChildDocument()
.View()
->GetLayoutView()
->EnclosingLayer()
->GraphicsLayerBacking()));
}
TEST_P(CompositedLayerMappingTest, InterestRectOfIframeWithFixedContents) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
GetDocument().GetFrame()->GetSettings()->SetPreferCompositingToLCDTextEnabled(
true);
SetBodyInnerHTML(R"HTML(
<style> * { margin:0; } </style>
<iframe src='http://test.com' width='500' height='500' frameBorder='0'>
</iframe>
)HTML");
SetChildFrameHTML(R"HTML(
<style>body { margin:0; } ::-webkit-scrollbar { display:none; }</style>
<div id='forcescroll' style='height:6000px;'></div>
<div id='fixed' style='position:fixed; top:0; left:0; width:400px;
height:300px; background:blue'>
<div id='leftbox' style='
position:absolute; left:-5000px; width:10px; height:10px;'></div>
<div id='child' style='
position:absolute; top:0; left:0; width:400px; height:300px;'></div>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
auto* fixed = ChildDocument().getElementById("fixed")->GetLayoutObject();
auto* graphics_layer = fixed->EnclosingLayer()->GraphicsLayerBacking(fixed);
// The graphics layer has dimensions 5400x300 but the interest rect clamps
// this to the right-most 4000x4000 area.
EXPECT_EQ(gfx::Rect(1000, 0, 4400, 300), PaintableRegion(graphics_layer));
ChildDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0.0, 3000.0), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
// Because the fixed element does not scroll, the interest rect is unchanged.
EXPECT_EQ(gfx::Rect(1000, 0, 4400, 300), PaintableRegion(graphics_layer));
}
TEST_P(CompositedLayerMappingTest, ScrolledFixedPositionInterestRect) {
GetDocument().GetFrame()->GetSettings()->SetPreferCompositingToLCDTextEnabled(
true);
SetBodyInnerHTML(R"HTML(
<style>body { margin:0; } ::-webkit-scrollbar { display:none; }</style>
<div id="fixed" style="position: fixed;">
<div style="background: blue; width: 30px; height: 30px;"></div>
<div style="position: absolute; transform: translateY(-4500px);
top: 0; left: 0; width: 100px; height: 100px;"></div>
</div>
<div id="forcescroll" style="height: 2000px;"></div>
)HTML");
auto* fixed = GetDocument().getElementById("fixed")->GetLayoutObject();
auto* graphics_layer = fixed->EnclosingLayer()->GraphicsLayerBacking(fixed);
EXPECT_EQ(gfx::Rect(0, 500, 100, 4030), PaintableRegion(graphics_layer));
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0.0, 200.0), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
// Because the fixed element does not scroll, the interest rect is unchanged.
EXPECT_EQ(gfx::Rect(0, 500, 100, 4030), PaintableRegion(graphics_layer));
}
TEST_P(CompositedLayerMappingTest,
ScrollingContentsAndForegroundLayerPaintingPhase) {
GetDocument().GetFrame()->GetSettings()->SetPreferCompositingToLCDTextEnabled(
true);
SetBodyInnerHTML(R"HTML(
<div id='container' style='position: relative; z-index: 1; overflow:
scroll; width: 300px; height: 300px'>
<div id='negative-composited-child' style='background-color: red;
width: 1px; height: 1px; position: absolute;
backface-visibility: hidden; z-index: -1'></div>
<div style='background-color: blue; width: 2000px; height: 2000px;
position: relative; top: 10px'></div>
</div>
)HTML");
auto* mapping = To<LayoutBlock>(GetLayoutObjectByElementId("container"))
->Layer()
->GetCompositedLayerMapping();
ASSERT_TRUE(mapping->ScrollingContentsLayer());
EXPECT_EQ(static_cast<GraphicsLayerPaintingPhase>(
kGraphicsLayerPaintOverflowContents |
kGraphicsLayerPaintCompositedScroll),
mapping->ScrollingContentsLayer()->PaintingPhase());
ASSERT_TRUE(mapping->ForegroundLayer());
EXPECT_EQ(
static_cast<GraphicsLayerPaintingPhase>(
kGraphicsLayerPaintForeground | kGraphicsLayerPaintOverflowContents),
mapping->ForegroundLayer()->PaintingPhase());
// Regression test for crbug.com/767908: a foreground layer should also
// participates hit testing.
EXPECT_TRUE(mapping->ForegroundLayer()->IsHitTestable());
Element* negative_composited_child =
GetDocument().getElementById("negative-composited-child");
negative_composited_child->parentNode()->RemoveChild(
negative_composited_child);
UpdateAllLifecyclePhasesForTest();
mapping = To<LayoutBlock>(GetLayoutObjectByElementId("container"))
->Layer()
->GetCompositedLayerMapping();
ASSERT_TRUE(mapping->ScrollingContentsLayer());
EXPECT_EQ(
static_cast<GraphicsLayerPaintingPhase>(
kGraphicsLayerPaintOverflowContents |
kGraphicsLayerPaintCompositedScroll | kGraphicsLayerPaintForeground),
mapping->ScrollingContentsLayer()->PaintingPhase());
EXPECT_FALSE(mapping->ForegroundLayer());
}
TEST_P(CompositedLayerMappingTest,
DecorationOutlineLayerOnlyCreatedInCompositedScrolling) {
SetBodyInnerHTML(R"HTML(
<style>
#target {
overflow: scroll; height: 200px; width: 200px; will-change: transform;
background: white local content-box;
outline: 1px solid blue; outline-offset: -2px;
}
#scrolled { height: 300px; }
</style>
<div id="parent">
<div id="target"><div id="scrolled"></div></div>
</div>
)HTML");
Element* element = GetDocument().getElementById("target");
PaintLayer* paint_layer =
To<LayoutBoxModelObject>(element->GetLayoutObject())->Layer();
ASSERT_TRUE(paint_layer);
// Decoration outline layer is created when composited scrolling.
EXPECT_TRUE(paint_layer->HasCompositedLayerMapping());
EXPECT_TRUE(paint_layer->NeedsCompositedScrolling());
CompositedLayerMapping* mapping = paint_layer->GetCompositedLayerMapping();
EXPECT_TRUE(mapping->DecorationOutlineLayer());
// No decoration outline layer is created when not composited scrolling.
element->setAttribute(html_names::kStyleAttr, "overflow: visible;");
UpdateAllLifecyclePhasesForTest();
paint_layer = To<LayoutBoxModelObject>(element->GetLayoutObject())->Layer();
ASSERT_TRUE(paint_layer);
mapping = paint_layer->GetCompositedLayerMapping();
EXPECT_FALSE(paint_layer->NeedsCompositedScrolling());
EXPECT_FALSE(mapping->DecorationOutlineLayer());
}
TEST_P(CompositedLayerMappingTest,
DecorationOutlineLayerCreatedAndDestroyedInCompositedScrolling) {
SetBodyInnerHTML(R"HTML(
<style>
#scroller {
overflow: scroll; height: 200px; width: 200px; contain: paint;
background: white local content-box; outline: 1px solid blue;
}
#scrolled { height: 300px; }
</style>
<div id="parent">
<div id="scroller"><div id="scrolled"></div></div>
</div>
)HTML");
Element* scroller = GetDocument().getElementById("scroller");
PaintLayer* paint_layer =
To<LayoutBoxModelObject>(scroller->GetLayoutObject())->Layer();
ASSERT_TRUE(paint_layer);
CompositedLayerMapping* mapping = paint_layer->GetCompositedLayerMapping();
EXPECT_FALSE(mapping->DecorationOutlineLayer());
// The decoration outline layer is created when composited scrolling
// with an outline drawn over the composited scrolling region.
scroller->setAttribute(html_names::kStyleAttr, "outline-offset: -2px;");
UpdateAllLifecyclePhasesForTest();
paint_layer = To<LayoutBoxModelObject>(scroller->GetLayoutObject())->Layer();
ASSERT_TRUE(paint_layer);
mapping = paint_layer->GetCompositedLayerMapping();
EXPECT_TRUE(paint_layer->NeedsCompositedScrolling());
EXPECT_TRUE(mapping->DecorationOutlineLayer());
// The decoration outline layer is destroyed when the scrolling region
// will not be covered up by the outline.
scroller->removeAttribute(html_names::kStyleAttr);
UpdateAllLifecyclePhasesForTest();
paint_layer = To<LayoutBoxModelObject>(scroller->GetLayoutObject())->Layer();
ASSERT_TRUE(paint_layer);
mapping = paint_layer->GetCompositedLayerMapping();
EXPECT_FALSE(mapping->DecorationOutlineLayer());
}
TEST_P(CompositedLayerMappingTest, StickyPositionMainThreadOffset) {
SetBodyInnerHTML(R"HTML(
<style>.composited { backface-visibility: hidden; }
#scroller { overflow: auto; height: 200px; width: 200px; }
.container { height: 500px; }
.innerPadding { height: 10px; }
#sticky { position: sticky; top: 25px; height: 50px; }</style>
<div id='scroller' class='composited'>
<div class='composited container'>
<div class='composited container'>
<div class='innerPadding'></div>
<div id='sticky' class='composited'></div>
</div></div></div>
)HTML");
PaintLayer* sticky_layer = GetPaintLayerByElementId("sticky");
CompositedLayerMapping* sticky_mapping =
sticky_layer->GetCompositedLayerMapping();
ASSERT_TRUE(sticky_mapping);
// Now scroll the page - this should increase the main thread offset.
LayoutBoxModelObject* scroller =
To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().X(), 100));
ASSERT_EQ(100.0, scrollable_area->ScrollPosition().Y());
sticky_layer->SetNeedsCompositingInputsUpdate();
EXPECT_TRUE(sticky_layer->NeedsCompositingInputsUpdate());
GetDocument().View()->UpdateLifecycleToCompositingCleanPlusScrolling(
DocumentUpdateReason::kTest);
EXPECT_FALSE(sticky_layer->NeedsCompositingInputsUpdate());
}
TEST_P(CompositedLayerMappingTest, StickyPositionNotSquashed) {
SetBodyInnerHTML(R"HTML(
<style>
#scroller { overflow: auto; height: 200px; }
#sticky1, #sticky2, #sticky3 {position: sticky; top: 0; width: 50px;
height: 50px; background: rgba(0, 128, 0, 0.5);}
#sticky1 {backface-visibility: hidden;}
.spacer {height: 2000px;}
</style>
<div id='scroller'>
<div id='sticky1'></div>
<div id='sticky2'></div>
<div id='sticky3'></div>
<div class='spacer'></div>
</div>
)HTML");
auto* sticky1 =
To<LayoutBlock>(GetLayoutObjectByElementId("sticky1"))->Layer();
auto* sticky2 =
To<LayoutBlock>(GetLayoutObjectByElementId("sticky2"))->Layer();
auto* sticky3 =
To<LayoutBlock>(GetLayoutObjectByElementId("sticky3"))->Layer();
// All three sticky-pos elements are composited, because we composite
// all sticky elements which stick to scrollers.
EXPECT_EQ(kPaintsIntoOwnBacking, sticky1->GetCompositingState());
EXPECT_EQ(kPaintsIntoOwnBacking, sticky2->GetCompositingState());
EXPECT_EQ(kPaintsIntoOwnBacking, sticky3->GetCompositingState());
}
TEST_P(CompositedLayerMappingTest,
LayerPositionForStickyElementInCompositedScroller) {
SetBodyInnerHTML(R"HTML(
<style>
.scroller { overflow: scroll; width: 200px; height: 600px; }
.composited { will-change:transform; }
.perspective { perspective: 150px; }
.box { position: sticky; width: 185px; height: 50px; top: 0px; }
.container { width: 100%; height: 1000px; }
</style>
<div id='scroller' class='composited scroller'>
<div class='composited container'>
<div id='sticky' class='perspective box'></div>
</div>
</div>
)HTML");
LayoutBoxModelObject* sticky =
To<LayoutBoxModelObject>(GetLayoutObjectByElementId("sticky"));
CompositedLayerMapping* mapping =
sticky->Layer()->GetCompositedLayerMapping();
ASSERT_TRUE(mapping);
GraphicsLayer* main_graphics_layer = mapping->MainGraphicsLayer();
ASSERT_TRUE(main_graphics_layer);
auto* scroller =
To<LayoutBlock>(GetLayoutObjectByElementId("scroller"))->Layer();
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().Y(), 100));
UpdateAllLifecyclePhasesForTest();
// On the blink side, a sticky offset of (0, 100) should have been applied to
// the sticky element.
EXPECT_EQ(PhysicalOffset(0, 100), sticky->StickyPositionOffset());
GraphicsLayer* root_scrolling_layer =
GetDocument().GetLayoutView()->Layer()->GraphicsLayerBacking();
const auto& root_layer_state = root_scrolling_layer->GetPropertyTreeState();
const auto& sticky_layer_state = main_graphics_layer->GetPropertyTreeState();
auto transform_from_sticky_to_root =
GeometryMapper::SourceToDestinationProjection(
sticky_layer_state.Transform(), root_layer_state.Transform());
// Irrespective of if the ancestor scroller is composited or not, the sticky
// position element should be at the same location.
auto sticky_position_relative_to_root =
transform_from_sticky_to_root.MapPoint(
FloatPoint(main_graphics_layer->GetOffsetFromTransformNode()));
EXPECT_FLOAT_EQ(8, sticky_position_relative_to_root.X());
EXPECT_FLOAT_EQ(8, sticky_position_relative_to_root.Y());
}
TEST_P(CompositedLayerMappingTest,
LayerPositionForStickyElementInNonCompositedScroller) {
SetBodyInnerHTML(R"HTML(
<style>
.scroller { overflow: scroll; width: 200px; height: 600px; }
.composited { will-change:transform; }
.box { position: sticky; width: 185px; height: 50px; top: 0px; }
.container { width: 100%; height: 1000px; }
</style>
<div id='scroller' class='scroller'>
<div class='composited container'>
<div id='sticky' class='box'></div>
</div>
</div>
)HTML");
auto* mapping = To<LayoutBlock>(GetLayoutObjectByElementId("sticky"))
->Layer()
->GetCompositedLayerMapping();
ASSERT_TRUE(mapping);
GraphicsLayer* main_graphics_layer = mapping->MainGraphicsLayer();
auto* scroller =
To<LayoutBlock>(GetLayoutObjectByElementId("scroller"))->Layer();
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
ASSERT_TRUE(scrollable_area);
scrollable_area->ScrollToAbsolutePosition(
FloatPoint(scrollable_area->ScrollPosition().Y(), 100));
UpdateAllLifecyclePhasesForTest();
GraphicsLayer* root_scrolling_layer =
GetDocument().GetLayoutView()->Layer()->GraphicsLayerBacking();
const auto& root_layer_state = root_scrolling_layer->GetPropertyTreeState();
const auto& sticky_layer_state = main_graphics_layer->GetPropertyTreeState();
auto transform_from_sticky_to_root =
GeometryMapper::SourceToDestinationProjection(
sticky_layer_state.Transform(), root_layer_state.Transform());
// Irrespective of if the ancestor scroller is composited or not, the sticky
// position element should be at the same location.
auto sticky_position_relative_to_root =
transform_from_sticky_to_root.MapPoint(
FloatPoint(main_graphics_layer->GetOffsetFromTransformNode()));
EXPECT_FLOAT_EQ(8, sticky_position_relative_to_root.X());
EXPECT_FLOAT_EQ(8, sticky_position_relative_to_root.Y());
}
TEST_P(CompositedLayerMappingTest, ScrollingContainerBoundsChange) {
GetDocument().GetFrame()->GetSettings()->SetPreferCompositingToLCDTextEnabled(
true);
SetBodyInnerHTML(R"HTML(
<style>
::-webkit-scrollbar { width: 0; height: 0; }
body { margin: 0; }
#scroller { overflow-y: scroll; }
#content {
width: 100px;
height: 100px;
margin-top: 50px;
margin-bottom: -50px;
}
</style>
<div id='scroller'>
<div id='content'></div>
</div
)HTML");
Element* scrollerElement = GetDocument().getElementById("scroller");
auto* scroller =
To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
cc::Layer* scrolling_layer = scrollable_area->LayerForScrolling();
auto element_id = scrollable_area->GetScrollElementId();
auto& scroll_tree =
scrolling_layer->layer_tree_host()->property_trees()->scroll_tree;
EXPECT_EQ(0, scroll_tree.current_scroll_offset(element_id).y());
EXPECT_EQ(150, scrolling_layer->bounds().height());
auto* scroll_node = scroll_tree.FindNodeFromElementId(element_id);
EXPECT_EQ(100, scroll_node->container_bounds.height());
scrollerElement->setScrollTop(300);
scrollerElement->setAttribute(html_names::kStyleAttr, "max-height: 25px;");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(50, scroll_tree.current_scroll_offset(element_id).y());
EXPECT_EQ(150, scrolling_layer->bounds().height());
scroll_node = scroll_tree.FindNodeFromElementId(element_id);
EXPECT_EQ(25, scroll_node->container_bounds.height());
scrollerElement->setAttribute(html_names::kStyleAttr, "max-height: 300px;");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(50, scroll_tree.current_scroll_offset(element_id).y());
EXPECT_EQ(150, scrolling_layer->bounds().height());
scroll_node = scroll_tree.FindNodeFromElementId(element_id);
EXPECT_EQ(100, scroll_node->container_bounds.height());
}
TEST_P(CompositedLayerMappingTest, MainFrameLayerBackgroundColor) {
EXPECT_EQ(Color::kWhite, GetDocument().View()->BaseBackgroundColor());
auto* view_cc_layer = ScrollingContentsCcLayerByScrollElementId(
GetFrame().View()->RootCcLayer(),
GetFrame().View()->LayoutViewport()->GetScrollElementId());
EXPECT_EQ(SK_ColorWHITE, view_cc_layer->background_color());
Color base_background(255, 0, 0);
GetDocument().View()->SetBaseBackgroundColor(base_background);
GetDocument().body()->setAttribute(html_names::kStyleAttr,
"background: rgba(0, 255, 0, 0.5)");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(base_background, GetDocument().View()->BaseBackgroundColor());
EXPECT_EQ(SkColorSetARGB(255, 127, 128, 0),
view_cc_layer->background_color());
}
TEST_P(CompositedLayerMappingTest, ScrollLayerSizingSubpixelAccumulation) {
// This test verifies that when subpixel accumulation causes snapping it
// applies to the scrolling contents layer. Verify that the mapping doesn't
// have any vertical scrolling introduced as a result of the snapping
// behavior. https://crbug.com/801381.
GetDocument().GetFrame()->GetSettings()->SetPreferCompositingToLCDTextEnabled(
true);
// The values below are chosen so that the subpixel accumulation causes the
// pixel snapped height to be increased relative to snapping without it.
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<style>
body {
margin: 0;
}
#scroller {
position: relative;
top: 0.5625px;
width: 200px;
height: 200.8125px;
overflow: auto;
}
#space {
width: 1000px;
height: 200.8125px;
}
</style>
<div id="scroller">
<div id="space"></div>
</div>
)HTML");
auto* mapping =
GetPaintLayerByElementId("scroller")->GetCompositedLayerMapping();
ASSERT_TRUE(mapping);
ASSERT_TRUE(mapping->ScrollingContentsLayer());
EXPECT_EQ(gfx::Size(200, 200), mapping->MainGraphicsLayer()->Size());
EXPECT_EQ(gfx::Size(1000, 200), mapping->ScrollingContentsLayer()->Size());
}
TEST_P(CompositedLayerMappingTest, SquashingScrollInterestRect) {
SetHtmlInnerHTML(R"HTML(
<style>
* { margin: 0 }
</style>
<div id=target style='width: 200px; height: 200px; position: relative;
will-change: transform'></div>
<div id=squashed style='width: 200px; height: 6000px; top: -200px;
position: relative;'></div>
)HTML");
auto* squashed = GetPaintLayerByElementId("squashed");
EXPECT_EQ(kPaintsIntoGroupedBacking, squashed->GetCompositingState());
GetDocument().View()->LayoutViewport()->ScrollBy(
ScrollOffset(0, 5000), mojom::blink::ScrollType::kUser);
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(
gfx::Rect(0, 1000, 200, 5000),
PaintableRegion(squashed->GroupedMapping()->SquashingLayer(*squashed)));
}
TEST_P(CompositedLayerMappingTest,
SquashingBoundsUnderCompositedScrollingWithTransform) {
SetHtmlInnerHTML(R"HTML(
<div id=scroller style="will-change: transform; overflow: scroll;
width: 200px; height: 400px;">
<div id=squashing style='width: 200px; height: 200px; position: relative;
will-change: transform'></div>
<div id=squashed style="width: 200px; height: 6000px; top: -100px;
position: relative;">
</div>
</div>
)HTML");
Element* scroller_element = GetDocument().getElementById("scroller");
auto* scroller = scroller_element->GetLayoutObject();
EXPECT_EQ(kPaintsIntoOwnBacking, scroller->GetCompositingState());
auto* squashing = GetPaintLayerByElementId("squashing");
EXPECT_EQ(kPaintsIntoOwnBacking, squashing->GetCompositingState());
auto* squashed = GetPaintLayerByElementId("squashed");
EXPECT_EQ(kPaintsIntoGroupedBacking, squashed->GetCompositingState());
scroller_element->setScrollTop(300);
UpdateAllLifecyclePhasesForTest();
ASSERT_EQ(kPaintsIntoGroupedBacking, squashed->GetCompositingState());
// 100px down from squashing's main graphics layer.
EXPECT_EQ(IntPoint(0, 100),
squashed->GraphicsLayerBacking()->GetOffsetFromTransformNode());
}
TEST_P(CompositedLayerMappingTest, ContentsNotOpaqueWithForegroundLayer) {
SetHtmlInnerHTML(R"HTML(
<style>
div {
width: 100px;
height: 100px;
position: relative;
isolation: isolate;
}
</style>
<div id='target' style='will-change: transform'>
<div style='background: blue; z-index: -1; will-change: transform'></div>
<div style='background: blue'></div>
</div>
)HTML");
PaintLayer* target_layer = GetPaintLayerByElementId("target");
CompositedLayerMapping* mapping = target_layer->GetCompositedLayerMapping();
EXPECT_TRUE(mapping->ForegroundLayer());
EXPECT_FALSE(mapping->MainGraphicsLayer()->CcLayer().contents_opaque());
}
TEST_P(CompositedLayerMappingTest, EmptyBoundsDoesntDrawContent) {
SetHtmlInnerHTML(R"HTML(
<style>
div {
width: 100px;
height: 0px;
position: relative;
isolation: isolate;
}
</style>
<div id='target' style='will-change: transform; background: blue'>
</div>
)HTML");
PaintLayer* target_layer = GetPaintLayerByElementId("target");
CompositedLayerMapping* mapping = target_layer->GetCompositedLayerMapping();
EXPECT_FALSE(mapping->MainGraphicsLayer()->DrawsContent());
}
TEST_P(CompositedLayerMappingTest, TouchActionRectsWithoutContent) {
SetBodyInnerHTML(
"<div id='target' style='will-change: transform; width: 100px;"
" height: 100px; touch-action: none;'></div>");
auto* box = To<LayoutBoxModelObject>(GetLayoutObjectByElementId("target"));
auto* mapping = box->Layer()->GetCompositedLayerMapping();
const auto& layer = mapping->MainGraphicsLayer()->CcLayer();
auto expected = gfx::Rect(0, 0, 100, 100);
EXPECT_EQ(layer.touch_action_region().GetAllRegions().bounds(), expected);
EXPECT_TRUE(mapping->MainGraphicsLayer()->PaintsHitTest());
// The only painted content for the main graphics layer is the touch-action
// rect which is not sent to cc, so the cc::layer should not draw content.
EXPECT_FALSE(layer.DrawsContent());
EXPECT_FALSE(mapping->MainGraphicsLayer()->DrawsContent());
}
TEST_P(CompositedLayerMappingTest, ContentsOpaque) {
SetHtmlInnerHTML(R"HTML(
<style>
div {
width: 100px;
height: 100px;
position: relative;
isolation: isolate;
}
</style>
<div id='target' style='will-change: transform'>
<div style='background: blue'></div>
</div>
)HTML");
PaintLayer* target_layer = GetPaintLayerByElementId("target");
CompositedLayerMapping* mapping = target_layer->GetCompositedLayerMapping();
EXPECT_FALSE(mapping->ForegroundLayer());
EXPECT_TRUE(mapping->MainGraphicsLayer()->CcLayer().contents_opaque());
}
TEST_P(CompositedLayerMappingTest, NullOverflowControlLayers) {
SetHtmlInnerHTML("<div id='target' style='will-change: transform'></div>");
CompositedLayerMapping* mapping =
GetPaintLayerByElementId("target")->GetCompositedLayerMapping();
EXPECT_FALSE(mapping->LayerForHorizontalScrollbar());
EXPECT_FALSE(mapping->LayerForVerticalScrollbar());
EXPECT_FALSE(mapping->LayerForScrollCorner());
}
TEST_P(CompositedLayerMappingTest, CompositedHiddenAnimatingLayer) {
SetHtmlInnerHTML(R"HTML(
<style>
@keyframes slide {
0% { transform: translate3d(0px, 0px, 0px); }
100% { transform: translate3d(100px, 0px, 1px); }
}
div {
width: 123px;
height: 234px;
animation-duration: 2s;
animation-name: slide;
animation-iteration-count: infinite;
animation-direction: alternate;
}
</style>
<div id="animated"></div>
)HTML");
PaintLayer* animated = GetPaintLayerByElementId("animated");
CompositedLayerMapping* mapping = animated->GetCompositedLayerMapping();
ASSERT_TRUE(mapping);
EXPECT_TRUE(mapping->MainGraphicsLayer()->GetCompositingReasons() &
CompositingReason::kActiveTransformAnimation);
// We still composite the animated layer even if visibility: hidden.
// TODO(crbug.com/937573): Is this necessary?
GetDocument()
.getElementById("animated")
->setAttribute(html_names::kStyleAttr, "visibility: hidden");
UpdateAllLifecyclePhasesForTest();
mapping = animated->GetCompositedLayerMapping();
ASSERT_TRUE(mapping);
EXPECT_TRUE(mapping->MainGraphicsLayer()->GetCompositingReasons() &
CompositingReason::kActiveTransformAnimation);
}
TEST_P(CompositedLayerMappingTest,
RepaintScrollableAreaLayersInMainThreadScrolling) {
SetHtmlInnerHTML(R"HTML(
<style>
#scroller {
width: 200px;
height: 100px;
overflow: scroll;
opacity: 0.8; /*MainThreadScrollingReason::kHasOpacityAndLCDText*/
}
#child {
width: 100px;
height: 200px;
transform: translate3d(0, 0, 0);
}
#uncorrelated {
transform: translate3d(0, 0, 0);
height: 100px;
width: 100px;
background-color: red;
}
</style>
<div id="scroller">
<div id="child">
</div>
</div>
<div id="uncorrelated"></div>
)HTML");
PaintLayer* scroller = GetPaintLayerByElementId("scroller");
PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
ASSERT_TRUE(scrollable_area);
ASSERT_TRUE(scrollable_area->VerticalScrollbar()->IsOverlayScrollbar());
ASSERT_FALSE(scrollable_area->NeedsCompositedScrolling());
EXPECT_FALSE(scrollable_area->VerticalScrollbar()->FrameRect().IsEmpty());
GraphicsLayer* vertical_scrollbar_layer =
scrollable_area->GraphicsLayerForVerticalScrollbar();
ASSERT_TRUE(vertical_scrollbar_layer);
CompositedLayerMapping* mapping = scroller->GetCompositedLayerMapping();
ASSERT_TRUE(mapping);
// Input events, animations and DOM changes, etc, can trigger cc::ProxyMain::
// BeginMainFrame, which may check if all graphics layers need repaint.
//
// We shouldn't repaint scrollable area layer which has no paint invalidation
// in many uncorrelated BeginMainFrame scenes, such as moving mouse over the
// non-scrollbar area, animating or DOM changes in another composited layer.
GetDocument()
.getElementById("uncorrelated")
->setAttribute(html_names::kStyleAttr, "width: 200px");
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
DocumentUpdateReason::kTest);
EXPECT_FALSE(mapping->NeedsRepaint(*vertical_scrollbar_layer));
GetDocument().getElementById("child")->setAttribute(html_names::kStyleAttr,
"height: 50px");
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
DocumentUpdateReason::kTest);
EXPECT_TRUE(mapping->NeedsRepaint(*vertical_scrollbar_layer));
}
TEST_P(CompositedLayerMappingTest, IsolationClippingContainer) {
SetBodyInnerHTML(R"HTML(
<style>
#hideable {
overflow: hidden;
height: 10px;
}
.isolation {
contain: style layout;
height: 100px;
}
.squash-container {
will-change: transform;
}
.squashed {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
}
</style>
<div id="hideable">
<div class="isolation" id="isolation_a">
<div class="squash-container" id="squash_container_a">a</div>
<div class="squashed"></div>
</div>
<div class="isolation">
<div class="squash-container">b</div>
<div class="squashed"></div>
</div>
</div>
)HTML");
Element* hideable = GetDocument().getElementById("hideable");
hideable->SetInlineStyleProperty(CSSPropertyID::kOverflow, "visible");
UpdateAllLifecyclePhasesForTest();
auto* isolation_a = GetDocument().getElementById("isolation_a");
auto* isolation_a_object = isolation_a->GetLayoutObject();
auto* squash_container_a = GetDocument().getElementById("squash_container_a");
PaintLayer* squash_container_a_layer =
To<LayoutBoxModelObject>(squash_container_a->GetLayoutObject())->Layer();
EXPECT_EQ(squash_container_a_layer->ClippingContainer(), isolation_a_object);
}
TEST_P(CompositedLayerMappingTest, SquashIntoScrollingContents) {
GetDocument().GetFrame()->GetSettings()->SetPreferCompositingToLCDTextEnabled(
true);
SetBodyInnerHTML(R"HTML(
<div style="position: absolute; top: 0.5px; left: 0.75px; z-index: 1">
<div style="height: 0.75px"></div>
<div id="scroller" style="width: 100px; height: 100px; overflow: scroll;
border: 10px solid blue">
<div id="target1" style="position: relative; top: 10.5px; left: 5.5px;
width: 10px; height: 10px; background: green"></div>
<div style="height: 300px"></div>
<div id="target2" style="position: relative; z-index: 2;
width: 10px; height: 10px; background: green"></div>
</div>
<div style="position: absolute; z-index: 1; top: 50px;
width: 10px; height: 10px; background: blue">
</div>
</div>
)HTML");
auto* scroller = GetPaintLayerByElementId("scroller");
auto* target1 = GetPaintLayerByElementId("target1");
auto* target2 = GetPaintLayerByElementId("target2");
auto* scroller_mapping = scroller->GetCompositedLayerMapping();
ASSERT_TRUE(scroller_mapping);
EXPECT_EQ(IntSize(),
scroller_mapping->MainGraphicsLayer()->OffsetFromLayoutObject());
EXPECT_EQ(
IntSize(10, 10),
scroller_mapping->ScrollingContentsLayer()->OffsetFromLayoutObject());
EXPECT_EQ(PhysicalOffset(LayoutUnit(-0.25), LayoutUnit(0.25)),
scroller->SubpixelAccumulation());
EXPECT_EQ(scroller_mapping, target1->GroupedMapping());
EXPECT_EQ(scroller_mapping->ScrollingContentsLayer(),
scroller_mapping->SquashingLayer(*target1));
EXPECT_EQ(scroller_mapping->ScrollingContentsLayer(),
target1->GraphicsLayerBacking());
EXPECT_EQ(PhysicalOffset(LayoutUnit(0.25), LayoutUnit(-0.25)),
target1->SubpixelAccumulation());
const GraphicsLayerPaintInfo* target1_info =
GetSquashedLayerInScrollingContents(*scroller_mapping, *target1);
ASSERT_TRUE(target1_info);
EXPECT_TRUE(target1_info->offset_from_layout_object_set);
EXPECT_EQ(IntSize(-5, -11), target1_info->offset_from_layout_object);
EXPECT_EQ(ClipRect(), target1_info->local_clip_rect_for_squashed_layer);
// target2 can't be squashed because the absolute position div is between
// the scrolling contents and target2.
EXPECT_FALSE(target2->GroupedMapping());
EXPECT_TRUE(target2->HasCompositedLayerMapping());
}
TEST_P(CompositedLayerMappingTest,
SwitchSquashingBetweenScrollingAndNonScrolling) {
GetDocument().GetFrame()->GetSettings()->SetPreferCompositingToLCDTextEnabled(
true);
SetBodyInnerHTML(R"HTML(
<style>.scroll { overflow: scroll; }</style>
<div id="container"
style="backface-visibility: hidden; width: 100px; height: 100px">
<div id="squashed"
style="z-index: 1; position: relative; width: 10px; height: 10px"></div>
<div id="filler" style="height: 300px"></div>
</div>
)HTML");
auto* container_element = GetDocument().getElementById("container");
auto* container = container_element->GetLayoutBox()->Layer();
auto* squashed = GetPaintLayerByElementId("squashed");
auto* mapping = container->GetCompositedLayerMapping();
ASSERT_TRUE(mapping);
EXPECT_EQ(mapping, squashed->GroupedMapping());
EXPECT_EQ(mapping->NonScrollingSquashingLayer(),
squashed->GraphicsLayerBacking());
EXPECT_EQ(mapping->NonScrollingSquashingLayer(),
mapping->SquashingLayer(*squashed));
EXPECT_TRUE(GetNonScrollingSquashedLayer(*mapping, *squashed));
EXPECT_FALSE(GetSquashedLayerInScrollingContents(*mapping, *squashed));
container_element->setAttribute(html_names::kClassAttr, "scroll");
UpdateAllLifecyclePhasesForTest();
ASSERT_EQ(mapping, container->GetCompositedLayerMapping());
EXPECT_EQ(mapping, squashed->GroupedMapping());
EXPECT_EQ(mapping->ScrollingContentsLayer(),
squashed->GraphicsLayerBacking());
EXPECT_EQ(mapping->ScrollingContentsLayer(),
mapping->SquashingLayer(*squashed));
EXPECT_FALSE(GetNonScrollingSquashedLayer(*mapping, *squashed));
EXPECT_TRUE(GetSquashedLayerInScrollingContents(*mapping, *squashed));
container_element->setAttribute(html_names::kClassAttr, "");
UpdateAllLifecyclePhasesForTest();
ASSERT_EQ(mapping, container->GetCompositedLayerMapping());
EXPECT_EQ(mapping->NonScrollingSquashingLayer(),
squashed->GraphicsLayerBacking());
EXPECT_EQ(mapping->NonScrollingSquashingLayer(),
mapping->SquashingLayer(*squashed));
EXPECT_TRUE(GetNonScrollingSquashedLayer(*mapping, *squashed));
EXPECT_FALSE(GetSquashedLayerInScrollingContents(*mapping, *squashed));
}
// Unlike CompositingTest.WillChangeTransformHintInSVG, will-change hints on the
// SVG element itself should not opt into creating layers after paint.
TEST_P(CompositedLayerMappingTest, WillChangeTransformHintOnSVG) {
ScopedCompositeSVGForTest enable_feature(true);
SetBodyInnerHTML(R"HTML(
<svg width="99" height="99" id="willChange" style="will-change: transform;">
<rect width="100%" height="100%" fill="blue"></rect>
</svg>
)HTML");
PaintLayer* paint_layer = GetPaintLayerByElementId("willChange");
GraphicsLayer* graphics_layer = paint_layer->GraphicsLayerBacking();
EXPECT_FALSE(graphics_layer->ShouldCreateLayersAfterPaint());
}
// Test that will-change changes inside SVG correctly update whether the
// graphics layer should create layers after paint.
TEST_P(CompositedLayerMappingTest, WillChangeTransformHintInSVGChanged) {
ScopedCompositeSVGForTest enable_feature(true);
SetBodyInnerHTML(R"HTML(
<svg width="99" height="99" id="svg" style="will-change: transform;">
<rect id="rect" width="100%" height="100%" fill="blue"></rect>
</svg>
)HTML");
Element* svg = GetDocument().getElementById("svg");
PaintLayer* paint_layer =
To<LayoutBoxModelObject>(svg->GetLayoutObject())->Layer();
EXPECT_FALSE(
paint_layer->GraphicsLayerBacking()->ShouldCreateLayersAfterPaint());
Element* rect = GetDocument().getElementById("rect");
rect->setAttribute(html_names::kStyleAttr, "will-change: transform;");
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(
paint_layer->GraphicsLayerBacking()->ShouldCreateLayersAfterPaint());
rect->removeAttribute(html_names::kStyleAttr);
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(
paint_layer->GraphicsLayerBacking()->ShouldCreateLayersAfterPaint());
// Remove will-change from the svg element and perform the same tests. The
// z-index just ensures a paint layer exists so the test is similar.
svg->setAttribute(html_names::kStyleAttr, "z-index: 5;");
UpdateAllLifecyclePhasesForTest();
paint_layer = To<LayoutBoxModelObject>(svg->GetLayoutObject())->Layer();
EXPECT_FALSE(paint_layer->GraphicsLayerBacking());
rect->setAttribute(html_names::kStyleAttr, "will-change: transform;");
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(
paint_layer->GraphicsLayerBacking()->ShouldCreateLayersAfterPaint());
rect->removeAttribute(html_names::kStyleAttr);
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(paint_layer->GraphicsLayerBacking());
}
} // namespace blink