// Copyright 2012 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 #include #include "base/threading/thread_task_runner_handle.h" #include "cc/animation/animation_host.h" #include "cc/input/scrollbar_animation_controller.h" #include "cc/layers/append_quads_data.h" #include "cc/layers/painted_overlay_scrollbar_layer.h" #include "cc/layers/painted_scrollbar_layer.h" #include "cc/layers/painted_scrollbar_layer_impl.h" #include "cc/layers/scrollbar_layer_interface.h" #include "cc/layers/solid_color_scrollbar_layer.h" #include "cc/layers/solid_color_scrollbar_layer_impl.h" #include "cc/resources/ui_resource_manager.h" #include "cc/test/fake_impl_task_runner_provider.h" #include "cc/test/fake_layer_tree_host.h" #include "cc/test/fake_layer_tree_host_client.h" #include "cc/test/fake_layer_tree_host_impl.h" #include "cc/test/fake_painted_scrollbar_layer.h" #include "cc/test/fake_scrollbar.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/mock_occlusion_tracker.h" #include "cc/test/stub_layer_tree_host_single_thread_client.h" #include "cc/test/test_task_graph_runner.h" #include "cc/trees/effect_node.h" #include "cc/trees/layer_tree_host.h" #include "cc/trees/layer_tree_host_common.h" #include "cc/trees/layer_tree_impl.h" #include "cc/trees/occlusion_tracker.h" #include "cc/trees/scroll_node.h" #include "cc/trees/single_thread_proxy.h" #include "cc/trees/tree_synchronizer.h" #include "components/viz/common/quads/solid_color_draw_quad.h" #include "components/viz/test/test_context_provider.h" #include "components/viz/test/test_gles2_interface.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace cc { class FakeResourceTrackingUIResourceManager : public UIResourceManager { public: FakeResourceTrackingUIResourceManager() : next_id_(1), total_ui_resource_created_(0), total_ui_resource_deleted_(0) {} ~FakeResourceTrackingUIResourceManager() override = default; UIResourceId CreateUIResource(UIResourceClient* content) override { total_ui_resource_created_++; UIResourceId nid = next_id_++; ui_resource_bitmap_map_.insert( std::make_pair(nid, content->GetBitmap(nid, false))); return nid; } // Deletes a UI resource. May safely be called more than once. void DeleteUIResource(UIResourceId id) override { auto iter = ui_resource_bitmap_map_.find(id); if (iter != ui_resource_bitmap_map_.end()) { ui_resource_bitmap_map_.erase(iter); total_ui_resource_deleted_++; } } size_t UIResourceCount() { return ui_resource_bitmap_map_.size(); } int TotalUIResourceDeleted() { return total_ui_resource_deleted_; } int TotalUIResourceCreated() { return total_ui_resource_created_; } gfx::Size ui_resource_size(UIResourceId id) { auto iter = ui_resource_bitmap_map_.find(id); if (iter != ui_resource_bitmap_map_.end()) return iter->second.GetSize(); return gfx::Size(); } UIResourceBitmap* ui_resource_bitmap(UIResourceId id) { auto iter = ui_resource_bitmap_map_.find(id); if (iter != ui_resource_bitmap_map_.end()) return &iter->second; return nullptr; } private: using UIResourceBitmapMap = std::unordered_map; UIResourceBitmapMap ui_resource_bitmap_map_; StubLayerTreeHostSingleThreadClient single_thread_client_; int next_id_; int total_ui_resource_created_; int total_ui_resource_deleted_; }; class BaseScrollbarLayerTest : public testing::Test { public: explicit BaseScrollbarLayerTest( LayerTreeSettings::ScrollbarAnimator animator) { layer_tree_settings_.single_thread_proxy_scheduler = false; layer_tree_settings_.use_zero_copy = true; layer_tree_settings_.scrollbar_animator = animator; layer_tree_settings_.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20); layer_tree_settings_.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(20); scrollbar_layer_id_ = -1; animation_host_ = AnimationHost::CreateForTesting(ThreadInstance::MAIN); LayerTreeHost::InitParams params; params.client = &fake_client_; params.settings = &layer_tree_settings_; params.task_graph_runner = &task_graph_runner_; params.mutator_host = animation_host_.get(); std::unique_ptr fake_ui_resource_manager = std::make_unique(); fake_ui_resource_manager_ = fake_ui_resource_manager.get(); layer_tree_host_ = std::make_unique( &fake_client_, std::move(params), CompositorMode::SINGLE_THREADED); layer_tree_host_->SetUIResourceManagerForTesting( std::move(fake_ui_resource_manager)); layer_tree_host_->InitializeSingleThreaded( &single_thread_client_, base::ThreadTaskRunnerHandle::Get()); layer_tree_host_->SetVisible(true); fake_client_.SetLayerTreeHost(layer_tree_host_.get()); } LayerImpl* LayerImplForScrollAreaAndScrollbar( FakeLayerTreeHost* host, std::unique_ptr scrollbar, bool reverse_order, bool use_solid_color_scrollbar, int thumb_thickness, int track_start) { scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr child1 = Layer::Create(); scoped_refptr child2; if (use_solid_color_scrollbar) { const bool kIsLeftSideVerticalScrollbar = false; child2 = SolidColorScrollbarLayer::Create( scrollbar->Orientation(), thumb_thickness, track_start, kIsLeftSideVerticalScrollbar, child1->element_id()); } else { child2 = PaintedScrollbarLayer::Create(std::move(scrollbar), child1->element_id()); } layer_tree_root->AddChild(child1); layer_tree_root->InsertChild(child2, reverse_order ? 0 : 1); scrollbar_layer_id_ = reverse_order ? child1->id() : child2->id(); host->SetRootLayer(layer_tree_root); host->BuildPropertyTreesForTesting(); return host->CommitAndCreateLayerImplTree(); } protected: FakeResourceTrackingUIResourceManager* fake_ui_resource_manager_; FakeLayerTreeHostClient fake_client_; StubLayerTreeHostSingleThreadClient single_thread_client_; TestTaskGraphRunner task_graph_runner_; LayerTreeSettings layer_tree_settings_; std::unique_ptr animation_host_; std::unique_ptr layer_tree_host_; int scrollbar_layer_id_; }; class ScrollbarLayerTest : public BaseScrollbarLayerTest { public: ScrollbarLayerTest() : BaseScrollbarLayerTest(LayerTreeSettings::ANDROID_OVERLAY) {} }; class AuraScrollbarLayerTest : public BaseScrollbarLayerTest { public: AuraScrollbarLayerTest() : BaseScrollbarLayerTest(LayerTreeSettings::AURA_OVERLAY) {} }; class FakePaintedOverlayScrollbar : public FakeScrollbar { public: FakePaintedOverlayScrollbar() : FakeScrollbar(true, true, true) {} bool UsesNinePatchThumbResource() const override { return true; } gfx::Size NinePatchThumbCanvasSize() const override { return gfx::Size(3, 3); } gfx::Rect NinePatchThumbAperture() const override { return gfx::Rect(1, 1, 1, 1); } }; // Test that a painted overlay scrollbar will repaint and recrate its resource // after its been disposed, even if Blink doesn't think it requires a repaint. // crbug.com/704656. TEST_F(ScrollbarLayerTest, RepaintOverlayWhenResourceDisposed) { scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr content_layer = Layer::Create(); std::unique_ptr scrollbar( new FakePaintedOverlayScrollbar); FakePaintedOverlayScrollbar* fake_scrollbar = scrollbar.get(); scoped_refptr scrollbar_layer = PaintedOverlayScrollbarLayer::Create(std::move(scrollbar), layer_tree_root->element_id()); // Setup. { layer_tree_root->AddChild(content_layer); layer_tree_root->AddChild(scrollbar_layer); layer_tree_host_->SetRootLayer(layer_tree_root); scrollbar_layer->SetIsDrawable(true); scrollbar_layer->SetBounds(gfx::Size(100, 100)); layer_tree_root->SetBounds(gfx::Size(100, 200)); content_layer->SetBounds(gfx::Size(100, 200)); } // First call to update should create a resource. The scrollbar itself thinks // it needs a repaint. { fake_scrollbar->set_needs_paint_thumb(true); EXPECT_EQ(0u, fake_ui_resource_manager_->UIResourceCount()); EXPECT_TRUE(scrollbar_layer->Update()); EXPECT_EQ(1u, fake_ui_resource_manager_->UIResourceCount()); } // Now the scrollbar has been painted and nothing else has changed, calling // Update() shouldn't have an effect. { fake_scrollbar->set_needs_paint_thumb(false); EXPECT_FALSE(scrollbar_layer->Update()); EXPECT_EQ(1u, fake_ui_resource_manager_->UIResourceCount()); } // Detach and reattach the LayerTreeHost (this can happen during tree // reconstruction). This should cause the UIResource for the scrollbar to be // disposed but the scrollbar itself hasn't changed so it reports that no // repaint is needed. An Update should cause us to recreate the resource // though. { scrollbar_layer->SetLayerTreeHost(nullptr); scrollbar_layer->SetLayerTreeHost(layer_tree_host_.get()); EXPECT_EQ(0u, fake_ui_resource_manager_->UIResourceCount()); EXPECT_TRUE(scrollbar_layer->Update()); EXPECT_EQ(1u, fake_ui_resource_manager_->UIResourceCount()); } } class FakeNinePatchScrollbar : public FakeScrollbar { public: bool UsesNinePatchThumbResource() const override { return true; } }; TEST_F(ScrollbarLayerTest, ScrollElementIdPushedAcrossCommit) { std::unique_ptr scrollbar1(new FakeScrollbar); std::unique_ptr scrollbar2(new FakeNinePatchScrollbar); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr layer_a = Layer::Create(); scoped_refptr layer_b = Layer::Create(); layer_a->SetElementId(LayerIdToElementIdForTesting(layer_a->id())); layer_b->SetElementId(LayerIdToElementIdForTesting(layer_b->id())); scoped_refptr painted_scrollbar_layer = PaintedScrollbarLayer::Create(std::move(scrollbar1), layer_a->element_id()); scoped_refptr painted_overlay_scrollbar_layer = PaintedOverlayScrollbarLayer::Create(std::move(scrollbar2), layer_a->element_id()); scoped_refptr solid_color_scrollbar_layer = SolidColorScrollbarLayer::Create(VERTICAL, 1, 1, false, layer_a->element_id()); layer_tree_host_->SetRootLayer(layer_tree_root); layer_tree_root->AddChild(layer_a); layer_tree_root->AddChild(layer_b); layer_tree_root->AddChild(painted_scrollbar_layer); layer_tree_root->AddChild(painted_overlay_scrollbar_layer); layer_tree_root->AddChild(solid_color_scrollbar_layer); layer_tree_host_->UpdateLayers(); LayerImpl* layer_impl_tree_root = layer_tree_host_->CommitAndCreateLayerImplTree(); ScrollbarLayerImplBase* painted_scrollbar_layer_impl = static_cast( layer_impl_tree_root->layer_tree_impl()->LayerById( painted_scrollbar_layer->id())); ScrollbarLayerImplBase* painted_overlay_scrollbar_layer_impl = static_cast( layer_impl_tree_root->layer_tree_impl()->LayerById( painted_overlay_scrollbar_layer->id())); ScrollbarLayerImplBase* solid_color_scrollbar_layer_impl = static_cast( layer_impl_tree_root->layer_tree_impl()->LayerById( solid_color_scrollbar_layer->id())); ASSERT_EQ(painted_scrollbar_layer_impl->scroll_element_id_, layer_a->element_id()); ASSERT_EQ(painted_overlay_scrollbar_layer_impl->scroll_element_id_, layer_a->element_id()); ASSERT_EQ(solid_color_scrollbar_layer_impl->scroll_element_id_, layer_a->element_id()); painted_scrollbar_layer->SetScrollElementId(layer_b->element_id()); painted_overlay_scrollbar_layer->SetScrollElementId(layer_b->element_id()); solid_color_scrollbar_layer->SetScrollElementId(layer_b->element_id()); ASSERT_TRUE(layer_tree_host_->needs_commit()); { DebugScopedSetImplThread scoped_impl_thread( layer_tree_host_->GetTaskRunnerProvider()); layer_tree_host_->FinishCommitOnImplThread(layer_tree_host_->host_impl()); } EXPECT_EQ(painted_scrollbar_layer_impl->scroll_element_id_, layer_b->element_id()); EXPECT_EQ(painted_overlay_scrollbar_layer_impl->scroll_element_id_, layer_b->element_id()); EXPECT_EQ(solid_color_scrollbar_layer_impl->scroll_element_id_, layer_b->element_id()); } TEST_F(ScrollbarLayerTest, ScrollOffsetSynchronization) { std::unique_ptr scrollbar(new FakeScrollbar); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr scroll_layer = Layer::Create(); scroll_layer->SetElementId(LayerIdToElementIdForTesting(scroll_layer->id())); scoped_refptr content_layer = Layer::Create(); scoped_refptr scrollbar_layer = PaintedScrollbarLayer::Create( std::move(scrollbar), scroll_layer->element_id()); // Choose bounds to give max_scroll_offset = (30, 50). layer_tree_root->SetBounds(gfx::Size(70, 150)); scroll_layer->SetScrollOffset(gfx::ScrollOffset(10, 20)); scroll_layer->SetBounds(gfx::Size(100, 200)); scroll_layer->SetScrollable(gfx::Size(70, 150)); content_layer->SetBounds(gfx::Size(100, 200)); layer_tree_host_->SetRootLayer(layer_tree_root); layer_tree_root->AddChild(scroll_layer); scroll_layer->AddChild(content_layer); layer_tree_root->AddChild(scrollbar_layer); layer_tree_host_->UpdateLayers(); LayerImpl* layer_impl_tree_root = layer_tree_host_->CommitAndCreateLayerImplTree(); ScrollbarLayerImplBase* cc_scrollbar_layer = static_cast( layer_impl_tree_root->layer_tree_impl()->LayerById( scrollbar_layer->id())); layer_impl_tree_root->layer_tree_impl()->UpdateScrollbarGeometries(); EXPECT_EQ(10.f, cc_scrollbar_layer->current_pos()); EXPECT_EQ(30, cc_scrollbar_layer->scroll_layer_length() - cc_scrollbar_layer->clip_layer_length()); layer_tree_root->SetBounds(gfx::Size(700, 1500)); scroll_layer->SetScrollable(gfx::Size(700, 1500)); scroll_layer->SetBounds(gfx::Size(1000, 2000)); scroll_layer->SetScrollOffset(gfx::ScrollOffset(100, 200)); content_layer->SetBounds(gfx::Size(1000, 2000)); layer_tree_host_->UpdateLayers(); layer_impl_tree_root = layer_tree_host_->CommitAndCreateLayerImplTree(); layer_impl_tree_root->layer_tree_impl()->UpdateScrollbarGeometries(); EXPECT_EQ(100.f, cc_scrollbar_layer->current_pos()); EXPECT_EQ(300, cc_scrollbar_layer->scroll_layer_length() - cc_scrollbar_layer->clip_layer_length()); LayerImpl* scroll_layer_impl = layer_impl_tree_root->layer_tree_impl()->LayerById(scroll_layer->id()); scroll_layer_impl->ScrollBy(gfx::Vector2d(12, 34)); layer_impl_tree_root->layer_tree_impl()->UpdateScrollbarGeometries(); EXPECT_EQ(112.f, cc_scrollbar_layer->current_pos()); EXPECT_EQ(300, cc_scrollbar_layer->scroll_layer_length() - cc_scrollbar_layer->clip_layer_length()); } #define UPDATE_AND_EXTRACT_LAYER_POINTERS() \ do { \ scrollbar_layer->UpdateInternalContentScale(); \ scrollbar_layer->UpdateThumbAndTrackGeometry(); \ root_layer_impl = layer_tree_host_->CommitAndCreateLayerImplTree(); \ root_layer_impl->layer_tree_impl()->UpdateScrollbarGeometries(); \ scrollbar_layer_impl = static_cast( \ root_layer_impl->layer_tree_impl()->LayerById(scrollbar_layer->id())); \ } while (false) TEST_F(ScrollbarLayerTest, UpdatePropertiesOfScrollBarWhenThumbRemoved) { scoped_refptr root_layer = Layer::Create(); scoped_refptr content_layer = Layer::Create(); scoped_refptr scrollbar_layer = FakePaintedScrollbarLayer::Create(false, true, root_layer->element_id()); // Give the root layer a size that will result in MaxScrollOffset = (80, 0). root_layer->SetScrollable(gfx::Size(20, 50)); root_layer->SetBounds(gfx::Size(100, 50)); content_layer->SetBounds(gfx::Size(100, 50)); layer_tree_host_->SetRootLayer(root_layer); root_layer->AddChild(content_layer); root_layer->AddChild(scrollbar_layer); root_layer->SetScrollOffset(gfx::ScrollOffset(0, 0)); scrollbar_layer->SetBounds(gfx::Size(70, 10)); scrollbar_layer->SetScrollElementId(root_layer->element_id()); // The track_rect should be relative to the scrollbar's origin. scrollbar_layer->fake_scrollbar()->set_location(gfx::Point(20, 10)); scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(10, 10, 50, 10)); scrollbar_layer->fake_scrollbar()->set_thumb_thickness(10); scrollbar_layer->fake_scrollbar()->set_thumb_length(4); LayerImpl* root_layer_impl = nullptr; PaintedScrollbarLayerImpl* scrollbar_layer_impl = nullptr; layer_tree_host_->BuildPropertyTreesForTesting(); UPDATE_AND_EXTRACT_LAYER_POINTERS(); EXPECT_EQ(gfx::Rect(10, 0, 4, 10).ToString(), scrollbar_layer_impl->ComputeThumbQuadRect().ToString()); scrollbar_layer->fake_scrollbar()->set_has_thumb(false); UPDATE_AND_EXTRACT_LAYER_POINTERS(); EXPECT_EQ(gfx::Rect(10, 0, 0, 0).ToString(), scrollbar_layer_impl->ComputeThumbQuadRect().ToString()); } TEST_F(ScrollbarLayerTest, ThumbRect) { scoped_refptr root_layer = Layer::Create(); scoped_refptr content_layer = Layer::Create(); scoped_refptr scrollbar_layer = FakePaintedScrollbarLayer::Create(false, true, root_layer->element_id()); root_layer->SetElementId(LayerIdToElementIdForTesting(root_layer->id())); // Give the root layer a size that will result in MaxScrollOffset = (80, 0). root_layer->SetScrollable(gfx::Size(20, 50)); root_layer->SetBounds(gfx::Size(100, 50)); content_layer->SetBounds(gfx::Size(100, 50)); layer_tree_host_->SetRootLayer(root_layer); root_layer->AddChild(content_layer); root_layer->AddChild(scrollbar_layer); root_layer->SetScrollOffset(gfx::ScrollOffset(0, 0)); scrollbar_layer->SetBounds(gfx::Size(70, 10)); scrollbar_layer->SetScrollElementId(root_layer->element_id()); // The track_rect should be relative to the scrollbar's origin. scrollbar_layer->fake_scrollbar()->set_location(gfx::Point(20, 10)); scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(10, 10, 50, 10)); scrollbar_layer->fake_scrollbar()->set_thumb_thickness(10); scrollbar_layer->fake_scrollbar()->set_thumb_length(4); layer_tree_host_->UpdateLayers(); LayerImpl* root_layer_impl = nullptr; PaintedScrollbarLayerImpl* scrollbar_layer_impl = nullptr; // Thumb is at the edge of the scrollbar (should be inset to // the start of the track within the scrollbar layer's // position). UPDATE_AND_EXTRACT_LAYER_POINTERS(); EXPECT_EQ(gfx::Rect(10, 0, 4, 10).ToString(), scrollbar_layer_impl->ComputeThumbQuadRect().ToString()); // Under-scroll (thumb position should clamp and be unchanged). root_layer->SetScrollOffset(gfx::ScrollOffset(-5, 0)); UPDATE_AND_EXTRACT_LAYER_POINTERS(); EXPECT_EQ(gfx::Rect(10, 0, 4, 10).ToString(), scrollbar_layer_impl->ComputeThumbQuadRect().ToString()); // Over-scroll (thumb position should clamp on the far side). root_layer->SetScrollOffset(gfx::ScrollOffset(85, 0)); layer_tree_host_->UpdateLayers(); UPDATE_AND_EXTRACT_LAYER_POINTERS(); EXPECT_EQ(gfx::Rect(56, 0, 4, 10).ToString(), scrollbar_layer_impl->ComputeThumbQuadRect().ToString()); // Change thumb thickness and length. scrollbar_layer->fake_scrollbar()->set_thumb_thickness(4); scrollbar_layer->fake_scrollbar()->set_thumb_length(6); UPDATE_AND_EXTRACT_LAYER_POINTERS(); EXPECT_EQ(gfx::Rect(54, 0, 6, 4).ToString(), scrollbar_layer_impl->ComputeThumbQuadRect().ToString()); // Shrink the scrollbar layer to cover only the track. scrollbar_layer->SetBounds(gfx::Size(50, 10)); scrollbar_layer->fake_scrollbar()->set_location(gfx::Point(30, 10)); scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(0, 10, 50, 10)); UPDATE_AND_EXTRACT_LAYER_POINTERS(); EXPECT_EQ(gfx::Rect(44, 0, 6, 4).ToString(), scrollbar_layer_impl->ComputeThumbQuadRect().ToString()); // Shrink the track in the non-scrolling dimension so that it only covers the // middle third of the scrollbar layer (this does not affect the thumb // position). scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(0, 12, 50, 6)); UPDATE_AND_EXTRACT_LAYER_POINTERS(); EXPECT_EQ(gfx::Rect(44, 0, 6, 4).ToString(), scrollbar_layer_impl->ComputeThumbQuadRect().ToString()); } TEST_F(ScrollbarLayerTest, ThumbRectForOverlayLeftSideVerticalScrollbar) { scoped_refptr root_layer = Layer::Create(); // Create an overlay left side vertical scrollbar. scoped_refptr scrollbar_layer = FakePaintedScrollbarLayer::Create(false, true, VERTICAL, true, true, root_layer->element_id()); root_layer->SetScrollable(gfx::Size(20, 50)); root_layer->SetBounds(gfx::Size(50, 100)); layer_tree_host_->SetRootLayer(root_layer); root_layer->AddChild(scrollbar_layer); root_layer->SetScrollOffset(gfx::ScrollOffset(0, 0)); scrollbar_layer->SetBounds(gfx::Size(10, 20)); scrollbar_layer->SetScrollElementId(root_layer->element_id()); scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(0, 0, 10, 20)); scrollbar_layer->fake_scrollbar()->set_thumb_thickness(10); scrollbar_layer->fake_scrollbar()->set_thumb_length(4); layer_tree_host_->UpdateLayers(); LayerImpl* root_layer_impl = nullptr; PaintedScrollbarLayerImpl* scrollbar_layer_impl = nullptr; // Thumb is at the edge of the scrollbar (should be inset to // the start of the track within the scrollbar layer's // position). UPDATE_AND_EXTRACT_LAYER_POINTERS(); EXPECT_EQ(gfx::Rect(0, 0, 10, 4).ToString(), scrollbar_layer_impl->ComputeThumbQuadRect().ToString()); // Change thumb thickness scale factor. scrollbar_layer_impl->SetThumbThicknessScaleFactor(0.5); UPDATE_AND_EXTRACT_LAYER_POINTERS(); // For overlay scrollbars thumb_rect.width = thumb_thickness * // thumb_thickness_scale_factor. EXPECT_EQ(gfx::Rect(0, 0, 5, 4).ToString(), scrollbar_layer_impl->ComputeThumbQuadRect().ToString()); // Change thumb thickness and length. scrollbar_layer->fake_scrollbar()->set_thumb_thickness(4); scrollbar_layer->fake_scrollbar()->set_thumb_length(6); UPDATE_AND_EXTRACT_LAYER_POINTERS(); // For left side vertical scrollbars thumb_rect.x = bounds.width() - // thumb_thickness. EXPECT_EQ(gfx::Rect(6, 0, 2, 6).ToString(), scrollbar_layer_impl->ComputeThumbQuadRect().ToString()); } TEST_F(ScrollbarLayerTest, SolidColorDrawQuads) { const int kThumbThickness = 3; const int kTrackStart = 1; const int kTrackLength = 100; std::unique_ptr scrollbar(new FakeScrollbar(false, true, true)); LayerImpl* layer_impl_tree_root = LayerImplForScrollAreaAndScrollbar( layer_tree_host_.get(), std::move(scrollbar), false, true, kThumbThickness, kTrackStart); ScrollbarLayerImplBase* scrollbar_layer_impl = static_cast( layer_impl_tree_root->layer_tree_impl()->LayerById( scrollbar_layer_id_)); scrollbar_layer_impl->SetBounds(gfx::Size(kTrackLength, kThumbThickness)); scrollbar_layer_impl->SetCurrentPos(10.f); scrollbar_layer_impl->SetClipLayerLength(200 / 3.f); scrollbar_layer_impl->SetScrollLayerLength(100 + 200 / 3.f); // Thickness should be overridden to 3. { std::unique_ptr render_pass = viz::RenderPass::Create(); AppendQuadsData data; scrollbar_layer_impl->AppendQuads(render_pass.get(), &data); const auto& quads = render_pass->quad_list; ASSERT_EQ(1u, quads.size()); EXPECT_EQ(viz::DrawQuad::Material::kSolidColor, quads.front()->material); EXPECT_EQ(gfx::Rect(6, 0, 39, 3), quads.front()->rect); } // For solid color scrollbars, position and size should reflect the // current viewport state. scrollbar_layer_impl->SetClipLayerLength(25.f); scrollbar_layer_impl->SetScrollLayerLength(125.f); { std::unique_ptr render_pass = viz::RenderPass::Create(); AppendQuadsData data; scrollbar_layer_impl->AppendQuads(render_pass.get(), &data); const auto& quads = render_pass->quad_list; ASSERT_EQ(1u, quads.size()); EXPECT_EQ(viz::DrawQuad::Material::kSolidColor, quads.front()->material); EXPECT_EQ(gfx::Rect(8, 0, 19, 3), quads.front()->rect); } // We shouldn't attempt div-by-zero when the maximum is zero. scrollbar_layer_impl->SetCurrentPos(0.f); scrollbar_layer_impl->SetClipLayerLength(125.f); scrollbar_layer_impl->SetScrollLayerLength(125.f); { std::unique_ptr render_pass = viz::RenderPass::Create(); AppendQuadsData data; scrollbar_layer_impl->AppendQuads(render_pass.get(), &data); const auto& quads = render_pass->quad_list; ASSERT_EQ(1u, quads.size()); EXPECT_EQ(viz::DrawQuad::Material::kSolidColor, quads.front()->material); EXPECT_EQ(gfx::Rect(1, 0, 98, 3), quads.front()->rect); } } TEST_F(ScrollbarLayerTest, LayerDrivenSolidColorDrawQuads) { const int kThumbThickness = 3; const int kTrackStart = 0; const int kTrackLength = 10; std::unique_ptr scrollbar(new FakeScrollbar(false, true, true)); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr scroll_layer = Layer::Create(); scroll_layer->SetElementId(LayerIdToElementIdForTesting(scroll_layer->id())); scoped_refptr child1 = Layer::Create(); scoped_refptr child2; const bool kIsLeftSideVerticalScrollbar = false; child2 = SolidColorScrollbarLayer::Create( scrollbar->Orientation(), kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar, scroll_layer->element_id()); scroll_layer->AddChild(child1); scroll_layer->InsertChild(child2, 1); layer_tree_root->AddChild(scroll_layer); layer_tree_host_->SetRootLayer(layer_tree_root); // Choose layer bounds to give max_scroll_offset = (8, 8). layer_tree_root->SetBounds(gfx::Size(2, 2)); scroll_layer->SetScrollable(gfx::Size(2, 2)); scroll_layer->SetBounds(gfx::Size(10, 10)); layer_tree_host_->UpdateLayers(); LayerImpl* layer_impl_tree_root = layer_tree_host_->CommitAndCreateLayerImplTree(); LayerImpl* scroll_layer_impl = layer_impl_tree_root->layer_tree_impl()->LayerById(scroll_layer->id()); auto* scrollbar_layer_impl = static_cast( scroll_layer_impl->layer_tree_impl()->LayerById(child2->id())); scroll_layer_impl->ScrollBy(gfx::Vector2dF(4.f, 0.f)); scrollbar_layer_impl->SetBounds(gfx::Size(kTrackLength, kThumbThickness)); scrollbar_layer_impl->SetCurrentPos(4.f); DCHECK(layer_tree_host_->active_tree()->ScrollbarGeometriesNeedUpdate()); layer_tree_host_->active_tree()->UpdateScrollbarGeometries(); { std::unique_ptr render_pass = viz::RenderPass::Create(); AppendQuadsData data; scrollbar_layer_impl->AppendQuads(render_pass.get(), &data); const auto& quads = render_pass->quad_list; ASSERT_EQ(1u, quads.size()); EXPECT_EQ(viz::DrawQuad::Material::kSolidColor, quads.front()->material); EXPECT_EQ(gfx::Rect(3, 0, 3, 3), quads.front()->rect); } } TEST_F(ScrollbarLayerTest, ScrollbarLayerOpacity) { const int kThumbThickness = 3; const int kTrackStart = 0; std::unique_ptr scrollbar(new FakeScrollbar(false, true, true)); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr scroll_layer = Layer::Create(); scroll_layer->SetElementId(ElementId(200)); scoped_refptr child1 = Layer::Create(); scoped_refptr scrollbar_layer; const bool kIsLeftSideVerticalScrollbar = false; scrollbar_layer = SolidColorScrollbarLayer::Create( scrollbar->Orientation(), kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar, scroll_layer->element_id()); scrollbar_layer->SetElementId(ElementId(300)); scroll_layer->AddChild(child1); scroll_layer->InsertChild(scrollbar_layer, 1); layer_tree_root->AddChild(scroll_layer); layer_tree_host_->SetRootLayer(layer_tree_root); scrollbar_layer->SetScrollElementId(scroll_layer->element_id()); // Choose layer bounds to give max_scroll_offset = (8, 8). layer_tree_root->SetBounds(gfx::Size(2, 2)); scroll_layer->SetBounds(gfx::Size(10, 10)); // Building property trees twice shouldn't change the size of // PropertyTrees::always_use_active_tree_opacity_effect_ids. layer_tree_host_->BuildPropertyTreesForTesting(); layer_tree_host_->property_trees()->needs_rebuild = true; layer_tree_host_->BuildPropertyTreesForTesting(); // A solid color scrollbar layer's opacity is initialized to 0 on main thread layer_tree_host_->UpdateLayers(); EffectNode* node = layer_tree_host_->property_trees()->effect_tree.Node( scrollbar_layer->effect_tree_index()); EXPECT_EQ(node->opacity, 0.f); // This tests that the initial opacity(0) of the scrollbar gets pushed onto // the pending tree and then onto the active tree. LayerTreeHostImpl* host_impl = layer_tree_host_->host_impl(); host_impl->CreatePendingTree(); LayerImpl* layer_impl_tree_root = layer_tree_host_->CommitAndCreatePendingTree(); LayerTreeImpl* layer_tree_impl = layer_impl_tree_root->layer_tree_impl(); EXPECT_TRUE(layer_tree_impl->IsPendingTree()); node = layer_tree_impl->property_trees()->effect_tree.Node( scrollbar_layer->effect_tree_index()); EXPECT_EQ(node->opacity, 0.f); host_impl->ActivateSyncTree(); layer_tree_impl = host_impl->active_tree(); node = layer_tree_impl->property_trees()->effect_tree.Node( scrollbar_layer->effect_tree_index()); EXPECT_EQ(node->opacity, 0.f); // This tests that activation does not change the opacity of scrollbar layer. ScrollbarLayerImplBase* scrollbar_layer_impl = static_cast( layer_tree_impl->LayerById(scrollbar_layer->id())); scrollbar_layer_impl->SetOverlayScrollbarLayerOpacityAnimated(0.25f); host_impl->CreatePendingTree(); layer_impl_tree_root = layer_tree_host_->CommitAndCreatePendingTree(); layer_tree_impl = layer_impl_tree_root->layer_tree_impl(); EXPECT_TRUE(layer_tree_impl->IsPendingTree()); node = layer_tree_impl->property_trees()->effect_tree.Node( scrollbar_layer->effect_tree_index()); EXPECT_EQ(node->opacity, 0.f); host_impl->ActivateSyncTree(); layer_tree_impl = host_impl->active_tree(); node = layer_tree_impl->property_trees()->effect_tree.Node( scrollbar_layer->effect_tree_index()); EXPECT_EQ(node->opacity, 0.25f); } TEST_F(ScrollbarLayerTest, ScrollbarLayerPushProperties) { // Pushing changed bounds of scroll layer can lead to calling // OnOpacityAnimated on scrollbar layer which means OnOpacityAnimated should // be independent of scrollbar layer's properties as scrollbar layer can push // its properties after scroll layer. const int kThumbThickness = 3; const int kTrackStart = 0; std::unique_ptr scrollbar(new FakeScrollbar(false, true, true)); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr scroll_layer = Layer::Create(); scroll_layer->SetElementId(LayerIdToElementIdForTesting(scroll_layer->id())); scoped_refptr child1 = Layer::Create(); scoped_refptr scrollbar_layer; const bool kIsLeftSideVerticalScrollbar = false; scrollbar_layer = SolidColorScrollbarLayer::Create( scrollbar->Orientation(), kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar, scroll_layer->element_id()); scroll_layer->AddChild(child1); scroll_layer->InsertChild(scrollbar_layer, 1); layer_tree_root->AddChild(scroll_layer); layer_tree_host_->SetRootLayer(layer_tree_root); layer_tree_root->SetBounds(gfx::Size(2, 2)); scroll_layer->SetBounds(gfx::Size(10, 10)); scroll_layer->SetScrollable(layer_tree_root->bounds()); layer_tree_host_->UpdateLayers(); LayerTreeHostImpl* host_impl = layer_tree_host_->host_impl(); host_impl->CreatePendingTree(); layer_tree_host_->CommitAndCreatePendingTree(); host_impl->ActivateSyncTree(); EXPECT_TRUE(host_impl->ScrollbarAnimationControllerForElementId( scroll_layer->element_id())); scroll_layer->SetBounds(gfx::Size(20, 20)); scroll_layer->ShowScrollbars(); scroll_layer->SetForceRenderSurfaceForTesting(true); layer_tree_host_->UpdateLayers(); host_impl->CreatePendingTree(); layer_tree_host_->CommitAndCreatePendingTree(); host_impl->ActivateSyncTree(); EffectNode* node = host_impl->active_tree()->property_trees()->effect_tree.Node( scrollbar_layer->effect_tree_index()); EXPECT_EQ(node->opacity, 1.f); } TEST_F(ScrollbarLayerTest, SubPixelCanScrollOrientation) { gfx::Size viewport_size(980, 980); LayerTestCommon::LayerImplTest impl; LayerImpl* scroll_layer = impl.AddChildToRoot(); scroll_layer->SetElementId(LayerIdToElementIdForTesting(scroll_layer->id())); const int kTrackStart = 0; const int kThumbThickness = 10; const bool kIsLeftSideVerticalScrollbar = false; const bool kIsOverlayScrollbar = false; SolidColorScrollbarLayerImpl* scrollbar_layer = impl.AddChild( scroll_layer, HORIZONTAL, kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar, kIsOverlayScrollbar); scrollbar_layer->SetScrollElementId(scroll_layer->element_id()); scroll_layer->SetScrollable(gfx::Size(980, 980)); scroll_layer->SetBounds(gfx::Size(980, 980)); impl.host_impl()->active_tree()->BuildPropertyTreesForTesting(); DCHECK(impl.host_impl()->active_tree()->ScrollbarGeometriesNeedUpdate()); impl.host_impl()->active_tree()->UpdateScrollbarGeometries(); impl.CalcDrawProps(viewport_size); // Fake clip layer length to scrollbar to mock rounding error. scrollbar_layer->SetClipLayerLength(979.999939f); impl.host_impl()->active_tree()->BuildPropertyTreesForTesting(); EXPECT_FALSE(scrollbar_layer->CanScrollOrientation()); // Fake clip layer length to scrollable. scrollbar_layer->SetClipLayerLength(979.0f); impl.host_impl()->active_tree()->BuildPropertyTreesForTesting(); EXPECT_TRUE(scrollbar_layer->CanScrollOrientation()); } TEST_F(ScrollbarLayerTest, LayerChangesAffectingScrollbarGeometries) { LayerTestCommon::LayerImplTest impl; LayerImpl* clip_layer = impl.AddChildToRoot(); LayerImpl* scroll_layer = impl.AddChild(clip_layer); scroll_layer->SetElementId(LayerIdToElementIdForTesting(scroll_layer->id())); // Make clip_layer the inner viewport container layer. This ensures the later // call to |SetViewportBoundsDelta| will be on a viewport layer. LayerTreeImpl::ViewportLayerIds viewport_ids; viewport_ids.inner_viewport_container = clip_layer->id(); impl.host_impl()->active_tree()->SetViewportLayersFromIds(viewport_ids); const int kTrackStart = 0; const int kThumbThickness = 10; const bool kIsLeftSideVerticalScrollbar = false; const bool kIsOverlayScrollbar = false; SolidColorScrollbarLayerImpl* scrollbar_layer = impl.AddChild( scroll_layer, HORIZONTAL, kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar, kIsOverlayScrollbar); scrollbar_layer->SetScrollElementId(scroll_layer->element_id()); EXPECT_TRUE(impl.host_impl()->active_tree()->ScrollbarGeometriesNeedUpdate()); impl.host_impl()->active_tree()->UpdateScrollbarGeometries(); scroll_layer->SetBounds(gfx::Size(900, 900)); // If the scroll layer is not scrollable, the bounds do not affect scrollbar // geometries. EXPECT_FALSE( impl.host_impl()->active_tree()->ScrollbarGeometriesNeedUpdate()); scroll_layer->SetScrollable(gfx::Size(900, 900)); EXPECT_TRUE(impl.host_impl()->active_tree()->ScrollbarGeometriesNeedUpdate()); impl.host_impl()->active_tree()->UpdateScrollbarGeometries(); clip_layer->SetBounds(gfx::Size(900, 900)); // The clip layer for scrolling is managed independently of the scroll // container bounds so changing the clip does not require an update. EXPECT_FALSE( impl.host_impl()->active_tree()->ScrollbarGeometriesNeedUpdate()); scroll_layer->SetBounds(gfx::Size(980, 980)); // Changes to the bounds should also require an update. EXPECT_TRUE(impl.host_impl()->active_tree()->ScrollbarGeometriesNeedUpdate()); impl.host_impl()->active_tree()->UpdateScrollbarGeometries(); clip_layer->SetViewportBoundsDelta(gfx::Vector2dF(1, 2)); EXPECT_TRUE(impl.host_impl()->active_tree()->ScrollbarGeometriesNeedUpdate()); impl.host_impl()->active_tree()->UpdateScrollbarGeometries(); // Not changing the current value should not require an update. scroll_layer->SetScrollable(gfx::Size(900, 900)); clip_layer->SetBounds(gfx::Size(900, 900)); scroll_layer->SetBounds(gfx::Size(980, 980)); clip_layer->SetViewportBoundsDelta(gfx::Vector2dF(1, 2)); EXPECT_TRUE( !impl.host_impl()->active_tree()->ScrollbarGeometriesNeedUpdate()); } TEST_F(AuraScrollbarLayerTest, ScrollbarLayerCreateAfterSetScrollable) { // Scrollbar Layer can be created after SetScrollable is called and in a // separate commit. Ensure we do not missing the DidRequestShowFromMainThread // call. const int kThumbThickness = 3; const int kTrackStart = 0; scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr scroll_layer = Layer::Create(); scroll_layer->SetElementId(LayerIdToElementIdForTesting(scroll_layer->id())); scoped_refptr child1 = Layer::Create(); const bool kIsLeftSideVerticalScrollbar = false; scroll_layer->AddChild(child1); layer_tree_root->AddChild(scroll_layer); layer_tree_host_->SetRootLayer(layer_tree_root); layer_tree_root->SetBounds(gfx::Size(2, 2)); scroll_layer->SetBounds(gfx::Size(10, 10)); scroll_layer->SetScrollable(layer_tree_root->bounds()); layer_tree_host_->UpdateLayers(); LayerTreeHostImpl* host_impl = layer_tree_host_->host_impl(); host_impl->CreatePendingTree(); layer_tree_host_->CommitAndCreatePendingTree(); host_impl->ActivateSyncTree(); std::unique_ptr scrollbar(new FakeScrollbar(false, true, true)); scoped_refptr scrollbar_layer = SolidColorScrollbarLayer::Create( scrollbar->Orientation(), kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar, scroll_layer->element_id()); scroll_layer->InsertChild(scrollbar_layer, 1); layer_tree_host_->UpdateLayers(); host_impl->CreatePendingTree(); layer_tree_host_->CommitAndCreatePendingTree(); host_impl->ActivateSyncTree(); EXPECT_TRUE(host_impl->ScrollbarAnimationControllerForElementId( scroll_layer->element_id())); EffectNode* node = host_impl->active_tree()->property_trees()->effect_tree.Node( scrollbar_layer->effect_tree_index()); EXPECT_EQ(node->opacity, 1.f); } class ScrollbarLayerSolidColorThumbTest : public testing::Test { public: ScrollbarLayerSolidColorThumbTest() { LayerTreeSettings layer_tree_settings; host_impl_.reset(new FakeLayerTreeHostImpl( layer_tree_settings, &task_runner_provider_, &task_graph_runner_)); const int kThumbThickness = 3; const int kTrackStart = 0; const bool kIsLeftSideVerticalScrollbar = false; const bool kIsOverlayScrollbar = false; horizontal_scrollbar_layer_ = SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(), 1, HORIZONTAL, kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar, kIsOverlayScrollbar); vertical_scrollbar_layer_ = SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(), 2, VERTICAL, kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar, kIsOverlayScrollbar); } protected: FakeImplTaskRunnerProvider task_runner_provider_; TestTaskGraphRunner task_graph_runner_; std::unique_ptr host_impl_; std::unique_ptr horizontal_scrollbar_layer_; std::unique_ptr vertical_scrollbar_layer_; }; TEST_F(ScrollbarLayerSolidColorThumbTest, SolidColorThumbLength) { horizontal_scrollbar_layer_->SetCurrentPos(0); // Simple case - one third of the scrollable area is visible, so the thumb // should be one third as long as the track. horizontal_scrollbar_layer_->SetClipLayerLength(5.f); horizontal_scrollbar_layer_->SetScrollLayerLength(15.f); horizontal_scrollbar_layer_->SetBounds(gfx::Size(100, 3)); EXPECT_EQ(33, horizontal_scrollbar_layer_->ComputeThumbQuadRect().width()); // The thumb's length should never be less than its thickness. horizontal_scrollbar_layer_->SetClipLayerLength(0.01f); horizontal_scrollbar_layer_->SetScrollLayerLength(15.f); horizontal_scrollbar_layer_->SetBounds(gfx::Size(100, 3)); EXPECT_EQ(3, horizontal_scrollbar_layer_->ComputeThumbQuadRect().width()); } TEST_F(ScrollbarLayerSolidColorThumbTest, SolidColorThumbPosition) { horizontal_scrollbar_layer_->SetBounds(gfx::Size(100, 3)); horizontal_scrollbar_layer_->SetCurrentPos(0.f); horizontal_scrollbar_layer_->SetClipLayerLength(12.f); horizontal_scrollbar_layer_->SetScrollLayerLength(112.f); EXPECT_EQ(0, horizontal_scrollbar_layer_->ComputeThumbQuadRect().x()); EXPECT_EQ(10, horizontal_scrollbar_layer_->ComputeThumbQuadRect().width()); horizontal_scrollbar_layer_->SetCurrentPos(100); // The thumb is 10px long and the track is 100px, so the maximum thumb // position is 90px. EXPECT_EQ(90, horizontal_scrollbar_layer_->ComputeThumbQuadRect().x()); horizontal_scrollbar_layer_->SetCurrentPos(80); // The scroll position is 80% of the maximum, so the thumb's position should // be at 80% of its maximum or 72px. EXPECT_EQ(72, horizontal_scrollbar_layer_->ComputeThumbQuadRect().x()); } TEST_F(ScrollbarLayerSolidColorThumbTest, SolidColorThumbVerticalAdjust) { SolidColorScrollbarLayerImpl* layers[2] = { horizontal_scrollbar_layer_.get(), vertical_scrollbar_layer_.get() }; for (size_t i = 0; i < 2; ++i) { layers[i]->SetCurrentPos(25.f); layers[i]->SetClipLayerLength(25.f); layers[i]->SetScrollLayerLength(125.f); } layers[0]->SetBounds(gfx::Size(100, 3)); layers[1]->SetBounds(gfx::Size(3, 100)); EXPECT_EQ(gfx::Rect(20, 0, 20, 3), horizontal_scrollbar_layer_->ComputeThumbQuadRect()); EXPECT_EQ(gfx::Rect(0, 20, 3, 20), vertical_scrollbar_layer_->ComputeThumbQuadRect()); horizontal_scrollbar_layer_->SetVerticalAdjust(10.f); vertical_scrollbar_layer_->SetVerticalAdjust(10.f); // The vertical adjustment factor has two effects: // 1.) Moves the horizontal scrollbar down // 2.) Increases the vertical scrollbar's effective track length which both // increases the thumb's length and its position within the track. EXPECT_EQ(gfx::Rect(20.f, 10.f, 20.f, 3.f), horizontal_scrollbar_layer_->ComputeThumbQuadRect()); EXPECT_EQ(gfx::Rect(0.f, 22, 3.f, 22.f), vertical_scrollbar_layer_->ComputeThumbQuadRect()); } class ScrollbarLayerTestResourceCreationAndRelease : public ScrollbarLayerTest { public: void TestResourceUpload(int num_updates, size_t expected_resources, int expected_created, int expected_deleted, bool use_solid_color_scrollbar) { std::unique_ptr scrollbar(new FakeScrollbar(false, true, false)); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr content_layer = Layer::Create(); scoped_refptr scrollbar_layer; if (use_solid_color_scrollbar) { const int kThumbThickness = 3; const int kTrackStart = 0; const bool kIsLeftSideVerticalScrollbar = false; scrollbar_layer = SolidColorScrollbarLayer::Create( scrollbar->Orientation(), kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar, layer_tree_root->element_id()); } else { scrollbar_layer = PaintedScrollbarLayer::Create( std::move(scrollbar), layer_tree_root->element_id()); } layer_tree_root->AddChild(content_layer); layer_tree_root->AddChild(scrollbar_layer); layer_tree_host_->SetRootLayer(layer_tree_root); LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs( layer_tree_root.get(), layer_tree_host_->device_viewport_rect()); LayerTreeHostCommon::CalculateDrawPropertiesForTesting(&inputs); scrollbar_layer->SetIsDrawable(true); scrollbar_layer->SetBounds(gfx::Size(100, 100)); layer_tree_root->SetScrollable(gfx::Size(100, 200)); layer_tree_root->SetScrollOffset(gfx::ScrollOffset(10, 20)); layer_tree_root->SetBounds(gfx::Size(100, 200)); content_layer->SetBounds(gfx::Size(100, 200)); testing::Mock::VerifyAndClearExpectations(layer_tree_host_.get()); EXPECT_EQ(scrollbar_layer->layer_tree_host(), layer_tree_host_.get()); for (int update_counter = 0; update_counter < num_updates; update_counter++) scrollbar_layer->Update(); // A non-solid-color scrollbar should have requested two textures. EXPECT_EQ(expected_resources, fake_ui_resource_manager_->UIResourceCount()); EXPECT_EQ(expected_created, fake_ui_resource_manager_->TotalUIResourceCreated()); EXPECT_EQ(expected_deleted, fake_ui_resource_manager_->TotalUIResourceDeleted()); testing::Mock::VerifyAndClearExpectations(layer_tree_host_.get()); } }; TEST_F(ScrollbarLayerTestResourceCreationAndRelease, ResourceUpload) { bool use_solid_color_scrollbars = false; TestResourceUpload(0, 0, 0, 0, use_solid_color_scrollbars); int num_updates[3] = {1, 5, 10}; int created = 0; int deleted = 0; for (int j = 0; j < 3; j++) { created += num_updates[j] * 2; deleted = created - 2; TestResourceUpload(num_updates[j], 2, created, deleted, use_solid_color_scrollbars); } } TEST_F(ScrollbarLayerTestResourceCreationAndRelease, SolidColorNoResourceUpload) { bool use_solid_color_scrollbars = true; TestResourceUpload(0, 0, 0, 0, use_solid_color_scrollbars); TestResourceUpload(1, 0, 0, 0, use_solid_color_scrollbars); } TEST_F(ScrollbarLayerTestResourceCreationAndRelease, TestResourceUpdate) { gfx::Point scrollbar_location(0, 185); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr content_layer = Layer::Create(); scoped_refptr scrollbar_layer = FakePaintedScrollbarLayer::Create(false, true, layer_tree_root->element_id()); layer_tree_root->AddChild(content_layer); layer_tree_root->AddChild(scrollbar_layer); layer_tree_host_->SetRootLayer(layer_tree_root); LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs( layer_tree_root.get(), layer_tree_host_->device_viewport_rect()); LayerTreeHostCommon::CalculateDrawPropertiesForTesting(&inputs); scrollbar_layer->SetIsDrawable(true); scrollbar_layer->SetBounds(gfx::Size(100, 15)); scrollbar_layer->SetPosition(gfx::PointF(scrollbar_location)); layer_tree_root->SetBounds(gfx::Size(100, 200)); content_layer->SetBounds(gfx::Size(100, 200)); testing::Mock::VerifyAndClearExpectations(layer_tree_host_.get()); EXPECT_EQ(scrollbar_layer->layer_tree_host(), layer_tree_host_.get()); size_t resource_count; int expected_created, expected_deleted; resource_count = 2; expected_created = 2; expected_deleted = 0; EXPECT_TRUE(scrollbar_layer->Update()); EXPECT_NE(0, scrollbar_layer->track_resource_id()); EXPECT_NE(0, scrollbar_layer->thumb_resource_id()); EXPECT_EQ(resource_count, fake_ui_resource_manager_->UIResourceCount()); EXPECT_EQ(expected_created, fake_ui_resource_manager_->TotalUIResourceCreated()); EXPECT_EQ(expected_deleted, fake_ui_resource_manager_->TotalUIResourceDeleted()); resource_count = 0; expected_created = 2; expected_deleted = 2; scrollbar_layer->SetBounds(gfx::Size(0, 0)); scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(0, 0, 0, 0)); EXPECT_TRUE(scrollbar_layer->Update()); EXPECT_EQ(0, scrollbar_layer->track_resource_id()); EXPECT_EQ(0, scrollbar_layer->thumb_resource_id()); EXPECT_EQ(resource_count, fake_ui_resource_manager_->UIResourceCount()); EXPECT_EQ(expected_created, fake_ui_resource_manager_->TotalUIResourceCreated()); EXPECT_EQ(expected_deleted, fake_ui_resource_manager_->TotalUIResourceDeleted()); resource_count = 0; expected_created = 2; expected_deleted = 2; scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(0, 0, 0, 0)); EXPECT_FALSE(scrollbar_layer->Update()); EXPECT_EQ(0, scrollbar_layer->track_resource_id()); EXPECT_EQ(0, scrollbar_layer->thumb_resource_id()); EXPECT_EQ(resource_count, fake_ui_resource_manager_->UIResourceCount()); EXPECT_EQ(expected_created, fake_ui_resource_manager_->TotalUIResourceCreated()); EXPECT_EQ(expected_deleted, fake_ui_resource_manager_->TotalUIResourceDeleted()); resource_count = 2; expected_created = 4; expected_deleted = 2; scrollbar_layer->SetBounds(gfx::Size(100, 15)); scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(30, 10, 50, 10)); EXPECT_TRUE(scrollbar_layer->Update()); EXPECT_NE(0, scrollbar_layer->track_resource_id()); EXPECT_NE(0, scrollbar_layer->thumb_resource_id()); EXPECT_EQ(resource_count, fake_ui_resource_manager_->UIResourceCount()); EXPECT_EQ(expected_created, fake_ui_resource_manager_->TotalUIResourceCreated()); EXPECT_EQ(expected_deleted, fake_ui_resource_manager_->TotalUIResourceDeleted()); resource_count = 1; expected_created = 5; expected_deleted = 4; scrollbar_layer->fake_scrollbar()->set_has_thumb(false); EXPECT_TRUE(scrollbar_layer->Update()); EXPECT_NE(0, scrollbar_layer->track_resource_id()); EXPECT_EQ(0, scrollbar_layer->thumb_resource_id()); EXPECT_EQ(resource_count, fake_ui_resource_manager_->UIResourceCount()); EXPECT_EQ(expected_created, fake_ui_resource_manager_->TotalUIResourceCreated()); EXPECT_EQ(expected_deleted, fake_ui_resource_manager_->TotalUIResourceDeleted()); resource_count = 0; expected_created = 5; expected_deleted = 5; scrollbar_layer->SetBounds(gfx::Size(0, 0)); scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(0, 0, 0, 0)); EXPECT_TRUE(scrollbar_layer->Update()); EXPECT_EQ(0, scrollbar_layer->track_resource_id()); EXPECT_EQ(0, scrollbar_layer->thumb_resource_id()); EXPECT_EQ(resource_count, fake_ui_resource_manager_->UIResourceCount()); EXPECT_EQ(expected_created, fake_ui_resource_manager_->TotalUIResourceCreated()); EXPECT_EQ(expected_deleted, fake_ui_resource_manager_->TotalUIResourceDeleted()); resource_count = 2; expected_created = 7; expected_deleted = 5; scrollbar_layer->SetBounds(gfx::Size(100, 15)); scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(30, 10, 50, 10)); scrollbar_layer->fake_scrollbar()->set_has_thumb(true); EXPECT_TRUE(scrollbar_layer->Update()); EXPECT_NE(0, scrollbar_layer->track_resource_id()); EXPECT_NE(0, scrollbar_layer->thumb_resource_id()); resource_count = 2; expected_created = 9; expected_deleted = 7; scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(0, 0, 0, 0)); EXPECT_TRUE(scrollbar_layer->Update()); EXPECT_NE(0, scrollbar_layer->track_resource_id()); EXPECT_NE(0, scrollbar_layer->thumb_resource_id()); EXPECT_EQ(resource_count, fake_ui_resource_manager_->UIResourceCount()); EXPECT_EQ(expected_created, fake_ui_resource_manager_->TotalUIResourceCreated()); EXPECT_EQ(expected_deleted, fake_ui_resource_manager_->TotalUIResourceDeleted()); resource_count = 1; expected_created = 10; expected_deleted = 9; scrollbar_layer->fake_scrollbar()->set_track_rect(gfx::Rect(30, 10, 50, 10)); scrollbar_layer->fake_scrollbar()->set_has_thumb(false); scrollbar_layer->SetBounds(gfx::Size(90, 15)); EXPECT_TRUE(scrollbar_layer->Update()); EXPECT_EQ(resource_count, fake_ui_resource_manager_->UIResourceCount()); EXPECT_EQ(expected_created, fake_ui_resource_manager_->TotalUIResourceCreated()); EXPECT_EQ(expected_deleted, fake_ui_resource_manager_->TotalUIResourceDeleted()); EXPECT_EQ(gfx::Size(90, 15), fake_ui_resource_manager_->ui_resource_size( scrollbar_layer->track_resource_id())); // Simulate commit to compositor thread. scrollbar_layer->PushPropertiesTo( scrollbar_layer->CreateLayerImpl(layer_tree_host_->active_tree()).get()); EXPECT_FALSE(scrollbar_layer->Update()); EXPECT_NE(0, scrollbar_layer->track_resource_id()); EXPECT_EQ(0, scrollbar_layer->thumb_resource_id()); EXPECT_EQ(resource_count, fake_ui_resource_manager_->UIResourceCount()); EXPECT_EQ(expected_created, fake_ui_resource_manager_->TotalUIResourceCreated()); EXPECT_EQ(expected_deleted, fake_ui_resource_manager_->TotalUIResourceDeleted()); testing::Mock::VerifyAndClearExpectations(layer_tree_host_.get()); } class ScaledScrollbarLayerTestResourceCreation : public ScrollbarLayerTest { public: void TestResourceUpload(float test_scale) { gfx::Point scrollbar_location(0, 185); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr content_layer = Layer::Create(); scoped_refptr scrollbar_layer = FakePaintedScrollbarLayer::Create(false, true, layer_tree_root->element_id()); layer_tree_root->AddChild(content_layer); layer_tree_root->AddChild(scrollbar_layer); layer_tree_host_->SetRootLayer(layer_tree_root); scrollbar_layer->SetIsDrawable(true); scrollbar_layer->SetBounds(gfx::Size(100, 15)); scrollbar_layer->SetPosition(gfx::PointF(scrollbar_location)); layer_tree_root->SetBounds(gfx::Size(100, 200)); content_layer->SetBounds(gfx::Size(100, 200)); EXPECT_EQ(scrollbar_layer->layer_tree_host(), layer_tree_host_.get()); LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs( layer_tree_root.get(), layer_tree_host_->device_viewport_rect()); inputs.device_scale_factor = test_scale; LayerTreeHostCommon::CalculateDrawPropertiesForTesting(&inputs); layer_tree_host_->SetViewportRectAndScale( layer_tree_host_->device_viewport_rect(), test_scale, layer_tree_host_->local_surface_id_allocation_from_parent()); scrollbar_layer->Update(); // Verify that we have not generated any content uploads that are larger // than their destination textures. gfx::Size track_size = fake_ui_resource_manager_->ui_resource_size( scrollbar_layer->track_resource_id()); gfx::Size thumb_size = fake_ui_resource_manager_->ui_resource_size( scrollbar_layer->thumb_resource_id()); EXPECT_LE(track_size.width(), scrollbar_layer->internal_content_bounds().width()); EXPECT_LE(track_size.height(), scrollbar_layer->internal_content_bounds().height()); EXPECT_LE(thumb_size.width(), scrollbar_layer->internal_content_bounds().width()); EXPECT_LE(thumb_size.height(), scrollbar_layer->internal_content_bounds().height()); } }; TEST_F(ScaledScrollbarLayerTestResourceCreation, ScaledResourceUpload) { // Pick a test scale that moves the scrollbar's (non-zero) position to // a non-pixel-aligned location. TestResourceUpload(.041f); TestResourceUpload(1.41f); TestResourceUpload(4.1f); // Try something extreme to be larger than max texture size, and make it a // non-integer for funsies. scoped_refptr context = viz::TestContextProvider::Create(); // Keep the max texture size reasonable so we don't OOM on low end devices // (crbug.com/642333). context->UnboundTestContextGL()->set_max_texture_size(512); context->BindToCurrentThread(); int max_texture_size = 0; context->ContextGL()->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); EXPECT_EQ(512, max_texture_size); TestResourceUpload(max_texture_size / 9.9f); } class ScaledScrollbarLayerTestScaledRasterization : public ScrollbarLayerTest { public: void TestScale(const gfx::Rect& scrollbar_rect, float test_scale) { bool paint_during_update = true; bool has_thumb = false; scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr scrollbar_layer = FakePaintedScrollbarLayer::Create(paint_during_update, has_thumb, layer_tree_root->element_id()); layer_tree_root->AddChild(scrollbar_layer); layer_tree_host_->SetRootLayer(layer_tree_root); scrollbar_layer->SetBounds(scrollbar_rect.size()); scrollbar_layer->SetPosition(gfx::PointF(scrollbar_rect.origin())); scrollbar_layer->fake_scrollbar()->set_location(scrollbar_rect.origin()); scrollbar_layer->fake_scrollbar()->set_track_rect(scrollbar_rect); LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs( layer_tree_root.get(), layer_tree_host_->device_viewport_rect()); inputs.device_scale_factor = test_scale; LayerTreeHostCommon::CalculateDrawPropertiesForTesting(&inputs); layer_tree_host_->SetViewportRectAndScale( layer_tree_host_->device_viewport_rect(), test_scale, layer_tree_host_->local_surface_id_allocation_from_parent()); scrollbar_layer->Update(); UIResourceBitmap* bitmap = fake_ui_resource_manager_->ui_resource_bitmap( scrollbar_layer->track_resource_id()); DCHECK(bitmap); const SkColor* pixels = reinterpret_cast(bitmap->GetPixels()); SkColor color = argb_to_skia( scrollbar_layer->fake_scrollbar()->paint_fill_color()); int width = bitmap->GetSize().width(); int height = bitmap->GetSize().height(); // Make sure none of the corners of the bitmap were inadvertently clipped. EXPECT_EQ(color, pixels[0]) << "Top left pixel doesn't match scrollbar color."; EXPECT_EQ(color, pixels[width - 1]) << "Top right pixel doesn't match scrollbar color."; EXPECT_EQ(color, pixels[width * (height - 1)]) << "Bottom left pixel doesn't match scrollbar color."; EXPECT_EQ(color, pixels[width * height - 1]) << "Bottom right pixel doesn't match scrollbar color."; } protected: // On Android, Skia uses ABGR static SkColor argb_to_skia(SkColor c) { return (SkColorGetA(c) << SK_A32_SHIFT) | (SkColorGetR(c) << SK_R32_SHIFT) | (SkColorGetG(c) << SK_G32_SHIFT) | (SkColorGetB(c) << SK_B32_SHIFT); } }; TEST_F(ScaledScrollbarLayerTestScaledRasterization, TestLostPrecisionInClip) { // Try rasterization at coordinates and scale that caused problematic // rounding and clipping errors. // Vertical Scrollbars. TestScale(gfx::Rect(1240, 0, 15, 1333), 2.7754839f); TestScale(gfx::Rect(1240, 0, 15, 677), 2.46677136f); // Horizontal Scrollbars. TestScale(gfx::Rect(0, 1240, 1333, 15), 2.7754839f); TestScale(gfx::Rect(0, 1240, 677, 15), 2.46677136f); } } // namespace cc