diff --git a/components/script/dom/audiobuffer.rs b/components/script/dom/audiobuffer.rs index 5645d418ee5..b26969bb013 100644 --- a/components/script/dom/audiobuffer.rs +++ b/components/script/dom/audiobuffer.rs @@ -3,13 +3,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cmp::min; -use std::ptr::{self, NonNull}; use dom_struct::dom_struct; -use js::jsapi::{Heap, JSObject, JS_GetArrayBufferViewBuffer}; -use js::rust::wrappers::DetachArrayBuffer; use js::rust::{CustomAutoRooterGuard, HandleObject}; -use js::typedarray::{CreateWith, Float32Array}; +use js::typedarray::Float32Array; use servo_media::audio::buffer_source_node::AudioBuffer as ServoMediaAudioBuffer; use crate::dom::audionode::MAX_CHANNEL_COUNT; @@ -21,6 +18,7 @@ use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::num::Finite; use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector}; use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::typedarrays::HeapFloat32Array; use crate::dom::globalscope::GlobalScope; use crate::dom::window::Window; use crate::realms::enter_realm; @@ -31,8 +29,6 @@ use crate::script_runtime::JSContext; pub const MIN_SAMPLE_RATE: f32 = 8000.; pub const MAX_SAMPLE_RATE: f32 = 192000.; -type JSAudioChannel = Heap<*mut JSObject>; - /// The AudioBuffer keeps its data either in js_channels /// or in shared_channels if js_channels buffers are detached. /// @@ -46,7 +42,7 @@ pub struct AudioBuffer { reflector_: Reflector, /// Float32Arrays returned by calls to GetChannelData. #[ignore_malloc_size_of = "mozjs"] - js_channels: DomRefCell>, + js_channels: DomRefCell>, /// Aggregates the data from js_channels. /// This is Some iff the buffers in js_channels are detached. #[ignore_malloc_size_of = "servo_media"] @@ -66,7 +62,9 @@ impl AudioBuffer { #[allow(crown::unrooted_must_root)] #[allow(unsafe_code)] pub fn new_inherited(number_of_channels: u32, length: u32, sample_rate: f32) -> AudioBuffer { - let vec = (0..number_of_channels).map(|_| Heap::default()).collect(); + let vec = (0..number_of_channels) + .map(|_| HeapFloat32Array::default()) + .collect(); AudioBuffer { reflector_: Reflector::new(), js_channels: DomRefCell::new(vec), @@ -152,34 +150,23 @@ impl AudioBuffer { *self.shared_channels.borrow_mut() = Some(channels); } - #[allow(unsafe_code)] fn restore_js_channel_data(&self, cx: JSContext) -> bool { let _ac = enter_realm(&*self); for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() { - if !channel.get().is_null() { + if channel.is_initialized() { // Already have data in JS array. continue; } - rooted!(in (*cx) let mut array = ptr::null_mut::()); if let Some(ref shared_channels) = *self.shared_channels.borrow() { // Step 4. of // https://webaudio.github.io/web-audio-api/#acquire-the-content // "Attach ArrayBuffers containing copies of the data to the AudioBuffer, // to be returned by the next call to getChannelData()". - unsafe { - if Float32Array::create( - *cx, - CreateWith::Slice(&shared_channels.buffers[i]), - array.handle_mut(), - ) - .is_err() - { - return false; - } + if channel.set_data(cx, &shared_channels.buffers[i]).is_err() { + return false; } } - channel.set(array.get()); } *self.shared_channels.borrow_mut() = None; @@ -188,7 +175,6 @@ impl AudioBuffer { } // https://webaudio.github.io/web-audio-api/#acquire-the-content - #[allow(unsafe_code)] fn acquire_contents(&self) -> Option { let mut result = ServoMediaAudioBuffer::new( self.number_of_channels as u8, @@ -198,31 +184,12 @@ impl AudioBuffer { let cx = GlobalScope::get_cx(); for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() { // Step 1. - if channel.get().is_null() { + if !channel.is_initialized() { return None; } - // Step 2. - let channel_data = unsafe { - typedarray!(in(*cx) let array: Float32Array = channel.get()); - if let Ok(array) = array { - let data = array.to_vec(); - let mut is_shared = false; - rooted!(in (*cx) let view_buffer = - JS_GetArrayBufferViewBuffer(*cx, channel.handle(), &mut is_shared)); - // This buffer is always created unshared - debug_assert!(!is_shared); - let _ = DetachArrayBuffer(*cx, view_buffer.handle()); - data - } else { - return None; - } - }; - - channel.set(ptr::null_mut()); - // Step 3. - result.buffers[i] = channel_data; + result.buffers[i] = channel.acquire_data(cx).ok()?; } Some(result) @@ -261,8 +228,7 @@ impl AudioBufferMethods for AudioBuffer { } // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata - #[allow(unsafe_code)] - fn GetChannelData(&self, cx: JSContext, channel: u32) -> Fallible> { + fn GetChannelData(&self, cx: JSContext, channel: u32) -> Fallible { if channel >= self.number_of_channels { return Err(Error::IndexSize); } @@ -270,11 +236,10 @@ impl AudioBufferMethods for AudioBuffer { if !self.restore_js_channel_data(cx) { return Err(Error::JSFailed); } - unsafe { - Ok(NonNull::new_unchecked( - self.js_channels.borrow()[channel as usize].get(), - )) - } + + self.js_channels.borrow()[channel as usize] + .get_internal() + .map_err(|_| Error::JSFailed) } // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copyfromchannel @@ -300,12 +265,13 @@ impl AudioBufferMethods for AudioBuffer { let mut dest = Vec::with_capacity(destination.len()); // We either copy form js_channels or shared_channels. - let js_channel = self.js_channels.borrow()[channel_number].get(); - if !js_channel.is_null() { - typedarray!(in(*cx) let array: Float32Array = js_channel); - if let Ok(array) = array { - let data = unsafe { array.as_slice() }; - dest.extend_from_slice(&data[offset..offset + bytes_to_copy]); + let js_channel = &self.js_channels.borrow()[channel_number]; + if js_channel.is_initialized() { + if js_channel + .copy_data_to(cx, &mut dest, offset, offset + bytes_to_copy) + .is_err() + { + return Err(Error::IndexSize); } } else if let Some(ref shared_channels) = *self.shared_channels.borrow() { if let Some(shared_channel) = shared_channels.buffers.get(channel_number) { @@ -321,7 +287,6 @@ impl AudioBufferMethods for AudioBuffer { } // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copytochannel - #[allow(unsafe_code)] fn CopyToChannel( &self, source: CustomAutoRooterGuard, @@ -341,25 +306,15 @@ impl AudioBufferMethods for AudioBuffer { return Err(Error::JSFailed); } - let js_channel = self.js_channels.borrow()[channel_number as usize].get(); - if js_channel.is_null() { + let js_channel = &self.js_channels.borrow()[channel_number as usize]; + if !js_channel.is_initialized() { // The array buffer was detached. return Err(Error::IndexSize); } - typedarray!(in(*cx) let js_channel: Float32Array = js_channel); - if let Ok(mut js_channel) = js_channel { - let bytes_to_copy = min(self.length - start_in_channel, source.len() as u32) as usize; - let js_channel_data = unsafe { js_channel.as_mut_slice() }; - let (_, js_channel_data) = js_channel_data.split_at_mut(start_in_channel as usize); - unsafe { - js_channel_data[0..bytes_to_copy] - .copy_from_slice(&source.as_slice()[0..bytes_to_copy]) - }; - } else { - return Err(Error::IndexSize); - } - - Ok(()) + let bytes_to_copy = min(self.length - start_in_channel, source.len() as u32) as usize; + js_channel + .copy_data_from(cx, source, start_in_channel as usize, bytes_to_copy) + .map_err(|_| Error::IndexSize) } } diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 361d6d60edb..a4e2bb01a81 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -119,7 +119,8 @@ builtinNames = { IDLType.Tags.unrestricted_float: 'f32', IDLType.Tags.float: 'Finite', IDLType.Tags.unrestricted_double: 'f64', - IDLType.Tags.double: 'Finite' + IDLType.Tags.double: 'Finite', + IDLType.Tags.float32array: 'Float32Array' } numericTags = [ @@ -1459,16 +1460,26 @@ def getConversionConfigForType(type, isEnforceRange, isClamp, treatNullAs): return "()" +def todo_switch_float_32(des): + return des.interface.identifier.name in ['XRView', 'XRRigidTransform', 'XRRay', 'GamepadPose'] + + +def builtin_return_type(returnType): + result = CGGeneric(builtinNames[returnType.tag()]) + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + + # Returns a CGThing containing the type of the return value. def getRetvalDeclarationForType(returnType, descriptorProvider): if returnType is None or returnType.isUndefined(): # Nothing to declare return CGGeneric("()") if returnType.isPrimitive() and returnType.tag() in builtinNames: - result = CGGeneric(builtinNames[returnType.tag()]) - if returnType.nullable(): - result = CGWrapper(result, pre="Option<", post=">") - return result + return builtin_return_type(returnType) + if returnType.isTypedArray() and returnType.tag() in builtinNames and not todo_switch_float_32(descriptorProvider): + return builtin_return_type(returnType) if returnType.isDOMString(): result = CGGeneric("DOMString") if returnType.nullable(): @@ -6494,6 +6505,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'js::rust::define_properties', 'js::rust::get_object_class', 'js::typedarray', + 'js::typedarray::Float32Array', 'crate::dom', 'crate::dom::bindings', 'crate::dom::bindings::codegen::InterfaceObjectMap', diff --git a/components/script/dom/bindings/mod.rs b/components/script/dom/bindings/mod.rs index 85bfad112c1..c71d5f322f4 100644 --- a/components/script/dom/bindings/mod.rs +++ b/components/script/dom/bindings/mod.rs @@ -158,6 +158,7 @@ pub mod str; pub mod structuredclone; pub mod trace; pub mod transferable; +pub mod typedarrays; pub mod utils; pub mod weakref; pub mod xmlname; diff --git a/components/script/dom/bindings/typedarrays.rs b/components/script/dom/bindings/typedarrays.rs new file mode 100644 index 00000000000..7bf2c010ec1 --- /dev/null +++ b/components/script/dom/bindings/typedarrays.rs @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#![allow(unsafe_code)] + +use std::ptr; + +use js::jsapi::{Heap, JSObject, JS_GetArrayBufferViewBuffer}; +use js::rust::wrappers::DetachArrayBuffer; +use js::rust::{CustomAutoRooterGuard, MutableHandleObject}; +use js::typedarray::{CreateWith, Float32Array}; + +use crate::script_runtime::JSContext; + +pub fn create_float32_array( + cx: JSContext, + data: &[f32], + dest: MutableHandleObject, +) -> Result { + let res = unsafe { Float32Array::create(*cx, CreateWith::Slice(data), dest) }; + if res.is_err() { + Err(()) + } else { + Float32Array::from(dest.get()) + } +} + +#[derive(Default, JSTraceable)] +pub struct HeapFloat32Array { + internal: Box>, +} + +impl HeapFloat32Array { + pub fn set_data(&self, cx: JSContext, data: &[f32]) -> Result<(), ()> { + rooted!(in (*cx) let mut array = ptr::null_mut::()); + let _ = create_float32_array(cx, data, array.handle_mut())?; + self.internal.set(*array); + Ok(()) + } + + pub fn acquire_data(&self, cx: JSContext) -> Result, ()> { + assert!(self.is_initialized()); + typedarray!(in(*cx) let array: Float32Array = self.internal.get()); + let data = if let Ok(array) = array { + let data = array.to_vec(); + let mut is_shared = false; + unsafe { + rooted!(in (*cx) let view_buffer = + JS_GetArrayBufferViewBuffer(*cx, self.internal.handle(), &mut is_shared)); + // This buffer is always created unshared + debug_assert!(!is_shared); + let _ = DetachArrayBuffer(*cx, view_buffer.handle()); + } + Ok(data) + } else { + Err(()) + }; + self.internal.set(ptr::null_mut()); + data + } + + pub fn copy_data_to( + &self, + cx: JSContext, + dest: &mut [f32], + source_start: usize, + length: usize, + ) -> Result<(), ()> { + assert!(self.is_initialized()); + typedarray!(in(*cx) let array: Float32Array = self.internal.get()); + let Ok(array) = array else { return Err(()) }; + unsafe { + let slice = (*array).as_slice(); + dest.copy_from_slice(&slice[source_start..length]); + } + Ok(()) + } + + pub fn copy_data_from( + &self, + cx: JSContext, + source: CustomAutoRooterGuard, + dest_start: usize, + length: usize, + ) -> Result<(), ()> { + assert!(self.is_initialized()); + typedarray!(in(*cx) let mut array: Float32Array = self.internal.get()); + let Ok(mut array) = array else { return Err(()) }; + unsafe { + let slice = (*array).as_mut_slice(); + let (_, dest) = slice.split_at_mut(dest_start); + dest[0..length].copy_from_slice(&source.as_slice()[0..length]) + } + Ok(()) + } + + pub fn is_initialized(&self) -> bool { + !self.internal.get().is_null() + } + + pub fn get_internal(&self) -> Result { + Float32Array::from(self.internal.get()) + } +} diff --git a/components/script/dom/dommatrixreadonly.rs b/components/script/dom/dommatrixreadonly.rs index 26413b02452..9e3bd868a5f 100644 --- a/components/script/dom/dommatrixreadonly.rs +++ b/components/script/dom/dommatrixreadonly.rs @@ -25,6 +25,7 @@ use crate::dom::bindings::error::Fallible; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector}; use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::typedarrays::create_float32_array; use crate::dom::dommatrix::DOMMatrix; use crate::dom::dompoint::DOMPoint; use crate::dom::globalscope::GlobalScope; @@ -676,8 +677,7 @@ impl DOMMatrixReadOnlyMethods for DOMMatrixReadOnly { } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-tofloat32array - #[allow(unsafe_code)] - fn ToFloat32Array(&self, cx: JSContext) -> NonNull { + fn ToFloat32Array(&self, cx: JSContext) -> Float32Array { let vec: Vec = self .matrix .borrow() @@ -685,11 +685,9 @@ impl DOMMatrixReadOnlyMethods for DOMMatrixReadOnly { .iter() .map(|&x| x as f32) .collect(); - unsafe { - rooted!(in (*cx) let mut array = ptr::null_mut::()); - let _ = Float32Array::create(*cx, CreateWith::Slice(&vec), array.handle_mut()).unwrap(); - NonNull::new_unchecked(array.get()) - } + rooted!(in (*cx) let mut array = ptr::null_mut::()); + create_float32_array(cx, &vec, array.handle_mut()) + .expect("Converting matrix to float32 array should never fail") } // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-tofloat64array diff --git a/third_party/WebIDL/WebIDL.py b/third_party/WebIDL/WebIDL.py index e1d973f5fe6..20b47d3ad71 100644 --- a/third_party/WebIDL/WebIDL.py +++ b/third_party/WebIDL/WebIDL.py @@ -2401,6 +2401,7 @@ class IDLType(IDLObject): "object", # Funny stuff "interface", + "float32array", "dictionary", "enum", "callback", @@ -3640,7 +3641,7 @@ class IDLBuiltinType(IDLType): Types.Uint16Array: IDLType.Tags.interface, Types.Int32Array: IDLType.Tags.interface, Types.Uint32Array: IDLType.Tags.interface, - Types.Float32Array: IDLType.Tags.interface, + Types.Float32Array: IDLType.Tags.float32array, Types.Float64Array: IDLType.Tags.interface, Types.ReadableStream: IDLType.Tags.interface, }