blob: a4359f3b017c441731ac89eb877fee2f2c57b18b [file] [log] [blame]
cdumez@apple.comee20abe2015-11-06 19:29:24 +00001/*
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)
cdumez@apple.combc42b742015-11-10 19:20:33 +000029#include "NetworkCacheSpeculativeLoadManager.h"
cdumez@apple.comee20abe2015-11-06 19:29:24 +000030
31#include "Logging.h"
cdumez@apple.com5f094142015-11-11 23:01:30 +000032#include "NetworkCacheEntry.h"
33#include "NetworkCacheSpeculativeLoad.h"
cdumez@apple.comee20abe2015-11-06 19:29:24 +000034#include "NetworkCacheSubresourcesEntry.h"
cdumez@apple.com8c987732015-12-08 17:39:46 +000035#include "NetworkProcess.h"
36#include <WebCore/DiagnosticLoggingKeys.h>
cdumez@apple.comee20abe2015-11-06 19:29:24 +000037#include <WebCore/HysteresisActivity.h>
cdumez@apple.com8c987732015-12-08 17:39:46 +000038#include <wtf/HashCountedSet.h>
cdumez@apple.comee20abe2015-11-06 19:29:24 +000039#include <wtf/NeverDestroyed.h>
cdumez@apple.com351ca6a2015-11-30 19:06:15 +000040#include <wtf/RefCounted.h>
cdumez@apple.comee20abe2015-11-06 19:29:24 +000041#include <wtf/RunLoop.h>
42
43namespace WebKit {
44
45namespace NetworkCache {
46
47using namespace WebCore;
48
beidson@apple.comc9860ae2016-05-22 20:09:57 +000049static const auto preloadedEntryLifetime = 10s;
cdumez@apple.com5f094142015-11-11 23:01:30 +000050
cdumez@apple.com8c987732015-12-08 17:39:46 +000051#if !LOG_DISABLED
52static HashCountedSet<String>& allSpeculativeLoadingDiagnosticMessages()
53{
54 static NeverDestroyed<HashCountedSet<String>> messages;
55 return messages;
56}
57
58static void printSpeculativeLoadingDiagnosticMessageCounts()
59{
60 LOG(NetworkCacheSpeculativePreloading, "-- Speculative loading statistics --");
61 for (auto& pair : allSpeculativeLoadingDiagnosticMessages())
62 LOG(NetworkCacheSpeculativePreloading, "%s: %u", pair.key.utf8().data(), pair.value);
63}
64#endif
65
66static void logSpeculativeLoadingDiagnosticMessage(const GlobalFrameID& frameID, const String& message)
67{
68#if !LOG_DISABLED
69 if (WebKit2LogNetworkCacheSpeculativePreloading.state == WTFLogChannelOn)
70 allSpeculativeLoadingDiagnosticMessages().add(message);
71#endif
72 NetworkProcess::singleton().logDiagnosticMessage(frameID.first, WebCore::DiagnosticLoggingKeys::networkCacheKey(), message, WebCore::ShouldSample::Yes);
73}
74
cdumez@apple.comee20abe2015-11-06 19:29:24 +000075static const AtomicString& subresourcesType()
76{
77 ASSERT(RunLoop::isMain());
cdumez@apple.com271388c2016-06-16 23:39:29 +000078 static NeverDestroyed<const AtomicString> resource("SubResources", AtomicString::ConstructFromLiteral);
cdumez@apple.comee20abe2015-11-06 19:29:24 +000079 return resource;
80}
81
antti@apple.com91466fb2016-11-20 00:38:09 +000082static inline Key makeSubresourcesKey(const Key& resourceKey, const Salt& salt)
cdumez@apple.comee20abe2015-11-06 19:29:24 +000083{
antti@apple.com91466fb2016-11-20 00:38:09 +000084 return Key(resourceKey.partition(), subresourcesType(), resourceKey.range(), resourceKey.identifier(), salt);
cdumez@apple.comee20abe2015-11-06 19:29:24 +000085}
86
cdumez@apple.com8f1cbe02016-05-25 10:59:06 +000087static inline ResourceRequest constructRevalidationRequest(const Entry& entry, const SubresourceInfo& subResourceInfo)
cdumez@apple.com5f094142015-11-11 23:01:30 +000088{
cdumez@apple.com8f1cbe02016-05-25 10:59:06 +000089 ResourceRequest revalidationRequest(entry.key().identifier());
cdumez@apple.com271388c2016-06-16 23:39:29 +000090 revalidationRequest.setHTTPHeaderFields(subResourceInfo.requestHeaders());
91 revalidationRequest.setFirstPartyForCookies(subResourceInfo.firstPartyForCookies());
cdumez@apple.com16dcb2d2016-03-09 21:22:36 +000092#if ENABLE(CACHE_PARTITIONING)
antti@apple.com91466fb2016-11-20 00:38:09 +000093 if (!entry.key().partition().isEmpty())
cdumez@apple.com8f1cbe02016-05-25 10:59:06 +000094 revalidationRequest.setCachePartition(entry.key().partition());
cdumez@apple.com16dcb2d2016-03-09 21:22:36 +000095#endif
96 ASSERT_WITH_MESSAGE(entry.key().range().isEmpty(), "range is not supported");
cdumez@apple.com5f094142015-11-11 23:01:30 +000097
cdumez@apple.com8f1cbe02016-05-25 10:59:06 +000098 revalidationRequest.makeUnconditional();
cdumez@apple.com5f094142015-11-11 23:01:30 +000099 String eTag = entry.response().httpHeaderField(HTTPHeaderName::ETag);
100 if (!eTag.isEmpty())
cdumez@apple.com8f1cbe02016-05-25 10:59:06 +0000101 revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag);
cdumez@apple.com5f094142015-11-11 23:01:30 +0000102
103 String lastModified = entry.response().httpHeaderField(HTTPHeaderName::LastModified);
104 if (!lastModified.isEmpty())
cdumez@apple.com8f1cbe02016-05-25 10:59:06 +0000105 revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified);
cdumez@apple.com5f094142015-11-11 23:01:30 +0000106
107 return revalidationRequest;
108}
109
110static bool responseNeedsRevalidation(const ResourceResponse& response, std::chrono::system_clock::time_point timestamp)
111{
112 if (response.cacheControlContainsNoCache())
113 return true;
114
115 auto age = computeCurrentAge(response, timestamp);
116 auto lifetime = computeFreshnessLifetimeForHTTPFamily(response, timestamp);
beidson@apple.comc9860ae2016-05-22 20:09:57 +0000117 return age - lifetime > 0ms;
cdumez@apple.com5f094142015-11-11 23:01:30 +0000118}
119
cdumez@apple.com8c987732015-12-08 17:39:46 +0000120class SpeculativeLoadManager::ExpiringEntry {
cdumez@apple.com5f094142015-11-11 23:01:30 +0000121 WTF_MAKE_FAST_ALLOCATED;
122public:
cdumez@apple.com8c987732015-12-08 17:39:46 +0000123 explicit ExpiringEntry(std::function<void()>&& expirationHandler)
aestes@apple.com13aae082016-01-02 08:03:08 +0000124 : m_lifetimeTimer(WTFMove(expirationHandler))
cdumez@apple.com5f094142015-11-11 23:01:30 +0000125 {
126 m_lifetimeTimer.startOneShot(preloadedEntryLifetime);
127 }
128
cdumez@apple.com8c987732015-12-08 17:39:46 +0000129private:
130 Timer m_lifetimeTimer;
131};
132
133class SpeculativeLoadManager::PreloadedEntry : private ExpiringEntry {
134 WTF_MAKE_FAST_ALLOCATED;
135public:
utatane.tea@gmail.com43926962016-11-27 06:08:16 +0000136 PreloadedEntry(std::unique_ptr<Entry> entry, std::optional<ResourceRequest>&& speculativeValidationRequest, std::function<void()>&& lifetimeReachedHandler)
aestes@apple.com13aae082016-01-02 08:03:08 +0000137 : ExpiringEntry(WTFMove(lifetimeReachedHandler))
138 , m_entry(WTFMove(entry))
cdumez@apple.com498265c2016-03-28 16:04:33 +0000139 , m_speculativeValidationRequest(WTFMove(speculativeValidationRequest))
cdumez@apple.com8c987732015-12-08 17:39:46 +0000140 { }
141
cdumez@apple.com5f094142015-11-11 23:01:30 +0000142 std::unique_ptr<Entry> takeCacheEntry()
143 {
144 ASSERT(m_entry);
aestes@apple.com13aae082016-01-02 08:03:08 +0000145 return WTFMove(m_entry);
cdumez@apple.com5f094142015-11-11 23:01:30 +0000146 }
147
utatane.tea@gmail.com43926962016-11-27 06:08:16 +0000148 const std::optional<ResourceRequest>& revalidationRequest() const { return m_speculativeValidationRequest; }
cdumez@apple.com498265c2016-03-28 16:04:33 +0000149 bool wasRevalidated() const { return !!m_speculativeValidationRequest; }
cdumez@apple.com5f094142015-11-11 23:01:30 +0000150
cdumez@apple.com8c987732015-12-08 17:39:46 +0000151private:
cdumez@apple.com5f094142015-11-11 23:01:30 +0000152 std::unique_ptr<Entry> m_entry;
utatane.tea@gmail.com43926962016-11-27 06:08:16 +0000153 std::optional<ResourceRequest> m_speculativeValidationRequest;
cdumez@apple.com5f094142015-11-11 23:01:30 +0000154};
155
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000156class SpeculativeLoadManager::PendingFrameLoad : public RefCounted<PendingFrameLoad> {
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000157public:
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000158 static Ref<PendingFrameLoad> create(Storage& storage, const Key& mainResourceKey, std::function<void()>&& loadCompletionHandler)
159 {
aestes@apple.com13aae082016-01-02 08:03:08 +0000160 return adoptRef(*new PendingFrameLoad(storage, mainResourceKey, WTFMove(loadCompletionHandler)));
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000161 }
162
163 ~PendingFrameLoad()
164 {
165 ASSERT(m_didFinishLoad);
166 ASSERT(m_didRetrieveExistingEntry);
167 }
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000168
cdumez@apple.comafe93712016-03-10 16:57:11 +0000169 void registerSubresourceLoad(const ResourceRequest& request, const Key& subresourceKey)
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000170 {
171 ASSERT(RunLoop::isMain());
cdumez@apple.comafe93712016-03-10 16:57:11 +0000172 m_subresourceLoads.append(std::make_unique<SubresourceLoad>(request, subresourceKey));
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000173 m_loadHysteresisActivity.impulse();
174 }
175
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000176 void markLoadAsCompleted()
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000177 {
178 ASSERT(RunLoop::isMain());
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000179 if (m_didFinishLoad)
180 return;
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000181
cdumez@apple.com8c987732015-12-08 17:39:46 +0000182#if !LOG_DISABLED
183 printSpeculativeLoadingDiagnosticMessageCounts();
184#endif
185
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000186 m_didFinishLoad = true;
187 saveToDiskIfReady();
188 m_loadCompletionHandler();
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000189 }
190
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000191 void setExistingSubresourcesEntry(std::unique_ptr<SubresourcesEntry> entry)
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000192 {
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000193 ASSERT(!m_existingEntry);
194 ASSERT(!m_didRetrieveExistingEntry);
195
aestes@apple.com13aae082016-01-02 08:03:08 +0000196 m_existingEntry = WTFMove(entry);
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000197 m_didRetrieveExistingEntry = true;
198 saveToDiskIfReady();
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000199 }
200
201private:
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000202 PendingFrameLoad(Storage& storage, const Key& mainResourceKey, std::function<void()>&& loadCompletionHandler)
203 : m_storage(storage)
204 , m_mainResourceKey(mainResourceKey)
aestes@apple.com13aae082016-01-02 08:03:08 +0000205 , m_loadCompletionHandler(WTFMove(loadCompletionHandler))
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000206 , m_loadHysteresisActivity([this](HysteresisState state) { if (state == HysteresisState::Stopped) markLoadAsCompleted(); })
cdumez@apple.come2f39582016-10-17 23:27:36 +0000207 {
208 m_loadHysteresisActivity.impulse();
209 }
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000210
211 void saveToDiskIfReady()
212 {
213 if (!m_didFinishLoad || !m_didRetrieveExistingEntry)
214 return;
215
cdumez@apple.comafe93712016-03-10 16:57:11 +0000216 if (m_subresourceLoads.isEmpty())
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000217 return;
218
219#if !LOG_DISABLED
220 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Saving to disk list of subresources for '%s':", m_mainResourceKey.identifier().utf8().data());
cdumez@apple.comafe93712016-03-10 16:57:11 +0000221 for (auto& subresourceLoad : m_subresourceLoads)
222 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) * Subresource: '%s'.", subresourceLoad->key.identifier().utf8().data());
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000223#endif
224
225 if (m_existingEntry) {
cdumez@apple.comafe93712016-03-10 16:57:11 +0000226 m_existingEntry->updateSubresourceLoads(m_subresourceLoads);
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000227 m_storage.store(m_existingEntry->encodeAsStorageRecord(), [](const Data&) { });
228 } else {
antti@apple.com91466fb2016-11-20 00:38:09 +0000229 SubresourcesEntry entry(makeSubresourcesKey(m_mainResourceKey, m_storage.salt()), m_subresourceLoads);
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000230 m_storage.store(entry.encodeAsStorageRecord(), [](const Data&) { });
231 }
232 }
233
234 Storage& m_storage;
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000235 Key m_mainResourceKey;
cdumez@apple.comafe93712016-03-10 16:57:11 +0000236 Vector<std::unique_ptr<SubresourceLoad>> m_subresourceLoads;
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000237 std::function<void()> m_loadCompletionHandler;
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000238 HysteresisActivity m_loadHysteresisActivity;
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000239 std::unique_ptr<SubresourcesEntry> m_existingEntry;
240 bool m_didFinishLoad { false };
241 bool m_didRetrieveExistingEntry { false };
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000242};
243
cdumez@apple.combc42b742015-11-10 19:20:33 +0000244SpeculativeLoadManager::SpeculativeLoadManager(Storage& storage)
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000245 : m_storage(storage)
246{
247}
248
cdumez@apple.combc42b742015-11-10 19:20:33 +0000249SpeculativeLoadManager::~SpeculativeLoadManager()
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000250{
251}
252
cdumez@apple.com498265c2016-03-28 16:04:33 +0000253#if !LOG_DISABLED
254
255static void dumpHTTPHeadersDiff(const HTTPHeaderMap& headersA, const HTTPHeaderMap& headersB)
256{
257 auto aEnd = headersA.end();
258 for (auto it = headersA.begin(); it != aEnd; ++it) {
259 String valueB = headersB.get(it->key);
260 if (valueB.isNull())
261 LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header is only in first request (value: %s)", it->key.utf8().data(), it->value.utf8().data());
262 else if (it->value != valueB)
263 LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header differs in both requests: %s != %s", it->key.utf8().data(), it->value.utf8().data(), valueB.utf8().data());
264 }
265 auto bEnd = headersB.end();
266 for (auto it = headersB.begin(); it != bEnd; ++it) {
267 if (!headersA.contains(it->key))
268 LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header is only in second request (value: %s)", it->key.utf8().data(), it->value.utf8().data());
269 }
270}
271
272#endif
273
cdumez@apple.com43ab71b2016-06-02 16:40:05 +0000274static bool requestsHeadersMatch(const ResourceRequest& speculativeValidationRequest, const ResourceRequest& actualRequest)
cdumez@apple.com498265c2016-03-28 16:04:33 +0000275{
cdumez@apple.com43ab71b2016-06-02 16:40:05 +0000276 ASSERT(!actualRequest.isConditional());
277 ResourceRequest speculativeRequest = speculativeValidationRequest;
278 speculativeRequest.makeUnconditional();
cdumez@apple.com498265c2016-03-28 16:04:33 +0000279
cdumez@apple.com43ab71b2016-06-02 16:40:05 +0000280 if (speculativeRequest.httpHeaderFields() != actualRequest.httpHeaderFields()) {
cdumez@apple.com498265c2016-03-28 16:04:33 +0000281 LOG(NetworkCacheSpeculativePreloading, "Cannot reuse speculatively validated entry because HTTP headers used for validation do not match");
282#if !LOG_DISABLED
cdumez@apple.com43ab71b2016-06-02 16:40:05 +0000283 dumpHTTPHeadersDiff(speculativeRequest.httpHeaderFields(), actualRequest.httpHeaderFields());
cdumez@apple.com498265c2016-03-28 16:04:33 +0000284#endif
285 return false;
286 }
287 return true;
288}
289
290bool SpeculativeLoadManager::canUsePreloadedEntry(const PreloadedEntry& entry, const ResourceRequest& actualRequest)
291{
292 if (!entry.wasRevalidated())
293 return true;
294
295 ASSERT(entry.revalidationRequest());
296 return requestsHeadersMatch(*entry.revalidationRequest(), actualRequest);
297}
298
299bool SpeculativeLoadManager::canUsePendingPreload(const SpeculativeLoad& load, const ResourceRequest& actualRequest)
300{
301 return requestsHeadersMatch(load.originalRequest(), actualRequest);
302}
303
cdumez@apple.com3de719a2016-05-25 19:17:57 +0000304bool SpeculativeLoadManager::retrieve(const GlobalFrameID& frameID, const Key& storageKey, const WebCore::ResourceRequest& request, RetrieveCompletionHandler&& completionHandler)
cdumez@apple.com5f094142015-11-11 23:01:30 +0000305{
306 // Check already preloaded entries.
307 if (auto preloadedEntry = m_preloadedEntries.take(storageKey)) {
cdumez@apple.com498265c2016-03-28 16:04:33 +0000308 if (!canUsePreloadedEntry(*preloadedEntry, request)) {
309 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: Could not use preloaded entry to satisfy request for '%s' due to HTTP headers mismatch:", storageKey.identifier().utf8().data());
310 logSpeculativeLoadingDiagnosticMessage(frameID, preloadedEntry->wasRevalidated() ? DiagnosticLoggingKeys::wastedSpeculativeWarmupWithRevalidationKey() : DiagnosticLoggingKeys::wastedSpeculativeWarmupWithoutRevalidationKey());
311 return false;
312 }
313
cdumez@apple.com5f094142015-11-11 23:01:30 +0000314 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: Using preloaded entry to satisfy request for '%s':", storageKey.identifier().utf8().data());
cdumez@apple.com498265c2016-03-28 16:04:33 +0000315 logSpeculativeLoadingDiagnosticMessage(frameID, preloadedEntry->wasRevalidated() ? DiagnosticLoggingKeys::successfulSpeculativeWarmupWithRevalidationKey() : DiagnosticLoggingKeys::successfulSpeculativeWarmupWithoutRevalidationKey());
cdumez@apple.com8c987732015-12-08 17:39:46 +0000316
cdumez@apple.com5f094142015-11-11 23:01:30 +0000317 completionHandler(preloadedEntry->takeCacheEntry());
318 return true;
319 }
320
321 // Check pending speculative revalidations.
cdumez@apple.com498265c2016-03-28 16:04:33 +0000322 auto* pendingPreload = m_pendingPreloads.get(storageKey);
323 if (!pendingPreload) {
cdumez@apple.com8c987732015-12-08 17:39:46 +0000324 if (m_notPreloadedEntries.remove(storageKey))
325 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::entryWronglyNotWarmedUpKey());
326 else
327 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::unknownEntryRequestKey());
328
cdumez@apple.com5f094142015-11-11 23:01:30 +0000329 return false;
cdumez@apple.com8c987732015-12-08 17:39:46 +0000330 }
cdumez@apple.com5f094142015-11-11 23:01:30 +0000331
cdumez@apple.com498265c2016-03-28 16:04:33 +0000332 if (!canUsePendingPreload(*pendingPreload, request)) {
333 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: revalidation already in progress for '%s' but unusable due to HTTP headers mismatch:", storageKey.identifier().utf8().data());
334 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::wastedSpeculativeWarmupWithRevalidationKey());
335 return false;
336 }
337
cdumez@apple.com5f094142015-11-11 23:01:30 +0000338 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: revalidation already in progress for '%s':", storageKey.identifier().utf8().data());
339
340 // FIXME: This breaks incremental loading when the revalidation is not successful.
cdumez@apple.com3de719a2016-05-25 19:17:57 +0000341 auto addResult = m_pendingRetrieveRequests.ensure(storageKey, [] { return std::make_unique<Vector<RetrieveCompletionHandler>>(); });
342 addResult.iterator->value->append(WTFMove(completionHandler));
cdumez@apple.com5f094142015-11-11 23:01:30 +0000343 return true;
344}
345
346void SpeculativeLoadManager::registerLoad(const GlobalFrameID& frameID, const ResourceRequest& request, const Key& resourceKey)
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000347{
348 ASSERT(RunLoop::isMain());
cdumez@apple.com297a5522016-04-30 03:36:48 +0000349 ASSERT(request.url().protocolIsInHTTPFamily());
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000350
cdumez@apple.com297a5522016-04-30 03:36:48 +0000351 if (request.httpMethod() != "GET")
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000352 return;
353
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000354 auto isMainResource = request.requester() == ResourceRequest::Requester::Main;
355 if (isMainResource) {
356 // Mark previous load in this frame as completed if necessary.
cdumez@apple.com5f094142015-11-11 23:01:30 +0000357 if (auto* pendingFrameLoad = m_pendingFrameLoads.get(frameID))
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000358 pendingFrameLoad->markLoadAsCompleted();
359
360 ASSERT(!m_pendingFrameLoads.contains(frameID));
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000361
362 // Start tracking loads in this frame.
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000363 RefPtr<PendingFrameLoad> pendingFrameLoad = PendingFrameLoad::create(m_storage, resourceKey, [this, frameID] {
364 bool wasRemoved = m_pendingFrameLoads.remove(frameID);
365 ASSERT_UNUSED(wasRemoved, wasRemoved);
366 });
367 m_pendingFrameLoads.add(frameID, pendingFrameLoad);
368
369 // Retrieve the subresources entry if it exists to start speculative revalidation and to update it.
370 retrieveSubresourcesEntry(resourceKey, [this, frameID, pendingFrameLoad](std::unique_ptr<SubresourcesEntry> entry) {
371 if (entry)
372 startSpeculativeRevalidation(frameID, *entry);
373
aestes@apple.com13aae082016-01-02 08:03:08 +0000374 pendingFrameLoad->setExistingSubresourcesEntry(WTFMove(entry));
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000375 });
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000376 return;
377 }
378
cdumez@apple.com5f094142015-11-11 23:01:30 +0000379 if (auto* pendingFrameLoad = m_pendingFrameLoads.get(frameID))
cdumez@apple.comafe93712016-03-10 16:57:11 +0000380 pendingFrameLoad->registerSubresourceLoad(request, resourceKey);
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000381}
382
utatane.tea@gmail.com43926962016-11-27 06:08:16 +0000383void SpeculativeLoadManager::addPreloadedEntry(std::unique_ptr<Entry> entry, const GlobalFrameID& frameID, std::optional<ResourceRequest>&& revalidationRequest)
cdumez@apple.com5f094142015-11-11 23:01:30 +0000384{
385 ASSERT(entry);
386 ASSERT(!entry->needsValidation());
387 auto key = entry->key();
cdumez@apple.com498265c2016-03-28 16:04:33 +0000388 m_preloadedEntries.add(key, std::make_unique<PreloadedEntry>(WTFMove(entry), WTFMove(revalidationRequest), [this, key, frameID] {
cdumez@apple.com8c987732015-12-08 17:39:46 +0000389 auto preloadedEntry = m_preloadedEntries.take(key);
390 ASSERT(preloadedEntry);
391 if (preloadedEntry->wasRevalidated())
392 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::wastedSpeculativeWarmupWithRevalidationKey());
393 else
394 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::wastedSpeculativeWarmupWithoutRevalidationKey());
cdumez@apple.com5f094142015-11-11 23:01:30 +0000395 }));
396}
397
cdumez@apple.com3de719a2016-05-25 19:17:57 +0000398void SpeculativeLoadManager::retrieveEntryFromStorage(const Key& key, RetrieveCompletionHandler&& completionHandler)
cdumez@apple.com5f094142015-11-11 23:01:30 +0000399{
cdumez@apple.com3de719a2016-05-25 19:17:57 +0000400 m_storage.retrieve(key, static_cast<unsigned>(ResourceLoadPriority::Medium), [completionHandler = WTFMove(completionHandler)](auto record) {
cdumez@apple.com5f094142015-11-11 23:01:30 +0000401 if (!record) {
402 completionHandler(nullptr);
403 return false;
404 }
405 auto entry = Entry::decodeStorageRecord(*record);
406 if (!entry) {
407 completionHandler(nullptr);
408 return false;
409 }
410
411 auto& response = entry->response();
412 if (!response.hasCacheValidatorFields()) {
413 completionHandler(nullptr);
414 return true;
415 }
416
cdumez@apple.comb4b78a52016-04-14 00:14:05 +0000417 if (responseNeedsRevalidation(response, entry->timeStamp())) {
418 // Do not use cached redirects that have expired.
419 if (entry->redirectRequest()) {
420 completionHandler(nullptr);
421 return true;
422 }
cdumez@apple.com16dcb2d2016-03-09 21:22:36 +0000423 entry->setNeedsValidation(true);
cdumez@apple.comb4b78a52016-04-14 00:14:05 +0000424 }
cdumez@apple.com5f094142015-11-11 23:01:30 +0000425
aestes@apple.com13aae082016-01-02 08:03:08 +0000426 completionHandler(WTFMove(entry));
cdumez@apple.com5f094142015-11-11 23:01:30 +0000427 return true;
428 });
429}
430
431bool SpeculativeLoadManager::satisfyPendingRequests(const Key& key, Entry* entry)
432{
433 auto completionHandlers = m_pendingRetrieveRequests.take(key);
434 if (!completionHandlers)
435 return false;
436
437 for (auto& completionHandler : *completionHandlers)
438 completionHandler(entry ? std::make_unique<Entry>(*entry) : nullptr);
439
440 return true;
441}
442
cdumez@apple.comafe93712016-03-10 16:57:11 +0000443void SpeculativeLoadManager::revalidateEntry(std::unique_ptr<Entry> entry, const SubresourceInfo& subresourceInfo, const GlobalFrameID& frameID)
cdumez@apple.com5f094142015-11-11 23:01:30 +0000444{
445 ASSERT(entry);
446 ASSERT(entry->needsValidation());
447
448 auto key = entry->key();
cdumez@apple.com16dcb2d2016-03-09 21:22:36 +0000449
450 // Range is not supported.
451 if (!key.range().isEmpty())
452 return;
453
cdumez@apple.com8f1cbe02016-05-25 10:59:06 +0000454 ResourceRequest revalidationRequest = constructRevalidationRequest(*entry, subresourceInfo);
cdumez@apple.comafe93712016-03-10 16:57:11 +0000455
cdumez@apple.com5f094142015-11-11 23:01:30 +0000456 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Speculatively revalidating '%s':", key.identifier().utf8().data());
cdumez@apple.com498265c2016-03-28 16:04:33 +0000457
cdumez@apple.com8f1cbe02016-05-25 10:59:06 +0000458 auto revalidator = std::make_unique<SpeculativeLoad>(frameID, revalidationRequest, WTFMove(entry), [this, key, revalidationRequest, frameID](std::unique_ptr<Entry> revalidatedEntry) {
cdumez@apple.com5f094142015-11-11 23:01:30 +0000459 ASSERT(!revalidatedEntry || !revalidatedEntry->needsValidation());
cdumez@apple.com16dcb2d2016-03-09 21:22:36 +0000460 ASSERT(!revalidatedEntry || revalidatedEntry->key() == key);
461
cdumez@apple.com5f094142015-11-11 23:01:30 +0000462 auto protectRevalidator = m_pendingPreloads.take(key);
463 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Speculative revalidation completed for '%s':", key.identifier().utf8().data());
464
cdumez@apple.com8c987732015-12-08 17:39:46 +0000465 if (satisfyPendingRequests(key, revalidatedEntry.get())) {
466 if (revalidatedEntry)
467 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::successfulSpeculativeWarmupWithRevalidationKey());
cdumez@apple.com5f094142015-11-11 23:01:30 +0000468 return;
cdumez@apple.com8c987732015-12-08 17:39:46 +0000469 }
cdumez@apple.com5f094142015-11-11 23:01:30 +0000470
471 if (revalidatedEntry)
cdumez@apple.com8f1cbe02016-05-25 10:59:06 +0000472 addPreloadedEntry(WTFMove(revalidatedEntry), frameID, revalidationRequest);
cdumez@apple.com5f094142015-11-11 23:01:30 +0000473 });
aestes@apple.com13aae082016-01-02 08:03:08 +0000474 m_pendingPreloads.add(key, WTFMove(revalidator));
cdumez@apple.com5f094142015-11-11 23:01:30 +0000475}
476
cdumez@apple.comafe93712016-03-10 16:57:11 +0000477void SpeculativeLoadManager::preloadEntry(const Key& key, const SubresourceInfo& subResourceInfo, const GlobalFrameID& frameID)
cdumez@apple.com5f094142015-11-11 23:01:30 +0000478{
cdumez@apple.comf99b1082016-05-10 17:45:47 +0000479 if (m_pendingPreloads.contains(key))
480 return;
481
cdumez@apple.com5f094142015-11-11 23:01:30 +0000482 m_pendingPreloads.add(key, nullptr);
cdumez@apple.com4da914e2016-05-25 05:18:00 +0000483 retrieveEntryFromStorage(key, [this, key, subResourceInfo, frameID](std::unique_ptr<Entry> entry) {
cdumez@apple.comf99b1082016-05-10 17:45:47 +0000484 ASSERT(!m_pendingPreloads.get(key));
485 bool removed = m_pendingPreloads.remove(key);
486 ASSERT_UNUSED(removed, removed);
cdumez@apple.com5f094142015-11-11 23:01:30 +0000487
cdumez@apple.com8c987732015-12-08 17:39:46 +0000488 if (satisfyPendingRequests(key, entry.get())) {
489 if (entry)
490 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::successfulSpeculativeWarmupWithoutRevalidationKey());
cdumez@apple.com5f094142015-11-11 23:01:30 +0000491 return;
cdumez@apple.com8c987732015-12-08 17:39:46 +0000492 }
cdumez@apple.com5f094142015-11-11 23:01:30 +0000493
494 if (!entry)
495 return;
496
497 if (entry->needsValidation())
cdumez@apple.com4da914e2016-05-25 05:18:00 +0000498 revalidateEntry(WTFMove(entry), subResourceInfo, frameID);
cdumez@apple.com5f094142015-11-11 23:01:30 +0000499 else
cdumez@apple.com498265c2016-03-28 16:04:33 +0000500 addPreloadedEntry(WTFMove(entry), frameID);
cdumez@apple.com5f094142015-11-11 23:01:30 +0000501 });
502}
503
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000504void SpeculativeLoadManager::startSpeculativeRevalidation(const GlobalFrameID& frameID, SubresourcesEntry& entry)
cdumez@apple.com5f094142015-11-11 23:01:30 +0000505{
cdumez@apple.comafe93712016-03-10 16:57:11 +0000506 for (auto& subresourcePair : entry.subresources()) {
cdumez@apple.com498265c2016-03-28 16:04:33 +0000507 auto& key = subresourcePair.key;
508 auto& subresourceInfo = subresourcePair.value;
cdumez@apple.com271388c2016-06-16 23:39:29 +0000509 if (!subresourceInfo.isTransient())
cdumez@apple.comafe93712016-03-10 16:57:11 +0000510 preloadEntry(key, subresourceInfo, frameID);
cdumez@apple.com8c987732015-12-08 17:39:46 +0000511 else {
cdumez@apple.comafe93712016-03-10 16:57:11 +0000512 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Not preloading '%s' because it is marked as transient", key.identifier().utf8().data());
cdumez@apple.com8c987732015-12-08 17:39:46 +0000513 m_notPreloadedEntries.add(key, std::make_unique<ExpiringEntry>([this, key, frameID] {
514 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::entryRightlyNotWarmedUpKey());
515 m_notPreloadedEntries.remove(key);
516 }));
517 }
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000518 }
519}
cdumez@apple.com5f094142015-11-11 23:01:30 +0000520
cdumez@apple.com3de719a2016-05-25 19:17:57 +0000521void SpeculativeLoadManager::retrieveSubresourcesEntry(const Key& storageKey, std::function<void (std::unique_ptr<SubresourcesEntry>)>&& completionHandler)
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000522{
cdumez@apple.com271388c2016-06-16 23:39:29 +0000523 ASSERT(storageKey.type() == "Resource");
antti@apple.com91466fb2016-11-20 00:38:09 +0000524 auto subresourcesStorageKey = makeSubresourcesKey(storageKey, m_storage.salt());
cdumez@apple.com3de719a2016-05-25 19:17:57 +0000525 m_storage.retrieve(subresourcesStorageKey, static_cast<unsigned>(ResourceLoadPriority::Medium), [completionHandler = WTFMove(completionHandler)](auto record) {
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000526 if (!record) {
527 completionHandler(nullptr);
cdumez@apple.com5f094142015-11-11 23:01:30 +0000528 return false;
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000529 }
cdumez@apple.com5f094142015-11-11 23:01:30 +0000530
531 auto subresourcesEntry = SubresourcesEntry::decodeStorageRecord(*record);
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000532 if (!subresourcesEntry) {
533 completionHandler(nullptr);
cdumez@apple.com5f094142015-11-11 23:01:30 +0000534 return false;
cdumez@apple.com351ca6a2015-11-30 19:06:15 +0000535 }
cdumez@apple.com5f094142015-11-11 23:01:30 +0000536
aestes@apple.com13aae082016-01-02 08:03:08 +0000537 completionHandler(WTFMove(subresourcesEntry));
cdumez@apple.com5f094142015-11-11 23:01:30 +0000538 return true;
539 });
540}
541
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000542} // namespace NetworkCache
543
544} // namespace WebKit
545
546#endif // ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)