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