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 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
196static 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
281PageCache& PageCache::singleton()
282{
283 static NeverDestroyed<PageCache> globalPageCache;
284 return globalPageCache;
285}
286
287PageCache::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
297void 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
306bool 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
321void PageCache::pruneToSizeNow(unsigned size, PruningReason pruningReason)
322{
323 SetForScope<unsigned> change(m_maxSize, size);
324 prune(pruningReason);
325}
326
327void PageCache::setMaxSize(unsigned maxSize)
328{
329 m_maxSize = maxSize;
330 prune(PruningReason::None);
331}
332
333unsigned 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
344void 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
353void 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)
363void PageCache::markPagesForCaptionPreferencesChanged()
364{
365 for (auto& item : m_items) {
366 ASSERT(item->m_cachedPage);
367 item->m_cachedPage->markForCaptionPreferencesChanged();
368 }
369}
370#endif
371
372static 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
388static 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.
399static 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
410static 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
428bool PageCache::addIfCacheable(HistoryItem& item, Page* page)
429{
430 if (item.isInPageCache())
431 return false;
432
433 if (!page || !canCache(*page))
434 return false;
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 false;
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 return true;
469}
470
471std::unique_ptr<CachedPage> PageCache::take(HistoryItem& item, Page* page)
472{
473 if (!item.m_cachedPage) {
474 if (item.m_pruningReason != PruningReason::None)
475 logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
476 return nullptr;
477 }
478
479 m_items.remove(&item);
480 std::unique_ptr<CachedPage> cachedPage = WTFMove(item.m_cachedPage);
481
482 if (cachedPage->hasExpired() || (page && page->isResourceCachingDisabled())) {
483 LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
484 logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
485 return nullptr;
486 }
487
488 return cachedPage;
489}
490
491void PageCache::removeAllItemsForPage(Page& page)
492{
493#if !ASSERT_DISABLED
494 ASSERT_WITH_MESSAGE(!m_isInRemoveAllItemsForPage, "We should not reenter this method");
495 SetForScope<bool> inRemoveAllItemsForPageScope { m_isInRemoveAllItemsForPage, true };
496#endif
497
498 for (auto it = m_items.begin(); it != m_items.end();) {
499 // Increment iterator first so it stays valid after the removal.
500 auto current = it;
501 ++it;
502 if (&(*current)->m_cachedPage->page() == &page) {
503 (*current)->m_cachedPage = nullptr;
504 m_items.remove(current);
505 }
506 }
507}
508
509CachedPage* PageCache::get(HistoryItem& item, Page* page)
510{
511 CachedPage* cachedPage = item.m_cachedPage.get();
512 if (!cachedPage) {
513 if (item.m_pruningReason != PruningReason::None)
514 logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
515 return nullptr;
516 }
517
518 if (cachedPage->hasExpired() || (page && page->isResourceCachingDisabled())) {
519 LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
520 logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
521 remove(item);
522 return nullptr;
523 }
524 return cachedPage;
525}
526
527void PageCache::remove(HistoryItem& item)
528{
529 // Safely ignore attempts to remove items not in the cache.
530 if (!item.m_cachedPage)
531 return;
532
533 m_items.remove(&item);
534 item.m_cachedPage = nullptr;
535}
536
537void PageCache::prune(PruningReason pruningReason)
538{
539 while (pageCount() > maxSize()) {
540 auto oldestItem = m_items.takeFirst();
541 oldestItem->m_cachedPage = nullptr;
542 oldestItem->m_pruningReason = pruningReason;
543 }
544}
545
546} // namespace WebCore
547