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