author | Steve Workman <sworkman@mozilla.com> |
Mon, 10 Mar 2014 23:04:28 +0100 | |
changeset 191089 | 760131490c817d3ff167e708f516da5a16761c91 |
parent 191088 | 6e7f5c8e3e83f89de43e1035a17a1127b6ca0cc5 |
child 191090 | 79d8037bf2ed299ca51529229a2f32c31a098697 |
push id | 474 |
push user | asasaki@mozilla.com |
push date | Mon, 02 Jun 2014 21:01:02 +0000 |
treeherder | mozilla-release@967f4cf1b31c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jduell |
bugs | 975338 |
milestone | 30.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/netwerk/base/public/moz.build +++ b/netwerk/base/public/moz.build @@ -31,16 +31,17 @@ XPIDL_SOURCES += [ 'nsIChannelPolicy.idl', 'nsIChildChannel.idl', 'nsIContentSniffer.idl', 'nsICryptoFIPSInfo.idl', 'nsICryptoHash.idl', 'nsICryptoHMAC.idl', 'nsIDashboard.idl', 'nsIDashboardEventNotifier.idl', + 'nsIDivertableChannel.idl', 'nsIDownloader.idl', 'nsIEncodedChannel.idl', 'nsIExternalProtocolHandler.idl', 'nsIFileStreams.idl', 'nsIFileURL.idl', 'nsIIncrementalDownload.idl', 'nsIInputStreamChannel.idl', 'nsIInputStreamPump.idl',
new file mode 100644 --- /dev/null +++ b/netwerk/base/public/nsIDivertableChannel.idl @@ -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(); +};
new file mode 100644 --- /dev/null +++ b/netwerk/base/src/ADivertableParentChannel.h @@ -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
new file mode 100644 --- /dev/null +++ b/netwerk/base/src/ChannelDiverterChild.cpp @@ -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
new file mode 100644 --- /dev/null +++ b/netwerk/base/src/ChannelDiverterChild.h @@ -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_ */
new file mode 100644 --- /dev/null +++ b/netwerk/base/src/ChannelDiverterParent.cpp @@ -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
new file mode 100644 --- /dev/null +++ b/netwerk/base/src/ChannelDiverterParent.h @@ -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_ */
--- a/netwerk/base/src/moz.build +++ b/netwerk/base/src/moz.build @@ -7,23 +7,27 @@ EXPORTS += [ 'nsFileStreams.h', 'nsMIMEInputStream.h', 'nsTemporaryFileInputStream.h', 'nsURLHelper.h', ] EXPORTS.mozilla.net += [ + 'ChannelDiverterChild.h', + 'ChannelDiverterParent.h', 'Dashboard.h', 'DashboardTypes.h', ] UNIFIED_SOURCES += [ 'ArrayBufferInputStream.cpp', 'BackgroundFileSaver.cpp', + 'ChannelDiverterChild.cpp', + 'ChannelDiverterParent.cpp', 'Dashboard.cpp', 'EventTokenBucket.cpp', 'LoadContextInfo.cpp', 'NetworkActivityMonitor.cpp', 'nsAsyncStreamCopier.cpp', 'nsAuthInformationHolder.cpp', 'nsBase64Encoder.cpp', 'nsBaseChannel.cpp', @@ -108,16 +112,17 @@ FAIL_ON_WARNINGS = True MSVC_ENABLE_PGO = True include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'necko' LOCAL_INCLUDES += [ '/dom/base', + '/netwerk/protocol/http' ] if 'rtsp' in CONFIG['NECKO_PROTOCOLS']: LOCAL_INCLUDES += [ '/netwerk/protocol/rtsp/controller', '/netwerk/protocol/rtsp/rtsp', ]
--- a/netwerk/base/src/nsBaseChannel.cpp +++ b/netwerk/base/src/nsBaseChannel.cpp @@ -151,17 +151,17 @@ nsBaseChannel::ContinueRedirect() ChannelDone(); return NS_OK; } bool nsBaseChannel::HasContentTypeHint() const { - NS_ASSERTION(!IsPending(), "HasContentTypeHint called too late"); + NS_ASSERTION(!Pending(), "HasContentTypeHint called too late"); return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE); } nsresult nsBaseChannel::PushStreamConverter(const char *fromType, const char *toType, bool invalidatesContentLength, nsIStreamListener **result) @@ -203,17 +203,17 @@ nsBaseChannel::BeginPumpingData() if (channel) { rv = NS_DispatchToCurrentThread(new RedirectRunnable(this, channel)); if (NS_SUCCEEDED(rv)) mWaitingOnAsyncRedirect = true; 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 // release mPump if we return an error. rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, -1, -1, 0, 0, true); if (NS_SUCCEEDED(rv)) @@ -307,17 +307,17 @@ nsBaseChannel::GetName(nsACString &resul return NS_OK; } return mURI->GetSpec(result); } NS_IMETHODIMP nsBaseChannel::IsPending(bool *result) { - *result = IsPending(); + *result = Pending(); return NS_OK; } NS_IMETHODIMP nsBaseChannel::GetStatus(nsresult *status) { if (mPump && NS_SUCCEEDED(mStatus)) { mPump->GetStatus(status); @@ -720,17 +720,17 @@ NS_IMETHODIMP nsBaseChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) { // If both mStatus and status are failure codes, we keep mStatus as-is since // that is consistent with our GetStatus and Cancel methods. 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 mListener->OnStopRequest(this, mListenerContext, mStatus); ChannelDone(); // No need to suspend pump in this scope since we will not be receiving // any more events from it.
--- a/netwerk/base/src/nsBaseChannel.h +++ b/netwerk/base/src/nsBaseChannel.h @@ -152,19 +152,19 @@ public: } // Test the load flags bool HasLoadFlag(uint32_t flag) { return (mLoadFlags & flag) != 0; } // 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) { GetInterface(NS_GET_TEMPLATE_IID(T), getter_AddRefs(result)); } // Helper function for calling QueryInterface on this. nsQueryInterface do_QueryInterface() {
--- a/netwerk/ipc/NeckoChannelParams.ipdlh +++ b/netwerk/ipc/NeckoChannelParams.ipdlh @@ -1,15 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 ft=c: */ /* 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 PHttpChannel; +include protocol PFTPChannel; include URIParams; include InputStreamParams; using struct mozilla::void_t from "ipc/IPCMessageUtils.h"; using RequestHeaderTuples from "mozilla/net/PHttpChannelParams.h"; using struct nsHttpAtom from "nsHttp.h"; namespace mozilla { @@ -75,10 +77,16 @@ struct FTPChannelConnectArgs }; union FTPChannelCreationArgs { FTPChannelOpenArgs; // For AsyncOpen: the common case. FTPChannelConnectArgs; // Used for redirected-to channels }; +union ChannelDiverterArgs +{ + PHttpChannel; + PFTPChannel; +}; + } // namespace ipc } // namespace mozilla
--- a/netwerk/ipc/NeckoChild.cpp +++ b/netwerk/ipc/NeckoChild.cpp @@ -11,16 +11,17 @@ #include "mozilla/dom/ContentChild.h" #include "mozilla/net/HttpChannelChild.h" #include "mozilla/net/CookieServiceChild.h" #include "mozilla/net/WyciwygChannelChild.h" #include "mozilla/net/FTPChannelChild.h" #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" #ifdef NECKO_PROTOCOL_rtsp #include "mozilla/net/RtspControllerChild.h" #endif #include "SerializedLoadContext.h" @@ -261,10 +262,23 @@ NeckoChild::AllocPRemoteOpenFileChild(co bool NeckoChild::DeallocPRemoteOpenFileChild(PRemoteOpenFileChild* aChild) { RemoteOpenFileChild *p = static_cast<RemoteOpenFileChild*>(aChild); p->ReleaseIPDLReference(); 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
--- a/netwerk/ipc/NeckoChild.h +++ b/netwerk/ipc/NeckoChild.h @@ -57,16 +57,20 @@ protected: const uint32_t& aFlags) MOZ_OVERRIDE; virtual bool DeallocPDNSRequestChild(PDNSRequestChild*) MOZ_OVERRIDE; virtual PRemoteOpenFileChild* AllocPRemoteOpenFileChild(const URIParams&, const OptionalURIParams&) MOZ_OVERRIDE; 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; }; /** * Reference to the PNecko Child protocol. * Null if this is not a content process. */ extern PNeckoChild *gNeckoChild;
--- a/netwerk/ipc/NeckoParent.cpp +++ b/netwerk/ipc/NeckoParent.cpp @@ -13,16 +13,17 @@ #include "mozilla/net/WyciwygChannelParent.h" #include "mozilla/net/FTPChannelParent.h" #include "mozilla/net/WebSocketChannelParent.h" #ifdef NECKO_PROTOCOL_rtsp #include "mozilla/net/RtspControllerParent.h" #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" #include "mozilla/dom/network/TCPServerSocketParent.h" #include "mozilla/dom/network/UDPSocketParent.h" #include "mozilla/ipc/URIUtils.h" #include "mozilla/LoadContext.h" #include "mozilla/AppProcessChecker.h" @@ -603,16 +604,38 @@ bool NeckoParent::RecvCancelHTMLDNSPrefetch(const nsString& hostname, const uint16_t& flags, const nsresult& reason) { nsHTMLDNSPrefetch::CancelPrefetch(hostname, flags, reason); 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) { aCtx->SetNeckoParent(this); // For cloning protocols managed by this. PNeckoParent::CloneManagees(aSource, aCtx); }
--- a/netwerk/ipc/NeckoParent.h +++ b/netwerk/ipc/NeckoParent.h @@ -139,16 +139,24 @@ protected: const nsresult& reason) MOZ_OVERRIDE; virtual mozilla::ipc::IProtocol* CloneProtocol(Channel* aChannel, mozilla::ipc::ProtocolCloneContext* aCtx) MOZ_OVERRIDE; 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; }; } // namespace net } // namespace mozilla
new file mode 100644 --- /dev/null +++ b/netwerk/ipc/PChannelDiverter.ipdl @@ -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
--- a/netwerk/ipc/PNecko.ipdl +++ b/netwerk/ipc/PNecko.ipdl @@ -12,16 +12,17 @@ include protocol PBrowser; include protocol PWyciwygChannel; include protocol PFTPChannel; include protocol PWebSocket; include protocol PTCPSocket; include protocol PTCPServerSocket; include protocol PUDPSocket; include protocol PRemoteOpenFile; include protocol PDNSRequest; +include protocol PChannelDiverter; include protocol PBlob; //FIXME: bug #792908 include protocol PRtspController; include URIParams; include InputStreamParams; include NeckoChannelParams; @@ -40,16 +41,17 @@ sync protocol PNecko manages PFTPChannel; manages PWebSocket; manages PTCPSocket; manages PTCPServerSocket; manages PUDPSocket; manages PDNSRequest; manages PRemoteOpenFile; manages PRtspController; + manages PChannelDiverter; parent: __delete__(); PCookieService(); PHttpChannel(nullable PBrowser browser, SerializedLoadContext loadContext, HttpChannelCreationArgs args); @@ -63,16 +65,17 @@ parent: PDNSRequest(nsCString hostName, uint32_t flags); PRemoteOpenFile(URIParams fileuri, OptionalURIParams appuri); HTMLDNSPrefetch(nsString hostname, uint16_t flags); CancelHTMLDNSPrefetch(nsString hostname, uint16_t flags, nsresult reason); PRtspController(); + PChannelDiverter(ChannelDiverterArgs channel); both: PTCPSocket(); }; } // namespace net } // namespace mozilla
--- a/netwerk/ipc/moz.build +++ b/netwerk/ipc/moz.build @@ -26,16 +26,17 @@ UNIFIED_SOURCES += [ 'NeckoCommon.cpp', 'NeckoParent.cpp', 'RemoteOpenFileChild.cpp', 'RemoteOpenFileParent.cpp', ] IPDL_SOURCES = [ 'NeckoChannelParams.ipdlh', + 'PChannelDiverter.ipdl', 'PNecko.ipdl', 'PRemoteOpenFile.ipdl', 'PRtspController.ipdl', ] FAIL_ON_WARNINGS = True include('/ipc/chromium/chromium-config.mozbuild')
--- a/netwerk/protocol/file/nsFileChannel.cpp +++ b/netwerk/protocol/file/nsFileChannel.cpp @@ -426,17 +426,17 @@ nsFileChannel::GetFile(nsIFile **file) //----------------------------------------------------------------------------- // nsFileChannel::nsIUploadChannel NS_IMETHODIMP 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; if (mUploadLength < 0) { // Make sure we know how much data we are uploading. uint64_t avail; nsresult rv = mUploadStream->Available(&avail); if (NS_FAILED(rv))
--- a/netwerk/protocol/ftp/FTPChannelChild.cpp +++ b/netwerk/protocol/ftp/FTPChannelChild.cpp @@ -1,16 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et 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/NeckoChild.h" +#include "mozilla/net/ChannelDiverterChild.h" #include "mozilla/net/FTPChannelChild.h" #include "mozilla/dom/TabChild.h" #include "nsFtpProtocolHandler.h" #include "nsITabChild.h" #include "nsStringStream.h" #include "nsNetUtil.h" #include "base/compiler_specific.h" #include "mozilla/ipc/InputStreamUtils.h" @@ -28,16 +29,19 @@ namespace net { FTPChannelChild::FTPChannelChild(nsIURI* uri) : mIPCOpen(false) , mCanceled(false) , mSuspendCount(0) , mIsPending(false) , 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. NS_ADDREF(gFtpHandler); SetURI(uri); mEventQ = new ChannelEventQueue(static_cast<nsIFTPChannel*>(this)); } @@ -62,23 +66,24 @@ FTPChannelChild::ReleaseIPDLReference() mIPCOpen = false; Release(); } //----------------------------------------------------------------------------- // FTPChannelChild::nsISupports //----------------------------------------------------------------------------- -NS_IMPL_ISUPPORTS_INHERITED5(FTPChannelChild, +NS_IMPL_ISUPPORTS_INHERITED6(FTPChannelChild, nsBaseChannel, nsIFTPChannel, nsIUploadChannel, nsIResumableChannel, nsIProxiedChannel, - nsIChildChannel) + nsIChildChannel, + nsIDivertableChannel) //----------------------------------------------------------------------------- NS_IMETHODIMP FTPChannelChild::GetLastModifiedTime(PRTime* lastModifiedTime) { *lastModifiedTime = mLastModifiedTime; return NS_OK; @@ -237,16 +242,23 @@ class FTPStartRequestEvent : public Chan bool FTPChannelChild::RecvOnStartRequest(const int64_t& aContentLength, const nsCString& aContentType, const PRTime& aLastModified, 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)); } else { DoOnStartRequest(aContentLength, aContentType, aLastModified, aEntityID, aURI); } return true; @@ -256,30 +268,45 @@ void FTPChannelChild::DoOnStartRequest(const int64_t& aContentLength, const nsCString& aContentType, const PRTime& aLastModified, const nsCString& aEntityID, const URIParams& aURI) { 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; mEntityID = aEntityID; nsCString spec; nsCOMPtr<nsIURI> uri = DeserializeURI(aURI); uri->GetSpec(spec); nsBaseChannel::URI()->SetSpec(spec); AutoEventEnqueuer ensureSerialDispatch(mEventQ); 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 { public: FTPDataAvailableEvent(FTPChannelChild* aChild, const nsCString& aData, const uint64_t& aOffset, const uint32_t& aCount) : mChild(aChild), mData(aData), mOffset(aOffset), mCount(aCount) {} @@ -291,31 +318,45 @@ class FTPDataAvailableEvent : public Cha uint32_t mCount; }; bool 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; } void FTPChannelChild::DoOnDataAvailable(const nsCString& data, const uint64_t& offset, const uint32_t& count) { 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; // 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 // support only reading part of the data, allowing later calls to read the // rest. @@ -346,30 +387,41 @@ class FTPStopRequestEvent : public Chann private: FTPChannelChild* mChild; nsresult mStatusCode; }; 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 { DoOnStopRequest(statusCode); } return true; } void 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; { // Ensure that all queued ipdl events are dispatched before // we initiate protocol deletion below. mIsPending = false; AutoEventEnqueuer ensureSerialDispatch(mEventQ); (void)mListener->OnStopRequest(this, mListenerContext, statusCode); @@ -425,16 +477,73 @@ FTPChannelChild::DoFailedAsyncOpen(const mListener = nullptr; mListenerContext = nullptr; if (mIPCOpen) 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: FTPDeleteSelfEvent(FTPChannelChild* aChild) : mChild(aChild) {} void Run() { mChild->DoDeleteSelf(); } private: FTPChannelChild* mChild; @@ -470,30 +579,39 @@ FTPChannelChild::Cancel(nsresult status) SendCancel(status); return NS_OK; } 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(); return NS_OK; } NS_IMETHODIMP 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(); return NS_OK; } //----------------------------------------------------------------------------- @@ -547,11 +665,44 @@ FTPChannelChild::CompleteRedirectSetup(n mLoadGroup->AddRequest(this, nullptr); // We already have an open IPDL connection to the parent. If on-modify-request // listeners or load group observers canceled us, let the parent handle it // and send it back to us naturally. return NS_OK; } +//----------------------------------------------------------------------------- +// 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
--- a/netwerk/protocol/ftp/FTPChannelChild.h +++ b/netwerk/protocol/ftp/FTPChannelChild.h @@ -11,16 +11,17 @@ #include "mozilla/net/PFTPChannelChild.h" #include "mozilla/net/ChannelEventQueue.h" #include "nsBaseChannel.h" #include "nsIFTPChannel.h" #include "nsIUploadChannel.h" #include "nsIProxiedChannel.h" #include "nsIResumableChannel.h" #include "nsIChildChannel.h" +#include "nsIDivertableChannel.h" #include "nsIStreamListener.h" #include "PrivateBrowsingChannel.h" namespace mozilla { namespace net { // This class inherits logic from nsBaseChannel that is not needed for an @@ -30,26 +31,28 @@ namespace net { class FTPChannelChild : public PFTPChannelChild , public nsBaseChannel , public nsIFTPChannel , public nsIUploadChannel , public nsIResumableChannel , public nsIProxiedChannel , public nsIChildChannel + , public nsIDivertableChannel { public: typedef ::nsIStreamListener nsIStreamListener; NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIFTPCHANNEL NS_DECL_NSIUPLOADCHANNEL NS_DECL_NSIRESUMABLECHANNEL NS_DECL_NSIPROXIEDCHANNEL NS_DECL_NSICHILDCHANNEL + NS_DECL_NSIDIVERTABLECHANNEL NS_IMETHOD Cancel(nsresult status); NS_IMETHOD Suspend(); NS_IMETHOD Resume(); FTPChannelChild(nsIURI* uri); virtual ~FTPChannelChild(); @@ -63,27 +66,31 @@ public: NS_IMETHOD IsPending(bool* result); nsresult OpenContentStream(bool async, nsIInputStream** stream, nsIChannel** channel) MOZ_OVERRIDE; bool IsSuspended(); + void FlushedForDiversion(); + protected: bool RecvOnStartRequest(const int64_t& aContentLength, const nsCString& aContentType, const PRTime& aLastModified, const nsCString& aEntityID, const URIParams& aURI) MOZ_OVERRIDE; bool RecvOnDataAvailable(const nsCString& data, const uint64_t& offset, 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, const nsCString& aContentType, const PRTime& aLastModified, const nsCString& aEntityID, const URIParams& aURI); void DoOnDataAvailable(const nsCString& data, @@ -107,16 +114,25 @@ private: bool mCanceled; uint32_t mSuspendCount; bool mIsPending; bool mWasOpened; 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 FTPChannelChild::IsSuspended() { return mSuspendCount != 0; }
--- a/netwerk/protocol/ftp/FTPChannelParent.cpp +++ b/netwerk/protocol/ftp/FTPChannelParent.cpp @@ -6,30 +6,35 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/net/FTPChannelParent.h" #include "nsFTPChannel.h" #include "nsNetUtil.h" #include "nsFtpProtocolHandler.h" #include "mozilla/ipc/InputStreamUtils.h" #include "mozilla/ipc/URIUtils.h" +#include "mozilla/unused.h" #include "SerializedLoadContext.h" using namespace mozilla::ipc; #undef LOG #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args) namespace mozilla { namespace net { FTPChannelParent::FTPChannelParent(nsILoadContext* aLoadContext, PBOverrideStatus aOverrideStatus) : 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); NS_ASSERTION(handler, "no ftp handler"); } FTPChannelParent::~FTPChannelParent() { @@ -174,25 +179,112 @@ FTPChannelParent::RecvSuspend() bool FTPChannelParent::RecvResume() { if (mChannel) mChannel->Resume(); 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 //----------------------------------------------------------------------------- NS_IMETHODIMP 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); int64_t contentLength; chan->GetContentLength(&contentLength); nsCString contentType; chan->GetContentType(contentType); @@ -230,16 +322,22 @@ FTPChannelParent::OnStartRequest(nsIRequ NS_IMETHODIMP FTPChannelParent::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode) { 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; } return NS_OK; } //----------------------------------------------------------------------------- @@ -249,17 +347,24 @@ FTPChannelParent::OnStopRequest(nsIReque NS_IMETHODIMP FTPChannelParent::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aInputStream, uint64_t aOffset, 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)) return rv; if (mIPCClosed || !SendOnDataAvailable(data, aOffset, aCount)) return NS_ERROR_UNEXPECTED; @@ -291,12 +396,201 @@ FTPChannelParent::GetInterface(const nsI NS_ADDREF(mLoadContext); *result = static_cast<nsILoadContext*>(mLoadContext); return NS_OK; } 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
--- a/netwerk/protocol/ftp/FTPChannelParent.h +++ b/netwerk/protocol/ftp/FTPChannelParent.h @@ -3,63 +3,105 @@ /* 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 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" #include "nsIInterfaceRequestor.h" class nsFtpChannel; class nsILoadContext; namespace mozilla { namespace net { class FTPChannelParent : public PFTPChannelParent , public nsIParentChannel , public nsIInterfaceRequestor + , public ADivertableParentChannel { public: NS_DECL_ISUPPORTS NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER NS_DECL_NSIPARENTCHANNEL NS_DECL_NSIINTERFACEREQUESTOR FTPChannelParent(nsILoadContext* aLoadContext, PBOverrideStatus aOverrideStatus); virtual ~FTPChannelParent(); 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); // used to connect redirected-to channel in parent with just created // ChildChannel. Used during HTTP->FTP redirects. bool ConnectChannel(const uint32_t& channelId); 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; nsRefPtr<nsFtpChannel> mChannel; bool mIPCClosed; 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 } // namespace mozilla #endif // mozilla_net_FTPChannelParent_h
--- a/netwerk/protocol/ftp/PFTPChannel.ipdl +++ b/netwerk/protocol/ftp/PFTPChannel.ipdl @@ -27,20 +27,39 @@ parent: // see PNecko.ipdl __delete__(); Cancel(nsresult status); 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(); }; } // namespace net } // namespace mozilla
--- a/netwerk/protocol/ftp/nsFTPChannel.cpp +++ b/netwerk/protocol/ftp/nsFTPChannel.cpp @@ -33,17 +33,17 @@ NS_IMPL_ISUPPORTS_INHERITED4(nsFtpChanne //----------------------------------------------------------------------------- NS_IMETHODIMP 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; // NOTE: contentLength is intentionally ignored here. return NS_OK; } @@ -56,17 +56,17 @@ nsFtpChannel::GetUploadStream(nsIInputSt return NS_OK; } //----------------------------------------------------------------------------- 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()); return NS_OK; } NS_IMETHODIMP nsFtpChannel::GetEntityID(nsACString& entityID) @@ -191,8 +191,31 @@ nsFtpChannel::GetFTPEventSink(nsCOMPtr<n nsCOMPtr<nsIFTPEventSink> ftpSink; GetCallback(ftpSink); if (ftpSink) { mFTPEventSink = new FTPEventSinkProxy(ftpSink); } } 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; +}
--- a/netwerk/protocol/ftp/nsFTPChannel.h +++ b/netwerk/protocol/ftp/nsFTPChannel.h @@ -31,29 +31,36 @@ public: NS_DECL_NSIRESUMABLECHANNEL NS_DECL_NSIPROXIEDCHANNEL nsFtpChannel(nsIURI *uri, nsIProxyInfo *pi) : mProxyInfo(pi) , mStartPos(0) , mResumeRequested(false) , mLastModifiedTime(0) + , mForcePending(false) { SetURI(uri); } nsIProxyInfo *ProxyInfo() { return mProxyInfo; } void SetProxyInfo(nsIProxyInfo *pi) { 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; } // Download from this byte offset uint64_t StartPos() { return mStartPos; } // ID of the entity to resume downloading const nsCString &EntityID() { @@ -76,26 +83,30 @@ public: // Data stream to upload nsIInputStream *UploadStream() { return mUploadStream; } // 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, nsIChannel** channel); virtual bool GetStatusArg(nsresult status, nsString &statusArg); virtual void OnCallbacksChanged(); private: nsCOMPtr<nsIProxyInfo> mProxyInfo; nsCOMPtr<nsIFTPEventSink> mFTPEventSink; nsCOMPtr<nsIInputStream> mUploadStream; uint64_t mStartPos; nsCString mEntityID; bool mResumeRequested; PRTime mLastModifiedTime; + bool mForcePending; }; #endif /* nsFTPChannel_h___ */
--- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -15,16 +15,17 @@ #include "nsStringStream.h" #include "nsHttpHandler.h" #include "nsNetUtil.h" #include "nsSerializationHelper.h" #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" using namespace mozilla::dom; using namespace mozilla::ipc; namespace mozilla { namespace net { @@ -36,16 +37,19 @@ namespace net { HttpChannelChild::HttpChannelChild() : HttpAsyncAborter<HttpChannelChild>(MOZ_THIS_IN_INITIALIZER_LIST()) , mIsFromCache(false) , mCacheEntryAvailable(false) , mCacheExpirationTime(nsICache::NO_EXPIRATION_TIME) , mSendResumeAt(false) , mIPCOpen(false) , mKeptAlive(false) + , mDivertingToParent(false) + , mFlushedForDiversion(false) + , mSuspendSent(false) { LOG(("Creating HttpChannelChild @%x\n", this)); mEventQ = new ChannelEventQueue(static_cast<nsIHttpChannel*>(this)); } HttpChannelChild::~HttpChannelChild() { @@ -97,16 +101,17 @@ NS_INTERFACE_MAP_BEGIN(HttpChannelChild) NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel) NS_INTERFACE_MAP_ENTRY(nsITraceableChannel) NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer) NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel) NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) 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) //----------------------------------------------------------------------------- // HttpChannelChild::PHttpChannelChild //----------------------------------------------------------------------------- void HttpChannelChild::AddIPDLReference() @@ -221,16 +226,23 @@ HttpChannelChild::RecvOnStartRequest(con const bool& isFromCache, const bool& cacheEntryAvailable, const uint32_t& cacheExpirationTime, const nsCString& cachedCharset, const nsCString& securityInfoSerialization, 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, cacheEntryAvailable, cacheExpirationTime, cachedCharset, securityInfoSerialization, selfAddr, peerAddr)); } else { @@ -250,16 +262,23 @@ HttpChannelChild::OnStartRequest(const n const uint32_t& cacheExpirationTime, const nsCString& cachedCharset, const nsCString& securityInfoSerialization, const NetAddr& selfAddr, const NetAddr& peerAddr) { 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); if (!securityInfoSerialization.IsEmpty()) { NS_DeserializeObject(securityInfoSerialization, getter_AddRefs(mSecurityInfo)); } @@ -281,16 +300,24 @@ HttpChannelChild::OnStartRequest(const n mTracingEnabled = false; nsresult rv = mListener->OnStartRequest(this, mListenerContext); if (NS_FAILED(rv)) { Cancel(rv); return; } + if (mDivertingToParent) { + mListener = nullptr; + mListenerContext = nullptr; + if (mLoadGroup) { + mLoadGroup->RemoveRequest(this, nullptr, mStatus); + } + } + if (mResponseHead) SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie)); rv = ApplyContentConversions(); if (NS_FAILED(rv)) Cancel(rv); mSelfAddr = selfAddr; @@ -330,36 +357,51 @@ class TransportAndDataEvent : public Cha bool HttpChannelChild::RecvOnTransportAndData(const nsresult& status, const uint64_t& progress, const uint64_t& progressMax, 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 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; } void HttpChannelChild::OnTransportAndData(const nsresult& status, const uint64_t progress, const uint64_t& progressMax, const nsCString& data, const uint64_t& offset, const uint32_t& count) { 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; // 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 @@ -426,30 +468,43 @@ class StopRequestEvent : public ChannelE private: HttpChannelChild* mChild; nsresult mStatusCode; }; 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; } void 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)) mStatus = statusCode; { // We must flush the queue before we Send__delete__ // (although we really shouldn't receive any msgs after OnStop), // so make sure this goes out of scope before then. @@ -772,16 +827,70 @@ HttpChannelChild::RecvRedirect3Complete( if (mEventQ->ShouldEnqueue()) { mEventQ->Enqueue(new Redirect3Event(this)); } else { Redirect3Complete(); } 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() { nsresult rv = NS_OK; // Chrome channel has been AsyncOpen'd. Reflect this in child. if (mRedirectChannelChild) rv = mRedirectChannelChild->CompleteRedirectSetup(mListener, @@ -938,33 +1047,42 @@ HttpChannelChild::Cancel(nsresult status } return NS_OK; } 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(); return NS_OK; } NS_IMETHODIMP HttpChannelChild::Resume() { NS_ENSURE_TRUE(RemoteChannelExists(), NS_ERROR_NOT_AVAILABLE); NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED); 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); mCallOnResume = nullptr; } } mEventQ->Resume(); @@ -1436,11 +1554,42 @@ NS_IMETHODIMP HttpChannelChild::AddCooki } NS_IMETHODIMP HttpChannelChild::GetClientSetRequestHeaders(RequestHeaderTuples **aRequestHeaders) { *aRequestHeaders = &mClientSetRequestHeaders; 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
--- a/netwerk/protocol/http/HttpChannelChild.h +++ b/netwerk/protocol/http/HttpChannelChild.h @@ -22,42 +22,45 @@ #include "nsIApplicationCacheChannel.h" #include "nsIUploadChannel2.h" #include "nsIResumableChannel.h" #include "nsIProxiedChannel.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "nsIAssociatedContentSecurity.h" #include "nsIChildChannel.h" #include "nsIHttpChannelChild.h" +#include "nsIDivertableChannel.h" #include "mozilla/net/DNS.h" namespace mozilla { namespace net { class HttpChannelChild : public PHttpChannelChild , public HttpBaseChannel , public HttpAsyncAborter<HttpChannelChild> , public nsICacheInfoChannel , public nsIProxiedChannel , public nsIApplicationCacheChannel , public nsIAsyncVerifyRedirectCallback , public nsIAssociatedContentSecurity , public nsIChildChannel , public nsIHttpChannelChild + , public nsIDivertableChannel { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSICACHEINFOCHANNEL NS_DECL_NSIPROXIEDCHANNEL NS_DECL_NSIAPPLICATIONCACHECONTAINER NS_DECL_NSIAPPLICATIONCACHECHANNEL NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK NS_DECL_NSIASSOCIATEDCONTENTSECURITY NS_DECL_NSICHILDCHANNEL NS_DECL_NSIHTTPCHANNELCHILD + NS_DECL_NSIDIVERTABLECHANNEL HttpChannelChild(); virtual ~HttpChannelChild(); // Methods HttpBaseChannel didn't implement for us or that we override. // // nsIRequest NS_IMETHOD Cancel(nsresult status); @@ -85,16 +88,18 @@ public: // IPDL holds a reference while the PHttpChannel protocol is live (starting at // AsyncOpen, and ending at either OnStopRequest or any IPDL error, either of // which call NeckoChild::DeallocPHttpChannelChild()). void AddIPDLReference(); void ReleaseIPDLReference(); bool IsSuspended(); + void FlushedForDiversion(); + protected: bool RecvOnStartRequest(const nsHttpResponseHead& responseHead, const bool& useResponseHead, const nsHttpHeaderArray& requestHeaders, const bool& isFromCache, const bool& cacheEntryAvailable, const uint32_t& cacheExpirationTime, const nsCString& cachedCharset, @@ -113,16 +118,18 @@ protected: bool RecvFailedAsyncOpen(const nsresult& status) MOZ_OVERRIDE; bool RecvRedirect1Begin(const uint32_t& newChannel, const URIParams& newURI, const uint32_t& redirectFlags, const nsHttpResponseHead& responseHead) MOZ_OVERRIDE; 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); virtual void DoNotifyListenerCleanup(); private: RequestHeaderTuples mClientSetRequestHeaders; nsCOMPtr<nsIChildChannel> mRedirectChannelChild; @@ -135,16 +142,25 @@ private: // If ResumeAt is called before AsyncOpen, we need to send extra data upstream bool mSendResumeAt; bool mIPCOpen; 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; } void AssociateApplicationCache(const nsCString &groupID, const nsCString &clientID); void OnStartRequest(const nsHttpResponseHead& responseHead, const bool& useResponseHead, const nsHttpHeaderArray& requestHeaders,
--- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -38,16 +38,20 @@ HttpChannelParent::HttpChannelParent(PBr , mStoredStatus(NS_OK) , mStoredProgress(0) , mStoredProgressMax(0) , mSentRedirect1Begin(false) , mSentRedirect1BeginFailed(false) , 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 = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http"); MOZ_ASSERT(gHttpHandler); mHttpHandler = gHttpHandler; @@ -174,67 +178,67 @@ HttpChannelParent::DoAsyncOpen( const U this, uriSpec.get())); nsresult rv; nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv)); 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); bool setChooseApplicationCache = chooseApplicationCache; if (appCacheChan && appCacheService) { // We might potentially want to drop this flag (that is TRUE by default) // after we successfully associate the channel with an application cache // reported by the channel child. Dropping it here may be too early. @@ -268,49 +272,50 @@ HttpChannelParent::DoAsyncOpen( const U // done mPBOverride logic by this point. chooseAppCache = NS_ShouldCheckAppCache(principal, NS_UsePrivateBrowsing(mChannel)); } appCacheChan->SetChooseApplicationCache(chooseAppCache); } } - rv = httpChan->AsyncOpen(channelListener, nullptr); + rv = mChannel->AsyncOpen(mParentListener, nullptr); if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv); return true; } bool 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); } } return true; } 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 = do_QueryInterface(mRedirectChannel); if (priorityRedirectChannel) priorityRedirectChannel->SetPriority(priority); return true; @@ -334,18 +339,17 @@ HttpChannelParent::RecvResume() return true; } bool HttpChannelParent::RecvCancel(const nsresult& status) { // May receive cancel before channel has been constructed! if (mChannel) { - nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get()); - httpChan->Cancel(status); + mChannel->Cancel(status); } return true; } bool HttpChannelParent::RecvSetCacheTokenCachedCharset(const nsCString& charset) { @@ -430,25 +434,114 @@ HttpChannelParent::RecvMarkOfflineCacheE if (mOfflineForeignMarker) { mOfflineForeignMarker->MarkAsForeign(); mOfflineForeignMarker = 0; } 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 //----------------------------------------------------------------------------- NS_IMETHODIMP 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(); bool isFromCache = false; chan->IsFromCache(&isFromCache); uint32_t expirationTime = nsICache::NO_EXPIRATION_TIME; chan->GetCacheTokenExpirationTime(&expirationTime); nsCString cachedCharset; @@ -487,39 +580,41 @@ HttpChannelParent::OnStartRequest(nsIReq chan->GetSecurityInfo(getter_AddRefs(secInfoSupp)); if (secInfoSupp) { mAssociatedContentSecurity = do_QueryInterface(secInfoSupp); nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp); if (secInfoSer) NS_SerializeToString(secInfoSer, secInfoSerialization); } - nsHttpChannel *httpChan = static_cast<nsHttpChannel *>(mChannel.get()); if (mIPCClosed || !SendOnStartRequest(responseHead ? *responseHead : nsHttpResponseHead(), !!responseHead, requestHead->Headers(), isFromCache, mCacheEntry ? true : false, expirationTime, cachedCharset, secInfoSerialization, - httpChan->GetSelfAddr(), httpChan->GetPeerAddr())) + mChannel->GetSelfAddr(), mChannel->GetPeerAddr())) { return NS_ERROR_UNEXPECTED; } return NS_OK; } NS_IMETHODIMP HttpChannelParent::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatusCode) { 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; } //----------------------------------------------------------------------------- // HttpChannelParent::nsIStreamListener //----------------------------------------------------------------------------- @@ -528,16 +623,19 @@ NS_IMETHODIMP HttpChannelParent::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext, nsIInputStream *aInputStream, uint64_t aOffset, uint32_t aCount) { 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)) return rv; // OnDataAvailable is always preceded by OnStatus/OnProgress calls that set // mStoredStatus/mStoredProgress(Max) to appropriate values, unless // LOAD_BACKGROUND set. In that case, they'll have garbage values, but @@ -624,18 +722,17 @@ HttpChannelParent::StartRedirect(uint32_ return NS_BINDING_ABORTED; nsCOMPtr<nsIURI> newURI; newChannel->GetURI(getter_AddRefs(newURI)); 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()); if (!result) { // Bug 621446 investigation mSentRedirect1BeginFailed = true; return NS_BINDING_ABORTED; } @@ -657,9 +754,201 @@ HttpChannelParent::CompleteRedirect(bool // TODO: check return value: assume child dead if failed unused << SendRedirect3Complete(); } mRedirectChannel = nullptr; 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
--- a/netwerk/protocol/http/HttpChannelParent.h +++ b/netwerk/protocol/http/HttpChannelParent.h @@ -3,16 +3,17 @@ /* 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 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" #include "mozilla/net/NeckoCommon.h" #include "mozilla/net/NeckoParent.h" #include "nsIParentRedirectingChannel.h" #include "nsIProgressEventSink.h" #include "nsHttpChannel.h" @@ -29,16 +30,17 @@ class TabParent; namespace net { class HttpChannelParentListener; class HttpChannelParent : public PHttpChannelParent , public nsIParentRedirectingChannel , public nsIProgressEventSink , public nsIInterfaceRequestor + , public ADivertableParentChannel { public: NS_DECL_ISUPPORTS NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER NS_DECL_NSIPARENTCHANNEL NS_DECL_NSIPARENTREDIRECTINGCHANNEL NS_DECL_NSIPROGRESSEVENTSINK @@ -46,16 +48,29 @@ public: HttpChannelParent(mozilla::dom::PBrowserParent* iframeEmbedding, nsILoadContext* aLoadContext, PBOverrideStatus aStatus); virtual ~HttpChannelParent(); 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. bool ConnectChannel(const uint32_t& channelId); bool DoAsyncOpen(const URIParams& uri, const OptionalURIParams& originalUri, const OptionalURIParams& docUri, @@ -84,25 +99,34 @@ protected: virtual bool RecvCancel(const nsresult& status) MOZ_OVERRIDE; virtual bool RecvRedirect2Verify(const nsresult& result, const RequestHeaderTuples& changedHeaders, const OptionalURIParams& apiRedirectUri) MOZ_OVERRIDE; virtual bool RecvUpdateAssociatedContentSecurity(const int32_t& broken, 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() nsCOMPtr<nsIChannel> mRedirectChannel; nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback; nsAutoPtr<class nsHttpChannel::OfflineCacheEntryAsForeignMarker> mOfflineForeignMarker; @@ -116,14 +140,27 @@ private: bool mSentRedirect1Begin : 1; bool mSentRedirect1BeginFailed : 1; bool mReceivedRedirect2Verify : 1; PBOverrideStatus mPBOverride; 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 } // namespace mozilla #endif // mozilla_net_HttpChannelParent_h
--- a/netwerk/protocol/http/HttpChannelParentListener.cpp +++ b/netwerk/protocol/http/HttpChannelParentListener.cpp @@ -14,18 +14,19 @@ #include "nsIHttpEventSink.h" using mozilla::unused; namespace mozilla { namespace net { HttpChannelParentListener::HttpChannelParentListener(HttpChannelParent* aInitialChannel) - : mActiveChannel(aInitialChannel) + : mNextListener(aInitialChannel) , mRedirectChannelId(0) + , mSuspendedForDiversion(false) { } HttpChannelParentListener::~HttpChannelParentListener() { } //----------------------------------------------------------------------------- @@ -41,55 +42,64 @@ NS_IMPL_ISUPPORTS5(HttpChannelParentList //----------------------------------------------------------------------------- // HttpChannelParentListener::nsIRequestObserver //----------------------------------------------------------------------------- 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 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; } //----------------------------------------------------------------------------- // HttpChannelParentListener::nsIStreamListener //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelParentListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext, nsIInputStream *aInputStream, 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); } //----------------------------------------------------------------------------- // HttpChannelParentListener::nsIInterfaceRequestor //----------------------------------------------------------------------------- NS_IMETHODIMP HttpChannelParentListener::GetInterface(const nsIID& aIID, void **result) @@ -97,18 +107,18 @@ HttpChannelParentListener::GetInterface( if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)) || aIID.Equals(NS_GET_IID(nsIHttpEventSink)) || aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) { return QueryInterface(aIID, 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); } return NS_NOINTERFACE; } @@ -131,17 +141,17 @@ HttpChannelParentListener::AsyncOnChanne NS_ENSURE_SUCCESS(rv, rv); rv = registrar->RegisterChannel(newChannel, &mRedirectChannelId); NS_ENSURE_SUCCESS(rv, rv); 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."); } return activeRedirectingChannel->StartRedirect(mRedirectChannelId, newChannel, redirectFlags, @@ -185,32 +195,75 @@ HttpChannelParentListener::OnRedirectRes mRedirectChannelId = 0; } if (!redirectChannel) { succeeded = false; } 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."); if (activeRedirectingChannel) { activeRedirectingChannel->CompleteRedirect(succeeded); } else { succeeded = false; } 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(); } 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
--- a/netwerk/protocol/http/HttpChannelParentListener.h +++ b/netwerk/protocol/http/HttpChannelParentListener.h @@ -30,17 +30,30 @@ public: NS_DECL_NSICHANNELEVENTSINK NS_DECL_NSIREDIRECTRESULTLISTENER NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER 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 } // namespace mozilla #endif // mozilla_net_HttpChannelParent_h
--- a/netwerk/protocol/http/PHttpChannel.ipdl +++ b/netwerk/protocol/http/PHttpChannel.ipdl @@ -61,16 +61,27 @@ parent: // load this document from the offline cache group it was just loaded from. // Marking the cache entry as foreign in its cache group will prevent // the document to load from the bad offline cache group. After it is marked, // we reload the document to take the effect. If we fail to mark the entry // as foreign, we will end up in the same situation and reload again and // 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: OnStartRequest(nsHttpResponseHead responseHead, bool useResponseHead, nsHttpHeaderArray requestHeaders, bool isFromCache, bool cacheEntryAvailable, @@ -108,16 +119,23 @@ child: // Called if redirect successful so that child can complete setup. Redirect3Complete(); // Associte the child with an application ids 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(); }; } // namespace net } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -215,16 +215,17 @@ nsHttpChannel::nsHttpChannel() , mCacheEntryIsReadOnly(false) , mCacheEntryIsWriteOnly(false) , mCacheEntriesToWaitFor(0) , mHasQueryString(0) , mConcurentCacheAccess(0) , mIsPartialRequest(0) , mHasAutoRedirectVetoNotifier(0) , mDidReval(false) + , mForcePending(false) { LOG(("Creating nsHttpChannel [this=%p]\n", this)); mChannelCreationTime = PR_Now(); mChannelCreationTimestamp = TimeStamp::Now(); } nsHttpChannel::~nsHttpChannel() { @@ -6195,9 +6196,27 @@ nsHttpChannel::SetNotificationCallbacks( nsresult rv = HttpBaseChannel::SetNotificationCallbacks(aCallbacks); if (NS_SUCCEEDED(rv)) { UpdateAggregateCallbacks(); } 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
--- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -100,16 +100,17 @@ public: nsIURI *aProxyURI); // Methods HttpBaseChannel didn't implement for us or that we override. // // nsIRequest 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); // nsIHttpChannelInternal NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey); // nsISupportsPriority NS_IMETHOD SetPriority(int32_t value); // nsIResumableChannel @@ -177,16 +178,18 @@ public: /* internal necko use only */ mChannel->mCacheEntriesToWaitFor &= mKeep; } private: nsHttpChannel* mChannel; uint32_t mKeep : 2; }; + void ForcePending(bool aForcePending); + private: typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result); bool RequestIsConditional(); nsresult BeginConnect(); nsresult Connect(); nsresult ContinueConnect(); void SpeculativeConnect(); @@ -419,13 +422,14 @@ protected: virtual void DoNotifyListenerCleanup(); private: // cache telemetry bool mDidReval; private: nsIPrincipal *GetPrincipal(); nsCOMPtr<nsIPrincipal> mPrincipal; + bool mForcePending; }; } } // namespace mozilla::net #endif // nsHttpChannel_h__