Bug 890908 - Move Negotiate auth off main thread. r=mayhemer
authorJan Horak <jhorak@redhat.com>
Thu, 14 Jul 2016 03:32:00 -0400
changeset 330213 344f289cc64dcc0f2541563b766d0d3ddd003190
parent 330212 41e1c88227ca0c9c3bcdf5adbc9e915520e58be4
child 330214 3bbf5d4f5e79f4d8c1068aa9dd089f44c7b0e759
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmayhemer
bugs890908
milestone50.0a1
Bug 890908 - Move Negotiate auth off main thread. r=mayhemer
extensions/auth/nsHttpNegotiateAuth.cpp
extensions/auth/nsHttpNegotiateAuth.h
netwerk/base/moz.build
netwerk/base/nsIHttpAuthenticatorCallback.idl
netwerk/protocol/http/nsHttpBasicAuth.cpp
netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
netwerk/protocol/http/nsHttpChannelAuthProvider.h
netwerk/protocol/http/nsHttpDigestAuth.cpp
netwerk/protocol/http/nsHttpNTLMAuth.cpp
netwerk/protocol/http/nsIHttpAuthenticator.idl
--- a/extensions/auth/nsHttpNegotiateAuth.cpp
+++ b/extensions/auth/nsHttpNegotiateAuth.cpp
@@ -35,27 +35,32 @@
 #include "prprf.h"
 #include "mozilla/Logging.h"
 #include "prmem.h"
 #include "prnetdb.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Snprintf.h"
 #include "nsIChannel.h"
 #include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "nsIHttpAuthenticatorCallback.h"
