Bug 1197679 - If nsUnknownDecoder is involved in e10s DivertToParent can break. r=jduell

This commit is contained in:
Dragana Damjanovic dd.mozilla@gmail.com 2015-10-11 18:13:09 +02:00
parent 17a52110f3
commit 48d945f4e6
7 changed files with 308 additions and 2 deletions

View File

@ -22,7 +22,7 @@ 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)]
[uuid(7a9bf52d-f828-4b31-b8df-b40fdd37d007)]
interface nsIDivertableChannel : nsISupports
{
/**
@ -57,4 +57,22 @@ interface nsIDivertableChannel : nsISupports
* canceled channel.
*/
ChannelDiverterChild divertToParent();
/**
* nsUnknownDecoder delays calling OnStartRequest until it gets enough data
* to decide about the content type (until OnDataAvaiable is called). In a
* OnStartRequest DivertToParent can be called but some OnDataAvailables are
* already called and therefore can not be diverted to parent.
*
* nsUnknownDecoder will call UnknownDecoderInvolvedKeepData in its
* OnStartRequest function and when it calls OnStartRequest of the next
* listener it will call UnknownDecoderInvolvedOnStartRequestCalled. In this
* function Child process will decide to discarge data if it is not diverting
* to parent or keep them if it is diverting to parent.
*/
void unknownDecoderInvolvedKeepData();
void unknownDecoderInvolvedOnStartRequestCalled();
readonly attribute bool divertingToParent;
};

View File

