Add performance logging for slow cache retrieves
[WebKit-https.git] / Source / WebKit / NetworkProcess / cache / NetworkCacheSpeculativeLoadManager.cpp
1 /*
2  * Copyright (C) 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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27
28 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
29 #include "NetworkCacheSpeculativeLoadManager.h"
30
31 #include "Logging.h"
32 #include "NetworkCacheEntry.h"
33 #include "NetworkCacheSpeculativeLoad.h"
34 #include "NetworkCacheSubresourcesEntry.h"
35 #include "NetworkProcess.h"
36 #include <WebCore/DiagnosticLoggingKeys.h>
37 #include <pal/HysteresisActivity.h>
38 #include <wtf/HashCountedSet.h>
39 #include <wtf/NeverDestroyed.h>
40 #include <wtf/RefCounted.h>
41 #include <wtf/RunLoop.h>
42 #include <wtf/Seconds.h>
43
44 namespace WebKit {
45
46 namespace NetworkCache {
47
48 using namespace WebCore;
49
50 static const Seconds preloadedEntryLifetime { 10_s };
51
52 #if !LOG_DISABLED
53 static HashCountedSet<String>& allSpeculativeLoadingDiagnosticMessages()
54 {
55     static NeverDestroyed<HashCountedSet<String>> messages;
56     return messages;
57 }
58
59 static void printSpeculativeLoadingDiagnosticMessageCounts()
60 {
61     LOG(NetworkCacheSpeculativePreloading, "-- Speculative loading statistics --");
62     for (auto& pair : allSpeculativeLoadingDiagnosticMessages())
63         LOG(NetworkCacheSpeculativePreloading, "%s: %u", pair.key.utf8().data(), pair.value);
64 }
65 #endif
66
67 static void logSpeculativeLoadingDiagnosticMessage(const GlobalFrameID& frameID, const String& message)
68 {
69 #if !LOG_DISABLED
70     if (WebKit2LogNetworkCacheSpeculativePreloading.state == WTFLogChannelOn)
71         allSpeculativeLoadingDiagnosticMessages().add(message);
72 #endif
73     NetworkProcess::singleton().logDiagnosticMessage(frameID.first, WebCore::DiagnosticLoggingKeys::networkCacheKey(), message, WebCore::ShouldSample::Yes);
74 }
75
76 static const AtomicString& subresourcesType()
77 {
78     ASSERT(RunLoop::isMain());
79     static NeverDestroyed<const AtomicString> resource("SubResources", AtomicString::ConstructFromLiteral);
80     return resource;
81 }
82
83 static inline Key makeSubresourcesKey(const Key& resourceKey, const Salt& salt)
84 {
85     return Key(resourceKey.partition(), subresourcesType(), resourceKey.range(), resourceKey.identifier(), salt);
86 }
87
88 static inline ResourceRequest constructRevalidationRequest(const Key& key, const SubresourceInfo& subResourceInfo, const Entry* entry)
89 {
90     ResourceRequest revalidationRequest(key.identifier());
91     revalidationRequest.setHTTPHeaderFields(subResourceInfo.requestHeaders());
92     revalidationRequest.setFirstPartyForCookies(subResourceInfo.firstPartyForCookies());
93     revalidationRequest.setIsSameSite(subResourceInfo.isSameSite());
94     revalidationRequest.setIsTopSite(subResourceInfo.isTopSite());
95     if (!key.partition().isEmpty())
96         revalidationRequest.setCachePartition(key.partition());
97     ASSERT_WITH_MESSAGE(key.range().isEmpty(), "range is not supported");
98     
99     revalidationRequest.makeUnconditional();
100     if (entry) {
101         String eTag = entry->response().httpHeaderField(HTTPHeaderName::ETag);
102         if (!eTag.isEmpty())
103             revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag);
104
105         String lastModified = entry->response().httpHeaderField(HTTPHeaderName::LastModified);
106         if (!lastModified.isEmpty())
107             revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified);
108     }
109     
110     revalidationRequest.setPriority(subResourceInfo.priority());
111
112     return revalidationRequest;
113 }
114
115 static bool responseNeedsRevalidation(const ResourceResponse& response, WallTime timestamp)
116 {
117     if (response.cacheControlContainsNoCache())
118         return true;
119
120     auto age = computeCurrentAge(response, timestamp);
121     auto lifetime = computeFreshnessLifetimeForHTTPFamily(response, timestamp);
122     return age - lifetime > 0_ms;
123 }
124
125 class SpeculativeLoadManager::ExpiringEntry {
126     WTF_MAKE_FAST_ALLOCATED;
127 public:
128     explicit ExpiringEntry(WTF::Function<void()>&& expirationHandler)
129         : m_lifetimeTimer(WTFMove(expirationHandler))
130     {
131         m_lifetimeTimer.startOneShot(preloadedEntryLifetime);
132     }
133
134 private:
135     Timer m_lifetimeTimer;
136 };
137
138 class SpeculativeLoadManager::PreloadedEntry : private ExpiringEntry {
139     WTF_MAKE_FAST_ALLOCATED;
140 public:
141     PreloadedEntry(std::unique_ptr<Entry> entry, std::optional<ResourceRequest>&& speculativeValidationRequest, WTF::Function<void()>&& lifetimeReachedHandler)
142         : ExpiringEntry(WTFMove(lifetimeReachedHandler))
143         , m_entry(WTFMove(entry))
144         , m_speculativeValidationRequest(WTFMove(speculativeValidationRequest))
145     { }
146
147     std::unique_ptr<Entry> takeCacheEntry()
148     {
149         ASSERT(m_entry);
150         return WTFMove(m_entry);
151     }
152
153     const std::optional<ResourceRequest>& revalidationRequest() const { return m_speculativeValidationRequest; }
154     bool wasRevalidated() const { return !!m_speculativeValidationRequest; }
155
156 private:
157     std::unique_ptr<Entry> m_entry;
158     std::optional<ResourceRequest> m_speculativeValidationRequest;
159 };
160
161 class SpeculativeLoadManager::PendingFrameLoad : public RefCounted<PendingFrameLoad> {
162 public:
163     static Ref<PendingFrameLoad> create(Storage& storage, const Key& mainResourceKey, WTF::Function<void()>&& loadCompletionHandler)
164     {
165         return adoptRef(*new PendingFrameLoad(storage, mainResourceKey, WTFMove(loadCompletionHandler)));
166     }
167
168     ~PendingFrameLoad()
169     {
170         ASSERT(m_didFinishLoad);
171         ASSERT(m_didRetrieveExistingEntry);
172     }
173
174     void registerSubresourceLoad(const ResourceRequest& request, const Key& subresourceKey)
175     {
176         ASSERT(RunLoop::isMain());
177         m_subresourceLoads.append(std::make_unique<SubresourceLoad>(request, subresourceKey));
178         m_loadHysteresisActivity.impulse();
179     }
180
181     void markLoadAsCompleted()
182     {
183         ASSERT(RunLoop::isMain());
184         if (m_didFinishLoad)
185             return;
186
187 #if !LOG_DISABLED
188         printSpeculativeLoadingDiagnosticMessageCounts();
189 #endif
190
191         m_didFinishLoad = true;
192         saveToDiskIfReady();
193         m_loadCompletionHandler();
194     }
195
196     void setExistingSubresourcesEntry(std::unique_ptr<SubresourcesEntry> entry)
197     {
198         ASSERT(!m_existingEntry);
199         ASSERT(!m_didRetrieveExistingEntry);
200
201         m_existingEntry = WTFMove(entry);
202         m_didRetrieveExistingEntry = true;
203         saveToDiskIfReady();
204     }
205
206 private:
207     PendingFrameLoad(Storage& storage, const Key& mainResourceKey, WTF::Function<void()>&& loadCompletionHandler)
208         : m_storage(storage)
209         , m_mainResourceKey(mainResourceKey)
210         , m_loadCompletionHandler(WTFMove(loadCompletionHandler))
211         , m_loadHysteresisActivity([this](PAL::HysteresisState state) { if (state == PAL::HysteresisState::Stopped) markLoadAsCompleted(); })
212     {
213         m_loadHysteresisActivity.impulse();
214     }
215
216     void saveToDiskIfReady()
217     {
218         if (!m_didFinishLoad || !m_didRetrieveExistingEntry)
219             return;
220
221         if (m_subresourceLoads.isEmpty())
222             return;
223
224 #if !LOG_DISABLED
225         LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Saving to disk list of subresources for '%s':", m_mainResourceKey.identifier().utf8().data());
226         for (auto& subresourceLoad : m_subresourceLoads)
227             LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) * Subresource: '%s'.", subresourceLoad->key.identifier().utf8().data());
228 #endif
229
230         if (m_existingEntry) {
231             m_existingEntry->updateSubresourceLoads(m_subresourceLoads);
232             m_storage.store(m_existingEntry->encodeAsStorageRecord(), [](const Data&) { });
233         } else {
234             SubresourcesEntry entry(makeSubresourcesKey(m_mainResourceKey, m_storage.salt()), m_subresourceLoads);
235             m_storage.store(entry.encodeAsStorageRecord(), [](const Data&) { });
236         }
237     }
238
239     Storage& m_storage;
240     Key m_mainResourceKey;
241     Vector<std::unique_ptr<SubresourceLoad>> m_subresourceLoads;
242     WTF::Function<void()> m_loadCompletionHandler;
243     PAL::HysteresisActivity m_loadHysteresisActivity;
244     std::unique_ptr<SubresourcesEntry> m_existingEntry;
245     bool m_didFinishLoad { false };
246     bool m_didRetrieveExistingEntry { false };
247 };
248
249 SpeculativeLoadManager::SpeculativeLoadManager(Cache& cache, Storage& storage)
250     : m_cache(cache)
251     , m_storage(storage)
252 {
253 }
254
255 SpeculativeLoadManager::~SpeculativeLoadManager()
256 {
257 }
258
259 #if !LOG_DISABLED
260
261 static void dumpHTTPHeadersDiff(const HTTPHeaderMap& headersA, const HTTPHeaderMap& headersB)
262 {
263     auto aEnd = headersA.end();
264     for (auto it = headersA.begin(); it != aEnd; ++it) {
265         String valueB = headersB.get(it->key);
266         if (valueB.isNull())
267             LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header is only in first request (value: %s)", it->key.utf8().data(), it->value.utf8().data());
268         else if (it->value != valueB)
269             LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header differs in both requests: %s != %s", it->key.utf8().data(), it->value.utf8().data(), valueB.utf8().data());
270     }
271     auto bEnd = headersB.end();
272     for (auto it = headersB.begin(); it != bEnd; ++it) {
273         if (!headersA.contains(it->key))
274             LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header is only in second request (value: %s)", it->key.utf8().data(), it->value.utf8().data());
275     }
276 }
277
278 #endif
279
280 static bool requestsHeadersMatch(const ResourceRequest& speculativeValidationRequest, const ResourceRequest& actualRequest)
281 {
282     ASSERT(!actualRequest.isConditional());
283     ResourceRequest speculativeRequest = speculativeValidationRequest;
284     speculativeRequest.makeUnconditional();
285
286     if (speculativeRequest.httpHeaderFields() != actualRequest.httpHeaderFields()) {
287         LOG(NetworkCacheSpeculativePreloading, "Cannot reuse speculatively validated entry because HTTP headers used for validation do not match");
288 #if !LOG_DISABLED
289         dumpHTTPHeadersDiff(speculativeRequest.httpHeaderFields(), actualRequest.httpHeaderFields());
290 #endif
291         return false;
292     }
293     return true;
294 }
295
296 bool SpeculativeLoadManager::canUsePreloadedEntry(const PreloadedEntry& entry, const ResourceRequest& actualRequest)
297 {
298     if (!entry.wasRevalidated())
299         return true;
300
301     ASSERT(entry.revalidationRequest());
302     return requestsHeadersMatch(*entry.revalidationRequest(), actualRequest);
303 }
304
305 bool SpeculativeLoadManager::canUsePendingPreload(const SpeculativeLoad& load, const ResourceRequest& actualRequest)
306 {
307     return requestsHeadersMatch(load.originalRequest(), actualRequest);
308 }
309
310 bool SpeculativeLoadManager::canRetrieve(const Key& storageKey, const WebCore::ResourceRequest& request, const GlobalFrameID& frameID) const
311 {
312     // Check already preloaded entries.
313     if (auto preloadedEntry = m_preloadedEntries.get(storageKey)) {
314         if (!canUsePreloadedEntry(*preloadedEntry, request)) {
315             LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: Could not use preloaded entry to satisfy request for '%s' due to HTTP headers mismatch:", storageKey.identifier().utf8().data());
316             logSpeculativeLoadingDiagnosticMessage(frameID, preloadedEntry->wasRevalidated() ? DiagnosticLoggingKeys::wastedSpeculativeWarmupWithRevalidationKey() : DiagnosticLoggingKeys::wastedSpeculativeWarmupWithoutRevalidationKey());
317             return false;
318         }
319
320         LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: Using preloaded entry to satisfy request for '%s':", storageKey.identifier().utf8().data());
321         logSpeculativeLoadingDiagnosticMessage(frameID, preloadedEntry->wasRevalidated() ? DiagnosticLoggingKeys::successfulSpeculativeWarmupWithRevalidationKey() : DiagnosticLoggingKeys::successfulSpeculativeWarmupWithoutRevalidationKey());
322         return true;
323     }
324
325     // Check pending speculative revalidations.
326     auto* pendingPreload = m_pendingPreloads.get(storageKey);
327     if (!pendingPreload) {
328         if (m_notPreloadedEntries.get(storageKey))
329             logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::entryWronglyNotWarmedUpKey());
330         else
331             logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::unknownEntryRequestKey());
332
333         return false;
334     }
335
336     if (!canUsePendingPreload(*pendingPreload, request)) {
337         LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: revalidation already in progress for '%s' but unusable due to HTTP headers mismatch:", storageKey.identifier().utf8().data());
338         logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::wastedSpeculativeWarmupWithRevalidationKey());
339         return false;
340     }
341
342     LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: revalidation already in progress for '%s':", storageKey.identifier().utf8().data());
343
344     return true;
345 }
346
347 void SpeculativeLoadManager::retrieve(const Key& storageKey, RetrieveCompletionHandler&& completionHandler)
348 {
349     if (auto preloadedEntry = m_preloadedEntries.take(storageKey)) {
350         RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), cacheEntry = preloadedEntry->takeCacheEntry()] () mutable {
351             completionHandler(WTFMove(cacheEntry));
352         });
353         return;
354     }
355     ASSERT(m_pendingPreloads.contains(storageKey));
356     // FIXME: This breaks incremental loading when the revalidation is not successful.
357     auto addResult = m_pendingRetrieveRequests.ensure(storageKey, [] {
358         return std::make_unique<Vector<RetrieveCompletionHandler>>();
359     });
360     addResult.iterator->value->append(WTFMove(completionHandler));
361 }
362
363 void SpeculativeLoadManager::registerLoad(const GlobalFrameID& frameID, const ResourceRequest& request, const Key& resourceKey)
364 {
365     ASSERT(RunLoop::isMain());
366     ASSERT(request.url().protocolIsInHTTPFamily());
367
368     if (request.httpMethod() != "GET")
369         return;
370     if (!request.httpHeaderField(HTTPHeaderName::Range).isEmpty())
371         return;
372
373     auto isMainResource = request.requester() == ResourceRequest::Requester::Main;
374     if (isMainResource) {
375         // Mark previous load in this frame as completed if necessary.
376         if (auto* pendingFrameLoad = m_pendingFrameLoads.get(frameID))
377             pendingFrameLoad->markLoadAsCompleted();
378
379         ASSERT(!m_pendingFrameLoads.contains(frameID));
380
381         // Start tracking loads in this frame.
382         auto pendingFrameLoad = PendingFrameLoad::create(m_storage, resourceKey, [this, frameID] {
383             bool wasRemoved = m_pendingFrameLoads.remove(frameID);
384             ASSERT_UNUSED(wasRemoved, wasRemoved);
385         });
386         m_pendingFrameLoads.add(frameID, pendingFrameLoad.copyRef());
387
388         // Retrieve the subresources entry if it exists to start speculative revalidation and to update it.
389         retrieveSubresourcesEntry(resourceKey, [this, frameID, pendingFrameLoad = WTFMove(pendingFrameLoad)](std::unique_ptr<SubresourcesEntry> entry) {
390             if (entry)
391                 startSpeculativeRevalidation(frameID, *entry);
392
393             pendingFrameLoad->setExistingSubresourcesEntry(WTFMove(entry));
394         });
395         return;
396     }
397
398     if (auto* pendingFrameLoad = m_pendingFrameLoads.get(frameID))
399         pendingFrameLoad->registerSubresourceLoad(request, resourceKey);
400 }
401
402 void SpeculativeLoadManager::addPreloadedEntry(std::unique_ptr<Entry> entry, const GlobalFrameID& frameID, std::optional<ResourceRequest>&& revalidationRequest)
403 {
404     ASSERT(entry);
405     ASSERT(!entry->needsValidation());
406     auto key = entry->key();
407     m_preloadedEntries.add(key, std::make_unique<PreloadedEntry>(WTFMove(entry), WTFMove(revalidationRequest), [this, key, frameID] {
408         auto preloadedEntry = m_preloadedEntries.take(key);
409         ASSERT(preloadedEntry);
410         if (preloadedEntry->wasRevalidated())
411             logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::wastedSpeculativeWarmupWithRevalidationKey());
412         else
413             logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::wastedSpeculativeWarmupWithoutRevalidationKey());
414     }));
415 }
416
417 void SpeculativeLoadManager::retrieveEntryFromStorage(const SubresourceInfo& info, RetrieveCompletionHandler&& completionHandler)
418 {
419     m_storage.retrieve(info.key(), static_cast<unsigned>(info.priority()), [completionHandler = WTFMove(completionHandler)](auto record, auto timings) {
420         if (!record) {
421             completionHandler(nullptr);
422             return false;
423         }
424         auto entry = Entry::decodeStorageRecord(*record);
425         if (!entry) {
426             completionHandler(nullptr);
427             return false;
428         }
429
430         auto& response = entry->response();
431         if (responseNeedsRevalidation(response, entry->timeStamp())) {
432             // Do not use cached redirects that have expired.
433             if (entry->redirectRequest()) {
434                 completionHandler(nullptr);
435                 return true;
436             }
437             entry->setNeedsValidation(true);
438         }
439
440         completionHandler(WTFMove(entry));
441         return true;
442     });
443 }
444
445 bool SpeculativeLoadManager::satisfyPendingRequests(const Key& key, Entry* entry)
446 {
447     auto completionHandlers = m_pendingRetrieveRequests.take(key);
448     if (!completionHandlers)
449         return false;
450
451     for (auto& completionHandler : *completionHandlers)
452         completionHandler(entry ? std::make_unique<Entry>(*entry) : nullptr);
453
454     return true;
455 }
456
457 void SpeculativeLoadManager::revalidateSubresource(const SubresourceInfo& subresourceInfo, std::unique_ptr<Entry> entry, const GlobalFrameID& frameID)
458 {
459     ASSERT(!entry || entry->needsValidation());
460
461     auto& key = subresourceInfo.key();
462
463     // Range is not supported.
464     if (!key.range().isEmpty())
465         return;
466
467     ResourceRequest revalidationRequest = constructRevalidationRequest(key, subresourceInfo, entry.get());
468
469     LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Speculatively revalidating '%s':", key.identifier().utf8().data());
470
471     auto revalidator = std::make_unique<SpeculativeLoad>(m_cache, frameID, revalidationRequest, WTFMove(entry), [this, key, revalidationRequest, frameID](std::unique_ptr<Entry> revalidatedEntry) {
472         ASSERT(!revalidatedEntry || !revalidatedEntry->needsValidation());
473         ASSERT(!revalidatedEntry || revalidatedEntry->key() == key);
474
475         auto protectRevalidator = m_pendingPreloads.take(key);
476         LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Speculative revalidation completed for '%s':", key.identifier().utf8().data());
477
478         if (satisfyPendingRequests(key, revalidatedEntry.get())) {
479             if (revalidatedEntry)
480                 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::successfulSpeculativeWarmupWithRevalidationKey());
481             return;
482         }
483
484         if (revalidatedEntry)
485             addPreloadedEntry(WTFMove(revalidatedEntry), frameID, revalidationRequest);
486     });
487     m_pendingPreloads.add(key, WTFMove(revalidator));
488 }
489     
490 static bool canRevalidate(const SubresourceInfo& subresourceInfo, const Entry* entry)
491 {
492     ASSERT(!subresourceInfo.isTransient());
493     ASSERT(!entry || entry->needsValidation());
494     
495     if (entry && entry->response().hasCacheValidatorFields())
496         return true;
497     
498     auto seenAge = subresourceInfo.lastSeen() - subresourceInfo.firstSeen();
499     if (seenAge == 0_ms) {
500         LOG(NetworkCacheSpeculativePreloading, "Speculative load: Seen only once");
501         return false;
502     }
503     
504     auto now = WallTime::now();
505     auto firstSeenAge = now - subresourceInfo.firstSeen();
506     auto lastSeenAge = now - subresourceInfo.lastSeen();
507     // Sanity check.
508     if (seenAge <= 0_ms || firstSeenAge <= 0_ms || lastSeenAge <= 0_ms)
509         return false;
510     
511     // Load full resources speculatively if they seem to stay the same.
512     const auto minimumAgeRatioToLoad = 2. / 3;
513     const auto recentMinimumAgeRatioToLoad = 1. / 3;
514     const auto recentThreshold = 5_min;
515     
516     auto ageRatio = seenAge / firstSeenAge;
517     auto minimumAgeRatio = lastSeenAge > recentThreshold ? minimumAgeRatioToLoad : recentMinimumAgeRatioToLoad;
518     
519     LOG(NetworkCacheSpeculativePreloading, "Speculative load: ok=%d ageRatio=%f entry=%d", ageRatio > minimumAgeRatio, ageRatio, !!entry);
520     
521     if (ageRatio > minimumAgeRatio)
522         return true;
523     
524     return false;
525 }
526
527 void SpeculativeLoadManager::preloadEntry(const Key& key, const SubresourceInfo& subresourceInfo, const GlobalFrameID& frameID)
528 {
529     if (m_pendingPreloads.contains(key))
530         return;
531     m_pendingPreloads.add(key, nullptr);
532     
533     retrieveEntryFromStorage(subresourceInfo, [this, key, subresourceInfo, frameID](std::unique_ptr<Entry> entry) {
534         ASSERT(!m_pendingPreloads.get(key));
535         bool removed = m_pendingPreloads.remove(key);
536         ASSERT_UNUSED(removed, removed);
537
538         if (satisfyPendingRequests(key, entry.get())) {
539             if (entry)
540                 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::successfulSpeculativeWarmupWithoutRevalidationKey());
541             return;
542         }
543         
544         if (!entry || entry->needsValidation()) {
545             if (canRevalidate(subresourceInfo, entry.get()))
546                 revalidateSubresource(subresourceInfo, WTFMove(entry), frameID);
547             return;
548         }
549         
550         addPreloadedEntry(WTFMove(entry), frameID);
551     });
552 }
553
554 void SpeculativeLoadManager::startSpeculativeRevalidation(const GlobalFrameID& frameID, SubresourcesEntry& entry)
555 {
556     for (auto& subresourceInfo : entry.subresources()) {
557         auto& key = subresourceInfo.key();
558         if (!subresourceInfo.isTransient())
559             preloadEntry(key, subresourceInfo, frameID);
560         else {
561             LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Not preloading '%s' because it is marked as transient", key.identifier().utf8().data());
562             m_notPreloadedEntries.add(key, std::make_unique<ExpiringEntry>([this, key, frameID] {
563                 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::entryRightlyNotWarmedUpKey());
564                 m_notPreloadedEntries.remove(key);
565             }));
566         }
567     }
568 }
569
570 void SpeculativeLoadManager::retrieveSubresourcesEntry(const Key& storageKey, WTF::Function<void (std::unique_ptr<SubresourcesEntry>)>&& completionHandler)
571 {
572     ASSERT(storageKey.type() == "Resource");
573     auto subresourcesStorageKey = makeSubresourcesKey(storageKey, m_storage.salt());
574     m_storage.retrieve(subresourcesStorageKey, static_cast<unsigned>(ResourceLoadPriority::Medium), [completionHandler = WTFMove(completionHandler)](auto record, auto timings) {
575         if (!record) {
576             completionHandler(nullptr);
577             return false;
578         }
579
580         auto subresourcesEntry = SubresourcesEntry::decodeStorageRecord(*record);
581         if (!subresourcesEntry) {
582             completionHandler(nullptr);
583             return false;
584         }
585
586         completionHandler(WTFMove(subresourcesEntry));
587         return true;
588     });
589 }
590
591 } // namespace NetworkCache
592
593 } // namespace WebKit
594
595 #endif // ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)