From 7b495468f6a199fc90f3aa8a3c94f3005edcc052 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 24 Sep 2018 13:49:12 -0700 Subject: [PATCH] Implement support for `Uint8ClampedArray` This commit implements support for binding APIs that take `Uint8ClampedArray` in JS. This is pretty rare but comes up in a `web-sys` binding or two, and we're now able to bind these APIs instead of having to omit the bindings. The `Uint8ClampedArray` type is bound by using the `Clamped` marker struct in Rust. For example this is declaring a JS API that takes `Uint8ClampedArray`: use wasm_bindgen::Clamped; #[wasm_bindgen] extern { fn takes_clamped(a: Clamped<&[u8]>); } The `Clamped` type currently only works when wrapping the `&[u8]`, `&mut [u8]`, and `Vec` types. Everything else will produce an error at `wasm-bindgen` time. Closes #421 --- crates/cli-support/src/descriptor.rs | 19 ++++++++++++ crates/cli-support/src/js/js2rust.rs | 2 +- crates/cli-support/src/js/mod.rs | 19 +++++++++++- crates/cli-support/src/js/rust2js.rs | 2 +- crates/webidl-tests/array.rs | 18 +++++++----- crates/webidl/src/idl_type.rs | 44 +++++++++++++++++++++------- crates/webidl/src/lib.rs | 2 +- crates/webidl/src/util.rs | 4 +-- examples/julia_set/Cargo.toml | 1 - examples/julia_set/src/lib.rs | 37 ++++------------------- src/convert/impls.rs | 18 +++++++++++- src/describe.rs | 10 ++++++- src/lib.rs | 31 +++++++++++++++++++- tests/wasm/slice.js | 8 +++++ tests/wasm/slice.rs | 14 +++++++++ 15 files changed, 169 insertions(+), 60 deletions(-) diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index 3b87f07d..3b8d2f01 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -35,6 +35,7 @@ tys! { CHAR OPTIONAL UNIT + CLAMPED } #[derive(Debug)] @@ -63,6 +64,7 @@ pub enum Descriptor { Char, Option(Box), Unit, + Clamped(Box), } #[derive(Debug)] @@ -81,6 +83,7 @@ pub struct Closure { pub enum VectorKind { I8, U8, + ClampedU8, I16, U16, I32, @@ -131,6 +134,7 @@ impl Descriptor { } CHAR => Descriptor::Char, UNIT => Descriptor::Unit, + CLAMPED => Descriptor::Clamped(Box::new(Descriptor::_decode(data))), other => panic!("unknown descriptor: {}", other), } } @@ -219,6 +223,12 @@ impl Descriptor { Descriptor::Slice(ref d) => &**d, _ => return None, }, + Descriptor::Clamped(ref d) => { + match d.vector_kind()? { + VectorKind::U8 => return Some(VectorKind::ClampedU8), + _ => return None, + } + } _ => return None, }; match *inner { @@ -268,6 +278,13 @@ impl Descriptor { } } + pub fn is_clamped_by_ref(&self) -> bool { + match self { + Descriptor::Clamped(d) => d.is_by_ref(), + _ => false, + } + } + pub fn is_mut_ref(&self) -> bool { match *self { Descriptor::RefMut(_) => true, @@ -311,6 +328,7 @@ impl VectorKind { VectorKind::String => "string", VectorKind::I8 => "Int8Array", VectorKind::U8 => "Uint8Array", + VectorKind::ClampedU8 => "Uint8ClampedArray", VectorKind::I16 => "Int16Array", VectorKind::U16 => "Uint16Array", VectorKind::I32 => "Int32Array", @@ -328,6 +346,7 @@ impl VectorKind { VectorKind::String => 1, VectorKind::I8 => 1, VectorKind::U8 => 1, + VectorKind::ClampedU8 => 1, VectorKind::I16 => 2, VectorKind::U16 => 2, VectorKind::I32 => 4, diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index f9b47229..e5153cd5 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -159,7 +159,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> { i = i, val = val, )); - if arg.is_by_ref() { + if arg.is_by_ref() || arg.is_clamped_by_ref() { if optional { bail!("optional slices aren't currently supported"); } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index c86b4e7b..fcbb5378 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1173,6 +1173,11 @@ impl<'a> Context<'a> { self.arrayget("getArrayU8FromWasm", "getUint8Memory", 1); } + fn expose_get_clamped_array_u8_from_wasm(&mut self) { + self.expose_clamped_uint8_memory(); + self.arrayget("getClampedArrayU8FromWasm", "getUint8ClampedMemory", 1); + } + fn expose_get_array_i16_from_wasm(&mut self) { self.expose_int16_memory(); self.arrayget("getArrayI16FromWasm", "getInt16Memory", 2); @@ -1237,6 +1242,10 @@ impl<'a> Context<'a> { self.memview("getUint8Memory", "Uint8Array"); } + fn expose_clamped_uint8_memory(&mut self) { + self.memview("getUint8ClampedMemory", "Uint8ClampedArray"); + } + fn expose_int16_memory(&mut self) { self.memview("getInt16Memory", "Int16Array"); } @@ -1283,6 +1292,10 @@ impl<'a> Context<'a> { self.expose_uint8_memory(); "getUint8Memory" } + VectorKind::ClampedU8 => { + self.expose_clamped_uint8_memory(); + "getUint8ClampedMemory" + } VectorKind::I16 => { self.expose_int16_memory(); "getInt16Memory" @@ -1444,7 +1457,7 @@ impl<'a> Context<'a> { self.expose_pass_string_to_wasm()?; "passStringToWasm" } - VectorKind::I8 | VectorKind::U8 => { + VectorKind::I8 | VectorKind::U8 | VectorKind::ClampedU8 => { self.expose_pass_array8_to_wasm()?; "passArray8ToWasm" } @@ -1490,6 +1503,10 @@ impl<'a> Context<'a> { self.expose_get_array_u8_from_wasm(); "getArrayU8FromWasm" } + VectorKind::ClampedU8 => { + self.expose_get_clamped_array_u8_from_wasm(); + "getClampedArrayU8FromWasm" + } VectorKind::I16 => { self.expose_get_array_i16_from_wasm(); "getArrayI16FromWasm" diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index f2173eb5..0f839725 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -112,7 +112,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { prefix = if optional { format!("{} == 0 ? undefined : ", abi) } else { String::new() }, )); - if !arg.is_by_ref() { + if !arg.is_by_ref() && !arg.is_clamped_by_ref() { self.prelude(&format!( "\ {start} diff --git a/crates/webidl-tests/array.rs b/crates/webidl-tests/array.rs index cfc2c24b..99f41ea8 100644 --- a/crates/webidl-tests/array.rs +++ b/crates/webidl-tests/array.rs @@ -1,4 +1,5 @@ use js_sys::Object; +use wasm_bindgen::Clamped; use wasm_bindgen_test::*; include!(concat!(env!("OUT_DIR"), "/array.rs")); @@ -9,12 +10,13 @@ fn take_and_return_a_bunch_of_slices() { assert_eq!(f.strings("y"), "x"); assert_eq!(f.byte_strings("yz"), "xx"); assert_eq!(f.usv_strings("abc"), "efg"); - assert_eq!(f.f32(&[1.0, 2.0]), [3.0, 4.0, 5.0]); - assert_eq!(f.f64(&[1.0, 2.0]), [3.0, 4.0, 5.0]); - assert_eq!(f.i8(&[1, 2]), [3, 4, 5]); - assert_eq!(f.i16(&[1, 2]), [3, 4, 5]); - assert_eq!(f.i32(&[1, 2]), [3, 4, 5]); - assert_eq!(f.u8(&[1, 2]), [3, 4, 5]); - assert_eq!(f.u16(&[1, 2]), [3, 4, 5]); - assert_eq!(f.u32(&[1, 2]), [3, 4, 5]); + assert_eq!(f.f32(&mut [1.0, 2.0]), [3.0, 4.0, 5.0]); + assert_eq!(f.f64(&mut [1.0, 2.0]), [3.0, 4.0, 5.0]); + assert_eq!(f.i8(&mut [1, 2]), [3, 4, 5]); + assert_eq!(f.i16(&mut [1, 2]), [3, 4, 5]); + assert_eq!(f.i32(&mut [1, 2]), [3, 4, 5]); + assert_eq!(f.u8(&mut [1, 2]), [3, 4, 5]); + assert_eq!(f.u16(&mut [1, 2]), [3, 4, 5]); + assert_eq!(f.u32(&mut [1, 2]), [3, 4, 5]); + assert_eq!(f.u8_clamped(Clamped(&mut [1, 2])).0, [3, 4, 5]); } diff --git a/crates/webidl/src/idl_type.rs b/crates/webidl/src/idl_type.rs index c923422d..33dfc9df 100644 --- a/crates/webidl/src/idl_type.rs +++ b/crates/webidl/src/idl_type.rs @@ -1,4 +1,5 @@ use backend::util::{ident_ty, leading_colon_path_ty, raw_ident, rust_ident}; +use proc_macro2::{Ident, Span}; use syn; use weedle::common::Identifier; use weedle::term; @@ -501,16 +502,16 @@ impl<'a> IdlType<'a> { IdlType::ArrayBuffer => js_sys("ArrayBuffer"), IdlType::DataView => None, - IdlType::Int8Array => Some(array("i8", pos, false)), - IdlType::Uint8Array => Some(array("u8", pos, false)), - IdlType::Uint8ArrayMut => Some(array("u8", pos, true)), - IdlType::Uint8ClampedArray => None, // FIXME(#421) - IdlType::Int16Array => Some(array("i16", pos, false)), - IdlType::Uint16Array => Some(array("u16", pos, false)), - IdlType::Int32Array => Some(array("i32", pos, false)), - IdlType::Uint32Array => Some(array("u32", pos, false)), - IdlType::Float32Array => Some(array("f32", pos, false)), - IdlType::Float64Array => Some(array("f64", pos, false)), + IdlType::Int8Array => Some(array("i8", pos)), + IdlType::Uint8Array => Some(array("u8", pos)), + IdlType::Uint8ArrayMut => Some(array("u8", pos)), + IdlType::Uint8ClampedArray => Some(clamped(array("u8", pos))), + IdlType::Int16Array => Some(array("i16", pos)), + IdlType::Uint16Array => Some(array("u16", pos)), + IdlType::Int32Array => Some(array("i32", pos)), + IdlType::Uint32Array => Some(array("u32", pos)), + IdlType::Float32Array => Some(array("f32", pos)), + IdlType::Float64Array => Some(array("f64", pos)), IdlType::ArrayBufferView | IdlType::BufferSource => js_sys("Object"), IdlType::Interface(name) @@ -709,3 +710,26 @@ fn idl_type_flatten_test() { ], ); } + +/// From `T` create `::wasm_bindgen::Clamped` +fn clamped(t: syn::Type) -> syn::Type { + let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Default::default(), + args: vec![syn::GenericArgument::Type(t)].into_iter().collect(), + gt_token: Default::default(), + }); + + let ident = raw_ident("Clamped"); + let seg = syn::PathSegment { ident, arguments }; + syn::TypePath { + qself: None, + path: syn::Path { + leading_colon: Some(Default::default()), + segments: vec![ + Ident::new("wasm_bindgen", Span::call_site()).into(), + seg, + ].into_iter().collect(), + }, + }.into() +} diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 92996612..40ab4baa 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -156,7 +156,7 @@ fn builtin_idents() -> BTreeSet { vec![ "str", "char", "bool", "JsValue", "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "usize", "isize", "f32", "f64", "Result", "String", "Vec", "Option", - "Array", "ArrayBuffer", "Object", "Promise", "Function", + "Array", "ArrayBuffer", "Object", "Promise", "Function", "Clamped", ].into_iter() .map(|id| proc_macro2::Ident::new(id, proc_macro2::Span::call_site())), ) diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index 055d3fc2..3043d311 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -63,10 +63,10 @@ pub fn mdn_doc(class: &str, method: Option<&str>) -> String { } // Array type is borrowed for arguments (`&[T]`) and owned for return value (`Vec`). -pub(crate) fn array(base_ty: &str, pos: TypePosition, mutable: bool) -> syn::Type { +pub(crate) fn array(base_ty: &str, pos: TypePosition) -> syn::Type { match pos { TypePosition::Argument => { - shared_ref(slice_ty(ident_ty(raw_ident(base_ty))), mutable) + shared_ref(slice_ty(ident_ty(raw_ident(base_ty))), /*mutable =*/ true) } TypePosition::Return => { vec_ty(ident_ty(raw_ident(base_ty))) diff --git a/examples/julia_set/Cargo.toml b/examples/julia_set/Cargo.toml index cb8b04c5..53cd471d 100644 --- a/examples/julia_set/Cargo.toml +++ b/examples/julia_set/Cargo.toml @@ -8,7 +8,6 @@ crate-type = ["cdylib"] [dependencies] wasm-bindgen = { path = "../.." } -js-sys = { path = '../../crates/js-sys' } [dependencies.web-sys] path = '../../crates/web-sys' diff --git a/examples/julia_set/src/lib.rs b/examples/julia_set/src/lib.rs index f8c5b11e..d5b833c9 100644 --- a/examples/julia_set/src/lib.rs +++ b/examples/julia_set/src/lib.rs @@ -1,24 +1,10 @@ -extern crate js_sys; extern crate wasm_bindgen; extern crate web_sys; use std::ops::Add; -use wasm_bindgen::JsCast; +use wasm_bindgen::Clamped; use wasm_bindgen::prelude::*; -use web_sys::CanvasRenderingContext2d; -use js_sys::{WebAssembly, Uint8ClampedArray}; - -// Unfortunately `web-sys` at this time doesn't bind APIs with -// `Uint8ClampedArray`. For more information see rustwasm/wasm-bindgen#421. -// -// For now we just bind it ourselves and do some manual frobbing below. -#[wasm_bindgen] -extern "C" { - type ImageData; - - #[wasm_bindgen(constructor)] - fn new(arr: &Uint8ClampedArray, width: u32, height: u32) -> ImageData; -} +use web_sys::{CanvasRenderingContext2d, ImageData}; #[wasm_bindgen] pub fn draw( @@ -30,22 +16,9 @@ pub fn draw( ) -> Result<(), JsValue> { // The real workhorse of this algorithm, generating pixel data let c = Complex { real, imaginary, }; - let data = get_julia_set(width, height, c); - - // And now that we've got some pixels, let's create an `ImageData` with the - // pixels and then ship it off to the canvas. - // - // See notes in the `wasm-in-wasm` example for why this is a bit dangerous - let my_memory = wasm_bindgen::memory() - .dyn_into::() - .unwrap(); - let uint8_array = Uint8ClampedArray::new(&my_memory.buffer()) - .subarray( - data.as_ptr() as u32, - data.as_ptr() as u32 + data.len() as u32, - ); - let data = ImageData::new(&uint8_array, width, height); - ctx.put_image_data(data.unchecked_ref(), 0.0, 0.0) + let mut data = get_julia_set(width, height, c); + let data = ImageData::new_with_u8_clamped_array_and_sh(Clamped(&mut data), width, height)?; + ctx.put_image_data(&data, 0.0, 0.0) } fn get_julia_set(width: u32, height: u32, c: Complex) -> Vec { diff --git a/src/convert/impls.rs b/src/convert/impls.rs index 2613816b..539b6a89 100644 --- a/src/convert/impls.rs +++ b/src/convert/impls.rs @@ -4,7 +4,7 @@ use core::mem::{self, ManuallyDrop}; use convert::{Stack, FromWasmAbi, IntoWasmAbi, RefFromWasmAbi}; use convert::{OptionIntoWasmAbi, OptionFromWasmAbi, ReturnWasmAbi}; use convert::traits::WasmAbi; -use JsValue; +use {JsValue, Clamped}; unsafe impl WasmAbi for () {} @@ -373,6 +373,22 @@ impl FromWasmAbi for Option { } } +impl IntoWasmAbi for Clamped { + type Abi = T::Abi; + + fn into_abi(self, extra: &mut Stack) -> Self::Abi { + self.0.into_abi(extra) + } +} + +impl FromWasmAbi for Clamped { + type Abi = T::Abi; + + unsafe fn from_abi(js: T::Abi, extra: &mut Stack) -> Self { + Clamped(T::from_abi(js, extra)) + } +} + impl IntoWasmAbi for () { type Abi = (); diff --git a/src/describe.rs b/src/describe.rs index 4a1b747e..7b2e6473 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -3,7 +3,7 @@ #![doc(hidden)] -use JsValue; +use {JsValue, Clamped}; macro_rules! tys { ($($a:ident)*) => (tys! { @ ($($a)*) 0 }); @@ -40,6 +40,7 @@ tys! { CHAR OPTIONAL UNIT + CLAMPED } #[inline(always)] // see `interpret.rs` in the the cli-support crate @@ -202,3 +203,10 @@ impl WasmDescribe for Result { T::describe() } } + +impl WasmDescribe for Clamped { + fn describe() { + inform(CLAMPED); + T::describe(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 90fd896c..967d372d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ extern crate wasm_bindgen_macro; use core::cell::UnsafeCell; use core::fmt; use core::mem; -use core::ops::Deref; +use core::ops::{Deref, DerefMut}; use core::ptr; use convert::FromWasmAbi; @@ -864,3 +864,32 @@ pub mod __rt { FOO.store(0, Ordering::SeqCst); } } + +/// A wrapper type around slices and vectors for binding the `Uint8ClampedArray` +/// array in JS. +/// +/// If you need to invoke a JS API which must take `Uint8ClampedArray` array, +/// then you can define it as taking one of these types: +/// +/// * `Clamped<&[u8]>` +/// * `Clamped<&mut [u8]>` +/// * `Clamped>` +/// +/// All of these types will show up as `Uint8ClampedArray` in JS and will have +/// different forms of ownership in Rust. +#[derive(Copy, Clone, PartialEq, Debug, Eq)] +pub struct Clamped(pub T); + +impl Deref for Clamped { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for Clamped { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} diff --git a/tests/wasm/slice.js b/tests/wasm/slice.js index b6ceaa28..7ec7ee8f 100644 --- a/tests/wasm/slice.js +++ b/tests/wasm/slice.js @@ -204,3 +204,11 @@ exports.js_return_vec = () => { assert.strictEqual(bad[8], 9); } }; + +exports.js_clamped = (a, offset) => { + assert.ok(a instanceof Uint8ClampedArray); + assert.equal(a.length, 3); + assert.equal(a[0], offset + 0); + assert.equal(a[1], offset + 1); + assert.equal(a[2], offset + 2); +}; diff --git a/tests/wasm/slice.rs b/tests/wasm/slice.rs index 6347a601..b3e9486a 100644 --- a/tests/wasm/slice.rs +++ b/tests/wasm/slice.rs @@ -1,4 +1,5 @@ use wasm_bindgen_test::*; +use wasm_bindgen::Clamped; use wasm_bindgen::prelude::*; #[wasm_bindgen(module = "tests/wasm/slice.js")] @@ -12,6 +13,12 @@ extern { fn js_export_mut(); fn js_return_vec(); + + fn js_clamped(val: Clamped<&[u8]>, offset: u8); + #[wasm_bindgen(js_name = js_clamped)] + fn js_clamped2(val: Clamped>, offset: u8); + #[wasm_bindgen(js_name = js_clamped)] + fn js_clamped3(val: Clamped<&mut [u8]>, offset: u8); } macro_rules! export_macro { @@ -208,3 +215,10 @@ impl ReturnVecApplication { fn return_vec() { js_return_vec(); } + +#[wasm_bindgen_test] +fn take_clamped() { + js_clamped(Clamped(&[1, 2, 3]), 1); + js_clamped2(Clamped(vec![4, 5, 6]), 4); + js_clamped3(Clamped(&mut [7, 8, 9]), 7); +}