Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / bindings / TypedArray.h
blob36c46f47b15f061707605d957364d2544366e0c9
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
10 #include <string>
11 #include <type_traits>
12 #include <utility>
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
54 * are:
56 * 1) Appending a copy of the data (or of a subset of the data) to a different
57 * data structure
58 * 2) Copying the data (or a subset of the data) into a different data
59 * structure
60 * 3) Creating a new data structure with a copy of the data (or of a subset of
61 * the data)
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&&...
86 * aCalculator) const;
88 * template <typename T, typename... Calculator>
89 * [[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult, Calculator&&...
90 * aCalculator) const;
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&&...
98 * aCalculator) const;
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.
104 * Examples:
106 * Vector<uint32_t> array;
107 * if (!aUint32Array.AppendDataTo(array)) {
108 * aError.ThrowTypeError("Failed to copy data from typed array");
109 * return;
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);
118 * }) {
119 * aError.ThrowTypeError("Failed to copy data from typed array");
120 * return;
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));
132 * })) {
133 * aError.ThrowTypeError("Failed to copy data from typed array");
134 * return;
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
150 * described above.
152 * Examples:
154 * float data[3];
155 * if (!aFloat32Array.CopyDataTo(data)) {
156 * aError.ThrowTypeError("Typed array doesn't contain the right amount"
157 * "of data");
158 * return;
161 * size_t offset;
162 * … // Getting offset value from somewhere.
163 * uint32_t data[3];
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)));
171 * }) {
172 * return;
175 * 3) Creating a new data structure with a copy of the data (or a subset of the
176 * data)
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.
201 * Examples:
203 * Maybe<Buffer<uint8_t>> buffer =
204 * aUint8Array.CreateFromData<Buffer<uint8_t>>();
205 * if (buffer.isNothing()) {
206 * aError.ThrowTypeError("Failed to create a buffer");
207 * return;
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)));
221 * });
222 * if (buffer.isNothing()) {
223 * return;
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
247 * GC is not allowed.
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
270 * that.
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.
282 * Examples:
284 * aUint32Array.ProcessData([] (const Span<uint32_t>& aData,
285 * JS::AutoCheckCannotGC&&) {
286 * for (size_t i = 0; i < aData.Length(); ++i) {
287 * aData[i] = i;
289 * });
291 * aUint32Array.ProcessData([&] (const Span<uint32_t>& aData,
292 * JS::AutoCheckCannotGC&& nogc) {
293 * for (size_t i = 0; i < aData.Length(); ++i) {
294 * if (!aData[i]) {
295 * nogc.reset();
296 * ThrowJSException("Data shouldn't contain 0");
297 * return;
298 * };
299 * DoSomething(aData[i]);
301 * });
303 * uint8_t max = aUint8Array.ProcessData([] (const Span<uint8_t>& aData) {
304 * return std::max_element(aData.cbegin(), aData.cend());
305 * });
307 * aUint8Array.ProcessFixedData([] (const Span<uint8_t>& aData) {
308 * return CallFunctionThatMightGC(aData);
309 * });
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,
324 * ToType& aResult);
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,
341 AllTypedArraysBase {
342 using element_type = typename ArrayT::DataType;
344 TypedArray_base() = default;
345 TypedArray_base(TypedArray_base&& aOther) = default;
347 public:
348 inline bool Init(JSObject* obj) {
349 MOZ_ASSERT(!inited());
350 mImplObj = mWrappedObj = ArrayT::unwrap(obj).asObject();
351 return inited();
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
379 // not detachable.
381 public:
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
395 * copied.
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
455 * argument.
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) {
467 return false;
469 for (size_t i = 0; i < N; ++i) {
470 aResult[i] = aData[i];
472 return true;
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
480 * fails.
482 * See the comments for AppendDataTo for information on the aCalculator
483 * argument.
486 template <typename T, typename... Calculator,
487 typename IsVector =
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())) {
498 return false;
500 aResult.infallibleAppend(aData.Elements(), aData.Length());
501 return true;
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) {
517 aResult =
518 MakeUniqueForOverwriteFallible<element_type[]>(aData.Length());
519 if (!aResult.get()) {
520 return false;
522 memcpy(aResult.get(), aData.Elements(), aData.LengthBytes());
523 return true;
525 std::forward<Calculator>(aCalculator)...);
528 template <typename T, typename... Calculator,
529 typename IsBuffer =
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()) {
542 return false;
544 aResult = buffer.extract();
545 return true;
547 std::forward<Calculator>(aCalculator)...);
550 private:
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) {
560 MOZ_ASSERT(
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) {
574 MOZ_ASSERT(
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),
584 mWasPinned(
585 !JS::PinArrayBufferOrViewLength(aTypedArray->Obj(), true)) {}
586 ~LengthPinner() {
587 if (!mWasPinned) {
588 JS::PinArrayBufferOrViewLength(mTypedArray->Obj(), false);
592 private:
593 const TypedArray_base* mTypedArray;
594 bool mWasPinned;
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;
609 } else {
610 if (offsetAndLength.isNothing()) {
611 return false;
613 std::tie(offset, length) = offsetAndLength.value();
616 return CallProcessorNoGC(data.Subspan(offset, length),
617 std::forward<Processor>(aProcessor),
618 std::move(nogc));
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));
631 public:
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>) {
645 if (!mImplObj) {
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()");
655 #endif
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));
662 if (!view) {
663 if (JSObject* unwrapped = js::CheckedUnwrapStatic(mImplObj)) {
664 if (!js::UnwrapArrayBufferView(unwrapped)) {
665 MOZ_CRASH(
666 "Null "
667 "js::UnwrapArrayBufferView(js::CheckedUnwrapStatic(mImplObj))");
669 view = unwrapped;
670 } else {
671 MOZ_CRASH("Null js::CheckedUnwrapStatic(mImplObj)");
674 if (!JS::IsArrayBufferViewShared(view)) {
675 JSAutoRealm ar(jsapi.cx(), view);
676 bool unused;
677 bool noBuffer;
679 JSObject* buffer =
680 JS_GetArrayBufferViewBuffer(jsapi.cx(), view, &unused);
681 noBuffer = !buffer;
683 if (noBuffer) {
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)
695 .isUndefined() &&
696 length <= JS_MaxMovableTypedArraySize()) {
697 MOZ_CRASH(
698 "We did run out of memory, maybe trying to uninline the "
699 "buffer");
701 if (length < INT32_MAX) {
702 MOZ_CRASH(
703 "We did run out of memory trying to create a buffer "
704 "smaller than 2GB - 1");
705 } else if (length < UINT32_MAX) {
706 MOZ_CRASH(
707 "We did run out of memory trying to create a between 2GB "
708 "and 4GB - 1");
709 } else {
710 MOZ_CRASH(
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) &&
717 exn.isObject()) {
718 JS::Rooted<JSObject*> exnObj(jsapi.cx(), &exn.toObject());
719 JSErrorReport* err =
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()) {
730 MOZ_CRASH(
731 "TypedArrayObject with bufferSlot containing null object");
732 } else {
733 MOZ_CRASH(
734 "JS_GetArrayBufferViewBuffer failed but bufferSlot "
735 "contains a non-null object");
737 } else {
738 MOZ_CRASH(
739 "TypedArrayObject with bufferSlot containing weird value");
741 } else {
742 MOZ_CRASH("JS_GetArrayBufferViewBuffer failed for DataViewObject");
747 #endif
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));
756 private:
757 Span<element_type> GetCurrentData() const {
758 MOZ_ASSERT(inited());
759 MOZ_RELEASE_ASSERT(
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;
766 bool shared;
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");
771 return span;
774 template <typename T, typename F, typename... Calculator>
775 [[nodiscard]] Maybe<T> CreateFromDataHelper(
776 F&& aCreator, Calculator&&... aCalculator) const {
777 Maybe<T> result;
778 bool ok = ProcessDataHelper(
779 [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
780 result.emplace();
781 return aCreator(aData, *result);
783 std::forward<Calculator>(aCalculator)...);
785 if (!ok) {
786 return Nothing();
789 return result;
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();
833 private:
834 template <typename>
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);
850 if (array) {
851 return array;
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,
863 ArrayT& dest) {
864 JS::AutoCheckCannotGC nogc;
865 bool isShared;
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> {
880 private:
881 using Base = TypedArray_base<JS::ArrayBufferView>;
883 public:
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;
891 private:
892 JS::Scalar::Type mType;
894 public:
895 inline bool Init(JSObject* obj) {
896 if (!Base::Init(obj)) {
897 return false;
900 mType = GetViewType(this->Obj());
901 return true;
904 inline JS::Scalar::Type Type() const {
905 MOZ_ASSERT(this->inited());
906 return mType;
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;
930 public:
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());
937 if (array) {
938 TypedArrayType::CopyFrom(aCx, mArray, array);
940 return array.asObject();
943 private:
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 */ \
974 /* 'Nothing()'. */ \
975 template <typename F> \
976 using ApplyReturnType = \
977 std::conditional_t<FunReturnsVoid<F>, bool, Maybe<FunReturnType<F>>>; \
979 public: \
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()); \
987 return true; \
988 } else { \
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)
1006 #undef APPLY_IMPL
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) {
1020 return result;
1021 } else {
1022 if (result) {
1023 return result;
1024 } else {
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;
1036 auto result =
1037 ApplyToTypedArrays::template Apply<Fun>(aUnion, std::forward<Fun>(aFun));
1038 if constexpr (ApplyToTypedArrays::HasNonTypedArrayMembers) {
1039 return result;
1040 } else {
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)>>,
1045 bool>) {
1046 return;
1047 } else {
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 */