Bug 1487100 - Allow calling nsICacheInfoChannel.preferAlternativeDataType(altDataType, contentType) multiple times r=michal,luke
authorValentin Gosu <valentin.gosu@gmail.com>
Wed, 17 Oct 2018 13:58:30 +0000
changeset 490206 e37adb23fd48ca1576ba954fe203b3fd6d6155f3
parent 490205 02a07fe8780872236c65ddc30470209bef95b71b
child 490207 42319047f3d9ad10a2849c1e7b1b9cf6d2bb6de0
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersmichal, luke
bugs1487100
milestone64.0a1
Bug 1487100 - Allow calling nsICacheInfoChannel.preferAlternativeDataType(altDataType, contentType) multiple times r=michal,luke This patch changes the way we set and handle the preferred alternate data type. It is no longer just one choice, but a set of preferences, each conditional on the contentType of the resource. For example: var cc = chan.QueryInterface(Ci.nsICacheInfoChannel); cc.preferAlternativeDataType("js-bytecode", "text/javascript"); cc.preferAlternativeDataType("ammended-text", "text/plain"); cc.preferAlternativeDataType("something-else", ""); When loaded from the cache, the available alt-data type will be checked against "js-bytecode" if the contentType is "text/javascript", "ammended-text" if the contentType is "text/plain" or "something-else" for all contentTypes. Note that the alt-data type could be "something-else" even if the contentType is "text/javascript". The preferences are saved as an nsTArray<mozilla::Tuple<nsCString, nsCString>>. Differential Revision: https://phabricator.services.mozilla.com/D8071
dom/fetch/FetchDriver.cpp
dom/script/ScriptLoader.cpp
dom/serviceworkers/ServiceWorkerEvents.cpp
dom/serviceworkers/ServiceWorkerPrivate.cpp
netwerk/base/nsICacheInfoChannel.idl
netwerk/cache2/CacheEntry.cpp
netwerk/cache2/CacheEntry.h
netwerk/cache2/CacheFile.cpp
netwerk/cache2/CacheFile.h
netwerk/cache2/OldWrappers.cpp
netwerk/cache2/OldWrappers.h
netwerk/cache2/nsICacheEntry.idl
netwerk/ipc/NeckoChannelParams.ipdlh
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/InterceptedHttpChannel.cpp
netwerk/protocol/http/PHttpChannelParams.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/viewsource/nsViewSourceChannel.cpp
netwerk/protocol/viewsource/nsViewSourceChannel.h
netwerk/test/unit/test_alt-data_cross_process.js
netwerk/test/unit/test_alt-data_overwrite.js
netwerk/test/unit/test_alt-data_simple.js
netwerk/test/unit/test_alt-data_stream.js
netwerk/test/unit/test_cache-entry-id.js
netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -729,17 +729,17 @@ FetchDriver::HttpFetch(const nsACString&
   }
 
   // if the preferred alternative data type in InternalRequest is not empty, set
   // the data type on the created channel and also create a AlternativeDataStreamListener
   // to be the stream listener of the channel.
   if (!aPreferredAlternativeDataType.IsEmpty()) {
     nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(chan);
     if (cic) {
-      cic->PreferAlternativeDataType(aPreferredAlternativeDataType);
+      cic->PreferAlternativeDataType(aPreferredAlternativeDataType, EmptyCString());
       MOZ_ASSERT(!mAltDataListener);
       mAltDataListener =
         new AlternativeDataStreamListener(this, chan, aPreferredAlternativeDataType);
       rv = chan->AsyncOpen2(mAltDataListener);
     } else {
       rv = chan->AsyncOpen2(this);
     }
   } else {
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -1104,26 +1104,26 @@ ScriptLoader::StartLoad(ScriptLoadReques
   nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(channel));
   if (cic && nsContentUtils::IsBytecodeCacheEnabled() &&
       // Bug 1436400: no bytecode cache support for modules yet.
       !aRequest->IsModuleRequest()) {
     if (!aRequest->IsLoadingSource()) {
       // Inform the HTTP cache that we prefer to have information coming from the
       // bytecode cache instead of the sources, if such entry is already registered.
       LOG(("ScriptLoadRequest (%p): Maybe request bytecode", aRequest));
-      cic->PreferAlternativeDataType(nsContentUtils::JSBytecodeMimeType());
+      cic->PreferAlternativeDataType(nsContentUtils::JSBytecodeMimeType(), EmptyCString());
     } else {
       // If we are explicitly loading from the sources, such as after a
       // restarted request, we might still want to save the bytecode after.
       //
       // The following tell the cache to look for an alternative data type which
       // does not exist, such that we can later save the bytecode with a
       // different alternative data type.
       LOG(("ScriptLoadRequest (%p): Request saving bytecode later", aRequest));
-      cic->PreferAlternativeDataType(kNullMimeType);
+      cic->PreferAlternativeDataType(kNullMimeType, EmptyCString());
     }
   }
 
   LOG(("ScriptLoadRequest (%p): mode=%u tracking=%d",
        aRequest, unsigned(aRequest->mScriptMode), aRequest->IsTracking()));
 
   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
   if (cos) {
--- a/dom/serviceworkers/ServiceWorkerEvents.cpp
+++ b/dom/serviceworkers/ServiceWorkerEvents.cpp
@@ -338,18 +338,19 @@ public:
     }
 
     auto castLoadInfo = static_cast<mozilla::net::LoadInfo*>(loadInfo.get());
     castLoadInfo->SynthesizeServiceWorkerTainting(mInternalResponse->GetTainting());
 
     // Get the preferred alternative data type of outter channel
     nsAutoCString preferredAltDataType(EmptyCString());
     nsCOMPtr<nsICacheInfoChannel> outerChannel = do_QueryInterface(underlyingChannel);
-    if (outerChannel) {
-      outerChannel->GetPreferredAlternativeDataType(preferredAltDataType);
+    if (outerChannel && !outerChannel->PreferredAlternativeDataTypes().IsEmpty()) {
+      // TODO: handle multiple types properly.
+      preferredAltDataType.Assign(mozilla::Get<0>(outerChannel->PreferredAlternativeDataTypes()[0]));
     }
 
     // Get the alternative data type saved in the InternalResponse
     nsAutoCString altDataType;
     nsCOMPtr<nsICacheInfoChannel> cacheInfoChannel =
       mInternalResponse->TakeCacheInfoChannel().get();
     if (cacheInfoChannel) {
       cacheInfoChannel->GetAlternativeDataType(altDataType);
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -1582,21 +1582,21 @@ private:
     internalReq->SetBody(mUploadStream, mUploadStreamContentLength);
     // For Telemetry, note that this Request object was created by a Fetch event.
     internalReq->SetCreatedByFetchEvent();
 
     nsCOMPtr<nsIChannel> channel;
     nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel));
     NS_ENSURE_SUCCESS(rv, false);
 
-    nsAutoCString alternativeDataType;
     nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(channel);
-    if (cic &&
-        NS_SUCCEEDED(cic->GetPreferredAlternativeDataType(alternativeDataType)) &&
-        !alternativeDataType.IsEmpty()) {
+    if (cic && !cic->PreferredAlternativeDataTypes().IsEmpty()) {
+      // TODO: the internal request probably needs all the preferred types.
+      nsAutoCString alternativeDataType;
+      alternativeDataType.Assign(mozilla::Get<0>(cic->PreferredAlternativeDataTypes()[0]));
       internalReq->SetPreferredAlternativeDataType(alternativeDataType);
     }
 
     nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(globalObj.GetAsSupports());
     if (NS_WARN_IF(!global)) {
       return false;
     }
 
--- a/netwerk/base/nsICacheInfoChannel.idl
+++ b/netwerk/base/nsICacheInfoChannel.idl
@@ -1,16 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIOutputStream;
 
+%{C++
+namespace mozilla {
+template<typename... Elements> class Tuple;
+} // namespace mozilla
+%}
+
+[ref] native ConstPreferenceArray(const nsTArray<mozilla::Tuple<nsCString, nsCString>>);
+
 [scriptable, uuid(72c34415-c6eb-48af-851f-772fa9ee5972)]
 interface nsICacheInfoChannel : nsISupports
 {
   /**
    * Get the number of times the cache entry has been opened. This attribute is
    * equivalent to nsICachingChannel.cacheToken.fetchCount.
    *
    * @throws NS_ERROR_NOT_AVAILABLE if the cache entry or the alternate data
@@ -74,26 +82,35 @@ interface nsICacheInfoChannel : nsISuppo
    * channel being the default load group's channel.
    */
   attribute boolean allowStaleCacheContent;
 
   /**
    * Calling this method instructs the channel to serve the alternative data
    * if that was previously saved in the cache, otherwise it will serve the
    * real data.
+   * @param type
+   *        a string identifying the alt-data format
+   * @param contentType
+   *        the contentType for which the preference applies.
+   *        an empty contentType means the preference applies for ANY contentType
+   *
+   * The method may be called several times, with different type and contentType.
+   *
    * Must be called before AsyncOpen.
    */
-  void preferAlternativeDataType(in ACString type);
+  void preferAlternativeDataType(in ACString type, in ACString contentType);
 
   /**
    * Get the preferred alternative data type set by preferAlternativeDataType().
-   * This attribute stands for the desired data type instead of the type of the
+   * The returned types stand for the desired data type instead of the type of the
    * information retrieved from the network stack.
    */
-  readonly attribute ACString preferredAlternativeDataType;
+  [noscript, notxpcom, nostdcall]
+  ConstPreferenceArray preferredAlternativeDataTypes();
 
   /**
    * Holds the type of the alternative data representation that the channel
    * is returning.
    * Is empty string if no alternative data representation was requested, or
    * if the requested representation wasn't found in the cache.
    * Can only be called during or after OnStartRequest.
    */
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -1270,16 +1270,21 @@ nsresult CacheEntry::OpenOutputStream(in
 
 nsresult CacheEntry::OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval)
 {
   LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
        PromiseFlatCString(type).get()));
 
   nsresult rv;
 
+  if (type.IsEmpty()) {
+    // The empty string is reserved to mean no alt-data available.
+    return NS_ERROR_INVALID_ARG;
+  }
+
   mozilla::MutexAutoLock lock(mLock);
 
   if (!mHasData || mState < READY || mOutputStream || mIsDoomed) {
     LOG(("  entry not in state to write alt-data"));
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (mFile->EntryWouldExceedLimit(0, predictedSize, true)) {
@@ -1549,26 +1554,33 @@ nsresult CacheEntry::GetDataSize(int64_t
     LOG(("  write in progress (stream active)"));
     return NS_ERROR_IN_PROGRESS;
   }
 
   LOG(("  size=%" PRId64, *aDataSize));
   return NS_OK;
 }
 
-
 nsresult CacheEntry::GetAltDataSize(int64_t *aDataSize)
 {
   LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
   if (NS_FAILED(mFileStatus)) {
     return mFileStatus;
   }
   return mFile->GetAltDataSize(aDataSize);
 }
 
+nsresult CacheEntry::GetAltDataType(nsACString &aType)
+{
+  LOG(("CacheEntry::GetAltDataType [this=%p]", this));
+  if (NS_FAILED(mFileStatus)) {
+    return mFileStatus;
+  }
+  return mFile->GetAltDataType(aType);
+}
 
 nsresult CacheEntry::MarkValid()
 {
   // NOT IMPLEMENTED ACTUALLY
   return NS_OK;
 }
 
 nsresult CacheEntry::MaybeMarkValid()
--- a/netwerk/cache2/CacheEntry.h
+++ b/netwerk/cache2/CacheEntry.h
@@ -88,16 +88,17 @@ public:
   nsresult SetMetaDataElement(const char * key, const char * value);
   nsresult VisitMetaData(nsICacheEntryMetaDataVisitor *visitor);
   nsresult MetaDataReady(void);
   nsresult SetValid(void);
   nsresult GetDiskStorageSizeInKB(uint32_t *aDiskStorageSizeInKB);
   nsresult Recreate(bool aMemoryOnly, nsICacheEntry * *_retval);
   nsresult GetDataSize(int64_t *aDataSize);
   nsresult GetAltDataSize(int64_t *aAltDataSize);
+  nsresult GetAltDataType(nsACString &aAltDataType);
   nsresult OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval);
   nsresult OpenAlternativeInputStream(const nsACString & type, nsIInputStream * *_retval);
   nsresult GetLoadContextInfo(nsILoadContextInfo * *aLoadContextInfo);
   nsresult Close(void);
   nsresult MarkValid(void);
   nsresult MaybeMarkValid(void);
   nsresult HasWriteAccess(bool aWriteAllowed, bool *_retval);
 
@@ -456,16 +457,17 @@ public:
   NS_IMETHOD SetMetaDataElement(const char * key, const char * value) override { return mEntry->SetMetaDataElement(key, value); }
   NS_IMETHOD VisitMetaData(nsICacheEntryMetaDataVisitor *visitor) override { return mEntry->VisitMetaData(visitor); }
   NS_IMETHOD MetaDataReady(void) override { return mEntry->MetaDataReady(); }
   NS_IMETHOD SetValid(void) override { return mEntry->SetValid(); }
   NS_IMETHOD GetDiskStorageSizeInKB(uint32_t *aDiskStorageSizeInKB) override { return mEntry->GetDiskStorageSizeInKB(aDiskStorageSizeInKB); }
   NS_IMETHOD Recreate(bool aMemoryOnly, nsICacheEntry * *_retval) override { return mEntry->Recreate(aMemoryOnly, _retval); }
   NS_IMETHOD GetDataSize(int64_t *aDataSize) override { return mEntry->GetDataSize(aDataSize); }
   NS_IMETHOD GetAltDataSize(int64_t *aAltDataSize) override { return mEntry->GetAltDataSize(aAltDataSize); }
+  NS_IMETHOD GetAltDataType(nsACString &aType) override { return mEntry->GetAltDataType(aType); }
   NS_IMETHOD OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval) override { return mEntry->OpenAlternativeOutputStream(type, predictedSize, _retval); }
   NS_IMETHOD OpenAlternativeInputStream(const nsACString & type, nsIInputStream * *_retval) override { return mEntry->OpenAlternativeInputStream(type, _retval); }
   NS_IMETHOD GetLoadContextInfo(nsILoadContextInfo * *aLoadContextInfo) override { return mEntry->GetLoadContextInfo(aLoadContextInfo); }
   NS_IMETHOD Close(void) override { return mEntry->Close(); }
   NS_IMETHOD MarkValid(void) override { return mEntry->MarkValid(); }
   NS_IMETHOD MaybeMarkValid(void) override { return mEntry->MaybeMarkValid(); }
   NS_IMETHOD HasWriteAccess(bool aWriteAllowed, bool *_retval) override { return mEntry->HasWriteAccess(aWriteAllowed, _retval); }
 
--- a/netwerk/cache2/CacheFile.cpp
+++ b/netwerk/cache2/CacheFile.cpp
@@ -642,22 +642,23 @@ CacheFile::OnMetadataRead(nsresult aResu
     mDataSize = mMetadata->Offset();
     if (mDataSize == 0 && mMetadata->ElementsSize() == 0) {
       isNew = true;
       mMetadata->MarkDirty();
     } else {
       const char *altData = mMetadata->GetElement(CacheFileUtils::kAltDataKey);
       if (altData &&
           (NS_FAILED(CacheFileUtils::ParseAlternativeDataInfo(
-            altData, &mAltDataOffset, nullptr)) ||
+            altData, &mAltDataOffset, &mAltDataType)) ||
           (mAltDataOffset > mDataSize))) {
         // alt-metadata cannot be parsed or alt-data offset is invalid
         mMetadata->InitEmptyMetadata();
         isNew = true;
         mAltDataOffset = -1;
+        mAltDataType.Truncate();
         mDataSize = 0;
       } else {
         CacheFileAutoLock lock(this);
         PreloadChunks(0);
       }
     }
 
     InitIndexEntry();
