Bug 1533369 - Add content type to cache index, r=mayhemer
authorMichal Novotny <michal.novotny@gmail.com>
Wed, 27 Mar 2019 14:32:12 +0000
changeset 466370 9e2dd8254c43067c965fcaf9388f403939b56894
parent 466369 10d5535b7bc552d1fc12b6e54a7a580753a2247a
child 466371 420f56c768f52a124219c889f31312fd06601d76
push id35768
push useropoprus@mozilla.com
push dateThu, 28 Mar 2019 09:55:54 +0000
treeherdermozilla-central@c045dd97faf2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmayhemer
bugs1533369
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1533369 - Add content type to cache index, r=mayhemer This patch adds content-type to metadata in cache entry and it is then propagated down to the cache index. Differential Revision: https://phabricator.services.mozilla.com/D23504
netwerk/cache2/CacheEntry.cpp
netwerk/cache2/CacheEntry.h
netwerk/cache2/CacheFile.cpp
netwerk/cache2/CacheFile.h
netwerk/cache2/CacheFileIOManager.cpp
netwerk/cache2/CacheFileIOManager.h
netwerk/cache2/CacheIndex.cpp
netwerk/cache2/CacheIndex.h
netwerk/cache2/OldWrappers.h
netwerk/cache2/nsICacheEntry.idl
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -1078,16 +1078,25 @@ nsresult CacheEntry::GetOnStopTime(uint6
 nsresult CacheEntry::SetNetworkTimes(uint64_t aOnStartTime,
                                      uint64_t aOnStopTime) {
   if (NS_SUCCEEDED(mFileStatus)) {
     return mFile->SetNetworkTimes(aOnStartTime, aOnStopTime);
   }
   return NS_ERROR_NOT_AVAILABLE;
 }
 
+nsresult CacheEntry::SetContentType(uint8_t aContentType) {
+  NS_ENSURE_ARG_MAX(aContentType, nsICacheEntry::CONTENT_TYPE_LAST - 1);
+
+  if (NS_SUCCEEDED(mFileStatus)) {
+    return mFile->SetContentType(aContentType);
+  }
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
 nsresult CacheEntry::GetIsForcedValid(bool *aIsForcedValid) {
   NS_ENSURE_ARG(aIsForcedValid);
 
   MOZ_ASSERT(mState > LOADING);
 
   if (mPinned) {
     *aIsForcedValid = true;
     return NS_OK;
--- a/netwerk/cache2/CacheEntry.h
+++ b/netwerk/cache2/CacheEntry.h
@@ -68,16 +68,17 @@ class CacheEntry final : public nsIRunna
   nsresult GetFetchCount(int32_t *aFetchCount);
   nsresult GetLastFetched(uint32_t *aLastFetched);
   nsresult GetLastModified(uint32_t *aLastModified);
   nsresult GetExpirationTime(uint32_t *aExpirationTime);
   nsresult SetExpirationTime(uint32_t expirationTime);
   nsresult GetOnStartTime(uint64_t *aOnStartTime);
   nsresult GetOnStopTime(uint64_t *aOnStopTime);
   nsresult SetNetworkTimes(uint64_t onStartTime, uint64_t onStopTime);
+  nsresult SetContentType(uint8_t aContentType);
   nsresult ForceValidFor(uint32_t aSecondsToTheFuture);
   nsresult GetIsForcedValid(bool *aIsForcedValid);
   nsresult OpenInputStream(int64_t offset, nsIInputStream **_retval);
   nsresult OpenOutputStream(int64_t offset, int64_t predictedSize,
                             nsIOutputStream **_retval);
   nsresult GetSecurityInfo(nsISupports **aSecurityInfo);
   nsresult SetSecurityInfo(nsISupports *aSecurityInfo);
   nsresult GetStorageDataSize(uint32_t *aStorageDataSize);
@@ -459,16 +460,19 @@ class CacheEntryHandle final : public ns
   }
   NS_IMETHOD GetOnStopTime(uint64_t *aOnStopTime) override {
     return mEntry->GetOnStopTime(aOnStopTime);
   }
   NS_IMETHOD SetNetworkTimes(uint64_t onStartTime,
                              uint64_t onStopTime) override {
     return mEntry->SetNetworkTimes(onStartTime, onStopTime);
   }
+  NS_IMETHOD SetContentType(uint8_t contentType) override {
+    return mEntry->SetContentType(contentType);
+  }
   NS_IMETHOD ForceValidFor(uint32_t aSecondsToTheFuture) override {
     return mEntry->ForceValidFor(aSecondsToTheFuture);
   }
   NS_IMETHOD GetIsForcedValid(bool *aIsForcedValid) override {
     return mEntry->GetIsForcedValid(aIsForcedValid);
   }
   NS_IMETHOD OpenInputStream(int64_t offset,
                              nsIInputStream **_retval) override {
--- a/netwerk/cache2/CacheFile.cpp
+++ b/netwerk/cache2/CacheFile.cpp
@@ -1167,17 +1167,17 @@ nsresult CacheFile::SetFrecency(uint32_t
 
   MOZ_ASSERT(mMetadata);
   NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
 
   PostWriteTimer();
 
   if (mHandle && !mHandle->IsDoomed())
     CacheFileIOManager::UpdateIndexEntry(mHandle, &aFrecency, nullptr, nullptr,
-                                         nullptr);
+                                         nullptr, nullptr);
 
   return mMetadata->SetFrecency(aFrecency);
 }
 
 nsresult CacheFile::GetFrecency(uint32_t *_retval) {
   CacheFileAutoLock lock(this);
   MOZ_ASSERT(mMetadata);
   NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
@@ -1215,18 +1215,18 @@ nsresult CacheFile::SetNetworkTimes(uint
 
   uint16_t onStartTime16 = aOnStartTime <= kIndexTimeOutOfBound
                                ? aOnStartTime
                                : kIndexTimeOutOfBound;
   uint16_t onStopTime16 =
       aOnStopTime <= kIndexTimeOutOfBound ? aOnStopTime : kIndexTimeOutOfBound;
 
   if (mHandle && !mHandle->IsDoomed()) {
-    CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, nullptr,
-                                         &onStartTime16, &onStopTime16);
+    CacheFileIOManager::UpdateIndexEntry(
+        mHandle, nullptr, nullptr, &onStartTime16, &onStopTime16, nullptr);
   }
   return NS_OK;
 }
 
 nsresult CacheFile::GetOnStartTime(uint64_t *_retval) {
   CacheFileAutoLock lock(this);
 
   MOZ_ASSERT(mMetadata);
@@ -1250,16 +1250,42 @@ nsresult CacheFile::GetOnStopTime(uint64
     return NS_ERROR_NOT_AVAILABLE;
   }
   nsresult rv;
   *_retval = nsDependentCString(onStopTimeStr).ToInteger64(&rv);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
   return NS_OK;
 }
 
+nsresult CacheFile::SetContentType(uint8_t aContentType) {
+  CacheFileAutoLock lock(this);
+
+  LOG(("CacheFile::SetContentType() this=%p, contentType=%u", this,
+       aContentType));
+
+  MOZ_ASSERT(mMetadata);
+  NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
+
+  PostWriteTimer();
+
+  // Save the content type to metadata for case we need to rebuild the index.
+  nsAutoCString contentType;
+  contentType.AppendInt(aContentType);
+  nsresult rv = mMetadata->SetElement("ctid", contentType.get());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (mHandle && !mHandle->IsDoomed()) {
+    CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, nullptr, nullptr,
+                                         nullptr, &aContentType);
+  }
+  return NS_OK;
+}
+
 nsresult CacheFile::SetAltMetadata(const char *aAltMetadata) {
   AssertOwnsLock();
   LOG(("CacheFile::SetAltMetadata() this=%p, aAltMetadata=%s", this,
        aAltMetadata ? aAltMetadata : ""));
 
   MOZ_ASSERT(mMetadata);
   NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
 
@@ -1275,17 +1301,17 @@ nsresult CacheFile::SetAltMetadata(const
 
     mAltDataOffset = -1;
     mAltDataType.Truncate();
     hasAltData = false;
   }
 
   if (mHandle && !mHandle->IsDoomed()) {
     CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, &hasAltData, nullptr,
-                                         nullptr);
+                                         nullptr, nullptr);
   }
   return rv;
 }
 
 nsresult CacheFile::GetLastModified(uint32_t *_retval) {
   CacheFileAutoLock lock(this);
   MOZ_ASSERT(mMetadata);
   NS_ENSURE_TRUE(mMetadata, NS_ERROR_UNEXPECTED);
@@ -2511,18 +2537,29 @@ nsresult CacheFile::InitIndexEntry() {
 
   const char *onStartTimeStr =
       mMetadata->GetElement("net-response-time-onstart");
   uint16_t onStartTime = toUint16(onStartTimeStr);
 
   const char *onStopTimeStr = mMetadata->GetElement("net-response-time-onstop");
   uint16_t onStopTime = toUint16(onStopTimeStr);
 
-  rv = CacheFileIOManager::UpdateIndexEntry(mHandle, &frecency, &hasAltData,
-                                            &onStartTime, &onStopTime);
+  const char *contentTypeStr = mMetadata->GetElement("ctid");
+  uint8_t contentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+  if (contentTypeStr) {
+    int64_t n64 = nsDependentCString(contentTypeStr).ToInteger64(&rv);
+    if (NS_FAILED(rv) || n64 < nsICacheEntry::CONTENT_TYPE_UNKNOWN ||
+        n64 >= nsICacheEntry::CONTENT_TYPE_LAST) {
+      n64 = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+    }
+    contentType = n64;
+  }
+
+  rv = CacheFileIOManager::UpdateIndexEntry(
+      mHandle, &frecency, &hasAltData, &onStartTime, &onStopTime, &contentType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 size_t CacheFile::SizeOfExcludingThis(
     mozilla::MallocSizeOf mallocSizeOf) const {
   CacheFileAutoLock lock(const_cast<CacheFile *>(this));
--- a/netwerk/cache2/CacheFile.h
+++ b/netwerk/cache2/CacheFile.h
@@ -96,16 +96,17 @@ class CacheFile final : public CacheFile
   nsresult SetElement(const char *aKey, const char *aValue);
   nsresult VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor);
   nsresult ElementsSize(uint32_t *_retval);
   nsresult SetExpirationTime(uint32_t aExpirationTime);
   nsresult GetExpirationTime(uint32_t *_retval);
   nsresult SetFrecency(uint32_t aFrecency);
   nsresult GetFrecency(uint32_t *_retval);
   nsresult SetNetworkTimes(uint64_t aOnStartTime, uint64_t aOnStopTime);
+  nsresult SetContentType(uint8_t aContentType);
   nsresult GetOnStartTime(uint64_t *_retval);
   nsresult GetOnStopTime(uint64_t *_retval);
   nsresult GetLastModified(uint32_t *_retval);
   nsresult GetLastFetched(uint32_t *_retval);
   nsresult GetFetchCount(uint32_t *_retval);
   nsresult GetDiskStorageSizeInKB(uint32_t *aDiskStorageSize);
   // Called by upper layers to indicated the entry has been fetched,
   // i.e. delivered to the consumer.
--- a/netwerk/cache2/CacheFileIOManager.cpp
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -971,90 +971,100 @@ class InitIndexEntryEvent : public Runna
                           mPinning);
 
     // We cannot set the filesize before we init the entry. If we're opening
     // an existing entry file, frecency will be set after parsing the entry
     // file, but we must set the filesize here since nobody is going to set it
     // if there is no write to the file.
     uint32_t sizeInK = mHandle->FileSizeInK();
     CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, nullptr, nullptr,
-                            &sizeInK);
+                            nullptr, &sizeInK);
 
     return NS_OK;
   }
 
  protected:
   RefPtr<CacheFileHandle> mHandle;
   OriginAttrsHash mOriginAttrsHash;
   bool mAnonymous;
   bool mPinning;
 };
 
 class UpdateIndexEntryEvent : public Runnable {
  public:
   UpdateIndexEntryEvent(CacheFileHandle *aHandle, const uint32_t *aFrecency,
                         const bool *aHasAltData, const uint16_t *aOnStartTime,
-                        const uint16_t *aOnStopTime)
+                        const uint16_t *aOnStopTime,
+                        const uint8_t *aContentType)
       : Runnable("net::UpdateIndexEntryEvent"),
         mHandle(aHandle),
         mHasFrecency(false),
         mHasHasAltData(false),
         mHasOnStartTime(false),
         mHasOnStopTime(false),
