Bug 513086 - Make redirect API async, r=bz, sr=biesi

--HG--
rename : netwerk/test/unit/test_redirect_caching.js => netwerk/test/unit/test_redirect-caching_passing.js
This commit is contained in:
Honza Bambas 2010-07-28 20:33:06 +02:00
Родитель 95422562da
Коммит f56aa1a89b
33 изменённых файлов: 2083 добавлений и 146 удалений

Просмотреть файл

@ -72,6 +72,7 @@ XPIDLSRCS = \
nsIAuthPromptAdapterFactory.idl \
nsIAuthPromptCallback.idl \
nsIAsyncStreamCopier.idl \
nsIAsyncVerifyRedirectCallback.idl \
nsIBufferedStreams.idl \
nsICancelable.idl \
nsIChannelPolicy.idl \
@ -149,6 +150,7 @@ EXPORTS = \
nsURIHashKey.h \
nsReadLine.h \
nsASocketHandler.h \
nsAsyncRedirectVerifyHelper.h \
$(NULL)
include $(topsrcdir)/config/rules.mk

Просмотреть файл

@ -0,0 +1,88 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org networking code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Honza Bambas <honzab@firemni.cz>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsAsyncRedirectVerifyHelper_h
#define nsAsyncRedirectVerifyHelper_h
#include "nsIRunnable.h"
#include "nsCOMPtr.h"
class nsIChannel;
/**
* This class simplifies call of OnChannelRedirect of IOService and
* the sink bound with the channel being redirected while the result of
* redirect decision is returned through the callback.
*/
class nsAsyncRedirectVerifyHelper : public nsIRunnable
{
NS_DECL_ISUPPORTS
NS_DECL_NSIRUNNABLE
public:
/**
* Initialize and runs the chain of OnChannelRedirect calls. OldChannel
* is QI'ed for nsIAsyncVerifyRedirectCallback. The result of the redirect
* decision is passed through this interface back to the oldChannel.
*
* @param oldChan
* channel being redirected, MUST implement
* nsIAsyncVerifyRedirectCallback
* @param newChan
* target of the redirect channel
* @param flags
* redirect flags
* @param synchronize
* set to TRUE if you want the Init method wait synchronously for
* all redirect callbacks
*/
nsresult Init(nsIChannel* oldChan,
nsIChannel* newChan,
PRUint32 flags,
PRBool synchronize = PR_FALSE);
protected:
nsCOMPtr<nsIChannel> mOldChan;
nsCOMPtr<nsIChannel> mNewChan;
PRUint32 mFlags;
PRBool mWaitingForRedirectCallback;
void Callback(nsresult result);
};
#endif

Просмотреть файл

@ -0,0 +1,52 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Honza Bambas <honzab@firemni.cz>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
[scriptable, uuid(8d171460-a716-41f1-92be-8c659db39b45)]
interface nsIAsyncVerifyRedirectCallback : nsISupports
{
/**
* Complement to nsIChannelEventSink asynchronous callback. The result of
* the redirect decision is passed through this callback.
*
* @param result
* Result of the redirect veto decision. If FAILED the redirect has been
* vetoed. If SUCCEEDED the redirect has been allowed by all consumers.
*/
void onRedirectVerifyCallback(in nsresult result);
};

Просмотреть файл

@ -54,6 +54,7 @@ endif
CPPSRCS = \
nsTransportUtils.cpp \
nsAsyncStreamCopier.cpp \
nsAsyncRedirectVerifyHelper.cpp \
nsAuthInformationHolder.cpp \
nsBaseChannel.cpp \
nsBaseContentStream.cpp \

Просмотреть файл

@ -0,0 +1,127 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org networking code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Honza Bambas <honzab@firemni.cz>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsAsyncRedirectVerifyHelper.h"
#include "nsThreadUtils.h"
#include "nsNetUtil.h"
#include "nsIOService.h"
#include "nsIChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIAsyncVerifyRedirectCallback.h"
NS_IMPL_ISUPPORTS1(nsAsyncRedirectVerifyHelper, nsIRunnable)
nsresult
nsAsyncRedirectVerifyHelper::Init(nsIChannel* oldChan, nsIChannel* newChan,
PRUint32 flags, PRBool synchronize)
{
mOldChan = oldChan;
mNewChan = newChan;
mFlags = flags;
if (synchronize)
mWaitingForRedirectCallback = PR_TRUE;
nsresult rv;
rv = NS_DispatchToMainThread(this);
NS_ENSURE_SUCCESS(rv, rv);
if (synchronize) {
nsIThread *thread = NS_GetCurrentThread();
while (mWaitingForRedirectCallback) {
if (!NS_ProcessNextEvent(thread)) {
return NS_ERROR_UNEXPECTED;
}
}
}
return NS_OK;
}
void
nsAsyncRedirectVerifyHelper::Callback(nsresult result)
{
// TODO E10S OnRedirectCallback has to be called on the original process
nsCOMPtr<nsIAsyncVerifyRedirectCallback> callback(do_QueryInterface(mOldChan));
NS_ASSERTION(callback, "nsAsyncRedirectVerifyHelper: oldChannel doesn't"
" implement nsIAsyncVerifyRedirectCallback");
if (callback)
callback->OnRedirectVerifyCallback(result);
mWaitingForRedirectCallback = PR_FALSE;
}
NS_IMETHODIMP
nsAsyncRedirectVerifyHelper::Run()
{
/* If the channel got canceled after it fired AsyncOnChannelRedirect
* (bug 546606) and before we got here, mostly because docloader
* load has been canceled, we must completely ignore this notification
* and prevent any further notification.
*
* TODO Bug 546606, this must be checked before every single call!
*/
PRBool canceled;
nsCOMPtr<nsIHttpChannelInternal> oldChannelInternal =
do_QueryInterface(mOldChan);
if (oldChannelInternal) {
oldChannelInternal->GetCanceled(&canceled);
if (canceled) {
Callback(NS_BINDING_ABORTED);
return NS_OK;
}
}
// First, the global observer
NS_ASSERTION(gIOService, "Must have an IO service at this point");
nsresult rv = gIOService->OnChannelRedirect(mOldChan, mNewChan, mFlags);
if (NS_FAILED(rv)) {
Callback(rv);
return NS_OK;
}
// Now, the per-channel observers
nsCOMPtr<nsIChannelEventSink> sink;
NS_QueryNotificationCallbacks(mOldChan, sink);
if (sink)
rv = sink->OnChannelRedirect(mOldChan, mNewChan, mFlags);
Callback(rv);
return NS_OK;
}

Просмотреть файл

