blob: 78bbfb6de1b636b0204af55a0a6a1a72bf6d6f40 [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/frame_host/back_forward_cache.h"
#include <unordered_set>
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_delegate.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/common/page_messages.h"
#include "content/public/common/navigation_policy.h"
#include "net/http/http_status_code.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
namespace content {
namespace {
using blink::scheduler::WebSchedulerTrackedFeature;
// The number of pages the BackForwardCache can hold per tab.
static constexpr size_t kBackForwardCacheLimit = 1;
// Converts a WebSchedulerTrackedFeature to a bit for use in a bitmask.
constexpr uint64_t ToFeatureBit(WebSchedulerTrackedFeature feature) {
return 1 << static_cast<uint32_t>(feature);
}
void SetPageFrozenImpl(
RenderFrameHostImpl* render_frame_host,
bool frozen,
std::unordered_set<RenderViewHostImpl*>* render_view_hosts) {
RenderViewHostImpl* render_view_host = render_frame_host->render_view_host();
// (Un)Freeze the frame's page if it is not (un)frozen yet.
if (render_view_hosts->find(render_view_host) == render_view_hosts->end()) {
// The state change for bfcache is:
// PageHidden -> PageFrozen -> PageResumed -> PageShown.
//
// See: https://developers.google.com/web/updates/2018/07/page-lifecycle-api
int rvh_routing_id = render_view_host->GetRoutingID();
if (frozen) {
render_view_host->Send(
new PageMsg_PutPageIntoBackForwardCache(rvh_routing_id));
} else {
render_view_host->Send(
new PageMsg_RestorePageFromBackForwardCache(rvh_routing_id));
}
render_view_hosts->insert(render_view_host);
}
// Recurse on |render_frame_host|'s children.
for (size_t index = 0; index < render_frame_host->child_count(); ++index) {
RenderFrameHostImpl* child_frame_host =
render_frame_host->child_at(index)->current_frame_host();
SetPageFrozenImpl(child_frame_host, frozen, render_view_hosts);
}
}
// Recursively checks whether this RenderFrameHost and all child frames
// can be cached.
bool CanStoreRenderFrameHost(RenderFrameHostImpl* rfh,
uint64_t disallowed_features) {
// For the main frame, we don't check loading at the FrameTreeNode level,
// because the FrameTreeNode has already begun loading the page being
// navigated to.
bool is_loading = rfh->frame_tree_node()->IsMainFrame()
? rfh->is_loading()
: rfh->frame_tree_node()->IsLoading();
if (is_loading)
return false;
// 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())
return false;
// Don't cache the page if it uses any disallowed features.
if (disallowed_features & rfh->scheduler_tracked_features())
return false;
for (size_t i = 0; i < rfh->child_count(); i++) {
if (!CanStoreRenderFrameHost(rfh->child_at(i)->current_frame_host(),
disallowed_features)) {
return false;
}
}
return true;
}
uint64_t GetDisallowedFeatures() {
// TODO(lowell): Finalize disallowed feature list, and test for each
// disallowed feature.
constexpr uint64_t kAlwaysDisallowedFeatures =
ToFeatureBit(WebSchedulerTrackedFeature::kWebRTC) |
ToFeatureBit(WebSchedulerTrackedFeature::kContainsPlugins) |
ToFeatureBit(WebSchedulerTrackedFeature::kDedicatedWorkerOrWorklet) |
ToFeatureBit(
WebSchedulerTrackedFeature::kOutstandingIndexedDBTransaction) |
ToFeatureBit(
WebSchedulerTrackedFeature::kHasScriptableFramesInMultipleTabs) |
ToFeatureBit(
WebSchedulerTrackedFeature::kRequestedNotificationsPermission) |
ToFeatureBit(WebSchedulerTrackedFeature::kRequestedMIDIPermission) |
ToFeatureBit(
WebSchedulerTrackedFeature::kRequestedAudioCapturePermission) |
ToFeatureBit(
WebSchedulerTrackedFeature::kRequestedVideoCapturePermission) |
ToFeatureBit(WebSchedulerTrackedFeature::kRequestedSensorsPermission) |
ToFeatureBit(
WebSchedulerTrackedFeature::kRequestedBackgroundWorkPermission) |
ToFeatureBit(WebSchedulerTrackedFeature::kBroadcastChannel) |
ToFeatureBit(WebSchedulerTrackedFeature::kIndexedDBConnection) |
ToFeatureBit(WebSchedulerTrackedFeature::kWebGL) |
ToFeatureBit(WebSchedulerTrackedFeature::kWebVR) |
ToFeatureBit(WebSchedulerTrackedFeature::kWebXR);
uint64_t result = kAlwaysDisallowedFeatures;
if (!base::FeatureList::IsEnabled(kBackForwardCacheWithServiceWorker)) {
result |=
ToFeatureBit(WebSchedulerTrackedFeature::kServiceWorkerControlledPage);
}
return result;
}
} // namespace
const base::Feature kBackForwardCacheWithServiceWorker = {
"BackForwardCacheWithServiceWorker", base::FEATURE_DISABLED_BY_DEFAULT};
BackForwardCache::BackForwardCache() = default;
BackForwardCache::~BackForwardCache() = default;
bool BackForwardCache::CanStoreDocument(RenderFrameHostImpl* rfh) {
// Use the BackForwardCache only for the main frame.
if (rfh->GetParent())
return false;
if (!IsBackForwardCacheEnabled() || is_disabled_for_testing_)
return false;
// Two pages in the same BrowsingInstance can script each other. When a page
// can be scripted from outside, it can't enter the BackForwardCache.
//
// The "RelatedActiveContentsCount" below is compared against 0, not 1. This
// is because the |rfh| is not a "current" RenderFrameHost anymore. It is not
// "active" itself.
//
// This check makes sure the old and new document aren't sharing the same
// BrowsingInstance.
if (rfh->GetSiteInstance()->GetRelatedActiveContentsCount() != 0)
return false;
// 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)
return false;
// Do store main document with non HTTP/HTTPS URL scheme. In particular, this
// excludes the new tab page.
if (!rfh->GetLastCommittedURL().SchemeIsHTTPOrHTTPS())
return false;
return CanStoreRenderFrameHost(rfh, GetDisallowedFeatures());
}
void BackForwardCache::StoreDocument(std::unique_ptr<RenderFrameHostImpl> rfh) {
DCHECK(CanStoreDocument(rfh.get()));
rfh->EnterBackForwardCache();
render_frame_hosts_.push_front(std::move(rfh));
size_t size_limit = cache_size_limit_for_testing_
? cache_size_limit_for_testing_
: kBackForwardCacheLimit;
// Remove the last recently used document if the BackForwardCache list is
// full.
if (render_frame_hosts_.size() > size_limit) {
// TODO(arthursonzogni): Handle RenderFrame deletion appropriately.
render_frame_hosts_.pop_back();
}
}
void BackForwardCache::Freeze(RenderFrameHostImpl* main_rfh) {
// Several RenderFrameHost can live under the same RenderViewHost.
// |frozen_render_view_hosts| keeps track of the ones that freezing has been
// applied to.
std::unordered_set<RenderViewHostImpl*> frozen_render_view_hosts;
SetPageFrozenImpl(main_rfh, /*frozen = */ true, &frozen_render_view_hosts);
}
void BackForwardCache::Resume(RenderFrameHostImpl* main_rfh) {
// |unfrozen_render_view_hosts| keeps track of the ones that resuming has
// been applied to.
std::unordered_set<RenderViewHostImpl*> unfrozen_render_view_hosts;
SetPageFrozenImpl(main_rfh, /*frozen = */ false, &unfrozen_render_view_hosts);
}
void BackForwardCache::EvictDocument(RenderFrameHostImpl* render_frame_host) {
auto matching_rfh = std::find_if(
render_frame_hosts_.begin(), render_frame_hosts_.end(),
[render_frame_host](std::unique_ptr<RenderFrameHostImpl>& rfh) {
return rfh.get() == render_frame_host;
});
DCHECK(matching_rfh != render_frame_hosts_.end());
render_frame_hosts_.erase(matching_rfh);
}
std::unique_ptr<RenderFrameHostImpl> BackForwardCache::RestoreDocument(
int navigation_entry_id) {
// Select the RenderFrameHostImpl matching the navigation entry.
auto matching_rfh = std::find_if(
render_frame_hosts_.begin(), render_frame_hosts_.end(),
[navigation_entry_id](std::unique_ptr<RenderFrameHostImpl>& rfh) {
return rfh->nav_entry_id() == navigation_entry_id;
});
// Not found.
if (matching_rfh == render_frame_hosts_.end())
return nullptr;
std::unique_ptr<RenderFrameHostImpl> rfh = std::move(*matching_rfh);
render_frame_hosts_.erase(matching_rfh);
rfh->LeaveBackForwardCache();
return rfh;
}
void BackForwardCache::Flush() {
render_frame_hosts_.clear();
}
void BackForwardCache::DisableForTesting(DisableForTestingReason reason) {
is_disabled_for_testing_ = true;
// This could happen if a test populated some pages in the cache, then
// called DisableForTesting(). This is not something we currently expect tests
// to do.
DCHECK(render_frame_hosts_.empty());
}
RenderFrameHostImpl* BackForwardCache::GetDocument(int navigation_entry_id) {
auto matching_rfh = std::find_if(
render_frame_hosts_.begin(), render_frame_hosts_.end(),
[navigation_entry_id](std::unique_ptr<RenderFrameHostImpl>& rfh) {
return rfh->nav_entry_id() == navigation_entry_id;
});
if (matching_rfh == render_frame_hosts_.end())
return nullptr;
return (*matching_rfh).get();
}
} // namespace content