Commit 827b0b6c authored by Tim Dresser's avatar Tim Dresser Committed by Commit Bot

First Input Delay: include swipes, add UKM.

Previously we excluded swiping input. We now include swipes (map panning,
carousel rotation), but still exclude scroll.

Also records a UKM.

Bug: 803935
Change-Id: I8f9d5a77ad61221eb9b614e8d4e11e2b757af9e2
Reviewed-on: https://chromium-review.googlesource.com/894826
Commit-Queue: Timothy Dresser <tdresser@chromium.org>
Reviewed-by: default avatarDave Tapuska <dtapuska@chromium.org>
Reviewed-by: default avatarBryan McQuade <bmcquade@chromium.org>
Cr-Commit-Position: refs/heads/master@{#534178}
parent 502f4c34
......@@ -32,6 +32,7 @@ const char kUkmFirstContentfulPaintName[] =
const char kUkmFirstMeaningfulPaintName[] =
"Experimental.PaintTiming.NavigationToFirstMeaningfulPaint";
const char kUkmInteractiveName[] = "Experimental.NavigationToInteractive";
const char kUkmFirstInputDelayName[] = "InteractiveTiming.FirstInputDelay";
const char kUkmForegroundDurationName[] = "PageTiming.ForegroundDuration";
const char kUkmFailedProvisionaLoadName[] =
"PageTiming.NavigationToFailedProvisionalLoad";
......@@ -204,6 +205,12 @@ void UkmPageLoadMetricsObserver::RecordTimingMetrics(
time_to_interactive.InMilliseconds());
}
}
if (timing.interactive_timing->first_input_delay) {
base::TimeDelta first_input_delay =
timing.interactive_timing->first_input_delay.value();
builder.SetInteractiveTiming_FirstInputDelay(
first_input_delay.InMilliseconds());
}
// Use a bucket spacing factor of 1.3 for bytes.
builder.SetNet_CacheBytes(ukm::GetExponentialBucketMin(cache_bytes_, 1.3));
......
......@@ -28,6 +28,7 @@ extern const char kUkmFirstPaintName[];
extern const char kUkmFirstContentfulPaintName[];
extern const char kUkmFirstMeaningfulPaintName[];
extern const char kUkmInteractiveName[];
extern const char kUkmFirstInputDelayName[];
extern const char kUkmForegroundDurationName[];
extern const char kUkmFailedProvisionaLoadName[];
extern const char kUkmNetErrorCode[];
......
......@@ -263,6 +263,34 @@ TEST_F(UkmPageLoadMetricsObserverTest, PageInteractiveInputInvalidated) {
kv.second.get(), internal::kUkmForegroundDurationName));
}
}
TEST_F(UkmPageLoadMetricsObserverTest, FirstInputDelay) {
page_load_metrics::mojom::PageLoadTiming timing;
page_load_metrics::InitPageLoadTimingForTest(&timing);
timing.navigation_start = base::Time::FromDoubleT(1);
timing.interactive_timing->first_input_delay =
base::TimeDelta::FromMilliseconds(50);
PopulateRequiredTimingFields(&timing);
NavigateAndCommit(GURL(kTestUrl1));
SimulateTimingUpdate(timing);
// Simulate closing the tab.
DeleteContents();
std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
test_ukm_recorder().GetMergedEntriesByName(
internal::kUkmPageLoadEventName);
EXPECT_EQ(1ul, merged_entries.size());
for (const auto& kv : merged_entries) {
test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(),
GURL(kTestUrl1));
test_ukm_recorder().ExpectEntryMetric(
kv.second.get(), internal::kUkmFirstInputDelayName, 50);
}
}
TEST_F(UkmPageLoadMetricsObserverTest, MultiplePageLoads) {
page_load_metrics::mojom::PageLoadTiming timing1;
page_load_metrics::InitPageLoadTimingForTest(&timing1);
......
......@@ -5097,6 +5097,92 @@ TEST_P(WebViewTest, FirstInputDelayReported) {
EXPECT_EQ(50, interactive_detector->GetFirstInputDelay().InMillisecondsF());
}
// Check that first input delay is correctly reported to the document when the
// first input is a pointer down event, and we receive a pointer up event.
TEST_P(WebViewTest, PointerDownUpFirstInputDelay) {
WebViewImpl* web_view = web_view_helper_.Initialize();
WebURL base_url = URLTestHelpers::ToKURL("http://example.com/");
FrameTestHelpers::LoadHTMLString(web_view->MainFrameImpl(),
"<html><body></body></html>", base_url);
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
ASSERT_NE(nullptr, main_frame);
Document* document = main_frame->GetDocument();
ASSERT_NE(nullptr, document);
WTF::ScopedMockClock clock;
InteractiveDetector* interactive_detector(
InteractiveDetector::From(*document));
ASSERT_NE(nullptr, interactive_detector);
WebPointerEvent pointer_down(
WebInputEvent::kPointerDown,
WebPointerProperties(1, WebPointerProperties::PointerType::kTouch), 5, 5);
pointer_down.SetTimeStampSeconds(CurrentTimeTicksInSeconds());
clock.Advance(TimeDelta::FromMilliseconds(50));
web_view->HandleInputEvent(WebCoalescedInputEvent(pointer_down));
// We don't know if this pointer event will result in a scroll or not, so we
// can't report its delay. We don't consider a scroll to be meaningful input.
EXPECT_EQ(0, interactive_detector->GetFirstInputDelay().InMillisecondsF());
// When we receive a pointer up, we report the delay of the pointer down.
WebPointerEvent pointer_up(
WebInputEvent::kPointerUp,
WebPointerProperties(1, WebPointerProperties::PointerType::kTouch), 5, 5);
clock.Advance(TimeDelta::FromMilliseconds(60));
pointer_up.SetTimeStampSeconds(CurrentTimeTicksInSeconds());
web_view->HandleInputEvent(WebCoalescedInputEvent(pointer_up));
EXPECT_EQ(50, interactive_detector->GetFirstInputDelay().InMillisecondsF());
}
// Check that first input delay isn't reported to the document when the
// first input is a pointer down event followed by a pointer cancel event.
TEST_P(WebViewTest, PointerDownCancelFirstInputDelay) {
WebViewImpl* web_view = web_view_helper_.Initialize();
WebURL base_url = URLTestHelpers::ToKURL("http://example.com/");
FrameTestHelpers::LoadHTMLString(web_view->MainFrameImpl(),
"<html><body></body></html>", base_url);
LocalFrame* main_frame = web_view->MainFrameImpl()->GetFrame();
ASSERT_NE(nullptr, main_frame);
Document* document = main_frame->GetDocument();
ASSERT_NE(nullptr, document);
WTF::ScopedMockClock clock;
InteractiveDetector* interactive_detector(
InteractiveDetector::From(*document));
ASSERT_NE(nullptr, interactive_detector);
WebPointerEvent pointer_down(
WebInputEvent::kPointerDown,
WebPointerProperties(1, WebPointerProperties::PointerType::kTouch), 5, 5);
pointer_down.SetTimeStampSeconds(CurrentTimeTicksInSeconds());
clock.Advance(TimeDelta::FromMilliseconds(50));
web_view->HandleInputEvent(WebCoalescedInputEvent(pointer_down));
// We don't know if this pointer event will result in a scroll or not, so we
// can't report its delay. We don't consider a scroll to be meaningful input.
EXPECT_EQ(0, interactive_detector->GetFirstInputDelay().InMillisecondsF());
// When we receive a pointer up, we report the delay of the pointer down.
WebPointerEvent pointer_cancel(
WebInputEvent::kPointerCancel,
WebPointerProperties(1, WebPointerProperties::PointerType::kTouch), 5, 5);
clock.Advance(TimeDelta::FromMilliseconds(60));
pointer_cancel.SetTimeStampSeconds(CurrentTimeTicksInSeconds());
web_view->HandleInputEvent(WebCoalescedInputEvent(pointer_cancel));
// We received a pointer cancel, so this is a scroll gesture. No meaningful
// input has occurred yet.
EXPECT_EQ(0, interactive_detector->GetFirstInputDelay().InMillisecondsF());
}
// Check that the input delay is correctly reported to the document.
TEST_P(WebViewTest, FirstInputDelayExcludesProcessingTime) {
// We need a way for JS to advance the mock clock. Hook into console.log, so
......
......@@ -39,6 +39,7 @@
#include "core/frame/Frame.h"
#include "core/frame/LocalFrameView.h"
#include "core/loader/FrameLoader.h"
#include "core/loader/InteractiveDetector.h"
#include "core/page/FrameTree.h"
#include "platform/Supplementable.h"
#include "platform/heap/Handle.h"
......
......@@ -10,6 +10,7 @@
#include "platform/instrumentation/tracing/TraceEvent.h"
#include "platform/loader/fetch/ResourceFetcher.h"
#include "platform/wtf/Time.h"
#include "public/platform/WebInputEvent.h"
namespace blink {
......@@ -117,6 +118,50 @@ TimeDelta InteractiveDetector::GetFirstInputDelay() const {
return page_event_times_.first_input_delay;
}
// This is called early enough in the pipeline that we don't need to worry about
// javascript dispatching untrusted input events.
void InteractiveDetector::HandleForFirstInputDelay(const WebInputEvent& event) {
if (!page_event_times_.first_input_delay.is_zero())
return;
DCHECK(event.GetType() != WebInputEvent::kTouchStart);
// We can't report a pointerDown until the pointerUp, in case it turns into a
// scroll.
if (event.GetType() == WebInputEvent::kPointerDown) {
pending_pointerdown_delay_ = TimeDelta::FromSecondsD(
CurrentTimeTicksInSeconds() - event.TimeStampSeconds());
return;
}
bool event_is_meaningful =
event.GetType() == WebInputEvent::kMouseDown ||
event.GetType() == WebInputEvent::kKeyDown ||
event.GetType() == WebInputEvent::kRawKeyDown ||
// We need to explicitly include tap, as if there are no listeners, we
// won't receive the pointer events.
event.GetType() == WebInputEvent::kGestureTap ||
event.GetType() == WebInputEvent::kPointerUp;
if (!event_is_meaningful)
return;
// It is possible that this pointer up doesn't match with the pointer down
// whose delay is stored in pending_pointerdown_delay_. In this case, the user
// gesture started by this event contained some non-scroll input, so we
// consider it reasonable to use the delay of the initial event.
const TimeDelta delay =
event.GetType() == WebInputEvent::kPointerUp
? pending_pointerdown_delay_
: TimeDelta::FromSecondsD(CurrentTimeTicksInSeconds() -
event.TimeStampSeconds());
pending_pointerdown_delay_ = base::TimeDelta();
page_event_times_.first_input_delay = delay;
if (GetSupplementable()->Loader())
GetSupplementable()->Loader()->DidChangePerformanceTiming();
}
void InteractiveDetector::BeginNetworkQuietPeriod(TimeTicks current_time) {
// Value of 0.0 indicates there is no currently actively network quiet window.
DCHECK(active_network_quiet_window_start_.is_null());
......
......@@ -17,6 +17,7 @@
namespace blink {
class Document;
class WebInputEvent;
// Detects when a page reaches First Idle and Time to Interactive. See
// https://goo.gl/SYt55W for detailed description and motivation of First Idle
......@@ -76,6 +77,8 @@ class CORE_EXPORT InteractiveDetector
// thread for the first click, tap or key press.
TimeDelta GetFirstInputDelay() const;
void HandleForFirstInputDelay(const WebInputEvent&);
virtual void Trace(Visitor*);
private:
......@@ -138,6 +141,11 @@ class CORE_EXPORT InteractiveDetector
// LongTaskObserver implementation
void OnLongTaskDetected(TimeTicks start_time, TimeTicks end_time) override;
// The duration between the hardware timestamp and when we received the event
// for the previous pointer down. Only non-zero if we've received a pointer
// down event, and haven't yet reported the first input delay.
base::TimeDelta pending_pointerdown_delay_;
DISALLOW_COPY_AND_ASSIGN(InteractiveDetector);
};
......
......@@ -131,7 +131,18 @@ WebInputEventResult PageWidgetDelegate::HandleInputEvent(
const WebCoalescedInputEvent& coalesced_event,
LocalFrame* root) {
const WebInputEvent& event = coalesced_event.Event();
ReportFirstInputDelay(event, root);
if (root) {
Document* document = root->GetDocument();
DCHECK(document);
InteractiveDetector* interactive_detector(
InteractiveDetector::From(*document));
// interactive_detector is null in the OOPIF case.
// TODO(crbug.com/808089): report across OOPIFs.
if (interactive_detector)
interactive_detector->HandleForFirstInputDelay(event);
}
if (event.GetModifiers() & WebInputEvent::kIsTouchAccessibility &&
WebInputEvent::IsMouseEventType(event.GetType())) {
......
......@@ -1514,6 +1514,15 @@ be describing additional metrics about the same event.
frame documents.
</summary>
</metric>
<metric name="InteractiveTiming.FirstInputDelay">
<summary>
Measures First Input Delay, the duration between the hardware timestamp
and the start of event processing on the main thread for the first
meaningful input per navigation. See
https://docs.google.com/document/d/1Tnobrn4I8ObzreIztfah_BYnDkbx3_ZfJV5gj2nrYnY/edit#
for a detailed explanation. In ms.
</summary>
</metric>
<metric name="Navigation.PageTransition">
<summary>
The |ui::PageTransition| for the main frame navigation of this page load.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment