| // Copyright 2021 The Chromium Authors |
| // 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/cull_rect_updater.h" |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| |
| namespace blink { |
| |
| class CullRectUpdaterTest |
| : public PaintControllerPaintTestBase, |
| public testing::WithParamInterface<bool>, |
| private ScopedDynamicScrollCullRectExpansionForTest { |
| protected: |
| CullRectUpdaterTest() |
| : ScopedDynamicScrollCullRectExpansionForTest(GetParam()) {} |
| |
| CullRect GetCullRect(const char* id) { |
| return GetLayoutObjectByElementId(id)->FirstFragment().GetCullRect(); |
| } |
| |
| CullRect GetCullRect(const PaintLayer& layer) { |
| return layer.GetLayoutObject().FirstFragment().GetCullRect(); |
| } |
| |
| CullRect GetContentsCullRect(const char* id) { |
| return GetLayoutObjectByElementId(id) |
| ->FirstFragment() |
| .GetContentsCullRect(); |
| } |
| |
| CullRect GetContentsCullRect(const PaintLayer& layer) { |
| return layer.GetLayoutObject().FirstFragment().GetContentsCullRect(); |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, CullRectUpdaterTest, testing::Bool()); |
| |
| TEST_P(CullRectUpdaterTest, SimpleCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' |
| style='width: 200px; height: 200px; position: relative'> |
| </div> |
| )HTML"); |
| |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, TallLayerCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' |
| style='width: 200px; height: 10000px; position: relative'> |
| </div> |
| )HTML"); |
| |
| // Viewport rect (0, 0, 800, 600) expanded by 4000 for scrolling then clipped |
| // by the contents rect. |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 4600), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, WideLayerCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' |
| style='width: 10000px; height: 200px; position: relative'> |
| </div> |
| )HTML"); |
| |
| // Same as TallLayerCullRect. |
| EXPECT_EQ(gfx::Rect(0, 0, 4800, 600), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, VerticalRLWritingModeDocument) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| html { writing-mode: vertical-rl; } |
| body { margin: 0; } |
| </style> |
| <div id='target' style='width: 10000px; height: 200px; position: relative'> |
| </div> |
| )HTML"); |
| |
| GetDocument().domWindow()->scrollTo(-5000, 0); |
| UpdateAllLifecyclePhasesForTest(); |
| |
| // A scroll by -5000px is equivalent to a scroll by (10000 - 5000 - 800)px = |
| // 4200px in non-RTL mode. Expanding the resulting rect by 4000px in each |
| // direction and clipping by the contents rect yields this result. |
| EXPECT_EQ(gfx::Rect(200, 0, 8800, 600), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, VerticalRLWritingModeScrollDiv) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| html { writing-mode: vertical-rl; } |
| </style> |
| <div id="scroller" style="width: 200px; height: 200px; overflow: scroll; |
| background: white"> |
| <div style="width: 10000px; height: 200px"></div> |
| </div> |
| )HTML"); |
| |
| GetDocument().getElementById(AtomicString("scroller"))->scrollTo(-5000, 0); |
| UpdateAllLifecyclePhasesForTest(); |
| |
| // Similar to the previous test case. |
| EXPECT_EQ(gfx::Rect(800, 0, 8200, 200), |
| GetContentsCullRect("scroller").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, ScaledCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 0 }</style> |
| <div id='target' |
| style='width: 200px; height: 300px; will-change: transform; |
| transform: scaleX(2) scaleY(0.75); transform-origin: 0 0'> |
| </div> |
| )HTML"); |
| |
| // The expansion is 4000 / max(scaleX, scaleY). |
| EXPECT_EQ(gfx::Rect(-2000, -2000, 4400, 4800), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, ScaledCullRectUnderCompositedScroller) { |
| SetBodyInnerHTML(R"HTML( |
| <div style='width: 200px; height: 300px; overflow: scroll; background: blue; |
| transform: scaleX(2) scaleY(0.75); transform-origin: 0 0'> |
| <div id='target' style='height: 400px; position: relative'></div> |
| <div style='width: 10000px; height: 9600px'></div> |
| </div> |
| )HTML"); |
| |
| // The expansion is calculated based on 4000 / max(scaleX, scaleY). |
| EXPECT_EQ(RuntimeEnabledFeatures::DynamicScrollCullRectExpansionEnabled() |
| ? gfx::Rect(0, 0, 1200, 1300) |
| : gfx::Rect(0, 0, 2200, 2300), |
| GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, ScaledAndRotatedCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' |
| style='width: 200px; height: 300px; will-change: transform; |
| transform: scaleX(3) scaleY(0.5) rotateZ(45deg)'> |
| </div> |
| )HTML"); |
| |
| // The expansion 6599 is 4000 * max_dimension(1x1 rect projected from screen |
| // to local). |
| EXPECT_EQ(gfx::Rect(-6748, -6836, 14236, 14236), |
| GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, ScaledAndRotatedCullRectUnderCompositedScroller) { |
| SetBodyInnerHTML(R"HTML( |
| <div style='width: 200px; height: 300px; overflow: scroll; background: blue; |
| transform: scaleX(3) scaleY(0.5) rotateZ(45deg)'> |
| <div id='target' style='height: 400px; position: relative; |
| will-change: transform'></div> |
| <div style='width: 10000px; height: 10000px'></div> |
| </div> |
| )HTML"); |
| |
| // The expansion 6599 is 4000 * max_dimension(1x1 rect projected from screen |
| // to local). |
| EXPECT_EQ(RuntimeEnabledFeatures::DynamicScrollCullRectExpansionEnabled() |
| ? gfx::Rect(-6599, -6599, 16697, 16797) |
| : gfx::Rect(0, 0, 6799, 6899), |
| GetCullRect("target").Rect()); |
| EXPECT_EQ(RuntimeEnabledFeatures::DynamicScrollCullRectExpansionEnabled() |
| ? gfx::Rect(-6599, -6599, 16697, 16797) |
| : gfx::Rect(0, 0, 6799, 6899), |
| GetContentsCullRect("target").Rect()); |
| } |
| |
| // This is a testcase for https://crbug.com/1227907 where repeated cull rect |
| // updates are expensive on the motionmark microbenchmark. |
| TEST_P(CullRectUpdaterTest, OptimizeNonCompositedTransformUpdate) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #target { |
| width: 50px; |
| height: 50px; |
| background: green; |
| transform: translate(-8px, -8px); |
| } |
| </style> |
| <div id='target'></div> |
| )HTML"); |
| |
| // The cull rect should be correctly calculated on first paint. |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect("target").Rect()); |
| |
| // On subsequent paints, fall back to an infinite cull rect. |
| GetDocument() |
| .getElementById(AtomicString("target")) |
| ->setAttribute(html_names::kStyleAttr, |
| AtomicString("transform: rotate(10deg);")); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_TRUE(GetCullRect("target").IsInfinite()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, 3DRotated90DegreesCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' |
| style='width: 200px; height: 300px; will-change: transform; |
| transform: rotateY(90deg)'> |
| </div> |
| )HTML"); |
| |
| EXPECT_TRUE(GetCullRect("target").Rect().Contains(gfx::Rect(0, 0, 200, 300))); |
| } |
| |
| TEST_P(CullRectUpdaterTest, 3DRotatedNear90DegreesCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' |
| style='width: 200px; height: 300px; will-change: transform; |
| transform: rotateY(89.9999deg)'> |
| </div> |
| )HTML"); |
| |
| EXPECT_TRUE(GetCullRect("target").Rect().Contains(gfx::Rect(0, 0, 200, 300))); |
| } |
| |
| TEST_P(CullRectUpdaterTest, PerspectiveCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id=target style='transform: perspective(1000px) rotateX(-100deg);'> |
| <div style='width: 2000px; height: 3000px></div> |
| </div> |
| )HTML"); |
| |
| EXPECT_TRUE( |
| GetCullRect("target").Rect().Contains(gfx::Rect(0, 0, 2000, 3000))); |
| } |
| |
| TEST_P(CullRectUpdaterTest, 3D60DegRotatedTallCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 0 }</style> |
| <div id='target' |
| style='width: 200px; height: 10000px; transform: rotateY(60deg)'> |
| </div> |
| )HTML"); |
| |
| // The cull rect is expanded in the y direction for the root scroller, and |
| // x direction for |target| itself. |
| EXPECT_EQ(gfx::Rect(-4100, 0, 9600, 4600), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, FixedPositionInNonScrollableViewCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' style='width: 1000px; height: 2000px; |
| position: fixed; top: 100px; left: 200px;'> |
| </div> |
| )HTML"); |
| |
| EXPECT_EQ(gfx::Rect(-200, -100, 800, 600), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, FixedPositionInScrollableViewCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' style='width: 1000px; height: 2000px; |
| position: fixed; top: 100px; left: 200px;'> |
| </div> |
| <div style='height: 3000px'></div> |
| )HTML"); |
| |
| EXPECT_EQ(gfx::Rect(-200, -100, 800, 600), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, LayerOffscreenNearCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' |
| style='width: 200px; height: 300px; will-change: transform; |
| position: absolute; top: 3000px; left: 0px;'> |
| </div> |
| )HTML"); |
| |
| auto cull_rect = GetCullRect("target").Rect(); |
| EXPECT_TRUE(cull_rect.Contains(gfx::Rect(0, 0, 200, 300))); |
| } |
| |
| TEST_P(CullRectUpdaterTest, LayerOffscreenFarCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' |
| style='width: 200px; height: 300px; will-change: transform; |
| position: absolute; top: 9000px'> |
| </div> |
| )HTML"); |
| |
| // The layer is too far away from the viewport. |
| EXPECT_FALSE( |
| GetCullRect("target").Rect().Intersects(gfx::Rect(0, 0, 200, 300))); |
| } |
| |
| TEST_P(CullRectUpdaterTest, ScrollingLayerCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| div::-webkit-scrollbar { width: 5px; } |
| </style> |
| <div style='width: 200px; height: 200px; overflow: scroll; |
| background: blue'> |
| <div id='target' |
| style='width: 100px; height: 10000px; position: relative'> |
| </div> |
| </div> |
| )HTML"); |
| |
| // In screen space, the scroller is (8, 8, 195, 193) (because of overflow clip |
| // of 'target', scrollbar and root margin). |
| // Applying the viewport clip of the root has no effect because |
| // the clip is already small. Mapping it down into the graphics layer |
| // space yields (0, 0, 195, 193). This is then expanded by 4000px and clipped |
| // by the contents rect. |
| EXPECT_EQ(gfx::Rect(0, 0, 195, 4193), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, NonCompositedScrollingLayerCullRect) { |
| SetPreferCompositingToLCDText(false); |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| div::-webkit-scrollbar { width: 5px; } |
| </style> |
| <div style='width: 200px; height: 200px; overflow: scroll'> |
| <div id='target' |
| style='width: 100px; height: 10000px; position: relative'> |
| </div> |
| </div> |
| )HTML"); |
| |
| // See ScrollingLayerCullRect for the calculation. |
| EXPECT_EQ(gfx::Rect(0, 0, 195, 4193), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, ClippedBigLayer) { |
| SetBodyInnerHTML(R"HTML( |
| <div style='width: 1px; height: 1px; overflow: hidden'> |
| <div id='target' |
| style='width: 10000px; height: 10000px; position: relative'> |
| </div> |
| </div> |
| )HTML"); |
| |
| EXPECT_EQ(gfx::Rect(8, 8, 1, 1), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, TallScrolledLayerCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' style='width: 200px; height: 12000px; position: relative'> |
| </div> |
| )HTML"); |
| |
| // Viewport rect (0, 0, 800, 600) expanded by 4000 for scrolling then clipped |
| // by the contents rect. |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 4600), GetCullRect("target").Rect()); |
| |
| GetDocument().View()->LayoutViewport()->SetScrollOffset( |
| ScrollOffset(0, 4000), mojom::blink::ScrollType::kProgrammatic); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 8600), GetCullRect("target").Rect()); |
| |
| GetDocument().View()->LayoutViewport()->SetScrollOffset( |
| ScrollOffset(0, 4500), mojom::blink::ScrollType::kProgrammatic); |
| UpdateAllLifecyclePhasesForTest(); |
| // Used the previous cull rect because the scroll amount is small. |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 8600), GetCullRect("target").Rect()); |
| |
| GetDocument().View()->LayoutViewport()->SetScrollOffset( |
| ScrollOffset(0, 4600), mojom::blink::ScrollType::kProgrammatic); |
| UpdateAllLifecyclePhasesForTest(); |
| // Used new cull rect. |
| EXPECT_EQ(gfx::Rect(0, 600, 800, 8600), GetCullRect("target").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, WholeDocumentCullRect) { |
| SetPreferCompositingToLCDText(true); |
| GetDocument().GetSettings()->SetMainFrameClipsContent(false); |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| div { background: blue; } |
| ::-webkit-scrollbar { display: none; } |
| </style> |
| <div id='relative' |
| style='width: 200px; height: 10000px; position: relative'> |
| </div> |
| <div id='fixed' style='width: 200px; height: 200px; position: fixed'> |
| </div> |
| <div id='scroll' style='width: 200px; height: 200px; overflow: scroll'> |
| <div id='below-scroll' style='height: 5000px; position: relative'></div> |
| <div style='height: 200px'>Should not paint</div> |
| </div> |
| <div id='normal' style='width: 200px; height: 200px'></div> |
| )HTML"); |
| |
| // Viewport clipping is disabled. |
| EXPECT_TRUE(GetCullRect(*GetLayoutView().Layer()).IsInfinite()); |
| EXPECT_TRUE(GetCullRect("relative").IsInfinite()); |
| EXPECT_TRUE(GetCullRect("fixed").IsInfinite()); |
| EXPECT_TRUE(GetCullRect("scroll").IsInfinite()); |
| |
| // Cull rect is normal for contents below scroll other than the viewport. |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 4200), GetCullRect("below-scroll").Rect()); |
| |
| EXPECT_EQ(7u, ContentDisplayItems().size()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, FixedPositionUnderClipPath) { |
| GetDocument().View()->Resize(800, 600); |
| SetBodyInnerHTML(R"HTML( |
| <div style="height: 100vh"></div> |
| <div style="width: 100px; height: 100px; clip-path: inset(0 0 0 0)"> |
| <div id="fixed" style="position: fixed; top: 0; left: 0; width: 1000px; |
| height: 1000px"></div> |
| </div> |
| )HTML"); |
| |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect("fixed").Rect()); |
| |
| GetDocument().GetFrame()->DomWindow()->scrollTo(0, 1000); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect("fixed").Rect()); |
| |
| GetDocument().View()->Resize(800, 1000); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 1000), GetCullRect("fixed").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, FixedPositionUnderClipPathWillChangeTransform) { |
| GetDocument().View()->Resize(800, 600); |
| SetBodyInnerHTML(R"HTML( |
| <div style="height: 100vh"></div> |
| <div style="width: 100px; height: 100px; clip-path: inset(0 0 0 0)"> |
| <div id="fixed" style="position: fixed; top: 0; left: 0; width: 1000px; |
| height: 1000px; will-change: transform"></div> |
| </div> |
| )HTML"); |
| |
| EXPECT_EQ(gfx::Rect(-4000, -4000, 8800, 8600), GetCullRect("fixed").Rect()); |
| |
| GetDocument().GetFrame()->DomWindow()->scrollTo(0, 1000); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(-4000, -4000, 8800, 8600), GetCullRect("fixed").Rect()); |
| |
| GetDocument().View()->Resize(800, 2000); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(-4000, -4000, 8800, 10000), GetCullRect("fixed").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, AbsolutePositionUnderNonContainingStackingContext) { |
| SetPreferCompositingToLCDText(false); |
| SetBodyInnerHTML(R"HTML( |
| <div id="scroller" style="width: 200px; height: 200px; overflow: auto; |
| position: relative"> |
| <div style="height: 0; overflow: hidden; opacity: 0.5; margin: 250px"> |
| <div id="absolute" |
| style="width: 100px; height: 100px; position: absolute; |
| background: green"></div> |
| </div> |
| </div> |
| )HTML"); |
| |
| EXPECT_EQ(gfx::Rect(0, 0, 500, 500), GetCullRect("absolute").Rect()); |
| |
| GetDocument().getElementById(AtomicString("scroller"))->scrollTo(200, 200); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 500, 500), GetCullRect("absolute").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, StackedChildOfNonStackingContextScroller) { |
| SetBodyInnerHTML(R"HTML( |
| <div id="scroller" style="width: 200px; height: 200px; overflow: auto; |
| background: white"> |
| <div id="child" style="height: 7000px; position: relative"></div> |
| </div> |
| )HTML"); |
| |
| auto* scroller = GetDocument().getElementById(AtomicString("scroller")); |
| |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 4200), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 4200), GetCullRect("child").Rect()); |
| |
| for (int i = 1000; i < 7000; i += 1000) { |
| scroller->scrollTo(0, i); |
| UpdateAllLifecyclePhasesForTest(); |
| } |
| // When scrolled to 3800, the cull rect covers the whole scrolling contents. |
| // Then we use this full cull rect on further scroll to avoid repaint. |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetCullRect("child").Rect()); |
| |
| // The full cull rect still applies when the scroller scrolls to the top. |
| scroller->scrollTo(0, 0); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetCullRect("child").Rect()); |
| |
| // CullRectUpdater won't update |child|'s cull rect even it needs repaint |
| // because its container's cull rect doesn't change. |
| GetPaintLayerByElementId("child")->SetNeedsRepaint(); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 7000), GetCullRect("child").Rect()); |
| |
| // Setting |scroller| needs repaint will lead to proactive update for it, |
| // and for |child| because |scroller|'s cull rect changes. |
| GetPaintLayerByElementId("scroller")->SetNeedsRepaint(); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 4200), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 4200), GetCullRect("child").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, ContentsCullRectCoveringWholeContentsRect) { |
| SetPreferCompositingToLCDText(true); |
| SetBodyInnerHTML(R"HTML( |
| <div id="scroller" style="width: 400px; height: 400px; overflow: scroll"> |
| <div style="height: 7000px"></div> |
| <div id="child" style="will-change: transform; height: 20px"></div> |
| </div> |
| )HTML"); |
| |
| EXPECT_EQ(gfx::Rect(0, 0, 400, 4400), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -7000, 8400, 4400), GetCullRect("child").Rect()); |
| |
| auto* scroller = GetDocument().getElementById(AtomicString("scroller")); |
| scroller->scrollTo(0, 2500); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 400, 6900), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -7000, 8400, 6900), GetCullRect("child").Rect()); |
| |
| scroller->scrollTo(0, 2800); |
| UpdateAllLifecyclePhasesForTest(); |
| // Cull rects are not updated with a small scroll delta. |
| EXPECT_EQ(gfx::Rect(0, 0, 400, 6900), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -7000, 8400, 6900), GetCullRect("child").Rect()); |
| |
| scroller->scrollTo(0, 3100); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 400, 7020), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -7000, 8400, 7020), GetCullRect("child").Rect()); |
| |
| // We will use the same cull rects that cover the whole contents on further |
| // scroll. |
| scroller->scrollTo(0, 4000); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 400, 7020), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -7000, 8400, 7020), GetCullRect("child").Rect()); |
| |
| scroller->scrollTo(0, 0); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 400, 7020), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -7000, 8400, 7020), GetCullRect("child").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, SVGForeignObject) { |
| SetPreferCompositingToLCDText(false); |
| SetBodyInnerHTML(R"HTML( |
| <div id="scroller" style="width: 100px; height: 100px; overflow: scroll"> |
| <svg id="svg" style="width: 100px; height: 4000px"> |
| <foreignObject id="foreign" style="width: 500px; height: 1000px"> |
| <div id="child" style="position: relative">Child</div> |
| </foreignObject> |
| </svg> |
| </div> |
| )HTML"); |
| |
| auto* child = GetPaintLayerByElementId("child"); |
| auto* foreign = GetPaintLayerByElementId("foreign"); |
| auto* svg = GetPaintLayerByElementId("svg"); |
| EXPECT_FALSE(child->NeedsCullRectUpdate()); |
| EXPECT_FALSE(foreign->DescendantNeedsCullRectUpdate()); |
| EXPECT_FALSE(svg->DescendantNeedsCullRectUpdate()); |
| |
| GetDocument().getElementById(AtomicString("scroller"))->scrollTo(0, 500); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_FALSE(child->NeedsCullRectUpdate()); |
| EXPECT_FALSE(foreign->DescendantNeedsCullRectUpdate()); |
| EXPECT_FALSE(svg->DescendantNeedsCullRectUpdate()); |
| |
| child->SetNeedsCullRectUpdate(); |
| EXPECT_TRUE(child->NeedsCullRectUpdate()); |
| EXPECT_TRUE(foreign->DescendantNeedsCullRectUpdate()); |
| EXPECT_TRUE(svg->DescendantNeedsCullRectUpdate()); |
| |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_FALSE(child->NeedsCullRectUpdate()); |
| EXPECT_FALSE(foreign->DescendantNeedsCullRectUpdate()); |
| EXPECT_FALSE(svg->DescendantNeedsCullRectUpdate()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, LayerUnderSVGHiddenContainer) { |
| SetBodyInnerHTML(R"HTML( |
| <div id="div" style="display: contents"> |
| <svg id="svg1"></svg> |
| </div> |
| <svg id="svg2"> |
| <defs id="defs"/> |
| </svg> |
| )HTML"); |
| |
| EXPECT_FALSE(GetCullRect("svg1").Rect().IsEmpty()); |
| |
| GetDocument() |
| .getElementById(AtomicString("defs")) |
| ->appendChild(GetDocument().getElementById(AtomicString("div"))); |
| // This should not crash. |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_FALSE(GetLayoutObjectByElementId("svg1")); |
| } |
| |
| TEST_P(CullRectUpdaterTest, PerspectiveDescendants) { |
| SetBodyInnerHTML(R"HTML( |
| <div style="perspective: 1000px"> |
| <div style="height: 300px; transform-style: preserve-3d; contain: strict"> |
| <div id="target" style="transform: rotateX(20deg)">TARGET</div> |
| </div> |
| </div> |
| )HTML"); |
| EXPECT_TRUE(GetCullRect("target").IsInfinite()); |
| } |
| |
| // Test case for crbug.com/1382842. |
| TEST_P(CullRectUpdaterTest, UpdateOnCompositedScrollingStatusChange) { |
| SetPreferCompositingToLCDText(false); |
| SetBodyInnerHTML(R"HTML( |
| <style>body {position: absolute}</style> |
| <div id="scroller" style="width: 100px; height: 100px; |
| overflow: auto; position: relative"> |
| <div style="height: 1000px">TEXT</div> |
| <div> |
| )HTML"); |
| |
| EXPECT_EQ(gfx::Rect(100, 1000), GetContentsCullRect("scroller").Rect()); |
| |
| auto* scroller = GetDocument().getElementById(AtomicString("scroller")); |
| scroller->SetInlineStyleProperty(CSSPropertyID::kBackgroundColor, "yellow"); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(100, 1000), GetContentsCullRect("scroller").Rect()); |
| |
| scroller->RemoveInlineStyleProperty(CSSPropertyID::kBackgroundColor); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(100, 1000), GetContentsCullRect("scroller").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, StickyPositionInCompositedScroller) { |
| SetPreferCompositingToLCDText(true); |
| SetBodyInnerHTML(R"HTML( |
| <div id="scroller" style="width: 300px; height: 300px; overflow: scroll"> |
| <div style="height: 600px"></div> |
| <div id="sticky1" style="position: sticky; top: 10px; height: 50px"></div> |
| <div id="clipper" style="overflow: clip; height: 200px"> |
| <div style="height: 300px"></div> |
| <div id="sticky2" style="position: sticky; bottom: 0; height: 50px"> |
| </div> |
| </div> |
| <div style="height: 10000px"></div> |
| </div> |
| )HTML"); |
| |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 4300), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -600, 8300, 4300), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4000, 8300, 8200), GetCullRect("sticky2").Rect()); |
| |
| // Cull rects should be updated when the scroller has scrolled enough (on the |
| // 2nd and the 4th scrolls, but not in the 1st and the 3rd scrolls). `sticky2` |
| // always uses expanded cull rect from the contents cull rect of the |
| // additional clip. |
| auto* scroller = GetDocument().getElementById(AtomicString("scroller")); |
| scroller->scrollBy(0, 300); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 4300), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -600, 8300, 4300), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4000, 8300, 8200), GetCullRect("sticky2").Rect()); |
| |
| scroller->scrollBy(0, 300); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 4900), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -610, 8300, 4900), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4200, 8300, 8200), GetCullRect("sticky2").Rect()); |
| |
| scroller->scrollBy(0, 300); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 4900), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -610, 8300, 4900), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4200, 8300, 8200), GetCullRect("sticky2").Rect()); |
| |
| scroller->scrollBy(0, 300); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 5500), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -1210, 8300, 5500), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4300, 8300, 8200), GetCullRect("sticky2").Rect()); |
| |
| scroller->scrollBy(0, 6000); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 3200, 300, 7650), |
| GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4010, 8300, 7650), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(), GetCullRect("sticky2").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, StickyPositionInNonCompositedScroller) { |
| SetPreferCompositingToLCDText(false); |
| SetBodyInnerHTML(R"HTML( |
| <div id="scroller" style="width: 300px; height: 300px; overflow: scroll"> |
| <div style="height: 600px"></div> |
| <div id="sticky1" style="position: sticky; top: 10px; height: 50px"></div> |
| <div id="clipper" style="overflow: clip; height: 200px"> |
| <div style="height: 300px"></div> |
| <div id="sticky2" style="position: sticky; bottom: 0; height: 50px"> |
| </div> |
| </div> |
| <div style="height: 10000px"></div> |
| </div> |
| )HTML"); |
| |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 4300), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -600, 8300, 4300), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4000, 8300, 8200), GetCullRect("sticky2").Rect()); |
| |
| // All cull rects should be updated on each non-composited scroll. |
| // We always composite and expand cull rect for sticky elements regardless |
| // whether the scroller is composited. |
| auto* scroller = GetDocument().getElementById(AtomicString("scroller")); |
| scroller->scrollBy(0, 300); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 4300), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -600, 8300, 4300), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4000, 8300, 8200), GetCullRect("sticky2").Rect()); |
| |
| scroller->scrollBy(0, 300); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 4900), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -610, 8300, 4900), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4200, 8300, 8200), GetCullRect("sticky2").Rect()); |
| |
| scroller->scrollBy(0, 300); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 4900), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -610, 8300, 4900), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4200, 8300, 8200), GetCullRect("sticky2").Rect()); |
| |
| scroller->scrollBy(0, 300); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 5500), GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -1210, 8300, 5500), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4300, 8300, 8200), GetCullRect("sticky2").Rect()); |
| |
| scroller->scrollBy(0, 6000); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::Rect(0, 3200, 300, 7650), |
| GetContentsCullRect("scroller").Rect()); |
| EXPECT_EQ(gfx::Rect(-4000, -4010, 8300, 7650), GetCullRect("sticky1").Rect()); |
| EXPECT_EQ(gfx::Rect(), GetCullRect("sticky2").Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, NestedOverriddenCullRectScopes) { |
| SetBodyInnerHTML(R"HTML( |
| <div id="div1" style="contain: paint; height: 100px"></div> |
| <div id="div2" style="contain: paint; height: 100px"></div> |
| )HTML"); |
| |
| auto& layer1 = *GetPaintLayerByElementId("div1"); |
| auto& layer2 = *GetPaintLayerByElementId("div2"); |
| CullRect cull_rect1 = GetCullRect(layer1); |
| CullRect cull_rect2 = GetCullRect(layer2); |
| CullRect special_cull_rect1(gfx::Rect(12, 34, 56, 78)); |
| CullRect special_cull_rect2(gfx::Rect(87, 65, 43, 21)); |
| const bool disable_expansion = false; |
| |
| { |
| OverriddenCullRectScope scope1(layer1, cull_rect1, disable_expansion); |
| { |
| OverriddenCullRectScope scope2(layer2, cull_rect2, disable_expansion); |
| EXPECT_EQ(cull_rect2, GetCullRect(layer2)); |
| } |
| EXPECT_EQ(cull_rect1, GetCullRect(layer1)); |
| } |
| EXPECT_EQ(cull_rect1, GetCullRect(layer1)); |
| EXPECT_EQ(cull_rect2, GetCullRect(layer2)); |
| |
| { |
| OverriddenCullRectScope scope1(layer1, special_cull_rect1, |
| disable_expansion); |
| { |
| OverriddenCullRectScope scope2(layer2, cull_rect2, disable_expansion); |
| EXPECT_EQ(cull_rect2, GetCullRect(layer2)); |
| } |
| EXPECT_EQ(special_cull_rect1, GetCullRect(layer1)); |
| } |
| EXPECT_EQ(cull_rect1, GetCullRect(layer1)); |
| EXPECT_EQ(cull_rect2, GetCullRect(layer2)); |
| |
| { |
| OverriddenCullRectScope scope1(layer1, cull_rect1, disable_expansion); |
| { |
| OverriddenCullRectScope scope2(layer2, special_cull_rect2, |
| disable_expansion); |
| EXPECT_EQ(special_cull_rect2, GetCullRect(layer2)); |
| } |
| EXPECT_EQ(cull_rect1, GetCullRect(layer1)); |
| } |
| EXPECT_EQ(cull_rect1, GetCullRect(layer1)); |
| EXPECT_EQ(cull_rect2, GetCullRect(layer2)); |
| |
| { |
| OverriddenCullRectScope scope1(layer1, special_cull_rect1, |
| disable_expansion); |
| { |
| OverriddenCullRectScope scope2(layer2, special_cull_rect2, |
| disable_expansion); |
| EXPECT_EQ(special_cull_rect2, GetCullRect(layer2)); |
| } |
| EXPECT_EQ(special_cull_rect1, GetCullRect(layer1)); |
| } |
| EXPECT_EQ(cull_rect1, GetCullRect(layer1)); |
| EXPECT_EQ(cull_rect2, GetCullRect(layer2)); |
| } |
| |
| TEST_P(CullRectUpdaterTest, OverriddenCullRectWithoutExpansion) { |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 0 }</style> |
| <div id="clip" style="width: 300px; height: 300px; overflow: hidden"> |
| <div id="scroller" style="width: 1000px; height: 1000px; |
| overflow: scroll; will-change: scroll-position"> |
| <div style="width: 2000px; height: 2000px"></div> |
| <div> |
| </div> |
| )HTML"); |
| |
| auto& clip = *GetPaintLayerByElementId("clip"); |
| auto& scroller = *GetPaintLayerByElementId("scroller"); |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect(clip).Rect()); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 300), GetContentsCullRect(clip).Rect()); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 300), GetCullRect(scroller).Rect()); |
| EXPECT_EQ(RuntimeEnabledFeatures::DynamicScrollCullRectExpansionEnabled() |
| ? gfx::Rect(0, 0, 1300, 2000) |
| : gfx::Rect(0, 0, 2000, 2000), |
| GetContentsCullRect(scroller).Rect()); |
| |
| { |
| const bool disable_expansion = true; |
| OverriddenCullRectScope scope(*GetLayoutView().Layer(), |
| CullRect(gfx::Rect(100, 100, 400, 400)), |
| disable_expansion); |
| EXPECT_EQ(gfx::Rect(100, 100, 400, 400), GetCullRect(clip).Rect()); |
| EXPECT_EQ(gfx::Rect(100, 100, 200, 200), GetContentsCullRect(clip).Rect()); |
| EXPECT_EQ(gfx::Rect(100, 100, 200, 200), GetCullRect(scroller).Rect()); |
| EXPECT_EQ(gfx::Rect(100, 100, 200, 200), |
| GetContentsCullRect(scroller).Rect()); |
| } |
| |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 600), GetCullRect(clip).Rect()); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 300), GetContentsCullRect(clip).Rect()); |
| EXPECT_EQ(gfx::Rect(0, 0, 300, 300), GetCullRect(scroller).Rect()); |
| EXPECT_EQ(RuntimeEnabledFeatures::DynamicScrollCullRectExpansionEnabled() |
| ? gfx::Rect(0, 0, 1300, 2000) |
| : gfx::Rect(0, 0, 2000, 2000), |
| GetContentsCullRect(scroller).Rect()); |
| } |
| |
| TEST_P(CullRectUpdaterTest, ViewScrollNeedsCullRectUpdate) { |
| SetBodyInnerHTML("<div style='height: 5000px'>"); |
| |
| auto& layer = *GetLayoutView().Layer(); |
| EXPECT_FALSE(layer.NeedsCullRectUpdate()); |
| EXPECT_EQ(gfx::PointF(), |
| layer.GetScrollableArea()->LastCullRectUpdateScrollPosition()); |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 4600), GetContentsCullRect(layer).Rect()); |
| |
| GetDocument().domWindow()->scrollBy(0, 300); |
| UpdateAllLifecyclePhasesExceptPaint(/*update_cull_rects*/ false); |
| EXPECT_FALSE(layer.NeedsCullRectUpdate()); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::PointF(), |
| layer.GetScrollableArea()->LastCullRectUpdateScrollPosition()); |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 4600), GetContentsCullRect(layer).Rect()); |
| |
| GetDocument().domWindow()->scrollBy(0, 300); |
| UpdateAllLifecyclePhasesExceptPaint(/*update_cull_rects*/ false); |
| EXPECT_TRUE(layer.NeedsCullRectUpdate()); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::PointF(0, 600), |
| layer.GetScrollableArea()->LastCullRectUpdateScrollPosition()); |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 5016), GetContentsCullRect(layer).Rect()); |
| |
| GetDocument().domWindow()->scrollBy(0, 300); |
| UpdateAllLifecyclePhasesExceptPaint(/*update_cull_rects*/ false); |
| EXPECT_FALSE(layer.NeedsCullRectUpdate()); |
| UpdateAllLifecyclePhasesForTest(); |
| EXPECT_EQ(gfx::PointF(0, 600), |
| layer.GetScrollableArea()->LastCullRectUpdateScrollPosition()); |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 5016), GetContentsCullRect(layer).Rect()); |
| } |
| |
| // The test doesn't apply on Android or iOS where the LayoutObject of <select> |
| // doesn't scroll. |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| TEST_P(CullRectUpdaterTest, SelectDoesntExpandCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <select id="select" style="height: 50px; font-size: 20px" size="3"> |
| <option>a</option> |
| <option>b</option> |
| <option>c</option> |
| <option>d</option> |
| <option>e</option> |
| </select> |
| )HTML"); |
| |
| const PaintLayer* layer = GetPaintLayerByElementId("select"); |
| ASSERT_TRUE(layer->GetScrollableArea()); |
| gfx::Rect contents_cull_rect = GetContentsCullRect(*layer).Rect(); |
| EXPECT_LE(contents_cull_rect.height(), 50); |
| } |
| #endif |
| |
| TEST_P(CullRectUpdaterTest, InputDoesntExpandCullRect) { |
| SetBodyInnerHTML(R"HTML( |
| <input id="input" style="font-size: 20px; width: 100px; height: 20px" |
| value="ABCDEFGHIJKLMNOPQRSTUVWXYZ"> |
| )HTML"); |
| |
| const LayoutObject* editor = |
| GetLayoutObjectByElementId("input")->SlowFirstChild(); |
| ASSERT_TRUE(editor); |
| ASSERT_TRUE(editor->HasLayer()); |
| const PaintLayer* layer = To<LayoutBoxModelObject>(editor)->Layer(); |
| ASSERT_TRUE(layer->GetScrollableArea()); |
| gfx::Rect contents_cull_rect = GetContentsCullRect(*layer).Rect(); |
| EXPECT_LE(contents_cull_rect.width(), 100); |
| } |
| |
| class CullRectUpdateOnPaintPropertyChangeTest : public CullRectUpdaterTest { |
| protected: |
| void Check(const String& old_style, |
| const String& new_style, |
| bool expected_needs_repaint, |
| bool expected_needs_cull_rect_update, |
| bool expected_needs_repaint_after_cull_rect_update) { |
| UpdateAllLifecyclePhasesExceptPaint(/*update_cull_rects*/ false); |
| const auto* target_layer = GetPaintLayerByElementId("target"); |
| EXPECT_EQ(expected_needs_repaint, target_layer->SelfNeedsRepaint()) |
| << old_style << " -> " << new_style; |
| EXPECT_EQ(expected_needs_cull_rect_update, |
| target_layer->NeedsCullRectUpdate()) |
| << old_style << " -> " << new_style; |
| UpdateCullRects(); |
| EXPECT_EQ(expected_needs_repaint_after_cull_rect_update, |
| target_layer->SelfNeedsRepaint()) |
| << old_style << " -> " << new_style; |
| } |
| |
| void TestTargetChange(const char* old_style, |
| const char* new_style, |
| bool expected_needs_repaint, |
| bool expected_needs_cull_rect_update, |
| bool expected_needs_repaint_after_cull_rect_update) { |
| SetBodyInnerHTML(html_); |
| auto* target = GetDocument().getElementById(AtomicString("target")); |
| target->setAttribute(html_names::kStyleAttr, AtomicString(old_style)); |
| UpdateAllLifecyclePhasesForTest(); |
| target->setAttribute(html_names::kStyleAttr, AtomicString(new_style)); |
| Check(old_style, new_style, expected_needs_repaint, |
| expected_needs_cull_rect_update, |
| expected_needs_repaint_after_cull_rect_update); |
| } |
| |
| void TestChildChange(const char* old_style, |
| const char* new_style, |
| bool expected_needs_repaint, |
| bool expected_needs_cull_rect_update, |
| bool expected_needs_repaint_after_cull_rect_update) { |
| SetBodyInnerHTML(html_); |
| auto* child = GetDocument().getElementById(AtomicString("child")); |
| child->setAttribute(html_names::kStyleAttr, AtomicString(old_style)); |
| UpdateAllLifecyclePhasesForTest(); |
| child->setAttribute(html_names::kStyleAttr, AtomicString(new_style)); |
| Check(old_style, new_style, expected_needs_repaint, |
| expected_needs_cull_rect_update, |
| expected_needs_repaint_after_cull_rect_update); |
| } |
| |
| void TestTargetScroll(const ScrollOffset& old_scroll_offset, |
| const ScrollOffset& new_scroll_offset, |
| bool expected_needs_repaint, |
| bool expected_needs_cull_rect_update, |
| bool expected_needs_repaint_after_cull_rect_update) { |
| SetBodyInnerHTML(html_); |
| auto* target = GetDocument().getElementById(AtomicString("target")); |
| target->scrollTo(old_scroll_offset.x(), old_scroll_offset.y()), |
| UpdateAllLifecyclePhasesForTest(); |
| target->scrollTo(new_scroll_offset.x(), new_scroll_offset.y()), |
| Check(String(old_scroll_offset.ToString()), |
| String(new_scroll_offset.ToString()), expected_needs_repaint, |
| expected_needs_cull_rect_update, |
| expected_needs_repaint_after_cull_rect_update); |
| } |
| |
| String html_ = R"HTML( |
| <style> |
| #target { |
| width: 100px; |
| height: 100px; |
| position: relative; |
| overflow: scroll; |
| background: white; |
| } |
| #child { width: 1000px; height: 1000px; } |
| </style> |
| <div id="target"> |
| <div id="child">child</div> |
| </div>" |
| )HTML"; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| CullRectUpdateOnPaintPropertyChangeTest, |
| testing::Bool()); |
| |
| TEST_P(CullRectUpdateOnPaintPropertyChangeTest, Opacity) { |
| TestTargetChange("opacity: 0.2", "opacity: 0.8", false, false, false); |
| TestTargetChange("opacity: 0.5", "", true, false, true); |
| TestTargetChange("", "opacity: 0.5", true, false, true); |
| TestTargetChange("will-change: opacity", "will-change: opacity; opacity: 0.5", |
| false, false, false); |
| TestTargetChange("will-change: opacity; opacity: 0.5", "will-change: opacity", |
| false, false, false); |
| } |
| |
| TEST_P(CullRectUpdateOnPaintPropertyChangeTest, NonPixelMovingFilter) { |
| TestTargetChange("filter: invert(5%)", "filter: invert(8%)", false, false, |
| false); |
| TestTargetChange("filter: invert(5%)", "", true, false, true); |
| TestTargetChange("", "filter: invert(5%)", true, false, true); |
| TestTargetChange("will-change: filter; filter: invert(5%)", |
| "will-change: filter", false, false, false); |
| TestTargetChange("will-change: filter", |
| "will-change: filter; filter: invert(5%)", false, false, |
| false); |
| } |
| |
| TEST_P(CullRectUpdateOnPaintPropertyChangeTest, PixelMovingFilter) { |
| TestTargetChange("filter: blur(5px)", "filter: blur(8px)", false, false, |
| false); |
| TestTargetChange("filter: blur(5px)", "", true, true, true); |
| TestTargetChange("", "filter: blur(5px)", true, true, true); |
| TestTargetChange("will-change: filter; filter: blur(5px)", |
| "will-change: filter", true, false, true); |
| TestTargetChange("will-change: filter", |
| "will-change: filter; filter: blur(5px)", true, false, true); |
| } |
| |
| TEST_P(CullRectUpdateOnPaintPropertyChangeTest, Transform) { |
| // We use infinite cull rect for small layers with non-composited transforms, |
| // so don't need to update cull rect on non-composited transform change. |
| TestTargetChange("transform: translateX(10px)", "transform: translateX(20px)", |
| false, false, false); |
| TestTargetChange("transform: translateX(10px)", "", true, true, true); |
| TestTargetChange("", "transform: translateX(10px)", true, true, true); |
| // We don't use infinite cull rect for layers with composited transforms. |
| TestTargetChange("will-change: transform; transform: translateX(10px)", |
| "will-change: transform; transform: translateX(20px)", false, |
| true, false); |
| TestTargetChange("will-change: transform; transform: translateX(10px)", |
| "will-change: transform", false, true, false); |
| TestTargetChange("will-change: transform", |
| "will-change: transform; transform: translateX(10px)", false, |
| true, false); |
| } |
| |
| TEST_P(CullRectUpdateOnPaintPropertyChangeTest, AnimatingTransform) { |
| html_ = html_ + R"HTML( |
| <style> |
| @keyframes test { |
| 0% { transform: translateX(0); } |
| 100% { transform: translateX(200px); } |
| } |
| #target { animation: test 1s infinite; } |
| </style> |
| )HTML"; |
| TestTargetChange("transform: translateX(10px)", "transform: translateX(20px)", |
| false, false, false); |
| TestTargetChange("transform: translateX(10px)", "", false, false, false); |
| TestTargetChange("", "transform: translateX(10px)", false, false, false); |
| } |
| |
| TEST_P(CullRectUpdateOnPaintPropertyChangeTest, ScrollContentsSizeChange) { |
| TestChildChange("", "width: 3000px", true, true, true); |
| TestChildChange("", "height: 3000px", true, true, true); |
| TestChildChange("", "width: 50px; height: 50px", true, true, true); |
| } |
| |
| TEST_P(CullRectUpdateOnPaintPropertyChangeTest, SmallContentsScroll) { |
| // TODO(wangxianzhu): Optimize for scrollers with small contents. |
| bool needs_cull_rect_update = false; |
| TestTargetScroll(ScrollOffset(), ScrollOffset(100, 200), false, |
| needs_cull_rect_update, false); |
| TestTargetScroll(ScrollOffset(100, 200), ScrollOffset(1000, 1000), false, |
| needs_cull_rect_update, false); |
| TestTargetScroll(ScrollOffset(1000, 1000), ScrollOffset(), false, |
| needs_cull_rect_update, false); |
| } |
| |
| TEST_P(CullRectUpdateOnPaintPropertyChangeTest, |
| LargeContentsScrollSmallDeltaOrNotExposingNewContents1) { |
| html_ = html_ + "<style>#child { width: auto; height: 10000px; }</style>"; |
| // Scroll offset changes that are small or won't expose new contents don't |
| // need cull rect update. |
| bool needs_cull_rect_update = false; |
| TestTargetScroll(ScrollOffset(), ScrollOffset(0, 200), false, |
| needs_cull_rect_update, false); |
| TestTargetScroll(ScrollOffset(0, 200), ScrollOffset(), false, |
| needs_cull_rect_update, false); |
| TestTargetScroll(ScrollOffset(0, 2000), ScrollOffset(), false, |
| needs_cull_rect_update, false); |
| TestTargetScroll(ScrollOffset(0, 7000), ScrollOffset(0, 8000), false, |
| needs_cull_rect_update, false); |
| } |
| |
| TEST_P(CullRectUpdateOnPaintPropertyChangeTest, |
| LargeContentsScrollSmallDeltaOrNotExposingNewContents2) { |
| html_ = html_ + "<style>#child { width: 10000px; height: 10000px; }</style>"; |
| // Scroll offset changes that are small or won't expose new contents don't |
| // need cull rect update. |
| bool needs_cull_rect_update = false; |
| TestTargetScroll(ScrollOffset(), ScrollOffset(200, 200), false, |
| needs_cull_rect_update, false); |
| TestTargetScroll(ScrollOffset(200, 200), ScrollOffset(), false, |
| needs_cull_rect_update, false); |
| TestTargetScroll(ScrollOffset(2000, 2000), ScrollOffset(), false, |
| needs_cull_rect_update, false); |
| TestTargetScroll(ScrollOffset(7000, 7000), ScrollOffset(7500, 7500), false, |
| needs_cull_rect_update, false); |
| } |
| |
| TEST_P(CullRectUpdateOnPaintPropertyChangeTest, |
| LargeContentsScrollExposingNewContents) { |
| html_ = html_ + "<style>#child { width: 10000px; height: 10000px; }</style>"; |
| // Big scroll offset changes that will expose new contents to paint need cull |
| // rect update. |
| TestTargetScroll(ScrollOffset(100, 200), ScrollOffset(100, 800), false, true, |
| true); |
| TestTargetScroll(ScrollOffset(100, 800), ScrollOffset(700, 800), false, true, |
| true); |
| TestTargetScroll(ScrollOffset(700, 800), ScrollOffset(1700, 1800), false, |
| true, true); |
| TestTargetScroll(ScrollOffset(8000, 8000), ScrollOffset(0, 8000), false, true, |
| true); |
| TestTargetScroll(ScrollOffset(8000, 100), ScrollOffset(), false, true, true); |
| TestTargetScroll(ScrollOffset(100, 8000), ScrollOffset(), false, true, true); |
| } |
| |
| } // namespace blink |