gecko-dev/dom/base/StructuredCloneHelper.cpp

419 lines
13 KiB
C++

/* -*- 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 "mozilla/dom/BlobBinding.h"
#include "mozilla/dom/FileListBinding.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<StructuredCloneHelperInternal*>(aClosure);
MOZ_ASSERT(helper);
return helper->ReadCallback(aCx, aReader, aTag, aIndex);
}
bool
StructuredCloneCallbacksWrite(JSContext* aCx,
JSStructuredCloneWriter* aWriter,
JS::Handle<JSObject*> aObj,
void* aClosure)
{
StructuredCloneHelperInternal* helper =
static_cast<StructuredCloneHelperInternal*>(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<StructuredCloneHelperInternal*>(aClosure);
MOZ_ASSERT(helper);
return helper->ReadTransferCallback(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)
{
StructuredCloneHelperInternal* helper =
static_cast<StructuredCloneHelperInternal*>(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<StructuredCloneHelperInternal*>(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<JS::Value> 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<JS::Value> aValue,
JS::Handle<JS::Value> 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<JS::Value> 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<JSObject*> 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(uint32_t aFlags)
: mFlags(aFlags)
, mParent(nullptr)
{}
StructuredCloneHelper::~StructuredCloneHelper()
{
Shutdown();
}
bool
StructuredCloneHelper::Write(JSContext* aCx,
JS::Handle<JS::Value> aValue,
JS::Handle<JS::Value> aTransfer)
{
bool ok = StructuredCloneHelperInternal::Write(aCx, aValue, aTransfer);
mTransferringPort.Clear();
return ok;
}
bool
StructuredCloneHelper::Read(nsISupports* aParent,
JSContext* aCx,
JS::MutableHandle<JS::Value> aValue)
{
mozilla::AutoRestore<nsISupports*> guard(mParent);
mParent = aParent;
return StructuredCloneHelperInternal::Read(aCx, aValue);
}
JSObject*
StructuredCloneHelper::ReadCallback(JSContext* aCx,
JSStructuredCloneReader* aReader,
uint32_t aTag,
uint32_t aIndex)
{
if (aTag == SCTAG_DOM_BLOB) {
MOZ_ASSERT(!(mFlags & eBlobNotSupported));
BlobImpl* blobImpl;
if (JS_ReadBytes(aReader, &blobImpl, sizeof(blobImpl))) {
MOZ_ASSERT(blobImpl);
// nsRefPtr<File> 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<JS::Value> val(aCx);
{
nsRefPtr<Blob> blob = Blob::Create(mParent, blobImpl);
if (!ToJSValue(aCx, blob, &val)) {
return nullptr;
}
}
return &val.toObject();
}
}
if (aTag == SCTAG_DOM_FILELIST) {
MOZ_ASSERT(!(mFlags & eFileListNotSupported));
FileListClonedData* fileListClonedData;
if (JS_ReadBytes(aReader, &fileListClonedData,
sizeof(fileListClonedData))) {
MOZ_ASSERT(fileListClonedData);
// nsRefPtr<FileList> 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<JS::Value> val(aCx);
{
nsRefPtr<FileList> fileList =
FileList::Create(mParent, fileListClonedData);
if (!fileList || !ToJSValue(aCx, fileList, &val)) {
return nullptr;
}
}
return &val.toObject();
}
}
return NS_DOMReadStructuredClone(aCx, aReader, aTag, aIndex, nullptr);
}
bool
StructuredCloneHelper::WriteCallback(JSContext* aCx,
JSStructuredCloneWriter* aWriter,
JS::Handle<JSObject*> aObj)
{
// See if this is a File/Blob object.
if (!(mFlags & eBlobNotSupported)) {
Blob* blob = nullptr;
if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
BlobImpl* blobImpl = blob->Impl();
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB, 0) &&
JS_WriteBytes(aWriter, &blobImpl, sizeof(blobImpl)) &&
StoreISupports(blobImpl);
}
}
if (!(mFlags & eFileListNotSupported)) {
FileList* fileList = nullptr;
if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList))) {
nsRefPtr<FileListClonedData> fileListClonedData =
fileList->CreateClonedData();
MOZ_ASSERT(fileListClonedData);
FileListClonedData* ptr = fileListClonedData.get();
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_FILELIST, 0) &&
JS_WriteBytes(aWriter, &ptr, sizeof(ptr)) &&
StoreISupports(fileListClonedData);
}
}
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)
{
if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
MOZ_ASSERT(!(mFlags & eMessagePortNotSupported));
// This can be null.
nsCOMPtr<nsPIDOMWindow> 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<MessagePort> port =
MessagePort::Create(window, portIdentifier, rv);
if (NS_WARN_IF(rv.Failed())) {
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;
}
return false;
}
bool
StructuredCloneHelper::WriteTransferCallback(JSContext* aCx,
JS::Handle<JSObject*> aObj,
uint32_t* aTag,
JS::TransferableOwnership* aOwnership,
void** aContent,
uint64_t* aExtraData)
{
if (!(mFlags & eMessagePortNotSupported)) {
MessagePortBase* port = nullptr;
nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port);
if (NS_SUCCEEDED(rv)) {
if (mTransferringPort.Contains(port)) {
// No duplicates.
return false;
}
// 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;
}
mTransferringPort.AppendElement(port);
*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)
{
if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
MOZ_ASSERT(!(mFlags & eMessagePortNotSupported));
MOZ_ASSERT(!aContent);
MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
MessagePort::ForceClose(mPortIdentifiers[aExtraData]);
}
}
} // dom namespace
} // mozilla namespace