+        mHasContentType(false),
         mFrecency(0),
         mHasAltData(false),
         mOnStartTime(0),
-        mOnStopTime(0) {
+        mOnStopTime(0),
+        mContentType(nsICacheEntry::CONTENT_TYPE_UNKNOWN) {
     if (aFrecency) {
       mHasFrecency = true;
       mFrecency = *aFrecency;
     }
     if (aHasAltData) {
       mHasHasAltData = true;
       mHasAltData = *aHasAltData;
     }
     if (aOnStartTime) {
       mHasOnStartTime = true;
       mOnStartTime = *aOnStartTime;
     }
     if (aOnStopTime) {
       mHasOnStopTime = true;
       mOnStopTime = *aOnStopTime;
     }
+    if (aContentType) {
+      mHasContentType = true;
+      mContentType = *aContentType;
+    }
   }
 
  protected:
   ~UpdateIndexEntryEvent() = default;
 
  public:
   NS_IMETHOD Run() override {
     if (mHandle->IsClosed() || mHandle->IsDoomed()) {
       return NS_OK;
     }
 
     CacheIndex::UpdateEntry(mHandle->Hash(),
                             mHasFrecency ? &mFrecency : nullptr,
                             mHasHasAltData ? &mHasAltData : nullptr,
                             mHasOnStartTime ? &mOnStartTime : nullptr,
-                            mHasOnStopTime ? &mOnStopTime : nullptr, nullptr);
+                            mHasOnStopTime ? &mOnStopTime : nullptr,
+                            mHasContentType ? &mContentType : nullptr, nullptr);
     return NS_OK;
   }
 
  protected:
   RefPtr<CacheFileHandle> mHandle;
 
   bool mHasFrecency;
   bool mHasHasAltData;
   bool mHasOnStartTime;
   bool mHasOnStopTime;
