diff options
author | Andras Becsi <andras.becsi@digia.com> | 2014-03-18 13:16:26 +0100 |
---|---|---|
committer | Frederik Gladhorn <frederik.gladhorn@digia.com> | 2014-03-20 15:55:39 +0100 |
commit | 3f0f86b0caed75241fa71c95a5d73bc0164348c5 (patch) | |
tree | 92b9fb00f2e9e90b0be2262093876d4f43b6cd13 /chromium/content/renderer/input | |
parent | e90d7c4b152c56919d963987e2503f9909a666d2 (diff) | |
download | qtwebengine-chromium-3f0f86b0caed75241fa71c95a5d73bc0164348c5.tar.gz |
Update to new stable branch 1750
This also includes an updated ninja and chromium dependencies
needed on Windows.
Change-Id: Icd597d80ed3fa4425933c9f1334c3c2e31291c42
Reviewed-by: Zoltan Arvai <zarvai@inf.u-szeged.hu>
Reviewed-by: Zeno Albisser <zeno.albisser@digia.com>
Diffstat (limited to 'chromium/content/renderer/input')
13 files changed, 2730 insertions, 0 deletions
diff --git a/chromium/content/renderer/input/OWNERS b/chromium/content/renderer/input/OWNERS new file mode 100644 index 00000000000..d012c830472 --- /dev/null +++ b/chromium/content/renderer/input/OWNERS @@ -0,0 +1,2 @@ +aelias@chromium.org +jdduke@chromium.org diff --git a/chromium/content/renderer/input/input_event_filter.cc b/chromium/content/renderer/input/input_event_filter.cc new file mode 100644 index 00000000000..a0399783988 --- /dev/null +++ b/chromium/content/renderer/input/input_event_filter.cc @@ -0,0 +1,175 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/input/input_event_filter.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/debug/trace_event.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "cc/input/input_handler.h" +#include "content/common/input/web_input_event_traits.h" +#include "content/common/input_messages.h" +#include "content/common/view_messages.h" +#include "content/public/common/content_switches.h" +#include "ui/gfx/vector2d_f.h" + +using blink::WebInputEvent; + +namespace content { + +InputEventFilter::InputEventFilter( + IPC::Listener* main_listener, + const scoped_refptr<base::MessageLoopProxy>& target_loop) + : main_loop_(base::MessageLoopProxy::current()), + main_listener_(main_listener), + sender_(NULL), + target_loop_(target_loop), + overscroll_notifications_enabled_(false) { + DCHECK(target_loop_.get()); + overscroll_notifications_enabled_ = + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableOverscrollNotifications); +} + +void InputEventFilter::SetBoundHandler(const Handler& handler) { + DCHECK(main_loop_->BelongsToCurrentThread()); + handler_ = handler; +} + +void InputEventFilter::DidAddInputHandler(int routing_id, + cc::InputHandler* input_handler) { + base::AutoLock locked(routes_lock_); + routes_.insert(routing_id); +} + +void InputEventFilter::DidRemoveInputHandler(int routing_id) { + base::AutoLock locked(routes_lock_); + routes_.erase(routing_id); +} + +void InputEventFilter::DidOverscroll(int routing_id, + const cc::DidOverscrollParams& params) { + DCHECK(target_loop_->BelongsToCurrentThread()); + + if (!overscroll_notifications_enabled_) + return; + + io_loop_->PostTask( + FROM_HERE, + base::Bind(&InputEventFilter::SendMessageOnIOThread, this, + ViewHostMsg_DidOverscroll(routing_id, + params.accumulated_overscroll, + params.current_fling_velocity))); +} + +void InputEventFilter::OnFilterAdded(IPC::Channel* channel) { + io_loop_ = base::MessageLoopProxy::current(); + sender_ = channel; +} + +void InputEventFilter::OnFilterRemoved() { + sender_ = NULL; +} + +void InputEventFilter::OnChannelClosing() { + sender_ = NULL; +} + +// This function returns true if the IPC message is one that the compositor +// thread can handle *or* one that needs to preserve relative order with +// messages that the compositor thread can handle. Returning true for a message +// type means that the message will go through an extra copy and thread hop, so +// use with care. +static bool RequiresThreadBounce(const IPC::Message& message) { + return IPC_MESSAGE_ID_CLASS(message.type()) == InputMsgStart; +} + +bool InputEventFilter::OnMessageReceived(const IPC::Message& message) { + TRACE_EVENT0("input", "InputEventFilter::OnMessageReceived"); + + if (!RequiresThreadBounce(message)) + return false; + + { + base::AutoLock locked(routes_lock_); + if (routes_.find(message.routing_id()) == routes_.end()) + return false; + } + + target_loop_->PostTask( + FROM_HERE, + base::Bind(&InputEventFilter::ForwardToHandler, this, message)); + return true; +} + +InputEventFilter::~InputEventFilter() { +} + +void InputEventFilter::ForwardToMainListener(const IPC::Message& message) { + main_listener_->OnMessageReceived(message); +} + +void InputEventFilter::ForwardToHandler(const IPC::Message& message) { + DCHECK(!handler_.is_null()); + DCHECK(target_loop_->BelongsToCurrentThread()); + + if (message.type() != InputMsg_HandleInputEvent::ID) { + main_loop_->PostTask( + FROM_HERE, + base::Bind(&InputEventFilter::ForwardToMainListener, + this, message)); + return; + } + + int routing_id = message.routing_id(); + ui::LatencyInfo latency_info; + const WebInputEvent* event = NULL; + bool is_keyboard_shortcut; + if (!InputMsg_HandleInputEvent::Read( + &message, &event, &latency_info, &is_keyboard_shortcut)) + return; + DCHECK(event); + + InputEventAckState ack = handler_.Run(routing_id, event, &latency_info); + + if (ack == INPUT_EVENT_ACK_STATE_NOT_CONSUMED) { + TRACE_EVENT0("input", "InputEventFilter::ForwardToHandler"); + IPC::Message new_msg = InputMsg_HandleInputEvent( + routing_id, event, latency_info, is_keyboard_shortcut); + main_loop_->PostTask( + FROM_HERE, + base::Bind(&InputEventFilter::ForwardToMainListener, + this, new_msg)); + return; + } + + if (!WebInputEventTraits::IgnoresAckDisposition(event->type)) + SendACK(event->type, ack, latency_info, routing_id); +} + +void InputEventFilter::SendACK(blink::WebInputEvent::Type type, + InputEventAckState ack_result, + const ui::LatencyInfo& latency_info, + int routing_id) { + DCHECK(target_loop_->BelongsToCurrentThread()); + + io_loop_->PostTask( + FROM_HERE, + base::Bind(&InputEventFilter::SendMessageOnIOThread, this, + InputHostMsg_HandleInputEvent_ACK( + routing_id, type, ack_result, latency_info))); +} + +void InputEventFilter::SendMessageOnIOThread(const IPC::Message& message) { + DCHECK(io_loop_->BelongsToCurrentThread()); + + if (!sender_) + return; // Filter was removed. + + sender_->Send(new IPC::Message(message)); +} + +} // namespace content diff --git a/chromium/content/renderer/input/input_event_filter.h b/chromium/content/renderer/input/input_event_filter.h new file mode 100644 index 00000000000..240f939f721 --- /dev/null +++ b/chromium/content/renderer/input/input_event_filter.h @@ -0,0 +1,94 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_INPUT_INPUT_EVENT_FILTER_H_ +#define CONTENT_RENDERER_INPUT_INPUT_EVENT_FILTER_H_ + +#include <queue> +#include <set> + +#include "base/callback_forward.h" +#include "base/synchronization/lock.h" +#include "content/common/content_export.h" +#include "content/port/common/input_event_ack_state.h" +#include "content/renderer/input/input_handler_manager_client.h" +#include "ipc/ipc_channel_proxy.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" + +// This class can be used to intercept InputMsg_HandleInputEvent messages +// and have them be delivered to a target thread. Input events are filtered +// based on routing_id (see AddRoute and RemoveRoute). +// +// The user of this class provides an instance of InputEventFilter::Handler, +// which will be passed WebInputEvents on the target thread. +// + +namespace content { + +class CONTENT_EXPORT InputEventFilter + : public InputHandlerManagerClient, + public IPC::ChannelProxy::MessageFilter { + public: + InputEventFilter(IPC::Listener* main_listener, + const scoped_refptr<base::MessageLoopProxy>& target_loop); + + // The |handler| is invoked on the thread associated with |target_loop| to + // handle input events matching the filtered routes. + // + // If INPUT_EVENT_ACK_STATE_NOT_CONSUMED is returned by the handler, + // the original InputMsg_HandleInputEvent message will be delivered to + // |main_listener| on the main thread. (The "main thread" in this context is + // the thread where the InputEventFilter was constructed.) The responsibility + // is left to the eventual handler to deliver the corresponding + // InputHostMsg_HandleInputEvent_ACK. + // + virtual void SetBoundHandler(const Handler& handler) OVERRIDE; + virtual void DidAddInputHandler(int routing_id, + cc::InputHandler* input_handler) OVERRIDE; + virtual void DidRemoveInputHandler(int routing_id) OVERRIDE; + virtual void DidOverscroll(int routing_id, + const cc::DidOverscrollParams& params) OVERRIDE; + + // IPC::ChannelProxy::MessageFilter methods: + virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE; + virtual void OnFilterRemoved() OVERRIDE; + virtual void OnChannelClosing() OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + private: + friend class IPC::ChannelProxy::MessageFilter; + virtual ~InputEventFilter(); + + void ForwardToMainListener(const IPC::Message& message); + void ForwardToHandler(const IPC::Message& message); + void SendACK(blink::WebInputEvent::Type type, + InputEventAckState ack_result, + const ui::LatencyInfo& latency_info, + int routing_id); + void SendMessageOnIOThread(const IPC::Message& message); + + scoped_refptr<base::MessageLoopProxy> main_loop_; + IPC::Listener* main_listener_; + + // The sender_ only gets invoked on the thread corresponding to io_loop_. + scoped_refptr<base::MessageLoopProxy> io_loop_; + IPC::Sender* sender_; + + // The handler_ only gets Run on the thread corresponding to target_loop_. + scoped_refptr<base::MessageLoopProxy> target_loop_; + Handler handler_; + + // Protects access to routes_. + base::Lock routes_lock_; + + // Indicates the routing_ids for which input events should be filtered. + std::set<int> routes_; + + // Specifies whether overscroll notifications are forwarded to the host. + bool overscroll_notifications_enabled_; +}; + +} // namespace content + +#endif // CONTENT_RENDERER_INPUT_INPUT_EVENT_FILTER_H_ diff --git a/chromium/content/renderer/input/input_event_filter_unittest.cc b/chromium/content/renderer/input/input_event_filter_unittest.cc new file mode 100644 index 00000000000..a131ceb47dc --- /dev/null +++ b/chromium/content/renderer/input/input_event_filter_unittest.cc @@ -0,0 +1,312 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <new> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "content/common/input_messages.h" +#include "content/common/view_messages.h" +#include "content/renderer/input/input_event_filter.h" +#include "ipc/ipc_test_sink.h" +#include "testing/gtest/include/gtest/gtest.h" + +using blink::WebInputEvent; +using blink::WebMouseEvent; + +namespace content { +namespace { + +const int kTestRoutingID = 13; + +class InputEventRecorder { + public: + InputEventRecorder() + : filter_(NULL), + handle_events_(false), + send_to_widget_(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; } + + size_t record_count() const { return records_.size(); } + + const WebInputEvent* record_at(size_t i) const { + const Record& record = records_[i]; + return reinterpret_cast<const WebInputEvent*>(&record.event_data[0]); + } + + void Clear() { + records_.clear(); + } + + InputEventAckState HandleInputEvent(int routing_id, + const WebInputEvent* event, + ui::LatencyInfo* latency_info) { + DCHECK_EQ(kTestRoutingID, routing_id); + + records_.push_back(Record(event)); + + if (handle_events_) { + return INPUT_EVENT_ACK_STATE_CONSUMED; + } else { + return send_to_widget_ ? INPUT_EVENT_ACK_STATE_NOT_CONSUMED + : INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; + } + } + + private: + struct Record { + Record(const WebInputEvent* event) { + const char* ptr = reinterpret_cast<const char*>(event); + event_data.assign(ptr, ptr + event->size); + } + std::vector<char> event_data; + }; + + InputEventFilter* filter_; + bool handle_events_; + bool send_to_widget_; + std::vector<Record> records_; +}; + +class IPCMessageRecorder : public IPC::Listener { + public: + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + messages_.push_back(message); + return true; + } + + size_t message_count() const { return messages_.size(); } + + const IPC::Message& message_at(size_t i) const { + return messages_[i]; + } + + void Clear() { + messages_.clear(); + } + + private: + std::vector<IPC::Message> messages_; +}; + +void InitMouseEvent(WebMouseEvent* event, WebInputEvent::Type type, + int x, int y) { + // Avoid valgrind false positives by initializing memory completely. + memset(event, 0, sizeof(*event)); + + new (event) WebMouseEvent(); + event->type = type; + event->x = x; + event->y = y; +} + +void AddMessagesToFilter(IPC::ChannelProxy::MessageFilter* message_filter, + const std::vector<IPC::Message>& events) { + for (size_t i = 0; i < events.size(); ++i) { + message_filter->OnMessageReceived(events[i]); + } + + base::MessageLoop::current()->RunUntilIdle(); +} + +void AddEventsToFilter(IPC::ChannelProxy::MessageFilter* message_filter, + const WebMouseEvent 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(), false)); + } + + AddMessagesToFilter(message_filter, messages); +} + +} // namespace + +class InputEventFilterTest : public testing::Test { + public: + virtual void SetUp() OVERRIDE { + filter_ = new InputEventFilter( + &message_recorder_, + message_loop_.message_loop_proxy()); + filter_->SetBoundHandler( + base::Bind(&InputEventRecorder::HandleInputEvent, + base::Unretained(&event_recorder_))); + + event_recorder_.set_filter(filter_.get()); + + filter_->OnFilterAdded(&ipc_sink_); + } + + + protected: + base::MessageLoop message_loop_; + + // Used to record IPCs sent by the filter to the RenderWidgetHost. + IPC::TestSink ipc_sink_; + + // Used to record IPCs forwarded by the filter to the main thread. + IPCMessageRecorder message_recorder_; + + // Used to record WebInputEvents delivered to the handler. + InputEventRecorder event_recorder_; + + scoped_refptr<InputEventFilter> filter_; +}; + +TEST_F(InputEventFilterTest, Basic) { + WebMouseEvent kEvents[3]; + InitMouseEvent(&kEvents[0], WebInputEvent::MouseDown, 10, 10); + InitMouseEvent(&kEvents[1], WebInputEvent::MouseMove, 20, 20); + InitMouseEvent(&kEvents[2], WebInputEvent::MouseUp, 30, 30); + + AddEventsToFilter(filter_.get(), kEvents, arraysize(kEvents)); + EXPECT_EQ(0U, ipc_sink_.message_count()); + EXPECT_EQ(0U, event_recorder_.record_count()); + EXPECT_EQ(0U, message_recorder_.message_count()); + + filter_->DidAddInputHandler(kTestRoutingID, NULL); + + AddEventsToFilter(filter_.get(), kEvents, arraysize(kEvents)); + ASSERT_EQ(arraysize(kEvents), ipc_sink_.message_count()); + ASSERT_EQ(arraysize(kEvents), event_recorder_.record_count()); + EXPECT_EQ(0U, message_recorder_.message_count()); + + for (size_t i = 0; i < arraysize(kEvents); ++i) { + const IPC::Message* message = ipc_sink_.GetMessageAt(i); + EXPECT_EQ(kTestRoutingID, message->routing_id()); + EXPECT_EQ(InputHostMsg_HandleInputEvent_ACK::ID, message->type()); + + WebInputEvent::Type event_type = WebInputEvent::Undefined; + InputEventAckState ack_result = INPUT_EVENT_ACK_STATE_NOT_CONSUMED; + ui::LatencyInfo latency_info; + EXPECT_TRUE(InputHostMsg_HandleInputEvent_ACK::Read(message, + &event_type, + &ack_result, + &latency_info)); + EXPECT_EQ(kEvents[i].type, event_type); + EXPECT_EQ(ack_result, INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS); + + const WebInputEvent* event = event_recorder_.record_at(i); + ASSERT_TRUE(event); + + EXPECT_EQ(kEvents[i].size, event->size); + EXPECT_TRUE(memcmp(&kEvents[i], event, event->size) == 0); + } + + event_recorder_.set_send_to_widget(true); + + AddEventsToFilter(filter_.get(), kEvents, arraysize(kEvents)); + EXPECT_EQ(arraysize(kEvents), ipc_sink_.message_count()); + EXPECT_EQ(2 * arraysize(kEvents), event_recorder_.record_count()); + EXPECT_EQ(arraysize(kEvents), message_recorder_.message_count()); + + for (size_t i = 0; i < arraysize(kEvents); ++i) { + const IPC::Message& message = message_recorder_.message_at(i); + + ASSERT_EQ(InputMsg_HandleInputEvent::ID, message.type()); + const WebInputEvent* event = NULL; + ui::LatencyInfo latency_info; + bool is_kbd_shortcut; + EXPECT_TRUE(InputMsg_HandleInputEvent::Read( + &message, &event, &latency_info, &is_kbd_shortcut)); + + EXPECT_EQ(kEvents[i].size, event->size); + EXPECT_TRUE(memcmp(&kEvents[i], event, event->size) == 0); + } + + // Now reset everything, and test that DidHandleInputEvent is called. + + ipc_sink_.ClearMessages(); + event_recorder_.Clear(); + message_recorder_.Clear(); + + event_recorder_.set_handle_events(true); + + AddEventsToFilter(filter_.get(), kEvents, arraysize(kEvents)); + EXPECT_EQ(arraysize(kEvents), ipc_sink_.message_count()); + EXPECT_EQ(arraysize(kEvents), event_recorder_.record_count()); + EXPECT_EQ(0U, message_recorder_.message_count()); + + for (size_t i = 0; i < arraysize(kEvents); ++i) { + const IPC::Message* message = ipc_sink_.GetMessageAt(i); + EXPECT_EQ(kTestRoutingID, message->routing_id()); + EXPECT_EQ(InputHostMsg_HandleInputEvent_ACK::ID, message->type()); + + WebInputEvent::Type event_type = WebInputEvent::Undefined; + InputEventAckState ack_result = INPUT_EVENT_ACK_STATE_NOT_CONSUMED; + ui::LatencyInfo latency_info; + EXPECT_TRUE(InputHostMsg_HandleInputEvent_ACK::Read(message, + &event_type, + &ack_result, + &latency_info)); + EXPECT_EQ(kEvents[i].type, event_type); + EXPECT_EQ(ack_result, INPUT_EVENT_ACK_STATE_CONSUMED); + } + + filter_->OnFilterRemoved(); +} + +TEST_F(InputEventFilterTest, PreserveRelativeOrder) { + filter_->DidAddInputHandler(kTestRoutingID, NULL); + event_recorder_.set_send_to_widget(true); + + + WebMouseEvent mouse_down; + mouse_down.type = WebMouseEvent::MouseDown; + WebMouseEvent mouse_up; + mouse_up.type = WebMouseEvent::MouseUp; + + std::vector<IPC::Message> messages; + messages.push_back(InputMsg_HandleInputEvent(kTestRoutingID, + &mouse_down, + ui::LatencyInfo(), + false)); + // Control where input events are delivered. + messages.push_back(InputMsg_MouseCaptureLost(kTestRoutingID)); + messages.push_back(InputMsg_SetFocus(kTestRoutingID, true)); + + // Editing operations + messages.push_back(InputMsg_Undo(kTestRoutingID)); + messages.push_back(InputMsg_Redo(kTestRoutingID)); + messages.push_back(InputMsg_Cut(kTestRoutingID)); + messages.push_back(InputMsg_Copy(kTestRoutingID)); +#if defined(OS_MACOSX) + messages.push_back(InputMsg_CopyToFindPboard(kTestRoutingID)); +#endif + messages.push_back(InputMsg_Paste(kTestRoutingID)); + messages.push_back(InputMsg_PasteAndMatchStyle(kTestRoutingID)); + messages.push_back(InputMsg_Delete(kTestRoutingID)); + messages.push_back(InputMsg_Replace(kTestRoutingID, base::string16())); + messages.push_back(InputMsg_ReplaceMisspelling(kTestRoutingID, + base::string16())); + messages.push_back(InputMsg_Delete(kTestRoutingID)); + messages.push_back(InputMsg_SelectAll(kTestRoutingID)); + messages.push_back(InputMsg_Unselect(kTestRoutingID)); + messages.push_back(InputMsg_SelectRange(kTestRoutingID, + gfx::Point(), gfx::Point())); + messages.push_back(InputMsg_MoveCaret(kTestRoutingID, gfx::Point())); + + messages.push_back(InputMsg_HandleInputEvent(kTestRoutingID, + &mouse_up, + ui::LatencyInfo(), + false)); + AddMessagesToFilter(filter_.get(), messages); + + // We should have sent all messages back to the main thread and preserved + // their relative order. + ASSERT_EQ(message_recorder_.message_count(), messages.size()); + for (size_t i = 0; i < messages.size(); ++i) { + EXPECT_EQ(message_recorder_.message_at(i).type(), messages[i].type()) << i; + } +} + +} // namespace content diff --git a/chromium/content/renderer/input/input_handler_manager.cc b/chromium/content/renderer/input/input_handler_manager.cc new file mode 100644 index 00000000000..6195f5195df --- /dev/null +++ b/chromium/content/renderer/input/input_handler_manager.cc @@ -0,0 +1,132 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/input/input_handler_manager.h" + +#include "base/bind.h" +#include "base/debug/trace_event.h" +#include "base/message_loop/message_loop_proxy.h" +#include "cc/input/input_handler.h" +#include "content/renderer/input/input_event_filter.h" +#include "content/renderer/input/input_handler_manager_client.h" +#include "content/renderer/input/input_handler_wrapper.h" + +using blink::WebInputEvent; + +namespace content { + +namespace { + +InputEventAckState InputEventDispositionToAck( + InputHandlerProxy::EventDisposition disposition) { + switch (disposition) { + case InputHandlerProxy::DID_HANDLE: + return INPUT_EVENT_ACK_STATE_CONSUMED; + case InputHandlerProxy::DID_NOT_HANDLE: + return INPUT_EVENT_ACK_STATE_NOT_CONSUMED; + case InputHandlerProxy::DROP_EVENT: + return INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; + } + NOTREACHED(); + return INPUT_EVENT_ACK_STATE_UNKNOWN; +} + +} // namespace + +InputHandlerManager::InputHandlerManager( + const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy, + InputHandlerManagerClient* client) + : message_loop_proxy_(message_loop_proxy), + client_(client) { + DCHECK(client_); + client_->SetBoundHandler(base::Bind(&InputHandlerManager::HandleInputEvent, + base::Unretained(this))); +} + +InputHandlerManager::~InputHandlerManager() { + client_->SetBoundHandler(InputHandlerManagerClient::Handler()); +} + +void InputHandlerManager::AddInputHandler( + int routing_id, + const base::WeakPtr<cc::InputHandler>& input_handler, + const base::WeakPtr<RenderViewImpl>& render_view_impl) { + if (message_loop_proxy_->BelongsToCurrentThread()) { + AddInputHandlerOnCompositorThread(routing_id, + base::MessageLoopProxy::current(), + input_handler, + render_view_impl); + } else { + message_loop_proxy_->PostTask( + FROM_HERE, + base::Bind(&InputHandlerManager::AddInputHandlerOnCompositorThread, + base::Unretained(this), + routing_id, + base::MessageLoopProxy::current(), + input_handler, + render_view_impl)); + } +} + +void InputHandlerManager::AddInputHandlerOnCompositorThread( + int routing_id, + const scoped_refptr<base::MessageLoopProxy>& main_loop, + const base::WeakPtr<cc::InputHandler>& input_handler, + const base::WeakPtr<RenderViewImpl>& render_view_impl) { + DCHECK(message_loop_proxy_->BelongsToCurrentThread()); + + // The handler could be gone by this point if the compositor has shut down. + if (!input_handler) + return; + + // The same handler may be registered for a route multiple times. + if (input_handlers_.count(routing_id) != 0) + return; + + TRACE_EVENT1("input", + "InputHandlerManager::AddInputHandlerOnCompositorThread", + "result", "AddingRoute"); + client_->DidAddInputHandler(routing_id, input_handler.get()); + input_handlers_.add(routing_id, + make_scoped_ptr(new InputHandlerWrapper(this, + routing_id, main_loop, input_handler, render_view_impl))); +} + +void InputHandlerManager::RemoveInputHandler(int routing_id) { + DCHECK(message_loop_proxy_->BelongsToCurrentThread()); + DCHECK(input_handlers_.contains(routing_id)); + + TRACE_EVENT0("input", "InputHandlerManager::RemoveInputHandler"); + + client_->DidRemoveInputHandler(routing_id); + input_handlers_.erase(routing_id); +} + +InputEventAckState InputHandlerManager::HandleInputEvent( + int routing_id, + const WebInputEvent* input_event, + ui::LatencyInfo* latency_info) { + DCHECK(message_loop_proxy_->BelongsToCurrentThread()); + + InputHandlerMap::iterator it = input_handlers_.find(routing_id); + if (it == input_handlers_.end()) { + TRACE_EVENT1("input", "InputHandlerManager::HandleInputEvent", + "result", "NoInputHandlerFound"); + // Oops, we no longer have an interested input handler.. + return INPUT_EVENT_ACK_STATE_NOT_CONSUMED; + } + + InputHandlerProxy* proxy = it->second->input_handler_proxy(); + return InputEventDispositionToAck( + proxy->HandleInputEventWithLatencyInfo(*input_event, latency_info)); +} + +void InputHandlerManager::DidOverscroll(int routing_id, + const cc::DidOverscrollParams& params) { + client_->DidOverscroll(routing_id, params); +} + + + +} // namespace content diff --git a/chromium/content/renderer/input/input_handler_manager.h b/chromium/content/renderer/input/input_handler_manager.h new file mode 100644 index 00000000000..f2df0415f29 --- /dev/null +++ b/chromium/content/renderer/input/input_handler_manager.h @@ -0,0 +1,78 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_INPUT_INPUT_HANDLER_MANAGER_H_ +#define CONTENT_RENDERER_INPUT_INPUT_HANDLER_MANAGER_H_ + +#include "base/containers/scoped_ptr_hash_map.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "content/port/common/input_event_ack_state.h" +#include "content/renderer/render_view_impl.h" + +namespace base { +class MessageLoopProxy; +} + +namespace cc { +class InputHandler; +struct DidOverscrollParams; +} + +namespace blink { +class WebInputEvent; +} + +namespace content { + +class InputHandlerWrapper; +class InputHandlerManagerClient; + +// InputHandlerManager class manages InputHandlerProxy instances for +// the WebViews in this renderer. +class InputHandlerManager { + public: + // |message_loop_proxy| is the MessageLoopProxy of the compositor thread. Both + // the underlying MessageLoop and supplied |client| must outlive this object. + InputHandlerManager( + const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy, + InputHandlerManagerClient* client); + ~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); + + // Callback only from the compositor's thread. + void RemoveInputHandler(int routing_id); + + // Called from the compositor's thread. + InputEventAckState HandleInputEvent(int routing_id, + const blink::WebInputEvent* input_event, + ui::LatencyInfo* latency_info); + + // Called from the compositor's thread. + void DidOverscroll(int routing_id, const cc::DidOverscrollParams& params); + + private: + // Called from the compositor's thread. + void AddInputHandlerOnCompositorThread( + int routing_id, + const scoped_refptr<base::MessageLoopProxy>& main_loop, + const base::WeakPtr<cc::InputHandler>& input_handler, + const base::WeakPtr<RenderViewImpl>& render_view_impl); + + typedef base::ScopedPtrHashMap<int, // routing_id + InputHandlerWrapper> InputHandlerMap; + InputHandlerMap input_handlers_; + + scoped_refptr<base::MessageLoopProxy> message_loop_proxy_; + InputHandlerManagerClient* client_; +}; + +} // namespace content + +#endif // CONTENT_RENDERER_INPUT_INPUT_HANDLER_MANAGER_H_ diff --git a/chromium/content/renderer/input/input_handler_manager_client.h b/chromium/content/renderer/input/input_handler_manager_client.h new file mode 100644 index 00000000000..0a36e50ba5d --- /dev/null +++ b/chromium/content/renderer/input/input_handler_manager_client.h @@ -0,0 +1,60 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_INPUT_INPUT_HANDLER_MANAGER_CLIENT_H_ +#define CONTENT_RENDERER_INPUT_INPUT_HANDLER_MANAGER_CLIENT_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/callback_forward.h" +#include "content/common/content_export.h" +#include "ui/gfx/vector2d_f.h" + +namespace ui { +struct LatencyInfo; +} + +namespace cc { +class InputHandler; +struct DidOverscrollParams; +} + +namespace blink { +class WebInputEvent; +} + +namespace content { + +class CONTENT_EXPORT InputHandlerManagerClient { + public: + virtual ~InputHandlerManagerClient() {} + + // The Manager will supply a |handler| when bound to the client. This is valid + // until the manager shuts down, at which point it supplies a null |handler|. + // The client should only makes calls to |handler| on the compositor thread. + typedef base::Callback< + InputEventAckState(int /*routing_id*/, + const blink::WebInputEvent*, + ui::LatencyInfo* latency_info)> Handler; + + // Called from the main thread. + virtual void SetBoundHandler(const Handler& handler) = 0; + + // Called from the compositor thread. + virtual void DidAddInputHandler(int routing_id, + cc::InputHandler* input_handler) = 0; + virtual void DidRemoveInputHandler(int routing_id) = 0; + virtual void DidOverscroll(int routing_id, + const cc::DidOverscrollParams& params) = 0; + + protected: + InputHandlerManagerClient() {} + + private: + DISALLOW_COPY_AND_ASSIGN(InputHandlerManagerClient); +}; + +} // namespace content + +#endif // CONTENT_COMMON_GPU_INPUT_HANDLER_MANAGER_CLIENT_H_ diff --git a/chromium/content/renderer/input/input_handler_proxy.cc b/chromium/content/renderer/input/input_handler_proxy.cc new file mode 100644 index 00000000000..ce9ce22a02d --- /dev/null +++ b/chromium/content/renderer/input/input_handler_proxy.cc @@ -0,0 +1,477 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/input/input_handler_proxy.h" + +#include "base/debug/trace_event.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "content/renderer/input/input_handler_proxy_client.h" +#include "third_party/WebKit/public/platform/Platform.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" +#include "ui/events/latency_info.h" +#include "ui/gfx/frame_time.h" + +using blink::WebFloatPoint; +using blink::WebFloatSize; +using blink::WebGestureEvent; +using blink::WebInputEvent; +using blink::WebMouseEvent; +using blink::WebMouseWheelEvent; +using blink::WebPoint; +using blink::WebTouchEvent; +using blink::WebTouchPoint; + +namespace { + +// Validate provided event timestamps that interact with animation timestamps. +const double kBadTimestampDeltaFromNowInS = 60. * 60. * 24. * 7.; + +double InSecondsF(const base::TimeTicks& time) { + return (time - base::TimeTicks()).InSecondsF(); +} + +void SendScrollLatencyUma(const WebInputEvent& event, + const ui::LatencyInfo& latency_info) { + if (!(event.type == WebInputEvent::GestureScrollBegin || + event.type == WebInputEvent::GestureScrollUpdate || + event.type == WebInputEvent::GestureScrollUpdateWithoutPropagation)) + return; + + ui::LatencyInfo::LatencyMap::const_iterator it = + latency_info.latency_components.find(std::make_pair( + ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0)); + + if (it == latency_info.latency_components.end()) + return; + + base::TimeDelta delta = base::TimeTicks::HighResNow() - it->second.event_time; + for (size_t i = 0; i < it->second.event_count; ++i) { + UMA_HISTOGRAM_CUSTOM_COUNTS( + "Event.Latency.RendererImpl.GestureScroll2", + delta.InMicroseconds(), + 0, + 1000000, + 100); + } +} // namespace + +} + +namespace content { + +InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler) + : client_(NULL), + input_handler_(input_handler), +#ifndef NDEBUG + expect_scroll_update_end_(false), + expect_pinch_update_end_(false), +#endif + gesture_scroll_on_impl_thread_(false), + gesture_pinch_on_impl_thread_(false), + fling_may_be_active_on_main_thread_(false), + fling_overscrolled_horizontally_(false), + fling_overscrolled_vertically_(false) { + input_handler_->BindToClient(this); +} + +InputHandlerProxy::~InputHandlerProxy() {} + +void InputHandlerProxy::WillShutdown() { + input_handler_ = NULL; + DCHECK(client_); + client_->WillShutdown(); +} + +void InputHandlerProxy::SetClient(InputHandlerProxyClient* client) { + DCHECK(!client_ || !client); + client_ = client; +} + +InputHandlerProxy::EventDisposition +InputHandlerProxy::HandleInputEventWithLatencyInfo( + const WebInputEvent& event, + ui::LatencyInfo* latency_info) { + DCHECK(input_handler_); + + SendScrollLatencyUma(event, *latency_info); + + scoped_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor = + input_handler_->CreateLatencyInfoSwapPromiseMonitor(latency_info); + InputHandlerProxy::EventDisposition disposition = HandleInputEvent(event); + return disposition; +} + +InputHandlerProxy::EventDisposition InputHandlerProxy::HandleInputEvent( + const WebInputEvent& event) { + DCHECK(client_); + DCHECK(input_handler_); + + if (event.type == WebInputEvent::MouseWheel) { + const WebMouseWheelEvent& wheel_event = + *static_cast<const WebMouseWheelEvent*>(&event); + if (wheel_event.scrollByPage) { + // TODO(jamesr): We don't properly handle scroll by page in the compositor + // thread, so punt it to the main thread. http://crbug.com/236639 + return DID_NOT_HANDLE; + } + cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin( + gfx::Point(wheel_event.x, wheel_event.y), cc::InputHandler::Wheel); + switch (scroll_status) { + case cc::InputHandler::ScrollStarted: { + TRACE_EVENT_INSTANT2( + "renderer", + "InputHandlerProxy::handle_input wheel scroll", + TRACE_EVENT_SCOPE_THREAD, + "deltaX", + -wheel_event.deltaX, + "deltaY", + -wheel_event.deltaY); + bool did_scroll = input_handler_->ScrollBy( + gfx::Point(wheel_event.x, wheel_event.y), + gfx::Vector2dF(-wheel_event.deltaX, -wheel_event.deltaY)); + input_handler_->ScrollEnd(); + return did_scroll ? DID_HANDLE : DROP_EVENT; + } + case cc::InputHandler::ScrollIgnored: + // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail + // to properly sync scrollability it's safer to send the event to the + // main thread. Change back to DROP_EVENT once we have synchronization + // bugs sorted out. + return DID_NOT_HANDLE; + case cc::InputHandler::ScrollOnMainThread: + return DID_NOT_HANDLE; + } + } else if (event.type == WebInputEvent::GestureScrollBegin) { + DCHECK(!gesture_scroll_on_impl_thread_); +#ifndef NDEBUG + DCHECK(!expect_scroll_update_end_); + expect_scroll_update_end_ = true; +#endif + const WebGestureEvent& gesture_event = + *static_cast<const WebGestureEvent*>(&event); + cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin( + gfx::Point(gesture_event.x, gesture_event.y), + cc::InputHandler::Gesture); + switch (scroll_status) { + case cc::InputHandler::ScrollStarted: + gesture_scroll_on_impl_thread_ = true; + return DID_HANDLE; + case cc::InputHandler::ScrollOnMainThread: + return DID_NOT_HANDLE; + case cc::InputHandler::ScrollIgnored: + return DROP_EVENT; + } + } else if (event.type == WebInputEvent::GestureScrollUpdate) { +#ifndef NDEBUG + DCHECK(expect_scroll_update_end_); +#endif + + if (!gesture_scroll_on_impl_thread_ && !gesture_pinch_on_impl_thread_) + return DID_NOT_HANDLE; + + const WebGestureEvent& gesture_event = + *static_cast<const WebGestureEvent*>(&event); + bool did_scroll = input_handler_->ScrollBy( + gfx::Point(gesture_event.x, gesture_event.y), + gfx::Vector2dF(-gesture_event.data.scrollUpdate.deltaX, + -gesture_event.data.scrollUpdate.deltaY)); + return did_scroll ? DID_HANDLE : DROP_EVENT; + } else if (event.type == WebInputEvent::GestureScrollEnd) { +#ifndef NDEBUG + DCHECK(expect_scroll_update_end_); + expect_scroll_update_end_ = false; +#endif + input_handler_->ScrollEnd(); + + if (!gesture_scroll_on_impl_thread_) + return DID_NOT_HANDLE; + + gesture_scroll_on_impl_thread_ = false; + return DID_HANDLE; + } else if (event.type == WebInputEvent::GesturePinchBegin) { +#ifndef NDEBUG + DCHECK(!expect_pinch_update_end_); + expect_pinch_update_end_ = true; +#endif + input_handler_->PinchGestureBegin(); + gesture_pinch_on_impl_thread_ = true; + return DID_HANDLE; + } else if (event.type == WebInputEvent::GesturePinchEnd) { +#ifndef NDEBUG + DCHECK(expect_pinch_update_end_); + expect_pinch_update_end_ = false; +#endif + gesture_pinch_on_impl_thread_ = false; + input_handler_->PinchGestureEnd(); + return DID_HANDLE; + } else if (event.type == WebInputEvent::GesturePinchUpdate) { +#ifndef NDEBUG + DCHECK(expect_pinch_update_end_); +#endif + const WebGestureEvent& gesture_event = + *static_cast<const WebGestureEvent*>(&event); + input_handler_->PinchGestureUpdate( + gesture_event.data.pinchUpdate.scale, + gfx::Point(gesture_event.x, gesture_event.y)); + return DID_HANDLE; + } else if (event.type == WebInputEvent::GestureFlingStart) { + const WebGestureEvent& gesture_event = + *static_cast<const WebGestureEvent*>(&event); + return HandleGestureFling(gesture_event); + } else if (event.type == WebInputEvent::GestureFlingCancel) { + if (CancelCurrentFling()) + return DID_HANDLE; + else if (!fling_may_be_active_on_main_thread_) + return DROP_EVENT; + } else if (event.type == WebInputEvent::TouchStart) { + const WebTouchEvent& touch_event = + *static_cast<const WebTouchEvent*>(&event); + for (size_t i = 0; i < touch_event.touchesLength; ++i) { + if (touch_event.touches[i].state != WebTouchPoint::StatePressed) + continue; + if (input_handler_->HaveTouchEventHandlersAt(touch_event.touches[i] + .position)) + return DID_NOT_HANDLE; + } + return DROP_EVENT; + } else if (WebInputEvent::isKeyboardEventType(event.type)) { + CancelCurrentFling(); + } + + return DID_NOT_HANDLE; +} + +InputHandlerProxy::EventDisposition +InputHandlerProxy::HandleGestureFling( + const WebGestureEvent& gesture_event) { + cc::InputHandler::ScrollStatus scroll_status; + + if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) { + scroll_status = input_handler_->ScrollBegin( + gfx::Point(gesture_event.x, gesture_event.y), + cc::InputHandler::NonBubblingGesture); + } else { + if (!gesture_scroll_on_impl_thread_) + scroll_status = cc::InputHandler::ScrollOnMainThread; + else + scroll_status = input_handler_->FlingScrollBegin(); + } + +#ifndef NDEBUG + expect_scroll_update_end_ = false; +#endif + + switch (scroll_status) { + case cc::InputHandler::ScrollStarted: { + if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) + input_handler_->ScrollEnd(); + + fling_curve_.reset(client_->CreateFlingAnimationCurve( + gesture_event.sourceDevice, + WebFloatPoint(gesture_event.data.flingStart.velocityX, + gesture_event.data.flingStart.velocityY), + blink::WebSize())); + fling_overscrolled_horizontally_ = false; + fling_overscrolled_vertically_ = false; + TRACE_EVENT_ASYNC_BEGIN0( + "renderer", + "InputHandlerProxy::HandleGestureFling::started", + this); + if (gesture_event.timeStampSeconds) { + fling_parameters_.startTime = gesture_event.timeStampSeconds; + DCHECK_LT(fling_parameters_.startTime - + InSecondsF(gfx::FrameTime::Now()), + kBadTimestampDeltaFromNowInS); + } + fling_parameters_.delta = + WebFloatPoint(gesture_event.data.flingStart.velocityX, + gesture_event.data.flingStart.velocityY); + fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y); + fling_parameters_.globalPoint = + WebPoint(gesture_event.globalX, gesture_event.globalY); + fling_parameters_.modifiers = gesture_event.modifiers; + fling_parameters_.sourceDevice = gesture_event.sourceDevice; + input_handler_->ScheduleAnimation(); + return DID_HANDLE; + } + case cc::InputHandler::ScrollOnMainThread: { + TRACE_EVENT_INSTANT0("renderer", + "InputHandlerProxy::HandleGestureFling::" + "scroll_on_main_thread", + TRACE_EVENT_SCOPE_THREAD); + fling_may_be_active_on_main_thread_ = true; + return DID_NOT_HANDLE; + } + case cc::InputHandler::ScrollIgnored: { + TRACE_EVENT_INSTANT0( + "renderer", + "InputHandlerProxy::HandleGestureFling::ignored", + TRACE_EVENT_SCOPE_THREAD); + if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) { + // We still pass the curve to the main thread if there's nothing + // scrollable, in case something + // registers a handler before the curve is over. + return DID_NOT_HANDLE; + } + return DROP_EVENT; + } + } + return DID_NOT_HANDLE; +} + +void InputHandlerProxy::Animate(base::TimeTicks time) { + if (!fling_curve_) + return; + + double monotonic_time_sec = InSecondsF(time); + if (!fling_parameters_.startTime) { + fling_parameters_.startTime = monotonic_time_sec; + input_handler_->ScheduleAnimation(); + return; + } + + if (fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime, + this)) { + input_handler_->ScheduleAnimation(); + } else { + TRACE_EVENT_INSTANT0("renderer", + "InputHandlerProxy::animate::flingOver", + TRACE_EVENT_SCOPE_THREAD); + CancelCurrentFling(); + } +} + +void InputHandlerProxy::MainThreadHasStoppedFlinging() { + fling_may_be_active_on_main_thread_ = false; +} + +void InputHandlerProxy::DidOverscroll(const cc::DidOverscrollParams& params) { + DCHECK(client_); + if (fling_curve_) { + static const int kFlingOverscrollThreshold = 1; + fling_overscrolled_horizontally_ |= + std::abs(params.accumulated_overscroll.x()) >= + kFlingOverscrollThreshold; + fling_overscrolled_vertically_ |= + std::abs(params.accumulated_overscroll.y()) >= + kFlingOverscrollThreshold; + } + + client_->DidOverscroll(params); +} + +bool InputHandlerProxy::CancelCurrentFling() { + bool had_fling_animation = fling_curve_; + if (had_fling_animation && + fling_parameters_.sourceDevice == WebGestureEvent::Touchscreen) { + input_handler_->ScrollEnd(); + TRACE_EVENT_ASYNC_END0( + "renderer", + "InputHandlerProxy::HandleGestureFling::started", + this); + } + + TRACE_EVENT_INSTANT1("renderer", + "InputHandlerProxy::CancelCurrentFling", + TRACE_EVENT_SCOPE_THREAD, + "had_fling_animation", + had_fling_animation); + fling_curve_.reset(); + gesture_scroll_on_impl_thread_ = false; + fling_parameters_ = blink::WebActiveWheelFlingParameters(); + return had_fling_animation; +} + +bool InputHandlerProxy::TouchpadFlingScroll( + const WebFloatSize& increment) { + WebMouseWheelEvent synthetic_wheel; + synthetic_wheel.type = WebInputEvent::MouseWheel; + 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); + switch (disposition) { + case DID_HANDLE: + return true; + case DROP_EVENT: + break; + case DID_NOT_HANDLE: + TRACE_EVENT_INSTANT0("renderer", + "InputHandlerProxy::scrollBy::AbortFling", + TRACE_EVENT_SCOPE_THREAD); + // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the + // main thread. In this case we need to schedule a commit and transfer the + // fling curve over to the main thread and run the rest of the wheels from + // there. This can happen when flinging a page that contains a scrollable + // subarea that we can't scroll on the thread if the fling starts outside + // the subarea but then is flung "under" the pointer. + client_->TransferActiveWheelFlingAnimation(fling_parameters_); + fling_may_be_active_on_main_thread_ = true; + CancelCurrentFling(); + break; + } + + return false; +} + +static gfx::Vector2dF ToClientScrollIncrement(const WebFloatSize& increment) { + return gfx::Vector2dF(-increment.width, -increment.height); +} + +void InputHandlerProxy::scrollBy(const WebFloatSize& increment) { + WebFloatSize clipped_increment; + if (!fling_overscrolled_horizontally_) + clipped_increment.width = increment.width; + if (!fling_overscrolled_vertically_) + clipped_increment.height = increment.height; + + if (clipped_increment == WebFloatSize()) + return; + + TRACE_EVENT2("renderer", + "InputHandlerProxy::scrollBy", + "x", + clipped_increment.width, + "y", + clipped_increment.height); + + bool did_scroll = false; + + switch (fling_parameters_.sourceDevice) { + case WebGestureEvent::Touchpad: + did_scroll = TouchpadFlingScroll(clipped_increment); + break; + case WebGestureEvent::Touchscreen: + clipped_increment = ToClientScrollIncrement(clipped_increment); + did_scroll = input_handler_->ScrollBy(fling_parameters_.point, + clipped_increment); + break; + } + + if (did_scroll) { + fling_parameters_.cumulativeScroll.width += clipped_increment.width; + fling_parameters_.cumulativeScroll.height += clipped_increment.height; + } +} + +void InputHandlerProxy::notifyCurrentFlingVelocity( + const WebFloatSize& velocity) { + TRACE_EVENT2("renderer", + "InputHandlerProxy::notifyCurrentFlingVelocity", + "vx", + velocity.width, + "vy", + velocity.height); + input_handler_->NotifyCurrentFlingVelocity(ToClientScrollIncrement(velocity)); +} + +} // namespace content diff --git a/chromium/content/renderer/input/input_handler_proxy.h b/chromium/content/renderer/input/input_handler_proxy.h new file mode 100644 index 00000000000..46f88ab5ea4 --- /dev/null +++ b/chromium/content/renderer/input/input_handler_proxy.h @@ -0,0 +1,95 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_INPUT_INPUT_HANDLER_PROXY_H_ +#define CONTENT_RENDERER_INPUT_INPUT_HANDLER_PROXY_H_ + +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/memory/scoped_ptr.h" +#include "cc/input/input_handler.h" +#include "content/common/content_export.h" +#include "third_party/WebKit/public/platform/WebGestureCurve.h" +#include "third_party/WebKit/public/platform/WebGestureCurveTarget.h" +#include "third_party/WebKit/public/web/WebActiveWheelFlingParameters.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" + +namespace content { + +class InputHandlerProxyClient; + +// This class is a proxy between the content input event filtering and the +// compositor's input handling logic. InputHandlerProxy instances live entirely +// on the compositor thread. Each InputHandler instance handles input events +// intended for a specific WebWidget. +class CONTENT_EXPORT InputHandlerProxy + : public cc::InputHandlerClient, + public NON_EXPORTED_BASE(blink::WebGestureCurveTarget) { + public: + explicit InputHandlerProxy(cc::InputHandler* input_handler); + virtual ~InputHandlerProxy(); + + void SetClient(InputHandlerProxyClient* client); + + enum EventDisposition { + DID_HANDLE, + DID_NOT_HANDLE, + DROP_EVENT + }; + EventDisposition HandleInputEventWithLatencyInfo( + const blink::WebInputEvent& event, + ui::LatencyInfo* latency_info); + EventDisposition HandleInputEvent(const blink::WebInputEvent& event); + + // cc::InputHandlerClient implementation. + virtual void WillShutdown() OVERRIDE; + virtual void Animate(base::TimeTicks time) OVERRIDE; + virtual void MainThreadHasStoppedFlinging() OVERRIDE; + virtual void DidOverscroll(const cc::DidOverscrollParams& params) OVERRIDE; + + // blink::WebGestureCurveTarget implementation. + virtual void scrollBy(const blink::WebFloatSize& offset); + virtual void notifyCurrentFlingVelocity(const blink::WebFloatSize& velocity); + + bool gesture_scroll_on_impl_thread_for_testing() const { + return gesture_scroll_on_impl_thread_; + } + + private: + EventDisposition HandleGestureFling(const blink::WebGestureEvent& event); + + // Returns true if we scrolled by the increment. + bool TouchpadFlingScroll(const blink::WebFloatSize& increment); + + // Returns true if we actually had an active fling to cancel. + bool CancelCurrentFling(); + + scoped_ptr<blink::WebGestureCurve> fling_curve_; + // Parameters for the active fling animation, stored in case we need to + // transfer it out later. + blink::WebActiveWheelFlingParameters fling_parameters_; + + InputHandlerProxyClient* client_; + cc::InputHandler* input_handler_; + +#ifndef NDEBUG + bool expect_scroll_update_end_; + bool expect_pinch_update_end_; +#endif + bool gesture_scroll_on_impl_thread_; + bool gesture_pinch_on_impl_thread_; + // This is always false when there are no flings on the main thread, but + // conservative in the sense that we might not be actually flinging when it is + // true. + bool fling_may_be_active_on_main_thread_; + // The axes on which the current fling has overshot the bounds of the content. + bool fling_overscrolled_horizontally_; + bool fling_overscrolled_vertically_; + + DISALLOW_COPY_AND_ASSIGN(InputHandlerProxy); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_INPUT_INPUT_HANDLER_PROXY_H_ diff --git a/chromium/content/renderer/input/input_handler_proxy_client.h b/chromium/content/renderer/input/input_handler_proxy_client.h new file mode 100644 index 00000000000..7ef1121bf7c --- /dev/null +++ b/chromium/content/renderer/input/input_handler_proxy_client.h @@ -0,0 +1,43 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_INPUT_INPUT_HANDLER_PROXY_CLIENT_H_ +#define CONTENT_RENDERER_INPUT_INPUT_HANDLER_PROXY_CLIENT_H_ + +namespace blink { +class WebGestureCurve; +struct WebActiveWheelFlingParameters; +struct WebFloatPoint; +struct WebSize; +} + +namespace content { + +// All callbacks invoked from the compositor thread. +class InputHandlerProxyClient { + public: + // Called just before the InputHandlerProxy shuts down. + virtual void WillShutdown() = 0; + + // Transfers an active wheel fling animation initiated by a previously + // handled input event out to the client. + virtual void TransferActiveWheelFlingAnimation( + const blink::WebActiveWheelFlingParameters& params) = 0; + + // Creates a new fling animation curve instance for device |device_source| + // with |velocity| and already scrolled |cumulative_scroll| pixels. + virtual blink::WebGestureCurve* CreateFlingAnimationCurve( + int device_source, + const blink::WebFloatPoint& velocity, + const blink::WebSize& cumulative_scroll) = 0; + + virtual void DidOverscroll(const cc::DidOverscrollParams& params) = 0; + + protected: + virtual ~InputHandlerProxyClient() {} +}; + +} // namespace content + +#endif // CONTENT_RENDERER_INPUT_INPUT_HANDLER_PROXY_CLIENT_H_ diff --git a/chromium/content/renderer/input/input_handler_proxy_unittest.cc b/chromium/content/renderer/input/input_handler_proxy_unittest.cc new file mode 100644 index 00000000000..c7cc9168c18 --- /dev/null +++ b/chromium/content/renderer/input/input_handler_proxy_unittest.cc @@ -0,0 +1,1152 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/input/input_handler_proxy.h" + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "cc/base/swap_promise_monitor.h" +#include "content/renderer/input/input_handler_proxy_client.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/public/platform/WebFloatPoint.h" +#include "third_party/WebKit/public/platform/WebFloatSize.h" +#include "third_party/WebKit/public/platform/WebGestureCurve.h" +#include "third_party/WebKit/public/platform/WebPoint.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" +#include "ui/events/latency_info.h" + +using blink::WebActiveWheelFlingParameters; +using blink::WebFloatPoint; +using blink::WebFloatSize; +using blink::WebGestureEvent; +using blink::WebInputEvent; +using blink::WebMouseWheelEvent; +using blink::WebPoint; +using blink::WebSize; +using blink::WebTouchEvent; +using blink::WebTouchPoint; + +namespace content { +namespace { + +class MockInputHandler : public cc::InputHandler { + public: + MockInputHandler() {} + virtual ~MockInputHandler() {} + + MOCK_METHOD0(PinchGestureBegin, void()); + MOCK_METHOD2(PinchGestureUpdate, + void(float magnify_delta, gfx::Point anchor)); + MOCK_METHOD0(PinchGestureEnd, void()); + + MOCK_METHOD0(ScheduleAnimation, void()); + + MOCK_METHOD2(ScrollBegin, + ScrollStatus(gfx::Point viewport_point, + cc::InputHandler::ScrollInputType type)); + MOCK_METHOD2(ScrollBy, + bool(gfx::Point viewport_point, gfx::Vector2dF scroll_delta)); + MOCK_METHOD2(ScrollVerticallyByPage, + bool(gfx::Point viewport_point, + cc::ScrollDirection direction)); + MOCK_METHOD0(ScrollEnd, void()); + MOCK_METHOD0(FlingScrollBegin, cc::InputHandler::ScrollStatus()); + + virtual scoped_ptr<cc::SwapPromiseMonitor> + CreateLatencyInfoSwapPromiseMonitor(ui::LatencyInfo* latency) OVERRIDE { + return scoped_ptr<cc::SwapPromiseMonitor>(); + } + + virtual void BindToClient(cc::InputHandlerClient* client) OVERRIDE {} + + virtual void StartPageScaleAnimation(gfx::Vector2d target_offset, + bool anchor_point, + float page_scale, + base::TimeDelta duration) OVERRIDE {} + + virtual void NotifyCurrentFlingVelocity(gfx::Vector2dF velocity) OVERRIDE {} + virtual void MouseMoveAt(gfx::Point mouse_position) OVERRIDE {} + + MOCK_METHOD1(HaveTouchEventHandlersAt, + bool(gfx::Point point)); + + virtual void SetRootLayerScrollOffsetDelegate( + cc::LayerScrollOffsetDelegate* root_layer_scroll_offset_delegate) + OVERRIDE {} + + virtual void OnRootLayerDelegatedScrollOffsetChanged() OVERRIDE {} + + DISALLOW_COPY_AND_ASSIGN(MockInputHandler); +}; + +// A simple WebGestureCurve implementation that flings at a constant velocity +// indefinitely. +class FakeWebGestureCurve : public blink::WebGestureCurve { + public: + FakeWebGestureCurve(const blink::WebFloatPoint& velocity, + const blink::WebSize& cumulative_scroll) + : velocity_(velocity), cumulative_scroll_(cumulative_scroll) {} + + virtual ~FakeWebGestureCurve() {} + + // Returns false if curve has finished and can no longer be applied. + virtual bool apply(double time, blink::WebGestureCurveTarget* target) { + blink::WebSize displacement(velocity_.x * time, velocity_.y * time); + blink::WebFloatSize increment( + displacement.width - cumulative_scroll_.width, + displacement.height - cumulative_scroll_.height); + cumulative_scroll_ = displacement; + // scrollBy() could delete this curve if the animation is over, so don't + // touch any member variables after making that call. + target->scrollBy(increment); + return true; + } + + private: + blink::WebFloatPoint velocity_; + blink::WebSize cumulative_scroll_; + + DISALLOW_COPY_AND_ASSIGN(FakeWebGestureCurve); +}; + +class MockInputHandlerProxyClient + : public content::InputHandlerProxyClient { + public: + MockInputHandlerProxyClient() {} + virtual ~MockInputHandlerProxyClient() {} + + virtual void WillShutdown() OVERRIDE {} + + MOCK_METHOD1(TransferActiveWheelFlingAnimation, + void(const WebActiveWheelFlingParameters&)); + + virtual blink::WebGestureCurve* CreateFlingAnimationCurve( + int deviceSource, + const WebFloatPoint& velocity, + const WebSize& cumulative_scroll) OVERRIDE { + return new FakeWebGestureCurve(velocity, cumulative_scroll); + } + + virtual void DidOverscroll(const cc::DidOverscrollParams& params) {} + + private: + DISALLOW_COPY_AND_ASSIGN(MockInputHandlerProxyClient); +}; + +class InputHandlerProxyTest : public testing::Test { + public: + InputHandlerProxyTest() + : expected_disposition_(InputHandlerProxy::DID_HANDLE) { + input_handler_.reset( + new content::InputHandlerProxy(&mock_input_handler_)); + input_handler_->SetClient(&mock_client_); + } + + ~InputHandlerProxyTest() { + input_handler_.reset(); + } + +// This is defined as a macro because when an expectation is not satisfied the +// only output you get +// out of gmock is the line number that set the expectation. +#define VERIFY_AND_RESET_MOCKS() \ + do { \ + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); \ + testing::Mock::VerifyAndClearExpectations(&mock_client_); \ + } while (false) + + protected: + testing::StrictMock<MockInputHandler> mock_input_handler_; + scoped_ptr<content::InputHandlerProxy> input_handler_; + testing::StrictMock<MockInputHandlerProxyClient> mock_client_; + WebGestureEvent gesture_; + + InputHandlerProxy::EventDisposition expected_disposition_; +}; + +TEST_F(InputHandlerProxyTest, MouseWheelByPageMainThread) { + expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + WebMouseWheelEvent wheel; + wheel.type = WebInputEvent::MouseWheel; + wheel.scrollByPage = true; + + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(wheel)); + testing::Mock::VerifyAndClearExpectations(&mock_client_); +} + +TEST_F(InputHandlerProxyTest, GestureScrollStarted) { + // We shouldn't send any events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + + gesture_.type = WebInputEvent::GestureScrollBegin; + EXPECT_EQ(expected_disposition_,input_handler_->HandleInputEvent(gesture_)); + + // The event should not be marked as handled if scrolling is not possible. + expected_disposition_ = InputHandlerProxy::DROP_EVENT; + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GestureScrollUpdate; + gesture_.data.scrollUpdate.deltaY = + -40; // -Y means scroll down - i.e. in the +Y direction. + EXPECT_CALL(mock_input_handler_, + ScrollBy(testing::_, + testing::Property(&gfx::Vector2dF::y, testing::Gt(0)))) + .WillOnce(testing::Return(false)); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + // Mark the event as handled if scroll happens. + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GestureScrollUpdate; + gesture_.data.scrollUpdate.deltaY = + -40; // -Y means scroll down - i.e. in the +Y direction. + EXPECT_CALL(mock_input_handler_, + ScrollBy(testing::_, + testing::Property(&gfx::Vector2dF::y, testing::Gt(0)))) + .WillOnce(testing::Return(true)); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GestureScrollEnd; + gesture_.data.scrollUpdate.deltaY = 0; + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GestureScrollOnMainThread) { + // We should send all events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(::testing::_, ::testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollOnMainThread)); + + gesture_.type = WebInputEvent::GestureScrollBegin; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GestureScrollUpdate; + gesture_.data.scrollUpdate.deltaY = 40; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GestureScrollEnd; + gesture_.data.scrollUpdate.deltaY = 0; + EXPECT_CALL(mock_input_handler_, ScrollEnd()).WillOnce(testing::Return()); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GestureScrollIgnored) { + // We shouldn't handle the GestureScrollBegin. + // Instead, we should get a DROP_EVENT result, indicating + // that we could determine that there's nothing that could scroll or otherwise + // react to this gesture sequence and thus we should drop the whole gesture + // sequence on the floor, except for the ScrollEnd. + expected_disposition_ = InputHandlerProxy::DROP_EVENT; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollIgnored)); + + gesture_.type = WebInputEvent::GestureScrollBegin; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + gesture_.type = WebInputEvent::GestureScrollEnd; + EXPECT_CALL(mock_input_handler_, ScrollEnd()).WillOnce(testing::Return()); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GesturePinch) { + // We shouldn't send any events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GesturePinchBegin; + EXPECT_CALL(mock_input_handler_, PinchGestureBegin()); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GesturePinchUpdate; + gesture_.data.pinchUpdate.scale = 1.5; + gesture_.x = 7; + gesture_.y = 13; + EXPECT_CALL(mock_input_handler_, PinchGestureUpdate(1.5, gfx::Point(7, 13))); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GesturePinchUpdate; + gesture_.data.pinchUpdate.scale = 0.5; + gesture_.x = 9; + gesture_.y = 6; + EXPECT_CALL(mock_input_handler_, PinchGestureUpdate(.5, gfx::Point(9, 6))); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GesturePinchEnd; + EXPECT_CALL(mock_input_handler_, PinchGestureEnd()); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GesturePinchAfterScrollOnMainThread) { + // Scrolls will start by being sent to the main thread. + expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(::testing::_, ::testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollOnMainThread)); + + gesture_.type = WebInputEvent::GestureScrollBegin; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GestureScrollUpdate; + gesture_.data.scrollUpdate.deltaY = 40; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + // However, after the pinch gesture starts, they should go to the impl + // thread. + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GesturePinchBegin; + EXPECT_CALL(mock_input_handler_, PinchGestureBegin()); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GesturePinchUpdate; + gesture_.data.pinchUpdate.scale = 1.5; + gesture_.x = 7; + gesture_.y = 13; + EXPECT_CALL(mock_input_handler_, PinchGestureUpdate(1.5, gfx::Point(7, 13))); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GestureScrollUpdate; + gesture_.data.scrollUpdate.deltaY = + -40; // -Y means scroll down - i.e. in the +Y direction. + EXPECT_CALL(mock_input_handler_, + ScrollBy(testing::_, + testing::Property(&gfx::Vector2dF::y, testing::Gt(0)))) + .WillOnce(testing::Return(true)); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GesturePinchUpdate; + gesture_.data.pinchUpdate.scale = 0.5; + gesture_.x = 9; + gesture_.y = 6; + EXPECT_CALL(mock_input_handler_, PinchGestureUpdate(.5, gfx::Point(9, 6))); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GesturePinchEnd; + EXPECT_CALL(mock_input_handler_, PinchGestureEnd()); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + // After the pinch gesture ends, they should go to back to the main + // thread. + expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + gesture_.type = WebInputEvent::GestureScrollEnd; + gesture_.data.scrollUpdate.deltaY = 0; + EXPECT_CALL(mock_input_handler_, ScrollEnd()) + .WillOnce(testing::Return()); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GestureFlingStartedTouchpad) { + // We shouldn't send any events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + + gesture_.type = WebInputEvent::GestureFlingStart; + gesture_.data.flingStart.velocityX = 10; + gesture_.sourceDevice = WebGestureEvent::Touchpad; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + // Verify that a GestureFlingCancel during an animation cancels it. + gesture_.type = WebInputEvent::GestureFlingCancel; + gesture_.sourceDevice = WebGestureEvent::Touchpad; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GestureFlingOnMainThreadTouchpad) { + // We should send all events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollOnMainThread)); + + gesture_.type = WebInputEvent::GestureFlingStart; + gesture_.sourceDevice = WebGestureEvent::Touchpad; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + // Since we returned ScrollStatusOnMainThread from scrollBegin, ensure the + // input handler knows it's scrolling off the impl thread + ASSERT_FALSE(input_handler_->gesture_scroll_on_impl_thread_for_testing()); + + VERIFY_AND_RESET_MOCKS(); + + // Even if we didn't start a fling ourselves, we still need to send the cancel + // event to the widget. + gesture_.type = WebInputEvent::GestureFlingCancel; + gesture_.sourceDevice = WebGestureEvent::Touchpad; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GestureFlingIgnoredTouchpad) { + expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollIgnored)); + + gesture_.type = WebInputEvent::GestureFlingStart; + gesture_.sourceDevice = WebGestureEvent::Touchpad; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + expected_disposition_ = InputHandlerProxy::DROP_EVENT; + VERIFY_AND_RESET_MOCKS(); + + // Since the previous fling was ignored, we should also be dropping the next + // fling_cancel. + gesture_.type = WebInputEvent::GestureFlingCancel; + gesture_.sourceDevice = WebGestureEvent::Touchpad; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GestureFlingAnimatesTouchpad) { + // 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); + int modifiers = 7; + gesture_.data.flingStart.velocityX = fling_delta.x; + gesture_.data.flingStart.velocityY = fling_delta.y; + gesture_.sourceDevice = WebGestureEvent::Touchpad; + gesture_.x = fling_point.x; + gesture_.y = fling_point.y; + gesture_.globalX = fling_global_point.x; + gesture_.globalY = fling_global_point.y; + gesture_.modifiers = modifiers; + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + // 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_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .Times(0); + base::TimeTicks time = base::TimeTicks() + base::TimeDelta::FromSeconds(10); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // The second call should start scrolling in the -X direction. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_CALL(mock_input_handler_, + ScrollBy(testing::_, + testing::Property(&gfx::Vector2dF::x, testing::Lt(0)))) + .WillOnce(testing::Return(true)); + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // Let's say on the third call we hit a non-scrollable region. We should abort + // the fling and not scroll. + // 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_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollOnMainThread)); + EXPECT_CALL(mock_input_handler_, ScrollBy(testing::_, testing::_)).Times(0); + EXPECT_CALL(mock_input_handler_, ScrollEnd()).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 + // *) cumulativeScroll depends on the curve, but since we've animated in the + // -X direction the X value should be < 0 + 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::Field(&WebSize::width, testing::Gt(0)))))); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + testing::Mock::VerifyAndClearExpectations(&mock_client_); + + // Since we've aborted the fling, the next animation should be a no-op and + // should not result in another + // frame being requested. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()).Times(0); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .Times(0); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->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_)); +} + +TEST_F(InputHandlerProxyTest, GestureFlingTransferResetsTouchpad) { + // We shouldn't send any events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + // Start a gesture fling in the -X direction with zero Y movement. + gesture_.type = WebInputEvent::GestureFlingStart; + WebFloatPoint fling_delta = WebFloatPoint(1000, 0); + WebPoint fling_point = WebPoint(7, 13); + WebPoint fling_global_point = WebPoint(17, 23); + int modifiers = 1; + gesture_.data.flingStart.velocityX = fling_delta.x; + gesture_.data.flingStart.velocityY = fling_delta.y; + gesture_.sourceDevice = WebGestureEvent::Touchpad; + gesture_.x = fling_point.x; + gesture_.y = fling_point.y; + gesture_.globalX = fling_global_point.x; + gesture_.globalY = fling_global_point.y; + gesture_.modifiers = modifiers; + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // Start the fling animation at time 10. This shouldn't actually scroll, just + // establish a start time. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .Times(0); + base::TimeTicks time = base::TimeTicks() + base::TimeDelta::FromSeconds(10); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // The second call should start scrolling in the -X direction. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_CALL(mock_input_handler_, + ScrollBy(testing::_, + testing::Property(&gfx::Vector2dF::x, testing::Lt(0)))) + .WillOnce(testing::Return(true)); + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // Let's say on the third call we hit a non-scrollable region. We should abort + // the fling and not scroll. + // 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_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollOnMainThread)); + EXPECT_CALL(mock_input_handler_, ScrollBy(testing::_, testing::_)).Times(0); + EXPECT_CALL(mock_input_handler_, ScrollEnd()).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 + // *) cumulativeScroll depends on the curve, but since we've animated in the + // -X direction the X value should be < 0 + 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::Field(&WebSize::width, testing::Gt(0)))))); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + testing::Mock::VerifyAndClearExpectations(&mock_client_); + + // Since we've aborted the fling, the next animation should be a no-op and + // should not result in another + // frame being requested. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()).Times(0); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .Times(0); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // 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(); + input_handler_->MainThreadHasStoppedFlinging(); + + // Start a second gesture fling, this time in the +Y direction with no X. + gesture_.type = WebInputEvent::GestureFlingStart; + fling_delta = WebFloatPoint(0, -1000); + fling_point = WebPoint(95, 87); + fling_global_point = WebPoint(32, 71); + modifiers = 2; + gesture_.data.flingStart.velocityX = fling_delta.x; + gesture_.data.flingStart.velocityY = fling_delta.y; + gesture_.sourceDevice = WebGestureEvent::Touchpad; + gesture_.x = fling_point.x; + gesture_.y = fling_point.y; + gesture_.globalX = fling_global_point.x; + gesture_.globalY = fling_global_point.y; + gesture_.modifiers = modifiers; + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // Start the second fling animation at time 30. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .Times(0); + time = base::TimeTicks() + base::TimeDelta::FromSeconds(30); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // Tick the second fling once normally. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_CALL(mock_input_handler_, + ScrollBy(testing::_, + testing::Property(&gfx::Vector2dF::y, testing::Gt(0)))) + .WillOnce(testing::Return(true)); + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // Then abort the second fling. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollOnMainThread)); + EXPECT_CALL(mock_input_handler_, ScrollBy(testing::_, testing::_)).Times(0); + EXPECT_CALL(mock_input_handler_, ScrollEnd()).Times(0); + + // We should get parameters from the second fling, nothing from the first + // fling should "leak". + 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(30)), + testing::Field(&WebActiveWheelFlingParameters::cumulativeScroll, + testing::Field(&WebSize::height, testing::Lt(0)))))); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->Animate(time); +} + +TEST_F(InputHandlerProxyTest, GestureFlingStartedTouchscreen) { + // We shouldn't send any events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + gesture_.type = WebInputEvent::GestureScrollBegin; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, FlingScrollBegin()) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + + gesture_.type = WebInputEvent::GestureFlingStart; + gesture_.data.flingStart.velocityX = 10; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + + // Verify that a GestureFlingCancel during an animation cancels it. + gesture_.type = WebInputEvent::GestureFlingCancel; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GestureFlingOnMainThreadTouchscreen) { + // We should send all events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollOnMainThread)); + + gesture_.type = WebInputEvent::GestureScrollBegin; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, FlingScrollBegin()).Times(0); + + gesture_.type = WebInputEvent::GestureFlingStart; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + // Even if we didn't start a fling ourselves, we still need to send the cancel + // event to the widget. + gesture_.type = WebInputEvent::GestureFlingCancel; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GestureFlingIgnoredTouchscreen) { + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + + gesture_.type = WebInputEvent::GestureScrollBegin; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + expected_disposition_ = InputHandlerProxy::DROP_EVENT; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, FlingScrollBegin()) + .WillOnce(testing::Return(cc::InputHandler::ScrollIgnored)); + + gesture_.type = WebInputEvent::GestureFlingStart; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + // Even if we didn't start a fling ourselves, we still need to send the cancel + // event to the widget. + gesture_.type = WebInputEvent::GestureFlingCancel; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GestureFlingAnimatesTouchscreen) { + // We shouldn't send any events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + + gesture_.type = WebInputEvent::GestureScrollBegin; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + 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); + int modifiers = 7; + gesture_.data.flingStart.velocityX = fling_delta.x; + gesture_.data.flingStart.velocityY = fling_delta.y; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + gesture_.x = fling_point.x; + gesture_.y = fling_point.y; + gesture_.globalX = fling_global_point.x; + gesture_.globalY = fling_global_point.y; + gesture_.modifiers = modifiers; + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, FlingScrollBegin()) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + // 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_CALL(mock_input_handler_, ScheduleAnimation()); + base::TimeTicks time = base::TimeTicks() + base::TimeDelta::FromSeconds(10); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // The second call should start scrolling in the -X direction. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, + ScrollBy(testing::_, + testing::Property(&gfx::Vector2dF::x, testing::Lt(0)))) + .WillOnce(testing::Return(true)); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + gesture_.type = WebInputEvent::GestureFlingCancel; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, GestureFlingWithValidTimestamp) { + // We shouldn't send any events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + + gesture_.type = WebInputEvent::GestureScrollBegin; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + VERIFY_AND_RESET_MOCKS(); + + // On the fling start, we should schedule an animation but not actually start + // scrolling. + base::TimeDelta startTimeOffset = base::TimeDelta::FromMilliseconds(10); + gesture_.type = WebInputEvent::GestureFlingStart; + WebFloatPoint fling_delta = WebFloatPoint(1000, 0); + WebPoint fling_point = WebPoint(7, 13); + WebPoint fling_global_point = WebPoint(17, 23); + int modifiers = 7; + gesture_.timeStampSeconds = startTimeOffset.InSecondsF(); + gesture_.data.flingStart.velocityX = fling_delta.x; + gesture_.data.flingStart.velocityY = fling_delta.y; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + gesture_.x = fling_point.x; + gesture_.y = fling_point.y; + gesture_.globalX = fling_global_point.x; + gesture_.globalY = fling_global_point.y; + gesture_.modifiers = modifiers; + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, FlingScrollBegin()) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + // With a valid time stamp, the first animate call should skip start time + // initialization and immediately begin scroll update production. This reduces + // the likelihood of a hitch between the scroll preceding the fling and + // the first scroll generated by the fling. + // Scrolling should start in the -X direction. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, + ScrollBy(testing::_, + testing::Property(&gfx::Vector2dF::x, testing::Lt(0)))) + .WillOnce(testing::Return(true)); + base::TimeTicks time = base::TimeTicks() + 2 * startTimeOffset; + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + gesture_.type = WebInputEvent::GestureFlingCancel; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); +} + +TEST_F(InputHandlerProxyTest, + GestureScrollOnImplThreadFlagClearedAfterFling) { + // We shouldn't send any events to the widget for this gesture. + expected_disposition_ = InputHandlerProxy::DID_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + + gesture_.type = WebInputEvent::GestureScrollBegin; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + // After sending a GestureScrollBegin, the member variable + // |gesture_scroll_on_impl_thread_| should be true. + EXPECT_TRUE(input_handler_->gesture_scroll_on_impl_thread_for_testing()); + + 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); + int modifiers = 7; + gesture_.data.flingStart.velocityX = fling_delta.x; + gesture_.data.flingStart.velocityY = fling_delta.y; + gesture_.sourceDevice = WebGestureEvent::Touchscreen; + gesture_.x = fling_point.x; + gesture_.y = fling_point.y; + gesture_.globalX = fling_global_point.x; + gesture_.globalY = fling_global_point.y; + gesture_.modifiers = modifiers; + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, FlingScrollBegin()) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + // |gesture_scroll_on_impl_thread_| should still be true after + // a GestureFlingStart is sent. + EXPECT_TRUE(input_handler_->gesture_scroll_on_impl_thread_for_testing()); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + // 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_CALL(mock_input_handler_, ScheduleAnimation()); + base::TimeTicks time = base::TimeTicks() + base::TimeDelta::FromSeconds(10); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // The second call should start scrolling in the -X direction. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, + ScrollBy(testing::_, + testing::Property(&gfx::Vector2dF::x, testing::Lt(0)))) + .WillOnce(testing::Return(true)); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->Animate(time); + + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + gesture_.type = WebInputEvent::GestureFlingCancel; + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + + // |gesture_scroll_on_impl_thread_| should be false once + // the fling has finished (note no GestureScrollEnd has been sent). + EXPECT_TRUE(!input_handler_->gesture_scroll_on_impl_thread_for_testing()); +} + +TEST_F(InputHandlerProxyTest, GestureFlingStopsAtContentEdge) { + // 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, 1000); + gesture_.data.flingStart.velocityX = fling_delta.x; + gesture_.data.flingStart.velocityY = fling_delta.y; + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(gesture_)); + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // The first animate doesn't cause any scrolling. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + base::TimeTicks time = base::TimeTicks() + base::TimeDelta::FromSeconds(10); + input_handler_->Animate(time); + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // The second animate starts scrolling in the positive X and Y directions. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_CALL(mock_input_handler_, + ScrollBy(testing::_, + testing::Property(&gfx::Vector2dF::y, testing::Lt(0)))) + .WillOnce(testing::Return(true)); + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->Animate(time); + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // Simulate hitting the bottom content edge. + cc::DidOverscrollParams overscroll_params; + overscroll_params.accumulated_overscroll = gfx::Vector2dF(0, 100); + overscroll_params.current_fling_velocity = gfx::Vector2dF(0, 10); + input_handler_->DidOverscroll(overscroll_params); + + // The next call to animate will no longer scroll vertically. + EXPECT_CALL(mock_input_handler_, ScheduleAnimation()); + EXPECT_CALL(mock_input_handler_, ScrollBegin(testing::_, testing::_)) + .WillOnce(testing::Return(cc::InputHandler::ScrollStarted)); + EXPECT_CALL(mock_input_handler_, + ScrollBy(testing::_, + testing::Property(&gfx::Vector2dF::y, testing::Eq(0)))) + .WillOnce(testing::Return(true)); + EXPECT_CALL(mock_input_handler_, ScrollEnd()); + time += base::TimeDelta::FromMilliseconds(100); + input_handler_->Animate(time); + testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); +} + +TEST_F(InputHandlerProxyTest, MultiTouchPointHitTestNegative) { + // None of the three touch points fall in the touch region. So the event + // should be dropped. + expected_disposition_ = InputHandlerProxy::DROP_EVENT; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, + HaveTouchEventHandlersAt( + testing::Property(&gfx::Point::x, testing::Gt(0)))) + .WillOnce(testing::Return(false)); + EXPECT_CALL(mock_input_handler_, + HaveTouchEventHandlersAt( + testing::Property(&gfx::Point::x, testing::Lt(0)))) + .WillOnce(testing::Return(false)); + + WebTouchEvent touch; + touch.type = WebInputEvent::TouchStart; + + touch.touchesLength = 3; + touch.touches[0].state = WebTouchPoint::StateStationary; + touch.touches[0].screenPosition = WebPoint(); + touch.touches[0].position = WebPoint(); + + touch.touches[1].state = WebTouchPoint::StatePressed; + touch.touches[1].screenPosition = WebPoint(10, 10); + touch.touches[1].position = WebPoint(10, 10); + + touch.touches[2].state = WebTouchPoint::StatePressed; + touch.touches[2].screenPosition = WebPoint(-10, 10); + touch.touches[2].position = WebPoint(-10, 10); + + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(touch)); +} + +TEST_F(InputHandlerProxyTest, MultiTouchPointHitTestPositive) { + // One of the touch points is on a touch-region. So the event should be sent + // to the main thread. + expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + VERIFY_AND_RESET_MOCKS(); + + EXPECT_CALL(mock_input_handler_, + HaveTouchEventHandlersAt( + testing::Property(&gfx::Point::x, testing::Eq(0)))) + .WillOnce(testing::Return(false)); + EXPECT_CALL(mock_input_handler_, + HaveTouchEventHandlersAt( + testing::Property(&gfx::Point::x, testing::Gt(0)))) + .WillOnce(testing::Return(true)); + // Since the second touch point hits a touch-region, there should be no + // hit-testing for the third touch point. + + WebTouchEvent touch; + touch.type = WebInputEvent::TouchStart; + + touch.touchesLength = 3; + touch.touches[0].state = WebTouchPoint::StatePressed; + touch.touches[0].screenPosition = WebPoint(); + touch.touches[0].position = WebPoint(); + + touch.touches[1].state = WebTouchPoint::StatePressed; + touch.touches[1].screenPosition = WebPoint(10, 10); + touch.touches[1].position = WebPoint(10, 10); + + touch.touches[2].state = WebTouchPoint::StatePressed; + touch.touches[2].screenPosition = WebPoint(-10, 10); + touch.touches[2].position = WebPoint(-10, 10); + + EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(touch)); +} + +} // namespace +} // namespace content diff --git a/chromium/content/renderer/input/input_handler_wrapper.cc b/chromium/content/renderer/input/input_handler_wrapper.cc new file mode 100644 index 00000000000..14e23ebf3ad --- /dev/null +++ b/chromium/content/renderer/input/input_handler_wrapper.cc @@ -0,0 +1,58 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/renderer/input/input_handler_wrapper.h" + +#include "base/message_loop/message_loop_proxy.h" +#include "content/renderer/input/input_event_filter.h" +#include "content/renderer/input/input_handler_manager.h" +#include "third_party/WebKit/public/platform/Platform.h" + +namespace content { + +InputHandlerWrapper::InputHandlerWrapper( + InputHandlerManager* input_handler_manager, + int routing_id, + const scoped_refptr<base::MessageLoopProxy>& main_loop, + const base::WeakPtr<cc::InputHandler>& input_handler, + const base::WeakPtr<RenderViewImpl>& render_view_impl) + : input_handler_manager_(input_handler_manager), + routing_id_(routing_id), + input_handler_proxy_(input_handler.get()), + main_loop_(main_loop), + render_view_impl_(render_view_impl) { + DCHECK(input_handler); + input_handler_proxy_.SetClient(this); +} + +InputHandlerWrapper::~InputHandlerWrapper() { + input_handler_proxy_.SetClient(NULL); +} + +void InputHandlerWrapper::TransferActiveWheelFlingAnimation( + const blink::WebActiveWheelFlingParameters& params) { + main_loop_->PostTask( + FROM_HERE, + base::Bind(&RenderViewImpl::TransferActiveWheelFlingAnimation, + render_view_impl_, + params)); +} + +void InputHandlerWrapper::WillShutdown() { + input_handler_manager_->RemoveInputHandler(routing_id_); +} + +blink::WebGestureCurve* InputHandlerWrapper::CreateFlingAnimationCurve( + int deviceSource, + const blink::WebFloatPoint& velocity, + const blink::WebSize& cumulative_scroll) { + return blink::Platform::current()->createFlingAnimationCurve( + deviceSource, velocity, cumulative_scroll); +} + +void InputHandlerWrapper::DidOverscroll(const cc::DidOverscrollParams& params) { + input_handler_manager_->DidOverscroll(routing_id_, params); +} + +} // namespace content diff --git a/chromium/content/renderer/input/input_handler_wrapper.h b/chromium/content/renderer/input/input_handler_wrapper.h new file mode 100644 index 00000000000..562012ace1d --- /dev/null +++ b/chromium/content/renderer/input/input_handler_wrapper.h @@ -0,0 +1,52 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_RENDERER_INPUT_INPUT_HANDLER_WRAPPER_H_ +#define CONTENT_RENDERER_INPUT_INPUT_HANDLER_WRAPPER_H_ + +#include "base/memory/weak_ptr.h" +#include "content/renderer/input/input_handler_manager.h" +#include "content/renderer/input/input_handler_proxy.h" +#include "content/renderer/input/input_handler_proxy_client.h" + +namespace content { + +// This class lives on the compositor thread. +class InputHandlerWrapper : public InputHandlerProxyClient { + public: + InputHandlerWrapper(InputHandlerManager* input_handler_manager, + int routing_id, + const scoped_refptr<base::MessageLoopProxy>& main_loop, + const base::WeakPtr<cc::InputHandler>& input_handler, + const base::WeakPtr<RenderViewImpl>& render_view_impl); + virtual ~InputHandlerWrapper(); + + int routing_id() const { return routing_id_; } + InputHandlerProxy* input_handler_proxy() { return &input_handler_proxy_; } + + // InputHandlerProxyClient implementation. + virtual void WillShutdown() OVERRIDE; + virtual void TransferActiveWheelFlingAnimation( + const blink::WebActiveWheelFlingParameters& params) OVERRIDE; + virtual blink::WebGestureCurve* CreateFlingAnimationCurve( + int deviceSource, + const blink::WebFloatPoint& velocity, + const blink::WebSize& cumulativeScroll) OVERRIDE; + virtual void DidOverscroll(const cc::DidOverscrollParams& params) OVERRIDE; + + private: + InputHandlerManager* input_handler_manager_; + int routing_id_; + InputHandlerProxy input_handler_proxy_; + scoped_refptr<base::MessageLoopProxy> main_loop_; + + // Can only be accessed on the main thread. + base::WeakPtr<RenderViewImpl> render_view_impl_; + + DISALLOW_COPY_AND_ASSIGN(InputHandlerWrapper); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_INPUT_INPUT_HANDLER_WRAPPER_H_ |