tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TypedArray.h (42984B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef mozilla_dom_TypedArray_h
      8 #define mozilla_dom_TypedArray_h
      9 
     10 #include <type_traits>
     11 #include <utility>
     12 
     13 #include "js/ArrayBuffer.h"
     14 #include "js/ArrayBufferMaybeShared.h"
     15 #include "js/Context.h"
     16 #include "js/GCAPI.h"       // JS::AutoCheckCannotGC
     17 #include "js/RootingAPI.h"  // JS::Rooted
     18 #include "js/ScalarType.h"  // JS::Scalar::Type
     19 #include "js/SharedArrayBuffer.h"
     20 #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
     21 #include "js/friend/ErrorMessages.h"
     22 #include "mozilla/Attributes.h"
     23 #include "mozilla/Buffer.h"
     24 #include "mozilla/ErrorResult.h"
     25 #include "mozilla/Vector.h"
     26 #include "mozilla/dom/BindingDeclarations.h"
     27 #include "mozilla/dom/ScriptSettings.h"
     28 #include "mozilla/dom/SpiderMonkeyInterface.h"
     29 #include "nsIGlobalObject.h"
     30 #include "nsWrapperCache.h"
     31 #include "nsWrapperCacheInlines.h"
     32 
     33 namespace mozilla::dom {
     34 
     35 /*
     36 * Various typed array classes for argument conversion.  We have a base class
     37 * that has a way of initializing a TypedArray from an existing typed array, and
     38 * a subclass of the base class that supports creation of a relevant typed array
     39 * or array buffer object.
     40 *
     41 * Accessing the data of a TypedArray is tricky. The underlying storage is in a
     42 * JS object, which is subject to the JS GC. The memory for the array can be
     43 * either inline in the JSObject or stored separately. If the data is stored
     44 * inline then the exact location in memory of the data buffer can change as a
     45 * result of a JS GC (which is a moving GC). Running JS code can also mutate the
     46 * data (including the length). For all these reasons one has to be careful when
     47 * holding a pointer to the data or keeping a local copy of the length value. On
     48 * the other hand, most code that takes a TypedArray has to access its data at
     49 * some point, to process it in some way. The TypedArray class tries to supply a
     50 * number of helper APIs, so that the most common cases of processing the data
     51 * can be done safely, without having to check the caller very closely for
     52 * potential security issues. The main classes of processing TypedArray data
     53 * are:
     54 *
     55 *  1) Appending a copy of the data (or of a subset of the data) to a different
     56 *     data structure
     57 *  2) Copying the data (or a subset of the data) into a different data
     58 *     structure
     59 *  3) Creating a new data structure with a copy of the data (or of a subset of
     60 *     the data)
     61 *  4) Processing the data in some other way
     62 *
     63 * The APIs for the first 3 classes all return a boolean and take an optional
     64 * argument named aCalculator. aCalculator should be a lambda taking a size_t
     65 * argument which will be passed the total length of the data in the typed
     66 * array. aCalculator is allowed to return a std::pair<size_t, size_t> or a
     67 * Maybe<std::pair<size_t, size_t>>. The return value should contain the offset
     68 * and the length of the subset of the data that should be appended, copied or
     69 * used for creating a new datastructure. If the calculation can fail then
     70 * aCalculator should return a Maybe<std::pair<size_t, size_t>>, with Nothing()
     71 * signaling that the operation should be aborted.
     72 * The return value of these APIs will be false if appending, copying or
     73 * creating a structure with the new data failed, or if the optional aCalculator
     74 * lambda returned Nothing().
     75 *
     76 * Here are the different APIs:
     77 *
     78 *  1) Appending to a different data structure
     79 *
     80 *     There are AppendDataTo helpers for nsCString, nsTArray<T>,
     81 *     FallibleTArray<T> and Vector<T>. The signatures are:
     82 *
     83 *       template <typename... Calculator>
     84 *       [[nodiscard]] bool AppendDataTo(nsCString& aResult, Calculator&&...
     85 *                                       aCalculator) const;
     86 *
     87 *       template <typename T, typename... Calculator>
     88 *       [[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult, Calculator&&...
     89 *                                       aCalculator) const;
     90 *
     91 *       template <typename T, typename... Calculator>
     92 *       [[nodiscard]] bool AppendDataTo(FallibleTArray<T>& aResult,
     93 *                                       Calculator&&... aCalculator) const;
     94 *
     95 *       template <typename T, typename... Calculator>
     96 *       [[nodiscard]] bool AppendDataTo(Vector<T>& aResult, Calculator&&...
     97 *                                       aCalculator) const;
     98 *
     99 *     The data (or the calculated subset) will be appended to aResult by using
    100 *     the appropriate fallible API. If the append fails then AppendDataTo will
    101 *     return false. The aCalculator optional argument is described above.
    102 *
    103 *     Examples:
    104 *
    105 *       Vector<uint32_t> array;
    106 *       if (!aUint32Array.AppendDataTo(array)) {
    107 *         aError.ThrowTypeError("Failed to copy data from typed array");
    108 *         return;
    109 *       }
    110 *
    111 *       size_t offset, length;
    112 *       … // Getting offset and length values from somewhere.
    113 *       FallibleTArray<float> array;
    114 *       if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) {
    115 *           size_t dataLength = std::min(aLength - offset, length);
    116 *           return std::make_pair(offset, dataLength);
    117 *         }) {
    118 *         aError.ThrowTypeError("Failed to copy data from typed array");
    119 *         return;
    120 *       }
    121 *
    122 *       size_t offset, length;
    123 *       … // Getting offset and length values from somewhere.
    124 *       FallibleTArray<float> array;
    125 *       if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) {
    126 *         if (aLength < offset + length) {
    127 *           return Maybe<std::pair<size_t, size_t>>();
    128 *         }
    129 *         size_t dataLength = std::min(aLength - offset, length);
    130 *         return Some(std::make_pair(offset, dataLength));
    131 *       })) {
    132 *         aError.ThrowTypeError("Failed to copy data from typed array");
    133 *         return;
    134 *       }
    135 *
    136 *
    137 *  2) Copying into a different data structure
    138 *
    139 *     There is a CopyDataTo helper for a fixed-size buffer. The signature is:
    140 *
    141 *       template <typename T, size_t N, typename... Calculator>
    142 *       [[nodiscard]] bool CopyDataTo(T (&aResult)[N],
    143 *                                     Calculator&&... aCalculator) const;
    144 *
    145 *     The data (or the calculated subset) will be copied to aResult, starting
    146 *     at aResult[0]. If the length of the data to be copied is bigger than the
    147 *     size of the fixed-size buffer (N) then nothing will be copied and
    148 *     CopyDataTo will return false. The aCalculator optional argument is
    149 *     described above.
    150 *
    151 *     Examples:
    152 *
    153 *       float data[3];
    154 *       if (!aFloat32Array.CopyDataTo(data)) {
    155 *         aError.ThrowTypeError("Typed array doesn't contain the right amount"
    156 *                               "of data");
    157 *         return;
    158 *       }
    159 *
    160 *       size_t offset;
    161 *       … // Getting offset value from somewhere.
    162 *       uint32_t data[3];
    163 *       if (!aUint32Array.CopyDataTo(data, [&](const size_t& aLength) {
    164 *         if (aLength - offset != std::size(data)) {
    165 *           aError.ThrowTypeError("Typed array doesn't contain the right"
    166 *                                 " amount of data");
    167 *           return Maybe<std::pair<size_t, size_t>>();
    168 *         }
    169 *         return Some(std::make_pair(offset, std::size(data)));
    170 *       }) {
    171 *         return;
    172 *       }
    173 *
    174 *  3) Creating a new data structure with a copy of the data (or a subset of the
    175 *     data)
    176 *
    177 *     There are CreateFromData helper for creating a Vector<element_type>, a
    178 *     UniquePtr<element_type[]> and a Buffer<element_type>.
    179 *
    180 *       template <typename... Calculator>
    181 *       [[nodiscard]] Maybe<Vector<element_type>>
    182 *       CreateFromData<Vector<element_type>>(
    183 *           Calculator&&... aCalculator) const;
    184 *
    185 *       template <typename... Calculator>
    186 *       [[nodiscard]] Maybe<UniquePtr<element_type[]>>
    187 *       CreateFromData<UniquePtr<element_type[]>>(
    188 *           Calculator&&... aCalculator) const;
    189 *
    190 *       template <typename... Calculator>
    191 *       [[nodiscard]] Maybe<Buffer<element_type>>
    192 *       CreateFromData<Buffer<element_type>>(
    193 *           Calculator&&... aCalculator) const;
    194 *
    195 *     A new container will be created, and the data (or the calculated subset)
    196 *     will be copied to it. The container will be returned inside a Maybe<…>.
    197 *     If creating the container with the right size fails then Nothing() will
    198 *     be returned. The aCalculator optional argument is described above.
    199 *
    200 *     Examples:
    201 *
    202 *       Maybe<Buffer<uint8_t>> buffer =
    203 *         aUint8Array.CreateFromData<Buffer<uint8_t>>();
    204 *       if (buffer.isNothing()) {
    205 *         aError.ThrowTypeError("Failed to create a buffer");
    206 *         return;
    207 *       }
    208 *
    209 *       size_t offset, length;
    210 *       … // Getting offset and length values from somewhere.
    211 *       Maybe<Buffer<uint8_t>> buffer =
    212 *         aUint8Array.CreateFromData<Buffer<uint8_t>>([&](
    213 *             const size_t& aLength) {
    214 *         if (aLength - offset != std::size(data)) {
    215 *           aError.ThrowTypeError(
    216 *               "Typed array doesn't contain the right amount" of data");
    217 *           return Maybe<std::pair<size_t, size_t>>();
    218 *         }
    219 *         return Some(std::make_pair(offset, std::size(data)));
    220 *       });
    221 *       if (buffer.isNothing()) {
    222 *         return;
    223 *       }
    224 *
    225 *  4) Processing the data in some other way
    226 *
    227 *     This is the API for when none of the APIs above are appropriate for your
    228 *     usecase. As these are the most dangerous APIs you really should check
    229 *     first if you can't use one of the safer alternatives above. The reason
    230 *     these APIs are more dangerous is because they give access to the typed
    231 *     array's data directly, and the location of that data can be changed by
    232 *     the JS GC (due to generational and/or compacting collection). There are
    233 *     two APIs for processing data:
    234 *
    235 *       template <typename Processor>
    236 *       [[nodiscard]] ProcessReturnType<Processor> ProcessData(
    237 *           Processor&& aProcessor) const;
    238 *
    239 *       template <typename Processor>
    240 *       [[nodiscard]] ProcessReturnType<Processor> ProcessFixedData(
    241 *           Processor&& aProcessor) const;
    242 *
    243 *     ProcessData will call the lambda with as arguments a |const Span<…>&|
    244 *     wrapping the data pointer and length for the data in the typed array, and
    245 *     a |JS::AutoCheckCannotGC&&|. The lambda will execute in a context where
    246 *     GC is not allowed.
    247 *
    248 *     ProcessFixedData will call the lambda with as argument |const Span<…>&|.
    249 *     For small typed arrays where the data is stored inline in the typed
    250 *     array, and thus could move during GC, then the data will be copied into a
    251 *     fresh out-of-line allocation before the lambda is called.
    252 *
    253 *     The signature of the lambdas for ProcessData and ProcessFixedData differ
    254 *     in that the ProcessData lambda will additionally be passed a nogc token
    255 *     to prevent GC from occurring and invalidating the data. If the processing
    256 *     you need to do in the lambda can't be proven to not GC then you should
    257 *     probably use ProcessFixedData instead. There are cases where you need to
    258 *     do something that can cause a GC but you don't actually need to access
    259 *     the data anymore. A good example would be throwing a JS exception and
    260 *     return. For those very specific cases you can call nogc.reset() before
    261 *     doing anything that causes a GC. Be extra careful to not access the data
    262 *     after you called nogc.reset().
    263 *
    264 *     Extra care must be taken to not let the Span<…> or any pointers derived
    265 *     from it escape the lambda, as the position in memory of the typed array's
    266 *     data can change again once we leave the lambda (invalidating the
    267 *     pointers). The lambda passed to ProcessData is not allowed to do anything
    268 *     that will trigger a GC, and the GC rooting hazard analysis will enforce
    269 *     that.
    270 *
    271 *     Both ProcessData and ProcessFixedData will pin the typed array's length
    272 *     while calling the lambda, to block any changes to the length of the data.
    273 *     Note that this means that the lambda itself isn't allowed to change the
    274 *     length of the typed array's data. Any attempt to change the length will
    275 *     throw a JS exception.
    276 *
    277 *     The return type of ProcessData and ProcessFixedData depends on the return
    278 *     type of the lambda, as they forward the return value from the lambda to
    279 *     the caller of ProcessData or ProcessFixedData.
    280 *
    281 *     Examples:
    282 *
    283 *       aUint32Array.ProcessData([] (const Span<uint32_t>& aData,
    284 *                                    JS::AutoCheckCannotGC&&) {
    285 *         for (size_t i = 0; i < aData.Length(); ++i) {
    286 *           aData[i] = i;
    287 *         }
    288 *       });
    289 *
    290 *       aUint32Array.ProcessData([&] (const Span<uint32_t>& aData,
    291 *                                     JS::AutoCheckCannotGC&& nogc) {
    292 *         for (size_t i = 0; i < aData.Length(); ++i) {
    293 *           if (!aData[i]) {
    294 *             nogc.reset();
    295 *             ThrowJSException("Data shouldn't contain 0");
    296 *             return;
    297 *           };
    298 *           DoSomething(aData[i]);
    299 *         }
    300 *       });
    301 *
    302 *       uint8_t max = aUint8Array.ProcessData([] (const Span<uint8_t>& aData) {
    303 *         return std::max_element(aData.cbegin(), aData.cend());
    304 *       });
    305 *
    306 *       aUint8Array.ProcessFixedData([] (const Span<uint8_t>& aData) {
    307 *         return CallFunctionThatMightGC(aData);
    308 *       });
    309 *
    310 *
    311 * In addition to the above APIs we provide helpers to call them on the typed
    312 * array members of WebIDL unions. We have helpers for the 4 different sets of
    313 * APIs above. The return value of the helpers depends on whether the union can
    314 * contain a type other than a typed array. If the union can't contain a type
    315 * other than a typed array then the return type is simply the type returned by
    316 * the corresponding API above. If the union can contain a type other than a
    317 * typed array then the return type of the helper is a Maybe<…> wrapping the
    318 * actual return type, with Nothing() signifying that the union contained a
    319 * non-typed array value.
    320 *
    321 *     template <typename ToType, typename T>
    322 *     [[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion,
    323 *                                               ToType& aResult);
    324 *
    325 *     template <typename ToType, typename T>
    326 *     [[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion);
    327 *
    328 *     template <typename T, typename Processor>
    329 *     [[nodiscard]] auto ProcessTypedArrays(
    330 *         const T& aUnion, Processor&& aProcessor);
    331 *
    332 *     template <typename T, typename Processor>
    333 *     [[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion,
    334 *                                                Processor&& aProcessor);
    335 *
    336 */
    337 
    338 template <class ArrayT>
    339 struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
    340                         AllTypedArraysBase {
    341  using element_type = typename ArrayT::DataType;
    342 
    343  TypedArray_base() = default;
    344  TypedArray_base(TypedArray_base&& aOther) = default;
    345 
    346 public:
    347  inline bool Init(JSObject* obj) {
    348    MOZ_ASSERT(!inited());
    349    mImplObj = mWrappedObj = ArrayT::unwrap(obj).asObject();
    350    return inited();
    351  }
    352 
    353  // About shared memory:
    354  //
    355  // Any DOM TypedArray as well as any DOM ArrayBufferView can map the
    356  // memory of either a JS ArrayBuffer or a JS SharedArrayBuffer.
    357  //
    358  // Code that elects to allow views that map shared memory to be used
    359  // -- ie, code that "opts in to shared memory" -- should generally
    360  // not access the raw data buffer with standard C++ mechanisms as
    361  // that creates the possibility of C++ data races, which is
    362  // undefined behavior.  The JS engine will eventually export (bug
    363  // 1225033) a suite of methods that avoid undefined behavior.
    364  //
    365  // Callers of Obj() that do not opt in to shared memory can produce
    366  // better diagnostics by checking whether the JSObject in fact maps
    367  // shared memory and throwing an error if it does.  However, it is
    368  // safe to use the value of Obj() without such checks.
    369  //
    370  // The DOM TypedArray abstraction prevents the underlying buffer object
    371  // from being accessed directly, but JS_GetArrayBufferViewBuffer(Obj())
    372  // will obtain the buffer object.  Code that calls that function must
    373  // not assume the returned buffer is an ArrayBuffer.  That is guarded
    374  // against by an out parameter on that call that communicates the
    375  // sharedness of the buffer.
    376  //
    377  // Finally, note that the buffer memory of a SharedArrayBuffer is
    378  // not detachable.
    379 
    380 public:
    381  /**
    382   * Helper functions to append a copy of this typed array's data to a
    383   * container. Returns false if the allocation for copying the data fails.
    384   *
    385   * aCalculator is an optional argument to which one can pass a lambda
    386   * expression that will calculate the offset and length of the data to copy
    387   * out of the typed array. aCalculator will be called with one argument of
    388   * type size_t set to the length of the data in the typed array. It is allowed
    389   * to return a std::pair<size_t, size_t> or a Maybe<std::pair<size_t, size_t>>
    390   * containing the offset and the length of the subset of the data that we
    391   * should copy. If the calculation can fail then aCalculator should return a
    392   * Maybe<std::pair<size_t, size_t>>, if .isNothing() returns true for the
    393   * return value then AppendDataTo will return false and the data won't be
    394   * copied.
    395   */
    396 
    397  template <typename... Calculator>
    398  [[nodiscard]] bool AppendDataTo(nsCString& aResult,
    399                                  Calculator&&... aCalculator) const {
    400    static_assert(sizeof...(aCalculator) <= 1,
    401                  "AppendDataTo takes at most one aCalculator");
    402 
    403    return ProcessDataHelper(
    404        [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
    405          return aResult.Append(aData, fallible);
    406        },
    407        std::forward<Calculator>(aCalculator)...);
    408  }
    409 
    410  template <typename T, typename... Calculator>
    411  [[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult,
    412                                  Calculator&&... aCalculator) const {
    413    static_assert(sizeof...(aCalculator) <= 1,
    414                  "AppendDataTo takes at most one aCalculator");
    415 
    416    return ProcessDataHelper(
    417        [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
    418          return aResult.AppendElements(aData, fallible);
    419        },
    420        std::forward<Calculator>(aCalculator)...);
    421  }
    422 
    423  template <typename T, typename... Calculator>
    424  [[nodiscard]] bool AppendDataTo(FallibleTArray<T>& aResult,
    425                                  Calculator&&... aCalculator) const {
    426    static_assert(sizeof...(aCalculator) <= 1,
    427                  "AppendDataTo takes at most one aCalculator");
    428 
    429    return ProcessDataHelper(
    430        [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
    431          return aResult.AppendElements(aData, fallible);
    432        },
    433        std::forward<Calculator>(aCalculator)...);
    434  }
    435 
    436  template <typename T, typename... Calculator>
    437  [[nodiscard]] bool AppendDataTo(Vector<T>& aResult,
    438                                  Calculator&&... aCalculator) const {
    439    static_assert(sizeof...(aCalculator) <= 1,
    440                  "AppendDataTo takes at most one aCalculator");
    441 
    442    return ProcessDataHelper(
    443        [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
    444          return aResult.append(aData.Elements(), aData.Length());
    445        },
    446        std::forward<Calculator>(aCalculator)...);
    447  }
    448 
    449  /**
    450   * Helper functions to copy this typed array's data to a container. This will
    451   * clear any existing data in the container.
    452   *
    453   * See the comments for AppendDataTo for information on the aCalculator
    454   * argument.
    455   */
    456 
    457  template <typename T, size_t N, typename... Calculator>
    458  [[nodiscard]] bool CopyDataTo(T (&aResult)[N],
    459                                Calculator&&... aCalculator) const {
    460    static_assert(sizeof...(aCalculator) <= 1,
    461                  "CopyDataTo takes at most one aCalculator");
    462 
    463    return ProcessDataHelper(
    464        [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
    465          if (aData.Length() != N) {
    466            return false;
    467          }
    468          for (size_t i = 0; i < N; ++i) {
    469            aResult[i] = aData[i];
    470          }
    471          return true;
    472        },
    473        std::forward<Calculator>(aCalculator)...);
    474  }
    475 
    476  template <typename T, size_t N, typename... Calculator>
    477  [[nodiscard]] bool CopyDataTo(std::array<T, N>* const aResult,
    478                                Calculator&&... aCalculator) const {
    479    static_assert(sizeof...(aCalculator) <= 1,
    480                  "CopyDataTo takes at most one aCalculator");
    481 
    482    return ProcessDataHelper(
    483        [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
    484          if (aData.Length() != N) {
    485            return false;
    486          }
    487          for (size_t i = 0; i < N; ++i) {
    488            (*aResult).at(i) = aData[i];
    489          }
    490          return true;
    491        },
    492        std::forward<Calculator>(aCalculator)...);
    493  }
    494 
    495  /**
    496   * Helper functions to copy this typed array's data to a newly created
    497   * container. Returns Nothing() if creating the container with the right size
    498   * fails.
    499   *
    500   * See the comments for AppendDataTo for information on the aCalculator
    501   * argument.
    502   */
    503 
    504  template <typename T, typename... Calculator,
    505            typename IsVector =
    506                std::enable_if_t<std::is_same_v<Vector<element_type>, T>>>
    507  [[nodiscard]] Maybe<Vector<element_type>> CreateFromData(
    508      Calculator&&... aCalculator) const {
    509    static_assert(sizeof...(aCalculator) <= 1,
    510                  "CreateFromData takes at most one aCalculator");
    511 
    512    return CreateFromDataHelper<T>(
    513        [&](const Span<const element_type>& aData,
    514            Vector<element_type>& aResult) {
    515          if (!aResult.initCapacity(aData.Length())) {
    516            return false;
    517          }
    518          aResult.infallibleAppend(aData.Elements(), aData.Length());
    519          return true;
    520        },
    521        std::forward<Calculator>(aCalculator)...);
    522  }
    523 
    524  template <typename T, typename... Calculator,
    525            typename IsUniquePtr =
    526                std::enable_if_t<std::is_same_v<T, UniquePtr<element_type[]>>>>
    527  [[nodiscard]] Maybe<UniquePtr<element_type[]>> CreateFromData(
    528      Calculator&&... aCalculator) const {
    529    static_assert(sizeof...(aCalculator) <= 1,
    530                  "CreateFromData takes at most one aCalculator");
    531 
    532    return CreateFromDataHelper<T>(
    533        [&](const Span<const element_type>& aData,
    534            UniquePtr<element_type[]>& aResult) {
    535          aResult =
    536              MakeUniqueForOverwriteFallible<element_type[]>(aData.Length());
    537          if (!aResult.get()) {
    538            return false;
    539          }
    540          memcpy(aResult.get(), aData.Elements(), aData.LengthBytes());
    541          return true;
    542        },
    543        std::forward<Calculator>(aCalculator)...);
    544  }
    545 
    546  template <typename T, typename... Calculator,
    547            typename IsBuffer =
    548                std::enable_if_t<std::is_same_v<T, Buffer<element_type>>>>
    549  [[nodiscard]] Maybe<Buffer<element_type>> CreateFromData(
    550      Calculator&&... aCalculator) const {
    551    static_assert(sizeof...(aCalculator) <= 1,
    552                  "CreateFromData takes at most one aCalculator");
    553 
    554    return CreateFromDataHelper<T>(
    555        [&](const Span<const element_type>& aData,
    556            Buffer<element_type>& aResult) {
    557          Maybe<Buffer<element_type>> buffer =
    558              Buffer<element_type>::CopyFrom(aData);
    559          if (buffer.isNothing()) {
    560            return false;
    561          }
    562          aResult = buffer.extract();
    563          return true;
    564        },
    565        std::forward<Calculator>(aCalculator)...);
    566  }
    567 
    568 private:
    569  template <typename Processor, typename R = decltype(std::declval<Processor>()(
    570                                    std::declval<Span<element_type>>(),
    571                                    std::declval<JS::AutoCheckCannotGC>()))>
    572  using ProcessNoGCReturnType = R;
    573 
    574  template <typename Processor>
    575  [[nodiscard]] static inline ProcessNoGCReturnType<Processor>
    576  CallProcessorNoGC(const Span<element_type>& aData, Processor&& aProcessor,
    577                    JS::AutoCheckCannotGC&& nogc) {
    578    MOZ_ASSERT(
    579        aData.IsEmpty() || aData.Elements(),
    580        "We expect a non-null data pointer for typed arrays that aren't empty");
    581 
    582    return aProcessor(aData, std::move(nogc));
    583  }
    584 
    585  template <typename Processor, typename R = decltype(std::declval<Processor>()(
    586                                    std::declval<Span<element_type>>()))>
    587  using ProcessReturnType = R;
    588 
    589  template <typename Processor>
    590  [[nodiscard]] static inline ProcessReturnType<Processor> CallProcessor(
    591      const Span<element_type>& aData, Processor&& aProcessor) {
    592    MOZ_ASSERT(
    593        aData.IsEmpty() || aData.Elements(),
    594        "We expect a non-null data pointer for typed arrays that aren't empty");
    595 
    596    return aProcessor(aData);
    597  }
    598 
    599  struct MOZ_STACK_CLASS LengthPinner {
    600    explicit LengthPinner(const TypedArray_base* aTypedArray)
    601        : mTypedArray(aTypedArray),
    602          mWasPinned(
    603              !JS::PinArrayBufferOrViewLength(aTypedArray->Obj(), true)) {}
    604    ~LengthPinner() {
    605      if (!mWasPinned) {
    606        JS::PinArrayBufferOrViewLength(mTypedArray->Obj(), false);
    607      }
    608    }
    609 
    610   private:
    611    const TypedArray_base* mTypedArray;
    612    bool mWasPinned;
    613  };
    614 
    615  template <typename Processor, typename Calculator>
    616  [[nodiscard]] bool ProcessDataHelper(
    617      Processor&& aProcessor, Calculator&& aCalculateOffsetAndLength) const {
    618    LengthPinner pinner(this);
    619 
    620    JS::AutoCheckCannotGC nogc;  // `data` is GC-sensitive.
    621    Span<element_type> data = GetCurrentData();
    622    const auto& offsetAndLength = aCalculateOffsetAndLength(data.Length());
    623    size_t offset, length;
    624    if constexpr (std::is_convertible_v<decltype(offsetAndLength),
    625                                        std::pair<size_t, size_t>>) {
    626      std::tie(offset, length) = offsetAndLength;
    627    } else {
    628      if (offsetAndLength.isNothing()) {
    629        return false;
    630      }
    631      std::tie(offset, length) = offsetAndLength.value();
    632    }
    633 
    634    return CallProcessorNoGC(data.Subspan(offset, length),
    635                             std::forward<Processor>(aProcessor),
    636                             std::move(nogc));
    637  }
    638 
    639  template <bool AllowLargeTypedArrays = false, typename Processor>
    640  [[nodiscard]] ProcessNoGCReturnType<Processor> ProcessDataHelper(
    641      Processor&& aProcessor) const {
    642    LengthPinner pinner(this);
    643    // The data from GetCurrentData() is GC sensitive.
    644    JS::AutoCheckCannotGC nogc;
    645    return CallProcessorNoGC(GetCurrentData<AllowLargeTypedArrays>(),
    646                             std::forward<Processor>(aProcessor),
    647                             std::move(nogc));
    648  }
    649 
    650 public:
    651  template <bool AllowLargeTypedArrays = false, typename Processor>
    652  [[nodiscard]] ProcessNoGCReturnType<Processor> ProcessData(
    653      Processor&& aProcessor) const {
    654    return ProcessDataHelper<AllowLargeTypedArrays>(
    655        std::forward<Processor>(aProcessor));
    656  }
    657 
    658  template <typename Processor>
    659  [[nodiscard]] ProcessReturnType<Processor> ProcessFixedData(
    660      Processor&& aProcessor) const {
    661    mozilla::dom::AutoJSAPI jsapi;
    662    if (!jsapi.Init(mImplObj)) {
    663 #if defined(EARLY_BETA_OR_EARLIER)
    664      if constexpr (std::is_same_v<ArrayT, JS::ArrayBufferView>) {
    665        if (!mImplObj) {
    666          MOZ_CRASH("Null mImplObj");
    667        }
    668        if (!xpc::NativeGlobal(mImplObj)) {
    669          MOZ_CRASH("Null xpc::NativeGlobal(mImplObj)");
    670        }
    671        if (!xpc::NativeGlobal(mImplObj)->GetGlobalJSObject()) {
    672          MOZ_CRASH("Null xpc::NativeGlobal(mImplObj)->GetGlobalJSObject()");
    673        }
    674      }
    675 #endif
    676      MOZ_CRASH("Failed to get JSContext");
    677    }
    678 #if defined(EARLY_BETA_OR_EARLIER)
    679    if constexpr (std::is_same_v<ArrayT, JS::ArrayBufferView>) {
    680      JS::Rooted<JSObject*> view(jsapi.cx(),
    681                                 js::UnwrapArrayBufferView(mImplObj));
    682      if (!view) {
    683        if (JSObject* unwrapped = js::CheckedUnwrapStatic(mImplObj)) {
    684          if (!js::UnwrapArrayBufferView(unwrapped)) {
    685            MOZ_CRASH(
    686                "Null "
    687                "js::UnwrapArrayBufferView(js::CheckedUnwrapStatic(mImplObj))");
    688          }
    689          view = unwrapped;
    690        } else {
    691          MOZ_CRASH("Null js::CheckedUnwrapStatic(mImplObj)");
    692        }
    693      }
    694    }
    695 #endif
    696    JS::AutoBrittleMode abm(jsapi.cx());
    697    if (!JS::EnsureNonInlineArrayBufferOrView(jsapi.cx(), mImplObj)) {
    698      MOZ_CRASH("small oom when moving inline data out-of-line");
    699    }
    700    LengthPinner pinner(this);
    701 
    702    return CallProcessor(GetCurrentData(), std::forward<Processor>(aProcessor));
    703  }
    704 
    705 private:
    706  template <bool AllowLargeTypedArrays = false>
    707  Span<element_type> GetCurrentData() const {
    708    MOZ_ASSERT(inited());
    709    MOZ_RELEASE_ASSERT(
    710        !ArrayT::fromObject(mImplObj).isResizable(),
    711        "Bindings must have checked ArrayBuffer{View} is non-resizable");
    712    MOZ_RELEASE_ASSERT(
    713        !ArrayT::fromObject(mImplObj).isImmutable(),
    714        "Bindings must have checked ArrayBuffer{View} is mutable");
    715 
    716    // Intentionally return a pointer and length that escape from a nogc region.
    717    // Private so it can only be used in very limited situations.
    718    JS::AutoCheckCannotGC nogc;
    719    bool shared;
    720    Span<element_type> span =
    721        ArrayT::fromObject(mImplObj).getData(&shared, nogc);
    722    if constexpr (!AllowLargeTypedArrays) {
    723      MOZ_RELEASE_ASSERT(span.Length() <= INT32_MAX,
    724                         "Bindings must have checked ArrayBuffer{View} length");
    725    }
    726    return span;
    727  }
    728 
    729  template <typename T, typename F, typename... Calculator>
    730  [[nodiscard]] Maybe<T> CreateFromDataHelper(
    731      F&& aCreator, Calculator&&... aCalculator) const {
    732    Maybe<T> result;
    733    bool ok = ProcessDataHelper(
    734        [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
    735          result.emplace();
    736          return aCreator(aData, *result);
    737        },
    738        std::forward<Calculator>(aCalculator)...);
    739 
    740    if (!ok) {
    741      return Nothing();
    742    }
    743 
    744    return result;
    745  }
    746 
    747  TypedArray_base(const TypedArray_base&) = delete;
    748 };
    749 
    750 template <class ArrayT>
    751 struct TypedArray : public TypedArray_base<ArrayT> {
    752  using Base = TypedArray_base<ArrayT>;
    753  using element_type = typename Base::element_type;
    754 
    755  TypedArray() = default;
    756 
    757  TypedArray(TypedArray&& aOther) = default;
    758 
    759  static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator,
    760                                 size_t length, ErrorResult& error) {
    761    return CreateCommon(cx, creator, length, error).asObject();
    762  }
    763 
    764  static inline JSObject* Create(JSContext* cx, size_t length,
    765                                 ErrorResult& error) {
    766    return CreateCommon(cx, length, error).asObject();
    767  }
    768 
    769  static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator,
    770                                 Span<const element_type> data,
    771                                 ErrorResult& error) {
    772    ArrayT array = CreateCommon(cx, creator, data.Length(), error);
    773    if (!error.Failed() && !data.IsEmpty()) {
    774      CopyFrom(cx, data, array);
    775    }
    776    return array.asObject();
    777  }
    778 
    779  static inline JSObject* Create(JSContext* cx, Span<const element_type> data,
    780                                 ErrorResult& error) {
    781    ArrayT array = CreateCommon(cx, data.Length(), error);
    782    if (!error.Failed() && !data.IsEmpty()) {
    783      CopyFrom(cx, data, array);
    784    }
    785    return array.asObject();
    786  }
    787 
    788 private:
    789  template <typename>
    790  friend class TypedArrayCreator;
    791 
    792  static inline ArrayT CreateCommon(JSContext* cx, nsWrapperCache* creator,
    793                                    size_t length, ErrorResult& error) {
    794    JS::Rooted<JSObject*> creatorWrapper(cx);
    795    Maybe<JSAutoRealm> ar;
    796    if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) {
    797      ar.emplace(cx, creatorWrapper);
    798    }
    799 
    800    return CreateCommon(cx, length, error);
    801  }
    802  static inline ArrayT CreateCommon(JSContext* cx, size_t length,
    803                                    ErrorResult& error) {
    804    error.MightThrowJSException();
    805    ArrayT array = CreateCommon(cx, length);
    806    if (array) {
    807      return array;
    808    }
    809    error.StealExceptionFromJSContext(cx);
    810    return ArrayT::fromObject(nullptr);
    811  }
    812  // NOTE: this leaves any exceptions on the JSContext, and the caller is
    813  //       required to deal with them.
    814  static inline ArrayT CreateCommon(JSContext* cx, size_t length) {
    815    return ArrayT::create(cx, length);
    816  }
    817  static inline void CopyFrom(JSContext* cx,
    818                              const Span<const element_type>& data,
    819                              ArrayT& dest) {
    820    JS::AutoCheckCannotGC nogc;
    821    bool isShared;
    822    mozilla::Span<element_type> span = dest.getData(&isShared, nogc);
    823    MOZ_ASSERT(span.size() == data.size(),
    824               "Didn't create a large enough typed array object?");
    825    // Data will not be shared, until a construction protocol exists
    826    // for constructing shared data.
    827    MOZ_ASSERT(!isShared);
    828    memcpy(span.Elements(), data.Elements(), data.LengthBytes());
    829  }
    830 
    831  TypedArray(const TypedArray&) = delete;
    832 };
    833 
    834 template <JS::Scalar::Type GetViewType(JSObject*)>
    835 struct ArrayBufferView_base : public TypedArray_base<JS::ArrayBufferView> {
    836 private:
    837  using Base = TypedArray_base<JS::ArrayBufferView>;
    838 
    839 public:
    840  ArrayBufferView_base() : Base(), mType(JS::Scalar::MaxTypedArrayViewType) {}
    841 
    842  ArrayBufferView_base(ArrayBufferView_base&& aOther)
    843      : Base(std::move(aOther)), mType(aOther.mType) {
    844    aOther.mType = JS::Scalar::MaxTypedArrayViewType;
    845  }
    846 
    847 private:
    848  JS::Scalar::Type mType;
    849 
    850 public:
    851  inline bool Init(JSObject* obj) {
    852    if (!Base::Init(obj)) {
    853      return false;
    854    }
    855 
    856    mType = GetViewType(this->Obj());
    857    return true;
    858  }
    859 
    860  inline JS::Scalar::Type Type() const {
    861    MOZ_ASSERT(this->inited());
    862    return mType;
    863  }
    864 };
    865 
    866 using Int8Array = TypedArray<JS::Int8Array>;
    867 using Uint8Array = TypedArray<JS::Uint8Array>;
    868 using Uint8ClampedArray = TypedArray<JS::Uint8ClampedArray>;
    869 using Int16Array = TypedArray<JS::Int16Array>;
    870 using Uint16Array = TypedArray<JS::Uint16Array>;
    871 using Int32Array = TypedArray<JS::Int32Array>;
    872 using Uint32Array = TypedArray<JS::Uint32Array>;
    873 using Float32Array = TypedArray<JS::Float32Array>;
    874 using Float64Array = TypedArray<JS::Float64Array>;
    875 using BigInt64Array = TypedArray<JS::BigInt64Array>;
    876 using BigUint64Array = TypedArray<JS::BigUint64Array>;
    877 using ArrayBufferView = ArrayBufferView_base<JS_GetArrayBufferViewType>;
    878 using ArrayBuffer = TypedArray<JS::ArrayBuffer>;
    879 
    880 // A class for converting an nsTArray to a TypedArray
    881 // Note: A TypedArrayCreator must not outlive the nsTArray it was created from.
    882 //       So this is best used to pass from things that understand nsTArray to
    883 //       things that understand TypedArray, as with ToJSValue.
    884 template <typename TypedArrayType>
    885 class MOZ_STACK_CLASS TypedArrayCreator {
    886  using ValuesType = typename TypedArrayType::element_type;
    887  using ArrayType = nsTArray<ValuesType>;
    888 
    889 public:
    890  explicit TypedArrayCreator(const ArrayType& aArray) : mValues(aArray) {}
    891  explicit TypedArrayCreator(const nsCString& aString) : mValues(aString) {}
    892 
    893  // NOTE: this leaves any exceptions on the JSContext, and the caller is
    894  //       required to deal with them.
    895  JSObject* Create(JSContext* aCx) const {
    896    auto array = TypedArrayType::CreateCommon(aCx, mValues.Length());
    897    if (array) {
    898      TypedArrayType::CopyFrom(aCx, mValues, array);
    899    }
    900    return array.asObject();
    901  }
    902 
    903 private:
    904  Span<const ValuesType> mValues;
    905 };
    906 
    907 namespace binding_detail {
    908 
    909 template <typename Union, typename UnionMemberType, typename = int>
    910 struct ApplyToTypedArray;
    911 
    912 #define APPLY_IMPL(type)                                                       \
    913  template <typename Union>                                                    \
    914  struct ApplyToTypedArray<Union, type, decltype((void)&Union::Is##type, 0)> { \
    915    /* Return type of calling the lambda with a TypedArray 'type'.         */  \
    916    template <typename F>                                                      \
    917    using FunReturnType = decltype(std::declval<F>()(std::declval<type>()));   \
    918                                                                               \
    919    /* Whether the return type of calling the lambda with a TypedArray     */  \
    920    /* 'type' is void. */                                                      \
    921    template <typename F>                                                      \
    922    static constexpr bool FunReturnsVoid =                                     \
    923        std::is_same_v<FunReturnType<F>, void>;                                \
    924                                                                               \
    925    /* The return type of calling Apply with a union that has 'type' as    */  \
    926    /* one of its union member types depends on the return type of         */  \
    927    /* calling the lambda. This return type will be bool if the lambda     */  \
    928    /* returns void, or it will be a Maybe<…> with the inner type being    */  \
    929    /* the actual return type of calling the lambda. If the union          */  \
    930    /* contains a value of the right type, then calling Apply will return  */  \
    931    /* either 'true', or 'Some(…)' containing the return value of calling  */  \
    932    /* the lambda. If the union does not contain a value of the right      */  \
    933    /* type, then calling Apply will return either 'false', or             */  \
    934    /* 'Nothing()'.                                                        */  \
    935    template <typename F>                                                      \
    936    using ApplyReturnType =                                                    \
    937        std::conditional_t<FunReturnsVoid<F>, bool, Maybe<FunReturnType<F>>>;  \
    938                                                                               \
    939   public:                                                                     \
    940    template <typename F>                                                      \
    941    static ApplyReturnType<F> Apply(const Union& aUnion, F&& aFun) {           \
    942      if (!aUnion.Is##type()) {                                                \
    943        return ApplyReturnType<F>(); /* false or Nothing() */                  \
    944      }                                                                        \
    945      if constexpr (FunReturnsVoid<F>) {                                       \
    946        std::forward<F>(aFun)(aUnion.GetAs##type());                           \
    947        return true;                                                           \
    948      } else {                                                                 \
    949        return Some(std::forward<F>(aFun)(aUnion.GetAs##type()));              \
    950      }                                                                        \
    951    }                                                                          \
    952  };
    953 
    954 APPLY_IMPL(Int8Array)
    955 APPLY_IMPL(Uint8Array)
    956 APPLY_IMPL(Uint8ClampedArray)
    957 APPLY_IMPL(Int16Array)
    958 APPLY_IMPL(Uint16Array)
    959 APPLY_IMPL(Int32Array)
    960 APPLY_IMPL(Uint32Array)
    961 APPLY_IMPL(Float32Array)
    962 APPLY_IMPL(Float64Array)
    963 APPLY_IMPL(BigInt64Array)
    964 APPLY_IMPL(BigUint64Array)
    965 APPLY_IMPL(ArrayBufferView)
    966 APPLY_IMPL(ArrayBuffer)
    967 
    968 #undef APPLY_IMPL
    969 
    970 // The binding code generate creates an alias of this type for every WebIDL
    971 // union that contains a typed array type, with the right value for H (which
    972 // will be true if there are non-typedarray types in the union).
    973 template <typename T, bool H, typename FirstUnionMember,
    974          typename... UnionMembers>
    975 struct ApplyToTypedArraysHelper {
    976  static constexpr bool HasNonTypedArrayMembers = H;
    977  template <typename Fun>
    978  static auto Apply(const T& aUnion, Fun&& aFun) {
    979    auto result = ApplyToTypedArray<T, FirstUnionMember>::Apply(
    980        aUnion, std::forward<Fun>(aFun));
    981    if constexpr (sizeof...(UnionMembers) == 0) {
    982      return result;
    983    } else {
    984      if (result) {
    985        return result;
    986      } else {
    987        return ApplyToTypedArraysHelper<T, H, UnionMembers...>::template Apply<
    988            Fun>(aUnion, std::forward<Fun>(aFun));
    989      }
    990    }
    991  }
    992 };
    993 
    994 template <typename T, typename Fun>
    995 auto ApplyToTypedArrays(const T& aUnion, Fun&& aFun) {
    996  using ApplyToTypedArrays = typename T::ApplyToTypedArrays;
    997 
    998  auto result =
    999      ApplyToTypedArrays::template Apply<Fun>(aUnion, std::forward<Fun>(aFun));
   1000  if constexpr (ApplyToTypedArrays::HasNonTypedArrayMembers) {
   1001    return result;
   1002  } else {
   1003    MOZ_ASSERT(result, "Didn't expect union members other than typed arrays");
   1004 
   1005    if constexpr (std::is_same_v<std::remove_cv_t<
   1006                                     std::remove_reference_t<decltype(result)>>,
   1007                                 bool>) {
   1008      return;
   1009    } else {
   1010      return result.extract();
   1011    }
   1012  }
   1013 }
   1014 
   1015 }  // namespace binding_detail
   1016 
   1017 template <typename T, typename ToType,
   1018          std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
   1019 [[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion, ToType& aResult) {
   1020  return binding_detail::ApplyToTypedArrays(
   1021      aUnion, [&](const auto& aTypedArray) {
   1022        return aTypedArray.AppendDataTo(aResult);
   1023      });
   1024 }
   1025 
   1026 template <typename ToType, typename T,
   1027          std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
   1028 [[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion) {
   1029  return binding_detail::ApplyToTypedArrays(
   1030      aUnion, [&](const auto& aTypedArray) {
   1031        return aTypedArray.template CreateFromData<ToType>();
   1032      });
   1033 }
   1034 
   1035 template <typename T, typename Processor,
   1036          std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
   1037 [[nodiscard]] auto ProcessTypedArrays(const T& aUnion, Processor&& aProcessor) {
   1038  return binding_detail::ApplyToTypedArrays(
   1039      aUnion, [&](const auto& aTypedArray) {
   1040        return aTypedArray.ProcessData(std::forward<Processor>(aProcessor));
   1041      });
   1042 }
   1043 
   1044 template <typename T, typename Processor,
   1045          std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0>
   1046 [[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion,
   1047                                           Processor&& aProcessor) {
   1048  return binding_detail::ApplyToTypedArrays(
   1049      aUnion, [&](const auto& aTypedArray) {
   1050        return aTypedArray.ProcessFixedData(
   1051            std::forward<Processor>(aProcessor));
   1052      });
   1053 }
   1054 
   1055 }  // namespace mozilla::dom
   1056 
   1057 #endif /* mozilla_dom_TypedArray_h */