blob: 0e1e56d07cc9967b57fc7ad297e541d49c9c16e0 [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.
#ifndef CONTENT_BROWSER_RENDERER_HOST_BACK_FORWARD_CACHE_METRICS_H_
#define CONTENT_BROWSER_RENDERER_HOST_BACK_FORWARD_CACHE_METRICS_H_
#include <bitset>
#include <memory>
#include <optional>
#include "base/containers/enum_set.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "content/browser/renderer_host/should_swap_browsing_instance.h"
#include "content/common/content_export.h"
#include "content/public/browser/back_forward_cache.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
#include "third_party/blink/public/mojom/back_forward_cache_not_restored_reasons.mojom-forward.h"
namespace base {
class TickClock;
}
namespace url {
class Origin;
}
namespace content {
class BackForwardCacheCanStoreDocumentResult;
class BackForwardCacheCanStoreTreeResult;
class NavigationEntryImpl;
class NavigationRequest;
class RenderFrameHostImpl;
struct BackForwardCacheCanStoreDocumentResultWithTree;
// Helper class for recording metrics around history navigations.
// Associated with a main frame document and shared between all
// NavigationEntries with the same document_sequence_number for the main
// document.
class BackForwardCacheMetrics
: public base::RefCounted<BackForwardCacheMetrics> {
public:
using NotRestoredReason = BackForwardCache::NotRestoredReason;
using NotRestoredReasons = base::EnumSet<NotRestoredReason,
NotRestoredReason::kMinValue,
NotRestoredReason::kMaxValue>;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(HistoryNavigationDirection)
enum class HistoryNavigationDirection {
kBack = 0,
kForward = 1,
kSameEntry = 2,
kMaxValue = kSameEntry,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/navigation/enums.xml:HistoryNavigationDirection)
// Please keep in sync with BackForwardCacheHistoryNavigationOutcome in
// tools/metrics/histograms/enums.xml. These values should not be renumbered.
enum class HistoryNavigationOutcome {
kRestored = 0,
kNotRestored = 1,
kMaxValue = kNotRestored,
};
// Please keep in sync with BackForwardCacheEvictedAfterDocumentRestoredReason
// in tools/metrics/histograms/enums.xml. These values should not be
// renumbered.
enum class EvictedAfterDocumentRestoredReason {
kRestored = 0,
kByJavaScript = 1,
kMaxValue = kByJavaScript,
};
// Please keep in sync with BackForwardCachePageWithFormStorable
// in tools/metrics/histograms/enums.xml. These values should not be
// renumbered.
enum class PageWithFormStorable {
kPageSeen = 0,
kPageStored = 1,
kMaxValue = kPageStored,
};
// Gets the metrics object for a committed navigation.
// Note that this object will not be used if the entry we are navigating to
// already has the BackForwardCacheMetrics object (which happens for history
// navigations). We will reuse `previous_entry`'s metrics object if the
// navigation is a subframe navigation or if it's same-document with
// `previous_entry`'s document.
//
// |document_sequence_number| is the sequence number of the document
// associated with the navigating frame.
static scoped_refptr<BackForwardCacheMetrics>
CreateOrReuseBackForwardCacheMetricsForNavigation(
NavigationEntryImpl* previous_entry,
bool is_main_frame_navigation,
int64_t committing_document_sequence_number);
explicit BackForwardCacheMetrics(int64_t document_sequence_number);
BackForwardCacheMetrics(const BackForwardCacheMetrics&) = delete;
BackForwardCacheMetrics& operator=(const BackForwardCacheMetrics&) = delete;
// Records when the page is evicted after the document is restored e.g. when
// the race condition by JavaScript happens.
static void RecordEvictedAfterDocumentRestored(
EvictedAfterDocumentRestoredReason reason);
// Sets the reason why the browsing instance is swapped/not swapped when
// navigating away from `navigated_away_rfh`. Passing`reason` as std::nullopt
// resets the reason and other tracked information.
void SetBrowsingInstanceSwapResult(
std::optional<ShouldSwapBrowsingInstance> reason,
RenderFrameHostImpl* navigated_away_rfh);
std::optional<ShouldSwapBrowsingInstance> browsing_instance_swap_result()
const {
return browsing_instance_swap_result_;
}
// Notifies that the main frame has started a navigation to an entry
// associated with |this|.
//
// This is the point in time that a back-forward cache hit could be shown to
// the user.
//
// Note that in some cases (synchronous renderer-initiated navigations
// which create navigation entry only when committed) this call might
// be missing, but they should not matter for bfcache.
void MainFrameDidStartNavigationToDocument();
// Notifies that an associated entry has committed a navigation.
// |back_forward_cache_allowed| indicates whether back-forward cache is
// allowed for the URL of |navigation_request|.
void DidCommitNavigation(NavigationRequest* navigation_request,
bool back_forward_cache_allowed);
// Records when another navigation commits away from the most recent entry
// associated with |this|. This is the point in time that the previous
// document could enter the back-forward cache.
void MainFrameDidNavigateAwayFromDocument();
// Snapshots the state of the features active on the page before closing it.
// It should be called at the same time when the document might have been
// placed in the back-forward cache.
void RecordFeatureUsage(RenderFrameHostImpl* main_frame);
// Adds the flattened list of NotRestoredReasons to the existing
// |page_store_result_|.
// TODO(yuzus): Make this function take
// BackForwardCacheCanStoreDocumentResultWithTree.
void AddNotRestoredFlattenedReasonsToExistingResult(
BackForwardCacheCanStoreDocumentResult& flattened);
// Sets |can_store| as the final NotRestoredReasons to report. This replaces
// the existing |page_store_tree_result_|.
void SetNotRestoredReasons(
BackForwardCacheCanStoreDocumentResultWithTree& can_store);
// Populate and return the mojom struct from |page_store_tree_result_|.
blink::mojom::BackForwardCacheNotRestoredReasonsPtr
GetWebExposedNotRestoredReasons();
// Records additional reasons why a history navigation was not served from
// BFCache. The reasons are recorded only after the history navigation started
// because it's about the history navigation (e.g. kSessionRestored) or
// reasons that might not have been recorded yet (e.g.
// kBrowsingInstanceNotSwapped).
void UpdateNotRestoredReasonsForNavigation(NavigationRequest* navigation);
// Used to specify whether any document within the page that this
// BackForwardCacheMetrics is associated with has any form data.
void SetHadFormDataAssociated(bool had_form_data_associated) {
had_form_data_associated_ = had_form_data_associated;
}
bool had_form_data_associated() const { return had_form_data_associated_; }
// Exported for testing.
// The DisabledReason's source and id combined to give a unique uint64.
CONTENT_EXPORT static uint64_t MetricValue(BackForwardCache::DisabledReason);
// Injects a clock for mocking time.
// Should be called only from the UI thread.
CONTENT_EXPORT static void OverrideTimeForTesting(base::TickClock* clock);
class TestObserver {
public:
virtual ~TestObserver() = default;
// Report the tree result of NotRestoredReason to the observer.
virtual void NotifyNotRestoredReasons(
std::unique_ptr<BackForwardCacheCanStoreTreeResult> tree_result) = 0;
};
void SetObserverForTesting(TestObserver* observer) {
test_observer_ = observer;
}
// Returns if |navigation| is cross-document main frame history navigation.
static bool IsCrossDocumentMainFrameHistoryNavigation(
NavigationRequest* navigation);
// Returns the debug string for `page_stored_result_`.
std::string GetPageStoredResultString();
private:
friend class base::RefCounted<BackForwardCacheMetrics>;
FRIEND_TEST_ALL_PREFIXES(BackForwardCacheBrowserTest, WindowOpen);
FRIEND_TEST_ALL_PREFIXES(BackForwardCacheBrowserTest, WindowOpenCrossSite);
FRIEND_TEST_ALL_PREFIXES(BackForwardCacheBrowserTest,
WindowOpenCrossSiteNavigateSameSite);
FRIEND_TEST_ALL_PREFIXES(BackForwardCacheBrowserTest,
WindowOpenCrossSiteWithSameSiteChild);
FRIEND_TEST_ALL_PREFIXES(BackForwardCacheBrowserTest, WindowOpenThenClose);
FRIEND_TEST_ALL_PREFIXES(BackForwardCacheBrowserTest,
WindowWithOpenerAndOpenee);
FRIEND_TEST_ALL_PREFIXES(
BackForwardCacheBrowserTestWithVaryingNavigationSite,
RelatedActiveContentsLoggingOnPageWithBlockingFeature);
FRIEND_TEST_ALL_PREFIXES(
BackForwardCacheBrowserTestWithVaryingNavigationSite,
RelatedActiveContentsLoggingOnPageWithBlockingFeatureAndRAC);
FRIEND_TEST_ALL_PREFIXES(BackForwardCacheBrowserTest,
WindowOpen_SameSitePopupPendingDeletion);
FRIEND_TEST_ALL_PREFIXES(BackForwardCacheBrowserTest,
WindowOpen_UnrelatedSameSiteAndProcessTab);
~BackForwardCacheMetrics();
// Recursively collects the feature usage information from the subtree
// of a given frame.
void CollectFeatureUsageFromSubtree(RenderFrameHostImpl* rfh,
const url::Origin& main_frame_origin);
// Dumps the current recorded information for a history navigation for UMA.
// |back_forward_cache_allowed| indicates whether back-forward cache is
// allowed for the URL of |navigation_request|.
void RecordHistoryNavigationUMA(NavigationRequest* navigation,
bool back_forward_cache_allowed) const;
// Records UKM for a history navigation.
void RecordHistoryNavigationUKM(NavigationRequest* navigation);
// Record metrics for the number of reloads after history navigation. In
// particular we are interested in number of reloads after a restore from
// the back-forward cache as a proxy for detecting whether the page was
// broken or not.
void RecordHistogramForReloadsAfterHistoryNavigations(
bool is_reload,
bool back_forward_cache_allowed) const;
// Whether the last navigation swapped BrowsingInstance or not. Returns true
// if the last navigation did swap BrowsingInstance, or if it's unknown
// (`browsing_instance_swap_result_` is not set). Returns false otherwise.
bool DidSwapBrowsingInstance() const;
// Sets information about `rfh`'s related active contents, whose existence
// make `rfh` ineligible for back/forward cache. This should be set at the
// same time as `browsing_instance_swap_result_` to reflect the condition of
// the related active contents at the time the BrowsingInstance swap decision
// was made when navigating away from `rfh`.
void SetRelatedActiveContentsInfo(RenderFrameHostImpl* rfh);
// Main frame document sequence number that identifies all
// NavigationEntries this metrics object is associated with.
const int64_t document_sequence_number_;
// NavigationHandle's ID for the last cross-document main frame navigation
// that uses this metrics object.
//
// Should not be confused with NavigationEntryId.
int64_t last_committed_cross_document_main_frame_navigation_id_ = -1;
// Whether any document within the page that this BackForwardCacheMetrics
// associated with has any form data. This state is not persisted and only
// set in Android Custom tabs for now.
// TODO(crbug.com/40251494): Set this boolean for all platforms or gated with
// android build flag.
bool had_form_data_associated_ = false;
// ====== Post-navigation reuse boundary ========
// The variables above these are kept after we finished
// logging the metrics for the last navigation that used this metrics object,
// as they are needed for logging metrics for future navigations.
// The variables below are reset after logging.
blink::scheduler::WebSchedulerTrackedFeatures main_frame_features_;
// We record metrics for same-origin frames and cross-origin frames
// differently as we might want to apply different policies for them,
// especially for the things around web platform compatibility (e.g. ignore
// unload handlers in cross-origin iframes but not in same-origin). The
// details are still subject to metrics, however. NOTE: This is not related to
// which process these frames are hosted in.
blink::scheduler::WebSchedulerTrackedFeatures same_origin_frames_features_;
blink::scheduler::WebSchedulerTrackedFeatures cross_origin_frames_features_;
std::optional<base::TimeTicks> started_navigation_timestamp_;
std::optional<base::TimeTicks> navigated_away_from_main_document_timestamp_;
std::optional<base::TimeTicks> renderer_killed_timestamp_;
// TODO: Store BackForwardCacheCanStoreDocumentResultWithTree instead of
// storing unique_ptr of BackForwardCacheCanStoreDocumentResult and
// BackForwardCacheCanStoreTreeResult respectively.
std::unique_ptr<BackForwardCacheCanStoreDocumentResult> page_store_result_;
std::unique_ptr<BackForwardCacheCanStoreTreeResult> page_store_tree_result_;
// The reason why the last attempted navigation in the main frame used or
// didn't use a new BrowsingInstance.
std::optional<ShouldSwapBrowsingInstance> browsing_instance_swap_result_;
// The number of related active contents for the page.
int related_active_contents_count_ = 1;
// Whether any document in the page can potentially be accessed synchronously
// by another document in a different page, i.e. if there are any documents
// using the same SiteInstance as any document in the page. See also
// `SetRelatedActiveContentsInfo()`.
// Please keep in sync with RelatedActiveContentsSyncAccessInfo
// in tools/metrics/histograms/enums.xml. These values should not be
// renumbered.
enum class RelatedActiveContentsSyncAccessInfo {
kNoSyncAccess = 0,
// Deprecated: We check using SiteInfo instead of just SiteInstance now,
// so this category is no longer used.
kPotentiallySyncAccessibleDefaultSiteInstance = 1,
kPotentiallySyncAccessible = 2,
kMaxValue = kPotentiallySyncAccessible
};
RelatedActiveContentsSyncAccessInfo
related_active_contents_sync_access_info_ =
RelatedActiveContentsSyncAccessInfo::kNoSyncAccess;
raw_ptr<TestObserver> test_observer_ = nullptr;
};
} // namespace content
#endif // CONTENT_BROWSER_RENDERER_HOST_BACK_FORWARD_CACHE_METRICS_H_