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

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 kMaxEntries, we rate limit the recorded events,
25 // allowing one per rate limit period.
26 const int kRateLimitClampMillis =
27 (kOldestAllowedEventAgeSeconds * 1000) / UserInputTracker::kMaxEntries;
28
29 bool IsInterestingInputEvent(const blink::WebInputEvent& event) {
30 // Ignore synthesized auto repeat events.
31 if (event.modifiers & blink::WebInputEvent::IsAutoRepeat)
32 return false;
33
34 switch (event.type) {
35 case blink::WebInputEvent::MouseDown:
36 case blink::WebInputEvent::MouseUp:
37 case blink::WebInputEvent::RawKeyDown:
38 case blink::WebInputEvent::KeyDown:
39 case blink::WebInputEvent::Char:
40 case blink::WebInputEvent::TouchStart:
41 case blink::WebInputEvent::TouchEnd:
42 return true;
43 default:
44 return false;
45 }
46 }
47
48 base::TimeTicks GetTimeTicksFromSeconds(double seconds) {
49 // WebInputEvent::timeStampSeconds is a double representing number of
Charlie Harrison 2016/12/05 19:17:46 optional: This looks like its only used once, I'd
Bryan McQuade 2016/12/06 01:52:24 You're right, there's no benefit to factoring this
50 // monotonic seconds in TimeTicks time base. There's no convenience API for
51 // initializing a TimeTicks from such a value. The canonical way to perform
52 // this initialization is to create a TimeTicks with value 0 and add a
53 // TimeDelta to it.
54 return base::TimeTicks() + base::TimeDelta::FromSecondsD(seconds);
55 }
56
57 } // namespace
58
59 UserInputTracker::UserInputTracker() {
60 event_times_.reserve(kMaxEntries);
61 }
62
63 UserInputTracker::~UserInputTracker() {}
64
65 const size_t UserInputTracker::kMaxEntries = 100;
Charlie Harrison 2016/12/05 19:17:46 Why 100 and not a nice round number like 128 or 64
Bryan McQuade 2016/12/06 01:52:24 Ha, is 64 or 128 rounder than 100? I guess it depe
Charlie Harrison 2016/12/06 18:43:09 It isn't a huge deal. Aligning to a multiple of 6
66
67 // static
68 base::TimeTicks UserInputTracker::GetEventTime(
69 const blink::WebInputEvent& event) {
70 return GetTimeTicksFromSeconds(event.timeStampSeconds);
71 }
72
73 // static
74 base::TimeTicks UserInputTracker::RoundToRateLimitedOffset(
75 base::TimeTicks time) {
76 base::TimeDelta time_as_delta = time - base::TimeTicks();
77 base::TimeDelta rate_limit_remainder =
78 time_as_delta % base::TimeDelta::FromMilliseconds(kRateLimitClampMillis);
79 return time - rate_limit_remainder;
80 }
81
82 void UserInputTracker::OnInputEvent(const blink::WebInputEvent& event) {
83 RemoveOldInputEvents();
84
85 if (!IsInterestingInputEvent(event))
86 return;
87
88 // TODO(bmcquade): ideally we'd limit tracking to events generated by a user
89 // action, as opposed to those generated from JavaScript. The JS API isTrusted
90 // can be used to distinguish these cases. isTrusted isn't yet a property of
91 // WebInputEvent. We should consider adding it.
92
93 base::TimeTicks time = RoundToRateLimitedOffset(GetEventTime(event));
Charlie Harrison 2016/12/05 19:17:46 Optional: Instead of rounding we could instead jus
Bryan McQuade 2016/12/06 01:52:24 That's an interesting idea. It adds some additiona
Charlie Harrison 2016/12/06 18:43:09 Yeah I'm not sure, after going through the rest of
94 if (time <= std::max(most_recent_consumed_time_, GetOldestAllowedEventTime()))
Charlie Harrison 2016/12/05 19:17:46 GetOldestAllowedEventTime calls the monotonic cloc
Bryan McQuade 2016/12/06 01:52:24 I'm mixed on this. I think the simplest way to do
Charlie Harrison 2016/12/06 18:43:09 Your call. I agree It is a bit awkward to thread t
Charlie Harrison 2016/12/06 22:04:55 What do you think about this idea?
Bryan McQuade 2016/12/06 22:15:21 That is better. I've changed it.
95 return;
96
97 if (time > base::TimeTicks::Now()) {
98 DCHECK(!base::TimeTicks::IsHighResolution());
99 return;
100 }
101
102 // lower_bound finds the first element >= |time|.
103 auto it = std::lower_bound(event_times_.begin(), event_times_.end(), time);
104 if (it != event_times_.end() && *it == time) {
105 // Don't insert duplicate values.
106 return;
107 }
108
109 event_times_.insert(it, time);
Charlie Harrison 2016/12/05 19:17:46 I am curious what the insertion pattern looks like
Bryan McQuade 2016/12/06 01:52:24 Yes, it should be usually at the end, but no guara
Charlie Harrison 2016/12/06 18:43:09 Acknowledged.
110 DCHECK(event_times_.size() <= kMaxEntries);
111 DCHECK(std::is_sorted(event_times_.begin(), event_times_.end()));
112 }
113
114 bool UserInputTracker::FindAndConsumeInputEventsBefore(base::TimeTicks time) {
115 base::TimeTicks recent_input_event_time =
116 FindMostRecentUserInputEventBefore(time);
117
118 if (recent_input_event_time.is_null())
119 return false;
120
121 RemoveInputEventsUpTo(time);
122 return true;
123 }
124
125 base::TimeTicks UserInputTracker::FindMostRecentUserInputEventBefore(
126 base::TimeTicks time) {
127 RemoveOldInputEvents();
128
129 if (event_times_.empty())
130 return base::TimeTicks();
131
132 // lower_bound finds the first element >= |time|.
133 auto it = std::lower_bound(event_times_.begin(), event_times_.end(), time);
134
135 // If all times are after the requested time, then we don't have a time to
136 // return.
137 if (it == event_times_.begin())
138 return base::TimeTicks();
139
140 // |it| points to the first event >= the specified time, so decrement once to
141 // find the greatest event before the specified time.
142 --it;
143 base::TimeTicks candidate = *it;
144 DCHECK(candidate < time);
145
146 // If the most recent event is too old, then don't return it.
147 if (candidate < time - base::TimeDelta::FromSeconds(kMaxEventAgeSeconds))
148 return base::TimeTicks();
149
150 return candidate;
151 }
152
153 void UserInputTracker::RemoveOldInputEvents() {
154 RemoveInputEventsUpTo(GetOldestAllowedEventTime());
155 }
156
157 void UserInputTracker::RemoveInputEventsUpTo(base::TimeTicks cutoff) {
Charlie Harrison 2016/12/05 19:17:46 This looks like it removes input events up to *and
Bryan McQuade 2016/12/06 01:52:24 Ah, interesting, I'd though of 'UpTo' as being inc
158 cutoff =
159 std::max(RoundToRateLimitedOffset(cutoff), GetOldestAllowedEventTime());
160 most_recent_consumed_time_ = std::max(most_recent_consumed_time_, cutoff);
161 event_times_.erase(
162 event_times_.begin(),
163 std::upper_bound(event_times_.begin(), event_times_.end(), cutoff));
164 }
165
166 // static
167 base::TimeTicks UserInputTracker::GetOldestAllowedEventTime() {
168 return base::TimeTicks::Now() -
169 base::TimeDelta::FromSeconds(kOldestAllowedEventAgeSeconds);
170 }
171
172 } // namespace page_load_metrics
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698