Bug 1231565 - (Part 2) Allow storing alternate data in the HTTP cache r=honzab
authorValentin Gosu <valentin.gosu@gmail.com>
Mon, 11 Apr 2016 05:17:02 +0200
changeset 315428 f6c8123885905804ac06f6dc01d0f7f969f0ceb8
parent 315427 40417bdcaedecc7f7430bc45ac1b703eb7859d34
child 315429 d3a0e25cebb306ee52992d15cffbc47248309f91
push id30750
push usercbook@mozilla.com
push dateWed, 28 Sep 2016 13:57:20 +0000
treeherdermozilla-central@b1d60f2f68c7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs1231565
milestone52.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 1231565 - (Part 2) Allow storing alternate data in the HTTP cache r=honzab * Add PAltDataOutputStream.ipdl to be able to open an OutputStream to the cache entry in the child process * AltDataOutputStreamChild/Parent are Main Thread only for now. * Adds methods for reading and writing alt-data to nsICacheInfoChannel.idl * Keep a ref of the cache entry after OnStopRequest in case the consumer tries to open the alt-data output stream MozReview-Commit-ID: jlraDI97Hg
netwerk/base/nsICacheInfoChannel.idl
netwerk/cache2/CacheEntry.cpp
netwerk/cache2/CacheEntry.h
netwerk/cache2/OldWrappers.h
netwerk/cache2/nsICacheEntry.idl
netwerk/ipc/NeckoChannelParams.ipdlh
netwerk/ipc/NeckoChild.cpp
netwerk/ipc/NeckoChild.h
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/NeckoParent.h
netwerk/ipc/PNecko.ipdl
netwerk/protocol/http/AltDataOutputStreamChild.cpp
netwerk/protocol/http/AltDataOutputStreamChild.h
netwerk/protocol/http/AltDataOutputStreamParent.cpp
netwerk/protocol/http/AltDataOutputStreamParent.h
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/PAltDataOutputStream.ipdl
netwerk/protocol/http/PHttpChannel.ipdl
netwerk/protocol/http/moz.build
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
--- a/netwerk/base/nsICacheInfoChannel.idl
+++ b/netwerk/base/nsICacheInfoChannel.idl
@@ -1,14 +1,16 @@
 /* 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;
+
 [scriptable, uuid(72c34415-c6eb-48af-851f-772fa9ee5972)]
 interface nsICacheInfoChannel : nsISupports
 {
   /**
    * Get expiration time from cache token. This attribute is equivalent to
    * nsICachingChannel.cacheToken.expirationTime.
    */
   readonly attribute uint32_t cacheTokenExpirationTime;
@@ -47,9 +49,36 @@ interface nsICacheInfoChannel : nsISuppo
   attribute nsISupports cacheKey;
 
   /**
    * Tells the channel to behave as if the LOAD_FROM_CACHE flag has been set,
    * but without affecting the loads for the entire loadGroup in case of this
    * 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.
+   * Must be called before AsyncOpen.
+   */
+  void preferAlternativeDataType(in ACString type);
+
+  /**
+   * 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.
+   */
+  readonly attribute ACString alternativeDataType;
+
+  /**
+   * Opens and returns an output stream that a consumer may use to save an
+   * alternate representation of the data.
+   * Must be called after the OnStopRequest that delivered the real data.
+   * The consumer may choose to replace the saved alt representation.
+   * Opening the output stream will fail if there are any open input streams
+   * reading the already saved alt representation.
+   */
+  nsIOutputStream openAlternativeOutputStream(in ACString type);
 };
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -1123,25 +1123,43 @@ NS_IMETHODIMP CacheEntry::SetExpirationT
   // Aligned assignment, thus atomic.
   mSortingExpirationTime = aExpirationTime;
   return NS_OK;
 }
 
 NS_IMETHODIMP CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval)
 {
   LOG(("CacheEntry::OpenInputStream [this=%p]", this));
+  return OpenInputStreamInternal(offset, nullptr, _retval);
+}
+
+NS_IMETHODIMP CacheEntry::OpenAlternativeInputStream(const nsACString & type, nsIInputStream * *_retval)
+{
+  LOG(("CacheEntry::OpenAlternativeInputStream [this=%p, type=%s]", this,
+       PromiseFlatCString(type).get()));
+  return OpenInputStreamInternal(0, PromiseFlatCString(type).get(), _retval);
+}
+
+nsresult CacheEntry::OpenInputStreamInternal(int64_t offset, const char *aAltDataType, nsIInputStream * *_retval)
+{
+  LOG(("CacheEntry::OpenInputStreamInternal [this=%p]", this));
 
   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
 
   nsresult rv;
 
   RefPtr<CacheEntryHandle> selfHandle = NewHandle();
 
   nsCOMPtr<nsIInputStream> stream;
-  rv = mFile->OpenInputStream(selfHandle, getter_AddRefs(stream));
+  if (aAltDataType) {
+    rv = mFile->OpenAlternativeInputStream(selfHandle, aAltDataType,
+                                           getter_AddRefs(stream));
+  } else {
+    rv = mFile->OpenInputStream(selfHandle, getter_AddRefs(stream));
+  }
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsISeekableStream> seekable =
     do_QueryInterface(stream, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -1183,16 +1201,40 @@ NS_IMETHODIMP CacheEntry::OpenOutputStre
     mState = READY;
 
   // Invoke any pending readers now.
   InvokeCallbacks();
 
   return NS_OK;
 }
 
+NS_IMETHODIMP CacheEntry::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval)
+{
+  LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
+       PromiseFlatCString(type).get()));
+
+  nsresult rv;
+
+  mozilla::MutexAutoLock lock(mLock);
+
+  if (!mHasData || mState < READY || mOutputStream || mIsDoomed) {
+    LOG(("  entry not in state to write alt-data"));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<nsIOutputStream> stream;
+  rv = mFile->OpenAlternativeOutputStream(nullptr,
+                                          PromiseFlatCString(type).get(),
+                                          getter_AddRefs(stream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  stream.swap(*_retval);
+  return NS_OK;
+}
+
 nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval)
 {
   LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
 
   NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
 
   mLock.AssertCurrentThreadOwns();
 
--- a/netwerk/cache2/CacheEntry.h
+++ b/netwerk/cache2/CacheEntry.h
@@ -237,16 +237,17 @@ private:
   void InvokeCallbacksLock();
   void InvokeCallbacks();
   bool InvokeCallbacks(bool aReadOnly);
   bool InvokeCallback(Callback & aCallback);
   void InvokeAvailableCallback(Callback const & aCallback);
   void OnFetched(Callback const & aCallback);
 
   nsresult OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval);
+  nsresult OpenInputStreamInternal(int64_t offset, const char *aAltDataType, nsIInputStream * *_retval);
 
   void OnHandleClosed(CacheEntryHandle const* aHandle);
 
 private:
   friend class CacheEntryHandle;
   // Increment/decrements the number of handles keeping this entry.
   void AddHandleRef() { ++mHandlesCount; }
   void ReleaseHandleRef() { --mHandlesCount; }
--- a/netwerk/cache2/OldWrappers.h
+++ b/netwerk/cache2/OldWrappers.h
@@ -40,16 +40,25 @@ public:
     return !mOldDesc ? NS_ERROR_NULL_POINTER :
                        mOldDesc->OpenInputStream(offset, _retval);
   }
   nsresult OpenOutputStream(uint32_t offset, nsIOutputStream * *_retval)
   {
     return !mOldDesc ? NS_ERROR_NULL_POINTER :
                        mOldDesc->OpenOutputStream(offset, _retval);
   }
