Bug 898524: Part 2 - Avoid IPC roundtrips for synthesized responses. r=mayhemer

This commit is contained in:
Josh Matthews 2014-10-17 09:55:09 -04:00
parent 162208d7d0
commit 8f259e6613
12 changed files with 651 additions and 192 deletions

View File

@ -38,10 +38,14 @@
#include "PrivateBrowsingChannel.h"
#include "mozilla/net/DNS.h"
#include "nsITimedChannel.h"
#include "nsIHttpChannel.h"
#include "nsISecurityConsoleMessage.h"
#include "nsCOMArray.h"
extern PRLogModuleInfo *gHttpLog;
class nsPerformance;
class nsISecurityConsoleMessage;
class nsIPrincipal;
namespace mozilla {
namespace net {

View File

@ -28,6 +28,8 @@
#include "mozilla/net/ChannelDiverterChild.h"
#include "mozilla/net/DNS.h"
#include "SerializedLoadContext.h"
#include "nsInputStreamPump.h"
#include "InterceptedChannel.h"
using namespace mozilla::dom;
using namespace mozilla::ipc;
@ -321,7 +323,16 @@ HttpChannelChild::OnStartRequest(const nsresult& channelStatus,
mTracingEnabled = false;
nsresult rv = mListener->OnStartRequest(this, mListenerContext);
DoOnStartRequest(this, mListenerContext);
mSelfAddr = selfAddr;
mPeerAddr = peerAddr;
}
void
HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
{
nsresult rv = mListener->OnStartRequest(aRequest, aContext);
if (NS_FAILED(rv)) {
Cancel(rv);
return;
@ -330,9 +341,6 @@ HttpChannelChild::OnStartRequest(const nsresult& channelStatus,
if (mResponseHead)
SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
mSelfAddr = selfAddr;
mPeerAddr = peerAddr;
if (mDivertingToParent) {
mListener = nullptr;
mListenerContext = nullptr;
@ -442,41 +450,12 @@ HttpChannelChild::OnTransportAndData(const nsresult& channelStatus,
if (mCanceled)
return;
// cache the progress sink so we don't have to query for it each time.
if (!mProgressSink) {
GetCallback(mProgressSink);
}
// Hold queue lock throughout all three calls, else we might process a later
// necko msg in between them.
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
// Block status/progress after Cancel or OnStopRequest has been called,
// or if channel has LOAD_BACKGROUND set.
// Note: Progress events will be received directly in RecvOnProgress if
// LOAD_BACKGROUND is set.
// - JDUELL: may not need mStatus/mIsPending checks, given this is always called
// during OnDataAvailable, and we've already checked mCanceled. Code
// dupe'd from nsHttpChannel
if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
!(mLoadFlags & LOAD_BACKGROUND))
{
// OnStatus
//
MOZ_ASSERT(transportStatus == NS_NET_STATUS_RECEIVING_FROM ||
transportStatus == NS_NET_STATUS_READING);
nsAutoCString host;
mURI->GetHost(host);
mProgressSink->OnStatus(this, nullptr, transportStatus,
NS_ConvertUTF8toUTF16(host).get());
// OnProgress
//
if (progress > 0) {
MOZ_ASSERT(progress <= progressMax, "unexpected progress values");
mProgressSink->OnProgress(this, nullptr, progress, progressMax);
}
}
DoOnStatus(this, transportStatus);
DoOnProgress(this, progress, progressMax);
// OnDataAvailable
//
@ -493,9 +472,70 @@ HttpChannelChild::OnTransportAndData(const nsresult& channelStatus,
return;
}
rv = mListener->OnDataAvailable(this, mListenerContext,
stringStream, offset, count);
DoOnDataAvailable(this, mListenerContext, stringStream, offset, count);
stringStream->Close();
}
void
HttpChannelChild::DoOnStatus(nsIRequest* aRequest, nsresult status)
{
if (mCanceled)
return;
// cache the progress sink so we don't have to query for it each time.
if (!mProgressSink)
GetCallback(mProgressSink);
// block status/progress after Cancel or OnStopRequest has been called,
// or if channel has LOAD_BACKGROUND set.
if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
!(mLoadFlags & LOAD_BACKGROUND))
{
// OnStatus
//
MOZ_ASSERT(status == NS_NET_STATUS_RECEIVING_FROM ||
status == NS_NET_STATUS_READING);
nsAutoCString host;
mURI->GetHost(host);
mProgressSink->OnStatus(aRequest, nullptr, status,
NS_ConvertUTF8toUTF16(host).get());
}
}
void
HttpChannelChild::DoOnProgress(nsIRequest* aRequest, uint64_t progress, uint64_t progressMax)
{
if (mCanceled)
return;
// cache the progress sink so we don't have to query for it each time.
if (!mProgressSink)
GetCallback(mProgressSink);
// block status/progress after Cancel or OnStopRequest has been called,
// or if channel has LOAD_BACKGROUND set.
if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
!(mLoadFlags & LOAD_BACKGROUND))
{
// OnProgress
//
if (progress > 0) {
MOZ_ASSERT(progress <= progressMax, "unexpected progress values");
mProgressSink->OnProgress(aRequest, nullptr, progress, progressMax);
}
}
}
void
HttpChannelChild::DoOnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
nsIInputStream* aStream,
uint64_t offset, uint32_t count)
{
if (mCanceled)
return;
nsresult rv = mListener->OnDataAvailable(aRequest, aContext, aStream, offset, count);
if (NS_FAILED(rv)) {
Cancel(rv);
}
@ -545,24 +585,14 @@ HttpChannelChild::OnStopRequest(const nsresult& channelStatus)
return;
}
mIsPending = false;
if (!mCanceled && NS_SUCCEEDED(mStatus)) {
mStatus = channelStatus;
}
DoPreOnStopRequest(channelStatus);
{ // 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(mEventQ);
mListener->OnStopRequest(this, mListenerContext, mStatus);
mListener = 0;
mListenerContext = 0;
mCacheEntryAvailable = false;
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nullptr, mStatus);
DoOnStopRequest(this, mListenerContext);
}
if (mLoadFlags & LOAD_DOCUMENT_URI) {
@ -576,6 +606,30 @@ HttpChannelChild::OnStopRequest(const nsresult& channelStatus)
}
}
void
HttpChannelChild::DoPreOnStopRequest(nsresult aStatus)
{
mIsPending = false;
if (!mCanceled && NS_SUCCEEDED(mStatus)) {
mStatus = aStatus;
}
}
void
HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest, nsISupports* aContext)
{
MOZ_ASSERT(!mIsPending);
mListener->OnStopRequest(aRequest, aContext, mStatus);
mListener = 0;
mListenerContext = 0;
mCacheEntryAvailable = false;
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nullptr, mStatus);
}
class ProgressEvent : public ChannelEvent
{
public:
@ -1103,14 +1157,19 @@ HttpChannelChild::Cancel(nsresult status)
NS_IMETHODIMP
HttpChannelChild::Suspend()
{
NS_ENSURE_TRUE(RemoteChannelExists(), NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(RemoteChannelExists() || mInterceptListener, NS_ERROR_NOT_AVAILABLE);
// 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;
if (RemoteChannelExists()) {
SendSuspend();
mSuspendSent = true;
}
}
if (mSynthesizedResponsePump) {
mSynthesizedResponsePump->Suspend();
}
mEventQ->Suspend();
@ -1120,7 +1179,7 @@ HttpChannelChild::Suspend()
NS_IMETHODIMP
HttpChannelChild::Resume()
{
NS_ENSURE_TRUE(RemoteChannelExists(), NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(RemoteChannelExists() || mInterceptListener, NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
nsresult rv = NS_OK;
@ -1130,12 +1189,17 @@ HttpChannelChild::Resume()
// suspend was sent earlier); otherwise, resume will be called at the correct
// time in the parent itself.
if (!--mSuspendCount && (!mDivertingToParent || mSuspendSent)) {
SendResume();
if (RemoteChannelExists()) {
SendResume();
}
if (mCallOnResume) {
AsyncCall(mCallOnResume);
mCallOnResume = nullptr;
}
}
if (mSynthesizedResponsePump) {
mSynthesizedResponsePump->Resume();
}
mEventQ->Resume();
return rv;
@ -1177,6 +1241,90 @@ HttpChannelChild::GetSecurityInfo(nsISupports **aSecurityInfo)
return NS_OK;
}
// A stream listener interposed between the nsInputStreamPump used for intercepted channels
// and this channel's original listener. This is only used to ensure the original listener
// sees the channel as the request object, and to synthesize OnStatus and OnProgress notifications.
class InterceptStreamListener : public nsIStreamListener
, public nsIProgressEventSink
{
nsRefPtr<HttpChannelChild> mOwner;
nsCOMPtr<nsISupports> mContext;
virtual ~InterceptStreamListener() {}
public:
InterceptStreamListener(HttpChannelChild* aOwner, nsISupports* aContext)
: mOwner(aOwner)
, mContext(aContext)
{
}
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIPROGRESSEVENTSINK
};
NS_IMPL_ISUPPORTS(InterceptStreamListener,
nsIStreamListener,
nsIRequestObserver,
nsIProgressEventSink)
NS_IMETHODIMP
InterceptStreamListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
{
mOwner->DoOnStartRequest(mOwner, mContext);
return NS_OK;
}
NS_IMETHODIMP
InterceptStreamListener::OnStatus(nsIRequest* aRequest, nsISupports* aContext,
nsresult status, const char16_t* aStatusArg)
{
mOwner->DoOnStatus(mOwner, status);
return NS_OK;
}
NS_IMETHODIMP
InterceptStreamListener::OnProgress(nsIRequest* aRequest, nsISupports* aContext,
uint64_t aProgress, uint64_t aProgressMax)
{
mOwner->DoOnProgress(mOwner, aProgress, aProgressMax);
return NS_OK;
}
NS_IMETHODIMP
InterceptStreamListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
nsIInputStream* aInputStream, uint64_t aOffset,
uint32_t aCount)
{
uint32_t loadFlags;
mOwner->GetLoadFlags(&loadFlags);
if (!(loadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
nsCOMPtr<nsIURI> uri;
mOwner->GetURI(getter_AddRefs(uri));
nsAutoCString host;
uri->GetHost(host);
OnStatus(mOwner, aContext, NS_NET_STATUS_READING, NS_ConvertUTF8toUTF16(host).get());
uint64_t progressMax(uint64_t(mOwner->GetResponseHead()->ContentLength()));
uint64_t progress = aOffset + uint64_t(aCount);
OnProgress(mOwner, aContext, progress, progressMax);
}
mOwner->DoOnDataAvailable(mOwner, mContext, aInputStream, aOffset, aCount);
return NS_OK;
}
NS_IMETHODIMP
InterceptStreamListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode)
{
mOwner->DoPreOnStopRequest(aStatusCode);
mOwner->DoOnStopRequest(mOwner, mContext);
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
{
@ -1234,6 +1382,24 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
return NS_OK;
}
if (ShouldIntercept()) {
nsCOMPtr<nsINetworkInterceptController> controller;
GetCallback(controller);
mInterceptListener = new InterceptStreamListener(this, mListenerContext);
nsRefPtr<InterceptedChannelContent> intercepted =
new InterceptedChannelContent(this, controller, mInterceptListener);
intercepted->NotifyController();
return NS_OK;
}
return ContinueAsyncOpen();
}
nsresult
HttpChannelChild::ContinueAsyncOpen()
{
nsCString appCacheClientId;
if (mInheritApplicationCache) {
// Pick up an application cache from the notification
@ -1243,7 +1409,7 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
if (appCacheContainer) {
nsCOMPtr<nsIApplicationCache> appCache;
rv = appCacheContainer->GetApplicationCache(getter_AddRefs(appCache));
nsresult rv = appCacheContainer->GetApplicationCache(getter_AddRefs(appCache));
if (NS_SUCCEEDED(rv) && appCache) {
appCache->GetClientID(appCacheClientId);
}
@ -1709,4 +1875,29 @@ HttpChannelChild::DivertToParent(ChannelDiverterChild **aChild)
return NS_OK;
}
void
HttpChannelChild::ResetInterception()
{
mInterceptListener = nullptr;
// Continue with the original cross-process request
nsresult rv = ContinueAsyncOpen();
NS_ENSURE_SUCCESS_VOID(rv);
}
void
HttpChannelChild::OverrideWithSynthesizedResponse(nsHttpResponseHead* aResponseHead,
nsInputStreamPump* aPump)
{
mSynthesizedResponsePump = aPump;
mResponseHead = aResponseHead;
// if this channel has been suspended previously, the pump needs to be
// correspondingly suspended now that it exists.
for (uint32_t i = 0; i < mSuspendCount; i++) {
nsresult rv = mSynthesizedResponsePump->Suspend();
NS_ENSURE_SUCCESS_VOID(rv);
}
}
}} // mozilla::net

View File

@ -30,9 +30,14 @@
#include "nsIDivertableChannel.h"
#include "mozilla/net/DNS.h"
class nsInputStreamPump;
namespace mozilla {
namespace net {
class InterceptedChannelContent;
class InterceptStreamListener;
class HttpChannelChild MOZ_FINAL : public PHttpChannelChild
, public HttpBaseChannel
, public HttpAsyncAborter<HttpChannelChild>
@ -134,9 +139,28 @@ protected:
virtual void DoNotifyListenerCleanup();
private:
nsresult ContinueAsyncOpen();
void DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext);
void DoOnStatus(nsIRequest* aRequest, nsresult status);
void DoOnProgress(nsIRequest* aRequest, uint64_t progress, uint64_t progressMax);
void DoOnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aStream,
uint64_t offset, uint32_t count);
void DoPreOnStopRequest(nsresult aStatus);
void DoOnStopRequest(nsIRequest* aRequest, nsISupports* aContext);
// Discard the prior interception and continue with the original network request.
void ResetInterception();
// Override this channel's pending response with a synthesized one. The content will be
// asynchronously read from the pump.
void OverrideWithSynthesizedResponse(nsHttpResponseHead* aResponseHead, nsInputStreamPump* aPump);
RequestHeaderTuples mClientSetRequestHeaders;
nsCOMPtr<nsIChildChannel> mRedirectChannelChild;
nsCOMPtr<nsISupports> mSecurityInfo;
nsRefPtr<InterceptStreamListener> mInterceptListener;
nsRefPtr<nsInputStreamPump> mSynthesizedResponsePump;
bool mIsFromCache;
bool mCacheEntryAvailable;
@ -205,6 +229,8 @@ private:
friend class Redirect3Event;
friend class DeleteSelfEvent;
friend class HttpAsyncAborter<HttpChannelChild>;
friend class InterceptStreamListener;
friend class InterceptedChannelContent;
};
//-----------------------------------------------------------------------------

View File

@ -0,0 +1,232 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: -*- */
/* vim:set expandtab ts=2 sw=2 sts=2 cin: */
/* 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 "HttpLog.h"
#include "InterceptedChannel.h"
#include "nsInputStreamPump.h"
#include "nsIPipe.h"
#include "nsIStreamListener.h"
#include "nsHttpChannel.h"
#include "HttpChannelChild.h"
#include "nsHttpResponseHead.h"
namespace mozilla {
namespace net {
extern nsresult
DoAddCacheEntryHeaders(nsHttpChannel *self,
nsICacheEntry *entry,
nsHttpRequestHead *requestHead,
nsHttpResponseHead *responseHead,
nsISupports *securityInfo);
NS_IMPL_ISUPPORTS(InterceptedChannelBase, nsIInterceptedChannel)
InterceptedChannelBase::InterceptedChannelBase(nsINetworkInterceptController* aController)
: mController(aController)
{
}
InterceptedChannelBase::~InterceptedChannelBase()
{
}
void
InterceptedChannelBase::EnsureSynthesizedResponse()
{
if (mSynthesizedResponseHead.isNothing()) {
mSynthesizedResponseHead.emplace();
}
}
void
InterceptedChannelBase::DoNotifyController(nsIOutputStream* aOut)
{
nsresult rv = mController->ChannelIntercepted(this, aOut);
NS_ENSURE_SUCCESS_VOID(rv);
}
nsresult
InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue)
{
EnsureSynthesizedResponse();
nsAutoCString header = aName + NS_LITERAL_CSTRING(": ") + aValue;
// Overwrite any existing header.
nsresult rv = mSynthesizedResponseHead->ParseHeaderLine(header.get());
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
InterceptedChannelChrome::InterceptedChannelChrome(nsHttpChannel* aChannel,
nsINetworkInterceptController* aController,
nsICacheEntry* aEntry)
: InterceptedChannelBase(aController)
, mChannel(aChannel)
, mSynthesizedCacheEntry(aEntry)
{
}
void
InterceptedChannelChrome::NotifyController()
{
nsCOMPtr<nsIOutputStream> out;
nsresult rv = mSynthesizedCacheEntry->OpenOutputStream(0, getter_AddRefs(out));
NS_ENSURE_SUCCESS_VOID(rv);
DoNotifyController(out);
}
NS_IMETHODIMP
InterceptedChannelChrome::ResetInterception()
{
if (!mChannel) {
return NS_ERROR_NOT_AVAILABLE;
}
mSynthesizedCacheEntry->AsyncDoom(nullptr);
mSynthesizedCacheEntry = nullptr;
nsCOMPtr<nsIURI> uri;
mChannel->GetURI(getter_AddRefs(uri));
nsresult rv = mChannel->StartRedirectChannelToURI(uri, nsIChannelEventSink::REDIRECT_INTERNAL);
NS_ENSURE_SUCCESS(rv, rv);
mChannel = nullptr;
return NS_OK;
}
NS_IMETHODIMP
InterceptedChannelChrome::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
{
if (!mSynthesizedCacheEntry) {
return NS_ERROR_NOT_AVAILABLE;
}
return DoSynthesizeHeader(aName, aValue);
}
NS_IMETHODIMP
InterceptedChannelChrome::FinishSynthesizedResponse()
{
if (!mChannel) {
return NS_ERROR_NOT_AVAILABLE;
}
EnsureSynthesizedResponse();
mChannel->MarkIntercepted();
// First we ensure the appropriate metadata is set on the synthesized cache entry
// (i.e. the flattened response head)
nsCOMPtr<nsISupports> securityInfo;
nsresult rv = mChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
NS_ENSURE_SUCCESS(rv, rv);
rv = DoAddCacheEntryHeaders(mChannel, mSynthesizedCacheEntry,
mChannel->GetRequestHead(),
mSynthesizedResponseHead.ptr(), securityInfo);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uri;
mChannel->GetURI(getter_AddRefs(uri));
bool usingSSL = false;
uri->SchemeIs("https", &usingSSL);
// Then we open a real cache entry to read the synthesized response from.
rv = mChannel->OpenCacheEntry(usingSSL);
NS_ENSURE_SUCCESS(rv, rv);
mSynthesizedCacheEntry = nullptr;
if (!mChannel->AwaitingCacheCallbacks()) {
rv = mChannel->ContinueConnect();
NS_ENSURE_SUCCESS(rv, rv);
}
mChannel = nullptr;
return NS_OK;
}
InterceptedChannelContent::InterceptedChannelContent(HttpChannelChild* aChannel,
nsINetworkInterceptController* aController,
nsIStreamListener* aListener)
: InterceptedChannelBase(aController)
, mChannel(aChannel)
, mStreamListener(aListener)
{
}
void
InterceptedChannelContent::NotifyController()
{
nsresult rv = NS_NewPipe(getter_AddRefs(mSynthesizedInput),
getter_AddRefs(mSynthesizedOutput),
0, UINT32_MAX, true, true);
NS_ENSURE_SUCCESS_VOID(rv);
DoNotifyController(mSynthesizedOutput);
}
NS_IMETHODIMP
InterceptedChannelContent::ResetInterception()
{
if (!mChannel) {
return NS_ERROR_NOT_AVAILABLE;
}
mSynthesizedOutput = nullptr;
mSynthesizedInput = nullptr;
mChannel->ResetInterception();
mChannel = nullptr;
return NS_OK;
}
NS_IMETHODIMP
InterceptedChannelContent::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
{
if (!mSynthesizedOutput) {
return NS_ERROR_NOT_AVAILABLE;
}
return DoSynthesizeHeader(aName, aValue);
}
NS_IMETHODIMP
InterceptedChannelContent::FinishSynthesizedResponse()
{
if (!mChannel) {
return NS_ERROR_NOT_AVAILABLE;
}
EnsureSynthesizedResponse();
nsresult rv = nsInputStreamPump::Create(getter_AddRefs(mStoragePump), mSynthesizedInput,
int64_t(-1), int64_t(-1), 0, 0, true);
if (NS_FAILED(rv)) {
mSynthesizedInput->Close();
return rv;
}
mSynthesizedOutput = nullptr;
rv = mStoragePump->AsyncRead(mStreamListener, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
mChannel->OverrideWithSynthesizedResponse(mSynthesizedResponseHead.ptr(), mStoragePump);
mChannel = nullptr;
return NS_OK;
}
} // namespace net
} // namespace mozilla

View File

@ -0,0 +1,96 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set expandtab ts=2 sw=2 sts=2 cin: */
/* 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 InterceptedChannel_h
#define InterceptedChannel_h
#include "nsINetworkInterceptController.h"
#include "nsRefPtr.h"
#include "mozilla/Maybe.h"
class nsICacheEntry;
class nsInputStreamPump;
class nsIStorageStream;
class nsIStreamListener;
namespace mozilla {
namespace net {
class nsHttpChannel;
class HttpChannelChild;
class nsHttpResponseHead;
// An object representing a channel that has been intercepted. This avoids complicating
// the actual channel implementation with the details of synthesizing responses.
class InterceptedChannelBase : public nsIInterceptedChannel {
protected:
// The interception controller to notify about the successful channel interception
nsCOMPtr<nsINetworkInterceptController> mController;
// Response head for use when synthesizing
Maybe<nsHttpResponseHead> mSynthesizedResponseHead;
void EnsureSynthesizedResponse();
void DoNotifyController(nsIOutputStream* aOut);
nsresult DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue);
virtual ~InterceptedChannelBase();
public:
InterceptedChannelBase(nsINetworkInterceptController* aController);
// Notify the interception controller that the channel has been intercepted
// and prepare the response body output stream.
virtual void NotifyController() = 0;
NS_DECL_ISUPPORTS
};
class InterceptedChannelChrome : public InterceptedChannelBase
{
// The actual channel being intercepted.
nsRefPtr<nsHttpChannel> mChannel;
// Writeable cache entry for use when synthesizing a response in a parent process
nsCOMPtr<nsICacheEntry> mSynthesizedCacheEntry;
public:
InterceptedChannelChrome(nsHttpChannel* aChannel,
nsINetworkInterceptController* aController,
nsICacheEntry* aEntry);
NS_DECL_NSIINTERCEPTEDCHANNEL
virtual void NotifyController() MOZ_OVERRIDE;
};
class InterceptedChannelContent : public InterceptedChannelBase
{
// The actual channel being intercepted.
nsRefPtr<HttpChannelChild> mChannel;
// Writeable buffer for use when synthesizing a response in a child process
nsCOMPtr<nsIOutputStream> mSynthesizedOutput;
nsCOMPtr<nsIInputStream> mSynthesizedInput;
// Pump to read the synthesized body in child processes
nsRefPtr<nsInputStreamPump> mStoragePump;
// Listener for the synthesized response to fix up the notifications before they reach
// the actual channel.
nsCOMPtr<nsIStreamListener> mStreamListener;
public:
InterceptedChannelContent(HttpChannelChild* aChannel,
nsINetworkInterceptController* aController,
nsIStreamListener* aListener);
NS_DECL_NSIINTERCEPTEDCHANNEL
virtual void NotifyController() MOZ_OVERRIDE;
};
} // namespace net
} // namespace mozilla
#endif // InterceptedChannel_h

View File

@ -14,6 +14,7 @@
#include "nsISocketTransport.h"
#include "nsITimer.h"
#include "NullHttpTransaction.h"
#include "mozilla/TimeStamp.h"
// a TLSFilterTransaction wraps another nsAHttpTransaction but
// applies a encode/decode filter of TLS onto the ReadSegments

View File

@ -56,6 +56,7 @@ UNIFIED_SOURCES += [
'HttpChannelParent.cpp',
'HttpChannelParentListener.cpp',
'HttpInfo.cpp',
'InterceptedChannel.cpp',
'nsHttp.cpp',
'nsHttpActivityDistributor.cpp',
'nsHttpAuthManager.cpp',

View File

@ -66,6 +66,7 @@
#include "CacheObserver.h"
#include "mozilla/Telemetry.h"
#include "AlternateServices.h"
#include "InterceptedChannel.h"
namespace mozilla { namespace net {
@ -200,43 +201,6 @@ AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded)
MOZ_EVENT_TRACER_DONE(channel, "net::http::redirect-callbacks");
}
// An object representing a channel that has been intercepted. This avoids complicating
// the actual channel implementation with the details of synthesizing responses.
class InterceptedChannel : public nsIInterceptedChannel
{
// The actual channel being intercepted
nsRefPtr<nsHttpChannel> mChannel;
// The interception controller to notify about the successful channel interception
nsCOMPtr<nsINetworkInterceptController> mController;
// Writeable cache entry for use when synthesizing a response
nsCOMPtr<nsICacheEntry> mSynthesizedCacheEntry;
// Response head for use when synthesizing
Maybe<nsHttpResponseHead> mSynthesizedResponseHead;
void EnsureSynthesizedResponse();
virtual ~InterceptedChannel() {}
public:
InterceptedChannel(nsHttpChannel* aChannel,
nsINetworkInterceptController* aController,
nsICacheEntry* aEntry)
: mChannel(aChannel)
, mController(aController)
, mSynthesizedCacheEntry(aEntry)
{
}
// Notify the interception controller that the channel has been intercepted
// and prepare the response body output stream.
void NotifyController();
NS_DECL_ISUPPORTS
NS_DECL_NSIINTERCEPTEDCHANNEL
};
//-----------------------------------------------------------------------------
// nsHttpChannel <public>
//-----------------------------------------------------------------------------
@ -2829,7 +2793,8 @@ nsHttpChannel::OpenCacheEntry(bool isHttps)
nsCOMPtr<nsINetworkInterceptController> controller;
GetCallback(controller);
nsRefPtr<InterceptedChannel> intercepted = new InterceptedChannel(this, controller, entry);
nsRefPtr<InterceptedChannelChrome> intercepted =
new InterceptedChannelChrome(this, controller, entry);
intercepted->NotifyController();
} else {
rv = cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags, this);
@ -6514,102 +6479,4 @@ nsHttpChannel::AwaitingCacheCallbacks()
return mCacheEntriesToWaitFor != 0;
}
NS_IMPL_ISUPPORTS(InterceptedChannel, nsIInterceptedChannel)
void
InterceptedChannel::NotifyController()
{
nsCOMPtr<nsIOutputStream> out;
nsresult rv = mSynthesizedCacheEntry->OpenOutputStream(0, getter_AddRefs(out));
NS_ENSURE_SUCCESS_VOID(rv);
rv = mController->ChannelIntercepted(this, out);
NS_ENSURE_SUCCESS_VOID(rv);
}
void
InterceptedChannel::EnsureSynthesizedResponse()
{
if (mSynthesizedResponseHead.isNothing()) {
mSynthesizedResponseHead.emplace();
}
}
NS_IMETHODIMP
InterceptedChannel::ResetInterception()
{
if (!mChannel) {
return NS_ERROR_NOT_AVAILABLE;
}
mSynthesizedCacheEntry->AsyncDoom(nullptr);
mSynthesizedCacheEntry = nullptr;
nsCOMPtr<nsIURI> uri;
mChannel->GetURI(getter_AddRefs(uri));
nsresult rv = mChannel->StartRedirectChannelToURI(uri, nsIChannelEventSink::REDIRECT_INTERNAL);
NS_ENSURE_SUCCESS(rv, rv);
mChannel = nullptr;
return NS_OK;
}
NS_IMETHODIMP
InterceptedChannel::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
{
if (!mSynthesizedCacheEntry) {
return NS_ERROR_NOT_AVAILABLE;
}
EnsureSynthesizedResponse();
nsAutoCString header = aName + NS_LITERAL_CSTRING(": ") + aValue;
// Overwrite any existing header.
nsresult rv = mSynthesizedResponseHead->ParseHeaderLine(header.get());
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
InterceptedChannel::FinishSynthesizedResponse()
{
if (!mChannel) {
return NS_ERROR_NOT_AVAILABLE;
}
mChannel->MarkIntercepted();
// First we ensure the appropriate metadata is set on the synthesized cache entry
// (i.e. the flattened response head)
nsCOMPtr<nsISupports> securityInfo;
nsresult rv = mChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
NS_ENSURE_SUCCESS(rv, rv);
EnsureSynthesizedResponse();
rv = DoAddCacheEntryHeaders(mChannel, mSynthesizedCacheEntry, mChannel->GetRequestHead(),
mSynthesizedResponseHead.ptr(), securityInfo);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uri;
mChannel->GetURI(getter_AddRefs(uri));
bool usingSSL = false;
uri->SchemeIs("https", &usingSSL);
// Then we open a real cache entry to read the synthesized response from.
rv = mChannel->OpenCacheEntry(usingSSL);
NS_ENSURE_SUCCESS(rv, rv);
mSynthesizedCacheEntry = nullptr;
if (!mChannel->AwaitingCacheCallbacks()) {
rv = mChannel->ContinueConnect();
NS_ENSURE_SUCCESS(rv, rv);
}
mChannel = nullptr;
return NS_OK;
}
} } // namespace mozilla::net

View File

@ -13,6 +13,7 @@
#include "nsProxyRelease.h"
#include "prinrval.h"
#include "TunnelUtils.h"
#include "mozilla/Mutex.h"
#include "nsIAsyncInputStream.h"
#include "nsIAsyncOutputStream.h"

View File

@ -19,16 +19,28 @@ function make_uri(url) {
// ensure the cache service is prepped when running the test
Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
var gotOnProgress;
var gotOnStatus;
function make_channel(url, body, cb) {
gotOnProgress = false;
gotOnStatus = false;
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
chan.notificationCallbacks = {
numChecks: 0,
QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterceptController,
Ci.nsIInterfaceRequestor]),
Ci.nsIInterfaceRequestor,
Ci.nsIProgressEventSink]),
getInterface: function(iid) {
return this.QueryInterface(iid);
},
onProgress: function(request, context, progress, progressMax) {
gotOnProgress = true;
},
onStatus: function(request, context, status, statusArg) {
gotOnStatus = true;
},
shouldPrepareForIntercept: function() {
do_check_eq(this.numChecks, 0);
this.numChecks++;
@ -72,16 +84,22 @@ function run_test() {
function handle_synthesized_response(request, buffer) {
do_check_eq(buffer, NON_REMOTE_BODY);
do_check_true(gotOnStatus);
do_check_true(gotOnProgress);
run_next_test();
}
function handle_synthesized_response_2(request, buffer) {
do_check_eq(buffer, NON_REMOTE_BODY_2);
do_check_true(gotOnStatus);
do_check_true(gotOnProgress);
run_next_test();
}
function handle_remote_response(request, buffer) {
do_check_eq(buffer, REMOTE_BODY);
do_check_true(gotOnStatus);
do_check_true(gotOnProgress);
run_next_test();
}
@ -140,6 +158,24 @@ add_test(function() {
chan.asyncOpen(new ChannelListener(handle_remote_response, null), null);
});
// ensure that the intercepted channel supports suspend/resume
add_test(function() {
var chan = make_channel(URL + '/body', null, function(intercepted, stream) {
var synthesized = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
synthesized.data = NON_REMOTE_BODY;
NetUtil.asyncCopy(synthesized, stream, function() {
// set the content-type to ensure that the stream converter doesn't hold up notifications
// and cause the test to fail
intercepted.synthesizeHeader("Content-Type", "text/plain");
intercepted.finishSynthesizedResponse();
});
});
chan.asyncOpen(new ChannelListener(handle_synthesized_response, null,
CL_ALLOW_UNKNOWN_CL | CL_SUSPEND | CL_EXPECT_3S_DELAY), null);
});
add_test(function() {
httpServer.stop(run_next_test);
});

View File

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

View File

@ -33,6 +33,7 @@ skip-if = true
[test_reentrancy_wrap.js]
[test_resumable_channel_wrap.js]
[test_simple_wrap.js]
[test_synthesized_response_wrap.js]
[test_xmlhttprequest_wrap.js]
[test_XHR_redirects.js]
[test_redirect_history_wrap.js]