1 // Copyright 2017 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/core/ukm_page_load_metrics_observer.h"
6
7 #include <cmath>
8 #include <memory>
9 #include <vector>
10
11 #include "base/feature_list.h"
12 #include "base/timer/elapsed_timer.h"
13 #include "base/trace_event/common/trace_event_common.h"
14 #include "cc/metrics/ukm_smoothness_data.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/content_settings/cookie_settings_factory.h"
17 #include "chrome/browser/engagement/site_engagement_service.h"
18 #include "chrome/browser/prefetch/no_state_prefetch/prerender_manager_factory.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/search_engines/template_url_service_factory.h"
21 #include "chrome/common/pref_names.h"
22 #include "components/content_settings/core/browser/cookie_settings.h"
23 #include "components/content_settings/core/common/features.h"
24 #include "components/content_settings/core/common/pref_names.h"
25 #include "components/metrics/net/network_metrics_provider.h"
26 #include "components/no_state_prefetch/browser/prerender_manager.h"
27 #include "components/no_state_prefetch/browser/prerender_util.h"
28 #include "components/no_state_prefetch/common/prerender_final_status.h"
29 #include "components/no_state_prefetch/common/prerender_origin.h"
30 #include "components/offline_pages/buildflags/buildflags.h"
31 #include "components/page_load_metrics/browser/observers/core/largest_contentful_paint_handler.h"
32 #include "components/page_load_metrics/browser/page_load_metrics_util.h"
33 #include "components/page_load_metrics/browser/protocol_util.h"
34 #include "components/prefs/pref_service.h"
35 #include "components/search_engines/template_url_service.h"
36 #include "content/public/browser/navigation_entry.h"
37 #include "content/public/browser/web_contents.h"
38 #include "media/base/mime_util.h"
39 #include "net/base/load_timing_info.h"
40 #include "net/http/http_response_headers.h"
41 #include "services/metrics/public/cpp/metrics_utils.h"
42 #include "services/metrics/public/cpp/ukm_builders.h"
43 #include "services/metrics/public/cpp/ukm_recorder.h"
44 #include "services/network/public/cpp/network_quality_tracker.h"
45 #include "third_party/blink/public/common/mime_util/mime_util.h"
46 #include "third_party/metrics_proto/system_profile.pb.h"
47 #include "ui/events/blink/blink_features.h"
48
49 #if BUILDFLAG(ENABLE_OFFLINE_PAGES)
50 #include "chrome/browser/offline_pages/offline_page_tab_helper.h"
51 #endif
52
53 namespace {
54
55 const char kOfflinePreviewsMimeType[] = "multipart/related";
56 extern const base::Feature kLayoutShiftNormalizationRecordUKM{
57 "LayoutShiftNormalizationRecordUKM", base::FEATURE_DISABLED_BY_DEFAULT};
58
IsSupportedProtocol(page_load_metrics::NetworkProtocol protocol)59 bool IsSupportedProtocol(page_load_metrics::NetworkProtocol protocol) {
60 switch (protocol) {
61 case page_load_metrics::NetworkProtocol::kHttp11:
62 return true;
63 case page_load_metrics::NetworkProtocol::kHttp2:
64 return true;
65 case page_load_metrics::NetworkProtocol::kQuic:
66 return true;
67 case page_load_metrics::NetworkProtocol::kOther:
68 return false;
69 }
70 }
71
IsDefaultSearchEngine(content::BrowserContext * browser_context,const GURL & url)72 bool IsDefaultSearchEngine(content::BrowserContext* browser_context,
73 const GURL& url) {
74 if (!browser_context)
75 return false;
76
77 auto* template_service = TemplateURLServiceFactory::GetForProfile(
78 Profile::FromBrowserContext(browser_context));
79
80 if (!template_service)
81 return false;
82
83 return template_service->IsSearchResultsPageFromDefaultSearchProvider(url);
84 }
85
IsUserHomePage(content::BrowserContext * browser_context,const GURL & url)86 bool IsUserHomePage(content::BrowserContext* browser_context, const GURL& url) {
87 if (!browser_context)
88 return false;
89
90 return url.spec() == Profile::FromBrowserContext(browser_context)
91 ->GetPrefs()
92 ->GetString(prefs::kHomePage);
93 }
94
CumulativeShiftScoreTraceData(float layout_shift_score,float layout_shift_score_before_input_or_scroll)95 std::unique_ptr<base::trace_event::TracedValue> CumulativeShiftScoreTraceData(
96 float layout_shift_score,
97 float layout_shift_score_before_input_or_scroll) {
98 std::unique_ptr<base::trace_event::TracedValue> data =
99 std::make_unique<base::trace_event::TracedValue>();
100 data->SetDouble("layoutShiftScore", layout_shift_score);
101 data->SetDouble("layoutShiftScoreBeforeInputOrScroll",
102 layout_shift_score_before_input_or_scroll);
103 return data;
104 }
105
SiteInstanceRenderProcessAssignmentToInt(content::SiteInstanceProcessAssignment assignment)106 int SiteInstanceRenderProcessAssignmentToInt(
107 content::SiteInstanceProcessAssignment assignment) {
108 // These values are logged in UKM and should not be reordered or changed. Add
109 // new values to the end and be sure to update the enum
110 // |SiteInstanceProcessAssignment| in
111 // //tools/metrics/histograms/enums.xml.
112 switch (assignment) {
113 case content::SiteInstanceProcessAssignment::UNKNOWN:
114 return 0;
115 case content::SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS:
116 return 1;
117 case content::SiteInstanceProcessAssignment::USED_SPARE_PROCESS:
118 return 2;
119 case content::SiteInstanceProcessAssignment::CREATED_NEW_PROCESS:
120 return 3;
121 }
122 return 0;
123 }
124
BucketWithOffsetAndUnit(int num,int offset,uint32_t unit)125 int BucketWithOffsetAndUnit(int num, int offset, uint32_t unit) {
126 // Bucketing raw number with `offset` centered.
127 const int grid = (num - offset) / unit;
128 const int bucketed =
129 grid == 0 ? 0
130 : grid > 0 ? std::pow(2, static_cast<int>(std::log2(grid)))
131 : -std::pow(2, static_cast<int>(std::log2(-grid)));
132 return bucketed * unit + offset;
133 }
134
135 } // namespace
136
137 // static
138 std::unique_ptr<page_load_metrics::PageLoadMetricsObserver>
CreateIfNeeded()139 UkmPageLoadMetricsObserver::CreateIfNeeded() {
140 if (!ukm::UkmRecorder::Get()) {
141 return nullptr;
142 }
143 return std::make_unique<UkmPageLoadMetricsObserver>(
144 g_browser_process->network_quality_tracker());
145 }
146
UkmPageLoadMetricsObserver(network::NetworkQualityTracker * network_quality_tracker)147 UkmPageLoadMetricsObserver::UkmPageLoadMetricsObserver(
148 network::NetworkQualityTracker* network_quality_tracker)
149 : network_quality_tracker_(network_quality_tracker) {
150 DCHECK(network_quality_tracker_);
151 }
152
153 UkmPageLoadMetricsObserver::~UkmPageLoadMetricsObserver() = default;
154
OnStart(content::NavigationHandle * navigation_handle,const GURL & currently_committed_url,bool started_in_foreground)155 UkmPageLoadMetricsObserver::ObservePolicy UkmPageLoadMetricsObserver::OnStart(
156 content::NavigationHandle* navigation_handle,
157 const GURL& currently_committed_url,
158 bool started_in_foreground) {
159 content::WebContents* web_contents = navigation_handle->GetWebContents();
160 is_portal_ = web_contents->IsPortal();
161
162 browser_context_ = web_contents->GetBrowserContext();
163
164 start_url_is_default_search_ =
165 IsDefaultSearchEngine(browser_context_, navigation_handle->GetURL());
166 start_url_is_home_page_ =
167 IsUserHomePage(browser_context_, navigation_handle->GetURL());
168
169 if (started_in_foreground) {
170 last_time_shown_ = navigation_handle->NavigationStart();
171 }
172 currently_in_foreground_ = started_in_foreground;
173
174 if (!started_in_foreground) {
175 was_hidden_ = true;
176 return CONTINUE_OBSERVING;
177 }
178
179 // When OnStart is invoked, we don't yet know whether we're observing a web
180 // page load, vs another kind of load (e.g. a download or a PDF). Thus,
181 // metrics and source information should not be recorded here. Instead, we
182 // store data we might want to persist in member variables below, and later
183 // record UKM metrics for that data once we've confirmed that we're observing
184 // a web page load.
185
186 effective_connection_type_ =
187 network_quality_tracker_->GetEffectiveConnectionType();
188 http_rtt_estimate_ = network_quality_tracker_->GetHttpRTT();
189 transport_rtt_estimate_ = network_quality_tracker_->GetTransportRTT();
190 downstream_kbps_estimate_ =
191 network_quality_tracker_->GetDownstreamThroughputKbps();
192 page_transition_ = navigation_handle->GetPageTransition();
193 return CONTINUE_OBSERVING;
194 }
195
196 page_load_metrics::PageLoadMetricsObserver::ObservePolicy
OnRedirect(content::NavigationHandle * navigation_handle)197 UkmPageLoadMetricsObserver::OnRedirect(
198 content::NavigationHandle* navigation_handle) {
199 main_frame_request_redirect_count_++;
200 return CONTINUE_OBSERVING;
201 }
202
203 UkmPageLoadMetricsObserver::ObservePolicy
ShouldObserveMimeType(const std::string & mime_type) const204 UkmPageLoadMetricsObserver::ShouldObserveMimeType(
205 const std::string& mime_type) const {
206 if (PageLoadMetricsObserver::ShouldObserveMimeType(mime_type) ==
207 CONTINUE_OBSERVING ||
208 mime_type == kOfflinePreviewsMimeType) {
209 return CONTINUE_OBSERVING;
210 }
211 return STOP_OBSERVING;
212 }
213
OnCommit(content::NavigationHandle * navigation_handle,ukm::SourceId source_id)214 UkmPageLoadMetricsObserver::ObservePolicy UkmPageLoadMetricsObserver::OnCommit(
215 content::NavigationHandle* navigation_handle,
216 ukm::SourceId source_id) {
217 if (navigation_handle->GetWebContents()->GetContentsMimeType() ==
218 kOfflinePreviewsMimeType) {
219 if (!IsOfflinePreview(navigation_handle->GetWebContents()))
220 return STOP_OBSERVING;
221 }
222 connection_info_ = navigation_handle->GetConnectionInfo();
223 const net::HttpResponseHeaders* response_headers =
224 navigation_handle->GetResponseHeaders();
225 if (response_headers)
226 http_response_code_ = response_headers->response_code();
227 // The PageTransition for the navigation may be updated on commit.
228 page_transition_ = navigation_handle->GetPageTransition();
229 was_cached_ = navigation_handle->WasResponseCached();
230 navigation_handle_timing_ = navigation_handle->GetNavigationHandleTiming();
231 prerender::PrerenderManager* const prerender_manager =
232 prerender::PrerenderManagerFactory::GetForBrowserContext(
233 navigation_handle->GetWebContents()->GetBrowserContext());
234 if (prerender_manager) {
235 prerender::RecordNoStatePrefetchMetrics(navigation_handle, source_id,
236 prerender_manager);
237 }
238 RecordGeneratedNavigationUKM(source_id, navigation_handle->GetURL());
239 navigation_is_cross_process_ = !navigation_handle->IsSameProcess();
240 navigation_entry_offset_ = navigation_handle->GetNavigationEntryOffset();
241 main_document_sequence_number_ = navigation_handle->GetWebContents()
242 ->GetController()
243 .GetLastCommittedEntry()
244 ->GetMainFrameDocumentSequenceNumber();
245
246 render_process_assignment_ = navigation_handle->GetWebContents()
247 ->GetMainFrame()
248 ->GetSiteInstance()
249 ->GetLastProcessAssignmentOutcome();
250
251 return CONTINUE_OBSERVING;
252 }
253
254 UkmPageLoadMetricsObserver::ObservePolicy
FlushMetricsOnAppEnterBackground(const page_load_metrics::mojom::PageLoadTiming & timing)255 UkmPageLoadMetricsObserver::FlushMetricsOnAppEnterBackground(
256 const page_load_metrics::mojom::PageLoadTiming& timing) {
257 if (is_portal_)
258 return STOP_OBSERVING;
259
260 base::TimeTicks current_time = base::TimeTicks::Now();
261 if (!was_hidden_) {
262 RecordNavigationTimingMetrics();
263 RecordPageLoadMetrics(current_time);
264 RecordRendererUsageMetrics();
265 RecordTimingMetrics(timing);
266 RecordInputTimingMetrics();
267 }
268 ReportLayoutStability();
269 RecordSmoothnessMetrics();
270 // Assume that page ends on this method, as the app could be evicted right
271 // after.
272 RecordPageEndMetrics(&timing, current_time);
273 return STOP_OBSERVING;
274 }
275
OnHidden(const page_load_metrics::mojom::PageLoadTiming & timing)276 UkmPageLoadMetricsObserver::ObservePolicy UkmPageLoadMetricsObserver::OnHidden(
277 const page_load_metrics::mojom::PageLoadTiming& timing) {
278 if (is_portal_)
279 return CONTINUE_OBSERVING;
280
281 if (currently_in_foreground_ && !last_time_shown_.is_null()) {
282 total_foreground_duration_ += base::TimeTicks::Now() - last_time_shown_;
283 }
284 currently_in_foreground_ = false;
285 if (!was_hidden_) {
286 RecordNavigationTimingMetrics();
287 RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */);
288 RecordRendererUsageMetrics();
289 RecordTimingMetrics(timing);
290 RecordInputTimingMetrics();
291 was_hidden_ = true;
292 }
293 return CONTINUE_OBSERVING;
294 }
295
296 UkmPageLoadMetricsObserver::ObservePolicy
OnShown()297 UkmPageLoadMetricsObserver::OnShown() {
298 if (is_portal_)
299 return CONTINUE_OBSERVING;
300
301 currently_in_foreground_ = true;
302 last_time_shown_ = base::TimeTicks::Now();
303 return CONTINUE_OBSERVING;
304 }
305
OnFailedProvisionalLoad(const page_load_metrics::FailedProvisionalLoadInfo & failed_load_info)306 void UkmPageLoadMetricsObserver::OnFailedProvisionalLoad(
307 const page_load_metrics::FailedProvisionalLoadInfo& failed_load_info) {
308 if (is_portal_)
309 return;
310
311 RecordPageEndMetrics(nullptr, base::TimeTicks());
312 if (was_hidden_)
313 return;
314
315 RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */);
316
317 RecordRendererUsageMetrics();
318
319 // Error codes have negative values, however we log net error code enum values
320 // for UMA histograms using the equivalent positive value. For consistency in
321 // UKM, we convert to a positive value here.
322 int64_t net_error_code = static_cast<int64_t>(failed_load_info.error) * -1;
323 DCHECK_GE(net_error_code, 0);
324 ukm::builders::PageLoad(GetDelegate().GetPageUkmSourceId())
325 .SetNet_ErrorCode_OnFailedProvisionalLoad(net_error_code)
326 .SetPageTiming_NavigationToFailedProvisionalLoad(
327 failed_load_info.time_to_failed_provisional_load.InMilliseconds())
328 .Record(ukm::UkmRecorder::Get());
329 }
330
OnComplete(const page_load_metrics::mojom::PageLoadTiming & timing)331 void UkmPageLoadMetricsObserver::OnComplete(
332 const page_load_metrics::mojom::PageLoadTiming& timing) {
333 if (is_portal_)
334 return;
335
336 base::TimeTicks current_time = base::TimeTicks::Now();
337 if (!was_hidden_) {
338 RecordNavigationTimingMetrics();
339 RecordPageLoadMetrics(current_time /* no app_background_time */);
340 RecordRendererUsageMetrics();
341 RecordTimingMetrics(timing);
342 RecordInputTimingMetrics();
343 }
344 ReportLayoutStability();
345 RecordSmoothnessMetrics();
346 ReportPerfectHeuristicsMetrics();
347 RecordPageEndMetrics(&timing, current_time);
348 RecordMobileFriendlinessMetrics();
349 }
350
OnResourceDataUseObserved(content::RenderFrameHost * content,const std::vector<page_load_metrics::mojom::ResourceDataUpdatePtr> & resources)351 void UkmPageLoadMetricsObserver::OnResourceDataUseObserved(
352 content::RenderFrameHost* content,
353 const std::vector<page_load_metrics::mojom::ResourceDataUpdatePtr>&
354 resources) {
355 if (was_hidden_)
356 return;
357 for (auto const& resource : resources) {
358 network_bytes_ += resource->delta_bytes;
359
360 if (blink::IsSupportedImageMimeType(resource->mime_type)) {
361 image_total_bytes_ += resource->delta_bytes;
362 if (!resource->is_main_frame_resource)
363 image_subframe_bytes_ += resource->delta_bytes;
364 } else if (media::IsSupportedMediaMimeType(resource->mime_type) ||
365 base::StartsWith(resource->mime_type, "audio/",
366 base::CompareCase::SENSITIVE) ||
367 base::StartsWith(resource->mime_type, "video/",
368 base::CompareCase::SENSITIVE)) {
369 media_bytes_ += resource->delta_bytes;
370 }
371
372 // Only sum body lengths for completed resources.
373 if (!resource->is_complete)
374 continue;
375 if (blink::IsSupportedJavascriptMimeType(resource->mime_type)) {
376 js_decoded_bytes_ += resource->decoded_body_length;
377 if (resource->decoded_body_length > js_max_decoded_bytes_)
378 js_max_decoded_bytes_ = resource->decoded_body_length;
379 }
380 if (resource->cache_type !=
381 page_load_metrics::mojom::CacheType::kNotCached) {
382 cache_bytes_ += resource->encoded_body_length;
383 }
384 }
385 }
386
OnLoadedResource(const page_load_metrics::ExtraRequestCompleteInfo & extra_request_complete_info)387 void UkmPageLoadMetricsObserver::OnLoadedResource(
388 const page_load_metrics::ExtraRequestCompleteInfo&
389 extra_request_complete_info) {
390 if (was_hidden_)
391 return;
392 if (extra_request_complete_info.request_destination ==
393 network::mojom::RequestDestination::kDocument) {
394 DCHECK(!main_frame_timing_.has_value());
395 main_frame_timing_ = *extra_request_complete_info.load_timing_info;
396 }
397 }
398
RecordNavigationTimingMetrics()399 void UkmPageLoadMetricsObserver::RecordNavigationTimingMetrics() {
400 const base::TimeTicks navigation_start_time =
401 GetDelegate().GetNavigationStart();
402 const content::NavigationHandleTiming& timing = navigation_handle_timing_;
403
404 // Record metrics for navigation only when all relevant milestones are
405 // recorded and in the expected order. It is allowed that they have the same
406 // value for some cases (e.g., internal redirection for HSTS).
407 if (navigation_start_time.is_null() ||
408 timing.first_request_start_time.is_null() ||
409 timing.first_response_start_time.is_null() ||
410 timing.first_loader_callback_time.is_null() ||
411 timing.final_request_start_time.is_null() ||
412 timing.final_response_start_time.is_null() ||
413 timing.final_loader_callback_time.is_null() ||
414 timing.navigation_commit_sent_time.is_null()) {
415 return;
416 }
417 // TODO(https://crbug.com/1076710): Change these early-returns to DCHECKs
418 // after the issue 1076710 is fixed.
419 if (navigation_start_time > timing.first_request_start_time ||
420 timing.first_request_start_time > timing.first_response_start_time ||
421 timing.first_response_start_time > timing.first_loader_callback_time ||
422 timing.first_loader_callback_time > timing.navigation_commit_sent_time) {
423 return;
424 }
425 if (navigation_start_time > timing.final_request_start_time ||
426 timing.final_request_start_time > timing.final_response_start_time ||
427 timing.final_response_start_time > timing.final_loader_callback_time ||
428 timing.final_loader_callback_time > timing.navigation_commit_sent_time) {
429 return;
430 }
431 DCHECK_LE(timing.first_request_start_time, timing.final_request_start_time);
432 DCHECK_LE(timing.first_response_start_time, timing.final_response_start_time);
433 DCHECK_LE(timing.first_loader_callback_time,
434 timing.final_loader_callback_time);
435
436 ukm::builders::NavigationTiming builder(GetDelegate().GetPageUkmSourceId());
437
438 // Record the elapsed time from the navigation start milestone.
439 builder
440 .SetFirstRequestStart(
441 (timing.first_request_start_time - navigation_start_time)
442 .InMilliseconds())
443 .SetFirstResponseStart(
444 (timing.first_response_start_time - navigation_start_time)
445 .InMilliseconds())
446 .SetFirstLoaderCallback(
447 (timing.first_loader_callback_time - navigation_start_time)
448 .InMilliseconds())
449 .SetFinalRequestStart(
450 (timing.final_request_start_time - navigation_start_time)
451 .InMilliseconds())
452 .SetFinalResponseStart(
453 (timing.final_response_start_time - navigation_start_time)
454 .InMilliseconds())
455 .SetFinalLoaderCallback(
456 (timing.final_loader_callback_time - navigation_start_time)
457 .InMilliseconds())
458 .SetNavigationCommitSent(
459 (timing.navigation_commit_sent_time - navigation_start_time)
460 .InMilliseconds());
461
462 // Record the elapsed time from the navigation start milestone for the 103
463 // Early Hints experiment (https://crbug.com/1093693). Note that multiple 103
464 // responses can be served per request. These metrics use the first 103
465 // response as the timing.
466 if (!timing.early_hints_for_first_request_time.is_null()) {
467 builder.SetEarlyHintsForFirstRequest(
468 (timing.early_hints_for_first_request_time - navigation_start_time)
469 .InMilliseconds());
470 }
471 if (!timing.early_hints_for_final_request_time.is_null()) {
472 builder.SetEarlyHintsForFinalRequest(
473 (timing.early_hints_for_final_request_time - navigation_start_time)
474 .InMilliseconds());
475 }
476
477 builder.Record(ukm::UkmRecorder::Get());
478 }
479
RecordTimingMetrics(const page_load_metrics::mojom::PageLoadTiming & timing)480 void UkmPageLoadMetricsObserver::RecordTimingMetrics(
481 const page_load_metrics::mojom::PageLoadTiming& timing) {
482 ukm::builders::PageLoad builder(GetDelegate().GetPageUkmSourceId());
483
484 base::Optional<int64_t> rounded_site_engagement_score =
485 GetRoundedSiteEngagementScore();
486 if (rounded_site_engagement_score) {
487 builder.SetSiteEngagementScore(rounded_site_engagement_score.value());
488 }
489
490 base::Optional<bool> third_party_cookie_blocking_enabled =
491 GetThirdPartyCookieBlockingEnabled();
492 if (third_party_cookie_blocking_enabled) {
493 builder.SetThirdPartyCookieBlockingEnabledForSite(
494 third_party_cookie_blocking_enabled.value());
495 UMA_HISTOGRAM_BOOLEAN("Privacy.ThirdPartyCookieBlockingEnabledForSite",
496 third_party_cookie_blocking_enabled.value());
497 }
498
499 if (timing.input_to_navigation_start) {
500 builder.SetExperimental_InputToNavigationStart(
501 timing.input_to_navigation_start.value().InMilliseconds());
502 }
503 if (timing.parse_timing->parse_start) {
504 builder.SetParseTiming_NavigationToParseStart(
505 timing.parse_timing->parse_start.value().InMilliseconds());
506 }
507 if (timing.document_timing->dom_content_loaded_event_start) {
508 builder.SetDocumentTiming_NavigationToDOMContentLoadedEventFired(
509 timing.document_timing->dom_content_loaded_event_start.value()
510 .InMilliseconds());
511 }
512 if (timing.document_timing->load_event_start) {
513 builder.SetDocumentTiming_NavigationToLoadEventFired(
514 timing.document_timing->load_event_start.value().InMilliseconds());
515 }
516 if (timing.paint_timing->first_paint) {
517 builder.SetPaintTiming_NavigationToFirstPaint(
518 timing.paint_timing->first_paint.value().InMilliseconds());
519 }
520 if (timing.paint_timing->first_contentful_paint) {
521 builder.SetPaintTiming_NavigationToFirstContentfulPaint(
522 timing.paint_timing->first_contentful_paint.value().InMilliseconds());
523 }
524 if (timing.paint_timing->first_meaningful_paint) {
525 builder.SetExperimental_PaintTiming_NavigationToFirstMeaningfulPaint(
526 timing.paint_timing->first_meaningful_paint.value().InMilliseconds());
527 }
528 const page_load_metrics::ContentfulPaintTimingInfo&
529 main_frame_largest_contentful_paint =
530 GetDelegate()
531 .GetLargestContentfulPaintHandler()
532 .MainFrameLargestContentfulPaint();
533 if (main_frame_largest_contentful_paint.ContainsValidTime() &&
534 WasStartedInForegroundOptionalEventInForeground(
535 main_frame_largest_contentful_paint.Time(), GetDelegate())) {
536 builder.SetPaintTiming_NavigationToLargestContentfulPaint2_MainFrame(
537 main_frame_largest_contentful_paint.Time().value().InMilliseconds());
538 }
539 const page_load_metrics::ContentfulPaintTimingInfo&
540 all_frames_largest_contentful_paint =
541 GetDelegate()
542 .GetLargestContentfulPaintHandler()
543 .MergeMainFrameAndSubframes();
544 if (all_frames_largest_contentful_paint.ContainsValidTime() &&
545 WasStartedInForegroundOptionalEventInForeground(
546 all_frames_largest_contentful_paint.Time(), GetDelegate())) {
547 builder.SetPaintTiming_NavigationToLargestContentfulPaint2(
548 all_frames_largest_contentful_paint.Time().value().InMilliseconds());
549 }
550 // TODO(crbug.com/1045640): Stop reporting the experimental obsolete versions.
551 const page_load_metrics::ContentfulPaintTimingInfo&
552 main_frame_experimental_largest_contentful_paint =
553 GetDelegate()
554 .GetExperimentalLargestContentfulPaintHandler()
555 .MainFrameLargestContentfulPaint();
556 if (main_frame_experimental_largest_contentful_paint.ContainsValidTime() &&
557 WasStartedInForegroundOptionalEventInForeground(
558 main_frame_experimental_largest_contentful_paint.Time(),
559 GetDelegate())) {
560 builder.SetPaintTiming_NavigationToLargestContentfulPaint_MainFrame(
561 main_frame_experimental_largest_contentful_paint.Time()
562 .value()
563 .InMilliseconds());
564 }
565 const page_load_metrics::ContentfulPaintTimingInfo&
566 all_frames_experimental_largest_contentful_paint =
567 GetDelegate()
568 .GetExperimentalLargestContentfulPaintHandler()
569 .MergeMainFrameAndSubframes();
570 if (all_frames_experimental_largest_contentful_paint.ContainsValidTime() &&
571 WasStartedInForegroundOptionalEventInForeground(
572 all_frames_experimental_largest_contentful_paint.Time(),
573 GetDelegate())) {
574 builder.SetPaintTiming_NavigationToLargestContentfulPaint(
575 all_frames_experimental_largest_contentful_paint.Time()
576 .value()
577 .InMilliseconds());
578 }
579 RecordInternalTimingMetrics(all_frames_largest_contentful_paint,
580 all_frames_experimental_largest_contentful_paint);
581 if (timing.interactive_timing->first_input_delay) {
582 base::TimeDelta first_input_delay =
583 timing.interactive_timing->first_input_delay.value();
584 builder.SetInteractiveTiming_FirstInputDelay4(
585 first_input_delay.InMilliseconds());
586 }
587 if (timing.interactive_timing->first_input_timestamp) {
588 base::TimeDelta first_input_timestamp =
589 timing.interactive_timing->first_input_timestamp.value();
590 builder.SetInteractiveTiming_FirstInputTimestamp4(
591 first_input_timestamp.InMilliseconds());
592 }
593
594 if (timing.interactive_timing->longest_input_delay) {
595 base::TimeDelta longest_input_delay =
596 timing.interactive_timing->longest_input_delay.value();
597 builder.SetInteractiveTiming_LongestInputDelay4(
598 longest_input_delay.InMilliseconds());
599 }
600 if (timing.interactive_timing->longest_input_timestamp) {
601 base::TimeDelta longest_input_timestamp =
602 timing.interactive_timing->longest_input_timestamp.value();
603 builder.SetInteractiveTiming_LongestInputTimestamp4(
604 longest_input_timestamp.InMilliseconds());
605 }
606 if (timing.interactive_timing->first_scroll_delay) {
607 base::TimeDelta first_scroll_delay =
608 timing.interactive_timing->first_scroll_delay.value();
609 builder.SetInteractiveTiming_FirstScrollDelay(
610 first_scroll_delay.InMilliseconds());
611 }
612 if (timing.interactive_timing->first_input_processing_time) {
613 base::TimeDelta first_input_processing_time =
614 timing.interactive_timing->first_input_processing_time.value();
615 builder.SetInteractiveTiming_FirstInputProcessingTimes(
616 first_input_processing_time.InMilliseconds());
617 }
618 builder.SetCpuTime(total_foreground_cpu_time_.InMilliseconds());
619
620 // Use a bucket spacing factor of 1.3 for bytes.
621 builder.SetNet_CacheBytes2(ukm::GetExponentialBucketMin(cache_bytes_, 1.3));
622 builder.SetNet_NetworkBytes2(
623 ukm::GetExponentialBucketMin(network_bytes_, 1.3));
624
625 // Use a bucket spacing factor of 10 for JS bytes.
626 builder.SetNet_JavaScriptBytes(
627 ukm::GetExponentialBucketMin(js_decoded_bytes_, 10));
628 builder.SetNet_JavaScriptMaxBytes(
629 ukm::GetExponentialBucketMin(js_max_decoded_bytes_, 10));
630
631 builder.SetNet_ImageBytes(
632 ukm::GetExponentialBucketMin(image_total_bytes_, 1.15));
633 builder.SetNet_ImageSubframeBytes(
634 ukm::GetExponentialBucketMin(image_subframe_bytes_, 1.15));
635 builder.SetNet_MediaBytes(ukm::GetExponentialBucketMin(media_bytes_, 1.15));
636
637 if (main_frame_timing_)
638 ReportMainResourceTimingMetrics(timing, &builder);
639
640 builder.Record(ukm::UkmRecorder::Get());
641 }
642
RecordInternalTimingMetrics(const page_load_metrics::ContentfulPaintTimingInfo & all_frames_largest_contentful_paint,const page_load_metrics::ContentfulPaintTimingInfo & all_frames_experimental_largest_contentful_paint)643 void UkmPageLoadMetricsObserver::RecordInternalTimingMetrics(
644 const page_load_metrics::ContentfulPaintTimingInfo&
645 all_frames_largest_contentful_paint,
646 const page_load_metrics::ContentfulPaintTimingInfo&
647 all_frames_experimental_largest_contentful_paint) {
648 ukm::builders::PageLoad_Internal debug_builder(
649 GetDelegate().GetPageUkmSourceId());
650 LargestContentState lcp_state = LargestContentState::kNotFound;
651 if (all_frames_largest_contentful_paint.ContainsValidTime()) {
652 if (WasStartedInForegroundOptionalEventInForeground(
653 all_frames_largest_contentful_paint.Time(), GetDelegate())) {
654 debug_builder.SetPaintTiming_LargestContentfulPaint_ContentType(
655 static_cast<int>(all_frames_largest_contentful_paint.Type()));
656 lcp_state = LargestContentState::kReported;
657 } else {
658 // TODO(npm): figure out why this code can be reached given that
659 // RecordTimingMetrics() is only called when was_hidden_ is set to false.
660 lcp_state = LargestContentState::kFoundButNotReported;
661 }
662 } else if (all_frames_largest_contentful_paint.Time().has_value()) {
663 DCHECK(all_frames_largest_contentful_paint.Size());
664 lcp_state = LargestContentState::kLargestImageLoading;
665 } else {
666 DCHECK(all_frames_largest_contentful_paint.Empty());
667 lcp_state = LargestContentState::kNotFound;
668 }
669 debug_builder.SetPaintTiming_LargestContentfulPaint_TerminationState(
670 static_cast<int>(lcp_state));
671
672 LargestContentState experimental_lcp_state = LargestContentState::kNotFound;
673 if (all_frames_experimental_largest_contentful_paint.ContainsValidTime()) {
674 if (WasStartedInForegroundOptionalEventInForeground(
675 all_frames_experimental_largest_contentful_paint.Time(),
676 GetDelegate())) {
677 debug_builder
678 .SetPaintTiming_ExperimentalLargestContentfulPaint_ContentType(
679 static_cast<int>(
680 all_frames_experimental_largest_contentful_paint.Type()));
681 experimental_lcp_state = LargestContentState::kReported;
682 } else {
683 // TODO(npm): figure out why this code can be reached given that
684 // RecordTimingMetrics() is only called when was_hidden_ is set to false.
685 experimental_lcp_state = LargestContentState::kFoundButNotReported;
686 }
687 } else if (all_frames_experimental_largest_contentful_paint.Time()
688 .has_value()) {
689 DCHECK(all_frames_experimental_largest_contentful_paint.Size());
690 experimental_lcp_state = LargestContentState::kLargestImageLoading;
691 } else {
692 DCHECK(all_frames_experimental_largest_contentful_paint.Empty());
693 experimental_lcp_state = LargestContentState::kNotFound;
694 }
695 debug_builder
696 .SetPaintTiming_ExperimentalLargestContentfulPaint_TerminationState(
697 static_cast<int>(lcp_state));
698 debug_builder.Record(ukm::UkmRecorder::Get());
699 }
700
RecordPageLoadMetrics(base::TimeTicks app_background_time)701 void UkmPageLoadMetricsObserver::RecordPageLoadMetrics(
702 base::TimeTicks app_background_time) {
703 ukm::builders::PageLoad builder(GetDelegate().GetPageUkmSourceId());
704 base::Optional<base::TimeDelta> foreground_duration =
705 page_load_metrics::GetInitialForegroundDuration(GetDelegate(),
706 app_background_time);
707 if (foreground_duration) {
708 builder.SetPageTiming_ForegroundDuration(
709 foreground_duration.value().InMilliseconds());
710 }
711
712 builder.SetSiteInstanceRenderProcessAssignment(
713 SiteInstanceRenderProcessAssignmentToInt(render_process_assignment_));
714
715 // Convert to the EffectiveConnectionType as used in SystemProfileProto
716 // before persisting the metric.
717 metrics::SystemProfileProto::Network::EffectiveConnectionType
718 proto_effective_connection_type =
719 metrics::ConvertEffectiveConnectionType(effective_connection_type_);
720 if (proto_effective_connection_type !=
721 metrics::SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {
722 builder.SetNet_EffectiveConnectionType2_OnNavigationStart(
723 static_cast<int64_t>(proto_effective_connection_type));
724 }
725
726 if (http_response_code_) {
727 builder.SetNet_HttpResponseCode(
728 static_cast<int64_t>(http_response_code_.value()));
729 }
730 if (http_rtt_estimate_) {
731 builder.SetNet_HttpRttEstimate_OnNavigationStart(
732 static_cast<int64_t>(http_rtt_estimate_.value().InMilliseconds()));
733 }
734 if (transport_rtt_estimate_) {
735 builder.SetNet_TransportRttEstimate_OnNavigationStart(
736 static_cast<int64_t>(transport_rtt_estimate_.value().InMilliseconds()));
737 }
738 if (downstream_kbps_estimate_) {
739 builder.SetNet_DownstreamKbpsEstimate_OnNavigationStart(
740 static_cast<int64_t>(downstream_kbps_estimate_.value()));
741 }
742 if (GetDelegate().DidCommit() && was_cached_) {
743 builder.SetWasCached(1);
744 }
745 if (GetDelegate().DidCommit() && navigation_is_cross_process_) {
746 builder.SetIsCrossProcessNavigation(navigation_is_cross_process_);
747 }
748 if (GetDelegate().DidCommit()) {
749 builder.SetNavigationEntryOffset(navigation_entry_offset_);
750 builder.SetMainDocumentSequenceNumber(main_document_sequence_number_);
751 }
752 builder.Record(ukm::UkmRecorder::Get());
753 }
754
RecordRendererUsageMetrics()755 void UkmPageLoadMetricsObserver::RecordRendererUsageMetrics() {
756 ukm::builders::PageLoad builder(GetDelegate().GetPageUkmSourceId());
757
758 builder.SetSiteInstanceRenderProcessAssignment(
759 SiteInstanceRenderProcessAssignmentToInt(render_process_assignment_));
760
761 builder.Record(ukm::UkmRecorder::Get());
762 }
763
ReportMainResourceTimingMetrics(const page_load_metrics::mojom::PageLoadTiming & timing,ukm::builders::PageLoad * builder)764 void UkmPageLoadMetricsObserver::ReportMainResourceTimingMetrics(
765 const page_load_metrics::mojom::PageLoadTiming& timing,
766 ukm::builders::PageLoad* builder) {
767 DCHECK(main_frame_timing_.has_value());
768
769 builder->SetMainFrameResource_SocketReused(main_frame_timing_->socket_reused);
770
771 int64_t dns_start_ms =
772 main_frame_timing_->connect_timing.dns_start.since_origin()
773 .InMilliseconds();
774 int64_t dns_end_ms = main_frame_timing_->connect_timing.dns_end.since_origin()
775 .InMilliseconds();
776 int64_t connect_start_ms =
777 main_frame_timing_->connect_timing.connect_start.since_origin()
778 .InMilliseconds();
779 int64_t connect_end_ms =
780 main_frame_timing_->connect_timing.connect_end.since_origin()
781 .InMilliseconds();
782 int64_t request_start_ms =
783 main_frame_timing_->request_start.since_origin().InMilliseconds();
784 int64_t send_start_ms =
785 main_frame_timing_->send_start.since_origin().InMilliseconds();
786 int64_t receive_headers_end_ms =
787 main_frame_timing_->receive_headers_end.since_origin().InMilliseconds();
788
789 DCHECK_LE(dns_start_ms, dns_end_ms);
790 DCHECK_LE(dns_end_ms, connect_start_ms);
791 DCHECK_LE(dns_start_ms, connect_start_ms);
792 DCHECK_LE(connect_start_ms, connect_end_ms);
793
794 int64_t dns_duration_ms = dns_end_ms - dns_start_ms;
795 int64_t connect_duration_ms = connect_end_ms - connect_start_ms;
796 int64_t request_start_to_send_start_ms = send_start_ms - request_start_ms;
797 int64_t send_start_to_receive_headers_end_ms =
798 receive_headers_end_ms - send_start_ms;
799 int64_t request_start_to_receive_headers_end_ms =
800 receive_headers_end_ms - request_start_ms;
801
802 builder->SetMainFrameResource_DNSDelay(dns_duration_ms);
803 builder->SetMainFrameResource_ConnectDelay(connect_duration_ms);
804 if (request_start_to_send_start_ms >= 0) {
805 builder->SetMainFrameResource_RequestStartToSendStart(
806 request_start_to_send_start_ms);
807 }
808 if (send_start_to_receive_headers_end_ms >= 0) {
809 builder->SetMainFrameResource_SendStartToReceiveHeadersEnd(
810 send_start_to_receive_headers_end_ms);
811 }
812 builder->SetMainFrameResource_RequestStartToReceiveHeadersEnd(
813 request_start_to_receive_headers_end_ms);
814
815 if (!main_frame_timing_->request_start.is_null() &&
816 !GetDelegate().GetNavigationStart().is_null()) {
817 base::TimeDelta navigation_start_to_request_start =
818 main_frame_timing_->request_start - GetDelegate().GetNavigationStart();
819
820 builder->SetMainFrameResource_NavigationStartToRequestStart(
821 navigation_start_to_request_start.InMilliseconds());
822 }
823
824 if (!main_frame_timing_->receive_headers_start.is_null() &&
825 !GetDelegate().GetNavigationStart().is_null()) {
826 base::TimeDelta navigation_start_to_receive_headers_start =
827 main_frame_timing_->receive_headers_start -
828 GetDelegate().GetNavigationStart();
829 builder->SetMainFrameResource_NavigationStartToReceiveHeadersStart(
830 navigation_start_to_receive_headers_start.InMilliseconds());
831 }
832
833 if (connection_info_.has_value()) {
834 page_load_metrics::NetworkProtocol protocol =
835 page_load_metrics::GetNetworkProtocol(*connection_info_);
836 if (IsSupportedProtocol(protocol)) {
837 builder->SetMainFrameResource_HttpProtocolScheme(
838 static_cast<int>(protocol));
839 }
840 }
841
842 if (main_frame_request_redirect_count_ > 0) {
843 builder->SetMainFrameResource_RedirectCount(
844 main_frame_request_redirect_count_);
845 }
846 }
847
ReportLayoutStability()848 void UkmPageLoadMetricsObserver::ReportLayoutStability() {
849 ukm::builders::PageLoad builder(GetDelegate().GetPageUkmSourceId());
850 builder
851 .SetLayoutInstability_CumulativeShiftScore(
852 page_load_metrics::LayoutShiftUkmValue(
853 GetDelegate().GetPageRenderData().layout_shift_score))
854 .SetLayoutInstability_CumulativeShiftScore_BeforeInputOrScroll(
855 page_load_metrics::LayoutShiftUkmValue(
856 GetDelegate()
857 .GetPageRenderData()
858 .layout_shift_score_before_input_or_scroll))
859 .SetLayoutInstability_CumulativeShiftScore_MainFrame(
860 page_load_metrics::LayoutShiftUkmValue(
861 GetDelegate().GetMainFrameRenderData().layout_shift_score))
862 .SetLayoutInstability_CumulativeShiftScore_MainFrame_BeforeInputOrScroll(
863 page_load_metrics::LayoutShiftUkmValue(
864 GetDelegate()
865 .GetMainFrameRenderData()
866 .layout_shift_score_before_input_or_scroll));
867 // Record CLS normalization UKM.
868 if (base::FeatureList::IsEnabled(kLayoutShiftNormalizationRecordUKM) &&
869 !GetDelegate().GetNormalizedCLSData().data_tainted) {
870 builder
871 .SetLayoutInstability_MaxCumulativeShiftScore_SessionWindow_Gap1000ms(
872 page_load_metrics::LayoutShiftUkmValue(
873 GetDelegate()
874 .GetNormalizedCLSData()
875 .session_windows_gap1000ms_maxMax_max_cls))
876 .SetLayoutInstability_MaxCumulativeShiftScore_SessionWindow_Gap1000ms_Max5000ms(
877 page_load_metrics::LayoutShiftUkmValue(
878 GetDelegate()
879 .GetNormalizedCLSData()
880 .session_windows_gap1000ms_max5000ms_max_cls))
881 .SetLayoutInstability_MaxCumulativeShiftScore_SlidingWindow_Duration1000ms(
882 page_load_metrics::LayoutShiftUkmValue(
883 GetDelegate()
884 .GetNormalizedCLSData()
885 .sliding_windows_duration1000ms_max_cls))
886 .SetLayoutInstability_MaxCumulativeShiftScore_SlidingWindow_Duration300ms(
887 page_load_metrics::LayoutShiftUkmValue(
888 GetDelegate()
889 .GetNormalizedCLSData()
890 .sliding_windows_duration300ms_max_cls))
891 .SetLayoutInstability_AverageCumulativeShiftScore_SessionWindow_Gap5000ms(
892 page_load_metrics::LayoutShiftUkmValue(
893 GetDelegate()
894 .GetNormalizedCLSData()
895 .session_windows_gap5000ms_maxMax_average_cls));
896 }
897 builder.Record(ukm::UkmRecorder::Get());
898
899 // TODO(crbug.com/1064483): We should move UMA recording to components/
900
901 UMA_HISTOGRAM_COUNTS_100(
902 "PageLoad.LayoutInstability.CumulativeShiftScore",
903 page_load_metrics::LayoutShiftUmaValue(
904 GetDelegate().GetPageRenderData().layout_shift_score));
905
906 TRACE_EVENT_INSTANT1("loading", "CumulativeShiftScore::AllFrames::UMA",
907 TRACE_EVENT_SCOPE_THREAD, "data",
908 CumulativeShiftScoreTraceData(
909 GetDelegate().GetPageRenderData().layout_shift_score,
910 GetDelegate()
911 .GetPageRenderData()
912 .layout_shift_score_before_input_or_scroll));
913
914 UMA_HISTOGRAM_COUNTS_100(
915 "PageLoad.LayoutInstability.CumulativeShiftScore.MainFrame",
916 page_load_metrics::LayoutShiftUmaValue(
917 GetDelegate().GetMainFrameRenderData().layout_shift_score));
918 }
919
ReportPerfectHeuristicsMetrics()920 void UkmPageLoadMetricsObserver::ReportPerfectHeuristicsMetrics() {
921 if (!delay_async_script_execution_before_finished_parsing_seen_ &&
922 !delay_competing_low_priority_requests_seen_) {
923 return;
924 }
925
926 ukm::builders::PerfectHeuristics builder(GetDelegate().GetPageUkmSourceId());
927 if (delay_async_script_execution_before_finished_parsing_seen_)
928 builder.Setdelay_async_script_execution_before_finished_parsing(1);
929 if (delay_competing_low_priority_requests_seen_)
930 builder.SetDelayCompetingLowPriorityRequests(1);
931 builder.Record(ukm::UkmRecorder::Get());
932 }
933
RecordAbortMetrics(const page_load_metrics::mojom::PageLoadTiming & timing,base::TimeTicks page_end_time,ukm::builders::PageLoad * builder)934 void UkmPageLoadMetricsObserver::RecordAbortMetrics(
935 const page_load_metrics::mojom::PageLoadTiming& timing,
936 base::TimeTicks page_end_time,
937 ukm::builders::PageLoad* builder) {
938 PageLoadType page_load_type = PageLoadType::kNeverForegrounded;
939 if (page_load_metrics::WasInForeground(GetDelegate())) {
940 page_load_type = timing.paint_timing->first_contentful_paint.has_value()
941 ? PageLoadType::kReachedFCP
942 : PageLoadType::kAborted;
943 }
944 if (currently_in_foreground_ && !last_time_shown_.is_null()) {
945 total_foreground_duration_ += page_end_time - last_time_shown_;
946 }
947 UMA_HISTOGRAM_ENUMERATION("PageLoad.Experimental.PageLoadType",
948 page_load_type);
949 PAGE_LOAD_LONG_HISTOGRAM("PageLoad.Experimental.TotalForegroundDuration",
950 total_foreground_duration_);
951
952 builder->SetExperimental_PageLoadType(static_cast<int>(page_load_type))
953 .SetExperimental_TotalForegroundDuration(
954 ukm::GetExponentialBucketMinForUserTiming(
955 total_foreground_duration_.InMilliseconds()));
956 }
957
RecordInputTimingMetrics()958 void UkmPageLoadMetricsObserver::RecordInputTimingMetrics() {
959 ukm::builders::PageLoad(GetDelegate().GetPageUkmSourceId())
960 .SetInteractiveTiming_NumInputEvents(
961 GetDelegate().GetPageInputTiming().num_input_events)
962 .SetInteractiveTiming_TotalInputDelay(
963 GetDelegate().GetPageInputTiming().total_input_delay.InMilliseconds())
964 .SetInteractiveTiming_TotalAdjustedInputDelay(
965 GetDelegate()
966 .GetPageInputTiming()
967 .total_adjusted_input_delay.InMilliseconds())
968 .Record(ukm::UkmRecorder::Get());
969 }
970
RecordSmoothnessMetrics()971 void UkmPageLoadMetricsObserver::RecordSmoothnessMetrics() {
972 auto* smoothness =
973 ukm_smoothness_data_.GetMemoryAs<cc::UkmSmoothnessDataShared>();
974 if (!smoothness) {
975 return;
976 }
977
978 base::ElapsedTimer timer;
979 const uint32_t kMaxRetries = 5;
980 uint32_t retries = 0;
981 cc::UkmSmoothnessData smoothness_data;
982 base::subtle::Atomic32 version;
983 do {
984 const uint32_t kMaxReadAttempts = 32;
985 version = smoothness->seq_lock.ReadBegin(kMaxReadAttempts);
986 device::OneWriterSeqLock::AtomicReaderMemcpy(
987 &smoothness_data, &smoothness->data, sizeof(cc::UkmSmoothnessData));
988 } while (smoothness->seq_lock.ReadRetry(version) && ++retries < kMaxRetries);
989
990 UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
991 "Graphics.Smoothness.Diagnostic.ReadSharedMemoryDuration",
992 timer.Elapsed(), base::TimeDelta::FromMicroseconds(1),
993 base::TimeDelta::FromMilliseconds(5), 100);
994 UMA_HISTOGRAM_BOOLEAN(
995 "Graphics.Smoothness.Diagnostic.ReadSharedMemoryUKMSuccess",
996 retries < kMaxRetries);
997
998 if (retries >= kMaxRetries)
999 return;
1000 ukm::builders::Graphics_Smoothness_NormalizedPercentDroppedFrames(
1001 GetDelegate().GetPageUkmSourceId())
1002 .SetAverage(smoothness_data.avg_smoothness)
1003 .SetPercentile95(smoothness_data.percentile_95)
1004 .SetAboveThreshold(smoothness_data.above_threshold)
1005 .SetWorstCase(smoothness_data.worst_smoothness)
1006 .Record(ukm::UkmRecorder::Get());
1007 }
1008
RecordMobileFriendlinessMetrics()1009 void UkmPageLoadMetricsObserver::RecordMobileFriendlinessMetrics() {
1010 ukm::builders::MobileFriendliness mf(GetDelegate().GetPageUkmSourceId());
1011 mf.SetViewportDeviceWidth(
1012 GetDelegate().GetMobileFriendliness().viewport_device_width)
1013 .SetAllowUserZoom(GetDelegate().GetMobileFriendliness().allow_user_zoom);
1014 const int initial_scale_x10 = std::floor(
1015 GetDelegate().GetMobileFriendliness().viewport_initial_scale * 10);
1016 if (initial_scale_x10 > 0) {
1017 mf.SetViewportInitialScaleX10(
1018 ukm::GetExponentialBucketMin(initial_scale_x10, 1.2));
1019 }
1020
1021 const int hardcoded_width =
1022 GetDelegate().GetMobileFriendliness().viewport_hardcoded_width;
1023 if (hardcoded_width > 0) {
1024 mf.SetViewportHardcodedWidth(
1025 BucketWithOffsetAndUnit(hardcoded_width, 500, 10));
1026 }
1027 mf.Record(ukm::UkmRecorder::Get());
1028 }
1029
RecordPageEndMetrics(const page_load_metrics::mojom::PageLoadTiming * timing,base::TimeTicks page_end_time)1030 void UkmPageLoadMetricsObserver::RecordPageEndMetrics(
1031 const page_load_metrics::mojom::PageLoadTiming* timing,
1032 base::TimeTicks page_end_time) {
1033 ukm::builders::PageLoad builder(GetDelegate().GetPageUkmSourceId());
1034 // page_transition_ fits in a uint32_t, so we can safely cast to int64_t.
1035 builder.SetNavigation_PageTransition(static_cast<int64_t>(page_transition_));
1036
1037 // GetDelegate().GetPageEndReason() fits in a uint32_t, so we can safely cast
1038 // to int64_t.
1039 int64_t page_end_reason = GetDelegate().GetPageEndReason();
1040 if (page_end_reason == page_load_metrics::PageEndReason::END_NONE &&
1041 was_hidden_) {
1042 page_end_reason = page_load_metrics::PageEndReason::END_HIDDEN;
1043 }
1044 builder.SetNavigation_PageEndReason2(page_end_reason);
1045 bool is_user_initiated_navigation =
1046 // All browser initiated page loads are user-initiated.
1047 GetDelegate().GetUserInitiatedInfo().browser_initiated ||
1048 // Renderer-initiated navigations are user-initiated if there is an
1049 // associated input event.
1050 GetDelegate().GetUserInitiatedInfo().user_input_event;
1051 builder.SetExperimental_Navigation_UserInitiated(
1052 is_user_initiated_navigation);
1053 if (timing)
1054 RecordAbortMetrics(*timing, page_end_time, &builder);
1055
1056 builder.Record(ukm::UkmRecorder::Get());
1057 }
1058
1059 base::Optional<int64_t>
GetRoundedSiteEngagementScore() const1060 UkmPageLoadMetricsObserver::GetRoundedSiteEngagementScore() const {
1061 if (!browser_context_)
1062 return base::nullopt;
1063
1064 Profile* profile = Profile::FromBrowserContext(browser_context_);
1065 SiteEngagementService* engagement_service =
1066 SiteEngagementService::Get(profile);
1067
1068 // UKM privacy requires the engagement score be rounded to nearest
1069 // value of 10.
1070 int64_t rounded_document_engagement_score =
1071 static_cast<int>(std::roundf(
1072 engagement_service->GetScore(GetDelegate().GetUrl()) / 10.0)) *
1073 10;
1074
1075 DCHECK(rounded_document_engagement_score >= 0 &&
1076 rounded_document_engagement_score <=
1077 engagement_service->GetMaxPoints());
1078
1079 return rounded_document_engagement_score;
1080 }
1081
1082 base::Optional<bool>
GetThirdPartyCookieBlockingEnabled() const1083 UkmPageLoadMetricsObserver::GetThirdPartyCookieBlockingEnabled() const {
1084 if (!browser_context_)
1085 return base::nullopt;
1086
1087 Profile* profile = Profile::FromBrowserContext(browser_context_);
1088 auto cookie_settings = CookieSettingsFactory::GetForProfile(profile);
1089 if (!cookie_settings->ShouldBlockThirdPartyCookies())
1090 return base::nullopt;
1091
1092 return !cookie_settings->IsThirdPartyAccessAllowed(GetDelegate().GetUrl(),
1093 nullptr /* source */);
1094 }
1095
OnTimingUpdate(content::RenderFrameHost * subframe_rfh,const page_load_metrics::mojom::PageLoadTiming & timing)1096 void UkmPageLoadMetricsObserver::OnTimingUpdate(
1097 content::RenderFrameHost* subframe_rfh,
1098 const page_load_metrics::mojom::PageLoadTiming& timing) {
1099 bool loading_enabled;
1100 TRACE_EVENT_CATEGORY_GROUP_ENABLED("loading", &loading_enabled);
1101 if (!loading_enabled)
1102 return;
1103 const page_load_metrics::ContentfulPaintTimingInfo& paint =
1104 GetDelegate()
1105 .GetLargestContentfulPaintHandler()
1106 .MergeMainFrameAndSubframes();
1107
1108 if (paint.ContainsValidTime()) {
1109 TRACE_EVENT_INSTANT2(
1110 "loading",
1111 "NavStartToLargestContentfulPaint::Candidate::AllFrames::UKM",
1112 TRACE_EVENT_SCOPE_THREAD, "data", paint.DataAsTraceValue(),
1113 "main_frame_tree_node_id",
1114 GetDelegate().GetLargestContentfulPaintHandler().MainFrameTreeNodeId());
1115 } else {
1116 TRACE_EVENT_INSTANT1(
1117 "loading",
1118 "NavStartToLargestContentfulPaint::"
1119 "Invalidate::AllFrames::UKM",
1120 TRACE_EVENT_SCOPE_THREAD, "main_frame_tree_node_id",
1121 GetDelegate().GetLargestContentfulPaintHandler().MainFrameTreeNodeId());
1122 }
1123
1124 const page_load_metrics::ContentfulPaintTimingInfo&
1125 experimental_largest_contentful_paint =
1126 GetDelegate()
1127 .GetExperimentalLargestContentfulPaintHandler()
1128 .MergeMainFrameAndSubframes();
1129 if (experimental_largest_contentful_paint.ContainsValidTime()) {
1130 TRACE_EVENT_INSTANT2(
1131 "loading",
1132 "NavStartToExperimentalLargestContentfulPaint::Candidate::AllFrames::"
1133 "UKM",
1134 TRACE_EVENT_SCOPE_THREAD, "data",
1135 experimental_largest_contentful_paint.DataAsTraceValue(),
1136 "main_frame_tree_node_id",
1137 GetDelegate()
1138 .GetExperimentalLargestContentfulPaintHandler()
1139 .MainFrameTreeNodeId());
1140 } else {
1141 TRACE_EVENT_INSTANT1("loading",
1142 "NavStartToExperimentalLargestContentfulPaint::"
1143 "Invalidate::AllFrames::UKM",
1144 TRACE_EVENT_SCOPE_THREAD, "main_frame_tree_node_id",
1145 GetDelegate()
1146 .GetExperimentalLargestContentfulPaintHandler()
1147 .MainFrameTreeNodeId());
1148 }
1149 }
1150
SetUpSharedMemoryForSmoothness(const base::ReadOnlySharedMemoryRegion & shared_memory)1151 void UkmPageLoadMetricsObserver::SetUpSharedMemoryForSmoothness(
1152 const base::ReadOnlySharedMemoryRegion& shared_memory) {
1153 ukm_smoothness_data_ = shared_memory.Map();
1154 }
1155
OnCpuTimingUpdate(content::RenderFrameHost * subframe_rfh,const page_load_metrics::mojom::CpuTiming & timing)1156 void UkmPageLoadMetricsObserver::OnCpuTimingUpdate(
1157 content::RenderFrameHost* subframe_rfh,
1158 const page_load_metrics::mojom::CpuTiming& timing) {
1159 if (GetDelegate().GetVisibilityTracker().currently_in_foreground())
1160 total_foreground_cpu_time_ += timing.task_time;
1161 }
1162
DidActivatePortal(base::TimeTicks activation_time)1163 void UkmPageLoadMetricsObserver::DidActivatePortal(
1164 base::TimeTicks activation_time) {
1165 is_portal_ = false;
1166 }
1167
RecordNoStatePrefetchMetrics(content::NavigationHandle * navigation_handle,ukm::SourceId source_id)1168 void UkmPageLoadMetricsObserver::RecordNoStatePrefetchMetrics(
1169 content::NavigationHandle* navigation_handle,
1170 ukm::SourceId source_id) {
1171 prerender::PrerenderManager* const prerender_manager =
1172 prerender::PrerenderManagerFactory::GetForBrowserContext(
1173 navigation_handle->GetWebContents()->GetBrowserContext());
1174 if (!prerender_manager)
1175 return;
1176
1177 const std::vector<GURL>& redirects = navigation_handle->GetRedirectChain();
1178
1179 base::TimeDelta prefetch_age;
1180 prerender::FinalStatus final_status;
1181 prerender::Origin prefetch_origin;
1182
1183 bool nostate_prefetch_entry_found = prerender_manager->GetPrefetchInformation(
1184 navigation_handle->GetURL(), &prefetch_age, &final_status,
1185 &prefetch_origin);
1186
1187 // Try the URLs from the redirect chain.
1188 if (!nostate_prefetch_entry_found) {
1189 for (const auto& url : redirects) {
1190 nostate_prefetch_entry_found = prerender_manager->GetPrefetchInformation(
1191 url, &prefetch_age, &final_status, &prefetch_origin);
1192 if (nostate_prefetch_entry_found)
1193 break;
1194 }
1195 }
1196
1197 if (!nostate_prefetch_entry_found)
1198 return;
1199
1200 ukm::builders::NoStatePrefetch builder(source_id);
1201 builder.SetPrefetchedRecently_PrefetchAge(
1202 ukm::GetExponentialBucketMinForUserTiming(prefetch_age.InMilliseconds()));
1203 builder.SetPrefetchedRecently_FinalStatus(final_status);
1204 builder.SetPrefetchedRecently_Origin(prefetch_origin);
1205 builder.Record(ukm::UkmRecorder::Get());
1206 }
1207
IsOfflinePreview(content::WebContents * web_contents) const1208 bool UkmPageLoadMetricsObserver::IsOfflinePreview(
1209 content::WebContents* web_contents) const {
1210 #if BUILDFLAG(ENABLE_OFFLINE_PAGES)
1211 offline_pages::OfflinePageTabHelper* tab_helper =
1212 offline_pages::OfflinePageTabHelper::FromWebContents(web_contents);
1213 return tab_helper && tab_helper->GetOfflinePreviewItem();
1214 #else
1215 return false;
1216 #endif
1217 }
1218
RecordGeneratedNavigationUKM(ukm::SourceId source_id,const GURL & committed_url)1219 void UkmPageLoadMetricsObserver::RecordGeneratedNavigationUKM(
1220 ukm::SourceId source_id,
1221 const GURL& committed_url) {
1222 bool final_url_is_home_page = IsUserHomePage(browser_context_, committed_url);
1223 bool final_url_is_default_search =
1224 IsDefaultSearchEngine(browser_context_, committed_url);
1225
1226 if (!final_url_is_home_page && !final_url_is_default_search &&
1227 !start_url_is_home_page_ && !start_url_is_default_search_) {
1228 return;
1229 }
1230
1231 ukm::builders::GeneratedNavigation builder(source_id);
1232 builder.SetFinalURLIsHomePage(final_url_is_home_page);
1233 builder.SetFinalURLIsDefaultSearchEngine(final_url_is_default_search);
1234 builder.SetFirstURLIsHomePage(start_url_is_home_page_);
1235 builder.SetFirstURLIsDefaultSearchEngine(start_url_is_default_search_);
1236 builder.Record(ukm::UkmRecorder::Get());
1237 }
1238
OnLoadingBehaviorObserved(content::RenderFrameHost * rfh,int behavior_flag)1239 void UkmPageLoadMetricsObserver::OnLoadingBehaviorObserved(
1240 content::RenderFrameHost* rfh,
1241 int behavior_flag) {
1242 if (behavior_flag &
1243 blink::LoadingBehaviorFlag::
1244 kLoadingBehaviorAsyncScriptReadyBeforeDocumentFinishedParsing) {
1245 delay_async_script_execution_before_finished_parsing_seen_ = true;
1246 }
1247
1248 if (behavior_flag & blink::LoadingBehaviorFlag::
1249 kLoadingBehaviorCompetingLowPriorityRequestsDelayed) {
1250 delay_competing_low_priority_requests_seen_ = true;
1251 }
1252 }
1253