Bug 1186290 - Notify TabParent to switch process when loading a signed package. r=honzab, r=kanru.
authorHenry Chang <hchang@mozilla.com>
Fri, 02 Oct 2015 05:25:22 -0700
changeset 266152 ca240e275047843b4f2920f463fd436d2691ccd0
parent 266151 7563e84f6efc72e380aac6d43336df93ad004462
child 266153 2f47017b463b4e89ecfb8de399b054ae28e818e4
push id66136
push usercbook@mozilla.com
push dateTue, 06 Oct 2015 06:34:53 +0000
treeherdermozilla-inbound@29467b3d2124 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab, kanru
bugs1186290
milestone44.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 1186290 - Notify TabParent to switch process when loading a signed package. r=honzab, r=kanru.
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
netwerk/base/moz.build
netwerk/base/nsIPackagedAppChannelListener.idl
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/HttpChannelParentListener.cpp
netwerk/protocol/http/HttpChannelParentListener.h
netwerk/protocol/http/PackagedAppService.cpp
netwerk/protocol/http/PackagedAppService.h
netwerk/protocol/http/PackagedAppVerifier.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/test/mochitests/mochitest.ini
netwerk/test/mochitests/signed_web_packaged_app.sjs
netwerk/test/mochitests/test_signed_web_packaged_app.html
netwerk/test/unit/test_packaged_app_service.js
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -103,16 +103,22 @@
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::services;
 using namespace mozilla::widget;
 using namespace mozilla::jsipc;
 
+#if DEUBG
+  #define LOG(args...) printf_stderr(args)
+#else
+  #define LOG(...)
+#endif
+
 // The flags passed by the webProgress notifications are 16 bits shifted
 // from the ones registered by webProgressListeners.
 #define NOTIFY_FLAG_SHIFT 16
 
 class OpenFileAndSendFDRunnable : public nsRunnable
 {
     const nsString mPath;
     nsRefPtr<TabParent> mTabParent;
@@ -432,16 +438,114 @@ TabParent::IsVisible()
     return false;
   }
 
   bool visible = false;
   frameLoader->GetVisible(&visible);
   return visible;
 }
 
+static void LogChannelRelevantInfo(nsIURI* aURI,
+                                   nsIPrincipal* aLoadingPrincipal,
+                                   nsIPrincipal* aChannelResultPrincipal,
+                                   nsContentPolicyType aContentPolicyType) {
+  nsCString loadingOrigin;
+  aLoadingPrincipal->GetOrigin(loadingOrigin);
+
+  nsCString uriString;
+  aURI->GetAsciiSpec(uriString);
+  LOG("Loading %s from origin %s (type: %d)\n", uriString.get(),
+                                                loadingOrigin.get(),
+                                                aContentPolicyType);
+
+  nsCString resultPrincipalOrigin;
+  aChannelResultPrincipal->GetOrigin(resultPrincipalOrigin);
+  LOG("Result principal origin: %s\n", resultPrincipalOrigin.get());
+}
+
+bool
+TabParent::ShouldSwitchProcess(nsIChannel* aChannel)
+{
+  // If we lack of any information which is required to decide the need of
+  // process switch, consider that we should switch process.
+
+  // Prepare the channel loading principal.
+  nsCOMPtr<nsILoadInfo> loadInfo;
+  aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+  NS_ENSURE_TRUE(loadInfo, true);
+  nsCOMPtr<nsIPrincipal> loadingPrincipal;
+  loadInfo->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal));
+  NS_ENSURE_TRUE(loadingPrincipal, true);
+
+  // Prepare the channel result principal.
+  nsCOMPtr<nsIPrincipal> resultPrincipal;
+  nsContentUtils::GetSecurityManager()->
+    GetChannelResultPrincipal(aChannel, getter_AddRefs(resultPrincipal));
+
+  // Log the debug info which is used to decide the need of proces switch.
+  nsCOMPtr<nsIURI> uri;
+  aChannel->GetURI(getter_AddRefs(uri));
+  LogChannelRelevantInfo(uri, loadingPrincipal, resultPrincipal,
+                         loadInfo->GetContentPolicyType());
+
+  // Check if the signed package is loaded from the same origin.
+  bool sameOrigin = false;
+  loadingPrincipal->Equals(resultPrincipal, &sameOrigin);
+  if (sameOrigin) {
+    LOG("Loading singed package from the same origin. Don't switch process.\n");
+    return false;
+  }
+
+  // If this is not a top level document, there's no need to switch process.
+  if (nsIContentPolicy::TYPE_DOCUMENT != loadInfo->GetContentPolicyType()) {
+    LOG("Subresource of a document. No need to switch process.\n");
+    return false;
+  }
+
+  // If this is a brand new process created to load the signed package
+  // (triggered by previous OnStartSignedPackageRequest), the loading origin
+  // will be "moz-safe-about:blank". In that case, we don't need to switch process
+  // again. We compare with "moz-safe-about:blank" without appId/isBrowserElement/etc
+  // taken into account. That's why we use originNoSuffix.
+  nsCString loadingOriginNoSuffix;
+  loadingPrincipal->GetOriginNoSuffix(loadingOriginNoSuffix);
+  if (loadingOriginNoSuffix.EqualsLiteral("moz-safe-about:blank")) {
+    LOG("The content is already loaded by a brand new process.\n");
+    return false;
+  }
+
+  return true;
+}
+
+void
+TabParent::OnStartSignedPackageRequest(nsIChannel* aChannel)
+{
+  if (!ShouldSwitchProcess(aChannel)) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  aChannel->GetURI(getter_AddRefs(uri));
+
+  aChannel->Cancel(NS_BINDING_FAILED);
+
+  nsCString uriString;
+  uri->GetAsciiSpec(uriString);
+  LOG("We decide to switch process. Call nsFrameLoader::SwitchProcessAndLoadURIs: %s\n",
+       uriString.get());
+
+  nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+  NS_ENSURE_TRUE_VOID(frameLoader);
+
+  nsresult rv = frameLoader->SwitchProcessAndLoadURI(uri);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to switch process.");
+  }
+}
+
 void
 TabParent::DestroyInternal()
 {
   IMEStateManager::OnTabParentDestroying(this);
 
   RemoveWindowListeners();
 
   // If this fails, it's most likely due to a content-process crash,
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -437,16 +437,20 @@ public:
                           const int32_t& aDragAreaX, const int32_t& aDragAreaY) override;
 
     void AddInitialDnDDataTo(DataTransfer* aDataTransfer);
 
     void TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface,
                                int32_t& aDragAreaX, int32_t& aDragAreaY);
     layout::RenderFrameParent* GetRenderFrame();
 
