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

#include "cc/trees/client_layer_tree_host_impl.h"

#include <stddef.h>

#include <algorithm>
#include <array>
#include <cmath>
#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/memory_pressure_listener_registry.h"
#include "base/memory/ptr_util.h"
#include "base/numerics/angle_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/run_until.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "cc/animation/animation.h"
#include "cc/animation/animation_host.h"
#include "cc/animation/animation_id_provider.h"
#include "cc/base/features.h"
#include "cc/input/browser_controls_offset_manager.h"
#include "cc/input/input_handler.h"
#include "cc/input/main_thread_scrolling_reason.h"
#include "cc/input/page_scale_animation.h"
#include "cc/input/scroll_elasticity_helper.h"
#include "cc/input/scroll_utils.h"
#include "cc/input/scrollbar_controller.h"
#include "cc/layers/append_quads_context.h"
#include "cc/layers/append_quads_data.h"
#include "cc/layers/layer_impl.h"
#include "cc/layers/nine_patch_thumb_scrollbar_layer_impl.h"
#include "cc/layers/painted_scrollbar_layer_impl.h"
#include "cc/layers/render_surface_impl.h"
#include "cc/layers/solid_color_layer_impl.h"
#include "cc/layers/solid_color_scrollbar_layer_impl.h"
#include "cc/layers/surface_layer_impl.h"
#include "cc/layers/viewport.h"
#include "cc/metrics/compositor_frame_reporting_controller.h"
#include "cc/metrics/frame_info.h"
#include "cc/resources/ui_resource_bitmap.h"
#include "cc/resources/ui_resource_manager.h"
#include "cc/test/animation_test_common.h"
#include "cc/test/event_metrics_test_creator.h"
#include "cc/test/fake_frame_info.h"
#include "cc/test/fake_impl_task_runner_provider.h"
#include "cc/test/fake_layer_tree_frame_sink.h"
#include "cc/test/fake_layer_tree_host_impl.h"
#include "cc/test/fake_picture_layer_impl.h"
#include "cc/test/fake_raster_source.h"
#include "cc/test/fake_recording_source.h"
#include "cc/test/fake_video_frame_provider.h"
#include "cc/test/layer_test_common.h"
#include "cc/test/layer_tree_test.h"
#include "cc/test/mock_latency_info_swap_promise_monitor.h"
#include "cc/test/skia_common.h"
#include "cc/test/test_layer_tree_frame_sink.h"
#include "cc/test/test_paint_worklet_layer_painter.h"
#include "cc/test/test_task_graph_runner.h"
#include "cc/trees/clip_node.h"
#include "cc/trees/compositor_commit_data.h"
#include "cc/trees/draw_property_utils.h"
#include "cc/trees/effect_node.h"
#include "cc/trees/frame_data.h"
#include "cc/trees/latency_info_swap_promise.h"
#include "cc/trees/layer_tree_host_impl_delegate.h"
#include "cc/trees/layer_tree_host_impl_test_base.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/mutator_host.h"
#include "cc/trees/render_frame_metadata.h"
#include "cc/trees/render_frame_metadata_observer.h"
#include "cc/trees/scroll_node.h"
#include "cc/trees/transform_node.h"
#include "cc/view_transition/view_transition_request.h"
#include "components/viz/client/client_resource_provider.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "components/viz/common/frame_timing_details.h"
#include "components/viz/common/quads/compositor_frame_metadata.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/quads/tile_draw_quad.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/common/surfaces/region_capture_bounds.h"
#include "components/viz/service/display/skia_output_surface.h"
#include "components/viz/service/layers/viz_layer_tree_host_impl.h"
#include "components/viz/test/begin_frame_args_test.h"
#include "components/viz/test/fake_output_surface.h"
#include "components/viz/test/fake_skia_output_surface.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "media/base/media.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkMallocPixelRef.h"
#include "ui/events/types/scroll_input_type.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/test/geometry_util.h"
#include "ui/gfx/geometry/transform_operations.h"
#include "ui/gfx/geometry/vector2d_conversions.h"
#include "ui/latency/latency_info.h"

#define EXPECT_SCOPED(statements) \
  {                               \
    SCOPED_TRACE("");             \
    statements;                   \
  }

using media::VideoFrame;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::ElementsAre;
using ::testing::Mock;
using ::testing::Pointee;
using ::testing::Pointer;
using ::testing::Pointwise;
using ::testing::Property;
using ::testing::Range;
using ::testing::Return;
using ::testing::StrictMock;

using ScrollThread = cc::InputHandler::ScrollThread;

namespace cc {
namespace {

MATCHER(UniquePtrMatches, negation ? "do not match" : "match") {
  return std::get<0>(arg).get() == std::get<1>(arg);
}

ClientLayerTreeHostImpl* client_host_impl(LayerTreeHostImpl* host) {
  return static_cast<ClientLayerTreeHostImpl*>(host);
}

class TestInputHandlerClient : public InputHandlerClient {
 public:
  TestInputHandlerClient() = default;
  ~TestInputHandlerClient() override = default;

  // InputHandlerClient implementation.
  void WillShutdown() override {}
  void Animate(base::TimeTicks time) override {}
  void ReconcileElasticOverscrollAndRootScroll() override {}
  void SetPrefersReducedMotion(bool prefers_reduced_motion) override {}
  void UpdateRootLayerStateForSynchronousInputHandler(
      const gfx::PointF& total_scroll_offset,
      const gfx::PointF& max_scroll_offset,
      const gfx::SizeF& scrollable_size,
      float page_scale_factor,
      float min_page_scale_factor,
      float max_page_scale_factor) override {
    DCHECK(total_scroll_offset.x() <= max_scroll_offset.x());
    DCHECK(total_scroll_offset.y() <= max_scroll_offset.y());
    last_set_scroll_offset_ = total_scroll_offset;
    max_scroll_offset_ = max_scroll_offset;
    scrollable_size_ = scrollable_size;
    page_scale_factor_ = page_scale_factor;
    min_page_scale_factor_ = min_page_scale_factor;
    max_page_scale_factor_ = max_page_scale_factor;
  }
  void DeliverInputForBeginFrame(const viz::BeginFrameArgs& args) override {}
  void DeliverInputForHighLatencyMode() override {}
  void DeliverInputForDeadline() override {}
  void DidFinishImplFrame() override {}
  bool HasQueuedInput() const override { return false; }
  void SetScrollEventDispatchMode(
      InputHandlerClient::ScrollEventDispatchMode mode,
      double scroll_deadline_ratio) override {}

  gfx::PointF last_set_scroll_offset() { return last_set_scroll_offset_; }
  gfx::PointF max_scroll_offset() const { return max_scroll_offset_; }
  gfx::SizeF scrollable_size() const { return scrollable_size_; }
  float page_scale_factor() const { return page_scale_factor_; }
  float min_page_scale_factor() const { return min_page_scale_factor_; }
  float max_page_scale_factor() const { return max_page_scale_factor_; }

 private:
  gfx::PointF last_set_scroll_offset_;
  gfx::PointF max_scroll_offset_;
  gfx::SizeF scrollable_size_;
  float page_scale_factor_ = 0;
  float min_page_scale_factor_ = -1;
  float max_page_scale_factor_ = -1;
};

// Test fixture that runs in all tree modes except for TreesInViz Service
// mode.
class ClientModeLayerTreeHostImplTest : public LayerTreeHostImplTest {};

class LayerTreeHostImplTestMultiScrollable : public LayerTreeHostImplTest {
 public:
  void SetUpLayers(LayerTreeSettings settings) {
    is_aura_scrollbar_ =
        settings.scrollbar_animator == LayerTreeSettings::AURA_OVERLAY;
    gfx::Size viewport_size(300, 200);
    gfx::Size content_size(1000, 1000);
    gfx::Size child_layer_size(250, 150);
    gfx::Size scrollbar_size_1(gfx::Size(15, viewport_size.height()));
    gfx::Size scrollbar_size_2(gfx::Size(15, child_layer_size.height()));

    CreateHostImpl(settings, CreateLayerTreeFrameSink());
    host_impl_->active_tree()->SetDeviceScaleFactor(1);
    SetupViewportLayers(host_impl_->active_tree(), viewport_size, content_size,
                        content_size);
    LayerImpl* root_scroll = OuterViewportScrollLayer();

    // scrollbar_1 on root scroll.
    scrollbar_1_ = AddLayer<SolidColorScrollbarLayerImpl>(
        host_impl_->active_tree(), ScrollbarOrientation::kVertical, 15, 0,
        true);
    SetupScrollbarLayer(root_scroll, scrollbar_1_);
    scrollbar_1_->SetBounds(scrollbar_size_1);
    TouchActionRegion touch_action_region;
    touch_action_region.Union(TouchAction::kNone, gfx::Rect(scrollbar_size_1));
    scrollbar_1_->SetTouchActionRegion(touch_action_region);

    // scrollbar_2 on child.
    auto* child =
        AddScrollableLayer(root_scroll, gfx::Size(100, 100), child_layer_size);
    GetTransformNode(child)->post_translation = gfx::Vector2dF(50, 50);

    scrollbar_2_ = AddLayer<SolidColorScrollbarLayerImpl>(
        host_impl_->active_tree(), ScrollbarOrientation::kVertical, 15, 0,
        true);
    SetupScrollbarLayer(child, scrollbar_2_);
    scrollbar_2_->SetBounds(scrollbar_size_2);

    host_impl_->active_tree()->UpdateAllScrollbarGeometriesForTesting();
    UpdateDrawProperties(host_impl_->active_tree());
    host_impl_->active_tree()->DidBecomeActive();

    ResetScrollbars();
  }

  void ResetScrollbars() {
    GetEffectNode(scrollbar_1_.get())->opacity = 0;
    GetEffectNode(scrollbar_2_.get())->opacity = 0;
    UpdateDrawProperties(host_impl_->active_tree());

    if (is_aura_scrollbar_) {
      animation_task_.Reset();
    }
  }

  bool is_aura_scrollbar_;
  raw_ptr<SolidColorScrollbarLayerImpl> scrollbar_1_;
  raw_ptr<SolidColorScrollbarLayerImpl> scrollbar_2_;
};

class LayerTreeHostImplBrowserControlsTest : public LayerTreeHostImplTest {
 public:
  LayerTreeHostImplBrowserControlsTest()
      // Make the clip size the same as the layer (content) size so the layer is
      // non-scrollable.
      : layer_size_(10, 10), clip_size_(layer_size_) {
    viewport_size_ = gfx::Size(clip_size_.width(),
                               clip_size_.height() + top_controls_height_);
  }

  bool CreateHostImpl(
      const LayerTreeSettings& settings,
      std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink) override {
    bool init = LayerTreeHostImplTest::CreateHostImpl(
        settings, std::move(layer_tree_frame_sink));
    if (init) {
      host_impl_->active_tree()->SetBrowserControlsParams(
          {top_controls_height_, 0, 0, 0, false, false});
      host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(1.f, 1.f);
      host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
    }
    return init;
  }

 protected:
  void SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      const gfx::Size& inner_viewport_size,
      const gfx::Size& outer_viewport_size,
      const gfx::Size& scroll_layer_size) {
    SetupBrowserControlsAndScrollLayerWithVirtualViewport(
        host_impl_->active_tree(), inner_viewport_size, outer_viewport_size,
        scroll_layer_size);
  }

  void SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      LayerTreeImpl* tree_impl,
      const gfx::Size& inner_viewport_size,
      const gfx::Size& outer_viewport_size,
      const gfx::Size& scroll_layer_size) {
    tree_impl->SetBrowserControlsParams(
        {top_controls_height_, 0, 0, 0, false, true});
    tree_impl->SetCurrentBrowserControlsShownRatio(1.f, 1.f);
    tree_impl->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
    host_impl_->DidChangeBrowserControlsPosition();

    SetupViewportLayers(tree_impl, inner_viewport_size, outer_viewport_size,
                        scroll_layer_size);
  }

  void ExpectViewportGeometries(float expected_browser_controls_shown_ratio) {
    auto* tree = host_impl_->active_tree();
    auto* property_trees = tree->property_trees();
    EXPECT_EQ(expected_browser_controls_shown_ratio,
              tree->CurrentTopControlsShownRatio());
    EXPECT_EQ(
        tree->top_controls_height() * expected_browser_controls_shown_ratio,
        host_impl_->browser_controls_manager()->ContentTopOffset());
    int delta = (tree->top_controls_height() + tree->bottom_controls_height()) *
                (1 - expected_browser_controls_shown_ratio);
    int scaled_delta = delta / tree->min_page_scale_factor();
    gfx::Size inner_scroll_bounds = tree->InnerViewportScrollNode()->bounds;
    inner_scroll_bounds.Enlarge(0, scaled_delta);
    EXPECT_EQ(inner_scroll_bounds, InnerViewportScrollLayer()->bounds());
    EXPECT_EQ(gfx::RectF(gfx::SizeF(inner_scroll_bounds)),
              tree->OuterViewportClipNode()->clip);
    EXPECT_EQ(gfx::Vector2dF(0, delta),
              property_trees->inner_viewport_container_bounds_delta());
    EXPECT_EQ(gfx::Vector2dF(0, scaled_delta),
              property_trees->outer_viewport_container_bounds_delta());
  }

  gfx::Size layer_size_;
  gfx::Size clip_size_;
  gfx::Size viewport_size_;
  float top_controls_height_ = 50;
};  // class LayerTreeHostImplBrowserControlsTest

class LayerTreeHostImplWithImplicitLimitsTest : public LayerTreeHostImplTest {
 public:
  LayerTreeSettings DefaultSettings() override {
    LayerTreeSettings settings = LayerTreeHostImplTest::DefaultSettings();
    settings.max_memory_for_prepaint_percentage = 50;
    return settings;
  }
};

// Test implementation of RenderFrameMetadataObserver which can optionally
// request the frame-token to be sent to the embedder during frame submission.
class TestRenderFrameMetadataObserver : public RenderFrameMetadataObserver {
 public:
  explicit TestRenderFrameMetadataObserver(bool increment_counter)
      : increment_counter_(increment_counter) {}
  TestRenderFrameMetadataObserver(const TestRenderFrameMetadataObserver&) =
      delete;
  ~TestRenderFrameMetadataObserver() override = default;

  TestRenderFrameMetadataObserver& operator=(
      const TestRenderFrameMetadataObserver&) = delete;

  void BindToCurrentSequence() override {}
  void OnRenderFrameSubmission(
      const RenderFrameMetadata& render_frame_metadata,
      viz::CompositorFrameMetadata* compositor_frame_metadata,
      bool force_send) override {
    if (increment_counter_) {
      compositor_frame_metadata->send_frame_token_to_embedder = true;
    }
    last_metadata_ = render_frame_metadata;
  }
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
  void DidEndScroll() override {}
#endif

  const std::optional<RenderFrameMetadata>& last_metadata() const {
    return last_metadata_;
  }

 private:
  bool increment_counter_;
  std::optional<RenderFrameMetadata> last_metadata_;
};


// Checks that we use the memory limits provided.
// TODO(crbug.com/458776836): Review memory limit related cc_unittests for
// TreesInViz Service mode
TEST_P(ClientModeLayerTreeHostImplTest, MemoryLimits) {
  host_impl_->ReleaseLayerTreeFrameSink();
  host_impl_ = nullptr;

  const size_t kGpuByteLimit = 1234321;
  const size_t kGpuResourceLimit = 2345432;
  const gpu::MemoryAllocation::PriorityCutoff kGpuCutoff =
      gpu::MemoryAllocation::CUTOFF_ALLOW_EVERYTHING;

  const TileMemoryLimitPolicy kGpuTileCutoff =
      ManagedMemoryPolicy::PriorityCutoffToTileMemoryLimitPolicy(kGpuCutoff);
  const TileMemoryLimitPolicy kNothingTileCutoff =
      ManagedMemoryPolicy::PriorityCutoffToTileMemoryLimitPolicy(
          gpu::MemoryAllocation::CUTOFF_ALLOW_NOTHING);
  EXPECT_NE(kGpuTileCutoff, kNothingTileCutoff);

  LayerTreeSettings settings = DefaultSettings();
  settings.memory_policy =
      ManagedMemoryPolicy(kGpuByteLimit, kGpuCutoff, kGpuResourceLimit);
  host_impl_ = CreateLayerTreeHostImplForTesting(
      settings, this, &task_runner_provider_, &stats_instrumentation_,
      &task_graph_runner_,
      AnimationHost::CreateForTesting(ThreadInstance::kImpl), nullptr, 0,
      nullptr, nullptr);
  InputHandler::Create(static_cast<CompositorDelegateForInput&>(*host_impl_));

  // Gpu compositing.
  layer_tree_frame_sink_ = FakeLayerTreeFrameSink::Create3d();
  host_impl_->SetVisible(true);
  host_impl_->InitializeFrameSink(layer_tree_frame_sink_.get());
  {
    const auto& state = host_impl_->global_tile_state();
    EXPECT_EQ(kGpuByteLimit, state.hard_memory_limit_in_bytes);
    EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit);
    EXPECT_EQ(kGpuTileCutoff, state.memory_limit_policy);
  }

  // Not visible, drops to 0.
  host_impl_->SetVisible(false);
  {
    const auto& state = host_impl_->global_tile_state();
    EXPECT_EQ(0u, state.hard_memory_limit_in_bytes);
    EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit);
    EXPECT_EQ(kNothingTileCutoff, state.memory_limit_policy);
  }

  // Visible, is the gpu limit again.
  host_impl_->SetVisible(true);
  {
    const auto& state = host_impl_->global_tile_state();
    EXPECT_EQ(kGpuByteLimit, state.hard_memory_limit_in_bytes);
    EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit);
  }

  // Software compositing.
  host_impl_->ReleaseLayerTreeFrameSink();
  layer_tree_frame_sink_ = FakeLayerTreeFrameSink::CreateSoftware();
  host_impl_->InitializeFrameSink(layer_tree_frame_sink_.get());
  {
    const auto& state = host_impl_->global_tile_state();
    EXPECT_EQ(kGpuByteLimit, state.hard_memory_limit_in_bytes);
    EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit);
    EXPECT_EQ(kGpuTileCutoff, state.memory_limit_policy);
  }

  // Not visible, drops to 0.
  host_impl_->SetVisible(false);
  {
    const auto& state = host_impl_->global_tile_state();
    EXPECT_EQ(0u, state.hard_memory_limit_in_bytes);
    EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit);
    EXPECT_EQ(kNothingTileCutoff, state.memory_limit_policy);
  }

  // Visible, is the software limit again.
  host_impl_->SetVisible(true);
  {
    const auto& state = host_impl_->global_tile_state();
    EXPECT_EQ(kGpuByteLimit, state.hard_memory_limit_in_bytes);
    EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit);
    EXPECT_EQ(kGpuTileCutoff, state.memory_limit_policy);
  }
}

// UIResource tests do not apply to TIV Service.
TEST_P(ClientModeLayerTreeHostImplTest, UIResourceManagement) {
  auto test_context_provider = viz::TestContextProvider::CreateRaster();
  gpu::TestSharedImageInterface* sii =
      test_context_provider->SharedImageInterface();
  CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::Create3d(
                                        std::move(test_context_provider)));

  EXPECT_EQ(0u, sii->shared_image_count());

  UIResourceId ui_resource_id = 1;
  bool is_opaque = false;
  UIResourceBitmap bitmap(gfx::Size(1, 1), is_opaque);
  host_impl_->CreateUIResource(ui_resource_id, bitmap);
  EXPECT_EQ(1u, sii->shared_image_count());
  viz::ResourceId id1 = host_impl_->ResourceIdForUIResource(ui_resource_id);
  EXPECT_NE(viz::kInvalidResourceId, id1);

  // Multiple requests with the same id is allowed.  The previous texture is
  // deleted.
  host_impl_->CreateUIResource(ui_resource_id, bitmap);
  EXPECT_EQ(1u, sii->shared_image_count());
  viz::ResourceId id2 = host_impl_->ResourceIdForUIResource(ui_resource_id);
  EXPECT_NE(viz::kInvalidResourceId, id2);
  EXPECT_NE(id1, id2);

  // Deleting invalid UIResourceId is allowed and does not change state.
  host_impl_->DeleteUIResource(-1);
  EXPECT_EQ(1u, sii->shared_image_count());

  // Should return zero for invalid UIResourceId.  Number of textures should
  // not change.
  EXPECT_EQ(viz::kInvalidResourceId, host_impl_->ResourceIdForUIResource(-1));
  EXPECT_EQ(1u, sii->shared_image_count());

  host_impl_->DeleteUIResource(ui_resource_id);
  EXPECT_EQ(viz::kInvalidResourceId,
            host_impl_->ResourceIdForUIResource(ui_resource_id));
  EXPECT_EQ(0u, sii->shared_image_count());

  // Should not change state for multiple deletion on one UIResourceId
  host_impl_->DeleteUIResource(ui_resource_id);
  EXPECT_EQ(0u, sii->shared_image_count());
}

TEST_P(ClientModeLayerTreeHostImplTest, CreateETC1UIResource) {
  auto test_context_provider = viz::TestContextProvider::CreateRaster();
  gpu::TestSharedImageInterface* sii =
      test_context_provider->SharedImageInterface();
  CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::Create3d(
                                        std::move(test_context_provider)));

  EXPECT_EQ(0u, sii->shared_image_count());

  gfx::Size size(4, 4);
  SkImageInfo info =
      SkImageInfo::Make(4, 4, kAlpha_8_SkColorType, kPremul_SkAlphaType);
  sk_sp<SkPixelRef> pixel_ref(SkMallocPixelRef::MakeAllocate(info, 0));
  pixel_ref->setImmutable();
  UIResourceBitmap bitmap(std::move(pixel_ref), size);
  UIResourceId ui_resource_id = 1;
  host_impl_->CreateUIResource(ui_resource_id, bitmap);
  EXPECT_EQ(1u, sii->shared_image_count());
  viz::ResourceId id1 = host_impl_->ResourceIdForUIResource(ui_resource_id);
  EXPECT_NE(viz::kInvalidResourceId, id1);
}

