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/. */
7 #ifndef mozilla_dom_TypedArray_h
8 #define mozilla_dom_TypedArray_h
11 #include <type_traits>
14 #include "js/ArrayBuffer.h"
15 #include "js/ArrayBufferMaybeShared.h"
16 #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
17 #include "js/GCAPI.h" // JS::AutoCheckCannotGC
18 #include "js/RootingAPI.h" // JS::Rooted
19 #include "js/ScalarType.h" // JS::Scalar::Type
20 #include "js/SharedArrayBuffer.h"
21 #include "js/friend/ErrorMessages.h"
22 #include "mozilla/Attributes.h"
23 #include "mozilla/Buffer.h"
24 #include "mozilla/ErrorResult.h"
25 #include "mozilla/Result.h"
26 #include "mozilla/Vector.h"
27 #include "mozilla/dom/BindingDeclarations.h"
28 #include "mozilla/dom/ScriptSettings.h"
29 #include "mozilla/dom/SpiderMonkeyInterface.h"
30 #include "nsIGlobalObject.h"
31 #include "nsWrapperCache.h"
32 #include "nsWrapperCacheInlines.h"
34 namespace mozilla::dom
{
37 * Various typed array classes for argument conversion. We have a base class
38 * that has a way of initializing a TypedArray from an existing typed array, and
39 * a subclass of the base class that supports creation of a relevant typed array
40 * or array buffer object.
42 * Accessing the data of a TypedArray is tricky. The underlying storage is in a
43 * JS object, which is subject to the JS GC. The memory for the array can be
44 * either inline in the JSObject or stored separately. If the data is stored
45 * inline then the exact location in memory of the data buffer can change as a
46 * result of a JS GC (which is a moving GC). Running JS code can also mutate the
47 * data (including the length). For all these reasons one has to be careful when
48 * holding a pointer to the data or keeping a local copy of the length value. On
49 * the other hand, most code that takes a TypedArray has to access its data at
50 * some point, to process it in some way. The TypedArray class tries to supply a
51 * number of helper APIs, so that the most common cases of processing the data
52 * can be done safely, without having to check the caller very closely for
53 * potential security issues. The main classes of processing TypedArray data
56 * 1) Appending a copy of the data (or of a subset of the data) to a different
58 * 2) Copying the data (or a subset of the data) into a different data
60 * 3) Creating a new data structure with a copy of the data (or of a subset of
62 * 4) Processing the data in some other way
64 * The APIs for the first 3 classes all return a boolean and take an optional
65 * argument named aCalculator. aCalculator should be a lambda taking a size_t
66 * argument which will be passed the total length of the data in the typed
67 * array. aCalculator is allowed to return a std::pair<size_t, size_t> or a
68 * Maybe<std::pair<size_t, size_t>>. The return value should contain the offset
69 * and the length of the subset of the data that should be appended, copied or
70 * used for creating a new datastructure. If the calculation can fail then
71 * aCalculator should return a Maybe<std::pair<size_t, size_t>>, with Nothing()
72 * signaling that the operation should be aborted.
73 * The return value of these APIs will be false if appending, copying or
74 * creating a structure with the new data failed, or if the optional aCalculator
75 * lambda returned Nothing().
77 * Here are the different APIs:
79 * 1) Appending to a different data structure
81 * There are AppendDataTo helpers for nsCString, nsTArray<T>,
82 * FallibleTArray<T> and Vector<T>. The signatures are:
84 * template <typename... Calculator>
85 * [[nodiscard]] bool AppendDataTo(nsCString& aResult, Calculator&&...
88 * template <typename T, typename... Calculator>
89 * [[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult, Calculator&&...
92 * template <typename T, typename... Calculator>
93 * [[nodiscard]] bool AppendDataTo(FallibleTArray<T>& aResult,
94 * Calculator&&... aCalculator) const;
96 * template <typename T, typename... Calculator>
97 * [[nodiscard]] bool AppendDataTo(Vector<T>& aResult, Calculator&&...
100 * The data (or the calculated subset) will be appended to aResult by using
101 * the appropriate fallible API. If the append fails then AppendDataTo will
102 * return false. The aCalculator optional argument is described above.
106 * Vector<uint32_t> array;
107 * if (!aUint32Array.AppendDataTo(array)) {
108 * aError.ThrowTypeError("Failed to copy data from typed array");
112 * size_t offset, length;
113 * … // Getting offset and length values from somewhere.
114 * FallibleTArray<float> array;
115 * if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) {
116 * size_t dataLength = std::min(aLength - offset, length);
117 * return std::make_pair(offset, dataLength);
119 * aError.ThrowTypeError("Failed to copy data from typed array");
123 * size_t offset, length;
124 * … // Getting offset and length values from somewhere.
125 * FallibleTArray<float> array;
126 * if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) {
127 * if (aLength < offset + length) {
128 * return Maybe<std::pair<size_t, size_t>>();
130 * size_t dataLength = std::min(aLength - offset, length);
131 * return Some(std::make_pair(offset, dataLength));
133 * aError.ThrowTypeError("Failed to copy data from typed array");
138 * 2) Copying into a different data structure
140 * There is a CopyDataTo helper for a fixed-size buffer. The signature is:
142 * template <typename T, size_t N, typename... Calculator>
143 * [[nodiscard]] bool CopyDataTo(T (&aResult)[N],
144 * Calculator&&... aCalculator) const;
146 * The data (or the calculated subset) will be copied to aResult, starting
147 * at aResult[0]. If the length of the data to be copied is bigger than the
148 * size of the fixed-size buffer (N) then nothing will be copied and
149 * CopyDataTo will return false. The aCalculator optional argument is
155 * if (!aFloat32Array.CopyDataTo(data)) {
156 * aError.ThrowTypeError("Typed array doesn't contain the right amount"
162 * … // Getting offset value from somewhere.
164 * if (!aUint32Array.CopyDataTo(data, [&](const size_t& aLength) {
165 * if (aLength - offset != ArrayLength(data)) {
166 * aError.ThrowTypeError("Typed array doesn't contain the right"
167 * " amount of data");
168 * return Maybe<std::pair<size_t, size_t>>();
170 * return Some(std::make_pair(offset, ArrayLength(data)));
175 * 3) Creating a new data structure with a copy of the data (or a subset of the
178 * There are CreateFromData helper for creating a Vector<element_type>, a
179 * UniquePtr<element_type[]> and a Buffer<element_type>.
181 * template <typename... Calculator>
182 * [[nodiscard]] Maybe<Vector<element_type>>
183 * CreateFromData<Vector<element_type>>(
184 * Calculator&&... aCalculator) const;
186 * template <typename... Calculator>
187 * [[nodiscard]] Maybe<UniquePtr<element_type[]>>
188 * CreateFromData<UniquePtr<element_type[]>>(
189 * Calculator&&... aCalculator) const;
191 * template <typename... Calculator>
192 * [[nodiscard]] Maybe<Buffer<element_type>>
193 * CreateFromData<Buffer<element_type>>(
194 * Calculator&&... aCalculator) const;
196 * A new container will be created, and the data (or the calculated subset)
197 * will be copied to it. The container will be returned inside a Maybe<…>.
198 * If creating the container with the right size fails then Nothing() will
199 * be returned. The aCalculator optional argument is described above.
203 * Maybe<Buffer<uint8_t>> buffer =
204 * aUint8Array.CreateFromData<Buffer<uint8_t>>();
205 * if (buffer.isNothing()) {
206 * aError.ThrowTypeError("Failed to create a buffer");
210 * size_t offset, length;
211 * … // Getting offset and length values from somewhere.
212 * Maybe<Buffer<uint8_t>> buffer =
213 * aUint8Array.CreateFromData<Buffer<uint8_t>>([&](
214 * const size_t& aLength) {
215 * if (aLength - offset != ArrayLength(data)) {
216 * aError.ThrowTypeError(
217 * "Typed array doesn't contain the right amount" of data");
218 * return Maybe<std::pair<size_t, size_t>>();
220 * return Some(std::make_pair(offset, ArrayLength(data)));
222 * if (buffer.isNothing()) {
226 * 4) Processing the data in some other way
228 * This is the API for when none of the APIs above are appropriate for your
229 * usecase. As these are the most dangerous APIs you really should check
230 * first if you can't use one of the safer alternatives above. The reason
231 * these APIs are more dangerous is because they give access to the typed
232 * array's data directly, and the location of that data can be changed by
233 * the JS GC (due to generational and/or compacting collection). There are
234 * two APIs for processing data:
236 * template <typename Processor>
237 * [[nodiscard]] ProcessReturnType<Processor> ProcessData(
238 * Processor&& aProcessor) const;
240 * template <typename Processor>
241 * [[nodiscard]] ProcessReturnType<Processor> ProcessFixedData(
242 * Processor&& aProcessor) const;
244 * ProcessData will call the lambda with as arguments a |const Span<…>&|
245 * wrapping the data pointer and length for the data in the typed array, and
246 * a |JS::AutoCheckCannotGC&&|. The lambda will execute in a context where
249 * ProcessFixedData will call the lambda with as argument |const Span<…>&|.
250 * For small typed arrays where the data is stored inline in the typed
251 * array, and thus could move during GC, then the data will be copied into a
252 * fresh out-of-line allocation before the lambda is called.
254 * The signature of the lambdas for ProcessData and ProcessFixedData differ
255 * in that the ProcessData lambda will additionally be passed a nogc token
256 * to prevent GC from occurring and invalidating the data. If the processing
257 * you need to do in the lambda can't be proven to not GC then you should
258 * probably use ProcessFixedData instead. There are cases where you need to
259 * do something that can cause a GC but you don't actually need to access
260 * the data anymore. A good example would be throwing a JS exception and
261 * return. For those very specific cases you can call nogc.reset() before
262 * doing anything that causes a GC. Be extra careful to not access the data
263 * after you called nogc.reset().
265 * Extra care must be taken to not let the Span<…> or any pointers derived
266 * from it escape the lambda, as the position in memory of the typed array's
267 * data can change again once we leave the lambda (invalidating the
268 * pointers). The lambda passed to ProcessData is not allowed to do anything
269 * that will trigger a GC, and the GC rooting hazard analysis will enforce
272 * Both ProcessData and ProcessFixedData will pin the typed array's length
273 * while calling the lambda, to block any changes to the length of the data.
274 * Note that this means that the lambda itself isn't allowed to change the
275 * length of the typed array's data. Any attempt to change the length will
276 * throw a JS exception.
278 * The return type of ProcessData and ProcessFixedData depends on the return
279 * type of the lambda, as they forward the return value from the lambda to
280 * the caller of ProcessData or ProcessFixedData.
284 * aUint32Array.ProcessData([] (const Span<uint32_t>& aData,
285 * JS::AutoCheckCannotGC&&) {
286 * for (size_t i = 0; i < aData.Length(); ++i) {
291 * aUint32Array.ProcessData([&] (const Span<uint32_t>& aData,
292 * JS::AutoCheckCannotGC&& nogc) {
293 * for (size_t i = 0; i < aData.Length(); ++i) {
296 * ThrowJSException("Data shouldn't contain 0");
299 * DoSomething(aData[i]);
303 * uint8_t max = aUint8Array.ProcessData([] (const Span<uint8_t>& aData) {
304 * return std::max_element(aData.cbegin(), aData.cend());
307 * aUint8Array.ProcessFixedData([] (const Span<uint8_t>& aData) {
308 * return CallFunctionThatMightGC(aData);
312 * In addition to the above APIs we provide helpers to call them on the typed
313 * array members of WebIDL unions. We have helpers for the 4 different sets of
314 * APIs above. The return value of the helpers depends on whether the union can
315 * contain a type other than a typed array. If the union can't contain a type
316 * other than a typed array then the return type is simply the type returned by
317 * the corresponding API above. If the union can contain a type other than a
318 * typed array then the return type of the helper is a Maybe<…> wrapping the
319 * actual return type, with Nothing() signifying that the union contained a
320 * non-typed array value.
322 * template <typename ToType, typename T>
323 * [[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion,
326 * template <typename ToType, typename T>
327 * [[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion);
329 * template <typename T, typename Processor>
330 * [[nodiscard]] auto ProcessTypedArrays(
331 * const T& aUnion, Processor&& aProcessor);
333 * template <typename T, typename Processor>
334 * [[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion,
335 * Processor&& aProcessor);
339 template <class ArrayT
>
340 struct TypedArray_base
: public SpiderMonkeyInterfaceObjectStorage
,
342 using element_type
= typename
ArrayT::DataType
;
344 TypedArray_base() = default;
345 TypedArray_base(TypedArray_base
&& aOther
) = default;
348 inline bool Init(JSObject
* obj
) {
349 MOZ_ASSERT(!inited());
350 mImplObj
= mWrappedObj
= ArrayT::unwrap(obj
).asObject();
354 // About shared memory:
356 // Any DOM TypedArray as well as any DOM ArrayBufferView can map the
357 // memory of either a JS ArrayBuffer or a JS SharedArrayBuffer.
359 // Code that elects to allow views that map shared memory to be used
360 // -- ie, code that "opts in to shared memory" -- should generally
361 // not access the raw data buffer with standard C++ mechanisms as
362 // that creates the possibility of C++ data races, which is
363 // undefined behavior. The JS engine will eventually export (bug
364 // 1225033) a suite of methods that avoid undefined behavior.
366 // Callers of Obj() that do not opt in to shared memory can produce
367 // better diagnostics by checking whether the JSObject in fact maps
368 // shared memory and throwing an error if it does. However, it is
369 // safe to use the value of Obj() without such checks.
371 // The DOM TypedArray abstraction prevents the underlying buffer object
372 // from being accessed directly, but JS_GetArrayBufferViewBuffer(Obj())
373 // will obtain the buffer object. Code that calls that function must
374 // not assume the returned buffer is an ArrayBuffer. That is guarded
375 // against by an out parameter on that call that communicates the
376 // sharedness of the buffer.
378 // Finally, note that the buffer memory of a SharedArrayBuffer is
383 * Helper functions to append a copy of this typed array's data to a
384 * container. Returns false if the allocation for copying the data fails.
386 * aCalculator is an optional argument to which one can pass a lambda
387 * expression that will calculate the offset and length of the data to copy
388 * out of the typed array. aCalculator will be called with one argument of
389 * type size_t set to the length of the data in the typed array. It is allowed
390 * to return a std::pair<size_t, size_t> or a Maybe<std::pair<size_t, size_t>>
391 * containing the offset and the length of the subset of the data that we
392 * should copy. If the calculation can fail then aCalculator should return a
393 * Maybe<std::pair<size_t, size_t>>, if .isNothing() returns true for the
394 * return value then AppendDataTo will return false and the data won't be
398 template <typename
... Calculator
>
399 [[nodiscard
]] bool AppendDataTo(nsCString
& aResult
,
400 Calculator
&&... aCalculator
) const {
401 static_assert(sizeof...(aCalculator
) <= 1,
402 "AppendDataTo takes at most one aCalculator");
404 return ProcessDataHelper(
405 [&](const Span
<const element_type
>& aData
, JS::AutoCheckCannotGC
&&) {
406 return aResult
.Append(aData
, fallible
);
408 std::forward
<Calculator
>(aCalculator
)...);
411 template <typename T
, typename
... Calculator
>
412 [[nodiscard
]] bool AppendDataTo(nsTArray
<T
>& aResult
,
413 Calculator
&&... aCalculator
) const {
414 static_assert(sizeof...(aCalculator
) <= 1,
415 "AppendDataTo takes at most one aCalculator");
417 return ProcessDataHelper(
418 [&](const Span
<const element_type
>& aData
, JS::AutoCheckCannotGC
&&) {
419 return aResult
.AppendElements(aData
, fallible
);
421 std::forward
<Calculator
>(aCalculator
)...);
424 template <typename T
, typename
... Calculator
>
425 [[nodiscard
]] bool AppendDataTo(FallibleTArray
<T
>& aResult
,
426 Calculator
&&... aCalculator
) const {
427 static_assert(sizeof...(aCalculator
) <= 1,
428 "AppendDataTo takes at most one aCalculator");
430 return ProcessDataHelper(
431 [&](const Span
<const element_type
>& aData
, JS::AutoCheckCannotGC
&&) {
432 return aResult
.AppendElements(aData
, fallible
);
434 std::forward
<Calculator
>(aCalculator
)...);
437 template <typename T
, typename
... Calculator
>
438 [[nodiscard
]] bool AppendDataTo(Vector
<T
>& aResult
,
439 Calculator
&&... aCalculator
) const {
440 static_assert(sizeof...(aCalculator
) <= 1,
441 "AppendDataTo takes at most one aCalculator");
443 return ProcessDataHelper(
444 [&](const Span
<const element_type
>& aData
, JS::AutoCheckCannotGC
&&) {
445 return aResult
.append(aData
.Elements(), aData
.Length());
447 std::forward
<Calculator
>(aCalculator
)...);
451 * Helper functions to copy this typed array's data to a container. This will
452 * clear any existing data in the container.
454 * See the comments for AppendDataTo for information on the aCalculator
458 template <typename T
, size_t N
, typename
... Calculator
>
459 [[nodiscard
]] bool CopyDataTo(T (&aResult
)[N
],
460 Calculator
&&... aCalculator
) const {
461 static_assert(sizeof...(aCalculator
) <= 1,
462 "CopyDataTo takes at most one aCalculator");
464 return ProcessDataHelper(
465 [&](const Span
<const element_type
>& aData
, JS::AutoCheckCannotGC
&&) {
466 if (aData
.Length() != N
) {
469 for (size_t i
= 0; i
< N
; ++i
) {
470 aResult
[i
] = aData
[i
];
474 std::forward
<Calculator
>(aCalculator
)...);
478 * Helper functions to copy this typed array's data to a newly created
479 * container. Returns Nothing() if creating the container with the right size
482 * See the comments for AppendDataTo for information on the aCalculator
486 template <typename T
, typename
... Calculator
,
488 std::enable_if_t
<std::is_same_v
<Vector
<element_type
>, T
>>>
489 [[nodiscard
]] Maybe
<Vector
<element_type
>> CreateFromData(
490 Calculator
&&... aCalculator
) const {
491 static_assert(sizeof...(aCalculator
) <= 1,
492 "CreateFromData takes at most one aCalculator");
494 return CreateFromDataHelper
<T
>(
495 [&](const Span
<const element_type
>& aData
,
496 Vector
<element_type
>& aResult
) {
497 if (!aResult
.initCapacity(aData
.Length())) {
500 aResult
.infallibleAppend(aData
.Elements(), aData
.Length());
503 std::forward
<Calculator
>(aCalculator
)...);
506 template <typename T
, typename
... Calculator
,
507 typename IsUniquePtr
=
508 std::enable_if_t
<std::is_same_v
<T
, UniquePtr
<element_type
[]>>>>
509 [[nodiscard
]] Maybe
<UniquePtr
<element_type
[]>> CreateFromData(
510 Calculator
&&... aCalculator
) const {
511 static_assert(sizeof...(aCalculator
) <= 1,
512 "CreateFromData takes at most one aCalculator");
514 return CreateFromDataHelper
<T
>(
515 [&](const Span
<const element_type
>& aData
,
516 UniquePtr
<element_type
[]>& aResult
) {
518 MakeUniqueForOverwriteFallible
<element_type
[]>(aData
.Length());
519 if (!aResult
.get()) {
522 memcpy(aResult
.get(), aData
.Elements(), aData
.LengthBytes());
525 std::forward
<Calculator
>(aCalculator
)...);
528 template <typename T
, typename
... Calculator
,
530 std::enable_if_t
<std::is_same_v
<T
, Buffer
<element_type
>>>>
531 [[nodiscard
]] Maybe
<Buffer
<element_type
>> CreateFromData(
532 Calculator
&&... aCalculator
) const {
533 static_assert(sizeof...(aCalculator
) <= 1,
534 "CreateFromData takes at most one aCalculator");
536 return CreateFromDataHelper
<T
>(
537 [&](const Span
<const element_type
>& aData
,
538 Buffer
<element_type
>& aResult
) {
539 Maybe
<Buffer
<element_type
>> buffer
=
540 Buffer
<element_type
>::CopyFrom(aData
);
541 if (buffer
.isNothing()) {
544 aResult
= buffer
.extract();
547 std::forward
<Calculator
>(aCalculator
)...);
551 template <typename Processor
, typename R
= decltype(std::declval
<Processor
>()(
552 std::declval
<Span
<element_type
>>(),
553 std::declval
<JS::AutoCheckCannotGC
>()))>
554 using ProcessNoGCReturnType
= R
;
556 template <typename Processor
>
557 [[nodiscard
]] static inline ProcessNoGCReturnType
<Processor
>
558 CallProcessorNoGC(const Span
<element_type
>& aData
, Processor
&& aProcessor
,
559 JS::AutoCheckCannotGC
&& nogc
) {
561 aData
.IsEmpty() || aData
.Elements(),
562 "We expect a non-null data pointer for typed arrays that aren't empty");
564 return aProcessor(aData
, std::move(nogc
));
567 template <typename Processor
, typename R
= decltype(std::declval
<Processor
>()(
568 std::declval
<Span
<element_type
>>()))>
569 using ProcessReturnType
= R
;
571 template <typename Processor
>
572 [[nodiscard
]] static inline ProcessReturnType
<Processor
> CallProcessor(
573 const Span
<element_type
>& aData
, Processor
&& aProcessor
) {
575 aData
.IsEmpty() || aData
.Elements(),
576 "We expect a non-null data pointer for typed arrays that aren't empty");
578 return aProcessor(aData
);
581 struct MOZ_STACK_CLASS LengthPinner
{
582 explicit LengthPinner(const TypedArray_base
* aTypedArray
)
583 : mTypedArray(aTypedArray
),
585 !JS::PinArrayBufferOrViewLength(aTypedArray
->Obj(), true)) {}
588 JS::PinArrayBufferOrViewLength(mTypedArray
->Obj(), false);
593 const TypedArray_base
* mTypedArray
;
597 template <typename Processor
, typename Calculator
>
598 [[nodiscard
]] bool ProcessDataHelper(
599 Processor
&& aProcessor
, Calculator
&& aCalculateOffsetAndLength
) const {
600 LengthPinner
pinner(this);
602 JS::AutoCheckCannotGC nogc
; // `data` is GC-sensitive.
603 Span
<element_type
> data
= GetCurrentData();
604 const auto& offsetAndLength
= aCalculateOffsetAndLength(data
.Length());
605 size_t offset
, length
;
606 if constexpr (std::is_convertible_v
<decltype(offsetAndLength
),
607 std::pair
<size_t, size_t>>) {
608 std::tie(offset
, length
) = offsetAndLength
;
610 if (offsetAndLength
.isNothing()) {
613 std::tie(offset
, length
) = offsetAndLength
.value();
616 return CallProcessorNoGC(data
.Subspan(offset
, length
),
617 std::forward
<Processor
>(aProcessor
),
621 template <typename Processor
>
622 [[nodiscard
]] ProcessNoGCReturnType
<Processor
> ProcessDataHelper(
623 Processor
&& aProcessor
) const {
624 LengthPinner
pinner(this);
625 // The data from GetCurrentData() is GC sensitive.
626 JS::AutoCheckCannotGC nogc
;
627 return CallProcessorNoGC(
628 GetCurrentData(), std::forward
<Processor
>(aProcessor
), std::move(nogc
));
632 template <typename Processor
>
633 [[nodiscard
]] ProcessNoGCReturnType
<Processor
> ProcessData(
634 Processor
&& aProcessor
) const {
635 return ProcessDataHelper(std::forward
<Processor
>(aProcessor
));
638 template <typename Processor
>
639 [[nodiscard
]] ProcessReturnType
<Processor
> ProcessFixedData(
640 Processor
&& aProcessor
) const {
641 mozilla::dom::AutoJSAPI jsapi
;
642 if (!jsapi
.Init(mImplObj
)) {
643 #if defined(EARLY_BETA_OR_EARLIER)
644 if constexpr (std::is_same_v
<ArrayT
, JS::ArrayBufferView
>) {
646 MOZ_CRASH("Null mImplObj");
648 if (!xpc::NativeGlobal(mImplObj
)) {
649 MOZ_CRASH("Null xpc::NativeGlobal(mImplObj)");
651 if (!xpc::NativeGlobal(mImplObj
)->GetGlobalJSObject()) {
652 MOZ_CRASH("Null xpc::NativeGlobal(mImplObj)->GetGlobalJSObject()");
656 MOZ_CRASH("Failed to get JSContext");
658 #if defined(EARLY_BETA_OR_EARLIER)
659 if constexpr (std::is_same_v
<ArrayT
, JS::ArrayBufferView
>) {
660 JS::Rooted
<JSObject
*> view(jsapi
.cx(),
661 js::UnwrapArrayBufferView(mImplObj
));
663 if (JSObject
* unwrapped
= js::CheckedUnwrapStatic(mImplObj
)) {
664 if (!js::UnwrapArrayBufferView(unwrapped
)) {
667 "js::UnwrapArrayBufferView(js::CheckedUnwrapStatic(mImplObj))");
671 MOZ_CRASH("Null js::CheckedUnwrapStatic(mImplObj)");
674 if (!JS::IsArrayBufferViewShared(view
)) {
675 JSAutoRealm
ar(jsapi
.cx(), view
);
680 JS_GetArrayBufferViewBuffer(jsapi
.cx(), view
, &unused
);
684 if (JS_IsTypedArrayObject(view
)) {
685 JS::Value bufferSlot
=
686 JS::GetReservedSlot(view
, /* BUFFER_SLOT */ 0);
687 if (bufferSlot
.isNull()) {
688 MOZ_CRASH("TypedArrayObject with bufferSlot containing null");
689 } else if (bufferSlot
.isBoolean()) {
690 // If we're here then TypedArrayObject::ensureHasBuffer must have
691 // failed in the call to JS_GetArrayBufferViewBuffer.
692 if (JS_IsThrowingOutOfMemory(jsapi
.cx())) {
693 size_t length
= JS_GetTypedArrayByteLength(view
);
694 if (!JS::GetReservedSlot(view
, /* DATA_SLOT */ 3)
696 length
<= JS_MaxMovableTypedArraySize()) {
698 "We did run out of memory, maybe trying to uninline the "
701 if (length
< INT32_MAX
) {
703 "We did run out of memory trying to create a buffer "
704 "smaller than 2GB - 1");
705 } else if (length
< UINT32_MAX
) {
707 "We did run out of memory trying to create a between 2GB "
711 "We did run out of memory trying to create a buffer "
712 "bigger than 4GB - 1");
714 } else if (JS_IsExceptionPending(jsapi
.cx())) {
715 JS::Rooted
<JS::Value
> exn(jsapi
.cx());
716 if (JS_GetPendingException(jsapi
.cx(), &exn
) &&
718 JS::Rooted
<JSObject
*> exnObj(jsapi
.cx(), &exn
.toObject());
720 JS_ErrorFromException(jsapi
.cx(), exnObj
);
721 if (err
&& err
->errorNumber
== JSMSG_BAD_ARRAY_LENGTH
) {
722 MOZ_CRASH("Length was too big");
726 // Did ArrayBufferObject::createBufferAndData fail without OOM?
727 MOZ_CRASH("TypedArrayObject with bufferSlot containing boolean");
728 } else if (bufferSlot
.isObject()) {
729 if (!bufferSlot
.toObjectOrNull()) {
731 "TypedArrayObject with bufferSlot containing null object");
734 "JS_GetArrayBufferViewBuffer failed but bufferSlot "
735 "contains a non-null object");
739 "TypedArrayObject with bufferSlot containing weird value");
742 MOZ_CRASH("JS_GetArrayBufferViewBuffer failed for DataViewObject");
748 if (!JS::EnsureNonInlineArrayBufferOrView(jsapi
.cx(), mImplObj
)) {
749 MOZ_CRASH("small oom when moving inline data out-of-line");
751 LengthPinner
pinner(this);
753 return CallProcessor(GetCurrentData(), std::forward
<Processor
>(aProcessor
));
757 Span
<element_type
> GetCurrentData() const {
758 MOZ_ASSERT(inited());
760 !ArrayT::fromObject(mImplObj
).isResizable(),
761 "Bindings must have checked ArrayBuffer{View} is non-resizable");
763 // Intentionally return a pointer and length that escape from a nogc region.
764 // Private so it can only be used in very limited situations.
765 JS::AutoCheckCannotGC nogc
;
767 Span
<element_type
> span
=
768 ArrayT::fromObject(mImplObj
).getData(&shared
, nogc
);
769 MOZ_RELEASE_ASSERT(span
.Length() <= INT32_MAX
,
770 "Bindings must have checked ArrayBuffer{View} length");
774 template <typename T
, typename F
, typename
... Calculator
>
775 [[nodiscard
]] Maybe
<T
> CreateFromDataHelper(
776 F
&& aCreator
, Calculator
&&... aCalculator
) const {
778 bool ok
= ProcessDataHelper(
779 [&](const Span
<const element_type
>& aData
, JS::AutoCheckCannotGC
&&) {
781 return aCreator(aData
, *result
);
783 std::forward
<Calculator
>(aCalculator
)...);
792 TypedArray_base(const TypedArray_base
&) = delete;
795 template <class ArrayT
>
796 struct TypedArray
: public TypedArray_base
<ArrayT
> {
797 using Base
= TypedArray_base
<ArrayT
>;
798 using element_type
= typename
Base::element_type
;
800 TypedArray() = default;
802 TypedArray(TypedArray
&& aOther
) = default;
804 static inline JSObject
* Create(JSContext
* cx
, nsWrapperCache
* creator
,
805 size_t length
, ErrorResult
& error
) {
806 return CreateCommon(cx
, creator
, length
, error
).asObject();
809 static inline JSObject
* Create(JSContext
* cx
, size_t length
,
810 ErrorResult
& error
) {
811 return CreateCommon(cx
, length
, error
).asObject();
814 static inline JSObject
* Create(JSContext
* cx
, nsWrapperCache
* creator
,
815 Span
<const element_type
> data
,
816 ErrorResult
& error
) {
817 ArrayT array
= CreateCommon(cx
, creator
, data
.Length(), error
);
818 if (!error
.Failed() && !data
.IsEmpty()) {
819 CopyFrom(cx
, data
, array
);
821 return array
.asObject();
824 static inline JSObject
* Create(JSContext
* cx
, Span
<const element_type
> data
,
825 ErrorResult
& error
) {
826 ArrayT array
= CreateCommon(cx
, data
.Length(), error
);
827 if (!error
.Failed() && !data
.IsEmpty()) {
828 CopyFrom(cx
, data
, array
);
830 return array
.asObject();
835 friend class TypedArrayCreator
;
837 static inline ArrayT
CreateCommon(JSContext
* cx
, nsWrapperCache
* creator
,
838 size_t length
, ErrorResult
& error
) {
839 JS::Rooted
<JSObject
*> creatorWrapper(cx
);
840 Maybe
<JSAutoRealm
> ar
;
841 if (creator
&& (creatorWrapper
= creator
->GetWrapperPreserveColor())) {
842 ar
.emplace(cx
, creatorWrapper
);
845 return CreateCommon(cx
, length
, error
);
847 static inline ArrayT
CreateCommon(JSContext
* cx
, size_t length
,
848 ErrorResult
& error
) {
849 ArrayT array
= CreateCommon(cx
, length
);
853 error
.StealExceptionFromJSContext(cx
);
854 return ArrayT::fromObject(nullptr);
856 // NOTE: this leaves any exceptions on the JSContext, and the caller is
857 // required to deal with them.
858 static inline ArrayT
CreateCommon(JSContext
* cx
, size_t length
) {
859 return ArrayT::create(cx
, length
);
861 static inline void CopyFrom(JSContext
* cx
,
862 const Span
<const element_type
>& data
,
864 JS::AutoCheckCannotGC nogc
;
866 mozilla::Span
<element_type
> span
= dest
.getData(&isShared
, nogc
);
867 MOZ_ASSERT(span
.size() == data
.size(),
868 "Didn't create a large enough typed array object?");
869 // Data will not be shared, until a construction protocol exists
870 // for constructing shared data.
871 MOZ_ASSERT(!isShared
);
872 memcpy(span
.Elements(), data
.Elements(), data
.LengthBytes());
875 TypedArray(const TypedArray
&) = delete;
878 template <JS::Scalar::Type
GetViewType(JSObject
*)>
879 struct ArrayBufferView_base
: public TypedArray_base
<JS::ArrayBufferView
> {
881 using Base
= TypedArray_base
<JS::ArrayBufferView
>;
884 ArrayBufferView_base() : Base(), mType(JS::Scalar::MaxTypedArrayViewType
) {}
886 ArrayBufferView_base(ArrayBufferView_base
&& aOther
)
887 : Base(std::move(aOther
)), mType(aOther
.mType
) {
888 aOther
.mType
= JS::Scalar::MaxTypedArrayViewType
;
892 JS::Scalar::Type mType
;
895 inline bool Init(JSObject
* obj
) {
896 if (!Base::Init(obj
)) {
900 mType
= GetViewType(this->Obj());
904 inline JS::Scalar::Type
Type() const {
905 MOZ_ASSERT(this->inited());
910 using Int8Array
= TypedArray
<JS::Int8Array
>;
911 using Uint8Array
= TypedArray
<JS::Uint8Array
>;
912 using Uint8ClampedArray
= TypedArray
<JS::Uint8ClampedArray
>;
913 using Int16Array
= TypedArray
<JS::Int16Array
>;
914 using Uint16Array
= TypedArray
<JS::Uint16Array
>;
915 using Int32Array
= TypedArray
<JS::Int32Array
>;
916 using Uint32Array
= TypedArray
<JS::Uint32Array
>;
917 using Float32Array
= TypedArray
<JS::Float32Array
>;
918 using Float64Array
= TypedArray
<JS::Float64Array
>;
919 using ArrayBufferView
= ArrayBufferView_base
<JS_GetArrayBufferViewType
>;
920 using ArrayBuffer
= TypedArray
<JS::ArrayBuffer
>;
922 // A class for converting an nsTArray to a TypedArray
923 // Note: A TypedArrayCreator must not outlive the nsTArray it was created from.
924 // So this is best used to pass from things that understand nsTArray to
925 // things that understand TypedArray, as with ToJSValue.
926 template <typename TypedArrayType
>
927 class MOZ_STACK_CLASS TypedArrayCreator
{
928 typedef nsTArray
<typename
TypedArrayType::element_type
> ArrayType
;
931 explicit TypedArrayCreator(const ArrayType
& aArray
) : mArray(aArray
) {}
933 // NOTE: this leaves any exceptions on the JSContext, and the caller is
934 // required to deal with them.
935 JSObject
* Create(JSContext
* aCx
) const {
936 auto array
= TypedArrayType::CreateCommon(aCx
, mArray
.Length());
938 TypedArrayType::CopyFrom(aCx
, mArray
, array
);
940 return array
.asObject();
944 const ArrayType
& mArray
;
947 namespace binding_detail
{
949 template <typename Union
, typename UnionMemberType
, typename
= int>
950 struct ApplyToTypedArray
;
952 #define APPLY_IMPL(type) \
953 template <typename Union> \
954 struct ApplyToTypedArray<Union, type, decltype((void)&Union::Is##type, 0)> { \
955 /* Return type of calling the lambda with a TypedArray 'type'. */ \
956 template <typename F> \
957 using FunReturnType = decltype(std::declval<F>()(std::declval<type>())); \
959 /* Whether the return type of calling the lambda with a TypedArray */ \
960 /* 'type' is void. */ \
961 template <typename F> \
962 static constexpr bool FunReturnsVoid = \
963 std::is_same_v<FunReturnType<F>, void>; \
965 /* The return type of calling Apply with a union that has 'type' as */ \
966 /* one of its union member types depends on the return type of */ \
967 /* calling the lambda. This return type will be bool if the lambda */ \
968 /* returns void, or it will be a Maybe<…> with the inner type being */ \
969 /* the actual return type of calling the lambda. If the union */ \
970 /* contains a value of the right type, then calling Apply will return */ \
971 /* either 'true', or 'Some(…)' containing the return value of calling */ \
972 /* the lambda. If the union does not contain a value of the right */ \
973 /* type, then calling Apply will return either 'false', or */ \
975 template <typename F> \
976 using ApplyReturnType = \
977 std::conditional_t<FunReturnsVoid<F>, bool, Maybe<FunReturnType<F>>>; \
980 template <typename F> \
981 static ApplyReturnType<F> Apply(const Union& aUnion, F&& aFun) { \
982 if (!aUnion.Is##type()) { \
983 return ApplyReturnType<F>(); /* false or Nothing() */ \
985 if constexpr (FunReturnsVoid<F>) { \
986 std::forward<F>(aFun)(aUnion.GetAs##type()); \
989 return Some(std::forward<F>(aFun)(aUnion.GetAs##type())); \
994 APPLY_IMPL(Int8Array
)
995 APPLY_IMPL(Uint8Array
)
996 APPLY_IMPL(Uint8ClampedArray
)
997 APPLY_IMPL(Int16Array
)
998 APPLY_IMPL(Uint16Array
)
999 APPLY_IMPL(Int32Array
)
1000 APPLY_IMPL(Uint32Array
)
1001 APPLY_IMPL(Float32Array
)
1002 APPLY_IMPL(Float64Array
)
1003 APPLY_IMPL(ArrayBufferView
)
1004 APPLY_IMPL(ArrayBuffer
)
1008 // The binding code generate creates an alias of this type for every WebIDL
1009 // union that contains a typed array type, with the right value for H (which
1010 // will be true if there are non-typedarray types in the union).
1011 template <typename T
, bool H
, typename FirstUnionMember
,
1012 typename
... UnionMembers
>
1013 struct ApplyToTypedArraysHelper
{
1014 static constexpr bool HasNonTypedArrayMembers
= H
;
1015 template <typename Fun
>
1016 static auto Apply(const T
& aUnion
, Fun
&& aFun
) {
1017 auto result
= ApplyToTypedArray
<T
, FirstUnionMember
>::Apply(
1018 aUnion
, std::forward
<Fun
>(aFun
));
1019 if constexpr (sizeof...(UnionMembers
) == 0) {
1025 return ApplyToTypedArraysHelper
<T
, H
, UnionMembers
...>::template Apply
<
1026 Fun
>(aUnion
, std::forward
<Fun
>(aFun
));
1032 template <typename T
, typename Fun
>
1033 auto ApplyToTypedArrays(const T
& aUnion
, Fun
&& aFun
) {
1034 using ApplyToTypedArrays
= typename
T::ApplyToTypedArrays
;
1037 ApplyToTypedArrays::template Apply
<Fun
>(aUnion
, std::forward
<Fun
>(aFun
));
1038 if constexpr (ApplyToTypedArrays::HasNonTypedArrayMembers
) {
1041 MOZ_ASSERT(result
, "Didn't expect union members other than typed arrays");
1043 if constexpr (std::is_same_v
<std::remove_cv_t
<
1044 std::remove_reference_t
<decltype(result
)>>,
1048 return result
.extract();
1053 } // namespace binding_detail
1055 template <typename T
, typename ToType
,
1056 std::enable_if_t
<is_dom_union_with_typedarray_members
<T
>, int> = 0>
1057 [[nodiscard
]] auto AppendTypedArrayDataTo(const T
& aUnion
, ToType
& aResult
) {
1058 return binding_detail::ApplyToTypedArrays(
1059 aUnion
, [&](const auto& aTypedArray
) {
1060 return aTypedArray
.AppendDataTo(aResult
);
1064 template <typename ToType
, typename T
,
1065 std::enable_if_t
<is_dom_union_with_typedarray_members
<T
>, int> = 0>
1066 [[nodiscard
]] auto CreateFromTypedArrayData(const T
& aUnion
) {
1067 return binding_detail::ApplyToTypedArrays(
1068 aUnion
, [&](const auto& aTypedArray
) {
1069 return aTypedArray
.template CreateFromData
<ToType
>();
1073 template <typename T
, typename Processor
,
1074 std::enable_if_t
<is_dom_union_with_typedarray_members
<T
>, int> = 0>
1075 [[nodiscard
]] auto ProcessTypedArrays(const T
& aUnion
, Processor
&& aProcessor
) {
1076 return binding_detail::ApplyToTypedArrays(
1077 aUnion
, [&](const auto& aTypedArray
) {
1078 return aTypedArray
.ProcessData(std::forward
<Processor
>(aProcessor
));
1082 template <typename T
, typename Processor
,
1083 std::enable_if_t
<is_dom_union_with_typedarray_members
<T
>, int> = 0>
1084 [[nodiscard
]] auto ProcessTypedArraysFixed(const T
& aUnion
,
1085 Processor
&& aProcessor
) {
1086 return binding_detail::ApplyToTypedArrays(
1087 aUnion
, [&](const auto& aTypedArray
) {
1088 return aTypedArray
.ProcessFixedData(
1089 std::forward
<Processor
>(aProcessor
));
1093 } // namespace mozilla::dom
1095 #endif /* mozilla_dom_TypedArray_h */