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