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