TEST_P(ClientModeLayerTreeHostImplTest,
       SelectionBoundsPassedToRenderFrameMetadata) {
  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10));
  UpdateDrawProperties(host_impl_->active_tree());

  auto observer = std::make_unique<TestRenderFrameMetadataObserver>(false);
  auto* observer_ptr = observer.get();
  host_impl_->SetRenderFrameObserver(std::move(observer));
  EXPECT_FALSE(observer_ptr->last_metadata());

  // Trigger a draw-swap sequence.
  host_impl_->SetNeedsRedraw(/*animation_only=*/false,
                             /*skip_if_inside_draw=*/false);
  DrawFrame();

  // Ensure the selection bounds propagated to the render frame metadata
  // represent an empty selection.
  ASSERT_TRUE(observer_ptr->last_metadata());
  const viz::Selection<gfx::SelectionBound>& selection_1 =
      observer_ptr->last_metadata()->selection;
  EXPECT_EQ(gfx::SelectionBound::EMPTY, selection_1.start.type());
  EXPECT_EQ(gfx::SelectionBound::EMPTY, selection_1.end.type());
  EXPECT_EQ(gfx::PointF(), selection_1.start.edge_end());
  EXPECT_EQ(gfx::PointF(), selection_1.start.edge_start());
  EXPECT_FALSE(selection_1.start.visible());
  EXPECT_FALSE(selection_1.end.visible());

  // Plumb the layer-local selection bounds.
  gfx::Point selection_start(5, 0);
  gfx::Point selection_end(5, 5);
  LayerSelection selection;
  selection.start.type = gfx::SelectionBound::CENTER;
  selection.start.layer_id = root->id();
  selection.start.edge_end = selection_end;
  selection.start.edge_start = selection_start;
  selection.end = selection.start;
  host_impl_->active_tree()->RegisterSelection(selection);

  // Trigger a draw-swap sequence.
  host_impl_->SetNeedsRedraw(/*animation_only=*/false,
                             /*skip_if_inside_draw=*/false);
  DrawFrame();

  // Ensure the selection bounds have propagated to the render frame metadata.
  ASSERT_TRUE(observer_ptr->last_metadata());
  const viz::Selection<gfx::SelectionBound>& selection_2 =
      observer_ptr->last_metadata()->selection;
  EXPECT_EQ(selection.start.type, selection_2.start.type());
  EXPECT_EQ(selection.end.type, selection_2.end.type());
  EXPECT_EQ(gfx::PointF(selection_end), selection_2.start.edge_end());
  EXPECT_EQ(gfx::PointF(selection_start), selection_2.start.edge_start());
  EXPECT_TRUE(selection_2.start.visible());
  EXPECT_TRUE(selection_2.end.visible());
}

TEST_P(ClientModeLayerTreeHostImplTest,
       VerticalScrollDirectionChangesPassedToRenderFrameMetadata) {
  // Set up the viewport.
  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));

  // Set up the render frame metadata observer.
  auto observer = std::make_unique<TestRenderFrameMetadataObserver>(false);
  auto* observer_ptr = observer.get();
  host_impl_->SetRenderFrameObserver(std::move(observer));
  EXPECT_FALSE(observer_ptr->last_metadata());

  // Our test will be comprised of multiple legs, each leg consisting of a
  // distinct scroll event and an expectation regarding the vertical scroll
  // direction passed to the render frame metadata.
  typedef struct {
    gfx::Vector2d scroll_delta;
    viz::VerticalScrollDirection expected_vertical_scroll_direction;
  } TestLeg;

  std::vector<TestLeg> test_legs;

  // Initially, vertical scroll direction should be |kNull| indicating absence.
  test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(),
                       /*expected_vertical_scroll_direction=*/viz::
                           VerticalScrollDirection::kNull});

  // Scrolling to the right should not affect vertical scroll direction.
  test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(10, 0),
                       /*expected_vertical_scroll_direction=*/viz::
                           VerticalScrollDirection::kNull});

  // After scrolling down, the vertical scroll direction should be |kDown|.
  test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(0, 10),
                       /*expected_vertical_scroll_direction=*/viz::
                           VerticalScrollDirection::kDown});

  // If we scroll down again, the vertical scroll direction should be |kNull| as
  // there was no change in vertical scroll direction.
  test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(0, 10),
                       /*expected_vertical_scroll_direction=*/viz::
                           VerticalScrollDirection::kNull});

  // Scrolling to the left should not affect last vertical scroll direction.
  test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(-10, 0),
                       /*expected_vertical_scroll_direction=*/viz::
                           VerticalScrollDirection::kNull});

  // After scrolling up, the vertical scroll direction should be |kUp|.
  test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(0, -10),
                       /*expected_vertical_scroll_direction=*/viz::
                           VerticalScrollDirection::kUp});

  // If we scroll up again, the vertical scroll direction should be |kNull| as
  // there was no change in vertical scroll direction.
  test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(0, -10),
                       /*expected_vertical_scroll_direction=*/viz::
                           VerticalScrollDirection::kNull});

  // Iterate over all legs of our test.
  for (auto& test_leg : test_legs) {
    // If the test leg contains a scroll, perform it.
    if (!test_leg.scroll_delta.IsZero()) {
      GetInputHandler().ScrollBegin(
          BeginState(gfx::Point(), test_leg.scroll_delta,
                     ui::ScrollInputType::kWheel)
              .get(),
          ui::ScrollInputType::kWheel);
      GetInputHandler().ScrollUpdate(UpdateState(
          gfx::Point(), test_leg.scroll_delta, ui::ScrollInputType::kWheel));
    }

    // Trigger draw.
    host_impl_->SetNeedsRedraw(/*animation_only=*/false,
                               /*skip_if_inside_draw=*/false);
    DrawFrame();

    // Assert our expectation regarding the vertical scroll direction.
    EXPECT_EQ(test_leg.expected_vertical_scroll_direction,
              observer_ptr->last_metadata()->new_vertical_scroll_direction);

    // If the test leg contains a scroll, end it.
    if (!test_leg.scroll_delta.IsZero()) {
      GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);
    }
  }
}

TEST_P(ClientModeLayerTreeHostImplTest,
       ImplThreadPhaseUponImplSideInvalidation) {
  LayerTreeSettings settings = DefaultSettings();
  CreateHostImpl(settings, CreateLayerTreeFrameSink());
  // In general invalidation should never be ran outside the impl frame.
  auto args = viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
  host_impl_->WillBeginImplFrame(args);
  // Expect no crash because the operation is within an impl frame.
  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->InvalidateContentOnImplSide();

  // Once the impl frame is finished the impl thread phase is set to IDLE.
  host_impl_->DidFinishImplFrame(args);

  settings.using_synchronous_renderer_compositor = true;
  CreateHostImpl(settings, CreateLayerTreeFrameSink());
  // Expect no crash when using synchronous renderer compositor regardless the
  // impl thread phase.
  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->InvalidateContentOnImplSide();

  // Test passes when there is no crash.
}

TEST_P(ClientModeLayerTreeHostImplTest,
       CommitScheduledOnAnimatedImageAdvanceWithCanvasChildId) {
  // Invalidation should never run outside the impl frame.
  auto args = viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1,
                                                  base::TimeTicks::Now());
  host_impl_->WillBeginImplFrame(args);

  // 1. Setup a layer with an animated image.
  gfx::Size layer_bounds(100, 100);
  auto recording_source = std::make_unique<FakeRecordingSource>(layer_bounds);

  std::vector<FrameMetadata> frames = {
      FrameMetadata(true, base::Milliseconds(100)),
      FrameMetadata(true, base::Milliseconds(100))};
  PaintImage image = CreateAnimatedImage(gfx::Size(100, 100), frames);
  recording_source->add_draw_image(image, gfx::Point(0, 0));
  recording_source->Rerecord();
  scoped_refptr<RasterSource> raster_source =
      recording_source->CreateRasterSource();

  // 2. Setup tree.
  ElementId canvas_child_id(0x12345);
  if (!CommitsToActiveTree()) {
    CreatePendingTree();
  }
  FakePictureLayerImpl* sync_layer = SetupRootLayer<FakePictureLayerImpl>(
      host_impl_->sync_tree(), layer_bounds);
  sync_layer->SetCanvasChildId(canvas_child_id);
  sync_layer->SetRasterSource(raster_source, Region());
  UpdateDrawProperties(host_impl_->sync_tree());
  FakePictureLayerImpl* active_layer;
  if (CommitsToActiveTree()) {
    active_layer = sync_layer;
  } else {
    host_impl_->ActivateSyncTree();
    active_layer = static_cast<FakePictureLayerImpl*>(
        host_impl_->active_tree()->root_layer());
    UpdateDrawProperties(host_impl_->active_tree());
  }

  EXPECT_EQ(canvas_child_id, active_layer->canvas_child_id());

  // 3. Make the image visible so it can animate.
  gfx::Rect visible_rect(0, 0, 100, 100);
  active_layer->SetVisibleLayerRectForTesting(visible_rect);
  EXPECT_TRUE(active_layer->ShouldAnimate(image.stable_id()));

  // 4. Trigger animation and verify NO commit is scheduled yet (since it hasn't
  // advanced).
  did_request_commit_ = false;
  client_host_impl(host_impl_.get())->InvalidateContentOnImplSide();
  EXPECT_FALSE(did_request_commit_);

  if (!CommitsToActiveTree()) {
    ASSERT_TRUE(host_impl_->pending_tree());
    ASSERT_TRUE(host_impl_->pending_tree()->root_layer());
    UpdateDrawProperties(host_impl_->pending_tree());
    host_impl_->ActivateSyncTree();
    active_layer = static_cast<FakePictureLayerImpl*>(
        host_impl_->active_tree()->root_layer());
    active_layer->SetVisibleLayerRectForTesting(visible_rect);
  }

  host_impl_->DidFinishImplFrame(args);

  // 5. Advance time and trigger animation again, verify commit IS scheduled.
  did_request_commit_ = false;

  // Advance time by 110ms (frame duration is 100ms).
  auto args2 = viz::CreateBeginFrameArgsForTesting(
      BEGINFRAME_FROM_HERE, 0, 2, args.frame_time + base::Milliseconds(110));
  host_impl_->WillBeginImplFrame(args2);

  client_host_impl(host_impl_.get())->InvalidateContentOnImplSide();

  // Now it should have advanced, and since it has canvas_child_id, it should
  // have scheduled a commit.
  EXPECT_TRUE(did_request_commit_);

  host_impl_->DidFinishImplFrame(args2);
}

// Scroll operations on a non-composited scroller are eligible
// for rasterization. This tests if rasterization is detected
// and requested by the LTHI. The scroll is applied to the
// pending tree, so activation will be required before the scroll
// is presented.
TEST_P(ClientModeLayerTreeHostImplTest, NonCompositedScrollUsesRaster) {
  gfx::Size scrollable_content_bounds(100, 100);
  gfx::Size container_bounds(50, 50);
  if (!CommitsToActiveTree()) {
    CreatePendingTree();
  }

  // Create root and scroll layers so that we can set up a
  // non-composited scrollable node, eligible for raster scroll.
  auto* sync_tree_root = SetupRootLayer<LayerImpl>(host_impl_->sync_tree(),
                                                   scrollable_content_bounds);
  sync_tree_root->SetNeedsPushProperties();
  auto* scrolling_layer =
      AddScrollableLayer(sync_tree_root, container_bounds, gfx::Size());
  scrolling_layer->SetNeedsPushProperties();
  CreateScrollNodeForNonCompositedScroller(
      host_impl_->sync_tree()->property_trees(), sync_tree_root->id(),
      scrolling_layer->element_id(), scrollable_content_bounds,
      container_bounds);

  // Draw at least one frame before ScrollBegin.
  host_impl_->sync_tree()->set_needs_update_draw_properties();
  UpdateDrawProperties(host_impl_->sync_tree());
  host_impl_->ActivateSyncTree();
  DrawFrame();

  // Scrolling on this non-composited tree should be marked as raster-inducing.
  ScrollStateData scroll_state_data;
  scroll_state_data.set_current_native_scrolling_element(
      scrolling_layer->element_id());
  scroll_state_data.is_beginning = true;
  std::unique_ptr<ScrollState> scroll_state =
      std::make_unique<ScrollState>(scroll_state_data);

  InputHandler::ScrollStatus status = GetInputHandler().ScrollBegin(
      scroll_state.get(), ui::ScrollInputType::kWheel);
  EXPECT_EQ(true, status.raster_inducing);

  // We always want to start applying the scroll offset to the active tree.
  host_impl_->active_tree()->DidUpdateScrollOffset(
      scrolling_layer->element_id(), /*pushed_from_main_or_pending_tree=*/true);

  // Draw the next frame of the scroll.
  {
    host_impl_->NotifyInputEvent(/*is_fling=*/false);
    host_impl_->SetFullViewportDamage();
    host_impl_->SetNeedsRedraw(/*animation_only=*/false,
                               /*skip_if_inside_draw=*/false);
    TestFrameData frame;
    auto args = viz::CreateBeginFrameArgsForTesting(
        BEGINFRAME_FROM_HERE, viz::BeginFrameArgs::kManualSourceId, 1,
        base::TimeTicks() + base::Milliseconds(1));
    host_impl_->WillBeginImplFrame(args);
    EXPECT_EQ(DrawResult::kSuccess, host_impl_->PrepareToDraw(&frame));

    // This call sets the invalidate_raster_scroll bit.
    // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
    client_host_impl(host_impl_.get())->InvalidateContentOnImplSide();
    if (!CommitsToActiveTree()) {
      // Activate the pending tree before drawing layers.
      host_impl_->ActivateSyncTree();
    }
    std::optional<SubmitInfo> draw_layers_state =
        host_impl_->DrawLayers(&frame);
    EXPECT_EQ(true, draw_layers_state->invalidate_raster_scroll);
    host_impl_->DidDrawAllLayers(frame);
    host_impl_->DidFinishImplFrame(args);
  }
}

INSTANTIATE_CLIENT_MODE_TREE_TEST_P(ClientModeLayerTreeHostImplTest);

TEST_P(LayerTreeHostImplTestMultiScrollable,
       ScrollbarFlashAfterAnyScrollUpdate) {
  LayerTreeSettings settings = DefaultSettings();
  settings.scrollbar_fade_delay = base::Milliseconds(500);
  settings.scrollbar_fade_duration = base::Milliseconds(300);
  settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY;
  settings.scrollbar_flash_after_any_scroll_update = true;
  settings.scrollbar_flash_once_after_scroll_update = false;

  SetUpLayers(settings);

  EXPECT_EQ(scrollbar_1_->Opacity(), 0);
  EXPECT_EQ(scrollbar_2_->Opacity(), 0);

  // Scroll on root should flash all scrollbars.
  GetInputHandler().RootScrollBegin(
      BeginState(gfx::Point(20, 20), gfx::Vector2dF(0, 10),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(20, 20), gfx::Vector2d(0, 10), ui::ScrollInputType::kWheel));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_TRUE(scrollbar_1_->Opacity());
  EXPECT_TRUE(scrollbar_2_->Opacity());

  EXPECT_FALSE(animation_task_.is_null());
  ResetScrollbars();

  // Scroll on child should flash all scrollbars.
  GetInputHandler().ScrollBegin(
      BeginState(gfx::Point(70, 70), gfx::Vector2dF(0, 100),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  GetInputHandler().ScrollUpdate(
      AnimatedUpdateState(gfx::Point(70, 70), gfx::Vector2d(0, 100)));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_TRUE(scrollbar_1_->Opacity());
  EXPECT_TRUE(scrollbar_2_->Opacity());

  EXPECT_FALSE(animation_task_.is_null());
}

TEST_P(LayerTreeHostImplTestMultiScrollable,
       ScrollbarFlashOnceAfterAnyScrollUpdate) {
  LayerTreeSettings settings = DefaultSettings();
  settings.scrollbar_fade_delay = base::Milliseconds(500);
  settings.scrollbar_fade_duration = base::Milliseconds(300);
  settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY;
  settings.scrollbar_flash_after_any_scroll_update = false;
  settings.scrollbar_flash_once_after_scroll_update = true;

  SetUpLayers(settings);

  EXPECT_EQ(scrollbar_1_->Opacity(), 0);
  EXPECT_EQ(scrollbar_2_->Opacity(), 0);

  // Beginning of scroll on root should flash all scrollbars.
  GetInputHandler().RootScrollBegin(
      BeginState(gfx::Point(20, 20), gfx::Vector2dF(0, 10),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(20, 20), gfx::Vector2d(0, 10), ui::ScrollInputType::kWheel));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_TRUE(scrollbar_1_->Opacity());
  EXPECT_TRUE(scrollbar_2_->Opacity());

  EXPECT_FALSE(animation_task_.is_null());
  ResetScrollbars();

  // Scrolling on root again mustn't flash other than the root scrollbar.
  GetInputHandler().RootScrollBegin(
      BeginState(gfx::Point(70, 70), gfx::Vector2dF(0, 100),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(70, 70), gfx::Vector2d(0, 100), ui::ScrollInputType::kWheel));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_TRUE(scrollbar_1_->Opacity());
  EXPECT_FALSE(scrollbar_2_->Opacity());

  EXPECT_FALSE(animation_task_.is_null());
  ResetScrollbars();

  // Yet another scroll on child should flash only the child scrollbar.
  GetInputHandler().ScrollBegin(
      BeginState(gfx::Point(70, 70), gfx::Vector2dF(0, 100),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  GetInputHandler().ScrollUpdate(
      AnimatedUpdateState(gfx::Point(70, 70), gfx::Vector2d(0, 100)));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_FALSE(scrollbar_1_->Opacity());
  EXPECT_TRUE(scrollbar_2_->Opacity());

  EXPECT_FALSE(animation_task_.is_null());
}

TEST_P(LayerTreeHostImplTestMultiScrollable,
       ScrollbarFlashOnceEnteredViewport) {
  LayerTreeSettings settings = DefaultSettings();
  settings.scrollbar_fade_delay = base::Milliseconds(500);
  settings.scrollbar_fade_duration = base::Milliseconds(300);
  settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY;
  settings.scrollbar_flash_after_any_scroll_update = false;
  settings.scrollbar_flash_once_after_scroll_update = false;
  settings.scrollbar_flash_once_visible_on_viewport = true;

  SetUpLayers(settings);

  raw_ptr<SolidColorScrollbarLayerImpl> scrollbar3 = nullptr;

  {
    // Create another child scroll element at (10, 210) with size 50x150
    LayerImpl* root_scroll = OuterViewportScrollLayer();

    auto* child = AddScrollableLayer(root_scroll, gfx::Size(100, 100),
                                     gfx::Size(250, 150));
    GetTransformNode(child)->post_translation = gfx::Vector2dF(50, 50);

    scrollbar3 = AddLayer<SolidColorScrollbarLayerImpl>(
        host_impl_->active_tree(), ScrollbarOrientation::kVertical, 15, 0,
        true);
    SetupScrollbarLayer(child, scrollbar3);
    scrollbar3->SetBounds(gfx::Size(50, 150));
    scrollbar3->SetOffsetToTransformParent(gfx::Vector2dF(10, 210));
    host_impl_->active_tree()->UpdateAllScrollbarGeometriesForTesting();
    UpdateDrawProperties(host_impl_->active_tree());
    host_impl_->active_tree()->DidBecomeActive();
  }

  EXPECT_EQ(scrollbar_1_->Opacity(), 0);
  EXPECT_EQ(scrollbar_2_->Opacity(), 0);
  EXPECT_EQ(scrollbar3->Opacity(), 0);

  // First scroll: root down by 10, visible scrollbars flash.
  GetInputHandler().RootScrollBegin(
      BeginState(gfx::Point(20, 20), gfx::Vector2dF(0, 10),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(20, 20), gfx::Vector2d(0, 10), ui::ScrollInputType::kWheel));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  // Scroll is less than threshold, so no flash.
  EXPECT_TRUE(scrollbar_1_->Opacity());
  EXPECT_FALSE(scrollbar_2_->Opacity());
  EXPECT_FALSE(scrollbar3->Opacity());
  EXPECT_FALSE(animation_task_.is_null());

  // Reset scrollbars
  GetEffectNode(scrollbar3.get())->opacity = 0;
  ResetScrollbars();

  // Second scroll: root down by another 200 right till where the third child
  // is. Both the root and the scrollbar2 should flash as the scrollbar2 is
  // one the viewport now.
  GetInputHandler().RootScrollBegin(
      BeginState(gfx::Point(150, 100), gfx::Vector2dF(0, 200),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  GetInputHandler().ScrollUpdate(UpdateState(gfx::Point(150, 100),
                                             gfx::Vector2d(0, 200),
                                             ui::ScrollInputType::kWheel));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_TRUE(scrollbar_1_->Opacity());
  EXPECT_TRUE(scrollbar_2_->Opacity());
  EXPECT_FALSE(scrollbar3->Opacity());
  EXPECT_FALSE(animation_task_.is_null());

  // Reset scrollbars
  GetEffectNode(scrollbar3.get())->opacity = 0;
  ResetScrollbars();

  // Scroll down a bit more. Now, the scrollbar3 should flash as it is visible
  // now.
  GetInputHandler().RootScrollBegin(
      BeginState(gfx::Point(150, 100), gfx::Vector2dF(0, 30),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(150, 100), gfx::Vector2d(0, 30), ui::ScrollInputType::kWheel));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_TRUE(scrollbar_1_->Opacity());
  EXPECT_FALSE(scrollbar_2_->Opacity());
  EXPECT_TRUE(scrollbar3->Opacity());
  EXPECT_FALSE(animation_task_.is_null());

  // Reset scrollbars
  GetEffectNode(scrollbar3.get())->opacity = 0;
  ResetScrollbars();

  // Scroll down more so that that last scrollbar is not visible anymore.
  GetInputHandler().RootScrollBegin(
      BeginState(gfx::Point(150, 100), gfx::Vector2dF(0, 300),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  GetInputHandler().ScrollUpdate(UpdateState(gfx::Point(150, 100),
                                             gfx::Vector2d(0, 300),
                                             ui::ScrollInputType::kWheel));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_TRUE(scrollbar_1_->Opacity());
  EXPECT_FALSE(scrollbar_2_->Opacity());
  EXPECT_FALSE(scrollbar3->Opacity());
  EXPECT_FALSE(animation_task_.is_null());

  // Reset scrollbars
  GetEffectNode(scrollbar3.get())->opacity = 0;
  ResetScrollbars();

  // Scroll back so that scrollbar3 is visible again.
  // Scroll down more so that that last scrollbar is not visible anymore.
  GetInputHandler().RootScrollBegin(
      BeginState(gfx::Point(150, 100), gfx::Vector2dF(0, -310),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  GetInputHandler().ScrollUpdate(UpdateState(gfx::Point(150, 100),
                                             gfx::Vector2d(0, -310),
                                             ui::ScrollInputType::kWheel));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_TRUE(scrollbar_1_->Opacity());
  EXPECT_FALSE(scrollbar_2_->Opacity());
  EXPECT_FALSE(scrollbar3->Opacity());
  EXPECT_FALSE(animation_task_.is_null());

  // Reset scrollbars
  GetEffectNode(scrollbar3.get())->opacity = 0;
  ResetScrollbars();

  // Scroll up a bit more. Now, the scrollbar3 should flash as it is visible
  // now and the threshold is passed.
  GetInputHandler().RootScrollBegin(
      BeginState(gfx::Point(150, 100), gfx::Vector2dF(0, 25),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(150, 100), gfx::Vector2d(0, 25), ui::ScrollInputType::kWheel));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_TRUE(scrollbar_1_->Opacity());
  EXPECT_FALSE(scrollbar_2_->Opacity());
  EXPECT_TRUE(scrollbar3->Opacity());
  EXPECT_FALSE(animation_task_.is_null());

  // Reset scrollbars
  GetEffectNode(scrollbar3.get())->opacity = 0;
  ResetScrollbars();
}

TEST_P(LayerTreeHostImplTestMultiScrollable, ScrollbarFlashWhenMouseEnter) {
  LayerTreeSettings settings = DefaultSettings();
  settings.scrollbar_fade_delay = base::Milliseconds(500);
  settings.scrollbar_fade_duration = base::Milliseconds(300);
  settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY;
  settings.scrollbar_flash_when_mouse_enter = true;

  SetUpLayers(settings);

  constexpr size_t kNumberOfRepeats = 3;
  for (size_t i = 0; i < kNumberOfRepeats; i++) {
    ScrollbarAnimationController* scrollbar_animation_controller =
        host_impl_->ScrollbarAnimationControllerForElementId(
            scrollbar_1_->scroll_element_id());

    const float kMouseMoveDistanceToTriggerFadeIn =
        scrollbar_animation_controller
            ->GetScrollbarAnimationController(ScrollbarOrientation::kVertical)
            .MouseMoveDistanceToTriggerFadeIn();
    const int thumb_thickness = scrollbar_1_->ThumbThickness();

    GetInputHandler().MouseMoveAt(
        gfx::Point(thumb_thickness + kMouseMoveDistanceToTriggerFadeIn + 1, 1));
    EXPECT_FALSE(scrollbar_animation_controller->MouseIsNearScrollbar(
        ScrollbarOrientation::kVertical));
    EXPECT_FALSE(scrollbar_animation_controller->MouseIsNearScrollbarThumb(
        ScrollbarOrientation::kVertical));

    EXPECT_FALSE(scrollbar_1_->Opacity());
    EXPECT_FALSE(scrollbar_2_->Opacity());

    ResetScrollbars();

    ScrollbarAnimationController* scrollbar_animation_controller2 =
        host_impl_->ScrollbarAnimationControllerForElementId(
            scrollbar_2_->scroll_element_id());

    const float kMouseMoveDistanceToTriggerFadeInChild =
        scrollbar_animation_controller2
            ->GetScrollbarAnimationController(ScrollbarOrientation::kVertical)
            .MouseMoveDistanceToTriggerFadeIn();
    const int kThumbThicknessChild = scrollbar_2_->ThumbThickness();

    GetInputHandler().MouseMoveAt(gfx::Point(
        kThumbThicknessChild + kMouseMoveDistanceToTriggerFadeInChild + 50,
        50));
    EXPECT_FALSE(scrollbar_animation_controller2->MouseIsNearScrollbar(
        ScrollbarOrientation::kVertical));
    EXPECT_FALSE(scrollbar_animation_controller2->MouseIsNearScrollbarThumb(
        ScrollbarOrientation::kVertical));

    EXPECT_FALSE(scrollbar_1_->Opacity());
    EXPECT_TRUE(scrollbar_2_->Opacity());

    ResetScrollbars();
  }
}

INSTANTIATE_CLIENT_MODE_TREE_TEST_P(LayerTreeHostImplTestMultiScrollable);

// Tests that, on a page with content the same size as the viewport, hiding
// the browser controls also increases the ScrollableSize (i.e. the content
// size). Since the viewport got larger, the effective scrollable "content" also
// did. This ensures, for one thing, that the overscroll glow is shown in the
// right place.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       HidingBrowserControlsExpandsScrollableSize) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(50, 50), gfx::Size(50, 50), gfx::Size(50, 50));

  LayerTreeImpl* active_tree = host_impl_->active_tree();
  DrawFrame();

  // The browser controls should start off showing so the viewport should be
  // shrunk.
  ExpectViewportGeometries(1);
  EXPECT_EQ(gfx::SizeF(50, 50), active_tree->ScrollableSize());

  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 25),
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);

  host_impl_->browser_controls_manager()->ScrollBegin();

  // Hide the browser controls by a bit, the scrollable size should increase but
  // the actual content bounds shouldn't.
  host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25));
  ExpectViewportGeometries(0.5f);
  EXPECT_EQ(gfx::SizeF(50, 75), active_tree->ScrollableSize());

  // Fully hide the browser controls.
  host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25));
  ExpectViewportGeometries(0);
  EXPECT_EQ(gfx::SizeF(50, 100), active_tree->ScrollableSize());

  // Scrolling additionally shouldn't have any effect.
  host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25));
  ExpectViewportGeometries(0);
  EXPECT_EQ(gfx::SizeF(50, 100), active_tree->ScrollableSize());

  host_impl_->browser_controls_manager()->ScrollEnd();
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);
}