@ -47,6 +47,7 @@
#include "nsIStreamConverterService.h"
#include "nsIContentSniffer.h"
#include "nsChannelClassifier.h"
#include "nsAsyncRedirectVerifyHelper.h"
static PLDHashOperator
CopyProperties(const nsAString &key, nsIVariant *data, void *closure)
@ -115,49 +116,60 @@ nsBaseChannel::Redirect(nsIChannel *newChannel, PRUint32 redirectFlags,
// we support nsIHttpEventSink if we are an HTTP channel and if this is not
// an internal redirect.
// Global observers. These come first so that other observers don't see
// redirects that get aborted for security reasons anyway.
NS_ASSERTION(gIOService, "Must have an IO service");
nsresult rv = gIOService->OnChannelRedirect(this, newChannel, redirectFlags);
nsRefPtr<nsAsyncRedirectVerifyHelper> redirectCallbackHelper =
new nsAsyncRedirectVerifyHelper();
PRBool checkRedirectSynchronously = !openNewChannel;
mRedirectChannel = newChannel;
mRedirectFlags = redirectFlags;
mOpenRedirectChannel = openNewChannel;
nsresult rv = redirectCallbackHelper->Init(this, newChannel, redirectFlags,
checkRedirectSynchronously);
if (NS_FAILED(rv))
return rv;
if (checkRedirectSynchronously && NS_FAILED(mStatus))
return mStatus;
return NS_OK;
}
nsresult
nsBaseChannel::ContinueRedirect()
{
// Backwards compat for non-internal redirects from a HTTP channel.
if (!(redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
// XXX Is our http channel implementation going to derive from nsBaseChannel?
// If not, this code can be removed.
if (!(mRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface();
if (httpChannel) {
nsCOMPtr<nsIHttpEventSink> httpEventSink;
GetCallback(httpEventSink);
if (httpEventSink) {
rv = httpEventSink->OnRedirect(httpChannel, newChannel);
if (NS_FAILED(rv))
nsresult rv = httpEventSink->OnRedirect(httpChannel, mRedirectChannel);
if (NS_FAILED(rv)) {
return rv;
}
}
}
}
nsCOMPtr<nsIChannelEventSink> channelEventSink;
// Give our consumer a chance to observe/block this redirect.
GetCallback(channelEventSink);
if (channelEventSink) {
rv = channelEventSink->OnChannelRedirect(this, newChannel, redirectFlags);
if (NS_FAILED(rv))
return rv;
}
// Make sure to do this _after_ making all the OnChannelRedirect calls
newChannel->SetOriginalURI(OriginalURI());
// Make sure to do this _after_ making all the OnChannelRedirect calls
mRedirectChannel->SetOriginalURI(OriginalURI());
// If we fail to open the new channel, then we want to leave this channel
// unaffected, so we defer tearing down our channel until we have succeeded
// with the redirect.
if (openNewChannel) {
rv = newChannel->AsyncOpen(mListener, mListenerContext);
if (mOpenRedirectChannel) {
nsresult rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
if (NS_FAILED(rv))
return rv;
}
mRedirectChannel = nsnull;
// close down this channel
Cancel(NS_BINDING_REDIRECTED);
mListener = nsnull;
@ -256,20 +268,30 @@ void
nsBaseChannel::HandleAsyncRedirect(nsIChannel* newChannel)
{
NS_ASSERTION(!mPump, "Shouldn't have gotten here");
PRBool doNotify = PR_TRUE;
nsresult rv = mStatus;
if (NS_SUCCEEDED(mStatus)) {
nsresult rv = Redirect(newChannel,
nsIChannelEventSink::REDIRECT_TEMPORARY,
PR_TRUE);
if (NS_FAILED(rv))
Cancel(rv);
else
doNotify = PR_FALSE;
rv = Redirect(newChannel,
nsIChannelEventSink::REDIRECT_TEMPORARY,
PR_TRUE);
if (NS_SUCCEEDED(rv)) {
// OnRedirectVerifyCallback will be called asynchronously
return;
}
}
ContinueHandleAsyncRedirect(rv);
}
void
nsBaseChannel::ContinueHandleAsyncRedirect(nsresult result)
{
mWaitingOnAsyncRedirect = PR_FALSE;
if (doNotify) {
if (NS_FAILED(result))
Cancel(result);
if (NS_FAILED(result) && mListener) {
// Notify our consumer ourselves
mListener->OnStartRequest(this, mListenerContext);
mListener->OnStopRequest(this, mListenerContext, mStatus);
@ -306,14 +328,15 @@ nsBaseChannel::ClassifyURI()
//-----------------------------------------------------------------------------
// nsBaseChannel::nsISupports
NS_IMPL_ISUPPORTS_INHERITED6(nsBaseChannel,
NS_IMPL_ISUPPORTS_INHERITED7(nsBaseChannel,
nsHashPropertyBag,
nsIRequest,
nsIChannel,
nsIInterfaceRequestor,
nsITransportEventSink,
nsIRequestObserver,
nsIStreamListener)
nsIStreamListener,
nsIAsyncVerifyRedirectCallback)
//-----------------------------------------------------------------------------
// nsBaseChannel::nsIRequest
@ -738,3 +761,21 @@ nsBaseChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
return rv;
}
NS_IMETHODIMP
nsBaseChannel::OnRedirectVerifyCallback(nsresult result)
{
if (NS_SUCCEEDED(result))
result = ContinueRedirect();
if (NS_FAILED(result) && !mWaitingOnAsyncRedirect) {
if (NS_SUCCEEDED(mStatus))
mStatus = result;
return NS_OK;
}
if (mWaitingOnAsyncRedirect)
ContinueHandleAsyncRedirect(result);
return NS_OK;
}

Просмотреть файл

@ -52,6 +52,7 @@
#include "nsIInterfaceRequestor.h"
#include "nsIProgressEventSink.h"
#include "nsITransport.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsThreadUtils.h"
//-----------------------------------------------------------------------------
@ -70,6 +71,7 @@ class nsBaseChannel : public nsHashPropertyBag
, public nsIChannel
, public nsIInterfaceRequestor
, public nsITransportEventSink
, public nsIAsyncVerifyRedirectCallback
, private nsIStreamListener
{
public:
@ -78,6 +80,7 @@ public:
NS_DECL_NSICHANNEL
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSITRANSPORTEVENTSINK
NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
nsBaseChannel();
@ -246,6 +249,8 @@ private:
// Handle an async redirect callback. This will only be called if we
// returned success from AsyncOpen while posting a redirect runnable.
void HandleAsyncRedirect(nsIChannel* newChannel);
void ContinueHandleAsyncRedirect(nsresult result);
nsresult ContinueRedirect();
// start URI classifier if requested
void ClassifyURI();
@ -281,6 +286,7 @@ private:
nsCOMPtr<nsISupports> mSecurityInfo;
nsCOMPtr<nsIStreamListener> mListener;
nsCOMPtr<nsISupports> mListenerContext;
nsCOMPtr<nsIChannel> mRedirectChannel;
nsCString mContentType;
nsCString mContentCharset;
PRUint32 mLoadFlags;
@ -289,6 +295,8 @@ private:
PRPackedBool mSynthProgressEvents;
PRPackedBool mWasOpened;
PRPackedBool mWaitingOnAsyncRedirect;
PRPackedBool mOpenRedirectChannel;
PRUint32 mRedirectFlags;
};
#endif // !nsBaseChannel_h__

Просмотреть файл

@ -55,6 +55,7 @@ HttpBaseChannel::HttpBaseChannel()
, mPriority(PRIORITY_NORMAL)
, mCaps(0)
, mRedirectionLimit(gHttpHandler->RedirectionLimit())
, mCanceled(PR_FALSE)
, mIsPending(PR_FALSE)
, mWasOpened(PR_FALSE)
, mResponseHeadersModified(PR_FALSE)
@ -919,6 +920,13 @@ HttpBaseChannel::SetForceAllowThirdPartyCookie(PRBool aForce)
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetCanceled(PRBool *aCanceled)
{
*aCanceled = mCanceled;
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpBaseChannel::nsISupportsPriority
//-----------------------------------------------------------------------------

Просмотреть файл

@ -161,6 +161,7 @@ public:
NS_IMETHOD SetCookie(const char *aCookieHeader);
NS_IMETHOD GetForceAllowThirdPartyCookie(PRBool *aForce);
NS_IMETHOD SetForceAllowThirdPartyCookie(PRBool aForce);
NS_IMETHOD GetCanceled(PRBool *aCanceled);
// nsISupportsPriority
NS_IMETHOD GetPriority(PRInt32 *value);
@ -205,12 +206,13 @@ protected:
PRUint8 mCaps;
PRUint8 mRedirectionLimit;
PRUint8 mCanceled : 1;
PRUint8 mIsPending : 1;
PRUint8 mWasOpened : 1;
PRUint8 mResponseHeadersModified : 1;
PRUint8 mAllowPipelining : 1;
PRUint8 mForceAllowThirdPartyCookie : 1;
PRUint32 mUploadStreamHasHeaders : 1;
PRUint8 mUploadStreamHasHeaders : 1;
};

Просмотреть файл

@ -92,7 +92,6 @@ nsHttpChannel::nsHttpChannel()
, mApplyConversion(PR_TRUE)
, mCachedContentIsValid(PR_FALSE)
, mCachedContentIsPartial(PR_FALSE)
, mCanceled(PR_FALSE)
, mTransactionReplaced(PR_FALSE)
, mAuthRetryPending(PR_FALSE)
, mResuming(PR_FALSE)
@ -105,6 +104,8 @@ nsHttpChannel::nsHttpChannel()
, mLoadedFromApplicationCache(PR_FALSE)
, mTracingEnabled(PR_TRUE)
, mCustomConditionalRequest(PR_FALSE)
, mFallingBack(PR_FALSE)
, mWaitingForRedirectCallback(PR_FALSE)
, mRemoteChannel(PR_FALSE)
{
LOG(("Creating nsHttpChannel [this=%p]\n", this));
@ -299,6 +300,10 @@ nsHttpChannel::HandleAsyncNotifyListener()
void
nsHttpChannel::DoNotifyListener()
{
// Make sure mIsPending is set to PR_FALSE. At this moment we are done from
// the point of view of our consumer and we have to report our self
// as not-pending.
mIsPending = PR_FALSE;
if (mListener) {
mListener->OnStartRequest(this, mListenerContext);
mListener->OnStopRequest(this, mListenerContext, mStatus);
@ -329,15 +334,28 @@ nsHttpChannel::HandleAsyncRedirect()
// channel could have been canceled, in which case there would be no point
// in processing the redirect.
if (NS_SUCCEEDED(mStatus)) {
rv = ProcessRedirection(mResponseHead->Status());
PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
rv = AsyncProcessRedirection(mResponseHead->Status());
if (NS_FAILED(rv)) {
// If ProcessRedirection fails, then we have to send out the
// OnStart/OnStop notifications.
LOG(("ProcessRedirection failed [rv=%x]\n", rv));
mStatus = rv;
DoNotifyListener();
PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
ContinueHandleAsyncRedirect(rv);
}
}
else {
ContinueHandleAsyncRedirect(NS_OK);
}
}
nsresult
nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv)
{
if (NS_FAILED(rv)) {
// If AsyncProcessRedirection fails, then we have to send out the
// OnStart/OnStop notifications.
LOG(("ContinueHandleAsyncRedirect got failure result [rv=%x]\n", rv));
mStatus = rv;
DoNotifyListener();
}
// close the cache entry. Blow it away if we couldn't process the redirect
// for some reason (the cache entry might be corrupt).
@ -351,6 +369,8 @@ nsHttpChannel::HandleAsyncRedirect()
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, mStatus);
return NS_OK;
}
void
@ -396,21 +416,34 @@ nsHttpChannel::HandleAsyncFallback()
// channel could have been canceled, in which case there would be no point
// in processing the fallback.
if (!mCanceled) {
PRBool fallingBack;
rv = ProcessFallback(&fallingBack);
if (NS_FAILED(rv) || !fallingBack) {
// If ProcessFallback fails, then we have to send out the
// OnStart/OnStop notifications.
LOG(("ProcessFallback failed [rv=%x, %d]\n", rv, fallingBack));
mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
DoNotifyListener();
}
PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
PRBool waitingForRedirectCallback;
rv = ProcessFallback(&waitingForRedirectCallback);
if (waitingForRedirectCallback)
return;
PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
}
ContinueHandleAsyncFallback(rv);
}
nsresult
nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv)
{
if (!mCanceled && (NS_FAILED(rv) || !mFallingBack)) {
// If ProcessFallback fails, then we have to send out the
// OnStart/OnStop notifications.
LOG(("ProcessFallback failed [rv=%x, %d]\n", rv, mFallingBack));
mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
DoNotifyListener();
}
mIsPending = PR_FALSE;
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, mStatus);
return rv;
}
nsresult
@ -891,22 +924,12 @@ nsHttpChannel::ProcessResponse()
#endif
// don't store the response body for redirects
MaybeInvalidateCacheEntryForSubsequentGet();
rv = ProcessRedirection(httpStatus);
if (NS_SUCCEEDED(rv)) {
InitCacheEntry();
CloseCacheEntry(PR_FALSE);
if (mCacheForOfflineUse) {
// Store response in the offline cache
InitOfflineCacheEntry();
CloseOfflineCacheEntry();
}
}
else {
LOG(("ProcessRedirection failed [rv=%x]\n", rv));
if (mTransaction->SSLConnectFailed())
return ProcessFailedSSLConnect(httpStatus);
rv = ProcessNormal();
PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse);
rv = AsyncProcessRedirection(httpStatus);
if (NS_FAILED(rv)) {
PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse);
LOG(("AsyncProcessRedirection failed [rv=%x]\n", rv));
rv = ContinueProcessResponse(rv);
}
break;
case 304:
@ -953,6 +976,28 @@ nsHttpChannel::ProcessResponse()
return rv;
}
nsresult
nsHttpChannel::ContinueProcessResponse(nsresult rv)
{
if (NS_SUCCEEDED(rv)) {
InitCacheEntry();
CloseCacheEntry(PR_FALSE);
if (mCacheForOfflineUse) {
// Store response in the offline cache
InitOfflineCacheEntry();
CloseOfflineCacheEntry();
}
return NS_OK;
}
LOG(("ContinueProcessResponse got failure result [rv=%x]\n", rv));
if (mTransaction->SSLConnectFailed()) {
return ProcessFailedSSLConnect(mRedirectType);
}
return ProcessNormal();
}
nsresult
nsHttpChannel::ProcessNormal()
{
@ -963,18 +1008,34 @@ nsHttpChannel::ProcessNormal()
PRBool succeeded;
rv = GetRequestSucceeded(&succeeded);
if (NS_SUCCEEDED(rv) && !succeeded) {
PRBool fallingBack;
rv = ProcessFallback(&fallingBack);
if (NS_FAILED(rv)) {
DoNotifyListener();
return rv;
}
if (fallingBack) {
// Do not continue with normal processing, fallback is in
// progress now.
PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
PRBool waitingForRedirectCallback;
rv = ProcessFallback(&waitingForRedirectCallback);
if (waitingForRedirectCallback) {
// The transaction has been suspended by ProcessFallback.
return NS_OK;
}
PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
}
return ContinueProcessNormal(NS_OK);
}
nsresult
nsHttpChannel::ContinueProcessNormal(nsresult rv)
{
if (NS_FAILED(rv)) {
// Fill the failure status here, we have failed to fall back, thus we
// have to report our status as failed.
mStatus = rv;
DoNotifyListener();
return rv;
}
if (mFallingBack) {
// Do not continue with normal processing, fallback is in
// progress now.
return NS_OK;
}
// if we're here, then any byte-range requests failed to result in a partial
@ -1086,7 +1147,7 @@ nsHttpChannel::ProxyFailover()
// XXXbz so where does this codepath remove us from the loadgroup,
// exactly?
return DoReplaceWithProxy(pi);
return AsyncDoReplaceWithProxy(pi);
}
void
@ -1107,21 +1168,41 @@ nsHttpChannel::HandleAsyncReplaceWithProxy()
nsCOMPtr<nsIProxyInfo> pi;
pi.swap(mTargetProxyInfo);
if (!mCanceled) {
status = DoReplaceWithProxy(pi);
if (mLoadGroup && NS_SUCCEEDED(status)) {
mLoadGroup->RemoveRequest(this, nsnull, mStatus);
}
PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncReplaceWithProxy);
status = AsyncDoReplaceWithProxy(pi);
if (NS_SUCCEEDED(status))
return;
PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncReplaceWithProxy);
}
if (NS_FAILED(status)) {
AsyncAbort(status);
ContinueHandleAsyncReplaceWithProxy(status);
}
}
nsresult
nsHttpChannel::DoReplaceWithProxy(nsIProxyInfo* pi)
nsHttpChannel::ContinueHandleAsyncReplaceWithProxy(nsresult status)
{
LOG(("nsHttpChannel::DoReplaceWithProxy [this=%p pi=%p]", this, pi));
if (mLoadGroup && NS_SUCCEEDED(status)) {
mLoadGroup->RemoveRequest(this, nsnull, mStatus);
}
else if (NS_FAILED(status)) {
AsyncAbort(status);
}
// Return NS_OK here, even it seems to be breaking the async function stack
// contract (i.e. passing the result code to a function bellow).
// ContinueHandleAsyncReplaceWithProxy will always be at the bottom of the
// stack. If we would return the failure code, the async function stack
// logic would cancel the channel synchronously, which is undesired after
// invoking AsyncAbort above.
return NS_OK;
}
nsresult
nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo* pi)
{
LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi));
nsresult rv;
nsCOMPtr<nsIChannel> newChannel;
@ -1134,16 +1215,37 @@ nsHttpChannel::DoReplaceWithProxy(nsIProxyInfo* pi)
return rv;
// Inform consumers about this fake redirect
mRedirectChannel = newChannel;
PRUint32 flags = nsIChannelEventSink::REDIRECT_INTERNAL;
rv = gHttpHandler->OnChannelRedirect(this, newChannel, flags);
PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
if (NS_SUCCEEDED(rv))
rv = WaitForRedirectCallback();
if (NS_FAILED(rv)) {
PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
mRedirectChannel = nsnull;
}
return rv;
}
nsresult
nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv)
{
if (NS_FAILED(rv))
return rv;
NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
// Make sure to do this _after_ calling OnChannelRedirect
newChannel->SetOriginalURI(mOriginalURI);
mRedirectChannel->SetOriginalURI(mOriginalURI);
// open new channel
rv = newChannel->AsyncOpen(mListener, mListenerContext);
rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
mRedirectChannel = nsnull;
if (NS_FAILED(rv))
return rv;
@ -1405,12 +1507,13 @@ nsHttpChannel::ProcessNotModified()
}
nsresult
nsHttpChannel::ProcessFallback(PRBool *fallingBack)
nsHttpChannel::ProcessFallback(PRBool *waitingForRedirectCallback)
{
LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
nsresult rv;
*fallingBack = PR_FALSE;
*waitingForRedirectCallback = PR_FALSE;
mFallingBack = PR_FALSE;
// At this point a load has failed (either due to network problems
// or an error returned on the server). Perform an application
@ -1475,15 +1578,40 @@ nsHttpChannel::ProcessFallback(PRBool *fallingBack)
rv = newChannel->SetLoadFlags(newLoadFlags);
// Inform consumers about this fake redirect
mRedirectChannel = newChannel;
PRUint32 redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
rv = gHttpHandler->OnChannelRedirect(this, newChannel, redirectFlags);
PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
if (NS_SUCCEEDED(rv))
rv = WaitForRedirectCallback();
if (NS_FAILED(rv)) {
PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
mRedirectChannel = nsnull;
return rv;
}
// Indicate we are now waiting for the asynchronous redirect callback
// if all went OK.
*waitingForRedirectCallback = PR_TRUE;
return NS_OK;
}
nsresult
nsHttpChannel::ContinueProcessFallback(nsresult rv)
{
if (NS_FAILED(rv))
return rv;
NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
// Make sure to do this _after_ calling OnChannelRedirect
newChannel->SetOriginalURI(mOriginalURI);
rv = newChannel->AsyncOpen(mListener, mListenerContext);
mRedirectChannel->SetOriginalURI(mOriginalURI);
rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
mRedirectChannel = nsnull;
NS_ENSURE_SUCCESS(rv, rv);
// close down this channel
@ -1496,7 +1624,7 @@ nsHttpChannel::ProcessFallback(PRBool *fallingBack)
mCallbacks = nsnull;
mProgressSink = nsnull;
*fallingBack = PR_TRUE;
mFallingBack = PR_TRUE;
return NS_OK;
}
@ -2768,9 +2896,9 @@ nsHttpChannel::SetupReplacementChannel(nsIURI *newURI,
}
nsresult
nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
nsHttpChannel::AsyncProcessRedirection(PRUint32 redirectType)
{
LOG(("nsHttpChannel::ProcessRedirection [this=%p type=%u]\n",
LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n",
this, redirectType));
const char *location = mResponseHead->PeekHeader(nsHttp::Location);
@ -2792,12 +2920,12 @@ nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
return NS_ERROR_REDIRECT_LOOP;
}
mRedirectType = redirectType;
LOG(("redirecting to: %s [redirection-limit=%u]\n",
location, PRUint32(mRedirectionLimit)));
nsresult rv;
nsCOMPtr<nsIChannel> newChannel;
nsCOMPtr<nsIURI> newURI;
// create a new URI using the location header and the current URL
// as a base...
@ -2811,36 +2939,49 @@ nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
if (NS_FAILED(rv))
originCharset.Truncate();
rv = ioService->NewURI(nsDependentCString(location), originCharset.get(), mURI,
getter_AddRefs(newURI));
rv = ioService->NewURI(nsDependentCString(location),
originCharset.get(),
mURI,
getter_AddRefs(mRedirectURI));
if (NS_FAILED(rv)) return rv;
if (mApplicationCache) {
// if we are redirected to a different origin check if there is a fallback
// cache entry to fall back to. we don't care about file strict
// checking, at least mURI is not a file URI.
if (!NS_SecurityCompareURIs(mURI, newURI, PR_FALSE)) {
PRBool fallingBack;
rv = ProcessFallback(&fallingBack);
if (NS_SUCCEEDED(rv) && fallingBack) {
// do not continue with redirect processing, fallback is in
// progress now.
if (!NS_SecurityCompareURIs(mURI, mRedirectURI, PR_FALSE)) {
PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
PRBool waitingForRedirectCallback;
rv = ProcessFallback(&waitingForRedirectCallback);
if (waitingForRedirectCallback)
return NS_OK;
}
PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
}
}
return ContinueProcessRedirectionAfterFallback(NS_OK);
}
nsresult
nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv)
{
if (NS_SUCCEEDED(rv) && mFallingBack) {
// do not continue with redirect processing, fallback is in
// progress now.
return NS_OK;
}
// Kill the current cache entry if we are redirecting
// back to ourself.
PRBool redirectingBackToSameURI = PR_FALSE;
if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE) &&
NS_SUCCEEDED(mURI->Equals(newURI, &redirectingBackToSameURI)) &&
NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
redirectingBackToSameURI)
mCacheEntry->Doom();
// move the reference of the old location to the new one if the new
// one has none.
nsCOMPtr<nsIURL> newURL = do_QueryInterface(newURI);
nsCOMPtr<nsIURL> newURL = do_QueryInterface(mRedirectURI);
if (newURL) {
nsCAutoString ref;
rv = newURL->GetRef(ref);
@ -2855,31 +2996,56 @@ nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
}
// if we need to re-send POST data then be sure to ask the user first.
PRBool preserveMethod = (redirectType == 307);
PRBool preserveMethod = (mRedirectType == 307);
if (preserveMethod && mUploadStream) {
rv = PromptTempRedirect();
if (NS_FAILED(rv)) return rv;
}
rv = ioService->NewChannelFromURI(newURI, getter_AddRefs(newChannel));
nsCOMPtr<nsIIOService> ioService;
rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
if (NS_FAILED(rv)) return rv;
rv = SetupReplacementChannel(newURI, newChannel, preserveMethod);
nsCOMPtr<nsIChannel> newChannel;
rv = ioService->NewChannelFromURI(mRedirectURI, getter_AddRefs(newChannel));
if (NS_FAILED(rv)) return rv;
rv = SetupReplacementChannel(mRedirectURI, newChannel, preserveMethod);
if (NS_FAILED(rv)) return rv;
PRUint32 redirectFlags;
if (redirectType == 301) // Moved Permanently
if (mRedirectType == 301) // Moved Permanently
redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
else
redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
// verify that this is a legal redirect
rv = gHttpHandler->OnChannelRedirect(this, newChannel, redirectFlags);
mRedirectChannel = newChannel;
PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
if (NS_SUCCEEDED(rv))
rv = WaitForRedirectCallback();
if (NS_FAILED(rv)) {
PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
mRedirectChannel = nsnull;
}
return rv;
}
nsresult
nsHttpChannel::ContinueProcessRedirection(nsresult rv)
{
if (NS_FAILED(rv))
return rv;
NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
// Make sure to do this _after_ calling OnChannelRedirect
newChannel->SetOriginalURI(mOriginalURI);
mRedirectChannel->SetOriginalURI(mOriginalURI);
// And now, the deprecated way
nsCOMPtr<nsIHttpEventSink> httpEventSink;
@ -2887,15 +3053,19 @@ nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
if (httpEventSink) {
// NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
// versions.
rv = httpEventSink->OnRedirect(this, newChannel);
if (NS_FAILED(rv)) return rv;
rv = httpEventSink->OnRedirect(this, mRedirectChannel);
if (NS_FAILED(rv))
return rv;
}
// XXX we used to talk directly with the script security manager, but that
// should really be handled by the event sink implementation.
// begin loading the new channel
rv = newChannel->AsyncOpen(mListener, mListenerContext);
if (NS_FAILED(rv)) return rv;
rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
mRedirectChannel = nsnull;
if (NS_FAILED(rv))
return rv;
// close down this channel
Cancel(NS_BINDING_REDIRECTED);
@ -2978,6 +3148,7 @@ NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
//-----------------------------------------------------------------------------
@ -2992,6 +3163,9 @@ nsHttpChannel::Cancel(nsresult status)
LOG((" ignoring; already canceled\n"));
return NS_OK;
}
if (mWaitingForRedirectCallback) {
LOG(("channel canceled during wait for redirect callback"));
}
mCanceled = PR_TRUE;
mStatus = status;
if (mProxyRequest)
@ -3397,22 +3571,52 @@ nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
// on proxy errors, try to failover
if (mConnectionInfo->ProxyInfo() &&
(mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
mStatus == NS_ERROR_NET_TIMEOUT)) {
(mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
mStatus == NS_ERROR_NET_TIMEOUT)) {
PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
if (NS_SUCCEEDED(ProxyFailover()))
return NS_OK;
PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
}
// on other request errors, try to fall back
PRBool fallingBack;
if (NS_FAILED(mStatus) &&
NS_SUCCEEDED(ProcessFallback(&fallingBack)) &&
fallingBack) {
return ContinueOnStartRequest2(NS_OK);
}
nsresult
nsHttpChannel::ContinueOnStartRequest1(nsresult result)
{
// Success indicates we passed ProxyFailover, in that case we must not continue
// with this code chain.
if (NS_SUCCEEDED(result))
return NS_OK;
return ContinueOnStartRequest2(result);
}
nsresult
nsHttpChannel::ContinueOnStartRequest2(nsresult result)
{
// on other request errors, try to fall back
if (NS_FAILED(mStatus)) {
PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
PRBool waitingForRedirectCallback;
nsresult rv = ProcessFallback(&waitingForRedirectCallback);
if (waitingForRedirectCallback)
return NS_OK;
PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
}
return ContinueOnStartRequest3(NS_OK);
}
nsresult
nsHttpChannel::ContinueOnStartRequest3(nsresult result)
{
if (mFallingBack)
return NS_OK;
return CallOnStartRequest();
}
@ -4126,6 +4330,98 @@ nsHttpChannel::SetChooseApplicationCache(PRBool aChoose)
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIAsyncVerifyRedirectCallback
//-----------------------------------------------------------------------------
nsresult
nsHttpChannel::WaitForRedirectCallback()
{
nsresult rv;
if (mTransactionPump) {
rv = mTransactionPump->Suspend();
NS_ENSURE_SUCCESS(rv, rv);
}
if (mCachePump) {
rv = mCachePump->Suspend();
if (NS_FAILED(rv) && mTransactionPump) {
nsresult resume = mTransactionPump->Resume();
NS_ASSERTION(NS_SUCCEEDED(resume),
"Failed to resume transaction pump");
}
NS_ENSURE_SUCCESS(rv, rv);
}
mWaitingForRedirectCallback = PR_TRUE;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::OnRedirectVerifyCallback(nsresult result)
{
NS_ASSERTION(mWaitingForRedirectCallback,
"Someone forgot to call WaitForRedirectCallback() ?!");
mWaitingForRedirectCallback = PR_FALSE;
if (mCanceled && NS_SUCCEEDED(result))
result = NS_BINDING_ABORTED;
for (PRUint32 i = mRedirectFuncStack.Length(); i > 0;) {
--i;
// Pop the last function pushed to the stack
nsContinueRedirectionFunc func = mRedirectFuncStack[i];
mRedirectFuncStack.RemoveElementAt(mRedirectFuncStack.Length() - 1);
// Call it with the result we got from the callback or the deeper
// function call.
result = (this->*func)(result);
// If a new function has been pushed to the stack and placed us in the
// waiting state, we need to break the chain and wait for the callback
// again.
if (mWaitingForRedirectCallback)
break;
}
if (NS_FAILED(result) && !mCanceled) {
// First, cancel this channel if we are in failure state to set mStatus
// and let it be propagated to pumps.
Cancel(result);
}
if (!mWaitingForRedirectCallback) {
// We are not waiting for the callback. At this moment we must release
// reference to the redirect target channel, otherwise we may leak.
mRedirectChannel = nsnull;
}
// We always resume the pumps here. If all functions on stack have been
// called we need OnStopRequest to be triggered, and if we broke out of the
// loop above (and are thus waiting for a new callback) the suspension
// count must be balanced in the pumps.
if (mTransactionPump)
mTransactionPump->Resume();
if (mCachePump)
mCachePump->Resume();
return result;
}
void
nsHttpChannel::PushRedirectAsyncFunc(nsContinueRedirectionFunc func)
{
mRedirectFuncStack.AppendElement(func);
}
void
nsHttpChannel::PopRedirectAsyncFunc(nsContinueRedirectionFunc func)
{
NS_ASSERTION(func == mRedirectFuncStack[mRedirectFuncStack.Length() - 1],
"Trying to pop wrong method from redirect async stack!");
mRedirectFuncStack.TruncateLength(mRedirectFuncStack.Length() - 1);
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsContentEncodings <public>
//-----------------------------------------------------------------------------

Просмотреть файл

@ -48,6 +48,7 @@
#include "nsHttpTransaction.h"
#include "nsInputStreamPump.h"
#include "nsThreadUtils.h"
#include "nsTArray.h"
#include "nsIHttpEventSink.h"
#include "nsICachingChannel.h"
@ -64,6 +65,7 @@
#include "nsIHttpAuthenticableChannel.h"
#include "nsITraceableChannel.h"
#include "nsIHttpChannelAuthProvider.h"
#include "nsIAsyncVerifyRedirectCallback.h"
class nsAHttpConnection;
@ -84,6 +86,7 @@ class nsHttpChannel : public HttpBaseChannel
, public nsIHttpAuthenticableChannel
, public nsITraceableChannel
, public nsIApplicationCacheChannel
, public nsIAsyncVerifyRedirectCallback
{
public:
NS_DECL_ISUPPORTS_INHERITED
@ -100,6 +103,7 @@ public:
NS_DECL_NSITRACEABLECHANNEL
NS_DECL_NSIAPPLICATIONCACHECONTAINER
NS_DECL_NSIAPPLICATIONCACHECHANNEL
NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
// nsIHttpAuthenticableChannel. We can't use
// NS_DECL_NSIHTTPAUTHENTICABLECHANNEL because it duplicates cancel() and
@ -160,6 +164,8 @@ public: /* internal necko use only */
}
private:
typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
// AsyncCall may be used to call a member function asynchronously.
// retval isn't refcounted and is set only when event was successfully
// posted, the event is returned for the purpose of cancelling when needed
@ -176,25 +182,38 @@ private:
nsresult ApplyContentConversions();
nsresult CallOnStartRequest();
nsresult ProcessResponse();
nsresult ContinueProcessResponse(nsresult);
nsresult ProcessNormal();
nsresult ContinueProcessNormal(nsresult);
nsresult ProcessNotModified();
nsresult ProcessRedirection(PRUint32 httpStatus);
nsresult AsyncProcessRedirection(PRUint32 httpStatus);
nsresult ContinueProcessRedirection(nsresult);
nsresult ContinueProcessRedirectionAfterFallback(nsresult);
PRBool ShouldSSLProxyResponseContinue(PRUint32 httpStatus);
nsresult ProcessFailedSSLConnect(PRUint32 httpStatus);
nsresult ProcessFallback(PRBool *fallingBack);
nsresult ProcessFallback(PRBool *waitingForRedirectCallback);
nsresult ContinueProcessFallback(nsresult);
PRBool ResponseWouldVary();
nsresult ContinueOnStartRequest1(nsresult);
nsresult ContinueOnStartRequest2(nsresult);
nsresult ContinueOnStartRequest3(nsresult);
// redirection specific methods
void HandleAsyncRedirect();
nsresult ContinueHandleAsyncRedirect(nsresult);
void HandleAsyncNotModified();
void HandleAsyncFallback();
nsresult ContinueHandleAsyncFallback(nsresult);
nsresult PromptTempRedirect();
nsresult SetupReplacementChannel(nsIURI *, nsIChannel *, PRBool preserveMethod);
// proxy specific methods
nsresult ProxyFailover();
nsresult DoReplaceWithProxy(nsIProxyInfo *);
nsresult AsyncDoReplaceWithProxy(nsIProxyInfo *);
nsresult ContinueDoReplaceWithProxy(nsresult);
void HandleAsyncReplaceWithProxy();
nsresult ContinueHandleAsyncReplaceWithProxy(nsresult);
nsresult ResolveProxy();
// cache specific methods
@ -276,11 +295,14 @@ private:
// cache entry.
nsCString mFallbackKey;
nsCOMPtr<nsIURI> mRedirectURI;
nsCOMPtr<nsIChannel> mRedirectChannel;
PRUint32 mRedirectType;
// state flags
PRUint32 mApplyConversion : 1;
PRUint32 mCachedContentIsValid : 1;
PRUint32 mCachedContentIsPartial : 1;
PRUint32 mCanceled : 1;
PRUint32 mTransactionReplaced : 1;
PRUint32 mAuthRetryPending : 1;
PRUint32 mResuming : 1;
@ -300,6 +322,8 @@ private:
// headers. In such a case we must not override them in the cache code
// and also we want to pass possible 304 code response through.
PRUint32 mCustomConditionalRequest : 1;
PRUint32 mFallingBack : 1;
PRUint32 mWaitingForRedirectCallback : 1;
// True iff this channel is servicing a remote HttpChannelChild
PRUint32 mRemoteChannel : 1;
@ -326,6 +350,12 @@ private:
PRPackedBool mReady;
};
nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
nsresult WaitForRedirectCallback();
void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
};
#endif // nsHttpChannel_h__

