/* -*- 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 #include #include #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 "js/friend/ErrorMessages.h" #include "mozilla/Attributes.h" #include "mozilla/Buffer.h" #include "mozilla/ErrorResult.h" #include "mozilla/Result.h" #include "mozilla/Vector.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/SpiderMonkeyInterface.h" #include "nsIGlobalObject.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. * * Accessing the data of a TypedArray is tricky. The underlying storage is in a * JS object, which is subject to the JS GC. The memory for the array can be * either inline in the JSObject or stored separately. If the data is stored * inline then the exact location in memory of the data buffer can change as a * result of a JS GC (which is a moving GC). Running JS code can also mutate the * data (including the length). For all these reasons one has to be careful when * holding a pointer to the data or keeping a local copy of the length value. On * the other hand, most code that takes a TypedArray has to access its data at * some point, to process it in some way. The TypedArray class tries to supply a * number of helper APIs, so that the most common cases of processing the data * can be done safely, without having to check the caller very closely for * potential security issues. The main classes of processing TypedArray data * are: * * 1) Appending a copy of the data (or of a subset of the data) to a different * data structure * 2) Copying the data (or a subset of the data) into a different data * structure * 3) Creating a new data structure with a copy of the data (or of a subset of * the data) * 4) Processing the data in some other way * * The APIs for the first 3 classes all return a boolean and take an optional * argument named aCalculator. aCalculator should be a lambda taking a size_t * argument which will be passed the total length of the data in the typed * array. aCalculator is allowed to return a std::pair or a * Maybe>. The return value should contain the offset * and the length of the subset of the data that should be appended, copied or * used for creating a new datastructure. If the calculation can fail then * aCalculator should return a Maybe>, with Nothing() * signaling that the operation should be aborted. * The return value of these APIs will be false if appending, copying or * creating a structure with the new data failed, or if the optional aCalculator * lambda returned Nothing(). * * Here are the different APIs: * * 1) Appending to a different data structure * * There are AppendDataTo helpers for nsCString, nsTArray, * FallibleTArray and Vector. The signatures are: * * template * [[nodiscard]] bool AppendDataTo(nsCString& aResult, Calculator&&... * aCalculator) const; * * template * [[nodiscard]] bool AppendDataTo(nsTArray& aResult, Calculator&&... * aCalculator) const; * * template * [[nodiscard]] bool AppendDataTo(FallibleTArray& aResult, * Calculator&&... aCalculator) const; * * template * [[nodiscard]] bool AppendDataTo(Vector& aResult, Calculator&&... * aCalculator) const; * * The data (or the calculated subset) will be appended to aResult by using * the appropriate fallible API. If the append fails then AppendDataTo will * return false. The aCalculator optional argument is described above. * * Examples: * * Vector array; * if (!aUint32Array.AppendDataTo(array)) { * aError.ThrowTypeError("Failed to copy data from typed array"); * return; * } * * size_t offset, length; * … // Getting offset and length values from somewhere. * FallibleTArray array; * if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) { * size_t dataLength = std::min(aLength - offset, length); * return std::make_pair(offset, dataLength); * }) { * aError.ThrowTypeError("Failed to copy data from typed array"); * return; * } * * size_t offset, length; * … // Getting offset and length values from somewhere. * FallibleTArray array; * if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) { * if (aLength < offset + length) { * return Maybe>(); * } * size_t dataLength = std::min(aLength - offset, length); * return Some(std::make_pair(offset, dataLength)); * })) { * aError.ThrowTypeError("Failed to copy data from typed array"); * return; * } * * * 2) Copying into a different data structure * * There is a CopyDataTo helper for a fixed-size buffer. The signature is: * * template * [[nodiscard]] bool CopyDataTo(T (&aResult)[N], * Calculator&&... aCalculator) const; * * The data (or the calculated subset) will be copied to aResult, starting * at aResult[0]. If the length of the data to be copied is bigger than the * size of the fixed-size buffer (N) then nothing will be copied and * CopyDataTo will return false. The aCalculator optional argument is * described above. * * Examples: * * float data[3]; * if (!aFloat32Array.CopyDataTo(data)) { * aError.ThrowTypeError("Typed array doesn't contain the right amount" * "of data"); * return; * } * * size_t offset; * … // Getting offset value from somewhere. * uint32_t data[3]; * if (!aUint32Array.CopyDataTo(data, [&](const size_t& aLength) { * if (aLength - offset != ArrayLength(data)) { * aError.ThrowTypeError("Typed array doesn't contain the right" * " amount of data"); * return Maybe>(); * } * return Some(std::make_pair(offset, ArrayLength(data))); * }) { * return; * } * * 3) Creating a new data structure with a copy of the data (or a subset of the * data) * * There are CreateFromData helper for creating a Vector, a * UniquePtr and a Buffer. * * template * [[nodiscard]] Maybe> * CreateFromData>( * Calculator&&... aCalculator) const; * * template * [[nodiscard]] Maybe> * CreateFromData>( * Calculator&&... aCalculator) const; * * template * [[nodiscard]] Maybe> * CreateFromData>( * Calculator&&... aCalculator) const; * * A new container will be created, and the data (or the calculated subset) * will be copied to it. The container will be returned inside a Maybe<…>. * If creating the container with the right size fails then Nothing() will * be returned. The aCalculator optional argument is described above. * * Examples: * * Maybe> buffer = * aUint8Array.CreateFromData>(); * if (buffer.isNothing()) { * aError.ThrowTypeError("Failed to create a buffer"); * return; * } * * size_t offset, length; * … // Getting offset and length values from somewhere. * Maybe> buffer = * aUint8Array.CreateFromData>([&]( * const size_t& aLength) { * if (aLength - offset != ArrayLength(data)) { * aError.ThrowTypeError( * "Typed array doesn't contain the right amount" of data"); * return Maybe>(); * } * return Some(std::make_pair(offset, ArrayLength(data))); * }); * if (buffer.isNothing()) { * return; * } * * 4) Processing the data in some other way * * This is the API for when none of the APIs above are appropriate for your * usecase. As these are the most dangerous APIs you really should check * first if you can't use one of the safer alternatives above. The reason * these APIs are more dangerous is because they give access to the typed * array's data directly, and the location of that data can be changed by * the JS GC (due to generational and/or compacting collection). There are * two APIs for processing data: * * template * [[nodiscard]] ProcessReturnType ProcessData( * Processor&& aProcessor) const; * * template * [[nodiscard]] ProcessReturnType ProcessFixedData( * Processor&& aProcessor) const; * * ProcessData will call the lambda with as arguments a |const Span<…>&| * wrapping the data pointer and length for the data in the typed array, and * a |JS::AutoCheckCannotGC&&|. The lambda will execute in a context where * GC is not allowed. * * ProcessFixedData will call the lambda with as argument |const Span<…>&|. * For small typed arrays where the data is stored inline in the typed * array, and thus could move during GC, then the data will be copied into a * fresh out-of-line allocation before the lambda is called. * * The signature of the lambdas for ProcessData and ProcessFixedData differ * in that the ProcessData lambda will additionally be passed a nogc token * to prevent GC from occurring and invalidating the data. If the processing * you need to do in the lambda can't be proven to not GC then you should * probably use ProcessFixedData instead. There are cases where you need to * do something that can cause a GC but you don't actually need to access * the data anymore. A good example would be throwing a JS exception and * return. For those very specific cases you can call nogc.reset() before * doing anything that causes a GC. Be extra careful to not access the data * after you called nogc.reset(). * * Extra care must be taken to not let the Span<…> or any pointers derived * from it escape the lambda, as the position in memory of the typed array's * data can change again once we leave the lambda (invalidating the * pointers). The lambda passed to ProcessData is not allowed to do anything * that will trigger a GC, and the GC rooting hazard analysis will enforce * that. * * Both ProcessData and ProcessFixedData will pin the typed array's length * while calling the lambda, to block any changes to the length of the data. * Note that this means that the lambda itself isn't allowed to change the * length of the typed array's data. Any attempt to change the length will * throw a JS exception. * * The return type of ProcessData and ProcessFixedData depends on the return * type of the lambda, as they forward the return value from the lambda to * the caller of ProcessData or ProcessFixedData. * * Examples: * * aUint32Array.ProcessData([] (const Span& aData, * JS::AutoCheckCannotGC&&) { * for (size_t i = 0; i < aData.Length(); ++i) { * aData[i] = i; * } * }); * * aUint32Array.ProcessData([&] (const Span& aData, * JS::AutoCheckCannotGC&& nogc) { * for (size_t i = 0; i < aData.Length(); ++i) { * if (!aData[i]) { * nogc.reset(); * ThrowJSException("Data shouldn't contain 0"); * return; * }; * DoSomething(aData[i]); * } * }); * * uint8_t max = aUint8Array.ProcessData([] (const Span& aData) { * return std::max_element(aData.cbegin(), aData.cend()); * }); * * aUint8Array.ProcessFixedData([] (const Span& aData) { * return CallFunctionThatMightGC(aData); * }); * * * In addition to the above APIs we provide helpers to call them on the typed * array members of WebIDL unions. We have helpers for the 4 different sets of * APIs above. The return value of the helpers depends on whether the union can * contain a type other than a typed array. If the union can't contain a type * other than a typed array then the return type is simply the type returned by * the corresponding API above. If the union can contain a type other than a * typed array then the return type of the helper is a Maybe<…> wrapping the * actual return type, with Nothing() signifying that the union contained a * non-typed array value. * * template * [[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion, * ToType& aResult); * * template * [[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion); * * template * [[nodiscard]] auto ProcessTypedArrays( * const T& aUnion, Processor&& aProcessor); * * template * [[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion, * Processor&& aProcessor); * */ template struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage, AllTypedArraysBase { using element_type = typename ArrayT::DataType; TypedArray_base() = default; TypedArray_base(TypedArray_base&& aOther) = default; 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. // // 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. public: /** * Helper functions to append a copy of this typed array's data to a * container. Returns false if the allocation for copying the data fails. * * aCalculator is an optional argument to which one can pass a lambda * expression that will calculate the offset and length of the data to copy * out of the typed array. aCalculator will be called with one argument of * type size_t set to the length of the data in the typed array. It is allowed * to return a std::pair or a Maybe> * containing the offset and the length of the subset of the data that we * should copy. If the calculation can fail then aCalculator should return a * Maybe>, if .isNothing() returns true for the * return value then AppendDataTo will return false and the data won't be * copied. */ template [[nodiscard]] bool AppendDataTo(nsCString& aResult, Calculator&&... aCalculator) const { static_assert(sizeof...(aCalculator) <= 1, "AppendDataTo takes at most one aCalculator"); return ProcessDataHelper( [&](const Span& aData, JS::AutoCheckCannotGC&&) { return aResult.Append(aData, fallible); }, std::forward(aCalculator)...); } template [[nodiscard]] bool AppendDataTo(nsTArray& aResult, Calculator&&... aCalculator) const { static_assert(sizeof...(aCalculator) <= 1, "AppendDataTo takes at most one aCalculator"); return ProcessDataHelper( [&](const Span& aData, JS::AutoCheckCannotGC&&) { return aResult.AppendElements(aData, fallible); }, std::forward(aCalculator)...); } template [[nodiscard]] bool AppendDataTo(FallibleTArray& aResult, Calculator&&... aCalculator) const { static_assert(sizeof...(aCalculator) <= 1, "AppendDataTo takes at most one aCalculator"); return ProcessDataHelper( [&](const Span& aData, JS::AutoCheckCannotGC&&) { return aResult.AppendElements(aData, fallible); }, std::forward(aCalculator)...); } template [[nodiscard]] bool AppendDataTo(Vector& aResult, Calculator&&... aCalculator) const { static_assert(sizeof...(aCalculator) <= 1, "AppendDataTo takes at most one aCalculator"); return ProcessDataHelper( [&](const Span& aData, JS::AutoCheckCannotGC&&) { return aResult.append(aData.Elements(), aData.Length()); }, std::forward(aCalculator)...); } /** * Helper functions to copy this typed array's data to a container. This will * clear any existing data in the container. * * See the comments for AppendDataTo for information on the aCalculator * argument. */ template [[nodiscard]] bool CopyDataTo(T (&aResult)[N], Calculator&&... aCalculator) const { static_assert(sizeof...(aCalculator) <= 1, "CopyDataTo takes at most one aCalculator"); return ProcessDataHelper( [&](const Span& aData, JS::AutoCheckCannotGC&&) { if (aData.Length() != N) { return false; } for (size_t i = 0; i < N; ++i) { aResult[i] = aData[i]; } return true; }, std::forward(aCalculator)...); } /** * Helper functions to copy this typed array's data to a newly created * container. Returns Nothing() if creating the container with the right size * fails. * * See the comments for AppendDataTo for information on the aCalculator * argument. */ template , T>>> [[nodiscard]] Maybe> CreateFromData( Calculator&&... aCalculator) const { static_assert(sizeof...(aCalculator) <= 1, "CreateFromData takes at most one aCalculator"); return CreateFromDataHelper( [&](const Span& aData, Vector& aResult) { if (!aResult.initCapacity(aData.Length())) { return false; } aResult.infallibleAppend(aData.Elements(), aData.Length()); return true; }, std::forward(aCalculator)...); } template >>> [[nodiscard]] Maybe> CreateFromData( Calculator&&... aCalculator) const { static_assert(sizeof...(aCalculator) <= 1, "CreateFromData takes at most one aCalculator"); return CreateFromDataHelper( [&](const Span& aData, UniquePtr& aResult) { aResult = MakeUniqueForOverwriteFallible(aData.Length()); if (!aResult.get()) { return false; } memcpy(aResult.get(), aData.Elements(), aData.LengthBytes()); return true; }, std::forward(aCalculator)...); } template >>> [[nodiscard]] Maybe> CreateFromData( Calculator&&... aCalculator) const { static_assert(sizeof...(aCalculator) <= 1, "CreateFromData takes at most one aCalculator"); return CreateFromDataHelper( [&](const Span& aData, Buffer& aResult) { Maybe> buffer = Buffer::CopyFrom(aData); if (buffer.isNothing()) { return false; } aResult = buffer.extract(); return true; }, std::forward(aCalculator)...); } private: template ()( std::declval>(), std::declval()))> using ProcessNoGCReturnType = R; template [[nodiscard]] static inline ProcessNoGCReturnType CallProcessorNoGC(const Span& aData, Processor&& aProcessor, JS::AutoCheckCannotGC&& nogc) { MOZ_ASSERT( aData.IsEmpty() || aData.Elements(), "We expect a non-null data pointer for typed arrays that aren't empty"); return aProcessor(aData, std::move(nogc)); } template ()( std::declval>()))> using ProcessReturnType = R; template [[nodiscard]] static inline ProcessReturnType CallProcessor( const Span& aData, Processor&& aProcessor) { MOZ_ASSERT( aData.IsEmpty() || aData.Elements(), "We expect a non-null data pointer for typed arrays that aren't empty"); return aProcessor(aData); } struct MOZ_STACK_CLASS LengthPinner { explicit LengthPinner(const TypedArray_base* aTypedArray) : mTypedArray(aTypedArray), mWasPinned( !JS::PinArrayBufferOrViewLength(aTypedArray->Obj(), true)) {} ~LengthPinner() { if (!mWasPinned) { JS::PinArrayBufferOrViewLength(mTypedArray->Obj(), false); } } private: const TypedArray_base* mTypedArray; bool mWasPinned; }; template [[nodiscard]] bool ProcessDataHelper( Processor&& aProcessor, Calculator&& aCalculateOffsetAndLength) const { LengthPinner pinner(this); JS::AutoCheckCannotGC nogc; // `data` is GC-sensitive. Span data = GetCurrentData(); const auto& offsetAndLength = aCalculateOffsetAndLength(data.Length()); size_t offset, length; if constexpr (std::is_convertible_v>) { std::tie(offset, length) = offsetAndLength; } else { if (offsetAndLength.isNothing()) { return false; } std::tie(offset, length) = offsetAndLength.value(); } return CallProcessorNoGC(data.Subspan(offset, length), std::forward(aProcessor), std::move(nogc)); } template [[nodiscard]] ProcessNoGCReturnType ProcessDataHelper( Processor&& aProcessor) const { LengthPinner pinner(this); // The data from GetCurrentData() is GC sensitive. JS::AutoCheckCannotGC nogc; return CallProcessorNoGC( GetCurrentData(), std::forward(aProcessor), std::move(nogc)); } public: template [[nodiscard]] ProcessNoGCReturnType ProcessData( Processor&& aProcessor) const { return ProcessDataHelper(std::forward(aProcessor)); } template [[nodiscard]] ProcessReturnType ProcessFixedData( Processor&& aProcessor) const { mozilla::dom::AutoJSAPI jsapi; if (!jsapi.Init(mImplObj)) { #if defined(EARLY_BETA_OR_EARLIER) if constexpr (std::is_same_v) { if (!mImplObj) { MOZ_CRASH("Null mImplObj"); } if (!xpc::NativeGlobal(mImplObj)) { MOZ_CRASH("Null xpc::NativeGlobal(mImplObj)"); } if (!xpc::NativeGlobal(mImplObj)->GetGlobalJSObject()) { MOZ_CRASH("Null xpc::NativeGlobal(mImplObj)->GetGlobalJSObject()"); } } #endif MOZ_CRASH("Failed to get JSContext"); } #if defined(EARLY_BETA_OR_EARLIER) if constexpr (std::is_same_v) { JS::Rooted view(jsapi.cx(), js::UnwrapArrayBufferView(mImplObj)); if (!view) { if (JSObject* unwrapped = js::CheckedUnwrapStatic(mImplObj)) { if (!js::UnwrapArrayBufferView(unwrapped)) { MOZ_CRASH( "Null " "js::UnwrapArrayBufferView(js::CheckedUnwrapStatic(mImplObj))"); } view = unwrapped; } else { MOZ_CRASH("Null js::CheckedUnwrapStatic(mImplObj)"); } } if (!JS::IsArrayBufferViewShared(view)) { JSAutoRealm ar(jsapi.cx(), view); bool unused; bool noBuffer; { JSObject* buffer = JS_GetArrayBufferViewBuffer(jsapi.cx(), view, &unused); noBuffer = !buffer; } if (noBuffer) { if (JS_IsTypedArrayObject(view)) { JS::Value bufferSlot = JS::GetReservedSlot(view, /* BUFFER_SLOT */ 0); if (bufferSlot.isNull()) { MOZ_CRASH("TypedArrayObject with bufferSlot containing null"); } else if (bufferSlot.isBoolean()) { // If we're here then TypedArrayObject::ensureHasBuffer must have // failed in the call to JS_GetArrayBufferViewBuffer. if (JS_IsThrowingOutOfMemory(jsapi.cx())) { size_t length = JS_GetTypedArrayByteLength(view); if (!JS::GetReservedSlot(view, /* DATA_SLOT */ 3) .isUndefined() && length <= JS_MaxMovableTypedArraySize()) { MOZ_CRASH( "We did run out of memory, maybe trying to uninline the " "buffer"); } if (length < INT32_MAX) { MOZ_CRASH( "We did run out of memory trying to create a buffer " "smaller than 2GB - 1"); } else if (length < UINT32_MAX) { MOZ_CRASH( "We did run out of memory trying to create a between 2GB " "and 4GB - 1"); } else { MOZ_CRASH( "We did run out of memory trying to create a buffer " "bigger than 4GB - 1"); } } else if (JS_IsExceptionPending(jsapi.cx())) { JS::Rooted exn(jsapi.cx()); if (JS_GetPendingException(jsapi.cx(), &exn) && exn.isObject()) { JS::Rooted exnObj(jsapi.cx(), &exn.toObject()); JSErrorReport* err = JS_ErrorFromException(jsapi.cx(), exnObj); if (err && err->errorNumber == JSMSG_BAD_ARRAY_LENGTH) { MOZ_CRASH("Length was too big"); } } } // Did ArrayBufferObject::createBufferAndData fail without OOM? MOZ_CRASH("TypedArrayObject with bufferSlot containing boolean"); } else if (bufferSlot.isObject()) { if (!bufferSlot.toObjectOrNull()) { MOZ_CRASH( "TypedArrayObject with bufferSlot containing null object"); } else { MOZ_CRASH( "JS_GetArrayBufferViewBuffer failed but bufferSlot " "contains a non-null object"); } } else { MOZ_CRASH( "TypedArrayObject with bufferSlot containing weird value"); } } else { MOZ_CRASH("JS_GetArrayBufferViewBuffer failed for DataViewObject"); } } } } #endif if (!JS::EnsureNonInlineArrayBufferOrView(jsapi.cx(), mImplObj)) { MOZ_CRASH("small oom when moving inline data out-of-line"); } LengthPinner pinner(this); return CallProcessor(GetCurrentData(), std::forward(aProcessor)); } private: Span GetCurrentData() const { MOZ_ASSERT(inited()); MOZ_RELEASE_ASSERT( !ArrayT::fromObject(mImplObj).isResizable(), "Bindings must have checked ArrayBuffer{View} is non-resizable"); // Intentionally return a pointer and length that escape from a nogc region. // Private so it can only be used in very limited situations. JS::AutoCheckCannotGC nogc; bool shared; Span span = ArrayT::fromObject(mImplObj).getData(&shared, nogc); MOZ_RELEASE_ASSERT(span.Length() <= INT32_MAX, "Bindings must have checked ArrayBuffer{View} length"); return span; } template [[nodiscard]] Maybe CreateFromDataHelper( F&& aCreator, Calculator&&... aCalculator) const { Maybe result; bool ok = ProcessDataHelper( [&](const Span& aData, JS::AutoCheckCannotGC&&) { result.emplace(); return aCreator(aData, *result); }, std::forward(aCalculator)...); if (!ok) { return Nothing(); } return result; } TypedArray_base(const TypedArray_base&) = delete; }; template struct TypedArray : public TypedArray_base { using Base = TypedArray_base; using element_type = typename Base::element_type; TypedArray() = default; TypedArray(TypedArray&& aOther) = default; static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator, size_t length, ErrorResult& error) { return CreateCommon(cx, creator, length, error).asObject(); } static inline JSObject* Create(JSContext* cx, size_t length, ErrorResult& error) { return CreateCommon(cx, length, error).asObject(); } static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator, Span data, ErrorResult& error) { ArrayT array = CreateCommon(cx, creator, data.Length(), error); if (!error.Failed() && !data.IsEmpty()) { CopyFrom(cx, data, array); } return array.asObject(); } static inline JSObject* Create(JSContext* cx, Span data, ErrorResult& error) { ArrayT array = CreateCommon(cx, data.Length(), error); if (!error.Failed() && !data.IsEmpty()) { CopyFrom(cx, data, array); } return array.asObject(); } private: template friend class TypedArrayCreator; static inline ArrayT CreateCommon(JSContext* cx, nsWrapperCache* creator, size_t length, ErrorResult& error) { JS::Rooted creatorWrapper(cx); Maybe ar; if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) { ar.emplace(cx, creatorWrapper); } return CreateCommon(cx, length, error); } static inline ArrayT CreateCommon(JSContext* cx, size_t length, ErrorResult& error) { ArrayT array = CreateCommon(cx, length); if (array) { return array; } error.StealExceptionFromJSContext(cx); return ArrayT::fromObject(nullptr); } // NOTE: this leaves any exceptions on the JSContext, and the caller is // required to deal with them. static inline ArrayT CreateCommon(JSContext* cx, size_t length) { return ArrayT::create(cx, length); } static inline void CopyFrom(JSContext* cx, const Span& data, ArrayT& dest) { JS::AutoCheckCannotGC nogc; bool isShared; mozilla::Span span = dest.getData(&isShared, nogc); MOZ_ASSERT(span.size() == data.size(), "Didn't create a large enough typed array object?"); // Data will not be shared, until a construction protocol exists // for constructing shared data. MOZ_ASSERT(!isShared); memcpy(span.Elements(), data.Elements(), data.LengthBytes()); } TypedArray(const TypedArray&) = delete; }; template struct ArrayBufferView_base : public TypedArray_base { private: using Base = TypedArray_base; 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; using Uint8Array = TypedArray; using Uint8ClampedArray = TypedArray; using Int16Array = TypedArray; using Uint16Array = TypedArray; using Int32Array = TypedArray; using Uint32Array = TypedArray; using Float32Array = TypedArray; using Float64Array = TypedArray; using ArrayBufferView = ArrayBufferView_base; using ArrayBuffer = TypedArray; // 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 class MOZ_STACK_CLASS TypedArrayCreator { typedef nsTArray ArrayType; public: explicit TypedArrayCreator(const ArrayType& aArray) : mArray(aArray) {} // NOTE: this leaves any exceptions on the JSContext, and the caller is // required to deal with them. JSObject* Create(JSContext* aCx) const { auto array = TypedArrayType::CreateCommon(aCx, mArray.Length()); if (array) { TypedArrayType::CopyFrom(aCx, mArray, array); } return array.asObject(); } private: const ArrayType& mArray; }; namespace binding_detail { template struct ApplyToTypedArray; #define APPLY_IMPL(type) \ template \ struct ApplyToTypedArray { \ /* Return type of calling the lambda with a TypedArray 'type'. */ \ template \ using FunReturnType = decltype(std::declval()(std::declval())); \ \ /* Whether the return type of calling the lambda with a TypedArray */ \ /* 'type' is void. */ \ template \ static constexpr bool FunReturnsVoid = \ std::is_same_v, void>; \ \ /* The return type of calling Apply with a union that has 'type' as */ \ /* one of its union member types depends on the return type of */ \ /* calling the lambda. This return type will be bool if the lambda */ \ /* returns void, or it will be a Maybe<…> with the inner type being */ \ /* the actual return type of calling the lambda. If the union */ \ /* contains a value of the right type, then calling Apply will return */ \ /* either 'true', or 'Some(…)' containing the return value of calling */ \ /* the lambda. If the union does not contain a value of the right */ \ /* type, then calling Apply will return either 'false', or */ \ /* 'Nothing()'. */ \ template \ using ApplyReturnType = \ std::conditional_t, bool, Maybe>>; \ \ public: \ template \ static ApplyReturnType Apply(const Union& aUnion, F&& aFun) { \ if (!aUnion.Is##type()) { \ return ApplyReturnType(); /* false or Nothing() */ \ } \ if constexpr (FunReturnsVoid) { \ std::forward(aFun)(aUnion.GetAs##type()); \ return true; \ } else { \ return Some(std::forward(aFun)(aUnion.GetAs##type())); \ } \ } \ }; APPLY_IMPL(Int8Array) APPLY_IMPL(Uint8Array) APPLY_IMPL(Uint8ClampedArray) APPLY_IMPL(Int16Array) APPLY_IMPL(Uint16Array) APPLY_IMPL(Int32Array) APPLY_IMPL(Uint32Array) APPLY_IMPL(Float32Array) APPLY_IMPL(Float64Array) APPLY_IMPL(ArrayBufferView) APPLY_IMPL(ArrayBuffer) #undef APPLY_IMPL // The binding code generate creates an alias of this type for every WebIDL // union that contains a typed array type, with the right value for H (which // will be true if there are non-typedarray types in the union). template struct ApplyToTypedArraysHelper { static constexpr bool HasNonTypedArrayMembers = H; template static auto Apply(const T& aUnion, Fun&& aFun) { auto result = ApplyToTypedArray::Apply( aUnion, std::forward(aFun)); if constexpr (sizeof...(UnionMembers) == 0) { return result; } else { if (result) { return result; } else { return ApplyToTypedArraysHelper::template Apply< Fun>(aUnion, std::forward(aFun)); } } } }; template auto ApplyToTypedArrays(const T& aUnion, Fun&& aFun) { using ApplyToTypedArrays = typename T::ApplyToTypedArrays; auto result = ApplyToTypedArrays::template Apply(aUnion, std::forward(aFun)); if constexpr (ApplyToTypedArrays::HasNonTypedArrayMembers) { return result; } else { MOZ_ASSERT(result, "Didn't expect union members other than typed arrays"); if constexpr (std::is_same_v>, bool>) { return; } else { return result.extract(); } } } } // namespace binding_detail template , int> = 0> [[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion, ToType& aResult) { return binding_detail::ApplyToTypedArrays( aUnion, [&](const auto& aTypedArray) { return aTypedArray.AppendDataTo(aResult); }); } template , int> = 0> [[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion) { return binding_detail::ApplyToTypedArrays( aUnion, [&](const auto& aTypedArray) { return aTypedArray.template CreateFromData(); }); } template , int> = 0> [[nodiscard]] auto ProcessTypedArrays(const T& aUnion, Processor&& aProcessor) { return binding_detail::ApplyToTypedArrays( aUnion, [&](const auto& aTypedArray) { return aTypedArray.ProcessData(std::forward(aProcessor)); }); } template , int> = 0> [[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion, Processor&& aProcessor) { return binding_detail::ApplyToTypedArrays( aUnion, [&](const auto& aTypedArray) { return aTypedArray.ProcessFixedData( std::forward(aProcessor)); }); } } // namespace mozilla::dom #endif /* mozilla_dom_TypedArray_h */