mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 536321 - e10s HTTP: suspend/resume. r=dwitte
This commit is contained in:
parent
db31d25583
commit
00afa443f6
@ -51,12 +51,14 @@
|
||||
#include "nsIEncodedChannel.h"
|
||||
#include "nsIResumableChannel.h"
|
||||
#include "nsIApplicationCacheChannel.h"
|
||||
#include "nsEscape.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
HttpBaseChannel::HttpBaseChannel()
|
||||
: mStatus(NS_OK)
|
||||
: mStartPos(LL_MAXUINT)
|
||||
, mStatus(NS_OK)
|
||||
, mLoadFlags(LOAD_NORMAL)
|
||||
, mPriority(PRIORITY_NORMAL)
|
||||
, mCaps(0)
|
||||
@ -950,6 +952,55 @@ HttpBaseChannel::AdjustPriority(PRInt32 delta)
|
||||
return SetPriority(mPriority + delta);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// HttpBaseChannel::nsIResumableChannel
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
NS_IMETHODIMP
|
||||
HttpBaseChannel::GetEntityID(nsACString& aEntityID)
|
||||
{
|
||||
// Don't return an entity ID for Non-GET requests which require
|
||||
// additional data
|
||||
if (mRequestHead.Method() != nsHttp::Get) {
|
||||
return NS_ERROR_NOT_RESUMABLE;
|
||||
}
|
||||
|
||||
// Don't return an entity if the server sent the following header:
|
||||
// Accept-Ranges: none
|
||||
// Not sending the Accept-Ranges header means we can still try
|
||||
// sending range requests.
|
||||
const char* acceptRanges =
|
||||
mResponseHead->PeekHeader(nsHttp::Accept_Ranges);
|
||||
if (acceptRanges &&
|
||||
!nsHttp::FindToken(acceptRanges, "bytes", HTTP_HEADER_VALUE_SEPS)) {
|
||||
return NS_ERROR_NOT_RESUMABLE;
|
||||
}
|
||||
|
||||
PRUint64 size = LL_MAXUINT;
|
||||
nsCAutoString etag, lastmod;
|
||||
if (mResponseHead) {
|
||||
size = mResponseHead->TotalEntitySize();
|
||||
const char* cLastMod = mResponseHead->PeekHeader(nsHttp::Last_Modified);
|
||||
if (cLastMod)
|
||||
lastmod = cLastMod;
|
||||
const char* cEtag = mResponseHead->PeekHeader(nsHttp::ETag);
|
||||
if (cEtag)
|
||||
etag = cEtag;
|
||||
}
|
||||
nsCString entityID;
|
||||
NS_EscapeURL(etag.BeginReading(), etag.Length(), esc_AlwaysCopy |
|
||||
esc_FileBaseName | esc_Forced, entityID);
|
||||
entityID.Append('/');
|
||||
entityID.AppendInt(PRInt64(size));
|
||||
entityID.Append('/');
|
||||
entityID.Append(lastmod);
|
||||
// NOTE: Appending lastmod as the last part avoids having to escape it
|
||||
|
||||
aEntityID = entityID;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -56,6 +56,7 @@
|
||||
#include "nsIURI.h"
|
||||
#include "nsISupportsPriority.h"
|
||||
#include "nsIApplicationCache.h"
|
||||
#include "nsIResumableChannel.h"
|
||||
|
||||
#define DIE_WITH_ASYNC_OPEN_MSG() \
|
||||
do { \
|
||||
@ -95,6 +96,7 @@ class HttpBaseChannel : public nsHashPropertyBag
|
||||
, public nsIUploadChannel
|
||||
, public nsIUploadChannel2
|
||||
, public nsISupportsPriority
|
||||
, public nsIResumableChannel
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
@ -168,6 +170,9 @@ public:
|
||||
NS_IMETHOD GetPriority(PRInt32 *value);
|
||||
NS_IMETHOD AdjustPriority(PRInt32 delta);
|
||||
|
||||
// nsIResumableChannel
|
||||
NS_IMETHOD GetEntityID(nsACString& aEntityID);
|
||||
|
||||
protected:
|
||||
void AddCookiesToRequest();
|
||||
virtual nsresult SetupReplacementChannel(nsIURI *,
|
||||
@ -205,6 +210,10 @@ protected:
|
||||
nsCString mContentCharsetHint;
|
||||
nsCString mUserSetCookieHeader;
|
||||
|
||||
// Resumable channel specific data
|
||||
nsCString mEntityID;
|
||||
PRUint64 mStartPos;
|
||||
|
||||
nsresult mStatus;
|
||||
PRUint32 mLoadFlags;
|
||||
PRInt16 mPriority;
|
||||
|
@ -56,6 +56,8 @@ namespace net {
|
||||
class ChildChannelEvent
|
||||
{
|
||||
public:
|
||||
ChildChannelEvent() { MOZ_COUNT_CTOR(Callback); }
|
||||
virtual ~ChildChannelEvent() { MOZ_COUNT_DTOR(Callback); }
|
||||
virtual void Run() = 0;
|
||||
};
|
||||
|
||||
@ -70,6 +72,7 @@ public:
|
||||
}
|
||||
~AutoEventEnqueuer()
|
||||
{
|
||||
mChannel->EndEventQueueing();
|
||||
mChannel->FlushEventQueue();
|
||||
}
|
||||
private:
|
||||
@ -85,6 +88,8 @@ HttpChannelChild::HttpChannelChild()
|
||||
: mIsFromCache(PR_FALSE)
|
||||
, mCacheEntryAvailable(PR_FALSE)
|
||||
, mCacheExpirationTime(nsICache::NO_EXPIRATION_TIME)
|
||||
, mSendResumeAt(false)
|
||||
, mSuspendCount(0)
|
||||
, mState(HCC_NEW)
|
||||
, mIPCOpen(false)
|
||||
, mQueuePhase(PHASE_UNQUEUED)
|
||||
@ -147,8 +152,8 @@ HttpChannelChild::FlushEventQueue()
|
||||
NS_ABORT_IF_FALSE(mQueuePhase != PHASE_UNQUEUED,
|
||||
"Queue flushing should not occur if PHASE_UNQUEUED");
|
||||
|
||||
// Queue already being flushed.
|
||||
if (mQueuePhase != PHASE_QUEUEING)
|
||||
// Queue already being flushed, or the channel's suspended.
|
||||
if (mQueuePhase != PHASE_FINISHED_QUEUEING || mSuspendCount)
|
||||
return;
|
||||
|
||||
if (mEventQueue.Length() > 0) {
|
||||
@ -157,14 +162,25 @@ HttpChannelChild::FlushEventQueue()
|
||||
// all callbacks have run.
|
||||
mQueuePhase = PHASE_FLUSHING;
|
||||
|
||||
nsCOMPtr<nsIHttpChannel> kungFuDeathGrip(this);
|
||||
for (PRUint32 i = 0; i < mEventQueue.Length(); i++) {
|
||||
nsRefPtr<HttpChannelChild> kungFuDeathGrip(this);
|
||||
PRUint32 i;
|
||||
for (i = 0; i < mEventQueue.Length(); i++) {
|
||||
mEventQueue[i]->Run();
|
||||
// If the callback ended up suspending us, abort all further flushing.
|
||||
if (mSuspendCount)
|
||||
break;
|
||||
}
|
||||
mEventQueue.Clear();
|
||||
// We will always want to remove at least one finished callback.
|
||||
if (i < mEventQueue.Length())
|
||||
i++;
|
||||
|
||||
mEventQueue.RemoveElementsAt(0, i);
|
||||
}
|
||||
|
||||
mQueuePhase = PHASE_UNQUEUED;
|
||||
if (mSuspendCount)
|
||||
mQueuePhase = PHASE_QUEUEING;
|
||||
else
|
||||
mQueuePhase = PHASE_UNQUEUED;
|
||||
}
|
||||
|
||||
class StartRequestEvent : public ChildChannelEvent
|
||||
@ -647,13 +663,22 @@ HttpChannelChild::Cancel(nsresult status)
|
||||
NS_IMETHODIMP
|
||||
HttpChannelChild::Suspend()
|
||||
{
|
||||
DROP_DEAD();
|
||||
NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
|
||||
SendSuspend();
|
||||
mSuspendCount++;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HttpChannelChild::Resume()
|
||||
{
|
||||
DROP_DEAD();
|
||||
NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
|
||||
NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
|
||||
SendResume();
|
||||
mSuspendCount--;
|
||||
if (!mSuspendCount)
|
||||
FlushEventQueue();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -770,7 +795,8 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
|
||||
IPC::URI(mDocumentURI), IPC::URI(mReferrer), mLoadFlags,
|
||||
mRequestHeaders, mRequestHead.Method(), uploadStreamData,
|
||||
uploadStreamInfo, mPriority, mRedirectionLimit,
|
||||
mAllowPipelining, mForceAllowThirdPartyCookie);
|
||||
mAllowPipelining, mForceAllowThirdPartyCookie, mSendResumeAt,
|
||||
mStartPos, mEntityID);
|
||||
|
||||
mState = HCC_OPENED;
|
||||
return NS_OK;
|
||||
@ -886,14 +912,14 @@ HttpChannelChild::SetApplyConversion(PRBool aApplyConversion)
|
||||
NS_IMETHODIMP
|
||||
HttpChannelChild::ResumeAt(PRUint64 startPos, const nsACString& entityID)
|
||||
{
|
||||
DROP_DEAD();
|
||||
ENSURE_CALLED_BEFORE_ASYNC_OPEN();
|
||||
mStartPos = startPos;
|
||||
mEntityID = entityID;
|
||||
mSendResumeAt = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HttpChannelChild::GetEntityID(nsACString& aEntityID)
|
||||
{
|
||||
DROP_DEAD();
|
||||
}
|
||||
// GetEntityID is shared in HttpBaseChannel
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// HttpChannelChild::nsISupportsPriority
|
||||
|
@ -79,7 +79,6 @@ class HttpChannelChild : public PHttpChannelChild
|
||||
, public HttpBaseChannel
|
||||
, public nsICacheInfoChannel
|
||||
, public nsIEncodedChannel
|
||||
, public nsIResumableChannel
|
||||
, public nsIProxiedChannel
|
||||
, public nsITraceableChannel
|
||||
, public nsIApplicationCacheChannel
|
||||
@ -89,7 +88,6 @@ public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_NSICACHEINFOCHANNEL
|
||||
NS_DECL_NSIENCODEDCHANNEL
|
||||
NS_DECL_NSIRESUMABLECHANNEL
|
||||
NS_DECL_NSIPROXIEDCHANNEL
|
||||
NS_DECL_NSITRACEABLECHANNEL
|
||||
NS_DECL_NSIAPPLICATIONCACHECONTAINER
|
||||
@ -116,6 +114,8 @@ public:
|
||||
NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey);
|
||||
// nsISupportsPriority
|
||||
NS_IMETHOD SetPriority(PRInt32 value);
|
||||
// nsIResumableChannel
|
||||
NS_IMETHOD ResumeAt(PRUint64 startPos, const nsACString& entityID);
|
||||
|
||||
// Final setup when redirect has proceeded successfully in chrome
|
||||
nsresult CompleteRedirectSetup(nsIStreamListener *listener,
|
||||
@ -156,6 +156,11 @@ private:
|
||||
PRUint32 mCacheExpirationTime;
|
||||
nsCString mCachedCharset;
|
||||
|
||||
// If ResumeAt is called before AsyncOpen, we need to send extra data upstream
|
||||
bool mSendResumeAt;
|
||||
// Current suspension depth for this channel object
|
||||
PRUint32 mSuspendCount;
|
||||
|
||||
// FIXME: replace with IPDL states (bug 536319)
|
||||
enum HttpChannelChildState mState;
|
||||
bool mIPCOpen;
|
||||
@ -166,6 +171,7 @@ private:
|
||||
// event loop (ex: IPDL rpc) could cause listener->OnDataAvailable (for
|
||||
// instance) to be called before mListener->OnStartRequest has completed.
|
||||
void BeginEventQueueing();
|
||||
void EndEventQueueing();
|
||||
void FlushEventQueue();
|
||||
void EnqueueEvent(ChildChannelEvent* callback);
|
||||
bool ShouldEnqueue();
|
||||
@ -174,6 +180,7 @@ private:
|
||||
enum {
|
||||
PHASE_UNQUEUED,
|
||||
PHASE_QUEUEING,
|
||||
PHASE_FINISHED_QUEUEING,
|
||||
PHASE_FLUSHING
|
||||
} mQueuePhase;
|
||||
|
||||
@ -211,16 +218,26 @@ private:
|
||||
inline void
|
||||
HttpChannelChild::BeginEventQueueing()
|
||||
{
|
||||
if (mQueuePhase == PHASE_FLUSHING)
|
||||
if (mQueuePhase != PHASE_UNQUEUED)
|
||||
return;
|
||||
// Store incoming IPDL messages for later.
|
||||
mQueuePhase = PHASE_QUEUEING;
|
||||
}
|
||||
|
||||
inline void
|
||||
HttpChannelChild::EndEventQueueing()
|
||||
{
|
||||
if (mQueuePhase != PHASE_QUEUEING)
|
||||
return;
|
||||
|
||||
mQueuePhase = PHASE_FINISHED_QUEUEING;
|
||||
}
|
||||
|
||||
|
||||
inline bool
|
||||
HttpChannelChild::ShouldEnqueue()
|
||||
{
|
||||
return mQueuePhase != PHASE_UNQUEUED;
|
||||
return mQueuePhase != PHASE_UNQUEUED || mSuspendCount;
|
||||
}
|
||||
|
||||
inline void
|
||||
|
@ -104,7 +104,10 @@ HttpChannelParent::RecvAsyncOpen(const IPC::URI& aURI,
|
||||
const PRUint16& priority,
|
||||
const PRUint8& redirectionLimit,
|
||||
const PRBool& allowPipelining,
|
||||
const PRBool& forceAllowThirdPartyCookie)
|
||||
const PRBool& forceAllowThirdPartyCookie,
|
||||
const bool& doResumeAt,
|
||||
const PRUint64& startPos,
|
||||
const nsCString& entityID)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri(aURI);
|
||||
nsCOMPtr<nsIURI> originalUri(aOriginalURI);
|
||||
@ -129,6 +132,9 @@ HttpChannelParent::RecvAsyncOpen(const IPC::URI& aURI,
|
||||
nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
|
||||
httpChan->SetRemoteChannel(true);
|
||||
|
||||
if (doResumeAt)
|
||||
httpChan->ResumeAt(startPos, entityID);
|
||||
|
||||
if (originalUri)
|
||||
httpChan->SetOriginalURI(originalUri);
|
||||
if (docUri)
|
||||
@ -183,6 +189,20 @@ HttpChannelParent::RecvSetPriority(const PRUint16& priority)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
HttpChannelParent::RecvSuspend()
|
||||
{
|
||||
mChannel->Suspend();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
HttpChannelParent::RecvResume()
|
||||
{
|
||||
mChannel->Resume();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
HttpChannelParent::RecvSetCacheTokenCachedCharset(const nsCString& charset)
|
||||
{
|
||||
|
@ -93,10 +93,15 @@ protected:
|
||||
const PRUint16& priority,
|
||||
const PRUint8& redirectionLimit,
|
||||
const PRBool& allowPipelining,
|
||||
const PRBool& forceAllowThirdPartyCookie);
|
||||
const PRBool& forceAllowThirdPartyCookie,
|
||||
const bool& doResumeAt,
|
||||
const PRUint64& startPos,
|
||||
const nsCString& entityID);
|
||||
|
||||
virtual bool RecvSetPriority(const PRUint16& priority);
|
||||
virtual bool RecvSetCacheTokenCachedCharset(const nsCString& charset);
|
||||
virtual bool RecvSuspend();
|
||||
virtual bool RecvResume();
|
||||
virtual bool RecvRedirect2Result(const nsresult& result,
|
||||
const RequestHeaderTuples& changedHeaders);
|
||||
|
||||
|
@ -73,12 +73,18 @@ parent:
|
||||
PRUint16 priority,
|
||||
PRUint8 redirectionLimit,
|
||||
PRBool allowPipelining,
|
||||
PRBool forceAllowThirdPartyCookie);
|
||||
PRBool forceAllowThirdPartyCookie,
|
||||
bool resumeAt,
|
||||
PRUint64 startPos,
|
||||
nsCString entityID);
|
||||
|
||||
SetPriority(PRUint16 priority);
|
||||
|
||||
SetCacheTokenCachedCharset(nsCString charset);
|
||||
|
||||
Suspend();
|
||||
Resume();
|
||||
|
||||
// Reports approval/veto of redirect by child process redirect observers
|
||||
Redirect2Result(nsresult result, RequestHeaderTuples changedHeaders);
|
||||
|
||||
|
@ -115,7 +115,6 @@ nsHttpChannel::nsHttpChannel()
|
||||
, mCacheAccess(0)
|
||||
, mPostID(0)
|
||||
, mRequestTime(0)
|
||||
, mStartPos(LL_MAXUINT)
|
||||
, mPendingAsyncCallOnResume(nsnull)
|
||||
, mSuspendCount(0)
|
||||
, mApplyConversion(PR_TRUE)
|
||||
@ -4073,51 +4072,6 @@ nsHttpChannel::ResumeAt(PRUint64 aStartPos,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHttpChannel::GetEntityID(nsACString& aEntityID)
|
||||
{
|
||||
// Don't return an entity ID for Non-GET requests which require
|
||||
// additional data
|
||||
if (mRequestHead.Method() != nsHttp::Get) {
|
||||
return NS_ERROR_NOT_RESUMABLE;
|
||||
}
|
||||
|
||||
// Don't return an entity if the server sent the following header:
|
||||
// Accept-Ranges: none
|
||||
// Not sending the Accept-Ranges header means we can still try
|
||||
// sending range requests.
|
||||
const char* acceptRanges =
|
||||
mResponseHead->PeekHeader(nsHttp::Accept_Ranges);
|
||||
if (acceptRanges &&
|
||||
!nsHttp::FindToken(acceptRanges, "bytes", HTTP_HEADER_VALUE_SEPS)) {
|
||||
return NS_ERROR_NOT_RESUMABLE;
|
||||
}
|
||||
|
||||
PRUint64 size = LL_MAXUINT;
|
||||
nsCAutoString etag, lastmod;
|
||||
if (mResponseHead) {
|
||||
size = mResponseHead->TotalEntitySize();
|
||||
const char* cLastMod = mResponseHead->PeekHeader(nsHttp::Last_Modified);
|
||||
if (cLastMod)
|
||||
lastmod = cLastMod;
|
||||
const char* cEtag = mResponseHead->PeekHeader(nsHttp::ETag);
|
||||
if (cEtag)
|
||||
etag = cEtag;
|
||||
}
|
||||
nsCString entityID;
|
||||
NS_EscapeURL(etag.BeginReading(), etag.Length(), esc_AlwaysCopy |
|
||||
esc_FileBaseName | esc_Forced, entityID);
|
||||
entityID.Append('/');
|
||||
entityID.AppendInt(PRInt64(size));
|
||||
entityID.Append('/');
|
||||
entityID.Append(lastmod);
|
||||
// NOTE: Appending lastmod as the last part avoids having to escape it
|
||||
|
||||
aEntityID = entityID;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpChannel::nsICacheListener
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -80,7 +80,6 @@ class nsHttpChannel : public HttpBaseChannel
|
||||
, public nsICacheListener
|
||||
, public nsIEncodedChannel
|
||||
, public nsITransportEventSink
|
||||
, public nsIResumableChannel
|
||||
, public nsIProtocolProxyCallback
|
||||
, public nsIHttpAuthenticableChannel
|
||||
, public nsITraceableChannel
|
||||
@ -96,7 +95,6 @@ public:
|
||||
NS_DECL_NSICACHELISTENER
|
||||
NS_DECL_NSIENCODEDCHANNEL
|
||||
NS_DECL_NSITRANSPORTEVENTSINK
|
||||
NS_DECL_NSIRESUMABLECHANNEL
|
||||
NS_DECL_NSIPROTOCOLPROXYCALLBACK
|
||||
NS_DECL_NSIPROXIEDCHANNEL
|
||||
NS_DECL_NSITRACEABLECHANNEL
|
||||
@ -143,6 +141,8 @@ public:
|
||||
NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey);
|
||||
// nsISupportsPriority
|
||||
NS_IMETHOD SetPriority(PRInt32 value);
|
||||
// nsIResumableChannel
|
||||
NS_IMETHOD ResumeAt(PRUint64 startPos, const nsACString& entityID);
|
||||
|
||||
public: /* internal necko use only */
|
||||
typedef void (nsHttpChannel:: *nsAsyncCallback)(void);
|
||||
@ -271,10 +271,6 @@ private:
|
||||
// auth specific data
|
||||
nsCOMPtr<nsIHttpChannelAuthProvider> mAuthProvider;
|
||||
|
||||
// Resumable channel specific data
|
||||
nsCString mEntityID;
|
||||
PRUint64 mStartPos;
|
||||
|
||||
// Function pointer that can be set to indicate that we got suspended while
|
||||
// waiting on an AsyncCall. When we get resumed we should AsyncCall this
|
||||
// function.
|
||||
|
@ -23,6 +23,10 @@ function read_stream(stream, count) {
|
||||
|
||||
const CL_EXPECT_FAILURE = 0x1;
|
||||
const CL_EXPECT_GZIP = 0x2;
|
||||
const CL_EXPECT_3S_DELAY = 0x4;
|
||||
const CL_SUSPEND = 0x8;
|
||||
|
||||
const SUSPEND_DELAY = 3000;
|
||||
|
||||
/**
|
||||
* A stream listener that calls a callback function with a specified
|
||||
@ -50,6 +54,7 @@ ChannelListener.prototype = {
|
||||
_got_onstartrequest: false,
|
||||
_got_onstoprequest: false,
|
||||
_contentLen: -1,
|
||||
_lastEvent: 0,
|
||||
|
||||
QueryInterface: function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsIStreamListener) ||
|
||||
@ -64,6 +69,7 @@ ChannelListener.prototype = {
|
||||
if (this._got_onstartrequest)
|
||||
do_throw("Got second onStartRequest event!");
|
||||
this._got_onstartrequest = true;
|
||||
this._lastEvent = Date.now();
|
||||
|
||||
request.QueryInterface(Components.interfaces.nsIChannel);
|
||||
try {
|
||||
@ -75,6 +81,12 @@ ChannelListener.prototype = {
|
||||
}
|
||||
if (this._contentLen == -1 && !(this._flags & CL_EXPECT_FAILURE))
|
||||
do_throw("Content length is unknown in onStartRequest!");
|
||||
|
||||
if (this._flags & CL_SUSPEND) {
|
||||
request.suspend();
|
||||
do_timeout(SUSPEND_DELAY, function() { request.resume(); });
|
||||
}
|
||||
|
||||
} catch (ex) {
|
||||
do_throw("Error in onStartRequest: " + ex);
|
||||
}
|
||||
@ -82,6 +94,8 @@ ChannelListener.prototype = {
|
||||
|
||||
onDataAvailable: function(request, context, stream, offset, count) {
|
||||
try {
|
||||
let current = Date.now();
|
||||
|
||||
if (!this._got_onstartrequest)
|
||||
do_throw("onDataAvailable without onStartRequest event!");
|
||||
if (this._got_onstoprequest)
|
||||
@ -91,7 +105,18 @@ ChannelListener.prototype = {
|
||||
if (this._flags & CL_EXPECT_FAILURE)
|
||||
do_throw("Got data despite expecting a failure");
|
||||
|
||||
if (current - this._lastEvent >= SUSPEND_DELAY &&
|
||||
!(this._flags & CL_EXPECT_3S_DELAY))
|
||||
do_throw("Data received after significant unexpected delay");
|
||||
else if (current - this._lastEvent < SUSPEND_DELAY &&
|
||||
this._flags & CL_EXPECT_3S_DELAY)
|
||||
do_throw("Data received sooner than expected");
|
||||
else if (current - this._lastEvent >= SUSPEND_DELAY &&
|
||||
this._flags & CL_EXPECT_3S_DELAY)
|
||||
this._flags &= ~CL_EXPECT_3S_DELAY; // No more delays expected
|
||||
|
||||
this._buffer = this._buffer.concat(read_stream(stream, count));
|
||||
this._lastEvent = current;
|
||||
} catch (ex) {
|
||||
do_throw("Error in onDataAvailable: " + ex);
|
||||
}
|
||||
|
72
netwerk/test/unit/test_httpsuspend.js
Normal file
72
netwerk/test/unit/test_httpsuspend.js
Normal file
@ -0,0 +1,72 @@
|
||||
// This file ensures that suspending a channel directly after opening it
|
||||
// suspends future notifications correctly.
|
||||
|
||||
do_load_httpd_js();
|
||||
|
||||
const MIN_TIME_DIFFERENCE = 3000;
|
||||
const RESUME_DELAY = 5000;
|
||||
|
||||
var listener = {
|
||||
_lastEvent: 0,
|
||||
_gotData: false,
|
||||
|
||||
QueryInterface: function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsIStreamListener) ||
|
||||
iid.equals(Components.interfaces.nsIRequestObserver) ||
|
||||
iid.equals(Components.interfaces.nsISupports))
|
||||
return this;
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
onStartRequest: function(request, ctx) {
|
||||
this._lastEvent = Date.now();
|
||||
request.QueryInterface(Ci.nsIRequest);
|
||||
|
||||
// Insert a delay between this and the next callback to ensure message buffering
|
||||
// works correctly
|
||||
request.suspend();
|
||||
do_timeout(RESUME_DELAY, function() request.resume());
|
||||
},
|
||||
|
||||
onDataAvailable: function(request, context, stream, offset, count) {
|
||||
do_check_true(Date.now() - this._lastEvent >= MIN_TIME_DIFFERENCE);
|
||||
read_stream(stream, count);
|
||||
|
||||
// Ensure that suspending and resuming inside a callback works correctly
|
||||
request.suspend();
|
||||
request.resume();
|
||||
|
||||
this._gotData = true;
|
||||
},
|
||||
|
||||
onStopRequest: function(request, ctx, status) {
|
||||
do_check_true(this._gotData);
|
||||
httpserv.stop(do_test_finished);
|
||||
}
|
||||
};
|
||||
|
||||
function makeChan(url) {
|
||||
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
|
||||
var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
|
||||
return chan;
|
||||
}
|
||||
|
||||
var httpserv = null;
|
||||
|
||||
function run_test() {
|
||||
httpserv = new nsHttpServer();
|
||||
httpserv.registerPathHandler("/woo", data);
|
||||
httpserv.start(4444);
|
||||
|
||||
var chan = makeChan("http://localhost:4444/woo");
|
||||
chan.QueryInterface(Ci.nsIRequest);
|
||||
chan.asyncOpen(listener, null);
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function data(metadata, response) {
|
||||
let httpbody = "0123456789";
|
||||
response.setHeader("Content-Type", "text/plain", false);
|
||||
response.bodyOutputStream.write(httpbody, httpbody.length);
|
||||
}
|
101
netwerk/test/unit/test_reentrancy.js
Normal file
101
netwerk/test/unit/test_reentrancy.js
Normal file
@ -0,0 +1,101 @@
|
||||
do_load_httpd_js();
|
||||
|
||||
var httpserver = new nsHttpServer();
|
||||
var testpath = "/simple";
|
||||
var httpbody = "<?xml version='1.0' ?><root>0123456789</root>";
|
||||
|
||||
function syncXHR()
|
||||
{
|
||||
var xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
xhr.open("GET", "http://localhost:4444" + testpath, false);
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
const MAX_TESTS = 2;
|
||||
|
||||
var listener = {
|
||||
_done_onStart: false,
|
||||
_done_onData: false,
|
||||
_test: 0,
|
||||
|
||||
QueryInterface: function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsIStreamListener) ||
|
||||
iid.equals(Components.interfaces.nsIRequestObserver) ||
|
||||
iid.equals(Components.interfaces.nsISupports))
|
||||
return this;
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
onStartRequest: function(request, ctx) {
|
||||
switch(this._test) {
|
||||
case 0:
|
||||
request.suspend();
|
||||
syncXHR();
|
||||
request.resume();
|
||||
break;
|
||||
case 1:
|
||||
request.suspend();
|
||||
syncXHR();
|
||||
do_execute_soon(function() request.resume());
|
||||
break;
|
||||
case 2:
|
||||
do_execute_soon(function() request.suspend());
|
||||
do_execute_soon(function() request.resume());
|
||||
syncXHR();
|
||||
break;
|
||||
}
|
||||
|
||||
this._done_onStart = true;
|
||||
},
|
||||
|
||||
onDataAvailable: function(request, context, stream, offset, count) {
|
||||
do_check_true(this._done_onStart);
|
||||
read_stream(stream, count);
|
||||
this._done_onData = true;
|
||||
},
|
||||
|
||||
onStopRequest: function(request, ctx, status) {
|
||||
do_check_true(this._done_onData);
|
||||
this._reset();
|
||||
if (this._test <= MAX_TESTS)
|
||||
next_test();
|
||||
else
|
||||
httpserver.stop(do_test_finished);
|
||||
},
|
||||
|
||||
_reset: function() {
|
||||
this._done_onStart = false;
|
||||
this._done_onData = false;
|
||||
this._test++;
|
||||
}
|
||||
};
|
||||
|
||||
function makeChan(url) {
|
||||
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
|
||||
var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
|
||||
return chan;
|
||||
}
|
||||
|
||||
function next_test()
|
||||
{
|
||||
var chan = makeChan("http://localhost:4444" + testpath);
|
||||
chan.QueryInterface(Ci.nsIRequest);
|
||||
chan.asyncOpen(listener, null);
|
||||
}
|
||||
|
||||
function run_test()
|
||||
{
|
||||
httpserver.registerPathHandler(testpath, serverHandler);
|
||||
httpserver.start(4444);
|
||||
|
||||
next_test();
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function serverHandler(metadata, response)
|
||||
{
|
||||
response.setHeader("Content-Type", "text/xml", false);
|
||||
response.bodyOutputStream.write(httpbody, httpbody.length);
|
||||
}
|
@ -188,6 +188,18 @@ function run_test() {
|
||||
do_check_true(request.nsIHttpChannel.requestSucceeded);
|
||||
do_check_eq(data, rangeBody);
|
||||
|
||||
// Try a successful suspend/resume from 0
|
||||
var chan = make_channel("http://localhost:4444/range");
|
||||
chan.nsIResumableChannel.resumeAt(0, entityID);
|
||||
chan.asyncOpen(new ChannelListener(try_suspend_resume, null,
|
||||
CL_SUSPEND | CL_EXPECT_3S_DELAY), null);
|
||||
}
|
||||
|
||||
function try_suspend_resume(request, data, ctx) {
|
||||
dump("*** try_suspend_resume()\n");
|
||||
do_check_true(request.nsIHttpChannel.requestSucceeded);
|
||||
do_check_eq(data, rangeBody);
|
||||
|
||||
// Try a successful resume from 0
|
||||
var chan = make_channel("http://localhost:4444/range");
|
||||
chan.nsIResumableChannel.resumeAt(0, entityID);
|
||||
@ -199,12 +211,20 @@ function run_test() {
|
||||
do_check_true(request.nsIHttpChannel.requestSucceeded);
|
||||
do_check_eq(data, rangeBody);
|
||||
|
||||
// XXX skip all authentication tests for now, as they're busted on e10s (bug 537782)
|
||||
|
||||
// Authentication (no password; working resume)
|
||||
// (should not give us any data)
|
||||
var chan = make_channel("http://localhost:4444/range");
|
||||
/*var chan = make_channel("http://localhost:4444/range");
|
||||
chan.nsIResumableChannel.resumeAt(1, entityID);
|
||||
chan.nsIHttpChannel.setRequestHeader("X-Need-Auth", "true", false);
|
||||
chan.asyncOpen(new ChannelListener(test_auth_nopw, null, CL_EXPECT_FAILURE), null);
|
||||
chan.asyncOpen(new ChannelListener(test_auth_nopw, null, CL_EXPECT_FAILURE), null);*/
|
||||
|
||||
// 404 page (same content length as real content)
|
||||
var chan = make_channel("http://localhost:4444/range");
|
||||
chan.nsIResumableChannel.resumeAt(1, entityID);
|
||||
chan.nsIHttpChannel.setRequestHeader("X-Want-404", "true", false);
|
||||
chan.asyncOpen(new ChannelListener(test_404, null, CL_EXPECT_FAILURE), null);
|
||||
}
|
||||
|
||||
function test_auth_nopw(request, data, ctx) {
|
||||
|
3
netwerk/test/unit_ipc/test_httpsuspend_wrap.js
Normal file
3
netwerk/test/unit_ipc/test_httpsuspend_wrap.js
Normal file
@ -0,0 +1,3 @@
|
||||
function run_test() {
|
||||
run_test_in_child("../unit/test_httpsuspend.js");
|
||||
}
|
3
netwerk/test/unit_ipc/test_reentrancy_wrap.js
Normal file
3
netwerk/test/unit_ipc/test_reentrancy_wrap.js
Normal file
@ -0,0 +1,3 @@
|
||||
function run_test() {
|
||||
run_test_in_child("../unit/test_reentrancy.js");
|
||||
}
|
3
netwerk/test/unit_ipc/test_resumable_channel_wrap.js
Normal file
3
netwerk/test/unit_ipc/test_resumable_channel_wrap.js
Normal file
@ -0,0 +1,3 @@
|
||||
function run_test() {
|
||||
run_test_in_child("../unit/test_resumable_channel.js");
|
||||
}
|
Loading…
Reference in New Issue
Block a user