BackForwardCache.cpp [plain text]
#include "config.h"
#include "BackForwardCache.h"
#include "ApplicationCacheHost.h"
#include "BackForwardController.h"
#include "CachedPage.h"
#include "DOMWindow.h"
#include "DeviceMotionController.h"
#include "DeviceOrientationController.h"
#include "DiagnosticLoggingClient.h"
#include "DiagnosticLoggingKeys.h"
#include "Document.h"
#include "DocumentLoader.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "FrameView.h"
#include "HistoryController.h"
#include "IgnoreOpensDuringUnloadCountIncrementer.h"
#include "Logging.h"
#include "Page.h"
#include "Quirks.h"
#include "ScriptDisallowedScope.h"
#include "Settings.h"
#include "SubframeLoader.h"
#include <pal/Logging.h>
#include <wtf/MemoryPressureHandler.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/SetForScope.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringConcatenate.h>
namespace WebCore {
#define PCLOG(...) LOG(BackForwardCache, "%*s%s", indentLevel*4, "", makeString(__VA_ARGS__).utf8().data())
static inline void logBackForwardCacheFailureDiagnosticMessage(DiagnosticLoggingClient& client, const String& reason)
{
client.logDiagnosticMessage(DiagnosticLoggingKeys::backForwardCacheFailureKey(), reason, ShouldSample::No);
}
static inline void logBackForwardCacheFailureDiagnosticMessage(Page* page, const String& reason)
{
if (!page)
return;
logBackForwardCacheFailureDiagnosticMessage(page->diagnosticLoggingClient(), reason);
}
static bool canCacheFrame(Frame& frame, DiagnosticLoggingClient& diagnosticLoggingClient, unsigned indentLevel)
{
PCLOG("+---");
FrameLoader& frameLoader = frame.loader();
if (!frame.isMainFrame() && frameLoader.state() == FrameState::Provisional) {
PCLOG(" -Frame is in provisional load stage");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::provisionalLoadKey());
return false;
}
if (frame.isMainFrame() && frameLoader.stateMachine().isDisplayingInitialEmptyDocument()) {
PCLOG(" -MainFrame is displaying initial empty document");
return false;
}
if (!frame.document()) {
PCLOG(" -Frame has no document");
return false;
}
if (frame.document()->shouldPreventEnteringBackForwardCacheForTesting()) {
PCLOG(" -Back/Forward caching is disabled for testing");
return false;
}
if (!frame.document()->frame()) {
PCLOG(" -Document is detached from frame");
ASSERT_NOT_REACHED();
return false;
}
DocumentLoader* documentLoader = frameLoader.documentLoader();
if (!documentLoader) {
PCLOG(" -There is no DocumentLoader object");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noDocumentLoaderKey());
return false;
}
URL currentURL = documentLoader->url();
URL newURL = frameLoader.provisionalDocumentLoader() ? frameLoader.provisionalDocumentLoader()->url() : URL();
if (!newURL.isEmpty())
PCLOG(" Determining if frame can be cached navigating from (", currentURL.string(), ") to (", newURL.string(), "):");
else
PCLOG(" Determining if subframe with URL (", currentURL.string(), ") can be cached:");
bool isCacheable = true;
if (frame.isMainFrame() && frame.document()->quirks().shouldBypassBackForwardCache()) {
PCLOG(" -Disabled by site-specific quirk");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::siteSpecificQuirkKey());
isCacheable = false;
}
if (documentLoader->substituteData().isValid() && !documentLoader->substituteData().failingURL().isEmpty()) {
PCLOG(" -Frame is an error page");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isErrorPageKey());
isCacheable = false;
}
if (frameLoader.subframeLoader().containsPlugins() && !frame.page()->settings().backForwardCacheSupportsPlugins()) {
PCLOG(" -Frame contains plugins");
isCacheable = false;
}
if (frame.isMainFrame() && frame.document() && frame.document()->url().protocolIs("https") && documentLoader->response().cacheControlContainsNoStore()) {
PCLOG(" -Frame is HTTPS, and cache control prohibits storing");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::httpsNoStoreKey());
isCacheable = false;
}
if (frame.isMainFrame() && !frameLoader.history().currentItem()) {
PCLOG(" -Main frame has no current history item");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noCurrentHistoryItemKey());
isCacheable = false;
}
if (frame.isMainFrame() && frame.view() && !frame.view()->isVisuallyNonEmpty()) {
PCLOG(" -Main frame is visually empty");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::visuallyEmptyKey());
isCacheable = false;
}
if (frameLoader.quickRedirectComing()) {
PCLOG(" -Quick redirect is coming");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::quirkRedirectComingKey());
isCacheable = false;
}
if (documentLoader->isLoading()) {
PCLOG(" -DocumentLoader is still loading");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isLoadingKey());
isCacheable = false;
}
if (documentLoader->isStopping()) {
PCLOG(" -DocumentLoader is in the middle of stopping");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::documentLoaderStoppingKey());
isCacheable = false;
}
if (!documentLoader->applicationCacheHost().canCacheInBackForwardCache()) {
PCLOG(" -The DocumentLoader uses an application cache");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::applicationCacheKey());
isCacheable = false;
}
if (!frameLoader.client().canCachePage()) {
PCLOG(" -The client says this frame cannot be cached");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deniedByClientKey());
isCacheable = false;
}
for (Frame* child = frame.tree().firstChild(); child; child = child->tree().nextSibling()) {
if (!canCacheFrame(*child, diagnosticLoggingClient, indentLevel + 1))
isCacheable = false;
}
PCLOG(isCacheable ? " Frame CAN be cached" : " Frame CANNOT be cached");
PCLOG("+---");
return isCacheable;
}
static bool canCachePage(Page& page)
{
RELEASE_ASSERT(!page.isRestoringCachedPage());
unsigned indentLevel = 0;
PCLOG("--------\n Determining if page can be cached:");
DiagnosticLoggingClient& diagnosticLoggingClient = page.diagnosticLoggingClient();
bool isCacheable = canCacheFrame(page.mainFrame(), diagnosticLoggingClient, indentLevel + 1);
if (!page.settings().usesBackForwardCache() || page.isResourceCachingDisabledByWebInspector()) {
PCLOG(" -Page settings says b/f cache disabled");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isDisabledKey());
isCacheable = false;
}
#if ENABLE(DEVICE_ORIENTATION) && !PLATFORM(IOS_FAMILY)
if (DeviceMotionController::isActiveAt(&page)) {
PCLOG(" -Page is using DeviceMotion");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceMotionKey());
isCacheable = false;
}
if (DeviceOrientationController::isActiveAt(&page)) {
PCLOG(" -Page is using DeviceOrientation");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceOrientationKey());
isCacheable = false;
}
#endif
FrameLoadType loadType = page.mainFrame().loader().loadType();
switch (loadType) {
case FrameLoadType::Reload:
PCLOG(" -Load type is: Reload");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadKey());
isCacheable = false;
break;
case FrameLoadType::Same: PCLOG(" -Load type is: Same");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::sameLoadKey());
isCacheable = false;
break;
case FrameLoadType::RedirectWithLockedBackForwardList:
PCLOG(" -Load type is: RedirectWithLockedBackForwardList");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::redirectKey());
isCacheable = false;
break;
case FrameLoadType::Replace:
PCLOG(" -Load type is: Replace");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::replaceKey());
isCacheable = false;
break;
case FrameLoadType::ReloadFromOrigin: {
PCLOG(" -Load type is: ReloadFromOrigin");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadFromOriginKey());
isCacheable = false;
break;
}
case FrameLoadType::ReloadExpiredOnly: {
PCLOG(" -Load type is: ReloadRevalidatingExpired");
logBackForwardCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadRevalidatingExpiredKey());
isCacheable = false;
break;
}
case FrameLoadType::Standard:
case FrameLoadType::Back:
case FrameLoadType::Forward:
case FrameLoadType::IndexedBackForward: break;
}
if (isCacheable)
PCLOG(" Page CAN be cached\n--------");
else
PCLOG(" Page CANNOT be cached\n--------");
diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::backForwardCacheKey(), DiagnosticLoggingKeys::canCacheKey(), isCacheable ? DiagnosticLoggingResultPass : DiagnosticLoggingResultFail, ShouldSample::No);
return isCacheable;
}
BackForwardCache& BackForwardCache::singleton()
{
static NeverDestroyed<BackForwardCache> globalBackForwardCache;
return globalBackForwardCache;
}
BackForwardCache::BackForwardCache()
{
static std::once_flag onceFlag;
std::call_once(onceFlag, [] {
PAL::registerNotifyCallback("com.apple.WebKit.showBackForwardCache", [] {
BackForwardCache::singleton().dump();
});
});
}
void BackForwardCache::dump() const
{
WTFLogAlways("Back/Forward Cache:");
for (auto& item : m_items) {
CachedPage& cachedPage = *item->m_cachedPage;
WTFLogAlways(" Page %p, document %p %s", &cachedPage.page(), cachedPage.document(), cachedPage.document() ? cachedPage.document()->url().string().utf8().data() : "");
}
}
bool BackForwardCache::canCache(Page& page) const
{
if (!m_maxSize) {
logBackForwardCacheFailureDiagnosticMessage(&page, DiagnosticLoggingKeys::isDisabledKey());
return false;
}
if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
logBackForwardCacheFailureDiagnosticMessage(&page, DiagnosticLoggingKeys::underMemoryPressureKey());
return false;
}
return canCachePage(page);
}
void BackForwardCache::pruneToSizeNow(unsigned size, PruningReason pruningReason)
{
SetForScope<unsigned> change(m_maxSize, size);
prune(pruningReason);
}
void BackForwardCache::setMaxSize(unsigned maxSize)
{
m_maxSize = maxSize;
prune(PruningReason::None);
}
unsigned BackForwardCache::frameCount() const
{
unsigned frameCount = m_items.size();
for (auto& item : m_items) {
ASSERT(item->m_cachedPage);
frameCount += item->m_cachedPage->cachedMainFrame()->descendantFrameCount();
}
return frameCount;
}
void BackForwardCache::markPagesForDeviceOrPageScaleChanged(Page& page)
{
for (auto& item : m_items) {
CachedPage& cachedPage = *item->m_cachedPage;
if (&page.mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
cachedPage.markForDeviceOrPageScaleChanged();
}
}
void BackForwardCache::markPagesForContentsSizeChanged(Page& page)
{
for (auto& item : m_items) {
CachedPage& cachedPage = *item->m_cachedPage;
if (&page.mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
cachedPage.markForContentsSizeChanged();
}
}
#if ENABLE(VIDEO)
void BackForwardCache::markPagesForCaptionPreferencesChanged()
{
for (auto& item : m_items) {
ASSERT(item->m_cachedPage);
item->m_cachedPage->markForCaptionPreferencesChanged();
}
}
#endif
static String pruningReasonToDiagnosticLoggingKey(PruningReason pruningReason)
{
switch (pruningReason) {
case PruningReason::MemoryPressure:
return DiagnosticLoggingKeys::prunedDueToMemoryPressureKey();
case PruningReason::ProcessSuspended:
return DiagnosticLoggingKeys::prunedDueToProcessSuspended();
case PruningReason::ReachedMaxSize:
return DiagnosticLoggingKeys::prunedDueToMaxSizeReached();
case PruningReason::None:
break;
}
ASSERT_NOT_REACHED();
return emptyString();
}
static void setBackForwardCacheState(Page& page, Document::BackForwardCacheState BackForwardCacheState)
{
for (Frame* frame = &page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
if (auto* document = frame->document())
document->setBackForwardCacheState(BackForwardCacheState);
}
}
static void destroyRenderTree(Frame& mainFrame)
{
for (Frame* frame = mainFrame.tree().traversePrevious(CanWrap::Yes); frame; frame = frame->tree().traversePrevious(CanWrap::No)) {
if (!frame->document())
continue;
auto& document = *frame->document();
if (document.hasLivingRenderTree())
document.destroyRenderTree();
}
}
static void firePageHideEventRecursively(Frame& frame)
{
auto* document = frame.document();
if (!document)
return;
IgnoreOpensDuringUnloadCountIncrementer ignoreOpensDuringUnloadCountIncrementer(document);
frame.loader().stopLoading(UnloadEventPolicy::UnloadAndPageHide);
for (RefPtr<Frame> child = frame.tree().firstChild(); child; child = child->tree().nextSibling())
firePageHideEventRecursively(*child);
}
bool BackForwardCache::addIfCacheable(HistoryItem& item, Page* page)
{
if (item.isInBackForwardCache())
return false;
if (!page)
return false;
page->mainFrame().loader().stopForBackForwardCache();
if (!canCache(*page))
return false;
ASSERT_WITH_MESSAGE(!page->isUtilityPage(), "Utility pages such as SVGImage pages should never go into BackForwardCache");
setBackForwardCacheState(*page, Document::AboutToEnterBackForwardCache);
if (page->focusController().focusedFrame())
page->focusController().setFocusedFrame(&page->mainFrame());
firePageHideEventRecursively(page->mainFrame());
destroyRenderTree(page->mainFrame());
page->mainFrame().loader().stopForBackForwardCache();
if (!canCache(*page)) {
setBackForwardCacheState(*page, Document::NotInBackForwardCache);
return false;
}
setBackForwardCacheState(*page, Document::InBackForwardCache);
{
ScriptDisallowedScope::InMainThread scriptDisallowedScope;
item.setCachedPage(makeUnique<CachedPage>(*page));
item.m_pruningReason = PruningReason::None;
m_items.add(&item);
}
prune(PruningReason::ReachedMaxSize);
RELEASE_LOG(BackForwardCache, "BackForwardCache::addIfCacheable item: %s, size: %u / %u", item.identifier().string().utf8().data(), pageCount(), maxSize());
return true;
}
std::unique_ptr<CachedPage> BackForwardCache::take(HistoryItem& item, Page* page)
{
if (!item.m_cachedPage) {
if (item.m_pruningReason != PruningReason::None)
logBackForwardCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
return nullptr;
}
m_items.remove(&item);
std::unique_ptr<CachedPage> cachedPage = item.takeCachedPage();
RELEASE_LOG(BackForwardCache, "BackForwardCache::take item: %s, size: %u / %u", item.identifier().string().utf8().data(), pageCount(), maxSize());
if (cachedPage->hasExpired() || (page && page->isResourceCachingDisabledByWebInspector())) {
LOG(BackForwardCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
logBackForwardCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
return nullptr;
}
return cachedPage;
}
void BackForwardCache::removeAllItemsForPage(Page& page)
{
#if ASSERT_ENABLED
ASSERT_WITH_MESSAGE(!m_isInRemoveAllItemsForPage, "We should not reenter this method");
SetForScope<bool> inRemoveAllItemsForPageScope { m_isInRemoveAllItemsForPage, true };
#endif
for (auto it = m_items.begin(); it != m_items.end();) {
auto current = it;
++it;
if (&(*current)->m_cachedPage->page() == &page) {
RELEASE_LOG(BackForwardCache, "BackForwardCache::removeAllItemsForPage removing item: %s, size: %u / %u", (*current)->identifier().string().utf8().data(), pageCount() - 1, maxSize());
(*current)->setCachedPage(nullptr);
m_items.remove(current);
}
}
}
CachedPage* BackForwardCache::get(HistoryItem& item, Page* page)
{
CachedPage* cachedPage = item.m_cachedPage.get();
if (!cachedPage) {
if (item.m_pruningReason != PruningReason::None)
logBackForwardCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
return nullptr;
}
if (cachedPage->hasExpired() || (page && page->isResourceCachingDisabledByWebInspector())) {
LOG(BackForwardCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
logBackForwardCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
remove(item);
return nullptr;
}
return cachedPage;
}
void BackForwardCache::remove(HistoryItem& item)
{
if (!item.m_cachedPage)
return;
m_items.remove(&item);
item.setCachedPage(nullptr);
RELEASE_LOG(BackForwardCache, "BackForwardCache::remove item: %s, size: %u / %u", item.identifier().string().utf8().data(), pageCount(), maxSize());
}
void BackForwardCache::prune(PruningReason pruningReason)
{
while (pageCount() > maxSize()) {
auto oldestItem = m_items.takeFirst();
oldestItem->setCachedPage(nullptr);
oldestItem->m_pruningReason = pruningReason;
RELEASE_LOG(BackForwardCache, "BackForwardCache::prune removing item: %s, size: %u / %u", oldestItem->identifier().string().utf8().data(), pageCount(), maxSize());
}
}
}