Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(74)

Side by Side Diff: chrome/browser/page_load_metrics/user_input_tracker.cc

Issue 2540183003: Add UserInputTracker, which keeps track of recent user input events. (Closed)
Patch Set: address comments Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/page_load_metrics/user_input_tracker.h"
6
7 #include <algorithm>
8
9 #include "third_party/WebKit/public/platform/WebInputEvent.h"
10
11 namespace page_load_metrics {
12
13 namespace {
14
15 // Blink's UserGestureIndicator allows events to be associated with gestures
16 // that are up to 1 second old, based on guidance in the HTML spec:
17 // https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-act ivation.
18 const int kMaxEventAgeSeconds = 1;
19
20 // Allow for up to 2x the oldest time. This allows consumers to continue to
21 // find events for timestamps up to 1 second in the past.
22 const int kOldestAllowedEventAgeSeconds = kMaxEventAgeSeconds * 2;
23
24 // In order to limit to at most kMaxTrackedEvents, we rate limit the recorded
25 // events,
26 // allowing one per rate limit period.
27 const int kRateLimitClampMillis = (kOldestAllowedEventAgeSeconds * 1000) /
28 UserInputTracker::kMaxTrackedEvents;
29
30 bool IsInterestingInputEvent(const blink::WebInputEvent& event) {
31 // Ignore synthesized auto repeat events.
32 if (event.modifiers & blink::WebInputEvent::IsAutoRepeat)
33 return false;
34
35 switch (event.type) {
36 case blink::WebInputEvent::MouseDown:
37 case blink::WebInputEvent::MouseUp:
38 case blink::WebInputEvent::RawKeyDown:
39 case blink::WebInputEvent::KeyDown:
40 case blink::WebInputEvent::Char:
41 case blink::WebInputEvent::TouchStart:
42 case blink::WebInputEvent::TouchEnd:
43 return true;
44 default:
45 return false;
46 }
47 }
48
49 base::TimeTicks GetTimeTicksFromSeconds(double seconds) {
50 // WebInputEvent::timeStampSeconds is a double representing number of
51 // monotonic seconds in TimeTicks time base. There's no convenience API for
52 // initializing a TimeTicks from such a value. The canonical way to perform
53 // this initialization is to create a TimeTicks with value 0 and add a
54 // TimeDelta to it.
55 return base::TimeTicks() + base::TimeDelta::FromSecondsD(seconds);
56 }
57
58 } // namespace
59
60 UserInputTracker::UserInputTracker() {
61 sorted_event_times_.reserve(kMaxTrackedEvents);
62 }
63
64 UserInputTracker::~UserInputTracker() {}
65
66 const size_t UserInputTracker::kMaxTrackedEvents = 100;
67
68 // static
69 base::TimeTicks UserInputTracker::GetEventTime(
70 const blink::WebInputEvent& event) {
71 return GetTimeTicksFromSeconds(event.timeStampSeconds);
72 }
73
74 // static
75 base::TimeTicks UserInputTracker::RoundToRateLimitedOffset(
76 base::TimeTicks time) {
77 base::TimeDelta time_as_delta = time - base::TimeTicks();
78 base::TimeDelta rate_limit_remainder =
79 time_as_delta % base::TimeDelta::FromMilliseconds(kRateLimitClampMillis);
80 return time - rate_limit_remainder;
81 }
82
83 void UserInputTracker::OnInputEvent(const blink::WebInputEvent& event) {
84 RemoveOldInputEvents();
85
86 if (!IsInterestingInputEvent(event))
87 return;
88
89 // TODO(bmcquade): ideally we'd limit tracking to events generated by a user
90 // action, as opposed to those generated from JavaScript. The JS API isTrusted
91 // can be used to distinguish these cases. isTrusted isn't yet a property of
92 // WebInputEvent. We should consider adding it.
93
94 const base::TimeTicks now = base::TimeTicks::Now();
95 base::TimeTicks time = RoundToRateLimitedOffset(GetEventTime(event));
96 if (time <=
97 std::max(most_recent_consumed_time_, GetOldestAllowedEventTime(now)))
98 return;
99
100 if (time > now) {
101 DCHECK(!base::TimeTicks::IsHighResolution());
102 return;
103 }
104
105 // lower_bound finds the first element >= |time|.
106 auto it = std::lower_bound(sorted_event_times_.begin(),
107 sorted_event_times_.end(), time);
108 if (it != sorted_event_times_.end() && *it == time) {
109 // Don't insert duplicate values.
110 return;
111 }
112
113 sorted_event_times_.insert(it, time);
114 DCHECK(sorted_event_times_.size() <= kMaxTrackedEvents);
Charlie Harrison 2016/12/06 18:43:09 DCHECK_LE
Bryan McQuade 2016/12/06 20:10:03 Done
115 DCHECK(
116 std::is_sorted(sorted_event_times_.begin(), sorted_event_times_.end()));
117 }
118
119 bool UserInputTracker::FindAndConsumeInputEventsBefore(base::TimeTicks time) {
120 base::TimeTicks recent_input_event_time =
121 FindMostRecentUserInputEventBefore(time);
122
123 if (recent_input_event_time.is_null())
124 return false;
125
126 RemoveInputEventsUpToInclusive(recent_input_event_time);
127 return true;
128 }
129
130 base::TimeTicks UserInputTracker::FindMostRecentUserInputEventBefore(
131 base::TimeTicks time) {
132 RemoveOldInputEvents();
133
134 if (sorted_event_times_.empty())
135 return base::TimeTicks();
136
137 // lower_bound finds the first element >= |time|.
138 auto it = std::lower_bound(sorted_event_times_.begin(),
139 sorted_event_times_.end(), time);
140
141 // If all times are after the requested time, then we don't have a time to
142 // return.
143 if (it == sorted_event_times_.begin())
144 return base::TimeTicks();
145
146 // |it| points to the first event >= the specified time, so decrement once to
147 // find the greatest event before the specified time.
148 --it;
149 base::TimeTicks candidate = *it;
150 DCHECK(candidate < time);
Charlie Harrison 2016/12/06 18:43:09 DCHECK_LT
Bryan McQuade 2016/12/06 20:10:03 Done
151
152 // If the most recent event is too old, then don't return it.
153 if (candidate < time - base::TimeDelta::FromSeconds(kMaxEventAgeSeconds))
154 return base::TimeTicks();
155
156 return candidate;
157 }
158
159 void UserInputTracker::RemoveOldInputEvents() {
160 RemoveInputEventsUpToInclusive(
161 GetOldestAllowedEventTime(base::TimeTicks::Now()));
162 }
163
164 void UserInputTracker::RemoveInputEventsUpToInclusive(base::TimeTicks cutoff) {
165 cutoff = std::max(RoundToRateLimitedOffset(cutoff),
166 GetOldestAllowedEventTime(base::TimeTicks::Now()));
167 most_recent_consumed_time_ = std::max(most_recent_consumed_time_, cutoff);
168 sorted_event_times_.erase(
169 sorted_event_times_.begin(),
170 std::upper_bound(sorted_event_times_.begin(), sorted_event_times_.end(),
171 cutoff));
172 }
173
174 // static
175 base::TimeTicks UserInputTracker::GetOldestAllowedEventTime(
176 base::TimeTicks now) {
177 return now - base::TimeDelta::FromSeconds(kOldestAllowedEventAgeSeconds);
178 }
179
180 } // namespace page_load_metrics
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698