blob: 5b6f6402f8e28135ce08d9dc894a680ff15ece72 [file] [log] [blame]
// Copyright 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/trees/layer_tree_host_impl.h"
#include <stddef.h>
#include <algorithm>
#include <cmath>
#include <utility>
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "cc/animation/animation_host.h"
#include "cc/animation/animation_id_provider.h"
#include "cc/animation/transform_operations.h"
#include "cc/base/histograms.h"
#include "cc/input/browser_controls_offset_manager.h"
#include "cc/input/main_thread_scrolling_reason.h"
#include "cc/input/page_scale_animation.h"
#include "cc/layers/append_quads_data.h"
#include "cc/layers/heads_up_display_layer_impl.h"
#include "cc/layers/layer_impl.h"
#include "cc/layers/painted_scrollbar_layer_impl.h"
#include "cc/layers/render_surface_impl.h"
#include "cc/layers/solid_color_layer_impl.h"
#include "cc/layers/solid_color_scrollbar_layer_impl.h"
#include "cc/layers/surface_layer_impl.h"
#include "cc/layers/texture_layer_impl.h"
#include "cc/layers/video_layer_impl.h"
#include "cc/layers/viewport.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/fake_layer_tree_frame_sink.h"
#include "cc/test/fake_layer_tree_host_impl.h"
#include "cc/test/fake_mask_layer_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/geometry_test_utils.h"
#include "cc/test/layer_test_common.h"
#include "cc/test/layer_tree_test.h"
#include "cc/test/skia_common.h"
#include "cc/test/test_task_graph_runner.h"
#include "cc/trees/clip_node.h"
#include "cc/trees/draw_property_utils.h"
#include "cc/trees/effect_node.h"
#include "cc/trees/latency_info_swap_promise.h"
#include "cc/trees/layer_tree_host_common.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/mutator_host.h"
#include "cc/trees/render_frame_metadata_observer.h"
#include "cc/trees/scroll_node.h"
#include "cc/trees/single_thread_proxy.h"
#include "cc/trees/transform_node.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/quads/compositor_frame_metadata.h"
#include "components/viz/common/quads/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/parent_local_surface_id_allocator.h"
#include "components/viz/service/display/gl_renderer.h"
#include "components/viz/service/display/skia_output_surface.h"
#include "components/viz/test/begin_frame_args_test.h"
#include "components/viz/test/fake_output_surface.h"
#include "components/viz/test/test_layer_tree_frame_sink.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "media/base/media.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkMallocPixelRef.h"
#include "ui/gfx/geometry/angle_conversions.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/vector2d_conversions.h"
#define EXPECT_SCOPED(statements) \
{ \
SCOPED_TRACE(""); \
statements; \
}
using ::testing::Mock;
using ::testing::Return;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::_;
using media::VideoFrame;
namespace cc {
namespace {
viz::SurfaceId MakeSurfaceId(const viz::FrameSinkId& frame_sink_id,
uint32_t parent_id) {
return viz::SurfaceId(
frame_sink_id,
viz::LocalSurfaceId(parent_id,
base::UnguessableToken::Deserialize(0, 1u)));
}
struct TestFrameData : public LayerTreeHostImpl::FrameData {
TestFrameData() {
// Set ack to something valid, so DCHECKs don't complain.
begin_frame_ack = viz::BeginFrameAck::CreateManualAckWithDamage();
}
};
class LayerTreeHostImplTest : public testing::Test,
public LayerTreeHostImplClient {
public:
LayerTreeHostImplTest()
: task_runner_provider_(base::ThreadTaskRunnerHandle::Get()),
always_main_thread_blocked_(&task_runner_provider_),
on_can_draw_state_changed_called_(false),
did_notify_ready_to_activate_(false),
did_request_commit_(false),
did_request_redraw_(false),
did_request_next_frame_(false),
did_request_prepare_tiles_(false),
did_complete_page_scale_animation_(false),
reduce_memory_result_(true),
did_request_impl_side_invalidation_(false) {
media::InitializeMediaLibrary();
}
LayerTreeSettings DefaultSettings() {
LayerTreeSettings settings;
settings.enable_surface_synchronization = true;
settings.minimum_occlusion_tracking_size = gfx::Size();
return settings;
}
void SetUp() override {
CreateHostImpl(DefaultSettings(), CreateLayerTreeFrameSink());
}
void CreatePendingTree() {
host_impl_->CreatePendingTree();
LayerTreeImpl* pending_tree = host_impl_->pending_tree();
pending_tree->SetDeviceViewportSize(
host_impl_->active_tree()->GetDeviceViewport().size());
pending_tree->SetDeviceScaleFactor(
host_impl_->active_tree()->device_scale_factor());
}
void TearDown() override {
if (host_impl_)
host_impl_->ReleaseLayerTreeFrameSink();
}
void DidLoseLayerTreeFrameSinkOnImplThread() override {}
void SetBeginFrameSource(viz::BeginFrameSource* source) override {}
void DidReceiveCompositorFrameAckOnImplThread() override {}
void OnCanDrawStateChanged(bool can_draw) override {
on_can_draw_state_changed_called_ = true;
}
void NotifyReadyToActivate() override {
did_notify_ready_to_activate_ = true;
host_impl_->ActivateSyncTree();
}
void NotifyReadyToDraw() override {}
void SetNeedsRedrawOnImplThread() override { did_request_redraw_ = true; }
void SetNeedsOneBeginImplFrameOnImplThread() override {
did_request_next_frame_ = true;
}
void SetNeedsPrepareTilesOnImplThread() override {
did_request_prepare_tiles_ = true;
}
void SetNeedsCommitOnImplThread() override { did_request_commit_ = true; }
void SetVideoNeedsBeginFrames(bool needs_begin_frames) override {}
void PostAnimationEventsToMainThreadOnImplThread(
std::unique_ptr<MutatorEvents> events) override {}
bool IsInsideDraw() override { return false; }
void RenewTreePriority() override {}
void PostDelayedAnimationTaskOnImplThread(base::OnceClosure task,
base::TimeDelta delay) override {
animation_task_ = std::move(task);
requested_animation_delay_ = delay;
}
void DidActivateSyncTree() override {
if (set_local_surface_id_on_activate_) {
// Make sure the active tree always has a valid LocalSurfaceId.
host_impl_->active_tree()->SetLocalSurfaceIdAllocationFromParent(
viz::LocalSurfaceIdAllocation(
viz::LocalSurfaceId(1,
base::UnguessableToken::Deserialize(2u, 3u)),
base::TimeTicks::Now()));
}
}
void WillPrepareTiles() override {}
void DidPrepareTiles() override {}
void DidCompletePageScaleAnimationOnImplThread() override {
did_complete_page_scale_animation_ = true;
}
void OnDrawForLayerTreeFrameSink(bool resourceless_software_draw,
bool skip_draw) override {
std::unique_ptr<TestFrameData> frame(new TestFrameData);
EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(frame.get()));
last_on_draw_render_passes_.clear();
viz::RenderPass::CopyAll(frame->render_passes,
&last_on_draw_render_passes_);
host_impl_->DrawLayers(frame.get());
host_impl_->DidDrawAllLayers(*frame);
last_on_draw_frame_ = std::move(frame);
}
void NeedsImplSideInvalidation(bool needs_first_draw_on_activation) override {
did_request_impl_side_invalidation_ = true;
}
void NotifyImageDecodeRequestFinished() override {}
void RequestBeginMainFrameNotExpected(bool new_state) override {}
void DidPresentCompositorFrameOnImplThread(
uint32_t frame_token,
std::vector<LayerTreeHost::PresentationTimeCallback> callbacks,
const gfx::PresentationFeedback& feedback) override {}
void DidGenerateLocalSurfaceIdAllocationOnImplThread(
const viz::LocalSurfaceIdAllocation& allocation) override {
last_generated_local_surface_id_ = allocation;
did_generate_local_surface_id_ = true;
}
void NotifyAnimationWorkletStateChange(AnimationWorkletMutationState state,
ElementListType tree_type) override {}
void set_reduce_memory_result(bool reduce_memory_result) {
reduce_memory_result_ = reduce_memory_result;
}
virtual bool CreateHostImpl(
const LayerTreeSettings& settings,
std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink) {
return CreateHostImplWithTaskRunnerProvider(
settings, std::move(layer_tree_frame_sink), &task_runner_provider_);
}
AnimationHost* GetImplAnimationHost() const {
return static_cast<AnimationHost*>(host_impl_->mutator_host());
}
virtual bool CreateHostImplWithTaskRunnerProvider(
const LayerTreeSettings& settings,
std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink,
TaskRunnerProvider* task_runner_provider) {
if (host_impl_)
host_impl_->ReleaseLayerTreeFrameSink();
host_impl_.reset();
InitializeImageWorker(settings);
host_impl_ = LayerTreeHostImpl::Create(
settings, this, task_runner_provider, &stats_instrumentation_,
&task_graph_runner_,
AnimationHost::CreateForTesting(ThreadInstance::IMPL), 0,
image_worker_ ? image_worker_->task_runner() : nullptr);
layer_tree_frame_sink_ = std::move(layer_tree_frame_sink);
host_impl_->SetVisible(true);
bool init = host_impl_->InitializeFrameSink(layer_tree_frame_sink_.get());
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(10, 10));
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
host_impl_->active_tree()->SetLocalSurfaceIdAllocationFromParent(
viz::LocalSurfaceIdAllocation(
viz::LocalSurfaceId(1, base::UnguessableToken::Deserialize(2u, 3u)),
base::TimeTicks::Now()));
// Set the viz::BeginFrameArgs so that methods which use it are able to.
host_impl_->WillBeginImplFrame(viz::CreateBeginFrameArgsForTesting(
BEGINFRAME_FROM_HERE, 0, 1,
base::TimeTicks() + base::TimeDelta::FromMilliseconds(1)));
host_impl_->DidFinishImplFrame();
timeline_ =
AnimationTimeline::Create(AnimationIdProvider::NextTimelineId());
GetImplAnimationHost()->AddAnimationTimeline(timeline_);
return init;
}
void SetupRootLayerImpl(std::unique_ptr<LayerImpl> root) {
root->test_properties()->position = gfx::PointF();
root->SetBounds(gfx::Size(10, 10));
root->SetDrawsContent(true);
root->draw_properties().visible_layer_rect = gfx::Rect(0, 0, 10, 10);
root->test_properties()->force_render_surface = true;
host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
}
static gfx::Vector2dF ScrollDelta(LayerImpl* layer_impl) {
gfx::ScrollOffset delta = layer_impl->layer_tree_impl()
->property_trees()
->scroll_tree.GetScrollOffsetDeltaForTesting(
layer_impl->element_id());
return gfx::Vector2dF(delta.x(), delta.y());
}
static void ExpectClearedScrollDeltasRecursive(LayerImpl* root) {
for (auto* layer : *root->layer_tree_impl())
ASSERT_EQ(ScrollDelta(layer), gfx::Vector2d());
}
static ::testing::AssertionResult ScrollInfoContains(
const ScrollAndScaleSet& scroll_info,
ElementId id,
const gfx::ScrollOffset& scroll_delta) {
int times_encountered = 0;
for (size_t i = 0; i < scroll_info.scrolls.size(); ++i) {
if (scroll_info.scrolls[i].element_id != id)
continue;
if (scroll_delta != scroll_info.scrolls[i].scroll_delta) {
return ::testing::AssertionFailure()
<< "Expected " << scroll_delta.ToString() << ", not "
<< scroll_info.scrolls[i].scroll_delta.ToString();
}
times_encountered++;
}
if (id == scroll_info.inner_viewport_scroll.element_id) {
if (scroll_delta != scroll_info.inner_viewport_scroll.scroll_delta) {
return ::testing::AssertionFailure()
<< "Expected " << scroll_delta.ToString() << ", not "
<< scroll_info.inner_viewport_scroll.scroll_delta.ToString();
}
times_encountered++;
}
if (times_encountered != 1)
return ::testing::AssertionFailure() << "No scroll found with id " << id;
return ::testing::AssertionSuccess();
}
static void ExpectNone(const ScrollAndScaleSet& scroll_info, ElementId id) {
int times_encountered = 0;
for (size_t i = 0; i < scroll_info.scrolls.size(); ++i) {
if (scroll_info.scrolls[i].element_id != id)
continue;
times_encountered++;
}
ASSERT_EQ(0, times_encountered);
}
LayerImpl* CreateScrollAndContentsLayers(LayerTreeImpl* layer_tree_impl,
const gfx::Size& content_size) {
// Clear any existing viewport layers that were setup so this function can
// be called multiple times.
layer_tree_impl->ClearViewportLayers();
// Create both an inner viewport scroll layer and an outer viewport scroll
// layer. The MaxScrollOffset of the outer viewport scroll layer will be
// 0x0, so the scrolls will be applied directly to the inner viewport.
const int kOuterViewportClipLayerId = 116;
const int kOuterViewportScrollLayerId = 117;
const int kContentLayerId = 118;
const int kInnerViewportScrollLayerId = 2;
const int kInnerViewportClipLayerId = 4;
const int kPageScaleLayerId = 5;
std::unique_ptr<LayerImpl> root = LayerImpl::Create(layer_tree_impl, 1);
root->SetBounds(content_size);
root->test_properties()->position = gfx::PointF();
root->test_properties()->force_render_surface = true;
std::unique_ptr<LayerImpl> inner_scroll =
LayerImpl::Create(layer_tree_impl, kInnerViewportScrollLayerId);
inner_scroll->test_properties()->is_container_for_fixed_position_layers =
true;
inner_scroll->layer_tree_impl()
->property_trees()
->scroll_tree.UpdateScrollOffsetBaseForTesting(
inner_scroll->element_id(), gfx::ScrollOffset());
std::unique_ptr<LayerImpl> inner_clip =
LayerImpl::Create(layer_tree_impl, kInnerViewportClipLayerId);
gfx::Size viewport_scroll_bounds =
gfx::Size(content_size.width() / 2, content_size.height() / 2);
inner_clip->SetBounds(viewport_scroll_bounds);
std::unique_ptr<LayerImpl> page_scale =
LayerImpl::Create(layer_tree_impl, kPageScaleLayerId);
inner_scroll->SetScrollable(viewport_scroll_bounds);
inner_scroll->SetHitTestable(true);
inner_scroll->SetElementId(
LayerIdToElementIdForTesting(inner_scroll->id()));
inner_scroll->SetBounds(content_size);
inner_scroll->test_properties()->position = gfx::PointF();
std::unique_ptr<LayerImpl> outer_clip =
LayerImpl::Create(layer_tree_impl, kOuterViewportClipLayerId);
outer_clip->SetBounds(content_size);
outer_clip->test_properties()->is_container_for_fixed_position_layers =
true;
std::unique_ptr<LayerImpl> outer_scroll =
LayerImpl::Create(layer_tree_impl, kOuterViewportScrollLayerId);
outer_scroll->SetScrollable(content_size);
outer_scroll->SetHitTestable(true);
outer_scroll->SetElementId(
LayerIdToElementIdForTesting(outer_scroll->id()));
outer_scroll->layer_tree_impl()
->property_trees()
->scroll_tree.UpdateScrollOffsetBaseForTesting(
outer_scroll->element_id(), gfx::ScrollOffset());
outer_scroll->SetBounds(content_size);
outer_scroll->test_properties()->position = gfx::PointF();
std::unique_ptr<LayerImpl> contents =
LayerImpl::Create(layer_tree_impl, kContentLayerId);
contents->SetDrawsContent(true);
contents->SetBounds(content_size);
contents->test_properties()->position = gfx::PointF();
outer_scroll->test_properties()->AddChild(std::move(contents));
outer_clip->test_properties()->AddChild(std::move(outer_scroll));
inner_scroll->test_properties()->AddChild(std::move(outer_clip));
page_scale->test_properties()->AddChild(std::move(inner_scroll));
inner_clip->test_properties()->AddChild(std::move(page_scale));
root->test_properties()->AddChild(std::move(inner_clip));
layer_tree_impl->SetRootLayerForTesting(std::move(root));
layer_tree_impl->BuildPropertyTreesForTesting();
LayerTreeImpl::ViewportLayerIds viewport_ids;
viewport_ids.page_scale = kPageScaleLayerId;
viewport_ids.inner_viewport_container = kInnerViewportClipLayerId;
viewport_ids.outer_viewport_container = kOuterViewportClipLayerId;
viewport_ids.inner_viewport_scroll = kInnerViewportScrollLayerId;
viewport_ids.outer_viewport_scroll = kOuterViewportScrollLayerId;
layer_tree_impl->SetViewportLayersFromIds(viewport_ids);
layer_tree_impl->DidBecomeActive();
return layer_tree_impl->InnerViewportScrollLayer();
}
LayerImpl* SetupScrollAndContentsLayers(const gfx::Size& content_size) {
LayerImpl* scroll_layer =
CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
host_impl_->active_tree()->DidBecomeActive();
return scroll_layer;
}
void CreateAndTestNonScrollableLayers(const bool& transparent_layer) {
LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
gfx::Size content_size = gfx::Size(360, 600);
gfx::Size scroll_content_size = gfx::Size(345, 3800);
gfx::Size scrollbar_size = gfx::Size(15, 600);
host_impl_->active_tree()->SetDeviceViewportSize(content_size);
std::unique_ptr<LayerImpl> root = LayerImpl::Create(layer_tree_impl, 1);
root->SetBounds(content_size);
root->test_properties()->position = gfx::PointF();
std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 3);
scroll->SetBounds(scroll_content_size);
scroll->SetScrollable(content_size);
scroll->SetHitTestable(true);
scroll->SetElementId(LayerIdToElementIdForTesting(scroll->id()));
scroll->SetDrawsContent(true);
std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar =
SolidColorScrollbarLayerImpl::Create(layer_tree_impl, 4, VERTICAL, 10,
0, false, true);
scrollbar->SetBounds(scrollbar_size);
scrollbar->test_properties()->position = gfx::PointF(345, 0);
scrollbar->SetScrollElementId(scroll->element_id());
scrollbar->SetDrawsContent(true);
scrollbar->test_properties()->opacity = 1.f;
std::unique_ptr<LayerImpl> squash1 = LayerImpl::Create(layer_tree_impl, 5);
squash1->SetBounds(gfx::Size(140, 300));
squash1->test_properties()->position = gfx::PointF(220, 0);
if (transparent_layer) {
squash1->test_properties()->opacity = 0.0f;
// The transparent layer should still participate in hit testing even
// through it does not draw content.
squash1->SetHitTestable(true);
} else {
squash1->SetDrawsContent(true);
squash1->SetHitTestable(true);
}
std::unique_ptr<LayerImpl> squash2 = LayerImpl::Create(layer_tree_impl, 6);
squash2->SetBounds(gfx::Size(140, 300));
squash2->test_properties()->position = gfx::PointF(220, 300);
squash2->SetDrawsContent(true);
squash2->SetHitTestable(true);
scroll->test_properties()->AddChild(std::move(squash2));
root->test_properties()->AddChild(std::move(scroll));
root->test_properties()->AddChild(std::move(scrollbar));
root->test_properties()->AddChild(std::move(squash1));
layer_tree_impl->SetRootLayerForTesting(std::move(root));
layer_tree_impl->BuildPropertyTreesForTesting();
layer_tree_impl->DidBecomeActive();
// The point hits squash1 layer and also scroll layer, because scroll layer
// is not an ancestor of squash1 layer, we cannot scroll on impl thread.
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point(230, 150)).get(), InputHandler::WHEEL);
ASSERT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread);
ASSERT_EQ(MainThreadScrollingReason::kFailedHitTest,
status.main_thread_scrolling_reasons);
// The point hits squash1 layer and also scrollbar layer.
status = host_impl_->ScrollBegin(BeginState(gfx::Point(350, 150)).get(),
InputHandler::WHEEL);
ASSERT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread);
ASSERT_EQ(MainThreadScrollingReason::kFailedHitTest,
status.main_thread_scrolling_reasons);
// The point hits squash2 layer and also scroll layer, because scroll layer
// is an ancestor of squash2 layer, we should scroll on impl.
status = host_impl_->ScrollBegin(BeginState(gfx::Point(230, 450)).get(),
InputHandler::WHEEL);
ASSERT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
}
// Sets up a typical virtual viewport setup with one child content layer.
// Returns a pointer to the content layer.
LayerImpl* CreateBasicVirtualViewportLayers(const gfx::Size& viewport_size,
const gfx::Size& content_size) {
// CreateScrollAndContentsLayers makes the outer viewport unscrollable and
// the inner a different size from the outer. We'll reuse its layer
// hierarchy but adjust the sizing to our needs.
CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
LayerImpl* content_layer = host_impl_->OuterViewportScrollLayer()
->test_properties()
->children.back();
content_layer->SetBounds(content_size);
host_impl_->OuterViewportScrollLayer()->SetBounds(content_size);
host_impl_->OuterViewportScrollLayer()->SetScrollable(viewport_size);
host_impl_->OuterViewportScrollLayer()->SetHitTestable(true);
LayerImpl* outer_clip =
host_impl_->OuterViewportScrollLayer()->test_properties()->parent;
outer_clip->SetBounds(viewport_size);
LayerImpl* inner_clip_layer = host_impl_->InnerViewportScrollLayer()
->test_properties()
->parent->test_properties()
->parent;
inner_clip_layer->SetBounds(viewport_size);
host_impl_->InnerViewportScrollLayer()->SetBounds(viewport_size);
host_impl_->InnerViewportScrollLayer()->SetScrollable(viewport_size);
host_impl_->InnerViewportScrollLayer()->SetHitTestable(true);
host_impl_->active_tree()->BuildPropertyTreesForTesting();
host_impl_->active_tree()->SetDeviceViewportSize(viewport_size);
host_impl_->active_tree()->DidBecomeActive();
return content_layer;
}
std::unique_ptr<LayerImpl> CreateScrollableLayer(int id,
const gfx::Size& size) {
std::unique_ptr<LayerImpl> layer =
LayerImpl::Create(host_impl_->active_tree(), id);
layer->SetElementId(LayerIdToElementIdForTesting(layer->id()));
layer->SetDrawsContent(true);
layer->SetBounds(size);
gfx::Size scroll_container_bounds =
gfx::Size(size.width() / 2, size.height() / 2);
layer->SetScrollable(scroll_container_bounds);
layer->SetHitTestable(true);
return layer;
}
std::unique_ptr<ScrollState> BeginState(const gfx::Point& point) {
ScrollStateData scroll_state_data;
scroll_state_data.is_beginning = true;
scroll_state_data.position_x = point.x();
scroll_state_data.position_y = point.y();
std::unique_ptr<ScrollState> scroll_state(
new ScrollState(scroll_state_data));
return scroll_state;
}
std::unique_ptr<ScrollState> UpdateState(const gfx::Point& point,
const gfx::Vector2dF& delta) {
ScrollStateData scroll_state_data;
scroll_state_data.delta_x = delta.x();
scroll_state_data.delta_y = delta.y();
scroll_state_data.position_x = point.x();
scroll_state_data.position_y = point.y();
std::unique_ptr<ScrollState> scroll_state(
new ScrollState(scroll_state_data));
return scroll_state;
}
std::unique_ptr<ScrollState> EndState() {
ScrollStateData scroll_state_data;
scroll_state_data.is_ending = true;
std::unique_ptr<ScrollState> scroll_state(
new ScrollState(scroll_state_data));
return scroll_state;
}
void DrawFrame() {
TestFrameData frame;
EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
host_impl_->DrawLayers(&frame);
host_impl_->DidDrawAllLayers(frame);
}
void TestGPUMemoryForTilings(const gfx::Size& layer_size) {
std::unique_ptr<FakeRecordingSource> recording_source =
FakeRecordingSource::CreateFilledRecordingSource(layer_size);
PaintImage checkerable_image =
CreateDiscardablePaintImage(gfx::Size(500, 500));
recording_source->add_draw_image(checkerable_image, gfx::Point(0, 0));
recording_source->Rerecord();
scoped_refptr<FakeRasterSource> raster_source =
FakeRasterSource::CreateFromRecordingSource(recording_source.get());
// Create the pending tree.
host_impl_->BeginCommit();
LayerTreeImpl* pending_tree = host_impl_->pending_tree();
pending_tree->SetDeviceViewportSize(layer_size);
pending_tree->SetRootLayerForTesting(
FakePictureLayerImpl::CreateWithRasterSource(pending_tree, 1,
raster_source));
auto* root = static_cast<FakePictureLayerImpl*>(*pending_tree->begin());
root->SetBounds(layer_size);
root->SetDrawsContent(true);
pending_tree->BuildPropertyTreesForTesting();
// CompleteCommit which should perform a PrepareTiles, adding tilings for
// the root layer, each one having a raster task.
host_impl_->CommitComplete();
// Activate the pending tree and ensure that all tiles are rasterized.
while (!did_notify_ready_to_activate_)
base::RunLoop().RunUntilIdle();
DrawFrame();
host_impl_->ReleaseLayerTreeFrameSink();
host_impl_ = nullptr;
}
void WhiteListedTouchActionTestHelper(float device_scale_factor,
float page_scale_factor) {
LayerImpl* scroll = SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(100, 100));
DrawFrame();
LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
// Just hard code some random number, we care about the actual page scale
// factor on the active tree.
float min_page_scale_factor = 0.1f;
float max_page_scale_factor = 5.0f;
host_impl_->active_tree()->PushPageScaleFromMainThread(
page_scale_factor, min_page_scale_factor, max_page_scale_factor);
host_impl_->active_tree()->SetDeviceScaleFactor(device_scale_factor);
std::unique_ptr<LayerImpl> child_layer =
LayerImpl::Create(host_impl_->active_tree(), 6);
LayerImpl* child = child_layer.get();
child_layer->SetDrawsContent(true);
child_layer->test_properties()->position = gfx::PointF(0, 0);
child_layer->SetBounds(gfx::Size(25, 25));
scroll->test_properties()->AddChild(std::move(child_layer));
host_impl_->active_tree()->BuildPropertyTreesForTesting();
TouchActionRegion root_touch_action_region;
root_touch_action_region.Union(kTouchActionPanX, gfx::Rect(0, 0, 50, 50));
root->SetTouchActionRegion(root_touch_action_region);
TouchActionRegion child_touch_action_region;
child_touch_action_region.Union(kTouchActionPanLeft,
gfx::Rect(0, 0, 25, 25));
child->SetTouchActionRegion(child_touch_action_region);
TouchAction touch_action = kTouchActionAuto;
host_impl_->EventListenerTypeForTouchStartOrMoveAt(gfx::Point(10, 10),
&touch_action);
EXPECT_EQ(kTouchActionPanLeft, touch_action);
touch_action = kTouchActionAuto;
host_impl_->EventListenerTypeForTouchStartOrMoveAt(gfx::Point(30, 30),
&touch_action);
EXPECT_EQ(kTouchActionPanX, touch_action);
TouchActionRegion new_child_region;
new_child_region.Union(kTouchActionPanY, gfx::Rect(0, 0, 25, 25));
child->SetTouchActionRegion(new_child_region);
touch_action = kTouchActionAuto;
host_impl_->EventListenerTypeForTouchStartOrMoveAt(gfx::Point(10, 10),
&touch_action);
EXPECT_EQ(kTouchActionPanY, touch_action);
touch_action = kTouchActionAuto;
host_impl_->EventListenerTypeForTouchStartOrMoveAt(gfx::Point(30, 30),
&touch_action);
EXPECT_EQ(kTouchActionPanX, touch_action);
}
LayerImpl* CreateLayerForSnapping() {
LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(100, 100));
gfx::Size overflow_size(400, 400);
EXPECT_EQ(1u, scroll_layer->test_properties()->children.size());
LayerImpl* overflow = scroll_layer->test_properties()->children[0];
overflow->SetBounds(overflow_size);
overflow->SetScrollable(gfx::Size(100, 100));
overflow->SetHitTestable(true);
overflow->SetElementId(LayerIdToElementIdForTesting(overflow->id()));
overflow->layer_tree_impl()
->property_trees()
->scroll_tree.UpdateScrollOffsetBaseForTesting(overflow->element_id(),
gfx::ScrollOffset());
overflow->test_properties()->position = gfx::PointF(0, 0);
SnapContainerData container_data(
ScrollSnapType(false, SnapAxis::kBoth, SnapStrictness::kMandatory),
gfx::RectF(0, 0, 200, 200), gfx::ScrollOffset(300, 300));
SnapAreaData area_data(ScrollSnapAlign(SnapAlignment::kStart),
gfx::RectF(50, 50, 100, 100), false);
container_data.AddSnapAreaData(area_data);
overflow->test_properties()->snap_container_data.emplace(container_data);
host_impl_->active_tree()->BuildPropertyTreesForTesting();
DrawFrame();
return overflow;
}
void pinch_zoom_pan_viewport_forces_commit_redraw(float device_scale_factor);
void pinch_zoom_pan_viewport_test(float device_scale_factor);
void pinch_zoom_pan_viewport_and_scroll_test(float device_scale_factor);
void pinch_zoom_pan_viewport_and_scroll_boundary_test(
float device_scale_factor);
void SetupMouseMoveAtWithDeviceScale(float device_scale_factor);
void SetupMouseMoveAtTestScrollbarStates(bool main_thread_scrolling);
scoped_refptr<AnimationTimeline> timeline() { return timeline_; }
protected:
virtual std::unique_ptr<LayerTreeFrameSink> CreateLayerTreeFrameSink() {
return FakeLayerTreeFrameSink::Create3dForGpuRasterization();
}
void DrawOneFrame() {
TestFrameData frame_data;
host_impl_->PrepareToDraw(&frame_data);
host_impl_->DidDrawAllLayers(frame_data);
}
static void SetScrollOffsetDelta(LayerImpl* layer_impl,
const gfx::Vector2dF& delta) {
if (layer_impl->layer_tree_impl()
->property_trees()
->scroll_tree.SetScrollOffsetDeltaForTesting(
layer_impl->element_id(), delta))
layer_impl->layer_tree_impl()->DidUpdateScrollOffset(
layer_impl->element_id());
}
void BeginImplFrameAndAnimate(viz::BeginFrameArgs begin_frame_args,
base::TimeTicks frame_time) {
begin_frame_args.frame_time = frame_time;
host_impl_->WillBeginImplFrame(begin_frame_args);
host_impl_->Animate();
host_impl_->UpdateAnimationState(true);
host_impl_->DidFinishImplFrame();
}
void InitializeImageWorker(const LayerTreeSettings& settings) {
if (settings.enable_checker_imaging) {
image_worker_ = std::make_unique<base::Thread>("ImageWorker");
ASSERT_TRUE(image_worker_->Start());
} else {
image_worker_.reset();
}
}
FakeImplTaskRunnerProvider task_runner_provider_;
DebugScopedSetMainThreadBlocked always_main_thread_blocked_;
TestTaskGraphRunner task_graph_runner_;
std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink_;
std::unique_ptr<LayerTreeHostImpl> host_impl_;
FakeRenderingStatsInstrumentation stats_instrumentation_;
bool on_can_draw_state_changed_called_;
bool did_notify_ready_to_activate_;
bool did_request_commit_;
bool did_request_redraw_;
bool did_request_next_frame_;
bool did_request_prepare_tiles_;
bool did_complete_page_scale_animation_;
bool reduce_memory_result_;
bool did_request_impl_side_invalidation_;
base::OnceClosure animation_task_;
base::TimeDelta requested_animation_delay_;
std::unique_ptr<TestFrameData> last_on_draw_frame_;
viz::RenderPassList last_on_draw_render_passes_;
scoped_refptr<AnimationTimeline> timeline_;
std::unique_ptr<base::Thread> image_worker_;
viz::LocalSurfaceIdAllocation last_generated_local_surface_id_;
bool did_generate_local_surface_id_ = false;
bool set_local_surface_id_on_activate_ = true;
};
class CommitToPendingTreeLayerTreeHostImplTest : public LayerTreeHostImplTest {
public:
void SetUp() override {
LayerTreeSettings settings = DefaultSettings();
settings.commit_to_active_tree = false;
CreateHostImpl(settings, CreateLayerTreeFrameSink());
}
};
// A test fixture for new animation timelines tests.
class LayerTreeHostImplTimelinesTest : public LayerTreeHostImplTest {
public:
void SetUp() override {
CreateHostImpl(DefaultSettings(), CreateLayerTreeFrameSink());
}
};
class TestInputHandlerClient : public InputHandlerClient {
public:
TestInputHandlerClient()
: page_scale_factor_(0.f),
min_page_scale_factor_(-1.f),
max_page_scale_factor_(-1.f) {}
~TestInputHandlerClient() override = default;
// InputHandlerClient implementation.
void WillShutdown() override {}
void Animate(base::TimeTicks time) override {}
void ReconcileElasticOverscrollAndRootScroll() override {}
void UpdateRootLayerStateForSynchronousInputHandler(
const gfx::ScrollOffset& total_scroll_offset,
const gfx::ScrollOffset& max_scroll_offset,
const gfx::SizeF& scrollable_size,
float page_scale_factor,
float min_page_scale_factor,
float max_page_scale_factor) override {
DCHECK(total_scroll_offset.x() <= max_scroll_offset.x());
DCHECK(total_scroll_offset.y() <= max_scroll_offset.y());
last_set_scroll_offset_ = total_scroll_offset;
max_scroll_offset_ = max_scroll_offset;
scrollable_size_ = scrollable_size;
page_scale_factor_ = page_scale_factor;
min_page_scale_factor_ = min_page_scale_factor;
max_page_scale_factor_ = max_page_scale_factor;
}
void DeliverInputForBeginFrame() override {}
gfx::ScrollOffset last_set_scroll_offset() { return last_set_scroll_offset_; }
gfx::ScrollOffset max_scroll_offset() const { return max_scroll_offset_; }
gfx::SizeF scrollable_size() const { return scrollable_size_; }
float page_scale_factor() const { return page_scale_factor_; }
float min_page_scale_factor() const { return min_page_scale_factor_; }
float max_page_scale_factor() const { return max_page_scale_factor_; }
private:
gfx::ScrollOffset last_set_scroll_offset_;
gfx::ScrollOffset max_scroll_offset_;
gfx::SizeF scrollable_size_;
float page_scale_factor_;
float min_page_scale_factor_;
float max_page_scale_factor_;
};
TEST_F(LayerTreeHostImplTest, LocalAndExternalPinchState) {
// PinchGestureBegin/End update pinch_gesture_active() properly.
EXPECT_FALSE(host_impl_->pinch_gesture_active());
host_impl_->PinchGestureBegin();
EXPECT_TRUE(host_impl_->pinch_gesture_active());
host_impl_->PinchGestureEnd(gfx::Point(), false /* snap_to_min */);
EXPECT_FALSE(host_impl_->pinch_gesture_active());
// set_external_pinch_gesture_active updates pinch_gesture_active() properly.
host_impl_->set_external_pinch_gesture_active(true);
EXPECT_TRUE(host_impl_->pinch_gesture_active());
host_impl_->set_external_pinch_gesture_active(false);
EXPECT_FALSE(host_impl_->pinch_gesture_active());
// Clearing external_pinch_gesture_active doesn't affect
// pinch_gesture_active() if it was set by PinchGestureBegin().
host_impl_->PinchGestureBegin();
EXPECT_TRUE(host_impl_->pinch_gesture_active());
host_impl_->set_external_pinch_gesture_active(false);
EXPECT_TRUE(host_impl_->pinch_gesture_active());
host_impl_->PinchGestureEnd(gfx::Point(), false /* snap_to_min */);
EXPECT_FALSE(host_impl_->pinch_gesture_active());
}
TEST_F(LayerTreeHostImplTest, NotifyIfCanDrawChanged) {
// Note: It is not possible to disable the renderer once it has been set,
// so we do not need to test that disabling the renderer notifies us
// that can_draw changed.
EXPECT_FALSE(host_impl_->CanDraw());
on_can_draw_state_changed_called_ = false;
// Set up the root layer, which allows us to draw.
SetupScrollAndContentsLayers(gfx::Size(100, 100));
EXPECT_TRUE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
// Toggle the root layer to make sure it toggles can_draw
host_impl_->active_tree()->SetRootLayerForTesting(nullptr);
host_impl_->active_tree()->DetachLayers();
EXPECT_FALSE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
SetupScrollAndContentsLayers(gfx::Size(100, 100));
EXPECT_TRUE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
// Toggle the device viewport size to make sure it toggles can_draw.
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size());
EXPECT_FALSE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(100, 100));
EXPECT_TRUE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
viz::ParentLocalSurfaceIdAllocator parent_allocator;
parent_allocator.GenerateId();
const uint32_t child_sequence_number1 =
host_impl_->GenerateChildSurfaceSequenceNumberSync();
EXPECT_NE(child_sequence_number1, viz::kInitialChildSequenceNumber);
EXPECT_FALSE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
host_impl_->active_tree()->SetLocalSurfaceIdAllocationFromParent(
parent_allocator.GetCurrentLocalSurfaceIdAllocation());
EXPECT_TRUE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
// Without this SetLocalSurfaceIdAllocationFromParent() is called from
// DidActivateSyncTree().
set_local_surface_id_on_activate_ = false;
// Necessary to set LocalSurfaceIdAllocation in
// |LayerTreeHostImpl::child_local_surface_id_allocator_|.
host_impl_->ActivateSyncTree();
const uint32_t child_sequence_number2 =
host_impl_->GenerateChildSurfaceSequenceNumberSync();
EXPECT_NE(child_sequence_number2, child_sequence_number1);
EXPECT_FALSE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
host_impl_->active_tree()->SetLocalSurfaceIdAllocationFromParent(
parent_allocator.GetCurrentLocalSurfaceIdAllocation());
EXPECT_FALSE(host_impl_->CanDraw());
EXPECT_FALSE(on_can_draw_state_changed_called_);
auto id_from_parent =
parent_allocator.GetCurrentLocalSurfaceIdAllocation().local_surface_id();
host_impl_->active_tree()->SetLocalSurfaceIdAllocationFromParent(
viz::LocalSurfaceIdAllocation(
viz::LocalSurfaceId(id_from_parent.parent_sequence_number(),
child_sequence_number2,
id_from_parent.embed_token()),
base::TimeTicks::Now()));
EXPECT_TRUE(host_impl_->CanDraw());
EXPECT_TRUE(on_can_draw_state_changed_called_);
on_can_draw_state_changed_called_ = false;
}
TEST_F(LayerTreeHostImplTest, ResourcelessDrawWithEmptyViewport) {
CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::CreateSoftware());
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->active_tree()->BuildPropertyTreesForTesting();
EXPECT_TRUE(host_impl_->CanDraw());
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size());
EXPECT_FALSE(host_impl_->CanDraw());
auto* fake_layer_tree_frame_sink =
static_cast<FakeLayerTreeFrameSink*>(host_impl_->layer_tree_frame_sink());
EXPECT_EQ(fake_layer_tree_frame_sink->num_sent_frames(), 0u);
gfx::Transform identity;
gfx::Rect viewport(100, 100);
const bool resourceless_software_draw = true;
host_impl_->OnDraw(identity, viewport, resourceless_software_draw, false);
ASSERT_EQ(fake_layer_tree_frame_sink->num_sent_frames(), 1u);
#if defined(OS_ANDROID)
EXPECT_EQ(
gfx::SizeF(100.f, 100.f),
fake_layer_tree_frame_sink->last_sent_frame()->metadata.root_layer_size);
#endif
}
TEST_F(LayerTreeHostImplTest, ScrollDeltaNoLayers) {
ASSERT_FALSE(host_impl_->active_tree()->root_layer_for_testing());
std::unique_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
ASSERT_EQ(scroll_info->scrolls.size(), 0u);
}
TEST_F(LayerTreeHostImplTest, ScrollDeltaTreeButNoChanges) {
{
std::unique_ptr<LayerImpl> root =
LayerImpl::Create(host_impl_->active_tree(), 1);
root->test_properties()->AddChild(
LayerImpl::Create(host_impl_->active_tree(), 2));
root->test_properties()->AddChild(
LayerImpl::Create(host_impl_->active_tree(), 3));
root->test_properties()->children[1]->test_properties()->AddChild(
LayerImpl::Create(host_impl_->active_tree(), 4));
root->test_properties()->children[1]->test_properties()->AddChild(
LayerImpl::Create(host_impl_->active_tree(), 5));
root->test_properties()
->children[1]
->test_properties()
->children[0]
->test_properties()
->AddChild(LayerImpl::Create(host_impl_->active_tree(), 6));
host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
}
LayerImpl* root = *host_impl_->active_tree()->begin();
ExpectClearedScrollDeltasRecursive(root);
std::unique_ptr<ScrollAndScaleSet> scroll_info;
scroll_info = host_impl_->ProcessScrollDeltas();
ASSERT_EQ(scroll_info->scrolls.size(), 0u);
ExpectClearedScrollDeltasRecursive(root);
scroll_info = host_impl_->ProcessScrollDeltas();
ASSERT_EQ(scroll_info->scrolls.size(), 0u);
ExpectClearedScrollDeltasRecursive(root);
}
TEST_F(LayerTreeHostImplTest, ScrollDeltaRepeatedScrolls) {
gfx::ScrollOffset scroll_offset(20, 30);
gfx::ScrollOffset scroll_delta(11, -15);
auto root_owned = LayerImpl::Create(host_impl_->active_tree(), 1);
auto* root = root_owned.get();
root->SetBounds(gfx::Size(110, 110));
root->SetScrollable(gfx::Size(10, 10));
root->SetHitTestable(true);
root->SetElementId(LayerIdToElementIdForTesting(root->id()));
root->layer_tree_impl()
->property_trees()
->scroll_tree.UpdateScrollOffsetBaseForTesting(root->element_id(),
scroll_offset);
host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_owned));
host_impl_->active_tree()->BuildPropertyTreesForTesting();
std::unique_ptr<ScrollAndScaleSet> scroll_info;
root->ScrollBy(gfx::ScrollOffsetToVector2dF(scroll_delta));
scroll_info = host_impl_->ProcessScrollDeltas();
ASSERT_EQ(scroll_info->scrolls.size(), 1u);
EXPECT_TRUE(
ScrollInfoContains(*scroll_info, root->element_id(), scroll_delta));
gfx::ScrollOffset scroll_delta2(-5, 27);
root->ScrollBy(gfx::ScrollOffsetToVector2dF(scroll_delta2));
scroll_info = host_impl_->ProcessScrollDeltas();
ASSERT_EQ(scroll_info->scrolls.size(), 1u);
EXPECT_TRUE(ScrollInfoContains(*scroll_info, root->element_id(),
scroll_delta + scroll_delta2));
root->ScrollBy(gfx::Vector2d());
scroll_info = host_impl_->ProcessScrollDeltas();
EXPECT_TRUE(ScrollInfoContains(*scroll_info, root->element_id(),
scroll_delta + scroll_delta2));
}
TEST_F(CommitToPendingTreeLayerTreeHostImplTest,
GPUMemoryForSmallLayerHistogramTest) {
base::HistogramTester histogram_tester;
SetClientNameForMetrics("Renderer");
// With default tile size being set to 256 * 256, the following layer needs
// one tile only which costs 256 * 256 * 4 / 1024 = 256KB memory.
TestGPUMemoryForTilings(gfx::Size(200, 200));
histogram_tester.ExpectBucketCount(
"Compositing.Renderer.GPUMemoryForTilingsInKb", 256, 1);
histogram_tester.ExpectTotalCount(
"Compositing.Renderer.GPUMemoryForTilingsInKb", 1);
}
TEST_F(CommitToPendingTreeLayerTreeHostImplTest,
GPUMemoryForLargeLayerHistogramTest) {
base::HistogramTester histogram_tester;
SetClientNameForMetrics("Renderer");
// With default tile size being set to 256 * 256, the following layer needs
// 4 tiles which cost 256 * 256 * 4 * 4 / 1024 = 1024KB memory.
TestGPUMemoryForTilings(gfx::Size(500, 500));
histogram_tester.ExpectBucketCount(
"Compositing.Renderer.GPUMemoryForTilingsInKb", 1024, 1);
histogram_tester.ExpectTotalCount(
"Compositing.Renderer.GPUMemoryForTilingsInKb", 1);
}
TEST_F(LayerTreeHostImplTest, ScrollBeforeRootLayerAttached) {
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point()).get(), InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer,
status.main_thread_scrolling_reasons);
status = host_impl_->RootScrollBegin(BeginState(gfx::Point()).get(),
InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer,
status.main_thread_scrolling_reasons);
}
TEST_F(LayerTreeHostImplTest, ScrollRootCallsCommitAndRedraw) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->active_tree()->BuildPropertyTreesForTesting();
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
DrawFrame();
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point()).get(), InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
status.main_thread_scrolling_reasons);
EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(),
InputHandler::WHEEL));
host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(0, 10),
InputHandler::WHEEL));
host_impl_->ScrollEnd(EndState().get());
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(),
InputHandler::WHEEL));
EXPECT_TRUE(did_request_redraw_);
EXPECT_TRUE(did_request_commit_);
}
TEST_F(LayerTreeHostImplTest, ScrollActiveOnlyAfterScrollMovement) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->active_tree()->BuildPropertyTreesForTesting();
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
DrawFrame();
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point()).get(), InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
status.main_thread_scrolling_reasons);
EXPECT_FALSE(host_impl_->IsActivelyScrolling());
host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
EXPECT_TRUE(host_impl_->IsActivelyScrolling());
host_impl_->ScrollEnd(EndState().get());
EXPECT_FALSE(host_impl_->IsActivelyScrolling());
}
TEST_F(LayerTreeHostImplTest, ScrollWithoutRootLayer) {
// We should not crash when trying to scroll an empty layer tree.
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point()).get(), InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer,
status.main_thread_scrolling_reasons);
}
TEST_F(LayerTreeHostImplTest, ScrollWithoutRenderer) {
auto gl_owned = std::make_unique<viz::TestGLES2Interface>();
gl_owned->LoseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
GL_INNOCENT_CONTEXT_RESET_ARB);
// Initialization will fail.
EXPECT_FALSE(
CreateHostImpl(DefaultSettings(),
FakeLayerTreeFrameSink::Create3d(std::move(gl_owned))));
SetupScrollAndContentsLayers(gfx::Size(100, 100));
// We should not crash when trying to scroll after the renderer initialization
// fails.
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point()).get(), InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
status.main_thread_scrolling_reasons);
}
TEST_F(LayerTreeHostImplTest, ReplaceTreeWhileScrolling) {
LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
DrawFrame();
// We should not crash if the tree is replaced while we are scrolling.
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
.thread);
host_impl_->active_tree()->DetachLayers();
scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
// We should still be scrolling, because the scrolled layer also exists in the
// new tree.
gfx::ScrollOffset scroll_delta(0, 10);
host_impl_->ScrollBy(
UpdateState(gfx::Point(), gfx::ScrollOffsetToVector2dF(scroll_delta))
.get());
host_impl_->ScrollEnd(EndState().get());
std::unique_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_TRUE(ScrollInfoContains(*scroll_info, scroll_layer->element_id(),
scroll_delta));
}
TEST_F(LayerTreeHostImplTest, ScrollBlocksOnWheelEventHandlers) {
LayerImpl* scroll = SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
scroll->SetWheelEventHandlerRegion(Region(gfx::Rect(20, 20)));
DrawFrame();
// Wheel handlers determine whether mouse events block scroll.
host_impl_->active_tree()->set_event_listener_properties(
EventListenerClass::kMouseWheel, EventListenerProperties::kBlocking);
EXPECT_EQ(
EventListenerProperties::kBlocking,
host_impl_->GetEventListenerProperties(EventListenerClass::kMouseWheel));
// LTHI should know the wheel event handler region and only block mouse events
// in that region.
EXPECT_TRUE(host_impl_->HasBlockingWheelEventHandlerAt(gfx::Point(10, 10)));
EXPECT_FALSE(host_impl_->HasBlockingWheelEventHandlerAt(gfx::Point(30, 30)));
// But they don't influence the actual handling of the scroll gestures.
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point()).get(), InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
status.main_thread_scrolling_reasons);
host_impl_->ScrollEnd(EndState().get());
}
TEST_F(LayerTreeHostImplTest, ScrollBlocksOnTouchEventHandlers) {
LayerImpl* scroll = SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
DrawFrame();
LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
LayerImpl* child = nullptr;
{
std::unique_ptr<LayerImpl> child_layer =
LayerImpl::Create(host_impl_->active_tree(), 6);
child = child_layer.get();
child_layer->SetDrawsContent(true);
child_layer->test_properties()->position = gfx::PointF(0, 20);
child_layer->SetBounds(gfx::Size(50, 50));
scroll->test_properties()->AddChild(std::move(child_layer));
host_impl_->active_tree()->BuildPropertyTreesForTesting();
}
// Touch handler regions determine whether touch events block scroll.
TouchAction touch_action;
TouchActionRegion touch_action_region;
touch_action_region.Union(kTouchActionPanLeft, gfx::Rect(0, 0, 100, 100));
touch_action_region.Union(kTouchActionPanRight, gfx::Rect(25, 25, 100, 100));
root->SetTouchActionRegion(std::move(touch_action_region));
EXPECT_EQ(InputHandler::TouchStartOrMoveEventListenerType::HANDLER,
host_impl_->EventListenerTypeForTouchStartOrMoveAt(
gfx::Point(10, 10), &touch_action));
EXPECT_EQ(kTouchActionPanLeft, touch_action);
// But they don't influence the actual handling of the scroll gestures.
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point()).get(), InputHandler::TOUCHSCREEN);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
status.main_thread_scrolling_reasons);
host_impl_->ScrollEnd(EndState().get());
EXPECT_EQ(InputHandler::TouchStartOrMoveEventListenerType::HANDLER,
host_impl_->EventListenerTypeForTouchStartOrMoveAt(
gfx::Point(10, 30), &touch_action));
root->SetTouchActionRegion(TouchActionRegion());
EXPECT_EQ(InputHandler::TouchStartOrMoveEventListenerType::NO_HANDLER,
host_impl_->EventListenerTypeForTouchStartOrMoveAt(
gfx::Point(10, 30), &touch_action));
EXPECT_EQ(kTouchActionAuto, touch_action);
touch_action_region = TouchActionRegion();
touch_action_region.Union(kTouchActionPanX, gfx::Rect(0, 0, 50, 50));
child->SetTouchActionRegion(std::move(touch_action_region));
EXPECT_EQ(InputHandler::TouchStartOrMoveEventListenerType::HANDLER,
host_impl_->EventListenerTypeForTouchStartOrMoveAt(
gfx::Point(10, 30), &touch_action));
EXPECT_EQ(kTouchActionPanX, touch_action);
}
TEST_F(LayerTreeHostImplTest, ShouldScrollOnMainThread) {
SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
root->test_properties()->main_thread_scrolling_reasons =
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects;
host_impl_->active_tree()->BuildPropertyTreesForTesting();
DrawFrame();
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point()).get(), InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
status.main_thread_scrolling_reasons);
status = host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
InputHandler::TOUCHSCREEN);
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
status.main_thread_scrolling_reasons);
}
TEST_F(LayerTreeHostImplTest, ScrollWithOverlappingNonScrollableLayer) {
CreateAndTestNonScrollableLayers(false);
}
TEST_F(LayerTreeHostImplTest,
ScrollWithOverlappingTransparentNonScrollableLayer) {
CreateAndTestNonScrollableLayers(true);
}
TEST_F(LayerTreeHostImplTest, ScrolledOverlappingDrawnScrollbarLayer) {
LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
gfx::Size content_size = gfx::Size(360, 600);
gfx::Size scroll_content_size = gfx::Size(345, 3800);
gfx::Size scrollbar_size = gfx::Size(15, 600);
host_impl_->active_tree()->SetDeviceViewportSize(content_size);
std::unique_ptr<LayerImpl> root = LayerImpl::Create(layer_tree_impl, 1);
root->SetBounds(content_size);
root->test_properties()->position = gfx::PointF();
std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 3);
scroll->SetBounds(scroll_content_size);
scroll->SetScrollable(content_size);
scroll->SetHitTestable(true);
scroll->SetElementId(LayerIdToElementIdForTesting(scroll->id()));
scroll->SetDrawsContent(true);
scroll->SetHitTestable(true);
std::unique_ptr<SolidColorScrollbarLayerImpl> drawn_scrollbar =
SolidColorScrollbarLayerImpl::Create(layer_tree_impl, 4, VERTICAL, 10, 0,
false, true);
drawn_scrollbar->SetBounds(scrollbar_size);
drawn_scrollbar->test_properties()->position = gfx::PointF(345, 0);
drawn_scrollbar->SetScrollElementId(scroll->element_id());
drawn_scrollbar->SetDrawsContent(true);
drawn_scrollbar->SetHitTestable(true);
drawn_scrollbar->test_properties()->opacity = 1.f;
std::unique_ptr<LayerImpl> squash = LayerImpl::Create(layer_tree_impl, 5);
squash->SetBounds(gfx::Size(140, 300));
squash->test_properties()->position = gfx::PointF(220, 0);
squash->SetDrawsContent(true);
squash->SetHitTestable(true);
scroll->test_properties()->AddChild(std::move(drawn_scrollbar));
scroll->test_properties()->AddChild(std::move(squash));
root->test_properties()->AddChild(std::move(scroll));
layer_tree_impl->SetRootLayerForTesting(std::move(root));
layer_tree_impl->BuildPropertyTreesForTesting();
layer_tree_impl->DidBecomeActive();
// The point hits squash layer and also scrollbar layer, but because the
// scrollbar layer is a drawn scrollbar, we cannot scroll on the impl thread.
auto status = host_impl_->ScrollBegin(BeginState(gfx::Point(350, 150)).get(),
InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest,
status.main_thread_scrolling_reasons);
// The point hits the drawn scrollbar layer completely and should scroll on
// the impl thread.
status = host_impl_->ScrollBegin(BeginState(gfx::Point(350, 500)).get(),
InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
status.main_thread_scrolling_reasons);
}
TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionBasic) {
SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(100, 100));
LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
outer_scroll->SetNonFastScrollableRegion(gfx::Rect(0, 0, 50, 50));
host_impl_->active_tree()->BuildPropertyTreesForTesting();
DrawFrame();
// All scroll types inside the non-fast scrollable region should fail.
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point(25, 25)).get(), InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion,
status.main_thread_scrolling_reasons);
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(25, 25),
InputHandler::WHEEL));
status = host_impl_->ScrollBegin(BeginState(gfx::Point(25, 25)).get(),
InputHandler::TOUCHSCREEN);
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion,
status.main_thread_scrolling_reasons);
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(
gfx::Point(25, 25), InputHandler::TOUCHSCREEN));
// All scroll types outside this region should succeed.
status = host_impl_->ScrollBegin(BeginState(gfx::Point(75, 75)).get(),
InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
status.main_thread_scrolling_reasons);
EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(
gfx::Point(75, 75), InputHandler::TOUCHSCREEN));
host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(
gfx::Point(25, 25), InputHandler::TOUCHSCREEN));
host_impl_->ScrollEnd(EndState().get());
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(
gfx::Point(75, 75), InputHandler::TOUCHSCREEN));
status = host_impl_->ScrollBegin(BeginState(gfx::Point(75, 75)).get(),
InputHandler::TOUCHSCREEN);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
status.main_thread_scrolling_reasons);
EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(
gfx::Point(75, 75), InputHandler::TOUCHSCREEN));
host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(
gfx::Point(75, 75), InputHandler::TOUCHSCREEN));
}
TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionWithOffset) {
SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(100, 100));
LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
outer_scroll->SetNonFastScrollableRegion(gfx::Rect(0, 0, 50, 50));
outer_scroll->test_properties()->position = gfx::PointF(-25.f, 0.f);
outer_scroll->SetDrawsContent(true);
host_impl_->active_tree()->BuildPropertyTreesForTesting();
DrawFrame();
// This point would fall into the non-fast scrollable region except that we've
// moved the layer left by 25 pixels.
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point(40, 10)).get(), InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
status.main_thread_scrolling_reasons);
EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(40, 10),
InputHandler::WHEEL));
host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 1)).get());
host_impl_->ScrollEnd(EndState().get());
// This point is still inside the non-fast region.
status = host_impl_->ScrollBegin(BeginState(gfx::Point(10, 10)).get(),
InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion,
status.main_thread_scrolling_reasons);
}
TEST_F(LayerTreeHostImplTest, ScrollHandlerNotPresent) {
SetupScrollAndContentsLayers(gfx::Size(200, 200));
EXPECT_FALSE(host_impl_->active_tree()->have_scroll_event_handlers());
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
DrawFrame();
EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
InputHandler::TOUCHSCREEN);
EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
host_impl_->ScrollEnd(EndState().get());
EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
}
TEST_F(LayerTreeHostImplTest, ScrollHandlerPresent) {
SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->active_tree()->set_have_scroll_event_handlers(true);
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
DrawFrame();
EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
InputHandler::TOUCHSCREEN);
EXPECT_TRUE(host_impl_->scroll_affects_scroll_handler());
host_impl_->ScrollEnd(EndState().get());
EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
}
TEST_F(LayerTreeHostImplTest, ScrollByReturnsCorrectValue) {
SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(100, 100));
DrawFrame();
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point()).get(), InputHandler::TOUCHSCREEN);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
status.main_thread_scrolling_reasons);
// Trying to scroll to the left/top will not succeed.
EXPECT_FALSE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, 0)).get())
.did_scroll);
EXPECT_FALSE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, -10)).get())
.did_scroll);
EXPECT_FALSE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, -10)).get())
.did_scroll);
// Scrolling to the right/bottom will succeed.
EXPECT_TRUE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(10, 0)).get())
.did_scroll);
EXPECT_TRUE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get())
.did_scroll);
EXPECT_TRUE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(10, 10)).get())
.did_scroll);
// Scrolling to left/top will now succeed.
EXPECT_TRUE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, 0)).get())
.did_scroll);
EXPECT_TRUE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, -10)).get())
.did_scroll);
EXPECT_TRUE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, -10)).get())
.did_scroll);
// Scrolling diagonally against an edge will succeed.
EXPECT_TRUE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(10, -10)).get())
.did_scroll);
EXPECT_TRUE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, 0)).get())
.did_scroll);
EXPECT_TRUE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, 10)).get())
.did_scroll);
// Trying to scroll more than the available space will also succeed.
EXPECT_TRUE(
host_impl_
->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(5000, 5000)).get())
.did_scroll);
}
// TODO(sunyunjia): Move scroll snap tests to a separate file.
// https://crbug.com/851690
TEST_F(LayerTreeHostImplTest, ScrollSnapOnX) {
LayerImpl* overflow = CreateLayerForSnapping();
gfx::Point pointer_position(10, 10);
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
gfx::Vector2dF x_delta(20, 0);
host_impl_->ScrollBy(UpdateState(pointer_position, x_delta).get());
viz::BeginFrameArgs begin_frame_args =
viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
host_impl_->ScrollEnd(EndState().get(), true);
base::TimeTicks start_time =
base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);
BeginImplFrameAndAnimate(begin_frame_args, start_time);
BeginImplFrameAndAnimate(begin_frame_args,
start_time + base::TimeDelta::FromMilliseconds(50));
BeginImplFrameAndAnimate(
begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1000));
EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 0), overflow->CurrentScrollOffset());
}
TEST_F(LayerTreeHostImplTest, ScrollSnapOnY) {
LayerImpl* overflow = CreateLayerForSnapping();
gfx::Point pointer_position(10, 10);
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
gfx::Vector2dF y_delta(0, 20);
host_impl_->ScrollBy(UpdateState(pointer_position, y_delta).get());
viz::BeginFrameArgs begin_frame_args =
viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
host_impl_->ScrollEnd(EndState().get(), true);
base::TimeTicks start_time =
base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);
BeginImplFrameAndAnimate(begin_frame_args, start_time);
BeginImplFrameAndAnimate(begin_frame_args,
start_time + base::TimeDelta::FromMilliseconds(50));
BeginImplFrameAndAnimate(
begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1000));
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 50), overflow->CurrentScrollOffset());
}
TEST_F(LayerTreeHostImplTest, ScrollSnapOnBoth) {
LayerImpl* overflow = CreateLayerForSnapping();
gfx::Point pointer_position(10, 10);
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
gfx::Vector2dF delta(20, 20);
host_impl_->ScrollBy(UpdateState(pointer_position, delta).get());
viz::BeginFrameArgs begin_frame_args =
viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
host_impl_->ScrollEnd(EndState().get(), true);
base::TimeTicks start_time =
base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);
BeginImplFrameAndAnimate(begin_frame_args, start_time);
BeginImplFrameAndAnimate(begin_frame_args,
start_time + base::TimeDelta::FromMilliseconds(50));
BeginImplFrameAndAnimate(
begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1000));
EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 50), overflow->CurrentScrollOffset());
}
TEST_F(LayerTreeHostImplTest, ScrollSnapAfterAnimatedScroll) {
LayerImpl* overflow = CreateLayerForSnapping();
gfx::Point pointer_position(10, 10);
gfx::Vector2dF delta(20, 20);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_->ScrollAnimated(pointer_position, delta).thread);
EXPECT_EQ(overflow->scroll_tree_index(),
host_impl_->CurrentlyScrollingNode()->id);
base::TimeTicks start_time =
base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);
viz::BeginFrameArgs begin_frame_args =
viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
BeginImplFrameAndAnimate(begin_frame_args, start_time);
// Animating for the wheel scroll.
BeginImplFrameAndAnimate(begin_frame_args,
start_time + base::TimeDelta::FromMilliseconds(50));
EXPECT_FALSE(host_impl_->is_animating_for_snap_for_testing());
gfx::ScrollOffset current_offset = overflow->CurrentScrollOffset();
EXPECT_LT(0, current_offset.x());
EXPECT_GT(20, current_offset.x());
EXPECT_LT(0, current_offset.y());
EXPECT_GT(20, current_offset.y());
// Animating for the snap.
BeginImplFrameAndAnimate(
begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1000));
EXPECT_TRUE(host_impl_->is_animating_for_snap_for_testing());
// Finish the animation.
BeginImplFrameAndAnimate(
begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1500));
EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 50), overflow->CurrentScrollOffset());
EXPECT_FALSE(host_impl_->is_animating_for_snap_for_testing());
}
TEST_F(LayerTreeHostImplTest, SnapAnimationCancelledByScroll) {
LayerImpl* overflow = CreateLayerForSnapping();
gfx::Point pointer_position(10, 10);
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
gfx::Vector2dF x_delta(20, 0);
host_impl_->ScrollBy(UpdateState(pointer_position, x_delta).get());
EXPECT_FALSE(host_impl_->is_animating_for_snap_for_testing());
viz::BeginFrameArgs begin_frame_args =
viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
host_impl_->ScrollEnd(EndState().get(), true);
base::TimeTicks start_time =
base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);
BeginImplFrameAndAnimate(begin_frame_args, start_time);
// Animating for the snap.
BeginImplFrameAndAnimate(begin_frame_args,
start_time + base::TimeDelta::FromMilliseconds(100));
EXPECT_TRUE(host_impl_->is_animating_for_snap_for_testing());
gfx::ScrollOffset current_offset = overflow->CurrentScrollOffset();
EXPECT_GT(50, current_offset.x());
EXPECT_LT(20, current_offset.x());
EXPECT_EQ(0, current_offset.y());
// Interrup the snap animation with ScrollBegin.
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_FALSE(host_impl_->is_animating_for_snap_for_testing());
BeginImplFrameAndAnimate(begin_frame_args,
start_time + base::TimeDelta::FromMilliseconds(150));
EXPECT_VECTOR_EQ(gfx::ScrollOffsetToVector2dF(current_offset),
overflow->CurrentScrollOffset());
}
TEST_F(LayerTreeHostImplTest, GetSnapFlingInfoWhenZoomed) {
LayerImpl* overflow = CreateLayerForSnapping();
// Scales the page to its 1/5.
host_impl_->active_tree()->PushPageScaleFromMainThread(0.2f, 0.1f, 5.f);
// Should be (10, 10) in the scroller's coordinate.
gfx::Point pointer_position(2, 2);
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
// Should be (20, 20) in the scroller's coordinate.
gfx::Vector2dF delta(4, 4);
InputHandlerScrollResult result =
host_impl_->ScrollBy(UpdateState(pointer_position, delta).get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(20, 20), overflow->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(4, 4), result.current_visual_offset);
gfx::Vector2dF initial_offset, target_offset;
EXPECT_TRUE(host_impl_->GetSnapFlingInfo(gfx::Vector2dF(10, 10),
&initial_offset, &target_offset));
EXPECT_VECTOR_EQ(gfx::Vector2dF(4, 4), initial_offset);
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), target_offset);
}
TEST_F(LayerTreeHostImplTest, OverscrollBehaviorPreventsPropagation) {
LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(100, 100));
gfx::Size overflow_size(400, 400);
ASSERT_EQ(1u, scroll_layer->test_properties()->children.size());
LayerImpl* overflow = scroll_layer->test_properties()->children[0];
overflow->SetBounds(overflow_size);
overflow->SetScrollable(gfx::Size(100, 100));
overflow->SetHitTestable(true);
overflow->SetElementId(LayerIdToElementIdForTesting(overflow->id()));
overflow->layer_tree_impl()
->property_trees()
->scroll_tree.UpdateScrollOffsetBaseForTesting(overflow->element_id(),
gfx::ScrollOffset());
overflow->test_properties()->position = gfx::PointF(40, 40);
host_impl_->active_tree()->BuildPropertyTreesForTesting();
scroll_layer->SetCurrentScrollOffset(gfx::ScrollOffset(30, 30));
DrawFrame();
gfx::Point pointer_position(50, 50);
// OverscrollBehaviorTypeAuto shouldn't prevent scroll propagation.
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(30, 30), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(), overflow->CurrentScrollOffset());
gfx::Vector2dF x_delta(-10, 0);
gfx::Vector2dF y_delta(0, -10);
gfx::Vector2dF diagonal_delta(-10, -10);
host_impl_->ScrollBy(UpdateState(pointer_position, x_delta).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(20, 30), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
overflow->test_properties()->overscroll_behavior =
OverscrollBehavior(OverscrollBehavior::kOverscrollBehaviorTypeContain,
OverscrollBehavior::kOverscrollBehaviorTypeAuto);
host_impl_->active_tree()->BuildPropertyTreesForTesting();
DrawFrame();
// OverscrollBehaviorContain on x should prevent propagations of scroll
// on x.
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(20, 30), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
host_impl_->ScrollBy(UpdateState(pointer_position, x_delta).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(20, 30), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
// OverscrollBehaviorContain on x shouldn't prevent propagations of
// scroll on y.
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(20, 30), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
host_impl_->ScrollBy(UpdateState(pointer_position, y_delta).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(20, 20), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
// A scroll update with both x & y delta will adhere to the most restrictive
// case.
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(20, 20), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
host_impl_->ScrollBy(UpdateState(pointer_position, diagonal_delta).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(20, 20), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
// Changing scroll-boundary-behavior to y axis.
overflow->test_properties()->overscroll_behavior =
OverscrollBehavior(OverscrollBehavior::kOverscrollBehaviorTypeAuto,
OverscrollBehavior::kOverscrollBehaviorTypeContain);
host_impl_->active_tree()->BuildPropertyTreesForTesting();
DrawFrame();
// OverscrollBehaviorContain on y shouldn't prevent propagations of
// scroll on x.
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(20, 20), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
host_impl_->ScrollBy(UpdateState(pointer_position, x_delta).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
// OverscrollBehaviorContain on y should prevent propagations of scroll
// on y.
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
host_impl_->ScrollBy(UpdateState(pointer_position, y_delta).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
// A scroll update with both x & y delta will adhere to the most restrictive
// case.
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
host_impl_->ScrollBy(UpdateState(pointer_position, diagonal_delta).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
// Gesture scroll should latch to the first scroller that has non-auto
// overscroll-behavior.
overflow->test_properties()->overscroll_behavior =
OverscrollBehavior(OverscrollBehavior::kOverscrollBehaviorTypeContain,
OverscrollBehavior::kOverscrollBehaviorTypeContain);
host_impl_->active_tree()->BuildPropertyTreesForTesting();
DrawFrame();
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(pointer_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), overflow->CurrentScrollOffset());
host_impl_->ScrollBy(UpdateState(pointer_position, x_delta).get());
host_impl_->ScrollBy(UpdateState(pointer_position, -x_delta).get());
host_impl_->ScrollBy(UpdateState(pointer_position, y_delta).get());
host_impl_->ScrollBy(UpdateState(pointer_position, -y_delta).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), overflow->CurrentScrollOffset());
}
TEST_F(LayerTreeHostImplTest, ScrollWithUserUnscrollableLayers) {
LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(100, 100));
gfx::Size overflow_size(400, 400);
ASSERT_EQ(1u, scroll_layer->test_properties()->children.size());
LayerImpl* overflow = scroll_layer->test_properties()->children[0];
overflow->SetBounds(overflow_size);
overflow->SetScrollable(gfx::Size(100, 100));
overflow->SetHitTestable(true);
overflow->SetElementId(LayerIdToElementIdForTesting(overflow->id()));
overflow->layer_tree_impl()
->property_trees()
->scroll_tree.UpdateScrollOffsetBaseForTesting(overflow->element_id(),
gfx::ScrollOffset());
overflow->test_properties()->position = gfx::PointF();
host_impl_->active_tree()->BuildPropertyTreesForTesting();
DrawFrame();
gfx::Point scroll_position(10, 10);
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(scroll_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(), overflow->CurrentScrollOffset());
gfx::Vector2dF scroll_delta(10, 10);
host_impl_->ScrollBy(UpdateState(scroll_position, scroll_delta).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), overflow->CurrentScrollOffset());
overflow->test_properties()->user_scrollable_horizontal = false;
host_impl_->active_tree()->BuildPropertyTreesForTesting();
DrawFrame();
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(scroll_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), overflow->CurrentScrollOffset());
host_impl_->ScrollBy(UpdateState(scroll_position, scroll_delta).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), overflow->CurrentScrollOffset());
overflow->test_properties()->user_scrollable_vertical = false;
host_impl_->active_tree()->BuildPropertyTreesForTesting();
DrawFrame();
EXPECT_EQ(
InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(scroll_position).get(), InputHandler::WHEEL)
.thread);
EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), overflow->CurrentScrollOffset());
host_impl_->ScrollBy(UpdateState(scroll_position, scroll_delta).get());
host_impl_->ScrollEnd(EndState().get());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), scroll_layer->CurrentScrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), overflow->CurrentScrollOffset());
}
TEST_F(LayerTreeHostImplTest, ForceMainThreadScrollWithoutScrollLayer) {
SetupScrollAndContentsLayers(gfx::Size(200, 200));
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(100, 100));
host_impl_->active_tree()->BuildPropertyTreesForTesting();
ScrollNode* scroll_node =
host_impl_->active_tree()->property_trees()->scroll_tree.Node(
host_impl_->OuterViewportScrollLayer()->scroll_tree_index());
// Change the scroll node so that it no longer has an associated layer.
scroll_node->element_id = ElementId(42);
DrawFrame();
InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
BeginState(gfx::Point(25, 25)).get(), InputHandler::WHEEL);
EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion,
status.main_thread_scrolling_reasons);
}
TEST_F(CommitToPendingTreeLayerTreeHostImplTest,
AnimationSchedulingPendingTree) {
EXPECT_FALSE(host_impl_->CommitToActiveTree());
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
CreatePendingTree();
auto root_owned = LayerImpl::Create(host_impl_->pending_tree(), 1);
auto* root = root_owned.get();
host_impl_->pending_tree()->SetRootLayerForTesting(std::move(root_owned));
root->SetBounds(gfx::Size(50, 50));
root->test_properties()->force_render_surface = true;
root->SetNeedsPushProperties();
root->test_properties()->AddChild(
LayerImpl::Create(host_impl_->pending_tree(), 2));
LayerImpl* child = root->test_properties()->children[0];
child->SetBounds(gfx::Size(10, 10));
child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
child->SetDrawsContent(true);
child->SetNeedsPushProperties();
host_impl_->pending_tree()->SetElementIdsForTesting();
AddAnimatedTransformToElementWithAnimation(child->element_id(), timeline(),
10.0, 3, 0);
host_impl_->pending_tree()->BuildPropertyTreesForTesting();
EXPECT_FALSE(did_request_next_frame_);
EXPECT_FALSE(did_request_redraw_);
EXPECT_FALSE(did_request_commit_);
host_impl_->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_F(CommitToPendingTreeLayerTreeHostImplTest,
AnimationSchedulingActiveTree) {
EXPECT_FALSE(host_impl_->CommitToActiveTree());
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
host_impl_->active_tree()->SetRootLayerForTesting(
LayerImpl::Create(host_impl_->active_tree(), 1));
LayerImpl* root = *host_impl_->active_tree()->begin();
root->SetBounds(gfx::Size(50, 50));
root->test_properties()->force_render_surface = true;
root->test_properties()->AddChild(
LayerImpl::Create(host_impl_->active_tree(), 2));
LayerImpl* child = root->test_properties()->children[0];
child->SetBounds(gfx::Size(10, 10));
child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
child->SetDrawsContent(true);
host_impl_->active_tree()->SetElementIdsForTesting();
// Add a translate from 6,7 to 8,9.
TransformOperations start;
start.AppendTranslate(6.f, 7.f, 0.f);
TransformOperations end;
end.AppendTranslate(8.f, 9.f, 0.f);
AddAnimatedTransformToElementWithAnimation(child->element_id(), timeline(),
4.0, start, end);
host_impl_->active_tree()->BuildPropertyTreesForTesting();
base::TimeTicks now = base::TimeTicks::Now();
host_impl_->WillBeginImplFrame(
viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2, now));
// TODO(crbug.com/551134): 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_F(LayerTreeHostImplTest, AnimationSchedulingCommitToActiveTree) {
FakeImplTaskRunnerProvider provider(nullptr);
CreateHostImplWithTaskRunnerProvider(DefaultSettings(),
CreateLayerTreeFrameSink(), &provider);
EXPECT_TRUE(host_impl_->CommitToActiveTree());
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
auto root_owned = LayerImpl::Create(host_impl_->active_tree(), 1);
auto* root = root_owned.get();
host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_owned));
root->SetBounds(gfx::Size(50, 50));
auto child_owned = LayerImpl::Create(host_impl_->active_tree(), 2);
auto* child = child_owned.get();
root->test_properties()->AddChild(std::move(child_owned));
child->SetBounds(gfx::Size(10, 10));
child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
child->SetDrawsContent(true);
host_impl_->active_tree()->SetElementIdsForTesting();
AddAnimatedTransformToElementWithAnimation(child->element_id(), timeline(),
10.0, 3, 0);
// Set up the property trees so that UpdateDrawProperties will work in
// CommitComplete below.
RenderSurfaceList list;
LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
root, gfx::Size(50, 50), &list);
LayerTreeHostCommon::CalculateDrawPropertiesForTesting(&inputs);
EXPECT_FALSE(did_request_next_frame_);
EXPECT_FALSE(did_request_redraw_);
EXPECT_FALSE(did_request_commit_);
host_impl_->CommitComplete();
// Animations on the active tree should be started and ticked, and a new frame
// should be requested to continue ticking them.
EXPECT_TRUE(did_request_next_frame_);
EXPECT_TRUE(did_request_redraw_);
EXPECT_FALSE(did_request_commit_);
// Delete the LayerTreeHostImpl before the TaskRunnerProvider goes away.
host_impl_->ReleaseLayerTreeFrameSink();
host_impl_ = nullptr;
}
TEST_F(LayerTreeHostImplTest, AnimationSchedulingOnLayerDestruction) {
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
host_impl_->active_tree()->SetRootLayerForTesting(
LayerImpl::Create(host_impl_->active_tree(), 1));
LayerImpl* root = *host_impl_->active_tree()->begin();
root->SetBounds(gfx::Size(50, 50));
root->test_properties()->AddChild(
LayerImpl::Create(host_impl_->active_tree(), 2));
LayerImpl* child = root->test_properties()->children[0];
child->SetBounds(gfx::Size(10, 10));
child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
child->SetDrawsContent(true);
host_impl_->active_tree()->SetElementIdsForTesting();
// Add a translate animation.
TransformOperations start;
start.AppendTranslate(6.f, 7.f, 0.f);
TransformOperations end;
end.AppendTranslate(8.f, 9.f, 0.f);
AddAnimatedTransformToElementWithAnimation(child->element_id(), timeline(),
4.0, start, end);
host_impl_->active_tree()->BuildPropertyTreesForTesting();
base::TimeTicks now = base::TimeTicks::Now();
host_impl_->WillBeginImplFrame(
viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2, now));
EXPECT_TRUE(did_request_next_frame_);
did_request_next_frame_ = 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_);
did_request_next_frame_ = 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_);
did_request_next_frame_ = false;
// Destroy layer, unregister animation target (element).
child->test_properties()->parent = nullptr;
root->test_properties()->RemoveChild(child);
child = nullptr;
// Doing Animate() doesn't request another frame after the current one.
host_impl_->Animate();
EXPECT_FALSE(did_request_next_frame_);
host_impl_->Animate();
EXPECT_FALSE(did_request_next_frame_);
}
class MissingTilesLayer : public LayerImpl {
public:
MissingTilesLayer(LayerTreeImpl* layer_tree_impl, int id)
: LayerImpl(layer_tree_impl, id), has_missing_tiles_(true) {}
void set_has_missing_tiles(bool has_missing_tiles) {
has_missing_tiles_ = has_missing_tiles;
}
void AppendQuads(viz::RenderPass* render_pass,
AppendQuadsData* append_quads_data) override {
append_quads_data->num_missing_tiles += has_missing_tiles_;
}
private:
bool has_missing_tiles_;
};
TEST_F(LayerTreeHostImplTest, ImplPinchZoom) {
LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
host_impl_->active_tree()->BuildPropertyTreesForTesting();
host_impl_->active_tree()->SetDeviceViewportSize(gfx::Size(50, 50));
DrawFrame();
EXPECT_EQ(scroll_layer, host_impl_->InnerViewportScrollLayer());
LayerImpl* container_layer = host_impl_->InnerViewportContainerLayer();
EXPECT_EQ(gfx::Size(50, 50), container_layer->bounds());
float min_page_scale = 1.f, max_page_scale = 4.f;
float page_scale_factor = 1.f;
// The impl-based pinch zoom should adjust the max scroll position.
{
host_impl_->active_tree()->PushPageScaleFromMainThread(
page_scale_factor, min_page_scale, max_page_scale);
host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);
SetScrollOffsetDelta(scroll_layer, gfx::Vector2d());
float page_scale_delta = 2.f;
host_impl_->ScrollBegin(BeginState(gfx::Point(50, 50)).get(),
InputHandler::TOUCHSCREEN);
host_impl_->PinchGestureBegin();
host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(50, 50));
host_impl_->PinchGestureEnd(gfx::Point(50, 50), true);
host_impl_->ScrollEnd(EndState().get());
EXPECT_FALSE(did_request_next_frame_);
EXPECT_TRUE(did_request_redraw_);
EXPECT_TRUE(did_request_commit_);
EXPECT_EQ(gfx::Size(50, 50), container_layer->bounds());
std::unique_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
EXPECT_EQ(gfx::ScrollOffset(75.0, 75.0).ToString(),
scroll_layer->MaxScrollOffset().ToString());
}
// Scrolling after a pinch gesture should always be in local space. The
// scroll deltas have the page scale factor applied.
{
host_impl_->active_tree()->PushPageScaleFromMainThread(
page_scale_factor, min_page_scale, max_page_scale);
host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);
SetScrollOffsetDelta(scroll_layer, gfx::Vector2d());
float page_scale_delta = 2.f;
host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
InputHandler::TOUCHSCREEN);
host_impl_->PinchGestureBegin();
host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point());
host_impl_->PinchGestureEnd(gfx::Point(), true);
host_impl_->ScrollEnd(EndState().get());
gfx::Vector2d scroll_delta(0, 10);
EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
host_impl_
->ScrollBegin(BeginState(gfx::Point(5, 5)).get(),
InputHandler::WHEEL)
.thread);
host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
host_impl_->ScrollEnd(EndState().get());
std::unique_ptr<ScrollAndScaleSet> scroll_info =
host_impl_->ProcessScrollDeltas();
EXPECT_TRUE(ScrollInfoContains(
*scroll_info.get(), scroll_layer->element_id(),
gfx::ScrollOffset(0, scroll_delta.y() / page_scale_delta)));
}
}
TEST_F(LayerTreeHostImplTest, ViewportScrollbarGeometry) {
// Tests for correct behavior of solid color scrollbars on unscrollable pages
// under tricky fractional scale/size issues.
// Nexus 6 viewport size.
const gfx::Size viewport_size(412, 604);
// The content size of a non-scrollable page we'd expect given Android's
// behavior of a 980px layout width on non-mobile pages (often ceiled to 981
// due to fractions resulting from DSF). Due to floating point error,
// viewport_size.height() / minimum_scale ~= 1438.165 < 1439. Ensure we snap
// correctly and err on the side of not showing the scrollbars.
const gfx::Size content_size(981, 1439);
const float minimum_scale =
viewport_size.width() / static_cast<float>(content_size.width());
// Since the page is unscrollable, the outer viewport matches the content
// size.
const gfx::Size outer_viewport_size = content_size;
SolidColorScrollbarLayerImpl* v_scrollbar;
SolidColorScrollbarLayerImpl* h_scrollbar;
// Setup
{
LayerTreeSettings settings = DefaultSettings();
CreateHostImpl(settings, CreateLayerTreeFrameSink());
LayerTreeImpl* active_tree = host_impl_->active_tree();
active_tree->PushPageScaleFromMainThread(1.f, minimum_scale, 4.f);
CreateBasicVirtualViewportLayers(viewport_size, content_size);
// When Chrome on Android loads a non-mobile page, it resizes the main
// frame (outer viewport) such that it matches the width of the content,
// preventing horizontal scrolling. Replicate that behavior here.
host_impl_->OuterViewportScrollLayer()->SetScrollable(outer_viewport_size);
host_impl_->OuterViewportScrollLayer()->SetHitTestable(true);
LayerImpl* outer_clip =
host_impl_->OuterViewportScrollLayer()->test_properties()->parent;
outer_clip->SetBounds(outer_viewport_size);
// Add scrollbars. They will always exist - even if unscrollable - but their
// visibility will be determined by whether the content can be scrolled.
{
std::unique_ptr<SolidColorScrollbarLayerImpl> v_scrollbar_unique =
SolidColorScrollbarLayerImpl::Create(active_tree, 400, VERTICAL, 10,
0, false, true);
std::unique_ptr<SolidColorScrollbarLayerImpl> h_scrollbar_unique =
SolidColorScrollbarLayerImpl::Create(active_tree, 401, HORIZONTAL, 10,
0, false, true);
v_scrollbar = v_scrollbar_unique.get();
h_scrollbar = h_scrollbar_unique.get();
LayerImpl* scroll = active_tree->OuterViewportScrollLayer();
LayerImpl* root = active_tree->InnerViewportContainerLayer();
v_scrollbar_unique->SetScrollElementId(scroll->element_id());
h_scrollbar_unique->SetScrollElementId(scroll->element_id());
root->test_properties()->AddChild(std::move(v_scrollbar_unique));
root->test_properties()->AddChild(std::move(h_scrollbar_unique));
}
host_impl_->active_tree()->BuildPropertyTreesForTesting();
host_impl_->active_tree()->DidBecomeActive();
}
// Zoom out to the minimum scale. The scrollbars shoud not be scrollable.
host_impl_->active_tree()->SetPageScaleOnActiveTree(0.f);
EXPECT_FALSE(v_scrollbar->CanScrollOrientation());
EXPECT_FALSE(h_scrollbar->CanScrollOrientation());
// Zoom in a little and confirm that they're now scrollable.
host_impl_->active_tree()->SetPageScaleOnActiveTree(minimum_scale * 1.05f);
EXPECT_TRUE(v_scrollbar->CanScrollOrientation());
EXPECT_TRUE(h_scrollbar->CanScrollOrientation());
}
TEST_F(LayerTreeHostImplTest, ViewportScrollOrder) {
LayerTreeSettings settings = DefaultSettings();
CreateHostImpl(settings, CreateLayerTreeFrameSink());
host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.25f, 4.f);
const gfx::Size content_size(1000, 1000);
const gfx::Size viewport_size(500, 500);
CreateBasicVirtualViewportLayers(viewport_size, content_size);