Load resources speculatively
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 1 Feb 2017 13:38:49 +0000 (13:38 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 1 Feb 2017 13:38:49 +0000 (13:38 +0000)
https://bugs.webkit.org/show_bug.cgi?id=167660

Reviewed by Andreas Kling.

* NetworkProcess/cache/NetworkCache.cpp:
(WebKit::NetworkCache::Cache::makeEntry):

    Factor to a function.

(WebKit::NetworkCache::Cache::store):
* NetworkProcess/cache/NetworkCache.h:
* NetworkProcess/cache/NetworkCacheSpeculativeLoad.cpp:
(WebKit::NetworkCache::SpeculativeLoad::SpeculativeLoad):

    Support loads where we don't have existing cache entry to validate.

(WebKit::NetworkCache::SpeculativeLoad::didReceiveBuffer):

    Synthesize a NetworkCache::Entry if we can't store it.

(WebKit::NetworkCache::SpeculativeLoad::didFinishLoading):
(WebKit::NetworkCache::SpeculativeLoad::didFailLoading):
(WebKit::NetworkCache::SpeculativeLoad::abort):
(WebKit::NetworkCache::SpeculativeLoad::didComplete):
* NetworkProcess/cache/NetworkCacheSpeculativeLoad.h:
* NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp:
(WebKit::NetworkCache::constructRevalidationRequest):

    Make having existing cache entry optional.

(WebKit::NetworkCache::SpeculativeLoadManager::retrieveEntryFromStorage):

    Allow validation without validation headers (that is, normal load).

(WebKit::NetworkCache::SpeculativeLoadManager::revalidateSubresource):

    Make having existing cache entry optional.

(WebKit::NetworkCache::canRevalidate):

    Allow speculative loads without validation headers if we have high confidence that the
    page is going to request this resource again. This is based on the time span we have
    seen this resource being loaded on a given page and how much time has elapsed since we
    last loaded it.

    For example if we have seen the resource over the last 10 days we'll speculate that it will
    be required for the next 5 days.

(WebKit::NetworkCache::SpeculativeLoadManager::preloadEntry):
(WebKit::NetworkCache::SpeculativeLoadManager::revalidateEntry): Deleted.
* NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.h:
* NetworkProcess/cache/NetworkCacheSubresourcesEntry.cpp:
(WebKit::NetworkCache::SubresourceInfo::encode):
(WebKit::NetworkCache::SubresourceInfo::decode):

    Encode the firstSeen and lastSeen time stamps.

(WebKit::NetworkCache::SubresourceInfo::SubresourceInfo):
(WebKit::NetworkCache::makeSubresourceInfoVector):
(WebKit::NetworkCache::SubresourcesEntry::SubresourcesEntry):
(WebKit::NetworkCache::SubresourcesEntry::updateSubresourceLoads):
* NetworkProcess/cache/NetworkCacheSubresourcesEntry.h:
(WebKit::NetworkCache::SubresourceInfo::lastSeen):
(WebKit::NetworkCache::SubresourceInfo::firstSeen):
(WebKit::NetworkCache::SubresourceInfo::setNonTransient):
(WebKit::NetworkCache::SubresourceInfo::SubresourceInfo): Deleted.
(WebKit::NetworkCache::SubresourceInfo::setTransient): Deleted.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@211480 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit2/ChangeLog
Source/WebKit2/NetworkProcess/cache/NetworkCache.cpp
Source/WebKit2/NetworkProcess/cache/NetworkCache.h
Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoad.cpp
Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoad.h
Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp
Source/WebKit2/NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.h
Source/WebKit2/NetworkProcess/cache/NetworkCacheSubresourcesEntry.cpp
Source/WebKit2/NetworkProcess/cache/NetworkCacheSubresourcesEntry.h

index b430d43..f51e1ce 100644 (file)
@@ -1,3 +1,74 @@
+2017-02-01  Antti Koivisto  <antti@apple.com>
+
+        Load resources speculatively
+        https://bugs.webkit.org/show_bug.cgi?id=167660
+
+        Reviewed by Andreas Kling.
+
+        * NetworkProcess/cache/NetworkCache.cpp:
+        (WebKit::NetworkCache::Cache::makeEntry):
+
+            Factor to a function.
+
+        (WebKit::NetworkCache::Cache::store):
+        * NetworkProcess/cache/NetworkCache.h:
+        * NetworkProcess/cache/NetworkCacheSpeculativeLoad.cpp:
+        (WebKit::NetworkCache::SpeculativeLoad::SpeculativeLoad):
+
+            Support loads where we don't have existing cache entry to validate.
+
+        (WebKit::NetworkCache::SpeculativeLoad::didReceiveBuffer):
+
+            Synthesize a NetworkCache::Entry if we can't store it.
+
+        (WebKit::NetworkCache::SpeculativeLoad::didFinishLoading):
+        (WebKit::NetworkCache::SpeculativeLoad::didFailLoading):
+        (WebKit::NetworkCache::SpeculativeLoad::abort):
+        (WebKit::NetworkCache::SpeculativeLoad::didComplete):
+        * NetworkProcess/cache/NetworkCacheSpeculativeLoad.h:
+        * NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.cpp:
+        (WebKit::NetworkCache::constructRevalidationRequest):
+
+            Make having existing cache entry optional.
+
+        (WebKit::NetworkCache::SpeculativeLoadManager::retrieveEntryFromStorage):
+
+            Allow validation without validation headers (that is, normal load).
+
+        (WebKit::NetworkCache::SpeculativeLoadManager::revalidateSubresource):
+
+            Make having existing cache entry optional.
+
+        (WebKit::NetworkCache::canRevalidate):
+
+            Allow speculative loads without validation headers if we have high confidence that the
+            page is going to request this resource again. This is based on the time span we have
+            seen this resource being loaded on a given page and how much time has elapsed since we
+            last loaded it.
+
+            For example if we have seen the resource over the last 10 days we'll speculate that it will
+            be required for the next 5 days.
+
+        (WebKit::NetworkCache::SpeculativeLoadManager::preloadEntry):
+        (WebKit::NetworkCache::SpeculativeLoadManager::revalidateEntry): Deleted.
+        * NetworkProcess/cache/NetworkCacheSpeculativeLoadManager.h:
+        * NetworkProcess/cache/NetworkCacheSubresourcesEntry.cpp:
+        (WebKit::NetworkCache::SubresourceInfo::encode):
+        (WebKit::NetworkCache::SubresourceInfo::decode):
+
+            Encode the firstSeen and lastSeen time stamps.
+
+        (WebKit::NetworkCache::SubresourceInfo::SubresourceInfo):
+        (WebKit::NetworkCache::makeSubresourceInfoVector):
+        (WebKit::NetworkCache::SubresourcesEntry::SubresourcesEntry):
+        (WebKit::NetworkCache::SubresourcesEntry::updateSubresourceLoads):
+        * NetworkProcess/cache/NetworkCacheSubresourcesEntry.h:
+        (WebKit::NetworkCache::SubresourceInfo::lastSeen):
+        (WebKit::NetworkCache::SubresourceInfo::firstSeen):
+        (WebKit::NetworkCache::SubresourceInfo::setNonTransient):
+        (WebKit::NetworkCache::SubresourceInfo::SubresourceInfo): Deleted.
+        (WebKit::NetworkCache::SubresourceInfo::setTransient): Deleted.
+
 2017-02-01  Csaba Osztrogon√°c  <ossy@webkit.org>
 
         [Mac][cmake] Unreviewed speculative buildfix after r211403.
index d0fa67d..315e963 100644 (file)
@@ -389,6 +389,12 @@ void Cache::retrieve(const WebCore::ResourceRequest& request, const GlobalFrameI
     });
 }
 