@ -51,6 +51,7 @@ class ChannelEventQueue final
// Puts IPDL-generated channel event into queue, to be run later
// automatically when EndForcedQueueing and/or Resume is called.
inline void Enqueue(ChannelEvent* callback);
inline nsresult PrependEvents(nsTArray<nsAutoPtr<ChannelEvent> >& aEvents);
// After StartForcedQueueing is called, ShouldEnqueue() will return true and
// no events will be run/flushed until EndForcedQueueing is called.
@ -127,6 +128,19 @@ ChannelEventQueue::EndForcedQueueing()
MaybeFlushQueue();
}
inline nsresult
ChannelEventQueue::PrependEvents(nsTArray<nsAutoPtr<ChannelEvent> >& aEvents)
{
if (!mEventQueue.InsertElementsAt(0, aEvents.Length())) {
return NS_ERROR_OUT_OF_MEMORY;
}
for (uint32_t i = 0; i < aEvents.Length(); i++) {
mEventQueue.ReplaceElementAt(i, aEvents[i].forget());
}
return NS_OK;
}
inline void
ChannelEventQueue::Suspend()
{

View File

@ -29,6 +29,7 @@ namespace net {
FTPChannelChild::FTPChannelChild(nsIURI* uri)
: mIPCOpen(false)
, mUnknownDecoderInvolved(false)
, mCanceled(false)
, mSuspendCount(0)
, mIsPending(false)
@ -390,6 +391,40 @@ FTPChannelChild::RecvOnDataAvailable(const nsresult& channelStatus,
return true;
}
class MaybeDivertOnDataFTPEvent : public ChannelEvent
{
public:
MaybeDivertOnDataFTPEvent(FTPChannelChild* child,
const nsCString& data,
const uint64_t& offset,
const uint32_t& count)
: mChild(child)
, mData(data)
, mOffset(offset)
, mCount(count) {}
void Run()
{
mChild->MaybeDivertOnData(mData, mOffset, mCount);
}
private:
FTPChannelChild* mChild;
nsCString mData;
uint64_t mOffset;
uint32_t mCount;
};
void
FTPChannelChild::MaybeDivertOnData(const nsCString& data,
const uint64_t& offset,
const uint32_t& count)
{
if (mDivertingToParent) {
SendDivertOnDataAvailable(data, offset, count);
}
}
void
FTPChannelChild::DoOnDataAvailable(const nsresult& channelStatus,
const nsCString& data,
@ -413,6 +448,11 @@ FTPChannelChild::DoOnDataAvailable(const nsresult& channelStatus,
if (mCanceled)
return;
if (mUnknownDecoderInvolved) {
mUnknownDecoderEventQ.AppendElement(
new MaybeDivertOnDataFTPEvent(this, data, offset, count));
}
// 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
// this function). Apparently the previous, non-e10s behavior was to actually
@ -472,6 +512,32 @@ FTPChannelChild::RecvOnStopRequest(const nsresult& aChannelStatus)
return true;
}
class MaybeDivertOnStopFTPEvent : public ChannelEvent
{
public:
MaybeDivertOnStopFTPEvent(FTPChannelChild* child,
const nsresult& aChannelStatus)
: mChild(child)
, mChannelStatus(aChannelStatus) {}
void Run()
{
mChild->MaybeDivertOnStop(mChannelStatus);
}
private:
FTPChannelChild* mChild;
nsresult mChannelStatus;
};
void
FTPChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus)
{
if (mDivertingToParent) {
SendDivertOnStopRequest(aChannelStatus);
}
}
void
FTPChannelChild::DoOnStopRequest(const nsresult& aChannelStatus)
{
@ -489,6 +555,11 @@ FTPChannelChild::DoOnStopRequest(const nsresult& aChannelStatus)
if (!mCanceled)
mStatus = aChannelStatus;
if (mUnknownDecoderInvolved) {
mUnknownDecoderEventQ.AppendElement(
new MaybeDivertOnStopFTPEvent(this, aChannelStatus));
}
{ // Ensure that all queued ipdl events are dispatched before
// we initiate protocol deletion below.
mIsPending = false;
@ -788,6 +859,36 @@ FTPChannelChild::DivertToParent(ChannelDiverterChild **aChild)
return NS_OK;
}
NS_IMETHODIMP
FTPChannelChild::UnknownDecoderInvolvedKeepData()
{
mUnknownDecoderInvolved = true;
return NS_OK;
}
NS_IMETHODIMP
FTPChannelChild::UnknownDecoderInvolvedOnStartRequestCalled()
{
mUnknownDecoderInvolved = false;
nsresult rv = NS_OK;
if (mDivertingToParent) {
rv = mEventQ->PrependEvents(mUnknownDecoderEventQ);
}
mUnknownDecoderEventQ.Clear();
return rv;
}
NS_IMETHODIMP
FTPChannelChild::GetDivertingToParent(bool* aDiverting)
{
NS_ENSURE_ARG_POINTER(aDiverting);
*aDiverting = mDivertingToParent;
return NS_OK;
}
} // namespace net
} // namespace mozilla

View File

@ -101,13 +101,19 @@ protected:
const nsCString& data,
const uint64_t& offset,
const uint32_t& count);
void MaybeDivertOnData(const nsCString& data,
const uint64_t& offset,
const uint32_t& count);
void MaybeDivertOnStop(const nsresult& statusCode);
void DoOnStopRequest(const nsresult& statusCode);
void DoFailedAsyncOpen(const nsresult& statusCode);
void DoDeleteSelf();
friend class FTPStartRequestEvent;
friend class FTPDataAvailableEvent;
friend class MaybeDivertOnDataFTPEvent;
friend class FTPStopRequestEvent;
friend class MaybeDivertOnStopFTPEvent;
friend class FTPFailedAsyncOpenEvent;
friend class FTPDeleteSelfEvent;
@ -116,6 +122,13 @@ private:
bool mIPCOpen;
nsRefPtr<ChannelEventQueue> mEventQ;
// If nsUnknownDecoder is involved we queue onDataAvailable (and possibly
// OnStopRequest) so that we can divert them if needed when the listener's
// OnStartRequest is finally called
nsTArray<nsAutoPtr<ChannelEvent>> mUnknownDecoderEventQ;
bool mUnknownDecoderInvolved;
bool mCanceled;
uint32_t mSuspendCount;
bool mIsPending;

View File

