Bug 975338 - Enable diverting of HTTP and FTP channels back to parent process from child r=jduell

This commit is contained in:
Steve Workman 2014-03-10 23:04:28 +01:00
parent 6e87c814ed
commit c0d484b966
35 changed files with 1572 additions and 68 deletions

View File

@ -36,6 +36,7 @@ XPIDL_SOURCES += [
'nsICryptoHMAC.idl',
'nsIDashboard.idl',
'nsIDashboardEventNotifier.idl',
'nsIDivertableChannel.idl',
'nsIDownloader.idl',
'nsIEncodedChannel.idl',
'nsIExternalProtocolHandler.idl',

View File

@ -0,0 +1,61 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
%{C++
//#include "mozilla/net/ChannelDiverterChild.h"
namespace mozilla {
namespace net {
class ChannelDiverterChild;
}
}
%}
[ptr] native ChannelDiverterChild(mozilla::net::ChannelDiverterChild);
interface nsIStreamListener;
/**
* A channel implementing this interface allows diverting from an
* nsIStreamListener in the child process to one in the parent.
*/
[uuid(4430e0d0-ff70-45f5-99dc-b5fd06943fc1)]
interface nsIDivertableChannel : nsISupports
{
/**
* CHILD ONLY.
* Called by Necko client in child process during OnStartRequest to divert
* nsIStreamListener and nsIRequest callbacks to the parent process.
*
* The process should look like the following:
*
* 1) divertToParent is called in the child process. It can only be called
* during OnStartRequest().
*
* 2) The ChannelDiverterChild that is returned is an IPDL object. It should
* be passed via some other IPDL method of the client's choosing to the
* parent. On the parent the ChannelDiverterParent's divertTo() function
* should be called with an nsIStreamListener that will then receive the
* OnStartRequest/OnDataAvailable/OnStopRequest for the channel. The
* ChannelDiverterParent can then be deleted (which will also destroy the
* ChannelDiverterChild in the child).
*
* After divertToParent() has been called, NO further function calls
* should be made on the channel. It is a dead object for all purposes.
* The reference that the channel holds to the listener in the child is
* released is once OnStartRequest completes, and no other
* nsIStreamListener calls (OnDataAvailable, OnStopRequest) will be made
* to it.
*
* @return ChannelDiverterChild IPDL actor to be passed to parent process by
* client IPDL message, e.g. PClient.DivertUsing(PDiverterChild).
*
* @throws exception if the channel was canceled early. Throws status code of
* canceled channel.
*/
ChannelDiverterChild divertToParent();
};

View File

@ -0,0 +1,38 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef _adivertablechannelparent_h_
#define _adivertablechannelparent_h_
#include "nsISupports.h"
class nsIStreamListener;
namespace mozilla {
namespace net {
// To be implemented by a channel's parent actors, e.g. HttpChannelParent
// and FTPChannelParent. Used by ChannelDiverterParent to divert
// nsIStreamListener callbacks from the child process to a new
// listener in the parent process.
class ADivertableParentChannel : public nsISupports
{
public:
// Called by ChannelDiverterParent::DivertTo(nsIStreamListener*).
// The listener should now be used to received nsIStreamListener callbacks,
// i.e. OnStartRequest, OnDataAvailable and OnStopRequest, as if it had been
// passed to AsyncOpen for the channel. A reference to the listener will be
// added and kept until OnStopRequest has completed.
virtual void DivertTo(nsIStreamListener *aListener) = 0;
// Called to suspend parent channel in ChannelDiverterParent constructor.
virtual nsresult SuspendForDiversion() = 0;
};
} // namespace net
} // namespace mozilla
#endif

View File

@ -0,0 +1,27 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/net/ChannelDiverterChild.h"
#include "mozilla/net/NeckoChannelParams.h"
#include "mozilla/net/HttpChannelChild.h"
#include "mozilla/net/FTPChannelChild.h"
#include "mozilla/net/PHttpChannelChild.h"
#include "mozilla/net/PFTPChannelChild.h"
#include "nsIDivertableChannel.h"
namespace mozilla {
namespace net {
ChannelDiverterChild::ChannelDiverterChild()
{
}
ChannelDiverterChild::~ChannelDiverterChild()
{
}
} // namespace net
} // namespace mozilla

View File

@ -0,0 +1,30 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef _channeldiverterchild_h_
#define _channeldiverterchild_h_
#include "mozilla/net/PChannelDiverterChild.h"
class nsIDivertableChannel;
namespace mozilla {
namespace net {
class ChannelDiverterArgs;
class ChannelDiverterChild :
public PChannelDiverterChild
{
public:
ChannelDiverterChild();
virtual ~ChannelDiverterChild();
};
} // namespace net
} // namespace mozilla
#endif /* _channeldiverterchild_h_ */

View File

@ -0,0 +1,65 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/net/ChannelDiverterParent.h"
#include "mozilla/net/NeckoChannelParams.h"
#include "mozilla/net/HttpChannelParent.h"
#include "mozilla/net/FTPChannelParent.h"
#include "mozilla/net/PHttpChannelParent.h"
#include "mozilla/net/PFTPChannelParent.h"
#include "ADivertableParentChannel.h"
namespace mozilla {
namespace net {
ChannelDiverterParent::ChannelDiverterParent()
{
}
ChannelDiverterParent::~ChannelDiverterParent()
{
}
bool
ChannelDiverterParent::Init(const ChannelDiverterArgs& aChannel)
{
switch (aChannel.type()) {
case ChannelDiverterArgs::TPHttpChannelParent:
{
mDivertableChannelParent = static_cast<ADivertableParentChannel*>(
static_cast<HttpChannelParent*>(aChannel.get_PHttpChannelParent()));
break;
}
case ChannelDiverterArgs::TPFTPChannelParent:
{
mDivertableChannelParent = static_cast<ADivertableParentChannel*>(
static_cast<FTPChannelParent*>(aChannel.get_PFTPChannelParent()));
break;
}
default:
NS_NOTREACHED("unknown ChannelDiverterArgs type");
return false;
}
MOZ_ASSERT(mDivertableChannelParent);
nsresult rv = mDivertableChannelParent->SuspendForDiversion();
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
return true;
}
void
ChannelDiverterParent::DivertTo(nsIStreamListener* newListener)
{
MOZ_ASSERT(newListener);
MOZ_ASSERT(mDivertableChannelParent);
mDivertableChannelParent->DivertTo(newListener);
}
} // namespace net
} // namespace mozilla

View File

@ -0,0 +1,37 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef _channeldiverterparent_h_
#define _channeldiverterparent_h_
#include "mozilla/net/PChannelDiverterParent.h"
class nsIStreamListener;
namespace mozilla {
namespace net {
class ChannelDiverterArgs;
class ADivertableParentChannel;
class ChannelDiverterParent :
public PChannelDiverterParent
{
public:
ChannelDiverterParent();
virtual ~ChannelDiverterParent();
bool Init(const ChannelDiverterArgs& aChannel);
void DivertTo(nsIStreamListener* newListener);
private:
nsRefPtr<ADivertableParentChannel> mDivertableChannelParent;
};
} // namespace net
} // namespace mozilla
#endif /* _channeldiverterparent_h_ */

View File

@ -12,6 +12,8 @@ EXPORTS += [
]
EXPORTS.mozilla.net += [
'ChannelDiverterChild.h',
'ChannelDiverterParent.h',
'Dashboard.h',
'DashboardTypes.h',
]
@ -19,6 +21,8 @@ EXPORTS.mozilla.net += [
UNIFIED_SOURCES += [
'ArrayBufferInputStream.cpp',
'BackgroundFileSaver.cpp',
'ChannelDiverterChild.cpp',
'ChannelDiverterParent.cpp',
'Dashboard.cpp',
'EventTokenBucket.cpp',
'LoadContextInfo.cpp',
@ -113,6 +117,7 @@ include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'necko'
LOCAL_INCLUDES += [
'/dom/base',
'/netwerk/protocol/http'
]
if 'rtsp' in CONFIG['NECKO_PROTOCOLS']:

View File

@ -156,7 +156,7 @@ nsBaseChannel::ContinueRedirect()
bool
nsBaseChannel::HasContentTypeHint() const
{
NS_ASSERTION(!IsPending(), "HasContentTypeHint called too late");
NS_ASSERTION(!Pending(), "HasContentTypeHint called too late");
return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE);
}
@ -208,7 +208,7 @@ nsBaseChannel::BeginPumpingData()
return rv;
}
// By assigning mPump, we flag this channel as pending (see IsPending). It's
// By assigning mPump, we flag this channel as pending (see Pending). It's
// important that the pending flag is set when we call into the stream (the
// call to AsyncRead results in the stream's AsyncWait method being called)
// and especially when we call into the loadgroup. Our caller takes care to
@ -312,7 +312,7 @@ nsBaseChannel::GetName(nsACString &result)
NS_IMETHODIMP
nsBaseChannel::IsPending(bool *result)
{
*result = IsPending();
*result = Pending();
return NS_OK;
}
@ -725,7 +725,7 @@ nsBaseChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
if (NS_SUCCEEDED(mStatus))
mStatus = status;
// Cause IsPending to return false.
// Cause Pending to return false.
mPump = nullptr;
if (mListener) // null in case of redirect

View File

@ -157,9 +157,9 @@ public:
}
// This is a short-cut to calling nsIRequest::IsPending()
bool IsPending() const {
virtual bool Pending() const {
return mPump || mWaitingOnAsyncRedirect;
}
}
// Helper function for querying the channel's notification callbacks.
template <class T> void GetCallback(nsCOMPtr<T> &result) {

View File

@ -5,6 +5,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PHttpChannel;
include protocol PFTPChannel;
include URIParams;
include InputStreamParams;
@ -80,5 +82,11 @@ union FTPChannelCreationArgs
FTPChannelConnectArgs; // Used for redirected-to channels
};
union ChannelDiverterArgs
{
PHttpChannel;
PFTPChannel;
};
} // namespace ipc
} // namespace mozilla

