// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/cfi_buildflags.h"
#include "base/functional/bind.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "cc/test/fake_content_layer_client.h"
#include "cc/test/fake_picture_layer.h"
#include "cc/test/fake_picture_layer_impl.h"
#include "cc/test/layer_tree_test.h"
#include "cc/test/property_tree_test_utils.h"
#include "cc/trees/layer_tree_host.h"
#include "cc/trees/layer_tree_impl.h"
#include "components/viz/test/test_context_provider.h"
#include "components/viz/test/test_raster_interface.h"

namespace cc {
namespace {

bool TreesInViz() {
  return base::FeatureList::IsEnabled(features::kTreesInViz);
}

// These tests deal with picture layers.
class LayerTreeHostPictureTest : public LayerTreeTest {
 protected:
  void SetupTreeWithSinglePictureLayer(const gfx::Size& size) {
    scoped_refptr<Layer> root = Layer::Create();
    root->SetBounds(size);

    root_picture_layer_ = FakePictureLayer::Create(&client_);
    root_picture_layer_->SetBounds(size);
    root->AddChild(root_picture_layer_);

    layer_tree_host()->SetRootLayer(root);
    client_.set_bounds(size);
  }

  scoped_refptr<FakePictureLayer> root_picture_layer_;
  FakeContentLayerClient client_;
};

class LayerTreeHostPictureTestTwinLayer
    : public LayerTreeHostPictureTest {
  void SetupTree() override {
    SetupTreeWithSinglePictureLayer(gfx::Size(1, 1));
    picture_id1_ = root_picture_layer_->id();
    picture_id2_ = -1;
  }

  void BeginTest() override {
    // Commit and activate to produce a pending (recycled) layer and an active
    // layer.
    PostSetNeedsCommitToMainThread();
  }

  void DidCommit() override {
    switch (layer_tree_host()->SourceFrameNumber()) {
      case 1:
        // Activate reusing an existing recycled pending layer, to an already
        // existing active layer.
        layer_tree_host()->SetNeedsCommit();
        break;
      case 2:
        // Drop the picture layer from the tree so the activate will have an
        // active layer without a pending twin.
        root_picture_layer_->RemoveFromParent();
        break;
      case 3: {
        // Add a new picture layer so the activate will have a pending layer
        // without an active twin.
        scoped_refptr<FakePictureLayer> picture =
            FakePictureLayer::Create(&client_);
        picture_id2_ = picture->id();
        layer_tree_host()->root_layer()->AddChild(picture);
        break;
      }
      case 4:
        // Activate while there are pending and active twins again.
        layer_tree_host()->SetNeedsCommit();
        break;
    }
  }

  void WillActivateTreeOnThread(LayerTreeHostImpl* impl) override {
    LayerImpl* active_root_impl = impl->active_tree()->root_layer();
    int picture_id = impl->active_tree()->source_frame_number() < 2
                         ? picture_id1_
                         : picture_id2_;

    if (!impl->pending_tree()->LayerById(picture_id)) {
      EXPECT_EQ(2, activates_);
      return;
    }

    FakePictureLayerImpl* pending_picture_impl =
        static_cast<FakePictureLayerImpl*>(
            impl->pending_tree()->LayerById(picture_id));

    if (!active_root_impl) {
      EXPECT_EQ(0, activates_);
      EXPECT_EQ(nullptr, pending_picture_impl->GetPendingOrActiveTwinLayer());
      return;
    }

    if (!impl->active_tree()->LayerById(picture_id)) {
      EXPECT_EQ(3, activates_);
      EXPECT_EQ(nullptr, pending_picture_impl->GetPendingOrActiveTwinLayer());
      return;
    }

    FakePictureLayerImpl* active_picture_impl =
        static_cast<FakePictureLayerImpl*>(
            impl->active_tree()->LayerById(picture_id));

    // After the first activation, when we commit again, we'll have a pending
    // and active layer. Then we recreate a picture layer in the 4th activate
    // and the next commit will have a pending and active twin again.
    EXPECT_TRUE(activates_ == 1 || activates_ == 4) << activates_;

    EXPECT_EQ(pending_picture_impl,
              active_picture_impl->GetPendingOrActiveTwinLayer());
    EXPECT_EQ(active_picture_impl,
              pending_picture_impl->GetPendingOrActiveTwinLayer());
  }

  void DidActivateTreeOnThread(LayerTreeHostImpl* impl) override {
    int picture_id = impl->active_tree()->source_frame_number() < 3
                         ? picture_id1_
                         : picture_id2_;
    if (!impl->active_tree()->LayerById(picture_id)) {
      EXPECT_EQ(2, activates_);
    } else {
      FakePictureLayerImpl* active_picture_impl =
          static_cast<FakePictureLayerImpl*>(
              impl->active_tree()->LayerById(picture_id));
      EXPECT_EQ(nullptr, active_picture_impl->GetPendingOrActiveTwinLayer());
    }

    ++activates_;
    if (activates_ == 5)
      EndTest();
  }

  int activates_ = 0;

  int picture_id1_;
  int picture_id2_;
};

// There is no pending layers in single thread mode.
MULTI_THREAD_TEST_F(LayerTreeHostPictureTestTwinLayer);

// TODO(crbug.com/): Flaky on MSAN, ASAN, TSAN, Linux CFI, and ARM64 Debug
// builds.
#if !defined(ADDRESS_SANITIZER) && !defined(THREAD_SANITIZER) && \
    !defined(MEMORY_SANITIZER) &&                                \
    !(BUILDFLAG(CFI_ICALL_CHECK) && BUILDFLAG(IS_LINUX)) &&      \
    !(defined(ARCH_CPU_ARM64) && !defined(NDEBUG))

class LayerTreeHostPictureTestResizeViewportWithGpuRaster
    : public LayerTreeHostPictureTest {
  void SetUpUnboundContextProviders(
      viz::TestContextProvider* context_provider,
      viz::TestContextProvider* worker_provider) override {
    context_provider->GetWritableGpuFeatureInfo()
        .status_values[gpu::GPU_FEATURE_TYPE_GPU_TILE_RASTERIZATION] =
        gpu::kGpuFeatureStatusEnabled;
    worker_provider->GetWritableGpuFeatureInfo()
        .status_values[gpu::GPU_FEATURE_TYPE_GPU_TILE_RASTERIZATION] =
        gpu::kGpuFeatureStatusEnabled;
  }

  void SetupTree() override {
    scoped_refptr<Layer> root = Layer::Create();
    root->SetBounds(gfx::Size(768, 960));
    client_.set_bounds(root->bounds());
    client_.set_fill_with_nonsolid_color(true);
    picture_ = FakePictureLayer::Create(&client_);
    picture_->SetBounds(gfx::Size(768, 960));
    root->AddChild(picture_);

    layer_tree_host()->SetRootLayer(root);
    LayerTreeHostPictureTest::SetupTree();
  }

  void BeginTest() override { PostSetNeedsCommitToMainThread(); }

  void CommitCompleteOnThread(LayerTreeHostImpl* impl) override {
    LayerImpl* child = impl->sync_tree()->LayerById(picture_->id());
    FakePictureLayerImpl* picture_impl =
        static_cast<FakePictureLayerImpl*>(child);
    gfx::Size tile_size =
        picture_impl->HighResTiling()->TileAt(0, 0)->content_rect().size();

    switch (impl->sync_tree()->source_frame_number()) {
      case 0:
        tile_size_ = tile_size;
        // GPU Raster picks a tile size based on the viewport size.
        EXPECT_EQ(gfx::Size(768, 256), tile_size);
        break;
      case 1:
        // When the viewport changed size, the new frame's tiles should change
        // along with it.
        EXPECT_NE(gfx::Size(768, 256), tile_size);
    }
  }

  void DidCommitAndDrawFrame() override {
    switch (layer_tree_host()->SourceFrameNumber()) {
      case 1:
        // Change the picture layer's size along with the viewport, so it will
        // consider picking a new tile size.
        picture_->SetBounds(gfx::Size(768, 1056));
        GenerateNewLocalSurfaceId();
        layer_tree_host()->SetViewportRectAndScale(gfx::Rect(768, 1056), 1.f,
                                                   GetCurrentLocalSurfaceId());
        break;
      case 2:
        EndTest();
    }
  }

  gfx::Size tile_size_;
  FakeContentLayerClient client_;
  scoped_refptr<FakePictureLayer> picture_;
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeHostPictureTestResizeViewportWithGpuRaster);

#endif  // !defined(ADDRESS_SANITIZER) && !defined(THREAD_SANITIZER) &&
        // !defined(MEMORY_SANITIZER) &&
        // !(BUILDFLAG(CFI_ICALL_CHECK) && BUILDFLAG(IS_LINUX)) &&
        // !(defined(ARCH_CPU_ARM64) && !defined(NDEBUG))

class LayerTreeHostPictureTestChangeLiveTilesRectWithRecycleTree
    : public LayerTreeHostPictureTest {
  void SetupTree() override {
    frame_ = 0;
    did_post_commit_ = false;

    scoped_refptr<Layer> root = Layer::Create();
    root->SetBounds(gfx::Size(100, 100));
    // The layer is big enough that the live tiles rect won't cover the full
    // layer.
    client_.set_fill_with_nonsolid_color(true);
    picture_ = FakePictureLayer::Create(&client_);
    picture_->SetBounds(gfx::Size(100, 100000));
    root->AddChild(picture_);

    // picture_'s transform is going to be changing on the compositor thread, so
    // force it to have a transform node by making it scrollable.
    picture_->SetScrollable(root->bounds());

    layer_tree_host()->SetRootLayer(root);
    LayerTreeHostPictureTest::SetupTree();
    client_.set_bounds(picture_->bounds());
  }

  void BeginTest() override { PostSetNeedsCommitToMainThread(); }

  void DrawLayersOnThread(LayerTreeHostImpl* impl) override {
    LayerImpl* child = impl->active_tree()->LayerById(picture_->id());
    FakePictureLayerImpl* picture_impl =
        static_cast<FakePictureLayerImpl*>(child);
    switch (++frame_) {
      case 1: {
        PictureLayerTiling* tiling = picture_impl->HighResTiling();
        int num_tiles_y = tiling->TilingDataForTesting().num_tiles_y();

        // There should be tiles at the top of the picture layer but not at the
        // bottom.
        EXPECT_TRUE(tiling->TileAt(0, 0));
        EXPECT_FALSE(tiling->TileAt(0, num_tiles_y));

        // Make the bottom of the layer visible.
        gfx::Transform transform;
        transform.Translate(0.f, -100000.f + 100.f);
        impl->active_tree()->SetTransformMutated(picture_impl->element_id(),
                                                 transform);
        impl->SetNeedsRedraw(/*animation_only=*/false,
                             /*skip_if_inside_draw=*/false);
        break;
      }
      case 2: {
        PictureLayerTiling* tiling = picture_impl->HighResTiling();

        // There not be tiles at the top of the layer now.
        EXPECT_FALSE(tiling->TileAt(0, 0));

        // Make the top of the layer visible again.
        impl->active_tree()->SetTransformMutated(picture_impl->element_id(),
                                                 gfx::Transform());
        impl->SetNeedsRedraw(/*animation_only=*/false,
                             /*skip_if_inside_draw=*/false);
        break;
      }
      case 3: {
        PictureLayerTiling* tiling = picture_impl->HighResTiling();
        int num_tiles_y = tiling->TilingDataForTesting().num_tiles_y();

        // There should be tiles at the top of the picture layer again.
        EXPECT_TRUE(tiling->TileAt(0, 0));
        EXPECT_FALSE(tiling->TileAt(0, num_tiles_y));

        // Make a new main frame without changing the picture layer at all, so
        // it won't need to update or push properties.
        did_post_commit_ = true;
        PostSetNeedsCommitToMainThread();
        break;
      }
    }
  }

  void WillActivateTreeOnThread(LayerTreeHostImpl* impl) override {
    LayerImpl* child = impl->sync_tree()->LayerById(picture_->id());
    FakePictureLayerImpl* picture_impl =
        static_cast<FakePictureLayerImpl*>(child);
    PictureLayerTiling* tiling = picture_impl->HighResTiling();
    int num_tiles_y = tiling->TilingDataForTesting().num_tiles_y();

    if (!impl->active_tree()->root_layer()) {
      // If active tree doesn't have the layer, then pending tree should have
      // all needed tiles.
      EXPECT_TRUE(tiling->TileAt(0, 0));
    } else {
      // Since there was no invalidation, the pending tree shouldn't have any
      // tiles.
      EXPECT_FALSE(tiling->TileAt(0, 0));
    }
    EXPECT_FALSE(tiling->TileAt(0, num_tiles_y));

    if (did_post_commit_)
      EndTest();
  }

  int frame_;
  bool did_post_commit_;
  FakeContentLayerClient client_;
  scoped_refptr<FakePictureLayer> picture_;
};

// Multi-thread only since there is no recycle tree in single thread.
MULTI_THREAD_TEST_F(LayerTreeHostPictureTestChangeLiveTilesRectWithRecycleTree);

class LayerTreeHostPictureTestRSLLMembership : public LayerTreeHostPictureTest {
  void SetupTree() override {
    scoped_refptr<Layer> root = Layer::Create();
    root->SetBounds(gfx::Size(100, 100));
    client_.set_bounds(root->bounds());

    child_ = Layer::Create();
    root->AddChild(child_);

    // Don't be solid color so the layer has tilings/tiles.
    client_.set_fill_with_nonsolid_color(true);
    picture_ = FakePictureLayer::Create(&client_);
    picture_->SetBounds(gfx::Size(100, 100));
    child_->AddChild(picture_);

    layer_tree_host()->SetRootLayer(root);
    LayerTreeHostPictureTest::SetupTree();
  }

