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::jsapi::{
17    ArrayBufferClone, ArrayBufferCopyData, GetArrayBufferByteLength,
18    HasDefinedArrayBufferDetachKey, Heap, IsArrayBufferObject, IsDetachedArrayBufferObject,
19    JS_ClearPendingException, JS_GetArrayBufferViewBuffer, JS_GetArrayBufferViewByteLength,
20    JS_GetArrayBufferViewByteOffset, JS_GetArrayBufferViewType, JS_GetPendingException,
21    JS_GetTypedArrayLength, JS_IsArrayBufferViewObject, JS_IsTypedArrayObject,
22    JS_NewBigInt64ArrayWithBuffer, JS_NewBigUint64ArrayWithBuffer, JS_NewDataView,
23    JS_NewFloat16ArrayWithBuffer, JS_NewFloat32ArrayWithBuffer, JS_NewFloat64ArrayWithBuffer,
24    JS_NewInt8ArrayWithBuffer, JS_NewInt16ArrayWithBuffer, JS_NewInt32ArrayWithBuffer,
25    JS_NewUint8ArrayWithBuffer, JS_NewUint8ClampedArrayWithBuffer, JS_NewUint16ArrayWithBuffer,
26    JS_NewUint32ArrayWithBuffer, JSObject, NewArrayBuffer, NewArrayBufferWithContents,
27    StealArrayBufferContents, Type,
28};
29use js::jsval::{ObjectValue, UndefinedValue};
30use js::rust::wrappers::DetachArrayBuffer;
31#[cfg(feature = "webgpu")]
32use js::rust::wrappers2::NewExternalArrayBuffer;
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: &mut js::context::JSContext,
525        data: &[T::Element],
526    ) -> Result<(), ()> {
527        rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
528        let _ = create_buffer_source::<T>(cx.into(), data, array.handle_mut(), CanGc::from_cx(cx))?;
529
530        match &self.buffer_source {
531            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
532                buffer.set(*array);
533            },
534        }
535        Ok(())
536    }
537
538    /// <https://streams.spec.whatwg.org/#abstract-opdef-cancopydatablockbytes>
539    // CanCopyDataBlockBytes(descriptorBuffer, destStart, queueBuffer, queueByteOffset, bytesToCopy)
540    pub(crate) fn can_copy_data_block_bytes(
541        &self,
542        cx: JSContext,
543        to_index: usize,
544        from_buffer: &HeapBufferSource<ArrayBufferU8>,
545        from_index: usize,
546        bytes_to_copy: usize,
547    ) -> bool {
548        // Assert: toBuffer is an Object.
549        // Assert: toBuffer has an [[ArrayBufferData]] internal slot.
550        assert!(self.is_array_buffer_object());
551
552        // Assert: fromBuffer is an Object.
553        // Assert: fromBuffer has an [[ArrayBufferData]] internal slot.
554        assert!(from_buffer.is_array_buffer_object());
555
556        // If toBuffer is fromBuffer, return false.
557        match &self.buffer_source {
558            BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => {
559                match &from_buffer.buffer_source {
560                    BufferSource::ArrayBufferView(from_heap) |
561                    BufferSource::ArrayBuffer(from_heap) => {
562                        if std::ptr::eq(heap.get(), from_heap.get()) {
563                            return false;
564                        }
565                    },
566                }
567            },
568        }
569
570        // If ! IsDetachedBuffer(toBuffer) is true, return false.
571        if self.is_detached_buffer(cx) {
572            return false;
573        }
574
575        // If ! IsDetachedBuffer(fromBuffer) is true, return false.
576        if from_buffer.is_detached_buffer(cx) {
577            return false;
578        }
579
580        // If toIndex + count > toBuffer.[[ArrayBufferByteLength]], return false.
581        if to_index + bytes_to_copy > self.byte_length() {
582            return false;
583        }
584
585        // If fromIndex + count > fromBuffer.[[ArrayBufferByteLength]], return false.
586        if from_index + bytes_to_copy > from_buffer.byte_length() {
587            return false;
588        }
589
590        // Return true.
591        true
592    }
593
594    pub(crate) fn copy_data_block_bytes(
595        &self,
596        cx: JSContext,
597        dest_start: usize,
598        from_buffer: &HeapBufferSource<ArrayBufferU8>,
599        from_byte_offset: usize,
600        bytes_to_copy: usize,
601    ) -> bool {
602        match &self.buffer_source {
603            BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => unsafe {
604                match &from_buffer.buffer_source {
605                    BufferSource::ArrayBufferView(from_heap) |
606                    BufferSource::ArrayBuffer(from_heap) => ArrayBufferCopyData(
607                        *cx,
608                        heap.handle(),
609                        dest_start,
610                        from_heap.handle(),
611                        from_byte_offset,
612                        bytes_to_copy,
613                    ),
614                }
615            },
616        }
617    }
618
619    /// <https://streams.spec.whatwg.org/#can-transfer-array-buffer>
620    pub(crate) fn can_transfer_array_buffer(&self, cx: JSContext) -> bool {
621        // Assert: O is an Object.
622        // Assert: O has an [[ArrayBufferData]] internal slot.
623        assert!(self.is_array_buffer_object());
624
625        // If ! IsDetachedBuffer(O) is true, return false.
626        if self.is_detached_buffer(cx) {
627            return false;
628        }
629
630        // If SameValue(O.[[ArrayBufferDetachKey]], undefined) is false, return false.
631        // Return true.
632        let mut is_defined = false;
633        match &self.buffer_source {
634            BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => unsafe {
635                if !HasDefinedArrayBufferDetachKey(*cx, heap.handle(), &mut is_defined) {
636                    return false;
637                }
638            },
639        }
640
641        !is_defined
642    }
643
644    /// <https://streams.spec.whatwg.org/#transfer-array-buffer>
645    pub(crate) fn transfer_array_buffer(
646        &self,
647        cx: JSContext,
648    ) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferU8>>> {
649        assert!(self.is_array_buffer_object());
650
651        // Assert: ! IsDetachedBuffer(O) is false.
652        assert!(!self.is_detached_buffer(cx));
653
654        // Let arrayBufferByteLength be O.[[ArrayBufferByteLength]].
655        // Step 3 (Reordered)
656        let buffer_length = self.byte_length();
657
658        // Let arrayBufferData be O.[[ArrayBufferData]].
659        // Step 2 (Reordered)
660        let buffer_data = match &self.buffer_source {
661            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => unsafe {
662                StealArrayBufferContents(*cx, buffer.handle())
663            },
664        };
665
666        // Perform ? DetachArrayBuffer(O).
667        // This will throw an exception if O has an [[ArrayBufferDetachKey]] that is not undefined,
668        // such as a WebAssembly.Memory’s buffer. [WASM-JS-API-1]
669        if !self.detach_buffer(cx) {
670            rooted!(in(*cx) let mut rval = UndefinedValue());
671            unsafe {
672                assert!(JS_GetPendingException(*cx, rval.handle_mut().into()));
673                JS_ClearPendingException(*cx)
674            };
675
676            Err(Error::Type(c"can't transfer array buffer".to_owned()))
677        } else {
678            // Return a new ArrayBuffer object, created in the current Realm,
679            // whose [[ArrayBufferData]] internal slot value is arrayBufferData and
680            // whose [[ArrayBufferByteLength]] internal slot value is arrayBufferByteLength.
681            Ok(RootedTraceableBox::new(
682                HeapBufferSource::<ArrayBufferU8>::new(BufferSource::ArrayBuffer(Heap::boxed(
683                    unsafe { NewArrayBufferWithContents(*cx, buffer_length, buffer_data) },
684                ))),
685            ))
686        }
687    }
688}
689
690unsafe impl<T> crate::dom::bindings::trace::JSTraceable for HeapBufferSource<T> {
691    #[inline]
692    unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
693        match &self.buffer_source {
694            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
695                unsafe { buffer.trace(tracer) };
696            },
697        }
698    }
699}
700
701/// <https://webidl.spec.whatwg.org/#arraybufferview-create>
702pub(crate) fn create_buffer_source<T>(
703    cx: JSContext,
704    data: &[T::Element],
705    mut dest: MutableHandleObject,
706    _can_gc: CanGc,
707) -> Result<RootedTypedArray<T>, ()>
708where
709    T: TypedArrayElement + TypedArrayElementCreator,
710{
711    let res = unsafe {
712        TypedArray::<T, *mut JSObject>::create(*cx, CreateWith::Slice(data), dest.reborrow())
713    };
714
715    if res.is_err() {
716        Err(())
717    } else {
718        TypedArray::from(dest.get()).map(RootedTraceableBox::new)
719    }
720}
721
722fn create_buffer_source_with_length<T>(
723    cx: JSContext,
724    len: usize,
725    mut dest: MutableHandleObject,
726    _can_gc: CanGc,
727) -> Result<RootedTypedArray<T>, ()>
728where
729    T: TypedArrayElement + TypedArrayElementCreator,
730{
731    let res = unsafe {
732        TypedArray::<T, *mut JSObject>::create(*cx, CreateWith::Length(len), dest.reborrow())
733    };
734
735    if res.is_err() {
736        Err(())
737    } else {
738        TypedArray::from(dest.get()).map(RootedTraceableBox::new)
739    }
740}
741
742pub(crate) fn byte_size(byte_type: Type) -> u64 {
743    match byte_type {
744        Type::Int8 | Type::Uint8 | Type::Uint8Clamped => 1,
745        Type::Int16 | Type::Uint16 | Type::Float16 => 2,
746        Type::Int32 | Type::Uint32 | Type::Float32 => 4,
747        Type::Int64 | Type::Float64 | Type::BigInt64 | Type::BigUint64 => 8,
748        Type::Simd128 => 16,
749        _ => unreachable!("invalid scalar type"),
750    }
751}
752
753#[derive(Clone, Eq, JSTraceable, MallocSizeOf, PartialEq)]
754pub(crate) enum Constructor {
755    DataView,
756    Name(
757        #[ignore_malloc_size_of = "mozjs"]
758        #[no_trace]
759        Type,
760    ),
761}
762
763pub(crate) fn create_buffer_source_with_constructor(
764    cx: &mut js::context::JSContext,
765    constructor: &Constructor,
766    buffer_source: &HeapBufferSource<ArrayBufferU8>,
767    byte_offset: usize,
768    byte_length: usize,
769) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferViewU8>>> {
770    match &buffer_source.buffer_source {
771        BufferSource::ArrayBuffer(heap) => match constructor {
772            Constructor::DataView => Ok(RootedTraceableBox::new(HeapBufferSource::new(
773                BufferSource::ArrayBufferView(Heap::boxed(unsafe {
774                    JS_NewDataView(cx.raw_cx(), heap.handle(), byte_offset, byte_length)
775                })),
776            ))),
777            Constructor::Name(name_type) => construct_typed_array(
778                cx,
779                name_type,
780                buffer_source,
781                byte_offset,
782                byte_length as i64,
783            ),
784        },
785        BufferSource::ArrayBufferView(_) => {
786            unreachable!("Can not create a new ArrayBufferView from an existing ArrayBufferView");
787        },
788    }
789}
790
791/// Helper function to construct different TypedArray views
792fn construct_typed_array(
793    cx: &mut js::context::JSContext,
794    name_type: &Type,
795    buffer_source: &HeapBufferSource<ArrayBufferU8>,
796    byte_offset: usize,
797    byte_length: i64,
798) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferViewU8>>> {
799    match &buffer_source.buffer_source {
800        BufferSource::ArrayBuffer(heap) => {
801            let array_view = unsafe {
802                match name_type {
803                    Type::Int8 => JS_NewInt8ArrayWithBuffer(
804                        cx.raw_cx(),
805                        heap.handle(),
806                        byte_offset,
807                        byte_length,
808                    ),
809                    Type::Uint8 => JS_NewUint8ArrayWithBuffer(
810                        cx.raw_cx(),
811                        heap.handle(),
812                        byte_offset,
813                        byte_length,
814                    ),
815                    Type::Uint16 => JS_NewUint16ArrayWithBuffer(
816                        cx.raw_cx(),
817                        heap.handle(),
818                        byte_offset,
819                        byte_length,
820                    ),
821                    Type::Int16 => JS_NewInt16ArrayWithBuffer(
822                        cx.raw_cx(),
823                        heap.handle(),
824                        byte_offset,
825                        byte_length,
826                    ),
827                    Type::Int32 => JS_NewInt32ArrayWithBuffer(
828                        cx.raw_cx(),
829                        heap.handle(),
830                        byte_offset,
831                        byte_length,
832                    ),
833                    Type::Uint32 => JS_NewUint32ArrayWithBuffer(
834                        cx.raw_cx(),
835                        heap.handle(),
836                        byte_offset,
837                        byte_length,
838                    ),
839                    Type::Float32 => JS_NewFloat32ArrayWithBuffer(
840                        cx.raw_cx(),
841                        heap.handle(),
842                        byte_offset,
843                        byte_length,
844                    ),
845                    Type::Float64 => JS_NewFloat64ArrayWithBuffer(
846                        cx.raw_cx(),
847                        heap.handle(),
848                        byte_offset,
849                        byte_length,
850                    ),
851                    Type::Uint8Clamped => JS_NewUint8ClampedArrayWithBuffer(
852                        cx.raw_cx(),
853                        heap.handle(),
854                        byte_offset,
855                        byte_length,
856                    ),
857                    Type::BigInt64 => JS_NewBigInt64ArrayWithBuffer(
858                        cx.raw_cx(),
859                        heap.handle(),
860                        byte_offset,
861                        byte_length,
862                    ),
863                    Type::BigUint64 => JS_NewBigUint64ArrayWithBuffer(
864                        cx.raw_cx(),
865                        heap.handle(),
866                        byte_offset,
867                        byte_length,
868                    ),
869                    Type::Float16 => JS_NewFloat16ArrayWithBuffer(
870                        cx.raw_cx(),
871                        heap.handle(),
872                        byte_offset,
873                        byte_length,
874                    ),
875                    Type::Int64 | Type::Simd128 | Type::MaxTypedArrayViewType => {
876                        unreachable!("Invalid TypedArray type")
877                    },
878                }
879            };
880
881            Ok(RootedTraceableBox::new(HeapBufferSource::new(
882                BufferSource::ArrayBufferView(Heap::boxed(array_view)),
883            )))
884        },
885        BufferSource::ArrayBufferView(_) => {
886            unreachable!("Can not create a new ArrayBufferView from an existing ArrayBufferView");
887        },
888    }
889}
890
891pub(crate) fn create_array_buffer_with_size(
892    cx: &mut js::context::JSContext,
893    size: usize,
894) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferU8>>> {
895    let result = unsafe { NewArrayBuffer(cx.raw_cx(), size) };
896    if result.is_null() {
897        rooted!(&in(cx) let mut rval = UndefinedValue());
898        unsafe {
899            assert!(JS_GetPendingException(
900                cx.raw_cx(),
901                rval.handle_mut().into()
902            ));
903            JS_ClearPendingException(cx.raw_cx())
904        };
905
906        Err(Error::Type(c"can't create array buffer".to_owned()))
907    } else {
908        Ok(RootedTraceableBox::new(
909            HeapBufferSource::<ArrayBufferU8>::new(BufferSource::ArrayBuffer(Heap::boxed(result))),
910        ))
911    }
912}
913
914#[cfg(feature = "webgpu")]
915#[derive(JSTraceable, MallocSizeOf)]
916#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
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(
959        &mut self,
960        cx: &mut js::context::JSContext,
961        range: Range<usize>,
962    ) -> Result<&DataView, ()> {
963        if self
964            .data_views
965            .iter()
966            .any(|view| range_overlap(&view.range, &range))
967        {
968            return Err(());
969        }
970        let range_len = range
971            .end
972            .checked_sub(range.start)
973            .expect("range end must be >= range start");
974        assert!(range.end <= self.data.len());
975
976        /// `freeFunc()` must be threadsafe, should be safely callable from any thread
977        /// without causing conflicts, unexpected behavior.
978        unsafe extern "C" fn free_func(_contents: *mut c_void, free_user_data: *mut c_void) {
979            let raw: *const Box<[u8]> = free_user_data.cast();
980            // SAFETY: `free_func` is called by SM and returns ownership of the Arc we
981            // leaked below with `into_raw`. Hence it is safe to reconstruct the Arc,
982            // and destroy it to release the reference count.
983            drop(unsafe { Arc::from_raw(raw) });
984        }
985        let raw: *const Box<[u8]> = Arc::into_raw(Arc::clone(&self.data));
986        // SAFETY: We leaked the Arc, so the underlying slice will stay alive
987        // until `free_func` is called. `range.start..range.end` is inside
988        // the valid range of the slice.
989        let data_ptr = unsafe { (**raw).as_ptr().add(range.start) };
990        rooted!(&in(cx) let object = unsafe {
991            NewExternalArrayBuffer(
992                cx,
993                range_len,
994                // FIXME(jschwe): I believe casting to a mutable pointer is unsound.
995                // We would need interior mutability.
996                data_ptr.cast_mut().cast(),
997                Some(free_func),
998                raw as _,
999            )
1000        });
1001        self.data_views.push(DataView {
1002            range,
1003            buffer: HeapArrayBuffer::from(*object).unwrap(),
1004        });
1005        Ok(self.data_views.last().unwrap())
1006    }
1007}
1008
1009#[cfg(feature = "webgpu")]
1010#[derive(JSTraceable, MallocSizeOf)]
1011#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1012pub(crate) struct DataView {
1013    #[no_trace]
1014    range: Range<usize>,
1015    #[ignore_malloc_size_of = "defined in mozjs"]
1016    buffer: HeapArrayBuffer,
1017}
1018
1019#[cfg(feature = "webgpu")]
1020impl DataView {
1021    pub(crate) fn array_buffer(&self) -> RootedTraceableBox<HeapArrayBuffer> {
1022        RootedTraceableBox::new(unsafe {
1023            HeapArrayBuffer::from(self.buffer.underlying_object().get()).unwrap()
1024        })
1025    }
1026}
1027
1028#[cfg(feature = "webgpu")]
1029impl Drop for DataView {
1030    #[expect(unsafe_code)]
1031    fn drop(&mut self) {
1032        let cx = GlobalScope::get_cx();
1033        assert!(unsafe {
1034            js::jsapi::DetachArrayBuffer(*cx, self.buffer.underlying_object().handle())
1035        })
1036    }
1037}