View File

@ -16,6 +16,7 @@
#include "mozilla/net/WebSocketChannelChild.h"
#include "mozilla/net/DNSRequestChild.h"
#include "mozilla/net/RemoteOpenFileChild.h"
#include "mozilla/net/ChannelDiverterChild.h"
#include "mozilla/dom/network/TCPSocketChild.h"
#include "mozilla/dom/network/TCPServerSocketChild.h"
#include "mozilla/dom/network/UDPSocketChild.h"
@ -266,5 +267,18 @@ NeckoChild::DeallocPRemoteOpenFileChild(PRemoteOpenFileChild* aChild)
return true;
}
PChannelDiverterChild*
NeckoChild::AllocPChannelDiverterChild(const ChannelDiverterArgs& channel)
{
return new ChannelDiverterChild();;
}
bool
NeckoChild::DeallocPChannelDiverterChild(PChannelDiverterChild* child)
{
delete static_cast<ChannelDiverterChild*>(child);
return true;
}
}} // mozilla::net

View File

@ -62,6 +62,10 @@ protected:
virtual bool DeallocPRemoteOpenFileChild(PRemoteOpenFileChild*) MOZ_OVERRIDE;
virtual PRtspControllerChild* AllocPRtspControllerChild() MOZ_OVERRIDE;
virtual bool DeallocPRtspControllerChild(PRtspControllerChild*) MOZ_OVERRIDE;
virtual PChannelDiverterChild*
AllocPChannelDiverterChild(const ChannelDiverterArgs& channel) MOZ_OVERRIDE;
virtual bool
DeallocPChannelDiverterChild(PChannelDiverterChild* actor) MOZ_OVERRIDE;
};
/**

View File

@ -18,6 +18,7 @@
#endif
#include "mozilla/net/DNSRequestParent.h"
#include "mozilla/net/RemoteOpenFileParent.h"
#include "mozilla/net/ChannelDiverterParent.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/TabParent.h"
#include "mozilla/dom/network/TCPSocketParent.h"
@ -608,6 +609,28 @@ NeckoParent::RecvCancelHTMLDNSPrefetch(const nsString& hostname,
return true;
}
PChannelDiverterParent*
NeckoParent::AllocPChannelDiverterParent(const ChannelDiverterArgs& channel)
{
return new ChannelDiverterParent();
}
bool
NeckoParent::RecvPChannelDiverterConstructor(PChannelDiverterParent* actor,
const ChannelDiverterArgs& channel)
{
auto parent = static_cast<ChannelDiverterParent*>(actor);
parent->Init(channel);
return true;
}
bool
NeckoParent::DeallocPChannelDiverterParent(PChannelDiverterParent* parent)
{
delete static_cast<ChannelDiverterParent*>(parent);
return true;
}
void
NeckoParent::CloneManagees(ProtocolBase* aSource,
mozilla::ipc::ProtocolCloneContext* aCtx)

View File

@ -144,6 +144,14 @@ protected:
virtual PRtspControllerParent* AllocPRtspControllerParent() MOZ_OVERRIDE;
virtual bool DeallocPRtspControllerParent(PRtspControllerParent*) MOZ_OVERRIDE;
virtual PChannelDiverterParent*
AllocPChannelDiverterParent(const ChannelDiverterArgs& channel) MOZ_OVERRIDE;
virtual bool
RecvPChannelDiverterConstructor(PChannelDiverterParent* actor,
const ChannelDiverterArgs& channel) MOZ_OVERRIDE;
virtual bool DeallocPChannelDiverterParent(PChannelDiverterParent* actor)
MOZ_OVERRIDE;
private:
nsCString mCoreAppsBasePath;
nsCString mWebAppsBasePath;

View File

@ -0,0 +1,25 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80 ft=cpp: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PFTPChannel;
include protocol PHttpChannel;
include protocol PNecko;
namespace mozilla {
namespace net {
// Used when diverting necko channels from child back to the parent.
// See nsIDivertableChannel.
async protocol PChannelDiverter
{
manager PNecko;
child:
__delete__();
};
}// namespace net
}// namespace mozilla

View File

@ -17,6 +17,7 @@ include protocol PTCPServerSocket;
include protocol PUDPSocket;
include protocol PRemoteOpenFile;
include protocol PDNSRequest;
include protocol PChannelDiverter;
include protocol PBlob; //FIXME: bug #792908
include protocol PRtspController;
@ -45,6 +46,7 @@ sync protocol PNecko
manages PDNSRequest;
manages PRemoteOpenFile;
manages PRtspController;
manages PChannelDiverter;
parent:
__delete__();
@ -68,6 +70,7 @@ parent:
HTMLDNSPrefetch(nsString hostname, uint16_t flags);
CancelHTMLDNSPrefetch(nsString hostname, uint16_t flags, nsresult reason);
PRtspController();
PChannelDiverter(ChannelDiverterArgs channel);
both:
PTCPSocket();

View File

@ -31,6 +31,7 @@ UNIFIED_SOURCES += [
IPDL_SOURCES = [
'NeckoChannelParams.ipdlh',
'PChannelDiverter.ipdl',
'PNecko.ipdl',
'PRemoteOpenFile.ipdl',
'PRtspController.ipdl',

View File

@ -431,7 +431,7 @@ nsFileChannel::SetUploadStream(nsIInputStream *stream,
const nsACString &contentType,
int64_t contentLength)
{
NS_ENSURE_TRUE(!IsPending(), NS_ERROR_IN_PROGRESS);
NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
if ((mUploadStream = stream)) {
mUploadLength = contentLength;

View File

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/net/NeckoChild.h"
#include "mozilla/net/ChannelDiverterChild.h"
#include "mozilla/net/FTPChannelChild.h"
#include "mozilla/dom/TabChild.h"
#include "nsFtpProtocolHandler.h"
@ -33,6 +34,9 @@ FTPChannelChild::FTPChannelChild(nsIURI* uri)
, mWasOpened(false)
, mLastModifiedTime(0)
, mStartPos(0)
, mDivertingToParent(false)
, mFlushedForDiversion(false)
, mSuspendSent(false)
{
LOG(("Creating FTPChannelChild @%x\n", this));
// grab a reference to the handler to ensure that it doesn't go away.
@ -67,13 +71,14 @@ FTPChannelChild::ReleaseIPDLReference()
// FTPChannelChild::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ISUPPORTS_INHERITED5(FTPChannelChild,
NS_IMPL_ISUPPORTS_INHERITED6(FTPChannelChild,
nsBaseChannel,
nsIFTPChannel,
nsIUploadChannel,
nsIResumableChannel,
nsIProxiedChannel,
nsIChildChannel)
nsIChildChannel,
nsIDivertableChannel)
//-----------------------------------------------------------------------------
@ -242,6 +247,13 @@ FTPChannelChild::RecvOnStartRequest(const int64_t& aContentLength,
const nsCString& aEntityID,
const URIParams& aURI)
{
// mFlushedForDiversion and mDivertingToParent should NEVER be set at this
// stage, as they are set in the listener's OnStartRequest.
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"mFlushedForDiversion should be unset before OnStartRequest!");
MOZ_RELEASE_ASSERT(!mDivertingToParent,
"mDivertingToParent should be unset before OnStartRequest!");
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new FTPStartRequestEvent(this, aContentLength, aContentType,
aLastModified, aEntityID, aURI));
@ -261,6 +273,13 @@ FTPChannelChild::DoOnStartRequest(const int64_t& aContentLength,
{
LOG(("FTPChannelChild::RecvOnStartRequest [this=%p]\n", this));
// mFlushedForDiversion and mDivertingToParent should NEVER be set at this
// stage, as they are set in the listener's OnStartRequest.
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"mFlushedForDiversion should be unset before OnStartRequest!");
MOZ_RELEASE_ASSERT(!mDivertingToParent,
"mDivertingToParent should be unset before OnStartRequest!");
mContentLength = aContentLength;
SetContentType(aContentType);
mLastModifiedTime = aLastModified;
@ -275,6 +294,14 @@ FTPChannelChild::DoOnStartRequest(const int64_t& aContentLength,
nsresult rv = mListener->OnStartRequest(this, mListenerContext);
if (NS_FAILED(rv))
Cancel(rv);
if (mDivertingToParent) {
mListener = nullptr;
mListenerContext = nullptr;
if (mLoadGroup) {
mLoadGroup->RemoveRequest(this, nullptr, mStatus);
}
}
}
class FTPDataAvailableEvent : public ChannelEvent
@ -296,9 +323,15 @@ FTPChannelChild::RecvOnDataAvailable(const nsCString& data,
const uint64_t& offset,
const uint32_t& count)
{
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be receiving any more callbacks from parent!");
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new FTPDataAvailableEvent(this, data, offset, count));
} else {
MOZ_RELEASE_ASSERT(!mDivertingToParent,
"ShouldEnqueue when diverting to parent!");
DoOnDataAvailable(data, offset, count);
}
return true;
@ -311,6 +344,14 @@ FTPChannelChild::DoOnDataAvailable(const nsCString& data,
{
LOG(("FTPChannelChild::RecvOnDataAvailable [this=%p]\n", this));
if (mDivertingToParent) {
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be processing any more callbacks from parent!");
SendDivertOnDataAvailable(data, offset, count);
return;
}
if (mCanceled)
return;
@ -351,6 +392,9 @@ class FTPStopRequestEvent : public ChannelEvent
bool
FTPChannelChild::RecvOnStopRequest(const nsresult& statusCode)
{
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be receiving any more callbacks from parent!");
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new FTPStopRequestEvent(this, statusCode));
} else {
@ -365,6 +409,14 @@ FTPChannelChild::DoOnStopRequest(const nsresult& statusCode)
LOG(("FTPChannelChild::RecvOnStopRequest [this=%p status=%u]\n",
this, statusCode));
if (mDivertingToParent) {
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be processing any more callbacks from parent!");
SendDivertOnStopRequest(statusCode);
return;
}
if (!mCanceled)
mStatus = statusCode;
@ -430,6 +482,63 @@ FTPChannelChild::DoFailedAsyncOpen(const nsresult& statusCode)
Send__delete__(this);
}
class FTPFlushedForDiversionEvent : public ChannelEvent
{
public:
FTPFlushedForDiversionEvent(FTPChannelChild* aChild)
: mChild(aChild)
{
MOZ_RELEASE_ASSERT(aChild);
}
void Run()
{
mChild->FlushedForDiversion();
}
private:
FTPChannelChild* mChild;
};
bool
FTPChannelChild::RecvFlushedForDiversion()
{
MOZ_ASSERT(mDivertingToParent);
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new FTPFlushedForDiversionEvent(this));
} else {
MOZ_CRASH();
}
return true;
}
void
FTPChannelChild::FlushedForDiversion()
{
MOZ_RELEASE_ASSERT(mDivertingToParent);
// Once this is set, it should not be unset before FTPChannelChild is taken
// down. After it is set, no OnStart/OnData/OnStop callbacks should be
// received from the parent channel, nor dequeued from the ChannelEventQueue.
mFlushedForDiversion = true;
SendDivertComplete();
}
bool
FTPChannelChild::RecvDivertMessages()
{
MOZ_RELEASE_ASSERT(mDivertingToParent);
MOZ_RELEASE_ASSERT(mSuspendCount > 0);
// DivertTo() has been called on parent, so we can now start sending queued
// IPDL messages back to parent listener.
if (NS_WARN_IF(NS_FAILED(Resume()))) {
return false;
}
return true;
}
class FTPDeleteSelfEvent : public ChannelEvent
{
public:
@ -475,8 +584,13 @@ NS_IMETHODIMP
FTPChannelChild::Suspend()
{
NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
if (!mSuspendCount++) {
// SendSuspend only once, when suspend goes from 0 to 1.
// Don't SendSuspend at all if we're diverting callbacks to the parent;
// suspend will be called at the correct time in the parent itself.
if (!mSuspendCount++ && !mDivertingToParent) {
SendSuspend();
mSuspendSent = true;
}
mEventQ->Suspend();
@ -488,7 +602,11 @@ FTPChannelChild::Resume()
{
NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
if (!--mSuspendCount) {
// SendResume only once, when suspend count drops to 0.
// Don't SendResume at all if we're diverting callbacks to the parent (unless
// suspend was sent earlier); otherwise, resume will be called at the correct
// time in the parent itself.
if (!--mSuspendCount && (!mDivertingToParent || mSuspendSent)) {
SendResume();
}
mEventQ->Resume();
@ -552,6 +670,39 @@ FTPChannelChild::CompleteRedirectSetup(nsIStreamListener *listener,
return NS_OK;
}
//-----------------------------------------------------------------------------
// FTPChannelChild::nsIDivertableChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
FTPChannelChild::DivertToParent(ChannelDiverterChild **aChild)
{
MOZ_RELEASE_ASSERT(aChild);
MOZ_RELEASE_ASSERT(gNeckoChild);
MOZ_RELEASE_ASSERT(!mDivertingToParent);
// We must fail DivertToParent() if there's no parent end of the channel (and
// won't be!) due to early failure.
if (NS_FAILED(mStatus) && !mIPCOpen) {
return mStatus;
}
nsresult rv = Suspend();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Once this is set, it should not be unset before the child is taken down.
mDivertingToParent = true;
PChannelDiverterChild* diverter =
gNeckoChild->SendPChannelDiverterConstructor(this);
MOZ_RELEASE_ASSERT(diverter);
*aChild = static_cast<ChannelDiverterChild*>(diverter);
return NS_OK;
}
} // namespace net
} // namespace mozilla

View File

@ -16,6 +16,7 @@
#include "nsIProxiedChannel.h"
#include "nsIResumableChannel.h"
#include "nsIChildChannel.h"
#include "nsIDivertableChannel.h"
#include "nsIStreamListener.h"
#include "PrivateBrowsingChannel.h"
@ -35,6 +36,7 @@ class FTPChannelChild : public PFTPChannelChild
, public nsIResumableChannel
, public nsIProxiedChannel
, public nsIChildChannel
, public nsIDivertableChannel
{
public:
typedef ::nsIStreamListener nsIStreamListener;
@ -45,6 +47,7 @@ public:
NS_DECL_NSIRESUMABLECHANNEL
NS_DECL_NSIPROXIEDCHANNEL
NS_DECL_NSICHILDCHANNEL
NS_DECL_NSIDIVERTABLECHANNEL
NS_IMETHOD Cancel(nsresult status);
NS_IMETHOD Suspend();
@ -68,6 +71,8 @@ public:
bool IsSuspended();
void FlushedForDiversion();
protected:
bool RecvOnStartRequest(const int64_t& aContentLength,
const nsCString& aContentType,
@ -79,6 +84,8 @@ protected:
const uint32_t& count) MOZ_OVERRIDE;
bool RecvOnStopRequest(const nsresult& statusCode) MOZ_OVERRIDE;
bool RecvFailedAsyncOpen(const nsresult& statusCode) MOZ_OVERRIDE;
bool RecvFlushedForDiversion() MOZ_OVERRIDE;
bool RecvDivertMessages() MOZ_OVERRIDE;
bool RecvDeleteSelf() MOZ_OVERRIDE;
void DoOnStartRequest(const int64_t& aContentLength,
@ -112,6 +119,15 @@ private:
PRTime mLastModifiedTime;
uint64_t mStartPos;
nsCString mEntityID;
// Once set, OnData and possibly OnStop will be diverted to the parent.
bool mDivertingToParent;
// Once set, no OnStart/OnData/OnStop callbacks should be received from the
// parent channel, nor dequeued from the ChannelEventQueue.
bool mFlushedForDiversion;
// Set if SendSuspend is called. Determines if SendResume is needed when
// diverting callbacks to parent.
bool mSuspendSent;
};
inline bool

View File

@ -11,6 +11,7 @@
#include "nsFtpProtocolHandler.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/ipc/URIUtils.h"
#include "mozilla/unused.h"
#include "SerializedLoadContext.h"
using namespace mozilla::ipc;
@ -25,6 +26,10 @@ FTPChannelParent::FTPChannelParent(nsILoadContext* aLoadContext, PBOverrideStatu
: mIPCClosed(false)
, mLoadContext(aLoadContext)
, mPBOverride(aOverrideStatus)
, mStatus(NS_OK)
, mDivertingFromChild(false)
, mDivertedOnStartRequest(false)
, mSuspendedForDiversion(false)
{
nsIProtocolHandler* handler;
CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler);
@ -179,6 +184,87 @@ FTPChannelParent::RecvResume()
return true;
}
bool
FTPChannelParent::RecvDivertOnDataAvailable(const nsCString& data,
const uint64_t& offset,
const uint32_t& count)
{
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot RecvDivertOnDataAvailable if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return false;
}
// Drop OnDataAvailables if the parent was canceled already.
if (NS_FAILED(mStatus)) {
return true;
}
nsCOMPtr<nsIInputStream> stringStream;
nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
count, NS_ASSIGNMENT_DEPEND);
if (NS_FAILED(rv)) {
if (mChannel) {
mChannel->Cancel(rv);
}
mStatus = rv;
return true;
}
rv = OnDataAvailable(mChannel, nullptr, stringStream, offset, count);
stringStream->Close();
if (NS_FAILED(rv)) {
if (mChannel) {
mChannel->Cancel(rv);
}
mStatus = rv;
}
return true;
}
bool
FTPChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode)
{
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot RecvDivertOnStopRequest if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return false;
}
// Honor the channel's status even if the underlying transaction completed.
nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode;
// Reset fake pending status in case OnStopRequest has already been called.
if (mChannel) {
mChannel->ForcePending(false);
}
OnStopRequest(mChannel, nullptr, status);
return true;
}
bool
FTPChannelParent::RecvDivertComplete()
{
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot RecvDivertComplete if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return false;
}
nsresult rv = ResumeForDiversion();
if (NS_WARN_IF(NS_FAILED(rv))) {
FailDiversion(NS_ERROR_UNEXPECTED);
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// FTPChannelParent::nsIRequestObserver
//-----------------------------------------------------------------------------
@ -188,6 +274,12 @@ FTPChannelParent::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
{
LOG(("FTPChannelParent::OnStartRequest [this=%p]\n", this));
if (mDivertingFromChild) {
MOZ_RELEASE_ASSERT(mDivertToListener,
"Cannot divert if listener is unset!");
return mDivertToListener->OnStartRequest(aRequest, aContext);
}
nsCOMPtr<nsIChannel> chan = do_QueryInterface(aRequest);
MOZ_ASSERT(chan);
NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
@ -235,6 +327,12 @@ FTPChannelParent::OnStopRequest(nsIRequest* aRequest,
LOG(("FTPChannelParent::OnStopRequest: [this=%p status=%ul]\n",
this, aStatusCode));
if (mDivertingFromChild) {
MOZ_RELEASE_ASSERT(mDivertToListener,
"Cannot divert if listener is unset!");
return mDivertToListener->OnStopRequest(aRequest, aContext, aStatusCode);
}
if (mIPCClosed || !SendOnStopRequest(aStatusCode)) {
return NS_ERROR_UNEXPECTED;
}
@ -254,7 +352,14 @@ FTPChannelParent::OnDataAvailable(nsIRequest* aRequest,
uint32_t aCount)
{
LOG(("FTPChannelParent::OnDataAvailable [this=%p]\n", this));
if (mDivertingFromChild) {
MOZ_RELEASE_ASSERT(mDivertToListener,
"Cannot divert if listener is unset!");
return mDivertToListener->OnDataAvailable(aRequest, aContext, aInputStream,
aOffset, aCount);
}
nsCString data;
nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
if (NS_FAILED(rv))
@ -296,6 +401,195 @@ FTPChannelParent::GetInterface(const nsIID& uuid, void** result)
return QueryInterface(uuid, result);
}
//-----------------------------------------------------------------------------
// FTPChannelParent::ADivertableParentChannel
//-----------------------------------------------------------------------------
nsresult
FTPChannelParent::SuspendForDiversion()
{
MOZ_ASSERT(mChannel);
if (NS_WARN_IF(mDivertingFromChild)) {
MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!");
return NS_ERROR_UNEXPECTED;
}
// Try suspending the channel. Allow it to fail, since OnStopRequest may have
// been called and thus the channel may not be pending.
nsresult rv = mChannel->Suspend();
MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE);
mSuspendedForDiversion = NS_SUCCEEDED(rv);
// Once this is set, no more OnStart/OnData/OnStop callbacks should be sent
// to the child.
mDivertingFromChild = true;
return NS_OK;
}
/* private, supporting function for ADivertableParentChannel */
nsresult
FTPChannelParent::ResumeForDiversion()
{
MOZ_ASSERT(mChannel);
MOZ_ASSERT(mDivertToListener);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot ResumeForDiversion if not diverting!");
return NS_ERROR_UNEXPECTED;
}
if (mSuspendedForDiversion) {
nsresult rv = mChannel->Resume();
if (NS_WARN_IF(NS_FAILED(rv))) {
FailDiversion(NS_ERROR_UNEXPECTED, true);
return rv;
}
mSuspendedForDiversion = false;
}
// Delete() will tear down IPDL, but ref from underlying nsFTPChannel will
// keep us alive if there's more data to be delivered to listener.
if (NS_WARN_IF(NS_FAILED(Delete()))) {
FailDiversion(NS_ERROR_UNEXPECTED);
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
void
FTPChannelParent::DivertTo(nsIStreamListener *aListener)
{
MOZ_ASSERT(aListener);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot DivertTo new listener if diverting is not set!");
return;
}
if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) {
FailDiversion(NS_ERROR_UNEXPECTED);
return;
}
mDivertToListener = aListener;
// Call OnStartRequest and SendDivertMessages asynchronously to avoid
// reentering client context.
NS_DispatchToCurrentThread(
NS_NewRunnableMethod(this, &FTPChannelParent::StartDiversion));
return;
}
void
FTPChannelParent::StartDiversion()
{
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot StartDiversion if diverting is not set!");
return;
}
// Fake pending status in case OnStopRequest has already been called.
if (mChannel) {
mChannel->ForcePending(true);
}
// Call OnStartRequest for the "DivertTo" listener.
nsresult rv = OnStartRequest(mChannel, nullptr);
if (NS_FAILED(rv)) {
if (mChannel) {
mChannel->Cancel(rv);
}
mStatus = rv;
return;
}
// After OnStartRequest has been called, tell FTPChannelChild to divert the
// OnDataAvailables and OnStopRequest to this FTPChannelParent.
if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) {
FailDiversion(NS_ERROR_UNEXPECTED);
return;
}
}
class FTPFailDiversionEvent : public nsRunnable
{
public:
FTPFailDiversionEvent(FTPChannelParent *aChannelParent,
nsresult aErrorCode,
bool aSkipResume)
: mChannelParent(aChannelParent)
, mErrorCode(aErrorCode)
, mSkipResume(aSkipResume)
{
MOZ_RELEASE_ASSERT(aChannelParent);
MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
}
NS_IMETHOD Run()
{
mChannelParent->NotifyDiversionFailed(mErrorCode, mSkipResume);
return NS_OK;
}
private:
nsRefPtr<FTPChannelParent> mChannelParent;
nsresult mErrorCode;
bool mSkipResume;
};
void
FTPChannelParent::FailDiversion(nsresult aErrorCode,
bool aSkipResume)
{
MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
MOZ_RELEASE_ASSERT(mDivertingFromChild);
MOZ_RELEASE_ASSERT(mDivertToListener);
MOZ_RELEASE_ASSERT(mChannel);
NS_DispatchToCurrentThread(
new FTPFailDiversionEvent(this, aErrorCode, aSkipResume));
}
void
FTPChannelParent::NotifyDiversionFailed(nsresult aErrorCode,
bool aSkipResume)
{
MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
MOZ_RELEASE_ASSERT(mDivertingFromChild);
MOZ_RELEASE_ASSERT(mDivertToListener);
MOZ_RELEASE_ASSERT(mChannel);
mChannel->Cancel(aErrorCode);
mChannel->ForcePending(false);
bool isPending = false;
nsresult rv = mChannel->IsPending(&isPending);
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
// Resume only we suspended earlier.
if (mSuspendedForDiversion) {
mChannel->Resume();
}
// Channel has already sent OnStartRequest to the child, so ensure that we
// call it here if it hasn't already been called.
if (!mDivertedOnStartRequest) {
mChannel->ForcePending(true);
mDivertToListener->OnStartRequest(mChannel, nullptr);
mChannel->ForcePending(false);
}
// If the channel is pending, it will call OnStopRequest itself; otherwise, do
// it here.
if (!isPending) {
mDivertToListener->OnStopRequest(mChannel, nullptr, aErrorCode);
}
mDivertToListener = nullptr;
mChannel = nullptr;
if (!mIPCClosed) {
unused << SendDeleteSelf();
}
}
//---------------------
} // namespace net
} // namespace mozilla

