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