#include "config.h"
#include "PageCache.h"
#include "ApplicationCacheHost.h"
#include "BackForwardController.h"
#include "MemoryCache.h"
#include "CachedPage.h"
#include "DOMWindow.h"
#include "DeviceMotionController.h"
#include "DeviceOrientationController.h"
#include "Document.h"
#include "DocumentLoader.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "FrameLoaderStateMachine.h"
#include "HistoryItem.h"
#include "Logging.h"
#include "Page.h"
#include "Settings.h"
#include "SharedWorkerRepository.h"
#include "SystemTime.h"
#include <wtf/CurrentTime.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringConcatenate.h>
using namespace std;
namespace WebCore {
static const double autoreleaseInterval = 3;
#ifndef NDEBUG
static String& pageCacheLogPrefix(int indentLevel)
{
static int previousIndent = -1;
DEFINE_STATIC_LOCAL(String, prefix, ());
if (indentLevel != previousIndent) {
previousIndent = indentLevel;
prefix.truncate(0);
for (int i = 0; i < previousIndent; ++i)
prefix += " ";
}
return prefix;
}
static void pageCacheLog(const String& prefix, const String& message)
{
LOG(PageCache, "%s%s", prefix.utf8().data(), message.utf8().data());
}
#define PCLOG(...) pageCacheLog(pageCacheLogPrefix(indentLevel), makeString(__VA_ARGS__))
static bool logCanCacheFrameDecision(Frame* frame, int indentLevel)
{
if (frame->loader()->stateMachine()->creatingInitialEmptyDocument())
return false;
KURL currentURL = frame->loader()->documentLoader() ? frame->loader()->documentLoader()->url() : KURL();
if (currentURL.isEmpty())
return false;
PCLOG("+---");
KURL newURL = frame->loader()->provisionalDocumentLoader() ? frame->loader()->provisionalDocumentLoader()->url() : KURL();
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 cannotCache = false;
do {
if (!frame->loader()->documentLoader()) {
PCLOG(" -There is no DocumentLoader object");
cannotCache = true;
break;
}
if (!frame->loader()->documentLoader()->mainDocumentError().isNull()) {
PCLOG(" -Main document has an error");
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.");
else
cannotCache = true;
}
if (frame->loader()->subframeLoader()->containsPlugins()) {
PCLOG(" -Frame contains plugins");
PCLOG(" -But, iOS can handle plugins.");
}
if (frame->document()->url().protocolIs("https")) {
PCLOG(" -Frame is HTTPS");
cannotCache = true;
}
if (frame->domWindow() && frame->domWindow()->hasEventListeners(eventNames().unloadEvent)) {
PCLOG(" -Frame has an unload event listener");
PCLOG(" -BUT iOS allows these pages to be cached.");
}
#if ENABLE(DATABASE)
if (frame->document()->hasOpenDatabases()) {
PCLOG(" -Frame has open database handles");
cannotCache = true;
}
#endif
#if ENABLE(SHARED_WORKERS)
if (SharedWorkerRepository::hasSharedWorkers(frame->document())) {
PCLOG(" -Frame has associated SharedWorkers");
cannotCache = true;
}
#endif
if (frame->document()->usingGeolocation()) {
PCLOG(" -Frame uses Geolocation");
PCLOG(" -BUT iOS allows these pages to be cached.");
}
if (!frame->loader()->history()->currentItem()) {
PCLOG(" -No current history item");
cannotCache = true;
}
if (frame->loader()->quickRedirectComing()) {
PCLOG(" -Quick redirect is coming");
cannotCache = true;
}
if (frame->loader()->documentLoader()->isLoadingInAPISense()) {
PCLOG(" -DocumentLoader is still loading in API sense");
cannotCache = true;
}
if (frame->loader()->documentLoader()->isStopping()) {
PCLOG(" -DocumentLoader is in the middle of stopping");
cannotCache = true;
}
if (!frame->document()->canSuspendActiveDOMObjects()) {
PCLOG(" -The document cannot suspect its active DOM Objects");
cannotCache = true;
}
#if ENABLE(OFFLINE_WEB_APPLICATIONS)
if (!frame->loader()->documentLoader()->applicationCacheHost()->canCacheInPageCache()) {
PCLOG(" -The DocumentLoader uses an application cache");
cannotCache = true;
}
#endif
if (!frame->loader()->client()->canCachePage()) {
PCLOG(" -The client says this frame cannot be cached");
cannotCache = true;
}
} while (false);
for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
if (!logCanCacheFrameDecision(child, indentLevel + 1))
cannotCache = true;
PCLOG(cannotCache ? " Frame CANNOT be cached" : " Frame CAN be cached");
PCLOG("+---");
return !cannotCache;
}
static void logCanCachePageDecision(Page* page)
{
if (page->mainFrame()->loader()->stateMachine()->creatingInitialEmptyDocument())
return;
KURL currentURL = page->mainFrame()->loader()->documentLoader() ? page->mainFrame()->loader()->documentLoader()->url() : KURL();
if (currentURL.isEmpty())
return;
int indentLevel = 0;
PCLOG("--------\n Determining if page can be cached:");
bool cannotCache = !logCanCacheFrameDecision(page->mainFrame(), 1);
FrameLoadType loadType = page->mainFrame()->loader()->loadType();
if (!page->backForward()->isActive()) {
PCLOG(" -The back/forward list is disabled or has 0 capacity");
cannotCache = true;
}
if (!page->settings()->usesPageCache()) {
PCLOG(" -Page settings says b/f cache disabled");
cannotCache = true;
}
#if ENABLE(DEVICE_ORIENTATION)
if (page->mainFrame()->document()->deviceMotionController() && page->mainFrame()->document()->deviceMotionController()->isActive()) {
PCLOG(" -Page is using DeviceMotion");
cannotCache = true;
}
if (page->mainFrame()->document()->deviceOrientationController() && page->mainFrame()->document()->deviceOrientationController()->isActive()) {
PCLOG(" -Page is using DeviceOrientation");
cannotCache = true;
}
#endif
if (loadType == FrameLoadTypeReload) {
PCLOG(" -Load type is: Reload");
cannotCache = true;
}
if (loadType == FrameLoadTypeReloadFromOrigin) {
PCLOG(" -Load type is: Reload from origin");
cannotCache = true;
}
if (loadType == FrameLoadTypeSame) {
PCLOG(" -Load type is: Same");
cannotCache = true;
}
PCLOG(cannotCache ? " Page CANNOT be cached\n--------" : " Page CAN be cached\n--------");
}
#endif
PageCache* pageCache()
{
static PageCache* staticPageCache = new PageCache;
return staticPageCache;
}
PageCache::PageCache()
: m_capacity(0)
, m_size(0)
, m_head(0)
, m_tail(0)
, m_autoreleaseTimer(this, &PageCache::releaseAutoreleasedPagesNowOrReschedule)
#if USE(ACCELERATED_COMPOSITING)
, m_shouldClearBackingStores(false)
#endif
{
}
bool PageCache::canCachePageContainingThisFrame(Frame* frame)
{
for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
if (!canCachePageContainingThisFrame(child))
return false;
}
return frame->loader()->documentLoader()
&& (frame->loader()->documentLoader()->mainDocumentError().isNull()
|| (frame->loader()->documentLoader()->mainDocumentError().isCancellation() && frame->loader()->documentLoader()->subresourceLoadersArePageCacheAcceptable()))
&& !(frame->loader()->documentLoader()->substituteData().isValid() && !frame->loader()->documentLoader()->substituteData().failingURL().isEmpty())
&& !frame->document()->url().protocolIs("https")
#if ENABLE(DATABASE)
&& !frame->document()->hasOpenDatabases()
#endif
#if ENABLE(SHARED_WORKERS)
&& !SharedWorkerRepository::hasSharedWorkers(frame->document())
#endif
&& frame->loader()->history()->currentItem()
&& !frame->loader()->quickRedirectComing()
&& !frame->loader()->documentLoader()->isLoadingInAPISense()
&& !frame->loader()->documentLoader()->isStopping()
&& frame->document()->canSuspendActiveDOMObjects()
#if ENABLE(OFFLINE_WEB_APPLICATIONS)
&& frame->loader()->documentLoader()->applicationCacheHost()->canCacheInPageCache()
#endif
&& frame->loader()->client()->canCachePage();
}
bool PageCache::canCache(Page* page)
{
if (!page)
return false;
#ifndef NDEBUG
logCanCachePageDecision(page);
#endif
FrameLoadType loadType = page->mainFrame()->loader()->loadType();
return canCachePageContainingThisFrame(page->mainFrame())
&& page->backForward()->isActive()
&& page->settings()->usesPageCache()
#if ENABLE(DEVICE_ORIENTATION)
&& !(page->mainFrame()->document()->deviceMotionController() && page->mainFrame()->document()->deviceMotionController()->isActive())
&& !(page->mainFrame()->document()->deviceOrientationController() && page->mainFrame()->document()->deviceOrientationController()->isActive())
#endif
&& loadType != FrameLoadTypeReload
&& loadType != FrameLoadTypeReloadFromOrigin
&& loadType != FrameLoadTypeSame;
}
void PageCache::setCapacity(int capacity)
{
ASSERT(capacity >= 0);
m_capacity = max(capacity, 0);
prune();
}
int 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;
}
return frameCount;
}
int PageCache::autoreleasedPageCount() const
{
return m_autoreleaseSet.size();
}
void PageCache::markPagesForVistedLinkStyleRecalc()
{
for (HistoryItem* current = m_head; current; current = current->m_next)
current->m_cachedPage->markForVistedLinkStyleRecalc();
}
void PageCache::add(PassRefPtr<HistoryItem> prpItem, Page* page)
{
ASSERT(prpItem);
ASSERT(page);
ASSERT(canCache(page));
HistoryItem* item = prpItem.releaseRef();
if (item->m_cachedPage)
remove(item);
item->m_cachedPage = CachedPage::create(page);
addToLRUList(item);
++m_size;
prune();
}
CachedPage* PageCache::get(HistoryItem* item)
{
if (!item)
return 0;
if (CachedPage* cachedPage = item->m_cachedPage.get()) {
if (currentTime() - cachedPage->timeStamp() <= 1800)
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;
}
void PageCache::remove(HistoryItem* item)
{
if (!item || !item->m_cachedPage)
return;
autorelease(item->m_cachedPage.release());
removeFromLRUList(item);
--m_size;
item->deref(); }
void PageCache::prune()
{
while (m_size > m_capacity) {
ASSERT(m_tail && m_tail->m_cachedPage);
remove(m_tail);
}
}
void PageCache::addToLRUList(HistoryItem* item)
{
item->m_next = m_head;
item->m_prev = 0;
if (m_head) {
ASSERT(m_tail);
m_head->m_prev = item;
} else {
ASSERT(!m_tail);
m_tail = item;
}
m_head = item;
}
void PageCache::removeFromLRUList(HistoryItem* item)
{
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;
}
}
void PageCache::releaseAutoreleasedPagesNowOrReschedule(Timer<PageCache>* timer)
{
double loadDelta = currentTime() - FrameLoader::timeOfLastCompletedLoad();
float userDelta = userIdleTime();
if ((userDelta < 0.5 || loadDelta < 1.25) && m_autoreleaseSet.size() < 42) {
LOG(PageCache, "WebCorePageCache: Postponing releaseAutoreleasedPagesNowOrReschedule() - %f since last load, %f since last input, %i objects pending release", loadDelta, userDelta, m_autoreleaseSet.size());
timer->startOneShot(autoreleaseInterval);
return;
}
LOG(PageCache, "WebCorePageCache: Releasing page caches - %f seconds since last load, %f since last input, %i objects pending release", loadDelta, userDelta, m_autoreleaseSet.size());
releaseAutoreleasedPagesNow();
}
void PageCache::releaseAutoreleasedPagesNow()
{
m_autoreleaseTimer.stop();
memoryCache()->setPruneEnabled(false);
CachedPageSet tmp;
tmp.swap(m_autoreleaseSet);
CachedPageSet::iterator end = tmp.end();
for (CachedPageSet::iterator it = tmp.begin(); it != end; ++it)
(*it)->destroy();
memoryCache()->setPruneEnabled(true);
memoryCache()->prune();
}
void PageCache::autorelease(PassRefPtr<CachedPage> page)
{
ASSERT(page);
ASSERT(!m_autoreleaseSet.contains(page.get()));
m_autoreleaseSet.add(page);
if (!m_autoreleaseTimer.isActive())
m_autoreleaseTimer.startOneShot(autoreleaseInterval);
}
}