+#include "mozilla/Mutex.h"
+#include "nsICancelable.h"
 
 //-----------------------------------------------------------------------------
 
 static const char kNegotiate[] = "Negotiate";
 static const char kNegotiateAuthTrustedURIs[] = "network.negotiate-auth.trusted-uris";
 static const char kNegotiateAuthDelegationURIs[] = "network.negotiate-auth.delegation-uris";
 static const char kNegotiateAuthAllowProxies[] = "network.negotiate-auth.allow-proxies";
 static const char kNegotiateAuthAllowNonFqdn[] = "network.negotiate-auth.allow-non-fqdn";
 static const char kNegotiateAuthSSPI[] = "network.auth.use-sspi";
 
 #define kNegotiateLen  (sizeof(kNegotiate)-1)
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
 
 //-----------------------------------------------------------------------------
 
 // Return false when the channel comes from a Private browsing window.
 static bool
 TestNotInPBMode(nsIHttpAuthenticableChannel *authChannel)
 {
     nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(authChannel);
@@ -179,17 +184,270 @@ nsHttpNegotiateAuth::ChallengeReceived(n
         return rv;
     }
 
     *continuationState = module;
     return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(nsHttpNegotiateAuth, nsIHttpAuthenticator)
-   
+
+namespace {
+
+//
+// GetNextTokenCompleteEvent
+//
+// This event is fired on main thread when async call of
+// nsHttpNegotiateAuth::GenerateCredentials is finished. During the Run()
+// method the nsIHttpAuthenticatorCallback::OnCredsAvailable is called with
+// obtained credentials, flags and NS_OK when successful, otherwise 
+// NS_ERROR_FAILURE is returned as a result of failed operation.
+//
+class GetNextTokenCompleteEvent final : public nsIRunnable,
+                                        public nsICancelable
+{
+    virtual ~GetNextTokenCompleteEvent()
+    {
+        if (mCreds) {
+            free(mCreds);
+        }
+    };
+
+public:
+    NS_DECL_THREADSAFE_ISUPPORTS
+
+    explicit GetNextTokenCompleteEvent(nsIHttpAuthenticatorCallback* aCallback)
+        : mCallback(aCallback)
+        , mCreds(nullptr)
+        , mCancelled(false)
+    {
+    }
+
+    NS_IMETHODIMP DispatchSuccess(char *aCreds,
+                                  uint32_t aFlags,
+                                  already_AddRefed<nsISupports> aSessionState,
+                                  already_AddRefed<nsISupports> aContinuationState)
+    {
+        // Called from worker thread
+        MOZ_ASSERT(!NS_IsMainThread());
+
+        mCreds = aCreds;
+        mFlags = aFlags;
+        mResult = NS_OK;
+        mSessionState = aSessionState;
+        mContinuationState = aContinuationState;
+        return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
+    }
+
+    NS_IMETHODIMP DispatchError(already_AddRefed<nsISupports> aSessionState,
+                                already_AddRefed<nsISupports> aContinuationState)
+    {
+        // Called from worker thread
+        MOZ_ASSERT(!NS_IsMainThread());
+
+        mResult = NS_ERROR_FAILURE;
+        mSessionState = aSessionState;
+        mContinuationState = aContinuationState;
+        return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
+    }
+
+    NS_IMETHODIMP Run() override
+    {
+        // Runs on main thread
+        MOZ_ASSERT(NS_IsMainThread());
+
+        if (!mCancelled) {
+            nsCOMPtr<nsIHttpAuthenticatorCallback> callback;
+            callback.swap(mCallback);
+            callback->OnCredsGenerated(mCreds, mFlags, mResult, mSessionState, mContinuationState);
+        }
+        return NS_OK;
+    }
+
+    NS_IMETHODIMP Cancel(nsresult aReason) override
+    {
+        // Supposed to be called from main thread
+        MOZ_ASSERT(NS_IsMainThread());
+
+        mCancelled = true;
+        return NS_OK;
+    }
+
+private:
+    nsCOMPtr<nsIHttpAuthenticatorCallback> mCallback;
+    char *mCreds; // This class owns it, freed in destructor
+    uint32_t mFlags;
+    nsresult mResult;
+    bool mCancelled;
+    nsCOMPtr<nsISupports> mSessionState;
+    nsCOMPtr<nsISupports> mContinuationState;
+};
+
+NS_IMPL_ISUPPORTS(GetNextTokenCompleteEvent, nsIRunnable, nsICancelable)
+
+//
+// GetNextTokenRunnable
+//
+// This runnable is created by GenerateCredentialsAsync and it runs
+// in nsHttpNegotiateAuth::mNegotiateThread and calling GenerateCredentials.
+//
+class GetNextTokenRunnable final : public mozilla::Runnable
+{
+    virtual ~GetNextTokenRunnable() {}
+    public:
+        GetNextTokenRunnable(nsIHttpAuthenticableChannel *authChannel,
+                             const char *challenge,
+                             bool isProxyAuth,
+                             const char16_t *domain,
+                             const char16_t *username,
+                             const char16_t *password,
+                             nsISupports *sessionState,
+                             nsISupports *continuationState,
+                             GetNextTokenCompleteEvent *aCompleteEvent
+                             )
+            : mAuthChannel(authChannel)
+            , mChallenge(challenge)
+            , mIsProxyAuth(isProxyAuth)
+            , mDomain(domain)
+            , mUsername(username)
+            , mPassword(password)
+            , mSessionState(sessionState)
+            , mContinuationState(continuationState)
+            , mCompleteEvent(aCompleteEvent)
+        {
+        }
+
+        NS_IMETHODIMP Run() override
+        {
+            // Runs on worker thread
+            MOZ_ASSERT(!NS_IsMainThread());
+
+            char *creds;
+            uint32_t flags;
+            nsresult rv = ObtainCredentialsAndFlags(&creds, &flags);
+
+            // Passing session and continuation state this way to not touch
+            // referencing of the object that may not be thread safe.
+            // Not having a thread safe referencing doesn't mean the object
+            // cannot be used on multiple threads (one example is nsAuthSSPI.)
+            // This ensures state objects will be destroyed on the main thread
+            // when not changed by GenerateCredentials.
+            if (NS_FAILED(rv)) {
+                return mCompleteEvent->DispatchError(mSessionState.forget(),
+                                                     mContinuationState.forget());
+            }
+
+            return mCompleteEvent->DispatchSuccess(creds, flags,
+                                                   mSessionState.forget(),
+                                                   mContinuationState.forget());
+        }
+
+        NS_IMETHODIMP ObtainCredentialsAndFlags(char **aCreds, uint32_t *aFlags)
+        {
+            nsresult rv;
+
+            // Use negotiate service to call GenerateCredentials outside of main thread
+            nsAutoCString contractId;
+            contractId.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
+            contractId.Append("negotiate");
+            nsCOMPtr<nsIHttpAuthenticator> authenticator =
+              do_GetService(contractId.get(), &rv);
+            NS_ENSURE_SUCCESS(rv, rv);
+
+            nsISupports *sessionState = mSessionState;
+            nsISupports *continuationState = mContinuationState;
+            // The continuationState is for the sake of completeness propagated
+            // to the caller (despite it is not changed in any GenerateCredentials
+            // implementation).
+            //
+            // The only implementation that use sessionState is the
+            // nsHttpDigestAuth::GenerateCredentials. Since there's no reason
+            // to implement nsHttpDigestAuth::GenerateCredentialsAsync
+            // because digest auth does not block the main thread, we won't
+            // propagate changes to sessionState to the caller because of
+            // the change is too complicated on the caller side.
+            //
+            // Should any of the session or continuation states change inside
+            // this method, they must be threadsafe.
+            rv = authenticator->GenerateCredentials(mAuthChannel,
+                                                    mChallenge.get(),
+                                                    mIsProxyAuth,
+                                                    mDomain.get(),
+                                                    mUsername.get(),
+                                                    mPassword.get(),
+                                                    &sessionState,
+                                                    &continuationState,
+                                                    aFlags,
+                                                    aCreds);
+            if (mSessionState != sessionState) {
+                mSessionState = sessionState;
+            }
+            if (mContinuationState != continuationState) {
+                mContinuationState = continuationState;
+            }
+            return rv;
+        }
+    private:
+        nsCOMPtr<nsIHttpAuthenticableChannel> mAuthChannel;
+        nsCString mChallenge;
+        bool mIsProxyAuth;
+        nsString mDomain;
+        nsString mUsername;
+        nsString mPassword;
+        nsCOMPtr<nsISupports> mSessionState;
+        nsCOMPtr<nsISupports> mContinuationState;
+        RefPtr<GetNextTokenCompleteEvent> mCompleteEvent;
+};
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+nsHttpNegotiateAuth::GenerateCredentialsAsync(nsIHttpAuthenticableChannel *authChannel,
+                                              nsIHttpAuthenticatorCallback* aCallback,
+                                              const char *challenge,
+                                              bool isProxyAuth,
+                                              const char16_t *domain,
+                                              const char16_t *username,
+                                              const char16_t *password,
+                                              nsISupports *sessionState,
+                                              nsISupports *continuationState,
+                                              nsICancelable **aCancelable)
+{
+   NS_ENSURE_ARG(aCallback);
+   NS_ENSURE_ARG_POINTER(aCancelable);
+
+   RefPtr<GetNextTokenCompleteEvent> cancelEvent =
+       new GetNextTokenCompleteEvent(aCallback);
+
+
+   nsCOMPtr<nsIRunnable> getNextTokenRunnable =
+       new GetNextTokenRunnable(authChannel,
+                                challenge,
+                                isProxyAuth,
+                                domain,
+                                username,
+                                password,
+                                sessionState,
+                                continuationState,
+                                cancelEvent);
+   cancelEvent.forget(aCancelable);
+
+   nsresult rv;
+   if (!mNegotiateThread) {
+       mNegotiateThread =
+           new mozilla::LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+                                       NS_LITERAL_CSTRING("NegotiateAuth"));
+       NS_ENSURE_TRUE(mNegotiateThread, NS_ERROR_OUT_OF_MEMORY);
+   }
+   rv = mNegotiateThread->Dispatch(getNextTokenRunnable, NS_DISPATCH_NORMAL);
+   NS_ENSURE_SUCCESS(rv, rv);
+
+   return NS_OK;
+}
+
 //
 // GenerateCredentials
 //
 // This routine is responsible for creating the correct authentication
 // blob to pass to the server that requested "Negotiate" authentication.
 //
 NS_IMETHODIMP
 nsHttpNegotiateAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel,
--- a/extensions/auth/nsHttpNegotiateAuth.h
+++ b/extensions/auth/nsHttpNegotiateAuth.h
@@ -5,24 +5,25 @@
 
 #ifndef nsHttpNegotiateAuth_h__
 #define nsHttpNegotiateAuth_h__
 
 #include "nsIHttpAuthenticator.h"
 #include "nsIURI.h"
 #include "nsSubstring.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/LazyIdleThread.h"
 
 // The nsHttpNegotiateAuth class provides responses for the GSS-API Negotiate method
 // as specified by Microsoft in draft-brezak-spnego-http-04.txt
 
 class nsHttpNegotiateAuth final : public nsIHttpAuthenticator
 {
 public:
-    NS_DECL_ISUPPORTS
+    NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIHTTPAUTHENTICATOR
 
 private:
     ~nsHttpNegotiateAuth() {}
 
     // returns the value of the given boolean pref
     bool TestBoolPref(const char *pref);
 
@@ -32,10 +33,12 @@ private:
     // returns true if URI is accepted by the list of hosts in the pref
     bool TestPref(nsIURI *, const char *pref);
 
     bool MatchesBaseURI(const nsCSubstring &scheme,
                           const nsCSubstring &host,
                           int32_t             port,
                           const char         *baseStart,
                           const char         *baseEnd);
+    // Thread for GenerateCredentialsAsync
+    RefPtr<mozilla::LazyIdleThread> mNegotiateThread;
 };
 #endif /* nsHttpNegotiateAuth_h__ */
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -43,16 +43,17 @@ XPIDL_SOURCES += [
     'nsIDivertableChannel.idl',
     'nsIDownloader.idl',
     'nsIEncodedChannel.idl',
     'nsIExternalProtocolHandler.idl',
     'nsIFileStreams.idl',
     'nsIFileURL.idl',
     'nsIForcePendingChannel.idl',
     'nsIFormPOSTActionChannel.idl',
+    'nsIHttpAuthenticatorCallback.idl',
     'nsIHttpPushListener.idl',
     'nsIIncrementalDownload.idl',
     'nsIIncrementalStreamLoader.idl',
     'nsIInputStreamChannel.idl',
     'nsIInputStreamPump.idl',
     'nsIIOService.idl',
     'nsIIOService2.idl',
     'nsILoadContextInfo.idl',
new file mode 100644
--- /dev/null
+++ b/netwerk/base/nsIHttpAuthenticatorCallback.idl
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(d989cb03-e446-4086-b9e6-46842cb97bd5)]
+interface nsIHttpAuthenticatorCallback : nsISupports
+{
+  /**
+   * Authentication data for a header is available.
+   *
+   * @param aCreds
+   *        Credentials which were obtained asynchonously.
+   * @param aFlags
+   *        Flags set by asynchronous call.
+   * @param aResult
+   *        Result status of credentials generation
+   * @param aSessionState
+   *        Modified session state to be passed to caller
+   * @param aContinuationState
+   *        Modified continuation state to be passed to caller
+   */
+  void onCredsGenerated(in string aCreds,
+                        in unsigned long aFlags,
+                        in nsresult aResult,
+                        in nsISupports aSessionsState,
+                        in nsISupports aContinuationState);
+
+};
+
--- a/netwerk/protocol/http/nsHttpBasicAuth.cpp
+++ b/netwerk/protocol/http/nsHttpBasicAuth.cpp
@@ -44,16 +44,30 @@ nsHttpBasicAuth::ChallengeReceived(nsIHt
                                    nsISupports **continuationState,
                                    bool *identityInvalid)
 {
     // if challenged, then the username:password that was sent must
     // have been wrong.
     *identityInvalid = true;
     return NS_OK;
 }
+NS_IMETHODIMP
+nsHttpBasicAuth::GenerateCredentialsAsync(nsIHttpAuthenticableChannel *authChannel,
+                                          nsIHttpAuthenticatorCallback* aCallback,
+                                          const char *challenge,
+                                          bool isProxyAuth,
+                                          const char16_t *domain,
+                                          const char16_t *username,
+                                          const char16_t *password,
+                                          nsISupports *sessionState,
+                                          nsISupports *continuationState,
+                                          nsICancelable **aCancellable)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
 
 NS_IMETHODIMP
 nsHttpBasicAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel,
                                      const char *challenge,
                                      bool isProxyAuth,
                                      const char16_t *domain,
                                      const char16_t *user,
                                      const char16_t *password,
--- a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
@@ -273,29 +273,39 @@ NS_IMETHODIMP
 nsHttpChannelAuthProvider::Cancel(nsresult status)
 {
     MOZ_ASSERT(mAuthChannel, "Channel not initialized");
 
     if (mAsyncPromptAuthCancelable) {
         mAsyncPromptAuthCancelable->Cancel(status);
         mAsyncPromptAuthCancelable = nullptr;
     }
+
+    if (mGenerateCredentialsCancelable) {
+        mGenerateCredentialsCancelable->Cancel(status);
+        mGenerateCredentialsCancelable = nullptr;
+    }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannelAuthProvider::Disconnect(nsresult status)
 {
     mAuthChannel = nullptr;
 
     if (mAsyncPromptAuthCancelable) {
         mAsyncPromptAuthCancelable->Cancel(status);
         mAsyncPromptAuthCancelable = nullptr;
     }
 
+    if (mGenerateCredentialsCancelable) {
+        mGenerateCredentialsCancelable->Cancel(status);
+        mGenerateCredentialsCancelable = nullptr;
+    }
+
     NS_IF_RELEASE(mProxyAuthContinuationState);
     NS_IF_RELEASE(mAuthContinuationState);
 
     return NS_OK;
 }
 
 // buf contains "domain\user"
 static void
@@ -361,34 +371,45 @@ nsHttpChannelAuthProvider::GenCredsAndSe
                                                const char           *directory,
                                                const char           *realm,
                                                const char           *challenge,
                                                const nsHttpAuthIdentity &ident,
                                                nsCOMPtr<nsISupports>    &sessionState,
                                                char                    **result)
 {
     nsresult rv;
-    uint32_t authFlags;
-
-    rv = auth->GetAuthFlags(&authFlags);
-    if (NS_FAILED(rv)) return rv;
-
     nsISupports *ss = sessionState;
 
     // set informations that depend on whether
     // we're authenticating against a proxy
     // or a webserver
     nsISupports **continuationState;
 
     if (proxyAuth) {
         continuationState = &mProxyAuthContinuationState;
     } else {
         continuationState = &mAuthContinuationState;
     }
 
+    rv = auth->GenerateCredentialsAsync(mAuthChannel,
+                                       this,
+                                       challenge,
+                                       proxyAuth,
+                                       ident.Domain(),
+                                       ident.User(),
+                                       ident.Password(),
+                                       ss,
+                                       *continuationState,
+                                       getter_AddRefs(mGenerateCredentialsCancelable));
+    if (NS_SUCCEEDED(rv)) {
+        // Calling generate credentials async, results will be dispatched to the
+        // main thread by calling OnCredsGenerated method
+        return NS_ERROR_IN_PROGRESS;
+    }
+
     uint32_t generateFlags;
     rv = auth->GenerateCredentials(mAuthChannel,
                                    challenge,
                                    proxyAuth,
                                    ident.Domain(),
                                    ident.User(),
                                    ident.Password(),
                                    &ss,
@@ -399,16 +420,39 @@ nsHttpChannelAuthProvider::GenCredsAndSe
     sessionState.swap(ss);
     if (NS_FAILED(rv)) return rv;
 
     // don't log this in release build since it could contain sensitive info.
 #ifdef DEBUG
     LOG(("generated creds: %s\n", *result));
 #endif
 
+    return UpdateCache(auth, scheme, host, port, directory, realm,
+            challenge, ident, *result, generateFlags, sessionState);
+}
+
+nsresult
+nsHttpChannelAuthProvider::UpdateCache(nsIHttpAuthenticator *auth,
+                                       const char           *scheme,
+                                       const char           *host,
+                                       int32_t               port,
+                                       const char           *directory,
+                                       const char           *realm,
+                                       const char           *challenge,
+                                       const nsHttpAuthIdentity &ident,
+                                       const char           *creds,
+                                       uint32_t              generateFlags,
+                                       nsISupports          *sessionState)
+{
+    nsresult rv;
+
+    uint32_t authFlags;
+    rv = auth->GetAuthFlags(&authFlags);
+    if (NS_FAILED(rv)) return rv;
+
     // find out if this authenticator allows reuse of credentials and/or
     // challenge.
     bool saveCreds =
         0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS);
     bool saveChallenge =
         0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE);
 
     bool saveIdentity =
@@ -416,29 +460,31 @@ nsHttpChannelAuthProvider::GenCredsAndSe
 
     // this getter never fails
     nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate);
 
     nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
     nsAutoCString suffix;
     GetOriginAttributesSuffix(chan, suffix);
 
