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