chromium/android_webview/browser/gfx/browser_view_renderer_unittest.cc

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

#include "android_webview/browser/gfx/browser_view_renderer.h"

#include <map>
#include <memory>
#include <queue>
#include <utility>

#include "android_webview/browser/gfx/child_frame.h"
#include "android_webview/browser/gfx/compositor_frame_consumer.h"
#include "android_webview/browser/gfx/render_thread_manager.h"
#include "android_webview/browser/gfx/test/rendering_test.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/task/single_thread_task_runner.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "content/public/test/test_synchronous_compositor_android.h"

namespace android_webview {

class SmokeTest : public RenderingTest {
  void StartTest() override {
    browser_view_renderer_->PostInvalidate(ActiveCompositor());
  }

  void DidDrawOnRT() override { EndTest(); }
};

RENDERING_TEST_F(SmokeTest);

// Test the case where SynchronousCompositor is constructed after the RVH that
// owns it is switched to be active.
class ActiveCompositorSwitchBeforeConstructionTest : public RenderingTest {
 public:
  ActiveCompositorSwitchBeforeConstructionTest()
      : on_draw_count_(0), new_compositor_(nullptr) {}
  void StartTest() override {
    browser_view_renderer_->PostInvalidate(ActiveCompositor());
  }

  void DidOnDraw(bool success) override {
    on_draw_count_++;
    switch (on_draw_count_) {
      case 1:
        EXPECT_TRUE(success);
        // Change compositor here. And do another ondraw.
        // The previous active compositor id is 1, 0, now change it to 1, 1.
        browser_view_renderer_->SetActiveFrameSinkId(viz::FrameSinkId(1, 1));
        browser_view_renderer_->PostInvalidate(ActiveCompositor());
        break;
      case 2:
        // The 2nd ondraw is skipped because there is no active compositor at
        // the moment.
        EXPECT_FALSE(success);
        new_compositor_ = std::make_unique<content::TestSynchronousCompositor>(
            viz::FrameSinkId(1, 1));
        new_compositor_->SetClient(browser_view_renderer_.get());
        EXPECT_EQ(ActiveCompositor(), new_compositor_.get());
        browser_view_renderer_->PostInvalidate(ActiveCompositor());
        break;
      case 3:
        EXPECT_TRUE(success);
        compositor_ = std::move(new_compositor_);

        EXPECT_EQ(ActiveCompositor(), compositor_.get());
        browser_view_renderer_->PostInvalidate(ActiveCompositor());
        break;
      case 4:
        EXPECT_TRUE(success);
        EndTest();
    }
  }

 private:
  int on_draw_count_;
  std::unique_ptr<content::TestSynchronousCompositor> new_compositor_;
};

RENDERING_TEST_F(ActiveCompositorSwitchBeforeConstructionTest);

// Test the case where SynchronousCompositor is constructed before the RVH that
// owns it is switched to be active.
class ActiveCompositorSwitchAfterConstructionTest : public RenderingTest {
 public:
  ActiveCompositorSwitchAfterConstructionTest()
      : on_draw_count_(0), new_compositor_(nullptr) {}
  void StartTest() override {
    browser_view_renderer_->PostInvalidate(ActiveCompositor());
  }

  void DidOnDraw(bool success) override {
    on_draw_count_++;
    switch (on_draw_count_) {
      case 1:
        EXPECT_TRUE(success);
        // Create a new compositor here. And switch it to be active.  And then
        // do another ondraw.
        new_compositor_ = std::make_unique<content::TestSynchronousCompositor>(
            viz::FrameSinkId(1, 1));
        new_compositor_->SetClient(browser_view_renderer_.get());
        browser_view_renderer_->SetActiveFrameSinkId(viz::FrameSinkId(1, 1));

        EXPECT_EQ(ActiveCompositor(), new_compositor_.get());
        browser_view_renderer_->PostInvalidate(ActiveCompositor());
        break;
      case 2:
        EXPECT_TRUE(success);
        compositor_ = std::move(new_compositor_);

        EXPECT_EQ(ActiveCompositor(), compositor_.get());
        browser_view_renderer_->PostInvalidate(ActiveCompositor());
        break;
      case 3:
        EXPECT_TRUE(success);
        EndTest();
    }
  }

 private:
  int on_draw_count_;
  std::unique_ptr<content::TestSynchronousCompositor> new_compositor_;
};

RENDERING_TEST_F(ActiveCompositorSwitchAfterConstructionTest);

class ClearViewTest : public RenderingTest {
 public:
  ClearViewTest() : on_draw_count_(0) {}

