blob: 99647af42a306a19d0c09c57a504fd43c519b182 [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"
namespace blink {
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;
}
.background {
background: blue;
}
.solid-composited-scroller {
overflow: scroll;
will-change: transform;
background: blue;
}
.local-background {
background-attachment: local;
overflow: scroll;
}
.gradient {
background-image: linear-gradient(blue, yellow);
}
.transform {
transform: scale(2);
}
</style>
<div id='target' class='background'></div>
)HTML");
}
INSTANTIATE_PAINT_TEST_CASE_P(PaintAndRasterInvalidationTest);
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");
{
// This is equivalent to enabling disabled-by-default-blink.invalidation
// for tracing.
ScopedPaintUnderInvalidationCheckingForTest checking(true);
target->setAttribute(HTMLNames::styleAttr, "height: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_THAT(GetCcLayerClient()->TakeDebugInfo(GetCcLayer())->ToString(),
testing::MatchesRegex(
"\\{\"layer_name\":.*\"annotated_invalidation_rects\":\\["
"\\{\"geometry_rect\":\\[8,108,100,100\\],"
"\"reason\":\"incremental\","
"\"client\":\"LayoutBlockFlow DIV id='target'\"\\}\\]\\}"));
target->setAttribute(HTMLNames::styleAttr, "height: 200px; width: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_THAT(GetCcLayerClient()->TakeDebugInfo(GetCcLayer())->ToString(),
testing::MatchesRegex(
"\\{\"layer_name\":.*\"annotated_invalidation_rects\":\\["
"\\{\"geometry_rect\":\\[108,8,100,200\\],"
"\"reason\":\"incremental\","
"\"client\":\"LayoutBlockFlow DIV id='target'\"\\}\\]\\}"));
}
target->setAttribute(HTMLNames::styleAttr, "height: 300px; width: 300px");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(std::string::npos, GetCcLayerClient()
->TakeDebugInfo(GetCcLayer())
->ToString()
.find("invalidation_rects"));
}
TEST_P(PaintAndRasterInvalidationTest, IncrementalInvalidationExpand) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr, "width: 100px; height: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto& raster_invalidations =
GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(2u, raster_invalidations.size());
EXPECT_EQ(IntRect(50, 0, 50, 200), raster_invalidations[0].rect);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
raster_invalidations[0].reason);
EXPECT_EQ(IntRect(0, 100, 100, 100), raster_invalidations[1].rect);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
raster_invalidations[1].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, IncrementalInvalidationShrink) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr, "width: 20px; height: 80px");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto& raster_invalidations =
GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(2u, raster_invalidations.size());
EXPECT_EQ(IntRect(20, 0, 30, 100), raster_invalidations[0].rect);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
raster_invalidations[0].reason);
EXPECT_EQ(IntRect(0, 80, 50, 20), raster_invalidations[1].rect);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
raster_invalidations[1].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, IncrementalInvalidationMixed) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr, "width: 100px; height: 80px");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto& raster_invalidations =
GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(2u, raster_invalidations.size());
EXPECT_EQ(IntRect(50, 0, 50, 80), raster_invalidations[0].rect);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
raster_invalidations[0].reason);
EXPECT_EQ(IntRect(0, 80, 50, 20), raster_invalidations[1].rect);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
raster_invalidations[1].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, SubpixelVisualRectChagne) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr, "width: 100.6px; height: 70.3px");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto* raster_invalidations =
&GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(2u, raster_invalidations->size());
EXPECT_EQ(IntRect(0, 0, 50, 100), (*raster_invalidations)[0].rect);
EXPECT_EQ(PaintInvalidationReason::kGeometry,
(*raster_invalidations)[0].reason);
EXPECT_EQ(IntRect(0, 0, 101, 71), (*raster_invalidations)[1].rect);
EXPECT_EQ(PaintInvalidationReason::kGeometry,
(*raster_invalidations)[1].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr, "width: 50px; height: 100px");
GetDocument().View()->UpdateAllLifecyclePhases();
raster_invalidations = &GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(2u, raster_invalidations->size());
EXPECT_EQ(IntRect(0, 0, 101, 71), (*raster_invalidations)[0].rect);
EXPECT_EQ(PaintInvalidationReason::kGeometry,
(*raster_invalidations)[0].reason);
EXPECT_EQ(IntRect(0, 0, 50, 100), (*raster_invalidations)[1].rect);
EXPECT_EQ(PaintInvalidationReason::kGeometry,
(*raster_invalidations)[1].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, SubpixelVisualRectChangeWithTransform) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::classAttr, "background transform");
GetDocument().View()->UpdateAllLifecyclePhases();
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr, "width: 100.6px; height: 70.3px");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto* raster_invalidations =
&GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(2u, raster_invalidations->size());
EXPECT_EQ(IntRect(0, 0, 100, 200), (*raster_invalidations)[0].rect);
EXPECT_EQ(PaintInvalidationReason::kGeometry,
(*raster_invalidations)[0].reason);
EXPECT_EQ(IntRect(0, 0, 202, 142), (*raster_invalidations)[1].rect);
EXPECT_EQ(PaintInvalidationReason::kGeometry,
(*raster_invalidations)[1].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr, "width: 50px; height: 100px");
GetDocument().View()->UpdateAllLifecyclePhases();
raster_invalidations = &GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(2u, raster_invalidations->size());
EXPECT_EQ(IntRect(0, 0, 202, 142), (*raster_invalidations)[0].rect);
EXPECT_EQ(PaintInvalidationReason::kGeometry,
(*raster_invalidations)[0].reason);
EXPECT_EQ(IntRect(0, 0, 100, 200), (*raster_invalidations)[1].rect);
EXPECT_EQ(PaintInvalidationReason::kGeometry,
(*raster_invalidations)[1].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, SubpixelWithinPixelsChange) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
LayoutObject* target_object = target->GetLayoutObject();
EXPECT_EQ(LayoutRect(0, 0, 50, 100),
target_object->FirstFragment().VisualRect());
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr,
"margin-top: 0.6px; width: 50px; height: 99.3px");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(LayoutRect(0, 0, 50, 100),
target_object->FirstFragment().VisualRect());
const auto* raster_invalidations =
&GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, raster_invalidations->size());
EXPECT_EQ(IntRect(0, 0, 50, 100), (*raster_invalidations)[0].rect);
EXPECT_EQ(PaintInvalidationReason::kGeometry,
(*raster_invalidations)[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr,
"margin-top: 0.6px; width: 49.3px; height: 98.5px");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(LayoutRect(0, 0, 50, 100),
target_object->FirstFragment().VisualRect());
raster_invalidations = &GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, raster_invalidations->size());
EXPECT_EQ(IntRect(0, 0, 50, 100), (*raster_invalidations)[0].rect);
EXPECT_EQ(PaintInvalidationReason::kGeometry,
(*raster_invalidations)[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, ResizeRotated) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::styleAttr, "transform: rotate(45deg)");
GetDocument().View()->UpdateAllLifecyclePhases();
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr,
"transform: rotate(45deg); width: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto* raster_invalidations =
&GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, raster_invalidations->size());
auto expected_rect = EnclosingIntRect(
TransformationMatrix().Rotate(45).MapRect(FloatRect(50, 0, 150, 100)));
expected_rect.Intersect(IntRect(0, 0, 800, 600));
EXPECT_EQ(expected_rect, (*raster_invalidations)[0].rect);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
(*raster_invalidations)[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, ResizeRotatedChild) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::styleAttr,
"transform: rotate(45deg); width: 200px");
target->SetInnerHTMLFromString(
"<div id=child style='width: 50px; height: 50px; background: "
"red'></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
Element* child = GetDocument().getElementById("child");
GetDocument().View()->SetTracksPaintInvalidations(true);
child->setAttribute(HTMLNames::styleAttr,
"width: 100px; height: 50px; background: red");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto* raster_invalidations =
&GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, raster_invalidations->size());
auto expected_rect = EnclosingIntRect(
TransformationMatrix().Rotate(45).MapRect(FloatRect(50, 0, 50, 50)));
expected_rect.Intersect(IntRect(0, 0, 800, 600));
EXPECT_EQ(expected_rect, (*raster_invalidations)[0].rect);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
(*raster_invalidations)[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, CompositedLayoutViewResize) {
// TODO(crbug.com/732611): Fix SPv2 for scrolling contents layer.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::classAttr, "");
target->setAttribute(HTMLNames::styleAttr, "height: 2000px");
GetDocument().View()->UpdateAllLifecyclePhases();
// Resize the content.
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr, "height: 3000px");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto& raster_invalidations =
GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, raster_invalidations.size());
EXPECT_EQ(IntRect(0, 2000, 800, 1000), raster_invalidations[0].rect);
EXPECT_EQ(static_cast<const DisplayItemClient*>(
ViewScrollingContentsDisplayItemClient()),
raster_invalidations[0].client);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
raster_invalidations[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
// Resize the viewport. No paint invalidation.
GetDocument().View()->SetTracksPaintInvalidations(true);
GetDocument().View()->Resize(800, 1000);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, CompositedLayoutViewGradientResize) {
// TODO(crbug.com/732611): Fix SPv2 for scrolling contents layer.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
SetUpHTML(*this);
GetDocument().body()->setAttribute(HTMLNames::classAttr, "gradient");
Element* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::classAttr, "");
target->setAttribute(HTMLNames::styleAttr, "height: 2000px");
GetDocument().View()->UpdateAllLifecyclePhases();
// Resize the content.
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr, "height: 3000px");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto& raster_invalidations =
GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, raster_invalidations.size());
EXPECT_EQ(IntRect(0, 0, 800, 3000), raster_invalidations[0].rect);
EXPECT_EQ(static_cast<const DisplayItemClient*>(
ViewScrollingContentsDisplayItemClient()),
raster_invalidations[0].client);
EXPECT_EQ(PaintInvalidationReason::kBackgroundOnScrollingContentsLayer,
raster_invalidations[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
// Resize the viewport. No paint invalidation.
GetDocument().View()->SetTracksPaintInvalidations(true);
GetDocument().View()->Resize(800, 1000);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, NonCompositedLayoutViewResize) {
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");
GetDocument().View()->UpdateAllLifecyclePhases();
Element* iframe = GetDocument().getElementById("iframe");
Element* content = ChildDocument().getElementById("content");
EXPECT_EQ(GetLayoutView(),
content->GetLayoutObject()->ContainerForPaintInvalidation());
// Resize the content.
GetDocument().View()->SetTracksPaintInvalidations(true);
content->setAttribute(HTMLNames::styleAttr, "height: 500px");
GetDocument().View()->UpdateAllLifecyclePhases();
// No invalidation because the changed part of layout overflow is clipped.
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
GetDocument().View()->SetTracksPaintInvalidations(false);
// Resize the iframe.
GetDocument().View()->SetTracksPaintInvalidations(true);
iframe->setAttribute(HTMLNames::styleAttr, "height: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
// The iframe doesn't have anything visible by itself, so we only issue raster
// invalidation for the frame contents.
const auto& raster_invalidations =
GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, raster_invalidations.size());
EXPECT_EQ(
static_cast<const DisplayItemClient*>(content->GetLayoutObject()->View()),
raster_invalidations[0].client);
EXPECT_EQ(IntRect(0, 100, 100, 100), raster_invalidations[0].rect);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
raster_invalidations[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, NonCompositedLayoutViewGradientResize) {
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");
GetDocument().View()->UpdateAllLifecyclePhases();
Element* iframe = GetDocument().getElementById("iframe");
Element* content = ChildDocument().getElementById("content");
LayoutView* frame_layout_view = content->GetLayoutObject()->View();
EXPECT_EQ(GetLayoutView(),
content->GetLayoutObject()->ContainerForPaintInvalidation());
// Resize the content.
GetDocument().View()->SetTracksPaintInvalidations(true);
content->setAttribute(HTMLNames::styleAttr, "height: 500px");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto* raster_invalidations =
&GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, raster_invalidations->size());
EXPECT_EQ(IntRect(0, 0, 100, 100), (*raster_invalidations)[0].rect);
EXPECT_EQ(static_cast<const DisplayItemClient*>(frame_layout_view),
(*raster_invalidations)[0].client);
EXPECT_EQ(PaintInvalidationReason::kBackground,
(*raster_invalidations)[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
// Resize the iframe.
GetDocument().View()->SetTracksPaintInvalidations(true);
iframe->setAttribute(HTMLNames::styleAttr, "height: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
raster_invalidations = &GetRasterInvalidationTracking()->Invalidations();
// The iframe doesn't have anything visible by itself, so we only issue raster
// invalidation for the frame contents.
ASSERT_EQ(1u, raster_invalidations->size());
EXPECT_EQ(static_cast<const DisplayItemClient*>(frame_layout_view),
(*raster_invalidations)[0].client);
EXPECT_EQ(IntRect(0, 0, 100, 200), (*raster_invalidations)[0].rect);
EXPECT_EQ(PaintInvalidationReason::kGeometry,
(*raster_invalidations)[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest,
CompositedBackgroundAttachmentLocalResize) {
// TODO(crbug.com/732611): Fix SPv2 for scrolling contents layer.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::classAttr, "background local-background");
target->setAttribute(HTMLNames::styleAttr, "will-change: transform");
target->SetInnerHTMLFromString(
"<div id=child style='width: 500px; height: 500px'></div>",
ASSERT_NO_EXCEPTION);
Element* child = GetDocument().getElementById("child");
GetDocument().View()->UpdateAllLifecyclePhases();
auto* target_obj = ToLayoutBoxModelObject(target->GetLayoutObject());
auto container_raster_invalidation_tracking =
[&]() -> const RasterInvalidationTracking* {
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return GetRasterInvalidationTracking(1);
return target_obj->Layer()
->GraphicsLayerBacking(target_obj)
->GetRasterInvalidationTracking();
};
auto contents_raster_invalidation_tracking =
[&]() -> const RasterInvalidationTracking* {
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return GetRasterInvalidationTracking(2);
return target_obj->Layer()
->GraphicsLayerBacking()
->GetRasterInvalidationTracking();
};
// Resize the content.
GetDocument().View()->SetTracksPaintInvalidations(true);
child->setAttribute(HTMLNames::styleAttr, "width: 500px; height: 1000px");
GetDocument().View()->UpdateAllLifecyclePhases();
// No invalidation on the container layer.
EXPECT_FALSE(container_raster_invalidation_tracking()->HasInvalidations());
// Incremental invalidation of background on contents layer.
const auto& contents_raster_invalidations =
contents_raster_invalidation_tracking()->Invalidations();
ASSERT_EQ(1u, contents_raster_invalidations.size());
EXPECT_EQ(IntRect(0, 500, 500, 500), contents_raster_invalidations[0].rect);
EXPECT_EQ(static_cast<const DisplayItemClient*>(
target_obj->Layer()->GraphicsLayerBacking()),
contents_raster_invalidations[0].client);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
contents_raster_invalidations[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
// Resize the container.
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr,
"will-change: transform; height: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
// No invalidation for composited layer resize.
EXPECT_FALSE(container_raster_invalidation_tracking()->HasInvalidations());
EXPECT_FALSE(contents_raster_invalidation_tracking()->HasInvalidations());
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest,
CompositedBackgroundAttachmentLocalGradientResize) {
// TODO(crbug.com/732611): Fix SPv2 for scrolling contents layer.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::classAttr, "local-background gradient");
target->setAttribute(HTMLNames::styleAttr, "will-change: transform");
target->SetInnerHTMLFromString(
"<div id='child' style='width: 500px; height: 500px'></div>",
ASSERT_NO_EXCEPTION);
Element* child = GetDocument().getElementById("child");
GetDocument().View()->UpdateAllLifecyclePhases();
// Resize the content.
GetDocument().View()->SetTracksPaintInvalidations(true);
child->setAttribute(HTMLNames::styleAttr, "width: 500px; height: 1000px");
GetDocument().View()->UpdateAllLifecyclePhases();
LayoutBoxModelObject* target_obj =
ToLayoutBoxModelObject(target->GetLayoutObject());
GraphicsLayer* container_layer =
target_obj->Layer()->GraphicsLayerBacking(target_obj);
GraphicsLayer* contents_layer = target_obj->Layer()->GraphicsLayerBacking();
// No invalidation on the container layer.
EXPECT_FALSE(
container_layer->GetRasterInvalidationTracking()->HasInvalidations());
// Full invalidation of background on contents layer because the gradient
// background is resized.
const auto& contents_raster_invalidations =
contents_layer->GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, contents_raster_invalidations.size());
EXPECT_EQ(IntRect(0, 0, 500, 1000), contents_raster_invalidations[0].rect);
EXPECT_EQ(static_cast<const DisplayItemClient*>(contents_layer),
contents_raster_invalidations[0].client);
EXPECT_EQ(PaintInvalidationReason::kBackgroundOnScrollingContentsLayer,
contents_raster_invalidations[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
// Resize the container.
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr,
"will-change: transform; height: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
// No explicit raster invalidation for composited layer resize.
EXPECT_FALSE(
container_layer->GetRasterInvalidationTracking()->HasInvalidations());
EXPECT_FALSE(
contents_layer->GetRasterInvalidationTracking()->HasInvalidations());
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest,
NonCompositedBackgroundAttachmentLocalResize) {
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::classAttr, "background local-background");
target->SetInnerHTMLFromString(
"<div id=child style='width: 500px; height: 500px'></div>",
ASSERT_NO_EXCEPTION);
Element* child = GetDocument().getElementById("child");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(&GetLayoutView(),
&target->GetLayoutObject()->ContainerForPaintInvalidation());
// Resize the content.
GetDocument().View()->SetTracksPaintInvalidations(true);
child->setAttribute(HTMLNames::styleAttr, "width: 500px; height: 1000px");
GetDocument().View()->UpdateAllLifecyclePhases();
// No invalidation because the changed part is invisible.
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
// Resize the container.
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr, "height: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto& raster_invalidations =
GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, raster_invalidations.size());
EXPECT_EQ(IntRect(0, 100, 50, 100), raster_invalidations[0].rect);
EXPECT_EQ(static_cast<const DisplayItemClient*>(target->GetLayoutObject()),
raster_invalidations[0].client);
EXPECT_EQ(PaintInvalidationReason::kIncremental,
raster_invalidations[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, CompositedSolidBackgroundResize) {
// TODO(crbug.com/732611): Fix SPv2 for scrolling contents layer.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
SetUpHTML(*this);
Element* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::classAttr, "solid-composited-scroller");
target->SetInnerHTMLFromString("<div style='height: 500px'></div>",
ASSERT_NO_EXCEPTION);
GetDocument().View()->UpdateAllLifecyclePhases();
// Resize the scroller.
GetDocument().View()->SetTracksPaintInvalidations(true);
target->setAttribute(HTMLNames::styleAttr, "width: 100px");
GetDocument().View()->UpdateAllLifecyclePhases();
LayoutBoxModelObject* target_object =
ToLayoutBoxModelObject(target->GetLayoutObject());
GraphicsLayer* scrolling_contents_layer =
target_object->Layer()->GraphicsLayerBacking();
const auto& invalidations =
scrolling_contents_layer->GetRasterInvalidationTracking()
->Invalidations();
ASSERT_EQ(1u, invalidations.size());
EXPECT_EQ(IntRect(50, 0, 50, 500), invalidations[0].rect);
EXPECT_EQ(static_cast<const DisplayItemClient*>(scrolling_contents_layer),
invalidations[0].client);
EXPECT_EQ(PaintInvalidationReason::kIncremental, invalidations[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(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");
GetDocument().View()->UpdateAllLifecyclePhases();
ScrollableArea* scrollable_area = GetDocument().View()->LayoutViewport();
ASSERT_EQ(scrollable_area->MaximumScrollOffset().Height(), 0);
EXPECT_FALSE(GetDocument().GetLayoutView()->MayNeedPaintInvalidation());
Element* container = GetDocument().getElementById("container");
container->setAttribute(HTMLNames::styleAttr,
"transform: translateY(1000px);");
GetDocument().UpdateStyleAndLayoutTree();
EXPECT_EQ(scrollable_area->MaximumScrollOffset().Height(), 1000);
EXPECT_TRUE(GetDocument().GetLayoutView()->MayNeedPaintInvalidation());
}
TEST_P(PaintAndRasterInvalidationTest,
UpdateVisualRectOnFrameBorderWidthChange) {
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 10px }
iframe { width: 100px; height: 100px; border: none; }
</style>
<iframe id='iframe'></iframe>
)HTML");
Element* iframe = GetDocument().getElementById("iframe");
LayoutView* child_layout_view = ChildDocument().GetLayoutView();
EXPECT_EQ(GetDocument().GetLayoutView(),
&child_layout_view->ContainerForPaintInvalidation());
EXPECT_EQ(LayoutRect(0, 0, 100, 100),
child_layout_view->FirstFragment().VisualRect());
iframe->setAttribute(HTMLNames::styleAttr, "border: 20px solid blue");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(GetDocument().GetLayoutView(),
&child_layout_view->ContainerForPaintInvalidation());
EXPECT_EQ(LayoutRect(0, 0, 100, 100),
child_layout_view->FirstFragment().VisualRect());
};
TEST_P(PaintAndRasterInvalidationTest, DelayedFullPaintInvalidation) {
// TODO(crbug.com/732611): Fix SPv2 for scrolling contents layer.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
EnableCompositing();
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::kDelayedFull);
EXPECT_EQ(PaintInvalidationReason::kDelayedFull,
target->FullPaintInvalidationReason());
EXPECT_FALSE(target->NeedsPaintOffsetAndVisualRectUpdate());
GetDocument().View()->SetTracksPaintInvalidations(true);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(GetRasterInvalidationTracking()->HasInvalidations());
EXPECT_EQ(PaintInvalidationReason::kDelayedFull,
target->FullPaintInvalidationReason());
EXPECT_FALSE(target->NeedsPaintOffsetAndVisualRectUpdate());
GetDocument().View()->SetTracksPaintInvalidations(false);
GetDocument().View()->SetTracksPaintInvalidations(true);
// Scroll target into view.
GetDocument().domWindow()->scrollTo(0, 4000);
GetDocument().View()->UpdateAllLifecyclePhases();
const auto& raster_invalidations =
GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, raster_invalidations.size());
EXPECT_EQ(PaintInvalidationReason::kNone,
target->FullPaintInvalidationReason());
EXPECT_EQ(IntRect(0, 4000, 100, 100), raster_invalidations[0].rect);
EXPECT_EQ(PaintInvalidationReason::kFull, raster_invalidations[0].reason);
EXPECT_FALSE(target->NeedsPaintOffsetAndVisualRectUpdate());
GetDocument().View()->SetTracksPaintInvalidations(false);
};
TEST_P(PaintAndRasterInvalidationTest, SVGHiddenContainer) {
EnableCompositing();
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");
// mask_rect's visual rect is in coordinates of the mask.
auto* mask_rect = GetLayoutObjectByElementId("mask-rect");
EXPECT_EQ(LayoutRect(), mask_rect->FirstFragment().VisualRect());
// real_rect's visual rect is in coordinates of its paint invalidation
// container (the view).
auto* real_rect = GetLayoutObjectByElementId("real-rect");
EXPECT_EQ(LayoutRect(55, 66, 7, 8), real_rect->FirstFragment().VisualRect());
GetDocument().View()->SetTracksPaintInvalidations(true);
ToElement(mask_rect->GetNode())->setAttribute("x", "20");
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint();
EXPECT_EQ(PaintInvalidationReason::kFull,
real_rect->GetPaintInvalidationReason());
// mask_rect has PaintInvalidationReason::kFull because it is not cached by
// any PaintController.
EXPECT_EQ(PaintInvalidationReason::kFull,
mask_rect->GetPaintInvalidationReason());
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(LayoutRect(), mask_rect->FirstFragment().VisualRect());
EXPECT_EQ(LayoutRect(55, 66, 7, 8), real_rect->FirstFragment().VisualRect());
// Should invalidate raster for real_rect only.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
// SPv2 creates composited layers for the rect and its mask.
const auto& rect_raster_invalidations =
GetRasterInvalidationTracking(1)->Invalidations();
ASSERT_EQ(1u, rect_raster_invalidations.size());
EXPECT_EQ(IntRect(0, 0, 7, 8), rect_raster_invalidations[0].rect);
EXPECT_EQ(PaintInvalidationReason::kFull,
rect_raster_invalidations[0].reason);
const auto& mask_raster_invalidations =
GetRasterInvalidationTracking(2)->Invalidations();
ASSERT_EQ(1u, mask_raster_invalidations.size());
EXPECT_EQ(IntRect(0, 0, 7, 8), mask_raster_invalidations[0].rect);
EXPECT_EQ(PaintInvalidationReason::kFull,
mask_raster_invalidations[0].reason);
} else {
const auto& raster_invalidations =
GetRasterInvalidationTracking()->Invalidations();
// The first is for the rect itself, the second is for its mask.
ASSERT_EQ(2u, raster_invalidations.size());
EXPECT_EQ(IntRect(155, 166, 7, 8), raster_invalidations[0].rect);
EXPECT_EQ(PaintInvalidationReason::kFull, raster_invalidations[0].reason);
EXPECT_EQ(IntRect(155, 166, 7, 8), raster_invalidations[1].rect);
EXPECT_EQ(PaintInvalidationReason::kFull, raster_invalidations[1].reason);
}
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_P(PaintAndRasterInvalidationTest, UpdateVisualRectWhenPrinting) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0;}
span {
display: inline-block;
width: 150px;
height: 20px;
background: rebeccapurple;
}
</style>
<div><span id="a"></span><span id="b"></span><span id="c"></div>
)HTML");
auto* a = GetDocument().getElementById("a")->GetLayoutObject();
EXPECT_EQ(LayoutRect(0, 0, 150, 20), a->FirstFragment().VisualRect());
auto* b = GetDocument().getElementById("b")->GetLayoutObject();
EXPECT_EQ(LayoutRect(150, 0, 150, 20), b->FirstFragment().VisualRect());
auto* c = GetDocument().getElementById("c")->GetLayoutObject();
EXPECT_EQ(LayoutRect(300, 0, 150, 20), c->FirstFragment().VisualRect());
// Print the page with a width of 400px which will require wrapping 'c'.
FloatSize page_size(400, 200);
GetFrame().StartPrinting(page_size, page_size, 1);
GetDocument().View()->UpdateLifecyclePhasesForPrinting();
EXPECT_EQ(LayoutRect(0, 0, 150, 20), a->FirstFragment().VisualRect());
EXPECT_EQ(LayoutRect(150, 0, 150, 20), b->FirstFragment().VisualRect());
// 'c' should be on the next line.
EXPECT_EQ(LayoutRect(0, 20, 150, 20), c->FirstFragment().VisualRect());
GetFrame().EndPrinting();
GetDocument().View()->UpdateLifecyclePhasesForPrinting();
EXPECT_EQ(LayoutRect(0, 0, 150, 20), a->FirstFragment().VisualRect());
EXPECT_EQ(LayoutRect(150, 0, 150, 20), b->FirstFragment().VisualRect());
EXPECT_EQ(LayoutRect(300, 0, 150, 20), c->FirstFragment().VisualRect());
};
class PaintInvalidatorTestClient : public EmptyChromeClient {
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(EmptyLocalFrameClient::Create()),
chrome_client_(new 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(R"HTML(
<div id=target style="opacity: 0.99"></div>
)HTML");
auto* target = GetDocument().getElementById("target");
ASSERT_TRUE(target);
ResetInvalidationRecorded();
target->setAttribute(HTMLNames::styleAttr, "opacity: 0.98");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_TRUE(InvalidationRecorded());
}
} // namespace blink