summaryrefslogtreecommitdiff
path: root/Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp')
-rw-r--r--Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp589
1 files changed, 589 insertions, 0 deletions
diff --git a/Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp b/Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp
new file mode 100644
index 000000000..db36bb8eb
--- /dev/null
+++ b/Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp
@@ -0,0 +1,589 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
+#include "NetworkCacheSpeculativeLoadManager.h"
+
+#include "Logging.h"
+#include "NetworkCacheEntry.h"
+#include "NetworkCacheSpeculativeLoad.h"
+#include "NetworkCacheSubresourcesEntry.h"
+#include "NetworkProcess.h"
+#include <WebCore/DiagnosticLoggingKeys.h>
+#include <WebCore/HysteresisActivity.h>
+#include <wtf/HashCountedSet.h>
+#include <wtf/NeverDestroyed.h>
+#include <wtf/RefCounted.h>
+#include <wtf/RunLoop.h>
+
+namespace WebKit {
+
+namespace NetworkCache {
+
+using namespace WebCore;
+
+static const auto preloadedEntryLifetime = 10s;
+
+#if !LOG_DISABLED
+static HashCountedSet<String>& allSpeculativeLoadingDiagnosticMessages()
+{
+ static NeverDestroyed<HashCountedSet<String>> messages;
+ return messages;
+}
+
+static void printSpeculativeLoadingDiagnosticMessageCounts()
+{
+ LOG(NetworkCacheSpeculativePreloading, "-- Speculative loading statistics --");
+ for (auto& pair : allSpeculativeLoadingDiagnosticMessages())
+ LOG(NetworkCacheSpeculativePreloading, "%s: %u", pair.key.utf8().data(), pair.value);
+}
+#endif
+
+static void logSpeculativeLoadingDiagnosticMessage(const GlobalFrameID& frameID, const String& message)
+{
+#if !LOG_DISABLED
+ if (WebKit2LogNetworkCacheSpeculativePreloading.state == WTFLogChannelOn)
+ allSpeculativeLoadingDiagnosticMessages().add(message);
+#endif
+ NetworkProcess::singleton().logDiagnosticMessage(frameID.first, WebCore::DiagnosticLoggingKeys::networkCacheKey(), message, WebCore::ShouldSample::Yes);
+}
+
+static const AtomicString& subresourcesType()
+{
+ ASSERT(RunLoop::isMain());
+ static NeverDestroyed<const AtomicString> resource("SubResources", AtomicString::ConstructFromLiteral);
+ return resource;
+}
+
+static inline Key makeSubresourcesKey(const Key& resourceKey, const Salt& salt)
+{
+ return Key(resourceKey.partition(), subresourcesType(), resourceKey.range(), resourceKey.identifier(), salt);
+}
+
+static inline ResourceRequest constructRevalidationRequest(const Key& key, const SubresourceInfo& subResourceInfo, const Entry* entry)
+{
+ ResourceRequest revalidationRequest(key.identifier());
+ revalidationRequest.setHTTPHeaderFields(subResourceInfo.requestHeaders());
+ revalidationRequest.setFirstPartyForCookies(subResourceInfo.firstPartyForCookies());
+ if (!key.partition().isEmpty())
+ revalidationRequest.setCachePartition(key.partition());
+ ASSERT_WITH_MESSAGE(key.range().isEmpty(), "range is not supported");
+
+ revalidationRequest.makeUnconditional();
+ if (entry) {
+ String eTag = entry->response().httpHeaderField(HTTPHeaderName::ETag);
+ if (!eTag.isEmpty())
+ revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag);
+
+ String lastModified = entry->response().httpHeaderField(HTTPHeaderName::LastModified);
+ if (!lastModified.isEmpty())
+ revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified);
+ }
+
+ revalidationRequest.setPriority(subResourceInfo.priority());
+
+ return revalidationRequest;
+}
+
+static bool responseNeedsRevalidation(const ResourceResponse& response, std::chrono::system_clock::time_point timestamp)
+{
+ if (response.cacheControlContainsNoCache())
+ return true;
+
+ auto age = computeCurrentAge(response, timestamp);
+ auto lifetime = computeFreshnessLifetimeForHTTPFamily(response, timestamp);
+ return age - lifetime > 0ms;
+}
+
+class SpeculativeLoadManager::ExpiringEntry {
+ WTF_MAKE_FAST_ALLOCATED;
+public:
+ explicit ExpiringEntry(std::function<void()>&& expirationHandler)
+ : m_lifetimeTimer(WTFMove(expirationHandler))
+ {
+ m_lifetimeTimer.startOneShot(preloadedEntryLifetime);
+ }
+
+private:
+ Timer m_lifetimeTimer;
+};
+
+class SpeculativeLoadManager::PreloadedEntry : private ExpiringEntry {
+ WTF_MAKE_FAST_ALLOCATED;
+public:
+ PreloadedEntry(std::unique_ptr<Entry> entry, std::optional<ResourceRequest>&& speculativeValidationRequest, std::function<void()>&& lifetimeReachedHandler)
+ : ExpiringEntry(WTFMove(lifetimeReachedHandler))
+ , m_entry(WTFMove(entry))
+ , m_speculativeValidationRequest(WTFMove(speculativeValidationRequest))
+ { }
+
+ std::unique_ptr<Entry> takeCacheEntry()
+ {
+ ASSERT(m_entry);
+ return WTFMove(m_entry);
+ }
+
+ const std::optional<ResourceRequest>& revalidationRequest() const { return m_speculativeValidationRequest; }
+ bool wasRevalidated() const { return !!m_speculativeValidationRequest; }
+
+private:
+ std::unique_ptr<Entry> m_entry;
+ std::optional<ResourceRequest> m_speculativeValidationRequest;
+};
+
+class SpeculativeLoadManager::PendingFrameLoad : public RefCounted<PendingFrameLoad> {
+public:
+ static Ref<PendingFrameLoad> create(Storage& storage, const Key& mainResourceKey, std::function<void()>&& loadCompletionHandler)
+ {
+ return adoptRef(*new PendingFrameLoad(storage, mainResourceKey, WTFMove(loadCompletionHandler)));
+ }
+
+ ~PendingFrameLoad()
+ {
+ ASSERT(m_didFinishLoad);
+ ASSERT(m_didRetrieveExistingEntry);
+ }
+
+ void registerSubresourceLoad(const ResourceRequest& request, const Key& subresourceKey)
+ {
+ ASSERT(RunLoop::isMain());
+ m_subresourceLoads.append(std::make_unique<SubresourceLoad>(request, subresourceKey));
+ m_loadHysteresisActivity.impulse();
+ }
+
+ void markLoadAsCompleted()
+ {
+ ASSERT(RunLoop::isMain());
+ if (m_didFinishLoad)
+ return;
+
+#if !LOG_DISABLED
+ printSpeculativeLoadingDiagnosticMessageCounts();
+#endif
+
+ m_didFinishLoad = true;
+ saveToDiskIfReady();
+ m_loadCompletionHandler();
+ }
+
+ void setExistingSubresourcesEntry(std::unique_ptr<SubresourcesEntry> entry)
+ {
+ ASSERT(!m_existingEntry);
+ ASSERT(!m_didRetrieveExistingEntry);
+
+ m_existingEntry = WTFMove(entry);
+ m_didRetrieveExistingEntry = true;
+ saveToDiskIfReady();
+ }
+
+private:
+ PendingFrameLoad(Storage& storage, const Key& mainResourceKey, std::function<void()>&& loadCompletionHandler)
+ : m_storage(storage)
+ , m_mainResourceKey(mainResourceKey)
+ , m_loadCompletionHandler(WTFMove(loadCompletionHandler))
+ , m_loadHysteresisActivity([this](HysteresisState state) { if (state == HysteresisState::Stopped) markLoadAsCompleted(); })
+ {
+ m_loadHysteresisActivity.impulse();
+ }
+
+ void saveToDiskIfReady()
+ {
+ if (!m_didFinishLoad || !m_didRetrieveExistingEntry)
+ return;
+
+ if (m_subresourceLoads.isEmpty())
+ return;
+
+#if !LOG_DISABLED
+ LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Saving to disk list of subresources for '%s':", m_mainResourceKey.identifier().utf8().data());
+ for (auto& subresourceLoad : m_subresourceLoads)
+ LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) * Subresource: '%s'.", subresourceLoad->key.identifier().utf8().data());
+#endif
+
+ if (m_existingEntry) {
+ m_existingEntry->updateSubresourceLoads(m_subresourceLoads);
+ m_storage.store(m_existingEntry->encodeAsStorageRecord(), [](const Data&) { });
+ } else {
+ SubresourcesEntry entry(makeSubresourcesKey(m_mainResourceKey, m_storage.salt()), m_subresourceLoads);
+ m_storage.store(entry.encodeAsStorageRecord(), [](const Data&) { });
+ }
+ }
+
+ Storage& m_storage;
+ Key m_mainResourceKey;
+ Vector<std::unique_ptr<SubresourceLoad>> m_subresourceLoads;
+ std::function<void()> m_loadCompletionHandler;
+ HysteresisActivity m_loadHysteresisActivity;
+ std::unique_ptr<SubresourcesEntry> m_existingEntry;
+ bool m_didFinishLoad { false };
+ bool m_didRetrieveExistingEntry { false };
+};
+
+SpeculativeLoadManager::SpeculativeLoadManager(Storage& storage)
+ : m_storage(storage)
+{
+}
+
+SpeculativeLoadManager::~SpeculativeLoadManager()
+{
+}
+
+#if !LOG_DISABLED
+
+static void dumpHTTPHeadersDiff(const HTTPHeaderMap& headersA, const HTTPHeaderMap& headersB)
+{
+ auto aEnd = headersA.end();
+ for (auto it = headersA.begin(); it != aEnd; ++it) {
+ String valueB = headersB.get(it->key);
+ if (valueB.isNull())
+ LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header is only in first request (value: %s)", it->key.utf8().data(), it->value.utf8().data());
+ else if (it->value != valueB)
+ LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header differs in both requests: %s != %s", it->key.utf8().data(), it->value.utf8().data(), valueB.utf8().data());
+ }
+ auto bEnd = headersB.end();
+ for (auto it = headersB.begin(); it != bEnd; ++it) {
+ if (!headersA.contains(it->key))
+ LOG(NetworkCacheSpeculativePreloading, "* '%s' HTTP header is only in second request (value: %s)", it->key.utf8().data(), it->value.utf8().data());
+ }
+}
+
+#endif
+
+static bool requestsHeadersMatch(const ResourceRequest& speculativeValidationRequest, const ResourceRequest& actualRequest)
+{
+ ASSERT(!actualRequest.isConditional());
+ ResourceRequest speculativeRequest = speculativeValidationRequest;
+ speculativeRequest.makeUnconditional();
+
+ if (speculativeRequest.httpHeaderFields() != actualRequest.httpHeaderFields()) {
+ LOG(NetworkCacheSpeculativePreloading, "Cannot reuse speculatively validated entry because HTTP headers used for validation do not match");
+#if !LOG_DISABLED
+ dumpHTTPHeadersDiff(speculativeRequest.httpHeaderFields(), actualRequest.httpHeaderFields());
+#endif
+ return false;
+ }
+ return true;
+}
+
+bool SpeculativeLoadManager::canUsePreloadedEntry(const PreloadedEntry& entry, const ResourceRequest& actualRequest)
+{
+ if (!entry.wasRevalidated())
+ return true;
+
+ ASSERT(entry.revalidationRequest());
+ return requestsHeadersMatch(*entry.revalidationRequest(), actualRequest);
+}
+
+bool SpeculativeLoadManager::canUsePendingPreload(const SpeculativeLoad& load, const ResourceRequest& actualRequest)
+{
+ return requestsHeadersMatch(load.originalRequest(), actualRequest);
+}
+
+bool SpeculativeLoadManager::canRetrieve(const Key& storageKey, const WebCore::ResourceRequest& request, const GlobalFrameID& frameID) const
+{
+ // Check already preloaded entries.
+ if (auto preloadedEntry = m_preloadedEntries.get(storageKey)) {
+ if (!canUsePreloadedEntry(*preloadedEntry, request)) {
+ LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: Could not use preloaded entry to satisfy request for '%s' due to HTTP headers mismatch:", storageKey.identifier().utf8().data());
+ logSpeculativeLoadingDiagnosticMessage(frameID, preloadedEntry->wasRevalidated() ? DiagnosticLoggingKeys::wastedSpeculativeWarmupWithRevalidationKey() : DiagnosticLoggingKeys::wastedSpeculativeWarmupWithoutRevalidationKey());
+ return false;
+ }
+
+ LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: Using preloaded entry to satisfy request for '%s':", storageKey.identifier().utf8().data());
+ logSpeculativeLoadingDiagnosticMessage(frameID, preloadedEntry->wasRevalidated() ? DiagnosticLoggingKeys::successfulSpeculativeWarmupWithRevalidationKey() : DiagnosticLoggingKeys::successfulSpeculativeWarmupWithoutRevalidationKey());
+ return true;
+ }
+
+ // Check pending speculative revalidations.
+ auto* pendingPreload = m_pendingPreloads.get(storageKey);
+ if (!pendingPreload) {
+ if (m_notPreloadedEntries.get(storageKey))
+ logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::entryWronglyNotWarmedUpKey());
+ else
+ logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::unknownEntryRequestKey());
+
+ return false;
+ }
+
+ if (!canUsePendingPreload(*pendingPreload, request)) {
+ LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: revalidation already in progress for '%s' but unusable due to HTTP headers mismatch:", storageKey.identifier().utf8().data());
+ logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::wastedSpeculativeWarmupWithRevalidationKey());
+ return false;
+ }
+
+ LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Retrieval: revalidation already in progress for '%s':", storageKey.identifier().utf8().data());
+
+ return true;
+}
+
+void SpeculativeLoadManager::retrieve(const Key& storageKey, RetrieveCompletionHandler&& completionHandler)
+{
+ if (auto preloadedEntry = m_preloadedEntries.take(storageKey)) {
+ RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), cacheEntry = preloadedEntry->takeCacheEntry()] () mutable {
+ completionHandler(WTFMove(cacheEntry));
+ });
+ return;
+ }
+ ASSERT(m_pendingPreloads.contains(storageKey));
+ // FIXME: This breaks incremental loading when the revalidation is not successful.
+ auto addResult = m_pendingRetrieveRequests.ensure(storageKey, [] {
+ return std::make_unique<Vector<RetrieveCompletionHandler>>();
+ });
+ addResult.iterator->value->append(WTFMove(completionHandler));
+}
+
+void SpeculativeLoadManager::registerLoad(const GlobalFrameID& frameID, const ResourceRequest& request, const Key& resourceKey)
+{
+ ASSERT(RunLoop::isMain());
+ ASSERT(request.url().protocolIsInHTTPFamily());
+
+ if (request.httpMethod() != "GET")
+ return;
+
+ auto isMainResource = request.requester() == ResourceRequest::Requester::Main;
+ if (isMainResource) {
+ // Mark previous load in this frame as completed if necessary.
+ if (auto* pendingFrameLoad = m_pendingFrameLoads.get(frameID))
+ pendingFrameLoad->markLoadAsCompleted();
+
+ ASSERT(!m_pendingFrameLoads.contains(frameID));
+
+ // Start tracking loads in this frame.
+ RefPtr<PendingFrameLoad> pendingFrameLoad = PendingFrameLoad::create(m_storage, resourceKey, [this, frameID] {
+ bool wasRemoved = m_pendingFrameLoads.remove(frameID);
+ ASSERT_UNUSED(wasRemoved, wasRemoved);
+ });
+ m_pendingFrameLoads.add(frameID, pendingFrameLoad);
+
+ // Retrieve the subresources entry if it exists to start speculative revalidation and to update it.
+ retrieveSubresourcesEntry(resourceKey, [this, frameID, pendingFrameLoad](std::unique_ptr<SubresourcesEntry> entry) {
+ if (entry)
+ startSpeculativeRevalidation(frameID, *entry);
+
+ pendingFrameLoad->setExistingSubresourcesEntry(WTFMove(entry));
+ });
+ return;
+ }
+
+ if (auto* pendingFrameLoad = m_pendingFrameLoads.get(frameID))
+ pendingFrameLoad->registerSubresourceLoad(request, resourceKey);
+}
+
+void SpeculativeLoadManager::addPreloadedEntry(std::unique_ptr<Entry> entry, const GlobalFrameID& frameID, std::optional<ResourceRequest>&& revalidationRequest)
+{
+ ASSERT(entry);
+ ASSERT(!entry->needsValidation());
+ auto key = entry->key();
+ m_preloadedEntries.add(key, std::make_unique<PreloadedEntry>(WTFMove(entry), WTFMove(revalidationRequest), [this, key, frameID] {
+ auto preloadedEntry = m_preloadedEntries.take(key);
+ ASSERT(preloadedEntry);
+ if (preloadedEntry->wasRevalidated())
+ logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::wastedSpeculativeWarmupWithRevalidationKey());
+ else
+ logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::wastedSpeculativeWarmupWithoutRevalidationKey());
+ }));
+}
+
+void SpeculativeLoadManager::retrieveEntryFromStorage(const SubresourceInfo& info, RetrieveCompletionHandler&& completionHandler)
+{
+ m_storage.retrieve(info.key(), static_cast<unsigned>(info.priority()), [completionHandler = WTFMove(completionHandler)](auto record) {
+ if (!record) {
+ completionHandler(nullptr);
+ return false;
+ }
+ auto entry = Entry::decodeStorageRecord(*record);
+ if (!entry) {
+ completionHandler(nullptr);
+ return false;
+ }
+
+ auto& response = entry->response();
+ if (responseNeedsRevalidation(response, entry->timeStamp())) {
+ // Do not use cached redirects that have expired.
+ if (entry->redirectRequest()) {
+ completionHandler(nullptr);
+ return true;
+ }
+ entry->setNeedsValidation(true);
+ }
+
+ completionHandler(WTFMove(entry));
+ return true;
+ });
+}
+
+bool SpeculativeLoadManager::satisfyPendingRequests(const Key& key, Entry* entry)
+{
+ auto completionHandlers = m_pendingRetrieveRequests.take(key);
+ if (!completionHandlers)
+ return false;
+
+ for (auto& completionHandler : *completionHandlers)
+ completionHandler(entry ? std::make_unique<Entry>(*entry) : nullptr);
+
+ return true;
+}
+
+void SpeculativeLoadManager::revalidateSubresource(const SubresourceInfo& subresourceInfo, std::unique_ptr<Entry> entry, const GlobalFrameID& frameID)
+{
+ ASSERT(!entry || entry->needsValidation());
+
+ auto& key = subresourceInfo.key();
+
+ // Range is not supported.
+ if (!key.range().isEmpty())
+ return;
+
+ ResourceRequest revalidationRequest = constructRevalidationRequest(key, subresourceInfo, entry.get());
+
+ LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Speculatively revalidating '%s':", key.identifier().utf8().data());
+
+ auto revalidator = std::make_unique<SpeculativeLoad>(frameID, revalidationRequest, WTFMove(entry), [this, key, revalidationRequest, frameID](std::unique_ptr<Entry> revalidatedEntry) {
+ ASSERT(!revalidatedEntry || !revalidatedEntry->needsValidation());
+ ASSERT(!revalidatedEntry || revalidatedEntry->key() == key);
+
+ auto protectRevalidator = m_pendingPreloads.take(key);
+ LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Speculative revalidation completed for '%s':", key.identifier().utf8().data());
+
+ if (satisfyPendingRequests(key, revalidatedEntry.get())) {
+ if (revalidatedEntry)
+ logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::successfulSpeculativeWarmupWithRevalidationKey());
+ return;
+ }
+
+ if (revalidatedEntry)
+ addPreloadedEntry(WTFMove(revalidatedEntry), frameID, revalidationRequest);
+ });
+ m_pendingPreloads.add(key, WTFMove(revalidator));
+}
+
+static bool canRevalidate(const SubresourceInfo& subresourceInfo, const Entry* entry)
+{
+ ASSERT(!subresourceInfo.isTransient());
+ ASSERT(!entry || entry->needsValidation());
+
+ if (entry && entry->response().hasCacheValidatorFields())
+ return true;
+
+ auto seenAge = subresourceInfo.lastSeen() - subresourceInfo.firstSeen();
+ if (seenAge == 0ms) {
+ LOG(NetworkCacheSpeculativePreloading, "Speculative load: Seen only once");
+ return false;
+ }
+
+ auto now = std::chrono::system_clock::now();
+ auto firstSeenAge = now - subresourceInfo.firstSeen();
+ auto lastSeenAge = now - subresourceInfo.lastSeen();
+ // Sanity check.
+ if (seenAge <= 0ms || firstSeenAge <= 0ms || lastSeenAge <= 0ms)
+ return false;
+
+ // Load full resources speculatively if they seem to stay the same.
+ const auto minimumAgeRatioToLoad = 2. / 3;
+ const auto recentMinimumAgeRatioToLoad = 1. / 3;
+ const auto recentThreshold = 5min;
+
+ auto ageRatio = std::chrono::duration_cast<std::chrono::duration<double>>(seenAge) / firstSeenAge;
+ auto minimumAgeRatio = lastSeenAge > recentThreshold ? minimumAgeRatioToLoad : recentMinimumAgeRatioToLoad;
+
+ LOG(NetworkCacheSpeculativePreloading, "Speculative load: ok=%d ageRatio=%f entry=%d", ageRatio > minimumAgeRatio, ageRatio, !!entry);
+
+ if (ageRatio > minimumAgeRatio)
+ return true;
+
+ return false;
+}
+
+void SpeculativeLoadManager::preloadEntry(const Key& key, const SubresourceInfo& subresourceInfo, const GlobalFrameID& frameID)
+{
+ if (m_pendingPreloads.contains(key))
+ return;
+ m_pendingPreloads.add(key, nullptr);
+
+ retrieveEntryFromStorage(subresourceInfo, [this, key, subresourceInfo, frameID](std::unique_ptr<Entry> entry) {
+ ASSERT(!m_pendingPreloads.get(key));
+ bool removed = m_pendingPreloads.remove(key);
+ ASSERT_UNUSED(removed, removed);
+
+ if (satisfyPendingRequests(key, entry.get())) {
+ if (entry)
+ logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::successfulSpeculativeWarmupWithoutRevalidationKey());
+ return;
+ }
+
+ if (!entry || entry->needsValidation()) {
+ if (canRevalidate(subresourceInfo, entry.get()))
+ revalidateSubresource(subresourceInfo, WTFMove(entry), frameID);
+ return;
+ }
+
+ addPreloadedEntry(WTFMove(entry), frameID);
+ });
+}
+
+void SpeculativeLoadManager::startSpeculativeRevalidation(const GlobalFrameID& frameID, SubresourcesEntry& entry)
+{
+ for (auto& subresourceInfo : entry.subresources()) {
+ auto& key = subresourceInfo.key();
+ if (!subresourceInfo.isTransient())
+ preloadEntry(key, subresourceInfo, frameID);
+ else {
+ LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Not preloading '%s' because it is marked as transient", key.identifier().utf8().data());
+ m_notPreloadedEntries.add(key, std::make_unique<ExpiringEntry>([this, key, frameID] {
+ logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::entryRightlyNotWarmedUpKey());
+ m_notPreloadedEntries.remove(key);
+ }));
+ }
+ }
+}
+
+void SpeculativeLoadManager::retrieveSubresourcesEntry(const Key& storageKey, std::function<void (std::unique_ptr<SubresourcesEntry>)>&& completionHandler)
+{
+ ASSERT(storageKey.type() == "Resource");
+ auto subresourcesStorageKey = makeSubresourcesKey(storageKey, m_storage.salt());
+ m_storage.retrieve(subresourcesStorageKey, static_cast<unsigned>(ResourceLoadPriority::Medium), [completionHandler = WTFMove(completionHandler)](auto record) {
+ if (!record) {
+ completionHandler(nullptr);
+ return false;
+ }
+
+ auto subresourcesEntry = SubresourcesEntry::decodeStorageRecord(*record);
+ if (!subresourcesEntry) {
+ completionHandler(nullptr);
+ return false;
+ }
+
+ completionHandler(WTFMove(subresourcesEntry));
+ return true;
+ });
+}
+
+} // namespace NetworkCache
+
+} // namespace WebKit
+
+#endif // ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)