View File

@ -8,6 +8,7 @@
#ifndef mozilla_net_FTPChannelParent_h
#define mozilla_net_FTPChannelParent_h
#include "ADivertableParentChannel.h"
#include "mozilla/net/PFTPChannelParent.h"
#include "mozilla/net/NeckoParent.h"
#include "nsIParentChannel.h"
@ -22,6 +23,7 @@ namespace net {
class FTPChannelParent : public PFTPChannelParent
, public nsIParentChannel
, public nsIInterfaceRequestor
, public ADivertableParentChannel
{
public:
NS_DECL_ISUPPORTS
@ -35,7 +37,26 @@ public:
bool Init(const FTPChannelCreationArgs& aOpenArgs);
// ADivertableParentChannel functions.
void DivertTo(nsIStreamListener *aListener) MOZ_OVERRIDE;
nsresult SuspendForDiversion() MOZ_OVERRIDE;
// Calls OnStartRequest for "DivertTo" listener, then notifies child channel
// that it should divert OnDataAvailable and OnStopRequest calls to this
// parent channel.
void StartDiversion();
// Handles calling OnStart/Stop if there are errors during diversion.
// Called asynchronously from FailDiversion.
void NotifyDiversionFailed(nsresult aErrorCode, bool aSkipResume = true);
protected:
// private, supporting function for ADivertableParentChannel.
nsresult ResumeForDiversion();
// Asynchronously calls NotifyDiversionFailed.
void FailDiversion(nsresult aErrorCode, bool aSkipResume = true);
bool DoAsyncOpen(const URIParams& aURI, const uint64_t& aStartPos,
const nsCString& aEntityID,
const OptionalInputStreamParams& aUploadStream);
@ -47,6 +68,11 @@ protected:
virtual bool RecvCancel(const nsresult& status) MOZ_OVERRIDE;
virtual bool RecvSuspend() MOZ_OVERRIDE;
virtual bool RecvResume() MOZ_OVERRIDE;
virtual bool RecvDivertOnDataAvailable(const nsCString& data,
const uint64_t& offset,
const uint32_t& count) MOZ_OVERRIDE;
virtual bool RecvDivertOnStopRequest(const nsresult& statusCode) MOZ_OVERRIDE;
virtual bool RecvDivertComplete() MOZ_OVERRIDE;
virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
@ -57,6 +83,22 @@ protected:
nsCOMPtr<nsILoadContext> mLoadContext;
PBOverrideStatus mPBOverride;
// If OnStart/OnData/OnStop have been diverted from the child, forward them to
// this listener.
nsCOMPtr<nsIStreamListener> mDivertToListener;
// Set to the canceled status value if the main channel was canceled.
nsresult mStatus;
// Once set, no OnStart/OnData/OnStop calls should be accepted; conversely, it
// must be set when RecvDivertOnData/~DivertOnStop/~DivertComplete are
// received from the child channel.
bool mDivertingFromChild;
// Set if OnStart|StopRequest was called during a diversion from the child.
bool mDivertedOnStartRequest;
// Set if we successfully suspended the nsHttpChannel for diversion. Unset
// when we call ResumeForDiversion.
bool mSuspendedForDiversion;
};
} // namespace net

