cf65c0f8608f9ebfa2bc369e605d7734104d315f
[WebKit-https.git] / Source / WebCore / history / PageCache.cpp
1 /*
2  * Copyright (C) 2007, 2014 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "PageCache.h"
28
29 #include "ApplicationCacheHost.h"
30 #include "BackForwardController.h"
31 #include "MemoryCache.h"
32 #include "CachedPage.h"
33 #include "DOMWindow.h"
34 #include "DatabaseManager.h"
35 #include "DeviceMotionController.h"
36 #include "DeviceOrientationController.h"
37 #include "DiagnosticLoggingClient.h"
38 #include "DiagnosticLoggingKeys.h"
39 #include "Document.h"
40 #include "DocumentLoader.h"
41 #include "FrameLoader.h"
42 #include "FrameLoaderClient.h"
43 #include "FrameLoaderStateMachine.h"
44 #include "FrameView.h"
45 #include "HistoryController.h"
46 #include "HistoryItem.h"
47 #include "Logging.h"
48 #include "MainFrame.h"
49 #include "MemoryPressureHandler.h"
50 #include "Page.h"
51 #include "Settings.h"
52 #include "SubframeLoader.h"
53 #include <wtf/CurrentTime.h>
54 #include <wtf/TemporaryChange.h>
55 #include <wtf/text/CString.h>
56 #include <wtf/text/StringConcatenate.h>
57
58 #if ENABLE(PROXIMITY_EVENTS)
59 #include "DeviceProximityController.h"
60 #endif
61
62 #if PLATFORM(IOS)
63 #include "MemoryPressureHandler.h"
64 #endif
65
66 namespace WebCore {
67
68 #if !defined(NDEBUG)
69
70 #define PCLOG(...) LOG(PageCache, "%*s%s", indentLevel*4, "", makeString(__VA_ARGS__).utf8().data())
71
72 #else
73
74 #define PCLOG(...) ((void)0)
75
76 #endif // !defined(NDEBUG)
77     
78 // Used in histograms, please only add at the end, and do not remove elements (renaming e.g. to "FooEnumUnused1" is fine).
79 // This is because statistics may be gathered from histograms between versions over time, and re-using values causes collisions.
80 enum ReasonFrameCannotBeInPageCache {
81     NoDocumentLoader = 0,
82     MainDocumentError,
83     IsErrorPage,
84     HasPlugins,
85     IsHttpsAndCacheControlled,
86     HasDatabaseHandles,
87     HasSharedWorkers, // FIXME: Remove.
88     NoHistoryItem,
89     QuickRedirectComing,
90     IsLoadingInAPISense,
91     IsStopping,
92     CannotSuspendActiveDOMObjects,
93     DocumentLoaderUsesApplicationCache,
94     ClientDeniesCaching,
95     NumberOfReasonsFramesCannotBeInPageCache,
96 };
97 COMPILE_ASSERT(NumberOfReasonsFramesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonFrameCannotBeInPageCacheDoesNotFitInBitmap);
98
99 static inline void logPageCacheFailureDiagnosticMessage(DiagnosticLoggingClient* client, const String& reason)
100 {
101     if (client)
102         client->logDiagnosticMessageWithValue(DiagnosticLoggingKeys::pageCacheKey(), DiagnosticLoggingKeys::failureKey(), reason);
103 }
104
105 static inline void logPageCacheFailureDiagnosticMessage(Page* page, const String& reason)
106 {
107     if (!page)
108         return;
109
110     MainFrame& mainFrame = page->mainFrame();
111     if (mainFrame.settings().diagnosticLoggingEnabled())
112         logPageCacheFailureDiagnosticMessage(mainFrame.diagnosticLoggingClient(), reason);
113 }
114
115 static unsigned logCanCacheFrameDecision(Frame& frame, DiagnosticLoggingClient* diagnosticLoggingClient, int indentLevel)
116 {
117     PCLOG("+---");
118     if (!frame.loader().documentLoader()) {
119         PCLOG("   -There is no DocumentLoader object");
120         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noDocumentLoaderKey());
121         return 1 << NoDocumentLoader;
122     }
123
124     URL currentURL = frame.loader().documentLoader()->url();
125     URL newURL = frame.loader().provisionalDocumentLoader() ? frame.loader().provisionalDocumentLoader()->url() : URL();
126     if (!newURL.isEmpty())
127         PCLOG(" Determining if frame can be cached navigating from (", currentURL.string(), ") to (", newURL.string(), "):");
128     else
129         PCLOG(" Determining if subframe with URL (", currentURL.string(), ") can be cached:");
130      
131     unsigned rejectReasons = 0;
132     if (!frame.loader().documentLoader()->mainDocumentError().isNull()) {
133         PCLOG("   -Main document has an error");
134         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::mainDocumentErrorKey());
135 #if !PLATFORM(IOS)
136         rejectReasons |= 1 << MainDocumentError;
137 #else
138         if (frame.loader().documentLoader()->mainDocumentError().isCancellation() && frame.loader().documentLoader()->subresourceLoadersArePageCacheAcceptable())
139             PCLOG("    -But, it was a cancellation and all loaders during the cancel were loading images.");
140         else
141             rejectReasons |= 1 << MainDocumentError;
142 #endif
143     }
144     if (frame.loader().documentLoader()->substituteData().isValid() && frame.loader().documentLoader()->substituteData().failingURL().isEmpty()) {
145         PCLOG("   -Frame is an error page");
146         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isErrorPageKey());
147         rejectReasons |= 1 << IsErrorPage;
148     }
149     if (frame.loader().subframeLoader().containsPlugins() && !frame.page()->settings().pageCacheSupportsPlugins()) {
150         PCLOG("   -Frame contains plugins");
151         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::hasPluginsKey());
152         rejectReasons |= 1 << HasPlugins;
153     }
154     if (frame.isMainFrame() && frame.document()->url().protocolIs("https") && frame.loader().documentLoader()->response().cacheControlContainsNoStore()) {
155         PCLOG("   -Frame is HTTPS, and cache control prohibits storing");
156         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::httpsNoStoreKey());
157         rejectReasons |= 1 << IsHttpsAndCacheControlled;
158     }
159     if (DatabaseManager::manager().hasOpenDatabases(frame.document())) {
160         PCLOG("   -Frame has open database handles");
161         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::hasOpenDatabasesKey());
162         rejectReasons |= 1 << HasDatabaseHandles;
163     }
164     if (!frame.loader().history().currentItem()) {
165         PCLOG("   -No current history item");
166         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noCurrentHistoryItemKey());
167         rejectReasons |= 1 << NoHistoryItem;
168     }
169     if (frame.loader().quickRedirectComing()) {
170         PCLOG("   -Quick redirect is coming");
171         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::quirkRedirectComingKey());
172         rejectReasons |= 1 << QuickRedirectComing;
173     }
174     if (frame.loader().documentLoader()->isLoadingInAPISense()) {
175         PCLOG("   -DocumentLoader is still loading in API sense");
176         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::loadingAPISenseKey());
177         rejectReasons |= 1 << IsLoadingInAPISense;
178     }
179     if (frame.loader().documentLoader()->isStopping()) {
180         PCLOG("   -DocumentLoader is in the middle of stopping");
181         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::documentLoaderStoppingKey());
182         rejectReasons |= 1 << IsStopping;
183     }
184
185     Vector<ActiveDOMObject*> unsuspendableObjects;
186     if (!frame.document()->canSuspendActiveDOMObjects(&unsuspendableObjects)) {
187         PCLOG("   -The document cannot suspend its active DOM Objects");
188         for (auto* activeDOMObject : unsuspendableObjects) {
189             PCLOG("    - Unsuspendable: ", activeDOMObject->activeDOMObjectName());
190             UNUSED_PARAM(activeDOMObject);
191         }
192         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::cannotSuspendActiveDOMObjectsKey());
193         rejectReasons |= 1 << CannotSuspendActiveDOMObjects;
194     }
195     if (!frame.loader().documentLoader()->applicationCacheHost()->canCacheInPageCache()) {
196         PCLOG("   -The DocumentLoader uses an application cache");
197         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::applicationCacheKey());
198         rejectReasons |= 1 << DocumentLoaderUsesApplicationCache;
199     }
200     if (!frame.loader().client().canCachePage()) {
201         PCLOG("   -The client says this frame cannot be cached");
202         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deniedByClientKey());
203         rejectReasons |= 1 << ClientDeniesCaching;
204     }
205
206     for (Frame* child = frame.tree().firstChild(); child; child = child->tree().nextSibling())
207         rejectReasons |= logCanCacheFrameDecision(*child, diagnosticLoggingClient, indentLevel + 1);
208     
209     PCLOG(rejectReasons ? " Frame CANNOT be cached" : " Frame CAN be cached");
210     PCLOG("+---");
211     
212     return rejectReasons;
213 }
214
215 // Used in histograms, please only add at the end, and do not remove elements (renaming e.g. to "FooEnumUnused1" is fine).
216 // This is because statistics may be gathered from histograms between versions over time, and re-using values causes collisions.
217 enum ReasonPageCannotBeInPageCache {
218     FrameCannotBeInPageCache = 0,
219     DisabledBackForwardList,
220     DisabledPageCache,
221     UsesDeviceMotion,
222     UsesDeviceOrientation,
223     IsReload,
224     IsReloadFromOrigin,
225     IsSameLoad,
226     NumberOfReasonsPagesCannotBeInPageCache,
227 };
228 COMPILE_ASSERT(NumberOfReasonsPagesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonPageCannotBeInPageCacheDoesNotFitInBitmap);
229
230 static void logCanCachePageDecision(Page& page)
231 {
232     // Only bother logging for main frames that have actually loaded and have content.
233     if (page.mainFrame().loader().stateMachine().creatingInitialEmptyDocument())
234         return;
235     URL currentURL = page.mainFrame().loader().documentLoader() ? page.mainFrame().loader().documentLoader()->url() : URL();
236     if (currentURL.isEmpty())
237         return;
238     
239     int indentLevel = 0;    
240     PCLOG("--------\n Determining if page can be cached:");
241     
242     unsigned rejectReasons = 0;
243     MainFrame& mainFrame = page.mainFrame();
244     DiagnosticLoggingClient* diagnosticLoggingClient = mainFrame.settings().diagnosticLoggingEnabled() ? mainFrame.diagnosticLoggingClient() : nullptr;
245     unsigned frameRejectReasons = logCanCacheFrameDecision(mainFrame, diagnosticLoggingClient, indentLevel + 1);
246     if (frameRejectReasons)
247         rejectReasons |= 1 << FrameCannotBeInPageCache;
248     
249     if (!page.settings().usesPageCache()) {
250         PCLOG("   -Page settings says b/f cache disabled");
251         rejectReasons |= 1 << DisabledPageCache;
252     }
253 #if ENABLE(DEVICE_ORIENTATION) && !PLATFORM(IOS)
254     if (DeviceMotionController::isActiveAt(page)) {
255         PCLOG("   -Page is using DeviceMotion");
256         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceMotionKey());
257         rejectReasons |= 1 << UsesDeviceMotion;
258     }
259     if (DeviceOrientationController::isActiveAt(page)) {
260         PCLOG("   -Page is using DeviceOrientation");
261         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceOrientationKey());
262         rejectReasons |= 1 << UsesDeviceOrientation;
263     }
264 #endif
265 #if ENABLE(PROXIMITY_EVENTS)
266     if (DeviceProximityController::isActiveAt(page)) {
267         PCLOG("   -Page is using DeviceProximity");
268         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, deviceProximityKey);
269         rejectReasons |= 1 << UsesDeviceMotion;
270     }
271 #endif
272     FrameLoadType loadType = page.mainFrame().loader().loadType();
273     if (loadType == FrameLoadType::Reload) {
274         PCLOG("   -Load type is: Reload");
275         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadKey());
276         rejectReasons |= 1 << IsReload;
277     }
278     if (loadType == FrameLoadType::ReloadFromOrigin) {
279         PCLOG("   -Load type is: Reload from origin");
280         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadFromOriginKey());
281         rejectReasons |= 1 << IsReloadFromOrigin;
282     }
283     if (loadType == FrameLoadType::Same) {
284         PCLOG("   -Load type is: Same");
285         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::sameLoadKey());
286         rejectReasons |= 1 << IsSameLoad;
287     }
288     
289     if (rejectReasons)
290         PCLOG(" Page CANNOT be cached\n--------");
291     else
292         PCLOG(" Page CAN be cached\n--------");
293
294     if (diagnosticLoggingClient)
295         diagnosticLoggingClient->logDiagnosticMessageWithResult(DiagnosticLoggingKeys::pageCacheKey(), emptyString(), rejectReasons ? DiagnosticLoggingResultFail : DiagnosticLoggingResultPass);
296 }
297
298 PageCache* pageCache()
299 {
300     static PageCache* staticPageCache = new PageCache;
301     return staticPageCache;
302 }
303
304 PageCache::PageCache()
305     : m_capacity(0)
306     , m_size(0)
307     , m_head(0)
308     , m_tail(0)
309     , m_shouldClearBackingStores(false)
310 {
311 }
312     
313 bool PageCache::canCachePageContainingThisFrame(Frame* frame)
314 {
315     for (Frame* child = frame->tree().firstChild(); child; child = child->tree().nextSibling()) {
316         if (!canCachePageContainingThisFrame(child))
317             return false;
318     }
319     
320     FrameLoader& frameLoader = frame->loader();
321     DocumentLoader* documentLoader = frameLoader.documentLoader();
322     Document* document = frame->document();
323     
324     return documentLoader
325 #if !PLATFORM(IOS)
326         && documentLoader->mainDocumentError().isNull()
327 #else
328         && (documentLoader->mainDocumentError().isNull() || (documentLoader->mainDocumentError().isCancellation() && documentLoader->subresourceLoadersArePageCacheAcceptable()))
329 #endif
330         // Do not cache error pages (these can be recognized as pages with substitute data or unreachable URLs).
331         && !(documentLoader->substituteData().isValid() && !documentLoader->substituteData().failingURL().isEmpty())
332         && (!frameLoader.subframeLoader().containsPlugins() || frame->page()->settings().pageCacheSupportsPlugins())
333         && !(frame->isMainFrame() && document->url().protocolIs("https") && documentLoader->response().cacheControlContainsNoStore())
334         && !DatabaseManager::manager().hasOpenDatabases(document)
335         && frameLoader.history().currentItem()
336         && !frameLoader.quickRedirectComing()
337         && !documentLoader->isLoadingInAPISense()
338         && !documentLoader->isStopping()
339         && document->canSuspendActiveDOMObjects()
340         // FIXME: We should investigating caching frames that have an associated
341         // application cache. <rdar://problem/5917899> tracks that work.
342         && documentLoader->applicationCacheHost()->canCacheInPageCache()
343         && frameLoader.client().canCachePage();
344 }
345     
346 bool PageCache::canCache(Page* page) const
347 {
348     if (!page)
349         return false;
350     
351     logCanCachePageDecision(*page);
352
353     if (memoryPressureHandler().isUnderMemoryPressure())
354         return false;
355
356     // Cache the page, if possible.
357     // Don't write to the cache if in the middle of a redirect, since we will want to
358     // store the final page we end up on.
359     // No point writing to the cache on a reload or loadSame, since we will just write
360     // over it again when we leave that page.
361     FrameLoadType loadType = page->mainFrame().loader().loadType();
362     
363     return m_capacity > 0
364         && canCachePageContainingThisFrame(&page->mainFrame())
365         && page->settings().usesPageCache()
366 #if ENABLE(DEVICE_ORIENTATION) && !PLATFORM(IOS)
367         && !DeviceMotionController::isActiveAt(page)
368         && !DeviceOrientationController::isActiveAt(page)
369 #endif
370 #if ENABLE(PROXIMITY_EVENTS)
371         && !DeviceProximityController::isActiveAt(page)
372 #endif
373         && (loadType == FrameLoadType::Standard
374             || loadType == FrameLoadType::Back
375             || loadType == FrameLoadType::Forward
376             || loadType == FrameLoadType::IndexedBackForward);
377 }
378
379 void PageCache::pruneToCapacityNow(int capacity, PruningReason pruningReason)
380 {
381     TemporaryChange<int>(m_capacity, std::max(capacity, 0));
382     prune(pruningReason);
383 }
384
385 void PageCache::setCapacity(int capacity)
386 {
387     ASSERT(capacity >= 0);
388     m_capacity = std::max(capacity, 0);
389
390     prune(PruningReason::None);
391 }
392
393 int PageCache::frameCount() const
394 {
395     int frameCount = 0;
396     for (HistoryItem* current = m_head; current; current = current->m_next) {
397         ++frameCount;
398         ASSERT(current->m_cachedPage);
399         frameCount += current->m_cachedPage ? current->m_cachedPage->cachedMainFrame()->descendantFrameCount() : 0;
400     }
401     
402     return frameCount;
403 }
404
405 void PageCache::markPagesForVistedLinkStyleRecalc()
406 {
407     for (HistoryItem* current = m_head; current; current = current->m_next) {
408         if (current->m_cachedPage)
409             current->m_cachedPage->markForVistedLinkStyleRecalc();
410     }
411 }
412
413 void PageCache::markPagesForFullStyleRecalc(Page* page)
414 {
415     for (HistoryItem* current = m_head; current; current = current->m_next) {
416         CachedPage* cachedPage = current->m_cachedPage.get();
417         if (cachedPage && &page->mainFrame() == &cachedPage->cachedMainFrame()->view()->frame())
418             cachedPage->markForFullStyleRecalc();
419     }
420 }
421
422 void PageCache::markPagesForDeviceScaleChanged(Page* page)
423 {
424     for (HistoryItem* current = m_head; current; current = current->m_next) {
425         CachedPage* cachedPage = current->m_cachedPage.get();
426         if (cachedPage && &page->mainFrame() == &cachedPage->cachedMainFrame()->view()->frame())
427             cachedPage->markForDeviceScaleChanged();
428     }
429 }
430
431 #if ENABLE(VIDEO_TRACK)
432 void PageCache::markPagesForCaptionPreferencesChanged()
433 {
434     for (HistoryItem* current = m_head; current; current = current->m_next) {
435         if (current->m_cachedPage)
436             current->m_cachedPage->markForCaptionPreferencesChanged();
437     }
438 }
439 #endif
440
441 static String pruningReasonToDiagnosticLoggingKey(PruningReason pruningReason)
442 {
443     switch (pruningReason) {
444     case PruningReason::MemoryPressure:
445         return DiagnosticLoggingKeys::prunedDueToMemoryPressureKey();
446     case PruningReason::ProcessSuspended:
447         return DiagnosticLoggingKeys::prunedDueToProcessSuspended();
448     case PruningReason::ReachedCapacity:
449         return DiagnosticLoggingKeys::prunedDueToCapacityReached();
450     case PruningReason::None:
451         break;
452     }
453     ASSERT_NOT_REACHED();
454     return emptyString();
455 }
456
457 void PageCache::add(PassRefPtr<HistoryItem> prpItem, Page& page)
458 {
459     ASSERT(prpItem);
460     ASSERT(canCache(&page));
461     
462     HistoryItem* item = prpItem.leakRef(); // Balanced in remove().
463
464     // Remove stale cache entry if necessary.
465     if (item->m_cachedPage)
466         remove(item);
467
468     item->m_cachedPage = std::make_unique<CachedPage>(page);
469     item->m_pruningReason = PruningReason::None;
470     addToLRUList(item);
471     ++m_size;
472     
473     prune(PruningReason::ReachedCapacity);
474 }
475
476 std::unique_ptr<CachedPage> PageCache::take(HistoryItem* item, Page* page)
477 {
478     if (!item)
479         return nullptr;
480
481     std::unique_ptr<CachedPage> cachedPage = WTF::move(item->m_cachedPage);
482
483     removeFromLRUList(item);
484     --m_size;
485
486     item->deref(); // Balanced in add().
487
488     if (!cachedPage) {
489         if (item->m_pruningReason != PruningReason::None)
490             logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item->m_pruningReason));
491         return nullptr;
492     }
493
494     if (cachedPage->hasExpired()) {
495         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data());
496         logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
497         return nullptr;
498     }
499
500     return cachedPage;
501 }
502
503 CachedPage* PageCache::get(HistoryItem* item, Page* page)
504 {
505     if (!item)
506         return nullptr;
507
508     if (CachedPage* cachedPage = item->m_cachedPage.get()) {
509         if (!cachedPage->hasExpired())
510             return cachedPage;
511         
512         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data());
513         logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
514         pageCache()->remove(item);
515     } else if (item->m_pruningReason != PruningReason::None)
516         logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item->m_pruningReason));
517
518     return nullptr;
519 }
520
521 void PageCache::remove(HistoryItem* item)
522 {
523     // Safely ignore attempts to remove items not in the cache.
524     if (!item || !item->m_cachedPage)
525         return;
526
527     item->m_cachedPage = nullptr;
528     removeFromLRUList(item);
529     --m_size;
530
531     item->deref(); // Balanced in add().
532 }
533
534 void PageCache::prune(PruningReason pruningReason)
535 {
536     while (m_size > m_capacity) {
537         ASSERT(m_tail && m_tail->m_cachedPage);
538         m_tail->m_pruningReason = pruningReason;
539         remove(m_tail);
540     }
541 }
542
543 void PageCache::addToLRUList(HistoryItem* item)
544 {
545     item->m_next = m_head;
546     item->m_prev = 0;
547
548     if (m_head) {
549         ASSERT(m_tail);
550         m_head->m_prev = item;
551     } else {
552         ASSERT(!m_tail);
553         m_tail = item;
554     }
555
556     m_head = item;
557 }
558
559 void PageCache::removeFromLRUList(HistoryItem* item)
560 {
561     if (!item->m_next) {
562         ASSERT(item == m_tail);
563         m_tail = item->m_prev;
564     } else {
565         ASSERT(item != m_tail);
566         item->m_next->m_prev = item->m_prev;
567     }
568
569     if (!item->m_prev) {
570         ASSERT(item == m_head);
571         m_head = item->m_next;
572     } else {
573         ASSERT(item != m_head);
574         item->m_prev->m_next = item->m_next;
575     }
576 }
577
578 } // namespace WebCore