| // 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 <vector> |
| |
| #include "base/macros.h" |
| #include "base/test/histogram_tester.h" |
| #include "content/common/input/synthetic_web_input_event_builders.h" |
| #include "content/common/input_messages.h" |
| #include "content/common/resize_params.h" |
| #include "content/public/test/mock_render_thread.h" |
| #include "content/renderer/devtools/render_widget_screen_metrics_emulator.h" |
| #include "content/test/fake_compositor_dependencies.h" |
| #include "content/test/mock_render_process.h" |
| #include "ipc/ipc_test_sink.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/WebKit/public/platform/WebInputEvent.h" |
| #include "third_party/WebKit/public/web/WebDeviceEmulationParams.h" |
| #include "ui/events/blink/web_input_event_traits.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| using testing::_; |
| |
| namespace content { |
| |
| namespace { |
| |
| const char* EVENT_LISTENER_RESULT_HISTOGRAM = "Event.PassiveListeners"; |
| |
| 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_COUNT |
| }; |
| |
| class MockWebWidget : public blink::WebWidget { |
| public: |
| MOCK_METHOD1(handleInputEvent, |
| blink::WebInputEventResult(const blink::WebInputEvent&)); |
| }; |
| |
| } // namespace |
| |
| class InteractiveRenderWidget : public RenderWidget { |
| public: |
| explicit InteractiveRenderWidget(CompositorDependencies* compositor_deps) |
| : RenderWidget(++next_routing_id_, |
| compositor_deps, |
| blink::WebPopupTypeNone, |
| ScreenInfo(), |
| false, |
| false, |
| false), |
| always_overscroll_(false) { |
| Init(RenderWidget::ShowCallback(), mock_webwidget()); |
| } |
| |
| void SetTouchRegion(const std::vector<gfx::Rect>& rects) { |
| rects_ = rects; |
| } |
| |
| void SendInputEvent(const blink::WebInputEvent& event) { |
| OnHandleInputEvent( |
| &event, ui::LatencyInfo(), |
| ui::WebInputEventTraits::ShouldBlockEventStream(event) |
| ? InputEventDispatchType::DISPATCH_TYPE_BLOCKING |
| : InputEventDispatchType::DISPATCH_TYPE_NON_BLOCKING); |
| } |
| |
| void set_always_overscroll(bool overscroll) { |
| always_overscroll_ = overscroll; |
| } |
| |
| IPC::TestSink* sink() { return &sink_; } |
| |
| MockWebWidget* mock_webwidget() { return &mock_webwidget_; } |
| |
| protected: |
| ~InteractiveRenderWidget() override { webwidget_internal_ = nullptr; } |
| |
| // Overridden from RenderWidget: |
| bool HasTouchEventHandlersAt(const gfx::Point& point) const override { |
| for (std::vector<gfx::Rect>::const_iterator iter = rects_.begin(); |
| iter != rects_.end(); ++iter) { |
| if ((*iter).Contains(point)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool WillHandleGestureEvent(const blink::WebGestureEvent& event) override { |
| if (always_overscroll_ && |
| event.type == blink::WebInputEvent::GestureScrollUpdate) { |
| didOverscroll(blink::WebFloatSize(event.data.scrollUpdate.deltaX, |
| event.data.scrollUpdate.deltaY), |
| blink::WebFloatSize(event.data.scrollUpdate.deltaX, |
| event.data.scrollUpdate.deltaY), |
| blink::WebFloatPoint(event.x, event.y), |
| blink::WebFloatSize(event.data.scrollUpdate.velocityX, |
| event.data.scrollUpdate.velocityY)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool Send(IPC::Message* msg) override { |
| sink_.OnMessageReceived(*msg); |
| delete msg; |
| return true; |
| } |
| |
| private: |
| std::vector<gfx::Rect> rects_; |
| IPC::TestSink sink_; |
| bool always_overscroll_; |
| MockWebWidget mock_webwidget_; |
| static int next_routing_id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(InteractiveRenderWidget); |
| }; |
| |
| int InteractiveRenderWidget::next_routing_id_ = 0; |
| |
| class RenderWidgetUnittest : public testing::Test { |
| public: |
| RenderWidgetUnittest() |
| : widget_(new InteractiveRenderWidget(&compositor_deps_)) { |
| // RenderWidget::Init does an AddRef that's balanced by a browser-initiated |
| // Close IPC. That Close will never happen in this test, so do a Release |
| // here to ensure |widget_| is properly freed. |
| widget_->Release(); |
| DCHECK(widget_->HasOneRef()); |
| } |
| ~RenderWidgetUnittest() override {} |
| |
| InteractiveRenderWidget* widget() const { return widget_.get(); } |
| |
| const base::HistogramTester& histogram_tester() const { |
| return histogram_tester_; |
| } |
| |
| private: |
| MockRenderProcess render_process_; |
| MockRenderThread render_thread_; |
| FakeCompositorDependencies compositor_deps_; |
| scoped_refptr<InteractiveRenderWidget> widget_; |
| base::HistogramTester histogram_tester_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetUnittest); |
| }; |
| |
| TEST_F(RenderWidgetUnittest, TouchHitTestSinglePoint) { |
| SyntheticWebTouchEvent touch; |
| touch.PressPoint(10, 10); |
| |
| EXPECT_CALL(*widget()->mock_webwidget(), handleInputEvent(_)) |
| .WillRepeatedly( |
| ::testing::Return(blink::WebInputEventResult::NotHandled)); |
| |
| widget()->SendInputEvent(touch); |
| ASSERT_EQ(1u, widget()->sink()->message_count()); |
| |
| // Since there's currently no touch-event handling region, the response should |
| // be 'no consumer exists'. |
| const IPC::Message* message = widget()->sink()->GetMessageAt(0); |
| EXPECT_EQ(InputHostMsg_HandleInputEvent_ACK::ID, message->type()); |
| InputHostMsg_HandleInputEvent_ACK::Param params; |
| InputHostMsg_HandleInputEvent_ACK::Read(message, ¶ms); |
| InputEventAckState ack_state = std::get<0>(params).state; |
| EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, ack_state); |
| widget()->sink()->ClearMessages(); |
| |
| std::vector<gfx::Rect> rects; |
| rects.push_back(gfx::Rect(0, 0, 20, 20)); |
| rects.push_back(gfx::Rect(25, 0, 10, 10)); |
| widget()->SetTouchRegion(rects); |
| |
| EXPECT_CALL(*widget()->mock_webwidget(), handleInputEvent(_)) |
| .WillRepeatedly( |
| ::testing::Return(blink::WebInputEventResult::NotHandled)); |
| |
| widget()->SendInputEvent(touch); |
| ASSERT_EQ(1u, widget()->sink()->message_count()); |
| message = widget()->sink()->GetMessageAt(0); |
| EXPECT_EQ(InputHostMsg_HandleInputEvent_ACK::ID, message->type()); |
| InputHostMsg_HandleInputEvent_ACK::Read(message, ¶ms); |
| ack_state = std::get<0>(params).state; |
| EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, ack_state); |
| widget()->sink()->ClearMessages(); |
| } |
| |
| TEST_F(RenderWidgetUnittest, TouchHitTestMultiplePoints) { |
| std::vector<gfx::Rect> rects; |
| rects.push_back(gfx::Rect(0, 0, 20, 20)); |
| rects.push_back(gfx::Rect(25, 0, 10, 10)); |
| widget()->SetTouchRegion(rects); |
| |
| SyntheticWebTouchEvent touch; |
| touch.PressPoint(25, 25); |
| |
| EXPECT_CALL(*widget()->mock_webwidget(), handleInputEvent(_)) |
| .WillRepeatedly( |
| ::testing::Return(blink::WebInputEventResult::NotHandled)); |
| |
| widget()->SendInputEvent(touch); |
| ASSERT_EQ(1u, widget()->sink()->message_count()); |
| |
| // Since there's currently no touch-event handling region, the response should |
| // be 'no consumer exists'. |
| const IPC::Message* message = widget()->sink()->GetMessageAt(0); |
| EXPECT_EQ(InputHostMsg_HandleInputEvent_ACK::ID, message->type()); |
| InputHostMsg_HandleInputEvent_ACK::Param params; |
| InputHostMsg_HandleInputEvent_ACK::Read(message, ¶ms); |
| InputEventAckState ack_state = std::get<0>(params).state; |
| EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, ack_state); |
| widget()->sink()->ClearMessages(); |
| |
| // Press a second touch point. This time, on a touch-handling region. |
| touch.PressPoint(10, 10); |
| widget()->SendInputEvent(touch); |
| ASSERT_EQ(1u, widget()->sink()->message_count()); |
| message = widget()->sink()->GetMessageAt(0); |
| EXPECT_EQ(InputHostMsg_HandleInputEvent_ACK::ID, message->type()); |
| InputHostMsg_HandleInputEvent_ACK::Read(message, ¶ms); |
| ack_state = std::get<0>(params).state; |
| EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, ack_state); |
| widget()->sink()->ClearMessages(); |
| } |
| |
| TEST_F(RenderWidgetUnittest, EventOverscroll) { |
| widget()->set_always_overscroll(true); |
| |
| EXPECT_CALL(*widget()->mock_webwidget(), handleInputEvent(_)) |
| .WillRepeatedly( |
| ::testing::Return(blink::WebInputEventResult::NotHandled)); |
| |
| blink::WebGestureEvent scroll; |
| scroll.type = blink::WebInputEvent::GestureScrollUpdate; |
| scroll.x = -10; |
| scroll.data.scrollUpdate.deltaY = 10; |
| widget()->SendInputEvent(scroll); |
| |
| // Overscroll notifications received while handling an input event should |
| // be bundled with the event ack IPC. |
| ASSERT_EQ(1u, widget()->sink()->message_count()); |
| const IPC::Message* message = widget()->sink()->GetMessageAt(0); |
| ASSERT_EQ(InputHostMsg_HandleInputEvent_ACK::ID, message->type()); |
| InputHostMsg_HandleInputEvent_ACK::Param params; |
| InputHostMsg_HandleInputEvent_ACK::Read(message, ¶ms); |
| const InputEventAck& ack = std::get<0>(params); |
| ASSERT_EQ(ack.type, scroll.type); |
| ASSERT_TRUE(ack.overscroll); |
| EXPECT_EQ(gfx::Vector2dF(0, 10), ack.overscroll->accumulated_overscroll); |
| EXPECT_EQ(gfx::Vector2dF(0, 10), ack.overscroll->latest_overscroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(), ack.overscroll->current_fling_velocity); |
| EXPECT_EQ(gfx::PointF(-10, 0), ack.overscroll->causal_event_viewport_point); |
| widget()->sink()->ClearMessages(); |
| } |
| |
| TEST_F(RenderWidgetUnittest, FlingOverscroll) { |
| // Overscroll notifications received outside of handling an input event should |
| // be sent as a separate IPC. |
| widget()->didOverscroll(blink::WebFloatSize(10, 5), blink::WebFloatSize(5, 5), |
| blink::WebFloatPoint(1, 1), |
| blink::WebFloatSize(10, 5)); |
| ASSERT_EQ(1u, widget()->sink()->message_count()); |
| const IPC::Message* message = widget()->sink()->GetMessageAt(0); |
| ASSERT_EQ(InputHostMsg_DidOverscroll::ID, message->type()); |
| InputHostMsg_DidOverscroll::Param params; |
| InputHostMsg_DidOverscroll::Read(message, ¶ms); |
| const ui::DidOverscrollParams& overscroll = std::get<0>(params); |
| EXPECT_EQ(gfx::Vector2dF(10, 5), overscroll.latest_overscroll_delta); |
| EXPECT_EQ(gfx::Vector2dF(5, 5), overscroll.accumulated_overscroll); |
| EXPECT_EQ(gfx::PointF(1, 1), overscroll.causal_event_viewport_point); |
| EXPECT_EQ(gfx::Vector2dF(10, 5), overscroll.current_fling_velocity); |
| widget()->sink()->ClearMessages(); |
| } |
| |
| TEST_F(RenderWidgetUnittest, RenderWidgetInputEventUmaMetrics) { |
| SyntheticWebTouchEvent touch; |
| touch.PressPoint(10, 10); |
| touch.touchStartOrFirstTouchMove = true; |
| |
| EXPECT_CALL(*widget()->mock_webwidget(), handleInputEvent(_)) |
| .Times(5) |
| .WillRepeatedly( |
| ::testing::Return(blink::WebInputEventResult::NotHandled)); |
| |
| widget()->SendInputEvent(touch); |
| histogram_tester().ExpectBucketCount(EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_CANCELABLE, 1); |
| |
| touch.dispatchType = blink::WebInputEvent::DispatchType::EventNonBlocking; |
| widget()->SendInputEvent(touch); |
| histogram_tester().ExpectBucketCount(EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_UNCANCELABLE, |
| 1); |
| |
| touch.dispatchType = |
| blink::WebInputEvent::DispatchType::ListenersNonBlockingPassive; |
| widget()->SendInputEvent(touch); |
| histogram_tester().ExpectBucketCount(EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_PASSIVE, 1); |
| |
| touch.dispatchType = |
| blink::WebInputEvent::DispatchType::ListenersForcedNonBlockingDueToFling; |
| widget()->SendInputEvent(touch); |
| histogram_tester().ExpectBucketCount( |
| EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_FLING, 1); |
| |
| touch.MovePoint(0, 10, 10); |
| touch.touchStartOrFirstTouchMove = true; |
| touch.dispatchType = |
| blink::WebInputEvent::DispatchType::ListenersForcedNonBlockingDueToFling; |
| widget()->SendInputEvent(touch); |
| histogram_tester().ExpectBucketCount( |
| EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_FLING, 2); |
| |
| EXPECT_CALL(*widget()->mock_webwidget(), handleInputEvent(_)) |
| .WillOnce( |
| ::testing::Return(blink::WebInputEventResult::HandledSuppressed)); |
| touch.dispatchType = blink::WebInputEvent::DispatchType::Blocking; |
| widget()->SendInputEvent(touch); |
| histogram_tester().ExpectBucketCount(EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_SUPPRESSED, 1); |
| |
| EXPECT_CALL(*widget()->mock_webwidget(), handleInputEvent(_)) |
| .WillOnce( |
| ::testing::Return(blink::WebInputEventResult::HandledApplication)); |
| touch.dispatchType = blink::WebInputEvent::DispatchType::Blocking; |
| widget()->SendInputEvent(touch); |
| histogram_tester().ExpectBucketCount( |
| EVENT_LISTENER_RESULT_HISTOGRAM, |
| PASSIVE_LISTENER_UMA_ENUM_CANCELABLE_AND_CANCELED, 1); |
| } |
| |
| TEST_F(RenderWidgetUnittest, TouchDuringOrOutsideFlingUmaMetrics) { |
| EXPECT_CALL(*widget()->mock_webwidget(), handleInputEvent(_)) |
| .Times(3) |
| .WillRepeatedly( |
| ::testing::Return(blink::WebInputEventResult::NotHandled)); |
| |
| SyntheticWebTouchEvent touch; |
| touch.PressPoint(10, 10); |
| touch.dispatchType = blink::WebInputEvent::DispatchType::Blocking; |
| touch.touchStartOrFirstTouchMove = true; |
| widget()->SendInputEvent(touch); |
| histogram_tester().ExpectTotalCount("Event.Touch.TouchLatencyOutsideFling", |
| 1); |
| |
| touch.MovePoint(0, 10, 10); |
| touch.touchStartOrFirstTouchMove = true; |
| widget()->SendInputEvent(touch); |
| histogram_tester().ExpectTotalCount("Event.Touch.TouchLatencyOutsideFling", |
| 2); |
| |
| touch.MovePoint(0, 30, 30); |
| touch.touchStartOrFirstTouchMove = false; |
| widget()->SendInputEvent(touch); |
| histogram_tester().ExpectTotalCount("Event.Touch.TouchLatencyOutsideFling", |
| 2); |
| } |
| |
| class PopupRenderWidget : public RenderWidget { |
| public: |
| explicit PopupRenderWidget(CompositorDependencies* compositor_deps) |
| : RenderWidget(1, |
| compositor_deps, |
| blink::WebPopupTypePage, |
| ScreenInfo(), |
| false, |
| false, |
| false) { |
| Init(RenderWidget::ShowCallback(), mock_webwidget()); |
| did_show_ = true; |
| } |
| |
| IPC::TestSink* sink() { return &sink_; } |
| |
| MockWebWidget* mock_webwidget() { return &mock_webwidget_; } |
| |
| void SetScreenMetricsEmulationParameters( |
| bool, |
| const blink::WebDeviceEmulationParams&) override {} |
| |
| protected: |
| ~PopupRenderWidget() override { webwidget_internal_ = nullptr; } |
| |
| bool Send(IPC::Message* msg) override { |
| sink_.OnMessageReceived(*msg); |
| delete msg; |
| return true; |
| } |
| |
| private: |
| IPC::TestSink sink_; |
| MockWebWidget mock_webwidget_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PopupRenderWidget); |
| }; |
| |
| class RenderWidgetPopupUnittest : public testing::Test { |
| public: |
| RenderWidgetPopupUnittest() |
| : widget_(new PopupRenderWidget(&compositor_deps_)) { |
| // RenderWidget::Init does an AddRef that's balanced by a browser-initiated |
| // Close IPC. That Close will never happen in this test, so do a Release |
| // here to ensure |widget_| is properly freed. |
| widget_->Release(); |
| DCHECK(widget_->HasOneRef()); |
| } |
| ~RenderWidgetPopupUnittest() override {} |
| |
| PopupRenderWidget* widget() const { return widget_.get(); } |
| FakeCompositorDependencies compositor_deps_; |
| |
| private: |
| MockRenderProcess render_process_; |
| MockRenderThread render_thread_; |
| scoped_refptr<PopupRenderWidget> widget_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetPopupUnittest); |
| }; |
| |
| TEST_F(RenderWidgetPopupUnittest, EmulatingPopupRect) { |
| blink::WebRect popup_screen_rect(200, 250, 100, 400); |
| widget()->setWindowRect(popup_screen_rect); |
| |
| // The view and window rect on a popup type RenderWidget should be |
| // immediately set, without requiring an ACK. |
| EXPECT_EQ(popup_screen_rect.x, widget()->windowRect().x); |
| EXPECT_EQ(popup_screen_rect.y, widget()->windowRect().y); |
| |
| EXPECT_EQ(popup_screen_rect.x, widget()->viewRect().x); |
| EXPECT_EQ(popup_screen_rect.y, widget()->viewRect().y); |
| |
| gfx::Rect emulated_window_rect(0, 0, 980, 1200); |
| |
| blink::WebDeviceEmulationParams emulation_params; |
| emulation_params.screenPosition = blink::WebDeviceEmulationParams::Mobile; |
| emulation_params.viewSize = emulated_window_rect.size(); |
| emulation_params.viewPosition = blink::WebPoint(150, 160); |
| emulation_params.fitToView = true; |
| |
| gfx::Rect parent_window_rect = gfx::Rect(0, 0, 800, 600); |
| |
| ResizeParams resize_params; |
| resize_params.new_size = parent_window_rect.size(); |
| |
| scoped_refptr<PopupRenderWidget> parent_widget( |
| new PopupRenderWidget(&compositor_deps_)); |
| parent_widget->Release(); // Balance Init(). |
| RenderWidgetScreenMetricsEmulator emulator( |
| parent_widget.get(), emulation_params, resize_params, parent_window_rect, |
| parent_window_rect); |
| emulator.Apply(); |
| |
| widget()->SetPopupOriginAdjustmentsForEmulation(&emulator); |
| |
| // Emulation-applied scale factor to fit the emulated device in the window. |
| float scale = |
| (float)parent_window_rect.height() / emulated_window_rect.height(); |
| |
| // Used to center the emulated device in the window. |
| gfx::Point offset( |
| (parent_window_rect.width() - emulated_window_rect.width() * scale) / 2, |
| (parent_window_rect.height() - emulated_window_rect.height() * scale) / |
| 2); |
| |
| // Position of the popup as seen by the emulated widget. |
| gfx::Point emulated_position(emulation_params.viewPosition.x + |
| (popup_screen_rect.x - offset.x()) / scale, |
| emulation_params.viewPosition.y + |
| (popup_screen_rect.y - offset.y()) / scale); |
| |
| // Both the window and view rects as read from the accessors should have the |
| // emulation parameters applied. |
| EXPECT_EQ(emulated_position.x(), widget()->windowRect().x); |
| EXPECT_EQ(emulated_position.y(), widget()->windowRect().y); |
| EXPECT_EQ(emulated_position.x(), widget()->viewRect().x); |
| EXPECT_EQ(emulated_position.y(), widget()->viewRect().y); |
| |
| // Setting a new window rect while emulated should remove the emulation |
| // transformation from the given rect so that getting the rect, which applies |
| // the transformation to the raw rect, should result in the same value. |
| blink::WebRect popup_emulated_rect(130, 170, 100, 400); |
| widget()->setWindowRect(popup_emulated_rect); |
| |
| EXPECT_EQ(popup_emulated_rect.x, widget()->windowRect().x); |
| EXPECT_EQ(popup_emulated_rect.y, widget()->windowRect().y); |
| EXPECT_EQ(popup_emulated_rect.x, widget()->viewRect().x); |
| EXPECT_EQ(popup_emulated_rect.y, widget()->viewRect().y); |
| } |
| |
| } // namespace content |