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
This commit is contained in:
Perry Jiang 2020-04-30 23:52:54 +00:00
parent 0b1ab06c29
commit 1ad4e039d4
15 changed files with 391 additions and 332 deletions

View File

@ -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;
};

View File

@ -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<mozilla::ipc::PrincipalInfo>(
aIPCRequest.principalInfo().ref());
}
const Maybe<BodyStreamVariant>& 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<IPCBlobInputStreamChild*>(
body->get_ParentToChildStream().actorChild())
->CreateStream();
}
}
InternalRequest::~InternalRequest() = default;
template void InternalRequest::ToIPC<mozilla::ipc::PBackgroundChild>(
IPCInternalRequest* aIPCRequest, mozilla::ipc::PBackgroundChild* aManager,
UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream);
template <typename M>
void InternalRequest::ToIPC(
IPCInternalRequest* aIPCRequest, M* aManager,
UniquePtr<mozilla::ipc::AutoIPCStream>& 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<bool> ok = aAutoStream->Serialize(mBodyStream, aManager);
MOZ_ASSERT(ok);
}
aIPCRequest->bodySize() = mBodyLength;
aIPCRequest->preferredAlternativeDataType() = mPreferredAlternativeDataType;
aIPCRequest->contentPolicyType() = static_cast<uint32_t>(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;

View File

@ -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 <typename M>
void ToIPC(IPCInternalRequest* aIPCRequest, M* aManager,
UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream);
already_AddRefed<InternalRequest> Clone();
void GetMethod(nsCString& aMethod) const { aMethod.Assign(mMethod); }

View File

@ -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<nsIInputStream> 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<nsIInputStream> 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<nsIInputStream> body =
mozilla::ipc::DeserializeIPCStream(aIPCResponse.body());
response->SetBody(body, aIPCResponse.bodySize());
if (aIPCResponse.body()) {
auto bodySize = aIPCResponse.bodySize();
nsCOMPtr<nsIInputStream> body =
TakeStreamFromStorage(*aIPCResponse.body(), bodySize);
response->SetBody(body, bodySize);
}
response->SetAlternativeDataType(aIPCResponse.alternativeDataType());
nsCOMPtr<nsIInputStream> alternativeBody =
mozilla::ipc::DeserializeIPCStream(aIPCResponse.alternativeBody());
response->SetAlternativeBody(alternativeBody);
if (aIPCResponse.alternativeBody()) {
nsCOMPtr<nsIInputStream> 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<mozilla::ipc::PBackgroundChild>(
void InternalResponse::ToIPC(
IPCInternalResponse* aIPCResponse, mozilla::ipc::PBackgroundChild* aManager,
UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoBodyStream,
UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoAlternativeBodyStream);
template <typename M>
void InternalResponse::ToIPC(
IPCInternalResponse* aIPCResponse, M* aManager,
UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoBodyStream,
UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoAlternativeBodyStream) {
MOZ_ASSERT(aIPCResponse);
nsTArray<HeadersEntry> 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<mozilla::ipc::PrincipalInfo> 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<uint64_t>(UNKNOWN_BODY_SIZE),
mErrorCode, GetAlternativeDataType(), Nothing(),
mChannelInfo.AsIPCChannelInfo(), principalInfo);
nsCOMPtr<nsIInputStream> 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<bool> ok = aAutoBodyStream->Serialize(body, aManager);
MOZ_ASSERT(ok);
}
aIPCResponse->bodySize() = bodySize;
aIPCResponse->errorCode() = mErrorCode;
aIPCResponse->alternativeDataType() = GetAlternativeDataType();
nsCOMPtr<nsIInputStream> 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<bool> ok =
aAutoAlternativeBodyStream->Serialize(alternativeBody, aManager);
MOZ_ASSERT(ok);
}
aIPCResponse->channelInfo() = mChannelInfo.AsIPCChannelInfo();
if (mPrincipalInfo) {
aIPCResponse->principalInfo().emplace(*mPrincipalInfo);
}
}
already_AddRefed<InternalResponse> InternalResponse::Clone(

View File

@ -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<InternalResponse> FromIPC(
const IPCInternalResponse& aIPCResponse);
template <typename M>
// Note: the AutoIPCStreams must outlive the IPCInternalResponse.
void ToIPC(
IPCInternalResponse* aIPCResponse, M* aManager,
IPCInternalResponse* aIPCResponse,
mozilla::ipc::PBackgroundChild* aManager,
UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoBodyStream,
UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoAlternativeBodyStream);
@ -111,6 +113,12 @@ class InternalResponse final {
return GetURLList(aURLList);
}
nsTArray<nsCString> GetUnfilteredURLList() const {
nsTArray<nsCString> list;
GetUnfilteredURLList(list);
return list;
}
void SetURLList(const nsTArray<nsCString>& aURLList) {
mURLList.Assign(aURLList);

View File

@ -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; }

View File

@ -46,8 +46,8 @@ class IPCBlobInputStreamStorage final : public nsIObserver {
const nsID& aID);
private:
IPCBlobInputStreamStorage();
~IPCBlobInputStreamStorage();
IPCBlobInputStreamStorage() = default;
~IPCBlobInputStreamStorage() = default;
struct StreamData {
nsCOMPtr<nsIInputStream> mInputStream;

View File

@ -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) {

View File

@ -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,

View File

@ -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<InternalHeaders> Extract() {
return RefPtr<InternalHeaders>(std::move(mInternalHeaders));
}
private:
~HeaderFiller() = default;
RefPtr<InternalHeaders> mInternalHeaders;
};
NS_IMPL_ISUPPORTS(HeaderFiller, nsIHttpHeaderVisitor)
nsresult GetIPCInternalRequest(nsIInterceptedChannel* aChannel,
IPCInternalRequest* aOutRequest,
UniquePtr<AutoIPCStream>& aAutoStream) {
AssertIsOnMainThread();
nsCOMPtr<nsIURI> uri;
nsresult rv = aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uriNoFragment;
rv = NS_GetURIWithoutRef(uri, getter_AddRefs(uriNoFragment));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIChannel> underlyingChannel;
rv = aChannel->GetChannel(getter_AddRefs(underlyingChannel));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(underlyingChannel);
MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");
nsCOMPtr<nsIHttpChannelInternal> internalChannel =
do_QueryInterface(httpChannel);
NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsICacheInfoChannel> 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<RequestCache>(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<RequestRedirect>(redirectMode);
RequestCredentials requestCredentials =
InternalRequest::MapChannelToRequestCredentials(underlyingChannel);
nsAutoString referrer;
ReferrerPolicy referrerPolicy = ReferrerPolicy::_empty;
nsCOMPtr<nsIReferrerInfo> 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<nsILoadInfo> 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> headerFiller =
MakeRefPtr<HeaderFiller>(HeadersGuardEnum::Request);
rv = httpChannel->VisitNonDefaultRequestHeaders(headerFiller);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<InternalHeaders> internalHeaders = headerFiller->Extract();
ErrorResult result;
internalHeaders->SetGuard(HeadersGuardEnum::Immutable, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
nsCOMPtr<nsIInputStream> uploadStream;
int64_t uploadStreamContentLength = -1;
if (uploadChannel) {
rv = uploadChannel->CloneUploadStream(&uploadStreamContentLength,
getter_AddRefs(uploadStream));
NS_ENSURE_SUCCESS(rv, rv);
}
RefPtr<InternalRequest> 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<GenericPromise> 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<AutoIPCStream>();
// 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<IPCInternalRequest*>(&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__);
}

View File

@ -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<ResetInterceptionArgs>()) {
Unused << self->SendRespondWith(
result.extract<ResetInterceptionArgs>());
@ -139,7 +145,7 @@ RefPtr<InternalRequest> FetchEventOpProxyChild::ExtractInternalRequest() {
MOZ_ASSERT(IsCurrentThreadRunningWorker());
MOZ_ASSERT(mInternalRequest);
return RefPtr<InternalRequest>(std::move(mInternalRequest));
return std::move(mInternalRequest);
}
void FetchEventOpProxyChild::ActorDestroy(ActorDestroyReason) {

View File

@ -9,13 +9,18 @@
#include <utility>
#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<IPCStream>& aDeserialize,
Maybe<IPCStream>& aReserialize,
UniquePtr<AutoIPCStream>& aAutoStream,
PBackgroundParent* aManager) {
nsCOMPtr<nsIInputStream> maybeDeserialized =
DeserializeIPCStream(aDeserialize);
if (!maybeDeserialized) {
return;
nsresult MaybeDeserializeAndWrapForMainThread(
const Maybe<BodyStreamVariant>& aSource, int64_t aBodyStreamSize,
Maybe<BodyStreamVariant>& aSink, PBackgroundParent* aManager) {
if (aSource.isNothing()) {
return NS_OK;
}
aAutoStream.reset(new AutoIPCStream(aReserialize));
DebugOnly<bool> ok = aAutoStream->Serialize(maybeDeserialized, aManager);
MOZ_ASSERT(ok);
nsCOMPtr<nsIInputStream> 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<IPCStream>& aDeserialize,
ServiceWorkerFetchEventOpArgs copyArgs = aArgs;
IPCInternalRequest& copyRequest = copyArgs.internalRequest();
PBackgroundParent* bgParent = aManager->Manager();
MOZ_ASSERT(bgParent);
if (copyRequest.body().ref().type() ==
BodyStreamVariant::TParentToParentStream) {
nsCOMPtr<nsIInputStream> stream;
auto streamLength = copyRequest.bodySize();
const auto& uuid =
copyRequest.body().ref().get_ParentToParentStream().uuid();
IPCBlobInputStreamStorage* storage = IPCBlobInputStreamStorage::Get();
UniquePtr<AutoIPCStream> autoBodyStream = MakeUnique<AutoIPCStream>();
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<AutoIPCStream> autoBodyStream = MakeUnique<AutoIPCStream>();
UniquePtr<AutoIPCStream> autoAlternativeBodyStream =
MakeUnique<AutoIPCStream>();
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);
}

View File

@ -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<InternalHeaders> Extract() {
return RefPtr<InternalHeaders>(std::move(mInternalHeaders));
}
private:
~HeaderFiller() = default;
RefPtr<InternalHeaders> mInternalHeaders;
};
NS_IMPL_ISUPPORTS(HeaderFiller, nsIHttpHeaderVisitor)
Result<IPCInternalRequest, nsresult> GetIPCInternalRequest(
nsIInterceptedChannel* aChannel) {
AssertIsOnMainThread();
nsCOMPtr<nsIURI> uri;
MOZ_TRY(aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri)));
nsCOMPtr<nsIURI> uriNoFragment;
MOZ_TRY(NS_GetURIWithoutRef(uri, getter_AddRefs(uriNoFragment)));
nsCOMPtr<nsIChannel> underlyingChannel;
MOZ_TRY(aChannel->GetChannel(getter_AddRefs(underlyingChannel)));
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(underlyingChannel);
MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");
nsCOMPtr<nsIHttpChannelInternal> internalChannel =
do_QueryInterface(httpChannel);
NS_ENSURE_TRUE(internalChannel, Err(NS_ERROR_NOT_AVAILABLE));
nsCOMPtr<nsICacheInfoChannel> 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<RequestCache>(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<RequestRedirect>(redirectMode);
RequestCredentials requestCredentials =
InternalRequest::MapChannelToRequestCredentials(underlyingChannel);
nsAutoString referrer;
ReferrerPolicy referrerPolicy = ReferrerPolicy::_empty;
nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
if (referrerInfo) {
referrerPolicy = referrerInfo->ReferrerPolicy();
Unused << referrerInfo->GetComputedReferrerSpec(referrer);
}
uint32_t loadFlags;
MOZ_TRY(underlyingChannel->GetLoadFlags(&loadFlags));
nsCOMPtr<nsILoadInfo> loadInfo;
MOZ_TRY(underlyingChannel->GetLoadInfo(getter_AddRefs(loadInfo)));
MOZ_ASSERT(loadInfo);
nsContentPolicyType contentPolicyType = loadInfo->InternalContentPolicyType();
nsAutoString integrity;
MOZ_TRY(internalChannel->GetIntegrityMetadata(integrity));
RefPtr<HeaderFiller> headerFiller =
MakeRefPtr<HeaderFiller>(HeadersGuardEnum::Request);
MOZ_TRY(httpChannel->VisitNonDefaultRequestHeaders(headerFiller));
RefPtr<InternalHeaders> internalHeaders = headerFiller->Extract();
ErrorResult result;
internalHeaders->SetGuard(HeadersGuardEnum::Immutable, result);
if (NS_WARN_IF(result.Failed())) {
return Err(result.StealNSResult());
}
nsTArray<HeadersEntry> 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> 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<nsIChannel> channel;
MOZ_ALWAYS_SUCCEEDS(aChannel->GetChannel(getter_AddRefs(channel)));
Maybe<BodyStreamVariant> body;
int64_t bodySize = -1;
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel);
if (uploadChannel) {
nsCOMPtr<nsIInputStream> uploadStream;
MOZ_TRY(uploadChannel->CloneUploadStream(&aIPCRequest.bodySize(),
getter_AddRefs(uploadStream)));
if (uploadStream) {
Maybe<BodyStreamVariant>& 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<ServiceWorkerRegistrationInfo> aRegistration,
nsCOMPtr<nsIInterceptedChannel> 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<nsIChannel> 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();

View File

@ -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) {

View File

@ -14,6 +14,12 @@
<input id="input" type="file">
<script class="testbody" type="text/javascript">
function GetFormData(file) {
const formData = new FormData();
formData.append('file', file);
return formData;
}
async function onOpened(message) {
let input = document.getElementById("input");
SpecialPowers.wrap(input).mozSetFileArray([message.file]);
@ -24,13 +30,22 @@ async function onOpened(message) {
let serviceWorker = reg.installing || reg.waiting || reg.active;
await waitForState(serviceWorker, 'activated');
let res = await fetch('server_file_upload.sjs', {
let res = await fetch('server_file_upload.sjs?clone=0', {
method: 'POST',
body: input.files[0],
});
let data = await res.clone().text();
ok(data.length > 0, "We have data!");
ok(data.length > 0, "We have data for an uncloned request!");
res = await fetch('server_file_upload.sjs?clone=1', {
method: 'POST',
// Make sure the underlying stream is a file stream
body: GetFormData(input.files[0]),
});
data = await res.clone().text();
ok(data.length > 0, "We have data for a file-stream-backed cloned request!");
await reg.unregister();
SimpleTest.finish();