tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit c9e18bdf3b9c591c2cac70f7880802b68a61081e
parent 17923efc19a4f53c0eed7532f8b50617d16782fa
Author: Randell Jesup <rjesup@mozilla.com>
Date:   Wed,  1 Oct 2025 18:45:59 +0000

Bug 1917974: Hook in Prefetching of cache entries for Compression Dictionaries r=necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D258165

Diffstat:
Mnetwerk/cache2/CacheFileIOManager.cpp | 12+++++++++---
Mnetwerk/cache2/CacheFileIOManager.h | 4++++
Mnetwerk/cache2/CacheStorageService.cpp | 3++-
Mnetwerk/cache2/CacheStorageService.h | 6++++--
Mnetwerk/cache2/Dictionary.cpp | 32+++++++++++++++++++++-----------
Mnetwerk/cache2/Dictionary.h | 5+++--
Mnetwerk/protocol/http/nsHttpChannel.cpp | 67+++++++++++++++++++++++++++++++++++--------------------------------
Mnetwerk/protocol/http/nsHttpHandler.cpp | 54+++++++++++++++++++++++++++++-------------------------
Mnetwerk/protocol/http/nsHttpHandler.h | 2+-
9 files changed, 108 insertions(+), 77 deletions(-)

diff --git a/netwerk/cache2/CacheFileIOManager.cpp b/netwerk/cache2/CacheFileIOManager.cpp @@ -1872,7 +1872,7 @@ nsresult CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash* aHash, if (exists) { // If this file has been found evicted through the context file evictor // above for any of pinned or non-pinned state, these calls ensure we doom - // the handle ASAP we know the real pinning state after metadta has been + // the handle ASAP we know the real pinning state after metadata has been // parsed. DoomFileInternal on the |handle| doesn't doom right now, since // the pinning state is unknown and we pass down a pinning restriction. if (evictedAsPinned) { @@ -2512,6 +2512,11 @@ nsresult CacheFileIOManager::DoomFileInternal( } if (!aHandle->IsSpecialFile()) { + // Remove from Dictionary cache if this is a dictionary + if (!mDictionaryCache) { + // mDictionaryCache = DictionaryCache::GetInstance(); + } + // mDictionaryCache->RemoveDictionaryFor(aHandle->mKey); CacheIndex::RemoveEntry(aHandle->Hash()); } @@ -2525,7 +2530,7 @@ nsresult CacheFileIOManager::DoomFileInternal( CacheFileUtils::ParseKey(aHandle->Key(), &idExtension, &url); MOZ_ASSERT(info); if (info) { - storageService->CacheFileDoomed(info, idExtension, url); + storageService->CacheFileDoomed(aHandle->mKey, info, idExtension, url); } } } @@ -3515,7 +3520,8 @@ nsresult CacheFileIOManager::EvictByContextInternal( } // Filter by origin. - if (!origin.IsEmpty()) { + if (!origin.IsEmpty()) { // XXX also look for dict:<origin>, or let that + // be handled by Doom? Probably Doom RefPtr<MozURL> url; rv = MozURL::Init(getter_AddRefs(url), uriSpec); if (NS_FAILED(rv)) { diff --git a/netwerk/cache2/CacheFileIOManager.h b/netwerk/cache2/CacheFileIOManager.h @@ -20,6 +20,7 @@ #include "nsString.h" #include "nsTHashtable.h" #include "prio.h" +#include "Dictionary.h" // #define DEBUG_HANDLES 1 #if !defined(MOZ_WIDGET_ANDROID) @@ -481,6 +482,9 @@ class CacheFileIOManager final : public nsITimerCallback, public nsINamed { static StaticRefPtr<CacheFileIOManager> gInstance; + // Pointer to DictionaryCache singleton + RefPtr<DictionaryCache> mDictionaryCache; + TimeStamp mStartTime; // Set true on the IO thread, CLOSE level as part of the internal shutdown // procedure. diff --git a/netwerk/cache2/CacheStorageService.cpp b/netwerk/cache2/CacheStorageService.cpp @@ -2057,7 +2057,8 @@ nsresult CacheStorageService::WalkStorageEntries( return event->Walk(); } -void CacheStorageService::CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo, +void CacheStorageService::CacheFileDoomed(const nsACString& aKey, + nsILoadContextInfo* aLoadContextInfo, const nsACString& aIdExtension, const nsACString& aURISpec) { nsAutoCString contextKey; diff --git a/netwerk/cache2/CacheStorageService.h b/netwerk/cache2/CacheStorageService.h @@ -285,9 +285,11 @@ class CacheStorageService final : public nsICacheStorageService, /** * CacheFileIOManager uses this method to notify CacheStorageService that * an active entry was removed. This method is called even if the entry - * removal was originated by CacheStorageService. + * removal was originated by CacheStorageService. This also removes the entry + * from the DictionaryCache. */ - void CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo, + void CacheFileDoomed(const nsACString& aKey, + nsILoadContextInfo* aLoadContextInfo, const nsACString& aIdExtension, const nsACString& aURISpec); diff --git a/netwerk/cache2/Dictionary.cpp b/netwerk/cache2/Dictionary.cpp @@ -59,6 +59,8 @@ using namespace mozilla; namespace mozilla { namespace net { +// Access to all these classes is from MainThread unless otherwise specified + LazyLogModule gDictionaryLog("CompressionDictionaries"); #define DICTIONARY_LOG(args) \ @@ -146,8 +148,9 @@ void DictionaryCacheEntry::UseCompleted() { } // returns aShouldSuspend=true if we should suspend to wait for the prefetch -bool DictionaryCacheEntry::Prefetch(nsILoadContextInfo* aLoadContextInfo, - const std::function<void()>& aFunc) { +nsresult DictionaryCacheEntry::Prefetch(nsILoadContextInfo* aLoadContextInfo, + bool& aShouldSuspend, + const std::function<void()>& aFunc) { DICTIONARY_LOG(("Prefetch for %s", mURI.get())); // Start reading the cache entry into memory and call completion // function when done @@ -159,28 +162,36 @@ bool DictionaryCacheEntry::Prefetch(nsILoadContextInfo* aLoadContextInfo, nsCOMPtr<nsICacheStorageService> cacheStorageService( components::CacheStorage::Service()); if (!cacheStorageService) { - return false; + aShouldSuspend = false; + return NS_ERROR_FAILURE; } nsCOMPtr<nsICacheStorage> cacheStorage; nsresult rv = cacheStorageService->DiskCacheStorage( aLoadContextInfo, getter_AddRefs(cacheStorage)); if (NS_FAILED(rv)) { - return false; + aShouldSuspend = false; + return NS_ERROR_FAILURE; + } + if (NS_FAILED(cacheStorage->AsyncOpenURIString( + mURI, ""_ns, nsICacheStorage::OPEN_READONLY, this))) { + // For some reason the cache no longer has this entry; + aShouldSuspend = false; + return NS_ERROR_FAILURE; } - // Add before we schedule! mWaitingPrefetch.AppendElement(aFunc); - cacheStorage->AsyncOpenURIString(mURI, ""_ns, - nsICacheStorage::OPEN_READONLY, this); DICTIONARY_LOG(("Started Prefetch for %s, anonymous=%d", mURI.get(), aLoadContextInfo->IsAnonymous())); - return true; + aShouldSuspend = true; + return NS_OK; } DICTIONARY_LOG(("Prefetch for %s - already have data in memory (%u users)", mURI.get(), mUsers)); - return false; + aShouldSuspend = false; + return NS_OK; } DICTIONARY_LOG(("Prefetch for %s - already waiting", mURI.get())); - return true; + aShouldSuspend = true; + return NS_OK; } void DictionaryCacheEntry::AccumulateHash(const char* aBuf, int32_t aCount) { @@ -620,7 +631,6 @@ NS_IMETHODIMP DictionaryOriginReader::OnDataAvailable(nsIRequest* request, nsIInputStream* aInputStream, uint64_t aOffset, uint32_t aCount) { - uint32_t n; DICTIONARY_LOG( ("DictionaryOriginReader %p OnDataAvailable %u", this, aCount)); return NS_OK; diff --git a/netwerk/cache2/Dictionary.h b/netwerk/cache2/Dictionary.h @@ -16,6 +16,7 @@ #include "nsIObserver.h" #include "nsIStreamListener.h" #include "mozilla/RefPtr.h" +#include "mozilla/Vector.h" #include "nsString.h" #include "nsTArray.h" #include "mozilla/TimeStamp.h" @@ -65,8 +66,8 @@ class DictionaryCacheEntry final : public nsICacheEntryOpenCallback, // Start reading the cache entry into memory and call completion // function when done - bool Prefetch(nsILoadContextInfo* aLoadContextInfo, - const std::function<void()>& aFunc); + nsresult Prefetch(nsILoadContextInfo* aLoadContextInfo, bool& aShouldSuspend, + const std::function<void()>& aFunc); const nsACString& GetHash() const { return mHash; } diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp @@ -547,38 +547,6 @@ nsresult nsHttpChannel::Init(nsIURI* uri, uint32_t caps, nsProxyInfo* proxyInfo, proxyURI, channelId, aLoadInfo); LOG1(("nsHttpChannel::Init [this=%p]\n", this)); - if (NS_FAILED(rv)) return rv; - // This may be async; the dictionary headers may need to fetch an origin - // dictionary cache entry from disk before adding the headers. We can - // continue with channel creation, and just block on this being done later - rv = gHttpHandler->AddAcceptAndDictionaryHeaders( - uri, &mRequestHead, IsHTTPS(), - [self = RefPtr(this)](DictionaryCacheEntry* aDict) { - self->mDictDecompress = aDict; - if (self->mDictDecompress) { - LOG_DICTIONARIES( - ("Added dictionary header for %p, DirectoryCacheEntry %p", - self.get(), aDict)); - // mDictDecompress is set if we added Available-Dictionary - self->mDictDecompress->InUse(); - self->mUsingDictionary = true; - self->mShouldSuspendForDictionary = self->mDictDecompress->Prefetch( - GetLoadContextInfo(self), [self]() { - // this is called when the prefetch is complete to - // un-Suspend the channel - MOZ_ASSERT(self->mDictDecompress->DictionaryReady()); - if (self->mSuspendedForDictionary) { - LOG( - ("nsHttpChannel::Init [this=%p] Resuming channel " - "suspended for " - "Dictionary", - self.get())); - self->mSuspendedForDictionary = false; - self->Resume(); - } - }); - } - }); return rv; } @@ -1988,6 +1956,41 @@ nsresult nsHttpChannel::SetupChannelForTransaction() { MOZ_ASSERT(NS_SUCCEEDED(rv)); } } + } else { + // This may be async; the dictionary headers may need to fetch an origin + // dictionary cache entry from disk before adding the headers. We can + // continue with channel creation, and just block on this being done later + rv = gHttpHandler->AddAcceptAndDictionaryHeaders( + mURI, &mRequestHead, IsHTTPS(), + [self = RefPtr(this)](DictionaryCacheEntry* aDict) { + self->mDictDecompress = aDict; + if (self->mDictDecompress) { + LOG_DICTIONARIES( + ("Added dictionary header for %p, DirectoryCacheEntry %p", + self.get(), aDict)); + // mDictDecompress is set if we added Available-Dictionary + self->mDictDecompress->InUse(); + self->mUsingDictionary = true; + return NS_SUCCEEDED(self->mDictDecompress->Prefetch( + GetLoadContextInfo(self), self->mShouldSuspendForDictionary, + [self]() { + // this is called when the prefetch is complete to + // un-Suspend the channel + MOZ_ASSERT(self->mDictDecompress->DictionaryReady()); + if (self->mSuspendedForDictionary) { + LOG( + ("nsHttpChannel::SetupChannelForTransaction [this=%p] " + "Resuming channel " + "suspended for Dictionary", + self.get())); + self->mSuspendedForDictionary = false; + self->Resume(); + } + })); + } + return true; + }); + if (NS_FAILED(rv)) return rv; } // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer. diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp @@ -656,15 +656,12 @@ nsresult nsHttpHandler::InitConnectionMgr() { // set by Fetch from the old request nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( nsIURI* aURI, nsHttpRequestHead* aRequest, bool aSecure, - const std::function<void(DictionaryCacheEntry*)>& aCallback) { + const std::function<bool(DictionaryCacheEntry*)>& aCallback) { LOG(("Adding Dictionary headers")); nsresult rv = NS_OK; // Add the "Accept-Encoding" header and possibly Dictionary headers if (aSecure) { // The dictionary info may require us to check the cache. - // XXX This would require that AddAcceptAndDictionaryHeaders be effectively - // async, perhaps by passing a lambda to call AddAcceptAndDictionaryHeaders - // and then unblock the request if (StaticPrefs::network_http_dictionaries_enable()) { mDictionaryCache->GetDictionaryFor( aURI, [self = RefPtr(this), aRequest, @@ -684,26 +681,6 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( nsAutoCStringN<64> encodedHash = ":"_ns + aDict->GetHash() + ":"_ns; - LOG_DICTIONARIES( - ("Setting Available-Dictionary: %s", encodedHash.get())); - rv = aRequest->SetHeader( - nsHttp::Available_Dictionary, encodedHash, false, - nsHttpHeaderArray::eVarietyRequestOverride); - if (NS_FAILED(rv)) { - (aCallback)(nullptr); - return rv; - } - if (!aDict->GetId().IsEmpty()) { - LOG_DICTIONARIES(("Setting Dictionary-Id: %s", - PromiseFlatCString(aDict->GetId()).get())); - rv = aRequest->SetHeader( - nsHttp::Dictionary_Id, aDict->GetId(), false, - nsHttpHeaderArray::eVarietyRequestOverride); - if (NS_FAILED(rv)) { - (aCallback)(nullptr); - return rv; - } - } // Need to retain access to the dictionary until the request // completes. Note that this includes if the dictionary we offered // gets replaced by another request while we're waiting for a @@ -711,7 +688,34 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( // dictionary into memory before overwriting it and store in dict // temporarily. aRequest->SetDictionary(aDict); - (aCallback)(aDict); + + // We want to make sure that the cache entry doesn't disappear out + // from under us if we set the header, so do the callback to + // Prefetch() the entry before adding the headers (so we don't + // have to remove the headers if Prefetch() fails). It might fail + // if something is asynchronously Dooming the entry but the + // DictionaryCache hasn't been updated to remove the entry yet (or + // any other thing that were to desynchronize the DictionaryCache + // with the actual cache. + if ((aCallback)(aDict)) { + LOG_DICTIONARIES( + ("Setting Available-Dictionary: %s", encodedHash.get())); + rv = aRequest->SetHeader( + nsHttp::Available_Dictionary, encodedHash, false, + nsHttpHeaderArray::eVarietyRequestOverride); + if (NS_FAILED(rv)) { + return rv; + } + if (!aDict->GetId().IsEmpty()) { + nsCString id(nsPrintfCString("\"%s\"", aDict->GetId().get())); + rv = aRequest->SetHeader( + nsHttp::Dictionary_Id, aDict->GetId(), false, + nsHttpHeaderArray::eVarietyRequestOverride); + if (NS_FAILED(rv)) { + return rv; + } + } + } return NS_OK; } rv = aRequest->SetHeader( diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h @@ -119,7 +119,7 @@ class nsHttpHandler final : public nsIHttpProtocolHandler, [[nodiscard]] nsresult AddAcceptAndDictionaryHeaders( nsIURI* aURI, nsHttpRequestHead* aRequest, bool aSecure, - const std::function<void(DictionaryCacheEntry*)>& aCallback); + const std::function<bool(DictionaryCacheEntry*)>& aCallback); [[nodiscard]] nsresult AddStandardRequestHeaders( nsHttpRequestHead*, nsIURI* aURI, ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting);