1 // Copyright 2019 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/bind.h"
6 #include "base/callback.h"
7 #include "base/run_loop.h"
8 #include "base/test/test_timeouts.h"
9 #include "build/build_config.h"
10 #include "cc/base/switches.h"
11 #include "content/browser/renderer_host/input/synthetic_gesture.h"
12 #include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h"
13 #include "content/browser/renderer_host/render_widget_host_impl.h"
14 #include "content/browser/web_contents/web_contents_impl.h"
15 #include "content/common/input/synthetic_gesture_params.h"
16 #include "content/common/input/synthetic_smooth_scroll_gesture_params.h"
17 #include "content/public/browser/render_view_host.h"
18 #include "content/public/browser/render_widget_host.h"
19 #include "content/public/browser/render_widget_host_view.h"
20 #include "content/public/browser/web_contents.h"
21 #include "content/public/test/browser_test.h"
22 #include "content/public/test/browser_test_utils.h"
23 #include "content/public/test/content_browser_test.h"
24 #include "content/public/test/content_browser_test_utils.h"
25 #include "content/public/test/hit_test_region_observer.h"
26 #include "content/public/test/test_utils.h"
27 #include "content/shell/browser/shell.h"
28 #include "third_party/blink/public/common/input/web_input_event.h"
29
30 namespace content {
31
32 class SyntheticInputTest : public ContentBrowserTest {
33 public:
SyntheticInputTest()34 SyntheticInputTest() {}
35
SetUpCommandLine(base::CommandLine * command_line)36 void SetUpCommandLine(base::CommandLine* command_line) override {
37 command_line->AppendSwitch(cc::switches::kEnableGpuBenchmarking);
38 }
39
GetRenderWidgetHost() const40 RenderWidgetHostImpl* GetRenderWidgetHost() const {
41 return RenderWidgetHostImpl::From(
42 shell()->web_contents()->GetRenderViewHost()->GetWidget());
43 }
44
LoadURL(const char * url)45 void LoadURL(const char* url) {
46 const GURL data_url(url);
47 EXPECT_TRUE(NavigateToURL(shell(), data_url));
48
49 RenderWidgetHostImpl* host = GetRenderWidgetHost();
50 HitTestRegionObserver observer(GetRenderWidgetHost()->GetFrameSinkId());
51 host->GetView()->SetSize(gfx::Size(400, 400));
52
53 base::string16 ready_title(base::ASCIIToUTF16("ready"));
54 TitleWatcher watcher(shell()->web_contents(), ready_title);
55 ignore_result(watcher.WaitAndGetTitle());
56
57 // Wait for the hit test data to be ready after initiating URL loading
58 // before returning
59 observer.WaitForHitTestData();
60 }
61
OnSyntheticGestureCompleted(SyntheticGesture::Result result)62 void OnSyntheticGestureCompleted(SyntheticGesture::Result result) {
63 EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result);
64 runner_->Quit();
65 }
66
67 protected:
68 std::unique_ptr<base::RunLoop> runner_;
69 };
70
71 class GestureScrollObserver : public RenderWidgetHost::InputEventObserver {
72 public:
OnInputEvent(const blink::WebInputEvent & event)73 void OnInputEvent(const blink::WebInputEvent& event) override {
74 if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollBegin)
75 gesture_scroll_seen_ = true;
76 }
HasSeenGestureScrollBegin() const77 bool HasSeenGestureScrollBegin() const { return gesture_scroll_seen_; }
78 bool gesture_scroll_seen_ = false;
79 };
80
81 // This test checks that we destroying a render widget host with an ongoing
82 // gesture doesn't cause lifetime issues. Namely, that the gesture
83 // CompletionCallback isn't destroyed before being called or the Mojo pipe
84 // being closed.
IN_PROC_BROWSER_TEST_F(SyntheticInputTest,DestroyWidgetWithOngoingGesture)85 IN_PROC_BROWSER_TEST_F(SyntheticInputTest, DestroyWidgetWithOngoingGesture) {
86 EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
87 EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
88
89 GestureScrollObserver gesture_observer;
90
91 GetRenderWidgetHost()->AddInputEventObserver(&gesture_observer);
92
93 // By starting a gesture, there's a Mojo callback that the renderer is
94 // waiting on the browser to resolve. If the browser is shutdown before
95 // ACKing the callback or closing the channel, we'll DCHECK.
96 ASSERT_TRUE(
97 ExecJs(shell()->web_contents(),
98 "chrome.gpuBenchmarking.smoothScrollByXY(0, 10000, ()=>{}, "
99 "100, 100, chrome.gpuBenchmarking.TOUCH_INPUT);"));
100
101 while (!gesture_observer.HasSeenGestureScrollBegin()) {
102 base::RunLoop run_loop;
103 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
104 FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
105 run_loop.Run();
106 }
107
108 shell()->Close();
109 }
110
111 // This test ensures that synthetic wheel scrolling works on all platforms.
IN_PROC_BROWSER_TEST_F(SyntheticInputTest,SmoothScrollWheel)112 IN_PROC_BROWSER_TEST_F(SyntheticInputTest, SmoothScrollWheel) {
113 LoadURL(R"HTML(
114 data:text/html;charset=utf-8,
115 <!DOCTYPE html>
116 <meta name='viewport' content='width=device-width'>
117 <style>
118 body {
119 width: 10px;
120 height: 2000px;
121 }
122 </style>
123 <script>
124 document.title = 'ready';
125 </script>
126 )HTML");
127
128 SyntheticSmoothScrollGestureParams params;
129 params.gesture_source_type = SyntheticGestureParams::MOUSE_INPUT;
130 params.anchor = gfx::PointF(1, 1);
131
132 // Note: 256 is precisely chosen since Android's minimum granularity is 64px.
133 // All other platforms can specify the delta per-pixel.
134 params.distances.push_back(gfx::Vector2d(0, -256));
135
136 // Use a speed that's fast enough that the entire scroll occurs in a single
137 // GSU, avoiding precision loss. SyntheticGestures can lose delta over time
138 // in slower scrolls on some platforms.
139 params.speed_in_pixels_s = 10000000.f;
140
141 // Use PrecisePixel to avoid animating.
142 params.granularity = ui::ScrollGranularity::kScrollByPrecisePixel;
143
144 runner_.reset(new base::RunLoop());
145
146 std::unique_ptr<SyntheticSmoothScrollGesture> gesture(
147 new SyntheticSmoothScrollGesture(params));
148 GetRenderWidgetHost()->QueueSyntheticGesture(
149 std::move(gesture),
150 base::BindOnce(&SyntheticInputTest::OnSyntheticGestureCompleted,
151 base::Unretained(this)));
152
153 // Run until we get the OnSyntheticGestureCompleted callback
154 runner_->Run();
155 runner_.reset();
156
157 EXPECT_EQ(256, EvalJs(shell()->web_contents(),
158 "document.scrollingElement.scrollTop"));
159 }
160
161 // This test ensures that slow synthetic wheel scrolling does not lose precision
162 // over time.
163 // https://crbug.com/1103731. Flaky on Android bots.
164 // https://crbug.com/1086334. Flaky on all desktop bots, but maybe for a
165 // different reason.
IN_PROC_BROWSER_TEST_F(SyntheticInputTest,DISABLED_SlowSmoothScrollWheel)166 IN_PROC_BROWSER_TEST_F(SyntheticInputTest, DISABLED_SlowSmoothScrollWheel) {
167 LoadURL(R"HTML(
168 data:text/html;charset=utf-8,
169 <!DOCTYPE html>
170 <meta name='viewport' content='width=device-width'>
171 <style>
172 body {
173 width: 10px;
174 height: 2000px;
175 }
176 </style>
177 <script>
178 document.title = 'ready';
179 </script>
180 )HTML");
181
182 SyntheticSmoothScrollGestureParams params;
183 params.gesture_source_type = SyntheticGestureParams::MOUSE_INPUT;
184 params.anchor = gfx::PointF(1, 1);
185
186 // Note: 1024 is precisely chosen since Android's minimum granularity is 64px.
187 // All other platforms can specify the delta per-pixel.
188 params.distances.push_back(gfx::Vector2d(0, -1024));
189
190 // Use a speed that's slow enough that it requires the browser to require
191 // multiple wheel-events to be dispatched, so that precision is needed to
192 // scroll the correct amount.
193 params.speed_in_pixels_s = 1000.f;
194
195 // Use PrecisePixel to avoid animating.
196 params.granularity = ui::ScrollGranularity::kScrollByPrecisePixel;
197
198 runner_ = std::make_unique<base::RunLoop>();
199
200 auto* web_contents = shell()->web_contents();
201 RenderFrameSubmissionObserver scroll_offset_wait(web_contents);
202 std::unique_ptr<SyntheticSmoothScrollGesture> gesture(
203 new SyntheticSmoothScrollGesture(params));
204 GetRenderWidgetHost()->QueueSyntheticGesture(
205 std::move(gesture),
206 base::BindOnce(&SyntheticInputTest::OnSyntheticGestureCompleted,
207 base::Unretained(this)));
208 float device_scale_factor =
209 web_contents->GetRenderWidgetHostView()->GetDeviceScaleFactor();
210 scroll_offset_wait.WaitForScrollOffset(
211 gfx::Vector2dF(0.f, 1024.f * device_scale_factor));
212
213 EXPECT_EQ(1024, EvalJs(shell()->web_contents(),
214 "document.scrollingElement.scrollTop"));
215 }
216
217 } // namespace content
218