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