View File

@ -32,12 +32,31 @@ parent:
Suspend();
Resume();
// Divert OnDataAvailable to the parent.
DivertOnDataAvailable(nsCString data,
uint64_t offset,
uint32_t count);
// Divert OnStopRequest to the parent.
DivertOnStopRequest(nsresult statusCode);
// Child has no more events/messages to divert to the parent.
DivertComplete();
child:
OnStartRequest(int64_t aContentLength, nsCString aContentType,
PRTime aLastModified, nsCString aEntityID, URIParams aURI);
OnDataAvailable(nsCString data, uint64_t offset, uint32_t count);
OnStopRequest(nsresult statusCode);
FailedAsyncOpen(nsresult statusCode);
// Parent has been suspended for diversion; no more events to be enqueued.
FlushedForDiversion();
// Child should resume processing the ChannelEventQueue, i.e. diverting any
// OnDataAvailable and OnStopRequest messages in the queue back to the parent.
DivertMessages();
DeleteSelf();
};

View File

@ -38,7 +38,7 @@ nsFtpChannel::SetUploadStream(nsIInputStream *stream,
const nsACString &contentType,
int64_t contentLength)
{
NS_ENSURE_TRUE(!IsPending(), NS_ERROR_IN_PROGRESS);
NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
mUploadStream = stream;
@ -61,7 +61,7 @@ nsFtpChannel::GetUploadStream(nsIInputStream **stream)
NS_IMETHODIMP
nsFtpChannel::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID)
{
NS_ENSURE_TRUE(!IsPending(), NS_ERROR_IN_PROGRESS);
NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
mEntityID = aEntityID;
mStartPos = aStartPos;
mResumeRequested = (mStartPos || !mEntityID.IsEmpty());
@ -196,3 +196,26 @@ nsFtpChannel::GetFTPEventSink(nsCOMPtr<nsIFTPEventSink> &aResult)
}
aResult = mFTPEventSink;
}
void
nsFtpChannel::ForcePending(bool aForcePending)
{
// Set true here so IsPending will return true.
// Required for callback diversion from child back to parent. In such cases
// OnStopRequest can be called in the parent before callbacks are diverted
// back from the child to the listener in the parent.
mForcePending = aForcePending;
}
NS_IMETHODIMP
nsFtpChannel::IsPending(bool *result)
{
*result = Pending();
return NS_OK;
}
bool
nsFtpChannel::Pending() const
{
return nsBaseChannel::Pending() || mForcePending;
}