@ -179,6 +179,7 @@ HttpChannelChild::HttpChannelChild()
, mSendResumeAt(false)
, mIPCOpen(false)
, mKeptAlive(false)
, mUnknownDecoderInvolved(false)
, mDivertingToParent(false)
, mFlushedForDiversion(false)
, mSuspendSent(false)
@ -580,6 +581,42 @@ HttpChannelChild::RecvOnTransportAndData(const nsresult& channelStatus,
return true;
}
class MaybeDivertOnDataHttpEvent : public ChannelEvent
{
public:
MaybeDivertOnDataHttpEvent(HttpChannelChild* child,
const nsCString& data,
const uint64_t& offset,
const uint32_t& count)
: mChild(child)
, mData(data)
, mOffset(offset)
, mCount(count) {}
void Run()
{
mChild->MaybeDivertOnData(mData, mOffset, mCount);
}
private:
HttpChannelChild* mChild;
nsCString mData;
uint64_t mOffset;
uint32_t mCount;
};
void
HttpChannelChild::MaybeDivertOnData(const nsCString& data,
const uint64_t& offset,
const uint32_t& count)
{
LOG(("HttpChannelChild::MaybeDivertOnData [this=%p]", this));
if (mDivertingToParent) {
SendDivertOnDataAvailable(data, offset, count);
}
}
void
HttpChannelChild::OnTransportAndData(const nsresult& channelStatus,
const nsresult& transportStatus,
@ -607,6 +644,13 @@ HttpChannelChild::OnTransportAndData(const nsresult& channelStatus,
if (mCanceled)
return;
if (mUnknownDecoderInvolved) {
LOG(("UnknownDecoder is involved queue OnDataAvailable call. [this=%p]",
this));
mUnknownDecoderEventQ.AppendElement(
new MaybeDivertOnDataHttpEvent(this, data, offset, count));
}
// Hold queue lock throughout all three calls, else we might process a later
// necko msg in between them.
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
@ -742,12 +786,42 @@ HttpChannelChild::RecvOnStopRequest(const nsresult& channelStatus,
return true;
}
class MaybeDivertOnStopHttpEvent : public ChannelEvent
{
public:
MaybeDivertOnStopHttpEvent(HttpChannelChild* child,
const nsresult& channelStatus)
: mChild(child)
, mChannelStatus(channelStatus)
{}
void Run()
{
mChild->MaybeDivertOnStop(mChannelStatus);
}
private:
HttpChannelChild* mChild;
nsresult mChannelStatus;
};
void
HttpChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus)
{
LOG(("HttpChannelChild::MaybeDivertOnStop [this=%p, "
"mDivertingToParent=%d status=%x]", this, mDivertingToParent,
aChannelStatus));
if (mDivertingToParent) {
SendDivertOnStopRequest(aChannelStatus);
}
}
void
HttpChannelChild::OnStopRequest(const nsresult& channelStatus,
const ResourceTimingStruct& timing)
{
LOG(("HttpChannelChild::OnStopRequest [this=%p status=%x]\n",
this, channelStatus));
this, channelStatus));
if (mDivertingToParent) {
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
@ -757,6 +831,13 @@ HttpChannelChild::OnStopRequest(const nsresult& channelStatus,
return;
}
if (mUnknownDecoderInvolved) {
LOG(("UnknownDecoder is involved queue OnStopRequest call. [this=%p]",
this));
mUnknownDecoderEventQ.AppendElement(
new MaybeDivertOnStopHttpEvent(this, channelStatus));
}
mTransactionTimings.domainLookupStart = timing.domainLookupStart;
mTransactionTimings.domainLookupEnd = timing.domainLookupEnd;
mTransactionTimings.connectStart = timing.connectStart;
@ -2239,6 +2320,41 @@ HttpChannelChild::DivertToParent(ChannelDiverterChild **aChild)
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::UnknownDecoderInvolvedKeepData()
{
LOG(("HttpChannelChild::UnknownDecoderInvolvedKeepData [this=%p]",
this));
mUnknownDecoderInvolved = true;
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::UnknownDecoderInvolvedOnStartRequestCalled()
{
LOG(("HttpChannelChild::UnknownDecoderInvolvedOnStartRequestCalled "
"[this=%p, mDivertingToParent=%d]", this, mDivertingToParent));
mUnknownDecoderInvolved = false;
nsresult rv = NS_OK;
if (mDivertingToParent) {
rv = mEventQ->PrependEvents(mUnknownDecoderEventQ);
}
mUnknownDecoderEventQ.Clear();
return rv;
}
NS_IMETHODIMP
HttpChannelChild::GetDivertingToParent(bool* aDiverting)
{
NS_ENSURE_ARG_POINTER(aDiverting);
*aDiverting = mDivertingToParent;
return NS_OK;
}
void
HttpChannelChild::ResetInterception()
{

View File

@ -195,6 +195,12 @@ private:
bool mKeptAlive; // IPC kept open, but only for security info
nsRefPtr<ChannelEventQueue> mEventQ;
// If nsUnknownDecoder is involved OnStartRequest call will be delayed and
// this queue keeps OnDataAvailable data until OnStartRequest is finally
// called.
nsTArray<nsAutoPtr<ChannelEvent>> mUnknownDecoderEventQ;
bool mUnknownDecoderInvolved;
// 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
@ -229,6 +235,9 @@ private:
const NetAddr& selfAddr,
const NetAddr& peerAddr,
const uint32_t& cacheKey);
void MaybeDivertOnData(const nsCString& data,
const uint64_t& offset,
const uint32_t& count);
void OnTransportAndData(const nsresult& channelStatus,
const nsresult& status,
const uint64_t progress,
@ -237,6 +246,7 @@ private:
const uint64_t& offset,
const uint32_t& count);
void OnStopRequest(const nsresult& channelStatus, const ResourceTimingStruct& timing);
void MaybeDivertOnStop(const nsresult& aChannelStatus);
void OnProgress(const int64_t& progress, const int64_t& progressMax);
void OnStatus(const nsresult& status);
void FailedAsyncOpen(const nsresult& status);
@ -253,6 +263,8 @@ private:
friend class StartRequestEvent;
friend class StopRequestEvent;
friend class TransportAndDataEvent;
friend class MaybeDivertOnDataHttpEvent;
friend class MaybeDivertOnStopHttpEvent;
friend class ProgressEvent;
friend class StatusEvent;
friend class FailedAsyncOpenEvent;

View File

@ -15,6 +15,7 @@
#include "nsIMIMEService.h"
#include "nsIDivertableChannel.h"
#include "nsIViewSourceChannel.h"
#include "nsIHttpChannel.h"
#include "nsIForcePendingChannel.h"
@ -213,6 +214,15 @@ nsUnknownDecoder::OnDataAvailable(nsIRequest* request,
NS_ASSERTION(!mContentType.IsEmpty(),
"Content type should be known by now.");
nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
if (divertable) {
bool diverting;
divertable->GetDivertingToParent(&diverting);
if (diverting) {
// The channel is diverted to the parent do not send any more data here.
return rv;
}
}
rv = mNextListener->OnDataAvailable(request, aCtxt, aStream,
aSourceOffset, aCount);
}
@ -242,6 +252,11 @@ nsUnknownDecoder::OnStartRequest(nsIRequest* request, nsISupports *aCtxt)
}
}
nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
if (divertable) {
divertable->UnknownDecoderInvolvedKeepData();
}
// Do not pass the OnStartRequest on to the next listener (yet)...
return rv;
}
@ -647,6 +662,12 @@ nsresult nsUnknownDecoder::FireListenerNotifications(nsIRequest* request,
// mNextListener looks at it.
request->Cancel(rv);
mNextListener->OnStartRequest(request, aCtxt);
nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
if (divertable) {
rv = divertable->UnknownDecoderInvolvedOnStartRequestCalled();
}
return rv;
}
}
@ -654,6 +675,17 @@ nsresult nsUnknownDecoder::FireListenerNotifications(nsIRequest* request,
// Fire the OnStartRequest(...)
rv = mNextListener->OnStartRequest(request, aCtxt);
nsCOMPtr<nsIDivertableChannel> divertable = do_QueryInterface(request);
if (divertable) {
rv = divertable->UnknownDecoderInvolvedOnStartRequestCalled();
bool diverting;
divertable->GetDivertingToParent(&diverting);
if (diverting) {
// The channel is diverted to the parent do not send any more data here.
return rv;
}
}
if (NS_SUCCEEDED(rv)) {
// install stream converter if required
nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(request);