+
     // create a cache entry.  we do this even though we don't yet know that
     // these credentials are valid b/c we need to avoid prompting the user
     // more than once in case the credentials are valid.
     //
     // if the credentials are not reusable, then we don't bother sticking
     // them in the auth cache.
     rv = authCache->SetAuthEntry(scheme, host, port, directory, realm,
-                                 saveCreds ? *result : nullptr,
+                                 saveCreds ? creds : nullptr,
                                  saveChallenge ? challenge : nullptr,
                                  suffix,
                                  saveIdentity ? &ident : nullptr,
                                  sessionState);
     return rv;
+
 }
 
 nsresult
 nsHttpChannelAuthProvider::PrepareForAuthentication(bool proxyAuth)
 {
     LOG(("nsHttpChannelAuthProvider::PrepareForAuthentication "
          "[this=%p channel=%p]\n", this, mAuthChannel));
 
@@ -1266,16 +1312,73 @@ NS_IMETHODIMP nsHttpChannelAuthProvider:
         mRemainingChallenges.Truncate();
     }
 
     mAuthChannel->OnAuthCancelled(userCancel);
 
     return NS_OK;
 }
 
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnCredsGenerated(const char *aGeneratedCreds,
+                                                          uint32_t aFlags,
+                                                          nsresult aResult,
+                                                          nsISupports* aSessionState,
+                                                          nsISupports* aContinuationState)
+{
+    nsresult rv;
+
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // When channel is closed, do not proceed
+    if (!mAuthChannel) {
+        return NS_OK;
+    }
+
+    mGenerateCredentialsCancelable = nullptr;
+
+    if (NS_FAILED(aResult)) {
+        return OnAuthCancelled(nullptr, true);
+    }
+
+    // We want to update m(Proxy)AuthContinuationState in case it was changed by
+    // nsHttpNegotiateAuth::GenerateCredentials
+    nsCOMPtr<nsISupports> contState(aContinuationState);
+    if (mProxyAuth) {
+        contState.swap(mProxyAuthContinuationState);
+    } else {
+        contState.swap(mAuthContinuationState);
+    }
+
+    nsCOMPtr<nsIHttpAuthenticator> auth;
+    nsAutoCString unused;
+    rv = GetAuthenticator(mCurrentChallenge.get(), unused, getter_AddRefs(auth));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    const char *host;
+    int32_t port;
+    nsHttpAuthIdentity *ident;
+    nsAutoCString directory, scheme;
+    nsISupports **unusedContinuationState;
+
+    // Get realm from challenge
+    nsAutoCString realm;
+    ParseRealm(mCurrentChallenge.get(), realm);
+
+    rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port,
+                                 directory, ident, unusedContinuationState);
+    if (NS_FAILED(rv)) return rv;
+
+    UpdateCache(auth, scheme.get(), host, port, directory.get(), realm.get(),
+            mCurrentChallenge.get(), *ident, aGeneratedCreds, aFlags, aSessionState);
+    mCurrentChallenge.Truncate();
+
+    ContinueOnAuthAvailable(nsDependentCString(aGeneratedCreds));
+    return NS_OK;
+}
+
 nsresult
 nsHttpChannelAuthProvider::ContinueOnAuthAvailable(const nsCSubstring& creds)
 {
     nsresult rv;
     if (mProxyAuth)
         rv = mAuthChannel->SetProxyCredentials(creds);
     else
         rv = mAuthChannel->SetWWWCredentials(creds);
@@ -1500,12 +1603,12 @@ nsHttpChannelAuthProvider::GetCurrentPat
     if (url)
         rv = url->GetDirectory(path);
     else
         rv = mURI->GetPath(path);
     return rv;
 }
 
 NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider, nsICancelable,