Просмотреть файл

@ -74,6 +74,7 @@
#include "nsQuickSort.h"
#include "nsNetUtil.h"
#include "nsIOService.h"
#include "nsAsyncRedirectVerifyHelper.h"
#include "nsIXULAppInfo.h"
@ -530,22 +531,14 @@ nsHttpHandler::NotifyObservers(nsIHttpChannel *chan, const char *event)
}
nsresult
nsHttpHandler::OnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
nsHttpHandler::AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
PRUint32 flags)
{
// First, the global observer
NS_ASSERTION(gIOService, "Must have an IO service at this point");
nsresult rv = gIOService->OnChannelRedirect(oldChan, newChan, flags);
if (NS_FAILED(rv))
return rv;
// TODO E10S This helper has to be initialized on the other process
nsRefPtr<nsAsyncRedirectVerifyHelper> redirectCallbackHelper =
new nsAsyncRedirectVerifyHelper();
// Now, the per-channel observers
nsCOMPtr<nsIChannelEventSink> sink;
NS_QueryNotificationCallbacks(oldChan, sink);
if (sink)
rv = sink->OnChannelRedirect(oldChan, newChan, flags);
return rv;
return redirectCallbackHelper->Init(oldChan, newChan, flags);
}
/* static */ nsresult

Просмотреть файл

