diff options
Diffstat (limited to 'dom/base/StructuredCloneHolder.cpp')
-rw-r--r-- | dom/base/StructuredCloneHolder.cpp | 1368 |
1 files changed, 1368 insertions, 0 deletions
diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp new file mode 100644 index 0000000000..1c27c632e0 --- /dev/null +++ b/dom/base/StructuredCloneHolder.cpp @@ -0,0 +1,1368 @@ +/* -*- 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 "StructuredCloneHolder.h" + +#include "ImageContainer.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/CryptoKey.h" +#include "mozilla/dom/Directory.h" +#include "mozilla/dom/DirectoryBinding.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/FileList.h" +#include "mozilla/dom/FileListBinding.h" +#include "mozilla/dom/FormData.h" +#include "mozilla/dom/ImageBitmap.h" +#include "mozilla/dom/ImageBitmapBinding.h" +#include "mozilla/dom/ImageData.h" +#include "mozilla/dom/ImageDataBinding.h" +#include "mozilla/dom/ipc/BlobChild.h" +#include "mozilla/dom/StructuredClone.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/MessagePortBinding.h" +#include "mozilla/dom/OffscreenCanvas.h" +#include "mozilla/dom/OffscreenCanvasBinding.h" +#include "mozilla/dom/PMessagePort.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "mozilla/dom/SubtleCryptoBinding.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/URLSearchParams.h" +#include "mozilla/dom/URLSearchParamsBinding.h" +#include "mozilla/dom/WebCryptoCommon.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "MultipartBlobImpl.h" +#include "nsIRemoteBlob.h" +#include "nsQueryObject.h" + +#ifdef MOZ_WEBRTC +#include "mozilla/dom/RTCCertificate.h" +#include "mozilla/dom/RTCCertificateBinding.h" +#endif + +using namespace mozilla::ipc; + +namespace mozilla { +namespace dom { + +namespace { + +JSObject* +StructuredCloneCallbacksRead(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, uint32_t aIndex, + void* aClosure) +{ + StructuredCloneHolderBase* holder = + static_cast<StructuredCloneHolderBase*>(aClosure); + MOZ_ASSERT(holder); + return holder->CustomReadHandler(aCx, aReader, aTag, aIndex); +} + +bool +StructuredCloneCallbacksWrite(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JS::Handle<JSObject*> aObj, + void* aClosure) +{ + StructuredCloneHolderBase* holder = + static_cast<StructuredCloneHolderBase*>(aClosure); + MOZ_ASSERT(holder); + return holder->CustomWriteHandler(aCx, aWriter, aObj); +} + +bool +StructuredCloneCallbacksReadTransfer(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + void* aContent, + uint64_t aExtraData, + void* aClosure, + JS::MutableHandleObject aReturnObject) +{ + StructuredCloneHolderBase* holder = + static_cast<StructuredCloneHolderBase*>(aClosure); + MOZ_ASSERT(holder); + return holder->CustomReadTransferHandler(aCx, aReader, aTag, aContent, + aExtraData, aReturnObject); +} + +bool +StructuredCloneCallbacksWriteTransfer(JSContext* aCx, + JS::Handle<JSObject*> aObj, + void* aClosure, + // Output: + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t* aExtraData) +{ + StructuredCloneHolderBase* holder = + static_cast<StructuredCloneHolderBase*>(aClosure); + MOZ_ASSERT(holder); + return holder->CustomWriteTransferHandler(aCx, aObj, aTag, aOwnership, + aContent, aExtraData); +} + +void +StructuredCloneCallbacksFreeTransfer(uint32_t aTag, + JS::TransferableOwnership aOwnership, + void* aContent, + uint64_t aExtraData, + void* aClosure) +{ + StructuredCloneHolderBase* holder = + static_cast<StructuredCloneHolderBase*>(aClosure); + MOZ_ASSERT(holder); + return holder->CustomFreeTransferHandler(aTag, aOwnership, aContent, + aExtraData); +} + +void +StructuredCloneCallbacksError(JSContext* aCx, + uint32_t aErrorId) +{ + NS_WARNING("Failed to clone data."); +} + +} // anonymous namespace + +const JSStructuredCloneCallbacks StructuredCloneHolder::sCallbacks = { + StructuredCloneCallbacksRead, + StructuredCloneCallbacksWrite, + StructuredCloneCallbacksError, + StructuredCloneCallbacksReadTransfer, + StructuredCloneCallbacksWriteTransfer, + StructuredCloneCallbacksFreeTransfer +}; + +// StructuredCloneHolderBase class + +StructuredCloneHolderBase::StructuredCloneHolderBase(StructuredCloneScope aScope) + : mStructuredCloneScope(aScope) +#ifdef DEBUG + , mClearCalled(false) +#endif +{} + +StructuredCloneHolderBase::~StructuredCloneHolderBase() +{ +#ifdef DEBUG + MOZ_ASSERT(mClearCalled); +#endif +} + +void +StructuredCloneHolderBase::Clear() +{ +#ifdef DEBUG + mClearCalled = true; +#endif + + mBuffer = nullptr; +} + +bool +StructuredCloneHolderBase::Write(JSContext* aCx, + JS::Handle<JS::Value> aValue) +{ + return Write(aCx, aValue, JS::UndefinedHandleValue, + JS::CloneDataPolicy().denySharedArrayBuffer()); +} + +bool +StructuredCloneHolderBase::Write(JSContext* aCx, + JS::Handle<JS::Value> aValue, + JS::Handle<JS::Value> aTransfer, + JS::CloneDataPolicy cloneDataPolicy) +{ + MOZ_ASSERT(!mBuffer, "Double Write is not allowed"); + MOZ_ASSERT(!mClearCalled, "This method cannot be called after Clear."); + + mBuffer = MakeUnique<JSAutoStructuredCloneBuffer>(mStructuredCloneScope, &StructuredCloneHolder::sCallbacks, this); + + if (!mBuffer->write(aCx, aValue, aTransfer, cloneDataPolicy, + &StructuredCloneHolder::sCallbacks, this)) + { + mBuffer = nullptr; + return false; + } + + return true; +} + +bool +StructuredCloneHolderBase::Read(JSContext* aCx, + JS::MutableHandle<JS::Value> aValue) +{ + MOZ_ASSERT(mBuffer, "Read() without Write() is not allowed."); + MOZ_ASSERT(!mClearCalled, "This method cannot be called after Clear."); + + bool ok = mBuffer->read(aCx, aValue, &StructuredCloneHolder::sCallbacks, this); + return ok; +} + +bool +StructuredCloneHolderBase::CustomReadTransferHandler(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + void* aContent, + uint64_t aExtraData, + JS::MutableHandleObject aReturnObject) +{ + MOZ_CRASH("Nothing to read."); + return false; +} + +bool +StructuredCloneHolderBase::CustomWriteTransferHandler(JSContext* aCx, + JS::Handle<JSObject*> aObj, + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t* aExtraData) +{ + // No transfers are supported by default. + return false; +} + +void +StructuredCloneHolderBase::CustomFreeTransferHandler(uint32_t aTag, + JS::TransferableOwnership aOwnership, + void* aContent, + uint64_t aExtraData) +{ + MOZ_CRASH("Nothing to free."); +} + +// StructuredCloneHolder class + +StructuredCloneHolder::StructuredCloneHolder(CloningSupport aSupportsCloning, + TransferringSupport aSupportsTransferring, + StructuredCloneScope aScope) + : StructuredCloneHolderBase(aScope) + , mSupportsCloning(aSupportsCloning == CloningSupported) + , mSupportsTransferring(aSupportsTransferring == TransferringSupported) + , mParent(nullptr) +#ifdef DEBUG + , mCreationThread(NS_GetCurrentThread()) +#endif +{} + +StructuredCloneHolder::~StructuredCloneHolder() +{ + Clear(); + MOZ_ASSERT(mTransferredPorts.IsEmpty()); +} + +void +StructuredCloneHolder::Write(JSContext* aCx, + JS::Handle<JS::Value> aValue, + ErrorResult& aRv) +{ + Write(aCx, aValue, JS::UndefinedHandleValue, + JS::CloneDataPolicy().denySharedArrayBuffer(), aRv); +} + +void +StructuredCloneHolder::Write(JSContext* aCx, + JS::Handle<JS::Value> aValue, + JS::Handle<JS::Value> aTransfer, + JS::CloneDataPolicy cloneDataPolicy, + ErrorResult& aRv) +{ + MOZ_ASSERT_IF(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread, + mCreationThread == NS_GetCurrentThread()); + + if (!StructuredCloneHolderBase::Write(aCx, aValue, aTransfer, cloneDataPolicy)) { + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + return; + } +} + +void +StructuredCloneHolder::Read(nsISupports* aParent, + JSContext* aCx, + JS::MutableHandle<JS::Value> aValue, + ErrorResult& aRv) +{ + MOZ_ASSERT_IF(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread, + mCreationThread == NS_GetCurrentThread()); + MOZ_ASSERT(aParent); + + mozilla::AutoRestore<nsISupports*> guard(mParent); + mParent = aParent; + + if (!StructuredCloneHolderBase::Read(aCx, aValue)) { + JS_ClearPendingException(aCx); + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + } + + // If we are tranferring something, we cannot call 'Read()' more than once. + if (mSupportsTransferring) { + mBlobImplArray.Clear(); + mClonedSurfaces.Clear(); + Clear(); + } +} + +void +StructuredCloneHolder::ReadFromBuffer(nsISupports* aParent, + JSContext* aCx, + JSStructuredCloneData& aBuffer, + JS::MutableHandle<JS::Value> aValue, + ErrorResult& aRv) +{ + ReadFromBuffer(aParent, aCx, aBuffer, + JS_STRUCTURED_CLONE_VERSION, aValue, aRv); +} + +void +StructuredCloneHolder::ReadFromBuffer(nsISupports* aParent, + JSContext* aCx, + JSStructuredCloneData& aBuffer, + uint32_t aAlgorithmVersion, + JS::MutableHandle<JS::Value> aValue, + ErrorResult& aRv) +{ + MOZ_ASSERT_IF(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread, + mCreationThread == NS_GetCurrentThread()); + + MOZ_ASSERT(!mBuffer, "ReadFromBuffer() must be called without a Write()."); + + mozilla::AutoRestore<nsISupports*> guard(mParent); + mParent = aParent; + + if (!JS_ReadStructuredClone(aCx, aBuffer, aAlgorithmVersion, + mStructuredCloneScope, aValue, &sCallbacks, + this)) { + JS_ClearPendingException(aCx); + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + } +} + +/* static */ JSObject* +StructuredCloneHolder::ReadFullySerializableObjects(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag) +{ + if (aTag == SCTAG_DOM_IMAGEDATA) { + return ReadStructuredCloneImageData(aCx, aReader); + } + + if (aTag == SCTAG_DOM_WEBCRYPTO_KEY || aTag == SCTAG_DOM_URLSEARCHPARAMS) { + nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); + if (!global) { + return nullptr; + } + + // Prevent the return value from being trashed by a GC during ~nsRefPtr. + JS::Rooted<JSObject*> result(aCx); + { + if (aTag == SCTAG_DOM_WEBCRYPTO_KEY) { + RefPtr<CryptoKey> key = new CryptoKey(global); + if (!key->ReadStructuredClone(aReader)) { + result = nullptr; + } else { + result = key->WrapObject(aCx, nullptr); + } + } else if (aTag == SCTAG_DOM_URLSEARCHPARAMS) { + RefPtr<URLSearchParams> usp = new URLSearchParams(global); + if (!usp->ReadStructuredClone(aReader)) { + result = nullptr; + } else { + result = usp->WrapObject(aCx, nullptr); + } + } + } + return result; + } + + if (aTag == SCTAG_DOM_NULL_PRINCIPAL || + aTag == SCTAG_DOM_SYSTEM_PRINCIPAL || + aTag == SCTAG_DOM_CONTENT_PRINCIPAL || + aTag == SCTAG_DOM_EXPANDED_PRINCIPAL) { + JSPrincipals* prin; + if (!nsJSPrincipals::ReadKnownPrincipalType(aCx, aReader, aTag, &prin)) { + return nullptr; + } + // nsJSPrincipals::ReadKnownPrincipalType addrefs for us, but because of the + // casting between JSPrincipals* and nsIPrincipal* we can't use + // getter_AddRefs above and have to already_AddRefed here. + nsCOMPtr<nsIPrincipal> principal = already_AddRefed<nsIPrincipal>(nsJSPrincipals::get(prin)); + + JS::RootedValue result(aCx); + nsresult rv = nsContentUtils::WrapNative(aCx, principal, + &NS_GET_IID(nsIPrincipal), + &result); + if (NS_FAILED(rv)) { + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return nullptr; + } + + return result.toObjectOrNull(); + } + +#ifdef MOZ_WEBRTC + if (aTag == SCTAG_DOM_RTC_CERTIFICATE) { + if (!NS_IsMainThread()) { + return nullptr; + } + + nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); + if (!global) { + return nullptr; + } + + // Prevent the return value from being trashed by a GC during ~nsRefPtr. + JS::Rooted<JSObject*> result(aCx); + { + RefPtr<RTCCertificate> cert = new RTCCertificate(global); + if (!cert->ReadStructuredClone(aReader)) { + result = nullptr; + } else { + result = cert->WrapObject(aCx, nullptr); + } + } + return result; + } +#endif + + // Don't know what this is. Bail. + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return nullptr; +} + +/* static */ bool +StructuredCloneHolder::WriteFullySerializableObjects(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JS::Handle<JSObject*> aObj) +{ + JS::Rooted<JSObject*> obj(aCx, aObj); + + // See if this is a ImageData object. + { + ImageData* imageData = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageData, &obj, imageData))) { + return WriteStructuredCloneImageData(aCx, aWriter, imageData); + } + } + + // Handle URLSearchParams cloning + { + URLSearchParams* usp = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(URLSearchParams, &obj, usp))) { + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_URLSEARCHPARAMS, 0) && + usp->WriteStructuredClone(aWriter); + } + } + + // Handle Key cloning + { + CryptoKey* key = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(CryptoKey, &obj, key))) { + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_WEBCRYPTO_KEY, 0) && + key->WriteStructuredClone(aWriter); + } + } + +#ifdef MOZ_WEBRTC + { + // Handle WebRTC Certificate cloning + RTCCertificate* cert = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, &obj, cert))) { + MOZ_ASSERT(NS_IsMainThread()); + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_RTC_CERTIFICATE, 0) && + cert->WriteStructuredClone(aWriter); + } + } +#endif + + if (NS_IsMainThread() && xpc::IsReflector(obj)) { + nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(obj); + nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(base); + if (principal) { + auto nsjsprincipals = nsJSPrincipals::get(principal); + return nsjsprincipals->write(aCx, aWriter); + } + } + + // Don't know what this is + xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); + return false; +} + +namespace { + +// Recursive! +already_AddRefed<BlobImpl> +EnsureBlobForBackgroundManager(BlobImpl* aBlobImpl, + PBackgroundChild* aManager, + ErrorResult& aRv) +{ + MOZ_ASSERT(aBlobImpl); + RefPtr<BlobImpl> blobImpl = aBlobImpl; + + if (!aManager) { + aManager = BackgroundChild::GetForCurrentThread(); + if (!aManager) { + return blobImpl.forget(); + } + } + + const nsTArray<RefPtr<BlobImpl>>* subBlobImpls = + aBlobImpl->GetSubBlobImpls(); + + if (!subBlobImpls || !subBlobImpls->Length()) { + if (nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryObject(blobImpl)) { + // Always make sure we have a blob from an actor we can use on this + // thread. + BlobChild* blobChild = BlobChild::GetOrCreate(aManager, blobImpl); + MOZ_ASSERT(blobChild); + + blobImpl = blobChild->GetBlobImpl(); + MOZ_ASSERT(blobImpl); + + DebugOnly<bool> isMutable; + MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable))); + MOZ_ASSERT(!isMutable); + } else { + MOZ_ALWAYS_SUCCEEDS(blobImpl->SetMutable(false)); + } + + return blobImpl.forget(); + } + + const uint32_t subBlobCount = subBlobImpls->Length(); + MOZ_ASSERT(subBlobCount); + + nsTArray<RefPtr<BlobImpl>> newSubBlobImpls; + newSubBlobImpls.SetLength(subBlobCount); + + bool newBlobImplNeeded = false; + + for (uint32_t index = 0; index < subBlobCount; index++) { + const RefPtr<BlobImpl>& subBlobImpl = subBlobImpls->ElementAt(index); + MOZ_ASSERT(subBlobImpl); + + RefPtr<BlobImpl>& newSubBlobImpl = newSubBlobImpls[index]; + + newSubBlobImpl = EnsureBlobForBackgroundManager(subBlobImpl, aManager, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + MOZ_ASSERT(newSubBlobImpl); + + if (subBlobImpl != newSubBlobImpl) { + newBlobImplNeeded = true; + } + } + + if (newBlobImplNeeded) { + nsString contentType; + blobImpl->GetType(contentType); + + if (blobImpl->IsFile()) { + nsString name; + blobImpl->GetName(name); + + blobImpl = MultipartBlobImpl::Create(Move(newSubBlobImpls), name, + contentType, aRv); + } else { + blobImpl = MultipartBlobImpl::Create(Move(newSubBlobImpls), contentType, aRv); + } + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + MOZ_ALWAYS_SUCCEEDS(blobImpl->SetMutable(false)); + } + + return blobImpl.forget(); +} + +JSObject* +ReadBlob(JSContext* aCx, + uint32_t aIndex, + StructuredCloneHolder* aHolder) +{ + MOZ_ASSERT(aHolder); + MOZ_ASSERT(aIndex < aHolder->BlobImpls().Length()); + RefPtr<BlobImpl> blobImpl = aHolder->BlobImpls()[aIndex]; + + ErrorResult rv; + blobImpl = EnsureBlobForBackgroundManager(blobImpl, nullptr, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return nullptr; + } + + MOZ_ASSERT(blobImpl); + + // RefPtr<File> needs to go out of scope before toObject() is + // called because the static analysis thinks dereferencing XPCOM objects + // can GC (because in some cases it can!), and a return statement with a + // JSObject* type means that JSObject* is on the stack as a raw pointer + // while destructors are running. + JS::Rooted<JS::Value> val(aCx); + { + RefPtr<Blob> blob = Blob::Create(aHolder->ParentDuringRead(), blobImpl); + if (!ToJSValue(aCx, blob, &val)) { + return nullptr; + } + } + + return &val.toObject(); +} + +bool +WriteBlob(JSStructuredCloneWriter* aWriter, + Blob* aBlob, + StructuredCloneHolder* aHolder) +{ + MOZ_ASSERT(aWriter); + MOZ_ASSERT(aBlob); + MOZ_ASSERT(aHolder); + + if (JS_GetStructuredCloneScope(aWriter) != JS::StructuredCloneScope::SameProcessSameThread && + !aBlob->Impl()->MayBeClonedToOtherThreads()) { + return false; + } + + ErrorResult rv; + RefPtr<BlobImpl> blobImpl = + EnsureBlobForBackgroundManager(aBlob->Impl(), nullptr, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } + + MOZ_ASSERT(blobImpl); + + MOZ_ALWAYS_SUCCEEDS(blobImpl->SetMutable(false)); + + // We store the position of the blobImpl in the array as index. + if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB, + aHolder->BlobImpls().Length())) { + aHolder->BlobImpls().AppendElement(blobImpl); + return true; + } + + return false; +} + +// A directory is serialized as: +// - pair of ints: SCTAG_DOM_DIRECTORY, path length +// - path as string +bool +WriteDirectory(JSStructuredCloneWriter* aWriter, + Directory* aDirectory) +{ + MOZ_ASSERT(aWriter); + MOZ_ASSERT(aDirectory); + + nsAutoString path; + aDirectory->GetFullRealPath(path); + + size_t charSize = sizeof(nsString::char_type); + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_DIRECTORY, path.Length()) && + JS_WriteBytes(aWriter, path.get(), path.Length() * charSize); +} + +already_AddRefed<Directory> +ReadDirectoryInternal(JSStructuredCloneReader* aReader, + uint32_t aPathLength, + StructuredCloneHolder* aHolder) +{ + MOZ_ASSERT(aReader); + MOZ_ASSERT(aHolder); + + nsAutoString path; + path.SetLength(aPathLength); + size_t charSize = sizeof(nsString::char_type); + if (!JS_ReadBytes(aReader, (void*) path.BeginWriting(), + aPathLength * charSize)) { + return nullptr; + } + + nsCOMPtr<nsIFile> file; + nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + RefPtr<Directory> directory = + Directory::Create(aHolder->ParentDuringRead(), file); + return directory.forget(); +} + +JSObject* +ReadDirectory(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aPathLength, + StructuredCloneHolder* aHolder) +{ + MOZ_ASSERT(aCx); + MOZ_ASSERT(aReader); + MOZ_ASSERT(aHolder); + + // RefPtr<Directory> needs to go out of scope before toObject() is + // called because the static analysis thinks dereferencing XPCOM objects + // can GC (because in some cases it can!), and a return statement with a + // JSObject* type means that JSObject* is on the stack as a raw pointer + // while destructors are running. + JS::Rooted<JS::Value> val(aCx); + { + RefPtr<Directory> directory = + ReadDirectoryInternal(aReader, aPathLength, aHolder); + if (!directory) { + return nullptr; + } + + if (!ToJSValue(aCx, directory, &val)) { + return nullptr; + } + } + + return &val.toObject(); +} + +// Read the WriteFileList for the format. +JSObject* +ReadFileList(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aCount, + StructuredCloneHolder* aHolder) +{ + MOZ_ASSERT(aCx); + MOZ_ASSERT(aReader); + + JS::Rooted<JS::Value> val(aCx); + { + RefPtr<FileList> fileList = new FileList(aHolder->ParentDuringRead()); + + uint32_t zero, index; + // |index| is the index of the first blobImpl. + if (!JS_ReadUint32Pair(aReader, &zero, &index)) { + return nullptr; + } + + MOZ_ASSERT(zero == 0); + + // |aCount| is the number of BlobImpls to use from the |index|. + for (uint32_t i = 0; i < aCount; ++i) { + uint32_t pos = index + i; + MOZ_ASSERT(pos < aHolder->BlobImpls().Length()); + + RefPtr<BlobImpl> blobImpl = aHolder->BlobImpls()[pos]; + MOZ_ASSERT(blobImpl->IsFile()); + + ErrorResult rv; + blobImpl = EnsureBlobForBackgroundManager(blobImpl, nullptr, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return nullptr; + } + + MOZ_ASSERT(blobImpl); + + RefPtr<File> file = File::Create(aHolder->ParentDuringRead(), blobImpl); + if (!fileList->Append(file)) { + return nullptr; + } + } + + if (!ToJSValue(aCx, fileList, &val)) { + return nullptr; + } + } + + return &val.toObject(); +} + +// The format of the FileList serialization is: +// - pair of ints: SCTAG_DOM_FILELIST, Length of the FileList +// - pair of ints: 0, The offset of the BlobImpl array +bool +WriteFileList(JSStructuredCloneWriter* aWriter, + FileList* aFileList, + StructuredCloneHolder* aHolder) +{ + MOZ_ASSERT(aWriter); + MOZ_ASSERT(aFileList); + MOZ_ASSERT(aHolder); + + // A FileList is serialized writing the X number of elements and the offset + // from mBlobImplArray. The Read will take X elements from mBlobImplArray + // starting from the offset. + if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_FILELIST, + aFileList->Length()) || + !JS_WriteUint32Pair(aWriter, 0, + aHolder->BlobImpls().Length())) { + return false; + } + + ErrorResult rv; + nsTArray<RefPtr<BlobImpl>> blobImpls; + + for (uint32_t i = 0; i < aFileList->Length(); ++i) { + RefPtr<BlobImpl> blobImpl = + EnsureBlobForBackgroundManager(aFileList->Item(i)->Impl(), nullptr, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } + + MOZ_ASSERT(blobImpl); + blobImpls.AppendElement(blobImpl); + } + + aHolder->BlobImpls().AppendElements(blobImpls); + return true; +} + +// Read the WriteFormData for the format. +JSObject* +ReadFormData(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aCount, + StructuredCloneHolder* aHolder) +{ + MOZ_ASSERT(aCx); + MOZ_ASSERT(aReader); + MOZ_ASSERT(aHolder); + + // See the serialization of the FormData for the format. + JS::Rooted<JS::Value> val(aCx); + { + RefPtr<FormData> formData = + new FormData(aHolder->ParentDuringRead()); + + Optional<nsAString> thirdArg; + for (uint32_t i = 0; i < aCount; ++i) { + nsAutoString name; + if (!ReadString(aReader, name)) { + return nullptr; + } + + uint32_t tag, indexOrLengthOfString; + if (!JS_ReadUint32Pair(aReader, &tag, &indexOrLengthOfString)) { + return nullptr; + } + + if (tag == SCTAG_DOM_BLOB) { + MOZ_ASSERT(indexOrLengthOfString < aHolder->BlobImpls().Length()); + + RefPtr<BlobImpl> blobImpl = + aHolder->BlobImpls()[indexOrLengthOfString]; + + ErrorResult rv; + blobImpl = EnsureBlobForBackgroundManager(blobImpl, nullptr, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return nullptr; + } + + MOZ_ASSERT(blobImpl); + + RefPtr<Blob> blob = + Blob::Create(aHolder->ParentDuringRead(), blobImpl); + MOZ_ASSERT(blob); + + formData->Append(name, *blob, thirdArg, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return nullptr; + } + + } else if (tag == SCTAG_DOM_DIRECTORY) { + RefPtr<Directory> directory = + ReadDirectoryInternal(aReader, indexOrLengthOfString, aHolder); + if (!directory) { + return nullptr; + } + + formData->Append(name, directory); + + } else { + MOZ_ASSERT(tag == 0); + + nsAutoString value; + value.SetLength(indexOrLengthOfString); + size_t charSize = sizeof(nsString::char_type); + if (!JS_ReadBytes(aReader, (void*) value.BeginWriting(), + indexOrLengthOfString * charSize)) { + return nullptr; + } + + ErrorResult rv; + formData->Append(name, value, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return nullptr; + } + } + } + + if (!ToJSValue(aCx, formData, &val)) { + return nullptr; + } + } + + return &val.toObject(); +} + +// The format of the FormData serialization is: +// - pair of ints: SCTAG_DOM_FORMDATA, Length of the FormData elements +// - for each Element element: +// - name string +// - if it's a blob: +// - pair of ints: SCTAG_DOM_BLOB, index of the BlobImpl in the array +// mBlobImplArray. +// - if it's a directory (See WriteDirectory): +// - pair of ints: SCTAG_DOM_DIRECTORY, path length +// - path as string +// - else: +// - pair of ints: 0, string length +// - value string +bool +WriteFormData(JSStructuredCloneWriter* aWriter, + FormData* aFormData, + StructuredCloneHolder* aHolder) +{ + MOZ_ASSERT(aWriter); + MOZ_ASSERT(aFormData); + MOZ_ASSERT(aHolder); + + if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_FORMDATA, + aFormData->Length())) { + return false; + } + + class MOZ_STACK_CLASS Closure final + { + JSStructuredCloneWriter* mWriter; + StructuredCloneHolder* mHolder; + + public: + Closure(JSStructuredCloneWriter* aWriter, + StructuredCloneHolder* aHolder) + : mWriter(aWriter), + mHolder(aHolder) + { } + + static bool + Write(const nsString& aName, const OwningBlobOrDirectoryOrUSVString& aValue, + void* aClosure) + { + Closure* closure = static_cast<Closure*>(aClosure); + if (!WriteString(closure->mWriter, aName)) { + return false; + } + + if (aValue.IsBlob()) { + ErrorResult rv; + RefPtr<BlobImpl> blobImpl = + EnsureBlobForBackgroundManager(aValue.GetAsBlob()->Impl(), nullptr, + rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } + + if (!JS_WriteUint32Pair(closure->mWriter, SCTAG_DOM_BLOB, + closure->mHolder->BlobImpls().Length())) { + return false; + } + + closure->mHolder->BlobImpls().AppendElement(blobImpl); + return true; + } + + if (aValue.IsDirectory()) { + Directory* directory = aValue.GetAsDirectory(); + + if (closure->mHolder->CloneScope() != + StructuredCloneHolder::StructuredCloneScope::SameProcessSameThread && + !directory->ClonableToDifferentThreadOrProcess()) { + return false; + } + + return WriteDirectory(closure->mWriter, directory); + } + + size_t charSize = sizeof(nsString::char_type); + if (!JS_WriteUint32Pair(closure->mWriter, 0, + aValue.GetAsUSVString().Length()) || + !JS_WriteBytes(closure->mWriter, aValue.GetAsUSVString().get(), + aValue.GetAsUSVString().Length() * charSize)) { + return false; + } + + return true; + } + }; + Closure closure(aWriter, aHolder); + return aFormData->ForEach(Closure::Write, &closure); +} + +JSObject* +ReadWasmModule(JSContext* aCx, + uint32_t aIndex, + StructuredCloneHolder* aHolder) +{ + MOZ_ASSERT(aHolder); + MOZ_ASSERT(aIndex < aHolder->WasmModules().Length()); + MOZ_ASSERT(aHolder->CloneScope() == StructuredCloneHolder::StructuredCloneScope::SameProcessSameThread || + aHolder->CloneScope() == StructuredCloneHolder::StructuredCloneScope::SameProcessDifferentThread); + + RefPtr<JS::WasmModule> wasmModule = aHolder->WasmModules()[aIndex]; + return wasmModule->createObject(aCx); +} + +bool +WriteWasmModule(JSStructuredCloneWriter* aWriter, + JS::WasmModule* aWasmModule, + StructuredCloneHolder* aHolder) +{ + MOZ_ASSERT(aWriter); + MOZ_ASSERT(aWasmModule); + MOZ_ASSERT(aHolder); + MOZ_ASSERT(aHolder->CloneScope() == StructuredCloneHolder::StructuredCloneScope::SameProcessSameThread || + aHolder->CloneScope() == StructuredCloneHolder::StructuredCloneScope::SameProcessDifferentThread); + + // We store the position of the wasmModule in the array as index. + if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_WASM, + aHolder->WasmModules().Length())) { + aHolder->WasmModules().AppendElement(aWasmModule); + return true; + } + + return false; +} + +} // anonymous namespace + +JSObject* +StructuredCloneHolder::CustomReadHandler(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + uint32_t aIndex) +{ + MOZ_ASSERT(mSupportsCloning); + + if (aTag == SCTAG_DOM_BLOB) { + return ReadBlob(aCx, aIndex, this); + } + + if (aTag == SCTAG_DOM_DIRECTORY) { + return ReadDirectory(aCx, aReader, aIndex, this); + } + + if (aTag == SCTAG_DOM_FILELIST) { + return ReadFileList(aCx, aReader, aIndex, this); + } + + if (aTag == SCTAG_DOM_FORMDATA) { + return ReadFormData(aCx, aReader, aIndex, this); + } + + if (aTag == SCTAG_DOM_IMAGEBITMAP) { + MOZ_ASSERT(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread || + mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread); + + // Get the current global object. + // This can be null. + nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent); + // aIndex is the index of the cloned image. + return ImageBitmap::ReadStructuredClone(aCx, aReader, + parent, GetSurfaces(), aIndex); + } + + if (aTag == SCTAG_DOM_WASM) { + return ReadWasmModule(aCx, aIndex, this); + } + + return ReadFullySerializableObjects(aCx, aReader, aTag); +} + +bool +StructuredCloneHolder::CustomWriteHandler(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JS::Handle<JSObject*> aObj) +{ + if (!mSupportsCloning) { + return false; + } + + JS::Rooted<JSObject*> obj(aCx, aObj); + + // See if this is a File/Blob object. + { + Blob* blob = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) { + return WriteBlob(aWriter, blob, this); + } + } + + // See if this is a Directory object. + { + Directory* directory = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(Directory, &obj, directory))) { + if (mStructuredCloneScope != StructuredCloneScope::SameProcessSameThread && + !directory->ClonableToDifferentThreadOrProcess()) { + return false; + } + + return WriteDirectory(aWriter, directory); + } + } + + // See if this is a FileList object. + { + FileList* fileList = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, &obj, fileList))) { + return WriteFileList(aWriter, fileList, this); + } + } + + // See if this is a FormData object. + { + FormData* formData = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(FormData, &obj, formData))) { + return WriteFormData(aWriter, formData, this); + } + } + + // See if this is an ImageBitmap object. + if (mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread || + mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) { + ImageBitmap* imageBitmap = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, &obj, imageBitmap))) { + return ImageBitmap::WriteStructuredClone(aWriter, + GetSurfaces(), + imageBitmap); + } + } + + // See if this is a WasmModule. + if ((mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread || + mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) && + JS::IsWasmModuleObject(obj)) { + RefPtr<JS::WasmModule> module = JS::GetWasmModule(obj); + MOZ_ASSERT(module); + + return WriteWasmModule(aWriter, module, this); + } + + return WriteFullySerializableObjects(aCx, aWriter, aObj); +} + +bool +StructuredCloneHolder::CustomReadTransferHandler(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + void* aContent, + uint64_t aExtraData, + JS::MutableHandleObject aReturnObject) +{ + MOZ_ASSERT(mSupportsTransferring); + + if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { + MOZ_ASSERT(aExtraData < mPortIdentifiers.Length()); + const MessagePortIdentifier& portIdentifier = mPortIdentifiers[aExtraData]; + + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent); + + ErrorResult rv; + RefPtr<MessagePort> port = + MessagePort::Create(global, portIdentifier, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } + + mTransferredPorts.AppendElement(port); + + JS::Rooted<JS::Value> value(aCx); + if (!GetOrCreateDOMReflector(aCx, port, &value)) { + JS_ClearPendingException(aCx); + return false; + } + + aReturnObject.set(&value.toObject()); + return true; + } + + if (aTag == SCTAG_DOM_CANVAS) { + MOZ_ASSERT(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread || + mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread); + MOZ_ASSERT(aContent); + OffscreenCanvasCloneData* data = + static_cast<OffscreenCanvasCloneData*>(aContent); + nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent); + RefPtr<OffscreenCanvas> canvas = OffscreenCanvas::CreateFromCloneData(parent, data); + delete data; + + JS::Rooted<JS::Value> value(aCx); + if (!GetOrCreateDOMReflector(aCx, canvas, &value)) { + JS_ClearPendingException(aCx); + return false; + } + + aReturnObject.set(&value.toObject()); + return true; + } + + if (aTag == SCTAG_DOM_IMAGEBITMAP) { + MOZ_ASSERT(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread || + mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread); + MOZ_ASSERT(aContent); + ImageBitmapCloneData* data = + static_cast<ImageBitmapCloneData*>(aContent); + nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent); + RefPtr<ImageBitmap> bitmap = ImageBitmap::CreateFromCloneData(parent, data); + delete data; + + JS::Rooted<JS::Value> value(aCx); + if (!GetOrCreateDOMReflector(aCx, bitmap, &value)) { + JS_ClearPendingException(aCx); + return false; + } + + aReturnObject.set(&value.toObject()); + return true; + } + + return false; +} + +bool +StructuredCloneHolder::CustomWriteTransferHandler(JSContext* aCx, + JS::Handle<JSObject*> aObj, + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t* aExtraData) +{ + if (!mSupportsTransferring) { + return false; + } + + JS::Rooted<JSObject*> obj(aCx, aObj); + + { + MessagePort* port = nullptr; + nsresult rv = UNWRAP_OBJECT(MessagePort, &obj, port); + if (NS_SUCCEEDED(rv)) { + // We use aExtraData to store the index of this new port identifier. + *aExtraData = mPortIdentifiers.Length(); + MessagePortIdentifier* identifier = mPortIdentifiers.AppendElement(); + + port->CloneAndDisentangle(*identifier); + + *aTag = SCTAG_DOM_MAP_MESSAGEPORT; + *aOwnership = JS::SCTAG_TMO_CUSTOM; + *aContent = nullptr; + + return true; + } + + if (mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread || + mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) { + OffscreenCanvas* canvas = nullptr; + rv = UNWRAP_OBJECT(OffscreenCanvas, &obj, canvas); + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(canvas); + + *aExtraData = 0; + *aTag = SCTAG_DOM_CANVAS; + *aOwnership = JS::SCTAG_TMO_CUSTOM; + *aContent = canvas->ToCloneData(); + MOZ_ASSERT(*aContent); + canvas->SetNeutered(); + + return true; + } + + ImageBitmap* bitmap = nullptr; + rv = UNWRAP_OBJECT(ImageBitmap, &obj, bitmap); + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(bitmap); + + *aExtraData = 0; + *aTag = SCTAG_DOM_IMAGEBITMAP; + *aOwnership = JS::SCTAG_TMO_CUSTOM; + *aContent = bitmap->ToCloneData().release(); + MOZ_ASSERT(*aContent); + bitmap->Close(); + + return true; + } + } + } + + return false; +} + +void +StructuredCloneHolder::CustomFreeTransferHandler(uint32_t aTag, + JS::TransferableOwnership aOwnership, + void* aContent, + uint64_t aExtraData) +{ + MOZ_ASSERT(mSupportsTransferring); + + if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { + MOZ_ASSERT(!aContent); + MOZ_ASSERT(aExtraData < mPortIdentifiers.Length()); + MessagePort::ForceClose(mPortIdentifiers[aExtraData]); + return; + } + + if (aTag == SCTAG_DOM_CANVAS) { + MOZ_ASSERT(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread || + mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread); + MOZ_ASSERT(aContent); + OffscreenCanvasCloneData* data = + static_cast<OffscreenCanvasCloneData*>(aContent); + delete data; + return; + } + + if (aTag == SCTAG_DOM_IMAGEBITMAP) { + MOZ_ASSERT(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread || + mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread); + MOZ_ASSERT(aContent); + ImageBitmapCloneData* data = + static_cast<ImageBitmapCloneData*>(aContent); + delete data; + return; + } +} + +bool +StructuredCloneHolder::TakeTransferredPortsAsSequence(Sequence<OwningNonNull<mozilla::dom::MessagePort>>& aPorts) +{ + nsTArray<RefPtr<MessagePort>> ports = TakeTransferredPorts(); + + aPorts.Clear(); + for (uint32_t i = 0, len = ports.Length(); i < len; ++i) { + if (!aPorts.AppendElement(ports[i].forget(), fallible)) { + return false; + } + } + + return true; +} + +} // dom namespace +} // mozilla namespace |