Bug 1161684 - Allow JAR channels to be intercepted by service workers. r=jdm
☠☠ backed out by 6f16344cdb28 ☠ ☠
authorFernando Jimenez <ferjmoreno@gmail.com>
Fri, 22 May 2015 09:32:25 +0200
changeset 245126 068cc6419d1e4129e0872f9e628af7621b2fb3cc
parent 245125 31662149dab4c44d35ed27ddd16c91ef32c8d745
child 245127 f938930b3ac59d38ad27d3cdeb2018f912b500d6
push id28797
push userryanvm@gmail.com
push dateFri, 22 May 2015 18:02:57 +0000
treeherdermozilla-central@a69094e0f2a4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
bugs1161684
milestone41.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 1161684 - Allow JAR channels to be intercepted by service workers. r=jdm
dom/workers/ServiceWorkerManager.cpp
modules/libjar/InterceptedJARChannel.cpp
modules/libjar/InterceptedJARChannel.h
modules/libjar/moz.build
modules/libjar/nsJARChannel.cpp
modules/libjar/nsJARChannel.h
netwerk/base/moz.build
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -9,16 +9,17 @@
 #include "nsIAppsService.h"
 #include "nsIDOMEventTarget.h"
 #include "nsIDocument.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIStreamLoader.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIHttpHeaderVisitor.h"
+#include "nsIJARChannel.h"
 #include "nsINetworkInterceptController.h"
 #include "nsIMutableArray.h"
 #include "nsIUploadChannel2.h"
 #include "nsPIDOMWindow.h"
 #include "nsScriptLoader.h"
 #include "nsDebug.h"
 
 #include "jsapi.h"
@@ -2589,70 +2590,80 @@ public:
 
     nsCOMPtr<nsIURI> uri;
     rv = channel->GetURI(getter_AddRefs(uri));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = uri->GetSpec(mSpec);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
-    NS_ENSURE_TRUE(httpChannel, NS_ERROR_NOT_AVAILABLE);
-
-    rv = httpChannel->GetRequestMethod(mMethod);
-    NS_ENSURE_SUCCESS(rv, rv);
-
     uint32_t loadFlags;
     rv = channel->GetLoadFlags(&loadFlags);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
-    NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
-
-    uint32_t mode;
-    internalChannel->GetCorsMode(&mode);
-    switch (mode) {
-      case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN:
-        mRequestMode = RequestMode::Same_origin;
-        break;
-      case nsIHttpChannelInternal::CORS_MODE_NO_CORS:
-        mRequestMode = RequestMode::No_cors;
-        break;
-      case nsIHttpChannelInternal::CORS_MODE_CORS:
-      case nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT:
-        mRequestMode = RequestMode::Cors;
-        break;
-      default:
-        MOZ_CRASH("Unexpected CORS mode");
-    }
-
-    if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
-      mRequestCredentials = RequestCredentials::Omit;
-    } else {
-      bool includeCrossOrigin;
-      internalChannel->GetCorsIncludeCredentials(&includeCrossOrigin);
-      if (includeCrossOrigin) {
-        mRequestCredentials = RequestCredentials::Include;
-      }
-    }
-
-    rv = httpChannel->VisitRequestHeaders(this);
-    NS_ENSURE_SUCCESS(rv, rv);
-
     nsCOMPtr<nsILoadInfo> loadInfo;
     rv = channel->GetLoadInfo(getter_AddRefs(loadInfo));
     NS_ENSURE_SUCCESS(rv, rv);
 
     mContentPolicyType = loadInfo->GetContentPolicyType();
 
