diff options
Diffstat (limited to 'dom/bindings/TypedArray.h')
-rw-r--r-- | dom/bindings/TypedArray.h | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/dom/bindings/TypedArray.h b/dom/bindings/TypedArray.h new file mode 100644 index 0000000000..b419575b87 --- /dev/null +++ b/dom/bindings/TypedArray.h @@ -0,0 +1,290 @@ +/* -*- 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 <utility> + +#include "js/ArrayBuffer.h" +#include "js/ArrayBufferMaybeShared.h" +#include "js/experimental/TypedData.h" // js::Unwrap(Ui|I)nt(8|16|32)Array, js::Get(Ui|I)nt(8|16|32)ArrayLengthAndData, js::UnwrapUint8ClampedArray, js::GetUint8ClampedArrayLengthAndData, js::UnwrapFloat(32|64)Array, js::GetFloat(32|64)ArrayLengthAndData, JS_GetArrayBufferViewType +#include "js/GCAPI.h" // JS::AutoCheckCannotGC +#include "js/RootingAPI.h" // JS::Rooted +#include "js/ScalarType.h" // JS::Scalar::Type +#include "js/SharedArrayBuffer.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/SpiderMonkeyInterface.h" +#include "nsWrapperCache.h" +#include "nsWrapperCacheInlines.h" + +namespace mozilla::dom { + +/* + * 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 <class ArrayT> +struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage, + AllTypedArraysBase { + using element_type = typename ArrayT::DataType; + + TypedArray_base() + : mData(nullptr), mLength(0), mShared(false), mComputed(false) {} + + TypedArray_base(TypedArray_base&& aOther) + : SpiderMonkeyInterfaceObjectStorage(std::move(aOther)), + mData(aOther.mData), + mLength(aOther.mLength), + mShared(aOther.mShared), + mComputed(aOther.mComputed) { + aOther.Reset(); + } + + private: + mutable element_type* mData; + mutable uint32_t mLength; + mutable bool mShared; + mutable bool mComputed; + + public: + inline bool Init(JSObject* obj) { + MOZ_ASSERT(!inited()); + mImplObj = mWrappedObj = ArrayT::unwrap(obj).asObject(); + return inited(); + } + + // About shared memory: + // + // Any DOM TypedArray as well as any DOM ArrayBufferView can map the + // memory of either a JS ArrayBuffer or a JS SharedArrayBuffer. 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 element_type* Data() const { + MOZ_ASSERT(mComputed); + return mData; + } + + // Return a pointer to data that will not move during a GC. + // + // For some smaller views, this will copy the data into the provided buffer + // and return that buffer as the pointer. Otherwise, this will return a + // direct pointer to the actual data with no copying. If the provided buffer + // is not large enough, nullptr will be returned. If bufSize is at least + // JS_MaxMovableTypedArraySize(), the data is guaranteed to fit. + inline element_type* FixedData(uint8_t* buffer, size_t bufSize) const { + MOZ_ASSERT(mComputed); + return JS_GetArrayBufferViewFixedData(mImplObj, buffer, bufSize); + } + + inline uint32_t Length() const { + MOZ_ASSERT(mComputed); + return mLength; + } + + inline void ComputeState() const { + MOZ_ASSERT(inited()); + MOZ_ASSERT(!mComputed); + size_t length; + JS::AutoCheckCannotGC nogc; + mData = + ArrayT::fromObject(mImplObj).getLengthAndData(&length, &mShared, nogc); + MOZ_RELEASE_ASSERT(length <= INT32_MAX, + "Bindings must have checked ArrayBuffer{View} length"); + mLength = length; + mComputed = true; + } + + inline void Reset() { + // This method mostly exists to inform the GC rooting hazard analysis that + // the variable can be considered dead, at least until you do anything else + // with it. + mData = nullptr; + mLength = 0; + mShared = false; + mComputed = false; + } + + private: + TypedArray_base(const TypedArray_base&) = delete; +}; + +template <class ArrayT> +struct TypedArray : public TypedArray_base<ArrayT> { + using Base = TypedArray_base<ArrayT>; + using element_type = typename Base::element_type; + + TypedArray() = default; + + TypedArray(TypedArray&& aOther) = default; + + static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator, + uint32_t length, + const element_type* data = nullptr) { + JS::Rooted<JSObject*> creatorWrapper(cx); + Maybe<JSAutoRealm> ar; + if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) { + ar.emplace(cx, creatorWrapper); + } + + return CreateCommon(cx, length, data); + } + + static inline JSObject* Create(JSContext* cx, uint32_t length, + const element_type* data = nullptr) { + return CreateCommon(cx, length, data); + } + + static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator, + Span<const element_type> data) { + // Span<> uses size_t as a length, and we use uint32_t instead. + if (MOZ_UNLIKELY(data.Length() > UINT32_MAX)) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + return Create(cx, creator, data.Length(), data.Elements()); + } + + static inline JSObject* Create(JSContext* cx, Span<const element_type> data) { + // Span<> uses size_t as a length, and we use uint32_t instead. + if (MOZ_UNLIKELY(data.Length() > UINT32_MAX)) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + return CreateCommon(cx, data.Length(), data.Elements()); + } + + private: + static inline JSObject* CreateCommon(JSContext* cx, uint32_t length, + const element_type* data) { + auto array = ArrayT::create(cx, length); + if (!array) { + return nullptr; + } + if (data) { + JS::AutoCheckCannotGC nogc; + bool isShared; + element_type* buf = array.getData(&isShared, nogc); + // Data will not be shared, until a construction protocol exists + // for constructing shared data. + MOZ_ASSERT(!isShared); + memcpy(buf, data, length * sizeof(element_type)); + } + return array.asObject(); + } + + TypedArray(const TypedArray&) = delete; +}; + +template <JS::Scalar::Type GetViewType(JSObject*)> +struct ArrayBufferView_base : public TypedArray_base<JS::ArrayBufferView> { + private: + using Base = TypedArray_base<JS::ArrayBufferView>; + + public: + ArrayBufferView_base() : Base(), mType(JS::Scalar::MaxTypedArrayViewType) {} + + ArrayBufferView_base(ArrayBufferView_base&& aOther) + : Base(std::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; + } +}; + +using Int8Array = TypedArray<JS::Int8Array>; +using Uint8Array = TypedArray<JS::Uint8Array>; +using Uint8ClampedArray = TypedArray<JS::Uint8ClampedArray>; +using Int16Array = TypedArray<JS::Int16Array>; +using Uint16Array = TypedArray<JS::Uint16Array>; +using Int32Array = TypedArray<JS::Int32Array>; +using Uint32Array = TypedArray<JS::Uint32Array>; +using Float32Array = TypedArray<JS::Float32Array>; +using Float64Array = TypedArray<JS::Float64Array>; +using ArrayBufferView = ArrayBufferView_base<JS_GetArrayBufferViewType>; +using ArrayBuffer = TypedArray<JS::ArrayBuffer>; + +// 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 ToJSValue. +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; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_TypedArray_h */ |