TEST_P(LayerTreeHostImplBrowserControlsTest,
       HidingBrowserControlsExpandsClipAncestorsOfReplacedOuterScroller) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(180, 180), gfx::Size(180, 180), gfx::Size(180, 180));

  LayerTreeImpl* active_tree = host_impl_->active_tree();
  PropertyTrees* property_trees = active_tree->property_trees();
  LayerImpl* original_outer_scroll = OuterViewportScrollLayer();

  LayerImpl* parent_clip_layer = AddLayerInActiveTree();
  CopyProperties(original_outer_scroll, parent_clip_layer);
  parent_clip_layer->SetBounds(gfx::Size(160, 160));
  CreateClipNode(parent_clip_layer);
  LayerImpl* clip_layer = AddLayerInActiveTree();
  clip_layer->SetBounds(gfx::Size(150, 150));
  CopyProperties(parent_clip_layer, clip_layer);
  CreateClipNode(clip_layer);
  LayerImpl* scroll_layer =
      AddScrollableLayer(clip_layer, gfx::Size(150, 150), gfx::Size(300, 300));
  GetScrollNode(scroll_layer)->scrolls_outer_viewport = true;
  ClipNode* original_outer_clip = GetClipNode(original_outer_scroll);
  ClipNode* parent_clip = GetClipNode(parent_clip_layer);
  ClipNode* scroll_clip = GetClipNode(clip_layer);

  auto viewport_property_ids = active_tree->ViewportPropertyIdsForTesting();
  viewport_property_ids.outer_clip = clip_layer->clip_tree_index();
  viewport_property_ids.outer_scroll = scroll_layer->scroll_tree_index();
  active_tree->SetViewportPropertyIds(viewport_property_ids);
  UpdateDrawProperties(active_tree);

  EXPECT_EQ(scroll_layer, OuterViewportScrollLayer());
  EXPECT_EQ(GetScrollNode(scroll_layer),
            active_tree->OuterViewportScrollNode());

  EXPECT_EQ(1, active_tree->CurrentTopControlsShownRatio());
  EXPECT_EQ(50, host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_EQ(gfx::Vector2dF(),
            property_trees->inner_viewport_container_bounds_delta());
  EXPECT_EQ(gfx::Vector2dF(),
            property_trees->outer_viewport_container_bounds_delta());
  EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize());
  EXPECT_EQ(gfx::RectF(0, 0, 180, 180), original_outer_clip->clip);
  EXPECT_EQ(gfx::RectF(0, 0, 160, 160), parent_clip->clip);
  EXPECT_EQ(gfx::RectF(0, 0, 150, 150), scroll_clip->clip);

  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 25),
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);

  // Hide the browser controls by a bit, the scrollable size should increase but
  // the actual content bounds shouldn't.
  host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25));
  EXPECT_EQ(0.5f, active_tree->CurrentTopControlsShownRatio());
  EXPECT_EQ(25, host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_EQ(gfx::Vector2dF(0, 25),
            property_trees->inner_viewport_container_bounds_delta());
  EXPECT_EQ(gfx::Vector2dF(0, 25),
            property_trees->outer_viewport_container_bounds_delta());
  EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize());
  EXPECT_EQ(gfx::RectF(0, 0, 150, 175), scroll_clip->clip);
  EXPECT_EQ(gfx::RectF(0, 0, 160, 175), parent_clip->clip);
  EXPECT_EQ(gfx::RectF(0, 0, 180, 180), original_outer_clip->clip);

  // Fully hide the browser controls.
  host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25));
  EXPECT_EQ(0, active_tree->CurrentTopControlsShownRatio());
  EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_EQ(gfx::Vector2dF(0, 50),
            property_trees->inner_viewport_container_bounds_delta());
  EXPECT_EQ(gfx::Vector2dF(0, 50),
            property_trees->outer_viewport_container_bounds_delta());
  EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize());
  EXPECT_EQ(gfx::RectF(0, 0, 150, 200), scroll_clip->clip);
  EXPECT_EQ(gfx::RectF(0, 0, 160, 200), parent_clip->clip);
  EXPECT_EQ(gfx::RectF(0, 0, 180, 200), original_outer_clip->clip);

  // Scrolling additionally shouldn't have any effect.
  host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25));
  EXPECT_EQ(0, active_tree->CurrentTopControlsShownRatio());
  EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_EQ(gfx::Vector2dF(0, 50),
            property_trees->inner_viewport_container_bounds_delta());
  EXPECT_EQ(gfx::Vector2dF(0, 50),
            property_trees->outer_viewport_container_bounds_delta());
  EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize());
  EXPECT_EQ(gfx::RectF(0, 0, 150, 200), scroll_clip->clip);
  EXPECT_EQ(gfx::RectF(0, 0, 160, 200), parent_clip->clip);
  EXPECT_EQ(gfx::RectF(0, 0, 180, 200), original_outer_clip->clip);

  host_impl_->browser_controls_manager()->ScrollEnd();
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);
}

TEST_P(LayerTreeHostImplBrowserControlsTest,
       HidingBrowserControlsAdjustsSnapFling) {
  gfx::Size view_size(100, 100);
  gfx::Size content_size(100, 1000);
  gfx::RectF snap_area_1(0, 0, 100, 700);
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(view_size, view_size,
                                                        content_size);

  SnapContainerData container(
      ScrollSnapType(false, SnapAxis::kY, SnapStrictness::kMandatory),
      gfx::RectF(0, 0, 100, 100), gfx::PointF(0, 900));
  ScrollSnapAlign start = ScrollSnapAlign(SnapAlignment::kStart);
  container.AddSnapAreaData(
      SnapAreaData(start, snap_area_1, false, false, ElementId(10)));
  host_impl_->OuterViewportScrollNode()->snap_container_data.emplace(container);

  DrawFrame();
  ExpectViewportGeometries(1.0f);

  LayerTreeImpl* active_tree = host_impl_->active_tree();
  PropertyTrees* property_trees = active_tree->property_trees();
  LayerImpl* outer_scroll_layer = OuterViewportScrollLayer();
  InputHandler& handler = GetInputHandler();
  gfx::PointF initial_offset, target_offset;
  gfx::Point position(50, 50);
  ui::ScrollInputType type = ui::ScrollInputType::kTouchscreen;

  handler.ScrollBegin(&*BeginState(position, gfx::Vector2dF(0, 15), type),
                      type);
  handler.ScrollUpdate(UpdateState(position, gfx::Vector2dF(0, 15), type));

  // The browser controls, now partially hidden, are consuming all of the scroll
  // delta so far.
  EXPECT_FLOAT_EQ(0.7, active_tree->CurrentTopControlsShownRatio());
  EXPECT_EQ(gfx::Vector2dF(0, 15),
            property_trees->outer_viewport_container_bounds_delta());
  EXPECT_POINTF_EQ(gfx::PointF(0, 0), CurrentScrollOffset(outer_scroll_layer));

  // Enter "constrained native fling" mode inside snap_area_1.
  EXPECT_FALSE(handler.GetSnapFlingInfoAndSetAnimatingSnapTarget(
      gfx::Vector2dF(0, 30), gfx::Vector2dF(0, 600), &initial_offset,
      &target_offset));

  // Finish hiding the browser controls and start scrolling the content.
  handler.ScrollUpdate(UpdateState(position, gfx::Vector2dF(0, 435), type));
  EXPECT_POINTF_EQ(gfx::PointF(0, 400),
                   CurrentScrollOffset(outer_scroll_layer));

  // Try to scroll past the bottom of snap_area_1.
  EXPECT_TRUE(handler.GetSnapFlingInfoAndSetAnimatingSnapTarget(
      gfx::Vector2dF(0, 300), gfx::Vector2dF(0, 1000), &initial_offset,
      &target_offset));

  // The fling constraint should take us to 550, which aligns with the bottom of
  // snap_area_1 using the expanded viewport size of 100x150 from hiding browser
  // controls, even though SnapContainerData is still based on 100x100 viewport.
  EXPECT_TRUE(handler.animating_for_snap_for_testing(
      host_impl_->OuterViewportScrollNode()->element_id));
  EXPECT_POINTF_EQ(gfx::PointF(0, 400), initial_offset);
  EXPECT_POINTF_EQ(gfx::PointF(0, 550), target_offset);

  handler.ScrollEnd(false, std::nullopt);
}

// Ensure that moving the browser controls (i.e. omnibox/url-bar on mobile) on
// pages with a non-1 minimum page scale factor (e.g. legacy desktop page)
// correctly scales the clipping adjustment performed to show the newly exposed
// region of the page.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       MovingBrowserControlsOuterClipDeltaScaled) {
  gfx::Size inner_size = gfx::Size(100, 100);
  gfx::Size outer_size = gfx::Size(100, 100);
  gfx::Size content_size = gfx::Size(200, 1000);
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(inner_size, outer_size,
                                                        content_size);

  LayerTreeImpl* active_tree = host_impl_->active_tree();

  // Create a content layer beneath the outer viewport scroll layer.
  LayerImpl* content = AddLayerInActiveTree();
  content->SetBounds(gfx::Size(100, 100));
  CopyProperties(OuterViewportScrollLayer(), content);
  active_tree->PushPageScaleFromMainThread(0.5f, 0.5f, 4);

  DrawFrame();

  // The browser controls should start off showing so the viewport should be
  // shrunk.
  ExpectViewportGeometries(1.0f);
  EXPECT_EQ(gfx::SizeF(200, 1000), active_tree->ScrollableSize());

  ASSERT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 25),
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);

  // Hide the browser controls by 25px. The outer clip should expand by 50px as
  // because the outer viewport is sized based on the minimum scale, in this
  // case 0.5. Therefore, changes to the outer viewport need to be divided by
  // the minimum scale as well.
  GetInputHandler().ScrollUpdate(
      UpdateState(gfx::Point(0, 0), gfx::Vector2dF(0, 25),
                  ui::ScrollInputType::kTouchscreen));
  ExpectViewportGeometries(0.5f);

  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);
}

// Tests that browser controls affect the position of horizontal scrollbars.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       HidingBrowserControlsAdjustsScrollbarPosition) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(50, 50), gfx::Size(50, 50), gfx::Size(50, 50));

  LayerTreeImpl* active_tree = host_impl_->active_tree();

  // Create a horizontal scrollbar.
  gfx::Size scrollbar_size(gfx::Size(50, 15));
  auto* scrollbar_layer = AddLayer<SolidColorScrollbarLayerImpl>(
      host_impl_->active_tree(), ScrollbarOrientation::kHorizontal, 3, 20,
      false);
  SetupScrollbarLayer(OuterViewportScrollLayer(), scrollbar_layer);
  scrollbar_layer->SetBounds(scrollbar_size);
  TouchActionRegion touch_action_region;
  touch_action_region.Union(TouchAction::kNone, gfx::Rect(scrollbar_size));
  scrollbar_layer->SetTouchActionRegion(touch_action_region);
  scrollbar_layer->SetOffsetToTransformParent(gfx::Vector2dF(0, 35));
  host_impl_->active_tree()->UpdateAllScrollbarGeometriesForTesting();
  UpdateDrawProperties(host_impl_->active_tree());

  DrawFrame();

  // The browser controls should start off showing so the viewport should be
  // shrunk.
  ExpectViewportGeometries(1.0f);
  EXPECT_EQ(gfx::SizeF(50, 50), active_tree->ScrollableSize());
  EXPECT_EQ(gfx::Size(50, 15), scrollbar_layer->bounds());
  EXPECT_EQ(gfx::Rect(20, 0, 10, 3), scrollbar_layer->ComputeThumbQuadRect());

  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 25),
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);

  host_impl_->browser_controls_manager()->ScrollBegin();

  // Hide the browser controls by a bit, the scrollable size should increase but
  // the actual content bounds shouldn't.
  {
    host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25));
    ExpectViewportGeometries(0.5f);
    EXPECT_EQ(gfx::SizeF(50, 75), active_tree->ScrollableSize());
    EXPECT_EQ(gfx::Size(50, 15), scrollbar_layer->bounds());
    EXPECT_EQ(gfx::Rect(20, 25, 10, 3),
              scrollbar_layer->ComputeThumbQuadRect());
  }

  // Fully hide the browser controls.
  {
    host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25));
    ExpectViewportGeometries(0);
    EXPECT_EQ(gfx::SizeF(50, 100), active_tree->ScrollableSize());
    EXPECT_EQ(gfx::Size(50, 15), scrollbar_layer->bounds());
    EXPECT_EQ(gfx::Rect(20, 50, 10, 3),
              scrollbar_layer->ComputeThumbQuadRect());
  }

  // Additional scrolling shouldn't have any effect.
  {
    host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25));
    ExpectViewportGeometries(0);
    EXPECT_EQ(gfx::SizeF(50, 100), active_tree->ScrollableSize());
    EXPECT_EQ(gfx::Size(50, 15), scrollbar_layer->bounds());
    EXPECT_EQ(gfx::Rect(20, 50, 10, 3),
              scrollbar_layer->ComputeThumbQuadRect());
  }

  host_impl_->browser_controls_manager()->ScrollEnd();
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);
}

TEST_P(LayerTreeHostImplBrowserControlsTest,
       ScrollBrowserControlsByFractionalAmount) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(10, 10), gfx::Size(10, 10), gfx::Size(10, 10));
  DrawFrame();

  gfx::Vector2dF top_controls_scroll_delta(0, 5.25f);
  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), top_controls_scroll_delta,
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);

  // Make the test scroll delta a fractional amount, to verify that the
  // fixed container size delta is (1) non-zero, and (2) fractional, and
  // (3) matches the movement of the browser controls.
  host_impl_->browser_controls_manager()->ScrollBegin();
  host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta);
  host_impl_->browser_controls_manager()->ScrollEnd();

  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);
  auto* property_trees = host_impl_->active_tree()->property_trees();
  EXPECT_FLOAT_EQ(top_controls_scroll_delta.y(),
                  property_trees->inner_viewport_container_bounds_delta().y());
}

// In this test, the outer viewport is initially unscrollable. We test that a
// scroll initiated on the inner viewport, causing the browser controls to show
// and thus making the outer viewport scrollable, still scrolls the outer
// viewport.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       BrowserControlsOuterViewportBecomesScrollable) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100));
  DrawFrame();

  LayerImpl* inner_scroll = InnerViewportScrollLayer();
  inner_scroll->SetDrawsContent(true);
  LayerImpl* outer_scroll = OuterViewportScrollLayer();
  outer_scroll->SetDrawsContent(true);

  // Need SetDrawsContent so ScrollBegin's hit test finds an actual layer.
  outer_scroll->SetDrawsContent(true);
  host_impl_->active_tree()->PushPageScaleFromMainThread(2, 1, 2);

  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 50),
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), gfx::Vector2dF(0, 50), ui::ScrollInputType::kTouchscreen));

  // The entire scroll delta should have been used to hide the browser controls.
  // The viewport layers should be resized back to their full sizes.
  EXPECT_EQ(0, host_impl_->active_tree()->CurrentTopControlsShownRatio());
  EXPECT_EQ(0, CurrentScrollOffset(inner_scroll).y());
  EXPECT_EQ(100, inner_scroll->bounds().height());
  EXPECT_EQ(100, outer_scroll->bounds().height());

  // The inner viewport should be scrollable by 50px * page_scale.
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), gfx::Vector2dF(0, 100), ui::ScrollInputType::kTouchscreen));
  EXPECT_EQ(50, CurrentScrollOffset(inner_scroll).y());
  EXPECT_EQ(0, CurrentScrollOffset(outer_scroll).y());
  EXPECT_EQ(gfx::PointF(), MaxScrollOffset(outer_scroll));

  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, -50),
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  EXPECT_EQ(host_impl_->CurrentlyScrollingNode()->id,
            outer_scroll->scroll_tree_index());

  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), gfx::Vector2dF(0, -50), ui::ScrollInputType::kTouchscreen));

  // The entire scroll delta should have been used to show the browser controls.
  // The outer viewport should be resized to accommodate and scrolled to the
  // bottom of the document to keep the viewport in place.
  EXPECT_EQ(1, host_impl_->active_tree()->CurrentTopControlsShownRatio());
  EXPECT_EQ(50, inner_scroll->bounds().height());
  EXPECT_EQ(100, outer_scroll->bounds().height());
  EXPECT_EQ(25, CurrentScrollOffset(outer_scroll).y());
  EXPECT_EQ(25, CurrentScrollOffset(inner_scroll).y());

  // Now when we continue scrolling, make sure the outer viewport gets scrolled
  // since it wasn't scrollable when the scroll began.
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), gfx::Vector2dF(0, -20), ui::ScrollInputType::kTouchscreen));
  EXPECT_EQ(25, CurrentScrollOffset(outer_scroll).y());
  EXPECT_EQ(15, CurrentScrollOffset(inner_scroll).y());

  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), gfx::Vector2dF(0, -30), ui::ScrollInputType::kTouchscreen));
  EXPECT_EQ(25, CurrentScrollOffset(outer_scroll).y());
  EXPECT_EQ(0, CurrentScrollOffset(inner_scroll).y());

  GetInputHandler().ScrollUpdate(
      UpdateState(gfx::Point(), gfx::Vector2dF(0.f, -50),
                  ui::ScrollInputType::kTouchscreen));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_EQ(0, CurrentScrollOffset(outer_scroll).y());
  EXPECT_EQ(0, CurrentScrollOffset(inner_scroll).y());
}

