blob: 8d8ba101cf211f2538b501e6fa249962af18d0a0 [file] [log] [blame]
// 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_final_status.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/prerender/prerender_manager_factory.h"
#include "chrome/browser/prerender/prerender_origin.h"
#include "chrome/browser/profiles/profile.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/page_load_metrics_util.h"
#include "components/page_load_metrics/browser/protocol_util.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.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/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;
}
}
int64_t LayoutShiftUkmValue(float shift_score) {
// Report (shift_score * 100) as an int in the range [0, 1000].
return static_cast<int>(roundf(std::min(shift_score, 10.0f) * 100.0f));
}
int32_t LayoutShiftUmaValue(float shift_score) {
// Report (shift_score * 10) as an int in the range [0, 100].
return static_cast<int>(roundf(std::min(shift_score, 10.0f) * 10.0f));
}
} // 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),
largest_contentful_paint_handler_() {
DCHECK(network_quality_tracker_);
}
UkmPageLoadMetricsObserver::~UkmPageLoadMetricsObserver() = default;
UkmPageLoadMetricsObserver::ObservePolicy UkmPageLoadMetricsObserver::OnStart(
content::NavigationHandle* navigation_handle,
const GURL& currently_committed_url,
bool started_in_foreground) {
if (!started_in_foreground) {
was_hidden_ = true;
return CONTINUE_OBSERVING;
}
browser_context_ = navigation_handle->GetWebContents()->GetBrowserContext();
// 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->IsInMainFrame()) {
largest_contentful_paint_handler_.RecordMainFrameTreeNodeId(
navigation_handle->GetFrameTreeNodeId());
}
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();
is_signed_exchange_inner_response_ =
navigation_handle->IsSignedExchangeInnerResponse();
RecordNoStatePrefetchMetrics(navigation_handle, source_id);
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 (!was_hidden_) {
RecordPageLoadMetrics(base::TimeTicks::Now());
RecordTimingMetrics(timing);
}
ReportLayoutStability();
return STOP_OBSERVING;
}
UkmPageLoadMetricsObserver::ObservePolicy UkmPageLoadMetricsObserver::OnHidden(
const page_load_metrics::mojom::PageLoadTiming& timing) {
if (!was_hidden_) {
RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */);
RecordTimingMetrics(timing);
was_hidden_ = true;
}
return CONTINUE_OBSERVING;
}
void UkmPageLoadMetricsObserver::OnFailedProvisionalLoad(
const page_load_metrics::FailedProvisionalLoadInfo& failed_load_info) {
if (was_hidden_)
return;
RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */);
// 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 (!was_hidden_) {
RecordPageLoadMetrics(base::TimeTicks() /* no app_background_time */);
RecordTimingMetrics(timing);
}
ReportLayoutStability();
}
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 (resource->is_complete &&
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.resource_type ==
content::ResourceType::kMainFrame) {
DCHECK(!main_frame_timing_.has_value());
main_frame_timing_ = *extra_request_complete_info.load_timing_info;
}
}
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 =
largest_contentful_paint_handler_.MainFrameLargestContentfulPaint();
if (!main_frame_largest_contentful_paint.IsEmpty() &&
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 =
largest_contentful_paint_handler_.MergeMainFrameAndSubframes();
if (!all_frames_largest_contentful_paint.IsEmpty() &&
WasStartedInForegroundOptionalEventInForeground(
all_frames_largest_contentful_paint.Time(), GetDelegate())) {
builder.SetPaintTiming_NavigationToLargestContentfulPaint(
all_frames_largest_contentful_paint.Time().value().InMilliseconds());
}
if (timing.interactive_timing->interactive) {
base::TimeDelta time_to_interactive =
timing.interactive_timing->interactive.value();
if (!timing.interactive_timing->first_invalidating_input ||
timing.interactive_timing->first_invalidating_input.value() >
time_to_interactive) {
builder.SetExperimental_NavigationToInteractive(
time_to_interactive.InMilliseconds());
}
}
if (timing.interactive_timing->first_input_delay) {
base::TimeDelta first_input_delay =
timing.interactive_timing->first_input_delay.value();
builder.SetInteractiveTiming_FirstInputDelay_SkipFilteringComparison(
first_input_delay.InMilliseconds());
if (base::FeatureList::IsEnabled(features::kSkipTouchEventFilter)) {
// This experiment will change the FID and first input metric by
// changing the timestamp on pointerdown events on mobile pages with no
// pointer event handlers. If it is ramped up to 100% to launch, we need
// to update the metric name (v3->v4).
builder.SetInteractiveTiming_FirstInputDelay4(
first_input_delay.InMilliseconds());
} else {
// If the SkipTouchEventFilter experiment does not launch, we want to
// continue reporting first input events under the current name.
builder.SetInteractiveTiming_FirstInputDelay3(
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_FirstInputTimestamp_SkipFilteringComparison(
first_input_timestamp.InMilliseconds());
if (base::FeatureList::IsEnabled(features::kSkipTouchEventFilter)) {
// This experiment will change the FID and first input metric by
// changing the timestamp on pointerdown events on mobile pages with no
// pointer event handlers. If it is ramped up to 100% to launch, we need
// to update the metric name (v3->v4).
builder.SetInteractiveTiming_FirstInputTimestamp4(
first_input_timestamp.InMilliseconds());
} else {
// If the SkipTouchEventFilter experiment does not launch, we want to
// continue reporting first input events under the current name.
builder.SetInteractiveTiming_FirstInputTimestamp3(
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());
}
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));
if (main_frame_timing_)
ReportMainResourceTimingMetrics(timing, &builder);
builder.Record(ukm::UkmRecorder::Get());
}
void UkmPageLoadMetricsObserver::RecordPageLoadMetrics(
base::TimeTicks app_background_time) {
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.
builder.SetNavigation_PageEndReason(
static_cast<int64_t>(GetDelegate().GetPageEndReason()));
if (GetDelegate().DidCommit() && was_cached_) {
builder.SetWasCached(1);
}
if (GetDelegate().DidCommit() && is_signed_exchange_inner_response_) {
builder.SetIsSignedExchangeInnerResponse(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(LayoutShiftUkmValue(
GetDelegate().GetPageRenderData().layout_shift_score))
.SetLayoutInstability_CumulativeShiftScore_MainFrame(LayoutShiftUkmValue(
GetDelegate().GetMainFrameRenderData().layout_shift_score))
.SetLayoutInstability_CumulativeShiftScore_MainFrame_BeforeInputOrScroll(
LayoutShiftUkmValue(GetDelegate()
.GetMainFrameRenderData()
.layout_shift_score_before_input_or_scroll))
.Record(ukm::UkmRecorder::Get());
UMA_HISTOGRAM_COUNTS_100(
"PageLoad.LayoutInstability.CumulativeShiftScore",
LayoutShiftUmaValue(
GetDelegate().GetPageRenderData().layout_shift_score));
UMA_HISTOGRAM_COUNTS_100(
"PageLoad.LayoutInstability.CumulativeShiftScore.MainFrame",
LayoutShiftUmaValue(
GetDelegate().GetMainFrameRenderData().layout_shift_score));
}
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());
}
void UkmPageLoadMetricsObserver::OnTimingUpdate(
content::RenderFrameHost* subframe_rfh,
const page_load_metrics::mojom::PageLoadTiming& timing) {
largest_contentful_paint_handler_.RecordTiming(timing.paint_timing,
subframe_rfh);
bool loading_enabled;
TRACE_EVENT_CATEGORY_GROUP_ENABLED("loading", &loading_enabled);
if (!loading_enabled)
return;
const page_load_metrics::ContentfulPaintTimingInfo& paint =
largest_contentful_paint_handler_.MergeMainFrameAndSubframes();
if (!paint.IsEmpty()) {
TRACE_EVENT_INSTANT2(
"loading",
"NavStartToLargestContentfulPaint::Candidate::AllFrames::UKM",
TRACE_EVENT_SCOPE_THREAD, "data", paint.DataAsTraceValue(),
"main_frame_tree_node_id",
largest_contentful_paint_handler_.MainFrameTreeNodeId());
} else {
TRACE_EVENT_INSTANT1(
"loading",
"NavStartToLargestContentfulPaint::"
"Invalidate::AllFrames::UKM",
TRACE_EVENT_SCOPE_THREAD, "main_frame_tree_node_id",
largest_contentful_paint_handler_.MainFrameTreeNodeId());
}
}
void UkmPageLoadMetricsObserver::OnDidFinishSubFrameNavigation(
content::NavigationHandle* navigation_handle) {
largest_contentful_paint_handler_.OnDidFinishSubFrameNavigation(
navigation_handle, GetDelegate());
}
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::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
}