blob: 42c51d88b8eab472a1ee3d50e823910b76320a74 [file] [log] [blame]
// Copyright 2019 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/box_painter.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h"
#include "third_party/blink/renderer/platform/testing/paint_property_test_helpers.h"
using testing::ElementsAre;
namespace blink {
using BoxPainterTest = PaintControllerPaintTest;
INSTANTIATE_PAINT_TEST_SUITE_P(BoxPainterTest);
TEST_P(BoxPainterTest, EmptyDecorationBackground) {
SetBodyInnerHTML(R"HTML(
<style>
body {
margin: 0;
/* to force a subsequene and paint chunk */
opacity: 0.5;
/* to verify child empty backgrounds expand chunk bounds */
height: 0;
}
</style>
<div id="div1" style="width: 100px; height: 100px; background: green">
</div>
<div id="div2" style="width: 100px; height: 100px; outline: 2px solid blue">
</div>
<div id="div3" style="width: 200px; height: 150px"></div>
)HTML");
auto* div1 = GetLayoutObjectByElementId("div1");
auto* div2 = GetLayoutObjectByElementId("div2");
auto* body = GetDocument().body()->GetLayoutBox();
// Empty backgrounds don't generate display items.
EXPECT_THAT(RootPaintController().GetDisplayItemList(),
ElementsAre(IsSameId(&ViewScrollingBackgroundClient(),
kDocumentBackgroundType),
IsSameId(div1, kBackgroundType),
IsSameId(div2, DisplayItem::PaintPhaseToDrawingType(
PaintPhase::kSelfOutlineOnly))));
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
EXPECT_THAT(
RootPaintController().PaintChunks(),
ElementsAre(
IsPaintChunk(0, 0), IsPaintChunk(0, 1), // LayoutView chunks.
// Empty backgrounds contribute to bounds of paint chunks.
IsPaintChunk(
1, 3, PaintChunk::Id(*body->Layer(), DisplayItem::kLayerChunk),
body->FirstFragment().LocalBorderBoxProperties(), nullptr,
IntRect(-2, 0, 202, 350))));
} else {
EXPECT_THAT(
RootPaintController().PaintChunks(),
ElementsAre(
IsPaintChunk(0, 1), // LayoutView chunks.
// Empty backgrounds contribute to bounds of paint chunks.
IsPaintChunk(
1, 3, PaintChunk::Id(*body->Layer(), DisplayItem::kLayerChunk),
body->FirstFragment().LocalBorderBoxProperties(), nullptr,
IntRect(-2, 0, 202, 350))));
}
}
TEST_P(BoxPainterTest, ScrollHitTestOrderWithScrollBackgroundAttachment) {
SetBodyInnerHTML(R"HTML(
<style>
::-webkit-scrollbar { display: none; }
body { margin: 0; }
#container {
width: 200px;
height: 200px;
overflow-y: scroll;
background: linear-gradient(yellow, blue);
background-attachment: scroll;
will-change: transform;
}
#child { height: 300px; width: 10px; background: blue; }
</style>
<div id='container'>
<div id='child'></div>
</div>
)HTML");
auto& container = ToLayoutBox(*GetLayoutObjectByElementId("container"));
auto& child = *GetLayoutObjectByElementId("child");
// As a reminder, "background-attachment: scroll" does not move when the
// container's scroll offset changes.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
// The scroll hit test should be after the non-scrolling (attachment:
// scroll) container background so that it does not prevent squashing the
// non-scrolling container background into the root layer.
EXPECT_THAT(RootPaintController().GetDisplayItemList(),
ElementsAre(IsSameId(&ViewScrollingBackgroundClient(),
kDocumentBackgroundType),
IsSameId(&container, kBackgroundType),
IsSameId(&child, kBackgroundType)));
HitTestData scroll_hit_test;
scroll_hit_test.scroll_translation =
container.FirstFragment().PaintProperties()->ScrollTranslation();
scroll_hit_test.scroll_hit_test_rect = IntRect(0, 0, 200, 200);
EXPECT_THAT(
RootPaintController().PaintChunks(),
ElementsAre(
IsPaintChunk(0, 0), IsPaintChunk(0, 1), // LayoutView chunks.
IsPaintChunk(
1, 2,
PaintChunk::Id(*container.Layer(), DisplayItem::kLayerChunk),
container.FirstFragment().LocalBorderBoxProperties()),
IsPaintChunk(2, 2,
PaintChunk::Id(container, DisplayItem::kScrollHitTest),
container.FirstFragment().LocalBorderBoxProperties(),
&scroll_hit_test, IntRect(0, 0, 200, 200)),
IsPaintChunk(2, 3)));
} else {
// Because the frame composited scrolls, no scroll hit test is needed.
const auto* non_scrolling_layer = To<LayoutBlock>(container)
.Layer()
->GetCompositedLayerMapping()
->MainGraphicsLayer();
EXPECT_THAT(non_scrolling_layer->GetPaintController().GetDisplayItemList(),
ElementsAre(IsSameId(&container, kBackgroundType)));
const auto* scrolling_layer = To<LayoutBlock>(container)
.Layer()
->GetCompositedLayerMapping()
->ScrollingContentsLayer();
EXPECT_THAT(scrolling_layer->GetPaintController().GetDisplayItemList(),
ElementsAre(IsSameId(&child, kBackgroundType)));
}
}
TEST_P(BoxPainterTest, ScrollHitTestOrderWithLocalBackgroundAttachment) {
SetBodyInnerHTML(R"HTML(
<style>
::-webkit-scrollbar { display: none; }
body { margin: 0; }
#container {
width: 200px;
height: 200px;
overflow-y: scroll;
background: linear-gradient(yellow, blue);
background-attachment: local;
will-change: transform;
}
#child { height: 300px; width: 10px; background: blue; }
</style>
<div id='container'>
<div id='child'></div>
</div>
)HTML");
auto& container = ToLayoutBox(*GetLayoutObjectByElementId("container"));
auto& child = *GetLayoutObjectByElementId("child");
auto* container_scrolling_client =
&container.GetScrollableArea()->GetScrollingBackgroundDisplayItemClient();
// As a reminder, "background-attachment: local" moves when the container's
// scroll offset changes.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
// The scroll hit test should be before the scrolling (attachment: local)
// container background so that it does not prevent squashing the scrolling
// background into the scrolling contents.
EXPECT_THAT(
RootPaintController().GetDisplayItemList(),
ElementsAre(
IsSameId(&ViewScrollingBackgroundClient(), kDocumentBackgroundType),
IsSameId(container_scrolling_client, kBackgroundType),
IsSameId(&child, kBackgroundType)));
HitTestData scroll_hit_test;
scroll_hit_test.scroll_translation =
container.FirstFragment().PaintProperties()->ScrollTranslation();
scroll_hit_test.scroll_hit_test_rect = IntRect(0, 0, 200, 200);
EXPECT_THAT(
RootPaintController().PaintChunks(),
ElementsAre(
IsPaintChunk(0, 0), IsPaintChunk(0, 1), // LayoutView chunks.
IsPaintChunk(
1, 1,
PaintChunk::Id(*container.Layer(), DisplayItem::kLayerChunk),
container.FirstFragment().LocalBorderBoxProperties()),
IsPaintChunk(1, 1,
PaintChunk::Id(container, DisplayItem::kScrollHitTest),
container.FirstFragment().LocalBorderBoxProperties(),
&scroll_hit_test, IntRect(0, 0, 200, 200)),
IsPaintChunk(
1, 3, PaintChunk::Id(container, kScrollingBackgroundChunkType),
container.FirstFragment().ContentsProperties())));
} else {
// Because the frame composited scrolls, no scroll hit test is needed.
const auto* non_scrolling_layer =
container.Layer()->GetCompositedLayerMapping()->MainGraphicsLayer();
EXPECT_TRUE(non_scrolling_layer->GetPaintController()
.GetDisplayItemList()
.IsEmpty());
const auto* scrolling_layer = container.Layer()
->GetCompositedLayerMapping()
->ScrollingContentsLayer();
EXPECT_THAT(
scrolling_layer->GetPaintController().GetDisplayItemList(),
ElementsAre(IsSameId(container_scrolling_client, kBackgroundType),
IsSameId(&child, kBackgroundType)));
}
}
TEST_P(BoxPainterTest, ScrollHitTestProperties) {
SetBodyInnerHTML(R"HTML(
<style>
::-webkit-scrollbar { display: none; }
body { margin: 0; }
#container {
width: 200px;
height: 200px;
overflow-y: scroll;
background: rgba(0, 128, 0, 0.5); /* to prevent compositing */
}
#child { width: 100px; height: 300px; background: green; }
</style>
<div id='container'>
<div id='child'></div>
</div>
)HTML");
auto& container = To<LayoutBlock>(*GetLayoutObjectByElementId("container"));
const auto& paint_chunks = RootPaintController().PaintChunks();
auto& child = *GetLayoutObjectByElementId("child");
// The scroll hit test should be after the container background but before the
// scrolled contents.
EXPECT_EQ(kBackgroundPaintInGraphicsLayer,
container.ComputeBackgroundPaintLocationIfComposited());
EXPECT_EQ(kBackgroundPaintInGraphicsLayer,
container.GetBackgroundPaintLocation());
EXPECT_THAT(
RootPaintController().GetDisplayItemList(),
ElementsAre(
IsSameId(&ViewScrollingBackgroundClient(), kDocumentBackgroundType),
IsSameId(&container, kBackgroundType),
IsSameId(&child, kBackgroundType)));
HitTestData scroll_hit_test_data;
const auto& scrolling_contents_properties =
container.FirstFragment().ContentsProperties();
scroll_hit_test_data.scroll_translation =
container.FirstFragment().PaintProperties()->ScrollTranslation();
scroll_hit_test_data.scroll_hit_test_rect = IntRect(0, 0, 200, 200);
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
EXPECT_THAT(
paint_chunks,
ElementsAre(
IsPaintChunk(0, 0), IsPaintChunk(0, 1), // LayoutView chunks.
IsPaintChunk(
1, 2,
PaintChunk::Id(*container.Layer(), DisplayItem::kLayerChunk),
container.FirstFragment().LocalBorderBoxProperties()),
IsPaintChunk(2, 2,
PaintChunk::Id(container, DisplayItem::kScrollHitTest),
container.FirstFragment().LocalBorderBoxProperties(),
&scroll_hit_test_data, IntRect(0, 0, 200, 200)),
IsPaintChunk(
2, 3,
PaintChunk::Id(container, kClippedContentsBackgroundChunkType),
scrolling_contents_properties)));
} else {
EXPECT_THAT(
paint_chunks,
ElementsAre(
IsPaintChunk(0, 1), // LayoutView.
IsPaintChunk(
1, 2,
PaintChunk::Id(*container.Layer(), DisplayItem::kLayerChunk),
container.FirstFragment().LocalBorderBoxProperties()),
IsPaintChunk(2, 2,
PaintChunk::Id(container, DisplayItem::kScrollHitTest),
container.FirstFragment().LocalBorderBoxProperties(),
&scroll_hit_test_data, IntRect(0, 0, 200, 200)),
IsPaintChunk(
2, 3,
PaintChunk::Id(container, kClippedContentsBackgroundChunkType),
scrolling_contents_properties)));
}
// We always create scroll node for the root layer.
wtf_size_t chunk_index =
RuntimeEnabledFeatures::CompositeAfterPaintEnabled() ? 1 : 0;
const auto& root_transform =
ToUnaliased(paint_chunks[chunk_index].properties.Transform());
EXPECT_NE(nullptr, root_transform.ScrollNode());
// The container's background chunk should not scroll and therefore should use
// the root transform. Its local transform is actually a paint offset
// transform.
const auto& container_transform =
ToUnaliased(paint_chunks[++chunk_index].properties.Transform());
EXPECT_EQ(&root_transform, container_transform.Parent());
EXPECT_EQ(nullptr, container_transform.ScrollNode());
// The scroll hit test should not be scrolled and should not be clipped.
// Its local transform is actually a paint offset transform.
const auto& scroll_hit_test_chunk = paint_chunks[++chunk_index];
const auto& scroll_hit_test_transform =
ToUnaliased(scroll_hit_test_chunk.properties.Transform());
EXPECT_EQ(nullptr, scroll_hit_test_transform.ScrollNode());
EXPECT_EQ(&root_transform, scroll_hit_test_transform.Parent());
const auto& scroll_hit_test_clip =
ToUnaliased(scroll_hit_test_chunk.properties.Clip());
EXPECT_EQ(FloatRect(0, 0, 800, 600),
scroll_hit_test_clip.UnsnappedClipRect().Rect());
// The scrolled contents should be scrolled and clipped.
const auto& contents_chunk = paint_chunks[++chunk_index];
const auto& contents_transform =
ToUnaliased(contents_chunk.properties.Transform());
const auto* contents_scroll = contents_transform.ScrollNode();
EXPECT_EQ(IntSize(200, 300), contents_scroll->ContentsSize());
EXPECT_EQ(IntRect(0, 0, 200, 200), contents_scroll->ContainerRect());
const auto& contents_clip = ToUnaliased(contents_chunk.properties.Clip());
EXPECT_EQ(FloatRect(0, 0, 200, 200),
contents_clip.UnsnappedClipRect().Rect());
// The scroll paint chunk maintains a reference to a scroll translation node
// and the contents should be scrolled by this node.
EXPECT_EQ(&contents_transform,
scroll_hit_test_chunk.hit_test_data->scroll_translation);
}
} // namespace blink