  void StartTest() override {
    browser_view_renderer_->PostInvalidate(ActiveCompositor());
    browser_view_renderer_->ClearView();
  }

  void DidOnDraw(bool success) override {
    on_draw_count_++;
    if (on_draw_count_ == 1) {
      // First OnDraw should be skipped due to ClearView.
      EXPECT_FALSE(success);
      browser_view_renderer_->DidUpdateContent(
          ActiveCompositor());  // Unset ClearView.
      browser_view_renderer_->PostInvalidate(ActiveCompositor());
    } else {
      // Following OnDraws should succeed.
      EXPECT_TRUE(success);
    }
  }

  void DidDrawOnRT() override { EndTest(); }

 private:
  int on_draw_count_;
};

RENDERING_TEST_F(ClearViewTest);

class TestAnimateInAndOutOfScreen : public RenderingTest {
 public:
  TestAnimateInAndOutOfScreen() : on_draw_count_(0), draw_gl_count_on_rt_(0) {}

  void StartTest() override {
    initial_constraints_ = ParentCompositorDrawConstraints(
        window_->surface_size(), gfx::Transform());
    new_constraints_ = ParentCompositorDrawConstraints(window_->surface_size(),
                                                       gfx::Transform());
    new_constraints_.transform.Scale(2.0, 2.0);
    browser_view_renderer_->PostInvalidate(ActiveCompositor());
  }

  void WillOnDraw() override {
    RenderingTest::WillOnDraw();
    // Step 0: A single onDraw on screen. The parent draw constraints
    // of the BVR will updated to be the initial constraints.
    // Step 1: A single onDrraw off screen. The parent draw constraints of the
    // BVR will be updated to the new constraints.
    // Step 2: This onDraw is to introduce the DrawGL that animates the
    // webview onto the screen on render thread. End the test when the parent
    // draw constraints of BVR is updated to initial constraints.
    if (on_draw_count_ == 1 || on_draw_count_ == 2)
      browser_view_renderer_->PrepareToDraw(gfx::Point(), gfx::Rect());
  }

  void DidOnDraw(bool success) override {
    EXPECT_TRUE(success);
    on_draw_count_++;
  }

  bool WillDrawOnRT(HardwareRendererDrawParams* params) override {
    if (draw_gl_count_on_rt_ == 1) {
      draw_gl_count_on_rt_++;
      ui_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(&RenderingTest::PostInvalidate, base::Unretained(this),
                         /*inside_vsync=*/false));
      return false;
    }

    params->width = window_->surface_size().width();
    params->height = window_->surface_size().height();

    gfx::Transform transform;
    if (draw_gl_count_on_rt_ == 0)
      transform = new_constraints_.transform;

    transform.GetColMajorF(params->transform);
    return true;
  }

  void DidDrawOnRT() override { draw_gl_count_on_rt_++; }

  bool DrawConstraintsEquals(
      const ParentCompositorDrawConstraints& constraints1,
      const ParentCompositorDrawConstraints& constraints2) {
    return constraints1 == constraints2;
  }

  void OnParentDrawDataUpdated() override {
    ParentCompositorDrawConstraints constraints;
    viz::FrameSinkId frame_sink_id;
    viz::FrameTimingDetailsMap timing_details;
    uint32_t frame_token = 0u;
    base::TimeDelta preferred_frame_interval;
    GetCompositorFrameConsumer()->TakeParentDrawDataOnUI(
        &constraints, &frame_sink_id, &timing_details, &frame_token,
        &preferred_frame_interval);
    switch (on_draw_count_) {
      case 0u:
        // This OnParentDrawDataUpdated is generated by
        // connecting the compositor frame consumer to the producer.
        break;
      case 1u:
        EXPECT_TRUE(DrawConstraintsEquals(constraints, new_constraints_));
        break;
      case 3u:
        EXPECT_TRUE(DrawConstraintsEquals(constraints, initial_constraints_));
        EndTest();
        break;
      // There will be a following 4th onDraw. But the hardware renderer won't
      // post back the draw constraints in DrawGL because the constraints
      // don't change.
      default:
        FAIL();
    }
  }

 private:
  int on_draw_count_;
  int draw_gl_count_on_rt_;
  ParentCompositorDrawConstraints initial_constraints_;
  ParentCompositorDrawConstraints new_constraints_;
};

