| // Copyright 2013 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 "content/renderer/render_widget.h" |
| |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/macros.h" |
| #include "base/optional.h" |
| #include "base/run_loop.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/task_environment.h" |
| #include "base/unguessable_token.h" |
| #include "build/build_config.h" |
| #include "cc/layers/solid_color_layer.h" |
| #include "cc/test/fake_layer_tree_frame_sink.h" |
| #include "cc/trees/layer_tree_host.h" |
| #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h" |
| #include "content/common/frame_replication_state.h" |
| #include "content/common/input_messages.h" |
| #include "content/common/view_messages.h" |
| #include "content/common/visual_properties.h" |
| #include "content/common/widget_messages.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/fake_render_widget_host.h" |
| #include "content/public/test/mock_render_thread.h" |
| #include "content/renderer/render_frame_proxy.h" |
| #include "content/renderer/render_process.h" |
| #include "content/renderer/render_widget_delegate.h" |
| #include "content/renderer/render_widget_screen_metrics_emulator.h" |
| #include "content/test/fake_compositor_dependencies.h" |
| #include "ipc/ipc_test_sink.h" |
| #include "mojo/public/cpp/bindings/pending_associated_receiver.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/input/synthetic_web_input_event_builders.h" |
| #include "third_party/blink/public/common/input/web_coalesced_input_event.h" |
| #include "third_party/blink/public/mojom/input/input_handler.mojom.h" |
| #include "third_party/blink/public/mojom/page/widget.mojom-test-utils.h" |
| #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h" |
| #include "third_party/blink/public/web/web_device_emulation_params.h" |
| #include "third_party/blink/public/web/web_external_widget.h" |
| #include "third_party/blink/public/web/web_external_widget_client.h" |
| #include "third_party/blink/public/web/web_frame_widget.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "third_party/blink/public/web/web_local_frame_client.h" |
| #include "third_party/blink/public/web/web_view.h" |
| #include "third_party/blink/public/web/web_view_client.h" |
| #include "ui/base/cursor/cursor.h" |
| #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/blink/web_input_event_traits.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| using testing::_; |
| |
| namespace blink { |
| |
| bool operator==(const InputHandlerProxy::DidOverscrollParams& lhs, |
| const InputHandlerProxy::DidOverscrollParams& rhs) { |
| return lhs.accumulated_overscroll == rhs.accumulated_overscroll && |
| lhs.latest_overscroll_delta == rhs.latest_overscroll_delta && |
| lhs.current_fling_velocity == rhs.current_fling_velocity && |
| lhs.causal_event_viewport_point == rhs.causal_event_viewport_point && |
| lhs.overscroll_behavior == rhs.overscroll_behavior; |
| } |
| |
| } // namespace blink |
| |
| namespace cc { |
| class AnimationHost; |
| } |
| |
| namespace content { |
| |
| namespace { |
| |
| const char* EVENT_LISTENER_RESULT_HISTOGRAM = "Event.PassiveListeners"; |
| |
| // Keep in sync with enum defined in |
| // RenderWidgetInputHandler::LogPassiveEventListenersUma. |
| enum { |
| PASSIVE_LISTENER_UMA_ENUM_PASSIVE, |
| PASSIVE_LISTENER_UMA_ENUM_UNCANCELABLE, |
| PASSIVE_LISTENER_UMA_ENUM_SUPPRESSED, |
| PASSIVE_LISTENER_UMA_ENUM_CANCELABLE, |
| PASSIVE_LISTENER_UMA_ENUM_CANCELABLE_AND_CANCELED, |
| PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_FLING, |
| PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_MAIN_THREAD_RESPONSIVENESS_DEPRECATED, |
| PASSIVE_LISTENER_UMA_ENUM_COUNT |
| }; |
| |
| // Since std::unique_ptr isn't copyable we can't use the |
| // MockCallback template. |
| class MockHandledEventCallback { |
| public: |
| MockHandledEventCallback() = default; |
| MOCK_METHOD4_T(Run, |
| void(blink::mojom::InputEventResultState, |
| const ui::LatencyInfo&, |
| blink::InputHandlerProxy::DidOverscrollParams*, |
| base::Optional<cc::TouchAction>)); |
| |
| blink::WebWidget::HandledEventCallback GetCallback() { |
| return base::BindOnce(&MockHandledEventCallback::HandleCallback, |
| base::Unretained(this)); |
| } |
| |
| private: |
| void HandleCallback( |
| blink::mojom::InputEventResultState ack_state, |
| const ui::LatencyInfo& latency_info, |
| std::unique_ptr<blink::InputHandlerProxy::DidOverscrollParams> overscroll, |
| base::Optional<cc::TouchAction> touch_action) { |
| Run(ack_state, latency_info, overscroll.get(), touch_action); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(MockHandledEventCallback); |
| }; |
| |
| class MockWebExternalWidgetClient : public blink::WebExternalWidgetClient { |
| public: |
| MockWebExternalWidgetClient() = default; |
| |
| // WebExternalWidgetClient implementation. |
| MOCK_METHOD1(DidResize, void(const gfx::Size& size)); |
| MOCK_METHOD0(DispatchBufferedTouchEvents, blink::WebInputEventResult()); |
| MOCK_METHOD1( |
| HandleInputEvent, |
| blink::WebInputEventResult(const blink::WebCoalescedInputEvent&)); |
| MOCK_METHOD1(RequestNewLayerTreeFrameSink, void(LayerTreeFrameSinkCallback)); |
| MOCK_METHOD0(DidCommitAndDrawCompositorFrame, void()); |
| MOCK_METHOD1(WillHandleGestureEvent, bool(const blink::WebGestureEvent&)); |
| MOCK_METHOD4(DidHandleGestureScrollEvent, |
| void(const blink::WebGestureEvent& gesture_event, |
| const gfx::Vector2dF& unused_delta, |
| const cc::OverscrollBehavior& overscroll_behavior, |
| bool event_processed)); |
| |
| // Because we mock DispatchBufferedTouchEvents indicate we have support. |
| bool SupportsBufferedTouchEvents() override { return true; } |
| }; |
| |
| } // namespace |
| |
| class InteractiveRenderWidget : public RenderWidget { |
| public: |
| explicit InteractiveRenderWidget(CompositorDependencies* compositor_deps) |
| : RenderWidget(++next_routing_id_, |
| compositor_deps, |
| /*is_hidden=*/false, |
| /*never_composited=*/false) {} |
| |
| void Init(blink::WebWidget* widget, const ScreenInfo& screen_info) { |
| Initialize(base::NullCallback(), widget, screen_info); |
| } |
| |
| using RenderWidget::Close; |
| |
| void SendInputEvent(const blink::WebInputEvent& event, |
| blink::WebWidget::HandledEventCallback callback) { |
| GetWebWidget()->ProcessInputEventSynchronously( |
| blink::WebCoalescedInputEvent(event.Clone(), {}, {}, ui::LatencyInfo()), |
| std::move(callback)); |
| } |
| |
| IPC::TestSink* sink() { return &sink_; } |
| |
| const viz::LocalSurfaceIdAllocation& local_surface_id_allocation_from_parent() |
| const { |
| return local_surface_id_allocation_from_parent_; |
| } |
| |
| bool OverscrollGestureEvent(const blink::WebGestureEvent& event) { |
| if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollUpdate) { |
| GetWebWidget()->DidOverscrollForTesting( |
| gfx::Vector2dF(event.data.scroll_update.delta_x, |
| event.data.scroll_update.delta_y), |
| gfx::Vector2dF(event.data.scroll_update.delta_x, |
| event.data.scroll_update.delta_y), |
| event.PositionInWidget(), |
| gfx::Vector2dF(event.data.scroll_update.velocity_x, |
| event.data.scroll_update.velocity_y)); |
| return true; |
| } |
| return false; |
| } |
| |
| protected: |
| bool Send(IPC::Message* msg) override { |
| sink_.OnMessageReceived(*msg); |
| delete msg; |
| return true; |
| } |
| |
| private: |
| IPC::TestSink sink_; |
| static int next_routing_id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(InteractiveRenderWidget); |
| }; |
| |
| int InteractiveRenderWidget::next_routing_id_ = 0; |
| |
| class RenderWidgetUnittest : public testing::Test { |
| public: |
| void SetUp() override { |
| mojo::AssociatedRemote<blink::mojom::FrameWidget> frame_widget_remote; |
| mojo::PendingAssociatedReceiver<blink::mojom::FrameWidget> |
| frame_widget_receiver = |
| frame_widget_remote |
| .BindNewEndpointAndPassDedicatedReceiverForTesting(); |
| |
| mojo::AssociatedRemote<blink::mojom::FrameWidgetHost> frame_widget_host; |
| mojo::PendingAssociatedReceiver<blink::mojom::FrameWidgetHost> |
| frame_widget_host_receiver = |
| frame_widget_host |
| .BindNewEndpointAndPassDedicatedReceiverForTesting(); |
| |
| mojo::AssociatedRemote<blink::mojom::Widget> widget_remote; |
| mojo::PendingAssociatedReceiver<blink::mojom::Widget> widget_receiver = |
| widget_remote.BindNewEndpointAndPassDedicatedReceiverForTesting(); |
| |
| mojo::AssociatedRemote<blink::mojom::WidgetHost> widget_host; |
| mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> |
| widget_host_receiver = |
| widget_host.BindNewEndpointAndPassDedicatedReceiverForTesting(); |
| |
| web_view_ = blink::WebView::Create(/*client=*/&web_view_client_, |
| /*is_hidden=*/false, |
| /*compositing_enabled=*/true, nullptr, |
| mojo::NullAssociatedReceiver()); |
| widget_ = std::make_unique<InteractiveRenderWidget>(&compositor_deps_); |
| web_local_frame_ = blink::WebLocalFrame::CreateMainFrame( |
| web_view_, &web_frame_client_, nullptr, |
| base::UnguessableToken::Create(), nullptr); |
| web_frame_widget_ = blink::WebFrameWidget::CreateForMainFrame( |
| widget_.get(), web_local_frame_, frame_widget_host.Unbind(), |
| std::move(frame_widget_receiver), widget_host.Unbind(), |
| std::move(widget_receiver)); |
| widget_->Init(web_frame_widget_, ScreenInfo()); |
| web_view_->DidAttachLocalMainFrame(); |
| } |
| |
| void TearDown() override { |
| widget_->Close(std::move(widget_)); |
| web_local_frame_ = nullptr; |
| web_frame_widget_ = nullptr; |
| web_view_ = nullptr; |
| // RenderWidget::Close() posts some destruction. Don't leak them. |
| base::RunLoop loop; |
| compositor_deps_.GetCleanupTaskRunner()->PostTask(FROM_HERE, |
| loop.QuitClosure()); |
| loop.Run(); |
| } |
| |
| InteractiveRenderWidget* widget() const { return widget_.get(); } |
| |
| blink::WebFrameWidget* frame_widget() const { return web_frame_widget_; } |
| |
| const base::HistogramTester& histogram_tester() const { |
| return histogram_tester_; |
| } |
| |
| cc::FakeLayerTreeFrameSink* GetFrameSink() { |
| return compositor_deps_.last_created_frame_sink(); |
| } |
| |
| private: |
| base::test::TaskEnvironment task_environment_; |
| RenderProcess render_process_; |
| MockRenderThread render_thread_; |
| blink::WebViewClient web_view_client_; |
| blink::WebView* web_view_; |
| blink::WebLocalFrame* web_local_frame_; |
| blink::WebFrameWidget* web_frame_widget_; |
| blink::WebLocalFrameClient web_frame_client_; |
| FakeCompositorDependencies compositor_deps_; |
| std::unique_ptr<InteractiveRenderWidget> widget_; |
| base::HistogramTester histogram_tester_; |
| }; |
| |
| class RenderWidgetExternalWidgetUnittest : public testing::Test { |
| public: |
| void SetUp() override { |
| mojo::PendingAssociatedRemote<blink::mojom::WidgetHost> widget_host_remote; |
| mojo::PendingAssociatedReceiver<blink::mojom::Widget> widget_receiver; |
| std::tie(widget_host_remote, widget_receiver) = |
| render_widget_host_.BindNewWidgetInterfaces(); |
| external_web_widget_ = blink::WebExternalWidget::Create( |
| &mock_web_external_widget_client_, blink::WebURL(), |
| std::move(widget_host_remote), std::move(widget_receiver)); |
| |
| widget_ = std::make_unique<InteractiveRenderWidget>(&compositor_deps_); |
| widget_->Init(external_web_widget_.get(), ScreenInfo()); |
| } |
| |
| void TearDown() override { |
| widget_->Close(std::move(widget_)); |
| // RenderWidget::Close() posts some destruction. Don't leak them. |
| base::RunLoop loop; |
| compositor_deps_.GetCleanupTaskRunner()->PostTask(FROM_HERE, |
| loop.QuitClosure()); |
| loop.Run(); |
| } |
| |
| InteractiveRenderWidget* widget() const { return widget_.get(); } |
| |
| MockWebExternalWidgetClient* mock_web_external_widget_client() { |
| return &mock_web_external_widget_client_; |
| } |
| |
| blink::WebExternalWidget* external_web_widget() { |
| return external_web_widget_.get(); |
| } |
| |
| FakeRenderWidgetHost* render_widget_host() { return &render_widget_host_; } |
| |
| const base::HistogramTester& histogram_tester() const { |
| return histogram_tester_; |
| } |
| |
| cc::FakeLayerTreeFrameSink* GetFrameSink() { |
| return compositor_deps_.last_created_frame_sink(); |
| } |
| |
| private: |
| base::test::TaskEnvironment task_environment_; |
| RenderProcess render_process_; |
| MockRenderThread render_thread_; |
| FakeRenderWidgetHost render_widget_host_; |
| FakeCompositorDependencies compositor_deps_; |
| MockWebExternalWidgetClient mock_web_external_widget_client_; |
| std::unique_ptr<blink::WebExternalWidget> external_web_widget_; |
| std::unique_ptr<InteractiveRenderWidget> widget_; |
| base::HistogramTester histogram_tester_; |
| }; |
| |
| class SetCursorInterceptor |
| : public blink::mojom::WidgetHostInterceptorForTesting { |
| public: |
| explicit SetCursorInterceptor(FakeRenderWidgetHost* render_widget_host) |
| : render_widget_host_(render_widget_host) { |
| render_widget_host_->widget_host_receiver_for_testing().SwapImplForTesting( |
| this); |
| } |
| ~SetCursorInterceptor() override = default; |
| |
| WidgetHost* GetForwardingInterface() override { return render_widget_host_; } |
| |
| void SetCursor(const ui::Cursor& cursor) override { set_cursor_count_++; } |
| |
| int set_cursor_count() { return set_cursor_count_; } |
| |
| private: |
| FakeRenderWidgetHost* render_widget_host_; |
| int set_cursor_count_ = 0; |
| }; |
| |
| TEST_F(RenderWidgetExternalWidgetUnittest, CursorChange) { |
| ui::Cursor cursor; |
| |
| auto set_cursor_interceptor = |
| std::make_unique<SetCursorInterceptor>(render_widget_host()); |
| widget()->GetWebWidget()->SetCursor(cursor); |
| render_widget_host()->widget_host_receiver_for_testing().FlushForTesting(); |
| EXPECT_EQ(set_cursor_interceptor->set_cursor_count(), 1); |
| |
| widget()->GetWebWidget()->SetCursor(cursor); |
| render_widget_host()->widget_host_receiver_for_testing().FlushForTesting(); |
| EXPECT_EQ(set_cursor_interceptor->set_cursor_count(), 1); |
| |
| EXPECT_CALL(*mock_web_external_widget_client(), HandleInputEvent(_)) |
| .WillOnce(::testing::Return(blink::WebInputEventResult::kNotHandled)); |
| widget()->SendInputEvent(blink::SyntheticWebMouseEventBuilder::Build( |
| blink::WebInputEvent::Type::kMouseLeave), |
| base::DoNothing()); |
| render_widget_host()->widget_host_receiver_for_testing().FlushForTesting(); |
| EXPECT_EQ(set_cursor_interceptor->set_cursor_count(), 1); |
| |
| widget()->GetWebWidget()->SetCursor(cursor); |
| render_widget_host()->widget_host_receiver_for_testing().FlushForTesting(); |
| EXPECT_EQ(set_cursor_interceptor->set_cursor_count(), 2); |
| } |
| |
| TEST_F(RenderWidgetExternalWidgetUnittest, EventOverscroll) { |
| ON_CALL(*mock_web_external_widget_client(), WillHandleGestureEvent(_)) |
| .WillByDefault(testing::Invoke( |
| widget(), &InteractiveRenderWidget::OverscrollGestureEvent)); |
| |
| EXPECT_CALL(*mock_web_external_widget_client(), HandleInputEvent(_)) |
| .WillRepeatedly( |
| ::testing::Return(blink::WebInputEventResult::kNotHandled)); |
| |
| blink::WebGestureEvent scroll( |
| blink::WebInputEvent::Type::kGestureScrollUpdate, |
| blink::WebInputEvent::kNoModifiers, ui::EventTimeForNow()); |
| scroll.SetPositionInWidget(gfx::PointF(-10, 0)); |
| scroll.data.scroll_update.delta_y = 10; |
| MockHandledEventCallback handled_event; |
| |
| blink::InputHandlerProxy::DidOverscrollParams expected_overscroll; |
| expected_overscroll.latest_overscroll_delta = gfx::Vector2dF(0, 10); |
| expected_overscroll.accumulated_overscroll = gfx::Vector2dF(0, 10); |
| expected_overscroll.causal_event_viewport_point = gfx::PointF(-10, 0); |
| expected_overscroll.current_fling_velocity = gfx::Vector2dF(); |
| |
| // Overscroll notifications received while handling an input event should |
| // be bundled with the event ack IPC. |
| EXPECT_CALL(handled_event, Run(blink::mojom::InputEventResultState::kConsumed, |
| _, testing::Pointee(expected_overscroll), _)) |
| .Times(1); |
| |
| widget()->SendInputEvent(scroll, handled_event.GetCallback()); |
| } |
| |
| TEST_F(RenderWidgetExternalWidgetUnittest, RenderWidgetInputEventUmaMetrics) { |
| blink::SyntheticWebTouchEvent touch; |
| touch.PressPoint(10, 10); |
| touch.touch_start_or_first_touch_move = true; |
| |
| EXPECT_CALL(*mock_web_external_widget_client(), HandleInputEvent(_)) |
| .Times(5) |
| .WillRepeatedly( |
| ::testing::Return(blink::WebInputEventResult::kNotHandled)); |
| |
| EXPECT_CALL(*mock_web_external_widget_client(), DispatchBufferedTouchEvents()) |
| .Times(5) |
| .WillRepeatedly( |
| ::testing::Return(blink::WebInputEventResult::kNotHandled)); |
| |
| widget()->SendInputEvent(touch, base::DoNothing()); |
| histogram_tester().ExpectBucketCount(EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_CANCELABLE, 1); |
| |
| touch.dispatch_type = blink::WebInputEvent::DispatchType::kEventNonBlocking; |
| widget()->SendInputEvent(touch, base::DoNothing()); |
| histogram_tester().ExpectBucketCount(EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_UNCANCELABLE, |
| 1); |
| |
| touch.dispatch_type = |
| blink::WebInputEvent::DispatchType::kListenersNonBlockingPassive; |
| widget()->SendInputEvent(touch, base::DoNothing()); |
| histogram_tester().ExpectBucketCount(EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_PASSIVE, 1); |
| |
| touch.dispatch_type = |
| blink::WebInputEvent::DispatchType::kListenersForcedNonBlockingDueToFling; |
| widget()->SendInputEvent(touch, base::DoNothing()); |
| histogram_tester().ExpectBucketCount( |
| EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_FLING, 1); |
| |
| touch.MovePoint(0, 10, 10); |
| touch.touch_start_or_first_touch_move = true; |
| touch.dispatch_type = |
| blink::WebInputEvent::DispatchType::kListenersForcedNonBlockingDueToFling; |
| widget()->SendInputEvent(touch, base::DoNothing()); |
| histogram_tester().ExpectBucketCount( |
| EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_FLING, 2); |
| |
| EXPECT_CALL(*mock_web_external_widget_client(), HandleInputEvent(_)) |
| .WillOnce(::testing::Return(blink::WebInputEventResult::kNotHandled)); |
| EXPECT_CALL(*mock_web_external_widget_client(), DispatchBufferedTouchEvents()) |
| .WillOnce( |
| ::testing::Return(blink::WebInputEventResult::kHandledSuppressed)); |
| touch.dispatch_type = blink::WebInputEvent::DispatchType::kBlocking; |
| widget()->SendInputEvent(touch, base::DoNothing()); |
| histogram_tester().ExpectBucketCount(EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_SUPPRESSED, 1); |
| |
| EXPECT_CALL(*mock_web_external_widget_client(), HandleInputEvent(_)) |
| .WillOnce(::testing::Return(blink::WebInputEventResult::kNotHandled)); |
| EXPECT_CALL(*mock_web_external_widget_client(), DispatchBufferedTouchEvents()) |
| .WillOnce( |
| ::testing::Return(blink::WebInputEventResult::kHandledApplication)); |
| touch.dispatch_type = blink::WebInputEvent::DispatchType::kBlocking; |
| widget()->SendInputEvent(touch, base::DoNothing()); |
| histogram_tester().ExpectBucketCount( |
| EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_CANCELABLE_AND_CANCELED, 1); |
| } |
| |
| // Ensures that the compositor thread gets sent the gesture event & overscroll |
| // amount for an overscroll initiated by a touchpad. |
| TEST_F(RenderWidgetExternalWidgetUnittest, SendElasticOverscrollForTouchpad) { |
| blink::WebGestureEvent scroll( |
| blink::WebInputEvent::Type::kGestureScrollUpdate, |
| blink::WebInputEvent::kNoModifiers, ui::EventTimeForNow(), |
| blink::WebGestureDevice::kTouchpad); |
| scroll.SetPositionInWidget(gfx::PointF(-10, 0)); |
| scroll.data.scroll_update.delta_y = 10; |
| |
| // We only really care that DidHandleGestureScrollEvent was called; we |
| // therefore suppress the warning for the call to |
| // mock_webwidget()->HandleInputEvent(). |
| EXPECT_CALL(*mock_web_external_widget_client(), |
| DidHandleGestureScrollEvent(_, _, _, _)) |
| .Times(1); |
| EXPECT_CALL(*mock_web_external_widget_client(), HandleInputEvent(_)) |
| .Times(testing::AnyNumber()); |
| |
| widget()->SendInputEvent(scroll, base::DoNothing()); |
| } |
| |
| // Ensures that the compositor thread gets sent the gesture event & overscroll |
| // amount for an overscroll initiated by a touchscreen. |
| TEST_F(RenderWidgetExternalWidgetUnittest, |
| SendElasticOverscrollForTouchscreen) { |
| blink::WebGestureEvent scroll( |
| blink::WebInputEvent::Type::kGestureScrollUpdate, |
| blink::WebInputEvent::kNoModifiers, ui::EventTimeForNow(), |
| blink::WebGestureDevice::kTouchscreen); |
| scroll.SetPositionInWidget(gfx::PointF(-10, 0)); |
| scroll.data.scroll_update.delta_y = 10; |
| |
| // We only really care that DidHandleGestureScrollEvent was called; we |
| // therefore suppress the warning for the call to |
| // mock_webwidget()->HandleInputEvent(). |
| EXPECT_CALL(*mock_web_external_widget_client(), |
| DidHandleGestureScrollEvent(_, _, _, _)) |
| .Times(1); |
| EXPECT_CALL(*mock_web_external_widget_client(), HandleInputEvent(_)) |
| .Times(testing::AnyNumber()); |
| |
| widget()->SendInputEvent(scroll, base::DoNothing()); |
| } |
| |
| // Tests that if a RenderWidget is auto-resized, it requests a new |
| // viz::LocalSurfaceId to be allocated on the impl thread. |
| TEST_F(RenderWidgetUnittest, AutoResizeAllocatedLocalSurfaceId) { |
| viz::ParentLocalSurfaceIdAllocator allocator; |
| |
| // Enable auto-resize. |
| content::VisualProperties visual_properties; |
| visual_properties.auto_resize_enabled = true; |
| visual_properties.min_size_for_auto_resize = gfx::Size(100, 100); |
| visual_properties.max_size_for_auto_resize = gfx::Size(200, 200); |
| allocator.GenerateId(); |
| visual_properties.local_surface_id_allocation = |
| allocator.GetCurrentLocalSurfaceIdAllocation(); |
| { |
| WidgetMsg_UpdateVisualProperties msg(widget()->routing_id(), |
| visual_properties); |
| widget()->OnMessageReceived(msg); |
| } |
| EXPECT_EQ(allocator.GetCurrentLocalSurfaceIdAllocation(), |
| widget()->local_surface_id_allocation_from_parent()); |
| EXPECT_FALSE(widget() |
| ->layer_tree_host() |
| ->new_local_surface_id_request_for_testing()); |
| |
| constexpr gfx::Size size(200, 200); |
| widget()->DidAutoResize(size); |
| EXPECT_EQ(allocator.GetCurrentLocalSurfaceIdAllocation(), |
| widget()->local_surface_id_allocation_from_parent()); |
| EXPECT_TRUE(widget() |
| ->layer_tree_host() |
| ->new_local_surface_id_request_for_testing()); |
| } |
| |
| class StubRenderWidgetDelegate : public RenderWidgetDelegate { |
| public: |
| void SetActiveForWidget(bool active) override {} |
| bool SupportsMultipleWindowsForWidget() override { return true; } |
| bool ShouldAckSyntheticInputImmediately() override { return true; } |
| void ApplyAutoResizeLimitsForWidget(const gfx::Size& min_size, |
| const gfx::Size& max_size) override {} |
| void DisableAutoResizeForWidget() override {} |
| void ScrollFocusedNodeIntoViewForWidget() override {} |
| void DidReceiveSetFocusEventForWidget() override {} |
| void DidCommitCompositorFrameForWidget() override {} |
| void DidCompletePageScaleAnimationForWidget() override {} |
| void ResizeWebWidgetForWidget(const gfx::Size& main_frame_widget_size, |
| const gfx::Size& visible_viewport_size, |
| cc::BrowserControlsParams) override {} |
| void SetScreenMetricsEmulationParametersForWidget( |
| bool enabled, |
| const blink::WebDeviceEmulationParams& params) override {} |
| }; |
| |
| // Tests that the value of VisualProperties::is_pinch_gesture_active is |
| // propagated to the LayerTreeHost when properties are synced for subframes. |
| TEST_F(RenderWidgetUnittest, ActivePinchGestureUpdatesLayerTreeHostSubFrame) { |
| cc::LayerTreeHost* layer_tree_host = widget()->layer_tree_host(); |
| EXPECT_FALSE(layer_tree_host->is_external_pinch_gesture_active_for_testing()); |
| content::VisualProperties visual_properties; |
| |
| // Sync visual properties on a child RenderWidget. |
| visual_properties.is_pinch_gesture_active = true; |
| { |
| WidgetMsg_UpdateVisualProperties msg(widget()->routing_id(), |
| visual_properties); |
| widget()->OnMessageReceived(msg); |
| } |
| // We expect the |is_pinch_gesture_active| value to propagate to the |
| // LayerTreeHost for sub-frames. Since GesturePinch events are handled |
| // directly in the main-frame's layer tree (and only there), information about |
| // whether or not we're in a pinch gesture must be communicated separately to |
| // sub-frame layer trees, via OnUpdateVisualProperties. This information |
| // is required to allow sub-frame compositors to throttle rastering while |
| // pinch gestures are active. |
| EXPECT_TRUE(layer_tree_host->is_external_pinch_gesture_active_for_testing()); |
| visual_properties.is_pinch_gesture_active = false; |
| { |
| WidgetMsg_UpdateVisualProperties msg(widget()->routing_id(), |
| visual_properties); |
| widget()->OnMessageReceived(msg); |
| } |
| EXPECT_FALSE(layer_tree_host->is_external_pinch_gesture_active_for_testing()); |
| } |
| |
| // Verify desktop memory limit calculations. |
| #if !defined(OS_ANDROID) |
| TEST(RenderWidgetTest, IgnoreGivenMemoryPolicy) { |
| auto policy = RenderWidget::GetGpuMemoryPolicy(cc::ManagedMemoryPolicy(256), |
| gfx::Size(), 1.f); |
| EXPECT_EQ(512u * 1024u * 1024u, policy.bytes_limit_when_visible); |
| EXPECT_EQ(gpu::MemoryAllocation::CUTOFF_ALLOW_NICE_TO_HAVE, |
| policy.priority_cutoff_when_visible); |
| } |
| |
| TEST(RenderWidgetTest, LargeScreensUseMoreMemory) { |
| auto policy = RenderWidget::GetGpuMemoryPolicy(cc::ManagedMemoryPolicy(256), |
| gfx::Size(4096, 2160), 1.f); |
| EXPECT_EQ(2u * 512u * 1024u * 1024u, policy.bytes_limit_when_visible); |
| EXPECT_EQ(gpu::MemoryAllocation::CUTOFF_ALLOW_NICE_TO_HAVE, |
| policy.priority_cutoff_when_visible); |
| |
| policy = RenderWidget::GetGpuMemoryPolicy(cc::ManagedMemoryPolicy(256), |
| gfx::Size(2048, 1080), 2.f); |
| EXPECT_EQ(2u * 512u * 1024u * 1024u, policy.bytes_limit_when_visible); |
| EXPECT_EQ(gpu::MemoryAllocation::CUTOFF_ALLOW_NICE_TO_HAVE, |
| policy.priority_cutoff_when_visible); |
| } |
| #endif |
| |
| #if defined(OS_ANDROID) |
| TEST_F(RenderWidgetUnittest, ForceSendMetadataOnInput) { |
| cc::LayerTreeHost* layer_tree_host = widget()->layer_tree_host(); |
| // We should not have any force send metadata requests at start. |
| EXPECT_FALSE(layer_tree_host->TakeForceSendMetadataRequest()); |
| // ShowVirtualKeyboard will trigger a text input state update. |
| widget()->GetWebWidget()->ShowVirtualKeyboard(); |
| // We should now have a force send metadata request. |
| EXPECT_TRUE(layer_tree_host->TakeForceSendMetadataRequest()); |
| } |
| #endif // !defined(OS_ANDROID) |
| |
| class NotifySwapTimesRenderWidgetUnittest : public RenderWidgetUnittest { |
| public: |
| void SetUp() override { |
| RenderWidgetUnittest::SetUp(); |
| |
| viz::ParentLocalSurfaceIdAllocator allocator; |
| WidgetMsg_WasShown msg(widget()->routing_id(), |
| /*show_request_timestamp=*/base::TimeTicks(), |
| /*was_evicted=*/false, |
| /*record_tab_switch_time_request=*/base::nullopt); |
| widget()->OnMessageReceived(msg); |
| |
| // TODO(danakj): This usually happens through |
| // RenderWidget::OnUpdateVisualProperties() and we are cutting past that for |
| // some reason. |
| allocator.GenerateId(); |
| widget()->layer_tree_host()->SetViewportRectAndScale( |
| gfx::Rect(200, 100), 1.f, |
| allocator.GetCurrentLocalSurfaceIdAllocation()); |
| |
| auto root_layer = cc::SolidColorLayer::Create(); |
| root_layer->SetBounds(gfx::Size(200, 100)); |
| root_layer->SetBackgroundColor(SK_ColorGREEN); |
| widget()->layer_tree_host()->SetNonBlinkManagedRootLayer(root_layer); |
| |
| auto color_layer = cc::SolidColorLayer::Create(); |
| color_layer->SetBounds(gfx::Size(100, 100)); |
| root_layer->AddChild(color_layer); |
| color_layer->SetBackgroundColor(SK_ColorRED); |
| } |
| |
| // |swap_to_presentation| determines how long after swap should presentation |
| // happen. This can be negative, positive, or zero. If zero, an invalid (null) |
| // presentation time is used. |
| void CompositeAndWaitForPresentation(base::TimeDelta swap_to_presentation) { |
| base::RunLoop swap_run_loop; |
| base::RunLoop presentation_run_loop; |
| |
| // Register callbacks for swap time and presentation time. |
| base::TimeTicks swap_time; |
| frame_widget()->NotifySwapAndPresentationTime( |
| base::BindOnce( |
| [](base::OnceClosure swap_quit_closure, base::TimeTicks* swap_time, |
| blink::WebSwapResult result, base::TimeTicks timestamp) { |
| DCHECK(!timestamp.is_null()); |
| *swap_time = timestamp; |
| std::move(swap_quit_closure).Run(); |
| }, |
| swap_run_loop.QuitClosure(), &swap_time), |
| base::BindOnce( |
| [](base::OnceClosure presentation_quit_closure, |
| blink::WebSwapResult result, base::TimeTicks timestamp) { |
| DCHECK(!timestamp.is_null()); |
| std::move(presentation_quit_closure).Run(); |
| }, |
| presentation_run_loop.QuitClosure())); |
| |
| // Composite and wait for the swap to complete. |
| widget()->layer_tree_host()->Composite(base::TimeTicks::Now(), |
| /*raster=*/true); |
| swap_run_loop.Run(); |
| |
| // Present and wait for it to complete. |
| viz::FrameTimingDetails timing_details; |
| if (!swap_to_presentation.is_zero()) { |
| timing_details.presentation_feedback = gfx::PresentationFeedback( |
| /*presentation_time=*/swap_time + swap_to_presentation, |
| base::TimeDelta::FromMilliseconds(16), 0); |
| } |
| GetFrameSink()->NotifyDidPresentCompositorFrame(1, timing_details); |
| presentation_run_loop.Run(); |
| } |
| }; |
| |
| TEST_F(NotifySwapTimesRenderWidgetUnittest, PresentationTimestampValid) { |
| base::HistogramTester histograms; |
| |
| CompositeAndWaitForPresentation(base::TimeDelta::FromMilliseconds(2)); |
| |
| EXPECT_THAT(histograms.GetAllSamples( |
| "PageLoad.Internal.Renderer.PresentationTime.Valid"), |
| testing::ElementsAre(base::Bucket(true, 1))); |
| EXPECT_THAT( |
| histograms.GetAllSamples( |
| "PageLoad.Internal.Renderer.PresentationTime.DeltaFromSwapTime"), |
| testing::ElementsAre(base::Bucket(2, 1))); |
| } |
| |
| TEST_F(NotifySwapTimesRenderWidgetUnittest, PresentationTimestampInvalid) { |
| base::HistogramTester histograms; |
| |
| CompositeAndWaitForPresentation(base::TimeDelta()); |
| |
| EXPECT_THAT(histograms.GetAllSamples( |
| "PageLoad.Internal.Renderer.PresentationTime.Valid"), |
| testing::ElementsAre(base::Bucket(false, 1))); |
| EXPECT_THAT( |
| histograms.GetAllSamples( |
| "PageLoad.Internal.Renderer.PresentationTime.DeltaFromSwapTime"), |
| testing::IsEmpty()); |
| } |
| |
| TEST_F(NotifySwapTimesRenderWidgetUnittest, |
| PresentationTimestampEarlierThanSwaptime) { |
| base::HistogramTester histograms; |
| |
| CompositeAndWaitForPresentation(base::TimeDelta::FromMilliseconds(-2)); |
| |
| EXPECT_THAT(histograms.GetAllSamples( |
| "PageLoad.Internal.Renderer.PresentationTime.Valid"), |
| testing::ElementsAre(base::Bucket(false, 1))); |
| EXPECT_THAT( |
| histograms.GetAllSamples( |
| "PageLoad.Internal.Renderer.PresentationTime.DeltaFromSwapTime"), |
| testing::IsEmpty()); |
| } |
| |
| } // namespace content |