blob: 9056d61df998eafaeeb2acf8b8ff517620a42421 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/back_forward_cache_metrics.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/metrics_hashes.h"
#include "base/metrics/sparse_histogram.h"
#include "base/strings/stringprintf.h"
#include "components/back_forward_cache/disabled_reason_id.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/renderer_host/back_forward_cache_impl.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_entry_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/should_swap_browsing_instance.h"
#include "content/browser/site_instance_impl.h"
#include "content/common/debug_utils.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/reload_type.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
#include "ui/accessibility/ax_event.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
// Overridden time for unit tests. Should be accessed only from the main thread.
base::TickClock* g_mock_time_clock_for_testing = nullptr;
// Reduce the resolution of the longer intervals due to privacy considerations.
base::TimeDelta ClampTime(base::TimeDelta time) {
if (time < base::Seconds(5))
return base::Milliseconds(time.InMilliseconds());
if (time < base::Minutes(3))
return base::Seconds(time.InSeconds());
if (time < base::Hours(3))
return base::Minutes(time.InMinutes());
return base::Hours(time.InHours());
}
base::TimeTicks Now() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (g_mock_time_clock_for_testing)
return g_mock_time_clock_for_testing->NowTicks();
return base::TimeTicks::Now();
}
} // namespace
// static
void BackForwardCacheMetrics::OverrideTimeForTesting(base::TickClock* clock) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
g_mock_time_clock_for_testing = clock;
}
// static
bool BackForwardCacheMetrics::IsCrossDocumentMainFrameHistoryNavigation(
NavigationRequest* navigation) {
return navigation->IsInPrimaryMainFrame() &&
!navigation->frame_tree_node()->GetParentOrOuterDocumentOrEmbedder() &&
!navigation->IsSameDocument() &&
navigation->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK;
}
// static
scoped_refptr<BackForwardCacheMetrics>
BackForwardCacheMetrics::CreateOrReuseBackForwardCacheMetricsForNavigation(
NavigationEntryImpl* previous_entry,
bool is_main_frame_navigation,
int64_t committing_document_sequence_number) {
if (!previous_entry) {
// There is no previous NavigationEntry, so we must create a new metrics
// object.
return base::WrapRefCounted(new BackForwardCacheMetrics(
is_main_frame_navigation ? committing_document_sequence_number : -1));
}
BackForwardCacheMetrics* previous_entry_metrics =
previous_entry->back_forward_cache_metrics();
if (!previous_entry_metrics) {
// It's possible to encounter a `previous_entry` without metrics, e.g. on
// session restore. We will have to create a new metrics object for the main
// document.
return base::WrapRefCounted(new BackForwardCacheMetrics(
is_main_frame_navigation
? committing_document_sequence_number
: previous_entry->root_node()
->frame_entry->document_sequence_number()));
}
// Reuse `previous_entry_metrics` on subframe navigations and same-document
// navigations.
if (!is_main_frame_navigation ||
committing_document_sequence_number ==
previous_entry_metrics->document_sequence_number_) {
return previous_entry_metrics;
}
return base::WrapRefCounted(
new BackForwardCacheMetrics(committing_document_sequence_number));
}
BackForwardCacheMetrics::BackForwardCacheMetrics(
int64_t document_sequence_number)
: document_sequence_number_(document_sequence_number),
page_store_result_(
std::make_unique<BackForwardCacheCanStoreDocumentResult>()) {}
BackForwardCacheMetrics::~BackForwardCacheMetrics() = default;
void BackForwardCacheMetrics::MainFrameDidStartNavigationToDocument() {
if (!started_navigation_timestamp_)
started_navigation_timestamp_ = Now();
}
void BackForwardCacheMetrics::DidCommitNavigation(
NavigationRequest* navigation,
bool back_forward_cache_allowed) {
// Back-forward cache in enabled only for primary frame trees, so we need to
// record metrics only for primary main frame navigations.
if (!navigation->IsInPrimaryMainFrame() || navigation->IsSameDocument())
return;
// Record metrics for history navigation, if applicable.
if (IsCrossDocumentMainFrameHistoryNavigation(navigation)) {
// We have to update not restored reasons even though we already did in
// |SendCommitNavigation()|, because the NavigationEntry and
// the BackForwardCacheMetrics object might not exist anymore, e.g. when the
// NavigationEntry got pruned by another navigation committing before the
// history navigation committed.
UpdateNotRestoredReasonsForNavigation(navigation);
bool can_restore = page_store_result_->CanRestore();
bool did_store = navigation->IsServedFromBackForwardCache();
DCHECK_EQ(can_restore, did_store) << page_store_result_->ToString();
// If a navigation serves the result from back/forward cache, then it must
// not have logged any NotRestoredReasons. Also if it is not restored from
// back/forward cache, the logged reasons must match the actual condition of
// the navigation and other logged data.
bool served_from_bfcache_not_match =
did_store && !page_store_result_->not_restored_reasons().empty();
bool browsing_instance_not_swapped_not_match =
page_store_result_->HasNotRestoredReason(
NotRestoredReason::kBrowsingInstanceNotSwapped) &&
DidSwapBrowsingInstance();
bool disable_for_rfh_not_match =
page_store_result_->HasNotRestoredReason(
NotRestoredReason::kDisableForRenderFrameHostCalled) &&
page_store_result_->disabled_reasons().size() == 0;
bool blocklisted_features_not_match =
page_store_result_->HasNotRestoredReason(
NotRestoredReason::kBlocklistedFeatures) &&
page_store_result_->blocklisted_features().empty();
if (served_from_bfcache_not_match ||
browsing_instance_not_swapped_not_match || disable_for_rfh_not_match ||
blocklisted_features_not_match) {
CaptureTraceForNavigationDebugScenario(
DebugScenario::kDebugBackForwardCacheMetricsMismatch);
}
// TODO(crbug.com/40229455): Remove this.
if (served_from_bfcache_not_match) {
SCOPED_CRASH_KEY_BOOL("BFCacheMismatch", "did_store", did_store);
SCOPED_CRASH_KEY_BOOL("BFCacheMismatch", "can_restore", can_restore);
SCOPED_CRASH_KEY_NUMBER("BFCacheMismatch", "not_restored",
page_store_result_->not_restored_reasons()
.GetNth64bitWordBitmask(0)
.value());
auto not_restored_1 =
page_store_result_->not_restored_reasons().GetNth64bitWordBitmask(1);
if (not_restored_1.has_value()) {
SCOPED_CRASH_KEY_NUMBER("BFCacheMismatch", "not_restored_1",
not_restored_1.value());
}
SCOPED_CRASH_KEY_NUMBER(
"BFCacheMismatch", "bi_swap",
page_store_result_->browsing_instance_swap_result().has_value()
? static_cast<int>(
page_store_result_->browsing_instance_swap_result().value())
: -1);
SCOPED_CRASH_KEY_NUMBER("BFCacheMismatch", "blocklisted",
page_store_result_->blocklisted_features()
.GetNth64bitWordBitmask(0)
.value());
auto blocklisted_1 =
page_store_result_->blocklisted_features().GetNth64bitWordBitmask(1);
if (blocklisted_1.has_value()) {
SCOPED_CRASH_KEY_NUMBER("BFCacheMismatch", "blocklisted_1",
blocklisted_1.value());
}
SCOPED_CRASH_KEY_NUMBER("BFCacheMismatch", "disabled",
page_store_result_->disabled_reasons().size());
SCOPED_CRASH_KEY_NUMBER(
"BFCacheMismatch", "disallow_activation",
page_store_result_->disallow_activation_reasons().size());
SCOPED_CRASH_KEY_NUMBER("BFCacheMismatch", "restore_type",
static_cast<int>(navigation->GetRestoreType()));
SCOPED_CRASH_KEY_NUMBER("BFCacheMismatch", "reload_type",
static_cast<int>(navigation->GetReloadType()));
SCOPED_CRASH_KEY_STRING256("BFCacheMismatch", "url",
navigation->GetURL().spec());
SCOPED_CRASH_KEY_STRING256(
"BFCacheMismatch", "previous_url",
navigation->GetPreviousPrimaryMainFrameURL().spec());
// TODO(https://crbug.com/40229455): Remove this and the debugging above.
base::debug::DumpWithoutCrashing();
}
TRACE_EVENT1("navigation", "HistoryNavigationOutcome", "outcome",
page_store_result_->ToString());
RecordHistoryNavigationUMA(navigation, back_forward_cache_allowed);
RecordHistoryNavigationUKM(navigation);
if (!navigation->IsServedFromBackForwardCache()) {
devtools_instrumentation::BackForwardCacheNotUsed(
navigation, page_store_result_.get(), page_store_tree_result_.get());
}
if (test_observer_) {
// This is for reporting |page_store_tree_result_| for testing.
test_observer_->NotifyNotRestoredReasons(
std::move(page_store_tree_result_));
}
}
// Save the information about the last cross-document main frame navigation
// that uses this metrics object.
last_committed_cross_document_main_frame_navigation_id_ =
navigation->GetNavigationId();
// BackForwardCacheMetrics can be reused in some cases. Reset fields for UKM
// for the next navigation.
page_store_result_ =
std::make_unique<BackForwardCacheCanStoreDocumentResult>();
page_store_tree_result_ = nullptr;
navigated_away_from_main_document_timestamp_ = std::nullopt;
started_navigation_timestamp_ = std::nullopt;
renderer_killed_timestamp_ = std::nullopt;
browsing_instance_swap_result_ = std::nullopt;
}
namespace {
void RecordDisabledForRenderFrameHostReasonUKM(ukm::SourceId source_id,
uint64_t reason) {
ukm::builders::BackForwardCacheDisabledForRenderFrameHostReason(source_id)
.SetReason2(reason)
.Record(ukm::UkmRecorder::Get());
}
} // namespace
void BackForwardCacheMetrics::RecordHistoryNavigationUKM(
NavigationRequest* navigation) {
DCHECK(IsCrossDocumentMainFrameHistoryNavigation(navigation));
// We've visited an entry associated with this main frame document before,
// so record metrics to determine whether it might be a back-forward cache
// hit.
ukm::SourceId source_id = ukm::ConvertToSourceId(
navigation->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
ukm::builders::HistoryNavigation builder(source_id);
if (last_committed_cross_document_main_frame_navigation_id_ != -1) {
// Only record `last_committed_cross_document_main_frame_navigation_id_`
// when it's set. It won't be set if the NavigationEntry this history
// navigation is targeting hasn't been navigated to in this session (e.g.
// due to session restore or cloning a tab).
builder.SetLastCommittedCrossDocumentNavigationSourceIdForTheSameDocument(
ukm::ConvertToSourceId(
last_committed_cross_document_main_frame_navigation_id_,
ukm::SourceIdType::NAVIGATION_ID));
}
builder.SetMainFrameFeatures(
main_frame_features_.GetNth64bitWordBitmask(0).value());
auto main_frame_features_2 = main_frame_features_.GetNth64bitWordBitmask(1);
if (main_frame_features_2.has_value()) {
builder.SetMainFrameFeatures2(main_frame_features_2.value());
}
builder.SetSameOriginSubframesFeatures(
same_origin_frames_features_.GetNth64bitWordBitmask(0).value());
auto same_origin_frames_features_2 =
same_origin_frames_features_.GetNth64bitWordBitmask(1);
if (same_origin_frames_features_2.has_value()) {
builder.SetSameOriginSubframesFeatures2(
same_origin_frames_features_2.value());
}
builder.SetCrossOriginSubframesFeatures(
cross_origin_frames_features_.GetNth64bitWordBitmask(0).value());
auto cross_origin_frames_features_2 =
cross_origin_frames_features_.GetNth64bitWordBitmask(1);
if (cross_origin_frames_features_2.has_value()) {
builder.SetCrossOriginSubframesFeatures2(
cross_origin_frames_features_2.value());
}
// DidStart notification might be missing for some same-document
// navigations. It's good that we don't care about the time in the cache
// in that case.
if (started_navigation_timestamp_ &&
navigated_away_from_main_document_timestamp_) {
builder.SetTimeSinceNavigatedAwayFromDocument(
ClampTime(started_navigation_timestamp_.value() -
navigated_away_from_main_document_timestamp_.value())
.InMilliseconds());
}
builder.SetBackForwardCache_IsServedFromBackForwardCache(
navigation->IsServedFromBackForwardCache());
builder.SetBackForwardCache_NotRestoredReasons(
page_store_result_->not_restored_reasons()
.GetNth64bitWordBitmask(0)
.value());
auto not_restored_reasons_2 =
page_store_result_->not_restored_reasons().GetNth64bitWordBitmask(1);
if (not_restored_reasons_2.has_value()) {
builder.SetBackForwardCache_NotRestoredReasons2(
not_restored_reasons_2.value());
}
builder.SetBackForwardCache_BlocklistedFeatures(
page_store_result_->blocklisted_features()
.GetNth64bitWordBitmask(0)
.value());
auto blocklisted_features_2 =
page_store_result_->blocklisted_features().GetNth64bitWordBitmask(1);
if (blocklisted_features_2.has_value()) {
builder.SetBackForwardCache_BlocklistedFeatures2(
blocklisted_features_2.value());
}
if (browsing_instance_swap_result_) {
builder.SetBackForwardCache_BrowsingInstanceNotSwappedReason(
static_cast<int64_t>(browsing_instance_swap_result_.value()));
}
builder.SetBackForwardCache_DisabledForRenderFrameHostReasonCount(
page_store_result_->disabled_reasons().size());
builder.Record(ukm::UkmRecorder::Get());
bool is_disabled_for_extension_messaging = false;
std::string blocking_extension_id;
for (const auto& [reason, associated_source_ids] :
page_store_result_->disabled_reasons()) {
uint64_t reason_value = MetricValue(reason);
// We always record the event under the source id that was obtained from
// the navigation.
RecordDisabledForRenderFrameHostReasonUKM(source_id, reason_value);
if (!is_disabled_for_extension_messaging &&
reason.id == static_cast<BackForwardCache::DisabledReasonType>(
back_forward_cache::DisabledReasonId::
kExtensionSentMessageToCachedFrame)) {
// Only the first extension (ideally, there should be only one)
// that triggers `kExtensionSentMessageToCachedFrame` will be recorded in
// the message.
is_disabled_for_extension_messaging = true;
blocking_extension_id = reason.context;
}
for (const auto& associated_source_id : associated_source_ids) {
if (associated_source_id.has_value()) {
RecordDisabledForRenderFrameHostReasonUKM(associated_source_id.value(),
reason_value);
}
}
}
if (is_disabled_for_extension_messaging) {
navigation->GetRenderFrameHost()->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kWarning,
base::StringPrintf(
"This page was not restored from back/forward cache because a "
"content script from the extension with ID %s received a message "
"while the page was cached. This behavior will change shortly "
"which may break the extension. If you are the developer of the "
"extension, see "
"https://developer.chrome.com/blog/"
"bfcache-extension-messaging-changes.",
blocking_extension_id.c_str()));
}
for (const uint64_t reason :
page_store_result_->disallow_activation_reasons()) {
ukm::builders::BackForwardCacheDisallowActivationReason reason_builder(
source_id);
reason_builder.SetReason(reason);
reason_builder.Record(ukm::UkmRecorder::Get());
}
}
void BackForwardCacheMetrics::MainFrameDidNavigateAwayFromDocument() {
// MainFrameDidNavigateAwayFromDocument is called when we commit a navigation
// to another main frame document and the current document loses its "last
// committed" status.
navigated_away_from_main_document_timestamp_ = Now();
}
void BackForwardCacheMetrics::RecordFeatureUsage(
RenderFrameHostImpl* main_frame) {
DCHECK(!main_frame->GetParent());
main_frame_features_.Clear();
same_origin_frames_features_.Clear();
cross_origin_frames_features_.Clear();
CollectFeatureUsageFromSubtree(main_frame,
main_frame->GetLastCommittedOrigin());
}
void BackForwardCacheMetrics::CollectFeatureUsageFromSubtree(
RenderFrameHostImpl* rfh,
const url::Origin& main_frame_origin) {
blink::scheduler::WebSchedulerTrackedFeatures features =
rfh->GetBackForwardCacheDisablingFeatures();
if (!rfh->GetParent()) {
main_frame_features_.PutAll(features);
} else if (rfh->GetLastCommittedOrigin().IsSameOriginWith(
main_frame_origin)) {
same_origin_frames_features_.PutAll(features);
} else {
cross_origin_frames_features_.PutAll(features);
}
for (size_t i = 0; i < rfh->child_count(); ++i) {
CollectFeatureUsageFromSubtree(rfh->child_at(i)->current_frame_host(),
main_frame_origin);
}
}
void BackForwardCacheMetrics::AddNotRestoredFlattenedReasonsToExistingResult(
BackForwardCacheCanStoreDocumentResult& flattened) {
page_store_result_->AddReasonsFrom(flattened);
const BackForwardCacheCanStoreDocumentResult::NotRestoredReasons&
not_restored_reasons = flattened.not_restored_reasons();
if (not_restored_reasons.Has(NotRestoredReason::kRendererProcessKilled)) {
renderer_killed_timestamp_ = Now();
}
}
void BackForwardCacheMetrics::SetNotRestoredReasons(
BackForwardCacheCanStoreDocumentResultWithTree& can_store) {
DCHECK(can_store.tree_reasons->FlattenTree() == can_store.flattened_reasons);
page_store_tree_result_ = std::move(can_store.tree_reasons);
AddNotRestoredFlattenedReasonsToExistingResult(can_store.flattened_reasons);
}
blink::mojom::BackForwardCacheNotRestoredReasonsPtr
BackForwardCacheMetrics::GetWebExposedNotRestoredReasons() {
return page_store_tree_result_->GetWebExposedNotRestoredReasons();
}
void BackForwardCacheMetrics::UpdateNotRestoredReasonsForNavigation(
NavigationRequest* navigation) {
DCHECK(IsCrossDocumentMainFrameHistoryNavigation(navigation));
BackForwardCacheCanStoreDocumentResult new_blocking_reasons;
// |last_committed_cross_document_main_frame_navigation_id_| is -1 even though
// this is a history navigation. This can happen only when the session history
// has been restored, as the NavigationEntry will exist and can be navigated
// to, but the BackForwardCacheMetrics object is brand new (as it's not
// persisted and restored).
if (last_committed_cross_document_main_frame_navigation_id_ == -1) {
new_blocking_reasons.No(NotRestoredReason::kSessionRestored);
}
// TODO(rakina): Remove this call from here and move it to
// |SetNotRestoredReasons()| that is called from |UnloadOldFrame()|.
if (!DidSwapBrowsingInstance()) {
new_blocking_reasons.No(NotRestoredReason::kBrowsingInstanceNotSwapped);
}
// This should not happen, but record this as an 'unknown' reason just in
// case.
if (page_store_result_->not_restored_reasons().empty() &&
new_blocking_reasons.not_restored_reasons().empty() &&
!navigation->IsServedFromBackForwardCache()) {
// TODO(altimin): Add a (D)CHECK here, but this code is reached in
// unittests.
new_blocking_reasons.No(NotRestoredReason::kUnknown);
}
page_store_result_->AddReasonsFrom(new_blocking_reasons);
// Initialize the empty tree result if nothing is set.
if (!page_store_tree_result_) {
page_store_tree_result_ =
BackForwardCacheCanStoreTreeResult::CreateEmptyTreeForNavigation(
navigation);
}
// Add the same reason to the root node of the tree once we update the
// flattened list of reasons.
page_store_tree_result_->AddReasonsToSubtreeRootFrom(new_blocking_reasons);
TRACE_EVENT("navigation",
"BackForwardCacheMetrics::UpdateNotRestoredReasonsForNavigation",
ChromeTrackEvent::kBackForwardCacheCanStoreDocumentResult,
*(page_store_result_.get()));
}
void BackForwardCacheMetrics::RecordHistoryNavigationUMA(
NavigationRequest* navigation,
bool back_forward_cache_allowed) const {
HistoryNavigationOutcome outcome = HistoryNavigationOutcome::kNotRestored;
if (navigation->IsServedFromBackForwardCache()) {
outcome = HistoryNavigationOutcome::kRestored;
if (back_forward_cache_allowed) {
UMA_HISTOGRAM_ENUMERATION(
"BackForwardCache.EvictedAfterDocumentRestoredReason",
EvictedAfterDocumentRestoredReason::kRestored);
}
UMA_HISTOGRAM_ENUMERATION(
"BackForwardCache.AllSites.EvictedAfterDocumentRestoredReason",
EvictedAfterDocumentRestoredReason::kRestored);
}
if (back_forward_cache_allowed) {
UMA_HISTOGRAM_ENUMERATION("BackForwardCache.HistoryNavigationOutcome",
outcome);
int nav_offset = navigation->GetNavigationEntryOffset();
HistoryNavigationDirection direction =
nav_offset == 0
? HistoryNavigationDirection::kSameEntry
: (nav_offset < 0 ? HistoryNavigationDirection::kBack
: HistoryNavigationDirection::kForward);
if (navigation->IsServedFromBackForwardCache()) {
base::UmaHistogramEnumeration(
"BackForwardCache.RestoredNavigationDirection", direction);
} else {
base::UmaHistogramEnumeration(
"BackForwardCache.NonRestoredNavigationDirection", direction);
}
}
UMA_HISTOGRAM_ENUMERATION(
"BackForwardCache.AllSites.HistoryNavigationOutcome", outcome);
if (had_form_data_associated()) {
UMA_HISTOGRAM_ENUMERATION("BackForwardCache.PageWithForm.RestoreResult",
outcome);
}
for (NotRestoredReason reason : page_store_result_->not_restored_reasons()) {
DCHECK(!navigation->IsServedFromBackForwardCache());
if (back_forward_cache_allowed) {
UMA_HISTOGRAM_ENUMERATION(
"BackForwardCache.HistoryNavigationOutcome.NotRestoredReason",
reason);
}
UMA_HISTOGRAM_ENUMERATION(
"BackForwardCache.AllSites.HistoryNavigationOutcome.NotRestoredReason",
reason);
if (reason == NotRestoredReason::kRendererProcessKilled) {
CHECK(renderer_killed_timestamp_);
// It's possible (https://crbug.com/427426299) for the renderer to be
// killed before we record this timestamp. In that case, record 0.
base::TimeDelta time =
navigated_away_from_main_document_timestamp_
? (renderer_killed_timestamp_.value() -
navigated_away_from_main_document_timestamp_.value())
: base::Seconds(0);
UMA_HISTOGRAM_LONG_TIMES(
"BackForwardCache.Eviction.TimeUntilProcessKilled", time);
}
}
for (blink::scheduler::WebSchedulerTrackedFeature feature :
page_store_result_->blocklisted_features()) {
if (back_forward_cache_allowed) {
UMA_HISTOGRAM_ENUMERATION(
"BackForwardCache.HistoryNavigationOutcome.BlocklistedFeature",
feature);
}
UMA_HISTOGRAM_ENUMERATION(
"BackForwardCache.AllSites.HistoryNavigationOutcome."
"BlocklistedFeature",
feature);
}
if (back_forward_cache_allowed) {
for (const auto& [reason, _] : page_store_result_->disabled_reasons()) {
// Use SparseHistogram instead of other simple macros for metrics. The
// reasons cannot be represented as a unified enum because they come from
// multiple sources. At first they were represented as strings but that
// makes it hard to track new additions. Now they are represented by
// a combination of source and source-specific enum.
base::UmaHistogramSparse(
"BackForwardCache.HistoryNavigationOutcome."
"DisabledForRenderFrameHostReason2",
MetricValue(reason));
}
for (const uint64_t reason :
page_store_result_->disallow_activation_reasons()) {
base::UmaHistogramSparse(
"BackForwardCache.HistoryNavigationOutcome."
"DisallowActivationReason",
reason);
}
}
if (!DidSwapBrowsingInstance()) {
DCHECK(!navigation->IsServedFromBackForwardCache());
if (back_forward_cache_allowed) {
UMA_HISTOGRAM_ENUMERATION(
"BackForwardCache.HistoryNavigationOutcome."
"BrowsingInstanceNotSwappedReason",
browsing_instance_swap_result_.value());
}
UMA_HISTOGRAM_ENUMERATION(
"BackForwardCache.AllSites.HistoryNavigationOutcome."
"BrowsingInstanceNotSwappedReason",
browsing_instance_swap_result_.value());
if (back_forward_cache_allowed &&
browsing_instance_swap_result_ ==
ShouldSwapBrowsingInstance::kNo_HasRelatedActiveContents) {
CHECK_GT(related_active_contents_count_, 1);
// If a page was not restored from the back/forward cache because there
// are related active contents, log the details of the related active
// contents. Note that this also logs in cases where there are other
// reasons causing the page to not get restored from the back/forward
// cache (e.g. use of blocking features).
base::UmaHistogramCounts100(
"BackForwardCache.HistoryNavigationOutcome."
"RelatedActiveContents.Count2",
related_active_contents_count_);
base::UmaHistogramEnumeration(
"BackForwardCache.HistoryNavigationOutcome."
"RelatedActiveContents.IsPotentiallySyncAccessible2",
related_active_contents_sync_access_info_);
}
}
}
void BackForwardCacheMetrics::RecordEvictedAfterDocumentRestored(
EvictedAfterDocumentRestoredReason reason) {
UMA_HISTOGRAM_ENUMERATION(
"BackForwardCache.EvictedAfterDocumentRestoredReason", reason);
UMA_HISTOGRAM_ENUMERATION(
"BackForwardCache.AllSites.EvictedAfterDocumentRestoredReason", reason);
}
// static
uint64_t BackForwardCacheMetrics::MetricValue(
BackForwardCache::DisabledReason reason) {
return static_cast<BackForwardCache::DisabledReasonType>(reason.source)
<< BackForwardCache::kDisabledReasonTypeBits |
reason.id;
}
void BackForwardCacheMetrics::SetBrowsingInstanceSwapResult(
std::optional<ShouldSwapBrowsingInstance> reason,
RenderFrameHostImpl* navigated_away_rfh) {
browsing_instance_swap_result_ = reason;
if (navigated_away_rfh) {
SetRelatedActiveContentsInfo(navigated_away_rfh);
} else {
// The tracked reason is being reset as a result of prerender activation.
// Also reset `related_active_contents_count_` and
// `related_active_contents_sync_access_info_` as they are stale too.
CHECK(!reason.has_value());
related_active_contents_count_ = 1;
related_active_contents_sync_access_info_ =
RelatedActiveContentsSyncAccessInfo::kNoSyncAccess;
}
}
void BackForwardCacheMetrics::SetRelatedActiveContentsInfo(
RenderFrameHostImpl* navigated_away_rfh) {
CHECK(navigated_away_rfh->is_main_frame());
related_active_contents_count_ =
navigated_away_rfh->GetSiteInstance()->GetRelatedActiveContentsCount();
// Count how many documents in the navigating page are using each SiteInfo.
std::map<SiteInfo, int> doc_count_in_page;
navigated_away_rfh->ForEachRenderFrameHost([&doc_count_in_page](
RenderFrameHost* rfh) {
const SiteInfo& site_info = static_cast<RenderFrameHostImpl*>(rfh)
->last_committed_url_derived_site_info();
if (doc_count_in_page.contains(site_info)) {
doc_count_in_page[site_info]++;
} else {
doc_count_in_page[site_info] = 1;
}
});
// Determine if any document in the navigating page is potentially
// synchronously accessible by documents in other pages, by checking if there
// are documents in other pages that use the same SiteInfo as a document in
// the navigating page. This uses SiteInfos derived from document URLs, which
// works even when Site Isolation is disabled and the default SiteInstance may
// contain multiple sites.
related_active_contents_sync_access_info_ =
RelatedActiveContentsSyncAccessInfo::kNoSyncAccess;
navigated_away_rfh->ForEachRenderFrameHostWithAction(
[&doc_count_in_page, this](RenderFrameHost* rfh) {
// `active_document_count()` counts the number of committed
// documents in all pages that are using the same SiteInfo, including
// the navigating page. To get the number of committed documents using
// the same SiteInfo in pages other than the navigating page, just
// subtract by the number of committed documents using SiteInfo in the
// navigating page.
auto* rfhi = static_cast<RenderFrameHostImpl*>(rfh);
const SiteInfo& site_info =
rfhi->last_committed_url_derived_site_info();
int matching_doc_count =
rfhi->GetSiteInstance()->GetActiveDocumentCount(site_info);
int matching_doc_in_other_pages_count =
matching_doc_count - doc_count_in_page[site_info];
if (matching_doc_in_other_pages_count > 0) {
// The document shares a SiteInfo with another tab. This means the
// contents of this document might be synchronously accessible by
// a document in another tab (either because the documents are
// same-origin, or through modifying document.domain), so note down
// this information.
related_active_contents_sync_access_info_ =
RelatedActiveContentsSyncAccessInfo::kPotentiallySyncAccessible;
// Once we've found a case where sync access is possible, we can stop,
// as we've reached the maximum value for the enum
// (kPotentiallySyncAccessible).
return RenderFrameHost::FrameIterationAction::kStop;
}
return RenderFrameHost::FrameIterationAction::kContinue;
});
}
bool BackForwardCacheMetrics::DidSwapBrowsingInstance() const {
if (!browsing_instance_swap_result_)
return true;
switch (browsing_instance_swap_result_.value()) {
case ShouldSwapBrowsingInstance::kNo_ProactiveSwapDisabled:
case ShouldSwapBrowsingInstance::kNo_NotMainFrame:
case ShouldSwapBrowsingInstance::kNo_HasRelatedActiveContents:
case ShouldSwapBrowsingInstance::kNo_DoesNotHaveSite:
case ShouldSwapBrowsingInstance::kNo_SourceURLSchemeIsNotHTTPOrHTTPS:
case ShouldSwapBrowsingInstance::kNo_SameSiteNavigation:
case ShouldSwapBrowsingInstance::kNo_AlreadyHasMatchingBrowsingInstance:
case ShouldSwapBrowsingInstance::kNo_RendererDebugURL:
case ShouldSwapBrowsingInstance::kNo_NotNeededForBackForwardCache:
case ShouldSwapBrowsingInstance::kNo_SameDocumentNavigation:
case ShouldSwapBrowsingInstance::kNo_SameUrlNavigation:
case ShouldSwapBrowsingInstance::kNo_WillReplaceEntry:
case ShouldSwapBrowsingInstance::kNo_Reload:
case ShouldSwapBrowsingInstance::kNo_Guest:
case ShouldSwapBrowsingInstance::kNo_HasNotComittedAnyNavigation:
case ShouldSwapBrowsingInstance::kNo_NotPrimaryMainFrame:
case ShouldSwapBrowsingInstance::kNo_InitiatorRequestedNoProactiveSwap:
return false;
case ShouldSwapBrowsingInstance::kYes_ForceSwap:
case ShouldSwapBrowsingInstance::kYes_CrossSiteProactiveSwap:
case ShouldSwapBrowsingInstance::kYes_SameSiteProactiveSwap:
return true;
}
}
std::string BackForwardCacheMetrics::GetPageStoredResultString() {
return page_store_result_->ToString();
}
} // namespace content