Bug 765934 - Expose XPCOM API for performing internal redirects. r=honza, a=akeybl
authorMike Perry <mikeperry>
Fri, 11 Jan 2013 15:46:00 -0800
changeset 128161 237990d8bcba5dbb6674d290ed8ade690e74de98
parent 128160 e8d9ae8dbb72c8c5f1bcbaef344abe950ba7b3bc
child 128162 30c3a5df665cebd5da59f0ee42860701b5b0fad4
push id297
push userlsblakk@mozilla.com
push dateTue, 26 Mar 2013 17:28:00 +0000
treeherdermozilla-release@64d7b45c34e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonza, akeybl
bugs765934
milestone20.0a2
Bug 765934 - Expose XPCOM API for performing internal redirects. r=honza, a=akeybl
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/PHttpChannel.ipdl
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsIHttpChannel.idl
netwerk/protocol/viewsource/nsViewSourceChannel.cpp
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -1151,16 +1151,28 @@ HttpBaseChannel::GetRequestSucceeded(boo
 {
   if (!mResponseHead)
     return NS_ERROR_NOT_AVAILABLE;
   uint32_t status = mResponseHead->Status();
   *aValue = (status / 100 == 2);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HttpBaseChannel::RedirectTo(nsIURI *newURI)
+{
+  // We can only redirect unopened channels
+  ENSURE_CALLED_BEFORE_CONNECT();
+
+  // The redirect is stored internally for use in AsyncOpen
+  mAPIRedirectToURI = newURI;
+
+  return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // HttpBaseChannel::nsIHttpChannelInternal
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpBaseChannel::GetDocumentURI(nsIURI **aDocumentURI)
 {
   NS_ENSURE_ARG_POINTER(aDocumentURI);
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -12,16 +12,17 @@
 #include "nsAutoPtr.h"
 #include "nsHashPropertyBag.h"
 #include "nsProxyInfo.h"
 #include "nsHttpRequestHead.h"
 #include "nsHttpResponseHead.h"
 #include "nsHttpConnectionInfo.h"
 #include "nsIEncodedChannel.h"
 #include "nsIHttpChannel.h"
+#include "nsHttpHandler.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIUploadChannel.h"
 #include "nsIUploadChannel2.h"
 #include "nsIProgressEventSink.h"
 #include "nsIURI.h"
 #include "nsIStringEnumerator.h"
 #include "nsISupportsPriority.h"
 #include "nsIApplicationCache.h"
@@ -119,16 +120,17 @@ public:
   NS_IMETHOD SetAllowPipelining(bool value);
   NS_IMETHOD GetRedirectionLimit(uint32_t *value);
   NS_IMETHOD SetRedirectionLimit(uint32_t value);
   NS_IMETHOD IsNoStoreResponse(bool *value);
   NS_IMETHOD IsNoCacheResponse(bool *value);
   NS_IMETHOD GetResponseStatus(uint32_t *aValue);
   NS_IMETHOD GetResponseStatusText(nsACString& aValue);
   NS_IMETHOD GetRequestSucceeded(bool *aValue);
+  NS_IMETHOD RedirectTo(nsIURI *newURI);
 
   // nsIHttpChannelInternal
   NS_IMETHOD GetDocumentURI(nsIURI **aDocumentURI);
   NS_IMETHOD SetDocumentURI(nsIURI *aDocumentURI);
   NS_IMETHOD GetRequestVersion(uint32_t *major, uint32_t *minor);
   NS_IMETHOD GetResponseVersion(uint32_t *major, uint32_t *minor);
   NS_IMETHOD SetCookie(const char *aCookieHeader);
   NS_IMETHOD GetForceAllowThirdPartyCookie(bool *aForce);
@@ -205,16 +207,22 @@ protected:
 
   nsresult ApplyContentConversions();
 
   void AddCookiesToRequest();
   virtual nsresult SetupReplacementChannel(nsIURI *,
                                            nsIChannel *,
                                            bool preserveMethod);
 
+  // bundle calling OMR observers and marking flag into one function
+  inline void CallOnModifyRequestObservers() {
+    gHttpHandler->OnModifyRequest(this);
+    mRequestObserversCalled = true;
+  }
+
   // Helper function to simplify getting notification callbacks.
   template <class T>
   void GetCallback(nsCOMPtr<T> &aResult)
   {
     NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
                                   NS_GET_TEMPLATE_IID(T),
                                   getter_AddRefs(aResult));
   }
@@ -280,16 +288,17 @@ protected:
   uint32_t                          mTimingEnabled              : 1;
   uint32_t                          mAllowSpdy                  : 1;
   uint32_t                          mLoadAsBlocking             : 1;
   uint32_t                          mLoadUnblocked              : 1;
 
   // Current suspension depth for this channel object
   uint32_t                          mSuspendCount;
 
+  nsCOMPtr<nsIURI>                  mAPIRedirectToURI;
   nsAutoPtr<nsTArray<nsCString> >   mRedirectedCachekeys;
 
   uint32_t                          mProxyResolveFlags;
   nsCOMPtr<nsIURI>                  mProxyURI;
 
   uint32_t                          mContentDispositionHint;
   nsAutoPtr<nsString>               mContentDispositionFilename;
 };
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -855,16 +855,17 @@ HttpChannelChild::CompleteRedirectSetup(
 
 //-----------------------------------------------------------------------------
 // HttpChannelChild::nsIAsyncVerifyRedirectCallback
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelChild::OnRedirectVerifyCallback(nsresult result)
 {
+  OptionalURIParams redirectURI;
   nsCOMPtr<nsIHttpChannel> newHttpChannel =
       do_QueryInterface(mRedirectChannelChild);
 
   if (newHttpChannel) {
     // Must not be called until after redirect observers called.
     newHttpChannel->SetOriginalURI(mOriginalURI);
   }
 
@@ -873,26 +874,38 @@ HttpChannelChild::OnRedirectVerifyCallba
 
   nsCOMPtr<nsIHttpChannelChild> newHttpChannelChild =
       do_QueryInterface(mRedirectChannelChild);
   if (newHttpChannelChild && NS_SUCCEEDED(result)) {
     newHttpChannelChild->AddCookiesToRequest();
     newHttpChannelChild->GetClientSetRequestHeaders(&headerTuples);
   }
 
-  // Note: this is where we would notify "http-on-modify-response" observers.
-  // We have deliberately disabled this for child processes (see bug 806753)
-  // 
-  // After we verify redirect, nsHttpChannel may hit the network: must give
-  // "http-on-modify-request" observers the chance to cancel before that.
-  //if (NS_SUCCEEDED(result))
-  //  gHttpHandler->OnModifyRequest(newHttpChannel);
+  if (NS_SUCCEEDED(result)) {
+    // we know this is an HttpChannelChild
+    HttpChannelChild* base =
+      static_cast<HttpChannelChild*>(mRedirectChannelChild.get());
+    // Note: this is where we would notify "http-on-modify-response" observers.
+    // We have deliberately disabled this for child processes (see bug 806753)
+    //
+    // After we verify redirect, nsHttpChannel may hit the network: must give
+    // "http-on-modify-request" observers the chance to cancel before that.
+    //base->CallOnModifyRequestObservers();
+
+    /* If there was an API redirect of this redirect, we need to send it
+     * down here, since it can't get sent via SendAsyncOpen. */
+    SerializeURI(base->mAPIRedirectToURI, redirectURI);
+  } else {
+    /* If the redirect was canceled, bypass OMR and send an empty API
+     * redirect URI */
+    SerializeURI(nullptr, redirectURI);
+  }
 
   if (mIPCOpen)
-    SendRedirect2Verify(result, *headerTuples);
+    SendRedirect2Verify(result, *headerTuples, redirectURI);
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannelChild::nsIRequest
 //-----------------------------------------------------------------------------
 
@@ -988,21 +1001,21 @@ HttpChannelChild::AsyncOpen(nsIStreamLis
 
   AddCookiesToRequest();
 
   //
   // NOTE: From now on we must return NS_OK; all errors must be handled via
   // OnStart/OnStopRequest
   //
 
-  // Note: this is where we would notify "http-on-modify-response" observers.
+  // Note: this is where we would notify "http-on-modify-request" observers.
   // We have deliberately disabled this for child processes (see bug 806753)
   //
   // notify "http-on-modify-request" observers
-  //gHttpHandler->OnModifyRequest(this);
+  //CallOnModifyRequestObservers();
 
   mIsPending = true;
   mWasOpened = true;
   mListener = listener;
   mListenerContext = aContext;
 
   // add ourselves to the load group. 
   if (mLoadGroup)
@@ -1050,25 +1063,26 @@ HttpChannelChild::AsyncOpen(nsIStreamLis
   AddIPDLReference();
 
   gNeckoChild->SendPHttpChannelConstructor(this, tabChild,
                                            IPC::SerializedLoadContext(this));
 
   URIParams uri;
   SerializeURI(mURI, uri);
 
-  OptionalURIParams originalURI, documentURI, referrer;
+  OptionalURIParams originalURI, documentURI, referrer, redirectURI;
   SerializeURI(mOriginalURI, originalURI);
   SerializeURI(mDocumentURI, documentURI);
   SerializeURI(mReferrer, referrer);
+  SerializeURI(mAPIRedirectToURI, redirectURI);
 
   OptionalInputStreamParams uploadStream;
   SerializeInputStream(mUploadStream, uploadStream);
 
-  SendAsyncOpen(uri, originalURI, documentURI, referrer, mLoadFlags,
+  SendAsyncOpen(uri, originalURI, documentURI, referrer, redirectURI, mLoadFlags,
                 mClientSetRequestHeaders, mRequestHead.Method(), uploadStream,
                 mUploadStreamHasHeaders, mPriority, mRedirectionLimit,
                 mAllowPipelining, mForceAllowThirdPartyCookie, mSendResumeAt,
                 mStartPos, mEntityID, mChooseApplicationCache,
                 appCacheClientId, mAllowSpdy);
 
   return NS_OK;
 }
@@ -1091,16 +1105,23 @@ HttpChannelChild::SetRequestHeader(const
     return NS_ERROR_OUT_OF_MEMORY;
 
   tuple->mHeader = aHeader;
   tuple->mValue = aValue;
   tuple->mMerge = aMerge;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HttpChannelChild::RedirectTo(nsIURI *newURI)
+{
+  // disabled until/unless addons run in child or something else needs this
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 //-----------------------------------------------------------------------------
 // HttpChannelChild::nsIHttpChannelInternal
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelChild::SetupFallbackChannel(const char *aFallbackKey)
 {
   DROP_DEAD();
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -65,16 +65,17 @@ public:
   NS_IMETHOD Resume();
   // nsIChannel
   NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo);
   NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *aContext);
   // HttpBaseChannel::nsIHttpChannel
   NS_IMETHOD SetRequestHeader(const nsACString& aHeader, 
                               const nsACString& aValue, 
                               bool aMerge);
+  NS_IMETHOD RedirectTo(nsIURI *newURI);
   // nsIHttpChannelInternal
   NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey);
   NS_IMETHOD GetLocalAddress(nsACString& addr);
   NS_IMETHOD GetLocalPort(int32_t* port);
   NS_IMETHOD GetRemoteAddress(nsACString& addr);
   NS_IMETHOD GetRemotePort(int32_t* port);
   // nsISupportsPriority
   NS_IMETHOD SetPriority(int32_t value);
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -109,16 +109,17 @@ HttpChannelParent::GetInterface(const ns
 // HttpChannelParent::PHttpChannelParent
 //-----------------------------------------------------------------------------
 
 bool 
 HttpChannelParent::RecvAsyncOpen(const URIParams&           aURI,
                                  const OptionalURIParams&   aOriginalURI,
                                  const OptionalURIParams&   aDocURI,
                                  const OptionalURIParams&   aReferrerURI,
+                                 const OptionalURIParams&   aAPIRedirectToURI,
                                  const uint32_t&            loadFlags,
                                  const RequestHeaderTuples& requestHeaders,
                                  const nsHttpAtom&          requestMethod,
                                  const OptionalInputStreamParams& uploadStream,
                                  const bool&              uploadStreamHasHeaders,
                                  const uint16_t&            priority,
                                  const uint8_t&             redirectionLimit,
                                  const bool&              allowPipelining,
@@ -129,16 +130,17 @@ HttpChannelParent::RecvAsyncOpen(const U
                                  const bool&                chooseApplicationCache,
                                  const nsCString&           appCacheClientID,
                                  const bool&                allowSpdy)
 {
   nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
   nsCOMPtr<nsIURI> originalUri = DeserializeURI(aOriginalURI);
   nsCOMPtr<nsIURI> docUri = DeserializeURI(aDocURI);
   nsCOMPtr<nsIURI> referrerUri = DeserializeURI(aReferrerURI);
+  nsCOMPtr<nsIURI> apiRedirectToUri = DeserializeURI(aAPIRedirectToURI);
 
   nsCString uriSpec;
   uri->GetSpec(uriSpec);
   LOG(("HttpChannelParent RecvAsyncOpen [this=%x uri=%s]\n", 
        this, uriSpec.get()));
 
   nsresult rv;
 
@@ -159,16 +161,18 @@ HttpChannelParent::RecvAsyncOpen(const U
     httpChan->ResumeAt(startPos, entityID);
 
   if (originalUri)
     httpChan->SetOriginalURI(originalUri);
   if (docUri)
     httpChan->SetDocumentURI(docUri);
   if (referrerUri)
     httpChan->SetReferrerInternal(referrerUri);
+  if (apiRedirectToUri)
+    httpChan->RedirectTo(apiRedirectToUri);
   if (loadFlags != nsIRequest::LOAD_NORMAL)
     httpChan->SetLoadFlags(loadFlags);
 
   for (uint32_t i = 0; i < requestHeaders.Length(); i++) {
     httpChan->SetRequestHeader(requestHeaders[i].mHeader,
                                requestHeaders[i].mValue,
                                requestHeaders[i].mMerge);
   }
@@ -311,23 +315,29 @@ HttpChannelParent::RecvUpdateAssociatedC
     mAssociatedContentSecurity->SetCountSubRequestsBrokenSecurity(broken);
     mAssociatedContentSecurity->SetCountSubRequestsNoSecurity(no);
   }
   return true;
 }
 
 bool
 HttpChannelParent::RecvRedirect2Verify(const nsresult& result, 
-                                       const RequestHeaderTuples& changedHeaders)
+                                       const RequestHeaderTuples& changedHeaders,
+                                       const OptionalURIParams&   aAPIRedirectURI)
 {
   if (NS_SUCCEEDED(result)) {
     nsCOMPtr<nsIHttpChannel> newHttpChannel =
         do_QueryInterface(mRedirectChannel);
 
     if (newHttpChannel) {
+      nsCOMPtr<nsIURI> apiRedirectUri = DeserializeURI(aAPIRedirectURI);
+
+      if (apiRedirectUri)
+        newHttpChannel->RedirectTo(apiRedirectUri);
+
       for (uint32_t i = 0; i < changedHeaders.Length(); i++) {
         newHttpChannel->SetRequestHeader(changedHeaders[i].mHeader,
                                          changedHeaders[i].mValue,
                                          changedHeaders[i].mMerge);
       }
     }
   }
 
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -49,16 +49,17 @@ public:
                     PBOverrideStatus aStatus);
   virtual ~HttpChannelParent();
 
 protected:
   virtual bool RecvAsyncOpen(const URIParams&           uri,
                              const OptionalURIParams&   originalUri,
                              const OptionalURIParams&   docUri,
                              const OptionalURIParams&   referrerUri,
+                             const OptionalURIParams&   internalRedirectUri,
                              const uint32_t&            loadFlags,
                              const RequestHeaderTuples& requestHeaders,
                              const nsHttpAtom&          requestMethod,
                              const OptionalInputStreamParams& uploadStream,
                              const bool&              uploadStreamHasHeaders,
                              const uint16_t&            priority,
                              const uint8_t&             redirectionLimit,
                              const bool&              allowPipelining,
@@ -72,17 +73,18 @@ protected:
 
   virtual bool RecvConnectChannel(const uint32_t& channelId);
   virtual bool RecvSetPriority(const uint16_t& priority);
   virtual bool RecvSetCacheTokenCachedCharset(const nsCString& charset);
   virtual bool RecvSuspend();
   virtual bool RecvResume();
   virtual bool RecvCancel(const nsresult& status);
   virtual bool RecvRedirect2Verify(const nsresult& result,
-                                   const RequestHeaderTuples& changedHeaders);
+                                   const RequestHeaderTuples& changedHeaders,
+                                   const OptionalURIParams&   apiRedirectUri);
   virtual bool RecvUpdateAssociatedContentSecurity(const int32_t& broken,
                                                    const int32_t& no);
   virtual bool RecvDocumentChannelCleanup();
   virtual bool RecvMarkOfflineCacheEntryAsForeign();
 
   virtual void ActorDestroy(ActorDestroyReason why);
 
 protected:
--- a/netwerk/protocol/http/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -33,16 +33,17 @@ protocol PHttpChannel
 parent:
   AsyncOpen(URIParams           uri,
             // - TODO: bug 571161: unclear if any HTTP channel clients ever
             // set originalURI != uri (about:credits?); also not clear if
             // chrome channel would ever need to know.  Get rid of next arg?
             OptionalURIParams   original,
             OptionalURIParams   doc,
             OptionalURIParams   referrer,
+            OptionalURIParams   apiRedirectTo,
             uint32_t            loadFlags,
             RequestHeaderTuples requestHeaders,
             nsHttpAtom          requestMethod,
             OptionalInputStreamParams uploadStream,
             bool                uploadStreamHasHeaders,
             uint16_t            priority,
             uint8_t             redirectionLimit,
             bool                allowPipelining,
@@ -65,17 +66,18 @@ parent:
   UpdateAssociatedContentSecurity(int32_t broken,
                                   int32_t no);
   Suspend();
   Resume();
 
   Cancel(nsresult status);
 
   // Reports approval/veto of redirect by child process redirect observers
-  Redirect2Verify(nsresult result, RequestHeaderTuples changedHeaders);
+  Redirect2Verify(nsresult result, RequestHeaderTuples changedHeaders,
+                  OptionalURIParams apiRedirectTo);
 
   // 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. 
   DocumentChannelCleanup();
 
   // This might have to be sync. If this fails we must fail the document load
   // to avoid endless loop.
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1528,28 +1528,27 @@ nsHttpChannel::HandleAsyncRedirectChanne
     NS_PRECONDITION(!mCallOnResume, "How did that happen?");
 
     if (mSuspendCount) {
         LOG(("Waiting until resume to do async redirect to https [this=%p]\n", this));
         mCallOnResume = &nsHttpChannel::HandleAsyncRedirectChannelToHttps;
         return;
     }
 
-    nsresult rv = AsyncRedirectChannelToHttps();
+    nsresult rv = StartRedirectChannelToHttps();
     if (NS_FAILED(rv))
-        ContinueAsyncRedirectChannelToHttps(rv);
+        ContinueAsyncRedirectChannelToURI(rv);
 }
 
 nsresult
-nsHttpChannel::AsyncRedirectChannelToHttps()
+nsHttpChannel::StartRedirectChannelToHttps()
 {
     nsresult rv = NS_OK;
     LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n"));
 
-    nsCOMPtr<nsIChannel> newChannel;
     nsCOMPtr<nsIURI> upgradedURI;
 
     rv = mURI->Clone(getter_AddRefs(upgradedURI));
     NS_ENSURE_SUCCESS(rv,rv);
 
     upgradedURI->SetScheme(NS_LITERAL_CSTRING("https"));
 
     int32_t oldPort = -1;
@@ -1561,48 +1560,82 @@ nsHttpChannel::AsyncRedirectChannelToHtt
     //  http://foo.com:80 -> https://foo.com:443
     //  http://foo.com:81 -> https://foo.com:81
 
     if (oldPort == 80 || oldPort == -1)
         upgradedURI->SetPort(-1);
     else
         upgradedURI->SetPort(oldPort);
 
+    return StartRedirectChannelToURI(upgradedURI);
+}
+
+void
+nsHttpChannel::HandleAsyncAPIRedirect()
+{
+    NS_PRECONDITION(!mCallOnResume, "How did that happen?");
+    NS_PRECONDITION(mAPIRedirectToURI, "How did that happen?");
+
+    if (mSuspendCount) {
+        LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
+        mCallOnResume = &nsHttpChannel::HandleAsyncAPIRedirect;
+        return;
+    }
+
+    nsresult rv = StartRedirectChannelToURI(mAPIRedirectToURI);
+    if (NS_FAILED(rv))
+        ContinueAsyncRedirectChannelToURI(rv);
+
+    return;
+}
+
+nsresult
+nsHttpChannel::StartRedirectChannelToURI(nsIURI *upgradedURI)
+{
+    nsresult rv = NS_OK;
+    LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));
+
+    nsCOMPtr<nsIChannel> newChannel;
+
     nsCOMPtr<nsIIOService> ioService;
     rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = ioService->NewChannelFromURI(upgradedURI, getter_AddRefs(newChannel));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = SetupReplacementChannel(upgradedURI, newChannel, true);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Inform consumers about this fake redirect
     mRedirectChannel = newChannel;
     uint32_t flags = nsIChannelEventSink::REDIRECT_PERMANENT;
 
     PushRedirectAsyncFunc(
-        &nsHttpChannel::ContinueAsyncRedirectChannelToHttps);
+        &nsHttpChannel::ContinueAsyncRedirectChannelToURI);
     rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
 
     if (NS_SUCCEEDED(rv))
         rv = WaitForRedirectCallback();
 
     if (NS_FAILED(rv)) {
         AutoRedirectVetoNotifier notifier(this);
+
+        /* Remove the async call to ContinueAsyncRedirectChannelToURI().
+         * It is called directly by our callers upon return (to clean up
+         * the failed redirect). */
         PopRedirectAsyncFunc(
-            &nsHttpChannel::ContinueAsyncRedirectChannelToHttps);
+            &nsHttpChannel::ContinueAsyncRedirectChannelToURI);
     }
 
     return rv;
 }
 
 nsresult
-nsHttpChannel::ContinueAsyncRedirectChannelToHttps(nsresult rv)
+nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv)
 {
     if (NS_SUCCEEDED(rv))
         rv = OpenRedirectChannel(rv);
 
     if (NS_FAILED(rv)) {
         // Fill the failure status here, the update to https had been vetoed
         // but from the security reasons we have to discard the whole channel
         // load.
@@ -4362,19 +4395,23 @@ nsHttpChannel::AsyncOpen(nsIStreamListen
 
 nsresult
 nsHttpChannel::BeginConnect()
 {
     LOG(("nsHttpChannel::BeginConnect [this=%p]\n", this));
     nsresult rv;
 
     // notify "http-on-modify-request" observers
-    gHttpHandler->OnModifyRequest(this);
-
-    mRequestObserversCalled = true;
+    CallOnModifyRequestObservers();
+
+    // Check to see if we should redirect this channel elsewhere by
+    // nsIHttpChannel.redirectTo API request
+    if (mAPIRedirectToURI) {
+        return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
+    }
 
     // If mTimingEnabled flag is not set after OnModifyRequest() then
     // clear the already recorded AsyncOpen value for consistency.
     if (!mTimingEnabled)
         mAsyncOpenTime = TimeStamp();
 
     // Construct connection info object
     nsAutoCString host;
@@ -5517,17 +5554,17 @@ nsHttpChannel::DoAuthRetry(nsAHttpConnec
 
     // fetch cookies, and add them to the request header.
     // the server response could have included cookies that must be sent with
     // this authentication attempt (bug 84794).
     // TODO: save cookies from auth response and send them here (bug 572151).
     AddCookiesToRequest();
     
     // notify "http-on-modify-request" observers
-    gHttpHandler->OnModifyRequest(this);
+    CallOnModifyRequestObservers();
 
     mIsPending = true;
 
     // get rid of the old response headers
     mResponseHead = nullptr;
 
     // set sticky connection flag and disable pipelining.
     mCaps |=  NS_HTTP_STICKY_CONNECTION;
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -176,22 +176,24 @@ private:
     nsresult EnsureAssocReq();
 
     nsresult ContinueOnStartRequest1(nsresult);
     nsresult ContinueOnStartRequest2(nsresult);
     nsresult ContinueOnStartRequest3(nsresult);
 
     // redirection specific methods
     void     HandleAsyncRedirect();
+    void     HandleAsyncAPIRedirect();
     nsresult ContinueHandleAsyncRedirect(nsresult);
     void     HandleAsyncNotModified();
     void     HandleAsyncFallback();
     nsresult ContinueHandleAsyncFallback(nsresult);
     nsresult PromptTempRedirect();
-    virtual nsresult SetupReplacementChannel(nsIURI *, nsIChannel *, bool preserveMethod);
+    nsresult StartRedirectChannelToURI(nsIURI *);
+    virtual  nsresult SetupReplacementChannel(nsIURI *, nsIChannel *, bool preserveMethod);
 
     // proxy specific methods
     nsresult ProxyFailover();
     nsresult AsyncDoReplaceWithProxy(nsIProxyInfo *);
     nsresult ContinueDoReplaceWithProxy(nsresult);
     nsresult ResolveProxy();
 
     // cache specific methods
@@ -236,18 +238,18 @@ private:
 
     // byte range request specific methods
     nsresult ProcessPartialContent();
     nsresult OnDoneReadingPartialCacheEntry(bool *streamDone);
 
     nsresult DoAuthRetry(nsAHttpConnection *);
 
     void     HandleAsyncRedirectChannelToHttps();
-    nsresult AsyncRedirectChannelToHttps();
-    nsresult ContinueAsyncRedirectChannelToHttps(nsresult rv);
+    nsresult StartRedirectChannelToHttps();
+    nsresult ContinueAsyncRedirectChannelToURI(nsresult rv);
     nsresult OpenRedirectChannel(nsresult rv);
 
     /**
      * A function that takes care of reading STS headers and enforcing STS 
      * load rules.  After a secure channel is erected, STS requires the channel
      * to be trusted or any STS header data on the channel is ignored.
      * This is called from ProcessResponse.
      */
--- a/netwerk/protocol/http/nsIHttpChannel.idl
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -9,17 +9,17 @@ interface nsIHttpHeaderVisitor;
 
 /**
  * nsIHttpChannel
  *
  * This interface allows for the modification of HTTP request parameters and
  * the inspection of the resulting HTTP response status and headers when they
  * become available.
  */
-[scriptable, uuid(9277fe09-f0cc-4cd9-bbce-581dd94b0260)]
+[scriptable, uuid(a01362a0-5c45-11e2-bcfd-0800200c9a66)]
 interface nsIHttpChannel : nsIChannel
 {
     /**************************************************************************
      * REQUEST CONFIGURATION
      *
      * Modifying request parameters after asyncOpen has been called is an error.
      */
 
@@ -252,9 +252,21 @@ interface nsIHttpChannel : nsIChannel
      * no-cache" response header.  Equivalent response headers include:
      * "Pragma: no-cache", "Expires: 0", and "Expires" with a date value
      * in the past relative to the value of the "Date" header.
      *
      * @throws NS_ERROR_NOT_AVAILABLE if called before the response
      *         has been received (before onStartRequest).
      */
     boolean isNoCacheResponse();
+
+    /**
+     * Instructs the channel to immediately redirect to a new destination.
+     * Can only be called on channels not yet opened.
+     *
+     * This method provides no explicit conflict resolution. The last
+     * caller to call it wins.
+     *
+     * @throws NS_ERROR_ALREADY_OPENED if called after the channel
+     *         has been opened.
+     */
+    void redirectTo(in nsIURI aNewURI);
 };
--- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -675,9 +675,17 @@ nsViewSourceChannel::IsNoStoreResponse(b
         mHttpChannel->IsNoStoreResponse(_retval);
 }
 
 NS_IMETHODIMP
 nsViewSourceChannel::IsNoCacheResponse(bool *_retval)
 {
     return !mHttpChannel ? NS_ERROR_NULL_POINTER :
         mHttpChannel->IsNoCacheResponse(_retval);
-} 
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::RedirectTo(nsIURI *uri)
+{
+    return !mHttpChannel ? NS_ERROR_NULL_POINTER :
+        mHttpChannel->RedirectTo(uri);
+}
+