// Test that the fixed position container delta is appropriately adjusted
// by the browser controls showing/hiding and page scale doesn't affect it.
TEST_P(LayerTreeHostImplBrowserControlsTest, FixedContainerDelta) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(100, 100), gfx::Size(100, 100), gfx::Size(100, 100));
  DrawFrame();
  host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 2);

  float page_scale = 1.5f;
  // Zoom in, since the fixed container is the outer viewport, the delta should
  // not be scaled.
  host_impl_->active_tree()->PushPageScaleFromMainThread(page_scale, 1, 2);

  gfx::Vector2dF top_controls_scroll_delta(0, 20);
  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), top_controls_scroll_delta,
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);

  // Scroll down, the browser controls hiding should expand the viewport size so
  // the delta should be equal to the scroll distance.
  host_impl_->browser_controls_manager()->ScrollBegin();
  host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta);
  EXPECT_FLOAT_EQ(top_controls_height_ - top_controls_scroll_delta.y(),
                  host_impl_->browser_controls_manager()->ContentTopOffset());

  auto* property_trees = host_impl_->active_tree()->property_trees();
  EXPECT_FLOAT_EQ(top_controls_scroll_delta.y(),
                  property_trees->outer_viewport_container_bounds_delta().y());
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  // Scroll past the maximum extent. The delta shouldn't be greater than the
  // browser controls height.
  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), top_controls_scroll_delta,
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  host_impl_->browser_controls_manager()->ScrollBegin();
  host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta);
  host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta);
  host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta);
  EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_VECTOR2DF_EQ(gfx::Vector2dF(0, top_controls_height_),
                      property_trees->outer_viewport_container_bounds_delta());
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  // Scroll in the direction to make the browser controls show.
  EXPECT_EQ(
      ScrollThread::kScrollOnImplThread,
      GetInputHandler()
          .ScrollBegin(BeginState(gfx::Point(), -top_controls_scroll_delta,
                                  ui::ScrollInputType::kTouchscreen)
                           .get(),
                       ui::ScrollInputType::kTouchscreen)
          .thread);
  host_impl_->browser_controls_manager()->ScrollBegin();
  host_impl_->browser_controls_manager()->ScrollBy(-top_controls_scroll_delta);
  EXPECT_EQ(top_controls_scroll_delta.y(),
            host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_VECTOR2DF_EQ(
      gfx::Vector2dF(0, top_controls_height_ - top_controls_scroll_delta.y()),
      property_trees->outer_viewport_container_bounds_delta());
  host_impl_->browser_controls_manager()->ScrollEnd();
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);
}

// Push a browser controls ratio from the main thread that we didn't send as a
// delta and make sure that the ratio is clamped to the [0, 1] range.
TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsPushUnsentRatio) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100));
  DrawFrame();

  // Need SetDrawsContent so ScrollBegin's hit test finds an actual layer.
  LayerImpl* inner_scroll = InnerViewportScrollLayer();
  inner_scroll->SetDrawsContent(true);
  LayerImpl* outer_scroll = OuterViewportScrollLayer();
  outer_scroll->SetDrawsContent(true);

  host_impl_->active_tree()->PushBrowserControlsFromMainThread(1, 1);
  ASSERT_EQ(1.0f, host_impl_->active_tree()->CurrentTopControlsShownRatio());

  host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0.5f, 0.5f);
  ASSERT_EQ(0.5f, host_impl_->active_tree()->CurrentTopControlsShownRatio());

  host_impl_->active_tree()->PushBrowserControlsFromMainThread(0, 0);

  ASSERT_EQ(0, host_impl_->active_tree()->CurrentTopControlsShownRatio());
}

// Test that if a scrollable sublayer doesn't consume the scroll,
// browser controls should hide when scrolling down.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       BrowserControlsScrollableSublayer) {
  gfx::Size sub_content_size(100, 400);
  gfx::Size sub_content_layer_size(100, 300);
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(100, 50), gfx::Size(100, 100), gfx::Size(100, 100));
  DrawFrame();

  // Show browser controls
  EXPECT_EQ(1, host_impl_->active_tree()->CurrentTopControlsShownRatio());

  LayerImpl* outer_viewport_scroll_layer = OuterViewportScrollLayer();
  LayerImpl* child = AddLayerInActiveTree();

  child->SetHitTestOpaqueness(HitTestOpaqueness::kMixed);
  child->SetElementId(LayerIdToElementIdForTesting(child->id()));
  child->SetBounds(sub_content_size);
  child->SetDrawsContent(true);

  CopyProperties(outer_viewport_scroll_layer, child);
  CreateTransformNode(child);
  CreateScrollNode(child, sub_content_layer_size);
  UpdateDrawProperties(host_impl_->active_tree());

  // Scroll child to the limit.
  SetScrollOffsetDelta(child, gfx::Vector2dF(0, 100));

  // Scroll 25px to hide browser controls
  gfx::Vector2dF scroll_delta(0, 25);
  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), scroll_delta,
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  // Browser controls should be hidden
  EXPECT_EQ(scroll_delta.y(),
            top_controls_height_ -
                host_impl_->browser_controls_manager()->ContentTopOffset());
}

// Ensure setting the browser controls position explicitly using the setters on
// the TreeImpl correctly affects the browser controls manager and viewport
// bounds for the active tree.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       PositionBrowserControlsToActiveTreeExplicitly) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      layer_size_, layer_size_, layer_size_);
  DrawFrame();

  host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0, 0);
  host_impl_->active_tree()->top_controls_shown_ratio()->PushMainToPending(
      30 / top_controls_height_);
  host_impl_->active_tree()->top_controls_shown_ratio()->PushPendingToActive();
  EXPECT_FLOAT_EQ(30,
                  host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_FLOAT_EQ(-20,
                  host_impl_->browser_controls_manager()->ControlsTopOffset());

  host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0, 0);
  EXPECT_FLOAT_EQ(0,
                  host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_FLOAT_EQ(-50,
                  host_impl_->browser_controls_manager()->ControlsTopOffset());

  host_impl_->DidChangeBrowserControlsPosition();

  auto* property_trees = host_impl_->active_tree()->property_trees();
  EXPECT_EQ(gfx::Vector2dF(0, 50),
            property_trees->inner_viewport_container_bounds_delta());
}

// Ensure setting the browser controls position explicitly using the setters on
// the TreeImpl correctly affects the browser controls manager and viewport
// bounds for the pending tree.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       PositionBrowserControlsToPendingTreeExplicitly) {
  EnsureSyncTree();
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      host_impl_->sync_tree(), layer_size_, layer_size_, layer_size_);

  // Changing SetCurrentBrowserControlsShownRatio is one way to cause the
  // pending tree to update it's viewport.
  host_impl_->SetCurrentBrowserControlsShownRatio(0, 0);
  EXPECT_FLOAT_EQ(top_controls_height_,
                  host_impl_->sync_tree()
                      ->property_trees()
                      ->inner_viewport_container_bounds_delta()
                      .y());
  host_impl_->SetCurrentBrowserControlsShownRatio(0.5f, 0.5f);
  EXPECT_FLOAT_EQ(0.5f * top_controls_height_,
                  host_impl_->sync_tree()
                      ->property_trees()
                      ->inner_viewport_container_bounds_delta()
                      .y());
  host_impl_->SetCurrentBrowserControlsShownRatio(1, 1);
  EXPECT_FLOAT_EQ(0, host_impl_->sync_tree()
                         ->property_trees()
                         ->inner_viewport_container_bounds_delta()
                         .y());

  // Pushing changes from the main thread is the second way. These values are
  // added to the 1 set above.
  host_impl_->sync_tree()->PushBrowserControlsFromMainThread(-0.5f, -0.5f);
  EXPECT_FLOAT_EQ(0.5f * top_controls_height_,
                  host_impl_->sync_tree()
                      ->property_trees()
                      ->inner_viewport_container_bounds_delta()
                      .y());
  host_impl_->sync_tree()->PushBrowserControlsFromMainThread(-1, -1);
  EXPECT_FLOAT_EQ(top_controls_height_,
                  host_impl_->sync_tree()
                      ->property_trees()
                      ->inner_viewport_container_bounds_delta()
                      .y());
}

// Test that the top_controls delta and sent delta are appropriately
// applied on sync tree activation. The total browser controls offset shouldn't
// change after the activation.
TEST_P(LayerTreeHostImplBrowserControlsTest, ApplyDeltaOnTreeActivation) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      layer_size_, layer_size_, layer_size_);
  DrawFrame();

  host_impl_->active_tree()->top_controls_shown_ratio()->PushMainToPending(
      20 / top_controls_height_);
  host_impl_->active_tree()->top_controls_shown_ratio()->PushPendingToActive();
  host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(
      15 / top_controls_height_, 15 / top_controls_height_);
  host_impl_->active_tree()->top_controls_shown_ratio()->PullDeltaForMainThread(
      /* next_bmf */ false);
  host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0, 0);
  EnsureSyncTree();
  host_impl_->sync_tree()->PushBrowserControlsFromMainThread(
      15 / top_controls_height_, 15 / top_controls_height_);

  host_impl_->DidChangeBrowserControlsPosition();
  auto* property_trees = host_impl_->active_tree()->property_trees();
  EXPECT_EQ(gfx::Vector2dF(0, 50),
            property_trees->inner_viewport_container_bounds_delta());
  EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset());

  host_impl_->ActivateSyncTree();

  EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_EQ(CommitsToActiveTree() ? gfx::Vector2dF(0, 50) : gfx::Vector2dF(),
            property_trees->inner_viewport_container_bounds_delta());
  EXPECT_FLOAT_EQ(
      -15, host_impl_->active_tree()->top_controls_shown_ratio()->Delta() *
               top_controls_height_);
  EXPECT_FLOAT_EQ(
      15, host_impl_->active_tree()->top_controls_shown_ratio()->ActiveBase() *
              top_controls_height_);
}

// Test that changing the browser controls layout height is correctly applied to
// the inner viewport container bounds. That is, the browser controls layout
// height is the amount that the inner viewport container was shrunk outside
// the compositor to accommodate the browser controls.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       BrowserControlsLayoutHeightChanged) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      layer_size_, layer_size_, layer_size_);
  DrawFrame();

  EnsureSyncTree();
  host_impl_->sync_tree()->PushBrowserControlsFromMainThread(1, 1);
  host_impl_->sync_tree()->SetBrowserControlsParams(
      {top_controls_height_, 0, 0, 0, false, true});

  host_impl_->active_tree()->top_controls_shown_ratio()->PushMainToPending(1);
  host_impl_->active_tree()->top_controls_shown_ratio()->PushPendingToActive();
  host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0, 0);

  host_impl_->DidChangeBrowserControlsPosition();
  auto* property_trees = host_impl_->active_tree()->property_trees();
  EXPECT_EQ(gfx::Vector2dF(0, 50),
            property_trees->inner_viewport_container_bounds_delta());
  EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset());

  host_impl_->ActivateSyncTree();

  EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset());

  // The total bounds should remain unchanged since the bounds delta should
  // account for the difference between the layout height and the current
  // browser controls offset.
  EXPECT_EQ(CommitsToActiveTree() ? gfx::Vector2dF(0, 50) : gfx::Vector2dF(),
            property_trees->inner_viewport_container_bounds_delta());

  host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(1, 1);
  host_impl_->DidChangeBrowserControlsPosition();

  EXPECT_EQ(1, host_impl_->browser_controls_manager()->TopControlsShownRatio());
  EXPECT_EQ(50, host_impl_->browser_controls_manager()->TopControlsHeight());
  EXPECT_EQ(50, host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_EQ(gfx::Vector2dF(),
            property_trees->inner_viewport_container_bounds_delta());
}

// Test that showing/hiding the browser controls when the viewport is fully
// scrolled doesn't incorrectly change the viewport offset due to clamping from
// changing viewport bounds.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       BrowserControlsViewportOffsetClamping) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400));
  DrawFrame();

  EXPECT_EQ(1, host_impl_->active_tree()->CurrentTopControlsShownRatio());

  LayerImpl* outer_scroll = OuterViewportScrollLayer();
  LayerImpl* inner_scroll = InnerViewportScrollLayer();

  // Scroll the viewports to max scroll offset.
  SetScrollOffsetDelta(outer_scroll, gfx::Vector2dF(0, 200));
  SetScrollOffsetDelta(inner_scroll, gfx::Vector2dF(100, 100));

  gfx::PointF viewport_offset = host_impl_->active_tree()->TotalScrollOffset();
  EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(), viewport_offset);

  // Hide the browser controls by 25px.
  gfx::Vector2dF scroll_delta(0, 25);
  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), scroll_delta,
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen));

  // scrolling down at the max extents no longer hides the browser controls
  EXPECT_EQ(1, host_impl_->active_tree()->CurrentTopControlsShownRatio());

  // forcefully hide the browser controls by 25px
  host_impl_->browser_controls_manager()->ScrollBy(scroll_delta);
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_FLOAT_EQ(
      scroll_delta.y(),
      top_controls_height_ -
          host_impl_->browser_controls_manager()->ContentTopOffset());

  // We should still be fully scrolled.
  EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(),
            host_impl_->active_tree()->TotalScrollOffset());

  viewport_offset = host_impl_->active_tree()->TotalScrollOffset();

  // Bring the browser controls down by 25px.
  scroll_delta = gfx::Vector2dF(0, -25);
  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), scroll_delta,
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  // The viewport offset shouldn't have changed.
  EXPECT_EQ(viewport_offset, host_impl_->active_tree()->TotalScrollOffset());

  // Scroll the viewports to max scroll offset.
  SetScrollOffsetDelta(outer_scroll, gfx::Vector2dF(0, 200));
  SetScrollOffsetDelta(inner_scroll, gfx::Vector2dF(100, 100));
  EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(),
            host_impl_->active_tree()->TotalScrollOffset());
}

// Test that the browser controls coming in and out maintains the same aspect
// ratio between the inner and outer viewports.
TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsAspectRatio) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400));
  host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.5f, 2);
  DrawFrame();

  EXPECT_FLOAT_EQ(top_controls_height_,
                  host_impl_->browser_controls_manager()->ContentTopOffset());

  gfx::Vector2dF scroll_delta(0, 25);
  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), scroll_delta,
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen));
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_FLOAT_EQ(
      scroll_delta.y(),
      top_controls_height_ -
          host_impl_->browser_controls_manager()->ContentTopOffset());

  // Browser controls were hidden by 25px so the inner viewport should have
  // expanded by that much.
  auto* property_trees = host_impl_->active_tree()->property_trees();
  EXPECT_EQ(gfx::Vector2dF(0, 25),
            property_trees->inner_viewport_container_bounds_delta());

  // Outer viewport should match inner's aspect ratio. The bounds are ceiled.
  float aspect_ratio = 100.0f / 125.0f;
  auto expected = gfx::ToCeiledSize(gfx::SizeF(200, 200 / aspect_ratio));
  EXPECT_EQ(expected, InnerViewportScrollLayer()->bounds());
}

TEST_P(LayerTreeHostImplBrowserControlsTest, NoShrinkNotUserScrollable) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(100, 100), gfx::Size(100, 100), gfx::Size(100, 200));

  GetScrollNode(OuterViewportScrollLayer())->user_scrollable_vertical = false;
  DrawFrame();

  gfx::Vector2dF delta(0, 15);
  ui::ScrollInputType type = ui::ScrollInputType::kTouchscreen;
  auto& handler = GetInputHandler();

  handler.ScrollBegin(BeginState(gfx::Point(), delta, type).get(), type);
  handler.ScrollUpdate(UpdateState(gfx::Point(), delta, type));
  handler.ScrollEnd(false, std::nullopt);

  // Outer viewport has overflow, but is not user-scrollable. Make sure the
  // browser controls did not shrink when we tried to scroll.
  EXPECT_EQ(top_controls_height_,
            host_impl_->browser_controls_manager()->ContentTopOffset());
}

// Test that scrolling the outer viewport affects the browser controls.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       BrowserControlsScrollOuterViewport) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400));
  DrawFrame();

  EXPECT_EQ(top_controls_height_,
            host_impl_->browser_controls_manager()->ContentTopOffset());

  // Send a gesture scroll that will scroll the outer viewport, make sure the
  // browser controls get scrolled.
  gfx::Vector2dF scroll_delta(0, 15);
  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), scroll_delta,
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen));

  EXPECT_EQ(OuterViewportScrollLayer()->scroll_tree_index(),
            host_impl_->CurrentlyScrollingNode()->id);
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_FLOAT_EQ(
      scroll_delta.y(),
      top_controls_height_ -
          host_impl_->browser_controls_manager()->ContentTopOffset());

  scroll_delta = gfx::Vector2dF(0, 50);
  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), scroll_delta,
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen));

  EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_EQ(OuterViewportScrollLayer()->scroll_tree_index(),
            host_impl_->CurrentlyScrollingNode()->id);

  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  // Position the viewports such that the inner viewport will be scrolled.
  gfx::Vector2dF inner_viewport_offset(0, 25);
  SetScrollOffsetDelta(OuterViewportScrollLayer(), gfx::Vector2dF());
  SetScrollOffsetDelta(InnerViewportScrollLayer(), inner_viewport_offset);

  scroll_delta = gfx::Vector2dF(0, -65);
  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), scroll_delta,
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen));

  EXPECT_EQ(top_controls_height_,
            host_impl_->browser_controls_manager()->ContentTopOffset());
  EXPECT_FLOAT_EQ(
      inner_viewport_offset.y() + (scroll_delta.y() + top_controls_height_),
      ScrollDelta(InnerViewportScrollLayer()).y());

  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);
}

TEST_P(LayerTreeHostImplBrowserControlsTest,
       ScrollNonScrollableRootWithBrowserControls) {
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      layer_size_, layer_size_, layer_size_);
  DrawFrame();

  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 50),
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);

  host_impl_->browser_controls_manager()->ScrollBegin();
  host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 50));
  host_impl_->browser_controls_manager()->ScrollEnd();
  EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset());
  // Now that browser controls have moved, expect the clip to resize.
  auto* property_trees = host_impl_->active_tree()->property_trees();
  EXPECT_EQ(gfx::Vector2dF(0, 50),
            property_trees->inner_viewport_container_bounds_delta());

  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, -25),
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);

  float scroll_increment_y = -25;
  host_impl_->browser_controls_manager()->ScrollBegin();
  host_impl_->browser_controls_manager()->ScrollBy(
      gfx::Vector2dF(0, scroll_increment_y));
  EXPECT_FLOAT_EQ(-scroll_increment_y,
                  host_impl_->browser_controls_manager()->ContentTopOffset());
  // Now that browser controls have moved, expect the clip to resize.
  EXPECT_EQ(gfx::Vector2dF(0, 25),
            property_trees->inner_viewport_container_bounds_delta());

  host_impl_->browser_controls_manager()->ScrollBy(
      gfx::Vector2dF(0, scroll_increment_y));
  host_impl_->browser_controls_manager()->ScrollEnd();
  EXPECT_FLOAT_EQ(-2 * scroll_increment_y,
                  host_impl_->browser_controls_manager()->ContentTopOffset());
  // Now that browser controls have moved, expect the clip to resize.
  EXPECT_EQ(gfx::Vector2dF(),
            property_trees->inner_viewport_container_bounds_delta());

  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  // Verify the layer is once-again non-scrollable.
  EXPECT_EQ(gfx::PointF(), MaxScrollOffset(InnerViewportScrollLayer()));

  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 10),
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
}

