| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h" |
| |
| #include <cmath> |
| #include <memory> |
| #include <vector> |
| |
| #include "base/feature_list.h" |
| #include "base/trace_event/common/trace_event_common.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/content_settings/cookie_settings_factory.h" |
| #include "chrome/browser/engagement/site_engagement_service.h" |
| #include "chrome/browser/prerender/prerender_manager.h" |
| #include "chrome/browser/prerender/prerender_manager_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/content_settings/core/browser/cookie_settings.h" |
| #include "components/content_settings/core/common/features.h" |
| #include "components/content_settings/core/common/pref_names.h" |
| #include "components/metrics/net/network_metrics_provider.h" |
| #include "components/offline_pages/buildflags/buildflags.h" |
| #include "components/page_load_metrics/browser/observers/largest_contentful_paint_handler.h" |
| #include "components/page_load_metrics/browser/page_load_metrics_util.h" |
| #include "components/page_load_metrics/browser/protocol_util.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prerender/common/prerender_final_status.h" |
| #include "components/prerender/common/prerender_origin.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/web_contents.h" |
| #include "media/base/mime_util.h" |
| #include "net/base/load_timing_info.h" |
| #include "net/http/http_response_headers.h" |
| #include "services/metrics/public/cpp/metrics_utils.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_recorder.h" |
| #include "services/network/public/cpp/network_quality_tracker.h" |
| #include "third_party/blink/public/common/mime_util/mime_util.h" |
| #include "third_party/metrics_proto/system_profile.pb.h" |
| #include "ui/events/blink/blink_features.h" |
| |
| #if BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| #include "chrome/browser/offline_pages/offline_page_tab_helper.h" |
| #endif |
| |
| namespace { |
| |
| const char kOfflinePreviewsMimeType[] = "multipart/related"; |
| |
| bool IsSupportedProtocol(page_load_metrics::NetworkProtocol protocol) { |
| switch (protocol) { |
| case page_load_metrics::NetworkProtocol::kHttp11: |
| return true; |
| case page_load_metrics::NetworkProtocol::kHttp2: |
| return true; |
| case page_load_metrics::NetworkProtocol::kQuic: |
| return true; |
| case page_load_metrics::NetworkProtocol::kOther: |
| return false; |
| } |
| } |
| |
| bool IsDefaultSearchEngine(content::BrowserContext* browser_context, |
| const GURL& url) { |
| if (!browser_context) |
| return false; |
| |
| auto* template_service = TemplateURLServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser_context)); |
| |
| if (!template_service) |
| return false; |
| |
| return template_service->IsSearchResultsPageFromDefaultSearchProvider(url); |
| } |
| |
| bool IsUserHomePage(content::BrowserContext* browser_context, const GURL& url) { |
| if (!browser_context) |
| return false; |
| |
| return url.spec() == Profile::FromBrowserContext(browser_context) |
| ->GetPrefs() |
| ->GetString(prefs::kHomePage); |
| } |
| |
| std::unique_ptr<base::trace_event::TracedValue> CumulativeShiftScoreTraceData( |
| float layout_shift_score, |
| float layout_shift_score_before_input_or_scroll) { |
| std::unique_ptr<base::trace_event::TracedValue> data = |
| std::make_unique<base::trace_event::TracedValue>(); |
| data->SetDouble("layoutShiftScore", layout_shift_score); |
| data->SetDouble("layoutShiftScoreBeforeInputOrScroll", |
| layout_shift_score_before_input_or_scroll); |
| return data; |
| } |
| |
| int8_t ComputeMedianForThroughput(const base::flat_map<int8_t, int>& data) { |
| int total_samples = 0; |
| for (const auto& e : data) |
| total_samples += e.second; |
| int half_samples = total_samples / 2; |
| int current_samples = 0; |
| for (const auto& e : data) { |
| current_samples += e.second; |
| if (current_samples > half_samples) |
| return e.first; |
| } |
| NOTREACHED(); |
| return 0; |
| } |
| |
| bool ValidatePercent(int8_t percent) { |
| if (percent >= 0 && percent <= 100) |
| return true; |
| return false; |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<page_load_metrics::PageLoadMetricsObserver> |
| UkmPageLoadMetricsObserver::CreateIfNeeded() { |
| if (!ukm::UkmRecorder::Get()) { |
| return nullptr; |
| } |
| return std::make_unique<UkmPageLoadMetricsObserver>( |
| g_browser_process->network_quality_tracker()); |
| } |
| |
| UkmPageLoadMetricsObserver::UkmPageLoadMetricsObserver( |
| network::NetworkQualityTracker* network_quality_tracker) |
| : network_quality_tracker_(network_quality_tracker) { |
| DCHECK(network_quality_tracker_); |
| } |
| |
| UkmPageLoadMetricsObserver::~UkmPageLoadMetricsObserver() = default; |
| |
| UkmPageLoadMetricsObserver::ObservePolicy UkmPageLoadMetricsObserver::OnStart( |
| content::NavigationHandle* navigation_handle, |
| const GURL& currently_committed_url, |
| bool started_in_foreground) { |
| content::WebContents* web_contents = navigation_handle->GetWebContents(); |
| is_portal_ = web_contents->IsPortal(); |
| |
| browser_context_ = web_contents->GetBrowserContext(); |
| |
| start_url_is_default_search_ = |
| IsDefaultSearchEngine(browser_context_, navigation_handle->GetURL()); |
| start_url_is_home_page_ = |
| IsUserHomePage(browser_context_, navigation_handle->GetURL()); |
| |
| if (started_in_foreground) { |
| last_time_shown_ = navigation_handle->NavigationStart(); |
| } |
| currently_in_foreground_ = started_in_foreground; |
| |
| if (!started_in_foreground) { |
| was_hidden_ = true; |
| return CONTINUE_OBSERVING; |
| } |
| |
| // When OnStart is invoked, we don't yet know whether we're observing a web |
| // page load, vs another kind of load (e.g. a download or a PDF). Thus, |
| // metrics and source information should not be recorded here. Instead, we |
| // store data we might want to persist in member variables below, and later |
| // record UKM metrics for that data once we've confirmed that we're observing |
| // a web page load. |
| |
| effective_connection_type_ = |
| network_quality_tracker_->GetEffectiveConnectionType(); |
| http_rtt_estimate_ = network_quality_tracker_->GetHttpRTT(); |
| transport_rtt_estimate_ = network_quality_tracker_->GetTransportRTT(); |
| downstream_kbps_estimate_ = |
| network_quality_tracker_->GetDownstreamThroughputKbps(); |
| page_transition_ = navigation_handle->GetPageTransition(); |
| return CONTINUE_OBSERVING; |
| } |
| |
| page_load_metrics::PageLoadMetricsObserver::ObservePolicy |
| UkmPageLoadMetricsObserver::OnRedirect( |
| content::NavigationHandle* navigation_handle) { |
| main_frame_request_redirect_count_++; |
| return CONTINUE_OBSERVING; |
| } |
| |
| UkmPageLoadMetricsObserver::ObservePolicy |
| UkmPageLoadMetricsObserver::ShouldObserveMimeType( |
| const std::string& mime_type) const { |
| if (PageLoadMetricsObserver::ShouldObserveMimeType(mime_type) == |
| CONTINUE_OBSERVING || |
| mime_type == kOfflinePreviewsMimeType) { |
| return CONTINUE_OBSERVING; |
| } |
| return STOP_OBSERVING; |
| } |
| |
| UkmPageLoadMetricsObserver::ObservePolicy UkmPageLoadMetricsObserver::OnCommit( |
| content::NavigationHandle* navigation_handle, |
| ukm::SourceId source_id) { |
| if (navigation_handle->GetWebContents()->GetContentsMimeType() == |
| kOfflinePreviewsMimeType) { |
| if (!IsOfflinePreview(navigation_handle->GetWebContents())) |
| return STOP_OBSERVING; |
| } |
| connection_info_ = navigation_handle->GetConnectionInfo(); |
| const net::HttpResponseHeaders* response_headers = |
| navigation_handle->GetResponseHeaders(); |
| if (response_headers) |
| http_response_code_ = response_headers->response_code(); |
| // The PageTransition for the navigation may be updated on commit. |
| page_transition_ = navigation_handle->GetPageTransition(); |
| was_cached_ = navigation_handle->WasResponseCached(); |
| navigation_handle_timing_ = navigation_handle->GetNavigationHandleTiming(); |
| RecordNoStatePrefetchMetrics(navigation_handle, source_id); |
| RecordGeneratedNavigationUKM(source_id, navigation_handle->GetURL()); |
| navigation_is_cross_process_ = !navigation_handle->IsSameProcess(); |
| navigation_entry_offset_ = navigation_handle->GetNavigationEntryOffset(); |
| main_document_sequence_number_ = navigation_handle->GetWebContents() |
| ->GetController() |
| .GetLastCommittedEntry() |
| ->GetMainFrameDocumentSequenceNumber(); |
| return CONTINUE_OBSERVING; |
| } |
| |
| UkmPageLoadMetricsObserver::ObservePolicy |
| UkmPageLoadMetricsObserver::FlushMetricsOnAppEnterBackground( |
| const page_load_metrics::mojom::PageLoadTiming& timing) { |
| if (is_portal_) |
| return STOP_OBSERVING; |
| |
| base::TimeTicks current_time = base::TimeTicks::Now(); |
| if (!was_hidden_) { |
| RecordNavigationTimingMetrics(); |
| RecordPageLoadMetrics(current_time, true /* became_hidden */); |
| RecordTimingMetrics(timing); |
| RecordInputTimingMetrics(); |
| } |
| ReportLayoutStability(); |
| // Assume that page ends on this method, as the app could be evicted right |
| // after. |
| ReportAbortMetrics(timing, current_time); |
| return STOP_OBSERVING; |
| } |
| |
| UkmPageLoadMetricsObserver::ObservePolicy UkmPageLoadMetricsObserver::OnHidden( |
| const page_load_metrics::mojom::PageLoadTiming& timing) { |
| if (is_portal_) |
| return CONTINUE_OBSERVING; |
| |
| if (currently_in_foreground_ && !last_time_shown_.is_null()) { |
| total_foreground_duration_ += base::TimeTicks::Now() - last_time_shown_; |
| } |
| currently_in_foreground_ = false; |
| if (!was_hidden_) { |
| RecordNavigationTimingMetrics(); |
| RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */, |
| true /* became_hidden */); |
| RecordTimingMetrics(timing); |
| RecordInputTimingMetrics(); |
| was_hidden_ = true; |
| } |
| return CONTINUE_OBSERVING; |
| } |
| |
| UkmPageLoadMetricsObserver::ObservePolicy |
| UkmPageLoadMetricsObserver::OnShown() { |
| if (is_portal_) |
| return CONTINUE_OBSERVING; |
| |
| currently_in_foreground_ = true; |
| last_time_shown_ = base::TimeTicks::Now(); |
| return CONTINUE_OBSERVING; |
| } |
| |
| void UkmPageLoadMetricsObserver::OnFailedProvisionalLoad( |
| const page_load_metrics::FailedProvisionalLoadInfo& failed_load_info) { |
| if (is_portal_) |
| return; |
| |
| if (was_hidden_) |
| return; |
| RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */, |
| false /* became_hidden */); |
| |
| // Error codes have negative values, however we log net error code enum values |
| // for UMA histograms using the equivalent positive value. For consistency in |
| // UKM, we convert to a positive value here. |
| int64_t net_error_code = static_cast<int64_t>(failed_load_info.error) * -1; |
| DCHECK_GE(net_error_code, 0); |
| ukm::builders::PageLoad(GetDelegate().GetSourceId()) |
| .SetNet_ErrorCode_OnFailedProvisionalLoad(net_error_code) |
| .SetPageTiming_NavigationToFailedProvisionalLoad( |
| failed_load_info.time_to_failed_provisional_load.InMilliseconds()) |
| .Record(ukm::UkmRecorder::Get()); |
| } |
| |
| void UkmPageLoadMetricsObserver::OnComplete( |
| const page_load_metrics::mojom::PageLoadTiming& timing) { |
| if (is_portal_) |
| return; |
| |
| base::TimeTicks current_time = base::TimeTicks::Now(); |
| if (!was_hidden_) { |
| RecordNavigationTimingMetrics(); |
| RecordPageLoadMetrics(current_time /* no app_background_time */, |
| false /* became_hidden */); |
| RecordTimingMetrics(timing); |
| RecordInputTimingMetrics(); |
| } |
| ReportLayoutStability(); |
| ReportAbortMetrics(timing, current_time); |
| } |
| |
| void UkmPageLoadMetricsObserver::OnResourceDataUseObserved( |
| content::RenderFrameHost* content, |
| const std::vector<page_load_metrics::mojom::ResourceDataUpdatePtr>& |
| resources) { |
| if (was_hidden_) |
| return; |
| for (auto const& resource : resources) { |
| network_bytes_ += resource->delta_bytes; |
| |
| if (blink::IsSupportedImageMimeType(resource->mime_type)) { |
| image_total_bytes_ += resource->delta_bytes; |
| if (!resource->is_main_frame_resource) |
| image_subframe_bytes_ += resource->delta_bytes; |
| } else if (media::IsSupportedMediaMimeType(resource->mime_type) || |
| base::StartsWith(resource->mime_type, "audio/", |
| base::CompareCase::SENSITIVE) || |
| base::StartsWith(resource->mime_type, "video/", |
| base::CompareCase::SENSITIVE)) { |
| media_bytes_ += resource->delta_bytes; |
| } |
| |
| // Only sum body lengths for completed resources. |
| if (!resource->is_complete) |
| continue; |
| if (blink::IsSupportedJavascriptMimeType(resource->mime_type)) { |
| js_decoded_bytes_ += resource->decoded_body_length; |
| if (resource->decoded_body_length > js_max_decoded_bytes_) |
| js_max_decoded_bytes_ = resource->decoded_body_length; |
| } |
| if (resource->cache_type != |
| page_load_metrics::mojom::CacheType::kNotCached) { |
| cache_bytes_ += resource->encoded_body_length; |
| } |
| } |
| } |
| |
| void UkmPageLoadMetricsObserver::OnLoadedResource( |
| const page_load_metrics::ExtraRequestCompleteInfo& |
| extra_request_complete_info) { |
| if (was_hidden_) |
| return; |
| if (extra_request_complete_info.request_destination == |
| network::mojom::RequestDestination::kDocument) { |
| DCHECK(!main_frame_timing_.has_value()); |
| main_frame_timing_ = *extra_request_complete_info.load_timing_info; |
| } |
| } |
| |
| void UkmPageLoadMetricsObserver::RecordNavigationTimingMetrics() { |
| const base::TimeTicks navigation_start_time = |
| GetDelegate().GetNavigationStart(); |
| const content::NavigationHandleTiming& timing = navigation_handle_timing_; |
| |
| // Record metrics for navigation only when all relevant milestones are |
| // recorded and in the expected order. It is allowed that they have the same |
| // value for some cases (e.g., internal redirection for HSTS). |
| if (navigation_start_time.is_null() || |
| timing.first_request_start_time.is_null() || |
| timing.first_response_start_time.is_null() || |
| timing.first_loader_callback_time.is_null() || |
| timing.final_request_start_time.is_null() || |
| timing.final_response_start_time.is_null() || |
| timing.final_loader_callback_time.is_null() || |
| timing.navigation_commit_sent_time.is_null()) { |
| return; |
| } |
| // TODO(https://crbug.com/1076710): Change these early-returns to DCHECKs |
| // after the issue 1076710 is fixed. |
| if (navigation_start_time > timing.first_request_start_time || |
| timing.first_request_start_time > timing.first_response_start_time || |
| timing.first_response_start_time > timing.first_loader_callback_time || |
| timing.first_loader_callback_time > timing.navigation_commit_sent_time) { |
| return; |
| } |
| if (navigation_start_time > timing.final_request_start_time || |
| timing.final_request_start_time > timing.final_response_start_time || |
| timing.final_response_start_time > timing.final_loader_callback_time || |
| timing.final_loader_callback_time > timing.navigation_commit_sent_time) { |
| return; |
| } |
| DCHECK_LE(timing.first_request_start_time, timing.final_request_start_time); |
| DCHECK_LE(timing.first_response_start_time, timing.final_response_start_time); |
| DCHECK_LE(timing.first_loader_callback_time, |
| timing.final_loader_callback_time); |
| |
| ukm::builders::NavigationTiming builder(GetDelegate().GetSourceId()); |
| |
| // Record the elapsed time from the navigation start milestone. |
| builder |
| .SetFirstRequestStart( |
| (timing.first_request_start_time - navigation_start_time) |
| .InMilliseconds()) |
| .SetFirstResponseStart( |
| (timing.first_response_start_time - navigation_start_time) |
| .InMilliseconds()) |
| .SetFirstLoaderCallback( |
| (timing.first_loader_callback_time - navigation_start_time) |
| .InMilliseconds()) |
| .SetFinalRequestStart( |
| (timing.final_request_start_time - navigation_start_time) |
| .InMilliseconds()) |
| .SetFinalResponseStart( |
| (timing.final_response_start_time - navigation_start_time) |
| .InMilliseconds()) |
| .SetFinalLoaderCallback( |
| (timing.final_loader_callback_time - navigation_start_time) |
| .InMilliseconds()) |
| .SetNavigationCommitSent( |
| (timing.navigation_commit_sent_time - navigation_start_time) |
| .InMilliseconds()); |
| |
| // Record the elapsed time from the navigation start milestone for the 103 |
| // Early Hints experiment (https://crbug.com/1093693). Note that multiple 103 |
| // responses can be served per request. These metrics use the first 103 |
| // response as the timing. |
| if (!timing.early_hints_for_first_request_time.is_null()) { |
| builder.SetEarlyHintsForFirstRequest( |
| (timing.early_hints_for_first_request_time - navigation_start_time) |
| .InMilliseconds()); |
| } |
| if (!timing.early_hints_for_final_request_time.is_null()) { |
| builder.SetEarlyHintsForFinalRequest( |
| (timing.early_hints_for_final_request_time - navigation_start_time) |
| .InMilliseconds()); |
| } |
| |
| builder.Record(ukm::UkmRecorder::Get()); |
| } |
| |
| void UkmPageLoadMetricsObserver::RecordTimingMetrics( |
| const page_load_metrics::mojom::PageLoadTiming& timing) { |
| ukm::builders::PageLoad builder(GetDelegate().GetSourceId()); |
| |
| base::Optional<int64_t> rounded_site_engagement_score = |
| GetRoundedSiteEngagementScore(); |
| if (rounded_site_engagement_score) { |
| builder.SetSiteEngagementScore(rounded_site_engagement_score.value()); |
| } |
| |
| base::Optional<bool> third_party_cookie_blocking_enabled = |
| GetThirdPartyCookieBlockingEnabled(); |
| if (third_party_cookie_blocking_enabled) { |
| builder.SetThirdPartyCookieBlockingEnabledForSite( |
| third_party_cookie_blocking_enabled.value()); |
| UMA_HISTOGRAM_BOOLEAN("Privacy.ThirdPartyCookieBlockingEnabledForSite", |
| third_party_cookie_blocking_enabled.value()); |
| } |
| |
| if (timing.input_to_navigation_start) { |
| builder.SetExperimental_InputToNavigationStart( |
| timing.input_to_navigation_start.value().InMilliseconds()); |
| } |
| if (timing.parse_timing->parse_start) { |
| builder.SetParseTiming_NavigationToParseStart( |
| timing.parse_timing->parse_start.value().InMilliseconds()); |
| } |
| if (timing.document_timing->dom_content_loaded_event_start) { |
| builder.SetDocumentTiming_NavigationToDOMContentLoadedEventFired( |
| timing.document_timing->dom_content_loaded_event_start.value() |
| .InMilliseconds()); |
| } |
| if (timing.document_timing->load_event_start) { |
| builder.SetDocumentTiming_NavigationToLoadEventFired( |
| timing.document_timing->load_event_start.value().InMilliseconds()); |
| } |
| if (timing.paint_timing->first_paint) { |
| builder.SetPaintTiming_NavigationToFirstPaint( |
| timing.paint_timing->first_paint.value().InMilliseconds()); |
| } |
| if (timing.paint_timing->first_contentful_paint) { |
| builder.SetPaintTiming_NavigationToFirstContentfulPaint( |
| timing.paint_timing->first_contentful_paint.value().InMilliseconds()); |
| } |
| if (timing.paint_timing->first_meaningful_paint) { |
| builder.SetExperimental_PaintTiming_NavigationToFirstMeaningfulPaint( |
| timing.paint_timing->first_meaningful_paint.value().InMilliseconds()); |
| } |
| const page_load_metrics::ContentfulPaintTimingInfo& |
| main_frame_largest_contentful_paint = |
| GetDelegate() |
| .GetLargestContentfulPaintHandler() |
| .MainFrameLargestContentfulPaint(); |
| if (main_frame_largest_contentful_paint.ContainsValidTime() && |
| WasStartedInForegroundOptionalEventInForeground( |
| main_frame_largest_contentful_paint.Time(), GetDelegate())) { |
| builder.SetPaintTiming_NavigationToLargestContentfulPaint_MainFrame( |
| main_frame_largest_contentful_paint.Time().value().InMilliseconds()); |
| } |
| const page_load_metrics::ContentfulPaintTimingInfo& |
| all_frames_largest_contentful_paint = |
| GetDelegate() |
| .GetLargestContentfulPaintHandler() |
| .MergeMainFrameAndSubframes(); |
| if (all_frames_largest_contentful_paint.ContainsValidTime() && |
| WasStartedInForegroundOptionalEventInForeground( |
| all_frames_largest_contentful_paint.Time(), GetDelegate())) { |
| builder.SetPaintTiming_NavigationToLargestContentfulPaint( |
| all_frames_largest_contentful_paint.Time().value().InMilliseconds()); |
| } |
| const page_load_metrics::ContentfulPaintTimingInfo& |
| main_frame_experimental_largest_contentful_paint = |
| GetDelegate() |
| .GetExperimentalLargestContentfulPaintHandler() |
| .MainFrameLargestContentfulPaint(); |
| if (main_frame_experimental_largest_contentful_paint.ContainsValidTime() && |
| WasStartedInForegroundOptionalEventInForeground( |
| main_frame_experimental_largest_contentful_paint.Time(), |
| GetDelegate())) { |
| builder |
| .SetPaintTiming_NavigationToExperimentalLargestContentfulPaint_MainFrame( |
| main_frame_experimental_largest_contentful_paint.Time() |
| .value() |
| .InMilliseconds()); |
| } |
| const page_load_metrics::ContentfulPaintTimingInfo& |
| all_frames_experimental_largest_contentful_paint = |
| GetDelegate() |
| .GetExperimentalLargestContentfulPaintHandler() |
| .MergeMainFrameAndSubframes(); |
| if (all_frames_experimental_largest_contentful_paint.ContainsValidTime() && |
| WasStartedInForegroundOptionalEventInForeground( |
| all_frames_experimental_largest_contentful_paint.Time(), |
| GetDelegate())) { |
| builder.SetPaintTiming_NavigationToExperimentalLargestContentfulPaint( |
| all_frames_experimental_largest_contentful_paint.Time() |
| .value() |
| .InMilliseconds()); |
| } |
| RecordInternalTimingMetrics(all_frames_largest_contentful_paint, |
| all_frames_experimental_largest_contentful_paint); |
| if (timing.interactive_timing->first_input_delay) { |
| base::TimeDelta first_input_delay = |
| timing.interactive_timing->first_input_delay.value(); |
| builder.SetInteractiveTiming_FirstInputDelay4( |
| first_input_delay.InMilliseconds()); |
| } |
| if (timing.interactive_timing->first_input_timestamp) { |
| base::TimeDelta first_input_timestamp = |
| timing.interactive_timing->first_input_timestamp.value(); |
| builder.SetInteractiveTiming_FirstInputTimestamp4( |
| first_input_timestamp.InMilliseconds()); |
| } |
| |
| if (timing.interactive_timing->longest_input_delay) { |
| base::TimeDelta longest_input_delay = |
| timing.interactive_timing->longest_input_delay.value(); |
| builder.SetInteractiveTiming_LongestInputDelay4( |
| longest_input_delay.InMilliseconds()); |
| } |
| if (timing.interactive_timing->longest_input_timestamp) { |
| base::TimeDelta longest_input_timestamp = |
| timing.interactive_timing->longest_input_timestamp.value(); |
| builder.SetInteractiveTiming_LongestInputTimestamp4( |
| longest_input_timestamp.InMilliseconds()); |
| } |
| if (timing.interactive_timing->first_scroll_delay) { |
| base::TimeDelta first_scroll_delay = |
| timing.interactive_timing->first_scroll_delay.value(); |
| builder.SetInteractiveTiming_FirstScrollDelay( |
| first_scroll_delay.InMilliseconds()); |
| } |
| if (timing.interactive_timing->first_input_processing_time) { |
| base::TimeDelta first_input_processing_time = |
| timing.interactive_timing->first_input_processing_time.value(); |
| builder.SetInteractiveTiming_FirstInputProcessingTimes( |
| first_input_processing_time.InMilliseconds()); |
| } |
| builder.SetCpuTime(total_foreground_cpu_time_.InMilliseconds()); |
| |
| // Use a bucket spacing factor of 1.3 for bytes. |
| builder.SetNet_CacheBytes2(ukm::GetExponentialBucketMin(cache_bytes_, 1.3)); |
| builder.SetNet_NetworkBytes2( |
| ukm::GetExponentialBucketMin(network_bytes_, 1.3)); |
| |
| // Use a bucket spacing factor of 10 for JS bytes. |
| builder.SetNet_JavaScriptBytes( |
| ukm::GetExponentialBucketMin(js_decoded_bytes_, 10)); |
| builder.SetNet_JavaScriptMaxBytes( |
| ukm::GetExponentialBucketMin(js_max_decoded_bytes_, 10)); |
| |
| builder.SetNet_ImageBytes( |
| ukm::GetExponentialBucketMin(image_total_bytes_, 1.3)); |
| builder.SetNet_ImageSubframeBytes( |
| ukm::GetExponentialBucketMin(image_subframe_bytes_, 1.3)); |
| builder.SetNet_MediaBytes(ukm::GetExponentialBucketMin(media_bytes_, 1.3)); |
| |
| if (main_frame_timing_) |
| ReportMainResourceTimingMetrics(timing, &builder); |
| |
| builder.Record(ukm::UkmRecorder::Get()); |
| |
| if (throughput_source_id_ != ukm::kInvalidSourceId) |
| ReportThroughputUkm(); |
| } |
| |
| void UkmPageLoadMetricsObserver::RecordInternalTimingMetrics( |
| const page_load_metrics::ContentfulPaintTimingInfo& |
| all_frames_largest_contentful_paint, |
| const page_load_metrics::ContentfulPaintTimingInfo& |
| all_frames_experimental_largest_contentful_paint) { |
| ukm::builders::PageLoad_Internal debug_builder(GetDelegate().GetSourceId()); |
| LargestContentState lcp_state = LargestContentState::kNotFound; |
| if (all_frames_largest_contentful_paint.ContainsValidTime()) { |
| if (WasStartedInForegroundOptionalEventInForeground( |
| all_frames_largest_contentful_paint.Time(), GetDelegate())) { |
| debug_builder.SetPaintTiming_LargestContentfulPaint_ContentType( |
| static_cast<int>(all_frames_largest_contentful_paint.Type())); |
| lcp_state = LargestContentState::kReported; |
| } else { |
| // TODO(npm): figure out why this code can be reached given that |
| // RecordTimingMetrics() is only called when was_hidden_ is set to false. |
| lcp_state = LargestContentState::kFoundButNotReported; |
| } |
| } else if (all_frames_largest_contentful_paint.Time().has_value()) { |
| DCHECK(all_frames_largest_contentful_paint.Size()); |
| lcp_state = LargestContentState::kLargestImageLoading; |
| } else { |
| DCHECK(all_frames_largest_contentful_paint.Empty()); |
| lcp_state = LargestContentState::kNotFound; |
| } |
| debug_builder.SetPaintTiming_LargestContentfulPaint_TerminationState( |
| static_cast<int>(lcp_state)); |
| |
| LargestContentState experimental_lcp_state = LargestContentState::kNotFound; |
| if (all_frames_experimental_largest_contentful_paint.ContainsValidTime()) { |
| if (WasStartedInForegroundOptionalEventInForeground( |
| all_frames_experimental_largest_contentful_paint.Time(), |
| GetDelegate())) { |
| debug_builder |
| .SetPaintTiming_ExperimentalLargestContentfulPaint_ContentType( |
| static_cast<int>( |
| all_frames_experimental_largest_contentful_paint.Type())); |
| experimental_lcp_state = LargestContentState::kReported; |
| } else { |
| // TODO(npm): figure out why this code can be reached given that |
| // RecordTimingMetrics() is only called when was_hidden_ is set to false. |
| experimental_lcp_state = LargestContentState::kFoundButNotReported; |
| } |
| } else if (all_frames_experimental_largest_contentful_paint.Time() |
| .has_value()) { |
| DCHECK(all_frames_experimental_largest_contentful_paint.Size()); |
| experimental_lcp_state = LargestContentState::kLargestImageLoading; |
| } else { |
| DCHECK(all_frames_experimental_largest_contentful_paint.Empty()); |
| experimental_lcp_state = LargestContentState::kNotFound; |
| } |
| debug_builder |
| .SetPaintTiming_ExperimentalLargestContentfulPaint_TerminationState( |
| static_cast<int>(lcp_state)); |
| debug_builder.Record(ukm::UkmRecorder::Get()); |
| } |
| |
| void UkmPageLoadMetricsObserver::RecordPageLoadMetrics( |
| base::TimeTicks app_background_time, |
| bool became_hidden) { |
| ukm::builders::PageLoad builder(GetDelegate().GetSourceId()); |
| base::Optional<base::TimeDelta> foreground_duration = |
| page_load_metrics::GetInitialForegroundDuration(GetDelegate(), |
| app_background_time); |
| if (foreground_duration) { |
| builder.SetPageTiming_ForegroundDuration( |
| foreground_duration.value().InMilliseconds()); |
| } |
| |
| bool is_user_initiated_navigation = |
| // All browser initiated page loads are user-initiated. |
| GetDelegate().GetUserInitiatedInfo().browser_initiated || |
| |
| // Renderer-initiated navigations are user-initiated if there is an |
| // associated input event. |
| GetDelegate().GetUserInitiatedInfo().user_input_event; |
| |
| builder.SetExperimental_Navigation_UserInitiated( |
| is_user_initiated_navigation); |
| |
| // Convert to the EffectiveConnectionType as used in SystemProfileProto |
| // before persisting the metric. |
| metrics::SystemProfileProto::Network::EffectiveConnectionType |
| proto_effective_connection_type = |
| metrics::ConvertEffectiveConnectionType(effective_connection_type_); |
| if (proto_effective_connection_type != |
| metrics::SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) { |
| builder.SetNet_EffectiveConnectionType2_OnNavigationStart( |
| static_cast<int64_t>(proto_effective_connection_type)); |
| } |
| |
| if (http_response_code_) { |
| builder.SetNet_HttpResponseCode( |
| static_cast<int64_t>(http_response_code_.value())); |
| } |
| if (http_rtt_estimate_) { |
| builder.SetNet_HttpRttEstimate_OnNavigationStart( |
| static_cast<int64_t>(http_rtt_estimate_.value().InMilliseconds())); |
| } |
| if (transport_rtt_estimate_) { |
| builder.SetNet_TransportRttEstimate_OnNavigationStart( |
| static_cast<int64_t>(transport_rtt_estimate_.value().InMilliseconds())); |
| } |
| if (downstream_kbps_estimate_) { |
| builder.SetNet_DownstreamKbpsEstimate_OnNavigationStart( |
| static_cast<int64_t>(downstream_kbps_estimate_.value())); |
| } |
| // page_transition_ fits in a uint32_t, so we can safely cast to int64_t. |
| builder.SetNavigation_PageTransition(static_cast<int64_t>(page_transition_)); |
| // GetDelegate().GetPageEndReason() fits in a uint32_t, so we can safely cast |
| // to int64_t. |
| int64_t page_end_reason = GetDelegate().GetPageEndReason(); |
| if (page_end_reason == page_load_metrics::PageEndReason::END_NONE && |
| became_hidden) { |
| page_end_reason = page_load_metrics::PageEndReason::END_HIDDEN; |
| } |
| builder.SetNavigation_PageEndReason2(page_end_reason); |
| if (GetDelegate().DidCommit() && was_cached_) { |
| builder.SetWasCached(1); |
| } |
| if (GetDelegate().DidCommit() && navigation_is_cross_process_) { |
| builder.SetIsCrossProcessNavigation(navigation_is_cross_process_); |
| } |
| if (GetDelegate().DidCommit()) { |
| builder.SetNavigationEntryOffset(navigation_entry_offset_); |
| builder.SetMainDocumentSequenceNumber(main_document_sequence_number_); |
| } |
| builder.Record(ukm::UkmRecorder::Get()); |
| } |
| |
| void UkmPageLoadMetricsObserver::ReportMainResourceTimingMetrics( |
| const page_load_metrics::mojom::PageLoadTiming& timing, |
| ukm::builders::PageLoad* builder) { |
| DCHECK(main_frame_timing_.has_value()); |
| |
| builder->SetMainFrameResource_SocketReused(main_frame_timing_->socket_reused); |
| |
| int64_t dns_start_ms = |
| main_frame_timing_->connect_timing.dns_start.since_origin() |
| .InMilliseconds(); |
| int64_t dns_end_ms = main_frame_timing_->connect_timing.dns_end.since_origin() |
| .InMilliseconds(); |
| int64_t connect_start_ms = |
| main_frame_timing_->connect_timing.connect_start.since_origin() |
| .InMilliseconds(); |
| int64_t connect_end_ms = |
| main_frame_timing_->connect_timing.connect_end.since_origin() |
| .InMilliseconds(); |
| int64_t request_start_ms = |
| main_frame_timing_->request_start.since_origin().InMilliseconds(); |
| int64_t send_start_ms = |
| main_frame_timing_->send_start.since_origin().InMilliseconds(); |
| int64_t receive_headers_end_ms = |
| main_frame_timing_->receive_headers_end.since_origin().InMilliseconds(); |
| |
| DCHECK_LE(dns_start_ms, dns_end_ms); |
| DCHECK_LE(dns_end_ms, connect_start_ms); |
| DCHECK_LE(dns_start_ms, connect_start_ms); |
| DCHECK_LE(connect_start_ms, connect_end_ms); |
| |
| int64_t dns_duration_ms = dns_end_ms - dns_start_ms; |
| int64_t connect_duration_ms = connect_end_ms - connect_start_ms; |
| int64_t request_start_to_send_start_ms = send_start_ms - request_start_ms; |
| int64_t send_start_to_receive_headers_end_ms = |
| receive_headers_end_ms - send_start_ms; |
| int64_t request_start_to_receive_headers_end_ms = |
| receive_headers_end_ms - request_start_ms; |
| |
| builder->SetMainFrameResource_DNSDelay(dns_duration_ms); |
| builder->SetMainFrameResource_ConnectDelay(connect_duration_ms); |
| if (request_start_to_send_start_ms >= 0) { |
| builder->SetMainFrameResource_RequestStartToSendStart( |
| request_start_to_send_start_ms); |
| } |
| if (send_start_to_receive_headers_end_ms >= 0) { |
| builder->SetMainFrameResource_SendStartToReceiveHeadersEnd( |
| send_start_to_receive_headers_end_ms); |
| } |
| builder->SetMainFrameResource_RequestStartToReceiveHeadersEnd( |
| request_start_to_receive_headers_end_ms); |
| |
| if (!main_frame_timing_->request_start.is_null() && |
| !GetDelegate().GetNavigationStart().is_null()) { |
| base::TimeDelta navigation_start_to_request_start = |
| main_frame_timing_->request_start - GetDelegate().GetNavigationStart(); |
| |
| builder->SetMainFrameResource_NavigationStartToRequestStart( |
| navigation_start_to_request_start.InMilliseconds()); |
| } |
| |
| if (!main_frame_timing_->receive_headers_start.is_null() && |
| !GetDelegate().GetNavigationStart().is_null()) { |
| base::TimeDelta navigation_start_to_receive_headers_start = |
| main_frame_timing_->receive_headers_start - |
| GetDelegate().GetNavigationStart(); |
| builder->SetMainFrameResource_NavigationStartToReceiveHeadersStart( |
| navigation_start_to_receive_headers_start.InMilliseconds()); |
| } |
| |
| if (connection_info_.has_value()) { |
| page_load_metrics::NetworkProtocol protocol = |
| page_load_metrics::GetNetworkProtocol(*connection_info_); |
| if (IsSupportedProtocol(protocol)) { |
| builder->SetMainFrameResource_HttpProtocolScheme( |
| static_cast<int>(protocol)); |
| } |
| } |
| |
| if (main_frame_request_redirect_count_ > 0) { |
| builder->SetMainFrameResource_RedirectCount( |
| main_frame_request_redirect_count_); |
| } |
| } |
| |
| void UkmPageLoadMetricsObserver::ReportLayoutStability() { |
| ukm::builders::PageLoad(GetDelegate().GetSourceId()) |
| .SetLayoutInstability_CumulativeShiftScore( |
| page_load_metrics::LayoutShiftUkmValue( |
| GetDelegate().GetPageRenderData().layout_shift_score)) |
| .SetLayoutInstability_CumulativeShiftScore_MainFrame( |
| page_load_metrics::LayoutShiftUkmValue( |
| GetDelegate().GetMainFrameRenderData().layout_shift_score)) |
| .SetLayoutInstability_CumulativeShiftScore_MainFrame_BeforeInputOrScroll( |
| page_load_metrics::LayoutShiftUkmValue( |
| GetDelegate() |
| .GetMainFrameRenderData() |
| .layout_shift_score_before_input_or_scroll)) |
| .Record(ukm::UkmRecorder::Get()); |
| |
| // TODO(crbug.com/1064483): We should move UMA recording to components/ |
| |
| UMA_HISTOGRAM_COUNTS_100( |
| "PageLoad.LayoutInstability.CumulativeShiftScore", |
| page_load_metrics::LayoutShiftUmaValue( |
| GetDelegate().GetPageRenderData().layout_shift_score)); |
| |
| TRACE_EVENT_INSTANT1("loading", "CumulativeShiftScore::AllFrames::UMA", |
| TRACE_EVENT_SCOPE_THREAD, "data", |
| CumulativeShiftScoreTraceData( |
| GetDelegate().GetPageRenderData().layout_shift_score, |
| GetDelegate() |
| .GetPageRenderData() |
| .layout_shift_score_before_input_or_scroll)); |
| |
| UMA_HISTOGRAM_COUNTS_100( |
| "PageLoad.LayoutInstability.CumulativeShiftScore.MainFrame", |
| page_load_metrics::LayoutShiftUmaValue( |
| GetDelegate().GetMainFrameRenderData().layout_shift_score)); |
| |
| // Note: This depends on PageLoadMetrics internally processing loading |
| // behavior before timing metrics if they come in the same IPC update. |
| if (font_preload_started_before_rendering_observed_) { |
| UMA_HISTOGRAM_COUNTS_100( |
| "PageLoad.Clients.FontPreload.LayoutInstability.CumulativeShiftScore", |
| page_load_metrics::LayoutShiftUmaValue( |
| GetDelegate().GetPageRenderData().layout_shift_score)); |
| } |
| } |
| |
| void UkmPageLoadMetricsObserver::ReportAbortMetrics( |
| const page_load_metrics::mojom::PageLoadTiming& timing, |
| base::TimeTicks page_end_time) { |
| PageLoadType page_load_type = PageLoadType::kNeverForegrounded; |
| if (page_load_metrics::WasInForeground(GetDelegate())) { |
| page_load_type = timing.paint_timing->first_contentful_paint.has_value() |
| ? PageLoadType::kReachedFCP |
| : PageLoadType::kAborted; |
| } |
| if (currently_in_foreground_ && !last_time_shown_.is_null()) { |
| total_foreground_duration_ += page_end_time - last_time_shown_; |
| } |
| UMA_HISTOGRAM_ENUMERATION("PageLoad.Experimental.PageLoadType", |
| page_load_type); |
| PAGE_LOAD_LONG_HISTOGRAM("PageLoad.Experimental.TotalForegroundDuration", |
| total_foreground_duration_); |
| ukm::builders::PageLoad(GetDelegate().GetSourceId()) |
| .SetExperimental_PageLoadType(static_cast<int>(page_load_type)) |
| .SetExperimental_TotalForegroundDuration( |
| ukm::GetExponentialBucketMinForUserTiming( |
| total_foreground_duration_.InMilliseconds())) |
| .Record(ukm::UkmRecorder::Get()); |
| } |
| |
| void UkmPageLoadMetricsObserver::RecordInputTimingMetrics() { |
| ukm::builders::PageLoad(GetDelegate().GetSourceId()) |
| .SetInteractiveTiming_NumInputEvents( |
| GetDelegate().GetPageInputTiming().num_input_events) |
| .SetInteractiveTiming_TotalInputDelay( |
| GetDelegate().GetPageInputTiming().total_input_delay.InMilliseconds()) |
| .SetInteractiveTiming_TotalAdjustedInputDelay( |
| GetDelegate() |
| .GetPageInputTiming() |
| .total_adjusted_input_delay.InMilliseconds()) |
| .Record(ukm::UkmRecorder::Get()); |
| } |
| |
| base::Optional<int64_t> |
| UkmPageLoadMetricsObserver::GetRoundedSiteEngagementScore() const { |
| if (!browser_context_) |
| return base::nullopt; |
| |
| Profile* profile = Profile::FromBrowserContext(browser_context_); |
| SiteEngagementService* engagement_service = |
| SiteEngagementService::Get(profile); |
| |
| // UKM privacy requires the engagement score be rounded to nearest |
| // value of 10. |
| int64_t rounded_document_engagement_score = |
| static_cast<int>(std::roundf( |
| engagement_service->GetScore(GetDelegate().GetUrl()) / 10.0)) * |
| 10; |
| |
| DCHECK(rounded_document_engagement_score >= 0 && |
| rounded_document_engagement_score <= |
| engagement_service->GetMaxPoints()); |
| |
| return rounded_document_engagement_score; |
| } |
| |
| base::Optional<bool> |
| UkmPageLoadMetricsObserver::GetThirdPartyCookieBlockingEnabled() const { |
| if (!browser_context_) |
| return base::nullopt; |
| |
| Profile* profile = Profile::FromBrowserContext(browser_context_); |
| auto cookie_settings = CookieSettingsFactory::GetForProfile(profile); |
| if (!cookie_settings->IsCookieControlsEnabled()) |
| return base::nullopt; |
| |
| return !cookie_settings->IsThirdPartyAccessAllowed(GetDelegate().GetUrl(), |
| nullptr /* source */); |
| } |
| |
| void UkmPageLoadMetricsObserver::OnTimingUpdate( |
| content::RenderFrameHost* subframe_rfh, |
| const page_load_metrics::mojom::PageLoadTiming& timing) { |
| bool loading_enabled; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED("loading", &loading_enabled); |
| if (!loading_enabled) |
| return; |
| const page_load_metrics::ContentfulPaintTimingInfo& paint = |
| GetDelegate() |
| .GetLargestContentfulPaintHandler() |
| .MergeMainFrameAndSubframes(); |
| |
| if (paint.ContainsValidTime()) { |
| TRACE_EVENT_INSTANT2( |
| "loading", |
| "NavStartToLargestContentfulPaint::Candidate::AllFrames::UKM", |
| TRACE_EVENT_SCOPE_THREAD, "data", paint.DataAsTraceValue(), |
| "main_frame_tree_node_id", |
| GetDelegate().GetLargestContentfulPaintHandler().MainFrameTreeNodeId()); |
| } else { |
| TRACE_EVENT_INSTANT1( |
| "loading", |
| "NavStartToLargestContentfulPaint::" |
| "Invalidate::AllFrames::UKM", |
| TRACE_EVENT_SCOPE_THREAD, "main_frame_tree_node_id", |
| GetDelegate().GetLargestContentfulPaintHandler().MainFrameTreeNodeId()); |
| } |
| |
| const page_load_metrics::ContentfulPaintTimingInfo& |
| experimental_largest_contentful_paint = |
| GetDelegate() |
| .GetExperimentalLargestContentfulPaintHandler() |
| .MergeMainFrameAndSubframes(); |
| if (experimental_largest_contentful_paint.ContainsValidTime()) { |
| TRACE_EVENT_INSTANT2( |
| "loading", |
| "NavStartToExperimentalLargestContentfulPaint::Candidate::AllFrames::" |
| "UKM", |
| TRACE_EVENT_SCOPE_THREAD, "data", |
| experimental_largest_contentful_paint.DataAsTraceValue(), |
| "main_frame_tree_node_id", |
| GetDelegate() |
| .GetExperimentalLargestContentfulPaintHandler() |
| .MainFrameTreeNodeId()); |
| } else { |
| TRACE_EVENT_INSTANT1("loading", |
| "NavStartToExperimentalLargestContentfulPaint::" |
| "Invalidate::AllFrames::UKM", |
| TRACE_EVENT_SCOPE_THREAD, "main_frame_tree_node_id", |
| GetDelegate() |
| .GetExperimentalLargestContentfulPaintHandler() |
| .MainFrameTreeNodeId()); |
| } |
| } |
| |
| void UkmPageLoadMetricsObserver::OnThroughputUpdate( |
| const page_load_metrics::mojom::ThroughputUkmDataPtr& throughput_data) { |
| ukm::SourceId source_id = throughput_data->source_id; |
| DCHECK_NE(source_id, ukm::kInvalidSourceId); |
| |
| int8_t aggregated_throughput_percent = |
| throughput_data->aggregated_throughput_percent; |
| int8_t impl_throughput_percent = throughput_data->impl_throughput_percent; |
| page_load_metrics::mojom::PercentOptionalPtr main_throughput_percent = |
| std::move(throughput_data->main_throughput_percent); |
| |
| throughput_source_id_ = source_id; |
| if (!ValidatePercent(aggregated_throughput_percent) || |
| !ValidatePercent(impl_throughput_percent) || |
| (main_throughput_percent && |
| !ValidatePercent(main_throughput_percent->percent))) { |
| mojo::ReportBadMessage("Invalid percentage value in ThroughputUkmData."); |
| return; |
| } |
| ++aggregated_throughput_data_[aggregated_throughput_percent]; |
| ++impl_throughput_data_[impl_throughput_percent]; |
| if (main_throughput_percent) |
| ++main_throughput_data_[main_throughput_percent->percent]; |
| } |
| |
| void UkmPageLoadMetricsObserver::ReportThroughputUkm() { |
| DCHECK_NE(throughput_source_id_, ukm::kInvalidSourceId); |
| |
| ukm::builders::Graphics_Smoothness_PercentDroppedFrames builder( |
| throughput_source_id_); |
| if (aggregated_throughput_data_.size() > 0) { |
| builder.SetSlowerThread_Universal( |
| ComputeMedianForThroughput(aggregated_throughput_data_)); |
| aggregated_throughput_data_.clear(); |
| } |
| if (impl_throughput_data_.size() > 0) { |
| builder.SetCompositorThread_Universal( |
| ComputeMedianForThroughput(impl_throughput_data_)); |
| impl_throughput_data_.clear(); |
| } |
| if (main_throughput_data_.size() > 0) { |
| builder.SetMainThread_Universal( |
| ComputeMedianForThroughput(main_throughput_data_)); |
| main_throughput_data_.clear(); |
| } |
| builder.Record(ukm::UkmRecorder::Get()); |
| } |
| |
| void UkmPageLoadMetricsObserver::OnCpuTimingUpdate( |
| content::RenderFrameHost* subframe_rfh, |
| const page_load_metrics::mojom::CpuTiming& timing) { |
| if (GetDelegate().GetVisibilityTracker().currently_in_foreground()) |
| total_foreground_cpu_time_ += timing.task_time; |
| } |
| |
| void UkmPageLoadMetricsObserver::DidActivatePortal( |
| base::TimeTicks activation_time) { |
| is_portal_ = false; |
| } |
| |
| void UkmPageLoadMetricsObserver::RecordNoStatePrefetchMetrics( |
| content::NavigationHandle* navigation_handle, |
| ukm::SourceId source_id) { |
| prerender::PrerenderManager* const prerender_manager = |
| prerender::PrerenderManagerFactory::GetForBrowserContext( |
| navigation_handle->GetWebContents()->GetBrowserContext()); |
| if (!prerender_manager) |
| return; |
| |
| const std::vector<GURL>& redirects = navigation_handle->GetRedirectChain(); |
| |
| base::TimeDelta prefetch_age; |
| prerender::FinalStatus final_status; |
| prerender::Origin prefetch_origin; |
| |
| bool nostate_prefetch_entry_found = prerender_manager->GetPrefetchInformation( |
| navigation_handle->GetURL(), &prefetch_age, &final_status, |
| &prefetch_origin); |
| |
| // Try the URLs from the redirect chain. |
| if (!nostate_prefetch_entry_found) { |
| for (const auto& url : redirects) { |
| nostate_prefetch_entry_found = prerender_manager->GetPrefetchInformation( |
| url, &prefetch_age, &final_status, &prefetch_origin); |
| if (nostate_prefetch_entry_found) |
| break; |
| } |
| } |
| |
| if (!nostate_prefetch_entry_found) |
| return; |
| |
| ukm::builders::NoStatePrefetch builder(source_id); |
| builder.SetPrefetchedRecently_PrefetchAge( |
| ukm::GetExponentialBucketMinForUserTiming(prefetch_age.InMilliseconds())); |
| builder.SetPrefetchedRecently_FinalStatus(final_status); |
| builder.SetPrefetchedRecently_Origin(prefetch_origin); |
| builder.Record(ukm::UkmRecorder::Get()); |
| } |
| |
| bool UkmPageLoadMetricsObserver::IsOfflinePreview( |
| content::WebContents* web_contents) const { |
| #if BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| offline_pages::OfflinePageTabHelper* tab_helper = |
| offline_pages::OfflinePageTabHelper::FromWebContents(web_contents); |
| return tab_helper && tab_helper->GetOfflinePreviewItem(); |
| #else |
| return false; |
| #endif |
| } |
| |
| void UkmPageLoadMetricsObserver::RecordGeneratedNavigationUKM( |
| ukm::SourceId source_id, |
| const GURL& committed_url) { |
| bool final_url_is_home_page = IsUserHomePage(browser_context_, committed_url); |
| bool final_url_is_default_search = |
| IsDefaultSearchEngine(browser_context_, committed_url); |
| |
| if (!final_url_is_home_page && !final_url_is_default_search && |
| !start_url_is_home_page_ && !start_url_is_default_search_) { |
| return; |
| } |
| |
| ukm::builders::GeneratedNavigation builder(source_id); |
| builder.SetFinalURLIsHomePage(final_url_is_home_page); |
| builder.SetFinalURLIsDefaultSearchEngine(final_url_is_default_search); |
| builder.SetFirstURLIsHomePage(start_url_is_home_page_); |
| builder.SetFirstURLIsDefaultSearchEngine(start_url_is_default_search_); |
| builder.Record(ukm::UkmRecorder::Get()); |
| } |
| |
| void UkmPageLoadMetricsObserver::OnLoadingBehaviorObserved( |
| content::RenderFrameHost* rfh, |
| int behavior_flag) { |
| if (behavior_flag & blink::LoadingBehaviorFlag:: |
| kLoadingBehaviorFontPreloadStartedBeforeRendering) { |
| font_preload_started_before_rendering_observed_ = true; |
| } |
| } |