Skip to main content

script_bindings/
buffer_source.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![expect(unsafe_code)]
6
7use std::marker::PhantomData;
8use std::ptr;
9
10use js::context::{JSContext, NoGC};
11use js::jsapi::{
12    GetArrayBufferByteLength, Heap, IsArrayBufferObject, IsDetachedArrayBufferObject,
13    JS_GetArrayBufferViewByteLength, JS_GetArrayBufferViewByteOffset, JS_GetArrayBufferViewType,
14    JS_GetTypedArrayLength, JS_IsArrayBufferViewObject, JS_IsTypedArrayObject, JSObject, Type,
15};
16use js::jsval::{ObjectValue, UndefinedValue};
17use js::rust::wrappers2::{
18    ArrayBufferClone, ArrayBufferCopyData, DetachArrayBuffer, HasDefinedArrayBufferDetachKey,
19    JS_ClearPendingException, JS_GetArrayBufferViewBuffer, JS_GetPendingException,
20    JS_NewBigInt64ArrayWithBuffer, JS_NewBigUint64ArrayWithBuffer, JS_NewDataView,
21    JS_NewFloat16ArrayWithBuffer, JS_NewFloat32ArrayWithBuffer, JS_NewFloat64ArrayWithBuffer,
22    JS_NewInt8ArrayWithBuffer, JS_NewInt16ArrayWithBuffer, JS_NewInt32ArrayWithBuffer,
23    JS_NewUint8ArrayWithBuffer, JS_NewUint8ClampedArrayWithBuffer, JS_NewUint16ArrayWithBuffer,
24    JS_NewUint32ArrayWithBuffer, NewArrayBuffer, NewArrayBufferWithContents,
25    StealArrayBufferContents,
26};
27use js::rust::{
28    CustomAutoRooterGuard, Handle, MutableHandleObject,
29    MutableHandleValue as SafeMutableHandleValue, Trace,
30};
31use js::typedarray::{
32    ArrayBufferU8, ArrayBufferViewU8, CreateWith, TypedArray, TypedArrayElement,
33    TypedArrayElementCreator,
34};
35
36use crate::codegen::GenericUnionTypes::ArrayBufferViewOrArrayBuffer;
37use crate::error::{Error, Fallible};
38use crate::trace::RootedTraceableBox;
39
40pub(crate) type RootedTypedArray<T> = RootedTraceableBox<TypedArray<T, Box<Heap<*mut JSObject>>>>;
41
42/// Represents a `BufferSource` as defined in the WebIDL specification.
43///
44/// A `BufferSource` is either an `ArrayBuffer` or an `ArrayBufferView`, which
45/// provides a view onto an `ArrayBuffer`.
46///
47/// See: <https://webidl.spec.whatwg.org/#BufferSource>
48#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
49pub(crate) enum BufferSource {
50    /// Represents an `ArrayBufferView` (e.g., `Uint8Array`, `DataView`).
51    /// See: <https://webidl.spec.whatwg.org/#ArrayBufferView>
52    ArrayBufferView(Box<Heap<*mut JSObject>>),
53
54    /// Represents an `ArrayBuffer`, a fixed-length binary data buffer.
55    /// See: <https://webidl.spec.whatwg.org/#idl-ArrayBuffer>
56    ArrayBuffer(Box<Heap<*mut JSObject>>),
57}
58
59impl Clone for BufferSource {
60    fn clone(&self) -> Self {
61        match self {
62            BufferSource::ArrayBufferView(heap) => {
63                BufferSource::ArrayBufferView(Heap::boxed(heap.get()))
64            },
65            BufferSource::ArrayBuffer(heap) => BufferSource::ArrayBuffer(Heap::boxed(heap.get())),
66        }
67    }
68}
69
70pub enum ArrayBufferViewOrArrayBufferRef<'a> {
71    ArrayBufferView(&'a RootedTypedArray<ArrayBufferViewU8>),
72    ArrayBuffer(&'a RootedTypedArray<ArrayBufferU8>),
73}
74
75impl<'a> From<&'a RootedTypedArray<ArrayBufferViewU8>> for ArrayBufferViewOrArrayBufferRef<'a> {
76    fn from(view: &'a RootedTypedArray<ArrayBufferViewU8>) -> Self {
77        ArrayBufferViewOrArrayBufferRef::ArrayBufferView(view)
78    }
79}
80
81impl<'a> From<&'a RootedTypedArray<ArrayBufferU8>> for ArrayBufferViewOrArrayBufferRef<'a> {
82    fn from(buffer: &'a RootedTypedArray<ArrayBufferU8>) -> Self {
83        ArrayBufferViewOrArrayBufferRef::ArrayBuffer(buffer)
84    }
85}
86
87impl<'a> From<&'a ArrayBufferViewOrArrayBuffer> for ArrayBufferViewOrArrayBufferRef<'a> {
88    fn from(view_or_buffer: &'a ArrayBufferViewOrArrayBuffer) -> Self {
89        match view_or_buffer {
90            ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => {
91                ArrayBufferViewOrArrayBufferRef::ArrayBufferView(view)
92            },
93            ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => {
94                ArrayBufferViewOrArrayBufferRef::ArrayBuffer(buffer)
95            },
96        }
97    }
98}
99
100/// <https://webidl.spec.whatwg.org/#dfn-get-buffer-source-copy>
101///
102/// Spec steps and how they're covered:
103///
104/// - **Convert to JS value**: Handled by WebIDL bindings before this function
105///   receives the typed `ArrayBufferViewOrArrayBuffer` union.
106///
107/// - **ArrayBufferView offset/length**: `view.to_vec()` (mozjs) calls
108///   `GetArrayBufferViewLengthAndData`, which respects the view's
109///   `[[ByteOffset]]` and `[[ByteLength]]` — only the viewed bytes are copied.
110///
111/// - **ArrayBuffer**: `buffer.to_vec()` (mozjs) calls
112///   `GetArrayBufferLengthAndData`, copying the entire buffer contents.
113///
114/// - **Detached buffer**: When a buffer is detached, SpiderMonkey's
115///   `GetArrayBuffer(LengthAndData|ViewLengthAndData)` returns a null pointer
116///   and zero length. `to_vec()` thus produces an empty `Vec<u8>`.
117///
118/// - **SharedArrayBuffer**: Not applicable — `ArrayBufferViewOrArrayBuffer`
119///   does not include a SharedArrayBuffer variant.
120pub fn get_buffer_source_copy(source: ArrayBufferViewOrArrayBufferRef<'_>) -> Vec<u8> {
121    match source {
122        ArrayBufferViewOrArrayBufferRef::ArrayBufferView(view) => view.to_vec(),
123        ArrayBufferViewOrArrayBufferRef::ArrayBuffer(buffer) => buffer.to_vec(),
124    }
125    .unwrap_or(vec![])
126}
127
128/// Returns a slice referencing the bytes in the buffer source, without copying.
129///
130/// Use this instead of [`get_buffer_source_copy`] when the data is consumed
131/// synchronously — it avoids the allocation of a `Vec<u8>`.
132pub fn get_buffer_source_slice<'a>(
133    source: &'a ArrayBufferViewOrArrayBuffer,
134    no_gc: &'a NoGC,
135) -> &'a [u8] {
136    match source {
137        ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => view.as_slice_safe(no_gc),
138        ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => buffer.as_slice_safe(no_gc),
139    }
140    .unwrap_or(&[])
141}
142
143pub fn create_heap_buffer_source_with_length<T>(
144    cx: &mut JSContext,
145    len: u32,
146) -> Fallible<RootedTraceableBox<HeapBufferSource<T>>>
147where
148    T: TypedArrayElement + TypedArrayElementCreator + 'static,
149    T::Element: Clone + Copy,
150{
151    rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
152    let typed_array_result =
153        create_buffer_source_with_length::<T>(cx, len as usize, array.handle_mut());
154    if typed_array_result.is_err() {
155        return Err(Error::JSFailed);
156    }
157
158    Ok(RootedTraceableBox::new(HeapBufferSource::<T>::new(
159        array.handle(),
160    )))
161}
162
163#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
164pub struct HeapBufferSource<T> {
165    buffer_source: BufferSource,
166    phantom: PhantomData<T>,
167}
168
169impl<T> Eq for HeapBufferSource<T> where T: TypedArrayElement {}
170
171impl<T> PartialEq for HeapBufferSource<T>
172where
173    T: TypedArrayElement,
174{
175    fn eq(&self, other: &Self) -> bool {
176        match &self.buffer_source {
177            BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => match &other
178                .buffer_source
179            {
180                BufferSource::ArrayBufferView(from_heap) | BufferSource::ArrayBuffer(from_heap) => {
181                    std::ptr::eq(heap.get(), from_heap.get())
182                },
183            },
184        }
185    }
186}
187
188impl<T> Clone for HeapBufferSource<T>
189where
190    T: TypedArrayElement,
191{
192    fn clone(&self) -> Self {
193        HeapBufferSource {
194            buffer_source: self.buffer_source.clone(),
195            phantom: PhantomData,
196        }
197    }
198}
199
200impl<T> HeapBufferSource<T>
201where
202    T: TypedArrayElement,
203{
204    /// Create a buffer source from a rooted ArrayBuffer or ArrayBufferView.
205    pub fn new(object: Handle<*mut JSObject>) -> HeapBufferSource<T> {
206        let object = object.get();
207        assert!(!object.is_null());
208
209        HeapBufferSource {
210            buffer_source: if unsafe { IsArrayBufferObject(object) } {
211                BufferSource::ArrayBuffer(Heap::boxed(object))
212            } else {
213                assert!(unsafe { JS_IsArrayBufferViewObject(object) });
214                BufferSource::ArrayBufferView(Heap::boxed(object))
215            },
216            phantom: PhantomData,
217        }
218    }
219
220    pub fn from_view(
221        cx: &mut JSContext,
222        chunk: CustomAutoRooterGuard<TypedArray<T, *mut JSObject>>,
223    ) -> RootedTraceableBox<HeapBufferSource<T>> {
224        rooted!(&in(cx) let object = unsafe { *chunk.underlying_object() });
225        RootedTraceableBox::new(HeapBufferSource::<T>::new(object.handle()))
226    }
227
228    #[expect(clippy::should_implement_trait)]
229    pub fn default() -> Self {
230        HeapBufferSource {
231            buffer_source: BufferSource::ArrayBufferView(Heap::boxed(std::ptr::null_mut())),
232            phantom: PhantomData,
233        }
234    }
235
236    pub fn is_initialized(&self) -> bool {
237        match &self.buffer_source {
238            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
239                !buffer.get().is_null()
240            },
241        }
242    }
243
244    #[expect(clippy::result_unit_err)]
245    pub fn get_typed_array(&self) -> Result<RootedTypedArray<T>, ()> {
246        TypedArray::from(match &self.buffer_source {
247            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
248                buffer.get()
249            },
250        })
251        .map(RootedTraceableBox::new)
252    }
253
254    pub fn get_buffer_view_value(
255        &self,
256        cx: &mut JSContext,
257        mut handle_mut: SafeMutableHandleValue,
258    ) {
259        match &self.buffer_source {
260            BufferSource::ArrayBufferView(buffer) => {
261                rooted!(&in(cx) let value = ObjectValue(buffer.get()));
262                handle_mut.set(*value);
263            },
264            BufferSource::ArrayBuffer(_) => {
265                unreachable!("BufferSource::ArrayBuffer does not have a view buffer.")
266            },
267        }
268    }
269
270    pub fn get_array_buffer_view_buffer(
271        &self,
272        cx: &mut JSContext,
273    ) -> RootedTraceableBox<HeapBufferSource<ArrayBufferU8>> {
274        match &self.buffer_source {
275            BufferSource::ArrayBufferView(buffer) => unsafe {
276                let mut is_shared = false;
277                rooted!(&in(cx) let view_buffer =
278                         JS_GetArrayBufferViewBuffer(cx, Handle::from_raw(buffer.handle()), &mut is_shared));
279
280                RootedTraceableBox::new(HeapBufferSource::<ArrayBufferU8>::new(
281                    view_buffer.handle(),
282                ))
283            },
284            BufferSource::ArrayBuffer(_) => {
285                unreachable!("BufferSource::ArrayBuffer does not have a view buffer.")
286            },
287        }
288    }
289
290    /// <https://tc39.es/ecma262/#sec-detacharraybuffer>
291    pub fn detach_buffer(&self, cx: &mut JSContext) -> bool {
292        assert!(self.is_initialized());
293        match &self.buffer_source {
294            BufferSource::ArrayBufferView(buffer) => {
295                let mut is_shared = false;
296                unsafe {
297                    // assert buffer is an ArrayBuffer view
298                    assert!(JS_IsArrayBufferViewObject(*buffer.handle()));
299                    rooted!(&in(cx) let view_buffer =
300                            JS_GetArrayBufferViewBuffer(cx, Handle::from_raw(buffer.handle()), &mut is_shared));
301                    // This buffer is always created unshared
302                    debug_assert!(!is_shared);
303                    // Detach the ArrayBuffer
304                    DetachArrayBuffer(cx, view_buffer.handle())
305                }
306            },
307            BufferSource::ArrayBuffer(buffer) => unsafe {
308                DetachArrayBuffer(cx, Handle::from_raw(buffer.handle()))
309            },
310        }
311    }
312
313    pub fn typed_array_to_option(&self) -> Option<RootedTypedArray<T>> {
314        if self.is_initialized() {
315            self.get_typed_array().ok()
316        } else {
317            warn!("Buffer not initialized.");
318            None
319        }
320    }
321
322    pub fn is_detached_buffer(&self, cx: &mut JSContext) -> bool {
323        assert!(self.is_initialized());
324        match &self.buffer_source {
325            BufferSource::ArrayBufferView(buffer) => {
326                let mut is_shared = false;
327                unsafe {
328                    assert!(JS_IsArrayBufferViewObject(*buffer.handle()));
329                    rooted!(&in(cx) let view_buffer =
330                            JS_GetArrayBufferViewBuffer(cx, Handle::from_raw(buffer.handle()), &mut is_shared));
331                    debug_assert!(!is_shared);
332                    IsDetachedArrayBufferObject(*view_buffer.handle())
333                }
334            },
335            BufferSource::ArrayBuffer(buffer) => unsafe {
336                IsDetachedArrayBufferObject(*buffer.handle())
337            },
338        }
339    }
340
341    pub fn viewed_buffer_array_byte_length(&self, cx: &mut JSContext) -> usize {
342        assert!(self.is_initialized());
343        match &self.buffer_source {
344            BufferSource::ArrayBufferView(buffer) => {
345                let mut is_shared = false;
346                unsafe {
347                    assert!(JS_IsArrayBufferViewObject(*buffer.handle()));
348                    rooted!(&in(cx) let view_buffer =
349                            JS_GetArrayBufferViewBuffer(cx, Handle::from_raw(buffer.handle()), &mut is_shared));
350                    debug_assert!(!is_shared);
351                    GetArrayBufferByteLength(*view_buffer.handle())
352                }
353            },
354            BufferSource::ArrayBuffer(buffer) => unsafe {
355                GetArrayBufferByteLength(*buffer.handle())
356            },
357        }
358    }
359
360    pub fn byte_length(&self) -> usize {
361        match &self.buffer_source {
362            BufferSource::ArrayBufferView(buffer) => unsafe {
363                JS_GetArrayBufferViewByteLength(*buffer.handle())
364            },
365            BufferSource::ArrayBuffer(buffer) => unsafe {
366                GetArrayBufferByteLength(*buffer.handle())
367            },
368        }
369    }
370
371    pub fn get_byte_offset(&self) -> usize {
372        match &self.buffer_source {
373            BufferSource::ArrayBufferView(buffer) => unsafe {
374                JS_GetArrayBufferViewByteOffset(*buffer.handle())
375            },
376            BufferSource::ArrayBuffer(_) => {
377                unreachable!("BufferSource::ArrayBuffer does not have a byte offset.")
378            },
379        }
380    }
381
382    pub fn get_typed_array_length(&self) -> usize {
383        match &self.buffer_source {
384            BufferSource::ArrayBufferView(buffer) => unsafe {
385                JS_GetTypedArrayLength(*buffer.handle())
386            },
387            BufferSource::ArrayBuffer(_) => {
388                unreachable!("BufferSource::ArrayBuffer does not have a length.")
389            },
390        }
391    }
392
393    /// <https://tc39.es/ecma262/#typedarray>
394    pub fn has_typed_array_name(&self) -> bool {
395        match &self.buffer_source {
396            BufferSource::ArrayBufferView(buffer) => unsafe {
397                JS_IsTypedArrayObject(*buffer.handle())
398            },
399            BufferSource::ArrayBuffer(_) => false,
400        }
401    }
402
403    pub fn get_array_buffer_view_type(&self) -> Type {
404        match &self.buffer_source {
405            BufferSource::ArrayBufferView(buffer) => unsafe {
406                JS_GetArrayBufferViewType(*buffer.handle())
407            },
408            BufferSource::ArrayBuffer(_) => unreachable!("ArrayBuffer does not have a view type."),
409        }
410    }
411
412    pub fn is_array_buffer_object(&self) -> bool {
413        match &self.buffer_source {
414            BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => unsafe {
415                IsArrayBufferObject(*heap.handle())
416            },
417        }
418    }
419
420    /// <https://tc39.es/ecma262/#sec-clonearraybuffer>
421    pub fn clone_array_buffer(
422        &self,
423        cx: &mut JSContext,
424        byte_offset: usize,
425        byte_length: usize,
426    ) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferU8>>> {
427        let result = match &self.buffer_source {
428            BufferSource::ArrayBufferView(buffer) => {
429                let mut is_shared = false;
430                rooted!(&in(cx) let view_buffer =
431                    unsafe { JS_GetArrayBufferViewBuffer(cx, Handle::from_raw(buffer.handle()), &mut is_shared) });
432                debug_assert!(!is_shared);
433
434                unsafe { ArrayBufferClone(cx, view_buffer.handle(), byte_offset, byte_length) }
435            },
436            BufferSource::ArrayBuffer(buffer) => unsafe {
437                ArrayBufferClone(
438                    cx,
439                    Handle::from_raw(buffer.handle()),
440                    byte_offset,
441                    byte_length,
442                )
443            },
444        };
445
446        rooted!(&in(cx) let result = result);
447
448        if result.is_null() {
449            // Normalize SpiderMonkey failure: consume pending exception and
450            // map it to a DOM Error.
451            rooted!(&in(cx) let mut _ex = UndefinedValue());
452            unsafe {
453                // If SpiderMonkey set an exception, clear it so callers see a clean cx.
454                if JS_GetPendingException(cx, _ex.handle_mut()) {
455                    JS_ClearPendingException(cx);
456                }
457            }
458
459            Err(Error::Type(c"can't clone array buffer".to_owned()))
460        } else {
461            Ok(RootedTraceableBox::new(
462                HeapBufferSource::<ArrayBufferU8>::new(result.handle()),
463            ))
464        }
465    }
466    /// <https://streams.spec.whatwg.org/#abstract-opdef-cloneasuint8array>
467    pub fn clone_as_uint8_array(
468        &self,
469        cx: &mut JSContext,
470    ) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferViewU8>>> {
471        match &self.buffer_source {
472            BufferSource::ArrayBufferView(buffer) => {
473                // Assert: O is an Object.
474                // Assert: O has an [[ViewedArrayBuffer]] internal slot.
475                assert!(unsafe { JS_IsArrayBufferViewObject(*buffer.handle()) });
476
477                // Assert: ! IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false.
478                assert!(!self.is_detached_buffer(cx));
479
480                // Let buffer be ? CloneArrayBuffer(O.[[ViewedArrayBuffer]],
481                // O.[[ByteOffset]], O.[[ByteLength]], %ArrayBuffer%).
482                let byte_offset = self.get_byte_offset();
483                let byte_length = self.byte_length();
484
485                let buffer = self.clone_array_buffer(cx, byte_offset, byte_length)?;
486
487                // Let array be ! Construct(%Uint8Array%, « buffer »).
488                // Return array.
489                construct_typed_array(cx, &Type::Uint8, &buffer, 0, byte_length as i64)
490            },
491            BufferSource::ArrayBuffer(_buffer) => {
492                unreachable!("BufferSource::ArrayBuffer does not have a view buffer.")
493            },
494        }
495    }
496
497    pub fn is_undefined(&self) -> bool {
498        match &self.buffer_source {
499            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
500                buffer.get().is_null()
501            },
502        }
503    }
504}
505
506impl<T> HeapBufferSource<T>
507where
508    T: TypedArrayElement + TypedArrayElementCreator + 'static,
509    T::Element: Clone + Copy,
510{
511    #[expect(clippy::result_unit_err)]
512    pub fn acquire_data(&self, cx: &mut JSContext) -> Result<Vec<T::Element>, ()> {
513        assert!(self.is_initialized());
514
515        typedarray!(&in(cx) let array: TypedArray = match &self.buffer_source {
516            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer)
517            => {
518                buffer.get()
519            },
520        });
521        let data = if let Ok(array) =
522            array as Result<CustomAutoRooterGuard<'_, TypedArray<T, *mut JSObject>>, &mut ()>
523        {
524            if let Some(data) = array.to_vec() {
525                let _ = self.detach_buffer(cx);
526                Ok(data)
527            } else {
528                Err(())
529            }
530        } else {
531            Err(())
532        };
533
534        match &self.buffer_source {
535            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
536                buffer.set(ptr::null_mut());
537            },
538        }
539        data
540    }
541
542    #[expect(clippy::result_unit_err)]
543    pub fn copy_data_to(
544        &self,
545        cx: &mut JSContext,
546        dest: &mut [T::Element],
547        source_start: usize,
548        source_end: usize,
549    ) -> Result<(), ()> {
550        assert!(self.is_initialized());
551        typedarray!(&in(cx) let array: TypedArray = match &self.buffer_source {
552            BufferSource::ArrayBufferView(buffer) |  BufferSource::ArrayBuffer(buffer)
553            => {
554                buffer.get()
555            },
556        });
557        let Ok(array) =
558            array as Result<CustomAutoRooterGuard<'_, TypedArray<T, *mut JSObject>>, &mut ()>
559        else {
560            return Err(());
561        };
562        let slice = (*array).as_slice_safe(cx.no_gc()).unwrap_or(&[]);
563        dest.copy_from_slice(&slice[source_start..source_end]);
564        Ok(())
565    }
566
567    #[expect(clippy::result_unit_err)]
568    pub fn copy_data_from(
569        &self,
570        cx: &mut JSContext,
571        source: CustomAutoRooterGuard<TypedArray<T, *mut JSObject>>,
572        dest_start: usize,
573        length: usize,
574    ) -> Result<(), ()> {
575        assert!(self.is_initialized());
576        typedarray!(&in(cx) let mut array: TypedArray = match &self.buffer_source {
577            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer)
578            => {
579                buffer.get()
580            },
581        });
582        let Ok(mut array) =
583            array as Result<CustomAutoRooterGuard<'_, TypedArray<T, *mut JSObject>>, &mut ()>
584        else {
585            return Err(());
586        };
587        let slice = (*array).as_mut_slice_safe(cx.no_gc()).unwrap_or(&mut []);
588        let (_, dest) = slice.split_at_mut(dest_start);
589        let source = source.as_slice_safe(cx.no_gc()).unwrap_or(&[]);
590        dest[0..length].copy_from_slice(&source[0..length]);
591        Ok(())
592    }
593
594    #[expect(clippy::result_unit_err)]
595    pub fn set_data(&self, cx: &mut JSContext, data: &[T::Element]) -> Result<(), ()> {
596        rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
597        let _ = create_buffer_source::<T>(cx, data, array.handle_mut())?;
598
599        match &self.buffer_source {
600            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
601                buffer.set(*array);
602            },
603        }
604        Ok(())
605    }
606
607    /// <https://streams.spec.whatwg.org/#abstract-opdef-cancopydatablockbytes>
608    // CanCopyDataBlockBytes(descriptorBuffer, destStart, queueBuffer, queueByteOffset, bytesToCopy)
609    pub fn can_copy_data_block_bytes(
610        &self,
611        cx: &mut JSContext,
612        to_index: usize,
613        from_buffer: &HeapBufferSource<ArrayBufferU8>,
614        from_index: usize,
615        bytes_to_copy: usize,
616    ) -> bool {
617        // Assert: toBuffer is an Object.
618        // Assert: toBuffer has an [[ArrayBufferData]] internal slot.
619        assert!(self.is_array_buffer_object());
620
621        // Assert: fromBuffer is an Object.
622        // Assert: fromBuffer has an [[ArrayBufferData]] internal slot.
623        assert!(from_buffer.is_array_buffer_object());
624
625        // If toBuffer is fromBuffer, return false.
626        match &self.buffer_source {
627            BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => {
628                match &from_buffer.buffer_source {
629                    BufferSource::ArrayBufferView(from_heap) |
630                    BufferSource::ArrayBuffer(from_heap) => {
631                        if std::ptr::eq(heap.get(), from_heap.get()) {
632                            return false;
633                        }
634                    },
635                }
636            },
637        }
638
639        // If ! IsDetachedBuffer(toBuffer) is true, return false.
640        if self.is_detached_buffer(cx) {
641            return false;
642        }
643
644        // If ! IsDetachedBuffer(fromBuffer) is true, return false.
645        if from_buffer.is_detached_buffer(cx) {
646            return false;
647        }
648
649        // If toIndex + count > toBuffer.[[ArrayBufferByteLength]], return false.
650        if to_index + bytes_to_copy > self.byte_length() {
651            return false;
652        }
653
654        // If fromIndex + count > fromBuffer.[[ArrayBufferByteLength]], return false.
655        if from_index + bytes_to_copy > from_buffer.byte_length() {
656            return false;
657        }
658
659        // Return true.
660        true
661    }
662
663    pub fn copy_data_block_bytes(
664        &self,
665        cx: &mut JSContext,
666        dest_start: usize,
667        from_buffer: &HeapBufferSource<ArrayBufferU8>,
668        from_byte_offset: usize,
669        bytes_to_copy: usize,
670    ) -> bool {
671        match &self.buffer_source {
672            BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => unsafe {
673                match &from_buffer.buffer_source {
674                    BufferSource::ArrayBufferView(from_heap) |
675                    BufferSource::ArrayBuffer(from_heap) => ArrayBufferCopyData(
676                        cx,
677                        Handle::from_raw(heap.handle()),
678                        dest_start,
679                        Handle::from_raw(from_heap.handle()),
680                        from_byte_offset,
681                        bytes_to_copy,
682                    ),
683                }
684            },
685        }
686    }
687
688    /// <https://streams.spec.whatwg.org/#can-transfer-array-buffer>
689    pub fn can_transfer_array_buffer(&self, cx: &mut JSContext) -> bool {
690        // Assert: O is an Object.
691        // Assert: O has an [[ArrayBufferData]] internal slot.
692        assert!(self.is_array_buffer_object());
693
694        // If ! IsDetachedBuffer(O) is true, return false.
695        if self.is_detached_buffer(cx) {
696            return false;
697        }
698
699        // If SameValue(O.[[ArrayBufferDetachKey]], undefined) is false, return false.
700        // Return true.
701        let mut is_defined = false;
702        match &self.buffer_source {
703            BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => unsafe {
704                if !HasDefinedArrayBufferDetachKey(
705                    cx,
706                    Handle::from_raw(heap.handle()),
707                    &mut is_defined,
708                ) {
709                    return false;
710                }
711            },
712        }
713
714        !is_defined
715    }
716
717    /// <https://streams.spec.whatwg.org/#transfer-array-buffer>
718    pub fn transfer_array_buffer(
719        &self,
720        cx: &mut JSContext,
721    ) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferU8>>> {
722        assert!(self.is_array_buffer_object());
723
724        // Assert: ! IsDetachedBuffer(O) is false.
725        assert!(!self.is_detached_buffer(cx));
726
727        // Let arrayBufferByteLength be O.[[ArrayBufferByteLength]].
728        // Step 3 (Reordered)
729        let buffer_length = self.byte_length();
730
731        // Let arrayBufferData be O.[[ArrayBufferData]].
732        // Step 2 (Reordered)
733        let buffer_data = match &self.buffer_source {
734            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => unsafe {
735                StealArrayBufferContents(cx, Handle::from_raw(buffer.handle()))
736            },
737        };
738
739        // Perform ? DetachArrayBuffer(O).
740        // This will throw an exception if O has an [[ArrayBufferDetachKey]] that is not undefined,
741        // such as a WebAssembly.Memory’s buffer. [WASM-JS-API-1]
742        if !self.detach_buffer(cx) {
743            rooted!(&in(cx) let mut rval = UndefinedValue());
744            unsafe {
745                assert!(JS_GetPendingException(cx, rval.handle_mut()));
746                JS_ClearPendingException(cx)
747            };
748
749            Err(Error::Type(c"can't transfer array buffer".to_owned()))
750        } else {
751            // Return a new ArrayBuffer object, created in the current Realm,
752            // whose [[ArrayBufferData]] internal slot value is arrayBufferData and
753            // whose [[ArrayBufferByteLength]] internal slot value is arrayBufferByteLength.
754            rooted!(&in(cx) let result = unsafe {
755                NewArrayBufferWithContents(cx, buffer_length, buffer_data)
756            });
757            if result.is_null() {
758                return Err(Error::JSFailed);
759            }
760            Ok(RootedTraceableBox::new(
761                HeapBufferSource::<ArrayBufferU8>::new(result.handle()),
762            ))
763        }
764    }
765}
766
767unsafe impl<T> Trace for HeapBufferSource<T> {
768    #[inline]
769    unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
770        match &self.buffer_source {
771            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
772                unsafe { buffer.trace(tracer) };
773            },
774        }
775    }
776}
777
778/// <https://webidl.spec.whatwg.org/#arraybufferview-create>
779#[expect(clippy::result_unit_err)]
780pub fn create_buffer_source<T>(
781    cx: &mut JSContext,
782    data: &[T::Element],
783    mut dest: MutableHandleObject,
784) -> Result<RootedTypedArray<T>, ()>
785where
786    T: TypedArrayElement + TypedArrayElementCreator,
787{
788    let res = unsafe {
789        TypedArray::<T, *mut JSObject>::create(cx, CreateWith::Slice(data), dest.reborrow())
790    };
791
792    if res.is_err() {
793        Err(())
794    } else {
795        TypedArray::from(dest.get()).map(RootedTraceableBox::new)
796    }
797}
798
799fn create_buffer_source_with_length<T>(
800    cx: &mut JSContext,
801    len: usize,
802    mut dest: MutableHandleObject,
803) -> Result<RootedTypedArray<T>, ()>
804where
805    T: TypedArrayElement + TypedArrayElementCreator,
806{
807    let res = unsafe {
808        TypedArray::<T, *mut JSObject>::create(cx, CreateWith::Length(len), dest.reborrow())
809    };
810
811    if res.is_err() {
812        Err(())
813    } else {
814        TypedArray::from(dest.get()).map(RootedTraceableBox::new)
815    }
816}
817
818pub fn byte_size(byte_type: Type) -> u64 {
819    match byte_type {
820        Type::Int8 | Type::Uint8 | Type::Uint8Clamped => 1,
821        Type::Int16 | Type::Uint16 | Type::Float16 => 2,
822        Type::Int32 | Type::Uint32 | Type::Float32 => 4,
823        Type::Int64 | Type::Float64 | Type::BigInt64 | Type::BigUint64 => 8,
824        Type::Simd128 => 16,
825        _ => unreachable!("invalid scalar type"),
826    }
827}
828
829#[derive(Clone, Eq, JSTraceable, MallocSizeOf, PartialEq)]
830pub enum Constructor {
831    DataView,
832    Name(
833        #[ignore_malloc_size_of = "mozjs"]
834        #[no_trace]
835        Type,
836    ),
837}
838
839pub fn create_buffer_source_with_constructor(
840    cx: &mut JSContext,
841    constructor: &Constructor,
842    buffer_source: &HeapBufferSource<ArrayBufferU8>,
843    byte_offset: usize,
844    byte_length: usize,
845) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferViewU8>>> {
846    match &buffer_source.buffer_source {
847        BufferSource::ArrayBuffer(heap) => match constructor {
848            Constructor::DataView => {
849                rooted!(&in(cx) let view = unsafe {
850                    JS_NewDataView(
851                        cx,
852                        Handle::from_raw(heap.handle()),
853                        byte_offset,
854                        byte_length,
855                    )
856                });
857                if view.is_null() {
858                    return Err(Error::JSFailed);
859                }
860                Ok(RootedTraceableBox::new(HeapBufferSource::new(
861                    view.handle(),
862                )))
863            },
864            Constructor::Name(name_type) => construct_typed_array(
865                cx,
866                name_type,
867                buffer_source,
868                byte_offset,
869                byte_length as i64,
870            ),
871        },
872        BufferSource::ArrayBufferView(_) => {
873            unreachable!("Can not create a new ArrayBufferView from an existing ArrayBufferView");
874        },
875    }
876}
877
878/// Helper function to construct different TypedArray views
879fn construct_typed_array(
880    cx: &mut JSContext,
881    name_type: &Type,
882    buffer_source: &HeapBufferSource<ArrayBufferU8>,
883    byte_offset: usize,
884    byte_length: i64,
885) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferViewU8>>> {
886    match &buffer_source.buffer_source {
887        BufferSource::ArrayBuffer(heap) => {
888            rooted!(&in(cx) let array_view = unsafe {
889                match name_type {
890                    Type::Int8 => JS_NewInt8ArrayWithBuffer(
891                        cx,
892                        Handle::from_raw(heap.handle()),
893                        byte_offset,
894                        byte_length,
895                    ),
896                    Type::Uint8 => JS_NewUint8ArrayWithBuffer(
897                        cx,
898                        Handle::from_raw(heap.handle()),
899                        byte_offset,
900                        byte_length,
901                    ),
902                    Type::Uint16 => JS_NewUint16ArrayWithBuffer(
903                        cx,
904                        Handle::from_raw(heap.handle()),
905                        byte_offset,
906                        byte_length,
907                    ),
908                    Type::Int16 => JS_NewInt16ArrayWithBuffer(
909                        cx,
910                        Handle::from_raw(heap.handle()),
911                        byte_offset,
912                        byte_length,
913                    ),
914                    Type::Int32 => JS_NewInt32ArrayWithBuffer(
915                        cx,
916                        Handle::from_raw(heap.handle()),
917                        byte_offset,
918                        byte_length,
919                    ),
920                    Type::Uint32 => JS_NewUint32ArrayWithBuffer(
921                        cx,
922                        Handle::from_raw(heap.handle()),
923                        byte_offset,
924                        byte_length,
925                    ),
926                    Type::Float32 => JS_NewFloat32ArrayWithBuffer(
927                        cx,
928                        Handle::from_raw(heap.handle()),
929                        byte_offset,
930                        byte_length,
931                    ),
932                    Type::Float64 => JS_NewFloat64ArrayWithBuffer(
933                        cx,
934                        Handle::from_raw(heap.handle()),
935                        byte_offset,
936                        byte_length,
937                    ),
938                    Type::Uint8Clamped => JS_NewUint8ClampedArrayWithBuffer(
939                        cx,
940                        Handle::from_raw(heap.handle()),
941                        byte_offset,
942                        byte_length,
943                    ),
944                    Type::BigInt64 => JS_NewBigInt64ArrayWithBuffer(
945                        cx,
946                        Handle::from_raw(heap.handle()),
947                        byte_offset,
948                        byte_length,
949                    ),
950                    Type::BigUint64 => JS_NewBigUint64ArrayWithBuffer(
951                        cx,
952                        Handle::from_raw(heap.handle()),
953                        byte_offset,
954                        byte_length,
955                    ),
956                    Type::Float16 => JS_NewFloat16ArrayWithBuffer(
957                        cx,
958                        Handle::from_raw(heap.handle()),
959                        byte_offset,
960                        byte_length,
961                    ),
962                    Type::Int64 | Type::Simd128 | Type::MaxTypedArrayViewType => {
963                        unreachable!("Invalid TypedArray type")
964                    },
965                }
966            });
967            if array_view.is_null() {
968                return Err(Error::JSFailed);
969            }
970
971            Ok(RootedTraceableBox::new(HeapBufferSource::new(
972                array_view.handle(),
973            )))
974        },
975        BufferSource::ArrayBufferView(_) => {
976            unreachable!("Can not create a new ArrayBufferView from an existing ArrayBufferView");
977        },
978    }
979}
980
981pub fn create_array_buffer_with_size(
982    cx: &mut JSContext,
983    size: usize,
984) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferU8>>> {
985    rooted!(&in(cx) let result = unsafe { NewArrayBuffer(cx, size) });
986    if result.is_null() {
987        rooted!(&in(cx) let mut rval = UndefinedValue());
988        unsafe {
989            assert!(JS_GetPendingException(cx, rval.handle_mut()));
990            JS_ClearPendingException(cx)
991        };
992
993        Err(Error::Type(c"can't create array buffer".to_owned()))
994    } else {
995        Ok(RootedTraceableBox::new(
996            HeapBufferSource::<ArrayBufferU8>::new(result.handle()),
997        ))
998    }
999}