blob: 7764baf7acdc33316437bc97dc7f2117d5a6c7a4 [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.
#include "content/browser/renderer_host/back_forward_cache_impl.h"
#include <algorithm>
#include <string>
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_process_host_observer.h"
#include "content/public/browser/visibility.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
#if defined(OS_ANDROID)
#include "content/public/browser/android/child_process_importance.h"
#endif
namespace content {
class RenderProcessHostInternalObserver;
namespace {
using blink::scheduler::WebSchedulerTrackedFeature;
// Removes the time limit for cached content. This is used on bots to identify
// accidentally passing tests.
const base::Feature kBackForwardCacheNoTimeEviction{
"BackForwardCacheNoTimeEviction", base::FEATURE_DISABLED_BY_DEFAULT};
// The default number of entries the BackForwardCache can hold per tab.
static constexpr size_t kDefaultBackForwardCacheSize = 1;
// The default number value for the "foreground_cache_size" field trial
// parameter. This parameter controls the numbers of entries associated with
// foregrounded process the BackForwardCache can hold per tab, when using the
// foreground/background cache-limiting strategy. This strategy is enabled if
// the parameter values is non-zero.
static constexpr size_t kDefaultForegroundBackForwardCacheSize = 0;
// The default time to live in seconds for documents in BackForwardCache.
static constexpr int kDefaultTimeToLiveInBackForwardCacheInSeconds = 15;
#if defined(OS_ANDROID)
bool IsProcessBindingEnabled() {
// Avoid activating BackForwardCache trial for checking the parameters
// associated with it.
if (!IsBackForwardCacheEnabled())
return false;
const std::string process_binding_param =
base::GetFieldTrialParamValueByFeature(features::kBackForwardCache,
"process_binding_strength");
return process_binding_param.empty() || process_binding_param == "DISABLE";
}
// Association of ChildProcessImportance to corresponding string names.
const base::FeatureParam<ChildProcessImportance>::Option
child_process_importance_options[] = {
{ChildProcessImportance::IMPORTANT, "IMPORTANT"},
{ChildProcessImportance::MODERATE, "MODERATE"},
{ChildProcessImportance::NORMAL, "NORMAL"}};
// Defines the binding strength for a processes holding cached pages. The value
// is read from an experiment parameter value. Ideally this would be lower than
// the one for processes holding the foreground page and similar to that of
// background tabs so that the OS will hopefully kill the foreground tab last.
// The default importance is set to MODERATE.
const base::FeatureParam<ChildProcessImportance> kChildProcessImportanceParam{
&features::kBackForwardCache, "process_binding_strength",
ChildProcessImportance::MODERATE, &child_process_importance_options};
#endif
bool IsGeolocationSupported() {
if (!IsBackForwardCacheEnabled())
return false;
static constexpr base::FeatureParam<bool> geolocation_supported(
&features::kBackForwardCache, "geolocation_supported",
true
);
return geolocation_supported.Get();
}
bool IsContentInjectionSupported() {
if (!IsBackForwardCacheEnabled())
return false;
static constexpr base::FeatureParam<bool> content_injection_supported(
&features::kBackForwardCache, "content_injection_supported", false);
return content_injection_supported.Get();
}
bool IsFileSystemSupported() {
if (!IsBackForwardCacheEnabled())
return false;
static constexpr base::FeatureParam<bool> file_system_api_supported(
&features::kBackForwardCache, "file_system_api_supported", false);
return file_system_api_supported.Get();
}
bool IsOptInHeaderRequired() {
if (!IsBackForwardCacheEnabled())
return false;
static constexpr base::FeatureParam<bool> opt_in_header_required(
&features::kBackForwardCache, "opt_in_header_required", false);
return opt_in_header_required.Get();
}
uint64_t SupportedFeaturesBitmaskImpl() {
if (!IsBackForwardCacheEnabled())
return 0;
static constexpr base::FeatureParam<std::string> supported_features(
&features::kBackForwardCache, "supported_features", "");
std::vector<std::string> tokens =
base::SplitString(supported_features.Get(), ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
uint64_t mask = 0;
for (const std::string& token : tokens) {
auto feature = blink::scheduler::StringToFeature(token);
DCHECK(feature.has_value()) << "invalid feature string: " << token;
if (feature.has_value()) {
mask |= blink::scheduler::FeatureToBit(feature.value());
}
}
return mask;
}
uint64_t SupportedFeaturesBitmask() {
static uint64_t mask = SupportedFeaturesBitmaskImpl();
return mask;
}
bool IgnoresOutstandingNetworkRequestForTesting() {
if (!IsBackForwardCacheEnabled())
return false;
static constexpr base::FeatureParam<bool>
outstanding_network_request_supported(
&features::kBackForwardCache,
"ignore_outstanding_network_request_for_testing", false);
return outstanding_network_request_supported.Get();
}
// Ignore all features that the page is using and all DisableForRenderFrameHost
// calls and force all pages to be cached. Should be used only for local testing
// and debugging -- things will break when this param is used.
bool ShouldIgnoreBlocklists() {
if (!IsBackForwardCacheEnabled())
return false;
static constexpr base::FeatureParam<bool> should_ignore_blocklists(
&features::kBackForwardCache, "should_ignore_blocklists", false);
return should_ignore_blocklists.Get();
}
enum RequestedFeatures { kAll, kOnlySticky };
uint64_t GetDisallowedFeatures(RenderFrameHostImpl* rfh,
RequestedFeatures requested_features) {
// TODO(https://crbug.com/1015784): Finalize disallowed feature list, and test
// for each disallowed feature.
constexpr uint64_t kAlwaysDisallowedFeatures =
FeatureToBit(WebSchedulerTrackedFeature::kAppBanner) |
FeatureToBit(WebSchedulerTrackedFeature::kBroadcastChannel) |
FeatureToBit(WebSchedulerTrackedFeature::kContainsPlugins) |
FeatureToBit(WebSchedulerTrackedFeature::kDedicatedWorkerOrWorklet) |
FeatureToBit(WebSchedulerTrackedFeature::kIdleManager) |
FeatureToBit(WebSchedulerTrackedFeature::kIndexedDBConnection) |
FeatureToBit(WebSchedulerTrackedFeature::kKeyboardLock) |
FeatureToBit(
WebSchedulerTrackedFeature::kOutstandingIndexedDBTransaction) |
FeatureToBit(WebSchedulerTrackedFeature::kPaymentManager) |
FeatureToBit(WebSchedulerTrackedFeature::kPictureInPicture) |
FeatureToBit(WebSchedulerTrackedFeature::kPortal) |
FeatureToBit(WebSchedulerTrackedFeature::kPrinting) |
FeatureToBit(
WebSchedulerTrackedFeature::kRequestedAudioCapturePermission) |
FeatureToBit(WebSchedulerTrackedFeature::
kRequestedBackForwardCacheBlockedSensors) |
FeatureToBit(
WebSchedulerTrackedFeature::kRequestedBackgroundWorkPermission) |
FeatureToBit(WebSchedulerTrackedFeature::kRequestedMIDIPermission) |
FeatureToBit(
WebSchedulerTrackedFeature::kRequestedNotificationsPermission) |
FeatureToBit(
WebSchedulerTrackedFeature::kRequestedVideoCapturePermission) |
FeatureToBit(WebSchedulerTrackedFeature::kSharedWorker) |
FeatureToBit(WebSchedulerTrackedFeature::kWebOTPService) |
FeatureToBit(WebSchedulerTrackedFeature::kSpeechRecognizer) |
FeatureToBit(WebSchedulerTrackedFeature::kSpeechSynthesis) |
FeatureToBit(WebSchedulerTrackedFeature::kWebDatabase) |
FeatureToBit(WebSchedulerTrackedFeature::kWebHID) |
FeatureToBit(WebSchedulerTrackedFeature::kWebLocks) |
FeatureToBit(WebSchedulerTrackedFeature::kWebRTC) |
FeatureToBit(WebSchedulerTrackedFeature::kWebShare) |
FeatureToBit(WebSchedulerTrackedFeature::kWebSocket) |
FeatureToBit(WebSchedulerTrackedFeature::kWebVR) |
FeatureToBit(WebSchedulerTrackedFeature::kWebXR) |
FeatureToBit(
WebSchedulerTrackedFeature::kMediaSessionImplOnServiceCreated);
uint64_t result = kAlwaysDisallowedFeatures;
if (!IsGeolocationSupported()) {
result |= FeatureToBit(
WebSchedulerTrackedFeature::kRequestedGeolocationPermission);
}
if (!IsContentInjectionSupported()) {
result |= FeatureToBit(WebSchedulerTrackedFeature::kIsolatedWorldScript) |
FeatureToBit(WebSchedulerTrackedFeature::kInjectedStyleSheet);
}
if (!IgnoresOutstandingNetworkRequestForTesting()) {
result |=
FeatureToBit(
WebSchedulerTrackedFeature::kOutstandingNetworkRequestOthers) |
FeatureToBit(
WebSchedulerTrackedFeature::kOutstandingNetworkRequestFetch) |
FeatureToBit(WebSchedulerTrackedFeature::kOutstandingNetworkRequestXHR);
}
if (!IsFileSystemSupported()) {
result |= FeatureToBit(WebSchedulerTrackedFeature::kWebFileSystem);
}
if (requested_features == RequestedFeatures::kOnlySticky) {
// Remove all non-sticky features from |result|.
result &= blink::scheduler::StickyFeaturesBitmask();
}
result &= ~SupportedFeaturesBitmask();
return result;
}
// The BackForwardCache feature is controlled via an experiment. This function
// returns the allowed URL list where it is enabled.
std::string GetAllowedURLList() {
// Avoid activating BackForwardCache trial for checking the parameters
// associated with it.
if (!IsBackForwardCacheEnabled()) {
if (base::FeatureList::IsEnabled(
kRecordBackForwardCacheMetricsWithoutEnabling)) {
return base::GetFieldTrialParamValueByFeature(
kRecordBackForwardCacheMetricsWithoutEnabling, "allowed_websites");
}
return "";
}
return base::GetFieldTrialParamValueByFeature(features::kBackForwardCache,
"allowed_websites");
}
// To enter the BackForwardCache the URL of a document must have a host and a
// path matching with at least one URL in this map. We represent/format the
// string associated with parameter as comma separated urls.
std::map<std::string, std::vector<std::string>> GetAllowedURLs() {
std::map<std::string, std::vector<std::string>> allowed_urls;
for (auto& it :
base::SplitString(GetAllowedURLList(), ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_ALL)) {
GURL url = GURL(it);
allowed_urls[url.host()].emplace_back(url.path());
}
return allowed_urls;
}
BackForwardCacheTestDelegate* g_bfcache_disabled_test_observer = nullptr;
void RestoreBrowserControlsState(RenderFrameHostImpl* cached_rfh) {
auto* current_rfh =
cached_rfh->frame_tree_node()->render_manager()->current_frame_host();
DCHECK_NE(current_rfh, cached_rfh);
float prev_top_controls_shown_ratio = current_rfh->GetRenderWidgetHost()
->render_frame_metadata_provider()
->LastRenderFrameMetadata()
.top_controls_shown_ratio;
if (prev_top_controls_shown_ratio < 1) {
// Make sure the state in the restored renderer matches the current one.
// If we currently aren't showing the controls let the cached renderer
// know, so that it then reacts correctly to the SHOW controls message
// that might follow during DidCommitNavigation.
cached_rfh->UpdateBrowserControlsState(
cc::BrowserControlsState::kBoth, cc::BrowserControlsState::kHidden,
// Do not animate as we want this to happen "instantaneously"
false);
}
}
void RequestRecordTimeToVisible(RenderFrameHostImpl* rfh,
base::TimeTicks navigation_start) {
// Make sure we record only when the frame is not in hidden state to avoid
// cases like page navigating back with window.history.back(), while being
// hidden.
if (rfh->delegate()->GetVisibility() != Visibility::HIDDEN) {
rfh->GetView()->SetRecordContentToVisibleTimeRequest(
navigation_start, false /* destination_is_loaded */,
false /* show_reason_tab_switching */,
false /* show_reason_unoccluded */,
true /* show_reason_bfcache_restore */);
}
}
// Returns true if any of the processes associated with the RenderViewHosts in
// this Entry are foregrounded.
bool HasForegroundedProcess(BackForwardCacheImpl::Entry& entry) {
for (auto* rvh : entry.render_view_hosts) {
if (!rvh->GetProcess()->IsProcessBackgrounded()) {
return true;
}
}
return false;
}
} // namespace
// static
BackForwardCacheImpl::MessageHandlingPolicyWhenCached
BackForwardCacheImpl::GetChannelAssociatedMessageHandlingPolicy() {
// Avoid activating BackForwardCache trial for checking the parameters
// associated with it.
if (!IsBackForwardCacheEnabled())
return kMessagePolicyNone;
static constexpr char kFieldTrialParam[] = "message_handling_when_cached";
auto param = base::GetFieldTrialParamValueByFeature(
features::kBackForwardCache, kFieldTrialParam);
if (param.empty() || param == "log") {
return kMessagePolicyLog;
} else if (param == "none") {
return kMessagePolicyNone;
} else if (param == "dump") {
return kMessagePolicyDump;
} else {
DLOG(WARNING) << "Failed to parse field trial param " << kFieldTrialParam
<< " with string value " << param
<< " under feature kBackForwardCache"
<< features::kBackForwardCache.name;
return kMessagePolicyLog;
}
}
BackForwardCacheImpl::Entry::Entry(
std::unique_ptr<RenderFrameHostImpl> rfh,
RenderFrameProxyHostMap proxies,
std::set<RenderViewHostImpl*> render_view_hosts)
: render_frame_host(std::move(rfh)),
proxy_hosts(std::move(proxies)),
render_view_hosts(std::move(render_view_hosts)) {}
BackForwardCacheImpl::Entry::~Entry() = default;
void BackForwardCacheImpl::RenderProcessBackgroundedChanged(
RenderProcessHostImpl* host) {
EnforceCacheSizeLimit();
}
BackForwardCacheTestDelegate::BackForwardCacheTestDelegate() {
DCHECK(!g_bfcache_disabled_test_observer);
g_bfcache_disabled_test_observer = this;
}
BackForwardCacheTestDelegate::~BackForwardCacheTestDelegate() {
DCHECK_EQ(g_bfcache_disabled_test_observer, this);
g_bfcache_disabled_test_observer = nullptr;
}
BackForwardCacheImpl::BackForwardCacheImpl()
: allowed_urls_(GetAllowedURLs()), weak_factory_(this) {}
BackForwardCacheImpl::~BackForwardCacheImpl() {
Shutdown();
}
base::TimeDelta BackForwardCacheImpl::GetTimeToLiveInBackForwardCache() {
// We use the following order of priority if multiple values exist:
// - The programmatical value set in params. Used in specific tests.
// - Infinite if kBackForwardCacheNoTimeEviction is enabled.
// - Default value otherwise, kDefaultTimeToLiveInBackForwardCacheInSeconds.
if (base::FeatureList::IsEnabled(kBackForwardCacheNoTimeEviction) &&
GetFieldTrialParamValueByFeature(features::kBackForwardCache,
"TimeToLiveInBackForwardCacheInSeconds")
.empty()) {
return base::TimeDelta::Max();
}
return base::TimeDelta::FromSeconds(base::GetFieldTrialParamByFeatureAsInt(
features::kBackForwardCache, "TimeToLiveInBackForwardCacheInSeconds",
kDefaultTimeToLiveInBackForwardCacheInSeconds));
}
// static
size_t BackForwardCacheImpl::GetCacheSize() {
if (!IsBackForwardCacheEnabled())
return 0;
return base::GetFieldTrialParamByFeatureAsInt(
features::kBackForwardCache, "cache_size", kDefaultBackForwardCacheSize);
}
// static
size_t BackForwardCacheImpl::GetForegroundedEntriesCacheSize() {
if (!IsBackForwardCacheEnabled())
return 0;
return base::GetFieldTrialParamByFeatureAsInt(
features::kBackForwardCache, "foreground_cache_size",
kDefaultForegroundBackForwardCacheSize);
}
// static
bool BackForwardCacheImpl::UsingForegroundBackgroundCacheSizeLimit() {
return GetForegroundedEntriesCacheSize() > 0;
}
BackForwardCacheCanStoreDocumentResult BackForwardCacheImpl::CanStorePageNow(
RenderFrameHostImpl* rfh) {
BackForwardCacheCanStoreDocumentResult result =
CanPotentiallyStorePageLater(rfh);
CheckDynamicBlocklistedFeaturesOnSubtree(&result, rfh);
DVLOG(1) << "CanStorePageNow: " << rfh->GetLastCommittedURL() << " : "
<< result.ToString();
return result;
}
BackForwardCacheCanStoreDocumentResult
BackForwardCacheImpl::CanPotentiallyStorePageLater(RenderFrameHostImpl* rfh) {
BackForwardCacheCanStoreDocumentResult result;
// Use the BackForwardCache only for the main frame.
if (rfh->GetParent())
result.No(BackForwardCacheMetrics::NotRestoredReason::kNotMainFrame);
// If the the delegate doesn't support back forward cache, disable it.
if (!rfh->delegate()->IsBackForwardCacheSupported()) {
result.No(BackForwardCacheMetrics::NotRestoredReason::
kBackForwardCacheDisabledForDelegate);
}
if (!IsBackForwardCacheEnabled() || is_disabled_for_testing_ ||
// TODO(https://crbug.com/1176151): Replace with LifecycleState check once
// it tracks prerender too.
rfh->frame_tree()->is_prerendering()) {
result.No(
BackForwardCacheMetrics::NotRestoredReason::kBackForwardCacheDisabled);
// In addition to the general "BackForwardCacheDisabled" reason above, also
// track more specific reasons on why BackForwardCache is disabled.
if (IsBackForwardCacheDisabledByCommandLine()) {
result.No(BackForwardCacheMetrics::NotRestoredReason::
kBackForwardCacheDisabledByCommandLine);
}
if (!DeviceHasEnoughMemoryForBackForwardCache()) {
result.No(BackForwardCacheMetrics::NotRestoredReason::
kBackForwardCacheDisabledByLowMemory);
}
// TODO(https://crbug.com/1176151): Replace with LifecycleState check once
// it tracks prerender too.
if (rfh->frame_tree()->is_prerendering()) {
result.No(BackForwardCacheMetrics::NotRestoredReason::
kBackForwardCacheDisabledForPrerender);
}
}
// If this function is called after we navigated to a new RenderFrameHost,
// then |rfh| must already be replaced by the new RenderFrameHost. If this
// function is called before we navigated, then |rfh| must be a current
// RenderFrameHost.
bool is_current_rfh = rfh->IsCurrent();
// Two pages in the same BrowsingInstance can script each other. When a page
// can be scripted from outside, it can't enter the BackForwardCache.
//
// If the |rfh| is not a "current" RenderFrameHost anymore, the
// "RelatedActiveContentsCount" below is compared against 0, not 1. This is
// because |rfh| is not "active" itself.
//
// This check makes sure the old and new document aren't sharing the same
// BrowsingInstance. Note that the existence of related active contents might
// change in the future, but we are checking this in
// CanPotentiallyStorePageLater instead of CanStorePageNow because it's needed
// to determine whether to do a proactive BrowsingInstance swap or not, which
// should not be done if the page has related active contents.
unsigned expected_related_active_contents_count = is_current_rfh ? 1 : 0;
// We should never have fewer than expected.
DCHECK_GE(rfh->GetSiteInstance()->GetRelatedActiveContentsCount(),
expected_related_active_contents_count);
if (rfh->GetSiteInstance()->GetRelatedActiveContentsCount() >
expected_related_active_contents_count) {
result.No(BackForwardCacheMetrics::NotRestoredReason::
kRelatedActiveContentsExist);
}
// Only store documents that have successful http status code.
// Note that for error pages, |last_http_status_code| is equal to 0.
if (rfh->last_http_status_code() != net::HTTP_OK)
result.No(BackForwardCacheMetrics::NotRestoredReason::kHTTPStatusNotOK);
// Only store documents that were fetched via HTTP GET method.
if (rfh->last_http_method() != net::HttpRequestHeaders::kGetMethod)
result.No(BackForwardCacheMetrics::NotRestoredReason::kHTTPMethodNotGET);
// Do not store main document with non HTTP/HTTPS URL scheme. Among other
// things, this excludes the new tab page and all WebUI pages.
if (!rfh->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) {
result.No(
BackForwardCacheMetrics::NotRestoredReason::kSchemeNotHTTPOrHTTPS);
}
// We should not cache pages with Cache-control: no-store. Note that
// even though this is categorized as a "feature", we will check this within
// CanPotentiallyStorePageLater as it's not possible to change the HTTP
// headers, so if it's not possible to cache this page now due to this, it's
// impossible to cache this page later.
// TODO(rakina): Once we move cache-control tracking to RenderFrameHostImpl,
// change this part to use the information stored in RenderFrameHostImpl
// instead.
uint64_t cache_control_no_store_feature = FeatureToBit(
WebSchedulerTrackedFeature::kMainResourceHasCacheControlNoStore);
if (rfh->scheduler_tracked_features() & cache_control_no_store_feature) {
result.NoDueToFeatures(cache_control_no_store_feature);
}
// Only store documents that have URLs allowed through experiment.
if (!IsAllowed(rfh->GetLastCommittedURL()))
result.No(BackForwardCacheMetrics::NotRestoredReason::kDomainNotAllowed);
// TODO(crbug.com/1201653): Also implement a variant that checks for the
// existance of an `unload` handler.
if (IsOptInHeaderRequired()) {
const network::mojom::URLResponseHeadPtr& response_head =
rfh->last_response_head();
if (!response_head) {
// For the cases without `response_head`, we should have already bailed
// out of BFCache for other reasons.
DCHECK(!result.CanStore());
} else {
const network::mojom::ParsedHeadersPtr& headers =
response_head->parsed_headers;
if (!headers || !headers->bfcache_opt_in_unload) {
result.No(BackForwardCacheMetrics::NotRestoredReason::
kOptInUnloadHeaderNotPresent);
}
}
}
CanStoreRenderFrameHostLater(&result, rfh);
DVLOG(1) << "CanPotentiallyStorePageLater: " << rfh->GetLastCommittedURL()
<< " : " << result.ToString();
return result;
}
// Recursively checks whether this RenderFrameHost and all child frames
// can be cached later.
void BackForwardCacheImpl::CanStoreRenderFrameHostLater(
BackForwardCacheCanStoreDocumentResult* result,
RenderFrameHostImpl* rfh) {
// If the rfh has ever granted media access, prevent it from entering cache.
// TODO(crbug.com/989379): Consider only blocking when there's an active
// media stream.
if (rfh->was_granted_media_access()) {
result->No(
BackForwardCacheMetrics::NotRestoredReason::kWasGrantedMediaAccess);
}
if (rfh->IsBackForwardCacheDisabled() && !ShouldIgnoreBlocklists()) {
result->NoDueToDisableForRenderFrameHostCalled(
rfh->back_forward_cache_disabled_reasons());
}
// Do not store documents if they have inner WebContents.
if (rfh->IsOuterDelegateFrame())
result->No(BackForwardCacheMetrics::NotRestoredReason::kHaveInnerContents);
// When it's not the final decision for putting a page in the back-forward
// cache, we should only consider "sticky" features here - features that
// will always result in a page becoming ineligible for back-forward cache
// since the first time it's used.
if (uint64_t banned_features =
GetDisallowedFeatures(rfh, RequestedFeatures::kOnlySticky) &
rfh->scheduler_tracked_features()) {
if (!ShouldIgnoreBlocklists()) {
result->NoDueToFeatures(banned_features);
}
}
for (size_t i = 0; i < rfh->child_count(); i++)
CanStoreRenderFrameHostLater(result,
rfh->child_at(i)->current_frame_host());
}
// Recursively checks dynamic states that might affect whether this
// RenderFrameHost and all child frames can be cached right now.
void BackForwardCacheImpl::CheckDynamicBlocklistedFeaturesOnSubtree(
BackForwardCacheCanStoreDocumentResult* result,
RenderFrameHostImpl* rfh) {
if (!rfh->IsDOMContentLoaded())
result->No(BackForwardCacheMetrics::NotRestoredReason::kLoading);
// Check for banned features currently being used. Note that unlike the check
// in CanStoreRenderFrameHostLater, we are checking all banned features here
// (not only the "sticky" features), because this time we're making a decision
// on whether we should store a page in the back-forward cache or not.
if (uint64_t banned_features =
GetDisallowedFeatures(rfh, RequestedFeatures::kAll) &
rfh->scheduler_tracked_features()) {
bool should_ignore_features_for_now =
CheckFeatureUsageOnlyAfterAck() &&
!rfh->render_view_host()->DidReceiveBackForwardCacheAck();
if (!ShouldIgnoreBlocklists() && !should_ignore_features_for_now) {
result->NoDueToFeatures(banned_features);
}
}
bool has_navigation_request = rfh->frame_tree_node()->navigation_request() ||
rfh->HasPendingCommitNavigation();
// Do not cache if we have navigations in any of the subframes.
if (rfh->GetParent() && has_navigation_request) {
result->No(
BackForwardCacheMetrics::NotRestoredReason::kSubframeIsNavigating);
}
for (size_t i = 0; i < rfh->child_count(); i++)
CheckDynamicBlocklistedFeaturesOnSubtree(
result, rfh->child_at(i)->current_frame_host());
}
void BackForwardCacheImpl::StoreEntry(
std::unique_ptr<BackForwardCacheImpl::Entry> entry) {
TRACE_EVENT0("navigation", "BackForwardCache::StoreEntry");
DCHECK(CanStorePageNow(entry->render_frame_host.get()));
#if defined(OS_ANDROID)
if (!IsProcessBindingEnabled()) {
// Set the priority of the main frame on entering the back-forward cache to
// make sure the page gets evicted instead of foreground tab. This might not
// become the effective priority of the process if it owns other higher
// priority RenderWidgetHost. We don't need to reset the priority in
// RestoreEntry as it is taken care by WebContentsImpl::NotifyFrameSwapped
// on restoration.
RenderWidgetHostImpl* rwh = entry->render_frame_host->GetRenderWidgetHost();
ChildProcessImportance current_importance = rwh->importance();
rwh->SetImportance(
std::min(current_importance, kChildProcessImportanceParam.Get()));
}
#endif
entry->render_frame_host->DidEnterBackForwardCache();
entries_.push_front(std::move(entry));
AddProcessesForEntry(*entries_.front());
EnforceCacheSizeLimit();
}
void BackForwardCacheImpl::EnforceCacheSizeLimit() {
if (!IsBackForwardCacheEnabled())
return;
if (UsingForegroundBackgroundCacheSizeLimit()) {
// First enforce the foregrounded limit. The idea is that we need to
// strictly enforce the limit on pages using foregrounded processes because
// Android will not kill a foregrounded process, however it will kill a
// backgrounded process if there is memory pressue, so we can allow more of
// those to be kept in the cache.
EnforceCacheSizeLimitInternal(GetForegroundedEntriesCacheSize(),
/*foregrounded_only=*/true);
}
EnforceCacheSizeLimitInternal(GetCacheSize(),
/*foregrounded_only=*/false);
}
size_t BackForwardCacheImpl::EnforceCacheSizeLimitInternal(
size_t limit,
bool foregrounded_only) {
size_t count = 0;
for (auto& stored_entry : entries_) {
if (stored_entry->render_frame_host->is_evicted_from_back_forward_cache())
continue;
if (foregrounded_only && !HasForegroundedProcess(*stored_entry))
continue;
if (++count > limit) {
stored_entry->render_frame_host->EvictFromBackForwardCacheWithReason(
foregrounded_only
? BackForwardCacheMetrics::NotRestoredReason::
kForegroundCacheLimit
: BackForwardCacheMetrics::NotRestoredReason::kCacheLimit);
}
}
return count;
}
std::unique_ptr<BackForwardCacheImpl::Entry> BackForwardCacheImpl::RestoreEntry(
int navigation_entry_id,
blink::mojom::PageRestoreParamsPtr page_restore_params) {
TRACE_EVENT0("navigation", "BackForwardCache::RestoreEntry");
// Select the RenderFrameHostImpl matching the navigation entry.
auto matching_entry = std::find_if(
entries_.begin(), entries_.end(),
[navigation_entry_id](std::unique_ptr<Entry>& entry) {
return entry->render_frame_host->nav_entry_id() == navigation_entry_id;
});
// Not found.
if (matching_entry == entries_.end())
return nullptr;
// Don't restore an evicted frame.
if ((*matching_entry)
->render_frame_host->is_evicted_from_back_forward_cache())
return nullptr;
std::unique_ptr<Entry> entry = std::move(*matching_entry);
entries_.erase(matching_entry);
RemoveProcessesForEntry(*entry);
entry->page_restore_params = std::move(page_restore_params);
RequestRecordTimeToVisible(entry->render_frame_host.get(),
entry->page_restore_params->navigation_start);
entry->render_frame_host->WillLeaveBackForwardCache();
RestoreBrowserControlsState(entry->render_frame_host.get());
return entry;
}
void BackForwardCacheImpl::Flush() {
TRACE_EVENT0("navigation", "BackForwardCache::Flush");
for (std::unique_ptr<Entry>& entry : entries_) {
entry->render_frame_host->EvictFromBackForwardCacheWithReason(
BackForwardCacheMetrics::NotRestoredReason::kCacheFlushed);
}
}
void BackForwardCacheImpl::Shutdown() {
if (UsingForegroundBackgroundCacheSizeLimit()) {
for (auto& entry : entries_)
RemoveProcessesForEntry(*entry.get());
}
entries_.clear();
}
void BackForwardCacheImpl::EvictFramesInRelatedSiteInstances(
SiteInstance* site_instance) {
for (std::unique_ptr<Entry>& entry : entries_) {
if (entry->render_frame_host->GetSiteInstance()->IsRelatedSiteInstance(
site_instance)) {
entry->render_frame_host->EvictFromBackForwardCacheWithReason(
BackForwardCacheMetrics::NotRestoredReason::
kConflictingBrowsingInstance);
}
}
}
void BackForwardCacheImpl::PostTaskToDestroyEvictedFrames() {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&BackForwardCacheImpl::DestroyEvictedFrames,
weak_factory_.GetWeakPtr()));
}
// static
bool BackForwardCache::IsBackForwardCacheFeatureEnabled() {
return IsBackForwardCacheEnabled();
}
// static
void BackForwardCache::DisableForRenderFrameHost(
RenderFrameHost* render_frame_host,
BackForwardCache::DisabledReason reason) {
DisableForRenderFrameHost(static_cast<RenderFrameHostImpl*>(render_frame_host)
->GetGlobalFrameRoutingId(),
reason);
}
// static
void BackForwardCache::DisableForRenderFrameHost(
GlobalFrameRoutingId id,
BackForwardCache::DisabledReason reason) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (g_bfcache_disabled_test_observer)
g_bfcache_disabled_test_observer->OnDisabledForFrameWithReason(id, reason);
if (auto* rfh = RenderFrameHostImpl::FromID(id))
rfh->DisableBackForwardCache(reason);
}
void BackForwardCacheImpl::DisableForTesting(DisableForTestingReason reason) {
is_disabled_for_testing_ = true;
// Flush all the entries to make sure there are no entries in the cache after
// DisableForTesting() is called.
Flush();
}
const std::list<std::unique_ptr<BackForwardCacheImpl::Entry>>&
BackForwardCacheImpl::GetEntries() {
return entries_;
}
BackForwardCacheImpl::Entry* BackForwardCacheImpl::GetEntry(
int navigation_entry_id) {
auto matching_entry = std::find_if(
entries_.begin(), entries_.end(),
[navigation_entry_id](std::unique_ptr<Entry>& entry) {
return entry->render_frame_host->nav_entry_id() == navigation_entry_id;
});
if (matching_entry == entries_.end())
return nullptr;
// Don't return the frame if it is evicted.
if ((*matching_entry)
->render_frame_host->is_evicted_from_back_forward_cache())
return nullptr;
return (*matching_entry).get();
}
void BackForwardCacheImpl::AddProcessesForEntry(Entry& entry) {
if (!UsingForegroundBackgroundCacheSizeLimit())
return;
for (auto* rvh : entry.render_view_hosts) {
RenderProcessHostImpl* process =
static_cast<RenderProcessHostImpl*>(rvh->GetProcess());
if (observed_processes_.find(process) == observed_processes_.end())
process->AddInternalObserver(this);
observed_processes_.insert(process);
}
}
void BackForwardCacheImpl::RemoveProcessesForEntry(Entry& entry) {
if (!UsingForegroundBackgroundCacheSizeLimit())
return;
for (auto* rvh : entry.render_view_hosts) {
RenderProcessHostImpl* process =
static_cast<RenderProcessHostImpl*>(rvh->GetProcess());
// Remove 1 instance of this process from the multiset.
observed_processes_.erase(observed_processes_.find(process));
if (observed_processes_.find(process) == observed_processes_.end())
process->RemoveInternalObserver(this);
}
}
void BackForwardCacheImpl::DestroyEvictedFrames() {
TRACE_EVENT0("navigation", "BackForwardCache::DestroyEvictedFrames");
if (entries_.empty())
return;
base::EraseIf(entries_, [this](std::unique_ptr<Entry>& entry) {
if (entry->render_frame_host->is_evicted_from_back_forward_cache()) {
RemoveProcessesForEntry(*entry);
return true;
}
return false;
});
}
bool BackForwardCacheImpl::IsAllowed(const GURL& current_url) {
// By convention, when |allowed_urls_| is empty, it means there are no
// restrictions about what RenderFrameHost can enter the BackForwardCache.
if (allowed_urls_.empty())
return true;
// Checking for each url in the |allowed_urls_|, if the current_url matches
// the corresponding host and path is the prefix of the allowed url path. We
// only check for host and path and not any other components including url
// scheme here.
const auto& entry = allowed_urls_.find(current_url.host());
if (entry != allowed_urls_.end()) {
for (auto allowed_path : entry->second) {
if (base::StartsWith(current_url.path_piece(), allowed_path))
return true;
}
}
return false;
}
bool BackForwardCacheImpl::CheckFeatureUsageOnlyAfterAck() {
if (!IsBackForwardCacheEnabled())
return false;
return base::GetFieldTrialParamByFeatureAsBool(
features::kBackForwardCache, "check_eligibility_after_pagehide", false);
}
bool BackForwardCacheImpl::IsMediaSessionImplOnServiceCreatedAllowed() {
return (SupportedFeaturesBitmask() &
FeatureToBit(
WebSchedulerTrackedFeature::kMediaSessionImplOnServiceCreated)) !=
0;
}
bool BackForwardCache::DisabledReason::operator<(
const DisabledReason& other) const {
return std::tie(source, id) < std::tie(other.source, other.id);
}
bool BackForwardCache::DisabledReason::operator==(
const DisabledReason& other) const {
return std::tie(source, id) == std::tie(other.source, other.id);
}
bool BackForwardCache::DisabledReason::operator!=(
const DisabledReason& other) const {
return !(*this == other);
}
} // namespace content