From 1ad4e039d47b51c4201514e453aafbfe70ecc3dc Mon Sep 17 00:00:00 2001 From: Perry Jiang Date: Thu, 30 Apr 2020 23:52:54 +0000 Subject: [PATCH] Bug 1622451 - minimize stream copying across IPC boundaries r=asuth,baku Initially, IPCInternal{Request,Response} had contained IPCStreams which would result in unecessary copying when sending the objects over IPC. The patch makes these streams either: 1) ParentToParentStream (just a UUID) 2) ParentToChildStream (a PIPCBlobInputStream actor, acting as a handle) 3) ChildToParentStream (a real IPCStream) These three types are union-ed together by the BodyStreamVariant IPDL structure. This structure replaces the IPCStream members in IPCInternal{Request,Response} so that, depending on the particular IPDL protocol, we can avoid cloning streams and just pass handles/IDs instead. As a side effect, this makes file-backed Response objects cloneable. Initially, these Responses would be backed by an nsFileInputStream, which is not cloneable outside the parent process. They are now backed by IPCBlobInputStreams, which are cloneable. One thing that's not really satisfactory (IMO), is the manual management of IPCBlobInputStreamStorage so that no streams are leaked, e.g. if we store a stream in the IPCBlobInputStreamStorage but fail to send an IPC message and therefore fail to remove the stream from storage on the other side of the IPC boundary (only parent-to-parent in this case). Differential Revision: https://phabricator.services.mozilla.com/D73173 --- dom/fetch/FetchTypes.ipdlh | 28 ++- dom/fetch/InternalRequest.cpp | 60 ++---- dom/fetch/InternalRequest.h | 5 - dom/fetch/InternalResponse.cpp | 86 +++++--- dom/fetch/InternalResponse.h | 14 +- dom/file/ipc/IPCBlobInputStreamStorage.cpp | 4 - dom/file/ipc/IPCBlobInputStreamStorage.h | 4 +- dom/file/ipc/IPCBlobUtils.cpp | 8 + dom/file/ipc/IPCBlobUtils.h | 4 + dom/serviceworkers/FetchEventOpChild.cpp | 187 ---------------- dom/serviceworkers/FetchEventOpProxyChild.cpp | 12 +- .../FetchEventOpProxyParent.cpp | 79 ++++--- .../ServiceWorkerPrivateImpl.cpp | 204 ++++++++++++++++-- dom/serviceworkers/test/sw_file_upload.js | 9 +- dom/serviceworkers/test/test_file_upload.html | 19 +- 15 files changed, 391 insertions(+), 332 deletions(-) diff --git a/dom/fetch/FetchTypes.ipdlh b/dom/fetch/FetchTypes.ipdlh index 8b127bee504b..2ea46b1e1dd5 100644 --- a/dom/fetch/FetchTypes.ipdlh +++ b/dom/fetch/FetchTypes.ipdlh @@ -6,6 +6,8 @@ include IPCStream; include ChannelInfo; include PBackgroundSharedTypes; +include protocol PIPCBlobInputStream; + using HeadersGuardEnum from "mozilla/dom/FetchIPCTypes.h"; using ReferrerPolicy from "mozilla/dom/FetchIPCTypes.h"; using RequestCache from "mozilla/dom/FetchIPCTypes.h"; @@ -13,6 +15,7 @@ using RequestCredentials from "mozilla/dom/FetchIPCTypes.h"; using RequestMode from "mozilla/dom/FetchIPCTypes.h"; using RequestRedirect from "mozilla/dom/FetchIPCTypes.h"; using ResponseType from "mozilla/dom/FetchIPCTypes.h"; +using struct nsID from "nsID.h"; namespace mozilla { namespace dom { @@ -22,12 +25,31 @@ struct HeadersEntry { nsCString value; }; +struct ParentToParentStream { + // Used as a key for IPCBlobInputStreamStorage + nsID uuid; +}; + +struct ParentToChildStream { + PIPCBlobInputStream actor; +}; + +struct ChildToParentStream { + IPCStream stream; +}; + +union BodyStreamVariant { + ParentToParentStream; + ParentToChildStream; + ChildToParentStream; +}; + struct IPCInternalRequest { nsCString method; nsCString[] urlList; HeadersGuardEnum headersGuard; HeadersEntry[] headers; - IPCStream? body; + BodyStreamVariant? body; int64_t bodySize; nsCString preferredAlternativeDataType; uint32_t contentPolicyType; @@ -49,11 +71,11 @@ struct IPCInternalResponse { nsCString statusText; HeadersGuardEnum headersGuard; HeadersEntry[] headers; - IPCStream? body; + BodyStreamVariant? body; int64_t bodySize; nsresult errorCode; nsCString alternativeDataType; - IPCStream? alternativeBody; + BodyStreamVariant? alternativeBody; IPCChannelInfo channelInfo; PrincipalInfo? principalInfo; }; diff --git a/dom/fetch/InternalRequest.cpp b/dom/fetch/InternalRequest.cpp index 8d185742ab9b..67065506029b 100644 --- a/dom/fetch/InternalRequest.cpp +++ b/dom/fetch/InternalRequest.cpp @@ -6,18 +6,17 @@ #include "InternalRequest.h" -#include "nsIContentPolicy.h" -#include "mozilla/dom/Document.h" -#include "nsStreamUtils.h" - #include "mozilla/ErrorResult.h" +#include "mozilla/dom/Document.h" #include "mozilla/dom/FetchTypes.h" +#include "mozilla/dom/IPCBlobInputStreamChild.h" #include "mozilla/dom/ScriptSettings.h" - #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/ipc/IPCStreamUtils.h" #include "mozilla/ipc/PBackgroundChild.h" +#include "nsIContentPolicy.h" +#include "nsStreamUtils.h" namespace mozilla { namespace dom { @@ -146,7 +145,6 @@ InternalRequest::InternalRequest(const IPCInternalRequest& aIPCRequest) mURLList(aIPCRequest.urlList()), mHeaders(new InternalHeaders(aIPCRequest.headers(), aIPCRequest.headersGuard())), - mBodyStream(mozilla::ipc::DeserializeIPCStream(aIPCRequest.body())), mBodyLength(aIPCRequest.bodySize()), mPreferredAlternativeDataType(aIPCRequest.preferredAlternativeDataType()), mContentPolicyType( @@ -163,49 +161,21 @@ InternalRequest::InternalRequest(const IPCInternalRequest& aIPCRequest) mPrincipalInfo = MakeUnique( aIPCRequest.principalInfo().ref()); } + + const Maybe& body = aIPCRequest.body(); + + // This constructor is (currently) only used for parent -> child communication + // (constructed on the child side). + if (body) { + MOZ_ASSERT(body->type() == BodyStreamVariant::TParentToChildStream); + mBodyStream = static_cast( + body->get_ParentToChildStream().actorChild()) + ->CreateStream(); + } } InternalRequest::~InternalRequest() = default; -template void InternalRequest::ToIPC( - IPCInternalRequest* aIPCRequest, mozilla::ipc::PBackgroundChild* aManager, - UniquePtr& aAutoStream); - -template -void InternalRequest::ToIPC( - IPCInternalRequest* aIPCRequest, M* aManager, - UniquePtr& aAutoStream) { - MOZ_ASSERT(aIPCRequest); - MOZ_ASSERT(aManager); - MOZ_ASSERT(!mURLList.IsEmpty()); - - aIPCRequest->method() = mMethod; - aIPCRequest->urlList() = mURLList; - mHeaders->ToIPC(aIPCRequest->headers(), aIPCRequest->headersGuard()); - - if (mBodyStream) { - aAutoStream.reset(new mozilla::ipc::AutoIPCStream(aIPCRequest->body())); - DebugOnly ok = aAutoStream->Serialize(mBodyStream, aManager); - MOZ_ASSERT(ok); - } - - aIPCRequest->bodySize() = mBodyLength; - aIPCRequest->preferredAlternativeDataType() = mPreferredAlternativeDataType; - aIPCRequest->contentPolicyType() = static_cast(mContentPolicyType); - aIPCRequest->referrer() = mReferrer; - aIPCRequest->referrerPolicy() = mReferrerPolicy; - aIPCRequest->requestMode() = mMode; - aIPCRequest->requestCredentials() = mCredentialsMode; - aIPCRequest->cacheMode() = mCacheMode; - aIPCRequest->requestRedirect() = mRedirectMode; - aIPCRequest->integrity() = mIntegrity; - aIPCRequest->fragment() = mFragment; - - if (mPrincipalInfo) { - aIPCRequest->principalInfo().emplace(*mPrincipalInfo); - } -} - void InternalRequest::SetContentPolicyType( nsContentPolicyType aContentPolicyType) { mContentPolicyType = aContentPolicyType; diff --git a/dom/fetch/InternalRequest.h b/dom/fetch/InternalRequest.h index eba3c1d1bcee..5af7b55769b8 100644 --- a/dom/fetch/InternalRequest.h +++ b/dom/fetch/InternalRequest.h @@ -25,7 +25,6 @@ namespace mozilla { namespace ipc { class PrincipalInfo; -class AutoIPCStream; } // namespace ipc namespace dom { @@ -90,10 +89,6 @@ class InternalRequest final { explicit InternalRequest(const IPCInternalRequest& aIPCRequest); - template - void ToIPC(IPCInternalRequest* aIPCRequest, M* aManager, - UniquePtr& aAutoStream); - already_AddRefed Clone(); void GetMethod(nsCString& aMethod) const { aMethod.Assign(mMethod); } diff --git a/dom/fetch/InternalResponse.cpp b/dom/fetch/InternalResponse.cpp index e9e8f6edf992..98b479adaab3 100644 --- a/dom/fetch/InternalResponse.cpp +++ b/dom/fetch/InternalResponse.cpp @@ -9,6 +9,7 @@ #include "mozilla/Assertions.h" #include "mozilla/RefPtr.h" #include "mozilla/dom/FetchTypes.h" +#include "mozilla/dom/IPCBlobInputStreamStorage.h" #include "mozilla/dom/InternalHeaders.h" #include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" @@ -26,6 +27,22 @@ namespace { // XXX This will be tweaked to something more meaningful in Bug 1383656. const uint32_t kMaxRandomNumber = 102400; +nsCOMPtr TakeStreamFromStorage( + const BodyStreamVariant& aVariant, int64_t aBodySize) { + MOZ_ASSERT(aVariant.type() == BodyStreamVariant::TParentToParentStream); + const auto& uuid = aVariant.get_ParentToParentStream().uuid(); + + IPCBlobInputStreamStorage* storage = IPCBlobInputStreamStorage::Get(); + + nsCOMPtr body; + storage->GetStream(uuid, 0, aBodySize, getter_AddRefs(body)); + MOZ_DIAGNOSTIC_ASSERT(body); + + storage->ForgetStream(uuid); + + return body; +} + } // namespace InternalResponse::InternalResponse(uint16_t aStatus, @@ -53,15 +70,20 @@ InternalResponse::InternalResponse(uint16_t aStatus, response->mHeaders = new InternalHeaders(aIPCResponse.headers(), aIPCResponse.headersGuard()); - nsCOMPtr body = - mozilla::ipc::DeserializeIPCStream(aIPCResponse.body()); - response->SetBody(body, aIPCResponse.bodySize()); + if (aIPCResponse.body()) { + auto bodySize = aIPCResponse.bodySize(); + nsCOMPtr body = + TakeStreamFromStorage(*aIPCResponse.body(), bodySize); + response->SetBody(body, bodySize); + } response->SetAlternativeDataType(aIPCResponse.alternativeDataType()); - nsCOMPtr alternativeBody = - mozilla::ipc::DeserializeIPCStream(aIPCResponse.alternativeBody()); - response->SetAlternativeBody(alternativeBody); + if (aIPCResponse.alternativeBody()) { + nsCOMPtr alternativeBody = TakeStreamFromStorage( + *aIPCResponse.alternativeBody(), UNKNOWN_BODY_SIZE); + response->SetAlternativeBody(alternativeBody); + } response->InitChannelInfo(aIPCResponse.channelInfo()); @@ -96,54 +118,50 @@ InternalResponse::InternalResponse(uint16_t aStatus, InternalResponse::~InternalResponse() = default; -template void InternalResponse::ToIPC( +void InternalResponse::ToIPC( IPCInternalResponse* aIPCResponse, mozilla::ipc::PBackgroundChild* aManager, UniquePtr& aAutoBodyStream, - UniquePtr& aAutoAlternativeBodyStream); - -template -void InternalResponse::ToIPC( - IPCInternalResponse* aIPCResponse, M* aManager, - UniquePtr& aAutoBodyStream, UniquePtr& aAutoAlternativeBodyStream) { - MOZ_ASSERT(aIPCResponse); + nsTArray headers; + HeadersGuardEnum headersGuard; + UnfilteredHeaders()->ToIPC(headers, headersGuard); - aIPCResponse->type() = mType; - GetUnfilteredURLList(aIPCResponse->urlList()); - aIPCResponse->status() = GetUnfilteredStatus(); - aIPCResponse->statusText() = GetUnfilteredStatusText(); - UnfilteredHeaders()->ToIPC(aIPCResponse->headers(), - aIPCResponse->headersGuard()); + Maybe principalInfo = + mPrincipalInfo ? Some(*mPrincipalInfo) : Nothing(); + + // Note: all the arguments are copied rather than moved, which would be more + // efficient, because there's no move-friendly constructor generated. + *aIPCResponse = + IPCInternalResponse(mType, GetUnfilteredURLList(), GetUnfilteredStatus(), + GetUnfilteredStatusText(), headersGuard, headers, + Nothing(), static_cast(UNKNOWN_BODY_SIZE), + mErrorCode, GetAlternativeDataType(), Nothing(), + mChannelInfo.AsIPCChannelInfo(), principalInfo); nsCOMPtr body; int64_t bodySize; GetUnfilteredBody(getter_AddRefs(body), &bodySize); if (body) { - aAutoBodyStream.reset( - new mozilla::ipc::AutoIPCStream(aIPCResponse->body())); + aIPCResponse->body().emplace(ChildToParentStream()); + aIPCResponse->bodySize() = bodySize; + + aAutoBodyStream.reset(new mozilla::ipc::AutoIPCStream( + aIPCResponse->body()->get_ChildToParentStream().stream())); DebugOnly ok = aAutoBodyStream->Serialize(body, aManager); MOZ_ASSERT(ok); } - aIPCResponse->bodySize() = bodySize; - aIPCResponse->errorCode() = mErrorCode; - aIPCResponse->alternativeDataType() = GetAlternativeDataType(); - nsCOMPtr alternativeBody = TakeAlternativeBody(); if (alternativeBody) { - aAutoAlternativeBodyStream.reset( - new mozilla::ipc::AutoIPCStream(aIPCResponse->alternativeBody())); + aIPCResponse->alternativeBody().emplace(ChildToParentStream()); + + aAutoAlternativeBodyStream.reset(new mozilla::ipc::AutoIPCStream( + aIPCResponse->alternativeBody()->get_ChildToParentStream().stream())); DebugOnly ok = aAutoAlternativeBodyStream->Serialize(alternativeBody, aManager); MOZ_ASSERT(ok); } - - aIPCResponse->channelInfo() = mChannelInfo.AsIPCChannelInfo(); - - if (mPrincipalInfo) { - aIPCResponse->principalInfo().emplace(*mPrincipalInfo); - } } already_AddRefed InternalResponse::Clone( diff --git a/dom/fetch/InternalResponse.h b/dom/fetch/InternalResponse.h index 2e0338eeb392..6ba71e3e3151 100644 --- a/dom/fetch/InternalResponse.h +++ b/dom/fetch/InternalResponse.h @@ -20,8 +20,9 @@ namespace mozilla { namespace ipc { -class PrincipalInfo; class AutoIPCStream; +class PBackgroundChild; +class PrincipalInfo; } // namespace ipc namespace dom { @@ -42,9 +43,10 @@ class InternalResponse final { static RefPtr FromIPC( const IPCInternalResponse& aIPCResponse); - template + // Note: the AutoIPCStreams must outlive the IPCInternalResponse. void ToIPC( - IPCInternalResponse* aIPCResponse, M* aManager, + IPCInternalResponse* aIPCResponse, + mozilla::ipc::PBackgroundChild* aManager, UniquePtr& aAutoBodyStream, UniquePtr& aAutoAlternativeBodyStream); @@ -111,6 +113,12 @@ class InternalResponse final { return GetURLList(aURLList); } + nsTArray GetUnfilteredURLList() const { + nsTArray list; + GetUnfilteredURLList(list); + return list; + } + void SetURLList(const nsTArray& aURLList) { mURLList.Assign(aURLList); diff --git a/dom/file/ipc/IPCBlobInputStreamStorage.cpp b/dom/file/ipc/IPCBlobInputStreamStorage.cpp index 5ff9d66104c8..6d6c658e5259 100644 --- a/dom/file/ipc/IPCBlobInputStreamStorage.cpp +++ b/dom/file/ipc/IPCBlobInputStreamStorage.cpp @@ -31,10 +31,6 @@ NS_INTERFACE_MAP_END NS_IMPL_ADDREF(IPCBlobInputStreamStorage) NS_IMPL_RELEASE(IPCBlobInputStreamStorage) -IPCBlobInputStreamStorage::IPCBlobInputStreamStorage() = default; - -IPCBlobInputStreamStorage::~IPCBlobInputStreamStorage() = default; - /* static */ IPCBlobInputStreamStorage* IPCBlobInputStreamStorage::Get() { return gStorage; } diff --git a/dom/file/ipc/IPCBlobInputStreamStorage.h b/dom/file/ipc/IPCBlobInputStreamStorage.h index a8dba21a48c4..8e6f2c0a3037 100644 --- a/dom/file/ipc/IPCBlobInputStreamStorage.h +++ b/dom/file/ipc/IPCBlobInputStreamStorage.h @@ -46,8 +46,8 @@ class IPCBlobInputStreamStorage final : public nsIObserver { const nsID& aID); private: - IPCBlobInputStreamStorage(); - ~IPCBlobInputStreamStorage(); + IPCBlobInputStreamStorage() = default; + ~IPCBlobInputStreamStorage() = default; struct StreamData { nsCOMPtr mInputStream; diff --git a/dom/file/ipc/IPCBlobUtils.cpp b/dom/file/ipc/IPCBlobUtils.cpp index ae097878df73..64860f9b2221 100644 --- a/dom/file/ipc/IPCBlobUtils.cpp +++ b/dom/file/ipc/IPCBlobUtils.cpp @@ -135,6 +135,14 @@ nsresult SerializeInputStream(nsIInputStream* aInputStream, uint64_t aSize, aActorParent, aManager); } +nsresult SerializeInputStream(nsIInputStream* aInputStream, uint64_t aSize, + PIPCBlobInputStreamParent*& aActorParent, + PBackgroundParent* aManager) { + return SerializeInputStreamParent(aInputStream, aSize, + BackgroundParent::GetChildID(aManager), + aActorParent, aManager); +} + nsresult SerializeInputStream(nsIInputStream* aInputStream, uint64_t aSize, IPCBlobStream& aIPCBlob, ContentParent* aManager) { diff --git a/dom/file/ipc/IPCBlobUtils.h b/dom/file/ipc/IPCBlobUtils.h index bc73c9e2e77a..3e7de09399ba 100644 --- a/dom/file/ipc/IPCBlobUtils.h +++ b/dom/file/ipc/IPCBlobUtils.h @@ -255,6 +255,10 @@ nsresult SerializeInputStream(nsIInputStream* aInputStream, uint64_t aSize, PIPCBlobInputStreamParent*& aActorParent, ContentParent* aManager); +nsresult SerializeInputStream(nsIInputStream* aInputStream, uint64_t aSize, + PIPCBlobInputStreamParent*& aActorParent, + mozilla::ipc::PBackgroundParent* aManager); + // WARNING: If you pass any actor which does not have P{Content,Background} as // its toplevel protocol, this method will MOZ_CRASH. nsresult SerializeUntyped(BlobImpl* aBlobImpl, mozilla::ipc::IProtocol* aActor, diff --git a/dom/serviceworkers/FetchEventOpChild.cpp b/dom/serviceworkers/FetchEventOpChild.cpp index f09945b8d486..f81da0b18f97 100644 --- a/dom/serviceworkers/FetchEventOpChild.cpp +++ b/dom/serviceworkers/FetchEventOpChild.cpp @@ -16,8 +16,6 @@ #include "nsIChannel.h" #include "nsIConsoleReportCollector.h" #include "nsIContentPolicy.h" -#include "nsIHttpChannelInternal.h" -#include "nsIHttpHeaderVisitor.h" #include "nsIInputStream.h" #include "nsILoadInfo.h" #include "nsINetworkInterceptController.h" @@ -25,7 +23,6 @@ #include "nsIScriptError.h" #include "nsISupportsImpl.h" #include "nsIURI.h" -#include "nsIUploadChannel2.h" #include "nsNetUtil.h" #include "nsProxyRelease.h" #include "nsTArray.h" @@ -40,7 +37,6 @@ #include "mozilla/Unused.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/dom/InternalHeaders.h" -#include "mozilla/dom/InternalRequest.h" #include "mozilla/dom/InternalResponse.h" #include "mozilla/dom/PRemoteWorkerControllerChild.h" #include "mozilla/dom/ServiceWorkerRegistrationInfo.h" @@ -185,168 +181,6 @@ class SynthesizeResponseWatcher final : public nsIInterceptedBodyCallback { NS_IMPL_ISUPPORTS(SynthesizeResponseWatcher, nsIInterceptedBodyCallback) -class HeaderFiller final : public nsIHttpHeaderVisitor { - public: - NS_DECL_ISUPPORTS - - explicit HeaderFiller(HeadersGuardEnum aGuard) - : mInternalHeaders(new InternalHeaders(aGuard)) { - MOZ_ASSERT(mInternalHeaders); - } - - NS_IMETHOD - VisitHeader(const nsACString& aHeader, const nsACString& aValue) override { - ErrorResult result; - mInternalHeaders->Append(aHeader, aValue, result); - - if (NS_WARN_IF(result.Failed())) { - return result.StealNSResult(); - } - - return NS_OK; - } - - RefPtr Extract() { - return RefPtr(std::move(mInternalHeaders)); - } - - private: - ~HeaderFiller() = default; - - RefPtr mInternalHeaders; -}; - -NS_IMPL_ISUPPORTS(HeaderFiller, nsIHttpHeaderVisitor) - -nsresult GetIPCInternalRequest(nsIInterceptedChannel* aChannel, - IPCInternalRequest* aOutRequest, - UniquePtr& aAutoStream) { - AssertIsOnMainThread(); - - nsCOMPtr uri; - nsresult rv = aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr uriNoFragment; - rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriNoFragment)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr underlyingChannel; - rv = aChannel->GetChannel(getter_AddRefs(underlyingChannel)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr httpChannel = do_QueryInterface(underlyingChannel); - MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?"); - - nsCOMPtr internalChannel = - do_QueryInterface(httpChannel); - NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE); - - nsCOMPtr cacheInfoChannel = - do_QueryInterface(underlyingChannel); - - nsAutoCString spec; - rv = uriNoFragment->GetSpec(spec); - NS_ENSURE_SUCCESS(rv, rv); - - nsAutoCString fragment; - rv = uri->GetRef(fragment); - NS_ENSURE_SUCCESS(rv, rv); - - nsAutoCString method; - rv = httpChannel->GetRequestMethod(method); - NS_ENSURE_SUCCESS(rv, rv); - - // This is safe due to static_asserts in ServiceWorkerManager.cpp - uint32_t cacheModeInt; - rv = internalChannel->GetFetchCacheMode(&cacheModeInt); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - RequestCache cacheMode = static_cast(cacheModeInt); - - RequestMode requestMode = - InternalRequest::MapChannelToRequestMode(underlyingChannel); - - // This is safe due to static_asserts in ServiceWorkerManager.cpp - uint32_t redirectMode; - rv = internalChannel->GetRedirectMode(&redirectMode); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - RequestRedirect requestRedirect = static_cast(redirectMode); - - RequestCredentials requestCredentials = - InternalRequest::MapChannelToRequestCredentials(underlyingChannel); - - nsAutoString referrer; - ReferrerPolicy referrerPolicy = ReferrerPolicy::_empty; - - nsCOMPtr referrerInfo = httpChannel->GetReferrerInfo(); - if (referrerInfo) { - referrerPolicy = referrerInfo->ReferrerPolicy(); - Unused << referrerInfo->GetComputedReferrerSpec(referrer); - } - - uint32_t loadFlags; - rv = underlyingChannel->GetLoadFlags(&loadFlags); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr loadInfo; - rv = underlyingChannel->GetLoadInfo(getter_AddRefs(loadInfo)); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_STATE(loadInfo); - - nsContentPolicyType contentPolicyType = loadInfo->InternalContentPolicyType(); - - nsAutoString integrity; - rv = internalChannel->GetIntegrityMetadata(integrity); - NS_ENSURE_SUCCESS(rv, rv); - - RefPtr headerFiller = - MakeRefPtr(HeadersGuardEnum::Request); - rv = httpChannel->VisitNonDefaultRequestHeaders(headerFiller); - NS_ENSURE_SUCCESS(rv, rv); - - RefPtr internalHeaders = headerFiller->Extract(); - - ErrorResult result; - internalHeaders->SetGuard(HeadersGuardEnum::Immutable, result); - if (NS_WARN_IF(result.Failed())) { - return result.StealNSResult(); - } - - nsCOMPtr uploadChannel = do_QueryInterface(httpChannel); - nsCOMPtr uploadStream; - int64_t uploadStreamContentLength = -1; - if (uploadChannel) { - rv = uploadChannel->CloneUploadStream(&uploadStreamContentLength, - getter_AddRefs(uploadStream)); - NS_ENSURE_SUCCESS(rv, rv); - } - - RefPtr internalRequest = new InternalRequest( - spec, fragment, method, internalHeaders.forget(), cacheMode, requestMode, - requestRedirect, requestCredentials, referrer, referrerPolicy, - contentPolicyType, integrity); - internalRequest->SetBody(uploadStream, uploadStreamContentLength); - - nsAutoCString alternativeDataType; - if (cacheInfoChannel && - !cacheInfoChannel->PreferredAlternativeDataTypes().IsEmpty()) { - // TODO: the internal request probably needs all the preferred types. - alternativeDataType.Assign( - cacheInfoChannel->PreferredAlternativeDataTypes()[0].type()); - internalRequest->SetPreferredAlternativeDataType(alternativeDataType); - } - - PBackgroundChild* bgChild = BackgroundChild::GetForCurrentThread(); - - if (NS_WARN_IF(!bgChild)) { - return NS_ERROR_DOM_INVALID_STATE_ERR; - } - - internalRequest->ToIPC(aOutRequest, bgChild, aAutoStream); - - return NS_OK; -} - } // anonymous namespace /* static */ RefPtr FetchEventOpChild::SendFetchEvent( @@ -364,28 +198,7 @@ nsresult GetIPCInternalRequest(nsIInterceptedChannel* aChannel, std::move(aArgs), std::move(aInterceptedChannel), std::move(aRegistration), std::move(aKeepAliveToken)); - // autoStream will contain a pointer into the IPCInternalRequest passed into - // GetIPCInternalRequest, so autoStream shouldn't outlive that - // IPCInternalRequest or the containing FetchEventOpChild. - auto autoStream = MakeUnique(); - - // const_cast-ing the IPCInternalRequest is okay because this is conceptually - // part of initializing actor->mArgs, and `autoStream` needs its - // IPCStream (physically part of actor->mArgs.internalRequest()) to be in its - // final location in memory, so actor->mArgs must be created before this call. - nsresult rv = GetIPCInternalRequest( - actor->mInterceptedChannel, - const_cast(&actor->mArgs.internalRequest()), - autoStream); - if (NS_WARN_IF(NS_FAILED(rv))) { - // `actor` must be manually delete-d before the actor tree can manage its - // lifetime starting with SendPFetchEventOpConstructor. - delete actor; - return GenericPromise::CreateAndReject(rv, __func__); - } - Unused << aManager->SendPFetchEventOpConstructor(actor, actor->mArgs); - autoStream->TakeOptionalValue(); return actor->mPromiseHolder.Ensure(__func__); } diff --git a/dom/serviceworkers/FetchEventOpProxyChild.cpp b/dom/serviceworkers/FetchEventOpProxyChild.cpp index 3cd0fcb3dcf1..944776a039ef 100644 --- a/dom/serviceworkers/FetchEventOpProxyChild.cpp +++ b/dom/serviceworkers/FetchEventOpProxyChild.cpp @@ -120,8 +120,14 @@ void FetchEventOpProxyChild::Initialize( } Unused << self->SendRespondWith(ipcArgs); - autoBodyStream->TakeOptionalValue(); - autoAlternativeBodyStream->TakeOptionalValue(); + + if (ipcArgs.internalResponse().body()) { + autoBodyStream->TakeValue(); + } + + if (ipcArgs.internalResponse().alternativeBody()) { + autoAlternativeBodyStream->TakeValue(); + } } else if (result.is()) { Unused << self->SendRespondWith( result.extract()); @@ -139,7 +145,7 @@ RefPtr FetchEventOpProxyChild::ExtractInternalRequest() { MOZ_ASSERT(IsCurrentThreadRunningWorker()); MOZ_ASSERT(mInternalRequest); - return RefPtr(std::move(mInternalRequest)); + return std::move(mInternalRequest); } void FetchEventOpProxyChild::ActorDestroy(ActorDestroyReason) { diff --git a/dom/serviceworkers/FetchEventOpProxyParent.cpp b/dom/serviceworkers/FetchEventOpProxyParent.cpp index a00201873929..257891307e90 100644 --- a/dom/serviceworkers/FetchEventOpProxyParent.cpp +++ b/dom/serviceworkers/FetchEventOpProxyParent.cpp @@ -9,13 +9,18 @@ #include #include "nsCOMPtr.h" +#include "nsContentUtils.h" #include "nsIInputStream.h" #include "mozilla/Assertions.h" #include "mozilla/DebugOnly.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" #include "mozilla/UniquePtr.h" #include "mozilla/Unused.h" #include "mozilla/dom/FetchEventOpParent.h" +#include "mozilla/dom/IPCBlobInputStreamStorage.h" +#include "mozilla/dom/IPCBlobUtils.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/IPCStreamUtils.h" @@ -27,20 +32,25 @@ namespace dom { namespace { -void MaybeDeserializeAndReserialize(const Maybe& aDeserialize, - Maybe& aReserialize, - UniquePtr& aAutoStream, - PBackgroundParent* aManager) { - nsCOMPtr maybeDeserialized = - DeserializeIPCStream(aDeserialize); - - if (!maybeDeserialized) { - return; +nsresult MaybeDeserializeAndWrapForMainThread( + const Maybe& aSource, int64_t aBodyStreamSize, + Maybe& aSink, PBackgroundParent* aManager) { + if (aSource.isNothing()) { + return NS_OK; } - aAutoStream.reset(new AutoIPCStream(aReserialize)); - DebugOnly ok = aAutoStream->Serialize(maybeDeserialized, aManager); - MOZ_ASSERT(ok); + nsCOMPtr deserialized = + DeserializeIPCStream(aSource->get_ChildToParentStream().stream()); + + aSink = Some(ParentToParentStream()); + auto& uuid = aSink->get_ParentToParentStream().uuid(); + + MOZ_TRY(nsContentUtils::GenerateUUIDInPlace(uuid)); + + IPCBlobInputStreamStorage::Get()->AddStream(deserialized, uuid, + aBodyStreamSize, 0); + + return NS_OK; } } // anonymous namespace @@ -64,15 +74,30 @@ void MaybeDeserializeAndReserialize(const Maybe& aDeserialize, ServiceWorkerFetchEventOpArgs copyArgs = aArgs; IPCInternalRequest& copyRequest = copyArgs.internalRequest(); - PBackgroundParent* bgParent = aManager->Manager(); - MOZ_ASSERT(bgParent); + if (copyRequest.body().ref().type() == + BodyStreamVariant::TParentToParentStream) { + nsCOMPtr stream; + auto streamLength = copyRequest.bodySize(); + const auto& uuid = + copyRequest.body().ref().get_ParentToParentStream().uuid(); + IPCBlobInputStreamStorage* storage = IPCBlobInputStreamStorage::Get(); - UniquePtr autoBodyStream = MakeUnique(); - MaybeDeserializeAndReserialize(aArgs.internalRequest().body(), - copyRequest.body(), autoBodyStream, bgParent); + storage->GetStream(uuid, 0, streamLength, getter_AddRefs(stream)); + storage->ForgetStream(uuid); + + MOZ_DIAGNOSTIC_ASSERT(stream); + + PBackgroundParent* bgParent = aManager->Manager(); + MOZ_ASSERT(bgParent); + + copyRequest.body() = Some(ParentToChildStream()); + MOZ_ALWAYS_SUCCEEDS(IPCBlobUtils::SerializeInputStream( + stream, streamLength, + copyRequest.body().ref().get_ParentToChildStream().actorParent(), + bgParent)); + } Unused << aManager->SendPFetchEventOpProxyConstructor(actor, copyArgs); - autoBodyStream->TakeOptionalValue(); } FetchEventOpProxyParent::~FetchEventOpProxyParent() { @@ -125,20 +150,14 @@ mozilla::ipc::IPCResult FetchEventOpProxyParent::RecvRespondWith( PBackgroundParent* bgParent = manager->Manager(); MOZ_ASSERT(bgParent); - UniquePtr autoBodyStream = MakeUnique(); - UniquePtr autoAlternativeBodyStream = - MakeUnique(); - - MaybeDeserializeAndReserialize(originalResponse.body(), copyResponse.body(), - autoBodyStream, bgParent); - MaybeDeserializeAndReserialize(originalResponse.alternativeBody(), - copyResponse.alternativeBody(), - autoAlternativeBodyStream, bgParent); + MOZ_ALWAYS_SUCCEEDS(MaybeDeserializeAndWrapForMainThread( + originalResponse.body(), copyResponse.bodySize(), copyResponse.body(), + bgParent)); + MOZ_ALWAYS_SUCCEEDS(MaybeDeserializeAndWrapForMainThread( + originalResponse.alternativeBody(), InternalResponse::UNKNOWN_BODY_SIZE, + copyResponse.alternativeBody(), bgParent)); Unused << mReal->SendRespondWith(copyArgs); - - autoBodyStream->TakeOptionalValue(); - autoAlternativeBodyStream->TakeOptionalValue(); } else { Unused << mReal->SendRespondWith(aResult); } diff --git a/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp b/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp index 081cc7d5aee9..69f140aea7fc 100644 --- a/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp +++ b/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp @@ -14,10 +14,13 @@ #include "nsDebug.h" #include "nsError.h" #include "nsIChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIHttpHeaderVisitor.h" #include "nsINetworkInterceptController.h" #include "nsIObserverService.h" #include "nsIScriptError.h" #include "nsIURI.h" +#include "nsIUploadChannel2.h" #include "nsThreadUtils.h" #include "ServiceWorkerManager.h" @@ -25,6 +28,8 @@ #include "mozilla/Assertions.h" #include "mozilla/ErrorResult.h" #include "mozilla/Maybe.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" #include "mozilla/ScopeExit.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs_dom.h" @@ -32,7 +37,9 @@ #include "mozilla/dom/ClientIPCTypes.h" #include "mozilla/dom/DOMTypes.h" #include "mozilla/dom/FetchEventOpChild.h" +#include "mozilla/dom/IPCBlobInputStreamStorage.h" #include "mozilla/dom/InternalHeaders.h" +#include "mozilla/dom/InternalRequest.h" #include "mozilla/dom/ReferrerInfo.h" #include "mozilla/dom/RemoteWorkerControllerChild.h" #include "mozilla/dom/ServiceWorkerBinding.h" @@ -574,12 +581,189 @@ ServiceWorkerPrivateImpl::PendingFetchEvent::~PendingFetchEvent() { } } +namespace { + +class HeaderFiller final : public nsIHttpHeaderVisitor { + public: + NS_DECL_ISUPPORTS + + explicit HeaderFiller(HeadersGuardEnum aGuard) + : mInternalHeaders(new InternalHeaders(aGuard)) { + MOZ_ASSERT(mInternalHeaders); + } + + NS_IMETHOD + VisitHeader(const nsACString& aHeader, const nsACString& aValue) override { + ErrorResult result; + mInternalHeaders->Append(aHeader, aValue, result); + + if (NS_WARN_IF(result.Failed())) { + return result.StealNSResult(); + } + + return NS_OK; + } + + RefPtr Extract() { + return RefPtr(std::move(mInternalHeaders)); + } + + private: + ~HeaderFiller() = default; + + RefPtr mInternalHeaders; +}; + +NS_IMPL_ISUPPORTS(HeaderFiller, nsIHttpHeaderVisitor) + +Result GetIPCInternalRequest( + nsIInterceptedChannel* aChannel) { + AssertIsOnMainThread(); + + nsCOMPtr uri; + MOZ_TRY(aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri))); + + nsCOMPtr uriNoFragment; + MOZ_TRY(NS_GetURIWithoutRef(uri, getter_AddRefs(uriNoFragment))); + + nsCOMPtr underlyingChannel; + MOZ_TRY(aChannel->GetChannel(getter_AddRefs(underlyingChannel))); + + nsCOMPtr httpChannel = do_QueryInterface(underlyingChannel); + MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?"); + + nsCOMPtr internalChannel = + do_QueryInterface(httpChannel); + NS_ENSURE_TRUE(internalChannel, Err(NS_ERROR_NOT_AVAILABLE)); + + nsCOMPtr cacheInfoChannel = + do_QueryInterface(underlyingChannel); + + nsAutoCString spec; + MOZ_TRY(uriNoFragment->GetSpec(spec)); + + nsAutoCString fragment; + MOZ_TRY(uri->GetRef(fragment)); + + nsAutoCString method; + MOZ_TRY(httpChannel->GetRequestMethod(method)); + + // This is safe due to static_asserts in ServiceWorkerManager.cpp + uint32_t cacheModeInt; + MOZ_ALWAYS_SUCCEEDS(internalChannel->GetFetchCacheMode(&cacheModeInt)); + RequestCache cacheMode = static_cast(cacheModeInt); + + RequestMode requestMode = + InternalRequest::MapChannelToRequestMode(underlyingChannel); + + // This is safe due to static_asserts in ServiceWorkerManager.cpp + uint32_t redirectMode; + MOZ_ALWAYS_SUCCEEDS(internalChannel->GetRedirectMode(&redirectMode)); + RequestRedirect requestRedirect = static_cast(redirectMode); + + RequestCredentials requestCredentials = + InternalRequest::MapChannelToRequestCredentials(underlyingChannel); + + nsAutoString referrer; + ReferrerPolicy referrerPolicy = ReferrerPolicy::_empty; + + nsCOMPtr referrerInfo = httpChannel->GetReferrerInfo(); + if (referrerInfo) { + referrerPolicy = referrerInfo->ReferrerPolicy(); + Unused << referrerInfo->GetComputedReferrerSpec(referrer); + } + + uint32_t loadFlags; + MOZ_TRY(underlyingChannel->GetLoadFlags(&loadFlags)); + + nsCOMPtr loadInfo; + MOZ_TRY(underlyingChannel->GetLoadInfo(getter_AddRefs(loadInfo))); + MOZ_ASSERT(loadInfo); + + nsContentPolicyType contentPolicyType = loadInfo->InternalContentPolicyType(); + + nsAutoString integrity; + MOZ_TRY(internalChannel->GetIntegrityMetadata(integrity)); + + RefPtr headerFiller = + MakeRefPtr(HeadersGuardEnum::Request); + MOZ_TRY(httpChannel->VisitNonDefaultRequestHeaders(headerFiller)); + + RefPtr internalHeaders = headerFiller->Extract(); + + ErrorResult result; + internalHeaders->SetGuard(HeadersGuardEnum::Immutable, result); + if (NS_WARN_IF(result.Failed())) { + return Err(result.StealNSResult()); + } + + nsTArray ipcHeaders; + HeadersGuardEnum ipcHeadersGuard; + internalHeaders->ToIPC(ipcHeaders, ipcHeadersGuard); + + nsAutoCString alternativeDataType; + if (cacheInfoChannel && + !cacheInfoChannel->PreferredAlternativeDataTypes().IsEmpty()) { + // TODO: the internal request probably needs all the preferred types. + alternativeDataType.Assign( + cacheInfoChannel->PreferredAlternativeDataTypes()[0].type()); + } + + Maybe principalInfo; + + if (loadInfo->TriggeringPrincipal()) { + principalInfo.emplace(); + MOZ_ALWAYS_SUCCEEDS(PrincipalToPrincipalInfo( + loadInfo->TriggeringPrincipal(), principalInfo.ptr())); + } + + // Note: all the arguments are copied rather than moved, which would be more + // efficient, because there's no move-friendly constructor generated. + return IPCInternalRequest( + method, {spec}, ipcHeadersGuard, ipcHeaders, Nothing(), -1, + alternativeDataType, contentPolicyType, referrer, referrerPolicy, + requestMode, requestCredentials, cacheMode, requestRedirect, integrity, + fragment, principalInfo); +} + +nsresult MaybeStoreStreamForBackgroundThread(nsIInterceptedChannel* aChannel, + IPCInternalRequest& aIPCRequest) { + nsCOMPtr channel; + MOZ_ALWAYS_SUCCEEDS(aChannel->GetChannel(getter_AddRefs(channel))); + + Maybe body; + int64_t bodySize = -1; + nsCOMPtr uploadChannel = do_QueryInterface(channel); + + if (uploadChannel) { + nsCOMPtr uploadStream; + MOZ_TRY(uploadChannel->CloneUploadStream(&aIPCRequest.bodySize(), + getter_AddRefs(uploadStream))); + + if (uploadStream) { + Maybe& body = aIPCRequest.body(); + body.emplace(ParentToParentStream()); + + MOZ_TRY(nsContentUtils::GenerateUUIDInPlace( + body->get_ParentToParentStream().uuid())); + + IPCBlobInputStreamStorage::Get()->AddStream( + uploadStream, body->get_ParentToParentStream().uuid(), bodySize, 0); + } + } + + return NS_OK; +} + +} // anonymous namespace + nsresult ServiceWorkerPrivateImpl::SendFetchEvent( RefPtr aRegistration, nsCOMPtr aChannel, const nsAString& aClientId, const nsAString& aResultingClientId) { AssertIsOnMainThread(); MOZ_ASSERT(mOuter); + MOZ_ASSERT(mOuter->mInfo); MOZ_ASSERT(aRegistration); MOZ_ASSERT(aChannel); @@ -589,19 +773,15 @@ nsresult ServiceWorkerPrivateImpl::SendFetchEvent( }); nsCOMPtr channel; - nsresult rv = aChannel->GetChannel(getter_AddRefs(channel)); + MOZ_TRY(aChannel->GetChannel(getter_AddRefs(channel))); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } + IPCInternalRequest request; + MOZ_TRY_VAR(request, GetIPCInternalRequest(aChannel)); scopeExit.release(); - MOZ_ASSERT(mOuter->mInfo); - - // FetchEventOpChild will fill in the IPCInternalRequest. ServiceWorkerFetchEventOpArgs args( - mOuter->mInfo->ScriptSpec(), IPCInternalRequest(), nsString(aClientId), + mOuter->mInfo->ScriptSpec(), std::move(request), nsString(aClientId), nsString(aResultingClientId), nsContentUtils::IsNonSubresourceRequest(channel)); @@ -634,11 +814,9 @@ nsresult ServiceWorkerPrivateImpl::SendFetchEventInternal( return NS_ERROR_DOM_INVALID_STATE_ERR; } - nsresult rv = SpawnWorkerIfNeeded(); - - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } + MOZ_TRY(SpawnWorkerIfNeeded()); + MOZ_TRY( + MaybeStoreStreamForBackgroundThread(aChannel, aArgs.internalRequest())); scopeExit.release(); diff --git a/dom/serviceworkers/test/sw_file_upload.js b/dom/serviceworkers/test/sw_file_upload.js index cb960c06a350..c35269924be9 100644 --- a/dom/serviceworkers/test/sw_file_upload.js +++ b/dom/serviceworkers/test/sw_file_upload.js @@ -1,7 +1,14 @@ self.skipWaiting(); addEventListener("fetch", event => { - event.respondWith(fetch(event.request)); + const url = new URL(event.request.url); + const params = new URLSearchParams(url.search); + + if (params.get("clone") === "1") { + event.respondWith(fetch(event.request.clone())); + } else { + event.respondWith(fetch(event.request)); + } }); addEventListener("activate", function(event) { diff --git a/dom/serviceworkers/test/test_file_upload.html b/dom/serviceworkers/test/test_file_upload.html index 6141bcbbe211..1fd07d7308ca 100644 --- a/dom/serviceworkers/test/test_file_upload.html +++ b/dom/serviceworkers/test/test_file_upload.html @@ -14,6 +14,12 @@