blob: cb91fcbb89dedee5aafc159ed7f865e8bb280b2e [file] [log] [blame]
// 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