// Tests that activating a pending tree while there's a bounds_delta on the
// viewport layers from browser controls doesn't cause a scroll jump. This bug
// was occurring because the UpdateViewportContainerSizes was being called
// before the property trees were updated with the bounds_delta.
// crbug.com/597266.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       ViewportBoundsDeltaOnTreeActivation) {
  // This test needs to create both the pending tree and the active tree.
  if (CommitsToActiveTree()) {
    GTEST_SKIP();
  }

  const gfx::Size inner_viewport_size(1000, 1000);
  const gfx::Size outer_viewport_size(1000, 1000);
  const gfx::Size content_size(2000, 2000);

  // Initialization
  {
    SetupBrowserControlsAndScrollLayerWithVirtualViewport(
        inner_viewport_size, outer_viewport_size, content_size);
    host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 1);

    // Start off with the browser controls hidden on both main and impl.
    host_impl_->active_tree()->SetBrowserControlsParams(
        {top_controls_height_, 0, 0, 0, false, false});
    host_impl_->active_tree()->PushBrowserControlsFromMainThread(0, 0);

    CreatePendingTree();
    SetupBrowserControlsAndScrollLayerWithVirtualViewport(
        host_impl_->pending_tree(), inner_viewport_size, outer_viewport_size,
        content_size);
    host_impl_->pending_tree()->SetBrowserControlsParams(
        {top_controls_height_, 0, 0, 0, false, false});
    UpdateDrawProperties(host_impl_->pending_tree());

    // Fully scroll the viewport.
    GetInputHandler().ScrollBegin(
        BeginState(gfx::Point(75, 75), gfx::Vector2dF(0, 2000),
                   ui::ScrollInputType::kTouchscreen)
            .get(),
        ui::ScrollInputType::kTouchscreen);
    GetInputHandler().ScrollUpdate(
        UpdateState(gfx::Point(), gfx::Vector2d(0, 2000),
                    ui::ScrollInputType::kTouchscreen));
    GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);
  }

  LayerImpl* outer_scroll = OuterViewportScrollLayer();

  ASSERT_FLOAT_EQ(0,
                  host_impl_->browser_controls_manager()->ContentTopOffset());
  ASSERT_EQ(1000, MaxScrollOffset(outer_scroll).y());
  ASSERT_EQ(1000, CurrentScrollOffset(outer_scroll).y());

  // Kick off an animation to show the browser controls.
  host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
      BrowserControlsState::kBoth, BrowserControlsState::kShown, true,
      std::nullopt);
  base::TimeTicks start_time = base::TimeTicks::Now();
  viz::BeginFrameArgs begin_frame_args =
      viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

  // The first animation frame will not produce any delta, it will establish
  // the animation.
  {
    begin_frame_args.frame_time = start_time;
    begin_frame_args.frame_id.sequence_number++;
    host_impl_->WillBeginImplFrame(begin_frame_args);
    host_impl_->Animate();
    host_impl_->UpdateAnimationState(true);
    host_impl_->DidFinishImplFrame(begin_frame_args);
    float delta =
        host_impl_->active_tree()->top_controls_shown_ratio()->Delta();
    ASSERT_EQ(delta, 0);
  }

  // Pump an animation frame to put some delta in the browser controls.
  {
    begin_frame_args.frame_time = start_time + base::Milliseconds(50);
    begin_frame_args.frame_id.sequence_number++;
    host_impl_->WillBeginImplFrame(begin_frame_args);
    host_impl_->Animate();
    host_impl_->UpdateAnimationState(true);
    host_impl_->DidFinishImplFrame(begin_frame_args);
  }

  // Pull the browser controls delta and get it back to the pending tree so that
  // when we go to activate the pending tree we cause a change to browser
  // controls.
  {
    float delta =
        host_impl_->active_tree()->top_controls_shown_ratio()->Delta();
    ASSERT_GT(delta, 0);
    ASSERT_LT(delta, 1);
    host_impl_->active_tree()
        ->top_controls_shown_ratio()
        ->PullDeltaForMainThread(/* next_bmf */ false);
    host_impl_->active_tree()->top_controls_shown_ratio()->PushMainToPending(
        delta);
  }

  // 200 is the kShowHideMaxDurationMs value from browser_controls_manager.cc so
  // the browser controls should be fully animated in this frame.
  {
    begin_frame_args.frame_time = start_time + base::Milliseconds(200);
    begin_frame_args.frame_id.sequence_number++;
    host_impl_->WillBeginImplFrame(begin_frame_args);
    host_impl_->Animate();
    host_impl_->UpdateAnimationState(true);
    host_impl_->DidFinishImplFrame(begin_frame_args);

    ASSERT_EQ(50, host_impl_->browser_controls_manager()->ContentTopOffset());
    ASSERT_EQ(1050, MaxScrollOffset(outer_scroll).y());
    // NEAR because clip layer bounds are truncated in MaxScrollOffset so we
    // lose some precision in the intermediate animation steps.
    ASSERT_NEAR(1050, CurrentScrollOffset(outer_scroll).y(), 1);
  }

  // Activate the pending tree which should have the same scroll value as the
  // active tree.
  {
    host_impl_->pending_tree()
        ->property_trees()
        ->scroll_tree_mutable()
        .SetScrollOffsetDeltaForTesting(outer_scroll->element_id(),
                                        gfx::Vector2dF(0, 1050));
    host_impl_->ActivateSyncTree();

    // Make sure we don't accidentally clamp the outer offset based on a bounds
    // delta that hasn't yet been updated.
    EXPECT_NEAR(1050, CurrentScrollOffset(outer_scroll).y(), 1);
  }
}

// Tests that when we set a child scroller (e.g. a scrolling div) as the outer
// viewport, scrolling it controls the browser controls.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       ReplacedOuterViewportScrollsBrowserControls) {
  const gfx::Size scroll_content_size(400, 400);
  const gfx::Size root_layer_size(200, 200);
  const gfx::Size viewport_size(100, 100);

  SetupBrowserControlsAndScrollLayerWithVirtualViewport(
      viewport_size, viewport_size, root_layer_size);

  LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
  LayerImpl* outer_scroll = OuterViewportScrollLayer();

  // Initialization: Add a child scrolling layer to the outer scroll layer and
  // set its scroll layer as the outer viewport. This simulates setting a
  // scrolling element as the root scroller on the page.
  LayerImpl* clip_layer = AddLayerInActiveTree();
  clip_layer->SetBounds(root_layer_size);
  CopyProperties(outer_scroll, clip_layer);
  CreateClipNode(clip_layer);
  LayerImpl* scroll_layer =
      AddScrollableLayer(clip_layer, root_layer_size, scroll_content_size);
  GetScrollNode(scroll_layer)->scrolls_outer_viewport = true;

  auto viewport_property_ids = layer_tree_impl->ViewportPropertyIdsForTesting();
  viewport_property_ids.outer_clip = clip_layer->clip_tree_index();
  viewport_property_ids.outer_scroll = scroll_layer->scroll_tree_index();
  layer_tree_impl->SetViewportPropertyIds(viewport_property_ids);
  DrawFrame();

  ASSERT_EQ(1, layer_tree_impl->CurrentTopControlsShownRatio());

  // Scrolling should scroll the child content and the browser controls. The
  // original outer viewport should get no scroll.
  {
    GetInputHandler().ScrollBegin(
        BeginState(gfx::Point(0, 0), gfx::Vector2dF(100, 100),
                   ui::ScrollInputType::kTouchscreen)
            .get(),
        ui::ScrollInputType::kTouchscreen);
    GetInputHandler().ScrollUpdate(
        UpdateState(gfx::Point(0, 0), gfx::Vector2dF(100, 100),
                    ui::ScrollInputType::kTouchscreen));
    GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

    EXPECT_POINTF_EQ(gfx::PointF(0, 0), CurrentScrollOffset(outer_scroll));
    EXPECT_POINTF_EQ(gfx::PointF(100, 50), CurrentScrollOffset(scroll_layer));
    EXPECT_EQ(0, layer_tree_impl->CurrentTopControlsShownRatio());
  }
}

// Check if LayerTreeImpl::GetActivelyScrollingType() returns kPrecise even
// when BrowserControl is consuming ScrollUpdate.
TEST_P(LayerTreeHostImplBrowserControlsTest,
       BrowserControlsActivelyScrollingType) {
  // This test creates layer on the active tree with a raster source, which
  // doesn't work if we commit to the pending tree.
  if (!CommitsToActiveTree()) {
    GTEST_SKIP();
  }

  gfx::Size inner_size = gfx::Size(100, 100);
  gfx::Size outer_size = gfx::Size(100, 100);
  gfx::Size content_size = gfx::Size(100, 200);
  SetupBrowserControlsAndScrollLayerWithVirtualViewport(inner_size, outer_size,
                                                        content_size);

  LayerTreeImpl* active_tree = host_impl_->active_tree();

  // Create a content layer beneath the outer viewport scroll layer.
  scoped_refptr<FakeRasterSource> raster_source(
      FakeRasterSource::CreateFilled(content_size));

  auto* picture_layer =
      AddLayer<FakePictureLayerImpl>(host_impl_->active_tree(), raster_source);
  CopyProperties(OuterViewportScrollLayer(), picture_layer);
  picture_layer->SetBounds(content_size);
  picture_layer->SetDrawsContent(true);
  picture_layer->SetNeedsPushProperties();
  active_tree->PushPageScaleFromMainThread(1.0f, 1.0f, 2.0f);
  DrawFrame();

  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 50),
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  // shownratio == 1
  EXPECT_EQ(1, host_impl_->active_tree()->CurrentTopControlsShownRatio());
  EXPECT_EQ(host_impl_->active_tree()->GetActivelyScrollingType(),
            ActivelyScrollingType::kNone);

  // 0 < shownratio <1
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), gfx::Vector2dF(0, 25), ui::ScrollInputType::kTouchscreen));
  EXPECT_GT(host_impl_->active_tree()->CurrentTopControlsShownRatio(), 0);
  EXPECT_LT(host_impl_->active_tree()->CurrentTopControlsShownRatio(), 1);
  EXPECT_EQ(host_impl_->active_tree()->GetActivelyScrollingType(),
            ActivelyScrollingType::kPrecise);

  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), gfx::Vector2dF(0, 30), ui::ScrollInputType::kTouchscreen));
  // now shownratio == 0
  EXPECT_EQ(0, host_impl_->active_tree()->CurrentTopControlsShownRatio());
  EXPECT_EQ(host_impl_->active_tree()->GetActivelyScrollingType(),
            ActivelyScrollingType::kPrecise);

  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);
  // scroll end, shownratio == 0
  EXPECT_EQ(0, host_impl_->active_tree()->CurrentTopControlsShownRatio());
  EXPECT_EQ(host_impl_->active_tree()->GetActivelyScrollingType(),
            ActivelyScrollingType::kNone);
}

INSTANTIATE_CLIENT_MODE_TREE_TEST_P(LayerTreeHostImplBrowserControlsTest);

// TODO(crbug.com/458776836): Review memory limit related cc_unittests for
// TreesInViz Service mode
TEST_P(LayerTreeHostImplWithImplicitLimitsTest, ImplicitMemoryLimits) {
  // Set up a memory policy and percentages which could cause
  // 32-bit integer overflows.
  ManagedMemoryPolicy mem_policy(300 * 1024 * 1024);  // 300MB

  // Verify implicit limits are calculated correctly with no overflows
  host_impl_->SetMemoryPolicy(mem_policy);
  EXPECT_EQ(host_impl_->global_tile_state().hard_memory_limit_in_bytes,
            300u * 1024u * 1024u);
  EXPECT_EQ(host_impl_->global_tile_state().soft_memory_limit_in_bytes,
            150u * 1024u * 1024u);
}

INSTANTIATE_CLIENT_MODE_TREE_TEST_P(LayerTreeHostImplWithImplicitLimitsTest);
void ClearMainThreadDeltasForTesting(LayerTreeHostImpl* host) {
  host->active_tree()->ApplySentScrollAndScaleDeltasFromAbortedCommit(
      /* next_bmf */ false, /* main_frame_applied_deltas */ false);
}

gfx::PresentationFeedback ExampleFeedback() {
  auto feedback = gfx::PresentationFeedback(
      base::TimeTicks() + base::Milliseconds(42), base::Microseconds(18),
      gfx::PresentationFeedback::Flags::kVSync |
          gfx::PresentationFeedback::Flags::kHWCompletion);
#if BUILDFLAG(IS_MAC)
  feedback.ca_layer_error_code = gfx::kCALayerFailedQuadBlendMode;
#endif
  return feedback;
}

// Test fixture that enables only Pending tree commit modes,
// CommitToPendingTree and CommitToPendingTreeTreesInVizClient.
class PendingTreeLayerTreeHostImplTest : public LayerTreeHostImplTest {
 protected:
  void SetupScrollDelayTest() {
    CreateHostImpl(DefaultSettings(), CreateLayerTreeFrameSink());
    SetupRootLayer<SolidColorLayerImpl>(host_impl_->active_tree(),
                                        gfx::Size(10, 10));
    UpdateDrawProperties(host_impl_->active_tree());
    EXPECT_EQ(first_scroll_observed, 0);
  }

  void QueueLatencyInfoSwapPromise(ui::LatencyComponentType type,
                                   int trace_id) {
    ui::LatencyInfo latency_info;
    latency_info.set_trace_id(trace_id);
    latency_info.AddLatencyNumber(type);
    std::unique_ptr<SwapPromise> swap_promise(
        new LatencyInfoSwapPromise(latency_info));
    host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise));
  }

  void DrawAndPresentFrame(uint32_t frame_token, bool damage = true) {
    if (damage) {
      host_impl_->SetFullViewportDamage();
      host_impl_->SetNeedsRedraw(/*animation_only=*/false,
                                 /*skip_if_inside_draw=*/false);
    }
    DrawFrame();
    viz::FrameTimingDetails mock_details;
    mock_details.presentation_feedback = ExampleFeedback();
    host_impl_->DidPresentCompositorFrame(frame_token, mock_details);
  }

  void RunPaintWorkletCommitTest() {
    auto painter_owned = std::make_unique<TestPaintWorkletLayerPainter>();
    TestPaintWorkletLayerPainter* painter = painter_owned.get();
    host_impl_->SetPaintWorkletLayerPainter(std::move(painter_owned));

    client_host_impl(host_impl_.get())->CreatePendingTree();
    auto* root = SetupRootLayer<PictureLayerImpl>(host_impl_->pending_tree(),
                                                  gfx::Size(100, 100));
    root->SetNeedsPushProperties();

    scoped_refptr<RasterSource> raster_source_with_pws =
        FakeRasterSource::CreateFilledWithPaintWorklet(root->bounds());
    root->SetRasterSourceForTesting(raster_source_with_pws);
    UpdateDrawProperties(host_impl_->pending_tree());

    did_prepare_tiles_ = false;
    client_host_impl(host_impl_.get())->CommitComplete();
    EXPECT_FALSE(did_prepare_tiles_);

    ASSERT_EQ(root->GetPaintWorkletRecordMap().size(), 1u);
    scoped_refptr<const PaintWorkletInput> input =
        root->GetPaintWorkletRecordMap().begin()->first;
    int worklet_id = input->WorkletId();

    PaintWorkletJob painted_job(worklet_id, input, {});
    PaintRecord record;
    painted_job.SetOutput(record);

    auto painted_job_vector = base::MakeRefCounted<PaintWorkletJobVector>();
    painted_job_vector->data.push_back(std::move(painted_job));
    PaintWorkletJobMap painted_job_map;
    painted_job_map[worklet_id] = std::move(painted_job_vector);

    std::move(painter->TakeDoneCallback()).Run(std::move(painted_job_map));
    EXPECT_TRUE(root->GetPaintWorkletRecordMap()
                    .find(input)
                    ->second.second->EqualsForTesting(record));
  }
};

INSTANTIATE_COMMIT_TO_PENDING_TREE_TEST_P(PendingTreeLayerTreeHostImplTest);

TEST_P(PendingTreeLayerTreeHostImplTest, SyncedScrollAbortedCommit) {
  CreateHostImpl(DefaultSettings(), CreateLayerTreeFrameSink());
  CreatePendingTree();
  gfx::PointF scroll_offset(20, 30);
  auto* root = SetupDefaultRootLayer(gfx::Size(110, 110));
  auto& scroll_tree =
      root->layer_tree_impl()->property_trees()->scroll_tree_mutable();
  root->SetHitTestOpaqueness(HitTestOpaqueness::kMixed);
  std::unique_ptr<AnimationHost> main_thread_animation_host(
      AnimationHost::CreateForTesting(ThreadInstance::kMain));

  // SyncedProperty should be created on the pending tree and then pushed to the
  // active tree, to avoid bifurcation. Simulate commit by pushing base scroll
  // offsets to pending tree.
  PushScrollOffsetsToPendingTree({{root->element_id(), gfx::PointF(0, 0)}});
  ClearNonScrollSyncTreeDeltasForTesting();
  host_impl_->active_tree()
      ->property_trees()
      ->scroll_tree_mutable()
      .PushScrollUpdatesFromPendingTree(
          host_impl_->pending_tree()->property_trees(),
          host_impl_->active_tree());

  CreateScrollNode(root, gfx::Size(10, 10));
  auto* synced_scroll = scroll_tree.GetSyncedScrollOffset(root->element_id());
  ASSERT_TRUE(synced_scroll);
  scroll_tree.UpdateScrollOffsetBaseForTesting(root->element_id(),
                                               scroll_offset);
  UpdateDrawProperties(host_impl_->active_tree());

  gfx::Vector2dF scroll_delta(11, -15);
  root->ScrollBy(scroll_delta);
  EXPECT_EQ(scroll_delta, synced_scroll->UnsentDelta());
  // Null mutator_host implies non-pipelined main frame
  host_impl_->ProcessCompositorDeltas(/* mutator_host */ nullptr);
  EXPECT_EQ(scroll_delta, synced_scroll->reflected_delta_in_main_tree());
  EXPECT_EQ(gfx::Vector2dF(),
            synced_scroll->next_reflected_delta_in_main_tree());

  std::unique_ptr<CompositorCommitData> commit_data2;
  gfx::Vector2dF scroll_delta2(-5, 27);
  root->ScrollBy(scroll_delta2);
  EXPECT_EQ(scroll_delta2, synced_scroll->UnsentDelta());
  // Non-null mutator_host implies pipelined main frame
  host_impl_->ProcessCompositorDeltas(main_thread_animation_host.get());
  EXPECT_EQ(scroll_delta, synced_scroll->reflected_delta_in_main_tree());
  EXPECT_EQ(scroll_delta2, synced_scroll->next_reflected_delta_in_main_tree());

  // Simulate aborting the second main frame. Scroll deltas applied by the
  // second frame should be combined with delta from first frame.
  root->layer_tree_impl()->ApplySentScrollAndScaleDeltasFromAbortedCommit(
      /* next_bmf */ true, /* main_frame_applied_deltas */ true);
  EXPECT_EQ(scroll_delta + scroll_delta2,
            synced_scroll->reflected_delta_in_main_tree());
  EXPECT_EQ(gfx::Vector2dF(),
            synced_scroll->next_reflected_delta_in_main_tree());

  // Send a third main frame, pipelined behind the first.
  gfx::Vector2dF scroll_delta3(-2, -13);
  root->ScrollBy(scroll_delta3);
  EXPECT_EQ(scroll_delta3, synced_scroll->UnsentDelta());
  host_impl_->ProcessCompositorDeltas(main_thread_animation_host.get());
  EXPECT_EQ(scroll_delta + scroll_delta2,
            synced_scroll->reflected_delta_in_main_tree());
  EXPECT_EQ(scroll_delta3, synced_scroll->next_reflected_delta_in_main_tree());

  // Simulate commit of the first frame
  PushScrollOffsetsToPendingTree(
      {{root->element_id(), scroll_offset + scroll_delta + scroll_delta2}});
  EXPECT_EQ(scroll_offset + scroll_delta + scroll_delta2,
            synced_scroll->PendingBase());
  EXPECT_EQ(scroll_delta3, synced_scroll->PendingDelta());
  EXPECT_EQ(scroll_delta3, synced_scroll->reflected_delta_in_main_tree());
  EXPECT_EQ(gfx::Vector2dF(),
            synced_scroll->next_reflected_delta_in_main_tree());
}

TEST_P(PendingTreeLayerTreeHostImplTest, ActivateTreeScrollingNodeDisappeared) {
  SetupViewportLayersOuterScrolls(gfx::Size(100, 100), gfx::Size(1000, 1000));

  auto status = GetInputHandler().ScrollBegin(
      BeginState(gfx::Point(30, 30), gfx::Vector2d(0, 10),
                 ui::ScrollInputType::kWheel)
          .get(),
      ui::ScrollInputType::kWheel);
  EXPECT_EQ(ScrollThread::kScrollOnImplThread, status.thread);
  EXPECT_TRUE(status.main_thread_repaint_reasons.empty());
  EXPECT_TRUE(status.main_thread_hit_test_reasons.empty());
  GetInputHandler().ScrollUpdate(UpdateState(gfx::Point(), gfx::Vector2d(0, 10),
                                             ui::ScrollInputType::kWheel));
  EXPECT_TRUE(host_impl_->active_tree()->CurrentlyScrollingNode());

  // Create the pending tree containing only the root layer.
  CreatePendingTree();
  PropertyTrees pending_property_trees;
  pending_property_trees.set_sequence_number(
      host_impl_->active_tree()->property_trees()->sequence_number() + 1);
  host_impl_->pending_tree()->SetPropertyTrees(pending_property_trees,
                                               ViewportPropertyIds());
  SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), gfx::Size(100, 100));
  host_impl_->ActivateSyncTree();

  // The scroll should stop.
  EXPECT_FALSE(host_impl_->active_tree()->CurrentlyScrollingNode());
}

TEST_P(PendingTreeLayerTreeHostImplTest, AnimationSchedulingPendingTree) {
  CreatePendingTree();
  auto* root =
      SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), gfx::Size(50, 50));
  root->SetNeedsPushProperties();

  auto* child = AddLayer<LayerImpl>(host_impl_->pending_tree());
  child->SetBounds(gfx::Size(10, 10));
  child->SetDrawsContent(true);
  child->SetNeedsPushProperties();

  host_impl_->pending_tree()->SetElementIdsForTesting();
  CopyProperties(root, child);
  CreateTransformNode(child);

  scoped_refptr<Animation> animation =
      Animation::Create(AnimationIdProvider::NextAnimationId());
  timeline()->AttachAnimation(animation);
  animation->AttachElement(child->element_id());
  AddAnimatedTransformToAnimation(animation.get(), 10.0, 3, 0);
  animation->GetKeyframeModel(TargetProperty::TRANSFORM)
      ->set_affects_active_elements(false);
  UpdateDrawProperties(host_impl_->pending_tree());

  EXPECT_FALSE(did_request_next_frame_);
  EXPECT_FALSE(did_request_redraw_);
  EXPECT_FALSE(did_request_commit_);

  client_host_impl(host_impl_.get())->AnimatePendingTreeAfterCommit();

  // An animation exists on the pending layer. Doing
  // AnimatePendingTreeAfterCommit() requests another frame.
  // In reality, animations without has_set_start_time() == true do not need to
  // be continuously ticked on the pending tree, so it should not request
  // another animation frame here. But we currently do so blindly if any
  // animation exists.
  EXPECT_TRUE(did_request_next_frame_);
  // The pending tree with an animation does not need to draw after animating.
  EXPECT_FALSE(did_request_redraw_);
  EXPECT_FALSE(did_request_commit_);

  did_request_next_frame_ = false;
  did_request_redraw_ = false;
  did_request_commit_ = false;

  host_impl_->ActivateSyncTree();

  // When the animation activates, we should request another animation frame
  // to keep the animation moving.
  EXPECT_TRUE(did_request_next_frame_);
  // On activation we don't need to request a redraw for the animation,
  // activating will draw on its own when it's ready.
  EXPECT_FALSE(did_request_redraw_);
  EXPECT_FALSE(did_request_commit_);
}

