diff options
Diffstat (limited to 'Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp')
-rw-r--r-- | Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp | 589 |
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) |