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