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