  void BeginTest() override { PostSetNeedsCommitToMainThread(); }

  void CommitCompleteOnThread(LayerTreeHostImpl* impl) override {
    LayerImpl* gchild = impl->sync_tree()->LayerById(picture_->id());
    FakePictureLayerImpl* picture = static_cast<FakePictureLayerImpl*>(gchild);

    switch (impl->active_tree()->source_frame_number()) {
      case 0:
        // On 1st commit the layer has tilings.
        EXPECT_GT(picture->tilings()->num_tilings(), 0u);
        break;
      case 1:
        // On 2nd commit, the layer is transparent, but its tilings are left
        // there.
        EXPECT_GT(picture->tilings()->num_tilings(), 0u);
        break;
      case 2:
        // On 3rd commit, the layer is visible again, so has tilings.
        EXPECT_GT(picture->tilings()->num_tilings(), 0u);
    }
  }

  void DidActivateTreeOnThread(LayerTreeHostImpl* impl) override {
    LayerImpl* gchild = impl->active_tree()->LayerById(picture_->id());
    FakePictureLayerImpl* picture = static_cast<FakePictureLayerImpl*>(gchild);

    switch (impl->active_tree()->source_frame_number()) {
      case 0:
        // On 1st commit the layer has tilings.
        EXPECT_GT(picture->tilings()->num_tilings(), 0u);
        break;
      case 1:
        // On 2nd commit, the layer is transparent, but its tilings are left
        // there.
        EXPECT_GT(picture->tilings()->num_tilings(), 0u);
        break;
      case 2:
        // On 3rd commit, the layer is visible again, so has tilings.
        EXPECT_GT(picture->tilings()->num_tilings(), 0u);
        EndTest();
    }
  }

