diff options
Diffstat (limited to 'dom/file/ipc')
43 files changed, 4991 insertions, 0 deletions
diff --git a/dom/file/ipc/BlobTypes.ipdlh b/dom/file/ipc/BlobTypes.ipdlh new file mode 100644 index 0000000000..97baaf7b15 --- /dev/null +++ b/dom/file/ipc/BlobTypes.ipdlh @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PFileDescriptorSet; + +include "mozilla/ipc/ProtocolMessageUtils.h"; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +namespace mozilla { +namespace dom { + +union OptionalFileDescriptorSet +{ + PFileDescriptorSet; + FileDescriptor[]; + void_t; +}; + +} +} diff --git a/dom/file/ipc/FileCreatorChild.cpp b/dom/file/ipc/FileCreatorChild.cpp new file mode 100644 index 0000000000..95ac19f17b --- /dev/null +++ b/dom/file/ipc/FileCreatorChild.cpp @@ -0,0 +1,59 @@ +/* -*- 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 "FileCreatorChild.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/IPCBlobUtils.h" + +namespace mozilla::dom { + +FileCreatorChild::FileCreatorChild() = default; + +FileCreatorChild::~FileCreatorChild() { MOZ_ASSERT(!mPromise); } + +void FileCreatorChild::SetPromise(Promise* aPromise) { + MOZ_ASSERT(aPromise); + MOZ_ASSERT(!mPromise); + + mPromise = aPromise; +} + +mozilla::ipc::IPCResult FileCreatorChild::Recv__delete__( + const FileCreationResult& aResult) { + MOZ_ASSERT(mPromise); + + RefPtr<Promise> promise; + promise.swap(mPromise); + + if (aResult.type() == FileCreationResult::TFileCreationErrorResult) { + promise->MaybeReject(aResult.get_FileCreationErrorResult().errorCode()); + return IPC_OK(); + } + + MOZ_ASSERT(aResult.type() == FileCreationResult::TFileCreationSuccessResult); + + RefPtr<dom::BlobImpl> impl = dom::IPCBlobUtils::Deserialize( + aResult.get_FileCreationSuccessResult().blob()); + + RefPtr<File> file = File::Create(promise->GetParentObject(), impl); + if (NS_WARN_IF(!file)) { + promise->MaybeReject(NS_ERROR_FAILURE); + return IPC_OK(); + } + + promise->MaybeResolve(file); + return IPC_OK(); +} + +void FileCreatorChild::ActorDestroy(ActorDestroyReason aWhy) { + if (mPromise) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + mPromise = nullptr; + } +}; + +} // namespace mozilla::dom diff --git a/dom/file/ipc/FileCreatorChild.h b/dom/file/ipc/FileCreatorChild.h new file mode 100644 index 0000000000..2dbc59050a --- /dev/null +++ b/dom/file/ipc/FileCreatorChild.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_FileCreatorChild_h +#define mozilla_dom_FileCreatorChild_h + +#include "mozilla/dom/PFileCreatorChild.h" + +namespace mozilla { +namespace dom { + +class FileCreatorChild final : public mozilla::dom::PFileCreatorChild { + friend class mozilla::dom::PFileCreatorChild; + + public: + FileCreatorChild(); + ~FileCreatorChild(); + + void SetPromise(Promise* aPromise); + + private: + mozilla::ipc::IPCResult Recv__delete__(const FileCreationResult& aResult); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + RefPtr<Promise> mPromise; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_FileCreatorChild_h diff --git a/dom/file/ipc/FileCreatorParent.cpp b/dom/file/ipc/FileCreatorParent.cpp new file mode 100644 index 0000000000..02f52f0c6d --- /dev/null +++ b/dom/file/ipc/FileCreatorParent.cpp @@ -0,0 +1,137 @@ +/* -*- 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 "FileCreatorParent.h" +#include "mozilla/dom/FileBlobImpl.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/dom/MultipartBlobImpl.h" +#include "nsIFile.h" + +namespace mozilla::dom { + +FileCreatorParent::FileCreatorParent() + : mBackgroundEventTarget(GetCurrentEventTarget()), mIPCActive(true) {} + +FileCreatorParent::~FileCreatorParent() = default; + +mozilla::ipc::IPCResult FileCreatorParent::CreateAndShareFile( + const nsString& aFullPath, const nsString& aType, const nsString& aName, + const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck, + const bool& aIsFromNsIFile) { + RefPtr<dom::BlobImpl> blobImpl; + nsresult rv = + CreateBlobImpl(aFullPath, aType, aName, aLastModified.isSome(), + aLastModified.isSome() ? aLastModified.value() : 0, + aExistenceCheck, aIsFromNsIFile, getter_AddRefs(blobImpl)); + if (NS_WARN_IF(NS_FAILED(rv))) { + Unused << Send__delete__(this, FileCreationErrorResult(rv)); + return IPC_OK(); + } + + MOZ_ASSERT(blobImpl); + + // FileBlobImpl is unable to return the correct type on this thread because + // nsIMIMEService is not thread-safe. We must exec the 'type' getter on + // main-thread before send the blob to the child actor. + + RefPtr<FileCreatorParent> self = this; + NS_DispatchToMainThread(NS_NewRunnableFunction( + "FileCreatorParent::CreateAndShareFile", [self, blobImpl]() { + nsAutoString type; + blobImpl->GetType(type); + + self->mBackgroundEventTarget->Dispatch(NS_NewRunnableFunction( + "FileCreatorParent::CreateAndShareFile return", [self, blobImpl]() { + if (self->mIPCActive) { + IPCBlob ipcBlob; + nsresult rv = dom::IPCBlobUtils::Serialize( + blobImpl, self->Manager(), ipcBlob); + if (NS_WARN_IF(NS_FAILED(rv))) { + Unused << Send__delete__(self, FileCreationErrorResult(rv)); + return; + } + + Unused << Send__delete__(self, + FileCreationSuccessResult(ipcBlob)); + } + })); + })); + + return IPC_OK(); +} + +void FileCreatorParent::ActorDestroy(ActorDestroyReason aWhy) { + mIPCActive = false; +} + +/* static */ +nsresult FileCreatorParent::CreateBlobImpl( + const nsAString& aPath, const nsAString& aType, const nsAString& aName, + bool aLastModifiedPassed, int64_t aLastModified, bool aExistenceCheck, + bool aIsFromNsIFile, BlobImpl** aBlobImpl) { + MOZ_ASSERT(!NS_IsMainThread()); + + nsCOMPtr<nsIFile> file; + nsresult rv = NS_NewLocalFile(aPath, true, getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = file->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (aExistenceCheck) { + if (!exists) { + return NS_ERROR_FILE_NOT_FOUND; + } + + bool isDir; + rv = file->IsDirectory(&isDir); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (isDir) { + return NS_ERROR_FILE_IS_DIRECTORY; + } + } + + RefPtr<FileBlobImpl> impl = new FileBlobImpl(file); + + // If the file doesn't exist, we cannot have its path, its size and so on. + // Let's set them now. + if (!exists) { + MOZ_ASSERT(!aExistenceCheck); + + impl->SetMozFullPath(aPath); + impl->SetLastModified(0); + impl->SetEmptySize(); + } + + if (!aName.IsEmpty()) { + impl->SetName(aName); + } + + if (!aType.IsEmpty()) { + impl->SetType(aType); + } + + if (aLastModifiedPassed) { + impl->SetLastModified(aLastModified); + } + + if (!aIsFromNsIFile) { + impl->SetMozFullPath(u""_ns); + } + + impl.forget(aBlobImpl); + return NS_OK; +} + +} // namespace mozilla::dom diff --git a/dom/file/ipc/FileCreatorParent.h b/dom/file/ipc/FileCreatorParent.h new file mode 100644 index 0000000000..86728e6b95 --- /dev/null +++ b/dom/file/ipc/FileCreatorParent.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_FileCreatorParent_h +#define mozilla_dom_FileCreatorParent_h + +#include "mozilla/dom/PFileCreatorParent.h" + +class nsIFile; + +namespace mozilla { +namespace dom { + +class BlobImpl; + +class FileCreatorParent final : public mozilla::dom::PFileCreatorParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileCreatorParent) + + FileCreatorParent(); + + mozilla::ipc::IPCResult CreateAndShareFile( + const nsString& aFullPath, const nsString& aType, const nsString& aName, + const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck, + const bool& aIsFromNsIFile); + + private: + ~FileCreatorParent(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + nsresult CreateBlobImpl(const nsAString& aPath, const nsAString& aType, + const nsAString& aName, bool aLastModifiedPassed, + int64_t aLastModified, bool aExistenceCheck, + bool aIsFromNsIFile, BlobImpl** aBlobImpl); + + nsCOMPtr<nsIEventTarget> mBackgroundEventTarget; + bool mIPCActive; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_FileCreatorParent_h diff --git a/dom/file/ipc/IPCBlob.ipdlh b/dom/file/ipc/IPCBlob.ipdlh new file mode 100644 index 0000000000..bcad597cea --- /dev/null +++ b/dom/file/ipc/IPCBlob.ipdlh @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PChildToParentStream; +include protocol PParentToChildStream; +include protocol PRemoteLazyInputStream; + +include IPCStream; +include ProtocolTypes; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +namespace mozilla { + +union RemoteLazyStream +{ + // Parent to Child: The child will receive a RemoteLazyInputStream. Nothing + // can be done with it except retrieving the size. + PRemoteLazyInputStream; + + // Child to Parent: Normal serialization. + IPCStream; +}; + +namespace dom { + +// This contains any extra bit for making a File out of a Blob. +// For more information about Blobs and IPC, please read the comments in +// IPCBlobUtils.h + +struct IPCFile +{ + nsString name; + int64_t lastModified; + nsString DOMPath; + nsString fullPath; + + // Useful for Entries API. + bool isDirectory; +}; + +struct IPCBlob +{ + nsString type; + uint64_t size; + nsString blobImplType; + + RemoteLazyStream inputStream; + + // Nothing is for Blob + IPCFile? file; + + // This ID is used only by indexedDB tests. + int64_t fileId; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/file/ipc/IPCBlobUtils.cpp b/dom/file/ipc/IPCBlobUtils.cpp new file mode 100644 index 0000000000..e58e0785e1 --- /dev/null +++ b/dom/file/ipc/IPCBlobUtils.cpp @@ -0,0 +1,231 @@ +/* -*- 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 "IPCBlobUtils.h" +#include "RemoteLazyInputStream.h" +#include "RemoteLazyInputStreamChild.h" +#include "RemoteLazyInputStreamParent.h" +#include "RemoteLazyInputStreamUtils.h" +#include "mozilla/dom/IPCBlob.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/PBackgroundParent.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "RemoteLazyInputStreamStorage.h" +#include "StreamBlobImpl.h" +#include "prtime.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom::IPCBlobUtils { + +already_AddRefed<BlobImpl> Deserialize(const IPCBlob& aIPCBlob) { + nsCOMPtr<nsIInputStream> inputStream; + + const RemoteLazyStream& stream = aIPCBlob.inputStream(); + switch (stream.type()) { + // Parent to child: when an nsIInputStream is sent from parent to child, the + // child receives a RemoteLazyInputStream actor. + case RemoteLazyStream::TPRemoteLazyInputStreamChild: { + RemoteLazyInputStreamChild* actor = + static_cast<RemoteLazyInputStreamChild*>( + stream.get_PRemoteLazyInputStreamChild()); + inputStream = actor->CreateStream(); + break; + } + + // Child to Parent: when a blob is created on the content process send it's + // sent to the parent, we have an IPCStream object. + case RemoteLazyStream::TIPCStream: + MOZ_ASSERT(XRE_IsParentProcess()); + inputStream = DeserializeIPCStream(stream.get_IPCStream()); + break; + + default: + MOZ_CRASH("Unknown type."); + break; + } + + MOZ_ASSERT(inputStream); + + RefPtr<StreamBlobImpl> blobImpl; + + if (aIPCBlob.file().isNothing()) { + blobImpl = StreamBlobImpl::Create(inputStream.forget(), aIPCBlob.type(), + aIPCBlob.size(), aIPCBlob.blobImplType()); + } else { + const IPCFile& file = aIPCBlob.file().ref(); + blobImpl = StreamBlobImpl::Create(inputStream.forget(), file.name(), + aIPCBlob.type(), file.lastModified(), + aIPCBlob.size(), aIPCBlob.blobImplType()); + blobImpl->SetDOMPath(file.DOMPath()); + blobImpl->SetFullPath(file.fullPath()); + blobImpl->SetIsDirectory(file.isDirectory()); + } + + blobImpl->SetFileId(aIPCBlob.fileId()); + + return blobImpl.forget(); +} + +template <typename M> +nsresult SerializeInternal(BlobImpl* aBlobImpl, M* aManager, + IPCBlob& aIPCBlob) { + MOZ_ASSERT(aBlobImpl); + + nsAutoString value; + aBlobImpl->GetType(value); + aIPCBlob.type() = value; + + aBlobImpl->GetBlobImplType(value); + aIPCBlob.blobImplType() = value; + + ErrorResult rv; + aIPCBlob.size() = aBlobImpl->GetSize(rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + if (!aBlobImpl->IsFile()) { + aIPCBlob.file() = Nothing(); + } else { + IPCFile file; + + aBlobImpl->GetName(value); + file.name() = value; + + file.lastModified() = aBlobImpl->GetLastModified(rv) * PR_USEC_PER_MSEC; + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + aBlobImpl->GetDOMPath(value); + file.DOMPath() = value; + + aBlobImpl->GetMozFullPathInternal(value, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + file.fullPath() = value; + + file.isDirectory() = aBlobImpl->IsDirectory(); + + aIPCBlob.file() = Some(file); + } + + aIPCBlob.fileId() = aBlobImpl->GetFileId(); + + nsCOMPtr<nsIInputStream> inputStream; + aBlobImpl->CreateInputStream(getter_AddRefs(inputStream), rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + RemoteLazyStream stream; + rv = RemoteLazyInputStreamUtils::SerializeInputStream( + inputStream, aIPCBlob.size(), stream, aManager); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + aIPCBlob.inputStream() = stream; + return NS_OK; +} + +nsresult Serialize(BlobImpl* aBlobImpl, ContentChild* aManager, + IPCBlob& aIPCBlob) { + return SerializeInternal(aBlobImpl, aManager, aIPCBlob); +} + +nsresult Serialize(BlobImpl* aBlobImpl, PBackgroundChild* aManager, + IPCBlob& aIPCBlob) { + return SerializeInternal(aBlobImpl, aManager, aIPCBlob); +} + +nsresult Serialize(BlobImpl* aBlobImpl, ContentParent* aManager, + IPCBlob& aIPCBlob) { + return SerializeInternal(aBlobImpl, aManager, aIPCBlob); +} + +nsresult Serialize(BlobImpl* aBlobImpl, PBackgroundParent* aManager, + IPCBlob& aIPCBlob) { + return SerializeInternal(aBlobImpl, aManager, aIPCBlob); +} + +nsresult SerializeUntyped(BlobImpl* aBlobImpl, IProtocol* aActor, + IPCBlob& aIPCBlob) { + // We always want to act on the toplevel protocol. + IProtocol* manager = aActor; + while (manager->Manager()) { + manager = manager->Manager(); + } + + // We always need the toplevel protocol + switch (manager->GetProtocolId()) { + case PBackgroundMsgStart: + if (manager->GetSide() == mozilla::ipc::ParentSide) { + return SerializeInternal( + aBlobImpl, static_cast<PBackgroundParent*>(manager), aIPCBlob); + } else { + return SerializeInternal( + aBlobImpl, static_cast<PBackgroundChild*>(manager), aIPCBlob); + } + case PContentMsgStart: + if (manager->GetSide() == mozilla::ipc::ParentSide) { + return SerializeInternal( + aBlobImpl, static_cast<ContentParent*>(manager), aIPCBlob); + } else { + return SerializeInternal(aBlobImpl, static_cast<ContentChild*>(manager), + aIPCBlob); + } + default: + MOZ_CRASH("Unsupported protocol passed to BlobImpl serialize"); + } +} + +} // namespace dom::IPCBlobUtils + +namespace ipc { +void IPDLParamTraits<mozilla::dom::BlobImpl*>::Write( + IPC::Message* aMsg, IProtocol* aActor, mozilla::dom::BlobImpl* aParam) { + nsresult rv; + mozilla::dom::IPCBlob ipcblob; + if (aParam) { + rv = mozilla::dom::IPCBlobUtils::SerializeUntyped(aParam, aActor, ipcblob); + } + if (!aParam || NS_WARN_IF(NS_FAILED(rv))) { + WriteIPDLParam(aMsg, aActor, false); + } else { + WriteIPDLParam(aMsg, aActor, true); + WriteIPDLParam(aMsg, aActor, ipcblob); + } +} + +bool IPDLParamTraits<mozilla::dom::BlobImpl*>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, + RefPtr<mozilla::dom::BlobImpl>* aResult) { + *aResult = nullptr; + + bool notnull = false; + if (!ReadIPDLParam(aMsg, aIter, aActor, ¬null)) { + return false; + } + if (notnull) { + mozilla::dom::IPCBlob ipcblob; + if (!ReadIPDLParam(aMsg, aIter, aActor, &ipcblob)) { + return false; + } + *aResult = mozilla::dom::IPCBlobUtils::Deserialize(ipcblob); + } + return true; +} +} // namespace ipc +} // namespace mozilla diff --git a/dom/file/ipc/IPCBlobUtils.h b/dom/file/ipc/IPCBlobUtils.h new file mode 100644 index 0000000000..9028542d3d --- /dev/null +++ b/dom/file/ipc/IPCBlobUtils.h @@ -0,0 +1,296 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_IPCBlobUtils_h +#define mozilla_dom_IPCBlobUtils_h + +#include "mozilla/RefPtr.h" +#include "mozilla/dom/File.h" +#include "mozilla/ipc/IPDLParamTraits.h" + +/* + * Blobs and IPC + * ~~~~~~~~~~~~~ + * + * Simplifying, DOM Blob objects are chunks of data with a content type and a + * size. DOM Files are Blobs with a name. They are are used in many APIs and + * they can be cloned and sent cross threads and cross processes. + * + * If we see Blobs from a platform point of view, the main (and often, the only) + * interesting part is how to retrieve data from it. This is done via + * nsIInputStream and, except for a couple of important details, this stream is + * used in the parent process. + * + * For this reason, when we consider the serialization of a blob via IPC + * messages, the biggest effort is put in how to manage the nsInputStream + * correctly. To serialize, we use the IPCBlob data struct: basically, the blob + * properties (size, type, name if it's a file) and the nsIInputStream. + * + * Before talking about the nsIInputStream it's important to say that we have + * different kinds of Blobs, based on the different kinds of sources. A non + * exaustive list is: + * - a memory buffer: MemoryBlobImpl + * - a string: StringBlobImpl + * - a real OS file: FileBlobImpl + * - a generic nsIInputStream: StreamBlobImpl + * - an empty blob: EmptyBlobImpl + * - more blobs combined together: MultipartBlobImpl + * Each one of these implementations has a custom ::CreateInputStream method. + * So, basically, each one has a different kind of nsIInputStream (nsFileStream, + * nsIStringInputStream, SlicedInputStream, and so on). + * + * Another important point to keep in mind is that a Blob can be created on the + * content process (for example: |new Blob([123])|) or it can be created on the + * parent process and sent to content (a FilePicker creates Blobs and it runs on + * the parent process). + * + * DocumentLoadListener uses blobs to serialize the POST data back to the + * content process (for insertion into session history). This lets it correctly + * handle OS files by reference, and avoid copying the underlying buffer data + * unless it is read. This can hopefully be removed once SessionHistory is + * handled in the parent process. + * + * Child to Parent Blob Serialization + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * When a document creates a blob, this can be sent, for different reasons to + * the parent process. For instance it can be sent as part of a FormData, or it + * can be converted to a BlobURL and broadcasted to any other existing + * processes. + * + * When this happens, we use the IPCStream data struct for the serialization + * of the nsIInputStream. This means that, if the stream is fully serializable + * and its size is lower than 1Mb, we are able to recreate the stream completely + * on the parent side. This happens, basically with any kind of child-to-parent + * stream except for huge memory streams. In this case we end up using + * PChildToParentStream. See more information in IPCStreamUtils.h. + * + * In order to populate IPCStream correctly, we use AutoIPCStream as documented + * in IPCStreamUtils.h. Note that we use the 'delayed start' feature because, + * often, the stream doesn't need to be read on the parent side. + * + * Parent to Child Blob Serialization + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This scenario is common when we talk about Blobs pointing to real files: + * HTMLInputElement (type=file), or Entries API, DataTransfer and so on. But we + * also have this scenario when a content process creates a Blob and it + * broadcasts it because of a BlobURL or because BroadcastChannel API is used. + * + * The approach here is this: normally, the content process doesn't really read + * data from the blob nsIInputStream. The content process needs to have the + * nsIInputStream and be able to send it back to the parent process when the + * "real" work needs to be done. This is true except for 2 usecases: FileReader + * API and BlobURL usage. So, if we ignore these 2, normally, the parent sends a + * blob nsIInputStream to a content process, and then, it will receive it back + * in order to do some networking, or whatever. + * + * For this reason, IPCBlobUtils uses a particular protocol for serializing + * nsIInputStream parent to child: PRemoteLazyInputStream. This protocol keeps + * the original nsIInputStream alive on the parent side, and gives its size and + * a UUID to the child side. The child side creates a RemoteLazyInputStream and + * that is incapsulated into a StreamBlobImpl. + * + * The UUID is useful when the content process sends the same nsIInputStream + * back to the parent process because, the only information it has to share is + * the UUID. Each nsIInputStream sent via PRemoteLazyInputStream, is registered + * into the RemoteLazyInputStreamStorage. + * + * On the content process side, RemoteLazyInputStream is a special inputStream: + * the only reliable methods are: + * - nsIInputStream.available() - the size is shared by PRemoteLazyInputStream + * actor. + * - nsIIPCSerializableInputStream.serialize() - we can give back this stream to + * the parent because we know its UUID. + * - nsICloneableInputStream.cloneable() and nsICloneableInputStream.clone() - + * this stream can be cloned. We just need to have a reference of the + * PRemoteLazyInputStream actor and its UUID. + * - nsIAsyncInputStream.asyncWait() - see next section. + * + * Any other method (read, readSegment and so on) will fail if asyncWait() is + * not previously called (see the next section). Basically, this inputStream + * cannot be used synchronously for any 'real' reading operation. + * + * When the parent receives the serialization of a RemoteLazyInputStream, it is + * able to retrieve the correct nsIInputStream using the UUID and + * RemoteLazyInputStreamStorage. + * + * Parent to Child Streams, FileReader and BlobURL + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * The FileReader and BlobURL scenarios are described here. + * + * When content process needs to read data from a Blob sent from the parent + * process, it must do it asynchronously using RemoteLazyInputStream as a + * nsIAsyncInputStream stream. This happens calling + * RemoteLazyInputStream.asyncWait(). At that point, the child actor will send a + * StreamNeeded() IPC message to the parent side. When this is received, the + * parent retrieves the 'real' stream from RemoteLazyInputStreamStorage using + * the UUID, it will serialize the 'real' stream, and it will send it to the + * child side. + * + * When the 'real' stream is received (RecvStreamReady()), the asyncWait + * callback will be executed and, from that moment, any RemoteLazyInputStream + * method will be forwarded to the 'real' stream ones. This means that the + * reading will be available. + * + * RemoteLazyInputStream Thread + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * RemoteLazyInputStreamChild actor can be created in any thread (sort of) and + * their top-level IPDL protocol is PBackground. These actors are wrapped by 1 + * or more RemoteLazyInputStream objects in order to expose nsIInputStream + * interface and be thread-safe. + * + * But IPDL actors are not thread-safe and any SendFoo() method must be executed + * on the owning thread. This means that this thread must be kept alive for the + * life-time of the RemoteLazyInputStream. + * + * In doing this, there are 2 main issues: + * a. if a remote Blob is created on a worker (because of a + * BroadcastChannel/MessagePort for instance) and it sent to the main-thread + * via PostMessage(), we have to keep that worker alive. + * b. if the remote Blob is created on the main-thread, any SendFoo() has to be + * executed on the main-thread. This is true also when the inputStream is + * used on another thread (note that nsIInputStream could do I/O and usually + * they are used on special I/O threads). + * + * In order to avoid this, RemoteLazyInputStreamChild are 'migrated' to a + * DOM-File thread. This is done in this way: + * + * 1. If RemoteLazyInputStreamChild actor is not already owned by DOM-File + * thread, it calls Send__delete__ in order to inform the parent side that we + * don't need this IPC channel on the current thread. + * 2. A new RemoteLazyInputStreamChild is created. RemoteLazyInputStreamThread + * is used to assign this actor to the DOM-File thread. + * RemoteLazyInputStreamThread::GetOrCreate() creates the DOM-File thread if + * it doesn't exist yet. Pending operations and RemoteLazyInputStreams are + * moved onto the new actor. + * 3. RemoteLazyInputStreamParent::Recv__delete__ is called on the parent side + * and the parent actor is deleted. Doing this we don't remove the UUID from + * RemoteLazyInputStreamStorage. + * 4. The RemoteLazyInputStream constructor is sent with the new + * RemoteLazyInputStreamChild actor, with the DOM-File thread's PBackground + * as its manager. + * 5. When the new RemoteLazyInputStreamParent actor is created, it will receive + * the same UUID of the previous parent actor. The nsIInputStream will be + * retrieved from RemoteLazyInputStreamStorage. + * 6. In order to avoid leaks, RemoteLazyInputStreamStorage will monitor child + * processes and in case one of them dies, it will release the + * nsIInputStream objects belonging to that process. + * + * If any API wants to retrieve a 'real inputStream when the migration is in + * progress, that operation is stored in a pending queue and processed at the + * end of the migration. + * + * IPCBlob and nsIAsyncInputStream + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * RemoteLazyInputStream is always async. If the remote inputStream is not + * async, RemoteLazyInputStream will create a pipe stream around it in order to + * be consistently async. + * + * Slicing IPCBlob + * ~~~~~~~~~~~~~~~ + * + * Normally, slicing a blob consists of the creation of a new Blob, with a + * SlicedInputStream() wrapping a clone of the original inputStream. But this + * approach is extremely inefficient with IPCBlob, because it could be that we + * wrap the pipe stream and not the remote inputStream (See the previous section + * of this documentation). If we end up doing so, also if the remote + * inputStream is seekable, the pipe will not be, and in order to reach the + * starting point, SlicedInputStream will do consecutive read()s. + * + * This problem is fixed implmenting nsICloneableWithRange in + * RemoteLazyInputStream and using cloneWithRange() when a StreamBlobImpl is + * sliced. When the remote stream is received, it will be sliced directly. + * + * If we want to represent the hierarchy of the InputStream classes, instead + * of having: |SlicedInputStream(RemoteLazyInputStream(Async + * Pipe(RemoteStream)))|, we have: |RemoteLazyInputStream(Async + * Pipe(SlicedInputStream(RemoteStream)))|. + * + * When RemoteLazyInputStream is serialized and sent to the parent process, + * start and range are sent too and SlicedInputStream is used in the parent side + * as well. + * + * Socket Process + * ~~~~~~~~~~~~~~ + * + * The socket process is a separate process used to do networking operations. + * When a website sends a blob as the body of a POST/PUT request, we need to + * send the corresponding RemoteLazyInputStream to the socket process. + * + * This is the only serialization of RemoteLazyInputStream from parent to child + * process and it works _only_ for the socket process. Do not expose this + * serialization to PContent or PBackground or any other top-level IPDL protocol + * without a DOM File peer review! + * + * The main difference between Socket Process is that DOM-File thread is not + * used. Here is a list of reasons: + * - DOM-File moves the ownership of the RemoteLazyInputStream actors to + * PBackground, but in the Socket Process we don't have PBackground (yet?) + * - Socket Process is a stable process with a simple life-time configuration: + * we can keep the actors on the main-thread because no Workers are involved. + */ + +namespace mozilla { + +namespace ipc { +class IProtocol; +class PBackgroundChild; +class PBackgroundParent; +} // namespace ipc + +namespace dom { + +class IPCBlob; +class ContentChild; +class ContentParent; + +namespace IPCBlobUtils { + +already_AddRefed<BlobImpl> Deserialize(const IPCBlob& aIPCBlob); + +// These 4 methods serialize aBlobImpl into aIPCBlob using the right manager. + +nsresult Serialize(BlobImpl* aBlobImpl, ContentChild* aManager, + IPCBlob& aIPCBlob); + +nsresult Serialize(BlobImpl* aBlobImpl, + mozilla::ipc::PBackgroundChild* aManager, IPCBlob& aIPCBlob); + +nsresult Serialize(BlobImpl* aBlobImpl, ContentParent* aManager, + IPCBlob& aIPCBlob); + +nsresult Serialize(BlobImpl* aBlobImpl, + mozilla::ipc::PBackgroundParent* aManager, + IPCBlob& aIPCBlob); + +// WARNING: If you pass any actor which does not have P{Content,Background} as +// its toplevel protocol, this method will MOZ_CRASH. +nsresult SerializeUntyped(BlobImpl* aBlobImpl, mozilla::ipc::IProtocol* aActor, + IPCBlob& aIPCBlob); + +} // namespace IPCBlobUtils +} // namespace dom + +namespace ipc { +// ParamTraits implementation for BlobImpl. N.B: If the original BlobImpl cannot +// be successfully serialized, a warning will be produced and a nullptr will be +// sent over the wire. When Read()-ing a BlobImpl, +// __always make sure to handle null!__ +template <> +struct IPDLParamTraits<mozilla::dom::BlobImpl*> { + static void Write(IPC::Message* aMsg, IProtocol* aActor, + mozilla::dom::BlobImpl* aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, RefPtr<mozilla::dom::BlobImpl>* aResult); +}; +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_dom_IPCBlobUtils_h diff --git a/dom/file/ipc/PFileCreator.ipdl b/dom/file/ipc/PFileCreator.ipdl new file mode 100644 index 0000000000..2d07b02d09 --- /dev/null +++ b/dom/file/ipc/PFileCreator.ipdl @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PBackground; +include protocol PChildToParentStream; +include protocol PFileDescriptorSet; +include protocol PRemoteLazyInputStream; +include protocol PParentToChildStream; + +include IPCBlob; + +namespace mozilla { +namespace dom { + +struct FileCreationSuccessResult +{ + IPCBlob blob; +}; + +struct FileCreationErrorResult +{ + nsresult errorCode; +}; + +union FileCreationResult +{ + FileCreationSuccessResult; + FileCreationErrorResult; +}; + +protocol PFileCreator +{ + manager PBackground; + +child: + async __delete__(FileCreationResult aResult); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/file/ipc/PRemoteLazyInputStream.ipdl b/dom/file/ipc/PRemoteLazyInputStream.ipdl new file mode 100644 index 0000000000..a2bc3a5ca2 --- /dev/null +++ b/dom/file/ipc/PRemoteLazyInputStream.ipdl @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PBackground; +include protocol PChildToParentStream; +include protocol PContent; +include protocol PFileDescriptorSet; +include protocol PParentToChildStream; +include protocol PSocketProcess; + +include IPCStream; + +namespace mozilla { + +refcounted protocol PRemoteLazyInputStream +{ + manager PBackground or PContent or PSocketProcess; + +parent: + async StreamNeeded(); + + async LengthNeeded(); + + // When this is called, the parent releases the inputStream and sends a + // __delete__. + async Close(); + +child: + async StreamReady(IPCStream? aStream); + + async LengthReady(int64_t aLength); + +both: + // __delete__ can be called by parent and by child for 2 reasons: + // - parent->child: This happens after a Close(). The child wants to inform + // the parent that no other messages will be dispatched and + // that the channel can be interrupted. + // - child->parent: before any operation, the child could start a migration + // from the current thread to a dedicated DOM-File one. The + // reason why a __delete__ is sent from child to parent is + // because it doesn't require any additional runnables. + async __delete__(); +}; + +} // namespace mozilla diff --git a/dom/file/ipc/PTemporaryIPCBlob.ipdl b/dom/file/ipc/PTemporaryIPCBlob.ipdl new file mode 100644 index 0000000000..5c57221b96 --- /dev/null +++ b/dom/file/ipc/PTemporaryIPCBlob.ipdl @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PBackground; +include protocol PChildToParentStream; +include protocol PFileDescriptorSet; +include protocol PParentToChildStream; +include protocol PRemoteLazyInputStream; + +include IPCBlob; + +namespace mozilla { +namespace dom { + +union IPCBlobOrError +{ + IPCBlob; + nsresult; +}; + +protocol PTemporaryIPCBlob +{ + manager PBackground; + + // When this actor is created on the child side, the parent will send + // immediatelly back a FileDescriptor or a __delete__ in case of error. + // When the FileDescriptor is received, the child has to call + // OperationDone(). When OperationDone() is received on the parent side, the + // parent actor will send a __delete__. + +child: + async FileDesc(FileDescriptor aFD); + async __delete__(IPCBlobOrError aBlobOrError); + +parent: + async OperationFailed(); + + async OperationDone(nsCString aContentType, FileDescriptor aFD); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/file/ipc/RemoteLazyInputStream.cpp b/dom/file/ipc/RemoteLazyInputStream.cpp new file mode 100644 index 0000000000..10e2e41633 --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStream.cpp @@ -0,0 +1,960 @@ +/* -*- 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 "RemoteLazyInputStream.h" +#include "RemoteLazyInputStreamChild.h" +#include "RemoteLazyInputStreamParent.h" +#include "mozilla/ipc/InputStreamParams.h" +#include "mozilla/net/SocketProcessParent.h" +#include "mozilla/SlicedInputStream.h" +#include "mozilla/NonBlockingAsyncInputStream.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIPipe.h" +#include "nsNetUtil.h" +#include "nsStreamUtils.h" +#include "nsStringStream.h" +#include "RemoteLazyInputStreamStorage.h" +#include "RemoteLazyInputStreamThread.h" + +namespace mozilla { + +using namespace dom; +using net::SocketProcessParent; + +class RemoteLazyInputStream; + +namespace { + +class InputStreamCallbackRunnable final : public DiscardableRunnable { + public: + // Note that the execution can be synchronous in case the event target is + // null. + static void Execute(nsIInputStreamCallback* aCallback, + nsIEventTarget* aEventTarget, + RemoteLazyInputStream* aStream) { + MOZ_ASSERT(aCallback); + + RefPtr<InputStreamCallbackRunnable> runnable = + new InputStreamCallbackRunnable(aCallback, aStream); + + nsCOMPtr<nsIEventTarget> target = aEventTarget; + if (aEventTarget) { + target->Dispatch(runnable, NS_DISPATCH_NORMAL); + } else { + runnable->Run(); + } + } + + NS_IMETHOD + Run() override { + mCallback->OnInputStreamReady(mStream); + mCallback = nullptr; + mStream = nullptr; + return NS_OK; + } + + private: + InputStreamCallbackRunnable(nsIInputStreamCallback* aCallback, + RemoteLazyInputStream* aStream) + : DiscardableRunnable("dom::InputStreamCallbackRunnable"), + mCallback(aCallback), + mStream(aStream) { + MOZ_ASSERT(mCallback); + MOZ_ASSERT(mStream); + } + + nsCOMPtr<nsIInputStreamCallback> mCallback; + RefPtr<RemoteLazyInputStream> mStream; +}; + +class FileMetadataCallbackRunnable final : public DiscardableRunnable { + public: + static void Execute(nsIFileMetadataCallback* aCallback, + nsIEventTarget* aEventTarget, + RemoteLazyInputStream* aStream) { + MOZ_ASSERT(aCallback); + MOZ_ASSERT(aEventTarget); + + RefPtr<FileMetadataCallbackRunnable> runnable = + new FileMetadataCallbackRunnable(aCallback, aStream); + + nsCOMPtr<nsIEventTarget> target = aEventTarget; + target->Dispatch(runnable, NS_DISPATCH_NORMAL); + } + + NS_IMETHOD + Run() override { + mCallback->OnFileMetadataReady(mStream); + mCallback = nullptr; + mStream = nullptr; + return NS_OK; + } + + private: + FileMetadataCallbackRunnable(nsIFileMetadataCallback* aCallback, + RemoteLazyInputStream* aStream) + : DiscardableRunnable("dom::FileMetadataCallbackRunnable"), + mCallback(aCallback), + mStream(aStream) { + MOZ_ASSERT(mCallback); + MOZ_ASSERT(mStream); + } + + nsCOMPtr<nsIFileMetadataCallback> mCallback; + RefPtr<RemoteLazyInputStream> mStream; +}; + +} // namespace + +NS_IMPL_ADDREF(RemoteLazyInputStream); +NS_IMPL_RELEASE(RemoteLazyInputStream); + +NS_INTERFACE_MAP_BEGIN(RemoteLazyInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) + NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream) + NS_INTERFACE_MAP_ENTRY(nsICloneableInputStreamWithRange) + NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) + NS_INTERFACE_MAP_ENTRY(nsIFileMetadata) + NS_INTERFACE_MAP_ENTRY(nsIAsyncFileMetadata) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength) + NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStreamLength) + NS_INTERFACE_MAP_ENTRY(mozIRemoteLazyInputStream) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +RemoteLazyInputStream::RemoteLazyInputStream(RemoteLazyInputStreamChild* aActor) + : mActor(aActor), + mState(eInit), + mStart(0), + mLength(0), + mConsumed(false), + mMutex("RemoteLazyInputStream::mMutex") { + MOZ_ASSERT(aActor); + + mLength = aActor->Size(); + + if (XRE_IsParentProcess()) { + nsCOMPtr<nsIInputStream> stream; + auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr); + if (storage) { + storage->GetStream(mActor->ID(), 0, mLength, getter_AddRefs(stream)); + if (stream) { + mState = eRunning; + mRemoteStream = stream; + } + } + } +} + +RemoteLazyInputStream::~RemoteLazyInputStream() { Close(); } + +// nsIInputStream interface + +NS_IMETHODIMP +RemoteLazyInputStream::Available(uint64_t* aLength) { + nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream; + { + MutexAutoLock lock(mMutex); + + // We don't have a remoteStream yet: let's return 0. + if (mState == eInit || mState == ePending) { + *aLength = 0; + return NS_OK; + } + + if (mState == eClosed) { + return NS_BASE_STREAM_CLOSED; + } + + MOZ_ASSERT(mState == eRunning); + MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream); + + nsresult rv = EnsureAsyncRemoteStream(lock); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + asyncRemoteStream = mAsyncRemoteStream; + } + + MOZ_ASSERT(asyncRemoteStream); + return asyncRemoteStream->Available(aLength); +} + +NS_IMETHODIMP +RemoteLazyInputStream::Read(char* aBuffer, uint32_t aCount, + uint32_t* aReadCount) { + nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream; + { + MutexAutoLock lock(mMutex); + + // Read is not available is we don't have a remoteStream. + if (mState == eInit || mState == ePending) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + if (mState == eClosed) { + return NS_BASE_STREAM_CLOSED; + } + + MOZ_ASSERT(mState == eRunning); + MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream); + + nsresult rv = EnsureAsyncRemoteStream(lock); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + asyncRemoteStream = mAsyncRemoteStream; + } + + MOZ_ASSERT(asyncRemoteStream); + nsresult rv = asyncRemoteStream->Read(aBuffer, aCount, aReadCount); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + { + MutexAutoLock lock(mMutex); + mConsumed = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +RemoteLazyInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream; + { + MutexAutoLock lock(mMutex); + + // ReadSegments is not available is we don't have a remoteStream. + if (mState == eInit || mState == ePending) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + if (mState == eClosed) { + return NS_BASE_STREAM_CLOSED; + } + + MOZ_ASSERT(mState == eRunning); + MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream); + + nsresult rv = EnsureAsyncRemoteStream(lock); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + asyncRemoteStream = mAsyncRemoteStream; + } + + MOZ_ASSERT(asyncRemoteStream); + nsresult rv = + asyncRemoteStream->ReadSegments(aWriter, aClosure, aCount, aResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // If some data has been read, we mark the stream as consumed. + if (*aResult != 0) { + MutexAutoLock lock(mMutex); + mConsumed = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +RemoteLazyInputStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = true; + return NS_OK; +} + +NS_IMETHODIMP +RemoteLazyInputStream::Close() { + nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream; + nsCOMPtr<nsIInputStream> remoteStream; + { + MutexAutoLock lock(mMutex); + + if (mActor) { + mActor->ForgetStream(this); + mActor = nullptr; + } + + asyncRemoteStream.swap(mAsyncRemoteStream); + remoteStream.swap(mRemoteStream); + + mInputStreamCallback = nullptr; + mInputStreamCallbackEventTarget = nullptr; + + mFileMetadataCallback = nullptr; + mFileMetadataCallbackEventTarget = nullptr; + + mState = eClosed; + } + + if (asyncRemoteStream) { + asyncRemoteStream->CloseWithStatus(NS_BASE_STREAM_CLOSED); + } + + if (remoteStream) { + remoteStream->Close(); + } + + return NS_OK; +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +RemoteLazyInputStream::GetCloneable(bool* aCloneable) { + MutexAutoLock lock(mMutex); + *aCloneable = mState != eClosed; + return NS_OK; +} + +NS_IMETHODIMP +RemoteLazyInputStream::Clone(nsIInputStream** aResult) { + MutexAutoLock lock(mMutex); + + if (mState == eClosed) { + return NS_BASE_STREAM_CLOSED; + } + + MOZ_ASSERT(mActor); + + RefPtr<RemoteLazyInputStream> stream = mActor->CreateStream(); + if (!stream) { + return NS_ERROR_FAILURE; + } + + stream->InitWithExistingRange(mStart, mLength, lock); + + stream.forget(aResult); + return NS_OK; +} + +// nsICloneableInputStreamWithRange interface + +NS_IMETHODIMP +RemoteLazyInputStream::CloneWithRange(uint64_t aStart, uint64_t aLength, + nsIInputStream** aResult) { + MutexAutoLock lock(mMutex); + + if (mState == eClosed) { + return NS_BASE_STREAM_CLOSED; + } + + // Too short or out of range. + if (aLength == 0 || aStart >= mLength) { + return NS_NewCStringInputStream(aResult, ""_ns); + } + + MOZ_ASSERT(mActor); + + RefPtr<RemoteLazyInputStream> stream = mActor->CreateStream(); + if (!stream) { + return NS_ERROR_FAILURE; + } + + CheckedInt<uint64_t> streamSize = mLength; + streamSize -= aStart; + if (!streamSize.isValid()) { + return NS_ERROR_FAILURE; + } + + if (aLength > streamSize.value()) { + aLength = streamSize.value(); + } + + stream->InitWithExistingRange(aStart + mStart, aLength, lock); + + stream.forget(aResult); + return NS_OK; +} + +// nsIAsyncInputStream interface + +NS_IMETHODIMP +RemoteLazyInputStream::CloseWithStatus(nsresult aStatus) { return Close(); } + +NS_IMETHODIMP +RemoteLazyInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream; + { + MutexAutoLock lock(mMutex); + + // See RemoteLazyInputStream.h for more information about this state + // machine. + + switch (mState) { + // First call, we need to retrieve the stream from the parent actor. + case eInit: + MOZ_ASSERT(mActor); + + mInputStreamCallback = aCallback; + mInputStreamCallbackEventTarget = aEventTarget; + mState = ePending; + + mActor->StreamNeeded(this, aEventTarget); + return NS_OK; + + // We are still waiting for the remote inputStream + case ePending: { + if (mInputStreamCallback && aCallback) { + return NS_ERROR_FAILURE; + } + + mInputStreamCallback = aCallback; + mInputStreamCallbackEventTarget = aEventTarget; + return NS_OK; + } + + // We have the remote inputStream, let's check if we can execute the + // callback. + case eRunning: { + if (mInputStreamCallback && aCallback) { + return NS_ERROR_FAILURE; + } + + nsresult rv = EnsureAsyncRemoteStream(lock); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mInputStreamCallback = aCallback; + mInputStreamCallbackEventTarget = aEventTarget; + + asyncRemoteStream = mAsyncRemoteStream; + break; + } + + case eClosed: + [[fallthrough]]; + default: + MOZ_ASSERT(mState == eClosed); + if (mInputStreamCallback && aCallback) { + return NS_ERROR_FAILURE; + } + break; + } + } + + if (asyncRemoteStream) { + return asyncRemoteStream->AsyncWait(aCallback ? this : nullptr, 0, 0, + aEventTarget); + } + + // if asyncRemoteStream is nullptr here, that probably means the stream has + // been closed and the callback can be executed immediately + InputStreamCallbackRunnable::Execute(aCallback, aEventTarget, this); + return NS_OK; +} + +void RemoteLazyInputStream::StreamReady( + already_AddRefed<nsIInputStream> aInputStream) { + nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream); + + // If inputStream is null, it means that the serialization went wrong or the + // stream is not available anymore. We keep the state as pending just to + // block any additional operation. + + if (!inputStream) { + return; + } + + nsCOMPtr<nsIFileMetadataCallback> fileMetadataCallback; + nsCOMPtr<nsIEventTarget> fileMetadataCallbackEventTarget; + nsCOMPtr<nsIInputStreamCallback> inputStreamCallback; + nsCOMPtr<nsIEventTarget> inputStreamCallbackEventTarget; + nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream; + { + MutexAutoLock lock(mMutex); + + // We have been closed in the meantime. + if (mState == eClosed) { + if (inputStream) { + MutexAutoUnlock unlock(mMutex); + inputStream->Close(); + } + return; + } + + // Now it's the right time to apply a slice if needed. + if (mStart > 0 || mLength < mActor->Size()) { + inputStream = + new SlicedInputStream(inputStream.forget(), mStart, mLength); + } + + mRemoteStream = inputStream; + + MOZ_ASSERT(mState == ePending); + mState = eRunning; + + fileMetadataCallback.swap(mFileMetadataCallback); + fileMetadataCallbackEventTarget.swap(mFileMetadataCallbackEventTarget); + + inputStreamCallback = mInputStreamCallback ? this : nullptr; + inputStreamCallbackEventTarget = mInputStreamCallbackEventTarget; + + if (inputStreamCallback) { + nsresult rv = EnsureAsyncRemoteStream(lock); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + MOZ_ASSERT(mAsyncRemoteStream); + asyncRemoteStream = mAsyncRemoteStream; + } + } + + if (fileMetadataCallback) { + FileMetadataCallbackRunnable::Execute( + fileMetadataCallback, fileMetadataCallbackEventTarget, this); + } + + if (inputStreamCallback) { + MOZ_ASSERT(asyncRemoteStream); + + nsresult rv = asyncRemoteStream->AsyncWait(inputStreamCallback, 0, 0, + inputStreamCallbackEventTarget); + Unused << NS_WARN_IF(NS_FAILED(rv)); + } +} + +void RemoteLazyInputStream::InitWithExistingRange( + uint64_t aStart, uint64_t aLength, const MutexAutoLock& aProofOfLock) { + MOZ_ASSERT(mActor->Size() >= aStart + aLength); + mStart = aStart; + mLength = aLength; + + // In the child, we slice in StreamReady() when we set mState to eRunning. + // But in the parent, we start out eRunning, so it's necessary to slice the + // stream as soon as we have the information during the initialization phase + // because the stream is immediately consumable. + if (mState == eRunning && mRemoteStream && XRE_IsParentProcess() && + (mStart > 0 || mLength < mActor->Size())) { + mRemoteStream = + new SlicedInputStream(mRemoteStream.forget(), mStart, mLength); + } +} + +// nsIInputStreamCallback + +NS_IMETHODIMP +RemoteLazyInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) { + nsCOMPtr<nsIInputStreamCallback> callback; + nsCOMPtr<nsIEventTarget> callbackEventTarget; + { + MutexAutoLock lock(mMutex); + + // We have been closed in the meantime. + if (mState == eClosed) { + return NS_OK; + } + + MOZ_ASSERT(mState == eRunning); + MOZ_ASSERT(mAsyncRemoteStream == aStream); + + // The callback has been canceled in the meantime. + if (!mInputStreamCallback) { + return NS_OK; + } + + callback.swap(mInputStreamCallback); + callbackEventTarget.swap(mInputStreamCallbackEventTarget); + } + + // This must be the last operation because the execution of the callback can + // be synchronous. + MOZ_ASSERT(callback); + InputStreamCallbackRunnable::Execute(callback, callbackEventTarget, this); + return NS_OK; +} + +// nsIIPCSerializableInputStream + +void RemoteLazyInputStream::Serialize( + mozilla::ipc::InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors, bool aDelayedStart, + uint32_t aMaxSize, uint32_t* aSizeUsed, + mozilla::ipc::ParentToChildStreamActorManager* aManager) { + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = 0; + + // So far we support only socket process serialization. + MOZ_DIAGNOSTIC_ASSERT( + aManager == SocketProcessParent::GetSingleton(), + "Serializing an RemoteLazyInputStream parent to child is " + "wrong! The caller must be fixed! See IPCBlobUtils.h."); + SocketProcessParent* socketActor = SocketProcessParent::GetSingleton(); + + nsresult rv; + nsCOMPtr<nsIAsyncInputStream> asyncRemoteStream; + RefPtr<RemoteLazyInputStreamParent> parentActor; + { + MutexAutoLock lock(mMutex); + rv = EnsureAsyncRemoteStream(lock); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + asyncRemoteStream = mAsyncRemoteStream; + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + MOZ_ASSERT(asyncRemoteStream); + + parentActor = RemoteLazyInputStreamParent::Create(asyncRemoteStream, mLength, + 0, &rv, socketActor); + MOZ_ASSERT(parentActor); + + if (!socketActor->SendPRemoteLazyInputStreamConstructor( + parentActor, parentActor->ID(), parentActor->Size())) { + MOZ_CRASH("The serialization is not supposed to fail"); + } + + aParams = mozilla::ipc::RemoteLazyInputStreamParams(parentActor); +} + +void RemoteLazyInputStream::Serialize( + mozilla::ipc::InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors, bool aDelayedStart, + uint32_t aMaxSize, uint32_t* aSizeUsed, + mozilla::ipc::ChildToParentStreamActorManager* aManager) { + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = 0; + + MutexAutoLock lock(mMutex); + + mozilla::ipc::RemoteLazyInputStreamRef params; + params.id() = mActor->ID(); + params.start() = mStart; + params.length() = mLength; + + aParams = params; +} + +bool RemoteLazyInputStream::Deserialize( + const mozilla::ipc::InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) { + MOZ_CRASH("This should never be called."); + return false; +} + +// nsIAsyncFileMetadata + +NS_IMETHODIMP +RemoteLazyInputStream::AsyncFileMetadataWait(nsIFileMetadataCallback* aCallback, + nsIEventTarget* aEventTarget) { + MOZ_ASSERT(!!aCallback == !!aEventTarget); + + // If we have the callback, we must have the event target. + if (NS_WARN_IF(!!aCallback != !!aEventTarget)) { + return NS_ERROR_FAILURE; + } + + // See RemoteLazyInputStream.h for more information about this state + // machine. + + { + MutexAutoLock lock(mMutex); + + switch (mState) { + // First call, we need to retrieve the stream from the parent actor. + case eInit: + MOZ_ASSERT(mActor); + + mFileMetadataCallback = aCallback; + mFileMetadataCallbackEventTarget = aEventTarget; + mState = ePending; + + mActor->StreamNeeded(this, aEventTarget); + return NS_OK; + + // We are still waiting for the remote inputStream + case ePending: + if (mFileMetadataCallback && aCallback) { + return NS_ERROR_FAILURE; + } + + mFileMetadataCallback = aCallback; + mFileMetadataCallbackEventTarget = aEventTarget; + return NS_OK; + + // We have the remote inputStream, let's check if we can execute the + // callback. + case eRunning: + break; + + // Stream is closed. + default: + MOZ_ASSERT(mState == eClosed); + return NS_BASE_STREAM_CLOSED; + } + + MOZ_ASSERT(mState == eRunning); + } + + FileMetadataCallbackRunnable::Execute(aCallback, aEventTarget, this); + return NS_OK; +} + +// nsIFileMetadata + +NS_IMETHODIMP +RemoteLazyInputStream::GetSize(int64_t* aRetval) { + nsCOMPtr<nsIFileMetadata> fileMetadata; + { + MutexAutoLock lock(mMutex); + fileMetadata = do_QueryInterface(mRemoteStream); + if (!fileMetadata) { + return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE; + } + } + + return fileMetadata->GetSize(aRetval); +} + +NS_IMETHODIMP +RemoteLazyInputStream::GetLastModified(int64_t* aRetval) { + nsCOMPtr<nsIFileMetadata> fileMetadata; + { + MutexAutoLock lock(mMutex); + fileMetadata = do_QueryInterface(mRemoteStream); + if (!fileMetadata) { + return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE; + } + } + + return fileMetadata->GetLastModified(aRetval); +} + +NS_IMETHODIMP +RemoteLazyInputStream::GetFileDescriptor(PRFileDesc** aRetval) { + nsCOMPtr<nsIFileMetadata> fileMetadata; + { + MutexAutoLock lock(mMutex); + fileMetadata = do_QueryInterface(mRemoteStream); + if (!fileMetadata) { + return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE; + } + } + + return fileMetadata->GetFileDescriptor(aRetval); +} + +nsresult RemoteLazyInputStream::EnsureAsyncRemoteStream( + const MutexAutoLock& aProofOfLock) { + // We already have an async remote stream. + if (mAsyncRemoteStream) { + return NS_OK; + } + + if (!mRemoteStream) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIInputStream> stream = mRemoteStream; + // We don't return NS_ERROR_NOT_IMPLEMENTED from ReadSegments, + // so it's possible that callers are expecting us to succeed in the future. + // We need to make sure the stream we return here supports ReadSegments, + // so wrap if in a buffered stream if necessary. + if (!NS_InputStreamIsBuffered(stream)) { + nsCOMPtr<nsIInputStream> bufferedStream; + nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), + stream.forget(), 4096); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + stream = bufferedStream; + } + + // If the stream is blocking, we want to make it unblocking using a pipe. + bool nonBlocking = false; + nsresult rv = stream->IsNonBlocking(&nonBlocking); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(stream); + + // If non-blocking and non-async, let's use NonBlockingAsyncInputStream. + if (nonBlocking && !asyncStream) { + rv = NonBlockingAsyncInputStream::Create(stream.forget(), + getter_AddRefs(asyncStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(asyncStream); + } + + if (!asyncStream) { + // Let's make the stream async using the DOMFile thread. + nsCOMPtr<nsIAsyncInputStream> pipeIn; + nsCOMPtr<nsIAsyncOutputStream> pipeOut; + rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, + true); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RefPtr<RemoteLazyInputStreamThread> thread = + RemoteLazyInputStreamThread::GetOrCreate(); + if (NS_WARN_IF(!thread)) { + return NS_ERROR_FAILURE; + } + + rv = NS_AsyncCopy(stream, pipeOut, thread, NS_ASYNCCOPY_VIA_WRITESEGMENTS); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + asyncStream = pipeIn; + } + + MOZ_ASSERT(asyncStream); + mAsyncRemoteStream = asyncStream; + mRemoteStream = nullptr; + + return NS_OK; +} + +// nsIInputStreamLength + +NS_IMETHODIMP +RemoteLazyInputStream::Length(int64_t* aLength) { + MutexAutoLock lock(mMutex); + + if (mState == eClosed) { + return NS_BASE_STREAM_CLOSED; + } + + if (mConsumed) { + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_BASE_STREAM_WOULD_BLOCK; +} + +namespace { + +class InputStreamLengthCallbackRunnable final : public DiscardableRunnable { + public: + static void Execute(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget, + RemoteLazyInputStream* aStream, int64_t aLength) { + MOZ_ASSERT(aCallback); + MOZ_ASSERT(aEventTarget); + + RefPtr<InputStreamLengthCallbackRunnable> runnable = + new InputStreamLengthCallbackRunnable(aCallback, aStream, aLength); + + nsCOMPtr<nsIEventTarget> target = aEventTarget; + target->Dispatch(runnable, NS_DISPATCH_NORMAL); + } + + NS_IMETHOD + Run() override { + mCallback->OnInputStreamLengthReady(mStream, mLength); + mCallback = nullptr; + mStream = nullptr; + return NS_OK; + } + + private: + InputStreamLengthCallbackRunnable(nsIInputStreamLengthCallback* aCallback, + RemoteLazyInputStream* aStream, + int64_t aLength) + : DiscardableRunnable("dom::InputStreamLengthCallbackRunnable"), + mCallback(aCallback), + mStream(aStream), + mLength(aLength) { + MOZ_ASSERT(mCallback); + MOZ_ASSERT(mStream); + } + + nsCOMPtr<nsIInputStreamLengthCallback> mCallback; + RefPtr<RemoteLazyInputStream> mStream; + const int64_t mLength; +}; + +} // namespace + +// nsIAsyncInputStreamLength + +NS_IMETHODIMP +RemoteLazyInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) { + // If we have the callback, we must have the event target. + if (NS_WARN_IF(!!aCallback != !!aEventTarget)) { + return NS_ERROR_FAILURE; + } + + { + MutexAutoLock lock(mMutex); + + mLengthCallback = aCallback; + mLengthCallbackEventTarget = aEventTarget; + + if (mState != eClosed && !mConsumed) { + MOZ_ASSERT(mActor); + + if (aCallback) { + mActor->LengthNeeded(this, aEventTarget); + } + + return NS_OK; + } + } + + // If execution has reached here, it means the stream is either closed or + // consumed, and therefore the callback can be executed immediately + InputStreamLengthCallbackRunnable::Execute(aCallback, aEventTarget, this, -1); + return NS_OK; +} + +void RemoteLazyInputStream::LengthReady(int64_t aLength) { + nsCOMPtr<nsIInputStreamLengthCallback> lengthCallback; + nsCOMPtr<nsIEventTarget> lengthCallbackEventTarget; + + { + MutexAutoLock lock(mMutex); + + // Stream has been closed in the meantime. Callback can be executed + // immediately + if (mState == eClosed || mConsumed) { + aLength = -1; + } else { + if (mStart > 0) { + aLength -= mStart; + } + + if (mLength < mActor->Size()) { + // If the remote stream must be sliced, we must return here the + // correct value. + aLength = XPCOM_MIN(aLength, (int64_t)mLength); + } + } + + lengthCallback.swap(mLengthCallback); + lengthCallbackEventTarget.swap(mLengthCallbackEventTarget); + } + + if (lengthCallback) { + InputStreamLengthCallbackRunnable::Execute( + lengthCallback, lengthCallbackEventTarget, this, aLength); + } +} + +} // namespace mozilla diff --git a/dom/file/ipc/RemoteLazyInputStream.h b/dom/file/ipc/RemoteLazyInputStream.h new file mode 100644 index 0000000000..c36f63f4ab --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStream.h @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_RemoteLazyInputStream_h +#define mozilla_RemoteLazyInputStream_h + +#include "mozilla/Mutex.h" +#include "mozIRemoteLazyInputStream.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIFileStreams.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsIInputStreamLength.h" +#include "nsCOMPtr.h" + +namespace mozilla { + +class RemoteLazyInputStreamChild; + +class RemoteLazyInputStream final : public nsIAsyncInputStream, + public nsIInputStreamCallback, + public nsICloneableInputStreamWithRange, + public nsIIPCSerializableInputStream, + public nsIAsyncFileMetadata, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength, + public mozIRemoteLazyInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAMWITHRANGE + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSIFILEMETADATA + NS_DECL_NSIASYNCFILEMETADATA + NS_DECL_NSIINPUTSTREAMLENGTH + NS_DECL_NSIASYNCINPUTSTREAMLENGTH + + explicit RemoteLazyInputStream(RemoteLazyInputStreamChild* aActor); + + void StreamReady(already_AddRefed<nsIInputStream> aInputStream); + + void LengthReady(int64_t aLength); + + // mozIRemoteLazyInputStream + NS_IMETHOD_(nsIInputStream*) GetInternalStream() override { + if (mRemoteStream) { + return mRemoteStream; + } + + if (mAsyncRemoteStream) { + return mAsyncRemoteStream; + } + + return nullptr; + } + + private: + ~RemoteLazyInputStream(); + + nsresult EnsureAsyncRemoteStream(const MutexAutoLock& aProofOfLock); + + void InitWithExistingRange(uint64_t aStart, uint64_t aLength, + const MutexAutoLock& aProofOfLock); + + RefPtr<RemoteLazyInputStreamChild> mActor; + + // This is the list of possible states. + enum { + // The initial state. Only ::Available() can be used without receiving an + // error. The available size is known by the actor. + eInit, + + // AsyncWait() has been called for the first time. SendStreamNeeded() has + // been called and we are waiting for the 'real' inputStream. + ePending, + + // When the child receives the stream from the parent, we move to this + // state. The received stream is stored in mRemoteStream. From now on, any + // method call will be forwared to mRemoteStream. + eRunning, + + // If Close() or CloseWithStatus() is called, we move to this state. + // mRemoveStream is released and any method will return + // NS_BASE_STREAM_CLOSED. + eClosed, + } mState; + + uint64_t mStart; + uint64_t mLength; + + // Set to true if the stream is used via Read/ReadSegments or Close. + bool mConsumed; + + nsCOMPtr<nsIInputStream> mRemoteStream; + nsCOMPtr<nsIAsyncInputStream> mAsyncRemoteStream; + + // These 2 values are set only if mState is ePending. + nsCOMPtr<nsIInputStreamCallback> mInputStreamCallback; + nsCOMPtr<nsIEventTarget> mInputStreamCallbackEventTarget; + + // These 2 values are set only if mState is ePending. + nsCOMPtr<nsIFileMetadataCallback> mFileMetadataCallback; + nsCOMPtr<nsIEventTarget> mFileMetadataCallbackEventTarget; + + // These 2 values are set only when nsIAsyncInputStreamLength::asyncWait() is + // called. + nsCOMPtr<nsIInputStreamLengthCallback> mLengthCallback; + nsCOMPtr<nsIEventTarget> mLengthCallbackEventTarget; + + // Any member of this class is protected by mutex because touched on + // multiple threads. + Mutex mMutex; +}; + +} // namespace mozilla + +#endif // mozilla_RemoteLazyInputStream_h diff --git a/dom/file/ipc/RemoteLazyInputStreamChild.cpp b/dom/file/ipc/RemoteLazyInputStreamChild.cpp new file mode 100644 index 0000000000..53e6cee055 --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStreamChild.cpp @@ -0,0 +1,440 @@ +/* -*- 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 "RemoteLazyInputStreamChild.h" +#include "RemoteLazyInputStreamThread.h" + +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerRef.h" + +namespace mozilla { + +using namespace dom; + +namespace { + +// This runnable is used in case the last stream is forgotten on the 'wrong' +// thread. +class ShutdownRunnable final : public DiscardableRunnable { + public: + explicit ShutdownRunnable(RemoteLazyInputStreamChild* aActor) + : DiscardableRunnable("dom::ShutdownRunnable"), mActor(aActor) {} + + NS_IMETHOD + Run() override { + mActor->Shutdown(); + return NS_OK; + } + + private: + RefPtr<RemoteLazyInputStreamChild> mActor; +}; + +// This runnable is used in case StreamNeeded() has been called on a non-owning +// thread. +class StreamNeededRunnable final : public DiscardableRunnable { + public: + explicit StreamNeededRunnable(RemoteLazyInputStreamChild* aActor) + : DiscardableRunnable("dom::StreamNeededRunnable"), mActor(aActor) {} + + NS_IMETHOD + Run() override { + MOZ_ASSERT( + mActor->State() != RemoteLazyInputStreamChild::eActiveMigrating && + mActor->State() != RemoteLazyInputStreamChild::eInactiveMigrating); + if (mActor->State() == RemoteLazyInputStreamChild::eActive) { + mActor->SendStreamNeeded(); + } + return NS_OK; + } + + private: + RefPtr<RemoteLazyInputStreamChild> mActor; +}; + +// When the stream has been received from the parent, we inform the +// RemoteLazyInputStream. +class StreamReadyRunnable final : public DiscardableRunnable { + public: + StreamReadyRunnable(RemoteLazyInputStream* aDestinationStream, + already_AddRefed<nsIInputStream> aCreatedStream) + : DiscardableRunnable("dom::StreamReadyRunnable"), + mDestinationStream(aDestinationStream), + mCreatedStream(std::move(aCreatedStream)) { + MOZ_ASSERT(mDestinationStream); + // mCreatedStream can be null. + } + + NS_IMETHOD + Run() override { + mDestinationStream->StreamReady(mCreatedStream.forget()); + return NS_OK; + } + + private: + RefPtr<RemoteLazyInputStream> mDestinationStream; + nsCOMPtr<nsIInputStream> mCreatedStream; +}; + +// This runnable is used in case LengthNeeded() has been called on a non-owning +// thread. +class LengthNeededRunnable final : public DiscardableRunnable { + public: + explicit LengthNeededRunnable(RemoteLazyInputStreamChild* aActor) + : DiscardableRunnable("dom::LengthNeededRunnable"), mActor(aActor) {} + + NS_IMETHOD + Run() override { + MOZ_ASSERT( + mActor->State() != RemoteLazyInputStreamChild::eActiveMigrating && + mActor->State() != RemoteLazyInputStreamChild::eInactiveMigrating); + if (mActor->State() == RemoteLazyInputStreamChild::eActive) { + mActor->SendLengthNeeded(); + } + return NS_OK; + } + + private: + RefPtr<RemoteLazyInputStreamChild> mActor; +}; + +// When the stream has been received from the parent, we inform the +// RemoteLazyInputStream. +class LengthReadyRunnable final : public DiscardableRunnable { + public: + LengthReadyRunnable(RemoteLazyInputStream* aDestinationStream, int64_t aSize) + : DiscardableRunnable("dom::LengthReadyRunnable"), + mDestinationStream(aDestinationStream), + mSize(aSize) { + MOZ_ASSERT(mDestinationStream); + } + + NS_IMETHOD + Run() override { + mDestinationStream->LengthReady(mSize); + return NS_OK; + } + + private: + RefPtr<RemoteLazyInputStream> mDestinationStream; + int64_t mSize; +}; + +} // namespace + +RemoteLazyInputStreamChild::RemoteLazyInputStreamChild(const nsID& aID, + uint64_t aSize) + : mMutex("RemoteLazyInputStreamChild::mMutex"), + mID(aID), + mSize(aSize), + mState(eActive), + mOwningEventTarget(GetCurrentSerialEventTarget()) { + // If we are running in a worker, we need to send a Close() to the parent side + // before the thread is released. + if (!NS_IsMainThread()) { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + if (!workerPrivate) { + return; + } + + RefPtr<StrongWorkerRef> workerRef = + StrongWorkerRef::Create(workerPrivate, "RemoteLazyInputStreamChild"); + if (!workerRef) { + return; + } + + // We must keep the worker alive until the migration is completed. + mWorkerRef = new ThreadSafeWorkerRef(workerRef); + } +} + +RemoteLazyInputStreamChild::~RemoteLazyInputStreamChild() = default; + +void RemoteLazyInputStreamChild::Shutdown() { + MutexAutoLock lock(mMutex); + + RefPtr<RemoteLazyInputStreamChild> kungFuDeathGrip = this; + + mWorkerRef = nullptr; + mPendingOperations.Clear(); + + if (mState == eActive) { + SendClose(); + mState = eInactive; + } +} + +void RemoteLazyInputStreamChild::ActorDestroy( + IProtocol::ActorDestroyReason aReason) { + bool migrating = false; + + { + MutexAutoLock lock(mMutex); + migrating = mState == eActiveMigrating; + mState = migrating ? eInactiveMigrating : eInactive; + } + + if (!migrating) { + // Let's cleanup the workerRef and the pending operation queue. + Shutdown(); + return; + } +} + +RemoteLazyInputStreamChild::ActorState RemoteLazyInputStreamChild::State() { + MutexAutoLock lock(mMutex); + return mState; +} + +already_AddRefed<RemoteLazyInputStream> +RemoteLazyInputStreamChild::CreateStream() { + bool shouldMigrate = false; + + RefPtr<RemoteLazyInputStream> stream; + + { + MutexAutoLock lock(mMutex); + + if (mState == eInactive) { + return nullptr; + } + + // The stream is active but maybe it is not running in the DOM-File thread. + // We should migrate it there. + if (mState == eActive && + !RemoteLazyInputStreamThread::IsOnFileEventTarget(mOwningEventTarget)) { + MOZ_ASSERT(mStreams.IsEmpty()); + + shouldMigrate = true; + mState = eActiveMigrating; + + RefPtr<RemoteLazyInputStreamThread> thread = + RemoteLazyInputStreamThread::GetOrCreate(); + MOZ_ASSERT(thread, "We cannot continue without DOMFile thread."); + + // Create a new actor object to connect to the target thread. + RefPtr<RemoteLazyInputStreamChild> newActor = + new RemoteLazyInputStreamChild(mID, mSize); + { + MutexAutoLock newActorLock(newActor->mMutex); + + // Move over our local state onto the new actor object. + newActor->mWorkerRef = mWorkerRef; + newActor->mState = eInactiveMigrating; + newActor->mPendingOperations = std::move(mPendingOperations); + + // Create the actual stream object. + stream = new RemoteLazyInputStream(newActor); + newActor->mStreams.AppendElement(stream); + } + + // Perform the actual migration. + thread->MigrateActor(newActor); + } else { + stream = new RemoteLazyInputStream(this); + mStreams.AppendElement(stream); + } + } + + // Send__delete__ will call ActorDestroy(). mMutex cannot be locked at this + // time. + if (shouldMigrate) { + Send__delete__(this); + } + + return stream.forget(); +} + +void RemoteLazyInputStreamChild::ForgetStream(RemoteLazyInputStream* aStream) { + MOZ_ASSERT(aStream); + + RefPtr<RemoteLazyInputStreamChild> kungFuDeathGrip = this; + + { + MutexAutoLock lock(mMutex); + mStreams.RemoveElement(aStream); + + if (!mStreams.IsEmpty() || mState != eActive) { + return; + } + } + + if (mOwningEventTarget->IsOnCurrentThread()) { + Shutdown(); + return; + } + + RefPtr<ShutdownRunnable> runnable = new ShutdownRunnable(this); + mOwningEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL); +} + +void RemoteLazyInputStreamChild::StreamNeeded(RemoteLazyInputStream* aStream, + nsIEventTarget* aEventTarget) { + MutexAutoLock lock(mMutex); + + if (mState == eInactive) { + return; + } + + MOZ_ASSERT(mStreams.Contains(aStream)); + + PendingOperation* opt = mPendingOperations.AppendElement(); + opt->mStream = aStream; + opt->mEventTarget = aEventTarget; + opt->mOp = PendingOperation::eStreamNeeded; + + if (mState == eActiveMigrating || mState == eInactiveMigrating) { + // This operation will be continued when the migration is completed. + return; + } + + MOZ_ASSERT(mState == eActive); + + if (mOwningEventTarget->IsOnCurrentThread()) { + SendStreamNeeded(); + return; + } + + RefPtr<StreamNeededRunnable> runnable = new StreamNeededRunnable(this); + mOwningEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); +} + +mozilla::ipc::IPCResult RemoteLazyInputStreamChild::RecvStreamReady( + const Maybe<IPCStream>& aStream) { + nsCOMPtr<nsIInputStream> stream = mozilla::ipc::DeserializeIPCStream(aStream); + + RefPtr<RemoteLazyInputStream> pendingStream; + nsCOMPtr<nsIEventTarget> eventTarget; + + { + MutexAutoLock lock(mMutex); + + // We have been shutdown in the meantime. + if (mState == eInactive) { + return IPC_OK(); + } + + MOZ_ASSERT(!mPendingOperations.IsEmpty()); + MOZ_ASSERT(mState == eActive); + + pendingStream = mPendingOperations[0].mStream; + eventTarget = mPendingOperations[0].mEventTarget; + MOZ_ASSERT(mPendingOperations[0].mOp == PendingOperation::eStreamNeeded); + + mPendingOperations.RemoveElementAt(0); + } + + RefPtr<StreamReadyRunnable> runnable = + new StreamReadyRunnable(pendingStream, stream.forget()); + + // If RemoteLazyInputStream::AsyncWait() has been executed without passing an + // event target, we run the callback synchronous because any thread could be + // result to be the wrong one. See more in nsIAsyncInputStream::asyncWait + // documentation. + if (eventTarget) { + eventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL); + } else { + runnable->Run(); + } + + return IPC_OK(); +} + +void RemoteLazyInputStreamChild::LengthNeeded(RemoteLazyInputStream* aStream, + nsIEventTarget* aEventTarget) { + MutexAutoLock lock(mMutex); + + if (mState == eInactive) { + return; + } + + MOZ_ASSERT(mStreams.Contains(aStream)); + + PendingOperation* opt = mPendingOperations.AppendElement(); + opt->mStream = aStream; + opt->mEventTarget = aEventTarget; + opt->mOp = PendingOperation::eLengthNeeded; + + if (mState == eActiveMigrating || mState == eInactiveMigrating) { + // This operation will be continued when the migration is completed. + return; + } + + MOZ_ASSERT(mState == eActive); + + if (mOwningEventTarget->IsOnCurrentThread()) { + SendLengthNeeded(); + return; + } + + RefPtr<LengthNeededRunnable> runnable = new LengthNeededRunnable(this); + mOwningEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); +} + +mozilla::ipc::IPCResult RemoteLazyInputStreamChild::RecvLengthReady( + const int64_t& aLength) { + RefPtr<RemoteLazyInputStream> pendingStream; + nsCOMPtr<nsIEventTarget> eventTarget; + + { + MutexAutoLock lock(mMutex); + + // We have been shutdown in the meantime. + if (mState == eInactive) { + return IPC_OK(); + } + + MOZ_ASSERT(!mPendingOperations.IsEmpty()); + MOZ_ASSERT(mState == eActive); + + pendingStream = mPendingOperations[0].mStream; + eventTarget = mPendingOperations[0].mEventTarget; + MOZ_ASSERT(mPendingOperations[0].mOp == PendingOperation::eLengthNeeded); + + mPendingOperations.RemoveElementAt(0); + } + + RefPtr<LengthReadyRunnable> runnable = + new LengthReadyRunnable(pendingStream, aLength); + + MOZ_ASSERT(eventTarget); + eventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL); + + return IPC_OK(); +} +void RemoteLazyInputStreamChild::Migrated() { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mState == eInactiveMigrating); + + mWorkerRef = nullptr; + + mOwningEventTarget = GetCurrentSerialEventTarget(); + MOZ_ASSERT( + RemoteLazyInputStreamThread::IsOnFileEventTarget(mOwningEventTarget)); + + // Maybe we have no reasons to keep this actor alive. + if (mStreams.IsEmpty()) { + mState = eInactive; + SendClose(); + return; + } + + mState = eActive; + + // Let's processing the pending operations. We need a stream for each pending + // operation. + for (uint32_t i = 0; i < mPendingOperations.Length(); ++i) { + if (mPendingOperations[i].mOp == PendingOperation::eStreamNeeded) { + SendStreamNeeded(); + } else { + MOZ_ASSERT(mPendingOperations[i].mOp == PendingOperation::eLengthNeeded); + SendLengthNeeded(); + } + } +} + +} // namespace mozilla diff --git a/dom/file/ipc/RemoteLazyInputStreamChild.h b/dom/file/ipc/RemoteLazyInputStreamChild.h new file mode 100644 index 0000000000..7e86a90ac8 --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStreamChild.h @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_RemoteLazyInputStreamChild_h +#define mozilla_RemoteLazyInputStreamChild_h + +#include "mozilla/PRemoteLazyInputStreamChild.h" +#include "mozilla/RemoteLazyInputStream.h" +#include "mozilla/Mutex.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + +namespace mozilla { + +class RemoteLazyInputStream; + +namespace dom { +class ThreadSafeWorkerRef; +} + +class RemoteLazyInputStreamChild final : public PRemoteLazyInputStreamChild { + public: + enum ActorState { + // The actor is connected via IPDL to the parent. + eActive, + + // The actor is disconnected. + eInactive, + + // The actor is waiting to be disconnected. Once it has been disconnected, + // it will be reactivated on the DOM-File thread. + eActiveMigrating, + + // The actor has been disconnected and it's waiting to be connected on the + // DOM-File thread. + eInactiveMigrating, + }; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteLazyInputStreamChild, final) + + RemoteLazyInputStreamChild(const nsID& aID, uint64_t aSize); + + void ActorDestroy(IProtocol::ActorDestroyReason aReason) override; + + ActorState State(); + + already_AddRefed<RemoteLazyInputStream> CreateStream(); + + void ForgetStream(RemoteLazyInputStream* aStream); + + const nsID& ID() const { return mID; } + + uint64_t Size() const { return mSize; } + + void StreamNeeded(RemoteLazyInputStream* aStream, + nsIEventTarget* aEventTarget); + + mozilla::ipc::IPCResult RecvStreamReady(const Maybe<IPCStream>& aStream); + + void LengthNeeded(RemoteLazyInputStream* aStream, + nsIEventTarget* aEventTarget); + + mozilla::ipc::IPCResult RecvLengthReady(const int64_t& aLength); + + void Shutdown(); + + void Migrated(); + + private: + ~RemoteLazyInputStreamChild(); + + // Raw pointers because these streams keep this actor alive. When the last + // stream is unregister, the actor will be deleted. This list is protected by + // mutex. + nsTArray<RemoteLazyInputStream*> mStreams; + + // This mutex protects mStreams because that can be touched in any thread. + Mutex mMutex; + + const nsID mID; + const uint64_t mSize; + + ActorState mState; + + // This struct and the array are used for creating streams when needed. + struct PendingOperation { + RefPtr<RemoteLazyInputStream> mStream; + nsCOMPtr<nsIEventTarget> mEventTarget; + enum { + eStreamNeeded, + eLengthNeeded, + } mOp; + }; + nsTArray<PendingOperation> mPendingOperations; + + nsCOMPtr<nsISerialEventTarget> mOwningEventTarget; + + RefPtr<dom::ThreadSafeWorkerRef> mWorkerRef; +}; + +} // namespace mozilla + +#endif // mozilla_RemoteLazyInputStreamChild_h diff --git a/dom/file/ipc/RemoteLazyInputStreamParent.cpp b/dom/file/ipc/RemoteLazyInputStreamParent.cpp new file mode 100644 index 0000000000..7aced41ac4 --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStreamParent.cpp @@ -0,0 +1,253 @@ +/* -*- 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 "RemoteLazyInputStreamParent.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/InputStreamLengthHelper.h" +#include "nsContentUtils.h" +#include "RemoteLazyInputStreamStorage.h" + +namespace mozilla { + +template <typename M> +/* static */ +already_AddRefed<RemoteLazyInputStreamParent> +RemoteLazyInputStreamParent::Create(nsIInputStream* aInputStream, + uint64_t aSize, uint64_t aChildID, + nsresult* aRv, M* aManager) { + MOZ_ASSERT(aInputStream); + MOZ_ASSERT(aRv); + + nsID id; + *aRv = nsContentUtils::GenerateUUIDInPlace(id); + if (NS_WARN_IF(NS_FAILED(*aRv))) { + return nullptr; + } + + auto storageOrErr = RemoteLazyInputStreamStorage::Get(); + + if (NS_WARN_IF(storageOrErr.isErr())) { + *aRv = storageOrErr.unwrapErr(); + return nullptr; + } + + auto storage = storageOrErr.unwrap(); + storage->AddStream(aInputStream, id, aSize, aChildID); + + RefPtr<RemoteLazyInputStreamParent> parent = + new RemoteLazyInputStreamParent(id, aSize, aManager); + return parent.forget(); +} + +/* static */ +already_AddRefed<RemoteLazyInputStreamParent> +RemoteLazyInputStreamParent::Create(const nsID& aID, uint64_t aSize, + PBackgroundParent* aManager) { + RefPtr<RemoteLazyInputStreamParent> actor = + new RemoteLazyInputStreamParent(aID, aSize, aManager); + + auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr); + + if (storage) { + actor->mCallback = storage->TakeCallback(aID); + return actor.forget(); + } + + return nullptr; +} + +template already_AddRefed<RemoteLazyInputStreamParent> +RemoteLazyInputStreamParent::Create<mozilla::ipc::PBackgroundParent>( + nsIInputStream*, uint64_t, uint64_t, nsresult*, + mozilla::ipc::PBackgroundParent*); + +/* static */ +already_AddRefed<RemoteLazyInputStreamParent> +RemoteLazyInputStreamParent::Create(const nsID& aID, uint64_t aSize, + net::SocketProcessParent* aManager) { + RefPtr<RemoteLazyInputStreamParent> actor = + new RemoteLazyInputStreamParent(aID, aSize, aManager); + + auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr); + + if (storage) { + actor->mCallback = storage->TakeCallback(aID); + return actor.forget(); + } + + return nullptr; +} + +template already_AddRefed<RemoteLazyInputStreamParent> +RemoteLazyInputStreamParent::Create<mozilla::net::SocketProcessParent>( + nsIInputStream*, uint64_t, uint64_t, nsresult*, + mozilla::net::SocketProcessParent*); + +template already_AddRefed<RemoteLazyInputStreamParent> +RemoteLazyInputStreamParent::Create<dom::ContentParent>(nsIInputStream*, + uint64_t, uint64_t, + nsresult*, + dom::ContentParent*); + +RemoteLazyInputStreamParent::RemoteLazyInputStreamParent( + const nsID& aID, uint64_t aSize, dom::ContentParent* aManager) + : mID(aID), + mSize(aSize), + mContentManager(aManager), + mPBackgroundManager(nullptr), + mSocketProcessManager(nullptr), + mMigrating(false) {} + +RemoteLazyInputStreamParent::RemoteLazyInputStreamParent( + const nsID& aID, uint64_t aSize, PBackgroundParent* aManager) + : mID(aID), + mSize(aSize), + mContentManager(nullptr), + mPBackgroundManager(aManager), + mSocketProcessManager(nullptr), + mMigrating(false) {} + +RemoteLazyInputStreamParent::RemoteLazyInputStreamParent( + const nsID& aID, uint64_t aSize, net::SocketProcessParent* aManager) + : mID(aID), + mSize(aSize), + mContentManager(nullptr), + mPBackgroundManager(nullptr), + mSocketProcessManager(aManager), + mMigrating(false) {} + +void RemoteLazyInputStreamParent::ActorDestroy( + IProtocol::ActorDestroyReason aReason) { + MOZ_ASSERT(mContentManager || mPBackgroundManager || mSocketProcessManager); + + mContentManager = nullptr; + mPBackgroundManager = nullptr; + mSocketProcessManager = nullptr; + + RefPtr<RemoteLazyInputStreamParentCallback> callback; + mCallback.swap(callback); + + auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr); + + if (mMigrating) { + if (callback && storage) { + // We need to assign this callback to the next parent. + storage->StoreCallback(mID, callback); + } + return; + } + + if (storage) { + storage->ForgetStream(mID); + } + + if (callback) { + callback->ActorDestroyed(mID); + } +} + +void RemoteLazyInputStreamParent::SetCallback( + RemoteLazyInputStreamParentCallback* aCallback) { + MOZ_ASSERT(aCallback); + MOZ_ASSERT(!mCallback); + + mCallback = aCallback; +} + +mozilla::ipc::IPCResult RemoteLazyInputStreamParent::RecvStreamNeeded() { + MOZ_ASSERT(mContentManager || mPBackgroundManager || mSocketProcessManager); + + nsCOMPtr<nsIInputStream> stream; + auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr); + if (storage) { + storage->GetStream(mID, 0, mSize, getter_AddRefs(stream)); + } + + if (!stream) { + if (!SendStreamReady(Nothing())) { + return IPC_FAIL(this, "SendStreamReady failed"); + } + + return IPC_OK(); + } + + mozilla::ipc::AutoIPCStream ipcStream; + bool ok = false; + + if (mContentManager) { + MOZ_ASSERT(NS_IsMainThread()); + ok = ipcStream.Serialize(stream, mContentManager); + } else if (mPBackgroundManager) { + ok = ipcStream.Serialize(stream, mPBackgroundManager); + } else { + MOZ_ASSERT(mSocketProcessManager); + ok = ipcStream.Serialize(stream, mSocketProcessManager); + } + + if (NS_WARN_IF(!ok)) { + return IPC_FAIL(this, "SendStreamReady failed"); + } + + if (!SendStreamReady(Some(ipcStream.TakeValue()))) { + return IPC_FAIL(this, "SendStreamReady failed"); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult RemoteLazyInputStreamParent::RecvLengthNeeded() { + MOZ_ASSERT(mContentManager || mPBackgroundManager || mSocketProcessManager); + + nsCOMPtr<nsIInputStream> stream; + auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr); + if (storage) { + storage->GetStream(mID, 0, mSize, getter_AddRefs(stream)); + } + + if (!stream) { + if (!SendLengthReady(-1)) { + return IPC_FAIL(this, "SendLengthReady failed"); + } + + return IPC_OK(); + } + + int64_t length = -1; + if (InputStreamLengthHelper::GetSyncLength(stream, &length)) { + Unused << SendLengthReady(length); + return IPC_OK(); + } + + RefPtr<RemoteLazyInputStreamParent> self = this; + InputStreamLengthHelper::GetAsyncLength(stream, [self](int64_t aLength) { + if (self->mContentManager || self->mPBackgroundManager || + self->mSocketProcessManager) { + Unused << self->SendLengthReady(aLength); + } + }); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult RemoteLazyInputStreamParent::RecvClose() { + MOZ_ASSERT(mContentManager || mPBackgroundManager || mSocketProcessManager); + + Unused << Send__delete__(this); + return IPC_OK(); +} + +mozilla::ipc::IPCResult RemoteLazyInputStreamParent::Recv__delete__() { + MOZ_ASSERT(mContentManager || mPBackgroundManager || mSocketProcessManager); + mMigrating = true; + return IPC_OK(); +} + +bool RemoteLazyInputStreamParent::HasValidStream() const { + auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr); + return storage ? storage->HasStream(mID) : false; +} + +} // namespace mozilla diff --git a/dom/file/ipc/RemoteLazyInputStreamParent.h b/dom/file/ipc/RemoteLazyInputStreamParent.h new file mode 100644 index 0000000000..c9d6ea341e --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStreamParent.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_RemoteLazyInputStreamParent_h +#define mozilla_RemoteLazyInputStreamParent_h + +#include "mozilla/PRemoteLazyInputStreamParent.h" + +class nsIInputStream; + +namespace mozilla { + +namespace dom { +class ContentParent; +} + +namespace net { +class SocketProcessParent; +} + +class NS_NO_VTABLE RemoteLazyInputStreamParentCallback { + public: + virtual void ActorDestroyed(const nsID& aID) = 0; + + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + protected: + virtual ~RemoteLazyInputStreamParentCallback() = default; +}; + +class RemoteLazyInputStreamParent final : public PRemoteLazyInputStreamParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteLazyInputStreamParent, final) + + // The size of the inputStream must be passed as argument in order to avoid + // the use of nsIInputStream::Available() which could open a fileDescriptor in + // case the stream is a nsFileStream. + template <typename M> + static already_AddRefed<RemoteLazyInputStreamParent> Create( + nsIInputStream* aInputStream, uint64_t aSize, uint64_t aChildID, + nsresult* aRv, M* aManager); + + static already_AddRefed<RemoteLazyInputStreamParent> Create( + const nsID& aID, uint64_t aSize, + mozilla::ipc::PBackgroundParent* aManager); + + static already_AddRefed<RemoteLazyInputStreamParent> Create( + const nsID& aID, uint64_t aSize, + mozilla::net::SocketProcessParent* aManager); + + void ActorDestroy(IProtocol::ActorDestroyReason aReason) override; + + const nsID& ID() const { return mID; } + + uint64_t Size() const { return mSize; } + + void SetCallback(RemoteLazyInputStreamParentCallback* aCallback); + + mozilla::ipc::IPCResult RecvStreamNeeded(); + + mozilla::ipc::IPCResult RecvLengthNeeded(); + + mozilla::ipc::IPCResult RecvClose(); + + mozilla::ipc::IPCResult Recv__delete__() override; + + bool HasValidStream() const; + + private: + RemoteLazyInputStreamParent(const nsID& aID, uint64_t aSize, + mozilla::dom::ContentParent* aManager); + + RemoteLazyInputStreamParent(const nsID& aID, uint64_t aSize, + mozilla::ipc::PBackgroundParent* aManager); + + RemoteLazyInputStreamParent(const nsID& aID, uint64_t aSize, + mozilla::net::SocketProcessParent* aManager); + + ~RemoteLazyInputStreamParent() = default; + + const nsID mID; + const uint64_t mSize; + + // Only 1 of these is set. Raw pointer because these managers are keeping + // the parent actor alive. The pointers will be nullified in ActorDestroyed. + mozilla::dom::ContentParent* mContentManager; + mozilla::ipc::PBackgroundParent* mPBackgroundManager; + mozilla::net::SocketProcessParent* mSocketProcessManager; + + RefPtr<RemoteLazyInputStreamParentCallback> mCallback; + + bool mMigrating; +}; + +} // namespace mozilla + +#endif // mozilla_RemoteLazyInputStreamParent_h diff --git a/dom/file/ipc/RemoteLazyInputStreamStorage.cpp b/dom/file/ipc/RemoteLazyInputStreamStorage.cpp new file mode 100644 index 0000000000..94d1ec78f7 --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStreamStorage.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/SlicedInputStream.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "nsIPropertyBag2.h" +#include "nsStreamUtils.h" +#include "RemoteLazyInputStreamParent.h" +#include "RemoteLazyInputStreamStorage.h" + +namespace mozilla { + +using namespace hal; + +namespace { +StaticMutex gMutex; +StaticRefPtr<RemoteLazyInputStreamStorage> gStorage; +} // namespace + +NS_INTERFACE_MAP_BEGIN(RemoteLazyInputStreamStorage) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(RemoteLazyInputStreamStorage) +NS_IMPL_RELEASE(RemoteLazyInputStreamStorage) + +/* static */ +Result<RefPtr<RemoteLazyInputStreamStorage>, nsresult> +RemoteLazyInputStreamStorage::Get() { + mozilla::StaticMutexAutoLock lock(gMutex); + if (gStorage) { + RefPtr<RemoteLazyInputStreamStorage> storage = gStorage; + return storage; + } + + return Err(NS_ERROR_NOT_INITIALIZED); +} + +/* static */ +void RemoteLazyInputStreamStorage::Initialize() { + mozilla::StaticMutexAutoLock lock(gMutex); + MOZ_ASSERT(!gStorage); + + gStorage = new RemoteLazyInputStreamStorage(); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(gStorage, "xpcom-shutdown", false); + obs->AddObserver(gStorage, "ipc:content-shutdown", false); + } +} + +NS_IMETHODIMP +RemoteLazyInputStreamStorage::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "xpcom-shutdown")) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "xpcom-shutdown"); + obs->RemoveObserver(this, "ipc:content-shutdown"); + } + + mozilla::StaticMutexAutoLock lock(gMutex); + gStorage = nullptr; + return NS_OK; + } + + MOZ_ASSERT(!strcmp(aTopic, "ipc:content-shutdown")); + + nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject); + if (NS_WARN_IF(!props)) { + return NS_ERROR_FAILURE; + } + + uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN; + props->GetPropertyAsUint64(u"childID"_ns, &childID); + if (NS_WARN_IF(childID == CONTENT_PROCESS_ID_UNKNOWN)) { + return NS_ERROR_FAILURE; + } + + mozilla::StaticMutexAutoLock lock(gMutex); + + for (auto iter = mStorage.Iter(); !iter.Done(); iter.Next()) { + if (iter.Data()->mChildID == childID) { + iter.Remove(); + } + } + + return NS_OK; +} + +void RemoteLazyInputStreamStorage::AddStream(nsIInputStream* aInputStream, + const nsID& aID, uint64_t aSize, + uint64_t aChildID) { + MOZ_ASSERT(aInputStream); + + StreamData* data = new StreamData(); + data->mInputStream = aInputStream; + data->mChildID = aChildID; + data->mSize = aSize; + + mozilla::StaticMutexAutoLock lock(gMutex); + mStorage.Put(aID, data); +} + +nsCOMPtr<nsIInputStream> RemoteLazyInputStreamStorage::ForgetStream( + const nsID& aID) { + UniquePtr<StreamData> entry; + + mozilla::StaticMutexAutoLock lock(gMutex); + mStorage.Remove(aID, &entry); + + if (!entry) { + return nullptr; + } + + return std::move(entry->mInputStream); +} + +bool RemoteLazyInputStreamStorage::HasStream(const nsID& aID) { + mozilla::StaticMutexAutoLock lock(gMutex); + StreamData* data = mStorage.Get(aID); + return !!data; +} + +void RemoteLazyInputStreamStorage::GetStream(const nsID& aID, uint64_t aStart, + uint64_t aLength, + nsIInputStream** aInputStream) { + *aInputStream = nullptr; + + nsCOMPtr<nsIInputStream> inputStream; + uint64_t size; + + // NS_CloneInputStream cannot be called when the mutex is locked because it + // can, recursively call GetStream() in case the child actor lives on the + // parent process. + { + mozilla::StaticMutexAutoLock lock(gMutex); + StreamData* data = mStorage.Get(aID); + if (!data) { + return; + } + + inputStream = data->mInputStream; + size = data->mSize; + } + + MOZ_ASSERT(inputStream); + + // We cannot return always the same inputStream because not all of them are + // able to be reused. Better to clone them. + + nsCOMPtr<nsIInputStream> clonedStream; + nsCOMPtr<nsIInputStream> replacementStream; + + nsresult rv = NS_CloneInputStream(inputStream, getter_AddRefs(clonedStream), + getter_AddRefs(replacementStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + if (replacementStream) { + mozilla::StaticMutexAutoLock lock(gMutex); + StreamData* data = mStorage.Get(aID); + // data can be gone in the meantime. + if (!data) { + return; + } + + data->mInputStream = replacementStream; + } + + // Now it's the right time to apply a slice if needed. + if (aStart > 0 || aLength < size) { + clonedStream = + new SlicedInputStream(clonedStream.forget(), aStart, aLength); + } + + clonedStream.forget(aInputStream); +} + +void RemoteLazyInputStreamStorage::StoreCallback( + const nsID& aID, RemoteLazyInputStreamParentCallback* aCallback) { + MOZ_ASSERT(aCallback); + + mozilla::StaticMutexAutoLock lock(gMutex); + StreamData* data = mStorage.Get(aID); + if (data) { + MOZ_ASSERT(!data->mCallback); + data->mCallback = aCallback; + } +} + +already_AddRefed<RemoteLazyInputStreamParentCallback> +RemoteLazyInputStreamStorage::TakeCallback(const nsID& aID) { + mozilla::StaticMutexAutoLock lock(gMutex); + StreamData* data = mStorage.Get(aID); + if (!data) { + return nullptr; + } + + RefPtr<RemoteLazyInputStreamParentCallback> callback; + data->mCallback.swap(callback); + return callback.forget(); +} + +} // namespace mozilla diff --git a/dom/file/ipc/RemoteLazyInputStreamStorage.h b/dom/file/ipc/RemoteLazyInputStreamStorage.h new file mode 100644 index 0000000000..c62f759a0a --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStreamStorage.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_RemoteLazyInputStreamStorage_h +#define mozilla_RemoteLazyInputStreamStorage_h + +#include "mozilla/RefPtr.h" +#include "nsClassHashtable.h" +#include "nsIObserver.h" + +class nsIInputStream; +struct nsID; + +namespace mozilla { + +class RemoteLazyInputStreamParentCallback; + +class RemoteLazyInputStreamStorage final : public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + // This initializes the singleton and it must be called on the main-thread. + static void Initialize(); + + static Result<RefPtr<RemoteLazyInputStreamStorage>, nsresult> Get(); + + void AddStream(nsIInputStream* aInputStream, const nsID& aID, uint64_t aSize, + uint64_t aChildID); + + // Removes and returns the stream corresponding to the nsID. May return a + // nullptr if there's no stream stored for the nsID. + nsCOMPtr<nsIInputStream> ForgetStream(const nsID& aID); + + bool HasStream(const nsID& aID); + + void GetStream(const nsID& aID, uint64_t aStart, uint64_t aLength, + nsIInputStream** aInputStream); + + void StoreCallback(const nsID& aID, + RemoteLazyInputStreamParentCallback* aCallback); + + already_AddRefed<RemoteLazyInputStreamParentCallback> TakeCallback( + const nsID& aID); + + private: + RemoteLazyInputStreamStorage() = default; + ~RemoteLazyInputStreamStorage() = default; + + struct StreamData { + nsCOMPtr<nsIInputStream> mInputStream; + RefPtr<RemoteLazyInputStreamParentCallback> mCallback; + + // This is the Process ID connected with this inputStream. We need to store + // this information in order to delete it if the child crashes/shutdowns. + uint64_t mChildID; + + uint64_t mSize; + }; + + nsClassHashtable<nsIDHashKey, StreamData> mStorage; +}; + +} // namespace mozilla + +#endif // mozilla_RemoteLazyInputStreamStorage_h diff --git a/dom/file/ipc/RemoteLazyInputStreamThread.cpp b/dom/file/ipc/RemoteLazyInputStreamThread.cpp new file mode 100644 index 0000000000..c03cc99425 --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStreamThread.cpp @@ -0,0 +1,255 @@ +/* -*- 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 "RemoteLazyInputStreamThread.h" + +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TaskCategory.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "nsXPCOMPrivate.h" + +using namespace mozilla::ipc; + +namespace mozilla { + +namespace { + +StaticMutex gRemoteLazyThreadMutex; +StaticRefPtr<RemoteLazyInputStreamThread> gRemoteLazyThread; +bool gShutdownHasStarted = false; + +class ThreadInitializeRunnable final : public Runnable { + public: + ThreadInitializeRunnable() : Runnable("dom::ThreadInitializeRunnable") {} + + NS_IMETHOD + Run() override { + StaticMutexAutoLock lock(gRemoteLazyThreadMutex); + MOZ_ASSERT(gRemoteLazyThread); + gRemoteLazyThread->InitializeOnMainThread(); + return NS_OK; + } +}; + +class MigrateActorRunnable final : public Runnable { + public: + explicit MigrateActorRunnable(RemoteLazyInputStreamChild* aActor) + : Runnable("dom::MigrateActorRunnable"), mActor(aActor) { + MOZ_ASSERT(mActor); + } + + NS_IMETHOD + Run() override { + MOZ_ASSERT(mActor->State() == + RemoteLazyInputStreamChild::eInactiveMigrating); + + PBackgroundChild* actorChild = + BackgroundChild::GetOrCreateForCurrentThread(); + if (!actorChild) { + return NS_OK; + } + + if (actorChild->SendPRemoteLazyInputStreamConstructor(mActor, mActor->ID(), + mActor->Size())) { + mActor->Migrated(); + } + + return NS_OK; + } + + private: + ~MigrateActorRunnable() = default; + + RefPtr<RemoteLazyInputStreamChild> mActor; +}; + +} // namespace + +NS_IMPL_ISUPPORTS(RemoteLazyInputStreamThread, nsIObserver, nsIEventTarget) + +/* static */ +bool RemoteLazyInputStreamThread::IsOnFileEventTarget( + nsIEventTarget* aEventTarget) { + MOZ_ASSERT(aEventTarget); + + // Note that we don't migrate actors when we are on the socket process + // because, on that process, we don't have complex life-time contexts such + // as workers and documents. + if (XRE_IsSocketProcess()) { + return true; + } + + StaticMutexAutoLock lock(gRemoteLazyThreadMutex); + return gRemoteLazyThread && aEventTarget == gRemoteLazyThread->mThread; +} + +/* static */ +RemoteLazyInputStreamThread* RemoteLazyInputStreamThread::Get() { + StaticMutexAutoLock lock(gRemoteLazyThreadMutex); + + if (gShutdownHasStarted) { + return nullptr; + } + + return gRemoteLazyThread; +} + +/* static */ +RemoteLazyInputStreamThread* RemoteLazyInputStreamThread::GetOrCreate() { + StaticMutexAutoLock lock(gRemoteLazyThreadMutex); + + if (gShutdownHasStarted) { + return nullptr; + } + + if (!gRemoteLazyThread) { + gRemoteLazyThread = new RemoteLazyInputStreamThread(); + if (!gRemoteLazyThread->Initialize()) { + return nullptr; + } + } + + return gRemoteLazyThread; +} + +bool RemoteLazyInputStreamThread::Initialize() { + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_NewNamedThread("RemoteLzyStream", getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + mThread = thread; + + if (!mPendingActors.IsEmpty()) { + for (uint32_t i = 0; i < mPendingActors.Length(); ++i) { + MigrateActorInternal(mPendingActors[i]); + } + + mPendingActors.Clear(); + } + + if (!NS_IsMainThread()) { + RefPtr<Runnable> runnable = new ThreadInitializeRunnable(); + SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget()); + return true; + } + + InitializeOnMainThread(); + return true; +} + +void RemoteLazyInputStreamThread::InitializeOnMainThread() { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return; + } + + nsresult rv = + obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } +} + +NS_IMETHODIMP +RemoteLazyInputStreamThread::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)); + + StaticMutexAutoLock lock(gRemoteLazyThreadMutex); + + if (mThread) { + mThread->Shutdown(); + mThread = nullptr; + } + + gShutdownHasStarted = true; + gRemoteLazyThread = nullptr; + + return NS_OK; +} + +void RemoteLazyInputStreamThread::MigrateActor( + RemoteLazyInputStreamChild* aActor) { + MOZ_ASSERT(aActor->State() == RemoteLazyInputStreamChild::eInactiveMigrating); + + StaticMutexAutoLock lock(gRemoteLazyThreadMutex); + + if (gShutdownHasStarted) { + return; + } + + if (!mThread) { + // The thread is not initialized yet. + mPendingActors.AppendElement(aActor); + return; + } + + MigrateActorInternal(aActor); +} + +void RemoteLazyInputStreamThread::MigrateActorInternal( + RemoteLazyInputStreamChild* aActor) { + RefPtr<Runnable> runnable = new MigrateActorRunnable(aActor); + mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); +} + +// nsIEventTarget + +NS_IMETHODIMP_(bool) +RemoteLazyInputStreamThread::IsOnCurrentThreadInfallible() { + return mThread->IsOnCurrentThread(); +} + +NS_IMETHODIMP +RemoteLazyInputStreamThread::IsOnCurrentThread(bool* aRetval) { + return mThread->IsOnCurrentThread(aRetval); +} + +NS_IMETHODIMP +RemoteLazyInputStreamThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable, + uint32_t aFlags) { + nsCOMPtr<nsIRunnable> runnable(aRunnable); + + StaticMutexAutoLock lock(gRemoteLazyThreadMutex); + + if (gShutdownHasStarted) { + return NS_ERROR_NOT_INITIALIZED; + } + + return mThread->Dispatch(runnable.forget(), aFlags); +} + +NS_IMETHODIMP +RemoteLazyInputStreamThread::DispatchFromScript(nsIRunnable* aRunnable, + uint32_t aFlags) { + nsCOMPtr<nsIRunnable> runnable(aRunnable); + return Dispatch(runnable.forget(), aFlags); +} + +NS_IMETHODIMP +RemoteLazyInputStreamThread::DelayedDispatch(already_AddRefed<nsIRunnable>, + uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +bool IsOnDOMFileThread() { + StaticMutexAutoLock lock(gRemoteLazyThreadMutex); + + MOZ_ASSERT(!gShutdownHasStarted); + MOZ_ASSERT(gRemoteLazyThread); + + return gRemoteLazyThread->IsOnCurrentThreadInfallible(); +} + +void AssertIsOnDOMFileThread() { MOZ_ASSERT(IsOnDOMFileThread()); } + +} // namespace mozilla diff --git a/dom/file/ipc/RemoteLazyInputStreamThread.h b/dom/file/ipc/RemoteLazyInputStreamThread.h new file mode 100644 index 0000000000..44e0069c05 --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStreamThread.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_RemoteLazyInputStreamThread_h +#define mozilla_RemoteLazyInputStreamThread_h + +#include "mozilla/RemoteLazyInputStreamChild.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" +#include "nsTArray.h" + +class nsIThread; + +namespace mozilla { + +class RemoteLazyInputStreamChild; + +class RemoteLazyInputStreamThread final : public nsIObserver, + public nsIEventTarget { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSIEVENTTARGET + + static bool IsOnFileEventTarget(nsIEventTarget* aEventTarget); + + static RemoteLazyInputStreamThread* Get(); + + static RemoteLazyInputStreamThread* GetOrCreate(); + + void MigrateActor(RemoteLazyInputStreamChild* aActor); + + bool Initialize(); + + void InitializeOnMainThread(); + + private: + ~RemoteLazyInputStreamThread() = default; + + void MigrateActorInternal(RemoteLazyInputStreamChild* aActor); + + nsCOMPtr<nsIThread> mThread; + + // This is populated if MigrateActor() is called before the initialization of + // the thread. + nsTArray<RefPtr<RemoteLazyInputStreamChild>> mPendingActors; +}; + +bool IsOnDOMFileThread(); + +void AssertIsOnDOMFileThread(); + +} // namespace mozilla + +#endif // mozilla_RemoteLazyInputStreamThread_h diff --git a/dom/file/ipc/RemoteLazyInputStreamUtils.cpp b/dom/file/ipc/RemoteLazyInputStreamUtils.cpp new file mode 100644 index 0000000000..33fce73e14 --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStreamUtils.cpp @@ -0,0 +1,119 @@ +/* -*- 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 "RemoteLazyInputStreamUtils.h" +#include "RemoteLazyInputStream.h" +#include "RemoteLazyInputStreamChild.h" +#include "RemoteLazyInputStreamParent.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/PBackgroundParent.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "RemoteLazyInputStreamStorage.h" +#include "StreamBlobImpl.h" + +namespace mozilla { + +namespace { + +template <typename M> +nsresult SerializeInputStreamParent(nsIInputStream* aInputStream, + uint64_t aSize, uint64_t aChildID, + PRemoteLazyInputStreamParent*& aActorParent, + M* aManager) { + // Parent to Child we always send a RemoteLazyInputStream. + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr<nsIInputStream> stream = aInputStream; + + // In case this is a RemoteLazyInputStream, we don't want to create a loop: + // RemoteLazyInputStreamParent -> RemoteLazyInputStream -> + // RemoteLazyInputStreamParent. Let's use the underlying inputStream instead. + nsCOMPtr<mozIRemoteLazyInputStream> remoteLazyInputStream = + do_QueryInterface(aInputStream); + if (remoteLazyInputStream) { + stream = remoteLazyInputStream->GetInternalStream(); + // If we don't have an underlying stream, it's better to terminate here + // instead of sending an 'empty' RemoteLazyInputStream actor on the other + // side, unable to be used. + if (NS_WARN_IF(!stream)) { + return NS_ERROR_FAILURE; + } + } + + nsresult rv; + RefPtr<RemoteLazyInputStreamParent> parentActor = + RemoteLazyInputStreamParent::Create(stream, aSize, aChildID, &rv, + aManager); + if (!parentActor) { + return rv; + } + + if (!aManager->SendPRemoteLazyInputStreamConstructor( + parentActor, parentActor->ID(), parentActor->Size())) { + return NS_ERROR_FAILURE; + } + + aActorParent = parentActor; + return NS_OK; +} + +} // anonymous namespace + +// static +nsresult RemoteLazyInputStreamUtils::SerializeInputStream( + nsIInputStream* aInputStream, uint64_t aSize, RemoteLazyStream& aOutStream, + dom::ContentParent* aManager) { + PRemoteLazyInputStreamParent* actor = nullptr; + nsresult rv = SerializeInputStreamParent( + aInputStream, aSize, aManager->ChildID(), actor, aManager); + NS_ENSURE_SUCCESS(rv, rv); + + aOutStream = actor; + return NS_OK; +} + +// static +nsresult RemoteLazyInputStreamUtils::SerializeInputStream( + nsIInputStream* aInputStream, uint64_t aSize, RemoteLazyStream& aOutStream, + mozilla::ipc::PBackgroundParent* aManager) { + PRemoteLazyInputStreamParent* actor = nullptr; + nsresult rv = SerializeInputStreamParent( + aInputStream, aSize, mozilla::ipc::BackgroundParent::GetChildID(aManager), + actor, aManager); + NS_ENSURE_SUCCESS(rv, rv); + + aOutStream = actor; + return NS_OK; +} + +// static +nsresult RemoteLazyInputStreamUtils::SerializeInputStream( + nsIInputStream* aInputStream, uint64_t aSize, RemoteLazyStream& aOutStream, + dom::ContentChild* aManager) { + mozilla::ipc::AutoIPCStream ipcStream(true /* delayed start */); + if (!ipcStream.Serialize(aInputStream, aManager)) { + return NS_ERROR_FAILURE; + } + + aOutStream = ipcStream.TakeValue(); + return NS_OK; +} + +// static +nsresult RemoteLazyInputStreamUtils::SerializeInputStream( + nsIInputStream* aInputStream, uint64_t aSize, RemoteLazyStream& aOutStream, + mozilla::ipc::PBackgroundChild* aManager) { + mozilla::ipc::AutoIPCStream ipcStream(true /* delayed start */); + if (!ipcStream.Serialize(aInputStream, aManager)) { + return NS_ERROR_FAILURE; + } + + aOutStream = ipcStream.TakeValue(); + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/file/ipc/RemoteLazyInputStreamUtils.h b/dom/file/ipc/RemoteLazyInputStreamUtils.h new file mode 100644 index 0000000000..b65d46d004 --- /dev/null +++ b/dom/file/ipc/RemoteLazyInputStreamUtils.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_RemoteLazyInputStreamUtils_h +#define mozilla_RemoteLazyInputStreamUtils_h + +#include <cstdint> + +/* + * RemoteLazyInputStream was previously part of the IPCBlob world. + * See IPCBlobUtils.h to know how to use it. As a follow up, the documentation + * will be partially moved here too. + */ + +class nsIInputStream; +enum class nsresult : uint32_t; + +namespace mozilla { + +class RemoteLazyStream; + +namespace ipc { +class IPCStream; +class PBackgroundChild; +class PBackgroundParent; +} // namespace ipc + +namespace dom { +class ContentChild; +class ContentParent; +} // namespace dom + +class RemoteLazyInputStreamUtils final { + public: + static nsresult SerializeInputStream(nsIInputStream* aInputStream, + uint64_t aSize, + RemoteLazyStream& aOutStream, + dom::ContentParent* aManager); + + static nsresult SerializeInputStream( + nsIInputStream* aInputStream, uint64_t aSize, + RemoteLazyStream& aOutStream, mozilla::ipc::PBackgroundParent* aManager); + + static nsresult SerializeInputStream(nsIInputStream* aInputStream, + uint64_t aSize, + RemoteLazyStream& aOutStream, + dom::ContentChild* aManager); + + static nsresult SerializeInputStream( + nsIInputStream* aInputStream, uint64_t aSize, + RemoteLazyStream& aOutStream, mozilla::ipc::PBackgroundChild* aManager); +}; + +} // namespace mozilla + +#endif // mozilla_RemoteLazyInputStreamUtils_h diff --git a/dom/file/ipc/TemporaryIPCBlobChild.cpp b/dom/file/ipc/TemporaryIPCBlobChild.cpp new file mode 100644 index 0000000000..2c66d4ec3b --- /dev/null +++ b/dom/file/ipc/TemporaryIPCBlobChild.cpp @@ -0,0 +1,85 @@ +/* -*- 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 "TemporaryIPCBlobChild.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/MutableBlobStorage.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include <private/pprio.h> + +namespace mozilla::dom { + +TemporaryIPCBlobChild::TemporaryIPCBlobChild(MutableBlobStorage* aStorage) + : mMutableBlobStorage(aStorage), mActive(true) { + MOZ_ASSERT(aStorage); +} + +TemporaryIPCBlobChild::~TemporaryIPCBlobChild() = default; + +mozilla::ipc::IPCResult TemporaryIPCBlobChild::RecvFileDesc( + const FileDescriptor& aFD) { + MOZ_ASSERT(mActive); + + auto rawFD = aFD.ClonePlatformHandle(); + PRFileDesc* prfile = PR_ImportFile(PROsfd(rawFD.release())); + + mMutableBlobStorage->TemporaryFileCreated(prfile); + mMutableBlobStorage = nullptr; + return IPC_OK(); +} + +mozilla::ipc::IPCResult TemporaryIPCBlobChild::Recv__delete__( + const IPCBlobOrError& aData) { + mActive = false; + mMutableBlobStorage = nullptr; + + if (aData.type() == IPCBlobOrError::TIPCBlob) { + // This must be always deserialized. + RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(aData.get_IPCBlob()); + MOZ_ASSERT(blobImpl); + + if (mCallback) { + mCallback->OperationSucceeded(blobImpl); + } + } else if (mCallback) { + MOZ_ASSERT(aData.type() == IPCBlobOrError::Tnsresult); + mCallback->OperationFailed(aData.get_nsresult()); + } + + mCallback = nullptr; + + return IPC_OK(); +} + +void TemporaryIPCBlobChild::ActorDestroy(ActorDestroyReason aWhy) { + mActive = false; + mMutableBlobStorage = nullptr; + + if (mCallback) { + mCallback->OperationFailed(NS_ERROR_FAILURE); + mCallback = nullptr; + } +} + +void TemporaryIPCBlobChild::AskForBlob(TemporaryIPCBlobChildCallback* aCallback, + const nsACString& aContentType, + PRFileDesc* aFD) { + MOZ_ASSERT(aCallback); + MOZ_ASSERT(!mCallback); + + if (!mActive) { + aCallback->OperationFailed(NS_ERROR_FAILURE); + return; + } + + FileDescriptor fdd = FileDescriptor( + FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(aFD))); + + mCallback = aCallback; + SendOperationDone(nsCString(aContentType), fdd); +} + +} // namespace mozilla::dom diff --git a/dom/file/ipc/TemporaryIPCBlobChild.h b/dom/file/ipc/TemporaryIPCBlobChild.h new file mode 100644 index 0000000000..bafdd37380 --- /dev/null +++ b/dom/file/ipc/TemporaryIPCBlobChild.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_TemporaryIPCBlobChild_h +#define mozilla_dom_TemporaryIPCBlobChild_h + +#include "mozilla/dom/PTemporaryIPCBlob.h" +#include "mozilla/dom/PTemporaryIPCBlobChild.h" + +namespace mozilla { +namespace dom { + +class BlobImpl; +class MutableBlobStorage; + +class TemporaryIPCBlobChildCallback { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + virtual void OperationSucceeded(BlobImpl* aBlobImpl) = 0; + virtual void OperationFailed(nsresult aRv) = 0; +}; + +class TemporaryIPCBlobChild final : public PTemporaryIPCBlobChild { + friend class PTemporaryIPCBlobChild; + + public: + NS_INLINE_DECL_REFCOUNTING(TemporaryIPCBlobChild) + + explicit TemporaryIPCBlobChild(MutableBlobStorage* aMutableBlobStorage); + + void AskForBlob(TemporaryIPCBlobChildCallback* aCallback, + const nsACString& aContentType, PRFileDesc* aFD); + + private: + ~TemporaryIPCBlobChild(); + + mozilla::ipc::IPCResult RecvFileDesc(const FileDescriptor& aFD); + + mozilla::ipc::IPCResult Recv__delete__(const IPCBlobOrError& aBlobOrError); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + RefPtr<MutableBlobStorage> mMutableBlobStorage; + RefPtr<TemporaryIPCBlobChildCallback> mCallback; + bool mActive; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_TemporaryIPCBlobChild_h diff --git a/dom/file/ipc/TemporaryIPCBlobParent.cpp b/dom/file/ipc/TemporaryIPCBlobParent.cpp new file mode 100644 index 0000000000..719316054c --- /dev/null +++ b/dom/file/ipc/TemporaryIPCBlobParent.cpp @@ -0,0 +1,102 @@ +/* -*- 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 "private/pprio.h" +#include "TemporaryIPCBlobParent.h" +#include "mozilla/dom/FileBlobImpl.h" +#include "nsAnonymousTemporaryFile.h" +#include "TemporaryFileBlobImpl.h" +#include "mozilla/dom/IPCBlobUtils.h" + +namespace mozilla::dom { + +TemporaryIPCBlobParent::TemporaryIPCBlobParent() : mActive(true) {} + +TemporaryIPCBlobParent::~TemporaryIPCBlobParent() { + // If we still have mFile, let's remove it. + if (mFile) { + mFile->Remove(false); + } +} + +mozilla::ipc::IPCResult TemporaryIPCBlobParent::CreateAndShareFile() { + MOZ_ASSERT(mActive); + MOZ_ASSERT(!mFile); + + nsresult rv = NS_OpenAnonymousTemporaryNsIFile(getter_AddRefs(mFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return SendDeleteError(rv); + } + + PRFileDesc* fd; + rv = mFile->OpenNSPRFileDesc(PR_RDWR, PR_IRWXU, &fd); + if (NS_WARN_IF(NS_FAILED(rv))) { + return SendDeleteError(rv); + } + + FileDescriptor fdd = FileDescriptor( + FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(fd))); + + // The FileDescriptor object owns a duplicate of the file handle; we + // must close the original (and clean up the NSPR descriptor). + PR_Close(fd); + + Unused << SendFileDesc(fdd); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TemporaryIPCBlobParent::RecvOperationFailed() { + MOZ_ASSERT(mActive); + mActive = false; + + // Nothing to do. + Unused << Send__delete__(this, NS_ERROR_FAILURE); + return IPC_OK(); +} + +mozilla::ipc::IPCResult TemporaryIPCBlobParent::RecvOperationDone( + const nsCString& aContentType, const FileDescriptor& aFD) { + MOZ_ASSERT(mActive); + mActive = false; + + // We have received a file descriptor because in this way we have kept the + // file locked on windows during the IPC communication. After the creation of + // the TemporaryFileBlobImpl, this prfile can be closed. + auto rawFD = aFD.ClonePlatformHandle(); + PRFileDesc* prfile = PR_ImportFile(PROsfd(rawFD.release())); + + // Let's create the BlobImpl. + nsCOMPtr<nsIFile> file = std::move(mFile); + + RefPtr<TemporaryFileBlobImpl> blobImpl = + new TemporaryFileBlobImpl(file, NS_ConvertUTF8toUTF16(aContentType)); + + PR_Close(prfile); + + IPCBlob ipcBlob; + nsresult rv = IPCBlobUtils::Serialize(blobImpl, Manager(), ipcBlob); + if (NS_WARN_IF(NS_FAILED(rv))) { + Unused << Send__delete__(this, NS_ERROR_FAILURE); + return IPC_OK(); + } + + Unused << Send__delete__(this, ipcBlob); + return IPC_OK(); +} + +void TemporaryIPCBlobParent::ActorDestroy(ActorDestroyReason aWhy) { + mActive = false; +} + +mozilla::ipc::IPCResult TemporaryIPCBlobParent::SendDeleteError(nsresult aRv) { + MOZ_ASSERT(mActive); + mActive = false; + + Unused << Send__delete__(this, aRv); + return IPC_OK(); +} + +} // namespace mozilla::dom diff --git a/dom/file/ipc/TemporaryIPCBlobParent.h b/dom/file/ipc/TemporaryIPCBlobParent.h new file mode 100644 index 0000000000..10339bd4c9 --- /dev/null +++ b/dom/file/ipc/TemporaryIPCBlobParent.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_TemporaryIPCBlobParent_h +#define mozilla_dom_TemporaryIPCBlobParent_h + +#include "mozilla/dom/PTemporaryIPCBlob.h" +#include "mozilla/dom/PTemporaryIPCBlobParent.h" + +class nsIFile; + +namespace mozilla { +namespace dom { + +class TemporaryIPCBlobParent final : public PTemporaryIPCBlobParent { + friend class PTemporaryIPCBlobParent; + + public: + explicit TemporaryIPCBlobParent(); + + mozilla::ipc::IPCResult CreateAndShareFile(); + + private: + ~TemporaryIPCBlobParent(); + + mozilla::ipc::IPCResult RecvOperationFailed(); + + mozilla::ipc::IPCResult RecvOperationDone(const nsCString& aContentType, + const FileDescriptor& aFD); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + mozilla::ipc::IPCResult SendDeleteError(nsresult aRv); + + nsCOMPtr<nsIFile> mFile; + bool mActive; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_TemporaryIPCBlobParent_h diff --git a/dom/file/ipc/moz.build b/dom/file/ipc/moz.build new file mode 100644 index 0000000000..ae62ffa2dc --- /dev/null +++ b/dom/file/ipc/moz.build @@ -0,0 +1,74 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: File") + +XPIDL_SOURCES += [ + "mozIRemoteLazyInputStream.idl", +] + +XPIDL_MODULE = "dom" + +EXPORTS.mozilla.dom += [ + "FileCreatorChild.h", + "FileCreatorParent.h", + "IPCBlobUtils.h", + "TemporaryIPCBlobChild.h", + "TemporaryIPCBlobParent.h", +] + +EXPORTS.mozilla += [ + "RemoteLazyInputStream.h", + "RemoteLazyInputStreamChild.h", + "RemoteLazyInputStreamParent.h", + "RemoteLazyInputStreamStorage.h", + "RemoteLazyInputStreamThread.h", + "RemoteLazyInputStreamUtils.h", +] + +UNIFIED_SOURCES += [ + "FileCreatorChild.cpp", + "FileCreatorParent.cpp", + "IPCBlobUtils.cpp", + "RemoteLazyInputStream.cpp", + "RemoteLazyInputStreamChild.cpp", + "RemoteLazyInputStreamParent.cpp", + "RemoteLazyInputStreamStorage.cpp", + "RemoteLazyInputStreamThread.cpp", + "RemoteLazyInputStreamUtils.cpp", + "TemporaryIPCBlobChild.cpp", + "TemporaryIPCBlobParent.cpp", +] + +IPDL_SOURCES += [ + "BlobTypes.ipdlh", + "IPCBlob.ipdlh", + "PFileCreator.ipdl", + "PRemoteLazyInputStream.ipdl", + "PTemporaryIPCBlob.ipdl", +] + +LOCAL_INCLUDES += [ + "/dom/file", + "/dom/ipc", + "/xpcom/build", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") + +FINAL_LIBRARY = "xul" + +CXXFLAGS += CONFIG["TK_CFLAGS"] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] + +BROWSER_CHROME_MANIFESTS += ["tests/browser.ini"] +MOCHITEST_MANIFESTS += ["tests/mochitest.ini"] diff --git a/dom/file/ipc/mozIRemoteLazyInputStream.idl b/dom/file/ipc/mozIRemoteLazyInputStream.idl new file mode 100644 index 0000000000..07d77d0cb2 --- /dev/null +++ b/dom/file/ipc/mozIRemoteLazyInputStream.idl @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; + +/* + * A simple interface to get the underlying stream from an + * RemoteLazyInputStream. + */ +[scriptable, builtinclass, uuid(4125585f-b0c2-4964-a83c-4b0d99f26d49)] +interface mozIRemoteLazyInputStream : nsISupports +{ + [notxpcom, noscript] nsIInputStream GetInternalStream(); +}; diff --git a/dom/file/ipc/tests/browser.ini b/dom/file/ipc/tests/browser.ini new file mode 100644 index 0000000000..f6fcdc37a4 --- /dev/null +++ b/dom/file/ipc/tests/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = + empty.html + +[browser_ipcBlob.js] +[browser_ipcBlob_temporary.js] +support-files = temporary.sjs diff --git a/dom/file/ipc/tests/browser_ipcBlob.js b/dom/file/ipc/tests/browser_ipcBlob.js new file mode 100644 index 0000000000..3ba0f05c90 --- /dev/null +++ b/dom/file/ipc/tests/browser_ipcBlob.js @@ -0,0 +1,251 @@ +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */ + +requestLongerTimeout(3); + +const BASE_URI = "http://mochi.test:8888/browser/dom/file/ipc/tests/empty.html"; + +add_task(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.partition.bloburl_per_agent_cluster", false]], + }); +}); + +// More than 1mb memory blob childA-parent-childB. +add_task(async function test_CtoPtoC_big() { + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser1 = gBrowser.getBrowserForTab(tab1); + + let blob = await SpecialPowers.spawn(browser1, [], function() { + Cu.importGlobalProperties(["Blob"]); + let blob = new Blob([new Array(1024 * 1024).join("123456789ABCDEF")]); + return blob; + }); + + ok(blob, "CtoPtoC-big: We have a blob!"); + is( + blob.size, + new Array(1024 * 1024).join("123456789ABCDEF").length, + "CtoPtoC-big: The size matches" + ); + + let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser2 = gBrowser.getBrowserForTab(tab2); + + let status = await SpecialPowers.spawn(browser2, [blob], function(blob) { + return new Promise(resolve => { + let fr = new content.FileReader(); + fr.readAsText(blob); + fr.onloadend = function() { + resolve(fr.result == new Array(1024 * 1024).join("123456789ABCDEF")); + }; + }); + }); + + ok(status, "CtoPtoC-big: Data match!"); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); + +// Less than 1mb memory blob childA-parent-childB. +add_task(async function test_CtoPtoC_small() { + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser1 = gBrowser.getBrowserForTab(tab1); + + let blob = await SpecialPowers.spawn(browser1, [], function() { + Cu.importGlobalProperties(["Blob"]); + let blob = new Blob(["hello world!"]); + return blob; + }); + + ok(blob, "CtoPtoC-small: We have a blob!"); + is(blob.size, "hello world!".length, "CtoPtoC-small: The size matches"); + + let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser2 = gBrowser.getBrowserForTab(tab2); + + let status = await SpecialPowers.spawn(browser2, [blob], function(blob) { + return new Promise(resolve => { + let fr = new content.FileReader(); + fr.readAsText(blob); + fr.onloadend = function() { + resolve(fr.result == "hello world!"); + }; + }); + }); + + ok(status, "CtoPtoC-small: Data match!"); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); + +// More than 1mb memory blob childA-parent-childB: BroadcastChannel +add_task(async function test_CtoPtoC_bc_big() { + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser1 = gBrowser.getBrowserForTab(tab1); + + await SpecialPowers.spawn(browser1, [], function() { + Cu.importGlobalProperties(["Blob"]); + var bc = new content.BroadcastChannel("test"); + bc.onmessage = function() { + bc.postMessage( + new Blob([new Array(1024 * 1024).join("123456789ABCDEF")]) + ); + }; + }); + + let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser2 = gBrowser.getBrowserForTab(tab2); + + let status = await SpecialPowers.spawn(browser2, [], function() { + return new Promise(resolve => { + var bc = new content.BroadcastChannel("test"); + bc.onmessage = function(e) { + let fr = new content.FileReader(); + fr.readAsText(e.data); + fr.onloadend = function() { + resolve(fr.result == new Array(1024 * 1024).join("123456789ABCDEF")); + }; + }; + + bc.postMessage("GO!"); + }); + }); + + ok(status, "CtoPtoC-broadcastChannel-big: Data match!"); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); + +// Less than 1mb memory blob childA-parent-childB: BroadcastChannel +add_task(async function test_CtoPtoC_bc_small() { + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser1 = gBrowser.getBrowserForTab(tab1); + + await SpecialPowers.spawn(browser1, [], function() { + Cu.importGlobalProperties(["Blob"]); + var bc = new content.BroadcastChannel("test"); + bc.onmessage = function() { + bc.postMessage(new Blob(["hello world!"])); + }; + }); + + let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser2 = gBrowser.getBrowserForTab(tab2); + + let status = await SpecialPowers.spawn(browser2, [], function() { + return new Promise(resolve => { + var bc = new content.BroadcastChannel("test"); + bc.onmessage = function(e) { + let fr = new content.FileReader(); + fr.readAsText(e.data); + fr.onloadend = function() { + resolve(fr.result == "hello world!"); + }; + }; + + bc.postMessage("GO!"); + }); + }); + + ok(status, "CtoPtoC-broadcastChannel-small: Data match!"); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); + +// blob URL childA-parent-childB +add_task(async function test_CtoPtoC_bc_small() { + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser1 = gBrowser.getBrowserForTab(tab1); + + let blobURL = await SpecialPowers.spawn(browser1, [], function() { + Cu.importGlobalProperties(["Blob"]); + return content.URL.createObjectURL(new content.Blob(["hello world!"])); + }); + + let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser2 = gBrowser.getBrowserForTab(tab2); + + let status = await SpecialPowers.spawn(browser2, [blobURL], function( + blobURL + ) { + return new Promise(resolve => { + var xhr = new content.XMLHttpRequest(); + xhr.open("GET", blobURL); + xhr.onloadend = function() { + resolve(xhr.response == "hello world!"); + }; + + xhr.send(); + }); + }); + + ok(status, "CtoPtoC-blobURL: Data match!"); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); + +// Multipart Blob childA-parent-childB. +add_task(async function test_CtoPtoC_multipart() { + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser1 = gBrowser.getBrowserForTab(tab1); + + let blob = await SpecialPowers.spawn(browser1, [], function() { + Cu.importGlobalProperties(["Blob"]); + return new Blob(["!"]); + }); + + ok(blob, "CtoPtoC-multipart: We have a blob!"); + is(blob.size, "!".length, "CtoPtoC-multipart: The size matches"); + + let newBlob = new Blob(["world", blob]); + + let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser2 = gBrowser.getBrowserForTab(tab2); + + let status = await SpecialPowers.spawn(browser2, [newBlob], function(blob) { + Cu.importGlobalProperties(["Blob"]); + return new Promise(resolve => { + let fr = new content.FileReader(); + fr.readAsText(new Blob(["hello ", blob])); + fr.onloadend = function() { + resolve(fr.result == "hello world!"); + }; + }); + }); + + ok(status, "CtoPtoC-multipart: Data match!"); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); + +// Multipart Blob childA-parent with a max size +add_task(async function test_CtoPsize_multipart() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser = gBrowser.getBrowserForTab(tab); + + let blob = await SpecialPowers.spawn(browser, [], function() { + Cu.importGlobalProperties(["Blob"]); + + let data = new Array(1024 * 512).join("A"); + let blob1 = new Blob([data]); + let blob2 = new Blob([data]); + let blob3 = new Blob([data]); + + return new Blob([blob1, blob2, blob3]); + }); + + ok(blob, "CtoPsize-multipart: We have a blob!"); + is( + blob.size, + new Array(1024 * 512).join("A").length * 3, + "CtoPsize-multipart: The size matches" + ); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/dom/file/ipc/tests/browser_ipcBlob_temporary.js b/dom/file/ipc/tests/browser_ipcBlob_temporary.js new file mode 100644 index 0000000000..c2d9d54e87 --- /dev/null +++ b/dom/file/ipc/tests/browser_ipcBlob_temporary.js @@ -0,0 +1,115 @@ +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */ + +requestLongerTimeout(3); + +const BASE_URI = "http://mochi.test:8888/browser/dom/file/ipc/tests/empty.html"; + +add_task(async function test() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.blob.memoryToTemporaryFile", 1], + ["dom.ipc.processCount", 4], + ], + }); + + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser1 = gBrowser.getBrowserForTab(tab1); + + let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI); + let browser2 = gBrowser.getBrowserForTab(tab2); + + await SpecialPowers.spawn(browser2, [], function() { + content.window.testPromise = new content.window.Promise(resolve => { + let bc = new content.window.BroadcastChannel("foobar"); + bc.onmessage = e => { + function realTest() { + return new content.window.Promise(resolve => { + let count = 10; + for (let i = 0; i < count; ++i) { + info("FileReader at the same time: " + i); + let fr = new content.window.FileReader(); + fr.readAsText(e.data); + fr.onerror = () => { + ok(false, "Something wrong happened."); + }; + + fr.onloadend = () => { + is(fr.result.length, e.data.size, "FileReader worked fine."); + if (!--count) { + resolve(true); + } + }; + } + }); + } + + let promises = []; + for (let i = 0; i < 5; ++i) { + promises.push(realTest()); + } + + Promise.all(promises).then(() => { + resolve(true); + }); + }; + }); + }); + + let status = await SpecialPowers.spawn(browser1, [], function() { + let p = new content.window.Promise(resolve => { + let xhr = new content.window.XMLHttpRequest(); + xhr.open("GET", "temporary.sjs", true); + xhr.responseType = "blob"; + xhr.onload = () => { + resolve(xhr.response); + }; + xhr.send(); + }); + + return p.then(blob => { + function realTest() { + return new content.window.Promise(resolve => { + info("Let's broadcast the blob..."); + let bc = new content.window.BroadcastChannel("foobar"); + bc.postMessage(blob); + + info("Here the test..."); + let count = 10; + for (let i = 0; i < count; ++i) { + info("FileReader at the same time: " + i); + let fr = new content.window.FileReader(); + fr.readAsText(blob); + fr.onerror = () => { + ok(false, "Something wrong happened."); + }; + + fr.onloadend = () => { + is(fr.result.length, blob.size, "FileReader worked fine."); + if (!--count) { + resolve(true); + } + }; + } + }); + } + + let promises = []; + for (let i = 0; i < 5; ++i) { + promises.push(realTest()); + } + + return Promise.all(promises); + }); + }); + + ok(status, "All good for tab1!"); + + status = await SpecialPowers.spawn(browser2, [], function() { + return content.window.testPromise; + }); + + ok(status, "All good for tab2!"); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); diff --git a/dom/file/ipc/tests/empty.html b/dom/file/ipc/tests/empty.html new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/file/ipc/tests/empty.html diff --git a/dom/file/ipc/tests/green.jpg b/dom/file/ipc/tests/green.jpg Binary files differnew file mode 100644 index 0000000000..48c454d27c --- /dev/null +++ b/dom/file/ipc/tests/green.jpg diff --git a/dom/file/ipc/tests/mochitest.ini b/dom/file/ipc/tests/mochitest.ini new file mode 100644 index 0000000000..75bda265fb --- /dev/null +++ b/dom/file/ipc/tests/mochitest.ini @@ -0,0 +1,10 @@ +[DEFAULT] +support-files = script_file.js + +[test_ipcBlob_fileReaderSync.html] +[test_ipcBlob_workers.html] +[test_ipcBlob_createImageBitmap.html] +support-files = green.jpg +[test_ipcBlob_emptyMultiplex.html] +[test_ipcBlob_mixedMultiplex.html] +support-files = ok.sjs diff --git a/dom/file/ipc/tests/ok.sjs b/dom/file/ipc/tests/ok.sjs new file mode 100644 index 0000000000..edca5e4692 --- /dev/null +++ b/dom/file/ipc/tests/ok.sjs @@ -0,0 +1,10 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) +{ + response.setHeader("Content-Type", "text/html", false); + response.write(request.getHeader("Content-Length")); +} diff --git a/dom/file/ipc/tests/script_file.js b/dom/file/ipc/tests/script_file.js new file mode 100644 index 0000000000..d3147a9b16 --- /dev/null +++ b/dom/file/ipc/tests/script_file.js @@ -0,0 +1,51 @@ +Cu.importGlobalProperties(["File"]); + +addMessageListener("file.open", function(e) { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIDirectoryService) + .QueryInterface(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + testFile.append("ipc_fileReader_testing"); + testFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + + var outStream = Cc[ + "@mozilla.org/network/file-output-stream;1" + ].createInstance(Ci.nsIFileOutputStream); + outStream.init( + testFile, + 0x02 | 0x08 | 0x20, // write, create, truncate + 0666, + 0 + ); + + var fileData = "Hello World!"; + outStream.write(fileData, fileData.length); + outStream.close(); + + File.createFromNsIFile(testFile).then(function(file) { + sendAsyncMessage("file.opened", { file }); + }); +}); + +addMessageListener("emptyfile.open", function(e) { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIDirectoryService) + .QueryInterface(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + testFile.append("ipc_fileReader_testing"); + testFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + + var outStream = Cc[ + "@mozilla.org/network/file-output-stream;1" + ].createInstance(Ci.nsIFileOutputStream); + outStream.init( + testFile, + 0x02 | 0x08 | 0x20, // write, create, truncate + 0666, + 0 + ); + + File.createFromNsIFile(testFile).then(function(file) { + sendAsyncMessage("emptyfile.opened", { file }); + }); +}); diff --git a/dom/file/ipc/tests/temporary.sjs b/dom/file/ipc/tests/temporary.sjs new file mode 100644 index 0000000000..ab81ab474e --- /dev/null +++ b/dom/file/ipc/tests/temporary.sjs @@ -0,0 +1,7 @@ +function handleRequest(request, response) +{ + response.setHeader("Content-Type", "text/plain", false); + + var data = new Array(1024*64).join("1234567890ABCDEF"); + response.bodyOutputStream.write(data, data.length); +} diff --git a/dom/file/ipc/tests/test_ipcBlob_createImageBitmap.html b/dom/file/ipc/tests/test_ipcBlob_createImageBitmap.html new file mode 100644 index 0000000000..868888c529 --- /dev/null +++ b/dom/file/ipc/tests/test_ipcBlob_createImageBitmap.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test IPCBlob and CreateImageBitmap</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function test_mainThread() { + let bc = new BroadcastChannel('testMainThread'); + bc.onmessage = e => { + createImageBitmap(e.data).then(image => { + ok(image.height, "this image has a valid size."); + }, () => { + ok(false, "error creating the image!"); + }).then(next); + } + + fetch('green.jpg').then(r => r.blob()).then(blob => { + let bc = new BroadcastChannel('testMainThread'); + bc.postMessage(blob); + }); +} + +function test_worker() { + function workerScript() { + function ok(a, msg) { postMessage({ type: 'test', status: !!a, msg }); }; + function finish() { postMessage({ type: 'finish' }); }; + + let bc = new BroadcastChannel('testWorker'); + bc.onmessage = e => { + createImageBitmap(e.data).then(image => { + ok(image.height, "this image has a valid size."); + }, () => { + ok(false, "error creating the image!"); + }).then(finish); + } + + fetch('http://mochi.test:8888/tests/dom/file/ipc/tests/green.jpg').then(r => r.blob()).then(blob => { + let bc = new BroadcastChannel('testWorker'); + bc.postMessage(blob); + }); + } + let workerUrl = URL.createObjectURL(new Blob(["(", workerScript.toString(), ")()"])); + let worker = new Worker(workerUrl); + + worker.onmessage = event => { + if (event.data.type == 'test') { + ok(event.data.status, event.data.msg); + return; + } + + if (event.data.type == 'finish') { + next(); + } + } +} + +let tests = [ + test_mainThread, + test_worker, +]; + +function next() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + let test = tests.shift(); + test(); +} + +next(); + +</script> +</pre> +</body> +</html> diff --git a/dom/file/ipc/tests/test_ipcBlob_emptyMultiplex.html b/dom/file/ipc/tests/test_ipcBlob_emptyMultiplex.html new file mode 100644 index 0000000000..0487069467 --- /dev/null +++ b/dom/file/ipc/tests/test_ipcBlob_emptyMultiplex.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test an empty IPCBlob together with other parts</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="text/javascript"> + +function checkContent(msg, content) { + return new Promise(resolve => { + let fr = new FileReader(); + fr.readAsText(new Blob(content)); + fr.onloadend = () => { + is(fr.result, "Hello world!", "The content matches: " + msg); + resolve(); + }; + }); +} + +SimpleTest.waitForExplicitFinish(); + +let url = SimpleTest.getTestFileURL("script_file.js"); + +let script = SpecialPowers.loadChromeScript(url); +script.addMessageListener("emptyfile.opened", message => { + checkContent("middle", ["Hello ", message.file, "world!"]). + then(() => checkContent("begin", [message.file, "Hello world!"])). + then(() => checkContent("end", ["Hello world!", message.file])). + then(() => checkContent("random", [message.file, message.file, "Hello world!", message.file])). + then(() => checkContent("random 2", [message.file, message.file, "Hello ", + message.file, "world", message.file, + message.file, "!", message.file, "", + message.file, message.file])). + then(SimpleTest.finish); +}); + +script.sendAsyncMessage("emptyfile.open"); + +</script> +</pre> +</body> +</html> diff --git a/dom/file/ipc/tests/test_ipcBlob_fileReaderSync.html b/dom/file/ipc/tests/test_ipcBlob_fileReaderSync.html new file mode 100644 index 0000000000..f37d4b79ed --- /dev/null +++ b/dom/file/ipc/tests/test_ipcBlob_fileReaderSync.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test IPCBlob and FileReaderSync</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="text/javascript"> + +function workerScript() { + onmessage = function(event) { + let readerMemoryBlob = new FileReaderSync(); + let status = readerMemoryBlob.readAsText(new Blob(['hello world'])) == 'hello world'; + postMessage({ status, message: "FileReaderSync with memory blob still works" }); + + let readerIPCBlob1 = new FileReaderSync(); + postMessage({ blob: event.data, method: 'readAsText', + data: readerIPCBlob1.readAsText(event.data)}); + + let readerIPCBlob2 = new FileReaderSync(); + postMessage({ blob: event.data, method: 'readAsArrayBuffer', + data: readerIPCBlob2.readAsArrayBuffer(event.data)}); + + let readerIPCBlob3 = new FileReaderSync(); + postMessage({ blob: event.data, method: 'readAsDataURL', + data: readerIPCBlob3.readAsDataURL(event.data)}); + + let multipartBlob = new Blob(['wow', event.data]); + + let readerIPCBlobMultipart1 = new FileReaderSync(); + postMessage({ blob: multipartBlob, method: 'readAsText', + data: readerIPCBlobMultipart1.readAsText(multipartBlob)}); + + let readerIPCBlobMultipart2 = new FileReaderSync(); + postMessage({ blob: multipartBlob, method: 'readAsArrayBuffer', + data: readerIPCBlobMultipart2.readAsArrayBuffer(multipartBlob)}); + + let readerIPCBlobMultipart3 = new FileReaderSync(); + postMessage({ blob: multipartBlob, method: 'readAsDataURL', + data: readerIPCBlobMultipart3.readAsDataURL(multipartBlob)}); + + postMessage({ finish: true }); + } +} + +let completed = false; +let pendingTasks = 0; +function maybeFinish() { + if (completed && !pendingTasks) { + SimpleTest.finish(); + } +} + +let workerUrl = URL.createObjectURL(new Blob(["(", workerScript.toString(), ")()"])); +let worker = new Worker(workerUrl); +worker.onmessage = event => { + if ("status" in event.data) { + ok(event.data.status, event.data.message); + return; + } + + if ("blob" in event.data) { + let fr = new FileReader(); + fr[event.data.method](event.data.blob); + ++pendingTasks; + fr.onload = () => { + if (event.data.method != 'readAsArrayBuffer') { + is(event.data.data, fr.result, "The file has been read"); + } else { + is(event.data.data.byteLength, fr.result.byteLength, "The file has been read"); + } + --pendingTasks; + maybeFinish(); + } + + return; + } + + if ("finish" in event.data) { + completed = true; + maybeFinish(); + } +}; + +let url = SimpleTest.getTestFileURL("script_file.js"); +let script = SpecialPowers.loadChromeScript(url); +script.addMessageListener("file.opened", message => { + worker.postMessage(message.file); +}); + +script.sendAsyncMessage("file.open"); + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/file/ipc/tests/test_ipcBlob_mixedMultiplex.html b/dom/file/ipc/tests/test_ipcBlob_mixedMultiplex.html new file mode 100644 index 0000000000..c8e046fa88 --- /dev/null +++ b/dom/file/ipc/tests/test_ipcBlob_mixedMultiplex.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test an empty IPCBlob together with other parts</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="text/javascript"> + +let url = SimpleTest.getTestFileURL("script_file.js"); +let data = new Array(1024*1024).join('A'); + +let script = SpecialPowers.loadChromeScript(url); +script.addMessageListener("file.opened", message => { + let blob = new Blob([data]); + + let form = new FormData(); + form.append("blob1", blob); + form.append("blob2", message.file); + form.append("blob3", blob); + + fetch("ok.sjs", { + method: "POST", + body: form, + }) + .then(r => r.text()) + .then(r => { + ok(parseInt(r, 10) > (data.length * 2), "We have data"); + }) + . then(SimpleTest.finish); +}); + +script.sendAsyncMessage("file.open"); +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/file/ipc/tests/test_ipcBlob_workers.html b/dom/file/ipc/tests/test_ipcBlob_workers.html new file mode 100644 index 0000000000..a473948f25 --- /dev/null +++ b/dom/file/ipc/tests/test_ipcBlob_workers.html @@ -0,0 +1,121 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test IPCBlob and Workers</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="text/javascript"> + +function test_workerOwner() { + info("test_workerOwner"); + + function workerScript() { + onmessage = e => { + e.ports[0].onmessage = event => { + let reader = new FileReader(); + reader.readAsText(event.data); + reader.onloadend = () => { + let status = reader.result == 'hello world'; + postMessage(status); + } + } + } + } + + let mc = new MessageChannel(); + mc.port1.postMessage(new Blob(['hello world'])); + + let workerUrl = URL.createObjectURL(new Blob(["(", workerScript.toString(), ")()"])); + let worker = new Worker(workerUrl); + + worker.postMessage("", [mc.port2]); + worker.onmessage = event => { + ok(event.data, "All is done!"); + next(); + } +} + +function test_workerToMainThread() { + info("test_workerToMainThread"); + function workerScript() { + onmessage = e => { + e.ports[0].onmessage = event => { + postMessage(event.data); + } + } + } + + let mc = new MessageChannel(); + mc.port1.postMessage(new Blob(['hello world'])); + + let workerUrl = URL.createObjectURL(new Blob(["(", workerScript.toString(), ")()"])); + let worker = new Worker(workerUrl); + + worker.postMessage("", [mc.port2]); + worker.onmessage = event => { + info("Blob received back, terminate the worker and force GC"); + worker.terminate(); + worker = null; + SpecialPowers.forceGC(); + + var fr = new FileReader(); + fr.readAsText(event.data); + fr.onloadend = () => { + is(fr.result, "hello world", "Data matches"); + next(); + } + } +} + +function test_workerOwnerPlusFileReaderSync() { + info("test_workerOwnerPlusFileReaderSync"); + + function workerScript() { + onmessage = e => { + e.ports[0].onmessage = event => { + let reader = new FileReaderSync(); + let status = reader.readAsText(event.data) == 'hello world'; + postMessage(status); + } + } + } + + let mc = new MessageChannel(); + mc.port1.postMessage(new Blob(['hello world'])); + + let workerUrl = URL.createObjectURL(new Blob(["(", workerScript.toString(), ")()"])); + let worker = new Worker(workerUrl); + + worker.postMessage("", [mc.port2]); + worker.onmessage = event => { + ok(event.data, "All is done!"); + next(); + } +} + +var tests = [ + test_workerOwner, + test_workerToMainThread, + test_workerOwnerPlusFileReaderSync, +]; + +function next() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +SimpleTest.waitForExplicitFinish(); +next(); + +</script> +</pre> +</body> +</html> |