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 "third_party/blink/renderer/core/input/fallback_cursor_event_manager.h"
6
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "third_party/blink/public/common/input/web_mouse_event.h"
9 #include "third_party/blink/renderer/core/dom/document.h"
10 #include "third_party/blink/renderer/core/frame/local_frame.h"
11 #include "third_party/blink/renderer/core/frame/visual_viewport.h"
12 #include "third_party/blink/renderer/core/input/event_handler.h"
13 #include "third_party/blink/renderer/core/loader/empty_clients.h"
14 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
15 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
16 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
17
18 namespace {
19
20 constexpr size_t kLeft = 0;
21 constexpr size_t kRight = 1;
22 constexpr size_t kUp = 2;
23 constexpr size_t kDown = 3;
24
25 } // namespace
26
27 #define ExpectLock(l, r, u, d) \
28 EXPECT_EQ(GetFallbackCursorChromeClient().cursor_lock_[kLeft], l); \
29 EXPECT_EQ(GetFallbackCursorChromeClient().cursor_lock_[kRight], r); \
30 EXPECT_EQ(GetFallbackCursorChromeClient().cursor_lock_[kUp], u); \
31 EXPECT_EQ(GetFallbackCursorChromeClient().cursor_lock_[kDown], d);
32
33 namespace blink {
34
35 class FallbackCursorChromeClient : public RenderingTestChromeClient {
36 public:
FallbackCursorChromeClient()37 FallbackCursorChromeClient() {}
38
FallbackCursorModeLockCursor(LocalFrame * frame,bool left,bool right,bool up,bool down)39 void FallbackCursorModeLockCursor(LocalFrame* frame,
40 bool left,
41 bool right,
42 bool up,
43 bool down) override {
44 cursor_lock_[0] = left;
45 cursor_lock_[1] = right;
46 cursor_lock_[2] = up;
47 cursor_lock_[3] = down;
48 }
49
FallbackCursorModeSetCursorVisibility(LocalFrame * frame,bool visible)50 void FallbackCursorModeSetCursorVisibility(LocalFrame* frame,
51 bool visible) override {
52 cursor_visible_ = visible;
53 }
54
55 bool cursor_lock_[4] = {0};
56 bool cursor_visible_ = true;
57
58 private:
59 DISALLOW_COPY_AND_ASSIGN(FallbackCursorChromeClient);
60 };
61
62 class FallbackCursorEventManagerTest : public RenderingTest,
63 private ScopedFallbackCursorModeForTest {
64 protected:
FallbackCursorEventManagerTest()65 FallbackCursorEventManagerTest()
66 : RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()),
67 ScopedFallbackCursorModeForTest(true),
68 chrome_client_(MakeGarbageCollected<FallbackCursorChromeClient>()) {}
69
~FallbackCursorEventManagerTest()70 ~FallbackCursorEventManagerTest() override {}
71
GetChromeClient() const72 RenderingTestChromeClient& GetChromeClient() const override {
73 return *chrome_client_;
74 }
75
GetFallbackCursorChromeClient() const76 FallbackCursorChromeClient& GetFallbackCursorChromeClient() const {
77 return *chrome_client_;
78 }
79
TurnOnFallbackCursorMode()80 void TurnOnFallbackCursorMode() {
81 GetDocument().GetFrame()->GetEventHandler().SetIsFallbackCursorModeOn(true);
82 }
83
MouseMove(int x,int y,float scale=1.0f)84 void MouseMove(int x, int y, float scale = 1.0f) {
85 WebMouseEvent event(WebInputEvent::kMouseMove, gfx::PointF(x, y),
86 gfx::PointF(x, y),
87 WebPointerProperties::Button::kNoButton, 0,
88 WebInputEvent::kNoModifiers, base::TimeTicks::Now());
89 event.SetFrameScale(scale);
90 GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
91 event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
92 }
93
94 // Simulates a mouse move at the given point in the visual viewport (i.e. the
95 // coordinates relative to the Chrome window).
96 // TODO(bokan): Replace all above uses with this method.
MouseMoveViewport(IntPoint point)97 void MouseMoveViewport(IntPoint point) {
98 VisualViewport& visual_viewport =
99 GetDocument().GetPage()->GetVisualViewport();
100 FloatPoint root_frame_point =
101 visual_viewport.ViewportToRootFrame(FloatPoint(point));
102
103 WebMouseEvent event(WebInputEvent::kMouseMove, root_frame_point,
104 root_frame_point,
105 WebPointerProperties::Button::kNoButton, 0,
106 WebInputEvent::kNoModifiers, base::TimeTicks::Now());
107 event.SetFrameScale(1.0f);
108 GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
109 event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
110 }
111
MouseDown(int x,int y)112 void MouseDown(int x, int y) {
113 WebMouseEvent event(WebInputEvent::kMouseDown, gfx::PointF(x, y),
114 gfx::PointF(x, y), WebPointerProperties::Button::kLeft,
115 0, WebInputEvent::Modifiers::kLeftButtonDown,
116 base::TimeTicks::Now());
117 event.SetFrameScale(1);
118 GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(event);
119 }
120
KeyBack()121 bool KeyBack() {
122 return GetDocument()
123 .GetFrame()
124 ->GetEventHandler()
125 .HandleFallbackCursorModeBackEvent();
126 }
127
128 private:
129 Persistent<FallbackCursorChromeClient> chrome_client_;
130
131 DISALLOW_COPY_AND_ASSIGN(FallbackCursorEventManagerTest);
132 };
133
TEST_F(FallbackCursorEventManagerTest,RootFrameNotScrollable)134 TEST_F(FallbackCursorEventManagerTest, RootFrameNotScrollable) {
135 SetBodyInnerHTML("A");
136 TurnOnFallbackCursorMode();
137
138 // Mouse move to edge.
139 MouseMove(0, 0);
140 ExpectLock(false, false, false, false);
141
142 MouseMove(0, 600);
143 ExpectLock(false, false, false, false);
144
145 MouseMove(800, 0);
146 ExpectLock(false, false, false, false);
147
148 MouseMove(800, 600);
149 ExpectLock(false, false, false, false);
150 }
151
TEST_F(FallbackCursorEventManagerTest,ResetOnOutOfFrame)152 TEST_F(FallbackCursorEventManagerTest, ResetOnOutOfFrame) {
153 SetBodyInnerHTML(R"HTML(
154 <style>
155 html, body {
156 margin: 0px;
157 }
158 .big {
159 height: 10000px;
160 width: 10000px;
161 }
162 </style>
163 <div class='big'></div>
164 )HTML");
165 TurnOnFallbackCursorMode();
166
167 // Move below the scroll down line.
168 MouseMove(100, 500);
169 ExpectLock(false, false, false, true);
170
171 // Ensure an invalid or out-of-bounds mouse move will reset the lock.
172 MouseMove(-1, -1);
173 ExpectLock(false, false, false, false);
174
175 // Ensure an invalid or out-of-bounds mouse move will reset the lock.
176 MouseMove(790, 590);
177 ExpectLock(false, true, false, true);
178
179 // Ensure an invalid or out-of-bounds mouse move will reset the lock.
180 MouseMove(800, 600);
181 ExpectLock(false, false, false, false);
182 }
183
TEST_F(FallbackCursorEventManagerTest,MouseMoveCursorLockOnRootFrame)184 TEST_F(FallbackCursorEventManagerTest, MouseMoveCursorLockOnRootFrame) {
185 SetBodyInnerHTML(R"HTML(
186 <style>
187 html, body {
188 margin: 0px;
189 }
190 .big {
191 height: 10000px;
192 width: 10000px;
193 }
194 </style>
195 <div class='big'></div>
196 )HTML");
197 TurnOnFallbackCursorMode();
198
199 // Move below the scroll down line.
200 MouseMove(100, 500);
201 ExpectLock(false, false, false, true);
202
203 // Move above the scroll down line.
204 MouseMove(100, 400);
205 ExpectLock(false, false, false, false);
206
207 // Move to the right of scroll right line.
208 MouseMove(600, 400);
209 ExpectLock(false, true, false, false);
210 }
211
TEST_F(FallbackCursorEventManagerTest,MouseMoveCursorLockOnRootFrameWithScale)212 TEST_F(FallbackCursorEventManagerTest,
213 MouseMoveCursorLockOnRootFrameWithScale) {
214 const float SCALE = 0.5f;
215 SetBodyInnerHTML(R"HTML(
216 <style>
217 html, body {
218 margin: 0px;
219 }
220 .big {
221 height: 10000px;
222 width: 10000px;
223 }
224 </style>
225 <div class='big'></div>
226 )HTML");
227 TurnOnFallbackCursorMode();
228
229 // Move below the scroll down line.
230 MouseMove(50, 250, SCALE);
231 ExpectLock(false, false, false, true);
232
233 // Move above the scroll down line.
234 MouseMove(50, 200, SCALE);
235 ExpectLock(false, false, false, false);
236
237 // Move to the right of scroll right line.
238 MouseMove(300, 200, SCALE);
239 ExpectLock(false, true, false, false);
240 }
241
TEST_F(FallbackCursorEventManagerTest,MouseMoveCursorLockOnDiv)242 TEST_F(FallbackCursorEventManagerTest, MouseMoveCursorLockOnDiv) {
243 SetBodyInnerHTML(R"HTML(
244 <style>
245 html, body {
246 margin: 0px;
247 }
248 .big {
249 height: 10000px;
250 width: 10000px;
251 }
252 #d1 {
253 height: 100px;
254 width: 100px;
255 overflow: auto;
256 }
257 </style>
258 <div id='d1'>
259 <div class='big'></div>
260 </div>
261 )HTML");
262 TurnOnFallbackCursorMode();
263
264 // Move below the scroll down line but before mouse down.
265 MouseMove(50, 80);
266 ExpectLock(false, false, false, false);
267 EXPECT_FALSE(GetDocument()
268 .GetFrame()
269 ->GetEventHandler()
270 .fallback_cursor_event_manager_->current_node_);
271
272 // Mouse down and move lock on down.
273 MouseDown(50, 80);
274 Element* d1 = GetDocument().getElementById("d1");
275 EXPECT_EQ(GetDocument()
276 .GetFrame()
277 ->GetEventHandler()
278 .fallback_cursor_event_manager_->current_node_.Get(),
279 d1);
280 MouseMove(50, 80);
281 ExpectLock(false, false, false, true);
282
283 // Mouse move out of div.
284 MouseMove(200, 200);
285 ExpectLock(false, false, false, false);
286 EXPECT_FALSE(GetDocument()
287 .GetFrame()
288 ->GetEventHandler()
289 .fallback_cursor_event_manager_->current_node_);
290
291 // key back.
292 MouseMove(50, 80);
293 MouseDown(50, 80);
294 EXPECT_EQ(GetDocument()
295 .GetFrame()
296 ->GetEventHandler()
297 .fallback_cursor_event_manager_->current_node_.Get(),
298 d1);
299 EXPECT_TRUE(KeyBack());
300 EXPECT_FALSE(GetDocument()
301 .GetFrame()
302 ->GetEventHandler()
303 .fallback_cursor_event_manager_->current_node_);
304 }
305
TEST_F(FallbackCursorEventManagerTest,MouseMoveCursorLockOnIFrame)306 TEST_F(FallbackCursorEventManagerTest, MouseMoveCursorLockOnIFrame) {
307 SetBodyInnerHTML(R"HTML(
308 <style>
309 html, body {
310 margin: 0px;
311 }
312 #ifr {
313 height: 100px;
314 width: 100px;
315 }
316 </style>
317 <iframe id='ifr'></iframe>
318 )HTML");
319
320 SetChildFrameHTML(R"HTML(
321 <style>
322 html, body {
323 margin: 0px;
324 }
325 .big {
326 height: 10000px;
327 width: 10000px;
328 }
329 </style>
330 <div class='big'></div>
331 )HTML");
332 TurnOnFallbackCursorMode();
333
334 // Move below the scroll down line but before mouse down.
335 MouseMove(50, 80);
336 ExpectLock(false, false, false, false);
337 EXPECT_FALSE(GetDocument()
338 .GetFrame()
339 ->GetEventHandler()
340 .fallback_cursor_event_manager_->current_node_);
341
342 // Mouse down and move lock on down.
343 MouseDown(50, 80);
344 MouseMove(50, 80);
345 ExpectLock(false, false, false, true);
346 Node* child_frame_doc = ChildFrame().GetDocument();
347 EXPECT_EQ(GetDocument()
348 .GetFrame()
349 ->GetEventHandler()
350 .fallback_cursor_event_manager_->current_node_.Get(),
351 child_frame_doc);
352
353 // Mouse move out of iframe.
354 MouseMove(200, 200);
355 ExpectLock(false, false, false, false);
356 EXPECT_FALSE(GetDocument()
357 .GetFrame()
358 ->GetEventHandler()
359 .fallback_cursor_event_manager_->current_node_);
360
361 // key back.
362 MouseMove(50, 80);
363 MouseDown(50, 80);
364 EXPECT_EQ(GetDocument()
365 .GetFrame()
366 ->GetEventHandler()
367 .fallback_cursor_event_manager_->current_node_.Get(),
368 child_frame_doc);
369 EXPECT_TRUE(KeyBack());
370 EXPECT_FALSE(GetDocument()
371 .GetFrame()
372 ->GetEventHandler()
373 .fallback_cursor_event_manager_->current_node_);
374 }
375
TEST_F(FallbackCursorEventManagerTest,KeyBackAndMouseMove)376 TEST_F(FallbackCursorEventManagerTest, KeyBackAndMouseMove) {
377 SetBodyInnerHTML(R"HTML(
378 <style>
379 html, body {
380 margin: 0px;
381 }
382 #ifr {
383 height: 100px;
384 width: 100px;
385 }
386 div {
387 height: 10000px;
388 width: 10000px;
389 }
390 </style>
391 <iframe id='ifr'></iframe>
392 <div></div>
393 )HTML");
394
395 SetChildFrameHTML(R"HTML(
396 <style>
397 html, body {
398 margin: 0px;
399 }
400 .big {
401 height: 10000px;
402 width: 10000px;
403 }
404 </style>
405 <div class='big'></div>
406 )HTML");
407 TurnOnFallbackCursorMode();
408
409 // Move below the scroll down line but before mouse down.
410 MouseMove(50, 80);
411 ExpectLock(false, false, false, false);
412 EXPECT_FALSE(GetDocument()
413 .GetFrame()
414 ->GetEventHandler()
415 .fallback_cursor_event_manager_->current_node_);
416
417 // Mouse down and move lock on down.
418 MouseDown(50, 80);
419 MouseMove(50, 80);
420 ExpectLock(false, false, false, true);
421 Node* child_frame_doc = ChildFrame().GetDocument();
422 EXPECT_EQ(GetDocument()
423 .GetFrame()
424 ->GetEventHandler()
425 .fallback_cursor_event_manager_->current_node_.Get(),
426 child_frame_doc);
427
428 // key back.
429 EXPECT_TRUE(KeyBack());
430 EXPECT_FALSE(GetDocument()
431 .GetFrame()
432 ->GetEventHandler()
433 .fallback_cursor_event_manager_->current_node_);
434
435 // Move below the scroll down line of page.
436 MouseMove(100, 500);
437 ExpectLock(false, false, false, true);
438 }
439
TEST_F(FallbackCursorEventManagerTest,MouseDownOnEditor)440 TEST_F(FallbackCursorEventManagerTest, MouseDownOnEditor) {
441 SetBodyInnerHTML(R"HTML(
442 <style>
443 html, body {
444 margin: 0px;
445 }
446 #editor {
447 height: 100px;
448 width: 100px;
449 }
450 </style>
451 <div id='editor' contenteditable='true'>
452 </div>
453 )HTML");
454 TurnOnFallbackCursorMode();
455
456 MouseMove(50, 80);
457 MouseDown(50, 80);
458
459 EXPECT_EQ(GetFallbackCursorChromeClient().cursor_visible_, false);
460
461 Element* editor = GetDocument().getElementById("editor");
462 EXPECT_EQ(GetDocument().FocusedElement(), editor);
463
464 EXPECT_TRUE(KeyBack());
465
466 EXPECT_EQ(GetFallbackCursorChromeClient().cursor_visible_, true);
467 EXPECT_FALSE(GetDocument().FocusedElement());
468 }
469
470 // Ensure the cursor causes correct locking and scrolling when the web page is
471 // zoomed in and the visual viewport is offset.
TEST_F(FallbackCursorEventManagerTest,ZoomedIn)472 TEST_F(FallbackCursorEventManagerTest, ZoomedIn) {
473 SetBodyInnerHTML(R"HTML(
474 <style>
475 html, body {
476 margin: 0px;
477 }
478 .big {
479 height: 10000px;
480 width: 10000px;
481 }
482 </style>
483 <div class='big'></div>
484 )HTML");
485 TurnOnFallbackCursorMode();
486 VisualViewport& visual_viewport =
487 GetDocument().GetPage()->GetVisualViewport();
488 visual_viewport.SetScaleAndLocation(4, /*is_pinch_gesture_active=*/false,
489 FloatPoint(400, 300));
490
491 ASSERT_EQ(IntSize(800, 600), GetDocument().View()->Size());
492 ASSERT_EQ(FloatSize(200, 150), visual_viewport.VisibleRect().Size());
493
494 // Move to the center of the viewport.
495 MouseMoveViewport(IntPoint(400, 300));
496 ExpectLock(false, false, false, false);
497
498 // Move below the scroll down line.
499 MouseMoveViewport(IntPoint(400, 550));
500 ExpectLock(false, false, false, true);
501
502 // Move to the left of scroll left line.
503 MouseMoveViewport(IntPoint(50, 300));
504 ExpectLock(true, false, false, false);
505 }
506
507 // Ensure the cursor causes correct locking in the presence of overflow:hidden.
TEST_F(FallbackCursorEventManagerTest,AccountsForOverflowHidden)508 TEST_F(FallbackCursorEventManagerTest, AccountsForOverflowHidden) {
509 SetBodyInnerHTML(R"HTML(
510 <style>
511 html, body {
512 margin: 0px;
513 }
514 html {
515 overflow-x: hidden;
516 }
517 .big {
518 height: 10000px;
519 width: 10000px;
520 }
521 </style>
522 <div class='big'></div>
523 )HTML");
524 TurnOnFallbackCursorMode();
525 VisualViewport& visual_viewport =
526 GetDocument().GetPage()->GetVisualViewport();
527
528 // Start fully zoomed out.
529 ASSERT_EQ(IntSize(800, 600), GetDocument().View()->Size());
530 ASSERT_EQ(FloatSize(800, 600), visual_viewport.VisibleRect().Size());
531
532 // Move to the center of the viewport.
533 MouseMoveViewport(IntPoint(400, 300));
534 ExpectLock(false, false, false, false);
535
536 // Move to the right scroll region. We don't expect to lock because the visual
537 // viewport has no scroll extent. The layout viewport has scroll extent but
538 // is limited by overflow-x:hidden.
539 MouseMoveViewport(IntPoint(750, 300));
540 ExpectLock(false, false, false, false);
541
542 // Move to the bottom scroll region. Since only overflow-x is hidden, this
543 // should cause locking in the down direction.
544 MouseMoveViewport(IntPoint(400, 550));
545 ExpectLock(false, false, false, true);
546
547 // Now zoom in. Make sure we can still scroll the visual viewport but not the
548 // layout.
549 visual_viewport.SetScaleAndLocation(4, /*is_pinch_gesture_active=*/false,
550 FloatPoint(0, 0));
551 ASSERT_EQ(IntSize(800, 600), GetDocument().View()->Size());
552 ASSERT_EQ(FloatSize(200, 150), visual_viewport.VisibleRect().Size());
553
554 // Move to the right scroll region; since the visual viewport can scroll, we
555 // should expect to lock to the right.
556 MouseMoveViewport(IntPoint(750, 300));
557 ExpectLock(false, true, false, false);
558
559 // Now move the visual viewport to the bottom right corner of the layout
560 // viewport.
561 visual_viewport.SetScaleAndLocation(4, /*is_pinch_gesture_active=*/false,
562 FloatPoint(600, 450));
563
564 // Move mouse to the right scroll region. Since the visual viewport is at the
565 // extent, and the layout viewport isn't user scrollable, we shouldn't cause
566 // locking.
567 MouseMoveViewport(IntPoint(750, 350));
568 ExpectLock(false, false, false, false);
569
570 // Move the mouse to the bottom scroll region, we should expect to lock
571 // because the layout viewport can scroll vertically, even though the visual
572 // viewport is at the extent.
573 MouseMoveViewport(IntPoint(750, 550));
574 ExpectLock(false, false, false, true);
575
576 // Move the mouse to the bottom scroll region, we should expect to lock
577 // because the layout viewport can scroll vertically, even though the visual
578 // viewport is at the extent.
579 MouseMoveViewport(IntPoint(745, 550));
580 ExpectLock(false, false, false, true);
581
582 // Fully scroll the layout viewport to the bottom.
583 GetDocument().View()->LayoutViewport()->SetScrollOffset(
584 ScrollOffset(0, 100000), mojom::blink::ScrollType::kProgrammatic);
585
586 // Move the mouse to the bottom of the viewport, we shouldn't lock because
587 // both layout and visual are at the extent.
588 MouseMoveViewport(IntPoint(740, 550));
589 ExpectLock(false, false, false, false);
590 }
591
TEST_F(FallbackCursorEventManagerTest,NotInCursorMode)592 TEST_F(FallbackCursorEventManagerTest, NotInCursorMode) {
593 GetPage().SetIsCursorVisible(false);
594 EXPECT_FALSE(KeyBack());
595 }
596
597 } // namespace blink
598