blob: 925a4a56144208e60aa14356a9ff7b988095f318 [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/renderer/core/input/fallback_cursor_event_manager.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_mouse_event.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/loader/empty_clients.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
namespace {
constexpr size_t kLeft = 0;
constexpr size_t kRight = 1;
constexpr size_t kUp = 2;
constexpr size_t kDown = 3;
} // namespace
#define ExpectLock(l, r, u, d) \
EXPECT_EQ(GetFallbackCursorChromeClient().cursor_lock_[kLeft], l); \
EXPECT_EQ(GetFallbackCursorChromeClient().cursor_lock_[kRight], r); \
EXPECT_EQ(GetFallbackCursorChromeClient().cursor_lock_[kUp], u); \
EXPECT_EQ(GetFallbackCursorChromeClient().cursor_lock_[kDown], d);
namespace blink {
class FallbackCursorChromeClient : public RenderingTestChromeClient {
public:
FallbackCursorChromeClient() {}
void FallbackCursorModeLockCursor(LocalFrame* frame,
bool left,
bool right,
bool up,
bool down) override {
cursor_lock_[0] = left;
cursor_lock_[1] = right;
cursor_lock_[2] = up;
cursor_lock_[3] = down;
}
void FallbackCursorModeSetCursorVisibility(LocalFrame* frame,
bool visible) override {
cursor_visible_ = visible;
}
bool cursor_lock_[4] = {0};
bool cursor_visible_ = true;
private:
DISALLOW_COPY_AND_ASSIGN(FallbackCursorChromeClient);
};
class FallbackCursorEventManagerTest : public RenderingTest,
private ScopedFallbackCursorModeForTest {
protected:
FallbackCursorEventManagerTest()
: RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()),
ScopedFallbackCursorModeForTest(true),
chrome_client_(MakeGarbageCollected<FallbackCursorChromeClient>()) {}
~FallbackCursorEventManagerTest() override {}
RenderingTestChromeClient& GetChromeClient() const override {
return *chrome_client_;
}
FallbackCursorChromeClient& GetFallbackCursorChromeClient() const {
return *chrome_client_;
}
void TurnOnFallbackCursorMode() {
GetDocument().GetFrame()->GetEventHandler().SetIsFallbackCursorModeOn(true);
}
void MouseMove(int x, int y, float scale = 1.0f) {
WebMouseEvent event(WebInputEvent::kMouseMove, WebFloatPoint(x, y),
WebFloatPoint(x, y),
WebPointerProperties::Button::kNoButton, 0,
WebInputEvent::kNoModifiers, base::TimeTicks::Now());
event.SetFrameScale(scale);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
}
// Simulates a mouse move at the given point in the visual viewport (i.e. the
// coordinates relative to the Chrome window).
// TODO(bokan): Replace all above uses with this method.
void MouseMoveViewport(IntPoint point) {
VisualViewport& visual_viewport =
GetDocument().GetPage()->GetVisualViewport();
FloatPoint root_frame_point =
visual_viewport.ViewportToRootFrame(FloatPoint(point));
WebMouseEvent event(WebInputEvent::kMouseMove, root_frame_point,
root_frame_point,
WebPointerProperties::Button::kNoButton, 0,
WebInputEvent::kNoModifiers, base::TimeTicks::Now());
event.SetFrameScale(1.0f);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
}
void MouseDown(int x, int y) {
WebMouseEvent event(
WebInputEvent::kMouseDown, WebFloatPoint(x, y), WebFloatPoint(x, y),
WebPointerProperties::Button::kLeft, 0,
WebInputEvent::Modifiers::kLeftButtonDown, base::TimeTicks::Now());
event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(event);
}
bool KeyBack() {
return GetDocument()
.GetFrame()
->GetEventHandler()
.HandleFallbackCursorModeBackEvent();
}
private:
Persistent<FallbackCursorChromeClient> chrome_client_;
DISALLOW_COPY_AND_ASSIGN(FallbackCursorEventManagerTest);
};
TEST_F(FallbackCursorEventManagerTest, RootFrameNotScrollable) {
SetBodyInnerHTML("A");
TurnOnFallbackCursorMode();
// Mouse move to edge.
MouseMove(0, 0);
ExpectLock(false, false, false, false);
MouseMove(0, 600);
ExpectLock(false, false, false, false);
MouseMove(800, 0);
ExpectLock(false, false, false, false);
MouseMove(800, 600);
ExpectLock(false, false, false, false);
}
TEST_F(FallbackCursorEventManagerTest, ResetOnOutOfFrame) {
SetBodyInnerHTML(R"HTML(
<style>
html, body {
margin: 0px;
}
.big {
height: 10000px;
width: 10000px;
}
</style>
<div class='big'></div>
)HTML");
TurnOnFallbackCursorMode();
// Move below the scroll down line.
MouseMove(100, 500);
ExpectLock(false, false, false, true);
// Ensure an invalid or out-of-bounds mouse move will reset the lock.
MouseMove(-1, -1);
ExpectLock(false, false, false, false);
// Ensure an invalid or out-of-bounds mouse move will reset the lock.
MouseMove(790, 590);
ExpectLock(false, true, false, true);
// Ensure an invalid or out-of-bounds mouse move will reset the lock.
MouseMove(800, 600);
ExpectLock(false, false, false, false);
}
TEST_F(FallbackCursorEventManagerTest, MouseMoveCursorLockOnRootFrame) {
SetBodyInnerHTML(R"HTML(
<style>
html, body {
margin: 0px;
}
.big {
height: 10000px;
width: 10000px;
}
</style>
<div class='big'></div>
)HTML");
TurnOnFallbackCursorMode();
// Move below the scroll down line.
MouseMove(100, 500);
ExpectLock(false, false, false, true);
// Move above the scroll down line.
MouseMove(100, 400);
ExpectLock(false, false, false, false);
// Move to the right of scroll right line.
MouseMove(600, 400);
ExpectLock(false, true, false, false);
}
TEST_F(FallbackCursorEventManagerTest,
MouseMoveCursorLockOnRootFrameWithScale) {
const float SCALE = 0.5f;
SetBodyInnerHTML(R"HTML(
<style>
html, body {
margin: 0px;
}
.big {
height: 10000px;
width: 10000px;
}
</style>
<div class='big'></div>
)HTML");
TurnOnFallbackCursorMode();
// Move below the scroll down line.
MouseMove(50, 250, SCALE);
ExpectLock(false, false, false, true);
// Move above the scroll down line.
MouseMove(50, 200, SCALE);
ExpectLock(false, false, false, false);
// Move to the right of scroll right line.
MouseMove(300, 200, SCALE);
ExpectLock(false, true, false, false);
}
TEST_F(FallbackCursorEventManagerTest, MouseMoveCursorLockOnDiv) {
SetBodyInnerHTML(R"HTML(
<style>
html, body {
margin: 0px;
}
.big {
height: 10000px;
width: 10000px;
}
#d1 {
height: 100px;
width: 100px;
overflow: auto;
}
</style>
<div id='d1'>
<div class='big'></div>
</div>
)HTML");
TurnOnFallbackCursorMode();
// Move below the scroll down line but before mouse down.
MouseMove(50, 80);
ExpectLock(false, false, false, false);
EXPECT_FALSE(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_);
// Mouse down and move lock on down.
MouseDown(50, 80);
Element* d1 = GetDocument().getElementById("d1");
EXPECT_EQ(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_.Get(),
d1);
MouseMove(50, 80);
ExpectLock(false, false, false, true);
// Mouse move out of div.
MouseMove(200, 200);
ExpectLock(false, false, false, false);
EXPECT_FALSE(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_);
// key back.
MouseMove(50, 80);
MouseDown(50, 80);
EXPECT_EQ(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_.Get(),
d1);
EXPECT_TRUE(KeyBack());
EXPECT_FALSE(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_);
}
TEST_F(FallbackCursorEventManagerTest, MouseMoveCursorLockOnIFrame) {
SetBodyInnerHTML(R"HTML(
<style>
html, body {
margin: 0px;
}
#ifr {
height: 100px;
width: 100px;
}
</style>
<iframe id='ifr'></iframe>
)HTML");
SetChildFrameHTML(R"HTML(
<style>
html, body {
margin: 0px;
}
.big {
height: 10000px;
width: 10000px;
}
</style>
<div class='big'></div>
)HTML");
TurnOnFallbackCursorMode();
// Move below the scroll down line but before mouse down.
MouseMove(50, 80);
ExpectLock(false, false, false, false);
EXPECT_FALSE(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_);
// Mouse down and move lock on down.
MouseDown(50, 80);
MouseMove(50, 80);
ExpectLock(false, false, false, true);
Node* child_frame_doc = ChildFrame().GetDocument();
EXPECT_EQ(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_.Get(),
child_frame_doc);
// Mouse move out of iframe.
MouseMove(200, 200);
ExpectLock(false, false, false, false);
EXPECT_FALSE(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_);
// key back.
MouseMove(50, 80);
MouseDown(50, 80);
EXPECT_EQ(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_.Get(),
child_frame_doc);
EXPECT_TRUE(KeyBack());
EXPECT_FALSE(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_);
}
TEST_F(FallbackCursorEventManagerTest, KeyBackAndMouseMove) {
SetBodyInnerHTML(R"HTML(
<style>
html, body {
margin: 0px;
}
#ifr {
height: 100px;
width: 100px;
}
div {
height: 10000px;
width: 10000px;
}
</style>
<iframe id='ifr'></iframe>
<div></div>
)HTML");
SetChildFrameHTML(R"HTML(
<style>
html, body {
margin: 0px;
}
.big {
height: 10000px;
width: 10000px;
}
</style>
<div class='big'></div>
)HTML");
TurnOnFallbackCursorMode();
// Move below the scroll down line but before mouse down.
MouseMove(50, 80);
ExpectLock(false, false, false, false);
EXPECT_FALSE(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_);
// Mouse down and move lock on down.
MouseDown(50, 80);
MouseMove(50, 80);
ExpectLock(false, false, false, true);
Node* child_frame_doc = ChildFrame().GetDocument();
EXPECT_EQ(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_.Get(),
child_frame_doc);
// key back.
EXPECT_TRUE(KeyBack());
EXPECT_FALSE(GetDocument()
.GetFrame()
->GetEventHandler()
.fallback_cursor_event_manager_->current_node_);
// Move below the scroll down line of page.
MouseMove(100, 500);
ExpectLock(false, false, false, true);
}
TEST_F(FallbackCursorEventManagerTest, MouseDownOnEditor) {
SetBodyInnerHTML(R"HTML(
<style>
html, body {
margin: 0px;
}
#editor {
height: 100px;
width: 100px;
}
</style>
<div id='editor' contenteditable='true'>
</div>
)HTML");
TurnOnFallbackCursorMode();
MouseMove(50, 80);
MouseDown(50, 80);
EXPECT_EQ(GetFallbackCursorChromeClient().cursor_visible_, false);
Element* editor = GetDocument().getElementById("editor");
EXPECT_EQ(GetDocument().FocusedElement(), editor);
EXPECT_TRUE(KeyBack());
EXPECT_EQ(GetFallbackCursorChromeClient().cursor_visible_, true);
EXPECT_FALSE(GetDocument().FocusedElement());
}
// Ensure the cursor causes correct locking and scrolling when the web page is
// zoomed in and the visual viewport is offset.
TEST_F(FallbackCursorEventManagerTest, ZoomedIn) {
SetBodyInnerHTML(R"HTML(
<style>
html, body {
margin: 0px;
}
.big {
height: 10000px;
width: 10000px;
}
</style>
<div class='big'></div>
)HTML");
TurnOnFallbackCursorMode();
VisualViewport& visual_viewport =
GetDocument().GetPage()->GetVisualViewport();
visual_viewport.SetScaleAndLocation(4, /*is_pinch_gesture_active=*/false,
FloatPoint(400, 300));
ASSERT_EQ(IntSize(800, 600), GetDocument().View()->Size());
ASSERT_EQ(FloatSize(200, 150), visual_viewport.VisibleRect().Size());
// Move to the center of the viewport.
MouseMoveViewport(IntPoint(400, 300));
ExpectLock(false, false, false, false);
// Move below the scroll down line.
MouseMoveViewport(IntPoint(400, 550));
ExpectLock(false, false, false, true);
// Move to the left of scroll left line.
MouseMoveViewport(IntPoint(50, 300));
ExpectLock(true, false, false, false);
}
// Ensure the cursor causes correct locking in the presence of overflow:hidden.
TEST_F(FallbackCursorEventManagerTest, AccountsForOverflowHidden) {
SetBodyInnerHTML(R"HTML(
<style>
html, body {
margin: 0px;
}
html {
overflow-x: hidden;
}
.big {
height: 10000px;
width: 10000px;
}
</style>
<div class='big'></div>
)HTML");
TurnOnFallbackCursorMode();
VisualViewport& visual_viewport =
GetDocument().GetPage()->GetVisualViewport();
// Start fully zoomed out.
ASSERT_EQ(IntSize(800, 600), GetDocument().View()->Size());
ASSERT_EQ(FloatSize(800, 600), visual_viewport.VisibleRect().Size());
// Move to the center of the viewport.
MouseMoveViewport(IntPoint(400, 300));
ExpectLock(false, false, false, false);
// Move to the right scroll region. We don't expect to lock because the visual
// viewport has no scroll extent. The layout viewport has scroll extent but
// is limited by overflow-x:hidden.
MouseMoveViewport(IntPoint(750, 300));
ExpectLock(false, false, false, false);
// Move to the bottom scroll region. Since only overflow-x is hidden, this
// should cause locking in the down direction.
MouseMoveViewport(IntPoint(400, 550));
ExpectLock(false, false, false, true);
// Now zoom in. Make sure we can still scroll the visual viewport but not the
// layout.
visual_viewport.SetScaleAndLocation(4, /*is_pinch_gesture_active=*/false,
FloatPoint(0, 0));
ASSERT_EQ(IntSize(800, 600), GetDocument().View()->Size());
ASSERT_EQ(FloatSize(200, 150), visual_viewport.VisibleRect().Size());
// Move to the right scroll region; since the visual viewport can scroll, we
// should expect to lock to the right.
MouseMoveViewport(IntPoint(750, 300));
ExpectLock(false, true, false, false);
// Now move the visual viewport to the bottom right corner of the layout
// viewport.
visual_viewport.SetScaleAndLocation(4, /*is_pinch_gesture_active=*/false,
FloatPoint(600, 450));
// Move mouse to the right scroll region. Since the visual viewport is at the
// extent, and the layout viewport isn't user scrollable, we shouldn't cause
// locking.
MouseMoveViewport(IntPoint(750, 350));
ExpectLock(false, false, false, false);
// Move the mouse to the bottom scroll region, we should expect to lock
// because the layout viewport can scroll vertically, even though the visual
// viewport is at the extent.
MouseMoveViewport(IntPoint(750, 550));
ExpectLock(false, false, false, true);
// Move the mouse to the bottom scroll region, we should expect to lock
// because the layout viewport can scroll vertically, even though the visual
// viewport is at the extent.
MouseMoveViewport(IntPoint(745, 550));
ExpectLock(false, false, false, true);
// Fully scroll the layout viewport to the bottom.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 100000), kProgrammaticScroll);
// Move the mouse to the bottom of the viewport, we shouldn't lock because
// both layout and visual are at the extent.
MouseMoveViewport(IntPoint(740, 550));
ExpectLock(false, false, false, false);
}
TEST_F(FallbackCursorEventManagerTest, NotInCursorMode) {
GetPage().SetIsCursorVisible(false);
EXPECT_FALSE(KeyBack());
}
} // namespace blink