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