1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
// 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/WebKit/public/platform/WebInputEvent.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:
// https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation.
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.
const int kRateLimitClampMillis = (kOldestAllowedEventAgeSeconds * 1000) /
UserInputTracker::kMaxTrackedEvents;
bool IsInterestingInputEvent(const blink::WebInputEvent& event) {
// Ignore synthesized auto repeat events.
if (event.modifiers() & blink::WebInputEvent::IsAutoRepeat)
return false;
switch (event.type()) {
case blink::WebInputEvent::MouseDown:
case blink::WebInputEvent::MouseUp:
case blink::WebInputEvent::RawKeyDown:
case blink::WebInputEvent::KeyDown:
case blink::WebInputEvent::Char:
case blink::WebInputEvent::TouchStart:
case blink::WebInputEvent::TouchEnd:
return true;
default:
return false;
}
}
base::TimeTicks GetTimeTicksFromSeconds(double seconds) {
// WebInputEvent::timeStampSeconds is a double representing number of
// monotonic seconds in TimeTicks time base. There's no convenience API for
// initializing a TimeTicks from such a value. The canonical way to perform
// this initialization is to create a TimeTicks with value 0 and add a
// TimeDelta to it.
return base::TimeTicks() + base::TimeDelta::FromSecondsD(seconds);
}
} // namespace
UserInputTracker::UserInputTracker() {
sorted_event_times_.reserve(kMaxTrackedEvents);
}
UserInputTracker::~UserInputTracker() {}
const size_t UserInputTracker::kMaxTrackedEvents = 100;
// static
base::TimeTicks UserInputTracker::GetEventTime(
const blink::WebInputEvent& event) {
return GetTimeTicksFromSeconds(event.timeStampSeconds());
}
// 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() -
GetOldEventThreshold());
if (!IsInterestingInputEvent(event))
return;
// 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(GetEventTime(event));
if (time <=
std::max(most_recent_consumed_time_, now - GetOldEventThreshold()))
return;
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
// crbug.com/678093 for more details.
// DCHECK(!base::TimeTicks::IsHighResolution());
return;
}
// 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.
return;
}
sorted_event_times_.insert(it, time);
DCHECK_LE(sorted_event_times_.size(), kMaxTrackedEvents);
DCHECK(
std::is_sorted(sorted_event_times_.begin(), sorted_event_times_.end()));
}
bool UserInputTracker::FindAndConsumeInputEventsBefore(base::TimeTicks time) {
base::TimeTicks recent_input_event_time =
FindMostRecentUserInputEventBefore(time);
if (recent_input_event_time.is_null())
return false;
RemoveInputEventsUpToInclusive(recent_input_event_time);
return true;
}
base::TimeTicks UserInputTracker::FindMostRecentUserInputEventBefore(
base::TimeTicks time) {
RemoveInputEventsUpToInclusive(base::TimeTicks::Now() -
GetOldEventThreshold());
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.
--it;
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);
sorted_event_times_.erase(
sorted_event_times_.begin(),
std::upper_bound(sorted_event_times_.begin(), sorted_event_times_.end(),
cutoff));
}
// static
base::TimeDelta UserInputTracker::GetOldEventThreshold() {
return base::TimeDelta::FromSeconds(kOldestAllowedEventAgeSeconds);
}
} // namespace page_load_metrics
|