blob: 01f5820d2a1a62c47cf7e073fbc1c0700e566d1d [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/input/render_input_router_latency_tracker.h"
#include <stddef.h>
#include <string>
#include "base/check_op.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "base/trace_event/trace_id_helper.h"
#include "build/build_config.h"
#include "components/input/render_input_router_delegate.h"
#include "ui/events/blink/web_input_event_traits.h"
using blink::WebGestureEvent;
using blink::WebInputEvent;
using blink::WebMouseEvent;
using blink::WebMouseWheelEvent;
using blink::WebTouchEvent;
using ui::LatencyInfo;
namespace input {
namespace {
const char* GetTraceNameFromType(blink::WebInputEvent::Type type) {
#define CASE_TYPE(t) \
case WebInputEvent::Type::k##t: \
return "InputLatency::" #t
switch (type) {
CASE_TYPE(Undefined);
CASE_TYPE(MouseDown);
CASE_TYPE(MouseUp);
CASE_TYPE(MouseMove);
CASE_TYPE(MouseEnter);
CASE_TYPE(MouseLeave);
CASE_TYPE(ContextMenu);
CASE_TYPE(MouseWheel);
CASE_TYPE(RawKeyDown);
CASE_TYPE(KeyDown);
CASE_TYPE(KeyUp);
CASE_TYPE(Char);
CASE_TYPE(GestureScrollBegin);
CASE_TYPE(GestureScrollEnd);
CASE_TYPE(GestureScrollUpdate);
CASE_TYPE(GestureFlingStart);
CASE_TYPE(GestureFlingCancel);
CASE_TYPE(GestureShowPress);
CASE_TYPE(GestureTap);
CASE_TYPE(GestureTapUnconfirmed);
CASE_TYPE(GestureTapDown);
CASE_TYPE(GestureTapCancel);
CASE_TYPE(GestureDoubleTap);
CASE_TYPE(GestureTwoFingerTap);
CASE_TYPE(GestureShortPress);
CASE_TYPE(GestureLongPress);
CASE_TYPE(GestureLongTap);
CASE_TYPE(GestureBegin);
CASE_TYPE(GestureEnd);
CASE_TYPE(GesturePinchBegin);
CASE_TYPE(GesturePinchEnd);
CASE_TYPE(GesturePinchUpdate);
CASE_TYPE(TouchStart);
CASE_TYPE(TouchMove);
CASE_TYPE(TouchEnd);
CASE_TYPE(TouchCancel);
CASE_TYPE(TouchScrollStarted);
CASE_TYPE(PointerDown);
CASE_TYPE(PointerUp);
CASE_TYPE(PointerMove);
CASE_TYPE(PointerRawUpdate);
CASE_TYPE(PointerCancel);
CASE_TYPE(PointerCausedUaAction);
}
#undef CASE_TYPE
NOTREACHED();
}
} // namespace
RenderInputRouterLatencyTracker::RenderInputRouterLatencyTracker(
RenderInputRouterDelegate* delegate)
: has_seen_first_gesture_scroll_update_(false),
gesture_scroll_id_(-1),
touch_trace_id_(-1),
active_multi_finger_gesture_(false),
touch_start_default_prevented_(false),
render_input_router_delegate_(delegate) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
RenderInputRouterLatencyTracker::~RenderInputRouterLatencyTracker() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void RenderInputRouterLatencyTracker::OnInputEvent(
const blink::WebInputEvent& event,
LatencyInfo* latency,
ui::EventLatencyMetadata* event_latency_metadata) {
DCHECK(latency);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
OnEventStart(latency);
if (event.GetType() == WebInputEvent::Type::kTouchStart) {
const WebTouchEvent& touch_event =
*static_cast<const WebTouchEvent*>(&event);
DCHECK_GE(touch_event.touches_length, static_cast<unsigned>(1));
active_multi_finger_gesture_ = touch_event.touches_length != 1;
}
// This is the only place to add the BEGIN_RWH component. So this component
// should not already be present in the latency info.
bool found_component = latency->FindLatency(
ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr);
DCHECK(!found_component);
if (!event.TimeStamp().is_null() &&
!latency->FindLatency(ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
nullptr)) {
base::TimeTicks timestamp_now = base::TimeTicks::Now();
base::TimeTicks timestamp_original = event.TimeStamp();
// Timestamp from platform input can wrap, e.g. 32 bits timestamp
// for Xserver and Window MSG time will wrap about 49.6 days. Do a
// sanity check here and if wrap does happen, use TimeTicks::Now()
// as the timestamp instead.
if ((timestamp_now - timestamp_original).InDays() > 0)
timestamp_original = timestamp_now;
latency->AddLatencyNumberWithTimestamp(
ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, timestamp_original);
}
base::TimeTicks begin_rwh_timestamp = base::TimeTicks::Now();
latency->AddLatencyNumberWithTraceName(
ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
GetTraceNameFromType(event.GetType()), begin_rwh_timestamp);
event_latency_metadata->arrived_in_browser_main_timestamp =
begin_rwh_timestamp;
if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollBegin) {
has_seen_first_gesture_scroll_update_ = false;
gesture_scroll_id_ = latency->trace_id();
latency->set_gesture_scroll_id(gesture_scroll_id_);
} else if (event.GetType() ==
blink::WebInputEvent::Type::kGestureScrollUpdate) {
// Make a copy of the INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT with a
// different name INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT.
// So we can track the latency specifically for scroll update events.
base::TimeTicks original_event_timestamp;
if (latency->FindLatency(ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
&original_event_timestamp)) {
latency->AddLatencyNumberWithTimestamp(
has_seen_first_gesture_scroll_update_
? ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT
: ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT,
original_event_timestamp);
}
has_seen_first_gesture_scroll_update_ = true;
latency->set_gesture_scroll_id(gesture_scroll_id_);
} else if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollEnd) {
latency->set_gesture_scroll_id(gesture_scroll_id_);
gesture_scroll_id_ = -1;
} else if (blink::WebInputEvent::IsTouchEventType(event.GetType())) {
// Store the trace id for the TouchStart event on all other Touch events
// until the corresponding end or cancel event so they can be grouped
// together.
if (event.GetType() == blink::WebInputEvent::Type::kTouchStart)
touch_trace_id_ = latency->trace_id();
latency->set_touch_trace_id(touch_trace_id_);
if (event.GetType() == blink::WebInputEvent::Type::kTouchEnd ||
event.GetType() == blink::WebInputEvent::Type::kTouchCancel)
touch_trace_id_ = -1;
}
}
void RenderInputRouterLatencyTracker::OnInputEventAck(
const blink::WebInputEvent& event,
LatencyInfo* latency,
blink::mojom::InputEventResultState ack_result) {
DCHECK(latency);
// Latency ends if an event is acked but does not cause render scheduling.
bool rendering_scheduled = latency->FindLatency(
ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_MAIN_COMPONENT, nullptr);
rendering_scheduled |= latency->FindLatency(
ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT, nullptr);
if (WebInputEvent::IsTouchEventType(event.GetType())) {
const WebTouchEvent& touch_event =
*static_cast<const WebTouchEvent*>(&event);
if (event.GetType() == WebInputEvent::Type::kTouchStart) {
touch_start_default_prevented_ =
ack_result == blink::mojom::InputEventResultState::kConsumed;
} else if (event.GetType() == WebInputEvent::Type::kTouchEnd ||
event.GetType() == WebInputEvent::Type::kTouchCancel) {
active_multi_finger_gesture_ = touch_event.touches_length > 2;
}
}
// If this event couldn't have caused a gesture event, and it didn't trigger
// rendering, we're done processing it. If the event got coalesced then
// terminate it as well. We also exclude cases where we're against the scroll
// extent from scrolling metrics.
if (!rendering_scheduled || latency->coalesced() ||
(event.GetType() == WebInputEvent::Type::kGestureScrollUpdate &&
ack_result == blink::mojom::InputEventResultState::kNoConsumerExists)) {
latency->Terminate();
}
}
void RenderInputRouterLatencyTracker::OnEventStart(ui::LatencyInfo* latency) {
if (latency->trace_id() == -1) {
latency->set_trace_id(base::trace_event::GetNextGlobalTraceId());
}
}
} // namespace input