Chromium Code Reviews| Index: chrome/browser/page_load_metrics/user_input_tracker.cc |
| diff --git a/chrome/browser/page_load_metrics/user_input_tracker.cc b/chrome/browser/page_load_metrics/user_input_tracker.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..5304171c4569efddc7439a09fb7c5358fa6b337d |
| --- /dev/null |
| +++ b/chrome/browser/page_load_metrics/user_input_tracker.cc |
| @@ -0,0 +1,172 @@ |
| +// 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 kMaxEntries, we rate limit the recorded events, |
| +// allowing one per rate limit period. |
| +const int kRateLimitClampMillis = |
| + (kOldestAllowedEventAgeSeconds * 1000) / UserInputTracker::kMaxEntries; |
| + |
| +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 |
|
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
|
| + // 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() { |
| + event_times_.reserve(kMaxEntries); |
| +} |
| + |
| +UserInputTracker::~UserInputTracker() {} |
| + |
| +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
|
| + |
| +// 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) { |
| + RemoveOldInputEvents(); |
| + |
| + 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. |
| + |
| + 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
|
| + 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.
|
| + return; |
| + |
| + if (time > base::TimeTicks::Now()) { |
| + DCHECK(!base::TimeTicks::IsHighResolution()); |
| + return; |
| + } |
| + |
| + // lower_bound finds the first element >= |time|. |
| + auto it = std::lower_bound(event_times_.begin(), event_times_.end(), time); |
| + if (it != event_times_.end() && *it == time) { |
| + // Don't insert duplicate values. |
| + return; |
| + } |
| + |
| + 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.
|
| + DCHECK(event_times_.size() <= kMaxEntries); |
| + DCHECK(std::is_sorted(event_times_.begin(), 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; |
| + |
| + RemoveInputEventsUpTo(time); |
| + return true; |
| +} |
| + |
| +base::TimeTicks UserInputTracker::FindMostRecentUserInputEventBefore( |
| + base::TimeTicks time) { |
| + RemoveOldInputEvents(); |
| + |
| + if (event_times_.empty()) |
| + return base::TimeTicks(); |
| + |
| + // lower_bound finds the first element >= |time|. |
| + auto it = std::lower_bound(event_times_.begin(), event_times_.end(), time); |
| + |
| + // If all times are after the requested time, then we don't have a time to |
| + // return. |
| + if (it == 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(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::RemoveOldInputEvents() { |
| + RemoveInputEventsUpTo(GetOldestAllowedEventTime()); |
| +} |
| + |
| +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
|
| + cutoff = |
| + std::max(RoundToRateLimitedOffset(cutoff), GetOldestAllowedEventTime()); |
| + most_recent_consumed_time_ = std::max(most_recent_consumed_time_, cutoff); |
| + event_times_.erase( |
| + event_times_.begin(), |
| + std::upper_bound(event_times_.begin(), event_times_.end(), cutoff)); |
| +} |
| + |
| +// static |
| +base::TimeTicks UserInputTracker::GetOldestAllowedEventTime() { |
| + return base::TimeTicks::Now() - |
| + base::TimeDelta::FromSeconds(kOldestAllowedEventAgeSeconds); |
| +} |
| + |
| +} // namespace page_load_metrics |