| // 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_macros.h" | 
 | #include "base/metrics/metrics_hashes.h" | 
 | #include "base/metrics/sparse_histogram.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->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 reloads after history navigation, if applicable. | 
 |   RecordHistogramForReloadsAfterHistoryNavigations( | 
 |       navigation->GetReloadType() != ReloadType::NONE, | 
 |       back_forward_cache_allowed); | 
 |  | 
 |   // 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(https://crbug.com/1338089): 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().ToEnumBitmask()); | 
 |       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); | 
 |       // TODO(crbug.com/1446619): handle the remaining elements from the masks. | 
 |       std::vector<uint64_t> masks = blink::scheduler::ToEnumBitMasks( | 
 |           page_store_result_->blocklisted_features()); | 
 |       SCOPED_CRASH_KEY_NUMBER("BFCacheMismatch", "blocklisted", masks[0]); | 
 |       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()); | 
 |       // 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. | 
 |   previous_navigation_is_served_from_bfcache_ = | 
 |       navigation->IsServedFromBackForwardCache(); | 
 |   previous_navigation_is_history_ = | 
 |       IsCrossDocumentMainFrameHistoryNavigation(navigation); | 
 |   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_ = absl::nullopt; | 
 |   started_navigation_timestamp_ = absl::nullopt; | 
 |   renderer_killed_timestamp_ = absl::nullopt; | 
 |   browsing_instance_swap_result_ = absl::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)); | 
 |   } | 
 |   // TODO(crbug.com/1446619): handle the remaining elements from the masks. | 
 |   std::vector<uint64_t> main_frame_features_masks = | 
 |       blink::scheduler::ToEnumBitMasks(main_frame_features_); | 
 |   builder.SetMainFrameFeatures(main_frame_features_masks[0]); | 
 |   std::vector<uint64_t> same_origin_frames_features_masks = | 
 |       blink::scheduler::ToEnumBitMasks(same_origin_frames_features_); | 
 |   builder.SetSameOriginSubframesFeatures(same_origin_frames_features_masks[0]); | 
 |   std::vector<uint64_t> cross_origin_frames_features_masks = | 
 |       blink::scheduler::ToEnumBitMasks(same_origin_frames_features_); | 
 |   builder.SetCrossOriginSubframesFeatures( | 
 |       cross_origin_frames_features_masks[0]); | 
 |   // 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().ToEnumBitmask()); | 
 |  | 
 |   // TODO(crbug.com/1446619): handle the remaining elements from the masks. | 
 |   std::vector<uint64_t> page_store_result_masks = | 
 |       blink::scheduler::ToEnumBitMasks( | 
 |           page_store_result_->blocklisted_features()); | 
 |   builder.SetBackForwardCache_BlocklistedFeatures(page_store_result_masks[0]); | 
 |  | 
 |   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()); | 
 |  | 
 |   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); | 
 |  | 
 |     for (const auto& associated_source_id : associated_source_ids) { | 
 |       if (associated_source_id.has_value()) { | 
 |         RecordDisabledForRenderFrameHostReasonUKM(associated_source_id.value(), | 
 |                                                   reason_value); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   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(); | 
 |   } | 
 |   if (!not_restored_reasons.Has(NotRestoredReason::kHTTPStatusNotOK) && | 
 |       !not_restored_reasons.Has(NotRestoredReason::kSchemeNotHTTPOrHTTPS) && | 
 |       not_restored_reasons.Has(NotRestoredReason::kNoResponseHead)) { | 
 |     CaptureTraceForNavigationDebugScenario( | 
 |         DebugScenario::kDebugNoResponseHeadForHttpOrHttps); | 
 |     base::debug::DumpWithoutCrashing(); | 
 |   } | 
 | } | 
 |  | 
 | 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); | 
 |  | 
 |     // Record total number of history navigations for all websites allowed by | 
 |     // back-forward cache. | 
 |     UMA_HISTOGRAM_ENUMERATION("BackForwardCache.ReloadsAndHistoryNavigations", | 
 |                               ReloadsAndHistoryNavigations::kHistoryNavigation); | 
 |   } | 
 |  | 
 |   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) { | 
 |       DCHECK(renderer_killed_timestamp_); | 
 |       DCHECK(navigated_away_from_main_document_timestamp_); | 
 |       base::TimeDelta time = | 
 |           renderer_killed_timestamp_.value() - | 
 |           navigated_away_from_main_document_timestamp_.value(); | 
 |       UMA_HISTOGRAM_LONG_TIMES( | 
 |           "BackForwardCache.Eviction.TimeUntilProcessKilled", time); | 
 |     } | 
 |   } | 
 |  | 
 |   for (blink::scheduler::WebSchedulerTrackedFeature feature : | 
 |        page_store_result_->blocklisted_features()) { | 
 |     // Since there could be more than 64 variants in | 
 |     // `WebSchedulerTrackedFeature`, we need to split them into different | 
 |     // chunks and record that in the corresponding field. | 
 |     // TODO(crbug.com/1446619): Only add the first 64 bit into | 
 |     // `BlocklistedFeature` and put the other chunks into another field once UKM | 
 |     // approval is obtained. | 
 |     if (back_forward_cache_allowed) { | 
 |       UMA_HISTOGRAM_ENUMERATION( | 
 |           "BackForwardCache.HistoryNavigationOutcome.BlocklistedFeature", | 
 |           feature); | 
 |     } | 
 |     UMA_HISTOGRAM_ENUMERATION( | 
 |         "BackForwardCache.AllSites.HistoryNavigationOutcome." | 
 |         "BlocklistedFeature", | 
 |         feature); | 
 |   } | 
 |  | 
 |   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 (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); | 
 | } | 
 |  | 
 | void BackForwardCacheMetrics::RecordHistogramForReloadsAfterHistoryNavigations( | 
 |     bool is_reload, | 
 |     bool back_forward_cache_allowed) const { | 
 |   if (!is_reload || !previous_navigation_is_history_ || | 
 |       !back_forward_cache_allowed) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // Record the total number of reloads after a history navigation. | 
 |   UMA_HISTOGRAM_ENUMERATION( | 
 |       "BackForwardCache.ReloadsAndHistoryNavigations", | 
 |       ReloadsAndHistoryNavigations::kReloadAfterHistoryNavigation); | 
 |  | 
 |   // Record separate buckets for cases served and not served from | 
 |   // back-forward cache. | 
 |   UMA_HISTOGRAM_ENUMERATION( | 
 |       "BackForwardCache.ReloadsAfterHistoryNavigation", | 
 |       previous_navigation_is_served_from_bfcache_ | 
 |           ? ReloadsAfterHistoryNavigation::kServedFromBackForwardCache | 
 |           : ReloadsAfterHistoryNavigation::kNotServedFromBackForwardCache); | 
 | } | 
 |  | 
 | // static | 
 | uint64_t BackForwardCacheMetrics::MetricValue( | 
 |     BackForwardCache::DisabledReason reason) { | 
 |   return static_cast<BackForwardCache::DisabledReasonType>(reason.source) | 
 |              << BackForwardCache::kDisabledReasonTypeBits | | 
 |          reason.id; | 
 | } | 
 |  | 
 | void BackForwardCacheMetrics::SetBrowsingInstanceSwapResult( | 
 |     absl::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: | 
 |       return false; | 
 |     case ShouldSwapBrowsingInstance::kYes_ForceSwap: | 
 |     case ShouldSwapBrowsingInstance::kYes_CrossSiteProactiveSwap: | 
 |     case ShouldSwapBrowsingInstance::kYes_SameSiteProactiveSwap: | 
 |       return true; | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace content |