1 // Copyright 2017 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 <memory>
6 #include <string>
7 #include <utility>
8 #include <vector>
9
10 #include "base/bind.h"
11 #include "base/json/json_reader.h"
12 #include "base/run_loop.h"
13 #include "base/test/test_timeouts.h"
14 #include "build/build_config.h"
15 #include "content/browser/renderer_host/input/synthetic_gesture.h"
16 #include "content/browser/renderer_host/input/synthetic_gesture_controller.h"
17 #include "content/browser/renderer_host/input/synthetic_gesture_target.h"
18 #include "content/browser/renderer_host/input/synthetic_smooth_move_gesture.h"
19 #include "content/browser/renderer_host/input/synthetic_tap_gesture.h"
20 #include "content/browser/renderer_host/render_widget_host_factory.h"
21 #include "content/browser/renderer_host/render_widget_host_impl.h"
22 #include "content/browser/web_contents/web_contents_impl.h"
23 #include "content/common/input/synthetic_gesture_params.h"
24 #include "content/public/browser/render_view_host.h"
25 #include "content/public/browser/render_widget_host_view.h"
26 #include "content/public/browser/tracing_controller.h"
27 #include "content/public/test/browser_test_utils.h"
28 #include "content/public/test/content_browser_test.h"
29 #include "content/public/test/content_browser_test_utils.h"
30 #include "content/shell/browser/shell.h"
31 #include "mojo/public/cpp/bindings/pending_remote.h"
32 #include "testing/gmock/include/gmock/gmock.h"
33
34 namespace {
35
36 const char kDataURL[] =
37 "data:text/html;charset=utf-8,"
38 "<!DOCTYPE html>"
39 "<html>"
40 "<head>"
41 "<title>Mouse event trace events reported.</title>"
42 "<script>"
43 " let i=0;"
44 " document.addEventListener('mousemove', () => {"
45 " var end = performance.now() + 20;"
46 " while(performance.now() < end);"
47 " document.body.style.backgroundColor = 'rgb(' + (i++) + ',0,0)'"
48 " });"
49 " document.addEventListener('wheel', (e) => {"
50 " if (!e.cancelable)"
51 " return;"
52 " var end = performance.now() + 50;"
53 " while(performance.now() < end);"
54 " });"
55 "</script>"
56 "<style>"
57 "body {"
58 " height:3000px;"
59 // Prevent text selection logic from triggering, as it makes the test flaky.
60 " user-select: none;"
61 "}"
62 "</style>"
63 "</head>"
64 "<body>"
65 "</body>"
66 "</html>";
67
68 } // namespace
69
70 namespace content {
71
72 // This class listens for terminated latency info events. It listens
73 // for both the mouse event ack and the gpu swap buffers event since
74 // the event could occur in either.
75 class TracingRenderWidgetHost : public RenderWidgetHostImpl {
76 public:
TracingRenderWidgetHost(RenderWidgetHostDelegate * delegate,RenderProcessHost * process,int32_t routing_id,mojo::PendingRemote<mojom::Widget> widget,bool hidden)77 TracingRenderWidgetHost(RenderWidgetHostDelegate* delegate,
78 RenderProcessHost* process,
79 int32_t routing_id,
80 mojo::PendingRemote<mojom::Widget> widget,
81 bool hidden)
82 : RenderWidgetHostImpl(delegate,
83 process,
84 routing_id,
85 std::move(widget),
86 hidden,
87 std::make_unique<FrameTokenMessageQueue>()) {
88 }
89
OnMouseEventAck(const MouseEventWithLatencyInfo & event,InputEventAckSource ack_source,InputEventAckState ack_result)90 void OnMouseEventAck(const MouseEventWithLatencyInfo& event,
91 InputEventAckSource ack_source,
92 InputEventAckState ack_result) override {
93 RenderWidgetHostImpl::OnMouseEventAck(event, ack_source, ack_result);
94 }
95
96 private:
97 };
98
99 class TracingRenderWidgetHostFactory : public RenderWidgetHostFactory {
100 public:
TracingRenderWidgetHostFactory()101 TracingRenderWidgetHostFactory() {
102 RenderWidgetHostFactory::RegisterFactory(this);
103 }
104
~TracingRenderWidgetHostFactory()105 ~TracingRenderWidgetHostFactory() override {
106 RenderWidgetHostFactory::UnregisterFactory();
107 }
108
CreateRenderWidgetHost(RenderWidgetHostDelegate * delegate,RenderProcessHost * process,int32_t routing_id,mojo::PendingRemote<mojom::Widget> widget_interface,bool hidden)109 std::unique_ptr<RenderWidgetHostImpl> CreateRenderWidgetHost(
110 RenderWidgetHostDelegate* delegate,
111 RenderProcessHost* process,
112 int32_t routing_id,
113 mojo::PendingRemote<mojom::Widget> widget_interface,
114 bool hidden) override {
115 return std::make_unique<TracingRenderWidgetHost>(
116 delegate, process, routing_id, std::move(widget_interface), hidden);
117 }
118
119 private:
120 DISALLOW_COPY_AND_ASSIGN(TracingRenderWidgetHostFactory);
121 };
122
123 class MouseLatencyBrowserTest : public ContentBrowserTest {
124 public:
MouseLatencyBrowserTest()125 MouseLatencyBrowserTest() {}
~MouseLatencyBrowserTest()126 ~MouseLatencyBrowserTest() override {}
127
GetWidgetHost()128 RenderWidgetHostImpl* GetWidgetHost() {
129 return RenderWidgetHostImpl::From(
130 shell()->web_contents()->GetRenderViewHost()->GetWidget());
131 }
132
OnSyntheticGestureCompleted(SyntheticGesture::Result result)133 void OnSyntheticGestureCompleted(SyntheticGesture::Result result) {
134 EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result);
135 runner_->Quit();
136 }
137
OnTraceDataCollected(std::unique_ptr<std::string> trace_data_string)138 void OnTraceDataCollected(std::unique_ptr<std::string> trace_data_string) {
139 std::unique_ptr<base::Value> trace_data =
140 base::JSONReader::ReadDeprecated(*trace_data_string);
141 ASSERT_TRUE(trace_data);
142 trace_data_ = trace_data->Clone();
143 runner_->Quit();
144 }
145
146 protected:
LoadURL()147 void LoadURL() {
148 const GURL data_url(kDataURL);
149 EXPECT_TRUE(NavigateToURL(shell(), data_url));
150
151 RenderWidgetHostImpl* host = GetWidgetHost();
152 host->GetView()->SetSize(gfx::Size(400, 400));
153 }
154
155 // Generate mouse events for a synthetic click at |point|.
DoSyncClick(const gfx::PointF & position)156 void DoSyncClick(const gfx::PointF& position) {
157 SyntheticTapGestureParams params;
158 params.gesture_source_type = SyntheticGestureParams::MOUSE_INPUT;
159 params.position = position;
160 params.duration_ms = 100;
161 std::unique_ptr<SyntheticTapGesture> gesture(
162 new SyntheticTapGesture(params));
163
164 GetWidgetHost()->QueueSyntheticGesture(
165 std::move(gesture),
166 base::BindOnce(&MouseLatencyBrowserTest::OnSyntheticGestureCompleted,
167 base::Unretained(this)));
168
169 // Runs until we get the OnSyntheticGestureCompleted callback
170 runner_ = std::make_unique<base::RunLoop>();
171 runner_->Run();
172 }
173
174 // Generate mouse events drag from |position|.
DoSyncCoalescedMoves(const gfx::PointF position,const gfx::Vector2dF & delta1,const gfx::Vector2dF & delta2)175 void DoSyncCoalescedMoves(const gfx::PointF position,
176 const gfx::Vector2dF& delta1,
177 const gfx::Vector2dF& delta2) {
178 SyntheticSmoothMoveGestureParams params;
179 params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_DRAG_INPUT;
180 params.start_point.SetPoint(position.x(), position.y());
181 params.distances.push_back(delta1);
182 params.distances.push_back(delta2);
183
184 std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
185 new SyntheticSmoothMoveGesture(params));
186
187 GetWidgetHost()->QueueSyntheticGesture(
188 std::move(gesture),
189 base::BindOnce(&MouseLatencyBrowserTest::OnSyntheticGestureCompleted,
190 base::Unretained(this)));
191
192 // Runs until we get the OnSyntheticGestureCompleted callback
193 runner_ = std::make_unique<base::RunLoop>();
194 runner_->Run();
195 }
196
197 // Generate mouse wheel scroll.
DoSyncCoalescedMouseWheel(const gfx::PointF position,const gfx::Vector2dF & delta)198 void DoSyncCoalescedMouseWheel(const gfx::PointF position,
199 const gfx::Vector2dF& delta) {
200 SyntheticSmoothScrollGestureParams params;
201 params.gesture_source_type = SyntheticGestureParams::MOUSE_INPUT;
202 params.anchor = position;
203 params.distances.push_back(delta);
204
205 GetWidgetHost()->QueueSyntheticGesture(
206 SyntheticGesture::Create(params),
207 base::BindOnce(&MouseLatencyBrowserTest::OnSyntheticGestureCompleted,
208 base::Unretained(this)));
209
210 // Runs until we get the OnSyntheticGestureCompleted callback
211 runner_ = std::make_unique<base::RunLoop>();
212 runner_->Run();
213 }
214
StartTracing()215 void StartTracing() {
216 base::trace_event::TraceConfig trace_config(
217 "{"
218 "\"enable_argument_filter\":false,"
219 "\"enable_systrace\":false,"
220 "\"included_categories\":["
221 "\"latencyInfo\""
222 "],"
223 "\"record_mode\":\"record-until-full\""
224 "}");
225
226 base::RunLoop run_loop;
227 ASSERT_TRUE(TracingController::GetInstance()->StartTracing(
228 trace_config, run_loop.QuitClosure()));
229 run_loop.Run();
230 }
231
StopTracing()232 const base::Value& StopTracing() {
233 bool success = TracingController::GetInstance()->StopTracing(
234 TracingController::CreateStringEndpoint(
235 base::BindOnce(&MouseLatencyBrowserTest::OnTraceDataCollected,
236 base::Unretained(this))));
237 EXPECT_TRUE(success);
238
239 // Runs until we get the OnTraceDataCollected callback, which populates
240 // trace_data_;
241 runner_ = std::make_unique<base::RunLoop>();
242 runner_->Run();
243 return trace_data_;
244 }
245
ShowTraceEventsWithId(const std::string & id_to_show,const base::ListValue * traceEvents)246 std::string ShowTraceEventsWithId(const std::string& id_to_show,
247 const base::ListValue* traceEvents) {
248 std::stringstream stream;
249 for (size_t i = 0; i < traceEvents->GetSize(); ++i) {
250 const base::DictionaryValue* traceEvent;
251 if (!traceEvents->GetDictionary(i, &traceEvent))
252 continue;
253
254 std::string id;
255 if (!traceEvent->GetString("id", &id))
256 continue;
257
258 if (id == id_to_show)
259 stream << *traceEvent;
260 }
261 return stream.str();
262 }
263
AssertTraceIdsBeginAndEnd(const base::Value & trace_data,const std::string & trace_event_name)264 void AssertTraceIdsBeginAndEnd(const base::Value& trace_data,
265 const std::string& trace_event_name) {
266 const base::DictionaryValue* trace_data_dict;
267 ASSERT_TRUE(trace_data.GetAsDictionary(&trace_data_dict));
268
269 const base::ListValue* traceEvents;
270 ASSERT_TRUE(trace_data_dict->GetList("traceEvents", &traceEvents));
271
272 std::map<std::string, int> trace_ids;
273
274 for (size_t i = 0; i < traceEvents->GetSize(); ++i) {
275 const base::DictionaryValue* traceEvent;
276 ASSERT_TRUE(traceEvents->GetDictionary(i, &traceEvent));
277
278 std::string name;
279 ASSERT_TRUE(traceEvent->GetString("name", &name));
280
281 if (name != trace_event_name)
282 continue;
283
284 std::string id;
285 if (traceEvent->GetString("id", &id))
286 ++trace_ids[id];
287 }
288
289 for (auto i : trace_ids) {
290 // Each trace id should show up once for the begin, and once for the end.
291 EXPECT_EQ(2, i.second) << ShowTraceEventsWithId(i.first, traceEvents);
292 }
293 }
294
295 private:
296 std::unique_ptr<base::RunLoop> runner_;
297 base::Value trace_data_;
298 TracingRenderWidgetHostFactory widget_factory_;
299
300 DISALLOW_COPY_AND_ASSIGN(MouseLatencyBrowserTest);
301 };
302
303 // Ensures that LatencyInfo async slices are reported correctly for MouseUp and
304 // MouseDown events in the case where no swap is generated.
305 // Disabled on Android because we don't support synthetic mouse input on
306 // Android (crbug.com/723618).
307 // Disabled on Windows due to flakyness (https://crbug.com/800303).
308 // Disabled on Linux due to flakyness (https://crbug.com/815363).
309 #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_LINUX)
310 #define MAYBE_MouseDownAndUpRecordedWithoutSwap \
311 DISABLED_MouseDownAndUpRecordedWithoutSwap
312 #else
313 #define MAYBE_MouseDownAndUpRecordedWithoutSwap \
314 MouseDownAndUpRecordedWithoutSwap
315 #endif
IN_PROC_BROWSER_TEST_F(MouseLatencyBrowserTest,MAYBE_MouseDownAndUpRecordedWithoutSwap)316 IN_PROC_BROWSER_TEST_F(MouseLatencyBrowserTest,
317 MAYBE_MouseDownAndUpRecordedWithoutSwap) {
318 LoadURL();
319
320 auto filter = std::make_unique<InputMsgWatcher>(
321 GetWidgetHost(), blink::WebInputEvent::kMouseUp);
322 StartTracing();
323 DoSyncClick(gfx::PointF(100, 100));
324 EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
325 filter->GetAckStateWaitIfNecessary());
326 const base::Value& trace_data = StopTracing();
327
328 const base::DictionaryValue* trace_data_dict;
329 trace_data.GetAsDictionary(&trace_data_dict);
330 ASSERT_TRUE(trace_data.GetAsDictionary(&trace_data_dict));
331
332 const base::ListValue* traceEvents;
333 ASSERT_TRUE(trace_data_dict->GetList("traceEvents", &traceEvents));
334
335 std::vector<std::string> trace_event_names;
336
337 for (size_t i = 0; i < traceEvents->GetSize(); ++i) {
338 const base::DictionaryValue* traceEvent;
339 ASSERT_TRUE(traceEvents->GetDictionary(i, &traceEvent));
340
341 std::string name;
342 ASSERT_TRUE(traceEvent->GetString("name", &name));
343
344 if (name != "InputLatency::MouseUp" && name != "InputLatency::MouseDown")
345 continue;
346 trace_event_names.push_back(name);
347 }
348
349 // We see two events per async slice, a begin and an end.
350 EXPECT_THAT(trace_event_names,
351 testing::UnorderedElementsAre(
352 "InputLatency::MouseDown", "InputLatency::MouseDown",
353 "InputLatency::MouseUp", "InputLatency::MouseUp"));
354 }
355
356 // Ensures that LatencyInfo async slices are reported correctly for MouseMove
357 // events in the case where events are coalesced. (crbug.com/771165).
358 // Disabled on Android because we don't support synthetic mouse input on Android
359 // (crbug.com/723618).
360 // http://crbug.com/801629 : Flaky on Linux and Windows, and Mac with
361 // --enable-features=VizDisplayCompositor
362 #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_LINUX)
363 #define MAYBE_CoalescedMouseMovesCorrectlyTerminated \
364 DISABLED_CoalescedMouseMovesCorrectlyTerminated
365 #else
366 #define MAYBE_CoalescedMouseMovesCorrectlyTerminated \
367 CoalescedMouseMovesCorrectlyTerminated
368 #endif
IN_PROC_BROWSER_TEST_F(MouseLatencyBrowserTest,MAYBE_CoalescedMouseMovesCorrectlyTerminated)369 IN_PROC_BROWSER_TEST_F(MouseLatencyBrowserTest,
370 MAYBE_CoalescedMouseMovesCorrectlyTerminated) {
371 LoadURL();
372
373 StartTracing();
374 DoSyncCoalescedMoves(gfx::PointF(100, 100), gfx::Vector2dF(150, 150),
375 gfx::Vector2dF(250, 250));
376 // The following wait is the upper bound for gpu swap completed callback. It
377 // is two frames to account for double buffering.
378 MainThreadFrameObserver observer(RenderWidgetHostImpl::From(
379 shell()->web_contents()->GetRenderViewHost()->GetWidget()));
380 observer.Wait();
381 observer.Wait();
382
383 const base::Value& trace_data = StopTracing();
384
385 AssertTraceIdsBeginAndEnd(trace_data, "InputLatency::MouseMove");
386 }
387
388 // TODO(https://crbug.com/923627): This is flaky on multiple platforms.
IN_PROC_BROWSER_TEST_F(MouseLatencyBrowserTest,DISABLED_CoalescedMouseWheelsCorrectlyTerminated)389 IN_PROC_BROWSER_TEST_F(MouseLatencyBrowserTest,
390 DISABLED_CoalescedMouseWheelsCorrectlyTerminated) {
391 LoadURL();
392
393 StartTracing();
394 DoSyncCoalescedMouseWheel(gfx::PointF(100, 100), gfx::Vector2dF(0, -100));
395 const base::Value& trace_data = StopTracing();
396
397 AssertTraceIdsBeginAndEnd(trace_data, "InputLatency::MouseWheel");
398 }
399
400 } // namespace content
401