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