1 // Copyright (c) 2016 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 "testing/gmock/include/gmock/gmock.h"
6 #include "testing/gtest/include/gtest/gtest.h"
7 #include "third_party/blink/public/platform/web_coalesced_input_event.h"
8 #include "third_party/blink/public/web/web_settings.h"
9 #include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
10 #include "third_party/blink/renderer/core/dom/range.h"
11 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
12 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
13 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
14 #include "third_party/blink/renderer/core/input/event_handler.h"
15 #include "third_party/blink/renderer/core/page/chrome_client.h"
16 #include "third_party/blink/renderer/core/page/context_menu_controller.h"
17 #include "third_party/blink/renderer/core/page/focus_controller.h"
18 #include "third_party/blink/renderer/core/page/page.h"
19 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
20 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
21 #include "ui/base/cursor/cursor.h"
22 #include "ui/base/mojom/cursor_type.mojom-blink.h"
23 
24 using testing::_;
25 
26 namespace blink {
27 
Scaled(IntSize p,float scale)28 IntSize Scaled(IntSize p, float scale) {
29   p.Scale(scale, scale);
30   return p;
31 }
32 
33 class LinkSelectionTestBase : public testing::Test {
34  protected:
35   enum DragFlag { kSendDownEvent = 1, kSendUpEvent = 1 << 1 };
36   using DragFlags = unsigned;
37 
38   void EmulateMouseDrag(const IntPoint& down_point,
39                         const IntPoint& up_point,
40                         int modifiers,
41                         DragFlags = kSendDownEvent | kSendUpEvent);
42 
43   void EmulateMouseClick(const IntPoint& click_point,
44                          WebMouseEvent::Button,
45                          int modifiers,
46                          int count = 1);
47   void EmulateMouseDown(const IntPoint& click_point,
48                         WebMouseEvent::Button,
49                         int modifiers,
50                         int count = 1);
51 
52   String GetSelectionText();
53 
54   frame_test_helpers::WebViewHelper helper_;
55   WebViewImpl* web_view_ = nullptr;
56   Persistent<WebLocalFrameImpl> main_frame_ = nullptr;
57 };
58 
EmulateMouseDrag(const IntPoint & down_point,const IntPoint & up_point,int modifiers,DragFlags drag_flags)59 void LinkSelectionTestBase::EmulateMouseDrag(const IntPoint& down_point,
60                                              const IntPoint& up_point,
61                                              int modifiers,
62                                              DragFlags drag_flags) {
63   if (drag_flags & kSendDownEvent) {
64     const auto& down_event = frame_test_helpers::CreateMouseEvent(
65         WebMouseEvent::kMouseDown, WebMouseEvent::Button::kLeft, down_point,
66         modifiers);
67     web_view_->MainFrameWidget()->HandleInputEvent(
68         WebCoalescedInputEvent(down_event));
69   }
70 
71   const int kMoveEventsNumber = 10;
72   const float kMoveIncrementFraction = 1. / kMoveEventsNumber;
73   const auto& up_down_vector = up_point - down_point;
74   for (int i = 0; i < kMoveEventsNumber; ++i) {
75     const auto& move_point =
76         down_point + Scaled(up_down_vector, i * kMoveIncrementFraction);
77     const auto& move_event = frame_test_helpers::CreateMouseEvent(
78         WebMouseEvent::kMouseMove, WebMouseEvent::Button::kLeft, move_point,
79         modifiers);
80     web_view_->MainFrameWidget()->HandleInputEvent(
81         WebCoalescedInputEvent(move_event));
82   }
83 
84   if (drag_flags & kSendUpEvent) {
85     const auto& up_event = frame_test_helpers::CreateMouseEvent(
86         WebMouseEvent::kMouseUp, WebMouseEvent::Button::kLeft, up_point,
87         modifiers);
88     web_view_->MainFrameWidget()->HandleInputEvent(
89         WebCoalescedInputEvent(up_event));
90   }
91 }
92 
EmulateMouseClick(const IntPoint & click_point,WebMouseEvent::Button button,int modifiers,int count)93 void LinkSelectionTestBase::EmulateMouseClick(const IntPoint& click_point,
94                                               WebMouseEvent::Button button,
95                                               int modifiers,
96                                               int count) {
97   auto event = frame_test_helpers::CreateMouseEvent(
98       WebMouseEvent::kMouseDown, button, click_point, modifiers);
99   event.click_count = count;
100   web_view_->MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(event));
101   event.SetType(WebMouseEvent::kMouseUp);
102   web_view_->MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(event));
103 }
104 
EmulateMouseDown(const IntPoint & click_point,WebMouseEvent::Button button,int modifiers,int count)105 void LinkSelectionTestBase::EmulateMouseDown(const IntPoint& click_point,
106                                              WebMouseEvent::Button button,
107                                              int modifiers,
108                                              int count) {
109   auto event = frame_test_helpers::CreateMouseEvent(
110       WebMouseEvent::kMouseDown, button, click_point, modifiers);
111   event.click_count = count;
112   web_view_->MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(event));
113 }
114 
GetSelectionText()115 String LinkSelectionTestBase::GetSelectionText() {
116   return main_frame_->SelectionAsText();
117 }
118 
119 class TestFrameClient : public frame_test_helpers::TestWebFrameClient {
120  public:
BeginNavigation(std::unique_ptr<blink::WebNavigationInfo> info)121   void BeginNavigation(
122       std::unique_ptr<blink::WebNavigationInfo> info) override {
123     last_policy_ = info->navigation_policy;
124   }
125 
GetLastNavigationPolicy() const126   WebNavigationPolicy GetLastNavigationPolicy() const { return last_policy_; }
127 
128  private:
129   WebNavigationPolicy last_policy_ = kWebNavigationPolicyCurrentTab;
130 };
131 
132 class LinkSelectionTest : public LinkSelectionTestBase {
133  protected:
SetUp()134   void SetUp() override {
135     constexpr char kHTMLString[] =
136         "<a id='link' href='foo.com' style='font-size:20pt'>Text to select "
137         "foobar</a>"
138         "<div id='page_text'>Lorem ipsum dolor sit amet</div>";
139 
140     web_view_ = helper_.Initialize(&test_frame_client_);
141     main_frame_ = web_view_->MainFrameImpl();
142     frame_test_helpers::LoadHTMLString(
143         main_frame_, kHTMLString,
144         url_test_helpers::ToKURL("http://foobar.com"));
145     web_view_->MainFrameWidget()->Resize(WebSize(800, 600));
146     web_view_->GetPage()->GetFocusController().SetActive(true);
147 
148     auto* document = main_frame_->GetFrame()->GetDocument();
149     ASSERT_NE(nullptr, document);
150     auto* link_to_select = document->getElementById("link")->firstChild();
151     ASSERT_NE(nullptr, link_to_select);
152     // We get larger range that we actually want to select, because we need a
153     // slightly larger rect to include the last character to the selection.
154     auto* const range_to_select = MakeGarbageCollected<Range>(
155         *document, link_to_select, 5, link_to_select, 16);
156 
157     const auto& selection_rect = range_to_select->BoundingBox();
158     const auto& selection_rect_center_y = selection_rect.Center().Y();
159     left_point_in_link_ = selection_rect.MinXMinYCorner();
160     left_point_in_link_.SetY(selection_rect_center_y);
161 
162     right_point_in_link_ = selection_rect.MaxXMinYCorner();
163     right_point_in_link_.SetY(selection_rect_center_y);
164     right_point_in_link_.Move(-2, 0);
165   }
166 
TearDown()167   void TearDown() override {
168     // Manually reset since |test_frame_client_| won't outlive |helper_|.
169     helper_.Reset();
170   }
171 
172   TestFrameClient test_frame_client_;
173   IntPoint left_point_in_link_;
174   IntPoint right_point_in_link_;
175 };
176 
TEST_F(LinkSelectionTest,MouseDragWithoutAltAllowNoLinkSelection)177 TEST_F(LinkSelectionTest, MouseDragWithoutAltAllowNoLinkSelection) {
178   EmulateMouseDrag(left_point_in_link_, right_point_in_link_, 0);
179   EXPECT_TRUE(GetSelectionText().IsEmpty());
180 }
181 
TEST_F(LinkSelectionTest,MouseDragWithAltAllowSelection)182 TEST_F(LinkSelectionTest, MouseDragWithAltAllowSelection) {
183   EmulateMouseDrag(left_point_in_link_, right_point_in_link_,
184                    WebInputEvent::kAltKey);
185   EXPECT_EQ("to select", GetSelectionText());
186 }
187 
TEST_F(LinkSelectionTest,HandCursorDuringLinkDrag)188 TEST_F(LinkSelectionTest, HandCursorDuringLinkDrag) {
189   EmulateMouseDrag(right_point_in_link_, left_point_in_link_, 0,
190                    kSendDownEvent);
191   main_frame_->GetFrame()
192       ->LocalFrameRoot()
193       .GetEventHandler()
194       .ScheduleCursorUpdate();
195   test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(50));
196   const auto& cursor =
197       main_frame_->GetFrame()->GetChromeClient().LastSetCursorForTesting();
198   EXPECT_EQ(ui::mojom::blink::CursorType::kHand, cursor.type());
199 }
200 
TEST_F(LinkSelectionTest,DragOnNothingShowsPointer)201 TEST_F(LinkSelectionTest, DragOnNothingShowsPointer) {
202   EmulateMouseDrag(IntPoint(100, 500), IntPoint(300, 500), 0, kSendDownEvent);
203   main_frame_->GetFrame()
204       ->LocalFrameRoot()
205       .GetEventHandler()
206       .ScheduleCursorUpdate();
207   test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(50));
208   const auto& cursor =
209       main_frame_->GetFrame()->GetChromeClient().LastSetCursorForTesting();
210   EXPECT_EQ(ui::mojom::blink::CursorType::kPointer, cursor.type());
211 }
212 
TEST_F(LinkSelectionTest,CaretCursorOverLinkDuringSelection)213 TEST_F(LinkSelectionTest, CaretCursorOverLinkDuringSelection) {
214   EmulateMouseDrag(right_point_in_link_, left_point_in_link_,
215                    WebInputEvent::kAltKey, kSendDownEvent);
216   main_frame_->GetFrame()
217       ->LocalFrameRoot()
218       .GetEventHandler()
219       .ScheduleCursorUpdate();
220   test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(50));
221   const auto& cursor =
222       main_frame_->GetFrame()->GetChromeClient().LastSetCursorForTesting();
223   EXPECT_EQ(ui::mojom::blink::CursorType::kIBeam, cursor.type());
224 }
225 
TEST_F(LinkSelectionTest,HandCursorOverLinkAfterContextMenu)226 TEST_F(LinkSelectionTest, HandCursorOverLinkAfterContextMenu) {
227   // Move mouse.
228   EmulateMouseDrag(right_point_in_link_, left_point_in_link_, 0, 0);
229 
230   // Show context menu. We don't send mouseup event here since in browser it
231   // doesn't reach blink because of shown context menu.
232   EmulateMouseDown(left_point_in_link_, WebMouseEvent::Button::kRight, 0, 1);
233 
234   LocalFrame* frame = main_frame_->GetFrame();
235   // Hide context menu.
236   frame->GetPage()->GetContextMenuController().ClearContextMenu();
237 
238   frame->LocalFrameRoot().GetEventHandler().ScheduleCursorUpdate();
239   test::RunDelayedTasks(base::TimeDelta::FromMilliseconds(50));
240   const auto& cursor =
241       main_frame_->GetFrame()->GetChromeClient().LastSetCursorForTesting();
242   EXPECT_EQ(ui::mojom::blink::CursorType::kHand, cursor.type());
243 }
244 
TEST_F(LinkSelectionTest,SingleClickWithAltStartsDownload)245 TEST_F(LinkSelectionTest, SingleClickWithAltStartsDownload) {
246   EmulateMouseClick(left_point_in_link_, WebMouseEvent::Button::kLeft,
247                     WebInputEvent::kAltKey);
248   EXPECT_EQ(kWebNavigationPolicyDownload,
249             test_frame_client_.GetLastNavigationPolicy());
250 }
251 
TEST_F(LinkSelectionTest,SingleClickWithAltStartsDownloadWhenTextSelected)252 TEST_F(LinkSelectionTest, SingleClickWithAltStartsDownloadWhenTextSelected) {
253   auto* document = main_frame_->GetFrame()->GetDocument();
254   auto* text_to_select = document->getElementById("page_text")->firstChild();
255   ASSERT_NE(nullptr, text_to_select);
256 
257   // Select some page text outside the link element.
258   const auto* range_to_select = MakeGarbageCollected<Range>(
259       *document, text_to_select, 1, text_to_select, 20);
260   const auto& selection_rect = range_to_select->BoundingBox();
261   main_frame_->MoveRangeSelection(selection_rect.MinXMinYCorner(),
262                                   selection_rect.MaxXMaxYCorner());
263   EXPECT_FALSE(GetSelectionText().IsEmpty());
264 
265   EmulateMouseClick(left_point_in_link_, WebMouseEvent::Button::kLeft,
266                     WebInputEvent::kAltKey);
267   EXPECT_EQ(kWebNavigationPolicyDownload,
268             test_frame_client_.GetLastNavigationPolicy());
269 }
270 
271 class LinkSelectionClickEventsTest : public LinkSelectionTestBase {
272  protected:
273   class MockEventListener final : public NativeEventListener {
274    public:
275     MOCK_METHOD2(Invoke, void(ExecutionContext* executionContext, Event*));
276   };
277 
SetUp()278   void SetUp() override {
279     const char* const kHTMLString =
280         "<div id='empty_div' style='width: 100px; height: 100px;'></div>"
281         "<span id='text_div'>Sometexttoshow</span>";
282 
283     web_view_ = helper_.Initialize();
284     main_frame_ = web_view_->MainFrameImpl();
285     frame_test_helpers::LoadHTMLString(
286         main_frame_, kHTMLString,
287         url_test_helpers::ToKURL("http://foobar.com"));
288     web_view_->MainFrameWidget()->Resize(WebSize(800, 600));
289     web_view_->GetPage()->GetFocusController().SetActive(true);
290 
291     auto* document = main_frame_->GetFrame()->GetDocument();
292     ASSERT_NE(nullptr, document);
293 
294     auto* empty_div = document->getElementById("empty_div");
295     auto* text_div = document->getElementById("text_div");
296     ASSERT_NE(nullptr, empty_div);
297     ASSERT_NE(nullptr, text_div);
298   }
299 
CheckMouseClicks(Element & element,bool double_click_event)300   void CheckMouseClicks(Element& element, bool double_click_event) {
301     struct ScopedListenersCleaner {
302       ScopedListenersCleaner(Element* element) : element_(element) {}
303 
304       ~ScopedListenersCleaner() { element_->RemoveAllEventListeners(); }
305 
306       Persistent<Element> element_;
307     } const listeners_cleaner(&element);
308 
309     auto* event_handler = MakeGarbageCollected<MockEventListener>();
310     element.addEventListener(double_click_event ? event_type_names::kDblclick
311                                                 : event_type_names::kClick,
312                              event_handler);
313 
314     testing::InSequence s;
315     EXPECT_CALL(*event_handler, Invoke(_, _)).Times(1);
316 
317     const auto& elem_bounds = element.BoundsInViewport();
318     const int click_count = double_click_event ? 2 : 1;
319     EmulateMouseClick(elem_bounds.Center(), WebMouseEvent::Button::kLeft, 0,
320                       click_count);
321 
322     if (double_click_event) {
323       EXPECT_EQ(element.innerText().IsEmpty(), GetSelectionText().IsEmpty());
324     }
325   }
326 };
327 
TEST_F(LinkSelectionClickEventsTest,SingleAndDoubleClickWillBeHandled)328 TEST_F(LinkSelectionClickEventsTest, SingleAndDoubleClickWillBeHandled) {
329   auto* document = main_frame_->GetFrame()->GetDocument();
330   auto* element = document->getElementById("empty_div");
331 
332   {
333     SCOPED_TRACE("Empty div, single click");
334     CheckMouseClicks(*element, false);
335   }
336 
337   {
338     SCOPED_TRACE("Empty div, double click");
339     CheckMouseClicks(*element, true);
340   }
341 
342   element = document->getElementById("text_div");
343 
344   {
345     SCOPED_TRACE("Text div, single click");
346     CheckMouseClicks(*element, false);
347   }
348 
349   {
350     SCOPED_TRACE("Text div, double click");
351     CheckMouseClicks(*element, true);
352   }
353 }
354 
355 }  // namespace blink
356