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