TEST_P(PendingTreeLayerTreeHostImplTest, AnimationSchedulingActiveTree) {
  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(50, 50));
  LayerImpl* child = AddLayerInActiveTree();
  child->SetBounds(gfx::Size(10, 10));
  child->SetDrawsContent(true);

  host_impl_->active_tree()->SetElementIdsForTesting();
  CopyProperties(root, child);
  CreateTransformNode(child);

  // Add a translate from 6,7 to 8,9.
  gfx::TransformOperations start;
  start.AppendTranslate(6, 7, 0);
  gfx::TransformOperations end;
  end.AppendTranslate(8, 9, 0);
  AddAnimatedTransformToElementWithAnimation(child->element_id(), timeline(),
                                             4.0, start, end);
  UpdateDrawProperties(host_impl_->active_tree());

  base::TimeTicks now = base::TimeTicks::Now();
  host_impl_->WillBeginImplFrame(
      viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2, now));

  // TODO(crbug.com/40443202): We always request a new frame and a draw for
  // animations that are on the pending tree, but we don't need to do that
  // unless they are waiting for some future time to start.
  EXPECT_TRUE(did_request_next_frame_);
  EXPECT_TRUE(did_request_redraw_);
  EXPECT_FALSE(did_request_commit_);
  did_request_next_frame_ = false;
  did_request_redraw_ = false;
  did_request_commit_ = false;

  host_impl_->ActivateAnimations();

  // On activating an animation, we should request another frame so that we'll
  // continue ticking the animation.
  EXPECT_TRUE(did_request_next_frame_);
  EXPECT_FALSE(did_request_redraw_);
  EXPECT_FALSE(did_request_commit_);
  did_request_next_frame_ = false;
  did_request_redraw_ = false;
  did_request_commit_ = false;

  // The next frame after activating, we'll tick the animation again.
  host_impl_->Animate();

  // An animation exists on the active layer. Doing Animate() requests another
  // frame after the current one.
  EXPECT_TRUE(did_request_next_frame_);
  // The animation should cause us to draw at the frame's deadline.
  EXPECT_TRUE(did_request_redraw_);
  EXPECT_FALSE(did_request_commit_);
}

TEST_P(PendingTreeLayerTreeHostImplTest,
       ScrollbarVisibilityChangeCausesRedrawAndCommit) {
  LayerTreeSettings settings = DefaultSettings();
  settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY;
  settings.scrollbar_fade_delay = base::Milliseconds(20);
  settings.scrollbar_fade_duration = base::Milliseconds(20);
  gfx::Size viewport_size(50, 50);
  gfx::Size content_size(100, 100);

  CreateHostImpl(settings, CreateLayerTreeFrameSink());
  CreatePendingTree();
  SetupViewportLayers(host_impl_->pending_tree(), viewport_size, content_size,
                      content_size);
  LayerImpl* scroll =
      host_impl_->pending_tree()->OuterViewportScrollLayerForTesting();
  auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
      host_impl_->pending_tree(), ScrollbarOrientation::kVertical, 10, 0,
      false);
  SetupScrollbarLayer(scroll, scrollbar);
  scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(90, 0));

  host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1);
  host_impl_->ActivateSyncTree();

  ScrollbarAnimationController* scrollbar_controller =
      host_impl_->ScrollbarAnimationControllerForElementId(
          scroll->element_id());

  // Scrollbars will flash shown but we should have a fade out animation
  // queued. Run it and fade out the scrollbars.
  ASSERT_FALSE(scrollbar_controller->ScrollbarsHidden());
  EXPECT_TRUE(scrollbar_controller->visibility_changed());
  ClearMainThreadDeltasForTesting(host_impl_.get());
  auto commit_data = host_impl_->ProcessCompositorDeltas(
      /* main_thread_mutator_host */ nullptr);
  using ScrollbarsInfo = CompositorCommitData::ScrollbarsUpdateInfo;
  EXPECT_THAT(commit_data->scrollbars, testing::ElementsAre(ScrollbarsInfo{
                                           scroll->element_id(), false}));
  EXPECT_FALSE(scrollbar_controller->visibility_changed());
  {
    ASSERT_FALSE(animation_task_.is_null());
    ASSERT_FALSE(animation_task_.IsCancelled());
    std::move(animation_task_).Run();

    base::TimeTicks fake_now = base::TimeTicks::Now();
    scrollbar_controller->Animate(fake_now);
    fake_now += settings.scrollbar_fade_delay;
    scrollbar_controller->Animate(fake_now);

    ASSERT_TRUE(scrollbar_controller->ScrollbarsHidden());
    ClearMainThreadDeltasForTesting(host_impl_.get());
    commit_data = host_impl_->ProcessCompositorDeltas(
        /* main_thread_mutator_host */ nullptr);
    EXPECT_THAT(commit_data->scrollbars, testing::ElementsAre(ScrollbarsInfo{
                                             scroll->element_id(), true}));
    EXPECT_FALSE(scrollbar_controller->visibility_changed());
  }

  // Move the mouse over the scrollbar region. This should post a delayed fade
  // in task. Execute it to fade in the scrollbars.
  {
    animation_task_.Reset();
    scrollbar_controller->DidMouseMove(gfx::PointF(90, 0));
    ASSERT_FALSE(animation_task_.is_null());
    ASSERT_FALSE(animation_task_.IsCancelled());
  }

  // The fade in task should cause the scrollbars to show. Ensure that we
  // requested a redraw and a commit.
  {
    did_request_redraw_ = false;
    did_request_commit_ = false;
    ASSERT_TRUE(scrollbar_controller->ScrollbarsHidden());
    EXPECT_FALSE(scrollbar_controller->visibility_changed());
    ClearMainThreadDeltasForTesting(host_impl_.get());
    commit_data = host_impl_->ProcessCompositorDeltas(
        /* main_thread_mutator_host */ nullptr);
    EXPECT_TRUE(commit_data->scrollbars.empty());
    std::move(animation_task_).Run();

    base::TimeTicks fake_now = base::TimeTicks::Now();
    scrollbar_controller->Animate(fake_now);
    fake_now += settings.scrollbar_fade_duration;
    scrollbar_controller->Animate(fake_now);

    ASSERT_FALSE(scrollbar_controller->ScrollbarsHidden());
    EXPECT_TRUE(scrollbar_controller->visibility_changed());
    ClearMainThreadDeltasForTesting(host_impl_.get());
    commit_data = host_impl_->ProcessCompositorDeltas(
        /* main_thread_mutator_host */ nullptr);
    EXPECT_THAT(commit_data->scrollbars, testing::ElementsAre(ScrollbarsInfo{
                                             scroll->element_id(), false}));
    EXPECT_FALSE(scrollbar_controller->visibility_changed());
    EXPECT_TRUE(did_request_redraw_);
    EXPECT_TRUE(did_request_commit_);
  }
}

TEST_P(PendingTreeLayerTreeHostImplTest, ClampingAfterActivation) {
  CreatePendingTree();
  host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1);
  SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50),
                      gfx::Size(100, 100), gfx::Size(100, 100));
  host_impl_->ActivateSyncTree();

  CreatePendingTree();
  const gfx::PointF pending_scroll(-100, -100);
  LayerImpl* active_outer_layer = OuterViewportScrollLayer();
  LayerImpl* pending_outer_layer =
      host_impl_->pending_tree()->OuterViewportScrollLayerForTesting();
  pending_outer_layer->layer_tree_impl()
      ->property_trees()
      ->scroll_tree_mutable()
      .UpdateScrollOffsetBaseForTesting(pending_outer_layer->element_id(),
                                        pending_scroll);

  host_impl_->ActivateSyncTree();
  // Scrolloffsets on the active tree will be clamped after activation.
  EXPECT_EQ(CurrentScrollOffset(active_outer_layer), gfx::PointF(0, 0));
}

TEST_P(PendingTreeLayerTreeHostImplTest, RootLayerScrollOffsetDelegation) {
  TestInputHandlerClient scroll_watcher;
  SetupViewportLayersInnerScrolls(gfx::Size(10, 20), gfx::Size(100, 100));
  auto* scroll_layer = InnerViewportScrollLayer();
  UpdateDrawProperties(host_impl_->active_tree());

  GetInputHandler().BindToClient(&scroll_watcher);

  gfx::Vector2dF initial_scroll_delta(10, 10);
  scroll_layer->layer_tree_impl()
      ->property_trees()
      ->scroll_tree_mutable()
      .UpdateScrollOffsetBaseForTesting(scroll_layer->element_id(),
                                        gfx::PointF());
  SetScrollOffsetDelta(scroll_layer, initial_scroll_delta);

  EXPECT_EQ(gfx::PointF(), scroll_watcher.last_set_scroll_offset());

  // Requesting an update results in the current scroll offset being set.
  GetInputHandler().RequestUpdateForSynchronousInputHandler();
  EXPECT_EQ(initial_scroll_delta,
            scroll_watcher.last_set_scroll_offset().OffsetFromOrigin());

  // Setting the delegate results in the scrollable_size, max_scroll_offset,
  // page_scale_factor and {min|max}_page_scale_factor being set.
  EXPECT_EQ(gfx::SizeF(100, 100), scroll_watcher.scrollable_size());
  EXPECT_EQ(gfx::PointF(90, 80), scroll_watcher.max_scroll_offset());
  EXPECT_EQ(1, scroll_watcher.page_scale_factor());
  EXPECT_EQ(1, scroll_watcher.min_page_scale_factor());
  EXPECT_EQ(1, scroll_watcher.max_page_scale_factor());

  // Put a page scale on the tree.
  host_impl_->active_tree()->PushPageScaleFromMainThread(2, 0.5f, 4);
  EXPECT_EQ(1, scroll_watcher.page_scale_factor());
  EXPECT_EQ(1, scroll_watcher.min_page_scale_factor());
  EXPECT_EQ(1, scroll_watcher.max_page_scale_factor());
  // Activation will update the delegate.
  host_impl_->ActivateSyncTree();
  EXPECT_EQ(2, scroll_watcher.page_scale_factor());
  EXPECT_EQ(.5f, scroll_watcher.min_page_scale_factor());
  EXPECT_EQ(4, scroll_watcher.max_page_scale_factor());

  // Animating page scale can change the root offset, so it should update the
  // delegate. Also resets the page scale to 1 for the rest of the test.
  host_impl_->LayerTreeHostImpl::StartPageScaleAnimation(
      gfx::Point(0, 0), false, 1, base::TimeDelta());
  host_impl_->Animate();
  EXPECT_EQ(1, scroll_watcher.page_scale_factor());
  EXPECT_EQ(.5f, scroll_watcher.min_page_scale_factor());
  EXPECT_EQ(4, scroll_watcher.max_page_scale_factor());

  // The pinch gesture doesn't put the delegate into a state where the scroll
  // offset is outside of the scroll range.  (this is verified by DCHECKs in the
  // delegate).
  GetInputHandler().ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(),
                                           ui::ScrollInputType::kTouchscreen)
                                    .get(),
                                ui::ScrollInputType::kTouchscreen);
  GetInputHandler().PinchGestureBegin(gfx::Point(),
                                      ui::ScrollInputType::kWheel);
  GetInputHandler().PinchGestureUpdate(2, gfx::Point());
  GetInputHandler().PinchGestureUpdate(.5f, gfx::Point());
  GetInputHandler().PinchGestureEnd(gfx::Point());
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

  // Scrolling should be relative to the offset as given by the delegate.
  gfx::Vector2dF scroll_delta(0, 10);
  gfx::PointF current_offset(7, 8);

  EXPECT_EQ(ScrollThread::kScrollOnImplThread,
            GetInputHandler()
                .ScrollBegin(BeginState(gfx::Point(), scroll_delta,
                                        ui::ScrollInputType::kTouchscreen)
                                 .get(),
                             ui::ScrollInputType::kTouchscreen)
                .thread);
  GetInputHandler().SetSynchronousInputHandlerRootScrollOffset(current_offset);

  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen));
  EXPECT_EQ(current_offset + scroll_delta,
            scroll_watcher.last_set_scroll_offset());

  current_offset = gfx::PointF(42, 41);
  GetInputHandler().SetSynchronousInputHandlerRootScrollOffset(current_offset);
  GetInputHandler().ScrollUpdate(UpdateState(
      gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen));
  EXPECT_EQ(current_offset + scroll_delta,
            scroll_watcher.last_set_scroll_offset());
  GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);
  GetInputHandler().SetSynchronousInputHandlerRootScrollOffset(gfx::PointF());

  // Forces a full tree synchronization and ensures that the scroll delegate
  // sees the correct size of the new tree.
  gfx::Size new_viewport_size(21, 12);
  gfx::Size new_content_size(42, 24);
  CreatePendingTree();
  host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1);
  SetupViewportLayers(host_impl_->pending_tree(), new_viewport_size,
                      new_content_size, new_content_size);
  host_impl_->ActivateSyncTree();
  EXPECT_EQ(gfx::SizeF(new_content_size), scroll_watcher.scrollable_size());

  // Tear down the LayerTreeHostImpl before the InputHandlerClient.
  host_impl_->ReleaseLayerTreeFrameSink();
  host_impl_ = nullptr;
}

TEST_P(PendingTreeLayerTreeHostImplTest,
       ExternalTileConstraintReflectedInPendingTree) {
  const gfx::Size layer_size(100, 100);

  // Set up active and pending tree.
  CreatePendingTree();
  SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), layer_size);
  UpdateDrawProperties(host_impl_->pending_tree());
  host_impl_->pending_tree()->root_layer()->SetNeedsPushProperties();

  host_impl_->ActivateSyncTree();
  UpdateDrawProperties(host_impl_->active_tree());

  CreatePendingTree();
  UpdateDrawProperties(host_impl_->pending_tree());
  UpdateDrawProperties(host_impl_->active_tree());

  EXPECT_FALSE(host_impl_->pending_tree()->needs_update_draw_properties());
  EXPECT_FALSE(host_impl_->active_tree()->needs_update_draw_properties());

  // Update external constraints should set_needs_update_draw_properties on
  // both trees.
  gfx::Transform external_transform;
  gfx::Rect external_viewport(10, 20);
  host_impl_->SetExternalTilePriorityConstraints(external_viewport,
                                                 external_transform);
  EXPECT_TRUE(host_impl_->pending_tree()->needs_update_draw_properties());
  EXPECT_TRUE(host_impl_->active_tree()->needs_update_draw_properties());
}

TEST_P(PendingTreeLayerTreeHostImplTest, OneScrollForFirstScrollDelay) {
  SetupScrollDelayTest();

  QueueLatencyInfoSwapPromise(
      ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, 5);

  DrawAndPresentFrame(1);

  EXPECT_EQ(first_scroll_observed, 1);
}

TEST_P(PendingTreeLayerTreeHostImplTest, OtherInputsForFirstScrollDelay) {
  SetupScrollDelayTest();

  QueueLatencyInfoSwapPromise(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 5);

  DrawAndPresentFrame(1);

  EXPECT_EQ(first_scroll_observed, 0);
}

TEST_P(PendingTreeLayerTreeHostImplTest, MultipleScrollsForFirstScrollDelay) {
  SetupScrollDelayTest();

  QueueLatencyInfoSwapPromise(
      ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, 5);
  DrawAndPresentFrame(1, /*damage=*/false);
  EXPECT_EQ(first_scroll_observed, 1);

  QueueLatencyInfoSwapPromise(
      ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, 6);
  DrawAndPresentFrame(2, /*damage=*/true);
  EXPECT_EQ(first_scroll_observed, 1);
}

TEST_P(PendingTreeLayerTreeHostImplTest, InvalidLayerNotAddedToRasterQueue) {
  CreatePendingTree();

  scoped_refptr<RasterSource> raster_source_with_tiles(
      FakeRasterSource::CreateFilled(gfx::Size(10, 10)));

  auto* layer = SetupRootLayer<FakePictureLayerImpl>(host_impl_->pending_tree(),
                                                     gfx::Size(10, 10));
  layer->set_gpu_raster_max_texture_size(
      host_impl_->active_tree()->GetDeviceViewport().size());
  layer->SetDrawsContent(true);
  layer->tilings()->AddTiling(gfx::AxisTransform2d(), raster_source_with_tiles);
  layer->SetRasterSourceForTesting(raster_source_with_tiles);
  layer->tilings()->tiling_at(0)->set_resolution(
      TileResolution::HIGH_RESOLUTION);
  layer->tilings()->tiling_at(0)->CreateAllTilesForTesting();
  layer->tilings()->UpdateTilePriorities(gfx::Rect(gfx::Size(10, 10)), 1, 1.0,
                                         Occlusion(), true);

  layer->set_has_valid_tile_priorities(true);
  std::unique_ptr<RasterTilePriorityQueue> non_empty_raster_priority_queue_all =
      host_impl_->BuildRasterQueue(TreePriority::SAME_PRIORITY_FOR_BOTH_TREES,
                                   RasterTilePriorityQueue::Type::ALL);
  EXPECT_FALSE(non_empty_raster_priority_queue_all->IsEmpty());

  layer->set_has_valid_tile_priorities(false);
  std::unique_ptr<RasterTilePriorityQueue> empty_raster_priority_queue_all =
      host_impl_->BuildRasterQueue(TreePriority::SAME_PRIORITY_FOR_BOTH_TREES,
                                   RasterTilePriorityQueue::Type::ALL);
  EXPECT_TRUE(empty_raster_priority_queue_all->IsEmpty());
}

TEST_P(PendingTreeLayerTreeHostImplTest, DidBecomeActive) {
  CreatePendingTree();
  host_impl_->ActivateSyncTree();
  CreatePendingTree();

  LayerTreeImpl* pending_tree = host_impl_->pending_tree();

  auto* pending_layer =
      SetupRootLayer<FakePictureLayerImpl>(pending_tree, gfx::Size(10, 10));

  EXPECT_EQ(0u, pending_layer->did_become_active_call_count());
  pending_tree->DidBecomeActive();
  EXPECT_EQ(1u, pending_layer->did_become_active_call_count());

  std::unique_ptr<FakePictureLayerImpl> mask_layer =
      FakePictureLayerImpl::Create(pending_tree, next_layer_id_++);
  FakePictureLayerImpl* raw_mask_layer = mask_layer.get();
  SetupMaskProperties(pending_layer, raw_mask_layer);
  pending_tree->AddLayer(std::move(mask_layer));

  EXPECT_EQ(1u, pending_layer->did_become_active_call_count());
  EXPECT_EQ(0u, raw_mask_layer->did_become_active_call_count());
  pending_tree->DidBecomeActive();
  EXPECT_EQ(2u, pending_layer->did_become_active_call_count());
  EXPECT_EQ(1u, raw_mask_layer->did_become_active_call_count());

  EXPECT_EQ(2u, pending_layer->did_become_active_call_count());
  EXPECT_EQ(1u, raw_mask_layer->did_become_active_call_count());
  pending_tree->DidBecomeActive();
  EXPECT_EQ(3u, pending_layer->did_become_active_call_count());
  EXPECT_EQ(2u, raw_mask_layer->did_become_active_call_count());
}

TEST_P(PendingTreeLayerTreeHostImplTest, UpdatePageScaleFactorOnActiveTree) {
  // Check page scale factor updates the property trees when an update is made
  // on the active tree.
  CreatePendingTree();
  host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 3);
  SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50),
                      gfx::Size(100, 100), gfx::Size(100, 100));
  host_impl_->ActivateSyncTree();
  DrawFrame();

  CreatePendingTree();
  host_impl_->active_tree()->SetPageScaleOnActiveTree(2);

  TransformNode* active_tree_node =
      host_impl_->active_tree()->PageScaleTransformNode();
  // SetPageScaleOnActiveTree also updates the factors in property trees.
  EXPECT_TRUE(active_tree_node->local.IsScale2d());
  EXPECT_EQ(gfx::Vector2dF(2, 2), active_tree_node->local.To2dScale());
  EXPECT_EQ(gfx::Point3F(), active_tree_node->origin);
  EXPECT_EQ(2, host_impl_->active_tree()->current_page_scale_factor());
  EXPECT_EQ(2, host_impl_->active_tree()
                   ->property_trees()
                   ->transform_tree()
                   .page_scale_factor());

  TransformNode* pending_tree_node =
      host_impl_->pending_tree()->PageScaleTransformNode();

  // Since the pending tree shares the scale factor with the active tree, its
  // value and property trees should also have been updated.
  EXPECT_TRUE(pending_tree_node->local.IsScale2d());
  EXPECT_EQ(gfx::Vector2dF(2, 2), pending_tree_node->local.To2dScale());
  EXPECT_EQ(gfx::Point3F(), pending_tree_node->origin);
  EXPECT_EQ(2, host_impl_->pending_tree()->current_page_scale_factor());
  EXPECT_EQ(2, host_impl_->pending_tree()
                   ->property_trees()
                   ->transform_tree()
                   .page_scale_factor());

  // Update draw properties doesn't change the correct values
  host_impl_->pending_tree()->set_needs_update_draw_properties();
  UpdateDrawProperties(host_impl_->pending_tree());
  pending_tree_node = host_impl_->pending_tree()->PageScaleTransformNode();
  EXPECT_TRUE(pending_tree_node->local.IsScale2d());
  EXPECT_EQ(gfx::Vector2dF(2, 2), pending_tree_node->local.To2dScale());
  EXPECT_EQ(gfx::Point3F(), pending_tree_node->origin);

  host_impl_->ActivateSyncTree();
  UpdateDrawProperties(host_impl_->active_tree());
  active_tree_node = host_impl_->active_tree()->PageScaleTransformNode();
  EXPECT_TRUE(active_tree_node->local.IsScale2d());
  EXPECT_EQ(gfx::Vector2dF(2, 2), active_tree_node->local.To2dScale());
  EXPECT_EQ(gfx::Point3F(), active_tree_node->origin);
}