+  NS_IMETHOD OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval) override
+  {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+  NS_IMETHOD OpenAlternativeInputStream(const nsACString & type, nsIInputStream * *_retval) override
+  {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
   NS_IMETHOD GetPredictedDataSize(int64_t *aPredictedDataSize) override
   {
     return !mOldDesc ? NS_ERROR_NULL_POINTER :
                        mOldDesc->GetPredictedDataSize(aPredictedDataSize);
   }
   NS_IMETHOD SetPredictedDataSize(int64_t aPredictedDataSize) override
   {
     return !mOldDesc ? NS_ERROR_NULL_POINTER :
--- a/netwerk/cache2/nsICacheEntry.idl
+++ b/netwerk/cache2/nsICacheEntry.idl
@@ -191,16 +191,39 @@ interface nsICacheEntry : nsISupports
 
   /**
    * Returns the length of data this entry holds.
    * @throws
    *    NS_ERROR_IN_PROGRESS when the write is still in progress.
    */
   readonly attribute long long dataSize;
 
+  /**
+   * Opens and returns an output stream that a consumer may use to save an
+   * alternate representation of the data.
+   * @throws
+   *    - NS_ERROR_NOT_AVAILABLE if the real data hasn't been written.
+   *    - NS_ERROR_IN_PROGRESS when the writing regular content or alt-data to
+   *      the cache entry is still in progress.
+   *
+   * If there is alt-data already saved, it will be overwritten.
+   */
+  nsIOutputStream openAlternativeOutputStream(in ACString type);
+
+  /**
+   * Opens and returns an input stream that can be used to read the alternative
+   * representation previously saved in the cache.
+   * If this call is made while writing alt-data is still in progress, it is
+   * still possible to read content from the input stream as it's being written.
+   * @throws
+   *    - NS_ERROR_NOT_AVAILABLE if the alt-data representation doesn't exist at
+   *      all or if alt-data of the given type doesn't exist.
+   */
+  nsIInputStream openAlternativeInputStream(in ACString type);
+
   /****************************************************************************
    * The following methods might be added to some nsICacheEntryInternal
    * interface since we want to remove them as soon as the old cache backend is
    * completely removed.
    */
 
   /**
    * @deprecated
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -121,16 +121,17 @@ struct HttpChannelOpenArgs
   nsCString                   requestContextID;
   OptionalCorsPreflightArgs   preflightArgs;
   uint32_t                    initialRwin;
   bool                        blockAuthPrompt;
   bool                        suspendAfterSynthesizeResponse;
   bool                        allowStaleCacheContent;
   nsCString                   contentTypeHint;
   nsCString                   channelId;
+  nsCString                   preferredAlternativeType;
 };
 
 struct HttpChannelConnectArgs
 {
   uint32_t registrarId;
   bool shouldIntercept;
 };
 
--- a/netwerk/ipc/NeckoChild.cpp
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -18,16 +18,18 @@
 #include "mozilla/net/WebSocketEventListenerChild.h"
 #include "mozilla/net/DNSRequestChild.h"
 #include "mozilla/net/RemoteOpenFileChild.h"
 #include "mozilla/net/ChannelDiverterChild.h"
 #include "mozilla/net/IPCTransportProvider.h"
 #include "mozilla/dom/network/TCPSocketChild.h"
 #include "mozilla/dom/network/TCPServerSocketChild.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
+#include "mozilla/net/AltDataOutputStreamChild.h"
+
 #ifdef NECKO_PROTOCOL_rtsp
 #include "mozilla/net/RtspControllerChild.h"
 #include "mozilla/net/RtspChannelChild.h"
 #endif
 #include "SerializedLoadContext.h"
 #include "nsIOService.h"
 #include "nsINetworkPredictor.h"
 #include "nsINetworkPredictorVerifier.h"
@@ -83,16 +85,34 @@ NeckoChild::DeallocPHttpChannelChild(PHt
 {
   MOZ_ASSERT(IsNeckoChild(), "DeallocPHttpChannelChild called by non-child!");
 
   HttpChannelChild* child = static_cast<HttpChannelChild*>(channel);
   child->ReleaseIPDLReference();
   return true;
 }
 
+PAltDataOutputStreamChild*
+NeckoChild::AllocPAltDataOutputStreamChild(
+        const nsCString& type,
+        PHttpChannelChild* channel)
+{
+  AltDataOutputStreamChild* stream = new AltDataOutputStreamChild();
+  stream->AddIPDLReference();
+  return stream;
+}
+
+bool
+NeckoChild::DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor)
+{
+  AltDataOutputStreamChild* child = static_cast<AltDataOutputStreamChild*>(aActor);
+  child->ReleaseIPDLReference();
+  return true;
+}
+
 PFTPChannelChild*
 NeckoChild::AllocPFTPChannelChild(const PBrowserOrId& aBrowser,
                                   const SerializedLoadContext& aSerialized,
                                   const FTPChannelCreationArgs& aOpenArgs)
 {
   // We don't allocate here: see FTPChannelChild::AsyncOpen()
   NS_RUNTIMEABORT("AllocPFTPChannelChild should not be called");
   return nullptr;
--- a/netwerk/ipc/NeckoChild.h
+++ b/netwerk/ipc/NeckoChild.h
@@ -24,16 +24,20 @@ public:
 
   static void InitNeckoChild();
 
 protected:
   virtual PHttpChannelChild*
     AllocPHttpChannelChild(const PBrowserOrId&, const SerializedLoadContext&,
                            const HttpChannelCreationArgs& aOpenArgs) override;
   virtual bool DeallocPHttpChannelChild(PHttpChannelChild*) override;
+
+  virtual PAltDataOutputStreamChild* AllocPAltDataOutputStreamChild(const nsCString& type, PHttpChannelChild* channel) override;
+  virtual bool DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor) override;
+
   virtual PCookieServiceChild* AllocPCookieServiceChild() override;
   virtual bool DeallocPCookieServiceChild(PCookieServiceChild*) override;
   virtual PWyciwygChannelChild* AllocPWyciwygChannelChild() override;
   virtual bool DeallocPWyciwygChannelChild(PWyciwygChannelChild*) override;
   virtual PFTPChannelChild*
     AllocPFTPChannelChild(const PBrowserOrId& aBrowser,
                           const SerializedLoadContext& aSerialized,
                           const FTPChannelCreationArgs& aOpenArgs) override;
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -10,16 +10,18 @@
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/net/HttpChannelParent.h"
 #include "mozilla/net/CookieServiceParent.h"
 #include "mozilla/net/WyciwygChannelParent.h"
 #include "mozilla/net/FTPChannelParent.h"
 #include "mozilla/net/WebSocketChannelParent.h"
 #include "mozilla/net/WebSocketEventListenerParent.h"
 #include "mozilla/net/DataChannelParent.h"
+#include "mozilla/net/AltDataOutputStreamParent.h"
+#include "mozilla/Unused.h"
 #ifdef NECKO_PROTOCOL_rtsp
 #include "mozilla/net/RtspControllerParent.h"
 #include "mozilla/net/RtspChannelParent.h"
 #endif
 #include "mozilla/net/DNSRequestParent.h"
 #include "mozilla/net/RemoteOpenFileParent.h"
 #include "mozilla/net/ChannelDiverterParent.h"
 #include "mozilla/net/IPCTransportProvider.h"
@@ -252,16 +254,40 @@ NeckoParent::RecvPHttpChannelConstructor
                       const PBrowserOrId& aBrowser,
                       const SerializedLoadContext& aSerialized,
                       const HttpChannelCreationArgs& aOpenArgs)
 {
   HttpChannelParent* p = static_cast<HttpChannelParent*>(aActor);
   return p->Init(aOpenArgs);
 }
 
+PAltDataOutputStreamParent*
+NeckoParent::AllocPAltDataOutputStreamParent(
+        const nsCString& type,
+        PHttpChannelParent* channel)
+{
+  HttpChannelParent* chan = static_cast<HttpChannelParent*>(channel);
+  nsCOMPtr<nsIOutputStream> stream;
+  nsresult rv = chan->OpenAlternativeOutputStream(type, getter_AddRefs(stream));
+  AltDataOutputStreamParent* parent = new AltDataOutputStreamParent(stream);
+  parent->AddRef();
+  // If the return value was not NS_OK, the error code will be sent
+  // asynchronously to the child, after receiving the first message.
+  parent->SetError(rv);
+  return parent;
+}
+
+bool
+NeckoParent::DeallocPAltDataOutputStreamParent(PAltDataOutputStreamParent* aActor)
+{
+  AltDataOutputStreamParent* parent = static_cast<AltDataOutputStreamParent*>(aActor);
+  parent->Release();
+  return true;
+}
+
 PFTPChannelParent*
 NeckoParent::AllocPFTPChannelParent(const PBrowserOrId& aBrowser,
                                     const SerializedLoadContext& aSerialized,
                                     const FTPChannelCreationArgs& aOpenArgs)
 {
   nsCOMPtr<nsILoadContext> loadContext;
   const char *error = CreateChannelLoadContext(aBrowser, Manager(),
                                                aSerialized, loadContext);
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -100,16 +100,22 @@ protected:
                             const HttpChannelCreationArgs& aOpenArgs) override;
   virtual bool
     RecvPHttpChannelConstructor(
                       PHttpChannelParent* aActor,
                       const PBrowserOrId& aBrowser,
                       const SerializedLoadContext& aSerialized,
                       const HttpChannelCreationArgs& aOpenArgs) override;
   virtual bool DeallocPHttpChannelParent(PHttpChannelParent*) override;
+
+  virtual PAltDataOutputStreamParent* AllocPAltDataOutputStreamParent(
+    const nsCString& type, PHttpChannelParent* channel) override;
+  virtual bool DeallocPAltDataOutputStreamParent(
+    PAltDataOutputStreamParent* aActor) override;
+
   virtual bool DeallocPCookieServiceParent(PCookieServiceParent*) override;
   virtual PWyciwygChannelParent* AllocPWyciwygChannelParent() override;
   virtual bool DeallocPWyciwygChannelParent(PWyciwygChannelParent*) override;
   virtual PFTPChannelParent*
     AllocPFTPChannelParent(const PBrowserOrId& aBrowser,
                            const SerializedLoadContext& aSerialized,
                            const FTPChannelCreationArgs& aOpenArgs) override;
   virtual bool
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -25,16 +25,17 @@ include protocol PDataChannel;
 include protocol PTransportProvider;
 
 include protocol PRtspController;
 include protocol PRtspChannel;
 include URIParams;
 include InputStreamParams;
 include NeckoChannelParams;
 include PBrowserOrId;
+include protocol PAltDataOutputStream;
 
 using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 
 namespace mozilla {
 namespace net {
 
@@ -53,16 +54,17 @@ prio(normal upto urgent) sync protocol P
   manages PUDPSocket;
   manages PDNSRequest;
   manages PRemoteOpenFile;
   manages PDataChannel;
   manages PRtspController;
   manages PRtspChannel;
   manages PChannelDiverter;
   manages PTransportProvider;
+  manages PAltDataOutputStream;
 
 parent:
   async __delete__();
 
   prio(urgent) async PCookieService();
   async PHttpChannel(PBrowserOrId browser,
                      SerializedLoadContext loadContext,
                      HttpChannelCreationArgs args);
@@ -111,16 +113,18 @@ parent:
    * corresponding to an nsIAuthPromptCallback
    */
   async OnAuthAvailable(uint64_t callbackId, nsString user,
                         nsString password, nsString domain);
   async OnAuthCancelled(uint64_t callbackId, bool userCancel);
 
   async RemoveRequestContext(nsCString rcid);
 
+  async PAltDataOutputStream(nsCString type, PHttpChannel channel);
+
 child:
   /*
    * Bring up the http auth prompt for a nested remote mozbrowser.
    * NestedFrameId is the id corresponding to the PBrowser.  It is the same id
    * that was passed to the PBrowserOrId param in to the PHttpChannel constructor
    */
   async AsyncAuthPromptForNestedFrame(TabId nestedFrameId, nsCString uri,
                                       nsString realm, uint64_t callbackId);
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamChild.cpp
@@ -0,0 +1,151 @@
+#include "mozilla/net/AltDataOutputStreamChild.h"
+#include "mozilla/Unused.h"
+#include "nsIInputStream.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(AltDataOutputStreamChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) AltDataOutputStreamChild::Release()
+{
+  NS_PRECONDITION(0 != mRefCnt, "dup release");
+  MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+  --mRefCnt;
+  NS_LOG_RELEASE(this, mRefCnt, "AltDataOutputStreamChild");
+
+  if (mRefCnt == 1 && mIPCOpen) {
+    // Send_delete calls NeckoChild::PAltDataOutputStreamChild, which will release
+    // again to refcount == 0
+    PAltDataOutputStreamChild::Send__delete__(this);
+    return 0;
+  }
+
+  if (mRefCnt == 0) {
+    mRefCnt = 1; /* stabilize */
+    delete this;
+    return 0;
+  }
+  return mRefCnt;
+}
+
+NS_INTERFACE_MAP_BEGIN(AltDataOutputStreamChild)
+  NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+AltDataOutputStreamChild::AltDataOutputStreamChild()
+  : mIPCOpen(false)
+  , mError(NS_OK)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+AltDataOutputStreamChild::~AltDataOutputStreamChild()
+{
+}
+
+void
+AltDataOutputStreamChild::AddIPDLReference()
+{
+  MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+  mIPCOpen = true;
+  AddRef();
+}
+
+void
+AltDataOutputStreamChild::ReleaseIPDLReference()
+{
+  MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+  mIPCOpen = false;
+  Release();
+}
+
+bool
+AltDataOutputStreamChild::WriteDataInChunks(const nsCString& data)
+{
+  const uint32_t kChunkSize = 128*1024;
+  uint32_t next = std::min(data.Length(), kChunkSize);
+  for (uint32_t i = 0; i < data.Length();
+       i = next, next = std::min(data.Length(), next + kChunkSize)) {
+    nsCString chunk(Substring(data, i, kChunkSize));
+    if (mIPCOpen && !SendWriteData(chunk)) {
+      mIPCOpen = false;
+      return false;
+    }
+  }
+  return true;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Close()
+{
+  if (!mIPCOpen) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  if (NS_FAILED(mError)) {
+    return mError;
+  }
+  Unused << SendClose();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Flush()
+{
+  if (!mIPCOpen) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  if (NS_FAILED(mError)) {
+    return mError;
+  }
+
+  // This is a no-op
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::Write(const char * aBuf, uint32_t aCount, uint32_t *_retval)
+{
+  if (!mIPCOpen) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  if (NS_FAILED(mError)) {
+    return mError;
+  }
+  if (WriteDataInChunks(nsCString(aBuf, aCount))) {
+    *_retval = aCount;
+    return NS_OK;
+  }
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount, uint32_t *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::WriteSegments(nsReadSegmentFun aReader, void *aClosure, uint32_t aCount, uint32_t *_retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AltDataOutputStreamChild::IsNonBlocking(bool *_retval)
+{
+  *_retval = false;
+  return NS_OK;
+}
+
+bool
+AltDataOutputStreamChild::RecvError(const nsresult& err)
+{
+  mError = err;
+  return true;
+}
+
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamChild.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* 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/. */
+
+#ifndef mozilla_net_AltDataOutputStreamChild_h
+#define mozilla_net_AltDataOutputStreamChild_h
+
+#include "mozilla/net/PAltDataOutputStreamChild.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+class AltDataOutputStreamChild
+  : public PAltDataOutputStreamChild
+  , public nsIOutputStream
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOUTPUTSTREAM
+  explicit AltDataOutputStreamChild();
+
+  void AddIPDLReference();
+  void ReleaseIPDLReference();
+  // Saves an error code which will be reported to the writer on the next call.
+  virtual bool RecvError(const nsresult& err) override;
+
+private:
+  virtual ~AltDataOutputStreamChild();
+  // Sends data to the parent process in 256k chunks.
+  bool WriteDataInChunks(const nsCString& data);
+
+  bool mIPCOpen;
+  // If there was an error opening the output stream or writing to it on the
+  // parent side, this will be set to the error code. We check it before we
+  // write so we can report an error to the consumer.
+  nsresult mError;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AltDataOutputStreamChild_h
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamParent.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* 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 "mozilla/net/AltDataOutputStreamParent.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS0(AltDataOutputStreamParent)
+
+AltDataOutputStreamParent::AltDataOutputStreamParent(nsIOutputStream* aStream)
+  : mOutputStream(aStream)
+  , mStatus(NS_OK)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+AltDataOutputStreamParent::~AltDataOutputStreamParent()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+}
+
+bool
+AltDataOutputStreamParent::RecvWriteData(const nsCString& data)
+{
+  if (NS_FAILED(mStatus)) {
+    Unused << SendError(mStatus);
+    return true;
+  }
+  nsresult rv;
+  uint32_t n;
+  if (mOutputStream) {
+    rv = mOutputStream->Write(data.BeginReading(), data.Length(), &n);
+    MOZ_ASSERT(n == data.Length());
+    if (NS_FAILED(rv)) {
+      Unused << SendError(rv);
+    }
+  }
+  return true;
+}
+
+bool
+AltDataOutputStreamParent::RecvClose()
+{
+  if (NS_FAILED(mStatus)) {
+    Unused << SendError(mStatus);
+    return true;
+  }
+  nsresult rv;
+  if (mOutputStream) {
+    rv = mOutputStream->Close();
+    if (NS_FAILED(rv)) {
+      Unused << SendError(rv);
+    }
+    mOutputStream = nullptr;
+  }
+  return true;
+}
+
+void
+AltDataOutputStreamParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamParent.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+
+/* 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/. */
+
+#ifndef mozilla_net_AltDataOutputStreamParent_h
+#define mozilla_net_AltDataOutputStreamParent_h
+
+#include "mozilla/net/PAltDataOutputStreamParent.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace net {
+
+// Forwards data received from the content process to an output stream.
+class AltDataOutputStreamParent
+  : public PAltDataOutputStreamParent
+  , public nsISupports
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  // Called from NeckoParent::AllocPAltDataOutputStreamParent which also opens
+  // the output stream.
+  // aStream may be null
+  explicit AltDataOutputStreamParent(nsIOutputStream* aStream);
+
+  // Called when data is received from the content process.
+  // We proceed to write that data to the output stream.
+  virtual bool RecvWriteData(const nsCString& data) override;
+  // Called when AltDataOutputStreamChild::Close() is
+  // Closes and nulls the output stream.
+  virtual bool RecvClose() override;
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  // Sets an error that will be reported to the content process.
+  void SetError(nsresult status) { mStatus = status; }
+
+private:
+  virtual ~AltDataOutputStreamParent();
+  nsCOMPtr<nsIOutputStream> mOutputStream;
+  // In case any error occurs mStatus will be != NS_OK, and this status code will
+  // be sent to the content process asynchronously.
+  nsresult mStatus;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_AltDataOutputStreamParent_h
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -49,16 +49,17 @@
 #include "LoadInfo.h"
 #include "nsISSLSocketControl.h"
 #include "mozilla/Telemetry.h"
 #include "nsIURL.h"
 #include "nsIConsoleService.h"
 #include "mozilla/BinarySearch.h"
 #include "nsIHttpHeaderVisitor.h"
 #include "nsIXULRuntime.h"
+#include "nsICacheInfoChannel.h"
 
 #include <algorithm>
 
 namespace mozilla {
 namespace net {
 
 HttpBaseChannel::HttpBaseChannel()
   : mStartPos(UINT64_MAX)
@@ -102,16 +103,17 @@ HttpBaseChannel::HttpBaseChannel()
   , mRedirectCount(0)
   , mForcePending(false)
   , mCorsIncludeCredentials(false)
   , mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS)
   , mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW)
   , mFetchCacheMode(nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT)
   , mOnStartRequestCalled(false)
   , mOnStopRequestCalled(false)
+  , mAfterOnStartRequestBegun(false)
   , mTransferSize(0)
   , mDecodedBodySize(0)
   , mEncodedBodySize(0)
   , mRequireCORSPreflight(false)
   , mReportCollector(new ConsoleReportCollector())
   , mForceMainDocumentChannel(false)
 {
   LOG(("Creating HttpBaseChannel @%x\n", this));
@@ -919,16 +921,21 @@ HttpBaseChannel::DoApplyContentConversio
 
   LOG(("HttpBaseChannel::DoApplyContentConversions [this=%p]\n", this));
 
   if (!mApplyConversion) {
     LOG(("not applying conversion per mApplyConversion\n"));
     return NS_OK;
   }
 
+  if (!mAvailableCachedAltDataType.IsEmpty()) {
+    LOG(("not applying conversion because delivering alt-data\n"));
+    return NS_OK;
+  }
+
   nsAutoCString contentEncoding;
   nsresult rv = mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
   if (NS_FAILED(rv) || contentEncoding.IsEmpty())
     return NS_OK;
 
   nsCOMPtr<nsIStreamListener> nextListener = new InterceptFailedOnStop(aNextListener, this);
 
   // The encodings are listed in the order they were applied
@@ -3156,16 +3163,22 @@ HttpBaseChannel::SetupReplacementChannel
     if (loadInfo && loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) {
       nsCOMPtr<nsIPrincipal> principal = loadInfo->LoadingPrincipal();
       newTimedChannel->SetAllRedirectsPassTimingAllowCheck(
         mAllRedirectsPassTimingAllowCheck &&
         oldTimedChannel->TimingAllowCheck(principal));
     }
   }
 
+  // Pass the preferred alt-data type on to the new channel.
+  nsCOMPtr<nsICacheInfoChannel> cacheInfoChan(do_QueryInterface(newChannel));
+  if (cacheInfoChan) {
+    cacheInfoChan->PreferAlternativeDataType(mPreferredCachedAltDataType);
+  }
+
   if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
                        nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
     // Copy non-origin related headers to the new channel.
     nsCOMPtr<nsIHttpHeaderVisitor> visitor =
       new SetupReplacementChannelHeaderVisitor(httpChannel);
     mRequestHead.VisitHeaders(visitor);
   }
 
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -525,31 +525,40 @@ protected:
   uint32_t mRedirectMode;
   uint32_t mFetchCacheMode;
 
   // These parameters are used to ensure that we do not call OnStartRequest and
   // OnStopRequest more than once.
   bool mOnStartRequestCalled;
   bool mOnStopRequestCalled;
 
+  // Defaults to false. Is set to true at the begining of OnStartRequest.
+  // Used to ensure methods can't be called before OnStartRequest.
+  bool mAfterOnStartRequestBegun;
+
   uint64_t mTransferSize;
   uint64_t mDecodedBodySize;
   uint64_t mEncodedBodySize;
 
   // The network interface id that's associated with this channel.
   nsCString mNetworkInterfaceId;
 
   nsID mRequestContextID;
   bool EnsureRequestContextID();
 
   bool                              mRequireCORSPreflight;
   nsTArray<nsCString>               mUnsafeHeaders;
 
   nsCOMPtr<nsIConsoleReportCollector> mReportCollector;
 
+  // Holds the name of the preferred alt-data type.
+  nsCString mPreferredCachedAltDataType;
+  // Holds the name of the alternative data type the channel returned.
+  nsCString mAvailableCachedAltDataType;
+
   bool mForceMainDocumentChannel;
 
   nsID mChannelId;
 
   nsString mIntegrityMetadata;
 };
 
 // Share some code while working around C++'s absurd inability to handle casting
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -309,71 +309,75 @@ class StartRequestEvent : public Channel
                     const nsHttpHeaderArray& aRequestHeaders,
                     const bool& aIsFromCache,
                     const bool& aCacheEntryAvailable,
                     const uint32_t& aCacheExpirationTime,
                     const nsCString& aCachedCharset,
                     const nsCString& aSecurityInfoSerialization,
                     const NetAddr& aSelfAddr,
                     const NetAddr& aPeerAddr,
-                    const uint32_t& aCacheKey)
+                    const uint32_t& aCacheKey,
+                    const nsCString& altDataType)
   : mChild(aChild)
   , mChannelStatus(aChannelStatus)
   , mResponseHead(aResponseHead)
   , mRequestHeaders(aRequestHeaders)
   , mUseResponseHead(aUseResponseHead)
   , mIsFromCache(aIsFromCache)
   , mCacheEntryAvailable(aCacheEntryAvailable)
   , mCacheExpirationTime(aCacheExpirationTime)
   , mCachedCharset(aCachedCharset)
   , mSecurityInfoSerialization(aSecurityInfoSerialization)
   , mSelfAddr(aSelfAddr)
   , mPeerAddr(aPeerAddr)
   , mCacheKey(aCacheKey)
+  , mAltDataType(altDataType)
   {}
 
   void Run()
   {
     LOG(("StartRequestEvent [this=%p]\n", mChild));
     mChild->OnStartRequest(mChannelStatus, mResponseHead, mUseResponseHead,
                            mRequestHeaders, mIsFromCache, mCacheEntryAvailable,
                            mCacheExpirationTime, mCachedCharset,
                            mSecurityInfoSerialization, mSelfAddr, mPeerAddr,
-                           mCacheKey);
+                           mCacheKey, mAltDataType);
   }
  private:
   HttpChannelChild* mChild;
   nsresult mChannelStatus;
   nsHttpResponseHead mResponseHead;
   nsHttpHeaderArray mRequestHeaders;
   bool mUseResponseHead;
   bool mIsFromCache;
   bool mCacheEntryAvailable;
   uint32_t mCacheExpirationTime;
   nsCString mCachedCharset;
   nsCString mSecurityInfoSerialization;
   NetAddr mSelfAddr;
   NetAddr mPeerAddr;
   uint32_t mCacheKey;
+  nsCString mAltDataType;
 };
 
 bool
 HttpChannelChild::RecvOnStartRequest(const nsresult& channelStatus,
                                      const nsHttpResponseHead& responseHead,
                                      const bool& useResponseHead,
                                      const nsHttpHeaderArray& requestHeaders,
                                      const bool& isFromCache,
                                      const bool& cacheEntryAvailable,
                                      const uint32_t& cacheExpirationTime,
                                      const nsCString& cachedCharset,
                                      const nsCString& securityInfoSerialization,
                                      const NetAddr& selfAddr,
                                      const NetAddr& peerAddr,
                                      const int16_t& redirectCount,
-                                     const uint32_t& cacheKey)
+                                     const uint32_t& cacheKey,
+                                     const nsCString& altDataType)
 {
   LOG(("HttpChannelChild::RecvOnStartRequest [this=%p]\n", this));
   // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
   // stage, as they are set in the listener's OnStartRequest.
   MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
     "mFlushedForDiversion should be unset before OnStartRequest!");
   MOZ_RELEASE_ASSERT(!mDivertingToParent,
     "mDivertingToParent should be unset before OnStartRequest!");