+    // Called by HttpChannelParent. The function may use a new process to
+    // reload the URI associated with the given channel.
+    void OnStartSignedPackageRequest(nsIChannel* aChannel);
+
 protected:
     bool ReceiveMessage(const nsString& aMessage,
                         bool aSync,
                         ipc::StructuredCloneData* aData,
                         mozilla::jsipc::CpowHolder* aCpows,
                         nsIPrincipal* aPrincipal,
                         nsTArray<ipc::StructuredCloneData>* aJSONRetVal = nullptr);
 
@@ -477,16 +481,20 @@ protected:
     virtual bool RecvAudioChannelActivityNotification(const uint32_t& aAudioChannel,
                                                       const bool& aActive) override;
 
     bool InitBrowserConfiguration(const nsCString& aURI,
                                   BrowserConfiguration& aConfiguration);
 
     void SetHasContentOpener(bool aHasContentOpener);
 
+    // Decide whether we have to use a new process to reload the URI associated
+    // with the given channel.
+    bool ShouldSwitchProcess(nsIChannel* aChannel);
+
     ContentCacheInParent mContentCache;
 
     nsIntRect mRect;
     ScreenIntSize mDimensions;
     ScreenOrientationInternal mOrientation;
     float mDPI;
     CSSToLayoutDeviceScale mDefaultScale;
     bool mUpdatedDimensions;
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -64,16 +64,17 @@ XPIDL_SOURCES += [
     'nsINetworkInterceptController.idl',
     'nsINetworkLinkService.idl',
     'nsINetworkPredictor.idl',
     'nsINetworkPredictorVerifier.idl',
     'nsINetworkProperties.idl',
     'nsINSSErrorsService.idl',
     'nsINullChannel.idl',
     'nsIPACGenerator.idl',
+    'nsIPackagedAppChannelListener.idl',
     'nsIPackagedAppService.idl',
     'nsIPackagedAppUtils.idl',
     'nsIPackagedAppVerifier.idl',
     'nsIParentChannel.idl',
     'nsIParentRedirectingChannel.idl',
     'nsIPermission.idl',
     'nsIPermissionManager.idl',
     'nsIPrivateBrowsingChannel.idl',
new file mode 100644
--- /dev/null
+++ b/netwerk/base/nsIPackagedAppChannelListener.idl
@@ -0,0 +1,25 @@
+/* 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"
+
+/**
+ * nsIPackagedAppChannelListener
+ */
+[scriptable, uuid(27caf7d0-3c0e-11e5-b970-0800200c9a66)]
+interface nsIPackagedAppChannelListener : nsISupports
+{
+  /**
+   * @param aPackageId
+   *    the package identifier of the signed package that we are going
+   *    to load. The identifier will be defined in the manifest of the
+   *    package.
+   *
+   * This callback is to notify a signed package is about to load. Some
+   * component else will be in charge of responding to this fact properly.
+   * The procotol layer should have no idea what to do with this.
+   *
+   */
+  void onStartSignedPackageRequest(in ACString aPackageId);
+};
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -149,16 +149,17 @@ HttpChannelParent::Init(const HttpChanne
 // HttpChannelParent::nsISupports
 //-----------------------------------------------------------------------------
 
 NS_IMPL_ISUPPORTS(HttpChannelParent,
                   nsIInterfaceRequestor,
                   nsIProgressEventSink,
                   nsIRequestObserver,
                   nsIStreamListener,
+                  nsIPackagedAppChannelListener,
                   nsIParentChannel,
                   nsIAuthPromptProvider,
                   nsIParentRedirectingChannel,
                   nsINetworkInterceptController,
                   nsIDeprecationWarner)
 
 NS_IMETHODIMP
 HttpChannelParent::ShouldPrepareForIntercept(nsIURI* aURI, bool aIsNavigate,
@@ -1032,16 +1033,29 @@ HttpChannelParent::RecvRemoveCorsPreflig
     return false;
   }
   nsCORSListenerProxy::RemoveFromCorsPreflightCache(deserializedURI,
                                                     principal);
   return true;
 }
 
 //-----------------------------------------------------------------------------
+// HttpChannelParent::nsIPackagedAppChannelListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelParent::OnStartSignedPackageRequest(const nsACString& aPackageId)
+{
+  if (mTabParent) {
+    mTabParent->OnStartSignedPackageRequest(mChannel);
+  }
+  return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
 // HttpChannelParent::nsIRequestObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
 {
   LOG(("HttpChannelParent::OnStartRequest [this=%p, aRequest=%p]\n",
        this, aRequest));
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -17,16 +17,17 @@
 #include "nsIObserver.h"
 #include "nsIParentRedirectingChannel.h"
 #include "nsIProgressEventSink.h"
 #include "nsHttpChannel.h"
 #include "nsIAuthPromptProvider.h"
 #include "mozilla/dom/ipc/IdType.h"
 #include "nsINetworkInterceptController.h"
 #include "nsIDeprecationWarner.h"
+#include "nsIPackagedAppChannelListener.h"
 
 class nsICacheEntry;
 class nsIAssociatedContentSecurity;
 
 namespace mozilla {
 
 namespace dom{
 class TabParent;
@@ -41,24 +42,26 @@ class HttpChannelParent final : public P
                               , public nsIParentRedirectingChannel
                               , public nsIProgressEventSink
                               , public nsIInterfaceRequestor
                               , public ADivertableParentChannel
                               , public nsIAuthPromptProvider
                               , public nsINetworkInterceptController
                               , public nsIDeprecationWarner
                               , public DisconnectableParent
+                              , public nsIPackagedAppChannelListener
                               , public HttpChannelSecurityWarningReporter
 {
   virtual ~HttpChannelParent();
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
+  NS_DECL_NSIPACKAGEDAPPCHANNELLISTENER
   NS_DECL_NSIPARENTCHANNEL
   NS_DECL_NSIPARENTREDIRECTINGCHANNEL
   NS_DECL_NSIPROGRESSEVENTSINK
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIAUTHPROMPTPROVIDER
   NS_DECL_NSINETWORKINTERCEPTCONTROLLER
   NS_DECL_NSIDEPRECATIONWARNER
 
--- a/netwerk/protocol/http/HttpChannelParentListener.cpp
+++ b/netwerk/protocol/http/HttpChannelParentListener.cpp
@@ -7,16 +7,17 @@
 // HttpLog.h should generally be included first
 #include "HttpLog.h"
 
 #include "HttpChannelParentListener.h"
 #include "mozilla/net/HttpChannelParent.h"
 #include "mozilla/unused.h"
 #include "nsIRedirectChannelRegistrar.h"
 #include "nsIHttpEventSink.h"
+#include "nsIPackagedAppChannelListener.h"
 
 using mozilla::unused;
 
 namespace mozilla {
 namespace net {
 
 HttpChannelParentListener::HttpChannelParentListener(HttpChannelParent* aInitialChannel)
   : mNextListener(aInitialChannel)
@@ -33,16 +34,17 @@ HttpChannelParentListener::~HttpChannelP
 // HttpChannelParentListener::nsISupports
 //-----------------------------------------------------------------------------
 
 NS_IMPL_ISUPPORTS(HttpChannelParentListener,
                   nsIInterfaceRequestor,
                   nsIStreamListener,
                   nsIRequestObserver,
                   nsIChannelEventSink,
+                  nsIPackagedAppChannelListener,
                   nsIRedirectResultListener)
 
 //-----------------------------------------------------------------------------
 // HttpChannelParentListener::nsIRequestObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelParentListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
@@ -93,16 +95,32 @@ HttpChannelParentListener::OnDataAvailab
   if (!mNextListener)
     return NS_ERROR_UNEXPECTED;
 
   LOG(("HttpChannelParentListener::OnDataAvailable [this=%p]\n", this));
   return mNextListener->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
 }
 
 //-----------------------------------------------------------------------------
+// HttpChannelParentListener::nsIPackagedAppChannelListener
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+HttpChannelParentListener::OnStartSignedPackageRequest(const nsACString& aPackageId)
+{
+  nsCOMPtr<nsIPackagedAppChannelListener> listener = do_QueryInterface(mNextListener);
+  if (listener) {
+    listener->OnStartSignedPackageRequest(aPackageId);
+  } else {
+    NS_WARNING("mNextListener is not nsIPackagedAppChannelListener");
+  }
+
+  return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
 // HttpChannelParentListener::nsIInterfaceRequestor
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelParentListener::GetInterface(const nsIID& aIID, void **result)
 {
   if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)) ||
       aIID.Equals(NS_GET_IID(nsIHttpEventSink))  ||
--- a/netwerk/protocol/http/HttpChannelParentListener.h
+++ b/netwerk/protocol/http/HttpChannelParentListener.h
@@ -6,31 +6,34 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_net_HttpChannelCallbackWrapper_h
 #define mozilla_net_HttpChannelCallbackWrapper_h
 
 #include "nsIInterfaceRequestor.h"
 #include "nsIChannelEventSink.h"
 #include "nsIRedirectResultListener.h"
+#include "nsIPackagedAppChannelListener.h"
 
 namespace mozilla {
 namespace net {
 
 class HttpChannelParent;
 
 class HttpChannelParentListener final : public nsIInterfaceRequestor
                                       , public nsIChannelEventSink
                                       , public nsIRedirectResultListener
+                                      , public nsIPackagedAppChannelListener
                                       , public nsIStreamListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSICHANNELEVENTSINK
+  NS_DECL_NSIPACKAGEDAPPCHANNELLISTENER
   NS_DECL_NSIREDIRECTRESULTLISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
 
   explicit HttpChannelParentListener(HttpChannelParent* aInitialChannel);
 
   // For channel diversion from child to parent.
   nsresult DivertTo(nsIStreamListener *aListener);
--- a/netwerk/protocol/http/PackagedAppService.cpp
+++ b/netwerk/protocol/http/PackagedAppService.cpp
@@ -721,55 +721,67 @@ PackagedAppService::PackagedAppDownloade
                                                            uint32_t aCount)
 {
   uint32_t n;
   return aInputStream->ReadSegments(ConsumeData, this, aCount, &n);
 }
 
 nsresult
 PackagedAppService::PackagedAppDownloader::AddCallback(nsIURI *aURI,
-                                                       nsICacheEntryOpenCallback *aCallback)
+                                                       nsICacheEntryOpenCallback *aCallback,
+                                                       nsIChannel* aRequester)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread(), "mCallbacks hashtable is not thread safe");
   nsAutoCString spec;
   aURI->GetAsciiSpec(spec);
 
   LogURI("PackagedAppDownloader::AddCallback", this, aURI);
   LOG(("[%p]    > callback: %p\n", this, aCallback));
 
+  nsCOMPtr<nsIPackagedAppChannelListener> listener = do_QueryInterface(aRequester);
+
   // Check if we already have a resource waiting for this resource
   nsCOMArray<nsICacheEntryOpenCallback>* array = mCallbacks.Get(spec);
   if (array) {
     if (array->Length() == 0) {
       // The download of this resource has already completed, hence we don't
       // need to wait for it to be inserted in the cache and we can serve it
       // right now, directly.  See also the CallCallbacks method bellow.
       LOG(("[%p]    > already downloaded\n", this));
 
       // This is the case where a package downloader is still running and we
       // peek data from it.
-
-      // TODO: Bug 1186290 to notify that the signed packaged content is ready
-      //       to load.
+      if (mVerifier && mVerifier->GetIsPackageSigned()) {
+        // TODO: Bug 1178526 will deal with the package identifier things.
+        //       For now we just use the origin as the identifier.
+        listener->OnStartSignedPackageRequest(mVerifier->GetPackageOrigin());
+        listener = nullptr; // So that the request will not be added to the queue.
+      }
       mCacheStorage->AsyncOpenURI(aURI, EmptyCString(),
                                   nsICacheStorage::OPEN_READONLY, aCallback);
     } else {
       LOG(("[%p]    > adding to array\n", this));
       // Add this resource to the callback array
       array->AppendObject(aCallback);
     }
   } else {
     LOG(("[%p]    > creating array\n", this));
     // This is the first callback for this URI.
     // Create a new array and add the callback
     nsCOMArray<nsICacheEntryOpenCallback>* newArray =
       new nsCOMArray<nsICacheEntryOpenCallback>();
     newArray->AppendObject(aCallback);
     mCallbacks.Put(spec, newArray);
   }
+
+  // Add the outer channel for notifying OnStartSignedPackageRequest.
+  if (listener) {
+    mRequesters.AppendObject(listener);
+  }
+
   return NS_OK;
 }
 
 nsresult
 PackagedAppService::PackagedAppDownloader::CallCallbacks(nsIURI *aURI,
                                                          nsICacheEntry *aEntry,
                                                          nsresult aResult)
 {
@@ -854,19 +866,29 @@ PackagedAppService::PackagedAppDownloade
   }
 
   return NS_OK;
 }
 
 void
 PackagedAppService::PackagedAppDownloader::NotifyOnStartSignedPackageRequest(const nsACString& aPackageOrigin)
 {
-  // TODO: Bug 1186290 to notify whoever wants to know when the signed package is
-  //       about to load.
-  LOG(("Notifying the signed package is ready to load."));
+  MOZ_RELEASE_ASSERT(NS_IsMainThread(), "mRequesters is not thread safe");
+
+  LOG(("Ready to notify OnStartSignedPackageRequest to all requesters."));
+  // Notify all requesters that a signed package is about to download and let
+  // TabParent to decide if the request needs to be re-made in a new process.
+  for (uint32_t i = 0; i < mRequesters.Length(); i++) {
+    nsCOMPtr<nsIPackagedAppChannelListener> requester = mRequesters.ObjectAt(i);
+    LOG(("Notifying %p OnStartSignedPackageRequest. New origin: %s", requester.get(),
+          nsCString(aPackageOrigin).get()));
+    requester->OnStartSignedPackageRequest(aPackageOrigin);
+  }
+
+  mRequesters.Clear();
 }
 
 void PackagedAppService::PackagedAppDownloader::InstallSignedPackagedApp(const ResourceCacheInfo* aInfo)
 {
   // TODO: Bug 1178533 to register permissions, system messages etc on navigation to
   //       signed packages.
   LOG(("Install this packaged app."));
   bool isSuccess = false;
@@ -960,16 +982,25 @@ PackagedAppService::PackagedAppDownloade
 void
 PackagedAppService::PackagedAppDownloader::OnResourceVerified(const ResourceCacheInfo* aInfo,
                                                               bool aSuccess)
 {
   if (!aSuccess) {
     return OnError(ERROR_RESOURCE_VERIFIED_FAILED);
   }
 
+  // If this package is signed and there is any pending requests, we just notify
+  // right now no matter if this is the requested resource. Doing this can
+  // have the potential process switch be done as early as possible.
+  if (mVerifier->GetIsPackageSigned()) {
+    // TODO: Bug 1178526 will deal with the package identifier things.
+    //       For now we just use the origin as the identifier.
+    NotifyOnStartSignedPackageRequest(mVerifier->GetPackageOrigin());
+  }
+
   // Serve this resource to all listeners.
   CallCallbacks(aInfo->mURI, aInfo->mCacheEntry, aInfo->mStatusCode);
 
   if (aInfo->mIsLastPart) {
     LOG(("This is the last part. FinalizeDownload (%d)", aInfo->mStatusCode));
     FinalizeDownload(aInfo->mStatusCode);
   }
 }
@@ -1091,18 +1122,17 @@ PackagedAppService::GetResource(nsIChann
   }
 
   nsRefPtr<PackagedAppDownloader> downloader;
   if (mDownloadingPackages.Get(key, getter_AddRefs(downloader))) {
     // We have determined that the file is not in the cache.
     // If we find that the package that the file belongs to is currently being
     // downloaded, we will add the callback to the package's queue, and it will
     // be called once the file is processed and saved in the cache.
-
-    downloader->AddCallback(uri, aCallback);
+    downloader->AddCallback(uri, aCallback, aChannel);
     return NS_OK;
   }
 
   nsCOMPtr<nsIChannel> channel;
   rv = NS_NewChannelInternal(
     getter_AddRefs(channel), packageURI,
     loadInfo,
     nullptr, nullptr, loadFlags);
@@ -1122,17 +1152,17 @@ PackagedAppService::GetResource(nsIChann
   downloader = new PackagedAppDownloader();
   nsCString packageOrigin;
   principal->GetOrigin(packageOrigin);
   rv = downloader->Init(loadContextInfo, key, packageOrigin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  downloader->AddCallback(uri, aCallback);
+  downloader->AddCallback(uri, aCallback, aChannel);
 
   nsCOMPtr<nsIStreamConverterService> streamconv =
     do_GetService("@mozilla.org/streamConverters;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<nsIStreamListener> mimeConverter;
--- a/netwerk/protocol/http/PackagedAppService.h
+++ b/netwerk/protocol/http/PackagedAppService.h
@@ -8,16 +8,17 @@
 #define mozilla_net_PackagedAppService_h
 
 #include "nsIPackagedAppService.h"
 #include "nsILoadContextInfo.h"
 #include "nsICacheStorage.h"
 #include "PackagedAppVerifier.h"
 #include "nsIMultiPartChannel.h"
 #include "PackagedAppVerifier.h"
+#include "nsIPackagedAppChannelListener.h"
 
 namespace mozilla {
 namespace net {
 
 class nsHttpResponseHead;
 
 // This service is used to download packages from the web.
 // Individual resources in the package are saved in the browser cache. It also
@@ -119,17 +120,20 @@ private:
     // Initializes mCacheStorage and saves aKey as mPackageKey which is later
     // used to remove this object from PackagedAppService::mDownloadingPackages
     // - aKey is a string which uniquely identifies this package within the
     //   packagedAppService
     nsresult Init(nsILoadContextInfo* aInfo, const nsCString &aKey,
                                              const nsACString& aPackageOrigin);
     // Registers a callback which gets called when the given nsIURI is downloaded
     // aURI is the full URI of a subresource, composed of packageURI + !// + subresourcePath
-    nsresult AddCallback(nsIURI *aURI, nsICacheEntryOpenCallback *aCallback);
+    // aRequester is the outer channel who makes the request for aURI.
+    nsresult AddCallback(nsIURI *aURI,
+                         nsICacheEntryOpenCallback *aCallback,
+                         nsIChannel* aRequester);
 
     // Remove the callback from the resource callback list.
     nsresult RemoveCallbacks(nsICacheEntryOpenCallback* aCallback);
 
     // Called by PackagedAppChannelListener to note the fact that the package
     // is coming from the cache, and no subresources are to be expected as only
     // package metadata is saved in the cache.
     void SetIsFromCache(bool aFromCache) { mIsFromCache = aFromCache; }
@@ -204,16 +208,19 @@ private:
     nsCString mPackageKey;
 
     // Whether the package is from the cache
     bool mIsFromCache;
 
     // Deal with verification and delegate callbacks to the downloader.
     nsRefPtr<PackagedAppVerifier> mVerifier;
 
+    // The outer channels which have issued the request to the downloader.
+    nsCOMArray<nsIPackagedAppChannelListener> mRequesters;
+
     // The package origin without signed package origin identifier.
     // If you need the origin with the signity taken into account, use
     // PackagedAppVerifier::GetPackageOrigin().
     nsCString mPackageOrigin;
 
     //The app id of the package loaded from the LoadContextInfo
     uint32_t mAppId;
 
--- a/netwerk/protocol/http/PackagedAppVerifier.h
+++ b/netwerk/protocol/http/PackagedAppVerifier.h
@@ -99,16 +99,26 @@ public:
 
   // A internal used function to let the verifier know there's a broken
   // last part.
   void SetHasBrokenLastPart(nsresult aStatusCode);
 
   // Used to explicitly clear the listener to avoid circula reference.
   void ClearListener() { mListener = nullptr; }
 
+  bool GetIsPackageSigned() const
+  {
+    return mIsPackageSigned;
+  }
+
+  const nsACString& GetPackageOrigin() const
+  {
+    return mPackageOrigin;
+  }
+
   static const char* kSignedPakOriginMetadataKey;
 
 private:
   virtual ~PackagedAppVerifier();
 
   // Called when a resource is already fully written in the cache. This resource
   // will be processed and is guaranteed to be called back in either:
   //
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -4848,16 +4848,17 @@ NS_IMETHODIMP nsHttpChannel::OnAuthCance
 NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel)
 NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel)
 
 NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
     NS_INTERFACE_MAP_ENTRY(nsIRequest)
     NS_INTERFACE_MAP_ENTRY(nsIChannel)
     NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
     NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+    NS_INTERFACE_MAP_ENTRY(nsIPackagedAppChannelListener)
     NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
     NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
     NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
     NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
     NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
     NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
     NS_INTERFACE_MAP_ENTRY(nsICacheEntryOpenCallback)
     NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
@@ -5658,16 +5659,35 @@ nsHttpChannel::GetLoadGroup(nsILoadGroup
 
 NS_IMETHODIMP
 nsHttpChannel::GetRequestMethod(nsACString& aMethod)
 {
     return HttpBaseChannel::GetRequestMethod(aMethod);
 }
 
 //-----------------------------------------------------------------------------
+// nsHttpChannel::nsIPackagedAppChannelListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::OnStartSignedPackageRequest(const nsACString& aPackageId)
+{
+    nsCOMPtr<nsIPackagedAppChannelListener> listener;
+    NS_QueryNotificationCallbacks(this, listener);
+
+    if (listener) {
+        listener->OnStartSignedPackageRequest(aPackageId);
+    } else {
+        LOG(("nsHttpChannel::OnStartSignedPackageRequest [this=%p], no listener on %p", this, mListener.get()));
+    }
+
+    return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
 // nsHttpChannel::nsIRequestObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
 {
     PROFILER_LABEL("nsHttpChannel", "OnStartRequest",
         js::ProfileEntry::Category::NETWORK);
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -4,16 +4,17 @@
  * 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 nsHttpChannel_h__
 #define nsHttpChannel_h__
 
 #include "HttpBaseChannel.h"
 #include "nsTArray.h"
+#include "nsIPackagedAppChannelListener.h"
 #include "nsICachingChannel.h"
 #include "nsICacheEntry.h"
 #include "nsICacheEntryOpenCallback.h"
 #include "nsIDNSListener.h"
 #include "nsIApplicationCacheChannel.h"
 #include "nsIProtocolProxyCallback.h"
 #include "nsIHttpAuthenticableChannel.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
@@ -54,16 +55,17 @@ public:
   0x7bb3,                                          \
   0x4ae1,                                          \
   {0xa9, 0x71, 0x40, 0xbc, 0xfa, 0x81, 0xde, 0x12} \
 }
 
 class nsHttpChannel final : public HttpBaseChannel
                           , public HttpAsyncAborter<nsHttpChannel>
                           , public nsIStreamListener
+                          , public nsIPackagedAppChannelListener
                           , public nsICachingChannel
                           , public nsICacheEntryOpenCallback
                           , public nsITransportEventSink
                           , public nsIProtocolProxyCallback
                           , public nsIHttpAuthenticableChannel
                           , public nsIApplicationCacheChannel
                           , public nsIAsyncVerifyRedirectCallback
                           , public nsIThreadRetargetableRequest
@@ -71,16 +73,17 @@ class nsHttpChannel final : public HttpB
                           , public nsIDNSListener
                           , public nsSupportsWeakReference
                           , public nsICorsPreflightCallback
 {
 public:
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIREQUESTOBSERVER
     NS_DECL_NSISTREAMLISTENER
+    NS_DECL_NSIPACKAGEDAPPCHANNELLISTENER
     NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
     NS_DECL_NSICACHEINFOCHANNEL
     NS_DECL_NSICACHINGCHANNEL
     NS_DECL_NSICACHEENTRYOPENCALLBACK
     NS_DECL_NSITRANSPORTEVENTSINK
     NS_DECL_NSIPROTOCOLPROXYCALLBACK
     NS_DECL_NSIPROXIEDCHANNEL
     NS_DECL_NSIAPPLICATIONCACHECONTAINER
--- a/netwerk/test/mochitests/mochitest.ini
+++ b/netwerk/test/mochitests/mochitest.ini
@@ -6,23 +6,26 @@ support-files =
   partial_content.sjs
   rel_preconnect.sjs
   user_agent.sjs
   user_agent_update.sjs
   redirect_idn.html^headers^
   redirect_idn.html
   empty.html
   web_packaged_app.sjs
+  signed_web_packaged_app.sjs
 
 [test_arraybufferinputstream.html]
 [test_partially_cached_content.html]
 [test_rel_preconnect.html]
 skip-if = e10s
 [test_uri_scheme.html]
 [test_user_agent_overrides.html]
 skip-if = e10s
 [test_user_agent_updates.html]
 skip-if = e10s
 [test_user_agent_updates_reset.html]
 [test_xhr_method_case.html]
 skip-if = e10s
 [test_idn_redirect.html]
+[test_signed_web_packaged_app.html]
+skip-if = e10s || buildapp != 'browser'
 [test_web_packaged_app.html]
new file mode 100644
--- /dev/null
+++ b/netwerk/test/mochitests/signed_web_packaged_app.sjs
@@ -0,0 +1,37 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+function handleRequest(request, response)
+{
+  response.setHeader("Content-Type", "application/package", false);
+  response.write(octetStreamData.packageHeader + octetStreamData.getData());
+  return;
+}
+
+// The package content
+// getData formats it as described at http://www.w3.org/TR/web-packaging/#streamable-package-format
+var octetStreamData = {
+  packageHeader: 'manifest-signature: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\r\n',
+
+  content: [
+   { headers: ["Content-Location: /index.html", "Content-Type: text/html"], data: "<html>\r\n  <head>\r\n    <script> alert('OK: hello'); alert('DONE'); </script>\r\n</head>\r\n  Web Packaged App Index\r\n</html>\r\n", type: "text/html" },
+   { headers: ["Content-Location: /scripts/app.js", "Content-Type: text/javascript"], data: "module Math from '/scripts/helpers/math.js';\r\n...\r\n", type: "text/javascript" },
+   { headers: ["Content-Location: /scripts/helpers/math.js", "Content-Type: text/javascript"], data: "export function sum(nums) { ... }\r\n...\r\n", type: "text/javascript" }
+  ],
+  token : "gc0pJq0M:08jU534c0p",
+  getData: function() {
+    var str = "";
+    for (var i in this.content) {
+      str += "--" + this.token + "\r\n";
+      for (var j in this.content[i].headers) {
+        str += this.content[i].headers[j] + "\r\n";
+      }
+      str += "\r\n";
+      str += this.content[i].data + "\r\n";
+    }
+
+    str += "--" + this.token + "--";
+    return str;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/test/mochitests/test_signed_web_packaged_app.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title> Web packaged app </title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.7">
+
+var Cc = SpecialPowers.Cc;
+var Ci = SpecialPowers.Ci;
+var Cu = SpecialPowers.Cu;
+var Cr = SpecialPowers.Cr;
+
+SpecialPowers.pushPrefEnv(
+  { "set": [["network.http.enable-packaged-apps", true],
+            ["network.http.packaged-apps-developer-mode", true],
+            ["dom.ipc.processPriorityManager.testMode", true],
+            ["dom.ipc.processPriorityManager.enabled", true],
+            ["dom.ipc.tabs.disabled", false],
+            ["dom.ipc.processCount", 3],
+            ["dom.mozBrowserFramesEnabled", true]] },
+  () => SpecialPowers.pushPermissions([
+    { "type": "browser", "allow": 1, "context": document }
+  ], function() {
+    runTest();
+  }));
+
+SimpleTest.waitForExplicitFinish();
+
+// Listen for and count process-created event. Since we are loading a
+// signed content from a remote tab, there shouls be two processes created.
+// One is for remote tab and one for the signed package.
+var kProcessCreatedTopic = "process-priority-manager:TEST-ONLY:process-created";
+var processCreatedCnt = 0;
+SpecialPowers.addObserver(() => {
+  processCreatedCnt++;
+  if (processCreatedCnt == 1) {
+    ok(true, "We have one more process to create.");
+  } else if (processCreatedCnt == 2) {
+    SimpleTest.finish();
+  }
+}, kProcessCreatedTopic, /* weak = */ false);
+
+function runTest() {
+  var iframe = document.createElement("iframe");
+  iframe.setAttribute('mozbrowser', 'true');
+  iframe.setAttribute('remote', 'true');
+  iframe.setAttribute("src", "http://example.org:80");
+
+  iframe.addEventListener("mozbrowserloadend", function loadend(e) {
+    iframe.removeEventListener("mozbrowserloadend", loadend);
+    ok(true, "Got mozbrowserloadend");
+    iframe.setAttribute("src", "http://mochi.test:8888/tests/netwerk/test/mochitests/signed_web_packaged_app.sjs!//scripts/app.js");
+  });
+
+  document.body.appendChild(iframe);
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/netwerk/test/unit/test_packaged_app_service.js
+++ b/netwerk/test/unit/test_packaged_app_service.js
@@ -36,16 +36,19 @@
 //      break the service and the async verifier.
 //
 
 Cu.import('resource://gre/modules/LoadContextInfo.jsm');
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 
+let gPrefs = Cc["@mozilla.org/preferences-service;1"]
+               .getService(Components.interfaces.nsIPrefBranch);
+
 // The number of times this package has been requested
 // This number might be reset by tests that use it
 var packagedAppRequestsMade = 0;
 // The default content handler. It just responds by sending the package data
 // with an application/package content type
 function packagedAppContentHandler(metadata, response)
 {
   packagedAppRequestsMade++;
@@ -60,39 +63,46 @@ function packagedAppContentHandler(metad
 
   if (packagedAppRequestsMade == 3) {
     // The third request returns a 200 OK response with a slightly different content
     body = body.replace(/\.\.\./g, 'xxx');
   }
   response.bodyOutputStream.write(body, body.length);
 }
 
-function getChannelForURL(url) {
+function getChannelForURL(url, notificationCallbacks) {
   let uri = createURI(url);
   let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
               .getService(Ci.nsIScriptSecurityManager);
   let principal = ssm.createCodebasePrincipal(uri, {});
   let tmpChannel =
     NetUtil.newChannel({
       uri: url,
       loadingPrincipal: principal,
       contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
     });
 
-  tmpChannel.notificationCallbacks =
-    new LoadContextCallback(principal.appId,
-                            principal.isInBrowserElement,
-                            false,
-                            false);
+  if (notificationCallbacks) {
+    // Use custom notificationCallbacks if any.
+    tmpChannel.notificationCallbacks = notificationCallbacks;
+  } else {
+    tmpChannel.notificationCallbacks =
+      new LoadContextCallback(principal.appId,
+                              principal.isInBrowserElement,
+                              false,
+                              false);
+
+  }
   return tmpChannel;
 }
 
 // The package content
 // getData formats it as described at http://www.w3.org/TR/web-packaging/#streamable-package-format
 var testData = {
+  packageHeader: 'manifest-signature: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\r\n',
   content: [
    { headers: ["Content-Location: /index.html", "Content-Type: text/html"], data: "<html>\r\n  <head>\r\n    <script src=\"/scripts/app.js\"></script>\r\n    ...\r\n  </head>\r\n  ...\r\n</html>\r\n", type: "text/html" },
    { headers: ["Content-Location: /scripts/app.js", "Content-Type: text/javascript"], data: "module Math from '/scripts/helpers/math.js';\r\n...\r\n", type: "text/javascript" },
    { headers: ["Content-Location: /scripts/helpers/math.js", "Content-Type: text/javascript"], data: "export function sum(nums) { ... }\r\n...\r\n", type: "text/javascript" }
   ],
   token : "gc0pJq0M:08jU534c0p",
   getData: function() {
     var str = "";
@@ -131,18 +141,27 @@ function run_test()
   httpserver.registerPathHandler("/badPackage", packagedAppBadContentHandler);
 
   let worsePackageNum = 6;
   for (let i = 0; i < worsePackageNum; i++) {
     httpserver.registerPathHandler("/worsePackage_" + i,
                                    packagedAppWorseContentHandler.bind(null, i));
   }
 
+  httpserver.registerPathHandler("/signedPackage", signedPackagedAppContentHandler);
   httpserver.start(-1);
 
+  // We will enable the developer mode in 'test_signed_package_callback'.
+  // So restore it after testing.
+  //
+  // TODO: To be removed in Bug 1178518.
+  do_register_cleanup(function() {
+    gPrefs.clearUserPref("network.http.packaged-apps-developer-mode");
+  });
+
   paservice = Cc["@mozilla.org/network/packaged-app-service;1"]
                      .getService(Ci.nsIPackagedAppService);
   ok(!!paservice, "test service exists");
 
   add_test(test_bad_args);
 
   add_test(test_callback_gets_called);
   add_test(test_same_content);
@@ -150,16 +169,19 @@ function run_test()
   add_test(test_updated_package);
 
   add_test(test_package_does_not_exist);
   add_test(test_file_does_not_exist);
 
   add_test(test_bad_package);
   add_test(test_bad_package_404);
 
+  add_test(test_signed_package_callback);
+  add_test(test_unsigned_package_callback);
+
   // Channels created by addons could have no load info.
   // In debug mode this triggers an assertion, but we still want to test that
   // it works in optimized mode. See bug 1196021 comment 17
   if (Components.classes["@mozilla.org/xpcom/debug;1"]
                 .getService(Components.interfaces.nsIDebug2)
                 .isDebugBuild == false) {
     add_test(test_channel_no_loadinfo);
   }
@@ -469,8 +491,86 @@ function test_worse_package_3() {
 
 function test_worse_package_4() {
   test_worse_package(4, true);
 }
 
 function test_worse_package_5() {
   test_worse_package(5, true);
 }
+
+//-----------------------------------------------------------------------------
+
+function signedPackagedAppContentHandler(metadata, response)
+{
+  response.setHeader("Content-Type", 'application/package');
+  var body = testData.packageHeader + testData.getData();
+  response.bodyOutputStream.write(body, body.length);
+}
+
+// Used as a stub when the cache listener is not important.
+let dummyCacheListener = {
+  QueryInterface: function (iid) {
+    if (iid.equals(Ci.nsICacheEntryOpenCallback) ||
+        iid.equals(Ci.nsISupports))
+      return this;
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+  onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
+  onCacheEntryAvailable: function () {}
+};
+
+function test_signed_package_callback()
+{
+  // TODO: To be removed in Bug 1178518.
+  gPrefs.setBoolPref("network.http.packaged-apps-developer-mode", true);
+
+  packagePath = "/signedPackage";
+  let url = uri + packagePath + "!//index.html";
+  let channel = getChannelForURL(url, {
+    onStartSignedPackageRequest: function(aPackageId) {
+      ok(true, "onStartSignedPackageRequest is notifited as expected");
+      run_next_test();
+    },
+
+    getInterface: function (iid) {
+      return this.QueryInterface(iid);
+    },
+
+    QueryInterface: function (iid) {
+      if (iid.equals(Ci.nsISupports) ||
+          iid.equals(Ci.nsIInterfaceRequestor) ||
+          iid.equals(Ci.nsIPackagedAppChannelListener)) {
+        return this;
+      }
+      throw Cr.NS_ERROR_NO_INTERFACE;
+    },
+  });
+
+  paservice.getResource(channel, dummyCacheListener);
+}
+
+function test_unsigned_package_callback()
+{
+  packagePath = "/package";
+  let url = uri + packagePath + "!//index.html";
+  let channel = getChannelForURL(url, {
+    onStartSignedPackageRequest: function(aPackageId) {
+      ok(false, "Unsigned package shouldn't be called.");
+    },
+
+    getInterface: function (iid) {
+      return this.QueryInterface(iid);
+    },
+
+    QueryInterface: function (iid) {
+      if (iid.equals(Ci.nsISupports) ||
+          iid.equals(Ci.nsIInterfaceRequestor) ||
+          iid.equals(Ci.nsIPackagedAppChannelListener)) {
+        return this;
+      }
+      throw Cr.NS_ERROR_NO_INTERFACE;
+    },
+  });
+
+  // Pass cacheListener since we rely on 'run_next_test' in it.
+  paservice.getResource(channel, cacheListener);
+}
\ No newline at end of file