Bug 1476996 - Implement cross process redirection in Http on the parent process r=bagder,nika
☠☠ backed out by eb6516c0e7ef ☠ ☠
authorValentin Gosu <valentin.gosu@gmail.com>
Tue, 04 Sep 2018 16:40:57 +0000
changeset 483013 45605798ecfe97742b9571c04cc151afe5426c19
parent 483012 e2206a13d6d36d17e38b2ebb78d15164884cf5a6
child 483014 7ad7b862c561de147fbde605f4418b88d295470c
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersbagder, nika
bugs1476996
milestone63.0a1
Bug 1476996 - Implement cross process redirection in Http on the parent process r=bagder,nika This patch builds the foundation for the ability to relocate HTTP channels from one content process to another in order to ensure that origins are properly isolated. This relocation would normally occur when the response to an HTTP request is a redirect to a different origin. The patch merely adds the mechanism for relocating the channel, rather than the logic of doing so. This will be provided in a follow-up patch by a specialized service. Right now that functionality is mocked in the test. How this works: In nsHttpChannel::OnStartRequest we will query the service that decides whether we need to direct the response to another process. If so, it will return a promise that resolves to a TabParent. When the promise resolves, in HttpChannelParentListener::TriggerCrossProcessRedirect we call NeckoParent::SendCrossProcessRedirect passing along the required information to recreate the channel in the new process. The NeckoChild in the new process will then instantiate a new channel, call ConnectParent() which creates the associated parent channel, and connects it with the existing nsHttpChannel. A listener in the new process is then notified of the existence of the new channel. It is required to call completeRedirectSetup on the channel, passing an nsIStreamListener to the call. We then finish the entire operation with a call to HttpChannelChild::SendCrossProcessRedirectDone which causes us to close the old HttpChannelChild in the previous process and to resume the nsHttpChannel in the main process. Differential Revision: https://phabricator.services.mozilla.com/D2958
ipc/glue/URIUtils.h
netwerk/ipc/NeckoChild.cpp
netwerk/ipc/NeckoChild.h
netwerk/ipc/PNecko.ipdl
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/HttpChannelParentListener.cpp
netwerk/protocol/http/HttpChannelParentListener.h
netwerk/protocol/http/PHttpChannel.ipdl
netwerk/protocol/http/moz.build
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsIRedirectProcessChooser.idl
netwerk/test/browser/browser.ini
netwerk/test/browser/browser_cross_process_redirect.js
netwerk/test/browser/redirect.sjs
--- a/ipc/glue/URIUtils.h
+++ b/ipc/glue/URIUtils.h
@@ -34,17 +34,17 @@ struct IPDLParamTraits<nsIURI>
 {
   static void Write(IPC::Message* aMsg, IProtocol* aActor, nsIURI* aParam)
   {
     OptionalURIParams params;
     SerializeURI(aParam, params);
     WriteIPDLParam(aMsg, aActor, params);
   }
 
-  static bool Read(IPC::Message* aMsg, PickleIterator* aIter,
+  static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
                    IProtocol* aActor, RefPtr<nsIURI>* aResult)
   {
     OptionalURIParams params;
     if (!ReadIPDLParam(aMsg, aIter, aActor, &params)) {
       return false;
     }
     *aResult = DeserializeURI(params);
     return true;
--- a/netwerk/ipc/NeckoChild.cpp
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -28,16 +28,18 @@
 #endif
 
 #include "SerializedLoadContext.h"
 #include "nsGlobalWindow.h"
 #include "nsIOService.h"
 #include "nsINetworkPredictor.h"
 #include "nsINetworkPredictorVerifier.h"
 #include "nsINetworkLinkService.h"
+#include "nsIRedirectProcessChooser.h"
+#include "nsQueryObject.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "nsNetUtil.h"
 
 using mozilla::dom::TCPSocketChild;
 using mozilla::dom::TCPServerSocketChild;
 using mozilla::dom::UDPSocketChild;
 
 namespace mozilla {
@@ -367,16 +369,84 @@ NeckoChild::AllocPTransportProviderChild
 
 bool
 NeckoChild::DeallocPTransportProviderChild(PTransportProviderChild* aActor)
 {
   return true;
 }
 
 mozilla::ipc::IPCResult
+NeckoChild::RecvCrossProcessRedirect(
+            const uint32_t& aRegistrarId,
+            nsIURI* aURI,
+            const uint32_t& aNewLoadFlags,
+            const OptionalLoadInfoArgs& aLoadInfo,
+            const uint64_t& aChannelId,
+            nsIURI* aOriginalURI,
+            const uint64_t& aIdentifier)
+{
+  nsCOMPtr<nsILoadInfo> loadInfo;
+  nsresult rv = ipc::LoadInfoArgsToLoadInfo(aLoadInfo, getter_AddRefs(loadInfo));
+  if (NS_FAILED(rv)) {
+    MOZ_DIAGNOSTIC_ASSERT(false, "LoadInfoArgsToLoadInfo failed");
+    return IPC_OK();
+  }
+
+  nsCOMPtr<nsIChannel> newChannel;
+  rv = NS_NewChannelInternal(getter_AddRefs(newChannel),
+                             aURI,
+                             loadInfo,
+                             nullptr, // PerformanceStorage
+                             nullptr, // aLoadGroup
+                             nullptr, // aCallbacks
+                             aNewLoadFlags);
+
+  // We are sure this is a HttpChannelChild because the parent
+  // is always a HTTP channel.
+  RefPtr<HttpChannelChild> httpChild = do_QueryObject(newChannel);
+  if (NS_FAILED(rv) || !httpChild) {
+    MOZ_DIAGNOSTIC_ASSERT(false, "NS_NewChannelInternal failed");
+    return IPC_OK();
+  }
+
+  // This is used to report any errors back to the parent by calling
+  // CrossProcessRedirectFinished.
+  auto scopeExit = MakeScopeExit([&]() {
+    httpChild->CrossProcessRedirectFinished(rv);
+  });
+
+  rv = httpChild->SetChannelId(aChannelId);
+  if (NS_FAILED(rv)) {
+    return IPC_OK();
+  }
+
+  rv = httpChild->SetOriginalURI(aOriginalURI);
+  if (NS_FAILED(rv)) {
+    return IPC_OK();
+  }
+
+  // connect parent.
+  rv = httpChild->ConnectParent(aRegistrarId); // creates parent channel
+  if (NS_FAILED(rv)) {
+    return IPC_OK();
+  }
+
+  nsCOMPtr<nsIChildProcessChannelListener> processListener =
+      do_GetClassObject("@mozilla.org/network/childProcessChannelListener");
+  // The listener will call completeRedirectSetup on the channel.
+  rv = processListener->OnChannelReady(httpChild, aIdentifier);
+  if (NS_FAILED(rv)) {
+    return IPC_OK();
+  }
+
+  // scopeExit will call CrossProcessRedirectFinished(rv) here
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 NeckoChild::RecvAsyncAuthPromptForNestedFrame(const TabId& aNestedFrameId,
                                               const nsCString& aUri,
                                               const nsString& aRealm,
                                               const uint64_t& aCallbackId)
 {
   RefPtr<dom::TabChild> tabChild = dom::TabChild::FindTabChild(aNestedFrameId);
   if (!tabChild) {
     MOZ_CRASH();
--- a/netwerk/ipc/NeckoChild.h
+++ b/netwerk/ipc/NeckoChild.h
@@ -91,16 +91,25 @@ protected:
   /* Predictor Messsages */
   virtual mozilla::ipc::IPCResult RecvPredOnPredictPrefetch(const URIParams& aURI,
                                                             const uint32_t& aHttpStatus) override;
   virtual mozilla::ipc::IPCResult RecvPredOnPredictPreconnect(const URIParams& aURI) override;
   virtual mozilla::ipc::IPCResult RecvPredOnPredictDNS(const URIParams& aURI) override;
 
   virtual mozilla::ipc::IPCResult RecvSpeculativeConnectRequest() override;
   virtual mozilla::ipc::IPCResult RecvNetworkChangeNotification(nsCString const& type) override;
+
+  virtual mozilla::ipc::IPCResult RecvCrossProcessRedirect(
+                                    const uint32_t& aRegistrarId,
+                                    nsIURI* aURI,
+                                    const uint32_t& aNewLoadFlags,
+                                    const OptionalLoadInfoArgs& aLoadInfoForwarder,
+                                    const uint64_t& aChannelId,
+                                    nsIURI* aOriginalURI,
+                                    const uint64_t& aIdentifier) override;
 };
 
 /**
  * Reference to the PNecko Child protocol.
  * Null if this is not a content process.
  */
 extern PNeckoChild *gNeckoChild;
 
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -32,16 +32,17 @@ include URIParams;
 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";
 using refcounted class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h";
+using refcounted class nsIURI from "mozilla/ipc/URIUtils.h";
 
 namespace mozilla {
 namespace net {
 
 //-------------------------------------------------------------------
 nested(upto inside_cpow) sync protocol PNecko
 {
   manager PContent;
@@ -148,16 +149,27 @@ child:
   async SpeculativeConnectRequest();
 
   // Using high priority to deliver this notification possibly sooner than we
   // enter poll() on the child process with infinite timeout.
   prio(high) async NetworkChangeNotification(nsCString type);
 
   async PTransportProvider();
 
+  // This message is sent to PNecko and triggers the creation of a new
+  // HttpChannelChild that will be connected to the parent channel represented
+  // by registrarId.
+  async CrossProcessRedirect(uint32_t aRegistrarId,
+                             nsIURI aURI,
+                             uint32_t aNewLoadFlags,
+                             OptionalLoadInfoArgs aLoadInfo,
+                             uint64_t aChannelId,
+                             nsIURI aOriginalURI,
+                             uint64_t aIdentifier);
+
 both:
   // Actually we need PTCPSocket() for parent. But ipdl disallows us having different
   // signatures on parent and child. So when constructing the parent side object, we just
   // leave host/port unused.
   async PTCPSocket(nsString host, uint16_t port);
 };
 
 
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -292,16 +292,17 @@ NS_INTERFACE_MAP_BEGIN(HttpChannelChild)
   NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
   NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
   NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
   NS_INTERFACE_MAP_ENTRY(nsIChildChannel)
   NS_INTERFACE_MAP_ENTRY(nsIHttpChannelChild)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAssociatedContentSecurity, GetAssociatedContentSecurity())
   NS_INTERFACE_MAP_ENTRY(nsIDivertableChannel)
   NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+  NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpChannelChild)
 NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
 
 //-----------------------------------------------------------------------------
 // HttpChannelChild::PHttpChannelChild
 //-----------------------------------------------------------------------------
 
 void
 HttpChannelChild::AddIPDLReference()
@@ -2154,16 +2155,23 @@ HttpChannelChild::Redirect3Complete(Over
       httpChannelChild->mOverrideRunnable = nullptr;
       httpChannelChild->mInterceptingChannel = nullptr;
     }
     return true;
   }
   return false;
 }
 
+mozilla::ipc::IPCResult
+HttpChannelChild::RecvCancelRedirected()
+{
+  CleanupRedirectingChannel(NS_BINDING_REDIRECTED);
+  return IPC_OK();
+}
+
 void
 HttpChannelChild::CleanupRedirectingChannel(nsresult rv)
 {
   // Redirecting to new channel: shut this down and init new channel
   if (mLoadGroup)
     mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_ABORTED);
 
   if (NS_SUCCEEDED(rv)) {
@@ -4121,10 +4129,20 @@ HttpChannelChild::MaybeCallSynthesizedCa
   if (!mSynthesizedCallback) {
     return;
   }
 
   mSynthesizedCallback->BodyComplete(mStatus);
   mSynthesizedCallback = nullptr;
 }
 
+nsresult
+HttpChannelChild::CrossProcessRedirectFinished(nsresult aStatus)
+{
+  if (!mIPCOpen) {
+    return NS_BINDING_FAILED;
+  }
+  Unused << SendCrossProcessRedirectDone(aStatus);
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -36,16 +36,20 @@
 #include "mozilla/net/DNS.h"
 
 using mozilla::Telemetry::LABELS_HTTP_CHILD_OMT_STATS;
 
 class nsIEventTarget;
 class nsInputStreamPump;
 class nsIInterceptedBodyCallback;
 
+#define HTTP_CHANNEL_CHILD_IID  \
+{ 0x321bd99e, 0x2242, 0x4dc6, \
+  { 0xbb, 0xec, 0xd5, 0x06, 0x29, 0x7c, 0x39, 0x83 } }
+
 namespace mozilla {
 namespace net {
 
 class HttpBackgroundChannelChild;
 class InterceptedChannelContent;
 class InterceptStreamListener;
 class SyntheticDiversionListener;
 
@@ -71,16 +75,17 @@ public:
   NS_DECL_NSIAPPLICATIONCACHECONTAINER
   NS_DECL_NSIAPPLICATIONCACHECHANNEL
   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
   NS_DECL_NSIASSOCIATEDCONTENTSECURITY
   NS_DECL_NSICHILDCHANNEL
   NS_DECL_NSIHTTPCHANNELCHILD
   NS_DECL_NSIDIVERTABLECHANNEL
   NS_DECL_NSITHREADRETARGETABLEREQUEST
+  NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_CHANNEL_CHILD_IID)
 
   HttpChannelChild();
 
   // Methods HttpBaseChannel didn't implement for us or that we override.
   //
   // nsIRequest
   NS_IMETHOD Cancel(nsresult status) override;
   NS_IMETHOD Suspend() override;
@@ -122,16 +127,18 @@ public:
 
   void OnCopyComplete(nsresult aStatus) override;
 
   // Callback while background channel is ready.
   void OnBackgroundChildReady(HttpBackgroundChannelChild* aBgChild);
   // Callback while background channel is destroyed.
   void OnBackgroundChildDestroyed(HttpBackgroundChannelChild* aBgChild);
 
+  nsresult CrossProcessRedirectFinished(nsresult aStatus);
+
 protected:
   mozilla::ipc::IPCResult RecvOnStartRequest(const nsresult& channelStatus,
                                              const nsHttpResponseHead& responseHead,
                                              const bool& useResponseHead,
                                              const nsHttpHeaderArray& requestHeaders,
                                              const ParentLoadInfoForwarderArgs& loadInfoForwarder,
                                              const bool& isFromCache,
                                              const bool& cacheEntryAvailable,
@@ -171,16 +178,18 @@ protected:
                                                       const bool& asError) override;
 
   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;
+
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   MOZ_MUST_USE bool
   GetAssociatedContentSecurity(nsIAssociatedContentSecurity** res = nullptr);
   virtual void DoNotifyListenerCleanup() override;
 
   virtual void DoAsyncAbort(nsresult aStatus) override;
 
@@ -512,16 +521,19 @@ private:
   friend class HttpAsyncAborter<HttpChannelChild>;
   friend class InterceptStreamListener;
   friend class InterceptedChannelContent;
   friend class SyntheticDiversionListener;
   friend class HttpBackgroundChannelChild;
   friend class NeckoTargetChannelEvent<HttpChannelChild>;
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpChannelChild,
+                              HTTP_CHANNEL_CHILD_IID)
+
 // A stream listener interposed between the nsInputStreamPump used for intercepted channels
 // and this channel's original listener. This is only used to ensure the original listener
 // sees the channel as the request object, and to synthesize OnStatus and OnProgress notifications.
 class InterceptStreamListener : public nsIStreamListener
                               , public nsIProgressEventSink
 {
   RefPtr<HttpChannelChild> mOwner;
   nsCOMPtr<nsISupports> mContext;
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -78,16 +78,17 @@ HttpChannelParent::HttpChannelParent(con
   , mDivertingFromChild(false)
   , mDivertedOnStartRequest(false)
   , mSuspendedForDiversion(false)
   , mSuspendAfterSynthesizeResponse(false)
   , mWillSynthesizeResponse(false)
   , mCacheNeedFlowControlInitialized(false)
   , mNeedFlowControl(true)
   , mSuspendedForFlowControl(false)
+  , mDoingCrossProcessRedirect(false)
 {
   LOG(("Creating HttpChannelParent [this=%p]\n", this));
 
   // Ensure gHttpHandler is initialized: we need the atom table up and running.
   nsCOMPtr<nsIHttpProtocolHandler> dummyInitializer =
     do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http");
 
   MOZ_ASSERT(gHttpHandler);
@@ -308,17 +309,18 @@ NS_INTERFACE_MAP_END
 //-----------------------------------------------------------------------------
 // HttpChannelParent::nsIInterfaceRequestor
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelParent::GetInterface(const nsIID& aIID, void **result)
 {
   if (aIID.Equals(NS_GET_IID(nsIAuthPromptProvider)) ||
-      aIID.Equals(NS_GET_IID(nsISecureBrowserUI))) {
+      aIID.Equals(NS_GET_IID(nsISecureBrowserUI)) ||
+      aIID.Equals(NS_GET_IID(nsITabParent))) {
     if (mTabParent) {
       return mTabParent->QueryInterface(aIID, result);
     }
   }
 
   // Only support nsIAuthPromptProvider in Content process
   if (XRE_IsParentProcess() &&
       aIID.Equals(NS_GET_IID(nsIAuthPromptProvider))) {
@@ -1317,16 +1319,55 @@ HttpChannelParent::MaybeFlushPendingDive
   }
 
   if (mDivertListener) {
     DivertTo(mDivertListener);
   }
 
 }
 
+static void
+FinishCrossProcessRedirect(nsHttpChannel *channel, nsresult status)
+{
+  if (NS_SUCCEEDED(status)) {
+    nsCOMPtr<nsINetworkInterceptController> controller;
+    NS_QueryNotificationCallbacks(channel, controller);
+    RefPtr<HttpChannelParentListener> parentListener = do_QueryObject(controller);
+    MOZ_ASSERT(parentListener);
+
+    // This updates HttpChannelParentListener to point to this parent and at
+    // the same time cancels the old channel.
+    parentListener->OnRedirectResult(status == NS_OK);
+  }
+
+  channel->OnRedirectVerifyCallback(status);
+}
+
+mozilla::ipc::IPCResult
+HttpChannelParent::RecvCrossProcessRedirectDone(const nsresult& aResult)
+{
+  RefPtr<nsHttpChannel> chan = do_QueryObject(mChannel);
+  if (!mBgParent) {
+    RefPtr<GenericPromise> promise = WaitForBgParent();
+    RefPtr<HttpChannelParent> self = this;
+    promise->Then(GetMainThreadSerialEventTarget(), __func__,
+                  [self, chan, aResult]() {
+                    FinishCrossProcessRedirect(chan, aResult);
+                  },
+                  [self, chan](const nsresult& aRejectionRv) {
+                    MOZ_ASSERT(NS_FAILED(aRejectionRv), "This should be an error code");
+                    FinishCrossProcessRedirect(chan, aRejectionRv);
+                  });
+  } else {
+    FinishCrossProcessRedirect(chan, aResult);
+  }
+
+  return IPC_OK();
+}
+
 void
 HttpChannelParent::ResponseSynthesized()
 {
   // Suspend now even though the FinishSynthesizeResponse runnable has
   // not executed.  We want to suspend after we get far enough to trigger
   // the synthesis, but not actually allow the nsHttpChannel to trigger
   // any OnStartRequests().
   if (mSuspendAfterSynthesizeResponse) {
@@ -1998,16 +2039,25 @@ HttpChannelParent::StartRedirect(uint32_
 }
 
 NS_IMETHODIMP
 HttpChannelParent::CompleteRedirect(bool succeeded)
 {
   LOG(("HttpChannelParent::CompleteRedirect [this=%p succeeded=%d]\n",
        this, succeeded));
 
+  // The channel was redirected to another process, and the channel parent
+  // is about to be deleted. We send a CancelRedirected message in order to
+  // inform the listener that this child is going away.
+  if (mDoingCrossProcessRedirect && !mIPCClosed) {
+      MOZ_ASSERT(!mRedirectChannel);
+      Unused << SendCancelRedirected();
+      return NS_OK;
+  }
+
   // If this was an internal redirect for a service worker interception then
   // we will not have a redirecting channel here.  Hide this redirect from
   // the child.
   if (!mRedirectChannel) {
     return NS_OK;
   }
 
   if (succeeded && !mIPCClosed) {
@@ -2416,17 +2466,17 @@ HttpChannelParent::IssueWarning(uint32_t
 
 //-----------------------------------------------------------------------------
 // nsIAsyncVerifyRedirectReadyCallback
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelParent::ReadyToVerify(nsresult aResult)
 {
-  LOG(("HttpChannelParent::RecvRedirect2Verify [this=%p result=%" PRIx32 "]\n",
+  LOG(("HttpChannelParent::ReadyToVerify [this=%p result=%" PRIx32 "]\n",
        this, static_cast<uint32_t>(aResult)));
   MOZ_ASSERT(NS_IsMainThread());
 
   ContinueRedirect2Verify(aResult);
 
   return NS_OK;
 }
 
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -118,16 +118,18 @@ public:
 
   // Callback while background channel is ready.
   void OnBackgroundParentReady(HttpBackgroundChannelParent* aBgParent);
   // Callback while background channel is destroyed.
   void OnBackgroundParentDestroyed();
 
   base::ProcessId OtherPid() const override;
 
+  void SetCrossProcessRedirect() { mDoingCrossProcessRedirect = true; }
+
 protected:
   // used to connect redirected-to channel in parent with just created
   // ChildChannel.  Used during redirects.
   MOZ_MUST_USE bool ConnectChannel(const uint32_t& channelId,
                                    const bool& shouldIntercept);
 
   MOZ_MUST_USE bool
   DoAsyncOpen(const URIParams&           uri,
@@ -202,16 +204,17 @@ protected:
                                                    const int32_t& no) override;
   virtual mozilla::ipc::IPCResult RecvDocumentChannelCleanup(const bool& clearCacheEntry) override;
   virtual mozilla::ipc::IPCResult RecvMarkOfflineCacheEntryAsForeign() override;
   virtual mozilla::ipc::IPCResult RecvDivertOnDataAvailable(const nsCString& data,
                                          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 void ActorDestroy(ActorDestroyReason why) override;
 
   // Supporting function for ADivertableParentChannel.
   MOZ_MUST_USE nsresult ResumeForDiversion();
 
@@ -335,16 +338,17 @@ private:
   uint8_t mSuspendAfterSynthesizeResponse : 1;
   // Set if this channel will synthesize its response.
   uint8_t mWillSynthesizeResponse         : 1;
 
   // Set if we get the result of and cache |mNeedFlowControl|
   uint8_t mCacheNeedFlowControlInitialized : 1;
   uint8_t mNeedFlowControl : 1;
   uint8_t mSuspendedForFlowControl : 1;
+  uint8_t mDoingCrossProcessRedirect : 1;
 
   // Number of events to wait before actually invoking AsyncOpen on the main
   // channel. For each asynchronous step required before InvokeAsyncOpen, should
   // increase 1 to mAsyncOpenBarrier and invoke TryInvokeAsyncOpen after
   // finished. This attribute is main thread only.
   uint8_t mAsyncOpenBarrier = 0;
 };
 
--- a/netwerk/protocol/http/HttpChannelParentListener.cpp
+++ b/netwerk/protocol/http/HttpChannelParentListener.cpp
@@ -6,25 +6,28 @@
 
 // HttpLog.h should generally be included first
 #include "HttpLog.h"
 
 #include "HttpChannelParentListener.h"
 #include "mozilla/dom/ServiceWorkerInterceptController.h"
 #include "mozilla/dom/ServiceWorkerUtils.h"
 #include "mozilla/net/HttpChannelParent.h"
+#include "mozilla/net/RedirectChannelRegistrar.h"
 #include "mozilla/Unused.h"
 #include "nsIAuthPrompt.h"
 #include "nsIAuthPrompt2.h"
 #include "nsIHttpHeaderVisitor.h"
-#include "mozilla/net/RedirectChannelRegistrar.h"
+#include "nsIRedirectProcessChooser.h"
+#include "nsITabParent.h"
 #include "nsIPromptFactory.h"
 #include "nsIWindowWatcher.h"
 #include "nsQueryObject.h"
 
+
 using mozilla::Unused;
 using mozilla::dom::ServiceWorkerInterceptController;
 using mozilla::dom::ServiceWorkerParentInterceptEnabled;
 
 namespace mozilla {
 namespace net {
 
 HttpChannelParentListener::HttpChannelParentListener(HttpChannelParent* aInitialChannel)
@@ -151,51 +154,121 @@ HttpChannelParentListener::GetInterface(
   }
 
   return NS_NOINTERFACE;
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannelParentListener::nsIChannelEventSink
 //-----------------------------------------------------------------------------
+nsresult
+HttpChannelParentListener::TriggerCrossProcessRedirect(nsIChannel *aChannel,
+                                                       nsILoadInfo *aLoadInfo,
+                                                       uint64_t aIdentifier)
+{
+  RefPtr<HttpChannelParent> channelParent = do_QueryObject(mNextListener);
+  MOZ_ASSERT(channelParent);
+  channelParent->SetCrossProcessRedirect();
+
+  nsCOMPtr<nsIChannel> channel = aChannel;
+  RefPtr<nsHttpChannel> httpChannel = do_QueryObject(channel);
+  RefPtr<nsHttpChannel::TabPromise> p = httpChannel->TakeRedirectTabPromise();
+  nsCOMPtr<nsILoadInfo> loadInfo = aLoadInfo;
+
+  RefPtr<HttpChannelParentListener> self = this;
+  p->Then(GetMainThreadSerialEventTarget(), __func__,
+          [=](nsCOMPtr<nsITabParent> tp) {
+            nsresult rv;
+
+            // Register the new channel and obtain id for it
+            nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+              RedirectChannelRegistrar::GetOrCreate();
+            MOZ_ASSERT(registrar);
+            rv = registrar->RegisterChannel(channel, &self->mRedirectChannelId);
+            NS_ENSURE_SUCCESS(rv, rv);
+
+            LOG(("Registered %p channel under id=%d", channel.get(), self->mRedirectChannelId));
+
+            OptionalLoadInfoArgs loadInfoArgs;
+            MOZ_ALWAYS_SUCCEEDS(LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));
+
+            uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
+            MOZ_ALWAYS_SUCCEEDS(channel->GetLoadFlags(&newLoadFlags));
+
+            nsCOMPtr<nsIURI> uri;
+            channel->GetURI(getter_AddRefs(uri));
+
+            nsCOMPtr<nsIURI> originalURI;
+            channel->GetOriginalURI(getter_AddRefs(originalURI));
+
+            uint64_t channelId;
+            MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&channelId));
+
+            dom::TabParent* tabParent = dom::TabParent::GetFrom(tp);
+            ContentParent* cp = tabParent->Manager()->AsContentParent();
+            PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
+
+            RefPtr<HttpChannelParent> channelParent = do_QueryObject(self->mNextListener);
+            MOZ_ASSERT(channelParent);
+            channelParent->SetCrossProcessRedirect();
+
+            auto result = neckoParent->SendCrossProcessRedirect(self->mRedirectChannelId,
+                                                                uri,
+                                                                newLoadFlags,
+                                                                loadInfoArgs,
+                                                                channelId,
+                                                                originalURI,
+                                                                aIdentifier);
+
+            MOZ_ASSERT(result, "SendCrossProcessRedirect failed");
+
+            return NS_OK;
+          },
+          [httpChannel](nsresult aStatus) {
+            MOZ_ASSERT(NS_FAILED(aStatus), "Status should be error");
+            httpChannel->OnRedirectVerifyCallback(aStatus);
+          });
+
+  return NS_OK;
+}
 
 NS_IMETHODIMP
 HttpChannelParentListener::AsyncOnChannelRedirect(
-                                    nsIChannel *oldChannel,
-                                    nsIChannel *newChannel,
-                                    uint32_t redirectFlags,
-                                    nsIAsyncVerifyRedirectCallback* callback)
+                                    nsIChannel *aOldChannel,
+                                    nsIChannel *aNewChannel,
+                                    uint32_t aRedirectFlags,
+                                    nsIAsyncVerifyRedirectCallback* aCallback)
 {
   LOG(("HttpChannelParentListener::AsyncOnChannelRedirect [this=%p, old=%p, new=%p, flags=%u]",
-       this, oldChannel, newChannel, redirectFlags));
+       this, aOldChannel, aNewChannel, aRedirectFlags));
 
   nsresult rv;
 
   nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
       do_QueryInterface(mNextListener);
   if (!activeRedirectingChannel) {
     NS_ERROR("Channel got a redirect response, but doesn't implement "
              "nsIParentRedirectingChannel to handle it.");
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   // Register the new channel and obtain id for it
   nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
       RedirectChannelRegistrar::GetOrCreate();
   MOZ_ASSERT(registrar);
 
-  rv = registrar->RegisterChannel(newChannel, &mRedirectChannelId);
+  rv = registrar->RegisterChannel(aNewChannel, &mRedirectChannelId);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  LOG(("Registered %p channel under id=%d", newChannel, mRedirectChannelId));
+  LOG(("Registered %p channel under id=%d", aNewChannel, mRedirectChannelId));
 
   return activeRedirectingChannel->StartRedirect(mRedirectChannelId,
-                                                 newChannel,
-                                                 redirectFlags,
-                                                 callback);
+                                                 aNewChannel,
+                                                 aRedirectFlags,
+                                                 aCallback);
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannelParentListener::nsIRedirectResultListener
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelParentListener::OnRedirectResult(bool succeeded)
--- a/netwerk/protocol/http/HttpChannelParentListener.h
+++ b/netwerk/protocol/http/HttpChannelParentListener.h
@@ -49,16 +49,20 @@ public:
   // For channel diversion from child to parent.
   MOZ_MUST_USE nsresult DivertTo(nsIStreamListener *aListener);
   MOZ_MUST_USE nsresult SuspendForDiversion();
 
   void SetupInterception(const nsHttpResponseHead& aResponseHead);
   void SetupInterceptionAfterRedirect(bool aShouldIntercept);
   void ClearInterceptedChannel(nsIStreamListener* aListener);
 
+  nsresult TriggerCrossProcessRedirect(nsIChannel *oldChannel,
+                                       nsILoadInfo *aLoadInfo,
+                                       uint64_t aIdentifier);
+
 private:
   virtual ~HttpChannelParentListener() = default;
 
   // Private partner function to SuspendForDiversion.
   MOZ_MUST_USE nsresult ResumeForDiversion();
 
   // Can be the original HttpChannelParent that created this object (normal
   // case), a different {HTTP|FTP}ChannelParent that we've been redirected to,
--- a/netwerk/protocol/http/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -46,16 +46,20 @@ parent:
   async Redirect2Verify(nsresult result, RequestHeaderTuples changedHeaders,
                         ChildLoadInfoForwarderArgs loadInfoForwarder,
                         uint32_t loadFlags, uint32_t referrerPolicy,
                         OptionalURIParams referrerUri,
                         OptionalURIParams apiRedirectTo,
                         OptionalCorsPreflightArgs corsPreflightArgs,
                         bool chooseAppcache);
 
+  // Sent to the parent in order signal that the child side listeners have been
+  // set up and the parent side of the channel can be opened.
+  async CrossProcessRedirectDone(nsresult result);
+
   // For document loads we keep this protocol open after child's
   // OnStopRequest, and send this msg (instead of __delete__) to allow
   // partial cleanup on parent.
   async DocumentChannelCleanup(bool clearCacheEntry);
 
   // This might have to be sync. If this fails we must fail the document load
   // to avoid endless loop.
   //
@@ -141,16 +145,21 @@ child:
   // Associate the child with an application ids
   async AssociateApplicationCache(nsCString groupID,
                                   nsCString clientID);
 
   // Report a security message to the console associated with this
   // channel.
   async ReportSecurityMessage(nsString messageTag, nsString messageCategory);
 
+  // This message is sent to a child that has been redirected to another process.
+  // As a consequence, it should cleanup the channel listeners and remove the
+  // request from the loadGroup.
+  async CancelRedirected();
+
   // Tell child to delete channel (all IPDL deletes must be done from child to
   // avoid races: see bug 591708).
   async DeleteSelf();
 
   // Tell the child to issue a deprecation warning.
   async IssueDeprecationWarning(uint32_t warning, bool asError);
 
   // When CORS blocks the request in the parent process, it doesn't have the
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -15,16 +15,17 @@ XPIDL_SOURCES += [
     'nsIHttpAuthManager.idl',
     'nsIHttpChannel.idl',
     'nsIHttpChannelAuthProvider.idl',
     'nsIHttpChannelChild.idl',
     'nsIHttpChannelInternal.idl',
     'nsIHttpHeaderVisitor.idl',
     'nsIHttpProtocolHandler.idl',
     'nsIRaceCacheWithNetwork.idl',
+    'nsIRedirectProcessChooser.idl',
     'nsIWellKnownOpportunisticUtils.idl',
 ]
 
 XPIDL_MODULE = 'necko_http'
 
 EXPORTS += [
     'nsCORSListenerProxy.h',
     'nsHttp.h',
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -111,16 +111,18 @@
 #include "HttpChannelParent.h"
 #include "InterceptedHttpChannel.h"
 #include "nsIBufferedStreams.h"
 #include "nsIFileStreams.h"
 #include "nsIMIMEInputStream.h"
 #include "nsIMultiplexInputStream.h"
 #include "../../cache2/CacheFileUtils.h"
 #include "nsINetworkLinkService.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/Promise.h"
 
 #ifdef MOZ_TASK_TRACER
 #include "GeckoTaskTracer.h"
 #endif
 
 #ifdef MOZ_GECKO_PROFILER
 #include "ProfilerMarkerPayload.h"
 #endif
@@ -7098,16 +7100,87 @@ nsHttpChannel::GetRequestMethod(nsACStri
 {
     return HttpBaseChannel::GetRequestMethod(aMethod);
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIRequestObserver
 //-----------------------------------------------------------------------------
 
+// This class is used to convert from a DOM promise to a MozPromise.
+// Once we have a native implementation of nsIRedirectProcessChooser we can
+// remove it and use MozPromises directly.
+class DomPromiseListener final
+    : dom::PromiseNativeHandler
+{
+    NS_DECL_ISUPPORTS
+
+    static RefPtr<nsHttpChannel::TabPromise>
+    Create(dom::Promise* aDOMPromise)
+    {
+        MOZ_ASSERT(aDOMPromise);
+        RefPtr<DomPromiseListener> handler = new DomPromiseListener();
+        RefPtr<nsHttpChannel::TabPromise> promise = handler->mPromiseHolder.Ensure(__func__);
+        aDOMPromise->AppendNativeHandler(handler);
+        return promise;
+    }
+
+    virtual void
+    ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+    {
+        nsCOMPtr<nsITabParent> tabParent;
+        JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+        nsresult rv = UnwrapArg<nsITabParent>(aCx, obj, getter_AddRefs(tabParent));
+        if (NS_FAILED(rv)) {
+            mPromiseHolder.Reject(rv, __func__);
+            return;
+        }
+        mPromiseHolder.Resolve(tabParent, __func__);
+    }
+
+    virtual void
+    RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+    {
+        if (!aValue.isInt32()) {
+            mPromiseHolder.Reject(NS_ERROR_DOM_NOT_NUMBER_ERR, __func__);
+            return;
+        }
+        mPromiseHolder.Reject((nsresult) aValue.toInt32(), __func__);
+    }
+
+private:
+    DomPromiseListener() = default;
+    ~DomPromiseListener() = default;
+    MozPromiseHolder<nsHttpChannel::TabPromise> mPromiseHolder;
+};
+
+NS_IMPL_ISUPPORTS0(DomPromiseListener)
+
+nsresult
+nsHttpChannel::StartCrossProcessRedirect()
+{
+    nsresult rv = CheckRedirectLimit(nsIChannelEventSink::REDIRECT_INTERNAL);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    RefPtr<HttpChannelParentListener> listener = do_QueryObject(mCallbacks);
+    MOZ_ASSERT(listener);
+
+    nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+      CloneLoadInfoForRedirect(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+
+    listener->TriggerCrossProcessRedirect(this,
+                                          redirectLoadInfo,
+                                          mCrossProcessRedirectIdentifier);
+
+    // This will suspend the channel
+    rv = WaitForRedirectCallback();
+
+    return rv;
+}
+
 NS_IMETHODIMP
 nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
 {
     nsresult rv;
 
     MOZ_ASSERT(mRequestObserversCalled);
 
     AUTO_PROFILER_LABEL("nsHttpChannel::OnStartRequest", NETWORK);
@@ -7203,16 +7276,42 @@ nsHttpChannel::OnStartRequest(nsIRequest
     if (mCacheEntry && mCachePump && RECOVER_FROM_CACHE_FILE_ERROR(mStatus)) {
         LOG(("  cache file error, reloading from server"));
         mCacheEntry->AsyncDoom(nullptr);
         rv = StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
         if (NS_SUCCEEDED(rv))
             return NS_OK;
     }
 
+    // Check if the channel should be redirected to another process.
+    // If so, trigger a redirect, and the HttpChannelParentListener will
+    // redirect to the correct process
+    nsCOMPtr<nsIRedirectProcessChooser> requestChooser =
+        do_GetClassObject("@mozilla.org/network/processChooser");
+    if (requestChooser) {
+        nsCOMPtr<nsITabParent> tp;
+        nsCOMPtr<nsIParentChannel> parentChannel;
+        NS_QueryNotificationCallbacks(this, parentChannel);
+
+        RefPtr<dom::Promise> tabPromise;
+        rv = requestChooser->GetChannelRedirectTarget(this, parentChannel, &mCrossProcessRedirectIdentifier, getter_AddRefs(tabPromise));
+
+        if (NS_SUCCEEDED(rv) && tabPromise) {
+            // The promise will be handled in AsyncOnChannelRedirect.
+            mRedirectTabPromise = DomPromiseListener::Create(tabPromise);
+
+            PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
+            rv = StartCrossProcessRedirect();
+            if (NS_SUCCEEDED(rv)) {
+                return NS_OK;
+            }
+            PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
+        }
+    }
+
     // avoid crashing if mListener happens to be null...
     if (!mListener) {
         MOZ_ASSERT_UNREACHABLE("mListener is null");
         return NS_OK;
     }
 
     // before we start any content load, check for redirectTo being called
     // this code is executed mainly before we start load from the cache
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -27,16 +27,17 @@
 #include "AutoClose.h"
 #include "nsIStreamListener.h"
 #include "nsISupportsPrimitives.h"
 #include "nsICorsPreflightCallback.h"
 #include "AlternateServices.h"
 #include "nsIRaceCacheWithNetwork.h"
 #include "mozilla/extensions/PStreamFilterParent.h"
 #include "mozilla/Mutex.h"
+#include "nsITabParent.h"
 
 class nsDNSPrefetch;
 class nsICancelable;
 class nsIHttpChannelAuthProvider;
 class nsInputStreamPump;
 class nsISSLStatus;
 
 namespace mozilla { namespace net {
@@ -273,16 +274,20 @@ public: /* internal necko use only */
 
 private: // used for alternate service validation
     RefPtr<TransactionObserver> mTransactionObserver;
 public:
     void SetConnectionInfo(nsHttpConnectionInfo *); // clones the argument
     void SetTransactionObserver(TransactionObserver *arg) { mTransactionObserver = arg; }
     TransactionObserver *GetTransactionObserver() { return mTransactionObserver; }
 
+    typedef MozPromise<nsCOMPtr<nsITabParent>, nsresult, false> TabPromise;
+    already_AddRefed<TabPromise> TakeRedirectTabPromise() { return mRedirectTabPromise.forget(); }
+    uint64_t CrossProcessRedirectIdentifier() { return mCrossProcessRedirectIdentifier; }
+
 protected:
     virtual ~nsHttpChannel();
 
 private:
     typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
 
     bool     RequestIsConditional();
     void HandleContinueCancelledByTrackingProtection();
@@ -342,16 +347,17 @@ private:
     MOZ_MUST_USE nsresult ContinueHandleAsyncRedirect(nsresult);
     void     HandleAsyncNotModified();
     void     HandleAsyncFallback();
     MOZ_MUST_USE nsresult ContinueHandleAsyncFallback(nsresult);
     MOZ_MUST_USE nsresult PromptTempRedirect();
     virtual MOZ_MUST_USE nsresult
     SetupReplacementChannel(nsIURI *, nsIChannel *, bool preserveMethod,
                             uint32_t redirectFlags) override;
+    nsresult StartCrossProcessRedirect();
 
     // proxy specific methods
     MOZ_MUST_USE nsresult ProxyFailover();
     MOZ_MUST_USE nsresult AsyncDoReplaceWithProxy(nsIProxyInfo *);
     MOZ_MUST_USE nsresult ContinueDoReplaceWithProxy(nsresult);
     MOZ_MUST_USE nsresult ResolveProxy();
 
     // cache specific methods
@@ -502,16 +508,22 @@ private:
     // all the references need to be proxy released on main thread.
     nsCOMPtr<nsIApplicationCache> mApplicationCacheForWrite;
     // auth specific data
     nsCOMPtr<nsIHttpChannelAuthProvider> mAuthProvider;
     nsCOMPtr<nsIURI> mRedirectURI;
     nsCOMPtr<nsIChannel> mRedirectChannel;
     nsCOMPtr<nsIChannel> mPreflightChannel;
 
+    // The associated childChannel is getting relocated to another process.
+    // This promise will be resolved when that process is set up.
+    RefPtr<TabPromise> mRedirectTabPromise;
+    // This identifier is passed to the childChannel in order to identify it.
+    uint64_t mCrossProcessRedirectIdentifier = 0;
+
     // nsChannelClassifier checks this channel's URI against
     // the URI classifier service.
     // nsChannelClassifier will be invoked twice in InitLocalBlockList() and
     // BeginConnectActual(), so save the nsChannelClassifier here to keep the
     // state of whether tracking protection is enabled or not.
     RefPtr<nsChannelClassifier> mChannelClassifier;
 
     // Proxy release all members above on main thread.
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/nsIRedirectProcessChooser.idl
@@ -0,0 +1,45 @@
+/* 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 nsIRequest;
+interface nsITabParent;
+interface nsIChannel;
+interface nsIParentChannel;
+interface nsIChildChannel;
+
+[scriptable, uuid(cd7166de-e221-485f-8172-f1d05f97c6a8)]
+interface nsIRedirectProcessChooser : nsISupports
+{
+  /**
+   * @param aChannel - channel which may need to be redirected to other process.
+   * @param aParentChannel - parent channel associated with aChannel;
+   *                         may be null when aChannel is main process only.
+   * @param aIdentifier - returned by the process chooser when a decision is
+   *                      made to redirect the channel. The identifier is used
+   *                      to track the new channel in the other process.
+   * @return - promise that resolves with a nsITabParent or rejects with nsresult.
+   * @throws NS_ERROR_NOT_AVAILABLE when redirection is not necessary.
+   */
+  Promise getChannelRedirectTarget(in nsIChannel aChannel,
+                                   in nsIParentChannel aParentChannel,
+                                   inout unsigned long long aIdentifier);
+};
+
+[scriptable, uuid(50af805f-e4db-40d9-9dd8-1ae9ab12bdc2)]
+interface nsIChildProcessChannelListener : nsISupports
+{
+  /**
+   * @param aChildChannel - new child channel
+   * @param aIdentifier - returned by call to getChannelRedirectTarget. Used to
+                          identify the new channel.
+   *
+   * This method has to call aChildChannel->completeRedirectSetup(listener, context)
+   * in order to be ready for content; listener is a class that implements
+   * nsIStreamListener.
+   */
+  void onChannelReady(in nsIChildChannel aChildChannel, in unsigned long long aIdentifier);
+};
+
--- a/netwerk/test/browser/browser.ini
+++ b/netwerk/test/browser/browser.ini
@@ -1,15 +1,17 @@
 [DEFAULT]
 support-files =
   dummy.html
   ioactivity.html
+  redirect.sjs
 
 [browser_about_cache.js]
 [browser_NetUtil.js]
 [browser_child_resource.js]
 skip-if = !crashreporter || (e10s && debug && os == "linux" && bits == 64) || debug # Bug 1370783
 [browser_post_file.js]
 [browser_nsIFormPOSTActionChannel.js]
 skip-if = e10s # protocol handler and channel does not work in content process
 [browser_resource_navigation.js]
 [browser_test_io_activity.js]
 [browser_cookie_sync_across_tabs.js]
+[browser_cross_process_redirect.js]
copy from netwerk/test/browser/browser_cookie_sync_across_tabs.js
copy to netwerk/test/browser/browser_cross_process_redirect.js
--- a/netwerk/test/browser/browser_cookie_sync_across_tabs.js
+++ b/netwerk/test/browser/browser_cross_process_redirect.js
@@ -1,65 +1,210 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 "use strict";
 
-add_task(async function() {
-  info("Make sure cookie changes in one process are visible in the other");
+const gRegistrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+let gLoadedInProcess2Promise = null;
+
+function _createProcessChooser(tabParent, from, to, rejectPromise = false) {
+  let processChooser = new ProcessChooser(tabParent, "example.com", "example.org", rejectPromise);
+  processChooser.onComplete = () => {
+    gRegistrar.unregisterFactory(processChooser.classID, processChooser);
+  };
+  gRegistrar.registerFactory(processChooser.classID, "",
+                            "@mozilla.org/network/processChooser",
+                            processChooser);
+  registerCleanupFunction(function() {
+    if (processChooser.onComplete) {
+      processChooser.onComplete();
+    }
+  });
+}
+
+function ProcessChooser(tabParent, from, to, rejectPromise = false) {
+  this.tabParent = tabParent;
+  this.fromDomain = from;
+  this.toDomain = to;
+  this.rejectPromise = rejectPromise;
+}
+
+ProcessChooser.prototype = {
+  // nsIRedirectProcessChooser
+  getChannelRedirectTarget: function(aChannel, aParentChannel, aIdentifier) {
+    // Don't report failure when returning NS_ERROR_NOT_AVAILABLE
+    expectUncaughtException(true);
+
+    // let tabParent = aParentChannel.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsITabParent);
+    if (this.channel && this.channel != aChannel) {
+      // Hack: this is just so we don't get redirected multiple times.
+      info("same channel. give null");
+      throw Cr.NS_ERROR_NOT_AVAILABLE;
+    }
+
+    if (aChannel.URI.host != this.toDomain) {
+      info("wrong host for channel " + aChannel.URI.host);
+      throw Cr.NS_ERROR_NOT_AVAILABLE;
+    }
+
+    let redirects = aChannel.loadInfo.redirectChain;
+    if (redirects[redirects.length - 1].principal.URI.host != this.fromDomain) {
+      info("didn't find redirect");
+      throw Cr.NS_ERROR_NOT_AVAILABLE;
+    }
 
-  const kRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
-                                                    "https://example.com/");
-  const kTestPage = kRoot + "dummy.html";
+    info("returning promise");
+    this.channel = aChannel;
+    let self = this;
+
+    expectUncaughtException(false);
+    if (this.onComplete) {
+      this.onComplete();
+      this.onComplete = null;
+    }
+    aIdentifier.value = 42;
+    return new Promise((resolve, reject) => {
+      if (self.rejectPromise) {
+        info("rejecting");
+        reject(Cr.NS_ERROR_NOT_AVAILABLE);
+        return;
+      }
+      // Can asyncly create a tab, or can resolve with a tab that was
+      // previously created.
+      info("resolving");
+      resolve(self.tabParent);
+    });
+  },
 
-  Services.cookies.removeAll();
+  // nsIFactory
+  createInstance: function(aOuter, aIID) {
+    if (aOuter) {
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    }
+    return this.QueryInterface(aIID);
+  },
+  lockFactory: function() {},
+
+  // nsISupports
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIRedirectProcessChooser, Ci.nsIFactory]),
+  classID: Components.ID("{62561fa8-c091-4c9f-8897-b59ae18b0979}")
+}
 
-  let tab1 = await BrowserTestUtils.openNewForegroundTab({ gBrowser, url: kTestPage, forceNewProcess: true });
-  let tab2 = await BrowserTestUtils.openNewForegroundTab({ gBrowser, url: kTestPage, forceNewProcess: true });
+add_task(async function() {
+  info("Check that a redirect in process A may be correctly handled in process B");
+
+  const kRoot1 = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
+                                                     "https://example.com/");
+  const kRoot2 = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
+                                                     "https://example.org/");
+  const kRoot3 = getRootDirectory(gTestPath);
+
+  // This process will attempt to load the page that redirects to a different origin
+  let tab1 = await BrowserTestUtils.openNewForegroundTab({ gBrowser, url: kRoot1 + "dummy.html", forceNewProcess: true });
+  // This process will eventually receive the redirected channel.
+  let tab2 = await BrowserTestUtils.openNewForegroundTab({ gBrowser, url: kRoot2 + "dummy.html", forceNewProcess: true });
 
   let browser1 = gBrowser.getBrowserForTab(tab1);
   let browser2 = gBrowser.getBrowserForTab(tab2);
 
-  let pid1 = browser1.frameLoader.tabParent.osPid;
-  let pid2 = browser2.frameLoader.tabParent.osPid;
+  // This is for testing purposes only.
+  // This "process chooser" will direct the channel to be opened in the second
+  // tab, and thus in the second parent.
+  let processChooser = _createProcessChooser(browser2.frameLoader.tabParent, "example.com", "example.org");
 
-  // Note, this might not be true once fission is implemented (Bug 1451850)
-  ok(pid1 != pid2, "We should have different processes here.");
+  info("Loading redirected URL");
+  // Open the URL in the first process. We expect it to wind up in the second
+  // process.
 
-  await ContentTask.spawn(browser1, null, async function() {
-    is(content.document.cookie, "", "Expecting no cookies");
-  });
+  // Define the child listener in the new channel.
+  await ContentTask.spawn(browser2, null, async function(arg) {
+    function ChannelListener(childListener) { this.childListener = childListener; }
+    ChannelListener.prototype = {
+      onStartRequest: function(aRequest, aContext) {
+        info("onStartRequest");
+        let channel = aRequest.QueryInterface(Ci.nsIChannel);
+        Assert.equal(channel.URI.spec, this.childListener.URI, "Make sure the channel has the proper URI");
+        Assert.equal(channel.originalURI.spec, this.childListener.originalURI, "Make sure the originalURI is correct");
+      },
+      onStopRequest: function(aRequest, aContext, aStatusCode) {
+        info("onStopRequest");
+        Assert.equal(aStatusCode, Cr.NS_OK, "Check the status code");
+        Assert.equal(this.gotData, true, "Check that the channel received data");
+        if (this.childListener.onComplete) {
+          this.childListener.onComplete();
+        }
+        this.childListener.resolve();
+      },
+      onDataAvailable: function(aRequest, aContext, aInputStream,
+                                aOffset, aCount) {
+        this.gotData = true;
+        info("onDataAvailable");
+      },
+      QueryInterface: ChromeUtils.generateQI([Ci.nsIStreamListener,
+                                              Ci.nsIRequestObserver])
+    };
 
-  await ContentTask.spawn(browser2, null, async function() {
-    is(content.document.cookie, "", "Expecting no cookies");
-  });
+    function ChildListener(uri, originalURI, resolve) { this.URI = uri; this.originalURI = originalURI; this.resolve = resolve;}
+    ChildListener.prototype = {
+      // nsIChildProcessChannelListener
+      onChannelReady: function(aChildChannel, aIdentifier) {
+        Assert.equal(aIdentifier, 42, "Check the status code");
+        info("onChannelReady");
+        aChildChannel.completeRedirectSetup(new ChannelListener(this), null);
+      },
+      // nsIFactory
+      createInstance: function(aOuter, aIID) {
+        if (aOuter) {
+          throw Cr.NS_ERROR_NO_AGGREGATION;
+        }
+        return this.QueryInterface(aIID);
+      },
+      lockFactory: function() {},
+      // nsISupports
+      QueryInterface: ChromeUtils.generateQI([Ci.nsIChildProcessChannelListener, Ci.nsIFactory]),
+      classID: Components.ID("{a6c142a9-eb38-4a09-a940-b71cdad479e1}")
+    }
 
-  await ContentTask.spawn(browser1, null, async function() {
-    content.document.cookie = "a1=test";
+    content.window.ChildListener = ChildListener;
   });
 
-  await ContentTask.spawn(browser2, null, async function() {
-    is(content.document.cookie, "a1=test", "Cookie should be set");
-    content.document.cookie = "a1=other_test";
-  });
-
-  await ContentTask.spawn(browser1, null, async function() {
-    is(content.document.cookie, "a1=other_test", "Cookie should be set");
-    content.document.cookie = "a2=again";
+  // This promise instantiates a ChildListener and is resolved when the redirected
+  // channel is completed.
+  let loadedInProcess2Promise = ContentTask.spawn(browser2, { URI: kRoot2 + "dummy.html", originalURI: kRoot1 + "redirect.sjs?" + kRoot2 + "dummy.html"}, async function(arg) {
+    // We register the listener in process no. 2
+    return new Promise(resolve => {
+      var childListener = new content.window.ChildListener(arg.URI, arg.originalURI, resolve);
+      var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+      childListener.onComplete = () => {
+        registrar.unregisterFactory(childListener.classID, childListener);
+      }
+      registrar.registerFactory(childListener.classID, "",
+                              "@mozilla.org/network/childProcessChannelListener",
+                              childListener);
+    });
   });
 
-  await ContentTask.spawn(browser2, null, async function() {
-    is(content.document.cookie, "a1=other_test; a2=again", "Cookie should be set");
-    content.document.cookie = "a1=; expires=Thu, 01-Jan-1970 00:00:01 GMT;";
-    content.document.cookie = "a2=; expires=Thu, 01-Jan-1970 00:00:01 GMT;";
-  });
+  let browser1LoadHasStopped = BrowserTestUtils.browserStopped(browser1);
+
+  await BrowserTestUtils.loadURI(browser1, kRoot1 + "redirect.sjs?" + kRoot2 + "dummy.html");
 
-  await ContentTask.spawn(browser1, null, async function() {
-    is(content.document.cookie, "", "Cookies should be cleared");
-  });
+  // Check that the channel was delivered to process no. 2
+  await loadedInProcess2Promise;
+  info("channel has loaded in second process");
+  // This is to check that the old channel was cancelled.
+  await browser1LoadHasStopped;
+
+  // check that a rejected promise also works.
+  processChooser = _createProcessChooser(browser2.frameLoader.tabParent, "example.com", "example.org", true);
+  let browser1LoadHasStoppedAgain = BrowserTestUtils.browserStopped(browser1);
+  await BrowserTestUtils.loadURI(browser1, kRoot1 + "redirect.sjs?" + kRoot2 + "dummy.html");
+  await browser1LoadHasStoppedAgain;
+  info("this is done now");
 
   BrowserTestUtils.removeTab(tab1);
   BrowserTestUtils.removeTab(tab2);
 
   ok(true, "Got to the end of the test!");
 });
copy from dom/media/test/redirect.sjs
copy to netwerk/test/browser/redirect.sjs
--- a/dom/media/test/redirect.sjs
+++ b/netwerk/test/browser/redirect.sjs
@@ -1,26 +1,7 @@
-function parseQuery(request, key) {
-  var params = request.queryString.split('&');
-  for (var j = 0; j < params.length; ++j) {
-    var p = params[j];
-	if (p == key)
-	  return true;
-    if (p.indexOf(key + "=") == 0)
-	  return p.substring(key.length + 1);
-	if (p.indexOf("=") < 0 && key == "")
-	  return p;
-  }
-  return false;
-}
-
-// Return file content for the first request with a given key.
-// All subsequent requests return a redirect to a different-origin resource.
 function handleRequest(request, response)
 {
-  var domain = parseQuery(request, "domain");
-  var file = parseQuery(request, "file");
-  var allowed = parseQuery(request, "allowed");
-
-  response.setStatusLine(request.httpVersion, 303, "See Other");
-  response.setHeader("Location", "http://" + domain + "/tests/dom/media/test/" + (allowed ? "allowed.sjs?" : "") + file);
-  response.setHeader("Content-Type", "text/html");
+  response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+  let location = request.queryString;
+  response.setHeader("Location", location, false);
+  response.write("Hello world!");
 }