blob: 75541b147b2e80ba1e2e87323b56d0c28c12cc8e [file] [log] [blame]
// Copyright 2018 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.
#ifndef CONTENT_BROWSER_RENDERER_HOST_BACK_FORWARD_CACHE_IMPL_H_
#define CONTENT_BROWSER_RENDERER_HOST_BACK_FORWARD_CACHE_IMPL_H_
#include <list>
#include <memory>
#include <set>
#include <unordered_set>
#include "base/feature_list.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "content/browser/renderer_host/back_forward_cache_can_store_document_result.h"
#include "content/browser/renderer_host/page_impl.h"
#include "content/browser/renderer_host/render_process_host_internal_observer.h"
#include "content/browser/renderer_host/stored_page.h"
#include "content/common/content_export.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_observer.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "net/cookies/canonical_cookie.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "third_party/blink/public/mojom/page/page.mojom.h"
#include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
#include "url/gurl.h"
namespace content {
class RenderFrameHostImpl;
class RenderViewHostImpl;
class SiteInstance;
// This feature is used to limit the scope of back-forward cache experiment
// without enabling it. To control the URLs list by using this feature by
// generating the metrics only for "allowed_websites" param. Mainly, to ensure
// that metrics from the control and experiment groups are consistent.
constexpr base::Feature kRecordBackForwardCacheMetricsWithoutEnabling{
"RecordBackForwardCacheMetricsWithoutEnabling",
base::FEATURE_DISABLED_BY_DEFAULT};
// Removes the time limit for cached content. This is used on bots to identify
// accidentally passing tests.
constexpr base::Feature kBackForwardCacheNoTimeEviction{
"BackForwardCacheNoTimeEviction", base::FEATURE_DISABLED_BY_DEFAULT};
// Allows pages with cache-control:no-store to enter the back/forward cache.
// Feature params can specify whether pages with cache-control:no-store can be
// restored if cookies change / if HTTPOnly cookies change.
// TODO(crbug.com/1228611): Enable this feature.
const base::Feature kCacheControlNoStoreEnterBackForwardCache{
"CacheControlNoStoreEnterBackForwardCache",
base::FEATURE_DISABLED_BY_DEFAULT};
// Allows pages with MediaSession's playback state change to stay eligible for
// the back/forward cache.
const base::Feature kBackForwardCacheMediaSessionPlaybackStateChange{
"BackForwardCacheMediaSessionPlaybackStateChange",
base::FEATURE_DISABLED_BY_DEFAULT};
// Combines a flattened list and a tree of the reasons why each document cannot
// enter the back/forward cache (might be empty if it can). The tree saves the
// reasons for each document in the tree (including those without the reasons)
// in a tree format, with each node corresponding to one document. The flattened
// list is the combination of all reasons for all documents in the tree.
// CONTENT_EXPORT is for exporting only for testing.
struct CONTENT_EXPORT BackForwardCacheCanStoreDocumentResultWithTree {
BackForwardCacheCanStoreDocumentResultWithTree(
BackForwardCacheCanStoreDocumentResult& flattened_reasons,
std::unique_ptr<BackForwardCacheCanStoreTreeResult> tree_reasons);
~BackForwardCacheCanStoreDocumentResultWithTree();
BackForwardCacheCanStoreDocumentResult flattened_reasons;
std::unique_ptr<BackForwardCacheCanStoreTreeResult> tree_reasons;
// If BFCache is available, it returns true. If there are reasons that BFCache
// is not available, it returns false.
explicit operator bool() const { return flattened_reasons; }
};
// BackForwardCache:
//
// After the user navigates away from a document, the old one goes into the
// frozen state and is kept in this object. They can potentially be reused
// after an history navigation. Reusing a document means swapping it back with
// the current_frame_host.
class CONTENT_EXPORT BackForwardCacheImpl
: public BackForwardCache,
public RenderProcessHostInternalObserver {
public:
enum MessageHandlingPolicyWhenCached {
kMessagePolicyNone,
kMessagePolicyLog,
kMessagePolicyDump,
};
static MessageHandlingPolicyWhenCached
GetChannelAssociatedMessageHandlingPolicy();
// BackForwardCache entry, consisting of the page and associated metadata.
class Entry : public ::network::mojom::CookieChangeListener {
public:
explicit Entry(std::unique_ptr<StoredPage> stored_page);
~Entry() override;
void WriteIntoTrace(perfetto::TracedValue context);
// Starts monitoring the cookie change in this entry.
void StartMonitoringCookieChange();
// Indicates whether or not all the |render_view_hosts| in this entry have
// received the acknowledgement from renderer that it finished running
// handlers.
bool AllRenderViewHostsReceivedAckFromRenderer();
std::unique_ptr<StoredPage> TakeStoredPage() {
return std::move(stored_page_);
}
void SetPageRestoreParams(
blink::mojom::PageRestoreParamsPtr page_restore_params) {
stored_page_->page_restore_params = std::move(page_restore_params);
}
// The main document being stored.
RenderFrameHostImpl* render_frame_host() {
return stored_page_->render_frame_host.get();
}
std::set<RenderViewHostImpl*> render_view_hosts() {
return stored_page_->render_view_hosts;
}
const StoredPage::RenderFrameProxyHostMap& proxy_hosts() const {
return stored_page_->proxy_hosts;
}
size_t proxy_hosts_size() { return stored_page_->proxy_hosts.size(); }
private:
friend class BackForwardCacheImpl;
// ::network::mojom::CookieChangeListener
void OnCookieChange(const net::CookieChangeInfo& change) override;
mojo::Receiver<::network::mojom::CookieChangeListener>
cookie_listener_receiver_{this};
struct CookieModified {
// Indicates whether or not cookie on the bfcache entry has been modified
// while the entry is in bfcache.
bool cookie_modified = false;
// Indicates whether or not HTTPOnly cookie on the bfcache entry
// has been modified while the entry is in bfcache.
bool http_only_cookie_modified = false;
};
// Only populated when |AllowStoringPagesWithCacheControlNoStore()| is true.
absl::optional<CookieModified> cookie_modified_;
std::unique_ptr<StoredPage> stored_page_;
};
// UnloadSupportStrategy is possible actions to take against pages with
// "unload" handlers.
// TODO(crbug.com/1201653): Consider making this private.
enum class UnloadSupportStrategy {
kAlways,
kOptInHeaderRequired,
kNo,
};
BackForwardCacheImpl();
BackForwardCacheImpl(const BackForwardCacheImpl&) = delete;
BackForwardCacheImpl& operator=(const BackForwardCacheImpl&) = delete;
~BackForwardCacheImpl() override;
// Returns whether MediaSession's playback state change is allowed for the
// BackForwardCache.
static bool IsMediaSessionPlaybackStateChangedAllowed();
// Returns whether MediaSession's service is allowed for the BackForwardCache.
static bool IsMediaSessionServiceAllowed();
// Returns whether back/forward cache is enabled for screen reader users.
static bool IsScreenReaderAllowed();
// Returns the reasons (if any) why this document and its children cannot
// enter the back/forward cache. Depends on the |render_frame_host| and its
// children's state. Should only be called after we've navigated away from
// |render_frame_host|, which means nothing about the page can change (usage
// of blocklisted features, pending navigations, load state, etc.) anymore.
// Note that criteria for storing and restoring can be different.
// |include_ccns| indicates whether or not we should consider cache-control:
// no-store related reasons. We don't include those reasons in the default
// case to allow pages with cache-control:no-store in back/forward cache
// temporarily for metrics collection, but those pages should never be
// restored. When trying to restore a page, |include_ccns| is set to true to
// include cache-control:no-store reasons, so that the pages containing them
// will not be restored.
BackForwardCacheCanStoreDocumentResultWithTree CanStorePageNow(
RenderFrameHostImpl* render_frame_host,
bool include_ccns = false);
// Whether a RenderFrameHost could be stored into the BackForwardCache at some
// point in the future. Different than CanStorePageNow() above, we won't check
// for properties of |render_frame_host| that might change in the future such
// as usage of certain APIs, loading state, existence of pending navigation
// requests, etc. This should be treated as a "best guess" on whether a page
// still has a chance to be stored in the back-forward cache later on, and
// should not be used as a final check before storing a page to the
// back-forward cache (for that, use CanStorePageNow() instead).
BackForwardCacheCanStoreDocumentResult CanPotentiallyStorePageLater(
RenderFrameHostImpl* render_frame_host);
// Moves the specified BackForwardCache entry into the BackForwardCache. It
// can be reused in a future history navigation by using RestoreEntry(). When
// the BackForwardCache is full, the least recently used document is evicted.
// Precondition: CanStoreDocument(*(entry->render_frame_host)).
void StoreEntry(std::unique_ptr<Entry> entry);
// Ensures that the cache is within its size limits. This should be called
// whenever events occur that could put the cache outside its limits. What
// those events are depends on the cache limit policy.
void EnforceCacheSizeLimit();
// Returns a pointer to a cached BackForwardCache entry matching
// |navigation_entry_id| if it exists in the BackForwardCache. Returns nullptr
// if no matching entry is found.
//
// Note: The returned pointer should be used temporarily only within the
// execution of a single task on the event loop. Beyond that, there is no
// guarantee the pointer will be valid, because the document may be
// removed/evicted from the cache.
Entry* GetEntry(int navigation_entry_id);
// During a history navigation, moves an entry out of the BackForwardCache
// knowing its |navigation_entry_id|. |page_restore_params| includes
// information that is needed by the entry's page after getting restored,
// which includes the latest history information (offset, length) and the
// timestamp corresponding to the start of the back-forward cached navigation,
// which would be communicated to the page to allow it to record the latency
// of this navigation.
std::unique_ptr<Entry> RestoreEntry(
int navigation_entry_id,
blink::mojom::PageRestoreParamsPtr page_restore_params);
// Evict all cached pages in the same BrowsingInstance as
// |site_instance|.
void EvictFramesInRelatedSiteInstances(SiteInstance* site_instance);
// Immediately deletes all frames in the cache. This should only be called
// when WebContents is being destroyed.
void Shutdown();
// Posts a task to destroy all frames in the BackForwardCache that have been
// marked as evicted.
void PostTaskToDestroyEvictedFrames();
// Storing frames in back-forward cache is not supported indefinitely
// due to potential privacy issues and memory leaks. Instead we are evicting
// the frame from the cache after the time to live, which can be controlled
// via experiment.
static base::TimeDelta GetTimeToLiveInBackForwardCache();
// Gets the maximum number of entries the BackForwardCache can hold per tab.
static size_t GetCacheSize();
// The back-forward cache is experimented on a limited set of URLs. This
// method returns true if the |url| matches one of those. URL not matching
// this won't enter the back-forward cache. This can still return true even
// when BackForwardCache is disabled for metrics purposes. It checks
// |IsHostPathAllowed| then |IsHostPathAllowed|
bool IsAllowed(const GURL& current_url);
// Returns true if the host and path are allowed according to the
// "allowed_websites" and "blocked_webites" parameters of
// |feature::kBackForwardCache|. An empty "allowed_websites" implies that all
// websites are allowed.
bool IsHostPathAllowed(const GURL& current_url);
// Returns true if query does not contain any of the parameters in
// "blocked_cgi_params" parameter of |feature::kBackForwardCache|. The
// comparison is done by splitting the query string on "&" and looking for
// exact matches in the list (parameter name and value). It does not consider
// URL escaping.
bool IsQueryAllowed(const GURL& current_url);
// Called just before commit for a navigation that's served out of the back
// forward cache. This method will disable eviction in renderers and invoke
// |done_callback| when they are ready for the navigation to be committed.
void WillCommitNavigationToCachedEntry(Entry& bfcache_entry,
base::OnceClosure done_callback);
// Returns the task runner that should be used by the eviction timer.
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() {
return task_runner_for_testing_ ? task_runner_for_testing_
: base::ThreadTaskRunnerHandle::Get();
}
// Inject task runner for precise timing control in browser tests.
void SetTaskRunnerForTesting(
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
task_runner_for_testing_ = task_runner;
}
const std::list<std::unique_ptr<Entry>>& GetEntries();
// BackForwardCache overrides:
void Flush() override;
void Prune(size_t limit) override;
void DisableForTesting(DisableForTestingReason reason) override;
// RenderProcessHostInternalObserver methods
void RenderProcessBackgroundedChanged(RenderProcessHostImpl* host) override;
// Returns true if we are managing the cache size using foreground and
// background limits (if finch parameter "foreground_cache_size" > 0).
static bool UsingForegroundBackgroundCacheSizeLimit();
// Returns true if one of the BFCache entries has a matching
// BrowsingInstanceId/SiteInstanceId/RenderFrameProxyHost.
// TODO(https://crbug.com/1243541): Remove these once the bug is fixed.
bool IsBrowsingInstanceInBackForwardCacheForDebugging(
BrowsingInstanceId browsing_instance_id);
bool IsSiteInstanceInBackForwardCacheForDebugging(
SiteInstanceId site_instance_id);
bool IsProxyInBackForwardCacheForDebugging(RenderFrameProxyHost* proxy);
private:
// Destroys all evicted frames in the BackForwardCache.
void DestroyEvictedFrames();
// Populates the reasons that are only relevant for main documents such as
// browser settings, the main document's URL & HTTP status, etc.
void PopulateReasonsForMainDocument(
BackForwardCacheCanStoreDocumentResult& result,
RenderFrameHostImpl* render_frame_host);
// Populates `result` with the blocking reasons for this document. If
// "include_non_sticky" is true, it includes non-sticky reasons.
void PopulateReasonsForDocument(
BackForwardCacheCanStoreDocumentResult& result,
RenderFrameHostImpl* rfh,
bool include_non_sticky);
// Populates the reasons why this |rfh| and its subframes cannot enter the
// back/forward cache.
// If |create_tree| is true, returns a tree of reasons by the document.
// |include_non_sticky| controls whether we include non-sticky reasons in the
// result.
std::unique_ptr<BackForwardCacheCanStoreTreeResult> PopulateReasonsForPage(
RenderFrameHostImpl* rfh,
BackForwardCacheCanStoreDocumentResult& flattened_result,
bool include_non_sticky,
bool create_tree);
// Populates the reasons why this |rfh| and its subframes cannot enter the
// back/forward cache.
// |main_origin| is the origin of the outermost document. Refer to
// |PopulateReasonsForPage| for other params.
std::unique_ptr<BackForwardCacheCanStoreTreeResult>
PopulateReasonsForDocumentAndDescendants(
RenderFrameHostImpl* rfh,
const url::Origin& main_origin,
BackForwardCacheCanStoreDocumentResult& flattened_result,
bool include_non_sticky,
bool create_tree);
// Populates the sticky reasons for `rfh` without recursing into subframes.
// Sticky features can't be unregistered and remain active for the rest of the
// lifetime of the page.
void PopulateStickyReasonsForDocument(
BackForwardCacheCanStoreDocumentResult& result,
RenderFrameHostImpl* rfh);
// Populates the non-sticky reasons for `rfh` without recursing into
// subframes. Non-sticky reasons mean the reasons that may be resolved later
// such as when the page releases blocking resources in pagehide.
void PopulateNonStickyReasonsForDocument(
BackForwardCacheCanStoreDocumentResult& result,
RenderFrameHostImpl* rfh);
// Updates the result to include CacheControlNoStore reasons if the flag is
// on.
void UpdateCanStoreToIncludeCacheControlNoStore(
BackForwardCacheCanStoreDocumentResult& result,
RenderFrameHostImpl* render_frame_host);
// Return the matching entry which has |page|.
BackForwardCacheImpl::Entry* FindMatchingEntry(PageImpl& page);
// If non-zero, the cache may contain at most this many entries with involving
// foregrounded processes and the remaining space can only be used by entries
// with no foregrounded processes. We can be less strict on memory usage of
// background processes because Android will kill the process if memory
// becomes scarce.
static size_t GetForegroundedEntriesCacheSize();
// Enforces a limit on the number of entries. Which entries are counted
// towards the limit depends on the values of |foregrounded_only|. If it's
// true it only considers entries that are associated with a foregrounded
// process. Otherwise all entries are considered.
size_t EnforceCacheSizeLimitInternal(size_t limit, bool foregrounded_only);
// Updates |process_to_entry_map_| with processes from |entry|. These must
// be called after adding or removing an entry in |entries_|.
void AddProcessesForEntry(Entry& entry);
void RemoveProcessesForEntry(Entry& entry);
// Returns true if the flag is on for pages with cache-control:no-store to
// get restored from back/forward cache unless cookies change.
static bool AllowStoringPagesWithCacheControlNoStore();
// Contains the set of stored Entries.
// Invariant:
// - Ordered from the most recently used to the last recently used.
// - Once the list is full, the least recently used document is evicted.
std::list<std::unique_ptr<Entry>> entries_;
// Keeps track of the observed RenderProcessHosts. This is populated
// from and kept in sync with |entries_|. The RenderProcessHosts are collected
// from each Entry's RenderViewHosts. Every RenderProcessHost in here is
// observed by |this|. Every RenderProcessHost in this is referenced by a
// RenderViewHost in the Entry and so will be valid.
std::multiset<RenderProcessHost*> observed_processes_;
// Only used in tests. Whether the BackforwardCached has been disabled for
// testing.
bool is_disabled_for_testing_ = false;
// Only used for tests. This task runner is used for precise injection in
// browser tests and for timing control.
scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_testing_;
// To enter the back-forward cache, the main document URL's must match one of
// the field trial parameter "allowed_websites". This is represented here by a
// set of host and path prefix. When |allowed_urls_| is empty, it means there
// are no restrictions on URLs.
const std::map<std::string, // URL's host,
std::vector<std::string> // URL's path prefix
>
allowed_urls_;
// This is an emergency kill switch per url to stop BFCache. The data will be
// provided via the field trial parameter "blocked_websites".
// "blocked_websites" have priority over "allowed_websites". This is
// represented here by a set of host and path prefix.
const std::map<std::string, // URL's host,
std::vector<std::string> // URL's path prefix
>
blocked_urls_;
// Data provided from the "blocked_cgi_params" feature param. If any of these
// occur in the query of the URL then the page is not eligible for caching.
// See |IsQueryAllowed|.
const std::unordered_set<std::string> blocked_cgi_params_;
const UnloadSupportStrategy unload_strategy_;
base::WeakPtrFactory<BackForwardCacheImpl> weak_factory_;
};
// Allow external code to be notified when back-forward cache is disabled for a
// RenderFrameHost. This should be used only by the testing infrastructure which
// want to know the exact reason why the cache was disabled. There can be only
// one observer.
class CONTENT_EXPORT BackForwardCacheTestDelegate {
public:
BackForwardCacheTestDelegate();
virtual ~BackForwardCacheTestDelegate();
virtual void OnDisabledForFrameWithReason(
GlobalRenderFrameHostId id,
BackForwardCache::DisabledReason reason) = 0;
};
// Represents the reasons that a subtree cannot enter BFCache as a tree with a
// node for every document in the subtree, in frame tree order. It also includes
// documents that have no blocking reason.
class CONTENT_EXPORT BackForwardCacheCanStoreTreeResult {
public:
friend class BackForwardCacheImpl;
using ChildrenVector =
std::vector<std::unique_ptr<BackForwardCacheCanStoreTreeResult>>;
BackForwardCacheCanStoreTreeResult() = delete;
BackForwardCacheCanStoreTreeResult(BackForwardCacheCanStoreTreeResult&) =
delete;
BackForwardCacheCanStoreTreeResult& operator=(
BackForwardCacheCanStoreTreeResult&&) = delete;
~BackForwardCacheCanStoreTreeResult();
// Adds reasons of this subtree's root document to the tree result from
// |BackForwardCacheCanStoreDocumentResult|.
void AddReasonsToSubtreeRootFrom(
const BackForwardCacheCanStoreDocumentResult& result);
// The reasons for this subtree's root document.
const BackForwardCacheCanStoreDocumentResult& GetDocumentResult() const {
return document_result_;
}
// The children nodes. We can access the children nodes of this
// node/document from this vector.
const ChildrenVector& GetChildren() const { return children_; }
// Whether this subtree's root document's origin is the same origin with the
// origin of the page's root document origin. Returns false if this document
// is cross-origin.
bool IsSameOrigin() const { return is_same_origin_; }
// The URL of the document corresponding to this subtree's root document.
const GURL& GetUrl() const { return url_; }
// Creates and returns an empty tree.
static std::unique_ptr<BackForwardCacheCanStoreTreeResult> CreateEmptyTree(
RenderFrameHostImpl* rfh);
private:
BackForwardCacheCanStoreTreeResult(
RenderFrameHostImpl* rfh,
const url::Origin& main_document_origin,
BackForwardCacheCanStoreDocumentResult& result_for_this_document,
ChildrenVector children);
// See |GetDocumentResult|
BackForwardCacheCanStoreDocumentResult document_result_;
// See |GetChildren|
const ChildrenVector children_;
// See |IsSameOrigin|
const bool is_same_origin_;
// See |GetUrl|
const GURL url_;
// TODO(crbug.com/1278620): Add the value of the id attribute of the iframe
// element.
};
} // namespace content
#endif // CONTENT_BROWSER_RENDERER_HOST_BACK_FORWARD_CACHE_IMPL_H_