blob: 195a24466b57daee7853c9ae9c1c0ffe795dde8c [file] [log] [blame]
// Copyright 2015 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/event_handler.h"
#include <memory>
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_keyboard_event.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/range.h"
#include "third_party/blink/renderer/core/editing/editing_behavior.h"
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/selection_controller.h"
#include "third_party/blink/renderer/core/editing/selection_template.h"
#include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/loader/empty_clients.h"
#include "third_party/blink/renderer/core/page/autoscroll_controller.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/keyboard_codes.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
class EventHandlerTest : public PageTestBase {
protected:
void SetUp() override;
void SetHtmlInnerHTML(const char* html_content);
ShadowRoot* SetShadowContent(const char* shadow_content, const char* host);
};
class EventHandlerSimTest : public SimTest {};
class TapEventBuilder : public WebGestureEvent {
public:
TapEventBuilder(FloatPoint position, int tap_count)
: WebGestureEvent(WebInputEvent::kGestureTap,
WebInputEvent::kNoModifiers,
CurrentTimeTicks(),
WebGestureDevice::kTouchscreen) {
SetPositionInWidget(position);
SetPositionInScreen(position);
data.tap.tap_count = tap_count;
data.tap.width = 5;
data.tap.height = 5;
frame_scale_ = 1;
}
};
class TapDownEventBuilder : public WebGestureEvent {
public:
TapDownEventBuilder(FloatPoint position)
: WebGestureEvent(WebInputEvent::kGestureTapDown,
WebInputEvent::kNoModifiers,
CurrentTimeTicks(),
WebGestureDevice::kTouchscreen) {
SetPositionInWidget(position);
SetPositionInScreen(position);
data.tap_down.width = 5;
data.tap_down.height = 5;
frame_scale_ = 1;
}
};
class ShowPressEventBuilder : public WebGestureEvent {
public:
ShowPressEventBuilder(FloatPoint position)
: WebGestureEvent(WebInputEvent::kGestureShowPress,
WebInputEvent::kNoModifiers,
CurrentTimeTicks(),
WebGestureDevice::kTouchscreen) {
SetPositionInWidget(position);
SetPositionInScreen(position);
data.show_press.width = 5;
data.show_press.height = 5;
frame_scale_ = 1;
}
};
class LongPressEventBuilder : public WebGestureEvent {
public:
LongPressEventBuilder(FloatPoint position)
: WebGestureEvent(WebInputEvent::kGestureLongPress,
WebInputEvent::kNoModifiers,
CurrentTimeTicks(),
WebGestureDevice::kTouchscreen) {
SetPositionInWidget(position);
SetPositionInScreen(position);
data.long_press.width = 5;
data.long_press.height = 5;
frame_scale_ = 1;
}
};
class MousePressEventBuilder : public WebMouseEvent {
public:
MousePressEventBuilder(IntPoint position_param,
int click_count_param,
WebMouseEvent::Button button_param)
: WebMouseEvent(WebInputEvent::kMouseDown,
WebInputEvent::kNoModifiers,
CurrentTimeTicks()) {
click_count = click_count_param;
button = button_param;
SetPositionInWidget(position_param.X(), position_param.Y());
SetPositionInScreen(position_param.X(), position_param.Y());
frame_scale_ = 1;
}
};
void EventHandlerTest::SetUp() {
PageTestBase::SetUp(IntSize(300, 400));
}
void EventHandlerTest::SetHtmlInnerHTML(const char* html_content) {
GetDocument().documentElement()->SetInnerHTMLFromString(
String::FromUTF8(html_content));
UpdateAllLifecyclePhasesForTest();
}
ShadowRoot* EventHandlerTest::SetShadowContent(const char* shadow_content,
const char* host) {
ShadowRoot* shadow_root =
EditingTestBase::CreateShadowRootForElementWithIDAndSetInnerHTML(
GetDocument(), host, shadow_content);
return shadow_root;
}
TEST_F(EventHandlerTest, dragSelectionAfterScroll) {
SetHtmlInnerHTML(
"<style> body { margin: 0px; } .upper { width: 300px; height: 400px; }"
".lower { margin: 0px; width: 300px; height: 400px; } .line { display: "
"block; width: 300px; height: 30px; } </style>"
"<div class='upper'></div>"
"<div class='lower'>"
"<span class='line'>Line 1</span><span class='line'>Line 2</span><span "
"class='line'>Line 3</span><span class='line'>Line 4</span><span "
"class='line'>Line 5</span>"
"<span class='line'>Line 6</span><span class='line'>Line 7</span><span "
"class='line'>Line 8</span><span class='line'>Line 9</span><span "
"class='line'>Line 10</span>"
"</div>");
LocalFrameView* frame_view = GetDocument().View();
frame_view->LayoutViewport()->SetScrollOffset(ScrollOffset(0, 400),
kProgrammaticScroll);
WebMouseEvent mouse_down_event(WebInputEvent::kMouseDown, WebFloatPoint(0, 0),
WebFloatPoint(100, 200),
WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_down_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_down_event);
ASSERT_TRUE(GetDocument()
.GetFrame()
->GetEventHandler()
.GetSelectionController()
.MouseDownMayStartSelect());
WebMouseEvent mouse_move_event(
WebInputEvent::kMouseMove, WebFloatPoint(100, 50),
WebFloatPoint(200, 250), WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_move_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
GetPage().GetAutoscrollController().Animate();
GetPage().Animator().ServiceScriptedAnimations(WTF::CurrentTimeTicks());
WebMouseEvent mouse_up_event(
WebMouseEvent::kMouseUp, WebFloatPoint(100, 50), WebFloatPoint(200, 250),
WebPointerProperties::Button::kLeft, 1, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_up_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseReleaseEvent(
mouse_up_event);
ASSERT_FALSE(GetDocument()
.GetFrame()
->GetEventHandler()
.GetSelectionController()
.MouseDownMayStartSelect());
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsRange());
Range* range =
CreateRange(EphemeralRange(Selection().GetSelectionInDOMTree().Base(),
Selection().GetSelectionInDOMTree().Extent()));
ASSERT_TRUE(range);
EXPECT_EQ("Line 1\nLine 2", range->GetText());
}
TEST_F(EventHandlerTest, multiClickSelectionFromTap) {
SetHtmlInnerHTML(
"<style> body { margin: 0px; } .line { display: block; width: 300px; "
"height: 30px; } </style>"
"<body contenteditable='true'><span class='line' id='line'>One Two "
"Three</span></body>");
Node* line = GetDocument().getElementById("line")->firstChild();
TapEventBuilder single_tap_event(FloatPoint(0, 0), 1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
single_tap_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
EXPECT_EQ(Position(line, 0), Selection().GetSelectionInDOMTree().Base());
// Multi-tap events on editable elements should trigger selection, just
// like multi-click events.
TapEventBuilder double_tap_event(FloatPoint(0, 0), 2);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
double_tap_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsRange());
EXPECT_EQ(Position(line, 0), Selection().GetSelectionInDOMTree().Base());
if (GetDocument()
.GetFrame()
->GetEditor()
.IsSelectTrailingWhitespaceEnabled()) {
EXPECT_EQ(Position(line, 4), Selection().GetSelectionInDOMTree().Extent());
EXPECT_EQ("One ", WebString(Selection().SelectedText()).Utf8());
} else {
EXPECT_EQ(Position(line, 3), Selection().GetSelectionInDOMTree().Extent());
EXPECT_EQ("One", WebString(Selection().SelectedText()).Utf8());
}
TapEventBuilder triple_tap_event(FloatPoint(0, 0), 3);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
triple_tap_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsRange());
EXPECT_EQ(Position(line, 0), Selection().GetSelectionInDOMTree().Base());
EXPECT_EQ(Position(line, 13), Selection().GetSelectionInDOMTree().Extent());
EXPECT_EQ("One Two Three", WebString(Selection().SelectedText()).Utf8());
}
TEST_F(EventHandlerTest, multiClickSelectionFromTapDisabledIfNotEditable) {
SetHtmlInnerHTML(
"<style> body { margin: 0px; } .line { display: block; width: 300px; "
"height: 30px; } </style>"
"<span class='line' id='line'>One Two Three</span>");
Node* line = GetDocument().getElementById("line")->firstChild();
TapEventBuilder single_tap_event(FloatPoint(0, 0), 1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
single_tap_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
EXPECT_EQ(Position(line, 0), Selection().GetSelectionInDOMTree().Base());
// As the text is readonly, multi-tap events should not trigger selection.
TapEventBuilder double_tap_event(FloatPoint(0, 0), 2);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
double_tap_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
EXPECT_EQ(Position(line, 0), Selection().GetSelectionInDOMTree().Base());
TapEventBuilder triple_tap_event(FloatPoint(0, 0), 3);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
triple_tap_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
EXPECT_EQ(Position(line, 0), Selection().GetSelectionInDOMTree().Base());
}
TEST_F(EventHandlerTest, draggedInlinePositionTest) {
SetHtmlInnerHTML(
"<style>"
"body { margin: 0px; }"
".line { font-family: sans-serif; background: blue; width: 300px; "
"height: 30px; font-size: 40px; margin-left: 250px; }"
"</style>"
"<div style='width: 300px; height: 100px;'>"
"<span class='line' draggable='true'>abcd</span>"
"</div>");
WebMouseEvent mouse_down_event(WebMouseEvent::kMouseDown,
WebFloatPoint(262, 29), WebFloatPoint(329, 67),
WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_down_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_down_event);
WebMouseEvent mouse_move_event(
WebMouseEvent::kMouseMove, WebFloatPoint(618, 298),
WebFloatPoint(685, 436), WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_move_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
EXPECT_EQ(IntPoint(12, 29), GetDocument()
.GetFrame()
->GetEventHandler()
.DragDataTransferLocationForTesting());
}
TEST_F(EventHandlerTest, draggedSVGImagePositionTest) {
SetHtmlInnerHTML(
"<style>"
"body { margin: 0px; }"
"[draggable] {"
"-webkit-user-select: none; user-select: none; -webkit-user-drag: "
"element; }"
"</style>"
"<div style='width: 300px; height: 100px;'>"
"<svg width='500' height='500'>"
"<rect x='100' y='100' width='100px' height='100px' fill='blue' "
"draggable='true'/>"
"</svg>"
"</div>");
WebMouseEvent mouse_down_event(
WebMouseEvent::kMouseDown, WebFloatPoint(145, 144),
WebFloatPoint(212, 282), WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_down_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_down_event);
WebMouseEvent mouse_move_event(
WebMouseEvent::kMouseMove, WebFloatPoint(618, 298),
WebFloatPoint(685, 436), WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_move_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
EXPECT_EQ(IntPoint(45, 44), GetDocument()
.GetFrame()
->GetEventHandler()
.DragDataTransferLocationForTesting());
}
TEST_F(EventHandlerTest, HitOnNothingDoesNotShowIBeam) {
SetHtmlInnerHTML("");
HitTestLocation location((LayoutPoint(10, 10)));
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_FALSE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(
GetDocument().body(), hit));
}
TEST_F(EventHandlerTest, HitOnTextShowsIBeam) {
SetHtmlInnerHTML("blabla");
Node* const text = GetDocument().body()->firstChild();
HitTestLocation location(
text->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_TRUE(text->CanStartSelection());
EXPECT_TRUE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(text,
hit));
}
TEST_F(EventHandlerTest, HitOnUserSelectNoneDoesNotShowIBeam) {
SetHtmlInnerHTML("<span style='user-select: none'>blabla</span>");
Node* const text = GetDocument().body()->firstChild()->firstChild();
HitTestLocation location(
text->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_FALSE(text->CanStartSelection());
EXPECT_FALSE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(text,
hit));
}
TEST_F(EventHandlerTest, ShadowChildCanOverrideUserSelectNone) {
SetHtmlInnerHTML("<p style='user-select: none' id='host'></p>");
ShadowRoot* const shadow_root = SetShadowContent(
"<span style='user-select: text' id='bla'>blabla</span>", "host");
Node* const text = shadow_root->getElementById("bla")->firstChild();
HitTestLocation location(
text->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_TRUE(text->CanStartSelection());
EXPECT_TRUE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(text,
hit));
}
TEST_F(EventHandlerTest, UserSelectAllCanOverrideUserSelectNone) {
SetHtmlInnerHTML(
"<div style='user-select: none'>"
"<span style='user-select: all'>blabla</span>"
"</div>");
Node* const text =
GetDocument().body()->firstChild()->firstChild()->firstChild();
HitTestLocation location(
text->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_TRUE(text->CanStartSelection());
EXPECT_TRUE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(text,
hit));
}
TEST_F(EventHandlerTest, UserSelectNoneCanOverrideUserSelectAll) {
SetHtmlInnerHTML(
"<div style='user-select: all'>"
"<span style='user-select: none'>blabla</span>"
"</div>");
Node* const text =
GetDocument().body()->firstChild()->firstChild()->firstChild();
HitTestLocation location(
text->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_FALSE(text->CanStartSelection());
EXPECT_FALSE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(text,
hit));
}
TEST_F(EventHandlerTest, UserSelectTextCanOverrideUserSelectNone) {
SetHtmlInnerHTML(
"<div style='user-select: none'>"
"<span style='user-select: text'>blabla</span>"
"</div>");
Node* const text =
GetDocument().body()->firstChild()->firstChild()->firstChild();
HitTestLocation location(
text->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_TRUE(text->CanStartSelection());
EXPECT_TRUE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(text,
hit));
}
TEST_F(EventHandlerTest, UserSelectNoneCanOverrideUserSelectText) {
SetHtmlInnerHTML(
"<div style='user-select: text'>"
"<span style='user-select: none'>blabla</span>"
"</div>");
Node* const text = GetDocument().body()->firstChild()->firstChild()->firstChild();
HitTestLocation location(
text->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_FALSE(text->CanStartSelection());
EXPECT_FALSE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(text,
hit));
}
TEST_F(EventHandlerTest, ShadowChildCanOverrideUserSelectText) {
SetHtmlInnerHTML("<p style='user-select: text' id='host'></p>");
ShadowRoot* const shadow_root = SetShadowContent(
"<span style='user-select: none' id='bla'>blabla</span>", "host");
Node* const text = shadow_root->getElementById("bla")->firstChild();
HitTestLocation location(
text->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_FALSE(text->CanStartSelection());
EXPECT_FALSE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(text,
hit));
}
TEST_F(EventHandlerTest, InputFieldsCanStartSelection) {
SetHtmlInnerHTML("<input value='blabla'>");
auto* const field = ToHTMLInputElement(GetDocument().body()->firstChild());
Element* const text = field->InnerEditorElement();
HitTestLocation location(
text->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_TRUE(text->CanStartSelection());
EXPECT_TRUE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(text,
hit));
}
TEST_F(EventHandlerTest, ReadOnlyInputDoesNotInheritUserSelect) {
SetHtmlInnerHTML(
"<div style='user-select: none'>"
"<input id='sample' readonly value='blabla'>"
"</div>");
HTMLInputElement* const input =
ToHTMLInputElement(GetDocument().getElementById("sample"));
Node* const text = input->InnerEditorElement()->firstChild();
HitTestLocation location(
text->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_TRUE(text->CanStartSelection());
EXPECT_TRUE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(text,
hit));
}
TEST_F(EventHandlerTest, ImagesCannotStartSelection) {
SetHtmlInnerHTML("<img>");
Element* const img = ToElement(GetDocument().body()->firstChild());
HitTestLocation location(
img->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult hit =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
EXPECT_FALSE(img->CanStartSelection());
EXPECT_FALSE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(img,
hit));
}
TEST_F(EventHandlerTest, AnchorTextCannotStartSelection) {
SetHtmlInnerHTML("<a href='bala'>link text</a>");
Node* const link = GetDocument().body()->firstChild();
HitTestLocation location(
link->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult result =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
Node* const text = link->firstChild();
EXPECT_FALSE(text->CanStartSelection());
EXPECT_TRUE(result.IsOverLink());
// ShouldShowIBeamForNode() returns |cursor: auto|'s value.
// In https://github.com/w3c/csswg-drafts/issues/1598 it was decided that:
// a { cursor: auto } /* gives I-beam over links */
EXPECT_TRUE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(
text, result));
EXPECT_EQ(GetDocument()
.GetFrame()
->GetEventHandler()
.SelectCursor(location, result)
.GetCursor()
.GetType(),
Cursor::Type::kHand); // A hand signals ability to navigate.
}
TEST_F(EventHandlerTest, EditableAnchorTextCanStartSelection) {
SetHtmlInnerHTML("<a contenteditable='true' href='bala'>editable link</a>");
Node* const link = GetDocument().body()->firstChild();
HitTestLocation location(
link->GetLayoutObject()->AbsoluteBoundingBoxRect().Center());
HitTestResult result =
GetDocument().GetFrame()->GetEventHandler().HitTestResultAtLocation(
location);
Node* const text = link->firstChild();
EXPECT_TRUE(text->CanStartSelection());
EXPECT_TRUE(result.IsOverLink());
EXPECT_TRUE(
GetDocument().GetFrame()->GetEventHandler().ShouldShowIBeamForNode(
text, result));
EXPECT_EQ(GetDocument()
.GetFrame()
->GetEventHandler()
.SelectCursor(location, result)
.GetCursor()
.GetType(),
Cursor::Type::kIBeam); // An I-beam signals editability.
}
// Regression test for http://crbug.com/641403 to verify we use up-to-date
// layout tree for dispatching "contextmenu" event.
TEST_F(EventHandlerTest, sendContextMenuEventWithHover) {
SetHtmlInnerHTML(
"<style>*:hover { color: red; }</style>"
"<div>foo</div>");
GetDocument().GetSettings()->SetScriptEnabled(true);
Element* script = GetDocument().CreateRawElement(html_names::kScriptTag);
script->SetInnerHTMLFromString(
"document.addEventListener('contextmenu', event => "
"event.preventDefault());");
GetDocument().body()->AppendChild(script);
GetDocument().UpdateStyleAndLayout();
GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
SelectionInDOMTree::Builder()
.Collapse(Position(GetDocument().body(), 0))
.Build());
WebMouseEvent mouse_down_event(
WebMouseEvent::kMouseDown, WebFloatPoint(0, 0), WebFloatPoint(100, 200),
WebPointerProperties::Button::kRight, 1,
WebInputEvent::Modifiers::kRightButtonDown, CurrentTimeTicks());
mouse_down_event.SetFrameScale(1);
EXPECT_EQ(WebInputEventResult::kHandledApplication,
GetDocument().GetFrame()->GetEventHandler().SendContextMenuEvent(
mouse_down_event));
}
TEST_F(EventHandlerTest, EmptyTextfieldInsertionOnTap) {
SetHtmlInnerHTML("<textarea cols=50 rows=50></textarea>");
TapEventBuilder single_tap_event(FloatPoint(200, 200), 1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
single_tap_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
ASSERT_FALSE(Selection().IsHandleVisible());
}
TEST_F(EventHandlerTest, NonEmptyTextfieldInsertionOnTap) {
SetHtmlInnerHTML("<textarea cols=50 rows=50>Enter text</textarea>");
TapEventBuilder single_tap_event(FloatPoint(200, 200), 1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
single_tap_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
ASSERT_TRUE(Selection().IsHandleVisible());
}
TEST_F(EventHandlerTest, NewlineDivInsertionOnTap) {
SetHtmlInnerHTML("<div contenteditable><br/></div>");
TapEventBuilder single_tap_event(FloatPoint(10, 10), 1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
single_tap_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
ASSERT_TRUE(Selection().IsHandleVisible());
}
TEST_F(EventHandlerTest, EmptyTextfieldInsertionOnLongPress) {
SetHtmlInnerHTML("<textarea cols=50 rows=50></textarea>");
LongPressEventBuilder long_press_event(FloatPoint(200, 200));
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
long_press_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
ASSERT_TRUE(Selection().IsHandleVisible());
// Single Tap on an empty edit field should clear insertion handle
TapEventBuilder single_tap_event(FloatPoint(200, 200), 1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
single_tap_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
ASSERT_FALSE(Selection().IsHandleVisible());
}
TEST_F(EventHandlerTest, NonEmptyTextfieldInsertionOnLongPress) {
SetHtmlInnerHTML("<textarea cols=50 rows=50>Enter text</textarea>");
LongPressEventBuilder long_press_event(FloatPoint(200, 200));
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
long_press_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
ASSERT_TRUE(Selection().IsHandleVisible());
}
TEST_F(EventHandlerTest, ClearHandleAfterTap) {
SetHtmlInnerHTML("<textarea cols=50 rows=10>Enter text</textarea>");
// Show handle
LongPressEventBuilder long_press_event(FloatPoint(200, 10));
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
long_press_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
ASSERT_TRUE(Selection().IsHandleVisible());
// Tap away from text area should clear handle
TapEventBuilder single_tap_event(FloatPoint(200, 350), 1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
single_tap_event);
ASSERT_FALSE(Selection().IsHandleVisible());
}
TEST_F(EventHandlerTest, HandleNotShownOnMouseEvents) {
SetHtmlInnerHTML("<textarea cols=50 rows=50>Enter text</textarea>");
MousePressEventBuilder left_mouse_press_event(
IntPoint(200, 200), 1, WebPointerProperties::Button::kLeft);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
left_mouse_press_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
ASSERT_FALSE(Selection().IsHandleVisible());
MousePressEventBuilder right_mouse_press_event(
IntPoint(200, 200), 1, WebPointerProperties::Button::kRight);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
right_mouse_press_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
ASSERT_FALSE(Selection().IsHandleVisible());
MousePressEventBuilder double_click_mouse_press_event(
IntPoint(200, 200), 2, WebPointerProperties::Button::kLeft);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
double_click_mouse_press_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsRange());
ASSERT_FALSE(Selection().IsHandleVisible());
MousePressEventBuilder triple_click_mouse_press_event(
IntPoint(200, 200), 3, WebPointerProperties::Button::kLeft);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
triple_click_mouse_press_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsRange());
ASSERT_FALSE(Selection().IsHandleVisible());
}
TEST_F(EventHandlerTest, MisspellingContextMenuEvent) {
if (GetDocument()
.GetFrame()
->GetEditor()
.Behavior()
.ShouldSelectOnContextualMenuClick())
return;
SetHtmlInnerHTML("<textarea cols=50 rows=50>Mispellinggg</textarea>");
TapEventBuilder single_tap_event(FloatPoint(10, 10), 1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
single_tap_event);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
ASSERT_TRUE(Selection().IsHandleVisible());
GetDocument().GetFrame()->GetEventHandler().ShowNonLocatedContextMenu(
nullptr, kMenuSourceTouchHandle);
ASSERT_TRUE(Selection().GetSelectionInDOMTree().IsCaret());
ASSERT_TRUE(Selection().IsHandleVisible());
}
TEST_F(EventHandlerTest, dragEndInNewDrag) {
SetHtmlInnerHTML(
"<style>.box { width: 100px; height: 100px; display: block; }</style>"
"<a class='box' href=''>Drag me</a>");
WebMouseEvent mouse_down_event(
WebInputEvent::kMouseDown, WebFloatPoint(50, 50), WebFloatPoint(50, 50),
WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown, CurrentTimeTicks());
mouse_down_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_down_event);
WebMouseEvent mouse_move_event(
WebInputEvent::kMouseMove, WebFloatPoint(51, 50), WebFloatPoint(51, 50),
WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown, CurrentTimeTicks());
mouse_move_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
// This reproduces what might be the conditions of http://crbug.com/677916
//
// TODO(crbug.com/682047): The call sequence below should not occur outside
// this contrived test. Given the current code, it is unclear how the
// dragSourceEndedAt() call could occur before a drag operation is started.
WebMouseEvent mouse_up_event(WebInputEvent::kMouseUp, WebFloatPoint(100, 50),
WebFloatPoint(200, 250),
WebPointerProperties::Button::kLeft, 1,
WebInputEvent::kNoModifiers, CurrentTimeTicks());
mouse_up_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().DragSourceEndedAt(
mouse_up_event, kDragOperationNone);
// This test passes if it doesn't crash.
}
// This test mouse move with modifier kRelativeMotionEvent
// should not start drag.
TEST_F(EventHandlerTest, FakeMouseMoveNotStartDrag) {
SetHtmlInnerHTML(
"<style>"
"body { margin: 0px; }"
".line { font-family: sans-serif; background: blue; width: 300px; "
"height: 30px; font-size: 40px; margin-left: 250px; }"
"</style>"
"<div style='width: 300px; height: 100px;'>"
"<span class='line' draggable='true'>abcd</span>"
"</div>");
WebMouseEvent mouse_down_event(WebMouseEvent::kMouseDown,
WebFloatPoint(262, 29), WebFloatPoint(329, 67),
WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_down_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_down_event);
WebMouseEvent fake_mouse_move(
WebMouseEvent::kMouseMove, WebFloatPoint(618, 298),
WebFloatPoint(685, 436), WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown |
WebInputEvent::Modifiers::kRelativeMotionEvent,
WebInputEvent::GetStaticTimeStampForTests());
fake_mouse_move.SetFrameScale(1);
EXPECT_EQ(
WebInputEventResult::kHandledSuppressed,
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
fake_mouse_move, Vector<WebMouseEvent>(), Vector<WebMouseEvent>()));
EXPECT_EQ(IntPoint(0, 0), GetDocument()
.GetFrame()
->GetEventHandler()
.DragDataTransferLocationForTesting());
}
class TooltipCapturingChromeClient : public EmptyChromeClient {
public:
TooltipCapturingChromeClient() = default;
void SetToolTip(LocalFrame&, const String& str, TextDirection) override {
last_tool_tip_ = str;
}
String& LastToolTip() { return last_tool_tip_; }
private:
String last_tool_tip_;
};
class EventHandlerTooltipTest : public EventHandlerTest {
public:
EventHandlerTooltipTest() = default;
void SetUp() override {
chrome_client_ = MakeGarbageCollected<TooltipCapturingChromeClient>();
Page::PageClients clients;
FillWithEmptyClients(clients);
clients.chrome_client = chrome_client_.Get();
SetupPageWithClients(&clients);
}
String& LastToolTip() { return chrome_client_->LastToolTip(); }
private:
Persistent<TooltipCapturingChromeClient> chrome_client_;
};
TEST_F(EventHandlerTooltipTest, mouseLeaveClearsTooltip) {
GetDocument().SetCompatibilityMode(Document::kQuirksMode);
SetHtmlInnerHTML(
"<style>.box { width: 100%; height: 100%; }</style>"
"<img src='image.png' class='box' title='tooltip'>link</img>");
EXPECT_EQ(WTF::String(), LastToolTip());
WebMouseEvent mouse_move_event(
WebInputEvent::kMouseMove, WebFloatPoint(51, 50), WebFloatPoint(51, 50),
WebPointerProperties::Button::kNoButton, 0, WebInputEvent::kNoModifiers,
CurrentTimeTicks());
mouse_move_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
EXPECT_EQ("tooltip", LastToolTip());
WebMouseEvent mouse_leave_event(
WebInputEvent::kMouseLeave, WebFloatPoint(0, 0), WebFloatPoint(0, 0),
WebPointerProperties::Button::kNoButton, 0, WebInputEvent::kNoModifiers,
CurrentTimeTicks());
mouse_leave_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseLeaveEvent(
mouse_leave_event);
EXPECT_EQ(WTF::String(), LastToolTip());
}
class UnbufferedInputEventsTrackingChromeClient : public EmptyChromeClient {
public:
UnbufferedInputEventsTrackingChromeClient() = default;
void RequestUnbufferedInputEvents(LocalFrame*) override {
received_unbuffered_request_ = true;
}
bool ReceivedRequestForUnbufferedInput() {
bool value = received_unbuffered_request_;
received_unbuffered_request_ = false;
return value;
}
private:
bool received_unbuffered_request_ = false;
};
class EventHandlerLatencyTest : public PageTestBase {
protected:
void SetUp() override {
chrome_client_ =
MakeGarbageCollected<UnbufferedInputEventsTrackingChromeClient>();
Page::PageClients page_clients;
FillWithEmptyClients(page_clients);
page_clients.chrome_client = chrome_client_.Get();
SetupPageWithClients(&page_clients);
}
void SetHtmlInnerHTML(const char* html_content) {
GetDocument().documentElement()->SetInnerHTMLFromString(
String::FromUTF8(html_content));
UpdateAllLifecyclePhasesForTest();
}
Persistent<UnbufferedInputEventsTrackingChromeClient> chrome_client_;
};
TEST_F(EventHandlerLatencyTest, NeedsUnbufferedInput) {
GetDocument().GetSettings()->SetScriptEnabled(true);
SetHtmlInnerHTML(
"<canvas style='width: 100px; height: 100px' id='first' "
"onpointermove='return;'>");
HTMLCanvasElement& canvas =
ToHTMLCanvasElement(*GetDocument().getElementById("first"));
ASSERT_FALSE(chrome_client_->ReceivedRequestForUnbufferedInput());
WebMouseEvent mouse_press_event(
WebInputEvent::kMouseDown, WebFloatPoint(51, 50), WebFloatPoint(51, 50),
WebPointerProperties::Button::kLeft, 0, WebInputEvent::kNoModifiers,
CurrentTimeTicks());
mouse_press_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_press_event);
ASSERT_FALSE(chrome_client_->ReceivedRequestForUnbufferedInput());
canvas.SetNeedsUnbufferedInputEvents(true);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_press_event);
ASSERT_TRUE(chrome_client_->ReceivedRequestForUnbufferedInput());
canvas.SetNeedsUnbufferedInputEvents(false);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_press_event);
ASSERT_FALSE(chrome_client_->ReceivedRequestForUnbufferedInput());
}
class NavigationCapturingFrameClient : public EmptyLocalFrameClient {
public:
NavigationCapturingFrameClient() = default;
bool NavigateBackForward(int offset) const override {
offset_ = offset;
return true;
}
int Offset() const { return offset_; }
private:
mutable int offset_ = 0;
};
class EventHandlerNavigationTest : public EventHandlerTest {
public:
EventHandlerNavigationTest() = default;
void SetUp() override {
frame_client_ = MakeGarbageCollected<NavigationCapturingFrameClient>();
Page::PageClients clients;
FillWithEmptyClients(clients);
SetupPageWithClients(&clients, frame_client_);
}
int Offset() { return frame_client_->Offset(); }
private:
Persistent<NavigationCapturingFrameClient> frame_client_;
};
TEST_F(EventHandlerNavigationTest, MouseButtonsNavigate) {
SetHtmlInnerHTML("<div>");
EXPECT_EQ(0, Offset());
WebMouseEvent mouse_back_event(
WebInputEvent::kMouseUp, WebFloatPoint(51, 50), WebFloatPoint(51, 50),
WebPointerProperties::Button::kBack, 0, WebInputEvent::kNoModifiers,
CurrentTimeTicks());
mouse_back_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseReleaseEvent(
mouse_back_event);
EXPECT_EQ(-1, Offset());
WebMouseEvent mouse_forward_event(
WebInputEvent::kMouseUp, WebFloatPoint(51, 50), WebFloatPoint(51, 50),
WebPointerProperties::Button::kForward, 0, WebInputEvent::kNoModifiers,
CurrentTimeTicks());
mouse_forward_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseReleaseEvent(
mouse_forward_event);
EXPECT_EQ(1, Offset());
}
TEST_F(EventHandlerNavigationTest, MouseButtonsDontNavigate) {
SetHtmlInnerHTML("<div>");
GetDocument().GetSettings()->SetScriptEnabled(true);
Element* script = GetDocument().CreateRawElement(html_names::kScriptTag);
script->SetInnerHTMLFromString(
"document.addEventListener('mouseup', event => "
"event.preventDefault());");
GetDocument().body()->AppendChild(script);
EXPECT_EQ(0, Offset());
WebMouseEvent mouse_back_event(
WebInputEvent::kMouseUp, WebFloatPoint(51, 50), WebFloatPoint(51, 50),
WebPointerProperties::Button::kBack, 0, WebInputEvent::kNoModifiers,
CurrentTimeTicks());
mouse_back_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseReleaseEvent(
mouse_back_event);
EXPECT_EQ(0, Offset());
WebMouseEvent mouse_forward_event(
WebInputEvent::kMouseUp, WebFloatPoint(51, 50), WebFloatPoint(51, 50),
WebPointerProperties::Button::kForward, 0, WebInputEvent::kNoModifiers,
CurrentTimeTicks());
mouse_forward_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseReleaseEvent(
mouse_forward_event);
EXPECT_EQ(0, Offset());
}
// Test that leaving a window leaves mouse position unknown.
TEST_F(EventHandlerTest, MouseLeaveResetsUnknownState) {
SetHtmlInnerHTML("<div></div>");
WebMouseEvent mouse_down_event(WebMouseEvent::kMouseDown,
WebFloatPoint(262, 29), WebFloatPoint(329, 67),
WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_down_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_down_event);
EXPECT_FALSE(
GetDocument().GetFrame()->GetEventHandler().IsMousePositionUnknown());
WebMouseEvent mouse_leave_event(
WebMouseEvent::kMouseLeave, WebFloatPoint(262, 29),
WebFloatPoint(329, 67), WebPointerProperties::Button::kNoButton, 1,
WebInputEvent::Modifiers::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_leave_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseLeaveEvent(
mouse_leave_event);
EXPECT_TRUE(
GetDocument().GetFrame()->GetEventHandler().IsMousePositionUnknown());
}
// Test that leaving an iframe sets the mouse position to unknown on that
// iframe.
TEST_F(EventHandlerSimTest, MouseLeaveIFrameResets) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest main_resource("https://example.com/test.html", "text/html");
SimRequest frame_resource("https://example.com/frame.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<style>
div {
width: 200px;
height: 200px;
}
iframe {
width: 200px;
height: 200px;
}
</style>
<div></div>
<iframe id='frame' src='frame.html'></iframe>
)HTML");
frame_resource.Complete("<!DOCTYPE html>");
Compositor().BeginFrame();
WebMouseEvent mouse_move_inside_event(
WebMouseEvent::kMouseMove, WebFloatPoint(100, 229),
WebFloatPoint(100, 229), WebPointerProperties::Button::kNoButton, 0,
WebInputEvent::Modifiers::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_move_inside_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_inside_event, Vector<WebMouseEvent>(),
Vector<WebMouseEvent>());
EXPECT_FALSE(
GetDocument().GetFrame()->GetEventHandler().IsMousePositionUnknown());
auto* child_frame =
ToHTMLIFrameElement(GetDocument().getElementById("frame"));
child_frame->contentDocument()->UpdateStyleAndLayout();
EXPECT_TRUE(GetDocument().GetFrame()->Tree().FirstChild());
EXPECT_FALSE(To<LocalFrame>(GetDocument().GetFrame()->Tree().FirstChild())
->GetEventHandler()
.IsMousePositionUnknown());
WebMouseEvent mouse_move_outside_event(
WebMouseEvent::kMouseMove, WebFloatPoint(300, 29), WebFloatPoint(300, 29),
WebPointerProperties::Button::kNoButton, 0,
WebInputEvent::Modifiers::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_move_outside_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_outside_event, Vector<WebMouseEvent>(),
Vector<WebMouseEvent>());
EXPECT_FALSE(
GetDocument().GetFrame()->GetEventHandler().IsMousePositionUnknown());
EXPECT_TRUE(GetDocument().GetFrame()->Tree().FirstChild());
EXPECT_TRUE(To<LocalFrame>(GetDocument().GetFrame()->Tree().FirstChild())
->GetEventHandler()
.IsMousePositionUnknown());
}
// Test that mouse down and move a small distance on a draggable element will
// not change cursor style.
TEST_F(EventHandlerSimTest, CursorStyleBeforeStartDragging) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
div {
width: 300px;
height: 100px;
cursor: help;
}
</style>
<div draggable='true'>foo</div>
)HTML");
Compositor().BeginFrame();
WebMouseEvent mouse_down_event(WebMouseEvent::kMouseDown,
WebFloatPoint(150, 50), WebFloatPoint(150, 50),
WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_down_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_down_event);
WebMouseEvent mouse_move_event(WebMouseEvent::kMouseMove,
WebFloatPoint(151, 50), WebFloatPoint(151, 50),
WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_move_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
EXPECT_EQ(Cursor::Type::kHelp, GetDocument()
.GetFrame()
->GetChromeClient()
.LastSetCursorForTesting()
.GetType());
}
// Ensure that tap on element in iframe should apply active state.
TEST_F(EventHandlerSimTest, TapActiveInFrame) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest main_resource("https://example.com/test.html", "text/html");
SimRequest frame_resource("https://example.com/iframe.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
margin: 0;
}
iframe {
width: 200px;
height: 200px;
}
</style>
<iframe id='iframe' src='iframe.html'>
</iframe>
)HTML");
frame_resource.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
margin: 0;
}
div {
width: 100px;
height: 100px;
}
</style>
<div></div>
)HTML");
Compositor().BeginFrame();
auto* iframe_element =
ToHTMLIFrameElement(GetDocument().getElementById("iframe"));
Document* iframe_doc = iframe_element->contentDocument();
TapDownEventBuilder tap_down(FloatPoint(10, 10));
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(tap_down);
ShowPressEventBuilder show_press(FloatPoint(10, 10));
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(show_press);
// TapDown and ShowPress active the iframe.
EXPECT_TRUE(GetDocument().GetActiveElement());
EXPECT_TRUE(iframe_doc->GetActiveElement());
TapEventBuilder tap(FloatPoint(10, 10), 1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(tap);
// Should still active.
EXPECT_TRUE(GetDocument().GetActiveElement());
EXPECT_TRUE(iframe_doc->GetActiveElement());
// The active will cancel after 15ms.
test::RunDelayedTasks(TimeDelta::FromSecondsD(0.2));
EXPECT_FALSE(GetDocument().GetActiveElement());
EXPECT_FALSE(iframe_doc->GetActiveElement());
}
// Test that the hover is updated at the next begin frame after the compositor
// scroll ends.
TEST_F(EventHandlerSimTest, TestUpdateHoverAfterCompositorScrollAtBeginFrame) {
RuntimeEnabledFeatures::SetUpdateHoverFromScrollAtBeginFrameEnabled(true);
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body, html {
margin: 0;
}
div {
height: 300px;
width: 100%;
}
</style>
<body>
<div class="hoverme" id="line1">hover over me</div>
<div class="hoverme" id="line2">hover over me</div>
<div class="hoverme" id="line3">hover over me</div>
<div class="hoverme" id="line4">hover over me</div>
<div class="hoverme" id="line5">hover over me</div>
</body>
<script>
let array = document.getElementsByClassName('hoverme');
for (let element of array) {
element.addEventListener('mouseover', function (e) {
this.innerHTML = "currently hovered";
});
element.addEventListener('mouseout', function (e) {
this.innerHTML = "was hovered";
});
}
</script>
)HTML");
Compositor().BeginFrame();
// Set mouse position and active web view.
WebMouseEvent mouse_down_event(WebMouseEvent::kMouseDown, WebFloatPoint(1, 1),
WebFloatPoint(1, 1),
WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_down_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_down_event);
WebMouseEvent mouse_up_event(
WebInputEvent::kMouseUp, WebFloatPoint(1, 1), WebFloatPoint(1, 1),
WebPointerProperties::Button::kLeft, 1, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_up_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseReleaseEvent(
mouse_up_event);
WebView().MainFrameWidget()->SetFocus(true);
WebView().SetIsActive(true);
WebElement element1 = GetDocument().getElementById("line1");
WebElement element2 = GetDocument().getElementById("line2");
WebElement element3 = GetDocument().getElementById("line3");
EXPECT_EQ("currently hovered", element1.InnerHTML().Utf8());
EXPECT_EQ("hover over me", element2.InnerHTML().Utf8());
EXPECT_EQ("hover over me", element3.InnerHTML().Utf8());
// Do a compositor scroll and set |hover_needs_update_at_scroll_end| to be
// true in WebViewImpl.
LocalFrameView* frame_view = GetDocument().View();
frame_view->LayoutViewport()->DidScroll(FloatPoint(0, 500));
WebView().MainFrameWidget()->ApplyViewportChanges(
{gfx::ScrollOffset(), gfx::Vector2dF(), 1.0f, false, 0,
cc::BrowserControlsState::kBoth, true});
ASSERT_EQ(500, frame_view->LayoutViewport()->GetScrollOffset().Height());
EXPECT_EQ("currently hovered", element1.InnerHTML().Utf8());
EXPECT_EQ("hover over me", element2.InnerHTML().Utf8());
EXPECT_EQ("hover over me", element3.InnerHTML().Utf8());
// The fake mouse move event is dispatched at the begin frame to update hover.
Compositor().BeginFrame();
EXPECT_EQ("was hovered", element1.InnerHTML().Utf8());
EXPECT_EQ("currently hovered", element2.InnerHTML().Utf8());
EXPECT_EQ("hover over me", element3.InnerHTML().Utf8());
}
// Test that the hover is updated at the next begin frame after the main thread
// scroll ends.
TEST_F(EventHandlerSimTest, TestUpdateHoverAfterMainThreadScrollAtBeginFrame) {
RuntimeEnabledFeatures::SetUpdateHoverFromScrollAtBeginFrameEnabled(true);
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body, html {
margin: 0;
}
div {
height: 300px;
width: 100%;
}
</style>
<body>
<div class="hoverme" id="line1">hover over me</div>
<div class="hoverme" id="line2">hover over me</div>
<div class="hoverme" id="line3">hover over me</div>
<div class="hoverme" id="line4">hover over me</div>
<div class="hoverme" id="line5">hover over me</div>
</body>
<script>
let array = document.getElementsByClassName('hoverme');
for (let element of array) {
element.addEventListener('mouseover', function (e) {
this.innerHTML = "currently hovered";
});
element.addEventListener('mouseout', function (e) {
this.innerHTML = "was hovered";
});
}
</script>
)HTML");
Compositor().BeginFrame();
// Set mouse position and active web view.
WebMouseEvent mouse_down_event(WebMouseEvent::kMouseDown, WebFloatPoint(1, 1),
WebFloatPoint(1, 1),
WebPointerProperties::Button::kLeft, 1,
WebInputEvent::Modifiers::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
mouse_down_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
mouse_down_event);
WebMouseEvent mouse_up_event(
WebInputEvent::kMouseUp, WebFloatPoint(1, 1), WebFloatPoint(1, 1),
WebPointerProperties::Button::kLeft, 1, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_up_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseReleaseEvent(
mouse_up_event);
WebView().MainFrameWidget()->SetFocus(true);
WebView().SetIsActive(true);
WebElement element1 = GetDocument().getElementById("line1");
WebElement element2 = GetDocument().getElementById("line2");
WebElement element3 = GetDocument().getElementById("line3");
EXPECT_EQ("currently hovered", element1.InnerHTML().Utf8());
EXPECT_EQ("hover over me", element2.InnerHTML().Utf8());
EXPECT_EQ("hover over me", element3.InnerHTML().Utf8());
// Send scroll gesture events which will set
// |hover_needs_update_at_scroll_end_| to be true in ScrollManager.
WebGestureEvent scroll_begin_event(
WebInputEvent::kGestureScrollBegin, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchpad);
WebGestureEvent scroll_update_event(
WebInputEvent::kGestureScrollUpdate, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchpad);
scroll_update_event.data.scroll_update.delta_y = -500;
WebGestureEvent scroll_end_event(WebInputEvent::kGestureScrollEnd,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchpad);
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(scroll_begin_event));
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(scroll_update_event));
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(scroll_end_event));
LocalFrameView* frame_view = GetDocument().View();
ASSERT_EQ(500, frame_view->LayoutViewport()->GetScrollOffset().Height());
EXPECT_EQ("currently hovered", element1.InnerHTML().Utf8());
EXPECT_EQ("hover over me", element2.InnerHTML().Utf8());
EXPECT_EQ("hover over me", element3.InnerHTML().Utf8());
// The fake mouse move event is dispatched at the begin frame to update hover.
Compositor().BeginFrame();
EXPECT_EQ("was hovered", element1.InnerHTML().Utf8());
EXPECT_EQ("currently hovered", element2.InnerHTML().Utf8());
EXPECT_EQ("hover over me", element3.InnerHTML().Utf8());
}
// Test that the hover is updated at the next begin frame after the smooth JS
// scroll ends.
TEST_F(EventHandlerSimTest, TestUpdateHoverAfterJSScrollAtBeginFrame) {
RuntimeEnabledFeatures::SetUpdateHoverFromScrollAtBeginFrameEnabled(true);
WebView().MainFrameWidget()->Resize(WebSize(800, 500));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body, html {
margin: 0;
height: 500vh;
}
div {
height: 500px;
width: 100%;
}
</style>
<body>
<div class="hoverme" id="hoverarea">hover over me</div>
</body>
)HTML");
Compositor().BeginFrame();
// Set mouse position and active web view.
WebMouseEvent mouse_move_event(
WebMouseEvent::kMouseMove, WebFloatPoint(100, 100),
WebFloatPoint(100, 100), WebPointerProperties::Button::kNoButton, 0,
WebInputEvent::Modifiers::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
mouse_move_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
WebView().MainFrameWidget()->SetFocus(true);
WebView().SetIsActive(true);
Element* element = GetDocument().getElementById("hoverarea");
EXPECT_TRUE(element->IsHovered());
// Find the scrollable area and set scroll offset.
ScrollableArea* scrollable_area =
GetDocument().GetLayoutView()->GetScrollableArea();
bool finished = false;
scrollable_area->SetScrollOffset(
ScrollOffset(0, 1000), kProgrammaticScroll, kScrollBehaviorSmooth,
ScrollableArea::ScrollCallback(
base::BindOnce([](bool* finished) { *finished = true; }, &finished)));
Compositor().BeginFrame();
LocalFrameView* frame_view = GetDocument().View();
ASSERT_EQ(0, frame_view->LayoutViewport()->GetScrollOffset().Height());
ASSERT_FALSE(finished);
// Scrolling is in progress but the hover is not updated yet.
Compositor().BeginFrame();
// Start scroll animation, but it is not finished.
Compositor().BeginFrame();
ASSERT_GT(frame_view->LayoutViewport()->GetScrollOffset().Height(), 0);
ASSERT_FALSE(finished);
// Mark hover state dirty but the hover state does not change after the
// animation finishes.
Compositor().BeginFrame(1);
ASSERT_EQ(1000, frame_view->LayoutViewport()->GetScrollOffset().Height());
ASSERT_TRUE(finished);
EXPECT_TRUE(element->IsHovered());
// Hover state is updated after the begin frame.
Compositor().BeginFrame();
EXPECT_FALSE(element->IsHovered());
}
TEST_F(EventHandlerSimTest, LargeCustomCursorIntersectsViewport) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
SimSubresourceRequest cursor_request("https://example.com/100x100.png",
"image/png");
LoadURL("https://example.com/test.html");
request.Complete(
R"HTML(
<!DOCTYPE html>
<style>
div {
width: 300px;
height: 100px;
cursor: url('100x100.png') 100 100, auto;
}
</style>
<div>foo</div>
)HTML");
GetDocument().UpdateStyleAndLayoutTree();
scoped_refptr<SharedBuffer> img =
test::ReadFromFile(test::CoreTestDataPath("notifications/100x100.png"));
cursor_request.Complete(img->CopyAs<Vector<char>>());
Compositor().BeginFrame();
// Move the cursor so no part of it intersects the viewport.
{
WebMouseEvent mouse_move_event(
WebMouseEvent::kMouseMove, WebFloatPoint(101, 101),
WebFloatPoint(101, 101), WebPointerProperties::Button::kNoButton, 0, 0,
WebInputEvent::GetStaticTimeStampForTests());
mouse_move_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
const Cursor& cursor =
GetDocument().GetFrame()->GetChromeClient().LastSetCursorForTesting();
EXPECT_EQ(Cursor::Type::kCustom, cursor.GetType());
}
// Now, move the cursor so that it intersects the visual viewport. The cursor
// should be removed.
{
WebMouseEvent mouse_move_event(
WebMouseEvent::kMouseMove, WebFloatPoint(99, 99), WebFloatPoint(99, 99),
WebPointerProperties::Button::kNoButton, 0, 0,
WebInputEvent::GetStaticTimeStampForTests());
mouse_move_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
const Cursor& cursor =
GetDocument().GetFrame()->GetChromeClient().LastSetCursorForTesting();
EXPECT_EQ(Cursor::Type::kPointer, cursor.GetType());
}
}
TEST_F(EventHandlerSimTest, SmallCustomCursorIntersectsViewport) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
SimSubresourceRequest cursor_request("https://example.com/48x48.png",
"image/png");
LoadURL("https://example.com/test.html");
request.Complete(
R"HTML(
<!DOCTYPE html>
<style>
div {
width: 300px;
height: 100px;
cursor: -webkit-image-set(url('48x48.png') 2x) 24 24, auto;
}
</style>
<div>foo</div>
)HTML");
GetDocument().UpdateStyleAndLayoutTree();
scoped_refptr<SharedBuffer> img =
test::ReadFromFile(test::CoreTestDataPath("notifications/48x48.png"));
cursor_request.Complete(img->CopyAs<Vector<char>>());
Compositor().BeginFrame();
// Move the cursor so no part of it intersects the viewport.
{
WebMouseEvent mouse_move_event(
WebMouseEvent::kMouseMove, WebFloatPoint(25, 25), WebFloatPoint(25, 25),
WebPointerProperties::Button::kNoButton, 0, 0,
WebInputEvent::GetStaticTimeStampForTests());
mouse_move_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
const Cursor& cursor =
GetDocument().GetFrame()->GetChromeClient().LastSetCursorForTesting();
EXPECT_EQ(Cursor::Type::kCustom, cursor.GetType());
}
// Now, move the cursor so that it intersects the visual viewport. The cursor
// should not be removed because it is below
// kMaximumCursorSizeWithoutFallback.
{
WebMouseEvent mouse_move_event(
WebMouseEvent::kMouseMove, WebFloatPoint(23, 23), WebFloatPoint(23, 23),
WebPointerProperties::Button::kNoButton, 0, 0,
WebInputEvent::GetStaticTimeStampForTests());
mouse_move_event.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
const Cursor& cursor =
GetDocument().GetFrame()->GetChromeClient().LastSetCursorForTesting();
EXPECT_EQ(Cursor::Type::kCustom, cursor.GetType());
}
}
TEST_F(EventHandlerSimTest, NotExposeKeyboardEvent) {
GetDocument().GetSettings()->SetDontSendKeyEventsToJavascript(true);
GetDocument().GetSettings()->SetScrollAnimatorEnabled(false);
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
height: 10000px;
}
</style>
Last event: <br>
<p id='log'>no event</p>
<input id="input1" type="text">
<script>
document.addEventListener('keydown', (e) => {
let log = document.getElementById('log');
log.innerText = 'keydown cancelable=' + e.cancelable;
});
document.addEventListener('keyup', (e) => {
let log = document.getElementById('log');
log.innerText = 'keyup cancelable=' + e.cancelable;
});
</script>
)HTML");
Compositor().BeginFrame();
WebElement element = GetDocument().getElementById("log");
WebKeyboardEvent e{WebInputEvent::kRawKeyDown, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests()};
e.windows_key_code = VKEY_DOWN;
// TODO(crbug.com/949766) Should cleanup these magic number.
e.dom_key = 0x00200309;
GetDocument().GetFrame()->GetEventHandler().KeyEvent(e);
EXPECT_EQ("no event", element.InnerHTML().Utf8());
e.SetType(WebInputEvent::kKeyUp);
GetDocument().GetFrame()->GetEventHandler().KeyEvent(e);
EXPECT_EQ("no event", element.InnerHTML().Utf8());
e.SetType(WebInputEvent::kKeyDown);
GetDocument().GetFrame()->GetEventHandler().KeyEvent(e);
EXPECT_EQ("no event", element.InnerHTML().Utf8());
e.SetType(WebInputEvent::kKeyUp);
GetDocument().GetFrame()->GetEventHandler().KeyEvent(e);
EXPECT_EQ("no event", element.InnerHTML().Utf8());
// Key send to js but not cancellable.
e.dom_key = 0x00400031;
e.SetType(WebInputEvent::kRawKeyDown);
GetDocument().GetFrame()->GetEventHandler().KeyEvent(e);
EXPECT_EQ("keydown cancelable=false", element.InnerHTML().Utf8());
e.SetType(WebInputEvent::kKeyUp);
GetDocument().GetFrame()->GetEventHandler().KeyEvent(e);
EXPECT_EQ("keyup cancelable=false", element.InnerHTML().Utf8());
// Key send to js and cancellable in editor.
WebElement input = GetDocument().getElementById("input1");
GetDocument().SetFocusedElement(
input.Unwrap<Element>(),
FocusParams(SelectionBehaviorOnFocus::kNone, kWebFocusTypeNone, nullptr));
e.SetType(WebInputEvent::kRawKeyDown);
GetDocument().GetFrame()->GetEventHandler().KeyEvent(e);
EXPECT_EQ("keydown cancelable=true", element.InnerHTML().Utf8());
// Arrow key caused scroll down in post event dispatch process. Ensure page
// scrolled.
ScrollableArea* scrollable_area = GetDocument().View()->LayoutViewport();
EXPECT_GT(scrollable_area->ScrollOffsetInt().Height(), 0);
}
TEST_F(EventHandlerSimTest, DoNotScrollWithTouchpadIfOverflowIsHidden) {
WebView().MainFrameWidget()->Resize(WebSize(400, 400));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
#outer {
width: 100vw;
height: 100vh;
overflow-x: hidden;
overflow-y: scroll;
}
#inner {
width: 300vw;
height: 300vh;
}
</style>
<body>
<div id='outer'>
<div id='inner'>
</div>
</body>
)HTML");
Compositor().BeginFrame();
WebGestureEvent scroll_begin_event(
WebInputEvent::kGestureScrollBegin, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchpad);
scroll_begin_event.SetPositionInWidget(WebFloatPoint(10, 10));
scroll_begin_event.SetPositionInScreen(WebFloatPoint(10, 10));
scroll_begin_event.SetFrameScale(1);
WebGestureEvent scroll_update_event(
WebInputEvent::kGestureScrollUpdate, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchpad);
scroll_update_event.data.scroll_update.delta_x = -100;
scroll_update_event.data.scroll_update.delta_y = -100;
scroll_update_event.SetPositionInWidget(WebFloatPoint(10, 10));
scroll_update_event.SetPositionInScreen(WebFloatPoint(10, 10));
scroll_update_event.SetFrameScale(1);
WebGestureEvent scroll_end_event(WebInputEvent::kGestureScrollEnd,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchpad);
scroll_end_event.SetPositionInWidget(WebFloatPoint(10, 10));
scroll_end_event.SetPositionInScreen(WebFloatPoint(10, 10));
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(scroll_begin_event));
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(scroll_update_event));
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(scroll_end_event));
EXPECT_EQ(0, GetDocument().getElementById("outer")->scrollLeft());
}
TEST_F(EventHandlerSimTest, ElementTargetedGestureScroll) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
#scroller {
overflow-y:scroll;
height:200px;
}
#talldiv {
height:1000px;
}
</style>
<div id="talldiv">Tall text to create viewport scrollbar</div>
<div id="scroller">
<div style="height:2000px">To create subscroller scrollbar</div>
</div>
)HTML");
Compositor().BeginFrame();
Element* const scroller = GetDocument().getElementById("scroller");
constexpr float delta_y = 100;
// Send GSB/GSU at 0,0 to target the viewport first, then verify that
// the viewport scrolled accordingly.
WebGestureEvent gesture_scroll_begin{
WebInputEvent::kGestureScrollBegin, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests()};
gesture_scroll_begin.SetFrameScale(1);
gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0;
gesture_scroll_begin.data.scroll_begin.delta_y_hint = -delta_y;
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_begin);
WebGestureEvent gesture_scroll_update{
WebInputEvent::kGestureScrollUpdate, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests()};
gesture_scroll_update.SetFrameScale(1);
gesture_scroll_update.data.scroll_update.delta_x = 0;
gesture_scroll_update.data.scroll_update.delta_y = -delta_y;
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_update);
WebGestureEvent gesture_scroll_end{
WebInputEvent::kGestureScrollEnd, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests()};
gesture_scroll_end.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_end);
LocalFrameView* frame_view = GetDocument().View();
ASSERT_EQ(frame_view->LayoutViewport()->GetScrollOffset().Height(), delta_y);
// Switch to the element_id-based targeting for GSB, then resend GSU
// and validate that the subscroller scrolled (and that the viewport
// did not).
ScrollableArea* scrollable_area =
scroller->GetLayoutBox()->GetScrollableArea();
gesture_scroll_begin.data.scroll_begin.scrollable_area_element_id =
scrollable_area->GetCompositorElementId().GetInternalValue();
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_begin);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_update);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_end);
ASSERT_EQ(scrollable_area->ScrollOffsetInt().Height(), delta_y);
ASSERT_EQ(frame_view->LayoutViewport()->GetScrollOffset().Height(), delta_y);
// Remove the scroller, update layout, and ensure the same gestures
// don't crash or scroll the layout viewport.
scroller->remove();
GetDocument().UpdateStyleAndLayout();
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_begin);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_update);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_end);
ASSERT_EQ(frame_view->LayoutViewport()->GetScrollOffset().Height(), delta_y);
}
TEST_F(EventHandlerSimTest, ElementTargetedGestureScrollIFrame) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest request_outer("https://example.com/test-outer.html", "text/html");
SimRequest request_inner("https://example.com/test-inner.html", "text/html");
LoadURL("https://example.com/test-outer.html");
request_outer.Complete(R"HTML(
<!DOCTYPE html>
<iframe id="iframe" src="test-inner.html"></iframe>
<div style="height:1000px"></div>
)HTML");
request_inner.Complete(R"HTML(
<!DOCTYPE html>
<div style="height:1000px"></div>
)HTML");
Compositor().BeginFrame();
HTMLFrameElementBase* const iframe =
ToHTMLFrameElementBase(GetDocument().getElementById("iframe"));
FrameView* child_frame_view =
iframe->GetLayoutEmbeddedContent()->ChildFrameView();
auto* local_child_frame_view = DynamicTo<LocalFrameView>(child_frame_view);
ScrollableArea* scrollable_area = local_child_frame_view->GetScrollableArea();
// Target the iframe scrollable area and make sure it scrolls when targeted
// with gestures.
constexpr float delta_y = 100;
WebGestureEvent gesture_scroll_begin{
WebInputEvent::kGestureScrollBegin, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests()};
gesture_scroll_begin.SetFrameScale(1);
gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0;
gesture_scroll_begin.data.scroll_begin.delta_y_hint = -delta_y;
gesture_scroll_begin.data.scroll_begin.scrollable_area_element_id =
scrollable_area->GetCompositorElementId().GetInternalValue();
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_begin);
WebGestureEvent gesture_scroll_update{
WebInputEvent::kGestureScrollUpdate, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests()};
gesture_scroll_update.SetFrameScale(1);
gesture_scroll_update.data.scroll_update.delta_x = 0;
gesture_scroll_update.data.scroll_update.delta_y = -delta_y;
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_update);
WebGestureEvent gesture_scroll_end{
WebInputEvent::kGestureScrollEnd, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests()};
gesture_scroll_end.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_end);
LocalFrameView* frame_view = GetDocument().View();
ASSERT_EQ(frame_view->LayoutViewport()->GetScrollOffset().Height(), 0);
ASSERT_EQ(scrollable_area->ScrollOffsetInt().Height(), delta_y);
}
TEST_F(EventHandlerSimTest, ElementTargetedGestureScrollViewport) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
// Set a page scale factor so that the VisualViewport will also scroll.
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<div style="height:1000px">Tall text to create viewport scrollbar</div>
)HTML");
WebView().SetPageScaleFactor(2);
Compositor().BeginFrame();
// Target the visual viewport (which is a ScrollableArea), and validate
// that the layout viewport scrolled.
constexpr float delta_y = 700;
const VisualViewport& visual_viewport =
GetDocument().GetPage()->GetVisualViewport();
WebGestureEvent gesture_scroll_begin{
WebInputEvent::kGestureScrollBegin, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests()};
gesture_scroll_begin.SetFrameScale(1);
gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0;
gesture_scroll_begin.data.scroll_begin.delta_y_hint = -delta_y;
gesture_scroll_begin.data.scroll_begin.scrollable_area_element_id =
visual_viewport.GetCompositorElementId().GetInternalValue();
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_begin);
WebGestureEvent gesture_scroll_update{
WebInputEvent::kGestureScrollUpdate, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests()};
gesture_scroll_update.SetFrameScale(1);
gesture_scroll_update.data.scroll_update.delta_x = 0;
gesture_scroll_update.data.scroll_update.delta_y = -delta_y;
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_update);
WebGestureEvent gesture_scroll_end{
WebInputEvent::kGestureScrollEnd, WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests()};
gesture_scroll_end.SetFrameScale(1);
GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
gesture_scroll_end);
LocalFrameView* frame_view = GetDocument().View();
ASSERT_EQ(frame_view->LayoutViewport()->GetScrollOffset().Height(), 400);
ASSERT_EQ(visual_viewport.GetScrollOffset().Height(), 300);
}
} // namespace blink