1 // Copyright 2018 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 "content/browser/media/capture/mouse_cursor_overlay_controller.h"
6 
7 #include "base/macros.h"
8 #include "base/run_loop.h"
9 #include "content/public/browser/browser_task_traits.h"
10 #include "content/public/browser/browser_thread.h"
11 #include "content/public/browser/web_contents.h"
12 #include "content/public/test/browser_test.h"
13 #include "content/public/test/content_browser_test.h"
14 #include "content/shell/browser/shell.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "third_party/skia/include/core/SkBitmap.h"
17 #include "ui/gfx/geometry/point_f.h"
18 #include "ui/gfx/geometry/rect_f.h"
19 #include "ui/gfx/geometry/size.h"
20 #include "ui/gfx/geometry/size_f.h"
21 
22 namespace content {
23 namespace {
24 
25 class FakeOverlay : public MouseCursorOverlayController::Overlay {
26  public:
27   FakeOverlay() = default;
28   ~FakeOverlay() final = default;
29 
image() const30   const SkBitmap& image() const { return image_; }
bounds() const31   const gfx::RectF& bounds() const { return bounds_; }
32 
SetImageAndBounds(const SkBitmap & image,const gfx::RectF & bounds)33   void SetImageAndBounds(const SkBitmap& image,
34                          const gfx::RectF& bounds) final {
35     image_ = image;
36     bounds_ = bounds;
37   }
38 
SetBounds(const gfx::RectF & bounds)39   void SetBounds(const gfx::RectF& bounds) final { bounds_ = bounds; }
40 
41  private:
42   SkBitmap image_;
43   gfx::RectF bounds_;
44 
45   DISALLOW_COPY_AND_ASSIGN(FakeOverlay);
46 };
47 
48 }  // namespace
49 
50 class MouseCursorOverlayControllerBrowserTest : public ContentBrowserTest {
51  public:
52   MouseCursorOverlayControllerBrowserTest() = default;
53   ~MouseCursorOverlayControllerBrowserTest() override = default;
54 
SetUpOnMainThread()55   void SetUpOnMainThread() final {
56     ContentBrowserTest::SetUpOnMainThread();
57     controller_.SetTargetView(shell()->web_contents()->GetNativeView());
58     controller_.DisconnectFromToolkitForTesting();
59     base::RunLoop().RunUntilIdle();
60   }
61 
Start()62   FakeOverlay* Start() {
63     auto overlay_ptr = std::make_unique<FakeOverlay>();
64     FakeOverlay* const overlay = overlay_ptr.get();
65     DCHECK_CURRENTLY_ON(BrowserThread::UI);
66     controller_.Start(std::move(overlay_ptr), GetUIThreadTaskRunner({}));
67     return overlay;
68   }
69 
GetLowerRightMostPointInsideView()70   gfx::PointF GetLowerRightMostPointInsideView() {
71     const gfx::Size& view_size = GetAbsoluteViewSize();
72     return gfx::PointF(1.0f - 1.0f / view_size.width(),
73                        1.0f - 1.0f / view_size.height());
74   }
75 
SimulateMouseTravel(float from_x,float from_y,float to_x,float to_y)76   void SimulateMouseTravel(float from_x, float from_y, float to_x, float to_y) {
77     constexpr int kNumMoves = 10;
78     for (int i = kNumMoves; i >= 0; --i) {
79       const float t = static_cast<float>(i) / kNumMoves;
80       const float x = t * from_x + (1.0f - t) * to_x;
81       const float y = t * from_y + (1.0f - t) * to_y;
82       controller_.OnMouseMoved(ToAbsoluteLocationInView(x, y));
83     }
84     base::RunLoop().RunUntilIdle();
85   }
86 
SimulateMouseClick(float x,float y)87   void SimulateMouseClick(float x, float y) {
88     controller_.OnMouseClicked(ToAbsoluteLocationInView(x, y));
89     base::RunLoop().RunUntilIdle();
90   }
91 
SimulateMouseHasGoneIdle()92   void SimulateMouseHasGoneIdle() {
93     controller_.OnMouseHasGoneIdle();
94     base::RunLoop().RunUntilIdle();
95     EXPECT_FALSE(controller_.mouse_activity_ended_timer_.IsRunning());
96   }
97 
SimulateUnintentionalMouseMovement(float x,float y)98   void SimulateUnintentionalMouseMovement(float x, float y) {
99     const gfx::Size& view_size = GetAbsoluteViewSize();
100     const float distance_x =
101         (0.5f * MouseCursorOverlayController::kMinMovementPixels) /
102         view_size.width();
103     const float distance_y =
104         (0.5f * MouseCursorOverlayController::kMinMovementPixels) /
105         view_size.height();
106     DoSquareDance(x, y, distance_x, distance_y);
107   }
108 
SimulateBarelyIntentionalMouseMovement(float x,float y)109   void SimulateBarelyIntentionalMouseMovement(float x, float y) {
110     const gfx::Size& view_size = GetAbsoluteViewSize();
111     const float distance_x =
112         (1.5f * MouseCursorOverlayController::kMinMovementPixels) /
113         view_size.width();
114     const float distance_y =
115         (1.5f * MouseCursorOverlayController::kMinMovementPixels) /
116         view_size.height();
117     DoSquareDance(x, y, distance_x, distance_y);
118   }
119 
ExpectOverlayPositionedAt(const FakeOverlay & overlay,float expected_x,float expected_y)120   void ExpectOverlayPositionedAt(const FakeOverlay& overlay,
121                                  float expected_x,
122                                  float expected_y) {
123     const gfx::SizeF& overlay_size = GetExpectedOverlaySize();
124     // The position will be slightly off because of the hotspot offset.
125     EXPECT_NEAR(expected_x, overlay.bounds().x(), overlay_size.width() / 2.0f);
126     EXPECT_NEAR(expected_y, overlay.bounds().y(), overlay_size.height() / 2.0f);
127   }
128 
ExpectOverlaySizeMatchesCurrentCursor(const FakeOverlay & overlay) const129   void ExpectOverlaySizeMatchesCurrentCursor(const FakeOverlay& overlay) const {
130     const gfx::SizeF& expected_size = GetExpectedOverlaySize();
131     EXPECT_FALSE(expected_size.IsEmpty());
132     EXPECT_FALSE(overlay.image().drawsNothing());
133     EXPECT_NEAR(expected_size.width(), overlay.bounds().width(), 0.001);
134     EXPECT_NEAR(expected_size.height(), overlay.bounds().height(), 0.001);
135   }
136 
IsUserInteractingWithView() const137   bool IsUserInteractingWithView() const {
138     return controller_.IsUserInteractingWithView();
139   }
140 
141  private:
GetAbsoluteViewSize() const142   gfx::Size GetAbsoluteViewSize() const {
143     const gfx::Size view_size =
144         shell()->web_contents()->GetContainerBounds().size();
145     CHECK(!view_size.IsEmpty());
146     return view_size;
147   }
148 
ToAbsoluteLocationInView(float relative_x,float relative_y)149   gfx::PointF ToAbsoluteLocationInView(float relative_x, float relative_y) {
150     const gfx::Size& view_size = GetAbsoluteViewSize();
151     return gfx::PointF(relative_x * view_size.width(),
152                        relative_y * view_size.height());
153   }
154 
GetExpectedOverlaySize() const155   gfx::SizeF GetExpectedOverlaySize() const {
156     const gfx::Size& view_size = GetAbsoluteViewSize();
157     const SkBitmap image =
158         controller_.GetCursorImage(controller_.GetCurrentCursorOrDefault());
159     return gfx::SizeF(static_cast<float>(image.width()) / view_size.width(),
160                       static_cast<float>(image.height()) / view_size.height());
161   }
162 
DoSquareDance(float x,float y,float distance_x,float distance_y)163   void DoSquareDance(float x, float y, float distance_x, float distance_y) {
164     SimulateMouseTravel(x, y, x + distance_x, y);
165     SimulateMouseTravel(x + distance_x, y, x + distance_x, y + distance_y);
166     SimulateMouseTravel(x + distance_x, y + distance_y, x, y + distance_y);
167     SimulateMouseTravel(x, y + distance_y, x, y);
168     base::RunLoop().RunUntilIdle();
169   }
170 
171   MouseCursorOverlayController controller_;
172 
173   DISALLOW_COPY_AND_ASSIGN(MouseCursorOverlayControllerBrowserTest);
174 };
175 
IN_PROC_BROWSER_TEST_F(MouseCursorOverlayControllerBrowserTest,PositionsOverlayOnMouseMoves)176 IN_PROC_BROWSER_TEST_F(MouseCursorOverlayControllerBrowserTest,
177                        PositionsOverlayOnMouseMoves) {
178   FakeOverlay* const overlay = Start();
179 
180   // Cursor not showing at start.
181   EXPECT_TRUE(overlay->image().drawsNothing());
182   EXPECT_TRUE(overlay->bounds().IsEmpty());
183   EXPECT_FALSE(IsUserInteractingWithView());
184 
185   // Move to upper-leftmost corner.
186   {
187     SCOPED_TRACE(testing::Message() << "upper-leftmost corner of view");
188     SimulateMouseTravel(0.5f, 0.5f, 0.0f, 0.0f);
189     ExpectOverlayPositionedAt(*overlay, 0.0f, 0.0f);
190     ExpectOverlaySizeMatchesCurrentCursor(*overlay);
191     EXPECT_TRUE(IsUserInteractingWithView());
192   }
193 
194   // Move to middle.
195   {
196     SCOPED_TRACE(testing::Message() << "center of view");
197     SimulateMouseTravel(0.0f, 0.0f, 0.5f, 0.5f);
198     ExpectOverlayPositionedAt(*overlay, 0.5f, 0.5f);
199     ExpectOverlaySizeMatchesCurrentCursor(*overlay);
200     EXPECT_TRUE(IsUserInteractingWithView());
201   }
202 
203   // Move to lower-rightmost corner.
204   {
205     SCOPED_TRACE(testing::Message() << "lower-rightmost corner of view");
206     const gfx::PointF lower_right = GetLowerRightMostPointInsideView();
207     SimulateMouseTravel(0.5f, 0.5f, lower_right.x(), lower_right.y());
208     ExpectOverlayPositionedAt(*overlay, lower_right.x(), lower_right.y());
209     ExpectOverlaySizeMatchesCurrentCursor(*overlay);
210     EXPECT_TRUE(IsUserInteractingWithView());
211   }
212 }
213 
IN_PROC_BROWSER_TEST_F(MouseCursorOverlayControllerBrowserTest,PositionsOverlayOnMouseClicks)214 IN_PROC_BROWSER_TEST_F(MouseCursorOverlayControllerBrowserTest,
215                        PositionsOverlayOnMouseClicks) {
216   FakeOverlay* const overlay = Start();
217 
218   // Cursor not showing at start.
219   EXPECT_TRUE(overlay->bounds().IsEmpty());
220   EXPECT_FALSE(IsUserInteractingWithView());
221 
222   // Click in the middle of the view.
223   SimulateMouseClick(0.5f, 0.5f);
224   ExpectOverlayPositionedAt(*overlay, 0.5f, 0.5f);
225   ExpectOverlaySizeMatchesCurrentCursor(*overlay);
226   EXPECT_TRUE(IsUserInteractingWithView());
227 }
228 
IN_PROC_BROWSER_TEST_F(MouseCursorOverlayControllerBrowserTest,CursorHidesWhenMouseStopsMoving)229 IN_PROC_BROWSER_TEST_F(MouseCursorOverlayControllerBrowserTest,
230                        CursorHidesWhenMouseStopsMoving) {
231   FakeOverlay* const overlay = Start();
232 
233   // Cursor not showing at start.
234   EXPECT_TRUE(overlay->bounds().IsEmpty());
235   EXPECT_FALSE(IsUserInteractingWithView());
236 
237   // Move to middle.
238   SimulateMouseTravel(0.0f, 0.0f, 0.5f, 0.5f);
239   ExpectOverlayPositionedAt(*overlay, 0.5f, 0.5f);
240   ExpectOverlaySizeMatchesCurrentCursor(*overlay);
241   EXPECT_TRUE(IsUserInteractingWithView());
242 
243   // Simulate no movement for the timeout period.
244   SimulateMouseHasGoneIdle();
245   EXPECT_TRUE(overlay->bounds().IsEmpty());
246   EXPECT_FALSE(IsUserInteractingWithView());
247 
248   // Move the mouse a little, but not enough to trip the "intentionally moved"
249   // logic.
250   SimulateUnintentionalMouseMovement(0.5f, 0.5f);
251   EXPECT_TRUE(overlay->bounds().IsEmpty());
252   EXPECT_FALSE(IsUserInteractingWithView());
253 
254   // Move the mouse just a bit more, to trip the "intentionally moved" logic.
255   SimulateBarelyIntentionalMouseMovement(0.5f, 0.5f);
256   ExpectOverlayPositionedAt(*overlay, 0.5f, 0.5f);
257   ExpectOverlaySizeMatchesCurrentCursor(*overlay);
258   EXPECT_TRUE(IsUserInteractingWithView());
259 }
260 
261 }  // namespace content
262