Bug 1487100 - Allow opening the input stream for original content when alt-data is available r=michal,luke
authorValentin Gosu <valentin.gosu@gmail.com>
Wed, 17 Oct 2018 12:27:37 +0000
changeset 490207 42319047f3d9ad10a2849c1e7b1b9cf6d2bb6de0
parent 490206 e37adb23fd48ca1576ba954fe203b3fd6d6155f3
child 490208 826cd78b94cb08b178956660832c4596543a23b0
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersmichal, luke
bugs1487100
milestone64.0a1
Bug 1487100 - Allow opening the input stream for original content when alt-data is available r=michal,luke In trying to use fetch with alt-data, we sometimes want the benefit of using alt-data but the JS consumer actually needs to use the original HTTP response from the server. To get around this problem, we introduce a new API - nsICacheInfoChannel.getOriginalInputStream(nsIInputStreamReceiver) that asyncly receives the input stream containing the HTTP response in the cache entry. Depends on D8071 Differential Revision: https://phabricator.services.mozilla.com/D8072
netwerk/base/nsICacheInfoChannel.idl
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/InterceptedHttpChannel.cpp
netwerk/protocol/http/PHttpChannel.ipdl
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/test/unit/test_alt-data_simple.js
--- a/netwerk/base/nsICacheInfoChannel.idl
+++ b/netwerk/base/nsICacheInfoChannel.idl
@@ -1,24 +1,31 @@
 /* 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;
+interface nsIInputStream;
 
 %{C++
 namespace mozilla {
 template<typename... Elements> class Tuple;
 } // namespace mozilla
 %}
 
 [ref] native ConstPreferenceArray(const nsTArray<mozilla::Tuple<nsCString, nsCString>>);
 
+[scriptable, uuid(1fb8ccf2-5fa5-45ec-bc57-8c8022a5d0d3)]
+interface nsIInputStreamReceiver : nsISupports
+{
+  void onInputStreamReady(in nsIInputStream aStream);
+};
+
 [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
@@ -112,16 +119,23 @@ interface nsICacheInfoChannel : nsISuppo
    * 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;
 
   /**
+   * Sometimes when the channel is delivering alt-data, we may want to somehow
+   * access the original content too. This method asynchronously opens the
+   * input stream and delivers it to the receiver.
+   */
+  void getOriginalInputStream(in nsIInputStreamReceiver aReceiver);
+
+  /**
    * 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.
    *
    * @param type
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -3336,16 +3336,46 @@ HttpChannelChild::OpenAlternativeOutputS
                                                         this)) {
     return NS_ERROR_FAILURE;
   }
 
   stream.forget(_retval);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HttpChannelChild::GetOriginalInputStream(nsIInputStreamReceiver *aReceiver)
+{
+  if (aReceiver == nullptr) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (!mIPCOpen) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mInputStreamReceiver = aReceiver;
+  Unused << SendOpenOriginalCacheInputStream();
+
+  return NS_OK;
+}
+
+mozilla::ipc::IPCResult
+HttpChannelChild::RecvOriginalCacheInputStreamAvailable(const OptionalIPCStream& aStream)
+{
+  nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStream);
+  nsCOMPtr<nsIInputStreamReceiver> receiver;
+  receiver.swap(mInputStreamReceiver);
+  if (receiver) {
+    receiver->OnInputStreamReady(stream);
+  }
+
+  return IPC_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
@@ -177,16 +177,18 @@ protected:
   mozilla::ipc::IPCResult RecvSetPriority(const int16_t& aPriority) override;
 
   mozilla::ipc::IPCResult RecvAttachStreamFilter(Endpoint<extensions::PStreamFilterParent>&& aEndpoint) override;
 
   mozilla::ipc::IPCResult RecvCancelDiversion() override;
 
   mozilla::ipc::IPCResult RecvCancelRedirected() override;
 
+  mozilla::ipc::IPCResult RecvOriginalCacheInputStreamAvailable(const OptionalIPCStream& aStream) override;
+
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual void DoNotifyListenerCleanup() override;
 
   virtual void DoAsyncAbort(nsresult aStatus) override;
 
   nsresult
   AsyncCall(void (HttpChannelChild::*funcPtr)(),
@@ -322,16 +324,18 @@ private:
 
   RequestHeaderTuples mClientSetRequestHeaders;
   RefPtr<nsInputStreamPump> mSynthesizedResponsePump;
   nsCOMPtr<nsIInputStream> mSynthesizedInput;
   nsCOMPtr<nsIInterceptedBodyCallback> mSynthesizedCallback;
   nsCOMPtr<nsICacheInfoChannel> mSynthesizedCacheInfo;
   RefPtr<ChannelEventQueue> mEventQ;
 
+  nsCOMPtr<nsIInputStreamReceiver> mInputStreamReceiver;
+
   // Used to ensure atomicity of mBgChild and mBgInitFailCallback
   Mutex mBgChildMutex;
 
   // Associated HTTP background channel
   RefPtr<HttpBackgroundChannelChild> mBgChild;
 
   // Error handling procedure if failed to establish PBackground IPC
   nsCOMPtr<nsIRunnable> mBgInitFailCallback;
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -1768,16 +1768,36 @@ HttpChannelParent::RecvBytesRead(const i
     MOZ_ASSERT(mSuspendedForFlowControl);
     Unused << mChannel->Resume();
     mSuspendedForFlowControl = false;
   }
   mSendWindowSize += aCount;
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+HttpChannelParent::RecvOpenOriginalCacheInputStream()
+{
+  if (mIPCClosed) {
+    return IPC_OK();
+  }
+  AutoIPCStream autoStream;
+  if (mCacheEntry) {
+    nsCOMPtr<nsIInputStream> inputStream;
+    nsresult rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
+    if (NS_SUCCEEDED(rv)) {
+      PContentParent* pcp = Manager()->Manager();
+      Unused << autoStream.Serialize(inputStream, static_cast<ContentParent*>(pcp));
+    }
+  }
+
+  Unused << SendOriginalCacheInputStreamAvailable(autoStream.TakeOptionalValue());
+  return IPC_OK();
+}
+
 //-----------------------------------------------------------------------------
 // HttpChannelParent::nsIProgressEventSink
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelParent::OnProgress(nsIRequest *aRequest,
                               nsISupports *aContext,
                               int64_t aProgress,
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -205,16 +205,17 @@ protected:
                                          const uint64_t& offset,
                                          const uint32_t& count) override;
   virtual mozilla::ipc::IPCResult RecvDivertOnStopRequest(const nsresult& statusCode) override;
   virtual mozilla::ipc::IPCResult RecvDivertComplete() override;
   virtual mozilla::ipc::IPCResult RecvCrossProcessRedirectDone(const nsresult& aResult) override;
   virtual mozilla::ipc::IPCResult RecvRemoveCorsPreflightCacheEntry(const URIParams& uri,
                                                                     const mozilla::ipc::PrincipalInfo& requestingPrincipal) override;
   virtual mozilla::ipc::IPCResult RecvBytesRead(const int32_t& aCount) override;
+  virtual mozilla::ipc::IPCResult RecvOpenOriginalCacheInputStream() override;
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   // Supporting function for ADivertableParentChannel.
   MOZ_MUST_USE nsresult ResumeForDiversion();
 
   // Asynchronously calls NotifyDiversionFailed.
   void FailDiversion(nsresult aErrorCode);
 
--- a/netwerk/protocol/http/InterceptedHttpChannel.cpp
+++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp
@@ -1343,16 +1343,25 @@ InterceptedHttpChannel::OpenAlternativeO
 {
   if (mSynthesizedCacheInfo) {
     return mSynthesizedCacheInfo->OpenAlternativeOutputStream(type, predictedSize, _retval);
   }
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 NS_IMETHODIMP
+InterceptedHttpChannel::GetOriginalInputStream(nsIInputStreamReceiver *aReceiver)
+{
+  if (mSynthesizedCacheInfo) {
+    return mSynthesizedCacheInfo->GetOriginalInputStream(aReceiver);
+  }
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
 InterceptedHttpChannel::GetCacheKey(uint32_t* key)
 {
   if (mSynthesizedCacheInfo) {
     return mSynthesizedCacheInfo->GetCacheKey(key);
   }
   return NS_ERROR_NOT_AVAILABLE;
 }
 
--- a/netwerk/protocol/http/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -7,16 +7,17 @@
 
 include protocol PNecko;
 include protocol PStreamFilter;
 include InputStreamParams;
 include URIParams;
 include PBackgroundSharedTypes;
 include NeckoChannelParams;
 include IPCServiceWorkerDescriptor;
+include IPCStream;
 
 include "mozilla/net/NeckoMessageUtils.h";
 
 using class nsHttpHeaderArray from "nsHttpHeaderArray.h";
 using mozilla::net::NetAddr from "mozilla/net/DNS.h";
 using struct mozilla::net::ResourceTimingStruct from "mozilla/net/TimingStruct.h";
 
 namespace mozilla {
@@ -88,16 +89,19 @@ parent:
   // to remove any matching entry from the CORS preflight cache.
   async RemoveCorsPreflightCacheEntry(URIParams uri,
                                       PrincipalInfo requestingPrincipal);
 
   // After receiving this message, the parent calls SendDeleteSelf, and makes
   // sure not to send any more messages after that.
   async DeletingChannel();
 
+  // Called to get the input stream when altData was delivered.
+  async OpenOriginalCacheInputStream();
+
   // Tell the parent the amount bytes read by child for the e10s back pressure
   // flow control
   async BytesRead(int32_t count);
 
   async __delete__();
 
 child:
   async OnStartRequest(nsresult            channelStatus,
@@ -165,16 +169,17 @@ child:
   // console.
   async LogBlockedCORSRequest(nsString message, nsCString category);
 
   async AttachStreamFilter(Endpoint<PStreamFilterParent> aEndpoint);
 
   // See ADivertableParentChannel::CancelDiversion
   async CancelDiversion();
 
+  async OriginalCacheInputStreamAvailable(OptionalIPCStream stream);
 both:
   // After receiving this message, the parent also calls
   // SendFinishInterceptedRedirect, and makes sure not to send any more messages
   // after that. When receiving this message, the child will call
   // Send__delete__() and complete the steps required to finish the redirect.
   async FinishInterceptedRedirect();
 
   async SetPriority(int16_t priority);
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -8344,16 +8344,33 @@ nsHttpChannel::OpenAlternativeOutputStre
     if (NS_SUCCEEDED(rv)) {
         // Clear this metadata flag in case it exists.
         // The caller of this method may set it again.
         cacheEntry->SetMetaDataElement("alt-data-from-child", nullptr);
     }
     return rv;
 }
 
+NS_IMETHODIMP
+nsHttpChannel::GetOriginalInputStream(nsIInputStreamReceiver *aReceiver)
+{
+    if (aReceiver == nullptr) {
+        return NS_ERROR_INVALID_ARG;
+    }
+    nsCOMPtr<nsIInputStream> inputStream;
+
+    nsCOMPtr<nsICacheEntry> cacheEntry = mCacheEntry ? mCacheEntry
+                                                     : mAltDataCacheEntry;
+    if (cacheEntry) {
+        cacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
+    }
+    aReceiver->OnInputStreamReady(inputStream);
+    return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsICachingChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::GetCacheToken(nsISupports **token)
 {
     NS_ENSURE_ARG_POINTER(token);
--- a/netwerk/test/unit/test_alt-data_simple.js
+++ b/netwerk/test/unit/test_alt-data_simple.js
@@ -147,17 +147,25 @@ function readAltContent(request, buffer)
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   Assert.equal(servedNotModified, true);
   Assert.equal(cc.alternativeDataType, altContentType);
   Assert.equal(buffer, altContent);
   check_has_alt_data_in_index(true);
 
-  requestAgain();
+  cc.getOriginalInputStream({
+    onInputStreamReady: function(aInputStream) {
+      executeSoon(function() {
+        let originalData = read_stream(aInputStream, aInputStream.available());
+        Assert.equal(originalData, responseContent);
+        requestAgain();
+      });
+    }
+  });
 }
 
 function requestAgain()
 {
   shouldPassRevalidation = false;
   var chan = make_channel(URL);
   var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
   cc.preferAlternativeDataType(altContentType, "");