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