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