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