| // 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
|