| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <tuple> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "build/build_config.h" |
| #include "cc/trees/render_frame_metadata.h" |
| #include "components/viz/common/features.h" |
| #include "components/viz/common/surfaces/local_surface_id.h" |
| #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h" |
| #include "components/viz/test/begin_frame_args_test.h" |
| #include "components/viz/test/compositor_frame_helpers.h" |
| #include "components/viz/test/mock_compositor_frame_sink_client.h" |
| #include "content/browser/gpu/compositor_util.h" |
| #include "content/browser/renderer_host/frame_token_message_queue.h" |
| #include "content/browser/renderer_host/input/touch_emulator.h" |
| #include "content/browser/renderer_host/render_view_host_delegate_view.h" |
| #include "content/browser/renderer_host/render_widget_host_delegate.h" |
| #include "content/browser/renderer_host/render_widget_host_view_base.h" |
| #include "content/common/edit_command.h" |
| #include "content/common/input/synthetic_web_input_event_builders.h" |
| #include "content/common/input_messages.h" |
| #include "content/common/render_frame_metadata.mojom.h" |
| #include "content/common/view_messages.h" |
| #include "content/common/visual_properties.h" |
| #include "content/public/browser/keyboard_event_processing_result.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/mock_render_process_host.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "content/test/fake_renderer_compositor_frame_sink.h" |
| #include "content/test/mock_widget_impl.h" |
| #include "content/test/mock_widget_input_handler.h" |
| #include "content/test/test_render_view_host.h" |
| #include "mojo/public/cpp/bindings/interface_request.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/blink/blink_features.h" |
| #include "ui/events/blink/web_input_event_traits.h" |
| #include "ui/events/gesture_detection/gesture_provider_config_helper.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| #include "ui/gfx/canvas.h" |
| |
| #if defined(OS_ANDROID) |
| #include "content/browser/renderer_host/render_widget_host_view_android.h" |
| #include "ui/android/screen_android.h" |
| #endif |
| |
| #if defined(USE_AURA) || defined(OS_MACOSX) |
| #include "content/browser/compositor/test/test_image_transport_factory.h" |
| #endif |
| |
| #if defined(USE_AURA) |
| #include "content/browser/renderer_host/render_widget_host_view_aura.h" |
| #include "content/browser/renderer_host/ui_events_helper.h" |
| #include "ui/aura/test/test_screen.h" |
| #include "ui/events/event.h" |
| #endif |
| |
| using base::TimeDelta; |
| using blink::WebGestureDevice; |
| using blink::WebGestureEvent; |
| using blink::WebInputEvent; |
| using blink::WebKeyboardEvent; |
| using blink::WebMouseEvent; |
| using blink::WebMouseWheelEvent; |
| using blink::WebTouchEvent; |
| using blink::WebTouchPoint; |
| |
| namespace content { |
| |
| // MockInputRouter ------------------------------------------------------------- |
| |
| class MockInputRouter : public InputRouter { |
| public: |
| explicit MockInputRouter(InputRouterClient* client) |
| : sent_mouse_event_(false), |
| sent_wheel_event_(false), |
| sent_keyboard_event_(false), |
| sent_gesture_event_(false), |
| send_touch_event_not_cancelled_(false), |
| message_received_(false), |
| client_(client) {} |
| ~MockInputRouter() override {} |
| |
| // InputRouter |
| void SendMouseEvent(const MouseEventWithLatencyInfo& mouse_event) override { |
| sent_mouse_event_ = true; |
| } |
| void SendWheelEvent( |
| const MouseWheelEventWithLatencyInfo& wheel_event) override { |
| sent_wheel_event_ = true; |
| } |
| void SendKeyboardEvent( |
| const NativeWebKeyboardEventWithLatencyInfo& key_event) override { |
| sent_keyboard_event_ = true; |
| } |
| void SendGestureEvent( |
| const GestureEventWithLatencyInfo& gesture_event) override { |
| sent_gesture_event_ = true; |
| } |
| void SendTouchEvent(const TouchEventWithLatencyInfo& touch_event) override { |
| send_touch_event_not_cancelled_ = |
| client_->FilterInputEvent(touch_event.event, touch_event.latency) == |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED; |
| } |
| void NotifySiteIsMobileOptimized(bool is_mobile_optimized) override {} |
| bool HasPendingEvents() const override { return false; } |
| void SetDeviceScaleFactor(float device_scale_factor) override {} |
| void SetFrameTreeNodeId(int frameTreeNodeId) override {} |
| base::Optional<cc::TouchAction> AllowedTouchAction() override { |
| return cc::kTouchActionAuto; |
| } |
| void SetForceEnableZoom(bool enabled) override {} |
| void BindHost(mojom::WidgetInputHandlerHostRequest request, |
| bool frame_handler) override {} |
| void StopFling() override {} |
| bool FlingCancellationIsDeferred() override { return false; } |
| void OnSetTouchAction(cc::TouchAction touch_action) override {} |
| |
| // IPC::Listener |
| bool OnMessageReceived(const IPC::Message& message) override { |
| message_received_ = true; |
| return false; |
| } |
| |
| bool sent_mouse_event_; |
| bool sent_wheel_event_; |
| bool sent_keyboard_event_; |
| bool sent_gesture_event_; |
| bool send_touch_event_not_cancelled_; |
| bool message_received_; |
| |
| private: |
| InputRouterClient* client_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockInputRouter); |
| }; |
| |
| // TestFrameTokenMessageQueue ---------------------------------------------- |
| |
| class TestFrameTokenMessageQueue : public FrameTokenMessageQueue { |
| public: |
| TestFrameTokenMessageQueue(FrameTokenMessageQueue::Client* client) |
| : FrameTokenMessageQueue(client) {} |
| ~TestFrameTokenMessageQueue() override {} |
| |
| uint32_t processed_frame_messages_count() { |
| return processed_frame_messages_count_; |
| } |
| |
| protected: |
| void ProcessSwapMessages(std::vector<IPC::Message> messages) override { |
| processed_frame_messages_count_++; |
| } |
| |
| private: |
| uint32_t processed_frame_messages_count_ = 0; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestFrameTokenMessageQueue); |
| }; |
| |
| // MockRenderWidgetHost ---------------------------------------------------- |
| |
| class MockRenderWidgetHost : public RenderWidgetHostImpl { |
| public: |
| |
| // Allow poking at a few private members. |
| using RenderWidgetHostImpl::GetVisualProperties; |
| using RenderWidgetHostImpl::RendererExited; |
| using RenderWidgetHostImpl::SetInitialVisualProperties; |
| using RenderWidgetHostImpl::old_visual_properties_; |
| using RenderWidgetHostImpl::is_hidden_; |
| using RenderWidgetHostImpl::visual_properties_ack_pending_; |
| using RenderWidgetHostImpl::input_router_; |
| using RenderWidgetHostImpl::frame_token_message_queue_; |
| |
| void OnTouchEventAck(const TouchEventWithLatencyInfo& event, |
| InputEventAckSource ack_source, |
| InputEventAckState ack_result) override { |
| // Sniff touch acks. |
| acked_touch_event_type_ = event.event.GetType(); |
| RenderWidgetHostImpl::OnTouchEventAck(event, ack_source, ack_result); |
| } |
| |
| void reset_new_content_rendering_timeout_fired() { |
| new_content_rendering_timeout_fired_ = false; |
| } |
| |
| bool new_content_rendering_timeout_fired() const { |
| return new_content_rendering_timeout_fired_; |
| } |
| |
| void DisableGestureDebounce() { |
| input_router_.reset(new InputRouterImpl(this, this, fling_scheduler_.get(), |
| InputRouter::Config())); |
| } |
| |
| void ExpectForceEnableZoom(bool enable) { |
| EXPECT_EQ(enable, force_enable_zoom_); |
| |
| InputRouterImpl* input_router = |
| static_cast<InputRouterImpl*>(input_router_.get()); |
| EXPECT_EQ(enable, input_router->touch_action_filter_.force_enable_zoom_); |
| } |
| |
| WebInputEvent::Type acked_touch_event_type() const { |
| return acked_touch_event_type_; |
| } |
| |
| // Mocks out |renderer_compositor_frame_sink_| with a |
| // CompositorFrameSinkClientPtr bound to |
| // |mock_renderer_compositor_frame_sink|. |
| void SetMockRendererCompositorFrameSink( |
| viz::MockCompositorFrameSinkClient* mock_renderer_compositor_frame_sink) { |
| renderer_compositor_frame_sink_ = |
| mock_renderer_compositor_frame_sink->BindInterfacePtr(); |
| } |
| |
| void SetupForInputRouterTest() { |
| input_router_.reset(new MockInputRouter(this)); |
| } |
| |
| MockInputRouter* mock_input_router() { |
| return static_cast<MockInputRouter*>(input_router_.get()); |
| } |
| |
| InputRouter* input_router() { return input_router_.get(); } |
| |
| uint32_t processed_frame_messages_count() { |
| CHECK(frame_token_message_queue_); |
| return static_cast<TestFrameTokenMessageQueue*>( |
| frame_token_message_queue_.get()) |
| ->processed_frame_messages_count(); |
| } |
| |
| static MockRenderWidgetHost* Create(RenderWidgetHostDelegate* delegate, |
| RenderProcessHost* process, |
| int32_t routing_id) { |
| mojom::WidgetPtr widget; |
| std::unique_ptr<MockWidgetImpl> widget_impl = |
| std::make_unique<MockWidgetImpl>(mojo::MakeRequest(&widget)); |
| |
| return new MockRenderWidgetHost(delegate, process, routing_id, |
| std::move(widget_impl), std::move(widget)); |
| } |
| |
| mojom::WidgetInputHandler* GetWidgetInputHandler() override { |
| return &mock_widget_input_handler_; |
| } |
| |
| MockWidgetInputHandler mock_widget_input_handler_; |
| |
| protected: |
| void NotifyNewContentRenderingTimeoutForTesting() override { |
| new_content_rendering_timeout_fired_ = true; |
| } |
| |
| bool new_content_rendering_timeout_fired_; |
| WebInputEvent::Type acked_touch_event_type_; |
| |
| private: |
| MockRenderWidgetHost(RenderWidgetHostDelegate* delegate, |
| RenderProcessHost* process, |
| int routing_id, |
| std::unique_ptr<MockWidgetImpl> widget_impl, |
| mojom::WidgetPtr widget) |
| : RenderWidgetHostImpl(delegate, |
| process, |
| routing_id, |
| std::move(widget), |
| false), |
| new_content_rendering_timeout_fired_(false), |
| widget_impl_(std::move(widget_impl)), |
| fling_scheduler_(std::make_unique<FlingScheduler>(this)) { |
| acked_touch_event_type_ = blink::WebInputEvent::kUndefined; |
| frame_token_message_queue_.reset(new TestFrameTokenMessageQueue(this)); |
| } |
| |
| std::unique_ptr<MockWidgetImpl> widget_impl_; |
| |
| std::unique_ptr<FlingScheduler> fling_scheduler_; |
| DISALLOW_COPY_AND_ASSIGN(MockRenderWidgetHost); |
| }; |
| |
| namespace { |
| |
| // RenderWidgetHostProcess ----------------------------------------------------- |
| |
| class RenderWidgetHostProcess : public MockRenderProcessHost { |
| public: |
| explicit RenderWidgetHostProcess(BrowserContext* browser_context) |
| : MockRenderProcessHost(browser_context) { |
| } |
| ~RenderWidgetHostProcess() override {} |
| |
| bool IsInitializedAndNotDead() const override { return true; } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostProcess); |
| }; |
| |
| // TestView -------------------------------------------------------------------- |
| |
| // This test view allows us to specify the size, and keep track of acked |
| // touch-events. |
| class TestView : public TestRenderWidgetHostView { |
| public: |
| explicit TestView(RenderWidgetHostImpl* rwh) |
| : TestRenderWidgetHostView(rwh), |
| unhandled_wheel_event_count_(0), |
| acked_event_count_(0), |
| gesture_event_type_(-1), |
| use_fake_compositor_viewport_pixel_size_(false), |
| ack_result_(INPUT_EVENT_ACK_STATE_UNKNOWN), |
| top_controls_height_(0.f), |
| bottom_controls_height_(0.f) {} |
| |
| // Sets the bounds returned by GetViewBounds. |
| void SetBounds(const gfx::Rect& bounds) override { |
| if (bounds_ == bounds) |
| return; |
| bounds_ = bounds; |
| local_surface_id_allocator_.GenerateId(); |
| } |
| |
| void SetScreenInfo(const ScreenInfo& screen_info) { |
| if (screen_info_ == screen_info) |
| return; |
| screen_info_ = screen_info; |
| local_surface_id_allocator_.GenerateId(); |
| } |
| |
| void GetScreenInfo(ScreenInfo* screen_info) const override { |
| *screen_info = screen_info_; |
| } |
| |
| void set_top_controls_height(float top_controls_height) { |
| top_controls_height_ = top_controls_height; |
| } |
| |
| void set_bottom_controls_height(float bottom_controls_height) { |
| bottom_controls_height_ = bottom_controls_height; |
| } |
| |
| const WebTouchEvent& acked_event() const { return acked_event_; } |
| int acked_event_count() const { return acked_event_count_; } |
| void ClearAckedEvent() { |
| acked_event_.SetType(blink::WebInputEvent::kUndefined); |
| acked_event_count_ = 0; |
| } |
| |
| const WebMouseWheelEvent& unhandled_wheel_event() const { |
| return unhandled_wheel_event_; |
| } |
| int unhandled_wheel_event_count() const { |
| return unhandled_wheel_event_count_; |
| } |
| int gesture_event_type() const { return gesture_event_type_; } |
| InputEventAckState ack_result() const { return ack_result_; } |
| |
| void SetMockCompositorViewportPixelSize( |
| const gfx::Size& mock_compositor_viewport_pixel_size) { |
| if (use_fake_compositor_viewport_pixel_size_ && |
| mock_compositor_viewport_pixel_size_ == |
| mock_compositor_viewport_pixel_size) { |
| return; |
| } |
| use_fake_compositor_viewport_pixel_size_ = true; |
| mock_compositor_viewport_pixel_size_ = mock_compositor_viewport_pixel_size; |
| local_surface_id_allocator_.GenerateId(); |
| } |
| void ClearMockCompositorViewportPixelSize() { |
| if (!use_fake_compositor_viewport_pixel_size_) |
| return; |
| use_fake_compositor_viewport_pixel_size_ = false; |
| local_surface_id_allocator_.GenerateId(); |
| } |
| |
| const viz::BeginFrameAck& last_did_not_produce_frame_ack() { |
| return last_did_not_produce_frame_ack_; |
| } |
| |
| // RenderWidgetHostView override. |
| gfx::Rect GetViewBounds() const override { return bounds_; } |
| float GetTopControlsHeight() const override { return top_controls_height_; } |
| float GetBottomControlsHeight() const override { |
| return bottom_controls_height_; |
| } |
| viz::LocalSurfaceId GetLocalSurfaceId() const override { |
| return local_surface_id_allocator_.GetCurrentLocalSurfaceId(); |
| } |
| |
| void ProcessAckedTouchEvent(const TouchEventWithLatencyInfo& touch, |
| InputEventAckState ack_result) override { |
| acked_event_ = touch.event; |
| ++acked_event_count_; |
| } |
| void WheelEventAck(const WebMouseWheelEvent& event, |
| InputEventAckState ack_result) override { |
| if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) |
| return; |
| unhandled_wheel_event_count_++; |
| unhandled_wheel_event_ = event; |
| } |
| void GestureEventAck(const WebGestureEvent& event, |
| InputEventAckState ack_result) override { |
| gesture_event_type_ = event.GetType(); |
| ack_result_ = ack_result; |
| } |
| gfx::Size GetCompositorViewportPixelSize() const override { |
| if (use_fake_compositor_viewport_pixel_size_) |
| return mock_compositor_viewport_pixel_size_; |
| return TestRenderWidgetHostView::GetCompositorViewportPixelSize(); |
| } |
| void OnDidNotProduceFrame(const viz::BeginFrameAck& ack) override { |
| last_did_not_produce_frame_ack_ = ack; |
| } |
| |
| protected: |
| WebMouseWheelEvent unhandled_wheel_event_; |
| int unhandled_wheel_event_count_; |
| WebTouchEvent acked_event_; |
| int acked_event_count_; |
| int gesture_event_type_; |
| gfx::Rect bounds_; |
| bool use_fake_compositor_viewport_pixel_size_; |
| gfx::Size mock_compositor_viewport_pixel_size_; |
| InputEventAckState ack_result_; |
| float top_controls_height_; |
| float bottom_controls_height_; |
| viz::BeginFrameAck last_did_not_produce_frame_ack_; |
| viz::ParentLocalSurfaceIdAllocator local_surface_id_allocator_; |
| ScreenInfo screen_info_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TestView); |
| }; |
| |
| // MockRenderViewHostDelegateView ------------------------------------------ |
| class MockRenderViewHostDelegateView : public RenderViewHostDelegateView { |
| public: |
| MockRenderViewHostDelegateView() = default; |
| ~MockRenderViewHostDelegateView() override = default; |
| |
| int start_dragging_count() const { return start_dragging_count_; } |
| |
| // RenderViewHostDelegateView: |
| void StartDragging(const DropData& drop_data, |
| blink::WebDragOperationsMask allowed_ops, |
| const gfx::ImageSkia& image, |
| const gfx::Vector2d& image_offset, |
| const DragEventSourceInfo& event_info, |
| RenderWidgetHostImpl* source_rwh) override { |
| ++start_dragging_count_; |
| } |
| |
| private: |
| int start_dragging_count_ = 0; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockRenderViewHostDelegateView); |
| }; |
| |
| // FakeRenderFrameMetadataObserver ----------------------------------------- |
| |
| // Fake out the renderer side of mojom::RenderFrameMetadataObserver, allowing |
| // for RenderWidgetHostImpl to be created. |
| // |
| // All methods are no-opts, the provided mojo request and info are held, but |
| // never bound. |
| class FakeRenderFrameMetadataObserver |
| : public mojom::RenderFrameMetadataObserver { |
| public: |
| FakeRenderFrameMetadataObserver( |
| mojom::RenderFrameMetadataObserverRequest request, |
| mojom::RenderFrameMetadataObserverClientPtrInfo client_info); |
| ~FakeRenderFrameMetadataObserver() override {} |
| |
| void ReportAllFrameSubmissionsForTesting(bool enabled) override {} |
| |
| private: |
| mojom::RenderFrameMetadataObserverRequest request_; |
| mojom::RenderFrameMetadataObserverClientPtrInfo client_info_; |
| DISALLOW_COPY_AND_ASSIGN(FakeRenderFrameMetadataObserver); |
| }; |
| |
| FakeRenderFrameMetadataObserver::FakeRenderFrameMetadataObserver( |
| mojom::RenderFrameMetadataObserverRequest request, |
| mojom::RenderFrameMetadataObserverClientPtrInfo client_info) |
| : request_(std::move(request)), client_info_(std::move(client_info)) {} |
| |
| // MockRenderWidgetHostDelegate -------------------------------------------- |
| |
| class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate { |
| public: |
| MockRenderWidgetHostDelegate() |
| : prehandle_keyboard_event_(false), |
| prehandle_keyboard_event_is_shortcut_(false), |
| prehandle_keyboard_event_called_(false), |
| prehandle_keyboard_event_type_(WebInputEvent::kUndefined), |
| unhandled_keyboard_event_called_(false), |
| unhandled_keyboard_event_type_(WebInputEvent::kUndefined), |
| handle_wheel_event_(false), |
| handle_wheel_event_called_(false), |
| unresponsive_timer_fired_(false), |
| render_view_host_delegate_view_(new MockRenderViewHostDelegateView()) {} |
| ~MockRenderWidgetHostDelegate() override {} |
| |
| // Tests that make sure we ignore keyboard event acknowledgments to events we |
| // didn't send work by making sure we didn't call UnhandledKeyboardEvent(). |
| bool unhandled_keyboard_event_called() const { |
| return unhandled_keyboard_event_called_; |
| } |
| |
| WebInputEvent::Type unhandled_keyboard_event_type() const { |
| return unhandled_keyboard_event_type_; |
| } |
| |
| bool prehandle_keyboard_event_called() const { |
| return prehandle_keyboard_event_called_; |
| } |
| |
| WebInputEvent::Type prehandle_keyboard_event_type() const { |
| return prehandle_keyboard_event_type_; |
| } |
| |
| void set_prehandle_keyboard_event(bool handle) { |
| prehandle_keyboard_event_ = handle; |
| } |
| |
| void set_handle_wheel_event(bool handle) { |
| handle_wheel_event_ = handle; |
| } |
| |
| void set_prehandle_keyboard_event_is_shortcut(bool is_shortcut) { |
| prehandle_keyboard_event_is_shortcut_ = is_shortcut; |
| } |
| |
| bool handle_wheel_event_called() const { return handle_wheel_event_called_; } |
| |
| bool unresponsive_timer_fired() const { return unresponsive_timer_fired_; } |
| |
| MockRenderViewHostDelegateView* mock_delegate_view() { |
| return render_view_host_delegate_view_.get(); |
| } |
| |
| RenderViewHostDelegateView* GetDelegateView() override { |
| return mock_delegate_view(); |
| } |
| |
| protected: |
| KeyboardEventProcessingResult PreHandleKeyboardEvent( |
| const NativeWebKeyboardEvent& event) override { |
| prehandle_keyboard_event_type_ = event.GetType(); |
| prehandle_keyboard_event_called_ = true; |
| if (prehandle_keyboard_event_) |
| return KeyboardEventProcessingResult::HANDLED; |
| return prehandle_keyboard_event_is_shortcut_ |
| ? KeyboardEventProcessingResult::NOT_HANDLED_IS_SHORTCUT |
| : KeyboardEventProcessingResult::NOT_HANDLED; |
| } |
| |
| void HandleKeyboardEvent(const NativeWebKeyboardEvent& event) override { |
| unhandled_keyboard_event_type_ = event.GetType(); |
| unhandled_keyboard_event_called_ = true; |
| } |
| |
| bool HandleWheelEvent(const blink::WebMouseWheelEvent& event) override { |
| handle_wheel_event_called_ = true; |
| return handle_wheel_event_; |
| } |
| |
| void RendererUnresponsive(RenderWidgetHostImpl* render_widget_host) override { |
| unresponsive_timer_fired_ = true; |
| } |
| |
| void ExecuteEditCommand( |
| const std::string& command, |
| const base::Optional<base::string16>& value) override {} |
| |
| void Cut() override {} |
| void Copy() override {} |
| void Paste() override {} |
| void SelectAll() override {} |
| |
| private: |
| bool prehandle_keyboard_event_; |
| bool prehandle_keyboard_event_is_shortcut_; |
| bool prehandle_keyboard_event_called_; |
| WebInputEvent::Type prehandle_keyboard_event_type_; |
| |
| bool unhandled_keyboard_event_called_; |
| WebInputEvent::Type unhandled_keyboard_event_type_; |
| |
| bool handle_wheel_event_; |
| bool handle_wheel_event_called_; |
| |
| bool unresponsive_timer_fired_; |
| |
| std::unique_ptr<MockRenderViewHostDelegateView> |
| render_view_host_delegate_view_; |
| }; |
| |
| enum WheelScrollingMode { |
| kWheelScrollingModeNone, |
| kWheelScrollLatching, |
| kAsyncWheelEvents, |
| }; |
| |
| // RenderWidgetHostTest -------------------------------------------------------- |
| |
| class RenderWidgetHostTest : public testing::Test { |
| public: |
| RenderWidgetHostTest( |
| WheelScrollingMode wheel_scrolling_mode = kWheelScrollLatching) |
| : process_(nullptr), |
| handle_key_press_event_(false), |
| handle_mouse_event_(false), |
| last_simulated_event_time_(ui::EventTimeForNow()), |
| wheel_scroll_latching_enabled_(wheel_scrolling_mode != |
| kWheelScrollingModeNone) { |
| std::vector<base::StringPiece> features; |
| std::vector<base::StringPiece> disabled_features; |
| |
| switch (wheel_scrolling_mode) { |
| case kWheelScrollingModeNone: |
| disabled_features.push_back( |
| features::kTouchpadAndWheelScrollLatching.name); |
| disabled_features.push_back(features::kAsyncWheelEvents.name); |
| break; |
| case kWheelScrollLatching: |
| features.push_back(features::kTouchpadAndWheelScrollLatching.name); |
| disabled_features.push_back(features::kAsyncWheelEvents.name); |
| break; |
| case kAsyncWheelEvents: |
| features.push_back(features::kTouchpadAndWheelScrollLatching.name); |
| features.push_back(features::kAsyncWheelEvents.name); |
| break; |
| } |
| |
| features.push_back(features::kVsyncAlignedInputEvents.name); |
| |
| feature_list_.InitFromCommandLine(base::JoinString(features, ","), |
| base::JoinString(disabled_features, ",")); |
| } |
| ~RenderWidgetHostTest() override {} |
| |
| bool KeyPressEventCallback(const NativeWebKeyboardEvent& /* event */) { |
| return handle_key_press_event_; |
| } |
| bool MouseEventCallback(const blink::WebMouseEvent& /* event */) { |
| return handle_mouse_event_; |
| } |
| |
| protected: |
| // testing::Test |
| void SetUp() override { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| command_line->AppendSwitch(switches::kValidateInputEventStream); |
| browser_context_.reset(new TestBrowserContext()); |
| delegate_.reset(new MockRenderWidgetHostDelegate()); |
| process_ = new RenderWidgetHostProcess(browser_context_.get()); |
| sink_ = &process_->sink(); |
| #if defined(USE_AURA) || defined(OS_MACOSX) |
| ImageTransportFactory::SetFactory( |
| std::make_unique<TestImageTransportFactory>()); |
| #endif |
| #if defined(OS_ANDROID) |
| ui::SetScreenAndroid(); // calls display::Screen::SetScreenInstance(). |
| #endif |
| #if defined(USE_AURA) |
| screen_.reset(aura::TestScreen::Create(gfx::Size())); |
| display::Screen::SetScreenInstance(screen_.get()); |
| #endif |
| host_.reset(MockRenderWidgetHost::Create(delegate_.get(), process_, |
| process_->GetNextRoutingID())); |
| view_.reset(new TestView(host_.get())); |
| ConfigureView(view_.get()); |
| host_->SetView(view_.get()); |
| SetInitialVisualProperties(); |
| host_->Init(); |
| host_->DisableGestureDebounce(); |
| |
| viz::mojom::CompositorFrameSinkPtr sink; |
| viz::mojom::CompositorFrameSinkRequest sink_request = |
| mojo::MakeRequest(&sink); |
| viz::mojom::CompositorFrameSinkClientRequest client_request = |
| mojo::MakeRequest(&renderer_compositor_frame_sink_ptr_); |
| renderer_compositor_frame_sink_ = |
| std::make_unique<FakeRendererCompositorFrameSink>( |
| std::move(sink), std::move(client_request)); |
| |
| mojom::RenderFrameMetadataObserverPtr |
| renderer_render_frame_metadata_observer_ptr; |
| mojom::RenderFrameMetadataObserverRequest |
| render_frame_metadata_observer_request = |
| mojo::MakeRequest(&renderer_render_frame_metadata_observer_ptr); |
| mojom::RenderFrameMetadataObserverClientPtrInfo |
| render_frame_metadata_observer_client_info; |
| mojom::RenderFrameMetadataObserverClientRequest |
| render_frame_metadata_observer_client_request = |
| mojo::MakeRequest(&render_frame_metadata_observer_client_info); |
| renderer_render_frame_metadata_observer_ = |
| std::make_unique<FakeRenderFrameMetadataObserver>( |
| std::move(render_frame_metadata_observer_request), |
| std::move(render_frame_metadata_observer_client_info)); |
| |
| host_->RequestCompositorFrameSink( |
| std::move(sink_request), |
| std::move(renderer_compositor_frame_sink_ptr_)); |
| host_->RegisterRenderFrameMetadataObserver( |
| std::move(render_frame_metadata_observer_client_request), |
| std::move(renderer_render_frame_metadata_observer_ptr)); |
| } |
| |
| void TearDown() override { |
| view_.reset(); |
| host_.reset(); |
| delegate_.reset(); |
| process_ = nullptr; |
| browser_context_.reset(); |
| |
| #if defined(USE_AURA) |
| display::Screen::SetScreenInstance(nullptr); |
| screen_.reset(); |
| #endif |
| #if defined(USE_AURA) || defined(OS_MACOSX) |
| ImageTransportFactory::Terminate(); |
| #endif |
| #if defined(OS_ANDROID) |
| display::Screen::SetScreenInstance(nullptr); |
| #endif |
| |
| // Process all pending tasks to avoid leaks. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SetInitialVisualProperties() { |
| VisualProperties visual_properties; |
| bool needs_ack = false; |
| host_->GetVisualProperties(&visual_properties, &needs_ack); |
| host_->SetInitialVisualProperties(visual_properties, needs_ack); |
| } |
| |
| virtual void ConfigureView(TestView* view) { |
| } |
| |
| base::TimeTicks GetNextSimulatedEventTime() { |
| last_simulated_event_time_ += simulated_event_time_delta_; |
| return last_simulated_event_time_; |
| } |
| |
| void SimulateKeyboardEvent(WebInputEvent::Type type) { |
| SimulateKeyboardEvent(type, 0); |
| } |
| |
| void SimulateKeyboardEvent(WebInputEvent::Type type, int modifiers) { |
| NativeWebKeyboardEvent native_event(type, modifiers, |
| GetNextSimulatedEventTime()); |
| host_->ForwardKeyboardEvent(native_event); |
| } |
| |
| void SimulateKeyboardEventWithCommands(WebInputEvent::Type type) { |
| NativeWebKeyboardEvent native_event(type, 0, GetNextSimulatedEventTime()); |
| EditCommands commands; |
| commands.emplace_back("name", "value"); |
| host_->ForwardKeyboardEventWithCommands(native_event, ui::LatencyInfo(), |
| &commands, nullptr); |
| } |
| |
| void SimulateMouseEvent(WebInputEvent::Type type) { |
| host_->ForwardMouseEvent(SyntheticWebMouseEventBuilder::Build(type)); |
| } |
| |
| void SimulateMouseEventWithLatencyInfo(WebInputEvent::Type type, |
| const ui::LatencyInfo& ui_latency) { |
| host_->ForwardMouseEventWithLatencyInfo( |
| SyntheticWebMouseEventBuilder::Build(type), |
| ui_latency); |
| } |
| |
| void SimulateWheelEvent(float dX, float dY, int modifiers, bool precise) { |
| host_->ForwardWheelEvent(SyntheticWebMouseWheelEventBuilder::Build( |
| 0, 0, dX, dY, modifiers, precise)); |
| } |
| |
| void SimulateWheelEventPossiblyIncludingPhase( |
| float dX, |
| float dY, |
| int modifiers, |
| bool precise, |
| WebMouseWheelEvent::Phase phase) { |
| WebMouseWheelEvent wheel_event = SyntheticWebMouseWheelEventBuilder::Build( |
| 0, 0, dX, dY, modifiers, precise); |
| if (wheel_scroll_latching_enabled_) |
| wheel_event.phase = phase; |
| host_->ForwardWheelEvent(wheel_event); |
| } |
| |
| void SimulateWheelEventWithLatencyInfo(float dX, |
| float dY, |
| int modifiers, |
| bool precise, |
| const ui::LatencyInfo& ui_latency) { |
| host_->ForwardWheelEventWithLatencyInfo( |
| SyntheticWebMouseWheelEventBuilder::Build(0, 0, dX, dY, modifiers, |
| precise), |
| ui_latency); |
| } |
| |
| void SimulateWheelEventWithLatencyInfoAndPossiblyPhase( |
| float dX, |
| float dY, |
| int modifiers, |
| bool precise, |
| const ui::LatencyInfo& ui_latency, |
| WebMouseWheelEvent::Phase phase) { |
| WebMouseWheelEvent wheel_event = SyntheticWebMouseWheelEventBuilder::Build( |
| 0, 0, dX, dY, modifiers, precise); |
| if (wheel_scroll_latching_enabled_) |
| wheel_event.phase = phase; |
| host_->ForwardWheelEventWithLatencyInfo(wheel_event, ui_latency); |
| } |
| |
| void SimulateMouseMove(int x, int y, int modifiers) { |
| SimulateMouseEvent(WebInputEvent::kMouseMove, x, y, modifiers, false); |
| } |
| |
| void SimulateMouseEvent( |
| WebInputEvent::Type type, int x, int y, int modifiers, bool pressed) { |
| WebMouseEvent event = |
| SyntheticWebMouseEventBuilder::Build(type, x, y, modifiers); |
| if (pressed) |
| event.button = WebMouseEvent::Button::kLeft; |
| event.SetTimeStamp(GetNextSimulatedEventTime()); |
| host_->ForwardMouseEvent(event); |
| } |
| |
| // Inject simple synthetic WebGestureEvent instances. |
| void SimulateGestureEvent(WebInputEvent::Type type, |
| WebGestureDevice sourceDevice) { |
| host_->ForwardGestureEvent( |
| SyntheticWebGestureEventBuilder::Build(type, sourceDevice)); |
| } |
| |
| void SimulateGestureEventWithLatencyInfo(WebInputEvent::Type type, |
| WebGestureDevice sourceDevice, |
| const ui::LatencyInfo& ui_latency) { |
| host_->ForwardGestureEventWithLatencyInfo( |
| SyntheticWebGestureEventBuilder::Build(type, sourceDevice), ui_latency); |
| } |
| |
| // Set the timestamp for the touch-event. |
| void SetTouchTimestamp(base::TimeTicks timestamp) { |
| touch_event_.SetTimestamp(timestamp); |
| } |
| |
| // Sends a touch event (irrespective of whether the page has a touch-event |
| // handler or not). |
| uint32_t SendTouchEvent() { |
| uint32_t touch_event_id = touch_event_.unique_touch_event_id; |
| host_->ForwardTouchEventWithLatencyInfo(touch_event_, ui::LatencyInfo()); |
| |
| touch_event_.ResetPoints(); |
| return touch_event_id; |
| } |
| |
| int PressTouchPoint(int x, int y) { |
| return touch_event_.PressPoint(x, y); |
| } |
| |
| void MoveTouchPoint(int index, int x, int y) { |
| touch_event_.MovePoint(index, x, y); |
| } |
| |
| void ReleaseTouchPoint(int index) { |
| touch_event_.ReleasePoint(index); |
| } |
| |
| const WebInputEvent* GetInputEventFromMessage(const IPC::Message& message) { |
| base::PickleIterator iter(message); |
| const char* data; |
| int data_length; |
| if (!iter.ReadData(&data, &data_length)) |
| return nullptr; |
| return reinterpret_cast<const WebInputEvent*>(data); |
| } |
| |
| void UnhandledWheelEvent(); |
| void HandleWheelEvent(); |
| void InputEventRWHLatencyComponent(); |
| |
| std::unique_ptr<TestBrowserContext> browser_context_; |
| RenderWidgetHostProcess* process_; // Deleted automatically by the widget. |
| std::unique_ptr<MockRenderWidgetHostDelegate> delegate_; |
| std::unique_ptr<MockRenderWidgetHost> host_; |
| std::unique_ptr<TestView> view_; |
| std::unique_ptr<display::Screen> screen_; |
| bool handle_key_press_event_; |
| bool handle_mouse_event_; |
| base::TimeTicks last_simulated_event_time_; |
| base::TimeDelta simulated_event_time_delta_; |
| IPC::TestSink* sink_; |
| std::unique_ptr<FakeRendererCompositorFrameSink> |
| renderer_compositor_frame_sink_; |
| std::unique_ptr<FakeRenderFrameMetadataObserver> |
| renderer_render_frame_metadata_observer_; |
| bool wheel_scroll_latching_enabled_; |
| |
| private: |
| SyntheticWebTouchEvent touch_event_; |
| |
| TestBrowserThreadBundle thread_bundle_; |
| base::test::ScopedFeatureList feature_list_; |
| viz::mojom::CompositorFrameSinkClientPtr renderer_compositor_frame_sink_ptr_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostTest); |
| }; |
| |
| class RenderWidgetHostWheelScrollLatchingDisabledTest |
| : public RenderWidgetHostTest { |
| public: |
| RenderWidgetHostWheelScrollLatchingDisabledTest() |
| : RenderWidgetHostTest(kWheelScrollingModeNone) {} |
| }; |
| |
| class RenderWidgetHostAsyncWheelEventsEnabledTest |
| : public RenderWidgetHostTest { |
| public: |
| RenderWidgetHostAsyncWheelEventsEnabledTest() |
| : RenderWidgetHostTest(kAsyncWheelEvents) {} |
| }; |
| |
| // RenderWidgetHostWithSourceTest ---------------------------------------------- |
| |
| // This is for tests that are to be run for all source devices. |
| class RenderWidgetHostWithSourceTest |
| : public RenderWidgetHostTest, |
| public testing::WithParamInterface<WebGestureDevice> {}; |
| |
| } // namespace |
| |
| // ----------------------------------------------------------------------------- |
| |
| TEST_F(RenderWidgetHostTest, Resize) { |
| // The initial bounds is the empty rect, so setting it to the same thing |
| // shouldn't send the resize message. |
| view_->SetBounds(gfx::Rect()); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_FALSE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| |
| // No resize ack if the physical backing gets set, but the view bounds are |
| // zero. |
| view_->SetMockCompositorViewportPixelSize(gfx::Size(200, 200)); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| |
| // Setting the view bounds to nonzero should send out the notification. |
| // but should not expect ack for empty physical backing size. |
| gfx::Rect original_size(0, 0, 100, 100); |
| process_->sink().ClearMessages(); |
| view_->SetBounds(original_size); |
| view_->SetMockCompositorViewportPixelSize(gfx::Size()); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_EQ(original_size.size(), host_->old_visual_properties_->new_size); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| |
| // Setting the bounds and physical backing size to nonzero should send out |
| // the notification and expect an ack. |
| process_->sink().ClearMessages(); |
| view_->ClearMockCompositorViewportPixelSize(); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_TRUE(host_->visual_properties_ack_pending_); |
| EXPECT_EQ(original_size.size(), host_->old_visual_properties_->new_size); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| cc::RenderFrameMetadata metadata; |
| metadata.viewport_size_in_pixels = original_size.size(); |
| metadata.local_surface_id = base::nullopt; |
| host_->DidUpdateVisualProperties(metadata); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| |
| process_->sink().ClearMessages(); |
| gfx::Rect second_size(0, 0, 110, 110); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| view_->SetBounds(second_size); |
| EXPECT_TRUE(host_->SynchronizeVisualProperties()); |
| EXPECT_TRUE(host_->visual_properties_ack_pending_); |
| |
| // Sending out a new notification should NOT send out a new IPC message since |
| // a resize ACK is pending. |
| gfx::Rect third_size(0, 0, 120, 120); |
| process_->sink().ClearMessages(); |
| view_->SetBounds(third_size); |
| EXPECT_FALSE(host_->SynchronizeVisualProperties()); |
| EXPECT_TRUE(host_->visual_properties_ack_pending_); |
| EXPECT_FALSE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| |
| // Send a update that's a resize ack, but for the original_size we sent. Since |
| // this isn't the second_size, the message handler should immediately send |
| // a new resize message for the new size to the renderer. |
| process_->sink().ClearMessages(); |
| metadata.viewport_size_in_pixels = original_size.size(); |
| metadata.local_surface_id = base::nullopt; |
| host_->DidUpdateVisualProperties(metadata); |
| EXPECT_TRUE(host_->visual_properties_ack_pending_); |
| EXPECT_EQ(third_size.size(), host_->old_visual_properties_->new_size); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| |
| // Send the resize ack for the latest size. |
| process_->sink().ClearMessages(); |
| metadata.viewport_size_in_pixels = third_size.size(); |
| metadata.local_surface_id = base::nullopt; |
| host_->DidUpdateVisualProperties(metadata); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_EQ(third_size.size(), host_->old_visual_properties_->new_size); |
| EXPECT_FALSE(process_->sink().GetFirstMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| |
| // Now clearing the bounds should send out a notification but we shouldn't |
| // expect a resize ack (since the renderer won't ack empty sizes). The message |
| // should contain the new size (0x0) and not the previous one that we skipped |
| process_->sink().ClearMessages(); |
| view_->SetBounds(gfx::Rect()); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_EQ(gfx::Size(), host_->old_visual_properties_->new_size); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| |
| // Send a rect that has no area but has either width or height set. |
| process_->sink().ClearMessages(); |
| view_->SetBounds(gfx::Rect(0, 0, 0, 30)); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_EQ(gfx::Size(0, 30), host_->old_visual_properties_->new_size); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| |
| // Set the same size again. It should not be sent again. |
| process_->sink().ClearMessages(); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_EQ(gfx::Size(0, 30), host_->old_visual_properties_->new_size); |
| EXPECT_FALSE(process_->sink().GetFirstMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| |
| // A different size should be sent again, however. |
| view_->SetBounds(gfx::Rect(0, 0, 0, 31)); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_EQ(gfx::Size(0, 31), host_->old_visual_properties_->new_size); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| } |
| |
| // Test that a resize event is sent if SynchronizeVisualProperties() is called |
| // after a ScreenInfo change. |
| TEST_F(RenderWidgetHostTest, ResizeScreenInfo) { |
| ScreenInfo screen_info; |
| screen_info.device_scale_factor = 1.f; |
| screen_info.rect = blink::WebRect(0, 0, 800, 600); |
| screen_info.available_rect = blink::WebRect(0, 0, 800, 600); |
| screen_info.orientation_angle = 0; |
| screen_info.orientation_type = SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY; |
| |
| view_->SetScreenInfo(screen_info); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| process_->sink().ClearMessages(); |
| |
| screen_info.orientation_angle = 180; |
| screen_info.orientation_type = SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY; |
| |
| view_->SetScreenInfo(screen_info); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| process_->sink().ClearMessages(); |
| |
| screen_info.device_scale_factor = 2.f; |
| |
| view_->SetScreenInfo(screen_info); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| process_->sink().ClearMessages(); |
| |
| // No screen change. |
| view_->SetScreenInfo(screen_info); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_FALSE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| } |
| |
| // Test for crbug.com/25097. If a renderer crashes between a resize and the |
| // corresponding update message, we must be sure to clear the resize ack logic. |
| TEST_F(RenderWidgetHostTest, ResizeThenCrash) { |
| // Clear the first Resize message that carried screen info. |
| process_->sink().ClearMessages(); |
| |
| // Setting the bounds to a "real" rect should send out the notification. |
| gfx::Rect original_size(0, 0, 100, 100); |
| view_->SetBounds(original_size); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_TRUE(host_->visual_properties_ack_pending_); |
| EXPECT_EQ(original_size.size(), host_->old_visual_properties_->new_size); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| |
| // Simulate a renderer crash before the update message. Ensure all the |
| // resize ack logic is cleared. Must clear the view first so it doesn't get |
| // deleted. |
| host_->SetView(nullptr); |
| host_->RendererExited(base::TERMINATION_STATUS_PROCESS_CRASHED, -1); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_EQ(nullptr, host_->old_visual_properties_); |
| |
| // Reset the view so we can exit the test cleanly. |
| host_->SetView(view_.get()); |
| } |
| |
| // Unable to include render_widget_host_view_mac.h and compile. |
| #if !defined(OS_MACOSX) |
| // Tests setting background transparency. |
| TEST_F(RenderWidgetHostTest, Background) { |
| std::unique_ptr<RenderWidgetHostViewBase> view; |
| #if defined(USE_AURA) |
| view.reset(new RenderWidgetHostViewAura( |
| host_.get(), false, false /* is_mus_browser_plugin_guest */)); |
| // TODO(derat): Call this on all platforms: http://crbug.com/102450. |
| view->InitAsChild(nullptr); |
| #elif defined(OS_ANDROID) |
| view.reset(new RenderWidgetHostViewAndroid(host_.get(), NULL)); |
| #endif |
| host_->SetView(view.get()); |
| |
| ASSERT_FALSE(view->GetBackgroundColor()); |
| view->SetBackgroundColor(SK_ColorTRANSPARENT); |
| ASSERT_TRUE(view->GetBackgroundColor()); |
| EXPECT_EQ(static_cast<unsigned>(SK_ColorTRANSPARENT), |
| *view->GetBackgroundColor()); |
| |
| const IPC::Message* set_background = |
| process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SetBackgroundOpaque::ID); |
| ASSERT_TRUE(set_background); |
| std::tuple<bool> sent_background; |
| ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background); |
| EXPECT_FALSE(std::get<0>(sent_background)); |
| |
| host_->SetView(nullptr); |
| static_cast<RenderWidgetHostViewBase*>(view.release())->Destroy(); |
| } |
| #endif |
| |
| // Test that we don't paint when we're hidden, but we still send the ACK. Most |
| // of the rest of the painting is tested in the GetBackingStore* ones. |
| TEST_F(RenderWidgetHostTest, HiddenPaint) { |
| // Hide the widget, it should have sent out a message to the renderer. |
| EXPECT_FALSE(host_->is_hidden_); |
| host_->WasHidden(); |
| EXPECT_TRUE(host_->is_hidden_); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(ViewMsg_WasHidden::ID)); |
| |
| // Send it an update as from the renderer. |
| process_->sink().ClearMessages(); |
| cc::RenderFrameMetadata metadata; |
| metadata.viewport_size_in_pixels = gfx::Size(100, 100); |
| metadata.local_surface_id = base::nullopt; |
| host_->DidUpdateVisualProperties(metadata); |
| |
| // Now unhide. |
| process_->sink().ClearMessages(); |
| host_->WasShown(false /* record_presentation_time */); |
| EXPECT_FALSE(host_->is_hidden_); |
| |
| // It should have sent out a restored message with a request to paint. |
| const IPC::Message* restored = process_->sink().GetUniqueMessageMatching( |
| ViewMsg_WasShown::ID); |
| ASSERT_TRUE(restored); |
| std::tuple<bool, base::TimeTicks> needs_repaint; |
| ViewMsg_WasShown::Read(restored, &needs_repaint); |
| EXPECT_TRUE(std::get<0>(needs_repaint)); |
| } |
| |
| TEST_F(RenderWidgetHostTest, IgnoreKeyEventsHandledByRenderer) { |
| // Simulate a keyboard event. |
| SimulateKeyboardEvent(WebInputEvent::kRawKeyDown); |
| |
| // Make sure we sent the input event to the renderer. |
| MockWidgetInputHandler::MessageVector dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| ASSERT_EQ(1u, dispatched_events.size()); |
| ASSERT_TRUE(dispatched_events[0]->ToEvent()); |
| |
| // Send the simulated response from the renderer back. |
| dispatched_events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED); |
| EXPECT_FALSE(delegate_->unhandled_keyboard_event_called()); |
| } |
| |
| TEST_F(RenderWidgetHostTest, SendEditCommandsBeforeKeyEvent) { |
| // Simulate a keyboard event. |
| SimulateKeyboardEventWithCommands(WebInputEvent::kRawKeyDown); |
| |
| // Make sure we sent commands and key event to the renderer. |
| MockWidgetInputHandler::MessageVector dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| ASSERT_EQ(2u, dispatched_events.size()); |
| |
| ASSERT_TRUE(dispatched_events[0]->ToEditCommand()); |
| ASSERT_TRUE(dispatched_events[1]->ToEvent()); |
| // Send the simulated response from the renderer back. |
| dispatched_events[1]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED); |
| } |
| |
| TEST_F(RenderWidgetHostTest, PreHandleRawKeyDownEvent) { |
| // Simulate the situation that the browser handled the key down event during |
| // pre-handle phrase. |
| delegate_->set_prehandle_keyboard_event(true); |
| |
| // Simulate a keyboard event. |
| SimulateKeyboardEventWithCommands(WebInputEvent::kRawKeyDown); |
| |
| EXPECT_TRUE(delegate_->prehandle_keyboard_event_called()); |
| EXPECT_EQ(WebInputEvent::kRawKeyDown, |
| delegate_->prehandle_keyboard_event_type()); |
| |
| // Make sure the commands and key event are not sent to the renderer. |
| MockWidgetInputHandler::MessageVector dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| EXPECT_EQ(0u, dispatched_events.size()); |
| |
| // The browser won't pre-handle a Char event. |
| delegate_->set_prehandle_keyboard_event(false); |
| |
| // Forward the Char event. |
| SimulateKeyboardEvent(WebInputEvent::kChar); |
| |
| // Make sure the Char event is suppressed. |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| EXPECT_EQ(0u, dispatched_events.size()); |
| |
| // Forward the KeyUp event. |
| SimulateKeyboardEvent(WebInputEvent::kKeyUp); |
| |
| // Make sure the KeyUp event is suppressed. |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| EXPECT_EQ(0u, dispatched_events.size()); |
| |
| // Simulate a new RawKeyDown event. |
| SimulateKeyboardEvent(WebInputEvent::kRawKeyDown); |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| ASSERT_EQ(1u, dispatched_events.size()); |
| ASSERT_TRUE(dispatched_events[0]->ToEvent()); |
| EXPECT_EQ(WebInputEvent::kRawKeyDown, |
| dispatched_events[0]->ToEvent()->Event()->web_event->GetType()); |
| |
| // Send the simulated response from the renderer back. |
| dispatched_events[0]->ToEvent()->CallCallback( |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| EXPECT_TRUE(delegate_->unhandled_keyboard_event_called()); |
| EXPECT_EQ(WebInputEvent::kRawKeyDown, |
| delegate_->unhandled_keyboard_event_type()); |
| } |
| |
| TEST_F(RenderWidgetHostTest, RawKeyDownShortcutEvent) { |
| // Simulate the situation that the browser marks the key down as a keyboard |
| // shortcut, but doesn't consume it in the pre-handle phase. |
| delegate_->set_prehandle_keyboard_event_is_shortcut(true); |
| |
| // Simulate a keyboard event. |
| SimulateKeyboardEvent(WebInputEvent::kRawKeyDown); |
| |
| EXPECT_TRUE(delegate_->prehandle_keyboard_event_called()); |
| EXPECT_EQ(WebInputEvent::kRawKeyDown, |
| delegate_->prehandle_keyboard_event_type()); |
| |
| // Make sure the RawKeyDown event is sent to the renderer. |
| MockWidgetInputHandler::MessageVector dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| ASSERT_EQ(1u, dispatched_events.size()); |
| ASSERT_TRUE(dispatched_events[0]->ToEvent()); |
| EXPECT_EQ(WebInputEvent::kRawKeyDown, |
| dispatched_events[0]->ToEvent()->Event()->web_event->GetType()); |
| |
| // Send the simulated response from the renderer back. |
| dispatched_events[0]->ToEvent()->CallCallback( |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(WebInputEvent::kRawKeyDown, |
| delegate_->unhandled_keyboard_event_type()); |
| |
| // The browser won't pre-handle a Char event. |
| delegate_->set_prehandle_keyboard_event_is_shortcut(false); |
| |
| // Forward the Char event. |
| SimulateKeyboardEvent(WebInputEvent::kChar); |
| |
| // The Char event is not suppressed; the renderer will ignore it |
| // if the preceding RawKeyDown shortcut goes unhandled. |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| ASSERT_EQ(1u, dispatched_events.size()); |
| ASSERT_TRUE(dispatched_events[0]->ToEvent()); |
| EXPECT_EQ(WebInputEvent::kChar, |
| dispatched_events[0]->ToEvent()->Event()->web_event->GetType()); |
| |
| // Send the simulated response from the renderer back. |
| dispatched_events[0]->ToEvent()->CallCallback( |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(WebInputEvent::kChar, delegate_->unhandled_keyboard_event_type()); |
| |
| // Forward the KeyUp event. |
| SimulateKeyboardEvent(WebInputEvent::kKeyUp); |
| |
| // Make sure only KeyUp was sent to the renderer. |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| ASSERT_EQ(1u, dispatched_events.size()); |
| ASSERT_TRUE(dispatched_events[0]->ToEvent()); |
| EXPECT_EQ(WebInputEvent::kKeyUp, |
| dispatched_events[0]->ToEvent()->Event()->web_event->GetType()); |
| |
| // Send the simulated response from the renderer back. |
| dispatched_events[0]->ToEvent()->CallCallback( |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| EXPECT_EQ(WebInputEvent::kKeyUp, delegate_->unhandled_keyboard_event_type()); |
| } |
| |
| void RenderWidgetHostTest::UnhandledWheelEvent() { |
| SimulateWheelEventPossiblyIncludingPhase(-5, 0, 0, true, |
| WebMouseWheelEvent::kPhaseBegan); |
| |
| MockWidgetInputHandler::MessageVector dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| ASSERT_EQ(1u, dispatched_events.size()); |
| ASSERT_TRUE(dispatched_events[0]->ToEvent()); |
| EXPECT_EQ(WebInputEvent::kMouseWheel, |
| dispatched_events[0]->ToEvent()->Event()->web_event->GetType()); |
| |
| // Send the simulated response from the renderer back. |
| dispatched_events[0]->ToEvent()->CallCallback( |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| EXPECT_TRUE(delegate_->handle_wheel_event_called()); |
| EXPECT_EQ(1, view_->unhandled_wheel_event_count()); |
| EXPECT_EQ(-5, view_->unhandled_wheel_event().delta_x); |
| } |
| TEST_F(RenderWidgetHostTest, UnhandledWheelEvent) { |
| UnhandledWheelEvent(); |
| } |
| TEST_F(RenderWidgetHostWheelScrollLatchingDisabledTest, UnhandledWheelEvent) { |
| UnhandledWheelEvent(); |
| } |
| TEST_F(RenderWidgetHostAsyncWheelEventsEnabledTest, UnhandledWheelEvent) { |
| UnhandledWheelEvent(); |
| } |
| |
| void RenderWidgetHostTest::HandleWheelEvent() { |
| // Indicate that we're going to handle this wheel event |
| delegate_->set_handle_wheel_event(true); |
| |
| SimulateWheelEventPossiblyIncludingPhase(-5, 0, 0, true, |
| WebMouseWheelEvent::kPhaseBegan); |
| |
| MockWidgetInputHandler::MessageVector dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| ASSERT_EQ(1u, dispatched_events.size()); |
| ASSERT_TRUE(dispatched_events[0]->ToEvent()); |
| EXPECT_EQ(WebInputEvent::kMouseWheel, |
| dispatched_events[0]->ToEvent()->Event()->web_event->GetType()); |
| |
| // Send the simulated response from the renderer back. |
| dispatched_events[0]->ToEvent()->CallCallback( |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| // ensure the wheel event handler was invoked |
| EXPECT_TRUE(delegate_->handle_wheel_event_called()); |
| |
| // and that it suppressed the unhandled wheel event handler. |
| EXPECT_EQ(0, view_->unhandled_wheel_event_count()); |
| } |
| TEST_F(RenderWidgetHostTest, HandleWheelEvent) { |
| HandleWheelEvent(); |
| } |
| TEST_F(RenderWidgetHostWheelScrollLatchingDisabledTest, HandleWheelEvent) { |
| HandleWheelEvent(); |
| } |
| TEST_F(RenderWidgetHostAsyncWheelEventsEnabledTest, HandleWheelEvent) { |
| HandleWheelEvent(); |
| } |
| |
| TEST_F(RenderWidgetHostTest, UnhandledGestureEvent) { |
| SimulateGestureEvent(WebInputEvent::kGestureTwoFingerTap, |
| blink::kWebGestureDeviceTouchscreen); |
| |
| MockWidgetInputHandler::MessageVector dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| ASSERT_EQ(1u, dispatched_events.size()); |
| ASSERT_TRUE(dispatched_events[0]->ToEvent()); |
| EXPECT_EQ(WebInputEvent::kGestureTwoFingerTap, |
| dispatched_events[0]->ToEvent()->Event()->web_event->GetType()); |
| |
| // Send the simulated response from the renderer back. |
| dispatched_events[0]->ToEvent()->CallCallback( |
| INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| EXPECT_EQ(WebInputEvent::kGestureTwoFingerTap, view_->gesture_event_type()); |
| EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, view_->ack_result()); |
| } |
| |
| // Test that the hang monitor timer expires properly if a new timer is started |
| // while one is in progress (see crbug.com/11007). |
| TEST_F(RenderWidgetHostTest, DontPostponeHangMonitorTimeout) { |
| // Start with a short timeout. |
| host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10)); |
| |
| // Immediately try to add a long 30 second timeout. |
| EXPECT_FALSE(delegate_->unresponsive_timer_fired()); |
| host_->StartHangMonitorTimeout(TimeDelta::FromSeconds(30)); |
| |
| // Wait long enough for first timeout and see if it fired. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), |
| TimeDelta::FromMilliseconds(10)); |
| base::RunLoop().Run(); |
| EXPECT_TRUE(delegate_->unresponsive_timer_fired()); |
| } |
| |
| // Test that the hang monitor timer expires properly if it is started, stopped, |
| // and then started again. |
| TEST_F(RenderWidgetHostTest, StopAndStartHangMonitorTimeout) { |
| // Start with a short timeout, then stop it. |
| host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10)); |
| host_->StopHangMonitorTimeout(); |
| |
| // Start it again to ensure it still works. |
| EXPECT_FALSE(delegate_->unresponsive_timer_fired()); |
| host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(10)); |
| |
| // Wait long enough for first timeout and see if it fired. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), |
| TimeDelta::FromMilliseconds(40)); |
| base::RunLoop().Run(); |
| EXPECT_TRUE(delegate_->unresponsive_timer_fired()); |
| } |
| |
| // Test that the hang monitor timer expires properly if it is started, then |
| // updated to a shorter duration. |
| TEST_F(RenderWidgetHostTest, ShorterDelayHangMonitorTimeout) { |
| // Start with a timeout. |
| host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(100)); |
| |
| // Start it again with shorter delay. |
| EXPECT_FALSE(delegate_->unresponsive_timer_fired()); |
| host_->StartHangMonitorTimeout(TimeDelta::FromMilliseconds(20)); |
| |
| // Wait long enough for the second timeout and see if it fired. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), |
| TimeDelta::FromMilliseconds(25)); |
| base::RunLoop().Run(); |
| EXPECT_TRUE(delegate_->unresponsive_timer_fired()); |
| } |
| |
| // Test that the hang monitor timer is effectively disabled when the widget is |
| // hidden. |
| TEST_F(RenderWidgetHostTest, HangMonitorTimeoutDisabledForInputWhenHidden) { |
| host_->set_hung_renderer_delay(base::TimeDelta::FromMicroseconds(1)); |
| SimulateMouseEvent(WebInputEvent::kMouseMove, 10, 10, 0, false); |
| |
| // Hiding the widget should deactivate the timeout. |
| host_->WasHidden(); |
| |
| // The timeout should not fire. |
| EXPECT_FALSE(delegate_->unresponsive_timer_fired()); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), |
| TimeDelta::FromMicroseconds(2)); |
| base::RunLoop().Run(); |
| EXPECT_FALSE(delegate_->unresponsive_timer_fired()); |
| |
| // The timeout should never reactivate while hidden. |
| SimulateMouseEvent(WebInputEvent::kMouseMove, 10, 10, 0, false); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), |
| TimeDelta::FromMicroseconds(2)); |
| base::RunLoop().Run(); |
| EXPECT_FALSE(delegate_->unresponsive_timer_fired()); |
| |
| // Showing the widget should restore the timeout, as the events have |
| // not yet been ack'ed. |
| host_->WasShown(false /* record_presentation_time */); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), |
| TimeDelta::FromMicroseconds(2)); |
| base::RunLoop().Run(); |
| EXPECT_TRUE(delegate_->unresponsive_timer_fired()); |
| } |
| |
| // Test that the hang monitor catches two input events but only one ack. |
| // This can happen if the second input event causes the renderer to hang. |
| // This test will catch a regression of crbug.com/111185. |
| TEST_F(RenderWidgetHostTest, MultipleInputEvents) { |
| // Configure the host to wait 10ms before considering |
| // the renderer hung. |
| host_->set_hung_renderer_delay(base::TimeDelta::FromMicroseconds(10)); |
| |
| // Send two events but only one ack. |
| SimulateKeyboardEvent(WebInputEvent::kRawKeyDown); |
| SimulateKeyboardEvent(WebInputEvent::kRawKeyDown); |
| |
| MockWidgetInputHandler::MessageVector dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| ASSERT_EQ(2u, dispatched_events.size()); |
| ASSERT_TRUE(dispatched_events[0]->ToEvent()); |
| |
| // Send the simulated response from the renderer back. |
| dispatched_events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED); |
| |
| // Wait long enough for first timeout and see if it fired. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), |
| TimeDelta::FromMicroseconds(20)); |
| base::RunLoop().Run(); |
| EXPECT_TRUE(delegate_->unresponsive_timer_fired()); |
| } |
| |
| // Test that the rendering timeout for newly loaded content fires when enough |
| // time passes without receiving a new compositor frame. This test assumes |
| // Surface Synchronization is off. |
| TEST_F(RenderWidgetHostTest, NewContentRenderingTimeoutWithoutSurfaceSync) { |
| // If Surface Synchronization is on, we have a separate code path for |
| // cancelling new content rendering timeout that is tested separately. |
| if (features::IsSurfaceSynchronizationEnabled()) |
| return; |
| |
| const viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| |
| // Mocking |renderer_compositor_frame_sink_| to prevent crashes in |
| // renderer_compositor_frame_sink_->DidReceiveCompositorFrameAck(resources). |
| std::unique_ptr<viz::MockCompositorFrameSinkClient> |
| mock_compositor_frame_sink_client = |
| std::make_unique<viz::MockCompositorFrameSinkClient>(); |
| host_->SetMockRendererCompositorFrameSink( |
| mock_compositor_frame_sink_client.get()); |
| |
| host_->set_new_content_rendering_delay_for_testing( |
| base::TimeDelta::FromMicroseconds(10)); |
| |
| // Start the timer and immediately send a CompositorFrame with the |
| // content_source_id of the new page. The timeout shouldn't fire. |
| host_->DidNavigate(5); |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetContentSourceId(5) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), |
| TimeDelta::FromMicroseconds(20)); |
| base::RunLoop().Run(); |
| |
| EXPECT_FALSE(host_->new_content_rendering_timeout_fired()); |
| host_->reset_new_content_rendering_timeout_fired(); |
| |
| // Start the timer but receive frames only from the old page. The timer |
| // should fire. |
| host_->DidNavigate(10); |
| frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetContentSourceId(9) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), |
| TimeDelta::FromMicroseconds(20)); |
| base::RunLoop().Run(); |
| |
| EXPECT_TRUE(host_->new_content_rendering_timeout_fired()); |
| host_->reset_new_content_rendering_timeout_fired(); |
| |
| // Send a CompositorFrame with content_source_id of the new page before we |
| // attempt to start the timer. The timer shouldn't fire. |
| frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetContentSourceId(7) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| host_->DidNavigate(7); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), |
| TimeDelta::FromMicroseconds(20)); |
| base::RunLoop().Run(); |
| |
| EXPECT_FALSE(host_->new_content_rendering_timeout_fired()); |
| host_->reset_new_content_rendering_timeout_fired(); |
| |
| // Don't send any frames after the timer starts. The timer should fire. |
| host_->DidNavigate(20); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated(), |
| TimeDelta::FromMicroseconds(20)); |
| base::RunLoop().Run(); |
| EXPECT_TRUE(host_->new_content_rendering_timeout_fired()); |
| host_->reset_new_content_rendering_timeout_fired(); |
| } |
| |
| // This tests that a compositor frame received with a stale content source ID |
| // in its metadata is properly discarded. |
| TEST_F(RenderWidgetHostTest, SwapCompositorFrameWithBadSourceId) { |
| // If Surface Synchronization is on, we don't keep track of content_source_id |
| // in CompositorFrameMetadata. |
| if (features::IsSurfaceSynchronizationEnabled()) |
| return; |
| const viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| |
| host_->DidNavigate(100); |
| host_->set_new_content_rendering_delay_for_testing( |
| base::TimeDelta::FromMicroseconds(9999)); |
| |
| { |
| // First swap a frame with an invalid ID. |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetBeginFrameAck(viz::BeginFrameAck(0, 1, true)) |
| .SetContentSourceId(99) |
| .Build(); |
| |
| // Mocking |renderer_compositor_frame_sink_| to prevent crashes in |
| // renderer_compositor_frame_sink_->DidReceiveCompositorFrameAck(resources). |
| std::unique_ptr<viz::MockCompositorFrameSinkClient> |
| mock_compositor_frame_sink_client = |
| std::make_unique<viz::MockCompositorFrameSinkClient>(); |
| host_->SetMockRendererCompositorFrameSink( |
| mock_compositor_frame_sink_client.get()); |
| |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_FALSE( |
| static_cast<TestView*>(host_->GetView())->did_swap_compositor_frame()); |
| EXPECT_EQ(viz::BeginFrameAck(0, 1, false), |
| static_cast<TestView*>(host_->GetView()) |
| ->last_did_not_produce_frame_ack()); |
| static_cast<TestView*>(host_->GetView())->reset_did_swap_compositor_frame(); |
| } |
| |
| { |
| // Test with a valid content ID as a control. |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetContentSourceId(100) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_TRUE( |
| static_cast<TestView*>(host_->GetView())->did_swap_compositor_frame()); |
| static_cast<TestView*>(host_->GetView())->reset_did_swap_compositor_frame(); |
| } |
| |
| { |
| // We also accept frames with higher content IDs, to cover the case where |
| // the browser process receives a compositor frame for a new page before |
| // the corresponding DidCommitProvisionalLoad (it's a race). |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetContentSourceId(101) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_TRUE( |
| static_cast<TestView*>(host_->GetView())->did_swap_compositor_frame()); |
| } |
| } |
| |
| TEST_F(RenderWidgetHostTest, IgnoreInputEvent) { |
| host_->SetupForInputRouterTest(); |
| |
| host_->SetIgnoreInputEvents(true); |
| |
| SimulateKeyboardEvent(WebInputEvent::kRawKeyDown); |
| EXPECT_FALSE(host_->mock_input_router()->sent_keyboard_event_); |
| |
| SimulateMouseEvent(WebInputEvent::kMouseMove); |
| EXPECT_FALSE(host_->mock_input_router()->sent_mouse_event_); |
| |
| SimulateWheelEvent(0, 100, 0, true); |
| EXPECT_FALSE(host_->mock_input_router()->sent_wheel_event_); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchpad); |
| EXPECT_FALSE(host_->mock_input_router()->sent_gesture_event_); |
| |
| PressTouchPoint(100, 100); |
| SendTouchEvent(); |
| EXPECT_FALSE(host_->mock_input_router()->send_touch_event_not_cancelled_); |
| } |
| |
| TEST_F(RenderWidgetHostTest, KeyboardListenerIgnoresEvent) { |
| host_->SetupForInputRouterTest(); |
| host_->AddKeyPressEventCallback( |
| base::Bind(&RenderWidgetHostTest::KeyPressEventCallback, |
| base::Unretained(this))); |
| handle_key_press_event_ = false; |
| SimulateKeyboardEvent(WebInputEvent::kRawKeyDown); |
| |
| EXPECT_TRUE(host_->mock_input_router()->sent_keyboard_event_); |
| } |
| |
| TEST_F(RenderWidgetHostTest, KeyboardListenerSuppressFollowingEvents) { |
| host_->SetupForInputRouterTest(); |
| |
| host_->AddKeyPressEventCallback( |
| base::Bind(&RenderWidgetHostTest::KeyPressEventCallback, |
| base::Unretained(this))); |
| |
| // The callback handles the first event |
| handle_key_press_event_ = true; |
| SimulateKeyboardEvent(WebInputEvent::kRawKeyDown); |
| |
| EXPECT_FALSE(host_->mock_input_router()->sent_keyboard_event_); |
| |
| // Following Char events should be suppressed |
| handle_key_press_event_ = false; |
| SimulateKeyboardEvent(WebInputEvent::kChar); |
| EXPECT_FALSE(host_->mock_input_router()->sent_keyboard_event_); |
| SimulateKeyboardEvent(WebInputEvent::kChar); |
| EXPECT_FALSE(host_->mock_input_router()->sent_keyboard_event_); |
| |
| // Sending RawKeyDown event should stop suppression |
| SimulateKeyboardEvent(WebInputEvent::kRawKeyDown); |
| EXPECT_TRUE(host_->mock_input_router()->sent_keyboard_event_); |
| |
| host_->mock_input_router()->sent_keyboard_event_ = false; |
| SimulateKeyboardEvent(WebInputEvent::kChar); |
| EXPECT_TRUE(host_->mock_input_router()->sent_keyboard_event_); |
| } |
| |
| TEST_F(RenderWidgetHostTest, MouseEventCallbackCanHandleEvent) { |
| host_->SetupForInputRouterTest(); |
| |
| host_->AddMouseEventCallback( |
| base::Bind(&RenderWidgetHostTest::MouseEventCallback, |
| base::Unretained(this))); |
| |
| handle_mouse_event_ = true; |
| SimulateMouseEvent(WebInputEvent::kMouseDown); |
| |
| EXPECT_FALSE(host_->mock_input_router()->sent_mouse_event_); |
| |
| handle_mouse_event_ = false; |
| SimulateMouseEvent(WebInputEvent::kMouseDown); |
| |
| EXPECT_TRUE(host_->mock_input_router()->sent_mouse_event_); |
| } |
| |
| TEST_F(RenderWidgetHostTest, InputRouterReceivesHasTouchEventHandlers) { |
| host_->SetupForInputRouterTest(); |
| |
| host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true)); |
| |
| EXPECT_TRUE(host_->mock_input_router()->message_received_); |
| } |
| |
| void CheckLatencyInfoComponentInMessage( |
| MockWidgetInputHandler::MessageVector& dispatched_events, |
| WebInputEvent::Type expected_type) { |
| ASSERT_EQ(1u, dispatched_events.size()); |
| ASSERT_TRUE(dispatched_events[0]->ToEvent()); |
| |
| EXPECT_TRUE(dispatched_events[0]->ToEvent()->Event()->web_event->GetType() == |
| expected_type); |
| EXPECT_TRUE( |
| dispatched_events[0]->ToEvent()->Event()->latency_info.FindLatency( |
| ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr)); |
| dispatched_events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED); |
| } |
| |
| void CheckLatencyInfoComponentInGestureScrollUpdate( |
| MockWidgetInputHandler::MessageVector& dispatched_events) { |
| ASSERT_EQ(2u, dispatched_events.size()); |
| ASSERT_TRUE(dispatched_events[0]->ToEvent()); |
| ASSERT_TRUE(dispatched_events[1]->ToEvent()); |
| EXPECT_EQ(WebInputEvent::kTouchScrollStarted, |
| dispatched_events[0]->ToEvent()->Event()->web_event->GetType()); |
| |
| EXPECT_EQ(WebInputEvent::kGestureScrollUpdate, |
| dispatched_events[1]->ToEvent()->Event()->web_event->GetType()); |
| EXPECT_TRUE( |
| dispatched_events[1]->ToEvent()->Event()->latency_info.FindLatency( |
| ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr)); |
| dispatched_events[1]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED); |
| } |
| |
| // Tests that after input event passes through RWHI through ForwardXXXEvent() |
| // or ForwardXXXEventWithLatencyInfo(), LatencyInfo component |
| // ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT will always present in the |
| // event's LatencyInfo. |
| void RenderWidgetHostTest::InputEventRWHLatencyComponent() { |
| host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true)); |
| |
| // Tests RWHI::ForwardWheelEvent(). |
| SimulateWheelEventPossiblyIncludingPhase(-5, 0, 0, true, |
| WebMouseWheelEvent::kPhaseBegan); |
| MockWidgetInputHandler::MessageVector dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| CheckLatencyInfoComponentInMessage(dispatched_events, |
| WebInputEvent::kMouseWheel); |
| |
| // Tests RWHI::ForwardWheelEventWithLatencyInfo(). |
| SimulateWheelEventWithLatencyInfoAndPossiblyPhase( |
| -5, 0, 0, true, ui::LatencyInfo(), WebMouseWheelEvent::kPhaseChanged); |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| CheckLatencyInfoComponentInMessage(dispatched_events, |
| WebInputEvent::kMouseWheel); |
| |
| // Tests RWHI::ForwardMouseEvent(). |
| SimulateMouseEvent(WebInputEvent::kMouseMove); |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| CheckLatencyInfoComponentInMessage(dispatched_events, |
| WebInputEvent::kMouseMove); |
| |
| // Tests RWHI::ForwardMouseEventWithLatencyInfo(). |
| SimulateMouseEventWithLatencyInfo(WebInputEvent::kMouseMove, |
| ui::LatencyInfo()); |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| CheckLatencyInfoComponentInMessage(dispatched_events, |
| WebInputEvent::kMouseMove); |
| |
| // Tests RWHI::ForwardGestureEvent(). |
| PressTouchPoint(0, 1); |
| SendTouchEvent(); |
| host_->input_router()->OnSetTouchAction(cc::kTouchActionAuto); |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| CheckLatencyInfoComponentInMessage(dispatched_events, |
| WebInputEvent::kTouchStart); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureScrollBegin, |
| blink::kWebGestureDeviceTouchscreen); |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| CheckLatencyInfoComponentInMessage(dispatched_events, |
| WebInputEvent::kGestureScrollBegin); |
| |
| // Tests RWHI::ForwardGestureEventWithLatencyInfo(). |
| SimulateGestureEventWithLatencyInfo(WebInputEvent::kGestureScrollUpdate, |
| blink::kWebGestureDeviceTouchscreen, |
| ui::LatencyInfo()); |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| CheckLatencyInfoComponentInGestureScrollUpdate(dispatched_events); |
| |
| ReleaseTouchPoint(0); |
| SendTouchEvent(); |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| |
| // Tests RWHI::ForwardTouchEventWithLatencyInfo(). |
| PressTouchPoint(0, 1); |
| SendTouchEvent(); |
| dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| CheckLatencyInfoComponentInMessage(dispatched_events, |
| WebInputEvent::kTouchStart); |
| } |
| TEST_F(RenderWidgetHostTest, InputEventRWHLatencyComponent) { |
| InputEventRWHLatencyComponent(); |
| } |
| TEST_F(RenderWidgetHostWheelScrollLatchingDisabledTest, |
| InputEventRWHLatencyComponent) { |
| InputEventRWHLatencyComponent(); |
| } |
| TEST_F(RenderWidgetHostAsyncWheelEventsEnabledTest, |
| InputEventRWHLatencyComponent) { |
| InputEventRWHLatencyComponent(); |
| } |
| |
| TEST_F(RenderWidgetHostTest, RendererExitedResetsInputRouter) { |
| // RendererExited will delete the view. |
| host_->SetView(new TestView(host_.get())); |
| host_->RendererExited(base::TERMINATION_STATUS_PROCESS_CRASHED, -1); |
| |
| // Make sure the input router is in a fresh state. |
| ASSERT_FALSE(host_->input_router()->HasPendingEvents()); |
| } |
| |
| // Regression test for http://crbug.com/401859 and http://crbug.com/522795. |
| TEST_F(RenderWidgetHostTest, RendererExitedResetsIsHidden) { |
| // RendererExited will delete the view. |
| host_->SetView(new TestView(host_.get())); |
| host_->WasShown(false /* record_presentation_time */); |
| |
| ASSERT_FALSE(host_->is_hidden()); |
| host_->RendererExited(base::TERMINATION_STATUS_PROCESS_CRASHED, -1); |
| ASSERT_TRUE(host_->is_hidden()); |
| |
| // Make sure the input router is in a fresh state. |
| ASSERT_FALSE(host_->input_router()->HasPendingEvents()); |
| } |
| |
| TEST_F(RenderWidgetHostTest, VisualProperties) { |
| gfx::Rect bounds(0, 0, 100, 100); |
| gfx::Size compositor_viewport_pixel_size(40, 50); |
| view_->SetBounds(bounds); |
| view_->SetMockCompositorViewportPixelSize(compositor_viewport_pixel_size); |
| |
| VisualProperties visual_properties; |
| bool needs_ack = false; |
| host_->GetVisualProperties(&visual_properties, &needs_ack); |
| EXPECT_EQ(bounds.size(), visual_properties.new_size); |
| EXPECT_EQ(compositor_viewport_pixel_size, |
| visual_properties.compositor_viewport_pixel_size); |
| } |
| |
| TEST_F(RenderWidgetHostTest, VisualPropertiesDeviceScale) { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| command_line->AppendSwitchASCII(switches::kEnableUseZoomForDSF, "true"); |
| |
| float device_scale = 3.5f; |
| ScreenInfo screen_info; |
| screen_info.device_scale_factor = device_scale; |
| |
| view_->SetScreenInfo(screen_info); |
| host_->SynchronizeVisualProperties(); |
| |
| float top_controls_height = 10.0f; |
| float bottom_controls_height = 20.0f; |
| view_->set_top_controls_height(top_controls_height); |
| view_->set_bottom_controls_height(bottom_controls_height); |
| |
| VisualProperties visual_properties; |
| bool needs_ack = false; |
| host_->GetVisualProperties(&visual_properties, &needs_ack); |
| EXPECT_EQ(top_controls_height * device_scale, |
| visual_properties.top_controls_height); |
| EXPECT_EQ(bottom_controls_height * device_scale, |
| visual_properties.bottom_controls_height); |
| } |
| |
| // Make sure no dragging occurs after renderer exited. See crbug.com/704832. |
| TEST_F(RenderWidgetHostTest, RendererExitedNoDrag) { |
| host_->SetView(new TestView(host_.get())); |
| |
| EXPECT_EQ(delegate_->mock_delegate_view()->start_dragging_count(), 0); |
| |
| GURL http_url = GURL("http://www.domain.com/index.html"); |
| DropData drop_data; |
| drop_data.url = http_url; |
| drop_data.html_base_url = http_url; |
| blink::WebDragOperationsMask drag_operation = blink::kWebDragOperationEvery; |
| DragEventSourceInfo event_info; |
| host_->OnStartDragging(drop_data, drag_operation, SkBitmap(), gfx::Vector2d(), |
| event_info); |
| EXPECT_EQ(delegate_->mock_delegate_view()->start_dragging_count(), 1); |
| |
| // Simulate that renderer exited due navigation to the next page. |
| host_->RendererExited(base::TERMINATION_STATUS_NORMAL_TERMINATION, 0); |
| EXPECT_FALSE(host_->GetView()); |
| host_->OnStartDragging(drop_data, drag_operation, SkBitmap(), gfx::Vector2d(), |
| event_info); |
| EXPECT_EQ(delegate_->mock_delegate_view()->start_dragging_count(), 1); |
| } |
| |
| class RenderWidgetHostInitialSizeTest : public RenderWidgetHostTest { |
| public: |
| RenderWidgetHostInitialSizeTest() |
| : RenderWidgetHostTest(), initial_size_(200, 100) {} |
| |
| void ConfigureView(TestView* view) override { |
| view->SetBounds(gfx::Rect(initial_size_)); |
| } |
| |
| protected: |
| gfx::Size initial_size_; |
| }; |
| |
| TEST_F(RenderWidgetHostInitialSizeTest, InitialSize) { |
| // Having an initial size set means that the size information had been sent |
| // with the reqiest to new up the RenderView and so subsequent |
| // SynchronizeVisualProperties calls should not result in new IPC (unless the |
| // size has actually changed). |
| EXPECT_FALSE(host_->SynchronizeVisualProperties()); |
| EXPECT_FALSE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| EXPECT_EQ(initial_size_, host_->old_visual_properties_->new_size); |
| EXPECT_TRUE(host_->visual_properties_ack_pending_); |
| } |
| |
| TEST_F(RenderWidgetHostTest, HideUnthrottlesResize) { |
| gfx::Size original_size(100, 100); |
| view_->SetBounds(gfx::Rect(original_size)); |
| process_->sink().ClearMessages(); |
| EXPECT_TRUE(host_->SynchronizeVisualProperties()); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| ViewMsg_SynchronizeVisualProperties::ID)); |
| EXPECT_EQ(original_size, host_->old_visual_properties_->new_size); |
| EXPECT_TRUE(host_->visual_properties_ack_pending_); |
| |
| // Hiding the widget should unthrottle resize. |
| host_->WasHidden(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| } |
| |
| // Tests that event dispatch after the delegate has been detached doesn't cause |
| // a crash. See crbug.com/563237. |
| TEST_F(RenderWidgetHostTest, EventDispatchPostDetach) { |
| host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, true)); |
| process_->sink().ClearMessages(); |
| |
| host_->DetachDelegate(); |
| |
| // Tests RWHI::ForwardGestureEventWithLatencyInfo(). |
| SimulateGestureEventWithLatencyInfo(WebInputEvent::kGestureScrollUpdate, |
| blink::kWebGestureDeviceTouchscreen, |
| ui::LatencyInfo()); |
| |
| // Tests RWHI::ForwardWheelEventWithLatencyInfo(). |
| SimulateWheelEventWithLatencyInfo(-5, 0, 0, true, ui::LatencyInfo()); |
| |
| ASSERT_FALSE(host_->input_router()->HasPendingEvents()); |
| } |
| |
| // Check that if messages of a frame arrive earlier than the frame itself, we |
| // queue the messages until the frame arrives and then process them. |
| TEST_F(RenderWidgetHostTest, FrameToken_MessageThenFrame) { |
| const uint32_t frame_token = 99; |
| const viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages; |
| messages.push_back(ViewHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->OnMessageReceived( |
| ViewHostMsg_FrameSwapMessages(0, frame_token, messages)); |
| EXPECT_EQ(1u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetFrameToken(frame_token) |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(1u, host_->processed_frame_messages_count()); |
| } |
| |
| // Check that if a frame arrives earlier than its messages, we process the |
| // messages immedtiately. |
| TEST_F(RenderWidgetHostTest, FrameToken_FrameThenMessage) { |
| const uint32_t frame_token = 99; |
| const viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages; |
| messages.push_back(ViewHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetFrameToken(frame_token) |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->OnMessageReceived( |
| ViewHostMsg_FrameSwapMessages(0, frame_token, messages)); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(1u, host_->processed_frame_messages_count()); |
| } |
| |
| // Check that if messages of multiple frames arrive before the frames, we |
| // process each message once it frame arrives. |
| TEST_F(RenderWidgetHostTest, FrameToken_MultipleMessagesThenTokens) { |
| const uint32_t frame_token1 = 99; |
| const uint32_t frame_token2 = 100; |
| const viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages1; |
| std::vector<IPC::Message> messages2; |
| messages1.push_back(ViewHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| messages2.push_back(ViewHostMsg_DidFirstVisuallyNonEmptyPaint(6)); |
| |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->OnMessageReceived( |
| ViewHostMsg_FrameSwapMessages(0, frame_token1, messages1)); |
| EXPECT_EQ(1u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->OnMessageReceived( |
| ViewHostMsg_FrameSwapMessages(0, frame_token2, messages2)); |
| EXPECT_EQ(2u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetFrameToken(frame_token1) |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_EQ(1u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(1u, host_->processed_frame_messages_count()); |
| |
| frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetFrameToken(frame_token2) |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(2u, host_->processed_frame_messages_count()); |
| } |
| |
| // Check that if multiple frames arrive before their messages, each message is |
| // processed immediately as soon as it arrives. |
| TEST_F(RenderWidgetHostTest, FrameToken_MultipleTokensThenMessages) { |
| const uint32_t frame_token1 = 99; |
| const uint32_t frame_token2 = 100; |
| const viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages1; |
| std::vector<IPC::Message> messages2; |
| messages1.push_back(ViewHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| messages2.push_back(ViewHostMsg_DidFirstVisuallyNonEmptyPaint(6)); |
| |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetFrameToken(frame_token1) |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetFrameToken(frame_token2) |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->OnMessageReceived( |
| ViewHostMsg_FrameSwapMessages(0, frame_token1, messages1)); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(1u, host_->processed_frame_messages_count()); |
| |
| host_->OnMessageReceived( |
| ViewHostMsg_FrameSwapMessages(0, frame_token2, messages2)); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(2u, host_->processed_frame_messages_count()); |
| } |
| |
| // Check that if one frame is lost but its messages arrive, we process the |
| // messages on the arrival of the next frame. |
| TEST_F(RenderWidgetHostTest, FrameToken_DroppedFrame) { |
| const uint32_t frame_token1 = 99; |
| const uint32_t frame_token2 = 100; |
| const viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages1; |
| std::vector<IPC::Message> messages2; |
| messages1.push_back(ViewHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| messages2.push_back(ViewHostMsg_DidFirstVisuallyNonEmptyPaint(6)); |
| |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->OnMessageReceived( |
| ViewHostMsg_FrameSwapMessages(0, frame_token1, messages1)); |
| EXPECT_EQ(1u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->OnMessageReceived( |
| ViewHostMsg_FrameSwapMessages(0, frame_token2, messages2)); |
| EXPECT_EQ(2u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetFrameToken(frame_token2) |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(2u, host_->processed_frame_messages_count()); |
| } |
| |
| // Check that if the renderer crashes, we drop all queued messages and allow |
| // smaller frame tokens to be sent by the renderer. |
| TEST_F(RenderWidgetHostTest, FrameToken_RendererCrash) { |
| const uint32_t frame_token1 = 99; |
| const uint32_t frame_token2 = 50; |
| const uint32_t frame_token3 = 30; |
| const viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages1; |
| std::vector<IPC::Message> messages3; |
| messages1.push_back(ViewHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| messages3.push_back(ViewHostMsg_DidFirstVisuallyNonEmptyPaint(6)); |
| |
| // Mocking |renderer_compositor_frame_sink_| to prevent crashes in |
| // renderer_compositor_frame_sink_->DidReceiveCompositorFrameAck(resources). |
| std::unique_ptr<viz::MockCompositorFrameSinkClient> |
| mock_compositor_frame_sink_client = |
| std::make_unique<viz::MockCompositorFrameSinkClient>(); |
| host_->SetMockRendererCompositorFrameSink( |
| mock_compositor_frame_sink_client.get()); |
| |
| // If we don't do this, then RWHI destroys the view in RendererExited and |
| // then a crash occurs when we attempt to destroy it again in TearDown(). |
| host_->SetView(nullptr); |
| |
| host_->OnMessageReceived( |
| ViewHostMsg_FrameSwapMessages(0, frame_token1, messages1)); |
| EXPECT_EQ(1u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->RendererExited(base::TERMINATION_STATUS_PROCESS_CRASHED, -1); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| host_->Init(); |
| |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetFrameToken(frame_token2) |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->RendererExited(base::TERMINATION_STATUS_PROCESS_CRASHED, -1); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| host_->SetView(view_.get()); |
| host_->Init(); |
| |
| host_->OnMessageReceived( |
| ViewHostMsg_FrameSwapMessages(0, frame_token3, messages3)); |
| EXPECT_EQ(1u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetFrameToken(frame_token3) |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame), |
| base::nullopt, 0); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(1u, host_->processed_frame_messages_count()); |
| } |
| |
| TEST_F(RenderWidgetHostTest, InflightEventCountResetsAfterRebind) { |
| // Simulate a keyboard event. |
| SimulateKeyboardEvent(WebInputEvent::kRawKeyDown); |
| |
| EXPECT_EQ(1u, host_->in_flight_event_count()); |
| mojom::WidgetPtr widget; |
| std::unique_ptr<MockWidgetImpl> widget_impl = |
| std::make_unique<MockWidgetImpl>(mojo::MakeRequest(&widget)); |
| host_->SetWidget(std::move(widget)); |
| EXPECT_EQ(0u, host_->in_flight_event_count()); |
| } |
| |
| TEST_F(RenderWidgetHostTest, ForceEnableZoomShouldUpdateAfterRebind) { |
| SCOPED_TRACE("force_enable_zoom is false at start."); |
| host_->ExpectForceEnableZoom(false); |
| |
| // Set force_enable_zoom true. |
| host_->SetForceEnableZoom(true); |
| |
| SCOPED_TRACE("force_enable_zoom is true after set."); |
| host_->ExpectForceEnableZoom(true); |
| |
| // Rebind should also update to the latest force_enable_zoom state. |
| mojom::WidgetPtr widget; |
| std::unique_ptr<MockWidgetImpl> widget_impl = |
| std::make_unique<MockWidgetImpl>(mojo::MakeRequest(&widget)); |
| host_->SetWidget(std::move(widget)); |
| |
| SCOPED_TRACE("force_enable_zoom is true after rebind."); |
| host_->ExpectForceEnableZoom(true); |
| } |
| |
| TEST_F(RenderWidgetHostTest, RenderWidgetSurfaceProperties) { |
| RenderWidgetSurfaceProperties prop1; |
| prop1.size = gfx::Size(200, 200); |
| prop1.device_scale_factor = 1.f; |
| RenderWidgetSurfaceProperties prop2; |
| prop2.size = gfx::Size(300, 300); |
| prop2.device_scale_factor = 2.f; |
| |
| EXPECT_EQ( |
| "RenderWidgetSurfaceProperties(size(this: 200x200, other: 300x300), " |
| "device_scale_factor(this: 1, other: 2))", |
| prop1.ToDiffString(prop2)); |
| EXPECT_EQ( |
| "RenderWidgetSurfaceProperties(size(this: 300x300, other: 200x200), " |
| "device_scale_factor(this: 2, other: 1))", |
| prop2.ToDiffString(prop1)); |
| EXPECT_EQ("", prop1.ToDiffString(prop1)); |
| EXPECT_EQ("", prop2.ToDiffString(prop2)); |
| } |
| |
| // If a navigation happens while the widget is hidden, we shouldn't show |
| // contents of the previous page when we become visible. |
| TEST_F(RenderWidgetHostTest, NavigateInBackgroundShowsBlank) { |
| // When visible, navigation does not immediately call into |
| // ClearDisplayedGraphics. |
| host_->WasShown(false /* record_presentation_time */); |
| host_->DidNavigate(5); |
| EXPECT_FALSE(host_->new_content_rendering_timeout_fired()); |
| |
| // Hide then show. ClearDisplayedGraphics must be called. |
| host_->WasHidden(); |
| host_->WasShown(false /* record_presentation_time */); |
| EXPECT_TRUE(host_->new_content_rendering_timeout_fired()); |
| host_->reset_new_content_rendering_timeout_fired(); |
| |
| // Hide, navigate, then show. ClearDisplayedGraphics must be called. |
| host_->WasHidden(); |
| host_->DidNavigate(6); |
| host_->WasShown(false /* record_presentation_time */); |
| EXPECT_TRUE(host_->new_content_rendering_timeout_fired()); |
| } |
| |
| TEST_F(RenderWidgetHostTest, RendererHangRecordsMetrics) { |
| base::SimpleTestTickClock clock; |
| host_->set_clock_for_testing(&clock); |
| base::HistogramTester tester; |
| |
| // RenderWidgetHost makes private the methods it overrides from |
| // InputRouterClient. Call them through the base class. |
| InputRouterClient* input_router_client = host_.get(); |
| |
| // Do a 3s hang. This shouldn't affect metrics. |
| input_router_client->IncrementInFlightEventCount(); |
| clock.Advance(base::TimeDelta::FromSeconds(3)); |
| input_router_client->DecrementInFlightEventCount( |
| InputEventAckSource::UNKNOWN); |
| tester.ExpectTotalCount("Renderer.Hung.Duration", 0u); |
| |
| // Do a 17s hang. This should affect metrics. |
| input_router_client->IncrementInFlightEventCount(); |
| clock.Advance(base::TimeDelta::FromSeconds(17)); |
| input_router_client->DecrementInFlightEventCount( |
| InputEventAckSource::UNKNOWN); |
| tester.ExpectTotalCount("Renderer.Hung.Duration", 1u); |
| tester.ExpectUniqueSample("Renderer.Hung.Duration", 17000, 1); |
| } |
| |
| } // namespace content |