summaryrefslogtreecommitdiff
path: root/Source/WebCore/history/PageCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/history/PageCache.cpp')
-rw-r--r--Source/WebCore/history/PageCache.cpp703
1 files changed, 319 insertions, 384 deletions
diff --git a/Source/WebCore/history/PageCache.cpp b/Source/WebCore/history/PageCache.cpp
index 57ec24efb..02f8ff418 100644
--- a/Source/WebCore/history/PageCache.cpp
+++ b/Source/WebCore/history/PageCache.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2007, 2014, 2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -10,10 +10,10 @@
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
@@ -28,28 +28,29 @@
#include "ApplicationCacheHost.h"
#include "BackForwardController.h"
-#include "MemoryCache.h"
#include "CachedPage.h"
#include "DOMWindow.h"
-#include "DatabaseManager.h"
#include "DeviceMotionController.h"
#include "DeviceOrientationController.h"
+#include "DiagnosticLoggingClient.h"
+#include "DiagnosticLoggingKeys.h"
#include "Document.h"
#include "DocumentLoader.h"
+#include "FocusController.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
-#include "FrameLoaderStateMachine.h"
#include "FrameView.h"
-#include "HistogramSupport.h"
#include "HistoryController.h"
-#include "HistoryItem.h"
+#include "IgnoreOpensDuringUnloadCountIncrementer.h"
#include "Logging.h"
#include "MainFrame.h"
+#include "MemoryPressureHandler.h"
+#include "NoEventDispatchAssertion.h"
#include "Page.h"
#include "Settings.h"
-#include "SharedWorkerRepository.h"
#include "SubframeLoader.h"
-#include <wtf/CurrentTime.h>
+#include <wtf/NeverDestroyed.h>
+#include <wtf/SetForScope.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringConcatenate.h>
@@ -57,514 +58,448 @@
#include "DeviceProximityController.h"
#endif
-#if PLATFORM(IOS)
-#include "MemoryPressureHandler.h"
-#endif
-
namespace WebCore {
-#if !defined(NDEBUG)
-
#define PCLOG(...) LOG(PageCache, "%*s%s", indentLevel*4, "", makeString(__VA_ARGS__).utf8().data())
-
-// Used in histograms, please only add at the end, and do not remove elements (renaming e.g. to "FooEnumUnused1" is fine).
-// This is because statistics may be gathered from histograms between versions over time, and re-using values causes collisions.
-enum ReasonFrameCannotBeInPageCache {
- NoDocumentLoader = 0,
- MainDocumentError,
- IsErrorPage,
- HasPlugins,
- IsHttpsAndCacheControlled,
- HasUnloadListener,
- HasDatabaseHandles,
- HasSharedWorkers,
- NoHistoryItem,
- QuickRedirectComing,
- IsLoadingInAPISense,
- IsStopping,
- CannotSuspendActiveDOMObjects,
- DocumentLoaderUsesApplicationCache,
- ClientDeniesCaching,
- NumberOfReasonsFramesCannotBeInPageCache,
-};
-COMPILE_ASSERT(NumberOfReasonsFramesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonFrameCannotBeInPageCacheDoesNotFitInBitmap);
-
-static unsigned logCanCacheFrameDecision(Frame* frame, int indentLevel)
+
+static inline void logPageCacheFailureDiagnosticMessage(DiagnosticLoggingClient& client, const String& reason)
+{
+ client.logDiagnosticMessage(DiagnosticLoggingKeys::pageCacheFailureKey(), reason, ShouldSample::Yes);
+}
+
+static inline void logPageCacheFailureDiagnosticMessage(Page* page, const String& reason)
+{
+ if (!page)
+ return;
+
+ logPageCacheFailureDiagnosticMessage(page->diagnosticLoggingClient(), reason);
+}
+
+static bool canCacheFrame(Frame& frame, DiagnosticLoggingClient& diagnosticLoggingClient, unsigned indentLevel)
{
PCLOG("+---");
- if (!frame->loader().documentLoader()) {
+ FrameLoader& frameLoader = frame.loader();
+
+ // Prevent page caching if a subframe is still in provisional load stage.
+ // We only do this check for subframes because the main frame is reused when navigating to a new page.
+ if (!frame.isMainFrame() && frameLoader.state() == FrameStateProvisional) {
+ PCLOG(" -Frame is in provisional load stage");
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::provisionalLoadKey());
+ return false;
+ }
+
+ DocumentLoader* documentLoader = frameLoader.documentLoader();
+ if (!documentLoader) {
PCLOG(" -There is no DocumentLoader object");
- return 1 << NoDocumentLoader;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noDocumentLoaderKey());
+ return false;
}
- URL currentURL = frame->loader().documentLoader()->url();
- URL newURL = frame->loader().provisionalDocumentLoader() ? frame->loader().provisionalDocumentLoader()->url() : URL();
+ 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:");
- unsigned rejectReasons = 0;
- if (!frame->loader().documentLoader()->mainDocumentError().isNull()) {
+ bool isCacheable = true;
+ if (!documentLoader->mainDocumentError().isNull()) {
PCLOG(" -Main document has an error");
-#if !PLATFORM(IOS)
- rejectReasons |= 1 << MainDocumentError;
-#else
- if (frame->loader().documentLoader()->mainDocumentError().isCancellation() && frame->loader().documentLoader()->subresourceLoadersArePageCacheAcceptable())
- PCLOG(" -But, it was a cancellation and all loaders during the cancel were loading images.");
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::mainDocumentErrorKey());
+
+ if (documentLoader->mainDocumentError().isCancellation() && documentLoader->subresourceLoadersArePageCacheAcceptable())
+ PCLOG(" -But, it was a cancellation and all loaders during the cancelation were loading images or XHR.");
else
- rejectReasons |= 1 << MainDocumentError;
-#endif
+ isCacheable = false;
}
- if (frame->loader().documentLoader()->substituteData().isValid() && frame->loader().documentLoader()->substituteData().failingURL().isEmpty()) {
+ // Do not cache error pages (these can be recognized as pages with substitute data or unreachable URLs).
+ if (documentLoader->substituteData().isValid() && !documentLoader->substituteData().failingURL().isEmpty()) {
PCLOG(" -Frame is an error page");
- rejectReasons |= 1 << IsErrorPage;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isErrorPageKey());
+ isCacheable = false;
}
- if (frame->loader().subframeLoader().containsPlugins() && !frame->page()->settings().pageCacheSupportsPlugins()) {
+ if (frameLoader.subframeLoader().containsPlugins() && !frame.page()->settings().pageCacheSupportsPlugins()) {
PCLOG(" -Frame contains plugins");
- rejectReasons |= 1 << HasPlugins;
- }
- if (frame->document()->url().protocolIs("https")
- && (frame->loader().documentLoader()->response().cacheControlContainsNoCache()
- || frame->loader().documentLoader()->response().cacheControlContainsNoStore())) {
- PCLOG(" -Frame is HTTPS, and cache control prohibits caching or storing");
- rejectReasons |= 1 << IsHttpsAndCacheControlled;
- }
- if (frame->document()->domWindow() && frame->document()->domWindow()->hasEventListeners(eventNames().unloadEvent)) {
- PCLOG(" -Frame has an unload event listener");
-#if !PLATFORM(IOS)
- rejectReasons |= 1 << HasUnloadListener;
-#else
- // iOS allows pages with unload event listeners to enter the page cache.
- PCLOG(" -BUT iOS allows these pages to be cached.");
-#endif
- }
-#if ENABLE(SQL_DATABASE)
- if (DatabaseManager::manager().hasOpenDatabases(frame->document())) {
- PCLOG(" -Frame has open database handles");
- rejectReasons |= 1 << HasDatabaseHandles;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::hasPluginsKey());
+ isCacheable = false;
}
-#endif
-#if ENABLE(SHARED_WORKERS)
- if (SharedWorkerRepository::hasSharedWorkers(frame->document())) {
- PCLOG(" -Frame has associated SharedWorkers");
- rejectReasons |= 1 << HasSharedWorkers;
+ if (frame.isMainFrame() && frame.document() && frame.document()->url().protocolIs("https") && documentLoader->response().cacheControlContainsNoStore()) {
+ PCLOG(" -Frame is HTTPS, and cache control prohibits storing");
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::httpsNoStoreKey());
+ isCacheable = false;
}
-#endif
- if (!frame->loader().history().currentItem()) {
- PCLOG(" -No current history item");
- rejectReasons |= 1 << NoHistoryItem;
+ if (frame.isMainFrame() && !frameLoader.history().currentItem()) {
+ PCLOG(" -Main frame has no current history item");
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noCurrentHistoryItemKey());
+ isCacheable = false;
}
- if (frame->loader().quickRedirectComing()) {
+ if (frameLoader.quickRedirectComing()) {
PCLOG(" -Quick redirect is coming");
- rejectReasons |= 1 << QuickRedirectComing;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::quirkRedirectComingKey());
+ isCacheable = false;
}
- if (frame->loader().documentLoader()->isLoadingInAPISense()) {
- PCLOG(" -DocumentLoader is still loading in API sense");
- rejectReasons |= 1 << IsLoadingInAPISense;
+ if (documentLoader->isLoading()) {
+ PCLOG(" -DocumentLoader is still loading");
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isLoadingKey());
+ isCacheable = false;
}
- if (frame->loader().documentLoader()->isStopping()) {
+ if (documentLoader->isStopping()) {
PCLOG(" -DocumentLoader is in the middle of stopping");
- rejectReasons |= 1 << IsStopping;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::documentLoaderStoppingKey());
+ isCacheable = false;
}
- if (!frame->document()->canSuspendActiveDOMObjects()) {
- PCLOG(" -The document cannot suspect its active DOM Objects");
- rejectReasons |= 1 << CannotSuspendActiveDOMObjects;
+
+ Vector<ActiveDOMObject*> unsuspendableObjects;
+ if (frame.document() && !frame.document()->canSuspendActiveDOMObjectsForDocumentSuspension(&unsuspendableObjects)) {
+ PCLOG(" -The document cannot suspend its active DOM Objects");
+ for (auto* activeDOMObject : unsuspendableObjects) {
+ PCLOG(" - Unsuspendable: ", activeDOMObject->activeDOMObjectName());
+ diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::unsuspendableDOMObjectKey(), activeDOMObject->activeDOMObjectName(), ShouldSample::Yes);
+ UNUSED_PARAM(activeDOMObject);
+ }
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::cannotSuspendActiveDOMObjectsKey());
+ isCacheable = false;
}
- if (!frame->loader().documentLoader()->applicationCacheHost()->canCacheInPageCache()) {
+ // FIXME: We should investigating caching frames that have an associated
+ // application cache. <rdar://problem/5917899> tracks that work.
+ if (!documentLoader->applicationCacheHost().canCacheInPageCache()) {
PCLOG(" -The DocumentLoader uses an application cache");
- rejectReasons |= 1 << DocumentLoaderUsesApplicationCache;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::applicationCacheKey());
+ isCacheable = false;
}
- if (!frame->loader().client().canCachePage()) {
+ if (!frameLoader.client().canCachePage()) {
PCLOG(" -The client says this frame cannot be cached");
- rejectReasons |= 1 << ClientDeniesCaching;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deniedByClientKey());
+ isCacheable = false;
}
- HistogramSupport::histogramEnumeration("PageCache.FrameCacheable", !rejectReasons, 2);
- int reasonCount = 0;
- for (int i = 0; i < NumberOfReasonsFramesCannotBeInPageCache; ++i) {
- if (rejectReasons & (1 << i)) {
- ++reasonCount;
- HistogramSupport::histogramEnumeration("PageCache.FrameRejectReason", i, NumberOfReasonsFramesCannotBeInPageCache);
- }
+ for (Frame* child = frame.tree().firstChild(); child; child = child->tree().nextSibling()) {
+ if (!canCacheFrame(*child, diagnosticLoggingClient, indentLevel + 1))
+ isCacheable = false;
}
- HistogramSupport::histogramEnumeration("PageCache.FrameRejectReasonCount", reasonCount, 1 + NumberOfReasonsFramesCannotBeInPageCache);
-
- for (Frame* child = frame->tree().firstChild(); child; child = child->tree().nextSibling())
- rejectReasons |= logCanCacheFrameDecision(child, indentLevel + 1);
- PCLOG(rejectReasons ? " Frame CANNOT be cached" : " Frame CAN be cached");
+ PCLOG(isCacheable ? " Frame CAN be cached" : " Frame CANNOT be cached");
PCLOG("+---");
- return rejectReasons;
+ return isCacheable;
}
-// Used in histograms, please only add at the end, and do not remove elements (renaming e.g. to "FooEnumUnused1" is fine).
-// This is because statistics may be gathered from histograms between versions over time, and re-using values causes collisions.
-enum ReasonPageCannotBeInPageCache {
- FrameCannotBeInPageCache = 0,
- DisabledBackForwardList,
- DisabledPageCache,
- UsesDeviceMotion,
- UsesDeviceOrientation,
- IsReload,
- IsReloadFromOrigin,
- IsSameLoad,
- NumberOfReasonsPagesCannotBeInPageCache,
-};
-COMPILE_ASSERT(NumberOfReasonsPagesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonPageCannotBeInPageCacheDoesNotFitInBitmap);
-
-static void logCanCachePageDecision(Page* page)
+static bool canCachePage(Page& page)
{
- // Only bother logging for main frames that have actually loaded and have content.
- if (page->mainFrame().loader().stateMachine()->creatingInitialEmptyDocument())
- return;
- URL currentURL = page->mainFrame().loader().documentLoader() ? page->mainFrame().loader().documentLoader()->url() : URL();
- if (currentURL.isEmpty())
- return;
-
- int indentLevel = 0;
+ unsigned indentLevel = 0;
PCLOG("--------\n Determining if page can be cached:");
+
+ DiagnosticLoggingClient& diagnosticLoggingClient = page.diagnosticLoggingClient();
+ bool isCacheable = canCacheFrame(page.mainFrame(), diagnosticLoggingClient, indentLevel + 1);
- unsigned rejectReasons = 0;
- unsigned frameRejectReasons = logCanCacheFrameDecision(&page->mainFrame(), indentLevel+1);
- if (frameRejectReasons)
- rejectReasons |= 1 << FrameCannotBeInPageCache;
-
- if (!page->settings().usesPageCache()) {
+ if (!page.settings().usesPageCache() || page.isResourceCachingDisabled()) {
PCLOG(" -Page settings says b/f cache disabled");
- rejectReasons |= 1 << DisabledPageCache;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isDisabledKey());
+ isCacheable = false;
}
#if ENABLE(DEVICE_ORIENTATION) && !PLATFORM(IOS)
- if (DeviceMotionController::isActiveAt(page)) {
+ if (DeviceMotionController::isActiveAt(&page)) {
PCLOG(" -Page is using DeviceMotion");
- rejectReasons |= 1 << UsesDeviceMotion;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceMotionKey());
+ isCacheable = false;
}
- if (DeviceOrientationController::isActiveAt(page)) {
+ if (DeviceOrientationController::isActiveAt(&page)) {
PCLOG(" -Page is using DeviceOrientation");
- rejectReasons |= 1 << UsesDeviceOrientation;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceOrientationKey());
+ isCacheable = false;
}
#endif
#if ENABLE(PROXIMITY_EVENTS)
if (DeviceProximityController::isActiveAt(page)) {
PCLOG(" -Page is using DeviceProximity");
- rejectReasons |= 1 << UsesDeviceMotion;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, deviceProximityKey);
+ isCacheable = false;
}
#endif
- FrameLoadType loadType = page->mainFrame().loader().loadType();
- if (loadType == FrameLoadTypeReload) {
+ FrameLoadType loadType = page.mainFrame().loader().loadType();
+ switch (loadType) {
+ case FrameLoadType::Reload:
+ // No point writing to the cache on a reload, since we will just write over it again when we leave that page.
PCLOG(" -Load type is: Reload");
- rejectReasons |= 1 << IsReload;
- }
- if (loadType == FrameLoadTypeReloadFromOrigin) {
- PCLOG(" -Load type is: Reload from origin");
- rejectReasons |= 1 << IsReloadFromOrigin;
- }
- if (loadType == FrameLoadTypeSame) {
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadKey());
+ isCacheable = false;
+ break;
+ case FrameLoadType::Same: // user loads same URL again (but not reload button)
+ // No point writing to the cache on a same load, since we will just write over it again when we leave that page.
PCLOG(" -Load type is: Same");
- rejectReasons |= 1 << IsSameLoad;
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::sameLoadKey());
+ isCacheable = false;
+ break;
+ case FrameLoadType::RedirectWithLockedBackForwardList:
+ // Don't write to the cache if in the middle of a redirect, since we will want to store the final page we end up on.
+ PCLOG(" -Load type is: RedirectWithLockedBackForwardList");
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::redirectKey());
+ isCacheable = false;
+ break;
+ case FrameLoadType::Replace:
+ // No point writing to the cache on a replace, since we will just write over it again when we leave that page.
+ PCLOG(" -Load type is: Replace");
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::replaceKey());
+ isCacheable = false;
+ break;
+ case FrameLoadType::ReloadFromOrigin: {
+ // No point writing to the cache on a reload, since we will just write over it again when we leave that page.
+ PCLOG(" -Load type is: ReloadFromOrigin");
+ logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadFromOriginKey());
+ isCacheable = false;
+ break;
+ }
+ case FrameLoadType::Standard:
+ case FrameLoadType::Back:
+ case FrameLoadType::Forward:
+ case FrameLoadType::IndexedBackForward: // a multi-item hop in the backforward list
+ // Cacheable.
+ break;
}
- PCLOG(rejectReasons ? " Page CANNOT be cached\n--------" : " Page CAN be cached\n--------");
-
- HistogramSupport::histogramEnumeration("PageCache.PageCacheable", !rejectReasons, 2);
- int reasonCount = 0;
- for (int i = 0; i < NumberOfReasonsPagesCannotBeInPageCache; ++i) {
- if (rejectReasons & (1 << i)) {
- ++reasonCount;
- HistogramSupport::histogramEnumeration("PageCache.PageRejectReason", i, NumberOfReasonsPagesCannotBeInPageCache);
- }
- }
- HistogramSupport::histogramEnumeration("PageCache.PageRejectReasonCount", reasonCount, 1 + NumberOfReasonsPagesCannotBeInPageCache);
- const bool settingsDisabledPageCache = rejectReasons & (1 << DisabledPageCache);
- HistogramSupport::histogramEnumeration("PageCache.PageRejectReasonCountExcludingSettings", reasonCount - settingsDisabledPageCache, NumberOfReasonsPagesCannotBeInPageCache);
-
- // Report also on the frame reasons by page; this is distinct from the per frame statistics since it coalesces the
- // causes from all subframes together.
- HistogramSupport::histogramEnumeration("PageCache.FrameCacheableByPage", !frameRejectReasons, 2);
- int frameReasonCount = 0;
- for (int i = 0; i <= NumberOfReasonsFramesCannotBeInPageCache; ++i) {
- if (frameRejectReasons & (1 << i)) {
- ++frameReasonCount;
- HistogramSupport::histogramEnumeration("PageCache.FrameRejectReasonByPage", i, NumberOfReasonsFramesCannotBeInPageCache);
- }
- }
-
- HistogramSupport::histogramEnumeration("PageCache.FrameRejectReasonCountByPage", frameReasonCount, 1 + NumberOfReasonsFramesCannotBeInPageCache);
-}
-
-#endif // !defined(NDEBUG)
+ if (isCacheable)
+ PCLOG(" Page CAN be cached\n--------");
+ else
+ PCLOG(" Page CANNOT be cached\n--------");
-PageCache* pageCache()
-{
- static PageCache* staticPageCache = new PageCache;
- return staticPageCache;
+ diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::pageCacheKey(), DiagnosticLoggingKeys::canCacheKey(), isCacheable ? DiagnosticLoggingResultPass : DiagnosticLoggingResultFail, ShouldSample::Yes);
+ return isCacheable;
}
-PageCache::PageCache()
- : m_capacity(0)
- , m_size(0)
- , m_head(0)
- , m_tail(0)
-#if USE(ACCELERATED_COMPOSITING)
- , m_shouldClearBackingStores(false)
-#endif
+PageCache& PageCache::singleton()
{
+ static NeverDestroyed<PageCache> globalPageCache;
+ return globalPageCache;
}
-bool PageCache::canCachePageContainingThisFrame(Frame* frame)
+bool PageCache::canCache(Page& page) const
{
- for (Frame* child = frame->tree().firstChild(); child; child = child->tree().nextSibling()) {
- if (!canCachePageContainingThisFrame(child))
- return false;
- }
-
- FrameLoader& frameLoader = frame->loader();
- DocumentLoader* documentLoader = frameLoader.documentLoader();
- Document* document = frame->document();
-
- return documentLoader
-#if !PLATFORM(IOS)
- && documentLoader->mainDocumentError().isNull()
-#else
- && (documentLoader->mainDocumentError().isNull() || (documentLoader->mainDocumentError().isCancellation() && documentLoader->subresourceLoadersArePageCacheAcceptable()))
-#endif
- // Do not cache error pages (these can be recognized as pages with substitute data or unreachable URLs).
- && !(documentLoader->substituteData().isValid() && !documentLoader->substituteData().failingURL().isEmpty())
- && (!frameLoader.subframeLoader().containsPlugins() || frame->page()->settings().pageCacheSupportsPlugins())
- && (!document->url().protocolIs("https") || (!documentLoader->response().cacheControlContainsNoCache() && !documentLoader->response().cacheControlContainsNoStore()))
-#if !PLATFORM(IOS)
- && (!document->domWindow() || !document->domWindow()->hasEventListeners(eventNames().unloadEvent))
-#endif
-#if ENABLE(SQL_DATABASE)
- && !DatabaseManager::manager().hasOpenDatabases(document)
-#endif
-#if ENABLE(SHARED_WORKERS)
- && !SharedWorkerRepository::hasSharedWorkers(document)
-#endif
- && frameLoader.history().currentItem()
- && !frameLoader.quickRedirectComing()
- && !documentLoader->isLoadingInAPISense()
- && !documentLoader->isStopping()
- && document->canSuspendActiveDOMObjects()
- // FIXME: We should investigating caching frames that have an associated
- // application cache. <rdar://problem/5917899> tracks that work.
- && documentLoader->applicationCacheHost()->canCacheInPageCache()
- && frameLoader.client().canCachePage();
-}
-
-bool PageCache::canCache(Page* page) const
-{
- if (!page)
+ if (!m_maxSize) {
+ logPageCacheFailureDiagnosticMessage(&page, DiagnosticLoggingKeys::isDisabledKey());
return false;
-
-#if !defined(NDEBUG)
- logCanCachePageDecision(page);
-#endif
+ }
-#if PLATFORM(IOS)
- if (memoryPressureHandler().hasReceivedMemoryPressure())
+ if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
+ logPageCacheFailureDiagnosticMessage(&page, DiagnosticLoggingKeys::underMemoryPressureKey());
return false;
-#endif
-
- // Cache the page, if possible.
- // Don't write to the cache if in the middle of a redirect, since we will want to
- // store the final page we end up on.
- // No point writing to the cache on a reload or loadSame, since we will just write
- // over it again when we leave that page.
- FrameLoadType loadType = page->mainFrame().loader().loadType();
+ }
- return m_capacity > 0
- && canCachePageContainingThisFrame(&page->mainFrame())
- && page->settings().usesPageCache()
-#if ENABLE(DEVICE_ORIENTATION) && !PLATFORM(IOS)
- && !DeviceMotionController::isActiveAt(page)
- && !DeviceOrientationController::isActiveAt(page)
-#endif
-#if ENABLE(PROXIMITY_EVENTS)
- && !DeviceProximityController::isActiveAt(page)
-#endif
- && (loadType == FrameLoadTypeStandard
- || loadType == FrameLoadTypeBack
- || loadType == FrameLoadTypeForward
- || loadType == FrameLoadTypeIndexedBackForward);
+ return canCachePage(page);
}
-void PageCache::pruneToCapacityNow(int capacity)
+void PageCache::pruneToSizeNow(unsigned size, PruningReason pruningReason)
{
- int savedCapacity = m_capacity;
- setCapacity(capacity);
- setCapacity(savedCapacity);
+ SetForScope<unsigned> change(m_maxSize, size);
+ prune(pruningReason);
}
-void PageCache::setCapacity(int capacity)
+void PageCache::setMaxSize(unsigned maxSize)
{
- ASSERT(capacity >= 0);
- m_capacity = std::max(capacity, 0);
-
- prune();
+ m_maxSize = maxSize;
+ prune(PruningReason::None);
}
-int PageCache::frameCount() const
+unsigned PageCache::frameCount() const
{
- int frameCount = 0;
- for (HistoryItem* current = m_head; current; current = current->m_next) {
- ++frameCount;
- ASSERT(current->m_cachedPage);
- frameCount += current->m_cachedPage ? current->m_cachedPage->cachedMainFrame()->descendantFrameCount() : 0;
+ unsigned frameCount = m_items.size();
+ for (auto& item : m_items) {
+ ASSERT(item->m_cachedPage);
+ frameCount += item->m_cachedPage->cachedMainFrame()->descendantFrameCount();
}
return frameCount;
}
-void PageCache::markPagesForVistedLinkStyleRecalc()
+void PageCache::markPagesForDeviceOrPageScaleChanged(Page& page)
{
- for (HistoryItem* current = m_head; current; current = current->m_next) {
- if (current->m_cachedPage)
- current->m_cachedPage->markForVistedLinkStyleRecalc();
+ for (auto& item : m_items) {
+ CachedPage& cachedPage = *item->m_cachedPage;
+ if (&page.mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
+ cachedPage.markForDeviceOrPageScaleChanged();
}
}
-void PageCache::markPagesForFullStyleRecalc(Page* page)
+void PageCache::markPagesForContentsSizeChanged(Page& page)
{
- for (HistoryItem* current = m_head; current; current = current->m_next) {
- CachedPage* cachedPage = current->m_cachedPage.get();
- if (cachedPage && &page->mainFrame() == &cachedPage->cachedMainFrame()->view()->frame())
- cachedPage->markForFullStyleRecalc();
+ for (auto& item : m_items) {
+ CachedPage& cachedPage = *item->m_cachedPage;
+ if (&page.mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
+ cachedPage.markForContentsSizeChanged();
}
}
-
-#if USE(ACCELERATED_COMPOSITING)
-void PageCache::markPagesForDeviceScaleChanged(Page* page)
+#if ENABLE(VIDEO_TRACK)
+void PageCache::markPagesForCaptionPreferencesChanged()
{
- for (HistoryItem* current = m_head; current; current = current->m_next) {
- CachedPage* cachedPage = current->m_cachedPage.get();
- if (cachedPage && &page->mainFrame() == &cachedPage->cachedMainFrame()->view()->frame())
- cachedPage->markForDeviceScaleChanged();
+ for (auto& item : m_items) {
+ ASSERT(item->m_cachedPage);
+ item->m_cachedPage->markForCaptionPreferencesChanged();
}
}
#endif
-#if ENABLE(VIDEO_TRACK)
-void PageCache::markPagesForCaptionPreferencesChanged()
+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 setPageCacheState(Page& page, Document::PageCacheState pageCacheState)
{
- for (HistoryItem* current = m_head; current; current = current->m_next) {
- if (current->m_cachedPage)
- current->m_cachedPage->markForCaptionPreferencesChanged();
+ for (Frame* frame = &page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
+ if (auto* document = frame->document())
+ document->setPageCacheState(pageCacheState);
}
}
-#endif
-void PageCache::add(PassRefPtr<HistoryItem> prpItem, Page& page)
+// When entering page cache, tear down the render tree before setting the in-cache flag.
+// This maintains the invariant that render trees are never present in the page cache.
+// Note that destruction happens bottom-up so that the main frame's tree dies last.
+static void destroyRenderTree(MainFrame& mainFrame)
{
- ASSERT(prpItem);
- ASSERT(canCache(&page));
-
- HistoryItem* item = prpItem.leakRef(); // Balanced in remove().
+ for (Frame* frame = mainFrame.tree().traversePreviousWithWrap(true); frame; frame = frame->tree().traversePreviousWithWrap(false)) {
+ if (!frame->document())
+ continue;
+ auto& document = *frame->document();
+ if (document.hasLivingRenderTree())
+ document.destroyRenderTree();
+ }
+}
- // Remove stale cache entry if necessary.
- if (item->m_cachedPage)
- remove(item);
+static void firePageHideEventRecursively(Frame& frame)
+{
+ auto* document = frame.document();
+ if (!document)
+ return;
- item->m_cachedPage = std::make_unique<CachedPage>(page);
- addToLRUList(item);
- ++m_size;
-
- prune();
+ // stopLoading() will fire the pagehide event in each subframe and the HTML specification states
+ // that the parent document's ignore-opens-during-unload counter should be incremented while the
+ // pagehide event is being fired in its subframes:
+ // https://html.spec.whatwg.org/multipage/browsers.html#unload-a-document
+ IgnoreOpensDuringUnloadCountIncrementer ignoreOpensDuringUnloadCountIncrementer(document);
+
+ frame.loader().stopLoading(UnloadEventPolicyUnloadAndPageHide);
+
+ for (RefPtr<Frame> child = frame.tree().firstChild(); child; child = child->tree().nextSibling())
+ firePageHideEventRecursively(*child);
}
-std::unique_ptr<CachedPage> PageCache::take(HistoryItem* item)
+void PageCache::addIfCacheable(HistoryItem& item, Page* page)
{
- if (!item)
- return nullptr;
+ if (item.isInPageCache())
+ return;
- std::unique_ptr<CachedPage> cachedPage = std::move(item->m_cachedPage);
+ if (!page || !canCache(*page))
+ return;
- removeFromLRUList(item);
- --m_size;
+ setPageCacheState(*page, Document::AboutToEnterPageCache);
- item->deref(); // Balanced in add().
+ // Focus the main frame, defocusing a focused subframe (if we have one). We do this here,
+ // before the page enters the page cache, while we still can dispatch DOM blur/focus events.
+ if (page->focusController().focusedFrame())
+ page->focusController().setFocusedFrame(&page->mainFrame());
- if (!cachedPage)
- return nullptr;
+ // Fire the pagehide event in all frames.
+ firePageHideEventRecursively(page->mainFrame());
- if (cachedPage->hasExpired()) {
- LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data());
- return nullptr;
+ // Check that the page is still page-cacheable after firing the pagehide event. The JS event handlers
+ // could have altered the page in a way that could prevent caching.
+ if (!canCache(*page)) {
+ setPageCacheState(*page, Document::NotInPageCache);
+ return;
}
- return cachedPage;
-}
+ destroyRenderTree(page->mainFrame());
-CachedPage* PageCache::get(HistoryItem* item)
-{
- if (!item)
- return 0;
+ setPageCacheState(*page, Document::InPageCache);
- if (CachedPage* cachedPage = item->m_cachedPage.get()) {
- if (!cachedPage->hasExpired())
- return cachedPage;
-
- LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data());
- pageCache()->remove(item);
- }
- return 0;
+ // Make sure we no longer fire any JS events past this point.
+ NoEventDispatchAssertion assertNoEventDispatch;
+
+ item.m_cachedPage = std::make_unique<CachedPage>(*page);
+ item.m_pruningReason = PruningReason::None;
+ m_items.add(&item);
+
+ prune(PruningReason::ReachedMaxSize);
}
-void PageCache::remove(HistoryItem* item)
+std::unique_ptr<CachedPage> PageCache::take(HistoryItem& item, Page* page)
{
- // Safely ignore attempts to remove items not in the cache.
- if (!item || !item->m_cachedPage)
- return;
+ if (!item.m_cachedPage) {
+ if (item.m_pruningReason != PruningReason::None)
+ logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
+ return nullptr;
+ }
- item->m_cachedPage = nullptr;
- removeFromLRUList(item);
- --m_size;
+ m_items.remove(&item);
+ std::unique_ptr<CachedPage> cachedPage = WTFMove(item.m_cachedPage);
- item->deref(); // Balanced in add().
+ if (cachedPage->hasExpired() || (page && page->isResourceCachingDisabled())) {
+ LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
+ logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
+ return nullptr;
+ }
+
+ return cachedPage;
}
-void PageCache::prune()
+void PageCache::removeAllItemsForPage(Page& page)
{
- while (m_size > m_capacity) {
- ASSERT(m_tail && m_tail->m_cachedPage);
- remove(m_tail);
+ for (auto it = m_items.begin(); it != m_items.end();) {
+ // Increment iterator first so it stays valid after the removal.
+ auto current = it;
+ ++it;
+ if (&(*current)->m_cachedPage->page() == &page) {
+ (*current)->m_cachedPage = nullptr;
+ m_items.remove(current);
+ }
}
}
-void PageCache::addToLRUList(HistoryItem* item)
+CachedPage* PageCache::get(HistoryItem& item, Page* page)
{
- item->m_next = m_head;
- item->m_prev = 0;
+ CachedPage* cachedPage = item.m_cachedPage.get();
+ if (!cachedPage) {
+ if (item.m_pruningReason != PruningReason::None)
+ logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
+ return nullptr;
+ }
- if (m_head) {
- ASSERT(m_tail);
- m_head->m_prev = item;
- } else {
- ASSERT(!m_tail);
- m_tail = item;
+ if (cachedPage->hasExpired() || (page && page->isResourceCachingDisabled())) {
+ LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
+ logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
+ remove(item);
+ return nullptr;
}
+ return cachedPage;
+}
+
+void PageCache::remove(HistoryItem& item)
+{
+ // Safely ignore attempts to remove items not in the cache.
+ if (!item.m_cachedPage)
+ return;
- m_head = item;
+ m_items.remove(&item);
+ item.m_cachedPage = nullptr;
}
-void PageCache::removeFromLRUList(HistoryItem* item)
+void PageCache::prune(PruningReason pruningReason)
{
- if (!item->m_next) {
- ASSERT(item == m_tail);
- m_tail = item->m_prev;
- } else {
- ASSERT(item != m_tail);
- item->m_next->m_prev = item->m_prev;
- }
-
- if (!item->m_prev) {
- ASSERT(item == m_head);
- m_head = item->m_next;
- } else {
- ASSERT(item != m_head);
- item->m_prev->m_next = item->m_next;
+ while (pageCount() > maxSize()) {
+ auto oldestItem = m_items.takeFirst();
+ oldestItem->m_cachedPage = nullptr;
+ oldestItem->m_pruningReason = pruningReason;
}
}