Bug 536317 - e10s HTTP: implement Cancel. r=jduell, a=blocker

This commit is contained in:
Dan Witte 2010-08-12 02:05:16 -07:00
parent 71818995a5
commit 185a2a0dbe
7 changed files with 190 additions and 81 deletions

View File

@ -90,7 +90,6 @@ HttpChannelChild::HttpChannelChild()
, mCacheExpirationTime(nsICache::NO_EXPIRATION_TIME)
, mSendResumeAt(false)
, mSuspendCount(0)
, mState(HCC_NEW)
, mIPCOpen(false)
, mQueuePhase(PHASE_UNQUEUED)
{
@ -247,12 +246,8 @@ HttpChannelChild::OnStartRequest(const nsHttpResponseHead& responseHead,
{
LOG(("HttpChannelChild::RecvOnStartRequest [this=%x]\n", this));
mState = HCC_ONSTART;
if (useResponseHead)
if (useResponseHead && !mCanceled)
mResponseHead = new nsHttpResponseHead(responseHead);
else
mResponseHead = nsnull;
mIsFromCache = isFromCache;
mCacheEntryAvailable = cacheEntryAvailable;
@ -266,10 +261,7 @@ HttpChannelChild::OnStartRequest(const nsHttpResponseHead& responseHead,
if (mResponseHead)
SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
} else {
// TODO: Cancel request: (bug 536317)
// - Send Cancel msg to parent
// - drop any in flight OnDataAvail msgs we receive
// - make sure we do call OnStopRequest eventually
Cancel(rv);
}
}
@ -311,9 +303,10 @@ HttpChannelChild::OnDataAvailable(const nsCString& data,
const PRUint32& offset,
const PRUint32& count)
{
LOG(("HttpChannelChild::RecvOnDataAvailable [this=%x]\n", this));
LOG(("HttpChannelChild::OnDataAvailable [this=%x]\n", this));
mState = HCC_ONDATA;
if (mCanceled)
return;
// NOTE: the OnDataAvailable contract requires the client to read all the data
// in the inputstream. This code relies on that ('data' will go away after
@ -326,7 +319,7 @@ HttpChannelChild::OnDataAvailable(const nsCString& data,
count,
NS_ASSIGNMENT_DEPEND);
if (NS_FAILED(rv)) {
// TODO: what to do here? Cancel request? Very unlikely to fail.
Cancel(rv);
return;
}
@ -336,7 +329,7 @@ HttpChannelChild::OnDataAvailable(const nsCString& data,
stringStream, offset, count);
stringStream->Close();
if (NS_FAILED(rv)) {
// TODO: Cancel request: see OnStartRequest. Bug 536317
Cancel(rv);
}
}
@ -368,19 +361,21 @@ HttpChannelChild::RecvOnStopRequest(const nsresult& statusCode)
void
HttpChannelChild::OnStopRequest(const nsresult& statusCode)
{
LOG(("HttpChannelChild::RecvOnStopRequest [this=%x status=%u]\n",
LOG(("HttpChannelChild::OnStopRequest [this=%x status=%u]\n",
this, statusCode));
mState = HCC_ONSTOP;
mIsPending = PR_FALSE;
mStatus = statusCode;
{ // We must flush the queue before we Send__delete__,
if (!mCanceled)
mStatus = statusCode;
{ // We must flush the queue before we Send__delete__
// (although we really shouldn't receive any msgs after OnStop),
// so make sure this goes out of scope before then.
AutoEventEnqueuer ensureSerialDispatch(this);
mListener->OnStopRequest(this, mListenerContext, statusCode);
mListener = 0;
mListenerContext = 0;
mCacheEntryAvailable = PR_FALSE;
@ -426,9 +421,12 @@ void
HttpChannelChild::OnProgress(const PRUint64& progress,
const PRUint64& progressMax)
{
LOG(("HttpChannelChild::RecvOnProgress [this=%p progress=%llu/%llu]\n",
LOG(("HttpChannelChild::OnProgress [this=%p progress=%llu/%llu]\n",
this, progress, progressMax));
if (mCanceled)
return;
// cache the progress sink so we don't have to query for it each time.
if (!mProgressSink)
GetCallback(mProgressSink);
@ -439,7 +437,7 @@ HttpChannelChild::OnProgress(const PRUint64& progress,
if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
!(mLoadFlags & LOAD_BACKGROUND))
{
if (progress > 0) {
if (progress > 0) {
NS_ASSERTION(progress <= progressMax, "unexpected progress values");
mProgressSink->OnProgress(this, nsnull, progress, progressMax);
}
@ -479,7 +477,10 @@ void
HttpChannelChild::OnStatus(const nsresult& status,
const nsString& statusArg)
{
LOG(("HttpChannelChild::RecvOnStatus [this=%p status=%x]\n", this, status));
LOG(("HttpChannelChild::OnStatus [this=%p status=%x]\n", this, status));
if (mCanceled)
return;
// cache the progress sink so we don't have to query for it each time.
if (!mProgressSink)
@ -495,6 +496,57 @@ HttpChannelChild::OnStatus(const nsresult& status,
}
}
class CancelEvent : public ChildChannelEvent
{
public:
CancelEvent(HttpChannelChild* child, const nsresult& status)
: mChild(child)
, mStatus(status) {}
void Run() { mChild->OnCancel(mStatus); }
private:
HttpChannelChild* mChild;
nsresult mStatus;
};
bool
HttpChannelChild::RecvCancelEarly(const nsresult& status)
{
if (ShouldEnqueue()) {
EnqueueEvent(new CancelEvent(this, status));
} else {
OnCancel(status);
}
return true;
}
void
HttpChannelChild::OnCancel(const nsresult& status)
{
LOG(("HttpChannelChild::OnCancel [this=%p status=%x]\n", this, status));
if (mCanceled)
return;
mCanceled = true;
mStatus = status;
mIsPending = false;
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, mStatus);
if (mListener) {
mListener->OnStartRequest(this, mListenerContext);
mListener->OnStopRequest(this, mListenerContext, mStatus);
}
mListener = NULL;
mListenerContext = NULL;
if (mIPCOpen)
PHttpChannelChild::Send__delete__(this);
}
class Redirect1Event : public ChildChannelEvent
{
public:
@ -550,8 +602,12 @@ HttpChannelChild::Redirect1Begin(PHttpChannelChild* newChannel,
nsresult rv =
newHttpChannelChild->HttpBaseChannel::Init(uri, mCaps,
mConnectionInfo->ProxyInfo());
if (NS_FAILED(rv))
return; // TODO Bug 536317
if (NS_FAILED(rv)) {
// Cancel the channel and veto the redirect.
Cancel(rv);
SendRedirect2Result(rv, mRedirectChannelChild->mRequestHeaders);
return;
}
// We won't get OnStartRequest, set cookies here.
mResponseHead = new nsHttpResponseHead(responseHead);
@ -559,16 +615,20 @@ HttpChannelChild::Redirect1Begin(PHttpChannelChild* newChannel,
PRBool preserveMethod = (mResponseHead->Status() == 307);
rv = SetupReplacementChannel(uri, newHttpChannelChild, preserveMethod);
if (NS_FAILED(rv))
return; // TODO Bug 536317
if (NS_FAILED(rv)) {
// Cancel the channel and veto the redirect.
Cancel(rv);
SendRedirect2Result(rv, mRedirectChannelChild->mRequestHeaders);
return;
}
mRedirectChannelChild = newHttpChannelChild;
nsresult result = gHttpHandler->AsyncOnChannelRedirect(this,
newHttpChannelChild,
redirectFlags);
if (NS_FAILED(result))
OnRedirectVerifyCallback(result);
rv = gHttpHandler->AsyncOnChannelRedirect(this,
newHttpChannelChild,
redirectFlags);
if (NS_FAILED(rv))
OnRedirectVerifyCallback(rv);
}
class Redirect3Event : public ChildChannelEvent
@ -604,7 +664,7 @@ HttpChannelChild::Redirect3Complete()
rv = mRedirectChannelChild->CompleteRedirectSetup(mListener,
mListenerContext);
if (NS_FAILED(rv))
; // TODO Cancel: Bug 536317
Cancel(rv);
}
nsresult
@ -628,9 +688,9 @@ HttpChannelChild::CompleteRedirectSetup(nsIStreamListener *listener,
if (mLoadGroup)
mLoadGroup->AddRequest(this, nsnull);
// TODO: may have been canceled by on-modify-request observers: bug 536317
mState = HCC_OPENED;
// We already have an open IPDL connection to the parent. If on-modify-request
// listeners or load group observers canceled us, let the parent handle it
// and send it back to us naturally.
return NS_OK;
}
@ -656,7 +716,14 @@ HttpChannelChild::OnRedirectVerifyCallback(nsresult result)
NS_IMETHODIMP
HttpChannelChild::Cancel(nsresult status)
{
// FIXME: bug 536317
if (!mCanceled) {
// If this cancel occurs before nsHttpChannel has been set up, AsyncOpen
// is responsible for cleaning up.
mCanceled = true;
mStatus = status;
if (mIPCOpen)
SendCancel(status);
}
return NS_OK;
}
@ -699,6 +766,9 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
{
LOG(("HttpChannelChild::AsyncOpen [this=%x uri=%s]\n", this, mSpec.get()));
if (mCanceled)
return mStatus;
NS_ENSURE_TRUE(gNeckoChild != nsnull, NS_ERROR_FAILURE);
NS_ENSURE_ARG_POINTER(listener);
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
@ -758,19 +828,16 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
if (mLoadGroup)
mLoadGroup->AddRequest(this, nsnull);
// FIXME: bug 536317: We may have been cancelled already, either by
// on-modify-request listeners or by load group observers; in that case,
// don't create IPDL connection. See nsHttpChannel::AsyncOpen(): I think
// we'll need something like
//
// if (mCanceled) {
// LOG(("Calling AsyncAbort [rv=%x mCanceled=%i]\n", rv, mCanceled));
// AsyncAbort(rv);
// return NS_OK;
// }
//
// (This is assuming on-modify-request/loadgroup observers will still be able
// to cancel synchronously)
if (mCanceled) {
// We may have been canceled already, either by on-modify-request
// listeners or by load group observers; in that case, don't create IPDL
// connection. See nsHttpChannel::AsyncOpen().
// Clear mCanceled here, or we will bail out at top of OnCancel().
mCanceled = false;
OnCancel(mStatus);
return NS_OK;
}
//
// Send request to the chrome process...
@ -798,7 +865,6 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
mAllowPipelining, mForceAllowThirdPartyCookie, mSendResumeAt,
mStartPos, mEntityID);
mState = HCC_OPENED;
return NS_OK;
}

View File

@ -140,6 +140,7 @@ protected:
bool RecvOnStopRequest(const nsresult& statusCode);
bool RecvOnProgress(const PRUint64& progress, const PRUint64& progressMax);
bool RecvOnStatus(const nsresult& status, const nsString& statusArg);
bool RecvCancelEarly(const nsresult& status);
bool RecvRedirect1Begin(PHttpChannelChild* newChannel,
const URI& newURI,
const PRUint32& redirectFlags,
@ -161,8 +162,6 @@ private:
// Current suspension depth for this channel object
PRUint32 mSuspendCount;
// FIXME: replace with IPDL states (bug 536319)
enum HttpChannelChildState mState;
bool mIPCOpen;
// Workaround for Necko re-entrancy dangers. We buffer IPDL messages in a
@ -196,6 +195,7 @@ private:
void OnStopRequest(const nsresult& statusCode);
void OnProgress(const PRUint64& progress, const PRUint64& progressMax);
void OnStatus(const nsresult& status, const nsString& statusArg);
void OnCancel(const nsresult& status);
void Redirect1Begin(PHttpChannelChild* newChannel, const URI& newURI,
const PRUint32& redirectFlags,
const nsHttpResponseHead& responseHead);
@ -207,6 +207,7 @@ private:
friend class DataAvailableEvent;
friend class ProgressEvent;
friend class StatusEvent;
friend class CancelEvent;
friend class Redirect1Event;
friend class Redirect3Event;
};

View File

@ -123,11 +123,11 @@ HttpChannelParent::RecvAsyncOpen(const IPC::URI& aURI,
nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
if (NS_FAILED(rv))
return false; // TODO: cancel request (bug 536317), return true
return SendCancelEarly(rv);
rv = NS_NewChannel(getter_AddRefs(mChannel), uri, ios, nsnull, nsnull, loadFlags);
if (NS_FAILED(rv))
return false; // TODO: cancel request (bug 536317), return true
return SendCancelEarly(rv);
nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
httpChan->SetRemoteChannel(true);
@ -159,9 +159,9 @@ HttpChannelParent::RecvAsyncOpen(const IPC::URI& aURI,
if (uploadStreamInfo != eUploadStream_null) {
nsCOMPtr<nsIInputStream> stream;
rv = NS_NewPostDataStream(getter_AddRefs(stream), false, uploadStreamData, 0);
if (!NS_SUCCEEDED(rv)) {
return false; // TODO: cancel request (bug 536317), return true
}
if (NS_FAILED(rv))
return SendCancelEarly(rv);
httpChan->InternalSetUploadStream(stream);
// We're casting uploadStreamInfo into PRBool here on purpose because
// we know possible values are either 0 or 1. See uploadStreamInfoType.
@ -176,7 +176,7 @@ HttpChannelParent::RecvAsyncOpen(const IPC::URI& aURI,
rv = httpChan->AsyncOpen(mChannelListener, nsnull);
if (NS_FAILED(rv))
return false; // TODO: cancel request (bug 536317), return true
return SendCancelEarly(rv);
return true;
}
@ -203,6 +203,18 @@ HttpChannelParent::RecvResume()
return true;
}
bool
HttpChannelParent::RecvCancel(const nsresult& status)
{
// May receive cancel before channel has been constructed!
if (mChannel) {
nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
httpChan->Cancel(status);
}
return true;
}
bool
HttpChannelParent::RecvSetCacheTokenCachedCharset(const nsCString& charset)
{
@ -281,19 +293,13 @@ HttpChannelParent::OnDataAvailable(nsIRequest *aRequest,
LOG(("HttpChannelParent::OnDataAvailable [this=%x]\n", this));
nsresult rv;
nsCString data;
data.SetLength(aCount);
char * p = data.BeginWriting();
PRUint32 bytesRead;
rv = aInputStream->Read(p, aCount, &bytesRead);
data.EndWriting();
if (!NS_SUCCEEDED(rv) || bytesRead != aCount) {
return rv; // TODO: cancel request locally (bug 536317)
}
if (mIPCClosed || !SendOnDataAvailable(data, aOffset, bytesRead))
rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
if (NS_FAILED(rv) ||
bytesRead != aCount ||
mIPCClosed ||
!SendOnDataAvailable(data, aOffset, bytesRead))
return NS_ERROR_UNEXPECTED;
return NS_OK;
}

View File

@ -102,6 +102,7 @@ protected:
virtual bool RecvSetCacheTokenCachedCharset(const nsCString& charset);
virtual bool RecvSuspend();
virtual bool RecvResume();
virtual bool RecvCancel(const nsresult& status);
virtual bool RecvRedirect2Result(const nsresult& result,
const RequestHeaderTuples& changedHeaders);

View File

@ -85,6 +85,8 @@ parent:
Suspend();
Resume();
Cancel(nsresult status);
// Reports approval/veto of redirect by child process redirect observers
Redirect2Result(nsresult result, RequestHeaderTuples changedHeaders);
@ -106,6 +108,10 @@ child:
OnStatus(nsresult status, nsString statusArg);
// Used to cancel child channel if we hit errors during creating and
// AsyncOpen of nsHttpChannel on the parent.
CancelEarly(nsresult status);
// Called to initiate content channel redirect, starts talking to sinks
// on the content process and reports result via OnRedirect2Result above
Redirect1Begin(PHttpChannel newChannel,

View File

@ -4,6 +4,7 @@
do_load_httpd_js();
const NS_BINDING_ABORTED = 0x804b0002;
const TEST_COUNT = 2;
var observer = {
QueryInterface: function eventsink_qi(iid) {
@ -33,8 +34,14 @@ var listener = {
},
onStopRequest: function test_onStopR(request, ctx, status) {
httpserv.stop(do_test_finished);
}
this.currentTest++;
if (this.currentTest == TEST_COUNT)
httpserv.stop(do_test_finished);
else
execute_test(this.currentTest)
},
currentTest : 0
};
function makeChan(url) {
@ -48,18 +55,31 @@ function makeChan(url) {
var httpserv = null;
function execute_test(id) {
if (id < 0 || id >= TEST_COUNT)
do_throw("Invalid test id.");
var chan = makeChan("http://localhost:4444/failtest");
if (id == 0) {
listener.shouldNotStart = true;
var obs = Components.classes["@mozilla.org/observer-service;1"].getService();
obs = obs.QueryInterface(Components.interfaces.nsIObserverService);
obs.addObserver(observer, "http-on-modify-request", false);
}
chan.asyncOpen(listener, null);
if (id == 1)
chan.cancel(NS_BINDING_ABORTED);
}
function run_test() {
httpserv = new nsHttpServer();
httpserv.registerPathHandler("/failtest", failtest);
httpserv.start(4444);
var obs = Components.classes["@mozilla.org/observer-service;1"].getService();
obs = obs.QueryInterface(Components.interfaces.nsIObserverService);
obs.addObserver(observer, "http-on-modify-request", false);
var chan = makeChan("http://localhost:4444/failtest");
chan.asyncOpen(listener, null);
execute_test(0);
do_test_pending();
}

View File

@ -0,0 +1,9 @@
//
// Run test script in content process instead of chrome (xpcshell's default)
//
function run_test() {
run_test_in_child("../unit/test_httpcancel.js");
dump("And here");
}