| // Copyright 2026 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cc/trees/client_layer_tree_host_impl.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <cmath> |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/memory_pressure_listener.h" |
| #include "base/memory/memory_pressure_listener_registry.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/numerics/angle_conversions.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/gtest_util.h" |
| #include "base/test/run_until.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "cc/animation/animation.h" |
| #include "cc/animation/animation_host.h" |
| #include "cc/animation/animation_id_provider.h" |
| #include "cc/base/features.h" |
| #include "cc/input/browser_controls_offset_manager.h" |
| #include "cc/input/input_handler.h" |
| #include "cc/input/main_thread_scrolling_reason.h" |
| #include "cc/input/page_scale_animation.h" |
| #include "cc/input/scroll_elasticity_helper.h" |
| #include "cc/input/scroll_utils.h" |
| #include "cc/input/scrollbar_controller.h" |
| #include "cc/layers/append_quads_context.h" |
| #include "cc/layers/append_quads_data.h" |
| #include "cc/layers/layer_impl.h" |
| #include "cc/layers/nine_patch_thumb_scrollbar_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/surface_layer_impl.h" |
| #include "cc/layers/viewport.h" |
| #include "cc/metrics/compositor_frame_reporting_controller.h" |
| #include "cc/metrics/frame_info.h" |
| #include "cc/resources/ui_resource_bitmap.h" |
| #include "cc/resources/ui_resource_manager.h" |
| #include "cc/test/animation_test_common.h" |
| #include "cc/test/event_metrics_test_creator.h" |
| #include "cc/test/fake_frame_info.h" |
| #include "cc/test/fake_impl_task_runner_provider.h" |
| #include "cc/test/fake_layer_tree_frame_sink.h" |
| #include "cc/test/fake_layer_tree_host_impl.h" |
| #include "cc/test/fake_picture_layer_impl.h" |
| #include "cc/test/fake_raster_source.h" |
| #include "cc/test/fake_recording_source.h" |
| #include "cc/test/fake_video_frame_provider.h" |
| #include "cc/test/layer_test_common.h" |
| #include "cc/test/layer_tree_test.h" |
| #include "cc/test/mock_latency_info_swap_promise_monitor.h" |
| #include "cc/test/skia_common.h" |
| #include "cc/test/test_layer_tree_frame_sink.h" |
| #include "cc/test/test_paint_worklet_layer_painter.h" |
| #include "cc/test/test_task_graph_runner.h" |
| #include "cc/trees/clip_node.h" |
| #include "cc/trees/compositor_commit_data.h" |
| #include "cc/trees/draw_property_utils.h" |
| #include "cc/trees/effect_node.h" |
| #include "cc/trees/frame_data.h" |
| #include "cc/trees/latency_info_swap_promise.h" |
| #include "cc/trees/layer_tree_host_impl_delegate.h" |
| #include "cc/trees/layer_tree_host_impl_test_base.h" |
| #include "cc/trees/layer_tree_impl.h" |
| #include "cc/trees/mutator_host.h" |
| #include "cc/trees/render_frame_metadata.h" |
| #include "cc/trees/render_frame_metadata_observer.h" |
| #include "cc/trees/scroll_node.h" |
| #include "cc/trees/transform_node.h" |
| #include "cc/view_transition/view_transition_request.h" |
| #include "components/viz/client/client_resource_provider.h" |
| #include "components/viz/common/frame_sinks/begin_frame_args.h" |
| #include "components/viz/common/frame_sinks/copy_output_request.h" |
| #include "components/viz/common/frame_sinks/copy_output_result.h" |
| #include "components/viz/common/frame_timing_details.h" |
| #include "components/viz/common/quads/compositor_frame_metadata.h" |
| #include "components/viz/common/quads/compositor_render_pass_draw_quad.h" |
| #include "components/viz/common/quads/solid_color_draw_quad.h" |
| #include "components/viz/common/quads/texture_draw_quad.h" |
| #include "components/viz/common/quads/tile_draw_quad.h" |
| #include "components/viz/common/surfaces/frame_sink_id.h" |
| #include "components/viz/common/surfaces/region_capture_bounds.h" |
| #include "components/viz/service/display/skia_output_surface.h" |
| #include "components/viz/service/layers/viz_layer_tree_host_impl.h" |
| #include "components/viz/test/begin_frame_args_test.h" |
| #include "components/viz/test/fake_output_surface.h" |
| #include "components/viz/test/fake_skia_output_surface.h" |
| #include "gpu/GLES2/gl2extchromium.h" |
| #include "gpu/command_buffer/client/client_shared_image.h" |
| #include "media/base/media.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkMallocPixelRef.h" |
| #include "ui/events/types/scroll_input_type.h" |
| #include "ui/gfx/geometry/point_conversions.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| #include "ui/gfx/geometry/test/geometry_util.h" |
| #include "ui/gfx/geometry/transform_operations.h" |
| #include "ui/gfx/geometry/vector2d_conversions.h" |
| #include "ui/latency/latency_info.h" |
| |
| #define EXPECT_SCOPED(statements) \ |
| { \ |
| SCOPED_TRACE(""); \ |
| statements; \ |
| } |
| |
| using media::VideoFrame; |
| using ::testing::_; |
| using ::testing::AnyNumber; |
| using ::testing::AtLeast; |
| using ::testing::ElementsAre; |
| using ::testing::Mock; |
| using ::testing::Pointee; |
| using ::testing::Pointer; |
| using ::testing::Pointwise; |
| using ::testing::Property; |
| using ::testing::Range; |
| using ::testing::Return; |
| using ::testing::StrictMock; |
| |
| using ScrollThread = cc::InputHandler::ScrollThread; |
| |
| namespace cc { |
| namespace { |
| |
| MATCHER(UniquePtrMatches, negation ? "do not match" : "match") { |
| return std::get<0>(arg).get() == std::get<1>(arg); |
| } |
| |
| ClientLayerTreeHostImpl* client_host_impl(LayerTreeHostImpl* host) { |
| return static_cast<ClientLayerTreeHostImpl*>(host); |
| } |
| |
| class TestInputHandlerClient : public InputHandlerClient { |
| public: |
| TestInputHandlerClient() = default; |
| ~TestInputHandlerClient() override = default; |
| |
| // InputHandlerClient implementation. |
| void WillShutdown() override {} |
| void Animate(base::TimeTicks time) override {} |
| void ReconcileElasticOverscrollAndRootScroll() override {} |
| void SetPrefersReducedMotion(bool prefers_reduced_motion) override {} |
| void UpdateRootLayerStateForSynchronousInputHandler( |
| const gfx::PointF& total_scroll_offset, |
| const gfx::PointF& 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; |
| } |
| void DeliverInputForBeginFrame(const viz::BeginFrameArgs& args) override {} |
| void DeliverInputForHighLatencyMode() override {} |
| void DeliverInputForDeadline() override {} |
| void DidFinishImplFrame() override {} |
| bool HasQueuedInput() const override { return false; } |
| void SetScrollEventDispatchMode( |
| InputHandlerClient::ScrollEventDispatchMode mode, |
| double scroll_deadline_ratio) override {} |
| |
| gfx::PointF last_set_scroll_offset() { return last_set_scroll_offset_; } |
| gfx::PointF 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::PointF last_set_scroll_offset_; |
| gfx::PointF max_scroll_offset_; |
| gfx::SizeF scrollable_size_; |
| float page_scale_factor_ = 0; |
| float min_page_scale_factor_ = -1; |
| float max_page_scale_factor_ = -1; |
| }; |
| |
| // Test fixture that runs in all tree modes except for TreesInViz Service |
| // mode. |
| class ClientModeLayerTreeHostImplTest : public LayerTreeHostImplTest {}; |
| |
| class LayerTreeHostImplTestMultiScrollable : public LayerTreeHostImplTest { |
| public: |
| void SetUpLayers(LayerTreeSettings settings) { |
| is_aura_scrollbar_ = |
| settings.scrollbar_animator == LayerTreeSettings::AURA_OVERLAY; |
| gfx::Size viewport_size(300, 200); |
| gfx::Size content_size(1000, 1000); |
| gfx::Size child_layer_size(250, 150); |
| gfx::Size scrollbar_size_1(gfx::Size(15, viewport_size.height())); |
| gfx::Size scrollbar_size_2(gfx::Size(15, child_layer_size.height())); |
| |
| CreateHostImpl(settings, CreateLayerTreeFrameSink()); |
| host_impl_->active_tree()->SetDeviceScaleFactor(1); |
| SetupViewportLayers(host_impl_->active_tree(), viewport_size, content_size, |
| content_size); |
| LayerImpl* root_scroll = OuterViewportScrollLayer(); |
| |
| // scrollbar_1 on root scroll. |
| scrollbar_1_ = AddLayer<SolidColorScrollbarLayerImpl>( |
| host_impl_->active_tree(), ScrollbarOrientation::kVertical, 15, 0, |
| true); |
| SetupScrollbarLayer(root_scroll, scrollbar_1_); |
| scrollbar_1_->SetBounds(scrollbar_size_1); |
| TouchActionRegion touch_action_region; |
| touch_action_region.Union(TouchAction::kNone, gfx::Rect(scrollbar_size_1)); |
| scrollbar_1_->SetTouchActionRegion(touch_action_region); |
| |
| // scrollbar_2 on child. |
| auto* child = |
| AddScrollableLayer(root_scroll, gfx::Size(100, 100), child_layer_size); |
| GetTransformNode(child)->post_translation = gfx::Vector2dF(50, 50); |
| |
| scrollbar_2_ = AddLayer<SolidColorScrollbarLayerImpl>( |
| host_impl_->active_tree(), ScrollbarOrientation::kVertical, 15, 0, |
| true); |
| SetupScrollbarLayer(child, scrollbar_2_); |
| scrollbar_2_->SetBounds(scrollbar_size_2); |
| |
| host_impl_->active_tree()->UpdateAllScrollbarGeometriesForTesting(); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| host_impl_->active_tree()->DidBecomeActive(); |
| |
| ResetScrollbars(); |
| } |
| |
| void ResetScrollbars() { |
| GetEffectNode(scrollbar_1_.get())->opacity = 0; |
| GetEffectNode(scrollbar_2_.get())->opacity = 0; |
| UpdateDrawProperties(host_impl_->active_tree()); |
| |
| if (is_aura_scrollbar_) { |
| animation_task_.Reset(); |
| } |
| } |
| |
| bool is_aura_scrollbar_; |
| raw_ptr<SolidColorScrollbarLayerImpl> scrollbar_1_; |
| raw_ptr<SolidColorScrollbarLayerImpl> scrollbar_2_; |
| }; |
| |
| class LayerTreeHostImplBrowserControlsTest : public LayerTreeHostImplTest { |
| public: |
| LayerTreeHostImplBrowserControlsTest() |
| // 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_) { |
| viewport_size_ = gfx::Size(clip_size_.width(), |
| clip_size_.height() + top_controls_height_); |
| } |
| |
| bool CreateHostImpl( |
| const LayerTreeSettings& settings, |
| std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink) override { |
| bool init = LayerTreeHostImplTest::CreateHostImpl( |
| settings, std::move(layer_tree_frame_sink)); |
| if (init) { |
| host_impl_->active_tree()->SetBrowserControlsParams( |
| {top_controls_height_, 0, 0, 0, false, false}); |
| host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(1.f, 1.f); |
| host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f); |
| } |
| return init; |
| } |
| |
| protected: |
| void SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| const gfx::Size& inner_viewport_size, |
| const gfx::Size& outer_viewport_size, |
| const gfx::Size& scroll_layer_size) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| host_impl_->active_tree(), inner_viewport_size, outer_viewport_size, |
| scroll_layer_size); |
| } |
| |
| void SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| LayerTreeImpl* tree_impl, |
| const gfx::Size& inner_viewport_size, |
| const gfx::Size& outer_viewport_size, |
| const gfx::Size& scroll_layer_size) { |
| tree_impl->SetBrowserControlsParams( |
| {top_controls_height_, 0, 0, 0, false, true}); |
| tree_impl->SetCurrentBrowserControlsShownRatio(1.f, 1.f); |
| tree_impl->PushPageScaleFromMainThread(1.f, 1.f, 1.f); |
| host_impl_->DidChangeBrowserControlsPosition(); |
| |
| SetupViewportLayers(tree_impl, inner_viewport_size, outer_viewport_size, |
| scroll_layer_size); |
| } |
| |
| void ExpectViewportGeometries(float expected_browser_controls_shown_ratio) { |
| auto* tree = host_impl_->active_tree(); |
| auto* property_trees = tree->property_trees(); |
| EXPECT_EQ(expected_browser_controls_shown_ratio, |
| tree->CurrentTopControlsShownRatio()); |
| EXPECT_EQ( |
| tree->top_controls_height() * expected_browser_controls_shown_ratio, |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| int delta = (tree->top_controls_height() + tree->bottom_controls_height()) * |
| (1 - expected_browser_controls_shown_ratio); |
| int scaled_delta = delta / tree->min_page_scale_factor(); |
| gfx::Size inner_scroll_bounds = tree->InnerViewportScrollNode()->bounds; |
| inner_scroll_bounds.Enlarge(0, scaled_delta); |
| EXPECT_EQ(inner_scroll_bounds, InnerViewportScrollLayer()->bounds()); |
| EXPECT_EQ(gfx::RectF(gfx::SizeF(inner_scroll_bounds)), |
| tree->OuterViewportClipNode()->clip); |
| EXPECT_EQ(gfx::Vector2dF(0, delta), |
| property_trees->inner_viewport_container_bounds_delta()); |
| EXPECT_EQ(gfx::Vector2dF(0, scaled_delta), |
| property_trees->outer_viewport_container_bounds_delta()); |
| } |
| |
| gfx::Size layer_size_; |
| gfx::Size clip_size_; |
| gfx::Size viewport_size_; |
| float top_controls_height_ = 50; |
| }; // class LayerTreeHostImplBrowserControlsTest |
| |
| class LayerTreeHostImplWithImplicitLimitsTest : public LayerTreeHostImplTest { |
| public: |
| LayerTreeSettings DefaultSettings() override { |
| LayerTreeSettings settings = LayerTreeHostImplTest::DefaultSettings(); |
| settings.max_memory_for_prepaint_percentage = 50; |
| return settings; |
| } |
| }; |
| |
| // Test implementation of RenderFrameMetadataObserver which can optionally |
| // request the frame-token to be sent to the embedder during frame submission. |
| class TestRenderFrameMetadataObserver : public RenderFrameMetadataObserver { |
| public: |
| explicit TestRenderFrameMetadataObserver(bool increment_counter) |
| : increment_counter_(increment_counter) {} |
| TestRenderFrameMetadataObserver(const TestRenderFrameMetadataObserver&) = |
| delete; |
| ~TestRenderFrameMetadataObserver() override = default; |
| |
| TestRenderFrameMetadataObserver& operator=( |
| const TestRenderFrameMetadataObserver&) = delete; |
| |
| void BindToCurrentSequence() override {} |
| void OnRenderFrameSubmission( |
| const RenderFrameMetadata& render_frame_metadata, |
| viz::CompositorFrameMetadata* compositor_frame_metadata, |
| bool force_send) override { |
| if (increment_counter_) { |
| compositor_frame_metadata->send_frame_token_to_embedder = true; |
| } |
| last_metadata_ = render_frame_metadata; |
| } |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) |
| void DidEndScroll() override {} |
| #endif |
| |
| const std::optional<RenderFrameMetadata>& last_metadata() const { |
| return last_metadata_; |
| } |
| |
| private: |
| bool increment_counter_; |
| std::optional<RenderFrameMetadata> last_metadata_; |
| }; |
| |
| |
| // Checks that we use the memory limits provided. |
| // TODO(crbug.com/458776836): Review memory limit related cc_unittests for |
| // TreesInViz Service mode |
| TEST_P(ClientModeLayerTreeHostImplTest, MemoryLimits) { |
| host_impl_->ReleaseLayerTreeFrameSink(); |
| host_impl_ = nullptr; |
| |
| const size_t kGpuByteLimit = 1234321; |
| const size_t kGpuResourceLimit = 2345432; |
| const gpu::MemoryAllocation::PriorityCutoff kGpuCutoff = |
| gpu::MemoryAllocation::CUTOFF_ALLOW_EVERYTHING; |
| |
| const TileMemoryLimitPolicy kGpuTileCutoff = |
| ManagedMemoryPolicy::PriorityCutoffToTileMemoryLimitPolicy(kGpuCutoff); |
| const TileMemoryLimitPolicy kNothingTileCutoff = |
| ManagedMemoryPolicy::PriorityCutoffToTileMemoryLimitPolicy( |
| gpu::MemoryAllocation::CUTOFF_ALLOW_NOTHING); |
| EXPECT_NE(kGpuTileCutoff, kNothingTileCutoff); |
| |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.memory_policy = |
| ManagedMemoryPolicy(kGpuByteLimit, kGpuCutoff, kGpuResourceLimit); |
| host_impl_ = CreateLayerTreeHostImplForTesting( |
| settings, this, &task_runner_provider_, &stats_instrumentation_, |
| &task_graph_runner_, |
| AnimationHost::CreateForTesting(ThreadInstance::kImpl), nullptr, 0, |
| nullptr, nullptr); |
| InputHandler::Create(static_cast<CompositorDelegateForInput&>(*host_impl_)); |
| |
| // Gpu compositing. |
| layer_tree_frame_sink_ = FakeLayerTreeFrameSink::Create3d(); |
| host_impl_->SetVisible(true); |
| host_impl_->InitializeFrameSink(layer_tree_frame_sink_.get()); |
| { |
| const auto& state = host_impl_->global_tile_state(); |
| EXPECT_EQ(kGpuByteLimit, state.hard_memory_limit_in_bytes); |
| EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit); |
| EXPECT_EQ(kGpuTileCutoff, state.memory_limit_policy); |
| } |
| |
| // Not visible, drops to 0. |
| host_impl_->SetVisible(false); |
| { |
| const auto& state = host_impl_->global_tile_state(); |
| EXPECT_EQ(0u, state.hard_memory_limit_in_bytes); |
| EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit); |
| EXPECT_EQ(kNothingTileCutoff, state.memory_limit_policy); |
| } |
| |
| // Visible, is the gpu limit again. |
| host_impl_->SetVisible(true); |
| { |
| const auto& state = host_impl_->global_tile_state(); |
| EXPECT_EQ(kGpuByteLimit, state.hard_memory_limit_in_bytes); |
| EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit); |
| } |
| |
| // Software compositing. |
| host_impl_->ReleaseLayerTreeFrameSink(); |
| layer_tree_frame_sink_ = FakeLayerTreeFrameSink::CreateSoftware(); |
| host_impl_->InitializeFrameSink(layer_tree_frame_sink_.get()); |
| { |
| const auto& state = host_impl_->global_tile_state(); |
| EXPECT_EQ(kGpuByteLimit, state.hard_memory_limit_in_bytes); |
| EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit); |
| EXPECT_EQ(kGpuTileCutoff, state.memory_limit_policy); |
| } |
| |
| // Not visible, drops to 0. |
| host_impl_->SetVisible(false); |
| { |
| const auto& state = host_impl_->global_tile_state(); |
| EXPECT_EQ(0u, state.hard_memory_limit_in_bytes); |
| EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit); |
| EXPECT_EQ(kNothingTileCutoff, state.memory_limit_policy); |
| } |
| |
| // Visible, is the software limit again. |
| host_impl_->SetVisible(true); |
| { |
| const auto& state = host_impl_->global_tile_state(); |
| EXPECT_EQ(kGpuByteLimit, state.hard_memory_limit_in_bytes); |
| EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit); |
| EXPECT_EQ(kGpuTileCutoff, state.memory_limit_policy); |
| } |
| } |
| |
| // UIResource tests do not apply to TIV Service. |
| TEST_P(ClientModeLayerTreeHostImplTest, UIResourceManagement) { |
| auto test_context_provider = viz::TestContextProvider::CreateRaster(); |
| gpu::TestSharedImageInterface* sii = |
| test_context_provider->SharedImageInterface(); |
| CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::Create3d( |
| std::move(test_context_provider))); |
| |
| EXPECT_EQ(0u, sii->shared_image_count()); |
| |
| 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, sii->shared_image_count()); |
| viz::ResourceId id1 = host_impl_->ResourceIdForUIResource(ui_resource_id); |
| EXPECT_NE(viz::kInvalidResourceId, id1); |
| |
| // Multiple requests with the same id is allowed. The previous texture is |
| // deleted. |
| host_impl_->CreateUIResource(ui_resource_id, bitmap); |
| EXPECT_EQ(1u, sii->shared_image_count()); |
| viz::ResourceId id2 = host_impl_->ResourceIdForUIResource(ui_resource_id); |
| EXPECT_NE(viz::kInvalidResourceId, id2); |
| EXPECT_NE(id1, id2); |
| |
| // Deleting invalid UIResourceId is allowed and does not change state. |
| host_impl_->DeleteUIResource(-1); |
| EXPECT_EQ(1u, sii->shared_image_count()); |
| |
| // Should return zero for invalid UIResourceId. Number of textures should |
| // not change. |
| EXPECT_EQ(viz::kInvalidResourceId, host_impl_->ResourceIdForUIResource(-1)); |
| EXPECT_EQ(1u, sii->shared_image_count()); |
| |
| host_impl_->DeleteUIResource(ui_resource_id); |
| EXPECT_EQ(viz::kInvalidResourceId, |
| host_impl_->ResourceIdForUIResource(ui_resource_id)); |
| EXPECT_EQ(0u, sii->shared_image_count()); |
| |
| // Should not change state for multiple deletion on one UIResourceId |
| host_impl_->DeleteUIResource(ui_resource_id); |
| EXPECT_EQ(0u, sii->shared_image_count()); |
| } |
| |
| TEST_P(ClientModeLayerTreeHostImplTest, CreateETC1UIResource) { |
| auto test_context_provider = viz::TestContextProvider::CreateRaster(); |
| gpu::TestSharedImageInterface* sii = |
| test_context_provider->SharedImageInterface(); |
| CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::Create3d( |
| std::move(test_context_provider))); |
| |
| EXPECT_EQ(0u, sii->shared_image_count()); |
| |
| gfx::Size size(4, 4); |
| SkImageInfo info = |
| SkImageInfo::Make(4, 4, kAlpha_8_SkColorType, kPremul_SkAlphaType); |
| sk_sp<SkPixelRef> pixel_ref(SkMallocPixelRef::MakeAllocate(info, 0)); |
| pixel_ref->setImmutable(); |
| UIResourceBitmap bitmap(std::move(pixel_ref), size); |
| UIResourceId ui_resource_id = 1; |
| host_impl_->CreateUIResource(ui_resource_id, bitmap); |
| EXPECT_EQ(1u, sii->shared_image_count()); |
| viz::ResourceId id1 = host_impl_->ResourceIdForUIResource(ui_resource_id); |
| EXPECT_NE(viz::kInvalidResourceId, id1); |
| } |
| |
| TEST_P(ClientModeLayerTreeHostImplTest, |
| SelectionBoundsPassedToRenderFrameMetadata) { |
| LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10)); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| |
| auto observer = std::make_unique<TestRenderFrameMetadataObserver>(false); |
| auto* observer_ptr = observer.get(); |
| host_impl_->SetRenderFrameObserver(std::move(observer)); |
| EXPECT_FALSE(observer_ptr->last_metadata()); |
| |
| // Trigger a draw-swap sequence. |
| host_impl_->SetNeedsRedraw(/*animation_only=*/false, |
| /*skip_if_inside_draw=*/false); |
| DrawFrame(); |
| |
| // Ensure the selection bounds propagated to the render frame metadata |
| // represent an empty selection. |
| ASSERT_TRUE(observer_ptr->last_metadata()); |
| const viz::Selection<gfx::SelectionBound>& selection_1 = |
| observer_ptr->last_metadata()->selection; |
| EXPECT_EQ(gfx::SelectionBound::EMPTY, selection_1.start.type()); |
| EXPECT_EQ(gfx::SelectionBound::EMPTY, selection_1.end.type()); |
| EXPECT_EQ(gfx::PointF(), selection_1.start.edge_end()); |
| EXPECT_EQ(gfx::PointF(), selection_1.start.edge_start()); |
| EXPECT_FALSE(selection_1.start.visible()); |
| EXPECT_FALSE(selection_1.end.visible()); |
| |
| // Plumb the layer-local selection bounds. |
| gfx::Point selection_start(5, 0); |
| gfx::Point selection_end(5, 5); |
| LayerSelection selection; |
| selection.start.type = gfx::SelectionBound::CENTER; |
| selection.start.layer_id = root->id(); |
| selection.start.edge_end = selection_end; |
| selection.start.edge_start = selection_start; |
| selection.end = selection.start; |
| host_impl_->active_tree()->RegisterSelection(selection); |
| |
| // Trigger a draw-swap sequence. |
| host_impl_->SetNeedsRedraw(/*animation_only=*/false, |
| /*skip_if_inside_draw=*/false); |
| DrawFrame(); |
| |
| // Ensure the selection bounds have propagated to the render frame metadata. |
| ASSERT_TRUE(observer_ptr->last_metadata()); |
| const viz::Selection<gfx::SelectionBound>& selection_2 = |
| observer_ptr->last_metadata()->selection; |
| EXPECT_EQ(selection.start.type, selection_2.start.type()); |
| EXPECT_EQ(selection.end.type, selection_2.end.type()); |
| EXPECT_EQ(gfx::PointF(selection_end), selection_2.start.edge_end()); |
| EXPECT_EQ(gfx::PointF(selection_start), selection_2.start.edge_start()); |
| EXPECT_TRUE(selection_2.start.visible()); |
| EXPECT_TRUE(selection_2.end.visible()); |
| } |
| |
| TEST_P(ClientModeLayerTreeHostImplTest, |
| VerticalScrollDirectionChangesPassedToRenderFrameMetadata) { |
| // Set up the viewport. |
| SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100)); |
| host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50)); |
| |
| // Set up the render frame metadata observer. |
| auto observer = std::make_unique<TestRenderFrameMetadataObserver>(false); |
| auto* observer_ptr = observer.get(); |
| host_impl_->SetRenderFrameObserver(std::move(observer)); |
| EXPECT_FALSE(observer_ptr->last_metadata()); |
| |
| // Our test will be comprised of multiple legs, each leg consisting of a |
| // distinct scroll event and an expectation regarding the vertical scroll |
| // direction passed to the render frame metadata. |
| typedef struct { |
| gfx::Vector2d scroll_delta; |
| viz::VerticalScrollDirection expected_vertical_scroll_direction; |
| } TestLeg; |
| |
| std::vector<TestLeg> test_legs; |
| |
| // Initially, vertical scroll direction should be |kNull| indicating absence. |
| test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(), |
| /*expected_vertical_scroll_direction=*/viz:: |
| VerticalScrollDirection::kNull}); |
| |
| // Scrolling to the right should not affect vertical scroll direction. |
| test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(10, 0), |
| /*expected_vertical_scroll_direction=*/viz:: |
| VerticalScrollDirection::kNull}); |
| |
| // After scrolling down, the vertical scroll direction should be |kDown|. |
| test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(0, 10), |
| /*expected_vertical_scroll_direction=*/viz:: |
| VerticalScrollDirection::kDown}); |
| |
| // If we scroll down again, the vertical scroll direction should be |kNull| as |
| // there was no change in vertical scroll direction. |
| test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(0, 10), |
| /*expected_vertical_scroll_direction=*/viz:: |
| VerticalScrollDirection::kNull}); |
| |
| // Scrolling to the left should not affect last vertical scroll direction. |
| test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(-10, 0), |
| /*expected_vertical_scroll_direction=*/viz:: |
| VerticalScrollDirection::kNull}); |
| |
| // After scrolling up, the vertical scroll direction should be |kUp|. |
| test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(0, -10), |
| /*expected_vertical_scroll_direction=*/viz:: |
| VerticalScrollDirection::kUp}); |
| |
| // If we scroll up again, the vertical scroll direction should be |kNull| as |
| // there was no change in vertical scroll direction. |
| test_legs.push_back({/*scroll_delta=*/gfx::Vector2d(0, -10), |
| /*expected_vertical_scroll_direction=*/viz:: |
| VerticalScrollDirection::kNull}); |
| |
| // Iterate over all legs of our test. |
| for (auto& test_leg : test_legs) { |
| // If the test leg contains a scroll, perform it. |
| if (!test_leg.scroll_delta.IsZero()) { |
| GetInputHandler().ScrollBegin( |
| BeginState(gfx::Point(), test_leg.scroll_delta, |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), test_leg.scroll_delta, ui::ScrollInputType::kWheel)); |
| } |
| |
| // Trigger draw. |
| host_impl_->SetNeedsRedraw(/*animation_only=*/false, |
| /*skip_if_inside_draw=*/false); |
| DrawFrame(); |
| |
| // Assert our expectation regarding the vertical scroll direction. |
| EXPECT_EQ(test_leg.expected_vertical_scroll_direction, |
| observer_ptr->last_metadata()->new_vertical_scroll_direction); |
| |
| // If the test leg contains a scroll, end it. |
| if (!test_leg.scroll_delta.IsZero()) { |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| } |
| } |
| } |
| |
| TEST_P(ClientModeLayerTreeHostImplTest, |
| ImplThreadPhaseUponImplSideInvalidation) { |
| LayerTreeSettings settings = DefaultSettings(); |
| CreateHostImpl(settings, CreateLayerTreeFrameSink()); |
| // In general invalidation should never be ran outside the impl frame. |
| auto args = viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1); |
| host_impl_->WillBeginImplFrame(args); |
| // Expect no crash because the operation is within an impl frame. |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->InvalidateContentOnImplSide(); |
| |
| // Once the impl frame is finished the impl thread phase is set to IDLE. |
| host_impl_->DidFinishImplFrame(args); |
| |
| settings.using_synchronous_renderer_compositor = true; |
| CreateHostImpl(settings, CreateLayerTreeFrameSink()); |
| // Expect no crash when using synchronous renderer compositor regardless the |
| // impl thread phase. |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->InvalidateContentOnImplSide(); |
| |
| // Test passes when there is no crash. |
| } |
| |
| TEST_P(ClientModeLayerTreeHostImplTest, |
| CommitScheduledOnAnimatedImageAdvanceWithCanvasDrawableId) { |
| // Invalidation should never run outside the impl frame. |
| auto args = viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1, |
| base::TimeTicks::Now()); |
| host_impl_->WillBeginImplFrame(args); |
| |
| // 1. Setup a layer with an animated image. |
| gfx::Size layer_bounds(100, 100); |
| auto recording_source = std::make_unique<FakeRecordingSource>(layer_bounds); |
| |
| std::vector<FrameMetadata> frames = { |
| FrameMetadata(true, base::Milliseconds(100)), |
| FrameMetadata(true, base::Milliseconds(100))}; |
| PaintImage image = CreateAnimatedImage(gfx::Size(100, 100), frames); |
| recording_source->add_draw_image(image, gfx::Point(0, 0)); |
| recording_source->Rerecord(); |
| scoped_refptr<RasterSource> raster_source = |
| recording_source->CreateRasterSource(); |
| |
| // 2. Setup tree. |
| ElementId canvas_drawable_id(0x12345); |
| if (!CommitsToActiveTree()) { |
| CreatePendingTree(); |
| } |
| FakePictureLayerImpl* sync_layer = SetupRootLayer<FakePictureLayerImpl>( |
| host_impl_->sync_tree(), layer_bounds); |
| sync_layer->SetCanvasDrawableId(canvas_drawable_id); |
| sync_layer->SetRasterSource(raster_source, Region()); |
| UpdateDrawProperties(host_impl_->sync_tree()); |
| FakePictureLayerImpl* active_layer; |
| if (CommitsToActiveTree()) { |
| active_layer = sync_layer; |
| } else { |
| host_impl_->ActivateSyncTree(); |
| active_layer = static_cast<FakePictureLayerImpl*>( |
| host_impl_->active_tree()->root_layer()); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| } |
| |
| EXPECT_EQ(canvas_drawable_id, active_layer->canvas_drawable_id()); |
| |
| // 3. Make the image visible so it can animate. |
| gfx::Rect visible_rect(0, 0, 100, 100); |
| active_layer->SetVisibleLayerRectForTesting(visible_rect); |
| EXPECT_TRUE(active_layer->ShouldAnimate(image.stable_id())); |
| |
| // 4. Trigger animation and verify NO commit is scheduled yet (since it hasn't |
| // advanced). |
| did_request_commit_ = false; |
| client_host_impl(host_impl_.get())->InvalidateContentOnImplSide(); |
| EXPECT_FALSE(did_request_commit_); |
| |
| if (!CommitsToActiveTree()) { |
| ASSERT_TRUE(host_impl_->pending_tree()); |
| ASSERT_TRUE(host_impl_->pending_tree()->root_layer()); |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| host_impl_->ActivateSyncTree(); |
| active_layer = static_cast<FakePictureLayerImpl*>( |
| host_impl_->active_tree()->root_layer()); |
| active_layer->SetVisibleLayerRectForTesting(visible_rect); |
| } |
| |
| host_impl_->DidFinishImplFrame(args); |
| |
| // 5. Advance time and trigger animation again, verify commit IS scheduled. |
| did_request_commit_ = false; |
| |
| // Advance time by 110ms (frame duration is 100ms). |
| auto args2 = viz::CreateBeginFrameArgsForTesting( |
| BEGINFRAME_FROM_HERE, 0, 2, args.frame_time + base::Milliseconds(110)); |
| host_impl_->WillBeginImplFrame(args2); |
| |
| client_host_impl(host_impl_.get())->InvalidateContentOnImplSide(); |
| |
| // Now it should have advanced, and since it has canvas_drawable_id, it should |
| // have scheduled a commit. |
| EXPECT_TRUE(did_request_commit_); |
| |
| host_impl_->DidFinishImplFrame(args2); |
| } |
| |
| // Scroll operations on a non-composited scroller are eligible |
| // for rasterization. This tests if rasterization is detected |
| // and requested by the LTHI. The scroll is applied to the |
| // pending tree, so activation will be required before the scroll |
| // is presented. |
| TEST_P(ClientModeLayerTreeHostImplTest, NonCompositedScrollUsesRaster) { |
| gfx::Size scrollable_content_bounds(100, 100); |
| gfx::Size container_bounds(50, 50); |
| if (!CommitsToActiveTree()) { |
| CreatePendingTree(); |
| } |
| |
| // Create root and scroll layers so that we can set up a |
| // non-composited scrollable node, eligible for raster scroll. |
| auto* sync_tree_root = SetupRootLayer<LayerImpl>(host_impl_->sync_tree(), |
| scrollable_content_bounds); |
| sync_tree_root->SetNeedsPushProperties(); |
| auto* scrolling_layer = |
| AddScrollableLayer(sync_tree_root, container_bounds, gfx::Size()); |
| scrolling_layer->SetNeedsPushProperties(); |
| CreateScrollNodeForNonCompositedScroller( |
| host_impl_->sync_tree()->property_trees(), sync_tree_root->id(), |
| scrolling_layer->element_id(), scrollable_content_bounds, |
| container_bounds); |
| |
| // Draw at least one frame before ScrollBegin. |
| host_impl_->sync_tree()->set_needs_update_draw_properties(); |
| UpdateDrawProperties(host_impl_->sync_tree()); |
| host_impl_->ActivateSyncTree(); |
| DrawFrame(); |
| |
| // Scrolling on this non-composited tree should be marked as raster-inducing. |
| ScrollStateData scroll_state_data; |
| scroll_state_data.set_current_native_scrolling_element( |
| scrolling_layer->element_id()); |
| scroll_state_data.is_beginning = true; |
| std::unique_ptr<ScrollState> scroll_state = |
| std::make_unique<ScrollState>(scroll_state_data); |
| |
| InputHandler::ScrollStatus status = GetInputHandler().ScrollBegin( |
| scroll_state.get(), ui::ScrollInputType::kWheel); |
| EXPECT_EQ(true, status.raster_inducing); |
| |
| // We always want to start applying the scroll offset to the active tree. |
| host_impl_->active_tree()->DidUpdateScrollOffset( |
| scrolling_layer->element_id(), /*pushed_from_main_or_pending_tree=*/true); |
| |
| // Draw the next frame of the scroll. |
| { |
| host_impl_->NotifyInputEvent(/*is_fling=*/false); |
| host_impl_->SetFullViewportDamage(); |
| host_impl_->SetNeedsRedraw(/*animation_only=*/false, |
| /*skip_if_inside_draw=*/false); |
| TestFrameData frame; |
| auto args = viz::CreateBeginFrameArgsForTesting( |
| BEGINFRAME_FROM_HERE, viz::BeginFrameArgs::kManualSourceId, 1, |
| base::TimeTicks() + base::Milliseconds(1)); |
| host_impl_->WillBeginImplFrame(args); |
| EXPECT_EQ(DrawResult::kSuccess, host_impl_->PrepareToDraw(&frame)); |
| |
| // This call sets the invalidate_raster_scroll bit. |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->InvalidateContentOnImplSide(); |
| if (!CommitsToActiveTree()) { |
| // Activate the pending tree before drawing layers. |
| host_impl_->ActivateSyncTree(); |
| } |
| std::optional<SubmitInfo> draw_layers_state = |
| host_impl_->DrawLayers(&frame); |
| EXPECT_EQ(true, draw_layers_state->invalidate_raster_scroll); |
| host_impl_->DidDrawAllLayers(frame); |
| host_impl_->DidFinishImplFrame(args); |
| } |
| } |
| |
| INSTANTIATE_CLIENT_MODE_TREE_TEST_P(ClientModeLayerTreeHostImplTest); |
| |
| TEST_P(LayerTreeHostImplTestMultiScrollable, |
| ScrollbarFlashAfterAnyScrollUpdate) { |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.scrollbar_fade_delay = base::Milliseconds(500); |
| settings.scrollbar_fade_duration = base::Milliseconds(300); |
| settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY; |
| settings.scrollbar_flash_after_any_scroll_update = true; |
| settings.scrollbar_flash_once_after_scroll_update = false; |
| |
| SetUpLayers(settings); |
| |
| EXPECT_EQ(scrollbar_1_->Opacity(), 0); |
| EXPECT_EQ(scrollbar_2_->Opacity(), 0); |
| |
| // Scroll on root should flash all scrollbars. |
| GetInputHandler().RootScrollBegin( |
| BeginState(gfx::Point(20, 20), gfx::Vector2dF(0, 10), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(20, 20), gfx::Vector2d(0, 10), ui::ScrollInputType::kWheel)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_TRUE(scrollbar_1_->Opacity()); |
| EXPECT_TRUE(scrollbar_2_->Opacity()); |
| |
| EXPECT_FALSE(animation_task_.is_null()); |
| ResetScrollbars(); |
| |
| // Scroll on child should flash all scrollbars. |
| GetInputHandler().ScrollBegin( |
| BeginState(gfx::Point(70, 70), gfx::Vector2dF(0, 100), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate( |
| AnimatedUpdateState(gfx::Point(70, 70), gfx::Vector2d(0, 100))); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_TRUE(scrollbar_1_->Opacity()); |
| EXPECT_TRUE(scrollbar_2_->Opacity()); |
| |
| EXPECT_FALSE(animation_task_.is_null()); |
| } |
| |
| TEST_P(LayerTreeHostImplTestMultiScrollable, |
| ScrollbarFlashOnceAfterAnyScrollUpdate) { |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.scrollbar_fade_delay = base::Milliseconds(500); |
| settings.scrollbar_fade_duration = base::Milliseconds(300); |
| settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY; |
| settings.scrollbar_flash_after_any_scroll_update = false; |
| settings.scrollbar_flash_once_after_scroll_update = true; |
| |
| SetUpLayers(settings); |
| |
| EXPECT_EQ(scrollbar_1_->Opacity(), 0); |
| EXPECT_EQ(scrollbar_2_->Opacity(), 0); |
| |
| // Beginning of scroll on root should flash all scrollbars. |
| GetInputHandler().RootScrollBegin( |
| BeginState(gfx::Point(20, 20), gfx::Vector2dF(0, 10), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(20, 20), gfx::Vector2d(0, 10), ui::ScrollInputType::kWheel)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_TRUE(scrollbar_1_->Opacity()); |
| EXPECT_TRUE(scrollbar_2_->Opacity()); |
| |
| EXPECT_FALSE(animation_task_.is_null()); |
| ResetScrollbars(); |
| |
| // Scrolling on root again mustn't flash other than the root scrollbar. |
| GetInputHandler().RootScrollBegin( |
| BeginState(gfx::Point(70, 70), gfx::Vector2dF(0, 100), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(70, 70), gfx::Vector2d(0, 100), ui::ScrollInputType::kWheel)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_TRUE(scrollbar_1_->Opacity()); |
| EXPECT_FALSE(scrollbar_2_->Opacity()); |
| |
| EXPECT_FALSE(animation_task_.is_null()); |
| ResetScrollbars(); |
| |
| // Yet another scroll on child should flash only the child scrollbar. |
| GetInputHandler().ScrollBegin( |
| BeginState(gfx::Point(70, 70), gfx::Vector2dF(0, 100), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate( |
| AnimatedUpdateState(gfx::Point(70, 70), gfx::Vector2d(0, 100))); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_FALSE(scrollbar_1_->Opacity()); |
| EXPECT_TRUE(scrollbar_2_->Opacity()); |
| |
| EXPECT_FALSE(animation_task_.is_null()); |
| } |
| |
| TEST_P(LayerTreeHostImplTestMultiScrollable, |
| ScrollbarFlashOnceEnteredViewport) { |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.scrollbar_fade_delay = base::Milliseconds(500); |
| settings.scrollbar_fade_duration = base::Milliseconds(300); |
| settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY; |
| settings.scrollbar_flash_after_any_scroll_update = false; |
| settings.scrollbar_flash_once_after_scroll_update = false; |
| settings.scrollbar_flash_once_visible_on_viewport = true; |
| |
| SetUpLayers(settings); |
| |
| raw_ptr<SolidColorScrollbarLayerImpl> scrollbar3 = nullptr; |
| |
| { |
| // Create another child scroll element at (10, 210) with size 50x150 |
| LayerImpl* root_scroll = OuterViewportScrollLayer(); |
| |
| auto* child = AddScrollableLayer(root_scroll, gfx::Size(100, 100), |
| gfx::Size(250, 150)); |
| GetTransformNode(child)->post_translation = gfx::Vector2dF(50, 50); |
| |
| scrollbar3 = AddLayer<SolidColorScrollbarLayerImpl>( |
| host_impl_->active_tree(), ScrollbarOrientation::kVertical, 15, 0, |
| true); |
| SetupScrollbarLayer(child, scrollbar3); |
| scrollbar3->SetBounds(gfx::Size(50, 150)); |
| scrollbar3->SetOffsetToTransformParent(gfx::Vector2dF(10, 210)); |
| host_impl_->active_tree()->UpdateAllScrollbarGeometriesForTesting(); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| host_impl_->active_tree()->DidBecomeActive(); |
| } |
| |
| EXPECT_EQ(scrollbar_1_->Opacity(), 0); |
| EXPECT_EQ(scrollbar_2_->Opacity(), 0); |
| EXPECT_EQ(scrollbar3->Opacity(), 0); |
| |
| // First scroll: root down by 10, visible scrollbars flash. |
| GetInputHandler().RootScrollBegin( |
| BeginState(gfx::Point(20, 20), gfx::Vector2dF(0, 10), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(20, 20), gfx::Vector2d(0, 10), ui::ScrollInputType::kWheel)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| // Scroll is less than threshold, so no flash. |
| EXPECT_TRUE(scrollbar_1_->Opacity()); |
| EXPECT_FALSE(scrollbar_2_->Opacity()); |
| EXPECT_FALSE(scrollbar3->Opacity()); |
| EXPECT_FALSE(animation_task_.is_null()); |
| |
| // Reset scrollbars |
| GetEffectNode(scrollbar3.get())->opacity = 0; |
| ResetScrollbars(); |
| |
| // Second scroll: root down by another 200 right till where the third child |
| // is. Both the root and the scrollbar2 should flash as the scrollbar2 is |
| // one the viewport now. |
| GetInputHandler().RootScrollBegin( |
| BeginState(gfx::Point(150, 100), gfx::Vector2dF(0, 200), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate(UpdateState(gfx::Point(150, 100), |
| gfx::Vector2d(0, 200), |
| ui::ScrollInputType::kWheel)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_TRUE(scrollbar_1_->Opacity()); |
| EXPECT_TRUE(scrollbar_2_->Opacity()); |
| EXPECT_FALSE(scrollbar3->Opacity()); |
| EXPECT_FALSE(animation_task_.is_null()); |
| |
| // Reset scrollbars |
| GetEffectNode(scrollbar3.get())->opacity = 0; |
| ResetScrollbars(); |
| |
| // Scroll down a bit more. Now, the scrollbar3 should flash as it is visible |
| // now. |
| GetInputHandler().RootScrollBegin( |
| BeginState(gfx::Point(150, 100), gfx::Vector2dF(0, 30), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(150, 100), gfx::Vector2d(0, 30), ui::ScrollInputType::kWheel)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_TRUE(scrollbar_1_->Opacity()); |
| EXPECT_FALSE(scrollbar_2_->Opacity()); |
| EXPECT_TRUE(scrollbar3->Opacity()); |
| EXPECT_FALSE(animation_task_.is_null()); |
| |
| // Reset scrollbars |
| GetEffectNode(scrollbar3.get())->opacity = 0; |
| ResetScrollbars(); |
| |
| // Scroll down more so that that last scrollbar is not visible anymore. |
| GetInputHandler().RootScrollBegin( |
| BeginState(gfx::Point(150, 100), gfx::Vector2dF(0, 300), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate(UpdateState(gfx::Point(150, 100), |
| gfx::Vector2d(0, 300), |
| ui::ScrollInputType::kWheel)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_TRUE(scrollbar_1_->Opacity()); |
| EXPECT_FALSE(scrollbar_2_->Opacity()); |
| EXPECT_FALSE(scrollbar3->Opacity()); |
| EXPECT_FALSE(animation_task_.is_null()); |
| |
| // Reset scrollbars |
| GetEffectNode(scrollbar3.get())->opacity = 0; |
| ResetScrollbars(); |
| |
| // Scroll back so that scrollbar3 is visible again. |
| // Scroll down more so that that last scrollbar is not visible anymore. |
| GetInputHandler().RootScrollBegin( |
| BeginState(gfx::Point(150, 100), gfx::Vector2dF(0, -310), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate(UpdateState(gfx::Point(150, 100), |
| gfx::Vector2d(0, -310), |
| ui::ScrollInputType::kWheel)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_TRUE(scrollbar_1_->Opacity()); |
| EXPECT_FALSE(scrollbar_2_->Opacity()); |
| EXPECT_FALSE(scrollbar3->Opacity()); |
| EXPECT_FALSE(animation_task_.is_null()); |
| |
| // Reset scrollbars |
| GetEffectNode(scrollbar3.get())->opacity = 0; |
| ResetScrollbars(); |
| |
| // Scroll up a bit more. Now, the scrollbar3 should flash as it is visible |
| // now and the threshold is passed. |
| GetInputHandler().RootScrollBegin( |
| BeginState(gfx::Point(150, 100), gfx::Vector2dF(0, 25), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(150, 100), gfx::Vector2d(0, 25), ui::ScrollInputType::kWheel)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_TRUE(scrollbar_1_->Opacity()); |
| EXPECT_FALSE(scrollbar_2_->Opacity()); |
| EXPECT_TRUE(scrollbar3->Opacity()); |
| EXPECT_FALSE(animation_task_.is_null()); |
| |
| // Reset scrollbars |
| GetEffectNode(scrollbar3.get())->opacity = 0; |
| ResetScrollbars(); |
| } |
| |
| TEST_P(LayerTreeHostImplTestMultiScrollable, ScrollbarFlashWhenMouseEnter) { |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.scrollbar_fade_delay = base::Milliseconds(500); |
| settings.scrollbar_fade_duration = base::Milliseconds(300); |
| settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY; |
| settings.scrollbar_flash_when_mouse_enter = true; |
| |
| SetUpLayers(settings); |
| |
| constexpr size_t kNumberOfRepeats = 3; |
| for (size_t i = 0; i < kNumberOfRepeats; i++) { |
| ScrollbarAnimationController* scrollbar_animation_controller = |
| host_impl_->ScrollbarAnimationControllerForElementId( |
| scrollbar_1_->scroll_element_id()); |
| |
| const float kMouseMoveDistanceToTriggerFadeIn = |
| scrollbar_animation_controller |
| ->GetScrollbarAnimationController(ScrollbarOrientation::kVertical) |
| .MouseMoveDistanceToTriggerFadeIn(); |
| const int thumb_thickness = scrollbar_1_->ThumbThickness(); |
| |
| GetInputHandler().MouseMoveAt( |
| gfx::Point(thumb_thickness + kMouseMoveDistanceToTriggerFadeIn + 1, 1)); |
| EXPECT_FALSE(scrollbar_animation_controller->MouseIsNearScrollbar( |
| ScrollbarOrientation::kVertical)); |
| EXPECT_FALSE(scrollbar_animation_controller->MouseIsNearScrollbarThumb( |
| ScrollbarOrientation::kVertical)); |
| |
| EXPECT_FALSE(scrollbar_1_->Opacity()); |
| EXPECT_FALSE(scrollbar_2_->Opacity()); |
| |
| ResetScrollbars(); |
| |
| ScrollbarAnimationController* scrollbar_animation_controller2 = |
| host_impl_->ScrollbarAnimationControllerForElementId( |
| scrollbar_2_->scroll_element_id()); |
| |
| const float kMouseMoveDistanceToTriggerFadeInChild = |
| scrollbar_animation_controller2 |
| ->GetScrollbarAnimationController(ScrollbarOrientation::kVertical) |
| .MouseMoveDistanceToTriggerFadeIn(); |
| const int kThumbThicknessChild = scrollbar_2_->ThumbThickness(); |
| |
| GetInputHandler().MouseMoveAt(gfx::Point( |
| kThumbThicknessChild + kMouseMoveDistanceToTriggerFadeInChild + 50, |
| 50)); |
| EXPECT_FALSE(scrollbar_animation_controller2->MouseIsNearScrollbar( |
| ScrollbarOrientation::kVertical)); |
| EXPECT_FALSE(scrollbar_animation_controller2->MouseIsNearScrollbarThumb( |
| ScrollbarOrientation::kVertical)); |
| |
| EXPECT_FALSE(scrollbar_1_->Opacity()); |
| EXPECT_TRUE(scrollbar_2_->Opacity()); |
| |
| ResetScrollbars(); |
| } |
| } |
| |
| INSTANTIATE_CLIENT_MODE_TREE_TEST_P(LayerTreeHostImplTestMultiScrollable); |
| |
| // Tests that, on a page with content the same size as the viewport, hiding |
| // the browser controls also increases the ScrollableSize (i.e. the content |
| // size). Since the viewport got larger, the effective scrollable "content" also |
| // did. This ensures, for one thing, that the overscroll glow is shown in the |
| // right place. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| HidingBrowserControlsExpandsScrollableSize) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(50, 50), gfx::Size(50, 50), gfx::Size(50, 50)); |
| |
| LayerTreeImpl* active_tree = host_impl_->active_tree(); |
| DrawFrame(); |
| |
| // The browser controls should start off showing so the viewport should be |
| // shrunk. |
| ExpectViewportGeometries(1); |
| EXPECT_EQ(gfx::SizeF(50, 50), active_tree->ScrollableSize()); |
| |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 25), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| |
| host_impl_->browser_controls_manager()->ScrollBegin(); |
| |
| // Hide the browser controls by a bit, the scrollable size should increase but |
| // the actual content bounds shouldn't. |
| host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); |
| ExpectViewportGeometries(0.5f); |
| EXPECT_EQ(gfx::SizeF(50, 75), active_tree->ScrollableSize()); |
| |
| // Fully hide the browser controls. |
| host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); |
| ExpectViewportGeometries(0); |
| EXPECT_EQ(gfx::SizeF(50, 100), active_tree->ScrollableSize()); |
| |
| // Scrolling additionally shouldn't have any effect. |
| host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); |
| ExpectViewportGeometries(0); |
| EXPECT_EQ(gfx::SizeF(50, 100), active_tree->ScrollableSize()); |
| |
| host_impl_->browser_controls_manager()->ScrollEnd(); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| } |
| |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| HidingBrowserControlsExpandsClipAncestorsOfReplacedOuterScroller) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(180, 180), gfx::Size(180, 180), gfx::Size(180, 180)); |
| |
| LayerTreeImpl* active_tree = host_impl_->active_tree(); |
| PropertyTrees* property_trees = active_tree->property_trees(); |
| LayerImpl* original_outer_scroll = OuterViewportScrollLayer(); |
| |
| LayerImpl* parent_clip_layer = AddLayerInActiveTree(); |
| CopyProperties(original_outer_scroll, parent_clip_layer); |
| parent_clip_layer->SetBounds(gfx::Size(160, 160)); |
| CreateClipNode(parent_clip_layer); |
| LayerImpl* clip_layer = AddLayerInActiveTree(); |
| clip_layer->SetBounds(gfx::Size(150, 150)); |
| CopyProperties(parent_clip_layer, clip_layer); |
| CreateClipNode(clip_layer); |
| LayerImpl* scroll_layer = |
| AddScrollableLayer(clip_layer, gfx::Size(150, 150), gfx::Size(300, 300)); |
| GetScrollNode(scroll_layer)->scrolls_outer_viewport = true; |
| ClipNode* original_outer_clip = GetClipNode(original_outer_scroll); |
| ClipNode* parent_clip = GetClipNode(parent_clip_layer); |
| ClipNode* scroll_clip = GetClipNode(clip_layer); |
| |
| auto viewport_property_ids = active_tree->ViewportPropertyIdsForTesting(); |
| viewport_property_ids.outer_clip = clip_layer->clip_tree_index(); |
| viewport_property_ids.outer_scroll = scroll_layer->scroll_tree_index(); |
| active_tree->SetViewportPropertyIds(viewport_property_ids); |
| UpdateDrawProperties(active_tree); |
| |
| EXPECT_EQ(scroll_layer, OuterViewportScrollLayer()); |
| EXPECT_EQ(GetScrollNode(scroll_layer), |
| active_tree->OuterViewportScrollNode()); |
| |
| EXPECT_EQ(1, active_tree->CurrentTopControlsShownRatio()); |
| EXPECT_EQ(50, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF(), |
| property_trees->inner_viewport_container_bounds_delta()); |
| EXPECT_EQ(gfx::Vector2dF(), |
| property_trees->outer_viewport_container_bounds_delta()); |
| EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); |
| EXPECT_EQ(gfx::RectF(0, 0, 180, 180), original_outer_clip->clip); |
| EXPECT_EQ(gfx::RectF(0, 0, 160, 160), parent_clip->clip); |
| EXPECT_EQ(gfx::RectF(0, 0, 150, 150), scroll_clip->clip); |
| |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 25), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| |
| // Hide the browser controls by a bit, the scrollable size should increase but |
| // the actual content bounds shouldn't. |
| host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); |
| EXPECT_EQ(0.5f, active_tree->CurrentTopControlsShownRatio()); |
| EXPECT_EQ(25, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF(0, 25), |
| property_trees->inner_viewport_container_bounds_delta()); |
| EXPECT_EQ(gfx::Vector2dF(0, 25), |
| property_trees->outer_viewport_container_bounds_delta()); |
| EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); |
| EXPECT_EQ(gfx::RectF(0, 0, 150, 175), scroll_clip->clip); |
| EXPECT_EQ(gfx::RectF(0, 0, 160, 175), parent_clip->clip); |
| EXPECT_EQ(gfx::RectF(0, 0, 180, 180), original_outer_clip->clip); |
| |
| // Fully hide the browser controls. |
| host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); |
| EXPECT_EQ(0, active_tree->CurrentTopControlsShownRatio()); |
| EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF(0, 50), |
| property_trees->inner_viewport_container_bounds_delta()); |
| EXPECT_EQ(gfx::Vector2dF(0, 50), |
| property_trees->outer_viewport_container_bounds_delta()); |
| EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); |
| EXPECT_EQ(gfx::RectF(0, 0, 150, 200), scroll_clip->clip); |
| EXPECT_EQ(gfx::RectF(0, 0, 160, 200), parent_clip->clip); |
| EXPECT_EQ(gfx::RectF(0, 0, 180, 200), original_outer_clip->clip); |
| |
| // Scrolling additionally shouldn't have any effect. |
| host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); |
| EXPECT_EQ(0, active_tree->CurrentTopControlsShownRatio()); |
| EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF(0, 50), |
| property_trees->inner_viewport_container_bounds_delta()); |
| EXPECT_EQ(gfx::Vector2dF(0, 50), |
| property_trees->outer_viewport_container_bounds_delta()); |
| EXPECT_EQ(gfx::SizeF(300, 300), active_tree->ScrollableSize()); |
| EXPECT_EQ(gfx::RectF(0, 0, 150, 200), scroll_clip->clip); |
| EXPECT_EQ(gfx::RectF(0, 0, 160, 200), parent_clip->clip); |
| EXPECT_EQ(gfx::RectF(0, 0, 180, 200), original_outer_clip->clip); |
| |
| host_impl_->browser_controls_manager()->ScrollEnd(); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| } |
| |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| HidingBrowserControlsAdjustsSnapFling) { |
| gfx::Size view_size(100, 100); |
| gfx::Size content_size(100, 1000); |
| gfx::RectF snap_area_1(0, 0, 100, 700); |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport(view_size, view_size, |
| content_size); |
| |
| SnapContainerData container( |
| ScrollSnapType(false, SnapAxis::kY, SnapStrictness::kMandatory), |
| gfx::RectF(0, 0, 100, 100), gfx::PointF(0, 900)); |
| ScrollSnapAlign start = ScrollSnapAlign(SnapAlignment::kStart); |
| container.AddSnapAreaData( |
| SnapAreaData(start, snap_area_1, false, false, ElementId(10))); |
| host_impl_->OuterViewportScrollNode()->snap_container_data.emplace(container); |
| |
| DrawFrame(); |
| ExpectViewportGeometries(1.0f); |
| |
| LayerTreeImpl* active_tree = host_impl_->active_tree(); |
| PropertyTrees* property_trees = active_tree->property_trees(); |
| LayerImpl* outer_scroll_layer = OuterViewportScrollLayer(); |
| InputHandler& handler = GetInputHandler(); |
| gfx::PointF initial_offset, target_offset; |
| gfx::Point position(50, 50); |
| ui::ScrollInputType type = ui::ScrollInputType::kTouchscreen; |
| |
| handler.ScrollBegin(&*BeginState(position, gfx::Vector2dF(0, 15), type), |
| type); |
| handler.ScrollUpdate(UpdateState(position, gfx::Vector2dF(0, 15), type)); |
| |
| // The browser controls, now partially hidden, are consuming all of the scroll |
| // delta so far. |
| EXPECT_FLOAT_EQ(0.7, active_tree->CurrentTopControlsShownRatio()); |
| EXPECT_EQ(gfx::Vector2dF(0, 15), |
| property_trees->outer_viewport_container_bounds_delta()); |
| EXPECT_POINTF_EQ(gfx::PointF(0, 0), CurrentScrollOffset(outer_scroll_layer)); |
| |
| // Enter "constrained native fling" mode inside snap_area_1. |
| EXPECT_FALSE(handler.GetSnapFlingInfoAndSetAnimatingSnapTarget( |
| gfx::Vector2dF(0, 30), gfx::Vector2dF(0, 600), &initial_offset, |
| &target_offset)); |
| |
| // Finish hiding the browser controls and start scrolling the content. |
| handler.ScrollUpdate(UpdateState(position, gfx::Vector2dF(0, 435), type)); |
| EXPECT_POINTF_EQ(gfx::PointF(0, 400), |
| CurrentScrollOffset(outer_scroll_layer)); |
| |
| // Try to scroll past the bottom of snap_area_1. |
| EXPECT_TRUE(handler.GetSnapFlingInfoAndSetAnimatingSnapTarget( |
| gfx::Vector2dF(0, 300), gfx::Vector2dF(0, 1000), &initial_offset, |
| &target_offset)); |
| |
| // The fling constraint should take us to 550, which aligns with the bottom of |
| // snap_area_1 using the expanded viewport size of 100x150 from hiding browser |
| // controls, even though SnapContainerData is still based on 100x100 viewport. |
| EXPECT_TRUE(handler.animating_for_snap_for_testing( |
| host_impl_->OuterViewportScrollNode()->element_id)); |
| EXPECT_POINTF_EQ(gfx::PointF(0, 400), initial_offset); |
| EXPECT_POINTF_EQ(gfx::PointF(0, 550), target_offset); |
| |
| handler.ScrollEnd(false, std::nullopt); |
| } |
| |
| // Ensure that moving the browser controls (i.e. omnibox/url-bar on mobile) on |
| // pages with a non-1 minimum page scale factor (e.g. legacy desktop page) |
| // correctly scales the clipping adjustment performed to show the newly exposed |
| // region of the page. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| MovingBrowserControlsOuterClipDeltaScaled) { |
| gfx::Size inner_size = gfx::Size(100, 100); |
| gfx::Size outer_size = gfx::Size(100, 100); |
| gfx::Size content_size = gfx::Size(200, 1000); |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport(inner_size, outer_size, |
| content_size); |
| |
| LayerTreeImpl* active_tree = host_impl_->active_tree(); |
| |
| // Create a content layer beneath the outer viewport scroll layer. |
| LayerImpl* content = AddLayerInActiveTree(); |
| content->SetBounds(gfx::Size(100, 100)); |
| CopyProperties(OuterViewportScrollLayer(), content); |
| active_tree->PushPageScaleFromMainThread(0.5f, 0.5f, 4); |
| |
| DrawFrame(); |
| |
| // The browser controls should start off showing so the viewport should be |
| // shrunk. |
| ExpectViewportGeometries(1.0f); |
| EXPECT_EQ(gfx::SizeF(200, 1000), active_tree->ScrollableSize()); |
| |
| ASSERT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 25), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| |
| // Hide the browser controls by 25px. The outer clip should expand by 50px as |
| // because the outer viewport is sized based on the minimum scale, in this |
| // case 0.5. Therefore, changes to the outer viewport need to be divided by |
| // the minimum scale as well. |
| GetInputHandler().ScrollUpdate( |
| UpdateState(gfx::Point(0, 0), gfx::Vector2dF(0, 25), |
| ui::ScrollInputType::kTouchscreen)); |
| ExpectViewportGeometries(0.5f); |
| |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| } |
| |
| // Tests that browser controls affect the position of horizontal scrollbars. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| HidingBrowserControlsAdjustsScrollbarPosition) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(50, 50), gfx::Size(50, 50), gfx::Size(50, 50)); |
| |
| LayerTreeImpl* active_tree = host_impl_->active_tree(); |
| |
| // Create a horizontal scrollbar. |
| gfx::Size scrollbar_size(gfx::Size(50, 15)); |
| auto* scrollbar_layer = AddLayer<SolidColorScrollbarLayerImpl>( |
| host_impl_->active_tree(), ScrollbarOrientation::kHorizontal, 3, 20, |
| false); |
| SetupScrollbarLayer(OuterViewportScrollLayer(), scrollbar_layer); |
| scrollbar_layer->SetBounds(scrollbar_size); |
| TouchActionRegion touch_action_region; |
| touch_action_region.Union(TouchAction::kNone, gfx::Rect(scrollbar_size)); |
| scrollbar_layer->SetTouchActionRegion(touch_action_region); |
| scrollbar_layer->SetOffsetToTransformParent(gfx::Vector2dF(0, 35)); |
| host_impl_->active_tree()->UpdateAllScrollbarGeometriesForTesting(); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| |
| DrawFrame(); |
| |
| // The browser controls should start off showing so the viewport should be |
| // shrunk. |
| ExpectViewportGeometries(1.0f); |
| EXPECT_EQ(gfx::SizeF(50, 50), active_tree->ScrollableSize()); |
| EXPECT_EQ(gfx::Size(50, 15), scrollbar_layer->bounds()); |
| EXPECT_EQ(gfx::Rect(20, 0, 10, 3), scrollbar_layer->ComputeThumbQuadRect()); |
| |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 25), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| |
| host_impl_->browser_controls_manager()->ScrollBegin(); |
| |
| // Hide the browser controls by a bit, the scrollable size should increase but |
| // the actual content bounds shouldn't. |
| { |
| host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); |
| ExpectViewportGeometries(0.5f); |
| EXPECT_EQ(gfx::SizeF(50, 75), active_tree->ScrollableSize()); |
| EXPECT_EQ(gfx::Size(50, 15), scrollbar_layer->bounds()); |
| EXPECT_EQ(gfx::Rect(20, 25, 10, 3), |
| scrollbar_layer->ComputeThumbQuadRect()); |
| } |
| |
| // Fully hide the browser controls. |
| { |
| host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); |
| ExpectViewportGeometries(0); |
| EXPECT_EQ(gfx::SizeF(50, 100), active_tree->ScrollableSize()); |
| EXPECT_EQ(gfx::Size(50, 15), scrollbar_layer->bounds()); |
| EXPECT_EQ(gfx::Rect(20, 50, 10, 3), |
| scrollbar_layer->ComputeThumbQuadRect()); |
| } |
| |
| // Additional scrolling shouldn't have any effect. |
| { |
| host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 25)); |
| ExpectViewportGeometries(0); |
| EXPECT_EQ(gfx::SizeF(50, 100), active_tree->ScrollableSize()); |
| EXPECT_EQ(gfx::Size(50, 15), scrollbar_layer->bounds()); |
| EXPECT_EQ(gfx::Rect(20, 50, 10, 3), |
| scrollbar_layer->ComputeThumbQuadRect()); |
| } |
| |
| host_impl_->browser_controls_manager()->ScrollEnd(); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| } |
| |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| ScrollBrowserControlsByFractionalAmount) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(10, 10), gfx::Size(10, 10), gfx::Size(10, 10)); |
| DrawFrame(); |
| |
| gfx::Vector2dF top_controls_scroll_delta(0, 5.25f); |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), top_controls_scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| |
| // 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 browser controls. |
| host_impl_->browser_controls_manager()->ScrollBegin(); |
| host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta); |
| host_impl_->browser_controls_manager()->ScrollEnd(); |
| |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| auto* property_trees = host_impl_->active_tree()->property_trees(); |
| EXPECT_FLOAT_EQ(top_controls_scroll_delta.y(), |
| property_trees->inner_viewport_container_bounds_delta().y()); |
| } |
| |
| // In this test, the outer viewport is initially unscrollable. We test that a |
| // scroll initiated on the inner viewport, causing the browser controls to show |
| // and thus making the outer viewport scrollable, still scrolls the outer |
| // viewport. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| BrowserControlsOuterViewportBecomesScrollable) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100)); |
| DrawFrame(); |
| |
| LayerImpl* inner_scroll = InnerViewportScrollLayer(); |
| inner_scroll->SetDrawsContent(true); |
| LayerImpl* outer_scroll = OuterViewportScrollLayer(); |
| outer_scroll->SetDrawsContent(true); |
| |
| // Need SetDrawsContent so ScrollBegin's hit test finds an actual layer. |
| outer_scroll->SetDrawsContent(true); |
| host_impl_->active_tree()->PushPageScaleFromMainThread(2, 1, 2); |
| |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 50), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), gfx::Vector2dF(0, 50), ui::ScrollInputType::kTouchscreen)); |
| |
| // The entire scroll delta should have been used to hide the browser controls. |
| // The viewport layers should be resized back to their full sizes. |
| EXPECT_EQ(0, host_impl_->active_tree()->CurrentTopControlsShownRatio()); |
| EXPECT_EQ(0, CurrentScrollOffset(inner_scroll).y()); |
| EXPECT_EQ(100, inner_scroll->bounds().height()); |
| EXPECT_EQ(100, outer_scroll->bounds().height()); |
| |
| // The inner viewport should be scrollable by 50px * page_scale. |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), gfx::Vector2dF(0, 100), ui::ScrollInputType::kTouchscreen)); |
| EXPECT_EQ(50, CurrentScrollOffset(inner_scroll).y()); |
| EXPECT_EQ(0, CurrentScrollOffset(outer_scroll).y()); |
| EXPECT_EQ(gfx::PointF(), MaxScrollOffset(outer_scroll)); |
| |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, -50), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| EXPECT_EQ(host_impl_->CurrentlyScrollingNode()->id, |
| outer_scroll->scroll_tree_index()); |
| |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), gfx::Vector2dF(0, -50), ui::ScrollInputType::kTouchscreen)); |
| |
| // The entire scroll delta should have been used to show the browser controls. |
| // The outer viewport should be resized to accommodate and scrolled to the |
| // bottom of the document to keep the viewport in place. |
| EXPECT_EQ(1, host_impl_->active_tree()->CurrentTopControlsShownRatio()); |
| EXPECT_EQ(50, inner_scroll->bounds().height()); |
| EXPECT_EQ(100, outer_scroll->bounds().height()); |
| EXPECT_EQ(25, CurrentScrollOffset(outer_scroll).y()); |
| EXPECT_EQ(25, CurrentScrollOffset(inner_scroll).y()); |
| |
| // Now when we continue scrolling, make sure the outer viewport gets scrolled |
| // since it wasn't scrollable when the scroll began. |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), gfx::Vector2dF(0, -20), ui::ScrollInputType::kTouchscreen)); |
| EXPECT_EQ(25, CurrentScrollOffset(outer_scroll).y()); |
| EXPECT_EQ(15, CurrentScrollOffset(inner_scroll).y()); |
| |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), gfx::Vector2dF(0, -30), ui::ScrollInputType::kTouchscreen)); |
| EXPECT_EQ(25, CurrentScrollOffset(outer_scroll).y()); |
| EXPECT_EQ(0, CurrentScrollOffset(inner_scroll).y()); |
| |
| GetInputHandler().ScrollUpdate( |
| UpdateState(gfx::Point(), gfx::Vector2dF(0.f, -50), |
| ui::ScrollInputType::kTouchscreen)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_EQ(0, CurrentScrollOffset(outer_scroll).y()); |
| EXPECT_EQ(0, CurrentScrollOffset(inner_scroll).y()); |
| } |
| |
| // Test that the fixed position container delta is appropriately adjusted |
| // by the browser controls showing/hiding and page scale doesn't affect it. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, FixedContainerDelta) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(100, 100), gfx::Size(100, 100), gfx::Size(100, 100)); |
| DrawFrame(); |
| host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 2); |
| |
| float page_scale = 1.5f; |
| // Zoom in, since the fixed container is the outer viewport, the delta should |
| // not be scaled. |
| host_impl_->active_tree()->PushPageScaleFromMainThread(page_scale, 1, 2); |
| |
| gfx::Vector2dF top_controls_scroll_delta(0, 20); |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), top_controls_scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| |
| // Scroll down, the browser controls hiding should expand the viewport size so |
| // the delta should be equal to the scroll distance. |
| host_impl_->browser_controls_manager()->ScrollBegin(); |
| host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta); |
| EXPECT_FLOAT_EQ(top_controls_height_ - top_controls_scroll_delta.y(), |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| |
| auto* property_trees = host_impl_->active_tree()->property_trees(); |
| EXPECT_FLOAT_EQ(top_controls_scroll_delta.y(), |
| property_trees->outer_viewport_container_bounds_delta().y()); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| // Scroll past the maximum extent. The delta shouldn't be greater than the |
| // browser controls height. |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), top_controls_scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| host_impl_->browser_controls_manager()->ScrollBegin(); |
| host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta); |
| host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta); |
| host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta); |
| EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_VECTOR2DF_EQ(gfx::Vector2dF(0, top_controls_height_), |
| property_trees->outer_viewport_container_bounds_delta()); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| // Scroll in the direction to make the browser controls show. |
| EXPECT_EQ( |
| ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), -top_controls_scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| host_impl_->browser_controls_manager()->ScrollBegin(); |
| host_impl_->browser_controls_manager()->ScrollBy(-top_controls_scroll_delta); |
| EXPECT_EQ(top_controls_scroll_delta.y(), |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_VECTOR2DF_EQ( |
| gfx::Vector2dF(0, top_controls_height_ - top_controls_scroll_delta.y()), |
| property_trees->outer_viewport_container_bounds_delta()); |
| host_impl_->browser_controls_manager()->ScrollEnd(); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| } |
| |
| // Push a browser controls ratio from the main thread that we didn't send as a |
| // delta and make sure that the ratio is clamped to the [0, 1] range. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsPushUnsentRatio) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100)); |
| DrawFrame(); |
| |
| // Need SetDrawsContent so ScrollBegin's hit test finds an actual layer. |
| LayerImpl* inner_scroll = InnerViewportScrollLayer(); |
| inner_scroll->SetDrawsContent(true); |
| LayerImpl* outer_scroll = OuterViewportScrollLayer(); |
| outer_scroll->SetDrawsContent(true); |
| |
| host_impl_->active_tree()->PushBrowserControlsFromMainThread(1, 1); |
| ASSERT_EQ(1.0f, host_impl_->active_tree()->CurrentTopControlsShownRatio()); |
| |
| host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0.5f, 0.5f); |
| ASSERT_EQ(0.5f, host_impl_->active_tree()->CurrentTopControlsShownRatio()); |
| |
| host_impl_->active_tree()->PushBrowserControlsFromMainThread(0, 0); |
| |
| ASSERT_EQ(0, host_impl_->active_tree()->CurrentTopControlsShownRatio()); |
| } |
| |
| // Test that if a scrollable sublayer doesn't consume the scroll, |
| // browser controls should hide when scrolling down. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| BrowserControlsScrollableSublayer) { |
| gfx::Size sub_content_size(100, 400); |
| gfx::Size sub_content_layer_size(100, 300); |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(100, 50), gfx::Size(100, 100), gfx::Size(100, 100)); |
| DrawFrame(); |
| |
| // Show browser controls |
| EXPECT_EQ(1, host_impl_->active_tree()->CurrentTopControlsShownRatio()); |
| |
| LayerImpl* outer_viewport_scroll_layer = OuterViewportScrollLayer(); |
| LayerImpl* child = AddLayerInActiveTree(); |
| |
| child->SetHitTestOpaqueness(HitTestOpaqueness::kMixed); |
| child->SetElementId(LayerIdToElementIdForTesting(child->id())); |
| child->SetBounds(sub_content_size); |
| child->SetDrawsContent(true); |
| |
| CopyProperties(outer_viewport_scroll_layer, child); |
| CreateTransformNode(child); |
| CreateScrollNode(child, sub_content_layer_size); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| |
| // Scroll child to the limit. |
| SetScrollOffsetDelta(child, gfx::Vector2dF(0, 100)); |
| |
| // Scroll 25px to hide browser controls |
| gfx::Vector2dF scroll_delta(0, 25); |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| // Browser controls should be hidden |
| EXPECT_EQ(scroll_delta.y(), |
| top_controls_height_ - |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| } |
| |
| // Ensure setting the browser controls position explicitly using the setters on |
| // the TreeImpl correctly affects the browser controls manager and viewport |
| // bounds for the active tree. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| PositionBrowserControlsToActiveTreeExplicitly) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| layer_size_, layer_size_, layer_size_); |
| DrawFrame(); |
| |
| host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0, 0); |
| host_impl_->active_tree()->top_controls_shown_ratio()->PushMainToPending( |
| 30 / top_controls_height_); |
| host_impl_->active_tree()->top_controls_shown_ratio()->PushPendingToActive(); |
| EXPECT_FLOAT_EQ(30, |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_FLOAT_EQ(-20, |
| host_impl_->browser_controls_manager()->ControlsTopOffset()); |
| |
| host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0, 0); |
| EXPECT_FLOAT_EQ(0, |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_FLOAT_EQ(-50, |
| host_impl_->browser_controls_manager()->ControlsTopOffset()); |
| |
| host_impl_->DidChangeBrowserControlsPosition(); |
| |
| auto* property_trees = host_impl_->active_tree()->property_trees(); |
| EXPECT_EQ(gfx::Vector2dF(0, 50), |
| property_trees->inner_viewport_container_bounds_delta()); |
| } |
| |
| // Ensure setting the browser controls position explicitly using the setters on |
| // the TreeImpl correctly affects the browser controls manager and viewport |
| // bounds for the pending tree. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| PositionBrowserControlsToPendingTreeExplicitly) { |
| EnsureSyncTree(); |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| host_impl_->sync_tree(), layer_size_, layer_size_, layer_size_); |
| |
| // Changing SetCurrentBrowserControlsShownRatio is one way to cause the |
| // pending tree to update it's viewport. |
| host_impl_->SetCurrentBrowserControlsShownRatio(0, 0); |
| EXPECT_FLOAT_EQ(top_controls_height_, |
| host_impl_->sync_tree() |
| ->property_trees() |
| ->inner_viewport_container_bounds_delta() |
| .y()); |
| host_impl_->SetCurrentBrowserControlsShownRatio(0.5f, 0.5f); |
| EXPECT_FLOAT_EQ(0.5f * top_controls_height_, |
| host_impl_->sync_tree() |
| ->property_trees() |
| ->inner_viewport_container_bounds_delta() |
| .y()); |
| host_impl_->SetCurrentBrowserControlsShownRatio(1, 1); |
| EXPECT_FLOAT_EQ(0, host_impl_->sync_tree() |
| ->property_trees() |
| ->inner_viewport_container_bounds_delta() |
| .y()); |
| |
| // Pushing changes from the main thread is the second way. These values are |
| // added to the 1 set above. |
| host_impl_->sync_tree()->PushBrowserControlsFromMainThread(-0.5f, -0.5f); |
| EXPECT_FLOAT_EQ(0.5f * top_controls_height_, |
| host_impl_->sync_tree() |
| ->property_trees() |
| ->inner_viewport_container_bounds_delta() |
| .y()); |
| host_impl_->sync_tree()->PushBrowserControlsFromMainThread(-1, -1); |
| EXPECT_FLOAT_EQ(top_controls_height_, |
| host_impl_->sync_tree() |
| ->property_trees() |
| ->inner_viewport_container_bounds_delta() |
| .y()); |
| } |
| |
| // Test that the top_controls delta and sent delta are appropriately |
| // applied on sync tree activation. The total browser controls offset shouldn't |
| // change after the activation. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, ApplyDeltaOnTreeActivation) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| layer_size_, layer_size_, layer_size_); |
| DrawFrame(); |
| |
| host_impl_->active_tree()->top_controls_shown_ratio()->PushMainToPending( |
| 20 / top_controls_height_); |
| host_impl_->active_tree()->top_controls_shown_ratio()->PushPendingToActive(); |
| host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio( |
| 15 / top_controls_height_, 15 / top_controls_height_); |
| host_impl_->active_tree()->top_controls_shown_ratio()->PullDeltaForMainThread( |
| /* next_bmf */ false); |
| host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0, 0); |
| EnsureSyncTree(); |
| host_impl_->sync_tree()->PushBrowserControlsFromMainThread( |
| 15 / top_controls_height_, 15 / top_controls_height_); |
| |
| host_impl_->DidChangeBrowserControlsPosition(); |
| auto* property_trees = host_impl_->active_tree()->property_trees(); |
| EXPECT_EQ(gfx::Vector2dF(0, 50), |
| property_trees->inner_viewport_container_bounds_delta()); |
| EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| |
| host_impl_->ActivateSyncTree(); |
| |
| EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_EQ(CommitsToActiveTree() ? gfx::Vector2dF(0, 50) : gfx::Vector2dF(), |
| property_trees->inner_viewport_container_bounds_delta()); |
| EXPECT_FLOAT_EQ( |
| -15, host_impl_->active_tree()->top_controls_shown_ratio()->Delta() * |
| top_controls_height_); |
| EXPECT_FLOAT_EQ( |
| 15, host_impl_->active_tree()->top_controls_shown_ratio()->ActiveBase() * |
| top_controls_height_); |
| } |
| |
| // Test that changing the browser controls layout height is correctly applied to |
| // the inner viewport container bounds. That is, the browser controls layout |
| // height is the amount that the inner viewport container was shrunk outside |
| // the compositor to accommodate the browser controls. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| BrowserControlsLayoutHeightChanged) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| layer_size_, layer_size_, layer_size_); |
| DrawFrame(); |
| |
| EnsureSyncTree(); |
| host_impl_->sync_tree()->PushBrowserControlsFromMainThread(1, 1); |
| host_impl_->sync_tree()->SetBrowserControlsParams( |
| {top_controls_height_, 0, 0, 0, false, true}); |
| |
| host_impl_->active_tree()->top_controls_shown_ratio()->PushMainToPending(1); |
| host_impl_->active_tree()->top_controls_shown_ratio()->PushPendingToActive(); |
| host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0, 0); |
| |
| host_impl_->DidChangeBrowserControlsPosition(); |
| auto* property_trees = host_impl_->active_tree()->property_trees(); |
| EXPECT_EQ(gfx::Vector2dF(0, 50), |
| property_trees->inner_viewport_container_bounds_delta()); |
| EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| |
| host_impl_->ActivateSyncTree(); |
| |
| EXPECT_EQ(0, host_impl_->browser_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 |
| // browser controls offset. |
| EXPECT_EQ(CommitsToActiveTree() ? gfx::Vector2dF(0, 50) : gfx::Vector2dF(), |
| property_trees->inner_viewport_container_bounds_delta()); |
| |
| host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(1, 1); |
| host_impl_->DidChangeBrowserControlsPosition(); |
| |
| EXPECT_EQ(1, host_impl_->browser_controls_manager()->TopControlsShownRatio()); |
| EXPECT_EQ(50, host_impl_->browser_controls_manager()->TopControlsHeight()); |
| EXPECT_EQ(50, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_EQ(gfx::Vector2dF(), |
| property_trees->inner_viewport_container_bounds_delta()); |
| } |
| |
| // Test that showing/hiding the browser controls when the viewport is fully |
| // scrolled doesn't incorrectly change the viewport offset due to clamping from |
| // changing viewport bounds. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| BrowserControlsViewportOffsetClamping) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); |
| DrawFrame(); |
| |
| EXPECT_EQ(1, host_impl_->active_tree()->CurrentTopControlsShownRatio()); |
| |
| LayerImpl* outer_scroll = OuterViewportScrollLayer(); |
| LayerImpl* inner_scroll = InnerViewportScrollLayer(); |
| |
| // Scroll the viewports to max scroll offset. |
| SetScrollOffsetDelta(outer_scroll, gfx::Vector2dF(0, 200)); |
| SetScrollOffsetDelta(inner_scroll, gfx::Vector2dF(100, 100)); |
| |
| gfx::PointF viewport_offset = host_impl_->active_tree()->TotalScrollOffset(); |
| EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(), viewport_offset); |
| |
| // Hide the browser controls by 25px. |
| gfx::Vector2dF scroll_delta(0, 25); |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen)); |
| |
| // scrolling down at the max extents no longer hides the browser controls |
| EXPECT_EQ(1, host_impl_->active_tree()->CurrentTopControlsShownRatio()); |
| |
| // forcefully hide the browser controls by 25px |
| host_impl_->browser_controls_manager()->ScrollBy(scroll_delta); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_FLOAT_EQ( |
| scroll_delta.y(), |
| top_controls_height_ - |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| |
| // 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 browser controls down by 25px. |
| scroll_delta = gfx::Vector2dF(0, -25); |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| // The viewport offset shouldn't have changed. |
| EXPECT_EQ(viewport_offset, host_impl_->active_tree()->TotalScrollOffset()); |
| |
| // Scroll the viewports to max scroll offset. |
| SetScrollOffsetDelta(outer_scroll, gfx::Vector2dF(0, 200)); |
| SetScrollOffsetDelta(inner_scroll, gfx::Vector2dF(100, 100)); |
| EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(), |
| host_impl_->active_tree()->TotalScrollOffset()); |
| } |
| |
| // Test that the browser controls coming in and out maintains the same aspect |
| // ratio between the inner and outer viewports. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, BrowserControlsAspectRatio) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); |
| host_impl_->active_tree()->PushPageScaleFromMainThread(1, 0.5f, 2); |
| DrawFrame(); |
| |
| EXPECT_FLOAT_EQ(top_controls_height_, |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| |
| gfx::Vector2dF scroll_delta(0, 25); |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_FLOAT_EQ( |
| scroll_delta.y(), |
| top_controls_height_ - |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| |
| // Browser controls were hidden by 25px so the inner viewport should have |
| // expanded by that much. |
| auto* property_trees = host_impl_->active_tree()->property_trees(); |
| EXPECT_EQ(gfx::Vector2dF(0, 25), |
| property_trees->inner_viewport_container_bounds_delta()); |
| |
| // Outer viewport should match inner's aspect ratio. The bounds are ceiled. |
| float aspect_ratio = 100.0f / 125.0f; |
| auto expected = gfx::ToCeiledSize(gfx::SizeF(200, 200 / aspect_ratio)); |
| EXPECT_EQ(expected, InnerViewportScrollLayer()->bounds()); |
| } |
| |
| TEST_P(LayerTreeHostImplBrowserControlsTest, NoShrinkNotUserScrollable) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(100, 100), gfx::Size(100, 100), gfx::Size(100, 200)); |
| |
| GetScrollNode(OuterViewportScrollLayer())->user_scrollable_vertical = false; |
| DrawFrame(); |
| |
| gfx::Vector2dF delta(0, 15); |
| ui::ScrollInputType type = ui::ScrollInputType::kTouchscreen; |
| auto& handler = GetInputHandler(); |
| |
| handler.ScrollBegin(BeginState(gfx::Point(), delta, type).get(), type); |
| handler.ScrollUpdate(UpdateState(gfx::Point(), delta, type)); |
| handler.ScrollEnd(false, std::nullopt); |
| |
| // Outer viewport has overflow, but is not user-scrollable. Make sure the |
| // browser controls did not shrink when we tried to scroll. |
| EXPECT_EQ(top_controls_height_, |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| } |
| |
| // Test that scrolling the outer viewport affects the browser controls. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| BrowserControlsScrollOuterViewport) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400)); |
| DrawFrame(); |
| |
| EXPECT_EQ(top_controls_height_, |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| |
| // Send a gesture scroll that will scroll the outer viewport, make sure the |
| // browser controls get scrolled. |
| gfx::Vector2dF scroll_delta(0, 15); |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen)); |
| |
| EXPECT_EQ(OuterViewportScrollLayer()->scroll_tree_index(), |
| host_impl_->CurrentlyScrollingNode()->id); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_FLOAT_EQ( |
| scroll_delta.y(), |
| top_controls_height_ - |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| |
| scroll_delta = gfx::Vector2dF(0, 50); |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen)); |
| |
| EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_EQ(OuterViewportScrollLayer()->scroll_tree_index(), |
| host_impl_->CurrentlyScrollingNode()->id); |
| |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| // Position the viewports such that the inner viewport will be scrolled. |
| gfx::Vector2dF inner_viewport_offset(0, 25); |
| SetScrollOffsetDelta(OuterViewportScrollLayer(), gfx::Vector2dF()); |
| SetScrollOffsetDelta(InnerViewportScrollLayer(), inner_viewport_offset); |
| |
| scroll_delta = gfx::Vector2dF(0, -65); |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen)); |
| |
| EXPECT_EQ(top_controls_height_, |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| EXPECT_FLOAT_EQ( |
| inner_viewport_offset.y() + (scroll_delta.y() + top_controls_height_), |
| ScrollDelta(InnerViewportScrollLayer()).y()); |
| |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| } |
| |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| ScrollNonScrollableRootWithBrowserControls) { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| layer_size_, layer_size_, layer_size_); |
| DrawFrame(); |
| |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 50), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| |
| host_impl_->browser_controls_manager()->ScrollBegin(); |
| host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0, 50)); |
| host_impl_->browser_controls_manager()->ScrollEnd(); |
| EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| // Now that browser controls have moved, expect the clip to resize. |
| auto* property_trees = host_impl_->active_tree()->property_trees(); |
| EXPECT_EQ(gfx::Vector2dF(0, 50), |
| property_trees->inner_viewport_container_bounds_delta()); |
| |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, -25), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| |
| float scroll_increment_y = -25; |
| host_impl_->browser_controls_manager()->ScrollBegin(); |
| host_impl_->browser_controls_manager()->ScrollBy( |
| gfx::Vector2dF(0, scroll_increment_y)); |
| EXPECT_FLOAT_EQ(-scroll_increment_y, |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| // Now that browser controls have moved, expect the clip to resize. |
| EXPECT_EQ(gfx::Vector2dF(0, 25), |
| property_trees->inner_viewport_container_bounds_delta()); |
| |
| host_impl_->browser_controls_manager()->ScrollBy( |
| gfx::Vector2dF(0, scroll_increment_y)); |
| host_impl_->browser_controls_manager()->ScrollEnd(); |
| EXPECT_FLOAT_EQ(-2 * scroll_increment_y, |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| // Now that browser controls have moved, expect the clip to resize. |
| EXPECT_EQ(gfx::Vector2dF(), |
| property_trees->inner_viewport_container_bounds_delta()); |
| |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| // Verify the layer is once-again non-scrollable. |
| EXPECT_EQ(gfx::PointF(), MaxScrollOffset(InnerViewportScrollLayer())); |
| |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 10), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| } |
| |
| // Tests that activating a pending tree while there's a bounds_delta on the |
| // viewport layers from browser controls doesn't cause a scroll jump. This bug |
| // was occurring because the UpdateViewportContainerSizes was being called |
| // before the property trees were updated with the bounds_delta. |
| // crbug.com/597266. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| ViewportBoundsDeltaOnTreeActivation) { |
| // This test needs to create both the pending tree and the active tree. |
| if (CommitsToActiveTree()) { |
| GTEST_SKIP(); |
| } |
| |
| const gfx::Size inner_viewport_size(1000, 1000); |
| const gfx::Size outer_viewport_size(1000, 1000); |
| const gfx::Size content_size(2000, 2000); |
| |
| // Initialization |
| { |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| inner_viewport_size, outer_viewport_size, content_size); |
| host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 1); |
| |
| // Start off with the browser controls hidden on both main and impl. |
| host_impl_->active_tree()->SetBrowserControlsParams( |
| {top_controls_height_, 0, 0, 0, false, false}); |
| host_impl_->active_tree()->PushBrowserControlsFromMainThread(0, 0); |
| |
| CreatePendingTree(); |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| host_impl_->pending_tree(), inner_viewport_size, outer_viewport_size, |
| content_size); |
| host_impl_->pending_tree()->SetBrowserControlsParams( |
| {top_controls_height_, 0, 0, 0, false, false}); |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| |
| // Fully scroll the viewport. |
| GetInputHandler().ScrollBegin( |
| BeginState(gfx::Point(75, 75), gfx::Vector2dF(0, 2000), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen); |
| GetInputHandler().ScrollUpdate( |
| UpdateState(gfx::Point(), gfx::Vector2d(0, 2000), |
| ui::ScrollInputType::kTouchscreen)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| } |
| |
| LayerImpl* outer_scroll = OuterViewportScrollLayer(); |
| |
| ASSERT_FLOAT_EQ(0, |
| host_impl_->browser_controls_manager()->ContentTopOffset()); |
| ASSERT_EQ(1000, MaxScrollOffset(outer_scroll).y()); |
| ASSERT_EQ(1000, CurrentScrollOffset(outer_scroll).y()); |
| |
| // Kick off an animation to show the browser controls. |
| host_impl_->browser_controls_manager()->UpdateBrowserControlsState( |
| BrowserControlsState::kBoth, BrowserControlsState::kShown, true, |
| std::nullopt); |
| base::TimeTicks start_time = base::TimeTicks::Now(); |
| viz::BeginFrameArgs begin_frame_args = |
| viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1); |
| |
| // The first animation frame will not produce any delta, it will establish |
| // the animation. |
| { |
| begin_frame_args.frame_time = start_time; |
| begin_frame_args.frame_id.sequence_number++; |
| host_impl_->WillBeginImplFrame(begin_frame_args); |
| host_impl_->Animate(); |
| host_impl_->UpdateAnimationState(true); |
| host_impl_->DidFinishImplFrame(begin_frame_args); |
| float delta = |
| host_impl_->active_tree()->top_controls_shown_ratio()->Delta(); |
| ASSERT_EQ(delta, 0); |
| } |
| |
| // Pump an animation frame to put some delta in the browser controls. |
| { |
| begin_frame_args.frame_time = start_time + base::Milliseconds(50); |
| begin_frame_args.frame_id.sequence_number++; |
| host_impl_->WillBeginImplFrame(begin_frame_args); |
| host_impl_->Animate(); |
| host_impl_->UpdateAnimationState(true); |
| host_impl_->DidFinishImplFrame(begin_frame_args); |
| } |
| |
| // Pull the browser controls delta and get it back to the pending tree so that |
| // when we go to activate the pending tree we cause a change to browser |
| // controls. |
| { |
| float delta = |
| host_impl_->active_tree()->top_controls_shown_ratio()->Delta(); |
| ASSERT_GT(delta, 0); |
| ASSERT_LT(delta, 1); |
| host_impl_->active_tree() |
| ->top_controls_shown_ratio() |
| ->PullDeltaForMainThread(/* next_bmf */ false); |
| host_impl_->active_tree()->top_controls_shown_ratio()->PushMainToPending( |
| delta); |
| } |
| |
| // 200 is the kShowHideMaxDurationMs value from browser_controls_manager.cc so |
| // the browser controls should be fully animated in this frame. |
| { |
| begin_frame_args.frame_time = start_time + base::Milliseconds(200); |
| begin_frame_args.frame_id.sequence_number++; |
| host_impl_->WillBeginImplFrame(begin_frame_args); |
| host_impl_->Animate(); |
| host_impl_->UpdateAnimationState(true); |
| host_impl_->DidFinishImplFrame(begin_frame_args); |
| |
| ASSERT_EQ(50, host_impl_->browser_controls_manager()->ContentTopOffset()); |
| ASSERT_EQ(1050, MaxScrollOffset(outer_scroll).y()); |
| // NEAR because clip layer bounds are truncated in MaxScrollOffset so we |
| // lose some precision in the intermediate animation steps. |
| ASSERT_NEAR(1050, CurrentScrollOffset(outer_scroll).y(), 1); |
| } |
| |
| // Activate the pending tree which should have the same scroll value as the |
| // active tree. |
| { |
| host_impl_->pending_tree() |
| ->property_trees() |
| ->scroll_tree_mutable() |
| .SetScrollOffsetDeltaForTesting(outer_scroll->element_id(), |
| gfx::Vector2dF(0, 1050)); |
| host_impl_->ActivateSyncTree(); |
| |
| // Make sure we don't accidentally clamp the outer offset based on a bounds |
| // delta that hasn't yet been updated. |
| EXPECT_NEAR(1050, CurrentScrollOffset(outer_scroll).y(), 1); |
| } |
| } |
| |
| // Tests that when we set a child scroller (e.g. a scrolling div) as the outer |
| // viewport, scrolling it controls the browser controls. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| ReplacedOuterViewportScrollsBrowserControls) { |
| const gfx::Size scroll_content_size(400, 400); |
| const gfx::Size root_layer_size(200, 200); |
| const gfx::Size viewport_size(100, 100); |
| |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport( |
| viewport_size, viewport_size, root_layer_size); |
| |
| LayerTreeImpl* layer_tree_impl = host_impl_->active_tree(); |
| LayerImpl* outer_scroll = OuterViewportScrollLayer(); |
| |
| // Initialization: Add a child scrolling layer to the outer scroll layer and |
| // set its scroll layer as the outer viewport. This simulates setting a |
| // scrolling element as the root scroller on the page. |
| LayerImpl* clip_layer = AddLayerInActiveTree(); |
| clip_layer->SetBounds(root_layer_size); |
| CopyProperties(outer_scroll, clip_layer); |
| CreateClipNode(clip_layer); |
| LayerImpl* scroll_layer = |
| AddScrollableLayer(clip_layer, root_layer_size, scroll_content_size); |
| GetScrollNode(scroll_layer)->scrolls_outer_viewport = true; |
| |
| auto viewport_property_ids = layer_tree_impl->ViewportPropertyIdsForTesting(); |
| viewport_property_ids.outer_clip = clip_layer->clip_tree_index(); |
| viewport_property_ids.outer_scroll = scroll_layer->scroll_tree_index(); |
| layer_tree_impl->SetViewportPropertyIds(viewport_property_ids); |
| DrawFrame(); |
| |
| ASSERT_EQ(1, layer_tree_impl->CurrentTopControlsShownRatio()); |
| |
| // Scrolling should scroll the child content and the browser controls. The |
| // original outer viewport should get no scroll. |
| { |
| GetInputHandler().ScrollBegin( |
| BeginState(gfx::Point(0, 0), gfx::Vector2dF(100, 100), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen); |
| GetInputHandler().ScrollUpdate( |
| UpdateState(gfx::Point(0, 0), gfx::Vector2dF(100, 100), |
| ui::ScrollInputType::kTouchscreen)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| EXPECT_POINTF_EQ(gfx::PointF(0, 0), CurrentScrollOffset(outer_scroll)); |
| EXPECT_POINTF_EQ(gfx::PointF(100, 50), CurrentScrollOffset(scroll_layer)); |
| EXPECT_EQ(0, layer_tree_impl->CurrentTopControlsShownRatio()); |
| } |
| } |
| |
| // Check if LayerTreeImpl::GetActivelyScrollingType() returns kPrecise even |
| // when BrowserControl is consuming ScrollUpdate. |
| TEST_P(LayerTreeHostImplBrowserControlsTest, |
| BrowserControlsActivelyScrollingType) { |
| // This test creates layer on the active tree with a raster source, which |
| // doesn't work if we commit to the pending tree. |
| if (!CommitsToActiveTree()) { |
| GTEST_SKIP(); |
| } |
| |
| gfx::Size inner_size = gfx::Size(100, 100); |
| gfx::Size outer_size = gfx::Size(100, 100); |
| gfx::Size content_size = gfx::Size(100, 200); |
| SetupBrowserControlsAndScrollLayerWithVirtualViewport(inner_size, outer_size, |
| content_size); |
| |
| LayerTreeImpl* active_tree = host_impl_->active_tree(); |
| |
| // Create a content layer beneath the outer viewport scroll layer. |
| scoped_refptr<FakeRasterSource> raster_source( |
| FakeRasterSource::CreateFilled(content_size)); |
| |
| auto* picture_layer = |
| AddLayer<FakePictureLayerImpl>(host_impl_->active_tree(), raster_source); |
| CopyProperties(OuterViewportScrollLayer(), picture_layer); |
| picture_layer->SetBounds(content_size); |
| picture_layer->SetDrawsContent(true); |
| picture_layer->SetNeedsPushProperties(); |
| active_tree->PushPageScaleFromMainThread(1.0f, 1.0f, 2.0f); |
| DrawFrame(); |
| |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 50), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| // shownratio == 1 |
| EXPECT_EQ(1, host_impl_->active_tree()->CurrentTopControlsShownRatio()); |
| EXPECT_EQ(host_impl_->active_tree()->GetActivelyScrollingType(), |
| ActivelyScrollingType::kNone); |
| |
| // 0 < shownratio <1 |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), gfx::Vector2dF(0, 25), ui::ScrollInputType::kTouchscreen)); |
| EXPECT_GT(host_impl_->active_tree()->CurrentTopControlsShownRatio(), 0); |
| EXPECT_LT(host_impl_->active_tree()->CurrentTopControlsShownRatio(), 1); |
| EXPECT_EQ(host_impl_->active_tree()->GetActivelyScrollingType(), |
| ActivelyScrollingType::kPrecise); |
| |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), gfx::Vector2dF(0, 30), ui::ScrollInputType::kTouchscreen)); |
| // now shownratio == 0 |
| EXPECT_EQ(0, host_impl_->active_tree()->CurrentTopControlsShownRatio()); |
| EXPECT_EQ(host_impl_->active_tree()->GetActivelyScrollingType(), |
| ActivelyScrollingType::kPrecise); |
| |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| // scroll end, shownratio == 0 |
| EXPECT_EQ(0, host_impl_->active_tree()->CurrentTopControlsShownRatio()); |
| EXPECT_EQ(host_impl_->active_tree()->GetActivelyScrollingType(), |
| ActivelyScrollingType::kNone); |
| } |
| |
| INSTANTIATE_CLIENT_MODE_TREE_TEST_P(LayerTreeHostImplBrowserControlsTest); |
| |
| // TODO(crbug.com/458776836): Review memory limit related cc_unittests for |
| // TreesInViz Service mode |
| TEST_P(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); |
| } |
| |
| INSTANTIATE_CLIENT_MODE_TREE_TEST_P(LayerTreeHostImplWithImplicitLimitsTest); |
| void ClearMainThreadDeltasForTesting(LayerTreeHostImpl* host) { |
| host->active_tree()->ApplySentScrollAndScaleDeltasFromAbortedCommit( |
| /* next_bmf */ false, /* main_frame_applied_deltas */ false); |
| } |
| |
| gfx::PresentationFeedback ExampleFeedback() { |
| auto feedback = gfx::PresentationFeedback( |
| base::TimeTicks() + base::Milliseconds(42), base::Microseconds(18), |
| gfx::PresentationFeedback::Flags::kVSync | |
| gfx::PresentationFeedback::Flags::kHWCompletion); |
| #if BUILDFLAG(IS_MAC) |
| feedback.ca_layer_error_code = gfx::kCALayerFailedQuadBlendMode; |
| #endif |
| return feedback; |
| } |
| |
| // Test fixture that enables only Pending tree commit modes, |
| // CommitToPendingTree and CommitToPendingTreeTreesInVizClient. |
| class PendingTreeLayerTreeHostImplTest : public LayerTreeHostImplTest { |
| protected: |
| void SetupScrollDelayTest() { |
| CreateHostImpl(DefaultSettings(), CreateLayerTreeFrameSink()); |
| SetupRootLayer<SolidColorLayerImpl>(host_impl_->active_tree(), |
| gfx::Size(10, 10)); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| EXPECT_EQ(first_scroll_observed, 0); |
| } |
| |
| void QueueLatencyInfoSwapPromise(ui::LatencyComponentType type, |
| int trace_id) { |
| ui::LatencyInfo latency_info; |
| latency_info.set_trace_id(trace_id); |
| latency_info.AddLatencyNumber(type); |
| std::unique_ptr<SwapPromise> swap_promise( |
| new LatencyInfoSwapPromise(latency_info)); |
| host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise)); |
| } |
| |
| void DrawAndPresentFrame(uint32_t frame_token, bool damage = true) { |
| if (damage) { |
| host_impl_->SetFullViewportDamage(); |
| host_impl_->SetNeedsRedraw(/*animation_only=*/false, |
| /*skip_if_inside_draw=*/false); |
| } |
| DrawFrame(); |
| viz::FrameTimingDetails mock_details; |
| mock_details.presentation_feedback = ExampleFeedback(); |
| host_impl_->DidPresentCompositorFrame(frame_token, mock_details); |
| } |
| |
| void RunPaintWorkletCommitTest() { |
| auto painter_owned = std::make_unique<TestPaintWorkletLayerPainter>(); |
| TestPaintWorkletLayerPainter* painter = painter_owned.get(); |
| host_impl_->SetPaintWorkletLayerPainter(std::move(painter_owned)); |
| |
| client_host_impl(host_impl_.get())->CreatePendingTree(); |
| auto* root = SetupRootLayer<PictureLayerImpl>(host_impl_->pending_tree(), |
| gfx::Size(100, 100)); |
| root->SetNeedsPushProperties(); |
| |
| scoped_refptr<RasterSource> raster_source_with_pws = |
| FakeRasterSource::CreateFilledWithPaintWorklet(root->bounds()); |
| root->SetRasterSourceForTesting(raster_source_with_pws); |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| |
| did_prepare_tiles_ = false; |
| client_host_impl(host_impl_.get())->CommitComplete(); |
| EXPECT_FALSE(did_prepare_tiles_); |
| |
| ASSERT_EQ(root->GetPaintWorkletRecordMap().size(), 1u); |
| scoped_refptr<const PaintWorkletInput> input = |
| root->GetPaintWorkletRecordMap().begin()->first; |
| int worklet_id = input->WorkletId(); |
| |
| PaintWorkletJob painted_job(worklet_id, input, {}); |
| PaintRecord record; |
| painted_job.SetOutput(record); |
| |
| auto painted_job_vector = base::MakeRefCounted<PaintWorkletJobVector>(); |
| painted_job_vector->data.push_back(std::move(painted_job)); |
| PaintWorkletJobMap painted_job_map; |
| painted_job_map[worklet_id] = std::move(painted_job_vector); |
| |
| std::move(painter->TakeDoneCallback()).Run(std::move(painted_job_map)); |
| EXPECT_TRUE(root->GetPaintWorkletRecordMap() |
| .find(input) |
| ->second.second->EqualsForTesting(record)); |
| } |
| }; |
| |
| INSTANTIATE_COMMIT_TO_PENDING_TREE_TEST_P(PendingTreeLayerTreeHostImplTest); |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, SyncedScrollAbortedCommit) { |
| CreateHostImpl(DefaultSettings(), CreateLayerTreeFrameSink()); |
| CreatePendingTree(); |
| gfx::PointF scroll_offset(20, 30); |
| auto* root = SetupDefaultRootLayer(gfx::Size(110, 110)); |
| auto& scroll_tree = |
| root->layer_tree_impl()->property_trees()->scroll_tree_mutable(); |
| root->SetHitTestOpaqueness(HitTestOpaqueness::kMixed); |
| std::unique_ptr<AnimationHost> main_thread_animation_host( |
| AnimationHost::CreateForTesting(ThreadInstance::kMain)); |
| |
| // SyncedProperty should be created on the pending tree and then pushed to the |
| // active tree, to avoid bifurcation. Simulate commit by pushing base scroll |
| // offsets to pending tree. |
| PushScrollOffsetsToPendingTree({{root->element_id(), gfx::PointF(0, 0)}}); |
| ClearNonScrollSyncTreeDeltasForTesting(); |
| host_impl_->active_tree() |
| ->property_trees() |
| ->scroll_tree_mutable() |
| .PushScrollUpdatesFromPendingTree( |
| host_impl_->pending_tree()->property_trees(), |
| host_impl_->active_tree()); |
| |
| CreateScrollNode(root, gfx::Size(10, 10)); |
| auto* synced_scroll = scroll_tree.GetSyncedScrollOffset(root->element_id()); |
| ASSERT_TRUE(synced_scroll); |
| scroll_tree.UpdateScrollOffsetBaseForTesting(root->element_id(), |
| scroll_offset); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| |
| gfx::Vector2dF scroll_delta(11, -15); |
| root->ScrollBy(scroll_delta); |
| EXPECT_EQ(scroll_delta, synced_scroll->UnsentDelta()); |
| // Null mutator_host implies non-pipelined main frame |
| host_impl_->ProcessCompositorDeltas(/* mutator_host */ nullptr); |
| EXPECT_EQ(scroll_delta, synced_scroll->reflected_delta_in_main_tree()); |
| EXPECT_EQ(gfx::Vector2dF(), |
| synced_scroll->next_reflected_delta_in_main_tree()); |
| |
| std::unique_ptr<CompositorCommitData> commit_data2; |
| gfx::Vector2dF scroll_delta2(-5, 27); |
| root->ScrollBy(scroll_delta2); |
| EXPECT_EQ(scroll_delta2, synced_scroll->UnsentDelta()); |
| // Non-null mutator_host implies pipelined main frame |
| host_impl_->ProcessCompositorDeltas(main_thread_animation_host.get()); |
| EXPECT_EQ(scroll_delta, synced_scroll->reflected_delta_in_main_tree()); |
| EXPECT_EQ(scroll_delta2, synced_scroll->next_reflected_delta_in_main_tree()); |
| |
| // Simulate aborting the second main frame. Scroll deltas applied by the |
| // second frame should be combined with delta from first frame. |
| root->layer_tree_impl()->ApplySentScrollAndScaleDeltasFromAbortedCommit( |
| /* next_bmf */ true, /* main_frame_applied_deltas */ true); |
| EXPECT_EQ(scroll_delta + scroll_delta2, |
| synced_scroll->reflected_delta_in_main_tree()); |
| EXPECT_EQ(gfx::Vector2dF(), |
| synced_scroll->next_reflected_delta_in_main_tree()); |
| |
| // Send a third main frame, pipelined behind the first. |
| gfx::Vector2dF scroll_delta3(-2, -13); |
| root->ScrollBy(scroll_delta3); |
| EXPECT_EQ(scroll_delta3, synced_scroll->UnsentDelta()); |
| host_impl_->ProcessCompositorDeltas(main_thread_animation_host.get()); |
| EXPECT_EQ(scroll_delta + scroll_delta2, |
| synced_scroll->reflected_delta_in_main_tree()); |
| EXPECT_EQ(scroll_delta3, synced_scroll->next_reflected_delta_in_main_tree()); |
| |
| // Simulate commit of the first frame |
| PushScrollOffsetsToPendingTree( |
| {{root->element_id(), scroll_offset + scroll_delta + scroll_delta2}}); |
| EXPECT_EQ(scroll_offset + scroll_delta + scroll_delta2, |
| synced_scroll->PendingBase()); |
| EXPECT_EQ(scroll_delta3, synced_scroll->PendingDelta()); |
| EXPECT_EQ(scroll_delta3, synced_scroll->reflected_delta_in_main_tree()); |
| EXPECT_EQ(gfx::Vector2dF(), |
| synced_scroll->next_reflected_delta_in_main_tree()); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, ActivateTreeScrollingNodeDisappeared) { |
| SetupViewportLayersOuterScrolls(gfx::Size(100, 100), gfx::Size(1000, 1000)); |
| |
| auto status = GetInputHandler().ScrollBegin( |
| BeginState(gfx::Point(30, 30), gfx::Vector2d(0, 10), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, status.thread); |
| EXPECT_TRUE(status.main_thread_repaint_reasons.empty()); |
| EXPECT_TRUE(status.main_thread_hit_test_reasons.empty()); |
| GetInputHandler().ScrollUpdate(UpdateState(gfx::Point(), gfx::Vector2d(0, 10), |
| ui::ScrollInputType::kWheel)); |
| EXPECT_TRUE(host_impl_->active_tree()->CurrentlyScrollingNode()); |
| |
| // Create the pending tree containing only the root layer. |
| CreatePendingTree(); |
| PropertyTrees pending_property_trees; |
| pending_property_trees.set_sequence_number( |
| host_impl_->active_tree()->property_trees()->sequence_number() + 1); |
| host_impl_->pending_tree()->SetPropertyTrees(pending_property_trees, |
| ViewportPropertyIds()); |
| SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), gfx::Size(100, 100)); |
| host_impl_->ActivateSyncTree(); |
| |
| // The scroll should stop. |
| EXPECT_FALSE(host_impl_->active_tree()->CurrentlyScrollingNode()); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, AnimationSchedulingPendingTree) { |
| CreatePendingTree(); |
| auto* root = |
| SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), gfx::Size(50, 50)); |
| root->SetNeedsPushProperties(); |
| |
| auto* child = AddLayer<LayerImpl>(host_impl_->pending_tree()); |
| child->SetBounds(gfx::Size(10, 10)); |
| child->SetDrawsContent(true); |
| child->SetNeedsPushProperties(); |
| |
| host_impl_->pending_tree()->SetElementIdsForTesting(); |
| CopyProperties(root, child); |
| CreateTransformNode(child); |
| |
| scoped_refptr<Animation> animation = |
| Animation::Create(AnimationIdProvider::NextAnimationId()); |
| timeline()->AttachAnimation(animation); |
| animation->AttachElement(child->element_id()); |
| AddAnimatedTransformToAnimation(animation.get(), 10.0, 3, 0); |
| animation->GetKeyframeModel(TargetProperty::TRANSFORM) |
| ->set_affects_active_elements(false); |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| |
| EXPECT_FALSE(did_request_next_frame_); |
| EXPECT_FALSE(did_request_redraw_); |
| EXPECT_FALSE(did_request_commit_); |
| |
| client_host_impl(host_impl_.get())->AnimatePendingTreeAfterCommit(); |
| |
| // An animation exists on the pending layer. Doing |
| // AnimatePendingTreeAfterCommit() requests another frame. |
| // In reality, animations without has_set_start_time() == true do not need to |
| // be continuously ticked on the pending tree, so it should not request |
| // another animation frame here. But we currently do so blindly if any |
| // animation exists. |
| EXPECT_TRUE(did_request_next_frame_); |
| // The pending tree with an animation does not need to draw after animating. |
| EXPECT_FALSE(did_request_redraw_); |
| EXPECT_FALSE(did_request_commit_); |
| |
| did_request_next_frame_ = false; |
| did_request_redraw_ = false; |
| did_request_commit_ = false; |
| |
| host_impl_->ActivateSyncTree(); |
| |
| // When the animation activates, we should request another animation frame |
| // to keep the animation moving. |
| EXPECT_TRUE(did_request_next_frame_); |
| // On activation we don't need to request a redraw for the animation, |
| // activating will draw on its own when it's ready. |
| EXPECT_FALSE(did_request_redraw_); |
| EXPECT_FALSE(did_request_commit_); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, AnimationSchedulingActiveTree) { |
| LayerImpl* root = SetupDefaultRootLayer(gfx::Size(50, 50)); |
| LayerImpl* child = AddLayerInActiveTree(); |
| child->SetBounds(gfx::Size(10, 10)); |
| child->SetDrawsContent(true); |
| |
| host_impl_->active_tree()->SetElementIdsForTesting(); |
| CopyProperties(root, child); |
| CreateTransformNode(child); |
| |
| // Add a translate from 6,7 to 8,9. |
| gfx::TransformOperations start; |
| start.AppendTranslate(6, 7, 0); |
| gfx::TransformOperations end; |
| end.AppendTranslate(8, 9, 0); |
| AddAnimatedTransformToElementWithAnimation(child->element_id(), timeline(), |
| 4.0, start, end); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| |
| base::TimeTicks now = base::TimeTicks::Now(); |
| host_impl_->WillBeginImplFrame( |
| viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2, now)); |
| |
| // TODO(crbug.com/40443202): We always request a new frame and a draw for |
| // animations that are on the pending tree, but we don't need to do that |
| // unless they are waiting for some future time to start. |
| EXPECT_TRUE(did_request_next_frame_); |
| EXPECT_TRUE(did_request_redraw_); |
| EXPECT_FALSE(did_request_commit_); |
| did_request_next_frame_ = false; |
| did_request_redraw_ = false; |
| did_request_commit_ = false; |
| |
| host_impl_->ActivateAnimations(); |
| |
| // On activating an animation, we should request another frame so that we'll |
| // continue ticking the animation. |
| EXPECT_TRUE(did_request_next_frame_); |
| EXPECT_FALSE(did_request_redraw_); |
| EXPECT_FALSE(did_request_commit_); |
| did_request_next_frame_ = false; |
| did_request_redraw_ = false; |
| did_request_commit_ = false; |
| |
| // The next frame after activating, we'll tick the animation again. |
| host_impl_->Animate(); |
| |
| // An animation exists on the active layer. Doing Animate() requests another |
| // frame after the current one. |
| EXPECT_TRUE(did_request_next_frame_); |
| // The animation should cause us to draw at the frame's deadline. |
| EXPECT_TRUE(did_request_redraw_); |
| EXPECT_FALSE(did_request_commit_); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, |
| ScrollbarVisibilityChangeCausesRedrawAndCommit) { |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY; |
| settings.scrollbar_fade_delay = base::Milliseconds(20); |
| settings.scrollbar_fade_duration = base::Milliseconds(20); |
| gfx::Size viewport_size(50, 50); |
| gfx::Size content_size(100, 100); |
| |
| CreateHostImpl(settings, CreateLayerTreeFrameSink()); |
| CreatePendingTree(); |
| SetupViewportLayers(host_impl_->pending_tree(), viewport_size, content_size, |
| content_size); |
| LayerImpl* scroll = |
| host_impl_->pending_tree()->OuterViewportScrollLayerForTesting(); |
| auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>( |
| host_impl_->pending_tree(), ScrollbarOrientation::kVertical, 10, 0, |
| false); |
| SetupScrollbarLayer(scroll, scrollbar); |
| scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(90, 0)); |
| |
| host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1); |
| host_impl_->ActivateSyncTree(); |
| |
| ScrollbarAnimationController* scrollbar_controller = |
| host_impl_->ScrollbarAnimationControllerForElementId( |
| scroll->element_id()); |
| |
| // Scrollbars will flash shown but we should have a fade out animation |
| // queued. Run it and fade out the scrollbars. |
| ASSERT_FALSE(scrollbar_controller->ScrollbarsHidden()); |
| EXPECT_TRUE(scrollbar_controller->visibility_changed()); |
| ClearMainThreadDeltasForTesting(host_impl_.get()); |
| auto commit_data = host_impl_->ProcessCompositorDeltas( |
| /* main_thread_mutator_host */ nullptr); |
| using ScrollbarsInfo = CompositorCommitData::ScrollbarsUpdateInfo; |
| EXPECT_THAT(commit_data->scrollbars, testing::ElementsAre(ScrollbarsInfo{ |
| scroll->element_id(), false})); |
| EXPECT_FALSE(scrollbar_controller->visibility_changed()); |
| { |
| ASSERT_FALSE(animation_task_.is_null()); |
| ASSERT_FALSE(animation_task_.IsCancelled()); |
| std::move(animation_task_).Run(); |
| |
| base::TimeTicks fake_now = base::TimeTicks::Now(); |
| scrollbar_controller->Animate(fake_now); |
| fake_now += settings.scrollbar_fade_delay; |
| scrollbar_controller->Animate(fake_now); |
| |
| ASSERT_TRUE(scrollbar_controller->ScrollbarsHidden()); |
| ClearMainThreadDeltasForTesting(host_impl_.get()); |
| commit_data = host_impl_->ProcessCompositorDeltas( |
| /* main_thread_mutator_host */ nullptr); |
| EXPECT_THAT(commit_data->scrollbars, testing::ElementsAre(ScrollbarsInfo{ |
| scroll->element_id(), true})); |
| EXPECT_FALSE(scrollbar_controller->visibility_changed()); |
| } |
| |
| // Move the mouse over the scrollbar region. This should post a delayed fade |
| // in task. Execute it to fade in the scrollbars. |
| { |
| animation_task_.Reset(); |
| scrollbar_controller->DidMouseMove(gfx::PointF(90, 0)); |
| ASSERT_FALSE(animation_task_.is_null()); |
| ASSERT_FALSE(animation_task_.IsCancelled()); |
| } |
| |
| // The fade in task should cause the scrollbars to show. Ensure that we |
| // requested a redraw and a commit. |
| { |
| did_request_redraw_ = false; |
| did_request_commit_ = false; |
| ASSERT_TRUE(scrollbar_controller->ScrollbarsHidden()); |
| EXPECT_FALSE(scrollbar_controller->visibility_changed()); |
| ClearMainThreadDeltasForTesting(host_impl_.get()); |
| commit_data = host_impl_->ProcessCompositorDeltas( |
| /* main_thread_mutator_host */ nullptr); |
| EXPECT_TRUE(commit_data->scrollbars.empty()); |
| std::move(animation_task_).Run(); |
| |
| base::TimeTicks fake_now = base::TimeTicks::Now(); |
| scrollbar_controller->Animate(fake_now); |
| fake_now += settings.scrollbar_fade_duration; |
| scrollbar_controller->Animate(fake_now); |
| |
| ASSERT_FALSE(scrollbar_controller->ScrollbarsHidden()); |
| EXPECT_TRUE(scrollbar_controller->visibility_changed()); |
| ClearMainThreadDeltasForTesting(host_impl_.get()); |
| commit_data = host_impl_->ProcessCompositorDeltas( |
| /* main_thread_mutator_host */ nullptr); |
| EXPECT_THAT(commit_data->scrollbars, testing::ElementsAre(ScrollbarsInfo{ |
| scroll->element_id(), false})); |
| EXPECT_FALSE(scrollbar_controller->visibility_changed()); |
| EXPECT_TRUE(did_request_redraw_); |
| EXPECT_TRUE(did_request_commit_); |
| } |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, ClampingAfterActivation) { |
| CreatePendingTree(); |
| host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1); |
| SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50), |
| gfx::Size(100, 100), gfx::Size(100, 100)); |
| host_impl_->ActivateSyncTree(); |
| |
| CreatePendingTree(); |
| const gfx::PointF pending_scroll(-100, -100); |
| LayerImpl* active_outer_layer = OuterViewportScrollLayer(); |
| LayerImpl* pending_outer_layer = |
| host_impl_->pending_tree()->OuterViewportScrollLayerForTesting(); |
| pending_outer_layer->layer_tree_impl() |
| ->property_trees() |
| ->scroll_tree_mutable() |
| .UpdateScrollOffsetBaseForTesting(pending_outer_layer->element_id(), |
| pending_scroll); |
| |
| host_impl_->ActivateSyncTree(); |
| // Scrolloffsets on the active tree will be clamped after activation. |
| EXPECT_EQ(CurrentScrollOffset(active_outer_layer), gfx::PointF(0, 0)); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, RootLayerScrollOffsetDelegation) { |
| TestInputHandlerClient scroll_watcher; |
| SetupViewportLayersInnerScrolls(gfx::Size(10, 20), gfx::Size(100, 100)); |
| auto* scroll_layer = InnerViewportScrollLayer(); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| |
| GetInputHandler().BindToClient(&scroll_watcher); |
| |
| gfx::Vector2dF initial_scroll_delta(10, 10); |
| scroll_layer->layer_tree_impl() |
| ->property_trees() |
| ->scroll_tree_mutable() |
| .UpdateScrollOffsetBaseForTesting(scroll_layer->element_id(), |
| gfx::PointF()); |
| SetScrollOffsetDelta(scroll_layer, initial_scroll_delta); |
| |
| EXPECT_EQ(gfx::PointF(), scroll_watcher.last_set_scroll_offset()); |
| |
| // Requesting an update results in the current scroll offset being set. |
| GetInputHandler().RequestUpdateForSynchronousInputHandler(); |
| EXPECT_EQ(initial_scroll_delta, |
| scroll_watcher.last_set_scroll_offset().OffsetFromOrigin()); |
| |
| // 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_watcher.scrollable_size()); |
| EXPECT_EQ(gfx::PointF(90, 80), scroll_watcher.max_scroll_offset()); |
| EXPECT_EQ(1, scroll_watcher.page_scale_factor()); |
| EXPECT_EQ(1, scroll_watcher.min_page_scale_factor()); |
| EXPECT_EQ(1, scroll_watcher.max_page_scale_factor()); |
| |
| // Put a page scale on the tree. |
| host_impl_->active_tree()->PushPageScaleFromMainThread(2, 0.5f, 4); |
| EXPECT_EQ(1, scroll_watcher.page_scale_factor()); |
| EXPECT_EQ(1, scroll_watcher.min_page_scale_factor()); |
| EXPECT_EQ(1, scroll_watcher.max_page_scale_factor()); |
| // Activation will update the delegate. |
| host_impl_->ActivateSyncTree(); |
| EXPECT_EQ(2, scroll_watcher.page_scale_factor()); |
| EXPECT_EQ(.5f, scroll_watcher.min_page_scale_factor()); |
| EXPECT_EQ(4, scroll_watcher.max_page_scale_factor()); |
| |
| // Animating page scale can change the root offset, so it should update the |
| // delegate. Also resets the page scale to 1 for the rest of the test. |
| host_impl_->LayerTreeHostImpl::StartPageScaleAnimation( |
| gfx::Point(0, 0), false, 1, base::TimeDelta()); |
| host_impl_->Animate(); |
| EXPECT_EQ(1, scroll_watcher.page_scale_factor()); |
| EXPECT_EQ(.5f, scroll_watcher.min_page_scale_factor()); |
| EXPECT_EQ(4, scroll_watcher.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). |
| GetInputHandler().ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(), |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen); |
| GetInputHandler().PinchGestureBegin(gfx::Point(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().PinchGestureUpdate(2, gfx::Point()); |
| GetInputHandler().PinchGestureUpdate(.5f, gfx::Point()); |
| GetInputHandler().PinchGestureEnd(gfx::Point()); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| // Scrolling should be relative to the offset as given by the delegate. |
| gfx::Vector2dF scroll_delta(0, 10); |
| gfx::PointF current_offset(7, 8); |
| |
| EXPECT_EQ(ScrollThread::kScrollOnImplThread, |
| GetInputHandler() |
| .ScrollBegin(BeginState(gfx::Point(), scroll_delta, |
| ui::ScrollInputType::kTouchscreen) |
| .get(), |
| ui::ScrollInputType::kTouchscreen) |
| .thread); |
| GetInputHandler().SetSynchronousInputHandlerRootScrollOffset(current_offset); |
| |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen)); |
| EXPECT_EQ(current_offset + scroll_delta, |
| scroll_watcher.last_set_scroll_offset()); |
| |
| current_offset = gfx::PointF(42, 41); |
| GetInputHandler().SetSynchronousInputHandlerRootScrollOffset(current_offset); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), scroll_delta, ui::ScrollInputType::kTouchscreen)); |
| EXPECT_EQ(current_offset + scroll_delta, |
| scroll_watcher.last_set_scroll_offset()); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| GetInputHandler().SetSynchronousInputHandlerRootScrollOffset(gfx::PointF()); |
| |
| // Forces a full tree synchronization and ensures that the scroll delegate |
| // sees the correct size of the new tree. |
| gfx::Size new_viewport_size(21, 12); |
| gfx::Size new_content_size(42, 24); |
| CreatePendingTree(); |
| host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1); |
| SetupViewportLayers(host_impl_->pending_tree(), new_viewport_size, |
| new_content_size, new_content_size); |
| host_impl_->ActivateSyncTree(); |
| EXPECT_EQ(gfx::SizeF(new_content_size), scroll_watcher.scrollable_size()); |
| |
| // Tear down the LayerTreeHostImpl before the InputHandlerClient. |
| host_impl_->ReleaseLayerTreeFrameSink(); |
| host_impl_ = nullptr; |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, |
| ExternalTileConstraintReflectedInPendingTree) { |
| const gfx::Size layer_size(100, 100); |
| |
| // Set up active and pending tree. |
| CreatePendingTree(); |
| SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), layer_size); |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| host_impl_->pending_tree()->root_layer()->SetNeedsPushProperties(); |
| |
| host_impl_->ActivateSyncTree(); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| |
| CreatePendingTree(); |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| |
| EXPECT_FALSE(host_impl_->pending_tree()->needs_update_draw_properties()); |
| EXPECT_FALSE(host_impl_->active_tree()->needs_update_draw_properties()); |
| |
| // Update external constraints should set_needs_update_draw_properties on |
| // both trees. |
| gfx::Transform external_transform; |
| gfx::Rect external_viewport(10, 20); |
| host_impl_->SetExternalTilePriorityConstraints(external_viewport, |
| external_transform); |
| EXPECT_TRUE(host_impl_->pending_tree()->needs_update_draw_properties()); |
| EXPECT_TRUE(host_impl_->active_tree()->needs_update_draw_properties()); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, OneScrollForFirstScrollDelay) { |
| SetupScrollDelayTest(); |
| |
| QueueLatencyInfoSwapPromise( |
| ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, 5); |
| |
| DrawAndPresentFrame(1); |
| |
| EXPECT_EQ(first_scroll_observed, 1); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, OtherInputsForFirstScrollDelay) { |
| SetupScrollDelayTest(); |
| |
| QueueLatencyInfoSwapPromise(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 5); |
| |
| DrawAndPresentFrame(1); |
| |
| EXPECT_EQ(first_scroll_observed, 0); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, MultipleScrollsForFirstScrollDelay) { |
| SetupScrollDelayTest(); |
| |
| QueueLatencyInfoSwapPromise( |
| ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, 5); |
| DrawAndPresentFrame(1, /*damage=*/false); |
| EXPECT_EQ(first_scroll_observed, 1); |
| |
| QueueLatencyInfoSwapPromise( |
| ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, 6); |
| DrawAndPresentFrame(2, /*damage=*/true); |
| EXPECT_EQ(first_scroll_observed, 1); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, InvalidLayerNotAddedToRasterQueue) { |
| CreatePendingTree(); |
| |
| scoped_refptr<RasterSource> raster_source_with_tiles( |
| FakeRasterSource::CreateFilled(gfx::Size(10, 10))); |
| |
| auto* layer = SetupRootLayer<FakePictureLayerImpl>(host_impl_->pending_tree(), |
| gfx::Size(10, 10)); |
| layer->set_gpu_raster_max_texture_size( |
| host_impl_->active_tree()->GetDeviceViewport().size()); |
| layer->SetDrawsContent(true); |
| layer->tilings()->AddTiling(gfx::AxisTransform2d(), raster_source_with_tiles); |
| layer->SetRasterSourceForTesting(raster_source_with_tiles); |
| layer->tilings()->tiling_at(0)->set_resolution( |
| TileResolution::HIGH_RESOLUTION); |
| layer->tilings()->tiling_at(0)->CreateAllTilesForTesting(); |
| layer->tilings()->UpdateTilePriorities(gfx::Rect(gfx::Size(10, 10)), 1, 1.0, |
| Occlusion(), true); |
| |
| layer->set_has_valid_tile_priorities(true); |
| std::unique_ptr<RasterTilePriorityQueue> non_empty_raster_priority_queue_all = |
| host_impl_->BuildRasterQueue(TreePriority::SAME_PRIORITY_FOR_BOTH_TREES, |
| RasterTilePriorityQueue::Type::ALL); |
| EXPECT_FALSE(non_empty_raster_priority_queue_all->IsEmpty()); |
| |
| layer->set_has_valid_tile_priorities(false); |
| std::unique_ptr<RasterTilePriorityQueue> empty_raster_priority_queue_all = |
| host_impl_->BuildRasterQueue(TreePriority::SAME_PRIORITY_FOR_BOTH_TREES, |
| RasterTilePriorityQueue::Type::ALL); |
| EXPECT_TRUE(empty_raster_priority_queue_all->IsEmpty()); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, DidBecomeActive) { |
| CreatePendingTree(); |
| host_impl_->ActivateSyncTree(); |
| CreatePendingTree(); |
| |
| LayerTreeImpl* pending_tree = host_impl_->pending_tree(); |
| |
| auto* pending_layer = |
| SetupRootLayer<FakePictureLayerImpl>(pending_tree, gfx::Size(10, 10)); |
| |
| EXPECT_EQ(0u, pending_layer->did_become_active_call_count()); |
| pending_tree->DidBecomeActive(); |
| EXPECT_EQ(1u, pending_layer->did_become_active_call_count()); |
| |
| std::unique_ptr<FakePictureLayerImpl> mask_layer = |
| FakePictureLayerImpl::Create(pending_tree, next_layer_id_++); |
| FakePictureLayerImpl* raw_mask_layer = mask_layer.get(); |
| SetupMaskProperties(pending_layer, raw_mask_layer); |
| pending_tree->AddLayer(std::move(mask_layer)); |
| |
| EXPECT_EQ(1u, pending_layer->did_become_active_call_count()); |
| EXPECT_EQ(0u, raw_mask_layer->did_become_active_call_count()); |
| pending_tree->DidBecomeActive(); |
| EXPECT_EQ(2u, pending_layer->did_become_active_call_count()); |
| EXPECT_EQ(1u, raw_mask_layer->did_become_active_call_count()); |
| |
| EXPECT_EQ(2u, pending_layer->did_become_active_call_count()); |
| EXPECT_EQ(1u, raw_mask_layer->did_become_active_call_count()); |
| pending_tree->DidBecomeActive(); |
| EXPECT_EQ(3u, pending_layer->did_become_active_call_count()); |
| EXPECT_EQ(2u, raw_mask_layer->did_become_active_call_count()); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, UpdatePageScaleFactorOnActiveTree) { |
| // Check page scale factor updates the property trees when an update is made |
| // on the active tree. |
| CreatePendingTree(); |
| host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 3); |
| SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50), |
| gfx::Size(100, 100), gfx::Size(100, 100)); |
| host_impl_->ActivateSyncTree(); |
| DrawFrame(); |
| |
| CreatePendingTree(); |
| host_impl_->active_tree()->SetPageScaleOnActiveTree(2); |
| |
| TransformNode* active_tree_node = |
| host_impl_->active_tree()->PageScaleTransformNode(); |
| // SetPageScaleOnActiveTree also updates the factors in property trees. |
| EXPECT_TRUE(active_tree_node->local.IsScale2d()); |
| EXPECT_EQ(gfx::Vector2dF(2, 2), active_tree_node->local.To2dScale()); |
| EXPECT_EQ(gfx::Point3F(), active_tree_node->origin); |
| EXPECT_EQ(2, host_impl_->active_tree()->current_page_scale_factor()); |
| EXPECT_EQ(2, host_impl_->active_tree() |
| ->property_trees() |
| ->transform_tree() |
| .page_scale_factor()); |
| |
| TransformNode* pending_tree_node = |
| host_impl_->pending_tree()->PageScaleTransformNode(); |
| |
| // Since the pending tree shares the scale factor with the active tree, its |
| // value and property trees should also have been updated. |
| EXPECT_TRUE(pending_tree_node->local.IsScale2d()); |
| EXPECT_EQ(gfx::Vector2dF(2, 2), pending_tree_node->local.To2dScale()); |
| EXPECT_EQ(gfx::Point3F(), pending_tree_node->origin); |
| EXPECT_EQ(2, host_impl_->pending_tree()->current_page_scale_factor()); |
| EXPECT_EQ(2, host_impl_->pending_tree() |
| ->property_trees() |
| ->transform_tree() |
| .page_scale_factor()); |
| |
| // Update draw properties doesn't change the correct values |
| host_impl_->pending_tree()->set_needs_update_draw_properties(); |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| pending_tree_node = host_impl_->pending_tree()->PageScaleTransformNode(); |
| EXPECT_TRUE(pending_tree_node->local.IsScale2d()); |
| EXPECT_EQ(gfx::Vector2dF(2, 2), pending_tree_node->local.To2dScale()); |
| EXPECT_EQ(gfx::Point3F(), pending_tree_node->origin); |
| |
| host_impl_->ActivateSyncTree(); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| active_tree_node = host_impl_->active_tree()->PageScaleTransformNode(); |
| EXPECT_TRUE(active_tree_node->local.IsScale2d()); |
| EXPECT_EQ(gfx::Vector2dF(2, 2), active_tree_node->local.To2dScale()); |
| EXPECT_EQ(gfx::Point3F(), active_tree_node->origin); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, CheckerImagingTileInvalidation) { |
| LayerTreeSettings settings = LegacySWSettings(); |
| settings.enable_checker_imaging = true; |
| settings.min_image_bytes_to_checker = 512 * 1024; |
| settings.default_tile_size = gfx::Size(256, 256); |
| settings.max_untiled_layer_size = gfx::Size(256, 256); |
| CreateHostImpl(settings, CreateLayerTreeFrameSink()); |
| gfx::Size layer_size = gfx::Size(750, 750); |
| |
| FakeRecordingSource recording_source(layer_size); |
| PaintImage checkerable_image = |
| PaintImageBuilder::WithCopy( |
| CreateDiscardablePaintImage(gfx::Size(500, 500))) |
| .set_decoding_mode(PaintImage::DecodingMode::kAsync) |
| .TakePaintImage(); |
| recording_source.add_draw_image(checkerable_image, gfx::Point(0, 0)); |
| |
| SkColor non_solid_color = SkColorSetARGB(128, 45, 56, 67); |
| PaintFlags non_solid_flags; |
| non_solid_flags.setColor(non_solid_color); |
| recording_source.add_draw_rect_with_flags(gfx::Rect(510, 0, 200, 600), |
| non_solid_flags); |
| recording_source.add_draw_rect_with_flags(gfx::Rect(0, 510, 200, 400), |
| non_solid_flags); |
| recording_source.Rerecord(); |
| scoped_refptr<RasterSource> raster_source = |
| recording_source.CreateRasterSource(); |
| |
| viz::BeginFrameArgs begin_frame_args = |
| viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1); |
| host_impl_->WillBeginImplFrame(begin_frame_args); |
| |
| // Create the pending tree. |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->BeginCommit(0, BeginMainFrameTraceId{1}); |
| LayerTreeImpl* pending_tree = host_impl_->pending_tree(); |
| auto* root = SetupRootLayer<FakePictureLayerImpl>(pending_tree, layer_size, |
| raster_source); |
| root->SetDrawsContent(true); |
| UpdateDrawProperties(pending_tree); |
| |
| // CompleteCommit which should perform a PrepareTiles, adding tilings for the |
| // root layer, each one having a raster task. |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->CommitComplete(); |
| EXPECT_EQ(root->num_tilings(), 1U); |
| const PictureLayerTiling* tiling = root->tilings()->tiling_at(0); |
| EXPECT_EQ(tiling->AllTilesForTesting().size(), 9U); |
| for (auto* tile : tiling->AllTilesForTesting()) { |
| EXPECT_TRUE(tile->HasRasterTask()); |
| } |
| |
| // Activate the pending tree and ensure that all tiles are rasterized. |
| ASSERT_TRUE( |
| base::test::RunUntil([&]() { return did_notify_ready_to_activate_; })); |
| for (auto* tile : tiling->AllTilesForTesting()) { |
| EXPECT_FALSE(tile->HasRasterTask()); |
| } |
| |
| // PrepareTiles should have scheduled a decode with the ImageDecodeService, |
| // ensure that it requests an impl-side invalidation. |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return did_request_impl_side_invalidation_; })); |
| |
| // Invalidate content on impl-side and ensure that the correct tiles are |
| // invalidated on the pending tree. |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->InvalidateContentOnImplSide(); |
| pending_tree = host_impl_->pending_tree(); |
| root = static_cast<FakePictureLayerImpl*>(pending_tree->root_layer()); |
| for (auto* tile : root->tilings()->tiling_at(0)->AllTilesForTesting()) { |
| if (tile->tiling_i_index() < 2 && tile->tiling_j_index() < 2) { |
| EXPECT_TRUE(tile->HasRasterTask()); |
| } else { |
| EXPECT_FALSE(tile->HasRasterTask()); |
| } |
| } |
| const auto expected_invalidation = |
| ImageRectsToRegion(root->discardable_image_map()->GetRectsForImage( |
| checkerable_image.stable_id())); |
| EXPECT_EQ(expected_invalidation, *(root->GetPendingInvalidation())); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, UpdatedTilingsForNonDrawingLayers) { |
| gfx::Size layer_bounds(500, 500); |
| |
| CreatePendingTree(); |
| auto* root = |
| SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), layer_bounds); |
| |
| scoped_refptr<FakeRasterSource> raster_source( |
| FakeRasterSource::CreateFilled(layer_bounds)); |
| auto* animated_transform_layer = |
| AddLayer<FakePictureLayerImpl>(host_impl_->pending_tree(), raster_source); |
| animated_transform_layer->SetBounds(layer_bounds); |
| animated_transform_layer->SetDrawsContent(true); |
| |
| host_impl_->pending_tree()->SetElementIdsForTesting(); |
| gfx::Transform singular; |
| singular.Scale3d(6, 6, 0); |
| CopyProperties(root, animated_transform_layer); |
| CreateTransformNode(animated_transform_layer).local = singular; |
| |
| // A layer with a non-invertible transform is not drawn or rasterized. Since |
| // this layer is not rasterized, we shouldn't be creating any tilings for it. |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| EXPECT_FALSE(animated_transform_layer->HasValidTilePriorities()); |
| EXPECT_EQ(animated_transform_layer->tilings()->num_tilings(), 0u); |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| EXPECT_FALSE(animated_transform_layer->raster_even_if_not_drawn()); |
| EXPECT_FALSE(animated_transform_layer->contributes_to_drawn_render_surface()); |
| EXPECT_EQ(animated_transform_layer->tilings()->num_tilings(), 0u); |
| |
| // Now add a transform animation to this layer. While we don't drawn layers |
| // with non-invertible transforms, we still raster them if there is a |
| // transform animation. |
| gfx::TransformOperations start_transform_operations; |
| start_transform_operations.AppendMatrix(singular); |
| gfx::TransformOperations end_transform_operations; |
| AddAnimatedTransformToElementWithAnimation( |
| animated_transform_layer->element_id(), timeline(), 10.0, |
| start_transform_operations, end_transform_operations); |
| |
| // The layer is still not drawn, but it will be rasterized. Since the layer is |
| // rasterized, we should be creating tilings for it in UpdateDrawProperties. |
| // However, none of these tiles should be required for activation. |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| EXPECT_TRUE(animated_transform_layer->raster_even_if_not_drawn()); |
| EXPECT_FALSE(animated_transform_layer->contributes_to_drawn_render_surface()); |
| ASSERT_EQ(animated_transform_layer->tilings()->num_tilings(), 1u); |
| EXPECT_FALSE(animated_transform_layer->tilings() |
| ->tiling_at(0) |
| ->can_require_tiles_for_activation()); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, |
| RasterTilePrioritizationForNonDrawingLayers) { |
| gfx::Size layer_bounds(500, 500); |
| CreatePendingTree(); |
| auto* root = |
| SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), layer_bounds); |
| root->SetBounds(layer_bounds); |
| |
| scoped_refptr<FakeRasterSource> raster_source( |
| FakeRasterSource::CreateFilled(layer_bounds)); |
| |
| auto* hidden_layer = |
| AddLayer<FakePictureLayerImpl>(host_impl_->pending_tree(), raster_source); |
| hidden_layer->SetBounds(layer_bounds); |
| hidden_layer->SetDrawsContent(true); |
| hidden_layer->set_contributes_to_drawn_render_surface(true); |
| CopyProperties(root, hidden_layer); |
| |
| auto* drawing_layer = |
| AddLayer<FakePictureLayerImpl>(host_impl_->pending_tree(), raster_source); |
| drawing_layer->SetBounds(layer_bounds); |
| drawing_layer->SetDrawsContent(true); |
| drawing_layer->set_contributes_to_drawn_render_surface(true); |
| CopyProperties(root, drawing_layer); |
| |
| gfx::Rect layer_rect(0, 0, 500, 500); |
| |
| hidden_layer->tilings()->AddTiling(gfx::AxisTransform2d(), raster_source); |
| PictureLayerTiling* hidden_tiling = hidden_layer->tilings()->tiling_at(0); |
| hidden_tiling->set_resolution(TileResolution::HIGH_RESOLUTION); |
| hidden_tiling->CreateAllTilesForTesting(); |
| hidden_tiling->SetTilePriorityRectsForTesting( |
| layer_rect, // Visible rect. |
| layer_rect, // Skewport rect. |
| layer_rect, // Soon rect. |
| layer_rect); // Eventually rect. |
| |
| drawing_layer->tilings()->AddTiling(gfx::AxisTransform2d(), raster_source); |
| PictureLayerTiling* drawing_tiling = drawing_layer->tilings()->tiling_at(0); |
| drawing_tiling->set_resolution(TileResolution::HIGH_RESOLUTION); |
| drawing_tiling->CreateAllTilesForTesting(); |
| drawing_tiling->SetTilePriorityRectsForTesting( |
| layer_rect, // Visible rect. |
| layer_rect, // Skewport rect. |
| layer_rect, // Soon rect. |
| layer_rect); // Eventually rect. |
| |
| // Hide the hidden layer and set it to so it still rasters. The drawing |
| // layer should be prioritized over the hidden layer. |
| hidden_layer->set_contributes_to_drawn_render_surface(false); |
| hidden_layer->set_raster_even_if_not_drawn(true); |
| std::unique_ptr<RasterTilePriorityQueue> queue = |
| host_impl_->BuildRasterQueue(TreePriority::SMOOTHNESS_TAKES_PRIORITY, |
| RasterTilePriorityQueue::Type::ALL); |
| EXPECT_EQ(queue->Top().tile()->layer_id(), 3); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, DrawAfterDroppingTileResources) { |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.using_synchronous_renderer_compositor = true; |
| CreateHostImpl(settings, CreateLayerTreeFrameSink()); |
| CreatePendingTree(); |
| |
| gfx::Size bounds(100, 100); |
| scoped_refptr<FakeRasterSource> raster_source( |
| FakeRasterSource::CreateFilled(bounds)); |
| auto* root = SetupRootLayer<FakePictureLayerImpl>(host_impl_->pending_tree(), |
| bounds, raster_source); |
| root->SetDrawsContent(true); |
| host_impl_->ActivateSyncTree(); |
| |
| FakePictureLayerImpl* layer = static_cast<FakePictureLayerImpl*>( |
| host_impl_->active_tree()->FindActiveTreeLayerById(root->id())); |
| |
| DrawFrame(); |
| EXPECT_FALSE(host_impl_->active_tree()->needs_update_draw_properties()); |
| EXPECT_LT(0, layer->raster_page_scale()); |
| EXPECT_GT(layer->tilings()->num_tilings(), 0u); |
| |
| const ManagedMemoryPolicy policy = host_impl_->ActualManagedMemoryPolicy(); |
| const ManagedMemoryPolicy zero_policy(0u); |
| host_impl_->SetMemoryPolicy(zero_policy); |
| EXPECT_EQ(0, layer->raster_page_scale()); |
| EXPECT_EQ(layer->tilings()->num_tilings(), 0u); |
| |
| host_impl_->SetMemoryPolicy(policy); |
| DrawFrame(); |
| EXPECT_LT(0, layer->raster_page_scale()); |
| EXPECT_GT(layer->tilings()->num_tilings(), 0u); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, CommitWithNoPaintWorkletLayerPainter) { |
| ASSERT_FALSE(host_impl_->GetPaintWorkletLayerPainterForTesting()); |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->CreatePendingTree(); |
| |
| // When there is no PaintWorkletLayerPainter registered, commits should finish |
| // immediately and move onto preparing tiles. |
| ASSERT_FALSE(did_prepare_tiles_); |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->CommitComplete(); |
| EXPECT_TRUE(did_prepare_tiles_); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, CommitWithNoPaintWorklets) { |
| host_impl_->SetPaintWorkletLayerPainter( |
| std::make_unique<TestPaintWorkletLayerPainter>()); |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->CreatePendingTree(); |
| |
| // When there are no PaintWorklets in the committed display lists, commits |
| // should finish immediately and move onto preparing tiles. |
| ASSERT_FALSE(did_prepare_tiles_); |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->CommitComplete(); |
| EXPECT_TRUE(did_prepare_tiles_); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, CommitWithDirtyPaintWorklets) { |
| RunPaintWorkletCommitTest(); |
| EXPECT_TRUE(did_prepare_tiles_); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, CommitWithNoDirtyPaintWorklets) { |
| host_impl_->SetPaintWorkletLayerPainter( |
| std::make_unique<TestPaintWorkletLayerPainter>()); |
| |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->CreatePendingTree(); |
| auto* root = SetupRootLayer<PictureLayerImpl>(host_impl_->pending_tree(), |
| gfx::Size(100, 100)); |
| root->SetNeedsPushProperties(); |
| |
| // Add some PaintWorklets. |
| scoped_refptr<RasterSource> raster_source_with_pws = |
| FakeRasterSource::CreateFilledWithPaintWorklet(root->bounds()); |
| root->SetRasterSourceForTesting(raster_source_with_pws); |
| |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| |
| // Pretend that our worklets were already painted. |
| ASSERT_EQ(root->GetPaintWorkletRecordMap().size(), 1u); |
| root->SetPaintWorkletRecord(root->GetPaintWorkletRecordMap().begin()->first, |
| PaintRecord()); |
| |
| // Since there are no dirty PaintWorklets, the commit should immediately |
| // prepare tiles. |
| ASSERT_FALSE(did_prepare_tiles_); |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->CommitComplete(); |
| EXPECT_TRUE(did_prepare_tiles_); |
| } |
| |
| TEST_P(PendingTreeLayerTreeHostImplTest, |
| ActivatedPendingTreeRetainsRasterMetrics) { |
| gfx::Size scrollable_content_bounds(100, 100); |
| gfx::Size container_bounds(50, 50); |
| if (!CommitsToActiveTree()) { |
| CreatePendingTree(); |
| } |
| |
| // Create root and scroll layers so that we can set up a |
| // non-composited scrollable node, eligible for raster scroll. |
| auto* sync_tree_root = SetupRootLayer<LayerImpl>(host_impl_->sync_tree(), |
| scrollable_content_bounds); |
| sync_tree_root->SetNeedsPushProperties(); |
| auto* scrolling_layer = |
| AddScrollableLayer(sync_tree_root, container_bounds, gfx::Size()); |
| scrolling_layer->SetNeedsPushProperties(); |
| CreateScrollNodeForNonCompositedScroller( |
| host_impl_->sync_tree()->property_trees(), sync_tree_root->id(), |
| scrolling_layer->element_id(), scrollable_content_bounds, |
| container_bounds); |
| |
| // Draw at least one frame before ScrollBegin. |
| host_impl_->sync_tree()->set_needs_update_draw_properties(); |
| UpdateDrawProperties(host_impl_->sync_tree()); |
| host_impl_->ActivateSyncTree(); |
| DrawFrame(); |
| |
| // Scrolling on this non-composited tree should be marked as raster-inducing. |
| ScrollStateData scroll_state_data; |
| scroll_state_data.set_current_native_scrolling_element( |
| scrolling_layer->element_id()); |
| scroll_state_data.is_beginning = true; |
| std::unique_ptr<ScrollState> scroll_state(new ScrollState(scroll_state_data)); |
| InputHandler::ScrollStatus status = GetInputHandler().ScrollBegin( |
| scroll_state.get(), ui::ScrollInputType::kTouchscreen); |
| EXPECT_EQ(true, status.raster_inducing); |
| |
| base::TimeTicks scroll_begin_generated_timestamp = |
| base::TimeTicks::Now() - base::Milliseconds(1); |
| base::TimeTicks scroll_begin_arrival_timestamp = base::TimeTicks::Now(); |
| GetInputHandler().RecordScrollBegin( |
| ui::ScrollInputType::kTouchscreen, |
| ScrollBeginThreadState::kRasterInducingScroll); |
| |
| { |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), gfx::Vector2d(0, 10), ui::ScrollInputType::kTouchscreen)); |
| |
| base::TimeTicks now = base::TimeTicks::Now(); |
| std::unique_ptr<EventMetrics> metrics = ScrollUpdateEventMetrics::Create( |
| ui::EventType::kGestureScrollUpdate, ui::ScrollInputType::kTouchscreen, |
| /*is_inertial=*/false, |
| ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, |
| /*delta=*/10.0f, /*timestamp=*/now, |
| /*arrived_in_browser_main_timestamp=*/now + base::Milliseconds(1), |
| /*blocking_touch_dispatched_to_renderer=*/base::TimeTicks(), |
| /*trace_id=*/base::IdType64<class ui::LatencyInfo>(123), |
| scroll_begin_generated_timestamp, scroll_begin_arrival_timestamp); |
| |
| // Associate metrics with the scoped metrics monitor by registering a done |
| // callback. |
| auto done_callback = base::BindOnce( |
| [](std::unique_ptr<EventMetrics> metrics, bool handled) { |
| metrics->SetDispatchStageTimestamp( |
| EventMetrics::DispatchStage::kRendererCompositorStarted); |
| return handled ? std::move(metrics) : nullptr; |
| }, |
| std::move(metrics)); |
| auto scoped_event_monitor = |
| host_impl_->GetScopedEventMetricsMonitor(std::move(done_callback)); |
| |
| host_impl_->SetNeedsOneBeginImplFrame(); |
| TestFrameData frame; |
| auto args = viz::CreateBeginFrameArgsForTesting( |
| BEGINFRAME_FROM_HERE, viz::BeginFrameArgs::kManualSourceId, 1, |
| base::TimeTicks() + base::Milliseconds(1)); |
| host_impl_->WillBeginImplFrame(args); |
| EXPECT_EQ(DrawResult::kSuccess, host_impl_->PrepareToDraw(&frame)); |
| } |
| // This call creates a new pending tree. |
| // TODO(496580137): Move this to ClientLayerTreeHostImpl specific tests. |
| client_host_impl(host_impl_.get())->InvalidateContentOnImplSide(); |
| if (!CommitsToActiveTree()) { |
| // If a pending tree exists, we expect to see that there are metrics |
| // associated with the raster frame associated with it. |
| EXPECT_EQ((size_t)1, |
| host_impl_->pending_tree() |
| ->events_metrics_from_raster_thread_count_for_testing()); |
| // Activating the tree should show that raster metrics are now |
| // associated with it. |
| host_impl_->ActivateSyncTree(); |
| EXPECT_EQ((size_t)1, |
| host_impl_->active_tree() |
| ->events_metrics_from_raster_thread_count_for_testing()); |
| } |
| } |
| |
| class LayerTreeHostImplTestScrollbarOpacity |
| : public PendingTreeLayerTreeHostImplTest { |
| protected: |
| void RunTest(LayerTreeSettings::ScrollbarAnimator animator) { |
| LayerTreeSettings settings = DefaultSettings(); |
| settings.scrollbar_animator = animator; |
| settings.scrollbar_fade_delay = base::Milliseconds(20); |
| settings.scrollbar_fade_duration = base::Milliseconds(20); |
| gfx::Size viewport_size(50, 50); |
| gfx::Size content_size(100, 100); |
| |
| // If no animator is set, scrollbar won't show and no animation is expected. |
| bool expecting_animations = animator != LayerTreeSettings::NO_ANIMATOR; |
| |
| CreateHostImpl(settings, CreateLayerTreeFrameSink()); |
| CreatePendingTree(); |
| SetupViewportLayers(host_impl_->pending_tree(), viewport_size, content_size, |
| content_size); |
| |
| LayerImpl* scroll = |
| host_impl_->pending_tree()->OuterViewportScrollLayerForTesting(); |
| auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>( |
| host_impl_->pending_tree(), ScrollbarOrientation::kVertical, 10, 0, |
| false); |
| SetupScrollbarLayer(scroll, scrollbar); |
| scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(90, 0)); |
| |
| host_impl_->pending_tree()->PushPageScaleFromMainThread(1, 1, 1); |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| host_impl_->ActivateSyncTree(); |
| |
| LayerImpl* active_scrollbar_layer = |
| host_impl_->active_tree()->LayerById(scrollbar->id()); |
| |
| EffectNode* active_tree_node = GetEffectNode(active_scrollbar_layer); |
| EXPECT_FLOAT_EQ(active_scrollbar_layer->Opacity(), |
| active_tree_node->opacity); |
| |
| if (expecting_animations) { |
| host_impl_->ScrollbarAnimationControllerForElementId(scroll->element_id()) |
| ->DidMouseMove(gfx::PointF(0, 90)); |
| } else { |
| EXPECT_EQ(nullptr, host_impl_->ScrollbarAnimationControllerForElementId( |
| scroll->element_id())); |
| } |
| GetInputHandler().ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 5), |
| ui::ScrollInputType::kWheel) |
| .get(), |
| ui::ScrollInputType::kWheel); |
| GetInputHandler().ScrollUpdate(UpdateState( |
| gfx::Point(), gfx::Vector2dF(0, 5), ui::ScrollInputType::kWheel)); |
| GetInputHandler().ScrollEnd(/*should_snap=*/false, std::nullopt); |
| |
| CreatePendingTree(); |
| // To test the case where the effect tree index of scrollbar layer changes, |
| // we create an effect node with a render surface above the scrollbar's |
| // effect node. |
| auto* pending_root = host_impl_->pending_tree()->root_layer(); |
| auto& new_effect_node = CreateEffectNode( |
| GetPropertyTrees(pending_root), pending_root->effect_tree_index(), |
| pending_root->transform_tree_index(), pending_root->clip_tree_index()); |
| new_effect_node.render_surface_reason = RenderSurfaceReason::kTest; |
| new_effect_node.element_id = ElementId(123); |
| LayerImpl* pending_scrollbar_layer = |
| host_impl_->pending_tree()->LayerById(scrollbar->id()); |
| GetEffectNode(pending_scrollbar_layer)->parent_id = new_effect_node.id; |
| pending_scrollbar_layer->SetNeedsPushProperties(); |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| |
| EffectNode* pending_tree_node = GetEffectNode(pending_scrollbar_layer); |
| if (expecting_animations) { |
| EXPECT_FLOAT_EQ(1, active_tree_node->opacity); |
| EXPECT_FLOAT_EQ(1, active_scrollbar_layer->Opacity()); |
| } else { |
| EXPECT_FLOAT_EQ(0, active_tree_node->opacity); |
| EXPECT_FLOAT_EQ(0, active_scrollbar_layer->Opacity()); |
| } |
| EXPECT_FLOAT_EQ(0, pending_tree_node->opacity); |
| |
| host_impl_->ActivateSyncTree(); |
| active_tree_node = GetEffectNode(active_scrollbar_layer); |
| if (expecting_animations) { |
| EXPECT_FLOAT_EQ(1, active_tree_node->opacity); |
| EXPECT_FLOAT_EQ(1, active_scrollbar_layer->Opacity()); |
| } else { |
| EXPECT_FLOAT_EQ(0, active_tree_node->opacity); |
| EXPECT_FLOAT_EQ(0, active_scrollbar_layer->Opacity()); |
| } |
| } |
| }; |
| |
| INSTANTIATE_COMMIT_TO_PENDING_TREE_TEST_P( |
| LayerTreeHostImplTestScrollbarOpacity); |
| |
| TEST_P(LayerTreeHostImplTestScrollbarOpacity, Android) { |
| RunTest(LayerTreeSettings::ANDROID_OVERLAY); |
| } |
| |
| TEST_P(LayerTreeHostImplTestScrollbarOpacity, AuraOverlay) { |
| RunTest(LayerTreeSettings::AURA_OVERLAY); |
| } |
| |
| TEST_P(LayerTreeHostImplTestScrollbarOpacity, NoAnimator) { |
| RunTest(LayerTreeSettings::NO_ANIMATOR); |
| } |
| |
| class ResourcelessSoftwareLayerTreeHostImplTest |
| : public PendingTreeLayerTreeHostImplTest { |
| protected: |
| std::unique_ptr<LayerTreeFrameSink> CreateLayerTreeFrameSink() override { |
| return FakeLayerTreeFrameSink::Create3d(); |
| } |
| }; |
| |
| INSTANTIATE_COMMIT_TO_PENDING_TREE_TEST_P( |
| ResourcelessSoftwareLayerTreeHostImplTest); |
| |
| TEST_P(ResourcelessSoftwareLayerTreeHostImplTest, |
| ResourcelessSoftwareSetNeedsRedraw) { |
| const gfx::Size viewport_size(100, 100); |
| SetupDefaultRootLayer(viewport_size); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| |
| const gfx::Transform draw_transform; |
| const gfx::Rect draw_viewport(viewport_size); |
| bool resourceless_software_draw = false; |
| |
| // Clear any damage. |
| host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw, |
| false); |
| last_on_draw_frame_.reset(); |
| |
| // Always swap even if same draw params. |
| resourceless_software_draw = true; |
| host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw, |
| false); |
| EXPECT_FALSE(last_on_draw_frame_->has_no_damage); |
| last_on_draw_frame_.reset(); |
| |
| // Next hardware draw has damage. |
| resourceless_software_draw = false; |
| host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw, |
| false); |
| EXPECT_FALSE(last_on_draw_frame_->has_no_damage); |
| } |
| |
| TEST_P(ResourcelessSoftwareLayerTreeHostImplTest, |
| ResourcelessSoftwareDrawSkipsUpdateTiles) { |
| const gfx::Size viewport_size(100, 100); |
| |
| CreatePendingTree(); |
| scoped_refptr<FakeRasterSource> raster_source( |
| FakeRasterSource::CreateFilled(viewport_size)); |
| auto* root = SetupRootLayer<FakePictureLayerImpl>( |
| host_impl_->pending_tree(), viewport_size, raster_source); |
| root->SetBounds(viewport_size); |
| root->SetDrawsContent(true); |
| |
| UpdateDrawProperties(host_impl_->pending_tree()); |
| host_impl_->ActivateSyncTree(); |
| |
| const gfx::Transform draw_transform; |
| const gfx::Rect draw_viewport(viewport_size); |
| bool resourceless_software_draw = false; |
| |
| // Regular draw causes UpdateTiles. |
| did_request_prepare_tiles_ = false; |
| host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw, |
| false); |
| EXPECT_TRUE(did_request_prepare_tiles_); |
| host_impl_->PrepareTiles(); |
| |
| // Resourceless draw skips UpdateTiles. |
| const gfx::Rect new_draw_viewport(50, 50); |
| resourceless_software_draw = true; |
| did_request_prepare_tiles_ = false; |
| host_impl_->OnDraw(draw_transform, new_draw_viewport, |
| resourceless_software_draw, false); |
| EXPECT_FALSE(did_request_prepare_tiles_); |
| } |
| |
| class ForceActivateAfterPaintWorkletPaintLayerTreeHostImplTest |
| : public PendingTreeLayerTreeHostImplTest { |
| public: |
| void NotifyPaintWorkletStateChange( |
| Scheduler::PaintWorkletState state) override { |
| if (state == Scheduler::PaintWorkletState::IDLE) { |
| // Pretend a force activation happened. |
| host_impl_->ActivateSyncTree(); |
| ASSERT_FALSE(host_impl_->pending_tree()); |
| } |
| } |
| }; |
| |
| INSTANTIATE_COMMIT_TO_PENDING_TREE_TEST_P( |
| ForceActivateAfterPaintWorkletPaintLayerTreeHostImplTest); |
| |
| TEST_P(ForceActivateAfterPaintWorkletPaintLayerTreeHostImplTest, |
| ForceActivationAfterPaintWorkletsFinishPainting) { |
| RunPaintWorkletCommitTest(); |
| EXPECT_FALSE(did_prepare_tiles_); |
| } |
| |
| class BlendStateCheckLayer : public LayerImpl { |
| public: |
| static std::unique_ptr<BlendStateCheckLayer> Create( |
| LayerTreeImpl* tree_impl, |
| int id, |
| viz::ClientResourceProvider* resource_provider) { |
| return base::WrapUnique( |
| new BlendStateCheckLayer(tree_impl, id, resource_provider)); |
| } |
| |
| BlendStateCheckLayer(LayerTreeImpl* tree_impl, |
| int id, |
| viz::ClientResourceProvider* resource_provider) |
| : LayerImpl(tree_impl, id), |
| resource_provider_(resource_provider), |
| comparison_layer_(nullptr), |
| quad_rect_(5, 5, 5, 5), |
| quad_visible_rect_(5, 5, 5, 5), |
| shared_image_interface_( |
| base::MakeRefCounted<gpu::TestSharedImageInterface>()) { |
| auto shared_image = |
| shared_image_interface_->CreateSharedImageForSoftwareCompositor( |
| {viz::SinglePlaneFormat::kBGRA_8888, gfx::Size(1, 1), |
| gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, |
| "BlendStateCheckLayerTest"}); |
| auto sync_token = shared_image_interface_->GenUnverifiedSyncToken(); |
| viz::TransferableResource resource = viz::TransferableResource::Make( |
| shared_image, |
| viz::TransferableResource::ResourceSource::kTileRasterTask, sync_token); |
| |
| resource_id_ = resource_provider_->ImportResource(std::move(resource), |
| base::DoNothing()); |
| SetBounds(gfx::Size(10, 10)); |
| SetDrawsContent(true); |
| } |
| |
| void ReleaseResources() override { |
| resource_provider_->RemoveImportedResource(resource_id_); |
| } |
| |
| void AppendQuads(const AppendQuadsContext& context, |
| viz::CompositorRenderPass* render_pass, |
| 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_visible_rect_; |
| bool needs_blending = !opaque_rect.Contains(visible_quad_rect); |
| |
| viz::SharedQuadState* shared_quad_state = |
| render_pass->CreateAndAppendSharedQuadState(); |
| PopulateSharedQuadState(shared_quad_state, contents_opaque()); |
| |
| auto* test_blending_draw_quad = |
| render_pass->CreateAndAppendDrawQuad<viz::TileDrawQuad>(); |
| test_blending_draw_quad->SetNew( |
| shared_quad_state, quad_rect_, visible_quad_rect, needs_blending, |
| resource_id_, gfx::RectF(0, 0, 1, 1), false, false); |
| |
| EXPECT_EQ(blend_, test_blending_draw_quad->ShouldDrawWithBlending()); |
| EXPECT_EQ(has_render_surface_, |
| GetRenderSurface(this) != GetRenderSurface(comparison_layer_)); |
| } |
| |
| void SetExpectation(bool blend, |
| bool has_render_surface, |
| LayerImpl* comparison_layer) { |
| blend_ = blend; |
| has_render_surface_ = has_render_surface; |
| comparison_layer_ = comparison_layer; |
| 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: |
| raw_ptr<viz::ClientResourceProvider> resource_provider_; |
| bool blend_ = false; |
| bool has_render_surface_ = false; |
| raw_ptr<LayerImpl> comparison_layer_; |
| bool quads_appended_ = false; |
| gfx::Rect quad_rect_; |
| gfx::Rect opaque_content_rect_; |
| gfx::Rect quad_visible_rect_; |
| viz::ResourceId resource_id_; |
| scoped_refptr<gpu::TestSharedImageInterface> shared_image_interface_; |
| }; |
| |
| class LayerTreeHostImplViewportCoveredTest |
| : public ClientModeLayerTreeHostImplTest { |
| protected: |
| LayerTreeHostImplViewportCoveredTest() : child_(nullptr) {} |
| |
| std::unique_ptr<LayerTreeFrameSink> CreateFakeLayerTreeFrameSink( |
| bool software) { |
| if (software) { |
| return FakeLayerTreeFrameSink::CreateSoftware(); |
| } |
| return FakeLayerTreeFrameSink::Create3d(); |
| } |
| |
| void SetupActiveTreeLayers() { |
| host_impl_->active_tree()->set_background_color(SkColors::kGray); |
| LayerImpl* root = SetupDefaultRootLayer(viewport_size_); |
| child_ = AddLayer<BlendStateCheckLayer>(host_impl_->active_tree(), |
| host_impl_->resource_provider()); |
| child_->SetExpectation(false, false, root); |
| child_->SetContentsOpaque(true); |
| CopyProperties(root, child_); |
| UpdateDrawProperties(host_impl_->active_tree()); |
| } |
| |
| void SetLayerGeometry(const gfx::Rect& layer_rect) { |
| child_->SetBounds(layer_rect.size()); |
| child_->SetQuadRect(gfx::Rect(layer_rect.size())); |
| child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size())); |
| child_->SetOffsetToTransformParent( |
| gfx::Vector2dF(layer_rect.OffsetFromOrigin())); |
| } |
| |
| // Expect no gutter rects. |
| void TestLayerCoversFullViewport() { |
| SetLayerGeometry(gfx::Rect(viewport_size_)); |
| |
| TestFrameData frame; |
| EXPECT_EQ(DrawResult::kSuccess, 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 SetUpEmptylayer() { SetLayerGeometry(gfx::Rect()); } |
| |
| void VerifyEmptyLayerRenderPasses( |
| const viz::CompositorRenderPassList& render_passes) { |
| ASSERT_EQ(1u, render_passes.size()); |
| |
| EXPECT_EQ(1u, CountGutterQuads(render_passes[0]->quad_list)); |
| EXPECT_EQ(1u, render_passes[0]->quad_list.size()); |
| ValidateTextureDrawQuads(render_passes[0]->quad_list); |
| |
| VerifyQuadsExactlyCoverViewport(render_passes[0]->quad_list); |
| } |
| |
| void TestEmptyLayer() { |
| SetUpEmptylayer(); |
| DrawFrame(); |
| } |
| |
| void TestEmptyLayerWithOnDraw() { |
| SetUpEmptylayer(); |
| gfx::Transform identity; |
| gfx::Rect viewport(viewport_size_); |
| bool resourceless_software_draw = true; |
| host_impl_->OnDraw(identity, viewport, resourceless_software_draw, false); |
| VerifyEmptyLayerRenderPasses(last_on_draw_render_passes_); |
| } |
| |
| // Expect four surrounding gutter rects. |
| void SetUpLayerInMiddleOfViewport() { |
| SetLayerGeometry(gfx::Rect(500, 500, 200, 200)); |
| } |
| |
| void VerifyLayerInMiddleOfViewport( |
| const viz::CompositorRenderPassList& render_passes) { |
| ASSERT_EQ(1u, render_passes.size()); |
| |
| EXPECT_EQ(4u, CountGutterQuads(render_passes[0]->quad_list)); |
| EXPECT_EQ(5u, render_passes[0]->quad_list.size()); |
| ValidateTextureDrawQuads(render_passes[0]->quad_list); |
| |
| VerifyQuadsExactlyCoverViewport(render_passes[0]->quad_list); |
| } |
| |
| void TestLayerInMiddleOfViewport() { |
| SetUpLayerInMiddleOfViewport(); |
| DrawFrame(); |
| } |
| |
| void TestLayerInMiddleOfViewportWithOnDraw() { |
| SetUpLayerInMiddleOfViewport(); |
| gfx::Transform identity; |
| gfx::Rect viewport(viewport_size_); |
| bool resourceless_software_draw = true; |
| host_impl_->OnDraw(identity, viewport, resourceless_software_draw, false); |
| VerifyLayerInMiddleOfViewport(last_on_draw_render_passes_); |
| } |
| |
| // Expect no gutter rects. |
| void SetUpLayerIsLargerThanViewport() { |
| SetLayerGeometry( |
| gfx::Rect(viewport_size_.width() + 10, viewport_size_.height() + 10)); |
| } |
| |
| void VerifyLayerIsLargerThanViewport( |
| const viz::CompositorRenderPassList& render_passes) { |
| ASSERT_EQ(1u, render_passes.size()); |
| |
| EXPECT_EQ(0u, CountGutterQuads(render_passes[0]->quad_list)); |
| EXPECT_EQ(1u, render_passes[0]->quad_list.size()); |
| ValidateTextureDrawQuads(render_passes[0]->quad_list); |
| } |
| |
| void TestLayerIsLargerThanViewport() { |
| SetUpLayerIsLargerThanViewport(); |
| DrawFrame(); |
| } |
| |
| void TestLayerIsLargerThanViewportWithOnDraw() { |
| SetUpLayerIsLargerThanViewport(); |
| gfx::Transform identity; |
| gfx::Rect viewport(viewport_size_); |
| bool resourceless_software_draw = true; |
| host_impl_->OnDraw(identity, viewport, resourceless_software_draw, false); |
| VerifyLayerIsLargerThanViewport(last_on_draw_render_passes_); |
| } |
| |
| void DidActivateSyncTree() override { |
| LayerTreeHostImplTest::DidActivateSyncTree(); |
| did_activate_pending_tree_ = true; |
| } |
| |
| void set_gutter_quad_material(viz::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 viz::QuadList& quad_list) { |
| size_t num_gutter_quads = 0; |
| for (auto* quad : quad_list) { |
| num_gutter_quads += (quad->material == gutter_quad_material_) ? 1 : 0; |
| } |
| return num_gutter_quads; |
| } |
| |
| void VerifyQuadsExactlyCoverViewport(const viz::QuadList& quad_list) { |
| VerifyQuadsExactlyCoverRect(quad_list, |
| gfx::Rect(DipSizeToPixelSize(viewport_size_))); |
| } |
| |
| // Make sure that the texture coordinates match their expectations. |
| void ValidateTextureDrawQuads(const viz::QuadList& quad_list) { |
| for (auto* quad : quad_list) { |
| if (quad->material != viz::DrawQuad::Material::kTextureContent) { |
| continue; |
| } |
| const viz::TextureDrawQuad* texture_quad = |
| viz::TextureDrawQuad::MaterialCast(quad); |
| gfx::SizeF gutter_texture_size_pixels = |
| gfx::ScaleSize(gfx::SizeF(gutter_texture_size_), |
| host_impl_->active_tree()->device_scale_factor()); |
| const gfx::RectF texture_quad_tex_coords( |
| texture_quad->GetNormalizedTexCoords( |
| gfx::ToRoundedSize(gutter_texture_size_pixels))); |
| EXPECT_EQ(texture_quad_tex_coords.x(), |
| texture_quad->rect.x() / gutter_texture_size_pixels.width()); |
| EXPECT_EQ(texture_quad_tex_coords.y(), |
| texture_quad->rect.y() / gutter_texture_size_pixels.height()); |
| EXPECT_EQ( |
| texture_quad_tex_coords.right(), |
| texture_quad->rect.right() / gutter_texture_size_pixels.width()); |
| EXPECT_EQ( |
| texture_quad_tex_coords.bottom(), |
| texture_quad->rect.bottom() / gutter_texture_size_pixels.height()); |
| } |
| } |
| |
| viz::DrawQuad::Material gutter_quad_material_ = |
| viz::DrawQuad::Material::kSolidColor; |
| gfx::Size gutter_texture_size_; |
| gfx::Size viewport_size_; |
| raw_ptr<BlendStateCheckLayer> child_; |
| bool did_activate_pending_tree_ = false; |
| }; |
| |
| // These tests are only relevant for CommitToPendingTree since they are checking |
| // conditions that would need to be queried from the Viz process. |
| INSTANTIATE_COMMIT_TO_TREE_BASE_TEST_P(LayerTreeHostImplViewportCoveredTest, |
| CommitToPendingTree); |
| |
| TEST_P(LayerTreeHostImplViewportCoveredTest, ViewportCovered) { |
| viewport_size_ = gfx::Size(1000, 1000); |
| |
| bool software = false; |
| CreateHostImpl(DefaultSettings(), CreateFakeLayerTreeFrameSink(software)); |
| SetupActiveTreeLayers(); |
| EXPECT_SCOPED(TestLayerCoversFullViewport()); |
| EXPECT_SCOPED(TestEmptyLayer()); |
| EXPECT_SCOPED(TestLayerInMiddleOfViewport()); |
| EXPECT_SCOPED(TestLayerIsLargerThanViewport()); |
| } |
| |
| TEST_P(LayerTreeHostImplViewportCoveredTest, ViewportCoveredScaled) { |
| viewport_size_ = gfx::Size(1000, 1000); |
| |
| bool software = false; |
| CreateHostImpl(DefaultSettings(), CreateFakeLayerTreeFrameSink(software)); |
| |
| host_impl_->active_tree()->SetDeviceScaleFactor(2); |
| SetupActiveTreeLayers(); |
| EXPECT_SCOPED(TestLayerCoversFullViewport()); |
| EXPECT_SCOPED(TestEmptyLayer()); |
| EXPECT_SCOPED(TestLayerInMiddleOfViewport()); |
| EXPECT_SCOPED(TestLayerIsLargerThanViewport()); |
| } |
| |
| TEST_P(LayerTreeHostImplViewportCoveredTest, ActiveTreeGrowViewportInvalid) { |
| viewport_size_ = gfx::Size(1000, 1000); |
| |
| bool software = true; |
| CreateHostImpl(DefaultSettings(), CreateFakeLayerTreeFrameSink(software)); |
| |
| // Pending tree to force active_tree size invalid. Not used otherwise. |
| CreatePendingTree(); |
| |
| SetupActiveTreeLayers(); |
| EXPECT_SCOPED(TestEmptyLayerWithOnDraw()); |
| EXPECT_SCOPED(TestLayerInMiddleOfViewportWithOnDraw()); |
| EXPECT_SCOPED(TestLayerIsLargerThanViewportWithOnDraw()); |
| } |
| |
| TEST_P(LayerTreeHostImplViewportCoveredTest, ActiveTreeShrinkViewportInvalid) { |
| viewport_size_ = gfx::Size(1000, 1000); |
| |
| bool software = true; |
| CreateHostImpl(DefaultSettings(), CreateFakeLayerTreeFrameSink(software)); |
| |
| // Set larger viewport and activate it to active tree. |
| CreatePendingTree(); |
| gfx::Size larger_viewport(viewport_size_.width() + 100, |
| viewport_size_.height() + 100); |
| host_impl_->active_tree()->SetDeviceViewportRect( |
| gfx::Rect(DipSizeToPixelSize(larger_viewport))); |
| host_impl_->ActivateSyncTree(); |
| EXPECT_TRUE(did_activate_pending_tree_); |
| |
| // Shrink pending tree viewport without activating. |
| CreatePendingTree(); |
| host_impl_->active_tree()->SetDeviceViewportRect( |
| gfx::Rect(DipSizeToPixelSize(viewport_size_))); |
| |
| SetupActiveTreeLayers(); |
| EXPECT_SCOPED(TestEmptyLayerWithOnDraw()); |
| EXPECT_SCOPED(TestLayerInMiddleOfViewportWithOnDraw()); |
| EXPECT_SCOPED(TestLayerIsLargerThanViewportWithOnDraw()); |
| } |
| |
| // Test that TotalFrameCounter resets itself under certain conditions |
| TEST_P(ClientModeLayerTreeHostImplTest, FrameCounterReset) { |
| FrameSorter* frame_sorter = host_impl_->frame_sorter_for_testing(); |
| EXPECT_EQ(frame_sorter->total_frames(), 0u); |
| FrameInfo frame_info; |
| frame_info.final_state = FrameInfo::FrameFinalState::kPresentedAll; |
| frame_sorter->AddFrameInfoToBuffer(frame_info); |
| EXPECT_EQ(frame_sorter->total_frames(), 1u); |
| |
| auto interval = base::Milliseconds(16); |
| base::TimeTicks now = base::TimeTicks::Now(); |
| auto deadline = now + interval; |
| viz::BeginFrameArgs args = viz::BeginFrameArgs::Create( |
| BEGINFRAME_FROM_HERE, 1u /*source_id*/, 2u /*sequence_number*/, now, |
| deadline, interval, viz::BeginFrameArgs::NORMAL); |
| |
| frame_sorter->AddNewFrame(args); |
| // Delegates to DFC::AddSortedFrame, which calls DFC::OnEndFrame. |
| frame_sorter->AddFrameResult( |
| args, CreateFakeFrameInfo(FrameInfo::FrameFinalState::kDropped)); |
| // FCP not received, so the total_smoothness_dropped_ won't increase. |
| EXPECT_EQ(frame_sorter->total_dropped(), 0u); |
| |
| BeginMainFrameMetrics begin_frame_metrics; |
| begin_frame_metrics.should_measure_smoothness = true; |
| { |
| static_cast<ClientLayerTreeHostImpl*>(host_impl_.get()) |
| ->ReadyToCommit(/*scroll_and_viewport_changes_synced=*/true, |
| &begin_frame_metrics, /*commit_timeout=*/false); |
| } |
| frame_sorter->AddNewFrame(args); |
| // Delegates to DFC::AddSortedFrame, which calls DFC::OnEndFrame. |
| frame_sorter->AddFrameResult( |
| args, CreateFakeFrameInfo(FrameInfo::FrameFinalState::kDropped)); |
| frame_sorter->AddFrameInfoToBuffer(frame_info); |
| { |
| static_cast<ClientLayerTreeHostImpl*>(host_impl_.get()) |
| ->SetActiveURL(GURL(), 1u); |
| } |
| EXPECT_EQ(frame_sorter->total_frames(), 0u); |
| EXPECT_EQ(frame_sorter->total_dropped(), 0u); |
| } |
| |
| // Test that TotalFrameCounter does not reset itself under certain conditions |
| TEST_P(ClientModeLayerTreeHostImplTest, FrameCounterNotReset) { |
| FrameSorter* frame_sorter = host_impl_->frame_sorter_for_testing(); |
| EXPECT_EQ(frame_sorter->total_frames(), 0u); |
| |
| auto interval = base::Milliseconds(16); |
| base::TimeTicks now = base::TimeTicks::Now(); |
| auto deadline = now + interval; |
| viz::BeginFrameArgs arg1 = viz::BeginFrameArgs::Create( |
| BEGINFRAME_FROM_HERE, 1u /*source_id*/, 1u /*sequence_number*/, now, |
| deadline, interval, viz::BeginFrameArgs::NORMAL); |
| BeginMainFrameMetrics begin_frame_metrics; |
| begin_frame_metrics.should_measure_smoothness = true; |
| { |
| static_cast<ClientLayerTreeHostImpl*>(host_impl_.get()) |
| ->ReadyToCommit(/*scroll_and_viewport_changes_synced=*/true, |
| &begin_frame_metrics, /*commit_timeout=*/false); |
| } |
| EXPECT_EQ(frame_sorter->total_frames(), 0u); |
| FrameInfo frame_info; |
| frame_info.final_state = FrameInfo::FrameFinalState::kPresentedAll; |
| frame_sorter->AddFrameInfoToBuffer(frame_info); |
| EXPECT_EQ(frame_sorter->total_frames(), 1u); |
| |
| now = deadline; |
| deadline = now + interval; |
| viz::BeginFrameArgs arg2 = viz::BeginFrameArgs::Create( |
| BEGINFRAME_FROM_HERE, 1u /*source_id*/, 2u /*sequence_number*/, now, |
| deadline, interval, viz::BeginFrameArgs::NORMAL); |
| // Consecutive BeginFrameMetrics with the same |should_measure_smoothness| |
| // flag should not reset the counter. |
| { |
| static_cast<ClientLayerTreeHostImpl*>(host_impl_.get()) |
| ->ReadyToCommit(/*scroll_and_viewport_changes_synced=*/true, |
| &begin_frame_metrics, /*commit_timeout=*/false); |
| } |
| EXPECT_EQ(frame_sorter->total_frames(), 1u); |
| } |
| |
| } // namespace |
| } // namespace cc |