@@ -382,33 +386,35 @@ HttpChannelChild::RecvOnStartRequest(con
   mRedirectCount = redirectCount;
 
   mEventQ->RunOrEnqueue(new StartRequestEvent(this, channelStatus, responseHead,
                                               useResponseHead, requestHeaders,
                                               isFromCache, cacheEntryAvailable,
                                               cacheExpirationTime,
                                               cachedCharset,
                                               securityInfoSerialization,
-                                              selfAddr, peerAddr, cacheKey));
+                                              selfAddr, peerAddr, cacheKey,
+                                              altDataType));
   return true;
 }
 
 void
 HttpChannelChild::OnStartRequest(const nsresult& channelStatus,
                                  const nsHttpResponseHead& responseHead,
                                  const bool& useResponseHead,
                                  const nsHttpHeaderArray& requestHeaders,
                                  const bool& isFromCache,
                                  const bool& cacheEntryAvailable,
                                  const uint32_t& cacheExpirationTime,
                                  const nsCString& cachedCharset,
                                  const nsCString& securityInfoSerialization,
                                  const NetAddr& selfAddr,
                                  const NetAddr& peerAddr,
-                                 const uint32_t& cacheKey)
+                                 const uint32_t& cacheKey,
+                                 const nsCString& altDataType)
 {
   LOG(("HttpChannelChild::OnStartRequest [this=%p]\n", this));
 
   // mFlushedForDiversion and mDivertingToParent should NEVER be set at this
   // stage, as they are set in the listener's OnStartRequest.
   MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
     "mFlushedForDiversion should be unset before OnStartRequest!");
   MOZ_RELEASE_ASSERT(!mDivertingToParent,
@@ -428,16 +434,20 @@ HttpChannelChild::OnStartRequest(const n
 
   mIsFromCache = isFromCache;
   mCacheEntryAvailable = cacheEntryAvailable;
   mCacheExpirationTime = cacheExpirationTime;
   mCachedCharset = cachedCharset;
   mSelfAddr = selfAddr;
   mPeerAddr = peerAddr;
 
+  mAvailableCachedAltDataType = altDataType;
+
+  mAfterOnStartRequestBegun = true;
+
   AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
   nsresult rv;
   nsCOMPtr<nsISupportsPRUint32> container =
     do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
   if (NS_FAILED(rv)) {
     Cancel(rv);
     return;
@@ -908,16 +918,23 @@ HttpChannelChild::OnStopRequest(const ns
     // so make sure this goes out of scope before then.
     AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
     DoOnStopRequest(this, channelStatus, mListenerContext);
   }
 
   ReleaseListeners();
 
+  // DocumentChannelCleanup actually nulls out mCacheEntry in the parent, which
+  // we might need later to open the Alt-Data output stream, so just return here
+  if (!mPreferredCachedAltDataType.IsEmpty()) {
+    mKeptAlive = true;
+    return;
+  }
+
   if (mLoadFlags & LOAD_DOCUMENT_URI) {
     // Keep IPDL channel open, but only for updating security info.
     mKeptAlive = true;
     SendDocumentChannelCleanup();
   } else {
     // This calls NeckoChild::DeallocPHttpChannelChild(), which deletes |this| if IPDL
     // holds the last reference.  Don't rely on |this| existing after here.
     PHttpChannelChild::Send__delete__(this);
@@ -1900,16 +1917,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;
 
   nsTArray<mozilla::ipc::FileDescriptor> fds;
   SerializeInputStream(mUploadStream, openArgs.uploadStream(), fds);
 
   if (mResponseHead) {
     openArgs.synthesizedResponseHead() = *mResponseHead;
     openArgs.suspendAfterSynthesizeResponse() =
       mSuspendParentAfterSynthesizeResponse;
@@ -2173,16 +2191,46 @@ HttpChannelChild::SetAllowStaleCacheCont
 NS_IMETHODIMP
 HttpChannelChild::GetAllowStaleCacheContent(bool *aAllowStaleCacheContent)
 {
   NS_ENSURE_ARG(aAllowStaleCacheContent);
   *aAllowStaleCacheContent = mAllowStaleCacheContent;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HttpChannelChild::PreferAlternativeDataType(const nsACString & aType)
+{
+  ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+  mPreferredCachedAltDataType = aType;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetAlternativeDataType(nsACString & aType)
+{
+  // Must be called during or after OnStartRequest
+  if (!mAfterOnStartRequestBegun) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  aType = mAvailableCachedAltDataType;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::OpenAlternativeOutputStream(const nsACString & aType, nsIOutputStream * *_retval)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+  RefPtr<AltDataOutputStreamChild> stream =
+    static_cast<AltDataOutputStreamChild*>(gNeckoChild->SendPAltDataOutputStreamConstructor(nsCString(aType), this));
+  stream.forget(_retval);
+  return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // HttpChannelChild::nsIResumableChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelChild::ResumeAt(uint64_t startPos, const nsACString& entityID)
 {
   LOG(("HttpChannelChild::ResumeAt [this=%p]\n", this));
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -116,17 +116,18 @@ protected:
                           const bool& isFromCache,
                           const bool& cacheEntryAvailable,
                           const uint32_t& cacheExpirationTime,
                           const nsCString& cachedCharset,
                           const nsCString& securityInfoSerialization,
                           const NetAddr& selfAddr,
                           const NetAddr& peerAddr,
                           const int16_t& redirectCount,
-                          const uint32_t& cacheKey) override;
+                          const uint32_t& cacheKey,
+                          const nsCString& altDataType) override;
   bool RecvOnTransportAndData(const nsresult& channelStatus,
                               const nsresult& status,
                               const uint64_t& progress,
                               const uint64_t& progressMax,
                               const uint64_t& offset,
                               const uint32_t& count,
                               const nsCString& data) override;
   bool RecvOnStopRequest(const nsresult& statusCode, const ResourceTimingStruct& timing) override;
@@ -256,17 +257,18 @@ private:
                       const nsHttpHeaderArray& requestHeaders,
                       const bool& isFromCache,
                       const bool& cacheEntryAvailable,
                       const uint32_t& cacheExpirationTime,
                       const nsCString& cachedCharset,
                       const nsCString& securityInfoSerialization,
                       const NetAddr& selfAddr,
                       const NetAddr& peerAddr,
-                      const uint32_t& cacheKey);
+                      const uint32_t& cacheKey,
+                      const nsCString& altDataType);
   void MaybeDivertOnData(const nsCString& data,
                          const uint64_t& offset,
                          const uint32_t& count);
   void OnTransportAndData(const nsresult& channelStatus,
                           const nsresult& status,
                           const uint64_t progress,
                           const uint64_t& progressMax,
                           const uint64_t& offset,
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -129,17 +129,17 @@ HttpChannelParent::Init(const HttpChanne
                        a.entityID(), a.chooseApplicationCache(),
                        a.appCacheClientID(), a.allowSpdy(), a.allowAltSvc(), a.fds(),
                        a.loadInfo(), a.synthesizedResponseHead(),
                        a.synthesizedSecurityInfoSerialization(),
                        a.cacheKey(), a.requestContextID(), a.preflightArgs(),
                        a.initialRwin(), a.blockAuthPrompt(),
                        a.suspendAfterSynthesizeResponse(),
                        a.allowStaleCacheContent(), a.contentTypeHint(),
-                       a.channelId());
+                       a.channelId(), a.preferredAlternativeType());
   }
   case HttpChannelCreationArgs::THttpChannelConnectArgs:
   {
     const HttpChannelConnectArgs& cArgs = aArgs.get_HttpChannelConnectArgs();
     return ConnectChannel(cArgs.registrarId(), cArgs.shouldIntercept());
   }
   default:
     NS_NOTREACHED("unknown open type");
@@ -261,17 +261,18 @@ HttpChannelParent::DoAsyncOpen(  const U
                                  const uint32_t&            aCacheKey,
                                  const nsCString&           aRequestContextID,
                                  const OptionalCorsPreflightArgs& aCorsPreflightArgs,
                                  const uint32_t&            aInitialRwin,
                                  const bool&                aBlockAuthPrompt,
                                  const bool&                aSuspendAfterSynthesizeResponse,
                                  const bool&                aAllowStaleCacheContent,
                                  const nsCString&           aContentTypeHint,
-                                 const nsCString&           aChannelId)
+                                 const nsCString&           aChannelId,
+                                 const nsCString&           aPreferredAlternativeType)
 {
   nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
   if (!uri) {
     // URIParams does MOZ_ASSERT if null, but we need to protect opt builds from
     // null deref here.
     return false;
   }
   nsCOMPtr<nsIURI> originalUri = DeserializeURI(aOriginalURI);
@@ -416,16 +417,17 @@ HttpChannelParent::DoAsyncOpen(  const U
   }
 
   rv = cacheKey->SetData(aCacheKey);
   if (NS_FAILED(rv)) {
     return SendFailedAsyncOpen(rv);
   }
 
   mChannel->SetCacheKey(cacheKey);
+  mChannel->PreferAlternativeDataType(aPreferredAlternativeType);
 
   mChannel->SetAllowStaleCacheContent(aAllowStaleCacheContent);
 
   mChannel->SetContentType(aContentTypeHint);
 
   if (priority != nsISupportsPriority::PRIORITY_NORMAL) {
     mChannel->SetPriority(priority);
   }
@@ -1054,30 +1056,34 @@ HttpChannelParent::OnStartRequest(nsIReq
     }
 
     nsresult rv = container->GetData(&cacheKeyValue);
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
+  nsAutoCString altDataType;
+  mChannel->GetAlternativeDataType(altDataType);
+
   // !!! We need to lock headers and please don't forget to unlock them !!!
   requestHead->Enter();
   nsresult rv = NS_OK;
   if (mIPCClosed ||
       !SendOnStartRequest(channelStatus,
                           responseHead ? *responseHead : nsHttpResponseHead(),
                           !!responseHead,
                           requestHead->Headers(),
                           isFromCache,
                           mCacheEntry ? true : false,
                           expirationTime, cachedCharset, secInfoSerialization,
                           mChannel->GetSelfAddr(), mChannel->GetPeerAddr(),
                           redirectCount,
-                          cacheKeyValue))
+                          cacheKeyValue,
+                          altDataType))
   {
     rv = NS_ERROR_UNEXPECTED;
   }
   requestHead->Exit();
   return rv;
 }
 
 NS_IMETHODIMP
@@ -1587,16 +1593,27 @@ HttpChannelParent::NotifyDiversionFailed
   mParentListener = nullptr;
   mChannel = nullptr;
 
   if (!mIPCClosed) {
     Unused << DoSendDeleteSelf();
   }
 }
 
