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