1 // Copyright 2015 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 <utility>
6
7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/macros.h"
10 #include "base/run_loop.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/test/metrics/histogram_tester.h"
13 #include "base/test/scoped_feature_list.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_smooth_scroll_gesture.h"
17 #include "content/browser/renderer_host/render_widget_host_impl.h"
18 #include "content/browser/web_contents/web_contents_impl.h"
19 #include "content/common/input/synthetic_gesture_params.h"
20 #include "content/common/input/synthetic_smooth_scroll_gesture_params.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/common/content_switches.h"
23 #include "content/public/test/browser_test_utils.h"
24 #include "content/public/test/content_browser_test.h"
25 #include "content/public/test/content_browser_test_utils.h"
26 #include "content/public/test/hit_test_region_observer.h"
27 #include "content/public/test/test_utils.h"
28 #include "content/shell/browser/shell.h"
29 #include "ui/gfx/geometry/angle_conversions.h"
30
31 namespace {
32
33 const char kCompositedScrollingDataURL[] =
34 "data:text/html;charset=utf-8,"
35 "<!DOCTYPE html>"
36 "<meta name='viewport' content='width=device-width'/>"
37 "<style>"
38 "%23scroller {"
39 " width:500px;"
40 " height:500px;"
41 " overflow:scroll;"
42 " transform: rotateX(-30deg);"
43 "}"
44
45 "%23content {"
46 " background-color:red;"
47 " width:1000px;"
48 " height:1000px;"
49 "}"
50 "</style>"
51 "<div id='scroller'>"
52 " <div id='content'>"
53 " </div>"
54 "</div>"
55 "<script>"
56 " document.title='ready';"
57 "</script>";
58
59 } // namespace
60
61 namespace content {
62
63
64 class CompositedScrollingBrowserTest : public ContentBrowserTest {
65 public:
CompositedScrollingBrowserTest()66 CompositedScrollingBrowserTest() {
67 // Disable scroll resampling because this is checking scroll distance.
68 scoped_feature_list_.InitAndDisableFeature(
69 features::kResamplingScrollEvents);
70 }
71
72 ~CompositedScrollingBrowserTest() override = default;
73
SetUpCommandLine(base::CommandLine * cmd)74 void SetUpCommandLine(base::CommandLine* cmd) override {
75 cmd->AppendSwitch(switches::kEnablePreferCompositingToLCDText);
76 }
77
GetWidgetHost()78 RenderWidgetHostImpl* GetWidgetHost() {
79 return RenderWidgetHostImpl::From(
80 shell()->web_contents()->GetRenderViewHost()->GetWidget());
81 }
82
OnSyntheticGestureCompleted(SyntheticGesture::Result result)83 void OnSyntheticGestureCompleted(SyntheticGesture::Result result) {
84 EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result);
85 runner_->Quit();
86 }
87
88 protected:
LoadURL(const char * url)89 void LoadURL(const char* url) {
90 const GURL data_url(url);
91 EXPECT_TRUE(NavigateToURL(shell(), data_url));
92
93 RenderWidgetHostImpl* host = GetWidgetHost();
94 HitTestRegionObserver observer(GetWidgetHost()->GetFrameSinkId());
95 host->GetView()->SetSize(gfx::Size(400, 400));
96
97 base::string16 ready_title(base::ASCIIToUTF16("ready"));
98 TitleWatcher watcher(shell()->web_contents(), ready_title);
99 ignore_result(watcher.WaitAndGetTitle());
100
101 // Wait for the hit test data to be ready after initiating URL loading
102 // before returning
103 observer.WaitForHitTestData();
104 }
105
106 // ContentBrowserTest:
ExecuteScriptAndExtractInt(const std::string & script)107 int ExecuteScriptAndExtractInt(const std::string& script) {
108 int value = 0;
109 EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
110 shell(), "domAutomationController.send(" + script + ")", &value));
111 return value;
112 }
113
ExecuteScriptAndExtractDouble(const std::string & script)114 double ExecuteScriptAndExtractDouble(const std::string& script) {
115 double value = 0;
116 EXPECT_TRUE(content::ExecuteScriptAndExtractDouble(
117 shell(), "domAutomationController.send(" + script + ")", &value));
118 return value;
119 }
120
GetScrollTop()121 double GetScrollTop() {
122 return ExecuteScriptAndExtractDouble(
123 "document.getElementById(\"scroller\").scrollTop");
124 }
125
126 // Generate touch events for a synthetic scroll from |point| for |distance|.
127 // Returns the distance scrolled.
DoScroll(SyntheticGestureParams::GestureSourceType type,const gfx::Point & point,const gfx::Vector2d & distance)128 double DoScroll(SyntheticGestureParams::GestureSourceType type,
129 const gfx::Point& point,
130 const gfx::Vector2d& distance) {
131 SyntheticSmoothScrollGestureParams params;
132 params.gesture_source_type = type;
133 params.anchor = gfx::PointF(point);
134 params.distances.push_back(-distance);
135
136 runner_ = new MessageLoopRunner();
137
138 std::unique_ptr<SyntheticSmoothScrollGesture> gesture(
139 new SyntheticSmoothScrollGesture(params));
140 GetWidgetHost()->QueueSyntheticGesture(
141 std::move(gesture),
142 base::BindOnce(
143 &CompositedScrollingBrowserTest::OnSyntheticGestureCompleted,
144 base::Unretained(this)));
145
146 // Runs until we get the OnSyntheticGestureCompleted callback
147 runner_->Run();
148 runner_ = nullptr;
149
150 return GetScrollTop();
151 }
152
DoTouchScroll(const gfx::Point & point,const gfx::Vector2d & distance)153 double DoTouchScroll(const gfx::Point& point, const gfx::Vector2d& distance) {
154 return DoScroll(SyntheticGestureParams::TOUCH_INPUT, point, distance);
155 }
156
DoWheelScroll(const gfx::Point & point,const gfx::Vector2d & distance)157 double DoWheelScroll(const gfx::Point& point, const gfx::Vector2d& distance) {
158 return DoScroll(SyntheticGestureParams::MOUSE_INPUT, point, distance);
159 }
160
161 private:
162 base::test::ScopedFeatureList scoped_feature_list_;
163 scoped_refptr<MessageLoopRunner> runner_;
164
165 DISALLOW_COPY_AND_ASSIGN(CompositedScrollingBrowserTest);
166 };
167
168 // Verify transforming a scroller doesn't prevent it from scrolling. See
169 // crbug.com/543655 for a case where this was broken.
170 // Disabled on MacOS because it doesn't support touch input.
171 // Disabled on Android due to flakiness, see https://crbug.com/376668.
172 // Flaky on Windows: crbug.com/804009
173 #if defined(OS_MACOSX) || defined(OS_ANDROID) || defined(OS_WIN)
174 #define MAYBE_Scroll3DTransformedScroller DISABLED_Scroll3DTransformedScroller
175 #else
176 #define MAYBE_Scroll3DTransformedScroller Scroll3DTransformedScroller
177 #endif
IN_PROC_BROWSER_TEST_F(CompositedScrollingBrowserTest,MAYBE_Scroll3DTransformedScroller)178 IN_PROC_BROWSER_TEST_F(CompositedScrollingBrowserTest,
179 MAYBE_Scroll3DTransformedScroller) {
180 LoadURL(kCompositedScrollingDataURL);
181 ASSERT_EQ(0, GetScrollTop());
182
183 double scroll_distance =
184 DoTouchScroll(gfx::Point(50, 150), gfx::Vector2d(0, 100));
185 // The scroll distance is increased due to the rotation of the scroller.
186 EXPECT_NEAR(100 / std::cos(gfx::DegToRad(30.f)), scroll_distance, 1.f);
187 }
188
189 class CompositedScrollingMetricTest : public CompositedScrollingBrowserTest,
190 public testing::WithParamInterface<bool> {
191 public:
192 CompositedScrollingMetricTest() = default;
193 ~CompositedScrollingMetricTest() override = default;
194
SetUpCommandLine(base::CommandLine * cmd)195 void SetUpCommandLine(base::CommandLine* cmd) override {
196 const bool enable_composited_scrolling = GetParam();
197 if (enable_composited_scrolling)
198 cmd->AppendSwitch(switches::kEnablePreferCompositingToLCDText);
199 else
200 cmd->AppendSwitch(switches::kDisableThreadedScrolling);
201 }
202
CompositingEnabled()203 bool CompositingEnabled() { return GetParam(); }
204
205 const char* kWheelHistogramName = "Renderer4.ScrollingThread.Wheel";
206 const char* kTouchHistogramName = "Renderer4.ScrollingThread.Touch";
207
208 // These values are defined in input_handler_proxy.cc and match the enum in
209 // histograms.xml.
210 enum ScrollingThreadStatus {
211 kScrollingOnCompositor = 0,
212 kScrollingOnCompositorBlockedOnMain = 1,
213 kScrollingOnMain = 2,
214 kMaxValue = kScrollingOnMain,
215 };
216 };
217
218 INSTANTIATE_TEST_SUITE_P(All,
219 CompositedScrollingMetricTest,
220 ::testing::Bool());
221
222 // Tests the functionality of the histogram tracking composited vs main thread
223 // scrolls.
IN_PROC_BROWSER_TEST_P(CompositedScrollingMetricTest,RecordCorrectScrollingThread)224 IN_PROC_BROWSER_TEST_P(CompositedScrollingMetricTest,
225 RecordCorrectScrollingThread) {
226 LoadURL(R"HTML(
227 data:text/html;charset=utf-8,
228 <!DOCTYPE html>
229 <style>
230 %23scroller {
231 width: 300px;
232 height: 300px;
233 overflow: auto;
234 }
235 %23space {
236 width: 1000px;
237 height: 1000px;
238 }
239 </style>
240 <div id='scroller'>
241 <div id='space'></div>
242 </div>
243 <script>
244 document.title = 'ready';
245 </script>
246 )HTML");
247
248 base::HistogramTester histograms;
249
250 DoTouchScroll(gfx::Point(50, 50), gfx::Vector2d(0, 100));
251 DoTouchScroll(gfx::Point(50, 50), gfx::Vector2d(0, -100));
252
253 DoWheelScroll(gfx::Point(50, 50), gfx::Vector2d(0, 100));
254
255 content::FetchHistogramsFromChildProcesses();
256
257 base::HistogramBase::Sample expected_bucket =
258 CompositingEnabled() ? kScrollingOnCompositor : kScrollingOnMain;
259
260 histograms.ExpectUniqueSample(kTouchHistogramName, expected_bucket, 2);
261 histograms.ExpectUniqueSample(kWheelHistogramName, expected_bucket, 1);
262 }
263
264 // Tests the composited vs main thread scrolling histogram in the presence of
265 // blocking event handlers.
IN_PROC_BROWSER_TEST_P(CompositedScrollingMetricTest,BlockingEventHandlers)266 IN_PROC_BROWSER_TEST_P(CompositedScrollingMetricTest, BlockingEventHandlers) {
267 LoadURL(R"HTML(
268 data:text/html;charset=utf-8,
269 <!DOCTYPE html>
270 <style>
271 %23scroller {
272 width: 300px;
273 height: 300px;
274 overflow: auto;
275 }
276 %23space {
277 width: 1000px;
278 height: 1000px;
279 }
280 </style>
281 <div id='scroller'>
282 <div id='space'></div>
283 </div>
284 <script>
285 const scroller = document.getElementById('scroller');
286 scroller.addEventListener('wheel', (e) => { }, {passive: false});
287 scroller.addEventListener('touchstart', (e) => { }, {passive: false});
288 onload = () => {
289 document.title = 'ready';
290 }
291 </script>
292 )HTML");
293
294 base::HistogramTester histograms;
295
296 DoTouchScroll(gfx::Point(50, 50), gfx::Vector2d(0, 100));
297 DoTouchScroll(gfx::Point(50, 50), gfx::Vector2d(0, -100));
298
299 DoWheelScroll(gfx::Point(50, 50), gfx::Vector2d(0, 100));
300
301 content::FetchHistogramsFromChildProcesses();
302
303 base::HistogramBase::Sample expected_bucket =
304 CompositingEnabled() ? kScrollingOnCompositorBlockedOnMain
305 : kScrollingOnMain;
306
307 histograms.ExpectUniqueSample(kTouchHistogramName, expected_bucket, 2);
308 histograms.ExpectUniqueSample(kWheelHistogramName, expected_bucket, 1);
309 }
310
311 // Tests the composited vs main thread scrolling histogram in the presence of
312 // passive event handlers. These should behave the same as the case without any
313 // event handlers at all.
IN_PROC_BROWSER_TEST_P(CompositedScrollingMetricTest,PassiveEventHandlers)314 IN_PROC_BROWSER_TEST_P(CompositedScrollingMetricTest, PassiveEventHandlers) {
315 LoadURL(R"HTML(
316 data:text/html;charset=utf-8,
317 <!DOCTYPE html>
318 <style>
319 %23scroller {
320 width: 300px;
321 height: 300px;
322 overflow: auto;
323 }
324 %23space {
325 width: 1000px;
326 height: 1000px;
327 }
328 </style>
329 <div id='scroller'>
330 <div id='space'></div>
331 </div>
332 <script>
333 const scroller = document.getElementById('scroller');
334 scroller.addEventListener('wheel', (e) => { }, {passive: true});
335 scroller.addEventListener('touchstart', (e) => { }, {passive: true});
336 onload = () => {
337 document.title = 'ready';
338 }
339 </script>
340 )HTML");
341
342 base::HistogramTester histograms;
343
344 DoTouchScroll(gfx::Point(50, 50), gfx::Vector2d(0, 100));
345 DoTouchScroll(gfx::Point(50, 50), gfx::Vector2d(0, -100));
346
347 DoWheelScroll(gfx::Point(50, 50), gfx::Vector2d(0, 100));
348
349 content::FetchHistogramsFromChildProcesses();
350
351 base::HistogramBase::Sample expected_bucket =
352 CompositingEnabled() ? kScrollingOnCompositor : kScrollingOnMain;
353
354 histograms.ExpectUniqueSample(kTouchHistogramName, expected_bucket, 2);
355 histograms.ExpectUniqueSample(kWheelHistogramName, expected_bucket, 1);
356 }
357
358 } // namespace content
359