blob: df24ae69237c9e9ab1d09f564aa4197d61f88d72 [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 "ui/events/blink/scroll_predictor.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/trace_event/trace_event.h"
#include "ui/events/blink/prediction/predictor_factory.h"
using blink::WebInputEvent;
using blink::WebGestureEvent;
namespace ui {
ScrollPredictor::ScrollPredictor() {
std::string predictor_name = GetFieldTrialParamValueByFeature(
features::kResamplingScrollEvents, "predictor");
input_prediction::PredictorType predictor_type =
ui::PredictorFactory::GetPredictorTypeFromName(predictor_name);
predictor_ = ui::PredictorFactory::GetPredictor(predictor_type);
}
ScrollPredictor::~ScrollPredictor() = default;
void ScrollPredictor::ResetOnGestureScrollBegin(const WebGestureEvent& event) {
DCHECK(event.GetType() == WebInputEvent::kGestureScrollBegin);
// Only do resampling for scroll on touchscreen.
if (event.SourceDevice() == blink::WebGestureDevice::kTouchscreen) {
should_resample_scroll_events_ = true;
Reset();
}
}
std::unique_ptr<EventWithCallback> ScrollPredictor::ResampleScrollEvents(
std::unique_ptr<EventWithCallback> event_with_callback,
base::TimeTicks frame_time) {
if (!should_resample_scroll_events_)
return event_with_callback;
const EventWithCallback::OriginalEventList& original_events =
event_with_callback->original_events();
if (event_with_callback->event().GetType() ==
WebInputEvent::kGestureScrollUpdate) {
// TODO(eirage): When scroll events are coalesced with pinch, we can have
// empty original event list. In that case, we can't use the original events
// to update the prediction. We don't want to use the aggregated event to
// update because of the event time stamp, so skip the prediction for now.
if (original_events.empty())
return event_with_callback;
TRACE_EVENT_BEGIN0("input", "ScrollPredictor::ResampleScrollEvents");
temporary_accumulated_delta_ = current_accumulated_delta_;
for (auto& coalesced_event : original_events)
ComputeAccuracy(coalesced_event.event_);
for (auto& coalesced_event : original_events)
UpdatePrediction(coalesced_event.event_, frame_time);
if (should_resample_scroll_events_)
ResampleEvent(frame_time, event_with_callback->event_pointer(),
event_with_callback->mutable_latency_info());
TRACE_EVENT_END2("input", "ScrollPredictor::ResampleScrollEvents",
"OriginalPosition", current_accumulated_delta_.ToString(),
"PredictedPosition", last_accumulated_delta_.ToString());
} else if (event_with_callback->event().GetType() ==
WebInputEvent::kGestureScrollEnd) {
should_resample_scroll_events_ = false;
}
return event_with_callback;
}
void ScrollPredictor::Reset() {
predictor_->Reset();
current_accumulated_delta_ = gfx::PointF();
last_accumulated_delta_ = gfx::PointF();
}
void ScrollPredictor::UpdatePrediction(const WebScopedInputEvent& event,
base::TimeTicks frame_time) {
DCHECK(event->GetType() == WebInputEvent::kGestureScrollUpdate);
const WebGestureEvent& gesture_event =
static_cast<const WebGestureEvent&>(*event);
// When fling, GSU is sending per frame, resampling is not needed.
if (gesture_event.data.scroll_update.inertial_phase ==
WebGestureEvent::InertialPhaseState::kMomentum) {
should_resample_scroll_events_ = false;
return;
}
current_accumulated_delta_.Offset(gesture_event.data.scroll_update.delta_x,
gesture_event.data.scroll_update.delta_y);
InputPredictor::InputData data = {current_accumulated_delta_,
gesture_event.TimeStamp()};
predictor_->Update(data);
last_event_timestamp_ = gesture_event.TimeStamp();
}
void ScrollPredictor::ResampleEvent(base::TimeTicks time_stamp,
WebInputEvent* event,
LatencyInfo* latency_info) {
DCHECK(event->GetType() == WebInputEvent::kGestureScrollUpdate);
WebGestureEvent* gesture_event = static_cast<WebGestureEvent*>(event);
gfx::PointF predicted_accumulated_delta = current_accumulated_delta_;
InputPredictor::InputData result;
if (predictor_->HasPrediction() &&
predictor_->GeneratePrediction(time_stamp, true /* is_resampling */,
&result)) {
predicted_accumulated_delta = result.pos;
gesture_event->SetTimeStamp(time_stamp);
}
// If the last resampled GSU over predict the delta, new GSU might try to
// scroll back to make up the difference, which cause the scroll to jump back.
// So we set the new delta to 0 when predicted delta is in different direction
// to the original event.
gfx::Vector2dF new_delta =
predicted_accumulated_delta - last_accumulated_delta_;
gesture_event->data.scroll_update.delta_x =
(new_delta.x() * gesture_event->data.scroll_update.delta_x < 0)
? 0
: new_delta.x();
gesture_event->data.scroll_update.delta_y =
(new_delta.y() * gesture_event->data.scroll_update.delta_y < 0)
? 0
: new_delta.y();
// Sync the predicted delta_y to latency_info for AverageLag metric.
latency_info->set_predicted_scroll_update_delta(new_delta.y());
last_accumulated_delta_.Offset(gesture_event->data.scroll_update.delta_x,
gesture_event->data.scroll_update.delta_y);
}
void ScrollPredictor::ComputeAccuracy(const WebScopedInputEvent& event) {
const WebGestureEvent& gesture_event =
static_cast<const WebGestureEvent&>(*event);
base::TimeDelta time_delta = event->TimeStamp() - last_event_timestamp_;
std::string suffix;
if (time_delta < base::TimeDelta::FromMilliseconds(10))
suffix = "Short";
else if (time_delta < base::TimeDelta::FromMilliseconds(20))
suffix = "Middle";
else if (time_delta < base::TimeDelta::FromMilliseconds(35))
suffix = "Long";
else
return;
InputPredictor::InputData predict_result;
temporary_accumulated_delta_.Offset(gesture_event.data.scroll_update.delta_x,
gesture_event.data.scroll_update.delta_y);
if (predictor_->HasPrediction() &&
predictor_->GeneratePrediction(
event->TimeStamp(), false /* is_resampling */, &predict_result)) {
float distance =
(predict_result.pos - gfx::PointF(temporary_accumulated_delta_))
.Length();
base::UmaHistogramCounts1000(
"Event.InputEventPrediction.Accuracy.Scroll." + suffix,
static_cast<int>(distance));
// If the distance from predicted position to actual position is in same
// direction as the delta_y, the result is under predicted, otherwise over
// predict.
float dist_y = temporary_accumulated_delta_.y() - predict_result.pos.y();
if (gesture_event.data.scroll_update.delta_y * dist_y < 0) {
base::UmaHistogramCounts1000(
"Event.InputEventPrediction.Accuracy.Scroll.OverPredict." + suffix,
static_cast<int>(std::abs(dist_y)));
} else {
base::UmaHistogramCounts1000(
"Event.InputEventPrediction.Accuracy.Scroll.UnderPredict." + suffix,
static_cast<int>(std::abs(dist_y)));
}
}
}
} // namespace ui