blob: fb7cf4730cab900c2d9fe3fff51f94016cf7a3a9 [file] [log] [blame]
// Copyright 2018 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_and_raster_invalidation_test.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
namespace blink {
using ::testing::MatchesRegex;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
void SetUpHTML(PaintAndRasterInvalidationTest& test) {
test.SetBodyInnerHTML(R"HTML(
<style>
body {
margin: 0;
height: 0;
}
::-webkit-scrollbar { display: none }
#target {
width: 50px;
height: 100px;
transform-origin: 0 0;
}
.solid {
background: blue;
}
.translucent {
background: rgba(0, 0, 255, 0.5);
}
.gradient {
background-image: linear-gradient(blue, yellow);
}
.scroll {
overflow: scroll;
}
.solid-composited-scroller {
overflow: scroll;
will-change: transform;
background: blue;
}
.local-attachment {
background-attachment: local;
}
.transform {
transform: scale(2);
}
.border {
border: 10px solid black;
}
.composited {
will-change: transform;
}
</style>
<div id='target' class='solid'></div>
)HTML");
}
INSTANTIATE_PAINT_TEST_SUITE_P(PaintAndRasterInvalidationTest);
class ScopedEnablePaintInvalidationTracing {
public:
ScopedEnablePaintInvalidationTracing() {
trace_event::EnableTracing(TRACE_DISABLED_BY_DEFAULT("blink.invalidation"));
}
~ScopedEnablePaintInvalidationTracing() { trace_event::DisableTracing(); }
};
TEST_P(PaintAndRasterInvalidationTest, TrackingForTracing) {
SetBodyInnerHTML(R"HTML(
<style>#target { width: 100px; height: 100px; background: blue }</style>
<div id="target"></div>
)HTML");
auto* target = GetDocument().getElementById("target");
auto& cc_layer =
RuntimeEnabledFeatures::CompositeAfterPaintEnabled()
? *GetDocument()
.View()
->GetPaintArtifactCompositor()
->RootLayer()
->children()[1]
: GetLayoutView().Layer()->GraphicsLayerBacking()->CcLayer();
{
ScopedEnablePaintInvalidationTracing tracing;
target->setAttribute(html_names::kStyleAttr, "height: 200px");
UpdateAllLifecyclePhasesForTest();
ASSERT_TRUE(cc_layer.debug_info());
EXPECT_EQ(1u, cc_layer.debug_info()->invalidations.size());
target->setAttribute(html_names::kStyleAttr, "height: 200px; width: 200px");
UpdateAllLifecyclePhasesForTest();
ASSERT_TRUE(cc_layer.debug_info());
EXPECT_EQ(2u, cc_layer.debug_info()->invalidations.size());
}
target->setAttribute(html_names::kStyleAttr, "height: 300px; width: 300px");
UpdateAllLifecyclePhasesForTest();
ASSERT_TRUE(cc_layer.debug_info());
// No new invalidations tracked.
EXPECT_EQ(2u, cc_layer.debug_info()->invalidations.size());
}
TEST_P(PaintAndRasterInvalidationTest, IncrementalInvalidationExpand) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
auto* object = target->GetLayoutObject();
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "width: 100px; height: 200px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(
RasterInvalidationInfo{object, object->DebugName(),
IntRect(50, 0, 50, 200),
PaintInvalidationReason::kIncremental},
RasterInvalidationInfo{
object, object->DebugName(), IntRect(0, 100, 100, 100),
PaintInvalidationReason::kIncremental}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, IncrementalInvalidationShrink) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
auto* object = target->GetLayoutObject();
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "width: 20px; height: 80px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(
RasterInvalidationInfo{object, object->DebugName(),
IntRect(20, 0, 30, 100),
PaintInvalidationReason::kIncremental},
RasterInvalidationInfo{
object, object->DebugName(), IntRect(0, 80, 50, 20),
PaintInvalidationReason::kIncremental}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, IncrementalInvalidationMixed) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
auto* object = target->GetLayoutObject();
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "width: 100px; height: 80px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(
RasterInvalidationInfo{object, object->DebugName(),
IntRect(50, 0, 50, 80),
PaintInvalidationReason::kIncremental},
RasterInvalidationInfo{
object, object->DebugName(), IntRect(0, 80, 50, 20),
PaintInvalidationReason::kIncremental}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, ResizeEmptyContent) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
// Make the box empty.
target->setAttribute(html_names::kClassAttr, "");
UpdateAllLifecyclePhasesForTest();
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "width: 100px; height: 80px");
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, SubpixelChange) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
auto* object = target->GetLayoutObject();
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr,
"width: 100.6px; height: 70.3px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(
RasterInvalidationInfo{object, object->DebugName(),
IntRect(0, 0, 50, 100),
PaintInvalidationReason::kGeometry},
RasterInvalidationInfo{object, object->DebugName(),
IntRect(0, 0, 101, 71),
PaintInvalidationReason::kGeometry}));
GetDocument().View()->SetTracksRasterInvalidations(false);
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "width: 50px; height: 100px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(
RasterInvalidationInfo{object, object->DebugName(),
IntRect(0, 0, 50, 100),
PaintInvalidationReason::kGeometry},
RasterInvalidationInfo{object, object->DebugName(),
IntRect(0, 0, 101, 71),
PaintInvalidationReason::kGeometry}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, SubpixelVisualRectChangeWithTransform) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
auto* object = target->GetLayoutObject();
target->setAttribute(html_names::kClassAttr, "solid transform");
UpdateAllLifecyclePhasesForTest();
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr,
"width: 100.6px; height: 70.3px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(
RasterInvalidationInfo{object, object->DebugName(),
IntRect(0, 0, 100, 200),
PaintInvalidationReason::kGeometry},
RasterInvalidationInfo{object, object->DebugName(),
IntRect(0, 0, 202, 142),
PaintInvalidationReason::kGeometry}));
GetDocument().View()->SetTracksRasterInvalidations(false);
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "width: 50px; height: 100px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(
RasterInvalidationInfo{object, object->DebugName(),
IntRect(0, 0, 100, 200),
PaintInvalidationReason::kGeometry},
RasterInvalidationInfo{object, object->DebugName(),
IntRect(0, 0, 202, 142),
PaintInvalidationReason::kGeometry}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, SubpixelWithinPixelsChange) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
LayoutObject* object = target->GetLayoutObject();
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr,
"margin-top: 0.6px; width: 50px; height: 99.3px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
object, object->DebugName(), IntRect(0, 0, 50, 100),
PaintInvalidationReason::kGeometry}));
GetDocument().View()->SetTracksRasterInvalidations(false);
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr,
"margin-top: 0.6px; width: 49.3px; height: 98.5px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
object, object->DebugName(), IntRect(0, 0, 50, 100),
PaintInvalidationReason::kGeometry}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, ResizeRotated) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
auto* object = target->GetLayoutObject();
target->setAttribute(html_names::kStyleAttr, "transform: rotate(45deg)");
UpdateAllLifecyclePhasesForTest();
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr,
"transform: rotate(45deg); width: 200px");
UpdateAllLifecyclePhasesForTest();
auto expected_rect = EnclosingIntRect(
TransformationMatrix().Rotate(45).MapRect(FloatRect(50, 0, 150, 100)));
expected_rect.Intersect(IntRect(0, 0, 800, 600));
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
object, object->DebugName(), expected_rect,
PaintInvalidationReason::kIncremental}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, ResizeRotatedChild) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(html_names::kStyleAttr,
"transform: rotate(45deg); width: 200px");
target->setInnerHTML(
"<div id=child style='width: 50px; height: 50px; background: "
"red'></div>");
UpdateAllLifecyclePhasesForTest();
Element* child = GetDocument().getElementById("child");
auto* child_object = child->GetLayoutObject();
GetDocument().View()->SetTracksRasterInvalidations(true);
child->setAttribute(html_names::kStyleAttr,
"width: 100px; height: 50px; background: red");
UpdateAllLifecyclePhasesForTest();
auto expected_rect = EnclosingIntRect(
TransformationMatrix().Rotate(45).MapRect(FloatRect(50, 0, 50, 50)));
expected_rect.Intersect(IntRect(0, 0, 800, 600));
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
child_object, child_object->DebugName(), expected_rect,
PaintInvalidationReason::kIncremental}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, CompositedLayoutViewResize) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(html_names::kClassAttr, "");
target->setAttribute(html_names::kStyleAttr, "height: 2000px");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(kBackgroundPaintInScrollingContents,
GetLayoutView().GetBackgroundPaintLocation());
// Resize the content.
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "height: 3000px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(
GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
&ViewScrollingBackgroundClient(),
ViewScrollingBackgroundClient().DebugName(),
IntRect(0, 2000, 800, 1000), PaintInvalidationReason::kIncremental}));
GetDocument().View()->SetTracksRasterInvalidations(false);
// Resize the viewport. No invalidation.
GetDocument().View()->SetTracksRasterInvalidations(true);
GetDocument().View()->Resize(800, 1000);
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, CompositedLayoutViewGradientResize) {
SetUpHTML(*this);
GetDocument().body()->setAttribute(html_names::kClassAttr, "gradient");
Element* target = GetDocument().getElementById("target");
target->setAttribute(html_names::kClassAttr, "");
target->setAttribute(html_names::kStyleAttr, "height: 2000px");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(kBackgroundPaintInScrollingContents,
GetLayoutView().GetBackgroundPaintLocation());
// Resize the content.
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "height: 3000px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(
GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
&ViewScrollingBackgroundClient(),
ViewScrollingBackgroundClient().DebugName(), IntRect(0, 0, 800, 3000),
PaintInvalidationReason::kBackground}));
GetDocument().View()->SetTracksRasterInvalidations(false);
// Resize the viewport. No invalidation.
GetDocument().View()->SetTracksRasterInvalidations(true);
GetDocument().View()->Resize(800, 1000);
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
GetDocument().View()->SetTracksRasterInvalidations(false);
}
static const LayoutBoxModelObject& EnclosingCompositedContainer(
const LayoutObject& layout_object) {
DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
return layout_object.PaintingLayer()
->EnclosingLayerForPaintInvalidationCrossingFrameBoundaries()
->GetLayoutObject();
}
TEST_P(PaintAndRasterInvalidationTest, NonCompositedLayoutViewResize) {
ScopedPreferNonCompositedScrollingForTest non_composited_scrolling(true);
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0 }
iframe { display: block; width: 100px; height: 100px; border: none; }
</style>
<iframe id='iframe'></iframe>
)HTML");
SetChildFrameHTML(R"HTML(
<style>
::-webkit-scrollbar { display: none }
body { margin: 0; background: green; height: 0 }
</style>
<div id='content' style='width: 200px; height: 200px'></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
Element* iframe = GetDocument().getElementById("iframe");
Element* content = ChildDocument().getElementById("content");
if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
EXPECT_EQ(GetLayoutView(),
EnclosingCompositedContainer(*content->GetLayoutObject()));
}
EXPECT_EQ(kBackgroundPaintInScrollingContents,
content->GetLayoutObject()
->View()
->ComputeBackgroundPaintLocationIfComposited());
EXPECT_EQ(kBackgroundPaintInGraphicsLayer,
content->GetLayoutObject()->View()->GetBackgroundPaintLocation());
// Resize the content.
GetDocument().View()->SetTracksRasterInvalidations(true);
content->setAttribute(html_names::kStyleAttr, "height: 500px");
UpdateAllLifecyclePhasesForTest();
// No invalidation because the changed part of layout overflow is clipped.
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
GetDocument().View()->SetTracksRasterInvalidations(false);
// Resize the iframe.
GetDocument().View()->SetTracksRasterInvalidations(true);
iframe->setAttribute(html_names::kStyleAttr, "height: 200px");
UpdateAllLifecyclePhasesForTest();
// The iframe doesn't have anything visible by itself, so we only issue
// raster invalidation for the frame contents.
const auto* client = content->GetLayoutObject()->View();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
client, client->DebugName(), IntRect(0, 100, 100, 100),
PaintInvalidationReason::kIncremental}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, FullInvalidationWithHTMLTransform) {
GetDocument().documentElement()->setAttribute(html_names::kStyleAttr,
"transform: scale(0.5)");
const DisplayItemClient* client =
&GetDocument()
.View()
->GetLayoutView()
->GetScrollableArea()
->GetScrollingBackgroundDisplayItemClient();
UpdateAllLifecyclePhasesForTest();
GetDocument().View()->SetTracksRasterInvalidations(true);
GetDocument().View()->Resize(WebSize(500, 500));
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(
RasterInvalidationInfo{client, client->DebugName(),
IntRect(0, 0, 500, 500),
PaintInvalidationReason::kBackground},
RasterInvalidationInfo{
client, client->DebugName(), IntRect(0, 0, 500, 500),
PaintInvalidationReason::kPaintProperty}));
}
TEST_P(PaintAndRasterInvalidationTest, NonCompositedLayoutViewGradientResize) {
ScopedPreferNonCompositedScrollingForTest non_composited_scrolling(true);
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0 }
iframe { display: block; width: 100px; height: 100px; border: none; }
</style>
<iframe id='iframe'></iframe>
)HTML");
SetChildFrameHTML(R"HTML(
<style>
::-webkit-scrollbar { display: none }
body {
margin: 0;
height: 0;
background-image: linear-gradient(blue, yellow);
}
</style>
<div id='content' style='width: 200px; height: 200px'></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
Element* iframe = GetDocument().getElementById("iframe");
Element* content = ChildDocument().getElementById("content");
if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
EXPECT_EQ(GetLayoutView(),
EnclosingCompositedContainer(*content->GetLayoutObject()));
}
// Resize the content.
GetDocument().View()->SetTracksRasterInvalidations(true);
content->setAttribute(html_names::kStyleAttr, "height: 500px");
UpdateAllLifecyclePhasesForTest();
const auto* client = content->GetLayoutObject()->View();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
client, client->DebugName(), IntRect(0, 0, 100, 100),
PaintInvalidationReason::kBackground}));
GetDocument().View()->SetTracksRasterInvalidations(false);
// Resize the iframe.
GetDocument().View()->SetTracksRasterInvalidations(true);
iframe->setAttribute(html_names::kStyleAttr, "height: 200px");
UpdateAllLifecyclePhasesForTest();
// The iframe doesn't have anything visible by itself, so we only issue
// raster invalidation for the frame contents.
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
client, client->DebugName(), IntRect(0, 0, 100, 200),
PaintInvalidationReason::kBackground}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest,
CompositedBackgroundAttachmentLocalResize) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(html_names::kClassAttr,
"solid composited scroll local-attachment border");
UpdateAllLifecyclePhasesForTest();
target->setInnerHTML(
"<div id=child style='width: 500px; height: 500px'></div>",
ASSERT_NO_EXCEPTION);
Element* child = GetDocument().getElementById("child");
UpdateAllLifecyclePhasesForTest();
auto* target_obj = To<LayoutBoxModelObject>(target->GetLayoutObject());
EXPECT_EQ(kBackgroundPaintInScrollingContents,
target_obj->GetBackgroundPaintLocation());
auto container_raster_invalidation_tracking =
[&]() -> const RasterInvalidationTracking* {
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return GetRasterInvalidationTracking(1);
return target_obj->Layer()
->GraphicsLayerBacking(target_obj)
->GetRasterInvalidationTracking();
};
auto contents_raster_invalidation_tracking =
[&]() -> const RasterInvalidationTracking* {
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return GetRasterInvalidationTracking(2);
return target_obj->Layer()
->GraphicsLayerBacking()
->GetRasterInvalidationTracking();
};
// Resize the content.
GetDocument().View()->SetTracksRasterInvalidations(true);
child->setAttribute(html_names::kStyleAttr, "width: 500px; height: 1000px");
UpdateAllLifecyclePhasesForTest();
// No invalidation on the container layer.
EXPECT_FALSE(container_raster_invalidation_tracking()->HasInvalidations());
// Incremental invalidation of background on contents layer.
const auto& client = target_obj->GetScrollableArea()
->GetScrollingBackgroundDisplayItemClient();
EXPECT_THAT(contents_raster_invalidation_tracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
&client, client.DebugName(), IntRect(0, 500, 500, 500),
PaintInvalidationReason::kIncremental}));
GetDocument().View()->SetTracksRasterInvalidations(false);
// Resize the container.
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "height: 200px");
UpdateAllLifecyclePhasesForTest();
// Border invalidated in the container layer.
EXPECT_THAT(container_raster_invalidation_tracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
target_obj, target_obj->DebugName(), IntRect(0, 0, 70, 220),
PaintInvalidationReason::kGeometry}));
// No invalidation on scrolling contents for container resize.
EXPECT_FALSE(contents_raster_invalidation_tracking()->HasInvalidations());
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest,
CompositedBackgroundAttachmentLocalGradientResize) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(html_names::kClassAttr,
"gradient composited scroll local-attachment border");
target->setInnerHTML(
"<div id='child' style='width: 500px; height: 500px'></div>",
ASSERT_NO_EXCEPTION);
Element* child = GetDocument().getElementById("child");
UpdateAllLifecyclePhasesForTest();
auto* target_obj = To<LayoutBoxModelObject>(target->GetLayoutObject());
auto container_raster_invalidation_tracking =
[&]() -> const RasterInvalidationTracking* {
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return GetRasterInvalidationTracking(1);
return target_obj->Layer()
->GraphicsLayerBacking(target_obj)
->GetRasterInvalidationTracking();
};
auto contents_raster_invalidation_tracking =
[&]() -> const RasterInvalidationTracking* {
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return GetRasterInvalidationTracking(2);
return target_obj->Layer()
->GraphicsLayerBacking()
->GetRasterInvalidationTracking();
};
// Resize the content.
GetDocument().View()->SetTracksRasterInvalidations(true);
child->setAttribute(html_names::kStyleAttr, "width: 500px; height: 1000px");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(kBackgroundPaintInScrollingContents,
target_obj->GetBackgroundPaintLocation());
// No invalidation on the container layer.
EXPECT_FALSE(container_raster_invalidation_tracking()->HasInvalidations());
// Full invalidation of background on contents layer because the gradient
// background is resized.
const auto& client = target_obj->GetScrollableArea()
->GetScrollingBackgroundDisplayItemClient();
EXPECT_THAT(contents_raster_invalidation_tracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
&client, client.DebugName(), IntRect(0, 0, 500, 1000),
PaintInvalidationReason::kBackground}));
GetDocument().View()->SetTracksRasterInvalidations(false);
// Resize the container.
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "height: 200px");
UpdateAllLifecyclePhasesForTest();
// Border invalidated in the container layer.
EXPECT_THAT(container_raster_invalidation_tracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
target_obj, target_obj->DebugName(), IntRect(0, 0, 70, 220),
PaintInvalidationReason::kGeometry}));
// No invalidation on scrolling contents for container resize.
EXPECT_FALSE(contents_raster_invalidation_tracking()->HasInvalidations());
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest,
NonCompositedBackgroundAttachmentLocalResize) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
auto* object = target->GetLayoutObject();
target->setAttribute(html_names::kClassAttr,
"translucent local-attachment scroll");
target->setInnerHTML(
"<div id=child style='width: 500px; height: 500px'></div>",
ASSERT_NO_EXCEPTION);
Element* child = GetDocument().getElementById("child");
UpdateAllLifecyclePhasesForTest();
if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
EXPECT_EQ(&GetLayoutView(), EnclosingCompositedContainer(*object));
EXPECT_EQ(kBackgroundPaintInScrollingContents,
To<LayoutBoxModelObject>(object)
->ComputeBackgroundPaintLocationIfComposited());
EXPECT_EQ(kBackgroundPaintInGraphicsLayer,
object->GetBackgroundPaintLocation());
// Resize the content.
GetDocument().View()->SetTracksRasterInvalidations(true);
child->setAttribute(html_names::kStyleAttr, "width: 500px; height: 1000px");
UpdateAllLifecyclePhasesForTest();
// No invalidation because the changed part is invisible.
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
// Resize the container.
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "height: 200px");
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
object, object->DebugName(), IntRect(0, 100, 50, 100),
PaintInvalidationReason::kIncremental}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, CompositedSolidBackgroundResize) {
// To trigger background painting on both container and contents layer.
// Note that the test may need update when we change the background paint
// location rules.
SetPreferCompositingToLCDText(false);
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(html_names::kClassAttr, "solid composited scroll");
target->setInnerHTML("<div style='height: 500px'></div>",
ASSERT_NO_EXCEPTION);
UpdateAllLifecyclePhasesForTest();
// Resize the scroller.
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "width: 100px");
UpdateAllLifecyclePhasesForTest();
auto* target_object = To<LayoutBoxModelObject>(target->GetLayoutObject());
EXPECT_EQ(
kBackgroundPaintInScrollingContents | kBackgroundPaintInGraphicsLayer,
target_object->GetBackgroundPaintLocation());
const auto* contents_raster_invalidation_tracking =
RuntimeEnabledFeatures::CompositeAfterPaintEnabled()
? GetRasterInvalidationTracking(2)
: target_object->Layer()
->GraphicsLayerBacking()
->GetRasterInvalidationTracking();
const auto& client = target_object->GetScrollableArea()
->GetScrollingBackgroundDisplayItemClient();
EXPECT_THAT(contents_raster_invalidation_tracking->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
&client, client.DebugName(), IntRect(50, 0, 50, 500),
PaintInvalidationReason::kIncremental}));
const auto* container_raster_invalidation_tracking =
RuntimeEnabledFeatures::CompositeAfterPaintEnabled()
? GetRasterInvalidationTracking(1)
: target_object->Layer()
->GraphicsLayerBacking(target_object)
->GetRasterInvalidationTracking();
EXPECT_THAT(
container_raster_invalidation_tracking->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
target_object, target_object->DebugName(), IntRect(50, 0, 50, 100),
PaintInvalidationReason::kIncremental}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
// Changing style in a way that changes overflow without layout should cause
// the layout view to possibly need a paint invalidation since we may have
// revealed additional background that can be scrolled into view.
TEST_P(PaintAndRasterInvalidationTest, RecalcOverflowInvalidatesBackground) {
GetDocument().GetPage()->GetSettings().SetViewportEnabled(true);
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<style type='text/css'>
body, html {
width: 100%;
height: 100%;
margin: 0px;
}
#container {
will-change: transform;
width: 100%;
height: 100%;
}
</style>
<div id='container'></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
ScrollableArea* scrollable_area = GetDocument().View()->LayoutViewport();
ASSERT_EQ(scrollable_area->MaximumScrollOffset().Height(), 0);
EXPECT_FALSE(
GetDocument().GetLayoutView()->ShouldCheckForPaintInvalidation());
Element* container = GetDocument().getElementById("container");
container->setAttribute(html_names::kStyleAttr,
"transform: translateY(1000px);");
GetDocument().UpdateStyleAndLayoutTree();
EXPECT_EQ(scrollable_area->MaximumScrollOffset().Height(), 1000);
EXPECT_TRUE(GetDocument().GetLayoutView()->ShouldCheckForPaintInvalidation());
}
TEST_P(PaintAndRasterInvalidationTest, DelayedFullPaintInvalidation) {
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0 }</style>
<div style='height: 4000px'></div>
<div id='target' style='width: 100px; height: 100px; background: blue'>
</div>
)HTML");
auto* target = GetLayoutObjectByElementId("target");
target->SetShouldDoFullPaintInvalidationWithoutGeometryChange(
PaintInvalidationReason::kForTesting);
target->SetShouldDelayFullPaintInvalidation();
EXPECT_FALSE(target->ShouldDoFullPaintInvalidation());
EXPECT_TRUE(target->ShouldDelayFullPaintInvalidation());
EXPECT_EQ(PaintInvalidationReason::kForTesting,
target->FullPaintInvalidationReason());
EXPECT_FALSE(target->ShouldCheckGeometryForPaintInvalidation());
EXPECT_TRUE(target->ShouldCheckForPaintInvalidation());
EXPECT_TRUE(target->Parent()->ShouldCheckForPaintInvalidation());
GetDocument().View()->SetTracksRasterInvalidations(true);
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
EXPECT_FALSE(target->ShouldDoFullPaintInvalidation());
EXPECT_TRUE(target->ShouldDelayFullPaintInvalidation());
EXPECT_EQ(PaintInvalidationReason::kForTesting,
target->FullPaintInvalidationReason());
EXPECT_FALSE(target->ShouldCheckGeometryForPaintInvalidation());
EXPECT_TRUE(target->ShouldCheckForPaintInvalidation());
EXPECT_TRUE(target->Parent()->ShouldCheckForPaintInvalidation());
GetDocument().View()->SetTracksRasterInvalidations(false);
GetDocument().View()->SetTracksRasterInvalidations(true);
// Scroll target into view.
GetDocument().domWindow()->scrollTo(0, 4000);
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(RasterInvalidationInfo{
target, target->DebugName(), IntRect(0, 4000, 100, 100),
PaintInvalidationReason::kForTesting}));
EXPECT_EQ(PaintInvalidationReason::kNone,
target->FullPaintInvalidationReason());
EXPECT_FALSE(target->ShouldDelayFullPaintInvalidation());
EXPECT_FALSE(target->ShouldCheckForPaintInvalidation());
EXPECT_FALSE(target->Parent()->ShouldCheckForPaintInvalidation());
EXPECT_FALSE(target->ShouldCheckGeometryForPaintInvalidation());
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, SVGHiddenContainer) {
SetBodyInnerHTML(R"HTML(
<svg style='position: absolute; top: 100px; left: 100px'>
<mask id='mask'>
<g transform='scale(2)'>
<rect id='mask-rect' x='11' y='22' width='33' height='44'/>
</g>
</mask>
<rect id='real-rect' x='55' y='66' width='7' height='8'
mask='url(#mask)'/>
</svg>
)HTML");
auto* mask_rect = GetLayoutObjectByElementId("mask-rect");
auto* real_rect = GetLayoutObjectByElementId("real-rect");
GetDocument().View()->SetTracksRasterInvalidations(true);
To<Element>(mask_rect->GetNode())->setAttribute("x", "20");
UpdateAllLifecyclePhasesForTest();
// Should invalidate raster for real_rect only.
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(
RasterInvalidationInfo{real_rect, real_rect->DebugName(),
IntRect(155, 166, 7, 8),
PaintInvalidationReason::kSubtree},
RasterInvalidationInfo{real_rect, real_rect->DebugName(),
IntRect(154, 165, 9, 10),
PaintInvalidationReason::kSubtree}));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, PaintPropertyChange) {
// TODO(wangxianzhu): See the TODO in CullRectUpdater::SetFragmentCullRects().
if (RuntimeEnabledFeatures::CullRectUpdateEnabled())
return;
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
auto* object = target->GetLayoutObject();
target->setAttribute(html_names::kClassAttr, "solid transform");
UpdateAllLifecyclePhasesForTest();
auto* layer = To<LayoutBoxModelObject>(object)->Layer();
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "transform: scale(3)");
UpdateAllLifecyclePhasesExceptPaint();
EXPECT_FALSE(layer->SelfNeedsRepaint());
const auto* transform =
object->FirstFragment().PaintProperties()->Transform();
EXPECT_TRUE(transform->Changed(
PaintPropertyChangeType::kChangedOnlySimpleValues, *transform->Parent()));
UpdateAllLifecyclePhasesForTest();
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAre(
RasterInvalidationInfo{
layer, layer->DebugName(), IntRect(0, 0, 100, 200),
PaintInvalidationReason::kPaintProperty},
RasterInvalidationInfo{
layer, layer->DebugName(), IntRect(0, 0, 150, 300),
PaintInvalidationReason::kPaintProperty}));
EXPECT_FALSE(transform->Changed(PaintPropertyChangeType::kChangedOnlyValues,
*transform->Parent()));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, ResizeContainerOfFixedSizeSVG) {
SetBodyInnerHTML(R"HTML(
<div id="target" style="width: 100px; height: 100px">
<svg viewBox="0 0 200 200" width="100" height="100">
<rect id="rect" width="100%" height="100%"/>
</svg>
</div>
)HTML");
Element* target = GetDocument().getElementById("target");
LayoutObject* rect = GetLayoutObjectByElementId("rect");
EXPECT_TRUE(static_cast<const DisplayItemClient*>(rect)->IsValid());
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "width: 200px; height: 200px");
UpdateAllLifecyclePhasesExceptPaint();
// We don't invalidate paint of the SVG rect.
EXPECT_TRUE(static_cast<const DisplayItemClient*>(rect)->IsValid());
UpdateAllLifecyclePhasesForTest();
// No raster invalidations because the resized-div doesn't paint anything by
// itself, and the svg is fixed sized.
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
GetDocument().View()->SetTracksRasterInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, ScrollingInvalidatesStickyOffset) {
SetBodyInnerHTML(R"HTML(
<div id="scroller" style="width:300px; height:200px; overflow:scroll">
<div id="sticky" style="position:sticky; top:50px;
width:50px; height:100px; background:red;">
<div id="inner" style="width:100px; height:50px; background:red;">
</div>
</div>
<div style="height:1000px;"></div>
</div>
)HTML");
Element* scroller = GetDocument().getElementById("scroller");
scroller->setScrollTop(100);
const auto* sticky = GetLayoutObjectByElementId("sticky");
EXPECT_TRUE(sticky->NeedsPaintPropertyUpdate());
EXPECT_EQ(PhysicalOffset(), sticky->FirstFragment().PaintOffset());
EXPECT_EQ(FloatSize(0, 50), sticky->FirstFragment()
.PaintProperties()
->StickyTranslation()
->Translation2D());
const auto* inner = GetLayoutObjectByElementId("inner");
EXPECT_EQ(PhysicalOffset(), inner->FirstFragment().PaintOffset());
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(sticky->NeedsPaintPropertyUpdate());
EXPECT_EQ(PhysicalOffset(), sticky->FirstFragment().PaintOffset());
EXPECT_EQ(FloatSize(0, 150), sticky->FirstFragment()
.PaintProperties()
->StickyTranslation()
->Translation2D());
EXPECT_EQ(PhysicalOffset(), inner->FirstFragment().PaintOffset());
}
TEST_P(PaintAndRasterInvalidationTest, ResizeElementWhichHasNonCustomResizer) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<style>
* { margin: 0;}
div {
width: 100px;
height: 100px;
background-color: red;
overflow: hidden;
resize: both;
}
</style>
<div id='target'></div>
)HTML");
auto* target = GetDocument().getElementById("target");
auto* object = target->GetLayoutObject();
GetDocument().View()->SetTracksRasterInvalidations(true);
target->setAttribute(html_names::kStyleAttr, "width: 200px");
UpdateAllLifecyclePhasesForTest();
Vector<RasterInvalidationInfo> invalidations;
// This is for DisplayItem::kResizerScrollHitTest.
invalidations.push_back(RasterInvalidationInfo{
object, object->DebugName(), IntRect(100, 0, 100, 100),
PaintInvalidationReason::kIncremental});
const auto& scroll_corner = To<LayoutBoxModelObject>(object)
->GetScrollableArea()
->GetScrollCornerDisplayItemClient();
invalidations.push_back(RasterInvalidationInfo{
&scroll_corner, scroll_corner.DebugName(), IntRect(93, 93, 7, 7),
PaintInvalidationReason::kGeometry});
invalidations.push_back(RasterInvalidationInfo{
&scroll_corner, scroll_corner.DebugName(), IntRect(193, 93, 7, 7),
PaintInvalidationReason::kGeometry});
EXPECT_THAT(GetRasterInvalidationTracking()->Invalidations(),
UnorderedElementsAreArray(invalidations));
GetDocument().View()->SetTracksRasterInvalidations(false);
}
class PaintInvalidatorTestClient : public RenderingTestChromeClient {
public:
void InvalidateRect(const IntRect&) override {
invalidation_recorded_ = true;
}
bool InvalidationRecorded() { return invalidation_recorded_; }
void ResetInvalidationRecorded() { invalidation_recorded_ = false; }
private:
bool invalidation_recorded_ = false;
};
class PaintInvalidatorCustomClientTest : public RenderingTest {
public:
PaintInvalidatorCustomClientTest()
: RenderingTest(MakeGarbageCollected<EmptyLocalFrameClient>()),
chrome_client_(MakeGarbageCollected<PaintInvalidatorTestClient>()) {}
PaintInvalidatorTestClient& GetChromeClient() const override {
return *chrome_client_;
}
bool InvalidationRecorded() { return chrome_client_->InvalidationRecorded(); }
void ResetInvalidationRecorded() {
chrome_client_->ResetInvalidationRecorded();
}
private:
Persistent<PaintInvalidatorTestClient> chrome_client_;
};
TEST_F(PaintInvalidatorCustomClientTest,
NonCompositedInvalidationChangeOpacity) {
// This test runs in a non-composited mode, so invalidations should
// be issued via InvalidateChromeClient.
SetBodyInnerHTML("<div id=target style='opacity: 0.99'></div>");
auto* target = GetDocument().getElementById("target");
ASSERT_TRUE(target);
ResetInvalidationRecorded();
target->setAttribute(html_names::kStyleAttr, "opacity: 0.98");
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(InvalidationRecorded());
}
TEST_F(PaintInvalidatorCustomClientTest,
NoInvalidationRepeatedUpdateLifecyleExceptPaint) {
SetBodyInnerHTML("<div id=target style='opacity: 0.99'></div>");
auto* target = GetDocument().getElementById("target");
ASSERT_TRUE(target);
ResetInvalidationRecorded();
target->setAttribute(html_names::kStyleAttr, "opacity: 0.98");
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
DocumentUpdateReason::kTest);
EXPECT_TRUE(
GetDocument().View()->GetLayoutView()->Layer()->DescendantNeedsRepaint());
EXPECT_TRUE(InvalidationRecorded());
ResetInvalidationRecorded();
// Let PrePaintTreeWalk do something instead of no-op.
GetDocument().View()->SetNeedsPaintPropertyUpdate();
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
DocumentUpdateReason::kTest);
// The layer DescendantNeedsRepaint flag is only cleared after paint.
EXPECT_TRUE(
GetDocument().View()->GetLayoutView()->Layer()->DescendantNeedsRepaint());
EXPECT_FALSE(InvalidationRecorded());
}
} // namespace blink