/* -*- 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 "StructuredCloneHelper.h" #include "ImageContainer.h" #include "mozilla/dom/BlobBinding.h" #include "mozilla/dom/FileListBinding.h" #include "mozilla/dom/ImageBitmap.h" #include "mozilla/dom/ImageBitmapBinding.h" #include "mozilla/dom/StructuredCloneTags.h" namespace mozilla { namespace dom { namespace { JSObject* StructuredCloneCallbacksRead(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, uint32_t aIndex, void* aClosure) { StructuredCloneHelperInternal* helper = static_cast(aClosure); MOZ_ASSERT(helper); return helper->ReadCallback(aCx, aReader, aTag, aIndex); } bool StructuredCloneCallbacksWrite(JSContext* aCx, JSStructuredCloneWriter* aWriter, JS::Handle aObj, void* aClosure) { StructuredCloneHelperInternal* helper = static_cast(aClosure); MOZ_ASSERT(helper); return helper->WriteCallback(aCx, aWriter, aObj); } bool StructuredCloneCallbacksReadTransfer(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, void* aContent, uint64_t aExtraData, void* aClosure, JS::MutableHandleObject aReturnObject) { StructuredCloneHelperInternal* helper = static_cast(aClosure); MOZ_ASSERT(helper); return helper->ReadTransferCallback(aCx, aReader, aTag, aContent, aExtraData, aReturnObject); } bool StructuredCloneCallbacksWriteTransfer(JSContext* aCx, JS::Handle aObj, void* aClosure, // Output: uint32_t* aTag, JS::TransferableOwnership* aOwnership, void** aContent, uint64_t* aExtraData) { StructuredCloneHelperInternal* helper = static_cast(aClosure); MOZ_ASSERT(helper); return helper->WriteTransferCallback(aCx, aObj, aTag, aOwnership, aContent, aExtraData); } void StructuredCloneCallbacksFreeTransfer(uint32_t aTag, JS::TransferableOwnership aOwnership, void* aContent, uint64_t aExtraData, void* aClosure) { StructuredCloneHelperInternal* helper = static_cast(aClosure); MOZ_ASSERT(helper); return helper->FreeTransferCallback(aTag, aOwnership, aContent, aExtraData); } void StructuredCloneCallbacksError(JSContext* aCx, uint32_t aErrorId) { NS_WARNING("Failed to clone data."); } const JSStructuredCloneCallbacks gCallbacks = { StructuredCloneCallbacksRead, StructuredCloneCallbacksWrite, StructuredCloneCallbacksError, StructuredCloneCallbacksReadTransfer, StructuredCloneCallbacksWriteTransfer, StructuredCloneCallbacksFreeTransfer }; } // anonymous namespace // StructuredCloneHelperInternal class StructuredCloneHelperInternal::StructuredCloneHelperInternal() #ifdef DEBUG : mShutdownCalled(false) #endif {} StructuredCloneHelperInternal::~StructuredCloneHelperInternal() { #ifdef DEBUG MOZ_ASSERT(mShutdownCalled); #endif } void StructuredCloneHelperInternal::Shutdown() { #ifdef DEBUG MOZ_ASSERT(!mShutdownCalled, "Shutdown already called!"); mShutdownCalled = true; #endif mBuffer = nullptr; } bool StructuredCloneHelperInternal::Write(JSContext* aCx, JS::Handle aValue) { MOZ_ASSERT(!mBuffer, "Double Write is not allowed"); MOZ_ASSERT(!mShutdownCalled, "This method cannot be called after Shutdown."); mBuffer = new JSAutoStructuredCloneBuffer(&gCallbacks, this); return mBuffer->write(aCx, aValue, &gCallbacks, this); } bool StructuredCloneHelperInternal::Write(JSContext* aCx, JS::Handle aValue, JS::Handle aTransfer) { MOZ_ASSERT(!mBuffer, "Double Write is not allowed"); MOZ_ASSERT(!mShutdownCalled, "This method cannot be called after Shutdown."); mBuffer = new JSAutoStructuredCloneBuffer(&gCallbacks, this); return mBuffer->write(aCx, aValue, aTransfer, &gCallbacks, this); } bool StructuredCloneHelperInternal::Read(JSContext* aCx, JS::MutableHandle aValue) { MOZ_ASSERT(mBuffer, "Read() without Write() is not allowed."); MOZ_ASSERT(!mShutdownCalled, "This method cannot be called after Shutdown."); bool ok = mBuffer->read(aCx, aValue, &gCallbacks, this); mBuffer = nullptr; return ok; } bool StructuredCloneHelperInternal::ReadTransferCallback(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, void* aContent, uint64_t aExtraData, JS::MutableHandleObject aReturnObject) { MOZ_CRASH("Nothing to read."); return false; } bool StructuredCloneHelperInternal::WriteTransferCallback(JSContext* aCx, JS::Handle aObj, uint32_t* aTag, JS::TransferableOwnership* aOwnership, void** aContent, uint64_t* aExtraData) { // No transfers are supported by default. return false; } void StructuredCloneHelperInternal::FreeTransferCallback(uint32_t aTag, JS::TransferableOwnership aOwnership, void* aContent, uint64_t aExtraData) { MOZ_CRASH("Nothing to free."); } // StructuredCloneHelper class StructuredCloneHelper::StructuredCloneHelper(CloningSupport aSupportsCloning, TransferringSupport aSupportsTransferring) : mSupportsCloning(aSupportsCloning == CloningSupported) , mSupportsTransferring(aSupportsTransferring == TransferringSupported) , mParent(nullptr) {} StructuredCloneHelper::~StructuredCloneHelper() { Shutdown(); } void StructuredCloneHelper::Write(JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { Write(aCx, aValue, JS::UndefinedHandleValue, aRv); } void StructuredCloneHelper::Write(JSContext* aCx, JS::Handle aValue, JS::Handle aTransfer, ErrorResult& aRv) { if (!StructuredCloneHelperInternal::Write(aCx, aValue, aTransfer)) { aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); } } void StructuredCloneHelper::Read(nsISupports* aParent, JSContext* aCx, JS::MutableHandle aValue, ErrorResult& aRv) { mozilla::AutoRestore guard(mParent); mParent = aParent; if (!StructuredCloneHelperInternal::Read(aCx, aValue)) { JS_ClearPendingException(aCx); aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); } mBlobImplArray.Clear(); } void StructuredCloneHelper::ReadFromBuffer(nsISupports* aParent, JSContext* aCx, uint64_t* aBuffer, size_t aBufferLength, JS::MutableHandle aValue, ErrorResult& aRv) { MOZ_ASSERT(!mBuffer, "ReadFromBuffer() must be called without a Write()."); MOZ_ASSERT(aBuffer); mozilla::AutoRestore guard(mParent); mParent = aParent; if (!JS_ReadStructuredClone(aCx, aBuffer, aBufferLength, JS_STRUCTURED_CLONE_VERSION, aValue, &gCallbacks, this)) { JS_ClearPendingException(aCx); aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); } } void StructuredCloneHelper::MoveBufferDataToArray(FallibleTArray& aArray, ErrorResult& aRv) { MOZ_ASSERT(mBuffer, "MoveBuffer() cannot be called without a Write()."); if (NS_WARN_IF(!aArray.SetLength(BufferSize(), mozilla::fallible))) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } uint64_t* buffer; size_t size; mBuffer->steal(&buffer, &size); mBuffer = nullptr; memcpy(aArray.Elements(), buffer, size); js_free(buffer); } void StructuredCloneHelper::FreeBuffer(uint64_t* aBuffer, size_t aBufferLength) { MOZ_ASSERT(!mBuffer, "FreeBuffer() must be called without a Write()."); MOZ_ASSERT(aBuffer); MOZ_ASSERT(aBufferLength); JS_ClearStructuredClone(aBuffer, aBufferLength, &gCallbacks, this, false); } JSObject* StructuredCloneHelper::ReadCallback(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, uint32_t aIndex) { MOZ_ASSERT(mSupportsCloning); if (aTag == SCTAG_DOM_BLOB) { MOZ_ASSERT(aIndex < mBlobImplArray.Length()); nsRefPtr blobImpl = mBlobImplArray[aIndex]; // nsRefPtr needs to go out of scope before toObjectOrNull() 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 val(aCx); { nsRefPtr blob = Blob::Create(mParent, blobImpl); if (!ToJSValue(aCx, blob, &val)) { return nullptr; } } return &val.toObject(); } if (aTag == SCTAG_DOM_FILELIST) { JS::Rooted val(aCx); { nsRefPtr fileList = new FileList(mParent); // |aIndex| is the number of BlobImpls to use from |offset|. uint32_t tag, offset; if (!JS_ReadUint32Pair(aReader, &tag, &offset)) { return nullptr; } MOZ_ASSERT(tag == 0); for (uint32_t i = 0; i < aIndex; ++i) { uint32_t index = offset + i; MOZ_ASSERT(index < mBlobImplArray.Length()); nsRefPtr blobImpl = mBlobImplArray[index]; MOZ_ASSERT(blobImpl->IsFile()); nsRefPtr file = File::Create(mParent, blobImpl); if (!fileList->Append(file)) { return nullptr; } } if (!ToJSValue(aCx, fileList, &val)) { return nullptr; } } return &val.toObject(); } if (aTag == SCTAG_DOM_IMAGEBITMAP) { // Get the current global object. // This can be null. nsCOMPtr parent = do_QueryInterface(mParent); // aIndex is the index of the cloned image. return ImageBitmap::ReadStructuredClone(aCx, aReader, parent, GetImages(), aIndex); } return NS_DOMReadStructuredClone(aCx, aReader, aTag, aIndex, nullptr); } bool StructuredCloneHelper::WriteCallback(JSContext* aCx, JSStructuredCloneWriter* aWriter, JS::Handle aObj) { if (!mSupportsCloning) { return false; } // See if this is a File/Blob object. { Blob* blob = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) { BlobImpl* blobImpl = blob->Impl(); if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB, mBlobImplArray.Length())) { mBlobImplArray.AppendElement(blobImpl); return true; } return false; } } { FileList* fileList = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList))) { // 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, fileList->Length()) || !JS_WriteUint32Pair(aWriter, 0, mBlobImplArray.Length())) { return false; } for (uint32_t i = 0; i < fileList->Length(); ++i) { mBlobImplArray.AppendElement(fileList->Item(i)->Impl()); } return true; } } // See if this is an ImageBitmap object. { ImageBitmap* imageBitmap = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, aObj, imageBitmap))) { return ImageBitmap::WriteStructuredClone(aWriter, GetImages(), imageBitmap); } } return NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nullptr); } bool StructuredCloneHelper::ReadTransferCallback(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, void* aContent, uint64_t aExtraData, JS::MutableHandleObject aReturnObject) { MOZ_ASSERT(mSupportsTransferring); if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { // This can be null. nsCOMPtr window = do_QueryInterface(mParent); MOZ_ASSERT(aExtraData < mPortIdentifiers.Length()); const MessagePortIdentifier& portIdentifier = mPortIdentifiers[aExtraData]; // aExtraData is the index of this port identifier. ErrorResult rv; nsRefPtr port = MessagePort::Create(window, portIdentifier, rv); if (NS_WARN_IF(rv.Failed())) { return false; } mTransferredPorts.AppendElement(port); JS::Rooted value(aCx); if (!GetOrCreateDOMReflector(aCx, port, &value)) { JS_ClearPendingException(aCx); return false; } aReturnObject.set(&value.toObject()); return true; } return false; } bool StructuredCloneHelper::WriteTransferCallback(JSContext* aCx, JS::Handle aObj, uint32_t* aTag, JS::TransferableOwnership* aOwnership, void** aContent, uint64_t* aExtraData) { if (!mSupportsTransferring) { return false; } { MessagePortBase* port = nullptr; nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); if (NS_SUCCEEDED(rv)) { // We use aExtraData to store the index of this new port identifier. *aExtraData = mPortIdentifiers.Length(); MessagePortIdentifier* identifier = mPortIdentifiers.AppendElement(); if (!port->CloneAndDisentangle(*identifier)) { return false; } *aTag = SCTAG_DOM_MAP_MESSAGEPORT; *aOwnership = JS::SCTAG_TMO_CUSTOM; *aContent = nullptr; return true; } } return false; } void StructuredCloneHelper::FreeTransferCallback(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]); } } } // dom namespace } // mozilla namespace