+nsresult
+HttpChannelParent::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval)
+{
+  // We need to make sure the child does not call SendDocumentChannelCleanup()
+  // before opening the altOutputStream, because that clears mCacheEntry.
+  if (!mCacheEntry) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  return mCacheEntry->OpenAlternativeOutputStream(type, _retval);
+}
+
 void
 HttpChannelParent::OfflineDisconnect()
 {
   if (mChannel) {
     mChannel->Cancel(NS_ERROR_OFFLINE);
   }
   mStatus = NS_ERROR_OFFLINE;
 }
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -93,16 +93,18 @@ public:
 
   // Forwarded to nsHttpChannel::SetApplyConversion.
   void SetApplyConversion(bool aApplyConversion) {
     if (mChannel) {
       mChannel->SetApplyConversion(aApplyConversion);
     }
   }
 
+  nsresult OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval);
+
 protected:
   // used to connect redirected-to channel in parent with just created
   // ChildChannel.  Used during redirects.
   bool ConnectChannel(const uint32_t& channelId, const bool& shouldIntercept);
 
   bool DoAsyncOpen(const URIParams&           uri,
                    const OptionalURIParams&   originalUri,
                    const OptionalURIParams&   docUri,
@@ -135,17 +137,18 @@ protected:
                    const uint32_t&            aCacheKey,
                    const nsCString&           aRequestContextID,
                    const OptionalCorsPreflightArgs& aCorsPreflightArgs,
                    const uint32_t&            aInitialRwin,
                    const bool&                aBlockAuthPrompt,
                    const bool&                aSuspendAfterSynthesizeResponse,
                    const bool&                aAllowStaleCacheContent,
                    const nsCString&           aContentTypeHint,
-                   const nsCString&           aChannelId);
+                   const nsCString&           aChannelId,
+                   const nsCString&           aPreferredAlternativeType);
 
   virtual bool RecvSetPriority(const uint16_t& priority) override;
   virtual bool RecvSetClassOfService(const uint32_t& cos) override;
   virtual bool RecvSetCacheTokenCachedCharset(const nsCString& charset) override;
   virtual bool RecvSuspend() override;
   virtual bool RecvResume() override;
   virtual bool RecvCancel(const nsresult& status) override;
   virtual bool RecvRedirect2Verify(const nsresult& result,
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/PAltDataOutputStream.ipdl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* 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 protocol PNecko;
+
+namespace mozilla {
+namespace net {
+
+protocol PAltDataOutputStream
+{
+  manager PNecko;
+
+parent:
+  // Sends data from the child to the parent that will be written to the cache.
+  async WriteData(nsCString data);
+  // Signals that writing to the output stream is done.
+  async Close();
+  async __delete__();
+
+child:
+  // The parent calls this method to signal that an error has ocurred.
+  // This may mean that opening the output stream has failed or that writing to
+  // the stream has returned an error.
+  async Error(nsresult err);
+};
+
+} // namespace net
+} // namespace mozilla
--- a/netwerk/protocol/http/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -94,17 +94,18 @@ child:
                        bool                isFromCache,
                        bool                cacheEntryAvailable,
                        uint32_t            cacheExpirationTime,
                        nsCString           cachedCharset,
                        nsCString           securityInfoSerialization,
                        NetAddr             selfAddr,
                        NetAddr             peerAddr,
                        int16_t             redirectCount,
-                       uint32_t            cacheKey);
+                       uint32_t            cacheKey,
+                       nsCString           altDataType);
 
   // Combines a single OnDataAvailable and its associated OnProgress &
   // OnStatus calls into one IPDL message
   async OnTransportAndData(nsresult  channelStatus,
                            nsresult  transportStatus,
                            uint64_t  progress,
                            uint64_t  progressMax,
                            uint64_t  offset,
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -26,16 +26,18 @@ EXPORTS += [
     'nsHttp.h',
     'nsHttpAtomList.h',
     'nsHttpHeaderArray.h',
     'nsHttpRequestHead.h',
     'nsHttpResponseHead.h',
 ]
 
 EXPORTS.mozilla.net += [
+    'AltDataOutputStreamChild.h',
+    'AltDataOutputStreamParent.h',
     'HttpBaseChannel.h',
     'HttpChannelChild.h',
     'HttpChannelParent.h',
     'HttpInfo.h',
     'NullHttpChannel.h',
     'PackagedAppService.h',
     'PackagedAppVerifier.h',
     'PHttpChannelParams.h',
@@ -48,16 +50,18 @@ EXPORTS.mozilla.net += [
 SOURCES += [
     'AlternateServices.cpp',
     'ASpdySession.cpp',
     'nsHttpAuthCache.cpp',
     'nsHttpChannelAuthProvider.cpp', # redefines GetAuthType
 ]
 
 UNIFIED_SOURCES += [
+    'AltDataOutputStreamChild.cpp',
+    'AltDataOutputStreamParent.cpp',
     'CacheControlParser.cpp',
     'ConnectionDiagnostics.cpp',
     'Http2Compression.cpp',
     'Http2Push.cpp',
     'Http2Session.cpp',
     'Http2Stream.cpp',
     'HttpBaseChannel.cpp',
     'HttpChannelChild.cpp',
@@ -90,16 +94,17 @@ UNIFIED_SOURCES += [
 ]
 
 # These files cannot be built in unified mode because of OS X headers.
 SOURCES += [
     'nsHttpHandler.cpp',
 ]
 
 IPDL_SOURCES += [
+    'PAltDataOutputStream.ipdl',
     'PHttpChannel.ipdl',
 ]
 
 EXTRA_JS_MODULES += [
     'UserAgentOverrides.jsm',
     'UserAgentUpdates.jsm',
 ]
 
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -461,17 +461,17 @@ nsHttpChannel::ContinueConnect()
         }
     }
     else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
         // If we have a fallback URI (and we're not already
         // falling back), process the fallback asynchronously.
         if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
             return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
         }
-        LOG(("  !mCachedEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
+        LOG(("  !mCacheEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
         return NS_ERROR_DOCUMENT_NOT_CACHED;
     }
 
     if (mLoadFlags & LOAD_NO_NETWORK_IO) {
         LOG(("  mLoadFlags & LOAD_NO_NETWORK_IO"));
         return NS_ERROR_DOCUMENT_NOT_CACHED;
     }
 
@@ -4491,17 +4491,34 @@ nsHttpChannel::OpenCacheInputStream(nsIC
         // just in case.
         LOG(("May skip read from cache based on LOAD_ONLY_IF_MODIFIED "
               "load flag\n"));
     }
 
     // Open an input stream for the entity, so that the call to OpenInputStream
     // happens off the main thread.
     nsCOMPtr<nsIInputStream> stream;
-    rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream));
+
+    // If an alternate representation was requested, try to open the alt
+    // input stream.
+    if (!mPreferredCachedAltDataType.IsEmpty()) {
+        rv = cacheEntry->OpenAlternativeInputStream(mPreferredCachedAltDataType,
+                                                    getter_AddRefs(stream));
+        if (NS_SUCCEEDED(rv)) {
+            // We have succeeded.
+            mAvailableCachedAltDataType = mPreferredCachedAltDataType;
+            // The alternative data may have a different length than the original
+            // content, so we clear the Content-Length header
+            mCachedResponseHead->SetContentLength(-1);
+        }
+    }
+
+    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;
     }
 
     if (startBuffering) {
@@ -6335,16 +6352,18 @@ nsHttpChannel::OnStartRequest(nsIRequest
         this, request, mStatus));
 
     // Make sure things are what we expect them to be...
     MOZ_ASSERT(request == mCachePump || request == mTransactionPump,
                "Unexpected request");
     MOZ_ASSERT(!(mTransactionPump && mCachePump) || mCachedContentIsPartial,
                "If we have both pumps, the cache content must be partial");
 
+    mAfterOnStartRequestBegun = true;
+
     if (!mSecurityInfo && !mCachePump && mTransaction) {
         // grab the security info from the connection object; the transaction
         // is guaranteed to own a reference to the connection.
         mSecurityInfo = mTransaction->SecurityInfo();
     }
 
     // don't enter this block if we're reading from the cache...
     if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
@@ -6684,16 +6703,24 @@ nsHttpChannel::OnStopRequest(nsIRequest 
     if (mListener) {
         LOG(("  calling OnStopRequest\n"));
         MOZ_ASSERT(!mOnStopRequestCalled,
                    "We should not call OnStopRequest twice");
         mListener->OnStopRequest(this, mListenerContext, status);
         mOnStopRequestCalled = true;
     }
 
+    // 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()) {
+        mAltDataCacheEntry = mCacheEntry;
+    }
+
     CloseCacheEntry(!contentComplete);
 
     if (mOfflineCacheEntry)
         CloseOfflineCacheEntry();
 
     if (mLoadGroup)
         mLoadGroup->RemoveRequest(this, nullptr, status);
 
@@ -7047,16 +7074,47 @@ nsHttpChannel::SetAllowStaleCacheContent
 NS_IMETHODIMP
 nsHttpChannel::GetAllowStaleCacheContent(bool *aAllowStaleCacheContent)
 {
     NS_ENSURE_ARG(aAllowStaleCacheContent);
     *aAllowStaleCacheContent = mAllowStaleCacheContent;
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsHttpChannel::PreferAlternativeDataType(const nsACString & aType)
+{
+    ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+    mPreferredCachedAltDataType = aType;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetAlternativeDataType(nsACString & aType)
+{
+    // must be called during or after OnStartRequest
+    if (!mAfterOnStartRequestBegun) {
+        return NS_ERROR_NOT_AVAILABLE;
+    }
+    aType = mAvailableCachedAltDataType;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval)
+{
+    // OnStopRequest will clear mCacheEntry, but we may use mAltDataCacheEntry
+    // if the consumer called PreferAlternativeDataType()
+    nsCOMPtr<nsICacheEntry> cacheEntry = mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
+    if (!cacheEntry) {
+        return NS_ERROR_NOT_AVAILABLE;
+    }
+    return cacheEntry->OpenAlternativeOutputStream(type, _retval);
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsICachingChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::GetCacheToken(nsISupports **token)
 {
     NS_ENSURE_ARG_POINTER(token);
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -452,16 +452,22 @@ private:
 
     RefPtr<nsInputStreamPump>       mTransactionPump;
     RefPtr<nsHttpTransaction>       mTransaction;
 
     uint64_t                          mLogicalOffset;
 
     // cache specific data
     nsCOMPtr<nsICacheEntry>           mCacheEntry;
+    // This will be set during OnStopRequest() before calling CloseCacheEntry(),
+    // but only if the listener wants to use alt-data (signaled by
+    // HttpBaseChannel::mPreferredCachedAltDataType being not empty)
+    // Needed because calling openAlternativeOutputStream needs a reference
+    // to the cache entry.
+    nsCOMPtr<nsICacheEntry>           mAltDataCacheEntry;
     // We must close mCacheInputStream explicitly to avoid leaks.
     AutoClose<nsIInputStream>         mCacheInputStream;
     RefPtr<nsInputStreamPump>       mCachePump;
     nsAutoPtr<nsHttpResponseHead>     mCachedResponseHead;
     nsCOMPtr<nsISupports>             mCachedSecurityInfo;
     uint32_t                          mPostID;
     uint32_t                          mRequestTime;