PageCache.cpp 18.3 KB
Newer Older
ggaren's avatar
ggaren committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/*
 * Copyright (C) 2007 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    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
 * 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
 * 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
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include "config.h"
#include "PageCache.h"

29
#include "ApplicationCacheHost.h"
30
#include "BackForwardController.h"
31
#include "MemoryCache.h"
ggaren's avatar
ggaren committed
32
#include "CachedPage.h"
33
#include "DOMWindow.h"
34
#include "DatabaseManager.h"
35 36
#include "DeviceMotionController.h"
#include "DeviceOrientationController.h"
37
#include "DeviceProximityController.h"
38 39 40
#include "Document.h"
#include "DocumentLoader.h"
#include "Frame.h"
ggaren's avatar
ggaren committed
41
#include "FrameLoader.h"
42
#include "FrameLoaderClient.h"
43
#include "FrameLoaderStateMachine.h"
44
#include "FrameView.h"
45
#include "HistogramSupport.h"
46
#include "HistoryController.h"
ggaren's avatar
ggaren committed
47
#include "HistoryItem.h"
ggaren's avatar
ggaren committed
48
#include "Logging.h"
49 50 51
#include "Page.h"
#include "Settings.h"
#include "SharedWorkerRepository.h"
ggaren's avatar
ggaren committed
52
#include "SystemTime.h"
ap@webkit.org's avatar
ap@webkit.org committed
53
#include <wtf/CurrentTime.h>
54
#include <wtf/text/CString.h>
55
#include <wtf/text/StringConcatenate.h>
ggaren's avatar
ggaren committed
56

ggaren's avatar
ggaren committed
57 58
using namespace std;

ggaren's avatar
ggaren committed
59 60
namespace WebCore {

61
#if !defined(NDEBUG)
62

63
#define PCLOG(...) LOG(PageCache, "%*s%s", indentLevel*4, "", makeString(__VA_ARGS__).utf8().data())
64
    
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
// 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,
80
    CannotSuspendActiveDOMObjects,
81 82 83 84
    DocumentLoaderUsesApplicationCache,
    ClientDeniesCaching,
    NumberOfReasonsFramesCannotBeInPageCache,
};
85
COMPILE_ASSERT(NumberOfReasonsFramesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonFrameCannotBeInPageCacheDoesNotFitInBitmap);
86 87

static unsigned logCanCacheFrameDecision(Frame* frame, int indentLevel)
88 89
{
    PCLOG("+---");
90 91 92 93 94 95
    if (!frame->loader()->documentLoader()) {
        PCLOG("   -There is no DocumentLoader object");
        return 1 << NoDocumentLoader;
    }

    KURL currentURL = frame->loader()->documentLoader()->url();
96 97
    KURL newURL = frame->loader()->provisionalDocumentLoader() ? frame->loader()->provisionalDocumentLoader()->url() : KURL();
    if (!newURL.isEmpty())
98
        PCLOG(" Determining if frame can be cached navigating from (", currentURL.string(), ") to (", newURL.string(), "):");
99
    else
100
        PCLOG(" Determining if subframe with URL (", currentURL.string(), ") can be cached:");
101 102
     
    unsigned rejectReasons = 0;
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
    if (!frame->loader()->documentLoader()->mainDocumentError().isNull()) {
        PCLOG("   -Main document has an error");
        rejectReasons |= 1 << MainDocumentError;
    }
    if (frame->loader()->documentLoader()->substituteData().isValid() && frame->loader()->documentLoader()->substituteData().failingURL().isEmpty()) {
        PCLOG("   -Frame is an error page");
        rejectReasons |= 1 << IsErrorPage;
    }
    if (frame->loader()->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;
    }
121
    if (frame->document()->domWindow() && frame->document()->domWindow()->hasEventListeners(eventNames().unloadEvent)) {
122 123 124
        PCLOG("   -Frame has an unload event listener");
        rejectReasons |= 1 << HasUnloadListener;
    }
125
#if ENABLE(SQL_DATABASE)
126
    if (DatabaseManager::manager().hasOpenDatabases(frame->document())) {
127 128 129
        PCLOG("   -Frame has open database handles");
        rejectReasons |= 1 << HasDatabaseHandles;
    }
130 131
#endif
#if ENABLE(SHARED_WORKERS)
132 133 134 135
    if (SharedWorkerRepository::hasSharedWorkers(frame->document())) {
        PCLOG("   -Frame has associated SharedWorkers");
        rejectReasons |= 1 << HasSharedWorkers;
    }
136
#endif
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
    if (!frame->loader()->history()->currentItem()) {
        PCLOG("   -No current history item");
        rejectReasons |= 1 << NoHistoryItem;
    }
    if (frame->loader()->quickRedirectComing()) {
        PCLOG("   -Quick redirect is coming");
        rejectReasons |= 1 << QuickRedirectComing;
    }
    if (frame->loader()->documentLoader()->isLoadingInAPISense()) {
        PCLOG("   -DocumentLoader is still loading in API sense");
        rejectReasons |= 1 << IsLoadingInAPISense;
    }
    if (frame->loader()->documentLoader()->isStopping()) {
        PCLOG("   -DocumentLoader is in the middle of stopping");
        rejectReasons |= 1 << IsStopping;
    }
    if (!frame->document()->canSuspendActiveDOMObjects()) {
        PCLOG("   -The document cannot suspect its active DOM Objects");
        rejectReasons |= 1 << CannotSuspendActiveDOMObjects;
    }
    if (!frame->loader()->documentLoader()->applicationCacheHost()->canCacheInPageCache()) {
        PCLOG("   -The DocumentLoader uses an application cache");
        rejectReasons |= 1 << DocumentLoaderUsesApplicationCache;
    }
    if (!frame->loader()->client()->canCachePage()) {
        PCLOG("   -The client says this frame cannot be cached");
        rejectReasons |= 1 << ClientDeniesCaching;
164
    }
165 166 167 168 169 170 171 172 173 174 175

    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);
        }
    }
    HistogramSupport::histogramEnumeration("PageCache.FrameRejectReasonCount", reasonCount, 1 + NumberOfReasonsFramesCannotBeInPageCache);

176
    for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling())
177
        rejectReasons |= logCanCacheFrameDecision(child, indentLevel + 1);
178
    
179
    PCLOG(rejectReasons ? " Frame CANNOT be cached" : " Frame CAN be cached");
180 181
    PCLOG("+---");
    
182
    return rejectReasons;
183
}
184 185 186 187 188 189 190 191 192 193 194 195 196 197

// 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,
};
198
COMPILE_ASSERT(NumberOfReasonsPagesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonPageCannotBeInPageCacheDoesNotFitInBitmap);
199

200 201 202
static void logCanCachePageDecision(Page* page)
{
    // Only bother logging for main frames that have actually loaded and have content.
203
    if (page->mainFrame()->loader()->stateMachine()->creatingInitialEmptyDocument())
204 205 206 207 208 209 210 211
        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:");
    
212 213 214 215
    unsigned rejectReasons = 0;
    unsigned frameRejectReasons = logCanCacheFrameDecision(page->mainFrame(), indentLevel+1);
    if (frameRejectReasons)
        rejectReasons |= 1 << FrameCannotBeInPageCache;
216
    
217
    if (!page->backForward()->isActive()) {
218
        PCLOG("   -The back/forward list is disabled or has 0 capacity");
219
        rejectReasons |= 1 << DisabledBackForwardList;
220 221 222
    }
    if (!page->settings()->usesPageCache()) {
        PCLOG("   -Page settings says b/f cache disabled");
223
        rejectReasons |= 1 << DisabledPageCache;
224
    }
225
#if ENABLE(DEVICE_ORIENTATION)
226
    if (DeviceMotionController::isActiveAt(page)) {
227
        PCLOG("   -Page is using DeviceMotion");
228
        rejectReasons |= 1 << UsesDeviceMotion;
229
    }
230
    if (DeviceOrientationController::isActiveAt(page)) {
231
        PCLOG("   -Page is using DeviceOrientation");
232
        rejectReasons |= 1 << UsesDeviceOrientation;
233
    }
234 235 236 237 238 239
#endif
#if ENABLE(PROXIMITY_EVENTS)
    if (DeviceProximityController::isActiveAt(page)) {
        PCLOG("   -Page is using DeviceProximity");
        rejectReasons |= 1 << UsesDeviceMotion;
    }
240
#endif
241
    FrameLoadType loadType = page->mainFrame()->loader()->loadType();
242 243
    if (loadType == FrameLoadTypeReload) {
        PCLOG("   -Load type is: Reload");
244
        rejectReasons |= 1 << IsReload;
245 246 247
    }
    if (loadType == FrameLoadTypeReloadFromOrigin) {
        PCLOG("   -Load type is: Reload from origin");
248
        rejectReasons |= 1 << IsReloadFromOrigin;
249 250 251
    }
    if (loadType == FrameLoadTypeSame) {
        PCLOG("   -Load type is: Same");
252
        rejectReasons |= 1 << IsSameLoad;
253 254
    }
    
255 256 257 258 259 260 261 262 263 264 265
    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);
266 267
    const bool settingsDisabledPageCache = rejectReasons & (1 << DisabledPageCache);
    HistogramSupport::histogramEnumeration("PageCache.PageRejectReasonCountExcludingSettings", reasonCount - settingsDisabledPageCache, NumberOfReasonsPagesCannotBeInPageCache);
268 269 270 271 272 273 274 275 276 277 278

    // 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);
        }
    }
279

280
    HistogramSupport::histogramEnumeration("PageCache.FrameRejectReasonCountByPage", frameReasonCount, 1 + NumberOfReasonsFramesCannotBeInPageCache);
281 282
}

283
#endif // !defined(NDEBUG)
284

ggaren's avatar
ggaren committed
285 286
PageCache* pageCache()
{
ggaren's avatar
ggaren committed
287
    static PageCache* staticPageCache = new PageCache;
ggaren's avatar
ggaren committed
288 289 290 291
    return staticPageCache;
}

PageCache::PageCache()
ggaren's avatar
ggaren committed
292
    : m_capacity(0)
ggaren's avatar
ggaren committed
293 294 295
    , m_size(0)
    , m_head(0)
    , m_tail(0)
296 297 298
#if USE(ACCELERATED_COMPOSITING)
    , m_shouldClearBackingStores(false)
#endif
ggaren's avatar
ggaren committed
299 300
{
}
301 302 303 304 305 306 307 308
    
bool PageCache::canCachePageContainingThisFrame(Frame* frame)
{
    for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
        if (!canCachePageContainingThisFrame(child))
            return false;
    }
    
309 310 311 312 313 314
    FrameLoader* frameLoader = frame->loader();
    DocumentLoader* documentLoader = frameLoader->documentLoader();
    Document* document = frame->document();
    
    return documentLoader
        && documentLoader->mainDocumentError().isNull()
315
        // Do not cache error pages (these can be recognized as pages with substitute data or unreachable URLs).
316 317
        && !(documentLoader->substituteData().isValid() && !documentLoader->substituteData().failingURL().isEmpty())
        && (!frameLoader->subframeLoader()->containsPlugins() || frame->page()->settings()->pageCacheSupportsPlugins())
318
        && (!document->url().protocolIs("https") || (!documentLoader->response().cacheControlContainsNoCache() && !documentLoader->response().cacheControlContainsNoStore()))
319
        && (!document->domWindow() || !document->domWindow()->hasEventListeners(eventNames().unloadEvent))
320
#if ENABLE(SQL_DATABASE)
321
        && !DatabaseManager::manager().hasOpenDatabases(document)
322 323
#endif
#if ENABLE(SHARED_WORKERS)
324
        && !SharedWorkerRepository::hasSharedWorkers(document)
325
#endif
326 327 328 329 330
        && frameLoader->history()->currentItem()
        && !frameLoader->quickRedirectComing()
        && !documentLoader->isLoadingInAPISense()
        && !documentLoader->isStopping()
        && document->canSuspendActiveDOMObjects()
331 332
        // FIXME: We should investigating caching frames that have an associated
        // application cache. <rdar://problem/5917899> tracks that work.
333 334
        && documentLoader->applicationCacheHost()->canCacheInPageCache()
        && frameLoader->client()->canCachePage();
335 336
}
    
337
bool PageCache::canCache(Page* page) const
338 339 340 341
{
    if (!page)
        return false;
    
342
#if !defined(NDEBUG)
343 344 345 346 347 348 349 350 351 352
    logCanCachePageDecision(page);
#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();
    
353 354
    return m_capacity > 0
        && canCachePageContainingThisFrame(page->mainFrame())
355
        && page->backForward()->isActive()
356
        && page->settings()->usesPageCache()
357
#if ENABLE(DEVICE_ORIENTATION)
358 359
        && !DeviceMotionController::isActiveAt(page)
        && !DeviceOrientationController::isActiveAt(page)
360 361 362
#endif
#if ENABLE(PROXIMITY_EVENTS)
        && !DeviceProximityController::isActiveAt(page)
363
#endif
364 365 366 367
        && (loadType == FrameLoadTypeStandard
            || loadType == FrameLoadTypeBack
            || loadType == FrameLoadTypeForward
            || loadType == FrameLoadTypeIndexedBackForward);
368
}
ggaren's avatar
ggaren committed
369 370 371 372 373 374 375 376 377

void PageCache::setCapacity(int capacity)
{
    ASSERT(capacity >= 0);
    m_capacity = max(capacity, 0);

    prune();
}

beidson@apple.com's avatar
beidson@apple.com committed
378 379 380 381 382 383 384 385 386 387 388 389
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;
}

390 391 392 393 394 395
void PageCache::markPagesForVistedLinkStyleRecalc()
{
    for (HistoryItem* current = m_head; current; current = current->m_next)
        current->m_cachedPage->markForVistedLinkStyleRecalc();
}

396 397 398 399 400 401 402 403 404 405 406
void PageCache::markPagesForFullStyleRecalc(Page* page)
{
    Frame* mainFrame = page->mainFrame();

    for (HistoryItem* current = m_head; current; current = current->m_next) {
        CachedPage* cachedPage = current->m_cachedPage.get();
        if (cachedPage->cachedMainFrame()->view()->frame() == mainFrame)
            cachedPage->markForFullStyleRecalc();
    }
}

407 408 409 410 411 412 413 414
#if ENABLE(VIDEO_TRACK)
void PageCache::markPagesForCaptionPreferencesChanged()
{
    for (HistoryItem* current = m_head; current; current = current->m_next)
        current->m_cachedPage->markForCaptionPreferencesChanged();
}
#endif

415
void PageCache::add(PassRefPtr<HistoryItem> prpItem, Page* page)
ggaren's avatar
ggaren committed
416
{
ggaren's avatar
ggaren committed
417
    ASSERT(prpItem);
418 419
    ASSERT(page);
    ASSERT(canCache(page));
ggaren's avatar
ggaren committed
420
    
421
    HistoryItem* item = prpItem.leakRef(); // Balanced in remove().
ggaren's avatar
ggaren committed
422

ggaren's avatar
ggaren committed
423 424 425 426
    // Remove stale cache entry if necessary.
    if (item->m_cachedPage)
        remove(item);

427
    item->m_cachedPage = CachedPage::create(page);
ggaren's avatar
ggaren committed
428 429 430
    addToLRUList(item);
    ++m_size;
    
ggaren's avatar
ggaren committed
431 432 433
    prune();
}

434 435 436 437 438 439
CachedPage* PageCache::get(HistoryItem* item)
{
    if (!item)
        return 0;

    if (CachedPage* cachedPage = item->m_cachedPage.get()) {
440
        if (!cachedPage->hasExpired())
441 442 443 444 445 446 447 448
            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;
}

ggaren's avatar
ggaren committed
449
void PageCache::remove(HistoryItem* item)
ggaren's avatar
ggaren committed
450
{
ggaren's avatar
ggaren committed
451
    // Safely ignore attempts to remove items not in the cache.
ggaren's avatar
ggaren committed
452
    if (!item || !item->m_cachedPage)
ggaren's avatar
ggaren committed
453 454
        return;

455
    item->m_cachedPage.clear();
ggaren's avatar
ggaren committed
456 457 458 459 460 461 462 463 464 465 466
    removeFromLRUList(item);
    --m_size;

    item->deref(); // Balanced in add().
}

void PageCache::prune()
{
    while (m_size > m_capacity) {
        ASSERT(m_tail && m_tail->m_cachedPage);
        remove(m_tail);
ggaren's avatar
ggaren committed
467 468 469
    }
}

ggaren's avatar
ggaren committed
470
void PageCache::addToLRUList(HistoryItem* item)
ggaren's avatar
ggaren committed
471
{
ggaren's avatar
ggaren committed
472 473 474 475 476 477 478 479 480 481 482 483
    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;
ggaren's avatar
ggaren committed
484 485
}

ggaren's avatar
ggaren committed
486
void PageCache::removeFromLRUList(HistoryItem* item)
ggaren's avatar
ggaren committed
487
{
ggaren's avatar
ggaren committed
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
    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;
    }
ggaren's avatar
ggaren committed
503 504 505
}

} // namespace WebCore