1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/auto_reset.h"
6#include "base/command_line.h"
7#include "base/run_loop.h"
8#include "content/browser/gpu/compositor_util.h"
9#include "content/browser/renderer_host/render_widget_host_impl.h"
10#include "content/browser/web_contents/web_contents_impl.h"
11#include "content/common/input/synthetic_web_input_event_builders.h"
12#include "content/common/input_messages.h"
13#include "content/public/browser/browser_message_filter.h"
14#include "content/public/browser/render_view_host.h"
15#include "content/public/browser/render_widget_host_view.h"
16#include "content/public/common/content_switches.h"
17#include "content/public/test/content_browser_test.h"
18#include "content/public/test/content_browser_test_utils.h"
19#include "content/shell/browser/shell.h"
20#include "third_party/WebKit/public/web/WebInputEvent.h"
21#include "ui/events/event_switches.h"
22#include "ui/events/latency_info.h"
23
24using blink::WebInputEvent;
25
26namespace {
27
28void GiveItSomeTime() {
29  base::RunLoop run_loop;
30  base::MessageLoop::current()->PostDelayedTask(
31      FROM_HERE,
32      run_loop.QuitClosure(),
33      base::TimeDelta::FromMilliseconds(10));
34  run_loop.Run();
35}
36
37const char kTouchEventDataURL[] =
38    "data:text/html;charset=utf-8,"
39    "<body onload='setup();'>"
40    "<div id='first'></div><div id='second'></div><div id='third'></div>"
41    "<style>"
42    "  #first {"
43    "    position: absolute;"
44    "    width: 100px;"
45    "    height: 100px;"
46    "    top: 0px;"
47    "    left: 0px;"
48    "    background-color: green;"
49    "    -webkit-transform: translate3d(0, 0, 0);"
50    "  }"
51    "  #second {"
52    "    position: absolute;"
53    "    width: 100px;"
54    "    height: 100px;"
55    "    top: 0px;"
56    "    left: 110px;"
57    "    background-color: blue;"
58    "    -webkit-transform: translate3d(0, 0, 0);"
59    "  }"
60    "  #third {"
61    "    position: absolute;"
62    "    width: 100px;"
63    "    height: 100px;"
64    "    top: 110px;"
65    "    left: 0px;"
66    "    background-color: yellow;"
67    "    -webkit-transform: translate3d(0, 0, 0);"
68    "  }"
69    "</style>"
70    "<script>"
71    "  function setup() {"
72    "    second.ontouchstart = function() {};"
73    "    third.ontouchstart = function(e) {"
74    "      e.preventDefault();"
75    "    };"
76    "  }"
77    "</script>";
78
79}  // namespace
80
81namespace content {
82
83class InputEventMessageFilter : public BrowserMessageFilter {
84 public:
85  InputEventMessageFilter()
86      : BrowserMessageFilter(InputMsgStart),
87        type_(WebInputEvent::Undefined),
88        state_(INPUT_EVENT_ACK_STATE_UNKNOWN) {}
89
90  void WaitForAck(WebInputEvent::Type type) {
91    base::RunLoop run_loop;
92    base::AutoReset<base::Closure> reset_quit(&quit_, run_loop.QuitClosure());
93    base::AutoReset<WebInputEvent::Type> reset_type(&type_, type);
94    run_loop.Run();
95  }
96
97  InputEventAckState last_ack_state() const { return state_; }
98
99 protected:
100  virtual ~InputEventMessageFilter() {}
101
102 private:
103  void ReceivedEventAck(WebInputEvent::Type type, InputEventAckState state) {
104    if (type_ == type) {
105      state_ = state;
106      quit_.Run();
107    }
108  }
109
110  // BrowserMessageFilter:
111  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
112    if (message.type() == InputHostMsg_HandleInputEvent_ACK::ID) {
113      InputHostMsg_HandleInputEvent_ACK::Param params;
114      InputHostMsg_HandleInputEvent_ACK::Read(&message, &params);
115      WebInputEvent::Type type = params.a.type;
116      InputEventAckState ack = params.a.state;
117      BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
118          base::Bind(&InputEventMessageFilter::ReceivedEventAck,
119                     this, type, ack));
120    }
121    return false;
122  }
123
124  base::Closure quit_;
125  WebInputEvent::Type type_;
126  InputEventAckState state_;
127
128  DISALLOW_COPY_AND_ASSIGN(InputEventMessageFilter);
129};
130
131class TouchInputBrowserTest : public ContentBrowserTest {
132 public:
133  TouchInputBrowserTest() {}
134  virtual ~TouchInputBrowserTest() {}
135
136  RenderWidgetHostImpl* GetWidgetHost() {
137    return RenderWidgetHostImpl::From(shell()->web_contents()->
138                                          GetRenderViewHost());
139  }
140
141  InputEventMessageFilter* filter() { return filter_.get(); }
142
143 protected:
144  void LoadURLAndAddFilter() {
145    const GURL data_url(kTouchEventDataURL);
146    NavigateToURL(shell(), data_url);
147
148    WebContentsImpl* web_contents =
149        static_cast<WebContentsImpl*>(shell()->web_contents());
150    RenderWidgetHostImpl* host =
151        RenderWidgetHostImpl::From(web_contents->GetRenderViewHost());
152    host->GetView()->SetSize(gfx::Size(400, 400));
153
154    // The page is loaded in the renderer, wait for a new frame to arrive.
155    while (!host->ScheduleComposite())
156      GiveItSomeTime();
157
158    filter_ = new InputEventMessageFilter();
159    host->GetProcess()->AddFilter(filter_.get());
160  }
161
162  virtual void SetUpCommandLine(CommandLine* cmd) OVERRIDE {
163    cmd->AppendSwitchASCII(switches::kTouchEvents,
164                           switches::kTouchEventsEnabled);
165  }
166
167  scoped_refptr<InputEventMessageFilter> filter_;
168};
169
170#if defined(OS_MACOSX)
171// TODO(ccameron): Failing on mac: crbug.com/346363
172#define MAYBE_TouchNoHandler DISABLED_TouchNoHandler
173#else
174#define MAYBE_TouchNoHandler TouchNoHandler
175#endif
176IN_PROC_BROWSER_TEST_F(TouchInputBrowserTest, MAYBE_TouchNoHandler) {
177  LoadURLAndAddFilter();
178  SyntheticWebTouchEvent touch;
179
180  // A press on |first| should be acked with NO_CONSUMER_EXISTS since there is
181  // no touch-handler on it.
182  touch.PressPoint(25, 25);
183  GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo());
184  filter()->WaitForAck(WebInputEvent::TouchStart);
185
186  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
187            filter()->last_ack_state());
188
189  // If a touch-press is acked with NO_CONSUMER_EXISTS, then subsequent
190  // touch-points don't need to be dispatched until the touch point is released.
191  touch.ReleasePoint(0);
192  GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo());
193  touch.ResetPoints();
194}
195
196IN_PROC_BROWSER_TEST_F(TouchInputBrowserTest, TouchHandlerNoConsume) {
197  LoadURLAndAddFilter();
198  SyntheticWebTouchEvent touch;
199
200  // Press on |second| should be acked with NOT_CONSUMED since there is a
201  // touch-handler on |second|, but it doesn't consume the event.
202  touch.PressPoint(125, 25);
203  GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo());
204  filter()->WaitForAck(WebInputEvent::TouchStart);
205  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, filter()->last_ack_state());
206
207  touch.ReleasePoint(0);
208  GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo());
209  filter()->WaitForAck(WebInputEvent::TouchEnd);
210  touch.ResetPoints();
211}
212
213IN_PROC_BROWSER_TEST_F(TouchInputBrowserTest, TouchHandlerConsume) {
214  LoadURLAndAddFilter();
215  SyntheticWebTouchEvent touch;
216
217  // Press on |third| should be acked with CONSUMED since the touch-handler on
218  // |third| consimes the event.
219  touch.PressPoint(25, 125);
220  GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo());
221  filter()->WaitForAck(WebInputEvent::TouchStart);
222  EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, filter()->last_ack_state());
223
224  touch.ReleasePoint(0);
225  GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo());
226  filter()->WaitForAck(WebInputEvent::TouchEnd);
227}
228
229#if defined(OS_MACOSX)
230// TODO(ccameron): Failing on mac: crbug.com/346363
231#define MAYBE_MultiPointTouchPress DISABLED_MultiPointTouchPress
232#else
233#define MAYBE_MultiPointTouchPress MultiPointTouchPress
234#endif
235IN_PROC_BROWSER_TEST_F(TouchInputBrowserTest, MAYBE_MultiPointTouchPress) {
236  LoadURLAndAddFilter();
237  SyntheticWebTouchEvent touch;
238
239  // Press on |first|, which sould be acked with NO_CONSUMER_EXISTS. Then press
240  // on |third|. That point should be acked with CONSUMED.
241  touch.PressPoint(25, 25);
242  GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo());
243  filter()->WaitForAck(WebInputEvent::TouchStart);
244  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS,
245            filter()->last_ack_state());
246
247  touch.PressPoint(25, 125);
248  GetWidgetHost()->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo());
249  filter()->WaitForAck(WebInputEvent::TouchStart);
250  EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, filter()->last_ack_state());
251}
252
253}  // namespace content
254