blob: b3949c532893d15258500322e93da11342ae623d [file] [log] [blame]
// Copyright 2018 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 "content/renderer/input/input_event_prediction.h"
#include <string>
#include "base/test/scoped_feature_list.h"
#include "content/common/input/synthetic_web_input_event_builders.h"
#include "content/public/common/content_features.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/blink/prediction/empty_predictor.h"
#include "ui/events/blink/prediction/predictor_factory.h"
namespace content {
namespace {
using blink::WebInputEvent;
using blink::WebMouseEvent;
using blink::WebPointerProperties;
using blink::WebTouchEvent;
using ui::input_prediction::PredictorType;
} // namespace
class InputEventPredictionTest : public testing::Test {
public:
InputEventPredictionTest() {
// Default to enable resampling with empty predictor for testing.
ConfigureFieldTrialAndInitialize(
features::kResamplingInputEvents,
ui::input_prediction::kScrollPredictorNameEmpty);
}
int GetPredictorMapSize() const {
return event_predictor_->pointer_id_predictor_map_.size();
}
bool GetPrediction(const WebPointerProperties& event,
ui::InputPredictor::InputData* result) const {
if (!event_predictor_)
return false;
if (event.pointer_type == WebPointerProperties::PointerType::kMouse) {
return event_predictor_->mouse_predictor_->GeneratePrediction(
ui::EventTimeForNow(), result);
} else {
auto predictor =
event_predictor_->pointer_id_predictor_map_.find(event.id);
if (predictor != event_predictor_->pointer_id_predictor_map_.end())
return predictor->second->GeneratePrediction(ui::EventTimeForNow(),
result);
else
return false;
}
}
void HandleEvents(const WebInputEvent& event) {
blink::WebCoalescedInputEvent coalesced_event(event);
event_predictor_->HandleEvents(coalesced_event, ui::EventTimeForNow());
}
void ConfigureFieldTrial(const base::Feature& feature,
const std::string& predictor_type) {
base::FieldTrialParams params;
params["predictor"] = predictor_type;
scoped_feature_list_.Reset();
scoped_feature_list_.InitAndEnableFeatureWithParameters(feature, params);
EXPECT_EQ(params["predictor"],
GetFieldTrialParamValueByFeature(feature, "predictor"));
}
void ConfigureFieldTrialAndInitialize(const base::Feature& feature,
const std::string& predictor_type) {
ConfigureFieldTrial(feature, predictor_type);
event_predictor_ = std::make_unique<InputEventPrediction>(
base::FeatureList::IsEnabled(features::kResamplingInputEvents));
}
protected:
std::unique_ptr<InputEventPrediction> event_predictor_;
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(InputEventPredictionTest);
};
TEST_F(InputEventPredictionTest, PredictorType) {
// Resampling is default to true for InputEventPredictionTest.
EXPECT_TRUE(event_predictor_->enable_resampling_);
EXPECT_EQ(event_predictor_->selected_predictor_type_,
PredictorType::kScrollPredictorTypeEmpty);
ConfigureFieldTrialAndInitialize(
features::kResamplingInputEvents,
ui::input_prediction::kScrollPredictorNameEmpty);
EXPECT_EQ(event_predictor_->selected_predictor_type_,
PredictorType::kScrollPredictorTypeEmpty);
ConfigureFieldTrialAndInitialize(
features::kResamplingInputEvents,
ui::input_prediction::kScrollPredictorNameKalman);
EXPECT_EQ(event_predictor_->selected_predictor_type_,
PredictorType::kScrollPredictorTypeKalman);
ConfigureFieldTrialAndInitialize(
features::kResamplingInputEvents,
ui::input_prediction::kScrollPredictorNameKalman);
EXPECT_EQ(event_predictor_->selected_predictor_type_,
PredictorType::kScrollPredictorTypeKalman);
ConfigureFieldTrialAndInitialize(
features::kResamplingInputEvents,
ui::input_prediction::kScrollPredictorNameLsq);
EXPECT_EQ(event_predictor_->selected_predictor_type_,
PredictorType::kScrollPredictorTypeLsq);
// Default to Kalman predictor.
ConfigureFieldTrialAndInitialize(features::kResamplingInputEvents, "");
EXPECT_EQ(event_predictor_->selected_predictor_type_,
PredictorType::kScrollPredictorTypeKalman);
ConfigureFieldTrialAndInitialize(
features::kInputPredictorTypeChoice,
ui::input_prediction::kScrollPredictorNameLsq);
EXPECT_FALSE(event_predictor_->enable_resampling_);
// When enable_resampling_ is true, kInputPredictorTypeChoice flag has no
// effect.
event_predictor_ = std::make_unique<InputEventPrediction>(true);
EXPECT_EQ(event_predictor_->selected_predictor_type_,
PredictorType::kScrollPredictorTypeKalman);
}
TEST_F(InputEventPredictionTest, MouseEvent) {
WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::kMouseMove, 10, 10, 0);
ui::InputPredictor::InputData last_point;
EXPECT_FALSE(GetPrediction(mouse_move, &last_point));
HandleEvents(mouse_move);
EXPECT_EQ(GetPredictorMapSize(), 0);
EXPECT_TRUE(GetPrediction(mouse_move, &last_point));
EXPECT_EQ(last_point.pos.x(), 10);
EXPECT_EQ(last_point.pos.y(), 10);
WebMouseEvent mouse_down = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::kMouseDown, 10, 10, 0);
HandleEvents(mouse_down);
EXPECT_FALSE(GetPrediction(mouse_down, &last_point));
}
TEST_F(InputEventPredictionTest, SingleTouchPoint) {
SyntheticWebTouchEvent touch_event;
ui::InputPredictor::InputData last_point;
touch_event.PressPoint(10, 10);
touch_event.touches[0].pointer_type =
WebPointerProperties::PointerType::kTouch;
HandleEvents(touch_event);
EXPECT_FALSE(GetPrediction(touch_event.touches[0], &last_point));
touch_event.MovePoint(0, 11, 12);
HandleEvents(touch_event);
EXPECT_EQ(GetPredictorMapSize(), 1);
EXPECT_TRUE(GetPrediction(touch_event.touches[0], &last_point));
EXPECT_EQ(last_point.pos.x(), 11);
EXPECT_EQ(last_point.pos.y(), 12);
touch_event.ReleasePoint(0);
HandleEvents(touch_event);
EXPECT_FALSE(GetPrediction(touch_event.touches[0], &last_point));
}
TEST_F(InputEventPredictionTest, MouseEventTypePen) {
WebMouseEvent pen_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::kMouseMove, 10, 10, 0,
WebPointerProperties::PointerType::kPen);
ui::InputPredictor::InputData last_point;
EXPECT_FALSE(GetPrediction(pen_move, &last_point));
HandleEvents(pen_move);
EXPECT_EQ(GetPredictorMapSize(), 1);
EXPECT_TRUE(GetPrediction(pen_move, &last_point));
EXPECT_EQ(last_point.pos.x(), 10);
EXPECT_EQ(last_point.pos.y(), 10);
WebMouseEvent pen_leave = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::kMouseLeave, 10, 10, 0,
WebPointerProperties::PointerType::kPen);
HandleEvents(pen_leave);
EXPECT_EQ(GetPredictorMapSize(), 0);
EXPECT_FALSE(GetPrediction(pen_leave, &last_point));
}
TEST_F(InputEventPredictionTest, MultipleTouchPoint) {
SyntheticWebTouchEvent touch_event;
// Press and move 1st touch point
touch_event.PressPoint(10, 10);
touch_event.MovePoint(0, 11, 12);
touch_event.touches[0].pointer_type =
WebPointerProperties::PointerType::kTouch;
HandleEvents(touch_event);
// Press 2nd touch point
touch_event.PressPoint(20, 30);
touch_event.touches[1].pointer_type = WebPointerProperties::PointerType::kPen;
HandleEvents(touch_event);
EXPECT_EQ(GetPredictorMapSize(), 1);
// Move 2nd touch point
touch_event.MovePoint(1, 25, 25);
HandleEvents(touch_event);
EXPECT_EQ(GetPredictorMapSize(), 2);
ui::InputPredictor::InputData last_point;
EXPECT_TRUE(GetPrediction(touch_event.touches[0], &last_point));
EXPECT_EQ(last_point.pos.x(), 11);
EXPECT_EQ(last_point.pos.y(), 12);
EXPECT_TRUE(GetPrediction(touch_event.touches[1], &last_point));
EXPECT_EQ(last_point.pos.x(), 25);
EXPECT_EQ(last_point.pos.y(), 25);
touch_event.ReleasePoint(0);
HandleEvents(touch_event);
EXPECT_EQ(GetPredictorMapSize(), 1);
}
TEST_F(InputEventPredictionTest, TouchAndStylusResetMousePredictor) {
WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::kMouseMove, 10, 10, 0);
HandleEvents(mouse_move);
ui::InputPredictor::InputData last_point;
EXPECT_TRUE(GetPrediction(mouse_move, &last_point));
WebMouseEvent pen_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::kMouseMove, 20, 20, 0,
WebPointerProperties::PointerType::kPen);
pen_move.id = 1;
HandleEvents(pen_move);
EXPECT_TRUE(GetPrediction(pen_move, &last_point));
EXPECT_FALSE(GetPrediction(mouse_move, &last_point));
HandleEvents(mouse_move);
EXPECT_TRUE(GetPrediction(mouse_move, &last_point));
SyntheticWebTouchEvent touch_event;
touch_event.PressPoint(10, 10);
touch_event.touches[0].pointer_type =
WebPointerProperties::PointerType::kTouch;
HandleEvents(touch_event);
touch_event.MovePoint(0, 10, 10);
HandleEvents(touch_event);
EXPECT_TRUE(GetPrediction(touch_event.touches[0], &last_point));
EXPECT_FALSE(GetPrediction(mouse_move, &last_point));
}
// TouchScrollStarted event removes all touch points.
TEST_F(InputEventPredictionTest, TouchScrollStartedRemoveAllTouchPoints) {
SyntheticWebTouchEvent touch_event;
// Press 1st & 2nd touch point
touch_event.PressPoint(10, 10);
touch_event.touches[0].pointer_type =
WebPointerProperties::PointerType::kTouch;
touch_event.PressPoint(20, 20);
touch_event.touches[1].pointer_type =
WebPointerProperties::PointerType::kTouch;
HandleEvents(touch_event);
// Move 1st & 2nd touch point
touch_event.MovePoint(0, 15, 18);
touch_event.MovePoint(1, 25, 27);
HandleEvents(touch_event);
EXPECT_EQ(GetPredictorMapSize(), 2);
touch_event.SetType(WebInputEvent::kTouchScrollStarted);
HandleEvents(touch_event);
EXPECT_EQ(GetPredictorMapSize(), 0);
}
TEST_F(InputEventPredictionTest, ResamplingDisabled) {
// When resampling is disabled, default to use kalman filter.
ConfigureFieldTrialAndInitialize(features::kInputPredictorTypeChoice, "");
EXPECT_FALSE(event_predictor_->enable_resampling_);
EXPECT_EQ(event_predictor_->selected_predictor_type_,
PredictorType::kScrollPredictorTypeKalman);
// Send 3 mouse move to get kalman predictor ready.
WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::kMouseMove, 10, 10, 0);
HandleEvents(mouse_move);
mouse_move =
SyntheticWebMouseEventBuilder::Build(WebInputEvent::kMouseMove, 11, 9, 0);
HandleEvents(mouse_move);
mouse_move =
SyntheticWebMouseEventBuilder::Build(WebInputEvent::kMouseMove, 12, 8, 0);
HandleEvents(mouse_move);
// The 4th move event should generate predicted events.
mouse_move =
SyntheticWebMouseEventBuilder::Build(WebInputEvent::kMouseMove, 13, 7, 0);
blink::WebCoalescedInputEvent coalesced_event(mouse_move);
event_predictor_->HandleEvents(coalesced_event, ui::EventTimeForNow());
EXPECT_GT(coalesced_event.PredictedEventSize(), 0u);
// Verify when resampling event is disabled, original event coordinates don't
// change.
const WebMouseEvent& event =
static_cast<const blink::WebMouseEvent&>(coalesced_event.Event());
EXPECT_EQ(event.PositionInWidget().x, 13);
EXPECT_EQ(event.PositionInWidget().y, 7);
}
// Test that when dt > maxResampling, resampling is cut off .
TEST_F(InputEventPredictionTest, NoResampleWhenExceedMaxResampleTime) {
ConfigureFieldTrialAndInitialize(
features::kResamplingInputEvents,
ui::input_prediction::kScrollPredictorNameKalman);
base::TimeDelta predictor_max_resample_time =
event_predictor_->mouse_predictor_->MaxResampleTime();
base::TimeTicks event_time = ui::EventTimeForNow();
// Send 3 mouse move each has 8ms interval to get kalman predictor ready.
WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::kMouseMove, 10, 10, 0);
mouse_move.SetTimeStamp(event_time);
HandleEvents(mouse_move);
mouse_move =
SyntheticWebMouseEventBuilder::Build(WebInputEvent::kMouseMove, 11, 9, 0);
mouse_move.SetTimeStamp(event_time += base::TimeDelta::FromMilliseconds(8));
HandleEvents(mouse_move);
mouse_move =
SyntheticWebMouseEventBuilder::Build(WebInputEvent::kMouseMove, 12, 8, 0);
mouse_move.SetTimeStamp(event_time += base::TimeDelta::FromMilliseconds(8));
HandleEvents(mouse_move);
{
// When frame_time is 8ms away from the last event, we have both resampling
// and 3 predicted events.
mouse_move = SyntheticWebMouseEventBuilder::Build(WebInputEvent::kMouseMove,
13, 7, 0);
mouse_move.SetTimeStamp(event_time += base::TimeDelta::FromMilliseconds(8));
blink::WebCoalescedInputEvent coalesced_event(mouse_move);
base::TimeTicks frame_time =
event_time + predictor_max_resample_time; // No cut off
event_predictor_->HandleEvents(coalesced_event, frame_time);
const WebMouseEvent& event =
static_cast<const blink::WebMouseEvent&>(coalesced_event.Event());
EXPECT_GT(event.PositionInWidget().x, 13);
EXPECT_LT(event.PositionInWidget().y, 7);
EXPECT_EQ(event.TimeStamp(), frame_time);
EXPECT_EQ(coalesced_event.PredictedEventSize(), 3u);
// First predicted event time stamp is 8ms from original event timestamp.
EXPECT_EQ(coalesced_event.PredictedEvent(0).TimeStamp(),
event_time + base::TimeDelta::FromMilliseconds(8));
}
{
// Test When the delta time between the frame time and the event is greater
// than the maximum resampling time for a predictor, the resampling is cut
// off to the maximum allowed by the predictor
mouse_move = SyntheticWebMouseEventBuilder::Build(WebInputEvent::kMouseMove,
14, 6, 0);
mouse_move.SetTimeStamp(event_time += base::TimeDelta::FromMilliseconds(8));
blink::WebCoalescedInputEvent coalesced_event(mouse_move);
base::TimeTicks frame_time =
event_time + predictor_max_resample_time +
base::TimeDelta::FromMilliseconds(10); // overpredict on purpose
event_predictor_->HandleEvents(coalesced_event, frame_time);
// We expect the prediction to be cut off to the max resampling time of
// the predictor
const WebMouseEvent& event =
static_cast<const blink::WebMouseEvent&>(coalesced_event.Event());
EXPECT_GT(event.PositionInWidget().x, 14);
EXPECT_LT(event.PositionInWidget().y, 6);
EXPECT_EQ(event.TimeStamp(), event_time + predictor_max_resample_time);
EXPECT_EQ(coalesced_event.PredictedEventSize(), 3u);
// First predicted event time stamp is 8ms from original event timestamp.
EXPECT_EQ(coalesced_event.PredictedEvent(0).TimeStamp(),
event_time + base::TimeDelta::FromMilliseconds(8));
}
}
// Test that when dt between events is 6ms, first predicted point is 6ms ahead.
TEST_F(InputEventPredictionTest, PredictedEventsTimeIntervalEqualRealEvents) {
ConfigureFieldTrialAndInitialize(
features::kResamplingInputEvents,
ui::input_prediction::kScrollPredictorNameKalman);
base::TimeTicks event_time = ui::EventTimeForNow();
// Send 3 mouse move each has 6ms interval to get kalman predictor ready.
WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
WebInputEvent::kMouseMove, 10, 10, 0);
mouse_move.SetTimeStamp(event_time);
HandleEvents(mouse_move);
mouse_move =
SyntheticWebMouseEventBuilder::Build(WebInputEvent::kMouseMove, 11, 9, 0);
mouse_move.SetTimeStamp(event_time += base::TimeDelta::FromMilliseconds(6));
HandleEvents(mouse_move);
mouse_move =
SyntheticWebMouseEventBuilder::Build(WebInputEvent::kMouseMove, 12, 8, 0);
mouse_move.SetTimeStamp(event_time += base::TimeDelta::FromMilliseconds(6));
HandleEvents(mouse_move);
{
mouse_move = SyntheticWebMouseEventBuilder::Build(WebInputEvent::kMouseMove,
13, 7, 0);
mouse_move.SetTimeStamp(event_time += base::TimeDelta::FromMilliseconds(6));
blink::WebCoalescedInputEvent coalesced_event(mouse_move);
event_predictor_->HandleEvents(coalesced_event, event_time);
EXPECT_EQ(coalesced_event.PredictedEventSize(), 4u);
// First predicted event time stamp is 6ms from original event timestamp.
EXPECT_EQ(coalesced_event.PredictedEvent(0).TimeStamp(),
event_time + base::TimeDelta::FromMilliseconds(6));
}
}
// Test that touch points other than kStateMove will not have predicted events.
TEST_F(InputEventPredictionTest, TouchPointStates) {
SyntheticWebTouchEvent touch_event;
touch_event.PressPoint(10, 10);
HandleEvents(touch_event);
// Send 3 moves to initialize predictor.
for (int i = 0; i < 3; i++) {
touch_event.MovePoint(0, 10, 10);
HandleEvents(touch_event);
}
for (int state = blink::WebTouchPoint::kStateUndefined;
state <= blink::WebTouchPoint::kStateMax; state++) {
touch_event.touches[0].state =
static_cast<blink::WebTouchPoint::State>(state);
blink::WebCoalescedInputEvent coalesced_event(touch_event);
event_predictor_->HandleEvents(coalesced_event, ui::EventTimeForNow());
if (state == blink::WebTouchPoint::kStateMoved)
EXPECT_GT(coalesced_event.PredictedEventSize(), 0u);
else
EXPECT_EQ(coalesced_event.PredictedEventSize(), 0u);
}
}
} // namespace content