@@ -796,18 +797,16 @@ nsresult
 CacheFile::OpenAlternativeInputStream(nsICacheEntry *aEntryHandle,
                                       const char *aAltDataType,
                                       nsIInputStream **_retval)
 {
   CacheFileAutoLock lock(this);
 
   MOZ_ASSERT(mHandle || mMemoryOnly || mOpeningFile);
 
-  nsresult rv;
-
   if (NS_WARN_IF(!mReady)) {
     LOG(("CacheFile::OpenAlternativeInputStream() - CacheFile is not ready "
          "[this=%p]", this));
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (mAltDataOffset == -1) {
     LOG(("CacheFile::OpenAlternativeInputStream() - Alternative data is not "
@@ -823,45 +822,23 @@ CacheFile::OpenAlternativeInputStream(ns
     // a failed state.  This is the only way to protect consumers correctly
     // from reading a broken entry.  When the file is in the failed state,
     // it's also doomed, so reopening the entry won't make any difference -
     // data will still be inaccessible anymore.  Note that for just doomed
     // files, we must allow reading the data.
     return mStatus;
   }
 
-  const char *altData = mMetadata->GetElement(CacheFileUtils::kAltDataKey);
-  MOZ_ASSERT(altData, "alt-metadata should exist but was not found!");
-  if (NS_WARN_IF(!altData)) {
-    LOG(("CacheFile::OpenAlternativeInputStream() - alt-metadata not found but "
-         "alt-data exists according to mAltDataOffset! [this=%p, ]", this));
+  if (mAltDataType != aAltDataType) {
+    LOG(("CacheFile::OpenAlternativeInputStream() - Alternative data is of a "
+         "different type than requested [this=%p, availableType=%s, "
+         "requestedType=%s]", this, mAltDataType.get(), aAltDataType));
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  int64_t offset;
-  nsCString availableAltData;
-  rv = CacheFileUtils::ParseAlternativeDataInfo(altData, &offset,
-                                                &availableAltData);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    MOZ_ASSERT(false, "alt-metadata unexpectedly failed to parse");
-    LOG(("CacheFile::OpenAlternativeInputStream() - Cannot parse alternative "
-         "metadata! [this=%p]", this));
-    return rv;
-  }
-
-  if (availableAltData != aAltDataType) {
-    LOG(("CacheFile::OpenAlternativeInputStream() - Alternative data is of a "
-         "different type than requested [this=%p, availableType=%s, "
-         "requestedType=%s]", this, availableAltData.get(), aAltDataType));
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  // mAltDataOffset must be in sync with what is stored in metadata
-  MOZ_ASSERT(mAltDataOffset == offset);
-
   // Once we open input stream we no longer allow preloading of chunks without
   // input stream, i.e. we will no longer keep first few chunks preloaded when
   // the last input stream is closed.
   mPreloadWithoutInputStreams = false;
 
   CacheFileInputStream *input = new CacheFileInputStream(this, aEntryHandle, true);
 
   LOG(("CacheFile::OpenAlternativeInputStream() - Creating new input stream %p "
@@ -920,16 +897,17 @@ CacheFile::OpenOutputStream(CacheOutputC
     rv = Truncate(mAltDataOffset);
     if (NS_FAILED(rv)) {
       LOG(("CacheFile::OpenOutputStream() - Truncating alt-data failed "
            "[rv=0x%08" PRIx32 "]", static_cast<uint32_t>(rv)));
       return rv;
     }
     SetAltMetadata(nullptr);
     mAltDataOffset = -1;
+    mAltDataType.Truncate();
   }
 
   // Once we open output stream we no longer allow preloading of chunks without
   // input stream. There is no reason to believe that some input stream will be
   // opened soon. Otherwise we would cache unused chunks of all newly created
   // entries until the CacheFile is destroyed.
   mPreloadWithoutInputStreams = false;
 
@@ -1014,16 +992,17 @@ CacheFile::OpenAlternativeOutputStream(C
   mPreloadWithoutInputStreams = false;
 
   mOutput = new CacheFileOutputStream(this, aCloseListener, true);
 
   LOG(("CacheFile::OpenAlternativeOutputStream() - Creating new output stream "
        "%p [this=%p]", mOutput, this));
 
   mDataAccessed = true;
+  mAltDataType = aAltDataType;
   NS_ADDREF(*_retval = mOutput);
   return NS_OK;
 }
 
 nsresult
 CacheFile::SetMemoryOnly()
 {
   LOG(("CacheFile::SetMemoryOnly() mMemoryOnly=%d [this=%p]",
@@ -1323,16 +1302,17 @@ CacheFile::SetAltMetadata(const char* aA
   nsresult rv = mMetadata->SetElement(CacheFileUtils::kAltDataKey, aAltMetadata);
   bool hasAltData = aAltMetadata ? true : false;
 
   if (NS_FAILED(rv)) {
     // Removing element shouldn't fail because it doesn't allocate memory.
     mMetadata->SetElement(CacheFileUtils::kAltDataKey, nullptr);
 
     mAltDataOffset = -1;
+    mAltDataType.Truncate();
     hasAltData = false;
   }
 
   if (mHandle && !mHandle->IsDoomed()) {
     CacheFileIOManager::UpdateIndexEntry(mHandle, nullptr, nullptr, &hasAltData, nullptr, nullptr);
   }
   return rv;
 }
@@ -2191,16 +2171,17 @@ CacheFile::RemoveOutput(CacheFileOutputS
         rv = Truncate(mAltDataOffset);
         if (NS_FAILED(rv)) {
           LOG(("CacheFile::RemoveOutput() - Truncating alt-data failed "
                "[rv=0x%08" PRIx32 "]", static_cast<uint32_t>(rv)));
           SetError(aStatus);
         } else {
           SetAltMetadata(nullptr);
           mAltDataOffset = -1;
+          mAltDataType.Truncate();
         }
       }
     } else {
       SetError(aStatus);
     }
   }
 
   // Notify close listener as the last action
@@ -2376,16 +2357,29 @@ CacheFile::GetAltDataSize(int64_t *aSize
   if (mAltDataOffset == -1) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   *aSize = mDataSize - mAltDataOffset;
   return NS_OK;
 }
 
+nsresult
+CacheFile::GetAltDataType(nsACString& aType)
+{
+  CacheFileAutoLock lock(this);
+
+  if (mAltDataOffset == -1) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  aType = mAltDataType;
+  return NS_OK;
+}
+
 bool
 CacheFile::IsDoomed()
 {
   CacheFileAutoLock lock(this);
 
   if (!mHandle)
     return false;
 
--- a/netwerk/cache2/CacheFile.h
+++ b/netwerk/cache2/CacheFile.h
@@ -87,16 +87,17 @@ public:
                                          const char *aAltDataType, nsIOutputStream **_retval);
   NS_IMETHOD SetMemoryOnly();
   NS_IMETHOD Doom(CacheFileListener *aCallback);
 
   void Kill() { mKill = true; }
   nsresult   ThrowMemoryCachedData();
 
   nsresult GetAltDataSize(int64_t *aSize);
+  nsresult GetAltDataType(nsACString& aType);
 
   // metadata forwarders
   nsresult GetElement(const char *aKey, char **_retval);
   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);
@@ -210,16 +211,17 @@ private:
   nsresult       mStatus;
   int64_t        mDataSize; // Size of the whole data including eventual
                             // alternative data represenation.
   int64_t        mAltDataOffset; // If there is alternative data present, it
                                  // contains size of the original data, i.e.
                                  // offset where alternative data starts.
                                  // Otherwise it is -1.
   nsCString      mKey;
+  nsCString      mAltDataType; // The type of the saved alt-data. May be empty.
 
   RefPtr<CacheFileHandle>      mHandle;
   RefPtr<CacheFileMetadata>    mMetadata;
   nsCOMPtr<CacheFileListener>  mListener;
   nsCOMPtr<CacheFileIOListener>   mDoomAfterOpenListener;
   Atomic<bool, Relaxed>        mKill;
 
   nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mChunks;
--- a/netwerk/cache2/OldWrappers.cpp
+++ b/netwerk/cache2/OldWrappers.cpp
@@ -391,16 +391,21 @@ NS_IMETHODIMP _OldCacheEntryWrapper::Get
   return NS_OK;
 }
 
 NS_IMETHODIMP _OldCacheEntryWrapper::GetAltDataSize(int64_t *aSize)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+NS_IMETHODIMP _OldCacheEntryWrapper::GetAltDataType(nsACString &aType)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 NS_IMETHODIMP _OldCacheEntryWrapper::GetPersistent(bool *aPersistToDisk)
 {
   if (!mOldDesc) {
     return NS_ERROR_NULL_POINTER;
   }
 
   nsresult rv;
 
--- a/netwerk/cache2/OldWrappers.h
+++ b/netwerk/cache2/OldWrappers.h
@@ -155,16 +155,17 @@ public:
   NS_IMETHOD GetPersistent(bool *aPersistToDisk) override;
   NS_IMETHOD GetIsForcedValid(bool *aIsForcedValid) override;
   NS_IMETHOD ForceValidFor(uint32_t aSecondsToTheFuture) override;
   NS_IMETHOD SetValid() override { return NS_OK; }
   NS_IMETHOD MetaDataReady() override { return NS_OK; }
   NS_IMETHOD Recreate(bool, nsICacheEntry**) override;
   NS_IMETHOD GetDataSize(int64_t *size) override;
   NS_IMETHOD GetAltDataSize(int64_t *size) override;
+  NS_IMETHOD GetAltDataType(nsACString &aType) override;
   NS_IMETHOD OpenInputStream(int64_t offset, nsIInputStream * *_retval) override;
   NS_IMETHOD OpenOutputStream(int64_t offset, int64_t predictedSize, nsIOutputStream * *_retval) override;
   NS_IMETHOD MaybeMarkValid() override;
   NS_IMETHOD HasWriteAccess(bool aWriteOnly, bool *aWriteAccess) override;
   NS_IMETHOD VisitMetaData(nsICacheEntryMetaDataVisitor*) override;
 
   explicit _OldCacheEntryWrapper(nsICacheEntryDescriptor* desc);
   explicit _OldCacheEntryWrapper(nsICacheEntryInfo* info);
--- a/netwerk/cache2/nsICacheEntry.idl
+++ b/netwerk/cache2/nsICacheEntry.idl
@@ -235,16 +235,23 @@ interface nsICacheEntry : nsISupports
   * @throws
   *    - NS_ERROR_IN_PROGRESS when a write is still in progress (either real
                               content or alt data).
   *    - NS_ERROR_NOT_AVAILABLE if alt data does not exist.
   */
   readonly attribute long long altDataSize;
 
   /**
+  * Returns the type of the saved alt data.
+  * @throws
+  *    - NS_ERROR_NOT_AVAILABLE if alt data does not exist.
+  */
+  readonly attribute ACString altDataType;
+
+  /**
    * Opens and returns an output stream that a consumer may use to save an
    * alternate representation of the data.
    *
    * @param type
    *        type of the alternative data representation
    * @param predictedSize
    *        Predicted size of the data that will be written. It's used to decide
    *        whether the resulting entry would exceed size limit, in which case
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -13,16 +13,17 @@ include ClientIPCTypes;
 include URIParams;
 include IPCServiceWorkerDescriptor;
 include IPCStream;
 include PBackgroundSharedTypes;
 
 using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 using RequestHeaderTuples from "mozilla/net/PHttpChannelParams.h";
+using ArrayOfStringPairs from "mozilla/net/PHttpChannelParams.h";
 using struct nsHttpAtom from "nsHttp.h";
 using class mozilla::net::nsHttpResponseHead from "nsHttpResponseHead.h";
 using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
 using Telemetry::LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED from "DocumentAnalyticsTrackerFastBlocked.h";
 
 namespace mozilla {
 namespace net {
 
@@ -240,17 +241,17 @@ struct HttpChannelOpenArgs
   bool                        suspendAfterSynthesizeResponse;
   bool                        allowStaleCacheContent;
   nsCString                   contentTypeHint;
   uint32_t                    corsMode;
   uint32_t                    redirectMode;
   uint64_t                    channelId;
   nsString                    integrityMetadata;
   uint64_t                    contentWindowId;
-  nsCString                   preferredAlternativeType;
+  ArrayOfStringPairs          preferredAlternativeTypes;
   uint64_t                    topLevelOuterContentWindowId;
   TimeStamp                   launchServiceWorkerStart;
   TimeStamp                   launchServiceWorkerEnd;
   TimeStamp                   dispatchFetchEventStart;
   TimeStamp                   dispatchFetchEventEnd;
   TimeStamp                   handleFetchEventStart;
   TimeStamp                   handleFetchEventEnd;
   bool                        forceMainDocumentChannel;
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -3904,17 +3904,19 @@ HttpBaseChannel::SetupReplacementChannel
     newTimedChannel->SetDispatchFetchEventEnd(mDispatchFetchEventEnd);
     newTimedChannel->SetHandleFetchEventStart(mHandleFetchEventStart);
     newTimedChannel->SetHandleFetchEventEnd(mHandleFetchEventEnd);
   }
 
   // Pass the preferred alt-data type on to the new channel.
   nsCOMPtr<nsICacheInfoChannel> cacheInfoChan(do_QueryInterface(newChannel));
   if (cacheInfoChan) {
-    cacheInfoChan->PreferAlternativeDataType(mPreferredCachedAltDataType);
+    for (auto& pair : mPreferredCachedAltDataTypes) {
+      cacheInfoChan->PreferAlternativeDataType(mozilla::Get<0>(pair), mozilla::Get<1>(pair));
+    }
   }
 
   if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
                        nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
     // Copy non-origin related headers to the new channel.
     nsCOMPtr<nsIHttpHeaderVisitor> visitor =
       new AddHeadersToChannelVisitor(httpChannel);
     rv = mRequestHead.VisitHeaders(visitor);
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -41,16 +41,17 @@
 #include "PrivateBrowsingChannel.h"
 #include "mozilla/net/DNS.h"
 #include "nsITimedChannel.h"
 #include "nsIHttpChannel.h"
 #include "nsISecurityConsoleMessage.h"
 #include "nsCOMArray.h"
 #include "mozilla/net/ChannelEventQueue.h"
 #include "mozilla/Move.h"
+#include "mozilla/Tuple.h"
 #include "nsIThrottledInputChannel.h"
 #include "nsTArray.h"
 #include "nsCOMPtr.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "nsStringEnumerator.h"
 
 #define HTTP_BASE_CHANNEL_IID \
 { 0x9d5cde03, 0xe6e9, 0x4612, \
@@ -66,16 +67,18 @@ namespace dom {
 class PerformanceStorage;
 }
 
 class LogCollector;
 
 namespace net {
 extern mozilla::LazyLogModule gHttpLog;
 
+typedef nsTArray<Tuple<nsCString, nsCString>> ArrayOfStringPairs;
+
 /*
  * This class is a partial implementation of nsIHttpChannel.  It contains code
  * shared by nsHttpChannel and HttpChannelChild.
  * - Note that this class has nothing to do with nsBaseChannel, which is an
  *   earlier effort at a base class for channels that somehow never made it all
  *   the way to the HTTP channel.
  */
 class HttpBaseChannel : public nsHashPropertyBag
@@ -548,18 +551,18 @@ protected:
   nsCString mUserSetCookieHeader;
   // HTTP Upgrade Data
   nsCString mUpgradeProtocol;
   // Resumable channel specific data
   nsCString mEntityID;
   // The initiator type (for this resource) - how was the resource referenced in
   // the HTML file.
   nsString mInitiatorType;
-  // Holds the name of the preferred alt-data type.
-  nsCString mPreferredCachedAltDataType;
+  // Holds the name of the preferred alt-data type for each contentType.
+  ArrayOfStringPairs mPreferredCachedAltDataTypes;
   // Holds the name of the alternative data type the channel returned.
   nsCString mAvailableCachedAltDataType;
   nsString mIntegrityMetadata;
 
   // Classified channel's matched information
   nsCString mMatchedList;
   nsCString mMatchedProvider;
   nsCString mMatchedFullHash;
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -1262,17 +1262,17 @@ HttpChannelChild::OnStopRequest(const ns
 
   CleanupBackgroundChannel();
 
   // If there is a possibility we might want to write alt data to the cache
   // entry, we keep the channel alive. We still send the DocumentChannelCleanup
   // message but request the cache entry to be kept by the parent.
   // If the channel has failed, the cache entry is in a non-writtable state and
   // we want to release it to not block following consumers.
-  if (NS_SUCCEEDED(channelStatus) && !mPreferredCachedAltDataType.IsEmpty()) {
+  if (NS_SUCCEEDED(channelStatus) && !mPreferredCachedAltDataTypes.IsEmpty()) {
     mKeptAlive = true;
     SendDocumentChannelCleanup(false); // don't clear cache entry
     return;
   }
 
   if (mLoadFlags & LOAD_DOCUMENT_URI) {
     // Keep IPDL channel open, but only for updating security info.
     // If IPDL is already closed, then do nothing.
@@ -1369,17 +1369,17 @@ HttpChannelChild::DoOnStopRequest(nsIReq
   // notify "http-on-stop-connect" observers
   gHttpHandler->OnStopRequest(this);
 
   ReleaseListeners();
 
   // If a preferred alt-data type was set, the parent would hold a reference to
   // the cache entry in case the child calls openAlternativeOutputStream().
   // (see nsHttpChannel::OnStopRequest)
-  if (!mPreferredCachedAltDataType.IsEmpty()) {
+  if (!mPreferredCachedAltDataTypes.IsEmpty()) {
     mAltDataCacheEntryAvailable = mCacheEntryAvailable;
   }
   mCacheEntryAvailable = false;
 
   if (mLoadGroup)
     mLoadGroup->RemoveRequest(this, nullptr, mStatus);
 }
 
@@ -2869,17 +2869,17 @@ HttpChannelChild::ContinueAsyncOpen()
   SerializeURI(mOriginalURI, openArgs.original());
   SerializeURI(mDocumentURI, openArgs.doc());
   SerializeURI(mReferrer, openArgs.referrer());
   openArgs.referrerPolicy() = mReferrerPolicy;
   SerializeURI(mAPIRedirectToURI, openArgs.apiRedirectTo());
   openArgs.loadFlags() = mLoadFlags;
   openArgs.requestHeaders() = mClientSetRequestHeaders;
   mRequestHead.Method(openArgs.requestMethod());
-  openArgs.preferredAlternativeType() = mPreferredCachedAltDataType;
+  openArgs.preferredAlternativeTypes() = mPreferredCachedAltDataTypes;
 
   AutoIPCStream autoStream(openArgs.uploadStream());
   if (mUploadStream) {
     autoStream.Serialize(mUploadStream, ContentChild::GetSingleton());
     autoStream.TakeOptionalValue();
   }
 
   if (mResponseHead) {
@@ -3266,33 +3266,33 @@ HttpChannelChild::GetAllowStaleCacheCont
   }
 
   NS_ENSURE_ARG(aAllowStaleCacheContent);
   *aAllowStaleCacheContent = mAllowStaleCacheContent;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-HttpChannelChild::PreferAlternativeDataType(const nsACString & aType)
+HttpChannelChild::PreferAlternativeDataType(const nsACString& aType,
+                                            const nsACString& aContentType)
 {
   ENSURE_CALLED_BEFORE_ASYNC_OPEN();
 
   if (mSynthesizedCacheInfo) {
-    return mSynthesizedCacheInfo->PreferAlternativeDataType(aType);
+    return mSynthesizedCacheInfo->PreferAlternativeDataType(aType, aContentType);
   }
 
-  mPreferredCachedAltDataType = aType;
+  mPreferredCachedAltDataTypes.AppendElement(MakePair(nsCString(aType), nsCString(aContentType)));
   return NS_OK;
 }
 
-NS_IMETHODIMP
-HttpChannelChild::GetPreferredAlternativeDataType(nsACString & aType)
+const nsTArray<mozilla::Tuple<nsCString, nsCString>>&
+HttpChannelChild::PreferredAlternativeDataTypes()
 {
-  aType = mPreferredCachedAltDataType;
-  return NS_OK;
+  return mPreferredCachedAltDataTypes;
 }
 
 NS_IMETHODIMP
 HttpChannelChild::GetAlternativeDataType(nsACString & aType)
 {
   if (mSynthesizedCacheInfo) {
     return mSynthesizedCacheInfo->GetAlternativeDataType(aType);
   }
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -148,17 +148,17 @@ HttpChannelParent::Init(const HttpChanne
                        a.tlsFlags(), a.loadInfo(), a.synthesizedResponseHead(),
                        a.synthesizedSecurityInfoSerialization(),
                        a.cacheKey(), a.requestContextID(), a.preflightArgs(),
                        a.initialRwin(), a.blockAuthPrompt(),
                        a.suspendAfterSynthesizeResponse(),
                        a.allowStaleCacheContent(), a.contentTypeHint(),
                        a.corsMode(), a.redirectMode(),
                        a.channelId(), a.integrityMetadata(),
-                       a.contentWindowId(), a.preferredAlternativeType(),
+                       a.contentWindowId(), a.preferredAlternativeTypes(),
                        a.topLevelOuterContentWindowId(),
                        a.launchServiceWorkerStart(),
                        a.launchServiceWorkerEnd(),
                        a.dispatchFetchEventStart(),
                        a.dispatchFetchEventEnd(),
                        a.handleFetchEventStart(),
                        a.handleFetchEventEnd(),
                        a.forceMainDocumentChannel(),
@@ -450,17 +450,17 @@ HttpChannelParent::DoAsyncOpen(  const U
                                  const bool&                aSuspendAfterSynthesizeResponse,
                                  const bool&                aAllowStaleCacheContent,
                                  const nsCString&           aContentTypeHint,
                                  const uint32_t&            aCorsMode,
                                  const uint32_t&            aRedirectMode,
                                  const uint64_t&            aChannelId,
                                  const nsString&            aIntegrityMetadata,
                                  const uint64_t&            aContentWindowId,
-                                 const nsCString&           aPreferredAlternativeType,
+                                 const ArrayOfStringPairs&  aPreferredAlternativeTypes,
                                  const uint64_t&            aTopLevelOuterContentWindowId,
                                  const TimeStamp&           aLaunchServiceWorkerStart,
                                  const TimeStamp&           aLaunchServiceWorkerEnd,
                                  const TimeStamp&           aDispatchFetchEventStart,
                                  const TimeStamp&           aDispatchFetchEventEnd,
                                  const TimeStamp&           aHandleFetchEventStart,
                                  const TimeStamp&           aHandleFetchEventEnd,
                                  const bool&                aForceMainDocumentChannel,
@@ -609,17 +609,19 @@ HttpChannelParent::DoAsyncOpen(  const U
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
   nsCOMPtr<nsICacheInfoChannel> cacheChannel =
     do_QueryInterface(static_cast<nsIChannel*>(httpChannel.get()));
   if (cacheChannel) {
     cacheChannel->SetCacheKey(aCacheKey);
-    cacheChannel->PreferAlternativeDataType(aPreferredAlternativeType);
+    for (auto& pair : aPreferredAlternativeTypes) {
+      cacheChannel->PreferAlternativeDataType(mozilla::Get<0>(pair), mozilla::Get<1>(pair));
+    }
 
     cacheChannel->SetAllowStaleCacheContent(aAllowStaleCacheContent);
 
     // This is to mark that the results are going to the content process.
     if (httpChannelImpl) {
       httpChannelImpl->SetAltDataForChild(true);
     }
   }
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -168,17 +168,17 @@ protected:
               const bool&                aSuspendAfterSynthesizeResponse,
               const bool&                aAllowStaleCacheContent,
               const nsCString&           aContentTypeHint,
               const uint32_t&            aCorsMode,
               const uint32_t&            aRedirectMode,
               const uint64_t&            aChannelId,
               const nsString&            aIntegrityMetadata,
               const uint64_t&            aContentWindowId,
-              const nsCString&           aPreferredAlternativeType,
+              const ArrayOfStringPairs&  aPreferredAlternativeTypes,
               const uint64_t&            aTopLevelOuterContentWindowId,
               const TimeStamp&           aLaunchServiceWorkerStart,
               const TimeStamp&           aLaunchServiceWorkerEnd,
               const TimeStamp&           aDispatchFetchEventStart,
               const TimeStamp&           aDispatchFetchEventEnd,
               const TimeStamp&           aHandleFetchEventStart,
               const TimeStamp&           aHandleFetchEventEnd,
               const bool&                aForceMainDocumentChannel,
--- a/netwerk/protocol/http/InterceptedHttpChannel.cpp
+++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp
@@ -1310,28 +1310,28 @@ InterceptedHttpChannel::GetAllowStaleCac
 {
   if (mSynthesizedCacheInfo) {
     return mSynthesizedCacheInfo->GetAllowStaleCacheContent(aAllowStaleCacheContent);
   }
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 NS_IMETHODIMP
-InterceptedHttpChannel::PreferAlternativeDataType(const nsACString & aType)
+InterceptedHttpChannel::PreferAlternativeDataType(const nsACString & aType,
+                                                  const nsACString& aContentType)
 {
   ENSURE_CALLED_BEFORE_ASYNC_OPEN();
-  mPreferredCachedAltDataType = aType;
+  mPreferredCachedAltDataTypes.AppendElement(MakePair(nsCString(aType), nsCString(aContentType)));
   return NS_OK;
 }
 
-NS_IMETHODIMP
-InterceptedHttpChannel::GetPreferredAlternativeDataType(nsACString & aType)
+const nsTArray<mozilla::Tuple<nsCString, nsCString>>&
+InterceptedHttpChannel::PreferredAlternativeDataTypes()
 {
-  aType = mPreferredCachedAltDataType;
-  return NS_OK;
+  return mPreferredCachedAltDataTypes;
 }
 
 NS_IMETHODIMP
 InterceptedHttpChannel::GetAlternativeDataType(nsACString & aType)
 {
   if (mSynthesizedCacheInfo) {
     return mSynthesizedCacheInfo->GetAlternativeDataType(aType);
   }
--- a/netwerk/protocol/http/PHttpChannelParams.h
+++ b/netwerk/protocol/http/PHttpChannelParams.h
@@ -9,16 +9,17 @@
 
 #define ALLOW_LATE_NSHTTP_H_INCLUDE 1
 #include "base/basictypes.h"
 
 #include "ipc/IPCMessageUtils.h"
 #include "nsHttp.h"
 #include "nsHttpHeaderArray.h"
 #include "nsHttpResponseHead.h"
+#include "mozilla/Tuple.h"
 
 #include "nsIClassInfo.h"
 
 namespace mozilla {
 namespace net {
 
 struct RequestHeaderTuple {
   nsCString mHeader;
@@ -31,22 +32,48 @@ struct RequestHeaderTuple {
            mValue.Equals(other.mValue) &&
            mMerge == other.mMerge &&
            mEmpty == other.mEmpty;
   }
 };
 
 typedef nsTArray<RequestHeaderTuple> RequestHeaderTuples;
 
+typedef nsTArray<Tuple<nsCString, nsCString>> ArrayOfStringPairs;
+
 } // namespace net
 } // namespace mozilla
 
 namespace IPC {
 
 template<>
+struct ParamTraits<mozilla::Tuple<nsCString, nsCString>>
+{
+  typedef mozilla::Tuple<nsCString, nsCString> paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, mozilla::Get<0>(aParam));
+    WriteParam(aMsg, mozilla::Get<1>(aParam));
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    nsCString first;
+    nsCString second;
+    if (!ReadParam(aMsg, aIter, &first) ||
+        !ReadParam(aMsg, aIter, &second))
+      return false;
+
+    *aResult = mozilla::MakeTuple(first, second);
+    return true;
+  }
+};
+
+template<>
 struct ParamTraits<mozilla::net::RequestHeaderTuple>
 {
   typedef mozilla::net::RequestHeaderTuple paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.mHeader);
     WriteParam(aMsg, aParam.mValue);
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -4998,30 +4998,50 @@ nsHttpChannel::OpenCacheInputStream(nsIC
     bool altDataFromChild = false;
     {
         nsCString value;
         rv = cacheEntry->GetMetaDataElement("alt-data-from-child",
                                             getter_Copies(value));
         altDataFromChild = !value.IsEmpty();
     }
 
-    if (!mPreferredCachedAltDataType.IsEmpty() && (altDataFromChild == mAltDataForChild)) {
-        rv = cacheEntry->OpenAlternativeInputStream(mPreferredCachedAltDataType,
+    nsAutoCString altDataType;
+    Unused << cacheEntry->GetAltDataType(altDataType);
+
+    nsAutoCString contentType;
+    mCachedResponseHead->ContentType(contentType);
+
+    bool foundAltData = false;
+    if (!altDataType.IsEmpty() &&
+        !mPreferredCachedAltDataTypes.IsEmpty() &&
+        altDataFromChild == mAltDataForChild) {
+        for (auto& pref : mPreferredCachedAltDataTypes) {
+            if (mozilla::Get<0>(pref) == altDataType &&
+                (mozilla::Get<1>(pref).IsEmpty() || mozilla::Get<1>(pref) == contentType)) {
+                foundAltData = true;
+                break;
+            }
+        }
+    }
+    if (foundAltData) {
+        rv = cacheEntry->OpenAlternativeInputStream(altDataType,
                                                     getter_AddRefs(stream));
         if (NS_SUCCEEDED(rv)) {
+            LOG(("Opened alt-data input stream type=%s", altDataType.get()));
             // We have succeeded.
-            mAvailableCachedAltDataType = mPreferredCachedAltDataType;
+            mAvailableCachedAltDataType = altDataType;
             // Set the correct data size on the channel.
             int64_t altDataSize;
             if (NS_SUCCEEDED(cacheEntry->GetAltDataSize(&altDataSize))) {
                 mAltDataLength = altDataSize;
             }
         }
     }
 
+
     if (!stream) {
         rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream));
     }
 
     if (NS_FAILED(rv)) {
         LOG(("Failed to open cache input stream [channel=%p, "
              "mCacheEntry=%p]", this, cacheEntry));
         return rv;
@@ -7859,17 +7879,17 @@ nsHttpChannel::OnStopRequest(nsIRequest 
     gHttpHandler->OnStopRequest(this);
 
     RemoveAsNonTailRequest();
 
     // If a preferred alt-data type was set, this signals the consumer is
     // interested in reading and/or writing the alt-data representation.
     // We need to hold a reference to the cache entry in case the listener calls
     // openAlternativeOutputStream() after CloseCacheEntry() clears mCacheEntry.
-    if (!mPreferredCachedAltDataType.IsEmpty()) {
+    if (!mPreferredCachedAltDataTypes.IsEmpty()) {
         mAltDataCacheEntry = mCacheEntry;
     }
 
     CloseCacheEntry(!contentComplete);
 
     if (mOfflineCacheEntry)
         CloseOfflineCacheEntry();
 
@@ -8281,28 +8301,28 @@ NS_IMETHODIMP
 nsHttpChannel::GetAllowStaleCacheContent(bool *aAllowStaleCacheContent)
 {
     NS_ENSURE_ARG(aAllowStaleCacheContent);
     *aAllowStaleCacheContent = mAllowStaleCacheContent;
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsHttpChannel::PreferAlternativeDataType(const nsACString & aType)
+nsHttpChannel::PreferAlternativeDataType(const nsACString& aType,
+                                         const nsACString& aContentType)
 {
     ENSURE_CALLED_BEFORE_ASYNC_OPEN();
-    mPreferredCachedAltDataType = aType;
+    mPreferredCachedAltDataTypes.AppendElement(MakePair(nsCString(aType), nsCString(aContentType)));
     return NS_OK;
 }
 
-NS_IMETHODIMP
-nsHttpChannel::GetPreferredAlternativeDataType(nsACString & aType)
-{
-  aType = mPreferredCachedAltDataType;
-  return NS_OK;
+const nsTArray<mozilla::Tuple<nsCString, nsCString>>&
+nsHttpChannel::PreferredAlternativeDataTypes()
+{
+    return mPreferredCachedAltDataTypes;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::GetAlternativeDataType(nsACString & aType)
 {
     // must be called during or after OnStartRequest
     if (!mAfterOnStartRequestBegun) {
         return NS_ERROR_NOT_AVAILABLE;
--- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -1141,8 +1141,17 @@ nsViewSourceChannel::LogBlockedCORSReque
                                            const nsACString& aCategory)
 {
   if (!mHttpChannel) {
     NS_WARNING("nsViewSourceChannel::LogBlockedCORSRequest mHttpChannel is null");
     return NS_ERROR_UNEXPECTED;
   }
   return mHttpChannel->LogBlockedCORSRequest(aMessage, aCategory);
 }
+
+const nsTArray<mozilla::Tuple<nsCString, nsCString>>&
+nsViewSourceChannel::PreferredAlternativeDataTypes()
+{
+    if (mCacheInfoChannel) {
+        return mCacheInfoChannel->PreferredAlternativeDataTypes();
+    }
+    return mEmptyArray;
+}
--- a/netwerk/protocol/viewsource/nsViewSourceChannel.h
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.h
@@ -60,16 +60,17 @@ public:
     // loadinfo to be prefixed with the "view-source:" schema as:
     //
     // mChannel.loadInfo.resultPrincipalURI = "view-source:" +
     //    (mChannel.loadInfo.resultPrincipalURI | mChannel.orignalURI);
     nsresult UpdateLoadInfoResultPrincipalURI();
 
 protected:
     ~nsViewSourceChannel() = default;
+    nsTArray<mozilla::Tuple<nsCString, nsCString>> mEmptyArray;
 
     // Clones aURI and prefixes it with "view-source:" schema,
     nsresult BuildViewSourceURI(nsIURI* aURI, nsIURI** aResult);
 
     nsCOMPtr<nsIChannel>        mChannel;
     nsCOMPtr<nsIHttpChannel>    mHttpChannel;
     nsCOMPtr<nsIHttpChannelInternal>    mHttpChannelInternal;
     nsCOMPtr<nsICachingChannel> mCachingChannel;
--- a/netwerk/test/unit/test_alt-data_cross_process.js
+++ b/netwerk/test/unit/test_alt-data_cross_process.js
@@ -80,17 +80,17 @@ function run_test()
   asyncOpen();
 }
 
 function asyncOpen()
 {
   var chan = make_channel(URL);
 
   var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
-  cc.preferAlternativeDataType(altContentType);
+  cc.preferAlternativeDataType(altContentType, "");
 
   chan.asyncOpen2(new ChannelListener(readServerContent, null));
 }
 
 function readServerContent(request, buffer)
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
@@ -115,17 +115,17 @@ function flushAndOpenAltChannel()
   do_await_remote_message('flushed').then(() => {
     openAltChannel();
   });
 }
 
 function openAltChannel() {
   var chan = make_channel(URL);
   var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
-  cc.preferAlternativeDataType(altContentType);
+  cc.preferAlternativeDataType(altContentType, "");
 
   chan.asyncOpen2(new ChannelListener(readAltContent, null));
 }
 
 function readAltContent(request, buffer)
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
--- a/netwerk/test/unit/test_alt-data_overwrite.js
+++ b/netwerk/test/unit/test_alt-data_overwrite.js
@@ -21,17 +21,17 @@ XPCOMUtils.defineLazyGetter(this, "URL",
 });
 
 let httpServer = null;
 
 function make_and_open_channel(url, altContentType, callback) {
   let chan = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
   if (altContentType) {
     let cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
-    cc.preferAlternativeDataType(altContentType);
+    cc.preferAlternativeDataType(altContentType, "");
   }
   chan.asyncOpen2(new ChannelListener(callback, null));
 }
 
 const responseContent = "response body";
 const altContent = "!@#$%^&*()";
 const altContentType = "text/binary";
 const altContent2 = "abc";
--- a/netwerk/test/unit/test_alt-data_simple.js
+++ b/netwerk/test/unit/test_alt-data_simple.js
@@ -86,17 +86,17 @@ function run_test()
   }
 }
 
 function asyncOpen()
 {
   var chan = make_channel(URL);
 
   var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
-  cc.preferAlternativeDataType(altContentType);
+  cc.preferAlternativeDataType(altContentType, "");
 
   chan.asyncOpen2(new ChannelListener(readServerContent, null));
 }
 
 function readServerContent(request, buffer)
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
@@ -131,17 +131,19 @@ function flushAndOpenAltChannel()
       openAltChannel();
     });
   }
 }
 
 function openAltChannel() {
   var chan = make_channel(URL);
   var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
-  cc.preferAlternativeDataType(altContentType);
+  cc.preferAlternativeDataType("dummy1", "text/javascript");
+  cc.preferAlternativeDataType(altContentType, "text/plain");
+  cc.preferAlternativeDataType("dummy2", "");
 
   chan.asyncOpen2(new ChannelListener(readAltContent, null));
 }
 
 function readAltContent(request, buffer)
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
@@ -153,17 +155,17 @@ function readAltContent(request, buffer)
   requestAgain();
 }
 
 function requestAgain()
 {
   shouldPassRevalidation = false;
   var chan = make_channel(URL);
   var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
-  cc.preferAlternativeDataType(altContentType);
+  cc.preferAlternativeDataType(altContentType, "");
   chan.asyncOpen2(new ChannelListener(readEmptyAltContent, null));
 }
 
 function readEmptyAltContent(request, buffer)
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   // the cache is overwrite and the alt-data is reset
--- a/netwerk/test/unit/test_alt-data_stream.js
+++ b/netwerk/test/unit/test_alt-data_stream.js
@@ -55,17 +55,17 @@ function run_test()
   do_get_profile();
   httpServer = new HttpServer();
   httpServer.registerPathHandler("/content", contentHandler);
   httpServer.start(-1);
 
   var chan = make_channel(URL);
 
   var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
-  cc.preferAlternativeDataType(altContentType);
+  cc.preferAlternativeDataType(altContentType, "");
 
   chan.asyncOpen2(new ChannelListener(readServerContent, null));
   do_test_pending();
 }
 
 // Output stream used to write alt-data to the cache entry.
 var os;
 
@@ -84,17 +84,17 @@ function readServerContent(request, buff
     executeSoon(openAltChannel);
   });
 }
 
 function openAltChannel()
 {
   var chan = make_channel(URL);
   var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
-  cc.preferAlternativeDataType(altContentType);
+  cc.preferAlternativeDataType(altContentType, "");
 
   chan.asyncOpen2(listener);
 }
 
 var listener = {
   buffer: "",
   onStartRequest: function(request, context) { },
   onDataAvailable: function(request, context, stream, offset, count) {
--- a/netwerk/test/unit/test_cache-entry-id.js
+++ b/netwerk/test/unit/test_cache-entry-id.js
@@ -48,17 +48,17 @@ function contentHandler(metadata, respon
 
 function fetch(preferredDataType = null)
 {
   return new Promise(resolve => {
     var chan = NetUtil.newChannel({uri: URL, loadUsingSystemPrincipal: true});
 
     if (preferredDataType) {
       var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
-      cc.preferAlternativeDataType(altContentType);
+      cc.preferAlternativeDataType(altContentType, "");
     }
 
     chan.asyncOpen2(new ChannelListener((request,
                                          buffer,
                                          ctx,
                                          isFromCache,
                                          cacheEntryId) => {
       resolve({request, buffer, isFromCache, cacheEntryId});
--- a/netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js
+++ b/netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js
@@ -27,17 +27,17 @@ function run_test() {
   run_test_in_child("../unit/test_alt-data_cross_process.js");
 }
 
 function load_channel(url) {
   ok(url);
   URL = url; // save this to open the alt data channel later
   var chan = make_channel(url);
   var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
-  cc.preferAlternativeDataType("text/binary");
+  cc.preferAlternativeDataType("text/binary", "");
   chan.asyncOpen2(new ChannelListener(readTextData, null));
 }
 
 function make_channel(url, callback, ctx) {
   return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
 }
 
 function readTextData(request, buffer)
@@ -59,17 +59,17 @@ function readTextData(request, buffer)
       Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(cacheFlushObserver2);
     });
   });
 }
 
 function openAltChannel() {
   var chan = make_channel(URL);
   var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
-  cc.preferAlternativeDataType("text/parent-binary");
+  cc.preferAlternativeDataType("text/parent-binary", "");
   chan.asyncOpen2(new ChannelListener(readAltData, null));
 }
 
 function readAltData(request, buffer)
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   // This was generated in the parent, so it's OK to get it.