// Copyright 2015 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 "third_party/blink/renderer/platform/scheduler/main_thread/user_model.h" #include "third_party/perfetto/include/perfetto/tracing/traced_value.h" namespace blink { namespace scheduler { UserModel::UserModel() : pending_input_event_count_(0), is_gesture_active_(false), is_gesture_expected_(false) {} void UserModel::DidStartProcessingInputEvent(blink::WebInputEvent::Type type, const base::TimeTicks now) { last_input_signal_time_ = now; if (type == blink::WebInputEvent::Type::kTouchStart || type == blink::WebInputEvent::Type::kGestureScrollBegin || type == blink::WebInputEvent::Type::kGesturePinchBegin) { // Only update stats once per gesture. if (!is_gesture_active_) last_gesture_start_time_ = now; is_gesture_active_ = true; } // We need to track continuous gestures seperatly for scroll detection // because taps should not be confused with scrolls. if (type == blink::WebInputEvent::Type::kGestureScrollBegin || type == blink::WebInputEvent::Type::kGestureScrollEnd || type == blink::WebInputEvent::Type::kGestureScrollUpdate || type == blink::WebInputEvent::Type::kGestureFlingStart || type == blink::WebInputEvent::Type::kGestureFlingCancel || type == blink::WebInputEvent::Type::kGesturePinchBegin || type == blink::WebInputEvent::Type::kGesturePinchEnd || type == blink::WebInputEvent::Type::kGesturePinchUpdate) { last_continuous_gesture_time_ = now; } // If the gesture has ended, clear |is_gesture_active_| and record a UMA // metric that tracks its duration. if (type == blink::WebInputEvent::Type::kGestureScrollEnd || type == blink::WebInputEvent::Type::kGesturePinchEnd || type == blink::WebInputEvent::Type::kGestureFlingStart || type == blink::WebInputEvent::Type::kTouchEnd) { is_gesture_active_ = false; } TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"), "is_gesture_active", is_gesture_active_); pending_input_event_count_++; } void UserModel::DidFinishProcessingInputEvent(const base::TimeTicks now) { last_input_signal_time_ = now; if (pending_input_event_count_ > 0) pending_input_event_count_--; } base::TimeDelta UserModel::TimeLeftInUserGesture(base::TimeTicks now) const { base::TimeDelta escalated_priority_duration = base::TimeDelta::FromMilliseconds(kGestureEstimationLimitMillis); // If the input event is still pending, go into input prioritized policy and // check again later. if (pending_input_event_count_ > 0) return escalated_priority_duration; if (last_input_signal_time_.is_null() || last_input_signal_time_ + escalated_priority_duration < now) { return base::TimeDelta(); } return last_input_signal_time_ + escalated_priority_duration - now; } bool UserModel::IsGestureExpectedSoon( const base::TimeTicks now, base::TimeDelta* prediction_valid_duration) { bool was_gesture_expected = is_gesture_expected_; is_gesture_expected_ = IsGestureExpectedSoonImpl(now, prediction_valid_duration); // Track when we start expecting a gesture so we can work out later if a // gesture actually happened. if (!was_gesture_expected && is_gesture_expected_) last_gesture_expected_start_time_ = now; return is_gesture_expected_; } bool UserModel::IsGestureExpectedSoonImpl( const base::TimeTicks now, base::TimeDelta* prediction_valid_duration) const { if (is_gesture_active_) { if (IsGestureExpectedToContinue(now, prediction_valid_duration)) return false; *prediction_valid_duration = base::TimeDelta::FromMilliseconds(kExpectSubsequentGestureMillis); return true; } else { // If we have finished a gesture then a subsequent gesture is deemed likely. base::TimeDelta expect_subsequent_gesture_for = base::TimeDelta::FromMilliseconds(kExpectSubsequentGestureMillis); if (last_continuous_gesture_time_.is_null() || last_continuous_gesture_time_ + expect_subsequent_gesture_for <= now) { return false; } *prediction_valid_duration = last_continuous_gesture_time_ + expect_subsequent_gesture_for - now; return true; } } bool UserModel::IsGestureExpectedToContinue( const base::TimeTicks now, base::TimeDelta* prediction_valid_duration) const { if (!is_gesture_active_) return false; base::TimeDelta median_gesture_duration = base::TimeDelta::FromMilliseconds(kMedianGestureDurationMillis); base::TimeTicks expected_gesture_end_time = last_gesture_start_time_ + median_gesture_duration; if (expected_gesture_end_time > now) { *prediction_valid_duration = expected_gesture_end_time - now; return true; } return false; } void UserModel::Reset(base::TimeTicks now) { last_input_signal_time_ = base::TimeTicks(); last_gesture_start_time_ = base::TimeTicks(); last_continuous_gesture_time_ = base::TimeTicks(); last_gesture_expected_start_time_ = base::TimeTicks(); last_reset_time_ = now; is_gesture_active_ = false; is_gesture_expected_ = false; pending_input_event_count_ = 0; } void UserModel::WriteIntoTracedValue(perfetto::TracedValue context) const { auto dict = std::move(context).WriteDictionary(); dict.Add("pending_input_event_count", pending_input_event_count_); dict.Add("last_input_signal_time", last_input_signal_time_); dict.Add("last_gesture_start_time", last_gesture_start_time_); dict.Add("last_continuous_gesture_time", last_continuous_gesture_time_); dict.Add("last_gesture_expected_start_time", last_gesture_expected_start_time_); dict.Add("last_reset_time", last_reset_time_); dict.Add("is_gesture_expected", is_gesture_expected_); dict.Add("is_gesture_active", is_gesture_active_); } } // namespace scheduler } // namespace blink