RENDERING_TEST_F(TestAnimateInAndOutOfScreen);

class TestAnimateOnScreenWithoutOnDraw : public RenderingTest {
 public:
  void StartTest() override {
    browser_view_renderer_->PostInvalidate(ActiveCompositor());
  }

  void WillOnDraw() override {
    RenderingTest::WillOnDraw();
    // Set empty tile viewport on first frame.
    browser_view_renderer_->PrepareToDraw(gfx::Point(), gfx::Rect());
  }

  void DidOnDraw(bool success) override {
    EXPECT_TRUE(success);
    on_draw_count_++;
  }

  bool WillDrawOnRT(HardwareRendererDrawParams* params) override {
    will_draw_on_rt_count_++;
    // What happens in practice is draw functor is skipped initially since
    // it is offscreen so entirely clipped. Then later, the webview is moved
    // onscreen without another OnDrawon UI thread, and draw functor is called
    // with non-empty clip. Here in the test we pretend this second draw happens
    // immediately.
    bool result = RenderingTest::WillDrawOnRT(params);
    assertNonEmptyClip(params);
    return result;
  }

  void OnParentDrawDataUpdated() override {
    switch (on_draw_count_) {
      case 0:
        // This OnParentDrawDataUpdated is generated by
        // connecting the compositor frame consumer to the producer.
        break;
      case 1:
        // DrawOnRT skipped for frame 1 so this should not happen.
        EXPECT_TRUE(window_->on_draw_hardware_pending());
        EndTest();
        break;
      case 2:
        // Might happen due to invalidate on_draw_hardware_pending from
        // previous frame.
        break;
      default:
        FAIL();
    }
  }

 private:
  void assertNonEmptyClip(HardwareRendererDrawParams* params) {
    gfx::Rect clip(params->clip_left, params->clip_top,
                   params->clip_right - params->clip_left,
                   params->clip_bottom - params->clip_top);
    ASSERT_FALSE(clip.IsEmpty());
  }

  int on_draw_count_ = 0;
  int will_draw_on_rt_count_ = 0;
};

RENDERING_TEST_F(TestAnimateOnScreenWithoutOnDraw);

class CompositorNoFrameTest : public RenderingTest {
 public:
  CompositorNoFrameTest() : on_draw_count_(0) {}

  void StartTest() override {
    browser_view_renderer_->PostInvalidate(ActiveCompositor());
  }

  void WillOnDraw() override {
    if (0 == on_draw_count_) {
      // No frame from compositor.
    } else if (1 == on_draw_count_) {
      compositor_->SetHardwareFrame(0u, ConstructEmptyFrame());
    } else if (2 == on_draw_count_) {
      // No frame from compositor.
    }
    // There may be trailing invalidates.
  }

  void DidOnDraw(bool success) override {
    // OnDraw should succeed even when there are no frames from compositor.
    EXPECT_TRUE(success);
    if (0 == on_draw_count_) {
      browser_view_renderer_->PostInvalidate(ActiveCompositor());
    } else if (1 == on_draw_count_) {
      browser_view_renderer_->PostInvalidate(ActiveCompositor());
    } else if (2 == on_draw_count_) {
      EndTest();
    }
    on_draw_count_++;
  }

 private:
  int on_draw_count_;
};

RENDERING_TEST_F(CompositorNoFrameTest);

class ClientIsVisibleOnConstructionTest : public RenderingTest {
  void SetUpTestHarness() override {
    browser_view_renderer_ = std::make_unique<BrowserViewRenderer>(
        this, base::SingleThreadTaskRunner::GetCurrentDefault(),
        base::SingleThreadTaskRunner::GetCurrentDefault());
  }

  void StartTest() override {
    EXPECT_FALSE(browser_view_renderer_->attached_to_window());
    EXPECT_FALSE(browser_view_renderer_->window_visible_for_tests());
    EXPECT_TRUE(browser_view_renderer_->IsClientVisible());
    EndTest();
  }
};

RENDERING_TEST_F(ClientIsVisibleOnConstructionTest);

class ClientIsVisibleAfterAttachTest : public RenderingTest {
  void StartTest() override {
    EXPECT_TRUE(browser_view_renderer_->attached_to_window());
    EXPECT_TRUE(browser_view_renderer_->window_visible_for_tests());

    EXPECT_TRUE(browser_view_renderer_->IsClientVisible());
    EndTest();
  }
};

