blob: 172e69daea72e193d19b4b0967de7a6ecf401f91 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/paint/object_paint_invalidator.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
#include "third_party/blink/renderer/platform/json/json_values.h"
namespace blink {
using ObjectPaintInvalidatorTest = RenderingTest;
TEST_F(ObjectPaintInvalidatorTest,
TraverseNonCompositingDescendantsInPaintOrder) {
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
EnableCompositing();
SetBodyInnerHTML(R"HTML(
<style>div { width: 10px; height: 10px; background-color: green;
}</style>
<div id='container' style='position: fixed'>
<div id='normal-child'></div>
<div id='stacked-child' style='position: relative'></div>
<div id='composited-stacking-context' style='will-change: transform'>
<div id='normal-child-of-composited-stacking-context'></div>
<div id='stacked-child-of-composited-stacking-context'
style='position: relative'></div>
</div>
<div id='composited-non-stacking-context' style='backface-visibility:
hidden'>
<div id='normal-child-of-composited-non-stacking-context'></div>
<div id='stacked-child-of-composited-non-stacking-context'
style='position: relative'></div>
<div
id='non-stacked-layered-child-of-composited-non-stacking-context'
style='overflow: scroll'></div>
</div>
</div>
)HTML");
GetDocument().View()->SetTracksPaintInvalidations(true);
ObjectPaintInvalidator(*GetLayoutObjectByElementId("container"))
.InvalidateDisplayItemClientsIncludingNonCompositingDescendants(
PaintInvalidationReason::kSubtree);
std::unique_ptr<JSONArray> invalidations =
GetDocument().View()->TrackedObjectPaintInvalidationsAsJSON();
GetDocument().View()->SetTracksPaintInvalidations(false);
ASSERT_EQ(4u, invalidations->size());
String s;
JSONObject::Cast(invalidations->at(0))->Get("object")->AsString(&s);
EXPECT_EQ(GetLayoutObjectByElementId("container")->DebugName(), s);
JSONObject::Cast(invalidations->at(1))->Get("object")->AsString(&s);
EXPECT_EQ(GetLayoutObjectByElementId("normal-child")->DebugName(), s);
JSONObject::Cast(invalidations->at(2))->Get("object")->AsString(&s);
EXPECT_EQ(GetLayoutObjectByElementId("stacked-child")->DebugName(), s);
JSONObject::Cast(invalidations->at(3))->Get("object")->AsString(&s);
EXPECT_EQ(GetLayoutObjectByElementId(
"stacked-child-of-composited-non-stacking-context")
->DebugName(),
s);
}
TEST_F(ObjectPaintInvalidatorTest, TraverseFloatUnderCompositedInline) {
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
EnableCompositing();
SetBodyInnerHTML(R"HTML(
<div id='compositedContainer' style='position: relative;
will-change: transform'>
<div id='containingBlock' style='position: relative'>
<span id='span' style='position: relative; will-change: transform'>
<div id='target' style='float: right'></div>
</span>
</div>
</div>
)HTML");
auto* target = GetLayoutObjectByElementId("target");
auto* containing_block = GetLayoutObjectByElementId("containingBlock");
auto* containing_block_layer =
ToLayoutBoxModelObject(containing_block)->Layer();
auto* composited_container =
GetLayoutObjectByElementId("compositedContainer");
auto* composited_container_layer =
ToLayoutBoxModelObject(composited_container)->Layer();
auto* span = GetLayoutObjectByElementId("span");
auto* span_layer = ToLayoutBoxModelObject(span)->Layer();
// Thought |target| is under |span| which is a composited stacking context,
// |span| is not the paint invalidation container of |target|.
EXPECT_TRUE(span->IsPaintInvalidationContainer());
EXPECT_TRUE(span->StyleRef().IsStackingContext());
EXPECT_EQ(composited_container, &target->ContainerForPaintInvalidation());
EXPECT_EQ(containing_block_layer, target->PaintingLayer());
// Traversing from target should mark needsRepaint on correct layers.
EXPECT_FALSE(containing_block_layer->NeedsRepaint());
EXPECT_FALSE(composited_container_layer->NeedsRepaint());
ObjectPaintInvalidator(*target)
.InvalidateDisplayItemClientsIncludingNonCompositingDescendants(
PaintInvalidationReason::kSubtree);
EXPECT_TRUE(containing_block_layer->NeedsRepaint());
EXPECT_TRUE(composited_container_layer->NeedsRepaint());
EXPECT_FALSE(span_layer->NeedsRepaint());
GetDocument().View()->UpdateAllLifecyclePhases();
// Traversing from span should mark needsRepaint on correct layers for target.
EXPECT_FALSE(containing_block_layer->NeedsRepaint());
EXPECT_FALSE(composited_container_layer->NeedsRepaint());
ObjectPaintInvalidator(*span)
.InvalidateDisplayItemClientsIncludingNonCompositingDescendants(
PaintInvalidationReason::kSubtree);
EXPECT_TRUE(containing_block_layer->NeedsRepaint());
EXPECT_TRUE(composited_container_layer->NeedsRepaint());
EXPECT_TRUE(span_layer->NeedsRepaint());
GetDocument().View()->UpdateAllLifecyclePhases();
// Traversing from compositedContainer should reach target.
GetDocument().View()->SetTracksPaintInvalidations(true);
EXPECT_FALSE(containing_block_layer->NeedsRepaint());
EXPECT_FALSE(composited_container_layer->NeedsRepaint());
ObjectPaintInvalidator(*composited_container)
.InvalidateDisplayItemClientsIncludingNonCompositingDescendants(
PaintInvalidationReason::kSubtree);
EXPECT_TRUE(containing_block_layer->NeedsRepaint());
EXPECT_TRUE(composited_container_layer->NeedsRepaint());
EXPECT_FALSE(span_layer->NeedsRepaint());
std::unique_ptr<JSONArray> invalidations =
GetDocument().View()->TrackedObjectPaintInvalidationsAsJSON();
GetDocument().View()->SetTracksPaintInvalidations(false);
ASSERT_EQ(4u, invalidations->size());
String s;
JSONObject::Cast(invalidations->at(0))->Get("object")->AsString(&s);
EXPECT_EQ(composited_container->DebugName(), s);
JSONObject::Cast(invalidations->at(1))->Get("object")->AsString(&s);
EXPECT_EQ(containing_block->DebugName(), s);
JSONObject::Cast(invalidations->at(2))->Get("object")->AsString(&s);
EXPECT_EQ(target->DebugName(), s);
// This is the text node after the span.
JSONObject::Cast(invalidations->at(3))->Get("object")->AsString(&s);
EXPECT_EQ("LayoutText #text", s);
}
TEST_F(ObjectPaintInvalidatorTest,
TraverseFloatUnderMultiLevelCompositedInlines) {
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
EnableCompositing();
SetBodyInnerHTML(R"HTML(
<div id='compositedContainer' style='position: relative;
will-change: transform'>
<div id='containingBlock' style='position: relative; z-index: 0'>
<span id='span' style='position: relative; will-change: transform'>
<span id='innerSpan'
style='position: relative; will-change: transform'>
<div id='target' style='float: right'></div>
</span>
</span>
</div>
</div>
)HTML");
auto* target = GetLayoutObjectByElementId("target");
auto* containing_block = GetLayoutObjectByElementId("containingBlock");
auto* containing_block_layer =
ToLayoutBoxModelObject(containing_block)->Layer();
auto* composited_container =
GetLayoutObjectByElementId("compositedContainer");
auto* composited_container_layer =
ToLayoutBoxModelObject(composited_container)->Layer();
auto* span = GetLayoutObjectByElementId("span");
auto* span_layer = ToLayoutBoxModelObject(span)->Layer();
auto* inner_span = GetLayoutObjectByElementId("innerSpan");
auto* inner_span_layer = ToLayoutBoxModelObject(inner_span)->Layer();
EXPECT_TRUE(span->IsPaintInvalidationContainer());
EXPECT_TRUE(span->StyleRef().IsStackingContext());
EXPECT_TRUE(inner_span->IsPaintInvalidationContainer());
EXPECT_TRUE(inner_span->StyleRef().IsStackingContext());
EXPECT_EQ(composited_container, &target->ContainerForPaintInvalidation());
EXPECT_EQ(containing_block_layer, target->PaintingLayer());
// Traversing from compositedContainer should reach target.
GetDocument().View()->SetTracksPaintInvalidations(true);
EXPECT_FALSE(containing_block_layer->NeedsRepaint());
EXPECT_FALSE(composited_container_layer->NeedsRepaint());
ObjectPaintInvalidator(*composited_container)
.InvalidateDisplayItemClientsIncludingNonCompositingDescendants(
PaintInvalidationReason::kSubtree);
EXPECT_TRUE(containing_block_layer->NeedsRepaint());
EXPECT_TRUE(composited_container_layer->NeedsRepaint());
EXPECT_FALSE(span_layer->NeedsRepaint());
EXPECT_FALSE(inner_span_layer->NeedsRepaint());
std::unique_ptr<JSONArray> invalidations =
GetDocument().View()->TrackedObjectPaintInvalidationsAsJSON();
GetDocument().View()->SetTracksPaintInvalidations(false);
ASSERT_EQ(4u, invalidations->size());
String s;
JSONObject::Cast(invalidations->at(0))->Get("object")->AsString(&s);
EXPECT_EQ(composited_container->DebugName(), s);
JSONObject::Cast(invalidations->at(1))->Get("object")->AsString(&s);
EXPECT_EQ(containing_block->DebugName(), s);
JSONObject::Cast(invalidations->at(2))->Get("object")->AsString(&s);
EXPECT_EQ(target->DebugName(), s);
// This is the text node after the span.
JSONObject::Cast(invalidations->at(3))->Get("object")->AsString(&s);
EXPECT_EQ("LayoutText #text", s);
}
TEST_F(ObjectPaintInvalidatorTest, TraverseStackedFloatUnderCompositedInline) {
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
EnableCompositing();
SetBodyInnerHTML(R"HTML(
<span id='span' style='position: relative; will-change: transform'>
<div id='target' style='position: relative; float: right'></div>
</span>
)HTML");
auto* target = GetLayoutObjectByElementId("target");
auto* target_layer = ToLayoutBoxModelObject(target)->Layer();
auto* span = GetLayoutObjectByElementId("span");
auto* span_layer = ToLayoutBoxModelObject(span)->Layer();
EXPECT_TRUE(span->IsPaintInvalidationContainer());
EXPECT_TRUE(span->StyleRef().IsStackingContext());
EXPECT_EQ(span, &target->ContainerForPaintInvalidation());
EXPECT_EQ(target_layer, target->PaintingLayer());
// Traversing from span should reach target.
GetDocument().View()->SetTracksPaintInvalidations(true);
EXPECT_FALSE(span_layer->NeedsRepaint());
ObjectPaintInvalidator(*span)
.InvalidateDisplayItemClientsIncludingNonCompositingDescendants(
PaintInvalidationReason::kSubtree);
EXPECT_TRUE(span_layer->NeedsRepaint());
std::unique_ptr<JSONArray> invalidations =
GetDocument().View()->TrackedObjectPaintInvalidationsAsJSON();
GetDocument().View()->SetTracksPaintInvalidations(false);
ASSERT_EQ(3u, invalidations->size());
String s;
JSONObject::Cast(invalidations->at(0))->Get("object")->AsString(&s);
EXPECT_EQ(span->DebugName(), s);
JSONObject::Cast(invalidations->at(1))->Get("object")->AsString(&s);
EXPECT_EQ("LayoutText #text", s);
JSONObject::Cast(invalidations->at(2))->Get("object")->AsString(&s);
EXPECT_EQ(target->DebugName(), s);
}
TEST_F(ObjectPaintInvalidatorTest, InvalidatePaintRectangle) {
EnableCompositing();
SetBodyInnerHTML(
"<div id='target' style='width: 200px; height: 200px; background: blue'>"
"</div>");
GetDocument().View()->SetTracksPaintInvalidations(true);
auto* target = GetLayoutObjectByElementId("target");
target->InvalidatePaintRectangle(LayoutRect(10, 10, 50, 50));
EXPECT_EQ(LayoutRect(10, 10, 50, 50), target->PartialInvalidationRect());
target->InvalidatePaintRectangle(LayoutRect(30, 30, 60, 60));
EXPECT_EQ(LayoutRect(10, 10, 80, 80), target->PartialInvalidationRect());
EXPECT_TRUE(target->MayNeedPaintInvalidation());
GetDocument().View()->UpdateLifecycleToPrePaintClean();
if (!RuntimeEnabledFeatures::PartialRasterInvalidationEnabled())
EXPECT_EQ(LayoutRect(), target->PartialInvalidationRect());
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(LayoutRect(), target->PartialInvalidationRect());
auto object_invalidations =
GetDocument().View()->TrackedObjectPaintInvalidationsAsJSON();
ASSERT_EQ(1u, object_invalidations->size());
String s;
const auto* entry = JSONObject::Cast(object_invalidations->at(0));
entry->Get("reason")->AsString(&s);
EXPECT_EQ(String(PaintInvalidationReasonToString(
PaintInvalidationReason::kRectangle)),
s);
entry->Get("object")->AsString(&s);
EXPECT_EQ(target->DebugName(), s);
const auto& raster_invalidations = GetLayoutView()
.Layer()
->GraphicsLayerBacking()
->GetRasterInvalidationTracking()
->Invalidations();
ASSERT_EQ(1u, raster_invalidations.size());
if (RuntimeEnabledFeatures::PartialRasterInvalidationEnabled())
EXPECT_EQ(IntRect(18, 18, 80, 80), raster_invalidations[0].rect);
else
EXPECT_EQ(IntRect(8, 8, 200, 200), raster_invalidations[0].rect);
EXPECT_EQ(PaintInvalidationReason::kRectangle,
raster_invalidations[0].reason);
GetDocument().View()->SetTracksPaintInvalidations(false);
}
TEST_F(ObjectPaintInvalidatorTest, Selection) {
EnableCompositing();
SetBodyInnerHTML("<img id='target' style='width: 100px; height: 100px'>");
auto* target = GetLayoutObjectByElementId("target");
EXPECT_EQ(LayoutRect(), target->SelectionVisualRect());
// Add selection.
GetDocument().View()->SetTracksPaintInvalidations(true);
GetDocument().GetFrame()->Selection().SelectAll();
GetDocument().View()->UpdateAllLifecyclePhases();
const auto* graphics_layer = GetLayoutView().Layer()->GraphicsLayerBacking();
const auto* invalidations =
&graphics_layer->GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, invalidations->size());
EXPECT_EQ(IntRect(8, 8, 100, 100), (*invalidations)[0].rect);
EXPECT_EQ(PaintInvalidationReason::kSelection, (*invalidations)[0].reason);
EXPECT_EQ(LayoutRect(8, 8, 100, 100), target->SelectionVisualRect());
GetDocument().View()->SetTracksPaintInvalidations(false);
// Simulate a change without full invalidation or selection change.
GetDocument().View()->SetTracksPaintInvalidations(true);
target->SetMayNeedPaintInvalidation();
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_TRUE(graphics_layer->GetRasterInvalidationTracking()
->Invalidations()
.IsEmpty());
EXPECT_EQ(LayoutRect(8, 8, 100, 100), target->SelectionVisualRect());
GetDocument().View()->SetTracksPaintInvalidations(false);
// Remove selection.
GetDocument().View()->SetTracksPaintInvalidations(true);
GetDocument().GetFrame()->Selection().Clear();
GetDocument().View()->UpdateAllLifecyclePhases();
invalidations =
&graphics_layer->GetRasterInvalidationTracking()->Invalidations();
ASSERT_EQ(1u, invalidations->size());
EXPECT_EQ(IntRect(8, 8, 100, 100), (*invalidations)[0].rect);
EXPECT_EQ(PaintInvalidationReason::kSelection, (*invalidations)[0].reason);
EXPECT_EQ(LayoutRect(), target->SelectionVisualRect());
GetDocument().View()->SetTracksPaintInvalidations(false);
}
} // namespace blink