blob: 7852770166a48c4c643278c78776d8090abd0978 [file] [log] [blame]
// Copyright 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/trees/layer_tree_host_impl.h"
#include <algorithm>
#include <cmath>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/containers/hash_tables.h"
#include "base/containers/scoped_ptr_hash_map.h"
#include "cc/animation/scrollbar_animation_controller_thinning.h"
#include "cc/base/math_util.h"
#include "cc/input/page_scale_animation.h"
#include "cc/input/top_controls_manager.h"
#include "cc/layers/append_quads_data.h"
#include "cc/layers/delegated_renderer_layer_impl.h"
#include "cc/layers/heads_up_display_layer_impl.h"
#include "cc/layers/io_surface_layer_impl.h"
#include "cc/layers/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/texture_layer_impl.h"
#include "cc/layers/tiled_layer_impl.h"
#include "cc/layers/video_layer_impl.h"
#include "cc/output/begin_frame_args.h"
#include "cc/output/compositor_frame_ack.h"
#include "cc/output/compositor_frame_metadata.h"
#include "cc/output/copy_output_request.h"
#include "cc/output/copy_output_result.h"
#include "cc/output/gl_renderer.h"
#include "cc/output/latency_info_swap_promise.h"
#include "cc/quads/render_pass_draw_quad.h"
#include "cc/quads/solid_color_draw_quad.h"
#include "cc/quads/texture_draw_quad.h"
#include "cc/quads/tile_draw_quad.h"
#include "cc/resources/layer_tiling_data.h"
#include "cc/test/animation_test_common.h"
#include "cc/test/begin_frame_args_test.h"
#include "cc/test/fake_layer_tree_host_impl.h"
#include "cc/test/fake_output_surface.h"
#include "cc/test/fake_output_surface_client.h"
#include "cc/test/fake_picture_layer_impl.h"
#include "cc/test/fake_picture_pile_impl.h"
#include "cc/test/fake_proxy.h"
#include "cc/test/fake_video_frame_provider.h"
#include "cc/test/geometry_test_utils.h"
#include "cc/test/layer_test_common.h"
#include "cc/test/layer_tree_test.h"
#include "cc/test/render_pass_test_common.h"
#include "cc/test/test_gpu_memory_buffer_manager.h"
#include "cc/test/test_shared_bitmap_manager.h"
#include "cc/test/test_web_graphics_context_3d.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/single_thread_proxy.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/gfx/frame_time.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/vector2d_conversions.h"
using ::testing::Mock;
using ::testing::Return;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::_;
using media::VideoFrame;
namespace cc {
namespace {
class LayerTreeHostImplTest : public testing::Test,
public LayerTreeHostImplClient {
public:
LayerTreeHostImplTest()
: proxy_(base::MessageLoopProxy::current(),
base::MessageLoopProxy::current()),
always_impl_thread_(&proxy_),
always_main_thread_blocked_(&proxy_),
shared_bitmap_manager_(new TestSharedBitmapManager),
gpu_memory_buffer_manager_(new TestGpuMemoryBufferManager),
on_can_draw_state_changed_called_(false),
did_notify_ready_to_activate_(false),
did_request_commit_(false),
did_request_redraw_(false),
did_request_animate_(false),
did_request_prepare_tiles_(false),
did_complete_page_scale_animation_(false),
reduce_memory_result_(true),
current_limit_bytes_(0),
current_priority_cutoff_value_(0) {
media::InitializeMediaLibraryForTesting();
}
LayerTreeSettings DefaultSettings() {
LayerTreeSettings settings;
settings.minimum_occlusion_tracking_size = gfx::Size();
settings.impl_side_painting = true;
settings.renderer_settings.texture_id_allocation_chunk_size = 1;
settings.report_overscroll_only_for_scrollable_axes = true;
settings.use_pinch_virtual_viewport = true;
return settings;
}
void SetUp() override {
CreateHostImpl(DefaultSettings(), CreateOutputSurface());
}
void TearDown() override {}
void UpdateRendererCapabilitiesOnImplThread() override {}
void DidLoseOutputSurfaceOnImplThread() override {}
void CommitVSyncParameters(base::TimeTicks timebase,
base::TimeDelta interval) override {}
void SetEstimatedParentDrawTime(base::TimeDelta draw_time) override {}
void SetMaxSwapsPendingOnImplThread(int max) override {}
void DidSwapBuffersOnImplThread() override {}
void DidSwapBuffersCompleteOnImplThread() override {}
void OnCanDrawStateChanged(bool can_draw) override {
on_can_draw_state_changed_called_ = true;
}
void NotifyReadyToActivate() override {
did_notify_ready_to_activate_ = true;
host_impl_->ActivateSyncTree();
}
void NotifyReadyToDraw() override {}
void SetNeedsRedrawOnImplThread() override { did_request_redraw_ = true; }
void SetNeedsRedrawRectOnImplThread(const gfx::Rect& damage_rect) override {
did_request_redraw_ = true;
}
void SetNeedsAnimateOnImplThread() override { did_request_animate_ = true; }
void SetNeedsPrepareTilesOnImplThread() override {
did_request_prepare_tiles_ = true;
}
void SetNeedsCommitOnImplThread() override { did_request_commit_ = true; }
void PostAnimationEventsToMainThreadOnImplThread(
scoped_ptr<AnimationEventsVector> events) override {}
bool ReduceContentsTextureMemoryOnImplThread(size_t limit_bytes,
int priority_cutoff) override {
current_limit_bytes_ = limit_bytes;
current_priority_cutoff_value_ = priority_cutoff;
return reduce_memory_result_;
}
bool IsInsideDraw() override { return false; }
void RenewTreePriority() override {}
void PostDelayedAnimationTaskOnImplThread(const base::Closure& task,
base::TimeDelta delay) override {
animation_task_ = task;
requested_animation_delay_ = delay;
}
void DidActivateSyncTree() override {}
void DidPrepareTiles() override {}
void DidCompletePageScaleAnimationOnImplThread() override {
did_complete_page_scale_animation_ = true;
}
void set_reduce_memory_result(bool reduce_memory_result) {
reduce_memory_result_ = reduce_memory_result;
}
virtual bool CreateHostImpl(const LayerTreeSettings& settings,
scoped_ptr<OutputSurface> output_surface) {
host_impl_ = LayerTreeHostImpl::Create(settings,
this,
&proxy_,
&stats_instrumentation_,
shared_bitmap_manager_.get(),
gpu_memory_buffer_manager_.get(),
0);
bool init = host_impl_->InitializeRenderer(output_surface.Pass());
host_impl_->SetViewportSize(gfx::Size(10, 10));
return init;
}
void SetupRootLayerImpl(scoped_ptr<LayerImpl> root) {
root->SetPosition(gfx::PointF());
root->SetBounds(gfx::Size(10, 10));
root->SetContentBounds(gfx::Size(10, 10));
root->SetDrawsContent(true);
root->draw_properties().visible_content_rect = gfx::Rect(0, 0, 10, 10);
root->SetHasRenderSurface(true);
host_impl_->active_tree()->SetRootLayer(root.Pass());
}
static void ExpectClearedScrollDeltasRecursive(LayerImpl* layer) {
ASSERT_EQ(layer->ScrollDelta(), gfx::Vector2d());
for (size_t i = 0; i < layer->children().size(); ++i)
ExpectClearedScrollDeltasRecursive(layer->children()[i]);
}
static void ExpectContains(const ScrollAndScaleSet& scroll_info,
int id,
const gfx::Vector2d& scroll_delta) {
int times_encountered = 0;
for (size_t i = 0; i < scroll_info.scrolls.size(); ++i) {
if (scroll_info.scrolls[i].layer_id != id)
continue;
EXPECT_VECTOR_EQ(scroll_delta, scroll_info.scrolls[i].scroll_delta);
times_encountered++;
}
ASSERT_EQ(1, times_encountered);
}
static void ExpectNone(const ScrollAndScaleSet& scroll_info, int id) {
int times_encountered = 0;
for (size_t i = 0; i < scroll_info.scrolls.size(); ++i) {
if (scroll_info.scrolls[i].layer_id != id)
continue;
times_encountered++;
}
ASSERT_EQ(0, times_encountered);
}
LayerImpl* CreateScrollAndContentsLayers(LayerTreeImpl* layer_tree_impl,
const gfx::Size& content_size) {
const int kInnerViewportScrollLayerId = 2;
const int kInnerViewportClipLayerId = 4;
const int kPageScaleLayerId = 5;
scoped_ptr<LayerImpl> root =
LayerImpl::Create(layer_tree_impl, 1);
root->SetBounds(content_size);
root->SetContentBounds(content_size);
root->SetPosition(gfx::PointF());
root->SetHasRenderSurface(true);
scoped_ptr<LayerImpl> scroll =
LayerImpl::Create(layer_tree_impl, kInnerViewportScrollLayerId);
LayerImpl* scroll_layer = scroll.get();
scroll->SetIsContainerForFixedPositionLayers(true);
scroll->PushScrollOffsetFromMainThread(gfx::ScrollOffset());
scoped_ptr<LayerImpl> clip =
LayerImpl::Create(layer_tree_impl, kInnerViewportClipLayerId);
clip->SetBounds(
gfx::Size(content_size.width() / 2, content_size.height() / 2));
scoped_ptr<LayerImpl> page_scale =
LayerImpl::Create(layer_tree_impl, kPageScaleLayerId);
scroll->SetScrollClipLayer(clip->id());
scroll->SetBounds(content_size);
scroll->SetContentBounds(content_size);
scroll->SetPosition(gfx::PointF());
scroll->SetIsContainerForFixedPositionLayers(true);
scoped_ptr<LayerImpl> contents =
LayerImpl::Create(layer_tree_impl, 3);
contents->SetDrawsContent(true);
contents->SetBounds(content_size);
contents->SetContentBounds(content_size);
contents->SetPosition(gfx::PointF());
scroll->AddChild(contents.Pass());
page_scale->AddChild(scroll.Pass());
clip->AddChild(page_scale.Pass());
root->AddChild(clip.Pass());
layer_tree_impl->SetRootLayer(root.Pass());
layer_tree_impl->SetViewportLayersFromIds(
Layer::INVALID_ID, kPageScaleLayerId, kInnerViewportScrollLayerId,
Layer::INVALID_ID);
return scroll_layer;
}
LayerImpl* SetupScrollAndContentsLayers(const gfx::Size& content_size) {
LayerImpl* scroll_layer = CreateScrollAndContentsLayers(
host_impl_->active_tree(), content_size);
host_impl_->active_tree()->DidBecomeActive();
return scroll_layer;
}
// TODO(wjmaclean) Add clip-layer pointer to parameters.
scoped_ptr<LayerImpl> CreateScrollableLayer(int id,
const gfx::Size& size,
LayerImpl* clip_layer) {
DCHECK(clip_layer);
DCHECK(id != clip_layer->id());
scoped_ptr<LayerImpl> layer =
LayerImpl::Create(host_impl_->active_tree(), id);
layer->SetScrollClipLayer(clip_layer->id());
layer->SetDrawsContent(true);
layer->SetBounds(size);
layer->SetContentBounds(size);
clip_layer->SetBounds(gfx::Size(size.width() / 2, size.height() / 2));
return layer.Pass();
}
void DrawFrame() {
LayerTreeHostImpl::FrameData frame;
EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame, gfx::FrameTime::Now());
host_impl_->DidDrawAllLayers(frame);
}
void pinch_zoom_pan_viewport_forces_commit_redraw(float device_scale_factor);
void pinch_zoom_pan_viewport_test(float device_scale_factor);
void pinch_zoom_pan_viewport_and_scroll_test(float device_scale_factor);
void pinch_zoom_pan_viewport_and_scroll_boundary_test(
float device_scale_factor);
void CheckNotifyCalledIfCanDrawChanged(bool always_draw) {
// Note: It is not possible to disable the renderer once it has been set,
// so we do not need to test that disabling the renderer notifies us
// that can_draw changed.
EXPECT_FALSE(host_impl_->CanDraw());
on_can_draw_state_changed_called_ = false;
// Set up the root layer, which allows us to draw.
SetupScrollAndContentsLayers(gfx::Size(100, 100));
EXPECT_TRUE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
// Toggle the root layer to make sure it toggles can_draw
host_impl_->active_tree()->SetRootLayer(nullptr);
EXPECT_FALSE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
SetupScrollAndContentsLayers(gfx::Size(100, 100));
EXPECT_TRUE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
// Toggle the device viewport size to make sure it toggles can_draw.
host_impl_->SetViewportSize(gfx::Size());
if (always_draw) {
EXPECT_TRUE(host_impl_->CanDraw());
} else {
EXPECT_FALSE(host_impl_->CanDraw());
}
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
host_impl_->SetViewportSize(gfx::Size(100, 100));
EXPECT_TRUE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
// Toggle contents textures purged without causing any evictions,
// and make sure that it does not change can_draw.
set_reduce_memory_result(false);
host_impl_->SetMemoryPolicy(ManagedMemoryPolicy(
host_impl_->memory_allocation_limit_bytes() - 1));
EXPECT_TRUE(host_impl_->CanDraw());
EXPECT_FALSE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
// Toggle contents textures purged to make sure it toggles can_draw.
set_reduce_memory_result(true);
host_impl_->SetMemoryPolicy(ManagedMemoryPolicy(
host_impl_->memory_allocation_limit_bytes() - 1));
if (always_draw) {
EXPECT_TRUE(host_impl_->CanDraw());
} else {
EXPECT_FALSE(host_impl_->CanDraw());
}
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
host_impl_->active_tree()->ResetContentsTexturesPurged();
EXPECT_TRUE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
}
void SetupMouseMoveAtWithDeviceScale(float device_scale_factor);
protected:
virtual scoped_ptr<OutputSurface> CreateOutputSurface() {
return FakeOutputSurface::Create3d();
}
void DrawOneFrame() {
LayerTreeHostImpl::FrameData frame_data;
host_impl_->PrepareToDraw(&frame_data);
host_impl_->DidDrawAllLayers(frame_data);
}
FakeProxy proxy_;
DebugScopedSetImplThread always_impl_thread_;
DebugScopedSetMainThreadBlocked always_main_thread_blocked_;
scoped_ptr<TestSharedBitmapManager> shared_bitmap_manager_;
scoped_ptr<TestGpuMemoryBufferManager> gpu_memory_buffer_manager_;
scoped_ptr<LayerTreeHostImpl> host_impl_;
FakeRenderingStatsInstrumentation stats_instrumentation_;
bool on_can_draw_state_changed_called_;
bool did_notify_ready_to_activate_;
bool did_request_commit_;
bool did_request_redraw_;
bool did_request_animate_;
bool did_request_prepare_tiles_;
bool did_complete_page_scale_animation_;
bool reduce_memory_result_;
base::Closure animation_task_;
base::TimeDelta requested_animation_delay_;
size_t current_limit_bytes_;
int current_priority_cutoff_value_;
};
TEST_F(LayerTreeHostImplTest, NotifyIfCanDrawChanged) {
bool always_draw = false;
CheckNotifyCalledIfCanDrawChanged(always_draw);
}
TEST_F(LayerTreeHostImplTest, CanDrawIncompleteFrames) {
CreateHostImpl(DefaultSettings(),
FakeOutputSurface::CreateAlwaysDrawAndSwap3d());
bool always_draw = true;
CheckNotifyCalledIfCanDrawChanged(always_draw);
}
TEST_F(LayerTreeHostImplTest, ScrollDeltaNoLayers) {
ASSERT_FALSE(host_impl_->active_tree()->root_layer());
scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
ASSERT_EQ(scroll_info->scrolls.size(), 0u);
}
TEST_F(LayerTreeHostImplTest, ScrollDeltaTreeButNoChanges) {
{
scoped_ptr<LayerImpl> root =
LayerImpl::Create(host_impl_->active_tree(), 1);
root->AddChild(LayerImpl::Create(host_impl_->active_tree(), 2));
root->AddChild(LayerImpl::Create(host_impl_->active_tree(), 3));
root->children()[1]->AddChild(
LayerImpl::Create(host_impl_->active_tree(), 4));
root->children()[1]->AddChild(
LayerImpl::Create(host_impl_->active_tree(), 5));
root->children()[1]->children()[0]->AddChild(
LayerImpl::Create(host_impl_->active_tree(), 6));
host_impl_->active_tree()->SetRootLayer(root.Pass());
}
LayerImpl* root = host_impl_->active_tree()->root_layer();
ExpectClearedScrollDeltasRecursive(root);
scoped_ptr<ScrollAndScaleSet> scroll_info;
scroll_info = host_impl_->ProcessScrollDeltas();
ASSERT_EQ(scroll_info->scrolls.size(), 0u);
ExpectClearedScrollDeltasRecursive(root);
scroll_info = host_impl_->ProcessScrollDeltas();
ASSERT_EQ(scroll_info->scrolls.size(), 0u);
ExpectClearedScrollDeltasRecursive(root);
}
TEST_F(LayerTreeHostImplTest, ScrollDeltaRepeatedScrolls) {
gfx::ScrollOffset scroll_offset(20, 30);
gfx::Vector2d scroll_delta(11, -15);
{
scoped_ptr<LayerImpl> root_clip =
LayerImpl::Create(host_impl_->active_tree(), 2);
scoped_ptr<LayerImpl> root =
LayerImpl::Create(host_impl_->active_tree(), 1);
root_clip->SetBounds(gfx::Size(10, 10));
LayerImpl* root_layer = root.get();
root_clip->AddChild(root.Pass());
root_layer->SetBounds(gfx::Size(110, 110));
root_layer->SetScrollClipLayer(root_clip->id());
root_layer->PushScrollOffsetFromMainThread(scroll_offset);
root_layer->ScrollBy(scroll_delta);
host_impl_->active_tree()->SetRootLayer(root_clip.Pass());
}
LayerImpl* root = host_impl_->active_tree()->root_layer()->children()[0];
scoped_ptr<ScrollAndScaleSet> scroll_info;
scroll_info = host_impl_->ProcessScrollDeltas();
ASSERT_EQ(scroll_info->scrolls.size(), 1u);
ExpectContains(*scroll_info, root->id(), scroll_delta);
gfx::Vector2d scroll_delta2(-5, 27);
root->ScrollBy(scroll_delta2);
scroll_info = host_impl_->ProcessScrollDeltas();
ASSERT_EQ(scroll_info->scrolls.size(), 1u);
ExpectContains(*scroll_info, root->id(), scroll_delta + scroll_delta2);
root->ScrollBy(gfx::Vector2d());
scroll_info = host_impl_->ProcessScrollDeltas();
ExpectContains(*scroll_info, root->id(), scroll_delta + scroll_delta2);
}
TEST_F(LayerTreeHostImplTest, ScrollRootCallsCommitAndRedraw) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(),
InputHandler::WHEEL));
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(0, 10),
InputHandler::WHEEL));
host_impl_->ScrollEnd();
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(),
InputHandler::WHEEL));
EXPECT_TRUE(did_request_redraw_);
EXPECT_TRUE(did_request_commit_);
}
TEST_F(LayerTreeHostImplTest, ScrollActiveOnlyAfterScrollMovement) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
EXPECT_FALSE(host_impl_->IsActivelyScrolling());
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
EXPECT_TRUE(host_impl_->IsActivelyScrolling());
host_impl_->ScrollEnd();
EXPECT_FALSE(host_impl_->IsActivelyScrolling());
}
TEST_F(LayerTreeHostImplTest, ScrollWithoutRootLayer) {
// We should not crash when trying to scroll an empty layer tree.
EXPECT_EQ(InputHandler::SCROLL_IGNORED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
}
TEST_F(LayerTreeHostImplTest, ScrollWithoutRenderer) {
scoped_ptr<TestWebGraphicsContext3D> context_owned =
TestWebGraphicsContext3D::Create();
context_owned->set_context_lost(true);
// Initialization will fail.
EXPECT_FALSE(CreateHostImpl(
DefaultSettings(), FakeOutputSurface::Create3d(context_owned.Pass())));
SetupScrollAndContentsLayers(gfx::Size(100, 100));
// We should not crash when trying to scroll after the renderer initialization
// fails.
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
}
TEST_F(LayerTreeHostImplTest, ReplaceTreeWhileScrolling) {
LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
// We should not crash if the tree is replaced while we are scrolling.
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
host_impl_->active_tree()->DetachLayerTree();
scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
// We should still be scrolling, because the scrolled layer also exists in the
// new tree.
gfx::Vector2d scroll_delta(0, 10);
host_impl_->ScrollBy(gfx::Point(), scroll_delta);
host_impl_->ScrollEnd();
scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
ExpectContains(*scroll_info, scroll_layer->id(), scroll_delta);
}
TEST_F(LayerTreeHostImplTest, ScrollBlocksOnWheelEventHandlers) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
LayerImpl* root = host_impl_->active_tree()->root_layer();
// With registered event handlers, wheel scrolls don't necessarily
// have to go to the main thread.
root->SetHaveWheelEventHandlers(true);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
host_impl_->ScrollEnd();
// But typically the scroll-blocks-on mode will require them to.
root->SetScrollBlocksOn(SCROLL_BLOCKS_ON_WHEEL_EVENT |
SCROLL_BLOCKS_ON_START_TOUCH);
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
// But gesture scrolls can still be handled.
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
host_impl_->ScrollEnd();
// And if the handlers go away, wheel scrolls can again be processed
// on impl (despite the scroll-blocks-on mode).
root->SetHaveWheelEventHandlers(false);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
host_impl_->ScrollEnd();
}
TEST_F(LayerTreeHostImplTest, ScrollBlocksOnTouchEventHandlers) {
LayerImpl* scroll = SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
LayerImpl* root = host_impl_->active_tree()->root_layer();
LayerImpl* child = 0;
{
scoped_ptr<LayerImpl> child_layer =
LayerImpl::Create(host_impl_->active_tree(), 6);
child = child_layer.get();
child_layer->SetDrawsContent(true);
child_layer->SetPosition(gfx::PointF(0, 20));
child_layer->SetBounds(gfx::Size(50, 50));
child_layer->SetContentBounds(gfx::Size(50, 50));
scroll->AddChild(child_layer.Pass());
}
// Touch handler regions determine whether touch events block scroll.
root->SetTouchEventHandlerRegion(gfx::Rect(0, 0, 100, 100));
EXPECT_FALSE(host_impl_->DoTouchEventsBlockScrollAt(gfx::Point(10, 10)));
root->SetScrollBlocksOn(SCROLL_BLOCKS_ON_START_TOUCH |
SCROLL_BLOCKS_ON_WHEEL_EVENT);
EXPECT_TRUE(host_impl_->DoTouchEventsBlockScrollAt(gfx::Point(10, 10)));
// But they don't influence the actual handling of the scroll gestures.
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
host_impl_->ScrollEnd();
// It's the union of scroll-blocks-on mode bits across all layers in the
// scroll paret chain that matters.
EXPECT_TRUE(host_impl_->DoTouchEventsBlockScrollAt(gfx::Point(10, 30)));
root->SetScrollBlocksOn(SCROLL_BLOCKS_ON_NONE);
EXPECT_FALSE(host_impl_->DoTouchEventsBlockScrollAt(gfx::Point(10, 30)));
child->SetScrollBlocksOn(SCROLL_BLOCKS_ON_START_TOUCH);
EXPECT_TRUE(host_impl_->DoTouchEventsBlockScrollAt(gfx::Point(10, 30)));
}
TEST_F(LayerTreeHostImplTest, ScrollBlocksOnScrollEventHandlers) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
LayerImpl* root = host_impl_->active_tree()->root_layer();
// With registered scroll handlers, scrolls don't generally have to go
// to the main thread.
root->SetHaveScrollEventHandlers(true);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
host_impl_->ScrollEnd();
// Even the default scroll blocks on mode doesn't require this.
root->SetScrollBlocksOn(SCROLL_BLOCKS_ON_WHEEL_EVENT |
SCROLL_BLOCKS_ON_START_TOUCH);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
host_impl_->ScrollEnd();
// But the page can opt in to blocking on scroll event handlers.
root->SetScrollBlocksOn(SCROLL_BLOCKS_ON_SCROLL_EVENT);
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
// GESTURE and WHEEL scrolls behave identically in this regard.
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
// And if the handlers go away, scrolls can again be processed on impl
// (despite the scroll-blocks-on mode).
root->SetHaveScrollEventHandlers(false);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
host_impl_->ScrollEnd();
}
TEST_F(LayerTreeHostImplTest, ScrollBlocksOnLayerTopology) {
host_impl_->SetViewportSize(gfx::Size(50, 50));
// Create a normal scrollable root layer
LayerImpl* root_scroll = SetupScrollAndContentsLayers(gfx::Size(100, 100));
LayerImpl* root_child = root_scroll->children()[0];
LayerImpl* root = host_impl_->active_tree()->root_layer();
DrawFrame();
// Create two child scrollable layers
LayerImpl* child1 = 0;
{
scoped_ptr<LayerImpl> scrollable_child_clip_1 =
LayerImpl::Create(host_impl_->active_tree(), 6);
scoped_ptr<LayerImpl> scrollable_child_1 = CreateScrollableLayer(
7, gfx::Size(10, 10), scrollable_child_clip_1.get());
child1 = scrollable_child_1.get();
scrollable_child_1->SetPosition(gfx::Point(5, 5));
scrollable_child_1->SetHaveWheelEventHandlers(true);
scrollable_child_1->SetHaveScrollEventHandlers(true);
scrollable_child_clip_1->AddChild(scrollable_child_1.Pass());
root_child->AddChild(scrollable_child_clip_1.Pass());
}
LayerImpl* child2 = 0;
{
scoped_ptr<LayerImpl> scrollable_child_clip_2 =
LayerImpl::Create(host_impl_->active_tree(), 8);
scoped_ptr<LayerImpl> scrollable_child_2 = CreateScrollableLayer(
9, gfx::Size(10, 10), scrollable_child_clip_2.get());
child2 = scrollable_child_2.get();
scrollable_child_2->SetPosition(gfx::Point(5, 20));
scrollable_child_2->SetHaveWheelEventHandlers(true);
scrollable_child_2->SetHaveScrollEventHandlers(true);
scrollable_child_clip_2->AddChild(scrollable_child_2.Pass());
root_child->AddChild(scrollable_child_clip_2.Pass());
}
// Scroll-blocks-on on a layer affects scrolls that hit that layer.
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(10, 10), InputHandler::GESTURE));
host_impl_->ScrollEnd();
child1->SetScrollBlocksOn(SCROLL_BLOCKS_ON_SCROLL_EVENT);
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(10, 10), InputHandler::GESTURE));
// But not those that hit only other layers.
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(10, 25), InputHandler::GESTURE));
host_impl_->ScrollEnd();
// It's the union of bits set across the scroll ancestor chain that matters.
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(10, 25), InputHandler::GESTURE));
host_impl_->ScrollEnd();
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(10, 25), InputHandler::WHEEL));
host_impl_->ScrollEnd();
root->SetScrollBlocksOn(SCROLL_BLOCKS_ON_WHEEL_EVENT);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(10, 25), InputHandler::GESTURE));
host_impl_->ScrollEnd();
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(10, 25), InputHandler::WHEEL));
child2->SetScrollBlocksOn(SCROLL_BLOCKS_ON_SCROLL_EVENT);
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(10, 25), InputHandler::WHEEL));
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(10, 25), InputHandler::GESTURE));
}
TEST_F(LayerTreeHostImplTest, FlingOnlyWhenScrollingTouchscreen) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
// Ignore the fling since no layer is being scrolled
EXPECT_EQ(InputHandler::SCROLL_IGNORED, host_impl_->FlingScrollBegin());
// Start scrolling a layer
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
// Now the fling should go ahead since we've started scrolling a layer
EXPECT_EQ(InputHandler::SCROLL_STARTED, host_impl_->FlingScrollBegin());
}
TEST_F(LayerTreeHostImplTest, FlingOnlyWhenScrollingTouchpad) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
// Ignore the fling since no layer is being scrolled
EXPECT_EQ(InputHandler::SCROLL_IGNORED, host_impl_->FlingScrollBegin());
// Start scrolling a layer
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
// Now the fling should go ahead since we've started scrolling a layer
EXPECT_EQ(InputHandler::SCROLL_STARTED, host_impl_->FlingScrollBegin());
}
TEST_F(LayerTreeHostImplTest, NoFlingWhenScrollingOnMain) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
LayerImpl* root = host_impl_->active_tree()->root_layer();
root->SetShouldScrollOnMainThread(true);
// Start scrolling a layer
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
// The fling should be ignored since there's no layer being scrolled impl-side
EXPECT_EQ(InputHandler::SCROLL_IGNORED, host_impl_->FlingScrollBegin());
}
TEST_F(LayerTreeHostImplTest, ShouldScrollOnMainThread) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
LayerImpl* root = host_impl_->active_tree()->root_layer();
root->SetShouldScrollOnMainThread(true);
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
}
TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionBasic) {
SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->SetViewportSize(gfx::Size(100, 100));
LayerImpl* root = host_impl_->active_tree()->root_layer();
root->SetContentsScale(2.f, 2.f);
root->SetNonFastScrollableRegion(gfx::Rect(0, 0, 50, 50));
DrawFrame();
// All scroll types inside the non-fast scrollable region should fail.
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(25, 25), InputHandler::WHEEL));
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(25, 25),
InputHandler::WHEEL));
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(25, 25), InputHandler::GESTURE));
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(25, 25),
InputHandler::GESTURE));
// All scroll types outside this region should succeed.
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(75, 75), InputHandler::WHEEL));
EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(75, 75),
InputHandler::GESTURE));
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(25, 25),
InputHandler::GESTURE));
host_impl_->ScrollEnd();
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(75, 75),
InputHandler::GESTURE));
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(75, 75), InputHandler::GESTURE));
EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(75, 75),
InputHandler::GESTURE));
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
host_impl_->ScrollEnd();
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(75, 75),
InputHandler::GESTURE));
}
TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionWithOffset) {
SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->SetViewportSize(gfx::Size(100, 100));
LayerImpl* root = host_impl_->active_tree()->root_layer();
root->SetContentsScale(2.f, 2.f);
root->SetNonFastScrollableRegion(gfx::Rect(0, 0, 50, 50));
root->SetPosition(gfx::PointF(-25.f, 0.f));
DrawFrame();
// This point would fall into the non-fast scrollable region except that we've
// moved the layer down by 25 pixels.
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(40, 10), InputHandler::WHEEL));
EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(40, 10),
InputHandler::WHEEL));
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 1));
host_impl_->ScrollEnd();
// This point is still inside the non-fast region.
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
host_impl_->ScrollBegin(gfx::Point(10, 10), InputHandler::WHEEL));
}
TEST_F(LayerTreeHostImplTest, ScrollHandlerNotPresent) {
LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(200, 200));
EXPECT_FALSE(scroll_layer->have_scroll_event_handlers());
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE);
EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
host_impl_->ScrollEnd();
EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
}
TEST_F(LayerTreeHostImplTest, ScrollHandlerPresent) {
LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(200, 200));
scroll_layer->SetHaveScrollEventHandlers(true);
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE);
EXPECT_TRUE(host_impl_->scroll_affects_scroll_handler());
host_impl_->ScrollEnd();
EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
}
TEST_F(LayerTreeHostImplTest, ScrollByReturnsCorrectValue) {
SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->SetViewportSize(gfx::Size(100, 100));
DrawFrame();
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
// Trying to scroll to the left/top will not succeed.
EXPECT_FALSE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, 0)).did_scroll);
EXPECT_FALSE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -10)).did_scroll);
EXPECT_FALSE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, -10)).did_scroll);
// Scrolling to the right/bottom will succeed.
EXPECT_TRUE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(10, 0)).did_scroll);
EXPECT_TRUE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10)).did_scroll);
EXPECT_TRUE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(10, 10)).did_scroll);
// Scrolling to left/top will now succeed.
EXPECT_TRUE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, 0)).did_scroll);
EXPECT_TRUE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -10)).did_scroll);
EXPECT_TRUE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, -10)).did_scroll);
// Scrolling diagonally against an edge will succeed.
EXPECT_TRUE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(10, -10)).did_scroll);
EXPECT_TRUE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, 0)).did_scroll);
EXPECT_TRUE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-10, 10)).did_scroll);
// Trying to scroll more than the available space will also succeed.
EXPECT_TRUE(
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(5000, 5000)).did_scroll);
}
TEST_F(LayerTreeHostImplTest, ScrollVerticallyByPageReturnsCorrectValue) {
SetupScrollAndContentsLayers(gfx::Size(200, 2000));
host_impl_->SetViewportSize(gfx::Size(100, 1000));
DrawFrame();
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
// Trying to scroll without a vertical scrollbar will fail.
EXPECT_FALSE(host_impl_->ScrollVerticallyByPage(
gfx::Point(), SCROLL_FORWARD));
EXPECT_FALSE(host_impl_->ScrollVerticallyByPage(
gfx::Point(), SCROLL_BACKWARD));
scoped_ptr<PaintedScrollbarLayerImpl> vertical_scrollbar(
PaintedScrollbarLayerImpl::Create(
host_impl_->active_tree(),
20,
VERTICAL));
vertical_scrollbar->SetBounds(gfx::Size(15, 1000));
host_impl_->InnerViewportScrollLayer()->AddScrollbar(
vertical_scrollbar.get());
// Trying to scroll with a vertical scrollbar will succeed.
EXPECT_TRUE(host_impl_->ScrollVerticallyByPage(
gfx::Point(), SCROLL_FORWARD));
EXPECT_FLOAT_EQ(875.f,
host_impl_->InnerViewportScrollLayer()->ScrollDelta().y());
EXPECT_TRUE(host_impl_->ScrollVerticallyByPage(
gfx::Point(), SCROLL_BACKWARD));
}
TEST_F(LayerTreeHostImplTest, ScrollWithUserUnscrollableLayers) {
LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->SetViewportSize(gfx::Size(100, 100));
gfx::Size overflow_size(400, 400);
ASSERT_EQ(1u, scroll_layer->children().size());
LayerImpl* overflow = scroll_layer->children()[0];
overflow->SetBounds(overflow_size);
overflow->SetContentBounds(overflow_size);
overflow->SetScrollClipLayer(scroll_layer->parent()->id());
overflow->PushScrollOffsetFromMainThread(gfx::ScrollOffset());
overflow->SetPosition(gfx::PointF());
DrawFrame();
gfx::Point scroll_position(10, 10);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(scroll_position, InputHandler::WHEEL));
EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(), overflow->CurrentScrollOffset());
gfx::Vector2dF scroll_delta(10, 10);
host_impl_->ScrollBy(scroll_position, scroll_delta);
host_impl_->ScrollEnd();
EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), overflow->CurrentScrollOffset());
overflow->set_user_scrollable_horizontal(false);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(scroll_position, InputHandler::WHEEL));
EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), overflow->CurrentScrollOffset());
host_impl_->ScrollBy(scroll_position, scroll_delta);
host_impl_->ScrollEnd();
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 0), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), overflow->CurrentScrollOffset());
overflow->set_user_scrollable_vertical(false);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(scroll_position, InputHandler::WHEEL));
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 0), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), overflow->CurrentScrollOffset());
host_impl_->ScrollBy(scroll_position, scroll_delta);
host_impl_->ScrollEnd();
EXPECT_VECTOR_EQ(gfx::Vector2dF(20, 10), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), overflow->CurrentScrollOffset());
}
TEST_F(LayerTreeHostImplTest, ImplPinchZoom) {
LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
EXPECT_EQ(scroll_layer, host_impl_->InnerViewportScrollLayer());
LayerImpl* container_layer = scroll_layer->scroll_clip_layer();
EXPECT_EQ(gfx::Size(50, 50), container_layer->bounds());
float min_page_scale = 1.f, max_page_scale = 4.f;
float page_scale_factor = 1.f;
// The impl-based pinch zoom should adjust the max scroll position.
{
host_impl_->active_tree()->PushPageScaleFromMainThread(
page_scale_factor, min_page_scale, max_page_scale);
host_impl_->SetPageScaleOnActiveTree(page_scale_factor);
scroll_layer->SetScrollDelta(gfx::Vector2d());
float page_scale_delta = 2.f;
host_impl_->ScrollBegin(gfx::Point(50, 50), InputHandler::GESTURE);
host_impl_->PinchGestureBegin();
host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(50, 50));
host_impl_->PinchGestureEnd();
host_impl_->ScrollEnd();
EXPECT_FALSE(did_request_animate_);
EXPECT_TRUE(did_request_redraw_);
EXPECT_TRUE(did_request_commit_);
EXPECT_EQ(gfx::Size(50, 50), container_layer->bounds());
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
EXPECT_EQ(gfx::ScrollOffset(75.0, 75.0).ToString(),
scroll_layer->MaxScrollOffset().ToString());
}
// Scrolling after a pinch gesture should always be in local space. The
// scroll deltas have the page scale factor applied.
{
host_impl_->active_tree()->PushPageScaleFromMainThread(
page_scale_factor, min_page_scale, max_page_scale);
host_impl_->SetPageScaleOnActiveTree(page_scale_factor);
scroll_layer->SetScrollDelta(gfx::Vector2d());
float page_scale_delta = 2.f;
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE);
host_impl_->PinchGestureBegin();
host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point());
host_impl_->PinchGestureEnd();
host_impl_->ScrollEnd();
gfx::Vector2d scroll_delta(0, 10);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(5, 5), InputHandler::WHEEL));
host_impl_->ScrollBy(gfx::Point(), scroll_delta);
host_impl_->ScrollEnd();
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
ExpectContains(*scroll_info.get(), scroll_layer->id(),
gfx::Vector2d(0, scroll_delta.y() / page_scale_delta));
}
}
TEST_F(LayerTreeHostImplTest, ScrollWithSwapPromises) {
ui::LatencyInfo latency_info;
latency_info.trace_id = 1234;
scoped_ptr<SwapPromise> swap_promise(
new LatencyInfoSwapPromise(latency_info));
SetupScrollAndContentsLayers(gfx::Size(100, 100));
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
host_impl_->QueueSwapPromiseForMainThreadScrollUpdate(swap_promise.Pass());
host_impl_->ScrollEnd();
scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
EXPECT_EQ(1u, scroll_info->swap_promises.size());
EXPECT_EQ(latency_info.trace_id, scroll_info->swap_promises[0]->TraceId());
}
TEST_F(LayerTreeHostImplTest, PinchGesture) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
DCHECK(scroll_layer);
float min_page_scale = 1.f;
float max_page_scale = 4.f;
// Basic pinch zoom in gesture
{
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
max_page_scale);
scroll_layer->SetScrollDelta(gfx::Vector2d());
float page_scale_delta = 2.f;
host_impl_->ScrollBegin(gfx::Point(50, 50), InputHandler::GESTURE);
host_impl_->PinchGestureBegin();
host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(50, 50));
host_impl_->PinchGestureEnd();
host_impl_->ScrollEnd();
EXPECT_FALSE(did_request_animate_);
EXPECT_TRUE(did_request_redraw_);
EXPECT_TRUE(did_request_commit_);
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
}
// Zoom-in clamping
{
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
max_page_scale);
scroll_layer->SetScrollDelta(gfx::Vector2d());
float page_scale_delta = 10.f;
host_impl_->ScrollBegin(gfx::Point(50, 50), InputHandler::GESTURE);
host_impl_->PinchGestureBegin();
host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(50, 50));
host_impl_->PinchGestureEnd();
host_impl_->ScrollEnd();
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, max_page_scale);
}
// Zoom-out clamping
{
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
max_page_scale);
scroll_layer->SetScrollDelta(gfx::Vector2d());
scroll_layer->PullDeltaForMainThread();
scroll_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(50, 50));
float page_scale_delta = 0.1f;
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE);
host_impl_->PinchGestureBegin();
host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point());
host_impl_->PinchGestureEnd();
host_impl_->ScrollEnd();
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, min_page_scale);
EXPECT_TRUE(scroll_info->scrolls.empty());
}
// Two-finger panning should not happen based on pinch events only
{
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
max_page_scale);
scroll_layer->SetScrollDelta(gfx::Vector2d());
scroll_layer->PullDeltaForMainThread();
scroll_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(20, 20));
float page_scale_delta = 1.f;
host_impl_->ScrollBegin(gfx::Point(10, 10), InputHandler::GESTURE);
host_impl_->PinchGestureBegin();
host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(10, 10));
host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(20, 20));
host_impl_->PinchGestureEnd();
host_impl_->ScrollEnd();
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
EXPECT_TRUE(scroll_info->scrolls.empty());
}
// Two-finger panning should work with interleaved scroll events
{
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
max_page_scale);
scroll_layer->SetScrollDelta(gfx::Vector2d());
scroll_layer->PullDeltaForMainThread();
scroll_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(20, 20));
float page_scale_delta = 1.f;
host_impl_->ScrollBegin(gfx::Point(10, 10), InputHandler::GESTURE);
host_impl_->PinchGestureBegin();
host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(10, 10));
host_impl_->ScrollBy(gfx::Point(10, 10), gfx::Vector2d(-10, -10));
host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(20, 20));
host_impl_->PinchGestureEnd();
host_impl_->ScrollEnd();
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
ExpectContains(*scroll_info, scroll_layer->id(), gfx::Vector2d(-10, -10));
}
// Two-finger panning should work when starting fully zoomed out.
{
host_impl_->active_tree()->PushPageScaleFromMainThread(0.5f, 0.5f, 4.f);
scroll_layer->SetScrollDelta(gfx::Vector2d());
scroll_layer->PullDeltaForMainThread();
scroll_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(0, 0));
host_impl_->ScrollBegin(gfx::Point(0, 0), InputHandler::GESTURE);
host_impl_->PinchGestureBegin();
host_impl_->PinchGestureUpdate(2.f, gfx::Point(0, 0));
host_impl_->PinchGestureUpdate(1.f, gfx::Point(0, 0));
host_impl_->ScrollBy(gfx::Point(0, 0), gfx::Vector2d(10, 10));
host_impl_->PinchGestureUpdate(1.f, gfx::Point(10, 10));
host_impl_->PinchGestureEnd();
host_impl_->ScrollEnd();
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, 2.f);
ExpectContains(*scroll_info, scroll_layer->id(), gfx::Vector2d(20, 20));
}
}
TEST_F(LayerTreeHostImplTest, PageScaleAnimation) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
DCHECK(scroll_layer);
float min_page_scale = 0.5f;
float max_page_scale = 4.f;
base::TimeTicks start_time = base::TimeTicks() +
base::TimeDelta::FromSeconds(1);
base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100);
base::TimeTicks halfway_through_animation = start_time + duration / 2;
base::TimeTicks end_time = start_time + duration;
// Non-anchor zoom-in
{
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
max_page_scale);
scroll_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(50, 50));
did_request_redraw_ = false;
did_request_animate_ = false;
host_impl_->active_tree()->SetPendingPageScaleAnimation(
scoped_ptr<PendingPageScaleAnimation>(new PendingPageScaleAnimation(
gfx::Vector2d(),
false,
2.f,
duration)));
host_impl_->ActivateSyncTree();
EXPECT_FALSE(did_request_redraw_);
EXPECT_TRUE(did_request_animate_);
did_request_redraw_ = false;
did_request_animate_ = false;
host_impl_->Animate(start_time);
EXPECT_TRUE(did_request_redraw_);
EXPECT_TRUE(did_request_animate_);
did_request_redraw_ = false;
did_request_animate_ = false;
host_impl_->Animate(halfway_through_animation);
EXPECT_TRUE(did_request_redraw_);
EXPECT_TRUE(did_request_animate_);
did_request_redraw_ = false;
did_request_animate_ = false;
did_request_commit_ = false;
host_impl_->Animate(end_time);
EXPECT_TRUE(did_request_commit_);
EXPECT_FALSE(did_request_animate_);
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, 2);
ExpectContains(*scroll_info, scroll_layer->id(), gfx::Vector2d(-50, -50));
}
// Anchor zoom-out
{
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
max_page_scale);
scroll_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(50, 50));
did_request_redraw_ = false;
did_request_animate_ = false;
host_impl_->active_tree()->SetPendingPageScaleAnimation(
scoped_ptr<PendingPageScaleAnimation> (new PendingPageScaleAnimation(
gfx::Vector2d(25, 25),
true,
min_page_scale,
duration)));
host_impl_->ActivateSyncTree();
EXPECT_FALSE(did_request_redraw_);
EXPECT_TRUE(did_request_animate_);
did_request_redraw_ = false;
did_request_animate_ = false;
host_impl_->Animate(start_time);
EXPECT_TRUE(did_request_redraw_);
EXPECT_TRUE(did_request_animate_);
did_request_redraw_ = false;
did_request_commit_ = false;
did_request_animate_ = false;
host_impl_->Animate(end_time);
EXPECT_TRUE(did_request_redraw_);
EXPECT_FALSE(did_request_animate_);
EXPECT_TRUE(did_request_commit_);
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, min_page_scale);
// Pushed to (0,0) via clamping against contents layer size.
ExpectContains(*scroll_info, scroll_layer->id(), gfx::Vector2d(-50, -50));
}
}
TEST_F(LayerTreeHostImplTest, PageScaleAnimationNoOp) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
DCHECK(scroll_layer);
float min_page_scale = 0.5f;
float max_page_scale = 4.f;
base::TimeTicks start_time = base::TimeTicks() +
base::TimeDelta::FromSeconds(1);
base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100);
base::TimeTicks halfway_through_animation = start_time + duration / 2;
base::TimeTicks end_time = start_time + duration;
// Anchor zoom with unchanged page scale should not change scroll or scale.
{
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
max_page_scale);
scroll_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(50, 50));
host_impl_->active_tree()->SetPendingPageScaleAnimation(
scoped_ptr<PendingPageScaleAnimation>(new PendingPageScaleAnimation(
gfx::Vector2d(),
true,
1.f,
duration)));
host_impl_->ActivateSyncTree();
host_impl_->Animate(start_time);
host_impl_->Animate(halfway_through_animation);
EXPECT_TRUE(did_request_redraw_);
host_impl_->Animate(end_time);
EXPECT_TRUE(did_request_commit_);
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, 1);
ExpectNone(*scroll_info, scroll_layer->id());
}
}
TEST_F(LayerTreeHostImplTest, PageScaleAnimationTransferedOnSyncTreeActivate) {
host_impl_->CreatePendingTree();
CreateScrollAndContentsLayers(
host_impl_->pending_tree(),
gfx::Size(100, 100));
host_impl_->ActivateSyncTree();
DrawFrame();
LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
DCHECK(scroll_layer);
float min_page_scale = 0.5f;
float max_page_scale = 4.f;
host_impl_->sync_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
max_page_scale);
host_impl_->ActivateSyncTree();
base::TimeTicks start_time = base::TimeTicks() +
base::TimeDelta::FromSeconds(1);
base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100);
base::TimeTicks third_through_animation = start_time + duration / 3;
base::TimeTicks halfway_through_animation = start_time + duration / 2;
base::TimeTicks end_time = start_time + duration;
float target_scale = 2.f;
scroll_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(50, 50));
// Make sure TakePageScaleAnimation works properly.
host_impl_->sync_tree()->SetPendingPageScaleAnimation(
scoped_ptr<PendingPageScaleAnimation>(new PendingPageScaleAnimation(
gfx::Vector2d(),
false,
target_scale,
duration)));
scoped_ptr<PendingPageScaleAnimation> psa =
host_impl_->sync_tree()->TakePendingPageScaleAnimation();
EXPECT_EQ(target_scale, psa->scale);
EXPECT_EQ(duration, psa->duration);
EXPECT_EQ(nullptr, host_impl_->sync_tree()->TakePendingPageScaleAnimation());
// Recreate the PSA. Nothing should happen here since the tree containing the
// PSA hasn't been activated yet.
did_request_redraw_ = false;
did_request_animate_ = false;
host_impl_->sync_tree()->SetPendingPageScaleAnimation(
scoped_ptr<PendingPageScaleAnimation>(new PendingPageScaleAnimation(
gfx::Vector2d(),
false,
target_scale,
duration)));
host_impl_->Animate(halfway_through_animation);
EXPECT_FALSE(did_request_animate_);
EXPECT_FALSE(did_request_redraw_);
// Activate the sync tree. This should cause the animation to become enabled.
// It should also clear the pointer on the sync tree.
host_impl_->ActivateSyncTree();
EXPECT_EQ(nullptr,
host_impl_->sync_tree()->TakePendingPageScaleAnimation().get());
EXPECT_FALSE(did_request_redraw_);
EXPECT_TRUE(did_request_animate_);
// From here on, make sure the animation runs as normal.
did_request_redraw_ = false;
did_request_animate_ = false;
host_impl_->Animate(start_time);
EXPECT_TRUE(did_request_redraw_);
EXPECT_TRUE(did_request_animate_);
did_request_redraw_ = false;
did_request_animate_ = false;
host_impl_->Animate(third_through_animation);
EXPECT_TRUE(did_request_redraw_);
EXPECT_TRUE(did_request_animate_);
// Another activation shouldn't have any effect on the animation.
host_impl_->ActivateSyncTree();
did_request_redraw_ = false;
did_request_animate_ = false;
host_impl_->Animate(halfway_through_animation);
EXPECT_TRUE(did_request_redraw_);
EXPECT_TRUE(did_request_animate_);
did_request_redraw_ = false;
did_request_animate_ = false;
did_request_commit_ = false;
host_impl_->Animate(end_time);
EXPECT_TRUE(did_request_commit_);
EXPECT_FALSE(did_request_animate_);
scoped_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, target_scale);
ExpectContains(*scroll_info, scroll_layer->id(), gfx::Vector2d(-50, -50));
}
TEST_F(LayerTreeHostImplTest, PageScaleAnimationCompletedNotification) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
DrawFrame();
LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
DCHECK(scroll_layer);
base::TimeTicks start_time =
base::TimeTicks() + base::TimeDelta::FromSeconds(1);
base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100);
base::TimeTicks halfway_through_animation = start_time + duration / 2;
base::TimeTicks end_time = start_time + duration;
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
scroll_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(50, 50));
did_complete_page_scale_animation_ = false;
host_impl_->active_tree()->SetPendingPageScaleAnimation(
scoped_ptr<PendingPageScaleAnimation>(new PendingPageScaleAnimation(
gfx::Vector2d(), false, 2.f, duration)));
host_impl_->ActivateSyncTree();
host_impl_->Animate(start_time);
EXPECT_FALSE(did_complete_page_scale_animation_);
host_impl_->Animate(halfway_through_animation);
EXPECT_FALSE(did_complete_page_scale_animation_);
host_impl_->Animate(end_time);
EXPECT_TRUE(did_complete_page_scale_animation_);
}
class LayerTreeHostImplOverridePhysicalTime : public LayerTreeHostImpl {
public:
LayerTreeHostImplOverridePhysicalTime(
const LayerTreeSettings& settings,
LayerTreeHostImplClient* client,
Proxy* proxy,
SharedBitmapManager* manager,
RenderingStatsInstrumentation* rendering_stats_instrumentation)
: LayerTreeHostImpl(settings,
client,
proxy,
rendering_stats_instrumentation,
manager,
NULL,
0) {}
BeginFrameArgs CurrentBeginFrameArgs() const override {
return CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE,
fake_current_physical_time_);
}
void SetCurrentPhysicalTimeTicksForTest(base::TimeTicks fake_now) {
fake_current_physical_time_ = fake_now;
}
private:
base::TimeTicks fake_current_physical_time_;
};
class LayerTreeHostImplTestScrollbarAnimation : public LayerTreeHostImplTest {
protected:
void SetupLayers(LayerTreeSettings settings) {
gfx::Size viewport_size(10, 10);
gfx::Size content_size(100, 100);
LayerTreeHostImplOverridePhysicalTime* host_impl_override_time =
new LayerTreeHostImplOverridePhysicalTime(settings, this, &proxy_,
shared_bitmap_manager_.get(),
&stats_instrumentation_);
host_impl_ = make_scoped_ptr(host_impl_override_time);
host_impl_->InitializeRenderer(CreateOutputSurface());
host_impl_->SetViewportSize(viewport_size);
scoped_ptr<LayerImpl> root =
LayerImpl::Create(host_impl_->active_tree(), 1);
root->SetBounds(viewport_size);
scoped_ptr<LayerImpl> scroll =
LayerImpl::Create(host_impl_->active_tree(), 2);
scroll->SetScrollClipLayer(root->id());
scroll->PushScrollOffsetFromMainThread(gfx::ScrollOffset());
root->SetBounds(viewport_size);
scroll->SetBounds(content_size);
scroll->SetContentBounds(content_size);
scroll->SetIsContainerForFixedPositionLayers(true);
scoped_ptr<LayerImpl> contents =
LayerImpl::Create(host_impl_->active_tree(), 3);
contents->SetDrawsContent(true);
contents->SetBounds(content_size);
contents->SetContentBounds(content_size);
scoped_ptr<SolidColorScrollbarLayerImpl> scrollbar =
SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(), 4,
VERTICAL, 10, 0, false, true);
EXPECT_FLOAT_EQ(0.f, scrollbar->opacity());
scroll->AddChild(contents.Pass());
root->AddChild(scroll.Pass());
root->SetHasRenderSurface(true);
scrollbar->SetScrollLayerAndClipLayerByIds(2, 1);
root->AddChild(scrollbar.Pass());
host_impl_->active_tree()->SetRootLayer(root.Pass());
host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 1, 2,
Layer::INVALID_ID);
host_impl_->active_tree()->DidBecomeActive();
DrawFrame();
}
void RunTest(LayerTreeSettings::ScrollbarAnimator animator) {
LayerTreeSettings settings;
settings.scrollbar_animator = animator;
settings.scrollbar_fade_delay_ms = 20;
settings.scrollbar_fade_duration_ms = 20;
SetupLayers(settings);
base::TimeTicks fake_now = gfx::FrameTime::Now();
EXPECT_FALSE(did_request_animate_);
EXPECT_FALSE(did_request_redraw_);
EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
EXPECT_TRUE(animation_task_.Equals(base::Closure()));
// If no scroll happened during a scroll gesture, it should have no effect.
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL);
host_impl_->ScrollEnd();
EXPECT_FALSE(did_request_animate_);
EXPECT_FALSE(did_request_redraw_);
EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
EXPECT_TRUE(animation_task_.Equals(base::Closure()));
// After a scroll, a scrollbar animation should be scheduled about 20ms from
// now.
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL);
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0, 5));
EXPECT_FALSE(did_request_animate_);
EXPECT_TRUE(did_request_redraw_);
did_request_redraw_ = false;
EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
EXPECT_TRUE(animation_task_.Equals(base::Closure()));
host_impl_->ScrollEnd();
EXPECT_FALSE(did_request_animate_);
EXPECT_FALSE(did_request_redraw_);
EXPECT_EQ(base::TimeDelta::FromMilliseconds(20),
requested_animation_delay_);
EXPECT_FALSE(animation_task_.Equals(base::Closure()));
fake_now += requested_animation_delay_;
requested_animation_delay_ = base::TimeDelta();
animation_task_.Run();
animation_task_ = base::Closure();
EXPECT_TRUE(did_request_animate_);
did_request_animate_ = false;
EXPECT_FALSE(did_request_redraw_);
// After the scrollbar animation begins, we should start getting redraws.
host_impl_->Animate(fake_now);
EXPECT_TRUE(did_request_animate_);
did_request_animate_ = false;
EXPECT_TRUE(did_request_redraw_);
did_request_redraw_ = false;
EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
EXPECT_TRUE(animation_task_.Equals(base::Closure()));
// Setting the scroll offset outside a scroll should also cause the
// scrollbar to appear and to schedule a scrollbar animation.
host_impl_->InnerViewportScrollLayer()->PushScrollOffsetFromMainThread(
gfx::ScrollOffset(5, 5));
EXPECT_FALSE(did_request_animate_);
EXPECT_FALSE(did_request_redraw_);
EXPECT_EQ(base::TimeDelta::FromMilliseconds(20),
requested_animation_delay_);
EXPECT_FALSE(animation_task_.Equals(base::Closure()));
requested_animation_delay_ = base::TimeDelta();
animation_task_ = base::Closure();
// Scrollbar animation is not triggered unnecessarily.
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL);
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(5, 0));
EXPECT_FALSE(did_request_animate_);
EXPECT_TRUE(did_request_redraw_);
did_request_redraw_ = false;
EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
EXPECT_TRUE(animation_task_.Equals(base::Closure()));
host_impl_->ScrollEnd();
EXPECT_FALSE(did_request_animate_);
EXPECT_FALSE(did_request_redraw_);
EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
EXPECT_TRUE(animation_task_.Equals(base::Closure()));
// Changing page scale triggers scrollbar animation.
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 4.f);
host_impl_->SetPageScaleOnActiveTree(1.1f);
EXPECT_FALSE(did_request_animate_);
EXPECT_FALSE(did_request_redraw_);
EXPECT_EQ(base::TimeDelta::FromMilliseconds(20),
requested_animation_delay_);
EXPECT_FALSE(animation_task_.Equals(base::Closure()));
requested_animation_delay_ = base::TimeDelta();
animation_task_ = base::Closure();
}
};
TEST_F(LayerTreeHostImplTestScrollbarAnimation, LinearFade) {
RunTest(LayerTreeSettings::LINEAR_FADE);
}
TEST_F(LayerTreeHostImplTestScrollbarAnimation, Thinning) {
RunTest(LayerTreeSettings::THINNING);
}
void LayerTreeHostImplTest::SetupMouseMoveAtWithDeviceScale(
float device_scale_factor) {
LayerTreeSettings settings;
settings.scrollbar_fade_delay_ms = 500;
settings.scrollbar_fade_duration_ms = 300;
settings.scrollbar_animator = LayerTreeSettings::THINNING;
gfx::Size viewport_size(300, 200);
gfx::Size device_viewport_size = gfx::ToFlooredSize(
gfx::ScaleSize(viewport_size, device_scale_factor));
gfx::Size content_size(1000, 1000);
CreateHostImpl(settings, CreateOutputSurface());
host_impl_->SetDeviceScaleFactor(device_scale_factor);
host_impl_->SetViewportSize(device_viewport_size);
scoped_ptr<LayerImpl> root =
LayerImpl::Create(host_impl_->active_tree(), 1);
root->SetBounds(viewport_size);
root->SetHasRenderSurface(true);
scoped_ptr<LayerImpl> scroll =
LayerImpl::Create(host_impl_->active_tree(), 2);
scroll->SetScrollClipLayer(root->id());
scroll->PushScrollOffsetFromMainThread(gfx::ScrollOffset());
scroll->SetBounds(content_size);
scroll->SetContentBounds(content_size);
scroll->SetIsContainerForFixedPositionLayers(true);
scoped_ptr<LayerImpl> contents =
LayerImpl::Create(host_impl_->active_tree(), 3);
contents->SetDrawsContent(true);
contents->SetBounds(content_size);
contents->SetContentBounds(content_size);
// The scrollbar is on the right side.
scoped_ptr<PaintedScrollbarLayerImpl> scrollbar =
PaintedScrollbarLayerImpl::Create(host_impl_->active_tree(), 5, VERTICAL);
scrollbar->SetDrawsContent(true);
scrollbar->SetBounds(gfx::Size(15, viewport_size.height()));
scrollbar->SetContentBounds(gfx::Size(15, viewport_size.height()));
scrollbar->SetPosition(gfx::Point(285, 0));
scroll->AddChild(contents.Pass());
root->AddChild(scroll.Pass());
scrollbar->SetScrollLayerAndClipLayerByIds(2, 1);
root->AddChild(scrollbar.Pass());
host_impl_->active_tree()->SetRootLayer(root.Pass());
host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 1, 2,
Layer::INVALID_ID);
host_impl_->active_tree()->DidBecomeActive();
DrawFrame();
LayerImpl* root_scroll =
host_impl_->active_tree()->InnerViewportScrollLayer();
ASSERT_TRUE(root_scroll->scrollbar_animation_controller());
ScrollbarAnimationControllerThinning* scrollbar_animation_controller =
static_cast<ScrollbarAnimationControllerThinning*>(
root_scroll->scrollbar_animation_controller());
scrollbar_animation_controller->set_mouse_move_distance_for_test(100.f);
host_impl_->MouseMoveAt(gfx::Point(1, 1));
EXPECT_FALSE(scrollbar_animation_controller->mouse_is_near_scrollbar());
host_impl_->MouseMoveAt(gfx::Point(200, 50));
EXPECT_TRUE(scrollbar_animation_controller->mouse_is_near_scrollbar());
host_impl_->MouseMoveAt(gfx::Point(184, 100));
EXPECT_FALSE(scrollbar_animation_controller->mouse_is_near_scrollbar());
scrollbar_animation_controller->set_mouse_move_distance_for_test(102.f);
host_impl_->MouseMoveAt(gfx::Point(184, 100));
EXPECT_TRUE(scrollbar_animation_controller->mouse_is_near_scrollbar());
did_request_redraw_ = false;
EXPECT_EQ(0, host_impl_->scroll_layer_id_when_mouse_over_scrollbar());
host_impl_->MouseMoveAt(gfx::Point(290, 100));
EXPECT_EQ(2, host_impl_->scroll_layer_id_when_mouse_over_scrollbar());
host_impl_->MouseMoveAt(gfx::Point(290, 120));
EXPECT_EQ(2, host_impl_->scroll_layer_id_when_mouse_over_scrollbar());
host_impl_->MouseMoveAt(gfx::Point(150, 120));
EXPECT_EQ(0, host_impl_->scroll_layer_id_when_mouse_over_scrollbar());
}
TEST_F(LayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf1) {
SetupMouseMoveAtWithDeviceScale(1.f);
}
TEST_F(LayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf2) {
SetupMouseMoveAtWithDeviceScale(2.f);
}
TEST_F(LayerTreeHostImplTest, CompositorFrameMetadata) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->SetViewportSize(gfx::Size(50, 50));
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
DrawFrame();
{
CompositorFrameMetadata metadata =
host_impl_->MakeCompositorFrameMetadata();
EXPECT_EQ(gfx::Vector2dF(), metadata.root_scroll_offset);
EXPECT_EQ(1.f, metadata.page_scale_factor);
EXPECT_EQ(gfx::SizeF(50.f, 50.f), metadata.scrollable_viewport_size);
EXPECT_EQ(gfx::SizeF(100.f, 100.f), metadata.root_layer_size);
EXPECT_EQ(0.5f, metadata.min_page_scale_factor);
EXPECT_EQ(4.f, metadata.max_page_scale_factor);
EXPECT_FALSE(metadata.root_overflow_x_hidden);
EXPECT_FALSE(metadata.root_overflow_y_hidden);
}
// Scrolling should update metadata immediately.
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10));
{
CompositorFrameMetadata metadata =
host_impl_->MakeCompositorFrameMetadata();
EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
}
host_impl_->ScrollEnd();
{
CompositorFrameMetadata metadata =
host_impl_->MakeCompositorFrameMetadata();
EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
}
// Root "overflow: hidden" properties should be reflected.
{
host_impl_->active_tree()
->InnerViewportScrollLayer()
->set_user_scrollable_horizontal(false);
CompositorFrameMetadata metadata =
host_impl_->MakeCompositorFrameMetadata();
EXPECT_TRUE(metadata.root_overflow_x_hidden);
EXPECT_FALSE(metadata.root_overflow_y_hidden);
host_impl_->active_tree()
->InnerViewportScrollLayer()
->set_user_scrollable_vertical(false);
metadata = host_impl_->MakeCompositorFrameMetadata();
EXPECT_TRUE(metadata.root_overflow_x_hidden);
EXPECT_TRUE(metadata.root_overflow_y_hidden);
}
// Page scale should update metadata correctly (shrinking only the viewport).
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE);
host_impl_->PinchGestureBegin();
host_impl_->PinchGestureUpdate(2.f, gfx::Point());
host_impl_->PinchGestureEnd();
host_impl_->ScrollEnd();
{
CompositorFrameMetadata metadata =
host_impl_->MakeCompositorFrameMetadata();
EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
EXPECT_EQ(2.f, metadata.page_scale_factor);
EXPECT_EQ(gfx::SizeF(25.f, 25.f), metadata.scrollable_viewport_size);
EXPECT_EQ(gfx::SizeF(100.f, 100.f), metadata.root_layer_size);
EXPECT_EQ(0.5f, metadata.min_page_scale_factor);
EXPECT_EQ(4.f, metadata.max_page_scale_factor);
}
// Likewise if set from the main thread.
host_impl_->ProcessScrollDeltas();
host_impl_->active_tree()->PushPageScaleFromMainThread(4.f, 0.5f, 4.f);
host_impl_->SetPageScaleOnActiveTree(4.f);
{
CompositorFrameMetadata metadata =
host_impl_->MakeCompositorFrameMetadata();
EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
EXPECT_EQ(4.f, metadata.page_scale_factor);
EXPECT_EQ(gfx::SizeF(12.5f, 12.5f), metadata.scrollable_viewport_size);
EXPECT_EQ(gfx::SizeF(100.f, 100.f), metadata.root_layer_size);
EXPECT_EQ(0.5f, metadata.min_page_scale_factor);
EXPECT_EQ(4.f, metadata.max_page_scale_factor);
}
}
class DidDrawCheckLayer : public LayerImpl {
public:
static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id) {
return make_scoped_ptr(new DidDrawCheckLayer(tree_impl, id));
}
bool WillDraw(DrawMode draw_mode, ResourceProvider* provider) override {
will_draw_called_ = true;
if (will_draw_returns_false_)
return false;
return LayerImpl::WillDraw(draw_mode, provider);
}
void AppendQuads(RenderPass* render_pass,
AppendQuadsData* append_quads_data) override {
append_quads_called_ = true;
LayerImpl::AppendQuads(render_pass, append_quads_data);
}
void DidDraw(ResourceProvider* provider) override {
did_draw_called_ = true;
LayerImpl::DidDraw(provider);
}
bool will_draw_called() const { return will_draw_called_; }
bool append_quads_called() const { return append_quads_called_; }
bool did_draw_called() const { return did_draw_called_; }
void set_will_draw_returns_false() { will_draw_returns_false_ = true; }
void ClearDidDrawCheck() {
will_draw_called_ = false;
append_quads_called_ = false;
did_draw_called_ = false;
}
static void IgnoreResult(scoped_ptr<CopyOutputResult> result) {}
void AddCopyRequest() {
ScopedPtrVector<CopyOutputRequest> requests;
requests.push_back(
CopyOutputRequest::CreateRequest(base::Bind(&IgnoreResult)));
SetHasRenderSurface(true);
PassCopyRequests(&requests);
}
protected:
DidDrawCheckLayer(LayerTreeImpl* tree_impl, int id)
: LayerImpl(tree_impl, id),
will_draw_returns_false_(false),
will_draw_called_(false),
append_quads_called_(false),
did_draw_called_(false) {
SetBounds(gfx::Size(10, 10));
SetContentBounds(gfx::Size(10, 10));
SetDrawsContent(true);
draw_properties().visible_content_rect = gfx::Rect(0, 0, 10, 10);
}
private:
bool will_draw_returns_false_;
bool will_draw_called_;
bool append_quads_called_;
bool did_draw_called_;
};
TEST_F(LayerTreeHostImplTest, WillDrawReturningFalseDoesNotCall) {
// The root layer is always drawn, so run this test on a child layer that
// will be masked out by the root layer's bounds.
host_impl_->active_tree()->SetRootLayer(
DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(
host_impl_->active_tree()->root_layer());
root->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
root->SetHasRenderSurface(true);
DidDrawCheckLayer* layer =
static_cast<DidDrawCheckLayer*>(root->children()[0]);
{
LayerTreeHostImpl::FrameData frame;
EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame, gfx::FrameTime::Now());
host_impl_->DidDrawAllLayers(frame);
EXPECT_TRUE(layer->will_draw_called());
EXPECT_TRUE(layer->append_quads_called());
EXPECT_TRUE(layer->did_draw_called());
}
host_impl_->SetViewportDamage(gfx::Rect(10, 10));
{
LayerTreeHostImpl::FrameData frame;
layer->set_will_draw_returns_false();
layer->ClearDidDrawCheck();
EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame, gfx::FrameTime::Now());
host_impl_->DidDrawAllLayers(frame);
EXPECT_TRUE(layer->will_draw_called());
EXPECT_FALSE(layer->append_quads_called());
EXPECT_FALSE(layer->did_draw_called());
}
}
TEST_F(LayerTreeHostImplTest, DidDrawNotCalledOnHiddenLayer) {
// The root layer is always drawn, so run this test on a child layer that
// will be masked out by the root layer's bounds.
host_impl_->active_tree()->SetRootLayer(
DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(
host_impl_->active_tree()->root_layer());
root->SetMasksToBounds(true);
root->SetHasRenderSurface(true);
root->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
DidDrawCheckLayer* layer =
static_cast<DidDrawCheckLayer*>(root->children()[0]);
// Ensure visible_content_rect for layer is empty.
layer->SetPosition(gfx::PointF(100.f, 100.f));
layer->SetBounds(gfx::Size(10, 10));
layer->SetContentBounds(gfx::Size(10, 10));
LayerTreeHostImpl::FrameData frame;
EXPECT_FALSE(layer->will_draw_called());
EXPECT_FALSE(layer->did_draw_called());
EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame, gfx::FrameTime::Now());
host_impl_->DidDrawAllLayers(frame);
EXPECT_FALSE(layer->will_draw_called());
EXPECT_FALSE(layer->did_draw_called());
EXPECT_TRUE(layer->visible_content_rect().IsEmpty());
// Ensure visible_content_rect for layer is not empty
layer->SetPosition(gfx::PointF());
EXPECT_FALSE(layer->will_draw_called());
EXPECT_FALSE(layer->did_draw_called());
EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame, gfx::FrameTime::Now());
host_impl_->DidDrawAllLayers(frame);
EXPECT_TRUE(layer->will_draw_called());
EXPECT_TRUE(layer->did_draw_called());
EXPECT_FALSE(layer->visible_content_rect().IsEmpty());
}
TEST_F(LayerTreeHostImplTest, WillDrawNotCalledOnOccludedLayer) {
gfx::Size big_size(1000, 1000);
host_impl_->SetViewportSize(big_size);
host_impl_->active_tree()->SetRootLayer(
DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
DidDrawCheckLayer* root =
static_cast<DidDrawCheckLayer*>(host_impl_->active_tree()->root_layer());
root->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
DidDrawCheckLayer* occluded_layer =
static_cast<DidDrawCheckLayer*>(root->children()[0]);
root->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 3));
root->SetHasRenderSurface(true);
DidDrawCheckLayer* top_layer =
static_cast<DidDrawCheckLayer*>(root->children()[1]);
// This layer covers the occluded_layer above. Make this layer large so it can
// occlude.
top_layer->SetBounds(big_size);
top_layer->SetContentBounds(big_size);
top_layer->SetContentsOpaque(true);
LayerTreeHostImpl::FrameData frame;
EXPECT_FALSE(occluded_layer->will_draw_called());
EXPECT_FALSE(occluded_layer->did_draw_called());
EXPECT_FALSE(top_layer->will_draw_called());
EXPECT_FALSE(top_layer->did_draw_called());
EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame, gfx::FrameTime::Now());
host_impl_->DidDrawAllLayers(frame);
EXPECT_FALSE(occluded_layer->will_draw_called());
EXPECT_FALSE(occluded_layer->did_draw_called());
EXPECT_TRUE(top_layer->will_draw_called());
EXPECT_TRUE(top_layer->did_draw_called());
}
TEST_F(LayerTreeHostImplTest, DidDrawCalledOnAllLayers) {
host_impl_->active_tree()->SetRootLayer(
DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
DidDrawCheckLayer* root =
static_cast<DidDrawCheckLayer*>(host_impl_->active_tree()->root_layer());
root->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
root->SetHasRenderSurface(true);
DidDrawCheckLayer* layer1 =
static_cast<DidDrawCheckLayer*>(root->children()[0]);
layer1->AddChild(DidDrawCheckLayer::Create(host_impl_->active_tree(), 3));
DidDrawCheckLayer* layer2 =
static_cast<DidDrawCheckLayer*>(layer1->children()[0]);
layer1->SetHasRenderSurface(true);
layer1->SetShouldFlattenTransform(true);
EXPECT_FALSE(root->did_draw_called());
EXPECT_FALSE(layer1->did_draw_called());
EXPECT_FALSE(layer2->did_draw_called());
LayerTreeHostImpl::FrameData frame;
FakeLayerTreeHostImpl::RecursiveUpdateNumChildren(
host_impl_->active_tree()->root_layer());
EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame, gfx::FrameTime::Now());
host_impl_->DidDrawAllLayers(frame);
EXPECT_TRUE(root->did_draw_called());
EXPECT_TRUE(layer1->did_draw_called());
EXPECT_TRUE(layer2->did_draw_called());
EXPECT_NE(root->render_surface(), layer1->render_surface());
EXPECT_TRUE(!!layer1->render_surface());
}
class MissingTextureAnimatingLayer : public DidDrawCheckLayer {
public:
static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl,
int id,
bool tile_missing,
bool had_incomplete_tile,
bool animating,
ResourceProvider* resource_provider) {
return make_scoped_ptr(new MissingTextureAnimatingLayer(tree_impl,
id,
tile_missing,
had_incomplete_tile,
animating,
resource_provider));
}
void AppendQuads(RenderPass* render_pass,
AppendQuadsData* append_quads_data) override {
LayerImpl::AppendQuads(render_pass, append_quads_data);
if (had_incomplete_tile_)
append_quads_data->num_incomplete_tiles++;
if (tile_missing_)
append_quads_data->num_missing_tiles++;
}
private:
MissingTextureAnimatingLayer(LayerTreeImpl* tree_impl,
int id,
bool tile_missing,
bool had_incomplete_tile,
bool animating,
ResourceProvider* resource_provider)
: DidDrawCheckLayer(tree_impl, id),
tile_missing_(tile_missing),
had_incomplete_tile_(had_incomplete_tile) {
if (animating)
AddAnimatedTransformToLayer(this, 10.0, 3, 0);
}
bool tile_missing_;
bool had_incomplete_tile_;
};
struct PrepareToDrawSuccessTestCase {
struct State {
bool has_missing_tile = false;
bool has_incomplete_tile = false;
bool is_animating = false;
bool has_copy_request = false;
};
bool high_res_required = false;
State layer_before;
State layer_between;
State layer_after;
DrawResult expected_result;
explicit PrepareToDrawSuccessTestCase(DrawResult result)
: expected_result(result) {}
};
TEST_F(LayerTreeHostImplTest, PrepareToDrawSucceedsAndFails) {
std::vector<PrepareToDrawSuccessTestCase> cases;
// 0. Default case.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
// 1. Animated layer first.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_before.is_animating = true;
// 2. Animated layer between.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_between.is_animating = true;
// 3. Animated layer last.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_after.is_animating = true;
// 4. Missing tile first.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_before.has_missing_tile = true;
// 5. Missing tile between.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_between.has_missing_tile = true;
// 6. Missing tile last.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_after.has_missing_tile = true;
// 7. Incomplete tile first.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_before.has_incomplete_tile = true;
// 8. Incomplete tile between.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_between.has_incomplete_tile = true;
// 9. Incomplete tile last.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_after.has_incomplete_tile = true;
// 10. Animation with missing tile.
cases.push_back(
PrepareToDrawSuccessTestCase(DRAW_ABORTED_CHECKERBOARD_ANIMATIONS));
cases.back().layer_between.has_missing_tile = true;
cases.back().layer_between.is_animating = true;
// 11. Animation with missing tile and copy request after. Must succeed
// because the animation checkerboard means we'll get a new frame and the copy
// request's layer may be destroyed.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_between.has_missing_tile = true;
cases.back().layer_between.is_animating = true;
cases.back().layer_after.has_copy_request = true;
// 12. Animation with missing tile and copy request before. Must succeed
// because the animation checkerboard means we'll get a new frame and the copy
// request's layer may be destroyed.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_between.has_missing_tile = true;
cases.back().layer_between.is_animating = true;
cases.back().layer_before.has_copy_request = true;
// 13. Animation with incomplete tile.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_between.has_incomplete_tile = true;
cases.back().layer_between.is_animating = true;
// 14. High res required.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().high_res_required = true;
// 15. High res required with incomplete tile.
cases.push_back(
PrepareToDrawSuccessTestCase(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT));
cases.back().high_res_required = true;
cases.back().layer_between.has_incomplete_tile = true;
// 16. High res required with missing tile.
cases.push_back(
PrepareToDrawSuccessTestCase(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT));
cases.back().high_res_required = true;
cases.back().layer_between.has_missing_tile = true;
// 17. High res required is higher priority than animating missing tiles.
cases.push_back(
PrepareToDrawSuccessTestCase(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT));
cases.back().high_res_required = true;
cases.back().layer_between.has_missing_tile = true;
cases.back().layer_after.has_missing_tile = true;
cases.back().layer_after.is_animating = true;
// 18. High res required is higher priority than animating missing tiles.
cases.push_back(
PrepareToDrawSuccessTestCase(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT));
cases.back().high_res_required = true;
cases.back().layer_between.has_missing_tile = true;
cases.back().layer_before.has_missing_tile = true;
cases.back().layer_before.is_animating = true;
// 19. High res required is higher priority than copy requests.
cases.push_back(
PrepareToDrawSuccessTestCase(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT));
cases.back().high_res_required = true;
cases.back().layer_between.has_missing_tile = true;
cases.back().layer_after.has_copy_request = true;
// 20. High res required is higher priority than copy requests.
cases.push_back(
PrepareToDrawSuccessTestCase(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT));
cases.back().high_res_required = true;
cases.back().layer_between.has_missing_tile = true;
cases.back().layer_before.has_copy_request = true;
// 21. High res required is higher priority than copy requests.
cases.push_back(
PrepareToDrawSuccessTestCase(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT));
cases.back().high_res_required = true;
cases.back().layer_between.has_missing_tile = true;
cases.back().layer_between.has_copy_request = true;
host_impl_->active_tree()->SetRootLayer(
DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
DidDrawCheckLayer* root =
static_cast<DidDrawCheckLayer*>(host_impl_->active_tree()->root_layer());
root->SetHasRenderSurface(true);
LayerTreeHostImpl::FrameData frame;
EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame, gfx::FrameTime::Now());
host_impl_->DidDrawAllLayers(frame);
host_impl_->SwapBuffers(frame);
for (size_t i = 0; i < cases.size(); ++i) {
const auto& testcase = cases[i];
std::vector<LayerImpl*> to_remove;
for (auto* child : root->children())
to_remove.push_back(child);
for (auto* child : to_remove)
root->RemoveChild(child);
std::ostringstream scope;
scope << "Test case: " << i;
SCOPED_TRACE(scope.str());
root->AddChild(MissingTextureAnimatingLayer::Create(
host_impl_->active_tree(), 2, testcase.layer_before.has_missing_tile,
testcase.layer_before.has_incomplete_tile,
testcase.layer_before.is_animating, host_impl_->resource_provider()));
DidDrawCheckLayer* before =
static_cast<DidDrawCheckLayer*>(root->children().back());
if (testcase.layer_before.has_copy_request)
before->AddCopyRequest();
root->AddChild(MissingTextureAnimatingLayer::Create(
host_impl_->active_tree(), 3, testcase.layer_between.has_missing_tile,
testcase.layer_between.has_incomplete_tile,
testcase.layer_between.is_animating, host_impl_->resource_provider()));
DidDrawCheckLayer* between =
static_cast<DidDrawCheckLayer*>(root->children().back());
if (testcase.layer_between.has_copy_request)
between->AddCopyRequest();
root->AddChild(MissingTextureAnimatingLayer::Create(
host_impl_->active_tree(), 4, testcase.layer_after.has_missing_tile,
testcase.layer_after.has_incomplete_tile,
testcase.layer_after.is_animating, host_impl_->resource_provider()));
DidDrawCheckLayer* after =
static_cast<DidDrawCheckLayer*>(root->children().back());
if (testcase.layer_after.has_copy_request)
after->AddCopyRequest();
if (testcase.high_res_required)
host_impl_->SetRequiresHighResToDraw();
LayerTreeHostImpl::FrameData frame;
EXPECT_EQ(testcase.expected_result, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame, gfx::FrameTime::Now());
host_impl_->DidDrawAllLayers(frame);
host_impl_->SwapBuffers(frame);
}
}
TEST_F(LayerTreeHostImplTest,
PrepareToDrawWhenDrawAndSwapFullViewportEveryFrame) {
CreateHostImpl(DefaultSettings(),
FakeOutputSurface::CreateAlwaysDrawAndSwap3d());
EXPECT_TRUE(host_impl_->output_surface()
->capabilities()
.draw_and_swap_full_viewport_every_frame);
std::vector<PrepareToDrawSuccessTestCase> cases;
// 0. Default case.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
// 1. Animation with missing tile.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().layer_between.has_missing_tile = true;
cases.back().layer_between.is_animating = true;
// 2. High res required with incomplete tile.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().high_res_required = true;
cases.back().layer_between.has_incomplete_tile = true;
// 3. High res required with missing tile.
cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
cases.back().high_res_required = true;
cases.back().layer_between.has_missing_tile = true;
host_impl_->active_tree()->SetRootLayer(
DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
DidDrawCheckLayer* root =
static_cast<DidDrawCheckLayer*>(host_impl_->active_tree()->root_layer());
root->SetHasRenderSurface(true);
LayerTreeHostImpl::FrameData frame;
EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame, gfx::FrameTime::Now());
host_impl_->DidDrawAllLayers(frame);
host_impl_->SwapBuffers(frame);
for (size_t i = 0; i < cases.size(); ++i) {
const auto& testcase = cases[i];
std::vector<LayerImpl*> to_remove;
for (auto* child : root->children())
to_remove.push_back(child);
for (auto* child : to_remove)
root->RemoveChild(child);
std::ostringstream scope;
scope << "Test case: " << i;
SCOPED_TRACE(scope.str());
root->AddChild(MissingTextureAnimatingLayer::Create(
host_impl_->active_tree(), 2, testcase.layer_before.has_missing_tile,
testcase.layer_before.has_incomplete_tile,
testcase.layer_before.is_animating, host_impl_->resource_provider()));
DidDrawCheckLayer* before =
static_cast<DidDrawCheckLayer*>(root->children().back());
if (testcase.layer_before.has_copy_request)
before->AddCopyRequest();
root->AddChild(MissingTextureAnimatingLayer::Create(
host_impl_->active_tree(), 3, testcase.layer_between.has_missing_tile,
testcase.layer_between.has_incomplete_tile,
testcase.layer_between.is_animating, host_impl_->resource_provider()));
DidDrawCheckLayer* between =
static_cast<DidDrawCheckLayer*>(root->children().back());
if (testcase.layer_between.has_copy_request)
between->AddCopyRequest();
root->AddChild(MissingTextureAnimatingLayer::Create(
host_impl_->active_tree(), 4, testcase.layer_after.has_missing_tile,
testcase.layer_after.has_incomplete_tile,
testcase.layer_after.is_animating, host_impl_->resource_provider()));
DidDrawCheckLayer* after =
static_cast<DidDrawCheckLayer*>(root->children().back());
if (testcase.layer_after.has_copy_request)
after->AddCopyRequest();
if (testcase.high_res_required)
host_impl_->SetRequiresHighResToDraw();
LayerTreeHostImpl::FrameData frame;
EXPECT_EQ(testcase.expected_result, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame, gfx::FrameTime::Now());
host_impl_->DidDrawAllLayers(frame);
host_impl_->SwapBuffers(frame);
}
}
TEST_F(LayerTreeHostImplTest, ScrollRootIgnored) {
scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
root->SetScrollClipLayer(Layer::INVALID_ID);
root->SetHasRenderSurface(true);
host_impl_->active_tree()->SetRootLayer(root.Pass());
DrawFrame();
// Scroll event is ignored because layer is not scrollable.
EXPECT_EQ(InputHandler::SCROLL_IGNORED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::WHEEL));
EXPECT_FALSE(did_request_redraw_);
EXPECT_FALSE(did_request_commit_);
}
// TODO(bokan): Convert these tests to create inner and outer viewports.
class LayerTreeHostImplTopControlsTest : public LayerTreeHostImplTest {
public:
LayerTreeHostImplTopControlsTest()
// 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_),
top_controls_height_(50) {
settings_.use_pinch_virtual_viewport = true;
viewport_size_ = gfx::Size(clip_size_.width(),
clip_size_.height() + top_controls_height_);
}
bool CreateHostImpl(const LayerTreeSettings& settings,
scoped_ptr<OutputSurface> output_surface) override {
bool init =
LayerTreeHostImplTest::CreateHostImpl(settings, output_surface.Pass());
if (init) {
host_impl_->active_tree()->set_top_controls_height(top_controls_height_);
host_impl_->active_tree()->SetCurrentTopControlsShownRatio(1.f);
}
return init;
}
void SetupTopControlsAndScrollLayer() {
scoped_ptr<LayerImpl> root =
LayerImpl::Create(host_impl_->active_tree(), 1);
scoped_ptr<LayerImpl> root_clip =
LayerImpl::Create(host_impl_->active_tree(), 2);
root_clip->SetBounds(clip_size_);
root->SetScrollClipLayer(root_clip->id());
root->SetBounds(layer_size_);
root->SetContentBounds(layer_size_);
root->SetPosition(gfx::PointF());
root->SetDrawsContent(false);
root->SetIsContainerForFixedPositionLayers(true);
int inner_viewport_scroll_layer_id = root->id();
int page_scale_layer_id = root_clip->id();
root_clip->SetHasRenderSurface(true);
root_clip->AddChild(root.Pass());
root_clip->SetHasRenderSurface(true);
host_impl_->active_tree()->SetRootLayer(root_clip.Pass());
host_impl_->active_tree()->SetViewportLayersFromIds(
Layer::INVALID_ID, page_scale_layer_id, inner_viewport_scroll_layer_id,
Layer::INVALID_ID);
// Set a viewport size that is large enough to contain both the top controls
// and some content.
host_impl_->SetViewportSize(viewport_size_);
host_impl_->sync_tree()->set_top_controls_shrink_blink_size(true);
host_impl_->DidChangeTopControlsPosition();
host_impl_->CreatePendingTree();
host_impl_->sync_tree()->set_top_controls_height(top_controls_height_);
root =
LayerImpl::Create(host_impl_->sync_tree(), 1);
root_clip =
LayerImpl::Create(host_impl_->sync_tree(), 2);
root_clip->SetBounds(clip_size_);
root->SetScrollClipLayer(root_clip->id());
root->SetBounds(layer_size_);
root->SetContentBounds(layer_size_);
root->SetPosition(gfx::PointF());
root->SetDrawsContent(false);
root->SetIsContainerForFixedPositionLayers(true);
inner_viewport_scroll_layer_id = root->id();
page_scale_layer_id = root_clip->id();
root_clip->AddChild(root.Pass());
host_impl_->sync_tree()->SetRootLayer(root_clip.Pass());
host_impl_->sync_tree()->SetViewportLayersFromIds(
Layer::INVALID_ID, page_scale_layer_id, inner_viewport_scroll_layer_id,
Layer::INVALID_ID);
// Set a viewport size that is large enough to contain both the top controls
// and some content.
host_impl_->SetViewportSize(viewport_size_);
host_impl_->sync_tree()->set_top_controls_shrink_blink_size(true);
host_impl_->DidChangeTopControlsPosition();
}
void SetupTopControlsAndScrollLayerWithVirtualViewport(
const gfx::Size& inner_viewport_size,
const gfx::Size& outer_viewport_size,
const gfx::Size& scroll_layer_size) {
CreateHostImpl(settings_, CreateOutputSurface());
host_impl_->sync_tree()->set_top_controls_shrink_blink_size(true);
host_impl_->sync_tree()->set_top_controls_height(top_controls_height_);
host_impl_->DidChangeTopControlsPosition();
scoped_ptr<LayerImpl> root =
LayerImpl::Create(host_impl_->active_tree(), 1);
scoped_ptr<LayerImpl> root_clip =
LayerImpl::Create(host_impl_->active_tree(), 2);
scoped_ptr<LayerImpl> page_scale =
LayerImpl::Create(host_impl_->active_tree(), 3);
scoped_ptr<LayerImpl> outer_scroll =
LayerImpl::Create(host_impl_->active_tree(), 4);
scoped_ptr<LayerImpl> outer_clip =
LayerImpl::Create(host_impl_->active_tree(), 5);
root_clip->SetBounds(inner_viewport_size);
root->SetScrollClipLayer(root_clip->id());
root->SetBounds(outer_viewport_size);
root->SetContentBounds(outer_viewport_size);
root->SetPosition(gfx::PointF());
root->SetDrawsContent(false);
root->SetIsContainerForFixedPositionLayers(true);
root_clip->SetHasRenderSurface(true);
outer_clip->SetBounds(outer_viewport_size);
outer_scroll->SetScrollClipLayer(outer_clip->id());
outer_scroll->SetBounds(scroll_layer_size);
outer_scroll->SetContentBounds(scroll_layer_size);
outer_scroll->SetPosition(gfx::PointF());
outer_scroll->SetDrawsContent(false);
outer_scroll->SetIsContainerForFixedPositionLayers(true);
int inner_viewport_scroll_layer_id = root->id();
int outer_viewport_scroll_layer_id = outer_scroll->id();
int page_scale_layer_id = page_scale->id();
outer_clip->AddChild(outer_scroll.Pass());
root->AddChild(outer_clip.Pass());
page_scale->AddChild(root.Pass());
root_clip->AddChild(page_scale.Pass());
host_impl_->active_tree()->SetRootLayer(root_clip.Pass());
host_impl_->active_tree()->SetViewportLayersFromIds(
Layer::INVALID_ID, page_scale_layer_id, inner_viewport_scroll_layer_id,
outer_viewport_scroll_layer_id);
host_impl_->SetViewportSize(inner_viewport_size);
LayerImpl* root_clip_ptr = host_impl_->active_tree()->root_layer();
EXPECT_EQ(inner_viewport_size, root_clip_ptr->bounds());
}
protected:
gfx::Size layer_size_;
gfx::Size clip_size_;
gfx::Size viewport_size_;
float top_controls_height_;
LayerTreeSettings settings_;
}; // class LayerTreeHostImplTopControlsTest
TEST_F(LayerTreeHostImplTopControlsTest, ScrollTopControlsByFractionalAmount) {
SetupTopControlsAndScrollLayerWithVirtualViewport(
gfx::Size(10, 10), gfx::Size(10, 10), gfx::Size(10, 10));
DrawFrame();
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
// 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 top controls.
gfx::Vector2dF top_controls_scroll_delta(0.f, 5.25f);
host_impl_->top_controls_manager()->ScrollBegin();
host_impl_->top_controls_manager()->ScrollBy(top_controls_scroll_delta);
host_impl_->top_controls_manager()->ScrollEnd();
LayerImpl* inner_viewport_scroll_layer =
host_impl_->active_tree()->InnerViewportScrollLayer();
DCHECK(inner_viewport_scroll_layer);
host_impl_->ScrollEnd();
EXPECT_FLOAT_EQ(top_controls_scroll_delta.y(),
inner_viewport_scroll_layer->FixedContainerSizeDelta().y());
}
// In this test, the outer viewport is initially unscrollable. We test that a
// scroll initiated on the inner viewport, causing the top controls to show and
// thus making the outer viewport scrollable, still scrolls the outer viewport.
TEST_F(LayerTreeHostImplTopControlsTest,
TopControlsOuterViewportBecomesScrollable) {
SetupTopControlsAndScrollLayerWithVirtualViewport(
gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100));
DrawFrame();
LayerImpl *inner_scroll =
host_impl_->active_tree()->InnerViewportScrollLayer();
LayerImpl *inner_container =
host_impl_->active_tree()->InnerViewportContainerLayer();
LayerImpl *outer_scroll =
host_impl_->active_tree()->OuterViewportScrollLayer();
LayerImpl *outer_container =
host_impl_->active_tree()->OuterViewportContainerLayer();
// Need SetDrawsContent so ScrollBegin's hit test finds an actual layer.
outer_scroll->SetDrawsContent(true);
host_impl_->active_tree()->PushPageScaleFromMainThread(2.f, 1.f, 2.f);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0.f, 50.f));
// The entire scroll delta should have been used to hide the top controls.
// The viewport layers should be resized back to their full sizes.
EXPECT_EQ(0.f, host_impl_->active_tree()->CurrentTopControlsShownRatio());
EXPECT_EQ(0.f, inner_scroll->CurrentScrollOffset().y());
EXPECT_EQ(100.f, inner_container->BoundsForScrolling().height());
EXPECT_EQ(100.f, outer_container->BoundsForScrolling().height());
// The inner viewport should be scrollable by 50px * page_scale.
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0.f, 100.f));
EXPECT_EQ(50.f, inner_scroll->CurrentScrollOffset().y());
EXPECT_EQ(0.f, outer_scroll->CurrentScrollOffset().y());
EXPECT_EQ(gfx::ScrollOffset(), outer_scroll->MaxScrollOffset());
host_impl_->ScrollEnd();
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), inner_scroll);
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0.f, -50.f));
// The entire scroll delta should have been used to show the top controls.
// The outer viewport should be resized to accomodate and scrolled to the
// bottom of the document to keep the viewport in place.
EXPECT_EQ(1.f, host_impl_->active_tree()->CurrentTopControlsShownRatio());
EXPECT_EQ(50.f, outer_container->BoundsForScrolling().height());
EXPECT_EQ(50.f, inner_container->BoundsForScrolling().height());
EXPECT_EQ(25.f, outer_scroll->CurrentScrollOffset().y());
EXPECT_EQ(25.f, inner_scroll->CurrentScrollOffset().y());
// Now when we continue scrolling, make sure the outer viewport gets scrolled
// since it wasn't scrollable when the scroll began.
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0.f, -20.f));
EXPECT_EQ(15.f, outer_scroll->CurrentScrollOffset().y());
EXPECT_EQ(25.f, inner_scroll->CurrentScrollOffset().y());
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0.f, -30.f));
EXPECT_EQ(0.f, outer_scroll->CurrentScrollOffset().y());
EXPECT_EQ(25.f, inner_scroll->CurrentScrollOffset().y());
host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0.f, -50.f));
host_impl_->ScrollEnd();
EXPECT_EQ(0.f, outer_scroll->CurrentScrollOffset().y());
EXPECT_EQ(0.f, inner_scroll->CurrentScrollOffset().y());
}
// Test that the fixed position container delta is appropriately adjusted
// by the top controls showing/hiding and page scale doesn't affect it.
TEST_F(LayerTreeHostImplTopControlsTest, FixedContainerDelta) {
SetupTopControlsAndScrollLayerWithVirtualViewport(
gfx::Size(100, 100), gfx::Size(100, 100), gfx::Size(100, 100));
DrawFrame();
float page_scale = 1.5f;
LayerImpl* outer_viewport_scroll_layer =
host_impl_->active_tree()->OuterViewportScrollLayer();
// Zoom in, since the fixed container is the outer viewport, the delta should
// not be scaled.
host_impl_->active_tree()->PushPageScaleFromMainThread(page_scale, 1.f, 2.f);
EXPECT_EQ(InputHandler::SCROLL_STARTED,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::GESTURE));
// Scroll down, the top controls hiding should expand the viewport size so
// the delta should be equal to the scroll distance.
gfx::Vector2dF top_controls_scroll_delta(0.f, 20.f);
host_impl_->top_controls_manager()->ScrollBegin();
host_impl_->top_controls_manager()->ScrollBy(top_controls_scroll_delta);
EXPECT_FLOAT_EQ(top_controls_height_ - top_controls_scroll_delta.y(),
host_impl_->top_controls_manager()->ContentTopOffset());
EXPECT_VECTOR_EQ(top_controls_scroll_delta,
outer_viewport_scroll_layer->FixedContainerSizeDelta());
host_impl_->ScrollEnd();
// Scroll past the maximum extent. The delta shouldn't be greater than the
// top controls height.
host_impl_->top_controls_manager()->ScrollBegin();
host_impl_->top_controls_manager()->ScrollBy(top_controls_scroll_delta);
host_impl_->top_controls_manager()->ScrollBy(top_controls_scroll_delta);
host_impl_->top_controls_manager()->ScrollBy(top_controls_scroll_delta);
EXPECT_EQ(0.f, host_impl_->top_controls_manager()->ContentTopOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, top_controls_height_),
outer_viewport_scroll_layer->FixedContainerSizeDelta());
host_impl_->ScrollEnd();
// Scroll in the direction to make the top controls show.
host_impl_->top_controls_manager()->ScrollBegin();
host_impl_->top_controls_manager()->ScrollBy(-top_controls_scroll_delta);
EXPECT_EQ(top_controls_scroll_delta.y(),
host_impl_->top_controls_manager()->ContentTopOffset());
EXPECT_VECTOR_EQ(
gfx::Vector2dF(0, top_controls_height_ - top_controls_scroll_delta.y()),
outer_viewport_scroll_layer->FixedContainerSizeDelta());
host_impl_->top_controls_manager()->ScrollEnd();
}
// Test that if a scrollable sublayer doesn't consume the scroll,
// top controls should hide when scrolling down.
TEST_F(LayerTreeHostImplTopControlsTest, TopControlsScrollableSublayer) {
gfx::Size sub_content_size(100, 400);
gfx::Size sub_content_layer_size(100, 300);
SetupTopControlsAndScrollLayerWithVirtualViewport(
gfx::Size(100, 50), gfx::Size(100, 100), gfx::Size(100, 100));
DrawFrame();
// Show top controls
EXPECT_EQ(1.f, host_impl_->active_tree()->CurrentTopControlsShownRatio());
LayerImpl* outer_viewport_scroll_layer =
host_impl_->active_tree()->OuterViewportScrollLayer();
int id = outer_viewport_scroll_layer->id();
scoped_ptr<LayerImpl> child =
LayerImpl::Create(host_impl_->active_tree(), id + 2);
scoped_ptr<LayerImpl> child_clip =
La