-                  nsIHttpChannelAuthProvider, nsIAuthPromptCallback)
+                  nsIHttpChannelAuthProvider, nsIAuthPromptCallback, nsIHttpAuthenticatorCallback)
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpChannelAuthProvider.h
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.h
@@ -4,38 +4,42 @@
  * 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 nsHttpChannelAuthProvider_h__
 #define nsHttpChannelAuthProvider_h__
 
 #include "nsIHttpChannelAuthProvider.h"
 #include "nsIAuthPromptCallback.h"
+#include "nsIHttpAuthenticatorCallback.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsHttpAuthCache.h"
 #include "nsProxyInfo.h"
 #include "nsCRT.h"
+#include "nsICancelableRunnable.h"
 
 class nsIHttpAuthenticableChannel;
 class nsIHttpAuthenticator;
 class nsIURI;
 
 namespace mozilla { namespace net {
 
 class nsHttpHandler;
 
 class nsHttpChannelAuthProvider : public nsIHttpChannelAuthProvider
                                 , public nsIAuthPromptCallback
+                                , public nsIHttpAuthenticatorCallback
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSICANCELABLE
     NS_DECL_NSIHTTPCHANNELAUTHPROVIDER
     NS_DECL_NSIAUTHPROMPTCALLBACK
+    NS_DECL_NSIHTTPAUTHENTICATORCALLBACK
 
     nsHttpChannelAuthProvider();
     static void InitializePrefs();
 private:
     virtual ~nsHttpChannelAuthProvider();
 
     const char *ProxyHost() const
     { return mProxyInfo ? mProxyInfo->Host().get() : nullptr; }
@@ -112,16 +116,29 @@ private:
     nsresult ProcessSTSHeader();
 
     // Depending on the pref setting, the authentication dialog may be blocked
     // for all sub-resources, blocked for cross-origin sub-resources, or
     // always allowed for sub-resources.
     // For more details look at the bug 647010.
     bool BlockPrompt();
 
+    // Store credentials to the cache when appropriate aFlags are set.
+    nsresult UpdateCache(nsIHttpAuthenticator *aAuth,
+                         const char           *aScheme,
+                         const char           *aHost,
+                         int32_t               aPort,
+                         const char           *aDirectory,
+                         const char           *aRealm,
+                         const char           *aChallenge,
+                         const nsHttpAuthIdentity &aIdent,
+                         const char           *aCreds,
+                         uint32_t              aGenerateFlags,
+                         nsISupports          *aSessionState);
+
 private:
     nsIHttpAuthenticableChannel      *mAuthChannel;  // weak ref
 
     nsCOMPtr<nsIURI>                  mURI;
     nsCOMPtr<nsProxyInfo>             mProxyInfo;
     nsCString                         mHost;
     int32_t                           mPort;
     bool                              mUsingSSL;
@@ -159,14 +176,15 @@ private:
     uint32_t                          mCrossOrigin              : 1;
 
     RefPtr<nsHttpHandler>           mHttpHandler;  // keep gHttpHandler alive
 
     // A variable holding the preference settings to whether to open HTTP
     // authentication credentials dialogs for sub-resources and cross-origin
     // sub-resources.
     static uint32_t                   sAuthAllowPref;
+    nsCOMPtr<nsICancelable>           mGenerateCredentialsCancelable;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // nsHttpChannelAuthProvider_h__
--- a/netwerk/protocol/http/nsHttpDigestAuth.cpp
+++ b/netwerk/protocol/http/nsHttpDigestAuth.cpp
@@ -153,16 +153,32 @@ nsHttpDigestAuth::ChallengeReceived(nsIH
   // and password prompting that usually accompanies a 401/407 challenge.
   *result = !stale;
 
   // clear any existing nonce_count since we have a new challenge.
   NS_IF_RELEASE(*sessionState);
   return NS_OK;
 }
 
+
+NS_IMETHODIMP
+nsHttpDigestAuth::GenerateCredentialsAsync(nsIHttpAuthenticableChannel *authChannel,
+                                           nsIHttpAuthenticatorCallback* aCallback,
+                                           const char *challenge,
+                                           bool isProxyAuth,
+                                           const char16_t *domain,
+                                           const char16_t *username,
+                                           const char16_t *password,
+                                           nsISupports *sessionState,
+                                           nsISupports *continuationState,
+                                           nsICancelable **aCancellable)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 NS_IMETHODIMP
 nsHttpDigestAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel,
                                       const char *challenge,
                                       bool isProxyAuth,
                                       const char16_t *userdomain,
                                       const char16_t *username,
                                       const char16_t *password,
                                       nsISupports **sessionState,
--- a/netwerk/protocol/http/nsHttpNTLMAuth.cpp
+++ b/netwerk/protocol/http/nsHttpNTLMAuth.cpp
@@ -328,16 +328,31 @@ nsHttpNTLMAuth::ChallengeReceived(nsIHtt
         // A non-null continuation state implies that we failed to authenticate.
         // Blow away the old authentication state, and use the new one.
         module.swap(*continuationState);
     }
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsHttpNTLMAuth::GenerateCredentialsAsync(nsIHttpAuthenticableChannel *authChannel,
+                                         nsIHttpAuthenticatorCallback* aCallback,
+                                         const char *challenge,
+                                         bool isProxyAuth,
+                                         const char16_t *domain,
+                                         const char16_t *username,
+                                         const char16_t *password,
+                                         nsISupports *sessionState,
+                                         nsISupports *continuationState,
+                                         nsICancelable **aCancellable)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
 nsHttpNTLMAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel,
                                     const char      *challenge,
                                     bool             isProxyAuth,
                                     const char16_t *domain,
                                     const char16_t *user,
                                     const char16_t *pass,
                                     nsISupports    **sessionState,
                                     nsISupports    **continuationState,
--- a/netwerk/protocol/http/nsIHttpAuthenticator.idl
+++ b/netwerk/protocol/http/nsIHttpAuthenticator.idl
@@ -1,29 +1,31 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 nsIHttpAuthenticableChannel;
+interface nsIHttpAuthenticatorCallback;
+interface nsICancelable;
 
 /**
  * nsIHttpAuthenticator
  *
  * Interface designed to allow for pluggable HTTP authentication modules.
  * Implementations are registered under the ContractID:
  *
  *   "@mozilla.org/network/http-authenticator;1?scheme=<auth-scheme>"
  *
  * where <auth-scheme> is the lower-cased value of the authentication scheme
  * found in the server challenge per the rules of RFC 2617.
  */
-[scriptable, uuid(16784db0-fcb1-4352-b0c9-6a3a67e3cf79)]
+[scriptable, uuid(fef7db8a-a4e2-49d1-9685-19ed7e309b7d)]
 interface nsIHttpAuthenticator : nsISupports
 {
     /**
      * Upon receipt of a server challenge, this function is called to determine
      * whether or not the current user identity has been rejected.  If true,
      * then the user will be prompted by the channel to enter (or revise) their
      * identity.  Following this, generateCredentials will be called.
      *
@@ -49,16 +51,64 @@ interface nsIHttpAuthenticator : nsISupp
                            in    string       aChallenge,
                            in    boolean      aProxyAuth,
                            inout nsISupports  aSessionState,
                            inout nsISupports  aContinuationState,
                            out   boolean      aInvalidatesIdentity);
 
     /**
      * Called to generate the authentication credentials for a particular
+     * server/proxy challenge asynchronously. Credentials will be sent back
+     * to the server via an Authorization/Proxy-Authorization header.
+     *
+     * @param aChannel
+     *        the http channel requesting credentials
+     * @param aCallback
+     *        callback function to be called when credentials are available
+     * @param aChallenge
+     *        the challenge from the WWW-Authenticate/Proxy-Authenticate
+     *        server response header.  (possibly from the auth cache.)
+     * @param aProxyAuth
+     *        flag indicating whether or not aChallenge is from a proxy.
+     * @param aDomain
+     *        string containing the domain name (if appropriate)
+     * @param aUser
+     *        string containing the user name
+     * @param aPassword
+     *        string containing the password
+     * @param aSessionState
+     *        state stored along side the user's identity in the auth cache
+     *        for the lifetime of the browser session.  if a new auth cache
+     *        entry is created for this challenge, then this parameter will
+     *        be null.  on return, the result will be stored in the new auth
+     *        cache entry.  this parameter is non-null when an auth cache entry
+     *        is being reused. currently modification of session state is not
+     *        communicated to caller, thus caching credentials obtained by
+     *        asynchronous way is not supported.
+     * @param aContinuationState
+     *        state held by the channel between consecutive calls to
+     *        generateCredentials, assuming multiple calls are required
+     *        to authenticate.  this state is held for at most the lifetime of
+     *        the channel.
+     * @pram aCancel
+     *        returns cancellable runnable object which caller can use to cancel
+     *        calling aCallback when finished.
+     */
+    void generateCredentialsAsync(in    nsIHttpAuthenticableChannel aChannel,
+                                  in    nsIHttpAuthenticatorCallback aCallback,
+                                  in    string         aChallenge,
+                                  in    boolean        aProxyAuth,
+                                  in    wstring        aDomain,
+                                  in    wstring        aUser,
+                                  in    wstring        aPassword,
+                                  in    nsISupports    aSessionState,
+                                  in    nsISupports    aContinuationState,
+                                  out   nsICancelable  aCancel);
+    /**
+     * Called to generate the authentication credentials for a particular
      * server/proxy challenge.  This is the value that will be sent back
      * to the server via an Authorization/Proxy-Authorization header.
      *
      * This function may be called using a cached challenge provided the
      * authenticator sets the REUSABLE_CHALLENGE flag.
      *
      * @param aChannel
      *        the http channel requesting credentials