  void DidCommit() override {
    switch (layer_tree_host()->SourceFrameNumber()) {
      case 1:
        // For the 2nd commit, change opacity to 0 so that the layer will not be
        // part of the visible frame.
        child_->SetOpacity(0.f);
        break;
      case 2:
        // For the 3rd commit, change opacity to 1 so that the layer will again
        // be part of the visible frame.
        child_->SetOpacity(1.f);
    }
  }

  FakeContentLayerClient client_;
  scoped_refptr<Layer> child_;
  scoped_refptr<FakePictureLayer> picture_;
};

SINGLE_THREAD_TEST_F(LayerTreeHostPictureTestRSLLMembership);

class LayerTreeHostPictureTestRSLLMembershipWithScale
    : public LayerTreeHostPictureTest {
  void SetupTree() override {
    scoped_refptr<Layer> root_clip = Layer::Create();
    root_clip->SetBounds(gfx::Size(100, 100));
    scoped_refptr<Layer> page_scale_layer = Layer::Create();
    page_scale_layer->SetBounds(gfx::Size(100, 100));

    pinch_ = Layer::Create();
    pinch_->SetBounds(gfx::Size(500, 500));
    pinch_->SetScrollable(root_clip->bounds());
    page_scale_layer->AddChild(pinch_);
    root_clip->AddChild(page_scale_layer);

    // Don't be solid color so the layer has tilings/tiles.
    client_.set_fill_with_nonsolid_color(true);
    picture_ = FakePictureLayer::Create(&client_);
    picture_->SetBounds(gfx::Size(100, 100));
    pinch_->AddChild(picture_);

    layer_tree_host()->SetPageScaleFactorAndLimits(1.f, 1.f, 4.f);
    layer_tree_host()->SetRootLayer(root_clip);
    LayerTreeHostPictureTest::SetupTree();
    client_.set_bounds(picture_->bounds());
  }

  void BeginTest() override {
    frame_ = 0;
    draws_in_frame_ = 0;
    last_frame_drawn_ = -1;
    ready_to_draw_ = false;
    PostSetNeedsCommitToMainThread();
  }

  void WillActivateTreeOnThread(LayerTreeHostImpl* impl) override {
    LayerImpl* gchild = impl->sync_tree()->LayerById(picture_->id());
    FakePictureLayerImpl* picture = static_cast<FakePictureLayerImpl*>(gchild);
    ready_to_draw_ = false;

    switch (frame_) {
      case 0:
        // On 1st commit the pending layer has tilings.
        ASSERT_EQ(1u, picture->tilings()->num_tilings());
        EXPECT_EQ(gfx::AxisTransform2d(),
                  picture->tilings()->tiling_at(0)->raster_transform());
        break;
      case 1:
        // On 2nd commit, the pending layer is transparent, so has a stale
        // value.
        ASSERT_EQ(1u, picture->tilings()->num_tilings());
        EXPECT_EQ(gfx::AxisTransform2d(),
                  picture->tilings()->tiling_at(0)->raster_transform());
        break;
      case 2:
        // On 3rd commit, the pending layer is visible again, so has tilings and
        // is updated for the pinch.
        ASSERT_EQ(1u, picture->tilings()->num_tilings());
        EXPECT_EQ(gfx::AxisTransform2d(2.f, gfx::Vector2dF()),
                  picture->tilings()->tiling_at(0)->raster_transform());
    }
  }

  void DrawLayersOnThread(LayerTreeHostImpl* impl) override {
    LayerImpl* gchild = impl->active_tree()->LayerById(picture_->id());
    FakePictureLayerImpl* picture = static_cast<FakePictureLayerImpl*>(gchild);

    if (frame_ != last_frame_drawn_)
      draws_in_frame_ = 0;
    ++draws_in_frame_;
    last_frame_drawn_ = frame_;

    switch (frame_) {
      case 0:
        if (draws_in_frame_ == 1) {
          // On 1st commit the layer has tilings.
          EXPECT_GT(picture->tilings()->num_tilings(), 0u);
          EXPECT_EQ(gfx::AxisTransform2d(),
                    picture->HighResTiling()->raster_transform());

          // Pinch zoom in to change the scale on the active tree.
          impl->GetInputHandler().PinchGestureBegin(
              gfx::Point(1, 1), ui::ScrollInputType::kWheel);
          impl->GetInputHandler().PinchGestureUpdate(2.f, gfx::Point(1, 1));
          impl->GetInputHandler().PinchGestureEnd(gfx::Point(1, 1));
        } else if (picture->tilings()->num_tilings() == 1) {
          // If the pinch gesture caused a commit we could get here with a
          // pending tree.
          EXPECT_FALSE(impl->pending_tree());
          EXPECT_EQ(gfx::AxisTransform2d(2.f, gfx::Vector2dF()),
                    picture->HighResTiling()->raster_transform());

          // Need to wait for ready to draw here so that the pinch is
          // entirely complete, otherwise another draw might come in before
          // the commit occurs.
          if (ready_to_draw_) {
            ++frame_;
            MainThreadTaskRunner()->PostTask(
                FROM_HERE,
                base::BindOnce(
                    &LayerTreeHostPictureTestRSLLMembershipWithScale::NextStep,
                    base::Unretained(this)));
          }
        }
        break;
      case 1:
        EXPECT_EQ(1, draws_in_frame_);
        // On 2nd commit, this active layer is transparent, so does not update
        // tilings.  It has the high res scale=2 from the previous frame, and
        // also a scale=1 copied from the pending layer's stale value during
        // activation.
        EXPECT_EQ(2u, picture->picture_layer_tiling_set()->num_tilings());

        ++frame_;
        MainThreadTaskRunner()->PostTask(
            FROM_HERE,
            base::BindOnce(
                &LayerTreeHostPictureTestRSLLMembershipWithScale::NextStep,
                base::Unretained(this)));
        break;
      case 2:
        EXPECT_EQ(1, draws_in_frame_);
        // On 3rd commit, the layer is visible again, so has tilings.
        EXPECT_GT(picture->tilings()->num_tilings(), 0u);
        EndTest();
    }
  }

  void NextStep() {
    switch (frame_) {
      case 1:
        // For the 2nd commit, change opacity to 0 so that the layer will not be
        // part of the visible frame.
        pinch_->SetOpacity(0.f);
        break;
      case 2:
        // For the 3rd commit, change opacity to 1 so that the layer will again
        // be part of the visible frame.
        pinch_->SetOpacity(1.f);
        break;
    }
  }

  void NotifyReadyToDrawOnThread(LayerTreeHostImpl* impl) override {
    ready_to_draw_ = true;
    if (frame_ == 0) {
      // The ready to draw can race with a draw in which everything is
      // actually ready.  Therefore, just issue one more extra draw
      // here to force notify->draw ordering.
      impl->SetNeedsRedraw(/*animation_only=*/false,
                           /*skip_if_inside_draw=*/false);
    }
  }

  FakeContentLayerClient client_;
  scoped_refptr<Layer> pinch_;
  scoped_refptr<FakePictureLayer> picture_;
  int frame_;
  int draws_in_frame_;
  int last_frame_drawn_;
  bool ready_to_draw_;
};

// Multi-thread only because in single thread you can't pinch zoom on the
// compositor thread.
// TODO(crbug.com/41478255): Flaky on several platforms.
// MULTI_THREAD_TEST_F(LayerTreeHostPictureTestRSLLMembershipWithScale);

class LayerTreeHostPictureTestForceRecalculateScales
    : public LayerTreeHostPictureTest {
 public:
  LayerTreeHostPictureTestForceRecalculateScales() { SetUseLayerLists(); }
 private:
  void SetupTree() override {
    gfx::Size size(100, 100);
    SetInitialRootBounds(size);
    LayerTreeTest::SetupTree();
    Layer* root = layer_tree_host()->root_layer();

    top_layer_ = Layer::Create();
    top_layer_->SetBounds(size);
    CopyProperties(root, top_layer_.get());
    // This transform node will be scaled up later.
    CreateTransformNode(top_layer_.get());
    root->AddChild(top_layer_);

    will_change_layer_ = FakePictureLayer::Create(&client_);
    will_change_layer_->SetBounds(size);
    CopyProperties(top_layer_.get(), will_change_layer_.get());
    CreateTransformNode(will_change_layer_.get()).will_change_transform = true;
    root->AddChild(will_change_layer_);

    normal_layer_ = FakePictureLayer::Create(&client_);
    normal_layer_->SetBounds(size);
    CopyProperties(top_layer_.get(), normal_layer_.get());
    root->AddChild(normal_layer_);

    client_.set_fill_with_nonsolid_color(true);
    client_.set_bounds(size);
  }

  void BeginTest() override { PostSetNeedsCommitToMainThread(); }

  void DrawLayersOnThread(LayerTreeHostImpl* impl) override {
    FakePictureLayerImpl* will_change_layer =
        static_cast<FakePictureLayerImpl*>(
            impl->active_tree()->LayerById(will_change_layer_->id()));
    FakePictureLayerImpl* normal_layer = static_cast<FakePictureLayerImpl*>(
        impl->active_tree()->LayerById(normal_layer_->id()));

    switch (impl->active_tree()->source_frame_number()) {
      case 0:
        // On first commit, both layers are at the default scale.
        ASSERT_EQ(1u, will_change_layer->tilings()->num_tilings());
        EXPECT_EQ(
            gfx::AxisTransform2d(),
            will_change_layer->tilings()->tiling_at(0)->raster_transform());
        ASSERT_EQ(1u, normal_layer->tilings()->num_tilings());
        EXPECT_EQ(gfx::AxisTransform2d(),
                  normal_layer->tilings()->tiling_at(0)->raster_transform());

        MainThreadTaskRunner()->PostTask(
            FROM_HERE,
            base::BindOnce(
                &LayerTreeHostPictureTestForceRecalculateScales::ScaleUp,
                base::Unretained(this)));
        break;
      case 1:
        // On 2nd commit after scaling up to 2, the normal layer will adjust its
        // scale and the will change layer should not (as it is will change).
        ASSERT_EQ(1u, will_change_layer->tilings()->num_tilings());
        EXPECT_EQ(
            gfx::AxisTransform2d(),
            will_change_layer->tilings()->tiling_at(0)->raster_transform());
        // Now we delay tiling removal (or proposal to remove) to DidDraw(),
        // so in DrawLayers, in both modes, tiling removal hasn't happened.
        ASSERT_EQ(2u, normal_layer->tilings()->num_tilings());
        EXPECT_EQ(gfx::AxisTransform2d(2.f, gfx::Vector2dF()),
                  normal_layer->tilings()->tiling_at(0)->raster_transform());
        EXPECT_EQ(gfx::AxisTransform2d(),
                  normal_layer->tilings()->tiling_at(1)->raster_transform());

        MainThreadTaskRunner()->PostTask(
            FROM_HERE,
            base::BindOnce(&LayerTreeHostPictureTestForceRecalculateScales::
                               ScaleUpAndRecalculateScales,
                           base::Unretained(this)));
        break;
      case 2:
        // On 3rd commit, both layers should adjust scales due to forced
        // recalculating.
        ASSERT_EQ(2u, will_change_layer->tilings()->num_tilings());
        EXPECT_EQ(
            gfx::AxisTransform2d(4.f, gfx::Vector2dF()),
            will_change_layer->tilings()->tiling_at(0)->raster_transform());
        EXPECT_EQ(
            gfx::AxisTransform2d(),
            will_change_layer->tilings()->tiling_at(1)->raster_transform());

        if (TreesInViz()) {
          // In TreesInViz mode, we query viz before deleting a tiling, so its
          // removal is delayed comparing with non TreesInViz mode.
          ASSERT_EQ(3u, normal_layer->tilings()->num_tilings());
          EXPECT_EQ(gfx::AxisTransform2d(4.f, gfx::Vector2dF()),
                    normal_layer->tilings()->tiling_at(0)->raster_transform());
          EXPECT_EQ(gfx::AxisTransform2d(2.f, gfx::Vector2dF()),
                    normal_layer->tilings()->tiling_at(1)->raster_transform());
          EXPECT_EQ(gfx::AxisTransform2d(),
                    normal_layer->tilings()->tiling_at(2)->raster_transform());
        } else {
          ASSERT_EQ(2u, normal_layer->tilings()->num_tilings());
          EXPECT_EQ(gfx::AxisTransform2d(4.f, gfx::Vector2dF()),
                    normal_layer->tilings()->tiling_at(0)->raster_transform());
          EXPECT_EQ(gfx::AxisTransform2d(2.f, gfx::Vector2dF()),
                    normal_layer->tilings()->tiling_at(1)->raster_transform());
        }

        EndTest();
        break;
    }
  }

  void ScaleUp() {
    gfx::Transform transform;
    transform.Scale(2, 2);
    SetTransform(top_layer_.get(), transform);
    layer_tree_host()->SetNeedsCommit();
  }

  void ScaleUpAndRecalculateScales() {
    gfx::Transform transform;
    transform.Scale(4, 4);
    SetTransform(top_layer_.get(), transform);
    layer_tree_host()->SetNeedsRecalculateRasterScales();
  }

  scoped_refptr<Layer> top_layer_;
  scoped_refptr<FakePictureLayer> will_change_layer_;
  scoped_refptr<FakePictureLayer> normal_layer_;
};

// TODO(crbug.com/473556590): Test is flaky; disabling for now.
// SINGLE_THREAD_TEST_F(LayerTreeHostPictureTestForceRecalculateScales);

}  // namespace
}  // namespace cc