-    nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
-    if (uploadChannel) {
-      MOZ_ASSERT(!mUploadStream);
-      rv = uploadChannel->CloneUploadStream(getter_AddRefs(mUploadStream));
+    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
+    if (httpChannel) {
+      rv = httpChannel->GetRequestMethod(mMethod);
       NS_ENSURE_SUCCESS(rv, rv);
+
+      nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
+      NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
+
+      uint32_t mode;
+      internalChannel->GetCorsMode(&mode);
+      switch (mode) {
+        case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN:
+          mRequestMode = RequestMode::Same_origin;
+          break;
+        case nsIHttpChannelInternal::CORS_MODE_NO_CORS:
+          mRequestMode = RequestMode::No_cors;
+          break;
+        case nsIHttpChannelInternal::CORS_MODE_CORS:
+        case nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT:
+          mRequestMode = RequestMode::Cors;
+          break;
+        default:
+          MOZ_CRASH("Unexpected CORS mode");
+      }
+
+      if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
+        mRequestCredentials = RequestCredentials::Omit;
+      } else {
+        bool includeCrossOrigin;
+        internalChannel->GetCorsIncludeCredentials(&includeCrossOrigin);
+        if (includeCrossOrigin) {
+          mRequestCredentials = RequestCredentials::Include;
+        }
+      }
+
+      rv = httpChannel->VisitRequestHeaders(this);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
+      if (uploadChannel) {
+        MOZ_ASSERT(!mUploadStream);
+        rv = uploadChannel->CloneUploadStream(getter_AddRefs(mUploadStream));
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+    } else {
+      nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel);
+      // If it is not an HTTP channel it must be a JAR one.
+      NS_ENSURE_TRUE(jarChannel, NS_ERROR_NOT_AVAILABLE);
+
+      mMethod = "GET";
+
+      if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
+        mRequestCredentials = RequestCredentials::Omit;
+      }
     }
 
     return NS_OK;
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
new file mode 100644
--- /dev/null
+++ b/modules/libjar/InterceptedJARChannel.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=2 sw=2 sts=2 cin: */
+/* 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 "InterceptedJARChannel.h"
+#include "nsIPipe.h"
+
+using namespace mozilla::net;
+
+NS_IMPL_ISUPPORTS(InterceptedJARChannel, nsIInterceptedChannel)
+
+InterceptedJARChannel::InterceptedJARChannel(nsJARChannel* aChannel,
+                                             nsINetworkInterceptController* aController,
+                                             bool aIsNavigation)
+: mController(aController)
+, mChannel(aChannel)
+, mIsNavigation(aIsNavigation)
+{
+}
+
+NS_IMETHODIMP
+InterceptedJARChannel::GetResponseBody(nsIOutputStream** aStream)
+{
+  NS_IF_ADDREF(*aStream = mResponseBody);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedJARChannel::GetIsNavigation(bool* aIsNavigation)
+{
+  *aIsNavigation = mIsNavigation;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedJARChannel::GetChannel(nsIChannel** aChannel)
+{
+  NS_IF_ADDREF(*aChannel = mChannel);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedJARChannel::ResetInterception()
+{
+  if (!mChannel) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mResponseBody = nullptr;
+  mSynthesizedInput = nullptr;
+
+  mChannel->ResetInterception();
+  mChannel = nullptr;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedJARChannel::SynthesizeStatus(uint16_t aStatus,
+                                        const nsACString& aReason)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedJARChannel::SynthesizeHeader(const nsACString& aName,
+                                        const nsACString& aValue)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedJARChannel::FinishSynthesizedResponse()
+{
+  if (NS_WARN_IF(!mChannel)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mChannel->OverrideWithSynthesizedResponse(mSynthesizedInput);
+
+  mResponseBody = nullptr;
+  mChannel = nullptr;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedJARChannel::Cancel()
+{
+  if (!mChannel) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = mChannel->Cancel(NS_BINDING_ABORTED);
+  NS_ENSURE_SUCCESS(rv, rv);
+  mResponseBody = nullptr;
+  mChannel = nullptr;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedJARChannel::SetSecurityInfo(nsISupports* aSecurityInfo)
+{
+  if (!mChannel) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return mChannel->OverrideSecurityInfo(aSecurityInfo);
+}
+
+void
+InterceptedJARChannel::NotifyController()
+{
+  nsresult rv = NS_NewPipe(getter_AddRefs(mSynthesizedInput),
+                           getter_AddRefs(mResponseBody),
+                           0, UINT32_MAX, true, true);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  rv = mController->ChannelIntercepted(this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    rv = ResetInterception();
+    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
+        "Failed to resume intercepted network request");
+  }
+  mController = nullptr;
+}
new file mode 100644
--- /dev/null
+++ b/modules/libjar/InterceptedJARChannel.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=2 sw=2 sts=2 cin: */
+/* 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 InterceptedJARChannel_h
+#define InterceptedJARChannel_h
+
+#include "nsJAR.h"
+#include "nsJARChannel.h"
+#include "nsIInputStream.h"
+#include "nsIInputStreamPump.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIOutputStream.h"
+#include "nsRefPtr.h"
+
+#include "mozilla/Maybe.h"
+
+class nsIStreamListener;
+class nsJARChannel;
+
+namespace mozilla {
+namespace net {
+
+// An object representing a channel that has been intercepted. This avoids
+// complicating the actual channel implementation with the details of
+// synthesizing responses.
+class InterceptedJARChannel : public nsIInterceptedChannel
+{
+  // The interception controller to notify about the successful channel
+  // interception.
+  nsCOMPtr<nsINetworkInterceptController> mController;
+
+  // The actual channel being intercepted.
+  nsRefPtr<nsJARChannel> mChannel;
+
+  // Reader-side of the synthesized response body.
+  nsCOMPtr<nsIInputStream> mSynthesizedInput;
+
+  // The stream to write the body of the synthesized response.
+  nsCOMPtr<nsIOutputStream> mResponseBody;
+
+  // Wether this intercepted channel was performing a navigation.
+  bool mIsNavigation;
+
+  virtual ~InterceptedJARChannel() {};
+public:
+  InterceptedJARChannel(nsJARChannel* aChannel,
+                        nsINetworkInterceptController* aController,
+                        bool aIsNavigation);
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIINTERCEPTEDCHANNEL
+
+  void NotifyController();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // InterceptedJARChannel_h
--- a/modules/libjar/moz.build
+++ b/modules/libjar/moz.build
@@ -18,22 +18,24 @@ XPIDL_SOURCES += [
     'nsIJARProtocolHandler.idl',
     'nsIJARURI.idl',
     'nsIZipReader.idl',
 ]
 
 XPIDL_MODULE = 'jar'
 
 EXPORTS += [
+    'InterceptedJARChannel.h',
     'nsJARURI.h',
     'nsZipArchive.h',
     'zipstruct.h',
 ]
 
 UNIFIED_SOURCES += [
+    'InterceptedJARChannel.cpp',
     'nsJARProtocolHandler.cpp',
     'nsJARURI.cpp',
 ]
 
 # These files cannot be built in unified mode because they rely on plarena.h.
 SOURCES += [
     'nsJAR.cpp',
     'nsJARChannel.cpp',
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -19,16 +19,19 @@
 #include "nsIScriptSecurityManager.h"
 #include "nsIPrincipal.h"
 #include "nsIFileURL.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/net/RemoteOpenFileChild.h"
 #include "nsITabChild.h"
 #include "private/pprio.h"
+#include "nsINetworkInterceptController.h"
+#include "InterceptedJARChannel.h"
+#include "nsInputStreamPump.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 
 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
 
 // the entry for a directory will either be empty (in the case of the
 // top-level directory) or will end with a slash
@@ -194,16 +197,17 @@ nsJARChannel::nsJARChannel()
     , mAppURI(nullptr)
     , mContentLength(-1)
     , mLoadFlags(LOAD_NORMAL)
     , mStatus(NS_OK)
     , mIsPending(false)
     , mIsUnsafe(true)
     , mOpeningRemote(false)
     , mEnsureChildFd(false)
+    , mSynthesizedStreamLength(0)
 {
     if (!gJarProtocolLog)
         gJarProtocolLog = PR_NewLogModule("nsJarProtocol");
 
     // hold an owning reference to the jar handler
     NS_ADDREF(gJarHandler);
 }
 
@@ -229,17 +233,17 @@ NS_IMPL_ISUPPORTS_INHERITED(nsJARChannel
                             nsIChannel,
                             nsIStreamListener,
                             nsIRequestObserver,
                             nsIRemoteOpenFileListener,
                             nsIThreadRetargetableRequest,
                             nsIThreadRetargetableStreamListener,
                             nsIJARChannel)
 
-nsresult 
+nsresult
 nsJARChannel::Init(nsIURI *uri)
 {
     nsresult rv;
     mJarURI = do_QueryInterface(uri, &rv);
     if (NS_FAILED(rv))
         return rv;
 
     mOriginalURI = mJarURI;
@@ -525,47 +529,55 @@ nsJARChannel::IsPending(bool *result)
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::GetStatus(nsresult *status)
 {
     if (mPump && NS_SUCCEEDED(mStatus))
         mPump->GetStatus(status);
+    else if (mSynthesizedResponsePump && NS_SUCCEEDED(mStatus))
+        mSynthesizedResponsePump->GetStatus(status);
     else
         *status = mStatus;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::Cancel(nsresult status)
 {
     mStatus = status;
     if (mPump)
         return mPump->Cancel(status);
+    if (mSynthesizedResponsePump)
+        return mSynthesizedResponsePump->Cancel(status);
 
     NS_ASSERTION(!mIsPending, "need to implement cancel when downloading");
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::Suspend()
 {
     if (mPump)
         return mPump->Suspend();
+    if (mSynthesizedResponsePump)
+        return mSynthesizedResponsePump->Suspend();
 
     NS_ASSERTION(!mIsPending, "need to implement suspend when downloading");
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::Resume()
 {
     if (mPump)
         return mPump->Resume();
+    if (mSynthesizedResponsePump)
+        return mSynthesizedResponsePump->Resume();
 
     NS_ASSERTION(!mIsPending, "need to implement resume when downloading");
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
 {
@@ -665,17 +677,30 @@ nsJARChannel::GetNotificationCallbacks(n
 
 NS_IMETHODIMP
 nsJARChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
 {
     mCallbacks = aCallbacks;
     return NS_OK;
 }
 
-NS_IMETHODIMP 
+nsresult
+nsJARChannel::OverrideSecurityInfo(nsISupports* aSecurityInfo)
+{
+  MOZ_RELEASE_ASSERT(!mSecurityInfo,
+                     "This can only be called when we don't have a security info object already");
+  MOZ_RELEASE_ASSERT(aSecurityInfo,
+                     "This can only be called with a valid security info object");
+  MOZ_RELEASE_ASSERT(ShouldIntercept(),
+                     "This can only be called on channels that can be intercepted");
+  mSecurityInfo = aSecurityInfo;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsJARChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
 {
     NS_PRECONDITION(aSecurityInfo, "Null out param");
     NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
     return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -831,43 +856,125 @@ nsJARChannel::Open(nsIInputStream **stre
 
     input.forget(stream);
     mOpened = true;
     // local files are always considered safe
     mIsUnsafe = false;
     return NS_OK;
 }
 
+bool
+nsJARChannel::ShouldIntercept()
+{
+    LOG(("nsJARChannel::ShouldIntercept [this=%x]\n", this));
+    // We only intercept app:// requests
+    if (!mAppURI) {
+      return false;
+    }
+
+    nsCOMPtr<nsINetworkInterceptController> controller;
+    NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+                                  NS_GET_IID(nsINetworkInterceptController),
+                                  getter_AddRefs(controller));
+    bool shouldIntercept = false;
+    if (controller) {
+      bool isNavigation = mLoadFlags & LOAD_DOCUMENT_URI;
+      nsresult rv = controller->ShouldPrepareForIntercept(mAppURI,
+                                                          isNavigation,
+                                                          &shouldIntercept);
+      NS_ENSURE_SUCCESS(rv, false);
+    }
+
+    return shouldIntercept;
+}
+
+void nsJARChannel::ResetInterception()
+{
+    LOG(("nsJARChannel::ResetInterception [this=%x]\n", this));
+
+    // Continue with the origin request.
+    nsresult rv = ContinueAsyncOpen();
+    NS_ENSURE_SUCCESS_VOID(rv);
+}
+
+void
+nsJARChannel::OverrideWithSynthesizedResponse(nsIInputStream* aSynthesizedInput)
+{
+    // In our current implementation, the FetchEvent handler will copy the
+    // response stream completely into the pipe backing the input stream so we
+    // can treat the available as the length of the stream.
+    uint64_t available;
+    nsresult rv = aSynthesizedInput->Available(&available);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mSynthesizedStreamLength = -1;
+    } else {
+      mSynthesizedStreamLength = int64_t(available);
+    }
+
+    rv = nsInputStreamPump::Create(getter_AddRefs(mSynthesizedResponsePump),
+                                   aSynthesizedInput,
+                                   int64_t(-1), int64_t(-1), 0, 0, true);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aSynthesizedInput->Close();
+      return;
+    }
+
+    rv = mSynthesizedResponsePump->AsyncRead(this, nullptr);
+    NS_ENSURE_SUCCESS_VOID(rv);
+}
+
 NS_IMETHODIMP
 nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
 {
     LOG(("nsJARChannel::AsyncOpen [this=%x]\n", this));
 
     NS_ENSURE_ARG_POINTER(listener);
     NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS);
     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
 
     mJarFile = nullptr;
     mIsUnsafe = true;
 
     // Initialize mProgressSink
     NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink);
 
-    nsresult rv = LookupFile(true);
-    if (NS_FAILED(rv))
-        return rv;
-
-    // These variables must only be set if we're going to trigger an
-    // OnStartRequest, either from AsyncRead or OnDownloadComplete.
-    // 
-    // That means: Do not add early return statements beyond this point!
     mListener = listener;
     mListenerContext = ctx;
     mIsPending = true;
 
+    // Check if this channel should intercept the network request and prepare
+    // for a possible synthesized response instead.
+    if (ShouldIntercept()) {
+      nsCOMPtr<nsINetworkInterceptController> controller;
+      NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+                                    NS_GET_IID(nsINetworkInterceptController),
+                                    getter_AddRefs(controller));
+
+      bool isNavigation = mLoadFlags & LOAD_DOCUMENT_URI;
+      nsRefPtr<InterceptedJARChannel> intercepted =
+        new InterceptedJARChannel(this, controller, isNavigation);
+      intercepted->NotifyController();
+      return NS_OK;
+    }
+
+    return ContinueAsyncOpen();
+}
+
+nsresult
+nsJARChannel::ContinueAsyncOpen()
+{
+    LOG(("nsJARChannel::ContinueAsyncOpen [this=%x]\n", this));
+    nsresult rv = LookupFile(true);
+    if (NS_FAILED(rv)) {
+        mIsPending = false;
+        mListenerContext = nullptr;
+        mListener = nullptr;
+        return rv;
+    }
+
     nsCOMPtr<nsIChannel> channel;
 
     if (!mJarFile) {
         // Not a local file...
         // kick off an async download of the base URI...
         nsCOMPtr<nsIStreamListener> downloader = new MemoryDownloader(this);
         // Since we might not have a loadinfo on all channels yet
         // we have to provide default arguments in case mLoadInfo is null;
--- a/modules/libjar/nsJARChannel.h
+++ b/modules/libjar/nsJARChannel.h
@@ -5,16 +5,17 @@
 
 #ifndef nsJARChannel_h__
 #define nsJARChannel_h__
 
 #include "mozilla/net/MemoryDownloader.h"
 #include "nsIJARChannel.h"
 #include "nsIJARURI.h"
 #include "nsIInputStreamPump.h"
+#include "InterceptedJARChannel.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIProgressEventSink.h"
 #include "nsIStreamListener.h"
 #include "nsIRemoteOpenFileListener.h"
 #include "nsIZipReader.h"
 #include "nsILoadGroup.h"
 #include "nsILoadInfo.h"
 #include "nsIThreadRetargetableRequest.h"
@@ -22,16 +23,23 @@
 #include "nsHashPropertyBag.h"
 #include "nsIFile.h"
 #include "nsIURI.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "mozilla/Logging.h"
 
 class nsJARInputThunk;
+class nsInputStreamPump;
+
+namespace mozilla {
+namespace net {
+  class InterceptedJARChannel;
+} // namespace net
+} // namespace mozilla
 
 //-----------------------------------------------------------------------------
 
 class nsJARChannel final : public nsIJARChannel
                          , public mozilla::net::MemoryDownloader::IObserver
                          , public nsIStreamListener
                          , public nsIRemoteOpenFileListener
                          , public nsIThreadRetargetableRequest
@@ -64,16 +72,28 @@ private:
     nsresult SetRemoteNSPRFileDesc(PRFileDesc *fd);
     virtual void OnDownloadComplete(mozilla::net::MemoryDownloader* aDownloader,
                                     nsIRequest* aRequest,
                                     nsISupports* aCtxt,
                                     nsresult aStatus,
                                     mozilla::net::MemoryDownloader::Data aData)
         override;
 
+    // Returns true if this channel should intercept the network request and
+    // prepare for a possible synthesized response instead.
+    bool ShouldIntercept();
+    nsresult ContinueAsyncOpen();
+    // Discard the prior interception and continue with the original network
+    // request.
+    void ResetInterception();
+    // Override this channel's pending response with a synthesized one. The
+    // content will be asynchronously read from the pump.
+    void OverrideWithSynthesizedResponse(nsIInputStream* aSynthesizedInput);
+    nsresult OverrideSecurityInfo(nsISupports* aSecurityInfo);
+
     nsCString                       mSpec;
 
     bool                            mOpened;
 
     nsCOMPtr<nsIJARURI>             mJarURI;
     nsCOMPtr<nsIURI>                mOriginalURI;
     nsCOMPtr<nsIURI>                mAppURI;
     nsCOMPtr<nsISupports>           mOwner;
@@ -102,11 +122,16 @@ private:
     nsCOMPtr<nsIInputStreamPump>    mPump;
     // mRequest is only non-null during OnStartRequest, so we'll have a pointer
     // to the request if we get called back via RetargetDeliveryTo.
     nsCOMPtr<nsIRequest>            mRequest;
     nsCOMPtr<nsIFile>               mJarFile;
     nsCOMPtr<nsIURI>                mJarBaseURI;
     nsCString                       mJarEntry;
     nsCString                       mInnerJarEntry;
+
+    nsRefPtr<nsInputStreamPump> mSynthesizedResponsePump;
+    int64_t mSynthesizedStreamLength;
+
+    friend class mozilla::net::InterceptedJARChannel;
 };
 
 #endif // nsJARChannel_h__
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -139,16 +139,17 @@ if CONFIG['MOZ_TOOLKIT_SEARCH']:
 
 XPIDL_MODULE = 'necko'
 
 EXPORTS += [
     'netCore.h',
     'nsASocketHandler.h',
     'nsAsyncRedirectVerifyHelper.h',
     'nsFileStreams.h',
+    'nsInputStreamPump.h',
     'nsMIMEInputStream.h',
     'nsNetUtil.h',
     'nsReadLine.h',
     'nsSerializationHelper.h',
     'nsSimpleNestedURI.h',
     'nsSimpleURI.h',
     'nsStreamListenerWrapper.h',
     'nsTemporaryFileInputStream.h',