From 00afa443f6931a1a354ff814df96103a123e34bf Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Tue, 10 Aug 2010 23:07:09 -0400 Subject: [PATCH] Bug 536321 - e10s HTTP: suspend/resume. r=dwitte --- netwerk/protocol/http/HttpBaseChannel.cpp | 53 ++++++++- netwerk/protocol/http/HttpBaseChannel.h | 9 ++ netwerk/protocol/http/HttpChannelChild.cpp | 56 +++++++--- netwerk/protocol/http/HttpChannelChild.h | 25 ++++- netwerk/protocol/http/HttpChannelParent.cpp | 22 +++- netwerk/protocol/http/HttpChannelParent.h | 7 +- netwerk/protocol/http/PHttpChannel.ipdl | 8 +- netwerk/protocol/http/nsHttpChannel.cpp | 46 -------- netwerk/protocol/http/nsHttpChannel.h | 8 +- netwerk/test/unit/head_channels.js | 25 +++++ netwerk/test/unit/test_httpsuspend.js | 72 +++++++++++++ netwerk/test/unit/test_reentrancy.js | 101 ++++++++++++++++++ netwerk/test/unit/test_resumable_channel.js | 24 ++++- .../test/unit_ipc/test_httpsuspend_wrap.js | 3 + netwerk/test/unit_ipc/test_reentrancy_wrap.js | 3 + .../unit_ipc/test_resumable_channel_wrap.js | 3 + 16 files changed, 388 insertions(+), 77 deletions(-) create mode 100644 netwerk/test/unit/test_httpsuspend.js create mode 100644 netwerk/test/unit/test_reentrancy.js create mode 100644 netwerk/test/unit_ipc/test_httpsuspend_wrap.js create mode 100644 netwerk/test/unit_ipc/test_reentrancy_wrap.js create mode 100644 netwerk/test/unit_ipc/test_resumable_channel_wrap.js diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index c811c47f3827..7cb49d120e98 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -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; +} + //------------------------------------------------------------------------------ //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index acf6dbee182e..180018da9429 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -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; diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index 2aeb10e0ace3..2765016cc94c 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -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 kungFuDeathGrip(this); - for (PRUint32 i = 0; i < mEventQueue.Length(); i++) { + nsRefPtr 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 diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h index 8cb60fb9e73a..7c4812f6a18f 100644 --- a/netwerk/protocol/http/HttpChannelChild.h +++ b/netwerk/protocol/http/HttpChannelChild.h @@ -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 diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index 7e11fd7cb25f..923289da8c18 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -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 uri(aURI); nsCOMPtr originalUri(aOriginalURI); @@ -129,6 +132,9 @@ HttpChannelParent::RecvAsyncOpen(const IPC::URI& aURI, nsHttpChannel *httpChan = static_cast(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) { diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h index 95b2df3a436c..d0ea1fd62869 100644 --- a/netwerk/protocol/http/HttpChannelParent.h +++ b/netwerk/protocol/http/HttpChannelParent.h @@ -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); diff --git a/netwerk/protocol/http/PHttpChannel.ipdl b/netwerk/protocol/http/PHttpChannel.ipdl index d3f42539d511..640cff0a9ab5 100644 --- a/netwerk/protocol/http/PHttpChannel.ipdl +++ b/netwerk/protocol/http/PHttpChannel.ipdl @@ -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); diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 65d05117a3f8..472e5918202c 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -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 //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index f69432ff9385..088bebf2443f 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -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 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. diff --git a/netwerk/test/unit/head_channels.js b/netwerk/test/unit/head_channels.js index 0261cba64de1..f92d6f872afb 100644 --- a/netwerk/test/unit/head_channels.js +++ b/netwerk/test/unit/head_channels.js @@ -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); } diff --git a/netwerk/test/unit/test_httpsuspend.js b/netwerk/test/unit/test_httpsuspend.js new file mode 100644 index 000000000000..0459c637aaeb --- /dev/null +++ b/netwerk/test/unit/test_httpsuspend.js @@ -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); +} diff --git a/netwerk/test/unit/test_reentrancy.js b/netwerk/test/unit/test_reentrancy.js new file mode 100644 index 000000000000..aecdfddf5050 --- /dev/null +++ b/netwerk/test/unit/test_reentrancy.js @@ -0,0 +1,101 @@ +do_load_httpd_js(); + +var httpserver = new nsHttpServer(); +var testpath = "/simple"; +var httpbody = "0123456789"; + +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); +} diff --git a/netwerk/test/unit/test_resumable_channel.js b/netwerk/test/unit/test_resumable_channel.js index 8aacdb47cc08..f58513d1cef8 100644 --- a/netwerk/test/unit/test_resumable_channel.js +++ b/netwerk/test/unit/test_resumable_channel.js @@ -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) { diff --git a/netwerk/test/unit_ipc/test_httpsuspend_wrap.js b/netwerk/test/unit_ipc/test_httpsuspend_wrap.js new file mode 100644 index 000000000000..348541283a45 --- /dev/null +++ b/netwerk/test/unit_ipc/test_httpsuspend_wrap.js @@ -0,0 +1,3 @@ +function run_test() { + run_test_in_child("../unit/test_httpsuspend.js"); +} \ No newline at end of file diff --git a/netwerk/test/unit_ipc/test_reentrancy_wrap.js b/netwerk/test/unit_ipc/test_reentrancy_wrap.js new file mode 100644 index 000000000000..43d72dd0598c --- /dev/null +++ b/netwerk/test/unit_ipc/test_reentrancy_wrap.js @@ -0,0 +1,3 @@ +function run_test() { + run_test_in_child("../unit/test_reentrancy.js"); +} \ No newline at end of file diff --git a/netwerk/test/unit_ipc/test_resumable_channel_wrap.js b/netwerk/test/unit_ipc/test_resumable_channel_wrap.js new file mode 100644 index 000000000000..573ab25b64e6 --- /dev/null +++ b/netwerk/test/unit_ipc/test_resumable_channel_wrap.js @@ -0,0 +1,3 @@ +function run_test() { + run_test_in_child("../unit/test_resumable_channel.js"); +}