+  bool mHasContentType;
 
   uint32_t mFrecency;
   bool mHasAltData;
   uint16_t mOnStartTime;
   uint16_t mOnStopTime;
+  uint8_t mContentType;
 };
 
 class MetadataWriteScheduleEvent : public Runnable {
  public:
   enum EMode { SCHEDULE, UNSCHEDULE, SHUTDOWN } mMode;
 
   RefPtr<CacheFile> mFile;
   RefPtr<CacheFileIOManager> mIOMan;
@@ -2037,17 +2047,17 @@ nsresult CacheFileIOManager::WriteIntern
       }
     }
 
     uint32_t newSizeInK = aHandle->FileSizeInK();
 
     if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() &&
         !aHandle->IsSpecialFile()) {
       CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, nullptr,
-                              nullptr, &newSizeInK);
+                              nullptr, nullptr, &newSizeInK);
 
       if (oldSizeInK < newSizeInK) {
         EvictIfOverLimitInternal();
       }
     }
   }
 
   if (bytesWritten != aCount) {
@@ -2563,17 +2573,17 @@ nsresult CacheFileIOManager::TruncateSee
 
   uint32_t oldSizeInK = aHandle->FileSizeInK();
   aHandle->mFileSize = aEOFPos;
   uint32_t newSizeInK = aHandle->FileSizeInK();
 
   if (oldSizeInK != newSizeInK && !aHandle->IsDoomed() &&
       !aHandle->IsSpecialFile()) {
     CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, nullptr, nullptr,
-                            &newSizeInK);
+                            nullptr, &newSizeInK);
 
     if (oldSizeInK < newSizeInK) {
       EvictIfOverLimitInternal();
     }
   }
 
   return NS_OK;
 }
@@ -2877,17 +2887,17 @@ nsresult CacheFileIOManager::OverLimitEv
       // EnsureEntryExists().
       rv = CacheIndex::EnsureEntryExists(&hash);
       NS_ENSURE_SUCCESS(rv, rv);
 
       // Move the entry at the end of both lists to make sure we won't end up
       // failing on one entry forever.
       uint32_t frecency = 0;
       rv = CacheIndex::UpdateEntry(&hash, &frecency, nullptr, nullptr, nullptr,
-                                   nullptr);
+                                   nullptr, nullptr);
       NS_ENSURE_SUCCESS(rv, rv);
 
       consecutiveFailures++;
       if (consecutiveFailures >= cnt) {
         // This doesn't necessarily mean that we've tried to doom every entry
         // but we've reached a sane number of tries. It is likely that another
         // eviction will start soon. And as said earlier, this normally doesn't
         // happen at all.
@@ -3535,38 +3545,40 @@ nsresult CacheFileIOManager::InitIndexEn
   return NS_OK;
 }
 
 // static
 nsresult CacheFileIOManager::UpdateIndexEntry(CacheFileHandle *aHandle,
                                               const uint32_t *aFrecency,
                                               const bool *aHasAltData,
                                               const uint16_t *aOnStartTime,
-                                              const uint16_t *aOnStopTime) {
+                                              const uint16_t *aOnStopTime,
+                                              const uint8_t *aContentType) {
   LOG(
       ("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, "
-       "hasAltData=%s, onStartTime=%s, onStopTime=%s]",
+       "hasAltData=%s, onStartTime=%s, onStopTime=%s, contentType=%s]",
        aHandle, aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
        aHasAltData ? (*aHasAltData ? "true" : "false") : "",
        aOnStartTime ? nsPrintfCString("%u", *aOnStartTime).get() : "",
-       aOnStopTime ? nsPrintfCString("%u", *aOnStopTime).get() : ""));
+       aOnStopTime ? nsPrintfCString("%u", *aOnStopTime).get() : "",
+       aContentType ? nsPrintfCString("%u", *aContentType).get() : ""));
 
   nsresult rv;
   RefPtr<CacheFileIOManager> ioMan = gInstance;
 
   if (aHandle->IsClosed() || !ioMan) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (aHandle->IsSpecialFile()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   RefPtr<UpdateIndexEntryEvent> ev = new UpdateIndexEntryEvent(
-      aHandle, aFrecency, aHasAltData, aOnStartTime, aOnStopTime);
+      aHandle, aFrecency, aHasAltData, aOnStartTime, aOnStopTime, aContentType);
   rv = ioMan->mIOThread->Dispatch(ev, aHandle->mPriority
                                           ? CacheIOThread::WRITE_PRIORITY
                                           : CacheIOThread::WRITE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
--- a/netwerk/cache2/CacheFileIOManager.h
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -317,17 +317,18 @@ class CacheFileIOManager final : public 
 
   static nsresult InitIndexEntry(CacheFileHandle *aHandle,
                                  OriginAttrsHash aOriginAttrsHash,
                                  bool aAnonymous, bool aPinning);
   static nsresult UpdateIndexEntry(CacheFileHandle *aHandle,
                                    const uint32_t *aFrecency,
                                    const bool *aHasAltData,
                                    const uint16_t *aOnStartTime,
-                                   const uint16_t *aOnStopTime);
+                                   const uint16_t *aOnStopTime,
+                                   const uint8_t *aContentType);
 
   static nsresult UpdateIndexEntry();
 
   enum EEnumerateMode { ENTRIES, DOOMED };
 
   static void GetCacheDirectory(nsIFile **result);
 #if defined(MOZ_WIDGET_ANDROID)
   static void GetProfilelessCacheDirectory(nsIFile **result);
