blob: f3654483cdfb5c42aded6e3bf61e2f1afa95fa5b [file] [log] [blame]
// Copyright 2017 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 "core/frame/FrameView.h"
#include "core/layout/LayoutTestHelper.h"
#include "core/layout/LayoutView.h"
#include "core/paint/PaintLayer.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/graphics/paint/RasterInvalidationTracking.h"
#include "platform/testing/RuntimeEnabledFeaturesTestHelpers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
class PaintInvalidationTest : public ::testing::WithParamInterface<bool>,
private ScopedRootLayerScrollingForTest,
public RenderingTest {
public:
PaintInvalidationTest()
: ScopedRootLayerScrollingForTest(GetParam()),
RenderingTest(SingleChildLocalFrameClient::Create()) {}
protected:
const RasterInvalidationTracking* GetRasterInvalidationTracking() const {
// TODO(wangxianzhu): Test SPv2.
return GetLayoutView()
.Layer()
->GraphicsLayerBacking()
->GetRasterInvalidationTracking();
}
};
INSTANTIATE_TEST_CASE_P(All, PaintInvalidationTest, ::testing::Bool());
// 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(PaintInvalidationTest, RecalcOverflowInvalidatesBackground) {
GetDocument().GetPage()->GetSettings().SetViewportEnabled(true);
SetBodyInnerHTML(
"<!DOCTYPE html>"
"<style type='text/css'>"
" body, html {"
" width: 100%;"
" height: 100%;"
" margin: 0px;"
" }"
" #container {"
" width: 100%;"
" height: 100%;"
" }"
"</style>"
"<div id='container'></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
ScrollableArea* scrollable_area = GetDocument().View();
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(PaintInvalidationTest, UpdateVisualRectOnFrameBorderWidthChange) {
// TODO(wangxianzhu): enable for SPv2.
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
return;
SetBodyInnerHTML(
"<style>"
" body { margin: 10px }"
" iframe { width: 100px; height: 100px; border: none; }"
"</style>"
"<iframe id='iframe'></iframe>");
Element* iframe = GetDocument().getElementById("iframe");
LayoutView* child_layout_view = ChildDocument().GetLayoutView();
EXPECT_EQ(GetDocument().GetLayoutView(),
&child_layout_view->ContainerForPaintInvalidation());
EXPECT_EQ(LayoutRect(10, 10, 100, 100), child_layout_view->VisualRect());
iframe->setAttribute(HTMLNames::styleAttr, "border: 20px solid blue");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(GetDocument().GetLayoutView(),
&child_layout_view->ContainerForPaintInvalidation());
EXPECT_EQ(LayoutRect(30, 30, 100, 100), child_layout_view->VisualRect());
};
// This is a simplified test case for crbug.com/704182. It ensures no repaint
// on transform change causing no visual change.
TEST_P(PaintInvalidationTest, InvisibleTransformUnderFixedOnScroll) {
EnableCompositing();
SetBodyInnerHTML(
"<style>"
" #fixed {"
" position: fixed;"
" top: 0;"
" left: 0;"
" width: 100px;"
" height: 100px;"
" background-color: blue;"
" }"
" #transform {"
" width: 100px;"
" height: 100px;"
" background-color: yellow;"
" will-change: transform;"
" transform: translate(10px, 20px);"
" }"
"</style>"
"<div style='height: 2000px'></div>"
"<div id='fixed' style='visibility: hidden'>"
" <div id='transform'></div>"
"</div>");
auto& fixed = *GetDocument().getElementById("fixed");
const auto& fixed_object = ToLayoutBox(*fixed.GetLayoutObject());
const auto& fixed_layer = *fixed_object.Layer();
auto& transform = *GetDocument().getElementById("transform");
EXPECT_TRUE(fixed_layer.SubtreeIsInvisible());
EXPECT_EQ(LayoutRect(0, 0, 110, 120), fixed_object.LayoutOverflowRect());
GetDocument().domWindow()->scrollTo(0, 100);
transform.setAttribute(HTMLNames::styleAttr,
"transform: translate(20px, 30px)");
GetDocument().View()->UpdateLifecycleToCompositingCleanPlusScrolling();
EXPECT_TRUE(fixed_layer.SubtreeIsInvisible());
// We skip invisible layers when setting non-composited fixed-position
// needing paint invalidation when the frame is scrolled.
EXPECT_FALSE(fixed_object.ShouldDoFullPaintInvalidation());
// This was set when fixedObject is marked needsOverflowRecaldAfterStyleChange
// when child changed transform.
EXPECT_TRUE(fixed_object.MayNeedPaintInvalidation());
EXPECT_EQ(LayoutRect(0, 0, 120, 130), fixed_object.LayoutOverflowRect());
// We should not repaint anything because all contents are invisible.
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint();
EXPECT_FALSE(fixed_layer.NeedsRepaint());
GetDocument().View()->UpdateAllLifecyclePhases();
// The following ensures normal paint invalidation still works.
transform.setAttribute(
HTMLNames::styleAttr,
"visibility: visible; transform: translate(20px, 30px)");
GetDocument().View()->UpdateLifecycleToCompositingCleanPlusScrolling();
EXPECT_FALSE(fixed_layer.SubtreeIsInvisible());
GetDocument().View()->UpdateAllLifecyclePhases();
fixed.setAttribute(HTMLNames::styleAttr, "top: 50px");
GetDocument().View()->UpdateLifecycleToCompositingCleanPlusScrolling();
EXPECT_TRUE(fixed_object.MayNeedPaintInvalidation());
EXPECT_FALSE(fixed_layer.SubtreeIsInvisible());
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint();
EXPECT_TRUE(fixed_layer.NeedsRepaint());
GetDocument().View()->UpdateAllLifecyclePhases();
}
TEST_P(PaintInvalidationTest, DelayedFullPaintInvalidation) {
EnableCompositing();
SetBodyInnerHTML(
"<style>body { margin: 0 }</style>"
"<div style='height: 4000px'></div>"
"<div id='target' style='width: 100px; height: 100px; background: blue'>"
"</div>");
auto* target = GetLayoutObjectByElementId("target");
target->SetShouldDoFullPaintInvalidationWithoutGeometryChange(
kPaintInvalidationDelayedFull);
EXPECT_EQ(kPaintInvalidationDelayedFull,
target->FullPaintInvalidationReason());
EXPECT_FALSE(target->NeedsPaintOffsetAndVisualRectUpdate());
GetDocument().View()->SetTracksPaintInvalidations(true);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(nullptr, GetRasterInvalidationTracking());
EXPECT_EQ(kPaintInvalidationDelayedFull,
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(kPaintInvalidationNone, target->FullPaintInvalidationReason());
EXPECT_EQ(IntRect(0, 4000, 100, 100), raster_invalidations[0].rect);
EXPECT_EQ(kPaintInvalidationFull, raster_invalidations[0].reason);
EXPECT_FALSE(target->NeedsPaintOffsetAndVisualRectUpdate());
GetDocument().View()->SetTracksPaintInvalidations(false);
};
} // namespace
} // namespace blink