TEST_P(PendingTreeLayerTreeHostImplTest, CheckerImagingTileInvalidation) {
  LayerTreeSettings settings = LegacySWSettings();
  settings.enable_checker_imaging = true;
  settings.min_image_bytes_to_checker = 512 * 1024;
  settings.default_tile_size = gfx::Size(256, 256);
  settings.max_untiled_layer_size = gfx::Size(256, 256);
  CreateHostImpl(settings, CreateLayerTreeFrameSink());
  gfx::Size layer_size = gfx::Size(750, 750);

  FakeRecordingSource recording_source(layer_size);
  PaintImage checkerable_image =
      PaintImageBuilder::WithCopy(
          CreateDiscardablePaintImage(gfx::Size(500, 500)))
          .set_decoding_mode(PaintImage::DecodingMode::kAsync)
          .TakePaintImage();
  recording_source.add_draw_image(checkerable_image, gfx::Point(0, 0));

  SkColor non_solid_color = SkColorSetARGB(128, 45, 56, 67);
  PaintFlags non_solid_flags;
  non_solid_flags.setColor(non_solid_color);
  recording_source.add_draw_rect_with_flags(gfx::Rect(510, 0, 200, 600),
                                            non_solid_flags);
  recording_source.add_draw_rect_with_flags(gfx::Rect(0, 510, 200, 400),
                                            non_solid_flags);
  recording_source.Rerecord();
  scoped_refptr<RasterSource> raster_source =
      recording_source.CreateRasterSource();

  viz::BeginFrameArgs begin_frame_args =
      viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
  host_impl_->WillBeginImplFrame(begin_frame_args);

  // Create the pending tree.
  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->BeginCommit(0, BeginMainFrameTraceId{1});
  LayerTreeImpl* pending_tree = host_impl_->pending_tree();
  auto* root = SetupRootLayer<FakePictureLayerImpl>(pending_tree, layer_size,
                                                    raster_source);
  root->SetDrawsContent(true);
  UpdateDrawProperties(pending_tree);

  // CompleteCommit which should perform a PrepareTiles, adding tilings for the
  // root layer, each one having a raster task.
  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->CommitComplete();
  EXPECT_EQ(root->num_tilings(), 1U);
  const PictureLayerTiling* tiling = root->tilings()->tiling_at(0);
  EXPECT_EQ(tiling->AllTilesForTesting().size(), 9U);
  for (auto* tile : tiling->AllTilesForTesting()) {
    EXPECT_TRUE(tile->HasRasterTask());
  }

  // Activate the pending tree and ensure that all tiles are rasterized.
  ASSERT_TRUE(
      base::test::RunUntil([&]() { return did_notify_ready_to_activate_; }));
  for (auto* tile : tiling->AllTilesForTesting()) {
    EXPECT_FALSE(tile->HasRasterTask());
  }

  // PrepareTiles should have scheduled a decode with the ImageDecodeService,
  // ensure that it requests an impl-side invalidation.
  ASSERT_TRUE(base::test::RunUntil(
      [&]() { return did_request_impl_side_invalidation_; }));

  // Invalidate content on impl-side and ensure that the correct tiles are
  // invalidated on the pending tree.
  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->InvalidateContentOnImplSide();
  pending_tree = host_impl_->pending_tree();
  root = static_cast<FakePictureLayerImpl*>(pending_tree->root_layer());
  for (auto* tile : root->tilings()->tiling_at(0)->AllTilesForTesting()) {
    if (tile->tiling_i_index() < 2 && tile->tiling_j_index() < 2) {
      EXPECT_TRUE(tile->HasRasterTask());
    } else {
      EXPECT_FALSE(tile->HasRasterTask());
    }
  }
  const auto expected_invalidation =
      ImageRectsToRegion(root->discardable_image_map()->GetRectsForImage(
          checkerable_image.stable_id()));
  EXPECT_EQ(expected_invalidation, *(root->GetPendingInvalidation()));
}

TEST_P(PendingTreeLayerTreeHostImplTest, UpdatedTilingsForNonDrawingLayers) {
  gfx::Size layer_bounds(500, 500);

  CreatePendingTree();
  auto* root =
      SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), layer_bounds);

  scoped_refptr<FakeRasterSource> raster_source(
      FakeRasterSource::CreateFilled(layer_bounds));
  auto* animated_transform_layer =
      AddLayer<FakePictureLayerImpl>(host_impl_->pending_tree(), raster_source);
  animated_transform_layer->SetBounds(layer_bounds);
  animated_transform_layer->SetDrawsContent(true);

  host_impl_->pending_tree()->SetElementIdsForTesting();
  gfx::Transform singular;
  singular.Scale3d(6, 6, 0);
  CopyProperties(root, animated_transform_layer);
  CreateTransformNode(animated_transform_layer).local = singular;

  // A layer with a non-invertible transform is not drawn or rasterized. Since
  // this layer is not rasterized, we shouldn't be creating any tilings for it.
  UpdateDrawProperties(host_impl_->pending_tree());
  EXPECT_FALSE(animated_transform_layer->HasValidTilePriorities());
  EXPECT_EQ(animated_transform_layer->tilings()->num_tilings(), 0u);
  UpdateDrawProperties(host_impl_->pending_tree());
  EXPECT_FALSE(animated_transform_layer->raster_even_if_not_drawn());
  EXPECT_FALSE(animated_transform_layer->contributes_to_drawn_render_surface());
  EXPECT_EQ(animated_transform_layer->tilings()->num_tilings(), 0u);

  // Now add a transform animation to this layer. While we don't drawn layers
  // with non-invertible transforms, we still raster them if there is a
  // transform animation.
  gfx::TransformOperations start_transform_operations;
  start_transform_operations.AppendMatrix(singular);
  gfx::TransformOperations end_transform_operations;
  AddAnimatedTransformToElementWithAnimation(
      animated_transform_layer->element_id(), timeline(), 10.0,
      start_transform_operations, end_transform_operations);

  // The layer is still not drawn, but it will be rasterized. Since the layer is
  // rasterized, we should be creating tilings for it in UpdateDrawProperties.
  // However, none of these tiles should be required for activation.
  UpdateDrawProperties(host_impl_->pending_tree());
  EXPECT_TRUE(animated_transform_layer->raster_even_if_not_drawn());
  EXPECT_FALSE(animated_transform_layer->contributes_to_drawn_render_surface());
  ASSERT_EQ(animated_transform_layer->tilings()->num_tilings(), 1u);
  EXPECT_FALSE(animated_transform_layer->tilings()
                   ->tiling_at(0)
                   ->can_require_tiles_for_activation());
}

TEST_P(PendingTreeLayerTreeHostImplTest,
       RasterTilePrioritizationForNonDrawingLayers) {
  gfx::Size layer_bounds(500, 500);
  CreatePendingTree();
  auto* root =
      SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), layer_bounds);
  root->SetBounds(layer_bounds);

  scoped_refptr<FakeRasterSource> raster_source(
      FakeRasterSource::CreateFilled(layer_bounds));

  auto* hidden_layer =
      AddLayer<FakePictureLayerImpl>(host_impl_->pending_tree(), raster_source);
  hidden_layer->SetBounds(layer_bounds);
  hidden_layer->SetDrawsContent(true);
  hidden_layer->set_contributes_to_drawn_render_surface(true);
  CopyProperties(root, hidden_layer);

  auto* drawing_layer =
      AddLayer<FakePictureLayerImpl>(host_impl_->pending_tree(), raster_source);
  drawing_layer->SetBounds(layer_bounds);
  drawing_layer->SetDrawsContent(true);
  drawing_layer->set_contributes_to_drawn_render_surface(true);
  CopyProperties(root, drawing_layer);

  gfx::Rect layer_rect(0, 0, 500, 500);

  hidden_layer->tilings()->AddTiling(gfx::AxisTransform2d(), raster_source);
  PictureLayerTiling* hidden_tiling = hidden_layer->tilings()->tiling_at(0);
  hidden_tiling->set_resolution(TileResolution::HIGH_RESOLUTION);
  hidden_tiling->CreateAllTilesForTesting();
  hidden_tiling->SetTilePriorityRectsForTesting(
      layer_rect,   // Visible rect.
      layer_rect,   // Skewport rect.
      layer_rect,   // Soon rect.
      layer_rect);  // Eventually rect.

  drawing_layer->tilings()->AddTiling(gfx::AxisTransform2d(), raster_source);
  PictureLayerTiling* drawing_tiling = drawing_layer->tilings()->tiling_at(0);
  drawing_tiling->set_resolution(TileResolution::HIGH_RESOLUTION);
  drawing_tiling->CreateAllTilesForTesting();
  drawing_tiling->SetTilePriorityRectsForTesting(
      layer_rect,   // Visible rect.
      layer_rect,   // Skewport rect.
      layer_rect,   // Soon rect.
      layer_rect);  // Eventually rect.

  // Hide the hidden layer and set it to so it still rasters. The drawing
  // layer should be prioritized over the hidden layer.
  hidden_layer->set_contributes_to_drawn_render_surface(false);
  hidden_layer->set_raster_even_if_not_drawn(true);
  std::unique_ptr<RasterTilePriorityQueue> queue =
      host_impl_->BuildRasterQueue(TreePriority::SMOOTHNESS_TAKES_PRIORITY,
                                   RasterTilePriorityQueue::Type::ALL);
  EXPECT_EQ(queue->Top().tile()->layer_id(), 3);
}

TEST_P(PendingTreeLayerTreeHostImplTest, DrawAfterDroppingTileResources) {
  LayerTreeSettings settings = DefaultSettings();
  settings.using_synchronous_renderer_compositor = true;
  CreateHostImpl(settings, CreateLayerTreeFrameSink());
  CreatePendingTree();

  gfx::Size bounds(100, 100);
  scoped_refptr<FakeRasterSource> raster_source(
      FakeRasterSource::CreateFilled(bounds));
  auto* root = SetupRootLayer<FakePictureLayerImpl>(host_impl_->pending_tree(),
                                                    bounds, raster_source);
  root->SetDrawsContent(true);
  host_impl_->ActivateSyncTree();

  FakePictureLayerImpl* layer = static_cast<FakePictureLayerImpl*>(
      host_impl_->active_tree()->FindActiveTreeLayerById(root->id()));

  DrawFrame();
  EXPECT_FALSE(host_impl_->active_tree()->needs_update_draw_properties());
  EXPECT_LT(0, layer->raster_page_scale());
  EXPECT_GT(layer->tilings()->num_tilings(), 0u);

  const ManagedMemoryPolicy policy = host_impl_->ActualManagedMemoryPolicy();
  const ManagedMemoryPolicy zero_policy(0u);
  host_impl_->SetMemoryPolicy(zero_policy);
  EXPECT_EQ(0, layer->raster_page_scale());
  EXPECT_EQ(layer->tilings()->num_tilings(), 0u);

  host_impl_->SetMemoryPolicy(policy);
  DrawFrame();
  EXPECT_LT(0, layer->raster_page_scale());
  EXPECT_GT(layer->tilings()->num_tilings(), 0u);
}

TEST_P(PendingTreeLayerTreeHostImplTest, CommitWithNoPaintWorkletLayerPainter) {
  ASSERT_FALSE(host_impl_->GetPaintWorkletLayerPainterForTesting());
  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->CreatePendingTree();

  // When there is no PaintWorkletLayerPainter registered, commits should finish
  // immediately and move onto preparing tiles.
  ASSERT_FALSE(did_prepare_tiles_);
  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->CommitComplete();
  EXPECT_TRUE(did_prepare_tiles_);
}

TEST_P(PendingTreeLayerTreeHostImplTest, CommitWithNoPaintWorklets) {
  host_impl_->SetPaintWorkletLayerPainter(
      std::make_unique<TestPaintWorkletLayerPainter>());
  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->CreatePendingTree();

  // When there are no PaintWorklets in the committed display lists, commits
  // should finish immediately and move onto preparing tiles.
  ASSERT_FALSE(did_prepare_tiles_);
  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->CommitComplete();
  EXPECT_TRUE(did_prepare_tiles_);
}

TEST_P(PendingTreeLayerTreeHostImplTest, CommitWithDirtyPaintWorklets) {
  RunPaintWorkletCommitTest();
  EXPECT_TRUE(did_prepare_tiles_);
}

TEST_P(PendingTreeLayerTreeHostImplTest, CommitWithNoDirtyPaintWorklets) {
  host_impl_->SetPaintWorkletLayerPainter(
      std::make_unique<TestPaintWorkletLayerPainter>());

  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->CreatePendingTree();
  auto* root = SetupRootLayer<PictureLayerImpl>(host_impl_->pending_tree(),
                                                gfx::Size(100, 100));
  root->SetNeedsPushProperties();

  // Add some PaintWorklets.
  scoped_refptr<RasterSource> raster_source_with_pws =
      FakeRasterSource::CreateFilledWithPaintWorklet(root->bounds());
  root->SetRasterSourceForTesting(raster_source_with_pws);

  UpdateDrawProperties(host_impl_->pending_tree());

  // Pretend that our worklets were already painted.
  ASSERT_EQ(root->GetPaintWorkletRecordMap().size(), 1u);
  root->SetPaintWorkletRecord(root->GetPaintWorkletRecordMap().begin()->first,
                              PaintRecord());

  // Since there are no dirty PaintWorklets, the commit should immediately
  // prepare tiles.
  ASSERT_FALSE(did_prepare_tiles_);
  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->CommitComplete();
  EXPECT_TRUE(did_prepare_tiles_);
}

TEST_P(PendingTreeLayerTreeHostImplTest,
       ActivatedPendingTreeRetainsRasterMetrics) {
  gfx::Size scrollable_content_bounds(100, 100);
  gfx::Size container_bounds(50, 50);
  if (!CommitsToActiveTree()) {
    CreatePendingTree();
  }

  // Create root and scroll layers so that we can set up a
  // non-composited scrollable node, eligible for raster scroll.
  auto* sync_tree_root = SetupRootLayer<LayerImpl>(host_impl_->sync_tree(),
                                                   scrollable_content_bounds);
  sync_tree_root->SetNeedsPushProperties();
  auto* scrolling_layer =
      AddScrollableLayer(sync_tree_root, container_bounds, gfx::Size());
  scrolling_layer->SetNeedsPushProperties();
  CreateScrollNodeForNonCompositedScroller(
      host_impl_->sync_tree()->property_trees(), sync_tree_root->id(),
      scrolling_layer->element_id(), scrollable_content_bounds,
      container_bounds);

  // Draw at least one frame before ScrollBegin.
  host_impl_->sync_tree()->set_needs_update_draw_properties();
  UpdateDrawProperties(host_impl_->sync_tree());
  host_impl_->ActivateSyncTree();
  DrawFrame();

  // Scrolling on this non-composited tree should be marked as raster-inducing.
  ScrollStateData scroll_state_data;
  scroll_state_data.set_current_native_scrolling_element(
      scrolling_layer->element_id());
  scroll_state_data.is_beginning = true;
  std::unique_ptr<ScrollState> scroll_state(new ScrollState(scroll_state_data));
  InputHandler::ScrollStatus status = GetInputHandler().ScrollBegin(
      scroll_state.get(), ui::ScrollInputType::kTouchscreen);
  EXPECT_EQ(true, status.raster_inducing);

  base::TimeTicks scroll_begin_generated_timestamp =
      base::TimeTicks::Now() - base::Milliseconds(1);
  base::TimeTicks scroll_begin_arrival_timestamp = base::TimeTicks::Now();
  GetInputHandler().RecordScrollBegin(
      ui::ScrollInputType::kTouchscreen,
      ScrollBeginThreadState::kRasterInducingScroll);

  {
    GetInputHandler().ScrollUpdate(UpdateState(
        gfx::Point(), gfx::Vector2d(0, 10), ui::ScrollInputType::kTouchscreen));

    base::TimeTicks now = base::TimeTicks::Now();
    std::unique_ptr<EventMetrics> metrics = ScrollUpdateEventMetrics::Create(
        ui::EventType::kGestureScrollUpdate, ui::ScrollInputType::kTouchscreen,
        /*is_inertial=*/false,
        ScrollUpdateEventMetrics::ScrollUpdateType::kContinued,
        /*delta=*/10.0f, /*timestamp=*/now,
        /*arrived_in_browser_main_timestamp=*/now + base::Milliseconds(1),
        /*blocking_touch_dispatched_to_renderer=*/base::TimeTicks(),
        /*trace_id=*/base::IdType64<class ui::LatencyInfo>(123),
        scroll_begin_generated_timestamp, scroll_begin_arrival_timestamp);

    // Associate metrics with the scoped metrics monitor by registering a done
    // callback.
    auto done_callback = base::BindOnce(
        [](std::unique_ptr<EventMetrics> metrics, bool handled) {
          metrics->SetDispatchStageTimestamp(
              EventMetrics::DispatchStage::kRendererCompositorStarted);
          return handled ? std::move(metrics) : nullptr;
        },
        std::move(metrics));
    auto scoped_event_monitor =
        host_impl_->GetScopedEventMetricsMonitor(std::move(done_callback));

    host_impl_->SetNeedsOneBeginImplFrame();
    TestFrameData frame;
    auto args = viz::CreateBeginFrameArgsForTesting(
        BEGINFRAME_FROM_HERE, viz::BeginFrameArgs::kManualSourceId, 1,
        base::TimeTicks() + base::Milliseconds(1));
    host_impl_->WillBeginImplFrame(args);
    EXPECT_EQ(DrawResult::kSuccess, host_impl_->PrepareToDraw(&frame));
  }
  // This call creates a new pending tree.
  // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests.
  client_host_impl(host_impl_.get())->InvalidateContentOnImplSide();
  if (!CommitsToActiveTree()) {
    // If a pending tree exists, we expect to see that there are metrics
    // associated with the raster frame associated with it.
    EXPECT_EQ((size_t)1,
              host_impl_->pending_tree()
                  ->events_metrics_from_raster_thread_count_for_testing());
    // Activating the tree should show that raster metrics are now
    // associated with it.
    host_impl_->ActivateSyncTree();
    EXPECT_EQ((size_t)1,
              host_impl_->active_tree()
                  ->events_metrics_from_raster_thread_count_for_testing());
  }
}

class LayerTreeHostImplTestScrollbarOpacity
    : public PendingTreeLayerTreeHostImplTest {
 protected:
  void RunTest(LayerTreeSettings::ScrollbarAnimator animator) {
    LayerTreeSettings settings = DefaultSettings();
    settings.scrollbar_animator = animator;
    settings.scrollbar_fade_delay = base::Milliseconds(20);
    settings.scrollbar_fade_duration = base::Milliseconds(20);
    gfx::Size viewport_size(50, 50);
    gfx::Size content_size(100, 100);

    // If no animator is set, scrollbar won't show and no animation is expected.
    bool expecting_animations = animator != LayerTreeSettings::NO_ANIMATOR;

    CreateHostImpl(settings, CreateLayerTreeFrameSink());
    CreatePendingTree();
    SetupViewportLayers(host_impl_->pending_tree(), viewport_size, content_size,
                        content_size);

    LayerImpl* scroll =
        host_impl_->pending_tree()->OuterViewportScrollLayerForTesting();
    auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
        host_impl_->pending_tree(), ScrollbarOrientation::kVertical, 10, 0,
        false);
    SetupScrollbarLayer(scroll, scrollbar);
    scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(90, 0));

    host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1);
    UpdateDrawProperties(host_impl_->pending_tree());
    host_impl_->ActivateSyncTree();

    LayerImpl* active_scrollbar_layer =
        host_impl_->active_tree()->LayerById(scrollbar->id());

    EffectNode* active_tree_node = GetEffectNode(active_scrollbar_layer);
    EXPECT_FLOAT_EQ(active_scrollbar_layer->Opacity(),
                    active_tree_node->opacity);

    if (expecting_animations) {
      host_impl_->ScrollbarAnimationControllerForElementId(scroll->element_id())
          ->DidMouseMove(gfx::PointF(0, 90));
    } else {
      EXPECT_EQ(nullptr, host_impl_->ScrollbarAnimationControllerForElementId(
                             scroll->element_id()));
    }
    GetInputHandler().ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 5),
                                             ui::ScrollInputType::kWheel)
                                      .get(),
                                  ui::ScrollInputType::kWheel);
    GetInputHandler().ScrollUpdate(UpdateState(
        gfx::Point(), gfx::Vector2dF(0, 5), ui::ScrollInputType::kWheel));
    GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt);

    CreatePendingTree();
    // To test the case where the effect tree index of scrollbar layer changes,
    // we create an effect node with a render surface above the scrollbar's
    // effect node.
    auto* pending_root = host_impl_->pending_tree()->root_layer();
    auto& new_effect_node = CreateEffectNode(
        GetPropertyTrees(pending_root), pending_root->effect_tree_index(),
        pending_root->transform_tree_index(), pending_root->clip_tree_index());
    new_effect_node.render_surface_reason = RenderSurfaceReason::kTest;
    new_effect_node.element_id = ElementId(123);
    LayerImpl* pending_scrollbar_layer =
        host_impl_->pending_tree()->LayerById(scrollbar->id());
    GetEffectNode(pending_scrollbar_layer)->parent_id = new_effect_node.id;
    pending_scrollbar_layer->SetNeedsPushProperties();
    UpdateDrawProperties(host_impl_->pending_tree());

    EffectNode* pending_tree_node = GetEffectNode(pending_scrollbar_layer);
    if (expecting_animations) {
      EXPECT_FLOAT_EQ(1, active_tree_node->opacity);
      EXPECT_FLOAT_EQ(1, active_scrollbar_layer->Opacity());
    } else {
      EXPECT_FLOAT_EQ(0, active_tree_node->opacity);
      EXPECT_FLOAT_EQ(0, active_scrollbar_layer->Opacity());
    }
    EXPECT_FLOAT_EQ(0, pending_tree_node->opacity);

    host_impl_->ActivateSyncTree();
    active_tree_node = GetEffectNode(active_scrollbar_layer);
    if (expecting_animations) {
      EXPECT_FLOAT_EQ(1, active_tree_node->opacity);
      EXPECT_FLOAT_EQ(1, active_scrollbar_layer->Opacity());
    } else {
      EXPECT_FLOAT_EQ(0, active_tree_node->opacity);
      EXPECT_FLOAT_EQ(0, active_scrollbar_layer->Opacity());
    }
  }
};

INSTANTIATE_COMMIT_TO_PENDING_TREE_TEST_P(
    LayerTreeHostImplTestScrollbarOpacity);

TEST_P(LayerTreeHostImplTestScrollbarOpacity, Android) {
  RunTest(LayerTreeSettings::ANDROID_OVERLAY);
}

TEST_P(LayerTreeHostImplTestScrollbarOpacity, AuraOverlay) {
  RunTest(LayerTreeSettings::AURA_OVERLAY);
}

TEST_P(LayerTreeHostImplTestScrollbarOpacity, NoAnimator) {
  RunTest(LayerTreeSettings::NO_ANIMATOR);
}

class ResourcelessSoftwareLayerTreeHostImplTest
    : public PendingTreeLayerTreeHostImplTest {
 protected:
  std::unique_ptr<LayerTreeFrameSink> CreateLayerTreeFrameSink() override {
    return FakeLayerTreeFrameSink::Create3d();
  }
};

INSTANTIATE_COMMIT_TO_PENDING_TREE_TEST_P(
    ResourcelessSoftwareLayerTreeHostImplTest);

TEST_P(ResourcelessSoftwareLayerTreeHostImplTest,
       ResourcelessSoftwareSetNeedsRedraw) {
  const gfx::Size viewport_size(100, 100);
  SetupDefaultRootLayer(viewport_size);
  UpdateDrawProperties(host_impl_->active_tree());

  const gfx::Transform draw_transform;
  const gfx::Rect draw_viewport(viewport_size);
  bool resourceless_software_draw = false;

  // Clear any damage.
  host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw,
                     false);
  last_on_draw_frame_.reset();

  // Always swap even if same draw params.
  resourceless_software_draw = true;
  host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw,
                     false);
  EXPECT_FALSE(last_on_draw_frame_->has_no_damage);
  last_on_draw_frame_.reset();

  // Next hardware draw has damage.
  resourceless_software_draw = false;
  host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw,
                     false);
  EXPECT_FALSE(last_on_draw_frame_->has_no_damage);
}

