blob: 0412ba691b1209e20237f52f5fc0b66778188868 [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"
35#include <WebCore/HysteresisActivity.h>
36#include <wtf/NeverDestroyed.h>
37#include <wtf/RunLoop.h>
38
39namespace WebKit {
40
41namespace NetworkCache {
42
43using namespace WebCore;
44
cdumez@apple.com5f094142015-11-11 23:01:30 +000045static const auto preloadedEntryLifetime = 10_s;
46
cdumez@apple.comee20abe2015-11-06 19:29:24 +000047static const AtomicString& subresourcesType()
48{
49 ASSERT(RunLoop::isMain());
50 static NeverDestroyed<const AtomicString> resource("subresources", AtomicString::ConstructFromLiteral);
51 return resource;
52}
53
54static inline Key makeSubresourcesKey(const Key& resourceKey)
55{
56 return Key(resourceKey.partition(), subresourcesType(), resourceKey.range(), resourceKey.identifier());
57}
58
cdumez@apple.com5f094142015-11-11 23:01:30 +000059static inline ResourceRequest constructRevalidationRequest(const Entry& entry)
60{
61 ResourceRequest revalidationRequest(entry.key().identifier());
62
63 String eTag = entry.response().httpHeaderField(HTTPHeaderName::ETag);
64 if (!eTag.isEmpty())
65 revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag);
66
67 String lastModified = entry.response().httpHeaderField(HTTPHeaderName::LastModified);
68 if (!lastModified.isEmpty())
69 revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified);
70
71 return revalidationRequest;
72}
73
74static bool responseNeedsRevalidation(const ResourceResponse& response, std::chrono::system_clock::time_point timestamp)
75{
76 if (response.cacheControlContainsNoCache())
77 return true;
78
79 auto age = computeCurrentAge(response, timestamp);
80 auto lifetime = computeFreshnessLifetimeForHTTPFamily(response, timestamp);
81 return age - lifetime > 0_ms;
82}
83
84class SpeculativeLoadManager::PreloadedEntry {
85 WTF_MAKE_FAST_ALLOCATED;
86public:
87 PreloadedEntry(std::unique_ptr<Entry> entry, std::function<void()>&& lifetimeReachedHandler)
88 : m_entry(WTF::move(entry))
89 , m_lifetimeTimer(*this, &PreloadedEntry::lifetimeTimerFired)
90 , m_lifetimeReachedHandler(WTF::move(lifetimeReachedHandler))
91 {
92 m_lifetimeTimer.startOneShot(preloadedEntryLifetime);
93 }
94
95 std::unique_ptr<Entry> takeCacheEntry()
96 {
97 ASSERT(m_entry);
98 return WTF::move(m_entry);
99 }
100
101private:
102 void lifetimeTimerFired()
103 {
104 m_lifetimeReachedHandler();
105 }
106
107 std::unique_ptr<Entry> m_entry;
108 Timer m_lifetimeTimer;
109 std::function<void()> m_lifetimeReachedHandler;
110};
111
cdumez@apple.combc42b742015-11-10 19:20:33 +0000112class SpeculativeLoadManager::PendingFrameLoad {
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000113 WTF_MAKE_FAST_ALLOCATED;
114public:
cdumez@apple.com5f094142015-11-11 23:01:30 +0000115 PendingFrameLoad(const Key& mainResourceKey, std::function<void()>&& completionHandler)
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000116 : m_mainResourceKey(mainResourceKey)
cdumez@apple.com5f094142015-11-11 23:01:30 +0000117 , m_completionHandler(WTF::move(completionHandler))
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000118 , m_loadHysteresisActivity([this](HysteresisState state) { if (state == HysteresisState::Stopped) m_completionHandler(); })
119 { }
120
121 void registerSubresource(const Key& subresourceKey)
122 {
123 ASSERT(RunLoop::isMain());
124 m_subresourceKeys.add(subresourceKey);
125 m_loadHysteresisActivity.impulse();
126 }
127
128 Optional<Storage::Record> encodeAsSubresourcesRecord()
129 {
130 ASSERT(RunLoop::isMain());
131 if (m_subresourceKeys.isEmpty())
132 return { };
133
134 auto subresourcesStorageKey = makeSubresourcesKey(m_mainResourceKey);
135 Vector<Key> subresourceKeys;
136 copyToVector(m_subresourceKeys, subresourceKeys);
137
138#if !LOG_DISABLED
139 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Saving to disk list of subresources for '%s':", m_mainResourceKey.identifier().utf8().data());
140 for (auto& subresourceKey : subresourceKeys)
141 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) * Subresource: '%s'.", subresourceKey.identifier().utf8().data());
142#endif
143
cdumez@apple.comdbf65022015-11-18 23:21:38 +0000144 return SubresourcesEntry(WTF::move(subresourcesStorageKey), WTF::move(subresourceKeys)).encodeAsStorageRecord();
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000145 }
146
147 void markAsCompleted()
148 {
149 ASSERT(RunLoop::isMain());
150 m_completionHandler();
151 }
152
153private:
154 Key m_mainResourceKey;
155 HashSet<Key> m_subresourceKeys;
156 std::function<void()> m_completionHandler;
157 HysteresisActivity m_loadHysteresisActivity;
158};
159
cdumez@apple.combc42b742015-11-10 19:20:33 +0000160SpeculativeLoadManager::SpeculativeLoadManager(Storage& storage)
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000161 : m_storage(storage)
162{
163}
164
cdumez@apple.combc42b742015-11-10 19:20:33 +0000165SpeculativeLoadManager::~SpeculativeLoadManager()
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000166{
167}
168
cdumez@apple.com5f094142015-11-11 23:01:30 +0000169bool SpeculativeLoadManager::retrieve(const Key& storageKey, const RetrieveCompletionHandler& completionHandler)
170{
171 // Check already preloaded entries.
172 if (auto preloadedEntry = m_preloadedEntries.take(storageKey)) {
173 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: Using preloaded entry to satisfy request for '%s':", storageKey.identifier().utf8().data());
174 completionHandler(preloadedEntry->takeCacheEntry());
175 return true;
176 }
177
178 // Check pending speculative revalidations.
179 if (!m_pendingPreloads.contains(storageKey))
180 return false;
181
182 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: revalidation already in progress for '%s':", storageKey.identifier().utf8().data());
183
184 // FIXME: This breaks incremental loading when the revalidation is not successful.
185 auto addResult = m_pendingRetrieveRequests.add(storageKey, nullptr);
186 if (addResult.isNewEntry)
187 addResult.iterator->value = std::make_unique<Vector<RetrieveCompletionHandler>>();
188 addResult.iterator->value->append(completionHandler);
189 return true;
190}
191
192void SpeculativeLoadManager::registerLoad(const GlobalFrameID& frameID, const ResourceRequest& request, const Key& resourceKey)
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000193{
194 ASSERT(RunLoop::isMain());
195
196 if (!request.url().protocolIsInHTTPFamily() || request.httpMethod() != "GET")
197 return;
198
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000199 auto isMainResource = request.requester() == ResourceRequest::Requester::Main;
200 if (isMainResource) {
201 // Mark previous load in this frame as completed if necessary.
cdumez@apple.com5f094142015-11-11 23:01:30 +0000202 if (auto* pendingFrameLoad = m_pendingFrameLoads.get(frameID))
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000203 pendingFrameLoad->markAsCompleted();
204
205 // Start tracking loads in this frame.
cdumez@apple.com5f094142015-11-11 23:01:30 +0000206 m_pendingFrameLoads.add(frameID, std::make_unique<PendingFrameLoad>(resourceKey, [this, frameID]() {
207 auto frameLoad = m_pendingFrameLoads.take(frameID);
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000208 auto optionalRecord = frameLoad->encodeAsSubresourcesRecord();
209 if (!optionalRecord)
210 return;
211 m_storage.store(optionalRecord.value(), [](const Data&) { });
212 }));
213 return;
214 }
215
cdumez@apple.com5f094142015-11-11 23:01:30 +0000216 if (auto* pendingFrameLoad = m_pendingFrameLoads.get(frameID))
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000217 pendingFrameLoad->registerSubresource(resourceKey);
218}
219
cdumez@apple.com5f094142015-11-11 23:01:30 +0000220void SpeculativeLoadManager::addPreloadedEntry(std::unique_ptr<Entry> entry)
221{
222 ASSERT(entry);
223 ASSERT(!entry->needsValidation());
224 auto key = entry->key();
225 m_preloadedEntries.add(key, std::make_unique<PreloadedEntry>(WTF::move(entry), [this, key]() {
226 m_preloadedEntries.remove(key);
227 }));
228}
229
230void SpeculativeLoadManager::retrieveEntryFromStorage(const Key& key, const RetrieveCompletionHandler& completionHandler)
231{
232 m_storage.retrieve(key, static_cast<unsigned>(ResourceLoadPriority::Medium), [completionHandler](std::unique_ptr<Storage::Record> record) {
233 if (!record) {
234 completionHandler(nullptr);
235 return false;
236 }
237 auto entry = Entry::decodeStorageRecord(*record);
238 if (!entry) {
239 completionHandler(nullptr);
240 return false;
241 }
242
243 auto& response = entry->response();
244 if (!response.hasCacheValidatorFields()) {
245 completionHandler(nullptr);
246 return true;
247 }
248
249 if (responseNeedsRevalidation(response, entry->timeStamp()))
250 entry->setNeedsValidation();
251
252 completionHandler(WTF::move(entry));
253 return true;
254 });
255}
256
257bool SpeculativeLoadManager::satisfyPendingRequests(const Key& key, Entry* entry)
258{
259 auto completionHandlers = m_pendingRetrieveRequests.take(key);
260 if (!completionHandlers)
261 return false;
262
263 for (auto& completionHandler : *completionHandlers)
264 completionHandler(entry ? std::make_unique<Entry>(*entry) : nullptr);
265
266 return true;
267}
268
269void SpeculativeLoadManager::revalidateEntry(std::unique_ptr<Entry> entry, const GlobalFrameID& frameID)
270{
271 ASSERT(entry);
272 ASSERT(entry->needsValidation());
273
274 auto key = entry->key();
275 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Speculatively revalidating '%s':", key.identifier().utf8().data());
276 auto revalidator = std::make_unique<SpeculativeLoad>(frameID, constructRevalidationRequest(*entry), WTF::move(entry), [this, key](std::unique_ptr<Entry> revalidatedEntry) {
277 ASSERT(!revalidatedEntry || !revalidatedEntry->needsValidation());
278 auto protectRevalidator = m_pendingPreloads.take(key);
279 LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Speculative revalidation completed for '%s':", key.identifier().utf8().data());
280
281 if (satisfyPendingRequests(key, revalidatedEntry.get()))
282 return;
283
284 if (revalidatedEntry)
285 addPreloadedEntry(WTF::move(revalidatedEntry));
286 });
287 m_pendingPreloads.add(key, WTF::move(revalidator));
288}
289
290void SpeculativeLoadManager::preloadEntry(const Key& key, const GlobalFrameID& frameID)
291{
292 m_pendingPreloads.add(key, nullptr);
293 retrieveEntryFromStorage(key, [this, key, frameID](std::unique_ptr<Entry> entry) {
294 m_pendingPreloads.remove(key);
295
296 if (satisfyPendingRequests(key, entry.get()))
297 return;
298
299 if (!entry)
300 return;
301
302 if (entry->needsValidation())
303 revalidateEntry(WTF::move(entry), frameID);
304 else
305 addPreloadedEntry(WTF::move(entry));
306 });
307}
308
309void SpeculativeLoadManager::startSpeculativeRevalidation(const ResourceRequest& originalRequest, const GlobalFrameID& frameID, const Key& storageKey)
310{
311 if (originalRequest.requester() != ResourceRequest::Requester::Main)
312 return;
313
314 auto subresourcesStorageKey = makeSubresourcesKey(storageKey);
315
316 m_storage.retrieve(subresourcesStorageKey, static_cast<unsigned>(ResourceLoadPriority::Medium), [this, frameID](std::unique_ptr<Storage::Record> record) {
317 if (!record)
318 return false;
319
320 auto subresourcesEntry = SubresourcesEntry::decodeStorageRecord(*record);
321 if (!subresourcesEntry)
322 return false;
323
324 for (auto& subresourceKey : subresourcesEntry->subresourceKeys())
325 preloadEntry(subresourceKey, frameID);
326
327 return true;
328 });
329}
330
cdumez@apple.comee20abe2015-11-06 19:29:24 +0000331} // namespace NetworkCache
332
333} // namespace WebKit
334
335#endif // ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)