blob: da826ddcd1ff2407d670654b5050a263fcfb7e5b [file] [log] [blame]
// Copyright 2019 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 "content/browser/frame_host/back_forward_cache_metrics.h"
#include "content/browser/frame_host/navigation_entry_impl.h"
#include "content/public/browser/browser_thread.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.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::TimeDelta::FromSeconds(5))
return base::TimeDelta::FromMilliseconds(time.InMilliseconds());
if (time < base::TimeDelta::FromMinutes(3))
return base::TimeDelta::FromSeconds(time.InSeconds());
if (time < base::TimeDelta::FromHours(3))
return base::TimeDelta::FromMinutes(time.InMinutes());
return base::TimeDelta::FromHours(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
scoped_refptr<BackForwardCacheMetrics>
BackForwardCacheMetrics::CreateOrReuseBackForwardCacheMetrics(
NavigationEntryImpl* currently_committed_entry,
bool is_main_frame_navigation,
int64_t document_sequence_number) {
if (!currently_committed_entry) {
// In some rare cases it's possible to navigate a subframe
// without having a main frame navigation (e.g. extensions
// injecting frames into a blank page).
return base::WrapRefCounted(new BackForwardCacheMetrics(
is_main_frame_navigation ? document_sequence_number : -1));
}
BackForwardCacheMetrics* currently_committed_metrics =
currently_committed_entry->back_forward_cache_metrics();
if (!currently_committed_metrics) {
// When we restore the session it's possible to end up with an entry without
// metrics.
// We will have to create a new metrics object for the main document.
return base::WrapRefCounted(new BackForwardCacheMetrics(
is_main_frame_navigation
? document_sequence_number
: currently_committed_entry->root_node()
->frame_entry->document_sequence_number()));
}
if (!is_main_frame_navigation)
return currently_committed_metrics;
if (document_sequence_number ==
currently_committed_metrics->document_sequence_number_) {
return currently_committed_metrics;
}
return base::WrapRefCounted(
new BackForwardCacheMetrics(document_sequence_number));
}
BackForwardCacheMetrics::BackForwardCacheMetrics(
int64_t document_sequence_number)
: document_sequence_number_(document_sequence_number) {}
BackForwardCacheMetrics::~BackForwardCacheMetrics() {}
void BackForwardCacheMetrics::MainFrameDidStartNavigationToDocument() {
if (!started_navigation_timestamp_)
started_navigation_timestamp_ = Now();
}
void BackForwardCacheMetrics::DidCommitNavigation(
int64_t navigation_id,
int64_t navigation_entry_id,
bool is_main_frame_navigation) {
if (last_committed_main_frame_navigation_id_ != -1 &&
is_main_frame_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::builders::HistoryNavigation builder(ukm::ConvertToSourceId(
navigation_id, ukm::SourceIdType::NAVIGATION_ID));
builder.SetLastCommittedSourceIdForTheSameDocument(
ukm::ConvertToSourceId(last_committed_main_frame_navigation_id_,
ukm::SourceIdType::NAVIGATION_ID));
builder.SetNavigatedToTheMostRecentEntryForDocument(
navigation_entry_id == last_committed_navigation_entry_id_);
builder.SetMainFrameFeatures(main_frame_features_);
builder.SetSameOriginSubframesFeatures(same_origin_frames_features_);
builder.SetCrossOriginSubframesFeatures(cross_origin_frames_features_);
// 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.Record(ukm::UkmRecorder::Get());
}
if (is_main_frame_navigation)
last_committed_main_frame_navigation_id_ = navigation_id;
last_committed_navigation_entry_id_ = navigation_entry_id;
navigated_away_from_main_document_timestamp_ = base::nullopt;
started_navigation_timestamp_ = base::nullopt;
}
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_ = 0;
same_origin_frames_features_ = 0;
cross_origin_frames_features_ = 0;
CollectFeatureUsageFromSubtree(main_frame,
main_frame->GetLastCommittedOrigin());
}
void BackForwardCacheMetrics::CollectFeatureUsageFromSubtree(
RenderFrameHostImpl* rfh,
const url::Origin& main_frame_origin) {
uint64_t features = rfh->scheduler_tracked_features();
if (!rfh->GetParent()) {
main_frame_features_ |= features;
} else if (rfh->GetLastCommittedOrigin().IsSameOriginWith(
main_frame_origin)) {
same_origin_frames_features_ |= features;
} else {
cross_origin_frames_features_ |= features;
}
for (size_t i = 0; i < rfh->child_count(); ++i) {
CollectFeatureUsageFromSubtree(rfh->child_at(i)->current_frame_host(),
main_frame_origin);
}
}
} // namespace content