summaryrefslogtreecommitdiffstats
path: root/dom/file/ipc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/file/ipc/FileCreatorChild.cpp59
-rw-r--r--dom/file/ipc/FileCreatorChild.h33
-rw-r--r--dom/file/ipc/FileCreatorParent.cpp136
-rw-r--r--dom/file/ipc/FileCreatorParent.h45
-rw-r--r--dom/file/ipc/IPCBlob.ipdlh56
-rw-r--r--dom/file/ipc/IPCBlobUtils.cpp179
-rw-r--r--dom/file/ipc/IPCBlobUtils.h268
-rw-r--r--dom/file/ipc/PFileCreator.ipdl38
-rw-r--r--dom/file/ipc/PRemoteLazyInputStream.ipdl21
-rw-r--r--dom/file/ipc/PTemporaryIPCBlob.ipdl40
-rw-r--r--dom/file/ipc/RemoteLazyInputStream.cpp1424
-rw-r--r--dom/file/ipc/RemoteLazyInputStream.h155
-rw-r--r--dom/file/ipc/RemoteLazyInputStreamChild.cpp55
-rw-r--r--dom/file/ipc/RemoteLazyInputStreamChild.h41
-rw-r--r--dom/file/ipc/RemoteLazyInputStreamParent.cpp123
-rw-r--r--dom/file/ipc/RemoteLazyInputStreamParent.h44
-rw-r--r--dom/file/ipc/RemoteLazyInputStreamStorage.cpp243
-rw-r--r--dom/file/ipc/RemoteLazyInputStreamStorage.h78
-rw-r--r--dom/file/ipc/RemoteLazyInputStreamThread.cpp237
-rw-r--r--dom/file/ipc/RemoteLazyInputStreamThread.h52
-rw-r--r--dom/file/ipc/TemporaryIPCBlobChild.cpp86
-rw-r--r--dom/file/ipc/TemporaryIPCBlobChild.h53
-rw-r--r--dom/file/ipc/TemporaryIPCBlobParent.cpp102
-rw-r--r--dom/file/ipc/TemporaryIPCBlobParent.h43
-rw-r--r--dom/file/ipc/moz.build69
-rw-r--r--dom/file/ipc/mozIRemoteLazyInputStream.idl29
-rw-r--r--dom/file/ipc/tests/browser.ini7
-rw-r--r--dom/file/ipc/tests/browser_ipcBlob.js251
-rw-r--r--dom/file/ipc/tests/browser_ipcBlob_temporary.js115
-rw-r--r--dom/file/ipc/tests/empty.html0
-rw-r--r--dom/file/ipc/tests/green.jpgbin0 -> 361 bytes
-rw-r--r--dom/file/ipc/tests/mochitest.ini10
-rw-r--r--dom/file/ipc/tests/ok.sjs11
-rw-r--r--dom/file/ipc/tests/script_file.js53
-rw-r--r--dom/file/ipc/tests/temporary.sjs6
-rw-r--r--dom/file/ipc/tests/test_ipcBlob_createImageBitmap.html84
-rw-r--r--dom/file/ipc/tests/test_ipcBlob_emptyMultiplex.html45
-rw-r--r--dom/file/ipc/tests/test_ipcBlob_fileReaderSync.html100
-rw-r--r--dom/file/ipc/tests/test_ipcBlob_mixedMultiplex.html41
-rw-r--r--dom/file/ipc/tests/test_ipcBlob_workers.html121
40 files changed, 4553 insertions, 0 deletions
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..eb5dc09d92
--- /dev/null
+++ b/dom/file/ipc/FileCreatorChild.h
@@ -0,0 +1,33 @@
+/* -*- 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::dom {
+
+class FileCreatorChild final : public mozilla::dom::PFileCreatorChild {
+ friend class mozilla::dom::PFileCreatorChild;
+
+ public:
+ FileCreatorChild();
+ ~FileCreatorChild() override;
+
+ void SetPromise(Promise* aPromise);
+
+ private:
+ mozilla::ipc::IPCResult Recv__delete__(const FileCreationResult& aResult);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ RefPtr<Promise> mPromise;
+};
+
+} // namespace mozilla::dom
+
+#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..8a3f316390
--- /dev/null
+++ b/dom/file/ipc/FileCreatorParent.cpp
@@ -0,0 +1,136 @@
+/* -*- 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 nsAString& aFullPath, const nsAString& aType, const nsAString& 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, 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..f43dbee886
--- /dev/null
+++ b/dom/file/ipc/FileCreatorParent.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_FileCreatorParent_h
+#define mozilla_dom_FileCreatorParent_h
+
+#include "mozilla/dom/PFileCreatorParent.h"
+
+class nsIFile;
+
+namespace mozilla::dom {
+
+class BlobImpl;
+
+class FileCreatorParent final : public mozilla::dom::PFileCreatorParent {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileCreatorParent)
+
+ FileCreatorParent();
+
+ mozilla::ipc::IPCResult CreateAndShareFile(
+ const nsAString& aFullPath, const nsAString& aType,
+ const nsAString& aName, const Maybe<int64_t>& aLastModified,
+ const bool& aExistenceCheck, const bool& aIsFromNsIFile);
+
+ private:
+ ~FileCreatorParent() override;
+
+ 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 mozilla::dom
+
+#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..08c6b50a8d
--- /dev/null
+++ b/dom/file/ipc/IPCBlob.ipdlh
@@ -0,0 +1,56 @@
+/* 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 IPCStream;
+include ProtocolTypes;
+
+using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
+[RefCounted] using class mozilla::RemoteLazyInputStream from "mozilla/RemoteLazyInputStream.h";
+
+namespace mozilla {
+
+union RemoteLazyStream
+{
+ // Parent to Child: The child will receive a RemoteLazyInputStream. Nothing
+ // can be done with it except retrieving the size.
+ RemoteLazyInputStream;
+
+ // 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..78409da529
--- /dev/null
+++ b/dom/file/ipc/IPCBlobUtils.cpp
@@ -0,0 +1,179 @@
+/* -*- 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 "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::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::TRemoteLazyInputStream: {
+ inputStream = stream.get_RemoteLazyInputStream();
+ 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();
+}
+
+nsresult Serialize(BlobImpl* aBlobImpl, 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();
+ }
+
+ if (XRE_IsParentProcess()) {
+ RefPtr<RemoteLazyInputStream> stream =
+ RemoteLazyInputStream::WrapStream(inputStream);
+ if (NS_WARN_IF(!stream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aIPCBlob.inputStream() = stream;
+ return NS_OK;
+ }
+
+ mozilla::ipc::IPCStream stream;
+ if (!mozilla::ipc::SerializeIPCStream(inputStream.forget(), stream,
+ /* aAllowLazy */ true)) {
+ return NS_ERROR_FAILURE;
+ }
+ aIPCBlob.inputStream() = stream;
+ return NS_OK;
+}
+
+} // namespace mozilla::dom::IPCBlobUtils
+
+namespace IPC {
+
+void ParamTraits<mozilla::dom::BlobImpl*>::Write(
+ IPC::MessageWriter* aWriter, mozilla::dom::BlobImpl* aParam) {
+ nsresult rv;
+ mozilla::dom::IPCBlob ipcblob;
+ if (aParam) {
+ rv = mozilla::dom::IPCBlobUtils::Serialize(aParam, ipcblob);
+ }
+ if (!aParam || NS_WARN_IF(NS_FAILED(rv))) {
+ WriteParam(aWriter, false);
+ } else {
+ WriteParam(aWriter, true);
+ WriteParam(aWriter, ipcblob);
+ }
+}
+
+bool ParamTraits<mozilla::dom::BlobImpl*>::Read(
+ IPC::MessageReader* aReader, RefPtr<mozilla::dom::BlobImpl>* aResult) {
+ *aResult = nullptr;
+
+ bool notnull = false;
+ if (!ReadParam(aReader, &notnull)) {
+ return false;
+ }
+ if (notnull) {
+ mozilla::dom::IPCBlob ipcblob;
+ if (!ReadParam(aReader, &ipcblob)) {
+ return false;
+ }
+ *aResult = mozilla::dom::IPCBlobUtils::Deserialize(ipcblob);
+ }
+ return true;
+}
+
+} // namespace IPC
diff --git a/dom/file/ipc/IPCBlobUtils.h b/dom/file/ipc/IPCBlobUtils.h
new file mode 100644
index 0000000000..17fce3195a
--- /dev/null
+++ b/dom/file/ipc/IPCBlobUtils.h
@@ -0,0 +1,268 @@
+/* -*- 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
+ * DataPipe. See more information in IPCStreamUtils.h.
+ *
+ * In order to populate IPCStream correctly, we use SerializeIPCStream as
+ * documented in IPCStreamUtils.h.
+ *
+ * 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::dom {
+
+class IPCBlob;
+
+namespace IPCBlobUtils {
+
+already_AddRefed<BlobImpl> Deserialize(const IPCBlob& aIPCBlob);
+
+nsresult Serialize(BlobImpl* aBlobImpl, IPCBlob& aIPCBlob);
+
+} // namespace IPCBlobUtils
+} // namespace mozilla::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 ParamTraits<mozilla::dom::BlobImpl*> {
+ static void Write(IPC::MessageWriter* aWriter,
+ mozilla::dom::BlobImpl* aParam);
+ static bool Read(IPC::MessageReader* aReader,
+ RefPtr<mozilla::dom::BlobImpl>* aResult);
+};
+
+} // namespace IPC
+
+#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..d690820906
--- /dev/null
+++ b/dom/file/ipc/PFileCreator.ipdl
@@ -0,0 +1,38 @@
+/* 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 IPCBlob;
+
+namespace mozilla {
+namespace dom {
+
+struct FileCreationSuccessResult
+{
+ IPCBlob blob;
+};
+
+struct FileCreationErrorResult
+{
+ nsresult errorCode;
+};
+
+union FileCreationResult
+{
+ FileCreationSuccessResult;
+ FileCreationErrorResult;
+};
+
+[ManualDealloc]
+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..82fab20cdb
--- /dev/null
+++ b/dom/file/ipc/PRemoteLazyInputStream.ipdl
@@ -0,0 +1,21 @@
+/* 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 IPCStream;
+
+namespace mozilla {
+
+protocol PRemoteLazyInputStream
+{
+parent:
+ async Clone(Endpoint<PRemoteLazyInputStreamParent> aCloneEndpoint);
+
+ async StreamNeeded(uint64_t aStart, uint64_t aLength) returns (IPCStream? stream);
+
+ async LengthNeeded() returns (int64_t length);
+
+ async Goodbye();
+};
+
+} // namespace mozilla
diff --git a/dom/file/ipc/PTemporaryIPCBlob.ipdl b/dom/file/ipc/PTemporaryIPCBlob.ipdl
new file mode 100644
index 0000000000..2645d6fd50
--- /dev/null
+++ b/dom/file/ipc/PTemporaryIPCBlob.ipdl
@@ -0,0 +1,40 @@
+/* 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 IPCBlob;
+
+namespace mozilla {
+namespace dom {
+
+union IPCBlobOrError
+{
+ IPCBlob;
+ nsresult;
+};
+
+[ManualDealloc]
+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..726581ae13
--- /dev/null
+++ b/dom/file/ipc/RemoteLazyInputStream.cpp
@@ -0,0 +1,1424 @@
+/* -*- 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 "chrome/common/ipc_message_utils.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/Logging.h"
+#include "mozilla/PRemoteLazyInputStream.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/InputStreamParams.h"
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/ProtocolMessageUtils.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/SlicedInputStream.h"
+#include "mozilla/NonBlockingAsyncInputStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsID.h"
+#include "nsIInputStream.h"
+#include "nsIPipe.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "RemoteLazyInputStreamStorage.h"
+#include "RemoteLazyInputStreamThread.h"
+
+namespace mozilla {
+
+mozilla::LazyLogModule gRemoteLazyStreamLog("RemoteLazyStream");
+
+namespace {
+
+class InputStreamCallbackRunnable final : public DiscardableRunnable {
+ public:
+ // Note that the execution can be synchronous in case the event target is
+ // null.
+ static void Execute(already_AddRefed<nsIInputStreamCallback> aCallback,
+ already_AddRefed<nsIEventTarget> aEventTarget,
+ RemoteLazyInputStream* aStream) {
+ RefPtr<InputStreamCallbackRunnable> runnable =
+ new InputStreamCallbackRunnable(std::move(aCallback), aStream);
+
+ nsCOMPtr<nsIEventTarget> target = std::move(aEventTarget);
+ if (target) {
+ 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(
+ already_AddRefed<nsIInputStreamCallback> aCallback,
+ RemoteLazyInputStream* aStream)
+ : DiscardableRunnable("dom::InputStreamCallbackRunnable"),
+ mCallback(std::move(aCallback)),
+ mStream(aStream) {
+ MOZ_ASSERT(mCallback);
+ MOZ_ASSERT(mStream);
+ }
+
+ RefPtr<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,
+ uint64_t aStart, uint64_t aLength)
+ : mStart(aStart), mLength(aLength), mState(eInit), mActor(aActor) {
+ MOZ_ASSERT(aActor);
+
+ mActor->StreamCreated();
+
+ auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr);
+ if (storage) {
+ nsCOMPtr<nsIInputStream> stream;
+ storage->GetStream(mActor->StreamID(), mStart, mLength,
+ getter_AddRefs(stream));
+ if (stream) {
+ mState = eRunning;
+ mInnerStream = stream;
+ }
+ }
+}
+
+RemoteLazyInputStream::RemoteLazyInputStream(nsIInputStream* aStream)
+ : mStart(0), mLength(UINT64_MAX), mState(eRunning), mInnerStream(aStream) {}
+
+static already_AddRefed<RemoteLazyInputStreamChild> BindChildActor(
+ nsID aId, mozilla::ipc::Endpoint<PRemoteLazyInputStreamChild> aEndpoint) {
+ auto* thread = RemoteLazyInputStreamThread::GetOrCreate();
+ if (NS_WARN_IF(!thread)) {
+ return nullptr;
+ }
+ auto actor = MakeRefPtr<RemoteLazyInputStreamChild>(aId);
+ thread->Dispatch(
+ NS_NewRunnableFunction("RemoteLazyInputStream::BindChildActor",
+ [actor, childEp = std::move(aEndpoint)]() mutable {
+ bool ok = childEp.Bind(actor);
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("Binding child actor for %s (%p): %s",
+ nsIDToCString(actor->StreamID()).get(),
+ actor.get(), ok ? "OK" : "ERROR"));
+ }));
+
+ return actor.forget();
+}
+
+already_AddRefed<RemoteLazyInputStream> RemoteLazyInputStream::WrapStream(
+ nsIInputStream* aInputStream) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (nsCOMPtr<mozIRemoteLazyInputStream> lazyStream =
+ do_QueryInterface(aInputStream)) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("Returning already-wrapped stream"));
+ return lazyStream.forget().downcast<RemoteLazyInputStream>();
+ }
+
+ // If we have a stream and are in the parent process, create a new actor pair
+ // and transfer ownership of the stream into storage.
+ auto streamStorage = RemoteLazyInputStreamStorage::Get();
+ if (NS_WARN_IF(streamStorage.isErr())) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Warning,
+ ("Cannot wrap with no storage!"));
+ return nullptr;
+ }
+
+ nsID id = nsID::GenerateUUID();
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Wrapping stream %p as %s", aInputStream, nsIDToCString(id).get()));
+ streamStorage.inspect()->AddStream(aInputStream, id);
+
+ mozilla::ipc::Endpoint<PRemoteLazyInputStreamParent> parentEp;
+ mozilla::ipc::Endpoint<PRemoteLazyInputStreamChild> childEp;
+ MOZ_ALWAYS_SUCCEEDS(
+ PRemoteLazyInputStream::CreateEndpoints(&parentEp, &childEp));
+
+ // Bind the actor on our background thread.
+ streamStorage.inspect()->TaskQueue()->Dispatch(NS_NewRunnableFunction(
+ "RemoteLazyInputStreamParent::Bind",
+ [parentEp = std::move(parentEp), id]() mutable {
+ auto actor = MakeRefPtr<RemoteLazyInputStreamParent>(id);
+ bool ok = parentEp.Bind(actor);
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("Binding parent actor for %s (%p): %s",
+ nsIDToCString(id).get(), actor.get(), ok ? "OK" : "ERROR"));
+ }));
+
+ RefPtr<RemoteLazyInputStreamChild> actor =
+ BindChildActor(id, std::move(childEp));
+
+ if (!actor) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Warning,
+ ("Wrapping stream failed as we are probably late in shutdown!"));
+ return do_AddRef(new RemoteLazyInputStream());
+ }
+
+ return do_AddRef(new RemoteLazyInputStream(actor));
+}
+
+NS_IMETHODIMP RemoteLazyInputStream::TakeInternalStream(
+ nsIInputStream** aStream) {
+ RefPtr<RemoteLazyInputStreamChild> actor;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mState == eInit || mState == ePending) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ if (mState == eClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ if (mInputStreamCallback) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Do not call TakeInternalStream after calling AsyncWait");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Take the inner stream and return it, then close ourselves.
+ if (mInnerStream) {
+ mInnerStream.forget(aStream);
+ } else if (mAsyncInnerStream) {
+ mAsyncInnerStream.forget(aStream);
+ }
+ mState = eClosed;
+ actor = mActor.forget();
+ }
+ if (actor) {
+ actor->StreamConsumed();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP RemoteLazyInputStream::GetInternalStreamID(nsID& aID) {
+ MutexAutoLock lock(mMutex);
+ if (!mActor) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aID = mActor->StreamID();
+ return NS_OK;
+}
+
+RemoteLazyInputStream::~RemoteLazyInputStream() { Close(); }
+
+nsCString RemoteLazyInputStream::Describe() {
+ const char* state = "?";
+ switch (mState) {
+ case eInit:
+ state = "i";
+ break;
+ case ePending:
+ state = "p";
+ break;
+ case eRunning:
+ state = "r";
+ break;
+ case eClosed:
+ state = "c";
+ break;
+ }
+ return nsPrintfCString(
+ "[%p, %s, %s, %p%s, %s%s|%s%s]", this, state,
+ mActor ? nsIDToCString(mActor->StreamID()).get() : "<no actor>",
+ mInnerStream ? mInnerStream.get() : mAsyncInnerStream.get(),
+ mAsyncInnerStream ? "(A)" : "", mInputStreamCallback ? "I" : "",
+ mInputStreamCallbackEventTarget ? "+" : "",
+ mFileMetadataCallback ? "F" : "",
+ mFileMetadataCallbackEventTarget ? "+" : "");
+}
+
+// nsIInputStream interface
+
+NS_IMETHODIMP
+RemoteLazyInputStream::Available(uint64_t* aLength) {
+ nsCOMPtr<nsIAsyncInputStream> stream;
+ {
+ 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(mInnerStream || mAsyncInnerStream);
+
+ nsresult rv = EnsureAsyncRemoteStream();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ stream = mAsyncInnerStream;
+ }
+
+ MOZ_ASSERT(stream);
+ return stream->Available(aLength);
+}
+
+NS_IMETHODIMP
+RemoteLazyInputStream::Read(char* aBuffer, uint32_t aCount,
+ uint32_t* aReadCount) {
+ nsCOMPtr<nsIAsyncInputStream> stream;
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Read(%u) %s", aCount, Describe().get()));
+
+ // 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(mInnerStream || mAsyncInnerStream);
+
+ nsresult rv = EnsureAsyncRemoteStream();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ stream = mAsyncInnerStream;
+ }
+
+ MOZ_ASSERT(stream);
+ nsresult rv = stream->Read(aBuffer, aCount, aReadCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If some data has been read, we mark the stream as consumed.
+ if (*aReadCount > 0) {
+ MarkConsumed();
+ }
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Read %u/%u bytes", *aReadCount, aCount));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RemoteLazyInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult) {
+ nsCOMPtr<nsIAsyncInputStream> stream;
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("ReadSegments(%u) %s", aCount, Describe().get()));
+
+ // 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(mInnerStream || mAsyncInnerStream);
+
+ nsresult rv = EnsureAsyncRemoteStream();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Warning,
+ ("EnsureAsyncRemoteStream failed! %s %s",
+ mozilla::GetStaticErrorName(rv), Describe().get()));
+ return rv;
+ }
+
+ stream = mAsyncInnerStream;
+ }
+
+ MOZ_ASSERT(stream);
+ nsresult rv = stream->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) {
+ MarkConsumed();
+ }
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("ReadSegments %u/%u bytes", *aResult, aCount));
+
+ return NS_OK;
+}
+
+void RemoteLazyInputStream::MarkConsumed() {
+ RefPtr<RemoteLazyInputStreamChild> actor;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mActor) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("MarkConsumed %s", Describe().get()));
+ }
+
+ actor = mActor.forget();
+ }
+ if (actor) {
+ actor->StreamConsumed();
+ }
+}
+
+NS_IMETHODIMP
+RemoteLazyInputStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RemoteLazyInputStream::Close() {
+ RefPtr<RemoteLazyInputStreamChild> actor;
+
+ nsCOMPtr<nsIAsyncInputStream> asyncInnerStream;
+ nsCOMPtr<nsIInputStream> innerStream;
+
+ RefPtr<nsIInputStreamCallback> inputStreamCallback;
+ nsCOMPtr<nsIEventTarget> inputStreamCallbackEventTarget;
+
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("Close %s", Describe().get()));
+
+ actor = mActor.forget();
+
+ asyncInnerStream = mAsyncInnerStream.forget();
+ innerStream = mInnerStream.forget();
+
+ // TODO(Bug 1737783): Notify to the mFileMetadataCallback that this
+ // lazy input stream has been closed.
+ mFileMetadataCallback = nullptr;
+ mFileMetadataCallbackEventTarget = nullptr;
+
+ inputStreamCallback = mInputStreamCallback.forget();
+ inputStreamCallbackEventTarget = mInputStreamCallbackEventTarget.forget();
+
+ mState = eClosed;
+ }
+
+ if (actor) {
+ actor->StreamConsumed();
+ }
+
+ if (inputStreamCallback) {
+ InputStreamCallbackRunnable::Execute(
+ inputStreamCallback.forget(), inputStreamCallbackEventTarget.forget(),
+ this);
+ }
+
+ if (asyncInnerStream) {
+ asyncInnerStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
+ }
+
+ if (innerStream) {
+ innerStream->Close();
+ }
+
+ return NS_OK;
+}
+
+// nsICloneableInputStream interface
+
+NS_IMETHODIMP
+RemoteLazyInputStream::GetCloneable(bool* aCloneable) {
+ *aCloneable = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RemoteLazyInputStream::Clone(nsIInputStream** aResult) {
+ return CloneWithRange(0, UINT64_MAX, aResult);
+}
+
+// nsICloneableInputStreamWithRange interface
+
+NS_IMETHODIMP
+RemoteLazyInputStream::CloneWithRange(uint64_t aStart, uint64_t aLength,
+ nsIInputStream** aResult) {
+ MutexAutoLock lock(mMutex);
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("CloneWithRange %" PRIu64 " %" PRIu64 " %s", aStart, aLength,
+ Describe().get()));
+
+ nsresult rv;
+
+ RefPtr<RemoteLazyInputStream> stream;
+ if (mState == eClosed) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose, ("Cloning closed stream"));
+ stream = new RemoteLazyInputStream();
+ stream.forget(aResult);
+ return NS_OK;
+ }
+
+ uint64_t start = 0;
+ uint64_t length = 0;
+ auto maxLength = CheckedUint64(mLength) - aStart;
+ if (maxLength.isValid()) {
+ start = mStart + aStart;
+ length = std::min(maxLength.value(), aLength);
+ }
+
+ // If the slice would be empty, wrap an empty input stream and return it.
+ if (length == 0) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose, ("Creating empty stream"));
+
+ nsCOMPtr<nsIInputStream> emptyStream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(emptyStream), ""_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ stream = new RemoteLazyInputStream(emptyStream);
+ stream.forget(aResult);
+ return NS_OK;
+ }
+
+ // If we still have a connection to our actor, that means we haven't read any
+ // data yet, and can clone + slice by building a new stream backed by the same
+ // actor.
+ if (mActor) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Cloning stream with actor"));
+
+ stream = new RemoteLazyInputStream(mActor, start, length);
+ stream.forget(aResult);
+ return NS_OK;
+ }
+
+ // We no longer have our actor, either because we were constructed without
+ // one, or we've already begun reading. Perform the clone locally on our inner
+ // input stream.
+
+ nsCOMPtr<nsIInputStream> innerStream = mInnerStream;
+ if (mAsyncInnerStream) {
+ innerStream = mAsyncInnerStream;
+ }
+
+ nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(innerStream);
+ if (!cloneable || !cloneable->GetCloneable()) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Cloning non-cloneable stream - copying to pipe"));
+
+ // If our internal stream isn't cloneable, to perform a clone we'll need to
+ // copy into a pipe and replace our internal stream.
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true);
+
+ RefPtr<RemoteLazyInputStreamThread> thread =
+ RemoteLazyInputStreamThread::GetOrCreate();
+ if (NS_WARN_IF(!thread)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ mAsyncInnerStream = pipeIn;
+ mInnerStream = nullptr;
+
+ // If we have a callback pending, we need to re-call AsyncWait on the inner
+ // stream. This should not re-enter us immediately, as `pipeIn` hasn't been
+ // sent any data yet, but we may be called again as soon as `NS_AsyncCopy`
+ // has begun copying.
+ if (mInputStreamCallback) {
+ mAsyncInnerStream->AsyncWait(this, mInputStreamCallbackFlags,
+ mInputStreamCallbackRequestedCount,
+ mInputStreamCallbackEventTarget);
+ }
+
+ rv = NS_AsyncCopy(innerStream, pipeOut, thread,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // The copy failed, revert the changes we did and restore our previous
+ // inner stream.
+ mAsyncInnerStream = nullptr;
+ mInnerStream = innerStream;
+ return rv;
+ }
+
+ cloneable = do_QueryInterface(mAsyncInnerStream);
+ }
+
+ MOZ_ASSERT(cloneable && cloneable->GetCloneable());
+
+ // Check if we can clone more efficiently with a range.
+ if (length < UINT64_MAX) {
+ if (nsCOMPtr<nsICloneableInputStreamWithRange> cloneableWithRange =
+ do_QueryInterface(cloneable)) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose, ("Cloning with range"));
+ nsCOMPtr<nsIInputStream> cloned;
+ rv = cloneableWithRange->CloneWithRange(start, length,
+ getter_AddRefs(cloned));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ stream = new RemoteLazyInputStream(cloned);
+ stream.forget(aResult);
+ return NS_OK;
+ }
+ }
+
+ // Directly clone our inner stream, and then slice it if needed.
+ nsCOMPtr<nsIInputStream> cloned;
+ rv = cloneable->Clone(getter_AddRefs(cloned));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (length < UINT64_MAX) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Slicing stream with %" PRIu64 " %" PRIu64, start, length));
+ cloned = new SlicedInputStream(cloned.forget(), start, length);
+ }
+
+ stream = new RemoteLazyInputStream(cloned);
+ 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) {
+ // Ensure we always have an event target for AsyncWait callbacks, so that
+ // calls to `AsyncWait` cannot reenter us with `OnInputStreamReady`.
+ nsCOMPtr<nsIEventTarget> eventTarget = aEventTarget;
+ if (aCallback && !eventTarget) {
+ eventTarget = RemoteLazyInputStreamThread::GetOrCreate();
+ if (NS_WARN_IF(!eventTarget)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("AsyncWait(%p, %u, %u, %p) %s", aCallback, aFlags, aRequestedCount,
+ aEventTarget, Describe().get()));
+
+ // See RemoteLazyInputStream.h for more information about this state
+ // machine.
+
+ nsCOMPtr<nsIAsyncInputStream> stream;
+ switch (mState) {
+ // First call, we need to retrieve the stream from the parent actor.
+ case eInit:
+ MOZ_ASSERT(mActor);
+
+ mInputStreamCallback = aCallback;
+ mInputStreamCallbackEventTarget = eventTarget;
+ mInputStreamCallbackFlags = aFlags;
+ mInputStreamCallbackRequestedCount = aRequestedCount;
+ mState = ePending;
+
+ StreamNeeded();
+ return NS_OK;
+
+ // We are still waiting for the remote inputStream
+ case ePending: {
+ if (NS_WARN_IF(mInputStreamCallback && aCallback &&
+ mInputStreamCallback != aCallback)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mInputStreamCallback = aCallback;
+ mInputStreamCallbackEventTarget = eventTarget;
+ mInputStreamCallbackFlags = aFlags;
+ mInputStreamCallbackRequestedCount = aRequestedCount;
+ return NS_OK;
+ }
+
+ // We have the remote inputStream, let's check if we can execute the
+ // callback.
+ case eRunning: {
+ if (NS_WARN_IF(mInputStreamCallback && aCallback &&
+ mInputStreamCallback != aCallback)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = EnsureAsyncRemoteStream();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mInputStreamCallback = aCallback;
+ mInputStreamCallbackEventTarget = eventTarget;
+ mInputStreamCallbackFlags = aFlags;
+ mInputStreamCallbackRequestedCount = aRequestedCount;
+
+ stream = mAsyncInnerStream;
+ break;
+ }
+
+ case eClosed:
+ [[fallthrough]];
+ default:
+ MOZ_ASSERT(mState == eClosed);
+ if (NS_WARN_IF(mInputStreamCallback && aCallback &&
+ mInputStreamCallback != aCallback)) {
+ return NS_ERROR_FAILURE;
+ }
+ break;
+ }
+
+ if (stream) {
+ return stream->AsyncWait(aCallback ? this : nullptr, aFlags,
+ aRequestedCount, eventTarget);
+ }
+ }
+
+ if (aCallback) {
+ // if stream is nullptr here, that probably means the stream has
+ // been closed and the callback can be executed immediately
+ InputStreamCallbackRunnable::Execute(do_AddRef(aCallback),
+ do_AddRef(eventTarget), this);
+ }
+ return NS_OK;
+}
+
+void RemoteLazyInputStream::StreamNeeded() {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("StreamNeeded %s", Describe().get()));
+
+ auto* thread = RemoteLazyInputStreamThread::GetOrCreate();
+ if (NS_WARN_IF(!thread)) {
+ return;
+ }
+ thread->Dispatch(NS_NewRunnableFunction(
+ "RemoteLazyInputStream::StreamNeeded",
+ [self = RefPtr{this}, actor = mActor, start = mStart, length = mLength] {
+ MOZ_LOG(
+ gRemoteLazyStreamLog, LogLevel::Debug,
+ ("Sending StreamNeeded(%" PRIu64 " %" PRIu64 ") %s %d", start,
+ length, nsIDToCString(actor->StreamID()).get(), actor->CanSend()));
+
+ actor->SendStreamNeeded(
+ start, length,
+ [self](const Maybe<mozilla::ipc::IPCStream>& aStream) {
+ // Try to deserialize the stream from our remote, and close our
+ // stream if it fails.
+ nsCOMPtr<nsIInputStream> stream =
+ mozilla::ipc::DeserializeIPCStream(aStream);
+ if (NS_WARN_IF(!stream)) {
+ NS_WARNING("Failed to deserialize IPC stream");
+ self->Close();
+ }
+
+ // Lock our mutex to update the inner stream, and collect any
+ // callbacks which we need to invoke.
+ MutexAutoLock lock(self->mMutex);
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("ResolveStreamNeeded(%p) %s", stream.get(),
+ self->Describe().get()));
+
+ if (self->mState == ePending) {
+ self->mInnerStream = stream.forget();
+ self->mState = eRunning;
+
+ // Notify any listeners that we've now acquired the underlying
+ // stream, so file metadata information will be available.
+ nsCOMPtr<nsIFileMetadataCallback> fileMetadataCallback =
+ self->mFileMetadataCallback.forget();
+ nsCOMPtr<nsIEventTarget> fileMetadataCallbackEventTarget =
+ self->mFileMetadataCallbackEventTarget.forget();
+ if (fileMetadataCallback) {
+ FileMetadataCallbackRunnable::Execute(
+ fileMetadataCallback, fileMetadataCallbackEventTarget,
+ self);
+ }
+
+ // **NOTE** we can re-enter this class here **NOTE**
+ // If we already have an input stream callback, attempt to
+ // register ourselves with AsyncWait on the underlying stream.
+ if (self->mInputStreamCallback) {
+ if (NS_FAILED(self->EnsureAsyncRemoteStream()) ||
+ NS_FAILED(self->mAsyncInnerStream->AsyncWait(
+ self, self->mInputStreamCallbackFlags,
+ self->mInputStreamCallbackRequestedCount,
+ self->mInputStreamCallbackEventTarget))) {
+ InputStreamCallbackRunnable::Execute(
+ self->mInputStreamCallback.forget(),
+ self->mInputStreamCallbackEventTarget.forget(), self);
+ }
+ }
+ }
+
+ if (stream) {
+ NS_WARNING("Failed to save stream, closing it");
+ stream->Close();
+ }
+ },
+ [self](mozilla::ipc::ResponseRejectReason) {
+ NS_WARNING("SendStreamNeeded rejected");
+ self->Close();
+ });
+ }));
+}
+
+// nsIInputStreamCallback
+
+NS_IMETHODIMP
+RemoteLazyInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ RefPtr<nsIInputStreamCallback> callback;
+ nsCOMPtr<nsIEventTarget> callbackEventTarget;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("OnInputStreamReady %s", Describe().get()));
+
+ // We have been closed in the meantime.
+ if (mState == eClosed) {
+ return NS_OK;
+ }
+
+ // We got a callback from the wrong stream, likely due to a `CloneWithRange`
+ // call while we were waiting. Ignore this callback.
+ if (mAsyncInnerStream != aStream) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mState == eRunning);
+
+ // 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.forget(),
+ callbackEventTarget.forget(), this);
+ return NS_OK;
+}
+
+// nsIIPCSerializableInputStream
+
+void RemoteLazyInputStream::SerializedComplexity(uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ uint32_t* aNewPipes,
+ uint32_t* aTransferables) {
+ *aTransferables = 1;
+}
+
+void RemoteLazyInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams,
+ uint32_t aMaxSize, uint32_t* aSizeUsed) {
+ *aSizeUsed = 0;
+ aParams = mozilla::ipc::RemoteLazyInputStreamParams(this);
+}
+
+bool RemoteLazyInputStream::Deserialize(
+ const mozilla::ipc::InputStreamParams& aParams) {
+ 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);
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("AsyncFileMetadataWait(%p, %p) %s", aCallback, aEventTarget,
+ Describe().get()));
+
+ 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;
+
+ StreamNeeded();
+ 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);
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("GetSize %s", Describe().get()));
+
+ fileMetadata = do_QueryInterface(mInnerStream);
+ 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);
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("GetLastModified %s", Describe().get()));
+
+ fileMetadata = do_QueryInterface(mInnerStream);
+ 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);
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("GetFileDescriptor %s", Describe().get()));
+
+ fileMetadata = do_QueryInterface(mInnerStream);
+ if (!fileMetadata) {
+ return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE;
+ }
+ }
+
+ return fileMetadata->GetFileDescriptor(aRetval);
+}
+
+nsresult RemoteLazyInputStream::EnsureAsyncRemoteStream() {
+ // We already have an async remote stream.
+ if (mAsyncInnerStream) {
+ return NS_OK;
+ }
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("EnsureAsyncRemoteStream %s", Describe().get()));
+
+ if (NS_WARN_IF(!mInnerStream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIInputStream> stream = mInnerStream;
+
+ // Check if the stream is blocking, if it is, we want to make it non-blocking
+ // using a pipe.
+ bool nonBlocking = false;
+ nsresult rv = stream->IsNonBlocking(&nonBlocking);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // 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.
+ //
+ // We only need to do this if we won't be wrapping the stream in a pipe, which
+ // will add buffering anyway.
+ if (nonBlocking && !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;
+ }
+
+ 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;
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true);
+
+ RefPtr<RemoteLazyInputStreamThread> thread =
+ RemoteLazyInputStreamThread::GetOrCreate();
+ if (NS_WARN_IF(!thread)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ rv = NS_AsyncCopy(stream, pipeOut, thread, NS_ASYNCCOPY_VIA_WRITESEGMENTS);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ asyncStream = pipeIn;
+ }
+
+ MOZ_ASSERT(asyncStream);
+ mAsyncInnerStream = asyncStream;
+ mInnerStream = nullptr;
+
+ return NS_OK;
+}
+
+// nsIInputStreamLength
+
+NS_IMETHODIMP
+RemoteLazyInputStream::Length(int64_t* aLength) {
+ MutexAutoLock lock(mMutex);
+
+ if (mState == eClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (!mActor) {
+ 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);
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("AsyncLengthWait(%p, %p) %s", aCallback, aEventTarget,
+ Describe().get()));
+
+ if (mActor) {
+ if (aCallback) {
+ auto* thread = RemoteLazyInputStreamThread::GetOrCreate();
+ if (NS_WARN_IF(!thread)) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+ thread->Dispatch(NS_NewRunnableFunction(
+ "RemoteLazyInputStream::AsyncLengthWait",
+ [self = RefPtr{this}, actor = mActor,
+ callback = nsCOMPtr{aCallback},
+ eventTarget = nsCOMPtr{aEventTarget}] {
+ actor->SendLengthNeeded(
+ [self, callback, eventTarget](int64_t aLength) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("AsyncLengthWait resolve %" PRId64, aLength));
+ int64_t length = -1;
+ if (aLength > 0) {
+ uint64_t sourceLength =
+ aLength - std::min<uint64_t>(aLength, self->mStart);
+ length = int64_t(
+ std::min<uint64_t>(sourceLength, self->mLength));
+ }
+ InputStreamLengthCallbackRunnable::Execute(
+ callback, eventTarget, self, length);
+ },
+ [self, callback,
+ eventTarget](mozilla::ipc::ResponseRejectReason) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Warning,
+ ("AsyncLengthWait reject"));
+ InputStreamLengthCallbackRunnable::Execute(
+ callback, eventTarget, self, -1);
+ });
+ }));
+ }
+
+ return NS_OK;
+ }
+ }
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("AsyncLengthWait immediate"));
+
+ // 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::IPCWrite(IPC::MessageWriter* aWriter) {
+ // If we have an actor still, serialize efficiently by cloning our actor to
+ // maintain a reference to the parent side.
+ RefPtr<RemoteLazyInputStreamChild> actor;
+
+ nsCOMPtr<nsIInputStream> innerStream;
+
+ RefPtr<nsIInputStreamCallback> inputStreamCallback;
+ nsCOMPtr<nsIEventTarget> inputStreamCallbackEventTarget;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Serialize %s", Describe().get()));
+
+ actor = mActor.forget();
+
+ if (mAsyncInnerStream) {
+ MOZ_ASSERT(!mInnerStream);
+ innerStream = mAsyncInnerStream.forget();
+ } else {
+ innerStream = mInnerStream.forget();
+ }
+
+ // TODO(Bug 1737783): Notify to the mFileMetadataCallback that this
+ // lazy input stream has been closed.
+ mFileMetadataCallback = nullptr;
+ mFileMetadataCallbackEventTarget = nullptr;
+
+ inputStreamCallback = mInputStreamCallback.forget();
+ inputStreamCallbackEventTarget = mInputStreamCallbackEventTarget.forget();
+
+ mState = eClosed;
+ }
+
+ if (inputStreamCallback) {
+ InputStreamCallbackRunnable::Execute(
+ inputStreamCallback.forget(), inputStreamCallbackEventTarget.forget(),
+ this);
+ }
+
+ bool closed = !actor && !innerStream;
+ IPC::WriteParam(aWriter, closed);
+ if (closed) {
+ return;
+ }
+
+ // If we still have a connection to our remote actor, create a clone endpoint
+ // for it and tell it that the stream has been consumed. The clone of the
+ // connection can be transferred to another process.
+ if (actor) {
+ MOZ_LOG(
+ gRemoteLazyStreamLog, LogLevel::Debug,
+ ("Serializing as actor: %s", nsIDToCString(actor->StreamID()).get()));
+ // Create a clone of the actor, and then tell it that this stream is no
+ // longer referencing it.
+ mozilla::ipc::Endpoint<PRemoteLazyInputStreamParent> parentEp;
+ mozilla::ipc::Endpoint<PRemoteLazyInputStreamChild> childEp;
+ MOZ_ALWAYS_SUCCEEDS(
+ PRemoteLazyInputStream::CreateEndpoints(&parentEp, &childEp));
+
+ auto* thread = RemoteLazyInputStreamThread::GetOrCreate();
+ if (thread) {
+ thread->Dispatch(NS_NewRunnableFunction(
+ "RemoteLazyInputStreamChild::SendClone",
+ [actor, parentEp = std::move(parentEp)]() mutable {
+ bool ok = actor->SendClone(std::move(parentEp));
+ MOZ_LOG(
+ gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("SendClone for %s: %s", nsIDToCString(actor->StreamID()).get(),
+ ok ? "OK" : "ERR"));
+ }));
+
+ } // else we are shutting down xpcom threads.
+
+ // NOTE: Call `StreamConsumed` after dispatching the `SendClone` runnable,
+ // as this method may dispatch a runnable to `RemoteLazyInputStreamThread`
+ // to call `SendGoodbye`, which needs to happen after `SendClone`.
+ actor->StreamConsumed();
+
+ IPC::WriteParam(aWriter, actor->StreamID());
+ IPC::WriteParam(aWriter, mStart);
+ IPC::WriteParam(aWriter, mLength);
+ IPC::WriteParam(aWriter, std::move(childEp));
+
+ if (innerStream) {
+ innerStream->Close();
+ }
+ return;
+ }
+
+ // If we have a stream and are in the parent process, create a new actor pair
+ // and transfer ownership of the stream into storage.
+ auto streamStorage = RemoteLazyInputStreamStorage::Get();
+ if (streamStorage.isOk()) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ nsID id = nsID::GenerateUUID();
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("Serializing as new stream: %s", nsIDToCString(id).get()));
+
+ streamStorage.inspect()->AddStream(innerStream, id);
+
+ mozilla::ipc::Endpoint<PRemoteLazyInputStreamParent> parentEp;
+ mozilla::ipc::Endpoint<PRemoteLazyInputStreamChild> childEp;
+ MOZ_ALWAYS_SUCCEEDS(
+ PRemoteLazyInputStream::CreateEndpoints(&parentEp, &childEp));
+
+ // Bind the actor on our background thread.
+ streamStorage.inspect()->TaskQueue()->Dispatch(NS_NewRunnableFunction(
+ "RemoteLazyInputStreamParent::Bind",
+ [parentEp = std::move(parentEp), id]() mutable {
+ auto stream = MakeRefPtr<RemoteLazyInputStreamParent>(id);
+ parentEp.Bind(stream);
+ }));
+
+ IPC::WriteParam(aWriter, id);
+ IPC::WriteParam(aWriter, 0);
+ IPC::WriteParam(aWriter, UINT64_MAX);
+ IPC::WriteParam(aWriter, std::move(childEp));
+ return;
+ }
+
+ MOZ_CRASH("Cannot serialize new RemoteLazyInputStream from this process");
+}
+
+already_AddRefed<RemoteLazyInputStream> RemoteLazyInputStream::IPCRead(
+ IPC::MessageReader* aReader) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose, ("Deserialize"));
+
+ bool closed;
+ if (NS_WARN_IF(!IPC::ReadParam(aReader, &closed))) {
+ return nullptr;
+ }
+ if (closed) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Deserialize closed stream"));
+ return do_AddRef(new RemoteLazyInputStream());
+ }
+
+ nsID id{};
+ uint64_t start;
+ uint64_t length;
+ mozilla::ipc::Endpoint<PRemoteLazyInputStreamChild> endpoint;
+ if (NS_WARN_IF(!IPC::ReadParam(aReader, &id)) ||
+ NS_WARN_IF(!IPC::ReadParam(aReader, &start)) ||
+ NS_WARN_IF(!IPC::ReadParam(aReader, &length)) ||
+ NS_WARN_IF(!IPC::ReadParam(aReader, &endpoint))) {
+ return nullptr;
+ }
+
+ if (!endpoint.IsValid()) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Warning,
+ ("Deserialize failed due to invalid endpoint!"));
+ return do_AddRef(new RemoteLazyInputStream());
+ }
+
+ RefPtr<RemoteLazyInputStreamChild> actor =
+ BindChildActor(id, std::move(endpoint));
+
+ if (!actor) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Warning,
+ ("Deserialize failed as we are probably late in shutdown!"));
+ return do_AddRef(new RemoteLazyInputStream());
+ }
+
+ return do_AddRef(new RemoteLazyInputStream(actor, start, length));
+}
+
+} // namespace mozilla
+
+void IPC::ParamTraits<mozilla::RemoteLazyInputStream*>::Write(
+ IPC::MessageWriter* aWriter, mozilla::RemoteLazyInputStream* aParam) {
+ bool nonNull = !!aParam;
+ IPC::WriteParam(aWriter, nonNull);
+ if (aParam) {
+ aParam->IPCWrite(aWriter);
+ }
+}
+
+bool IPC::ParamTraits<mozilla::RemoteLazyInputStream*>::Read(
+ IPC::MessageReader* aReader,
+ RefPtr<mozilla::RemoteLazyInputStream>* aResult) {
+ bool nonNull = false;
+ if (!IPC::ReadParam(aReader, &nonNull)) {
+ return false;
+ }
+ if (!nonNull) {
+ *aResult = nullptr;
+ return true;
+ }
+ *aResult = mozilla::RemoteLazyInputStream::IPCRead(aReader);
+ return *aResult;
+}
diff --git a/dom/file/ipc/RemoteLazyInputStream.h b/dom/file/ipc/RemoteLazyInputStream.h
new file mode 100644
index 0000000000..08bb168e27
--- /dev/null
+++ b/dom/file/ipc/RemoteLazyInputStream.h
@@ -0,0 +1,155 @@
+/* -*- 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 "chrome/common/ipc_message_utils.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
+
+ // Create a new lazy RemoteLazyInputStream, and move the provided aInputStream
+ // into storage as referenced by it. May only be called in processes with
+ // RemoteLazyInputStreamStorage.
+ static already_AddRefed<RemoteLazyInputStream> WrapStream(
+ nsIInputStream* aInputStream);
+
+ // mozIRemoteLazyInputStream
+ NS_IMETHOD TakeInternalStream(nsIInputStream** aStream) override;
+ NS_IMETHOD GetInternalStreamID(nsID& aID) override;
+
+ private:
+ friend struct IPC::ParamTraits<mozilla::RemoteLazyInputStream*>;
+
+ // Constructor for an already-closed RemoteLazyInputStream.
+ RemoteLazyInputStream() = default;
+
+ explicit RemoteLazyInputStream(RemoteLazyInputStreamChild* aActor,
+ uint64_t aStart = 0,
+ uint64_t aLength = UINT64_MAX);
+
+ explicit RemoteLazyInputStream(nsIInputStream* aStream);
+
+ ~RemoteLazyInputStream();
+
+ void StreamNeeded() MOZ_REQUIRES(mMutex);
+
+ // Upon receiving the stream from our actor, we will not wrap it into an async
+ // stream until needed. This allows callers to get access to the underlying
+ // potentially-sync stream using `TakeInternalStream` before reading.
+ nsresult EnsureAsyncRemoteStream() MOZ_REQUIRES(mMutex);
+
+ // Note that data has been read from our input stream, and disconnect from our
+ // remote actor.
+ void MarkConsumed();
+
+ void IPCWrite(IPC::MessageWriter* aWriter);
+ static already_AddRefed<RemoteLazyInputStream> IPCRead(
+ IPC::MessageReader* aReader);
+
+ // Helper method to generate a description of a stream for use in loggging.
+ nsCString Describe() MOZ_REQUIRES(mMutex);
+
+ // Start and length of the slice to apply on this RemoteLazyInputStream when
+ // fetching the underlying stream with `SendStreamNeeded`.
+ const uint64_t mStart = 0;
+ const uint64_t mLength = UINT64_MAX;
+
+ // Any non-const member of this class is protected by mutex because it is
+ // touched on multiple threads.
+ Mutex mMutex{"RemoteLazyInputStream::mMutex"};
+
+ // 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 mInnerStream. From now on, any
+ // method call will be forwared to mInnerStream or mAsyncInnerStream.
+ eRunning,
+
+ // If Close() or CloseWithStatus() is called, we move to this state.
+ // mInnerStream is released and any method will return
+ // NS_BASE_STREAM_CLOSED.
+ eClosed,
+ } mState MOZ_GUARDED_BY(mMutex) = eClosed;
+
+ // The actor which will be used to provide the underlying stream or length
+ // information when needed, as well as to efficiently allow transferring the
+ // stream over IPC.
+ //
+ // The connection to our actor will be cleared once the stream has been closed
+ // or has started reading, at which point this stream will be serialized and
+ // cloned as-if it was the underlying stream.
+ RefPtr<RemoteLazyInputStreamChild> mActor MOZ_GUARDED_BY(mMutex);
+
+ nsCOMPtr<nsIInputStream> mInnerStream MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIAsyncInputStream> mAsyncInnerStream MOZ_GUARDED_BY(mMutex);
+
+ // These 2 values are set only if mState is ePending or eRunning.
+ // RefPtr is used instead of nsCOMPtr to avoid invoking QueryInterface when
+ // assigning in debug builds, as `mInputStreamCallback` may not be threadsafe.
+ RefPtr<nsIInputStreamCallback> mInputStreamCallback MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIEventTarget> mInputStreamCallbackEventTarget
+ MOZ_GUARDED_BY(mMutex);
+ uint32_t mInputStreamCallbackFlags MOZ_GUARDED_BY(mMutex) = 0;
+ uint32_t mInputStreamCallbackRequestedCount MOZ_GUARDED_BY(mMutex) = 0;
+
+ // These 2 values are set only if mState is ePending.
+ nsCOMPtr<nsIFileMetadataCallback> mFileMetadataCallback
+ MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIEventTarget> mFileMetadataCallbackEventTarget
+ MOZ_GUARDED_BY(mMutex);
+};
+
+} // namespace mozilla
+
+template <>
+struct IPC::ParamTraits<mozilla::RemoteLazyInputStream*> {
+ static void Write(IPC::MessageWriter* aWriter,
+ mozilla::RemoteLazyInputStream* aParam);
+ static bool Read(IPC::MessageReader* aReader,
+ RefPtr<mozilla::RemoteLazyInputStream>* aResult);
+};
+
+#endif // mozilla_RemoteLazyInputStream_h
diff --git a/dom/file/ipc/RemoteLazyInputStreamChild.cpp b/dom/file/ipc/RemoteLazyInputStreamChild.cpp
new file mode 100644
index 0000000000..f03aa47aad
--- /dev/null
+++ b/dom/file/ipc/RemoteLazyInputStreamChild.cpp
@@ -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/. */
+
+#include "RemoteLazyInputStreamChild.h"
+#include "RemoteLazyInputStreamThread.h"
+
+namespace mozilla {
+
+extern mozilla::LazyLogModule gRemoteLazyStreamLog;
+
+RemoteLazyInputStreamChild::RemoteLazyInputStreamChild(const nsID& aID)
+ : mID(aID) {}
+
+RemoteLazyInputStreamChild::~RemoteLazyInputStreamChild() = default;
+
+void RemoteLazyInputStreamChild::StreamCreated() {
+ size_t count = ++mStreamCount;
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Child::StreamCreated %s = %zu", nsIDToCString(mID).get(), count));
+}
+
+void RemoteLazyInputStreamChild::StreamConsumed() {
+ size_t count = --mStreamCount;
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Child::StreamConsumed %s = %zu", nsIDToCString(mID).get(), count));
+
+ // When the count reaches zero, close the underlying actor.
+ if (count == 0) {
+ auto* t = RemoteLazyInputStreamThread::Get();
+ if (t) {
+ t->Dispatch(
+ NS_NewRunnableFunction("RemoteLazyInputStreamChild::StreamConsumed",
+ [self = RefPtr{this}]() {
+ if (self->CanSend()) {
+ self->SendGoodbye();
+ }
+ }));
+ } // else the xpcom thread shutdown has already started.
+ }
+}
+
+void RemoteLazyInputStreamChild::ActorDestroy(ActorDestroyReason aReason) {
+ if (mStreamCount != 0) {
+ NS_WARNING(
+ nsPrintfCString("RemoteLazyInputStreamChild disconnected unexpectedly "
+ "(%zu streams remaining)! %p %s",
+ size_t(mStreamCount), this, nsIDToCString(mID).get())
+ .get());
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/file/ipc/RemoteLazyInputStreamChild.h b/dom/file/ipc/RemoteLazyInputStreamChild.h
new file mode 100644
index 0000000000..c95ed9f058
--- /dev/null
+++ b/dom/file/ipc/RemoteLazyInputStreamChild.h
@@ -0,0 +1,41 @@
+/* -*- 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"
+
+namespace mozilla {
+
+class RemoteLazyInputStream;
+
+class RemoteLazyInputStreamChild final : public PRemoteLazyInputStreamChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteLazyInputStreamChild, final)
+
+ explicit RemoteLazyInputStreamChild(const nsID& aID);
+
+ const nsID& StreamID() const { return mID; }
+
+ // Manage the count of streams registered on this actor. When the count
+ // reaches 0 the connection to our remote process will be closed.
+ void StreamCreated();
+ void StreamConsumed();
+
+ void ActorDestroy(ActorDestroyReason aReason) override;
+
+ private:
+ ~RemoteLazyInputStreamChild() override;
+
+ const nsID mID;
+
+ std::atomic<size_t> mStreamCount{0};
+};
+
+} // 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..d92c529546
--- /dev/null
+++ b/dom/file/ipc/RemoteLazyInputStreamParent.cpp
@@ -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/. */
+
+#include "RemoteLazyInputStreamParent.h"
+#include "RemoteLazyInputStreamStorage.h"
+#include "mozilla/InputStreamLengthHelper.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/InputStreamParams.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "nsStreamUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+
+namespace mozilla {
+
+extern mozilla::LazyLogModule gRemoteLazyStreamLog;
+
+RemoteLazyInputStreamParent::RemoteLazyInputStreamParent(const nsID& aID)
+ : mID(aID) {
+ auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr);
+ if (storage) {
+ storage->ActorCreated(mID);
+ }
+}
+
+void RemoteLazyInputStreamParent::ActorDestroy(
+ IProtocol::ActorDestroyReason aReason) {
+ auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr);
+ if (storage) {
+ storage->ActorDestroyed(mID);
+ }
+}
+
+mozilla::ipc::IPCResult RemoteLazyInputStreamParent::RecvClone(
+ mozilla::ipc::Endpoint<PRemoteLazyInputStreamParent>&& aCloneEndpoint) {
+ if (!aCloneEndpoint.IsValid()) {
+ return IPC_FAIL(this, "Unexpected invalid endpoint in RecvClone");
+ }
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Debug,
+ ("Parent::RecvClone %s", nsIDToCString(mID).get()));
+
+ auto* newActor = new RemoteLazyInputStreamParent(mID);
+ aCloneEndpoint.Bind(newActor);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemoteLazyInputStreamParent::RecvStreamNeeded(
+ uint64_t aStart, uint64_t aLength, StreamNeededResolver&& aResolver) {
+ nsCOMPtr<nsIInputStream> stream;
+ auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr);
+ if (storage) {
+ storage->GetStream(mID, aStart, aLength, getter_AddRefs(stream));
+ }
+
+ if (!stream) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Warning,
+ ("Parent::RecvStreamNeeded not available! %s",
+ nsIDToCString(mID).get()));
+ aResolver(Nothing());
+ return IPC_OK();
+ }
+
+ Maybe<IPCStream> ipcStream;
+ if (NS_WARN_IF(!SerializeIPCStream(stream.forget(), ipcStream,
+ /* aAllowLazy */ false))) {
+ return IPC_FAIL(this, "IPCStream serialization failed!");
+ }
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Parent::RecvStreamNeeded resolve %s", nsIDToCString(mID).get()));
+ aResolver(ipcStream);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemoteLazyInputStreamParent::RecvLengthNeeded(
+ LengthNeededResolver&& aResolver) {
+ nsCOMPtr<nsIInputStream> stream;
+ auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr);
+ if (storage) {
+ storage->GetStream(mID, 0, UINT64_MAX, getter_AddRefs(stream));
+ }
+
+ if (!stream) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Warning,
+ ("Parent::RecvLengthNeeded not available! %s",
+ nsIDToCString(mID).get()));
+ aResolver(-1);
+ return IPC_OK();
+ }
+
+ int64_t length = -1;
+ if (InputStreamLengthHelper::GetSyncLength(stream, &length)) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Parent::RecvLengthNeeded sync resolve %" PRId64 "! %s", length,
+ nsIDToCString(mID).get()));
+ aResolver(length);
+ return IPC_OK();
+ }
+
+ InputStreamLengthHelper::GetAsyncLength(
+ stream, [aResolver, id = mID](int64_t aLength) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Parent::RecvLengthNeeded async resolve %" PRId64 "! %s",
+ aLength, nsIDToCString(id).get()));
+ aResolver(aLength);
+ });
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemoteLazyInputStreamParent::RecvGoodbye() {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Parent::RecvGoodbye! %s", nsIDToCString(mID).get()));
+ Close();
+ return IPC_OK();
+}
+
+} // namespace mozilla
diff --git a/dom/file/ipc/RemoteLazyInputStreamParent.h b/dom/file/ipc/RemoteLazyInputStreamParent.h
new file mode 100644
index 0000000000..cb5b1d4285
--- /dev/null
+++ b/dom/file/ipc/RemoteLazyInputStreamParent.h
@@ -0,0 +1,44 @@
+/* -*- 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 {
+
+class RemoteLazyInputStreamParent final : public PRemoteLazyInputStreamParent {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteLazyInputStreamParent, final)
+
+ explicit RemoteLazyInputStreamParent(const nsID& aID);
+
+ const nsID& ID() const { return mID; }
+
+ mozilla::ipc::IPCResult RecvClone(
+ mozilla::ipc::Endpoint<PRemoteLazyInputStreamParent>&& aCloneEndpoint);
+
+ mozilla::ipc::IPCResult RecvStreamNeeded(uint64_t aStart, uint64_t aLength,
+ StreamNeededResolver&& aResolver);
+
+ mozilla::ipc::IPCResult RecvLengthNeeded(LengthNeededResolver&& aResolver);
+
+ mozilla::ipc::IPCResult RecvGoodbye();
+
+ void ActorDestroy(IProtocol::ActorDestroyReason aReason) override;
+
+ private:
+ ~RemoteLazyInputStreamParent() override = default;
+
+ const nsID mID;
+};
+
+} // 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..8ce2c22657
--- /dev/null
+++ b/dom/file/ipc/RemoteLazyInputStreamStorage.cpp
@@ -0,0 +1,243 @@
+/* -*- 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;
+
+extern mozilla::LazyLogModule gRemoteLazyStreamLog;
+
+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();
+
+ MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
+ "RemoteLazyInputStreamStorage", getter_AddRefs(gStorage->mTaskQueue)));
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(gStorage, "xpcom-shutdown", false);
+ }
+}
+
+NS_IMETHODIMP
+RemoteLazyInputStreamStorage::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "xpcom-shutdown");
+ }
+
+ mozilla::StaticMutexAutoLock lock(gMutex);
+ gStorage = nullptr;
+ return NS_OK;
+}
+
+void RemoteLazyInputStreamStorage::AddStream(nsIInputStream* aInputStream,
+ const nsID& aID) {
+ MOZ_ASSERT(aInputStream);
+
+ MOZ_LOG(
+ gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Storage::AddStream(%s) = %p", nsIDToCString(aID).get(), aInputStream));
+
+ UniquePtr<StreamData> data = MakeUnique<StreamData>();
+ data->mInputStream = aInputStream;
+
+ mozilla::StaticMutexAutoLock lock(gMutex);
+ mStorage.InsertOrUpdate(aID, std::move(data));
+}
+
+nsCOMPtr<nsIInputStream> RemoteLazyInputStreamStorage::ForgetStream(
+ const nsID& aID) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Storage::ForgetStream(%s)", nsIDToCString(aID).get()));
+
+ 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;
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Storage::GetStream(%s, %" PRIu64 " %" PRIu64 ")",
+ nsIDToCString(aID).get(), aStart, aLength));
+
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ // 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;
+ }
+
+ 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 < UINT64_MAX) {
+ clonedStream =
+ new SlicedInputStream(clonedStream.forget(), aStart, aLength);
+ }
+
+ clonedStream.forget(aInputStream);
+}
+
+void RemoteLazyInputStreamStorage::StoreCallback(
+ const nsID& aID, RemoteLazyInputStreamParentCallback* aCallback) {
+ MOZ_ASSERT(aCallback);
+
+ MOZ_LOG(
+ gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Storage::StoreCallback(%s, %p)", nsIDToCString(aID).get(), 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) {
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Storage::TakeCallback(%s)", nsIDToCString(aID).get()));
+
+ mozilla::StaticMutexAutoLock lock(gMutex);
+ StreamData* data = mStorage.Get(aID);
+ if (!data) {
+ return nullptr;
+ }
+
+ RefPtr<RemoteLazyInputStreamParentCallback> callback;
+ data->mCallback.swap(callback);
+ return callback.forget();
+}
+
+void RemoteLazyInputStreamStorage::ActorCreated(const nsID& aID) {
+ mozilla::StaticMutexAutoLock lock(gMutex);
+ StreamData* data = mStorage.Get(aID);
+ if (!data) {
+ return;
+ }
+
+ size_t count = ++data->mActorCount;
+
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Storage::ActorCreated(%s) = %zu", nsIDToCString(aID).get(), count));
+}
+
+void RemoteLazyInputStreamStorage::ActorDestroyed(const nsID& aID) {
+ UniquePtr<StreamData> entry;
+ {
+ mozilla::StaticMutexAutoLock lock(gMutex);
+ StreamData* data = mStorage.Get(aID);
+ if (!data) {
+ return;
+ }
+
+ auto newCount = --data->mActorCount;
+ MOZ_LOG(gRemoteLazyStreamLog, LogLevel::Verbose,
+ ("Storage::ActorDestroyed(%s) = %zu (cb=%p)",
+ nsIDToCString(aID).get(), newCount, data->mCallback.get()));
+
+ if (newCount == 0) {
+ mStorage.Remove(aID, &entry);
+ }
+ }
+
+ if (entry && entry->mCallback) {
+ entry->mCallback->ActorDestroyed(aID);
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/file/ipc/RemoteLazyInputStreamStorage.h b/dom/file/ipc/RemoteLazyInputStreamStorage.h
new file mode 100644
index 0000000000..296a8d9313
--- /dev/null
+++ b/dom/file/ipc/RemoteLazyInputStreamStorage.h
@@ -0,0 +1,78 @@
+/* -*- 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 NS_NO_VTABLE RemoteLazyInputStreamParentCallback {
+ public:
+ virtual void ActorDestroyed(const nsID& aID) = 0;
+
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ protected:
+ virtual ~RemoteLazyInputStreamParentCallback() = default;
+};
+
+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();
+
+ nsISerialEventTarget* TaskQueue() { return mTaskQueue; }
+
+ void AddStream(nsIInputStream* aInputStream, const nsID& aID);
+
+ // 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);
+
+ void ActorCreated(const nsID& aID);
+ void ActorDestroyed(const nsID& aID);
+
+ private:
+ RemoteLazyInputStreamStorage() = default;
+ ~RemoteLazyInputStreamStorage() = default;
+
+ nsCOMPtr<nsISerialEventTarget> mTaskQueue;
+
+ struct StreamData {
+ nsCOMPtr<nsIInputStream> mInputStream;
+ RefPtr<RemoteLazyInputStreamParentCallback> mCallback;
+ size_t mActorCount = 0;
+ };
+
+ 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..ec6c99952b
--- /dev/null
+++ b/dom/file/ipc/RemoteLazyInputStreamThread.cpp
@@ -0,0 +1,237 @@
+/* -*- 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 "ErrorList.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.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;
+
+class ThreadInitializeRunnable final : public Runnable {
+ public:
+ ThreadInitializeRunnable() : Runnable("dom::ThreadInitializeRunnable") {}
+
+ NS_IMETHOD
+ Run() override {
+ StaticMutexAutoLock lock(gRemoteLazyThreadMutex);
+ MOZ_ASSERT(gRemoteLazyThread);
+ if (NS_WARN_IF(!gRemoteLazyThread->InitializeOnMainThread())) {
+ // RemoteLazyInputStreamThread::GetOrCreate might have handed out a
+ // pointer to our thread already at this point such that we cannot
+ // just do gRemoteLazyThread = nullptr; here.
+ MOZ_DIAGNOSTIC_ASSERT(
+ false, "Async gRemoteLazyThread->InitializeOnMainThread() failed.");
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(RemoteLazyInputStreamThread, nsIObserver, nsIEventTarget,
+ nsISerialEventTarget, nsIDirectTaskDispatcher)
+
+bool RLISThreadIsInOrBeyondShutdown() {
+ // ShutdownPhase::XPCOMShutdownThreads matches
+ // obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false);
+ return AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads);
+}
+
+/* static */
+RemoteLazyInputStreamThread* RemoteLazyInputStreamThread::Get() {
+ if (RLISThreadIsInOrBeyondShutdown()) {
+ return nullptr;
+ }
+
+ StaticMutexAutoLock lock(gRemoteLazyThreadMutex);
+
+ return gRemoteLazyThread;
+}
+
+/* static */
+RemoteLazyInputStreamThread* RemoteLazyInputStreamThread::GetOrCreate() {
+ if (RLISThreadIsInOrBeyondShutdown()) {
+ return nullptr;
+ }
+
+ StaticMutexAutoLock lock(gRemoteLazyThreadMutex);
+
+ if (!gRemoteLazyThread) {
+ gRemoteLazyThread = new RemoteLazyInputStreamThread();
+ if (!gRemoteLazyThread->Initialize()) {
+ gRemoteLazyThread = 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 (!NS_IsMainThread()) {
+ RefPtr<Runnable> runnable = new ThreadInitializeRunnable();
+ nsresult rv =
+ SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget());
+ return !NS_WARN_IF(NS_FAILED(rv));
+ }
+
+ return InitializeOnMainThread();
+}
+
+bool RemoteLazyInputStreamThread::InitializeOnMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return false;
+ }
+
+ nsresult rv =
+ obs->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false);
+ return !NS_WARN_IF(NS_FAILED(rv));
+}
+
+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;
+ }
+
+ gRemoteLazyThread = nullptr;
+
+ return NS_OK;
+}
+
+// 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) {
+ if (RLISThreadIsInOrBeyondShutdown()) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+
+ StaticMutexAutoLock lock(gRemoteLazyThreadMutex);
+
+ 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;
+}
+
+NS_IMETHODIMP
+RemoteLazyInputStreamThread::RegisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteLazyInputStreamThread::UnregisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+RemoteLazyInputStreamThread::DispatchDirectTask(
+ already_AddRefed<nsIRunnable> aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+
+ StaticMutexAutoLock lock(gRemoteLazyThreadMutex);
+
+ nsCOMPtr<nsIDirectTaskDispatcher> dispatcher = do_QueryInterface(mThread);
+
+ if (dispatcher) {
+ return dispatcher->DispatchDirectTask(runnable.forget());
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP RemoteLazyInputStreamThread::DrainDirectTasks() {
+ StaticMutexAutoLock lock(gRemoteLazyThreadMutex);
+
+ nsCOMPtr<nsIDirectTaskDispatcher> dispatcher = do_QueryInterface(mThread);
+
+ if (dispatcher) {
+ return dispatcher->DrainDirectTasks();
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP RemoteLazyInputStreamThread::HaveDirectTasks(bool* aValue) {
+ StaticMutexAutoLock lock(gRemoteLazyThreadMutex);
+
+ nsCOMPtr<nsIDirectTaskDispatcher> dispatcher = do_QueryInterface(mThread);
+
+ if (dispatcher) {
+ return dispatcher->HaveDirectTasks(aValue);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+bool IsOnDOMFileThread() {
+ MOZ_ASSERT(!RLISThreadIsInOrBeyondShutdown());
+
+ StaticMutexAutoLock lock(gRemoteLazyThreadMutex);
+ 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..378cb09009
--- /dev/null
+++ b/dom/file/ipc/RemoteLazyInputStreamThread.h
@@ -0,0 +1,52 @@
+/* -*- 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;
+
+// XXX Rename this class since it's used by LSNG too.
+class RemoteLazyInputStreamThread final : public nsIObserver,
+ public nsISerialEventTarget,
+ public nsIDirectTaskDispatcher {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIEVENTTARGET
+ NS_DECL_NSISERIALEVENTTARGET
+ NS_DECL_NSIDIRECTTASKDISPATCHER
+
+ static RemoteLazyInputStreamThread* Get();
+
+ static RemoteLazyInputStreamThread* GetOrCreate();
+
+ bool Initialize();
+
+ bool InitializeOnMainThread();
+
+ private:
+ ~RemoteLazyInputStreamThread() = default;
+
+ nsCOMPtr<nsIThread> mThread;
+};
+
+bool IsOnDOMFileThread();
+
+void AssertIsOnDOMFileThread();
+
+} // namespace mozilla
+
+#endif // mozilla_RemoteLazyInputStreamThread_h
diff --git a/dom/file/ipc/TemporaryIPCBlobChild.cpp b/dom/file/ipc/TemporaryIPCBlobChild.cpp
new file mode 100644
index 0000000000..7c7df55d81
--- /dev/null
+++ b/dom/file/ipc/TemporaryIPCBlobChild.cpp
@@ -0,0 +1,86 @@
+/* -*- 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& aBlobOrError) {
+ mActive = false;
+ mMutableBlobStorage = nullptr;
+
+ if (aBlobOrError.type() == IPCBlobOrError::TIPCBlob) {
+ // This must be always deserialized.
+ RefPtr<BlobImpl> blobImpl =
+ IPCBlobUtils::Deserialize(aBlobOrError.get_IPCBlob());
+ MOZ_ASSERT(blobImpl);
+
+ if (mCallback) {
+ mCallback->OperationSucceeded(blobImpl);
+ }
+ } else if (mCallback) {
+ MOZ_ASSERT(aBlobOrError.type() == IPCBlobOrError::Tnsresult);
+ mCallback->OperationFailed(aBlobOrError.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(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..a909ee0d53
--- /dev/null
+++ b/dom/file/ipc/TemporaryIPCBlobChild.h
@@ -0,0 +1,53 @@
+/* -*- 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::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* aStorage);
+
+ void AskForBlob(TemporaryIPCBlobChildCallback* aCallback,
+ const nsACString& aContentType, PRFileDesc* aFD);
+
+ private:
+ ~TemporaryIPCBlobChild() override;
+
+ 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 mozilla::dom
+
+#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..d742859aa9
--- /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, 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..2609e8d820
--- /dev/null
+++ b/dom/file/ipc/TemporaryIPCBlobParent.h
@@ -0,0 +1,43 @@
+/* -*- 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::dom {
+
+class TemporaryIPCBlobParent final : public PTemporaryIPCBlobParent {
+ friend class PTemporaryIPCBlobParent;
+
+ public:
+ explicit TemporaryIPCBlobParent();
+
+ mozilla::ipc::IPCResult CreateAndShareFile();
+
+ private:
+ ~TemporaryIPCBlobParent() override;
+
+ 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 mozilla::dom
+
+#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..21c2d50cac
--- /dev/null
+++ b/dom/file/ipc/moz.build
@@ -0,0 +1,69 @@
+# -*- 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",
+]
+
+UNIFIED_SOURCES += [
+ "FileCreatorChild.cpp",
+ "FileCreatorParent.cpp",
+ "IPCBlobUtils.cpp",
+ "RemoteLazyInputStream.cpp",
+ "RemoteLazyInputStreamChild.cpp",
+ "RemoteLazyInputStreamParent.cpp",
+ "RemoteLazyInputStreamStorage.cpp",
+ "RemoteLazyInputStreamThread.cpp",
+ "TemporaryIPCBlobChild.cpp",
+ "TemporaryIPCBlobParent.cpp",
+]
+
+IPDL_SOURCES += [
+ "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"
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+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..8303b2e0fc
--- /dev/null
+++ b/dom/file/ipc/mozIRemoteLazyInputStream.idl
@@ -0,0 +1,29 @@
+/* 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
+{
+ /**
+ * Attempts to take the internal stream out of this mozIRemoteLazyInputStream.
+ * Throws NS_BASE_STREAM_WOULD_BLOCK if the stream isn't available yet, and
+ * NS_BASE_STREAM_CLOSED if it was already closed.
+ */
+ [noscript] nsIInputStream TakeInternalStream();
+
+ /**
+ * If this RemoteLazyInputStream is actively backed by an actor, get the
+ * underlying actor's ID. Will throw if the underlying actor is no longer
+ * available.
+ */
+ [noscript] readonly attribute nsIDRef internalStreamID;
+};
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
new file mode 100644
index 0000000000..48c454d27c
--- /dev/null
+++ b/dom/file/ipc/tests/green.jpg
Binary files differ
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..42f65733d9
--- /dev/null
+++ b/dom/file/ipc/tests/ok.sjs
@@ -0,0 +1,11 @@
+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..0f4e9665be
--- /dev/null
+++ b/dom/file/ipc/tests/script_file.js
@@ -0,0 +1,53 @@
+/* eslint-env mozilla/chrome-script */
+
+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
+ 0o666,
+ 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
+ 0o666,
+ 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..76db4e2f2e
--- /dev/null
+++ b/dom/file/ipc/tests/temporary.sjs
@@ -0,0 +1,6 @@
+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>