RENDERING_TEST_F(ClientIsVisibleAfterAttachTest);

class ClientIsInvisibleAfterWindowGoneTest : public RenderingTest {
  void StartTest() override {
    browser_view_renderer_->SetWindowVisibility(false);
    EXPECT_FALSE(browser_view_renderer_->IsClientVisible());
    EndTest();
  }
};

RENDERING_TEST_F(ClientIsInvisibleAfterWindowGoneTest);

class ClientIsInvisibleAfterDetachTest : public RenderingTest {
  void StartTest() override {
    browser_view_renderer_->OnDetachedFromWindow();
    EXPECT_FALSE(browser_view_renderer_->IsClientVisible());
    EndTest();
  }
};

RENDERING_TEST_F(ClientIsInvisibleAfterDetachTest);

class ResourceRenderingTest : public RenderingTest {
 public:
  using ResourceCountMap = std::map<viz::ResourceId, int>;
  using LayerTreeFrameSinkResourceCountMap =
      std::map<uint32_t, ResourceCountMap>;

  virtual std::unique_ptr<content::SynchronousCompositor::Frame> GetFrame(
      int frame_number) = 0;

  void StartTest() override {
    frame_number_ = 0;
    AdvanceFrame();
  }

  void WillOnDraw() override {
    if (next_frame_) {
      compositor_->SetHardwareFrame(next_frame_->layer_tree_frame_sink_id,
                                    std::move(next_frame_->frame));
    }
  }

  void DidOnDraw(bool success) override {
    EXPECT_EQ(next_frame_ != nullptr, success);
    if (!AdvanceFrame()) {
      ui_task_runner_->PostTask(
          FROM_HERE, base::BindOnce(&ResourceRenderingTest::CheckResults,
                                    base::Unretained(this)));
    }
  }

  LayerTreeFrameSinkResourceCountMap GetReturnedResourceCounts() {
    LayerTreeFrameSinkResourceCountMap counts;
    content::TestSynchronousCompositor::FrameAckArray returned_resources_array;
    compositor_->SwapReturnedResources(&returned_resources_array);
    for (const auto& resources : returned_resources_array) {
      for (const auto& returned_resource : resources.resources) {
        counts[resources.layer_tree_frame_sink_id][returned_resource.id] +=
            returned_resource.count;
      }
    }
    return counts;
  }

  virtual void CheckResults() = 0;

 private:
  bool AdvanceFrame() {
    next_frame_ = GetFrame(frame_number_++);
    if (next_frame_) {
      browser_view_renderer_->PostInvalidate(ActiveCompositor());
      return true;
    }
    return false;
  }

  std::unique_ptr<content::SynchronousCompositor::Frame> next_frame_;
  int frame_number_;
};

class SwitchLayerTreeFrameSinkIdTest : public ResourceRenderingTest {
  struct FrameInfo {
    FrameInfo(uint32_t frame_sink_id, viz::ResourceId id)
        : layer_tree_frame_sink_id(frame_sink_id), resource_id(id) {}

    uint32_t layer_tree_frame_sink_id;
    viz::ResourceId resource_id;  // Each frame contains a single resource.
  };

  std::unique_ptr<content::SynchronousCompositor::Frame> GetFrame(
      int frame_number) override {
    static const FrameInfo infos[] = {
        // First output surface.
        {0u, viz::ResourceId(1u)},
        {0u, viz::ResourceId(1u)},
        {0u, viz::ResourceId(2u)},
        {0u, viz::ResourceId(2u)},
        {0u, viz::ResourceId(3u)},
        {0u, viz::ResourceId(3u)},
        {0u, viz::ResourceId(4u)},
        // Second output surface.
        {1u, viz::ResourceId(1u)},
        {1u, viz::ResourceId(1u)},
        {1u, viz::ResourceId(2u)},
        {1u, viz::ResourceId(2u)},
        {1u, viz::ResourceId(3u)},
        {1u, viz::ResourceId(3u)},
        {1u, viz::ResourceId(4u)},
    };
    if (frame_number >= static_cast<int>(std::size(infos))) {
      return nullptr;
    }

    std::unique_ptr<content::SynchronousCompositor::Frame> frame(
        new content::SynchronousCompositor::Frame);
    frame->layer_tree_frame_sink_id =
        infos[frame_number].layer_tree_frame_sink_id;
    frame->frame = ConstructFrame(infos[frame_number].resource_id);

    if (last_layer_tree_frame_sink_id_ !=
        infos[frame_number].layer_tree_frame_sink_id) {
      expected_return_count_.clear();
      last_layer_tree_frame_sink_id_ =
          infos[frame_number].layer_tree_frame_sink_id;
    }
    ++expected_return_count_[infos[frame_number].resource_id];
    return frame;
  }

