diff options
44 files changed, 1374 insertions, 180 deletions
diff --git a/blimp/client/feature/compositor/blimp_input_handler_wrapper.cc b/blimp/client/feature/compositor/blimp_input_handler_wrapper.cc index 4716b58..5e38f33 100644 --- a/blimp/client/feature/compositor/blimp_input_handler_wrapper.cc +++ b/blimp/client/feature/compositor/blimp_input_handler_wrapper.cc @@ -51,6 +51,7 @@ void BlimpInputHandlerWrapper::HandleWebGestureEvent( case ui::InputHandlerProxy::EventDisposition::DROP_EVENT: consumed = true; break; + case ui::InputHandlerProxy::EventDisposition::DID_HANDLE_NON_BLOCKING: case ui::InputHandlerProxy::EventDisposition::DID_NOT_HANDLE: consumed = false; break; diff --git a/content/browser/android/in_process/synchronous_input_event_filter.cc b/content/browser/android/in_process/synchronous_input_event_filter.cc index 3ce1c61..80f1025 100644 --- a/content/browser/android/in_process/synchronous_input_event_filter.cc +++ b/content/browser/android/in_process/synchronous_input_event_filter.cc @@ -80,4 +80,8 @@ void SynchronousInputEventFilter::DidStopFlinging(int routing_id) { compositor->DidStopFlinging(); } +void SynchronousInputEventFilter::NonBlockingInputEventHandled( + int routing_id, + blink::WebInputEvent::Type type) {} + } // namespace content diff --git a/content/browser/android/in_process/synchronous_input_event_filter.h b/content/browser/android/in_process/synchronous_input_event_filter.h index 98375d7..32b6ecb 100644 --- a/content/browser/android/in_process/synchronous_input_event_filter.h +++ b/content/browser/android/in_process/synchronous_input_event_filter.h @@ -44,6 +44,8 @@ class SynchronousInputEventFilter : public InputHandlerManagerClient { void DidOverscroll(int routing_id, const DidOverscrollParams& params) override; void DidStopFlinging(int routing_id) override; + void NonBlockingInputEventHandled(int routing_id, + blink::WebInputEvent::Type type) override; private: void SetBoundHandlerOnUIThread(const Handler& handler); diff --git a/content/browser/renderer_host/input/input_router_impl.cc b/content/browser/renderer_host/input/input_router_impl.cc index 8ba9287..9f79fb4 100644 --- a/content/browser/renderer_host/input/input_router_impl.cc +++ b/content/browser/renderer_host/input/input_router_impl.cc @@ -53,6 +53,8 @@ const char* GetEventAckName(InputEventAckState ack_result) { case INPUT_EVENT_ACK_STATE_NOT_CONSUMED: return "NOT_CONSUMED"; case INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS: return "NO_CONSUMER_EXISTS"; case INPUT_EVENT_ACK_STATE_IGNORED: return "IGNORED"; + case INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING: + return "SET_NON_BLOCKING"; } DLOG(WARNING) << "Unhandled InputEventAckState in GetEventAckName."; return ""; @@ -410,8 +412,9 @@ bool InputRouterImpl::OfferToRenderer(const WebInputEvent& input_event, const WebInputEvent* event_to_send = event_in_viewport ? event_in_viewport.get() : &input_event; - if (Send(new InputMsg_HandleInputEvent(routing_id(), event_to_send, - latency_info))) { + if (Send(new InputMsg_HandleInputEvent( + routing_id(), event_to_send, latency_info, + InputEventDispatchType::DISPATCH_TYPE_NORMAL))) { // Ack messages for ignored ack event types should never be sent by the // renderer. Consequently, such event types should not affect event time // or in-flight event count metrics. diff --git a/content/browser/renderer_host/input/non_blocking_event_browsertest.cc b/content/browser/renderer_host/input/non_blocking_event_browsertest.cc new file mode 100644 index 0000000..6cc60c6 --- /dev/null +++ b/content/browser/renderer_host/input/non_blocking_event_browsertest.cc @@ -0,0 +1,188 @@ +// Copyright 2016 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 <utility> + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h" +#include "content/browser/renderer_host/render_widget_host_impl.h" +#include "content/browser/renderer_host/render_widget_host_view_base.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/common/input/synthetic_web_input_event_builders.h" +#include "content/common/input_messages.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/common/content_switches.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/content_browser_test.h" +#include "content/public/test/content_browser_test_utils.h" +#include "content/public/test/test_utils.h" +#include "content/shell/browser/shell.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" +#include "ui/events/event_switches.h" +#include "ui/events/latency_info.h" + +using blink::WebInputEvent; + +namespace { + +const char kNonBlockingEventDataURL[] = + "data:text/html;charset=utf-8," + "<!DOCTYPE html>" + "<meta name='viewport' content='width=device-width'/>" + "<style>" + "html, body {" + " margin: 0;" + "}" + ".spacer { height: 1000px; }" + "</style>" + "<div class=spacer></div>" + "<script>" + " document.addEventListener('wheel', function(e) { while(true) {} }, " + "{'passive': true});" + " document.addEventListener('touchstart', function(e) { while(true) {} }, " + "{'passive': true});" + " document.title='ready';" + "</script>"; + +} // namespace + +namespace content { + +class NonBlockingEventBrowserTest : public ContentBrowserTest { + public: + NonBlockingEventBrowserTest() {} + ~NonBlockingEventBrowserTest() override {} + + RenderWidgetHostImpl* GetWidgetHost() { + return RenderWidgetHostImpl::From( + shell()->web_contents()->GetRenderViewHost()->GetWidget()); + } + + void OnSyntheticGestureCompleted(SyntheticGesture::Result result) { + EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result); + } + + protected: + void LoadURL() { + const GURL data_url(kNonBlockingEventDataURL); + NavigateToURL(shell(), data_url); + + RenderWidgetHostImpl* host = GetWidgetHost(); + host->GetView()->SetSize(gfx::Size(400, 400)); + + base::string16 ready_title(base::ASCIIToUTF16("ready")); + TitleWatcher watcher(shell()->web_contents(), ready_title); + ignore_result(watcher.WaitAndGetTitle()); + + MainThreadFrameObserver main_thread_sync(host); + main_thread_sync.Wait(); + } + + // ContentBrowserTest: + void SetUpCommandLine(base::CommandLine* cmd) override { + // TODO(dtapuska): Remove this switch once wheel-gestures ships. + cmd->AppendSwitch(switches::kEnableExperimentalWebPlatformFeatures); + cmd->AppendSwitch(switches::kEnableWheelGestures); + } + + int ExecuteScriptAndExtractInt(const std::string& script) { + int value = 0; + EXPECT_TRUE(content::ExecuteScriptAndExtractInt( + shell()->web_contents(), "domAutomationController.send(" + script + ")", + &value)); + return value; + } + + int GetScrollTop() { + return ExecuteScriptAndExtractInt("document.scrollingElement.scrollTop"); + } + + void DoWheelScroll() { + EXPECT_EQ(0, GetScrollTop()); + + int scrollHeight = + ExecuteScriptAndExtractInt("document.documentElement.scrollHeight"); + EXPECT_EQ(1000, scrollHeight); + + scoped_refptr<FrameWatcher> frame_watcher(new FrameWatcher()); + GetWidgetHost()->GetProcess()->AddFilter(frame_watcher.get()); + scoped_refptr<InputMsgWatcher> input_msg_watcher( + new InputMsgWatcher(GetWidgetHost(), blink::WebInputEvent::MouseWheel)); + + GetWidgetHost()->ForwardWheelEvent( + SyntheticWebMouseWheelEventBuilder::Build(10, 10, 0, -53, 0, true)); + + // Runs until we get the InputMsgAck callback + EXPECT_EQ(INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING, + input_msg_watcher->WaitForAck()); + frame_watcher->WaitFrames(1); + + // Expect that the compositor scrolled at least one pixel while the + // main thread was in a busy loop. + EXPECT_LT(0, frame_watcher->LastMetadata().root_scroll_offset.y()); + } + + void DoTouchScroll() { + EXPECT_EQ(0, GetScrollTop()); + + int scrollHeight = + ExecuteScriptAndExtractInt("document.documentElement.scrollHeight"); + EXPECT_EQ(1000, scrollHeight); + + scoped_refptr<FrameWatcher> frame_watcher(new FrameWatcher()); + GetWidgetHost()->GetProcess()->AddFilter(frame_watcher.get()); + + SyntheticSmoothScrollGestureParams params; + params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT; + params.anchor = gfx::PointF(50, 50); + params.distances.push_back(gfx::Vector2d(0, -45)); + + scoped_ptr<SyntheticSmoothScrollGesture> gesture( + new SyntheticSmoothScrollGesture(params)); + GetWidgetHost()->QueueSyntheticGesture( + std::move(gesture), + base::Bind(&NonBlockingEventBrowserTest::OnSyntheticGestureCompleted, + base::Unretained(this))); + + // Expect that the compositor scrolled at least one pixel while the + // main thread was in a busy loop. + while (frame_watcher->LastMetadata().root_scroll_offset.y() <= 0) + frame_watcher->WaitFrames(1); + } + + private: + DISALLOW_COPY_AND_ASSIGN(NonBlockingEventBrowserTest); +}; + +// Disabled on MacOS because it doesn't support wheel gestures +// just yet. +#if defined(OS_MACOSX) +#define MAYBE_MouseWheel DISABLED_MouseWheel +#else +#define MAYBE_MouseWheel MouseWheel +#endif +IN_PROC_BROWSER_TEST_F(NonBlockingEventBrowserTest, MAYBE_MouseWheel) { + LoadURL(); + DoWheelScroll(); +} + +// Disabled on MacOS because it doesn't support touch input. +#if defined(OS_MACOSX) +#define MAYBE_TouchStart DISABLED_TouchStart +#else +#define MAYBE_TouchStart TouchStart +#endif +IN_PROC_BROWSER_TEST_F(NonBlockingEventBrowserTest, MAYBE_TouchStart) { + LoadURL(); + DoTouchScroll(); +} + +} // namespace content diff --git a/content/browser/renderer_host/input/touch_input_browsertest.cc b/content/browser/renderer_host/input/touch_input_browsertest.cc index 2c68ac7..7ff1dd0 100644 --- a/content/browser/renderer_host/input/touch_input_browsertest.cc +++ b/content/browser/renderer_host/input/touch_input_browsertest.cc @@ -19,6 +19,7 @@ #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/common/content_switches.h" +#include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" #include "content/shell/browser/shell.h" @@ -88,54 +89,6 @@ const char kTouchEventDataURL[] = namespace content { -class InputEventMessageFilter : public BrowserMessageFilter { - public: - InputEventMessageFilter() - : BrowserMessageFilter(InputMsgStart), - type_(WebInputEvent::Undefined), - state_(INPUT_EVENT_ACK_STATE_UNKNOWN) {} - - void WaitForAck(WebInputEvent::Type type) { - base::RunLoop run_loop; - base::AutoReset<base::Closure> reset_quit(&quit_, run_loop.QuitClosure()); - base::AutoReset<WebInputEvent::Type> reset_type(&type_, type); - run_loop.Run(); - } - - InputEventAckState last_ack_state() const { return state_; } - - protected: - ~InputEventMessageFilter() override {} - - private: - void ReceivedEventAck(WebInputEvent::Type type, InputEventAckState state) { - if (type_ == type) { - state_ = state; - quit_.Run(); - } - } - - // BrowserMessageFilter: - bool OnMessageReceived(const IPC::Message& message) override { - if (message.type() == InputHostMsg_HandleInputEvent_ACK::ID) { - InputHostMsg_HandleInputEvent_ACK::Param params; - InputHostMsg_HandleInputEvent_ACK::Read(&message, ¶ms); - WebInputEvent::Type type = base::get<0>(params).type; - InputEventAckState ack = base::get<0>(params).state; - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(&InputEventMessageFilter::ReceivedEventAck, - this, type, ack)); - } - return false; - } - - base::Closure quit_; - WebInputEvent::Type type_; - InputEventAckState state_; - - DISALLOW_COPY_AND_ASSIGN(InputEventMessageFilter); -}; - class TouchInputBrowserTest : public ContentBrowserTest { public: TouchInputBrowserTest() {} @@ -146,33 +99,27 @@ class TouchInputBrowserTest : public ContentBrowserTest { shell()->web_contents()->GetRenderViewHost()->GetWidget()); } - InputEventMessageFilter* filter() { return filter_.get(); } + scoped_refptr<InputMsgWatcher> AddFilter(blink::WebInputEvent::Type type) { + return new InputMsgWatcher(GetWidgetHost(), type); + } protected: - void LoadURLAndAddFilter() { + void LoadURL() { const GURL data_url(kTouchEventDataURL); NavigateToURL(shell(), data_url); - WebContentsImpl* web_contents = - static_cast<WebContentsImpl*>(shell()->web_contents()); - RenderWidgetHostImpl* host = RenderWidgetHostImpl::From( - web_contents->GetRenderViewHost()->GetWidget()); + RenderWidgetHostImpl* host = GetWidgetHost(); host->GetView()->SetSize(gfx::Size(400, 400)); // The page is loaded in the renderer, wait for a new frame to arrive. while (!host->ScheduleComposite()) GiveItSomeTime(); - - filter_ = new InputEventMessageFilter(); - host->GetProcess()->AddFilter(filter_.get()); } void SetUpCommandLine(base::CommandLine* cmd) override { cmd->AppendSwitchASCII(switches::kTouchEvents, switches::kTouchEventsEnabled); } - - scoped_refptr<InputEventMessageFilter> filter_; }; #if defined(OS_MACOSX) @@ -182,17 +129,16 @@ class TouchInputBrowserTest : public ContentBrowserTest { #define MAYBE_TouchNoHandler TouchNoHandler #endif IN_PROC_BROWSER_TEST_F(TouchInputBrowserTest, MAYBE_TouchNoHandler) { - LoadURLAndAddFilter(); + LoadURL(); SyntheticWebTouchEvent touch; // A press on |first| should be acked with NO_CONSUMER_EXISTS since there is // no touch-handler on it. touch.PressPoint(25, 25); + scoped_refptr<InputMsgWatcher> filter = AddFilter(WebInputEvent::TouchStart); GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo()); - filter()->WaitForAck(WebInputEvent::TouchStart); - EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, - filter()->last_ack_state()); + EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, filter->WaitForAck()); // If a touch-press is acked with NO_CONSUMER_EXISTS, then subsequent // touch-points don't need to be dispatched until the touch point is released. @@ -208,20 +154,21 @@ IN_PROC_BROWSER_TEST_F(TouchInputBrowserTest, MAYBE_TouchNoHandler) { #define MAYBE_TouchHandlerNoConsume TouchHandlerNoConsume #endif IN_PROC_BROWSER_TEST_F(TouchInputBrowserTest, MAYBE_TouchHandlerNoConsume) { - LoadURLAndAddFilter(); + LoadURL(); SyntheticWebTouchEvent touch; // Press on |second| should be acked with NOT_CONSUMED since there is a // touch-handler on |second|, but it doesn't consume the event. touch.PressPoint(125, 25); + scoped_refptr<InputMsgWatcher> filter = AddFilter(WebInputEvent::TouchStart); GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo()); - filter()->WaitForAck(WebInputEvent::TouchStart); - EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, filter()->last_ack_state()); + EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, filter->WaitForAck()); + filter = AddFilter(WebInputEvent::TouchEnd); touch.ReleasePoint(0); GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo()); - filter()->WaitForAck(WebInputEvent::TouchEnd); touch.ResetPoints(); + filter->WaitForAck(); } #if defined(OS_CHROMEOS) @@ -231,19 +178,20 @@ IN_PROC_BROWSER_TEST_F(TouchInputBrowserTest, MAYBE_TouchHandlerNoConsume) { #define MAYBE_TouchHandlerConsume TouchHandlerConsume #endif IN_PROC_BROWSER_TEST_F(TouchInputBrowserTest, MAYBE_TouchHandlerConsume) { - LoadURLAndAddFilter(); + LoadURL(); SyntheticWebTouchEvent touch; // Press on |third| should be acked with CONSUMED since the touch-handler on // |third| consimes the event. touch.PressPoint(25, 125); + scoped_refptr<InputMsgWatcher> filter = AddFilter(WebInputEvent::TouchStart); GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo()); - filter()->WaitForAck(WebInputEvent::TouchStart); - EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, filter()->last_ack_state()); + EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, filter->WaitForAck()); touch.ReleasePoint(0); + filter = AddFilter(WebInputEvent::TouchEnd); GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo()); - filter()->WaitForAck(WebInputEvent::TouchEnd); + filter->WaitForAck(); } #if defined(OS_CHROMEOS) @@ -256,21 +204,20 @@ IN_PROC_BROWSER_TEST_F(TouchInputBrowserTest, MAYBE_TouchHandlerConsume) { #define MAYBE_MultiPointTouchPress MultiPointTouchPress #endif IN_PROC_BROWSER_TEST_F(TouchInputBrowserTest, MAYBE_MultiPointTouchPress) { - LoadURLAndAddFilter(); + LoadURL(); SyntheticWebTouchEvent touch; // Press on |first|, which sould be acked with NO_CONSUMER_EXISTS. Then press // on |third|. That point should be acked with CONSUMED. touch.PressPoint(25, 25); + scoped_refptr<InputMsgWatcher> filter = AddFilter(WebInputEvent::TouchStart); GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo()); - filter()->WaitForAck(WebInputEvent::TouchStart); - EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, - filter()->last_ack_state()); + EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, filter->WaitForAck()); touch.PressPoint(25, 125); + filter = AddFilter(WebInputEvent::TouchStart); GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo()); - filter()->WaitForAck(WebInputEvent::TouchStart); - EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, filter()->last_ack_state()); + EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, filter->WaitForAck()); } } // namespace content diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm index 5178d8c..e7a7ace 100644 --- a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm +++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm @@ -1000,7 +1000,9 @@ class RenderWidgetHostViewMacPinchTest : public RenderWidgetHostViewMacTest { break; } DCHECK(message); - base::Tuple<IPC::WebInputEventPointer, ui::LatencyInfo> data; + base::Tuple<IPC::WebInputEventPointer, ui::LatencyInfo, + InputEventDispatchType> + data; InputMsg_HandleInputEvent::Read(message, &data); IPC::WebInputEventPointer ipc_event = base::get<0>(data); const blink::WebGestureEvent* gesture_event = diff --git a/content/common/input/input_event_ack_state.h b/content/common/input/input_event_ack_state.h index 5b9c706..c8e8f83 100644 --- a/content/common/input/input_event_ack_state.h +++ b/content/common/input/input_event_ack_state.h @@ -14,7 +14,8 @@ enum InputEventAckState { INPUT_EVENT_ACK_STATE_NOT_CONSUMED, INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, INPUT_EVENT_ACK_STATE_IGNORED, - INPUT_EVENT_ACK_STATE_MAX = INPUT_EVENT_ACK_STATE_IGNORED + INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING, + INPUT_EVENT_ACK_STATE_MAX = INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING }; } // namespace content diff --git a/content/common/input/input_event_dispatch_type.h b/content/common/input/input_event_dispatch_type.h new file mode 100644 index 0000000..e4acb4c --- /dev/null +++ b/content/common/input/input_event_dispatch_type.h @@ -0,0 +1,18 @@ +// Copyright 2016 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. + +#ifndef CONTENT_COMMON_INPUT_INPUT_EVENT_DISPATCH_TYPE_H_ +#define CONTENT_COMMON_INPUT_INPUT_EVENT_DISPATCH_TYPE_H_ + +namespace content { + +enum InputEventDispatchType { + DISPATCH_TYPE_NORMAL, // Dispatch a normal event. + DISPATCH_TYPE_NON_BLOCKING, // Dispatch a non-blocking event. + DISPATCH_TYPE_MAX = DISPATCH_TYPE_NON_BLOCKING +}; + +} // namespace content + +#endif // CONTENT_COMMON_INPUT_INPUT_EVENT_DISPATCH_TYPE_H_ diff --git a/content/common/input/web_input_event_queue.h b/content/common/input/web_input_event_queue.h new file mode 100644 index 0000000..fa85bd4 --- /dev/null +++ b/content/common/input/web_input_event_queue.h @@ -0,0 +1,79 @@ +// Copyright 2016 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. + +#ifndef CONTENT_COMMON_INPUT_WEB_INPUT_EVENT_QUEUE_H_ +#define CONTENT_COMMON_INPUT_WEB_INPUT_EVENT_QUEUE_H_ + +#include <deque> + +#include "base/memory/scoped_ptr.h" + +namespace content { + +enum class WebInputEventQueueState { ITEM_PENDING, ITEM_NOT_PENDING }; + +// WebInputEventQueue is a coalescing queue with the addition of a state +// variable that represents whether an item is pending to be processed. +// The desired usage sending with this queue is: +// if (queue.state() == WebInputEventQueueState::ITEM_PENDING) { +// queue.Queue(T); +// } else { +// send T +// queue.set_state(WebInputEventQueueState::ITEM_PENDING); +// } +// +// Processing the event response: +// if (!queue.empty()) { +// T = queue.Pop(); +// send T now +// } else { +// queue.set_state(WebInputEventQueueState::ITEM_NOT_PENDING); +// } +// +template <typename T> +class WebInputEventQueue { + public: + WebInputEventQueue() : state_(WebInputEventQueueState::ITEM_NOT_PENDING) {} + + // Adds an event to the queue. The event may be coalesced with previously + // queued events. + void Queue(const T& event) { + if (!queue_.empty()) { + scoped_ptr<T>& last_event = queue_.back(); + if (last_event->CanCoalesceWith(event)) { + last_event->CoalesceWith(event); + return; + } + } + queue_.emplace_back(scoped_ptr<T>(new T(event))); + } + + scoped_ptr<T> Pop() { + scoped_ptr<T> result; + if (!queue_.empty()) { + result.reset(queue_.front().release()); + queue_.pop_front(); + } + return result; + } + + bool empty() const { return queue_.empty(); } + + size_t size() const { return queue_.size(); } + + void set_state(WebInputEventQueueState state) { state_ = state; } + + WebInputEventQueueState state() const WARN_UNUSED_RESULT { return state_; } + + private: + typedef std::deque<scoped_ptr<T>> EventQueue; + EventQueue queue_; + WebInputEventQueueState state_; + + DISALLOW_COPY_AND_ASSIGN(WebInputEventQueue); +}; + +} // namespace content + +#endif // CONTENT_COMMON_INPUT_WEB_INPUT_EVENT_QUEUE_H_ diff --git a/content/common/input_messages.h b/content/common/input_messages.h index 7f05b9f..ed0f5f2 100644 --- a/content/common/input_messages.h +++ b/content/common/input_messages.h @@ -15,6 +15,7 @@ #include "content/common/input/input_event.h" #include "content/common/input/input_event_ack.h" #include "content/common/input/input_event_ack_state.h" +#include "content/common/input/input_event_dispatch_type.h" #include "content/common/input/input_param_traits.h" #include "content/common/input/synthetic_gesture_packet.h" #include "content/common/input/synthetic_gesture_params.h" @@ -48,6 +49,8 @@ IPC_ENUM_TRAITS_MAX_VALUE( IPC_ENUM_TRAITS_MAX_VALUE( content::SyntheticGestureParams::GestureType, content::SyntheticGestureParams::SYNTHETIC_GESTURE_TYPE_MAX) +IPC_ENUM_TRAITS_MAX_VALUE(content::InputEventDispatchType, + content::InputEventDispatchType::DISPATCH_TYPE_MAX) IPC_ENUM_TRAITS_VALIDATE(content::TouchAction, ( value >= 0 && value <= content::TOUCH_ACTION_MAX && @@ -114,9 +117,10 @@ IPC_STRUCT_TRAITS_BEGIN(content::InputEventAck) IPC_STRUCT_TRAITS_END() // Sends an input event to the render widget. -IPC_MESSAGE_ROUTED2(InputMsg_HandleInputEvent, +IPC_MESSAGE_ROUTED3(InputMsg_HandleInputEvent, IPC::WebInputEventPointer /* event */, - ui::LatencyInfo /* latency_info */) + ui::LatencyInfo /* latency_info */, + content::InputEventDispatchType) // Sends the cursor visibility state to the render widget. IPC_MESSAGE_ROUTED1(InputMsg_CursorVisibilityChange, diff --git a/content/content_common.gypi b/content/content_common.gypi index 482e46e..f781de3 100644 --- a/content/content_common.gypi +++ b/content/content_common.gypi @@ -391,6 +391,7 @@ 'common/input/input_event.h', 'common/input/input_event_ack.cc', 'common/input/input_event_ack.h', + 'common/input/input_event_dispatch_type.h', 'common/input/input_event_stream_validator.cc', 'common/input/input_event_stream_validator.h', 'common/input/input_event_utils.cc', @@ -415,6 +416,7 @@ 'common/input/synthetic_web_input_event_builders.h', 'common/input/touch_event_stream_validator.cc', 'common/input/touch_event_stream_validator.h', + 'common/input/web_input_event_queue.h', 'common/input/web_input_event_traits.cc', 'common/input/web_input_event_traits.h', 'common/input/web_touch_event_traits.cc', diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi index 0592cf2..338c04a 100644 --- a/content/content_renderer.gypi +++ b/content/content_renderer.gypi @@ -250,6 +250,8 @@ 'renderer/input/input_handler_wrapper.h', 'renderer/input/main_thread_input_event_filter.cc', 'renderer/input/main_thread_input_event_filter.h', + 'renderer/input/non_blocking_event_queue.cc', + 'renderer/input/non_blocking_event_queue.h', 'renderer/input/render_widget_input_handler.cc', 'renderer/input/render_widget_input_handler.h', 'renderer/input/render_widget_input_handler_delegate.h', diff --git a/content/content_tests.gypi b/content/content_tests.gypi index 5e03b22..5e56ecd 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -243,6 +243,7 @@ 'browser/mojo_shell_browsertest.cc', 'browser/net_info_browsertest.cc', 'browser/renderer_host/input/composited_scrolling_browsertest.cc', + 'browser/renderer_host/input/non_blocking_event_browsertest.cc', 'browser/renderer_host/input/touch_action_browsertest.cc', 'browser/renderer_host/input/touch_input_browsertest.cc', 'browser/renderer_host/input/touch_selection_controller_client_aura_browsertest.cc', @@ -709,6 +710,7 @@ 'renderer/gpu/render_widget_compositor_unittest.cc', 'renderer/ico_image_decoder_unittest.cc', 'renderer/input/input_event_filter_unittest.cc', + 'renderer/input/non_blocking_event_queue_unittest.cc', 'renderer/manifest/manifest_parser_unittest.cc', 'renderer/media/android/media_info_loader_unittest.cc', 'renderer/media/audio_message_filter_unittest.cc', diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc index be9b2e0..51ac846 100644 --- a/content/public/test/browser_test_utils.cc +++ b/content/public/test/browser_test_utils.cc @@ -25,6 +25,7 @@ #include "content/browser/web_contents/web_contents_impl.h" #include "content/browser/web_contents/web_contents_view.h" #include "content/common/input/synthetic_web_input_event_builders.h" +#include "content/common/input_messages.h" #include "content/common/view_messages.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/histogram_fetcher.h" @@ -1073,16 +1074,24 @@ FrameWatcher::FrameWatcher() FrameWatcher::~FrameWatcher() { } -void FrameWatcher::ReceivedFrameSwap() { +void FrameWatcher::ReceivedFrameSwap(cc::CompositorFrameMetadata metadata) { --frames_to_wait_; + last_metadata_ = metadata; if (frames_to_wait_ == 0) quit_.Run(); } bool FrameWatcher::OnMessageReceived(const IPC::Message& message) { if (message.type() == ViewHostMsg_SwapCompositorFrame::ID) { - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(&FrameWatcher::ReceivedFrameSwap, this)); + ViewHostMsg_SwapCompositorFrame::Param param; + if (!ViewHostMsg_SwapCompositorFrame::Read(&message, ¶m)) + return false; + scoped_ptr<cc::CompositorFrame> frame(new cc::CompositorFrame); + base::get<1>(param).AssignTo(frame.get()); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&FrameWatcher::ReceivedFrameSwap, this, frame->metadata)); } return false; } @@ -1103,6 +1112,10 @@ void FrameWatcher::WaitFrames(int frames_to_wait) { run_loop.Run(); } +const cc::CompositorFrameMetadata& FrameWatcher::LastMetadata() { + return last_metadata_; +} + MainThreadFrameObserver::MainThreadFrameObserver( RenderWidgetHost* render_widget_host) : render_widget_host_(render_widget_host), @@ -1140,4 +1153,48 @@ bool MainThreadFrameObserver::OnMessageReceived(const IPC::Message& msg) { return true; } +InputMsgWatcher::InputMsgWatcher(RenderWidgetHost* render_widget_host, + blink::WebInputEvent::Type type) + : BrowserMessageFilter(InputMsgStart), + wait_for_type_(type), + ack_result_(INPUT_EVENT_ACK_STATE_UNKNOWN) { + render_widget_host->GetProcess()->AddFilter(this); +} + +InputMsgWatcher::~InputMsgWatcher() {} + +void InputMsgWatcher::ReceivedAck(blink::WebInputEvent::Type ack_type, + uint32_t ack_state) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (wait_for_type_ == ack_type) { + ack_result_ = ack_state; + if (!quit_.is_null()) + quit_.Run(); + } +} + +bool InputMsgWatcher::OnMessageReceived(const IPC::Message& message) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + if (message.type() == InputHostMsg_HandleInputEvent_ACK::ID) { + InputHostMsg_HandleInputEvent_ACK::Param params; + InputHostMsg_HandleInputEvent_ACK::Read(&message, ¶ms); + blink::WebInputEvent::Type ack_type = base::get<0>(params).type; + InputEventAckState ack_state = base::get<0>(params).state; + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&InputMsgWatcher::ReceivedAck, this, ack_type, ack_state)); + } + return false; +} + +uint32_t InputMsgWatcher::WaitForAck() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (ack_result_ != INPUT_EVENT_ACK_STATE_UNKNOWN) + return ack_result_; + base::RunLoop run_loop; + base::AutoReset<base::Closure> reset_quit(&quit_, run_loop.QuitClosure()); + run_loop.Run(); + return ack_result_; +} + } // namespace content diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h index ed6aa65..1793726 100644 --- a/content/public/test/browser_test_utils.h +++ b/content/public/test/browser_test_utils.h @@ -17,6 +17,7 @@ #include "base/process/process.h" #include "base/strings/string16.h" #include "build/build_config.h" +#include "cc/output/compositor_frame.h" #include "content/public/browser/browser_message_filter.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" @@ -425,16 +426,21 @@ class FrameWatcher : public BrowserMessageFilter { // Wait for |frames_to_wait| swap mesages from the compositor. void WaitFrames(int frames_to_wait); + // Return the meta data received in the last compositor + // swap frame. + const cc::CompositorFrameMetadata& LastMetadata(); + private: ~FrameWatcher() override; // Overridden BrowserMessageFilter methods. bool OnMessageReceived(const IPC::Message& message) override; - void ReceivedFrameSwap(); + void ReceivedFrameSwap(cc::CompositorFrameMetadata meta_data); int frames_to_wait_; base::Closure quit_; + cc::CompositorFrameMetadata last_metadata_; DISALLOW_COPY_AND_ASSIGN(FrameWatcher); }; @@ -463,6 +469,31 @@ class MainThreadFrameObserver : public IPC::Listener { DISALLOW_COPY_AND_ASSIGN(MainThreadFrameObserver); }; +// Watches for an input msg to be consumed. +class InputMsgWatcher : public BrowserMessageFilter { + public: + InputMsgWatcher(RenderWidgetHost* render_widget_host, + blink::WebInputEvent::Type type); + + // Wait until ack message occurs, returning the ack result from + // the message. + uint32_t WaitForAck(); + + private: + ~InputMsgWatcher() override; + + // Overridden BrowserMessageFilter methods. + bool OnMessageReceived(const IPC::Message& message) override; + + void ReceivedAck(blink::WebInputEvent::Type ack_type, uint32_t ack_state); + + blink::WebInputEvent::Type wait_for_type_; + uint32_t ack_result_; + base::Closure quit_; + + DISALLOW_COPY_AND_ASSIGN(InputMsgWatcher); +}; + } // namespace content #endif // CONTENT_PUBLIC_TEST_BROWSER_TEST_UTILS_H_ diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc index 2706d71..c622ee8 100644 --- a/content/public/test/render_view_test.cc +++ b/content/public/test/render_view_test.cc @@ -441,14 +441,16 @@ void RenderViewTest::SendWebKeyboardEvent( const blink::WebKeyboardEvent& key_event) { RenderViewImpl* impl = static_cast<RenderViewImpl*>(view_); impl->OnMessageReceived( - InputMsg_HandleInputEvent(0, &key_event, ui::LatencyInfo())); + InputMsg_HandleInputEvent(0, &key_event, ui::LatencyInfo(), + InputEventDispatchType::DISPATCH_TYPE_NORMAL)); } void RenderViewTest::SendWebMouseEvent( const blink::WebMouseEvent& mouse_event) { RenderViewImpl* impl = static_cast<RenderViewImpl*>(view_); impl->OnMessageReceived( - InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo())); + InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo(), + InputEventDispatchType::DISPATCH_TYPE_NORMAL)); } const char* const kGetCoordinatesScript = @@ -515,10 +517,12 @@ void RenderViewTest::SimulatePointClick(const gfx::Point& point) { mouse_event.clickCount = 1; RenderViewImpl* impl = static_cast<RenderViewImpl*>(view_); impl->OnMessageReceived( - InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo())); + InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo(), + InputEventDispatchType::DISPATCH_TYPE_NORMAL)); mouse_event.type = WebInputEvent::MouseUp; impl->OnMessageReceived( - InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo())); + InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo(), + InputEventDispatchType::DISPATCH_TYPE_NORMAL)); } @@ -539,10 +543,12 @@ void RenderViewTest::SimulatePointRightClick(const gfx::Point& point) { mouse_event.clickCount = 1; RenderViewImpl* impl = static_cast<RenderViewImpl*>(view_); impl->OnMessageReceived( - InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo())); + InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo(), + InputEventDispatchType::DISPATCH_TYPE_NORMAL)); mouse_event.type = WebInputEvent::MouseUp; impl->OnMessageReceived( - InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo())); + InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo(), + InputEventDispatchType::DISPATCH_TYPE_NORMAL)); } void RenderViewTest::SimulateRectTap(const gfx::Rect& rect) { @@ -556,7 +562,8 @@ void RenderViewTest::SimulateRectTap(const gfx::Rect& rect) { gesture_event.sourceDevice = blink::WebGestureDeviceTouchpad; RenderViewImpl* impl = static_cast<RenderViewImpl*>(view_); impl->OnMessageReceived( - InputMsg_HandleInputEvent(0, &gesture_event, ui::LatencyInfo())); + InputMsg_HandleInputEvent(0, &gesture_event, ui::LatencyInfo(), + InputEventDispatchType::DISPATCH_TYPE_NORMAL)); impl->FocusChangeComplete(); } diff --git a/content/renderer/android/synchronous_compositor_filter.cc b/content/renderer/android/synchronous_compositor_filter.cc index 5f4c9d2..54dfed3 100644 --- a/content/renderer/android/synchronous_compositor_filter.cc +++ b/content/renderer/android/synchronous_compositor_filter.cc @@ -255,6 +255,10 @@ void SynchronousCompositorFilter::DidStopFlinging(int routing_id) { Send(new InputHostMsg_DidStopFlinging(routing_id)); } +void SynchronousCompositorFilter::NonBlockingInputEventHandled( + int routing_id, + blink::WebInputEvent::Type type) {} + SynchronousCompositorFilter::Entry::Entry() : begin_frame_source(nullptr), output_surface(nullptr), diff --git a/content/renderer/android/synchronous_compositor_filter.h b/content/renderer/android/synchronous_compositor_filter.h index 89d8300..bfab1f6 100644 --- a/content/renderer/android/synchronous_compositor_filter.h +++ b/content/renderer/android/synchronous_compositor_filter.h @@ -66,6 +66,8 @@ class SynchronousCompositorFilter : public IPC::MessageFilter, void DidOverscroll(int routing_id, const DidOverscrollParams& params) override; void DidStopFlinging(int routing_id) override; + void NonBlockingInputEventHandled(int routing_id, + blink::WebInputEvent::Type type) override; private: ~SynchronousCompositorFilter() override; diff --git a/content/renderer/idle_user_detector.cc b/content/renderer/idle_user_detector.cc index 339f68a..9ca3570 100644 --- a/content/renderer/idle_user_detector.cc +++ b/content/renderer/idle_user_detector.cc @@ -25,8 +25,10 @@ bool IdleUserDetector::OnMessageReceived(const IPC::Message& message) { return false; } -void IdleUserDetector::OnHandleInputEvent(const blink::WebInputEvent* event, - const ui::LatencyInfo& latency_info) { +void IdleUserDetector::OnHandleInputEvent( + const blink::WebInputEvent* event, + const ui::LatencyInfo& latency_info, + InputEventDispatchType dispatch_type) { if (GetContentClient()->renderer()->RunIdleHandlerWhenWidgetsHidden()) { RenderThreadImpl* render_thread = RenderThreadImpl::current(); if (render_thread != NULL) { diff --git a/content/renderer/idle_user_detector.h b/content/renderer/idle_user_detector.h index 3e3beba..cbe85a0 100644 --- a/content/renderer/idle_user_detector.h +++ b/content/renderer/idle_user_detector.h @@ -6,6 +6,7 @@ #define CONTENT_RENDERER_IDLE_USER_DETECTOR_H_ #include "base/macros.h" +#include "content/common/input/input_event_dispatch_type.h" #include "content/public/renderer/render_view_observer.h" namespace blink { @@ -30,7 +31,8 @@ class IdleUserDetector : public RenderViewObserver { bool OnMessageReceived(const IPC::Message& message) override; void OnHandleInputEvent(const blink::WebInputEvent* event, - const ui::LatencyInfo& latency_info); + const ui::LatencyInfo& latency_info, + InputEventDispatchType dispatch_type); DISALLOW_COPY_AND_ASSIGN(IdleUserDetector); }; diff --git a/content/renderer/input/input_event_filter.cc b/content/renderer/input/input_event_filter.cc index 927a402..9a90141 100644 --- a/content/renderer/input/input_event_filter.cc +++ b/content/renderer/input/input_event_filter.cc @@ -65,11 +65,13 @@ void InputEventFilter::DidAddInputHandler( synchronous_input_handler_proxy) { base::AutoLock locked(routes_lock_); routes_.insert(routing_id); + route_queues_[routing_id].reset(new NonBlockingEventQueue(routing_id, this)); } void InputEventFilter::DidRemoveInputHandler(int routing_id) { base::AutoLock locked(routes_lock_); routes_.erase(routing_id); + route_queues_.erase(routing_id); } void InputEventFilter::DidOverscroll(int routing_id, @@ -87,6 +89,17 @@ void InputEventFilter::DidStopFlinging(int routing_id) { SendMessage(make_scoped_ptr(new InputHostMsg_DidStopFlinging(routing_id))); } +void InputEventFilter::NonBlockingInputEventHandled( + int routing_id, + blink::WebInputEvent::Type type) { + DCHECK(target_task_runner_->BelongsToCurrentThread()); + RouteQueueMap::iterator iter = route_queues_.find(routing_id); + if (iter == route_queues_.end() || !iter->second) + return; + + iter->second->EventHandled(type); +} + void InputEventFilter::OnFilterAdded(IPC::Sender* sender) { io_task_runner_ = base::ThreadTaskRunnerHandle::Get(); sender_ = sender; @@ -152,9 +165,11 @@ void InputEventFilter::ForwardToHandler(const IPC::Message& message) { return; const WebInputEvent* event = base::get<0>(params); ui::LatencyInfo latency_info = base::get<1>(params); + InputEventDispatchType dispatch_type = base::get<2>(params); DCHECK(event); + DCHECK_EQ(DISPATCH_TYPE_NORMAL, dispatch_type); - const bool send_ack = WebInputEventTraits::WillReceiveAckFromRenderer(*event); + bool send_ack = WebInputEventTraits::WillReceiveAckFromRenderer(*event); // Intercept |DidOverscroll| notifications, bundling any triggered overscroll // response with the input event ack. @@ -165,16 +180,21 @@ void InputEventFilter::ForwardToHandler(const IPC::Message& message) { InputEventAckState ack_state = handler_.Run(routing_id, event, &latency_info); - if (ack_state == INPUT_EVENT_ACK_STATE_NOT_CONSUMED) { + if (ack_state == INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING) { + DCHECK(!overscroll_params); + RouteQueueMap::iterator iter = route_queues_.find(routing_id); + if (iter != route_queues_.end()) + iter->second->HandleEvent(event, latency_info); + } else if (ack_state == INPUT_EVENT_ACK_STATE_NOT_CONSUMED) { DCHECK(!overscroll_params); TRACE_EVENT_INSTANT0( - "input", - "InputEventFilter::ForwardToHandler::ForwardToMainListener", + "input", "InputEventFilter::ForwardToHandler::ForwardToMainListener", TRACE_EVENT_SCOPE_THREAD); IPC::Message new_msg = - InputMsg_HandleInputEvent(routing_id, event, latency_info); + InputMsg_HandleInputEvent(routing_id, event, latency_info, + InputEventDispatchType::DISPATCH_TYPE_NORMAL); main_task_runner_->PostTask(FROM_HERE, base::Bind(main_listener_, new_msg)); - return; + send_ack = false; } if (!send_ack) @@ -204,4 +224,15 @@ void InputEventFilter::SendMessageOnIOThread(scoped_ptr<IPC::Message> message) { sender_->Send(message.release()); } +void InputEventFilter::SendNonBlockingEvent(int routing_id, + const blink::WebInputEvent* event, + const ui::LatencyInfo& latency) { + TRACE_EVENT_INSTANT0("input", "InputEventFilter::SendNonBlockingEvent", + TRACE_EVENT_SCOPE_THREAD); + IPC::Message new_msg = InputMsg_HandleInputEvent( + routing_id, event, latency, + InputEventDispatchType::DISPATCH_TYPE_NON_BLOCKING); + main_task_runner_->PostTask(FROM_HERE, base::Bind(main_listener_, new_msg)); +} + } // namespace content diff --git a/content/renderer/input/input_event_filter.h b/content/renderer/input/input_event_filter.h index e2d18f3..71d22f7 100644 --- a/content/renderer/input/input_event_filter.h +++ b/content/renderer/input/input_event_filter.h @@ -7,12 +7,14 @@ #include <queue> #include <set> +#include <unordered_map> #include "base/callback.h" #include "base/synchronization/lock.h" #include "content/common/content_export.h" #include "content/common/input/input_event_ack_state.h" #include "content/renderer/input/input_handler_manager_client.h" +#include "content/renderer/input/non_blocking_event_queue.h" #include "ipc/message_filter.h" #include "third_party/WebKit/public/web/WebInputEvent.h" @@ -40,7 +42,8 @@ class Sender; namespace content { class CONTENT_EXPORT InputEventFilter : public InputHandlerManagerClient, - public IPC::MessageFilter { + public IPC::MessageFilter, + public NonBlockingEventQueueClient { public: InputEventFilter( const base::Callback<void(const IPC::Message&)>& main_listener, @@ -66,6 +69,8 @@ class CONTENT_EXPORT InputEventFilter : public InputHandlerManagerClient, void DidOverscroll(int routing_id, const DidOverscrollParams& params) override; void DidStopFlinging(int routing_id) override; + void NonBlockingInputEventHandled(int routing_id, + blink::WebInputEvent::Type type) override; // IPC::MessageFilter methods: void OnFilterAdded(IPC::Sender* sender) override; @@ -73,6 +78,11 @@ class CONTENT_EXPORT InputEventFilter : public InputHandlerManagerClient, void OnChannelClosing() override; bool OnMessageReceived(const IPC::Message& message) override; + // NonBlockingEventQueueClient methods: + void SendNonBlockingEvent(int routing_id, + const blink::WebInputEvent* event, + const ui::LatencyInfo& latency) override; + private: ~InputEventFilter() override; @@ -97,6 +107,10 @@ class CONTENT_EXPORT InputEventFilter : public InputHandlerManagerClient, // Indicates the routing_ids for which input events should be filtered. std::set<int> routes_; + using RouteQueueMap = + std::unordered_map<int, scoped_ptr<NonBlockingEventQueue>>; + RouteQueueMap route_queues_; + // Used to intercept overscroll notifications while an event is being // dispatched. If the event causes overscroll, the overscroll metadata can be // bundled in the event ack, saving an IPC. Note that we must continue diff --git a/content/renderer/input/input_event_filter_unittest.cc b/content/renderer/input/input_event_filter_unittest.cc index 1aace83..8027752 100644 --- a/content/renderer/input/input_event_filter_unittest.cc +++ b/content/renderer/input/input_event_filter_unittest.cc @@ -23,6 +23,8 @@ using blink::WebInputEvent; using blink::WebMouseEvent; +using blink::WebMouseWheelEvent; +using blink::WebTouchEvent; namespace content { namespace { @@ -34,12 +36,13 @@ class InputEventRecorder { InputEventRecorder() : filter_(NULL), handle_events_(false), - send_to_widget_(false) { - } + send_to_widget_(false), + passive_(false) {} void set_filter(InputEventFilter* filter) { filter_ = filter; } void set_handle_events(bool value) { handle_events_ = value; } void set_send_to_widget(bool value) { send_to_widget_ = value; } + void set_passive(bool value) { passive_ = value; } size_t record_count() const { return records_.size(); } @@ -61,9 +64,13 @@ class InputEventRecorder { if (handle_events_) { return INPUT_EVENT_ACK_STATE_CONSUMED; + } else if (send_to_widget_) { + if (passive_) + return INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING; + else + return INPUT_EVENT_ACK_STATE_NOT_CONSUMED; } else { - return send_to_widget_ ? INPUT_EVENT_ACK_STATE_NOT_CONSUMED - : INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; + return INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; } } @@ -79,6 +86,7 @@ class InputEventRecorder { InputEventFilter* filter_; bool handle_events_; bool send_to_widget_; + bool passive_; std::vector<Record> records_; }; @@ -112,13 +120,15 @@ void AddMessagesToFilter(IPC::MessageFilter* message_filter, base::MessageLoop::current()->RunUntilIdle(); } +template <typename T> void AddEventsToFilter(IPC::MessageFilter* message_filter, - const WebMouseEvent events[], + const T events[], size_t count) { std::vector<IPC::Message> messages; for (size_t i = 0; i < count; ++i) { - messages.push_back(InputMsg_HandleInputEvent(kTestRoutingID, &events[i], - ui::LatencyInfo())); + messages.push_back(InputMsg_HandleInputEvent( + kTestRoutingID, &events[i], ui::LatencyInfo(), + InputEventDispatchType::DISPATCH_TYPE_NORMAL)); } AddMessagesToFilter(message_filter, messages); @@ -254,8 +264,9 @@ TEST_F(InputEventFilterTest, PreserveRelativeOrder) { SyntheticWebMouseEventBuilder::Build(WebMouseEvent::MouseUp); std::vector<IPC::Message> messages; - messages.push_back(InputMsg_HandleInputEvent(kTestRoutingID, &mouse_down, - ui::LatencyInfo())); + messages.push_back( + InputMsg_HandleInputEvent(kTestRoutingID, &mouse_down, ui::LatencyInfo(), + InputEventDispatchType::DISPATCH_TYPE_NORMAL)); // Control where input events are delivered. messages.push_back(InputMsg_MouseCaptureLost(kTestRoutingID)); messages.push_back(InputMsg_SetFocus(kTestRoutingID, true)); @@ -282,7 +293,8 @@ TEST_F(InputEventFilterTest, PreserveRelativeOrder) { messages.push_back(InputMsg_MoveCaret(kTestRoutingID, gfx::Point())); messages.push_back( - InputMsg_HandleInputEvent(kTestRoutingID, &mouse_up, ui::LatencyInfo())); + InputMsg_HandleInputEvent(kTestRoutingID, &mouse_up, ui::LatencyInfo(), + InputEventDispatchType::DISPATCH_TYPE_NORMAL)); AddMessagesToFilter(filter_.get(), messages); // We should have sent all messages back to the main thread and preserved @@ -293,4 +305,156 @@ TEST_F(InputEventFilterTest, PreserveRelativeOrder) { } } +TEST_F(InputEventFilterTest, NonBlockingWheel) { + WebMouseWheelEvent kEvents[4] = { + SyntheticWebMouseWheelEventBuilder::Build(10, 10, 0, 53, 0, false), + SyntheticWebMouseWheelEventBuilder::Build(20, 20, 0, 53, 0, false), + SyntheticWebMouseWheelEventBuilder::Build(30, 30, 0, 53, 1, false), + SyntheticWebMouseWheelEventBuilder::Build(30, 30, 0, 53, 1, false), + }; + + filter_->DidAddInputHandler(kTestRoutingID, nullptr); + event_recorder_.set_send_to_widget(true); + event_recorder_.set_passive(true); + + AddEventsToFilter(filter_.get(), kEvents, arraysize(kEvents)); + EXPECT_EQ(arraysize(kEvents), event_recorder_.record_count()); + ASSERT_EQ(4u, ipc_sink_.message_count()); + + // First event is sent right away. + EXPECT_EQ(1u, message_recorder_.message_count()); + + // Second event was queued; ack the first. + filter_->NonBlockingInputEventHandled(kTestRoutingID, + WebInputEvent::MouseWheel); + base::MessageLoop::current()->RunUntilIdle(); + ASSERT_EQ(4u, ipc_sink_.message_count()); + EXPECT_EQ(2u, message_recorder_.message_count()); + + // Third event won't be coalesced into the second because modifiers are + // different. + filter_->NonBlockingInputEventHandled(kTestRoutingID, + WebInputEvent::MouseWheel); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(3u, message_recorder_.message_count()); + + // The last events will be coalesced. + filter_->NonBlockingInputEventHandled(kTestRoutingID, + WebInputEvent::MouseWheel); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(3u, message_recorder_.message_count()); + + // First two messages should be identical. + for (size_t i = 0; i < 2; ++i) { + const IPC::Message& message = message_recorder_.message_at(i); + + ASSERT_EQ(InputMsg_HandleInputEvent::ID, message.type()); + InputMsg_HandleInputEvent::Param params; + EXPECT_TRUE(InputMsg_HandleInputEvent::Read(&message, ¶ms)); + const WebInputEvent* event = base::get<0>(params); + InputEventDispatchType dispatch_type = base::get<2>(params); + + EXPECT_EQ(kEvents[i].size, event->size); + EXPECT_TRUE(memcmp(&kEvents[i], event, event->size) == 0); + EXPECT_EQ(InputEventDispatchType::DISPATCH_TYPE_NON_BLOCKING, + dispatch_type); + } + + // Third message is coalesced. + { + const IPC::Message& message = message_recorder_.message_at(2); + + ASSERT_EQ(InputMsg_HandleInputEvent::ID, message.type()); + InputMsg_HandleInputEvent::Param params; + EXPECT_TRUE(InputMsg_HandleInputEvent::Read(&message, ¶ms)); + const WebMouseWheelEvent* event = + static_cast<const WebMouseWheelEvent*>(base::get<0>(params)); + InputEventDispatchType dispatch_type = base::get<2>(params); + + EXPECT_EQ(kEvents[2].size, event->size); + EXPECT_EQ(kEvents[2].deltaX + kEvents[3].deltaX, event->deltaX); + EXPECT_EQ(kEvents[2].deltaY + kEvents[3].deltaY, event->deltaY); + EXPECT_EQ(InputEventDispatchType::DISPATCH_TYPE_NON_BLOCKING, + dispatch_type); + } +} + +TEST_F(InputEventFilterTest, NonBlockingTouch) { + SyntheticWebTouchEvent kEvents[4]; + kEvents[0].PressPoint(10, 10); + kEvents[1].PressPoint(10, 10); + kEvents[1].modifiers = 1; + kEvents[1].MovePoint(0, 20, 20); + kEvents[2].PressPoint(10, 10); + kEvents[2].MovePoint(0, 30, 30); + kEvents[3].PressPoint(10, 10); + kEvents[3].MovePoint(0, 35, 35); + + filter_->DidAddInputHandler(kTestRoutingID, nullptr); + event_recorder_.set_send_to_widget(true); + event_recorder_.set_passive(true); + + AddEventsToFilter(filter_.get(), kEvents, arraysize(kEvents)); + EXPECT_EQ(arraysize(kEvents), event_recorder_.record_count()); + ASSERT_EQ(4u, ipc_sink_.message_count()); + + // First event is sent right away. + EXPECT_EQ(1u, message_recorder_.message_count()); + + // Second event was queued; ack the first. + filter_->NonBlockingInputEventHandled(kTestRoutingID, + WebInputEvent::TouchStart); + base::MessageLoop::current()->RunUntilIdle(); + ASSERT_EQ(4u, ipc_sink_.message_count()); + EXPECT_EQ(2u, message_recorder_.message_count()); + + // Third event won't be coalesced into the second because modifiers are + // different. + filter_->NonBlockingInputEventHandled(kTestRoutingID, + WebInputEvent::TouchMove); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(3u, message_recorder_.message_count()); + + // The last events will be coalesced. + filter_->NonBlockingInputEventHandled(kTestRoutingID, + WebInputEvent::TouchMove); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(3u, message_recorder_.message_count()); + + // First two messages should be identical. + for (size_t i = 0; i < 2; ++i) { + const IPC::Message& message = message_recorder_.message_at(i); + + ASSERT_EQ(InputMsg_HandleInputEvent::ID, message.type()); + InputMsg_HandleInputEvent::Param params; + EXPECT_TRUE(InputMsg_HandleInputEvent::Read(&message, ¶ms)); + const WebInputEvent* event = base::get<0>(params); + InputEventDispatchType dispatch_type = base::get<2>(params); + + EXPECT_EQ(kEvents[i].size, event->size); + EXPECT_TRUE(memcmp(&kEvents[i], event, event->size) == 0); + EXPECT_EQ(InputEventDispatchType::DISPATCH_TYPE_NON_BLOCKING, + dispatch_type); + } + + // Third message is coalesced. + { + const IPC::Message& message = message_recorder_.message_at(2); + + ASSERT_EQ(InputMsg_HandleInputEvent::ID, message.type()); + InputMsg_HandleInputEvent::Param params; + EXPECT_TRUE(InputMsg_HandleInputEvent::Read(&message, ¶ms)); + const WebTouchEvent* event = + static_cast<const WebTouchEvent*>(base::get<0>(params)); + InputEventDispatchType dispatch_type = base::get<2>(params); + + EXPECT_EQ(kEvents[3].size, event->size); + EXPECT_EQ(1u, kEvents[3].touchesLength); + EXPECT_EQ(kEvents[3].touches[0].position.x, event->touches[0].position.x); + EXPECT_EQ(kEvents[3].touches[0].position.y, event->touches[0].position.y); + EXPECT_EQ(InputEventDispatchType::DISPATCH_TYPE_NON_BLOCKING, + dispatch_type); + } +} + } // namespace content diff --git a/content/renderer/input/input_handler_manager.cc b/content/renderer/input/input_handler_manager.cc index 35ef3b4..58fd95f 100644 --- a/content/renderer/input/input_handler_manager.cc +++ b/content/renderer/input/input_handler_manager.cc @@ -36,6 +36,8 @@ InputEventAckState InputEventDispositionToAck( return INPUT_EVENT_ACK_STATE_NOT_CONSUMED; case InputHandlerProxy::DROP_EVENT: return INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; + case InputHandlerProxy::DID_HANDLE_NON_BLOCKING: + return INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING; } NOTREACHED(); return INPUT_EVENT_ACK_STATE_UNKNOWN; @@ -63,20 +65,20 @@ void InputHandlerManager::AddInputHandler( int routing_id, const base::WeakPtr<cc::InputHandler>& input_handler, const base::WeakPtr<RenderViewImpl>& render_view_impl, - bool enable_smooth_scrolling) { + bool enable_smooth_scrolling, + bool enable_wheel_gestures) { if (task_runner_->BelongsToCurrentThread()) { - AddInputHandlerOnCompositorThread(routing_id, - base::ThreadTaskRunnerHandle::Get(), - input_handler, render_view_impl, - enable_smooth_scrolling); + AddInputHandlerOnCompositorThread( + routing_id, base::ThreadTaskRunnerHandle::Get(), input_handler, + render_view_impl, enable_smooth_scrolling, enable_wheel_gestures); } else { task_runner_->PostTask( FROM_HERE, base::Bind(&InputHandlerManager::AddInputHandlerOnCompositorThread, base::Unretained(this), routing_id, base::ThreadTaskRunnerHandle::Get(), input_handler, - render_view_impl, - enable_smooth_scrolling)); + render_view_impl, enable_smooth_scrolling, + enable_wheel_gestures)); } } @@ -85,7 +87,8 @@ void InputHandlerManager::AddInputHandlerOnCompositorThread( const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner, const base::WeakPtr<cc::InputHandler>& input_handler, const base::WeakPtr<RenderViewImpl>& render_view_impl, - bool enable_smooth_scrolling) { + bool enable_smooth_scrolling, + bool enable_wheel_gestures) { DCHECK(task_runner_->BelongsToCurrentThread()); // The handler could be gone by this point if the compositor has shut down. @@ -101,7 +104,7 @@ void InputHandlerManager::AddInputHandlerOnCompositorThread( "result", "AddingRoute"); scoped_ptr<InputHandlerWrapper> wrapper(new InputHandlerWrapper( this, routing_id, main_task_runner, input_handler, render_view_impl, - enable_smooth_scrolling)); + enable_smooth_scrolling, enable_wheel_gestures)); client_->DidAddInputHandler(routing_id, wrapper->input_handler_proxy()); input_handlers_.add(routing_id, std::move(wrapper)); } @@ -141,6 +144,27 @@ void InputHandlerManager::ObserveWheelEventAndResultOnCompositorThread( wheel_event, scroll_result); } +void InputHandlerManager::NonBlockingInputEventHandledOnMainThread( + int routing_id, + blink::WebInputEvent::Type type) { + task_runner_->PostTask( + FROM_HERE, + base::Bind( + &InputHandlerManager::NonBlockingInputEventHandledOnCompositorThread, + base::Unretained(this), routing_id, type)); +} + +void InputHandlerManager::NonBlockingInputEventHandledOnCompositorThread( + int routing_id, + blink::WebInputEvent::Type handled_type) { + DCHECK(task_runner_->BelongsToCurrentThread()); + auto it = input_handlers_.find(routing_id); + if (it == input_handlers_.end()) + return; + + client_->NonBlockingInputEventHandled(routing_id, handled_type); +} + InputEventAckState InputHandlerManager::HandleInputEvent( int routing_id, const WebInputEvent* input_event, diff --git a/content/renderer/input/input_handler_manager.h b/content/renderer/input/input_handler_manager.h index 6905a88..c8bfb75 100644 --- a/content/renderer/input/input_handler_manager.h +++ b/content/renderer/input/input_handler_manager.h @@ -50,17 +50,20 @@ class InputHandlerManager { ~InputHandlerManager(); // Callable from the main thread only. - void AddInputHandler( - int routing_id, - const base::WeakPtr<cc::InputHandler>& input_handler, - const base::WeakPtr<RenderViewImpl>& render_view_impl, - bool enable_smooth_scrolling); + void AddInputHandler(int routing_id, + const base::WeakPtr<cc::InputHandler>& input_handler, + const base::WeakPtr<RenderViewImpl>& render_view_impl, + bool enable_smooth_scrolling, + bool enable_wheel_gestures); void ObserveWheelEventAndResultOnMainThread( int routing_id, const blink::WebMouseWheelEvent& wheel_event, const cc::InputHandlerScrollResult& scroll_result); + void NonBlockingInputEventHandledOnMainThread(int routing_id, + blink::WebInputEvent::Type); + // Callback only from the compositor's thread. void RemoveInputHandler(int routing_id); @@ -85,13 +88,18 @@ class InputHandlerManager { const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner, const base::WeakPtr<cc::InputHandler>& input_handler, const base::WeakPtr<RenderViewImpl>& render_view_impl, - bool enable_smooth_scrolling); + bool enable_smooth_scrolling, + bool enable_wheel_gestures); void ObserveWheelEventAndResultOnCompositorThread( int routing_id, const blink::WebMouseWheelEvent& wheel_event, const cc::InputHandlerScrollResult& scroll_result); + void NonBlockingInputEventHandledOnCompositorThread( + int routing_id, + blink::WebInputEvent::Type); + typedef base::ScopedPtrHashMap<int, // routing_id scoped_ptr<InputHandlerWrapper>> InputHandlerMap; diff --git a/content/renderer/input/input_handler_manager_client.h b/content/renderer/input/input_handler_manager_client.h index cf4fe00..c794bd2 100644 --- a/content/renderer/input/input_handler_manager_client.h +++ b/content/renderer/input/input_handler_manager_client.h @@ -10,6 +10,7 @@ #include "base/macros.h" #include "content/common/content_export.h" #include "content/common/input/input_event_ack_state.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" #include "ui/gfx/geometry/vector2d_f.h" namespace ui { @@ -20,10 +21,6 @@ namespace cc { class InputHandler; } -namespace blink { -class WebInputEvent; -} - namespace ui { class SynchronousInputHandlerProxy; } @@ -54,6 +51,9 @@ class CONTENT_EXPORT InputHandlerManagerClient { virtual void DidOverscroll(int routing_id, const DidOverscrollParams& params) = 0; virtual void DidStopFlinging(int routing_id) = 0; + virtual void NonBlockingInputEventHandled( + int routing_id, + blink::WebInputEvent::Type type) = 0; protected: InputHandlerManagerClient() {} diff --git a/content/renderer/input/input_handler_wrapper.cc b/content/renderer/input/input_handler_wrapper.cc index 6265744..c8280e4 100644 --- a/content/renderer/input/input_handler_wrapper.cc +++ b/content/renderer/input/input_handler_wrapper.cc @@ -20,7 +20,8 @@ InputHandlerWrapper::InputHandlerWrapper( const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner, const base::WeakPtr<cc::InputHandler>& input_handler, const base::WeakPtr<RenderViewImpl>& render_view_impl, - bool enable_smooth_scrolling) + bool enable_smooth_scrolling, + bool enable_wheel_gestures) : input_handler_manager_(input_handler_manager), routing_id_(routing_id), input_handler_proxy_(input_handler.get(), this), @@ -28,6 +29,8 @@ InputHandlerWrapper::InputHandlerWrapper( render_view_impl_(render_view_impl) { DCHECK(input_handler); input_handler_proxy_.set_smooth_scroll_enabled(enable_smooth_scrolling); + input_handler_proxy_.set_use_gesture_events_for_mouse_wheel( + enable_wheel_gestures); } InputHandlerWrapper::~InputHandlerWrapper() { diff --git a/content/renderer/input/input_handler_wrapper.h b/content/renderer/input/input_handler_wrapper.h index bc864d9..d3781315 100644 --- a/content/renderer/input/input_handler_wrapper.h +++ b/content/renderer/input/input_handler_wrapper.h @@ -28,7 +28,8 @@ class InputHandlerWrapper : public ui::InputHandlerProxyClient { const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner, const base::WeakPtr<cc::InputHandler>& input_handler, const base::WeakPtr<RenderViewImpl>& render_view_impl, - bool enable_smooth_scrolling); + bool enable_smooth_scrolling, + bool enable_wheel_gestures); ~InputHandlerWrapper() override; int routing_id() const { return routing_id_; } diff --git a/content/renderer/input/non_blocking_event_queue.cc b/content/renderer/input/non_blocking_event_queue.cc new file mode 100644 index 0000000..4fc5d11 --- /dev/null +++ b/content/renderer/input/non_blocking_event_queue.cc @@ -0,0 +1,60 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/input/non_blocking_event_queue.h" + +namespace content { + +NonBlockingEventQueue::NonBlockingEventQueue( + int routing_id, + NonBlockingEventQueueClient* client) + : routing_id_(routing_id), client_(client) {} + +NonBlockingEventQueue::~NonBlockingEventQueue() {} + +void NonBlockingEventQueue::HandleEvent(const blink::WebInputEvent* event, + const ui::LatencyInfo& latency) { + if (event->type == blink::WebInputEvent::MouseWheel) { + if (wheel_events_.state() == WebInputEventQueueState::ITEM_PENDING) { + wheel_events_.Queue(MouseWheelEventWithLatencyInfo( + *static_cast<const blink::WebMouseWheelEvent*>(event), latency)); + } else { + wheel_events_.set_state(WebInputEventQueueState::ITEM_PENDING); + client_->SendNonBlockingEvent(routing_id_, event, latency); + } + } else if (blink::WebInputEvent::isTouchEventType(event->type)) { + if (touch_events_.state() == WebInputEventQueueState::ITEM_PENDING) { + touch_events_.Queue(TouchEventWithLatencyInfo( + *static_cast<const blink::WebTouchEvent*>(event), latency)); + } else { + touch_events_.set_state(WebInputEventQueueState::ITEM_PENDING); + client_->SendNonBlockingEvent(routing_id_, event, latency); + } + } else { + NOTREACHED() << "Invalid passive event type"; + } +} + +void NonBlockingEventQueue::EventHandled(blink::WebInputEvent::Type type) { + if (type == blink::WebInputEvent::MouseWheel) { + if (!wheel_events_.empty()) { + scoped_ptr<MouseWheelEventWithLatencyInfo> event = wheel_events_.Pop(); + + client_->SendNonBlockingEvent(routing_id_, &event->event, event->latency); + } else { + wheel_events_.set_state(WebInputEventQueueState::ITEM_NOT_PENDING); + } + } else if (blink::WebInputEvent::isTouchEventType(type)) { + if (!touch_events_.empty()) { + scoped_ptr<TouchEventWithLatencyInfo> event = touch_events_.Pop(); + client_->SendNonBlockingEvent(routing_id_, &event->event, event->latency); + } else { + touch_events_.set_state(WebInputEventQueueState::ITEM_NOT_PENDING); + } + } else { + NOTREACHED() << "Invalid passive event type"; + } +} + +} // namespace content diff --git a/content/renderer/input/non_blocking_event_queue.h b/content/renderer/input/non_blocking_event_queue.h new file mode 100644 index 0000000..14b99fd --- /dev/null +++ b/content/renderer/input/non_blocking_event_queue.h @@ -0,0 +1,62 @@ +// Copyright 2016 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. + +#ifndef CONTENT_RENDERER_INPUT_NON_BLOCKING_EVENT_QUEUE_H_ +#define CONTENT_RENDERER_INPUT_NON_BLOCKING_EVENT_QUEUE_H_ + +#include <deque> +#include "content/common/content_export.h" +#include "content/common/input/event_with_latency_info.h" +#include "content/common/input/web_input_event_queue.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" +#include "ui/events/latency_info.h" + +namespace content { + +class CONTENT_EXPORT NonBlockingEventQueueClient { + public: + // Send an |event| that was previously queued (possibly + // coalesced with another event) to the |routing_id|'s + // channel. Implementors must implement this callback. + virtual void SendNonBlockingEvent(int routing_id, + const blink::WebInputEvent* event, + const ui::LatencyInfo& latency) = 0; +}; + +// NonBlockingEventQueue implements a series of queues (one touch +// and one mouse wheel) for events that need to be queued between +// the compositor and main threads. When a non-blocking event is sent +// from the compositor to main it can either be sent directly if no +// outstanding events of that type are in flight; or it needs to +// wait in a queue until the main thread has finished processing +// the in-flight event. This class tracks the state and queues +// for the event types. Methods on this class should only be called +// from the compositor thread. +// +class CONTENT_EXPORT NonBlockingEventQueue { + public: + NonBlockingEventQueue(int routing_id, NonBlockingEventQueueClient* client); + ~NonBlockingEventQueue(); + + // Called once compositor has handled |event| and indicated that it is + // a non-blocking event to be queued to the main thread. + void HandleEvent(const blink::WebInputEvent* event, + const ui::LatencyInfo& latency); + + // Call once main thread has handled outstanding |type| event in flight. + void EventHandled(blink::WebInputEvent::Type type); + + private: + friend class NonBlockingEventQueueTest; + int routing_id_; + NonBlockingEventQueueClient* client_; + WebInputEventQueue<MouseWheelEventWithLatencyInfo> wheel_events_; + WebInputEventQueue<TouchEventWithLatencyInfo> touch_events_; + + DISALLOW_COPY_AND_ASSIGN(NonBlockingEventQueue); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_INPUT_NON_BLOCKING_EVENT_QUEUE_H_ diff --git a/content/renderer/input/non_blocking_event_queue_unittest.cc b/content/renderer/input/non_blocking_event_queue_unittest.cc new file mode 100644 index 0000000..8953d85 --- /dev/null +++ b/content/renderer/input/non_blocking_event_queue_unittest.cc @@ -0,0 +1,120 @@ +// Copyright 2016 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 <new> +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "build/build_config.h" +#include "content/common/input/synthetic_web_input_event_builders.h" +#include "content/renderer/input/non_blocking_event_queue.h" +#include "testing/gtest/include/gtest/gtest.h" + +using blink::WebInputEvent; +using blink::WebMouseEvent; +using blink::WebMouseWheelEvent; +using blink::WebTouchEvent; + +namespace content { +namespace { + +const int kTestRoutingID = 13; +} + +class NonBlockingEventQueueTest : public testing::Test, + public NonBlockingEventQueueClient { + public: + NonBlockingEventQueueTest() : queue_(kTestRoutingID, this) {} + + void SendNonBlockingEvent(int routing_id, + const blink::WebInputEvent* event, + const ui::LatencyInfo& latency) override { + ASSERT_EQ(kTestRoutingID, routing_id); + const unsigned char* eventPtr = + reinterpret_cast<const unsigned char*>(event); + last_event_.assign(eventPtr, eventPtr + event->size); + } + + WebInputEventQueue<MouseWheelEventWithLatencyInfo>& wheel_event_queue() { + return queue_.wheel_events_; + } + + WebInputEventQueue<TouchEventWithLatencyInfo>& touch_event_queue() { + return queue_.touch_events_; + } + + protected: + NonBlockingEventQueue queue_; + std::vector<unsigned char> last_event_; +}; + +TEST_F(NonBlockingEventQueueTest, NonBlockingWheel) { + WebMouseWheelEvent kEvents[4] = { + SyntheticWebMouseWheelEventBuilder::Build(10, 10, 0, 53, 0, false), + SyntheticWebMouseWheelEventBuilder::Build(20, 20, 0, 53, 0, false), + SyntheticWebMouseWheelEventBuilder::Build(30, 30, 0, 53, 1, false), + SyntheticWebMouseWheelEventBuilder::Build(30, 30, 0, 53, 1, false), + }; + + ASSERT_EQ(WebInputEventQueueState::ITEM_NOT_PENDING, + wheel_event_queue().state()); + queue_.HandleEvent(&kEvents[0], ui::LatencyInfo()); + ASSERT_EQ(WebInputEventQueueState::ITEM_PENDING, wheel_event_queue().state()); + queue_.HandleEvent(&kEvents[1], ui::LatencyInfo()); + ASSERT_EQ(kEvents[0].size, last_event_.size()); + ASSERT_TRUE(memcmp(&last_event_[0], &kEvents[0], kEvents[0].size) == 0); + queue_.EventHandled(blink::WebInputEvent::MouseWheel); + ASSERT_EQ(kEvents[1].size, last_event_.size()); + ASSERT_TRUE(memcmp(&last_event_[0], &kEvents[1], kEvents[1].size) == 0); + ASSERT_EQ(WebInputEventQueueState::ITEM_PENDING, wheel_event_queue().state()); + queue_.EventHandled(blink::WebInputEvent::MouseWheel); + ASSERT_EQ(WebInputEventQueueState::ITEM_NOT_PENDING, + wheel_event_queue().state()); + + // Ensure that coalescing takes place. + queue_.HandleEvent(&kEvents[0], ui::LatencyInfo()); + queue_.HandleEvent(&kEvents[2], ui::LatencyInfo()); + queue_.HandleEvent(&kEvents[3], ui::LatencyInfo()); + ASSERT_EQ(1u, wheel_event_queue().size()); + ASSERT_EQ(WebInputEventQueueState::ITEM_PENDING, wheel_event_queue().state()); +} + +TEST_F(NonBlockingEventQueueTest, NonBlockingTouch) { + SyntheticWebTouchEvent kEvents[4]; + kEvents[0].PressPoint(10, 10); + kEvents[1].PressPoint(10, 10); + kEvents[1].modifiers = 1; + kEvents[1].MovePoint(0, 20, 20); + kEvents[2].PressPoint(10, 10); + kEvents[2].MovePoint(0, 30, 30); + kEvents[3].PressPoint(10, 10); + kEvents[3].MovePoint(0, 35, 35); + + ASSERT_EQ(WebInputEventQueueState::ITEM_NOT_PENDING, + touch_event_queue().state()); + queue_.HandleEvent(&kEvents[0], ui::LatencyInfo()); + ASSERT_EQ(WebInputEventQueueState::ITEM_PENDING, touch_event_queue().state()); + queue_.HandleEvent(&kEvents[1], ui::LatencyInfo()); + ASSERT_EQ(kEvents[0].size, last_event_.size()); + ASSERT_TRUE(memcmp(&last_event_[0], &kEvents[0], kEvents[0].size) == 0); + queue_.EventHandled(blink::WebInputEvent::TouchStart); + ASSERT_EQ(kEvents[1].size, last_event_.size()); + ASSERT_TRUE(memcmp(&last_event_[0], &kEvents[1], kEvents[1].size) == 0); + ASSERT_EQ(WebInputEventQueueState::ITEM_PENDING, touch_event_queue().state()); + queue_.EventHandled(blink::WebInputEvent::TouchMove); + ASSERT_EQ(WebInputEventQueueState::ITEM_NOT_PENDING, + touch_event_queue().state()); + + // Ensure that coalescing takes place. + queue_.HandleEvent(&kEvents[0], ui::LatencyInfo()); + queue_.HandleEvent(&kEvents[2], ui::LatencyInfo()); + queue_.HandleEvent(&kEvents[3], ui::LatencyInfo()); + ASSERT_EQ(1u, touch_event_queue().size()); + ASSERT_EQ(WebInputEventQueueState::ITEM_PENDING, touch_event_queue().state()); +} + +} // namespace content diff --git a/content/renderer/input/render_widget_input_handler.cc b/content/renderer/input/render_widget_input_handler.cc index 54f2b9c..385d388 100644 --- a/content/renderer/input/render_widget_input_handler.cc +++ b/content/renderer/input/render_widget_input_handler.cc @@ -131,7 +131,7 @@ void LogPassiveLatency(int64_t latency) { } void LogPassiveEventListenersUma(WebInputEventResult result, - bool passive, + bool non_blocking, bool cancelable, double event_timestamp, const ui::LatencyInfo& latency_info) { @@ -145,7 +145,7 @@ void LogPassiveEventListenersUma(WebInputEventResult result, }; int enum_value; - if (passive) + if (non_blocking) enum_value = PASSIVE_LISTENER_UMA_ENUM_PASSIVE; else if (!cancelable) enum_value = PASSIVE_LISTENER_UMA_ENUM_UNCANCELABLE; @@ -191,9 +191,8 @@ RenderWidgetInputHandler::~RenderWidgetInputHandler() {} void RenderWidgetInputHandler::HandleInputEvent( const WebInputEvent& input_event, - const ui::LatencyInfo& latency_info) { - // TODO(dtapuska): Passive support not implemented yet crbug.com/489802 - bool passive = false; + const ui::LatencyInfo& latency_info, + InputEventDispatchType dispatch_type) { base::AutoReset<bool> handling_input_event_resetter(&handling_input_event_, true); base::AutoReset<WebInputEvent::Type> handling_event_type_resetter( @@ -314,6 +313,8 @@ void RenderWidgetInputHandler::HandleInputEvent( processed = widget_->webwidget()->handleInputEvent(input_event); } + bool non_blocking = + dispatch_type == InputEventDispatchType::DISPATCH_TYPE_NON_BLOCKING; // TODO(dtapuska): Use the input_event.timeStampSeconds as the start // ideally this should be when the event was sent by the compositor to the // renderer. crbug.com/565348 @@ -321,11 +322,11 @@ void RenderWidgetInputHandler::HandleInputEvent( input_event.type == WebInputEvent::TouchMove || input_event.type == WebInputEvent::TouchEnd) { LogPassiveEventListenersUma( - processed, passive, + processed, non_blocking, static_cast<const WebTouchEvent&>(input_event).cancelable, input_event.timeStampSeconds, latency_info); } else if (input_event.type == WebInputEvent::MouseWheel) { - LogPassiveEventListenersUma(processed, passive, !passive, + LogPassiveEventListenersUma(processed, non_blocking, !non_blocking, input_event.timeStampSeconds, latency_info); } @@ -362,11 +363,15 @@ void RenderWidgetInputHandler::HandleInputEvent( // Send mouse wheel events and their disposition to the compositor thread, so // that they can be used to produce the elastic overscroll effect on Mac. if (input_event.type == WebInputEvent::MouseWheel) { - delegate_->ObserveWheelEventAndResult( - static_cast<const WebMouseWheelEvent&>(input_event), - event_overscroll ? event_overscroll->latest_overscroll_delta - : gfx::Vector2dF(), - processed != WebInputEventResult::NotHandled); + const WebMouseWheelEvent& wheel_event = + static_cast<const WebMouseWheelEvent&>(input_event); + if (wheel_event.canScroll) { + delegate_->ObserveWheelEventAndResult( + wheel_event, + event_overscroll ? event_overscroll->latest_overscroll_delta + : gfx::Vector2dF(), + processed != WebInputEventResult::NotHandled); + } } bool frame_pending = @@ -390,7 +395,12 @@ void RenderWidgetInputHandler::HandleInputEvent( // by reentrant calls for events after the paused one. bool no_ack = ignore_ack_for_mouse_move_from_debugger_ && input_event.type == WebInputEvent::MouseMove; - if (WebInputEventTraits::WillReceiveAckFromRenderer(input_event) && !no_ack) { + if (non_blocking) { + // |non_blocking| means it was ack'd already by the InputHandlerProxy + // so let the delegate know the event has been handled. + delegate_->NonBlockingInputEventHandled(input_event.type); + } else if (WebInputEventTraits::WillReceiveAckFromRenderer(input_event) && + !no_ack) { scoped_ptr<InputEventAck> response(new InputEventAck( input_event.type, ack_result, swap_latency_info, std::move(event_overscroll), diff --git a/content/renderer/input/render_widget_input_handler.h b/content/renderer/input/render_widget_input_handler.h index f8ff1d3..3be968c 100644 --- a/content/renderer/input/render_widget_input_handler.h +++ b/content/renderer/input/render_widget_input_handler.h @@ -10,6 +10,7 @@ #include "base/time/time.h" #include "content/common/input/did_overscroll_params.h" #include "content/common/input/input_event_ack.h" +#include "content/common/input/input_event_dispatch_type.h" #include "third_party/WebKit/public/web/WebInputEvent.h" #include "ui/base/ui_base_types.h" @@ -37,7 +38,8 @@ class CONTENT_EXPORT RenderWidgetInputHandler { // Handle input events from the input event provider. void HandleInputEvent(const blink::WebInputEvent& input_event, - const ui::LatencyInfo& latency_info); + const ui::LatencyInfo& latency_info, + InputEventDispatchType dispatch_type); // Handle overscroll from Blink. void DidOverscrollFromBlink( diff --git a/content/renderer/input/render_widget_input_handler_delegate.h b/content/renderer/input/render_widget_input_handler_delegate.h index d00a080..7a7f544 100644 --- a/content/renderer/input/render_widget_input_handler_delegate.h +++ b/content/renderer/input/render_widget_input_handler_delegate.h @@ -58,6 +58,11 @@ class CONTENT_EXPORT RenderWidgetInputHandlerDelegate { // Called when an ACK is ready to be sent to the input event provider. virtual void OnInputEventAck(scoped_ptr<InputEventAck> input_event_ack) = 0; + // Called when a non-blocking event (DISPATCH_TYPE_NON_BLOCKING) of + // |handled_type| has been processed by the main thread. + virtual void NonBlockingInputEventHandled( + blink::WebInputEvent::Type handled_type) = 0; + // Notifies the delegate of the |input_handler| managing it. virtual void SetInputHandler(RenderWidgetInputHandler* input_handler) = 0; diff --git a/content/renderer/mus/render_widget_mus_connection.cc b/content/renderer/mus/render_widget_mus_connection.cc index c7dbac8..3ac0808 100644 --- a/content/renderer/mus/render_widget_mus_connection.cc +++ b/content/renderer/mus/render_widget_mus_connection.cc @@ -123,6 +123,11 @@ void RenderWidgetMusConnection::OnInputEventAck( pending_ack_.Reset(); } +void RenderWidgetMusConnection::NonBlockingInputEventHandled( + blink::WebInputEvent::Type handled_type) { + NOTIMPLEMENTED(); +} + void RenderWidgetMusConnection::SetInputHandler( RenderWidgetInputHandler* input_handler) { DCHECK(!input_handler_); @@ -171,7 +176,8 @@ void RenderWidgetMusConnection::OnWindowInputEvent( pending_ack_ = ack; // TODO(fsamuel, sadrul): Track real latency info. ui::LatencyInfo latency_info; - input_handler_->HandleInputEvent(*input_event, latency_info); + input_handler_->HandleInputEvent(*input_event, latency_info, + DISPATCH_TYPE_NORMAL); } } // namespace content diff --git a/content/renderer/mus/render_widget_mus_connection.h b/content/renderer/mus/render_widget_mus_connection.h index c6bb362..26f1b4b 100644 --- a/content/renderer/mus/render_widget_mus_connection.h +++ b/content/renderer/mus/render_widget_mus_connection.h @@ -46,6 +46,8 @@ class RenderWidgetMusConnection : public RenderWidgetInputHandlerDelegate { void OnDidHandleKeyEvent() override; void OnDidOverscroll(const DidOverscrollParams& params) override; void OnInputEventAck(scoped_ptr<InputEventAck> input_event_ack) override; + void NonBlockingInputEventHandled( + blink::WebInputEvent::Type handled_type) override; void SetInputHandler(RenderWidgetInputHandler* input_handler) override; void UpdateTextInputState(ShowIme show_ime, ChangeSource change_source) override; diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc index e2e7603..fb533e0 100644 --- a/content/renderer/render_view_impl.cc +++ b/content/renderer/render_view_impl.cc @@ -48,6 +48,7 @@ #include "content/common/frame_messages.h" #include "content/common/frame_replication_state.h" #include "content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.h" +#include "content/common/input/input_event_utils.h" #include "content/common/input_messages.h" #include "content/common/pepper_messages.h" #include "content/common/site_isolation_policy.h" @@ -2161,7 +2162,8 @@ void RenderViewImpl::initializeLayerTreeView() { if (input_handler_manager) { input_handler_manager->AddInputHandler( routing_id(), rwc->GetInputHandler(), AsWeakPtr(), - webkit_preferences_.enable_scroll_animator); + webkit_preferences_.enable_scroll_animator, + UseGestureBasedWheelScrolling()); } } } diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc index 85cb9f3..9709d2e 100644 --- a/content/renderer/render_widget.cc +++ b/content/renderer/render_widget.cc @@ -656,10 +656,11 @@ GURL RenderWidget::GetURLForGraphicsContext3D() { } void RenderWidget::OnHandleInputEvent(const blink::WebInputEvent* input_event, - const ui::LatencyInfo& latency_info) { + const ui::LatencyInfo& latency_info, + InputEventDispatchType dispatch_type) { if (!input_event) return; - input_handler_->HandleInputEvent(*input_event, latency_info); + input_handler_->HandleInputEvent(*input_event, latency_info, dispatch_type); } void RenderWidget::OnCursorVisibilityChange(bool is_visible) { @@ -959,6 +960,17 @@ void RenderWidget::OnInputEventAck(scoped_ptr<InputEventAck> input_event_ack) { Send(new InputHostMsg_HandleInputEvent_ACK(routing_id_, *input_event_ack)); } +void RenderWidget::NonBlockingInputEventHandled( + blink::WebInputEvent::Type handled_type) { + RenderThreadImpl* render_thread = RenderThreadImpl::current(); + InputHandlerManager* input_handler_manager = + render_thread ? render_thread->input_handler_manager() : NULL; + if (input_handler_manager) { + input_handler_manager->NonBlockingInputEventHandledOnMainThread( + routing_id_, handled_type); + } +} + void RenderWidget::SetInputHandler(RenderWidgetInputHandler* input_handler) { // Nothing to do here. RenderWidget created the |input_handler| and will take // ownership of it. We just verify here that we don't already have an input diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h index 1185fa2b..a7bf289 100644 --- a/content/renderer/render_widget.h +++ b/content/renderer/render_widget.h @@ -207,6 +207,8 @@ class CONTENT_EXPORT RenderWidget void OnDidHandleKeyEvent() override; void OnDidOverscroll(const DidOverscrollParams& params) override; void OnInputEventAck(scoped_ptr<InputEventAck> input_event_ack) override; + void NonBlockingInputEventHandled( + blink::WebInputEvent::Type handled_type) override; void SetInputHandler(RenderWidgetInputHandler* input_handler) override; void UpdateTextInputState(ShowIme show_ime, ChangeSource change_source) override; @@ -412,7 +414,8 @@ class CONTENT_EXPORT RenderWidget // RenderWidget IPC message handlers void OnHandleInputEvent(const blink::WebInputEvent* event, - const ui::LatencyInfo& latency_info); + const ui::LatencyInfo& latency_info, + InputEventDispatchType dispatch_type); void OnCursorVisibilityChange(bool is_visible); void OnMouseCaptureLost(); virtual void OnSetFocus(bool enable); diff --git a/content/renderer/render_widget_unittest.cc b/content/renderer/render_widget_unittest.cc index cdd195a..afd9cd8 100644 --- a/content/renderer/render_widget_unittest.cc +++ b/content/renderer/render_widget_unittest.cc @@ -38,7 +38,8 @@ class InteractiveRenderWidget : public RenderWidget { } void SendInputEvent(const blink::WebInputEvent& event) { - OnHandleInputEvent(&event, ui::LatencyInfo()); + OnHandleInputEvent(&event, ui::LatencyInfo(), + InputEventDispatchType::DISPATCH_TYPE_NORMAL); } void set_always_overscroll(bool overscroll) { diff --git a/ui/events/blink/input_handler_proxy.cc b/ui/events/blink/input_handler_proxy.cc index 17de4c6..23bc94d 100644 --- a/ui/events/blink/input_handler_proxy.cc +++ b/ui/events/blink/input_handler_proxy.cc @@ -35,6 +35,8 @@ using blink::WebTouchPoint; namespace { +const int32_t kEventDispositionUndefined = -1; + // Maximum time between a fling event's timestamp and the first |Animate| call // for the fling curve to use the fling timestamp as the initial animation time. // Two frames allows a minor delay between event creation and the first animate. @@ -227,7 +229,9 @@ InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler, disallow_vertical_fling_scroll_(false), has_fling_animation_started_(false), smooth_scroll_enabled_(false), - uma_latency_reporting_enabled_(base::TimeTicks::IsHighResolution()) { + uma_latency_reporting_enabled_(base::TimeTicks::IsHighResolution()), + use_gesture_events_for_mouse_wheel_(true), + touch_start_result_(kEventDispositionUndefined) { DCHECK(client); input_handler_->BindToClient(this); cc::ScrollElasticityHelper* scroll_elasticity_helper = @@ -343,6 +347,12 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleInputEvent( case WebInputEvent::TouchStart: return HandleTouchStart(static_cast<const WebTouchEvent&>(event)); + case WebInputEvent::TouchMove: + return HandleTouchMove(static_cast<const WebTouchEvent&>(event)); + + case WebInputEvent::TouchEnd: + return HandleTouchEnd(static_cast<const WebTouchEvent&>(event)); + case WebInputEvent::MouseMove: { const WebMouseEvent& mouse_event = static_cast<const WebMouseEvent&>(event); @@ -426,6 +436,28 @@ bool InputHandlerProxy::ShouldAnimate( InputHandlerProxy::EventDisposition InputHandlerProxy::HandleMouseWheel( const WebMouseWheelEvent& wheel_event) { + if (use_gesture_events_for_mouse_wheel_) { + cc::EventListenerProperties properties = + input_handler_->GetEventListenerProperties( + cc::EventListenerClass::kMouseWheel); + switch (properties) { + case cc::EventListenerProperties::kPassive: + return DID_HANDLE_NON_BLOCKING; + case cc::EventListenerProperties::kBlockingAndPassive: + case cc::EventListenerProperties::kBlocking: + return DID_NOT_HANDLE; + case cc::EventListenerProperties::kNone: + return DROP_EVENT; + default: + NOTREACHED(); + return DROP_EVENT; + } + } + return ScrollByMouseWheel(wheel_event); +} + +InputHandlerProxy::EventDisposition InputHandlerProxy::ScrollByMouseWheel( + const WebMouseWheelEvent& wheel_event) { InputHandlerProxy::EventDisposition result = DID_NOT_HANDLE; cc::InputHandlerScrollResult scroll_result; @@ -734,18 +766,67 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureFlingStart( InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchStart( const blink::WebTouchEvent& touch_event) { + EventDisposition result = DROP_EVENT; for (size_t i = 0; i < touch_event.touchesLength; ++i) { if (touch_event.touches[i].state != WebTouchPoint::StatePressed) continue; if (input_handler_->DoTouchEventsBlockScrollAt( gfx::Point(touch_event.touches[i].position.x, touch_event.touches[i].position.y))) { - // TODO(rbyers): We should consider still sending the touch events to - // main asynchronously (crbug.com/455539). - return DID_NOT_HANDLE; + result = DID_NOT_HANDLE; + break; + } + } + + // If |result| is DROP_EVENT it wasn't processed above. + if (result == DROP_EVENT) { + switch (input_handler_->GetEventListenerProperties( + cc::EventListenerClass::kTouch)) { + case cc::EventListenerProperties::kPassive: + result = DID_HANDLE_NON_BLOCKING; + break; + case cc::EventListenerProperties::kBlocking: + // The touch area rects above already have checked whether it hits + // a blocking region. Since it does not the event can be dropped. + result = DROP_EVENT; + break; + case cc::EventListenerProperties::kBlockingAndPassive: + // There is at least one passive listener that needs to possibly + // be notified so it can't be dropped. + result = DID_HANDLE_NON_BLOCKING; + break; + case cc::EventListenerProperties::kNone: + result = DROP_EVENT; + break; + default: + NOTREACHED(); + result = DROP_EVENT; + break; } } - return DROP_EVENT; + + // Merge |touch_start_result_| and |result| so the result has the highest + // priority value according to the sequence; (DROP_EVENT, + // DID_HANDLE_NON_BLOCKING, DID_NOT_HANDLE). + if (touch_start_result_ == kEventDispositionUndefined || + touch_start_result_ == DROP_EVENT || result == DID_NOT_HANDLE) + touch_start_result_ = result; + + return result; +} + +InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchMove( + const blink::WebTouchEvent& touch_event) { + if (touch_start_result_ != kEventDispositionUndefined) + return static_cast<EventDisposition>(touch_start_result_); + return DID_NOT_HANDLE; +} + +InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchEnd( + const blink::WebTouchEvent& touch_event) { + if (touch_event.touchesLength == 1) + touch_start_result_ = kEventDispositionUndefined; + return DID_NOT_HANDLE; } bool InputHandlerProxy::FilterInputEventForFlingBoosting( @@ -1089,25 +1170,47 @@ void InputHandlerProxy::RequestAnimation() { bool InputHandlerProxy::TouchpadFlingScroll( const WebFloatSize& increment) { - WebMouseWheelEvent synthetic_wheel; - synthetic_wheel.type = WebInputEvent::MouseWheel; - synthetic_wheel.timeStampSeconds = InSecondsF(base::TimeTicks::Now()); - synthetic_wheel.deltaX = increment.width; - synthetic_wheel.deltaY = increment.height; - synthetic_wheel.hasPreciseScrollingDeltas = true; - synthetic_wheel.x = fling_parameters_.point.x; - synthetic_wheel.y = fling_parameters_.point.y; - synthetic_wheel.globalX = fling_parameters_.globalPoint.x; - synthetic_wheel.globalY = fling_parameters_.globalPoint.y; - synthetic_wheel.modifiers = fling_parameters_.modifiers; - - InputHandlerProxy::EventDisposition disposition = - HandleInputEvent(synthetic_wheel); + InputHandlerProxy::EventDisposition disposition; + cc::EventListenerProperties properties = + input_handler_->GetEventListenerProperties( + cc::EventListenerClass::kMouseWheel); + switch (properties) { + case cc::EventListenerProperties::kPassive: + disposition = DID_HANDLE_NON_BLOCKING; + break; + case cc::EventListenerProperties::kBlocking: + disposition = DID_NOT_HANDLE; + break; + case cc::EventListenerProperties::kNone: { + WebMouseWheelEvent synthetic_wheel; + synthetic_wheel.type = WebInputEvent::MouseWheel; + synthetic_wheel.timeStampSeconds = InSecondsF(base::TimeTicks::Now()); + synthetic_wheel.deltaX = increment.width; + synthetic_wheel.deltaY = increment.height; + synthetic_wheel.hasPreciseScrollingDeltas = true; + synthetic_wheel.x = fling_parameters_.point.x; + synthetic_wheel.y = fling_parameters_.point.y; + synthetic_wheel.globalX = fling_parameters_.globalPoint.x; + synthetic_wheel.globalY = fling_parameters_.globalPoint.y; + synthetic_wheel.modifiers = fling_parameters_.modifiers; + + disposition = ScrollByMouseWheel(synthetic_wheel); + break; + } + default: + NOTREACHED(); + return false; + } + switch (disposition) { case DID_HANDLE: return true; case DROP_EVENT: break; + case DID_HANDLE_NON_BLOCKING: + // TODO(dtapuska): Process the fling on the compositor thread + // but post the events to the main thread; for now just pass it to the + // main thread. case DID_NOT_HANDLE: TRACE_EVENT_INSTANT0("input", "InputHandlerProxy::scrollBy::AbortFling", diff --git a/ui/events/blink/input_handler_proxy.h b/ui/events/blink/input_handler_proxy.h index e96b31b..32c0efc 100644 --- a/ui/events/blink/input_handler_proxy.h +++ b/ui/events/blink/input_handler_proxy.h @@ -46,10 +46,14 @@ class InputHandlerProxy } void set_smooth_scroll_enabled(bool value) { smooth_scroll_enabled_ = value; } + void set_use_gesture_events_for_mouse_wheel(bool value) { + use_gesture_events_for_mouse_wheel_ = value; + } enum EventDisposition { DID_HANDLE, DID_NOT_HANDLE, + DID_HANDLE_NON_BLOCKING, DROP_EVENT }; EventDisposition HandleInputEventWithLatencyInfo( @@ -91,6 +95,7 @@ class InputHandlerProxy // Helper functions for handling more complicated input events. EventDisposition HandleMouseWheel( const blink::WebMouseWheelEvent& event); + EventDisposition ScrollByMouseWheel(const blink::WebMouseWheelEvent& event); EventDisposition HandleGestureScrollBegin( const blink::WebGestureEvent& event); EventDisposition HandleGestureScrollUpdate( @@ -99,8 +104,9 @@ class InputHandlerProxy const blink::WebGestureEvent& event); EventDisposition HandleGestureFlingStart( const blink::WebGestureEvent& event); - EventDisposition HandleTouchStart( - const blink::WebTouchEvent& event); + EventDisposition HandleTouchStart(const blink::WebTouchEvent& event); + EventDisposition HandleTouchMove(const blink::WebTouchEvent& event); + EventDisposition HandleTouchEnd(const blink::WebTouchEvent& event); // Returns true if the event should be suppressed due to to an active, // boost-enabled fling, in which case further processing should cease. @@ -184,8 +190,13 @@ class InputHandlerProxy scoped_ptr<InputScrollElasticityController> scroll_elasticity_controller_; bool smooth_scroll_enabled_; - bool uma_latency_reporting_enabled_; + bool use_gesture_events_for_mouse_wheel_; + + // The merged result of the last touch start with previous touch starts. + // This value will get returned for subsequent TouchMove events to allow + // passive events not to block scrolling. + int32_t touch_start_result_; base::TimeTicks last_fling_animate_time_; diff --git a/ui/events/blink/input_handler_proxy_unittest.cc b/ui/events/blink/input_handler_proxy_unittest.cc index 8106b12..a5cee97 100644 --- a/ui/events/blink/input_handler_proxy_unittest.cc +++ b/ui/events/blink/input_handler_proxy_unittest.cc @@ -369,6 +369,10 @@ class InputHandlerProxyTest input_handler_->smooth_scroll_enabled_ = value; } + void SetMouseWheelGesturesOn(bool value) { + input_handler_->set_use_gesture_events_for_mouse_wheel(value); + } + protected: const bool synchronous_root_scroll_; const bool install_synchronous_handler_; @@ -385,6 +389,7 @@ class InputHandlerProxyTest TEST_P(InputHandlerProxyTest, MouseWheelByPageMainThread) { expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + SetMouseWheelGesturesOn(false); WebMouseWheelEvent wheel; wheel.type = WebInputEvent::MouseWheel; wheel.scrollByPage = true; @@ -395,6 +400,7 @@ TEST_P(InputHandlerProxyTest, MouseWheelByPageMainThread) { TEST_P(InputHandlerProxyTest, MouseWheelWithCtrlNotScroll) { expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + SetMouseWheelGesturesOn(false); WebMouseWheelEvent wheel; wheel.type = WebInputEvent::MouseWheel; wheel.modifiers = WebInputEvent::ControlKey; @@ -403,6 +409,45 @@ TEST_P(InputHandlerProxyTest, MouseWheelWithCtrlNotScroll) { VERIFY_AND_RESET_MOCKS(); } +TEST_P(InputHandlerProxyTest, MouseWheelNoListener) { + expected_disposition_ = InputHandlerProxy::DROP_EVENT; + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kNone)); + + WebMouseWheelEvent wheel; + wheel.type = WebInputEvent::MouseWheel; + wheel.modifiers = WebInputEvent::ControlKey; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(wheel)); + VERIFY_AND_RESET_MOCKS(); +} + +TEST_P(InputHandlerProxyTest, MouseWheelPassiveListener) { + expected_disposition_ = InputHandlerProxy::DID_HANDLE_NON_BLOCKING; + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kPassive)); + + WebMouseWheelEvent wheel; + wheel.type = WebInputEvent::MouseWheel; + wheel.modifiers = WebInputEvent::ControlKey; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(wheel)); + VERIFY_AND_RESET_MOCKS(); +} + +TEST_P(InputHandlerProxyTest, MouseWheelBlockingListener) { + expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kBlocking)); + + WebMouseWheelEvent wheel; + wheel.type = WebInputEvent::MouseWheel; + wheel.modifiers = WebInputEvent::ControlKey; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(wheel)); + VERIFY_AND_RESET_MOCKS(); +} + // Mac does not smooth scroll wheel events (crbug.com/574283). #if !defined(OS_MACOSX) TEST_P(InputHandlerProxyTest, MouseWheelWithPreciseScrollingDeltas) { @@ -410,6 +455,7 @@ TEST_P(InputHandlerProxyTest, MouseWheelWithPreciseScrollingDeltas) { TEST_P(InputHandlerProxyTest, DISABLED_MouseWheelWithPreciseScrollingDeltas) { #endif SetSmoothScrollEnabled(true); + SetMouseWheelGesturesOn(false); expected_disposition_ = InputHandlerProxy::DID_HANDLE; WebMouseWheelEvent wheel; wheel.type = WebInputEvent::MouseWheel; @@ -443,6 +489,7 @@ TEST_P(InputHandlerProxyTest, MouseWheelScrollIgnored) { TEST_P(InputHandlerProxyTest, DISABLED_MouseWheelScrollIgnored) { #endif SetSmoothScrollEnabled(true); + SetMouseWheelGesturesOn(false); expected_disposition_ = InputHandlerProxy::DROP_EVENT; WebMouseWheelEvent wheel; wheel.type = WebInputEvent::MouseWheel; @@ -877,6 +924,7 @@ TEST_P(InputHandlerProxyTest, GestureFlingAnimatesTouchpad) { EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); VERIFY_AND_RESET_MOCKS(); + // The first animate call should let us pick up an animation start time, but // we shouldn't actually move anywhere just yet. The first frame after the // fling start will typically include the last scroll from the gesture that @@ -892,6 +940,9 @@ TEST_P(InputHandlerProxyTest, GestureFlingAnimatesTouchpad) { // The second call should start scrolling in the -X direction. EXPECT_SET_NEEDS_ANIMATE_INPUT(1); + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kNone)); EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) .WillOnce(testing::Return(kImplThreadScrollState)); EXPECT_CALL( @@ -909,6 +960,9 @@ TEST_P(InputHandlerProxyTest, GestureFlingAnimatesTouchpad) { // We also should pass the current fling parameters out to the client so the // rest of the fling can be // transferred to the main thread. + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kNone)); EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) .WillOnce(testing::Return(kMainThreadScrollState)); EXPECT_CALL(mock_input_handler_, ScrollBy(testing::_)).Times(0); @@ -959,6 +1013,97 @@ TEST_P(InputHandlerProxyTest, GestureFlingAnimatesTouchpad) { VERIFY_AND_RESET_MOCKS(); } +TEST_P(InputHandlerProxyTest, GestureFlingPassiveListener) { + // We shouldn't send any events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + // On the fling start, we should schedule an animation but not actually start + // scrolling. + gesture_.type = WebInputEvent::GestureFlingStart; + WebFloatPoint fling_delta = WebFloatPoint(1000, 0); + WebPoint fling_point = WebPoint(7, 13); + WebPoint fling_global_point = WebPoint(17, 23); + // Note that for trackpad, wheel events with the Control modifier are + // special (reserved for zoom), so don't set that here. + int modifiers = WebInputEvent::ShiftKey | WebInputEvent::AltKey; + gesture_ = CreateFling(blink::WebGestureDeviceTouchpad, fling_delta, + fling_point, fling_global_point, modifiers); + EXPECT_SET_NEEDS_ANIMATE_INPUT(1); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(kImplThreadScrollState)); + EXPECT_CALL(mock_input_handler_, ScrollEnd(testing::_)); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + // The first animate call should let us pick up an animation start time, but + // we shouldn't actually move anywhere just yet. The first frame after the + // fling start will typically include the last scroll from the gesture that + // lead to the scroll (either wheel or gesture scroll), so there should be no + // visible hitch. + EXPECT_SET_NEEDS_ANIMATE_INPUT(1); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .Times(0); + base::TimeTicks time = base::TimeTicks() + base::TimeDelta::FromSeconds(10); + Animate(time); + + VERIFY_AND_RESET_MOCKS(); + + // The second call should punt the fling to the main thread + // because of a passive event listener. + EXPECT_SET_NEEDS_ANIMATE_INPUT(0); + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kPassive)); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .Times(0); + EXPECT_CALL(mock_input_handler_, ScrollBy(testing::_)).Times(0); + EXPECT_CALL(mock_input_handler_, ScrollEnd(testing::_)).Times(0); + // Expected wheel fling animation parameters: + // *) fling_delta and fling_point should match the original GestureFlingStart + // event + // *) startTime should be 10 to match the time parameter of the first + // Animate() call after the GestureFlingStart + EXPECT_CALL( + mock_client_, + TransferActiveWheelFlingAnimation(testing::AllOf( + testing::Field(&WebActiveWheelFlingParameters::delta, + testing::Eq(fling_delta)), + testing::Field(&WebActiveWheelFlingParameters::point, + testing::Eq(fling_point)), + testing::Field(&WebActiveWheelFlingParameters::globalPoint, + testing::Eq(fling_global_point)), + testing::Field(&WebActiveWheelFlingParameters::modifiers, + testing::Eq(modifiers)), + testing::Field(&WebActiveWheelFlingParameters::startTime, + testing::Eq(10)), + testing::Field(&WebActiveWheelFlingParameters::cumulativeScroll, + testing::_)))); + time += base::TimeDelta::FromMilliseconds(100); + Animate(time); + + VERIFY_AND_RESET_MOCKS(); + + // Since we've aborted the fling, the next animation should be a no-op and + // should not result in another + // frame being requested. + EXPECT_SET_NEEDS_ANIMATE_INPUT(0); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .Times(0); + time += base::TimeDelta::FromMilliseconds(100); + Animate(time); + + // Since we've transferred the fling to the main thread, we need to pass the + // next GestureFlingCancel to the main + // thread as well. + expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + gesture_.type = WebInputEvent::GestureFlingCancel; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); +} + TEST_P(InputHandlerProxyTest, GestureFlingTransferResetsTouchpad) { // We shouldn't send any events to the widget for this gesture. expected_disposition_ = InputHandlerProxy::DID_HANDLE; @@ -981,7 +1126,6 @@ TEST_P(InputHandlerProxyTest, GestureFlingTransferResetsTouchpad) { .WillOnce(testing::Return(kImplThreadScrollState)); EXPECT_CALL(mock_input_handler_, ScrollEnd(testing::_)); EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); - VERIFY_AND_RESET_MOCKS(); // Start the fling animation at time 10. This shouldn't actually scroll, just @@ -996,6 +1140,9 @@ TEST_P(InputHandlerProxyTest, GestureFlingTransferResetsTouchpad) { // The second call should start scrolling in the -X direction. EXPECT_SET_NEEDS_ANIMATE_INPUT(1); + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kNone)); EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) .WillOnce(testing::Return(kImplThreadScrollState)); EXPECT_CALL( @@ -1013,6 +1160,9 @@ TEST_P(InputHandlerProxyTest, GestureFlingTransferResetsTouchpad) { // We also should pass the current fling parameters out to the client so the // rest of the fling can be // transferred to the main thread. + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kNone)); EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) .WillOnce(testing::Return(kMainThreadScrollState)); @@ -1097,6 +1247,9 @@ TEST_P(InputHandlerProxyTest, GestureFlingTransferResetsTouchpad) { // Tick the second fling once normally. EXPECT_SET_NEEDS_ANIMATE_INPUT(1); + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kNone)); EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) .WillOnce(testing::Return(kImplThreadScrollState)); EXPECT_CALL( @@ -1110,6 +1263,9 @@ TEST_P(InputHandlerProxyTest, GestureFlingTransferResetsTouchpad) { VERIFY_AND_RESET_MOCKS(); // Then abort the second fling. + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kNone)); EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) .WillOnce(testing::Return(kMainThreadScrollState)); EXPECT_CALL(mock_input_handler_, ScrollBy(testing::_)).Times(0); @@ -1569,6 +1725,9 @@ TEST_P(InputHandlerProxyTest, GestureFlingStopsAtContentEdge) { VERIFY_AND_RESET_MOCKS(); // The second animate starts scrolling in the positive X and Y directions. + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kNone)); EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) .WillOnce(testing::Return(kImplThreadScrollState)); EXPECT_CALL( @@ -1588,6 +1747,9 @@ TEST_P(InputHandlerProxyTest, GestureFlingStopsAtContentEdge) { overscroll.did_overscroll_root = true; overscroll.accumulated_root_overscroll = gfx::Vector2dF(0, 100); overscroll.unused_scroll_delta = gfx::Vector2dF(0, 10); + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kNone)); EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) .WillOnce(testing::Return(kImplThreadScrollState)); EXPECT_CALL( @@ -1609,6 +1771,9 @@ TEST_P(InputHandlerProxyTest, GestureFlingStopsAtContentEdge) { // The next call to animate will no longer scroll vertically. EXPECT_SET_NEEDS_ANIMATE_INPUT(1); + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kMouseWheel)) + .WillOnce(testing::Return(cc::EventListenerProperties::kNone)); EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) .WillOnce(testing::Return(kImplThreadScrollState)); EXPECT_CALL( @@ -1807,8 +1972,9 @@ TEST_P(InputHandlerProxyTest, MultiTouchPointHitTestNegative) { VERIFY_AND_RESET_MOCKS(); EXPECT_CALL(mock_input_handler_, - DoTouchEventsBlockScrollAt( - testing::Property(&gfx::Point::x, testing::Gt(0)))) + GetEventListenerProperties(cc::EventListenerClass::kTouch)) + .WillOnce(testing::Return(cc::EventListenerProperties::kNone)); + EXPECT_CALL(mock_input_handler_, DoTouchEventsBlockScrollAt(testing::_)) .WillOnce(testing::Return(false)); EXPECT_CALL(mock_input_handler_, DoTouchEventsBlockScrollAt( @@ -1856,6 +2022,30 @@ TEST_P(InputHandlerProxyTest, MultiTouchPointHitTestPositive) { VERIFY_AND_RESET_MOCKS(); } +TEST_P(InputHandlerProxyTest, MultiTouchPointHitTestPassivePositive) { + // One of the touch points is on a touch-region. So the event should be sent + // to the main thread. + expected_disposition_ = InputHandlerProxy::DID_HANDLE_NON_BLOCKING; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, + GetEventListenerProperties(cc::EventListenerClass::kTouch)) + .WillRepeatedly(testing::Return(cc::EventListenerProperties::kPassive)); + EXPECT_CALL(mock_input_handler_, DoTouchEventsBlockScrollAt(testing::_)) + .WillRepeatedly(testing::Return(false)); + + WebTouchEvent touch; + touch.type = WebInputEvent::TouchStart; + + touch.touchesLength = 3; + touch.touches[0] = CreateWebTouchPoint(WebTouchPoint::StatePressed, 0, 0); + touch.touches[1] = CreateWebTouchPoint(WebTouchPoint::StatePressed, 10, 10); + touch.touches[2] = CreateWebTouchPoint(WebTouchPoint::StatePressed, -10, 10); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(touch)); + + VERIFY_AND_RESET_MOCKS(); +} + TEST_P(InputHandlerProxyTest, GestureFlingCancelledByKeyboardEvent) { // We shouldn't send any events to the widget for this gesture. expected_disposition_ = InputHandlerProxy::DID_HANDLE; |