| // Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "core/dom/Range.h" |
| #include "core/frame/FrameView.h" |
| #include "core/input/EventHandler.h" |
| #include "core/page/ChromeClient.h" |
| #include "core/page/ContextMenuController.h" |
| #include "core/page/FocusController.h" |
| #include "core/page/Page.h" |
| #include "platform/Cursor.h" |
| #include "platform/testing/URLTestHelpers.h" |
| #include "platform/testing/UnitTestHelpers.h" |
| #include "public/web/WebSettings.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "web/WebLocalFrameImpl.h" |
| #include "web/tests/FrameTestHelpers.h" |
| |
| using ::testing::_; |
| |
| namespace blink { |
| |
| IntSize scaled(IntSize p, float scale) { |
| p.scale(scale, scale); |
| return p; |
| } |
| |
| class LinkSelectionTestBase : public ::testing::Test { |
| protected: |
| enum DragFlag { SendDownEvent = 1, SendUpEvent = 1 << 1 }; |
| using DragFlags = unsigned; |
| |
| void emulateMouseDrag(const IntPoint& downPoint, |
| const IntPoint& upPoint, |
| int modifiers, |
| DragFlags = SendDownEvent | SendUpEvent); |
| |
| void emulateMouseClick(const IntPoint& clickPoint, |
| WebMouseEvent::Button, |
| int modifiers, |
| int count = 1); |
| void emulateMouseDown(const IntPoint& clickPoint, |
| WebMouseEvent::Button, |
| int modifiers, |
| int count = 1); |
| |
| String getSelectionText(); |
| |
| FrameTestHelpers::WebViewHelper m_helper; |
| WebViewImpl* m_webView = nullptr; |
| Persistent<WebLocalFrameImpl> m_mainFrame = nullptr; |
| }; |
| |
| void LinkSelectionTestBase::emulateMouseDrag(const IntPoint& downPoint, |
| const IntPoint& upPoint, |
| int modifiers, |
| DragFlags dragFlags) { |
| if (dragFlags & SendDownEvent) { |
| const auto& downEvent = FrameTestHelpers::createMouseEvent( |
| WebMouseEvent::MouseDown, WebMouseEvent::Button::Left, downPoint, |
| modifiers); |
| m_webView->handleInputEvent(downEvent); |
| } |
| |
| const int kMoveEventsNumber = 10; |
| const float kMoveIncrementFraction = 1. / kMoveEventsNumber; |
| const auto& upDownVector = upPoint - downPoint; |
| for (int i = 0; i < kMoveEventsNumber; ++i) { |
| const auto& movePoint = |
| downPoint + scaled(upDownVector, i * kMoveIncrementFraction); |
| const auto& moveEvent = FrameTestHelpers::createMouseEvent( |
| WebMouseEvent::MouseMove, WebMouseEvent::Button::Left, movePoint, |
| modifiers); |
| m_webView->handleInputEvent(moveEvent); |
| } |
| |
| if (dragFlags & SendUpEvent) { |
| const auto& upEvent = FrameTestHelpers::createMouseEvent( |
| WebMouseEvent::MouseUp, WebMouseEvent::Button::Left, upPoint, |
| modifiers); |
| m_webView->handleInputEvent(upEvent); |
| } |
| } |
| |
| void LinkSelectionTestBase::emulateMouseClick(const IntPoint& clickPoint, |
| WebMouseEvent::Button button, |
| int modifiers, |
| int count) { |
| auto event = FrameTestHelpers::createMouseEvent( |
| WebMouseEvent::MouseDown, button, clickPoint, modifiers); |
| event.clickCount = count; |
| m_webView->handleInputEvent(event); |
| event.type = WebMouseEvent::MouseUp; |
| m_webView->handleInputEvent(event); |
| } |
| |
| void LinkSelectionTestBase::emulateMouseDown(const IntPoint& clickPoint, |
| WebMouseEvent::Button button, |
| int modifiers, |
| int count) { |
| auto event = FrameTestHelpers::createMouseEvent( |
| WebMouseEvent::MouseDown, button, clickPoint, modifiers); |
| event.clickCount = count; |
| m_webView->handleInputEvent(event); |
| } |
| |
| String LinkSelectionTestBase::getSelectionText() { |
| return m_mainFrame->selectionAsText(); |
| } |
| |
| class TestFrameClient : public FrameTestHelpers::TestWebFrameClient { |
| public: |
| MOCK_METHOD4(loadURLExternally, |
| void(const WebURLRequest&, |
| WebNavigationPolicy, |
| const WebString& downloadName, |
| bool shouldReplaceCurrentEntry)); |
| }; |
| |
| class LinkSelectionTest : public LinkSelectionTestBase { |
| protected: |
| void SetUp() override { |
| const char* const kHTMLString = |
| "<a id='link' href='foo.com' style='font-size:20pt'>Text to select " |
| "foobar</a>" |
| "<div id='page_text'>Lorem ipsum dolor sit amet</div>"; |
| |
| // We need to set deviceSupportsMouse setting to true and page's focus |
| // controller to active so that FrameView can set the mouse cursor. |
| m_webView = m_helper.initialize( |
| false, &m_testFrameClient, nullptr, nullptr, |
| [](WebSettings* settings) { settings->setDeviceSupportsMouse(true); }); |
| m_mainFrame = m_webView->mainFrameImpl(); |
| FrameTestHelpers::loadHTMLString( |
| m_mainFrame, kHTMLString, URLTestHelpers::toKURL("http://foobar.com")); |
| m_webView->resize(WebSize(800, 600)); |
| m_webView->page()->focusController().setActive(true); |
| |
| auto* document = m_mainFrame->frame()->document(); |
| ASSERT_NE(nullptr, document); |
| auto* linkToSelect = document->getElementById("link")->firstChild(); |
| ASSERT_NE(nullptr, linkToSelect); |
| // We get larger range that we actually want to select, because we need a |
| // slightly larger rect to include the last character to the selection. |
| const auto rangeToSelect = |
| Range::create(*document, linkToSelect, 5, linkToSelect, 16); |
| |
| const auto& selectionRect = rangeToSelect->boundingBox(); |
| const auto& selectionRectCenterY = selectionRect.center().y(); |
| m_leftPointInLink = selectionRect.minXMinYCorner(); |
| m_leftPointInLink.setY(selectionRectCenterY); |
| |
| m_rightPointInLink = selectionRect.maxXMinYCorner(); |
| m_rightPointInLink.setY(selectionRectCenterY); |
| m_rightPointInLink.move(-2, 0); |
| } |
| |
| TestFrameClient m_testFrameClient; |
| IntPoint m_leftPointInLink; |
| IntPoint m_rightPointInLink; |
| }; |
| |
| TEST_F(LinkSelectionTest, MouseDragWithoutAltAllowNoLinkSelection) { |
| emulateMouseDrag(m_leftPointInLink, m_rightPointInLink, 0); |
| EXPECT_TRUE(getSelectionText().isEmpty()); |
| } |
| |
| TEST_F(LinkSelectionTest, MouseDragWithAltAllowSelection) { |
| emulateMouseDrag(m_leftPointInLink, m_rightPointInLink, |
| WebInputEvent::AltKey); |
| EXPECT_EQ("to select", getSelectionText()); |
| } |
| |
| TEST_F(LinkSelectionTest, HandCursorDuringLinkDrag) { |
| emulateMouseDrag(m_rightPointInLink, m_leftPointInLink, 0, SendDownEvent); |
| m_mainFrame->frame()->localFrameRoot()->eventHandler().scheduleCursorUpdate(); |
| testing::runDelayedTasks(50); |
| const auto& cursor = |
| m_mainFrame->frame()->chromeClient().lastSetCursorForTesting(); |
| EXPECT_EQ(Cursor::Hand, cursor.getType()); |
| } |
| |
| TEST_F(LinkSelectionTest, CaretCursorOverLinkDuringSelection) { |
| emulateMouseDrag(m_rightPointInLink, m_leftPointInLink, WebInputEvent::AltKey, |
| SendDownEvent); |
| m_mainFrame->frame()->localFrameRoot()->eventHandler().scheduleCursorUpdate(); |
| testing::runDelayedTasks(50); |
| const auto& cursor = |
| m_mainFrame->frame()->chromeClient().lastSetCursorForTesting(); |
| EXPECT_EQ(Cursor::IBeam, cursor.getType()); |
| } |
| |
| TEST_F(LinkSelectionTest, HandCursorOverLinkAfterContextMenu) { |
| // Move mouse. |
| emulateMouseDrag(m_rightPointInLink, m_leftPointInLink, 0, 0); |
| |
| // Show context menu. We don't send mouseup event here since in browser it |
| // doesn't reach blink because of shown context menu. |
| emulateMouseDown(m_leftPointInLink, WebMouseEvent::Button::Right, 0, 1); |
| |
| LocalFrame* frame = m_mainFrame->frame(); |
| // Hide context menu. |
| frame->page()->contextMenuController().clearContextMenu(); |
| |
| frame->localFrameRoot()->eventHandler().scheduleCursorUpdate(); |
| testing::runDelayedTasks(50); |
| const auto& cursor = |
| m_mainFrame->frame()->chromeClient().lastSetCursorForTesting(); |
| EXPECT_EQ(Cursor::Hand, cursor.getType()); |
| } |
| |
| TEST_F(LinkSelectionTest, SingleClickWithAltStartsDownload) { |
| EXPECT_CALL( |
| m_testFrameClient, |
| loadURLExternally(_, WebNavigationPolicy::WebNavigationPolicyDownload, |
| WebString(), _)); |
| emulateMouseClick(m_leftPointInLink, WebMouseEvent::Button::Left, |
| WebInputEvent::AltKey); |
| } |
| |
| TEST_F(LinkSelectionTest, SingleClickWithAltStartsDownloadWhenTextSelected) { |
| auto* document = m_mainFrame->frame()->document(); |
| auto* textToSelect = document->getElementById("page_text")->firstChild(); |
| ASSERT_NE(nullptr, textToSelect); |
| |
| // Select some page text outside the link element. |
| const Range* rangeToSelect = |
| Range::create(*document, textToSelect, 1, textToSelect, 20); |
| const auto& selectionRect = rangeToSelect->boundingBox(); |
| m_mainFrame->moveRangeSelection(selectionRect.minXMinYCorner(), |
| selectionRect.maxXMaxYCorner()); |
| EXPECT_FALSE(getSelectionText().isEmpty()); |
| |
| EXPECT_CALL( |
| m_testFrameClient, |
| loadURLExternally(_, WebNavigationPolicy::WebNavigationPolicyDownload, |
| WebString(), _)); |
| emulateMouseClick(m_leftPointInLink, WebMouseEvent::Button::Left, |
| WebInputEvent::AltKey); |
| } |
| |
| class LinkSelectionClickEventsTest : public LinkSelectionTestBase { |
| protected: |
| class MockEventListener final : public EventListener { |
| public: |
| static MockEventListener* create() { return new MockEventListener(); } |
| |
| bool operator==(const EventListener& other) const final { |
| return this == &other; |
| } |
| |
| MOCK_METHOD2(handleEvent, void(ExecutionContext* executionContext, Event*)); |
| |
| private: |
| MockEventListener() : EventListener(CPPEventListenerType) {} |
| }; |
| |
| void SetUp() override { |
| const char* const kHTMLString = |
| "<div id='empty_div' style='width: 100px; height: 100px;'></div>" |
| "<span id='text_div'>Sometexttoshow</span>"; |
| |
| m_webView = m_helper.initialize(false); |
| m_mainFrame = m_webView->mainFrameImpl(); |
| FrameTestHelpers::loadHTMLString( |
| m_mainFrame, kHTMLString, URLTestHelpers::toKURL("http://foobar.com")); |
| m_webView->resize(WebSize(800, 600)); |
| m_webView->page()->focusController().setActive(true); |
| |
| auto* document = m_mainFrame->frame()->document(); |
| ASSERT_NE(nullptr, document); |
| |
| auto* emptyDiv = document->getElementById("empty_div"); |
| auto* textDiv = document->getElementById("text_div"); |
| ASSERT_NE(nullptr, emptyDiv); |
| ASSERT_NE(nullptr, textDiv); |
| } |
| |
| void checkMouseClicks(Element& element, bool doubleClickEvent) { |
| struct ScopedListenersCleaner { |
| ScopedListenersCleaner(Element* element) : m_element(element) {} |
| |
| ~ScopedListenersCleaner() { m_element->removeAllEventListeners(); } |
| |
| Persistent<Element> m_element; |
| } const listenersCleaner(&element); |
| |
| MockEventListener* eventHandler = MockEventListener::create(); |
| element.addEventListener( |
| doubleClickEvent ? EventTypeNames::dblclick : EventTypeNames::click, |
| eventHandler); |
| |
| ::testing::InSequence s; |
| EXPECT_CALL(*eventHandler, handleEvent(_, _)).Times(1); |
| |
| const auto& elemBounds = element.boundsInViewport(); |
| const int clickCount = doubleClickEvent ? 2 : 1; |
| emulateMouseClick(elemBounds.center(), WebMouseEvent::Button::Left, 0, |
| clickCount); |
| |
| if (doubleClickEvent) { |
| EXPECT_EQ(element.innerText().isEmpty(), getSelectionText().isEmpty()); |
| } |
| } |
| }; |
| |
| TEST_F(LinkSelectionClickEventsTest, SingleAndDoubleClickWillBeHandled) { |
| auto* document = m_mainFrame->frame()->document(); |
| auto* element = document->getElementById("empty_div"); |
| |
| { |
| SCOPED_TRACE("Empty div, single click"); |
| checkMouseClicks(*element, false); |
| } |
| |
| { |
| SCOPED_TRACE("Empty div, double click"); |
| checkMouseClicks(*element, true); |
| } |
| |
| element = document->getElementById("text_div"); |
| |
| { |
| SCOPED_TRACE("Text div, single click"); |
| checkMouseClicks(*element, false); |
| } |
| |
| { |
| SCOPED_TRACE("Text div, double click"); |
| checkMouseClicks(*element, true); |
| } |
| } |
| |
| } // namespace blink |