View File

@ -36,6 +36,7 @@ public:
, mStartPos(0)
, mResumeRequested(false)
, mLastModifiedTime(0)
, mForcePending(false)
{
SetURI(uri);
}
@ -49,6 +50,12 @@ public:
mProxyInfo = pi;
}
NS_IMETHOD IsPending(bool *result) MOZ_OVERRIDE;
// This is a short-cut to calling nsIRequest::IsPending().
// Overrides Pending in nsBaseChannel.
bool Pending() const MOZ_OVERRIDE;
// Were we asked to resume a download?
bool ResumeRequested() { return mResumeRequested; }
@ -81,6 +88,9 @@ public:
// Helper function for getting the nsIFTPEventSink.
void GetFTPEventSink(nsCOMPtr<nsIFTPEventSink> &aResult);
public: /* Internal Necko use only. */
void ForcePending(bool aForcePending);
protected:
virtual ~nsFtpChannel() {}
virtual nsresult OpenContentStream(bool async, nsIInputStream **result,
@ -96,6 +106,7 @@ private:
nsCString mEntityID;
bool mResumeRequested;
PRTime mLastModifiedTime;
bool mForcePending;
};
#endif /* nsFTPChannel_h___ */

View File

@ -20,6 +20,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/ipc/URIUtils.h"
#include "mozilla/net/ChannelDiverterChild.h"
#include "mozilla/net/DNS.h"
#include "SerializedLoadContext.h"
@ -41,6 +42,9 @@ HttpChannelChild::HttpChannelChild()
, mSendResumeAt(false)
, mIPCOpen(false)
, mKeptAlive(false)
, mDivertingToParent(false)
, mFlushedForDiversion(false)
, mSuspendSent(false)
{
LOG(("Creating HttpChannelChild @%x\n", this));
@ -102,6 +106,7 @@ NS_INTERFACE_MAP_BEGIN(HttpChannelChild)
NS_INTERFACE_MAP_ENTRY(nsIChildChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannelChild)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAssociatedContentSecurity, GetAssociatedContentSecurity())
NS_INTERFACE_MAP_ENTRY(nsIDivertableChannel)
NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
//-----------------------------------------------------------------------------
@ -226,6 +231,13 @@ HttpChannelChild::RecvOnStartRequest(const nsHttpResponseHead& responseHead,
const NetAddr& selfAddr,
const NetAddr& peerAddr)
{
// mFlushedForDiversion and mDivertingToParent should NEVER be set at this
// stage, as they are set in the listener's OnStartRequest.
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"mFlushedForDiversion should be unset before OnStartRequest!");
MOZ_RELEASE_ASSERT(!mDivertingToParent,
"mDivertingToParent should be unset before OnStartRequest!");
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new StartRequestEvent(this, responseHead, useResponseHead,
requestHeaders, isFromCache,
@ -255,6 +267,13 @@ HttpChannelChild::OnStartRequest(const nsHttpResponseHead& responseHead,
{
LOG(("HttpChannelChild::RecvOnStartRequest [this=%p]\n", this));
// mFlushedForDiversion and mDivertingToParent should NEVER be set at this
// stage, as they are set in the listener's OnStartRequest.
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"mFlushedForDiversion should be unset before OnStartRequest!");
MOZ_RELEASE_ASSERT(!mDivertingToParent,
"mDivertingToParent should be unset before OnStartRequest!");
if (useResponseHead && !mCanceled)
mResponseHead = new nsHttpResponseHead(responseHead);
@ -286,6 +305,14 @@ HttpChannelChild::OnStartRequest(const nsHttpResponseHead& responseHead,
return;
}
if (mDivertingToParent) {
mListener = nullptr;
mListenerContext = nullptr;
if (mLoadGroup) {
mLoadGroup->RemoveRequest(this, nullptr, mStatus);
}
}
if (mResponseHead)
SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
@ -335,11 +362,17 @@ HttpChannelChild::RecvOnTransportAndData(const nsresult& status,
const uint64_t& offset,
const uint32_t& count)
{
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be receiving any more callbacks from parent!");
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new TransportAndDataEvent(this, status, progress,
progressMax, data, offset,
count));
} else {
MOZ_RELEASE_ASSERT(!mDivertingToParent,
"ShouldEnqueue when diverting to parent!");
OnTransportAndData(status, progress, progressMax, data, offset, count);
}
return true;
@ -355,6 +388,15 @@ HttpChannelChild::OnTransportAndData(const nsresult& status,
{
LOG(("HttpChannelChild::OnTransportAndData [this=%p]\n", this));
// For diversion to parent, just SendDivertOnDataAvailable.
if (mDivertingToParent) {
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be processing any more callbacks from parent!");
SendDivertOnDataAvailable(data, offset, count);
return;
}
if (mCanceled)
return;
@ -431,9 +473,14 @@ class StopRequestEvent : public ChannelEvent
bool
HttpChannelChild::RecvOnStopRequest(const nsresult& statusCode)
{
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be receiving any more callbacks from parent!");
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new StopRequestEvent(this, statusCode));
} else {
MOZ_ASSERT(!mDivertingToParent, "ShouldEnqueue when diverting to parent!");
OnStopRequest(statusCode);
}
return true;
@ -445,6 +492,14 @@ HttpChannelChild::OnStopRequest(const nsresult& statusCode)
LOG(("HttpChannelChild::OnStopRequest [this=%p status=%x]\n",
this, statusCode));
if (mDivertingToParent) {
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be processing any more callbacks from parent!");
SendDivertOnStopRequest(statusCode);
return;
}
mIsPending = false;
if (!mCanceled && NS_SUCCEEDED(mStatus))
@ -777,6 +832,60 @@ HttpChannelChild::RecvRedirect3Complete()
return true;
}
class HttpFlushedForDiversionEvent : public ChannelEvent
{
public:
HttpFlushedForDiversionEvent(HttpChannelChild* aChild)
: mChild(aChild)
{
MOZ_RELEASE_ASSERT(aChild);
}
void Run()
{
mChild->FlushedForDiversion();
}
private:
HttpChannelChild* mChild;
};
bool
HttpChannelChild::RecvFlushedForDiversion()
{
MOZ_RELEASE_ASSERT(mDivertingToParent);
MOZ_RELEASE_ASSERT(mEventQ->ShouldEnqueue());
mEventQ->Enqueue(new HttpFlushedForDiversionEvent(this));
return true;
}
void
HttpChannelChild::FlushedForDiversion()
{
MOZ_RELEASE_ASSERT(mDivertingToParent);
// Once this is set, it should not be unset before HttpChannelChild is taken
// down. After it is set, no OnStart/OnData/OnStop callbacks should be
// received from the parent channel, nor dequeued from the ChannelEventQueue.
mFlushedForDiversion = true;
SendDivertComplete();
}
bool
HttpChannelChild::RecvDivertMessages()
{
MOZ_RELEASE_ASSERT(mDivertingToParent);
MOZ_RELEASE_ASSERT(mSuspendCount > 0);
// DivertTo() has been called on parent, so we can now start sending queued
// IPDL messages back to parent listener.
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(Resume()));
return true;
}
void
HttpChannelChild::Redirect3Complete()
{
@ -943,8 +1052,13 @@ NS_IMETHODIMP
HttpChannelChild::Suspend()
{
NS_ENSURE_TRUE(RemoteChannelExists(), NS_ERROR_NOT_AVAILABLE);
if (!mSuspendCount++) {
// SendSuspend only once, when suspend goes from 0 to 1.
// Don't SendSuspend at all if we're diverting callbacks to the parent;
// suspend will be called at the correct time in the parent itself.
if (!mSuspendCount++ && !mDivertingToParent) {
SendSuspend();
mSuspendSent = true;
}
mEventQ->Suspend();
@ -959,7 +1073,11 @@ HttpChannelChild::Resume()
nsresult rv = NS_OK;
if (!--mSuspendCount) {
// SendResume only once, when suspend count drops to 0.
// Don't SendResume at all if we're diverting callbacks to the parent (unless
// suspend was sent earlier); otherwise, resume will be called at the correct
// time in the parent itself.
if (!--mSuspendCount && (!mDivertingToParent || mSuspendSent)) {
SendResume();
if (mCallOnResume) {
AsyncCall(mCallOnResume);
@ -1441,6 +1559,37 @@ NS_IMETHODIMP HttpChannelChild::GetClientSetRequestHeaders(RequestHeaderTuples *
return NS_OK;
}
//------------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIDivertableChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::DivertToParent(ChannelDiverterChild **aChild)
{
MOZ_RELEASE_ASSERT(aChild);
MOZ_RELEASE_ASSERT(gNeckoChild);
MOZ_RELEASE_ASSERT(!mDivertingToParent);
// We must fail DivertToParent() if there's no parent end of the channel (and
// won't be!) due to early failure.
if (NS_FAILED(mStatus) && !RemoteChannelExists()) {
return mStatus;
}
nsresult rv = Suspend();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Once this is set, it should not be unset before the child is taken down.
mDivertingToParent = true;
PChannelDiverterChild* diverter =
gNeckoChild->SendPChannelDiverterConstructor(this);
MOZ_RELEASE_ASSERT(diverter);
*aChild = static_cast<ChannelDiverterChild*>(diverter);
return NS_OK;
}
}} // mozilla::net

View File

@ -27,6 +27,7 @@
#include "nsIAssociatedContentSecurity.h"
#include "nsIChildChannel.h"
#include "nsIHttpChannelChild.h"
#include "nsIDivertableChannel.h"
#include "mozilla/net/DNS.h"
namespace mozilla {
@ -42,6 +43,7 @@ class HttpChannelChild : public PHttpChannelChild
, public nsIAssociatedContentSecurity
, public nsIChildChannel
, public nsIHttpChannelChild
, public nsIDivertableChannel
{
public:
NS_DECL_ISUPPORTS_INHERITED
@ -53,6 +55,7 @@ public:
NS_DECL_NSIASSOCIATEDCONTENTSECURITY
NS_DECL_NSICHILDCHANNEL
NS_DECL_NSIHTTPCHANNELCHILD
NS_DECL_NSIDIVERTABLECHANNEL
HttpChannelChild();
virtual ~HttpChannelChild();
@ -90,6 +93,8 @@ public:
bool IsSuspended();
void FlushedForDiversion();
protected:
bool RecvOnStartRequest(const nsHttpResponseHead& responseHead,
const bool& useResponseHead,
@ -118,6 +123,8 @@ protected:
bool RecvRedirect3Complete() MOZ_OVERRIDE;
bool RecvAssociateApplicationCache(const nsCString& groupID,
const nsCString& clientID) MOZ_OVERRIDE;
bool RecvFlushedForDiversion() MOZ_OVERRIDE;
bool RecvDivertMessages() MOZ_OVERRIDE;
bool RecvDeleteSelf() MOZ_OVERRIDE;
bool GetAssociatedContentSecurity(nsIAssociatedContentSecurity** res = nullptr);
@ -140,6 +147,15 @@ private:
bool mKeptAlive; // IPC kept open, but only for security info
nsRefPtr<ChannelEventQueue> mEventQ;
// Once set, OnData and possibly OnStop will be diverted to the parent.
bool mDivertingToParent;
// Once set, no OnStart/OnData/OnStop callbacks should be received from the
// parent channel, nor dequeued from the ChannelEventQueue.
bool mFlushedForDiversion;
// Set if SendSuspend is called. Determines if SendResume is needed when
// diverting callbacks to parent.
bool mSuspendSent;
// true after successful AsyncOpen until OnStopRequest completes.
bool RemoteChannelExists() { return mIPCOpen && !mKeptAlive; }

View File

@ -43,6 +43,10 @@ HttpChannelParent::HttpChannelParent(PBrowserParent* iframeEmbedding,
, mReceivedRedirect2Verify(false)
, mPBOverride(aOverrideStatus)
, mLoadContext(aLoadContext)
, mStatus(NS_OK)
, mDivertingFromChild(false)
, mDivertedOnStartRequest(false)
, mSuspendedForDiversion(false)
{
// Ensure gHttpHandler is initialized: we need the atom table up and running.
nsCOMPtr<nsIHttpProtocolHandler> dummyInitializer =
@ -179,57 +183,57 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI,
if (NS_FAILED(rv))
return SendFailedAsyncOpen(rv);
rv = NS_NewChannel(getter_AddRefs(mChannel), uri, ios, nullptr, nullptr, loadFlags);
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, nullptr, nullptr, loadFlags);
if (NS_FAILED(rv))
return SendFailedAsyncOpen(rv);
nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
mChannel = static_cast<nsHttpChannel *>(channel.get());
if (mPBOverride != kPBOverride_Unset) {
httpChan->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
mChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
}
if (doResumeAt)
httpChan->ResumeAt(startPos, entityID);
mChannel->ResumeAt(startPos, entityID);
if (originalUri)
httpChan->SetOriginalURI(originalUri);
mChannel->SetOriginalURI(originalUri);
if (docUri)
httpChan->SetDocumentURI(docUri);
mChannel->SetDocumentURI(docUri);
if (referrerUri)
httpChan->SetReferrerInternal(referrerUri);
mChannel->SetReferrerInternal(referrerUri);
if (apiRedirectToUri)
httpChan->RedirectTo(apiRedirectToUri);
mChannel->RedirectTo(apiRedirectToUri);
if (loadFlags != nsIRequest::LOAD_NORMAL)
httpChan->SetLoadFlags(loadFlags);
mChannel->SetLoadFlags(loadFlags);
for (uint32_t i = 0; i < requestHeaders.Length(); i++) {
httpChan->SetRequestHeader(requestHeaders[i].mHeader,
mChannel->SetRequestHeader(requestHeaders[i].mHeader,
requestHeaders[i].mValue,
requestHeaders[i].mMerge);
}
nsRefPtr<HttpChannelParentListener> channelListener =
new HttpChannelParentListener(this);
mParentListener = new HttpChannelParentListener(this);
httpChan->SetNotificationCallbacks(channelListener);
mChannel->SetNotificationCallbacks(mParentListener);
httpChan->SetRequestMethod(nsDependentCString(requestMethod.get()));
mChannel->SetRequestMethod(nsDependentCString(requestMethod.get()));
nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(uploadStream);
if (stream) {
httpChan->InternalSetUploadStream(stream);
httpChan->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
mChannel->InternalSetUploadStream(stream);
mChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
}
if (priority != nsISupportsPriority::PRIORITY_NORMAL)
httpChan->SetPriority(priority);
httpChan->SetRedirectionLimit(redirectionLimit);
httpChan->SetAllowPipelining(allowPipelining);
httpChan->SetForceAllowThirdPartyCookie(forceAllowThirdPartyCookie);
httpChan->SetAllowSpdy(allowSpdy);
mChannel->SetPriority(priority);
mChannel->SetRedirectionLimit(redirectionLimit);
mChannel->SetAllowPipelining(allowPipelining);
mChannel->SetForceAllowThirdPartyCookie(forceAllowThirdPartyCookie);
mChannel->SetAllowSpdy(allowSpdy);
nsCOMPtr<nsIApplicationCacheChannel> appCacheChan =
do_QueryInterface(mChannel);
do_QueryObject(mChannel);
nsCOMPtr<nsIApplicationCacheService> appCacheService =
do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
@ -273,7 +277,7 @@ HttpChannelParent::DoAsyncOpen( const URIParams& aURI,
}
}
rv = httpChan->AsyncOpen(channelListener, nullptr);
rv = mChannel->AsyncOpen(mParentListener, nullptr);
if (NS_FAILED(rv))
return SendFailedAsyncOpen(rv);
@ -286,12 +290,14 @@ HttpChannelParent::ConnectChannel(const uint32_t& channelId)
nsresult rv;
LOG(("Looking for a registered channel [this=%p, id=%d]", this, channelId));
rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(mChannel));
nsCOMPtr<nsIChannel> channel;
rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel));
mChannel = static_cast<nsHttpChannel*>(channel.get());
LOG((" found channel %p, rv=%08x", mChannel.get(), rv));
if (mPBOverride != kPBOverride_Unset) {
// redirected-to channel may not support PB
nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(mChannel);
nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryObject(mChannel);
if (pbChannel) {
pbChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
}
@ -304,8 +310,7 @@ bool
HttpChannelParent::RecvSetPriority(const uint16_t& priority)
{
if (mChannel) {
nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
httpChan->SetPriority(priority);
mChannel->SetPriority(priority);
}
nsCOMPtr<nsISupportsPriority> priorityRedirectChannel =
@ -339,8 +344,7 @@ 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);
mChannel->Cancel(status);
}
return true;
}
@ -435,6 +439,92 @@ HttpChannelParent::RecvMarkOfflineCacheEntryAsForeign()
return true;
}
bool
HttpChannelParent::RecvDivertOnDataAvailable(const nsCString& data,
const uint64_t& offset,
const uint32_t& count)
{
MOZ_ASSERT(mParentListener);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot RecvDivertOnDataAvailable if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return false;
}
// Drop OnDataAvailables if the parent was canceled already.
if (NS_FAILED(mStatus)) {
return true;
}
nsCOMPtr<nsIInputStream> stringStream;
nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
count, NS_ASSIGNMENT_DEPEND);
if (NS_FAILED(rv)) {
if (mChannel) {
mChannel->Cancel(rv);
}
mStatus = rv;
return true;
}
rv = mParentListener->OnDataAvailable(mChannel, nullptr, stringStream,
offset, count);
stringStream->Close();
if (NS_FAILED(rv)) {
if (mChannel) {
mChannel->Cancel(rv);
}
mStatus = rv;
return true;
}
return true;
}
bool
HttpChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode)
{
MOZ_ASSERT(mParentListener);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot RecvDivertOnStopRequest if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return false;
}
// Honor the channel's status even if the underlying transaction completed.
nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode;
// Reset fake pending status in case OnStopRequest has already been called.
if (mChannel) {
mChannel->ForcePending(false);
}
mParentListener->OnStopRequest(mChannel, nullptr, status);
return true;
}
bool
HttpChannelParent::RecvDivertComplete()
{
MOZ_ASSERT(mParentListener);
mParentListener = nullptr;
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot RecvDivertComplete if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return false;
}
nsresult rv = ResumeForDiversion();
if (NS_WARN_IF(NS_FAILED(rv))) {
FailDiversion(NS_ERROR_UNEXPECTED);
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIRequestObserver
//-----------------------------------------------------------------------------
@ -444,6 +534,9 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
{
LOG(("HttpChannelParent::OnStartRequest [this=%p]\n", this));
MOZ_RELEASE_ASSERT(!mDivertingFromChild,
"Cannot call OnStartRequest if diverting is set!");
nsHttpChannel *chan = static_cast<nsHttpChannel *>(aRequest);
nsHttpResponseHead *responseHead = chan->GetResponseHead();
nsHttpRequestHead *requestHead = chan->GetRequestHead();
@ -492,7 +585,6 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
NS_SerializeToString(secInfoSer, secInfoSerialization);
}
nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
if (mIPCClosed ||
!SendOnStartRequest(responseHead ? *responseHead : nsHttpResponseHead(),
!!responseHead,
@ -500,7 +592,7 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
isFromCache,
mCacheEntry ? true : false,
expirationTime, cachedCharset, secInfoSerialization,
httpChan->GetSelfAddr(), httpChan->GetPeerAddr()))
mChannel->GetSelfAddr(), mChannel->GetPeerAddr()))
{
return NS_ERROR_UNEXPECTED;
}
@ -515,6 +607,9 @@ HttpChannelParent::OnStopRequest(nsIRequest *aRequest,
LOG(("HttpChannelParent::OnStopRequest: [this=%p status=%x]\n",
this, aStatusCode));
MOZ_RELEASE_ASSERT(!mDivertingFromChild,
"Cannot call OnStopRequest if diverting is set!");
if (mIPCClosed || !SendOnStopRequest(aStatusCode))
return NS_ERROR_UNEXPECTED;
return NS_OK;
@ -533,6 +628,9 @@ HttpChannelParent::OnDataAvailable(nsIRequest *aRequest,
{
LOG(("HttpChannelParent::OnDataAvailable [this=%p]\n", this));
MOZ_RELEASE_ASSERT(!mDivertingFromChild,
"Cannot call OnDataAvailable if diverting is set!");
nsCString data;
nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
if (NS_FAILED(rv))
@ -629,8 +727,7 @@ HttpChannelParent::StartRedirect(uint32_t newChannelId,
URIParams uriParams;
SerializeURI(newURI, uriParams);
nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get());
nsHttpResponseHead *responseHead = httpChan->GetResponseHead();
nsHttpResponseHead *responseHead = mChannel->GetResponseHead();
bool result = SendRedirect1Begin(newChannelId, uriParams, redirectFlags,
responseHead ? *responseHead
: nsHttpResponseHead());
@ -662,4 +759,196 @@ HttpChannelParent::CompleteRedirect(bool succeeded)
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelParent::ADivertableParentChannel
//-----------------------------------------------------------------------------
nsresult
HttpChannelParent::SuspendForDiversion()
{
MOZ_ASSERT(mChannel);
MOZ_ASSERT(mParentListener);
if (NS_WARN_IF(mDivertingFromChild)) {
MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!");
return NS_ERROR_UNEXPECTED;
}
// Try suspending the channel. Allow it to fail, since OnStopRequest may have
// been called and thus the channel may not be pending.
nsresult rv = mChannel->Suspend();
MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE);
mSuspendedForDiversion = NS_SUCCEEDED(rv);
rv = mParentListener->SuspendForDiversion();
MOZ_ASSERT(NS_SUCCEEDED(rv));
// Once this is set, no more OnStart/OnData/OnStop callbacks should be sent
// to the child.
mDivertingFromChild = true;
return NS_OK;
}
/* private, supporting function for ADivertableParentChannel */
nsresult
HttpChannelParent::ResumeForDiversion()
{
MOZ_ASSERT(mChannel);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot ResumeForDiversion if not diverting!");
return NS_ERROR_UNEXPECTED;
}
if (mSuspendedForDiversion) {
// The nsHttpChannel will deliver remaining OnData/OnStop for the transfer.
nsresult rv = mChannel->Resume();
if (NS_WARN_IF(NS_FAILED(rv))) {
FailDiversion(NS_ERROR_UNEXPECTED, true);
return rv;
}
mSuspendedForDiversion = false;
}
if (NS_WARN_IF(mIPCClosed || !SendDeleteSelf())) {
FailDiversion(NS_ERROR_UNEXPECTED);
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
void
HttpChannelParent::DivertTo(nsIStreamListener *aListener)
{
MOZ_ASSERT(mParentListener);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot DivertTo new listener if diverting is not set!");
return;
}
DebugOnly<nsresult> rv = mParentListener->DivertTo(aListener);
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) {
FailDiversion(NS_ERROR_UNEXPECTED);
return;
}
// Call OnStartRequest and SendDivertMessages asynchronously to avoid
// reentering client context.
NS_DispatchToCurrentThread(
NS_NewRunnableMethod(this, &HttpChannelParent::StartDiversion));
return;
}
void
HttpChannelParent::StartDiversion()
{
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot StartDiversion if diverting is not set!");
return;
}
// Fake pending status in case OnStopRequest has already been called.
if (mChannel) {
mChannel->ForcePending(true);
}
// Call OnStartRequest for the "DivertTo" listener.
nsresult rv = mParentListener->OnStartRequest(mChannel, nullptr);
if (NS_FAILED(rv)) {
if (mChannel) {
mChannel->Cancel(rv);
}
mStatus = rv;
}
mDivertedOnStartRequest = true;
// After OnStartRequest has been called, tell HttpChannelChild to divert the
// OnDataAvailables and OnStopRequest to this HttpChannelParent.
if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) {
FailDiversion(NS_ERROR_UNEXPECTED);
return;
}
}
class HTTPFailDiversionEvent : public nsRunnable
{
public:
HTTPFailDiversionEvent(HttpChannelParent *aChannelParent,
nsresult aErrorCode,
bool aSkipResume)
: mChannelParent(aChannelParent)
, mErrorCode(aErrorCode)
, mSkipResume(aSkipResume)
{
MOZ_RELEASE_ASSERT(aChannelParent);
MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
}
NS_IMETHOD Run()
{
mChannelParent->NotifyDiversionFailed(mErrorCode, mSkipResume);
return NS_OK;
}
private:
nsRefPtr<HttpChannelParent> mChannelParent;
nsresult mErrorCode;
bool mSkipResume;
};
void
HttpChannelParent::FailDiversion(nsresult aErrorCode,
bool aSkipResume)
{
MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
MOZ_RELEASE_ASSERT(mDivertingFromChild);
MOZ_RELEASE_ASSERT(mParentListener);
MOZ_RELEASE_ASSERT(mChannel);
NS_DispatchToCurrentThread(
new HTTPFailDiversionEvent(this, aErrorCode, aSkipResume));
}
void
HttpChannelParent::NotifyDiversionFailed(nsresult aErrorCode,
bool aSkipResume)
{
MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode));
MOZ_RELEASE_ASSERT(mDivertingFromChild);
MOZ_RELEASE_ASSERT(mParentListener);
MOZ_RELEASE_ASSERT(mChannel);
mChannel->Cancel(aErrorCode);
mChannel->ForcePending(false);
bool isPending = false;
nsresult rv = mChannel->IsPending(&isPending);
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
// Resume only if we suspended earlier.
if (mSuspendedForDiversion) {
mChannel->Resume();
}
// Channel has already sent OnStartRequest to the child, so ensure that we
// call it here if it hasn't already been called.
if (!mDivertedOnStartRequest) {
mChannel->ForcePending(true);
mParentListener->OnStartRequest(mChannel, nullptr);
mChannel->ForcePending(false);
}
// If the channel is pending, it will call OnStopRequest itself; otherwise, do
// it here.
if (!isPending) {
mParentListener->OnStopRequest(mChannel, nullptr, aErrorCode);
}
mParentListener = nullptr;
mChannel = nullptr;
if (!mIPCClosed) {
unused << SendDeleteSelf();
}
}
}} // mozilla::net

