michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG michael@0: #endif michael@0: michael@0: #include "prlog.h" michael@0: #include "nsAsyncRedirectVerifyHelper.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsNetUtil.h" michael@0: michael@0: #include "nsIOService.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIHttpChannelInternal.h" michael@0: #include "nsIAsyncVerifyRedirectCallback.h" michael@0: michael@0: #undef LOG michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo * michael@0: GetRedirectLog() michael@0: { michael@0: static PRLogModuleInfo *sLog; michael@0: if (!sLog) michael@0: sLog = PR_NewLogModule("nsRedirect"); michael@0: return sLog; michael@0: } michael@0: #define LOG(args) PR_LOG(GetRedirectLog(), PR_LOG_DEBUG, args) michael@0: #else michael@0: #define LOG(args) michael@0: #endif michael@0: michael@0: NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper, michael@0: nsIAsyncVerifyRedirectCallback, michael@0: nsIRunnable) michael@0: michael@0: class nsAsyncVerifyRedirectCallbackEvent : public nsRunnable { michael@0: public: michael@0: nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback *cb, michael@0: nsresult result) michael@0: : mCallback(cb), mResult(result) { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: LOG(("nsAsyncVerifyRedirectCallbackEvent::Run() " michael@0: "callback to %p with result %x", michael@0: mCallback.get(), mResult)); michael@0: (void) mCallback->OnRedirectVerifyCallback(mResult); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsCOMPtr mCallback; michael@0: nsresult mResult; michael@0: }; michael@0: michael@0: nsAsyncRedirectVerifyHelper::nsAsyncRedirectVerifyHelper() michael@0: : mCallbackInitiated(false), michael@0: mExpectedCallbacks(0), michael@0: mResult(NS_OK) michael@0: { michael@0: } michael@0: michael@0: nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper() michael@0: { michael@0: NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0, michael@0: "Did not receive all required callbacks!"); michael@0: } michael@0: michael@0: nsresult michael@0: nsAsyncRedirectVerifyHelper::Init(nsIChannel* oldChan, nsIChannel* newChan, michael@0: uint32_t flags, bool synchronize) michael@0: { michael@0: LOG(("nsAsyncRedirectVerifyHelper::Init() " michael@0: "oldChan=%p newChan=%p", oldChan, newChan)); michael@0: mOldChan = oldChan; michael@0: mNewChan = newChan; michael@0: mFlags = flags; michael@0: mCallbackThread = do_GetCurrentThread(); michael@0: michael@0: if (synchronize) michael@0: mWaitingForRedirectCallback = true; michael@0: michael@0: nsresult rv; michael@0: rv = NS_DispatchToMainThread(this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (synchronize) { michael@0: nsIThread *thread = NS_GetCurrentThread(); michael@0: while (mWaitingForRedirectCallback) { michael@0: if (!NS_ProcessNextEvent(thread)) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result) michael@0: { michael@0: LOG(("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() " michael@0: "result=%x expectedCBs=%u mResult=%x", michael@0: result, mExpectedCallbacks, mResult)); michael@0: michael@0: --mExpectedCallbacks; michael@0: michael@0: // If response indicates failure we may call back immediately michael@0: if (NS_FAILED(result)) { michael@0: // We chose to store the first failure-value (as opposed to the last) michael@0: if (NS_SUCCEEDED(mResult)) michael@0: mResult = result; michael@0: michael@0: // If InitCallback() has been called, just invoke the callback and michael@0: // return. Otherwise it will be invoked from InitCallback() michael@0: if (mCallbackInitiated) { michael@0: ExplicitCallback(mResult); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // If the expected-counter is in balance and InitCallback() was called, all michael@0: // sinks have agreed that the redirect is ok and we can invoke our callback michael@0: if (mCallbackInitiated && mExpectedCallbacks == 0) { michael@0: ExplicitCallback(mResult); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(nsIChannelEventSink *sink, michael@0: nsIChannel *oldChannel, michael@0: nsIChannel *newChannel, michael@0: uint32_t flags) michael@0: { michael@0: LOG(("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() " michael@0: "sink=%p expectedCBs=%u mResult=%x", michael@0: sink, mExpectedCallbacks, mResult)); michael@0: michael@0: ++mExpectedCallbacks; michael@0: michael@0: if (IsOldChannelCanceled()) { michael@0: LOG((" old channel has been canceled, cancel the redirect by " michael@0: "emulating OnRedirectVerifyCallback...")); michael@0: (void) OnRedirectVerifyCallback(NS_BINDING_ABORTED); michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: nsresult rv = michael@0: sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); michael@0: michael@0: LOG((" result=%x expectedCBs=%u", rv, mExpectedCallbacks)); michael@0: michael@0: // If the sink returns failure from this call the redirect is vetoed. We michael@0: // emulate a callback from the sink in this case in order to perform all michael@0: // the necessary logic. michael@0: if (NS_FAILED(rv)) { michael@0: LOG((" emulating OnRedirectVerifyCallback...")); michael@0: (void) OnRedirectVerifyCallback(rv); michael@0: } michael@0: michael@0: return rv; // Return the actual status since our caller may need it michael@0: } michael@0: michael@0: void michael@0: nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result) michael@0: { michael@0: LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " michael@0: "result=%x expectedCBs=%u mCallbackInitiated=%u mResult=%x", michael@0: result, mExpectedCallbacks, mCallbackInitiated, mResult)); michael@0: michael@0: nsCOMPtr michael@0: callback(do_QueryInterface(mOldChan)); michael@0: michael@0: if (!callback || !mCallbackThread) { michael@0: LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " michael@0: "callback=%p mCallbackThread=%p", callback.get(), mCallbackThread.get())); michael@0: return; michael@0: } michael@0: michael@0: mCallbackInitiated = false; // reset to ensure only one callback michael@0: mWaitingForRedirectCallback = false; michael@0: michael@0: // Now, dispatch the callback on the event-target which called Init() michael@0: nsRefPtr event = michael@0: new nsAsyncVerifyRedirectCallbackEvent(callback, result); michael@0: if (!event) { michael@0: NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() " michael@0: "failed creating callback event!"); michael@0: return; michael@0: } michael@0: nsresult rv = mCallbackThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() " michael@0: "failed dispatching callback event!"); michael@0: } else { michael@0: LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " michael@0: "dispatched callback event=%p", event.get())); michael@0: } michael@0: michael@0: } michael@0: michael@0: void michael@0: nsAsyncRedirectVerifyHelper::InitCallback() michael@0: { michael@0: LOG(("nsAsyncRedirectVerifyHelper::InitCallback() " michael@0: "expectedCBs=%d mResult=%x", mExpectedCallbacks, mResult)); michael@0: michael@0: mCallbackInitiated = true; michael@0: michael@0: // Invoke the callback if we are done michael@0: if (mExpectedCallbacks == 0) michael@0: ExplicitCallback(mResult); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAsyncRedirectVerifyHelper::Run() michael@0: { michael@0: /* If the channel got canceled after it fired AsyncOnChannelRedirect michael@0: * and before we got here, mostly because docloader load has been canceled, michael@0: * we must completely ignore this notification and prevent any further michael@0: * notification. michael@0: */ michael@0: if (IsOldChannelCanceled()) { michael@0: ExplicitCallback(NS_BINDING_ABORTED); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // First, the global observer michael@0: NS_ASSERTION(gIOService, "Must have an IO service at this point"); michael@0: LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService...")); michael@0: nsresult rv = gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan, michael@0: mFlags, this); michael@0: if (NS_FAILED(rv)) { michael@0: ExplicitCallback(rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Now, the per-channel observers michael@0: nsCOMPtr sink; michael@0: NS_QueryNotificationCallbacks(mOldChan, sink); michael@0: if (sink) { michael@0: LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink...")); michael@0: rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags); michael@0: } michael@0: michael@0: // All invocations to AsyncOnChannelRedirect has been done - call michael@0: // InitCallback() to flag this michael@0: InitCallback(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsAsyncRedirectVerifyHelper::IsOldChannelCanceled() michael@0: { michael@0: bool canceled; michael@0: nsCOMPtr oldChannelInternal = michael@0: do_QueryInterface(mOldChan); michael@0: if (oldChannelInternal) { michael@0: oldChannelInternal->GetCanceled(&canceled); michael@0: if (canceled) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: }