| // 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/latency_info_swap_promise.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/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/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/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 "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::_; |
| |
| 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) { |
| } |
| |
| 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 PostDelayedScrollbarFadeOnImplThread(const base::Closure& start_fade, |
| base::TimeDelta delay) override { |
| scrollbar_fade_start_ = start_fade; |
| requested_scrollbar_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 scrollbar_fade_start_; |
| base::TimeDelta requested_scrollbar_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::ScrollStarted, |
| 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::ScrollStarted, |
| 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::ScrollIgnored, |
| 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::ScrollStarted, |
| 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::ScrollStarted, |
| 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, WheelEventHandlers) { |
| SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| host_impl_->SetViewportSize(gfx::Size(50, 50)); |
| DrawFrame(); |
| LayerImpl* root = host_impl_->active_tree()->root_layer(); |
| |
| root->SetHaveWheelEventHandlers(true); |
| |
| // With registered event handlers, wheel scrolls have to go to the main |
| // thread. |
| EXPECT_EQ(InputHandler::ScrollOnMainThread, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| |
| // But gesture scrolls can still be handled. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), 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::ScrollIgnored, |
| host_impl_->FlingScrollBegin()); |
| |
| // Start scrolling a layer |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| |
| // Now the fling should go ahead since we've started scrolling a layer |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| 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::ScrollIgnored, |
| host_impl_->FlingScrollBegin()); |
| |
| // Start scrolling a layer |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| |
| // Now the fling should go ahead since we've started scrolling a layer |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| 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::ScrollOnMainThread, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| |
| // The fling should be ignored since there's no layer being scrolled impl-side |
| EXPECT_EQ(InputHandler::ScrollIgnored, |
| 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::ScrollOnMainThread, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| EXPECT_EQ(InputHandler::ScrollOnMainThread, |
| 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::ScrollOnMainThread, |
| host_impl_->ScrollBegin(gfx::Point(25, 25), |
| InputHandler::Wheel)); |
| EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(25, 25), |
| InputHandler::Wheel)); |
| EXPECT_EQ(InputHandler::ScrollOnMainThread, |
| 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::ScrollStarted, |
| 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::ScrollStarted, |
| 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::ScrollStarted, |
| 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::ScrollOnMainThread, |
| 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::ScrollStarted, |
| 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::ScrollStarted, |
| 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::ScrollStarted, |
| 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::ScrollStarted, |
| 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::ScrollStarted, |
| 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::ScrollStarted, |
| 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::ScrollStarted, |
| 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_; |
| }; |
| |
| #define SETUP_LAYERS_FOR_SCROLLBAR_ANIMATION_TEST() \ |
| 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(); |
| |
| TEST_F(LayerTreeHostImplTest, ScrollbarLinearFadeScheduling) { |
| LayerTreeSettings settings; |
| settings.scrollbar_animator = LayerTreeSettings::LinearFade; |
| settings.scrollbar_fade_delay_ms = 20; |
| settings.scrollbar_fade_duration_ms = 20; |
| |
| SETUP_LAYERS_FOR_SCROLLBAR_ANIMATION_TEST(); |
| |
| base::TimeTicks fake_now = gfx::FrameTime::Now(); |
| |
| EXPECT_EQ(base::TimeDelta(), requested_scrollbar_animation_delay_); |
| EXPECT_FALSE(did_request_redraw_); |
| |
| // If no scroll happened during a scroll gesture, it should have no effect. |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel); |
| host_impl_->ScrollEnd(); |
| EXPECT_EQ(base::TimeDelta(), requested_scrollbar_animation_delay_); |
| EXPECT_FALSE(did_request_redraw_); |
| EXPECT_TRUE(scrollbar_fade_start_.Equals(base::Closure())); |
| |
| // After a scroll, a fade animation should be scheduled about 20ms from now. |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0, 5)); |
| host_impl_->ScrollEnd(); |
| did_request_redraw_ = false; |
| did_request_animate_ = false; |
| EXPECT_LT(base::TimeDelta::FromMilliseconds(19), |
| requested_scrollbar_animation_delay_); |
| EXPECT_FALSE(did_request_redraw_); |
| EXPECT_FALSE(did_request_animate_); |
| requested_scrollbar_animation_delay_ = base::TimeDelta(); |
| scrollbar_fade_start_.Run(); |
| host_impl_->Animate(fake_now); |
| |
| // After the fade begins, we should start getting redraws instead of a |
| // scheduled animation. |
| fake_now += base::TimeDelta::FromMilliseconds(25); |
| EXPECT_EQ(base::TimeDelta(), requested_scrollbar_animation_delay_); |
| EXPECT_TRUE(did_request_animate_); |
| did_request_animate_ = false; |
| |
| // Setting the scroll offset outside a scroll should also cause the scrollbar |
| // to appear and to schedule a fade. |
| host_impl_->InnerViewportScrollLayer()->PushScrollOffsetFromMainThread( |
| gfx::ScrollOffset(5, 5)); |
| EXPECT_LT(base::TimeDelta::FromMilliseconds(19), |
| requested_scrollbar_animation_delay_); |
| EXPECT_FALSE(did_request_redraw_); |
| EXPECT_FALSE(did_request_animate_); |
| requested_scrollbar_animation_delay_ = base::TimeDelta(); |
| |
| // Unnecessarily Fade animation of solid color scrollbar is not triggered. |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(5, 0)); |
| host_impl_->ScrollEnd(); |
| EXPECT_EQ(base::TimeDelta(), requested_scrollbar_animation_delay_); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollbarFadePinchZoomScrollbars) { |
| LayerTreeSettings settings; |
| settings.scrollbar_animator = LayerTreeSettings::LinearFade; |
| settings.scrollbar_fade_delay_ms = 20; |
| settings.scrollbar_fade_duration_ms = 20; |
| settings.use_pinch_zoom_scrollbars = true; |
| |
| SETUP_LAYERS_FOR_SCROLLBAR_ANIMATION_TEST(); |
| |
| base::TimeTicks fake_now = gfx::FrameTime::Now(); |
| |
| host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 4.f); |
| |
| EXPECT_EQ(base::TimeDelta(), requested_scrollbar_animation_delay_); |
| EXPECT_FALSE(did_request_animate_); |
| |
| // If no scroll happened during a scroll gesture, it should have no effect. |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel); |
| host_impl_->ScrollEnd(); |
| EXPECT_EQ(base::TimeDelta(), requested_scrollbar_animation_delay_); |
| EXPECT_FALSE(did_request_animate_); |
| EXPECT_TRUE(scrollbar_fade_start_.Equals(base::Closure())); |
| |
| // After a scroll, no fade animation should be scheduled. |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(5, 0)); |
| host_impl_->ScrollEnd(); |
| did_request_redraw_ = false; |
| EXPECT_EQ(base::TimeDelta(), requested_scrollbar_animation_delay_); |
| EXPECT_FALSE(did_request_animate_); |
| requested_scrollbar_animation_delay_ = base::TimeDelta(); |
| |
| // We should not see any draw requests. |
| fake_now += base::TimeDelta::FromMilliseconds(25); |
| EXPECT_EQ(base::TimeDelta(), requested_scrollbar_animation_delay_); |
| EXPECT_FALSE(did_request_animate_); |
| |
| // Make page scale > min so that subsequent scrolls will trigger fades. |
| host_impl_->SetPageScaleOnActiveTree(1.1f); |
| |
| // After a scroll, a fade animation should be scheduled about 20ms from now. |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(5, 0)); |
| host_impl_->ScrollEnd(); |
| did_request_redraw_ = false; |
| EXPECT_LT(base::TimeDelta::FromMilliseconds(19), |
| requested_scrollbar_animation_delay_); |
| EXPECT_FALSE(did_request_animate_); |
| requested_scrollbar_animation_delay_ = base::TimeDelta(); |
| scrollbar_fade_start_.Run(); |
| |
| // After the fade begins, we should start getting redraws instead of a |
| // scheduled animation. |
| fake_now += base::TimeDelta::FromMilliseconds(25); |
| host_impl_->Animate(fake_now); |
| EXPECT_TRUE(did_request_animate_); |
| } |
| |
| 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); |
| } |
| |
| // Scrolling should update metadata immediately. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| 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); |
| } |
| |
| // 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, |
| const Occlusion& occlusion_in_content_space, |
| AppendQuadsData* append_quads_data) override { |
| append_quads_called_ = true; |
| LayerImpl::AppendQuads( |
| render_pass, occlusion_in_content_space, 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; |
| } |
| |
| 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, |
| const Occlusion& occlusion_in_content_space, |
| AppendQuadsData* append_quads_data) override { |
| LayerImpl::AppendQuads( |
| render_pass, occlusion_in_content_space, 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_; |
| }; |
| |
| TEST_F(LayerTreeHostImplTest, PrepareToDrawSucceedsOnDefault) { |
| 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); |
| bool tile_missing = false; |
| bool had_incomplete_tile = false; |
| bool is_animating = false; |
| root->AddChild( |
| MissingTextureAnimatingLayer::Create(host_impl_->active_tree(), |
| 2, |
| tile_missing, |
| had_incomplete_tile, |
| is_animating, |
| host_impl_->resource_provider())); |
| |
| LayerTreeHostImpl::FrameData frame; |
| |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, PrepareToDrawSucceedsWithAnimatedLayer) { |
| 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); |
| bool tile_missing = false; |
| bool had_incomplete_tile = false; |
| bool is_animating = true; |
| root->AddChild( |
| MissingTextureAnimatingLayer::Create(host_impl_->active_tree(), |
| 2, |
| tile_missing, |
| had_incomplete_tile, |
| is_animating, |
| host_impl_->resource_provider())); |
| |
| LayerTreeHostImpl::FrameData frame; |
| |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, PrepareToDrawSucceedsWithMissingTiles) { |
| host_impl_->active_tree()->SetRootLayer( |
| DidDrawCheckLayer::Create(host_impl_->active_tree(), 3)); |
| 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); |
| |
| bool tile_missing = true; |
| bool had_incomplete_tile = false; |
| bool is_animating = false; |
| root->AddChild( |
| MissingTextureAnimatingLayer::Create(host_impl_->active_tree(), |
| 4, |
| tile_missing, |
| had_incomplete_tile, |
| is_animating, |
| host_impl_->resource_provider())); |
| LayerTreeHostImpl::FrameData frame2; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame2)); |
| host_impl_->DrawLayers(&frame2, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame2); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, PrepareToDrawSucceedsWithIncompleteTile) { |
| host_impl_->active_tree()->SetRootLayer( |
| DidDrawCheckLayer::Create(host_impl_->active_tree(), 3)); |
| 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); |
| |
| bool tile_missing = false; |
| bool had_incomplete_tile = true; |
| bool is_animating = false; |
| root->AddChild( |
| MissingTextureAnimatingLayer::Create(host_impl_->active_tree(), |
| 4, |
| tile_missing, |
| had_incomplete_tile, |
| is_animating, |
| host_impl_->resource_provider())); |
| LayerTreeHostImpl::FrameData frame2; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame2)); |
| host_impl_->DrawLayers(&frame2, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame2); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, |
| PrepareToDrawFailsWithAnimationAndMissingTilesUsesCheckerboard) { |
| host_impl_->active_tree()->SetRootLayer( |
| DidDrawCheckLayer::Create(host_impl_->active_tree(), 5)); |
| 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); |
| |
| bool tile_missing = true; |
| bool had_incomplete_tile = false; |
| bool is_animating = true; |
| root->AddChild( |
| MissingTextureAnimatingLayer::Create(host_impl_->active_tree(), |
| 6, |
| tile_missing, |
| had_incomplete_tile, |
| is_animating, |
| host_impl_->resource_provider())); |
| LayerTreeHostImpl::FrameData frame2; |
| EXPECT_EQ(DRAW_ABORTED_CHECKERBOARD_ANIMATIONS, |
| host_impl_->PrepareToDraw(&frame2)); |
| host_impl_->DrawLayers(&frame2, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame2); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, |
| PrepareToDrawSucceedsWithAnimationAndIncompleteTiles) { |
| host_impl_->active_tree()->SetRootLayer( |
| DidDrawCheckLayer::Create(host_impl_->active_tree(), 5)); |
| 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); |
| |
| bool tile_missing = false; |
| bool had_incomplete_tile = true; |
| bool is_animating = true; |
| root->AddChild( |
| MissingTextureAnimatingLayer::Create(host_impl_->active_tree(), |
| 6, |
| tile_missing, |
| had_incomplete_tile, |
| is_animating, |
| host_impl_->resource_provider())); |
| LayerTreeHostImpl::FrameData frame2; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame2)); |
| host_impl_->DrawLayers(&frame2, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame2); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, PrepareToDrawSucceedsWhenHighResRequired) { |
| host_impl_->active_tree()->SetRootLayer( |
| DidDrawCheckLayer::Create(host_impl_->active_tree(), 7)); |
| 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); |
| |
| bool tile_missing = false; |
| bool had_incomplete_tile = false; |
| bool is_animating = false; |
| root->AddChild( |
| MissingTextureAnimatingLayer::Create(host_impl_->active_tree(), |
| 8, |
| tile_missing, |
| had_incomplete_tile, |
| is_animating, |
| host_impl_->resource_provider())); |
| host_impl_->SetRequiresHighResToDraw(); |
| LayerTreeHostImpl::FrameData frame2; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame2)); |
| host_impl_->DrawLayers(&frame2, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame2); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, |
| PrepareToDrawFailsWhenHighResRequiredAndIncompleteTiles) { |
| host_impl_->active_tree()->SetRootLayer( |
| DidDrawCheckLayer::Create(host_impl_->active_tree(), 7)); |
| 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); |
| |
| bool tile_missing = false; |
| bool had_incomplete_tile = true; |
| bool is_animating = false; |
| root->AddChild( |
| MissingTextureAnimatingLayer::Create(host_impl_->active_tree(), |
| 8, |
| tile_missing, |
| had_incomplete_tile, |
| is_animating, |
| host_impl_->resource_provider())); |
| host_impl_->SetRequiresHighResToDraw(); |
| LayerTreeHostImpl::FrameData frame2; |
| EXPECT_EQ(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT, |
| host_impl_->PrepareToDraw(&frame2)); |
| host_impl_->DrawLayers(&frame2, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame2); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, |
| PrepareToDrawFailsWhenHighResRequiredAndMissingTile) { |
| host_impl_->active_tree()->SetRootLayer( |
| DidDrawCheckLayer::Create(host_impl_->active_tree(), 7)); |
| 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); |
| |
| bool tile_missing = true; |
| bool had_incomplete_tile = false; |
| bool is_animating = false; |
| root->AddChild( |
| MissingTextureAnimatingLayer::Create(host_impl_->active_tree(), |
| 8, |
| tile_missing, |
| had_incomplete_tile, |
| is_animating, |
| host_impl_->resource_provider())); |
| host_impl_->SetRequiresHighResToDraw(); |
| LayerTreeHostImpl::FrameData frame2; |
| EXPECT_EQ(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT, |
| host_impl_->PrepareToDraw(&frame2)); |
| host_impl_->DrawLayers(&frame2, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame2); |
| } |
| |
| 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::ScrollIgnored, |
| 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_.calculate_top_controls_position = true; |
| 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 && settings.calculate_top_controls_position) { |
| host_impl_->active_tree()->set_top_controls_height(top_controls_height_); |
| host_impl_->active_tree()->set_top_controls_delta(top_controls_height_); |
| host_impl_->top_controls_manager()->SetTopControlsHeight( |
| top_controls_height_); |
| host_impl_->DidChangeTopControlsPosition(); |
| } |
| 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(); |
| 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_->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::ScrollStarted, |
| 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_EQ(top_controls_scroll_delta, |
| inner_viewport_scroll_layer->FixedContainerSizeDelta()); |
| } |
| |
| // 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::ScrollStarted, |
| 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()->total_top_controls_content_offset()); |
| 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::ScrollStarted, |
| 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(50.f, |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| 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::ScrollStarted, |
| 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_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(top_controls_height_, |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| |
| 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 = |
| LayerImpl::Create(host_impl_->active_tree(), id + 3); |
| |
| child_clip->SetBounds(sub_content_layer_size); |
| child->SetScrollClipLayer(child_clip->id()); |
| child->SetBounds(sub_content_size); |
| child->SetContentBounds(sub_content_size); |
| child->SetPosition(gfx::PointF()); |
| child->SetDrawsContent(true); |
| child->SetIsContainerForFixedPositionLayers(true); |
| |
| // scroll child to limit |
| child->SetScrollDelta(gfx::Vector2dF(0, 100.f)); |
| child_clip->AddChild(child.Pass()); |
| outer_viewport_scroll_layer->AddChild(child_clip.Pass()); |
| |
| // Scroll 25px to hide top controls |
| gfx::Vector2dF scroll_delta(0.f, 25.f); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| // Top controls should be hidden |
| EXPECT_EQ(scroll_delta.y(), |
| top_controls_height_ - |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| } |
| |
| // Ensure setting the top controls position explicitly using the setters on the |
| // TreeImpl correctly affects the top controls manager and viewport bounds. |
| TEST_F(LayerTreeHostImplTopControlsTest, PositionTopControlsExplicitly) { |
| CreateHostImpl(settings_, CreateOutputSurface()); |
| SetupTopControlsAndScrollLayer(); |
| DrawFrame(); |
| |
| host_impl_->active_tree()->set_top_controls_delta(0.f); |
| host_impl_->active_tree()->set_top_controls_content_offset(30.f); |
| EXPECT_EQ(30.f, host_impl_->top_controls_manager()->ContentTopOffset()); |
| EXPECT_EQ(-20.f, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| |
| host_impl_->active_tree()->set_top_controls_delta(-30.f); |
| EXPECT_EQ(0.f, host_impl_->top_controls_manager()->ContentTopOffset()); |
| EXPECT_EQ(-50.f, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| |
| host_impl_->DidChangeTopControlsPosition(); |
| |
| // Now that top controls have moved, expect the clip to resize. |
| LayerImpl* root_clip_ptr = host_impl_->active_tree()->root_layer(); |
| EXPECT_EQ(viewport_size_, root_clip_ptr->bounds()); |
| } |
| |
| // Test that the top_controls delta and sent delta are appropriately |
| // applied on sync tree activation. The total top controls offset shouldn't |
| // change after the activation. |
| TEST_F(LayerTreeHostImplTopControlsTest, ApplyDeltaOnTreeActivation) { |
| CreateHostImpl(settings_, CreateOutputSurface()); |
| SetupTopControlsAndScrollLayer(); |
| DrawFrame(); |
| |
| host_impl_->sync_tree()->set_top_controls_content_offset(15.f); |
| |
| host_impl_->active_tree()->set_top_controls_content_offset(20.f); |
| host_impl_->active_tree()->set_top_controls_delta(-20.f); |
| host_impl_->active_tree()->set_sent_top_controls_delta(-5.f); |
| |
| host_impl_->DidChangeTopControlsPosition(); |
| LayerImpl* root_clip_ptr = host_impl_->active_tree()->root_layer(); |
| EXPECT_EQ(viewport_size_, root_clip_ptr->bounds()); |
| EXPECT_EQ(0.f, host_impl_->top_controls_manager()->ContentTopOffset()); |
| EXPECT_EQ(0.f, |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| |
| host_impl_->ActivateSyncTree(); |
| |
| root_clip_ptr = host_impl_->active_tree()->root_layer(); |
| EXPECT_EQ(0.f, host_impl_->top_controls_manager()->ContentTopOffset()); |
| EXPECT_EQ(viewport_size_, root_clip_ptr->bounds()); |
| |
| EXPECT_EQ(0.f, host_impl_->active_tree()->sent_top_controls_delta()); |
| EXPECT_EQ(-15.f, host_impl_->active_tree()->top_controls_delta()); |
| EXPECT_EQ(15.f, host_impl_->active_tree()->top_controls_content_offset()); |
| EXPECT_EQ(0.f, |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| } |
| |
| // Test that changing the top controls layout height is correctly applied to |
| // the inner viewport container bounds. That is, the top controls layout |
| // height is the amount that the inner viewport container was shrunk outside |
| // the compositor to accommodate the top controls. |
| TEST_F(LayerTreeHostImplTopControlsTest, TopControlsLayoutHeightChanged) { |
| CreateHostImpl(settings_, CreateOutputSurface()); |
| SetupTopControlsAndScrollLayer(); |
| DrawFrame(); |
| |
| host_impl_->sync_tree()->set_top_controls_content_offset(50.f); |
| host_impl_->sync_tree()->set_top_controls_shrink_blink_size(true); |
| |
| host_impl_->active_tree()->set_top_controls_content_offset(50.f); |
| host_impl_->active_tree()->set_top_controls_delta(-50.f); |
| |
| host_impl_->DidChangeTopControlsPosition(); |
| LayerImpl* root_clip_ptr = host_impl_->active_tree()->root_layer(); |
| EXPECT_EQ(viewport_size_, root_clip_ptr->bounds()); |
| EXPECT_EQ(0.f, host_impl_->top_controls_manager()->ContentTopOffset()); |
| |
| host_impl_->sync_tree()->root_layer()->SetBounds( |
| gfx::Size(root_clip_ptr->bounds().width(), |
| root_clip_ptr->bounds().height() - 50.f)); |
| |
| host_impl_->ActivateSyncTree(); |
| |
| root_clip_ptr = host_impl_->active_tree()->root_layer(); |
| EXPECT_EQ(0.f, host_impl_->top_controls_manager()->ContentTopOffset()); |
| |
| // The total bounds should remain unchanged since the bounds delta should |
| // account for the difference between the layout height and the current |
| // top controls offset. |
| EXPECT_EQ(viewport_size_, root_clip_ptr->bounds()); |
| EXPECT_VECTOR_EQ(gfx::Vector2dF(0.f, 50.f), root_clip_ptr->bounds_delta()); |
| |
| host_impl_->active_tree()->set_top_controls_delta(0.f); |
| host_impl_->DidChangeTopControlsPosition(); |
| |
| EXPECT_EQ(50.f, host_impl_->top_controls_manager()->ContentTopOffset()); |
| EXPECT_VECTOR_EQ(gfx::Vector2dF(0.f, 0.f), root_clip_ptr->bounds_delta()); |
| EXPECT_EQ(gfx::Size(viewport_size_.width(), viewport_size_.height() - 50.f), |
| root_clip_ptr->bounds()); |
| } |
| |
| // Test that showing/hiding the top controls when the viewport is fully scrolled |
| // doesn't incorrectly change the viewport offset due to clamping from changing |
| // viewport bounds. |
| TEST_F(LayerTreeHostImplTopControlsTest, TopControlsViewportOffsetClamping) { |
| SetupTopControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); |
| DrawFrame(); |
| |
| EXPECT_EQ(top_controls_height_, |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| |
| LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer(); |
| LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer(); |
| |
| // Scroll the viewports to max scroll offset. |
| outer_scroll->SetScrollDelta(gfx::Vector2dF(0, 200.f)); |
| inner_scroll->SetScrollDelta(gfx::Vector2dF(100, 100.f)); |
| |
| gfx::ScrollOffset viewport_offset = |
| host_impl_->active_tree()->TotalScrollOffset(); |
| EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(), viewport_offset); |
| |
| // Hide the top controls by 25px. |
| gfx::Vector2dF scroll_delta(0.f, 25.f); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| |
| // scrolling down at the max extents no longer hides the top controls |
| EXPECT_EQ(0.f, |
| top_controls_height_ - |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| |
| // forcefully hide the top controls by 25px |
| host_impl_->top_controls_manager()->ScrollBy(scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| EXPECT_EQ(scroll_delta.y(), |
| top_controls_height_ - |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| |
| inner_scroll->ClampScrollToMaxScrollOffset(); |
| outer_scroll->ClampScrollToMaxScrollOffset(); |
| |
| // We should still be fully scrolled. |
| EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(), |
| host_impl_->active_tree()->TotalScrollOffset()); |
| |
| viewport_offset = host_impl_->active_tree()->TotalScrollOffset(); |
| |
| // Bring the top controls down by 25px. |
| scroll_delta = gfx::Vector2dF(0.f, -25.f); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| // The viewport offset shouldn't have changed. |
| EXPECT_EQ(viewport_offset, |
| host_impl_->active_tree()->TotalScrollOffset()); |
| |
| // Scroll the viewports to max scroll offset. |
| outer_scroll->SetScrollDelta(gfx::Vector2dF(0, 200.f)); |
| inner_scroll->SetScrollDelta(gfx::Vector2dF(100, 100.f)); |
| EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(), |
| host_impl_->active_tree()->TotalScrollOffset()); |
| } |
| |
| // Test that the top controls coming in and out maintains the same aspect ratio |
| // between the inner and outer viewports. |
| TEST_F(LayerTreeHostImplTopControlsTest, TopControlsAspectRatio) { |
| SetupTopControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); |
| DrawFrame(); |
| |
| EXPECT_EQ(top_controls_height_, |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| |
| gfx::Vector2dF scroll_delta(0.f, 25.f); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| EXPECT_EQ(scroll_delta.y(), |
| top_controls_height_ - |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| |
| // Top controls were hidden by 25px so the inner viewport should have expanded |
| // by that much. |
| LayerImpl* outer_container = |
| host_impl_->active_tree()->OuterViewportContainerLayer(); |
| LayerImpl* inner_container = |
| host_impl_->active_tree()->InnerViewportContainerLayer(); |
| EXPECT_EQ(gfx::Size(100, 100+25), inner_container->BoundsForScrolling()); |
| |
| // Outer viewport should match inner's aspect ratio. The bounds are ceiled. |
| float aspect_ratio = inner_container->BoundsForScrolling().width() / |
| inner_container->BoundsForScrolling().height(); |
| gfx::Size expected = gfx::ToCeiledSize(gfx::SizeF(200, 200 / aspect_ratio)); |
| EXPECT_EQ(expected, outer_container->BoundsForScrolling()); |
| EXPECT_EQ(expected, |
| host_impl_->InnerViewportScrollLayer()->BoundsForScrolling()); |
| } |
| |
| // Test that scrolling the outer viewport affects the top controls. |
| TEST_F(LayerTreeHostImplTopControlsTest, TopControlsScrollOuterViewport) { |
| SetupTopControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); |
| DrawFrame(); |
| |
| EXPECT_EQ(top_controls_height_, |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| |
| // Send a gesture scroll that will scroll the outer viewport, make sure the |
| // top controls get scrolled. |
| gfx::Vector2dF scroll_delta(0.f, 15.f); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| EXPECT_EQ(host_impl_->OuterViewportScrollLayer(), |
| host_impl_->CurrentlyScrollingLayer()); |
| host_impl_->ScrollEnd(); |
| |
| EXPECT_EQ(scroll_delta.y(), |
| top_controls_height_ - |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| |
| scroll_delta = gfx::Vector2dF(0.f, 50.f); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| |
| EXPECT_EQ(0, host_impl_->active_tree()->total_top_controls_content_offset()); |
| EXPECT_EQ(host_impl_->OuterViewportScrollLayer(), |
| host_impl_->CurrentlyScrollingLayer()); |
| |
| host_impl_->ScrollEnd(); |
| |
| // Position the viewports such that the inner viewport will be scrolled. |
| gfx::Vector2dF inner_viewport_offset(0.f, 25.f); |
| host_impl_->OuterViewportScrollLayer()->SetScrollDelta(gfx::Vector2dF()); |
| host_impl_->InnerViewportScrollLayer()->SetScrollDelta(inner_viewport_offset); |
| |
| scroll_delta = gfx::Vector2dF(0.f, -65.f); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| |
| EXPECT_EQ(top_controls_height_, |
| host_impl_->active_tree()->total_top_controls_content_offset()); |
| EXPECT_EQ( |
| inner_viewport_offset.y() + (scroll_delta.y() + top_controls_height_), |
| host_impl_->InnerViewportScrollLayer()->ScrollDelta().y()); |
| |
| host_impl_->ScrollEnd(); |
| } |
| |
| TEST_F(LayerTreeHostImplTopControlsTest, |
| ScrollNonScrollableRootWithTopControls) { |
| CreateHostImpl(settings_, CreateOutputSurface()); |
| SetupTopControlsAndScrollLayer(); |
| DrawFrame(); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| |
| host_impl_->top_controls_manager()->ScrollBegin(); |
| host_impl_->top_controls_manager()->ScrollBy(gfx::Vector2dF(0.f, 50.f)); |
| host_impl_->top_controls_manager()->ScrollEnd(); |
| EXPECT_EQ(0.f, host_impl_->top_controls_manager()->ContentTopOffset()); |
| // Now that top controls have moved, expect the clip to resize. |
| LayerImpl* root_clip_ptr = host_impl_->active_tree()->root_layer(); |
| EXPECT_EQ(viewport_size_, root_clip_ptr->bounds()); |
| |
| host_impl_->ScrollEnd(); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| |
| float scroll_increment_y = -25.f; |
| host_impl_->top_controls_manager()->ScrollBegin(); |
| host_impl_->top_controls_manager()->ScrollBy( |
| gfx::Vector2dF(0.f, scroll_increment_y)); |
| EXPECT_EQ(-scroll_increment_y, |
| host_impl_->top_controls_manager()->ContentTopOffset()); |
| // Now that top controls have moved, expect the clip to resize. |
| EXPECT_EQ(gfx::Size(viewport_size_.width(), |
| viewport_size_.height() + scroll_increment_y), |
| root_clip_ptr->bounds()); |
| |
| host_impl_->top_controls_manager()->ScrollBy( |
| gfx::Vector2dF(0.f, scroll_increment_y)); |
| host_impl_->top_controls_manager()->ScrollEnd(); |
| EXPECT_EQ(-2 * scroll_increment_y, |
| host_impl_->top_controls_manager()->ContentTopOffset()); |
| // Now that top controls have moved, expect the clip to resize. |
| EXPECT_EQ(clip_size_, root_clip_ptr->bounds()); |
| |
| host_impl_->ScrollEnd(); |
| |
| // Verify the layer is once-again non-scrollable. |
| EXPECT_EQ( |
| gfx::ScrollOffset(), |
| host_impl_->active_tree()->InnerViewportScrollLayer()->MaxScrollOffset()); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollNonCompositedRoot) { |
| // Test the configuration where a non-composited root layer is embedded in a |
| // scrollable outer layer. |
| gfx::Size surface_size(10, 10); |
| gfx::Size contents_size(20, 20); |
| |
| scoped_ptr<LayerImpl> content_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 1); |
| content_layer->SetDrawsContent(true); |
| content_layer->SetPosition(gfx::PointF()); |
| content_layer->SetBounds(contents_size); |
| content_layer->SetContentBounds(contents_size); |
| content_layer->SetContentsScale(2.f, 2.f); |
| |
| scoped_ptr<LayerImpl> scroll_clip_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| scroll_clip_layer->SetBounds(surface_size); |
| |
| scoped_ptr<LayerImpl> scroll_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 2); |
| scroll_layer->SetScrollClipLayer(3); |
| scroll_layer->SetBounds(contents_size); |
| scroll_layer->SetContentBounds(contents_size); |
| scroll_layer->SetPosition(gfx::PointF()); |
| scroll_layer->AddChild(content_layer.Pass()); |
| scroll_clip_layer->AddChild(scroll_layer.Pass()); |
| |
| scroll_clip_layer->SetHasRenderSurface(true); |
| host_impl_->active_tree()->SetRootLayer(scroll_clip_layer.Pass()); |
| host_impl_->SetViewportSize(surface_size); |
| DrawFrame(); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10)); |
| host_impl_->ScrollEnd(); |
| EXPECT_TRUE(did_request_redraw_); |
| EXPECT_TRUE(did_request_commit_); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollChildCallsCommitAndRedraw) { |
| gfx::Size surface_size(10, 10); |
| gfx::Size contents_size(20, 20); |
| scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1); |
| root->SetBounds(surface_size); |
| root->SetContentBounds(contents_size); |
| root->AddChild(CreateScrollableLayer(2, contents_size, root.get())); |
| root->SetHasRenderSurface(true); |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| host_impl_->SetViewportSize(surface_size); |
| DrawFrame(); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10)); |
| host_impl_->ScrollEnd(); |
| EXPECT_TRUE(did_request_redraw_); |
| EXPECT_TRUE(did_request_commit_); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollMissesChild) { |
| gfx::Size surface_size(10, 10); |
| scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1); |
| root->AddChild(CreateScrollableLayer(2, surface_size, root.get())); |
| root->SetHasRenderSurface(true); |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| host_impl_->SetViewportSize(surface_size); |
| DrawFrame(); |
| |
| // Scroll event is ignored because the input coordinate is outside the layer |
| // boundaries. |
| EXPECT_EQ(InputHandler::ScrollIgnored, |
| host_impl_->ScrollBegin(gfx::Point(15, 5), |
| InputHandler::Wheel)); |
| EXPECT_FALSE(did_request_redraw_); |
| EXPECT_FALSE(did_request_commit_); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollMissesBackfacingChild) { |
| gfx::Size surface_size(10, 10); |
| scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1); |
| root->SetHasRenderSurface(true); |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(2, surface_size, root.get()); |
| host_impl_->SetViewportSize(surface_size); |
| |
| gfx::Transform matrix; |
| matrix.RotateAboutXAxis(180.0); |
| child->SetTransform(matrix); |
| child->SetDoubleSided(false); |
| |
| root->AddChild(child.Pass()); |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| DrawFrame(); |
| |
| // Scroll event is ignored because the scrollable layer is not facing the |
| // viewer and there is nothing scrollable behind it. |
| EXPECT_EQ(InputHandler::ScrollIgnored, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::Wheel)); |
| EXPECT_FALSE(did_request_redraw_); |
| EXPECT_FALSE(did_request_commit_); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollBlockedByContentLayer) { |
| gfx::Size surface_size(10, 10); |
| scoped_ptr<LayerImpl> clip_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| scoped_ptr<LayerImpl> content_layer = |
| CreateScrollableLayer(1, surface_size, clip_layer.get()); |
| content_layer->SetShouldScrollOnMainThread(true); |
| content_layer->SetScrollClipLayer(Layer::INVALID_ID); |
| |
| // Note: we can use the same clip layer for both since both calls to |
| // CreateScrollableLayer() use the same surface size. |
| scoped_ptr<LayerImpl> scroll_layer = |
| CreateScrollableLayer(2, surface_size, clip_layer.get()); |
| scroll_layer->AddChild(content_layer.Pass()); |
| clip_layer->AddChild(scroll_layer.Pass()); |
| clip_layer->SetHasRenderSurface(true); |
| |
| host_impl_->active_tree()->SetRootLayer(clip_layer.Pass()); |
| host_impl_->SetViewportSize(surface_size); |
| DrawFrame(); |
| |
| // Scrolling fails because the content layer is asking to be scrolled on the |
| // main thread. |
| EXPECT_EQ(InputHandler::ScrollOnMainThread, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::Wheel)); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnMainThread) { |
| gfx::Size surface_size(20, 20); |
| gfx::Size viewport_size(10, 10); |
| float page_scale = 2.f; |
| 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> root_scrolling = |
| CreateScrollableLayer(3, surface_size, root_clip.get()); |
| EXPECT_EQ(viewport_size, root_clip->bounds()); |
| root_scrolling->SetIsContainerForFixedPositionLayers(true); |
| root_clip->AddChild(root_scrolling.Pass()); |
| root->AddChild(root_clip.Pass()); |
| root->SetHasRenderSurface(true); |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| // The behaviour in this test assumes the page scale is applied at a layer |
| // above the clip layer. |
| host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 1, 3, |
| Layer::INVALID_ID); |
| host_impl_->active_tree()->DidBecomeActive(); |
| host_impl_->SetViewportSize(viewport_size); |
| DrawFrame(); |
| |
| LayerImpl* root_scroll = |
| host_impl_->active_tree()->InnerViewportScrollLayer(); |
| EXPECT_EQ(viewport_size, root_scroll->scroll_clip_layer()->bounds()); |
| |
| gfx::Vector2d scroll_delta(0, 10); |
| gfx::Vector2d expected_scroll_delta = scroll_delta; |
| gfx::ScrollOffset expected_max_scroll = root_scroll->MaxScrollOffset(); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| // Set new page scale from main thread. |
| host_impl_->active_tree()->PushPageScaleFromMainThread(page_scale, page_scale, |
| page_scale); |
| |
| scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas(); |
| ExpectContains(*scroll_info.get(), root_scroll->id(), expected_scroll_delta); |
| |
| // The scroll range should also have been updated. |
| EXPECT_EQ(expected_max_scroll, root_scroll->MaxScrollOffset()); |
| |
| // The page scale delta remains constant because the impl thread did not |
| // scale. |
| EXPECT_EQ(1.f, host_impl_->active_tree()->page_scale_delta()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnImplThread) { |
| gfx::Size surface_size(20, 20); |
| gfx::Size viewport_size(10, 10); |
| float page_scale = 2.f; |
| 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> root_scrolling = |
| CreateScrollableLayer(3, surface_size, root_clip.get()); |
| EXPECT_EQ(viewport_size, root_clip->bounds()); |
| root_scrolling->SetIsContainerForFixedPositionLayers(true); |
| root_clip->AddChild(root_scrolling.Pass()); |
| root->AddChild(root_clip.Pass()); |
| root->SetHasRenderSurface(true); |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| // The behaviour in this test assumes the page scale is applied at a layer |
| // above the clip layer. |
| host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 1, 3, |
| Layer::INVALID_ID); |
| host_impl_->active_tree()->DidBecomeActive(); |
| host_impl_->SetViewportSize(viewport_size); |
| host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, page_scale); |
| DrawFrame(); |
| |
| LayerImpl* root_scroll = |
| host_impl_->active_tree()->InnerViewportScrollLayer(); |
| EXPECT_EQ(viewport_size, root_scroll->scroll_clip_layer()->bounds()); |
| |
| gfx::Vector2d scroll_delta(0, 10); |
| gfx::Vector2d expected_scroll_delta = scroll_delta; |
| gfx::ScrollOffset expected_max_scroll = root_scroll->MaxScrollOffset(); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| // Set new page scale on impl thread by pinching. |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture); |
| host_impl_->PinchGestureBegin(); |
| host_impl_->PinchGestureUpdate(page_scale, gfx::Point()); |
| host_impl_->PinchGestureEnd(); |
| host_impl_->ScrollEnd(); |
| DrawOneFrame(); |
| |
| // The scroll delta is not scaled because the main thread did not scale. |
| scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas(); |
| ExpectContains(*scroll_info.get(), root_scroll->id(), expected_scroll_delta); |
| |
| // The scroll range should also have been updated. |
| EXPECT_EQ(expected_max_scroll, root_scroll->MaxScrollOffset()); |
| |
| // The page scale delta should match the new scale on the impl side. |
| EXPECT_EQ(page_scale, host_impl_->active_tree()->current_page_scale_factor()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, PageScaleDeltaAppliedToRootScrollLayerOnly) { |
| gfx::Size surface_size(10, 10); |
| float default_page_scale = 1.f; |
| gfx::Transform default_page_scale_matrix; |
| default_page_scale_matrix.Scale(default_page_scale, default_page_scale); |
| |
| float new_page_scale = 2.f; |
| gfx::Transform new_page_scale_matrix; |
| new_page_scale_matrix.Scale(new_page_scale, new_page_scale); |
| |
| // Create a normal scrollable root layer and another scrollable child layer. |
| LayerImpl* scroll = SetupScrollAndContentsLayers(surface_size); |
| LayerImpl* root = host_impl_->active_tree()->root_layer(); |
| LayerImpl* child = scroll->children()[0]; |
| |
| scoped_ptr<LayerImpl> scrollable_child_clip = |
| LayerImpl::Create(host_impl_->active_tree(), 6); |
| scoped_ptr<LayerImpl> scrollable_child = |
| CreateScrollableLayer(7, surface_size, scrollable_child_clip.get()); |
| scrollable_child_clip->AddChild(scrollable_child.Pass()); |
| child->AddChild(scrollable_child_clip.Pass()); |
| LayerImpl* grand_child = child->children()[0]; |
| |
| // Set new page scale on impl thread by pinching. |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture); |
| host_impl_->PinchGestureBegin(); |
| host_impl_->PinchGestureUpdate(new_page_scale, gfx::Point()); |
| host_impl_->PinchGestureEnd(); |
| host_impl_->ScrollEnd(); |
| DrawOneFrame(); |
| |
| EXPECT_EQ(1.f, root->contents_scale_x()); |
| EXPECT_EQ(1.f, root->contents_scale_y()); |
| EXPECT_EQ(1.f, scroll->contents_scale_x()); |
| EXPECT_EQ(1.f, scroll->contents_scale_y()); |
| EXPECT_EQ(1.f, child->contents_scale_x()); |
| EXPECT_EQ(1.f, child->contents_scale_y()); |
| EXPECT_EQ(1.f, grand_child->contents_scale_x()); |
| EXPECT_EQ(1.f, grand_child->contents_scale_y()); |
| |
| // Make sure all the layers are drawn with the page scale delta applied, i.e., |
| // the page scale delta on the root layer is applied hierarchically. |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| EXPECT_EQ(1.f, root->draw_transform().matrix().getDouble(0, 0)); |
| EXPECT_EQ(1.f, root->draw_transform().matrix().getDouble(1, 1)); |
| EXPECT_EQ(new_page_scale, scroll->draw_transform().matrix().getDouble(0, 0)); |
| EXPECT_EQ(new_page_scale, scroll->draw_transform().matrix().getDouble(1, 1)); |
| EXPECT_EQ(new_page_scale, child->draw_transform().matrix().getDouble(0, 0)); |
| EXPECT_EQ(new_page_scale, child->draw_transform().matrix().getDouble(1, 1)); |
| EXPECT_EQ(new_page_scale, |
| grand_child->draw_transform().matrix().getDouble(0, 0)); |
| EXPECT_EQ(new_page_scale, |
| grand_child->draw_transform().matrix().getDouble(1, 1)); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollChildAndChangePageScaleOnMainThread) { |
| gfx::Size surface_size(30, 30); |
| scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1); |
| root->SetBounds(gfx::Size(5, 5)); |
| root->SetHasRenderSurface(true); |
| scoped_ptr<LayerImpl> root_scrolling = |
| LayerImpl::Create(host_impl_->active_tree(), 2); |
| root_scrolling->SetBounds(surface_size); |
| root_scrolling->SetContentBounds(surface_size); |
| root_scrolling->SetScrollClipLayer(root->id()); |
| root_scrolling->SetIsContainerForFixedPositionLayers(true); |
| LayerImpl* root_scrolling_ptr = root_scrolling.get(); |
| root->AddChild(root_scrolling.Pass()); |
| int child_scroll_layer_id = 3; |
| scoped_ptr<LayerImpl> child_scrolling = CreateScrollableLayer( |
| child_scroll_layer_id, surface_size, root_scrolling_ptr); |
| LayerImpl* child = child_scrolling.get(); |
| root_scrolling_ptr->AddChild(child_scrolling.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(); |
| host_impl_->SetViewportSize(surface_size); |
| DrawFrame(); |
| |
| gfx::Vector2d scroll_delta(0, 10); |
| gfx::Vector2d expected_scroll_delta(scroll_delta); |
| gfx::ScrollOffset expected_max_scroll(child->MaxScrollOffset()); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| float page_scale = 2.f; |
| host_impl_->active_tree()->PushPageScaleFromMainThread(page_scale, 1.f, |
| page_scale); |
| |
| DrawOneFrame(); |
| |
| scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas(); |
| ExpectContains( |
| *scroll_info.get(), child_scroll_layer_id, expected_scroll_delta); |
| |
| // The scroll range should not have changed. |
| EXPECT_EQ(child->MaxScrollOffset(), expected_max_scroll); |
| |
| // The page scale delta remains constant because the impl thread did not |
| // scale. |
| EXPECT_EQ(1.f, host_impl_->active_tree()->page_scale_delta()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollChildBeyondLimit) { |
| // Scroll a child layer beyond its maximum scroll range and make sure the |
| // parent layer is scrolled on the axis on which the child was unable to |
| // scroll. |
| gfx::Size surface_size(10, 10); |
| gfx::Size content_size(20, 20); |
| scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1); |
| root->SetBounds(surface_size); |
| root->SetHasRenderSurface(true); |
| scoped_ptr<LayerImpl> grand_child = |
| CreateScrollableLayer(3, content_size, root.get()); |
| |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(2, content_size, root.get()); |
| LayerImpl* grand_child_layer = grand_child.get(); |
| child->AddChild(grand_child.Pass()); |
| |
| LayerImpl* child_layer = child.get(); |
| root->AddChild(child.Pass()); |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| host_impl_->active_tree()->DidBecomeActive(); |
| host_impl_->SetViewportSize(surface_size); |
| grand_child_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(0, 5)); |
| child_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(3, 0)); |
| |
| DrawFrame(); |
| { |
| gfx::Vector2d scroll_delta(-8, -7); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), |
| InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| scoped_ptr<ScrollAndScaleSet> scroll_info = |
| host_impl_->ProcessScrollDeltas(); |
| |
| // The grand child should have scrolled up to its limit. |
| LayerImpl* child = host_impl_->active_tree()->root_layer()->children()[0]; |
| LayerImpl* grand_child = child->children()[0]; |
| ExpectContains(*scroll_info.get(), grand_child->id(), gfx::Vector2d(0, -5)); |
| |
| // The child should have only scrolled on the other axis. |
| ExpectContains(*scroll_info.get(), child->id(), gfx::Vector2d(-3, 0)); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollWithoutBubbling) { |
| // Scroll a child layer beyond its maximum scroll range and make sure the |
| // the scroll doesn't bubble up to the parent layer. |
| gfx::Size surface_size(20, 20); |
| gfx::Size viewport_size(10, 10); |
| scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1); |
| root->SetHasRenderSurface(true); |
| scoped_ptr<LayerImpl> root_scrolling = |
| CreateScrollableLayer(2, surface_size, root.get()); |
| root_scrolling->SetIsContainerForFixedPositionLayers(true); |
| |
| scoped_ptr<LayerImpl> grand_child = |
| CreateScrollableLayer(4, surface_size, root.get()); |
| |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(3, surface_size, root.get()); |
| LayerImpl* grand_child_layer = grand_child.get(); |
| child->AddChild(grand_child.Pass()); |
| |
| LayerImpl* child_layer = child.get(); |
| root_scrolling->AddChild(child.Pass()); |
| root->AddChild(root_scrolling.Pass()); |
| EXPECT_EQ(viewport_size, root->bounds()); |
| 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(); |
| host_impl_->SetViewportSize(viewport_size); |
| |
| grand_child_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(0, 2)); |
| child_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(0, 3)); |
| |
| DrawFrame(); |
| { |
| gfx::Vector2d scroll_delta(0, -10); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), |
| InputHandler::NonBubblingGesture)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| scoped_ptr<ScrollAndScaleSet> scroll_info = |
| host_impl_->ProcessScrollDeltas(); |
| |
| // The grand child should have scrolled up to its limit. |
| LayerImpl* child = |
| host_impl_->active_tree()->root_layer()->children()[0]->children()[0]; |
| LayerImpl* grand_child = child->children()[0]; |
| ExpectContains(*scroll_info.get(), grand_child->id(), gfx::Vector2d(0, -2)); |
| |
| // The child should not have scrolled. |
| ExpectNone(*scroll_info.get(), child->id()); |
| |
| // The next time we scroll we should only scroll the parent. |
| scroll_delta = gfx::Vector2d(0, -3); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::NonBubblingGesture)); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child); |
| host_impl_->ScrollEnd(); |
| |
| scroll_info = host_impl_->ProcessScrollDeltas(); |
| |
| // The child should have scrolled up to its limit. |
| ExpectContains(*scroll_info.get(), child->id(), gfx::Vector2d(0, -3)); |
| |
| // The grand child should not have scrolled. |
| ExpectContains(*scroll_info.get(), grand_child->id(), gfx::Vector2d(0, -2)); |
| |
| // After scrolling the parent, another scroll on the opposite direction |
| // should still scroll the child. |
| scroll_delta = gfx::Vector2d(0, 7); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::NonBubblingGesture)); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child); |
| host_impl_->ScrollEnd(); |
| |
| scroll_info = host_impl_->ProcessScrollDeltas(); |
| |
| // The grand child should have scrolled. |
| ExpectContains(*scroll_info.get(), grand_child->id(), gfx::Vector2d(0, 5)); |
| |
| // The child should not have scrolled. |
| ExpectContains(*scroll_info.get(), child->id(), gfx::Vector2d(0, -3)); |
| |
| |
| // Scrolling should be adjusted from viewport space. |
| host_impl_->active_tree()->PushPageScaleFromMainThread(2.f, 2.f, 2.f); |
| host_impl_->SetPageScaleOnActiveTree(2.f); |
| |
| scroll_delta = gfx::Vector2d(0, -2); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(1, 1), |
| InputHandler::NonBubblingGesture)); |
| EXPECT_EQ(grand_child, host_impl_->CurrentlyScrollingLayer()); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| scroll_info = host_impl_->ProcessScrollDeltas(); |
| |
| // Should have scrolled by half the amount in layer space (5 - 2/2) |
| ExpectContains(*scroll_info.get(), grand_child->id(), gfx::Vector2d(0, 4)); |
| } |
| } |
| TEST_F(LayerTreeHostImplTest, ScrollEventBubbling) { |
| // When we try to scroll a non-scrollable child layer, the scroll delta |
| // should be applied to one of its ancestors if possible. |
| gfx::Size surface_size(10, 10); |
| gfx::Size content_size(20, 20); |
| scoped_ptr<LayerImpl> root_clip = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| root_clip->SetHasRenderSurface(true); |
| scoped_ptr<LayerImpl> root = |
| CreateScrollableLayer(1, content_size, root_clip.get()); |
| // Make 'root' the clip layer for child: since they have the same sizes the |
| // child will have zero max_scroll_offset and scrolls will bubble. |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(2, content_size, root.get()); |
| child->SetIsContainerForFixedPositionLayers(true); |
| root->SetBounds(content_size); |
| |
| int root_scroll_id = root->id(); |
| root->AddChild(child.Pass()); |
| root_clip->AddChild(root.Pass()); |
| |
| host_impl_->SetViewportSize(surface_size); |
| host_impl_->active_tree()->SetRootLayer(root_clip.Pass()); |
| host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 3, 2, |
| Layer::INVALID_ID); |
| host_impl_->active_tree()->DidBecomeActive(); |
| DrawFrame(); |
| { |
| gfx::Vector2d scroll_delta(0, 4); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| 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(); |
| |
| // Only the root scroll should have scrolled. |
| ASSERT_EQ(scroll_info->scrolls.size(), 1u); |
| ExpectContains(*scroll_info.get(), root_scroll_id, scroll_delta); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollBeforeRedraw) { |
| gfx::Size surface_size(10, 10); |
| scoped_ptr<LayerImpl> root_clip = |
| LayerImpl::Create(host_impl_->active_tree(), 1); |
| scoped_ptr<LayerImpl> root_scroll = |
| CreateScrollableLayer(2, surface_size, root_clip.get()); |
| root_scroll->SetIsContainerForFixedPositionLayers(true); |
| root_clip->SetHasRenderSurface(true); |
| root_clip->AddChild(root_scroll.Pass()); |
| host_impl_->active_tree()->SetRootLayer(root_clip.Pass()); |
| host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 1, 2, |
| Layer::INVALID_ID); |
| host_impl_->active_tree()->DidBecomeActive(); |
| host_impl_->SetViewportSize(surface_size); |
| |
| // Draw one frame and then immediately rebuild the layer tree to mimic a tree |
| // synchronization. |
| DrawFrame(); |
| host_impl_->active_tree()->DetachLayerTree(); |
| scoped_ptr<LayerImpl> root_clip2 = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| scoped_ptr<LayerImpl> root_scroll2 = |
| CreateScrollableLayer(4, surface_size, root_clip2.get()); |
| root_scroll2->SetIsContainerForFixedPositionLayers(true); |
| root_clip2->AddChild(root_scroll2.Pass()); |
| root_clip2->SetHasRenderSurface(true); |
| host_impl_->active_tree()->SetRootLayer(root_clip2.Pass()); |
| host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 3, 4, |
| Layer::INVALID_ID); |
| host_impl_->active_tree()->DidBecomeActive(); |
| |
| // Scrolling should still work even though we did not draw yet. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::Wheel)); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollAxisAlignedRotatedLayer) { |
| LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| |
| // Rotate the root layer 90 degrees counter-clockwise about its center. |
| gfx::Transform rotate_transform; |
| rotate_transform.Rotate(-90.0); |
| host_impl_->active_tree()->root_layer()->SetTransform(rotate_transform); |
| |
| gfx::Size surface_size(50, 50); |
| host_impl_->SetViewportSize(surface_size); |
| DrawFrame(); |
| |
| // Scroll to the right in screen coordinates with a gesture. |
| gfx::Vector2d gesture_scroll_delta(10, 0); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), |
| InputHandler::Gesture)); |
| host_impl_->ScrollBy(gfx::Point(), gesture_scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| // The layer should have scrolled down in its local coordinates. |
| scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas(); |
| ExpectContains(*scroll_info.get(), |
| scroll_layer->id(), |
| gfx::Vector2d(0, gesture_scroll_delta.x())); |
| |
| // Reset and scroll down with the wheel. |
| scroll_layer->SetScrollDelta(gfx::Vector2dF()); |
| gfx::Vector2d wheel_scroll_delta(0, 10); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), |
| InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), wheel_scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| // The layer should have scrolled down in its local coordinates. |
| scroll_info = host_impl_->ProcessScrollDeltas(); |
| ExpectContains(*scroll_info.get(), |
| scroll_layer->id(), |
| wheel_scroll_delta); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollNonAxisAlignedRotatedLayer) { |
| LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| int child_clip_layer_id = 6; |
| int child_layer_id = 7; |
| float child_layer_angle = -20.f; |
| |
| // Create a child layer that is rotated to a non-axis-aligned angle. |
| scoped_ptr<LayerImpl> clip_layer = |
| LayerImpl::Create(host_impl_->active_tree(), child_clip_layer_id); |
| scoped_ptr<LayerImpl> child = CreateScrollableLayer( |
| child_layer_id, scroll_layer->content_bounds(), clip_layer.get()); |
| gfx::Transform rotate_transform; |
| rotate_transform.Translate(-50.0, -50.0); |
| rotate_transform.Rotate(child_layer_angle); |
| rotate_transform.Translate(50.0, 50.0); |
| clip_layer->SetTransform(rotate_transform); |
| |
| // Only allow vertical scrolling. |
| clip_layer->SetBounds( |
| gfx::Size(child->bounds().width(), child->bounds().height() / 2)); |
| // The rotation depends on the layer's transform origin, and the child layer |
| // is a different size than the clip, so make sure the clip layer's origin |
| // lines up over the child. |
| clip_layer->SetTransformOrigin(gfx::Point3F( |
| clip_layer->bounds().width() * 0.5f, clip_layer->bounds().height(), 0.f)); |
| LayerImpl* child_ptr = child.get(); |
| clip_layer->AddChild(child.Pass()); |
| scroll_layer->AddChild(clip_layer.Pass()); |
| |
| gfx::Size surface_size(50, 50); |
| host_impl_->SetViewportSize(surface_size); |
| DrawFrame(); |
| { |
| // Scroll down in screen coordinates with a gesture. |
| gfx::Vector2d gesture_scroll_delta(0, 10); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(1, 1), |
| InputHandler::Gesture)); |
| host_impl_->ScrollBy(gfx::Point(), gesture_scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| // The child layer should have scrolled down in its local coordinates an |
| // amount proportional to the angle between it and the input scroll delta. |
| gfx::Vector2d expected_scroll_delta( |
| 0, |
| gesture_scroll_delta.y() * |
| std::cos(MathUtil::Deg2Rad(child_layer_angle))); |
| scoped_ptr<ScrollAndScaleSet> scroll_info = |
| host_impl_->ProcessScrollDeltas(); |
| ExpectContains(*scroll_info.get(), child_layer_id, expected_scroll_delta); |
| |
| // The root scroll layer should not have scrolled, because the input delta |
| // was close to the layer's axis of movement. |
| EXPECT_EQ(scroll_info->scrolls.size(), 1u); |
| } |
| { |
| // Now reset and scroll the same amount horizontally. |
| child_ptr->SetScrollDelta(gfx::Vector2dF()); |
| gfx::Vector2d gesture_scroll_delta(10, 0); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(1, 1), |
| InputHandler::Gesture)); |
| host_impl_->ScrollBy(gfx::Point(), gesture_scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| // The child layer should have scrolled down in its local coordinates an |
| // amount proportional to the angle between it and the input scroll delta. |
| gfx::Vector2d expected_scroll_delta( |
| 0, |
| -gesture_scroll_delta.x() * |
| std::sin(MathUtil::Deg2Rad(child_layer_angle))); |
| scoped_ptr<ScrollAndScaleSet> scroll_info = |
| host_impl_->ProcessScrollDeltas(); |
| ExpectContains(*scroll_info.get(), child_layer_id, expected_scroll_delta); |
| |
| // The root scroll layer should have scrolled more, since the input scroll |
| // delta was mostly orthogonal to the child layer's vertical scroll axis. |
| gfx::Vector2d expected_root_scroll_delta( |
| gesture_scroll_delta.x() * |
| std::pow(std::cos(MathUtil::Deg2Rad(child_layer_angle)), 2), |
| 0); |
| ExpectContains(*scroll_info.get(), |
| scroll_layer->id(), |
| expected_root_scroll_delta); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollScaledLayer) { |
| LayerImpl* scroll_layer = |
| SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| |
| // Scale the layer to twice its normal size. |
| int scale = 2; |
| gfx::Transform scale_transform; |
| scale_transform.Scale(scale, scale); |
| scroll_layer->SetTransform(scale_transform); |
| |
| gfx::Size surface_size(50, 50); |
| host_impl_->SetViewportSize(surface_size); |
| DrawFrame(); |
| |
| // Scroll down in screen coordinates with a gesture. |
| gfx::Vector2d scroll_delta(0, 10); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| // The layer should have scrolled down in its local coordinates, but half the |
| // amount. |
| scoped_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas(); |
| ExpectContains(*scroll_info.get(), |
| scroll_layer->id(), |
| gfx::Vector2d(0, scroll_delta.y() / scale)); |
| |
| // Reset and scroll down with the wheel. |
| scroll_layer->SetScrollDelta(gfx::Vector2dF()); |
| gfx::Vector2d wheel_scroll_delta(0, 10); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), wheel_scroll_delta); |
| host_impl_->ScrollEnd(); |
| |
| // It should apply the scale factor to the scroll delta for the wheel event. |
| scroll_info = host_impl_->ProcessScrollDeltas(); |
| ExpectContains(*scroll_info.get(), |
| scroll_layer->id(), |
| wheel_scroll_delta); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollViewportRounding) { |
| int width = 332; |
| int height = 20; |
| int scale = 3; |
| SetupScrollAndContentsLayers(gfx::Size(width, height)); |
| host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds( |
| gfx::Size(width * scale - 1, height * scale)); |
| host_impl_->SetDeviceScaleFactor(scale); |
| host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f); |
| |
| LayerImpl* inner_viewport_scroll_layer = |
| host_impl_->active_tree()->InnerViewportScrollLayer(); |
| EXPECT_EQ(gfx::ScrollOffset(0, 0), |
| inner_viewport_scroll_layer->MaxScrollOffset()); |
| } |
| |
| class TestScrollOffsetDelegate : public LayerScrollOffsetDelegate { |
| public: |
| TestScrollOffsetDelegate() |
| : page_scale_factor_(0.f), |
| min_page_scale_factor_(-1.f), |
| max_page_scale_factor_(-1.f) {} |
| |
| ~TestScrollOffsetDelegate() override {} |
| |
| gfx::ScrollOffset GetTotalScrollOffset() override { |
| return getter_return_value_; |
| } |
| |
| bool IsExternalFlingActive() const override { return false; } |
| |
| void UpdateRootLayerState(const gfx::ScrollOffset& total_scroll_offset, |
| const gfx::ScrollOffset& max_scroll_offset, |
| const gfx::SizeF& scrollable_size, |
| float page_scale_factor, |
| float min_page_scale_factor, |
| float max_page_scale_factor) override { |
| DCHECK(total_scroll_offset.x() <= max_scroll_offset.x()); |
| DCHECK(total_scroll_offset.y() <= max_scroll_offset.y()); |
| last_set_scroll_offset_ = total_scroll_offset; |
| max_scroll_offset_ = max_scroll_offset; |
| scrollable_size_ = scrollable_size; |
| page_scale_factor_ = page_scale_factor; |
| min_page_scale_factor_ = min_page_scale_factor; |
| max_page_scale_factor_ = max_page_scale_factor; |
| |
| set_getter_return_value(last_set_scroll_offset_); |
| } |
| |
| gfx::ScrollOffset last_set_scroll_offset() { |
| return last_set_scroll_offset_; |
| } |
| |
| void set_getter_return_value(const gfx::ScrollOffset& value) { |
| getter_return_value_ = value; |
| } |
| |
| gfx::ScrollOffset max_scroll_offset() const { |
| return max_scroll_offset_; |
| } |
| |
| gfx::SizeF scrollable_size() const { |
| return scrollable_size_; |
| } |
| |
| float page_scale_factor() const { |
| return page_scale_factor_; |
| } |
| |
| float min_page_scale_factor() const { |
| return min_page_scale_factor_; |
| } |
| |
| float max_page_scale_factor() const { |
| return max_page_scale_factor_; |
| } |
| |
| private: |
| gfx::ScrollOffset last_set_scroll_offset_; |
| gfx::ScrollOffset getter_return_value_; |
| gfx::ScrollOffset max_scroll_offset_; |
| gfx::SizeF scrollable_size_; |
| float page_scale_factor_; |
| float min_page_scale_factor_; |
| float max_page_scale_factor_; |
| }; |
| |
| TEST_F(LayerTreeHostImplTest, RootLayerScrollOffsetDelegation) { |
| TestScrollOffsetDelegate scroll_delegate; |
| host_impl_->SetViewportSize(gfx::Size(10, 20)); |
| LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| LayerImpl* clip_layer = scroll_layer->parent()->parent(); |
| clip_layer->SetBounds(gfx::Size(10, 20)); |
| |
| // Setting the delegate results in the current scroll offset being set. |
| gfx::Vector2dF initial_scroll_delta(10.f, 10.f); |
| scroll_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset()); |
| scroll_layer->SetScrollDelta(initial_scroll_delta); |
| host_impl_->SetRootLayerScrollOffsetDelegate(&scroll_delegate); |
| EXPECT_EQ(initial_scroll_delta.ToString(), |
| scroll_delegate.last_set_scroll_offset().ToString()); |
| |
| // Setting the delegate results in the scrollable_size, max_scroll_offset, |
| // page_scale_factor and {min|max}_page_scale_factor being set. |
| EXPECT_EQ(gfx::SizeF(100, 100), scroll_delegate.scrollable_size()); |
| EXPECT_EQ(gfx::ScrollOffset(90, 80), scroll_delegate.max_scroll_offset()); |
| EXPECT_EQ(1.f, scroll_delegate.page_scale_factor()); |
| EXPECT_EQ(0.f, scroll_delegate.min_page_scale_factor()); |
| EXPECT_EQ(0.f, scroll_delegate.max_page_scale_factor()); |
| |
| // Updating page scale immediately updates the delegate. |
| host_impl_->active_tree()->PushPageScaleFromMainThread(2.f, 0.5f, 4.f); |
| EXPECT_EQ(2.f, scroll_delegate.page_scale_factor()); |
| EXPECT_EQ(0.5f, scroll_delegate.min_page_scale_factor()); |
| EXPECT_EQ(4.f, scroll_delegate.max_page_scale_factor()); |
| host_impl_->SetPageScaleOnActiveTree(2.f * 1.5f); |
| EXPECT_EQ(3.f, scroll_delegate.page_scale_factor()); |
| EXPECT_EQ(0.5f, scroll_delegate.min_page_scale_factor()); |
| EXPECT_EQ(4.f, scroll_delegate.max_page_scale_factor()); |
| host_impl_->SetPageScaleOnActiveTree(2.f); |
| host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f); |
| EXPECT_EQ(1.f, scroll_delegate.page_scale_factor()); |
| EXPECT_EQ(0.5f, scroll_delegate.min_page_scale_factor()); |
| EXPECT_EQ(4.f, scroll_delegate.max_page_scale_factor()); |
| |
| // The pinch gesture doesn't put the delegate into a state where the scroll |
| // offset is outside of the scroll range. (this is verified by DCHECKs in the |
| // delegate). |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture); |
| host_impl_->PinchGestureBegin(); |
| host_impl_->PinchGestureUpdate(2.f, gfx::Point()); |
| host_impl_->PinchGestureUpdate(.5f, gfx::Point()); |
| host_impl_->PinchGestureEnd(); |
| host_impl_->ScrollEnd(); |
| |
| // Scrolling should be relative to the offset as returned by the delegate. |
| gfx::Vector2dF scroll_delta(0.f, 10.f); |
| gfx::ScrollOffset current_offset(7.f, 8.f); |
| |
| scroll_delegate.set_getter_return_value(current_offset); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| EXPECT_EQ(ScrollOffsetWithDelta(current_offset, scroll_delta), |
| scroll_delegate.last_set_scroll_offset()); |
| |
| current_offset = gfx::ScrollOffset(42.f, 41.f); |
| scroll_delegate.set_getter_return_value(current_offset); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| EXPECT_EQ(current_offset + gfx::ScrollOffset(scroll_delta), |
| scroll_delegate.last_set_scroll_offset()); |
| host_impl_->ScrollEnd(); |
| scroll_delegate.set_getter_return_value(gfx::ScrollOffset()); |
| |
| // Forces a full tree synchronization and ensures that the scroll delegate |
| // sees the correct size of the new tree. |
| gfx::Size new_size(42, 24); |
| host_impl_->CreatePendingTree(); |
| CreateScrollAndContentsLayers(host_impl_->pending_tree(), new_size); |
| host_impl_->ActivateSyncTree(); |
| EXPECT_EQ(new_size, scroll_delegate.scrollable_size()); |
| |
| // Un-setting the delegate should propagate the delegate's current offset to |
| // the root scrollable layer. |
| current_offset = gfx::ScrollOffset(13.f, 12.f); |
| scroll_delegate.set_getter_return_value(current_offset); |
| host_impl_->SetRootLayerScrollOffsetDelegate(NULL); |
| |
| EXPECT_EQ(current_offset.ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| } |
| |
| void CheckLayerScrollDelta(LayerImpl* layer, gfx::Vector2dF scroll_delta) { |
| const gfx::Transform target_space_transform = |
| layer->draw_properties().target_space_transform; |
| EXPECT_TRUE(target_space_transform.IsScaleOrTranslation()); |
| gfx::Point translated_point; |
| target_space_transform.TransformPoint(&translated_point); |
| gfx::Point expected_point = gfx::Point() - ToRoundedVector2d(scroll_delta); |
| EXPECT_EQ(expected_point.ToString(), translated_point.ToString()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, |
| ExternalRootLayerScrollOffsetDelegationReflectedInNextDraw) { |
| TestScrollOffsetDelegate scroll_delegate; |
| host_impl_->SetViewportSize(gfx::Size(10, 20)); |
| LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| LayerImpl* clip_layer = scroll_layer->parent()->parent(); |
| clip_layer->SetBounds(gfx::Size(10, 20)); |
| host_impl_->SetRootLayerScrollOffsetDelegate(&scroll_delegate); |
| |
| // Draw first frame to clear any pending draws and check scroll. |
| DrawFrame(); |
| CheckLayerScrollDelta(scroll_layer, gfx::Vector2dF(0.f, 0.f)); |
| EXPECT_FALSE(host_impl_->active_tree()->needs_update_draw_properties()); |
| |
| // Set external scroll delta on delegate and notify LayerTreeHost. |
| gfx::ScrollOffset scroll_offset(10.f, 10.f); |
| scroll_delegate.set_getter_return_value(scroll_offset); |
| host_impl_->OnRootLayerDelegatedScrollOffsetChanged(); |
| |
| // Check scroll delta reflected in layer. |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| EXPECT_FALSE(frame.has_no_damage); |
| CheckLayerScrollDelta(scroll_layer, ScrollOffsetToVector2dF(scroll_offset)); |
| |
| host_impl_->SetRootLayerScrollOffsetDelegate(NULL); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, OverscrollRoot) { |
| InputHandlerScrollResult scroll_result; |
| SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| host_impl_->SetViewportSize(gfx::Size(50, 50)); |
| host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f); |
| DrawFrame(); |
| EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll()); |
| |
| // In-bounds scrolling does not affect overscroll. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10)); |
| EXPECT_TRUE(scroll_result.did_scroll); |
| EXPECT_FALSE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll()); |
| |
| // Overscroll events are reflected immediately. |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 50)); |
| EXPECT_TRUE(scroll_result.did_scroll); |
| EXPECT_TRUE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(0, 10), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(0, 10), host_impl_->accumulated_root_overscroll()); |
| EXPECT_EQ(scroll_result.accumulated_root_overscroll, |
| host_impl_->accumulated_root_overscroll()); |
| |
| // In-bounds scrolling resets accumulated overscroll for the scrolled axes. |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -50)); |
| EXPECT_TRUE(scroll_result.did_scroll); |
| EXPECT_FALSE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(0, 0), host_impl_->accumulated_root_overscroll()); |
| EXPECT_EQ(scroll_result.accumulated_root_overscroll, |
| host_impl_->accumulated_root_overscroll()); |
| |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -10)); |
| EXPECT_FALSE(scroll_result.did_scroll); |
| EXPECT_TRUE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(0, -10), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll()); |
| EXPECT_EQ(scroll_result.accumulated_root_overscroll, |
| host_impl_->accumulated_root_overscroll()); |
| |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(10, 0)); |
| EXPECT_TRUE(scroll_result.did_scroll); |
| EXPECT_FALSE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(0, 0), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll()); |
| EXPECT_EQ(scroll_result.accumulated_root_overscroll, |
| host_impl_->accumulated_root_overscroll()); |
| |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(-15, 0)); |
| EXPECT_TRUE(scroll_result.did_scroll); |
| EXPECT_TRUE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(-5, 0), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(-5, -10), host_impl_->accumulated_root_overscroll()); |
| EXPECT_EQ(scroll_result.accumulated_root_overscroll, |
| host_impl_->accumulated_root_overscroll()); |
| |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 60)); |
| EXPECT_TRUE(scroll_result.did_scroll); |
| EXPECT_TRUE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(0, 10), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(-5, 10), host_impl_->accumulated_root_overscroll()); |
| EXPECT_EQ(scroll_result.accumulated_root_overscroll, |
| host_impl_->accumulated_root_overscroll()); |
| |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(10, -60)); |
| EXPECT_TRUE(scroll_result.did_scroll); |
| EXPECT_TRUE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(0, -10), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll()); |
| EXPECT_EQ(scroll_result.accumulated_root_overscroll, |
| host_impl_->accumulated_root_overscroll()); |
| |
| // Overscroll accumulates within the scope of ScrollBegin/ScrollEnd as long |
| // as no scroll occurs. |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -20)); |
| EXPECT_FALSE(scroll_result.did_scroll); |
| EXPECT_TRUE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(0, -20), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(0, -30), host_impl_->accumulated_root_overscroll()); |
| EXPECT_EQ(scroll_result.accumulated_root_overscroll, |
| host_impl_->accumulated_root_overscroll()); |
| |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -20)); |
| EXPECT_FALSE(scroll_result.did_scroll); |
| EXPECT_TRUE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(0, -20), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(0, -50), host_impl_->accumulated_root_overscroll()); |
| EXPECT_EQ(scroll_result.accumulated_root_overscroll, |
| host_impl_->accumulated_root_overscroll()); |
| |
| // Overscroll resets on valid scroll. |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10)); |
| EXPECT_TRUE(scroll_result.did_scroll); |
| EXPECT_FALSE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(0, 0), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(0, 0), host_impl_->accumulated_root_overscroll()); |
| EXPECT_EQ(scroll_result.accumulated_root_overscroll, |
| host_impl_->accumulated_root_overscroll()); |
| |
| scroll_result = host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -20)); |
| EXPECT_TRUE(scroll_result.did_scroll); |
| EXPECT_TRUE(scroll_result.did_overscroll_root); |
| EXPECT_EQ(gfx::Vector2dF(0, -10), scroll_result.unused_scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll()); |
| EXPECT_EQ(scroll_result.accumulated_root_overscroll, |
| host_impl_->accumulated_root_overscroll()); |
| |
| host_impl_->ScrollEnd(); |
| } |
| |
| |
| TEST_F(LayerTreeHostImplTest, OverscrollChildWithoutBubbling) { |
| // Scroll child layers beyond their maximum scroll range and make sure root |
| // overscroll does not accumulate. |
| gfx::Size surface_size(10, 10); |
| scoped_ptr<LayerImpl> root_clip = |
| LayerImpl::Create(host_impl_->active_tree(), 4); |
| root_clip->SetHasRenderSurface(true); |
| |
| scoped_ptr<LayerImpl> root = |
| CreateScrollableLayer(1, surface_size, root_clip.get()); |
| |
| scoped_ptr<LayerImpl> grand_child = |
| CreateScrollableLayer(3, surface_size, root_clip.get()); |
| |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(2, surface_size, root_clip.get()); |
| LayerImpl* grand_child_layer = grand_child.get(); |
| child->AddChild(grand_child.Pass()); |
| |
| LayerImpl* child_layer = child.get(); |
| root->AddChild(child.Pass()); |
| root_clip->AddChild(root.Pass()); |
| child_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(0, 3)); |
| grand_child_layer->PushScrollOffsetFromMainThread(gfx::ScrollOffset(0, 2)); |
| host_impl_->active_tree()->SetRootLayer(root_clip.Pass()); |
| host_impl_->active_tree()->DidBecomeActive(); |
| host_impl_->SetViewportSize(surface_size); |
| DrawFrame(); |
| { |
| gfx::Vector2d scroll_delta(0, -10); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), |
| InputHandler::NonBubblingGesture)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll()); |
| host_impl_->ScrollEnd(); |
| |
| // The next time we scroll we should only scroll the parent, but overscroll |
| // should still not reach the root layer. |
| scroll_delta = gfx::Vector2d(0, -30); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::NonBubblingGesture)); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child_layer); |
| EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll()); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child_layer); |
| EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll()); |
| host_impl_->ScrollEnd(); |
| |
| // After scrolling the parent, another scroll on the opposite direction |
| // should scroll the child. |
| scroll_delta = gfx::Vector2d(0, 70); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::NonBubblingGesture)); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child_layer); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child_layer); |
| EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll()); |
| host_impl_->ScrollEnd(); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, OverscrollChildEventBubbling) { |
| // When we try to scroll a non-scrollable child layer, the scroll delta |
| // should be applied to one of its ancestors if possible. Overscroll should |
| // be reflected only when it has bubbled up to the root scrolling layer. |
| gfx::Size surface_size(10, 10); |
| gfx::Size content_size(20, 20); |
| scoped_ptr<LayerImpl> root_clip = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| root_clip->SetHasRenderSurface(true); |
| |
| scoped_ptr<LayerImpl> root = |
| CreateScrollableLayer(1, content_size, root_clip.get()); |
| root->SetIsContainerForFixedPositionLayers(true); |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(2, content_size, root_clip.get()); |
| |
| child->SetScrollClipLayer(Layer::INVALID_ID); |
| root->AddChild(child.Pass()); |
| root_clip->AddChild(root.Pass()); |
| |
| host_impl_->SetViewportSize(surface_size); |
| host_impl_->active_tree()->SetRootLayer(root_clip.Pass()); |
| host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 3, 1, |
| Layer::INVALID_ID); |
| host_impl_->active_tree()->DidBecomeActive(); |
| DrawFrame(); |
| { |
| gfx::Vector2d scroll_delta(0, 8); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(5, 5), |
| InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll()); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(0, 6), host_impl_->accumulated_root_overscroll()); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(0, 14), host_impl_->accumulated_root_overscroll()); |
| host_impl_->ScrollEnd(); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, OverscrollAlways) { |
| LayerTreeSettings settings; |
| CreateHostImpl(settings, CreateOutputSurface()); |
| |
| LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(50, 50)); |
| LayerImpl* clip_layer = scroll_layer->parent()->parent(); |
| clip_layer->SetBounds(gfx::Size(50, 50)); |
| host_impl_->SetViewportSize(gfx::Size(50, 50)); |
| host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f); |
| DrawFrame(); |
| EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll()); |
| |
| // Even though the layer can't scroll the overscroll still happens. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10)); |
| EXPECT_EQ(gfx::Vector2dF(0, 10), host_impl_->accumulated_root_overscroll()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, NoOverscrollOnFractionalDeviceScale) { |
| gfx::Size surface_size(980, 1439); |
| gfx::Size content_size(980, 1438); |
| float device_scale_factor = 1.5f; |
| scoped_ptr<LayerImpl> root_clip = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| root_clip->SetHasRenderSurface(true); |
| |
| scoped_ptr<LayerImpl> root = |
| CreateScrollableLayer(1, content_size, root_clip.get()); |
| root->SetIsContainerForFixedPositionLayers(true); |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(2, content_size, root_clip.get()); |
| root->scroll_clip_layer()->SetBounds(gfx::Size(320, 469)); |
| host_impl_->active_tree()->PushPageScaleFromMainThread(0.326531f, 0.326531f, |
| 5.f); |
| host_impl_->SetPageScaleOnActiveTree(0.326531f); |
| child->SetScrollClipLayer(Layer::INVALID_ID); |
| root->AddChild(child.Pass()); |
| root_clip->AddChild(root.Pass()); |
| |
| host_impl_->SetViewportSize(surface_size); |
| host_impl_->SetDeviceScaleFactor(device_scale_factor); |
| host_impl_->active_tree()->SetRootLayer(root_clip.Pass()); |
| host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 3, 1, |
| Layer::INVALID_ID); |
| host_impl_->active_tree()->DidBecomeActive(); |
| DrawFrame(); |
| { |
| // Horizontal & Vertical GlowEffect should not be applied when |
| // content size is less then view port size. For Example Horizontal & |
| // vertical GlowEffect should not be applied in about:blank page. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(0, 0), InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0, -1)); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| host_impl_->accumulated_root_overscroll().ToString()); |
| |
| host_impl_->ScrollEnd(); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, NoOverscrollWhenNotAtEdge) { |
| gfx::Size surface_size(100, 100); |
| gfx::Size content_size(200, 200); |
| scoped_ptr<LayerImpl> root_clip = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| root_clip->SetHasRenderSurface(true); |
| |
| scoped_ptr<LayerImpl> root = |
| CreateScrollableLayer(1, content_size, root_clip.get()); |
| root->SetIsContainerForFixedPositionLayers(true); |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(2, content_size, root_clip.get()); |
| |
| child->SetScrollClipLayer(Layer::INVALID_ID); |
| root->AddChild(child.Pass()); |
| root_clip->AddChild(root.Pass()); |
| |
| host_impl_->SetViewportSize(surface_size); |
| host_impl_->active_tree()->SetRootLayer(root_clip.Pass()); |
| host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 3, 1, |
| Layer::INVALID_ID); |
| host_impl_->active_tree()->DidBecomeActive(); |
| DrawFrame(); |
| { |
| // Edge glow effect should be applicable only upon reaching Edges |
| // of the content. unnecessary glow effect calls shouldn't be |
| // called while scrolling up without reaching the edge of the content. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(0, 0), InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0, 100)); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| host_impl_->accumulated_root_overscroll().ToString()); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0, -2.30f)); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| host_impl_->accumulated_root_overscroll().ToString()); |
| host_impl_->ScrollEnd(); |
| // unusedrootDelta should be subtracted from applied delta so that |
| // unwanted glow effect calls are not called. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(0, 0), |
| InputHandler::NonBubblingGesture)); |
| EXPECT_EQ(InputHandler::ScrollStarted, host_impl_->FlingScrollBegin()); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0, 20)); |
| EXPECT_EQ(gfx::Vector2dF(0.000000f, 17.699997f).ToString(), |
| host_impl_->accumulated_root_overscroll().ToString()); |
| |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(0.02f, -0.01f)); |
| EXPECT_EQ(gfx::Vector2dF(0.000000f, 17.699997f).ToString(), |
| host_impl_->accumulated_root_overscroll().ToString()); |
| host_impl_->ScrollEnd(); |
| // TestCase to check kEpsilon, which prevents minute values to trigger |
| // gloweffect without reaching edge. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(0, 0), InputHandler::Wheel)); |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2dF(-0.12f, 0.1f)); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| host_impl_->accumulated_root_overscroll().ToString()); |
| host_impl_->ScrollEnd(); |
| } |
| } |
| |
| class BlendStateCheckLayer : public LayerImpl { |
| public: |
| static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, |
| int id, |
| ResourceProvider* resource_provider) { |
| return make_scoped_ptr( |
| new BlendStateCheckLayer(tree_impl, id, resource_provider)); |
| } |
| |
| void AppendQuads(RenderPass* render_pass, |
| const Occlusion& occlusion_in_content_space, |
| AppendQuadsData* append_quads_data) override { |
| quads_appended_ = true; |
| |
| gfx::Rect opaque_rect; |
| if (contents_opaque()) |
| opaque_rect = quad_rect_; |
| else |
| opaque_rect = opaque_content_rect_; |
| gfx::Rect visible_quad_rect = quad_rect_; |
| |
| SharedQuadState* shared_quad_state = |
| render_pass->CreateAndAppendSharedQuadState(); |
| PopulateSharedQuadState(shared_quad_state); |
| |
| TileDrawQuad* test_blending_draw_quad = |
| render_pass->CreateAndAppendDrawQuad<TileDrawQuad>(); |
| test_blending_draw_quad->SetNew(shared_quad_state, |
| quad_rect_, |
| opaque_rect, |
| visible_quad_rect, |
| resource_id_, |
| gfx::RectF(0.f, 0.f, 1.f, 1.f), |
| gfx::Size(1, 1), |
| false, |
| false); |
| test_blending_draw_quad->visible_rect = quad_visible_rect_; |
| EXPECT_EQ(blend_, test_blending_draw_quad->ShouldDrawWithBlending()); |
| EXPECT_EQ(has_render_surface_, !!render_surface()); |
| } |
| |
| void SetExpectation(bool blend, bool has_render_surface) { |
| blend_ = blend; |
| has_render_surface_ = has_render_surface; |
| quads_appended_ = false; |
| } |
| |
| bool quads_appended() const { return quads_appended_; } |
| |
| void SetQuadRect(const gfx::Rect& rect) { quad_rect_ = rect; } |
| void SetQuadVisibleRect(const gfx::Rect& rect) { quad_visible_rect_ = rect; } |
| void SetOpaqueContentRect(const gfx::Rect& rect) { |
| opaque_content_rect_ = rect; |
| } |
| |
| private: |
| BlendStateCheckLayer(LayerTreeImpl* tree_impl, |
| int id, |
| ResourceProvider* resource_provider) |
| : LayerImpl(tree_impl, id), |
| blend_(false), |
| has_render_surface_(false), |
| quads_appended_(false), |
| quad_rect_(5, 5, 5, 5), |
| quad_visible_rect_(5, 5, 5, 5), |
| resource_id_(resource_provider->CreateResource( |
| gfx::Size(1, 1), |
| GL_CLAMP_TO_EDGE, |
| ResourceProvider::TextureHintImmutable, |
| RGBA_8888)) { |
| resource_provider->AllocateForTesting(resource_id_); |
| SetBounds(gfx::Size(10, 10)); |
| SetContentBounds(gfx::Size(10, 10)); |
| SetDrawsContent(true); |
| } |
| |
| bool blend_; |
| bool has_render_surface_; |
| bool quads_appended_; |
| gfx::Rect quad_rect_; |
| gfx::Rect opaque_content_rect_; |
| gfx::Rect quad_visible_rect_; |
| ResourceProvider::ResourceId resource_id_; |
| }; |
| |
| TEST_F(LayerTreeHostImplTest, BlendingOffWhenDrawingOpaqueLayers) { |
| { |
| scoped_ptr<LayerImpl> root = |
| LayerImpl::Create(host_impl_->active_tree(), 1); |
| root->SetBounds(gfx::Size(10, 10)); |
| root->SetContentBounds(root->bounds()); |
| root->SetDrawsContent(false); |
| root->SetHasRenderSurface(true); |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| } |
| LayerImpl* root = host_impl_->active_tree()->root_layer(); |
| |
| root->AddChild( |
| BlendStateCheckLayer::Create(host_impl_->active_tree(), |
| 2, |
| host_impl_->resource_provider())); |
| BlendStateCheckLayer* layer1 = |
| static_cast<BlendStateCheckLayer*>(root->children()[0]); |
| layer1->SetPosition(gfx::PointF(2.f, 2.f)); |
| |
| LayerTreeHostImpl::FrameData frame; |
| |
| // Opaque layer, drawn without blending. |
| layer1->SetContentsOpaque(true); |
| layer1->SetExpectation(false, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // Layer with translucent content and painting, so drawn with blending. |
| layer1->SetContentsOpaque(false); |
| layer1->SetExpectation(true, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // Layer with translucent opacity, drawn with blending. |
| layer1->SetContentsOpaque(true); |
| layer1->SetOpacity(0.5f); |
| layer1->SetExpectation(true, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // Layer with translucent opacity and painting, drawn with blending. |
| layer1->SetContentsOpaque(true); |
| layer1->SetOpacity(0.5f); |
| layer1->SetExpectation(true, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| layer1->AddChild( |
| BlendStateCheckLayer::Create(host_impl_->active_tree(), |
| 3, |
| host_impl_->resource_provider())); |
| BlendStateCheckLayer* layer2 = |
| static_cast<BlendStateCheckLayer*>(layer1->children()[0]); |
| layer2->SetPosition(gfx::PointF(4.f, 4.f)); |
| |
| // 2 opaque layers, drawn without blending. |
| layer1->SetContentsOpaque(true); |
| layer1->SetOpacity(1.f); |
| layer1->SetExpectation(false, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| layer2->SetContentsOpaque(true); |
| layer2->SetOpacity(1.f); |
| layer2->SetExpectation(false, false); |
| layer2->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| EXPECT_TRUE(layer2->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // Parent layer with translucent content, drawn with blending. |
| // Child layer with opaque content, drawn without blending. |
| layer1->SetContentsOpaque(false); |
| layer1->SetExpectation(true, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| layer2->SetExpectation(false, false); |
| layer2->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| EXPECT_TRUE(layer2->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // Parent layer with translucent content but opaque painting, drawn without |
| // blending. |
| // Child layer with opaque content, drawn without blending. |
| layer1->SetContentsOpaque(true); |
| layer1->SetExpectation(false, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| layer2->SetExpectation(false, false); |
| layer2->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| EXPECT_TRUE(layer2->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // Parent layer with translucent opacity and opaque content. Since it has a |
| // drawing child, it's drawn to a render surface which carries the opacity, |
| // so it's itself drawn without blending. |
| // Child layer with opaque content, drawn without blending (parent surface |
| // carries the inherited opacity). |
| layer1->SetContentsOpaque(true); |
| layer1->SetOpacity(0.5f); |
| layer1->SetHasRenderSurface(true); |
| layer1->SetExpectation(false, true); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| layer2->SetExpectation(false, false); |
| layer2->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| FakeLayerTreeHostImpl::RecursiveUpdateNumChildren( |
| host_impl_->active_tree()->root_layer()); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| EXPECT_TRUE(layer2->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| layer1->SetHasRenderSurface(false); |
| |
| // Draw again, but with child non-opaque, to make sure |
| // layer1 not culled. |
| layer1->SetContentsOpaque(true); |
| layer1->SetOpacity(1.f); |
| layer1->SetExpectation(false, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| layer2->SetContentsOpaque(true); |
| layer2->SetOpacity(0.5f); |
| layer2->SetExpectation(true, false); |
| layer2->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| EXPECT_TRUE(layer2->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // A second way of making the child non-opaque. |
| layer1->SetContentsOpaque(true); |
| layer1->SetOpacity(1.f); |
| layer1->SetExpectation(false, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| layer2->SetContentsOpaque(false); |
| layer2->SetOpacity(1.f); |
| layer2->SetExpectation(true, false); |
| layer2->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| EXPECT_TRUE(layer2->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // And when the layer says its not opaque but is painted opaque, it is not |
| // blended. |
| layer1->SetContentsOpaque(true); |
| layer1->SetOpacity(1.f); |
| layer1->SetExpectation(false, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| layer2->SetContentsOpaque(true); |
| layer2->SetOpacity(1.f); |
| layer2->SetExpectation(false, false); |
| layer2->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| EXPECT_TRUE(layer2->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // Layer with partially opaque contents, drawn with blending. |
| layer1->SetContentsOpaque(false); |
| layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5)); |
| layer1->SetQuadVisibleRect(gfx::Rect(5, 5, 5, 5)); |
| layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5)); |
| layer1->SetExpectation(true, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // Layer with partially opaque contents partially culled, drawn with blending. |
| layer1->SetContentsOpaque(false); |
| layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5)); |
| layer1->SetQuadVisibleRect(gfx::Rect(5, 5, 5, 2)); |
| layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5)); |
| layer1->SetExpectation(true, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // Layer with partially opaque contents culled, drawn with blending. |
| layer1->SetContentsOpaque(false); |
| layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5)); |
| layer1->SetQuadVisibleRect(gfx::Rect(7, 5, 3, 5)); |
| layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5)); |
| layer1->SetExpectation(true, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // Layer with partially opaque contents and translucent contents culled, drawn |
| // without blending. |
| layer1->SetContentsOpaque(false); |
| layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5)); |
| layer1->SetQuadVisibleRect(gfx::Rect(5, 5, 2, 5)); |
| layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5)); |
| layer1->SetExpectation(false, false); |
| layer1->SetUpdateRect(gfx::Rect(layer1->content_bounds())); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(layer1->quads_appended()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| class LayerTreeHostImplViewportCoveredTest : public LayerTreeHostImplTest { |
| protected: |
| LayerTreeHostImplViewportCoveredTest() : |
| gutter_quad_material_(DrawQuad::SOLID_COLOR), |
| child_(NULL), |
| did_activate_pending_tree_(false) {} |
| |
| scoped_ptr<OutputSurface> CreateFakeOutputSurface(bool always_draw) { |
| if (always_draw) { |
| return FakeOutputSurface::CreateAlwaysDrawAndSwap3d(); |
| } |
| return FakeOutputSurface::Create3d(); |
| } |
| |
| void SetupActiveTreeLayers() { |
| host_impl_->active_tree()->set_background_color(SK_ColorGRAY); |
| host_impl_->active_tree()->SetRootLayer( |
| LayerImpl::Create(host_impl_->active_tree(), 1)); |
| host_impl_->active_tree()->root_layer()->SetHasRenderSurface(true); |
| host_impl_->active_tree()->root_layer()->AddChild( |
| BlendStateCheckLayer::Create(host_impl_->active_tree(), |
| 2, |
| host_impl_->resource_provider())); |
| child_ = static_cast<BlendStateCheckLayer*>( |
| host_impl_->active_tree()->root_layer()->children()[0]); |
| child_->SetExpectation(false, false); |
| child_->SetContentsOpaque(true); |
| } |
| |
| // Expect no gutter rects. |
| void TestLayerCoversFullViewport() { |
| gfx::Rect layer_rect(viewport_size_); |
| child_->SetPosition(layer_rect.origin()); |
| child_->SetBounds(layer_rect.size()); |
| child_->SetContentBounds(layer_rect.size()); |
| child_->SetQuadRect(gfx::Rect(layer_rect.size())); |
| child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size())); |
| |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| |
| EXPECT_EQ(0u, CountGutterQuads(frame.render_passes[0]->quad_list)); |
| EXPECT_EQ(1u, frame.render_passes[0]->quad_list.size()); |
| ValidateTextureDrawQuads(frame.render_passes[0]->quad_list); |
| |
| VerifyQuadsExactlyCoverViewport(frame.render_passes[0]->quad_list); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| // Expect fullscreen gutter rect. |
| void TestEmptyLayer() { |
| gfx::Rect layer_rect(0, 0, 0, 0); |
| child_->SetPosition(layer_rect.origin()); |
| child_->SetBounds(layer_rect.size()); |
| child_->SetContentBounds(layer_rect.size()); |
| child_->SetQuadRect(gfx::Rect(layer_rect.size())); |
| child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size())); |
| |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| |
| EXPECT_EQ(1u, CountGutterQuads(frame.render_passes[0]->quad_list)); |
| EXPECT_EQ(1u, frame.render_passes[0]->quad_list.size()); |
| ValidateTextureDrawQuads(frame.render_passes[0]->quad_list); |
| |
| VerifyQuadsExactlyCoverViewport(frame.render_passes[0]->quad_list); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| // Expect four surrounding gutter rects. |
| void TestLayerInMiddleOfViewport() { |
| gfx::Rect layer_rect(500, 500, 200, 200); |
| child_->SetPosition(layer_rect.origin()); |
| child_->SetBounds(layer_rect.size()); |
| child_->SetContentBounds(layer_rect.size()); |
| child_->SetQuadRect(gfx::Rect(layer_rect.size())); |
| child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size())); |
| |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| |
| EXPECT_EQ(4u, CountGutterQuads(frame.render_passes[0]->quad_list)); |
| EXPECT_EQ(5u, frame.render_passes[0]->quad_list.size()); |
| ValidateTextureDrawQuads(frame.render_passes[0]->quad_list); |
| |
| VerifyQuadsExactlyCoverViewport(frame.render_passes[0]->quad_list); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| // Expect no gutter rects. |
| void TestLayerIsLargerThanViewport() { |
| gfx::Rect layer_rect(viewport_size_.width() + 10, |
| viewport_size_.height() + 10); |
| child_->SetPosition(layer_rect.origin()); |
| child_->SetBounds(layer_rect.size()); |
| child_->SetContentBounds(layer_rect.size()); |
| child_->SetQuadRect(gfx::Rect(layer_rect.size())); |
| child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size())); |
| |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| |
| EXPECT_EQ(0u, CountGutterQuads(frame.render_passes[0]->quad_list)); |
| EXPECT_EQ(1u, frame.render_passes[0]->quad_list.size()); |
| ValidateTextureDrawQuads(frame.render_passes[0]->quad_list); |
| |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| void DidActivateSyncTree() override { did_activate_pending_tree_ = true; } |
| |
| void set_gutter_quad_material(DrawQuad::Material material) { |
| gutter_quad_material_ = material; |
| } |
| void set_gutter_texture_size(const gfx::Size& gutter_texture_size) { |
| gutter_texture_size_ = gutter_texture_size; |
| } |
| |
| protected: |
| size_t CountGutterQuads(const QuadList& quad_list) { |
| size_t num_gutter_quads = 0; |
| for (const auto& quad : quad_list) { |
| num_gutter_quads += (quad->material == gutter_quad_material_) ? 1 : 0; |
| } |
| return num_gutter_quads; |
| } |
| |
| void VerifyQuadsExactlyCoverViewport(const QuadList& quad_list) { |
| LayerTestCommon::VerifyQuadsExactlyCoverRect( |
| quad_list, gfx::Rect(DipSizeToPixelSize(viewport_size_))); |
| } |
| |
| // Make sure that the texture coordinates match their expectations. |
| void ValidateTextureDrawQuads(const QuadList& quad_list) { |
| for (const auto& quad : quad_list) { |
| if (quad->material != DrawQuad::TEXTURE_CONTENT) |
| continue; |
| const TextureDrawQuad* texture_quad = TextureDrawQuad::MaterialCast(quad); |
| gfx::SizeF gutter_texture_size_pixels = gfx::ScaleSize( |
| gutter_texture_size_, host_impl_->device_scale_factor()); |
| EXPECT_EQ(texture_quad->uv_top_left.x(), |
| texture_quad->rect.x() / gutter_texture_size_pixels.width()); |
| EXPECT_EQ(texture_quad->uv_top_left.y(), |
| texture_quad->rect.y() / gutter_texture_size_pixels.height()); |
| EXPECT_EQ( |
| texture_quad->uv_bottom_right.x(), |
| texture_quad->rect.right() / gutter_texture_size_pixels.width()); |
| EXPECT_EQ( |
| texture_quad->uv_bottom_right.y(), |
| texture_quad->rect.bottom() / gutter_texture_size_pixels.height()); |
| } |
| } |
| |
| gfx::Size DipSizeToPixelSize(const gfx::Size& size) { |
| return gfx::ToRoundedSize( |
| gfx::ScaleSize(size, host_impl_->device_scale_factor())); |
| } |
| |
| DrawQuad::Material gutter_quad_material_; |
| gfx::Size gutter_texture_size_; |
| gfx::Size viewport_size_; |
| BlendStateCheckLayer* child_; |
| bool did_activate_pending_tree_; |
| }; |
| |
| TEST_F(LayerTreeHostImplViewportCoveredTest, ViewportCovered) { |
| viewport_size_ = gfx::Size(1000, 1000); |
| |
| bool always_draw = false; |
| CreateHostImpl(DefaultSettings(), CreateFakeOutputSurface(always_draw)); |
| |
| host_impl_->SetViewportSize(DipSizeToPixelSize(viewport_size_)); |
| SetupActiveTreeLayers(); |
| TestLayerCoversFullViewport(); |
| TestEmptyLayer(); |
| TestLayerInMiddleOfViewport(); |
| TestLayerIsLargerThanViewport(); |
| } |
| |
| TEST_F(LayerTreeHostImplViewportCoveredTest, ViewportCoveredScaled) { |
| viewport_size_ = gfx::Size(1000, 1000); |
| |
| bool always_draw = false; |
| CreateHostImpl(DefaultSettings(), CreateFakeOutputSurface(always_draw)); |
| |
| host_impl_->SetDeviceScaleFactor(2.f); |
| host_impl_->SetViewportSize(DipSizeToPixelSize(viewport_size_)); |
| SetupActiveTreeLayers(); |
| TestLayerCoversFullViewport(); |
| TestEmptyLayer(); |
| TestLayerInMiddleOfViewport(); |
| TestLayerIsLargerThanViewport(); |
| } |
| |
| TEST_F(LayerTreeHostImplViewportCoveredTest, ActiveTreeGrowViewportInvalid) { |
| viewport_size_ = gfx::Size(1000, 1000); |
| |
| bool always_draw = true; |
| CreateHostImpl(DefaultSettings(), CreateFakeOutputSurface(always_draw)); |
| |
| // Pending tree to force active_tree size invalid. Not used otherwise. |
| host_impl_->CreatePendingTree(); |
| host_impl_->SetViewportSize(DipSizeToPixelSize(viewport_size_)); |
| EXPECT_TRUE(host_impl_->active_tree()->ViewportSizeInvalid()); |
| |
| SetupActiveTreeLayers(); |
| TestEmptyLayer(); |
| TestLayerInMiddleOfViewport(); |
| TestLayerIsLargerThanViewport(); |
| } |
| |
| TEST_F(LayerTreeHostImplViewportCoveredTest, ActiveTreeShrinkViewportInvalid) { |
| viewport_size_ = gfx::Size(1000, 1000); |
| |
| bool always_draw = true; |
| CreateHostImpl(DefaultSettings(), CreateFakeOutputSurface(always_draw)); |
| |
| // Set larger viewport and activate it to active tree. |
| host_impl_->CreatePendingTree(); |
| gfx::Size larger_viewport(viewport_size_.width() + 100, |
| viewport_size_.height() + 100); |
| host_impl_->SetViewportSize(DipSizeToPixelSize(larger_viewport)); |
| EXPECT_TRUE(host_impl_->active_tree()->ViewportSizeInvalid()); |
| host_impl_->ActivateSyncTree(); |
| EXPECT_TRUE(did_activate_pending_tree_); |
| EXPECT_FALSE(host_impl_->active_tree()->ViewportSizeInvalid()); |
| |
| // Shrink pending tree viewport without activating. |
| host_impl_->CreatePendingTree(); |
| host_impl_->SetViewportSize(DipSizeToPixelSize(viewport_size_)); |
| EXPECT_TRUE(host_impl_->active_tree()->ViewportSizeInvalid()); |
| |
| SetupActiveTreeLayers(); |
| TestEmptyLayer(); |
| TestLayerInMiddleOfViewport(); |
| TestLayerIsLargerThanViewport(); |
| } |
| |
| class FakeDrawableLayerImpl: public LayerImpl { |
| public: |
| static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id) { |
| return make_scoped_ptr(new FakeDrawableLayerImpl(tree_impl, id)); |
| } |
| protected: |
| FakeDrawableLayerImpl(LayerTreeImpl* tree_impl, int id) |
| : LayerImpl(tree_impl, id) {} |
| }; |
| |
| // Only reshape when we know we are going to draw. Otherwise, the reshape |
| // can leave the window at the wrong size if we never draw and the proper |
| // viewport size is never set. |
| TEST_F(LayerTreeHostImplTest, ReshapeNotCalledUntilDraw) { |
| scoped_refptr<TestContextProvider> provider(TestContextProvider::Create()); |
| scoped_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(provider)); |
| CreateHostImpl(DefaultSettings(), output_surface.Pass()); |
| |
| scoped_ptr<LayerImpl> root = |
| FakeDrawableLayerImpl::Create(host_impl_->active_tree(), 1); |
| root->SetBounds(gfx::Size(10, 10)); |
| root->SetContentBounds(gfx::Size(10, 10)); |
| root->SetDrawsContent(true); |
| root->SetHasRenderSurface(true); |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| EXPECT_FALSE(provider->TestContext3d()->reshape_called()); |
| provider->TestContext3d()->clear_reshape_called(); |
| |
| LayerTreeHostImpl::FrameData frame; |
| host_impl_->SetViewportSize(gfx::Size(10, 10)); |
| host_impl_->SetDeviceScaleFactor(1.f); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(provider->TestContext3d()->reshape_called()); |
| EXPECT_EQ(provider->TestContext3d()->width(), 10); |
| EXPECT_EQ(provider->TestContext3d()->height(), 10); |
| EXPECT_EQ(provider->TestContext3d()->scale_factor(), 1.f); |
| host_impl_->DidDrawAllLayers(frame); |
| provider->TestContext3d()->clear_reshape_called(); |
| |
| host_impl_->SetViewportSize(gfx::Size(20, 30)); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(provider->TestContext3d()->reshape_called()); |
| EXPECT_EQ(provider->TestContext3d()->width(), 20); |
| EXPECT_EQ(provider->TestContext3d()->height(), 30); |
| EXPECT_EQ(provider->TestContext3d()->scale_factor(), 1.f); |
| host_impl_->DidDrawAllLayers(frame); |
| provider->TestContext3d()->clear_reshape_called(); |
| |
| host_impl_->SetDeviceScaleFactor(2.f); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| EXPECT_TRUE(provider->TestContext3d()->reshape_called()); |
| EXPECT_EQ(provider->TestContext3d()->width(), 20); |
| EXPECT_EQ(provider->TestContext3d()->height(), 30); |
| EXPECT_EQ(provider->TestContext3d()->scale_factor(), 2.f); |
| host_impl_->DidDrawAllLayers(frame); |
| provider->TestContext3d()->clear_reshape_called(); |
| } |
| |
| // Make sure damage tracking propagates all the way to the graphics context, |
| // where it should request to swap only the sub-buffer that is damaged. |
| TEST_F(LayerTreeHostImplTest, PartialSwapReceivesDamageRect) { |
| scoped_refptr<TestContextProvider> context_provider( |
| TestContextProvider::Create()); |
| context_provider->BindToCurrentThread(); |
| context_provider->TestContext3d()->set_have_post_sub_buffer(true); |
| |
| scoped_ptr<FakeOutputSurface> output_surface( |
| FakeOutputSurface::Create3d(context_provider)); |
| FakeOutputSurface* fake_output_surface = output_surface.get(); |
| |
| // This test creates its own LayerTreeHostImpl, so |
| // that we can force partial swap enabled. |
| LayerTreeSettings settings; |
| settings.renderer_settings.partial_swap_enabled = true; |
| scoped_ptr<SharedBitmapManager> shared_bitmap_manager( |
| new TestSharedBitmapManager()); |
| scoped_ptr<LayerTreeHostImpl> layer_tree_host_impl = |
| LayerTreeHostImpl::Create(settings, |
| this, |
| &proxy_, |
| &stats_instrumentation_, |
| shared_bitmap_manager.get(), |
| NULL, |
| 0); |
| layer_tree_host_impl->InitializeRenderer(output_surface.Pass()); |
| layer_tree_host_impl->SetViewportSize(gfx::Size(500, 500)); |
| |
| scoped_ptr<LayerImpl> root = |
| FakeDrawableLayerImpl::Create(layer_tree_host_impl->active_tree(), 1); |
| root->SetHasRenderSurface(true); |
| scoped_ptr<LayerImpl> child = |
| FakeDrawableLayerImpl::Create(layer_tree_host_impl->active_tree(), 2); |
| child->SetPosition(gfx::PointF(12.f, 13.f)); |
| child->SetBounds(gfx::Size(14, 15)); |
| child->SetContentBounds(gfx::Size(14, 15)); |
| child->SetDrawsContent(true); |
| root->SetBounds(gfx::Size(500, 500)); |
| root->SetContentBounds(gfx::Size(500, 500)); |
| root->SetDrawsContent(true); |
| root->AddChild(child.Pass()); |
| layer_tree_host_impl->active_tree()->SetRootLayer(root.Pass()); |
| |
| LayerTreeHostImpl::FrameData frame; |
| |
| // First frame, the entire screen should get swapped. |
| EXPECT_EQ(DRAW_SUCCESS, layer_tree_host_impl->PrepareToDraw(&frame)); |
| layer_tree_host_impl->DrawLayers(&frame, gfx::FrameTime::Now()); |
| layer_tree_host_impl->DidDrawAllLayers(frame); |
| layer_tree_host_impl->SwapBuffers(frame); |
| gfx::Rect expected_swap_rect(0, 0, 500, 500); |
| EXPECT_EQ(expected_swap_rect.ToString(), |
| fake_output_surface->last_swap_rect().ToString()); |
| |
| // Second frame, only the damaged area should get swapped. Damage should be |
| // the union of old and new child rects. |
| // expected damage rect: gfx::Rect(26, 28); |
| // expected swap rect: vertically flipped, with origin at bottom left corner. |
| layer_tree_host_impl->active_tree()->root_layer()->children()[0]->SetPosition( |
| gfx::PointF()); |
| EXPECT_EQ(DRAW_SUCCESS, layer_tree_host_impl->PrepareToDraw(&frame)); |
| layer_tree_host_impl->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| layer_tree_host_impl->SwapBuffers(frame); |
| |
| // Make sure that partial swap is constrained to the viewport dimensions |
| // expected damage rect: gfx::Rect(500, 500); |
| // expected swap rect: flipped damage rect, but also clamped to viewport |
| expected_swap_rect = gfx::Rect(0, 500-28, 26, 28); |
| EXPECT_EQ(expected_swap_rect.ToString(), |
| fake_output_surface->last_swap_rect().ToString()); |
| |
| layer_tree_host_impl->SetViewportSize(gfx::Size(10, 10)); |
| // This will damage everything. |
| layer_tree_host_impl->active_tree()->root_layer()->SetBackgroundColor( |
| SK_ColorBLACK); |
| EXPECT_EQ(DRAW_SUCCESS, layer_tree_host_impl->PrepareToDraw(&frame)); |
| layer_tree_host_impl->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| layer_tree_host_impl->SwapBuffers(frame); |
| |
| expected_swap_rect = gfx::Rect(0, 0, 10, 10); |
| EXPECT_EQ(expected_swap_rect.ToString(), |
| fake_output_surface->last_swap_rect().ToString()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, RootLayerDoesntCreateExtraSurface) { |
| scoped_ptr<LayerImpl> root = |
| FakeDrawableLayerImpl::Create(host_impl_->active_tree(), 1); |
| scoped_ptr<LayerImpl> child = |
| FakeDrawableLayerImpl::Create(host_impl_->active_tree(), 2); |
| child->SetBounds(gfx::Size(10, 10)); |
| child->SetContentBounds(gfx::Size(10, 10)); |
| child->SetDrawsContent(true); |
| root->SetBounds(gfx::Size(10, 10)); |
| root->SetContentBounds(gfx::Size(10, 10)); |
| root->SetDrawsContent(true); |
| root->SetHasRenderSurface(true); |
| root->AddChild(child.Pass()); |
| |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| |
| LayerTreeHostImpl::FrameData frame; |
| |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| EXPECT_EQ(1u, frame.render_surface_layer_list->size()); |
| EXPECT_EQ(1u, frame.render_passes.size()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| class FakeLayerWithQuads : public LayerImpl { |
| public: |
| static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id) { |
| return make_scoped_ptr(new FakeLayerWithQuads(tree_impl, id)); |
| } |
| |
| void AppendQuads(RenderPass* render_pass, |
| const Occlusion& occlusion_in_content_space, |
| AppendQuadsData* append_quads_data) override { |
| SharedQuadState* shared_quad_state = |
| render_pass->CreateAndAppendSharedQuadState(); |
| PopulateSharedQuadState(shared_quad_state); |
| |
| SkColor gray = SkColorSetRGB(100, 100, 100); |
| gfx::Rect quad_rect(content_bounds()); |
| gfx::Rect visible_quad_rect(quad_rect); |
| SolidColorDrawQuad* my_quad = |
| render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>(); |
| my_quad->SetNew( |
| shared_quad_state, quad_rect, visible_quad_rect, gray, false); |
| } |
| |
| private: |
| FakeLayerWithQuads(LayerTreeImpl* tree_impl, int id) |
| : LayerImpl(tree_impl, id) {} |
| }; |
| |
| class MockContext : public TestWebGraphicsContext3D { |
| public: |
| MOCK_METHOD1(useProgram, void(GLuint program)); |
| MOCK_METHOD5(uniform4f, void(GLint location, |
| GLfloat x, |
| GLfloat y, |
| GLfloat z, |
| GLfloat w)); |
| MOCK_METHOD4(uniformMatrix4fv, void(GLint location, |
| GLsizei count, |
| GLboolean transpose, |
| const GLfloat* value)); |
| MOCK_METHOD4(drawElements, void(GLenum mode, |
| GLsizei count, |
| GLenum type, |
| GLintptr offset)); |
| MOCK_METHOD1(enable, void(GLenum cap)); |
| MOCK_METHOD1(disable, void(GLenum cap)); |
| MOCK_METHOD4(scissor, void(GLint x, |
| GLint y, |
| GLsizei width, |
| GLsizei height)); |
| }; |
| |
| class MockContextHarness { |
| private: |
| MockContext* context_; |
| |
| public: |
| explicit MockContextHarness(MockContext* context) |
| : context_(context) { |
| context_->set_have_post_sub_buffer(true); |
| |
| // Catch "uninteresting" calls |
| EXPECT_CALL(*context_, useProgram(_)) |
| .Times(0); |
| |
| EXPECT_CALL(*context_, drawElements(_, _, _, _)) |
| .Times(0); |
| |
| // These are not asserted |
| EXPECT_CALL(*context_, uniformMatrix4fv(_, _, _, _)) |
| .WillRepeatedly(Return()); |
| |
| EXPECT_CALL(*context_, uniform4f(_, _, _, _, _)) |
| .WillRepeatedly(Return()); |
| |
| // Any un-sanctioned calls to enable() are OK |
| EXPECT_CALL(*context_, enable(_)) |
| .WillRepeatedly(Return()); |
| |
| // Any un-sanctioned calls to disable() are OK |
| EXPECT_CALL(*context_, disable(_)) |
| .WillRepeatedly(Return()); |
| } |
| |
| void MustDrawSolidQuad() { |
| EXPECT_CALL(*context_, drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0)) |
| .WillOnce(Return()) |
| .RetiresOnSaturation(); |
| |
| EXPECT_CALL(*context_, useProgram(_)) |
| .WillOnce(Return()) |
| .RetiresOnSaturation(); |
| } |
| |
| void MustSetScissor(int x, int y, int width, int height) { |
| EXPECT_CALL(*context_, enable(GL_SCISSOR_TEST)) |
| .WillRepeatedly(Return()); |
| |
| EXPECT_CALL(*context_, scissor(x, y, width, height)) |
| .Times(AtLeast(1)) |
| .WillRepeatedly(Return()); |
| } |
| |
| void MustSetNoScissor() { |
| EXPECT_CALL(*context_, disable(GL_SCISSOR_TEST)) |
| .WillRepeatedly(Return()); |
| |
| EXPECT_CALL(*context_, enable(GL_SCISSOR_TEST)) |
| .Times(0); |
| |
| EXPECT_CALL(*context_, scissor(_, _, _, _)) |
| .Times(0); |
| } |
| }; |
| |
| TEST_F(LayerTreeHostImplTest, NoPartialSwap) { |
| scoped_ptr<MockContext> mock_context_owned(new MockContext); |
| MockContext* mock_context = mock_context_owned.get(); |
| MockContextHarness harness(mock_context); |
| |
| // Run test case |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.renderer_settings.partial_swap_enabled = false; |
| CreateHostImpl(settings, |
| FakeOutputSurface::Create3d(mock_context_owned.Pass())); |
| SetupRootLayerImpl(FakeLayerWithQuads::Create(host_impl_->active_tree(), 1)); |
| |
| // Without partial swap, and no clipping, no scissor is set. |
| harness.MustDrawSolidQuad(); |
| harness.MustSetNoScissor(); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| Mock::VerifyAndClearExpectations(&mock_context); |
| |
| // Without partial swap, but a layer does clip its subtree, one scissor is |
| // set. |
| host_impl_->active_tree()->root_layer()->SetMasksToBounds(true); |
| harness.MustDrawSolidQuad(); |
| harness.MustSetScissor(0, 0, 10, 10); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| Mock::VerifyAndClearExpectations(&mock_context); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, PartialSwap) { |
| scoped_ptr<MockContext> context_owned(new MockContext); |
| MockContext* mock_context = context_owned.get(); |
| MockContextHarness harness(mock_context); |
| |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.renderer_settings.partial_swap_enabled = true; |
| CreateHostImpl(settings, FakeOutputSurface::Create3d(context_owned.Pass())); |
| SetupRootLayerImpl(FakeLayerWithQuads::Create(host_impl_->active_tree(), 1)); |
| |
| // The first frame is not a partially-swapped one. |
| harness.MustSetScissor(0, 0, 10, 10); |
| harness.MustDrawSolidQuad(); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| Mock::VerifyAndClearExpectations(&mock_context); |
| |
| // Damage a portion of the frame. |
| host_impl_->active_tree()->root_layer()->SetUpdateRect( |
| gfx::Rect(0, 0, 2, 3)); |
| |
| // The second frame will be partially-swapped (the y coordinates are flipped). |
| harness.MustSetScissor(0, 7, 2, 3); |
| harness.MustDrawSolidQuad(); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| Mock::VerifyAndClearExpectations(&mock_context); |
| } |
| |
| static scoped_ptr<LayerTreeHostImpl> SetupLayersForOpacity( |
| bool partial_swap, |
| LayerTreeHostImplClient* client, |
| Proxy* proxy, |
| SharedBitmapManager* manager, |
| RenderingStatsInstrumentation* stats_instrumentation) { |
| scoped_refptr<TestContextProvider> provider(TestContextProvider::Create()); |
| scoped_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(provider)); |
| provider->BindToCurrentThread(); |
| provider->TestContext3d()->set_have_post_sub_buffer(true); |
| |
| LayerTreeSettings settings; |
| settings.renderer_settings.partial_swap_enabled = partial_swap; |
| scoped_ptr<LayerTreeHostImpl> my_host_impl = LayerTreeHostImpl::Create( |
| settings, client, proxy, stats_instrumentation, manager, NULL, 0); |
| my_host_impl->InitializeRenderer(output_surface.Pass()); |
| my_host_impl->SetViewportSize(gfx::Size(100, 100)); |
| |
| /* |
| Layers are created as follows: |
| |
| +--------------------+ |
| | 1 | |
| | +-----------+ | |
| | | 2 | | |
| | | +-------------------+ |
| | | | 3 | |
| | | +-------------------+ |
| | | | | |
| | +-----------+ | |
| | | |
| | | |
| +--------------------+ |
| |
| Layers 1, 2 have render surfaces |
| */ |
| scoped_ptr<LayerImpl> root = |
| LayerImpl::Create(my_host_impl->active_tree(), 1); |
| scoped_ptr<LayerImpl> child = |
| LayerImpl::Create(my_host_impl->active_tree(), 2); |
| scoped_ptr<LayerImpl> grand_child = |
| FakeLayerWithQuads::Create(my_host_impl->active_tree(), 3); |
| |
| gfx::Rect root_rect(0, 0, 100, 100); |
| gfx::Rect child_rect(10, 10, 50, 50); |
| gfx::Rect grand_child_rect(5, 5, 150, 150); |
| |
| root->SetHasRenderSurface(true); |
| root->SetPosition(root_rect.origin()); |
| root->SetBounds(root_rect.size()); |
| root->SetContentBounds(root->bounds()); |
| root->draw_properties().visible_content_rect = root_rect; |
| root->SetDrawsContent(false); |
| root->render_surface()->SetContentRect(gfx::Rect(root_rect.size())); |
| |
| child->SetPosition(gfx::PointF(child_rect.x(), child_rect.y())); |
| child->SetOpacity(0.5f); |
| child->SetBounds(gfx::Size(child_rect.width(), child_rect.height())); |
| child->SetContentBounds(child->bounds()); |
| child->draw_properties().visible_content_rect = child_rect; |
| child->SetDrawsContent(false); |
| child->SetHasRenderSurface(true); |
| |
| grand_child->SetPosition(grand_child_rect.origin()); |
| grand_child->SetBounds(grand_child_rect.size()); |
| grand_child->SetContentBounds(grand_child->bounds()); |
| grand_child->draw_properties().visible_content_rect = grand_child_rect; |
| grand_child->SetDrawsContent(true); |
| |
| child->AddChild(grand_child.Pass()); |
| root->AddChild(child.Pass()); |
| |
| my_host_impl->active_tree()->SetRootLayer(root.Pass()); |
| return my_host_impl.Pass(); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ContributingLayerEmptyScissorPartialSwap) { |
| scoped_ptr<SharedBitmapManager> shared_bitmap_manager( |
| new TestSharedBitmapManager()); |
| scoped_ptr<LayerTreeHostImpl> my_host_impl = |
| SetupLayersForOpacity(true, |
| this, |
| &proxy_, |
| shared_bitmap_manager.get(), |
| &stats_instrumentation_); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, my_host_impl->PrepareToDraw(&frame)); |
| |
| // Verify all quads have been computed |
| ASSERT_EQ(2U, frame.render_passes.size()); |
| ASSERT_EQ(1U, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(1U, frame.render_passes[1]->quad_list.size()); |
| EXPECT_EQ(DrawQuad::SOLID_COLOR, |
| frame.render_passes[0]->quad_list.front()->material); |
| EXPECT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[1]->quad_list.front()->material); |
| |
| my_host_impl->DrawLayers(&frame, gfx::FrameTime::Now()); |
| my_host_impl->DidDrawAllLayers(frame); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ContributingLayerEmptyScissorNoPartialSwap) { |
| scoped_ptr<SharedBitmapManager> shared_bitmap_manager( |
| new TestSharedBitmapManager()); |
| scoped_ptr<LayerTreeHostImpl> my_host_impl = |
| SetupLayersForOpacity(false, |
| this, |
| &proxy_, |
| shared_bitmap_manager.get(), |
| &stats_instrumentation_); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, my_host_impl->PrepareToDraw(&frame)); |
| |
| // Verify all quads have been computed |
| ASSERT_EQ(2U, frame.render_passes.size()); |
| ASSERT_EQ(1U, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(1U, frame.render_passes[1]->quad_list.size()); |
| EXPECT_EQ(DrawQuad::SOLID_COLOR, |
| frame.render_passes[0]->quad_list.front()->material); |
| EXPECT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[1]->quad_list.front()->material); |
| |
| my_host_impl->DrawLayers(&frame, gfx::FrameTime::Now()); |
| my_host_impl->DidDrawAllLayers(frame); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, LayersFreeTextures) { |
| scoped_ptr<TestWebGraphicsContext3D> context = |
| TestWebGraphicsContext3D::Create(); |
| TestWebGraphicsContext3D* context3d = context.get(); |
| scoped_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(context.Pass())); |
| CreateHostImpl(DefaultSettings(), output_surface.Pass()); |
| |
| scoped_ptr<LayerImpl> root_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 1); |
| root_layer->SetBounds(gfx::Size(10, 10)); |
| root_layer->SetHasRenderSurface(true); |
| |
| scoped_ptr<IOSurfaceLayerImpl> io_surface_layer = |
| IOSurfaceLayerImpl::Create(host_impl_->active_tree(), 5); |
| io_surface_layer->SetBounds(gfx::Size(10, 10)); |
| io_surface_layer->SetContentBounds(gfx::Size(10, 10)); |
| io_surface_layer->SetDrawsContent(true); |
| io_surface_layer->SetIOSurfaceProperties(1, gfx::Size(10, 10)); |
| root_layer->AddChild(io_surface_layer.Pass()); |
| |
| host_impl_->active_tree()->SetRootLayer(root_layer.Pass()); |
| |
| EXPECT_EQ(0u, context3d->NumTextures()); |
| |
| 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); |
| |
| EXPECT_GT(context3d->NumTextures(), 0u); |
| |
| // Kill the layer tree. |
| host_impl_->active_tree()->SetRootLayer( |
| LayerImpl::Create(host_impl_->active_tree(), 100)); |
| // There should be no textures left in use after. |
| EXPECT_EQ(0u, context3d->NumTextures()); |
| } |
| |
| class MockDrawQuadsToFillScreenContext : public TestWebGraphicsContext3D { |
| public: |
| MOCK_METHOD1(useProgram, void(GLuint program)); |
| MOCK_METHOD4(drawElements, void(GLenum mode, |
| GLsizei count, |
| GLenum type, |
| GLintptr offset)); |
| }; |
| |
| TEST_F(LayerTreeHostImplTest, HasTransparentBackground) { |
| scoped_ptr<MockDrawQuadsToFillScreenContext> mock_context_owned( |
| new MockDrawQuadsToFillScreenContext); |
| MockDrawQuadsToFillScreenContext* mock_context = mock_context_owned.get(); |
| |
| // Run test case |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.renderer_settings.partial_swap_enabled = false; |
| CreateHostImpl(settings, |
| FakeOutputSurface::Create3d(mock_context_owned.Pass())); |
| SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1)); |
| host_impl_->active_tree()->set_background_color(SK_ColorWHITE); |
| |
| // Verify one quad is drawn when transparent background set is not set. |
| host_impl_->active_tree()->set_has_transparent_background(false); |
| EXPECT_CALL(*mock_context, useProgram(_)) |
| .Times(1); |
| EXPECT_CALL(*mock_context, drawElements(_, _, _, _)) |
| .Times(1); |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| Mock::VerifyAndClearExpectations(&mock_context); |
| |
| // Verify no quads are drawn when transparent background is set. |
| host_impl_->active_tree()->set_has_transparent_background(true); |
| host_impl_->SetFullRootLayerDamage(); |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| Mock::VerifyAndClearExpectations(&mock_context); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ReleaseContentsTextureShouldTriggerCommit) { |
| set_reduce_memory_result(false); |
| |
| // If changing the memory limit wouldn't result in changing what was |
| // committed, then no commit should be requested. |
| set_reduce_memory_result(false); |
| host_impl_->set_max_memory_needed_bytes( |
| host_impl_->memory_allocation_limit_bytes() - 1); |
| host_impl_->SetMemoryPolicy(ManagedMemoryPolicy( |
| host_impl_->memory_allocation_limit_bytes() - 1)); |
| EXPECT_FALSE(did_request_commit_); |
| did_request_commit_ = false; |
| |
| // If changing the memory limit would result in changing what was |
| // committed, then a commit should be requested, even though nothing was |
| // evicted. |
| set_reduce_memory_result(false); |
| host_impl_->set_max_memory_needed_bytes( |
| host_impl_->memory_allocation_limit_bytes()); |
| host_impl_->SetMemoryPolicy(ManagedMemoryPolicy( |
| host_impl_->memory_allocation_limit_bytes() - 1)); |
| EXPECT_TRUE(did_request_commit_); |
| did_request_commit_ = false; |
| |
| // Especially if changing the memory limit caused evictions, we need |
| // to re-commit. |
| set_reduce_memory_result(true); |
| host_impl_->set_max_memory_needed_bytes(1); |
| host_impl_->SetMemoryPolicy(ManagedMemoryPolicy( |
| host_impl_->memory_allocation_limit_bytes() - 1)); |
| EXPECT_TRUE(did_request_commit_); |
| did_request_commit_ = false; |
| |
| // But if we set it to the same value that it was before, we shouldn't |
| // re-commit. |
| host_impl_->SetMemoryPolicy(ManagedMemoryPolicy( |
| host_impl_->memory_allocation_limit_bytes())); |
| EXPECT_FALSE(did_request_commit_); |
| } |
| |
| class LayerTreeHostImplTestWithDelegatingRenderer |
| : public LayerTreeHostImplTest { |
| protected: |
| scoped_ptr<OutputSurface> CreateOutputSurface() override { |
| return FakeOutputSurface::CreateDelegating3d(); |
| } |
| |
| void DrawFrameAndTestDamage(const gfx::RectF& expected_damage) { |
| bool expect_to_draw = !expected_damage.IsEmpty(); |
| |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| if (!expect_to_draw) { |
| // With no damage, we don't draw, and no quads are created. |
| ASSERT_EQ(0u, frame.render_passes.size()); |
| } else { |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| |
| // Verify the damage rect for the root render pass. |
| const RenderPass* root_render_pass = frame.render_passes.back(); |
| EXPECT_EQ(expected_damage, root_render_pass->damage_rect); |
| |
| // Verify the root and child layers' quads are generated and not being |
| // culled. |
| ASSERT_EQ(2u, root_render_pass->quad_list.size()); |
| |
| LayerImpl* child = host_impl_->active_tree()->root_layer()->children()[0]; |
| gfx::RectF expected_child_visible_rect(child->content_bounds()); |
| EXPECT_EQ(expected_child_visible_rect, |
| root_render_pass->quad_list.front()->visible_rect); |
| |
| LayerImpl* root = host_impl_->active_tree()->root_layer(); |
| gfx::RectF expected_root_visible_rect(root->content_bounds()); |
| EXPECT_EQ(expected_root_visible_rect, |
| root_render_pass->quad_list.ElementAt(1)->visible_rect); |
| } |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| EXPECT_EQ(expect_to_draw, host_impl_->SwapBuffers(frame)); |
| } |
| }; |
| |
| TEST_F(LayerTreeHostImplTestWithDelegatingRenderer, FrameIncludesDamageRect) { |
| scoped_ptr<SolidColorLayerImpl> root = |
| SolidColorLayerImpl::Create(host_impl_->active_tree(), 1); |
| root->SetPosition(gfx::PointF()); |
| root->SetBounds(gfx::Size(10, 10)); |
| root->SetContentBounds(gfx::Size(10, 10)); |
| root->SetDrawsContent(true); |
| root->SetHasRenderSurface(true); |
| |
| // Child layer is in the bottom right corner. |
| scoped_ptr<SolidColorLayerImpl> child = |
| SolidColorLayerImpl::Create(host_impl_->active_tree(), 2); |
| child->SetPosition(gfx::PointF(9.f, 9.f)); |
| child->SetBounds(gfx::Size(1, 1)); |
| child->SetContentBounds(gfx::Size(1, 1)); |
| child->SetDrawsContent(true); |
| root->AddChild(child.Pass()); |
| |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| |
| // Draw a frame. In the first frame, the entire viewport should be damaged. |
| gfx::Rect full_frame_damage(host_impl_->DrawViewportSize()); |
| DrawFrameAndTestDamage(full_frame_damage); |
| |
| // The second frame has damage that doesn't touch the child layer. Its quads |
| // should still be generated. |
| gfx::Rect small_damage = gfx::Rect(0, 0, 1, 1); |
| host_impl_->active_tree()->root_layer()->SetUpdateRect(small_damage); |
| DrawFrameAndTestDamage(small_damage); |
| |
| // The third frame should have no damage, so no quads should be generated. |
| gfx::Rect no_damage; |
| DrawFrameAndTestDamage(no_damage); |
| } |
| |
| // TODO(reveman): Remove this test and the ability to prevent on demand raster |
| // when delegating renderer supports PictureDrawQuads. crbug.com/342121 |
| TEST_F(LayerTreeHostImplTestWithDelegatingRenderer, PreventRasterizeOnDemand) { |
| LayerTreeSettings settings; |
| CreateHostImpl(settings, CreateOutputSurface()); |
| EXPECT_FALSE(host_impl_->GetRendererCapabilities().allow_rasterize_on_demand); |
| } |
| |
| class FakeMaskLayerImpl : public LayerImpl { |
| public: |
| static scoped_ptr<FakeMaskLayerImpl> Create(LayerTreeImpl* tree_impl, |
| int id) { |
| return make_scoped_ptr(new FakeMaskLayerImpl(tree_impl, id)); |
| } |
| |
| void GetContentsResourceId(ResourceProvider::ResourceId* resource_id, |
| gfx::Size* resource_size) const override { |
| *resource_id = 0; |
| } |
| |
| private: |
| FakeMaskLayerImpl(LayerTreeImpl* tree_impl, int id) |
| : LayerImpl(tree_impl, id) {} |
| }; |
| |
| TEST_F(LayerTreeHostImplTest, MaskLayerWithScaling) { |
| LayerTreeSettings settings; |
| settings.layer_transforms_should_scale_layer_contents = true; |
| CreateHostImpl(settings, CreateOutputSurface()); |
| |
| // Root |
| // | |
| // +-- Scaling Layer (adds a 2x scale) |
| // | |
| // +-- Content Layer |
| // +--Mask |
| scoped_ptr<LayerImpl> scoped_root = |
| LayerImpl::Create(host_impl_->active_tree(), 1); |
| LayerImpl* root = scoped_root.get(); |
| root->SetHasRenderSurface(true); |
| host_impl_->active_tree()->SetRootLayer(scoped_root.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_scaling_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 2); |
| LayerImpl* scaling_layer = scoped_scaling_layer.get(); |
| root->AddChild(scoped_scaling_layer.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_content_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| LayerImpl* content_layer = scoped_content_layer.get(); |
| scaling_layer->AddChild(scoped_content_layer.Pass()); |
| |
| scoped_ptr<FakeMaskLayerImpl> scoped_mask_layer = |
| FakeMaskLayerImpl::Create(host_impl_->active_tree(), 4); |
| FakeMaskLayerImpl* mask_layer = scoped_mask_layer.get(); |
| content_layer->SetHasRenderSurface(true); |
| content_layer->SetMaskLayer(scoped_mask_layer.Pass()); |
| |
| gfx::Size root_size(100, 100); |
| root->SetBounds(root_size); |
| root->SetContentBounds(root_size); |
| root->SetPosition(gfx::PointF()); |
| |
| gfx::Size scaling_layer_size(50, 50); |
| scaling_layer->SetBounds(scaling_layer_size); |
| scaling_layer->SetContentBounds(scaling_layer_size); |
| scaling_layer->SetPosition(gfx::PointF()); |
| gfx::Transform scale; |
| scale.Scale(2.f, 2.f); |
| scaling_layer->SetTransform(scale); |
| |
| content_layer->SetBounds(scaling_layer_size); |
| content_layer->SetContentBounds(scaling_layer_size); |
| content_layer->SetPosition(gfx::PointF()); |
| content_layer->SetDrawsContent(true); |
| |
| mask_layer->SetBounds(scaling_layer_size); |
| mask_layer->SetContentBounds(scaling_layer_size); |
| mask_layer->SetPosition(gfx::PointF()); |
| mask_layer->SetDrawsContent(true); |
| |
| |
| // Check that the tree scaling is correctly taken into account for the mask, |
| // that should fully map onto the quad. |
| float device_scale_factor = 1.f; |
| host_impl_->SetViewportSize(root_size); |
| host_impl_->SetDeviceScaleFactor(device_scale_factor); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.front()->material); |
| const RenderPassDrawQuad* render_pass_quad = |
| RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.front()); |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(), |
| render_pass_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(), |
| render_pass_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(1.f, 1.f).ToString(), |
| render_pass_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| |
| // Applying a DSF should change the render surface size, but won't affect |
| // which part of the mask is used. |
| device_scale_factor = 2.f; |
| gfx::Size device_viewport = |
| gfx::ToFlooredSize(gfx::ScaleSize(root_size, device_scale_factor)); |
| host_impl_->SetViewportSize(device_viewport); |
| host_impl_->SetDeviceScaleFactor(device_scale_factor); |
| host_impl_->active_tree()->set_needs_update_draw_properties(); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.front()->material); |
| const RenderPassDrawQuad* render_pass_quad = |
| RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.front()); |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 200).ToString(), |
| render_pass_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(), |
| render_pass_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(1.f, 1.f).ToString(), |
| render_pass_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| |
| // Applying an equivalent content scale on the content layer and the mask |
| // should still result in the same part of the mask being used. |
| gfx::Size content_bounds = |
| gfx::ToRoundedSize(gfx::ScaleSize(scaling_layer_size, |
| device_scale_factor)); |
| content_layer->SetContentBounds(content_bounds); |
| content_layer->SetContentsScale(device_scale_factor, device_scale_factor); |
| mask_layer->SetContentBounds(content_bounds); |
| mask_layer->SetContentsScale(device_scale_factor, device_scale_factor); |
| host_impl_->active_tree()->set_needs_update_draw_properties(); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.front()->material); |
| const RenderPassDrawQuad* render_pass_quad = |
| RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.front()); |
| EXPECT_EQ(gfx::Rect(0, 0, 200, 200).ToString(), |
| render_pass_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(), |
| render_pass_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(1.f, 1.f).ToString(), |
| render_pass_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, MaskLayerWithDifferentBounds) { |
| // The mask layer has bounds 100x100 but is attached to a layer with bounds |
| // 50x50. |
| |
| scoped_ptr<LayerImpl> scoped_root = |
| LayerImpl::Create(host_impl_->active_tree(), 1); |
| LayerImpl* root = scoped_root.get(); |
| root->SetHasRenderSurface(true); |
| |
| host_impl_->active_tree()->SetRootLayer(scoped_root.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_content_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| LayerImpl* content_layer = scoped_content_layer.get(); |
| root->AddChild(scoped_content_layer.Pass()); |
| |
| scoped_ptr<FakeMaskLayerImpl> scoped_mask_layer = |
| FakeMaskLayerImpl::Create(host_impl_->active_tree(), 4); |
| FakeMaskLayerImpl* mask_layer = scoped_mask_layer.get(); |
| content_layer->SetMaskLayer(scoped_mask_layer.Pass()); |
| content_layer->SetHasRenderSurface(true); |
| |
| gfx::Size root_size(100, 100); |
| root->SetBounds(root_size); |
| root->SetContentBounds(root_size); |
| root->SetPosition(gfx::PointF()); |
| |
| gfx::Size layer_size(50, 50); |
| content_layer->SetBounds(layer_size); |
| content_layer->SetContentBounds(layer_size); |
| content_layer->SetPosition(gfx::PointF()); |
| content_layer->SetDrawsContent(true); |
| |
| gfx::Size mask_size(100, 100); |
| mask_layer->SetBounds(mask_size); |
| mask_layer->SetContentBounds(mask_size); |
| mask_layer->SetPosition(gfx::PointF()); |
| mask_layer->SetDrawsContent(true); |
| |
| // Check that the mask fills the surface. |
| float device_scale_factor = 1.f; |
| host_impl_->SetViewportSize(root_size); |
| host_impl_->SetDeviceScaleFactor(device_scale_factor); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.front()->material); |
| const RenderPassDrawQuad* render_pass_quad = |
| RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.front()); |
| EXPECT_EQ(gfx::Rect(0, 0, 50, 50).ToString(), |
| render_pass_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(), |
| render_pass_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(1.f, 1.f).ToString(), |
| render_pass_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| // Applying a DSF should change the render surface size, but won't affect |
| // which part of the mask is used. |
| device_scale_factor = 2.f; |
| gfx::Size device_viewport = |
| gfx::ToFlooredSize(gfx::ScaleSize(root_size, device_scale_factor)); |
| host_impl_->SetViewportSize(device_viewport); |
| host_impl_->SetDeviceScaleFactor(device_scale_factor); |
| host_impl_->active_tree()->set_needs_update_draw_properties(); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.front()->material); |
| const RenderPassDrawQuad* render_pass_quad = |
| RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.front()); |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(), |
| render_pass_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(), |
| render_pass_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(1.f, 1.f).ToString(), |
| render_pass_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| // Applying an equivalent content scale on the content layer and the mask |
| // should still result in the same part of the mask being used. |
| gfx::Size layer_size_large = |
| gfx::ToRoundedSize(gfx::ScaleSize(layer_size, device_scale_factor)); |
| content_layer->SetContentBounds(layer_size_large); |
| content_layer->SetContentsScale(device_scale_factor, device_scale_factor); |
| gfx::Size mask_size_large = |
| gfx::ToRoundedSize(gfx::ScaleSize(mask_size, device_scale_factor)); |
| mask_layer->SetContentBounds(mask_size_large); |
| mask_layer->SetContentsScale(device_scale_factor, device_scale_factor); |
| host_impl_->active_tree()->set_needs_update_draw_properties(); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.front()->material); |
| const RenderPassDrawQuad* render_pass_quad = |
| RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.front()); |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(), |
| render_pass_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(), |
| render_pass_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(1.f, 1.f).ToString(), |
| render_pass_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| // Applying a different contents scale to the mask layer means it will have |
| // a larger texture, but it should use the same tex coords to cover the |
| // layer it masks. |
| mask_layer->SetContentBounds(mask_size); |
| mask_layer->SetContentsScale(1.f, 1.f); |
| host_impl_->active_tree()->set_needs_update_draw_properties(); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.front()->material); |
| const RenderPassDrawQuad* render_pass_quad = |
| RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.front()); |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(), |
| render_pass_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(), |
| render_pass_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(1.f, 1.f).ToString(), |
| render_pass_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ReflectionMaskLayerWithDifferentBounds) { |
| // The replica's mask layer has bounds 100x100 but the replica is of a |
| // layer with bounds 50x50. |
| |
| scoped_ptr<LayerImpl> scoped_root = |
| LayerImpl::Create(host_impl_->active_tree(), 1); |
| LayerImpl* root = scoped_root.get(); |
| root->SetHasRenderSurface(true); |
| |
| host_impl_->active_tree()->SetRootLayer(scoped_root.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_content_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| LayerImpl* content_layer = scoped_content_layer.get(); |
| root->AddChild(scoped_content_layer.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_replica_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 2); |
| LayerImpl* replica_layer = scoped_replica_layer.get(); |
| content_layer->SetReplicaLayer(scoped_replica_layer.Pass()); |
| content_layer->SetHasRenderSurface(true); |
| |
| scoped_ptr<FakeMaskLayerImpl> scoped_mask_layer = |
| FakeMaskLayerImpl::Create(host_impl_->active_tree(), 4); |
| FakeMaskLayerImpl* mask_layer = scoped_mask_layer.get(); |
| replica_layer->SetMaskLayer(scoped_mask_layer.Pass()); |
| replica_layer->SetHasRenderSurface(true); |
| |
| gfx::Size root_size(100, 100); |
| root->SetBounds(root_size); |
| root->SetContentBounds(root_size); |
| root->SetPosition(gfx::PointF()); |
| |
| gfx::Size layer_size(50, 50); |
| content_layer->SetBounds(layer_size); |
| content_layer->SetContentBounds(layer_size); |
| content_layer->SetPosition(gfx::PointF()); |
| content_layer->SetDrawsContent(true); |
| |
| gfx::Size mask_size(100, 100); |
| mask_layer->SetBounds(mask_size); |
| mask_layer->SetContentBounds(mask_size); |
| mask_layer->SetPosition(gfx::PointF()); |
| mask_layer->SetDrawsContent(true); |
| |
| // Check that the mask fills the surface. |
| float device_scale_factor = 1.f; |
| host_impl_->SetViewportSize(root_size); |
| host_impl_->SetDeviceScaleFactor(device_scale_factor); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.ElementAt(1)->material); |
| const RenderPassDrawQuad* replica_quad = RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.ElementAt(1)); |
| EXPECT_EQ(gfx::Rect(0, 0, 50, 50).ToString(), |
| replica_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(), |
| replica_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(1.f, 1.f).ToString(), |
| replica_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| // Applying a DSF should change the render surface size, but won't affect |
| // which part of the mask is used. |
| device_scale_factor = 2.f; |
| gfx::Size device_viewport = |
| gfx::ToFlooredSize(gfx::ScaleSize(root_size, device_scale_factor)); |
| host_impl_->SetViewportSize(device_viewport); |
| host_impl_->SetDeviceScaleFactor(device_scale_factor); |
| host_impl_->active_tree()->set_needs_update_draw_properties(); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.ElementAt(1)->material); |
| const RenderPassDrawQuad* replica_quad = RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.ElementAt(1)); |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(), |
| replica_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(), |
| replica_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(1.f, 1.f).ToString(), |
| replica_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| // Applying an equivalent content scale on the content layer and the mask |
| // should still result in the same part of the mask being used. |
| gfx::Size layer_size_large = |
| gfx::ToRoundedSize(gfx::ScaleSize(layer_size, device_scale_factor)); |
| content_layer->SetContentBounds(layer_size_large); |
| content_layer->SetContentsScale(device_scale_factor, device_scale_factor); |
| gfx::Size mask_size_large = |
| gfx::ToRoundedSize(gfx::ScaleSize(mask_size, device_scale_factor)); |
| mask_layer->SetContentBounds(mask_size_large); |
| mask_layer->SetContentsScale(device_scale_factor, device_scale_factor); |
| host_impl_->active_tree()->set_needs_update_draw_properties(); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.ElementAt(1)->material); |
| const RenderPassDrawQuad* replica_quad = RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.ElementAt(1)); |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(), |
| replica_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(), |
| replica_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(1.f, 1.f).ToString(), |
| replica_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| // Applying a different contents scale to the mask layer means it will have |
| // a larger texture, but it should use the same tex coords to cover the |
| // layer it masks. |
| mask_layer->SetContentBounds(mask_size); |
| mask_layer->SetContentsScale(1.f, 1.f); |
| host_impl_->active_tree()->set_needs_update_draw_properties(); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size()); |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.ElementAt(1)->material); |
| const RenderPassDrawQuad* replica_quad = RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.ElementAt(1)); |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(), |
| replica_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 1.f, 1.f).ToString(), |
| replica_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(1.f, 1.f).ToString(), |
| replica_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ReflectionMaskLayerForSurfaceWithUnclippedChild) { |
| // The replica is of a layer with bounds 50x50, but it has a child that causes |
| // the surface bounds to be larger. |
| |
| scoped_ptr<LayerImpl> scoped_root = |
| LayerImpl::Create(host_impl_->active_tree(), 1); |
| LayerImpl* root = scoped_root.get(); |
| root->SetHasRenderSurface(true); |
| host_impl_->active_tree()->SetRootLayer(scoped_root.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_content_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 2); |
| LayerImpl* content_layer = scoped_content_layer.get(); |
| root->AddChild(scoped_content_layer.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_content_child_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| LayerImpl* content_child_layer = scoped_content_child_layer.get(); |
| content_layer->AddChild(scoped_content_child_layer.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_replica_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 4); |
| LayerImpl* replica_layer = scoped_replica_layer.get(); |
| content_layer->SetReplicaLayer(scoped_replica_layer.Pass()); |
| content_layer->SetHasRenderSurface(true); |
| |
| scoped_ptr<FakeMaskLayerImpl> scoped_mask_layer = |
| FakeMaskLayerImpl::Create(host_impl_->active_tree(), 5); |
| FakeMaskLayerImpl* mask_layer = scoped_mask_layer.get(); |
| replica_layer->SetMaskLayer(scoped_mask_layer.Pass()); |
| replica_layer->SetHasRenderSurface(true); |
| |
| gfx::Size root_size(100, 100); |
| root->SetBounds(root_size); |
| root->SetContentBounds(root_size); |
| root->SetPosition(gfx::PointF()); |
| |
| gfx::Size layer_size(50, 50); |
| content_layer->SetBounds(layer_size); |
| content_layer->SetContentBounds(layer_size); |
| content_layer->SetPosition(gfx::PointF()); |
| content_layer->SetDrawsContent(true); |
| |
| gfx::Size child_size(50, 50); |
| content_child_layer->SetBounds(child_size); |
| content_child_layer->SetContentBounds(child_size); |
| content_child_layer->SetPosition(gfx::Point(50, 0)); |
| content_child_layer->SetDrawsContent(true); |
| |
| gfx::Size mask_size(50, 50); |
| mask_layer->SetBounds(mask_size); |
| mask_layer->SetContentBounds(mask_size); |
| mask_layer->SetPosition(gfx::PointF()); |
| mask_layer->SetDrawsContent(true); |
| |
| float device_scale_factor = 1.f; |
| host_impl_->SetViewportSize(root_size); |
| host_impl_->SetDeviceScaleFactor(device_scale_factor); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size()); |
| |
| // The surface is 100x50. |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.front()->material); |
| const RenderPassDrawQuad* render_pass_quad = |
| RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.front()); |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 50).ToString(), |
| render_pass_quad->rect.ToString()); |
| |
| // The mask covers the owning layer only. |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.ElementAt(1)->material); |
| const RenderPassDrawQuad* replica_quad = RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.ElementAt(1)); |
| EXPECT_EQ(gfx::Rect(0, 0, 100, 50).ToString(), |
| replica_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(0.f, 0.f, 2.f, 1.f).ToString(), |
| replica_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(2.f, 1.f).ToString(), |
| replica_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| // Move the child to (-50, 0) instead. Now the mask should be moved to still |
| // cover the layer being replicated. |
| content_child_layer->SetPosition(gfx::Point(-50, 0)); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(2u, frame.render_passes[0]->quad_list.size()); |
| |
| // The surface is 100x50 with its origin at (-50, 0). |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.front()->material); |
| const RenderPassDrawQuad* render_pass_quad = |
| RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.front()); |
| EXPECT_EQ(gfx::Rect(-50, 0, 100, 50).ToString(), |
| render_pass_quad->rect.ToString()); |
| |
| // The mask covers the owning layer only. |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.ElementAt(1)->material); |
| const RenderPassDrawQuad* replica_quad = RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.ElementAt(1)); |
| EXPECT_EQ(gfx::Rect(-50, 0, 100, 50).ToString(), |
| replica_quad->rect.ToString()); |
| EXPECT_EQ(gfx::RectF(-1.f, 0.f, 2.f, 1.f).ToString(), |
| replica_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(2.f, 1.f).ToString(), |
| replica_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, MaskLayerForSurfaceWithClippedLayer) { |
| // The masked layer has bounds 50x50, but it has a child that causes |
| // the surface bounds to be larger. It also has a parent that clips the |
| // masked layer and its surface. |
| |
| scoped_ptr<LayerImpl> scoped_root = |
| LayerImpl::Create(host_impl_->active_tree(), 1); |
| LayerImpl* root = scoped_root.get(); |
| root->SetHasRenderSurface(true); |
| |
| host_impl_->active_tree()->SetRootLayer(scoped_root.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_clipping_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 2); |
| LayerImpl* clipping_layer = scoped_clipping_layer.get(); |
| root->AddChild(scoped_clipping_layer.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_content_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| LayerImpl* content_layer = scoped_content_layer.get(); |
| clipping_layer->AddChild(scoped_content_layer.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_content_child_layer = |
| LayerImpl::Create(host_impl_->active_tree(), 4); |
| LayerImpl* content_child_layer = scoped_content_child_layer.get(); |
| content_layer->AddChild(scoped_content_child_layer.Pass()); |
| |
| scoped_ptr<FakeMaskLayerImpl> scoped_mask_layer = |
| FakeMaskLayerImpl::Create(host_impl_->active_tree(), 6); |
| FakeMaskLayerImpl* mask_layer = scoped_mask_layer.get(); |
| content_layer->SetMaskLayer(scoped_mask_layer.Pass()); |
| content_layer->SetHasRenderSurface(true); |
| |
| gfx::Size root_size(100, 100); |
| root->SetBounds(root_size); |
| root->SetContentBounds(root_size); |
| root->SetPosition(gfx::PointF()); |
| |
| gfx::Rect clipping_rect(20, 10, 10, 20); |
| clipping_layer->SetBounds(clipping_rect.size()); |
| clipping_layer->SetContentBounds(clipping_rect.size()); |
| clipping_layer->SetPosition(clipping_rect.origin()); |
| clipping_layer->SetMasksToBounds(true); |
| |
| gfx::Size layer_size(50, 50); |
| content_layer->SetBounds(layer_size); |
| content_layer->SetContentBounds(layer_size); |
| content_layer->SetPosition(gfx::Point() - clipping_rect.OffsetFromOrigin()); |
| content_layer->SetDrawsContent(true); |
| |
| gfx::Size child_size(50, 50); |
| content_child_layer->SetBounds(child_size); |
| content_child_layer->SetContentBounds(child_size); |
| content_child_layer->SetPosition(gfx::Point(50, 0)); |
| content_child_layer->SetDrawsContent(true); |
| |
| gfx::Size mask_size(100, 100); |
| mask_layer->SetBounds(mask_size); |
| mask_layer->SetContentBounds(mask_size); |
| mask_layer->SetPosition(gfx::PointF()); |
| mask_layer->SetDrawsContent(true); |
| |
| float device_scale_factor = 1.f; |
| host_impl_->SetViewportSize(root_size); |
| host_impl_->SetDeviceScaleFactor(device_scale_factor); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_EQ(1u, frame.render_passes[0]->quad_list.size()); |
| |
| // The surface is clipped to 10x20. |
| ASSERT_EQ(DrawQuad::RENDER_PASS, |
| frame.render_passes[0]->quad_list.front()->material); |
| const RenderPassDrawQuad* render_pass_quad = |
| RenderPassDrawQuad::MaterialCast( |
| frame.render_passes[0]->quad_list.front()); |
| EXPECT_EQ(gfx::Rect(20, 10, 10, 20).ToString(), |
| render_pass_quad->rect.ToString()); |
| // The masked layer is 50x50, but the surface size is 10x20. So the texture |
| // coords in the mask are scaled by 10/50 and 20/50. |
| // The surface is clipped to (20,10) so the mask texture coords are offset |
| // by 20/50 and 10/50 |
| EXPECT_EQ(gfx::ScaleRect(gfx::RectF(20.f, 10.f, 10.f, 20.f), 1.f / 50.f) |
| .ToString(), |
| render_pass_quad->MaskUVRect().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(10.f / 50.f, 20.f / 50.f).ToString(), |
| render_pass_quad->mask_uv_scale.ToString()); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| } |
| |
| class GLRendererWithSetupQuadForAntialiasing : public GLRenderer { |
| public: |
| using GLRenderer::ShouldAntialiasQuad; |
| }; |
| |
| TEST_F(LayerTreeHostImplTest, FarAwayQuadsDontNeedAA) { |
| // Due to precision issues (especially on Android), sometimes far |
| // away quads can end up thinking they need AA. |
| float device_scale_factor = 4.f / 3.f; |
| host_impl_->SetDeviceScaleFactor(device_scale_factor); |
| gfx::Size root_size(2000, 1000); |
| gfx::Size device_viewport_size = |
| gfx::ToCeiledSize(gfx::ScaleSize(root_size, device_scale_factor)); |
| host_impl_->SetViewportSize(device_viewport_size); |
| |
| host_impl_->CreatePendingTree(); |
| host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f / 16.f, |
| 16.f); |
| |
| scoped_ptr<LayerImpl> scoped_root = |
| LayerImpl::Create(host_impl_->pending_tree(), 1); |
| LayerImpl* root = scoped_root.get(); |
| root->SetHasRenderSurface(true); |
| |
| host_impl_->pending_tree()->SetRootLayer(scoped_root.Pass()); |
| |
| scoped_ptr<LayerImpl> scoped_scrolling_layer = |
| LayerImpl::Create(host_impl_->pending_tree(), 2); |
| LayerImpl* scrolling_layer = scoped_scrolling_layer.get(); |
| root->AddChild(scoped_scrolling_layer.Pass()); |
| |
| gfx::Size content_layer_bounds(100000, 100); |
| gfx::Size pile_tile_size(3000, 3000); |
| scoped_refptr<FakePicturePileImpl> pile(FakePicturePileImpl::CreateFilledPile( |
| pile_tile_size, content_layer_bounds)); |
| |
| scoped_ptr<FakePictureLayerImpl> scoped_content_layer = |
| FakePictureLayerImpl::CreateWithRasterSource(host_impl_->pending_tree(), |
| 3, pile); |
| LayerImpl* content_layer = scoped_content_layer.get(); |
| scrolling_layer->AddChild(scoped_content_layer.Pass()); |
| content_layer->SetBounds(content_layer_bounds); |
| content_layer->SetDrawsContent(true); |
| |
| root->SetBounds(root_size); |
| |
| gfx::ScrollOffset scroll_offset(100000, 0); |
| scrolling_layer->SetScrollClipLayer(root->id()); |
| scrolling_layer->PushScrollOffsetFromMainThread(scroll_offset); |
| |
| host_impl_->ActivateSyncTree(); |
| |
| host_impl_->active_tree()->UpdateDrawProperties(); |
| ASSERT_EQ(1u, host_impl_->active_tree()->RenderSurfaceLayerList().size()); |
| |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| |
| ASSERT_EQ(1u, frame.render_passes.size()); |
| ASSERT_LE(1u, frame.render_passes[0]->quad_list.size()); |
| const DrawQuad* quad = frame.render_passes[0]->quad_list.front(); |
| |
| bool antialiased = |
| GLRendererWithSetupQuadForAntialiasing::ShouldAntialiasQuad( |
| quad->quadTransform(), quad, false); |
| EXPECT_FALSE(antialiased); |
| |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| |
| |
| class CompositorFrameMetadataTest : public LayerTreeHostImplTest { |
| public: |
| CompositorFrameMetadataTest() |
| : swap_buffers_complete_(0) {} |
| |
| void DidSwapBuffersCompleteOnImplThread() override { |
| swap_buffers_complete_++; |
| } |
| |
| int swap_buffers_complete_; |
| }; |
| |
| TEST_F(CompositorFrameMetadataTest, CompositorFrameAckCountsAsSwapComplete) { |
| SetupRootLayerImpl(FakeLayerWithQuads::Create(host_impl_->active_tree(), 1)); |
| { |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, base::TimeTicks()); |
| host_impl_->DidDrawAllLayers(frame); |
| } |
| CompositorFrameAck ack; |
| host_impl_->ReclaimResources(&ack); |
| host_impl_->DidSwapBuffersComplete(); |
| EXPECT_EQ(swap_buffers_complete_, 1); |
| } |
| |
| class CountingSoftwareDevice : public SoftwareOutputDevice { |
| public: |
| CountingSoftwareDevice() : frames_began_(0), frames_ended_(0) {} |
| |
| SkCanvas* BeginPaint(const gfx::Rect& damage_rect) override { |
| ++frames_began_; |
| return SoftwareOutputDevice::BeginPaint(damage_rect); |
| } |
| void EndPaint(SoftwareFrameData* frame_data) override { |
| ++frames_ended_; |
| SoftwareOutputDevice::EndPaint(frame_data); |
| } |
| |
| int frames_began_, frames_ended_; |
| }; |
| |
| TEST_F(LayerTreeHostImplTest, ForcedDrawToSoftwareDeviceBasicRender) { |
| // No main thread evictions in resourceless software mode. |
| set_reduce_memory_result(false); |
| CountingSoftwareDevice* software_device = new CountingSoftwareDevice(); |
| bool delegated_rendering = false; |
| FakeOutputSurface* output_surface = |
| FakeOutputSurface::CreateDeferredGL( |
| scoped_ptr<SoftwareOutputDevice>(software_device), |
| delegated_rendering).release(); |
| EXPECT_TRUE(CreateHostImpl(DefaultSettings(), |
| scoped_ptr<OutputSurface>(output_surface))); |
| host_impl_->SetViewportSize(gfx::Size(50, 50)); |
| |
| SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| |
| const gfx::Transform external_transform; |
| const gfx::Rect external_viewport; |
| const gfx::Rect external_clip; |
| const bool resourceless_software_draw = true; |
| host_impl_->SetExternalDrawConstraints(external_transform, |
| external_viewport, |
| external_clip, |
| external_viewport, |
| external_transform, |
| resourceless_software_draw); |
| |
| EXPECT_EQ(0, software_device->frames_began_); |
| EXPECT_EQ(0, software_device->frames_ended_); |
| |
| DrawFrame(); |
| |
| EXPECT_EQ(1, software_device->frames_began_); |
| EXPECT_EQ(1, software_device->frames_ended_); |
| |
| // Call other API methods that are likely to hit NULL pointer in this mode. |
| EXPECT_TRUE(host_impl_->AsValue().get()); |
| EXPECT_TRUE(host_impl_->ActivationStateAsValue().get()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, |
| ForcedDrawToSoftwareDeviceSkipsUnsupportedLayers) { |
| set_reduce_memory_result(false); |
| bool delegated_rendering = false; |
| FakeOutputSurface* output_surface = |
| FakeOutputSurface::CreateDeferredGL( |
| scoped_ptr<SoftwareOutputDevice>(new CountingSoftwareDevice()), |
| delegated_rendering).release(); |
| EXPECT_TRUE(CreateHostImpl(DefaultSettings(), |
| scoped_ptr<OutputSurface>(output_surface))); |
| |
| const gfx::Transform external_transform; |
| const gfx::Rect external_viewport; |
| const gfx::Rect external_clip; |
| const bool resourceless_software_draw = true; |
| host_impl_->SetExternalDrawConstraints(external_transform, |
| external_viewport, |
| external_clip, |
| external_viewport, |
| external_transform, |
| resourceless_software_draw); |
| |
| // SolidColorLayerImpl will be drawn. |
| scoped_ptr<SolidColorLayerImpl> root_layer = |
| SolidColorLayerImpl::Create(host_impl_->active_tree(), 1); |
| |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| EXPECT_EQ(1u, frame.will_draw_layers.size()); |
| EXPECT_EQ(host_impl_->active_tree()->root_layer(), frame.will_draw_layers[0]); |
| } |
| |
| class LayerTreeHostImplTestDeferredInitialize : public LayerTreeHostImplTest { |
| protected: |
| void SetUp() override { |
| LayerTreeHostImplTest::SetUp(); |
| |
| set_reduce_memory_result(false); |
| |
| bool delegated_rendering = false; |
| scoped_ptr<FakeOutputSurface> output_surface( |
| FakeOutputSurface::CreateDeferredGL( |
| scoped_ptr<SoftwareOutputDevice>(new CountingSoftwareDevice()), |
| delegated_rendering)); |
| output_surface_ = output_surface.get(); |
| |
| EXPECT_TRUE(CreateHostImpl(DefaultSettings(), output_surface.Pass())); |
| |
| scoped_ptr<SolidColorLayerImpl> root_layer = |
| SolidColorLayerImpl::Create(host_impl_->active_tree(), 1); |
| SetupRootLayerImpl(root_layer.Pass()); |
| |
| onscreen_context_provider_ = TestContextProvider::Create(); |
| } |
| |
| void UpdateRendererCapabilitiesOnImplThread() override { |
| did_update_renderer_capabilities_ = true; |
| } |
| |
| FakeOutputSurface* output_surface_; |
| scoped_refptr<TestContextProvider> onscreen_context_provider_; |
| bool did_update_renderer_capabilities_; |
| }; |
| |
| |
| TEST_F(LayerTreeHostImplTestDeferredInitialize, Success) { |
| // Software draw. |
| DrawFrame(); |
| |
| EXPECT_FALSE(host_impl_->output_surface()->context_provider()); |
| |
| // DeferredInitialize and hardware draw. |
| did_update_renderer_capabilities_ = false; |
| EXPECT_TRUE( |
| output_surface_->InitializeAndSetContext3d(onscreen_context_provider_)); |
| EXPECT_EQ(onscreen_context_provider_.get(), |
| host_impl_->output_surface()->context_provider()); |
| EXPECT_TRUE(did_update_renderer_capabilities_); |
| |
| // Defer intialized GL draw. |
| DrawFrame(); |
| |
| // Revert back to software. |
| did_update_renderer_capabilities_ = false; |
| output_surface_->ReleaseGL(); |
| EXPECT_FALSE(host_impl_->output_surface()->context_provider()); |
| EXPECT_TRUE(did_update_renderer_capabilities_); |
| |
| // Software draw again. |
| DrawFrame(); |
| } |
| |
| TEST_F(LayerTreeHostImplTestDeferredInitialize, Fails) { |
| // Software draw. |
| DrawFrame(); |
| |
| // Fail initialization of the onscreen context before the OutputSurface binds |
| // it to the thread. |
| onscreen_context_provider_->UnboundTestContext3d()->set_context_lost(true); |
| |
| EXPECT_FALSE(host_impl_->output_surface()->context_provider()); |
| |
| // DeferredInitialize fails. |
| did_update_renderer_capabilities_ = false; |
| EXPECT_FALSE( |
| output_surface_->InitializeAndSetContext3d(onscreen_context_provider_)); |
| EXPECT_FALSE(host_impl_->output_surface()->context_provider()); |
| EXPECT_FALSE(did_update_renderer_capabilities_); |
| |
| // Software draw again. |
| DrawFrame(); |
| } |
| |
| // Checks that we have a non-0 default allocation if we pass a context that |
| // doesn't support memory management extensions. |
| TEST_F(LayerTreeHostImplTest, DefaultMemoryAllocation) { |
| LayerTreeSettings settings; |
| host_impl_ = LayerTreeHostImpl::Create(settings, |
| this, |
| &proxy_, |
| &stats_instrumentation_, |
| shared_bitmap_manager_.get(), |
| gpu_memory_buffer_manager_.get(), |
| 0); |
| |
| scoped_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(TestWebGraphicsContext3D::Create())); |
| host_impl_->InitializeRenderer(output_surface.Pass()); |
| EXPECT_LT(0ul, host_impl_->memory_allocation_limit_bytes()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, MemoryPolicy) { |
| ManagedMemoryPolicy policy1( |
| 456, gpu::MemoryAllocation::CUTOFF_ALLOW_EVERYTHING, 1000); |
| int everything_cutoff_value = ManagedMemoryPolicy::PriorityCutoffToValue( |
| gpu::MemoryAllocation::CUTOFF_ALLOW_EVERYTHING); |
| int allow_nice_to_have_cutoff_value = |
| ManagedMemoryPolicy::PriorityCutoffToValue( |
| gpu::MemoryAllocation::CUTOFF_ALLOW_NICE_TO_HAVE); |
| int nothing_cutoff_value = ManagedMemoryPolicy::PriorityCutoffToValue( |
| gpu::MemoryAllocation::CUTOFF_ALLOW_NOTHING); |
| |
| // GPU rasterization should be disabled by default on the tree(s) |
| EXPECT_FALSE(host_impl_->active_tree()->use_gpu_rasterization()); |
| EXPECT_TRUE(host_impl_->pending_tree() == NULL); |
| |
| host_impl_->SetVisible(true); |
| host_impl_->SetMemoryPolicy(policy1); |
| EXPECT_EQ(policy1.bytes_limit_when_visible, current_limit_bytes_); |
| EXPECT_EQ(everything_cutoff_value, current_priority_cutoff_value_); |
| |
| host_impl_->SetVisible(false); |
| EXPECT_EQ(0u, current_limit_bytes_); |
| EXPECT_EQ(nothing_cutoff_value, current_priority_cutoff_value_); |
| |
| host_impl_->SetVisible(true); |
| EXPECT_EQ(policy1.bytes_limit_when_visible, current_limit_bytes_); |
| EXPECT_EQ(everything_cutoff_value, current_priority_cutoff_value_); |
| |
| // Now enable GPU rasterization and test if we get nice to have cutoff, |
| // when visible. |
| LayerTreeSettings settings; |
| settings.gpu_rasterization_enabled = true; |
| host_impl_ = LayerTreeHostImpl::Create( |
| settings, this, &proxy_, &stats_instrumentation_, NULL, NULL, 0); |
| host_impl_->SetUseGpuRasterization(true); |
| host_impl_->SetVisible(true); |
| host_impl_->SetMemoryPolicy(policy1); |
| EXPECT_EQ(policy1.bytes_limit_when_visible, current_limit_bytes_); |
| EXPECT_EQ(allow_nice_to_have_cutoff_value, current_priority_cutoff_value_); |
| |
| host_impl_->SetVisible(false); |
| EXPECT_EQ(0u, current_limit_bytes_); |
| EXPECT_EQ(nothing_cutoff_value, current_priority_cutoff_value_); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, RequireHighResWhenVisible) { |
| ASSERT_TRUE(host_impl_->active_tree()); |
| |
| // RequiresHighResToDraw is set when new output surface is used. |
| EXPECT_TRUE(host_impl_->RequiresHighResToDraw()); |
| |
| host_impl_->ResetRequiresHighResToDraw(); |
| |
| host_impl_->SetVisible(false); |
| EXPECT_FALSE(host_impl_->RequiresHighResToDraw()); |
| host_impl_->SetVisible(true); |
| EXPECT_TRUE(host_impl_->RequiresHighResToDraw()); |
| host_impl_->SetVisible(false); |
| EXPECT_TRUE(host_impl_->RequiresHighResToDraw()); |
| |
| host_impl_->ResetRequiresHighResToDraw(); |
| |
| EXPECT_FALSE(host_impl_->RequiresHighResToDraw()); |
| host_impl_->SetVisible(true); |
| EXPECT_TRUE(host_impl_->RequiresHighResToDraw()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, RequireHighResAfterGpuRasterizationToggles) { |
| ASSERT_TRUE(host_impl_->active_tree()); |
| EXPECT_FALSE(host_impl_->use_gpu_rasterization()); |
| |
| // RequiresHighResToDraw is set when new output surface is used. |
| EXPECT_TRUE(host_impl_->RequiresHighResToDraw()); |
| |
| host_impl_->ResetRequiresHighResToDraw(); |
| |
| host_impl_->SetUseGpuRasterization(false); |
| EXPECT_FALSE(host_impl_->RequiresHighResToDraw()); |
| host_impl_->SetUseGpuRasterization(true); |
| EXPECT_TRUE(host_impl_->RequiresHighResToDraw()); |
| host_impl_->SetUseGpuRasterization(false); |
| EXPECT_TRUE(host_impl_->RequiresHighResToDraw()); |
| |
| host_impl_->ResetRequiresHighResToDraw(); |
| |
| EXPECT_FALSE(host_impl_->RequiresHighResToDraw()); |
| host_impl_->SetUseGpuRasterization(true); |
| EXPECT_TRUE(host_impl_->RequiresHighResToDraw()); |
| } |
| |
| class LayerTreeHostImplTestPrepareTiles : public LayerTreeHostImplTest { |
| public: |
| void SetUp() override { |
| LayerTreeSettings settings; |
| settings.impl_side_painting = true; |
| |
| fake_host_impl_ = new FakeLayerTreeHostImpl( |
| settings, &proxy_, shared_bitmap_manager_.get()); |
| host_impl_.reset(fake_host_impl_); |
| host_impl_->InitializeRenderer(CreateOutputSurface()); |
| host_impl_->SetViewportSize(gfx::Size(10, 10)); |
| } |
| |
| FakeLayerTreeHostImpl* fake_host_impl_; |
| }; |
| |
| TEST_F(LayerTreeHostImplTestPrepareTiles, PrepareTilesWhenInvisible) { |
| fake_host_impl_->DidModifyTilePriorities(); |
| EXPECT_TRUE(fake_host_impl_->prepare_tiles_needed()); |
| fake_host_impl_->SetVisible(false); |
| EXPECT_FALSE(fake_host_impl_->prepare_tiles_needed()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, UIResourceManagement) { |
| scoped_ptr<TestWebGraphicsContext3D> context = |
| TestWebGraphicsContext3D::Create(); |
| TestWebGraphicsContext3D* context3d = context.get(); |
| scoped_ptr<FakeOutputSurface> output_surface = FakeOutputSurface::Create3d(); |
| CreateHostImpl(DefaultSettings(), output_surface.Pass()); |
| |
| EXPECT_EQ(0u, context3d->NumTextures()); |
| |
| UIResourceId ui_resource_id = 1; |
| bool is_opaque = false; |
| UIResourceBitmap bitmap(gfx::Size(1, 1), is_opaque); |
| host_impl_->CreateUIResource(ui_resource_id, bitmap); |
| EXPECT_EQ(1u, context3d->NumTextures()); |
| ResourceProvider::ResourceId id1 = |
| host_impl_->ResourceIdForUIResource(ui_resource_id); |
| EXPECT_NE(0u, id1); |
| |
| // Multiple requests with the same id is allowed. The previous texture is |
| // deleted. |
| host_impl_->CreateUIResource(ui_resource_id, bitmap); |
| EXPECT_EQ(1u, context3d->NumTextures()); |
| ResourceProvider::ResourceId id2 = |
| host_impl_->ResourceIdForUIResource(ui_resource_id); |
| EXPECT_NE(0u, id2); |
| EXPECT_NE(id1, id2); |
| |
| // Deleting invalid UIResourceId is allowed and does not change state. |
| host_impl_->DeleteUIResource(-1); |
| EXPECT_EQ(1u, context3d->NumTextures()); |
| |
| // Should return zero for invalid UIResourceId. Number of textures should |
| // not change. |
| EXPECT_EQ(0u, host_impl_->ResourceIdForUIResource(-1)); |
| EXPECT_EQ(1u, context3d->NumTextures()); |
| |
| host_impl_->DeleteUIResource(ui_resource_id); |
| EXPECT_EQ(0u, host_impl_->ResourceIdForUIResource(ui_resource_id)); |
| EXPECT_EQ(0u, context3d->NumTextures()); |
| |
| // Should not change state for multiple deletion on one UIResourceId |
| host_impl_->DeleteUIResource(ui_resource_id); |
| EXPECT_EQ(0u, context3d->NumTextures()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, CreateETC1UIResource) { |
| scoped_ptr<TestWebGraphicsContext3D> context = |
| TestWebGraphicsContext3D::Create(); |
| TestWebGraphicsContext3D* context3d = context.get(); |
| CreateHostImpl(DefaultSettings(), FakeOutputSurface::Create3d()); |
| |
| EXPECT_EQ(0u, context3d->NumTextures()); |
| |
| gfx::Size size(4, 4); |
| // SkImageInfo has no support for ETC1. The |info| below contains the right |
| // total pixel size for the bitmap but not the right height and width. The |
| // correct width/height are passed directly to UIResourceBitmap. |
| SkImageInfo info = |
| SkImageInfo::Make(4, 2, kAlpha_8_SkColorType, kPremul_SkAlphaType); |
| skia::RefPtr<SkPixelRef> pixel_ref = |
| skia::AdoptRef(SkMallocPixelRef::NewAllocate(info, 0, 0)); |
| pixel_ref->setImmutable(); |
| UIResourceBitmap bitmap(pixel_ref, size); |
| UIResourceId ui_resource_id = 1; |
| host_impl_->CreateUIResource(ui_resource_id, bitmap); |
| EXPECT_EQ(1u, context3d->NumTextures()); |
| ResourceProvider::ResourceId id1 = |
| host_impl_->ResourceIdForUIResource(ui_resource_id); |
| EXPECT_NE(0u, id1); |
| } |
| |
| void ShutdownReleasesContext_Callback(scoped_ptr<CopyOutputResult> result) { |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ShutdownReleasesContext) { |
| scoped_refptr<TestContextProvider> context_provider = |
| TestContextProvider::Create(); |
| |
| CreateHostImpl(DefaultSettings(), |
| FakeOutputSurface::Create3d(context_provider)); |
| |
| SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1)); |
| |
| ScopedPtrVector<CopyOutputRequest> requests; |
| requests.push_back(CopyOutputRequest::CreateRequest( |
| base::Bind(&ShutdownReleasesContext_Callback))); |
| |
| host_impl_->active_tree()->root_layer()->PassCopyRequests(&requests); |
| |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| |
| // The CopyOutputResult's callback has a ref on the ContextProvider and a |
| // texture in a texture mailbox. |
| EXPECT_FALSE(context_provider->HasOneRef()); |
| EXPECT_EQ(1u, context_provider->TestContext3d()->NumTextures()); |
| |
| host_impl_ = nullptr; |
| |
| // The CopyOutputResult's callback was cancelled, the CopyOutputResult |
| // released, and the texture deleted. |
| EXPECT_TRUE(context_provider->HasOneRef()); |
| EXPECT_EQ(0u, context_provider->TestContext3d()->NumTextures()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, TouchFlingShouldNotBubble) { |
| // When flinging via touch, only the child should scroll (we should not |
| // bubble). |
| gfx::Size surface_size(10, 10); |
| gfx::Size content_size(20, 20); |
| scoped_ptr<LayerImpl> root_clip = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| root_clip->SetHasRenderSurface(true); |
| |
| scoped_ptr<LayerImpl> root = |
| CreateScrollableLayer(1, content_size, root_clip.get()); |
| root->SetIsContainerForFixedPositionLayers(true); |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(2, content_size, root_clip.get()); |
| |
| root->AddChild(child.Pass()); |
| int root_id = root->id(); |
| root_clip->AddChild(root.Pass()); |
| |
| host_impl_->SetViewportSize(surface_size); |
| host_impl_->active_tree()->SetRootLayer(root_clip.Pass()); |
| host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 3, 1, |
| Layer::INVALID_ID); |
| host_impl_->active_tree()->DidBecomeActive(); |
| DrawFrame(); |
| { |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), |
| InputHandler::Gesture)); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->FlingScrollBegin()); |
| |
| gfx::Vector2d scroll_delta(0, 100); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| |
| host_impl_->ScrollEnd(); |
| |
| scoped_ptr<ScrollAndScaleSet> scroll_info = |
| host_impl_->ProcessScrollDeltas(); |
| |
| // Only the child should have scrolled. |
| ASSERT_EQ(1u, scroll_info->scrolls.size()); |
| ExpectNone(*scroll_info.get(), root_id); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, TouchFlingShouldLockToFirstScrolledLayer) { |
| // Scroll a child layer beyond its maximum scroll range and make sure the |
| // the scroll doesn't bubble up to the parent layer. |
| gfx::Size surface_size(10, 10); |
| scoped_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1); |
| root->SetHasRenderSurface(true); |
| scoped_ptr<LayerImpl> root_scrolling = |
| CreateScrollableLayer(2, surface_size, root.get()); |
| |
| scoped_ptr<LayerImpl> grand_child = |
| CreateScrollableLayer(4, surface_size, root.get()); |
| grand_child->PushScrollOffsetFromMainThread(gfx::ScrollOffset(0, 2)); |
| |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(3, surface_size, root.get()); |
| child->PushScrollOffsetFromMainThread(gfx::ScrollOffset(0, 4)); |
| child->AddChild(grand_child.Pass()); |
| |
| root_scrolling->AddChild(child.Pass()); |
| root->AddChild(root_scrolling.Pass()); |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| host_impl_->active_tree()->DidBecomeActive(); |
| host_impl_->SetViewportSize(surface_size); |
| DrawFrame(); |
| { |
| scoped_ptr<ScrollAndScaleSet> scroll_info; |
| LayerImpl* child = |
| host_impl_->active_tree()->root_layer()->children()[0]->children()[0]; |
| LayerImpl* grand_child = child->children()[0]; |
| |
| gfx::Vector2d scroll_delta(0, -2); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), scroll_delta).did_scroll); |
| |
| // The grand child should have scrolled up to its limit. |
| scroll_info = host_impl_->ProcessScrollDeltas(); |
| ASSERT_EQ(1u, scroll_info->scrolls.size()); |
| ExpectContains(*scroll_info, grand_child->id(), scroll_delta); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child); |
| |
| // The child should have received the bubbled delta, but the locked |
| // scrolling layer should remain set as the grand child. |
| EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), scroll_delta).did_scroll); |
| scroll_info = host_impl_->ProcessScrollDeltas(); |
| ASSERT_EQ(2u, scroll_info->scrolls.size()); |
| ExpectContains(*scroll_info, grand_child->id(), scroll_delta); |
| ExpectContains(*scroll_info, child->id(), scroll_delta); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child); |
| |
| // The first |ScrollBy| after the fling should re-lock the scrolling |
| // layer to the first layer that scrolled, which is the child. |
| EXPECT_EQ(InputHandler::ScrollStarted, host_impl_->FlingScrollBegin()); |
| EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), scroll_delta).did_scroll); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child); |
| |
| // The child should have scrolled up to its limit. |
| scroll_info = host_impl_->ProcessScrollDeltas(); |
| ASSERT_EQ(2u, scroll_info->scrolls.size()); |
| ExpectContains(*scroll_info, grand_child->id(), scroll_delta); |
| ExpectContains(*scroll_info, child->id(), scroll_delta + scroll_delta); |
| |
| // As the locked layer is at it's limit, no further scrolling can occur. |
| EXPECT_FALSE(host_impl_->ScrollBy(gfx::Point(), scroll_delta).did_scroll); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child); |
| host_impl_->ScrollEnd(); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, WheelFlingShouldBubble) { |
| // When flinging via wheel, the root should eventually scroll (we should |
| // bubble). |
| gfx::Size surface_size(10, 10); |
| gfx::Size content_size(20, 20); |
| scoped_ptr<LayerImpl> root_clip = |
| LayerImpl::Create(host_impl_->active_tree(), 3); |
| root_clip->SetHasRenderSurface(true); |
| scoped_ptr<LayerImpl> root_scroll = |
| CreateScrollableLayer(1, content_size, root_clip.get()); |
| int root_scroll_id = root_scroll->id(); |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(2, content_size, root_clip.get()); |
| |
| root_scroll->AddChild(child.Pass()); |
| root_clip->AddChild(root_scroll.Pass()); |
| |
| host_impl_->SetViewportSize(surface_size); |
| host_impl_->active_tree()->SetRootLayer(root_clip.Pass()); |
| host_impl_->active_tree()->DidBecomeActive(); |
| DrawFrame(); |
| { |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->FlingScrollBegin()); |
| |
| gfx::Vector2d scroll_delta(0, 100); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| |
| host_impl_->ScrollEnd(); |
| |
| scoped_ptr<ScrollAndScaleSet> scroll_info = |
| host_impl_->ProcessScrollDeltas(); |
| |
| // The root should have scrolled. |
| ASSERT_EQ(2u, scroll_info->scrolls.size()); |
| ExpectContains(*scroll_info.get(), root_scroll_id, gfx::Vector2d(0, 10)); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollUnknownNotOnAncestorChain) { |
| // If we ray cast a scroller that is not on the first layer's ancestor chain, |
| // we should return ScrollUnknown. |
| gfx::Size content_size(100, 100); |
| SetupScrollAndContentsLayers(content_size); |
| |
| int scroll_layer_id = 2; |
| LayerImpl* scroll_layer = |
| host_impl_->active_tree()->LayerById(scroll_layer_id); |
| scroll_layer->SetDrawsContent(true); |
| |
| int page_scale_layer_id = 5; |
| LayerImpl* page_scale_layer = |
| host_impl_->active_tree()->LayerById(page_scale_layer_id); |
| |
| int occluder_layer_id = 6; |
| scoped_ptr<LayerImpl> occluder_layer = |
| LayerImpl::Create(host_impl_->active_tree(), occluder_layer_id); |
| occluder_layer->SetDrawsContent(true); |
| occluder_layer->SetBounds(content_size); |
| occluder_layer->SetContentBounds(content_size); |
| occluder_layer->SetPosition(gfx::PointF()); |
| |
| // The parent of the occluder is *above* the scroller. |
| page_scale_layer->AddChild(occluder_layer.Pass()); |
| |
| DrawFrame(); |
| |
| EXPECT_EQ(InputHandler::ScrollUnknown, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollUnknownScrollAncestorMismatch) { |
| // If we ray cast a scroller this is on the first layer's ancestor chain, but |
| // is not the first scroller we encounter when walking up from the layer, we |
| // should also return ScrollUnknown. |
| gfx::Size content_size(100, 100); |
| SetupScrollAndContentsLayers(content_size); |
| |
| int scroll_layer_id = 2; |
| LayerImpl* scroll_layer = |
| host_impl_->active_tree()->LayerById(scroll_layer_id); |
| scroll_layer->SetDrawsContent(true); |
| |
| int occluder_layer_id = 6; |
| scoped_ptr<LayerImpl> occluder_layer = |
| LayerImpl::Create(host_impl_->active_tree(), occluder_layer_id); |
| occluder_layer->SetDrawsContent(true); |
| occluder_layer->SetBounds(content_size); |
| occluder_layer->SetContentBounds(content_size); |
| occluder_layer->SetPosition(gfx::PointF(-10.f, -10.f)); |
| |
| int child_scroll_clip_layer_id = 7; |
| scoped_ptr<LayerImpl> child_scroll_clip = |
| LayerImpl::Create(host_impl_->active_tree(), child_scroll_clip_layer_id); |
| |
| int child_scroll_layer_id = 8; |
| scoped_ptr<LayerImpl> child_scroll = CreateScrollableLayer( |
| child_scroll_layer_id, content_size, child_scroll_clip.get()); |
| |
| child_scroll->SetPosition(gfx::PointF(10.f, 10.f)); |
| |
| child_scroll->AddChild(occluder_layer.Pass()); |
| scroll_layer->AddChild(child_scroll.Pass()); |
| |
| DrawFrame(); |
| |
| EXPECT_EQ(InputHandler::ScrollUnknown, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollInvisibleScroller) { |
| gfx::Size content_size(100, 100); |
| SetupScrollAndContentsLayers(content_size); |
| |
| LayerImpl* root = host_impl_->active_tree()->LayerById(1); |
| |
| int scroll_layer_id = 2; |
| LayerImpl* scroll_layer = |
| host_impl_->active_tree()->LayerById(scroll_layer_id); |
| |
| int child_scroll_layer_id = 7; |
| scoped_ptr<LayerImpl> child_scroll = |
| CreateScrollableLayer(child_scroll_layer_id, content_size, root); |
| child_scroll->SetDrawsContent(false); |
| |
| scroll_layer->AddChild(child_scroll.Pass()); |
| |
| DrawFrame(); |
| |
| // We should not have scrolled |child_scroll| even though we technically "hit" |
| // it. The reason for this is that if the scrolling the scroll would not move |
| // any layer that is a drawn RSLL member, then we can ignore the hit. |
| // |
| // Why ScrollStarted? In this case, it's because we've bubbled out and started |
| // overscrolling the inner viewport. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| |
| EXPECT_EQ(2, host_impl_->CurrentlyScrollingLayer()->id()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollInvisibleScrollerWithVisibleScrollChild) { |
| // This test case is very similar to the one above with one key difference: |
| // the invisible scroller has a scroll child that is indeed draw contents. |
| // If we attempt to initiate a gesture scroll off of the visible scroll child |
| // we should still start the scroll child. |
| gfx::Size content_size(100, 100); |
| SetupScrollAndContentsLayers(content_size); |
| |
| LayerImpl* root = host_impl_->active_tree()->LayerById(1); |
| |
| int scroll_layer_id = 2; |
| LayerImpl* scroll_layer = |
| host_impl_->active_tree()->LayerById(scroll_layer_id); |
| |
| int scroll_child_id = 6; |
| scoped_ptr<LayerImpl> scroll_child = |
| LayerImpl::Create(host_impl_->active_tree(), scroll_child_id); |
| scroll_child->SetDrawsContent(true); |
| scroll_child->SetBounds(content_size); |
| scroll_child->SetContentBounds(content_size); |
| // Move the scroll child so it's not hit by our test point. |
| scroll_child->SetPosition(gfx::PointF(10.f, 10.f)); |
| |
| int invisible_scroll_layer_id = 7; |
| scoped_ptr<LayerImpl> invisible_scroll = |
| CreateScrollableLayer(invisible_scroll_layer_id, content_size, root); |
| invisible_scroll->SetDrawsContent(false); |
| |
| int container_id = 8; |
| scoped_ptr<LayerImpl> container = |
| LayerImpl::Create(host_impl_->active_tree(), container_id); |
| |
| scoped_ptr<std::set<LayerImpl*>> scroll_children(new std::set<LayerImpl*>); |
| scroll_children->insert(scroll_child.get()); |
| invisible_scroll->SetScrollChildren(scroll_children.release()); |
| |
| scroll_child->SetScrollParent(invisible_scroll.get()); |
| |
| container->AddChild(invisible_scroll.Pass()); |
| container->AddChild(scroll_child.Pass()); |
| |
| scroll_layer->AddChild(container.Pass()); |
| |
| DrawFrame(); |
| |
| // We should not have scrolled |child_scroll| even though we technically "hit" |
| // it. The reason for this is that if the scrolling the scroll would not move |
| // any layer that is a drawn RSLL member, then we can ignore the hit. |
| // |
| // Why ScrollStarted? In this case, it's because we've bubbled out and started |
| // overscrolling the inner viewport. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| |
| EXPECT_EQ(7, host_impl_->CurrentlyScrollingLayer()->id()); |
| } |
| |
| // Make sure LatencyInfo carried by LatencyInfoSwapPromise are passed |
| // to CompositorFrameMetadata after SwapBuffers(); |
| TEST_F(LayerTreeHostImplTest, LatencyInfoPassedToCompositorFrameMetadata) { |
| scoped_ptr<SolidColorLayerImpl> root = |
| SolidColorLayerImpl::Create(host_impl_->active_tree(), 1); |
| root->SetPosition(gfx::PointF()); |
| root->SetBounds(gfx::Size(10, 10)); |
| root->SetContentBounds(gfx::Size(10, 10)); |
| root->SetDrawsContent(true); |
| root->SetHasRenderSurface(true); |
| |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| |
| FakeOutputSurface* fake_output_surface = |
| static_cast<FakeOutputSurface*>(host_impl_->output_surface()); |
| |
| const std::vector<ui::LatencyInfo>& metadata_latency_before = |
| fake_output_surface->last_sent_frame().metadata.latency_info; |
| EXPECT_TRUE(metadata_latency_before.empty()); |
| |
| ui::LatencyInfo latency_info; |
| latency_info.AddLatencyNumber( |
| ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 0, 0); |
| scoped_ptr<SwapPromise> swap_promise( |
| new LatencyInfoSwapPromise(latency_info)); |
| host_impl_->active_tree()->QueueSwapPromise(swap_promise.Pass()); |
| host_impl_->SetNeedsRedraw(); |
| |
| gfx::Rect full_frame_damage(host_impl_->DrawViewportSize()); |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| EXPECT_TRUE(host_impl_->SwapBuffers(frame)); |
| |
| const std::vector<ui::LatencyInfo>& metadata_latency_after = |
| fake_output_surface->last_sent_frame().metadata.latency_info; |
| EXPECT_EQ(1u, metadata_latency_after.size()); |
| EXPECT_TRUE(metadata_latency_after[0].FindLatency( |
| ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 0, NULL)); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToCompositorFrameMetadata) { |
| int root_layer_id = 1; |
| scoped_ptr<SolidColorLayerImpl> root = |
| SolidColorLayerImpl::Create(host_impl_->active_tree(), root_layer_id); |
| root->SetPosition(gfx::PointF()); |
| root->SetBounds(gfx::Size(10, 10)); |
| root->SetContentBounds(gfx::Size(10, 10)); |
| root->SetDrawsContent(true); |
| root->SetHasRenderSurface(true); |
| |
| host_impl_->active_tree()->SetRootLayer(root.Pass()); |
| |
| // Ensure the default frame selection bounds are empty. |
| FakeOutputSurface* fake_output_surface = |
| static_cast<FakeOutputSurface*>(host_impl_->output_surface()); |
| const ViewportSelectionBound& selection_start_before = |
| fake_output_surface->last_sent_frame().metadata.selection_start; |
| const ViewportSelectionBound& selection_end_before = |
| fake_output_surface->last_sent_frame().metadata.selection_end; |
| EXPECT_EQ(ViewportSelectionBound(), selection_start_before); |
| EXPECT_EQ(ViewportSelectionBound(), selection_end_before); |
| |
| // Plumb the layer-local selection bounds. |
| gfx::PointF selection_top(5, 0); |
| gfx::PointF selection_bottom(5, 5); |
| LayerSelectionBound start, end; |
| start.type = SELECTION_BOUND_CENTER; |
| start.layer_id = root_layer_id; |
| start.edge_bottom = selection_bottom; |
| start.edge_top = selection_top; |
| end = start; |
| host_impl_->active_tree()->RegisterSelection(start, end); |
| |
| // Trigger a draw-swap sequence. |
| host_impl_->SetNeedsRedraw(); |
| |
| gfx::Rect full_frame_damage(host_impl_->DrawViewportSize()); |
| LayerTreeHostImpl::FrameData frame; |
| EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame)); |
| host_impl_->DrawLayers(&frame, gfx::FrameTime::Now()); |
| host_impl_->DidDrawAllLayers(frame); |
| EXPECT_TRUE(host_impl_->SwapBuffers(frame)); |
| |
| // Ensure the selection bounds have propagated to the frame metadata. |
| const ViewportSelectionBound& selection_start_after = |
| fake_output_surface->last_sent_frame().metadata.selection_start; |
| const ViewportSelectionBound& selection_end_after = |
| fake_output_surface->last_sent_frame().metadata.selection_end; |
| EXPECT_EQ(start.type, selection_start_after.type); |
| EXPECT_EQ(end.type, selection_end_after.type); |
| EXPECT_EQ(selection_bottom, selection_start_after.edge_bottom); |
| EXPECT_EQ(selection_top, selection_start_after.edge_top); |
| EXPECT_TRUE(selection_start_after.visible); |
| EXPECT_TRUE(selection_start_after.visible); |
| } |
| |
| class SimpleSwapPromiseMonitor : public SwapPromiseMonitor { |
| public: |
| SimpleSwapPromiseMonitor(LayerTreeHost* layer_tree_host, |
| LayerTreeHostImpl* layer_tree_host_impl, |
| int* set_needs_commit_count, |
| int* set_needs_redraw_count, |
| int* forward_to_main_count) |
| : SwapPromiseMonitor(layer_tree_host, layer_tree_host_impl), |
| set_needs_commit_count_(set_needs_commit_count), |
| set_needs_redraw_count_(set_needs_redraw_count), |
| forward_to_main_count_(forward_to_main_count) {} |
| |
| ~SimpleSwapPromiseMonitor() override {} |
| |
| void OnSetNeedsCommitOnMain() override { (*set_needs_commit_count_)++; } |
| |
| void OnSetNeedsRedrawOnImpl() override { (*set_needs_redraw_count_)++; } |
| |
| void OnForwardScrollUpdateToMainThreadOnImpl() override { |
| (*forward_to_main_count_)++; |
| } |
| |
| private: |
| int* set_needs_commit_count_; |
| int* set_needs_redraw_count_; |
| int* forward_to_main_count_; |
| }; |
| |
| TEST_F(LayerTreeHostImplTest, SimpleSwapPromiseMonitor) { |
| int set_needs_commit_count = 0; |
| int set_needs_redraw_count = 0; |
| int forward_to_main_count = 0; |
| |
| { |
| scoped_ptr<SimpleSwapPromiseMonitor> swap_promise_monitor( |
| new SimpleSwapPromiseMonitor(NULL, |
| host_impl_.get(), |
| &set_needs_commit_count, |
| &set_needs_redraw_count, |
| &forward_to_main_count)); |
| host_impl_->SetNeedsRedraw(); |
| EXPECT_EQ(0, set_needs_commit_count); |
| EXPECT_EQ(1, set_needs_redraw_count); |
| EXPECT_EQ(0, forward_to_main_count); |
| } |
| |
| // Now the monitor is destroyed, SetNeedsRedraw() is no longer being |
| // monitored. |
| host_impl_->SetNeedsRedraw(); |
| EXPECT_EQ(0, set_needs_commit_count); |
| EXPECT_EQ(1, set_needs_redraw_count); |
| EXPECT_EQ(0, forward_to_main_count); |
| |
| { |
| scoped_ptr<SimpleSwapPromiseMonitor> swap_promise_monitor( |
| new SimpleSwapPromiseMonitor(NULL, |
| host_impl_.get(), |
| &set_needs_commit_count, |
| &set_needs_redraw_count, |
| &forward_to_main_count)); |
| host_impl_->SetNeedsRedrawRect(gfx::Rect(10, 10)); |
| EXPECT_EQ(0, set_needs_commit_count); |
| EXPECT_EQ(2, set_needs_redraw_count); |
| EXPECT_EQ(0, forward_to_main_count); |
| } |
| |
| { |
| scoped_ptr<SimpleSwapPromiseMonitor> swap_promise_monitor( |
| new SimpleSwapPromiseMonitor(NULL, |
| host_impl_.get(), |
| &set_needs_commit_count, |
| &set_needs_redraw_count, |
| &forward_to_main_count)); |
| // Empty damage rect won't signal the monitor. |
| host_impl_->SetNeedsRedrawRect(gfx::Rect()); |
| EXPECT_EQ(0, set_needs_commit_count); |
| EXPECT_EQ(2, set_needs_redraw_count); |
| EXPECT_EQ(0, forward_to_main_count); |
| } |
| |
| { |
| set_needs_commit_count = 0; |
| set_needs_redraw_count = 0; |
| forward_to_main_count = 0; |
| scoped_ptr<SimpleSwapPromiseMonitor> swap_promise_monitor( |
| new SimpleSwapPromiseMonitor(NULL, |
| host_impl_.get(), |
| &set_needs_commit_count, |
| &set_needs_redraw_count, |
| &forward_to_main_count)); |
| LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| |
| // Scrolling normally should not trigger any forwarding. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10)).did_scroll); |
| host_impl_->ScrollEnd(); |
| |
| EXPECT_EQ(0, set_needs_commit_count); |
| EXPECT_EQ(1, set_needs_redraw_count); |
| EXPECT_EQ(0, forward_to_main_count); |
| |
| // Scrolling with a scroll handler should defer the swap to the main |
| // thread. |
| scroll_layer->SetHaveScrollEventHandlers(true); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, 10)).did_scroll); |
| host_impl_->ScrollEnd(); |
| |
| EXPECT_EQ(0, set_needs_commit_count); |
| EXPECT_EQ(2, set_needs_redraw_count); |
| EXPECT_EQ(1, forward_to_main_count); |
| } |
| } |
| |
| class LayerTreeHostImplWithTopControlsTest : public LayerTreeHostImplTest { |
| public: |
| void SetUp() override { |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.calculate_top_controls_position = true; |
| CreateHostImpl(settings, CreateOutputSurface()); |
| host_impl_->active_tree()->set_top_controls_height(top_controls_height_); |
| host_impl_->active_tree()->set_top_controls_delta(top_controls_height_); |
| host_impl_->top_controls_manager()->SetTopControlsHeight( |
| top_controls_height_); |
| } |
| |
| protected: |
| static const int top_controls_height_; |
| }; |
| |
| const int LayerTreeHostImplWithTopControlsTest::top_controls_height_ = 50; |
| |
| TEST_F(LayerTreeHostImplWithTopControlsTest, NoIdleAnimations) { |
| SetupScrollAndContentsLayers(gfx::Size(100, 100)) |
| ->PushScrollOffsetFromMainThread(gfx::ScrollOffset(0, 10)); |
| host_impl_->Animate(base::TimeTicks()); |
| EXPECT_FALSE(did_request_redraw_); |
| } |
| |
| TEST_F(LayerTreeHostImplWithTopControlsTest, TopControlsHeightIsCommitted) { |
| SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| EXPECT_FALSE(did_request_redraw_); |
| host_impl_->CreatePendingTree(); |
| host_impl_->sync_tree()->set_top_controls_height(100); |
| host_impl_->ActivateSyncTree(); |
| EXPECT_EQ(100, host_impl_->top_controls_manager()->top_controls_height()); |
| } |
| |
| TEST_F(LayerTreeHostImplWithTopControlsTest, |
| TopControlsStayFullyVisibleOnHeightChange) { |
| SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| EXPECT_EQ(0.f, host_impl_->ControlsTopOffset()); |
| |
| host_impl_->CreatePendingTree(); |
| host_impl_->sync_tree()->set_top_controls_height(0); |
| host_impl_->ActivateSyncTree(); |
| EXPECT_EQ(0.f, host_impl_->ControlsTopOffset()); |
| |
| host_impl_->CreatePendingTree(); |
| host_impl_->sync_tree()->set_top_controls_height(50); |
| host_impl_->ActivateSyncTree(); |
| EXPECT_EQ(0.f, host_impl_->ControlsTopOffset()); |
| } |
| |
| TEST_F(LayerTreeHostImplWithTopControlsTest, TopControlsAnimationScheduling) { |
| SetupScrollAndContentsLayers(gfx::Size(100, 100)) |
| ->PushScrollOffsetFromMainThread(gfx::ScrollOffset(0, 10)); |
| host_impl_->DidChangeTopControlsPosition(); |
| EXPECT_TRUE(did_request_animate_); |
| EXPECT_TRUE(did_request_redraw_); |
| } |
| |
| TEST_F(LayerTreeHostImplWithTopControlsTest, ScrollHandledByTopControls) { |
| LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200)); |
| host_impl_->SetViewportSize(gfx::Size(100, 100)); |
| host_impl_->top_controls_manager()->UpdateTopControlsState( |
| BOTH, SHOWN, false); |
| DrawFrame(); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| EXPECT_EQ(0, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| // Scroll just the top controls and verify that the scroll succeeds. |
| const float residue = 10; |
| float offset = top_controls_height_ - residue; |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, offset)).did_scroll); |
| EXPECT_EQ(-offset, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| // Scroll across the boundary |
| const float content_scroll = 20; |
| offset = residue + content_scroll; |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, offset)).did_scroll); |
| EXPECT_EQ(-top_controls_height_, |
| host_impl_->top_controls_manager()->ControlsTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF(0, content_scroll).ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| // Now scroll back to the top of the content |
| offset = -content_scroll; |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, offset)).did_scroll); |
| EXPECT_EQ(-top_controls_height_, |
| host_impl_->top_controls_manager()->ControlsTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| // And scroll the top controls completely into view |
| offset = -top_controls_height_; |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, offset)).did_scroll); |
| EXPECT_EQ(0, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| // And attempt to scroll past the end |
| EXPECT_FALSE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, offset)).did_scroll); |
| EXPECT_EQ(0, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| host_impl_->ScrollEnd(); |
| } |
| |
| TEST_F(LayerTreeHostImplWithTopControlsTest, TopControlsAnimationAtOrigin) { |
| LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200)); |
| host_impl_->SetViewportSize(gfx::Size(100, 200)); |
| host_impl_->top_controls_manager()->UpdateTopControlsState( |
| BOTH, SHOWN, false); |
| DrawFrame(); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| EXPECT_EQ(0, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| // Scroll the top controls partially. |
| const float residue = 35; |
| float offset = top_controls_height_ - residue; |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, offset)).did_scroll); |
| EXPECT_EQ(-offset, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| did_request_redraw_ = false; |
| did_request_animate_ = false; |
| did_request_commit_ = false; |
| |
| // End the scroll while the controls are still offset from their limit. |
| host_impl_->ScrollEnd(); |
| ASSERT_TRUE(host_impl_->top_controls_manager()->animation()); |
| EXPECT_TRUE(did_request_animate_); |
| EXPECT_TRUE(did_request_redraw_); |
| EXPECT_FALSE(did_request_commit_); |
| |
| // The top controls should properly animate until finished, despite the scroll |
| // offset being at the origin. |
| base::TimeTicks animation_time = gfx::FrameTime::Now(); |
| while (did_request_animate_) { |
| did_request_redraw_ = false; |
| did_request_animate_ = false; |
| did_request_commit_ = false; |
| |
| float old_offset = |
| host_impl_->top_controls_manager()->ControlsTopOffset(); |
| |
| animation_time += base::TimeDelta::FromMilliseconds(5); |
| host_impl_->Animate(animation_time); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| float new_offset = |
| host_impl_->top_controls_manager()->ControlsTopOffset(); |
| |
| // No commit is needed as the controls are animating the content offset, |
| // not the scroll offset. |
| EXPECT_FALSE(did_request_commit_); |
| |
| if (new_offset != old_offset) |
| EXPECT_TRUE(did_request_redraw_); |
| |
| if (new_offset != 0) { |
| EXPECT_TRUE(host_impl_->top_controls_manager()->animation()); |
| EXPECT_TRUE(did_request_animate_); |
| } |
| } |
| EXPECT_FALSE(host_impl_->top_controls_manager()->animation()); |
| } |
| |
| TEST_F(LayerTreeHostImplWithTopControlsTest, TopControlsAnimationAfterScroll) { |
| LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200)); |
| host_impl_->SetViewportSize(gfx::Size(100, 100)); |
| host_impl_->top_controls_manager()->UpdateTopControlsState( |
| BOTH, SHOWN, false); |
| float initial_scroll_offset = 50; |
| scroll_layer->PushScrollOffsetFromMainThread( |
| gfx::ScrollOffset(0, initial_scroll_offset)); |
| DrawFrame(); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| EXPECT_EQ(0, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF(0, initial_scroll_offset).ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| // Scroll the top controls partially. |
| const float residue = 15; |
| float offset = top_controls_height_ - residue; |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, offset)).did_scroll); |
| EXPECT_EQ(-offset, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF(0, initial_scroll_offset).ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| did_request_redraw_ = false; |
| did_request_animate_ = false; |
| did_request_commit_ = false; |
| |
| // End the scroll while the controls are still offset from the limit. |
| host_impl_->ScrollEnd(); |
| ASSERT_TRUE(host_impl_->top_controls_manager()->animation()); |
| EXPECT_TRUE(did_request_animate_); |
| EXPECT_TRUE(did_request_redraw_); |
| EXPECT_FALSE(did_request_commit_); |
| |
| // Animate the top controls to the limit. |
| base::TimeTicks animation_time = gfx::FrameTime::Now(); |
| while (did_request_animate_) { |
| did_request_redraw_ = false; |
| did_request_animate_ = false; |
| did_request_commit_ = false; |
| |
| float old_offset = |
| host_impl_->top_controls_manager()->ControlsTopOffset(); |
| |
| animation_time += base::TimeDelta::FromMilliseconds(5); |
| host_impl_->Animate(animation_time); |
| |
| float new_offset = |
| host_impl_->top_controls_manager()->ControlsTopOffset(); |
| |
| if (new_offset != old_offset) { |
| EXPECT_TRUE(did_request_redraw_); |
| EXPECT_TRUE(did_request_commit_); |
| } |
| } |
| EXPECT_FALSE(host_impl_->top_controls_manager()->animation()); |
| } |
| |
| TEST_F(LayerTreeHostImplWithTopControlsTest, |
| TopControlsScrollDeltaInOverScroll) { |
| // test varifies that the overscroll delta should not have accumulated in |
| // the top controls if we do a hide and show without releasing finger. |
| |
| LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200)); |
| host_impl_->SetViewportSize(gfx::Size(100, 100)); |
| host_impl_->top_controls_manager()->UpdateTopControlsState(BOTH, SHOWN, |
| false); |
| DrawFrame(); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| EXPECT_EQ(0, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| |
| float offset = 50; |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, offset)).did_scroll); |
| EXPECT_EQ(-offset, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF().ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, offset)).did_scroll); |
| EXPECT_EQ(gfx::Vector2dF(0, offset).ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, offset)).did_scroll); |
| |
| // Should have fully scrolled |
| EXPECT_EQ(gfx::Vector2dF(0, scroll_layer->MaxScrollOffset().y()).ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| float overscrollamount = 10; |
| |
| // Overscroll the content |
| EXPECT_FALSE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, overscrollamount)) |
| .did_scroll); |
| EXPECT_EQ(gfx::Vector2dF(0, 2 * offset).ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| EXPECT_EQ(gfx::Vector2dF(0, overscrollamount).ToString(), |
| host_impl_->accumulated_root_overscroll().ToString()); |
| |
| EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -2 * offset)) |
| .did_scroll); |
| EXPECT_EQ(gfx::Vector2dF(0, 0).ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| EXPECT_EQ(-offset, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| |
| EXPECT_TRUE( |
| host_impl_->ScrollBy(gfx::Point(), gfx::Vector2d(0, -offset)).did_scroll); |
| EXPECT_EQ(gfx::Vector2dF(0, 0).ToString(), |
| scroll_layer->CurrentScrollOffset().ToString()); |
| |
| // Top controls should be fully visible |
| EXPECT_EQ(0, host_impl_->top_controls_manager()->ControlsTopOffset()); |
| |
| host_impl_->ScrollEnd(); |
| } |
| |
| class LayerTreeHostImplVirtualViewportTest : public LayerTreeHostImplTest { |
| public: |
| void SetupVirtualViewportLayers(const gfx::Size& content_size, |
| const gfx::Size& outer_viewport, |
| const gfx::Size& inner_viewport) { |
| LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); |
| const int kOuterViewportClipLayerId = 6; |
| const int kOuterViewportScrollLayerId = 7; |
| const int kInnerViewportScrollLayerId = 2; |
| const int kInnerViewportClipLayerId = 4; |
| const int kPageScaleLayerId = 5; |
| |
| scoped_ptr<LayerImpl> inner_scroll = |
| LayerImpl::Create(layer_tree_impl, kInnerViewportScrollLayerId); |
| inner_scroll->SetIsContainerForFixedPositionLayers(true); |
| inner_scroll->PushScrollOffsetFromMainThread(gfx::ScrollOffset()); |
| |
| scoped_ptr<LayerImpl> inner_clip = |
| LayerImpl::Create(layer_tree_impl, kInnerViewportClipLayerId); |
| inner_clip->SetBounds(inner_viewport); |
| |
| scoped_ptr<LayerImpl> page_scale = |
| LayerImpl::Create(layer_tree_impl, kPageScaleLayerId); |
| |
| inner_scroll->SetScrollClipLayer(inner_clip->id()); |
| inner_scroll->SetBounds(outer_viewport); |
| inner_scroll->SetContentBounds(outer_viewport); |
| inner_scroll->SetPosition(gfx::PointF()); |
| |
| scoped_ptr<LayerImpl> outer_clip = |
| LayerImpl::Create(layer_tree_impl, kOuterViewportClipLayerId); |
| outer_clip->SetBounds(outer_viewport); |
| outer_clip->SetIsContainerForFixedPositionLayers(true); |
| |
| scoped_ptr<LayerImpl> outer_scroll = |
| LayerImpl::Create(layer_tree_impl, kOuterViewportScrollLayerId); |
| outer_scroll->SetScrollClipLayer(outer_clip->id()); |
| outer_scroll->PushScrollOffsetFromMainThread(gfx::ScrollOffset()); |
| outer_scroll->SetBounds(content_size); |
| outer_scroll->SetContentBounds(content_size); |
| outer_scroll->SetPosition(gfx::PointF()); |
| |
| scoped_ptr<LayerImpl> contents = |
| LayerImpl::Create(layer_tree_impl, 8); |
| contents->SetDrawsContent(true); |
| contents->SetBounds(content_size); |
| contents->SetContentBounds(content_size); |
| contents->SetPosition(gfx::PointF()); |
| |
| outer_scroll->AddChild(contents.Pass()); |
| outer_clip->AddChild(outer_scroll.Pass()); |
| inner_scroll->AddChild(outer_clip.Pass()); |
| page_scale->AddChild(inner_scroll.Pass()); |
| inner_clip->AddChild(page_scale.Pass()); |
| |
| inner_clip->SetHasRenderSurface(true); |
| layer_tree_impl->SetRootLayer(inner_clip.Pass()); |
| layer_tree_impl->SetViewportLayersFromIds( |
| Layer::INVALID_ID, kPageScaleLayerId, kInnerViewportScrollLayerId, |
| kOuterViewportScrollLayerId); |
| |
| host_impl_->active_tree()->DidBecomeActive(); |
| } |
| }; |
| |
| TEST_F(LayerTreeHostImplVirtualViewportTest, FlingScrollBubblesToInner) { |
| gfx::Size content_size = gfx::Size(100, 160); |
| gfx::Size outer_viewport = gfx::Size(50, 80); |
| gfx::Size inner_viewport = gfx::Size(25, 40); |
| |
| SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport); |
| |
| LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer(); |
| LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer(); |
| DrawFrame(); |
| { |
| gfx::Vector2dF inner_expected; |
| gfx::Vector2dF outer_expected; |
| EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset()); |
| EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset()); |
| |
| // Make sure the fling goes to the outer viewport first |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| EXPECT_EQ(InputHandler::ScrollStarted, host_impl_->FlingScrollBegin()); |
| |
| gfx::Vector2d scroll_delta(inner_viewport.width(), inner_viewport.height()); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| outer_expected += gfx::Vector2dF(scroll_delta.x(), scroll_delta.y()); |
| |
| host_impl_->ScrollEnd(); |
| |
| EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset()); |
| EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset()); |
| |
| // Fling past the outer viewport boundry, make sure inner viewport scrolls. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| EXPECT_EQ(InputHandler::ScrollStarted, host_impl_->FlingScrollBegin()); |
| |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| outer_expected += gfx::Vector2dF(scroll_delta.x(), scroll_delta.y()); |
| |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| inner_expected += gfx::Vector2dF(scroll_delta.x(), scroll_delta.y()); |
| |
| host_impl_->ScrollEnd(); |
| |
| EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset()); |
| EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset()); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplVirtualViewportTest, |
| DiagonalScrollBubblesPerfectlyToInner) { |
| gfx::Size content_size = gfx::Size(100, 160); |
| gfx::Size outer_viewport = gfx::Size(50, 80); |
| gfx::Size inner_viewport = gfx::Size(25, 40); |
| |
| SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport); |
| |
| LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer(); |
| LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer(); |
| DrawFrame(); |
| { |
| gfx::Vector2dF inner_expected; |
| gfx::Vector2dF outer_expected; |
| EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset()); |
| EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset()); |
| |
| // Make sure the scroll goes to the outer viewport first. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| EXPECT_EQ(InputHandler::ScrollStarted, host_impl_->FlingScrollBegin()); |
| |
| // Scroll near the edge of the outer viewport. |
| gfx::Vector2d scroll_delta(inner_viewport.width(), inner_viewport.height()); |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| outer_expected += scroll_delta; |
| |
| EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset()); |
| EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset()); |
| |
| // Now diagonal scroll across the outer viewport boundary in a single event. |
| // The entirety of the scroll should be consumed, as bubbling between inner |
| // and outer viewport layers is perfect. |
| host_impl_->ScrollBy(gfx::Point(), gfx::ScaleVector2d(scroll_delta, 2)); |
| outer_expected += scroll_delta; |
| inner_expected += scroll_delta; |
| host_impl_->ScrollEnd(); |
| |
| EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset()); |
| EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset()); |
| } |
| } |
| |
| TEST_F(LayerTreeHostImplVirtualViewportTest, |
| TouchFlingCanLockToViewportLayerAfterBubbling) { |
| gfx::Size content_size = gfx::Size(100, 160); |
| gfx::Size outer_viewport = gfx::Size(50, 80); |
| gfx::Size inner_viewport = gfx::Size(25, 40); |
| |
| SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport); |
| |
| LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer(); |
| LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer(); |
| |
| scoped_ptr<LayerImpl> child = |
| CreateScrollableLayer(10, outer_viewport, outer_scroll); |
| LayerImpl* child_scroll = child.get(); |
| outer_scroll->children()[0]->AddChild(child.Pass()); |
| |
| DrawFrame(); |
| { |
| scoped_ptr<ScrollAndScaleSet> scroll_info; |
| |
| gfx::Vector2d scroll_delta(0, inner_viewport.height()); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture)); |
| EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), scroll_delta).did_scroll); |
| |
| // The child should have scrolled up to its limit. |
| scroll_info = host_impl_->ProcessScrollDeltas(); |
| ASSERT_EQ(1u, scroll_info->scrolls.size()); |
| ExpectContains(*scroll_info, child_scroll->id(), scroll_delta); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child_scroll); |
| |
| // The first |ScrollBy| after the fling should re-lock the scrolling |
| // layer to the first layer that scrolled, the inner viewport scroll layer. |
| EXPECT_EQ(InputHandler::ScrollStarted, host_impl_->FlingScrollBegin()); |
| EXPECT_TRUE(host_impl_->ScrollBy(gfx::Point(), scroll_delta).did_scroll); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), inner_scroll); |
| |
| // The inner viewport should have scrolled up to its limit. |
| scroll_info = host_impl_->ProcessScrollDeltas(); |
| ASSERT_EQ(2u, scroll_info->scrolls.size()); |
| ExpectContains(*scroll_info, child_scroll->id(), scroll_delta); |
| ExpectContains(*scroll_info, inner_scroll->id(), scroll_delta); |
| |
| // As the locked layer is at its limit, no further scrolling can occur. |
| EXPECT_FALSE(host_impl_->ScrollBy(gfx::Point(), scroll_delta).did_scroll); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), inner_scroll); |
| host_impl_->ScrollEnd(); |
| } |
| } |
| |
| class LayerTreeHostImplWithImplicitLimitsTest : public LayerTreeHostImplTest { |
| public: |
| void SetUp() override { |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.max_memory_for_prepaint_percentage = 50; |
| CreateHostImpl(settings, CreateOutputSurface()); |
| } |
| }; |
| |
| TEST_F(LayerTreeHostImplWithImplicitLimitsTest, ImplicitMemoryLimits) { |
| // Set up a memory policy and percentages which could cause |
| // 32-bit integer overflows. |
| ManagedMemoryPolicy mem_policy(300 * 1024 * 1024); // 300MB |
| |
| // Verify implicit limits are calculated correctly with no overflows |
| host_impl_->SetMemoryPolicy(mem_policy); |
| EXPECT_EQ(host_impl_->global_tile_state().hard_memory_limit_in_bytes, |
| 300u * 1024u * 1024u); |
| EXPECT_EQ(host_impl_->global_tile_state().soft_memory_limit_in_bytes, |
| 150u * 1024u * 1024u); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ExternalTransformReflectedInNextDraw) { |
| const gfx::Size layer_size(100, 100); |
| gfx::Transform external_transform; |
| const gfx::Rect external_viewport(layer_size); |
| const gfx::Rect external_clip(layer_size); |
| const bool resourceless_software_draw = false; |
| LayerImpl* layer = SetupScrollAndContentsLayers(layer_size); |
| |
| host_impl_->SetExternalDrawConstraints(external_transform, |
| external_viewport, |
| external_clip, |
| external_viewport, |
| external_transform, |
| resourceless_software_draw); |
| DrawFrame(); |
| EXPECT_TRANSFORMATION_MATRIX_EQ( |
| external_transform, layer->draw_properties().target_space_transform); |
| |
| external_transform.Translate(20, 20); |
| host_impl_->SetExternalDrawConstraints(external_transform, |
| external_viewport, |
| external_clip, |
| external_viewport, |
| external_transform, |
| resourceless_software_draw); |
| DrawFrame(); |
| EXPECT_TRANSFORMATION_MATRIX_EQ( |
| external_transform, layer->draw_properties().target_space_transform); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, ScrollAnimated) { |
| SetupScrollAndContentsLayers(gfx::Size(100, 200)); |
| DrawFrame(); |
| |
| base::TimeTicks start_time = |
| base::TimeTicks() + base::TimeDelta::FromMilliseconds(100); |
| |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50))); |
| |
| LayerImpl* scrolling_layer = host_impl_->CurrentlyScrollingLayer(); |
| |
| host_impl_->Animate(start_time); |
| host_impl_->UpdateAnimationState(true); |
| |
| EXPECT_EQ(gfx::ScrollOffset(), scrolling_layer->CurrentScrollOffset()); |
| |
| host_impl_->Animate(start_time + base::TimeDelta::FromMilliseconds(50)); |
| host_impl_->UpdateAnimationState(true); |
| |
| float y = scrolling_layer->CurrentScrollOffset().y(); |
| EXPECT_TRUE(y > 1 && y < 49); |
| |
| // Update target. |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50))); |
| |
| host_impl_->Animate(start_time + base::TimeDelta::FromMilliseconds(200)); |
| host_impl_->UpdateAnimationState(true); |
| |
| y = scrolling_layer->CurrentScrollOffset().y(); |
| EXPECT_TRUE(y > 50 && y < 100); |
| EXPECT_EQ(scrolling_layer, host_impl_->CurrentlyScrollingLayer()); |
| |
| host_impl_->Animate(start_time + base::TimeDelta::FromMilliseconds(250)); |
| host_impl_->UpdateAnimationState(true); |
| |
| EXPECT_VECTOR_EQ(gfx::ScrollOffset(0, 100), |
| scrolling_layer->CurrentScrollOffset()); |
| EXPECT_EQ(NULL, host_impl_->CurrentlyScrollingLayer()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, GetPictureLayerImplPairs) { |
| host_impl_->CreatePendingTree(); |
| |
| scoped_ptr<PictureLayerImpl> layer = |
| FakePictureLayerImpl::Create(host_impl_->pending_tree(), 10); |
| layer->SetBounds(gfx::Size(10, 10)); |
| |
| scoped_refptr<RasterSource> pile(FakePicturePileImpl::CreateEmptyPile( |
| gfx::Size(10, 10), gfx::Size(10, 10))); |
| Region empty_invalidation; |
| const PictureLayerTilingSet* null_tiling_set = nullptr; |
| layer->UpdateRasterSource(pile, &empty_invalidation, null_tiling_set); |
| |
| host_impl_->pending_tree()->SetRootLayer(layer.Pass()); |
| |
| LayerTreeImpl* pending_tree = host_impl_->pending_tree(); |
| LayerImpl* pending_layer = pending_tree->root_layer(); |
| |
| std::vector<PictureLayerImpl::Pair> layer_pairs; |
| host_impl_->GetPictureLayerImplPairs(&layer_pairs, true); |
| EXPECT_EQ(1u, layer_pairs.size()); |
| EXPECT_EQ(pending_layer, layer_pairs[0].pending); |
| EXPECT_EQ(nullptr, layer_pairs[0].active); |
| |
| host_impl_->ActivateSyncTree(); |
| |
| LayerTreeImpl* active_tree = host_impl_->active_tree(); |
| LayerImpl* active_layer = active_tree->root_layer(); |
| EXPECT_NE(active_tree, pending_tree); |
| EXPECT_NE(active_layer, pending_layer); |
| EXPECT_NE(nullptr, active_tree); |
| EXPECT_NE(nullptr, active_layer); |
| |
| host_impl_->CreatePendingTree(); |
| |
| layer_pairs.clear(); |
| host_impl_->GetPictureLayerImplPairs(&layer_pairs, true); |
| EXPECT_EQ(1u, layer_pairs.size()); |
| EXPECT_EQ(active_layer, layer_pairs[0].active); |
| EXPECT_EQ(pending_layer, layer_pairs[0].pending); |
| |
| // Activate, the active layer has no twin now. |
| host_impl_->ActivateSyncTree(); |
| |
| layer_pairs.clear(); |
| host_impl_->GetPictureLayerImplPairs(&layer_pairs, true); |
| EXPECT_EQ(1u, layer_pairs.size()); |
| EXPECT_EQ(active_layer, layer_pairs[0].active); |
| EXPECT_EQ(nullptr, layer_pairs[0].pending); |
| |
| // Create another layer in the pending tree that's not in the active tree. We |
| // should get two pairs. |
| host_impl_->CreatePendingTree(); |
| host_impl_->pending_tree()->root_layer()->AddChild( |
| FakePictureLayerImpl::Create(host_impl_->pending_tree(), 11)); |
| |
| LayerImpl* new_pending_layer = pending_tree->root_layer()->children()[0]; |
| |
| layer_pairs.clear(); |
| host_impl_->GetPictureLayerImplPairs(&layer_pairs, true); |
| EXPECT_EQ(2u, layer_pairs.size()); |
| |
| // The pair ordering is flaky, so make it consistent. |
| if (layer_pairs[0].active != active_layer) |
| std::swap(layer_pairs[0], layer_pairs[1]); |
| |
| EXPECT_EQ(active_layer, layer_pairs[0].active); |
| EXPECT_EQ(pending_layer, layer_pairs[0].pending); |
| EXPECT_EQ(new_pending_layer, layer_pairs[1].pending); |
| EXPECT_EQ(nullptr, layer_pairs[1].active); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, DidBecomeActive) { |
| host_impl_->CreatePendingTree(); |
| host_impl_->ActivateSyncTree(); |
| host_impl_->CreatePendingTree(); |
| |
| LayerTreeImpl* pending_tree = host_impl_->pending_tree(); |
| |
| scoped_ptr<FakePictureLayerImpl> pending_layer = |
| FakePictureLayerImpl::Create(pending_tree, 10); |
| FakePictureLayerImpl* raw_pending_layer = pending_layer.get(); |
| pending_tree->SetRootLayer(pending_layer.Pass()); |
| ASSERT_EQ(raw_pending_layer, pending_tree->root_layer()); |
| |
| EXPECT_EQ(0u, raw_pending_layer->did_become_active_call_count()); |
| pending_tree->DidBecomeActive(); |
| EXPECT_EQ(1u, raw_pending_layer->did_become_active_call_count()); |
| |
| scoped_ptr<FakePictureLayerImpl> mask_layer = |
| FakePictureLayerImpl::Create(pending_tree, 11); |
| FakePictureLayerImpl* raw_mask_layer = mask_layer.get(); |
| raw_pending_layer->SetMaskLayer(mask_layer.Pass()); |
| ASSERT_EQ(raw_mask_layer, raw_pending_layer->mask_layer()); |
| |
| EXPECT_EQ(1u, raw_pending_layer->did_become_active_call_count()); |
| EXPECT_EQ(0u, raw_mask_layer->did_become_active_call_count()); |
| pending_tree->DidBecomeActive(); |
| EXPECT_EQ(2u, raw_pending_layer->did_become_active_call_count()); |
| EXPECT_EQ(1u, raw_mask_layer->did_become_active_call_count()); |
| |
| scoped_ptr<FakePictureLayerImpl> replica_layer = |
| FakePictureLayerImpl::Create(pending_tree, 12); |
| scoped_ptr<FakePictureLayerImpl> replica_mask_layer = |
| FakePictureLayerImpl::Create(pending_tree, 13); |
| FakePictureLayerImpl* raw_replica_mask_layer = replica_mask_layer.get(); |
| replica_layer->SetMaskLayer(replica_mask_layer.Pass()); |
| raw_pending_layer->SetReplicaLayer(replica_layer.Pass()); |
| ASSERT_EQ(raw_replica_mask_layer, |
| raw_pending_layer->replica_layer()->mask_layer()); |
| |
| EXPECT_EQ(2u, raw_pending_layer->did_become_active_call_count()); |
| EXPECT_EQ(1u, raw_mask_layer->did_become_active_call_count()); |
| EXPECT_EQ(0u, raw_replica_mask_layer->did_become_active_call_count()); |
| pending_tree->DidBecomeActive(); |
| EXPECT_EQ(3u, raw_pending_layer->did_become_active_call_count()); |
| EXPECT_EQ(2u, raw_mask_layer->did_become_active_call_count()); |
| EXPECT_EQ(1u, raw_replica_mask_layer->did_become_active_call_count()); |
| } |
| |
| TEST_F(LayerTreeHostImplTest, WheelScrollWithPageScaleFactorOnInnerLayer) { |
| LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100)); |
| host_impl_->SetViewportSize(gfx::Size(50, 50)); |
| DrawFrame(); |
| |
| EXPECT_EQ(scroll_layer, host_impl_->InnerViewportScrollLayer()); |
| |
| float min_page_scale = 1.f, max_page_scale = 4.f; |
| float page_scale_factor = 1.f; |
| |
| // The scroll deltas should 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::Vector2dF scroll_delta(0, 5); |
| EXPECT_EQ(InputHandler::ScrollStarted, |
| host_impl_->ScrollBegin(gfx::Point(), InputHandler::Wheel)); |
| EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset()); |
| |
| host_impl_->ScrollBy(gfx::Point(), scroll_delta); |
| host_impl_->ScrollEnd(); |
| EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 2.5), |
| scroll_layer->CurrentScrollOffset()); |
| } |
| } |
| |
| class LayerTreeHostImplCountingLostSurfaces : public LayerTreeHostImplTest { |
| public: |
| LayerTreeHostImplCountingLostSurfaces() : num_lost_surfaces_(0) {} |
| void DidLoseOutputSurfaceOnImplThread() override { num_lost_surfaces_++; } |
| |
| protected: |
| int num_lost_surfaces_; |
| }; |
| |
| TEST_F(LayerTreeHostImplCountingLostSurfaces, TwiceLostSurface) { |
| // Really we just need at least one client notification each time |
| // we go from having a valid output surface to not having a valid output |
| // surface. |
| EXPECT_EQ(0, num_lost_surfaces_); |
| host_impl_->DidLoseOutputSurface(); |
| EXPECT_EQ(1, num_lost_surfaces_); |
| host_impl_->DidLoseOutputSurface(); |
| EXPECT_LE(1, num_lost_surfaces_); |
| } |
| |
| } // namespace |
| } // namespace cc |