@ -195,7 +195,7 @@ public:
// Called by channels before a redirect happens. This notifies both the
// channel's and the global redirect observers.
nsresult OnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
nsresult AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
PRUint32 flags);
// Called by the channel when the response is read from the cache without

Просмотреть файл

@ -45,7 +45,7 @@ interface nsIProxyInfo;
* using any feature exposed by this interface, be aware that this interface
* will change and you will be broken. You have been warned.
*/
[scriptable, uuid(0eb66361-faaa-4e52-8c7e-6c25f11f8e3c)]
[scriptable, uuid(91dbb42a-dffc-4f47-8b27-9579c0d92c3f)]
interface nsIHttpChannelInternal : nsISupports
{
/**
@ -83,4 +83,9 @@ interface nsIHttpChannelInternal : nsISupports
* wouldn't be.
*/
attribute boolean forceAllowThirdPartyCookie;
/**
* Returns true iff the channel has been canceled.
*/
readonly attribute boolean canceled;
};

Просмотреть файл

@ -66,8 +66,14 @@ ChannelListener.prototype = {
this._got_onstartrequest = true;
request.QueryInterface(Components.interfaces.nsIChannel);
this._contentLen = request.contentLength;
if (this._contentLen == -1)
try {
this._contentLen = request.contentLength;
}
catch (ex) {
if (!(this._flags & CL_EXPECT_FAILURE))
do_throw("Could not get contentLength");
}
if (this._contentLen == -1 && !(this._flags & CL_EXPECT_FAILURE))
do_throw("Content length is unknown in onStartRequest!");
} catch (ex) {
do_throw("Error in onStartRequest: " + ex);
@ -121,3 +127,31 @@ ChannelListener.prototype = {
}
}
};
var ES_ABORT_REDIRECT = 0x01;
function ChannelEventSink(flags)
{
this._flags = flags;
}
ChannelEventSink.prototype = {
QueryInterface: function(iid) {
if (iid.equals(Ci.nsIInterfaceRequestor) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
getInterface: function(iid) {
if (iid.equals(Ci.nsIChannelEventSink))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
onChannelRedirect: function(oldChannel, newChannel, flags) {
if (this._flags & ES_ABORT_REDIRECT) {
throw Cr.NS_BINDING_ABORTED;
}
}
};

Просмотреть файл

@ -0,0 +1,100 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/redirect/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
var cacheUpdateObserver = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
function make_uri(url) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newURI(url, null, null);
}
var responseBody = "Content body";
// start the test with loading this master entry referencing the manifest
function masterEntryHandler(metadata, response)
{
var masterEntryContent = "<html manifest='/manifest'></html>";
response.setHeader("Content-Type", "text/html");
response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
}
// manifest defines fallback namespace from any /redirect path to /content
function manifestHandler(metadata, response)
{
var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
response.setHeader("Content-Type", "text/cache-manifest");
response.bodyOutputStream.write(manifestContent, manifestContent.length);
}
// content handler correctly returns some plain text data
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
// finally check we got fallback content
function finish_test(request, buffer)
{
do_check_eq(buffer, "");
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
httpServer.registerPathHandler("/manifest", manifestHandler);
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(4444);
var pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
var uri = make_uri("http://localhost:4444");
if (pm.testPermission(uri, "offline-app") != 0) {
dump("Previous test failed to clear offline-app permission! Expect failures.\n");
}
pm.add(uri, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
var ps = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
dump(ps.getBoolPref("browser.cache.offline.enable"));
ps.setBoolPref("browser.cache.offline.enable", true);
ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
cacheUpdateObserver = {observe: function() {
dump("got offline-cache-update-completed\n");
// offline cache update completed.
// In this test the randomURI doesn't exists
var chan = make_channel(randomURI);
chan.loadFlags = (Ci.nsIRequest.INHIBIT_CACHING |
Ci.nsIRequest.LOAD_FROM_CACHE |
Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE);
chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
chanac.chooseApplicationCache = true;
chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE), null);
}}
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
us.scheduleUpdate(make_uri("http://localhost:4444/manifest"),
make_uri("http://localhost:4444/masterEntry"));
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,99 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/redirect/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
var cacheUpdateObserver = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
function make_uri(url) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newURI(url, null, null);
}
var responseBody = "Content body";
// start the test with loading this master entry referencing the manifest
function masterEntryHandler(metadata, response)
{
var masterEntryContent = "<html manifest='/manifest'></html>";
response.setHeader("Content-Type", "text/html");
response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
}
// manifest defines fallback namespace from any /redirect path to /content
function manifestHandler(metadata, response)
{
var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
response.setHeader("Content-Type", "text/cache-manifest");
response.bodyOutputStream.write(manifestContent, manifestContent.length);
}
// content handler correctly returns some plain text data
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
// finally check we got fallback content
function finish_test(request, buffer)
{
do_check_eq(buffer, responseBody);
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
httpServer.registerPathHandler("/manifest", manifestHandler);
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(4444);
var pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
var uri = make_uri("http://localhost:4444");
if (pm.testPermission(uri, "offline-app") != 0) {
dump("Previous test failed to clear offline-app permission! Expect failures.\n");
}
pm.add(uri, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
var ps = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
dump(ps.getBoolPref("browser.cache.offline.enable"));
ps.setBoolPref("browser.cache.offline.enable", true);
ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
cacheUpdateObserver = {observe: function() {
dump("got offline-cache-update-completed\n");
// offline cache update completed.
// In this test the randomURI doesn't exists
var chan = make_channel(randomURI);
chan.loadFlags = (Ci.nsIRequest.INHIBIT_CACHING |
Ci.nsIRequest.LOAD_FROM_CACHE |
Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE);
var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
chanac.chooseApplicationCache = true;
chan.asyncOpen(new ChannelListener(finish_test), null);
}}
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
us.scheduleUpdate(make_uri("http://localhost:4444/manifest"),
make_uri("http://localhost:4444/masterEntry"));
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,104 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/redirect/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
var cacheUpdateObserver = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
function make_uri(url) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newURI(url, null, null);
}
const responseBody = "Content body";
// start the test with loading this master entry referencing the manifest
function masterEntryHandler(metadata, response)
{
var masterEntryContent = "<html manifest='/manifest'></html>";
response.setHeader("Content-Type", "text/html");
response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
}
// manifest defines fallback namespace from any /redirect path to /content
function manifestHandler(metadata, response)
{
var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
response.setHeader("Content-Type", "text/cache-manifest");
response.bodyOutputStream.write(manifestContent, manifestContent.length);
}
// content handler correctly returns some plain text data
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
// redirect handler returns redirect
function redirectHandler(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 301, "Moved");
response.setHeader("Location", "http://example.com/", false);
}
// finally check we got fallback content
function finish_test(request, buffer)
{
do_check_eq(buffer, "");
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
httpServer.registerPathHandler("/manifest", manifestHandler);
httpServer.registerPathHandler("/content", contentHandler);
httpServer.registerPathHandler(randomPath, redirectHandler);
httpServer.start(4444);
var pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
var uri = make_uri("http://localhost:4444");
if (pm.testPermission(uri, "offline-app") != 0) {
dump("Previous test failed to clear offline-app permission! Expect failures.\n");
}
pm.add(uri, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
var ps = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
dump(ps.getBoolPref("browser.cache.offline.enable"));
ps.setBoolPref("browser.cache.offline.enable", true);
ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
cacheUpdateObserver = {observe: function() {
dump("got offline-cache-update-completed\n");
// offline cache update completed.
var chan = make_channel(randomURI);
chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
chanac.chooseApplicationCache = true;
chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE), null);
}}
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
us.scheduleUpdate(make_uri("http://localhost:4444/manifest"),
make_uri("http://localhost:4444/masterEntry"));
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,103 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/redirect/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
var cacheUpdateObserver = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
function make_uri(url) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newURI(url, null, null);
}
var responseBody = "Content body";
// start the test with loading this master entry referencing the manifest
function masterEntryHandler(metadata, response)
{
var masterEntryContent = "<html manifest='/manifest'></html>";
response.setHeader("Content-Type", "text/html");
response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
}
// manifest defines fallback namespace from any /redirect path to /content
function manifestHandler(metadata, response)
{
var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
response.setHeader("Content-Type", "text/cache-manifest");
response.bodyOutputStream.write(manifestContent, manifestContent.length);
}
// content handler correctly returns some plain text data
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
// redirect handler returns redirect
function redirectHandler(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 301, "Moved");
response.setHeader("Location", "http://example.com/", false);
}
// finally check we got fallback content
function finish_test(request, buffer)
{
do_check_eq(buffer, responseBody);
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
httpServer.registerPathHandler("/manifest", manifestHandler);
httpServer.registerPathHandler("/content", contentHandler);
httpServer.registerPathHandler(randomPath, redirectHandler);
httpServer.start(4444);
var pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
var uri = make_uri("http://localhost:4444");
if (pm.testPermission(uri, "offline-app") != 0) {
dump("Previous test failed to clear offline-app permission! Expect failures.\n");
}
pm.add(uri, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
var ps = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
dump(ps.getBoolPref("browser.cache.offline.enable"));
ps.setBoolPref("browser.cache.offline.enable", true);
ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
cacheUpdateObserver = {observe: function() {
dump("got offline-cache-update-completed\n");
// offline cache update completed.
var chan = make_channel(randomURI);
var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
chanac.chooseApplicationCache = true;
chan.asyncOpen(new ChannelListener(finish_test), null);
}}
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
us.scheduleUpdate(make_uri("http://localhost:4444/manifest"),
make_uri("http://localhost:4444/masterEntry"));
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,107 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/redirect/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
var cacheUpdateObserver = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
function make_uri(url) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newURI(url, null, null);
}
var responseBody = "Content body";
// start the test with loading this master entry referencing the manifest
function masterEntryHandler(metadata, response)
{
var masterEntryContent = "<html manifest='/manifest'></html>";
response.setHeader("Content-Type", "text/html");
response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
}
// manifest defines fallback namespace from any /redirect path to /content
function manifestHandler(metadata, response)
{
var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
response.setHeader("Content-Type", "text/cache-manifest");
response.bodyOutputStream.write(manifestContent, manifestContent.length);
}
// content handler correctly returns some plain text data
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
// redirect handler returns redirect
function redirectHandler(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 301, "Moved");
response.setHeader("Location", "http://example.com/", false);
}
// finally check we got fallback content
function finish_test(request, buffer)
{
do_check_eq(buffer, "");
do_test_finished();
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
httpServer.registerPathHandler("/manifest", manifestHandler);
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(4444);
var pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
var uri = make_uri("http://localhost:4444");
if (pm.testPermission(uri, "offline-app") != 0) {
dump("Previous test failed to clear offline-app permission! Expect failures.\n");
}
pm.add(uri, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
var ps = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
dump(ps.getBoolPref("browser.cache.offline.enable"));
ps.setBoolPref("browser.cache.offline.enable", true);
ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
cacheUpdateObserver = {observe: function() {
dump("got offline-cache-update-completed\n");
// offline cache update completed.
httpServer.stop(function() {
// Now shut the server down to have an error in onStartRequest
var chan = make_channel(randomURI);
chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
chanac.chooseApplicationCache = true;
chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE), null);
});
}}
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
us.scheduleUpdate(make_uri("http://localhost:4444/manifest"),
make_uri("http://localhost:4444/masterEntry"));
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,106 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/redirect/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
var cacheUpdateObserver = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
function make_uri(url) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newURI(url, null, null);
}
var responseBody = "Content body";
// start the test with loading this master entry referencing the manifest
function masterEntryHandler(metadata, response)
{
var masterEntryContent = "<html manifest='/manifest'></html>";
response.setHeader("Content-Type", "text/html");
response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
}
// manifest defines fallback namespace from any /redirect path to /content
function manifestHandler(metadata, response)
{
var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
response.setHeader("Content-Type", "text/cache-manifest");
response.bodyOutputStream.write(manifestContent, manifestContent.length);
}
// content handler correctly returns some plain text data
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
// redirect handler returns redirect
function redirectHandler(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 301, "Moved");
response.setHeader("Location", "http://example.com/", false);
}
// finally check we got fallback content
function finish_test(request, buffer)
{
do_check_eq(buffer, responseBody);
do_test_finished();
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
httpServer.registerPathHandler("/manifest", manifestHandler);
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(4444);
var pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
var uri = make_uri("http://localhost:4444");
if (pm.testPermission(uri, "offline-app") != 0) {
dump("Previous test failed to clear offline-app permission! Expect failures.\n");
}
pm.add(uri, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
var ps = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
dump(ps.getBoolPref("browser.cache.offline.enable"));
ps.setBoolPref("browser.cache.offline.enable", true);
ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
cacheUpdateObserver = {observe: function() {
dump("got offline-cache-update-completed\n");
// offline cache update completed.
httpServer.stop(function() {
// Now shut the server down to have an error in onstartrequest
var chan = make_channel(randomURI);
var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
chanac.chooseApplicationCache = true;
chan.asyncOpen(new ChannelListener(finish_test), null);
});
}}
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
us.scheduleUpdate(make_uri("http://localhost:4444/manifest"),
make_uri("http://localhost:4444/masterEntry"));
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,103 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/error/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
var cacheUpdateObserver = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
function make_uri(url) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newURI(url, null, null);
}
var responseBody = "Content body";
// start the test with loading this master entry referencing the manifest
function masterEntryHandler(metadata, response)
{
var masterEntryContent = "<html manifest='/manifest'></html>";
response.setHeader("Content-Type", "text/html");
response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
}
// manifest defines fallback namespace from any /error path to /content
function manifestHandler(metadata, response)
{
var manifestContent = "CACHE MANIFEST\nFALLBACK:\nerror /content\n";
response.setHeader("Content-Type", "text/cache-manifest");
response.bodyOutputStream.write(manifestContent, manifestContent.length);
}
// content handler correctly returns some plain text data
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
// error handler returns error
function errorHandler(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 404, "Bad request");
}
// finally check we got fallback content
function finish_test(request, buffer)
{
do_check_eq(buffer, "");
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
httpServer.registerPathHandler("/manifest", manifestHandler);
httpServer.registerPathHandler("/content", contentHandler);
httpServer.registerPathHandler(randomPath, errorHandler);
httpServer.start(4444);
var pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
var uri = make_uri("http://localhost:4444");
if (pm.testPermission(uri, "offline-app") != 0) {
dump("Previous test failed to clear offline-app permission! Expect failures.\n");
}
pm.add(uri, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
var ps = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
dump(ps.getBoolPref("browser.cache.offline.enable"));
ps.setBoolPref("browser.cache.offline.enable", true);
ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
cacheUpdateObserver = {observe: function() {
dump("got offline-cache-update-completed\n");
// offline cache update completed.
var chan = make_channel(randomURI);
chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
chanac.chooseApplicationCache = true;
chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE), null);
}}
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
us.scheduleUpdate(make_uri("http://localhost:4444/manifest"),
make_uri("http://localhost:4444/masterEntry"));
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,102 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/error/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
var cacheUpdateObserver = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
function make_uri(url) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newURI(url, null, null);
}
var responseBody = "Content body";
// start the test with loading this master entry referencing the manifest
function masterEntryHandler(metadata, response)
{
var masterEntryContent = "<html manifest='/manifest'></html>";
response.setHeader("Content-Type", "text/html");
response.bodyOutputStream.write(masterEntryContent, masterEntryContent.length);
}
// manifest defines fallback namespace from any /error path to /content
function manifestHandler(metadata, response)
{
var manifestContent = "CACHE MANIFEST\nFALLBACK:\nerror /content\n";
response.setHeader("Content-Type", "text/cache-manifest");
response.bodyOutputStream.write(manifestContent, manifestContent.length);
}
// content handler correctly returns some plain text data
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
// error handler returns error
function errorHandler(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 404, "Bad request");
}
// finally check we got fallback content
function finish_test(request, buffer)
{
do_check_eq(buffer, responseBody);
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
httpServer.registerPathHandler("/manifest", manifestHandler);
httpServer.registerPathHandler("/content", contentHandler);
httpServer.registerPathHandler(randomPath, errorHandler);
httpServer.start(4444);
var pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
var uri = make_uri("http://localhost:4444");
if (pm.testPermission(uri, "offline-app") != 0) {
dump("Previous test failed to clear offline-app permission! Expect failures.\n");
}
pm.add(uri, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
var ps = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
dump(ps.getBoolPref("browser.cache.offline.enable"));
ps.setBoolPref("browser.cache.offline.enable", true);
ps.setComplexValue("browser.cache.offline.parent_directory", Ci.nsILocalFile, do_get_profile());
cacheUpdateObserver = {observe: function() {
dump("got offline-cache-update-completed\n");
// offline cache update completed.
var chan = make_channel(randomURI);
var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
chanac.chooseApplicationCache = true;
chan.asyncOpen(new ChannelListener(finish_test), null);
}}
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.addObserver(cacheUpdateObserver, "offline-cache-update-completed", false);
var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
us.scheduleUpdate(make_uri("http://localhost:4444/manifest"),
make_uri("http://localhost:4444/masterEntry"));
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,62 @@
do_load_httpd_js();
var httpServer = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
const responseBody = "response body";
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
function finish_test(request, buffer)
{
do_check_eq(buffer, "");
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(4444);
var nc = new ChannelEventSink();
var on_modify_request_count = 0;
modifyrequestobserver = {observe: function() {
// We get 2 on-modify-request notifications:
// 1. when proxy service resolves the proxy settings from PAC function
// 2. when we try to fail over the first proxy (moving to the second one)
//
// In the second case we want to cancel the proxy engage, so, do not allow
// redirects from now.
if (++on_modify_request_count == 2)
nc._flags = ES_ABORT_REDIRECT;
}}
var os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
os.addObserver(modifyrequestobserver, "http-on-modify-request", false);
var prefserv = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService);
var prefs = prefserv.getBranch("network.proxy.");
prefs.setIntPref("type", 2);
prefs.setCharPref("autoconfig_url", "data:text/plain," +
"function FindProxyForURL(url, host) {return 'PROXY a_non_existent_domain_x7x6c572v:80; PROXY localhost:4444';}"
);
var chan = make_channel("http://localhost:4444/content");
chan.notificationCallbacks = nc;
chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE), null);
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,42 @@
do_load_httpd_js();
var httpServer = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
const responseBody = "response body";
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
function finish_test(request, buffer)
{
do_check_eq(buffer, responseBody);
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(4444);
var prefserv = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService);
var prefs = prefserv.getBranch("network.proxy.");
prefs.setIntPref("type", 2);
prefs.setCharPref("autoconfig_url", "data:text/plain," +
"function FindProxyForURL(url, host) {return 'PROXY a_non_existent_domain_x7x6c572v:80; PROXY localhost:4444';}"
);
var chan = make_channel("http://localhost:4444/content");
chan.asyncOpen(new ChannelListener(finish_test, null), null);
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,43 @@
do_load_httpd_js();
var httpServer = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
const responseBody = "response body";
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
function finish_test(request, buffer)
{
do_check_eq(buffer, "");
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(4444);
var prefserv = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService);
var prefs = prefserv.getBranch("network.proxy.");
prefs.setIntPref("type", 2);
prefs.setCharPref("autoconfig_url", "data:text/plain," +
"function FindProxyForURL(url, host) {return 'PROXY localhost:4444';}"
);
var chan = make_channel("http://localhost:4444/content");
chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE), null);
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,42 @@
do_load_httpd_js();
var httpServer = null;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
const responseBody = "response body";
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
function finish_test(request, buffer)
{
do_check_eq(buffer, responseBody);
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(4444);
var prefserv = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService);
var prefs = prefserv.getBranch("network.proxy.");
prefs.setIntPref("type", 2);
prefs.setCharPref("autoconfig_url", "data:text/plain," +
"function FindProxyForURL(url, host) {return 'PROXY localhost:4444';}"
);
var chan = make_channel("http://localhost:4444/content");
chan.asyncOpen(new ChannelListener(finish_test, null), null);
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,62 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/redirect/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
const responseBody = "response body";
function redirectHandler(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 301, "Moved");
response.setHeader("Location", "http://localhost:4444/content", false);
response.setHeader("Cache-control", "max-age=1000", false);
return;
}
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
function firstTimeThrough(request, buffer)
{
do_check_eq(buffer, responseBody);
var chan = make_channel(randomURI);
chan.asyncOpen(new ChannelListener(secondTimeThrough, null), null);
}
function secondTimeThrough(request, buffer)
{
do_check_eq(buffer, responseBody);
var chan = make_channel(randomURI);
chan.loadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE), null);
}
function finish_test(request, buffer)
{
do_check_eq(buffer, "");
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler(randomPath, redirectHandler);
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(4444);
var chan = make_channel(randomURI);
chan.asyncOpen(new ChannelListener(firstTimeThrough, null), null);
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,43 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/redirect/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
function redirectHandler(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 301, "Moved");
response.setHeader("Location", "httpx://localhost:4444/content", false);
response.setHeader("Cache-control", "max-age=1000", false);
}
function firstTimeThrough(request, buffer)
{
var chan = make_channel(randomURI);
chan.loadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE), null);
}
function finish_test(request, buffer)
{
do_check_eq(buffer, "");
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler(randomPath, redirectHandler);
httpServer.start(4444);
var chan = make_channel(randomURI);
chan.asyncOpen(new ChannelListener(firstTimeThrough), null);
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,45 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/redirect/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
const responseBody = "response body";
function redirectHandler(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 301, "Moved");
response.setHeader("Location", "http://localhost:4444/content", false);
}
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
function finish_test(request, buffer)
{
do_check_eq(buffer, "");
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler(randomPath, redirectHandler);
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(4444);
var chan = make_channel(randomURI);
chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
chan.asyncOpen(new ChannelListener(finish_test, null), null);
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,36 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/redirect/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
function redirectHandler(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 301, "Moved");
response.setHeader("Location", "httpx://localhost:4444/content", false);
response.setHeader("Cache-Control", "no-cache", false);
}
function finish_test(request, buffer)
{
do_check_eq(buffer, "");
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler(randomPath, redirectHandler);
httpServer.start(4444);
var chan = make_channel(randomURI);
chan.asyncOpen(new ChannelListener(finish_test), null);
do_test_pending();
}

