text: // Copyright 2015 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/paint_layer_painter.h"
  
  #include "testing/gmock/include/gmock/gmock.h"
  #include "third_party/blink/renderer/core/layout/layout_box_model_object.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/graphics/graphics_context.h"
  #include "third_party/blink/renderer/platform/testing/paint_property_test_helpers.h"
  #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
  
  using testing::ElementsAre;
  using testing::UnorderedElementsAre;
  
  namespace blink {
  
  class PaintLayerPainterTest : public PaintControllerPaintTest {
    USING_FAST_MALLOC(PaintLayerPainterTest);
  
   public:
    void ExpectPaintedOutputInvisibleAndPaintsWithTransparency(
        const char* element_name,
        bool expected_invisible,
        bool expected_paints_with_transparency) {
      // The optimization to skip painting for effectively-invisible content is
      // limited to pre-CAP.
      if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
        return;
  
      PaintLayer* target_layer =
          ToLayoutBox(GetLayoutObjectByElementId(element_name))->Layer();
      bool invisible = PaintLayerPainter::PaintedOutputInvisible(
          target_layer->GetLayoutObject().StyleRef());
      EXPECT_EQ(expected_invisible, invisible);
      EXPECT_EQ(expected_paints_with_transparency,
                target_layer->PaintsWithTransparency(kGlobalPaintNormalPhase));
    }
  
    PaintController& MainGraphicsLayerPaintController() {
      return GetLayoutView()
          .Layer()
          ->GraphicsLayerBacking(&GetLayoutView())
          ->GetPaintController();
    }
  };
  
  INSTANTIATE_PAINT_TEST_SUITE_P(PaintLayerPainterTest);
  
  TEST_P(PaintLayerPainterTest, CachedSubsequenceAndChunksWithBackgrounds) {
    SetBodyInnerHTML(R"HTML(
      <style>body { margin: 0 }</style>
      <div id='container1' style='position: relative; z-index: 1;
          width: 200px; height: 200px; background-color: blue'>
        <div id='content1' style='position: absolute; width: 100px;
            height: 100px; background-color: red'></div>
      </div>
      <div id='filler1' style='position: relative; z-index: 2;
          width: 20px; height: 20px; background-color: gray'></div>
      <div id='container2' style='position: relative; z-index: 3;
          width: 200px; height: 200px; background-color: blue'>
        <div id='content2' style='position: absolute; width: 100px;
            height: 100px; background-color: green;'></div>
      </div>
      <div id='filler2' style='position: relative; z-index: 4;
          width: 20px; height: 20px; background-color: gray'></div>
    )HTML");
  
    auto* container1 = GetLayoutObjectByElementId("container1");
    auto* content1 = GetLayoutObjectByElementId("content1");
    auto* filler1 = GetLayoutObjectByElementId("filler1");
    auto* container2 = GetLayoutObjectByElementId("container2");
    auto* content2 = GetLayoutObjectByElementId("content2");
    auto* filler2 = GetLayoutObjectByElementId("filler2");
    const auto& view_client = ViewScrollingBackgroundClient();
  
    auto* container1_layer = ToLayoutBoxModelObject(container1)->Layer();
    auto* filler1_layer = ToLayoutBoxModelObject(filler1)->Layer();
    auto* container2_layer = ToLayoutBoxModelObject(container2)->Layer();
    auto* filler2_layer = ToLayoutBoxModelObject(filler2)->Layer();
    auto chunk_state = GetLayoutView().FirstFragment().ContentsProperties();
  
    auto check_results = [&]() {
      EXPECT_THAT(
          RootPaintController().GetDisplayItemList(),
          ElementsAre(IsSameId(&view_client, kDocumentBackgroundType),
                      IsSameId(GetDisplayItemClientFromLayoutObject(container1),
                               kBackgroundType),
                      IsSameId(GetDisplayItemClientFromLayoutObject(content1),
                               kBackgroundType),
                      IsSameId(GetDisplayItemClientFromLayoutObject(filler1),
                               kBackgroundType),
                      IsSameId(GetDisplayItemClientFromLayoutObject(container2),
                               kBackgroundType),
                      IsSameId(GetDisplayItemClientFromLayoutObject(content2),
                               kBackgroundType),
                      IsSameId(GetDisplayItemClientFromLayoutObject(filler2),
                               kBackgroundType)));
  
      // Check that new paint chunks were forced for the layers.
      if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
        EXPECT_SUBSEQUENCE(*container1_layer, 2, 3);
        EXPECT_SUBSEQUENCE(*filler1_layer, 3, 4);
        EXPECT_SUBSEQUENCE(*container2_layer, 4, 5);
        EXPECT_SUBSEQUENCE(*filler2_layer, 5, 6);
  
        EXPECT_THAT(
            RootPaintController().PaintChunks(),
            ElementsAre(
                IsPaintChunk(0, 0), IsPaintChunk(0, 1),  // LayoutView chunks.
                IsPaintChunk(
                    1, 3,
                    PaintChunk::Id(*container1_layer, DisplayItem::kLayerChunk),
                    chunk_state, nullptr, IntRect(0, 0, 200, 200)),
                IsPaintChunk(
                    3, 4,
                    PaintChunk::Id(*filler1_layer, DisplayItem::kLayerChunk),
                    chunk_state, nullptr, IntRect(0, 200, 20, 20)),
                IsPaintChunk(
                    4, 6,
                    PaintChunk::Id(*container2_layer, DisplayItem::kLayerChunk),
                    chunk_state, nullptr, IntRect(0, 220, 200, 200)),
                IsPaintChunk(
                    6, 7,
                    PaintChunk::Id(*filler2_layer, DisplayItem::kLayerChunk),
                    chunk_state, nullptr, IntRect(0, 420, 20, 20))));
      } else {
        EXPECT_SUBSEQUENCE(*container1_layer, 1, 2);
        EXPECT_SUBSEQUENCE(*filler1_layer, 2, 3);
        EXPECT_SUBSEQUENCE(*container2_layer, 3, 4);
        EXPECT_SUBSEQUENCE(*filler2_layer, 4, 5);
  
        EXPECT_THAT(
            RootPaintController().PaintChunks(),
            ElementsAre(
                IsPaintChunk(0, 1),  // LayoutView.
                IsPaintChunk(
                    1, 3,
                    PaintChunk::Id(*container1_layer, DisplayItem::kLayerChunk),
                    chunk_state, nullptr, IntRect(0, 0, 200, 200)),
                IsPaintChunk(
                    3, 4,
                    PaintChunk::Id(*filler1_layer, DisplayItem::kLayerChunk),
                    chunk_state, nullptr, IntRect(0, 200, 20, 20)),
                IsPaintChunk(
                    4, 6,
                    PaintChunk::Id(*container2_layer, DisplayItem::kLayerChunk),
                    chunk_state, nullptr, IntRect(0, 220, 200, 200)),
                IsPaintChunk(
                    6, 7,
                    PaintChunk::Id(*filler2_layer, DisplayItem::kLayerChunk),
                    chunk_state, nullptr, IntRect(0, 420, 20, 20))));
      }
    };
  
    check_results();
  
    To<HTMLElement>(content1->GetNode())
        ->setAttribute(html_names::kStyleAttr,
                       "position: absolute; width: 100px; height: 100px; "
                       "background-color: green");
    GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
        DocumentUpdateReason::kTest);
    EXPECT_TRUE(PaintWithoutCommit());
    EXPECT_EQ(6, NumCachedNewItems());
  
    CommitAndFinishCycle();
  
    // We should still have the paint chunks forced by the cached subsequences.
    check_results();
  }
  
  TEST_P(PaintLayerPainterTest, CachedSubsequenceAndChunksWithoutBackgrounds) {
    if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
      return;
  
    SetBodyInnerHTML(R"HTML(
      <style>
        body { margin: 0 }
        ::-webkit-scrollbar { display: none }
      </style>
      <div id='container' style='position: relative; z-index: 0;
          width: 150px; height: 150px; overflow: scroll'>
        <div id='content' style='position: relative; z-index: 1;
            width: 200px; height: 100px'>
          <div id='inner-content'
               style='position: absolute; width: 100px; height: 100px'></div>
        </div>
        <div id='filler' style='position: relative; z-index: 2;
            width: 300px; height: 300px'></div>
      </div>
    )HTML");
  
    auto* container = GetLayoutObjectByElementId("container");
    auto* content = GetLayoutObjectByElementId("content");
    auto* inner_content = GetLayoutObjectByElementId("inner-content");
    auto* filler = GetLayoutObjectByElementId("filler");
    const auto& view_client = ViewScrollingBackgroundClient();
  
    EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                ElementsAre(IsSameId(&view_client, kDocumentBackgroundType)));
  
    auto* container_layer = ToLayoutBoxModelObject(container)->Layer();
    auto* content_layer = ToLayoutBoxModelObject(content)->Layer();
    auto* inner_content_layer = ToLayoutBoxModelObject(inner_content)->Layer();
    auto* filler_layer = ToLayoutBoxModelObject(filler)->Layer();
  
    EXPECT_SUBSEQUENCE(*container_layer, 2, 6);
    EXPECT_SUBSEQUENCE(*content_layer, 4, 5);
    EXPECT_SUBSEQUENCE(*filler_layer, 5, 6);
  
    auto container_properties =
        container->FirstFragment().LocalBorderBoxProperties();
    auto content_properties = container->FirstFragment().ContentsProperties();
    HitTestData scroll_hit_test;
    scroll_hit_test.scroll_translation =
        container->FirstFragment().PaintProperties()->ScrollTranslation();
    scroll_hit_test.scroll_hit_test_rect = IntRect(0, 0, 150, 150);
  
    EXPECT_THAT(
        RootPaintController().PaintChunks(),
        ElementsAre(
            IsPaintChunk(0, 0), IsPaintChunk(0, 1),  // LayoutView chunks.
            IsPaintChunk(
                1, 1, PaintChunk::Id(*container_layer, DisplayItem::kLayerChunk),
                container_properties, nullptr, IntRect(0, 0, 150, 150)),
            IsPaintChunk(
                1, 1, PaintChunk::Id(*container, DisplayItem::kScrollHitTest),
                container_properties, &scroll_hit_test, IntRect(0, 0, 150, 150)),
            IsPaintChunk(1, 1,
                         PaintChunk::Id(*content_layer, DisplayItem::kLayerChunk),
                         content_properties, nullptr, IntRect(0, 0, 200, 100)),
            IsPaintChunk(
                1, 1, PaintChunk::Id(*filler_layer, DisplayItem::kLayerChunk),
                content_properties, nullptr, IntRect(0, 100, 300, 300))));
  
    To<HTMLElement>(inner_content->GetNode())
        ->setAttribute(html_names::kStyleAttr,
                       "position: absolute; width: 100px; height: 100px; "
                       "top: 100px; background-color: green");
    UpdateAllLifecyclePhasesForTest();
  
    EXPECT_THAT(
        RootPaintController().GetDisplayItemList(),
        ElementsAre(IsSameId(&view_client, kDocumentBackgroundType),
                    IsSameId(GetDisplayItemClientFromLayoutObject(inner_content),
                             kBackgroundType)));
  
    EXPECT_SUBSEQUENCE(*container_layer, 2, 7);
    EXPECT_SUBSEQUENCE(*content_layer, 4, 6);
    EXPECT_SUBSEQUENCE(*filler_layer, 6, 7);
  
    EXPECT_THAT(
        RootPaintController().PaintChunks(),
        ElementsAre(
            IsPaintChunk(0, 0), IsPaintChunk(0, 1),  // LayoutView chunks.
            IsPaintChunk(
                1, 1, PaintChunk::Id(*container_layer, DisplayItem::kLayerChunk),
                container_properties, nullptr, IntRect(0, 0, 150, 150)),
            IsPaintChunk(
                1, 1, PaintChunk::Id(*container, DisplayItem::kScrollHitTest),
                container_properties, &scroll_hit_test, IntRect(0, 0, 150, 150)),
            IsPaintChunk(1, 1,
                         PaintChunk::Id(*content_layer, DisplayItem::kLayerChunk),
                         content_properties, nullptr, IntRect(0, 0, 200, 100)),
            IsPaintChunk(
                1, 2,
                PaintChunk::Id(*inner_content_layer, DisplayItem::kLayerChunk),
                content_properties, nullptr, IntRect(0, 100, 100, 100)),
            IsPaintChunk(
                2, 2, PaintChunk::Id(*filler_layer, DisplayItem::kLayerChunk),
                content_properties, nullptr, IntRect(0, 100, 300, 300))));
  }
  
  TEST_P(PaintLayerPainterTest, CachedSubsequenceOnCullRectChange) {
    // This test doesn't work in CompositeAfterPaint mode because we always paint
    // from the local root frame view, and we always expand cull rect for
    // scrolling.
    if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
      return;
  
    SetBodyInnerHTML(R"HTML(
      <div id='container1' style='position: relative; z-index: 1;
         width: 200px; height: 200px; background-color: blue'>
        <div id='content1' style='position: absolute; width: 100px;
            height: 100px; background-color: green'></div>
      </div>
      <div id='container2' style='position: relative; z-index: 1;
          width: 200px; height: 200px; background-color: blue'>
        <div id='content2a' style='position: absolute; width: 100px;
            height: 100px; background-color: green'></div>
        <div id='content2b' style='position: absolute; top: 200px;
            width: 100px; height: 100px; background-color: green'></div>
      </div>
      <div id='container3' style='position: absolute; z-index: 2;
          left: 300px; top: 0; width: 200px; height: 200px;
          background-color: blue'>
        <div id='content3' style='position: absolute; width: 200px;
            height: 200px; background-color: green'></div>
      </div>
    )HTML");
    InvalidateAll(RootPaintController());
  
    const DisplayItemClient& container1 =
        *GetDisplayItemClientFromElementId("container1");
    const DisplayItemClient& content1 =
        *GetDisplayItemClientFromElementId("content1");
    const DisplayItemClient& container2 =
        *GetDisplayItemClientFromElementId("container2");
    const DisplayItemClient& content2a =
        *GetDisplayItemClientFromElementId("content2a");
    const DisplayItemClient& content2b =
        *GetDisplayItemClientFromElementId("content2b");
    const DisplayItemClient& container3 =
        *GetDisplayItemClientFromElementId("container3");
    const DisplayItemClient& content3 =
        *GetDisplayItemClientFromElementId("content3");
  
    GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
        DocumentUpdateReason::kTest);
    Paint(IntRect(0, 0, 400, 300));
  
    const auto& background_display_item_client = ViewScrollingBackgroundClient();
  
    // Container1 is fully in the interest rect;
    // Container2 is partly (including its stacking chidren) in the interest rect;
    // Content2b is out of the interest rect and output nothing;
    // Container3 is partly in the interest rect.
    EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                ElementsAre(IsSameId(&background_display_item_client,
                                     kDocumentBackgroundType),
                            IsSameId(&container1, kBackgroundType),
                            IsSameId(&content1, kBackgroundType),
                            IsSameId(&container2, kBackgroundType),
                            IsSameId(&content2a, kBackgroundType),
                            IsSameId(&container3, kBackgroundType),
                            IsSameId(&content3, kBackgroundType)));
  
    GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
        DocumentUpdateReason::kTest);
    EXPECT_TRUE(PaintWithoutCommit(IntRect(0, 100, 300, 1000)));
  
    // Container1 becomes partly in the interest rect, but uses cached subsequence
    // because it was fully painted before;
    // Container2's intersection with the interest rect changes;
    // Content2b is out of the interest rect and outputs nothing;
    // Container3 becomes out of the interest rect and outputs empty subsequence
    // pair.
    EXPECT_EQ(5, NumCachedNewItems());
  
    CommitAndFinishCycle();
  
    EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                ElementsAre(IsSameId(&background_display_item_client,
                                     kDocumentBackgroundType),
                            IsSameId(&container1, kBackgroundType),
                            IsSameId(&content1, kBackgroundType),
                            IsSameId(&container2, kBackgroundType),
                            IsSameId(&content2a, kBackgroundType),
                            IsSameId(&content2b, kBackgroundType)));
  }
  
  TEST_P(PaintLayerPainterTest,
         CachedSubsequenceOnCullRectChangeUnderInvalidationChecking) {
    ScopedPaintUnderInvalidationCheckingForTest under_invalidation_checking(true);
  
    SetBodyInnerHTML(R"HTML(
      <style>p { width: 200px; height: 50px; background: green }</style>
      <div id='target' style='position: relative; z-index: 1'>
        <p></p><p></p><p></p><p></p>
      </div>
    )HTML");
    InvalidateAll(RootPaintController());
  
    // |target| will be fully painted.
    GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
        DocumentUpdateReason::kTest);
    Paint(IntRect(0, 0, 400, 300));
  
    // |target| will be partially painted. Should not trigger under-invalidation
    // checking DCHECKs.
    GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
        DocumentUpdateReason::kTest);
    Paint(IntRect(0, 100, 300, 1000));
  }
  
  TEST_P(PaintLayerPainterTest,
         CachedSubsequenceOnStyleChangeWithCullRectClipping) {
    SetBodyInnerHTML(R"HTML(
      <div id='container1' style='position: relative; z-index: 1;
          width: 200px; height: 200px; background-color: blue'>
        <div id='content1' style='position: absolute; width: 100px;
            height: 100px; background-color: red'></div>
      </div>
      <div id='container2' style='position: relative; z-index: 1;
          width: 200px; height: 200px; background-color: blue'>
        <div id='content2' style='position: absolute; width: 100px;
            height: 100px; background-color: green'></div>
      </div>
    )HTML");
    GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
        DocumentUpdateReason::kTest);
    // PaintResult of all subsequences will be MayBeClippedByCullRect.
    Paint(IntRect(0, 0, 50, 300));
  
    const DisplayItemClient& container1 =
        *GetDisplayItemClientFromElementId("container1");
    const DisplayItemClient& content1 =
        *GetDisplayItemClientFromElementId("content1");
    const DisplayItemClient& container2 =
        *GetDisplayItemClientFromElementId("container2");
    const DisplayItemClient& content2 =
        *GetDisplayItemClientFromElementId("content2");
  
    const auto& background_display_item_client = ViewScrollingBackgroundClient();
    EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                ElementsAre(IsSameId(&background_display_item_client,
                                     kDocumentBackgroundType),
                            IsSameId(&container1, kBackgroundType),
                            IsSameId(&content1, kBackgroundType),
                            IsSameId(&container2, kBackgroundType),
                            IsSameId(&content2, kBackgroundType)));
  
    To<HTMLElement>(GetElementById("content1"))
        ->setAttribute(html_names::kStyleAttr,
                       "position: absolute; width: 100px; height: 100px; "
                       "background-color: green");
    GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
        DocumentUpdateReason::kTest);
    EXPECT_TRUE(PaintWithoutCommit(IntRect(0, 0, 50, 300)));
    EXPECT_EQ(4, NumCachedNewItems());
  
    CommitAndFinishCycle();
  
    EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                ElementsAre(IsSameId(&background_display_item_client,
                                     kDocumentBackgroundType),
                            IsSameId(&container1, kBackgroundType),
                            IsSameId(&content1, kBackgroundType),
                            IsSameId(&container2, kBackgroundType),
                            IsSameId(&content2, kBackgroundType)));
  }
  
  TEST_P(PaintLayerPainterTest, CachedSubsequenceRetainsPreviousPaintResult) {
    SetBodyInnerHTML(R"HTML(
      <style>
        html, body { height: 100%; margin: 0 }
        ::-webkit-scrollbar { display:none }
      </style>
      <div id="target" style="height: 8000px; contain: paint">
        <div id="content1" style="height: 100px; background: blue"></div>
        <div style="height: 6000px"></div>
        <div id="content2" style="height: 100px; background: blue"></div>
      </div>
      <div id="change" style="display: none"></div>
    )HTML");
  
    const auto* target = ToLayoutBox(GetLayoutObjectByElementId("target"));
    const auto* target_layer = target->Layer();
    const auto* content1 = GetLayoutObjectByElementId("content1");
    const auto* content2 = GetLayoutObjectByElementId("content2");
    const auto& view_client = ViewScrollingBackgroundClient();
    // |target| is partially painted.
    EXPECT_EQ(kMayBeClippedByCullRect, target_layer->PreviousPaintResult());
    if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
      // CAP doesn't clip the cull rect by the scrolling contents rect, which
      // doesn't affect painted results.
      EXPECT_EQ(CullRect(IntRect(-4000, -4000, 8800, 8600)),
                target_layer->PreviousCullRect());
      // |content2| is out of the cull rect.
      EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                  ElementsAre(IsSameId(&view_client, kDocumentBackgroundType),
                              IsSameId(content1, kBackgroundType)));
      // |target| created subsequence.
      EXPECT_SUBSEQUENCE(*target_layer, 2, 4);
      EXPECT_EQ(0u, RootPaintController().PaintChunks()[2].size());
      EXPECT_EQ(1u, RootPaintController().PaintChunks()[3].size());
    } else {
      EXPECT_EQ(CullRect(IntRect(0, 0, 800, 4600)),
                target_layer->PreviousCullRect());
      // |content2| is out of the cull rect.
      EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                  ElementsAre(IsSameId(&view_client, kDocumentBackgroundType),
                              IsSameId(content1, kBackgroundType)));
      // |target| created subsequence.
      EXPECT_SUBSEQUENCE(*target_layer, 1, 2);
      EXPECT_EQ(1u, RootPaintController().PaintChunks()[1].size());
    }
  
    // Change something that triggers a repaint but |target| should use cached
    // subsequence.
    GetDocument().getElementById("change")->setAttribute(html_names::kStyleAttr,
                                                         "display: block");
    GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
        DocumentUpdateReason::kTest);
    EXPECT_FALSE(target_layer->SelfNeedsRepaint());
    EXPECT_TRUE(PaintWithoutCommit());
    EXPECT_EQ(2, NumCachedNewItems());
    CommitAndFinishCycle();
  
    // |target| is still partially painted.
    EXPECT_EQ(kMayBeClippedByCullRect, target_layer->PreviousPaintResult());
    if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
      // CAP doesn't clip the cull rect by the scrolling contents rect, which
      // doesn't affect painted results.
      EXPECT_EQ(CullRect(IntRect(-4000, -4000, 8800, 8600)),
                target_layer->PreviousCullRect());
      EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                  ElementsAre(IsSameId(&view_client, kDocumentBackgroundType),
                              IsSameId(content1, kBackgroundType)));
      // |target| still created subsequence (cached).
      EXPECT_SUBSEQUENCE(*target_layer, 2, 4);
      EXPECT_EQ(0u, RootPaintController().PaintChunks()[2].size());
      EXPECT_EQ(1u, RootPaintController().PaintChunks()[3].size());
    } else {
      EXPECT_EQ(CullRect(IntRect(0, 0, 800, 4600)),
                target_layer->PreviousCullRect());
      EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                  ElementsAre(IsSameId(&view_client, kDocumentBackgroundType),
                              IsSameId(content1, kBackgroundType)));
      // |target| still created subsequence (cached).
      EXPECT_SUBSEQUENCE(*target_layer, 1, 2);
      EXPECT_EQ(1u, RootPaintController().PaintChunks()[1].size());
    }
  
    // Scroll the view so that both |content1| and |content2| are in the interest
    // rect.
    GetLayoutView().GetScrollableArea()->SetScrollOffset(
        ScrollOffset(0, 3000), mojom::blink::ScrollType::kProgrammatic);
    GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
        DocumentUpdateReason::kTest);
    // Scrolling doesn't set SelfNeedsRepaint flag. Change of paint dirty rect of
    // a partially painted layer will trigger repaint.
    EXPECT_FALSE(target_layer->SelfNeedsRepaint());
    EXPECT_TRUE(PaintWithoutCommit());
    EXPECT_EQ(2, NumCachedNewItems());
    CommitAndFinishCycle();
  
    // |target| is still partially painted.
    EXPECT_EQ(kMayBeClippedByCullRect, target_layer->PreviousPaintResult());
    if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
      // CAP doesn't clip the cull rect by the scrolling contents rect, which
      // doesn't affect painted results.
      EXPECT_EQ(CullRect(IntRect(-4000, -1000, 8800, 8600)),
                target_layer->PreviousCullRect());
      // Painted result should include both |content1| and |content2|.
      EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                  ElementsAre(IsSameId(&view_client, kDocumentBackgroundType),
                              IsSameId(content1, kBackgroundType),
                              IsSameId(content2, kBackgroundType)));
      // |target| still created subsequence (repainted).
      EXPECT_SUBSEQUENCE(*target_layer, 2, 4);
      EXPECT_EQ(0u, RootPaintController().PaintChunks()[2].size());
      EXPECT_EQ(2u, RootPaintController().PaintChunks()[3].size());
    } else {
      EXPECT_EQ(CullRect(IntRect(0, 0, 800, 7600)),
                target_layer->PreviousCullRect());
      // Painted result should include both |content1| and |content2|.
      EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                  ElementsAre(IsSameId(&view_client, kDocumentBackgroundType),
                              IsSameId(content1, kBackgroundType),
                              IsSameId(content2, kBackgroundType)));
      // |target| still created subsequence (repainted).
      EXPECT_SUBSEQUENCE(*target_layer, 1, 2);
      EXPECT_EQ(2u, RootPaintController().PaintChunks()[1].size());
    }
  }
  
  TEST_P(PaintLayerPainterTest, HintedPaintChunksWithBackgrounds) {
    SetBodyInnerHTML(R"HTML(
      <style>
        body { margin: 0 }
        div { background: blue }
      </style>
      <div id='container1' style='position: relative; height: 150px; z-index: 1'>
        <div id='content1a' style='position: relative; height: 100px'></div>
        <div id='content1b' style='position: relative; height: 100px'></div>
      </div>
      <div id='container2' style='position: relative; z-index: 1'>
        <div id='content2a' style='position: relative; height: 100px'></div>
        <div id='content2b' style='position: relative; z-index: -1; height: 100px'></div>
      </div>
    )HTML");
  
    auto* container1 = ToLayoutBox(GetLayoutObjectByElementId("container1"));
    auto* content1a = ToLayoutBox(GetLayoutObjectByElementId("content1a"));
    auto* content1b = ToLayoutBox(GetLayoutObjectByElementId("content1b"));
    auto* container2 = ToLayoutBox(GetLayoutObjectByElementId("container2"));
    auto* content2a = ToLayoutBox(GetLayoutObjectByElementId("content2a"));
    auto* content2b = ToLayoutBox(GetLayoutObjectByElementId("content2b"));
    auto chunk_state = GetLayoutView().FirstFragment().ContentsProperties();
  
    EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                ElementsAre(IsSameId(&ViewScrollingBackgroundClient(),
                                     kDocumentBackgroundType),
                            IsSameId(container1, kBackgroundType),
                            IsSameId(content1a, kBackgroundType),
                            IsSameId(content1b, kBackgroundType),
                            IsSameId(container2, kBackgroundType),
                            IsSameId(content2b, kBackgroundType),
                            IsSameId(content2a, kBackgroundType)));
  
    if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
      HitTestData scroll_hit_test;
      scroll_hit_test.scroll_translation = &ToUnaliased(chunk_state.Transform());
      scroll_hit_test.scroll_hit_test_rect = IntRect(0, 0, 800, 600);
      EXPECT_THAT(
          RootPaintController().PaintChunks(),
          ElementsAre(
              IsPaintChunk(
                  0, 0,
                  PaintChunk::Id(GetLayoutView(), DisplayItem::kScrollHitTest),
                  GetLayoutView().FirstFragment().LocalBorderBoxProperties(),
                  &scroll_hit_test),
              IsPaintChunk(0, 1,
                           PaintChunk::Id(ViewScrollingBackgroundClient(),
                                          kDocumentBackgroundType),
                           chunk_state),
              // Includes |container1| and |content1a|.
              IsPaintChunk(
                  1, 3,
                  PaintChunk::Id(*container1->Layer(), DisplayItem::kLayerChunk),
                  chunk_state, nullptr, IntRect(0, 0, 800, 150)),
              // Includes |content1b| which overflows |container1|.
              IsPaintChunk(
                  3, 4,
                  PaintChunk::Id(*content1b->Layer(), DisplayItem::kLayerChunk),
                  chunk_state, nullptr, IntRect(0, 100, 800, 100)),
              IsPaintChunk(
                  4, 5,
                  PaintChunk::Id(*container2->Layer(), DisplayItem::kLayerChunk),
                  chunk_state, nullptr, IntRect(0, 150, 800, 200)),
              IsPaintChunk(
                  5, 6,
                  PaintChunk::Id(*content2b->Layer(), DisplayItem::kLayerChunk),
                  chunk_state, nullptr, IntRect(0, 250, 800, 100)),
              IsPaintChunk(
                  6, 7,
                  PaintChunk::Id(*content2a->Layer(), DisplayItem::kLayerChunk),
                  chunk_state, nullptr, IntRect(0, 150, 800, 100))));
    } else {
      EXPECT_THAT(
          RootPaintController().PaintChunks(),
          ElementsAre(
              IsPaintChunk(0, 1,
                           PaintChunk::Id(ViewScrollingBackgroundClient(),
                                          kDocumentBackgroundType),
                           chunk_state),
              // Includes |container1| and |content1a|.
              IsPaintChunk(
                  1, 3,
                  PaintChunk::Id(*container1->Layer(), DisplayItem::kLayerChunk),
                  chunk_state, nullptr, IntRect(0, 0, 800, 150)),
              // Includes |content1b| which overflows |container1|.
              IsPaintChunk(
                  3, 4,
                  PaintChunk::Id(*content1b->Layer(), DisplayItem::kLayerChunk),
                  chunk_state, nullptr, IntRect(0, 100, 800, 100)),
              IsPaintChunk(
                  4, 5,
                  PaintChunk::Id(*container2->Layer(), DisplayItem::kLayerChunk),
                  chunk_state, nullptr, IntRect(0, 150, 800, 200)),
              IsPaintChunk(
                  5, 6,
                  PaintChunk::Id(*content2b->Layer(), DisplayItem::kLayerChunk),
                  chunk_state, nullptr, IntRect(0, 250, 800, 100)),
              IsPaintChunk(
                  6, 7,
                  PaintChunk::Id(*content2a->Layer(), DisplayItem::kLayerChunk),
                  chunk_state, nullptr, IntRect(0, 150, 800, 100))));
    }
  }
  
  TEST_P(PaintLayerPainterTest, HintedPaintChunksWithoutBackgrounds) {
    if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
      return;
  
    SetBodyInnerHTML(R"HTML(
      <style>body { margin: 0 }</style>
      <div id='container1' style='position: relative; height: 150px; z-index: 1'>
        <div id='content1a' style='position: relative; height: 100px'></div>
        <div id='content1b' style='position: relative; height: 100px'></div>
      </div>
      <div id='container2' style='position: relative; z-index: 1'>
        <div id='content2a' style='position: relative; height: 100px'></div>
        <div id='content2b'
             style='position: relative; z-index: -1; height: 100px'></div>
      </div>
    )HTML");
  
    auto* container1 = ToLayoutBox(GetLayoutObjectByElementId("container1"));
    auto* content1b = ToLayoutBox(GetLayoutObjectByElementId("content1b"));
    auto* container2 = ToLayoutBox(GetLayoutObjectByElementId("container2"));
    auto* content2a = ToLayoutBox(GetLayoutObjectByElementId("content2a"));
    auto* content2b = ToLayoutBox(GetLayoutObjectByElementId("content2b"));
    auto chunk_state = GetLayoutView().FirstFragment().ContentsProperties();
  
    EXPECT_THAT(RootPaintController().GetDisplayItemList(),
                ElementsAre(IsSameId(&ViewScrollingBackgroundClient(),
                                     kDocumentBackgroundType)));
  
    HitTestData scroll_hit_test;
    scroll_hit_test.scroll_translation = &ToUnaliased(chunk_state.Transform());
    scroll_hit_test.scroll_hit_test_rect = IntRect(0, 0, 800, 600);
    EXPECT_THAT(
        RootPaintController().PaintChunks(),
        ElementsAre(
            IsPaintChunk(
                0, 0,
                PaintChunk::Id(GetLayoutView(), DisplayItem::kScrollHitTest),
                GetLayoutView().FirstFragment().LocalBorderBoxProperties(),
                &scroll_hit_test),
            IsPaintChunk(0, 1,
                         PaintChunk::Id(ViewScrollingBackgroundClient(),
                                        kDocumentBackgroundType),
                         chunk_state),
            IsPaintChunk(
                1, 1,
                PaintChunk::Id(*container1->Layer(), DisplayItem::kLayerChunk),
                chunk_state, nullptr, IntRect(0, 0, 800, 150)),
            IsPaintChunk(
                1, 1,
                PaintChunk::Id(*content1b->Layer(), DisplayItem::kLayerChunk),
                chunk_state, nullptr, IntRect(0, 100, 800, 100)),
            IsPaintChunk(
                1, 1,
                PaintChunk::Id(*container2->Layer(), DisplayItem::kLayerChunk),
                chunk_state, nullptr, IntRect(0, 150, 800, 200)),
            IsPaintChunk(
                1, 1,
                PaintChunk::Id(*content2b->Layer(), DisplayItem::kLayerChunk),
                chunk_state, nullptr, IntRect(0, 250, 800, 100)),
            IsPaintChunk(
                1, 1,
                PaintChunk::Id(*content2a->Layer(), DisplayItem::kLayerChunk),
                chunk_state, nullptr, IntRect(0, 150, 800, 100))));
  }
  
  TEST_P(PaintLayerPainterTest, PaintPhaseOutline) {
    AtomicString style_without_outline =
        "width: 50px; height: 50px; background-color: green";
    AtomicString style_with_outline =
        "outline: 1px solid blue; " + style_without_outline;
    SetBodyInnerHTML(R"HTML(
      <div id='self-painting-layer' style='position: absolute'>
        <div id='non-self-painting-layer' style='overflow: hidden'>
          <div>
            <div id='outline'></div>
          </div>
        </div>
      </div>
    )HTML");
    LayoutObject& outline_div =
        *GetDocument().getElementById("outline")->GetLayoutObject();
    To<HTMLElement>(outline_div.GetNode())
        ->setAttribute(html_names::kStyleAttr, style_without_outline);
    UpdateAllLifecyclePhasesForTest();
  
    LayoutBoxModelObject& self_painting_layer_object = *ToLayoutBoxModelObject(
        GetDocument().getElementById("self-painting-layer")->GetLayoutObject());
    PaintLayer& self_painting_layer = *self_painting_layer_object.Layer();
    ASSERT_TRUE(self_painting_layer.IsSelfPaintingLayer());
    PaintLayer& non_self_painting_layer =
        *ToLayoutBoxModelObject(GetDocument()
                                    .getElementById("non-self-painting-layer")
                                    ->GetLayoutObject())
             ->Layer();
    ASSERT_FALSE(non_self_painting_layer.IsSelfPaintingLayer());
    ASSERT_TRUE(&non_self_painting_layer == outline_div.EnclosingLayer());
  
    EXPECT_FALSE(self_painting_layer.NeedsPaintPhaseDescendantOutlines());
    EXPECT_FALSE(non_self_painting_layer.NeedsPaintPhaseDescendantOutlines());
  
    // Outline on the self-painting-layer node itself doesn't affect
    // PaintPhaseDescendantOutlines.
    To<HTMLElement>(self_painting_layer_object.GetNode())
        ->setAttribute(html_names::kStyleAttr,
                       "position: absolute; outline: 1px solid green");
    UpdateAllLifecyclePhasesForTest();
    EXPECT_FALSE(self_painting_layer.NeedsPaintPhaseDescendantOutlines());
    EXPECT_FALSE(non_self_painting_layer.NeedsPaintPhaseDescendantOutlines());
    EXPECT_TRUE(DisplayItemListContains(
        RootPaintController().GetDisplayItemList(), self_painting_layer_object,
        DisplayItem::PaintPhaseToDrawingType(PaintPhase::kSelfOutlineOnly)));
  
    // needsPaintPhaseDescendantOutlines should be set when any descendant on the
    // same layer has outline.
    To<HTMLElement>(outline_div.GetNode())
        ->setAttribute(html_names::kStyleAttr, style_with_outline);
    GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
        DocumentUpdateReason::kTest);
    EXPECT_TRUE(self_painting_layer.NeedsPaintPhaseDescendantOutlines());
    EXPECT_FALSE(non_self_painting_layer.NeedsPaintPhaseDescendantOutlines());
    Paint();
    EXPECT_TRUE(DisplayItemListContains(
        RootPaintController().GetDisplayItemList(), outline_div,
        DisplayItem::PaintPhaseToDrawingType(PaintPhase::kSelfOutlineOnly)));
  
    // needsPaintPhaseDescendantOutlines should be reset when no outline is
    // actually painted.
    To<HTMLElement>(outline_div.GetNode())
        ->setAttribute(html_names::kStyleAttr, style_without_outline);
    UpdateAllLifecyclePhasesForTest();
    EXPECT_TRUE(self_painting_layer.NeedsPaintPhaseDescendantOutlines());
  }
  
  TEST_P(PaintLayerPainterTest, PaintPhaseFloat) {
    AtomicString style_without_float =
        "width: 50px; height: 50px; background-color: green";
    AtomicString style_with_float = "float: left; " + style_without_float;
    SetBodyInnerHTML(R"HTML(
      <div id='self-painting-layer' style='position: absolute'>
        <div id='non-self-painting-layer' style='overflow: hidden'>
          <div>
            <div id='float' style='width: 10px; height: 10px;
                background-color: blue'></div>
          </div>
        </div>
      </div>
    )HTML");
    LayoutObject& float_div =
        *GetDocument().getElementById("float")->GetLayoutObject();
    To<HTMLElement>(float_div.GetNode())
        ->setAttribute(html_names::kStyleAttr, style_without_float);
    UpdateAllLifecyclePhasesForTest();
  
    LayoutBoxModelObject& self_painting_layer_object = *ToLayoutBoxModelObject(
        GetDocument().getElementById("self-painting-layer")->GetLayoutObject());
    PaintLayer& self_painting_layer = *self_painting_layer_object.Layer();
    ASSERT_TRUE(self_painting_layer.IsSelfPaintingLayer());
    PaintLayer& non_self_painting_layer =
        *ToLayoutBoxModelObject(GetDocument()
                                    .getElementById("non-self-painting-layer")
                                    ->GetLayoutObject())
             ->Layer();
    ASSERT_FALSE(non_self_painting_layer.IsSelfPaintingLayer());
    ASSERT_TRUE(&non_self_painting_layer == float_div.EnclosingLayer());
  
    EXPECT_FALSE(self_painting_layer.NeedsPaintPhaseFloat());
    EXPECT_FALSE(non_self_painting_layer.NeedsPaintPhaseFloat());
  
    // needsPaintPhaseFloat should be set when any descendant on the same layer
    // has float.
    To<HTMLElement>(float_div.GetNode())
        ->setAttribute(html_names::kStyleAttr, style_with_float);
    GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint(
        DocumentUpdateReason::kTest);
    EXPECT_TRUE(self_painting_layer.NeedsPaintPhaseFloat());
    EXPECT_FALSE(non_self_painting_layer.NeedsPaintPhaseFloat());
    Paint();
    EXPECT_TRUE(DisplayItemListContains(
        RootPaintController().GetDisplayItemList(), float_div,
        DisplayItem::kBoxDecorationBackground));
  
    // needsPaintPhaseFloat should be reset when there is no float actually
    // painted.
    To<HTMLElement>(float_div.GetNode())
        ->setAttribute(html_names::kStyleAttr, style_without_float);
    UpdateAllLifecyclePhasesForTest();
    EXPECT_TRUE(self_painting_layer.NeedsPaintPhaseFloat());
  }
  
  TEST_P(PaintLayerPainterTest, PaintPhaseFloatUnderInlineLayer) {
    SetBodyInnerHTML(R"HTML(
      <div id='self-painting-layer' style='position: absolute'>
        <div id='non-self-painting-layer' style='overflow: hidden'>
          <span id='span' style='position: relative'>
            <div id='float' style='width: 10px; height: 10px;
                background-color: blue; float: left'></div>
          </span>
        </div>
      </div>
    )HTML");
    UpdateAllLifecyclePhasesForTest();
  
    LayoutObject& float_div =
        *GetDocument().getElementById("float")->GetLayoutObject();
    LayoutBoxModelObject& span = *ToLayoutBoxModelObject(
        GetDocument().getElementById("span")->GetLayoutObject());
    PaintLayer& span_layer = *span.Layer();
    ASSERT_TRUE(&span_layer == float_div.EnclosingLayer());
    if (RuntimeEnabledFeatures::LayoutNGEnabled()) {
      ASSERT_TRUE(span_layer.NeedsPaintPhaseFloat());
    } else {
      ASSERT_FALSE(span_layer.NeedsPaintPhaseFloat());
    }
    LayoutBoxModelObject& self_painting_layer_object = *ToLayoutBoxModelObject(
        GetDocument().getElementById("self-painting-layer")->GetLayoutObject());
    PaintLayer& self_painting_layer = *self_painting_layer_object.Layer();
    ASSERT_TRUE(self_painting_layer.IsSelfPaintingLayer());
    PaintLayer& non_self_painting_layer =
        *ToLayoutBoxModelObject(GetDocument()
                                    .getElementById("non-self-painting-layer")
                                    ->GetLayoutObject())
             ->Layer();
    ASSERT_FALSE(non_self_painting_layer.IsSelfPaintingLayer());
  
    if (RuntimeEnabledFeatures::LayoutNGEnabled()) {
      EXPECT_FALSE(self_painting_layer.NeedsPaintPhaseFloat());
      EXPECT_TRUE(span_layer.NeedsPaintPhaseFloat());
    } else {
      EXPECT_TRUE(self_painting_layer.NeedsPaintPhaseFloat());
      EXPECT_FALSE(span_layer.NeedsPaintPhaseFloat());
    }
    EXPECT_FALSE(non_self_painting_layer.NeedsPaintPhaseFloat());
    EXPECT_TRUE(DisplayItemListContains(
        RootPaintController().GetDisplayItemList(), float_div,
        DisplayItem::kBoxDecorationBackground));
  }
  
  TEST_P(PaintLayerPainterTest, PaintPhasesUpdateOnLayerAddition) {
    SetBodyInnerHTML(R"HTML(
      <div id='will-be-layer'>
        <div style='height: 100px'>
          <div style='height: 20px; outline: 1px solid red;
              background-color: green'>outline and background</div>
          <div style='float: left'>float</div>
        </div>
      </div>
    )HTML");
  
    LayoutBoxModelObject& layer_div = *ToLayoutBoxModelObject(
        GetDocument().getElementById("will-be-layer")->GetLayoutObject());
    EXPECT_FALSE(layer_div.HasLayer());
  
    PaintLayer& html_layer =
        *ToLayoutBoxModelObject(
             GetDocument().documentElement()->GetLayoutObject())
             ->Layer();
    EXPECT_TRUE(html_layer.NeedsPaintPhaseDescendantOutlines());
    EXPECT_TRUE(html_layer.NeedsPaintPhaseFloat());
  
    To<HTMLElement>(layer_div.GetNode())
        ->setAttribute(html_names::kStyleAttr, "position: relative");
    UpdateAllLifecyclePhasesForTest();
    ASSERT_TRUE(layer_div.HasLayer());
    PaintLayer& layer = *layer_div.Layer();
    ASSERT_TRUE(layer.IsSelfPaintingLayer());
    EXPECT_TRUE(layer.NeedsPaintPhaseDescendantOutlines());
    EXPECT_TRUE(layer.NeedsPaintPhaseFloat());
  }
  
  TEST_P(PaintLayerPainterTest, PaintPhasesUpdateOnBecomingSelfPainting) {
    SetBodyInnerHTML(R"HTML(
      <div id='will-be-self-painting' style='width: 100px; height: 100px;
      overflow: hidden'>
        <div>
          <div style='outline: 1px solid red; background-color: green'>
            outline and background
          </div>
        </div>
      </div>
    )HTML");
  
    LayoutBoxModelObject& layer_div = *ToLayoutBoxModelObject(
        GetDocument().getElementById("will-be-self-painting")->GetLayoutObject());
    ASSERT_TRUE(layer_div.HasLayer());
    EXPECT_FALSE(layer_div.Layer()->IsSelfPaintingLayer());
  
    PaintLayer& html_layer =
        *ToLayoutBoxModelObject(
             GetDocument().documentElement()->GetLayoutObject())
             ->Layer();
    EXPECT_TRUE(html_layer.NeedsPaintPhaseDescendantOutlines());
  
    To<HTMLElement>(layer_div.GetNode())
        ->setAttribute(
            html_names::kStyleAttr,
            "width: 100px; height: 100px; overflow: hidden; position: relative");
    UpdateAllLifecyclePhasesForTest();
    PaintLayer& layer = *layer_div.Layer();
    ASSERT_TRUE(layer.IsSelfPaintingLayer());
    EXPECT_TRUE(layer.NeedsPaintPhaseDescendantOutlines());
  }
  
  TEST_P(PaintLayerPainterTest, PaintPhasesUpdateOnBecomingNonSelfPainting) {
    SetBodyInnerHTML(R"HTML(
      <div id='will-be-non-self-painting' style='width: 100px; height: 100px;
      overflow: hidden; position: relative'>
        <div>
          <div style='outline: 1px solid red; background-color: green'>
            outline and background
          </div>
        </div>
      </div>
    )HTML");
  
    LayoutBoxModelObject& layer_div =
        *ToLayoutBoxModelObject(GetDocument()
                                    .getElementById("will-be-non-self-painting")
                                    ->GetLayoutObject());
    ASSERT_TRUE(layer_div.HasLayer());
    PaintLayer& layer = *layer_div.Layer();
    EXPECT_TRUE(layer.IsSelfPaintingLayer());
    EXPECT_TRUE(layer.NeedsPaintPhaseDescendantOutlines());
  
    PaintLayer& html_layer =
        *ToLayoutBoxModelObject(
             GetDocument().documentElement()->GetLayoutObject())
             ->Layer();
    EXPECT_FALSE(html_layer.NeedsPaintPhaseDescendantOutlines());
  
    To<HTMLElement>(layer_div.GetNode())
        ->setAttribute(html_names::kStyleAttr,
                       "width: 100px; height: 100px; overflow: hidden");
    UpdateAllLifecyclePhasesForTest();
    EXPECT_FALSE(layer.IsSelfPaintingLayer());
    EXPECT_TRUE(html_layer.NeedsPaintPhaseDescendantOutlines());
  }
  
  TEST_P(PaintLayerPainterTest, DontPaintWithTinyOpacity) {
    SetBodyInnerHTML(
        "<div id='target' style='background: blue; opacity: 0.0001'></div>");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", true, true);
  }
  
  TEST_P(PaintLayerPainterTest, DoPaintWithTinyOpacityAndWillChangeOpacity) {
    SetBodyInnerHTML(
        "<div id='target' style='background: blue; opacity: 0.0001; "
        "    will-change: opacity'></div>");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false);
  }
  
  TEST_P(PaintLayerPainterTest, DoPaintWithTinyOpacityAndBackdropFilter) {
    SetBodyInnerHTML(
        "<div id='target' style='background: blue; opacity: 0.0001;"
        "    backdrop-filter: blur(2px);'></div>");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false);
  }
  
  TEST_P(PaintLayerPainterTest,
         DoPaintWithTinyOpacityAndBackdropFilterAndWillChangeOpacity) {
    SetBodyInnerHTML(
        "<div id='target' style='background: blue; opacity: 0.0001;"
        "    backdrop-filter: blur(2px); will-change: opacity'></div>");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false);
  }
  
  TEST_P(PaintLayerPainterTest, DoPaintWithCompositedTinyOpacity) {
    SetBodyInnerHTML(
        "<div id='target' style='background: blue; opacity: 0.0001;"
        "    will-change: transform'></div>");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", true, false);
  }
  
  TEST_P(PaintLayerPainterTest, DoPaintWithNonTinyOpacity) {
    SetBodyInnerHTML(
        "<div id='target' style='background: blue; opacity: 0.1'></div>");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, true);
  }
  
  TEST_P(PaintLayerPainterTest, DoPaintWithEffectAnimationZeroOpacity) {
    SetBodyInnerHTML(R"HTML(
      <style>
      div {
        width: 100px;
        height: 100px;
        animation-name: example;
        animation-duration: 4s;
      }
      @keyframes example {
        from { opacity: 0.0;}
        to { opacity: 1.0;}
      }
      </style>
      <div id='target'></div>
    )HTML");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", true, false);
  }
  
  TEST_P(PaintLayerPainterTest, DoPaintWithTransformAnimationZeroOpacity) {
    SetBodyInnerHTML(R"HTML(
      <style>
      div#target {
        animation-name: example;
        animation-duration: 4s;
        opacity: 0.0;
      }
      @keyframes example {
        from { transform: translate(0px, 0px); }
        to { transform: translate(3em, 0px); }
      }
      </style>
      <div id='target'>x</div></div>
    )HTML");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", true, false);
  }
  
  TEST_P(PaintLayerPainterTest,
         DoPaintWithTransformAnimationZeroOpacityWillChangeOpacity) {
    SetBodyInnerHTML(R"HTML(
      <style>
      div#target {
        animation-name: example;
        animation-duration: 4s;
        opacity: 0.0;
        will-change: opacity;
      }
      @keyframes example {
        from { transform: translate(0px, 0px); }
        to { transform: translate(3em, 0px); }
      }
      </style>
      <div id='target'>x</div></div>
    )HTML");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false);
  }
  
  TEST_P(PaintLayerPainterTest, DoPaintWithWillChangeOpacity) {
    SetBodyInnerHTML(R"HTML(
      <style>
      div {
        width: 100px;
        height: 100px;
        will-change: opacity;
      }
      </style>
      <div id='target'></div>
    )HTML");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false);
  }
  
  TEST_P(PaintLayerPainterTest, DoPaintWithZeroOpacityAndWillChangeOpacity) {
    SetBodyInnerHTML(R"HTML(
      <style>
      div {
        width: 100px;
        height: 100px;
        opacity: 0;
        will-change: opacity;
      }
      </style>
      <div id='target'></div>
    )HTML");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false);
  }
  
  TEST_P(PaintLayerPainterTest,
         DoPaintWithNoContentAndZeroOpacityAndWillChangeOpacity) {
    SetBodyInnerHTML(R"HTML(
      <style>
      div {
        width: 100px;
        height: 100px;
        opacity: 0;
        will-change: opacity;
      }
      </style>
      <div id='target'></div>
    )HTML");
    ExpectPaintedOutputInvisibleAndPaintsWithTransparency("target", false, false);
  }
  
  using PaintLayerPainterTestCAP = PaintLayerPainterTest;
  
  INSTANTIATE_CAP_TEST_SUITE_P(PaintLayerPainterTestCAP);
  
  TEST_P(PaintLayerPainterTestCAP, SimpleCullRect) {
    SetBodyInnerHTML(R"HTML(
      <div id='target'
           style='width: 200px; height: 200px; position: relative'>
      </div>
    )HTML");
  
    EXPECT_EQ(IntRect(0, 0, 800, 600),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, 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.
    EXPECT_EQ(IntRect(-4000, -4000, 8800, 8600),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, WideLayerCullRect) {
    SetBodyInnerHTML(R"HTML(
      <div id='target'
           style='width: 10000px; height: 200px; position: relative'>
      </div>
    )HTML");
  
    // Same as TallLayerCullRect.
    EXPECT_EQ(IntRect(-4000, -4000, 8800, 8600),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, TallScrolledLayerCullRect) {
    SetBodyInnerHTML(R"HTML(
      <div id='target' style='width: 200px; height: 10000px; position: relative'>
      </div>
    )HTML");
  
    // Viewport rect (0, 0, 800, 600) expanded by 4000.
    EXPECT_EQ(IntRect(-4000, -4000, 8800, 8600),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  
    GetDocument().View()->LayoutViewport()->SetScrollOffset(
        ScrollOffset(0, 6000), mojom::blink::ScrollType::kProgrammatic);
    UpdateAllLifecyclePhasesForTest();
    EXPECT_EQ(IntRect(-4000, 2000, 8800, 8600),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  
    GetDocument().View()->LayoutViewport()->SetScrollOffset(
        ScrollOffset(0, 6500), mojom::blink::ScrollType::kProgrammatic);
    UpdateAllLifecyclePhasesForTest();
    // Used the previous cull rect because the scroll amount is small.
    EXPECT_EQ(IntRect(-4000, 2000, 8800, 8600),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  
    GetDocument().View()->LayoutViewport()->SetScrollOffset(
        ScrollOffset(0, 6600), mojom::blink::ScrollType::kProgrammatic);
    UpdateAllLifecyclePhasesForTest();
    // Used new cull rect.
    EXPECT_EQ(IntRect(-4000, 2600, 8800, 8600),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, WholeDocumentCullRect) {
    GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(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(GetLayoutView().Layer()->PreviousCullRect().IsInfinite());
    EXPECT_TRUE(
        GetPaintLayerByElementId("relative")->PreviousCullRect().IsInfinite());
    EXPECT_TRUE(
        GetPaintLayerByElementId("fixed")->PreviousCullRect().IsInfinite());
    EXPECT_TRUE(
        GetPaintLayerByElementId("scroll")->PreviousCullRect().IsInfinite());
  
    // Cull rect is normal for contents below scroll other than the viewport.
    EXPECT_EQ(
        IntRect(-4000, -4000, 8200, 8200),
        GetPaintLayerByElementId("below-scroll")->PreviousCullRect().Rect());
  
    EXPECT_THAT(
        RootPaintController().GetDisplayItemList(),
        UnorderedElementsAre(
            IsSameId(&ViewScrollingBackgroundClient(), kDocumentBackgroundType),
            IsSameId(GetDisplayItemClientFromElementId("relative"),
                     kBackgroundType),
            IsSameId(GetDisplayItemClientFromElementId("normal"),
                     kBackgroundType),
            IsSameId(GetDisplayItemClientFromElementId("scroll"),
                     kBackgroundType),
            IsSameId(&ToLayoutBox(GetLayoutObjectByElementId("scroll"))
                          ->GetScrollableArea()
                          ->GetScrollingBackgroundDisplayItemClient(),
                     kBackgroundType),
            IsSameId(GetDisplayItemClientFromElementId("below-scroll"),
                     kBackgroundType),
            IsSameId(GetDisplayItemClientFromElementId("fixed"),
                     kBackgroundType)));
  }
  
  TEST_P(PaintLayerPainterTestCAP, VerticalRightLeftWritingModeDocument) {
    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().View()->LayoutViewport()->SetScrollOffset(
        ScrollOffset(-5000, 0), mojom::blink::ScrollType::kProgrammatic);
    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 yields this result.
    EXPECT_EQ(IntRect(200, -4000, 8800, 8600),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, ScaledCullRect) {
    GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
    SetBodyInnerHTML(R"HTML(
      <div style='width: 200px; height: 300px; overflow: scroll;
                  transform: scaleX(2) scaleY(0.5)'>
        <div id='target' style='height: 400px; position: relative'></div>
      </div>
    )HTML");
  
    // The scale doesn't affect the cull rect.
    EXPECT_EQ(IntRect(-4000, -4000, 8200, 8300),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, ScaledAndRotatedCullRect) {
    GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
    SetBodyInnerHTML(R"HTML(
      <div style='width: 200px; height: 300px; overflow: scroll;
                  transform: scaleX(2) scaleY(0.5) rotateZ(45deg)'>
        <div id='target' style='height: 400px; position: relative'></div>
      </div>
    )HTML");
  
    // The scale and the rotation don't affect the cull rect.
    EXPECT_EQ(IntRect(-4000, -4000, 8200, 8300),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, 3DRotated90DegreesCullRect) {
    GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
    SetBodyInnerHTML(R"HTML(
      <div style='width: 200px; height: 300px; overflow: scroll;
                  transform: rotateY(90deg)'>
        <div id='target' style='height: 400px; position: relative'></div>
      </div>
    )HTML");
  
    // It's rotated 90 degrees about the X axis, which means its visual content
    // rect is empty, we fall back to the 4000px cull rect padding amount.
    EXPECT_EQ(IntRect(-4000, -4000, 8200, 8300),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, 3DRotatedNear90DegreesCullRect) {
    GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
    SetBodyInnerHTML(R"HTML(
      <div style='width: 200px; height: 300px; overflow: scroll;
                  transform: rotateY(89.9999deg)'>
        <div id='target' style='height: 400px; position: relative'></div>
      </div>
    )HTML");
  
    // Because the layer is rotated to almost 90 degrees, floating-point error
    // leads to a reverse-projected rect that is much much larger than the
    // original layer size in certain dimensions. In such cases, we often fall
    // back to the 4000px cull rect padding amount.
    EXPECT_EQ(IntRect(-4000, -4000, 8200, 8300),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, PerspectiveCullRect) {
    SetBodyInnerHTML(R"HTML(
      <div id='target'
           style='width: 100px; height: 100px; transform: perspective(1000px)'>
      </div>
    )HTML");
  
    // Use infinite cull rect with perspective.
    EXPECT_TRUE(
        GetPaintLayerByElementId("target")->PreviousCullRect().IsInfinite());
  }
  
  TEST_P(PaintLayerPainterTestCAP, 3D45DegRotatedTallCullRect) {
    SetBodyInnerHTML(R"HTML(
      <div id='target'
           style='width: 200px; height: 10000px; transform: rotateY(45deg)'>
      </div>
    )HTML");
  
    // Use infinite cull rect with 3d transform.
    EXPECT_TRUE(
        GetPaintLayerByElementId("target")->PreviousCullRect().IsInfinite());
  }
  
  TEST_P(PaintLayerPainterTestCAP, FixedPositionCullRect) {
    SetBodyInnerHTML(R"HTML(
      <div id='target' style='width: 1000px; height: 2000px;
                              position: fixed; top: 100px; left: 200px;'>
      </div>
    )HTML");
  
    EXPECT_EQ(IntRect(-200, -100, 800, 600),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, LayerOffscreenNearCullRect) {
    GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
    SetBodyInnerHTML(R"HTML(
      <div style='width: 200px; height: 300px; overflow: scroll;
                  position: absolute; top: 3000px; left: 0px;'>
        <div id='target' style='height: 500px; position: relative'></div>
      </div>
    )HTML");
  
    EXPECT_EQ(IntRect(-4000, -4000, 8200, 8300),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, LayerOffscreenFarCullRect) {
    GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
    SetBodyInnerHTML(R"HTML(
      <div style='width: 200px; height: 300px; overflow: scroll;
                  position: absolute; top: 9000px'>
        <div id='target' style='height: 500px; position: relative'></div>
      </div>
    )HTML");
  
    // The layer is too far away from the viewport.
    EXPECT_EQ(IntRect(),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, ScrollingLayerCullRect) {
    GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(true);
    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");
  
    // 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.
    EXPECT_EQ(IntRect(-4000, -4000, 8195, 8193),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, NonCompositedScrollingLayerCullRect) {
    GetDocument().GetSettings()->SetPreferCompositingToLCDTextEnabled(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(IntRect(0, 0, 195, 193),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  TEST_P(PaintLayerPainterTestCAP, 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");
  
    // The viewport is not scrollable because of the clip, so the cull rect is
    // just the viewport rect.
    EXPECT_EQ(IntRect(0, 0, 800, 600),
              GetPaintLayerByElementId("target")->PreviousCullRect().Rect());
  }
  
  }  // namespace blink
  