commit e0dc9ce060fefe24961f2e9be125c5639c6b5a8b
parent 7c8c5134d575dd46e404bb746ff985813a12c273
Author: Randell Jesup <rjesup@mozilla.com>
Date: Tue, 7 Oct 2025 17:55:59 +0000
Bug 1978494: Don't use stale dictionaries, more logs, improve handling of dictionary origin reading r=necko-reviewers,valentin
Differential Revision: https://phabricator.services.mozilla.com/D261795
Diffstat:
6 files changed, 266 insertions(+), 153 deletions(-)
diff --git a/netwerk/cache2/Dictionary.cpp b/netwerk/cache2/Dictionary.cpp
@@ -4,6 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <algorithm>
+#include <stdlib.h>
#include "Dictionary.h"
@@ -89,15 +90,24 @@ static nsresult GetDictPath(nsIURI* aURI, nsACString& aPrePath) {
return NS_OK;
}
-DictionaryCacheEntry::DictionaryCacheEntry(const char* aKey) { mURI = aKey; }
+DictionaryCacheEntry::DictionaryCacheEntry(const char* aKey) {
+ mURI = aKey;
+ DICTIONARY_LOG(("Created DictionaryCacheEntry %p, uri=%s", this, aKey));
+}
DictionaryCacheEntry::DictionaryCacheEntry(const nsACString& aURI,
const nsACString& aPattern,
nsTArray<nsCString>& aMatchDest,
const nsACString& aId,
+ uint32_t aExpiration,
const Maybe<nsCString>& aHash)
- : mURI(aURI), mPattern(aPattern), mId(aId) {
+ : mURI(aURI), mExpiration(aExpiration), mPattern(aPattern), mId(aId) {
ConvertMatchDestToEnumArray(aMatchDest, mMatchDest);
+ DICTIONARY_LOG(
+ ("Created DictionaryCacheEntry %p, uri=%s, pattern=%s, id=%s, "
+ "expiration=%u",
+ this, PromiseFlatCString(aURI).get(), PromiseFlatCString(aPattern).get(),
+ PromiseFlatCString(aId).get(), aExpiration));
if (aHash) {
mHash = aHash.value();
}
@@ -123,9 +133,11 @@ void DictionaryCacheEntry::ConvertMatchDestToEnumArray(
aMatchEnums.SwapElements(temp);
}
-// returns true if the pattern for the dictionary matches the path given
+// Returns true if the pattern for the dictionary matches the path given.
+// Note: we need to verify that this entry has not expired due to 2.2.1 of
+// https://datatracker.ietf.org/doc/draft-ietf-httpbis-compression-dictionary/
bool DictionaryCacheEntry::Match(const nsACString& aFilePath,
- ExtContentPolicyType aType,
+ ExtContentPolicyType aType, uint32_t aNow,
uint32_t& aLongest) {
if (mHash.IsEmpty()) {
// We don't have the file yet
@@ -137,9 +149,11 @@ bool DictionaryCacheEntry::Match(const nsACString& aFilePath,
return false;
}
// Not worth checking if we wouldn't use it
- DICTIONARY_LOG(("Match: %s to %s, %s", PromiseFlatCString(aFilePath).get(),
- mPattern.get(), NS_CP_ContentTypeName(aType)));
- if (mPattern.Length() > aLongest) {
+ DICTIONARY_LOG(("Match: %p %s to %s, %s (now=%u, expiration=%u)", this,
+ PromiseFlatCString(aFilePath).get(), mPattern.get(),
+ NS_CP_ContentTypeName(aType), aNow, mExpiration));
+ if ((mExpiration == 0 || aNow < mExpiration) &&
+ mPattern.Length() > aLongest) {
// Need to match using match-dest, if it exists
if (mMatchDest.IsEmpty() ||
mMatchDest.IndexOf(
@@ -170,7 +184,12 @@ bool DictionaryCacheEntry::Match(const nsACString& aFilePath,
DICTIONARY_LOG(("Match: %s (longest %u)", mURI.get(), aLongest));
return true;
}
+ } else {
+ DICTIONARY_LOG((" Failed on matchDest"));
}
+ } else {
+ DICTIONARY_LOG(
+ (" Failed due to expiration: %u vs %u", aNow, mExpiration));
}
return false;
}
@@ -304,7 +323,10 @@ void DictionaryCacheEntry::FinishHash() {
if (mOrigin) {
DICTIONARY_LOG(("Write on hash"));
// This will also move us from mPendingEntries to mEntries
- mOrigin->Write(this);
+ if (NS_FAILED(mOrigin->Write(this))) {
+ mOrigin->RemoveEntry(this);
+ return;
+ }
if (!mBlocked) {
mOrigin->FinishAddEntry(this);
}
@@ -326,7 +348,8 @@ static const uint32_t METADATA_VERSION = 1;
// ** match-dest CString list, terminated by empty string
// *** CString: Match-dest
// ** CString: Id
-// ** CString: type
+// ** uint32 as a CString: expiration. If missing, 0 (none)
+// ** CString: type -- defaults to 'raw' if missing
// We store strings with a delimiter, and use escapes for delimiters or escape
// characters in the source strings.
//
@@ -367,6 +390,10 @@ void DictionaryCacheEntry::MakeMetadataEntry(nsCString& aNewValue) {
}
// List of match-dest values is terminated by an empty string
EscapeMetadataString(""_ns, aNewValue);
+ // Expiration time, as a CString
+ nsAutoCStringN<12> expiration;
+ expiration = nsPrintfCString("%u", mExpiration);
+ EscapeMetadataString(expiration, aNewValue);
// We don't store type, since we only support type 'raw' We can support
// type in the future by considering missing type as raw without changing the
// format
@@ -432,7 +459,12 @@ bool DictionaryCacheEntry::ParseMetadata(const char* aSrc) {
}
}
} while (!temp.IsEmpty());
- // XXX type
+ if (*aSrc == '|') {
+ char* newSrc;
+ mExpiration = strtoul(++aSrc, &newSrc, 10);
+ aSrc = newSrc;
+ } // else leave default of 0
+ // XXX type - we assume and only support 'raw', may be missing
aSrc = GetEncodedString(aSrc, temp);
DICTIONARY_LOG(
@@ -590,31 +622,26 @@ DictionaryCacheEntry::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
//----------------------------------------------------------------------------------
-class DictionaryOriginReader final : public nsICacheEntryOpenCallback,
- public nsIStreamListener {
- NS_DECL_THREADSAFE_ISUPPORTS
- NS_DECL_NSICACHEENTRYOPENCALLBACK
- NS_DECL_NSIREQUESTOBSERVER
- NS_DECL_NSISTREAMLISTENER
-
- DictionaryOriginReader() {}
-
- // If aOrigin is nullptr, we're just reading; if it's set then we're adding
- // a Dictionary to a maybe-existing Origin and we need to create it if it
- // doesn't exist yet.
- void Start(DictionaryOrigin* aOrigin, nsACString& aKey, nsIURI* aURI,
- ExtContentPolicyType aType, DictionaryCache* aCache,
- const std::function<nsresult(DictionaryCacheEntry*)>& aCallback) {
- mOrigin = aOrigin;
- mURI = aURI;
- mType = aType;
- mCallback = aCallback;
- mCache = aCache;
- mSelf = this; // keep this alive until we call aCallback
-
- // The cache entry is for originattribute extension of
- // META_DICTIONARY_PREFIX, plus key of prepath
-
+// Read the metadata for an Origin and parse it, creating DictionaryCacheEntrys
+// as needed. If aType is TYPE_OTHER, there is no Match() to do
+void DictionaryOriginReader::Start(
+ DictionaryOrigin* aOrigin, nsACString& aKey, nsIURI* aURI,
+ ExtContentPolicyType aType, DictionaryCache* aCache,
+ const std::function<nsresult(bool, DictionaryCacheEntry*)>& aCallback) {
+ mOrigin = aOrigin;
+ mURI = aURI;
+ mType = aType;
+ mCallback = aCallback;
+ mCache = aCache;
+
+ // The cache entry is for originattribute extension of
+ // META_DICTIONARY_PREFIX, plus key of prepath
+
+ // This also keeps this alive until we get the callback. We must do this
+ // BEFORE we call AsyncOpenURIString, or we may get a callback to
+ // OnCacheEntryAvailable before we can do this
+ mOrigin->mWaitingCacheRead.AppendElement(this);
+ if (mOrigin->mWaitingCacheRead.Length() == 1) { // was empty
DICTIONARY_LOG(("DictionaryOriginReader::Start(%s): %p",
PromiseFlatCString(aKey).get(), this));
DictionaryCache::sCacheStorage->AsyncOpenURIString(
@@ -625,18 +652,23 @@ class DictionaryOriginReader final : public nsICacheEntryOpenCallback,
: nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
nsICacheStorage::CHECK_MULTITHREADED,
this);
+ // This one will get the direct callback to do Match()
}
+ // Else we already have a read for this cache entry pending, just wait
+ // for that
+}
- private:
- ~DictionaryOriginReader() {}
-
- RefPtr<DictionaryOrigin> mOrigin;
- nsCOMPtr<nsIURI> mURI;
- ExtContentPolicyType mType;
- std::function<nsresult(DictionaryCacheEntry*)> mCallback;
- RefPtr<DictionaryCache> mCache;
- RefPtr<DictionaryOriginReader> mSelf;
-};
+void DictionaryOriginReader::FinishMatch() {
+ RefPtr<DictionaryCacheEntry> result;
+ // Don't Match if this was a call from AddEntry()
+ if (mType != ExtContentPolicy::TYPE_OTHER) {
+ nsCString path;
+ mURI->GetPathQueryRef(path);
+ result = mOrigin->Match(path, mType);
+ }
+ DICTIONARY_LOG(("Done with reading origin for %p", mOrigin.get()));
+ (mCallback)(true, result);
+}
NS_IMPL_ISUPPORTS(DictionaryOriginReader, nsICacheEntryOpenCallback,
nsIStreamListener)
@@ -665,47 +697,24 @@ NS_IMETHODIMP DictionaryOriginReader::OnCacheEntryAvailable(
if (!aCacheEntry) {
// Didn't have any dictionaries for this origin, and must have been readonly
- (mCallback)(nullptr);
+ for (auto& reader : mOrigin->mWaitingCacheRead) {
+ (reader->mCallback)(true, nullptr);
+ }
+ mOrigin->mWaitingCacheRead.Clear();
return NS_OK;
}
- // If we have an mOrigin, then it's already been created and inserted
- if (mOrigin) {
- mOrigin->SetCacheEntry(aCacheEntry);
- // There's no data in the cache entry, just metadata
- nsCOMPtr<nsICacheEntryMetaDataVisitor> metadata(mOrigin);
- aCacheEntry->VisitMetaData(metadata);
-
- // We only have mOrigin if we're creating it in AddEntry(), so no
- // need to Match()
- (mCallback)(nullptr);
- mSelf = nullptr; // this will delete 'this'
- return NS_OK;
- }
+ mOrigin->SetCacheEntry(aCacheEntry);
+ // There's no data in the cache entry, just metadata
+ nsCOMPtr<nsICacheEntryMetaDataVisitor> metadata(mOrigin);
+ aCacheEntry->VisitMetaData(metadata);
- nsCString prepath;
- if (NS_FAILED(GetDictPath(mURI, prepath))) {
- (mCallback)(nullptr);
- return NS_ERROR_FAILURE;
+ // This list is the only thing keeping us alive
+ RefPtr<DictionaryOriginReader> safety(this);
+ for (auto& reader : mOrigin->mWaitingCacheRead) {
+ reader->FinishMatch();
}
- // Add the Origin to the hash in DictionaryCache
- Unused << mCache->mDictionaryCache.WithEntryHandle(
- prepath, [&](auto&& entry) {
- auto& origin = entry.OrInsertWith(
- [&] { return new DictionaryOrigin(prepath, aCacheEntry); });
-
- // There's no data in the cache entry, just metadata
- nsCOMPtr<nsICacheEntryMetaDataVisitor> metadata(origin);
- aCacheEntry->VisitMetaData(metadata);
-
- nsCString path;
- mURI->GetPathQueryRef(path);
- RefPtr<DictionaryCacheEntry> result = origin->Match(path, mType);
-
- (mCallback)(result);
- mSelf = nullptr; // this will delete 'this'
- return NS_OK;
- });
+ mOrigin->mWaitingCacheRead.Clear();
return NS_OK;
}
@@ -766,13 +775,18 @@ nsresult DictionaryCache::AddEntry(nsIURI* aURI, const nsACString& aKey,
nsTArray<nsCString>& aMatchDest,
const nsACString& aId,
const Maybe<nsCString>& aHash,
- bool aNewEntry,
+ bool aNewEntry, uint32_t aExpiration,
DictionaryCacheEntry** aDictEntry) {
// Note that normally we're getting an entry in and until all the data
// has been received, we can't use it. The Hash being null is a flag
// that it's not yet valid.
- RefPtr<DictionaryCacheEntry> dict =
- new DictionaryCacheEntry(aKey, aPattern, aMatchDest, aId, aHash);
+ DICTIONARY_LOG(("AddEntry for %s, pattern %s, id %s, expiration %u",
+ PromiseFlatCString(aKey).get(),
+ PromiseFlatCString(aPattern).get(),
+ PromiseFlatCString(aId).get(), aExpiration));
+ // Note that we don't know if there's an entry for this key in the origin
+ RefPtr<DictionaryCacheEntry> dict = new DictionaryCacheEntry(
+ aKey, aPattern, aMatchDest, aId, aExpiration, aHash);
dict = AddEntry(aURI, aNewEntry, dict);
if (dict) {
*aDictEntry = do_AddRef(dict).take();
@@ -793,6 +807,8 @@ already_AddRefed<DictionaryCacheEntry> DictionaryCache::AddEntry(
if (NS_FAILED(GetDictPath(aURI, prepath))) {
return nullptr;
}
+ DICTIONARY_LOG(
+ ("AddEntry: %s, %d, %p", prepath.get(), aNewEntry, aDictEntry));
// create for the origin if it doesn't exist
RefPtr<DictionaryCacheEntry> newEntry;
Unused << mDictionaryCache.WithEntryHandle(prepath, [&](auto&& entry) {
@@ -811,17 +827,21 @@ already_AddRefed<DictionaryCacheEntry> DictionaryCache::AddEntry(
reader->Start(
origin, prepath, aURI, ExtContentPolicy::TYPE_OTHER, this,
[entry = RefPtr(aDictEntry)](
- DictionaryCacheEntry*
- aDict) { // XXX avoid so many lambdas which cause allocations
+ bool, DictionaryCacheEntry* aDict) { // XXX avoid so many lambdas
+ // which cause allocations
// Write the dirty entry we couldn't write before once
// we get the hash
aDictEntry->WriteOnHash();
return NS_OK;
});
+ // Since this is read asynchronously, we need to either add the entry
+ // async once the read is done and it's populated, or we have to handle
+ // collisions on the read
return origin;
});
newEntry = origin->AddEntry(aDictEntry, aNewEntry);
+ DICTIONARY_LOG(("AddEntry: added %s", prepath.get()));
return NS_OK;
});
return newEntry.forget();
@@ -942,35 +962,48 @@ void DictionaryCache::RemoveAllDictionaries() {
// Once we have a DictionaryOrigin (in-memory or parsed), scan it for matches.
// If it's not in the cache, return nullptr via callback.
void DictionaryCache::GetDictionaryFor(
- nsIURI* aURI, ExtContentPolicyType aType,
- const std::function<nsresult(DictionaryCacheEntry*)>& aCallback) {
+ nsIURI* aURI, ExtContentPolicyType aType, bool& aAsync,
+ const std::function<nsresult(bool, DictionaryCacheEntry*)>& aCallback) {
+ aAsync = false;
// Note: IETF 2.2.3 Multiple Matching Directories
// We need to return match-dest matches first
// If no match-dest, then the longest match
nsCString prepath;
if (NS_FAILED(GetDictPath(aURI, prepath))) {
- (aCallback)(nullptr);
+ (aCallback)(false, nullptr);
return;
}
- if (auto origin = mDictionaryCache.Lookup(prepath)) {
- // Find the longest match
- nsCString path;
- RefPtr<DictionaryCacheEntry> result;
-
- aURI->GetPathQueryRef(path);
- DICTIONARY_LOG(("GetDictionaryFor(%s %s)", prepath.get(), path.get()));
-
- result = origin.Data()->Match(path, aType);
- (aCallback)(result);
+ // Match immediately if we've already created the origin and read any
+ // metadata
+ if (auto existing = mDictionaryCache.Lookup(prepath)) {
+ if (existing.Data()->mWaitingCacheRead.IsEmpty()) {
+ // Find the longest match
+ nsCString path;
+ RefPtr<DictionaryCacheEntry> result;
+
+ aURI->GetPathQueryRef(path);
+ DICTIONARY_LOG(("GetDictionaryFor(%s %s)", prepath.get(), path.get()));
+
+ result = existing.Data()->Match(path, aType);
+ (aCallback)(false, result);
+ } else {
+ DICTIONARY_LOG(
+ ("GetDictionaryFor(%s): Waiting for metadata read to match",
+ prepath.get()));
+ // Wait for the metadata read to complete
+ RefPtr<DictionaryOriginReader> reader = new DictionaryOriginReader();
+ reader->Start(existing.Data(), prepath, aURI, aType, this, aCallback);
+ aAsync = true;
+ }
return;
}
- // We don't have an entry at all. We need to check if there's an
- // entry on disk for <origin>, unless we know we have all entries
- // in the memory cache.
+ // We don't have an entry at all. We need to check if there's an entry
+ // on disk for <origin>, unless we know we have all entries in the memory
+ // cache.
// Handle unknown origins by checking the disk cache
if (!sCacheStorage) {
- (aCallback)(nullptr); // in case we have no disk storage
+ (aCallback)(false, nullptr); // in case we have no disk storage
return;
}
@@ -985,41 +1018,56 @@ void DictionaryCache::GetDictionaryFor(
sCacheStorage->Exists(prepathURI, META_DICTIONARY_PREFIX, &exists)) &&
exists) {
// To keep track of the callback, we need a new object to get the
- // OnCacheEntryAvailable can resolve the callback. It will keep a reference
- // to itself until it call aCallback after the cache read has resolved
+ // OnCacheEntryAvailable can resolve the callback.
DICTIONARY_LOG(("Reading %s for dictionary entries", prepath.get()));
+ RefPtr<DictionaryOrigin> origin = new DictionaryOrigin(prepath, nullptr);
+ // Add the origin to the list; we'll immediately start a reader which
+ // will set mWaitingCacheRead, so future GetDictionaryFor() calls
+ // will wait for the metadata to be read before doing Match()
+ mDictionaryCache.InsertOrUpdate(prepath, origin);
+
RefPtr<DictionaryOriginReader> reader = new DictionaryOriginReader();
// After Start(), if we drop this ref reader will kill itself on
// completion; it holds a self-ref
- reader->Start(nullptr, prepath, aURI, aType, this, aCallback);
- } else {
- // No dictionaries for origin
- (aCallback)(nullptr);
+ reader->Start(origin, prepath, aURI, aType, this, aCallback);
+ aAsync = true;
+ return;
}
+ // No dictionaries for origin
+ (aCallback)(false, nullptr);
}
+
//-----------------------------------------------------------------------------
// DictionaryOrigin
//-----------------------------------------------------------------------------
NS_IMPL_ISUPPORTS(DictionaryOrigin, nsICacheEntryMetaDataVisitor)
-void DictionaryOrigin::Write(DictionaryCacheEntry* aDictEntry) {
+nsresult DictionaryOrigin::Write(DictionaryCacheEntry* aDictEntry) {
DICTIONARY_LOG(("DictionaryOrigin::Write %s %p", mOrigin.get(), aDictEntry));
if (mEntry) {
- aDictEntry->Write(mEntry);
- } else {
- // Write it once DictionaryOriginReader creates the entry
- mDeferredWrites = true;
+ return aDictEntry->Write(mEntry);
}
+ // Write it once DictionaryOriginReader creates the entry
+ mDeferredWrites = true;
+ return NS_OK;
}
void DictionaryOrigin::SetCacheEntry(nsICacheEntry* aEntry) {
mEntry = aEntry;
if (mDeferredWrites) {
for (auto& entry : mEntries) {
- Write(entry);
+ if (NS_FAILED(Write(entry))) {
+ RemoveEntry(entry);
+ }
}
}
mDeferredWrites = false;
+ // Handle removes that were pending
+ for (auto& remove : mPendingRemove) {
+ DICTIONARY_LOG(("Pending RemoveEntry for %s", remove->mURI.get()));
+ remove->RemoveEntry(mEntry);
+ }
+ mPendingRemove.Clear();
}
already_AddRefed<DictionaryCacheEntry> DictionaryOrigin::AddEntry(
@@ -1128,7 +1176,13 @@ nsresult DictionaryOrigin::RemoveEntry(const nsACString& aKey) {
// Ensure it doesn't disappear on us
RefPtr<DictionaryCacheEntry> hold(dict);
mEntries.RemoveElement(dict);
- hold->RemoveEntry(mEntry);
+ if (mEntry) {
+ hold->RemoveEntry(mEntry);
+ } else {
+ // We don't have the cache entry yet. Defer the removal from
+ // the entry until we do
+ mPendingRemove.AppendElement(hold);
+ }
if (MOZ_UNLIKELY(
MOZ_LOG_TEST(gDictionaryLog, mozilla::LogLevel::Debug))) {
DumpEntries();
@@ -1184,9 +1238,10 @@ DictionaryCacheEntry* DictionaryOrigin::Match(const nsACString& aPath,
ExtContentPolicyType aType) {
uint32_t longest = 0;
DictionaryCacheEntry* result = nullptr;
+ uint32_t now = mozilla::net::NowInSeconds();
for (const auto& dict : mEntries) {
- if (dict->Match(aPath, aType, longest)) {
+ if (dict->Match(aPath, aType, now, longest)) {
result = dict;
}
}
@@ -1209,9 +1264,20 @@ nsresult DictionaryOrigin::OnMetaDataElement(const char* asciiKey,
DICTIONARY_LOG(("DictionaryOrigin::OnMetaDataElement %s %s",
asciiKey ? asciiKey : "", asciiValue));
+ // If we already have an entry for this key (pending or in the list),
+ // don't override it
+ for (auto& entry : mEntries) {
+ if (entry->GetURI().Equals(asciiKey)) {
+ return NS_OK;
+ }
+ }
+ for (auto& entry : mPendingEntries) {
+ if (entry->GetURI().Equals(asciiKey)) {
+ return NS_OK;
+ }
+ }
RefPtr<DictionaryCacheEntry> entry = new DictionaryCacheEntry(asciiKey);
if (entry->ParseMetadata(asciiValue)) {
- // XXX Check for duplicates?
mEntries.AppendElement(entry);
}
return NS_OK;
diff --git a/netwerk/cache2/Dictionary.h b/netwerk/cache2/Dictionary.h
@@ -60,6 +60,7 @@ class DictionaryCacheEntry final : public nsICacheEntryOpenCallback,
DictionaryCacheEntry(const char* aKey);
DictionaryCacheEntry(const nsACString& aKey, const nsACString& aPattern,
nsTArray<nsCString>& aMatchDest, const nsACString& aId,
+ uint32_t aExpiration = 0,
const Maybe<nsCString>& aHash = Nothing());
static void ConvertMatchDestToEnumArray(
@@ -68,7 +69,7 @@ class DictionaryCacheEntry final : public nsICacheEntryOpenCallback,
// returns true if the pattern for the dictionary matches the path given
bool Match(const nsACString& aFilePath, ExtContentPolicyType aType,
- uint32_t& aLongest);
+ uint32_t aNow, uint32_t& aLongest);
// This will fail if the cache entry is no longer available.
// Start reading the cache entry into memory and call completion
@@ -171,12 +172,14 @@ class DictionaryCacheEntry final : public nsICacheEntryOpenCallback,
void UnblockAddEntry(DictionaryOrigin* aOrigin);
private:
- nsCString mURI; // URI (without ref) for the dictionary
+ // URI (without ref) for the dictionary
+ nsCString mURI;
+ // Expiration time, or 0 for none (default)
+ uint32_t mExpiration{0};
+
nsCString mPattern;
nsCString mId; // max length 1024
nsTArray<dom::RequestDestination> mMatchDest;
- // XXX add type
-
// dcb and dcz use type 'raw'. We're allowed to ignore types we don't
// understand, so we can fail to record a dictionary with type != 'raw'
// nsCString mType;
@@ -224,7 +227,32 @@ class DictionaryCacheEntry final : public nsICacheEntryOpenCallback,
// memory for rarely used origins. We could have a limit for dictionaries, and
// above that switch to partial caching and empty entries for origins without.
-// XXX Clear all dictionaries when cookies are cleared for a site
+class DictionaryCache;
+
+class DictionaryOriginReader final : public nsICacheEntryOpenCallback,
+ public nsIStreamListener {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ DictionaryOriginReader() {}
+
+ void Start(
+ DictionaryOrigin* aOrigin, nsACString& aKey, nsIURI* aURI,
+ ExtContentPolicyType aType, DictionaryCache* aCache,
+ const std::function<nsresult(bool, DictionaryCacheEntry*)>& aCallback);
+ void FinishMatch();
+
+ private:
+ ~DictionaryOriginReader() {}
+
+ RefPtr<DictionaryOrigin> mOrigin;
+ nsCOMPtr<nsIURI> mURI;
+ ExtContentPolicyType mType;
+ std::function<nsresult(bool, DictionaryCacheEntry*)> mCallback;
+ RefPtr<DictionaryCache> mCache;
+};
// using DictCacheList = AutoCleanLinkedList<RefPtr<DictionaryCacheEntry>>;
using DictCacheList = nsTArray<RefPtr<DictionaryCacheEntry>>;
@@ -233,6 +261,7 @@ using DictCacheList = nsTArray<RefPtr<DictionaryCacheEntry>>;
// add this: public LinkedListElement<RefPtr<DictionaryOrigin>>,
class DictionaryOrigin : public nsICacheEntryMetaDataVisitor {
friend class DictionaryCache;
+ friend class DictionaryOriginReader;
public:
NS_DECL_THREADSAFE_ISUPPORTS
@@ -242,7 +271,7 @@ class DictionaryOrigin : public nsICacheEntryMetaDataVisitor {
: mOrigin(aOrigin), mEntry(aEntry) {}
void SetCacheEntry(nsICacheEntry* aEntry);
- void Write(DictionaryCacheEntry* aDictEntry);
+ nsresult Write(DictionaryCacheEntry* aDictEntry);
already_AddRefed<DictionaryCacheEntry> AddEntry(
DictionaryCacheEntry* aDictEntry, bool aNewEntry);
nsresult RemoveEntry(const nsACString& aKey);
@@ -261,11 +290,15 @@ class DictionaryOrigin : public nsICacheEntryMetaDataVisitor {
// Dictionaries currently being received. Once these get a Hash, move to
// mEntries
DictCacheList mPendingEntries;
+ // Dictionaries removed from mEntries but waiting to be removed from the
+ // Cache metadata
+ DictCacheList mPendingRemove;
// Write out all entries once we have a cacheentry
bool mDeferredWrites{false};
-};
-class DictionaryOriginReader;
+ // readers that are waiting for this origin's metadata to be read
+ nsTArray<RefPtr<DictionaryOriginReader>> mWaitingCacheRead;
+};
// singleton class
class DictionaryCache final {
@@ -290,7 +323,8 @@ class DictionaryCache final {
nsresult AddEntry(nsIURI* aURI, const nsACString& aKey,
const nsACString& aPattern, nsTArray<nsCString>& aMatchDest,
const nsACString& aId, const Maybe<nsCString>& aHash,
- bool aNewEntry, DictionaryCacheEntry** aDictEntry);
+ bool aNewEntry, uint32_t aExpiration,
+ DictionaryCacheEntry** aDictEntry);
already_AddRefed<DictionaryCacheEntry> AddEntry(
nsIURI* aURI, bool aNewEntry, DictionaryCacheEntry* aDictEntry);
@@ -310,8 +344,8 @@ class DictionaryCache final {
// return an entry
void GetDictionaryFor(
- nsIURI* aURI, ExtContentPolicyType aType,
- const std::function<nsresult(DictionaryCacheEntry*)>& aCallback);
+ nsIURI* aURI, ExtContentPolicyType aType, bool& aAsync,
+ const std::function<nsresult(bool, DictionaryCacheEntry*)>& aCallback);
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
// XXX
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1565,7 +1565,10 @@ nsresult nsHttpChannel::DoConnectActual(
return rv;
}
- return DispatchTransaction(aTransWithStickyConn);
+ return CallOrWaitForResume(
+ [trans = RefPtr(aTransWithStickyConn)](auto* self) {
+ return self->DispatchTransaction(trans);
+ });
}
nsresult nsHttpChannel::DispatchTransaction(
@@ -1960,10 +1963,16 @@ nsresult nsHttpChannel::SetupChannelForTransaction() {
// 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
+ bool async;
rv = gHttpHandler->AddAcceptAndDictionaryHeaders(
mURI, mLoadInfo->GetExternalContentPolicyType(), &mRequestHead,
- IsHTTPS(), [self = RefPtr(this)](DictionaryCacheEntry* aDict) {
+ IsHTTPS(), async,
+ [self = RefPtr(this)](bool aNeedsResume, DictionaryCacheEntry* aDict) {
self->mDictDecompress = aDict;
+ if (aNeedsResume) {
+ LOG_DICTIONARIES(("Resuming after getting Dictionary headers"));
+ self->Resume();
+ }
if (self->mDictDecompress) {
LOG_DICTIONARIES(
("Added dictionary header for %p, DirectoryCacheEntry %p",
@@ -1991,6 +2000,11 @@ nsresult nsHttpChannel::SetupChannelForTransaction() {
return true;
});
if (NS_FAILED(rv)) return rv;
+ if (async) {
+ // we'll continue later if GetDictionaryFor is still reading
+ LOG_DICTIONARIES(("Suspending to get Dictionary headers"));
+ Suspend();
+ }
}
// See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
@@ -5976,14 +5990,6 @@ nsresult DoAddCacheEntryHeaders(nsHttpChannel* self, nsICacheEntry* entry,
rv = entry->SetMetaDataElement("original-response-headers", head.get());
if (NS_FAILED(rv)) return rv;
- // If this is being marked as a dictionary, add it to the list
- if (StaticPrefs::network_http_dictionaries_enable() && self->IsHTTPS()) {
- if (!self->ParseDictionary(entry, responseHead, aModified)) {
- LOG_DICTIONARIES(
- ("Failed to parse use-as-dictionary from %s", head.get()));
- }
- }
-
// Indicate we have successfully finished setting metadata on the cache entry.
rv = entry->MetaDataReady();
@@ -6021,8 +6027,12 @@ bool nsHttpChannel::ParseDictionary(nsICacheEntry* aEntry,
matchIdVal.get(),
matchDestItems.Length() > 0 ? matchDestItems[0].get() : "<none>",
typeVal.get()));
+
+ uint32_t expTime = 0;
+ Unused << GetCacheTokenExpirationTime(&expTime);
+
dicts->AddEntry(mURI, key, matchVal, matchDestItems, matchIdVal, Some(hash),
- aModified, getter_AddRefs(mDictSaving));
+ aModified, expTime, getter_AddRefs(mDictSaving));
// If this was 304 Not Modified, then we don't need the dictionary data
// (though we may update the dictionary entry if the match/id/etc changed).
// If this is 304, mDictSaving will be cleared by AddEntry.
diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -656,7 +656,8 @@ nsresult nsHttpHandler::InitConnectionMgr() {
// set by Fetch from the old request
nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders(
nsIURI* aURI, ExtContentPolicyType aType, nsHttpRequestHead* aRequest,
- bool aSecure, const std::function<bool(DictionaryCacheEntry*)>& aCallback) {
+ bool aSecure, bool& aAsync,
+ const std::function<bool(bool, DictionaryCacheEntry*)>& aCallback) {
LOG(("Adding Dictionary headers"));
nsresult rv = NS_OK;
// Add the "Accept-Encoding" header and possibly Dictionary headers
@@ -664,9 +665,9 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders(
// The dictionary info may require us to check the cache.
if (StaticPrefs::network_http_dictionaries_enable()) {
mDictionaryCache->GetDictionaryFor(
- aURI, aType,
- [self = RefPtr(this), aRequest,
- aCallback](DictionaryCacheEntry* aDict) {
+ aURI, aType, aAsync,
+ [self = RefPtr(this), aRequest, aCallback](
+ bool aNeedsResume, DictionaryCacheEntry* aDict) {
nsresult rv;
if (aDict) {
nsAutoCStringN<64> encodedHash =
@@ -688,7 +689,7 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders(
// 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)) {
+ if ((aCallback)(aNeedsResume, aDict)) {
LOG_DICTIONARIES(
("Setting Available-Dictionary: %s", encodedHash.get()));
rv = aRequest->SetHeader(
@@ -715,14 +716,15 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders(
rv = aRequest->SetHeader(
nsHttp::Accept_Encoding, self->mHttpsAcceptEncodings, false,
nsHttpHeaderArray::eVarietyRequestOverride);
- (aCallback)(nullptr);
+ (aCallback)(false, nullptr);
return rv;
});
} else {
rv = aRequest->SetHeader(nsHttp::Accept_Encoding, mHttpsAcceptEncodings,
false,
nsHttpHeaderArray::eVarietyRequestOverride);
- (aCallback)(nullptr);
+ aAsync = false;
+ (aCallback)(false, nullptr);
}
} else {
// We need to not override a previous setting of 'identity' (for range
@@ -734,9 +736,9 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders(
false,
nsHttpHeaderArray::eVarietyRequestOverride);
}
- (aCallback)(nullptr);
+ aAsync = false;
+ (aCallback)(false, nullptr);
}
-
return rv;
}
diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h
@@ -119,8 +119,8 @@ class nsHttpHandler final : public nsIHttpProtocolHandler,
[[nodiscard]] nsresult AddAcceptAndDictionaryHeaders(
nsIURI* aURI, ExtContentPolicyType aType, nsHttpRequestHead* aRequest,
- bool aSecure,
- const std::function<bool(DictionaryCacheEntry*)>& aCallback);
+ bool aSecure, bool& aAsync,
+ const std::function<bool(bool, DictionaryCacheEntry*)>& aCallback);
[[nodiscard]] nsresult AddStandardRequestHeaders(
nsHttpRequestHead*, nsIURI* aURI, ExtContentPolicyType aContentPolicyType,
bool aShouldResistFingerprinting);
diff --git a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp
@@ -404,7 +404,8 @@ nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream,
return NS_OK;
}
- // Dictionary-encoded brotli has a 36-byte header (4bit fixed + 32bit SHA-256)
+ // Dictionary-encoded brotli has a 36-byte header (4 byte fixed + 32 byte
+ // SHA-256)
if (self->mBrotli->mDictionary && self->mBrotli->mEaten < 36) {
uint8_t header_needed = 36 - self->mBrotli->mEaten;
if (avail >= header_needed) {
@@ -453,7 +454,7 @@ nsresult nsHTTPCompressConv::BrotliHandler(nsIInputStream* stream,
self->mBrotli->mTotalOut = totalOut;
self->mBrotli->mBrotliStateIsStreamEnd =
BrotliDecoderIsFinished(&self->mBrotli->mState);
- LOG(("nsHttpCompresssConv %p brotlihandler decompress rv=%" PRIx32
+ LOG(("nsHttpCompressConv %p brotlihandler decompress rv=%" PRIx32
" out=%zu\n",
self, static_cast<uint32_t>(res), outSize));