+    
+std::unique_ptr<Entry> Cache::makeEntry(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData)
+{
+    return std::make_unique<Entry>(makeCacheKey(request), response, WTFMove(responseData), WebCore::collectVaryingRequestHeaders(request, response));
+}
+
 std::unique_ptr<Entry> Cache::store(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData, Function<void (MappedBody&)>&& completionHandler)
 {
     ASSERT(isEnabled());
@@ -413,7 +419,7 @@ std::unique_ptr<Entry> Cache::store(const WebCore::ResourceRequest& request, con
         return nullptr;
     }
 
-    auto cacheEntry = std::make_unique<Entry>(makeCacheKey(request), response, WTFMove(responseData), WebCore::collectVaryingRequestHeaders(request, response));
+    auto cacheEntry = makeEntry(request, response, WTFMove(responseData));
     auto record = cacheEntry->encodeAsStorageRecord();
 
     m_storage->store(record, [completionHandler = WTFMove(completionHandler)](const Data& bodyData) {
index 64a154d..ab8e273 100644 (file)
@@ -123,6 +123,8 @@ public:
 
     void retrieveData(const DataKey&, Function<void (const uint8_t* data, size_t size)>);
     void storeData(const DataKey&,  const uint8_t* data, size_t);
+    
+    std::unique_ptr<Entry> makeEntry(const WebCore::ResourceRequest&, const WebCore::ResourceResponse&, RefPtr<WebCore::SharedBuffer>&&);
 
     void dumpContentsToFile();
 
index b4e775d..db76618 100644 (file)
@@ -46,10 +46,9 @@ SpeculativeLoad::SpeculativeLoad(const GlobalFrameID& frameID, const ResourceReq
     , m_completionHandler(WTFMove(completionHandler))
     , m_originalRequest(request)
     , m_bufferedDataForCache(SharedBuffer::create())
-    , m_cacheEntryForValidation(WTFMove(cacheEntryForValidation))
+    , m_cacheEntry(WTFMove(cacheEntryForValidation))
 {
-    ASSERT(m_cacheEntryForValidation);
-    ASSERT(m_cacheEntryForValidation->needsValidation());
+    ASSERT(!m_cacheEntry || m_cacheEntry->needsValidation());
 
     NetworkLoadParameters parameters;
     parameters.sessionID = SessionID::defaultSessionID();
@@ -82,20 +81,18 @@ auto SpeculativeLoad::didReceiveResponse(ResourceResponse&& receivedResponse) ->
     if (m_response.isMultipart())
         m_bufferedDataForCache = nullptr;
 
-    ASSERT(m_cacheEntryForValidation);
-
     bool validationSucceeded = m_response.httpStatusCode() == 304; // 304 Not Modified
-    if (validationSucceeded)
-        m_cacheEntryForValidation = NetworkCache::singleton().update(m_originalRequest, m_frameID, *m_cacheEntryForValidation, m_response);
+    if (validationSucceeded && m_cacheEntry)
+        m_cacheEntry = NetworkCache::singleton().update(m_originalRequest, m_frameID, *m_cacheEntry, m_response);
     else
-        m_cacheEntryForValidation = nullptr;
+        m_cacheEntry = nullptr;
 
     return ShouldContinueDidReceiveResponse::Yes;
 }
 
 void SpeculativeLoad::didReceiveBuffer(Ref<SharedBuffer>&& buffer, int reportedEncodedDataLength)
 {
-    ASSERT(!m_cacheEntryForValidation);
+    ASSERT(!m_cacheEntry);
 
     if (m_bufferedDataForCache) {
         // Prevent memory growth in case of streaming data.
@@ -109,8 +106,12 @@ void SpeculativeLoad::didReceiveBuffer(Ref<SharedBuffer>&& buffer, int reportedE
 
 void SpeculativeLoad::didFinishLoading(double finishTime)
 {
-    if (!m_cacheEntryForValidation && m_bufferedDataForCache)
-        m_cacheEntryForValidation = NetworkCache::singleton().store(m_originalRequest, m_response, WTFMove(m_bufferedDataForCache), [](auto& mappedBody) { });
+    if (!m_cacheEntry && m_bufferedDataForCache) {
+        m_cacheEntry = NetworkCache::singleton().store(m_originalRequest, m_response, WTFMove(m_bufferedDataForCache), [](auto& mappedBody) { });
+        // Create a synthetic cache entry if we can't store.
+        if (!m_cacheEntry && m_response.httpStatusCode() == 200)
+            m_cacheEntry = NetworkCache::singleton().makeEntry(m_originalRequest, m_response, WTFMove(m_bufferedDataForCache));
+    }
 
     didComplete();
 }
@@ -124,7 +125,7 @@ void SpeculativeLoad::canAuthenticateAgainstProtectionSpaceAsync(const WebCore::
 
 void SpeculativeLoad::didFailLoading(const ResourceError&)
 {
-    m_cacheEntryForValidation = nullptr;
+    m_cacheEntry = nullptr;
 
     didComplete();
 }
@@ -134,7 +135,7 @@ void SpeculativeLoad::abort()
     if (m_networkLoad)
         m_networkLoad->cancel();
 
-    m_cacheEntryForValidation = nullptr;
+    m_cacheEntry = nullptr;
     didComplete();
 }
 
@@ -145,10 +146,10 @@ void SpeculativeLoad::didComplete()
     m_networkLoad = nullptr;
 
     // Make sure speculatively revalidated resources do not get validated by the NetworkResourceLoader again.
-    if (m_cacheEntryForValidation)
-        m_cacheEntryForValidation->setNeedsValidation(false);
+    if (m_cacheEntry)
+        m_cacheEntry->setNeedsValidation(false);
 
-    m_completionHandler(WTFMove(m_cacheEntryForValidation));
+    m_completionHandler(WTFMove(m_cacheEntry));
 }
 
 } // namespace NetworkCache
index 02df2c0..6d045f6 100644 (file)
@@ -76,7 +76,7 @@ private:
     WebCore::ResourceResponse m_response;
 
     RefPtr<WebCore::SharedBuffer> m_bufferedDataForCache;
-    std::unique_ptr<NetworkCache::Entry> m_cacheEntryForValidation;
+    std::unique_ptr<NetworkCache::Entry> m_cacheEntry;
 };
 
 } // namespace NetworkCache
index 2c2008c..9671188 100644 (file)
@@ -84,25 +84,27 @@ 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 Entry& entry, const SubresourceInfo& subResourceInfo)
+static inline ResourceRequest constructRevalidationRequest(const Key& key, const SubresourceInfo& subResourceInfo, const Entry* entry)
 {
-    ResourceRequest revalidationRequest(entry.key().identifier());
+    ResourceRequest revalidationRequest(key.identifier());
     revalidationRequest.setHTTPHeaderFields(subResourceInfo.requestHeaders());
     revalidationRequest.setFirstPartyForCookies(subResourceInfo.firstPartyForCookies());
 #if ENABLE(CACHE_PARTITIONING)
-    if (!entry.key().partition().isEmpty())
-        revalidationRequest.setCachePartition(entry.key().partition());
+    if (!key.partition().isEmpty())
+        revalidationRequest.setCachePartition(key.partition());
 #endif
-    ASSERT_WITH_MESSAGE(entry.key().range().isEmpty(), "range is not supported");
+    ASSERT_WITH_MESSAGE(key.range().isEmpty(), "range is not supported");
 
     revalidationRequest.makeUnconditional();
-    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);
+    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());
 
@@ -406,9 +408,9 @@ void SpeculativeLoadManager::addPreloadedEntry(std::unique_ptr<Entry> entry, con
     }));
 }
 
