| // 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/visual_properties.h" |
| #include "content/common/widget_messages.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/stub_render_widget_host_owner_delegate.h" |
| #include "content/test/test_render_view_host.h" |
| #include "mojo/public/cpp/bindings/interface_request.h" |
| #include "testing/gmock/include/gmock/gmock.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; |
| |
| using testing::_; |
| |
| 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), |
| has_handlers_(false), |
| client_(client) {} |
| ~MockInputRouter() override {} |
| |
| // InputRouter |
| void SendMouseEvent(const MouseEventWithLatencyInfo& mouse_event, |
| MouseEventCallback event_result_callback) override { |
| sent_mouse_event_ = true; |
| } |
| void SendWheelEvent( |
| const MouseWheelEventWithLatencyInfo& wheel_event) override { |
| sent_wheel_event_ = true; |
| } |
| void SendKeyboardEvent(const NativeWebKeyboardEventWithLatencyInfo& key_event, |
| KeyboardEventCallback event_result_callback) 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 {} |
| void ForceSetTouchActionAuto() override {} |
| void OnHasTouchEventHandlers(bool has_handlers) override { |
| has_handlers_ = has_handlers; |
| } |
| |
| bool sent_mouse_event_; |
| bool sent_wheel_event_; |
| bool sent_keyboard_event_; |
| bool sent_gesture_event_; |
| bool send_touch_event_not_cancelled_; |
| bool has_handlers_; |
| |
| private: |
| InputRouterClient* client_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockInputRouter); |
| }; |
| |
| // TestFrameTokenMessageQueue ---------------------------------------------- |
| |
| class TestFrameTokenMessageQueue : public FrameTokenMessageQueue { |
| public: |
| explicit 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() 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) { |
| local_surface_id_allocator_.GenerateId(); |
| } |
| |
| // 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 InvalidateLocalSurfaceId() { local_surface_id_allocator_.Invalidate(); } |
| |
| 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_; |
| } |
| const viz::LocalSurfaceIdAllocation& GetLocalSurfaceIdAllocation() |
| const override { |
| return local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation(); |
| } |
| |
| 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), |
| ignore_input_events_(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(); |
| } |
| |
| void SetZoomLevel(double zoom_level) { zoom_level_ = zoom_level; } |
| |
| double GetPendingPageZoomLevel() override { return zoom_level_; } |
| |
| void FocusOwningWebContents( |
| RenderWidgetHostImpl* render_widget_host) override { |
| focus_owning_web_contents_call_count++; |
| } |
| |
| int GetFocusOwningWebContentsCallCount() const { |
| return focus_owning_web_contents_call_count; |
| } |
| |
| RenderViewHostDelegateView* GetDelegateView() override { |
| return mock_delegate_view(); |
| } |
| |
| void SetIgnoreInputEvents(bool ignore_input_events) { |
| ignore_input_events_ = ignore_input_events; |
| } |
| |
| 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; |
| } |
| |
| bool HandleKeyboardEvent(const NativeWebKeyboardEvent& event) override { |
| unhandled_keyboard_event_type_ = event.GetType(); |
| unhandled_keyboard_event_called_ = true; |
| return true; |
| } |
| |
| bool HandleWheelEvent(const blink::WebMouseWheelEvent& event) override { |
| handle_wheel_event_called_ = true; |
| return handle_wheel_event_; |
| } |
| |
| void RendererUnresponsive( |
| RenderWidgetHostImpl* render_widget_host, |
| base::RepeatingClosure hang_monitor_restarter) override { |
| unresponsive_timer_fired_ = true; |
| } |
| |
| bool ShouldIgnoreInputEvents() override { return ignore_input_events_; } |
| |
| 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_; |
| |
| bool ignore_input_events_; |
| |
| std::unique_ptr<MockRenderViewHostDelegateView> |
| render_view_host_delegate_view_; |
| |
| double zoom_level_ = 0; |
| |
| int focus_owning_web_contents_call_count = 0; |
| }; |
| |
| class MockRenderWidgetHostOwnerDelegate |
| : public StubRenderWidgetHostOwnerDelegate { |
| public: |
| MOCK_METHOD1(SetBackgroundOpaque, void(bool opaque)); |
| }; |
| |
| // RenderWidgetHostTest -------------------------------------------------------- |
| |
| class RenderWidgetHostTest : public testing::Test { |
| public: |
| RenderWidgetHostTest() |
| : process_(nullptr), |
| handle_key_press_event_(false), |
| handle_mouse_event_(false), |
| last_simulated_event_time_(ui::EventTimeForNow()) { |
| feature_list_.Init(); |
| } |
| ~RenderWidgetHostTest() override {} |
| |
| bool KeyPressEventCallback(const NativeWebKeyboardEvent& /* event */) { |
| return handle_key_press_event_; |
| } |
| bool MouseEventCallback(const blink::WebMouseEvent& /* event */) { |
| return handle_mouse_event_; |
| } |
| |
| void RunLoopFor(base::TimeDelta duration) { |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), duration); |
| run_loop.Run(); |
| } |
| |
| 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())); |
| // Set up the RenderWidgetHost as being for a main frame. |
| host_->set_owner_delegate(&mock_owner_delegate_); |
| 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 SimulateWheelEvent(float dX, |
| float dY, |
| int modifiers, |
| bool precise, |
| WebMouseWheelEvent::Phase phase) { |
| WebMouseWheelEvent wheel_event = SyntheticWebMouseWheelEventBuilder::Build( |
| 0, 0, dX, dY, modifiers, precise); |
| 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 SimulateWheelEventWithLatencyInfo(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); |
| 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); |
| } |
| |
| std::unique_ptr<TestBrowserContext> browser_context_; |
| RenderWidgetHostProcess* process_; // Deleted automatically by the widget. |
| std::unique_ptr<MockRenderWidgetHostDelegate> delegate_; |
| testing::NiceMock<MockRenderWidgetHostOwnerDelegate> mock_owner_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_; |
| |
| 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); |
| }; |
| |
| // 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, SynchronizeVisualProperties) { |
| // The initial zoom is 0 so host should not send a sync message |
| delegate_->SetZoomLevel(0); |
| EXPECT_FALSE(host_->SynchronizeVisualProperties()); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_FALSE(process_->sink().GetUniqueMessageMatching( |
| WidgetMsg_SynchronizeVisualProperties::ID)); |
| |
| // The zoom has changed so host should send out a sync message |
| process_->sink().ClearMessages(); |
| double new_zoom_level = content::ZoomFactorToZoomLevel(0.25); |
| delegate_->SetZoomLevel(new_zoom_level); |
| EXPECT_TRUE(host_->SynchronizeVisualProperties()); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_NEAR(new_zoom_level, host_->old_visual_properties_->zoom_level, 0.01); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| WidgetMsg_SynchronizeVisualProperties::ID)); |
| |
| // The initial bounds is the empty rect, so setting it to the same thing |
| // shouldn't send the resize message. |
| process_->sink().ClearMessages(); |
| view_->SetBounds(gfx::Rect()); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_FALSE(process_->sink().GetUniqueMessageMatching( |
| WidgetMsg_SynchronizeVisualProperties::ID)); |
| |
| // No visual properties 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( |
| WidgetMsg_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( |
| WidgetMsg_SynchronizeVisualProperties::ID)); |
| cc::RenderFrameMetadata metadata; |
| metadata.viewport_size_in_pixels = original_size.size(); |
| metadata.local_surface_id_allocation = 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 visual properties 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( |
| WidgetMsg_SynchronizeVisualProperties::ID)); |
| |
| // Send a update that's a visual properties 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_allocation = 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( |
| WidgetMsg_SynchronizeVisualProperties::ID)); |
| |
| // Send the visual properties ACK for the latest size. |
| process_->sink().ClearMessages(); |
| metadata.viewport_size_in_pixels = third_size.size(); |
| metadata.local_surface_id_allocation = 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( |
| WidgetMsg_SynchronizeVisualProperties::ID)); |
| |
| // Now clearing the bounds should send out a notification but we shouldn't |
| // expect a visual properties 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( |
| WidgetMsg_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( |
| WidgetMsg_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( |
| WidgetMsg_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( |
| WidgetMsg_SynchronizeVisualProperties::ID)); |
| |
| // An invalid LocalSurfaceId should result in no change to the |
| // |visual_properties_ack_pending_| bit. |
| process_->sink().ClearMessages(); |
| view_->SetBounds(gfx::Rect(25, 25)); |
| view_->InvalidateLocalSurfaceId(); |
| host_->SynchronizeVisualProperties(); |
| EXPECT_FALSE(host_->visual_properties_ack_pending_); |
| EXPECT_EQ(gfx::Size(25, 25), host_->old_visual_properties_->new_size); |
| EXPECT_TRUE(process_->sink().GetUniqueMessageMatching( |
| WidgetMsg_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( |
| WidgetMsg_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( |
| WidgetMsg_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( |
| WidgetMsg_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( |
| WidgetMsg_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 visual properties |
| // 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( |
| WidgetMsg_SynchronizeVisualProperties::ID)); |
| |
| // Simulate a renderer crash before the update message. Ensure all the |
| // visual properties 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()); |
| |
| { |
| // The background is assumed opaque by default, so choosing opaque won't |
| // do anything if it's not set to transparent first. |
| EXPECT_CALL(mock_owner_delegate_, SetBackgroundOpaque(_)).Times(0); |
| view->SetBackgroundColor(SK_ColorRED); |
| EXPECT_EQ(unsigned{SK_ColorRED}, *view->GetBackgroundColor()); |
| } |
| { |
| // Another opaque color doesn't inform the view of any change. |
| EXPECT_CALL(mock_owner_delegate_, SetBackgroundOpaque(_)).Times(0); |
| view->SetBackgroundColor(SK_ColorBLUE); |
| EXPECT_EQ(unsigned{SK_ColorBLUE}, *view->GetBackgroundColor()); |
| } |
| { |
| // The owner delegate will be called to pass it over IPC to the RenderView. |
| EXPECT_CALL(mock_owner_delegate_, SetBackgroundOpaque(false)); |
| view->SetBackgroundColor(SK_ColorTRANSPARENT); |
| // The browser side will represent the background color as transparent |
| // immediately. |
| EXPECT_EQ(unsigned{SK_ColorTRANSPARENT}, *view->GetBackgroundColor()); |
| } |
| { |
| // Setting back an opaque color informs the view. |
| EXPECT_CALL(mock_owner_delegate_, SetBackgroundOpaque(true)); |
| view->SetBackgroundColor(SK_ColorBLUE); |
| EXPECT_EQ(unsigned{SK_ColorBLUE}, *view->GetBackgroundColor()); |
| } |
| |
| host_->SetView(nullptr); |
| static_cast<RenderWidgetHostViewBase*>(view.release())->Destroy(); |
| } |
| #endif |
| |
| // Test that the RenderWidgetHost tells the renderer when it is hidden and |
| // shown, and can accept a racey update from the renderer after hiding. |
| TEST_F(RenderWidgetHostTest, HideShowMessages) { |
| // 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(WidgetMsg_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_allocation = 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. |
| EXPECT_TRUE( |
| process_->sink().GetUniqueMessageMatching(WidgetMsg_WasShown::ID)); |
| } |
| |
| 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()); |
| } |
| |
| TEST_F(RenderWidgetHostTest, UnhandledWheelEvent) { |
| SimulateWheelEvent(-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, HandleWheelEvent) { |
| // Indicate that we're going to handle this wheel event |
| delegate_->set_handle_wheel_event(true); |
| |
| SimulateWheelEvent(-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, EventsCausingFocus) { |
| SimulateMouseEvent(WebInputEvent::kMouseDown); |
| EXPECT_EQ(1, delegate_->GetFocusOwningWebContentsCallCount()); |
| |
| PressTouchPoint(0, 1); |
| SendTouchEvent(); |
| EXPECT_EQ(2, delegate_->GetFocusOwningWebContentsCallCount()); |
| |
| ReleaseTouchPoint(0); |
| SendTouchEvent(); |
| EXPECT_EQ(2, delegate_->GetFocusOwningWebContentsCallCount()); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureTapDown, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(2, delegate_->GetFocusOwningWebContentsCallCount()); |
| |
| SimulateGestureEvent(WebInputEvent::kGestureTap, |
| blink::kWebGestureDeviceTouchscreen); |
| EXPECT_EQ(3, delegate_->GetFocusOwningWebContentsCallCount()); |
| } |
| |
| 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, DontPostponeInputEventAckTimeout) { |
| // Start with a short timeout. |
| host_->StartInputEventAckTimeout(TimeDelta::FromMilliseconds(10)); |
| |
| // Immediately try to add a long 30 second timeout. |
| EXPECT_FALSE(delegate_->unresponsive_timer_fired()); |
| host_->StartInputEventAckTimeout(TimeDelta::FromSeconds(30)); |
| |
| // Wait long enough for first timeout and see if it fired. |
| RunLoopFor(TimeDelta::FromMilliseconds(10)); |
| 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, StopAndStartInputEventAckTimeout) { |
| // Start with a short timeout, then stop it. |
| host_->StartInputEventAckTimeout(TimeDelta::FromMilliseconds(10)); |
| host_->StopInputEventAckTimeout(); |
| |
| // Start it again to ensure it still works. |
| EXPECT_FALSE(delegate_->unresponsive_timer_fired()); |
| host_->StartInputEventAckTimeout(TimeDelta::FromMilliseconds(10)); |
| |
| // Wait long enough for first timeout and see if it fired. |
| RunLoopFor(TimeDelta::FromMilliseconds(40)); |
| 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, ShorterDelayInputEventAckTimeout) { |
| // Start with a timeout. |
| host_->StartInputEventAckTimeout(TimeDelta::FromMilliseconds(100)); |
| |
| // Start it again with shorter delay. |
| EXPECT_FALSE(delegate_->unresponsive_timer_fired()); |
| host_->StartInputEventAckTimeout(TimeDelta::FromMilliseconds(20)); |
| |
| // Wait long enough for the second timeout and see if it fired. |
| RunLoopFor(TimeDelta::FromMilliseconds(25)); |
| EXPECT_TRUE(delegate_->unresponsive_timer_fired()); |
| } |
| |
| // Test that the hang monitor timer is effectively disabled when the widget is |
| // hidden. |
| TEST_F(RenderWidgetHostTest, InputEventAckTimeoutDisabledForInputWhenHidden) { |
| 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()); |
| RunLoopFor(TimeDelta::FromMicroseconds(2)); |
| EXPECT_FALSE(delegate_->unresponsive_timer_fired()); |
| |
| // The timeout should never reactivate while hidden. |
| SimulateMouseEvent(WebInputEvent::kMouseMove, 10, 10, 0, false); |
| RunLoopFor(TimeDelta::FromMicroseconds(2)); |
| 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 */); |
| RunLoopFor(TimeDelta::FromMicroseconds(2)); |
| 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. |
| RunLoopFor(TimeDelta::FromMicroseconds(20)); |
| 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. |
| // Disabled due to flakiness on Android. See https://crbug.com/892700. |
| #if defined(OS_ANDROID) |
| #define MAYBE_NewContentRenderingTimeoutWithoutSurfaceSync \ |
| DISABLED_NewContentRenderingTimeoutWithoutSurfaceSync |
| #else |
| #define MAYBE_NewContentRenderingTimeoutWithoutSurfaceSync \ |
| NewContentRenderingTimeoutWithoutSurfaceSync |
| #endif |
| TEST_F(RenderWidgetHostTest, |
| NewContentRenderingTimeoutWithoutSurfaceSync_MAYBE) { |
| // 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); |
| RunLoopFor(TimeDelta::FromMicroseconds(20)); |
| |
| 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); |
| RunLoopFor(TimeDelta::FromMicroseconds(20)); |
| |
| 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); |
| RunLoopFor(TimeDelta::FromMicroseconds(20)); |
| |
| 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); |
| RunLoopFor(TimeDelta::FromMicroseconds(20)); |
| 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(); |
| |
| delegate_->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(); |
| |
| ASSERT_FALSE(host_->mock_input_router()->has_handlers_); |
| |
| host_->OnMessageReceived(WidgetHostMsg_HasTouchEventHandlers(0, true)); |
| EXPECT_TRUE(host_->mock_input_router()->has_handlers_); |
| } |
| |
| 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. |
| TEST_F(RenderWidgetHostTest, InputEventRWHLatencyComponent) { |
| host_->OnMessageReceived(WidgetHostMsg_HasTouchEventHandlers(0, true)); |
| |
| // Tests RWHI::ForwardWheelEvent(). |
| SimulateWheelEvent(-5, 0, 0, true, WebMouseWheelEvent::kPhaseBegan); |
| MockWidgetInputHandler::MessageVector dispatched_events = |
| host_->mock_widget_input_handler_.GetAndResetDispatchedMessages(); |
| CheckLatencyInfoComponentInMessage(dispatched_events, |
| WebInputEvent::kMouseWheel); |
| |
| // Tests RWHI::ForwardWheelEventWithLatencyInfo(). |
| SimulateWheelEventWithLatencyInfo(-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, 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. |
| // DISABLED for crbug.com/908012 |
| TEST_F(RenderWidgetHostTest, DISABLED_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( |
| WidgetMsg_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( |
| WidgetMsg_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(WidgetHostMsg_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 viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages; |
| messages.push_back(WidgetHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| const uint32_t frame_token = frame.metadata.frame_token; |
| |
| host_->OnMessageReceived( |
| WidgetHostMsg_FrameSwapMessages(0, frame_token, messages)); |
| EXPECT_EQ(1u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| 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 viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages; |
| messages.push_back(WidgetHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| const uint32_t frame_token = frame.metadata.frame_token; |
| 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( |
| WidgetHostMsg_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 viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages1; |
| std::vector<IPC::Message> messages2; |
| messages1.push_back(WidgetHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| messages2.push_back(WidgetHostMsg_DidFirstVisuallyNonEmptyPaint(6)); |
| |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| auto frame1 = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| const uint32_t frame_token1 = frame1.metadata.frame_token; |
| auto frame2 = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| const uint32_t frame_token2 = frame2.metadata.frame_token; |
| |
| host_->OnMessageReceived( |
| WidgetHostMsg_FrameSwapMessages(0, frame_token1, messages1)); |
| EXPECT_EQ(1u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->OnMessageReceived( |
| WidgetHostMsg_FrameSwapMessages(0, frame_token2, messages2)); |
| EXPECT_EQ(2u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame1), |
| base::nullopt, 0); |
| EXPECT_EQ(1u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(1u, host_->processed_frame_messages_count()); |
| host_->SubmitCompositorFrame(local_surface_id, std::move(frame2), |
| 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 viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages1; |
| std::vector<IPC::Message> messages2; |
| messages1.push_back(WidgetHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| messages2.push_back(WidgetHostMsg_DidFirstVisuallyNonEmptyPaint(6)); |
| |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| const uint32_t frame_token1 = frame.metadata.frame_token; |
| 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() |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| const uint32_t frame_token2 = frame.metadata.frame_token; |
| 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( |
| WidgetHostMsg_FrameSwapMessages(0, frame_token1, messages1)); |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(1u, host_->processed_frame_messages_count()); |
| |
| host_->OnMessageReceived( |
| WidgetHostMsg_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 = 1; |
| const viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages1; |
| std::vector<IPC::Message> messages2; |
| messages1.push_back(WidgetHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| messages2.push_back(WidgetHostMsg_DidFirstVisuallyNonEmptyPaint(6)); |
| |
| EXPECT_EQ(0u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| host_->OnMessageReceived( |
| WidgetHostMsg_FrameSwapMessages(0, frame_token1, messages1)); |
| EXPECT_EQ(1u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| const uint32_t frame_token2 = frame.metadata.frame_token; |
| host_->OnMessageReceived( |
| WidgetHostMsg_FrameSwapMessages(0, frame_token2, messages2)); |
| EXPECT_EQ(2u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| 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 viz::LocalSurfaceId local_surface_id(1, |
| base::UnguessableToken::Create()); |
| std::vector<IPC::Message> messages1; |
| std::vector<IPC::Message> messages3; |
| messages1.push_back(WidgetHostMsg_DidFirstVisuallyNonEmptyPaint(5)); |
| messages3.push_back(WidgetHostMsg_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); |
| |
| auto frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| const uint32_t frame_token1 = frame.metadata.frame_token; |
| host_->OnMessageReceived( |
| WidgetHostMsg_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(); |
| |
| frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .SetSendFrameTokenToEmbedder(true) |
| .Build(); |
| const uint32_t frame_token2 = frame.metadata.frame_token; |
| 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( |
| WidgetHostMsg_FrameSwapMessages(0, frame_token2, messages3)); |
| EXPECT_EQ(1u, host_->frame_token_message_queue_->size()); |
| EXPECT_EQ(0u, host_->processed_frame_messages_count()); |
| |
| frame = viz::CompositorFrameBuilder() |
| .AddDefaultRenderPass() |
| .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 |