Tables with vertical-lr writing-mode doesn't apply correctly vertical-align: baseline
[WebKit-https.git] / Source / WebCore / history / PageCache.cpp
1 /*
2  * Copyright (C) 2007, 2014, 2015 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 "CachedPage.h"
32 #include "DOMWindow.h"
33 #include "DeviceMotionController.h"
34 #include "DeviceOrientationController.h"
35 #include "DiagnosticLoggingClient.h"
36 #include "DiagnosticLoggingKeys.h"
37 #include "Document.h"
38 #include "DocumentLoader.h"
39 #include "FocusController.h"
40 #include "Frame.h"
41 #include "FrameLoader.h"
42 #include "FrameLoaderClient.h"
43 #include "FrameView.h"
44 #include "HistoryController.h"
45 #include "IgnoreOpensDuringUnloadCountIncrementer.h"
46 #include "Logging.h"
47 #include "Page.h"
48 #include "ScriptDisallowedScope.h"
49 #include "Settings.h"
50 #include "SubframeLoader.h"
51 #include <pal/Logging.h>
52 #include <wtf/MemoryPressureHandler.h>
53 #include <wtf/NeverDestroyed.h>
54 #include <wtf/SetForScope.h>
55 #include <wtf/text/CString.h>
56 #include <wtf/text/StringConcatenate.h>
57
58 namespace WebCore {
59
60 #define PCLOG(...) LOG(PageCache, "%*s%s", indentLevel*4, "", makeString(__VA_ARGS__).utf8().data())
61
62 static inline void logPageCacheFailureDiagnosticMessage(DiagnosticLoggingClient& client, const String& reason)
63 {
64     client.logDiagnosticMessage(DiagnosticLoggingKeys::pageCacheFailureKey(), reason, ShouldSample::Yes);
65 }
66
67 static inline void logPageCacheFailureDiagnosticMessage(Page* page, const String& reason)
68 {
69     if (!page)
70         return;
71
72     logPageCacheFailureDiagnosticMessage(page->diagnosticLoggingClient(), reason);
73 }
74
75 static bool canCacheFrame(Frame& frame, DiagnosticLoggingClient& diagnosticLoggingClient, unsigned indentLevel)
76 {
77     PCLOG("+---");
78     FrameLoader& frameLoader = frame.loader();
79
80     // Prevent page caching if a subframe is still in provisional load stage.
81     // We only do this check for subframes because the main frame is reused when navigating to a new page.
82     if (!frame.isMainFrame() && frameLoader.state() == FrameStateProvisional) {
83         PCLOG("   -Frame is in provisional load stage");
84         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::provisionalLoadKey());
85         return false;
86     }
87
88     if (frame.isMainFrame() && frameLoader.stateMachine().isDisplayingInitialEmptyDocument()) {
89         PCLOG("   -MainFrame is displaying initial empty document");
90         return false;
91     }
92
93     DocumentLoader* documentLoader = frameLoader.documentLoader();
94     if (!documentLoader) {
95         PCLOG("   -There is no DocumentLoader object");
96         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noDocumentLoaderKey());
97         return false;
98     }
99
100     URL currentURL = documentLoader->url();
101     URL newURL = frameLoader.provisionalDocumentLoader() ? frameLoader.provisionalDocumentLoader()->url() : URL();
102     if (!newURL.isEmpty())
103         PCLOG(" Determining if frame can be cached navigating from (", currentURL.string(), ") to (", newURL.string(), "):");
104     else
105         PCLOG(" Determining if subframe with URL (", currentURL.string(), ") can be cached:");
106      
107     bool isCacheable = true;
108     if (!documentLoader->mainDocumentError().isNull()) {
109         PCLOG("   -Main document has an error");
110         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::mainDocumentErrorKey());
111
112         if (documentLoader->mainDocumentError().isCancellation() && documentLoader->subresourceLoadersArePageCacheAcceptable())
113             PCLOG("    -But, it was a cancellation and all loaders during the cancelation were loading images or XHR.");
114         else
115             isCacheable = false;
116     }
117     // Do not cache error pages (these can be recognized as pages with substitute data or unreachable URLs).
118     if (documentLoader->substituteData().isValid() && !documentLoader->substituteData().failingURL().isEmpty()) {
119         PCLOG("   -Frame is an error page");
120         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isErrorPageKey());
121         isCacheable = false;
122     }
123     if (frameLoader.subframeLoader().containsPlugins() && !frame.page()->settings().pageCacheSupportsPlugins()) {
124         PCLOG("   -Frame contains plugins");
125         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::hasPluginsKey());
126         isCacheable = false;
127     }
128     if (frame.isMainFrame() && frame.document() && frame.document()->url().protocolIs("https") && documentLoader->response().cacheControlContainsNoStore()) {
129         PCLOG("   -Frame is HTTPS, and cache control prohibits storing");
130         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::httpsNoStoreKey());
131         isCacheable = false;
132     }
133     if (frame.isMainFrame() && !frameLoader.history().currentItem()) {
134         PCLOG("   -Main frame has no current history item");
135         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noCurrentHistoryItemKey());
136         isCacheable = false;
137     }
138     if (frameLoader.quickRedirectComing()) {
139         PCLOG("   -Quick redirect is coming");
140         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::quirkRedirectComingKey());
141         isCacheable = false;
142     }
143     if (documentLoader->isLoading()) {
144         PCLOG("   -DocumentLoader is still loading");
145         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isLoadingKey());
146         isCacheable = false;
147     }
148     if (documentLoader->isStopping()) {
149         PCLOG("   -DocumentLoader is in the middle of stopping");
150         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::documentLoaderStoppingKey());
151         isCacheable = false;
152     }
153
154     Vector<ActiveDOMObject*> unsuspendableObjects;
155     if (frame.document() && !frame.document()->canSuspendActiveDOMObjectsForDocumentSuspension(&unsuspendableObjects)) {
156         PCLOG("   -The document cannot suspend its active DOM Objects");
157         for (auto* activeDOMObject : unsuspendableObjects) {
158             PCLOG("    - Unsuspendable: ", activeDOMObject->activeDOMObjectName());
159             diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::unsuspendableDOMObjectKey(), activeDOMObject->activeDOMObjectName(), ShouldSample::Yes);
160             UNUSED_PARAM(activeDOMObject);
161         }
162         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::cannotSuspendActiveDOMObjectsKey());
163         isCacheable = false;
164     }
165 #if ENABLE(SERVICE_WORKER)
166     if (frame.document() && frame.document()->activeServiceWorker()) {
167         PCLOG("   -The document has an active service worker");
168         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::serviceWorkerKey());
169         isCacheable = false;
170     }
171 #endif
172     // FIXME: We should investigating caching frames that have an associated
173     // application cache. <rdar://problem/5917899> tracks that work.
174     if (!documentLoader->applicationCacheHost().canCacheInPageCache()) {
175         PCLOG("   -The DocumentLoader uses an application cache");
176         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::applicationCacheKey());
177         isCacheable = false;
178     }
179     if (!frameLoader.client().canCachePage()) {
180         PCLOG("   -The client says this frame cannot be cached");
181         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deniedByClientKey());
182         isCacheable = false;
183     }
184
185     for (Frame* child = frame.tree().firstChild(); child; child = child->tree().nextSibling()) {
186         if (!canCacheFrame(*child, diagnosticLoggingClient, indentLevel + 1))
187             isCacheable = false;
188     }
189     
190     PCLOG(isCacheable ? " Frame CAN be cached" : " Frame CANNOT be cached");
191     PCLOG("+---");
192     
193     return isCacheable;
194 }
195
196 static bool canCachePage(Page& page)
197 {
198     RELEASE_ASSERT(!page.isRestoringCachedPage());
199
200     unsigned indentLevel = 0;
201     PCLOG("--------\n Determining if page can be cached:");
202
203     DiagnosticLoggingClient& diagnosticLoggingClient = page.diagnosticLoggingClient();
204     bool isCacheable = canCacheFrame(page.mainFrame(), diagnosticLoggingClient, indentLevel + 1);
205
206     if (!page.settings().usesPageCache() || page.isResourceCachingDisabled()) {
207         PCLOG("   -Page settings says b/f cache disabled");
208         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isDisabledKey());
209         isCacheable = false;
210     }
211 #if ENABLE(DEVICE_ORIENTATION) && !PLATFORM(IOS_FAMILY)
212     if (DeviceMotionController::isActiveAt(&page)) {
213         PCLOG("   -Page is using DeviceMotion");
214         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceMotionKey());
215         isCacheable = false;
216     }
217     if (DeviceOrientationController::isActiveAt(&page)) {
218         PCLOG("   -Page is using DeviceOrientation");
219         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceOrientationKey());
220         isCacheable = false;
221     }
222 #endif
223
224     FrameLoadType loadType = page.mainFrame().loader().loadType();
225     switch (loadType) {
226     case FrameLoadType::Reload:
227         // No point writing to the cache on a reload, since we will just write over it again when we leave that page.
228         PCLOG("   -Load type is: Reload");
229         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadKey());
230         isCacheable = false;
231         break;
232     case FrameLoadType::Same: // user loads same URL again (but not reload button)
233         // No point writing to the cache on a same load, since we will just write over it again when we leave that page.
234         PCLOG("   -Load type is: Same");
235         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::sameLoadKey());
236         isCacheable = false;
237         break;
238     case FrameLoadType::RedirectWithLockedBackForwardList:
239         // 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.
240         PCLOG("   -Load type is: RedirectWithLockedBackForwardList");
241         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::redirectKey());
242         isCacheable = false;
243         break;
244     case FrameLoadType::Replace:
245         // No point writing to the cache on a replace, since we will just write over it again when we leave that page.
246         PCLOG("   -Load type is: Replace");
247         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::replaceKey());
248         isCacheable = false;
249         break;
250     case FrameLoadType::ReloadFromOrigin: {
251         // No point writing to the cache on a reload, since we will just write over it again when we leave that page.
252         PCLOG("   -Load type is: ReloadFromOrigin");
253         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadFromOriginKey());
254         isCacheable = false;
255         break;
256     }
257     case FrameLoadType::ReloadExpiredOnly: {
258         // No point writing to the cache on a reload, since we will just write over it again when we leave that page.
259         PCLOG("   -Load type is: ReloadRevalidatingExpired");
260         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadRevalidatingExpiredKey());
261         isCacheable = false;
262         break;
263     }
264     case FrameLoadType::Standard:
265     case FrameLoadType::Back:
266     case FrameLoadType::Forward:
267     case FrameLoadType::IndexedBackForward: // a multi-item hop in the backforward list
268         // Cacheable.
269         break;
270     }
271     
272     if (isCacheable)
273         PCLOG(" Page CAN be cached\n--------");
274     else
275         PCLOG(" Page CANNOT be cached\n--------");
276
277     diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::pageCacheKey(), DiagnosticLoggingKeys::canCacheKey(), isCacheable ? DiagnosticLoggingResultPass : DiagnosticLoggingResultFail, ShouldSample::Yes);
278     return isCacheable;
279 }
280
281 PageCache& PageCache::singleton()
282 {
283     static NeverDestroyed<PageCache> globalPageCache;
284     return globalPageCache;
285 }
286
287 PageCache::PageCache()
288 {
289     static std::once_flag onceFlag;
290     std::call_once(onceFlag, [] {
291         PAL::registerNotifyCallback("com.apple.WebKit.showPageCache", [] {
292             PageCache::singleton().dump();
293         });
294     });
295 }
296
297 void PageCache::dump() const
298 {
299     WTFLogAlways("\nPage Cache:");
300     for (auto& item : m_items) {
301         CachedPage& cachedPage = *item->m_cachedPage;
302         WTFLogAlways("  Page %p, document %p %s", &cachedPage.page(), cachedPage.document(), cachedPage.document() ? cachedPage.document()->url().string().utf8().data() : "");
303     }
304 }
305
306 bool PageCache::canCache(Page& page) const
307 {
308     if (!m_maxSize) {
309         logPageCacheFailureDiagnosticMessage(&page, DiagnosticLoggingKeys::isDisabledKey());
310         return false;
311     }
312
313     if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
314         logPageCacheFailureDiagnosticMessage(&page, DiagnosticLoggingKeys::underMemoryPressureKey());
315         return false;
316     }
317     
318     return canCachePage(page);
319 }
320
321 void PageCache::pruneToSizeNow(unsigned size, PruningReason pruningReason)
322 {
323     SetForScope<unsigned> change(m_maxSize, size);
324     prune(pruningReason);
325 }
326
327 void PageCache::setMaxSize(unsigned maxSize)
328 {
329     m_maxSize = maxSize;
330     prune(PruningReason::None);
331 }
332
333 unsigned PageCache::frameCount() const
334 {
335     unsigned frameCount = m_items.size();
336     for (auto& item : m_items) {
337         ASSERT(item->m_cachedPage);
338         frameCount += item->m_cachedPage->cachedMainFrame()->descendantFrameCount();
339     }
340     
341     return frameCount;
342 }
343
344 void PageCache::markPagesForDeviceOrPageScaleChanged(Page& page)
345 {
346     for (auto& item : m_items) {
347         CachedPage& cachedPage = *item->m_cachedPage;
348         if (&page.mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
349             cachedPage.markForDeviceOrPageScaleChanged();
350     }
351 }
352
353 void PageCache::markPagesForContentsSizeChanged(Page& page)
354 {
355     for (auto& item : m_items) {
356         CachedPage& cachedPage = *item->m_cachedPage;
357         if (&page.mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
358             cachedPage.markForContentsSizeChanged();
359     }
360 }
361
362 #if ENABLE(VIDEO_TRACK)
363 void PageCache::markPagesForCaptionPreferencesChanged()
364 {
365     for (auto& item : m_items) {
366         ASSERT(item->m_cachedPage);
367         item->m_cachedPage->markForCaptionPreferencesChanged();
368     }
369 }
370 #endif
371
372 static String pruningReasonToDiagnosticLoggingKey(PruningReason pruningReason)
373 {
374     switch (pruningReason) {
375     case PruningReason::MemoryPressure:
376         return DiagnosticLoggingKeys::prunedDueToMemoryPressureKey();
377     case PruningReason::ProcessSuspended:
378         return DiagnosticLoggingKeys::prunedDueToProcessSuspended();
379     case PruningReason::ReachedMaxSize:
380         return DiagnosticLoggingKeys::prunedDueToMaxSizeReached();
381     case PruningReason::None:
382         break;
383     }
384     ASSERT_NOT_REACHED();
385     return emptyString();
386 }
387
388 static void setPageCacheState(Page& page, Document::PageCacheState pageCacheState)
389 {
390     for (Frame* frame = &page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
391         if (auto* document = frame->document())
392             document->setPageCacheState(pageCacheState);
393     }
394 }
395
396 // When entering page cache, tear down the render tree before setting the in-cache flag.
397 // This maintains the invariant that render trees are never present in the page cache.
398 // Note that destruction happens bottom-up so that the main frame's tree dies last.
399 static void destroyRenderTree(Frame& mainFrame)
400 {
401     for (Frame* frame = mainFrame.tree().traversePrevious(CanWrap::Yes); frame; frame = frame->tree().traversePrevious(CanWrap::No)) {
402         if (!frame->document())
403             continue;
404         auto& document = *frame->document();
405         if (document.hasLivingRenderTree())
406             document.destroyRenderTree();
407     }
408 }
409
410 static void firePageHideEventRecursively(Frame& frame)
411 {
412     auto* document = frame.document();
413     if (!document)
414         return;
415
416     // stopLoading() will fire the pagehide event in each subframe and the HTML specification states
417     // that the parent document's ignore-opens-during-unload counter should be incremented while the
418     // pagehide event is being fired in its subframes:
419     // https://html.spec.whatwg.org/multipage/browsers.html#unload-a-document
420     IgnoreOpensDuringUnloadCountIncrementer ignoreOpensDuringUnloadCountIncrementer(document);
421
422     frame.loader().stopLoading(UnloadEventPolicyUnloadAndPageHide);
423
424     for (RefPtr<Frame> child = frame.tree().firstChild(); child; child = child->tree().nextSibling())
425         firePageHideEventRecursively(*child);
426 }
427
428 void PageCache::addIfCacheable(HistoryItem& item, Page* page)
429 {
430     if (item.isInPageCache())
431         return;
432
433     if (!page || !canCache(*page))
434         return;
435
436     ASSERT_WITH_MESSAGE(!page->isUtilityPage(), "Utility pages such as SVGImage pages should never go into PageCache");
437
438     setPageCacheState(*page, Document::AboutToEnterPageCache);
439
440     // Focus the main frame, defocusing a focused subframe (if we have one). We do this here,
441     // before the page enters the page cache, while we still can dispatch DOM blur/focus events.
442     if (page->focusController().focusedFrame())
443         page->focusController().setFocusedFrame(&page->mainFrame());
444
445     // Fire the pagehide event in all frames.
446     firePageHideEventRecursively(page->mainFrame());
447
448     // Check that the page is still page-cacheable after firing the pagehide event. The JS event handlers
449     // could have altered the page in a way that could prevent caching.
450     if (!canCache(*page)) {
451         setPageCacheState(*page, Document::NotInPageCache);
452         return;
453     }
454
455     destroyRenderTree(page->mainFrame());
456
457     setPageCacheState(*page, Document::InPageCache);
458
459     {
460         // Make sure we don't fire any JS events in this scope.
461         ScriptDisallowedScope::InMainThread scriptDisallowedScope;
462
463         item.m_cachedPage = std::make_unique<CachedPage>(*page);
464         item.m_pruningReason = PruningReason::None;
465         m_items.add(&item);
466     }
467     prune(PruningReason::ReachedMaxSize);
468 }
469
470 std::unique_ptr<CachedPage> PageCache::take(HistoryItem& item, Page* page)
471 {
472     if (!item.m_cachedPage) {
473         if (item.m_pruningReason != PruningReason::None)
474             logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
475         return nullptr;
476     }
477
478     m_items.remove(&item);
479     std::unique_ptr<CachedPage> cachedPage = WTFMove(item.m_cachedPage);
480
481     if (cachedPage->hasExpired() || (page && page->isResourceCachingDisabled())) {
482         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
483         logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
484         return nullptr;
485     }
486
487     return cachedPage;
488 }
489
490 void PageCache::removeAllItemsForPage(Page& page)
491 {
492 #if !ASSERT_DISABLED
493     ASSERT_WITH_MESSAGE(!m_isInRemoveAllItemsForPage, "We should not reenter this method");
494     SetForScope<bool> inRemoveAllItemsForPageScope { m_isInRemoveAllItemsForPage, true };
495 #endif
496
497     for (auto it = m_items.begin(); it != m_items.end();) {
498         // Increment iterator first so it stays valid after the removal.
499         auto current = it;
500         ++it;
501         if (&(*current)->m_cachedPage->page() == &page) {
502             (*current)->m_cachedPage = nullptr;
503             m_items.remove(current);
504         }
505     }
506 }
507
508 CachedPage* PageCache::get(HistoryItem& item, Page* page)
509 {
510     CachedPage* cachedPage = item.m_cachedPage.get();
511     if (!cachedPage) {
512         if (item.m_pruningReason != PruningReason::None)
513             logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
514         return nullptr;
515     }
516
517     if (cachedPage->hasExpired() || (page && page->isResourceCachingDisabled())) {
518         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
519         logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
520         remove(item);
521         return nullptr;
522     }
523     return cachedPage;
524 }
525
526 void PageCache::remove(HistoryItem& item)
527 {
528     // Safely ignore attempts to remove items not in the cache.
529     if (!item.m_cachedPage)
530         return;
531
532     m_items.remove(&item);
533     item.m_cachedPage = nullptr;
534 }
535
536 void PageCache::prune(PruningReason pruningReason)
537 {
538     while (pageCount() > maxSize()) {
539         auto oldestItem = m_items.takeFirst();
540         oldestItem->m_cachedPage = nullptr;
541         oldestItem->m_pruningReason = pruningReason;
542     }
543 }
544
545 } // namespace WebCore