Bug 536321 - e10s HTTP: suspend/resume. r=dwitte

This commit is contained in:
Josh Matthews 2010-08-10 23:07:09 -04:00
parent db31d25583
commit 00afa443f6
16 changed files with 388 additions and 77 deletions

View File

@ -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;
}
//------------------------------------------------------------------------------
//-----------------------------------------------------------------------------

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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)
{

View File

@ -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);

View File

@ -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);

View File

@ -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
//-----------------------------------------------------------------------------

View File

@ -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.

View File

@ -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);
}

View 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);
}

View 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);
}

View File

@ -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) {

View File

@ -0,0 +1,3 @@
function run_test() {
run_test_in_child("../unit/test_httpsuspend.js");
}

View File

@ -0,0 +1,3 @@
function run_test() {
run_test_in_child("../unit/test_reentrancy.js");
}

View File

@ -0,0 +1,3 @@
function run_test() {
run_test_in_child("../unit/test_resumable_channel.js");
}