-void SpeculativeLoadManager::retrieveEntryFromStorage(const Key& key, ResourceLoadPriority priority, RetrieveCompletionHandler&& completionHandler)
+void SpeculativeLoadManager::retrieveEntryFromStorage(const SubresourceInfo& info, RetrieveCompletionHandler&& completionHandler)
 {
-    m_storage.retrieve(key, static_cast<unsigned>(priority), [completionHandler = WTFMove(completionHandler)](auto record) {
+    m_storage.retrieve(info.key(), static_cast<unsigned>(info.priority()), [completionHandler = WTFMove(completionHandler)](auto record) {
         if (!record) {
             completionHandler(nullptr);
             return false;
@@ -420,11 +422,6 @@ void SpeculativeLoadManager::retrieveEntryFromStorage(const Key& key, ResourceLo
         }
 
         auto& response = entry->response();
-        if (!response.hasCacheValidatorFields()) {
-            completionHandler(nullptr);
-            return true;
-        }
-
         if (responseNeedsRevalidation(response, entry->timeStamp())) {
             // Do not use cached redirects that have expired.
             if (entry->redirectRequest()) {
@@ -451,18 +448,17 @@ bool SpeculativeLoadManager::satisfyPendingRequests(const Key& key, Entry* entry
     return true;
 }
 
-void SpeculativeLoadManager::revalidateEntry(std::unique_ptr<Entry> entry, const SubresourceInfo& subresourceInfo, const GlobalFrameID& frameID)
+void SpeculativeLoadManager::revalidateSubresource(const SubresourceInfo& subresourceInfo, std::unique_ptr<Entry> entry, const GlobalFrameID& frameID)
 {
-    ASSERT(entry);
-    ASSERT(entry->needsValidation());
+    ASSERT(!entry || entry->needsValidation());
 
-    auto key = entry->key();
+    auto& key = subresourceInfo.key();
 
     // Range is not supported.
     if (!key.range().isEmpty())
         return;
 
-    ResourceRequest revalidationRequest = constructRevalidationRequest(*entry, subresourceInfo);
+    ResourceRequest revalidationRequest = constructRevalidationRequest(key, subresourceInfo, entry.get());
 
     LOG(NetworkCacheSpeculativePreloading, "(NetworkProcess) Speculatively revalidating '%s':", key.identifier().utf8().data());
 
@@ -484,14 +480,51 @@ void SpeculativeLoadManager::revalidateEntry(std::unique_ptr<Entry> entry, const
     });
     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)
+void SpeculativeLoadManager::preloadEntry(const Key& key, const SubresourceInfo& subresourceInfo, const GlobalFrameID& frameID)
 {
     if (m_pendingPreloads.contains(key))
         return;
-    
     m_pendingPreloads.add(key, nullptr);
-    retrieveEntryFromStorage(key, subResourceInfo.priority(), [this, key, subResourceInfo, frameID](std::unique_ptr<Entry> entry) {
+    
+    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);
@@ -501,14 +534,14 @@ void SpeculativeLoadManager::preloadEntry(const Key& key, const SubresourceInfo&
                 logSpeculativeLoadingDiagnosticMessage(frameID, DiagnosticLoggingKeys::successfulSpeculativeWarmupWithoutRevalidationKey());
             return;
         }
-
-        if (!entry)
+        
+        if (!entry || entry->needsValidation()) {
+            if (canRevalidate(subresourceInfo, entry.get()))
+                revalidateSubresource(subresourceInfo, WTFMove(entry), frameID);
             return;
-
-        if (entry->needsValidation())
-            revalidateEntry(WTFMove(entry), subResourceInfo, frameID);
-        else
-            addPreloadedEntry(WTFMove(entry), frameID);
+        }
+        
+        addPreloadedEntry(WTFMove(entry), frameID);
     });
 }
 
index fb97c23..e9c621a 100644 (file)
@@ -61,8 +61,8 @@ private:
 
     void addPreloadedEntry(std::unique_ptr<Entry>, const GlobalFrameID&, std::optional<WebCore::ResourceRequest>&& revalidationRequest = std::nullopt);
     void preloadEntry(const Key&, const SubresourceInfo&, const GlobalFrameID&);
-    void retrieveEntryFromStorage(const Key&, WebCore::ResourceLoadPriority, RetrieveCompletionHandler&&);
-    void revalidateEntry(std::unique_ptr<Entry>, const SubresourceInfo&, const GlobalFrameID&);
+    void retrieveEntryFromStorage(const SubresourceInfo&, RetrieveCompletionHandler&&);
+    void revalidateSubresource(const SubresourceInfo&, std::unique_ptr<Entry>, const GlobalFrameID&);
     bool satisfyPendingRequests(const Key&, Entry*);
     void retrieveSubresourcesEntry(const Key& storageKey, std::function<void (std::unique_ptr<SubresourcesEntry>)>&&);
     void startSpeculativeRevalidation(const GlobalFrameID&, SubresourcesEntry&);
index b924516..561e4e6 100644 (file)
 #include "Logging.h"
 #include "NetworkCacheCoders.h"
 
+using namespace std::chrono;
+
 namespace WebKit {
 namespace NetworkCache {
 
 void SubresourceInfo::encode(WTF::Persistence::Encoder& encoder) const
 {
     encoder << m_key;
+    encoder << m_lastSeen;
+    encoder << m_firstSeen;
     encoder << m_isTransient;
 
     // Do not bother serializing other data members of transient resources as they are empty.
@@ -52,13 +56,17 @@ bool SubresourceInfo::decode(WTF::Persistence::Decoder& decoder, SubresourceInfo
 {
     if (!decoder.decode(info.m_key))
         return false;
-
+    if (!decoder.decode(info.m_lastSeen))
+        return false;
+    if (!decoder.decode(info.m_firstSeen))
+        return false;
+    
     if (!decoder.decode(info.m_isTransient))
         return false;
-
+    
     if (info.m_isTransient)
         return true;
-
+    
     if (!decoder.decode(info.m_firstPartyForCookies))
         return false;
 
@@ -103,17 +111,46 @@ SubresourcesEntry::SubresourcesEntry(const Storage::Record& storageEntry)
 {
     ASSERT(m_key.type() == "SubResources");
 }
-    
-static Vector<SubresourceInfo> makeSubresourceInfoVector(const Vector<std::unique_ptr<SubresourceLoad>>& subresourceLoads)
+
+SubresourceInfo::SubresourceInfo(const Key& key, const WebCore::ResourceRequest& request, const SubresourceInfo* previousInfo)
+    : m_key(key)
+    , m_lastSeen(std::chrono::system_clock::now())
+    , m_firstSeen(previousInfo ? previousInfo->firstSeen() : m_lastSeen)
+    , m_isTransient(!previousInfo)
+    , m_firstPartyForCookies(request.firstPartyForCookies())
+    , m_requestHeaders(request.httpHeaderFields())
+    , m_priority(request.priority())
+{
+}
+
+static Vector<SubresourceInfo> makeSubresourceInfoVector(const Vector<std::unique_ptr<SubresourceLoad>>& subresourceLoads, Vector<SubresourceInfo>* previousSubresources)
 {
     Vector<SubresourceInfo> result;
     result.reserveInitialCapacity(subresourceLoads.size());
     
-    HashSet<Key> seenKeys;
+    HashMap<Key, unsigned> previousMap;
+    if (previousSubresources) {
+        for (unsigned i = 0; i < previousSubresources->size(); ++i)
+            previousMap.add(previousSubresources->at(i).key(), i);
+    }
+
+    HashSet<Key> deduplicationSet;
     for (auto& load : subresourceLoads) {
-        if (!seenKeys.add(load->key).isNewEntry)
+        if (!deduplicationSet.add(load->key).isNewEntry)
             continue;
-        result.uncheckedAppend({ load->key, load->request });
+        
+        SubresourceInfo* previousInfo = nullptr;
+        if (previousSubresources) {
+            auto it = previousMap.find(load->key);
+            if (it != previousMap.end())
+                previousInfo = &(*previousSubresources)[it->value];
+        }
+        
+        result.uncheckedAppend({ load->key, load->request, previousInfo });
+        
+        // FIXME: We should really consider all resources seen for the first time transient.
+        if (!previousSubresources)
+            result.last().setNonTransient();
     }
 
     return result;
@@ -122,24 +159,14 @@ static Vector<SubresourceInfo> makeSubresourceInfoVector(const Vector<std::uniqu
 SubresourcesEntry::SubresourcesEntry(Key&& key, const Vector<std::unique_ptr<SubresourceLoad>>& subresourceLoads)
     : m_key(WTFMove(key))
     , m_timeStamp(std::chrono::system_clock::now())
-    , m_subresources(makeSubresourceInfoVector(subresourceLoads))
+    , m_subresources(makeSubresourceInfoVector(subresourceLoads, nullptr))
 {
     ASSERT(m_key.type() == "SubResources");
 }
-
+    
 void SubresourcesEntry::updateSubresourceLoads(const Vector<std::unique_ptr<SubresourceLoad>>& subresourceLoads)
 {
-    HashSet<Key> previousKeys;
-    for (auto& info : m_subresources)
-        previousKeys.add(info.key());
-    
-    m_subresources = makeSubresourceInfoVector(subresourceLoads);
-
-    // Mark keys that are not common with the last load as transient.
-    for (auto& subresourceInfo : m_subresources) {
-        if (!previousKeys.contains(subresourceInfo.key()))
-            subresourceInfo.setTransient();
-    }
+    m_subresources = makeSubresourceInfoVector(subresourceLoads, &m_subresources);
 }
 
 } // namespace WebKit
index 73db90e..c2d5da8 100644 (file)
@@ -43,24 +43,23 @@ public:
     static bool decode(WTF::Persistence::Decoder&, SubresourceInfo&);
 
     SubresourceInfo() = default;
-    SubresourceInfo(const Key& key, const WebCore::ResourceRequest& request)
-        : m_key(key)
-        , m_firstPartyForCookies(request.firstPartyForCookies())
-        , m_requestHeaders(request.httpHeaderFields())
-        , m_priority(request.priority())
-    {
-    }
+    SubresourceInfo(const Key&, const WebCore::ResourceRequest&, const SubresourceInfo* previousInfo);
 
     const Key& key() const { return m_key; }
+    std::chrono::system_clock::time_point lastSeen() const { return m_lastSeen; }
+    std::chrono::system_clock::time_point firstSeen() const { return m_firstSeen; }
+
     bool isTransient() const { return m_isTransient; }
     const WebCore::URL& firstPartyForCookies() const { ASSERT(!m_isTransient); return m_firstPartyForCookies; }
     const WebCore::HTTPHeaderMap& requestHeaders() const { ASSERT(!m_isTransient); return m_requestHeaders; }
     WebCore::ResourceLoadPriority priority() const { ASSERT(!m_isTransient); return m_priority; }
     
-    void setTransient() { m_isTransient = true; }
+    void setNonTransient() { m_isTransient = false; }
 
 private:
     Key m_key;
+    std::chrono::system_clock::time_point m_lastSeen;
+    std::chrono::system_clock::time_point m_firstSeen;
     bool m_isTransient { false };
     WebCore::URL m_firstPartyForCookies;
     WebCore::HTTPHeaderMap m_requestHeaders;