blob: 42d9a73c441d3bdf8f91a4e36f9827a806458b9a [file] [log] [blame]
// Copyright 2017 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/pointer_event_manager.h"
#include <limits>
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/input/event_handler.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/testing/runtime_enabled_features_test_helpers.h"
namespace blink {
namespace {
class CheckPointerEventListenerCallback final : public NativeEventListener {
public:
void Invoke(ExecutionContext*, Event* event) override {
const String pointer_type = ((PointerEvent*)event)->pointerType();
if (pointer_type == "mouse")
mouse_event_received_count_++;
else if (pointer_type == "touch")
touch_event_received_count_++;
else if (pointer_type == "pen")
pen_event_received_count_++;
}
int mouseEventCount() const { return mouse_event_received_count_; }
int touchEventCount() const { return touch_event_received_count_; }
int penEventCount() const { return pen_event_received_count_; }
private:
int mouse_event_received_count_ = 0;
int touch_event_received_count_ = 0;
int pen_event_received_count_ = 0;
};
class PointerEventCoordinateListenerCallback final
: public NativeEventListener {
public:
static PointerEventCoordinateListenerCallback* Create() {
return MakeGarbageCollected<PointerEventCoordinateListenerCallback>();
}
void Invoke(ExecutionContext*, Event* event) override {
const PointerEvent* pointer_event = (PointerEvent*)event;
last_client_x_ = pointer_event->clientX();
last_client_y_ = pointer_event->clientY();
last_page_x_ = pointer_event->pageX();
last_page_y_ = pointer_event->pageY();
last_screen_x_ = pointer_event->screenX();
last_screen_y_ = pointer_event->screenY();
last_width_ = pointer_event->width();
last_height_ = pointer_event->height();
last_movement_x_ = pointer_event->movementX();
last_movement_y_ = pointer_event->movementY();
}
double last_client_x_ = 0;
double last_client_y_ = 0;
double last_page_x_ = 0;
double last_page_y_ = 0;
double last_screen_x_ = 0;
double last_screen_y_ = 0;
double last_width_ = 0;
double last_height_ = 0;
double last_movement_x_ = 0;
double last_movement_y_ = 0;
};
} // namespace
class PointerEventManagerTest : public SimTest {
protected:
EventHandler& GetEventHandler() {
return GetDocument().GetFrame()->GetEventHandler();
}
WebPointerEvent CreateTestPointerEvent(
WebInputEvent::Type type,
WebPointerProperties::PointerType pointer_type,
gfx::PointF position_in_widget = gfx::PointF(100, 100),
gfx::PointF position_in_screen = gfx::PointF(100, 100),
int movement_x = 0,
int movement_y = 0,
float width = 1,
float height = 1) {
WebPointerEvent event(
type,
WebPointerProperties(
1, pointer_type, WebPointerProperties::Button::kLeft,
position_in_widget, position_in_screen, movement_x, movement_y),
width, height);
return event;
}
WebMouseEvent CreateTestMouseEvent(WebInputEvent::Type type,
const gfx::PointF& coordinates) {
WebMouseEvent event(type, coordinates, coordinates,
WebPointerProperties::Button::kLeft, 0,
WebInputEvent::kLeftButtonDown,
WebInputEvent::GetStaticTimeStampForTests());
event.SetFrameScale(1);
return event;
}
};
TEST_F(PointerEventManagerTest, HasPointerCapture) {
WebView().MainFrameWidget()->Resize(WebSize(400, 400));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(
"<body style='padding: 0px; width: 400px; height: 400px;'>"
"</body>");
ASSERT_FALSE(GetDocument().body()->hasPointerCapture(4));
ASSERT_FALSE(GetDocument().body()->hasPointerCapture(
std::numeric_limits<PointerId>::max()));
ASSERT_FALSE(GetDocument().body()->hasPointerCapture(0));
ASSERT_FALSE(GetDocument().body()->hasPointerCapture(-1));
ASSERT_FALSE(
GetDocument().body()->hasPointerCapture(PointerEventFactory::kMouseId));
ExceptionState exception(nullptr, ExceptionState::kExecutionContext, "", "");
GetEventHandler().HandleMousePressEvent(CreateTestMouseEvent(
WebInputEvent::Type::kMouseDown, gfx::PointF(100, 100)));
ASSERT_FALSE(
GetDocument().body()->hasPointerCapture(PointerEventFactory::kMouseId));
GetDocument().body()->setPointerCapture(PointerEventFactory::kMouseId,
exception);
ASSERT_TRUE(
GetDocument().body()->hasPointerCapture(PointerEventFactory::kMouseId));
GetEventHandler().HandleMouseMoveEvent(
CreateTestMouseEvent(WebInputEvent::Type::kMouseMove,
gfx::PointF(200, 200)),
Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
ASSERT_TRUE(
GetDocument().body()->hasPointerCapture(PointerEventFactory::kMouseId));
GetDocument().body()->releasePointerCapture(PointerEventFactory::kMouseId,
exception);
ASSERT_FALSE(
GetDocument().body()->hasPointerCapture(PointerEventFactory::kMouseId));
}
TEST_F(PointerEventManagerTest, PointerCancelsOfAllTypes) {
WebView().MainFrameWidget()->Resize(WebSize(400, 400));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(
"<body style='padding: 0px; width: 400px; height: 400px;'>"
"<div draggable='true' style='width: 150px; height: 150px;'></div>"
"</body>");
auto* callback = MakeGarbageCollected<CheckPointerEventListenerCallback>();
GetDocument().body()->addEventListener(event_type_names::kPointercancel,
callback);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerDown,
WebPointerProperties::PointerType::kTouch),
{}, {}));
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerDown,
WebPointerProperties::PointerType::kPen),
{}, {}));
GetEventHandler().HandleMousePressEvent(CreateTestMouseEvent(
WebInputEvent::Type::kMouseDown, gfx::PointF(100, 100)));
ASSERT_EQ(callback->mouseEventCount(), 0);
ASSERT_EQ(callback->touchEventCount(), 0);
ASSERT_EQ(callback->penEventCount(), 0);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerCausedUaAction,
WebPointerProperties::PointerType::kPen),
{}, {}));
ASSERT_EQ(callback->mouseEventCount(), 0);
ASSERT_EQ(callback->touchEventCount(), 1);
ASSERT_EQ(callback->penEventCount(), 1);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerCausedUaAction,
WebPointerProperties::PointerType::kTouch),
{}, {}));
ASSERT_EQ(callback->mouseEventCount(), 0);
ASSERT_EQ(callback->touchEventCount(), 1);
ASSERT_EQ(callback->penEventCount(), 1);
GetEventHandler().HandleMouseMoveEvent(
CreateTestMouseEvent(WebInputEvent::Type::kMouseMove,
gfx::PointF(200, 200)),
Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
ASSERT_EQ(callback->mouseEventCount(), 1);
ASSERT_EQ(callback->touchEventCount(), 1);
ASSERT_EQ(callback->penEventCount(), 1);
}
TEST_F(PointerEventManagerTest, PointerEventCoordinates) {
WebView().MainFrameWidget()->Resize(WebSize(400, 400));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(
"<body style='padding: 0px; width: 400px; height: 400px;'>"
"</body>");
WebView().SetPageScaleFactor(2);
PointerEventCoordinateListenerCallback* callback =
PointerEventCoordinateListenerCallback::Create();
GetDocument().body()->addEventListener(event_type_names::kPointerdown,
callback);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerDown,
WebPointerProperties::PointerType::kTouch,
gfx::PointF(150, 200), gfx::PointF(100, 50), 10,
10, 16, 24),
{}, {}));
ASSERT_EQ(callback->last_client_x_, 75);
ASSERT_EQ(callback->last_client_y_, 100);
ASSERT_EQ(callback->last_page_x_, 75);
ASSERT_EQ(callback->last_page_y_, 100);
ASSERT_EQ(callback->last_screen_x_, 100);
ASSERT_EQ(callback->last_screen_y_, 50);
ASSERT_EQ(callback->last_width_, 8);
ASSERT_EQ(callback->last_height_, 12);
ASSERT_EQ(callback->last_movement_x_, 10);
ASSERT_EQ(callback->last_movement_y_, 10);
}
TEST_F(PointerEventManagerTest, PointerEventMovements) {
WebView().MainFrameWidget()->Resize(WebSize(400, 400));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(
"<body style='padding: 0px; width: 400px; height: 400px;'>"
"</body>");
PointerEventCoordinateListenerCallback* callback =
PointerEventCoordinateListenerCallback::Create();
GetDocument().body()->addEventListener(event_type_names::kPointermove,
callback);
{
// Turn on the flag for test.
ScopedConsolidatedMovementXYForTest scoped_feature(true);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerMove,
WebPointerProperties::PointerType::kMouse,
gfx::PointF(150, 210), gfx::PointF(100, 50), 10,
10),
{}, {}));
// The first pointermove event has movement_x/y 0.
ASSERT_EQ(callback->last_screen_x_, 100);
ASSERT_EQ(callback->last_screen_y_, 50);
ASSERT_EQ(callback->last_movement_x_, 0);
ASSERT_EQ(callback->last_movement_y_, 0);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerMove,
WebPointerProperties::PointerType::kMouse,
gfx::PointF(150, 200), gfx::PointF(132, 29), 10,
10),
{}, {}));
// pointermove event movement = event.screenX/Y - last_event.screenX/Y.
ASSERT_EQ(callback->last_screen_x_, 132);
ASSERT_EQ(callback->last_screen_y_, 29);
ASSERT_EQ(callback->last_movement_x_, 32);
ASSERT_EQ(callback->last_movement_y_, -21);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerMove,
WebPointerProperties::PointerType::kMouse,
gfx::PointF(150, 210), gfx::PointF(113.8, 32.7),
10, 10),
{}, {}));
// fractional screen coordinates result in fractional movement.
ASSERT_FLOAT_EQ(callback->last_screen_x_, 113.8);
ASSERT_FLOAT_EQ(callback->last_screen_y_, 32.7);
// TODO(eirage): These should be float value once mouse_event.idl change.
ASSERT_FLOAT_EQ(callback->last_movement_x_, -19);
ASSERT_FLOAT_EQ(callback->last_movement_y_, 3);
}
{
// When flag is off, movementX/Y follows the value in WebPointerProperties.
ScopedConsolidatedMovementXYForTest scoped_feature(false);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerMove,
WebPointerProperties::PointerType::kMouse,
gfx::PointF(150, 210), gfx::PointF(100, 16.25),
1024, -8765),
{}, {}));
ASSERT_EQ(callback->last_screen_x_, 100);
ASSERT_EQ(callback->last_screen_y_, 16.25);
ASSERT_EQ(callback->last_movement_x_, 1024);
ASSERT_EQ(callback->last_movement_y_, -8765);
}
}
// Test that we are not losing fractions when truncating movements.
TEST_F(PointerEventManagerTest, PointerEventSmallFractionMovements) {
WebView().MainFrameWidget()->Resize(WebSize(400, 400));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(
"<body style='padding: 0px; width: 400px; height: 400px;'>"
"</body>");
PointerEventCoordinateListenerCallback* callback =
PointerEventCoordinateListenerCallback::Create();
GetDocument().body()->addEventListener(event_type_names::kPointermove,
callback);
// Turn on the flag for test.
ScopedConsolidatedMovementXYForTest scoped_feature(true);
WebPointerEvent pointer_event = CreateTestPointerEvent(
WebInputEvent::Type::kPointerMove,
WebPointerProperties::PointerType::kMouse, gfx::PointF(150, 210),
gfx::PointF(113.8, 32.7), 0, 0);
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(pointer_event));
ASSERT_FLOAT_EQ(callback->last_movement_x_, 0);
ASSERT_FLOAT_EQ(callback->last_movement_y_, 0);
pointer_event.SetPositionInScreen(113.4, 32.9);
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(pointer_event));
ASSERT_FLOAT_EQ(callback->last_movement_x_, 0);
ASSERT_FLOAT_EQ(callback->last_movement_y_, 0);
pointer_event.SetPositionInScreen(113.0, 33.1);
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(pointer_event));
ASSERT_FLOAT_EQ(callback->last_movement_x_, 0);
ASSERT_FLOAT_EQ(callback->last_movement_y_, 1);
pointer_event.SetPositionInScreen(112.6, 33.3);
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(pointer_event));
ASSERT_FLOAT_EQ(callback->last_movement_x_, -1);
ASSERT_FLOAT_EQ(callback->last_movement_y_, 0);
}
TEST_F(PointerEventManagerTest, PointerRawUpdateMovements) {
WebView().MainFrameWidget()->Resize(WebSize(400, 400));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(
"<body style='padding: 0px; width: 400px; height: 400px;'>"
"</body>");
PointerEventCoordinateListenerCallback* callback =
PointerEventCoordinateListenerCallback::Create();
GetDocument().body()->addEventListener(event_type_names::kPointermove,
callback);
GetDocument().body()->addEventListener(event_type_names::kPointerrawupdate,
callback);
// Turn on the flag for test.
ScopedConsolidatedMovementXYForTest scoped_feature(true);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerRawUpdate,
WebPointerProperties::PointerType::kMouse,
gfx::PointF(150, 210), gfx::PointF(100, 50), 10,
10),
{}, {}));
// The first pointerrawupdate event has movement_x/y 0.
ASSERT_EQ(callback->last_screen_x_, 100);
ASSERT_EQ(callback->last_screen_y_, 50);
ASSERT_EQ(callback->last_movement_x_, 0);
ASSERT_EQ(callback->last_movement_y_, 0);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerRawUpdate,
WebPointerProperties::PointerType::kMouse,
gfx::PointF(150, 200), gfx::PointF(132, 29), 10,
10),
{}, {}));
// pointerrawupdate event movement = event.screenX/Y - last_event.screenX/Y.
ASSERT_EQ(callback->last_screen_x_, 132);
ASSERT_EQ(callback->last_screen_y_, 29);
ASSERT_EQ(callback->last_movement_x_, 32);
ASSERT_EQ(callback->last_movement_y_, -21);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerMove,
WebPointerProperties::PointerType::kMouse,
gfx::PointF(150, 200), gfx::PointF(144, 30), 10,
10),
{}, {}));
// First pointermove, have 0 movements.
ASSERT_EQ(callback->last_screen_x_, 144);
ASSERT_EQ(callback->last_screen_y_, 30);
ASSERT_EQ(callback->last_movement_x_, 0);
ASSERT_EQ(callback->last_movement_y_, 0);
WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
CreateTestPointerEvent(WebInputEvent::Type::kPointerRawUpdate,
WebPointerProperties::PointerType::kMouse,
gfx::PointF(150, 200), gfx::PointF(142, 32), 10,
10),
{}, {}));
// pointerrawupdate event's movement is independent from pointermoves.
ASSERT_EQ(callback->last_screen_x_, 142);
ASSERT_EQ(callback->last_screen_y_, 32);
ASSERT_EQ(callback->last_movement_x_, 10);
ASSERT_EQ(callback->last_movement_y_, 3);
}
TEST_F(PointerEventManagerTest, PointerUnadjustedMovement) {
WebView().MainFrameWidget()->Resize(WebSize(400, 400));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(
"<body style='padding: 0px; width: 400px; height: 400px;'>"
"</body>");
PointerEventCoordinateListenerCallback* callback =
PointerEventCoordinateListenerCallback::Create();
GetDocument().body()->addEventListener(event_type_names::kPointermove,
callback);
WebPointerEvent event = CreateTestPointerEvent(
WebInputEvent::Type::kPointerMove,
WebPointerProperties::PointerType::kMouse, gfx::PointF(150, 210),
gfx::PointF(100, 50), 120, -321);
event.is_raw_movement_event = true;
WebView().MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(event, {}, {}));
// If is_raw_movement_event is true, PE use the raw movement value from
// movement_x/y.
ASSERT_EQ(callback->last_screen_x_, 100);
ASSERT_EQ(callback->last_screen_y_, 50);
ASSERT_EQ(callback->last_movement_x_, 120);
ASSERT_EQ(callback->last_movement_y_, -321);
}
} // namespace blink