--- a/netwerk/cache2/CacheIndex.cpp
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -21,17 +21,17 @@
 #include "mozilla/AutoRestore.h"
 #include <algorithm>
 #include "mozilla/Telemetry.h"
 #include "mozilla/Unused.h"
 
 #define kMinUnwrittenChanges 300
 #define kMinDumpInterval 20000  // in milliseconds
 #define kMaxBufSize 16384
-#define kIndexVersion 0x00000006
+#define kIndexVersion 0x00000007
 #define kUpdateIndexStartDelay 50000  // in milliseconds
 
 #define INDEX_NAME "index"
 #define TEMP_INDEX_NAME "index.tmp"
 #define JOURNAL_NAME "index.log"
 
 namespace mozilla {
 namespace net {
@@ -920,24 +920,27 @@ nsresult CacheIndex::RemoveEntry(const S
 }
 
 // static
 nsresult CacheIndex::UpdateEntry(const SHA1Sum::Hash *aHash,
                                  const uint32_t *aFrecency,
                                  const bool *aHasAltData,
                                  const uint16_t *aOnStartTime,
                                  const uint16_t *aOnStopTime,
+                                 const uint8_t *aContentType,
                                  const uint32_t *aSize) {
   LOG(
       ("CacheIndex::UpdateEntry() [hash=%08x%08x%08x%08x%08x, "
-       "frecency=%s, hasAltData=%s, onStartTime=%s, onStopTime=%s, size=%s]",
+       "frecency=%s, hasAltData=%s, onStartTime=%s, onStopTime=%s, "
+       "contentType=%s, size=%s]",
        LOGSHA1(aHash), aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
        aHasAltData ? (*aHasAltData ? "true" : "false") : "",
        aOnStartTime ? nsPrintfCString("%u", *aOnStartTime).get() : "",
        aOnStopTime ? nsPrintfCString("%u", *aOnStopTime).get() : "",
+       aContentType ? nsPrintfCString("%u", *aContentType).get() : "",
        aSize ? nsPrintfCString("%u", *aSize).get() : ""));
 
   MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
 
   StaticMutexAutoLock lock(sLock);
 
   RefPtr<CacheIndex> index = gInstance;
 
@@ -966,17 +969,17 @@ nsresult CacheIndex::UpdateEntry(const S
       if (!entry) {
         LOG(("CacheIndex::UpdateEntry() - Entry was not found in mIndex!"));
         NS_WARNING(
             ("CacheIndex::UpdateEntry() - Entry was not found in mIndex!"));
         return NS_ERROR_UNEXPECTED;
       }
 
       if (!HasEntryChanged(entry, aFrecency, aHasAltData, aOnStartTime,
-                           aOnStopTime, aSize)) {
+                           aOnStopTime, aContentType, aSize)) {
         return NS_OK;
       }
 
       MOZ_ASSERT(entry->IsFresh());
       MOZ_ASSERT(entry->IsInitialized());
       entry->MarkDirty();
 
       if (aFrecency) {
@@ -990,16 +993,20 @@ nsresult CacheIndex::UpdateEntry(const S
       if (aOnStartTime) {
         entry->SetOnStartTime(*aOnStartTime);
       }
 
       if (aOnStopTime) {
         entry->SetOnStopTime(*aOnStopTime);
       }
 
+      if (aContentType) {
+        entry->SetContentType(*aContentType);
+      }
+
       if (aSize) {
         entry->SetFileSize(*aSize);
       }
     } else {
       CacheIndexEntryUpdate *updated = index->mPendingUpdates.GetEntry(*aHash);
       DebugOnly<bool> removed = updated && updated->IsRemoved();
 
       MOZ_ASSERT(updated || !removed);
@@ -1036,16 +1043,20 @@ nsresult CacheIndex::UpdateEntry(const S
       if (aOnStartTime) {
         updated->SetOnStartTime(*aOnStartTime);
       }
 
       if (aOnStopTime) {
         updated->SetOnStopTime(*aOnStopTime);
       }
 
+      if (aContentType) {
+        updated->SetContentType(*aContentType);
+      }
+
       if (aSize) {
         updated->SetFileSize(*aSize);
       }
     }
   }
 
   index->WriteIndexToDiskIfNeeded();
 
@@ -1518,38 +1529,40 @@ bool CacheIndex::IsCollision(CacheIndexE
          aEntry->OriginAttrsHash(), aEntry->Anonymous()));
     return true;
   }
 
   return false;
 }
 
 // static
-bool CacheIndex::HasEntryChanged(CacheIndexEntry *aEntry,
-                                 const uint32_t *aFrecency,
-                                 const bool *aHasAltData,
-                                 const uint16_t *aOnStartTime,
-                                 const uint16_t *aOnStopTime,
-                                 const uint32_t *aSize) {
+bool CacheIndex::HasEntryChanged(
+    CacheIndexEntry *aEntry, const uint32_t *aFrecency, const bool *aHasAltData,
+    const uint16_t *aOnStartTime, const uint16_t *aOnStopTime,
+    const uint8_t *aContentType, const uint32_t *aSize) {
   if (aFrecency && *aFrecency != aEntry->GetFrecency()) {
     return true;
   }
 
   if (aHasAltData && *aHasAltData != aEntry->GetHasAltData()) {
     return true;
   }
 
   if (aOnStartTime && *aOnStartTime != aEntry->GetOnStartTime()) {
     return true;
   }
 
   if (aOnStopTime && *aOnStopTime != aEntry->GetOnStopTime()) {
     return true;
   }
 
+  if (aContentType && *aContentType != aEntry->GetContentType()) {
+    return true;
+  }
+
   if (aSize &&
       (*aSize & CacheIndexEntry::kFileSizeMask) != aEntry->GetFileSize()) {
     return true;
   }
 
   return false;
 }
 
@@ -2608,16 +2621,18 @@ nsresult CacheIndex::SetupDirectoryEnume
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult CacheIndex::InitEntryFromDiskData(CacheIndexEntry *aEntry,
                                            CacheFileMetadata *aMetaData,
                                            int64_t aFileSize) {
+  nsresult rv;
+
   aEntry->InitNew();
   aEntry->MarkDirty();
   aEntry->MarkFresh();
 
   aEntry->Init(GetOriginAttrsHash(aMetaData->OriginAttributes()),
                aMetaData->IsAnonymous(), aMetaData->Pinned());
 
   uint32_t frecency;
@@ -2642,16 +2657,28 @@ nsresult CacheIndex::InitEntryFromDiskDa
     return n64 <= kIndexTimeOutOfBound ? n64 : kIndexTimeOutOfBound;
   };
 
   aEntry->SetOnStartTime(
       toUint16(aMetaData->GetElement("net-response-time-onstart")));
   aEntry->SetOnStopTime(
       toUint16(aMetaData->GetElement("net-response-time-onstop")));
 
+  const char *contentTypeStr = aMetaData->GetElement("ctid");
+  uint8_t contentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+  if (contentTypeStr) {
+    int64_t n64 = nsDependentCString(contentTypeStr).ToInteger64(&rv);
+    if (NS_FAILED(rv) || n64 < nsICacheEntry::CONTENT_TYPE_UNKNOWN ||
+        n64 >= nsICacheEntry::CONTENT_TYPE_LAST) {
+      n64 = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+    }
+    contentType = n64;
+  }
+  aEntry->SetContentType(contentType);
+
   aEntry->SetFileSize(static_cast<uint32_t>(std::min(
       static_cast<int64_t>(PR_UINT32_MAX), (aFileSize + 0x3FF) >> 10)));
   return NS_OK;
 }
 
 bool CacheIndex::IsUpdatePending() {
   sLock.AssertCurrentThreadOwns();
 
--- a/netwerk/cache2/CacheIndex.h
+++ b/netwerk/cache2/CacheIndex.h
@@ -60,23 +60,24 @@ typedef struct {
 } CacheIndexHeader;
 
 static_assert(sizeof(CacheIndexHeader::mVersion) +
                       sizeof(CacheIndexHeader::mTimeStamp) +
                       sizeof(CacheIndexHeader::mIsDirty) ==
                   sizeof(CacheIndexHeader),
               "Unexpected sizeof(CacheIndexHeader)!");
 
-#pragma pack(push, 4)
+#pragma pack(push, 1)
 struct CacheIndexRecord {
   SHA1Sum::Hash mHash;
   uint32_t mFrecency;
   OriginAttrsHash mOriginAttrsHash;
   uint16_t mOnStartTime;
   uint16_t mOnStopTime;
+  uint8_t mContentType;
 
   /*
    *    1000 0000 0000 0000 0000 0000 0000 0000 : initialized
    *    0100 0000 0000 0000 0000 0000 0000 0000 : anonymous
    *    0010 0000 0000 0000 0000 0000 0000 0000 : removed
    *    0001 0000 0000 0000 0000 0000 0000 0000 : dirty
    *    0000 1000 0000 0000 0000 0000 0000 0000 : fresh
    *    0000 0100 0000 0000 0000 0000 0000 0000 : pinned
@@ -86,25 +87,27 @@ struct CacheIndexRecord {
    */
   uint32_t mFlags;
 
   CacheIndexRecord()
       : mFrecency(0),
         mOriginAttrsHash(0),
         mOnStartTime(kIndexTimeNotAvailable),
         mOnStopTime(kIndexTimeNotAvailable),
+        mContentType(nsICacheEntry::CONTENT_TYPE_UNKNOWN),
         mFlags(0) {}
 };
 #pragma pack(pop)
 
 static_assert(sizeof(CacheIndexRecord::mHash) +
                       sizeof(CacheIndexRecord::mFrecency) +
                       sizeof(CacheIndexRecord::mOriginAttrsHash) +
                       sizeof(CacheIndexRecord::mOnStartTime) +
                       sizeof(CacheIndexRecord::mOnStopTime) +
+                      sizeof(CacheIndexRecord::mContentType) +
                       sizeof(CacheIndexRecord::mFlags) ==
                   sizeof(CacheIndexRecord),
               "Unexpected sizeof(CacheIndexRecord)!");
 
 class CacheIndexEntry : public PLDHashEntryHdr {
  public:
   typedef const SHA1Sum::Hash &KeyType;
   typedef const SHA1Sum::Hash *KeyTypePointer;
@@ -148,33 +151,36 @@ class CacheIndexEntry : public PLDHashEn
 
   CacheIndexEntry &operator=(const CacheIndexEntry &aOther) {
     MOZ_ASSERT(
         memcmp(&mRec->mHash, &aOther.mRec->mHash, sizeof(SHA1Sum::Hash)) == 0);
     mRec->mFrecency = aOther.mRec->mFrecency;
     mRec->mOriginAttrsHash = aOther.mRec->mOriginAttrsHash;
     mRec->mOnStartTime = aOther.mRec->mOnStartTime;
     mRec->mOnStopTime = aOther.mRec->mOnStopTime;
+    mRec->mContentType = aOther.mRec->mContentType;
     mRec->mFlags = aOther.mRec->mFlags;
     return *this;
   }
 
   void InitNew() {
     mRec->mFrecency = 0;
     mRec->mOriginAttrsHash = 0;
     mRec->mOnStartTime = kIndexTimeNotAvailable;
     mRec->mOnStopTime = kIndexTimeNotAvailable;
+    mRec->mContentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
     mRec->mFlags = 0;
   }
 
   void Init(OriginAttrsHash aOriginAttrsHash, bool aAnonymous, bool aPinned) {
     MOZ_ASSERT(mRec->mFrecency == 0);
     MOZ_ASSERT(mRec->mOriginAttrsHash == 0);
     MOZ_ASSERT(mRec->mOnStartTime == kIndexTimeNotAvailable);
     MOZ_ASSERT(mRec->mOnStopTime == kIndexTimeNotAvailable);
+    MOZ_ASSERT(mRec->mContentType == nsICacheEntry::CONTENT_TYPE_UNKNOWN);
     // When we init the entry it must be fresh and may be dirty
     MOZ_ASSERT((mRec->mFlags & ~kDirtyMask) == kFreshMask);
 
     mRec->mOriginAttrsHash = aOriginAttrsHash;
     mRec->mFlags |= kInitializedMask;
     if (aAnonymous) {
       mRec->mFlags |= kAnonymousMask;
     }
@@ -215,16 +221,19 @@ class CacheIndexEntry : public PLDHashEn
   bool GetHasAltData() const { return !!(mRec->mFlags & kHasAltDataMask); }
 
   void SetOnStartTime(uint16_t aTime) { mRec->mOnStartTime = aTime; }
   uint16_t GetOnStartTime() const { return mRec->mOnStartTime; }
 
   void SetOnStopTime(uint16_t aTime) { mRec->mOnStopTime = aTime; }
   uint16_t GetOnStopTime() const { return mRec->mOnStopTime; }
 
+  void SetContentType(uint8_t aType) { mRec->mContentType = aType; }
+  uint8_t GetContentType() const { return mRec->mContentType; }
+
   // Sets filesize in kilobytes.
   void SetFileSize(uint32_t aFileSize) {
     if (aFileSize > kFileSizeMask) {
       LOG(
           ("CacheIndexEntry::SetFileSize() - FileSize is too large, "
            "truncating to %u",
            kFileSizeMask));
       aFileSize = kFileSizeMask;
@@ -249,16 +258,18 @@ class CacheIndexEntry : public PLDHashEn
     NetworkEndian::writeUint32(ptr, mRec->mFrecency);
     ptr += sizeof(uint32_t);
     NetworkEndian::writeUint64(ptr, mRec->mOriginAttrsHash);
     ptr += sizeof(uint64_t);
     NetworkEndian::writeUint16(ptr, mRec->mOnStartTime);
     ptr += sizeof(uint16_t);
     NetworkEndian::writeUint16(ptr, mRec->mOnStopTime);
     ptr += sizeof(uint16_t);
+    *ptr = mRec->mContentType;
+    ptr += sizeof(uint8_t);
     // Dirty and fresh flags should never go to disk, since they make sense only
     // during current session.
     NetworkEndian::writeUint32(ptr, mRec->mFlags & ~(kDirtyMask | kFreshMask));
   }
 
   void ReadFromBuf(void *aBuf) {
     const uint8_t *ptr = static_cast<const uint8_t *>(aBuf);
     MOZ_ASSERT(memcmp(&mRec->mHash, ptr, sizeof(SHA1Sum::Hash)) == 0);
@@ -266,28 +277,31 @@ class CacheIndexEntry : public PLDHashEn
     mRec->mFrecency = NetworkEndian::readUint32(ptr);
     ptr += sizeof(uint32_t);
     mRec->mOriginAttrsHash = NetworkEndian::readUint64(ptr);
     ptr += sizeof(uint64_t);
     mRec->mOnStartTime = NetworkEndian::readUint16(ptr);
     ptr += sizeof(uint16_t);
     mRec->mOnStopTime = NetworkEndian::readUint16(ptr);
     ptr += sizeof(uint16_t);
+    mRec->mContentType = *ptr;
+    ptr += sizeof(uint8_t);
     mRec->mFlags = NetworkEndian::readUint32(ptr);
   }
 
   void Log() const {
     LOG(
         ("CacheIndexEntry::Log() [this=%p, hash=%08x%08x%08x%08x%08x, fresh=%u,"
          " initialized=%u, removed=%u, dirty=%u, anonymous=%u, "
          "originAttrsHash=%" PRIx64 ", frecency=%u, hasAltData=%u, "
-         "onStartTime=%u, onStopTime=%u, size=%u]",
+         "onStartTime=%u, onStopTime=%u, contentType=%u, size=%u]",
          this, LOGSHA1(mRec->mHash), IsFresh(), IsInitialized(), IsRemoved(),
          IsDirty(), Anonymous(), OriginAttrsHash(), GetFrecency(),
-         GetHasAltData(), GetOnStartTime(), GetOnStopTime(), GetFileSize()));
+         GetHasAltData(), GetOnStartTime(), GetOnStopTime(), GetContentType(),
+         GetFileSize()));
   }
 
   static bool RecordMatchesLoadContextInfo(CacheIndexRecord *aRec,
                                            nsILoadContextInfo *aInfo) {
     MOZ_ASSERT(aInfo);
 
     if (!aInfo->IsPrivate() &&
         GetOriginAttrsHash(*aInfo->OriginAttributesPtr()) ==
@@ -360,17 +374,17 @@ class CacheIndexEntryUpdate : public Cac
     mUpdateFlags = 0;
     *(static_cast<CacheIndexEntry *>(this)) = aOther;
     return *this;
   }
 
   void InitNew() {
     mUpdateFlags = kFrecencyUpdatedMask | kHasAltDataUpdatedMask |
                    kOnStartTimeUpdatedMask | kOnStopTimeUpdatedMask |
-                   kFileSizeUpdatedMask;
+                   kContentTypeUpdatedMask | kFileSizeUpdatedMask;
     CacheIndexEntry::InitNew();
   }
 
   void SetFrecency(uint32_t aFrecency) {
     mUpdateFlags |= kFrecencyUpdatedMask;
     CacheIndexEntry::SetFrecency(aFrecency);
   }
 
@@ -384,16 +398,21 @@ class CacheIndexEntryUpdate : public Cac
     CacheIndexEntry::SetOnStartTime(aTime);
   }
 
   void SetOnStopTime(uint16_t aTime) {
     mUpdateFlags |= kOnStopTimeUpdatedMask;
     CacheIndexEntry::SetOnStopTime(aTime);
   }
 
+  void SetContentType(uint8_t aType) {
+    mUpdateFlags |= kContentTypeUpdatedMask;
+    CacheIndexEntry::SetContentType(aType);
+  }
+
   void SetFileSize(uint32_t aFileSize) {
     mUpdateFlags |= kFileSizeUpdatedMask;
     CacheIndexEntry::SetFileSize(aFileSize);
   }
 
   void ApplyUpdate(CacheIndexEntry *aDst) {
     MOZ_ASSERT(
         memcmp(&mRec->mHash, &aDst->mRec->mHash, sizeof(SHA1Sum::Hash)) == 0);
@@ -402,16 +421,19 @@ class CacheIndexEntryUpdate : public Cac
     }
     aDst->mRec->mOriginAttrsHash = mRec->mOriginAttrsHash;
     if (mUpdateFlags & kOnStartTimeUpdatedMask) {
       aDst->mRec->mOnStartTime = mRec->mOnStartTime;
     }
     if (mUpdateFlags & kOnStopTimeUpdatedMask) {
       aDst->mRec->mOnStopTime = mRec->mOnStopTime;
     }
+    if (mUpdateFlags & kContentTypeUpdatedMask) {
+      aDst->mRec->mContentType = mRec->mContentType;
+    }
     if (mUpdateFlags & kHasAltDataUpdatedMask &&
         ((aDst->mRec->mFlags ^ mRec->mFlags) & kHasAltDataMask)) {
       // Toggle the bit if we need to.
       aDst->mRec->mFlags ^= kHasAltDataMask;
     }
 
     if (mUpdateFlags & kFileSizeUpdatedMask) {
       // Copy all flags except |HasAltData|.
@@ -420,16 +442,17 @@ class CacheIndexEntryUpdate : public Cac
       // Copy all flags except |HasAltData| and file size.
       aDst->mRec->mFlags &= kFileSizeMask;
       aDst->mRec->mFlags |= (mRec->mFlags & ~kHasAltDataMask & ~kFileSizeMask);
     }
   }
 
  private:
   static const uint32_t kFrecencyUpdatedMask = 0x00000001;
+  static const uint32_t kContentTypeUpdatedMask = 0x00000002;
   static const uint32_t kFileSizeUpdatedMask = 0x00000004;
   static const uint32_t kHasAltDataUpdatedMask = 0x00000008;
   static const uint32_t kOnStartTimeUpdatedMask = 0x00000010;
   static const uint32_t kOnStopTimeUpdatedMask = 0x00000020;
 
   uint32_t mUpdateFlags;
 };
 
@@ -659,16 +682,17 @@ class CacheIndex final : public CacheFil
   // MUST be initialized. Call to AddEntry() or EnsureEntryExists() and to
   // InitEntry() must precede the call to this method.
   // Pass nullptr if the value didn't change.
   static nsresult UpdateEntry(const SHA1Sum::Hash *aHash,
                               const uint32_t *aFrecency,
                               const bool *aHasAltData,
                               const uint16_t *aOnStartTime,
                               const uint16_t *aOnStopTime,
+                              const uint8_t *aContentType,
                               const uint32_t *aSize);
 
   // Remove all entries from the index. Called when clearing the whole cache.
   static nsresult RemoveAll();
 
   enum EntryStatus { EXISTS = 0, DOES_NOT_EXIST = 1, DO_NOT_KNOW = 2 };
 
   // Returns status of the entry in index for the given key. It can be called
@@ -763,16 +787,17 @@ class CacheIndex final : public CacheFil
                           OriginAttrsHash aOriginAttrsHash, bool aAnonymous);
 
   // Checks whether any of the information about the entry has changed.
   static bool HasEntryChanged(CacheIndexEntry *aEntry,
                               const uint32_t *aFrecency,
                               const bool *aHasAltData,
                               const uint16_t *aOnStartTime,
                               const uint16_t *aOnStopTime,
+                              const uint8_t *aContentType,
                               const uint32_t *aSize);
 
   // Merge all pending operations from mPendingUpdates into mIndex.
   void ProcessPendingOperations();
 
   // Following methods perform writing of the index file.
   //
   // The index is written periodically, but not earlier than once in