Просмотреть файл

@ -0,0 +1,51 @@
do_load_httpd_js();
var httpServer = null;
// Need to randomize, because apparently no one clears our cache
var randomPath = "/redirect/" + Math.random();
var randomURI = "http://localhost:4444" + randomPath;
function make_channel(url, callback, ctx) {
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
return ios.newChannel(url, "", null);
}
const responseBody = "response body";
function redirectHandler(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 301, "Moved");
response.setHeader("Location", "http://localhost:4444/content", false);
}
function contentHandler(metadata, response)
{
response.setHeader("Content-Type", "text/plain");
response.bodyOutputStream.write(responseBody, responseBody.length);
}
function firstTimeThrough(request, buffer)
{
do_check_eq(buffer, responseBody);
var chan = make_channel(randomURI);
chan.asyncOpen(new ChannelListener(finish_test, null), null);
}
function finish_test(request, buffer)
{
do_check_eq(buffer, responseBody);
httpServer.stop(do_test_finished);
}
function run_test()
{
httpServer = new nsHttpServer();
httpServer.registerPathHandler(randomPath, redirectHandler);
httpServer.registerPathHandler("/content", contentHandler);
httpServer.start(4444);
var chan = make_channel(randomURI);
chan.asyncOpen(new ChannelListener(firstTimeThrough, null), null);
do_test_pending();
}