summaryrefslogtreecommitdiffstats
path: root/dom/bindings/TypedArray.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/bindings/TypedArray.h
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/bindings/TypedArray.h')
-rw-r--r--dom/bindings/TypedArray.h1095
1 files changed, 1095 insertions, 0 deletions
diff --git a/dom/bindings/TypedArray.h b/dom/bindings/TypedArray.h
new file mode 100644
index 0000000000..36c46f47b1
--- /dev/null
+++ b/dom/bindings/TypedArray.h
@@ -0,0 +1,1095 @@
+/* -*- 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 <string>
+#include <type_traits>
+#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 "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<size_t, size_t> or a
+ * Maybe<std::pair<size_t, size_t>>. 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<std::pair<size_t, size_t>>, 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<T>,
+ * FallibleTArray<T> and Vector<T>. The signatures are:
+ *
+ * template <typename... Calculator>
+ * [[nodiscard]] bool AppendDataTo(nsCString& aResult, Calculator&&...
+ * aCalculator) const;
+ *
+ * template <typename T, typename... Calculator>
+ * [[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult, Calculator&&...
+ * aCalculator) const;
+ *
+ * template <typename T, typename... Calculator>
+ * [[nodiscard]] bool AppendDataTo(FallibleTArray<T>& aResult,
+ * Calculator&&... aCalculator) const;
+ *
+ * template <typename T, typename... Calculator>
+ * [[nodiscard]] bool AppendDataTo(Vector<T>& 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<uint32_t> 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<float> 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<float> array;
+ * if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) {
+ * if (aLength < offset + length) {
+ * return Maybe<std::pair<size_t, size_t>>();
+ * }
+ * 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 <typename T, size_t N, typename... Calculator>
+ * [[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<std::pair<size_t, size_t>>();
+ * }
+ * 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<element_type>, a
+ * UniquePtr<element_type[]> and a Buffer<element_type>.
+ *
+ * template <typename... Calculator>
+ * [[nodiscard]] Maybe<Vector<element_type>>
+ * CreateFromData<Vector<element_type>>(
+ * Calculator&&... aCalculator) const;
+ *
+ * template <typename... Calculator>
+ * [[nodiscard]] Maybe<UniquePtr<element_type[]>>
+ * CreateFromData<UniquePtr<element_type[]>>(
+ * Calculator&&... aCalculator) const;
+ *
+ * template <typename... Calculator>
+ * [[nodiscard]] Maybe<Buffer<element_type>>
+ * CreateFromData<Buffer<element_type>>(
+ * 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<uint8_t>> buffer =
+ * aUint8Array.CreateFromData<Buffer<uint8_t>>();
+ * if (buffer.isNothing()) {
+ * aError.ThrowTypeError("Failed to create a buffer");
+ * return;
+ * }
+ *
+ * size_t offset, length;
+ * … // Getting offset and length values from somewhere.
+ * Maybe<Buffer<uint8_t>> buffer =
+ * aUint8Array.CreateFromData<Buffer<uint8_t>>([&](
+ * const size_t& aLength) {
+ * if (aLength - offset != ArrayLength(data)) {
+ * aError.ThrowTypeError(
+ * "Typed array doesn't contain the right amount" of data");
+ * return Maybe<std::pair<size_t, size_t>>();
+ * }
+ * 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 <typename Processor>
+ * [[nodiscard]] ProcessReturnType<Processor> ProcessData(
+ * Processor&& aProcessor) const;
+ *
+ * template <typename Processor>
+ * [[nodiscard]] ProcessReturnType<Processor> 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<uint32_t>& aData,
+ * JS::AutoCheckCannotGC&&) {
+ * for (size_t i = 0; i < aData.Length(); ++i) {
+ * aData[i] = i;
+ * }
+ * });
+ *
+ * aUint32Array.ProcessData([&] (const Span<uint32_t>& 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<uint8_t>& aData) {
+ * return std::max_element(aData.cbegin(), aData.cend());
+ * });
+ *
+ * aUint8Array.ProcessFixedData([] (const Span<uint8_t>& 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 <typename ToType, typename T>
+ * [[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion,
+ * ToType& aResult);
+ *
+ * template <typename ToType, typename T>
+ * [[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion);
+ *
+ * template <typename T, typename Processor>
+ * [[nodiscard]] auto ProcessTypedArrays(
+ * const T& aUnion, Processor&& aProcessor);
+ *
+ * template <typename T, typename Processor>
+ * [[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion,
+ * Processor&& aProcessor);
+ *
+ */
+
+template <class ArrayT>
+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<size_t, size_t> or a Maybe<std::pair<size_t, size_t>>
+ * 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<std::pair<size_t, size_t>>, if .isNothing() returns true for the
+ * return value then AppendDataTo will return false and the data won't be
+ * copied.
+ */
+
+ template <typename... Calculator>
+ [[nodiscard]] bool AppendDataTo(nsCString& aResult,
+ Calculator&&... aCalculator) const {
+ static_assert(sizeof...(aCalculator) <= 1,
+ "AppendDataTo takes at most one aCalculator");
+
+ return ProcessDataHelper(
+ [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
+ return aResult.Append(aData, fallible);
+ },
+ std::forward<Calculator>(aCalculator)...);
+ }
+
+ template <typename T, typename... Calculator>
+ [[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult,
+ Calculator&&... aCalculator) const {
+ static_assert(sizeof...(aCalculator) <= 1,
+ "AppendDataTo takes at most one aCalculator");
+
+ return ProcessDataHelper(
+ [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
+ return aResult.AppendElements(aData, fallible);
+ },
+ std::forward<Calculator>(aCalculator)...);
+ }
+
+ template <typename T, typename... Calculator>
+ [[nodiscard]] bool AppendDataTo(FallibleTArray<T>& aResult,
+ Calculator&&... aCalculator) const {
+ static_assert(sizeof...(aCalculator) <= 1,
+ "AppendDataTo takes at most one aCalculator");
+
+ return ProcessDataHelper(
+ [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
+ return aResult.AppendElements(aData, fallible);
+ },
+ std::forward<Calculator>(aCalculator)...);
+ }
+
+ template <typename T, typename... Calculator>
+ [[nodiscard]] bool AppendDataTo(Vector<T>& aResult,
+ Calculator&&... aCalculator) const {
+ static_assert(sizeof...(aCalculator) <= 1,
+ "AppendDataTo takes at most one aCalculator");
+
+ return ProcessDataHelper(
+ [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
+ return aResult.append(aData.Elements(), aData.Length());
+ },
+ std::forward<Calculator>(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 <typename T, size_t N, typename... Calculator>
+ [[nodiscard]] bool CopyDataTo(T (&aResult)[N],
+ Calculator&&... aCalculator) const {
+ static_assert(sizeof...(aCalculator) <= 1,
+ "CopyDataTo takes at most one aCalculator");
+
+ return ProcessDataHelper(
+ [&](const Span<const element_type>& 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<Calculator>(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 <typename T, typename... Calculator,
+ typename IsVector =
+ std::enable_if_t<std::is_same_v<Vector<element_type>, T>>>
+ [[nodiscard]] Maybe<Vector<element_type>> CreateFromData(
+ Calculator&&... aCalculator) const {
+ static_assert(sizeof...(aCalculator) <= 1,
+ "CreateFromData takes at most one aCalculator");
+
+ return CreateFromDataHelper<T>(
+ [&](const Span<const element_type>& aData,
+ Vector<element_type>& aResult) {
+ if (!aResult.initCapacity(aData.Length())) {
+ return false;
+ }
+ aResult.infallibleAppend(aData.Elements(), aData.Length());
+ return true;
+ },
+ std::forward<Calculator>(aCalculator)...);
+ }
+
+ template <typename T, typename... Calculator,
+ typename IsUniquePtr =
+ std::enable_if_t<std::is_same_v<T, UniquePtr<element_type[]>>>>
+ [[nodiscard]] Maybe<UniquePtr<element_type[]>> CreateFromData(
+ Calculator&&... aCalculator) const {
+ static_assert(sizeof...(aCalculator) <= 1,
+ "CreateFromData takes at most one aCalculator");
+
+ return CreateFromDataHelper<T>(
+ [&](const Span<const element_type>& aData,
+ UniquePtr<element_type[]>& aResult) {
+ aResult =
+ MakeUniqueForOverwriteFallible<element_type[]>(aData.Length());
+ if (!aResult.get()) {
+ return false;
+ }
+ memcpy(aResult.get(), aData.Elements(), aData.LengthBytes());
+ return true;
+ },
+ std::forward<Calculator>(aCalculator)...);
+ }
+
+ template <typename T, typename... Calculator,
+ typename IsBuffer =
+ std::enable_if_t<std::is_same_v<T, Buffer<element_type>>>>
+ [[nodiscard]] Maybe<Buffer<element_type>> CreateFromData(
+ Calculator&&... aCalculator) const {
+ static_assert(sizeof...(aCalculator) <= 1,
+ "CreateFromData takes at most one aCalculator");
+
+ return CreateFromDataHelper<T>(
+ [&](const Span<const element_type>& aData,
+ Buffer<element_type>& aResult) {
+ Maybe<Buffer<element_type>> buffer =
+ Buffer<element_type>::CopyFrom(aData);
+ if (buffer.isNothing()) {
+ return false;
+ }
+ aResult = buffer.extract();
+ return true;
+ },
+ std::forward<Calculator>(aCalculator)...);
+ }
+
+ private:
+ template <typename Processor, typename R = decltype(std::declval<Processor>()(
+ std::declval<Span<element_type>>(),
+ std::declval<JS::AutoCheckCannotGC>()))>
+ using ProcessNoGCReturnType = R;
+
+ template <typename Processor>
+ [[nodiscard]] static inline ProcessNoGCReturnType<Processor>
+ CallProcessorNoGC(const Span<element_type>& 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 <typename Processor, typename R = decltype(std::declval<Processor>()(
+ std::declval<Span<element_type>>()))>
+ using ProcessReturnType = R;
+
+ template <typename Processor>
+ [[nodiscard]] static inline ProcessReturnType<Processor> CallProcessor(
+ const Span<element_type>& 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 <typename Processor, typename Calculator>
+ [[nodiscard]] bool ProcessDataHelper(
+ Processor&& aProcessor, Calculator&& aCalculateOffsetAndLength) const {
+ LengthPinner pinner(this);
+
+ JS::AutoCheckCannotGC nogc; // `data` is GC-sensitive.
+ Span<element_type> data = GetCurrentData();
+ const auto& offsetAndLength = aCalculateOffsetAndLength(data.Length());
+ size_t offset, length;
+ if constexpr (std::is_convertible_v<decltype(offsetAndLength),
+ std::pair<size_t, size_t>>) {
+ 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<Processor>(aProcessor),
+ std::move(nogc));
+ }
+
+ template <typename Processor>
+ [[nodiscard]] ProcessNoGCReturnType<Processor> ProcessDataHelper(
+ Processor&& aProcessor) const {
+ LengthPinner pinner(this);
+ // The data from GetCurrentData() is GC sensitive.
+ JS::AutoCheckCannotGC nogc;
+ return CallProcessorNoGC(
+ GetCurrentData(), std::forward<Processor>(aProcessor), std::move(nogc));
+ }
+
+ public:
+ template <typename Processor>
+ [[nodiscard]] ProcessNoGCReturnType<Processor> ProcessData(
+ Processor&& aProcessor) const {
+ return ProcessDataHelper(std::forward<Processor>(aProcessor));
+ }
+
+ template <typename Processor>
+ [[nodiscard]] ProcessReturnType<Processor> ProcessFixedData(
+ Processor&& aProcessor) const {
+ mozilla::dom::AutoJSAPI jsapi;
+ if (!jsapi.Init(mImplObj)) {
+#if defined(EARLY_BETA_OR_EARLIER)
+ if constexpr (std::is_same_v<ArrayT, JS::ArrayBufferView>) {
+ 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<ArrayT, JS::ArrayBufferView>) {
+ JS::Rooted<JSObject*> 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<JS::Value> exn(jsapi.cx());
+ if (JS_GetPendingException(jsapi.cx(), &exn) &&
+ exn.isObject()) {
+ JS::Rooted<JSObject*> 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<Processor>(aProcessor));
+ }
+
+ private:
+ Span<element_type> 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<element_type> span =
+ ArrayT::fromObject(mImplObj).getData(&shared, nogc);
+ MOZ_RELEASE_ASSERT(span.Length() <= INT32_MAX,
+ "Bindings must have checked ArrayBuffer{View} length");
+ return span;
+ }
+
+ template <typename T, typename F, typename... Calculator>
+ [[nodiscard]] Maybe<T> CreateFromDataHelper(
+ F&& aCreator, Calculator&&... aCalculator) const {
+ Maybe<T> result;
+ bool ok = ProcessDataHelper(
+ [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
+ result.emplace();
+ return aCreator(aData, *result);
+ },
+ std::forward<Calculator>(aCalculator)...);
+
+ if (!ok) {
+ return Nothing();
+ }
+
+ return result;
+ }
+
+ 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,
+ 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<const element_type> 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<const element_type> data,
+ ErrorResult& error) {
+ ArrayT array = CreateCommon(cx, data.Length(), error);
+ if (!error.Failed() && !data.IsEmpty()) {
+ CopyFrom(cx, data, array);
+ }
+ return array.asObject();
+ }
+
+ private:
+ template <typename>
+ friend class TypedArrayCreator;
+
+ static inline ArrayT CreateCommon(JSContext* cx, nsWrapperCache* creator,
+ size_t length, ErrorResult& error) {
+ JS::Rooted<JSObject*> creatorWrapper(cx);
+ Maybe<JSAutoRealm> 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<const element_type>& data,
+ ArrayT& dest) {
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ mozilla::Span<element_type> 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 <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 MOZ_STACK_CLASS TypedArrayCreator {
+ typedef nsTArray<typename TypedArrayType::element_type> 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 <typename Union, typename UnionMemberType, typename = int>
+struct ApplyToTypedArray;
+
+#define APPLY_IMPL(type) \
+ template <typename Union> \
+ struct ApplyToTypedArray<Union, type, decltype((void)&Union::Is##type, 0)> { \
+ /* Return type of calling the lambda with a TypedArray 'type'. */ \
+ template <typename F> \
+ using FunReturnType = decltype(std::declval<F>()(std::declval<type>())); \
+ \
+ /* Whether the return type of calling the lambda with a TypedArray */ \
+ /* 'type' is void. */ \
+ template <typename F> \
+ static constexpr bool FunReturnsVoid = \
+ std::is_same_v<FunReturnType<F>, 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 <typename F> \
+ using ApplyReturnType = \
+ std::conditional_t<FunReturnsVoid<F>, bool, Maybe<FunReturnType<F>>>; \
+ \
+ public: \
+ template <typename F> \
+ static ApplyReturnType<F> Apply(const Union& aUnion, F&& aFun) { \
+ if (!aUnion.Is##type()) { \
+ return ApplyReturnType<F>(); /* false or Nothing() */ \
+ } \
+ if constexpr (FunReturnsVoid<F>) { \
+ std::forward<F>(aFun)(aUnion.GetAs##type()); \
+ return true; \
+ } else { \
+ return Some(std::forward<F>(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 <typename T, bool H, typename FirstUnionMember,
+ typename... UnionMembers>
+struct ApplyToTypedArraysHelper {
+ static constexpr bool HasNonTypedArrayMembers = H;
+ template <typename Fun>
+ static auto Apply(const T& aUnion, Fun&& aFun) {
+ auto result = ApplyToTypedArray<T, FirstUnionMember>::Apply(
+ aUnion, std::forward<Fun>(aFun));
+ if constexpr (sizeof...(UnionMembers) == 0) {
+ return result;
+ } else {
+ if (result) {
+ return result;
+ } else {
+ return ApplyToTypedArraysHelper<T, H, UnionMembers...>::template Apply<
+ Fun>(aUnion, std::forward<Fun>(aFun));
+ }
+ }
+ }
+};
+
+template <typename T, typename Fun>
+auto ApplyToTypedArrays(const T& aUnion, Fun&& aFun) {
+ using ApplyToTypedArrays = typename T::ApplyToTypedArrays;
+
+ auto result =
+ ApplyToTypedArrays::template Apply<Fun>(aUnion, std::forward<Fun>(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<std::remove_cv_t<
+ std::remove_reference_t<decltype(result)>>,
+ bool>) {
+ return;
+ } else {
+ return result.extract();
+ }
+ }
+}
+
+} // namespace binding_detail
+
+template <typename T, typename ToType,
+ std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
+[[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion, ToType& aResult) {
+ return binding_detail::ApplyToTypedArrays(
+ aUnion, [&](const auto& aTypedArray) {
+ return aTypedArray.AppendDataTo(aResult);
+ });
+}
+
+template <typename ToType, typename T,
+ std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
+[[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion) {
+ return binding_detail::ApplyToTypedArrays(
+ aUnion, [&](const auto& aTypedArray) {
+ return aTypedArray.template CreateFromData<ToType>();
+ });
+}
+
+template <typename T, typename Processor,
+ std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
+[[nodiscard]] auto ProcessTypedArrays(const T& aUnion, Processor&& aProcessor) {
+ return binding_detail::ApplyToTypedArrays(
+ aUnion, [&](const auto& aTypedArray) {
+ return aTypedArray.ProcessData(std::forward<Processor>(aProcessor));
+ });
+}
+
+template <typename T, typename Processor,
+ std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
+[[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion,
+ Processor&& aProcessor) {
+ return binding_detail::ApplyToTypedArrays(
+ aUnion, [&](const auto& aTypedArray) {
+ return aTypedArray.ProcessFixedData(
+ std::forward<Processor>(aProcessor));
+ });
+}
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_TypedArray_h */