TEST_P(ResourcelessSoftwareLayerTreeHostImplTest,
       ResourcelessSoftwareDrawSkipsUpdateTiles) {
  const gfx::Size viewport_size(100, 100);

  CreatePendingTree();
  scoped_refptr<FakeRasterSource> raster_source(
      FakeRasterSource::CreateFilled(viewport_size));
  auto* root = SetupRootLayer<FakePictureLayerImpl>(
      host_impl_->pending_tree(), viewport_size, raster_source);
  root->SetBounds(viewport_size);
  root->SetDrawsContent(true);

  UpdateDrawProperties(host_impl_->pending_tree());
  host_impl_->ActivateSyncTree();

  const gfx::Transform draw_transform;
  const gfx::Rect draw_viewport(viewport_size);
  bool resourceless_software_draw = false;

  // Regular draw causes UpdateTiles.
  did_request_prepare_tiles_ = false;
  host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw,
                     false);
  EXPECT_TRUE(did_request_prepare_tiles_);
  host_impl_->PrepareTiles();

  // Resourceless draw skips UpdateTiles.
  const gfx::Rect new_draw_viewport(50, 50);
  resourceless_software_draw = true;
  did_request_prepare_tiles_ = false;
  host_impl_->OnDraw(draw_transform, new_draw_viewport,
                     resourceless_software_draw, false);
  EXPECT_FALSE(did_request_prepare_tiles_);
}

class ForceActivateAfterPaintWorkletPaintLayerTreeHostImplTest
    : public PendingTreeLayerTreeHostImplTest {
 public:
  void NotifyPaintWorkletStateChange(
      Scheduler::PaintWorkletState state) override {
    if (state == Scheduler::PaintWorkletState::IDLE) {
      // Pretend a force activation happened.
      host_impl_->ActivateSyncTree();
      ASSERT_FALSE(host_impl_->pending_tree());
    }
  }
};

INSTANTIATE_COMMIT_TO_PENDING_TREE_TEST_P(
    ForceActivateAfterPaintWorkletPaintLayerTreeHostImplTest);

TEST_P(ForceActivateAfterPaintWorkletPaintLayerTreeHostImplTest,
       ForceActivationAfterPaintWorkletsFinishPainting) {
  RunPaintWorkletCommitTest();
  EXPECT_FALSE(did_prepare_tiles_);
}

class BlendStateCheckLayer : public LayerImpl {
 public:
  static std::unique_ptr<BlendStateCheckLayer> Create(
      LayerTreeImpl* tree_impl,
      int id,
      viz::ClientResourceProvider* resource_provider) {
    return base::WrapUnique(
        new BlendStateCheckLayer(tree_impl, id, resource_provider));
  }

  BlendStateCheckLayer(LayerTreeImpl* tree_impl,
                       int id,
                       viz::ClientResourceProvider* resource_provider)
      : LayerImpl(tree_impl, id),
        resource_provider_(resource_provider),
        comparison_layer_(nullptr),
        quad_rect_(5, 5, 5, 5),
        quad_visible_rect_(5, 5, 5, 5),
        shared_image_interface_(
            base::MakeRefCounted<gpu::TestSharedImageInterface>()) {
    auto shared_image =
        shared_image_interface_->CreateSharedImageForSoftwareCompositor(
            {viz::SinglePlaneFormat::kBGRA_8888, gfx::Size(1, 1),
             gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY,
             "BlendStateCheckLayerTest"});
    auto sync_token = shared_image_interface_->GenUnverifiedSyncToken();
    viz::TransferableResource resource = viz::TransferableResource::Make(
        shared_image,
        viz::TransferableResource::ResourceSource::kTileRasterTask, sync_token);

    resource_id_ = resource_provider_->ImportResource(std::move(resource),
                                                      base::DoNothing());
    SetBounds(gfx::Size(10, 10));
    SetDrawsContent(true);
  }

  void ReleaseResources() override {
    resource_provider_->RemoveImportedResource(resource_id_);
  }

  void AppendQuads(const AppendQuadsContext& context,
                   viz::CompositorRenderPass* render_pass,
                   AppendQuadsData* append_quads_data) override {
    quads_appended_ = true;

    gfx::Rect opaque_rect;
    if (contents_opaque()) {
      opaque_rect = quad_rect_;
    } else {
      opaque_rect = opaque_content_rect_;
    }
    gfx::Rect visible_quad_rect = quad_visible_rect_;
    bool needs_blending = !opaque_rect.Contains(visible_quad_rect);

    viz::SharedQuadState* shared_quad_state =
        render_pass->CreateAndAppendSharedQuadState();
    PopulateSharedQuadState(shared_quad_state, contents_opaque());

    auto* test_blending_draw_quad =
        render_pass->CreateAndAppendDrawQuad<viz::TileDrawQuad>();
    test_blending_draw_quad->SetNew(
        shared_quad_state, quad_rect_, visible_quad_rect, needs_blending,
        resource_id_, gfx::RectF(0, 0, 1, 1), false, false);

    EXPECT_EQ(blend_, test_blending_draw_quad->ShouldDrawWithBlending());
    EXPECT_EQ(has_render_surface_,
              GetRenderSurface(this) != GetRenderSurface(comparison_layer_));
  }

  void SetExpectation(bool blend,
                      bool has_render_surface,
                      LayerImpl* comparison_layer) {
    blend_ = blend;
    has_render_surface_ = has_render_surface;
    comparison_layer_ = comparison_layer;
    quads_appended_ = false;
  }

  bool quads_appended() const { return quads_appended_; }

  void SetQuadRect(const gfx::Rect& rect) { quad_rect_ = rect; }
  void SetQuadVisibleRect(const gfx::Rect& rect) { quad_visible_rect_ = rect; }
  void SetOpaqueContentRect(const gfx::Rect& rect) {
    opaque_content_rect_ = rect;
  }

 private:
  raw_ptr<viz::ClientResourceProvider> resource_provider_;
  bool blend_ = false;
  bool has_render_surface_ = false;
  raw_ptr<LayerImpl> comparison_layer_;
  bool quads_appended_ = false;
  gfx::Rect quad_rect_;
  gfx::Rect opaque_content_rect_;
  gfx::Rect quad_visible_rect_;
  viz::ResourceId resource_id_;
  scoped_refptr<gpu::TestSharedImageInterface> shared_image_interface_;
};

class LayerTreeHostImplViewportCoveredTest
    : public ClientModeLayerTreeHostImplTest {
 protected:
  LayerTreeHostImplViewportCoveredTest() : child_(nullptr) {}

  std::unique_ptr<LayerTreeFrameSink> CreateFakeLayerTreeFrameSink(
      bool software) {
    if (software) {
      return FakeLayerTreeFrameSink::CreateSoftware();
    }
    return FakeLayerTreeFrameSink::Create3d();
  }

  void SetupActiveTreeLayers() {
    host_impl_->active_tree()->set_background_color(SkColors::kGray);
    LayerImpl* root = SetupDefaultRootLayer(viewport_size_);
    child_ = AddLayer<BlendStateCheckLayer>(host_impl_->active_tree(),
                                            host_impl_->resource_provider());
    child_->SetExpectation(false, false, root);
    child_->SetContentsOpaque(true);
    CopyProperties(root, child_);
    UpdateDrawProperties(host_impl_->active_tree());
  }

  void SetLayerGeometry(const gfx::Rect& layer_rect) {
    child_->SetBounds(layer_rect.size());
    child_->SetQuadRect(gfx::Rect(layer_rect.size()));
    child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
    child_->SetOffsetToTransformParent(
        gfx::Vector2dF(layer_rect.OffsetFromOrigin()));
  }

  // Expect no gutter rects.
  void TestLayerCoversFullViewport() {
    SetLayerGeometry(gfx::Rect(viewport_size_));

    TestFrameData frame;
    EXPECT_EQ(DrawResult::kSuccess, host_impl_->PrepareToDraw(&frame));
    ASSERT_EQ(1u, frame.render_passes.size());

    EXPECT_EQ(0u, CountGutterQuads(frame.render_passes[0]->quad_list));
    EXPECT_EQ(1u, frame.render_passes[0]->quad_list.size());
    ValidateTextureDrawQuads(frame.render_passes[0]->quad_list);

    VerifyQuadsExactlyCoverViewport(frame.render_passes[0]->quad_list);
    host_impl_->DidDrawAllLayers(frame);
  }

  // Expect fullscreen gutter rect.
  void SetUpEmptylayer() { SetLayerGeometry(gfx::Rect()); }

  void VerifyEmptyLayerRenderPasses(
      const viz::CompositorRenderPassList& render_passes) {
    ASSERT_EQ(1u, render_passes.size());

    EXPECT_EQ(1u, CountGutterQuads(render_passes[0]->quad_list));
    EXPECT_EQ(1u, render_passes[0]->quad_list.size());
    ValidateTextureDrawQuads(render_passes[0]->quad_list);

    VerifyQuadsExactlyCoverViewport(render_passes[0]->quad_list);
  }

  void TestEmptyLayer() {
    SetUpEmptylayer();
    DrawFrame();
  }

  void TestEmptyLayerWithOnDraw() {
    SetUpEmptylayer();
    gfx::Transform identity;
    gfx::Rect viewport(viewport_size_);
    bool resourceless_software_draw = true;
    host_impl_->OnDraw(identity, viewport, resourceless_software_draw, false);
    VerifyEmptyLayerRenderPasses(last_on_draw_render_passes_);
  }

  // Expect four surrounding gutter rects.
  void SetUpLayerInMiddleOfViewport() {
    SetLayerGeometry(gfx::Rect(500, 500, 200, 200));
  }

  void VerifyLayerInMiddleOfViewport(
      const viz::CompositorRenderPassList& render_passes) {
    ASSERT_EQ(1u, render_passes.size());

    EXPECT_EQ(4u, CountGutterQuads(render_passes[0]->quad_list));
    EXPECT_EQ(5u, render_passes[0]->quad_list.size());
    ValidateTextureDrawQuads(render_passes[0]->quad_list);

    VerifyQuadsExactlyCoverViewport(render_passes[0]->quad_list);
  }

  void TestLayerInMiddleOfViewport() {
    SetUpLayerInMiddleOfViewport();
    DrawFrame();
  }

  void TestLayerInMiddleOfViewportWithOnDraw() {
    SetUpLayerInMiddleOfViewport();
    gfx::Transform identity;
    gfx::Rect viewport(viewport_size_);
    bool resourceless_software_draw = true;
    host_impl_->OnDraw(identity, viewport, resourceless_software_draw, false);
    VerifyLayerInMiddleOfViewport(last_on_draw_render_passes_);
  }

  // Expect no gutter rects.
  void SetUpLayerIsLargerThanViewport() {
    SetLayerGeometry(
        gfx::Rect(viewport_size_.width() + 10, viewport_size_.height() + 10));
  }

  void VerifyLayerIsLargerThanViewport(
      const viz::CompositorRenderPassList& render_passes) {
    ASSERT_EQ(1u, render_passes.size());

    EXPECT_EQ(0u, CountGutterQuads(render_passes[0]->quad_list));
    EXPECT_EQ(1u, render_passes[0]->quad_list.size());
    ValidateTextureDrawQuads(render_passes[0]->quad_list);
  }

  void TestLayerIsLargerThanViewport() {
    SetUpLayerIsLargerThanViewport();
    DrawFrame();
  }

  void TestLayerIsLargerThanViewportWithOnDraw() {
    SetUpLayerIsLargerThanViewport();
    gfx::Transform identity;
    gfx::Rect viewport(viewport_size_);
    bool resourceless_software_draw = true;
    host_impl_->OnDraw(identity, viewport, resourceless_software_draw, false);
    VerifyLayerIsLargerThanViewport(last_on_draw_render_passes_);
  }

  void DidActivateSyncTree() override {
    LayerTreeHostImplTest::DidActivateSyncTree();
    did_activate_pending_tree_ = true;
  }

  void set_gutter_quad_material(viz::DrawQuad::Material material) {
    gutter_quad_material_ = material;
  }
  void set_gutter_texture_size(const gfx::Size& gutter_texture_size) {
    gutter_texture_size_ = gutter_texture_size;
  }

 protected:
  size_t CountGutterQuads(const viz::QuadList& quad_list) {
    size_t num_gutter_quads = 0;
    for (auto* quad : quad_list) {
      num_gutter_quads += (quad->material == gutter_quad_material_) ? 1 : 0;
    }
    return num_gutter_quads;
  }

  void VerifyQuadsExactlyCoverViewport(const viz::QuadList& quad_list) {
    VerifyQuadsExactlyCoverRect(quad_list,
                                gfx::Rect(DipSizeToPixelSize(viewport_size_)));
  }

  // Make sure that the texture coordinates match their expectations.
  void ValidateTextureDrawQuads(const viz::QuadList& quad_list) {
    for (auto* quad : quad_list) {
      if (quad->material != viz::DrawQuad::Material::kTextureContent) {
        continue;
      }
      const viz::TextureDrawQuad* texture_quad =
          viz::TextureDrawQuad::MaterialCast(quad);
      gfx::SizeF gutter_texture_size_pixels =
          gfx::ScaleSize(gfx::SizeF(gutter_texture_size_),
                         host_impl_->active_tree()->device_scale_factor());
      const gfx::RectF texture_quad_tex_coords(
          texture_quad->GetNormalizedTexCoords(
              gfx::ToRoundedSize(gutter_texture_size_pixels)));
      EXPECT_EQ(texture_quad_tex_coords.x(),
                texture_quad->rect.x() / gutter_texture_size_pixels.width());
      EXPECT_EQ(texture_quad_tex_coords.y(),
                texture_quad->rect.y() / gutter_texture_size_pixels.height());
      EXPECT_EQ(
          texture_quad_tex_coords.right(),
          texture_quad->rect.right() / gutter_texture_size_pixels.width());
      EXPECT_EQ(
          texture_quad_tex_coords.bottom(),
          texture_quad->rect.bottom() / gutter_texture_size_pixels.height());
    }
  }

  viz::DrawQuad::Material gutter_quad_material_ =
      viz::DrawQuad::Material::kSolidColor;
  gfx::Size gutter_texture_size_;
  gfx::Size viewport_size_;
  raw_ptr<BlendStateCheckLayer> child_;
  bool did_activate_pending_tree_ = false;
};

// These tests are only relevant for CommitToPendingTree since they are checking
// conditions that would need to be queried from the Viz process.
INSTANTIATE_COMMIT_TO_TREE_BASE_TEST_P(LayerTreeHostImplViewportCoveredTest,
                                       CommitToPendingTree);

TEST_P(LayerTreeHostImplViewportCoveredTest, ViewportCovered) {
  viewport_size_ = gfx::Size(1000, 1000);

  bool software = false;
  CreateHostImpl(DefaultSettings(), CreateFakeLayerTreeFrameSink(software));
  SetupActiveTreeLayers();
  EXPECT_SCOPED(TestLayerCoversFullViewport());
  EXPECT_SCOPED(TestEmptyLayer());
  EXPECT_SCOPED(TestLayerInMiddleOfViewport());
  EXPECT_SCOPED(TestLayerIsLargerThanViewport());
}

TEST_P(LayerTreeHostImplViewportCoveredTest, ViewportCoveredScaled) {
  viewport_size_ = gfx::Size(1000, 1000);

  bool software = false;
  CreateHostImpl(DefaultSettings(), CreateFakeLayerTreeFrameSink(software));

  host_impl_->active_tree()->SetDeviceScaleFactor(2);
  SetupActiveTreeLayers();
  EXPECT_SCOPED(TestLayerCoversFullViewport());
  EXPECT_SCOPED(TestEmptyLayer());
  EXPECT_SCOPED(TestLayerInMiddleOfViewport());
  EXPECT_SCOPED(TestLayerIsLargerThanViewport());
}

TEST_P(LayerTreeHostImplViewportCoveredTest, ActiveTreeGrowViewportInvalid) {
  viewport_size_ = gfx::Size(1000, 1000);

  bool software = true;
  CreateHostImpl(DefaultSettings(), CreateFakeLayerTreeFrameSink(software));

  // Pending tree to force active_tree size invalid. Not used otherwise.
  CreatePendingTree();

  SetupActiveTreeLayers();
  EXPECT_SCOPED(TestEmptyLayerWithOnDraw());
  EXPECT_SCOPED(TestLayerInMiddleOfViewportWithOnDraw());
  EXPECT_SCOPED(TestLayerIsLargerThanViewportWithOnDraw());
}

TEST_P(LayerTreeHostImplViewportCoveredTest, ActiveTreeShrinkViewportInvalid) {
  viewport_size_ = gfx::Size(1000, 1000);

  bool software = true;
  CreateHostImpl(DefaultSettings(), CreateFakeLayerTreeFrameSink(software));

  // Set larger viewport and activate it to active tree.
  CreatePendingTree();
  gfx::Size larger_viewport(viewport_size_.width() + 100,
                            viewport_size_.height() + 100);
  host_impl_->active_tree()->SetDeviceViewportRect(
      gfx::Rect(DipSizeToPixelSize(larger_viewport)));
  host_impl_->ActivateSyncTree();
  EXPECT_TRUE(did_activate_pending_tree_);

  // Shrink pending tree viewport without activating.
  CreatePendingTree();
  host_impl_->active_tree()->SetDeviceViewportRect(
      gfx::Rect(DipSizeToPixelSize(viewport_size_)));

  SetupActiveTreeLayers();
  EXPECT_SCOPED(TestEmptyLayerWithOnDraw());
  EXPECT_SCOPED(TestLayerInMiddleOfViewportWithOnDraw());
  EXPECT_SCOPED(TestLayerIsLargerThanViewportWithOnDraw());
}

// Test that TotalFrameCounter resets itself under certain conditions
TEST_P(ClientModeLayerTreeHostImplTest, FrameCounterReset) {
  FrameSorter* frame_sorter = host_impl_->frame_sorter_for_testing();
  EXPECT_EQ(frame_sorter->total_frames(), 0u);
  FrameInfo frame_info;
  frame_info.final_state = FrameInfo::FrameFinalState::kPresentedAll;
  frame_sorter->AddFrameInfoToBuffer(frame_info);
  EXPECT_EQ(frame_sorter->total_frames(), 1u);

  auto interval = base::Milliseconds(16);
  base::TimeTicks now = base::TimeTicks::Now();
  auto deadline = now + interval;
  viz::BeginFrameArgs args = viz::BeginFrameArgs::Create(
      BEGINFRAME_FROM_HERE, 1u /*source_id*/, 2u /*sequence_number*/, now,
      deadline, interval, viz::BeginFrameArgs::NORMAL);

  frame_sorter->AddNewFrame(args);
  // Delegates to DFC::AddSortedFrame, which calls DFC::OnEndFrame.
  frame_sorter->AddFrameResult(
      args, CreateFakeFrameInfo(FrameInfo::FrameFinalState::kDropped));
  // FCP not received, so the total_smoothness_dropped_ won't increase.
  EXPECT_EQ(frame_sorter->total_dropped(), 0u);

  BeginMainFrameMetrics begin_frame_metrics;
  begin_frame_metrics.should_measure_smoothness = true;
  {
    static_cast<ClientLayerTreeHostImpl*>(host_impl_.get())
        ->ReadyToCommit(/*scroll_and_viewport_changes_synced=*/true,
                        &begin_frame_metrics, /*commit_timeout=*/false);
  }
  frame_sorter->AddNewFrame(args);
  // Delegates to DFC::AddSortedFrame, which calls DFC::OnEndFrame.
  frame_sorter->AddFrameResult(
      args, CreateFakeFrameInfo(FrameInfo::FrameFinalState::kDropped));
  frame_sorter->AddFrameInfoToBuffer(frame_info);
  {
    static_cast<ClientLayerTreeHostImpl*>(host_impl_.get())
        ->SetActiveURL(GURL(), 1u);
  }
  EXPECT_EQ(frame_sorter->total_frames(), 0u);
  EXPECT_EQ(frame_sorter->total_dropped(), 0u);
}

// Test that TotalFrameCounter does not reset itself under certain conditions
TEST_P(ClientModeLayerTreeHostImplTest, FrameCounterNotReset) {
  FrameSorter* frame_sorter = host_impl_->frame_sorter_for_testing();
  EXPECT_EQ(frame_sorter->total_frames(), 0u);

  auto interval = base::Milliseconds(16);
  base::TimeTicks now = base::TimeTicks::Now();
  auto deadline = now + interval;
  viz::BeginFrameArgs arg1 = viz::BeginFrameArgs::Create(
      BEGINFRAME_FROM_HERE, 1u /*source_id*/, 1u /*sequence_number*/, now,
      deadline, interval, viz::BeginFrameArgs::NORMAL);
  BeginMainFrameMetrics begin_frame_metrics;
  begin_frame_metrics.should_measure_smoothness = true;
  {
    static_cast<ClientLayerTreeHostImpl*>(host_impl_.get())
        ->ReadyToCommit(/*scroll_and_viewport_changes_synced=*/true,
                        &begin_frame_metrics, /*commit_timeout=*/false);
  }
  EXPECT_EQ(frame_sorter->total_frames(), 0u);
  FrameInfo frame_info;
  frame_info.final_state = FrameInfo::FrameFinalState::kPresentedAll;
  frame_sorter->AddFrameInfoToBuffer(frame_info);
  EXPECT_EQ(frame_sorter->total_frames(), 1u);

  now = deadline;
  deadline = now + interval;
  viz::BeginFrameArgs arg2 = viz::BeginFrameArgs::Create(
      BEGINFRAME_FROM_HERE, 1u /*source_id*/, 2u /*sequence_number*/, now,
      deadline, interval, viz::BeginFrameArgs::NORMAL);
  // Consecutive BeginFrameMetrics with the same |should_measure_smoothness|
  // flag should not reset the counter.
  {
    static_cast<ClientLayerTreeHostImpl*>(host_impl_.get())
        ->ReadyToCommit(/*scroll_and_viewport_changes_synced=*/true,
                        &begin_frame_metrics, /*commit_timeout=*/false);
  }
  EXPECT_EQ(frame_sorter->total_frames(), 1u);
}

}  // namespace
}  // namespace cc
