fd9df5233f743f01bbae14d3130d9b6eab93f519
[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     client.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::pageCacheKey(), DiagnosticLoggingKeys::failureKey(), reason);
102 }
103
104 static inline void logPageCacheFailureDiagnosticMessage(Page* page, const String& reason)
105 {
106     if (!page)
107         return;
108
109     logPageCacheFailureDiagnosticMessage(page->mainFrame().diagnosticLoggingClient(), reason);
110 }
111
112 static unsigned logCanCacheFrameDecision(Frame& frame, DiagnosticLoggingClient& diagnosticLoggingClient, int indentLevel)
113 {
114     PCLOG("+---");
115     if (!frame.loader().documentLoader()) {
116         PCLOG("   -There is no DocumentLoader object");
117         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noDocumentLoaderKey());
118         return 1 << NoDocumentLoader;
119     }
120
121     URL currentURL = frame.loader().documentLoader()->url();
122     URL newURL = frame.loader().provisionalDocumentLoader() ? frame.loader().provisionalDocumentLoader()->url() : URL();
123     if (!newURL.isEmpty())
124         PCLOG(" Determining if frame can be cached navigating from (", currentURL.string(), ") to (", newURL.string(), "):");
125     else
126         PCLOG(" Determining if subframe with URL (", currentURL.string(), ") can be cached:");
127      
128     unsigned rejectReasons = 0;
129     if (!frame.loader().documentLoader()->mainDocumentError().isNull()) {
130         PCLOG("   -Main document has an error");
131         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::mainDocumentErrorKey());
132 #if !PLATFORM(IOS)
133         rejectReasons |= 1 << MainDocumentError;
134 #else
135         if (frame.loader().documentLoader()->mainDocumentError().isCancellation() && frame.loader().documentLoader()->subresourceLoadersArePageCacheAcceptable())
136             PCLOG("    -But, it was a cancellation and all loaders during the cancel were loading images.");
137         else
138             rejectReasons |= 1 << MainDocumentError;
139 #endif
140     }
141     if (frame.loader().documentLoader()->substituteData().isValid() && frame.loader().documentLoader()->substituteData().failingURL().isEmpty()) {
142         PCLOG("   -Frame is an error page");
143         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isErrorPageKey());
144         rejectReasons |= 1 << IsErrorPage;
145     }
146     if (frame.loader().subframeLoader().containsPlugins() && !frame.page()->settings().pageCacheSupportsPlugins()) {
147         PCLOG("   -Frame contains plugins");
148         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::hasPluginsKey());
149         rejectReasons |= 1 << HasPlugins;
150     }
151     if (frame.isMainFrame() && frame.document()->url().protocolIs("https") && frame.loader().documentLoader()->response().cacheControlContainsNoStore()) {
152         PCLOG("   -Frame is HTTPS, and cache control prohibits storing");
153         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::httpsNoStoreKey());
154         rejectReasons |= 1 << IsHttpsAndCacheControlled;
155     }
156     if (DatabaseManager::manager().hasOpenDatabases(frame.document())) {
157         PCLOG("   -Frame has open database handles");
158         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::hasOpenDatabasesKey());
159         rejectReasons |= 1 << HasDatabaseHandles;
160     }
161     if (!frame.loader().history().currentItem()) {
162         PCLOG("   -No current history item");
163         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noCurrentHistoryItemKey());
164         rejectReasons |= 1 << NoHistoryItem;
165     }
166     if (frame.loader().quickRedirectComing()) {
167         PCLOG("   -Quick redirect is coming");
168         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::quirkRedirectComingKey());
169         rejectReasons |= 1 << QuickRedirectComing;
170     }
171     if (frame.loader().documentLoader()->isLoadingInAPISense()) {
172         PCLOG("   -DocumentLoader is still loading in API sense");
173         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::loadingAPISenseKey());
174         rejectReasons |= 1 << IsLoadingInAPISense;
175     }
176     if (frame.loader().documentLoader()->isStopping()) {
177         PCLOG("   -DocumentLoader is in the middle of stopping");
178         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::documentLoaderStoppingKey());
179         rejectReasons |= 1 << IsStopping;
180     }
181
182     Vector<ActiveDOMObject*> unsuspendableObjects;
183     if (!frame.document()->canSuspendActiveDOMObjects(&unsuspendableObjects)) {
184         PCLOG("   -The document cannot suspend its active DOM Objects");
185         for (auto* activeDOMObject : unsuspendableObjects) {
186             PCLOG("    - Unsuspendable: ", activeDOMObject->activeDOMObjectName());
187             UNUSED_PARAM(activeDOMObject);
188         }
189         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::cannotSuspendActiveDOMObjectsKey());
190         rejectReasons |= 1 << CannotSuspendActiveDOMObjects;
191     }
192     if (!frame.loader().documentLoader()->applicationCacheHost()->canCacheInPageCache()) {
193         PCLOG("   -The DocumentLoader uses an application cache");
194         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::applicationCacheKey());
195         rejectReasons |= 1 << DocumentLoaderUsesApplicationCache;
196     }
197     if (!frame.loader().client().canCachePage()) {
198         PCLOG("   -The client says this frame cannot be cached");
199         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deniedByClientKey());
200         rejectReasons |= 1 << ClientDeniesCaching;
201     }
202
203     for (Frame* child = frame.tree().firstChild(); child; child = child->tree().nextSibling())
204         rejectReasons |= logCanCacheFrameDecision(*child, diagnosticLoggingClient, indentLevel + 1);
205     
206     PCLOG(rejectReasons ? " Frame CANNOT be cached" : " Frame CAN be cached");
207     PCLOG("+---");
208     
209     return rejectReasons;
210 }
211
212 // Used in histograms, please only add at the end, and do not remove elements (renaming e.g. to "FooEnumUnused1" is fine).
213 // This is because statistics may be gathered from histograms between versions over time, and re-using values causes collisions.
214 enum ReasonPageCannotBeInPageCache {
215     FrameCannotBeInPageCache = 0,
216     DisabledBackForwardList,
217     DisabledPageCache,
218     UsesDeviceMotion,
219     UsesDeviceOrientation,
220     IsReload,
221     IsReloadFromOrigin,
222     IsSameLoad,
223     NumberOfReasonsPagesCannotBeInPageCache,
224 };
225 COMPILE_ASSERT(NumberOfReasonsPagesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonPageCannotBeInPageCacheDoesNotFitInBitmap);
226
227 static void logCanCachePageDecision(Page& page)
228 {
229     // Only bother logging for main frames that have actually loaded and have content.
230     if (page.mainFrame().loader().stateMachine().creatingInitialEmptyDocument())
231         return;
232     URL currentURL = page.mainFrame().loader().documentLoader() ? page.mainFrame().loader().documentLoader()->url() : URL();
233     if (currentURL.isEmpty())
234         return;
235     
236     int indentLevel = 0;    
237     PCLOG("--------\n Determining if page can be cached:");
238     
239     unsigned rejectReasons = 0;
240     MainFrame& mainFrame = page.mainFrame();
241     DiagnosticLoggingClient& diagnosticLoggingClient = mainFrame.diagnosticLoggingClient();
242     unsigned frameRejectReasons = logCanCacheFrameDecision(mainFrame, diagnosticLoggingClient, indentLevel + 1);
243     if (frameRejectReasons)
244         rejectReasons |= 1 << FrameCannotBeInPageCache;
245     
246     if (!page.settings().usesPageCache()) {
247         PCLOG("   -Page settings says b/f cache disabled");
248         rejectReasons |= 1 << DisabledPageCache;
249     }
250 #if ENABLE(DEVICE_ORIENTATION) && !PLATFORM(IOS)
251     if (DeviceMotionController::isActiveAt(page)) {
252         PCLOG("   -Page is using DeviceMotion");
253         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceMotionKey());
254         rejectReasons |= 1 << UsesDeviceMotion;
255     }
256     if (DeviceOrientationController::isActiveAt(page)) {
257         PCLOG("   -Page is using DeviceOrientation");
258         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceOrientationKey());
259         rejectReasons |= 1 << UsesDeviceOrientation;
260     }
261 #endif
262 #if ENABLE(PROXIMITY_EVENTS)
263     if (DeviceProximityController::isActiveAt(page)) {
264         PCLOG("   -Page is using DeviceProximity");
265         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, deviceProximityKey);
266         rejectReasons |= 1 << UsesDeviceMotion;
267     }
268 #endif
269     FrameLoadType loadType = page.mainFrame().loader().loadType();
270     if (loadType == FrameLoadType::Reload) {
271         PCLOG("   -Load type is: Reload");
272         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadKey());
273         rejectReasons |= 1 << IsReload;
274     }
275     if (loadType == FrameLoadType::ReloadFromOrigin) {
276         PCLOG("   -Load type is: Reload from origin");
277         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadFromOriginKey());
278         rejectReasons |= 1 << IsReloadFromOrigin;
279     }
280     if (loadType == FrameLoadType::Same) {
281         PCLOG("   -Load type is: Same");
282         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::sameLoadKey());
283         rejectReasons |= 1 << IsSameLoad;
284     }
285     
286     if (rejectReasons)
287         PCLOG(" Page CANNOT be cached\n--------");
288     else
289         PCLOG(" Page CAN be cached\n--------");
290
291     diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::pageCacheKey(), emptyString(), rejectReasons ? DiagnosticLoggingResultFail : DiagnosticLoggingResultPass);
292 }
293
294 PageCache* pageCache()
295 {
296     static PageCache* staticPageCache = new PageCache;
297     return staticPageCache;
298 }
299
300 PageCache::PageCache()
301     : m_capacity(0)
302     , m_size(0)
303     , m_head(0)
304     , m_tail(0)
305     , m_shouldClearBackingStores(false)
306 {
307 }
308     
309 bool PageCache::canCachePageContainingThisFrame(Frame* frame)
310 {
311     for (Frame* child = frame->tree().firstChild(); child; child = child->tree().nextSibling()) {
312         if (!canCachePageContainingThisFrame(child))
313             return false;
314     }
315     
316     FrameLoader& frameLoader = frame->loader();
317     DocumentLoader* documentLoader = frameLoader.documentLoader();
318     Document* document = frame->document();
319     
320     return documentLoader
321 #if !PLATFORM(IOS)
322         && documentLoader->mainDocumentError().isNull()
323 #else
324         && (documentLoader->mainDocumentError().isNull() || (documentLoader->mainDocumentError().isCancellation() && documentLoader->subresourceLoadersArePageCacheAcceptable()))
325 #endif
326         // Do not cache error pages (these can be recognized as pages with substitute data or unreachable URLs).
327         && !(documentLoader->substituteData().isValid() && !documentLoader->substituteData().failingURL().isEmpty())
328         && (!frameLoader.subframeLoader().containsPlugins() || frame->page()->settings().pageCacheSupportsPlugins())
329         && !(frame->isMainFrame() && document->url().protocolIs("https") && documentLoader->response().cacheControlContainsNoStore())
330         && !DatabaseManager::manager().hasOpenDatabases(document)
331         && frameLoader.history().currentItem()
332         && !frameLoader.quickRedirectComing()
333         && !documentLoader->isLoadingInAPISense()
334         && !documentLoader->isStopping()
335         && document->canSuspendActiveDOMObjects()
336         // FIXME: We should investigating caching frames that have an associated
337         // application cache. <rdar://problem/5917899> tracks that work.
338         && documentLoader->applicationCacheHost()->canCacheInPageCache()
339         && frameLoader.client().canCachePage();
340 }
341     
342 bool PageCache::canCache(Page* page) const
343 {
344     if (!page)
345         return false;
346     
347     logCanCachePageDecision(*page);
348
349     if (memoryPressureHandler().isUnderMemoryPressure())
350         return false;
351
352     // Cache the page, if possible.
353     // Don't write to the cache if in the middle of a redirect, since we will want to
354     // store the final page we end up on.
355     // No point writing to the cache on a reload or loadSame, since we will just write
356     // over it again when we leave that page.
357     FrameLoadType loadType = page->mainFrame().loader().loadType();
358     
359     return m_capacity > 0
360         && canCachePageContainingThisFrame(&page->mainFrame())
361         && page->settings().usesPageCache()
362 #if ENABLE(DEVICE_ORIENTATION) && !PLATFORM(IOS)
363         && !DeviceMotionController::isActiveAt(page)
364         && !DeviceOrientationController::isActiveAt(page)
365 #endif
366 #if ENABLE(PROXIMITY_EVENTS)
367         && !DeviceProximityController::isActiveAt(page)
368 #endif
369         && (loadType == FrameLoadType::Standard
370             || loadType == FrameLoadType::Back
371             || loadType == FrameLoadType::Forward
372             || loadType == FrameLoadType::IndexedBackForward);
373 }
374
375 void PageCache::pruneToCapacityNow(int capacity, PruningReason pruningReason)
376 {
377     TemporaryChange<int>(m_capacity, std::max(capacity, 0));
378     prune(pruningReason);
379 }
380
381 void PageCache::setCapacity(int capacity)
382 {
383     ASSERT(capacity >= 0);
384     m_capacity = std::max(capacity, 0);
385
386     prune(PruningReason::None);
387 }
388
389 int PageCache::frameCount() const
390 {
391     int frameCount = 0;
392     for (HistoryItem* current = m_head; current; current = current->m_next) {
393         ++frameCount;
394         ASSERT(current->m_cachedPage);
395         frameCount += current->m_cachedPage->cachedMainFrame()->descendantFrameCount();
396     }
397     
398     return frameCount;
399 }
400
401 void PageCache::markPagesForVistedLinkStyleRecalc()
402 {
403     for (HistoryItem* current = m_head; current; current = current->m_next) {
404         ASSERT(current->m_cachedPage);
405         current->m_cachedPage->markForVistedLinkStyleRecalc();
406     }
407 }
408
409 void PageCache::markPagesForFullStyleRecalc(Page* page)
410 {
411     for (HistoryItem* current = m_head; current; current = current->m_next) {
412         CachedPage& cachedPage = *current->m_cachedPage;
413         if (&page->mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
414             cachedPage.markForFullStyleRecalc();
415     }
416 }
417
418 void PageCache::markPagesForDeviceScaleChanged(Page* page)
419 {
420     for (HistoryItem* current = m_head; current; current = current->m_next) {
421         CachedPage& cachedPage = *current->m_cachedPage;
422         if (&page->mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
423             cachedPage.markForDeviceScaleChanged();
424     }
425 }
426
427 #if ENABLE(VIDEO_TRACK)
428 void PageCache::markPagesForCaptionPreferencesChanged()
429 {
430     for (HistoryItem* current = m_head; current; current = current->m_next) {
431         ASSERT(current->m_cachedPage);
432         current->m_cachedPage->markForCaptionPreferencesChanged();
433     }
434 }
435 #endif
436
437 static String pruningReasonToDiagnosticLoggingKey(PruningReason pruningReason)
438 {
439     switch (pruningReason) {
440     case PruningReason::MemoryPressure:
441         return DiagnosticLoggingKeys::prunedDueToMemoryPressureKey();
442     case PruningReason::ProcessSuspended:
443         return DiagnosticLoggingKeys::prunedDueToProcessSuspended();
444     case PruningReason::ReachedCapacity:
445         return DiagnosticLoggingKeys::prunedDueToCapacityReached();
446     case PruningReason::None:
447         break;
448     }
449     ASSERT_NOT_REACHED();
450     return emptyString();
451 }
452
453 void PageCache::add(PassRefPtr<HistoryItem> prpItem, Page& page)
454 {
455     ASSERT(prpItem);
456     ASSERT(canCache(&page));
457     
458     HistoryItem* item = prpItem.leakRef(); // Balanced in remove().
459
460     // Remove stale cache entry if necessary.
461     if (item->m_cachedPage)
462         remove(item);
463
464     item->m_cachedPage = std::make_unique<CachedPage>(page);
465     item->m_pruningReason = PruningReason::None;
466     addToLRUList(item);
467     ++m_size;
468     
469     prune(PruningReason::ReachedCapacity);
470 }
471
472 std::unique_ptr<CachedPage> PageCache::take(HistoryItem* item, Page* page)
473 {
474     if (!item)
475         return nullptr;
476
477     std::unique_ptr<CachedPage> cachedPage = WTF::move(item->m_cachedPage);
478
479     removeFromLRUList(item);
480     --m_size;
481
482     item->deref(); // Balanced in add().
483
484     if (!cachedPage) {
485         if (item->m_pruningReason != PruningReason::None)
486             logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item->m_pruningReason));
487         return nullptr;
488     }
489
490     if (cachedPage->hasExpired()) {
491         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data());
492         logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
493         return nullptr;
494     }
495
496     return cachedPage;
497 }
498
499 CachedPage* PageCache::get(HistoryItem* item, Page* page)
500 {
501     if (!item)
502         return nullptr;
503
504     if (CachedPage* cachedPage = item->m_cachedPage.get()) {
505         if (!cachedPage->hasExpired())
506             return cachedPage;
507         
508         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data());
509         logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
510         pageCache()->remove(item);
511     } else if (item->m_pruningReason != PruningReason::None)
512         logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item->m_pruningReason));
513
514     return nullptr;
515 }
516
517 void PageCache::remove(HistoryItem* item)
518 {
519     // Safely ignore attempts to remove items not in the cache.
520     if (!item || !item->m_cachedPage)
521         return;
522
523     item->m_cachedPage = nullptr;
524     removeFromLRUList(item);
525     --m_size;
526
527     item->deref(); // Balanced in add().
528 }
529
530 void PageCache::prune(PruningReason pruningReason)
531 {
532     while (m_size > m_capacity) {
533         ASSERT(m_tail && m_tail->m_cachedPage);
534         m_tail->m_pruningReason = pruningReason;
535         remove(m_tail);
536     }
537 }
538
539 void PageCache::addToLRUList(HistoryItem* item)
540 {
541     item->m_next = m_head;
542     item->m_prev = 0;
543
544     if (m_head) {
545         ASSERT(m_tail);
546         m_head->m_prev = item;
547     } else {
548         ASSERT(!m_tail);
549         m_tail = item;
550     }
551
552     m_head = item;
553 }
554
555 void PageCache::removeFromLRUList(HistoryItem* item)
556 {
557     if (!item->m_next) {
558         ASSERT(item == m_tail);
559         m_tail = item->m_prev;
560     } else {
561         ASSERT(item != m_tail);
562         item->m_next->m_prev = item->m_prev;
563     }
564
565     if (!item->m_prev) {
566         ASSERT(item == m_head);
567         m_head = item->m_next;
568     } else {
569         ASSERT(item != m_head);
570         item->m_prev->m_next = item->m_next;
571     }
572 }
573
574 } // namespace WebCore