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