  void StartTest() override {
    last_layer_tree_frame_sink_id_ = -1U;
    ResourceRenderingTest::StartTest();
  }

  void CheckResults() override {
    window_->Detach();
    functor_->ReleaseOnUIWithInvoke();
    ui_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&SwitchLayerTreeFrameSinkIdTest::PostedCheckResults,
                       base::Unretained(this)));
  }

  void PostedCheckResults() {
    // Make sure resources for the last output surface are returned.
    EXPECT_EQ(expected_return_count_,
              GetReturnedResourceCounts()[last_layer_tree_frame_sink_id_]);
    EndTest();
  }

 private:
  uint32_t last_layer_tree_frame_sink_id_;
  ResourceCountMap expected_return_count_;
};

RENDERING_TEST_F(SwitchLayerTreeFrameSinkIdTest);

class RenderThreadManagerDeletionTest : public ResourceRenderingTest {
  std::unique_ptr<content::SynchronousCompositor::Frame> GetFrame(
      int frame_number) override {
    if (frame_number > 0) {
      return nullptr;
    }

    const uint32_t layer_tree_frame_sink_id = 0u;
    const viz::ResourceId resource_id =
        static_cast<viz::ResourceId>(frame_number);

    std::unique_ptr<content::SynchronousCompositor::Frame> frame(
        new content::SynchronousCompositor::Frame);
    frame->layer_tree_frame_sink_id = layer_tree_frame_sink_id;
    frame->frame = ConstructFrame(resource_id);
    ++expected_return_count_[layer_tree_frame_sink_id][resource_id];
    return frame;
  }

  void CheckResults() override {
    functor_.reset();
    ui_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&RenderThreadManagerDeletionTest::PostedCheckResults,
                       base::Unretained(this)));
  }

  void PostedCheckResults() {
    // Make sure resources for the last frame are returned.
    EXPECT_EQ(expected_return_count_, GetReturnedResourceCounts());
    EndTest();
  }

 private:
  LayerTreeFrameSinkResourceCountMap expected_return_count_;
};

RENDERING_TEST_F(RenderThreadManagerDeletionTest);

class RenderThreadManagerDeletionOnRTTest : public ResourceRenderingTest {
  std::unique_ptr<content::SynchronousCompositor::Frame> GetFrame(
      int frame_number) override {
    if (frame_number > 0) {
      return nullptr;
    }

    const uint32_t layer_tree_frame_sink_id = 0u;
    const viz::ResourceId resource_id =
        static_cast<viz::ResourceId>(frame_number);

    std::unique_ptr<content::SynchronousCompositor::Frame> frame(
        new content::SynchronousCompositor::Frame);
    frame->layer_tree_frame_sink_id = layer_tree_frame_sink_id;
    frame->frame = ConstructFrame(resource_id);
    ++expected_return_count_[layer_tree_frame_sink_id][resource_id];
    return frame;
  }

  void CheckResults() override {
    functor_->ReleaseOnUIWithoutInvoke(
        base::BindOnce(&RenderThreadManagerDeletionOnRTTest::PostedCheckResults,
                       base::Unretained(this)));
  }

  void PostedCheckResults() {
    // Make sure resources for the last frame are returned.
    EXPECT_EQ(expected_return_count_, GetReturnedResourceCounts());
    EndTest();
  }

 private:
  LayerTreeFrameSinkResourceCountMap expected_return_count_;
};

RENDERING_TEST_F(RenderThreadManagerDeletionOnRTTest);

