blob: f137b1c4470ed5d0ded0c5611d4fc2b56cfc2ce6 [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 "core/paint/PaintLayer.h"
#include "core/html/HTMLIFrameElement.h"
#include "core/layout/LayoutBoxModelObject.h"
#include "core/layout/LayoutTestHelper.h"
#include "core/layout/LayoutView.h"
#include "platform/testing/RuntimeEnabledFeaturesTestHelpers.h"
#include "platform/testing/UnitTestHelpers.h"
namespace blink {
typedef std::pair<bool, bool> SlimmingPaintAndRootLayerScrolling;
class PaintLayerTest
: public ::testing::WithParamInterface<SlimmingPaintAndRootLayerScrolling>,
private ScopedSlimmingPaintV2ForTest,
private ScopedRootLayerScrollingForTest,
public RenderingTest {
public:
PaintLayerTest()
: ScopedSlimmingPaintV2ForTest(GetParam().first),
ScopedRootLayerScrollingForTest(GetParam().second),
RenderingTest(SingleChildFrameLoaderClient::create()) {}
};
SlimmingPaintAndRootLayerScrolling foo[] = {
SlimmingPaintAndRootLayerScrolling(false, false),
SlimmingPaintAndRootLayerScrolling(true, false),
SlimmingPaintAndRootLayerScrolling(false, true),
SlimmingPaintAndRootLayerScrolling(true, true)};
INSTANTIATE_TEST_CASE_P(All, PaintLayerTest, ::testing::ValuesIn(foo));
TEST_P(PaintLayerTest, CompositedBoundsAbsPosGrandchild) {
// TODO(chrishtr): fix this test for SPv2
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
return;
setBodyInnerHTML(
" <div id='parent'><div id='absposparent'><div id='absposchild'>"
" </div></div></div>"
"<style>"
" #parent { position: absolute; z-index: 0; overflow: hidden;"
" background: lightgray; width: 150px; height: 150px;"
" will-change: transform; }"
" #absposparent { position: absolute; z-index: 0; }"
" #absposchild { position: absolute; top: 0px; left: 0px; height: 200px;"
" width: 200px; background: lightblue; }</style>");
PaintLayer* parentLayer =
toLayoutBoxModelObject(getLayoutObjectByElementId("parent"))->layer();
// Since "absposchild" is clipped by "parent", it should not expand the
// composited bounds for "parent" beyond its intrinsic size of 150x150.
EXPECT_EQ(LayoutRect(0, 0, 150, 150),
parentLayer->boundingBoxForCompositing());
}
TEST_P(PaintLayerTest, PaintingExtentReflection) {
setBodyInnerHTML(
"<div id='target' style='background-color: blue; position: absolute;"
" width: 110px; height: 120px; top: 40px; left: 60px;"
" -webkit-box-reflect: below 3px'>"
"</div>");
PaintLayer* layer =
toLayoutBoxModelObject(getLayoutObjectByElementId("target"))->layer();
EXPECT_EQ(
LayoutRect(60, 40, 110, 243),
layer->paintingExtent(document().layoutView()->layer(), LayoutSize(), 0));
}
TEST_P(PaintLayerTest, PaintingExtentReflectionWithTransform) {
setBodyInnerHTML(
"<div id='target' style='background-color: blue; position: absolute;"
" width: 110px; height: 120px; top: 40px; left: 60px;"
" -webkit-box-reflect: below 3px; transform: translateX(30px)'>"
"</div>");
PaintLayer* layer =
toLayoutBoxModelObject(getLayoutObjectByElementId("target"))->layer();
EXPECT_EQ(
LayoutRect(90, 40, 110, 243),
layer->paintingExtent(document().layoutView()->layer(), LayoutSize(), 0));
}
TEST_P(PaintLayerTest, ScrollsWithViewportRelativePosition) {
setBodyInnerHTML("<div id='target' style='position: relative'></div>");
PaintLayer* layer =
toLayoutBoxModelObject(getLayoutObjectByElementId("target"))->layer();
EXPECT_FALSE(layer->sticksToViewport());
}
TEST_P(PaintLayerTest, ScrollsWithViewportFixedPosition) {
setBodyInnerHTML("<div id='target' style='position: fixed'></div>");
PaintLayer* layer =
toLayoutBoxModelObject(getLayoutObjectByElementId("target"))->layer();
EXPECT_TRUE(layer->sticksToViewport());
}
TEST_P(PaintLayerTest, ScrollsWithViewportFixedPositionInsideTransform) {
// We don't intend to launch SPv2 without root layer scrolling, so skip this
// test in that configuration because it's broken.
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() &&
!RuntimeEnabledFeatures::rootLayerScrollingEnabled())
return;
setBodyInnerHTML(
"<div style='transform: translateZ(0)'>"
" <div id='target' style='position: fixed'></div>"
"</div>"
"<div style='width: 10px; height: 1000px'></div>");
PaintLayer* layer =
toLayoutBoxModelObject(getLayoutObjectByElementId("target"))->layer();
EXPECT_FALSE(layer->sticksToViewport());
}
TEST_P(PaintLayerTest,
ScrollsWithViewportFixedPositionInsideTransformNoScroll) {
setBodyInnerHTML(
"<div style='transform: translateZ(0)'>"
" <div id='target' style='position: fixed'></div>"
"</div>");
PaintLayer* layer =
toLayoutBoxModelObject(getLayoutObjectByElementId("target"))->layer();
// In SPv2 mode, we correctly determine that the frame doesn't scroll at all,
// and so return true.
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
EXPECT_TRUE(layer->sticksToViewport());
else
EXPECT_FALSE(layer->sticksToViewport());
}
TEST_P(PaintLayerTest, ScrollsWithViewportStickyPosition) {
setBodyInnerHTML(
"<div style='transform: translateZ(0)'>"
" <div id='target' style='position: sticky'></div>"
"</div>"
"<div style='width: 10px; height: 1000px'></div>");
PaintLayer* layer =
toLayoutBoxModelObject(getLayoutObjectByElementId("target"))->layer();
EXPECT_TRUE(layer->sticksToViewport());
}
TEST_P(PaintLayerTest, ScrollsWithViewportStickyPositionNoScroll) {
setBodyInnerHTML(
"<div style='transform: translateZ(0)'>"
" <div id='target' style='position: sticky'></div>"
"</div>");
PaintLayer* layer =
toLayoutBoxModelObject(getLayoutObjectByElementId("target"))->layer();
EXPECT_TRUE(layer->sticksToViewport());
}
TEST_P(PaintLayerTest, ScrollsWithViewportStickyPositionInsideScroller) {
setBodyInnerHTML(
"<div style='overflow:scroll; width: 100px; height: 100px;'>"
" <div id='target' style='position: sticky'></div>"
" <div style='width: 50px; height: 1000px;'></div>"
"</div>");
PaintLayer* layer =
toLayoutBoxModelObject(getLayoutObjectByElementId("target"))->layer();
EXPECT_FALSE(layer->sticksToViewport());
}
TEST_P(PaintLayerTest, CompositedScrollingNoNeedsRepaint) {
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
return;
enableCompositing();
setBodyInnerHTML(
"<div id='scroll' style='width: 100px; height: 100px; overflow: scroll;"
" will-change: transform'>"
" <div id='content' style='position: relative; background: blue;"
" width: 2000px; height: 2000px'></div>"
"</div>");
PaintLayer* scrollLayer =
toLayoutBoxModelObject(getLayoutObjectByElementId("scroll"))->layer();
EXPECT_EQ(PaintsIntoOwnBacking, scrollLayer->compositingState());
PaintLayer* contentLayer =
toLayoutBoxModelObject(getLayoutObjectByElementId("content"))->layer();
EXPECT_EQ(NotComposited, contentLayer->compositingState());
EXPECT_EQ(LayoutPoint(), contentLayer->location());
scrollLayer->getScrollableArea()->setScrollOffset(ScrollOffset(1000, 1000),
ProgrammaticScroll);
document().view()->updateAllLifecyclePhasesExceptPaint();
EXPECT_EQ(LayoutPoint(-1000, -1000), contentLayer->location());
EXPECT_FALSE(contentLayer->needsRepaint());
EXPECT_FALSE(scrollLayer->needsRepaint());
document().view()->updateAllLifecyclePhases();
}
TEST_P(PaintLayerTest, NonCompositedScrollingNeedsRepaint) {
setBodyInnerHTML(
"<div id='scroll' style='width: 100px; height: 100px; overflow: scroll'>"
" <div id='content' style='position: relative; background: blue;"
" width: 2000px; height: 2000px'></div>"
"</div>");
PaintLayer* scrollLayer =
toLayoutBoxModelObject(getLayoutObjectByElementId("scroll"))->layer();
EXPECT_EQ(NotComposited, scrollLayer->compositingState());
PaintLayer* contentLayer =
toLayoutBoxModelObject(getLayoutObjectByElementId("content"))->layer();
EXPECT_EQ(NotComposited, scrollLayer->compositingState());
EXPECT_EQ(LayoutPoint(), contentLayer->location());
scrollLayer->getScrollableArea()->setScrollOffset(ScrollOffset(1000, 1000),
ProgrammaticScroll);
document().view()->updateAllLifecyclePhasesExceptPaint();
EXPECT_EQ(LayoutPoint(-1000, -1000), contentLayer->location());
EXPECT_TRUE(contentLayer->needsRepaint());
EXPECT_TRUE(scrollLayer->needsRepaint());
document().view()->updateAllLifecyclePhases();
}
TEST_P(PaintLayerTest, HasNonIsolatedDescendantWithBlendMode) {
setBodyInnerHTML(
"<div id='stacking-grandparent' style='isolation: isolate'>"
" <div id='stacking-parent' style='isolation: isolate'>"
" <div id='non-stacking-parent' style='position:relative'>"
" <div id='blend-mode' style='mix-blend-mode: overlay'>"
" </div>"
" </div>"
" </div>"
"</div>");
PaintLayer* stackingGrandparent =
toLayoutBoxModelObject(getLayoutObjectByElementId("stacking-grandparent"))
->layer();
PaintLayer* stackingParent =
toLayoutBoxModelObject(getLayoutObjectByElementId("stacking-parent"))
->layer();
PaintLayer* parent =
toLayoutBoxModelObject(getLayoutObjectByElementId("non-stacking-parent"))
->layer();
EXPECT_TRUE(parent->hasNonIsolatedDescendantWithBlendMode());
EXPECT_TRUE(stackingParent->hasNonIsolatedDescendantWithBlendMode());
EXPECT_FALSE(stackingGrandparent->hasNonIsolatedDescendantWithBlendMode());
EXPECT_FALSE(parent->hasDescendantWithClipPath());
EXPECT_TRUE(parent->hasVisibleDescendant());
}
TEST_P(PaintLayerTest, HasDescendantWithClipPath) {
setBodyInnerHTML(
"<div id='parent' style='position:relative'>"
" <div id='clip-path' style='clip-path: circle(50px at 0 100px)'>"
" </div>"
"</div>");
PaintLayer* parent =
toLayoutBoxModelObject(getLayoutObjectByElementId("parent"))->layer();
PaintLayer* clipPath =
toLayoutBoxModelObject(getLayoutObjectByElementId("clip-path"))->layer();
EXPECT_TRUE(parent->hasDescendantWithClipPath());
EXPECT_FALSE(clipPath->hasDescendantWithClipPath());
EXPECT_FALSE(parent->hasNonIsolatedDescendantWithBlendMode());
EXPECT_TRUE(parent->hasVisibleDescendant());
}
TEST_P(PaintLayerTest, HasVisibleDescendant) {
enableCompositing();
setBodyInnerHTML(
"<div id='invisible' style='position:relative'>"
" <div id='visible' style='visibility: visible; position: relative'>"
" </div>"
"</div>");
PaintLayer* invisible =
toLayoutBoxModelObject(getLayoutObjectByElementId("invisible"))->layer();
PaintLayer* visible =
toLayoutBoxModelObject(getLayoutObjectByElementId("visible"))->layer();
EXPECT_TRUE(invisible->hasVisibleDescendant());
EXPECT_FALSE(visible->hasVisibleDescendant());
EXPECT_FALSE(invisible->hasNonIsolatedDescendantWithBlendMode());
EXPECT_FALSE(invisible->hasDescendantWithClipPath());
}
TEST_P(PaintLayerTest, DescendantDependentFlagsStopsAtThrottledFrames) {
enableCompositing();
setBodyInnerHTML(
"<style>body { margin: 0; }</style>"
"<div id='transform' style='transform: translate3d(4px, 5px, 6px);'>"
"</div>"
"<iframe id='iframe' sandbox></iframe>");
setChildFrameHTML(
"<style>body { margin: 0; }</style>"
"<div id='iframeTransform'"
" style='transform: translate3d(4px, 5px, 6px);'/>");
// Move the child frame offscreen so it becomes available for throttling.
auto* iframe = toHTMLIFrameElement(document().getElementById("iframe"));
iframe->setAttribute(HTMLNames::styleAttr, "transform: translateY(5555px)");
document().view()->updateAllLifecyclePhases();
// Ensure intersection observer notifications get delivered.
testing::runPendingTasks();
EXPECT_FALSE(document().view()->isHiddenForThrottling());
EXPECT_TRUE(childDocument().view()->isHiddenForThrottling());
{
DocumentLifecycle::AllowThrottlingScope throttlingScope(
document().lifecycle());
EXPECT_FALSE(document().view()->shouldThrottleRendering());
EXPECT_TRUE(childDocument().view()->shouldThrottleRendering());
childDocument().view()->layoutView()->layer()->dirtyVisibleContentStatus();
EXPECT_TRUE(childDocument()
.view()
->layoutView()
->layer()
->m_needsDescendantDependentFlagsUpdate);
// Also check that the rest of the lifecycle succeeds without crashing due
// to a stale m_needsDescendantDependentFlagsUpdate.
document().view()->updateAllLifecyclePhases();
// Still dirty, because the frame was throttled.
EXPECT_TRUE(childDocument()
.view()
->layoutView()
->layer()
->m_needsDescendantDependentFlagsUpdate);
}
document().view()->updateAllLifecyclePhases();
EXPECT_FALSE(childDocument()
.view()
->layoutView()
->layer()
->m_needsDescendantDependentFlagsUpdate);
}
} // namespace blink