File: user_input_tracker.cc

package info (click to toggle)
chromium-browser 57.0.2987.98-1~deb8u1
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 2,637,852 kB
  • ctags: 2,544,394
  • sloc: cpp: 12,815,961; ansic: 3,676,222; python: 1,147,112; asm: 526,608; java: 523,212; xml: 286,794; perl: 92,654; sh: 86,408; objc: 73,271; makefile: 27,698; cs: 18,487; yacc: 13,031; tcl: 12,957; pascal: 4,875; ml: 4,716; lex: 3,904; sql: 3,862; ruby: 1,982; lisp: 1,508; php: 1,368; exp: 404; awk: 325; csh: 117; jsp: 39; sed: 37
file content (181 lines) | stat: -rw-r--r-- 6,453 bytes parent folder | download
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