Skip to main content

script/dom/bindings/
buffer_source.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![expect(unsafe_code)]
6
7#[cfg(feature = "webgpu")]
8use std::ffi::c_void;
9use std::marker::PhantomData;
10#[cfg(feature = "webgpu")]
11use std::ops::Range;
12use std::ptr;
13#[cfg(feature = "webgpu")]
14use std::sync::Arc;
15
16use js::context::JSContext;
17use js::jsapi::{
18    GetArrayBufferByteLength, Heap, IsArrayBufferObject, IsDetachedArrayBufferObject,
19    JS_GetArrayBufferViewByteLength, JS_GetArrayBufferViewByteOffset, JS_GetArrayBufferViewType,
20    JS_GetTypedArrayLength, JS_IsArrayBufferViewObject, JS_IsTypedArrayObject, JSObject, Type,
21};
22use js::jsval::{ObjectValue, UndefinedValue};
23#[cfg(feature = "webgpu")]
24use js::rust::wrappers2::NewExternalArrayBuffer;
25use js::rust::wrappers2::{
26    ArrayBufferClone, ArrayBufferCopyData, DetachArrayBuffer, HasDefinedArrayBufferDetachKey,
27    JS_ClearPendingException, JS_GetArrayBufferViewBuffer, JS_GetPendingException,
28    JS_NewBigInt64ArrayWithBuffer, JS_NewBigUint64ArrayWithBuffer, JS_NewDataView,
29    JS_NewFloat16ArrayWithBuffer, JS_NewFloat32ArrayWithBuffer, JS_NewFloat64ArrayWithBuffer,
30    JS_NewInt8ArrayWithBuffer, JS_NewInt16ArrayWithBuffer, JS_NewInt32ArrayWithBuffer,
31    JS_NewUint8ArrayWithBuffer, JS_NewUint8ClampedArrayWithBuffer, JS_NewUint16ArrayWithBuffer,
32    JS_NewUint32ArrayWithBuffer, NewArrayBuffer, NewArrayBufferWithContents,
33    StealArrayBufferContents,
34};
35use js::rust::{
36    CustomAutoRooterGuard, Handle, MutableHandleObject,
37    MutableHandleValue as SafeMutableHandleValue,
38};
39#[cfg(feature = "webgpu")]
40use js::typedarray::HeapArrayBuffer;
41use js::typedarray::{
42    ArrayBufferU8, ArrayBufferViewU8, CreateWith, TypedArray, TypedArrayElement,
43    TypedArrayElementCreator,
44};
45
46use crate::dom::bindings::error::{Error, Fallible};
47use crate::dom::bindings::trace::RootedTraceableBox;
48#[cfg(feature = "webgpu")]
49use crate::dom::globalscope::GlobalScope;
50
51pub(crate) type RootedTypedArray<T> = RootedTraceableBox<TypedArray<T, Box<Heap<*mut JSObject>>>>;
52
53/// Represents a `BufferSource` as defined in the WebIDL specification.
54///
55/// A `BufferSource` is either an `ArrayBuffer` or an `ArrayBufferView`, which
56/// provides a view onto an `ArrayBuffer`.
57///
58/// See: <https://webidl.spec.whatwg.org/#BufferSource>
59#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
60pub(crate) enum BufferSource {
61    /// Represents an `ArrayBufferView` (e.g., `Uint8Array`, `DataView`).
62    /// See: <https://webidl.spec.whatwg.org/#ArrayBufferView>
63    ArrayBufferView(Box<Heap<*mut JSObject>>),
64
65    /// Represents an `ArrayBuffer`, a fixed-length binary data buffer.
66    /// See: <https://webidl.spec.whatwg.org/#idl-ArrayBuffer>
67    ArrayBuffer(Box<Heap<*mut JSObject>>),
68}
69
70impl Clone for BufferSource {
71    fn clone(&self) -> Self {
72        match self {
73            BufferSource::ArrayBufferView(heap) => {
74                BufferSource::ArrayBufferView(Heap::boxed(heap.get()))
75            },
76            BufferSource::ArrayBuffer(heap) => BufferSource::ArrayBuffer(Heap::boxed(heap.get())),
77        }
78    }
79}
80
81pub(crate) fn create_heap_buffer_source_with_length<T>(
82    cx: &mut JSContext,
83    len: u32,
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());
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: &mut 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: &mut 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, Handle::from_raw(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: &mut 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, Handle::from_raw(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: &mut 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, Handle::from_raw(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: &mut 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, Handle::from_raw(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 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, Handle::from_raw(buffer.handle()), &mut is_shared) });
360                debug_assert!(!is_shared);
361
362                unsafe { ArrayBufferClone(cx, view_buffer.handle(), byte_offset, byte_length) }
363            },
364            BufferSource::ArrayBuffer(buffer) => unsafe {
365                ArrayBufferClone(
366                    cx,
367                    Handle::from_raw(buffer.handle()),
368                    byte_offset,
369                    byte_length,
370                )
371            },
372        };
373
374        if result.is_null() {
375            // Normalize SpiderMonkey failure: consume pending exception and
376            // map it to a DOM Error.
377            rooted!(&in(cx) let mut _ex = UndefinedValue());
378            unsafe {
379                // If SpiderMonkey set an exception, clear it so callers see a clean cx.
380                if JS_GetPendingException(cx, _ex.handle_mut()) {
381                    JS_ClearPendingException(cx);
382                }
383            }
384
385            Err(Error::Type(c"can't clone array buffer".to_owned()))
386        } else {
387            Ok(RootedTraceableBox::new(
388                HeapBufferSource::<ArrayBufferU8>::new(BufferSource::ArrayBuffer(Heap::boxed(
389                    result,
390                ))),
391            ))
392        }
393    }
394    /// <https://streams.spec.whatwg.org/#abstract-opdef-cloneasuint8array>
395    #[expect(unsafe_code)]
396    pub(crate) fn clone_as_uint8_array(
397        &self,
398        cx: &mut JSContext,
399    ) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferViewU8>>> {
400        match &self.buffer_source {
401            BufferSource::ArrayBufferView(buffer) => {
402                // Assert: O is an Object.
403                // Assert: O has an [[ViewedArrayBuffer]] internal slot.
404                assert!(unsafe { JS_IsArrayBufferViewObject(*buffer.handle()) });
405
406                // Assert: ! IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false.
407                assert!(!self.is_detached_buffer(cx));
408
409                // Let buffer be ? CloneArrayBuffer(O.[[ViewedArrayBuffer]],
410                // O.[[ByteOffset]], O.[[ByteLength]], %ArrayBuffer%).
411                let byte_offset = self.get_byte_offset();
412                let byte_length = self.byte_length();
413
414                let buffer = self.clone_array_buffer(cx, byte_offset, byte_length)?;
415
416                // Let array be ! Construct(%Uint8Array%, « buffer »).
417                // Return array.
418                construct_typed_array(cx, &Type::Uint8, &buffer, 0, byte_length as i64)
419            },
420            BufferSource::ArrayBuffer(_buffer) => {
421                unreachable!("BufferSource::ArrayBuffer does not have a view buffer.")
422            },
423        }
424    }
425
426    pub(crate) fn is_undefined(&self) -> bool {
427        match &self.buffer_source {
428            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
429                buffer.get().is_null()
430            },
431        }
432    }
433}
434
435impl<T> HeapBufferSource<T>
436where
437    T: TypedArrayElement + TypedArrayElementCreator + 'static,
438    T::Element: Clone + Copy,
439{
440    pub(crate) fn acquire_data(&self, cx: &mut JSContext) -> Result<Vec<T::Element>, ()> {
441        assert!(self.is_initialized());
442
443        typedarray!(&in(cx) let array: TypedArray = match &self.buffer_source {
444            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer)
445            => {
446                buffer.get()
447            },
448        });
449        let data = if let Ok(array) =
450            array as Result<CustomAutoRooterGuard<'_, TypedArray<T, *mut JSObject>>, &mut ()>
451        {
452            let data = array.to_vec();
453            let _ = self.detach_buffer(cx);
454            Ok(data)
455        } else {
456            Err(())
457        };
458
459        match &self.buffer_source {
460            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
461                buffer.set(ptr::null_mut());
462            },
463        }
464        data
465    }
466
467    pub(crate) fn copy_data_to(
468        &self,
469        cx: &mut JSContext,
470        dest: &mut [T::Element],
471        source_start: usize,
472        length: usize,
473    ) -> Result<(), ()> {
474        assert!(self.is_initialized());
475        typedarray!(&in(cx) let array: TypedArray = match &self.buffer_source {
476            BufferSource::ArrayBufferView(buffer) |  BufferSource::ArrayBuffer(buffer)
477            => {
478                buffer.get()
479            },
480        });
481        let Ok(array) =
482            array as Result<CustomAutoRooterGuard<'_, TypedArray<T, *mut JSObject>>, &mut ()>
483        else {
484            return Err(());
485        };
486        unsafe {
487            let slice = (*array).as_slice();
488            dest.copy_from_slice(&slice[source_start..length]);
489        }
490        Ok(())
491    }
492
493    pub(crate) fn copy_data_from(
494        &self,
495        cx: &mut JSContext,
496        source: CustomAutoRooterGuard<TypedArray<T, *mut JSObject>>,
497        dest_start: usize,
498        length: usize,
499    ) -> Result<(), ()> {
500        assert!(self.is_initialized());
501        typedarray!(&in(cx) let mut array: TypedArray = match &self.buffer_source {
502            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer)
503            => {
504                buffer.get()
505            },
506        });
507        let Ok(mut array) =
508            array as Result<CustomAutoRooterGuard<'_, TypedArray<T, *mut JSObject>>, &mut ()>
509        else {
510            return Err(());
511        };
512        unsafe {
513            let slice = (*array).as_mut_slice();
514            let (_, dest) = slice.split_at_mut(dest_start);
515            dest[0..length].copy_from_slice(&source.as_slice()[0..length])
516        }
517        Ok(())
518    }
519
520    pub(crate) fn set_data(&self, cx: &mut JSContext, data: &[T::Element]) -> Result<(), ()> {
521        rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
522        let _ = create_buffer_source::<T>(cx, data, array.handle_mut())?;
523
524        match &self.buffer_source {
525            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
526                buffer.set(*array);
527            },
528        }
529        Ok(())
530    }
531
532    /// <https://streams.spec.whatwg.org/#abstract-opdef-cancopydatablockbytes>
533    // CanCopyDataBlockBytes(descriptorBuffer, destStart, queueBuffer, queueByteOffset, bytesToCopy)
534    pub(crate) fn can_copy_data_block_bytes(
535        &self,
536        cx: &mut JSContext,
537        to_index: usize,
538        from_buffer: &HeapBufferSource<ArrayBufferU8>,
539        from_index: usize,
540        bytes_to_copy: usize,
541    ) -> bool {
542        // Assert: toBuffer is an Object.
543        // Assert: toBuffer has an [[ArrayBufferData]] internal slot.
544        assert!(self.is_array_buffer_object());
545
546        // Assert: fromBuffer is an Object.
547        // Assert: fromBuffer has an [[ArrayBufferData]] internal slot.
548        assert!(from_buffer.is_array_buffer_object());
549
550        // If toBuffer is fromBuffer, return false.
551        match &self.buffer_source {
552            BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => {
553                match &from_buffer.buffer_source {
554                    BufferSource::ArrayBufferView(from_heap) |
555                    BufferSource::ArrayBuffer(from_heap) => {
556                        if std::ptr::eq(heap.get(), from_heap.get()) {
557                            return false;
558                        }
559                    },
560                }
561            },
562        }
563
564        // If ! IsDetachedBuffer(toBuffer) is true, return false.
565        if self.is_detached_buffer(cx) {
566            return false;
567        }
568
569        // If ! IsDetachedBuffer(fromBuffer) is true, return false.
570        if from_buffer.is_detached_buffer(cx) {
571            return false;
572        }
573
574        // If toIndex + count > toBuffer.[[ArrayBufferByteLength]], return false.
575        if to_index + bytes_to_copy > self.byte_length() {
576            return false;
577        }
578
579        // If fromIndex + count > fromBuffer.[[ArrayBufferByteLength]], return false.
580        if from_index + bytes_to_copy > from_buffer.byte_length() {
581            return false;
582        }
583
584        // Return true.
585        true
586    }
587
588    pub(crate) fn copy_data_block_bytes(
589        &self,
590        cx: &mut JSContext,
591        dest_start: usize,
592        from_buffer: &HeapBufferSource<ArrayBufferU8>,
593        from_byte_offset: usize,
594        bytes_to_copy: usize,
595    ) -> bool {
596        match &self.buffer_source {
597            BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => unsafe {
598                match &from_buffer.buffer_source {
599                    BufferSource::ArrayBufferView(from_heap) |
600                    BufferSource::ArrayBuffer(from_heap) => ArrayBufferCopyData(
601                        cx,
602                        Handle::from_raw(heap.handle()),
603                        dest_start,
604                        Handle::from_raw(from_heap.handle()),
605                        from_byte_offset,
606                        bytes_to_copy,
607                    ),
608                }
609            },
610        }
611    }
612
613    /// <https://streams.spec.whatwg.org/#can-transfer-array-buffer>
614    pub(crate) fn can_transfer_array_buffer(&self, cx: &mut JSContext) -> bool {
615        // Assert: O is an Object.
616        // Assert: O has an [[ArrayBufferData]] internal slot.
617        assert!(self.is_array_buffer_object());
618
619        // If ! IsDetachedBuffer(O) is true, return false.
620        if self.is_detached_buffer(cx) {
621            return false;
622        }
623
624        // If SameValue(O.[[ArrayBufferDetachKey]], undefined) is false, return false.
625        // Return true.
626        let mut is_defined = false;
627        match &self.buffer_source {
628            BufferSource::ArrayBufferView(heap) | BufferSource::ArrayBuffer(heap) => unsafe {
629                if !HasDefinedArrayBufferDetachKey(
630                    cx,
631                    Handle::from_raw(heap.handle()),
632                    &mut is_defined,
633                ) {
634                    return false;
635                }
636            },
637        }
638
639        !is_defined
640    }
641
642    /// <https://streams.spec.whatwg.org/#transfer-array-buffer>
643    pub(crate) fn transfer_array_buffer(
644        &self,
645        cx: &mut JSContext,
646    ) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferU8>>> {
647        assert!(self.is_array_buffer_object());
648
649        // Assert: ! IsDetachedBuffer(O) is false.
650        assert!(!self.is_detached_buffer(cx));
651
652        // Let arrayBufferByteLength be O.[[ArrayBufferByteLength]].
653        // Step 3 (Reordered)
654        let buffer_length = self.byte_length();
655
656        // Let arrayBufferData be O.[[ArrayBufferData]].
657        // Step 2 (Reordered)
658        let buffer_data = match &self.buffer_source {
659            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => unsafe {
660                StealArrayBufferContents(cx, Handle::from_raw(buffer.handle()))
661            },
662        };
663
664        // Perform ? DetachArrayBuffer(O).
665        // This will throw an exception if O has an [[ArrayBufferDetachKey]] that is not undefined,
666        // such as a WebAssembly.Memory’s buffer. [WASM-JS-API-1]
667        if !self.detach_buffer(cx) {
668            rooted!(&in(cx) let mut rval = UndefinedValue());
669            unsafe {
670                assert!(JS_GetPendingException(cx, rval.handle_mut()));
671                JS_ClearPendingException(cx)
672            };
673
674            Err(Error::Type(c"can't transfer array buffer".to_owned()))
675        } else {
676            // Return a new ArrayBuffer object, created in the current Realm,
677            // whose [[ArrayBufferData]] internal slot value is arrayBufferData and
678            // whose [[ArrayBufferByteLength]] internal slot value is arrayBufferByteLength.
679            Ok(RootedTraceableBox::new(
680                HeapBufferSource::<ArrayBufferU8>::new(BufferSource::ArrayBuffer(Heap::boxed(
681                    unsafe { NewArrayBufferWithContents(cx, buffer_length, buffer_data) },
682                ))),
683            ))
684        }
685    }
686}
687
688unsafe impl<T> crate::dom::bindings::trace::JSTraceable for HeapBufferSource<T> {
689    #[inline]
690    unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
691        match &self.buffer_source {
692            BufferSource::ArrayBufferView(buffer) | BufferSource::ArrayBuffer(buffer) => {
693                unsafe { buffer.trace(tracer) };
694            },
695        }
696    }
697}
698
699/// <https://webidl.spec.whatwg.org/#arraybufferview-create>
700pub(crate) fn create_buffer_source<T>(
701    cx: &mut JSContext,
702    data: &[T::Element],
703    mut dest: MutableHandleObject,
704) -> Result<RootedTypedArray<T>, ()>
705where
706    T: TypedArrayElement + TypedArrayElementCreator,
707{
708    let res = unsafe {
709        TypedArray::<T, *mut JSObject>::create(
710            cx.raw_cx(),
711            CreateWith::Slice(data),
712            dest.reborrow(),
713        )
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: &mut JSContext,
725    len: usize,
726    mut dest: MutableHandleObject,
727) -> Result<RootedTypedArray<T>, ()>
728where
729    T: TypedArrayElement + TypedArrayElementCreator,
730{
731    let res = unsafe {
732        TypedArray::<T, *mut JSObject>::create(
733            cx.raw_cx(),
734            CreateWith::Length(len),
735            dest.reborrow(),
736        )
737    };
738
739    if res.is_err() {
740        Err(())
741    } else {
742        TypedArray::from(dest.get()).map(RootedTraceableBox::new)
743    }
744}
745
746pub(crate) fn byte_size(byte_type: Type) -> u64 {
747    match byte_type {
748        Type::Int8 | Type::Uint8 | Type::Uint8Clamped => 1,
749        Type::Int16 | Type::Uint16 | Type::Float16 => 2,
750        Type::Int32 | Type::Uint32 | Type::Float32 => 4,
751        Type::Int64 | Type::Float64 | Type::BigInt64 | Type::BigUint64 => 8,
752        Type::Simd128 => 16,
753        _ => unreachable!("invalid scalar type"),
754    }
755}
756
757#[derive(Clone, Eq, JSTraceable, MallocSizeOf, PartialEq)]
758pub(crate) enum Constructor {
759    DataView,
760    Name(
761        #[ignore_malloc_size_of = "mozjs"]
762        #[no_trace]
763        Type,
764    ),
765}
766
767pub(crate) fn create_buffer_source_with_constructor(
768    cx: &mut JSContext,
769    constructor: &Constructor,
770    buffer_source: &HeapBufferSource<ArrayBufferU8>,
771    byte_offset: usize,
772    byte_length: usize,
773) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferViewU8>>> {
774    match &buffer_source.buffer_source {
775        BufferSource::ArrayBuffer(heap) => match constructor {
776            Constructor::DataView => Ok(RootedTraceableBox::new(HeapBufferSource::new(
777                BufferSource::ArrayBufferView(Heap::boxed(unsafe {
778                    JS_NewDataView(
779                        cx,
780                        Handle::from_raw(heap.handle()),
781                        byte_offset,
782                        byte_length,
783                    )
784                })),
785            ))),
786            Constructor::Name(name_type) => construct_typed_array(
787                cx,
788                name_type,
789                buffer_source,
790                byte_offset,
791                byte_length as i64,
792            ),
793        },
794        BufferSource::ArrayBufferView(_) => {
795            unreachable!("Can not create a new ArrayBufferView from an existing ArrayBufferView");
796        },
797    }
798}
799
800/// Helper function to construct different TypedArray views
801fn construct_typed_array(
802    cx: &mut JSContext,
803    name_type: &Type,
804    buffer_source: &HeapBufferSource<ArrayBufferU8>,
805    byte_offset: usize,
806    byte_length: i64,
807) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferViewU8>>> {
808    match &buffer_source.buffer_source {
809        BufferSource::ArrayBuffer(heap) => {
810            let array_view = unsafe {
811                match name_type {
812                    Type::Int8 => JS_NewInt8ArrayWithBuffer(
813                        cx,
814                        Handle::from_raw(heap.handle()),
815                        byte_offset,
816                        byte_length,
817                    ),
818                    Type::Uint8 => JS_NewUint8ArrayWithBuffer(
819                        cx,
820                        Handle::from_raw(heap.handle()),
821                        byte_offset,
822                        byte_length,
823                    ),
824                    Type::Uint16 => JS_NewUint16ArrayWithBuffer(
825                        cx,
826                        Handle::from_raw(heap.handle()),
827                        byte_offset,
828                        byte_length,
829                    ),
830                    Type::Int16 => JS_NewInt16ArrayWithBuffer(
831                        cx,
832                        Handle::from_raw(heap.handle()),
833                        byte_offset,
834                        byte_length,
835                    ),
836                    Type::Int32 => JS_NewInt32ArrayWithBuffer(
837                        cx,
838                        Handle::from_raw(heap.handle()),
839                        byte_offset,
840                        byte_length,
841                    ),
842                    Type::Uint32 => JS_NewUint32ArrayWithBuffer(
843                        cx,
844                        Handle::from_raw(heap.handle()),
845                        byte_offset,
846                        byte_length,
847                    ),
848                    Type::Float32 => JS_NewFloat32ArrayWithBuffer(
849                        cx,
850                        Handle::from_raw(heap.handle()),
851                        byte_offset,
852                        byte_length,
853                    ),
854                    Type::Float64 => JS_NewFloat64ArrayWithBuffer(
855                        cx,
856                        Handle::from_raw(heap.handle()),
857                        byte_offset,
858                        byte_length,
859                    ),
860                    Type::Uint8Clamped => JS_NewUint8ClampedArrayWithBuffer(
861                        cx,
862                        Handle::from_raw(heap.handle()),
863                        byte_offset,
864                        byte_length,
865                    ),
866                    Type::BigInt64 => JS_NewBigInt64ArrayWithBuffer(
867                        cx,
868                        Handle::from_raw(heap.handle()),
869                        byte_offset,
870                        byte_length,
871                    ),
872                    Type::BigUint64 => JS_NewBigUint64ArrayWithBuffer(
873                        cx,
874                        Handle::from_raw(heap.handle()),
875                        byte_offset,
876                        byte_length,
877                    ),
878                    Type::Float16 => JS_NewFloat16ArrayWithBuffer(
879                        cx,
880                        Handle::from_raw(heap.handle()),
881                        byte_offset,
882                        byte_length,
883                    ),
884                    Type::Int64 | Type::Simd128 | Type::MaxTypedArrayViewType => {
885                        unreachable!("Invalid TypedArray type")
886                    },
887                }
888            };
889
890            Ok(RootedTraceableBox::new(HeapBufferSource::new(
891                BufferSource::ArrayBufferView(Heap::boxed(array_view)),
892            )))
893        },
894        BufferSource::ArrayBufferView(_) => {
895            unreachable!("Can not create a new ArrayBufferView from an existing ArrayBufferView");
896        },
897    }
898}
899
900pub(crate) fn create_array_buffer_with_size(
901    cx: &mut JSContext,
902    size: usize,
903) -> Fallible<RootedTraceableBox<HeapBufferSource<ArrayBufferU8>>> {
904    let result = unsafe { NewArrayBuffer(cx, size) };
905    if result.is_null() {
906        rooted!(&in(cx) let mut rval = UndefinedValue());
907        unsafe {
908            assert!(JS_GetPendingException(cx, rval.handle_mut()));
909            JS_ClearPendingException(cx)
910        };
911
912        Err(Error::Type(c"can't create array buffer".to_owned()))
913    } else {
914        Ok(RootedTraceableBox::new(
915            HeapBufferSource::<ArrayBufferU8>::new(BufferSource::ArrayBuffer(Heap::boxed(result))),
916        ))
917    }
918}
919
920#[cfg(feature = "webgpu")]
921#[derive(JSTraceable, MallocSizeOf)]
922#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
923pub(crate) struct DataBlock {
924    #[conditional_malloc_size_of]
925    data: Arc<Box<[u8]>>,
926    /// Data views (mutable subslices of data)
927    data_views: Vec<DataView>,
928}
929
930/// Returns true if two non-inclusive ranges overlap
931// https://stackoverflow.com/questions/3269434/whats-the-most-efficient-way-to-test-if-two-ranges-overlap
932#[cfg(feature = "webgpu")]
933fn range_overlap<T: std::cmp::PartialOrd>(range1: &Range<T>, range2: &Range<T>) -> bool {
934    range1.start < range2.end && range2.start < range1.end
935}
936
937#[cfg(feature = "webgpu")]
938impl DataBlock {
939    pub(crate) fn new_zeroed(size: usize) -> Self {
940        let data = vec![0; size];
941        Self {
942            data: Arc::new(data.into_boxed_slice()),
943            data_views: Vec::new(),
944        }
945    }
946
947    /// Panics if there is any active view or src data is not same length
948    pub(crate) fn load(&mut self, src: &[u8]) {
949        // `Arc::get_mut` ensures there are no views
950        Arc::get_mut(&mut self.data).unwrap().clone_from_slice(src)
951    }
952
953    /// Panics if there is any active view
954    pub(crate) fn data(&mut self) -> &mut [u8] {
955        // `Arc::get_mut` ensures there are no views
956        Arc::get_mut(&mut self.data).unwrap()
957    }
958
959    pub(crate) fn clear_views(&mut self) {
960        self.data_views.clear()
961    }
962
963    /// Returns error if requested range is already mapped
964    pub(crate) fn view(
965        &mut self,
966        cx: &mut JSContext,
967        range: Range<usize>,
968    ) -> Result<&DataView, ()> {
969        if self
970            .data_views
971            .iter()
972            .any(|view| range_overlap(&view.range, &range))
973        {
974            return Err(());
975        }
976        let range_len = range
977            .end
978            .checked_sub(range.start)
979            .expect("range end must be >= range start");
980        assert!(range.end <= self.data.len());
981
982        /// `freeFunc()` must be threadsafe, should be safely callable from any thread
983        /// without causing conflicts, unexpected behavior.
984        unsafe extern "C" fn free_func(_contents: *mut c_void, free_user_data: *mut c_void) {
985            let raw: *const Box<[u8]> = free_user_data.cast();
986            // SAFETY: `free_func` is called by SM and returns ownership of the Arc we
987            // leaked below with `into_raw`. Hence it is safe to reconstruct the Arc,
988            // and destroy it to release the reference count.
989            drop(unsafe { Arc::from_raw(raw) });
990        }
991        let raw: *const Box<[u8]> = Arc::into_raw(Arc::clone(&self.data));
992        // SAFETY: We leaked the Arc, so the underlying slice will stay alive
993        // until `free_func` is called. `range.start..range.end` is inside
994        // the valid range of the slice.
995        let data_ptr = unsafe { (**raw).as_ptr().add(range.start) };
996        rooted!(&in(cx) let object = unsafe {
997            NewExternalArrayBuffer(
998                cx,
999                range_len,
1000                // FIXME(jschwe): I believe casting to a mutable pointer is unsound.
1001                // We would need interior mutability.
1002                data_ptr.cast_mut().cast(),
1003                Some(free_func),
1004                raw as _,
1005            )
1006        });
1007        self.data_views.push(DataView {
1008            range,
1009            buffer: HeapArrayBuffer::from(*object).unwrap(),
1010        });
1011        Ok(self.data_views.last().unwrap())
1012    }
1013}
1014
1015#[cfg(feature = "webgpu")]
1016#[derive(JSTraceable, MallocSizeOf)]
1017#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1018pub(crate) struct DataView {
1019    #[no_trace]
1020    range: Range<usize>,
1021    #[ignore_malloc_size_of = "defined in mozjs"]
1022    buffer: HeapArrayBuffer,
1023}
1024
1025#[cfg(feature = "webgpu")]
1026impl DataView {
1027    pub(crate) fn array_buffer(&self) -> RootedTraceableBox<HeapArrayBuffer> {
1028        RootedTraceableBox::new(unsafe {
1029            HeapArrayBuffer::from(self.buffer.underlying_object().get()).unwrap()
1030        })
1031    }
1032}
1033
1034#[cfg(feature = "webgpu")]
1035impl Drop for DataView {
1036    #[expect(unsafe_code)]
1037    fn drop(&mut self) {
1038        let cx = GlobalScope::get_cx();
1039        assert!(unsafe {
1040            js::jsapi::DetachArrayBuffer(*cx, self.buffer.underlying_object().handle())
1041        })
1042    }
1043}