class RenderThreadManagerSwitchTest : public ResourceRenderingTest {
  std::unique_ptr<content::SynchronousCompositor::Frame> GetFrame(
      int frame_number) override {
    switch (frame_number) {
      case 0: {
        // Draw a frame with initial RTM.
        break;
      }
      case 1: {
        // Switch to new RTM.
        std::unique_ptr<FakeFunctor> functor(new FakeFunctor);
        functor->Init(window_.get(),
                      std::make_unique<RenderThreadManager>(
                          base::SingleThreadTaskRunner::GetCurrentDefault()));
        browser_view_renderer_->SetCurrentCompositorFrameConsumer(
            functor->GetCompositorFrameConsumer());
        saved_functor_ = std::move(functor_);
        functor_ = std::move(functor);
        break;
      }
      case 2: {
        // Draw a frame with the new RTM, but also redraw the initial RTM.
        window_->RequestDrawGL(saved_functor_.get());
        break;
      }
      case 3: {
        // Switch back to the initial RTM, allowing the new RTM to be destroyed.
        functor_ = std::move(saved_functor_);
        browser_view_renderer_->SetCurrentCompositorFrameConsumer(
            functor_->GetCompositorFrameConsumer());
        break;
      }
      default:
        return nullptr;
    }

    const uint32_t layer_tree_frame_sink_id = 0u;
    const viz::ResourceId resource_id =
        static_cast<viz::ResourceId>(frame_number);

    std::unique_ptr<content::SynchronousCompositor::Frame> frame(
        new content::SynchronousCompositor::Frame);
    frame->layer_tree_frame_sink_id = layer_tree_frame_sink_id;
    frame->frame = ConstructFrame(resource_id);
    ++expected_return_count_[layer_tree_frame_sink_id][resource_id];
    return frame;
  }

  void CheckResults() override {
    functor_.reset();
    ui_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&RenderThreadManagerSwitchTest::PostedCheckResults,
                       base::Unretained(this)));
  }

  void PostedCheckResults() {
    // Make sure resources for all frames are returned.
    EXPECT_EQ(expected_return_count_, GetReturnedResourceCounts());
    EndTest();
  }

 private:
  std::unique_ptr<FakeFunctor> saved_functor_;
  LayerTreeFrameSinkResourceCountMap expected_return_count_;
};

RENDERING_TEST_F(RenderThreadManagerSwitchTest);

// Test for https://crbug.com/881458, this test is to make sure we will reach
// the maximal scroll offset.
class DidReachMaximalScrollOffsetTest : public RenderingTest {
 public:
  void StartTest() override {
    browser_view_renderer_->SetDipScale(kDipScale);
    gfx::PointF total_scroll_offset = kTotalScrollOffset;
    gfx::PointF total_max_scroll_offset = kTotalMaxScrollOffset;
    gfx::SizeF scrollable_size = kScrollableSize;
    total_scroll_offset.Scale(kDipScale);
    total_max_scroll_offset.Scale(kDipScale);
    scrollable_size.Scale(kDipScale);
    // |UpdateRootLayerState()| will call |SetTotalRootLayerScrollOffset()|.
    browser_view_renderer_->UpdateRootLayerState(
        ActiveCompositor(), total_scroll_offset, total_max_scroll_offset,
        scrollable_size, kPageScaleFactor, kMinPageScaleFactor,
        kMaxPageScaleFactor);
  }

  void ScrollContainerViewTo(const gfx::Point& new_value) override {
    EXPECT_EQ(kExpectedScrollOffset, new_value);
    EndTest();
  }

 private:
  static constexpr float kDipScale = 2.625f;
  static const gfx::PointF kTotalScrollOffset;
  static const gfx::PointF kTotalMaxScrollOffset;
  static const gfx::SizeF kScrollableSize;
  static constexpr float kPageScaleFactor = 1.f;
  // These two are not used in this test.
  static constexpr float kMinPageScaleFactor = 1.f;
  static constexpr float kMaxPageScaleFactor = 5.f;

  static const gfx::Point kExpectedScrollOffset;
};

// The current scroll offset in logical pixel, which is at the end.
const gfx::PointF DidReachMaximalScrollOffsetTest::kTotalScrollOffset =
    gfx::PointF(0.f, 6132.f);
// The maximum possible scroll offset in logical pixel.
const gfx::PointF DidReachMaximalScrollOffsetTest::kTotalMaxScrollOffset =
    gfx::PointF(0.f, 6132.f);
// This is what passed to CTS test, not used for this test.
const gfx::SizeF DidReachMaximalScrollOffsetTest::kScrollableSize =
    gfx::SizeF(412.f, 6712.f);
// In max_scroll_offset() we are using ceiling rounding for scaled scroll
// offset. Therefore ceiling(2.625 * 6132 = 16096.5) = 16097.
const gfx::Point DidReachMaximalScrollOffsetTest::kExpectedScrollOffset =
    gfx::Point(0, 16097);

RENDERING_TEST_F(DidReachMaximalScrollOffsetTest);

}  // namespace android_webview