gecko-dev/dom/cache/AutoUtils.cpp
Nika Layzell 1aa11ffed6 Bug 1754004 - Part 11: Simplify the IPCStream serialization API, r=asuth,necko-reviewers,kershaw
As serializing IPCStream no longer requires a manager or FileDescriptor array,
the arguments are no longer necessary, and can be removed. The AutoIPCStream
helper can also be removed, as managed actors are no longer used for
serialization, so a delayed start callback is not necessary.

The delayed start parameter is also removed from nsIIPCSerializableInputStream
instances, but is still present as `aAllowLazy` on the toplevel serialization
methods.

Differential Revision: https://phabricator.services.mozilla.com/D141048
2022-05-13 14:16:13 +00:00

474 lines
16 KiB
C++

/* -*- 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/dom/cache/AutoUtils.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/InternalHeaders.h"
#include "mozilla/dom/InternalRequest.h"
#include "mozilla/dom/cache/CacheParent.h"
#include "mozilla/dom/cache/CacheStreamControlParent.h"
#include "mozilla/dom/cache/ReadStream.h"
#include "mozilla/dom/cache/SavedTypes.h"
#include "mozilla/dom/cache/StreamList.h"
#include "mozilla/dom/cache/TypeUtils.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsHttp.h"
using mozilla::Maybe;
using mozilla::Unused;
using mozilla::dom::cache::CacheReadStream;
using mozilla::ipc::PBackgroundParent;
namespace {
enum CleanupAction { Forget, Delete };
void CleanupChild(CacheReadStream& aReadStream, CleanupAction aAction) {
// fds cleaned up by mStreamCleanupList
}
void CleanupChild(Maybe<CacheReadStream>& aMaybeReadStream,
CleanupAction aAction) {
if (aMaybeReadStream.isNothing()) {
return;
}
CleanupChild(aMaybeReadStream.ref(), aAction);
}
} // namespace
namespace mozilla::dom::cache {
// --------------------------------------------
AutoChildOpArgs::AutoChildOpArgs(TypeUtils* aTypeUtils,
const CacheOpArgs& aOpArgs,
uint32_t aEntryCount)
: mTypeUtils(aTypeUtils), mOpArgs(aOpArgs), mSent(false) {
MOZ_DIAGNOSTIC_ASSERT(mTypeUtils);
MOZ_RELEASE_ASSERT(aEntryCount != 0);
if (mOpArgs.type() == CacheOpArgs::TCachePutAllArgs) {
CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs();
args.requestResponseList().SetCapacity(aEntryCount);
} else {
MOZ_DIAGNOSTIC_ASSERT(aEntryCount == 1);
}
}
AutoChildOpArgs::~AutoChildOpArgs() {
CleanupAction action = mSent ? Forget : Delete;
switch (mOpArgs.type()) {
case CacheOpArgs::TCacheMatchArgs: {
CacheMatchArgs& args = mOpArgs.get_CacheMatchArgs();
CleanupChild(args.request().body(), action);
break;
}
case CacheOpArgs::TCacheMatchAllArgs: {
CacheMatchAllArgs& args = mOpArgs.get_CacheMatchAllArgs();
if (args.maybeRequest().isNothing()) {
break;
}
CleanupChild(args.maybeRequest().ref().body(), action);
break;
}
case CacheOpArgs::TCachePutAllArgs: {
CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs();
auto& list = args.requestResponseList();
for (uint32_t i = 0; i < list.Length(); ++i) {
CleanupChild(list[i].request().body(), action);
CleanupChild(list[i].response().body(), action);
}
break;
}
case CacheOpArgs::TCacheDeleteArgs: {
CacheDeleteArgs& args = mOpArgs.get_CacheDeleteArgs();
CleanupChild(args.request().body(), action);
break;
}
case CacheOpArgs::TCacheKeysArgs: {
CacheKeysArgs& args = mOpArgs.get_CacheKeysArgs();
if (args.maybeRequest().isNothing()) {
break;
}
CleanupChild(args.maybeRequest().ref().body(), action);
break;
}
case CacheOpArgs::TStorageMatchArgs: {
StorageMatchArgs& args = mOpArgs.get_StorageMatchArgs();
CleanupChild(args.request().body(), action);
break;
}
default:
// Other types do not need cleanup
break;
}
}
void AutoChildOpArgs::Add(const InternalRequest& aRequest,
BodyAction aBodyAction, SchemeAction aSchemeAction,
ErrorResult& aRv) {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch (mOpArgs.type()) {
case CacheOpArgs::TCacheMatchArgs: {
CacheMatchArgs& args = mOpArgs.get_CacheMatchArgs();
mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction,
aSchemeAction, aRv);
break;
}
case CacheOpArgs::TCacheMatchAllArgs: {
CacheMatchAllArgs& args = mOpArgs.get_CacheMatchAllArgs();
MOZ_DIAGNOSTIC_ASSERT(args.maybeRequest().isNothing());
args.maybeRequest().emplace(CacheRequest());
mTypeUtils->ToCacheRequest(args.maybeRequest().ref(), aRequest,
aBodyAction, aSchemeAction, aRv);
break;
}
case CacheOpArgs::TCacheDeleteArgs: {
CacheDeleteArgs& args = mOpArgs.get_CacheDeleteArgs();
mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction,
aSchemeAction, aRv);
break;
}
case CacheOpArgs::TCacheKeysArgs: {
CacheKeysArgs& args = mOpArgs.get_CacheKeysArgs();
MOZ_DIAGNOSTIC_ASSERT(args.maybeRequest().isNothing());
args.maybeRequest().emplace(CacheRequest());
mTypeUtils->ToCacheRequest(args.maybeRequest().ref(), aRequest,
aBodyAction, aSchemeAction, aRv);
break;
}
case CacheOpArgs::TStorageMatchArgs: {
StorageMatchArgs& args = mOpArgs.get_StorageMatchArgs();
mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction,
aSchemeAction, aRv);
break;
}
default:
MOZ_CRASH("Cache args type cannot send a Request!");
}
}
namespace {
bool MatchInPutList(const InternalRequest& aRequest,
const nsTArray<CacheRequestResponse>& aPutList) {
// This method implements the SW spec QueryCache algorithm against an
// in memory array of Request/Response objects. This essentially the
// same algorithm that is implemented in DBSchema.cpp. Unfortunately
// we cannot unify them because when operating against the real database
// we don't want to load all request/response objects into memory.
// Note, we can skip the check for a invalid request method because
// Cache should only call into here with a GET or HEAD.
#ifdef DEBUG
nsAutoCString method;
aRequest.GetMethod(method);
MOZ_ASSERT(method.LowerCaseEqualsLiteral("get") ||
method.LowerCaseEqualsLiteral("head"));
#endif
RefPtr<InternalHeaders> requestHeaders = aRequest.Headers();
for (uint32_t i = 0; i < aPutList.Length(); ++i) {
const CacheRequest& cachedRequest = aPutList[i].request();
const CacheResponse& cachedResponse = aPutList[i].response();
nsAutoCString url;
aRequest.GetURL(url);
nsAutoCString requestUrl(cachedRequest.urlWithoutQuery());
requestUrl.Append(cachedRequest.urlQuery());
// If the URLs don't match, then just skip to the next entry.
if (url != requestUrl) {
continue;
}
RefPtr<InternalHeaders> cachedRequestHeaders =
TypeUtils::ToInternalHeaders(cachedRequest.headers());
RefPtr<InternalHeaders> cachedResponseHeaders =
TypeUtils::ToInternalHeaders(cachedResponse.headers());
nsCString varyHeaders;
ErrorResult rv;
cachedResponseHeaders->Get("vary"_ns, varyHeaders, rv);
MOZ_ALWAYS_TRUE(!rv.Failed());
// Assume the vary headers match until we find a conflict
bool varyHeadersMatch = true;
for (const nsACString& header :
nsCCharSeparatedTokenizer(varyHeaders, NS_HTTP_HEADER_SEP).ToRange()) {
MOZ_DIAGNOSTIC_ASSERT(!header.EqualsLiteral("*"),
"We should have already caught this in "
"TypeUtils::ToPCacheResponseWithoutBody()");
ErrorResult headerRv;
nsAutoCString value;
requestHeaders->Get(header, value, headerRv);
if (NS_WARN_IF(headerRv.Failed())) {
headerRv.SuppressException();
MOZ_DIAGNOSTIC_ASSERT(value.IsEmpty());
}
nsAutoCString cachedValue;
cachedRequestHeaders->Get(header, cachedValue, headerRv);
if (NS_WARN_IF(headerRv.Failed())) {
headerRv.SuppressException();
MOZ_DIAGNOSTIC_ASSERT(cachedValue.IsEmpty());
}
if (value != cachedValue) {
varyHeadersMatch = false;
break;
}
}
// URL was equal and all vary headers match!
if (varyHeadersMatch) {
return true;
}
}
return false;
}
} // namespace
void AutoChildOpArgs::Add(JSContext* aCx, const InternalRequest& aRequest,
BodyAction aBodyAction, SchemeAction aSchemeAction,
Response& aResponse, ErrorResult& aRv) {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch (mOpArgs.type()) {
case CacheOpArgs::TCachePutAllArgs: {
CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs();
// Throw an error if a request/response pair would mask another
// request/response pair in the same PutAll operation. This is
// step 2.3.2.3 from the "Batch Cache Operations" spec algorithm.
if (MatchInPutList(aRequest, args.requestResponseList())) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
MOZ_RELEASE_ASSERT(args.requestResponseList().Length() <
args.requestResponseList().Capacity());
// The FileDescriptorSetChild asserts in its destructor that all fds have
// been removed. The copy constructor, however, simply duplicates the
// fds without removing any. This means each temporary and copy must be
// explicitly cleaned up.
//
// Avoid a lot of this hassle by making sure we only create one here. On
// error we remove it.
CacheRequestResponse& pair = *args.requestResponseList().AppendElement();
pair.request().body() = Nothing();
pair.response().body() = Nothing();
mTypeUtils->ToCacheRequest(pair.request(), aRequest, aBodyAction,
aSchemeAction, aRv);
if (!aRv.Failed()) {
mTypeUtils->ToCacheResponse(aCx, pair.response(), aResponse, aRv);
}
if (aRv.Failed()) {
CleanupChild(pair.request().body(), Delete);
args.requestResponseList().RemoveLastElement();
}
break;
}
default:
MOZ_CRASH("Cache args type cannot send a Request/Response pair!");
}
}
const CacheOpArgs& AutoChildOpArgs::SendAsOpArgs() {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
mSent = true;
return mOpArgs;
}
// --------------------------------------------
AutoParentOpResult::AutoParentOpResult(
mozilla::ipc::PBackgroundParent* aManager, const CacheOpResult& aOpResult,
uint32_t aEntryCount)
: mManager(aManager),
mOpResult(aOpResult),
mStreamControl(nullptr),
mSent(false) {
MOZ_DIAGNOSTIC_ASSERT(mManager);
MOZ_RELEASE_ASSERT(aEntryCount != 0);
if (mOpResult.type() == CacheOpResult::TCacheMatchAllResult) {
CacheMatchAllResult& result = mOpResult.get_CacheMatchAllResult();
result.responseList().SetCapacity(aEntryCount);
} else if (mOpResult.type() == CacheOpResult::TCacheKeysResult) {
CacheKeysResult& result = mOpResult.get_CacheKeysResult();
result.requestList().SetCapacity(aEntryCount);
} else {
MOZ_DIAGNOSTIC_ASSERT(aEntryCount == 1);
}
}
AutoParentOpResult::~AutoParentOpResult() {
CleanupAction action = mSent ? Forget : Delete;
switch (mOpResult.type()) {
case CacheOpResult::TStorageOpenResult: {
StorageOpenResult& result = mOpResult.get_StorageOpenResult();
if (action == Forget || result.actorParent() == nullptr) {
break;
}
QM_WARNONLY_TRY(OkIf(PCacheParent::Send__delete__(result.actorParent())));
break;
}
default:
// other types do not need additional clean up
break;
}
if (action == Delete && mStreamControl) {
mStreamControl->AssertWillDelete();
QM_WARNONLY_TRY(
OkIf(PCacheStreamControlParent::Send__delete__(mStreamControl)));
}
}
void AutoParentOpResult::Add(CacheId aOpenedCacheId,
SafeRefPtr<Manager> aManager) {
MOZ_DIAGNOSTIC_ASSERT(mOpResult.type() == CacheOpResult::TStorageOpenResult);
MOZ_DIAGNOSTIC_ASSERT(mOpResult.get_StorageOpenResult().actorParent() ==
nullptr);
mOpResult.get_StorageOpenResult().actorParent() =
mManager->SendPCacheConstructor(
new CacheParent(std::move(aManager), aOpenedCacheId));
}
void AutoParentOpResult::Add(const SavedResponse& aSavedResponse,
StreamList& aStreamList) {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch (mOpResult.type()) {
case CacheOpResult::TCacheMatchResult: {
CacheMatchResult& result = mOpResult.get_CacheMatchResult();
MOZ_DIAGNOSTIC_ASSERT(result.maybeResponse().isNothing());
result.maybeResponse().emplace(aSavedResponse.mValue);
SerializeResponseBody(aSavedResponse, aStreamList,
&result.maybeResponse().ref());
break;
}
case CacheOpResult::TCacheMatchAllResult: {
CacheMatchAllResult& result = mOpResult.get_CacheMatchAllResult();
MOZ_RELEASE_ASSERT(result.responseList().Length() <
result.responseList().Capacity());
result.responseList().AppendElement(aSavedResponse.mValue);
SerializeResponseBody(aSavedResponse, aStreamList,
&result.responseList().LastElement());
break;
}
case CacheOpResult::TStorageMatchResult: {
StorageMatchResult& result = mOpResult.get_StorageMatchResult();
MOZ_DIAGNOSTIC_ASSERT(result.maybeResponse().isNothing());
result.maybeResponse().emplace(aSavedResponse.mValue);
SerializeResponseBody(aSavedResponse, aStreamList,
&result.maybeResponse().ref());
break;
}
default:
MOZ_CRASH("Cache result type cannot handle returning a Response!");
}
}
void AutoParentOpResult::Add(const SavedRequest& aSavedRequest,
StreamList& aStreamList) {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch (mOpResult.type()) {
case CacheOpResult::TCacheKeysResult: {
CacheKeysResult& result = mOpResult.get_CacheKeysResult();
MOZ_RELEASE_ASSERT(result.requestList().Length() <
result.requestList().Capacity());
result.requestList().AppendElement(aSavedRequest.mValue);
CacheRequest& request = result.requestList().LastElement();
if (!aSavedRequest.mHasBodyId) {
request.body() = Nothing();
break;
}
request.body().emplace(CacheReadStream());
SerializeReadStream(aSavedRequest.mBodyId, aStreamList,
&request.body().ref());
break;
}
default:
MOZ_CRASH("Cache result type cannot handle returning a Request!");
}
}
const CacheOpResult& AutoParentOpResult::SendAsOpResult() {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
mSent = true;
return mOpResult;
}
void AutoParentOpResult::SerializeResponseBody(
const SavedResponse& aSavedResponse, StreamList& aStreamList,
CacheResponse* aResponseOut) {
MOZ_DIAGNOSTIC_ASSERT(aResponseOut);
if (!aSavedResponse.mHasBodyId) {
aResponseOut->body() = Nothing();
return;
}
aResponseOut->body().emplace(CacheReadStream());
SerializeReadStream(aSavedResponse.mBodyId, aStreamList,
&aResponseOut->body().ref());
}
void AutoParentOpResult::SerializeReadStream(const nsID& aId,
StreamList& aStreamList,
CacheReadStream* aReadStreamOut) {
MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut);
MOZ_DIAGNOSTIC_ASSERT(!mSent);
nsCOMPtr<nsIInputStream> stream = aStreamList.Extract(aId);
if (!mStreamControl) {
mStreamControl = static_cast<CacheStreamControlParent*>(
mManager->SendPCacheStreamControlConstructor(
new CacheStreamControlParent()));
// If this failed, then the child process is gone. Warn and allow actor
// cleanup to proceed as normal.
if (!mStreamControl) {
NS_WARNING("Cache failed to create stream control actor.");
return;
}
}
aStreamList.SetStreamControl(mStreamControl);
RefPtr<ReadStream> readStream =
ReadStream::Create(mStreamControl, aId, stream);
ErrorResult rv;
readStream->Serialize(aReadStreamOut, rv);
MOZ_DIAGNOSTIC_ASSERT(!rv.Failed());
}
} // namespace mozilla::dom::cache