View File

@ -8,6 +8,7 @@
#ifndef mozilla_net_HttpChannelParent_h
#define mozilla_net_HttpChannelParent_h
#include "ADivertableParentChannel.h"
#include "nsHttp.h"
#include "mozilla/dom/PBrowserParent.h"
#include "mozilla/net/PHttpChannelParent.h"
@ -34,6 +35,7 @@ class HttpChannelParent : public PHttpChannelParent
, public nsIParentRedirectingChannel
, public nsIProgressEventSink
, public nsIInterfaceRequestor
, public ADivertableParentChannel
{
public:
NS_DECL_ISUPPORTS
@ -51,6 +53,19 @@ public:
bool Init(const HttpChannelCreationArgs& aOpenArgs);
// ADivertableParentChannel functions.
void DivertTo(nsIStreamListener *aListener) MOZ_OVERRIDE;
nsresult SuspendForDiversion() MOZ_OVERRIDE;
// Calls OnStartRequest for "DivertTo" listener, then notifies child channel
// that it should divert OnDataAvailable and OnStopRequest calls to this
// parent channel.
void StartDiversion();
// Handles calling OnStart/Stop if there are errors during diversion.
// Called asynchronously from FailDiversion.
void NotifyDiversionFailed(nsresult aErrorCode, bool aSkipResume = true);
protected:
// used to connect redirected-to channel in parent with just created
// ChildChannel. Used during redirects.
@ -89,15 +104,24 @@ protected:
const int32_t& no) MOZ_OVERRIDE;
virtual bool RecvDocumentChannelCleanup() MOZ_OVERRIDE;
virtual bool RecvMarkOfflineCacheEntryAsForeign() MOZ_OVERRIDE;
virtual bool RecvDivertOnDataAvailable(const nsCString& data,
const uint64_t& offset,
const uint32_t& count) MOZ_OVERRIDE;
virtual bool RecvDivertOnStopRequest(const nsresult& statusCode) MOZ_OVERRIDE;
virtual bool RecvDivertComplete() MOZ_OVERRIDE;
virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
protected:
// Supporting function for ADivertableParentChannel.
nsresult ResumeForDiversion();
// Asynchronously calls NotifyDiversionFailed.
void FailDiversion(nsresult aErrorCode, bool aSkipResume = true);
friend class HttpChannelParentListener;
nsRefPtr<mozilla::dom::TabParent> mTabParent;
private:
nsCOMPtr<nsIChannel> mChannel;
nsRefPtr<nsHttpChannel> mChannel;
nsCOMPtr<nsICacheEntry> mCacheEntry;
nsCOMPtr<nsIAssociatedContentSecurity> mAssociatedContentSecurity;
bool mIPCClosed; // PHttpChannel actor has been Closed()
@ -121,6 +145,19 @@ private:
nsCOMPtr<nsILoadContext> mLoadContext;
nsRefPtr<nsHttpHandler> mHttpHandler;
nsRefPtr<HttpChannelParentListener> mParentListener;
// Set to the canceled status value if the main channel was canceled.
nsresult mStatus;
// Once set, no OnStart/OnData/OnStop calls should be accepted; conversely, it
// must be set when RecvDivertOnData/~DivertOnStop/~DivertComplete are
// received from the child channel.
bool mDivertingFromChild;
// Set if OnStart|StopRequest was called during a diversion from the child.
bool mDivertedOnStartRequest;
bool mSuspendedForDiversion;
};
} // namespace net

