diff options
author | dtapuska <dtapuska@chromium.org> | 2016-02-18 09:08:10 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-02-18 17:09:22 +0000 |
commit | 0bd451acef1d65d36b09a60ffef3e8a6515434a6 (patch) | |
tree | 52ef74c53da589db9b5c791c81460fb87997a678 /content | |
parent | 55f9999801dbde105cc8276cf81fe7ffa5b04291 (diff) | |
download | chromium_src-0bd451acef1d65d36b09a60ffef3e8a6515434a6.zip chromium_src-0bd451acef1d65d36b09a60ffef3e8a6515434a6.tar.gz chromium_src-0bd451acef1d65d36b09a60ffef3e8a6515434a6.tar.bz2 |
Plumb firing passive event listeners.
Detect whether passive event listeners exist when handling wheel
and touch events. If the events can be handled on the compositor thread
and an event posted to main thread dispatch that. Add an event queue
for wheel and touch so that we can coalesce pending events for the main
thread.
BUG=489802
Review URL: https://codereview.chromium.org/1631963002
Cr-Commit-Position: refs/heads/master@{#376182}
Diffstat (limited to 'content')
40 files changed, 1044 insertions, 155 deletions
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) { |