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