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
44namespace WebKit {
45
46namespace NetworkCache {
47
48using namespace WebCore;
49
50static const Seconds preloadedEntryLifetime { 10_s };
51
52#if !LOG_DISABLED
53static HashCountedSet<String>& allSpeculativeLoadingDiagnosticMessages()
54{
55 static NeverDestroyed<HashCountedSet<String>> messages;
56 return messages;
57}
58
59static 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
67static void logSpeculativeLoadingDiagnosticMessage(NetworkProcess& networkProcess, const GlobalFrameID& frameID, const String& message)
68{
69#if !LOG_DISABLED
70 if (WebKit2LogNetworkCacheSpeculativePreloading.state == WTFLogChannelState::On)
71 allSpeculativeLoadingDiagnosticMessages().add(message);
72#endif
73 networkProcess.logDiagnosticMessage(frameID.first, WebCore::DiagnosticLoggingKeys::networkCacheKey(), message, WebCore::ShouldSample::Yes);
74}
75
76static const AtomString& subresourcesType()
77{
78 ASSERT(RunLoop::isMain());
79 static NeverDestroyed<const AtomString> resource("SubResources", AtomString::ConstructFromLiteral);
80 return resource;
81}
82
83static inline Key makeSubresourcesKey(const Key& resourceKey, const Salt& salt)
84{
85 return Key(resourceKey.partition(), subresourcesType(), resourceKey.range(), resourceKey.identifier(), salt);
86}
87
88static 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
115static 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
125class SpeculativeLoadManager::ExpiringEntry {
126 WTF_MAKE_FAST_ALLOCATED;
127public:
128 explicit ExpiringEntry(WTF::Function<void()>&& expirationHandler)
129 : m_lifetimeTimer(WTFMove(expirationHandler))
130 {
131 m_lifetimeTimer.startOneShot(preloadedEntryLifetime);
132 }
133
134private:
135 Timer m_lifetimeTimer;
136};
137
138class SpeculativeLoadManager::PreloadedEntry : private ExpiringEntry {
139 WTF_MAKE_FAST_ALLOCATED;
140public:
141 PreloadedEntry(std::unique_ptr<Entry> entry, 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 Optional<ResourceRequest>& revalidationRequest() const { return m_speculativeValidationRequest; }
154 bool wasRevalidated() const { return !!m_speculativeValidationRequest; }
155
156private:
157 std::unique_ptr<Entry> m_entry;
158 Optional<ResourceRequest> m_speculativeValidationRequest;
159};
160
161class SpeculativeLoadManager::PendingFrameLoad : public RefCounted<PendingFrameLoad> {
162public:
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
206private:
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
249SpeculativeLoadManager::SpeculativeLoadManager(Cache& cache, Storage& storage)
250 : m_cache(cache)
251 , m_storage(storage)
252{
253}
254
255SpeculativeLoadManager::~SpeculativeLoadManager()
256{
257}
258
259#if !LOG_DISABLED
260
261static 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
280static 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
296bool 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
305bool SpeculativeLoadManager::canUsePendingPreload(const SpeculativeLoad& load, const ResourceRequest& actualRequest)
306{
307 return requestsHeadersMatch(load.originalRequest(), actualRequest);
308}
309
310bool 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(m_cache.networkProcess(), 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(m_cache.networkProcess(), 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(m_cache.networkProcess(), frameID, DiagnosticLoggingKeys::entryWronglyNotWarmedUpKey());
330 else
331 logSpeculativeLoadingDiagnosticMessage(m_cache.networkProcess(), 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(m_cache.networkProcess(), 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
347void 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
363void 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
402void SpeculativeLoadManager::addPreloadedEntry(std::unique_ptr<Entry> entry, const GlobalFrameID& frameID, 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(m_cache.networkProcess(), frameID, DiagnosticLoggingKeys::wastedSpeculativeWarmupWithRevalidationKey());
412 else
413 logSpeculativeLoadingDiagnosticMessage(m_cache.networkProcess(), frameID, DiagnosticLoggingKeys::wastedSpeculativeWarmupWithoutRevalidationKey());
414 }));
415}
416
417void 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
445bool 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
457void 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(m_cache.networkProcess(), 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
490static 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
527void 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(m_cache.networkProcess(), 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
554void 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(m_cache.networkProcess(), frameID, DiagnosticLoggingKeys::entryRightlyNotWarmedUpKey());
564 m_notPreloadedEntries.remove(key);
565 }));
566 }
567 }
568}
569
570void 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)
596