--- a/netwerk/cache2/OldWrappers.h
+++ b/netwerk/cache2/OldWrappers.h
@@ -114,16 +114,19 @@ class _OldCacheEntryWrapper : public nsI
   }
   NS_IMETHOD GetOnStopTime(uint64_t *aTime) override {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   NS_IMETHOD SetNetworkTimes(uint64_t aOnStartTime,
                              uint64_t aOnStopTime) override {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
+  NS_IMETHOD SetContentType(uint8_t aContentType) override {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
   NS_IMETHOD GetLoadContextInfo(nsILoadContextInfo **aInfo) override {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   NS_IMETHOD Dismiss() override { return NS_ERROR_NOT_IMPLEMENTED; }
 
   NS_IMETHOD AsyncDoom(nsICacheEntryDoomCallback *listener) override;
   NS_IMETHOD GetPersistent(bool *aPersistToDisk) override;
   NS_IMETHOD GetIsForcedValid(bool *aIsForcedValid) override;
--- a/netwerk/cache2/nsICacheEntry.idl
+++ b/netwerk/cache2/nsICacheEntry.idl
@@ -13,16 +13,30 @@ interface nsICacheEntryDoomCallback;
 interface nsICacheListener;
 interface nsIFile;
 interface nsICacheEntryMetaDataVisitor;
 interface nsILoadContextInfo;
 
 [scriptable, uuid(607c2a2c-0a48-40b9-a956-8cf2bb9857cf)]
 interface nsICacheEntry : nsISupports
 {
+  const unsigned long CONTENT_TYPE_UNKNOWN = 0;
+  const unsigned long CONTENT_TYPE_OTHER = 1;
+  const unsigned long CONTENT_TYPE_JAVASCRIPT = 2;
+  const unsigned long CONTENT_TYPE_IMAGE = 3;
+  const unsigned long CONTENT_TYPE_MEDIA = 4;
+  const unsigned long CONTENT_TYPE_STYLESHEET = 5;
+  const unsigned long CONTENT_TYPE_WASM = 6;
+  /**
+   * Content type that is used internally to check whether the value parsed
+   * from disk is within allowed limits. Don't pass CONTENT_TYPE_LAST to
+   * setContentType method.
+   */
+  const unsigned long CONTENT_TYPE_LAST = 7;
+
   /**
    * Placeholder for the initial value of expiration time.
    */
   const unsigned long NO_EXPIRATION_TIME = 0xFFFFFFFF;
 
   /**
    * Get the key identifying the cache entry.
    */
@@ -76,16 +90,23 @@ interface nsICacheEntry : nsISupports
   readonly attribute uint64_t  onStopTime;
 
   /**
    * Set the network response times for onStartReqeust/onStopRequest (in ms).
    */
   void setNetworkTimes(in uint64_t onStartTime, in uint64_t onStopTime);
 
   /**
+   * Set content type. Available types are defined at the begining of this file.
+   * The content type is used internally for cache partitioning and telemetry
+   * purposes so there is no getter.
+   */
+  void setContentType(in uint8_t contentType);
+
+  /**
    * This method is intended to override the per-spec cache validation
    * decisions for a duration specified in seconds. The current state can
    * be examined with isForcedValid (see below). This value is not persisted,
    * so it will not survive session restart. Cache entries that are forced valid
    * will not be evicted from the cache for the duration of forced validity.
    * This means that there is a potential problem if the number of forced valid
    * entries grows to take up more space than the cache size allows.
    *
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1628,16 +1628,46 @@ void WarnWrongMIMEOfScript(nsHttpChannel
   aResponseHead->ContentType(contentType);
   NS_ConvertUTF8toUTF16 typeString(contentType);
   if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
     ReportMimeTypeMismatch(aChannel, "WarnScriptWithWrongMimeType", aURI,
                            contentType, Report::Warning);
   }
 }
 
+void nsHttpChannel::SetCachedContentType() {
+  if (!mResponseHead) {
+    return;
+  }
+
+  nsAutoCString contentTypeStr;
+  mResponseHead->ContentType(contentTypeStr);
+
+  uint8_t contentType = nsICacheEntry::CONTENT_TYPE_OTHER;
+  if (nsContentUtils::IsJavascriptMIMEType(
+          NS_ConvertUTF8toUTF16(contentTypeStr))) {
+    contentType = nsICacheEntry::CONTENT_TYPE_JAVASCRIPT;
+  } else if (StringBeginsWith(contentTypeStr, NS_LITERAL_CSTRING("text/css")) ||
+             mLoadInfo->GetExternalContentPolicyType() ==
+                 nsIContentPolicy::TYPE_STYLESHEET) {
+    contentType = nsICacheEntry::CONTENT_TYPE_STYLESHEET;
+  } else if (StringBeginsWith(contentTypeStr,
+                              NS_LITERAL_CSTRING("application/wasm"))) {
+    contentType = nsICacheEntry::CONTENT_TYPE_WASM;
+  } else if (StringBeginsWith(contentTypeStr, NS_LITERAL_CSTRING("image/"))) {
+    contentType = nsICacheEntry::CONTENT_TYPE_IMAGE;
+  } else if (StringBeginsWith(contentTypeStr, NS_LITERAL_CSTRING("video/"))) {
+    contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
+  } else if (StringBeginsWith(contentTypeStr, NS_LITERAL_CSTRING("audio/"))) {
+    contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
+  }
+
+  mCacheEntry->SetContentType(contentType);
+}
+
 nsresult nsHttpChannel::CallOnStartRequest() {
   LOG(("nsHttpChannel::CallOnStartRequest [this=%p]", this));
 
   MOZ_RELEASE_ASSERT(!mRequireCORSPreflight || mIsCorsPreflightDone,
                      "CORS preflight must have been finished by the time we "
                      "call OnStartRequest");
 
   if (mOnStartRequestCalled) {
@@ -1724,16 +1754,20 @@ nsresult nsHttpChannel::CallOnStartReque
         }
       }
     }
   }
 
   if (mResponseHead && !mResponseHead->HasContentCharset())
     mResponseHead->SetContentCharset(mContentCharsetHint);
 
+  if (mCacheEntry && mCacheEntryIsWriteOnly) {
+    SetCachedContentType();
+  }
+
   LOG(("  calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
        mListener.get()));
 
   // About to call OnStartRequest, dismiss the guard object.
   onStartGuard.release();
 
   if (mListener) {
     MOZ_ASSERT(!mOnStartRequestCalled,
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -545,16 +545,20 @@ class nsHttpChannel final : public HttpB
   void SetDoNotTrack();
 
   already_AddRefed<nsChannelClassifier> GetOrCreateChannelClassifier();
 
   // Start an internal redirect to a new InterceptedHttpChannel which will
   // resolve in firing a ServiceWorker FetchEvent.
   MOZ_MUST_USE nsresult RedirectToInterceptedChannel();
 
+  // Determines and sets content type in the cache entry. It's called when
+  // writing a new entry. The content type is used in cache internally only.
+  void SetCachedContentType();
+
  private:
   // this section is for main-thread-only object
   // all the references need to be proxy released on main thread.
   nsCOMPtr<nsIApplicationCache> mApplicationCacheForWrite;
   // auth specific data
   nsCOMPtr<nsIHttpChannelAuthProvider> mAuthProvider;
   nsCOMPtr<nsIURI> mRedirectURI;
   nsCOMPtr<nsIChannel> mRedirectChannel;