diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/ipc/Blob.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/ipc/Blob.cpp')
-rw-r--r-- | dom/ipc/Blob.cpp | 4793 |
1 files changed, 4793 insertions, 0 deletions
diff --git a/dom/ipc/Blob.cpp b/dom/ipc/Blob.cpp new file mode 100644 index 000000000..95d2e0513 --- /dev/null +++ b/dom/ipc/Blob.cpp @@ -0,0 +1,4793 @@ +/* -*- 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 "BlobChild.h" +#include "BlobParent.h" + +#include "BackgroundParent.h" +#include "ContentChild.h" +#include "ContentParent.h" +#include "FileDescriptorSetChild.h" +#include "jsapi.h" +#include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Monitor.h" +#include "mozilla/Mutex.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/nsIContentParent.h" +#include "mozilla/dom/nsIContentChild.h" +#include "mozilla/dom/PBlobStreamChild.h" +#include "mozilla/dom/PBlobStreamParent.h" +#include "mozilla/dom/indexedDB/FileSnapshot.h" +#include "mozilla/dom/IndexedDatabaseManager.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/PBackgroundParent.h" +#include "mozilla/ipc/PFileDescriptorSetParent.h" +#include "MultipartBlobImpl.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsID.h" +#include "nsIFileStreams.h" +#include "nsIInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsIMultiplexInputStream.h" +#include "nsIRemoteBlob.h" +#include "nsISeekableStream.h" +#include "nsIUUIDGenerator.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsStringStream.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" + +#ifdef DEBUG +#include "BackgroundChild.h" // BackgroundChild::GetForCurrentThread(). +#endif + +#ifdef OS_POSIX +#include "chrome/common/file_descriptor_set_posix.h" +#endif + +#define DISABLE_ASSERTS_FOR_FUZZING 0 + +#if DISABLE_ASSERTS_FOR_FUZZING +#define ASSERT_UNLESS_FUZZING(...) do { } while (0) +#else +#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__) +#endif + +#define PRIVATE_REMOTE_INPUT_STREAM_IID \ + {0x30c7699f, 0x51d2, 0x48c8, {0xad, 0x56, 0xc0, 0x16, 0xd7, 0x6f, 0x71, 0x27}} + +namespace mozilla { +namespace dom { + +using namespace mozilla::ipc; +using namespace mozilla::dom::indexedDB; +using namespace mozilla::dom::workers; + +namespace { + +const char kUUIDGeneratorContractId[] = "@mozilla.org/uuid-generator;1"; + +const uint32_t kMaxFileDescriptorsPerMessage = 250; + +#ifdef OS_POSIX +// Keep this in sync with other platforms. +static_assert(FileDescriptorSet::MAX_DESCRIPTORS_PER_MESSAGE == 250, + "MAX_DESCRIPTORS_PER_MESSAGE mismatch!"); +#endif + +StaticRefPtr<nsIUUIDGenerator> gUUIDGenerator; + +GeckoProcessType gProcessType = GeckoProcessType_Invalid; + +void +CommonStartup() +{ + MOZ_ASSERT(NS_IsMainThread()); + + gProcessType = XRE_GetProcessType(); + MOZ_ASSERT(gProcessType != GeckoProcessType_Invalid); + + nsCOMPtr<nsIUUIDGenerator> uuidGen = do_GetService(kUUIDGeneratorContractId); + MOZ_RELEASE_ASSERT(uuidGen); + + gUUIDGenerator = uuidGen; + ClearOnShutdown(&gUUIDGenerator); +} + +template <class ManagerType> +struct ConcreteManagerTypeTraits; + +template <> +struct ConcreteManagerTypeTraits<nsIContentChild> +{ + typedef ContentChild Type; +}; + +template <> +struct ConcreteManagerTypeTraits<PBackgroundChild> +{ + typedef PBackgroundChild Type; +}; + +template <> +struct ConcreteManagerTypeTraits<nsIContentParent> +{ + typedef ContentParent Type; +}; + +template <> +struct ConcreteManagerTypeTraits<PBackgroundParent> +{ + typedef PBackgroundParent Type; +}; + +void +AssertCorrectThreadForManager(nsIContentChild* aManager) +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +void +AssertCorrectThreadForManager(nsIContentParent* aManager) +{ + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + MOZ_ASSERT(NS_IsMainThread()); +} + +void +AssertCorrectThreadForManager(PBackgroundChild* aManager) +{ +#ifdef DEBUG + if (aManager) { + PBackgroundChild* backgroundChild = BackgroundChild::GetForCurrentThread(); + MOZ_ASSERT(backgroundChild); + MOZ_ASSERT(backgroundChild == aManager); + } +#endif +} + +void +AssertCorrectThreadForManager(PBackgroundParent* aManager) +{ + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + AssertIsOnBackgroundThread(); +} + +intptr_t +ActorManagerProcessID(nsIContentParent* aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return reinterpret_cast<intptr_t>(aManager); +} + +intptr_t +ActorManagerProcessID(PBackgroundParent* aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return BackgroundParent::GetRawContentParentForComparison(aManager); +} + +bool +ActorManagerIsSameProcess(nsIContentParent* aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return false; +} + +bool +ActorManagerIsSameProcess(PBackgroundParent* aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return !BackgroundParent::IsOtherProcessActor(aManager); +} + +bool +EventTargetIsOnCurrentThread(nsIEventTarget* aEventTarget) +{ + if (!aEventTarget) { + return NS_IsMainThread(); + } + + bool current; + + // If this fails, we are probably shutting down. + if (NS_WARN_IF(NS_FAILED(aEventTarget->IsOnCurrentThread(¤t)))) { + return true; + } + + return current; +} + +class CancelableRunnableWrapper final + : public CancelableRunnable +{ + nsCOMPtr<nsIRunnable> mRunnable; +#ifdef DEBUG + nsCOMPtr<nsIEventTarget> mDEBUGEventTarget; +#endif + +public: + CancelableRunnableWrapper(nsIRunnable* aRunnable, + nsIEventTarget* aEventTarget) + : mRunnable(aRunnable) +#ifdef DEBUG + , mDEBUGEventTarget(aEventTarget) +#endif + { + MOZ_ASSERT(aRunnable); + MOZ_ASSERT(aEventTarget); + } + + NS_DECL_ISUPPORTS_INHERITED + +private: + ~CancelableRunnableWrapper() + { } + + NS_DECL_NSIRUNNABLE + nsresult Cancel() override; +}; + +NS_IMPL_ISUPPORTS_INHERITED0(CancelableRunnableWrapper, CancelableRunnable) + +NS_IMETHODIMP +CancelableRunnableWrapper::Run() +{ + DebugOnly<bool> onTarget; + MOZ_ASSERT(mDEBUGEventTarget); + MOZ_ASSERT(NS_SUCCEEDED(mDEBUGEventTarget->IsOnCurrentThread(&onTarget))); + MOZ_ASSERT(onTarget); + + nsCOMPtr<nsIRunnable> runnable; + mRunnable.swap(runnable); + + if (runnable) { + return runnable->Run(); + } + + return NS_OK; +} + +nsresult +CancelableRunnableWrapper::Cancel() +{ + DebugOnly<bool> onTarget; + MOZ_ASSERT(mDEBUGEventTarget); + MOZ_ASSERT(NS_SUCCEEDED(mDEBUGEventTarget->IsOnCurrentThread(&onTarget))); + MOZ_ASSERT(onTarget); + + if (NS_WARN_IF(!mRunnable)) { + return NS_ERROR_UNEXPECTED; + } + + Unused << Run(); + MOZ_ASSERT(!mRunnable); + + return NS_OK; +} + +// Ensure that a nsCOMPtr/nsRefPtr is released on the target thread. +template <template <class> class SmartPtr, class T> +void +ReleaseOnTarget(SmartPtr<T>& aDoomed, nsIEventTarget* aTarget) +{ + MOZ_ASSERT(aDoomed); + MOZ_ASSERT(!EventTargetIsOnCurrentThread(aTarget)); + + T* doomedRaw; + aDoomed.forget(&doomedRaw); + + auto* doomedSupports = static_cast<nsISupports*>(doomedRaw); + + nsCOMPtr<nsIRunnable> releaseRunnable = + NewNonOwningRunnableMethod(doomedSupports, &nsISupports::Release); + MOZ_ASSERT(releaseRunnable); + + if (aTarget) { + // If we're targeting a non-main thread then make sure the runnable is + // cancelable. + releaseRunnable = new CancelableRunnableWrapper(releaseRunnable, aTarget); + + MOZ_ALWAYS_SUCCEEDS(aTarget->Dispatch(releaseRunnable, + NS_DISPATCH_NORMAL)); + } else { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(releaseRunnable)); + } +} + +template <class ManagerType> +void +ConstructFileDescriptorSet(ManagerType* aManager, + nsTArray<FileDescriptor>& aFDs, + OptionalFileDescriptorSet& aOptionalFDSet) +{ + typedef typename ConcreteManagerTypeTraits<ManagerType>::Type + ConcreteManagerType; + + MOZ_ASSERT(aManager); + + if (aFDs.IsEmpty()) { + aOptionalFDSet = void_t(); + return; + } + + if (aFDs.Length() <= kMaxFileDescriptorsPerMessage) { + aOptionalFDSet = nsTArray<FileDescriptor>(); + aOptionalFDSet.get_ArrayOfFileDescriptor().SwapElements(aFDs); + return; + } + + auto* concreteManager = static_cast<ConcreteManagerType*>(aManager); + + PFileDescriptorSetParent* fdSet = + concreteManager->SendPFileDescriptorSetConstructor(aFDs[0]); + if (!fdSet) { + aOptionalFDSet = void_t(); + return; + } + + for (uint32_t index = 1; index < aFDs.Length(); index++) { + if (!fdSet->SendAddFileDescriptor(aFDs[index])) { + aOptionalFDSet = void_t(); + return; + } + } + + aOptionalFDSet = fdSet; +} + +void +OptionalFileDescriptorSetToFDs(OptionalFileDescriptorSet& aOptionalSet, + nsTArray<FileDescriptor>& aFDs) +{ + MOZ_ASSERT(aFDs.IsEmpty()); + + switch (aOptionalSet.type()) { + case OptionalFileDescriptorSet::Tvoid_t: + return; + + case OptionalFileDescriptorSet::TArrayOfFileDescriptor: + aOptionalSet.get_ArrayOfFileDescriptor().SwapElements(aFDs); + return; + + case OptionalFileDescriptorSet::TPFileDescriptorSetChild: { + FileDescriptorSetChild* fdSetActor = + static_cast<FileDescriptorSetChild*>( + aOptionalSet.get_PFileDescriptorSetChild()); + MOZ_ASSERT(fdSetActor); + + fdSetActor->ForgetFileDescriptors(aFDs); + MOZ_ASSERT(!aFDs.IsEmpty()); + + PFileDescriptorSetChild::Send__delete__(fdSetActor); + return; + } + + default: + MOZ_CRASH("Unknown type!"); + } + + MOZ_CRASH("Should never get here!"); +} + +class NS_NO_VTABLE IPrivateRemoteInputStream + : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(PRIVATE_REMOTE_INPUT_STREAM_IID) + + // This will return the underlying stream. + virtual nsIInputStream* + BlockAndGetInternalStream() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IPrivateRemoteInputStream, + PRIVATE_REMOTE_INPUT_STREAM_IID) + +// This class exists to keep a blob alive at least as long as its internal +// stream. +class BlobInputStreamTether final + : public nsIMultiplexInputStream + , public nsISeekableStream + , public nsIIPCSerializableInputStream + , public nsIFileMetadata +{ + nsCOMPtr<nsIInputStream> mStream; + RefPtr<BlobImpl> mBlobImpl; + + nsIMultiplexInputStream* mWeakMultiplexStream; + nsISeekableStream* mWeakSeekableStream; + nsIIPCSerializableInputStream* mWeakSerializableStream; + nsIFileMetadata* mWeakFileMetadata; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_NSIINPUTSTREAM(mStream->) + NS_FORWARD_SAFE_NSIMULTIPLEXINPUTSTREAM(mWeakMultiplexStream) + NS_FORWARD_SAFE_NSISEEKABLESTREAM(mWeakSeekableStream) + NS_FORWARD_SAFE_NSIIPCSERIALIZABLEINPUTSTREAM(mWeakSerializableStream) + NS_FORWARD_SAFE_NSIFILEMETADATA(mWeakFileMetadata) + + BlobInputStreamTether(nsIInputStream* aStream, BlobImpl* aBlobImpl) + : mStream(aStream) + , mBlobImpl(aBlobImpl) + , mWeakMultiplexStream(nullptr) + , mWeakSeekableStream(nullptr) + , mWeakSerializableStream(nullptr) + , mWeakFileMetadata(nullptr) + { + MOZ_ASSERT(aStream); + MOZ_ASSERT(aBlobImpl); + + nsCOMPtr<nsIMultiplexInputStream> multiplexStream = + do_QueryInterface(aStream); + if (multiplexStream) { + MOZ_ASSERT(SameCOMIdentity(aStream, multiplexStream)); + mWeakMultiplexStream = multiplexStream; + } + + nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(aStream); + if (seekableStream) { + MOZ_ASSERT(SameCOMIdentity(aStream, seekableStream)); + mWeakSeekableStream = seekableStream; + } + + nsCOMPtr<nsIIPCSerializableInputStream> serializableStream = + do_QueryInterface(aStream); + if (serializableStream) { + MOZ_ASSERT(SameCOMIdentity(aStream, serializableStream)); + mWeakSerializableStream = serializableStream; + } + + nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(aStream); + if (fileMetadata) { + MOZ_ASSERT(SameCOMIdentity(aStream, fileMetadata)); + mWeakFileMetadata = fileMetadata; + } + } + +private: + ~BlobInputStreamTether() + { } +}; + +NS_IMPL_ADDREF(BlobInputStreamTether) +NS_IMPL_RELEASE(BlobInputStreamTether) + +NS_INTERFACE_MAP_BEGIN(BlobInputStreamTether) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMultiplexInputStream, + mWeakMultiplexStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, mWeakSeekableStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream, + mWeakSerializableStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileMetadata, + mWeakFileMetadata) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +class RemoteInputStream final + : public nsIInputStream + , public nsISeekableStream + , public nsIIPCSerializableInputStream + , public nsIFileMetadata + , public IPrivateRemoteInputStream +{ + Monitor mMonitor; + BlobChild* mActor; + nsCOMPtr<nsIInputStream> mStream; + RefPtr<BlobImpl> mBlobImpl; + nsCOMPtr<nsIEventTarget> mEventTarget; + nsISeekableStream* mWeakSeekableStream; + nsIFileMetadata* mWeakFileMetadata; + uint64_t mStart; + uint64_t mLength; + +public: + RemoteInputStream(BlobImpl* aBlobImpl, + uint64_t aStart, + uint64_t aLength); + + RemoteInputStream(BlobChild* aActor, + BlobImpl* aBlobImpl, + uint64_t aStart, + uint64_t aLength); + + bool + IsOnOwningThread() const + { + return EventTargetIsOnCurrentThread(mEventTarget); + } + + void + AssertIsOnOwningThread() const + { + MOZ_ASSERT(IsOnOwningThread()); + } + + bool + IsWorkerStream() const + { + return !!mActor; + } + + void + SetStream(nsIInputStream* aStream); + + NS_DECL_THREADSAFE_ISUPPORTS + +private: + ~RemoteInputStream(); + + nsresult + BlockAndWaitForStream(); + + void + ReallyBlockAndWaitForStream(); + + bool + IsSeekableStream(); + + bool + IsFileMetadata(); + + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSIFILEMETADATA + + virtual nsIInputStream* + BlockAndGetInternalStream() override; +}; + +class InputStreamChild final + : public PBlobStreamChild +{ + RefPtr<RemoteInputStream> mRemoteStream; + +public: + explicit + InputStreamChild(RemoteInputStream* aRemoteStream) + : mRemoteStream(aRemoteStream) + { + MOZ_ASSERT(aRemoteStream); + aRemoteStream->AssertIsOnOwningThread(); + } + + InputStreamChild() + { } + + ~InputStreamChild() + { } + +private: + // This method is only called by the IPDL message machinery. + virtual bool + Recv__delete__(const InputStreamParams& aParams, + const OptionalFileDescriptorSet& aFDs) override; +}; + +class InputStreamParent final + : public PBlobStreamParent +{ + typedef mozilla::ipc::InputStreamParams InputStreamParams; + typedef mozilla::dom::OptionalFileDescriptorSet OptionalFileDescriptorSet; + + bool* mSyncLoopGuard; + InputStreamParams* mParams; + OptionalFileDescriptorSet* mFDs; + +#ifdef DEBUG + PRThread* mOwningThread; +#endif + +public: + InputStreamParent() + : mSyncLoopGuard(nullptr) + , mParams(nullptr) + , mFDs(nullptr) + { +#ifdef DEBUG + mOwningThread = PR_GetCurrentThread(); +#endif + + AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(InputStreamParent); + } + + InputStreamParent(bool* aSyncLoopGuard, + InputStreamParams* aParams, + OptionalFileDescriptorSet* aFDs) + : mSyncLoopGuard(aSyncLoopGuard) + , mParams(aParams) + , mFDs(aFDs) + { +#ifdef DEBUG + mOwningThread = PR_GetCurrentThread(); +#endif + + AssertIsOnOwningThread(); + MOZ_ASSERT(aSyncLoopGuard); + MOZ_ASSERT(!*aSyncLoopGuard); + MOZ_ASSERT(aParams); + MOZ_ASSERT(aFDs); + + MOZ_COUNT_CTOR(InputStreamParent); + } + + ~InputStreamParent() + { + AssertIsOnOwningThread(); + + MOZ_COUNT_DTOR(InputStreamParent); + } + + void + AssertIsOnOwningThread() const + { +#ifdef DEBUG + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); +#endif + } + + bool + Destroy(const InputStreamParams& aParams, + const OptionalFileDescriptorSet& aFDs) + { + AssertIsOnOwningThread(); + + if (mSyncLoopGuard) { + MOZ_ASSERT(!*mSyncLoopGuard); + + *mSyncLoopGuard = true; + *mParams = aParams; + *mFDs = aFDs; + + // We're not a live actor so manage the memory ourselves. + delete this; + return true; + } + + // This will be destroyed by BlobParent::DeallocPBlobStreamParent. + return PBlobStreamParent::Send__delete__(this, aParams, aFDs); + } + +private: + // This method is only called by the IPDL message machinery. + virtual void + ActorDestroy(ActorDestroyReason aWhy) override + { + // Nothing needs to be done here. + } +}; + +struct MOZ_STACK_CLASS CreateBlobImplMetadata final +{ + nsString mContentType; + nsString mName; + uint64_t mLength; + int64_t mLastModifiedDate; + bool mHasRecursed; + const bool mIsSameProcessActor; + + explicit CreateBlobImplMetadata(bool aIsSameProcessActor) + : mLength(0) + , mLastModifiedDate(0) + , mHasRecursed(false) + , mIsSameProcessActor(aIsSameProcessActor) + { + MOZ_COUNT_CTOR(CreateBlobImplMetadata); + + mName.SetIsVoid(true); + } + + ~CreateBlobImplMetadata() + { + MOZ_COUNT_DTOR(CreateBlobImplMetadata); + } + + bool + IsFile() const + { + return !mName.IsVoid(); + } +}; + +already_AddRefed<BlobImpl> +CreateBlobImpl(const nsID& aKnownBlobIDData, + const CreateBlobImplMetadata& aMetadata) +{ + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + MOZ_ASSERT(aMetadata.mHasRecursed); + + RefPtr<BlobImpl> blobImpl = BlobParent::GetBlobImplForID(aKnownBlobIDData); + if (NS_WARN_IF(!blobImpl)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + DebugOnly<bool> isMutable; + MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + + return blobImpl.forget(); +} + +already_AddRefed<BlobImpl> +CreateBlobImpl(const BlobDataStream& aStream, + const CreateBlobImplMetadata& aMetadata) +{ + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + + nsCOMPtr<nsIInputStream> inputStream = DeserializeIPCStream(aStream.stream()); + if (!inputStream) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + uint64_t length = aStream.length(); + + RefPtr<BlobImpl> blobImpl; + if (!aMetadata.mHasRecursed && aMetadata.IsFile()) { + if (length) { + blobImpl = + BlobImplStream::Create(inputStream, + aMetadata.mName, + aMetadata.mContentType, + aMetadata.mLastModifiedDate, + length); + } else { + blobImpl = + new EmptyBlobImpl(aMetadata.mName, + aMetadata.mContentType, + aMetadata.mLastModifiedDate); + } + } else if (length) { + blobImpl = + BlobImplStream::Create(inputStream, aMetadata.mContentType, + length); + } else { + blobImpl = new EmptyBlobImpl(aMetadata.mContentType); + } + + MOZ_ALWAYS_SUCCEEDS(blobImpl->SetMutable(false)); + + return blobImpl.forget(); +} + +already_AddRefed<BlobImpl> +CreateBlobImpl(const nsTArray<BlobData>& aBlobData, + CreateBlobImplMetadata& aMetadata); + +already_AddRefed<BlobImpl> +CreateBlobImplFromBlobData(const BlobData& aBlobData, + CreateBlobImplMetadata& aMetadata) +{ + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + + RefPtr<BlobImpl> blobImpl; + + switch (aBlobData.type()) { + case BlobData::TnsID: { + blobImpl = CreateBlobImpl(aBlobData.get_nsID(), aMetadata); + break; + } + + case BlobData::TBlobDataStream: { + blobImpl = CreateBlobImpl(aBlobData.get_BlobDataStream(), aMetadata); + break; + } + + case BlobData::TArrayOfBlobData: { + blobImpl = CreateBlobImpl(aBlobData.get_ArrayOfBlobData(), aMetadata); + break; + } + + default: + MOZ_CRASH("Unknown params!"); + } + + return blobImpl.forget(); +} + +already_AddRefed<BlobImpl> +CreateBlobImpl(const nsTArray<BlobData>& aBlobDatas, + CreateBlobImplMetadata& aMetadata) +{ + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + + // Special case for a multipart blob with only one part. + if (aBlobDatas.Length() == 1) { + const BlobData& blobData = aBlobDatas[0]; + + RefPtr<BlobImpl> blobImpl = + CreateBlobImplFromBlobData(blobData, aMetadata); + if (NS_WARN_IF(!blobImpl)) { + return nullptr; + } + + DebugOnly<bool> isMutable; + MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + + return blobImpl.forget(); + } + + nsTArray<RefPtr<BlobImpl>> blobImpls; + if (NS_WARN_IF(!blobImpls.SetLength(aBlobDatas.Length(), fallible))) { + return nullptr; + } + + const bool hasRecursed = aMetadata.mHasRecursed; + aMetadata.mHasRecursed = true; + + for (uint32_t count = aBlobDatas.Length(), index = 0; + index < count; + index++) { + const BlobData& blobData = aBlobDatas[index]; + RefPtr<BlobImpl>& blobImpl = blobImpls[index]; + + blobImpl = CreateBlobImplFromBlobData(blobData, aMetadata); + if (NS_WARN_IF(!blobImpl)) { + return nullptr; + } + + DebugOnly<bool> isMutable; + MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + } + + ErrorResult rv; + RefPtr<BlobImpl> blobImpl; + if (!hasRecursed && aMetadata.IsFile()) { + blobImpl = MultipartBlobImpl::Create(Move(blobImpls), aMetadata.mName, + aMetadata.mContentType, rv); + } else { + blobImpl = MultipartBlobImpl::Create(Move(blobImpls), aMetadata.mContentType, rv); + } + + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return nullptr; + } + + MOZ_ALWAYS_SUCCEEDS(blobImpl->SetMutable(false)); + + return blobImpl.forget(); +} + +already_AddRefed<BlobImpl> +CreateBlobImpl(const ParentBlobConstructorParams& aParams, + const BlobData& aBlobData, + bool aIsSameProcessActor) +{ + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + MOZ_ASSERT(aParams.blobParams().type() == + AnyBlobConstructorParams::TNormalBlobConstructorParams || + aParams.blobParams().type() == + AnyBlobConstructorParams::TFileBlobConstructorParams); + + CreateBlobImplMetadata metadata(aIsSameProcessActor); + + if (aParams.blobParams().type() == + AnyBlobConstructorParams::TNormalBlobConstructorParams) { + const NormalBlobConstructorParams& params = + aParams.blobParams().get_NormalBlobConstructorParams(); + + if (NS_WARN_IF(params.length() == UINT64_MAX)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + metadata.mContentType = params.contentType(); + metadata.mLength = params.length(); + } else { + const FileBlobConstructorParams& params = + aParams.blobParams().get_FileBlobConstructorParams(); + + if (NS_WARN_IF(params.length() == UINT64_MAX)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + if (NS_WARN_IF(params.modDate() == INT64_MAX)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + if (NS_WARN_IF(!params.path().IsEmpty())) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + metadata.mContentType = params.contentType(); + metadata.mName = params.name(); + metadata.mLength = params.length(); + metadata.mLastModifiedDate = params.modDate(); + } + + RefPtr<BlobImpl> blobImpl = + CreateBlobImplFromBlobData(aBlobData, metadata); + return blobImpl.forget(); +} + +template <class ChildManagerType> +void +BlobDataFromBlobImpl(ChildManagerType* aManager, BlobImpl* aBlobImpl, + BlobData& aBlobData, + nsTArray<UniquePtr<AutoIPCStream>>& aIPCStreams) +{ + MOZ_ASSERT(gProcessType != GeckoProcessType_Default); + MOZ_ASSERT(aBlobImpl); + + const nsTArray<RefPtr<BlobImpl>>* subBlobs = aBlobImpl->GetSubBlobImpls(); + + if (subBlobs) { + MOZ_ASSERT(subBlobs->Length()); + + aBlobData = nsTArray<BlobData>(); + + nsTArray<BlobData>& subBlobDatas = aBlobData.get_ArrayOfBlobData(); + subBlobDatas.SetLength(subBlobs->Length()); + + for (uint32_t count = subBlobs->Length(), index = 0; + index < count; + index++) { + BlobDataFromBlobImpl(aManager, subBlobs->ElementAt(index), + subBlobDatas[index], aIPCStreams); + } + + return; + } + + nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryInterface(aBlobImpl); + if (remoteBlob) { + BlobChild* actor = remoteBlob->GetBlobChild(); + MOZ_ASSERT(actor); + + aBlobData = actor->ParentID(); + return; + } + + ErrorResult rv; + uint64_t length = aBlobImpl->GetSize(rv); + MOZ_ALWAYS_TRUE(!rv.Failed()); + + nsCOMPtr<nsIInputStream> inputStream; + aBlobImpl->GetInternalStream(getter_AddRefs(inputStream), rv); + MOZ_ALWAYS_TRUE(!rv.Failed()); + + UniquePtr<AutoIPCStream> autoStream(new AutoIPCStream()); + autoStream->Serialize(inputStream, aManager); + aBlobData = BlobDataStream(autoStream->TakeValue(), length); + + aIPCStreams.AppendElement(Move(autoStream)); +} + +RemoteInputStream::RemoteInputStream(BlobImpl* aBlobImpl, + uint64_t aStart, + uint64_t aLength) + : mMonitor("RemoteInputStream.mMonitor") + , mActor(nullptr) + , mBlobImpl(aBlobImpl) + , mWeakSeekableStream(nullptr) + , mWeakFileMetadata(nullptr) + , mStart(aStart) + , mLength(aLength) +{ + MOZ_ASSERT(aBlobImpl); + + if (!NS_IsMainThread()) { + mEventTarget = do_GetCurrentThread(); + MOZ_ASSERT(mEventTarget); + } + + MOZ_ASSERT(IsOnOwningThread()); +} + +RemoteInputStream::RemoteInputStream(BlobChild* aActor, + BlobImpl* aBlobImpl, + uint64_t aStart, + uint64_t aLength) + : mMonitor("RemoteInputStream.mMonitor") + , mActor(aActor) + , mBlobImpl(aBlobImpl) + , mEventTarget(NS_GetCurrentThread()) + , mWeakSeekableStream(nullptr) + , mWeakFileMetadata(nullptr) + , mStart(aStart) + , mLength(aLength) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aActor); + MOZ_ASSERT(aBlobImpl); + + MOZ_ASSERT(IsOnOwningThread()); +} + +RemoteInputStream::~RemoteInputStream() +{ + if (!IsOnOwningThread()) { + mStream = nullptr; + mWeakSeekableStream = nullptr; + mWeakFileMetadata = nullptr; + + if (mBlobImpl) { + ReleaseOnTarget(mBlobImpl, mEventTarget); + } + } +} + +void +RemoteInputStream::SetStream(nsIInputStream* aStream) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aStream); + + nsCOMPtr<nsIInputStream> stream = aStream; + nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(aStream); + nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(aStream); + + MOZ_ASSERT_IF(seekableStream, SameCOMIdentity(aStream, seekableStream)); + MOZ_ASSERT_IF(fileMetadata, SameCOMIdentity(aStream, fileMetadata)); + + { + MonitorAutoLock lock(mMonitor); + + MOZ_ASSERT_IF(mStream, IsWorkerStream()); + + if (!mStream) { + MOZ_ASSERT(!mWeakSeekableStream); + MOZ_ASSERT(!mWeakFileMetadata); + + mStream.swap(stream); + mWeakSeekableStream = seekableStream; + mWeakFileMetadata = fileMetadata; + + mMonitor.Notify(); + } + } +} + +nsresult +RemoteInputStream::BlockAndWaitForStream() +{ + if (mStream) { + return NS_OK; + } + + if (IsOnOwningThread()) { + if (NS_IsMainThread()) { + NS_WARNING("Blocking the main thread is not supported!"); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(IsWorkerStream()); + + InputStreamParams params; + OptionalFileDescriptorSet optionalFDs; + + mActor->SendBlobStreamSync(mStart, mLength, ¶ms, &optionalFDs); + + nsTArray<FileDescriptor> fds; + OptionalFileDescriptorSetToFDs(optionalFDs, fds); + + nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(params, fds); + MOZ_ASSERT(stream); + + SetStream(stream); + return NS_OK; + } + + ReallyBlockAndWaitForStream(); + + return NS_OK; +} + +void +RemoteInputStream::ReallyBlockAndWaitForStream() +{ + MOZ_ASSERT(!IsOnOwningThread()); + + DebugOnly<bool> waited; + + { + MonitorAutoLock lock(mMonitor); + + waited = !mStream; + + while (!mStream) { + mMonitor.Wait(); + } + } + + MOZ_ASSERT(mStream); + +#ifdef DEBUG + if (waited && mWeakSeekableStream) { + int64_t position; + if (NS_SUCCEEDED(mWeakSeekableStream->Tell(&position))) { + MOZ_ASSERT(!position, "Stream not starting at 0!"); + } + } +#endif +} + +bool +RemoteInputStream::IsSeekableStream() +{ + if (IsOnOwningThread()) { + if (!mStream) { + NS_WARNING("Don't know if this stream is seekable yet!"); + return true; + } + } else { + ReallyBlockAndWaitForStream(); + } + + return !!mWeakSeekableStream; +} + +bool +RemoteInputStream::IsFileMetadata() +{ + if (IsOnOwningThread()) { + if (!mStream) { + NS_WARNING("Don't know if this stream supports file metadata yet!"); + return true; + } + } else { + ReallyBlockAndWaitForStream(); + } + + return !!mWeakFileMetadata; +} + +NS_IMPL_ADDREF(RemoteInputStream) +NS_IMPL_RELEASE(RemoteInputStream) + +NS_INTERFACE_MAP_BEGIN(RemoteInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, IsSeekableStream()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFileMetadata, IsFileMetadata()) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) + NS_INTERFACE_MAP_ENTRY(IPrivateRemoteInputStream) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +RemoteInputStream::Close() +{ + nsresult rv = BlockAndWaitForStream(); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<BlobImpl> blobImpl; + mBlobImpl.swap(blobImpl); + + rv = mStream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +RemoteInputStream::Available(uint64_t* aAvailable) +{ + if (!IsOnOwningThread()) { + nsresult rv = BlockAndWaitForStream(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mStream->Available(aAvailable); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + +#ifdef DEBUG + if (NS_IsMainThread()) { + NS_WARNING("Someone is trying to do main-thread I/O..."); + } +#endif + + nsresult rv; + + // See if we already have our real stream. + nsCOMPtr<nsIInputStream> inputStream; + { + MonitorAutoLock lock(mMonitor); + + inputStream = mStream; + } + + // If we do then just call through. + if (inputStream) { + rv = inputStream->Available(aAvailable); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + + // If the stream is already closed then we can't do anything. + if (!mBlobImpl) { + return NS_BASE_STREAM_CLOSED; + } + + // Otherwise fake it... + NS_WARNING("Available() called before real stream has been delivered, " + "guessing the amount of data available!"); + + ErrorResult error; + *aAvailable = mBlobImpl->GetSize(error); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } + + return NS_OK; +} + +NS_IMETHODIMP +RemoteInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aResult) +{ + nsresult rv = BlockAndWaitForStream(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mStream->Read(aBuffer, aCount, aResult); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +RemoteInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, + uint32_t aCount, + uint32_t* aResult) +{ + nsresult rv = BlockAndWaitForStream(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mStream->ReadSegments(aWriter, aClosure, aCount, aResult); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +RemoteInputStream::IsNonBlocking(bool* aNonBlocking) +{ + NS_ENSURE_ARG_POINTER(aNonBlocking); + + *aNonBlocking = false; + return NS_OK; +} + +NS_IMETHODIMP +RemoteInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + nsresult rv = BlockAndWaitForStream(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mWeakSeekableStream) { + NS_WARNING("Underlying blob stream is not seekable!"); + return NS_ERROR_NO_INTERFACE; + } + + rv = mWeakSeekableStream->Seek(aWhence, aOffset); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +RemoteInputStream::Tell(int64_t* aResult) +{ + // We can cheat here and assume that we're going to start at 0 if we don't yet + // have our stream. Though, really, this should abort since most input streams + // could block here. + if (IsOnOwningThread() && !mStream) { + *aResult = 0; + return NS_OK; + } + + nsresult rv = BlockAndWaitForStream(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mWeakSeekableStream) { + NS_WARNING("Underlying blob stream is not seekable!"); + return NS_ERROR_NO_INTERFACE; + } + + rv = mWeakSeekableStream->Tell(aResult); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +RemoteInputStream::SetEOF() +{ + nsresult rv = BlockAndWaitForStream(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mWeakSeekableStream) { + NS_WARNING("Underlying blob stream is not seekable!"); + return NS_ERROR_NO_INTERFACE; + } + + rv = mWeakSeekableStream->SetEOF(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +void +RemoteInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& /* aFDs */) +{ + MOZ_RELEASE_ASSERT(mBlobImpl); + + nsCOMPtr<nsIRemoteBlob> remote = do_QueryInterface(mBlobImpl); + MOZ_ASSERT(remote); + + BlobChild* actor = remote->GetBlobChild(); + MOZ_ASSERT(actor); + + aParams = RemoteInputStreamParams(actor->ParentID()); +} + +bool +RemoteInputStream::Deserialize(const InputStreamParams& /* aParams */, + const FileDescriptorArray& /* aFDs */) +{ + // See InputStreamUtils.cpp to see how deserialization of a + // RemoteInputStream is special-cased. + MOZ_CRASH("RemoteInputStream should never be deserialized"); +} + +NS_IMETHODIMP +RemoteInputStream::GetSize(int64_t* aSize) +{ + nsresult rv = BlockAndWaitForStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!mWeakFileMetadata) { + NS_WARNING("Underlying blob stream doesn't support file metadata!"); + return NS_ERROR_NO_INTERFACE; + } + + rv = mWeakFileMetadata->GetSize(aSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +RemoteInputStream::GetLastModified(int64_t* aLastModified) +{ + nsresult rv = BlockAndWaitForStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!mWeakFileMetadata) { + NS_WARNING("Underlying blob stream doesn't support file metadata!"); + return NS_ERROR_NO_INTERFACE; + } + + rv = mWeakFileMetadata->GetLastModified(aLastModified); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +RemoteInputStream::GetFileDescriptor(PRFileDesc** aFileDescriptor) +{ + nsresult rv = BlockAndWaitForStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!mWeakFileMetadata) { + NS_WARNING("Underlying blob stream doesn't support file metadata!"); + return NS_ERROR_NO_INTERFACE; + } + + rv = mWeakFileMetadata->GetFileDescriptor(aFileDescriptor); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +Maybe<uint64_t> +RemoteInputStream::ExpectedSerializedLength() +{ + return Nothing(); +} + +nsIInputStream* +RemoteInputStream::BlockAndGetInternalStream() +{ + MOZ_ASSERT(!IsOnOwningThread()); + + nsresult rv = BlockAndWaitForStream(); + NS_ENSURE_SUCCESS(rv, nullptr); + + return mStream; +} + +} // namespace + +StaticAutoPtr<BlobParent::IDTable> BlobParent::sIDTable; +StaticAutoPtr<Mutex> BlobParent::sIDTableMutex; + +/******************************************************************************* + * BlobParent::IDTableEntry Declaration + ******************************************************************************/ + +class BlobParent::IDTableEntry final +{ + const nsID mID; + const intptr_t mProcessID; + const RefPtr<BlobImpl> mBlobImpl; + +public: + static already_AddRefed<IDTableEntry> + Create(const nsID& aID, intptr_t aProcessID, BlobImpl* aBlobImpl) + { + MOZ_ASSERT(aBlobImpl); + + DebugOnly<bool> isMutable; + MOZ_ASSERT(NS_SUCCEEDED(aBlobImpl->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + + return GetOrCreateInternal(aID, + aProcessID, + aBlobImpl, + /* aMayCreate */ true, + /* aMayGet */ false, + /* aIgnoreProcessID */ false); + } + + static already_AddRefed<IDTableEntry> + Get(const nsID& aID, intptr_t aProcessID) + { + return GetOrCreateInternal(aID, + aProcessID, + nullptr, + /* aMayCreate */ false, + /* aMayGet */ true, + /* aIgnoreProcessID */ false); + } + + static already_AddRefed<IDTableEntry> + Get(const nsID& aID) + { + return GetOrCreateInternal(aID, + 0, + nullptr, + /* aMayCreate */ false, + /* aMayGet */ true, + /* aIgnoreProcessID */ true); + } + + static already_AddRefed<IDTableEntry> + GetOrCreate(const nsID& aID, intptr_t aProcessID, BlobImpl* aBlobImpl) + { + MOZ_ASSERT(aBlobImpl); + + DebugOnly<bool> isMutable; + MOZ_ASSERT(NS_SUCCEEDED(aBlobImpl->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + + return GetOrCreateInternal(aID, + aProcessID, + aBlobImpl, + /* aMayCreate */ true, + /* aMayGet */ true, + /* aIgnoreProcessID */ false); + } + + const nsID& + ID() const + { + return mID; + } + + intptr_t + ProcessID() const + { + return mProcessID; + } + + BlobImpl* + GetBlobImpl() const + { + return mBlobImpl; + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IDTableEntry) + +private: + IDTableEntry(const nsID& aID, intptr_t aProcessID, BlobImpl* aBlobImpl); + ~IDTableEntry(); + + static already_AddRefed<IDTableEntry> + GetOrCreateInternal(const nsID& aID, + intptr_t aProcessID, + BlobImpl* aBlobImpl, + bool aMayCreate, + bool aMayGet, + bool aIgnoreProcessID); +}; + +/******************************************************************************* + * BlobParent::OpenStreamRunnable Declaration + ******************************************************************************/ + +// Each instance of this class will be dispatched to the network stream thread +// pool to run the first time where it will open the file input stream. It will +// then dispatch itself back to the owning thread to send the child process its +// response (assuming that the child has not crashed). The runnable will then +// dispatch itself to the thread pool again in order to close the file input +// stream. +class BlobParent::OpenStreamRunnable final + : public Runnable +{ + friend class nsRevocableEventPtr<OpenStreamRunnable>; + + // Only safe to access these pointers if mRevoked is false! + BlobParent* mBlobActor; + InputStreamParent* mStreamActor; + + nsCOMPtr<nsIInputStream> mStream; + nsCOMPtr<nsIIPCSerializableInputStream> mSerializable; + nsCOMPtr<nsIEventTarget> mActorTarget; + nsCOMPtr<nsIThread> mIOTarget; + + bool mRevoked; + bool mClosing; + +public: + OpenStreamRunnable(BlobParent* aBlobActor, + InputStreamParent* aStreamActor, + nsIInputStream* aStream, + nsIIPCSerializableInputStream* aSerializable, + nsIThread* aIOTarget) + : mBlobActor(aBlobActor) + , mStreamActor(aStreamActor) + , mStream(aStream) + , mSerializable(aSerializable) + , mIOTarget(aIOTarget) + , mRevoked(false) + , mClosing(false) + { + MOZ_ASSERT(aBlobActor); + aBlobActor->AssertIsOnOwningThread(); + MOZ_ASSERT(aStreamActor); + MOZ_ASSERT(aStream); + // aSerializable may be null. + MOZ_ASSERT(aIOTarget); + + if (!NS_IsMainThread()) { + AssertIsOnBackgroundThread(); + + mActorTarget = do_GetCurrentThread(); + MOZ_ASSERT(mActorTarget); + } + + AssertIsOnOwningThread(); + } + + nsresult + Dispatch() + { + AssertIsOnOwningThread(); + MOZ_ASSERT(mIOTarget); + + nsresult rv = mIOTarget->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + + NS_DECL_ISUPPORTS_INHERITED + +private: + ~OpenStreamRunnable() + { } + + bool + IsOnOwningThread() const + { + return EventTargetIsOnCurrentThread(mActorTarget); + } + + void + AssertIsOnOwningThread() const + { + MOZ_ASSERT(IsOnOwningThread()); + } + + void + Revoke() + { + AssertIsOnOwningThread(); +#ifdef DEBUG + mBlobActor = nullptr; + mStreamActor = nullptr; +#endif + mRevoked = true; + } + + nsresult + OpenStream() + { + MOZ_ASSERT(!IsOnOwningThread()); + MOZ_ASSERT(mStream); + + if (!mSerializable) { + nsCOMPtr<IPrivateRemoteInputStream> remoteStream = + do_QueryInterface(mStream); + MOZ_ASSERT(remoteStream, "Must QI to IPrivateRemoteInputStream here!"); + + nsCOMPtr<nsIInputStream> realStream = + remoteStream->BlockAndGetInternalStream(); + NS_ENSURE_TRUE(realStream, NS_ERROR_FAILURE); + + mSerializable = do_QueryInterface(realStream); + if (!mSerializable) { + MOZ_ASSERT(false, "Must be serializable!"); + return NS_ERROR_FAILURE; + } + + mStream.swap(realStream); + } + + // To force the stream open we call Available(). We don't actually care + // how much data is available. + uint64_t available; + if (NS_FAILED(mStream->Available(&available))) { + NS_WARNING("Available failed on this stream!"); + } + + if (mActorTarget) { + nsresult rv = mActorTarget->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + } else { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); + } + + return NS_OK; + } + + nsresult + CloseStream() + { + MOZ_ASSERT(!IsOnOwningThread()); + MOZ_ASSERT(mStream); + + // Going to always release here. + nsCOMPtr<nsIInputStream> stream; + mStream.swap(stream); + + nsCOMPtr<nsIThread> ioTarget; + mIOTarget.swap(ioTarget); + + DebugOnly<nsresult> rv = stream->Close(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to close stream!"); + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewRunnableMethod(ioTarget, &nsIThread::Shutdown))); + + return NS_OK; + } + + nsresult + SendResponse() + { + AssertIsOnOwningThread(); + MOZ_ASSERT(mStream); + MOZ_ASSERT(mSerializable); + MOZ_ASSERT(mIOTarget); + MOZ_ASSERT(!mClosing); + + nsCOMPtr<nsIIPCSerializableInputStream> serializable; + mSerializable.swap(serializable); + + if (mRevoked) { + MOZ_ASSERT(!mBlobActor); + MOZ_ASSERT(!mStreamActor); + } + else { + MOZ_ASSERT(mBlobActor); + MOZ_ASSERT(mBlobActor->HasManager()); + MOZ_ASSERT(mStreamActor); + + InputStreamParams params; + nsTArray<FileDescriptor> fds; + serializable->Serialize(params, fds); + + MOZ_ASSERT(params.type() != InputStreamParams::T__None); + + OptionalFileDescriptorSet optionalFDSet; + if (nsIContentParent* contentManager = mBlobActor->GetContentManager()) { + ConstructFileDescriptorSet(contentManager, fds, optionalFDSet); + } else { + ConstructFileDescriptorSet(mBlobActor->GetBackgroundManager(), + fds, + optionalFDSet); + } + + mStreamActor->Destroy(params, optionalFDSet); + + mBlobActor->NoteRunnableCompleted(this); + +#ifdef DEBUG + mBlobActor = nullptr; + mStreamActor = nullptr; +#endif + } + + // If our luck is *really* bad then it is possible for the CloseStream() and + // nsIThread::Shutdown() functions to run before the Dispatch() call here + // finishes... Keep the thread alive until this method returns. + nsCOMPtr<nsIThread> kungFuDeathGrip = mIOTarget; + + mClosing = true; + + nsresult rv = kungFuDeathGrip->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(mIOTarget); + + if (IsOnOwningThread()) { + return SendResponse(); + } + + if (!mClosing) { + return OpenStream(); + } + + return CloseStream(); + } +}; + +NS_IMPL_ISUPPORTS_INHERITED0(BlobParent::OpenStreamRunnable, Runnable) + +/******************************************************************************* + * BlobChild::RemoteBlobImpl Declaration + ******************************************************************************/ + +class BlobChild::RemoteBlobImpl + : public BlobImplBase + , public nsIRemoteBlob +{ +protected: + class CreateStreamHelper; + class WorkerHolder; + + BlobChild* mActor; + nsCOMPtr<nsIEventTarget> mActorTarget; + + // These member variables are protected by mutex and it's set to null when the + // worker goes away. + WorkerPrivate* mWorkerPrivate; + nsAutoPtr<WorkerHolder> mWorkerHolder; + Mutex mMutex; + + // We use this pointer to keep a live a blobImpl coming from a different + // process until this one is fully created. We set it to null when + // SendCreatedFromKnownBlob() is received. This is used only with KnownBlob + // params in the CTOR of a IPC BlobImpl. + RefPtr<BlobImpl> mDifferentProcessBlobImpl; + + RefPtr<BlobImpl> mSameProcessBlobImpl; + + const bool mIsSlice; + + const bool mIsDirectory; + +public: + + enum BlobImplIsDirectory + { + eNotDirectory, + eDirectory + }; + + // For File. + RemoteBlobImpl(BlobChild* aActor, + BlobImpl* aRemoteBlobImpl, + const nsAString& aName, + const nsAString& aContentType, + const nsAString& aPath, + uint64_t aLength, + int64_t aModDate, + BlobImplIsDirectory aIsDirectory, + bool aIsSameProcessBlob); + + // For Blob. + RemoteBlobImpl(BlobChild* aActor, + BlobImpl* aRemoteBlobImpl, + const nsAString& aContentType, + uint64_t aLength, + bool aIsSameProcessBlob); + + // For mystery blobs. + explicit + RemoteBlobImpl(BlobChild* aActor); + + void + NoteDyingActor(); + + BlobChild* + GetActor() const + { + MOZ_ASSERT(ActorEventTargetIsOnCurrentThread()); + + return mActor; + } + + nsIEventTarget* + GetActorEventTarget() const + { + return mActorTarget; + } + + bool + ActorEventTargetIsOnCurrentThread() const + { + return EventTargetIsOnCurrentThread(BaseRemoteBlobImpl()->mActorTarget); + } + + bool + IsSlice() const + { + return mIsSlice; + } + + RemoteBlobSliceImpl* + AsSlice() const; + + RemoteBlobImpl* + BaseRemoteBlobImpl() const; + + NS_DECL_ISUPPORTS_INHERITED + + virtual void + GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const override; + + virtual bool + IsDirectory() const override; + + virtual already_AddRefed<BlobImpl> + CreateSlice(uint64_t aStart, + uint64_t aLength, + const nsAString& aContentType, + ErrorResult& aRv) override; + + virtual void + GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv) override; + + virtual int64_t + GetFileId() override; + + virtual int64_t + GetLastModified(ErrorResult& aRv) override; + + virtual void + SetLastModified(int64_t aLastModified) override; + + virtual nsresult + SetMutable(bool aMutable) override; + + virtual BlobChild* + GetBlobChild() override; + + virtual BlobParent* + GetBlobParent() override; + + void + NullifyDifferentProcessBlobImpl() + { + MOZ_ASSERT(mDifferentProcessBlobImpl); + mDifferentProcessBlobImpl = nullptr; + } + + // Used only by CreateStreamHelper, it dispatches a runnable to the target + // thread. This thread can be the main-thread, the background thread or a + // worker. If the thread is a worker, the aRunnable is wrapper into a + // ControlRunnable in order to avoid to be blocked into a sync event loop. + nsresult + DispatchToTarget(nsIRunnable* aRunnable); + + void + WorkerHasNotified(); + +protected: + // For SliceImpl. + RemoteBlobImpl(const nsAString& aContentType, uint64_t aLength); + + ~RemoteBlobImpl() + { + MOZ_ASSERT_IF(mActorTarget, + EventTargetIsOnCurrentThread(mActorTarget)); + } + + void + CommonInit(BlobChild* aActor); + + void + Destroy(); +}; + +class BlobChild::RemoteBlobImpl::WorkerHolder final + : public workers::WorkerHolder +{ + // Raw pointer because this class is kept alive by the mRemoteBlobImpl. + RemoteBlobImpl* mRemoteBlobImpl; + +public: + explicit WorkerHolder(RemoteBlobImpl* aRemoteBlobImpl) + : mRemoteBlobImpl(aRemoteBlobImpl) + { + MOZ_ASSERT(aRemoteBlobImpl); + } + + bool Notify(Status aStatus) override + { + mRemoteBlobImpl->WorkerHasNotified(); + return true; + } +}; + +class BlobChild::RemoteBlobImpl::CreateStreamHelper final + : public CancelableRunnable +{ + Monitor mMonitor; + RefPtr<RemoteBlobImpl> mRemoteBlobImpl; + RefPtr<RemoteInputStream> mInputStream; + const uint64_t mStart; + const uint64_t mLength; + bool mDone; + +public: + explicit CreateStreamHelper(RemoteBlobImpl* aRemoteBlobImpl); + + nsresult + GetStream(nsIInputStream** aInputStream); + + NS_IMETHOD Run() override; + +private: + ~CreateStreamHelper() + { + MOZ_ASSERT(!mRemoteBlobImpl); + MOZ_ASSERT(!mInputStream); + MOZ_ASSERT(mDone); + } + + void + RunInternal(RemoteBlobImpl* aBaseRemoteBlobImpl, bool aNotify); +}; + +class BlobChild::RemoteBlobSliceImpl final + : public RemoteBlobImpl +{ + RefPtr<RemoteBlobImpl> mParent; + bool mActorWasCreated; + +public: + RemoteBlobSliceImpl(RemoteBlobImpl* aParent, + uint64_t aStart, + uint64_t aLength, + const nsAString& aContentType); + + RemoteBlobImpl* + Parent() const + { + MOZ_ASSERT(mParent); + + return const_cast<RemoteBlobImpl*>(mParent.get()); + } + + uint64_t + Start() const + { + return mStart; + } + + void + EnsureActorWasCreated() + { + MOZ_ASSERT_IF(!ActorEventTargetIsOnCurrentThread(), + mActorWasCreated); + + if (!mActorWasCreated) { + EnsureActorWasCreatedInternal(); + } + } + + NS_DECL_ISUPPORTS_INHERITED + + virtual BlobChild* + GetBlobChild() override; + +private: + ~RemoteBlobSliceImpl() + { } + + void + EnsureActorWasCreatedInternal(); +}; + +/******************************************************************************* + * BlobParent::RemoteBlobImpl Declaration + ******************************************************************************/ + +class BlobParent::RemoteBlobImpl final + : public BlobImpl + , public nsIRemoteBlob +{ + BlobParent* mActor; + nsCOMPtr<nsIEventTarget> mActorTarget; + RefPtr<BlobImpl> mBlobImpl; + +public: + RemoteBlobImpl(BlobParent* aActor, BlobImpl* aBlobImpl); + + void + NoteDyingActor(); + + NS_DECL_ISUPPORTS_INHERITED + + virtual void + GetName(nsAString& aName) const override; + + virtual void + GetDOMPath(nsAString& aPath) const override; + + virtual void + SetDOMPath(const nsAString& aPath) override; + + virtual int64_t + GetLastModified(ErrorResult& aRv) override; + + virtual void + SetLastModified(int64_t aLastModified) override; + + virtual void + GetMozFullPath(nsAString& aName, ErrorResult& aRv) const override; + + virtual void + GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const override; + + virtual bool + IsDirectory() const override; + + virtual uint64_t + GetSize(ErrorResult& aRv) override; + + virtual void + GetType(nsAString& aType) override; + + virtual uint64_t + GetSerialNumber() const override; + + virtual already_AddRefed<BlobImpl> + CreateSlice(uint64_t aStart, + uint64_t aLength, + const nsAString& aContentType, + ErrorResult& aRv) override; + + virtual const nsTArray<RefPtr<BlobImpl>>* + GetSubBlobImpls() const override; + + virtual void + GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv) override; + + virtual int64_t + GetFileId() override; + + virtual nsresult + GetSendInfo(nsIInputStream** aBody, + uint64_t* aContentLength, + nsACString& aContentType, + nsACString& aCharset) override; + + virtual nsresult + GetMutable(bool* aMutable) const override; + + virtual nsresult + SetMutable(bool aMutable) override; + + virtual void + SetLazyData(const nsAString& aName, + const nsAString& aContentType, + uint64_t aLength, + int64_t aLastModifiedDate) override; + + virtual bool + IsMemoryFile() const override; + + virtual bool + IsSizeUnknown() const override; + + virtual bool + IsDateUnknown() const override; + + virtual bool + IsFile() const override; + + virtual bool + MayBeClonedToOtherThreads() const override; + + virtual BlobChild* + GetBlobChild() override; + + virtual BlobParent* + GetBlobParent() override; + +private: + ~RemoteBlobImpl() + { + MOZ_ASSERT_IF(mActorTarget, + EventTargetIsOnCurrentThread(mActorTarget)); + } + + void + Destroy(); +}; + +/******************************************************************************* + * BlobChild::RemoteBlobImpl + ******************************************************************************/ + +BlobChild:: +RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor, + BlobImpl* aRemoteBlobImpl, + const nsAString& aName, + const nsAString& aContentType, + const nsAString& aDOMPath, + uint64_t aLength, + int64_t aModDate, + BlobImplIsDirectory aIsDirectory, + bool aIsSameProcessBlob) + : BlobImplBase(aName, aContentType, aLength, aModDate) + , mWorkerPrivate(nullptr) + , mMutex("BlobChild::RemoteBlobImpl::mMutex") + , mIsSlice(false), mIsDirectory(aIsDirectory == eDirectory) +{ + SetDOMPath(aDOMPath); + + if (aIsSameProcessBlob) { + MOZ_ASSERT(aRemoteBlobImpl); + mSameProcessBlobImpl = aRemoteBlobImpl; + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + } else { + mDifferentProcessBlobImpl = aRemoteBlobImpl; + } + + CommonInit(aActor); +} + +BlobChild:: +RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor, + BlobImpl* aRemoteBlobImpl, + const nsAString& aContentType, + uint64_t aLength, + bool aIsSameProcessBlob) + : BlobImplBase(aContentType, aLength) + , mWorkerPrivate(nullptr) + , mMutex("BlobChild::RemoteBlobImpl::mMutex") + , mIsSlice(false), mIsDirectory(false) +{ + if (aIsSameProcessBlob) { + MOZ_ASSERT(aRemoteBlobImpl); + mSameProcessBlobImpl = aRemoteBlobImpl; + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + } else { + mDifferentProcessBlobImpl = aRemoteBlobImpl; + } + + CommonInit(aActor); +} + +BlobChild:: +RemoteBlobImpl::RemoteBlobImpl(BlobChild* aActor) + : BlobImplBase(EmptyString(), EmptyString(), UINT64_MAX, INT64_MAX) + , mWorkerPrivate(nullptr) + , mMutex("BlobChild::RemoteBlobImpl::mMutex") + , mIsSlice(false), mIsDirectory(false) +{ + CommonInit(aActor); +} + +BlobChild:: +RemoteBlobImpl::RemoteBlobImpl(const nsAString& aContentType, uint64_t aLength) + : BlobImplBase(aContentType, aLength) + , mActor(nullptr) + , mWorkerPrivate(nullptr) + , mMutex("BlobChild::RemoteBlobImpl::mMutex") + , mIsSlice(true) + , mIsDirectory(false) +{ + mImmutable = true; +} + +void +BlobChild:: +RemoteBlobImpl::CommonInit(BlobChild* aActor) +{ + MOZ_ASSERT(aActor); + aActor->AssertIsOnOwningThread(); + + mActor = aActor; + mActorTarget = aActor->EventTarget(); + + if (!NS_IsMainThread()) { + mWorkerPrivate = GetCurrentThreadWorkerPrivate(); + // We must comunicate via IPC in the owning thread, so, if this BlobImpl has + // been created on a Workerr and then it's sent to a different thread (for + // instance the main-thread), we still need to keep alive that Worker. + if (mWorkerPrivate) { + mWorkerHolder = new RemoteBlobImpl::WorkerHolder(this); + if (NS_WARN_IF(!mWorkerHolder->HoldWorker(mWorkerPrivate, Closing))) { + // We don't care too much if the worker is already going away because no + // sync-event-loop can be created at this point. + mWorkerPrivate = nullptr; + mWorkerHolder = nullptr; + } + } + } + + mImmutable = true; +} + +void +BlobChild:: +RemoteBlobImpl::NoteDyingActor() +{ + MOZ_ASSERT(mActor); + mActor->AssertIsOnOwningThread(); + + mActor = nullptr; +} + +BlobChild::RemoteBlobSliceImpl* +BlobChild:: +RemoteBlobImpl::AsSlice() const +{ + MOZ_ASSERT(IsSlice()); + + return static_cast<RemoteBlobSliceImpl*>(const_cast<RemoteBlobImpl*>(this)); +} + +BlobChild::RemoteBlobImpl* +BlobChild:: +RemoteBlobImpl::BaseRemoteBlobImpl() const +{ + if (IsSlice()) { + return AsSlice()->Parent()->BaseRemoteBlobImpl(); + } + + return const_cast<RemoteBlobImpl*>(this); +} + +void +BlobChild:: +RemoteBlobImpl::Destroy() +{ + if (EventTargetIsOnCurrentThread(mActorTarget)) { + if (mActor) { + mActor->AssertIsOnOwningThread(); + mActor->NoteDyingRemoteBlobImpl(); + } + + if (mWorkerHolder) { + // We are in the worker thread. + MutexAutoLock lock(mMutex); + mWorkerPrivate = nullptr; + mWorkerHolder = nullptr; + } + + delete this; + return; + } + + nsCOMPtr<nsIRunnable> destroyRunnable = + NewNonOwningRunnableMethod(this, &RemoteBlobImpl::Destroy); + + if (mActorTarget) { + destroyRunnable = + new CancelableRunnableWrapper(destroyRunnable, mActorTarget); + + MOZ_ALWAYS_SUCCEEDS(mActorTarget->Dispatch(destroyRunnable, + NS_DISPATCH_NORMAL)); + } else { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(destroyRunnable)); + } +} + +NS_IMPL_ADDREF(BlobChild::RemoteBlobImpl) +NS_IMPL_RELEASE_WITH_DESTROY(BlobChild::RemoteBlobImpl, Destroy()) +NS_IMPL_QUERY_INTERFACE_INHERITED(BlobChild::RemoteBlobImpl, + BlobImpl, + nsIRemoteBlob) + +void +BlobChild:: +RemoteBlobImpl::GetMozFullPathInternal(nsAString& aFilePath, + ErrorResult& aRv) const +{ + if (!EventTargetIsOnCurrentThread(mActorTarget)) { + MOZ_CRASH("Not implemented!"); + } + + if (mSameProcessBlobImpl) { + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + + mSameProcessBlobImpl->GetMozFullPathInternal(aFilePath, aRv); + return; + } + + if (!mActor) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + + nsString filePath; + if (!mActor->SendGetFilePath(&filePath)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + aFilePath = filePath; +} + +bool +BlobChild:: +RemoteBlobImpl::IsDirectory() const +{ + return mIsDirectory; +} + +already_AddRefed<BlobImpl> +BlobChild:: +RemoteBlobImpl::CreateSlice(uint64_t aStart, + uint64_t aLength, + const nsAString& aContentType, + ErrorResult& aRv) +{ + // May be called on any thread. + if (mSameProcessBlobImpl) { + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + + return mSameProcessBlobImpl->CreateSlice(aStart, + aLength, + aContentType, + aRv); + } + + RefPtr<RemoteBlobSliceImpl> slice = + new RemoteBlobSliceImpl(this, aStart, aLength, aContentType); + return slice.forget(); +} + +void +BlobChild:: +RemoteBlobImpl::GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv) +{ + // May be called on any thread. + if (mSameProcessBlobImpl) { + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + + nsCOMPtr<nsIInputStream> realStream; + mSameProcessBlobImpl->GetInternalStream(getter_AddRefs(realStream), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + RefPtr<BlobInputStreamTether> tether = + new BlobInputStreamTether(realStream, mSameProcessBlobImpl); + tether.forget(aStream); + return; + } + + RefPtr<CreateStreamHelper> helper = new CreateStreamHelper(this); + aRv = helper->GetStream(aStream); + if (NS_WARN_IF(aRv.Failed())) { + return; + } +} + +int64_t +BlobChild:: +RemoteBlobImpl::GetFileId() +{ + if (!EventTargetIsOnCurrentThread(mActorTarget)) { + MOZ_CRASH("Not implemented!"); + } + + if (mSameProcessBlobImpl) { + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + + return mSameProcessBlobImpl->GetFileId(); + } + + int64_t fileId; + if (mActor && mActor->SendGetFileId(&fileId)) { + return fileId; + } + + return -1; +} + +int64_t +BlobChild:: +RemoteBlobImpl::GetLastModified(ErrorResult& aRv) +{ + if (IsDateUnknown()) { + return 0; + } + + return mLastModificationDate; +} + +void +BlobChild:: +RemoteBlobImpl::SetLastModified(int64_t aLastModified) +{ + MOZ_CRASH("SetLastModified of a remote blob is not allowed!"); +} + +nsresult +BlobChild:: +RemoteBlobImpl::SetMutable(bool aMutable) +{ + if (!aMutable && IsSlice()) { + // Make sure that slices are backed by a real actor now while we are still + // on the correct thread. + AsSlice()->EnsureActorWasCreated(); + } + + nsresult rv = BlobImplBase::SetMutable(aMutable); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT_IF(!aMutable, mImmutable); + + return NS_OK; +} + +BlobChild* +BlobChild:: +RemoteBlobImpl::GetBlobChild() +{ + return mActor; +} + +BlobParent* +BlobChild:: +RemoteBlobImpl::GetBlobParent() +{ + return nullptr; +} + +class RemoteBlobControlRunnable : public WorkerControlRunnable +{ + nsCOMPtr<nsIRunnable> mRunnable; + +public: + RemoteBlobControlRunnable(WorkerPrivate* aWorkerPrivate, + nsIRunnable* aRunnable) + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) + , mRunnable(aRunnable) + { + MOZ_ASSERT(aRunnable); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + mRunnable->Run(); + return true; + } +}; + +nsresult +BlobChild:: +RemoteBlobImpl::DispatchToTarget(nsIRunnable* aRunnable) +{ + MOZ_ASSERT(aRunnable); + + // We have to protected mWorkerPrivate because this method can be called by + // any thread (sort of). + MutexAutoLock lock(mMutex); + + if (mWorkerPrivate) { + MOZ_ASSERT(mWorkerHolder); + + RefPtr<RemoteBlobControlRunnable> controlRunnable = + new RemoteBlobControlRunnable(mWorkerPrivate, aRunnable); + if (!controlRunnable->Dispatch()) { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + nsCOMPtr<nsIEventTarget> target = BaseRemoteBlobImpl()->GetActorEventTarget(); + if (!target) { + target = do_GetMainThread(); + } + + MOZ_ASSERT(target); + + return target->Dispatch(aRunnable, NS_DISPATCH_NORMAL); +} + +void +BlobChild:: +RemoteBlobImpl::WorkerHasNotified() +{ + MutexAutoLock lock(mMutex); + + mWorkerHolder->ReleaseWorker(); + + mWorkerHolder = nullptr; + mWorkerPrivate = nullptr; +} + +/******************************************************************************* + * BlobChild::RemoteBlobImpl::CreateStreamHelper + ******************************************************************************/ + +BlobChild::RemoteBlobImpl:: +CreateStreamHelper::CreateStreamHelper(RemoteBlobImpl* aRemoteBlobImpl) + : mMonitor("BlobChild::RemoteBlobImpl::CreateStreamHelper::mMonitor") + , mRemoteBlobImpl(aRemoteBlobImpl) + , mStart(aRemoteBlobImpl->IsSlice() ? aRemoteBlobImpl->AsSlice()->Start() : 0) + , mLength(0) + , mDone(false) +{ + // This may be created on any thread. + MOZ_ASSERT(aRemoteBlobImpl); + + ErrorResult rv; + const_cast<uint64_t&>(mLength) = aRemoteBlobImpl->GetSize(rv); + MOZ_ASSERT(!rv.Failed()); +} + +nsresult +BlobChild::RemoteBlobImpl:: +CreateStreamHelper::GetStream(nsIInputStream** aInputStream) +{ + // This may be called on any thread. + MOZ_ASSERT(aInputStream); + MOZ_ASSERT(mRemoteBlobImpl); + MOZ_ASSERT(!mInputStream); + MOZ_ASSERT(!mDone); + + RefPtr<RemoteBlobImpl> baseRemoteBlobImpl = + mRemoteBlobImpl->BaseRemoteBlobImpl(); + MOZ_ASSERT(baseRemoteBlobImpl); + + if (EventTargetIsOnCurrentThread(baseRemoteBlobImpl->GetActorEventTarget())) { + RunInternal(baseRemoteBlobImpl, false); + } else { + nsresult rv = baseRemoteBlobImpl->DispatchToTarget(this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + DebugOnly<bool> warned = false; + + { + MonitorAutoLock lock(mMonitor); + + while (!mDone) { +#ifdef DEBUG + if (!warned) { + NS_WARNING("RemoteBlobImpl::GetInternalStream() called on thread " + "that can't send messages, blocking here to wait for the " + "actor's thread to send the message!"); + } +#endif + lock.Wait(); + } + } + } + + MOZ_ASSERT(!mRemoteBlobImpl); + MOZ_ASSERT(mDone); + + if (!mInputStream) { + return NS_ERROR_UNEXPECTED; + } + + mInputStream.forget(aInputStream); + return NS_OK; +} + +void +BlobChild::RemoteBlobImpl:: +CreateStreamHelper::RunInternal(RemoteBlobImpl* aBaseRemoteBlobImpl, + bool aNotify) +{ + MOZ_ASSERT(aBaseRemoteBlobImpl); + MOZ_ASSERT(aBaseRemoteBlobImpl->ActorEventTargetIsOnCurrentThread()); + MOZ_ASSERT(!mInputStream); + MOZ_ASSERT(!mDone); + + if (BlobChild* actor = aBaseRemoteBlobImpl->GetActor()) { + RefPtr<RemoteInputStream> stream; + + if (!NS_IsMainThread() && GetCurrentThreadWorkerPrivate()) { + stream = + new RemoteInputStream(actor, mRemoteBlobImpl, mStart, mLength); + } else { + stream = new RemoteInputStream(mRemoteBlobImpl, mStart, mLength); + } + + InputStreamChild* streamActor = new InputStreamChild(stream); + if (actor->SendPBlobStreamConstructor(streamActor, mStart, mLength)) { + stream.swap(mInputStream); + } + } + + mRemoteBlobImpl = nullptr; + + if (aNotify) { + MonitorAutoLock lock(mMonitor); + mDone = true; + lock.Notify(); + } else { + mDone = true; + } +} + +NS_IMETHODIMP +BlobChild::RemoteBlobImpl:: +CreateStreamHelper::Run() +{ + MOZ_ASSERT(mRemoteBlobImpl); + MOZ_ASSERT(mRemoteBlobImpl->ActorEventTargetIsOnCurrentThread()); + + RefPtr<RemoteBlobImpl> baseRemoteBlobImpl = + mRemoteBlobImpl->BaseRemoteBlobImpl(); + MOZ_ASSERT(baseRemoteBlobImpl); + + RunInternal(baseRemoteBlobImpl, true); + return NS_OK; +} + +/******************************************************************************* + * BlobChild::RemoteBlobSliceImpl + ******************************************************************************/ + +BlobChild:: +RemoteBlobSliceImpl::RemoteBlobSliceImpl(RemoteBlobImpl* aParent, + uint64_t aStart, + uint64_t aLength, + const nsAString& aContentType) + : RemoteBlobImpl(aContentType, aLength) + , mParent(aParent->BaseRemoteBlobImpl()) + , mActorWasCreated(false) +{ + MOZ_ASSERT(mParent); + MOZ_ASSERT(mParent->BaseRemoteBlobImpl() == mParent); + + DebugOnly<bool> isMutable; + MOZ_ASSERT(NS_SUCCEEDED(aParent->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + +#ifdef DEBUG + { + ErrorResult rv; + uint64_t parentSize = aParent->GetSize(rv); + MOZ_ASSERT(!rv.Failed()); + MOZ_ASSERT(parentSize >= aStart + aLength); + } +#endif + + // Account for the offset of the parent slice, if any. + mStart = aParent->IsSlice() ? aParent->AsSlice()->mStart + aStart : aStart; +} + +void +BlobChild:: +RemoteBlobSliceImpl::EnsureActorWasCreatedInternal() +{ + MOZ_ASSERT(ActorEventTargetIsOnCurrentThread()); + MOZ_ASSERT(!mActorWasCreated); + + mActorWasCreated = true; + + BlobChild* baseActor = mParent->GetActor(); + MOZ_ASSERT(baseActor); + MOZ_ASSERT(baseActor->HasManager()); + + nsID id; + MOZ_ALWAYS_SUCCEEDS(gUUIDGenerator->GenerateUUIDInPlace(&id)); + + ParentBlobConstructorParams params( + SlicedBlobConstructorParams(nullptr /* sourceParent */, + baseActor /* sourceChild */, + id /* id */, + mStart /* begin */, + mStart + mLength /* end */, + mContentType /* contentType */)); + + BlobChild* actor; + + if (nsIContentChild* contentManager = baseActor->GetContentManager()) { + actor = SendSliceConstructor(contentManager, this, params); + } else { + actor = + SendSliceConstructor(baseActor->GetBackgroundManager(), this, params); + } + + CommonInit(actor); +} + +NS_IMPL_ISUPPORTS_INHERITED0(BlobChild::RemoteBlobSliceImpl, + BlobChild::RemoteBlobImpl) + +BlobChild* +BlobChild:: +RemoteBlobSliceImpl::GetBlobChild() +{ + EnsureActorWasCreated(); + + return RemoteBlobImpl::GetBlobChild(); +} + +/******************************************************************************* + * BlobParent::RemoteBlobImpl + ******************************************************************************/ + +BlobParent:: +RemoteBlobImpl::RemoteBlobImpl(BlobParent* aActor, BlobImpl* aBlobImpl) + : mActor(aActor) + , mActorTarget(aActor->EventTarget()) + , mBlobImpl(aBlobImpl) +{ + MOZ_ASSERT(aActor); + aActor->AssertIsOnOwningThread(); + MOZ_ASSERT(aBlobImpl); + + DebugOnly<bool> isMutable; + MOZ_ASSERT(NS_SUCCEEDED(aBlobImpl->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); +} + +void +BlobParent:: +RemoteBlobImpl::NoteDyingActor() +{ + MOZ_ASSERT(mActor); + mActor->AssertIsOnOwningThread(); + + mActor = nullptr; +} + +void +BlobParent:: +RemoteBlobImpl::Destroy() +{ + if (EventTargetIsOnCurrentThread(mActorTarget)) { + if (mActor) { + mActor->AssertIsOnOwningThread(); + mActor->NoteDyingRemoteBlobImpl(); + } + + delete this; + return; + } + + nsCOMPtr<nsIRunnable> destroyRunnable = + NewNonOwningRunnableMethod(this, &RemoteBlobImpl::Destroy); + + if (mActorTarget) { + destroyRunnable = + new CancelableRunnableWrapper(destroyRunnable, mActorTarget); + + MOZ_ALWAYS_SUCCEEDS(mActorTarget->Dispatch(destroyRunnable, + NS_DISPATCH_NORMAL)); + } else { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(destroyRunnable)); + } +} + +NS_IMPL_ADDREF(BlobParent::RemoteBlobImpl) +NS_IMPL_RELEASE_WITH_DESTROY(BlobParent::RemoteBlobImpl, Destroy()) +NS_IMPL_QUERY_INTERFACE_INHERITED(BlobParent::RemoteBlobImpl, + BlobImpl, + nsIRemoteBlob) + +void +BlobParent:: +RemoteBlobImpl::GetName(nsAString& aName) const +{ + mBlobImpl->GetName(aName); +} + +void +BlobParent:: +RemoteBlobImpl::GetDOMPath(nsAString& aPath) const +{ + mBlobImpl->GetDOMPath(aPath); +} + +void +BlobParent:: +RemoteBlobImpl::SetDOMPath(const nsAString& aPath) +{ + mBlobImpl->SetDOMPath(aPath); +} + +int64_t +BlobParent:: +RemoteBlobImpl::GetLastModified(ErrorResult& aRv) +{ + return mBlobImpl->GetLastModified(aRv); +} + +void +BlobParent:: +RemoteBlobImpl::SetLastModified(int64_t aLastModified) +{ + MOZ_CRASH("SetLastModified of a remote blob is not allowed!"); +} + +void +BlobParent:: +RemoteBlobImpl::GetMozFullPath(nsAString& aName, ErrorResult& aRv) const +{ + mBlobImpl->GetMozFullPath(aName, aRv); +} + +void +BlobParent:: +RemoteBlobImpl::GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const +{ + mBlobImpl->GetMozFullPathInternal(aFileName, aRv); +} + +bool +BlobParent:: +RemoteBlobImpl::IsDirectory() const +{ + return mBlobImpl->IsDirectory(); +} + +uint64_t +BlobParent:: +RemoteBlobImpl::GetSize(ErrorResult& aRv) +{ + return mBlobImpl->GetSize(aRv); +} + +void +BlobParent:: +RemoteBlobImpl::GetType(nsAString& aType) +{ + mBlobImpl->GetType(aType); +} + +uint64_t +BlobParent:: +RemoteBlobImpl::GetSerialNumber() const +{ + return mBlobImpl->GetSerialNumber(); +} + +already_AddRefed<BlobImpl> +BlobParent:: +RemoteBlobImpl::CreateSlice(uint64_t aStart, + uint64_t aLength, + const nsAString& aContentType, + ErrorResult& aRv) +{ + return mBlobImpl->CreateSlice(aStart, aLength, aContentType, aRv); +} + +const nsTArray<RefPtr<BlobImpl>>* +BlobParent:: +RemoteBlobImpl::GetSubBlobImpls() const +{ + return mBlobImpl->GetSubBlobImpls(); +} + +void +BlobParent:: +RemoteBlobImpl::GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv) +{ + mBlobImpl->GetInternalStream(aStream, aRv); +} + +int64_t +BlobParent:: +RemoteBlobImpl::GetFileId() +{ + return mBlobImpl->GetFileId(); +} + +nsresult +BlobParent:: +RemoteBlobImpl::GetSendInfo(nsIInputStream** aBody, + uint64_t* aContentLength, + nsACString& aContentType, + nsACString& aCharset) +{ + return mBlobImpl->GetSendInfo(aBody, + aContentLength, + aContentType, + aCharset); +} + +nsresult +BlobParent:: +RemoteBlobImpl::GetMutable(bool* aMutable) const +{ + return mBlobImpl->GetMutable(aMutable); +} + +nsresult +BlobParent:: +RemoteBlobImpl::SetMutable(bool aMutable) +{ + return mBlobImpl->SetMutable(aMutable); +} + +void +BlobParent:: +RemoteBlobImpl::SetLazyData(const nsAString& aName, + const nsAString& aContentType, + uint64_t aLength, + int64_t aLastModifiedDate) +{ + MOZ_CRASH("This should never be called!"); +} + +bool +BlobParent:: +RemoteBlobImpl::IsMemoryFile() const +{ + return mBlobImpl->IsMemoryFile(); +} + +bool +BlobParent:: +RemoteBlobImpl::IsSizeUnknown() const +{ + return mBlobImpl->IsSizeUnknown(); +} + +bool +BlobParent:: +RemoteBlobImpl::IsDateUnknown() const +{ + return mBlobImpl->IsDateUnknown(); +} + +bool +BlobParent:: +RemoteBlobImpl::IsFile() const +{ + return mBlobImpl->IsFile(); +} + +bool +BlobParent:: +RemoteBlobImpl::MayBeClonedToOtherThreads() const +{ + return mBlobImpl->MayBeClonedToOtherThreads(); +} + +BlobChild* +BlobParent:: +RemoteBlobImpl::GetBlobChild() +{ + return nullptr; +} + +BlobParent* +BlobParent:: +RemoteBlobImpl::GetBlobParent() +{ + return mActor; +} + +/******************************************************************************* + * BlobChild + ******************************************************************************/ + +BlobChild::BlobChild(nsIContentChild* aManager, BlobImpl* aBlobImpl) + : mBackgroundManager(nullptr) + , mContentManager(aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + CommonInit(aBlobImpl); +} + +BlobChild::BlobChild(PBackgroundChild* aManager, BlobImpl* aBlobImpl) + : mBackgroundManager(aManager) + , mContentManager(nullptr) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + if (!NS_IsMainThread()) { + mEventTarget = do_GetCurrentThread(); + MOZ_ASSERT(mEventTarget); + } + + CommonInit(aBlobImpl); +} + +BlobChild::BlobChild(nsIContentChild* aManager, BlobChild* aOther) + : mBackgroundManager(nullptr) + , mContentManager(aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + CommonInit(aOther, /* aBlobImpl */ nullptr); +} + +BlobChild::BlobChild(PBackgroundChild* aManager, + BlobChild* aOther, + BlobImpl* aBlobImpl) + : mBackgroundManager(aManager) + , mContentManager(nullptr) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + MOZ_ASSERT(aBlobImpl); + + if (!NS_IsMainThread()) { + mEventTarget = do_GetCurrentThread(); + MOZ_ASSERT(mEventTarget); + } + + CommonInit(aOther, aBlobImpl); +} + +BlobChild::BlobChild(nsIContentChild* aManager, + const ChildBlobConstructorParams& aParams) + : mBackgroundManager(nullptr) + , mContentManager(aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + CommonInit(aParams); +} + +BlobChild::BlobChild(PBackgroundChild* aManager, + const ChildBlobConstructorParams& aParams) + : mBackgroundManager(aManager) + , mContentManager(nullptr) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + if (!NS_IsMainThread()) { + mEventTarget = do_GetCurrentThread(); + MOZ_ASSERT(mEventTarget); + } + + CommonInit(aParams); +} + +BlobChild::BlobChild(nsIContentChild* aManager, + const nsID& aParentID, + RemoteBlobSliceImpl* aRemoteBlobSliceImpl) + : mBackgroundManager(nullptr) + , mContentManager(aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + CommonInit(aParentID, aRemoteBlobSliceImpl); +} + +BlobChild::BlobChild(PBackgroundChild* aManager, + const nsID& aParentID, + RemoteBlobSliceImpl* aRemoteBlobSliceImpl) + : mBackgroundManager(aManager) + , mContentManager(nullptr) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + if (!NS_IsMainThread()) { + mEventTarget = do_GetCurrentThread(); + MOZ_ASSERT(mEventTarget); + } + + CommonInit(aParentID, aRemoteBlobSliceImpl); +} + +BlobChild::~BlobChild() +{ + AssertIsOnOwningThread(); + + MOZ_COUNT_DTOR(BlobChild); +} + +void +BlobChild::CommonInit(BlobImpl* aBlobImpl) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aBlobImpl); + + MOZ_COUNT_CTOR(BlobChild); + + mBlobImpl = aBlobImpl; + mRemoteBlobImpl = nullptr; + + mBlobImpl->AddRef(); + mOwnsBlobImpl = true; + + memset(&mParentID, 0, sizeof(mParentID)); +} + +void +BlobChild::CommonInit(BlobChild* aOther, BlobImpl* aBlobImpl) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aOther); + MOZ_ASSERT_IF(mContentManager, aOther->GetBackgroundManager()); + MOZ_ASSERT_IF(mContentManager, !aBlobImpl); + MOZ_ASSERT_IF(mBackgroundManager, aBlobImpl); + + RefPtr<BlobImpl> otherImpl; + if (mBackgroundManager && aOther->GetBackgroundManager()) { + otherImpl = aBlobImpl; + } else { + otherImpl = aOther->GetBlobImpl(); + } + MOZ_ASSERT(otherImpl); + + nsString contentType; + otherImpl->GetType(contentType); + + ErrorResult rv; + uint64_t length = otherImpl->GetSize(rv); + MOZ_ASSERT(!rv.Failed()); + + RemoteBlobImpl* remoteBlob = nullptr; + if (otherImpl->IsFile()) { + nsAutoString name; + otherImpl->GetName(name); + + nsAutoString domPath; + otherImpl->GetDOMPath(domPath); + + int64_t modDate = otherImpl->GetLastModified(rv); + MOZ_ASSERT(!rv.Failed()); + + RemoteBlobImpl::BlobImplIsDirectory directory = otherImpl->IsDirectory() ? + RemoteBlobImpl::BlobImplIsDirectory::eDirectory : + RemoteBlobImpl::BlobImplIsDirectory::eNotDirectory; + + remoteBlob = + new RemoteBlobImpl(this, otherImpl, name, contentType, domPath, + length, modDate, directory, + false /* SameProcessBlobImpl */); + } else { + remoteBlob = new RemoteBlobImpl(this, otherImpl, contentType, length, + false /* SameProcessBlobImpl */); + } + + // This RemoteBlob must be kept alive untill RecvCreatedFromKnownBlob is + // called because the parent will send this notification and we must be able + // to manage it. + MOZ_ASSERT(remoteBlob); + remoteBlob->AddRef(); + + CommonInit(aOther->ParentID(), remoteBlob); +} + +void +BlobChild::CommonInit(const ChildBlobConstructorParams& aParams) +{ + AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(BlobChild); + + const AnyBlobConstructorParams& blobParams = aParams.blobParams(); + + AnyBlobConstructorParams::Type paramsType = blobParams.type(); + MOZ_ASSERT(paramsType != AnyBlobConstructorParams::T__None && + paramsType != + AnyBlobConstructorParams::TSlicedBlobConstructorParams && + paramsType != + AnyBlobConstructorParams::TKnownBlobConstructorParams); + + RefPtr<RemoteBlobImpl> remoteBlob; + + switch (paramsType) { + case AnyBlobConstructorParams::TNormalBlobConstructorParams: { + const NormalBlobConstructorParams& params = + blobParams.get_NormalBlobConstructorParams(); + remoteBlob = + new RemoteBlobImpl(this, nullptr, params.contentType(), params.length(), + false /* SameProcessBlobImpl */); + break; + } + + case AnyBlobConstructorParams::TFileBlobConstructorParams: { + const FileBlobConstructorParams& params = + blobParams.get_FileBlobConstructorParams(); + RemoteBlobImpl::BlobImplIsDirectory directory = params.isDirectory() ? + RemoteBlobImpl::BlobImplIsDirectory::eDirectory : + RemoteBlobImpl::BlobImplIsDirectory::eNotDirectory; + remoteBlob = new RemoteBlobImpl(this, + nullptr, + params.name(), + params.contentType(), + params.path(), + params.length(), + params.modDate(), + directory, + false /* SameProcessBlobImpl */); + break; + } + + case AnyBlobConstructorParams::TSameProcessBlobConstructorParams: { + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + + const SameProcessBlobConstructorParams& params = + blobParams.get_SameProcessBlobConstructorParams(); + MOZ_ASSERT(params.addRefedBlobImpl()); + + RefPtr<BlobImpl> blobImpl = + dont_AddRef(reinterpret_cast<BlobImpl*>(params.addRefedBlobImpl())); + + ErrorResult rv; + uint64_t size = blobImpl->GetSize(rv); + MOZ_ASSERT(!rv.Failed()); + + nsString contentType; + blobImpl->GetType(contentType); + + if (blobImpl->IsFile()) { + nsAutoString name; + blobImpl->GetName(name); + + nsAutoString domPath; + blobImpl->GetDOMPath(domPath); + + int64_t lastModifiedDate = blobImpl->GetLastModified(rv); + MOZ_ASSERT(!rv.Failed()); + + RemoteBlobImpl::BlobImplIsDirectory directory = + blobImpl->IsDirectory() ? + RemoteBlobImpl::BlobImplIsDirectory::eDirectory : + RemoteBlobImpl::BlobImplIsDirectory::eNotDirectory; + + remoteBlob = + new RemoteBlobImpl(this, + blobImpl, + name, + contentType, + domPath, + size, + lastModifiedDate, + directory, + true /* SameProcessBlobImpl */); + } else { + remoteBlob = new RemoteBlobImpl(this, blobImpl, contentType, size, + true /* SameProcessBlobImpl */); + } + + break; + } + + case AnyBlobConstructorParams::TMysteryBlobConstructorParams: { + remoteBlob = new RemoteBlobImpl(this); + break; + } + + default: + MOZ_CRASH("Unknown params!"); + } + + MOZ_ASSERT(remoteBlob); + + DebugOnly<bool> isMutable; + MOZ_ASSERT(NS_SUCCEEDED(remoteBlob->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + + mRemoteBlobImpl = remoteBlob; + + remoteBlob.forget(&mBlobImpl); + mOwnsBlobImpl = true; + + mParentID = aParams.id(); +} + +void +BlobChild::CommonInit(const nsID& aParentID, RemoteBlobImpl* aRemoteBlobImpl) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aRemoteBlobImpl); + + DebugOnly<bool> isMutable; + MOZ_ASSERT(NS_SUCCEEDED(aRemoteBlobImpl->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + + MOZ_COUNT_CTOR(BlobChild); + + RefPtr<RemoteBlobImpl> remoteBlob = aRemoteBlobImpl; + + mRemoteBlobImpl = remoteBlob; + + remoteBlob.forget(&mBlobImpl); + mOwnsBlobImpl = true; + + mParentID = aParentID; +} + +#ifdef DEBUG + +void +BlobChild::AssertIsOnOwningThread() const +{ + MOZ_ASSERT(IsOnOwningThread()); +} + +#endif // DEBUG + +// static +void +BlobChild::Startup(const FriendKey& /* aKey */) +{ + MOZ_ASSERT(!XRE_IsParentProcess()); + + CommonStartup(); +} + +// static +BlobChild* +BlobChild::GetOrCreate(nsIContentChild* aManager, BlobImpl* aBlobImpl) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return GetOrCreateFromImpl(aManager, aBlobImpl); +} + +// static +BlobChild* +BlobChild::GetOrCreate(PBackgroundChild* aManager, BlobImpl* aBlobImpl) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return GetOrCreateFromImpl(aManager, aBlobImpl); +} + +// static +BlobChild* +BlobChild::Create(nsIContentChild* aManager, + const ChildBlobConstructorParams& aParams) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return CreateFromParams(aManager, aParams); +} + +// static +BlobChild* +BlobChild::Create(PBackgroundChild* aManager, + const ChildBlobConstructorParams& aParams) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return CreateFromParams(aManager, aParams); +} + +// static +template <class ChildManagerType> +BlobChild* +BlobChild::GetOrCreateFromImpl(ChildManagerType* aManager, + BlobImpl* aBlobImpl) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + MOZ_ASSERT(aBlobImpl); + + // If the blob represents a wrapper around real blob implementation (so called + // snapshot) then we need to get the real one. + if (nsCOMPtr<PIBlobImplSnapshot> snapshot = do_QueryInterface(aBlobImpl)) { + aBlobImpl = snapshot->GetBlobImpl(); + if (!aBlobImpl) { + // The snapshot is not valid anymore. + return nullptr; + } + } + + // If the blob represents a remote blob then we can simply pass its actor back + // here. + if (nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryInterface(aBlobImpl)) { + BlobChild* actor = + MaybeGetActorFromRemoteBlob(remoteBlob, aManager, aBlobImpl); + if (actor) { + return actor; + } + } + + // All blobs shared between threads or processes must be immutable. + if (NS_WARN_IF(NS_FAILED(aBlobImpl->SetMutable(false)))) { + return nullptr; + } + + MOZ_ASSERT(!aBlobImpl->IsSizeUnknown()); + MOZ_ASSERT(!aBlobImpl->IsDateUnknown()); + + AnyBlobConstructorParams blobParams; + nsTArray<UniquePtr<AutoIPCStream>> autoIPCStreams; + + if (gProcessType == GeckoProcessType_Default) { + RefPtr<BlobImpl> sameProcessImpl = aBlobImpl; + auto addRefedBlobImpl = + reinterpret_cast<intptr_t>(sameProcessImpl.forget().take()); + + blobParams = SameProcessBlobConstructorParams(addRefedBlobImpl); + } else { + // BlobData is going to be populate here and it _must_ be send via IPC in + // order to avoid leaks. + BlobData blobData; + BlobDataFromBlobImpl(aManager, aBlobImpl, blobData, autoIPCStreams); + + nsString contentType; + aBlobImpl->GetType(contentType); + + ErrorResult rv; + uint64_t length = aBlobImpl->GetSize(rv); + MOZ_ASSERT(!rv.Failed()); + + if (aBlobImpl->IsFile()) { + nsAutoString name; + aBlobImpl->GetName(name); + + nsAutoString domPath; + aBlobImpl->GetDOMPath(domPath); + + int64_t modDate = aBlobImpl->GetLastModified(rv); + MOZ_ASSERT(!rv.Failed()); + + blobParams = + FileBlobConstructorParams(name, contentType, domPath, length, modDate, + aBlobImpl->IsDirectory(), blobData); + } else { + blobParams = NormalBlobConstructorParams(contentType, length, blobData); + } + } + + BlobChild* actor = new BlobChild(aManager, aBlobImpl); + + ParentBlobConstructorParams params(blobParams); + + if (NS_WARN_IF(!aManager->SendPBlobConstructor(actor, params))) { + return nullptr; + } + + autoIPCStreams.Clear(); + return actor; +} + +// static +template <class ChildManagerType> +BlobChild* +BlobChild::CreateFromParams(ChildManagerType* aManager, + const ChildBlobConstructorParams& aParams) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + const AnyBlobConstructorParams& blobParams = aParams.blobParams(); + + switch (blobParams.type()) { + case AnyBlobConstructorParams::TNormalBlobConstructorParams: + case AnyBlobConstructorParams::TFileBlobConstructorParams: + case AnyBlobConstructorParams::TSameProcessBlobConstructorParams: + case AnyBlobConstructorParams::TMysteryBlobConstructorParams: { + return new BlobChild(aManager, aParams); + } + + case AnyBlobConstructorParams::TSlicedBlobConstructorParams: { + MOZ_CRASH("Parent should never send SlicedBlobConstructorParams!"); + } + + case AnyBlobConstructorParams::TKnownBlobConstructorParams: { + MOZ_CRASH("Parent should never send KnownBlobConstructorParams!"); + } + + default: + MOZ_CRASH("Unknown params!"); + } + + MOZ_CRASH("Should never get here!"); +} + +// static +template <class ChildManagerType> +BlobChild* +BlobChild::SendSliceConstructor(ChildManagerType* aManager, + RemoteBlobSliceImpl* aRemoteBlobSliceImpl, + const ParentBlobConstructorParams& aParams) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + MOZ_ASSERT(aRemoteBlobSliceImpl); + MOZ_ASSERT(aParams.blobParams().type() == + AnyBlobConstructorParams::TSlicedBlobConstructorParams); + + const nsID& id = aParams.blobParams().get_SlicedBlobConstructorParams().id(); + + BlobChild* newActor = new BlobChild(aManager, id, aRemoteBlobSliceImpl); + + if (aManager->SendPBlobConstructor(newActor, aParams)) { + if (gProcessType != GeckoProcessType_Default || !NS_IsMainThread()) { + newActor->SendWaitForSliceCreation(); + } + return newActor; + } + + return nullptr; +} + +// static +BlobChild* +BlobChild::MaybeGetActorFromRemoteBlob(nsIRemoteBlob* aRemoteBlob, + nsIContentChild* aManager, + BlobImpl* aBlobImpl) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aRemoteBlob); + MOZ_ASSERT(aManager); + MOZ_ASSERT(aBlobImpl); + + if (BlobChild* actor = aRemoteBlob->GetBlobChild()) { + if (actor->GetContentManager() == aManager) { + return actor; + } + + MOZ_ASSERT(actor->GetBackgroundManager()); + + actor = new BlobChild(aManager, actor); + + ParentBlobConstructorParams params( + KnownBlobConstructorParams(actor->ParentID())); + + aManager->SendPBlobConstructor(actor, params); + + return actor; + } + + return nullptr; +} + +// static +BlobChild* +BlobChild::MaybeGetActorFromRemoteBlob(nsIRemoteBlob* aRemoteBlob, + PBackgroundChild* aManager, + BlobImpl* aBlobImpl) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aRemoteBlob); + MOZ_ASSERT(aManager); + MOZ_ASSERT(aBlobImpl); + + if (BlobChild* actor = aRemoteBlob->GetBlobChild()) { + if (actor->GetBackgroundManager() == aManager) { + return actor; + } + + actor = new BlobChild(aManager, actor, aBlobImpl); + + ParentBlobConstructorParams params( + KnownBlobConstructorParams(actor->ParentID())); + + aManager->SendPBlobConstructor(actor, params); + + return actor; + } + + return nullptr; +} + +const nsID& +BlobChild::ParentID() const +{ + MOZ_ASSERT(mRemoteBlobImpl); + + return mParentID; +} + +already_AddRefed<BlobImpl> +BlobChild::GetBlobImpl() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mBlobImpl); + + RefPtr<BlobImpl> blobImpl; + + // Remote blobs are held alive until the first call to GetBlobImpl. Thereafter + // we only hold a weak reference. Normal blobs are held alive until the actor + // is destroyed. + if (mRemoteBlobImpl && mOwnsBlobImpl) { + blobImpl = dont_AddRef(mBlobImpl); + mOwnsBlobImpl = false; + } else { + blobImpl = mBlobImpl; + } + + MOZ_ASSERT(blobImpl); + + return blobImpl.forget(); +} + +bool +BlobChild::SetMysteryBlobInfo(const nsString& aName, + const nsString& aContentType, + uint64_t aLength, + int64_t aLastModifiedDate) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mBlobImpl); + MOZ_ASSERT(!mBlobImpl->IsDirectory()); + MOZ_ASSERT(mRemoteBlobImpl); + MOZ_ASSERT(!mRemoteBlobImpl->IsDirectory()); + MOZ_ASSERT(aLastModifiedDate != INT64_MAX); + + mBlobImpl->SetLazyData(aName, aContentType, aLength, aLastModifiedDate); + + FileBlobConstructorParams params(aName, + aContentType, + EmptyString(), + aLength, + aLastModifiedDate, + mBlobImpl->IsDirectory(), + void_t() /* optionalBlobData */); + return SendResolveMystery(params); +} + +bool +BlobChild::SetMysteryBlobInfo(const nsString& aContentType, uint64_t aLength) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mBlobImpl); + MOZ_ASSERT(mRemoteBlobImpl); + + mBlobImpl->SetLazyData(NullString(), aContentType, aLength, INT64_MAX); + + NormalBlobConstructorParams params(aContentType, + aLength, + void_t() /* optionalBlobData */); + return SendResolveMystery(params); +} + +void +BlobChild::NoteDyingRemoteBlobImpl() +{ + MOZ_ASSERT(mBlobImpl); + MOZ_ASSERT(mRemoteBlobImpl); + MOZ_ASSERT(!mOwnsBlobImpl); + + // This may be called on any thread due to the fact that RemoteBlobImpl is + // designed to be passed between threads. We must start the shutdown process + // on the owning thread, so we proxy here if necessary. + if (!IsOnOwningThread()) { + nsCOMPtr<nsIRunnable> runnable = + NewNonOwningRunnableMethod(this, &BlobChild::NoteDyingRemoteBlobImpl); + + if (mEventTarget) { + runnable = new CancelableRunnableWrapper(runnable, mEventTarget); + + MOZ_ALWAYS_SUCCEEDS(mEventTarget->Dispatch(runnable, + NS_DISPATCH_NORMAL)); + } else { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable)); + } + + return; + } + + // Must do this before calling Send__delete__ or we'll crash there trying to + // access a dangling pointer. + mBlobImpl = nullptr; + mRemoteBlobImpl = nullptr; + + PBlobChild::Send__delete__(this); +} + +bool +BlobChild::IsOnOwningThread() const +{ + return EventTargetIsOnCurrentThread(mEventTarget); +} + +void +BlobChild::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnOwningThread(); + + if (mRemoteBlobImpl) { + mRemoteBlobImpl->NoteDyingActor(); + } + + if (mBlobImpl && mOwnsBlobImpl) { + mBlobImpl->Release(); + } + +#ifdef DEBUG + mBlobImpl = nullptr; + mRemoteBlobImpl = nullptr; + mBackgroundManager = nullptr; + mContentManager = nullptr; + mOwnsBlobImpl = false; +#endif +} + +PBlobStreamChild* +BlobChild::AllocPBlobStreamChild(const uint64_t& aStart, + const uint64_t& aLength) +{ + AssertIsOnOwningThread(); + + return new InputStreamChild(); +} + +bool +BlobChild::DeallocPBlobStreamChild(PBlobStreamChild* aActor) +{ + AssertIsOnOwningThread(); + + delete static_cast<InputStreamChild*>(aActor); + return true; +} + +bool +BlobChild::RecvCreatedFromKnownBlob() +{ + MOZ_ASSERT(mRemoteBlobImpl); + + // Releasing the other blob now that this blob is fully created. + mRemoteBlobImpl->NullifyDifferentProcessBlobImpl(); + + // Release the additional reference to ourself that was added in order to + // receive this RecvCreatedFromKnownBlob. + mRemoteBlobImpl->Release(); + return true; +} + +/******************************************************************************* + * BlobParent + ******************************************************************************/ + +BlobParent::BlobParent(nsIContentParent* aManager, IDTableEntry* aIDTableEntry) + : mBackgroundManager(nullptr) + , mContentManager(aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + CommonInit(aIDTableEntry); +} + +BlobParent::BlobParent(PBackgroundParent* aManager, IDTableEntry* aIDTableEntry) + : mBackgroundManager(aManager) + , mContentManager(nullptr) + , mEventTarget(do_GetCurrentThread()) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mEventTarget); + + CommonInit(aIDTableEntry); +} + +BlobParent::BlobParent(nsIContentParent* aManager, + BlobImpl* aBlobImpl, + IDTableEntry* aIDTableEntry) + : mBackgroundManager(nullptr) + , mContentManager(aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + CommonInit(aBlobImpl, aIDTableEntry); +} + +BlobParent::BlobParent(PBackgroundParent* aManager, + BlobImpl* aBlobImpl, + IDTableEntry* aIDTableEntry) + : mBackgroundManager(aManager) + , mContentManager(nullptr) + , mEventTarget(do_GetCurrentThread()) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mEventTarget); + + CommonInit(aBlobImpl, aIDTableEntry); +} + +BlobParent::~BlobParent() +{ + AssertIsOnOwningThread(); + + MOZ_COUNT_DTOR(BlobParent); +} + +void +BlobParent::CommonInit(IDTableEntry* aIDTableEntry) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aIDTableEntry); + MOZ_ASSERT(aIDTableEntry->GetBlobImpl()); + + MOZ_COUNT_CTOR(BlobParent); + + mBlobImpl = aIDTableEntry->GetBlobImpl(); + mRemoteBlobImpl = nullptr; + + mBlobImpl->AddRef(); + mOwnsBlobImpl = true; + + mIDTableEntry = aIDTableEntry; +} + +void +BlobParent::CommonInit(BlobImpl* aBlobImpl, IDTableEntry* aIDTableEntry) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aBlobImpl); + MOZ_ASSERT(aIDTableEntry); + + MOZ_COUNT_CTOR(BlobParent); + + DebugOnly<bool> isMutable; + MOZ_ASSERT(NS_SUCCEEDED(aBlobImpl->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + + RefPtr<RemoteBlobImpl> remoteBlobImpl = new RemoteBlobImpl(this, aBlobImpl); + + MOZ_ASSERT(NS_SUCCEEDED(remoteBlobImpl->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + + mRemoteBlobImpl = remoteBlobImpl; + + remoteBlobImpl.forget(&mBlobImpl); + mOwnsBlobImpl = true; + + mIDTableEntry = aIDTableEntry; +} + +#ifdef DEBUG + +void +BlobParent::AssertIsOnOwningThread() const +{ + MOZ_ASSERT(IsOnOwningThread()); +} + +#endif // DEBUG + +// static +void +BlobParent::Startup(const FriendKey& /* aKey */) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + CommonStartup(); + + ClearOnShutdown(&sIDTable); + + sIDTableMutex = new Mutex("BlobParent::sIDTableMutex"); + ClearOnShutdown(&sIDTableMutex); +} + +// static +BlobParent* +BlobParent::GetOrCreate(nsIContentParent* aManager, BlobImpl* aBlobImpl) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return GetOrCreateFromImpl(aManager, aBlobImpl); +} + +// static +BlobParent* +BlobParent::GetOrCreate(PBackgroundParent* aManager, BlobImpl* aBlobImpl) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return GetOrCreateFromImpl(aManager, aBlobImpl); +} + +// static +BlobParent* +BlobParent::Create(nsIContentParent* aManager, + const ParentBlobConstructorParams& aParams) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return CreateFromParams(aManager, aParams); +} + +// static +BlobParent* +BlobParent::Create(PBackgroundParent* aManager, + const ParentBlobConstructorParams& aParams) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + return CreateFromParams(aManager, aParams); +} + +// static +already_AddRefed<BlobImpl> +BlobParent::GetBlobImplForID(const nsID& aID) +{ + if (NS_WARN_IF(gProcessType != GeckoProcessType_Default)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + RefPtr<IDTableEntry> idTableEntry = IDTableEntry::Get(aID); + if (NS_WARN_IF(!idTableEntry)) { + return nullptr; + } + + RefPtr<BlobImpl> blobImpl = idTableEntry->GetBlobImpl(); + MOZ_ASSERT(blobImpl); + + return blobImpl.forget(); +} + +// static +template <class ParentManagerType> +BlobParent* +BlobParent::GetOrCreateFromImpl(ParentManagerType* aManager, + BlobImpl* aBlobImpl) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + MOZ_ASSERT(aBlobImpl); + + MOZ_ASSERT(!nsCOMPtr<PIBlobImplSnapshot>(do_QueryInterface(aBlobImpl))); + + // If the blob represents a remote blob for this manager then we can simply + // pass its actor back here. + if (nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryInterface(aBlobImpl)) { + BlobParent* actor = MaybeGetActorFromRemoteBlob(remoteBlob, aManager); + if (actor) { + return actor; + } + } + + // All blobs shared between threads or processes must be immutable. + if (NS_WARN_IF(NS_FAILED(aBlobImpl->SetMutable(false)))) { + return nullptr; + } + + AnyBlobConstructorParams blobParams; + + if (ActorManagerIsSameProcess(aManager)) { + RefPtr<BlobImpl> sameProcessImpl = aBlobImpl; + auto addRefedBlobImpl = + reinterpret_cast<intptr_t>(sameProcessImpl.forget().take()); + + blobParams = SameProcessBlobConstructorParams(addRefedBlobImpl); + } else { + if (aBlobImpl->IsSizeUnknown() || aBlobImpl->IsDateUnknown()) { + // We don't want to call GetSize or GetLastModifiedDate yet since that may + // stat a file on the this thread. Instead we'll learn the size lazily + // from the other side. + blobParams = MysteryBlobConstructorParams(); + } else { + nsString contentType; + aBlobImpl->GetType(contentType); + + ErrorResult rv; + uint64_t length = aBlobImpl->GetSize(rv); + MOZ_ASSERT(!rv.Failed()); + + if (aBlobImpl->IsFile()) { + nsAutoString name; + aBlobImpl->GetName(name); + + nsAutoString domPath; + aBlobImpl->GetDOMPath(domPath); + + int64_t modDate = aBlobImpl->GetLastModified(rv); + MOZ_ASSERT(!rv.Failed()); + + blobParams = + FileBlobConstructorParams(name, contentType, domPath, length, modDate, + aBlobImpl->IsDirectory(), void_t()); + } else { + blobParams = NormalBlobConstructorParams(contentType, length, void_t()); + } + } + } + + nsID id; + MOZ_ALWAYS_SUCCEEDS(gUUIDGenerator->GenerateUUIDInPlace(&id)); + + RefPtr<IDTableEntry> idTableEntry = + IDTableEntry::GetOrCreate(id, ActorManagerProcessID(aManager), aBlobImpl); + MOZ_ASSERT(idTableEntry); + + BlobParent* actor = new BlobParent(aManager, idTableEntry); + + ChildBlobConstructorParams params(id, blobParams); + if (NS_WARN_IF(!aManager->SendPBlobConstructor(actor, params))) { + return nullptr; + } + + return actor; +} + +// static +template <class ParentManagerType> +BlobParent* +BlobParent::CreateFromParams(ParentManagerType* aManager, + const ParentBlobConstructorParams& aParams) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + const AnyBlobConstructorParams& blobParams = aParams.blobParams(); + + switch (blobParams.type()) { + case AnyBlobConstructorParams::TMysteryBlobConstructorParams: { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + case AnyBlobConstructorParams::TNormalBlobConstructorParams: + case AnyBlobConstructorParams::TFileBlobConstructorParams: { + const OptionalBlobData& optionalBlobData = + blobParams.type() == + AnyBlobConstructorParams::TNormalBlobConstructorParams ? + blobParams.get_NormalBlobConstructorParams().optionalBlobData() : + blobParams.get_FileBlobConstructorParams().optionalBlobData(); + + if (NS_WARN_IF(optionalBlobData.type() != OptionalBlobData::TBlobData)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + RefPtr<BlobImpl> blobImpl = + CreateBlobImpl(aParams, + optionalBlobData.get_BlobData(), + ActorManagerIsSameProcess(aManager)); + if (NS_WARN_IF(!blobImpl)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + nsID id; + MOZ_ALWAYS_SUCCEEDS(gUUIDGenerator->GenerateUUIDInPlace(&id)); + + RefPtr<IDTableEntry> idTableEntry = + IDTableEntry::Create(id, ActorManagerProcessID(aManager), blobImpl); + if (NS_WARN_IF(!idTableEntry)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + return new BlobParent(aManager, blobImpl, idTableEntry); + } + + case AnyBlobConstructorParams::TSlicedBlobConstructorParams: { + const SlicedBlobConstructorParams& params = + blobParams.get_SlicedBlobConstructorParams(); + + if (NS_WARN_IF(params.end() < params.begin())) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + auto* actor = + const_cast<BlobParent*>( + static_cast<const BlobParent*>(params.sourceParent())); + MOZ_ASSERT(actor); + + RefPtr<BlobImpl> source = actor->GetBlobImpl(); + MOZ_ASSERT(source); + + ErrorResult rv; + RefPtr<BlobImpl> slice = + source->CreateSlice(params.begin(), + params.end() - params.begin(), + params.contentType(), + rv); + if (NS_WARN_IF(rv.Failed())) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + MOZ_ALWAYS_SUCCEEDS(slice->SetMutable(false)); + + RefPtr<IDTableEntry> idTableEntry = + IDTableEntry::Create(params.id(), + ActorManagerProcessID(aManager), + slice); + if (NS_WARN_IF(!idTableEntry)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + return new BlobParent(aManager, slice, idTableEntry); + } + + case AnyBlobConstructorParams::TKnownBlobConstructorParams: { + const KnownBlobConstructorParams& params = + blobParams.get_KnownBlobConstructorParams(); + + RefPtr<IDTableEntry> idTableEntry = + IDTableEntry::Get(params.id(), ActorManagerProcessID(aManager)); + if (NS_WARN_IF(!idTableEntry)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + return new BlobParent(aManager, idTableEntry); + } + + case AnyBlobConstructorParams::TSameProcessBlobConstructorParams: { + if (NS_WARN_IF(!ActorManagerIsSameProcess(aManager))) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + const SameProcessBlobConstructorParams& params = + blobParams.get_SameProcessBlobConstructorParams(); + + RefPtr<BlobImpl> blobImpl = + dont_AddRef(reinterpret_cast<BlobImpl*>(params.addRefedBlobImpl())); + MOZ_ASSERT(blobImpl); + + nsID id; + MOZ_ALWAYS_SUCCEEDS(gUUIDGenerator->GenerateUUIDInPlace(&id)); + + RefPtr<IDTableEntry> idTableEntry = + IDTableEntry::Create(id, ActorManagerProcessID(aManager), blobImpl); + MOZ_ASSERT(idTableEntry); + + return new BlobParent(aManager, blobImpl, idTableEntry); + } + + default: + MOZ_CRASH("Unknown params!"); + } + + MOZ_CRASH("Should never get here!"); +} + +// static +template <class ParentManagerType> +BlobParent* +BlobParent::SendSliceConstructor( + ParentManagerType* aManager, + const ParentBlobConstructorParams& aParams, + const ChildBlobConstructorParams& aOtherSideParams) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aManager); + + BlobParent* newActor = BlobParent::Create(aManager, aParams); + MOZ_ASSERT(newActor); + + if (aManager->SendPBlobConstructor(newActor, aOtherSideParams)) { + return newActor; + } + + return nullptr; +} + +// static +BlobParent* +BlobParent::MaybeGetActorFromRemoteBlob(nsIRemoteBlob* aRemoteBlob, + nsIContentParent* aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aRemoteBlob); + MOZ_ASSERT(aManager); + + BlobParent* actor = aRemoteBlob->GetBlobParent(); + if (actor && actor->GetContentManager() == aManager) { + return actor; + } + + return nullptr; +} + +// static +BlobParent* +BlobParent::MaybeGetActorFromRemoteBlob(nsIRemoteBlob* aRemoteBlob, + PBackgroundParent* aManager) +{ + AssertCorrectThreadForManager(aManager); + MOZ_ASSERT(aRemoteBlob); + MOZ_ASSERT(aManager); + + BlobParent* actor = aRemoteBlob->GetBlobParent(); + if (actor && actor->GetBackgroundManager() == aManager) { + return actor; + } + + return nullptr; +} + +already_AddRefed<BlobImpl> +BlobParent::GetBlobImpl() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mBlobImpl); + + RefPtr<BlobImpl> blobImpl; + + // Remote blobs are held alive until the first call to GetBlobImpl. Thereafter + // we only hold a weak reference. Normal blobs are held alive until the actor + // is destroyed. + if (mRemoteBlobImpl && mOwnsBlobImpl) { + blobImpl = dont_AddRef(mBlobImpl); + mOwnsBlobImpl = false; + } else { + blobImpl = mBlobImpl; + } + + MOZ_ASSERT(blobImpl); + + return blobImpl.forget(); +} + +void +BlobParent::NoteDyingRemoteBlobImpl() +{ + MOZ_ASSERT(mRemoteBlobImpl); + MOZ_ASSERT(!mOwnsBlobImpl); + + // This may be called on any thread due to the fact that RemoteBlobImpl is + // designed to be passed between threads. We must start the shutdown process + // on the main thread, so we proxy here if necessary. + if (!IsOnOwningThread()) { + nsCOMPtr<nsIRunnable> runnable = + NewNonOwningRunnableMethod(this, &BlobParent::NoteDyingRemoteBlobImpl); + + if (mEventTarget) { + runnable = new CancelableRunnableWrapper(runnable, mEventTarget); + + MOZ_ALWAYS_SUCCEEDS(mEventTarget->Dispatch(runnable, + NS_DISPATCH_NORMAL)); + } else { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable)); + } + + return; + } + + // Must do this before calling Send__delete__ or we'll crash there trying to + // access a dangling pointer. + mBlobImpl = nullptr; + mRemoteBlobImpl = nullptr; + + Unused << PBlobParent::Send__delete__(this); +} + +void +BlobParent::NoteRunnableCompleted(OpenStreamRunnable* aRunnable) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aRunnable); + + for (uint32_t count = mOpenStreamRunnables.Length(), index = 0; + index < count; + index++) { + nsRevocableEventPtr<OpenStreamRunnable>& runnable = + mOpenStreamRunnables[index]; + + if (runnable.get() == aRunnable) { + runnable.Forget(); + mOpenStreamRunnables.RemoveElementAt(index); + return; + } + } + + MOZ_CRASH("Runnable not in our array!"); +} + +bool +BlobParent::IsOnOwningThread() const +{ + return EventTargetIsOnCurrentThread(mEventTarget); +} + +void +BlobParent::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnOwningThread(); + + if (mRemoteBlobImpl) { + mRemoteBlobImpl->NoteDyingActor(); + } + + if (mBlobImpl && mOwnsBlobImpl) { + mBlobImpl->Release(); + } + +#ifdef DEBUG + mBlobImpl = nullptr; + mRemoteBlobImpl = nullptr; + mBackgroundManager = nullptr; + mContentManager = nullptr; + mOwnsBlobImpl = false; +#endif +} + +PBlobStreamParent* +BlobParent::AllocPBlobStreamParent(const uint64_t& aStart, + const uint64_t& aLength) +{ + AssertIsOnOwningThread(); + + if (NS_WARN_IF(mRemoteBlobImpl)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + return new InputStreamParent(); +} + +bool +BlobParent::RecvPBlobStreamConstructor(PBlobStreamParent* aActor, + const uint64_t& aStart, + const uint64_t& aLength) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(mBlobImpl); + MOZ_ASSERT(!mRemoteBlobImpl); + MOZ_ASSERT(mOwnsBlobImpl); + + auto* actor = static_cast<InputStreamParent*>(aActor); + + // Make sure we can't overflow. + if (NS_WARN_IF(UINT64_MAX - aLength < aStart)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + ErrorResult errorResult; + uint64_t blobLength = mBlobImpl->GetSize(errorResult); + MOZ_ASSERT(!errorResult.Failed()); + + if (NS_WARN_IF(aStart + aLength > blobLength)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + RefPtr<BlobImpl> blobImpl; + + if (!aStart && aLength == blobLength) { + blobImpl = mBlobImpl; + } else { + nsString type; + mBlobImpl->GetType(type); + + blobImpl = mBlobImpl->CreateSlice(aStart, aLength, type, errorResult); + if (NS_WARN_IF(errorResult.Failed())) { + return false; + } + } + + nsCOMPtr<nsIInputStream> stream; + blobImpl->GetInternalStream(getter_AddRefs(stream), errorResult); + if (NS_WARN_IF(errorResult.Failed())) { + return false; + } + + // If the stream is entirely backed by memory then we can serialize and send + // it immediately. + if (mBlobImpl->IsMemoryFile()) { + InputStreamParams params; + nsTArray<FileDescriptor> fds; + SerializeInputStream(stream, params, fds); + + MOZ_ASSERT(params.type() != InputStreamParams::T__None); + MOZ_ASSERT(fds.IsEmpty()); + + return actor->Destroy(params, void_t()); + } + + nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryInterface(mBlobImpl); + nsCOMPtr<IPrivateRemoteInputStream> remoteStream; + if (remoteBlob) { + remoteStream = do_QueryInterface(stream); + } + + // There are three cases in which we can use the stream obtained from the blob + // directly as our serialized stream: + // + // 1. The blob is not a remote blob. + // 2. The blob is a remote blob that represents this actor. + // 3. The blob is a remote blob representing a different actor but we + // already have a non-remote, i.e. serialized, serialized stream. + // + // In all other cases we need to be on a background thread before we can get + // to the real stream. + nsCOMPtr<nsIIPCSerializableInputStream> serializableStream; + if (!remoteBlob || + remoteBlob->GetBlobParent() == this || + !remoteStream) { + serializableStream = do_QueryInterface(stream); + if (!serializableStream) { + MOZ_ASSERT(false, "Must be serializable!"); + return false; + } + } + + nsCOMPtr<nsIThread> target; + errorResult = NS_NewNamedThread("Blob Opener", getter_AddRefs(target)); + if (NS_WARN_IF(errorResult.Failed())) { + return false; + } + + RefPtr<OpenStreamRunnable> runnable = + new OpenStreamRunnable(this, actor, stream, serializableStream, target); + + errorResult = runnable->Dispatch(); + if (NS_WARN_IF(errorResult.Failed())) { + return false; + } + + // nsRevocableEventPtr lacks some of the operators needed for anything nicer. + *mOpenStreamRunnables.AppendElement() = runnable; + return true; +} + +bool +BlobParent::DeallocPBlobStreamParent(PBlobStreamParent* aActor) +{ + AssertIsOnOwningThread(); + + delete static_cast<InputStreamParent*>(aActor); + return true; +} + +bool +BlobParent::RecvResolveMystery(const ResolveMysteryParams& aParams) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aParams.type() != ResolveMysteryParams::T__None); + MOZ_ASSERT(mBlobImpl); + MOZ_ASSERT(!mRemoteBlobImpl); + MOZ_ASSERT(mOwnsBlobImpl); + + switch (aParams.type()) { + case ResolveMysteryParams::TNormalBlobConstructorParams: { + const NormalBlobConstructorParams& params = + aParams.get_NormalBlobConstructorParams(); + + if (NS_WARN_IF(params.length() == UINT64_MAX)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + mBlobImpl->SetLazyData(NullString(), + params.contentType(), + params.length(), + INT64_MAX); + return true; + } + + case ResolveMysteryParams::TFileBlobConstructorParams: { + const FileBlobConstructorParams& params = + aParams.get_FileBlobConstructorParams(); + if (NS_WARN_IF(params.name().IsVoid())) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + if (NS_WARN_IF(params.length() == UINT64_MAX)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + if (NS_WARN_IF(params.modDate() == INT64_MAX)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + mBlobImpl->SetLazyData(params.name(), + params.contentType(), + params.length(), + params.modDate()); + return true; + } + + default: + MOZ_CRASH("Unknown params!"); + } + + MOZ_CRASH("Should never get here!"); +} + +bool +BlobParent::RecvBlobStreamSync(const uint64_t& aStart, + const uint64_t& aLength, + InputStreamParams* aParams, + OptionalFileDescriptorSet* aFDs) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mBlobImpl); + MOZ_ASSERT(!mRemoteBlobImpl); + MOZ_ASSERT(mOwnsBlobImpl); + + bool finished = false; + + { + // Calling RecvPBlobStreamConstructor() may synchronously delete the actor + // we pass in so don't touch it outside this block. + auto* streamActor = new InputStreamParent(&finished, aParams, aFDs); + + if (NS_WARN_IF(!RecvPBlobStreamConstructor(streamActor, aStart, aLength))) { + // If RecvPBlobStreamConstructor() returns false then it is our + // responsibility to destroy the actor. + delete streamActor; + return false; + } + } + + if (finished) { + // The actor is already dead and we have already set our out params. + return true; + } + + // The actor is alive and will be doing asynchronous work to load the stream. + // Spin a nested loop here while we wait for it. + nsIThread* currentThread = NS_GetCurrentThread(); + MOZ_ASSERT(currentThread); + + while (!finished) { + MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread)); + } + + return true; +} + +bool +BlobParent::RecvWaitForSliceCreation() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mBlobImpl); + MOZ_ASSERT(mOwnsBlobImpl); + + // The whole point of this message is to ensure that the sliced blob created + // by the child has been inserted into our IDTable. + MOZ_ASSERT(mIDTableEntry); + +#ifdef DEBUG + { + MOZ_ASSERT(sIDTableMutex); + MutexAutoLock lock(*sIDTableMutex); + + MOZ_ASSERT(sIDTable); + MOZ_ASSERT(sIDTable->Contains(mIDTableEntry->ID())); + } +#endif + + return true; +} + +bool +BlobParent::RecvGetFileId(int64_t* aFileId) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mBlobImpl); + MOZ_ASSERT(!mRemoteBlobImpl); + MOZ_ASSERT(mOwnsBlobImpl); + + if (NS_WARN_IF(!IndexedDatabaseManager::InTestingMode())) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + *aFileId = mBlobImpl->GetFileId(); + return true; +} + +bool +BlobParent::RecvGetFilePath(nsString* aFilePath) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mBlobImpl); + MOZ_ASSERT(!mRemoteBlobImpl); + MOZ_ASSERT(mOwnsBlobImpl); + + // In desktop e10s the file picker code sends this message. + + nsString filePath; + ErrorResult rv; + mBlobImpl->GetMozFullPathInternal(filePath, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } + + *aFilePath = filePath; + return true; +} + +/******************************************************************************* + * BlobParent::IDTableEntry + ******************************************************************************/ + +BlobParent:: +IDTableEntry::IDTableEntry(const nsID& aID, + intptr_t aProcessID, + BlobImpl* aBlobImpl) + : mID(aID) + , mProcessID(aProcessID) + , mBlobImpl(aBlobImpl) +{ + MOZ_ASSERT(aBlobImpl); +} + +BlobParent:: +IDTableEntry::~IDTableEntry() +{ + MOZ_ASSERT(sIDTableMutex); + sIDTableMutex->AssertNotCurrentThreadOwns(); + MOZ_ASSERT(sIDTable); + + { + MutexAutoLock lock(*sIDTableMutex); + MOZ_ASSERT(sIDTable->Get(mID) == this); + + sIDTable->Remove(mID); + + if (!sIDTable->Count()) { + sIDTable = nullptr; + } + } +} + +// static +already_AddRefed<BlobParent::IDTableEntry> +BlobParent:: +IDTableEntry::GetOrCreateInternal(const nsID& aID, + intptr_t aProcessID, + BlobImpl* aBlobImpl, + bool aMayCreate, + bool aMayGet, + bool aIgnoreProcessID) +{ + MOZ_ASSERT(gProcessType == GeckoProcessType_Default); + MOZ_ASSERT(sIDTableMutex); + sIDTableMutex->AssertNotCurrentThreadOwns(); + + RefPtr<IDTableEntry> entry; + + { + MutexAutoLock lock(*sIDTableMutex); + + if (!sIDTable) { + if (NS_WARN_IF(!aMayCreate)) { + return nullptr; + } + + sIDTable = new IDTable(); + } + + entry = sIDTable->Get(aID); + + if (entry) { + MOZ_ASSERT_IF(aBlobImpl, entry->GetBlobImpl() == aBlobImpl); + + if (NS_WARN_IF(!aMayGet)) { + return nullptr; + } + + if (!aIgnoreProcessID && NS_WARN_IF(entry->mProcessID != aProcessID)) { + return nullptr; + } + } else { + if (NS_WARN_IF(!aMayCreate)) { + return nullptr; + } + + MOZ_ASSERT(aBlobImpl); + + entry = new IDTableEntry(aID, aProcessID, aBlobImpl); + + sIDTable->Put(aID, entry); + } + } + + MOZ_ASSERT(entry); + + return entry.forget(); +} + +/******************************************************************************* + * Other stuff + ******************************************************************************/ + +bool +InputStreamChild::Recv__delete__(const InputStreamParams& aParams, + const OptionalFileDescriptorSet& aOptionalSet) +{ + MOZ_ASSERT(mRemoteStream); + mRemoteStream->AssertIsOnOwningThread(); + + nsTArray<FileDescriptor> fds; + OptionalFileDescriptorSetToFDs( + // XXX Fix this somehow... + const_cast<OptionalFileDescriptorSet&>(aOptionalSet), + fds); + + nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(aParams, fds); + MOZ_ASSERT(stream); + + mRemoteStream->SetStream(stream); + return true; +} + +} // namespace dom +} // namespace mozilla |