blob: ccc13c8480ad11631d7343cd0d9300c96bccb1b3 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <new>
#include <tuple>
#include <utility>
#include <vector>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/strings/string_util.h"
#include "base/test/test_simple_task_runner.h"
#include "build/build_config.h"
#include "cc/metrics/event_metrics.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/synthetic_web_input_event_builders.h"
#include "third_party/blink/public/common/input/web_input_event_attribution.h"
#include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
#include "third_party/blink/public/platform/scheduler/test/web_mock_thread_scheduler.h"
#include "third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h"
namespace blink {
namespace {
// Simulate a 16ms frame signal.
const base::TimeDelta kFrameInterval = base::TimeDelta::FromMilliseconds(16);
bool Equal(const WebTouchEvent& lhs, const WebTouchEvent& rhs) {
auto tie = [](const WebTouchEvent& e) {
return std::make_tuple(
e.touches_length, e.dispatch_type, e.moved_beyond_slop_region,
e.hovering, e.touch_start_or_first_touch_move, e.unique_touch_event_id,
e.GetType(), e.TimeStamp(), e.FrameScale(), e.FrameTranslate(),
e.GetModifiers());
};
if (tie(lhs) != tie(rhs))
return false;
for (unsigned i = 0; i < lhs.touches_length; ++i) {
auto touch_tie = [](const blink::WebTouchPoint& e) {
return std::make_tuple(e.state, e.radius_x, e.radius_y, e.rotation_angle,
e.id, e.tilt_x, e.tilt_y, e.tangential_pressure,
e.twist, e.button, e.pointer_type, e.movement_x,
e.movement_y, e.is_raw_movement_event,
e.PositionInWidget(), e.PositionInScreen());
};
if (touch_tie(lhs.touches[i]) != touch_tie(rhs.touches[i]) ||
(!std::isnan(lhs.touches[i].force) &&
!std::isnan(rhs.touches[i].force) &&
lhs.touches[i].force != rhs.touches[i].force))
return false;
}
return true;
}
bool Equal(const WebMouseWheelEvent& lhs, const WebMouseWheelEvent& rhs) {
auto tie = [](const WebMouseWheelEvent& e) {
return std::make_tuple(
e.delta_x, e.delta_y, e.wheel_ticks_x, e.wheel_ticks_y,
e.acceleration_ratio_x, e.acceleration_ratio_y, e.phase,
e.momentum_phase, e.rails_mode, e.dispatch_type, e.event_action,
e.has_synthetic_phase, e.delta_units, e.click_count, e.menu_source_type,
e.id, e.button, e.movement_x, e.movement_y, e.is_raw_movement_event,
e.GetType(), e.TimeStamp(), e.FrameScale(), e.FrameTranslate(),
e.GetModifiers(), e.PositionInWidget(), e.PositionInScreen());
};
return tie(lhs) == tie(rhs);
}
} // namespace
class HandledTask {
public:
virtual ~HandledTask() = default;
virtual blink::WebCoalescedInputEvent* taskAsEvent() = 0;
virtual unsigned taskAsClosure() const = 0;
};
class HandledEvent : public HandledTask {
public:
explicit HandledEvent(const blink::WebCoalescedInputEvent& event)
: event_(event) {}
~HandledEvent() override = default;
blink::WebCoalescedInputEvent* taskAsEvent() override { return &event_; }
unsigned taskAsClosure() const override {
NOTREACHED();
return 0;
}
private:
blink::WebCoalescedInputEvent event_;
};
class HandledClosure : public HandledTask {
public:
explicit HandledClosure(unsigned closure_id) : closure_id_(closure_id) {}
~HandledClosure() override = default;
blink::WebCoalescedInputEvent* taskAsEvent() override {
NOTREACHED();
return nullptr;
}
unsigned taskAsClosure() const override { return closure_id_; }
private:
unsigned closure_id_;
};
enum class CallbackReceivedState {
kPending,
kCalledWhileHandlingEvent,
kCalledAfterHandleEvent,
};
class ReceivedCallback {
public:
ReceivedCallback()
: ReceivedCallback(CallbackReceivedState::kPending, false) {}
ReceivedCallback(CallbackReceivedState state, bool coalesced_latency)
: state_(state), coalesced_latency_(coalesced_latency) {}
bool operator==(const ReceivedCallback& other) const {
return state_ == other.state_ &&
coalesced_latency_ == other.coalesced_latency_;
}
private:
CallbackReceivedState state_;
bool coalesced_latency_;
};
class HandledEventCallbackTracker {
public:
HandledEventCallbackTracker() : handling_event_(false) {
weak_this_ = weak_ptr_factory_.GetWeakPtr();
}
HandledEventCallback GetCallback() {
callbacks_received_.push_back(ReceivedCallback());
HandledEventCallback callback =
base::BindOnce(&HandledEventCallbackTracker::DidHandleEvent, weak_this_,
callbacks_received_.size() - 1);
return callback;
}
void DidHandleEvent(size_t index,
blink::mojom::InputEventResultState ack_result,
const ui::LatencyInfo& latency,
mojom::blink::DidOverscrollParamsPtr params,
base::Optional<cc::TouchAction> touch_action) {
callbacks_received_[index] = ReceivedCallback(
handling_event_ ? CallbackReceivedState::kCalledWhileHandlingEvent
: CallbackReceivedState::kCalledAfterHandleEvent,
latency.coalesced());
}
const std::vector<ReceivedCallback>& GetReceivedCallbacks() const {
return callbacks_received_;
}
bool handling_event_;
private:
std::vector<ReceivedCallback> callbacks_received_;
base::WeakPtr<HandledEventCallbackTracker> weak_this_;
base::WeakPtrFactory<HandledEventCallbackTracker> weak_ptr_factory_{this};
};
class MainThreadEventQueueTest : public testing::Test,
public MainThreadEventQueueClient {
public:
MainThreadEventQueueTest()
: main_task_runner_(new base::TestSimpleTaskRunner()) {
handler_callback_ = std::make_unique<HandledEventCallbackTracker>();
}
void SetUp() override {
queue_ = new MainThreadEventQueue(this, main_task_runner_,
&thread_scheduler_, true);
queue_->ClearRafFallbackTimerForTesting();
}
void HandleEvent(WebInputEvent& event,
blink::mojom::InputEventResultState ack_result) {
base::AutoReset<bool> in_handle_event(&handler_callback_->handling_event_,
true);
queue_->HandleEvent(std::make_unique<blink::WebCoalescedInputEvent>(
event.Clone(), ui::LatencyInfo()),
MainThreadEventQueue::DispatchType::kBlocking,
ack_result, blink::WebInputEventAttribution(), nullptr,
handler_callback_->GetCallback());
}
void RunClosure(unsigned closure_id) {
auto closure = std::make_unique<HandledClosure>(closure_id);
handled_tasks_.push_back(std::move(closure));
}
void QueueClosure() {
unsigned closure_id = ++closure_count_;
queue_->QueueClosure(base::BindOnce(&MainThreadEventQueueTest::RunClosure,
base::Unretained(this), closure_id));
}
MainThreadEventQueueTaskList& event_queue() {
return queue_->shared_state_.events_;
}
bool needs_low_latency_until_pointer_up() {
return queue_->needs_low_latency_until_pointer_up_;
}
bool last_touch_start_forced_nonblocking_due_to_fling() {
return queue_->last_touch_start_forced_nonblocking_due_to_fling_;
}
void RunPendingTasksWithSimulatedRaf() {
while (needs_main_frame_ || main_task_runner_->HasPendingTask()) {
main_task_runner_->RunUntilIdle();
needs_main_frame_ = false;
frame_time_ += kFrameInterval;
queue_->DispatchRafAlignedInput(frame_time_);
}
}
void RunSimulatedRafOnce() {
if (needs_main_frame_) {
needs_main_frame_ = false;
frame_time_ += kFrameInterval;
queue_->DispatchRafAlignedInput(frame_time_);
}
}
// MainThreadEventQueueClient overrides.
bool HandleInputEvent(const blink::WebCoalescedInputEvent& event,
std::unique_ptr<cc::EventMetrics> metrics,
HandledEventCallback callback) override {
if (!handle_input_event_)
return false;
auto handled_event = std::make_unique<HandledEvent>(event);
handled_tasks_.push_back(std::move(handled_event));
std::move(callback).Run(blink::mojom::InputEventResultState::kNotConsumed,
event.latency_info(), nullptr, base::nullopt);
return true;
}
void SetNeedsMainFrame() override { needs_main_frame_ = true; }
std::vector<ReceivedCallback> GetAndResetCallbackResults() {
std::unique_ptr<HandledEventCallbackTracker> callback =
std::make_unique<HandledEventCallbackTracker>();
handler_callback_.swap(callback);
return callback->GetReceivedCallbacks();
}
void set_handle_input_event(bool handle) { handle_input_event_ = handle; }
protected:
scoped_refptr<base::TestSimpleTaskRunner> main_task_runner_;
blink::scheduler::WebMockThreadScheduler thread_scheduler_;
scoped_refptr<MainThreadEventQueue> queue_;
std::vector<std::unique_ptr<HandledTask>> handled_tasks_;
std::unique_ptr<HandledEventCallbackTracker> handler_callback_;
bool needs_main_frame_ = false;
bool handle_input_event_ = true;
base::TimeTicks frame_time_;
unsigned closure_count_ = 0;
};
TEST_F(MainThreadEventQueueTest, ClientDoesntHandleInputEvent) {
// Prevent MainThreadEventQueueClient::HandleInputEvent() from handling the
// event, and have it return false. Then the MainThreadEventQueue should
// call the handled callback.
set_handle_input_event(false);
// The blocking event used in this test is reported to the scheduler.
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(1);
// Inject and try to dispatch an input event. This event is not considered
// "non-blocking" which means the reply callback gets stored with the queued
// event, and will be run when we work through the queue.
SyntheticWebTouchEvent event;
event.PressPoint(10, 10);
event.MovePoint(0, 20, 20);
WebMouseWheelEvent event2 = SyntheticWebMouseWheelEventBuilder::Build(
10, 10, 0, 53, 0, ui::ScrollGranularity::kScrollByPixel);
HandleEvent(event2, blink::mojom::InputEventResultState::kNotConsumed);
RunPendingTasksWithSimulatedRaf();
std::vector<ReceivedCallback> received = GetAndResetCallbackResults();
// We didn't handle the event in the client method.
EXPECT_EQ(handled_tasks_.size(), 0u);
// There's 1 reply callback for our 1 event.
EXPECT_EQ(received.size(), 1u);
// The event was queued and disaptched, then the callback was run when
// the client failed to handle it. If this fails, the callback was run
// by HandleEvent() without dispatching it (kCalledWhileHandlingEvent)
// or was not called at all (kPending).
EXPECT_THAT(received,
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
}
TEST_F(MainThreadEventQueueTest, NonBlockingWheel) {
WebMouseWheelEvent kEvents[4] = {
SyntheticWebMouseWheelEventBuilder::Build(
10, 10, 0, 53, 0, ui::ScrollGranularity::kScrollByPixel),
SyntheticWebMouseWheelEventBuilder::Build(
20, 20, 0, 53, 0, ui::ScrollGranularity::kScrollByPixel),
SyntheticWebMouseWheelEventBuilder::Build(
30, 30, 0, 53, 1, ui::ScrollGranularity::kScrollByPixel),
SyntheticWebMouseWheelEventBuilder::Build(
30, 30, 0, 53, 1, ui::ScrollGranularity::kScrollByPixel),
};
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(0);
for (WebMouseWheelEvent& event : kEvents)
HandleEvent(event, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_EQ(2u, handled_tasks_.size());
for (const auto& task : handled_tasks_) {
EXPECT_EQ(2u, task->taskAsEvent()->CoalescedEventSize());
}
{
EXPECT_EQ(kEvents[0].GetType(),
handled_tasks_.at(0)->taskAsEvent()->Event().GetType());
const WebMouseWheelEvent* last_wheel_event =
static_cast<const WebMouseWheelEvent*>(
handled_tasks_.at(0)->taskAsEvent()->EventPointer());
EXPECT_EQ(WebInputEvent::DispatchType::kListenersNonBlockingPassive,
last_wheel_event->dispatch_type);
WebMouseWheelEvent coalesced_event = kEvents[0];
coalesced_event.Coalesce(kEvents[1]);
coalesced_event.dispatch_type =
WebInputEvent::DispatchType::kListenersNonBlockingPassive;
EXPECT_TRUE(Equal(coalesced_event, *last_wheel_event));
}
{
WebMouseWheelEvent coalesced_event = kEvents[0];
const auto& coalesced_events =
handled_tasks_[0]->taskAsEvent()->GetCoalescedEventsPointers();
const WebMouseWheelEvent* coalesced_wheel_event0 =
static_cast<const WebMouseWheelEvent*>(coalesced_events[0].get());
EXPECT_TRUE(Equal(coalesced_event, *coalesced_wheel_event0));
coalesced_event = kEvents[1];
const WebMouseWheelEvent* coalesced_wheel_event1 =
static_cast<const WebMouseWheelEvent*>(coalesced_events[1].get());
coalesced_event.dispatch_type =
WebInputEvent::DispatchType::kListenersNonBlockingPassive;
EXPECT_TRUE(Equal(coalesced_event, *coalesced_wheel_event1));
}
{
const WebMouseWheelEvent* last_wheel_event =
static_cast<const WebMouseWheelEvent*>(
handled_tasks_.at(1)->taskAsEvent()->EventPointer());
WebMouseWheelEvent coalesced_event = kEvents[2];
coalesced_event.Coalesce(kEvents[3]);
coalesced_event.dispatch_type =
WebInputEvent::DispatchType::kListenersNonBlockingPassive;
EXPECT_TRUE(Equal(coalesced_event, *last_wheel_event));
}
{
WebMouseWheelEvent coalesced_event = kEvents[2];
const auto& coalesced_events =
handled_tasks_[1]->taskAsEvent()->GetCoalescedEventsPointers();
const WebMouseWheelEvent* coalesced_wheel_event0 =
static_cast<const WebMouseWheelEvent*>(coalesced_events[0].get());
EXPECT_TRUE(Equal(coalesced_event, *coalesced_wheel_event0));
coalesced_event = kEvents[3];
const WebMouseWheelEvent* coalesced_wheel_event1 =
static_cast<const WebMouseWheelEvent*>(coalesced_events[1].get());
coalesced_event.dispatch_type =
WebInputEvent::DispatchType::kListenersNonBlockingPassive;
EXPECT_TRUE(Equal(coalesced_event, *coalesced_wheel_event1));
}
}
TEST_F(MainThreadEventQueueTest, NonBlockingTouch) {
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(0);
SyntheticWebTouchEvent kEvents[4];
kEvents[0].PressPoint(10, 10);
kEvents[1].PressPoint(10, 10);
kEvents[1].SetModifiers(1);
kEvents[1].MovePoint(0, 20, 20);
kEvents[2].PressPoint(10, 10);
kEvents[2].MovePoint(0, 30, 30);
kEvents[3].PressPoint(10, 10);
kEvents[3].MovePoint(0, 35, 35);
for (SyntheticWebTouchEvent& event : kEvents)
HandleEvent(event, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(3u, event_queue().size());
EXPECT_TRUE(main_task_runner_->HasPendingTask());
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_EQ(3u, handled_tasks_.size());
EXPECT_EQ(kEvents[0].GetType(),
handled_tasks_.at(0)->taskAsEvent()->Event().GetType());
const WebTouchEvent* last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(0)->taskAsEvent()->EventPointer());
SyntheticWebTouchEvent non_blocking_touch = kEvents[0];
non_blocking_touch.dispatch_type =
WebInputEvent::DispatchType::kListenersNonBlockingPassive;
EXPECT_TRUE(Equal(non_blocking_touch, *last_touch_event));
{
EXPECT_EQ(1u, handled_tasks_[0]->taskAsEvent()->CoalescedEventSize());
const WebTouchEvent* coalesced_touch_event =
static_cast<const WebTouchEvent*>(handled_tasks_[0]
->taskAsEvent()
->GetCoalescedEventsPointers()[0]
.get());
EXPECT_TRUE(Equal(kEvents[0], *coalesced_touch_event));
}
EXPECT_EQ(kEvents[1].GetType(),
handled_tasks_.at(1)->taskAsEvent()->Event().GetType());
last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(1)->taskAsEvent()->EventPointer());
non_blocking_touch = kEvents[1];
non_blocking_touch.dispatch_type =
WebInputEvent::DispatchType::kListenersNonBlockingPassive;
EXPECT_TRUE(Equal(non_blocking_touch, *last_touch_event));
{
EXPECT_EQ(1u, handled_tasks_[1]->taskAsEvent()->CoalescedEventSize());
const WebTouchEvent* coalesced_touch_event =
static_cast<const WebTouchEvent*>(handled_tasks_[1]
->taskAsEvent()
->GetCoalescedEventsPointers()[0]
.get());
EXPECT_TRUE(Equal(kEvents[1], *coalesced_touch_event));
}
EXPECT_EQ(kEvents[2].GetType(),
handled_tasks_.at(2)->taskAsEvent()->Event().GetType());
last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(2)->taskAsEvent()->EventPointer());
WebTouchEvent coalesced_event = kEvents[2];
coalesced_event.Coalesce(kEvents[3]);
coalesced_event.dispatch_type =
WebInputEvent::DispatchType::kListenersNonBlockingPassive;
EXPECT_TRUE(Equal(coalesced_event, *last_touch_event));
{
EXPECT_EQ(2u, handled_tasks_[2]->taskAsEvent()->CoalescedEventSize());
WebTouchEvent coalesced_event = kEvents[2];
const auto& coalesced_events =
handled_tasks_[2]->taskAsEvent()->GetCoalescedEventsPointers();
const WebTouchEvent* coalesced_touch_event0 =
static_cast<const WebTouchEvent*>(coalesced_events[0].get());
EXPECT_TRUE(Equal(coalesced_event, *coalesced_touch_event0));
coalesced_event = kEvents[3];
const WebTouchEvent* coalesced_touch_event1 =
static_cast<const WebTouchEvent*>(coalesced_events[1].get());
coalesced_event.dispatch_type =
WebInputEvent::DispatchType::kListenersNonBlockingPassive;
EXPECT_TRUE(Equal(coalesced_event, *coalesced_touch_event1));
}
}
TEST_F(MainThreadEventQueueTest, BlockingTouch) {
SyntheticWebTouchEvent kEvents[4];
kEvents[0].PressPoint(10, 10);
kEvents[1].PressPoint(10, 10);
kEvents[1].MovePoint(0, 20, 20);
kEvents[2].PressPoint(10, 10);
kEvents[2].MovePoint(0, 30, 30);
kEvents[3].PressPoint(10, 10);
kEvents[3].MovePoint(0, 35, 35);
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(3);
{
// Ensure that coalescing takes place.
HandleEvent(kEvents[0],
blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kNotConsumed);
HandleEvent(kEvents[2], blink::mojom::InputEventResultState::kNotConsumed);
HandleEvent(kEvents[3], blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(2u, event_queue().size());
EXPECT_TRUE(main_task_runner_->HasPendingTask());
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(
GetAndResetCallbackResults(),
testing::ElementsAre(
ReceivedCallback(CallbackReceivedState::kCalledWhileHandlingEvent,
false),
ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
false),
ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
true),
ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
true)));
EXPECT_EQ(0u, event_queue().size());
const WebTouchEvent* last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(1)->taskAsEvent()->EventPointer());
EXPECT_EQ(kEvents[1].unique_touch_event_id,
last_touch_event->unique_touch_event_id);
}
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(kEvents[2], blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(kEvents[3], blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(1u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
}
TEST_F(MainThreadEventQueueTest, InterleavedEvents) {
WebMouseWheelEvent kWheelEvents[2] = {
SyntheticWebMouseWheelEventBuilder::Build(
10, 10, 0, 53, 0, ui::ScrollGranularity::kScrollByPixel),
SyntheticWebMouseWheelEventBuilder::Build(
20, 20, 0, 53, 0, ui::ScrollGranularity::kScrollByPixel),
};
SyntheticWebTouchEvent kTouchEvents[2];
kTouchEvents[0].PressPoint(10, 10);
kTouchEvents[0].MovePoint(0, 20, 20);
kTouchEvents[1].PressPoint(10, 10);
kTouchEvents[1].MovePoint(0, 30, 30);
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(0);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
HandleEvent(kWheelEvents[0],
blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(kTouchEvents[0],
blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(kWheelEvents[1],
blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(kTouchEvents[1],
blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_EQ(2u, handled_tasks_.size());
{
EXPECT_EQ(kWheelEvents[0].GetType(),
handled_tasks_.at(0)->taskAsEvent()->Event().GetType());
const WebMouseWheelEvent* last_wheel_event =
static_cast<const WebMouseWheelEvent*>(
handled_tasks_.at(0)->taskAsEvent()->EventPointer());
EXPECT_EQ(WebInputEvent::DispatchType::kListenersNonBlockingPassive,
last_wheel_event->dispatch_type);
WebMouseWheelEvent coalesced_event = kWheelEvents[0];
coalesced_event.Coalesce(kWheelEvents[1]);
coalesced_event.dispatch_type =
WebInputEvent::DispatchType::kListenersNonBlockingPassive;
EXPECT_TRUE(Equal(coalesced_event, *last_wheel_event));
}
{
EXPECT_EQ(kTouchEvents[0].GetType(),
handled_tasks_.at(1)->taskAsEvent()->Event().GetType());
const WebTouchEvent* last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(1)->taskAsEvent()->EventPointer());
WebTouchEvent coalesced_event = kTouchEvents[0];
coalesced_event.Coalesce(kTouchEvents[1]);
coalesced_event.dispatch_type =
WebInputEvent::DispatchType::kListenersNonBlockingPassive;
EXPECT_TRUE(Equal(coalesced_event, *last_touch_event));
}
}
TEST_F(MainThreadEventQueueTest, RafAlignedMouseInput) {
WebMouseEvent mouseDown = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::Type::kMouseDown, 10, 10, 0);
WebMouseEvent mouseMove = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::Type::kMouseMove, 10, 10, 0);
WebMouseEvent mouseUp = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::Type::kMouseUp, 10, 10, 0);
WebMouseWheelEvent wheelEvents[3] = {
SyntheticWebMouseWheelEventBuilder::Build(
10, 10, 0, 53, 0, ui::ScrollGranularity::kScrollByPixel),
SyntheticWebMouseWheelEventBuilder::Build(
20, 20, 0, 53, 0, ui::ScrollGranularity::kScrollByPixel),
SyntheticWebMouseWheelEventBuilder::Build(
20, 20, 0, 53, 1, ui::ScrollGranularity::kScrollByPixel),
};
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(0);
// Simulate enqueing a discrete event, followed by continuous events and
// then a discrete event. The last discrete event should flush the
// continuous events so the aren't aligned to rAF and are processed
// immediately.
HandleEvent(mouseDown, blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(mouseMove, blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(wheelEvents[0],
blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(wheelEvents[1],
blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(mouseUp, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(4u, event_queue().size());
EXPECT_TRUE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
main_task_runner_->RunUntilIdle();
EXPECT_EQ(0u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
// Simulate the rAF running before the PostTask occurs. The rAF
// will consume everything.
HandleEvent(mouseDown, blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(wheelEvents[0],
blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
EXPECT_TRUE(needs_main_frame_);
RunSimulatedRafOnce();
EXPECT_FALSE(needs_main_frame_);
EXPECT_EQ(0u, event_queue().size());
main_task_runner_->RunUntilIdle();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
// Simulate event consumption but no rAF signal. The mouse wheel events
// should still be in the queue.
handled_tasks_.clear();
HandleEvent(mouseDown, blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(wheelEvents[0],
blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(mouseUp, blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(wheelEvents[2],
blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(wheelEvents[0],
blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(5u, event_queue().size());
EXPECT_TRUE(needs_main_frame_);
main_task_runner_->RunUntilIdle();
EXPECT_TRUE(needs_main_frame_);
EXPECT_EQ(2u, event_queue().size());
RunSimulatedRafOnce();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
EXPECT_EQ(wheelEvents[2].GetModifiers(),
handled_tasks_.at(3)->taskAsEvent()->Event().GetModifiers());
EXPECT_EQ(wheelEvents[0].GetModifiers(),
handled_tasks_.at(4)->taskAsEvent()->Event().GetModifiers());
}
TEST_F(MainThreadEventQueueTest, RafAlignedTouchInput) {
SyntheticWebTouchEvent kEvents[3];
kEvents[0].PressPoint(10, 10);
kEvents[1].PressPoint(10, 10);
kEvents[1].MovePoint(0, 50, 50);
kEvents[2].PressPoint(10, 10);
kEvents[2].ReleasePoint(0);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(3);
// Simulate enqueing a discrete event, followed by continuous events and
// then a discrete event. The last discrete event should flush the
// continuous events so the aren't aligned to rAF and are processed
// immediately.
for (SyntheticWebTouchEvent& event : kEvents)
HandleEvent(event, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(3u, event_queue().size());
EXPECT_TRUE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
main_task_runner_->RunUntilIdle();
EXPECT_EQ(0u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
// Simulate the rAF running before the PostTask occurs. The rAF
// will consume everything.
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
EXPECT_TRUE(needs_main_frame_);
RunSimulatedRafOnce();
EXPECT_FALSE(needs_main_frame_);
EXPECT_EQ(0u, event_queue().size());
main_task_runner_->RunUntilIdle();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
// Simulate event consumption but no rAF signal. The touch events
// should still be in the queue.
handled_tasks_.clear();
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
EXPECT_TRUE(needs_main_frame_);
main_task_runner_->RunUntilIdle();
EXPECT_TRUE(needs_main_frame_);
EXPECT_EQ(1u, event_queue().size());
RunSimulatedRafOnce();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
// Simulate the touch move being discrete
kEvents[0].touch_start_or_first_touch_move = true;
kEvents[1].touch_start_or_first_touch_move = true;
for (SyntheticWebTouchEvent& event : kEvents)
HandleEvent(event, blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(3u, event_queue().size());
EXPECT_TRUE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
main_task_runner_->RunUntilIdle();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
}
TEST_F(MainThreadEventQueueTest, RafAlignedTouchInputCoalescedMoves) {
SyntheticWebTouchEvent kEvents[2];
kEvents[0].PressPoint(10, 10);
kEvents[0].MovePoint(0, 50, 50);
kEvents[1].PressPoint(10, 10);
kEvents[1].MovePoint(0, 20, 20);
kEvents[0].dispatch_type = WebInputEvent::DispatchType::kEventNonBlocking;
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(4);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
{
// Send a non-blocking input event and then blocking event.
// The events should coalesce together.
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(1u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(1u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
RunPendingTasksWithSimulatedRaf();
EXPECT_EQ(0u, event_queue().size());
EXPECT_THAT(
GetAndResetCallbackResults(),
testing::ElementsAre(
ReceivedCallback(CallbackReceivedState::kCalledWhileHandlingEvent,
false),
ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
true)));
}
// Send a non-cancelable ack required event, and then a non-ack
// required event they should be coalesced together.
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(1u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(1u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
RunPendingTasksWithSimulatedRaf();
EXPECT_EQ(0u, event_queue().size());
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
// Send a non-ack required event, and then a non-cancelable ack
// required event they should be coalesced together.
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(1u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(1u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
RunPendingTasksWithSimulatedRaf();
EXPECT_EQ(0u, event_queue().size());
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
}
TEST_F(MainThreadEventQueueTest, RafAlignedTouchInputThrottlingMoves) {
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(3);
SyntheticWebTouchEvent kEvents[2];
kEvents[0].PressPoint(10, 10);
kEvents[0].MovePoint(0, 50, 50);
kEvents[0].dispatch_type = WebInputEvent::DispatchType::kEventNonBlocking;
kEvents[1].PressPoint(10, 10);
kEvents[1].MovePoint(0, 20, 20);
kEvents[1].dispatch_type = WebInputEvent::DispatchType::kEventNonBlocking;
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
// Send a non-cancelable touch move and then send it another one. The
// second one shouldn't go out with the next rAF call and should be throttled.
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(1u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kNotConsumed);
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(1u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
// Event should still be in queue after handling a single rAF call.
RunSimulatedRafOnce();
EXPECT_EQ(1u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
// And should eventually flush.
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
EXPECT_EQ(0u, event_queue().size());
}
TEST_F(MainThreadEventQueueTest, LowLatency) {
SyntheticWebTouchEvent kEvents[2];
kEvents[0].PressPoint(10, 10);
kEvents[1].PressPoint(10, 10);
kEvents[1].MovePoint(0, 50, 50);
queue_->SetNeedsLowLatency(true);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(0);
for (SyntheticWebTouchEvent& event : kEvents)
HandleEvent(event, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
EXPECT_TRUE(main_task_runner_->HasPendingTask());
EXPECT_FALSE(needs_main_frame_);
main_task_runner_->RunUntilIdle();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
EXPECT_EQ(0u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::Type::kMouseMove, 10, 10, 0);
WebMouseWheelEvent mouse_wheel = SyntheticWebMouseWheelEventBuilder::Build(
10, 10, 0, 53, 0, ui::ScrollGranularity::kScrollByPixel);
HandleEvent(mouse_move, blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(mouse_wheel,
blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
EXPECT_TRUE(main_task_runner_->HasPendingTask());
EXPECT_FALSE(needs_main_frame_);
main_task_runner_->RunUntilIdle();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
EXPECT_EQ(0u, event_queue().size());
// Now turn off low latency mode.
queue_->SetNeedsLowLatency(false);
for (SyntheticWebTouchEvent& event : kEvents)
HandleEvent(event, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
EXPECT_TRUE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
EXPECT_EQ(0u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
HandleEvent(mouse_move, blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(mouse_wheel,
blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_TRUE(needs_main_frame_);
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
EXPECT_EQ(0u, event_queue().size());
}
TEST_F(MainThreadEventQueueTest, BlockingTouchesDuringFling) {
SyntheticWebTouchEvent kEvents;
kEvents.PressPoint(10, 10);
kEvents.touch_start_or_first_touch_move = true;
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(4);
EXPECT_FALSE(last_touch_start_forced_nonblocking_due_to_fling());
HandleEvent(kEvents,
blink::mojom::InputEventResultState::kSetNonBlockingDueToFling);
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_EQ(1u, handled_tasks_.size());
EXPECT_EQ(kEvents.GetType(),
handled_tasks_.at(0)->taskAsEvent()->Event().GetType());
EXPECT_TRUE(last_touch_start_forced_nonblocking_due_to_fling());
const WebTouchEvent* last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(0)->taskAsEvent()->EventPointer());
kEvents.dispatch_type =
WebInputEvent::DispatchType::kListenersForcedNonBlockingDueToFling;
EXPECT_TRUE(Equal(kEvents, *last_touch_event));
kEvents.MovePoint(0, 30, 30);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
HandleEvent(kEvents,
blink::mojom::InputEventResultState::kSetNonBlockingDueToFling);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_EQ(2u, handled_tasks_.size());
EXPECT_EQ(kEvents.GetType(),
handled_tasks_.at(1)->taskAsEvent()->Event().GetType());
EXPECT_TRUE(last_touch_start_forced_nonblocking_due_to_fling());
last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(1)->taskAsEvent()->EventPointer());
kEvents.dispatch_type =
WebInputEvent::DispatchType::kListenersForcedNonBlockingDueToFling;
EXPECT_TRUE(Equal(kEvents, *last_touch_event));
kEvents.MovePoint(0, 50, 50);
kEvents.touch_start_or_first_touch_move = false;
HandleEvent(kEvents,
blink::mojom::InputEventResultState::kSetNonBlockingDueToFling);
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_EQ(3u, handled_tasks_.size());
EXPECT_EQ(kEvents.GetType(),
handled_tasks_.at(2)->taskAsEvent()->Event().GetType());
EXPECT_EQ(kEvents.dispatch_type, WebInputEvent::DispatchType::kBlocking);
last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(2)->taskAsEvent()->EventPointer());
EXPECT_TRUE(Equal(kEvents, *last_touch_event));
kEvents.ReleasePoint(0);
HandleEvent(kEvents,
blink::mojom::InputEventResultState::kSetNonBlockingDueToFling);
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_EQ(4u, handled_tasks_.size());
EXPECT_EQ(kEvents.GetType(),
handled_tasks_.at(3)->taskAsEvent()->Event().GetType());
EXPECT_EQ(kEvents.dispatch_type, WebInputEvent::DispatchType::kBlocking);
last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(3)->taskAsEvent()->EventPointer());
EXPECT_TRUE(Equal(kEvents, *last_touch_event));
}
TEST_F(MainThreadEventQueueTest, BlockingTouchesOutsideFling) {
SyntheticWebTouchEvent kEvents;
kEvents.PressPoint(10, 10);
kEvents.touch_start_or_first_touch_move = true;
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(4);
HandleEvent(kEvents, blink::mojom::InputEventResultState::kNotConsumed);
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_EQ(1u, handled_tasks_.size());
EXPECT_EQ(kEvents.GetType(),
handled_tasks_.at(0)->taskAsEvent()->Event().GetType());
EXPECT_EQ(kEvents.dispatch_type, WebInputEvent::DispatchType::kBlocking);
EXPECT_FALSE(last_touch_start_forced_nonblocking_due_to_fling());
const WebTouchEvent* last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(0)->taskAsEvent()->EventPointer());
EXPECT_TRUE(Equal(kEvents, *last_touch_event));
HandleEvent(kEvents, blink::mojom::InputEventResultState::kNotConsumed);
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_EQ(2u, handled_tasks_.size());
EXPECT_EQ(kEvents.GetType(),
handled_tasks_.at(1)->taskAsEvent()->Event().GetType());
EXPECT_EQ(kEvents.dispatch_type, WebInputEvent::DispatchType::kBlocking);
EXPECT_FALSE(last_touch_start_forced_nonblocking_due_to_fling());
last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(1)->taskAsEvent()->EventPointer());
EXPECT_TRUE(Equal(kEvents, *last_touch_event));
HandleEvent(kEvents, blink::mojom::InputEventResultState::kNotConsumed);
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_EQ(3u, handled_tasks_.size());
EXPECT_EQ(kEvents.GetType(),
handled_tasks_.at(2)->taskAsEvent()->Event().GetType());
EXPECT_EQ(kEvents.dispatch_type, WebInputEvent::DispatchType::kBlocking);
EXPECT_FALSE(last_touch_start_forced_nonblocking_due_to_fling());
last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(2)->taskAsEvent()->EventPointer());
EXPECT_TRUE(Equal(kEvents, *last_touch_event));
kEvents.MovePoint(0, 30, 30);
HandleEvent(kEvents, blink::mojom::InputEventResultState::kNotConsumed);
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_EQ(4u, handled_tasks_.size());
EXPECT_EQ(kEvents.GetType(),
handled_tasks_.at(3)->taskAsEvent()->Event().GetType());
EXPECT_EQ(kEvents.dispatch_type, WebInputEvent::DispatchType::kBlocking);
EXPECT_FALSE(last_touch_start_forced_nonblocking_due_to_fling());
last_touch_event = static_cast<const WebTouchEvent*>(
handled_tasks_.at(3)->taskAsEvent()->EventPointer());
EXPECT_TRUE(Equal(kEvents, *last_touch_event));
}
class MainThreadEventQueueInitializationTest
: public testing::Test,
public MainThreadEventQueueClient {
public:
MainThreadEventQueueInitializationTest() = default;
bool HandleInputEvent(const blink::WebCoalescedInputEvent& event,
std::unique_ptr<cc::EventMetrics> metrics,
HandledEventCallback callback) override {
std::move(callback).Run(blink::mojom::InputEventResultState::kNotConsumed,
event.latency_info(), nullptr, base::nullopt);
return true;
}
void SetNeedsMainFrame() override {}
protected:
scoped_refptr<MainThreadEventQueue> queue_;
blink::scheduler::WebMockThreadScheduler thread_scheduler_;
scoped_refptr<base::TestSimpleTaskRunner> main_task_runner_;
};
TEST_F(MainThreadEventQueueTest, QueuingTwoClosures) {
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
QueueClosure();
QueueClosure();
EXPECT_EQ(2u, event_queue().size());
EXPECT_TRUE(main_task_runner_->HasPendingTask());
EXPECT_FALSE(needs_main_frame_);
main_task_runner_->RunUntilIdle();
EXPECT_EQ(1u, handled_tasks_.at(0)->taskAsClosure());
EXPECT_EQ(2u, handled_tasks_.at(1)->taskAsClosure());
}
TEST_F(MainThreadEventQueueTest, QueuingClosureWithRafEvent) {
SyntheticWebTouchEvent kEvents[2];
kEvents[0].PressPoint(10, 10);
kEvents[1].PressPoint(10, 10);
kEvents[1].MovePoint(0, 20, 20);
// Simulate queueuing closure, event, closure, raf aligned event.
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
QueueClosure();
EXPECT_EQ(1u, event_queue().size());
EXPECT_TRUE(main_task_runner_->HasPendingTask());
EXPECT_FALSE(needs_main_frame_);
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(2);
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kNotConsumed);
QueueClosure();
EXPECT_EQ(3u, event_queue().size());
EXPECT_TRUE(main_task_runner_->HasPendingTask());
EXPECT_FALSE(needs_main_frame_);
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(4u, event_queue().size());
EXPECT_TRUE(needs_main_frame_);
main_task_runner_->RunUntilIdle();
// The queue should still have the rAF event.
EXPECT_TRUE(needs_main_frame_);
EXPECT_EQ(1u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_EQ(0u, event_queue().size());
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_FALSE(needs_main_frame_);
EXPECT_EQ(1u, handled_tasks_.at(0)->taskAsClosure());
EXPECT_EQ(kEvents[0].GetType(),
handled_tasks_.at(1)->taskAsEvent()->Event().GetType());
EXPECT_EQ(2u, handled_tasks_.at(2)->taskAsClosure());
EXPECT_EQ(kEvents[1].GetType(),
handled_tasks_.at(3)->taskAsEvent()->Event().GetType());
}
TEST_F(MainThreadEventQueueTest, QueuingClosuresBetweenEvents) {
SyntheticWebTouchEvent kEvents[2];
kEvents[0].PressPoint(10, 10);
kEvents[1].PressPoint(10, 10);
kEvents[1].ReleasePoint(0);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(2);
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kNotConsumed);
QueueClosure();
QueueClosure();
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(4u, event_queue().size());
EXPECT_FALSE(needs_main_frame_);
main_task_runner_->RunUntilIdle();
EXPECT_EQ(0u, event_queue().size());
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_FALSE(needs_main_frame_);
EXPECT_EQ(kEvents[0].GetType(),
handled_tasks_.at(0)->taskAsEvent()->Event().GetType());
EXPECT_EQ(1u, handled_tasks_.at(1)->taskAsClosure());
EXPECT_EQ(2u, handled_tasks_.at(2)->taskAsClosure());
EXPECT_EQ(kEvents[1].GetType(),
handled_tasks_.at(3)->taskAsEvent()->Event().GetType());
}
TEST_F(MainThreadEventQueueTest, BlockingTouchMoveBecomesNonBlocking) {
SyntheticWebTouchEvent kEvents[2];
kEvents[0].PressPoint(10, 10);
kEvents[0].MovePoint(0, 20, 20);
kEvents[1].SetModifiers(1);
kEvents[1].PressPoint(10, 10);
kEvents[1].MovePoint(0, 20, 30);
kEvents[1].dispatch_type = WebInputEvent::DispatchType::kEventNonBlocking;
WebTouchEvent scroll_start(WebInputEvent::Type::kTouchScrollStarted,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(3);
EXPECT_EQ(WebInputEvent::DispatchType::kBlocking, kEvents[0].dispatch_type);
EXPECT_EQ(WebInputEvent::DispatchType::kEventNonBlocking,
kEvents[1].dispatch_type);
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kNotConsumed);
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kNotConsumed);
HandleEvent(scroll_start, blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(3u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::ElementsAre(
ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false),
ReceivedCallback(
CallbackReceivedState::kCalledWhileHandlingEvent, false),
ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
EXPECT_EQ(0u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_FALSE(needs_main_frame_);
EXPECT_EQ(WebInputEvent::DispatchType::kEventNonBlocking,
static_cast<const WebTouchEvent&>(
handled_tasks_.at(0)->taskAsEvent()->Event())
.dispatch_type);
EXPECT_EQ(WebInputEvent::DispatchType::kEventNonBlocking,
static_cast<const WebTouchEvent&>(
handled_tasks_.at(1)->taskAsEvent()->Event())
.dispatch_type);
}
TEST_F(MainThreadEventQueueTest, BlockingTouchMoveWithTouchEnd) {
SyntheticWebTouchEvent kEvents[2];
kEvents[0].PressPoint(10, 10);
kEvents[0].MovePoint(0, 20, 20);
kEvents[1].PressPoint(10, 10);
kEvents[1].ReleasePoint(0);
WebTouchEvent scroll_start(WebInputEvent::Type::kTouchScrollStarted,
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(3);
EXPECT_EQ(WebInputEvent::DispatchType::kBlocking, kEvents[0].dispatch_type);
EXPECT_EQ(WebInputEvent::DispatchType::kBlocking, kEvents[1].dispatch_type);
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kNotConsumed);
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kNotConsumed);
HandleEvent(scroll_start, blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(3u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_THAT(GetAndResetCallbackResults(),
testing::Each(ReceivedCallback(
CallbackReceivedState::kCalledAfterHandleEvent, false)));
EXPECT_EQ(0u, event_queue().size());
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_FALSE(needs_main_frame_);
EXPECT_EQ(WebInputEvent::DispatchType::kBlocking,
static_cast<const WebTouchEvent&>(
handled_tasks_.at(0)->taskAsEvent()->Event())
.dispatch_type);
EXPECT_EQ(WebInputEvent::DispatchType::kBlocking,
static_cast<const WebTouchEvent&>(
handled_tasks_.at(1)->taskAsEvent()->Event())
.dispatch_type);
}
TEST_F(MainThreadEventQueueTest, UnbufferedDispatchTouchEvent) {
SyntheticWebTouchEvent kEvents[3];
kEvents[0].PressPoint(10, 10);
kEvents[1].PressPoint(10, 10);
kEvents[1].MovePoint(0, 20, 20);
kEvents[2].PressPoint(10, 10);
kEvents[2].ReleasePoint(0);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(3);
EXPECT_EQ(WebInputEvent::DispatchType::kBlocking, kEvents[0].dispatch_type);
EXPECT_EQ(WebInputEvent::DispatchType::kBlocking, kEvents[1].dispatch_type);
HandleEvent(kEvents[0], blink::mojom::InputEventResultState::kNotConsumed);
queue_->RequestUnbufferedInputEvents();
EXPECT_EQ(1u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_TRUE(needs_low_latency_until_pointer_up());
EXPECT_FALSE(needs_main_frame_);
HandleEvent(kEvents[1], blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(1u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_TRUE(needs_low_latency_until_pointer_up());
EXPECT_FALSE(needs_main_frame_);
HandleEvent(kEvents[2], blink::mojom::InputEventResultState::kNotConsumed);
EXPECT_EQ(1u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_FALSE(needs_low_latency_until_pointer_up());
EXPECT_FALSE(needs_main_frame_);
}
TEST_F(MainThreadEventQueueTest, PointerEventsCoalescing) {
queue_->HasPointerRawUpdateEventHandlers(true);
WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::Type::kMouseMove, 10, 10, 0);
SyntheticWebTouchEvent touch_move;
touch_move.PressPoint(10, 10);
touch_move.MovePoint(0, 50, 50);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
HandleEvent(mouse_move, blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(touch_move, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(4u, event_queue().size());
HandleEvent(mouse_move, blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(touch_move, blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(mouse_move, blink::mojom::InputEventResultState::kSetNonBlocking);
HandleEvent(touch_move, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(4u, event_queue().size());
main_task_runner_->RunUntilIdle();
EXPECT_EQ(2u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_EQ(0u, event_queue().size());
EXPECT_FALSE(needs_main_frame_);
}
TEST_F(MainThreadEventQueueTest, PointerRawUpdateEvents) {
WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::Type::kMouseMove, 10, 10, 0);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(0);
HandleEvent(mouse_move, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(1u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_EQ(0u, event_queue().size());
EXPECT_FALSE(needs_main_frame_);
queue_->HasPointerRawUpdateEventHandlers(true);
HandleEvent(mouse_move, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_EQ(0u, event_queue().size());
EXPECT_FALSE(needs_main_frame_);
queue_->HasPointerRawUpdateEventHandlers(false);
SyntheticWebTouchEvent touch_move;
touch_move.PressPoint(10, 10);
touch_move.MovePoint(0, 50, 50);
HandleEvent(touch_move, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(1u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_EQ(0u, event_queue().size());
EXPECT_FALSE(needs_main_frame_);
queue_->HasPointerRawUpdateEventHandlers(true);
HandleEvent(touch_move, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_EQ(0u, event_queue().size());
EXPECT_FALSE(needs_main_frame_);
}
TEST_F(MainThreadEventQueueTest, UnbufferedDispatchMouseEvent) {
WebMouseEvent mouse_down = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::Type::kMouseDown, 10, 10, 0);
WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::Type::kMouseMove, 10, 10, 0);
WebMouseEvent mouse_up = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::Type::kMouseUp, 10, 10, 0);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(0);
HandleEvent(mouse_down, blink::mojom::InputEventResultState::kSetNonBlocking);
queue_->RequestUnbufferedInputEvents();
EXPECT_EQ(1u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_TRUE(needs_low_latency_until_pointer_up());
EXPECT_FALSE(needs_main_frame_);
HandleEvent(mouse_move, blink::mojom::InputEventResultState::kSetNonBlocking);
queue_->RequestUnbufferedInputEvents();
EXPECT_EQ(1u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_TRUE(needs_low_latency_until_pointer_up());
EXPECT_FALSE(needs_main_frame_);
HandleEvent(mouse_up, blink::mojom::InputEventResultState::kSetNonBlocking);
queue_->RequestUnbufferedInputEvents();
EXPECT_EQ(1u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_FALSE(needs_low_latency_until_pointer_up());
EXPECT_FALSE(needs_main_frame_);
}
// This test verifies that the events marked with kRelativeMotionEvent modifier
// are not coalesced with other events. During pointer lock,
// kRelativeMotionEvent is sent to the Renderer only to update the new screen
// position. Events of this kind shouldn't be dispatched or coalesced.
TEST_F(MainThreadEventQueueTest, PointerEventsWithRelativeMotionCoalescing) {
WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::Type::kMouseMove, 10, 10, 0);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
EXPECT_EQ(0u, event_queue().size());
// Non blocking events are not reported to the scheduler.
EXPECT_CALL(thread_scheduler_,
DidHandleInputEventOnMainThread(testing::_, testing::_))
.Times(0);
queue_->HasPointerRawUpdateEventHandlers(true);
// Inject two mouse move events. For each event injected, there will be two
// events in the queue. One for kPointerRawUpdate and another kMouseMove
// event.
HandleEvent(mouse_move, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
// When another event of the same kind is injected, it is coalesced with the
// previous event, hence queue size doesn't change.
HandleEvent(mouse_move, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(2u, event_queue().size());
// Inject a kRelativeMotionEvent, which cannot be coalesced. Thus, the queue
// size should increase.
WebMouseEvent fake_mouse_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::Type::kMouseMove, 10, 10,
blink::WebInputEvent::Modifiers::kRelativeMotionEvent);
HandleEvent(fake_mouse_move,
blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(4u, event_queue().size());
// Lastly inject another mouse move event. Since it cannot be coalesced with
// previous event, which is a kRelativeMotionEvent, expect the queue size to
// increase again.
HandleEvent(mouse_move, blink::mojom::InputEventResultState::kSetNonBlocking);
EXPECT_EQ(6u, event_queue().size());
RunPendingTasksWithSimulatedRaf();
EXPECT_EQ(0u, event_queue().size());
EXPECT_FALSE(needs_main_frame_);
EXPECT_FALSE(main_task_runner_->HasPendingTask());
// For the 4 events injected, verify that the queue size should be 6, that is
// 3 kPointerRawUpdate events and 3 kMouseMove events.
EXPECT_EQ(6u, handled_tasks_.size());
{
// The first event should have a |CoalescedEventSize| of 2, since two events
// of the same kind are coalesced.
EXPECT_EQ(WebInputEvent::Type::kPointerRawUpdate,
handled_tasks_.at(0)->taskAsEvent()->Event().GetType());
EXPECT_EQ(2u, handled_tasks_.at(0)->taskAsEvent()->CoalescedEventSize());
}
{
// The second event is a kRelativeMotionEvent, it cannot be coalesced, so
// the |CoalescedEventSize| should be 1.
EXPECT_EQ(WebInputEvent::Type::kPointerRawUpdate,
handled_tasks_.at(1)->taskAsEvent()->Event().GetType());
EXPECT_EQ(1u, handled_tasks_.at(1)->taskAsEvent()->CoalescedEventSize());
EXPECT_EQ(blink::WebInputEvent::Modifiers::kRelativeMotionEvent,
handled_tasks_.at(1)->taskAsEvent()->Event().GetModifiers());
}
{
// The third event cannot be coalesced with the previous kPointerRawUpdate,
// so |CoalescedEventSize| should be 1.
EXPECT_EQ(WebInputEvent::Type::kPointerRawUpdate,
handled_tasks_.at(2)->taskAsEvent()->Event().GetType());
EXPECT_EQ(1u, handled_tasks_.at(2)->taskAsEvent()->CoalescedEventSize());
}
{
// The fourth event should have a |CoalescedEventSize| of 2, since two
// events of the same kind are coalesced.
EXPECT_EQ(WebInputEvent::Type::kMouseMove,
handled_tasks_.at(3)->taskAsEvent()->Event().GetType());
EXPECT_EQ(2u, handled_tasks_.at(3)->taskAsEvent()->CoalescedEventSize());
}
{
// The fifth event is a kRelativeMotionEvent, it cannot be coalesced, so
// the |CoalescedEventSize| should be 1.
EXPECT_EQ(WebInputEvent::Type::kMouseMove,
handled_tasks_.at(4)->taskAsEvent()->Event().GetType());
EXPECT_EQ(1u, handled_tasks_.at(4)->taskAsEvent()->CoalescedEventSize());
EXPECT_EQ(blink::WebInputEvent::Modifiers::kRelativeMotionEvent,
handled_tasks_.at(4)->taskAsEvent()->Event().GetModifiers());
}
{
// The sixth event cannot be coalesced with the previous kMouseMove,
// so |CoalescedEventSize| should be 1.
EXPECT_EQ(WebInputEvent::Type::kMouseMove,
handled_tasks_.at(5)->taskAsEvent()->Event().GetType());
EXPECT_EQ(1u, handled_tasks_.at(5)->taskAsEvent()->CoalescedEventSize());
}
}
} // namespace blink