Skip to main content

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