View File

@ -19,8 +19,9 @@ namespace mozilla {
namespace net {
HttpChannelParentListener::HttpChannelParentListener(HttpChannelParent* aInitialChannel)
: mActiveChannel(aInitialChannel)
: mNextListener(aInitialChannel)
, mRedirectChannelId(0)
, mSuspendedForDiversion(false)
{
}
@ -46,11 +47,14 @@ NS_IMPL_ISUPPORTS5(HttpChannelParentListener,
NS_IMETHODIMP
HttpChannelParentListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
{
if (!mActiveChannel)
MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
"Cannot call OnStartRequest if suspended for diversion!");
if (!mNextListener)
return NS_ERROR_UNEXPECTED;
LOG(("HttpChannelParentListener::OnStartRequest [this=%p]\n", this));
return mActiveChannel->OnStartRequest(aRequest, aContext);
return mNextListener->OnStartRequest(aRequest, aContext);
}
NS_IMETHODIMP
@ -58,14 +62,17 @@ HttpChannelParentListener::OnStopRequest(nsIRequest *aRequest,
nsISupports *aContext,
nsresult aStatusCode)
{
if (!mActiveChannel)
MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
"Cannot call OnStopRequest if suspended for diversion!");
if (!mNextListener)
return NS_ERROR_UNEXPECTED;
LOG(("HttpChannelParentListener::OnStopRequest: [this=%p status=%ul]\n",
this, aStatusCode));
nsresult rv = mActiveChannel->OnStopRequest(aRequest, aContext, aStatusCode);
nsresult rv = mNextListener->OnStopRequest(aRequest, aContext, aStatusCode);
mActiveChannel = nullptr;
mNextListener = nullptr;
return rv;
}
@ -80,11 +87,14 @@ HttpChannelParentListener::OnDataAvailable(nsIRequest *aRequest,
uint64_t aOffset,
uint32_t aCount)
{
if (!mActiveChannel)
MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
"Cannot call OnDataAvailable if suspended for diversion!");
if (!mNextListener)
return NS_ERROR_UNEXPECTED;
LOG(("HttpChannelParentListener::OnDataAvailable [this=%p]\n", this));
return mActiveChannel->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
return mNextListener->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
}
//-----------------------------------------------------------------------------
@ -102,8 +112,8 @@ HttpChannelParentListener::GetInterface(const nsIID& aIID, void **result)
}
nsCOMPtr<nsIInterfaceRequestor> ir;
if (mActiveChannel &&
NS_SUCCEEDED(CallQueryInterface(mActiveChannel.get(),
if (mNextListener &&
NS_SUCCEEDED(CallQueryInterface(mNextListener.get(),
getter_AddRefs(ir))))
{
return ir->GetInterface(aIID, result);
@ -136,7 +146,7 @@ HttpChannelParentListener::AsyncOnChannelRedirect(
LOG(("Registered %p channel under id=%d", newChannel, mRedirectChannelId));
nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
do_QueryInterface(mActiveChannel);
do_QueryInterface(mNextListener);
if (!activeRedirectingChannel) {
NS_RUNTIMEABORT("Channel got a redirect response, but doesn't implement "
"nsIParentRedirectingChannel to handle it.");
@ -190,7 +200,7 @@ HttpChannelParentListener::OnRedirectResult(bool succeeded)
}
nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
do_QueryInterface(mActiveChannel);
do_QueryInterface(mNextListener);
MOZ_ASSERT(activeRedirectingChannel,
"Channel finished a redirect response, but doesn't implement "
"nsIParentRedirectingChannel to complete it.");
@ -203,8 +213,12 @@ HttpChannelParentListener::OnRedirectResult(bool succeeded)
if (succeeded) {
// Switch to redirect channel and delete the old one.
mActiveChannel->Delete();
mActiveChannel = redirectChannel;
nsCOMPtr<nsIParentChannel> parent;
parent = do_QueryInterface(mNextListener);
MOZ_ASSERT(parent);
parent->Delete();
mNextListener = do_QueryInterface(redirectChannel);
MOZ_ASSERT(mNextListener);
} else if (redirectChannel) {
// Delete the redirect target channel: continue using old channel
redirectChannel->Delete();
@ -213,4 +227,43 @@ HttpChannelParentListener::OnRedirectResult(bool succeeded)
return NS_OK;
}
//-----------------------------------------------------------------------------
nsresult
HttpChannelParentListener::SuspendForDiversion()
{
if (NS_WARN_IF(mSuspendedForDiversion)) {
MOZ_ASSERT(!mSuspendedForDiversion, "Cannot SuspendForDiversion twice!");
return NS_ERROR_UNEXPECTED;
}
// While this is set, no OnStart/OnData/OnStop callbacks should be forwarded
// to mNextListener.
mSuspendedForDiversion = true;
return NS_OK;
}
nsresult
HttpChannelParentListener::ResumeForDiversion()
{
MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
// Allow OnStart/OnData/OnStop callbacks to be forwarded to mNextListener.
mSuspendedForDiversion = false;
return NS_OK;
}
nsresult
HttpChannelParentListener::DivertTo(nsIStreamListener* aListener)
{
MOZ_ASSERT(aListener);
MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
mNextListener = aListener;
return ResumeForDiversion();
}
}} // mozilla::net

View File

@ -35,9 +35,22 @@ public:
HttpChannelParentListener(HttpChannelParent* aInitialChannel);
virtual ~HttpChannelParentListener();
// For channel diversion from child to parent.
nsresult DivertTo(nsIStreamListener *aListener);
nsresult SuspendForDiversion();
private:
nsCOMPtr<nsIParentChannel> mActiveChannel;
// Private partner function to SuspendForDiversion.
nsresult ResumeForDiversion();
// Can be the original HttpChannelParent that created this object (normal
// case), a different {HTTP|FTP}ChannelParent that we've been redirected to,
// or some other listener that we have been diverted to via
// nsIDivertableChannel.
nsCOMPtr<nsIStreamListener> mNextListener;
uint32_t mRedirectChannelId;
// When set, no OnStart/OnData/OnStop calls should be received.
bool mSuspendedForDiversion;
};
} // namespace net

View File

@ -66,6 +66,17 @@ parent:
// again, indefinitely.
MarkOfflineCacheEntryAsForeign();
// Divert OnDataAvailable to the parent.
DivertOnDataAvailable(nsCString data,
uint64_t offset,
uint32_t count);
// Divert OnStopRequest to the parent.
DivertOnStopRequest(nsresult statusCode);
// Child has no more events/messages to divert to the parent.
DivertComplete();
__delete__();
child:
@ -113,6 +124,13 @@ child:
AssociateApplicationCache(nsCString groupID,
nsCString clientID);
// Parent has been suspended for diversion; no more events to be enqueued.
FlushedForDiversion();
// Child should resume processing the ChannelEventQueue, i.e. diverting any
// OnDataAvailable and OnStopRequest messages in the queue back to the parent.
DivertMessages();
// Tell child to delete channel (all IPDL deletes must be done from child to
// avoid races: see bug 591708).
DeleteSelf();

View File

@ -220,6 +220,7 @@ nsHttpChannel::nsHttpChannel()
, mIsPartialRequest(0)
, mHasAutoRedirectVetoNotifier(0)
, mDidReval(false)
, mForcePending(false)
{
LOG(("Creating nsHttpChannel [this=%p]\n", this));
mChannelCreationTime = PR_Now();
@ -6200,4 +6201,22 @@ nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
return rv;
}
void
nsHttpChannel::ForcePending(bool aForcePending)
{
// Set true here so IsPending will return true.
// Required for callback diversion from child back to parent. In such cases
// OnStopRequest can be called in the parent before callbacks are diverted
// back from the child to the listener in the parent.
mForcePending = aForcePending;
}
NS_IMETHODIMP
nsHttpChannel::IsPending(bool *aIsPending)
{
NS_ENSURE_ARG_POINTER(aIsPending);
*aIsPending = mIsPending || mForcePending;
return NS_OK;
}
} } // namespace mozilla::net

View File

@ -105,6 +105,7 @@ public:
NS_IMETHOD Cancel(nsresult status);
NS_IMETHOD Suspend();
NS_IMETHOD Resume();
NS_IMETHOD IsPending(bool *aIsPending);
// nsIChannel
NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo);
NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *aContext);
@ -182,6 +183,8 @@ public: /* internal necko use only */
uint32_t mKeep : 2;
};
void ForcePending(bool aForcePending);
private:
typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
@ -424,6 +427,7 @@ private: // cache telemetry
private:
nsIPrincipal *GetPrincipal();
nsCOMPtr<nsIPrincipal> mPrincipal;
bool mForcePending;
};
} } // namespace mozilla::net