1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/page_load_metrics/observers/from_gws_page_load_metrics_observer.h"
6 #include <string>
7
8 #include "base/metrics/histogram_macros.h"
9 #include "base/strings/string_util.h"
10 #include "chrome/browser/browser_process.h"
11 #include "components/page_load_metrics/browser/observers/core/largest_contentful_paint_handler.h"
12 #include "components/page_load_metrics/browser/page_load_metrics_util.h"
13 #include "components/page_load_metrics/common/page_load_timing.h"
14 #include "services/metrics/public/cpp/ukm_builders.h"
15 #include "services/metrics/public/cpp/ukm_recorder.h"
16
17 using page_load_metrics::PageAbortReason;
18
19 namespace internal {
20
21 const char kHistogramFromGWSDomContentLoaded[] =
22 "PageLoad.Clients.FromGoogleSearch.DocumentTiming."
23 "NavigationToDOMContentLoadedEventFired";
24 const char kHistogramFromGWSLoad[] =
25 "PageLoad.Clients.FromGoogleSearch.DocumentTiming."
26 "NavigationToLoadEventFired";
27 const char kHistogramFromGWSFirstPaint[] =
28 "PageLoad.Clients.FromGoogleSearch.PaintTiming.NavigationToFirstPaint";
29 const char kHistogramFromGWSFirstImagePaint[] =
30 "PageLoad.Clients.FromGoogleSearch.PaintTiming.NavigationToFirstImagePaint";
31 const char kHistogramFromGWSFirstContentfulPaint[] =
32 "PageLoad.Clients.FromGoogleSearch.PaintTiming."
33 "NavigationToFirstContentfulPaint";
34 const char kHistogramFromGWSLargestContentfulPaint[] =
35 "PageLoad.Clients.FromGoogleSearch.PaintTiming."
36 "NavigationToLargestContentfulPaint";
37 const char kHistogramFromGWSParseStartToFirstContentfulPaint[] =
38 "PageLoad.Clients.FromGoogleSearch.PaintTiming."
39 "ParseStartToFirstContentfulPaint";
40 const char kHistogramFromGWSParseDuration[] =
41 "PageLoad.Clients.FromGoogleSearch.ParseTiming.ParseDuration";
42 const char kHistogramFromGWSParseStart[] =
43 "PageLoad.Clients.FromGoogleSearch.ParseTiming.NavigationToParseStart";
44 const char kHistogramFromGWSFirstInputDelay[] =
45 "PageLoad.Clients.FromGoogleSearch.InteractiveTiming.FirstInputDelay4";
46
47 const char kHistogramFromGWSAbortNewNavigationBeforeCommit[] =
48 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.NewNavigation."
49 "BeforeCommit";
50 const char kHistogramFromGWSAbortNewNavigationBeforePaint[] =
51 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.NewNavigation."
52 "AfterCommit.BeforePaint";
53 const char kHistogramFromGWSAbortNewNavigationBeforeInteraction[] =
54 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.NewNavigation."
55 "AfterPaint.BeforeInteraction";
56 const char kHistogramFromGWSAbortStopBeforeCommit[] =
57 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Stop."
58 "BeforeCommit";
59 const char kHistogramFromGWSAbortStopBeforePaint[] =
60 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Stop."
61 "AfterCommit.BeforePaint";
62 const char kHistogramFromGWSAbortStopBeforeInteraction[] =
63 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Stop."
64 "AfterPaint.BeforeInteraction";
65 const char kHistogramFromGWSAbortCloseBeforeCommit[] =
66 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Close."
67 "BeforeCommit";
68 const char kHistogramFromGWSAbortCloseBeforePaint[] =
69 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Close."
70 "AfterCommit.BeforePaint";
71 const char kHistogramFromGWSAbortCloseBeforeInteraction[] =
72 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Close."
73 "AfterPaint.BeforeInteraction";
74 const char kHistogramFromGWSAbortOtherBeforeCommit[] =
75 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Other."
76 "BeforeCommit";
77 const char kHistogramFromGWSAbortReloadBeforeCommit[] =
78 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Reload."
79 "BeforeCommit";
80 const char kHistogramFromGWSAbortReloadBeforePaint[] =
81 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Reload."
82 "AfterCommit.BeforePaint";
83 const char kHistogramFromGWSAbortReloadBeforeInteraction[] =
84 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Reload."
85 "AfterPaint.Before1sDelayedInteraction";
86 const char kHistogramFromGWSAbortForwardBackBeforeCommit[] =
87 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming."
88 "ForwardBackNavigation.BeforeCommit";
89 const char kHistogramFromGWSAbortForwardBackBeforePaint[] =
90 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming."
91 "ForwardBackNavigation.AfterCommit.BeforePaint";
92 const char kHistogramFromGWSAbortForwardBackBeforeInteraction[] =
93 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming."
94 "ForwardBackNavigation.AfterPaint.Before1sDelayedInteraction";
95 const char kHistogramFromGWSAbortBackgroundBeforeCommit[] =
96 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Background."
97 "BeforeCommit";
98 const char kHistogramFromGWSAbortBackgroundBeforePaint[] =
99 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Background."
100 "AfterCommit.BeforePaint";
101 const char kHistogramFromGWSAbortBackgroundBeforeInteraction[] =
102 "PageLoad.Clients.FromGoogleSearch.Experimental.AbortTiming.Background."
103 "AfterPaint.BeforeInteraction";
104
105 const char kHistogramFromGWSForegroundDuration[] =
106 "PageLoad.Clients.FromGoogleSearch.PageTiming.ForegroundDuration";
107 const char kHistogramFromGWSForegroundDurationAfterPaint[] =
108 "PageLoad.Clients.FromGoogleSearch.PageTiming.ForegroundDuration."
109 "AfterPaint";
110 const char kHistogramFromGWSForegroundDurationWithPaint[] =
111 "PageLoad.Clients.FromGoogleSearch.PageTiming.ForegroundDuration."
112 "WithPaint";
113 const char kHistogramFromGWSForegroundDurationWithoutPaint[] =
114 "PageLoad.Clients.FromGoogleSearch.PageTiming.ForegroundDuration."
115 "WithoutPaint";
116 const char kHistogramFromGWSForegroundDurationNoCommit[] =
117 "PageLoad.Clients.FromGoogleSearch.PageTiming.ForegroundDuration.NoCommit";
118
119 const char kHistogramFromGWSCumulativeLayoutShiftMainFrame[] =
120 "PageLoad.Clients.FromGoogleSearch.LayoutInstability.CumulativeShiftScore."
121 "MainFrame";
122
123 } // namespace internal
124
125 namespace {
126
LogCommittedAbortsBeforePaint(PageAbortReason abort_reason,base::TimeDelta page_end_time)127 void LogCommittedAbortsBeforePaint(PageAbortReason abort_reason,
128 base::TimeDelta page_end_time) {
129 switch (abort_reason) {
130 case PageAbortReason::ABORT_STOP:
131 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSAbortStopBeforePaint,
132 page_end_time);
133 break;
134 case PageAbortReason::ABORT_CLOSE:
135 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSAbortCloseBeforePaint,
136 page_end_time);
137 break;
138 case PageAbortReason::ABORT_NEW_NAVIGATION:
139 PAGE_LOAD_HISTOGRAM(
140 internal::kHistogramFromGWSAbortNewNavigationBeforePaint,
141 page_end_time);
142 break;
143 case PageAbortReason::ABORT_RELOAD:
144 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSAbortReloadBeforePaint,
145 page_end_time);
146 break;
147 case PageAbortReason::ABORT_FORWARD_BACK:
148 PAGE_LOAD_HISTOGRAM(
149 internal::kHistogramFromGWSAbortForwardBackBeforePaint,
150 page_end_time);
151 break;
152 case PageAbortReason::ABORT_BACKGROUND:
153 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSAbortBackgroundBeforePaint,
154 page_end_time);
155 break;
156 default:
157 // These should only be logged for provisional aborts.
158 DCHECK_NE(abort_reason, PageAbortReason::ABORT_OTHER);
159 break;
160 }
161 }
162
LogAbortsAfterPaintBeforeInteraction(const page_load_metrics::PageAbortInfo & abort_info)163 void LogAbortsAfterPaintBeforeInteraction(
164 const page_load_metrics::PageAbortInfo& abort_info) {
165 switch (abort_info.reason) {
166 case PageAbortReason::ABORT_STOP:
167 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSAbortStopBeforeInteraction,
168 abort_info.time_to_abort);
169 break;
170 case PageAbortReason::ABORT_CLOSE:
171 PAGE_LOAD_HISTOGRAM(
172 internal::kHistogramFromGWSAbortCloseBeforeInteraction,
173 abort_info.time_to_abort);
174 break;
175 case PageAbortReason::ABORT_NEW_NAVIGATION:
176 PAGE_LOAD_HISTOGRAM(
177 internal::kHistogramFromGWSAbortNewNavigationBeforeInteraction,
178 abort_info.time_to_abort);
179 break;
180 case PageAbortReason::ABORT_RELOAD:
181 PAGE_LOAD_HISTOGRAM(
182 internal::kHistogramFromGWSAbortReloadBeforeInteraction,
183 abort_info.time_to_abort);
184 break;
185 case PageAbortReason::ABORT_FORWARD_BACK:
186 PAGE_LOAD_HISTOGRAM(
187 internal::kHistogramFromGWSAbortForwardBackBeforeInteraction,
188 abort_info.time_to_abort);
189 break;
190 case PageAbortReason::ABORT_BACKGROUND:
191 PAGE_LOAD_HISTOGRAM(
192 internal::kHistogramFromGWSAbortBackgroundBeforeInteraction,
193 abort_info.time_to_abort);
194 break;
195 default:
196 // These should only be logged for provisional aborts.
197 DCHECK_NE(abort_info.reason, PageAbortReason::ABORT_OTHER);
198 break;
199 }
200 }
201
LogProvisionalAborts(const page_load_metrics::PageAbortInfo & abort_info)202 void LogProvisionalAborts(const page_load_metrics::PageAbortInfo& abort_info) {
203 switch (abort_info.reason) {
204 case PageAbortReason::ABORT_STOP:
205 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSAbortStopBeforeCommit,
206 abort_info.time_to_abort);
207 break;
208 case PageAbortReason::ABORT_CLOSE:
209 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSAbortCloseBeforeCommit,
210 abort_info.time_to_abort);
211 break;
212 case PageAbortReason::ABORT_OTHER:
213 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSAbortOtherBeforeCommit,
214 abort_info.time_to_abort);
215 break;
216 case PageAbortReason::ABORT_NEW_NAVIGATION:
217 PAGE_LOAD_HISTOGRAM(
218 internal::kHistogramFromGWSAbortNewNavigationBeforeCommit,
219 abort_info.time_to_abort);
220 break;
221 case PageAbortReason::ABORT_RELOAD:
222 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSAbortReloadBeforeCommit,
223 abort_info.time_to_abort);
224 break;
225 case PageAbortReason::ABORT_FORWARD_BACK:
226 PAGE_LOAD_HISTOGRAM(
227 internal::kHistogramFromGWSAbortForwardBackBeforeCommit,
228 abort_info.time_to_abort);
229 break;
230 case PageAbortReason::ABORT_BACKGROUND:
231 PAGE_LOAD_HISTOGRAM(
232 internal::kHistogramFromGWSAbortBackgroundBeforeCommit,
233 abort_info.time_to_abort);
234 break;
235 default:
236 NOTREACHED();
237 break;
238 }
239 }
240
LogForegroundDurations(const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate,base::TimeTicks app_background_time)241 void LogForegroundDurations(
242 const page_load_metrics::mojom::PageLoadTiming& timing,
243 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate,
244 base::TimeTicks app_background_time) {
245 base::Optional<base::TimeDelta> foreground_duration =
246 page_load_metrics::GetInitialForegroundDuration(delegate,
247 app_background_time);
248 if (!foreground_duration)
249 return;
250
251 if (delegate.DidCommit()) {
252 PAGE_LOAD_LONG_HISTOGRAM(internal::kHistogramFromGWSForegroundDuration,
253 foreground_duration.value());
254 if (timing.paint_timing->first_paint &&
255 timing.paint_timing->first_paint < foreground_duration) {
256 PAGE_LOAD_LONG_HISTOGRAM(
257 internal::kHistogramFromGWSForegroundDurationAfterPaint,
258 foreground_duration.value() -
259 timing.paint_timing->first_paint.value());
260 PAGE_LOAD_LONG_HISTOGRAM(
261 internal::kHistogramFromGWSForegroundDurationWithPaint,
262 foreground_duration.value());
263 } else {
264 PAGE_LOAD_LONG_HISTOGRAM(
265 internal::kHistogramFromGWSForegroundDurationWithoutPaint,
266 foreground_duration.value());
267 }
268 } else {
269 PAGE_LOAD_LONG_HISTOGRAM(
270 internal::kHistogramFromGWSForegroundDurationNoCommit,
271 foreground_duration.value());
272 }
273 }
274
WasAbortedInForeground(const page_load_metrics::PageLoadMetricsObserverDelegate & delegate,const page_load_metrics::PageAbortInfo & abort_info)275 bool WasAbortedInForeground(
276 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate,
277 const page_load_metrics::PageAbortInfo& abort_info) {
278 if (!delegate.StartedInForeground() ||
279 abort_info.reason == PageAbortReason::ABORT_NONE)
280 return false;
281
282 base::Optional<base::TimeDelta> time_to_abort(abort_info.time_to_abort);
283 if (page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
284 time_to_abort, delegate))
285 return true;
286
287 const base::TimeDelta time_to_first_background =
288 delegate.GetFirstBackgroundTime().value();
289 DCHECK_GT(abort_info.time_to_abort, time_to_first_background);
290 base::TimeDelta background_abort_delta =
291 abort_info.time_to_abort - time_to_first_background;
292 // Consider this a foregrounded abort if it occurred within 100ms of a
293 // background. This is needed for closing some tabs, where the signal for
294 // background is often slightly ahead of the signal for close.
295 if (background_abort_delta.InMilliseconds() < 100)
296 return true;
297 return false;
298 }
299
WasAbortedBeforeInteraction(const page_load_metrics::PageAbortInfo & abort_info,const base::Optional<base::TimeDelta> & time_to_interaction)300 bool WasAbortedBeforeInteraction(
301 const page_load_metrics::PageAbortInfo& abort_info,
302 const base::Optional<base::TimeDelta>& time_to_interaction) {
303 // These conditions should be guaranteed by the call to
304 // WasAbortedInForeground, which is called before WasAbortedBeforeInteraction
305 // gets invoked.
306 DCHECK(abort_info.reason != PageAbortReason::ABORT_NONE);
307
308 if (!time_to_interaction)
309 return true;
310 // For the case the abort is a reload or forward_back. Since pull to
311 // reload / forward_back is the most common user case such aborts being
312 // triggered, add a sanitization threshold here: if the first user
313 // interaction are received before a reload / forward_back in a very
314 // short time, treat the interaction as a gesture to perform the abort.
315
316 // Why 1000ms?
317 // 1000ms is enough to perform a pull to reload / forward_back gesture.
318 // It's also too short a time for a user to consume any content
319 // revealed by the interaction.
320 if (abort_info.reason == PageAbortReason::ABORT_RELOAD ||
321 abort_info.reason == PageAbortReason::ABORT_FORWARD_BACK) {
322 return time_to_interaction.value() +
323 base::TimeDelta::FromMilliseconds(1000) >
324 abort_info.time_to_abort;
325 } else {
326 return time_to_interaction > abort_info.time_to_abort;
327 }
328 }
329
LayoutShiftUmaValue(float shift_score)330 int32_t LayoutShiftUmaValue(float shift_score) {
331 // Report (shift_score * 10) as an int in the range [0, 100].
332 return static_cast<int>(roundf(std::min(shift_score, 10.0f) * 10.0f));
333 }
334
335 } // namespace
336
337 FromGWSPageLoadMetricsLogger::FromGWSPageLoadMetricsLogger() = default;
338 FromGWSPageLoadMetricsLogger::~FromGWSPageLoadMetricsLogger() = default;
339
SetPreviouslyCommittedUrl(const GURL & url)340 void FromGWSPageLoadMetricsLogger::SetPreviouslyCommittedUrl(const GURL& url) {
341 previously_committed_url_is_search_results_ =
342 page_load_metrics::IsGoogleSearchResultUrl(url);
343 previously_committed_url_is_search_redirector_ =
344 page_load_metrics::IsGoogleSearchRedirectorUrl(url);
345 }
346
SetProvisionalUrl(const GURL & url)347 void FromGWSPageLoadMetricsLogger::SetProvisionalUrl(const GURL& url) {
348 provisional_url_has_search_hostname_ =
349 page_load_metrics::IsGoogleSearchHostname(url);
350 }
351
FromGWSPageLoadMetricsObserver()352 FromGWSPageLoadMetricsObserver::FromGWSPageLoadMetricsObserver() {}
353
354 page_load_metrics::PageLoadMetricsObserver::ObservePolicy
OnStart(content::NavigationHandle * navigation_handle,const GURL & currently_committed_url,bool started_in_foreground)355 FromGWSPageLoadMetricsObserver::OnStart(
356 content::NavigationHandle* navigation_handle,
357 const GURL& currently_committed_url,
358 bool started_in_foreground) {
359 logger_.SetPreviouslyCommittedUrl(currently_committed_url);
360 logger_.SetProvisionalUrl(navigation_handle->GetURL());
361 return CONTINUE_OBSERVING;
362 }
363
364 page_load_metrics::PageLoadMetricsObserver::ObservePolicy
OnCommit(content::NavigationHandle * navigation_handle,ukm::SourceId source_id)365 FromGWSPageLoadMetricsObserver::OnCommit(
366 content::NavigationHandle* navigation_handle,
367 ukm::SourceId source_id) {
368 // We'd like to also check navigation_handle->HasUserGesture() here, however
369 // this signal is not carried forward for navigations that open links in new
370 // tabs, so we look only at PAGE_TRANSITION_LINK. Back/forward navigations
371 // that were originally navigated from a link will continue to report a core
372 // type of link, so to filter out back/forward navs, we also check that the
373 // page transition is a new navigation.
374 logger_.set_navigation_initiated_via_link(
375 ui::PageTransitionCoreTypeIs(navigation_handle->GetPageTransition(),
376 ui::PAGE_TRANSITION_LINK) &&
377 ui::PageTransitionIsNewNavigation(
378 navigation_handle->GetPageTransition()));
379
380 logger_.SetNavigationStart(navigation_handle->NavigationStart());
381 logger_.OnCommit(navigation_handle, source_id);
382 return CONTINUE_OBSERVING;
383 }
384
385 page_load_metrics::PageLoadMetricsObserver::ObservePolicy
FlushMetricsOnAppEnterBackground(const page_load_metrics::mojom::PageLoadTiming & timing)386 FromGWSPageLoadMetricsObserver::FlushMetricsOnAppEnterBackground(
387 const page_load_metrics::mojom::PageLoadTiming& timing) {
388 logger_.FlushMetricsOnAppEnterBackground(timing, GetDelegate());
389 return STOP_OBSERVING;
390 }
391
OnDomContentLoadedEventStart(const page_load_metrics::mojom::PageLoadTiming & timing)392 void FromGWSPageLoadMetricsObserver::OnDomContentLoadedEventStart(
393 const page_load_metrics::mojom::PageLoadTiming& timing) {
394 logger_.OnDomContentLoadedEventStart(timing, GetDelegate());
395 }
396
OnLoadEventStart(const page_load_metrics::mojom::PageLoadTiming & timing)397 void FromGWSPageLoadMetricsObserver::OnLoadEventStart(
398 const page_load_metrics::mojom::PageLoadTiming& timing) {
399 logger_.OnLoadEventStart(timing, GetDelegate());
400 }
401
OnFirstPaintInPage(const page_load_metrics::mojom::PageLoadTiming & timing)402 void FromGWSPageLoadMetricsObserver::OnFirstPaintInPage(
403 const page_load_metrics::mojom::PageLoadTiming& timing) {
404 logger_.OnFirstPaintInPage(timing, GetDelegate());
405 }
406
OnFirstImagePaintInPage(const page_load_metrics::mojom::PageLoadTiming & timing)407 void FromGWSPageLoadMetricsObserver::OnFirstImagePaintInPage(
408 const page_load_metrics::mojom::PageLoadTiming& timing) {
409 logger_.OnFirstImagePaintInPage(timing, GetDelegate());
410 }
411
OnFirstContentfulPaintInPage(const page_load_metrics::mojom::PageLoadTiming & timing)412 void FromGWSPageLoadMetricsObserver::OnFirstContentfulPaintInPage(
413 const page_load_metrics::mojom::PageLoadTiming& timing) {
414 logger_.OnFirstContentfulPaintInPage(timing, GetDelegate());
415 }
416
OnFirstInputInPage(const page_load_metrics::mojom::PageLoadTiming & timing)417 void FromGWSPageLoadMetricsObserver::OnFirstInputInPage(
418 const page_load_metrics::mojom::PageLoadTiming& timing) {
419 logger_.OnFirstInputInPage(timing, GetDelegate());
420 }
421
OnParseStart(const page_load_metrics::mojom::PageLoadTiming & timing)422 void FromGWSPageLoadMetricsObserver::OnParseStart(
423 const page_load_metrics::mojom::PageLoadTiming& timing) {
424 logger_.OnParseStart(timing, GetDelegate());
425 }
426
OnParseStop(const page_load_metrics::mojom::PageLoadTiming & timing)427 void FromGWSPageLoadMetricsObserver::OnParseStop(
428 const page_load_metrics::mojom::PageLoadTiming& timing) {
429 logger_.OnParseStop(timing, GetDelegate());
430 }
431
OnComplete(const page_load_metrics::mojom::PageLoadTiming & timing)432 void FromGWSPageLoadMetricsObserver::OnComplete(
433 const page_load_metrics::mojom::PageLoadTiming& timing) {
434 logger_.OnComplete(timing, GetDelegate());
435 }
436
OnFailedProvisionalLoad(const page_load_metrics::FailedProvisionalLoadInfo & failed_load_info)437 void FromGWSPageLoadMetricsObserver::OnFailedProvisionalLoad(
438 const page_load_metrics::FailedProvisionalLoadInfo& failed_load_info) {
439 logger_.OnFailedProvisionalLoad(failed_load_info, GetDelegate());
440 }
441
OnUserInput(const blink::WebInputEvent & event,const page_load_metrics::mojom::PageLoadTiming & timing)442 void FromGWSPageLoadMetricsObserver::OnUserInput(
443 const blink::WebInputEvent& event,
444 const page_load_metrics::mojom::PageLoadTiming& timing) {
445 logger_.OnUserInput(event, timing, GetDelegate());
446 }
447
OnCommit(content::NavigationHandle * navigation_handle,ukm::SourceId source_id)448 void FromGWSPageLoadMetricsLogger::OnCommit(
449 content::NavigationHandle* navigation_handle,
450 ukm::SourceId source_id) {
451 if (!ShouldLogPostCommitMetrics(navigation_handle->GetURL()))
452 return;
453 ukm::builders::PageLoad_FromGoogleSearch(source_id).Record(
454 ukm::UkmRecorder::Get());
455 }
456
OnComplete(const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)457 void FromGWSPageLoadMetricsLogger::OnComplete(
458 const page_load_metrics::mojom::PageLoadTiming& timing,
459 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
460 if (!ShouldLogPostCommitMetrics(delegate.GetUrl()))
461 return;
462
463 LogMetricsOnComplete(delegate);
464
465 page_load_metrics::PageAbortInfo abort_info = GetPageAbortInfo(delegate);
466 if (!WasAbortedInForeground(delegate, abort_info))
467 return;
468
469 // If we did not receive any timing IPCs from the render process, we can't
470 // know for certain if the page was truly aborted before paint, or if the
471 // abort happened before we received the IPC from the render process. Thus, we
472 // do not log aborts for these page loads. Tracked page loads that receive no
473 // timing IPCs are tracked via the ERR_NO_IPCS_RECEIVED error code in the
474 // PageLoad.Events.InternalError histogram, so we can keep track of how often
475 // this happens.
476 if (page_load_metrics::IsEmpty(timing))
477 return;
478
479 if (!timing.paint_timing->first_paint ||
480 timing.paint_timing->first_paint >= abort_info.time_to_abort) {
481 LogCommittedAbortsBeforePaint(abort_info.reason, abort_info.time_to_abort);
482 } else if (WasAbortedBeforeInteraction(abort_info,
483 first_user_interaction_after_paint_)) {
484 LogAbortsAfterPaintBeforeInteraction(abort_info);
485 }
486
487 LogForegroundDurations(timing, delegate, base::TimeTicks());
488 }
489
OnFailedProvisionalLoad(const page_load_metrics::FailedProvisionalLoadInfo & failed_load_info,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)490 void FromGWSPageLoadMetricsLogger::OnFailedProvisionalLoad(
491 const page_load_metrics::FailedProvisionalLoadInfo& failed_load_info,
492 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
493 if (!ShouldLogFailedProvisionalLoadMetrics())
494 return;
495
496 page_load_metrics::PageAbortInfo abort_info = GetPageAbortInfo(delegate);
497 if (!WasAbortedInForeground(delegate, abort_info))
498 return;
499
500 LogProvisionalAborts(abort_info);
501
502 LogForegroundDurations(page_load_metrics::mojom::PageLoadTiming(), delegate,
503 base::TimeTicks());
504 }
505
ShouldLogFailedProvisionalLoadMetrics()506 bool FromGWSPageLoadMetricsLogger::ShouldLogFailedProvisionalLoadMetrics() {
507 // See comment in ShouldLogPostCommitMetrics above the call to
508 // page_load_metrics::IsGoogleSearchHostname for more info on this if test.
509 if (provisional_url_has_search_hostname_)
510 return false;
511
512 return previously_committed_url_is_search_results_ ||
513 previously_committed_url_is_search_redirector_;
514 }
515
ShouldLogPostCommitMetrics(const GURL & url)516 bool FromGWSPageLoadMetricsLogger::ShouldLogPostCommitMetrics(const GURL& url) {
517 DCHECK(!url.is_empty());
518
519 // If this page has a URL on a known google search hostname, then it may be a
520 // page associated with search (either a search results page, or a search
521 // redirector url), so we should not log stats. We could try to detect only
522 // the specific known search URLs here, and log navigations to other pages on
523 // the google search hostname (for example, a search for 'about google'
524 // includes a result for https://www.google.com/about/), however, we assume
525 // these cases are relatively uncommon, and we run the risk of logging metrics
526 // for some search redirector URLs. Thus we choose the more conservative
527 // approach of ignoring all urls on known search hostnames.
528 if (page_load_metrics::IsGoogleSearchHostname(url))
529 return false;
530
531 // We're only interested in tracking navigations (e.g. clicks) initiated via
532 // links. Note that the redirector will mask these, so don't enforce this if
533 // the navigation came from a redirect url. TODO(csharrison): Use this signal
534 // for provisional loads when the content APIs allow for it.
535 if (previously_committed_url_is_search_results_ &&
536 navigation_initiated_via_link_) {
537 return true;
538 }
539
540 // If the navigation was via the search redirector, then the information about
541 // whether the navigation was from a link would have been associated with the
542 // navigation to the redirector, and not included in the redirected
543 // navigation. Therefore, do not require link navigation this case.
544 return previously_committed_url_is_search_redirector_;
545 }
546
ShouldLogForegroundEventAfterCommit(const base::Optional<base::TimeDelta> & event,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)547 bool FromGWSPageLoadMetricsLogger::ShouldLogForegroundEventAfterCommit(
548 const base::Optional<base::TimeDelta>& event,
549 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
550 DCHECK(delegate.DidCommit())
551 << "ShouldLogForegroundEventAfterCommit called without committed URL.";
552 return ShouldLogPostCommitMetrics(delegate.GetUrl()) &&
553 page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
554 event, delegate);
555 }
556
OnDomContentLoadedEventStart(const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)557 void FromGWSPageLoadMetricsLogger::OnDomContentLoadedEventStart(
558 const page_load_metrics::mojom::PageLoadTiming& timing,
559 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
560 if (ShouldLogForegroundEventAfterCommit(
561 timing.document_timing->dom_content_loaded_event_start, delegate)) {
562 PAGE_LOAD_HISTOGRAM(
563 internal::kHistogramFromGWSDomContentLoaded,
564 timing.document_timing->dom_content_loaded_event_start.value());
565 }
566 }
567
OnLoadEventStart(const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)568 void FromGWSPageLoadMetricsLogger::OnLoadEventStart(
569 const page_load_metrics::mojom::PageLoadTiming& timing,
570 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
571 if (ShouldLogForegroundEventAfterCommit(
572 timing.document_timing->load_event_start, delegate)) {
573 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSLoad,
574 timing.document_timing->load_event_start.value());
575 }
576 }
577
OnFirstPaintInPage(const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)578 void FromGWSPageLoadMetricsLogger::OnFirstPaintInPage(
579 const page_load_metrics::mojom::PageLoadTiming& timing,
580 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
581 if (ShouldLogForegroundEventAfterCommit(timing.paint_timing->first_paint,
582 delegate)) {
583 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSFirstPaint,
584 timing.paint_timing->first_paint.value());
585 }
586 first_paint_triggered_ = true;
587 }
588
OnFirstImagePaintInPage(const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)589 void FromGWSPageLoadMetricsLogger::OnFirstImagePaintInPage(
590 const page_load_metrics::mojom::PageLoadTiming& timing,
591 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
592 if (ShouldLogForegroundEventAfterCommit(
593 timing.paint_timing->first_image_paint, delegate)) {
594 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSFirstImagePaint,
595 timing.paint_timing->first_image_paint.value());
596 }
597 }
598
OnFirstContentfulPaintInPage(const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)599 void FromGWSPageLoadMetricsLogger::OnFirstContentfulPaintInPage(
600 const page_load_metrics::mojom::PageLoadTiming& timing,
601 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
602 if (ShouldLogForegroundEventAfterCommit(
603 timing.paint_timing->first_contentful_paint, delegate)) {
604 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSFirstContentfulPaint,
605 timing.paint_timing->first_contentful_paint.value());
606
607 // If we have a foreground paint, we should have a foreground parse start,
608 // since paints can't happen until after parsing starts.
609 DCHECK(page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
610 timing.parse_timing->parse_start, delegate));
611 PAGE_LOAD_HISTOGRAM(
612 internal::kHistogramFromGWSParseStartToFirstContentfulPaint,
613 timing.paint_timing->first_contentful_paint.value() -
614 timing.parse_timing->parse_start.value());
615 }
616 }
617
OnFirstInputInPage(const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)618 void FromGWSPageLoadMetricsLogger::OnFirstInputInPage(
619 const page_load_metrics::mojom::PageLoadTiming& timing,
620 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
621 if (ShouldLogForegroundEventAfterCommit(
622 timing.interactive_timing->first_input_delay, delegate)) {
623 UMA_HISTOGRAM_CUSTOM_TIMES(
624 internal::kHistogramFromGWSFirstInputDelay,
625 timing.interactive_timing->first_input_delay.value(),
626 base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromSeconds(60),
627 50);
628 }
629 }
630
OnParseStart(const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)631 void FromGWSPageLoadMetricsLogger::OnParseStart(
632 const page_load_metrics::mojom::PageLoadTiming& timing,
633 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
634 if (ShouldLogForegroundEventAfterCommit(timing.parse_timing->parse_start,
635 delegate)) {
636 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSParseStart,
637 timing.parse_timing->parse_start.value());
638 }
639 }
640
OnParseStop(const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)641 void FromGWSPageLoadMetricsLogger::OnParseStop(
642 const page_load_metrics::mojom::PageLoadTiming& timing,
643 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
644 if (ShouldLogForegroundEventAfterCommit(timing.parse_timing->parse_stop,
645 delegate)) {
646 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSParseDuration,
647 timing.parse_timing->parse_stop.value() -
648 timing.parse_timing->parse_start.value());
649 }
650 }
651
OnUserInput(const blink::WebInputEvent & event,const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)652 void FromGWSPageLoadMetricsLogger::OnUserInput(
653 const blink::WebInputEvent& event,
654 const page_load_metrics::mojom::PageLoadTiming& timing,
655 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
656 if (first_paint_triggered_ && !first_user_interaction_after_paint_) {
657 DCHECK(!navigation_start_.is_null());
658 first_user_interaction_after_paint_ =
659 base::TimeTicks::Now() - navigation_start_;
660 }
661 }
662
FlushMetricsOnAppEnterBackground(const page_load_metrics::mojom::PageLoadTiming & timing,const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)663 void FromGWSPageLoadMetricsLogger::FlushMetricsOnAppEnterBackground(
664 const page_load_metrics::mojom::PageLoadTiming& timing,
665 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
666 LogMetricsOnComplete(delegate);
667 LogForegroundDurations(timing, delegate, base::TimeTicks::Now());
668 }
669
LogMetricsOnComplete(const page_load_metrics::PageLoadMetricsObserverDelegate & delegate)670 void FromGWSPageLoadMetricsLogger::LogMetricsOnComplete(
671 const page_load_metrics::PageLoadMetricsObserverDelegate& delegate) {
672 if (!delegate.DidCommit() || !ShouldLogPostCommitMetrics(delegate.GetUrl()))
673 return;
674
675 const page_load_metrics::ContentfulPaintTimingInfo&
676 all_frames_largest_contentful_paint =
677 delegate.GetLargestContentfulPaintHandler()
678 .MergeMainFrameAndSubframes();
679 if (all_frames_largest_contentful_paint.ContainsValidTime() &&
680 WasStartedInForegroundOptionalEventInForeground(
681 all_frames_largest_contentful_paint.Time(), delegate)) {
682 PAGE_LOAD_HISTOGRAM(internal::kHistogramFromGWSLargestContentfulPaint,
683 all_frames_largest_contentful_paint.Time().value());
684 }
685
686 UMA_HISTOGRAM_COUNTS_100(
687 internal::kHistogramFromGWSCumulativeLayoutShiftMainFrame,
688 LayoutShiftUmaValue(
689 delegate.GetMainFrameRenderData().layout_shift_score));
690 }
691