gecko/dom/bindings/TypedArray.h

438 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/. */
#ifndef mozilla_dom_TypedArray_h
#define mozilla_dom_TypedArray_h
#include "jsapi.h"
#include "jsfriendapi.h"
#include "js/RootingAPI.h"
#include "js/TracingAPI.h"
#include "mozilla/Attributes.h"
#include "mozilla/Move.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "nsWrapperCache.h"
namespace mozilla {
namespace dom {
/*
* Class that just handles the JSObject storage and tracing for typed arrays
*/
struct TypedArrayObjectStorage : AllTypedArraysBase {
protected:
JSObject* mTypedObj;
JSObject* mWrappedObj;
TypedArrayObjectStorage()
: mTypedObj(nullptr),
mWrappedObj(nullptr)
{
}
TypedArrayObjectStorage(TypedArrayObjectStorage&& aOther)
: mTypedObj(aOther.mTypedObj),
mWrappedObj(aOther.mWrappedObj)
{
aOther.mTypedObj = nullptr;
aOther.mWrappedObj = nullptr;
}
public:
inline void TraceSelf(JSTracer* trc)
{
JS::UnsafeTraceRoot(trc, &mTypedObj, "TypedArray.mTypedObj");
JS::UnsafeTraceRoot(trc, &mTypedObj, "TypedArray.mWrappedObj");
}
private:
TypedArrayObjectStorage(const TypedArrayObjectStorage&) = delete;
};
/*
* Various typed array classes for argument conversion. We have a base class
* that has a way of initializing a TypedArray from an existing typed array, and
* a subclass of the base class that supports creation of a relevant typed array
* or array buffer object.
*/
template<typename T,
JSObject* UnwrapArray(JSObject*),
void GetLengthAndDataAndSharedness(JSObject*, uint32_t*, bool*, T**)>
struct TypedArray_base : public TypedArrayObjectStorage {
typedef T element_type;
TypedArray_base()
: mData(nullptr),
mLength(0),
mShared(false),
mComputed(false)
{
}
TypedArray_base(TypedArray_base&& aOther)
: TypedArrayObjectStorage(Move(aOther)),
mData(aOther.mData),
mLength(aOther.mLength),
mShared(aOther.mShared),
mComputed(aOther.mComputed)
{
aOther.mData = nullptr;
aOther.mLength = 0;
aOther.mShared = false;
aOther.mComputed = false;
}
private:
mutable T* mData;
mutable uint32_t mLength;
mutable bool mShared;
mutable bool mComputed;
public:
inline bool Init(JSObject* obj)
{
MOZ_ASSERT(!inited());
mTypedObj = mWrappedObj = UnwrapArray(obj);
return inited();
}
inline bool inited() const {
return !!mTypedObj;
}
// About shared memory:
//
// Any DOM TypedArray as well as any DOM ArrayBufferView that does
// not represent a JS DataView can map the memory of either a JS
// ArrayBuffer or a JS SharedArrayBuffer. (DataView cannot view
// shared memory.) If the TypedArray maps a SharedArrayBuffer the
// Length() and Data() accessors on the DOM view will return zero
// and nullptr; to get the actual length and data, call the
// LengthAllowShared() and DataAllowShared() accessors instead.
//
// Two methods are available for determining if a DOM view maps
// shared memory. The IsShared() method is cheap and can be called
// if the view has been computed; the JS_GetTypedArraySharedness()
// method is slightly more expensive and can be called on the Obj()
// value if the view may not have been computed and if the value is
// known to represent a JS TypedArray.
//
// (Just use JS_IsSharedArrayBuffer() to test if any object is of
// that type.)
//
// Code that elects to allow views that map shared memory to be used
// -- ie, code that "opts in to shared memory" -- should generally
// not access the raw data buffer with standard C++ mechanisms as
// that creates the possibility of C++ data races, which is
// undefined behavior. The JS engine will eventually export (bug
// 1225033) a suite of methods that avoid undefined behavior.
//
// Callers of Obj() that do not opt in to shared memory can produce
// better diagnostics by checking whether the JSObject in fact maps
// shared memory and throwing an error if it does. However, it is
// safe to use the value of Obj() without such checks.
//
// The DOM TypedArray abstraction prevents the underlying buffer object
// from being accessed directly, but JS_GetArrayBufferViewBuffer(Obj())
// will obtain the buffer object. Code that calls that function must
// not assume the returned buffer is an ArrayBuffer. That is guarded
// against by an out parameter on that call that communicates the
// sharedness of the buffer.
//
// Finally, note that the buffer memory of a SharedArrayBuffer is
// not detachable.
inline bool IsShared() const {
MOZ_ASSERT(mComputed);
return mShared;
}
inline T *Data() const {
MOZ_ASSERT(mComputed);
if (mShared)
return nullptr;
return mData;
}
inline T *DataAllowShared() const {
MOZ_ASSERT(mComputed);
return mData;
}
inline uint32_t Length() const {
MOZ_ASSERT(mComputed);
if (mShared)
return 0;
return mLength;
}
inline uint32_t LengthAllowShared() const {
MOZ_ASSERT(mComputed);
return mLength;
}
inline JSObject *Obj() const {
MOZ_ASSERT(inited());
return mWrappedObj;
}
inline bool WrapIntoNewCompartment(JSContext* cx)
{
return JS_WrapObject(cx,
JS::MutableHandle<JSObject*>::fromMarkedLocation(&mWrappedObj));
}
inline void ComputeLengthAndData() const
{
MOZ_ASSERT(inited());
MOZ_ASSERT(!mComputed);
GetLengthAndDataAndSharedness(mTypedObj, &mLength, &mShared, &mData);
mComputed = true;
}
private:
TypedArray_base(const TypedArray_base&) = delete;
};
template<typename T,
JSObject* UnwrapArray(JSObject*),
T* GetData(JSObject*, bool* isShared, const JS::AutoCheckCannotGC&),
void GetLengthAndDataAndSharedness(JSObject*, uint32_t*, bool*, T**),
JSObject* CreateNew(JSContext*, uint32_t)>
struct TypedArray
: public TypedArray_base<T, UnwrapArray, GetLengthAndDataAndSharedness>
{
private:
typedef TypedArray_base<T, UnwrapArray, GetLengthAndDataAndSharedness> Base;
public:
TypedArray()
: Base()
{}
TypedArray(TypedArray&& aOther)
: Base(Move(aOther))
{
}
static inline JSObject*
Create(JSContext* cx, nsWrapperCache* creator, uint32_t length,
const T* data = nullptr) {
JS::Rooted<JSObject*> creatorWrapper(cx);
Maybe<JSAutoCompartment> ac;
if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) {
ac.emplace(cx, creatorWrapper);
}
return CreateCommon(cx, length, data);
}
static inline JSObject*
Create(JSContext* cx, uint32_t length, const T* data = nullptr) {
return CreateCommon(cx, length, data);
}
private:
static inline JSObject*
CreateCommon(JSContext* cx, uint32_t length, const T* data) {
JSObject* obj = CreateNew(cx, length);
if (!obj) {
return nullptr;
}
if (data) {
JS::AutoCheckCannotGC nogc;
bool isShared;
T* buf = static_cast<T*>(GetData(obj, &isShared, nogc));
// Data will not be shared, until a construction protocol exists
// for constructing shared data.
MOZ_ASSERT(!isShared);
memcpy(buf, data, length*sizeof(T));
}
return obj;
}
TypedArray(const TypedArray&) = delete;
};
template<JSObject* UnwrapArray(JSObject*),
void GetLengthAndDataAndSharedness(JSObject*, uint32_t*, bool*,
uint8_t**),
js::Scalar::Type GetViewType(JSObject*)>
struct ArrayBufferView_base
: public TypedArray_base<uint8_t, UnwrapArray, GetLengthAndDataAndSharedness>
{
private:
typedef TypedArray_base<uint8_t, UnwrapArray, GetLengthAndDataAndSharedness>
Base;
public:
ArrayBufferView_base()
: Base()
{
}
ArrayBufferView_base(ArrayBufferView_base&& aOther)
: Base(Move(aOther)),
mType(aOther.mType)
{
aOther.mType = js::Scalar::MaxTypedArrayViewType;
}
private:
js::Scalar::Type mType;
public:
inline bool Init(JSObject* obj)
{
if (!Base::Init(obj)) {
return false;
}
mType = GetViewType(this->Obj());
return true;
}
inline js::Scalar::Type Type() const
{
MOZ_ASSERT(this->inited());
return mType;
}
};
typedef TypedArray<int8_t, js::UnwrapInt8Array, JS_GetInt8ArrayData,
js::GetInt8ArrayLengthAndData, JS_NewInt8Array>
Int8Array;
typedef TypedArray<uint8_t, js::UnwrapUint8Array, JS_GetUint8ArrayData,
js::GetUint8ArrayLengthAndData, JS_NewUint8Array>
Uint8Array;
typedef TypedArray<uint8_t, js::UnwrapUint8ClampedArray, JS_GetUint8ClampedArrayData,
js::GetUint8ClampedArrayLengthAndData, JS_NewUint8ClampedArray>
Uint8ClampedArray;
typedef TypedArray<int16_t, js::UnwrapInt16Array, JS_GetInt16ArrayData,
js::GetInt16ArrayLengthAndData, JS_NewInt16Array>
Int16Array;
typedef TypedArray<uint16_t, js::UnwrapUint16Array, JS_GetUint16ArrayData,
js::GetUint16ArrayLengthAndData, JS_NewUint16Array>
Uint16Array;
typedef TypedArray<int32_t, js::UnwrapInt32Array, JS_GetInt32ArrayData,
js::GetInt32ArrayLengthAndData, JS_NewInt32Array>
Int32Array;
typedef TypedArray<uint32_t, js::UnwrapUint32Array, JS_GetUint32ArrayData,
js::GetUint32ArrayLengthAndData, JS_NewUint32Array>
Uint32Array;
typedef TypedArray<float, js::UnwrapFloat32Array, JS_GetFloat32ArrayData,
js::GetFloat32ArrayLengthAndData, JS_NewFloat32Array>
Float32Array;
typedef TypedArray<double, js::UnwrapFloat64Array, JS_GetFloat64ArrayData,
js::GetFloat64ArrayLengthAndData, JS_NewFloat64Array>
Float64Array;
typedef ArrayBufferView_base<js::UnwrapArrayBufferView,
js::GetArrayBufferViewLengthAndData,
JS_GetArrayBufferViewType>
ArrayBufferView;
typedef TypedArray<uint8_t, js::UnwrapArrayBuffer, JS_GetArrayBufferData,
js::GetArrayBufferLengthAndData, JS_NewArrayBuffer>
ArrayBuffer;
typedef TypedArray<uint8_t, js::UnwrapSharedArrayBuffer, JS_GetSharedArrayBufferData,
js::GetSharedArrayBufferLengthAndData, JS_NewSharedArrayBuffer>
SharedArrayBuffer;
// A class for converting an nsTArray to a TypedArray
// Note: A TypedArrayCreator must not outlive the nsTArray it was created from.
// So this is best used to pass from things that understand nsTArray to
// things that understand TypedArray, as with Promise::ArgumentToJSValue.
template<typename TypedArrayType>
class TypedArrayCreator
{
typedef nsTArray<typename TypedArrayType::element_type> ArrayType;
public:
explicit TypedArrayCreator(const ArrayType& aArray)
: mArray(aArray)
{}
JSObject* Create(JSContext* aCx) const
{
return TypedArrayType::Create(aCx, mArray.Length(), mArray.Elements());
}
private:
const ArrayType& mArray;
};
// A class for rooting an existing TypedArray struct
template<typename ArrayType>
class MOZ_RAII TypedArrayRooter : private JS::CustomAutoRooter
{
public:
TypedArrayRooter(JSContext* cx,
ArrayType* aArray MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
mArray(aArray)
{
}
virtual void trace(JSTracer* trc) override
{
mArray->TraceSelf(trc);
}
private:
TypedArrayObjectStorage* const mArray;
};
// And a specialization for dealing with nullable typed arrays
template<typename Inner> struct Nullable;
template<typename ArrayType>
class MOZ_RAII TypedArrayRooter<Nullable<ArrayType> > :
private JS::CustomAutoRooter
{
public:
TypedArrayRooter(JSContext* cx,
Nullable<ArrayType>* aArray MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
mArray(aArray)
{
}
virtual void trace(JSTracer* trc) override
{
if (!mArray->IsNull()) {
mArray->Value().TraceSelf(trc);
}
}
private:
Nullable<ArrayType>* const mArray;
};
// Class for easily setting up a rooted typed array object on the stack
template<typename ArrayType>
class MOZ_RAII RootedTypedArray final : public ArrayType,
private TypedArrayRooter<ArrayType>
{
public:
explicit RootedTypedArray(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
ArrayType(),
TypedArrayRooter<ArrayType>(cx, this
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
{
}
RootedTypedArray(JSContext* cx, JSObject* obj MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
ArrayType(obj),
TypedArrayRooter<ArrayType>(cx, this
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
{
}
};
} // namespace dom
} // namespace mozilla
#endif /* mozilla_dom_TypedArray_h */