blob: cc813993b47148b3c3cff2498c242f32452443ed [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 "chrome/browser/page_load_metrics/user_input_tracker.h"
#include <algorithm>
#include "third_party/blink/public/platform/web_input_event.h"
namespace page_load_metrics {
namespace {
// Blink's UserGestureIndicator allows events to be associated with gestures
// that are up to 1 second old, based on guidance in the HTML spec:
const int kMaxEventAgeSeconds = 1;
// Allow for up to 2x the oldest time. This allows consumers to continue to
// find events for timestamps up to 1 second in the past.
const int kOldestAllowedEventAgeSeconds = kMaxEventAgeSeconds * 2;
// In order to limit to at most kMaxTrackedEvents, we rate limit the recorded
// events,
// allowing one per rate limit period.
constexpr int kRateLimitClampMillis = (kOldestAllowedEventAgeSeconds * 1000) /
bool IsInterestingInputEvent(const blink::WebInputEvent& event) {
// Ignore synthesized auto repeat events.
if (event.GetModifiers() & blink::WebInputEvent::kIsAutoRepeat)
return false;
switch (event.GetType()) {
case blink::WebInputEvent::kMouseDown:
case blink::WebInputEvent::kMouseUp:
case blink::WebInputEvent::kRawKeyDown:
case blink::WebInputEvent::kKeyDown:
case blink::WebInputEvent::kChar:
case blink::WebInputEvent::kTouchStart:
case blink::WebInputEvent::kTouchEnd:
return true;
return false;
} // namespace
constexpr size_t UserInputTracker::kMaxTrackedEvents;
UserInputTracker::UserInputTracker() {
UserInputTracker::~UserInputTracker() {}
// static
base::TimeTicks UserInputTracker::RoundToRateLimitedOffset(
base::TimeTicks time) {
base::TimeDelta time_as_delta = time - base::TimeTicks();
base::TimeDelta rate_limit_remainder =
time_as_delta % base::TimeDelta::FromMilliseconds(kRateLimitClampMillis);
return time - rate_limit_remainder;
void UserInputTracker::OnInputEvent(const blink::WebInputEvent& event) {
RemoveInputEventsUpToInclusive(base::TimeTicks::Now() -
if (!IsInterestingInputEvent(event))
// TODO(bmcquade): ideally we'd limit tracking to events generated by a user
// action, as opposed to those generated from JavaScript. The JS API isTrusted
// can be used to distinguish these cases. isTrusted isn't yet a property of
// WebInputEvent. We should consider adding it.
const base::TimeTicks now = base::TimeTicks::Now();
base::TimeTicks time = RoundToRateLimitedOffset(event.TimeStamp());
if (time <=
std::max(most_recent_consumed_time_, now - GetOldEventThreshold()))
if (time > now) {
// We should never receive a UserInputEvent with a timestamp in the future
// if we're on a platform with a high-res clock, where the monotonic clock
// is system-wide monotonic. Unfortunately, this DCHECK seems to fire in
// some linux unit tests, so it is disabled for the time being. See
// for more details.
// DCHECK(!base::TimeTicks::IsHighResolution());
// lower_bound finds the first element >= |time|.
auto it = std::lower_bound(sorted_event_times_.begin(),
sorted_event_times_.end(), time);
if (it != sorted_event_times_.end() && *it == time) {
// Don't insert duplicate values.
sorted_event_times_.insert(it, time);
DCHECK_LE(sorted_event_times_.size(), kMaxTrackedEvents);
std::is_sorted(sorted_event_times_.begin(), sorted_event_times_.end()));
bool UserInputTracker::FindAndConsumeInputEventsBefore(base::TimeTicks time) {
base::TimeTicks recent_input_event_time =
if (recent_input_event_time.is_null())
return false;
return true;
base::TimeTicks UserInputTracker::FindMostRecentUserInputEventBefore(
base::TimeTicks time) {
RemoveInputEventsUpToInclusive(base::TimeTicks::Now() -
if (sorted_event_times_.empty())
return base::TimeTicks();
// lower_bound finds the first element >= |time|.
auto it = std::lower_bound(sorted_event_times_.begin(),
sorted_event_times_.end(), time);
// If all times are after the requested time, then we don't have a time to
// return.
if (it == sorted_event_times_.begin())
return base::TimeTicks();
// |it| points to the first event >= the specified time, so decrement once to
// find the greatest event before the specified time.
base::TimeTicks candidate = *it;
DCHECK_LT(candidate, time);
// If the most recent event is too old, then don't return it.
if (candidate < time - base::TimeDelta::FromSeconds(kMaxEventAgeSeconds))
return base::TimeTicks();
return candidate;
void UserInputTracker::RemoveInputEventsUpToInclusive(base::TimeTicks cutoff) {
cutoff = std::max(RoundToRateLimitedOffset(cutoff),
base::TimeTicks::Now() - GetOldEventThreshold());
most_recent_consumed_time_ = std::max(most_recent_consumed_time_, cutoff);
std::upper_bound(sorted_event_times_.begin(), sorted_event_times_.end(),
// static
base::TimeDelta UserInputTracker::GetOldEventThreshold() {
return base::TimeDelta::FromSeconds(kOldestAllowedEventAgeSeconds);
} // namespace page_load_metrics