// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #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/gpu/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(&record.event_data[0]); } void Clear() { records_.clear(); } InputEventAckState HandleInputEvent(int routing_id, const WebInputEvent* event, const 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(event); event_data.assign(ptr, ptr + event->size); } std::vector event_data; }; InputEventFilter* filter_; bool handle_events_; bool send_to_widget_; std::vector 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 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& 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 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 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 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