diff --git a/Cargo.toml b/Cargo.toml index 16dba02d0454..41b3e7903eaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" readme = "README.md" @@ -36,15 +36,16 @@ strict-macro = ["wasm-bindgen-macro/strict-macro"] xxx_debug_only_print_generated_code = ["wasm-bindgen-macro/xxx_debug_only_print_generated_code"] [dependencies] -wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.51" } +wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.52" } serde = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true } cfg-if = "0.1.9" +proc-macro-hack = "0.5" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -js-sys = { path = 'crates/js-sys', version = '0.3.28' } -wasm-bindgen-test = { path = 'crates/test', version = '=0.3.1' } -wasm-bindgen-futures = { path = 'crates/futures', version = '=0.4.1' } +js-sys = { path = 'crates/js-sys', version = '0.3.29' } +wasm-bindgen-test = { path = 'crates/test', version = '=0.3.2' } +wasm-bindgen-futures = { path = 'crates/futures', version = '=0.4.2' } serde_derive = "1.0" wasm-bindgen-test-crate-a = { path = 'tests/crates/a', version = '0.1' } wasm-bindgen-test-crate-b = { path = 'tests/crates/b', version = '0.1' } @@ -79,6 +80,20 @@ members = [ "examples/todomvc", "examples/wasm-in-wasm", "examples/wasm2js", + "examples/web-components-composed-composed-path", + "examples/web-components-defined-pseudo-class", + "examples/web-components-edit-word", + "examples/web-components-editable-list", + "examples/web-components-element-details", + "examples/web-components-expanding-list-web-component", + "examples/web-components-host-selectors", + "examples/web-components-life-cycle-callbacks", + "examples/web-components-popup-info-box-external-stylesheet", + "examples/web-components-popup-info-box-web-component", + "examples/web-components-simple-template", + "examples/web-components-slotchange", + "examples/web-components-slotted-pseudo-element", + "examples/web-components-word-count-web-component", "examples/webaudio", "examples/webgl", "examples/websockets", diff --git a/_package.json b/_package.json index 76a59e2b56f2..0d1364cf73b9 100644 --- a/_package.json +++ b/_package.json @@ -5,6 +5,7 @@ }, "devDependencies": { "@wasm-tool/wasm-pack-plugin": "1.0.1", + "copy-webpack-plugin": "^5.0.0", "html-webpack-plugin": "^3.2.0", "text-encoding": "^0.7.0", "webpack": "^4.29.4", diff --git a/crates/anyref-xform/Cargo.toml b/crates/anyref-xform/Cargo.toml index 0bec76e84aa7..fee326a9999f 100644 --- a/crates/anyref-xform/Cargo.toml +++ b/crates/anyref-xform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-anyref-xform" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/anyref-xform" diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index 90d5bdf24ad0..2634c8f484c3 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-backend" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend" @@ -22,4 +22,4 @@ log = "0.4" proc-macro2 = "1.0" quote = '1.0' syn = { version = '1.0', features = ['full'] } -wasm-bindgen-shared = { path = "../shared", version = "=0.2.51" } +wasm-bindgen-shared = { path = "../shared", version = "=0.2.52" } diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index a462a112bc9e..2108128995f9 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -44,6 +44,8 @@ pub struct Export { pub method_kind: MethodKind, /// The type of `self` (either `self`, `&self`, or `&mut self`) pub method_self: Option, + /// The body of the method + pub method_body: Option, /// The struct name, in Rust, this is attached to pub rust_class: Option, /// The name of the rust function/method on the rust side. @@ -57,8 +59,6 @@ pub struct Export { #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[derive(Clone)] pub enum MethodSelf { - /// `self` - ByValue, /// `&mut self` RefMutable, /// `&self` @@ -223,6 +223,8 @@ pub struct Struct { pub js_name: String, pub fields: Vec, pub comments: Vec, + pub prototype: syn::Type, + pub prototype_field: Option, } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 3bf0bb9abfab..68c7069b546e 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -10,6 +10,46 @@ use std::sync::Mutex; use syn; use wasm_bindgen_shared as shared; +struct TokenStreamEncoder { + dst: proc_macro2::TokenStream, + len: u32, + files: Vec, +} + +impl TokenStreamEncoder { + pub fn new() -> TokenStreamEncoder { + TokenStreamEncoder { + dst: proc_macro2::TokenStream::new(), + len: 0, + files: Vec::new(), + } + } +} + +impl encode::Encoder for TokenStreamEncoder { + fn byte(&mut self, byte: u8) { + self.dst.extend(quote!{ #byte, }); + self.len += 1; + } + + fn bytes(&mut self, bytes: &[u8]) { + self.dst.extend(quote!{ #(#bytes,)* }); + self.len += bytes.len() as u32; + } + + fn type_reference(&mut self, reference: &encode::TypeReference) { + const SIZE: usize = 8; + let byte = 0..SIZE; + let path = &reference.0; + self.dst.extend(quote!{ #(<#path as ::wasm_bindgen::WasmBindgenReferenceable>::ID[#byte],)* }); + self.len += SIZE as u32; + } + + fn files>(&mut self, files: F) { + self.files.extend(files); + } +} + pub trait TryToTokens { fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic>; @@ -92,17 +132,16 @@ impl TryToTokens for ast::Program { shared::SCHEMA_VERSION, shared::version() ); - let encoded = encode::encode(self)?; - let mut bytes = Vec::new(); - bytes.push((prefix_json.len() >> 0) as u8); - bytes.push((prefix_json.len() >> 8) as u8); - bytes.push((prefix_json.len() >> 16) as u8); - bytes.push((prefix_json.len() >> 24) as u8); - bytes.extend_from_slice(prefix_json.as_bytes()); - bytes.extend_from_slice(&encoded.custom_section); - - let generated_static_length = bytes.len(); - let generated_static_value = syn::LitByteStr::new(&bytes, Span::call_site()); + + let prefix_size = (prefix_json.len() as u32).to_le_bytes(); + let prefix_data = prefix_json.as_bytes(); + + let mut encoder = TokenStreamEncoder::new(); + encode::encode(self, &mut encoder)?; + let custom_size = encoder.len.to_le_bytes(); + let custom_data = encoder.dst; + + let generated_static_length = 8 + prefix_data.len() + encoder.len as usize; // We already consumed the contents of included files when generating // the custom section, but we want to make sure that updates to the @@ -112,7 +151,7 @@ impl TryToTokens for ast::Program { // automatically rerun rustc which will rerun this macro. Other than // this we don't actually need the results of the `include_str!`, so // it's just shoved into an anonymous static. - let file_dependencies = encoded.included_files.iter().map(|file| { + let file_dependencies = encoder.files.iter().map(|file| { let file = file.to_str().unwrap(); quote! { include_str!(#file) } }); @@ -126,7 +165,12 @@ impl TryToTokens for ast::Program { pub static #generated_static_name: [u8; #generated_static_length] = { static _INCLUDED_FILES: &[&str] = &[#(#file_dependencies),*]; - *#generated_static_value + [ + #(#prefix_size,)* + #(#prefix_data,)* + #(#custom_size,)* + #custom_data + ] }; }) @@ -136,15 +180,33 @@ impl TryToTokens for ast::Program { } } +fn gen_id() -> [u8; 8] { + static CNT: AtomicUsize = AtomicUsize::new(0); + let mut id = 0; + while id == 0 { // 0 is reserved + id = u64::from_str_radix( + &ShortHash(CNT.fetch_add(1, Ordering::SeqCst)).to_string(), + 16 + ) + .unwrap(); + } + id.to_be_bytes() +} + impl ToTokens for ast::Struct { fn to_tokens(&self, tokens: &mut TokenStream) { let name = &self.rust_name; let name_str = self.js_name.to_string(); let name_len = name_str.len() as u32; let name_chars = name_str.chars().map(|c| c as u32); - let new_fn = Ident::new(&shared::new_function(&name_str), Span::call_site()); + let borrow_fn = Ident::new(&shared::borrow_from_prototype_function(&name_str), Span::call_site()); let free_fn = Ident::new(&shared::free_function(&name_str), Span::call_site()); + let id_bytes = gen_id(); (quote! { + impl ::wasm_bindgen::WasmBindgenReferenceable for #name { + const ID: [u8; 8] = [#(#id_bytes),*]; + } + #[allow(clippy::all)] impl wasm_bindgen::describe::WasmDescribe for #name { fn describe() { @@ -162,55 +224,52 @@ impl ToTokens for ast::Struct { } } - #[allow(clippy::all)] - impl wasm_bindgen::convert::IntoWasmAbi for #name { - type Abi = u32; - - fn into_abi(self) -> u32 { - use wasm_bindgen::__rt::std::boxed::Box; - use wasm_bindgen::__rt::WasmRefCell; - Box::into_raw(Box::new(WasmRefCell::new(self))) as u32 + impl AsRef<::wasm_bindgen::JsValue> for #name { + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[inline] + fn as_ref(&self) -> &::wasm_bindgen::JsValue { + use ::wasm_bindgen::WasmBase; + self.get_underlying_ref(0) + .downcast_ref() + .unwrap() } - } - #[allow(clippy::all)] - impl wasm_bindgen::convert::FromWasmAbi for #name { - type Abi = u32; - - unsafe fn from_abi(js: u32) -> Self { - use wasm_bindgen::__rt::std::boxed::Box; - use wasm_bindgen::__rt::{assert_not_null, WasmRefCell}; - - let ptr = js as *mut WasmRefCell<#name>; - assert_not_null(ptr); - let js = Box::from_raw(ptr); - (*js).borrow_mut(); // make sure no one's borrowing - js.into_inner() + #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] + #[inline] + fn as_ref(&self) -> &::wasm_bindgen::JsValue { + unimplemented!() } } + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + #[no_mangle] + #[doc(hidden)] #[allow(clippy::all)] - impl wasm_bindgen::__rt::core::convert::From<#name> for - wasm_bindgen::JsValue - { - fn from(value: #name) -> Self { - let ptr = wasm_bindgen::convert::IntoWasmAbi::into_abi(value); - - #[link(wasm_import_module = "__wbindgen_placeholder__")] - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] - extern "C" { - fn #new_fn(ptr: u32) -> u32; - } - - #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] - unsafe fn #new_fn(_: u32) -> u32 { - panic!("cannot convert to JsValue outside of the wasm target") - } - - unsafe { - - ::from_abi(#new_fn(ptr)) - } + pub unsafe extern "C" fn #borrow_fn( + ptr: <*mut ::wasm_bindgen::WasmType<#name> as ::wasm_bindgen::convert::FromWasmAbi>::Abi, + id: ::Abi, + mutable: ::Abi, + ) -> <*mut () as ::wasm_bindgen::convert::IntoWasmAbi>::Abi { + use ::wasm_bindgen::__rt::{Ref, RefMut, assert_not_null, std::boxed::Box, core::any::Any}; + use ::wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi}; + use ::wasm_bindgen::{WasmBase, WasmType}; + + let ptr = <*mut WasmType<#name> as FromWasmAbi>::from_abi(ptr); + let id = ::from_abi(id); + let mutable = ::from_abi(mutable); + + assert_not_null(ptr); + + if mutable { + Box::into_raw(Box::new(RefMut::map( + (**ptr).borrow_mut(), + |me| me.get_underlying_mut(id), + ))).into_abi() + } else { + Box::into_raw(Box::new(Ref::map( + (**ptr).borrow(), + |me| me.get_underlying_ref(id), + ))).into_abi() } } @@ -218,46 +277,84 @@ impl ToTokens for ast::Struct { #[no_mangle] #[doc(hidden)] #[allow(clippy::all)] - pub unsafe extern "C" fn #free_fn(ptr: u32) { - <#name as wasm_bindgen::convert::FromWasmAbi>::from_abi(ptr); + pub unsafe extern "C" fn #free_fn(ptr: <*mut ::wasm_bindgen::WasmType<#name> as ::wasm_bindgen::convert::FromWasmAbi>::Abi) { + use ::wasm_bindgen::__rt::assert_not_null; + use ::wasm_bindgen::convert::FromWasmAbi; + use ::wasm_bindgen::WasmType; + + let ptr = <*mut WasmType<#name> as FromWasmAbi>::from_abi(ptr); + assert_not_null(ptr); + + // consume Rc and, if it's the last one, make sure no one's borrowing + if let Ok(me) = WasmType::try_unwrap(*Box::from_raw(ptr)) { + me.borrow_mut(); + } } #[allow(clippy::all)] - impl wasm_bindgen::convert::RefFromWasmAbi for #name { - type Abi = u32; - type Anchor = wasm_bindgen::__rt::Ref<'static, #name>; + impl ::wasm_bindgen::convert::RefFromWasmAbi for #name { + type Abi = <*mut ::wasm_bindgen::__rt::Ref<'static, dyn ::wasm_bindgen::__rt::core::any::Any> as ::wasm_bindgen::convert::FromWasmAbi>::Abi; + type Anchor = ::wasm_bindgen::__rt::Ref<'static, #name>; + #[inline] unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor { - let js = js as *mut wasm_bindgen::__rt::WasmRefCell<#name>; - wasm_bindgen::__rt::assert_not_null(js); - (*js).borrow() + use ::wasm_bindgen::__rt::core::any::Any; + use ::wasm_bindgen::__rt::{assert_not_null, Ref}; + use ::wasm_bindgen::convert::FromWasmAbi; + + let ptr = <*mut Ref<'static, dyn Any> as FromWasmAbi>::from_abi(js); + assert_not_null(ptr); + + Ref::map( + *Box::from_raw(ptr), + |val| val.downcast_ref().unwrap(), + ) } } #[allow(clippy::all)] - impl wasm_bindgen::convert::RefMutFromWasmAbi for #name { - type Abi = u32; - type Anchor = wasm_bindgen::__rt::RefMut<'static, #name>; + impl ::wasm_bindgen::convert::RefMutFromWasmAbi for #name { + type Abi = <*mut ::wasm_bindgen::__rt::RefMut<'static, dyn ::wasm_bindgen::__rt::core::any::Any> as ::wasm_bindgen::convert::FromWasmAbi>::Abi; + type Anchor = ::wasm_bindgen::__rt::RefMut<'static, #name>; + #[inline] unsafe fn ref_mut_from_abi(js: Self::Abi) -> Self::Anchor { - let js = js as *mut wasm_bindgen::__rt::WasmRefCell<#name>; - wasm_bindgen::__rt::assert_not_null(js); - (*js).borrow_mut() + use ::wasm_bindgen::__rt::core::any::Any; + use ::wasm_bindgen::__rt::{assert_not_null, RefMut}; + use ::wasm_bindgen::convert::FromWasmAbi; + + let ptr = <*mut RefMut<'static, dyn Any> as FromWasmAbi>::from_abi(js); + assert_not_null(ptr); + + RefMut::map( + *Box::from_raw(ptr), + |val| val.downcast_mut().unwrap(), + ) } } + }) + .to_tokens(tokens); + + if let Some(proto_field) = &self.prototype_field { + let prototype = &self.prototype; - impl wasm_bindgen::convert::OptionIntoWasmAbi for #name { - #[inline] - fn none() -> Self::Abi { 0 } - } + (quote! { + impl ::wasm_bindgen::WasmBindgenDerived for #name { + type Prototype = #prototype; + type Callback = ::wasm_bindgen::ExportedSuperconstructorCallback; + } - impl wasm_bindgen::convert::OptionFromWasmAbi for #name { - #[inline] - fn is_none(abi: &Self::Abi) -> bool { *abi == 0 } - } + impl ::core::ops::Deref for #name { + type Target = #prototype; + fn deref(&self) -> &#prototype { &self.#proto_field } + } - }) - .to_tokens(tokens); + impl ::core::ops::DerefMut for #name { + fn deref_mut(&mut self) -> &mut #prototype { &mut self.#proto_field } + } + }) + .to_tokens(tokens); + } for field in self.fields.iter() { field.to_tokens(tokens); @@ -273,8 +370,6 @@ impl ToTokens for ast::StructField { let getter = &self.getter; let setter = &self.setter; - let assert_copy = quote! { assert_copy::<#ty>() }; - let assert_copy = respan(assert_copy, ty); (quote! { #[doc(hidden)] #[allow(clippy::all)] @@ -282,15 +377,14 @@ impl ToTokens for ast::StructField { pub unsafe extern "C" fn #getter(js: u32) -> <#ty as wasm_bindgen::convert::IntoWasmAbi>::Abi { - use wasm_bindgen::__rt::{WasmRefCell, assert_not_null}; - use wasm_bindgen::convert::IntoWasmAbi; - - fn assert_copy(){} - #assert_copy; - - let js = js as *mut WasmRefCell<#struct_name>; - assert_not_null(js); - let val = (*js).borrow().#name; + use wasm_bindgen::convert::{IntoWasmAbi, RefFromWasmAbi}; + + let js = <#struct_name as RefFromWasmAbi>::ref_from_abi(js); + // Cloning fields of exported or imported types results in refs to the same underlying + // (the former are wrapped in an Rc so cloning just ups the refcount, whilst the + // latter are ultimately JsValues so cloning just dupes the ref on the JS "heap"). + // And cloning fields of primitive types is essentially just a Copy, so that's okay too. + let val = js.#name.clone(); <#ty as IntoWasmAbi>::into_abi(val) } }) @@ -317,13 +411,11 @@ impl ToTokens for ast::StructField { js: u32, val: <#ty as wasm_bindgen::convert::FromWasmAbi>::Abi, ) { - use wasm_bindgen::__rt::{WasmRefCell, assert_not_null}; - use wasm_bindgen::convert::FromWasmAbi; + use wasm_bindgen::convert::{FromWasmAbi, RefMutFromWasmAbi}; - let js = js as *mut WasmRefCell<#struct_name>; - assert_not_null(js); + let mut js = <#struct_name as RefMutFromWasmAbi>::ref_mut_from_abi(js); let val = <#ty as FromWasmAbi>::from_abi(val); - (*js).borrow_mut().#name = val; + js.#name = val; } }) .to_tokens(tokens); @@ -348,15 +440,6 @@ impl TryToTokens for ast::Export { let name = &self.rust_name; let receiver = match self.method_self { - Some(ast::MethodSelf::ByValue) => { - let class = self.rust_class.as_ref().unwrap(); - arg_conversions.push(quote! { - let me = unsafe { - <#class as wasm_bindgen::convert::FromWasmAbi>::from_abi(me) - }; - }); - quote! { me.#name } - } Some(ast::MethodSelf::RefMutable) => { let class = self.rust_class.as_ref().unwrap(); arg_conversions.push(quote! { @@ -364,7 +447,6 @@ impl TryToTokens for ast::Export { <#class as wasm_bindgen::convert::RefMutFromWasmAbi> ::ref_mut_from_abi(me) }; - let me = &mut *me; }); quote! { me.#name } } @@ -375,7 +457,6 @@ impl TryToTokens for ast::Export { <#class as wasm_bindgen::convert::RefFromWasmAbi> ::ref_from_abi(me) }; - let me = &*me; }); quote! { me.#name } } @@ -390,6 +471,10 @@ impl TryToTokens for ast::Export { argtys.push(&arg.ty); let i = i + offset; let ident = Ident::new(&format!("arg{}", i), Span::call_site()); + let ident = match arg.pat.as_ref() { + syn::Pat::Ident(pat_ident) => &pat_ident.ident, + _ => &ident, + }; let ty = &arg.ty; match &*arg.ty { syn::Type::Reference(syn::TypeReference { @@ -438,7 +523,10 @@ impl TryToTokens for ast::Export { elems: Default::default(), paren_token: Default::default(), }); - let syn_ret = self.function.ret.as_ref().unwrap_or(&syn_unit); + let syn_ret = self.function.ret + .as_ref() + .filter(|_| !self.method_body.is_some()) + .unwrap_or(&syn_unit); if let syn::Type::Reference(_) = syn_ret { bail_span!(syn_ret, "cannot return a borrowed ref with #[wasm_bindgen]",) } @@ -462,7 +550,6 @@ impl TryToTokens for ast::Export { ) }; let projection = quote! { <#ret_ty as wasm_bindgen::convert::ReturnWasmAbi> }; - let convert_ret = quote! { #projection::return_abi(#ret_expr) }; let describe_ret = quote! { <#ret_ty as WasmDescribe>::describe(); }; @@ -475,6 +562,17 @@ impl TryToTokens for ast::Export { quote! {} }; + let (inner, convert_ret) = if let Some(method_body) = &self.method_body {( + quote! { + use ::wasm_bindgen::instantiate; + #method_body + }, + Default::default(), + )} else {( + quote! { #receiver(#(#converted_arguments),*) }, + quote! { #projection::return_abi(#ret_expr) }, + )}; + (quote! { #(#attrs)* #[allow(non_snake_case)] @@ -490,7 +588,7 @@ impl TryToTokens for ast::Export { // leak anything. let #ret = { #(#arg_conversions)* - #receiver(#(#converted_arguments),*) + #inner }; #convert_ret } @@ -575,6 +673,8 @@ impl ToTokens for ast::ImportType { } }); + let id_bytes = gen_id(); + (quote! { #[allow(bad_style)] #(#attrs)* @@ -593,8 +693,19 @@ impl ToTokens for ast::ImportType { use wasm_bindgen::convert::RefFromWasmAbi; use wasm_bindgen::describe::WasmDescribe; use wasm_bindgen::{JsValue, JsCast}; + use wasm_bindgen::{WasmBindgenReferenceable, WasmBindgenDerived}; use wasm_bindgen::__rt::core; + impl WasmBindgenReferenceable for #rust_name { + const ID: [u8; 8] = [#(#id_bytes),*]; + } + + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + impl WasmBindgenDerived for #rust_name { + type Prototype = #internal_obj; + type Callback = ::wasm_bindgen::ImportedSuperconstructorCallback; + } + impl WasmDescribe for #rust_name { fn describe() { JsValue::describe(); @@ -610,6 +721,13 @@ impl ToTokens for ast::ImportType { } } + impl core::ops::DerefMut for #rust_name { + #[inline] + fn deref_mut(&mut self) -> &mut #internal_obj { + &mut self.obj + } + } + impl IntoWasmAbi for #rust_name { type Abi = ::Abi; diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index 5951f8d0ea0c..8a086039f9d5 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -1,7 +1,7 @@ use crate::util::ShortHash; use proc_macro2::{Ident, Span}; use std::cell::{Cell, RefCell}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::env; use std::fs; use std::path::PathBuf; @@ -9,27 +9,16 @@ use std::path::PathBuf; use crate::ast; use crate::Diagnostic; -pub struct EncodeResult { - pub custom_section: Vec, - pub included_files: Vec, -} - -pub fn encode(program: &ast::Program) -> Result { - let mut e = Encoder::new(); +pub fn encode(program: &ast::Program, e: &mut E) -> Result<(), Diagnostic> { let i = Interner::new(); - shared_program(program, &i)?.encode(&mut e); - let custom_section = e.finish(); - let included_files = i - .files + shared_program(program, &i)?.encode(e); + e.files(i.files .borrow() .values() .map(|p| &p.path) .cloned() - .collect(); - Ok(EncodeResult { - custom_section, - included_files, - }) + ); + Ok(()) } struct Interner { @@ -120,6 +109,12 @@ fn shared_program<'a>( prog: &'a ast::Program, intern: &'a Interner, ) -> Result, Diagnostic> { + let mut types = HashSet::new(); + for i in prog.imports.iter() { + if let ast::ImportKind::Type(t) = &i.kind { + types.insert(t.rust_name.clone()); + } + } Ok(Program { exports: prog .exports @@ -135,7 +130,7 @@ fn shared_program<'a>( imports: prog .imports .iter() - .map(|a| shared_import(a, intern)) + .map(|a| shared_import(a, intern, &types)) .collect::, _>>()?, typescript_custom_sections: prog .typescript_custom_sections @@ -176,15 +171,20 @@ fn shared_export<'a>( export: &'a ast::Export, intern: &'a Interner, ) -> Result, Diagnostic> { - let consumed = match export.method_self { - Some(ast::MethodSelf::ByValue) => true, + let mutable = match export.method_self { + Some(ast::MethodSelf::RefMutable) => true, _ => false, }; let method_kind = from_ast_method_kind(&export.function, intern, &export.method_kind)?; + let mut path = export.rust_class.as_ref().cloned().map_or(syn::Path { + leading_colon: Default::default(), + segments: Default::default(), + }, Into::into); + path.segments.push(export.rust_name.clone().into()); Ok(Export { class: export.js_class.as_ref().map(|s| &**s), comments: export.comments.iter().map(|s| &**s).collect(), - consumed, + mutable, function: shared_function(&export.function, intern), method_kind, start: export.start, @@ -228,7 +228,7 @@ fn shared_variant<'a>(v: &'a ast::Variant, intern: &'a Interner) -> EnumVariant< } } -fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result, Diagnostic> { +fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner, types: &HashSet) -> Result, Diagnostic> { Ok(Import { module: match &i.module { ast::ImportModule::Named(m, span) => { @@ -239,16 +239,17 @@ fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result ImportModule::None, }, js_namespace: i.js_namespace.as_ref().map(|s| intern.intern(s)), - kind: shared_import_kind(&i.kind, intern)?, + kind: shared_import_kind(&i.kind, intern, i.js_namespace.as_ref().filter(|ns| types.contains(ns)))?, }) } fn shared_import_kind<'a>( i: &'a ast::ImportKind, intern: &'a Interner, + js_namespace: Option<&Ident>, ) -> Result, Diagnostic> { Ok(match i { - ast::ImportKind::Function(f) => ImportKind::Function(shared_import_function(f, intern)?), + ast::ImportKind::Function(f) => ImportKind::Function(shared_import_function(f, intern, js_namespace.filter(|_| i.fits_on_impl()))?), ast::ImportKind::Static(f) => ImportKind::Static(shared_import_static(f, intern)), ast::ImportKind::Type(f) => ImportKind::Type(shared_import_type(f, intern)), ast::ImportKind::Enum(f) => ImportKind::Enum(shared_import_enum(f, intern)), @@ -258,15 +259,28 @@ fn shared_import_kind<'a>( fn shared_import_function<'a>( i: &'a ast::ImportFunction, intern: &'a Interner, + js_namespace: Option<&Ident>, ) -> Result, Diagnostic> { + let mut ref_path = js_namespace.cloned().map_or(syn::Path { + leading_colon: Default::default(), + segments: Default::default(), + }, Into::into); + let method = match &i.kind { - ast::ImportFunctionKind::Method { class, kind, .. } => { + ast::ImportFunctionKind::Method { class, kind, ref ty } => { let kind = from_ast_method_kind(&i.function, intern, kind)?; + if js_namespace.is_none() { + if let syn::Type::Path(syn::TypePath { qself: None, ref path }) = ty { + ref_path = path.clone(); + } + } Some(MethodData { class, kind }) } ast::ImportFunctionKind::Normal => None, }; + ref_path.segments.push(i.rust_name.clone().into()); + Ok(ImportFunction { shim: intern.intern(&i.shim), catch: i.catch, @@ -287,6 +301,7 @@ fn shared_import_static<'a>(i: &'a ast::ImportStatic, intern: &'a Interner) -> I fn shared_import_type<'a>(i: &'a ast::ImportType, intern: &'a Interner) -> ImportType<'a> { ImportType { + id: TypeReference::new(&i.rust_name), name: &i.js_name, instanceof_shim: &i.instanceof_shim, vendor_prefixes: i.vendor_prefixes.iter().map(|x| intern.intern(x)).collect(), @@ -299,6 +314,7 @@ fn shared_import_enum<'a>(_i: &'a ast::ImportEnum, _intern: &'a Interner) -> Imp fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> { Struct { + id: TypeReference::new(&s.rust_name), name: &s.js_name, fields: s .fields @@ -306,6 +322,7 @@ fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> { .map(|s| shared_struct_field(s, intern)) .collect(), comments: s.comments.iter().map(|s| &**s).collect(), + prototype: TypeReference(s.prototype.clone()), } } @@ -320,43 +337,25 @@ fn shared_struct_field<'a>(s: &'a ast::StructField, intern: &'a Interner) -> Str } } -trait Encode { - fn encode(&self, dst: &mut Encoder); +trait Encode { + fn encode(&self, dst: &mut E); } -struct Encoder { - dst: Vec, -} - -impl Encoder { - fn new() -> Encoder { - Encoder { - dst: vec![0, 0, 0, 0], - } - } - - fn finish(mut self) -> Vec { - let len = self.dst.len() - 4; - self.dst[0] = (len >> 0) as u8; - self.dst[1] = (len >> 8) as u8; - self.dst[2] = (len >> 16) as u8; - self.dst[3] = (len >> 24) as u8; - self.dst - } - - fn byte(&mut self, byte: u8) { - self.dst.push(byte); - } +pub trait Encoder { + fn byte(&mut self, byte: u8); + fn bytes(&mut self, bytes: &[u8]); + fn type_reference(&mut self, reference: &TypeReference); + fn files>(&mut self, files: F); } -impl Encode for bool { - fn encode(&self, dst: &mut Encoder) { +impl Encode for bool { + fn encode(&self, dst: &mut E) { dst.byte(*self as u8); } } -impl Encode for u32 { - fn encode(&self, dst: &mut Encoder) { +impl Encode for u32 { + fn encode(&self, dst: &mut E) { let mut val = *self; while (val >> 7) != 0 { dst.byte((val as u8) | 0x80); @@ -367,34 +366,34 @@ impl Encode for u32 { } } -impl Encode for usize { - fn encode(&self, dst: &mut Encoder) { +impl Encode for usize { + fn encode(&self, dst: &mut E) { assert!(*self <= u32::max_value() as usize); (*self as u32).encode(dst); } } -impl<'a> Encode for &'a [u8] { - fn encode(&self, dst: &mut Encoder) { +impl<'a, E: Encoder> Encode for &'a [u8] { + fn encode(&self, dst: &mut E) { self.len().encode(dst); - dst.dst.extend_from_slice(*self); + dst.bytes(self); } } -impl<'a> Encode for &'a str { - fn encode(&self, dst: &mut Encoder) { +impl<'a, E: Encoder> Encode for &'a str { + fn encode(&self, dst: &mut E) { self.as_bytes().encode(dst); } } -impl<'a> Encode for String { - fn encode(&self, dst: &mut Encoder) { +impl<'a, E: Encoder> Encode for String { + fn encode(&self, dst: &mut E) { self.as_bytes().encode(dst); } } -impl Encode for Vec { - fn encode(&self, dst: &mut Encoder) { +impl, E: Encoder> Encode for Vec { + fn encode(&self, dst: &mut E) { self.len().encode(dst); for item in self { item.encode(dst); @@ -402,8 +401,8 @@ impl Encode for Vec { } } -impl Encode for Option { - fn encode(&self, dst: &mut Encoder) { +impl, E: Encoder> Encode for Option { + fn encode(&self, dst: &mut E) { match self { None => dst.byte(0), Some(val) => { @@ -416,12 +415,12 @@ impl Encode for Option { macro_rules! encode_struct { ($name:ident ($($lt:tt)*) $($field:ident: $ty:ty,)*) => { - struct $name $($lt)* { + struct $name $(<$lt>)* { $($field: $ty,)* } - impl $($lt)* Encode for $name $($lt)* { - fn encode(&self, _dst: &mut Encoder) { + impl<$($lt, )*E:Encoder> Encode for $name $(<$lt>)* { + fn encode(&self, _dst: &mut E) { $(self.$field.encode(_dst);)* } } @@ -430,10 +429,10 @@ macro_rules! encode_struct { macro_rules! encode_enum { ($name:ident ($($lt:tt)*) $($fields:tt)*) => ( - enum $name $($lt)* { $($fields)* } + enum $name $(<$lt>)* { $($fields)* } - impl$($lt)* Encode for $name $($lt)* { - fn encode(&self, dst: &mut Encoder) { + impl<$($lt, )*E: Encoder> Encode for $name $(<$lt>)* { + fn encode(&self, dst: &mut E) { use self::$name::*; encode_enum!(@arms self dst (0) () $($fields)*) } @@ -471,25 +470,30 @@ macro_rules! encode_enum { macro_rules! encode_api { () => (); - (struct $name:ident<'a> { $($fields:tt)* } $($rest:tt)*) => ( - encode_struct!($name (<'a>) $($fields)*); - encode_api!($($rest)*); - ); - (struct $name:ident { $($fields:tt)* } $($rest:tt)*) => ( - encode_struct!($name () $($fields)*); - encode_api!($($rest)*); - ); - (enum $name:ident<'a> { $($variants:tt)* } $($rest:tt)*) => ( - encode_enum!($name (<'a>) $($variants)*); + (struct $name:ident$(<$lt:tt>)* { $($fields:tt)* } $($rest:tt)*) => ( + encode_struct!($name ($($lt)*) $($fields)*); encode_api!($($rest)*); ); - (enum $name:ident { $($variants:tt)* } $($rest:tt)*) => ( - encode_enum!($name () $($variants)*); + (enum $name:ident$(<$lt:tt>)* { $($variants:tt)* } $($rest:tt)*) => ( + encode_enum!($name ($($lt)*) $($variants)*); encode_api!($($rest)*); ); } wasm_bindgen_shared::shared_api!(encode_api); +pub struct TypeReference(pub syn::Type); +impl TypeReference { + fn new(id: &Ident) -> TypeReference { + TypeReference(syn::parse_quote! { #id }) + } +} + +impl Encode for TypeReference { + fn encode(&self, dst: &mut E) { + dst.type_reference(&self); + } +} + fn from_ast_method_kind<'a>( function: &'a ast::Function, intern: &'a Interner, diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs index 950ae9b45822..b011b44c7813 100644 --- a/crates/backend/src/util.rs +++ b/crates/backend/src/util.rs @@ -142,6 +142,9 @@ impl fmt::Display for ShortHash { env::var("CARGO_PKG_VERSION") .expect("should have CARGO_PKG_VERSION env var") .hash(&mut h); + + std::time::SystemTime::now().hash(&mut h); + // This may chop off 32 bits on 32-bit platforms, but that's ok, we // just want something to mix in below anyway. HASH.store(h.finish() as usize, SeqCst); diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 52c4d0abc1cd..7cb087d84f57 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-cli-support" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/cli-support" @@ -19,10 +19,10 @@ rustc-demangle = "0.1.13" serde_json = "1.0" tempfile = "3.0" walrus = "0.12.0" -wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.51' } -wasm-bindgen-shared = { path = "../shared", version = '=0.2.51' } -wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.51' } -wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.51' } -wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.51' } -wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.51' } +wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.52' } +wasm-bindgen-shared = { path = "../shared", version = '=0.2.52' } +wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.52' } +wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.52' } +wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.52' } +wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.52' } wasm-webidl-bindings = "0.5.0" diff --git a/crates/cli-support/src/decode.rs b/crates/cli-support/src/decode.rs index d58b49ff06dc..e418a42cb237 100644 --- a/crates/cli-support/src/decode.rs +++ b/crates/cli-support/src/decode.rs @@ -155,3 +155,30 @@ macro_rules! decode_api { } wasm_bindgen_shared::shared_api!(decode_api); + +fn get_u64(data: &mut &[u8]) -> u64 { + u64::from_be_bytes([ + get(data), + get(data), + get(data), + get(data), + get(data), + get(data), + get(data), + get(data), + ]) +} + +#[derive(Debug,Eq,PartialEq,Hash,Copy,Clone)] +pub struct TypeReference(pub u64); + +impl TypeReference { + #[inline] + pub fn is_null(&self) -> bool { self.0 == 0 } +} + +impl<'a> Decode<'a> for TypeReference { + fn decode(data: &mut &'a [u8]) -> Self { + TypeReference(get_u64(data)) + } +} diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index 70ef467809b0..d2988e5c1944 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -37,6 +37,8 @@ tys! { OPTIONAL UNIT CLAMPED + SUPERCONSTRUCTOR_CALLBACK + THIS_CALLBACK } #[derive(Debug, Clone)] @@ -67,6 +69,8 @@ pub enum Descriptor { Char, Option(Box), Unit, + SuperconstructorCallback, + ThisCallback, } #[derive(Debug, Clone)] @@ -142,6 +146,8 @@ impl Descriptor { CHAR => Descriptor::Char, UNIT => Descriptor::Unit, CLAMPED => Descriptor::_decode(data, true), + SUPERCONSTRUCTOR_CALLBACK => Descriptor::SuperconstructorCallback, + THIS_CALLBACK => Descriptor::ThisCallback, other => panic!("unknown descriptor: {}", other), } } diff --git a/crates/cli-support/src/intrinsic.rs b/crates/cli-support/src/intrinsic.rs index a54926f3bcb1..d2ce6cb5f30b 100644 --- a/crates/cli-support/src/intrinsic.rs +++ b/crates/cli-support/src/intrinsic.rs @@ -71,6 +71,10 @@ fn ref_string() -> Descriptor { Descriptor::Ref(Box::new(Descriptor::String)) } +fn ref_slice_anyref() -> Descriptor { + Descriptor::Ref(Box::new(Descriptor::Slice(Box::new(Descriptor::Anyref)))) +} + intrinsics! { pub enum Intrinsic { #[symbol = "__wbindgen_jsval_eq"] @@ -160,5 +164,23 @@ intrinsics! { #[symbol = "__wbindgen_init_anyref_table"] #[signature = fn() -> Unit] InitAnyrefTable, + #[symbol = "__wbindgen_export_get"] + #[signature = fn(U64) -> Anyref] + ExportGet, + #[symbol = "__wbindgen_instantiate"] + #[signature = fn(ref_anyref(), ref_slice_anyref()) -> U32] + Instantiate, + #[symbol = "__wbindgen_invoke"] + #[signature = fn(ref_anyref(), ref_slice_anyref()) -> U32] + Invoke, + #[symbol = "__wbindgen_wasm_pointer_get"] + #[signature = fn(ref_anyref()) -> U32] + WasmPointerGet, + #[symbol = "__wbindgen_wasm_pointer_set"] + #[signature = fn(ref_anyref(), U32) -> Unit] + WasmPointerSet, + #[symbol = "__wbindgen_set_heapref_state"] + #[signature = fn(U32, Boolean) -> Unit] + SetHeaprefState, } } diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 34d417e6e920..399551135ca6 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -7,7 +7,7 @@ use crate::js::incoming; use crate::js::outgoing; use crate::js::Context; -use crate::webidl::Binding; +use crate::webidl::{Binding, NonstandardIncoming}; use failure::{bail, Error}; use std::collections::HashSet; use wasm_webidl_bindings::ast; @@ -20,6 +20,12 @@ pub struct Builder<'a, 'b> { /// Prelude JS which is present before the main invocation to prepare /// arguments. args_prelude: String, + /// Prelude JS which is present before the main invocation but within a + /// try/finally block, to hold assertions that may require cleanup. + assertions: String, + /// Finally block to be executed only in the event that the main + /// invocation is reached, used for cleanups like releasing state. + cleanup: String, /// Finally block to be executed regardless of the call's status, mostly /// used for cleanups like free'ing. finally: String, @@ -41,8 +47,8 @@ pub struct Builder<'a, 'b> { /// so what class it's constructing. constructor: Option, /// Whether or not this is building a method of a Rust class instance, and - /// whether or not the method consumes `self` or not. - method: Option, + /// if so of what class and whether reference to self is mutable. + method: Option<(String, bool)>, /// Whether or not we're catching exceptions from the main function /// invocation. Currently only used for imports. catch: bool, @@ -54,6 +60,7 @@ pub struct Builder<'a, 'b> { pub struct JsBuilder { typescript: Vec, prelude: String, + assertions: String, finally: String, tmp: usize, args: Vec, @@ -71,6 +78,8 @@ impl<'a, 'b> Builder<'a, 'b> { log_error: cx.config.debug, cx, args_prelude: String::new(), + assertions: String::new(), + cleanup: String::new(), finally: String::new(), ret_finally: String::new(), function_args: Vec::new(), @@ -85,8 +94,8 @@ impl<'a, 'b> Builder<'a, 'b> { } } - pub fn method(&mut self, consumed: bool) { - self.method = Some(consumed); + pub fn method(&mut self, class: &str, mutable: bool) { + self.method = Some((class.to_string(), mutable)); } pub fn constructor(&mut self, class: &str) { @@ -125,7 +134,7 @@ impl<'a, 'b> Builder<'a, 'b> { let mut arg_names = Vec::new(); let mut js; if incoming_args { - let mut webidl_params = webidl.params.iter(); + let mut binding_args = binding.incoming.iter(); // If we're returning via an out pointer then it's guaranteed to be // the first argument. This isn't an argument of the function shim @@ -138,7 +147,7 @@ impl<'a, 'b> Builder<'a, 'b> { // around address 8, so this should be a safe address to use for // returning data through. if binding.return_via_outptr.is_some() { - drop(webidl_params.next()); + drop(binding_args.next()); self.args_prelude.push_str("const retptr = 8;\n"); arg_names.push("retptr".to_string()); } @@ -146,35 +155,48 @@ impl<'a, 'b> Builder<'a, 'b> { // If this is a method then we're generating this as part of a class // method, so the leading parameter is the this pointer stored on // the JS object, so synthesize that here. - match self.method { - Some(consumes_self) => { - drop(webidl_params.next()); - if self.cx.config.debug { - self.args_prelude.push_str( - "if (this.ptr == 0) throw new Error('Attempt to use a moved value');\n", - ); - } - if consumes_self { - self.args_prelude.push_str("const ptr = this.ptr;\n"); - self.args_prelude.push_str("this.ptr = 0;\n"); - arg_names.push("ptr".to_string()); - } else { - arg_names.push("this.ptr".to_string()); - } + if let Some((class, mutable)) = &self.method { + drop(binding_args.next()); + if self.cx.config.debug { + self.args_prelude.push_str(" + if (this[WASM_PTR] == 0) { + throw new Error('Attempt to use a moved value'); + } + "); } - None => {} + self.cx.require_internal_export("__wbindgen_release")?; + self.args_prelude.push_str(&format!("let me = this[BORROW]({}, {});\n", class, mutable)); + self.cleanup.push_str("me = undefined;\n"); + self.finally.push_str(&format!("if (typeof me === 'number') wasm.__wbindgen_release(me, {});\n", mutable)); + arg_names.push("me".to_string()); } // And now take the rest of the parameters and generate a name for them. - for (i, _) in webidl_params.enumerate() { + for (i, argument) in binding_args.enumerate() { let arg = match explicit_arg_names { Some(list) => list[i].clone(), None => format!("arg{}", i), }; - self.function_args.push(arg.clone()); - arg_names.push(arg); + // Strip synthesised callback argument from exported function signature + match argument { + NonstandardIncoming::SuperconstructorCallback + | NonstandardIncoming::ThisCallback => (), + _ => { + self.function_args.push(arg.clone()); + arg_names.push(arg); + } + } } js = JsBuilder::new(arg_names); + if let Some(class) = &self.constructor { + js.prelude(&format!( + "const _this = () => HOST_PTR in this ? this[HOST_PTR] : this[HOST_PTR] = {}(this);\n", + if self.cx.config.anyref { "addToAnyrefTable" } else { "addHeapObject" }, + )); + if self.cx.config.weak_refs { + js.finally(&format!("{}FinalizationGroup.register(this, ret, ret);", class)); + } + } let mut args = incoming::Incoming::new(self.cx, &webidl.params, &mut js); for argument in binding.incoming.iter() { self.invoc_args.extend(args.process(argument)?); @@ -205,6 +227,7 @@ impl<'a, 'b> Builder<'a, 'b> { // Save off the results of JS generation for the arguments. self.args_prelude.push_str(&js.prelude); + self.assertions.push_str(&js.assertions); self.finally.push_str(&js.finally); self.ts_args.extend(js.typescript); @@ -219,7 +242,6 @@ impl<'a, 'b> Builder<'a, 'b> { if incoming_args { if binding.outgoing.len() == 0 { assert!(binding.return_via_outptr.is_none()); - assert!(self.constructor.is_none()); let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; return Ok(self.finalize(&invoc)); } @@ -338,6 +360,7 @@ impl<'a, 'b> Builder<'a, 'b> { } self.ret_finally.push_str(&js.finally); self.ret_prelude.push_str(&js.prelude); + self.ret_prelude.push_str(&js.assertions); self.ts_ret = Some(js.typescript.remove(0)); let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; Ok(self.finalize(&invoc)) @@ -379,6 +402,11 @@ impl<'a, 'b> Builder<'a, 'b> { call.push_str("\n"); } + let cleanup = self.cleanup.trim(); + if self.cleanup.len() != 0 { + call = format!("try {{\n{}}} finally {{\n{}\n}}\n", call, cleanup); + } + if self.catch { call = format!("try {{\n{}}} catch (e) {{\n handleError(e)\n}}\n", call); } @@ -391,6 +419,10 @@ impl<'a, 'b> Builder<'a, 'b> { call = format!("try {{\n{}}} catch (e) {{\n logError(e)\n}}\n", call); } + if self.assertions.len() > 0 { + call = format!("{}\n{}", self.assertions.trim(), call); + } + let finally = self.finally.trim(); if finally.len() != 0 { call = format!("try {{\n{}}} finally {{\n{}\n}}\n", call, finally); @@ -461,7 +493,9 @@ impl<'a, 'b> Builder<'a, 'b> { }) .collect(); if let Some(ts) = &self.ts_ret { - ret.push_str(&format!("@returns {{{}}}", ts.ty)); + if self.constructor.is_none() { + ret.push_str(&format!("@returns {{{}}}", ts.ty)); + } } ret } @@ -473,6 +507,7 @@ impl JsBuilder { args, tmp: 0, finally: String::new(), + assertions: String::new(), prelude: String::new(), typescript: Vec::new(), } @@ -511,6 +546,13 @@ impl JsBuilder { } } + pub fn assertion(&mut self, assertion: &str) { + for line in assertion.trim().lines() { + self.assertions.push_str(line); + self.assertions.push_str("\n"); + } + } + pub fn finally(&mut self, finally: &str) { for line in finally.trim().lines() { self.finally.push_str(line); diff --git a/crates/cli-support/src/js/incoming.rs b/crates/cli-support/src/js/incoming.rs index 9791b7e22c6a..291905a66f38 100644 --- a/crates/cli-support/src/js/incoming.rs +++ b/crates/cli-support/src/js/incoming.rs @@ -27,13 +27,6 @@ impl<'a, 'b> Incoming<'a, 'b> { } pub fn process(&mut self, incoming: &NonstandardIncoming) -> Result, Error> { - let before = self.js.typescript_len(); - let ret = self.nonstandard(incoming)?; - assert_eq!(before + 1, self.js.typescript_len()); - Ok(ret) - } - - fn nonstandard(&mut self, incoming: &NonstandardIncoming) -> Result, Error> { let single = match incoming { NonstandardIncoming::Standard(val) => return self.standard(val), @@ -112,30 +105,24 @@ impl<'a, 'b> Incoming<'a, 'b> { format!("{}.codePointAt(0)", expr) } - // When moving a type back into Rust we need to clear out the - // internal pointer in JS to prevent it from being reused again in - // the future. + // Send the pointer to Rust, where the Rc that it represents will be cloned NonstandardIncoming::RustType { class, val } => { let (expr, ty) = self.standard_typed(val)?; assert_eq!(ty, ast::WebidlScalarType::Any.into()); self.assert_class(&expr, &class); self.assert_not_moved(&expr); - let i = self.js.tmp(); - self.js.prelude(&format!("const ptr{} = {}.ptr;", i, expr)); - self.js.prelude(&format!("{}.ptr = 0;", expr)); self.js.typescript_required(class); - format!("ptr{}", i) + format!("{}[WASM_PTR]", expr) } - // Here we can simply pass along the pointer with no extra fluff - // needed. - NonstandardIncoming::RustTypeRef { class, val } => { + // Borrow the relevant object from the leaf's prototype chain + NonstandardIncoming::RustTypeRef { class, val, mutable } => { let (expr, ty) = self.standard_typed(val)?; assert_eq!(ty, ast::WebidlScalarType::Any.into()); self.assert_class(&expr, &class); self.assert_not_moved(&expr); self.js.typescript_required(class); - format!("{}.ptr", expr) + format!("{}[BORROW]({}, {})", expr, class, mutable) } // the "stack-ful" nature means that we're always popping from the @@ -208,20 +195,19 @@ impl<'a, 'b> Incoming<'a, 'b> { format!("isLikeNone({0}) ? {1} : {0}", expr, hole) } - // `None` here is zero, but if `Some` then we need to clear out the - // internal pointer because the value is being moved. + // `None` here is zero, but if `Some` then we need to send the internal + // pointer to Rust where the `Rc` it represents will be cloned. NonstandardIncoming::OptionRustType { class, val } => { let (expr, ty) = self.standard_typed(val)?; assert_eq!(ty, ast::WebidlScalarType::Any.into()); self.cx.expose_is_like_none(); let i = self.js.tmp(); self.js.prelude(&format!("let ptr{} = 0;", i)); - self.js.prelude(&format!("if (!isLikeNone({0})) {{", expr)); + self.js.assertion(&format!("if (!isLikeNone({0})) {{", expr)); self.assert_class(&expr, class); self.assert_not_moved(&expr); - self.js.prelude(&format!("ptr{} = {}.ptr;", i, expr)); - self.js.prelude(&format!("{}.ptr = 0;", expr)); - self.js.prelude("}"); + self.js.assertion(&format!("ptr{} = {}[WASM_PTR];", i, expr)); + self.js.assertion("}"); self.js.typescript_optional(class); format!("ptr{}", i) } @@ -332,6 +318,31 @@ impl<'a, 'b> Incoming<'a, 'b> { self.js.typescript_optional(kind.js_ty()); return Ok(vec![format!("ptr{}", i), format!("len{}", i)]); } + + NonstandardIncoming::SuperconstructorCallback => { + let expr = "_super"; + self.js.prelude(&format!("const {} = (...args) => {{ super(...args); return _this(); }}", expr)); + if self.cx.config.anyref { + expr.to_string() + } else { + self.cx.expose_borrowed_objects(); + self.cx.expose_global_stack_pointer(); + self.js.finally("heap[stack_pointer++] = undefined;"); + format!("addBorrowedObject({})", expr) + } + } + + NonstandardIncoming::ThisCallback => { + let expr = "_this"; + if self.cx.config.anyref { + expr.to_string() + } else { + self.cx.expose_borrowed_objects(); + self.cx.expose_global_stack_pointer(); + self.js.finally("heap[stack_pointer++] = undefined;"); + format!("addBorrowedObject({})", expr) + } + } }; Ok(vec![single]) } @@ -468,7 +479,7 @@ impl<'a, 'b> Incoming<'a, 'b> { fn assert_class(&mut self, arg: &str, class: &str) { self.cx.expose_assert_class(); self.js - .prelude(&format!("_assertClass({}, {});", arg, class)); + .assertion(&format!("_assertClass({}, {});", arg, class)); } fn assert_number(&mut self, arg: &str) { @@ -476,7 +487,7 @@ impl<'a, 'b> Incoming<'a, 'b> { return; } self.cx.expose_assert_num(); - self.js.prelude(&format!("_assertNum({});", arg)); + self.js.assertion(&format!("_assertNum({});", arg)); } fn assert_bool(&mut self, arg: &str) { @@ -484,7 +495,7 @@ impl<'a, 'b> Incoming<'a, 'b> { return; } self.cx.expose_assert_bool(); - self.js.prelude(&format!("_assertBoolean({});", arg)); + self.js.assertion(&format!("_assertBoolean({});", arg)); } fn assert_optional_number(&mut self, arg: &str) { @@ -492,9 +503,9 @@ impl<'a, 'b> Incoming<'a, 'b> { return; } self.cx.expose_is_like_none(); - self.js.prelude(&format!("if (!isLikeNone({})) {{", arg)); + self.js.assertion(&format!("if (!isLikeNone({})) {{", arg)); self.assert_number(arg); - self.js.prelude("}"); + self.js.assertion("}"); } fn assert_optional_bool(&mut self, arg: &str) { @@ -502,18 +513,18 @@ impl<'a, 'b> Incoming<'a, 'b> { return; } self.cx.expose_is_like_none(); - self.js.prelude(&format!("if (!isLikeNone({})) {{", arg)); + self.js.assertion(&format!("if (!isLikeNone({})) {{", arg)); self.assert_bool(arg); - self.js.prelude("}"); + self.js.assertion("}"); } fn assert_not_moved(&mut self, arg: &str) { if !self.cx.config.debug { return; } - self.js.prelude(&format!( + self.js.assertion(&format!( "\ - if ({0}.ptr === 0) {{ + if ({}[WASM_PTR] === 0) {{ throw new Error('Attempt to use a moved value'); }} ", diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 4829ef65eeba..81fc626b62e9 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -4,6 +4,7 @@ use crate::webidl; use crate::webidl::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; use crate::webidl::{AuxValue, Binding}; use crate::webidl::{JsImport, JsImportName, NonstandardWebidlSection, WasmBindgenAux}; +use crate::webidl::AuxTypeRef; use crate::{Bindgen, EncodeInto, OutputMode}; use failure::{bail, Error, ResultExt}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; @@ -48,17 +49,28 @@ pub struct Context<'a> { /// A map of the name of npm dependencies we've loaded so far to the path /// they're defined in as well as their version specification. pub npm_dependencies: HashMap, + + /// Strings in `"x": Y` form, that form the members of output `DEFINITION_MAP` + /// used by `__wbindgen_export_get` intrinsic function to resolve Rust IDs ("x") + /// to their JavaScript type (Y). + definitions: Vec, } #[derive(Default)] pub struct ExportedClass { + rust_id: u64, comments: String, contents: String, typescript: String, has_constructor: bool, - wrap_needed: bool, /// Map from field name to type as a string plus whether it has a setter typescript_fields: HashMap, + prototype: Option, +} + +pub struct ExportedPrototype { + prototype_name: String, + is_export: bool, } const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"]; @@ -94,6 +106,7 @@ impl<'a> Context<'a> { module, memory, npm_dependencies: Default::default(), + definitions: Default::default(), }) } @@ -103,6 +116,7 @@ impl<'a> Context<'a> { fn export( &mut self, + rust_id: Option, export_name: &str, contents: &str, comments: Option, @@ -117,28 +131,30 @@ impl<'a> Context<'a> { self.globals.push_str(c); self.typescript.push_str(c); } - let global = match self.config.mode { + let (global, definition_name) = match self.config.mode { OutputMode::Node { experimental_modules: false, - } => { + } => ( if contents.starts_with("class") { format!("{}\nmodule.exports.{1} = {1};\n", contents, export_name) } else { format!("module.exports.{} = {};\n", export_name, contents) - } - } - OutputMode::NoModules { .. } => { + }, + format!("module.exports.{}", export_name) + ), + OutputMode::NoModules { .. } => ( if contents.starts_with("class") { format!("{}\n__exports.{1} = {1};\n", contents, export_name) } else { format!("__exports.{} = {};\n", export_name, contents) - } - } + }, + format!("__exports.{}", export_name), + ), OutputMode::Bundler { .. } | OutputMode::Node { experimental_modules: true, } - | OutputMode::Web => { + | OutputMode::Web => ( if contents.starts_with("function") { let body = &contents[8..]; if export_name == definition_name { @@ -155,10 +171,12 @@ impl<'a> Context<'a> { } else { assert_eq!(export_name, definition_name); format!("export const {} = {};\n", export_name, contents) - } - } + }, + definition_name, + ) }; self.global(&global); + self.expose_definition(rust_id, &definition_name); Ok(()) } @@ -214,7 +232,11 @@ impl<'a> Context<'a> { // Cause any future calls to `should_write_global` to panic, making sure // we don't ask for items which we can no longer emit. - drop(self.exposed_globals.take().unwrap()); + let exposed_globals = self.exposed_globals.take().unwrap(); + if exposed_globals.contains("definition_map") { + self.global(&format!("const DEFINITION_MAP = {{\n{}\n}};", self.definitions.join(",\n"))); + } + drop(exposed_globals); self.finalize_js(module_name, needs_manual_start) } @@ -589,44 +611,55 @@ impl<'a> Context<'a> { } fn write_classes(&mut self) -> Result<(), Error> { - for (class, exports) in self.exported_classes.take().unwrap() { - self.write_class(&class, &exports)?; + let classes = &mut self.exported_classes.take().unwrap(); + + // Class definitions are not hoisted, so derived classes cannot be output + // before their respective base classes. + if !classes.is_empty() { + use std::collections::VecDeque; + + self.expose_global_ptr_symbols()?; + + let mut todo = classes.iter().collect::>(); + let mut done = HashSet::new(); + + while let Some((name, exports)) = todo.pop_front() { + match &exports.prototype { + // If this class extends another exported type + // that has not yet been written out, push it to the back of the queue + Some(ExportedPrototype { prototype_name, is_export: true }) + if !done.contains(prototype_name) => todo.push_back((name, exports)), + + // Otherwise, write it out and mark it done + _ => { + self.write_class(name, exports)?; + done.insert(name); + } + } + } } + Ok(()) } fn write_class(&mut self, name: &str, class: &ExportedClass) -> Result<(), Error> { - let mut dst = format!("class {} {{\n", name); + let mut dst = match &class.prototype { + Some(ExportedPrototype { prototype_name, .. }) => format!("class {} extends {} {{\n", name, prototype_name), + None => format!("class {} {{\n", name), + }; let mut ts_dst = format!("export {}", dst); - if self.config.debug && !class.has_constructor { - dst.push_str( - " - constructor() { - throw new Error('cannot invoke `new` directly'); - } - ", - ); - } - - if class.wrap_needed { - dst.push_str(&format!( - " - static __wrap(ptr) {{ - const obj = Object.create({}.prototype); - obj.ptr = ptr; - {} - return obj; - }} - ", - name, - if self.config.weak_refs { - format!("{}FinalizationGroup.register(obj, obj.ptr, obj.ptr);", name) - } else { - String::new() - }, - )); - } + self.expose_uint32_memory(); + dst.push_str(&format!( + " + [BORROW](klass, mutable) {{ + {}[0] = klass[RUST_ID]; + return wasm.{}(this[WASM_PTR], u32CvtShim[0], u32CvtShim[1], mutable); + }} + ", + self.expose_uint64_cvt_shim(), + wasm_bindgen_shared::borrow_from_prototype_function(name), + )); if self.config.weak_refs { self.global(&format!( @@ -644,9 +677,9 @@ impl<'a> Context<'a> { dst.push_str(&format!( " - free() {{ - const ptr = this.ptr; - this.ptr = 0; + [FREE]() {{ + const ptr = this[WASM_PTR]; + this[WASM_PTR] = 0; {} wasm.{}(ptr); }} @@ -658,7 +691,7 @@ impl<'a> Context<'a> { }, wasm_bindgen_shared::free_function(&name), )); - ts_dst.push_str(" free(): void;\n"); + ts_dst.push_str(" [__wbg_free](): void;\n"); dst.push_str(&class.contents); ts_dst.push_str(&class.typescript); @@ -678,7 +711,8 @@ impl<'a> Context<'a> { dst.push_str("}\n"); ts_dst.push_str("}\n"); - self.export(&name, &dst, Some(class.comments.clone()))?; + self.export(Some(class.rust_id), &name, &dst, Some(class.comments.clone()))?; + self.global(&format!("{}[RUST_ID] = BigInt('{}');", name, class.rust_id)); self.typescript.push_str(&ts_dst); Ok(()) @@ -703,6 +737,12 @@ impl<'a> Context<'a> { } } + fn expose_definition(&mut self, rust_id: Option, name: &str) { + if let Some(rust_id) = rust_id { + self.definitions.push(format!("'{}': {}", rust_id, name)); + } + } + fn expose_drop_ref(&mut self) { if !self.should_write_global("drop_ref") { return; @@ -730,6 +770,21 @@ impl<'a> Context<'a> { )); } + fn expose_global_ptr_symbols(&mut self) -> Result<(), Error> { + if !self.should_write_global("ptr") { + return Ok(()); + } + self.global(" + const RUST_ID = Symbol('RUST_ID'); + const WASM_PTR = Symbol('WASM_PTR'); + const HOST_PTR = Symbol('HOST_PTR'); + const BORROW = Symbol('BORROW'); + const FREE = Symbol('FREE'); + "); + self.typescript.push_str("export const __wbg_free: unique symbol;\n"); + self.export(None, "__wbg_free", "FREE", None) + } + fn expose_global_heap(&mut self) { if !self.should_write_global("heap") { return; @@ -756,7 +811,12 @@ impl<'a> Context<'a> { // Accessing a heap object is just a simple index operation due to how // the stack/heap are laid out. - self.global("function getObject(idx) { return heap[idx]; }"); + self.global(&format!("function getObject(idx) {{ {} }}", if self.config.weak_refs {" + const current = heap[idx]; + return Object.getPrototypeOf(current) === WeakRef.prototype + ? current.deref() + : current; + "} else { "return heap[idx]" })); } fn expose_not_defined(&mut self) { @@ -1409,7 +1469,7 @@ impl<'a> Context<'a> { if (!(instance instanceof klass)) { throw new Error(`expected instance of ${klass.name}`); } - return instance.ptr; + return instance[WASM_PTR]; } ", ); @@ -1739,10 +1799,6 @@ impl<'a> Context<'a> { } } - fn require_class_wrap(&mut self, name: &str) { - require_class(&mut self.exported_classes, name).wrap_needed = true; - } - fn import_name(&mut self, import: &JsImport) -> Result { if let Some(name) = self.imported_names.get(&import.name) { let mut name = name.clone(); @@ -1904,7 +1960,7 @@ impl<'a> Context<'a> { } for s in aux.structs.iter() { - self.generate_struct(s)?; + self.generate_struct(s, &aux.structs)?; } self.typescript.push_str(&aux.extra_typescript); @@ -1969,10 +2025,11 @@ impl<'a> Context<'a> { builder.disable_log_error(true); match &export.kind { AuxExportKind::Function(_) => {} - AuxExportKind::StaticFunction { .. } => {} + AuxExportKind::StaticFunction { .. } | AuxExportKind::StaticGetter { .. } | AuxExportKind::StaticSetter { .. } => {} AuxExportKind::Constructor(class) => builder.constructor(class), - AuxExportKind::Getter { .. } | AuxExportKind::Setter { .. } => builder.method(false), - AuxExportKind::Method { consumed, .. } => builder.method(*consumed), + AuxExportKind::Method { class, mutable, .. } => builder.method(class, *mutable), + AuxExportKind::Getter { class, .. } => builder.method(class, false), + AuxExportKind::Setter { class, .. } => builder.method(class, true), } // Process the `binding` and generate a bunch of JS/TypeScript/etc. @@ -1991,7 +2048,7 @@ impl<'a> Context<'a> { // on what's being exported. match &export.kind { AuxExportKind::Function(name) => { - self.export(&name, &format!("function{}", js), Some(docs))?; + self.export(None, &name, &format!("function{}", js), Some(docs))?; self.globals.push_str("\n"); self.typescript.push_str("export function "); self.typescript.push_str(&name); @@ -2020,6 +2077,15 @@ impl<'a> Context<'a> { let exported = require_class(&mut self.exported_classes, class); exported.push(&docs, name, "static ", &js, &ts); } + AuxExportKind::StaticGetter { class, field } => { + let exported = require_class(&mut self.exported_classes, class); + exported.push(&docs, field, "static get ", &js, &ts); + } + AuxExportKind::StaticSetter { class, field } => { + let arg_ty = builder.ts_args[0].ty.clone(); + let exported = require_class(&mut self.exported_classes, class); + exported.push(&docs, field, "static set ", &js, &arg_ty); + } AuxExportKind::Method { class, name, .. } => { let exported = require_class(&mut self.exported_classes, class); exported.push(&docs, name, "", &js, &ts); @@ -2446,13 +2512,6 @@ impl<'a> Context<'a> { Ok(format!("delete {}[{}]", args[0], args[1])) } - AuxImport::WrapInExportedClass(class) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); - assert!(!variadic); - assert_eq!(args.len(), 1); - self.require_class_wrap(class); - Ok(format!("{}.__wrap({})", class, args[0])) - } AuxImport::Intrinsic(intrinsic) => { assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); @@ -2689,6 +2748,63 @@ impl<'a> Context<'a> { } base } + + Intrinsic::ExportGet => { + assert_eq!(args.len(), 1); + self.should_write_global("definition_map"); + format!("DEFINITION_MAP[{}]", args[0]) + } + + Intrinsic::Instantiate => { + assert_eq!(args.len(), 2); + prelude.push_str(&format!("const instance = new ({})(...{});", args[0], args[1])); + prelude.push_str(if self.config.anyref { + "wasm.__wbg_anyref_table.set(instance[HOST_PTR], instance);" + } else { + "heap[instance[HOST_PTR]] = instance;" + }); + "instance[WASM_PTR]".to_string() + } + + Intrinsic::Invoke => { + assert_eq!(args.len(), 2); + format!("{}(...{})", args[0], args[1]) + } + + Intrinsic::WasmPointerGet => { + assert_eq!(args.len(), 1); + format!("{}[WASM_PTR]", args[0]) + } + + Intrinsic::WasmPointerSet => { + assert_eq!(args.len(), 2); + format!("{}[WASM_PTR] = {}", args[0], args[1]) + } + + Intrinsic::SetHeaprefState => { + if self.config.weak_refs{ + assert_eq!(args.len(), 2); + prelude.push_str(&format!(" + const idx = {}; + const makeWeak = {}; + const current = {}; + ", args[0], args[1], if self.config.anyref { + "wasm.__wbg_anyref_table.get(idx)" + } else { + "heap[idx]" + })); + format!( + "if ((Object.getPrototypeOf(current) === WeakRef.prototype) !== makeWeak) {{ {} }}", + if self.config.anyref { + "wasm.__wbg_anyref_table.set(idx, makeWeak ? new WeakRef(current) : current.deref());" + } else { + "heap[idx] = makeWeak ? new WeakRef(current) : current.deref();" + } + ) + } else { + Default::default() + } + } }; Ok(expr) } @@ -2704,6 +2820,7 @@ impl<'a> Context<'a> { } self.typescript.push_str("\n}\n"); self.export( + None, &enum_.name, &format!("Object.freeze({{ {} }})", variants), Some(format_doc_comments(&enum_.comments, None)), @@ -2712,9 +2829,15 @@ impl<'a> Context<'a> { Ok(()) } - fn generate_struct(&mut self, struct_: &AuxStruct) -> Result<(), Error> { + fn generate_struct(&mut self, struct_: &AuxStruct, structs: &Vec) -> Result<(), Error> { + let prototype = struct_.prototype.as_ref().map(|p| match p { + AuxTypeRef::Import(js_import) => ExportedPrototype { prototype_name: self.import_name(js_import).unwrap(), is_export: false }, + AuxTypeRef::Export(n) => ExportedPrototype { prototype_name: structs[*n].name.clone(), is_export: true }, + }); let class = require_class(&mut self.exported_classes, &struct_.name); class.comments = format_doc_comments(&struct_.comments, None); + class.rust_id = struct_.rust_id; + class.prototype = prototype; Ok(()) } diff --git a/crates/cli-support/src/js/outgoing.rs b/crates/cli-support/src/js/outgoing.rs index 7ec175a65d55..da1ce6220987 100644 --- a/crates/cli-support/src/js/outgoing.rs +++ b/crates/cli-support/src/js/outgoing.rs @@ -36,14 +36,6 @@ impl<'a, 'b> Outgoing<'a, 'b> { Ok(format!("String.fromCodePoint({})", self.arg(*idx))) } - // Just need to wrap up the pointer we get from Rust into a JS type - // and then we can pass that along - NonstandardOutgoing::RustType { class, idx } => { - self.js.typescript_required(class); - self.cx.require_class_wrap(class); - Ok(format!("{}.__wrap({})", class, self.arg(*idx))) - } - // Just a small wrapper around `getObject` NonstandardOutgoing::BorrowedAnyref { idx } => { self.js.typescript_required("any"); @@ -239,16 +231,6 @@ impl<'a, 'b> Outgoing<'a, 'b> { )) } - NonstandardOutgoing::OptionRustType { class, idx } => { - self.cx.require_class_wrap(class); - self.js.typescript_optional(class); - Ok(format!( - "{0} === 0 ? undefined : {1}.__wrap({0})", - self.arg(*idx), - class, - )) - } - NonstandardOutgoing::OptionU32Sentinel { idx } => { self.js.typescript_optional("number"); Ok(format!( diff --git a/crates/cli-support/src/webidl/incoming.rs b/crates/cli-support/src/webidl/incoming.rs index b9eb7e4945de..f652bc0d0932 100644 --- a/crates/cli-support/src/webidl/incoming.rs +++ b/crates/cli-support/src/webidl/incoming.rs @@ -99,8 +99,9 @@ pub enum NonstandardIncoming { signed: bool, }, - /// An optional Rust-based type which internally has a pointer that's - /// wrapped up in a JS class. This transfers ownership from JS to Rust. + /// A Rc'd Rust-based type which internally has a pointer that's + /// wrapped up in a JS class. This sends that pointer to Rust, so that + /// it can be cloned. RustType { class: String, val: ast::IncomingBindingExpression, @@ -111,9 +112,10 @@ pub enum NonstandardIncoming { RustTypeRef { class: String, val: ast::IncomingBindingExpression, + mutable: bool, }, - /// An optional owned Rust type being transferred from JS to Rust. + /// An optional Rc'd Rust type being cloned from JS to Rust. OptionRustType { class: String, val: ast::IncomingBindingExpression, @@ -125,6 +127,12 @@ pub enum NonstandardIncoming { /// An arbitrary `anyref` being passed into Rust, but explicitly one that's /// borrowed and doesn't need to be persisted in a heap table. BorrowedAnyref { val: ast::IncomingBindingExpression }, + + // A callback that invokes `super()` being passed into Rust. + SuperconstructorCallback, + + // A callback that returns `this` being passed into Rust. + ThisCallback, } /// Builder used to create a incomig binding from a `Descriptor`. @@ -187,6 +195,16 @@ impl IncomingBuilder { self.webidl.push(ast::WebidlScalarType::Any); self.bindings.push(NonstandardIncoming::Standard(expr)); } + Descriptor::SuperconstructorCallback => { + self.wasm.push(ValType::Anyref); + self.webidl.push(ast::WebidlScalarType::Object); + self.bindings.push(NonstandardIncoming::SuperconstructorCallback); + } + Descriptor::ThisCallback => { + self.wasm.push(ValType::Anyref); + self.webidl.push(ast::WebidlScalarType::Object); + self.bindings.push(NonstandardIncoming::ThisCallback); + } Descriptor::RustStruct(class) => { let expr = self.expr_get(); self.wasm.push(ValType::I32); @@ -247,6 +265,7 @@ impl IncomingBuilder { self.bindings.push(NonstandardIncoming::RustTypeRef { val: expr, class: class.to_string(), + mutable, }); } Descriptor::Anyref => { diff --git a/crates/cli-support/src/webidl/mod.rs b/crates/cli-support/src/webidl/mod.rs index f780f73edd70..7fc897a14657 100644 --- a/crates/cli-support/src/webidl/mod.rs +++ b/crates/cli-support/src/webidl/mod.rs @@ -229,14 +229,19 @@ pub enum AuxExportKind { /// This is a free function (ish) but scoped inside of a class name. StaticFunction { class: String, name: String }, + /// This is a getter scoped inside of a class name. + StaticGetter { class: String, field: String }, + + /// This is a setter scoped inside of a class name. + StaticSetter { class: String, field: String }, + /// This is a member function of a class where the first parameter is the /// implicit integer stored in the class instance. Method { class: String, name: String, - /// Whether or not this is calling a by-value method in Rust and should - /// clear the internal pointer in JS automatically. - consumed: bool, + /// Whether or not this requires a mutable reference in Rust + mutable: bool, }, } @@ -252,10 +257,14 @@ pub struct AuxEnum { #[derive(Debug)] pub struct AuxStruct { + /// Internal identifier used by Rust to reference this struct + pub rust_id: u64, /// The name of this struct pub name: String, /// The copied Rust comments to forward to JS pub comments: String, + /// The (optional) superclass to forward to JS + pub prototype: Option, } /// All possible types of imports that can be imported by a wasm module. @@ -392,15 +401,6 @@ pub enum AuxImport { /// of import here? IndexingDeleterOfObject, - /// This import is a generated shim which will wrap the provided pointer in - /// a JS object corresponding to the Class name given here. The class name - /// is one that is exported from the Rust/wasm. - /// - /// TODO: sort of like the export map below we should ideally create the - /// `anyref` from within Rust itself and then return it directly rather than - /// requiring an intrinsic here to do so. - WrapInExportedClass(String), - /// This is an intrinsic function expected to be implemented with a JS glue /// shim. Each intrinsic has its own expected signature and implementation. Intrinsic(Intrinsic), @@ -468,6 +468,17 @@ pub enum JsImportName { VendorPrefixed { name: String, prefixes: Vec }, } +/// Resolved references to types either imported into or exported from WASM. +/// Used to indicate a Struct's prototype. +#[derive(Clone,Debug)] +pub enum AuxTypeRef { + /// Imported classes are resolved to their JsImport (for their name) + Import(JsImport), + + /// Exported structs are resolved to their index in the WasmBindgenAux::structs vector + Export(usize), +} + struct Context<'a> { start_found: bool, module: &'a mut Module, @@ -481,6 +492,8 @@ struct Context<'a> { anyref_enabled: bool, wasm_interface_types: bool, support_start: bool, + type_refs: HashMap, + struct_prototype_refs: Vec<(usize, decode::TypeReference)>, } pub fn process( @@ -505,6 +518,8 @@ pub fn process( anyref_enabled, wasm_interface_types, support_start, + type_refs: Default::default(), + struct_prototype_refs: Default::default(), }; cx.init()?; @@ -516,6 +531,7 @@ pub fn process( cx.standard(&standard)?; } + cx.resolve_prototype_refs(); cx.verify()?; let bindings = cx.module.customs.add(cx.bindings); @@ -758,17 +774,31 @@ impl<'a> Context<'a> { decode::MethodKind::Constructor => AuxExportKind::Constructor(class), decode::MethodKind::Operation(op) => match op.kind { decode::OperationKind::Getter(f) => { - descriptor.arguments.insert(0, Descriptor::I32); - AuxExportKind::Getter { - class, - field: f.to_string(), + if op.is_static { + AuxExportKind::StaticGetter { + class, + field: f.to_string(), + } + } else { + descriptor.arguments.insert(0, Descriptor::I32); + AuxExportKind::Getter { + class, + field: f.to_string(), + } } } decode::OperationKind::Setter(f) => { - descriptor.arguments.insert(0, Descriptor::I32); - AuxExportKind::Setter { - class, - field: f.to_string(), + if op.is_static { + AuxExportKind::StaticSetter { + class, + field: f.to_string(), + } + } else { + descriptor.arguments.insert(0, Descriptor::I32); + AuxExportKind::Setter { + class, + field: f.to_string(), + } } } _ if op.is_static => AuxExportKind::StaticFunction { @@ -780,7 +810,7 @@ impl<'a> Context<'a> { AuxExportKind::Method { class, name: export.function.name.to_string(), - consumed: export.consumed, + mutable: export.mutable, } } }, @@ -1084,6 +1114,8 @@ impl<'a> Context<'a> { import: &decode::Import<'_>, type_: &decode::ImportType<'_>, ) -> Result<(), Error> { + let import = self.determine_import(import, &type_.name)?; + assert!(self.type_refs.insert(type_.id, AuxTypeRef::Import(import.clone())).is_none()); let (import_id, _id) = match self.function_imports.get(type_.instanceof_shim) { Some(pair) => *pair, None => return Ok(()), @@ -1104,7 +1136,6 @@ impl<'a> Context<'a> { // And then save off that this function is is an instanceof shim for an // imported item. - let import = self.determine_import(import, &type_.name)?; self.aux .import_map .insert(import_id, AuxImport::Instanceof(import)); @@ -1191,30 +1222,18 @@ impl<'a> Context<'a> { ); } let aux = AuxStruct { + rust_id: struct_.id.0, name: struct_.name.to_string(), comments: concatenate_comments(&struct_.comments), + prototype: None, // Will be set, if appropriate, upon later call to resolve_prototype_refs() }; - self.aux.structs.push(aux); - - let wrap_constructor = wasm_bindgen_shared::new_function(struct_.name); - if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor) { - self.aux.import_map.insert( - *import_id, - AuxImport::WrapInExportedClass(struct_.name.to_string()), - ); - let binding = Function { - shim_idx: 0, - arguments: vec![Descriptor::I32], - ret: Descriptor::Anyref, - }; - bindings::register_import( - self.module, - &mut self.bindings, - *import_id, - binding, - ast::WebidlFunctionKind::Static, - )?; + let struct_id = self.aux.structs.len(); + assert!(self.type_refs.insert(struct_.id, AuxTypeRef::Export(struct_id)).is_none()); + + if !struct_.prototype.is_null() { + self.struct_prototype_refs.push((struct_id, struct_.prototype)); } + self.aux.structs.push(aux); Ok(()) } @@ -1460,6 +1479,12 @@ impl<'a> Context<'a> { Ok(()) } + fn resolve_prototype_refs(&mut self) { + while let Some((struct_id, prototype_ref)) = self.struct_prototype_refs.pop() { + self.aux.structs[struct_id].prototype = Some(self.type_refs[&prototype_ref].clone()); + } + } + /// Perform a small verification pass over the module to perform some /// internal sanity checks. fn verify(&self) -> Result<(), Error> { diff --git a/crates/cli-support/src/webidl/outgoing.rs b/crates/cli-support/src/webidl/outgoing.rs index 689463cc4cbe..01a4d1daadcf 100644 --- a/crates/cli-support/src/webidl/outgoing.rs +++ b/crates/cli-support/src/webidl/outgoing.rs @@ -29,10 +29,6 @@ pub enum NonstandardOutgoing { /// less JS shim code. Standard(ast::OutgoingBindingExpression), - /// We're returning a pointer from Rust to JS to get wrapped in a JS class - /// which has memory management around it. - RustType { class: String, idx: u32 }, - /// A single rust `char` value which is converted to a `string` in JS. Char { idx: u32 }, @@ -133,9 +129,6 @@ pub enum NonstandardOutgoing { signed: bool, }, - /// An optional owned Rust type being transferred from Rust to JS. - OptionRustType { class: String, idx: u32 }, - /// A temporary stack closure being passed from Rust to JS. A JS function is /// manufactured and then neutered just before the call returns. StackClosure { @@ -241,14 +234,7 @@ impl OutgoingBuilder<'_> { }); } - Descriptor::RustStruct(class) => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::RustType { - idx, - class: class.to_string(), - }); - } + Descriptor::RustStruct(_) => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), Descriptor::Ref(d) => self.process_ref(false, d)?, Descriptor::RefMut(d) => self.process_ref(true, d)?, @@ -273,7 +259,8 @@ impl OutgoingBuilder<'_> { Descriptor::Option(d) => self.process_option(d)?, - Descriptor::Function(_) | Descriptor::Closure(_) | Descriptor::Slice(_) => bail!( + Descriptor::Function(_) | Descriptor::Closure(_) | Descriptor::Slice(_) | + Descriptor::SuperconstructorCallback | Descriptor::ThisCallback => bail!( "unsupported argument type for calling JS function from Rust: {:?}", arg ), @@ -377,6 +364,14 @@ impl OutgoingBuilder<'_> { }); } + // Move borrow to JS from Rust + // Actually we just need the JsValue, which is what Rust sends anyway! + Descriptor::RustStruct(_) => { + let idx = self.push_wasm(ValType::Anyref); + self.webidl.push(ast::WebidlScalarType::Any); + self.bindings.push(NonstandardOutgoing::BorrowedAnyref { idx }); + } + _ => bail!( "unsupported reference argument type for calling JS function from Rust: {:?}", arg @@ -427,14 +422,7 @@ impl OutgoingBuilder<'_> { self.bindings .push(NonstandardOutgoing::OptionIntegerEnum { idx, hole: *hole }); } - Descriptor::RustStruct(name) => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionRustType { - idx, - class: name.to_string(), - }); - } + Descriptor::RustStruct(_) => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), Descriptor::Ref(d) => self.process_option_ref(false, d)?, Descriptor::RefMut(d) => self.process_option_ref(true, d)?, diff --git a/crates/cli-support/src/webidl/standard.rs b/crates/cli-support/src/webidl/standard.rs index ae48c9eae6b9..f1c00bd81053 100644 --- a/crates/cli-support/src/webidl/standard.rs +++ b/crates/cli-support/src/webidl/standard.rs @@ -221,6 +221,24 @@ pub fn add_section( name ); } + AuxExportKind::StaticGetter { class, field } => { + bail!( + "cannot export `{}::{}` static getter function when \ + generating a standalone WebAssembly module with no \ + JS glue", + class, + field, + ); + } + AuxExportKind::StaticSetter { class, field } => { + bail!( + "cannot export `{}::{}` static setter function when \ + generating a standalone WebAssembly module with no \ + JS glue", + class, + field, + ); + } AuxExportKind::Method { class, name, .. } => { bail!( "cannot export `{}::{}` method when \ @@ -435,6 +453,8 @@ fn extract_incoming( NonstandardIncoming::OptionRustType { .. } => "optional Rust type", NonstandardIncoming::Char { .. } => "character", NonstandardIncoming::BorrowedAnyref { .. } => "borrowed anyref", + NonstandardIncoming::SuperconstructorCallback => "superconstructor callback", + NonstandardIncoming::ThisCallback => "this callback", }; bail!( "cannot represent {} with a standard bindings expression", @@ -473,7 +493,6 @@ fn extract_outgoing( continue; } - NonstandardOutgoing::RustType { .. } => "rust type", NonstandardOutgoing::Char { .. } => "character", NonstandardOutgoing::Number64 { .. } => "64-bit integer", NonstandardOutgoing::BorrowedAnyref { .. } => "borrowed anyref", @@ -489,7 +508,6 @@ fn extract_outgoing( NonstandardOutgoing::OptionChar { .. } => "optional character", NonstandardOutgoing::OptionIntegerEnum { .. } => "optional enum", NonstandardOutgoing::OptionInt64 { .. } => "optional 64-bit integer", - NonstandardOutgoing::OptionRustType { .. } => "optional rust type", NonstandardOutgoing::StackClosure { .. } => "closures", }; bail!( @@ -592,9 +610,6 @@ fn check_standard_import(import: &AuxImport) -> Result<(), Error> { | AuxImport::IndexingGetterOfObject | AuxImport::IndexingSetterOfClass(_) | AuxImport::IndexingSetterOfObject => format!("indexing getters/setters/deleters"), - AuxImport::WrapInExportedClass(name) => { - format!("wrapping a pointer in a `{}` js class wrapper", name) - } AuxImport::Intrinsic(intrinsic) => { format!("wasm-bindgen specific intrinsic `{}`", intrinsic.name()) } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index d823016f6051..1975f8cb2498 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-cli" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/cli" @@ -25,8 +25,8 @@ serde = { version = "1.0", features = ['derive'] } serde_derive = "1.0" serde_json = "1.0" walrus = { version = "0.12.0", features = ['parallel'] } -wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.51" } -wasm-bindgen-shared = { path = "../shared", version = "=0.2.51" } +wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.52" } +wasm-bindgen-shared = { path = "../shared", version = "=0.2.52" } [dev-dependencies] assert_cmd = "0.11" diff --git a/crates/futures/Cargo.toml b/crates/futures/Cargo.toml index 0555bfe6ef81..464d81c44cd8 100644 --- a/crates/futures/Cargo.toml +++ b/crates/futures/Cargo.toml @@ -7,13 +7,13 @@ license = "MIT/Apache-2.0" name = "wasm-bindgen-futures" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures" readme = "./README.md" -version = "0.4.1" +version = "0.4.2" edition = "2018" [dependencies] cfg-if = "0.1.9" -js-sys = { path = "../js-sys", version = '0.3.28' } -wasm-bindgen = { path = "../..", version = '0.2.51' } +js-sys = { path = "../js-sys", version = '0.3.29' } +wasm-bindgen = { path = "../..", version = '0.2.52' } [target.'cfg(target_feature = "atomics")'.dependencies.web-sys] path = "../web-sys" @@ -24,5 +24,5 @@ features = [ ] [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test = { path = '../test', version = '0.3.1' } +wasm-bindgen-test = { path = '../test', version = '0.3.2' } futures-channel-preview = { version = "0.3.0-alpha.18" } diff --git a/crates/js-sys/Cargo.toml b/crates/js-sys/Cargo.toml index 40e34436b162..4afd98fbb974 100644 --- a/crates/js-sys/Cargo.toml +++ b/crates/js-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "js-sys" -version = "0.3.28" +version = "0.3.29" authors = ["The wasm-bindgen Developers"] readme = "./README.md" categories = ["wasm"] @@ -19,8 +19,8 @@ test = false doctest = false [dependencies] -wasm-bindgen = { path = "../..", version = "0.2.51" } +wasm-bindgen = { path = "../..", version = "0.2.52" } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test = { path = '../test', version = '=0.3.1' } -wasm-bindgen-futures = { path = '../futures', version = '0.4.1' } +wasm-bindgen-test = { path = '../test', version = '=0.3.2' } +wasm-bindgen-futures = { path = '../futures', version = '0.4.2' } diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index 578e4080304d..02af84decc22 100644 --- a/crates/js-sys/src/lib.rs +++ b/crates/js-sys/src/lib.rs @@ -1192,6 +1192,10 @@ impl Function { pub fn try_from(val: &JsValue) -> Option<&Function> { val.dyn_ref() } + + pub fn of() -> Function { + JsValue::from_type::().unchecked_into::() + } } // Generator diff --git a/crates/js-sys/tests/wasm/JSON.rs b/crates/js-sys/tests/wasm/JSON.rs index 9c9017b725d5..920ac1d6f623 100644 --- a/crates/js-sys/tests/wasm/JSON.rs +++ b/crates/js-sys/tests/wasm/JSON.rs @@ -5,7 +5,7 @@ use wasm_bindgen_test::*; #[wasm_bindgen_test] fn parse_array() { - let js_array = JSON::parse("[1, 2, 3]").unwrap();; + let js_array = JSON::parse("[1, 2, 3]").unwrap(); assert!(Array::is_array(&js_array)); let array = Array::from(&js_array); diff --git a/crates/macro-support/Cargo.toml b/crates/macro-support/Cargo.toml index 4b59331f94da..fbca95e298f9 100644 --- a/crates/macro-support/Cargo.toml +++ b/crates/macro-support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-macro-support" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support" @@ -20,5 +20,5 @@ strict-macro = [] syn = { version = '1.0', features = ['visit'] } quote = '1.0' proc-macro2 = "1.0" -wasm-bindgen-backend = { path = "../backend", version = "=0.2.51" } -wasm-bindgen-shared = { path = "../shared", version = "=0.2.51" } +wasm-bindgen-backend = { path = "../backend", version = "=0.2.52" } +wasm-bindgen-shared = { path = "../shared", version = "=0.2.52" } diff --git a/crates/macro-support/src/lib.rs b/crates/macro-support/src/lib.rs index 9996849596af..4d38e6f4423f 100644 --- a/crates/macro-support/src/lib.rs +++ b/crates/macro-support/src/lib.rs @@ -24,8 +24,28 @@ mod parser; /// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings pub fn expand(attr: TokenStream, input: TokenStream) -> Result { parser::reset_attrs_used(); + let opts = syn::parse2::(attr.clone())?; + + // If `__wasm_target` attribute is missing, inject it and re-run. + // This way, the parser can determine whether we are compiling for a wasm target + // in order to inject the necessary `__proto__` field whilst tracking such field + // member in the AST. + if !opts.has_wasm_target() { + let modified = quote::quote! { + #[cfg_attr( + all(target_arch = "wasm32", not(target_os = "emscripten")), + ::wasm_bindgen::prelude::wasm_bindgen(__wasm_target = true, #attr) + )] + #[cfg_attr( + not(all(target_arch = "wasm32", not(target_os = "emscripten"))), + ::wasm_bindgen::prelude::wasm_bindgen(__wasm_target = false, #attr) + )] + #input + }; + return Ok(modified); + } + let item = syn::parse2::(input)?; - let opts = syn::parse2(attr)?; let mut tokens = proc_macro2::TokenStream::new(); let mut program = backend::ast::Program::default(); @@ -50,7 +70,7 @@ pub fn expand_class_marker( let opts: ClassMarker = syn::parse2(attr)?; let mut program = backend::ast::Program::default(); - item.macro_parse(&mut program, (&opts.class, &opts.js_class))?; + item.macro_parse(&mut program, (&opts.class, &opts.js_class, opts.wasm_target))?; parser::assert_all_attrs_checked(); // same as above // This is where things are slightly different, we are being expanded in the @@ -93,6 +113,7 @@ pub fn expand_class_marker( struct ClassMarker { class: syn::Ident, js_class: String, + wasm_target: bool, } impl Parse for ClassMarker { @@ -100,6 +121,94 @@ impl Parse for ClassMarker { let class = input.parse::()?; input.parse::()?; let js_class = input.parse::()?.value(); - Ok(ClassMarker { class, js_class }) + input.parse::()?; + let wasm_target = input.parse::()?.value; + Ok(ClassMarker { class, js_class, wasm_target }) + } +} + +pub fn expand_instantiate(input: TokenStream) -> Result { + Ok(syn::parse2::(input)?.into_token_stream()) +} + +struct Instantiation { + ty: syn::Path, + expr: syn::Expr, +} + +impl Parse for Instantiation { + fn parse(input: ParseStream) -> SynResult { + let prototype: syn::Expr = if let Ok(_) = input.parse::() { + let args; + parenthesized!(args in input); + input.parse::()?; + + let mut super_args = syn::punctuated::Punctuated::::parse_terminated(&args)?; + super_args.iter_mut().for_each(|expr| *expr = parse_quote! { #expr.into() }); + + parse_quote! { __wbg_callback.invoke(Box::new([ #super_args ])) } + } else { + parse_quote! { __wbg_callback.get() } + }; + + let without_prototype = input.parse::()?; + let (ty, with_prototype): (syn::Path, syn::Expr) = match &without_prototype { + syn::Expr::Call(expr_call) => { + let ty = match &*expr_call.func { + syn::Expr::Path(expr_path) => expr_path.path.clone(), + err => return Err(syn::Error::new_spanned(err, "expression is not a struct literal")), + }; + let mut with_prototype = expr_call.clone(); + with_prototype.args.push(parse_quote!{ #prototype }); + (ty, with_prototype.into()) + } + + syn::Expr::Struct(expr_struct) => { + let mut with_prototype = expr_struct.clone(); + with_prototype.fields.push(parse_quote! { __proto__: #prototype }); + (expr_struct.path.clone(), with_prototype.into()) + } + + syn::Expr::Path(expr_path) => { + let with_prototype = parse_quote! { #without_prototype { __proto__: #prototype } }; + (expr_path.path.clone(), with_prototype) + } + + err => return Err(syn::Error::new_spanned(err, "expression not a struct literal")), + }; + + Ok(Instantiation { + ty, + expr: parse_quote!{{ + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + let expr = { #with_prototype }; + + #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] + let expr = { #without_prototype }; + + expr + }} + }) + } +} + +impl ToTokens for Instantiation { + fn to_tokens(&self, tokens: &mut TokenStream) { + let expr = &self.expr; + let ty = &self.ty; + + (quote::quote! {{ + use ::wasm_bindgen::{JsValue, WasmType, SuperconstructorCallback}; + use ::wasm_bindgen::__rt::{WasmRefCell, core::convert::AsRef}; + + let wasm = WasmType::new(WasmRefCell::new(#expr)); + + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + <#ty as AsRef>::as_ref(&*wasm.borrow()) + .set_wasm_pointer( Box::into_raw(Box::new(WasmType::clone(&wasm))) ); + + wasm + }}) + .to_tokens(tokens); } } diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 0271505b21b3..c632dfae8c94 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -52,6 +52,10 @@ macro_rules! attrgen { (typescript_custom_section, TypescriptCustomSection(Span)), (start, Start(Span)), (skip, Skip(Span)), + (prototype, Prototype(Span, syn::Type)), + + // Added internally + (__wasm_target, WasmTarget(Span, bool)), // For testing purposes only. (assert_no_shim, AssertNoShim(Span)), @@ -176,6 +180,10 @@ impl BindgenAttrs { } } + pub fn has_wasm_target(&self) -> bool { + self.__wasm_target().is_some() + } + attrgen!(methods); } @@ -258,11 +266,21 @@ impl Parse for BindgenAttr { return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); }); + (@parser $variant:ident(Span, syn::Type)) => ({ + input.parse::()?; + return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); + }); + (@parser $variant:ident(Span, syn::Expr)) => ({ input.parse::()?; return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); }); + (@parser $variant:ident(Span, bool)) => ({ + input.parse::()?; + return Ok(BindgenAttr::$variant(attr_span, input.parse::()?.value)); + }); + (@parser $variant:ident(Span, String, Span)) => ({ input.parse::()?; let (val, span) = match input.parse::() { @@ -354,6 +372,34 @@ impl<'a> ConvertToAst for &'a mut syn::ItemStruct { }); attrs.check_used()?; } + let prototype = attrs.prototype().cloned().unwrap_or(parse_quote!{ ::wasm_bindgen::JsValue }); + let prototype_field = if *attrs.__wasm_target().unwrap() { + use syn::parse::Parser; + use quote::quote; + + let span = Span::call_site(); + let ident = Ident::new("__proto__", span.clone()); + + Some(match &mut self.fields { + syn::Fields::Unnamed(fields) => { + let index = fields.unnamed.len() as u32; + fields.unnamed.push(syn::Field::parse_unnamed.parse2(quote!{ #prototype }).unwrap()); + syn::Member::Unnamed(syn::Index { index, span }) + } + + syn::Fields::Named(fields) => { + fields.named.push(syn::Field::parse_named.parse2(quote!{ #ident: #prototype }).unwrap()); + syn::Member::Named(ident) + } + + syn::Fields::Unit => { + self.fields = syn::Fields::Named(parse_quote!{{ #ident: #prototype }}); + syn::Member::Named(ident) + } + }) + } else { + None + }; let comments: Vec = extract_doc_comments(&self.attrs); attrs.check_used()?; Ok(ast::Struct { @@ -361,6 +407,8 @@ impl<'a> ConvertToAst for &'a mut syn::ItemStruct { js_name, fields, comments, + prototype, + prototype_field, }) } } @@ -682,7 +730,7 @@ fn function_from_decl( } assert!(method_self.is_none()); if r.reference.is_none() { - method_self = Some(ast::MethodSelf::ByValue); + panic!("exported method cannot consume self") } else if r.mutability.is_some() { method_self = Some(ast::MethodSelf::RefMutable); } else { @@ -770,6 +818,7 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { js_class: None, method_kind, method_self: None, + method_body: None, rust_class: None, rust_name, start, @@ -909,6 +958,7 @@ fn prepare_for_impl_recursion( .map(|s| s.0.to_string()) .unwrap_or(class.to_string()); + let wasm_target = impl_opts.__wasm_target().unwrap(); method.attrs.insert( 0, syn::Attribute { @@ -916,18 +966,18 @@ fn prepare_for_impl_recursion( style: syn::AttrStyle::Outer, bracket_token: Default::default(), path: syn::parse_quote! { wasm_bindgen::prelude::__wasm_bindgen_class_marker }, - tokens: quote::quote! { (#class = #js_class) }.into(), + tokens: quote::quote! { (#class = #js_class, #wasm_target) }.into(), }, ); Ok(()) } -impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod { +impl<'a, 'b> MacroParse<(&'a Ident, &'a str, bool)> for &'b mut syn::ImplItemMethod { fn macro_parse( self, program: &mut ast::Program, - (class, js_class): (&'a Ident, &'a str), + (class, js_class, wasm_target): (&'a Ident, &'a str, bool), ) -> Result<(), Diagnostic> { match self.vis { syn::Visibility::Public(_) => {} @@ -948,10 +998,43 @@ impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod { let opts = BindgenAttrs::find(&mut self.attrs)?; let comments = extract_doc_comments(&self.attrs); + + let mut extern_sig = self.sig.clone(); + let method_body = if wasm_target && opts.constructor().is_some() { + // Arguments with which to invoke the constructor of the JavaScript shim class + let js_args = self.sig.inputs.iter().filter_map(|input| match input { + syn::FnArg::Receiver(_) => panic!("A constructor cannot be an instance method!"), + syn::FnArg::Typed(syn::PatType { pat , .. }) => match pat.as_ref() { + syn::Pat::Ident(i) => Some(i.ident.clone()), + syn::Pat::Wild(_) => Some(syn::parse_quote!{ ::wasm_bindgen::JsValue::NULL }), + _ => None, + } + }).collect::>(); + + // The constructor of the JavaScript shim class will forward to our generated `extern` fn + // adding a callback argument through which either the super-constructor can be invoked + // (derived types), or the `this` object can be obtained (base types). + // Here we inject a parameter for receiving that callback. + extern_sig.inputs.push(syn::FnArg::Typed(syn::PatType { + attrs: Default::default(), + colon_token: Default::default(), + pat: parse_quote! { __wbg_callback }, + ty: parse_quote! { <<#class as ::wasm_bindgen::WasmBindgenDerived>::Prototype as ::wasm_bindgen::WasmBase>::Callback }, + })); + + // Rust function should invoke JS constructor; the user-provided function body appears + // instead in the generated `extern` fn. + Some(std::mem::replace(&mut self.block, parse_quote! {{ + ::wasm_bindgen::instantiate_via_js(Box::new([ #(#js_args.into(),)* ])) + }})) + } else { + None + }; + let (function, method_self) = function_from_decl( &self.sig.ident, &opts, - self.sig.clone(), + extern_sig, self.attrs.clone(), self.vis.clone(), true, @@ -970,6 +1053,7 @@ impl<'a, 'b> MacroParse<(&'a Ident, &'a str)> for &'b mut syn::ImplItemMethod { js_class: Some(js_class.to_string()), method_kind, method_self, + method_body, rust_class: Some(class.clone()), rust_name: self.sig.ident.clone(), start: false, diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml index 8d90ede33c43..6c185d859ed3 100644 --- a/crates/macro/Cargo.toml +++ b/crates/macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-macro" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro" @@ -20,10 +20,11 @@ xxx_debug_only_print_generated_code = [] strict-macro = ["wasm-bindgen-macro-support/strict-macro"] [dependencies] -wasm-bindgen-macro-support = { path = "../macro-support", version = "=0.2.51" } +wasm-bindgen-macro-support = { path = "../macro-support", version = "=0.2.52" } quote = "1.0" +proc-macro-hack = "0.5" [dev-dependencies] trybuild = "1.0" -wasm-bindgen = { path = "../..", version = "0.2.51", features = ['strict-macro'] } -wasm-bindgen-futures = { path = "../futures", version = "0.4.1" } +wasm-bindgen = { path = "../..", version = "0.2.52", features = ['strict-macro'] } +wasm-bindgen-futures = { path = "../futures", version = "0.4.2" } diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs index c677aaf24bff..63b6496571ce 100644 --- a/crates/macro/src/lib.rs +++ b/crates/macro/src/lib.rs @@ -3,6 +3,7 @@ extern crate proc_macro; use proc_macro::TokenStream; +use proc_macro_hack::proc_macro_hack; use quote::quote; #[proc_macro_attribute] @@ -12,7 +13,12 @@ pub fn wasm_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream { if cfg!(feature = "xxx_debug_only_print_generated_code") { println!("{}", tokens); } - tokens.into() + // merely returning `tokens.into()` loses span info in some situations + // whereas extending a new TokenStream with it does not !? + // possibly related to https://github.com/rust-lang/rust/issues/43081 ? + let mut result = TokenStream::new(); + result.extend::(tokens.into()); + result } Err(diagnostic) => (quote! { #diagnostic }).into(), } @@ -30,3 +36,18 @@ pub fn __wasm_bindgen_class_marker(attr: TokenStream, input: TokenStream) -> Tok Err(diagnostic) => (quote! { #diagnostic }).into(), } } + +// proc_macro_hack used here because this macro is unhygienic in order to +// transparently access the callback argument injected into constructors +#[proc_macro_hack] +pub fn instantiate(input: TokenStream) -> TokenStream { + match wasm_bindgen_macro_support::expand_instantiate(input.into()) { + Ok(tokens) => { + if cfg!(feature = "xxx_debug_only_print_generated_code") { + println!("{}", tokens); + } + tokens.into() + } + Err(diagnostic) => (quote! { #diagnostic }).into(), + } +} \ No newline at end of file diff --git a/crates/macro/ui-tests/async-errors.rs b/crates/macro/ui-tests/async-errors.rs index 22c5107e44ca..80d991bcf41a 100644 --- a/crates/macro/ui-tests/async-errors.rs +++ b/crates/macro/ui-tests/async-errors.rs @@ -11,7 +11,7 @@ pub async fn good2() -> JsValue { loop {} } #[wasm_bindgen] pub async fn good3() -> u32 { loop {} } #[wasm_bindgen] -pub async fn good4() -> MyType { loop {} } +pub async fn good4() -> WasmType { loop {} } #[wasm_bindgen] pub async fn good5() -> Result<(), JsValue> { loop {} } #[wasm_bindgen] @@ -19,11 +19,11 @@ pub async fn good6() -> Result { loop {} } #[wasm_bindgen] pub async fn good7() -> Result { loop {} } #[wasm_bindgen] -pub async fn good8() -> Result { loop {} } +pub async fn good8() -> Result, JsValue> { loop {} } #[wasm_bindgen] -pub async fn good9() -> Result { loop {} } +pub async fn good9() -> Result, u32> { loop {} } #[wasm_bindgen] -pub async fn good10() -> Result { loop {} } +pub async fn good10() -> Result, WasmType> { loop {} } pub struct BadType; diff --git a/crates/macro/ui-tests/async-errors.stderr b/crates/macro/ui-tests/async-errors.stderr index 19f97ffac3d5..0b58c6336cb3 100644 --- a/crates/macro/ui-tests/async-errors.stderr +++ b/crates/macro/ui-tests/async-errors.stderr @@ -30,8 +30,8 @@ error[E0277]: the trait bound `wasm_bindgen::JsValue: std::convert::From> > > - > - and 61 others + >> + and 63 others = note: required because of the requirements on the impl of `std::convert::Into` for `BadType` = note: required because of the requirements on the impl of `wasm_bindgen::__rt::IntoJsResult` for `BadType` = note: required by `wasm_bindgen::__rt::IntoJsResult::into_js_result` diff --git a/crates/macro/ui-tests/pub-not-copy.rs b/crates/macro/ui-tests/pub-not-copy.rs deleted file mode 100644 index b5f6b0cfb4b2..000000000000 --- a/crates/macro/ui-tests/pub-not-copy.rs +++ /dev/null @@ -1,8 +0,0 @@ -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -pub struct A { - pub field: String, -} - -fn main() {} diff --git a/crates/macro/ui-tests/pub-not-copy.stderr b/crates/macro/ui-tests/pub-not-copy.stderr deleted file mode 100644 index f6a268f623ed..000000000000 --- a/crates/macro/ui-tests/pub-not-copy.stderr +++ /dev/null @@ -1,12 +0,0 @@ -error[E0277]: the trait bound `std::string::String: std::marker::Copy` is not satisfied - --> $DIR/pub-not-copy.rs:5:16 - | -3 | #[wasm_bindgen] - | --------------- - | | - | required by this bound in `__wbg_get_a_field::assert_copy` -4 | pub struct A { -5 | pub field: String, - | ^^^^^^ the trait `std::marker::Copy` is not implemented for `std::string::String` - -For more information about this error, try `rustc --explain E0277`. diff --git a/crates/multi-value-xform/Cargo.toml b/crates/multi-value-xform/Cargo.toml index 5f7141b6ae0c..939ed8154b53 100644 --- a/crates/multi-value-xform/Cargo.toml +++ b/crates/multi-value-xform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-multi-value-xform" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/multi-value-xform" diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 510c19312fd2..014f8b19180b 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-shared" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/shared" diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index b8fa9f2413da..4a2b7c6782a6 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -80,6 +80,7 @@ macro_rules! shared_api { } struct ImportType<'a> { + id: TypeReference, name: &'a str, instanceof_shim: &'a str, vendor_prefixes: Vec<&'a str>, @@ -90,7 +91,7 @@ macro_rules! shared_api { struct Export<'a> { class: Option<&'a str>, comments: Vec<&'a str>, - consumed: bool, + mutable: bool, function: Function<'a>, method_kind: MethodKind<'a>, start: bool, @@ -113,9 +114,11 @@ macro_rules! shared_api { } struct Struct<'a> { + id: TypeReference, name: &'a str, fields: Vec>, comments: Vec<&'a str>, + prototype: TypeReference, } struct StructField<'a> { @@ -132,10 +135,10 @@ macro_rules! shared_api { }; // end of mac case } // end of mac definition -pub fn new_function(struct_name: &str) -> String { +pub fn borrow_from_prototype_function(struct_name: &str) -> String { let mut name = format!("__wbg_"); name.extend(struct_name.chars().flat_map(|s| s.to_lowercase())); - name.push_str("_new"); + name.push_str("_borrow"); return name; } diff --git a/crates/test-macro/Cargo.toml b/crates/test-macro/Cargo.toml index ad88bfe46b84..8330639db15d 100644 --- a/crates/test-macro/Cargo.toml +++ b/crates/test-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-test-macro" -version = "0.3.1" +version = "0.3.2" authors = ["The wasm-bindgen Developers"] description = "Internal testing macro for wasm-bindgen" license = "MIT/Apache-2.0" diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index ecf3b7a2e8cd..2f0d41635849 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-test" -version = "0.3.1" +version = "0.3.2" authors = ["The wasm-bindgen Developers"] description = "Internal testing crate for wasm-bindgen" license = "MIT/Apache-2.0" @@ -9,11 +9,11 @@ edition = "2018" [dependencies] console_error_panic_hook = '0.1' -js-sys = { path = '../js-sys', version = '0.3.28' } +js-sys = { path = '../js-sys', version = '0.3.29' } scoped-tls = "1.0" -wasm-bindgen = { path = '../..', version = '0.2.51' } -wasm-bindgen-futures = { path = '../futures', version = '0.4.1' } -wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.1' } +wasm-bindgen = { path = '../..', version = '0.2.52' } +wasm-bindgen-futures = { path = '../futures', version = '0.4.2' } +wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.2' } [lib] test = false diff --git a/crates/test/src/rt/mod.rs b/crates/test/src/rt/mod.rs index bf08fe19e424..bc8046f5d9ae 100644 --- a/crates/test/src/rt/mod.rs +++ b/crates/test/src/rt/mod.rs @@ -207,23 +207,25 @@ impl Context { /// coordinated, and this will collect output and results for all executed /// tests. #[wasm_bindgen(constructor)] - pub fn new() -> Context { + pub fn new() -> WasmType { console_error_panic_hook::set_once(); let formatter = match node::Node::new() { Some(node) => Box::new(node) as Box, None => Box::new(browser::Browser::new()), }; - Context { - state: Rc::new(State { - filter: Default::default(), - failures: Default::default(), - ignored: Default::default(), - remaining: Default::default(), - running: Default::default(), - succeeded: Default::default(), - formatter, - }), + instantiate! { + Context { + state: Rc::new(State { + filter: Default::default(), + failures: Default::default(), + ignored: Default::default(), + remaining: Default::default(), + running: Default::default(), + succeeded: Default::default(), + formatter, + }), + } } } diff --git a/crates/threads-xform/Cargo.toml b/crates/threads-xform/Cargo.toml index 53436771d83f..0b1e8a32a6fb 100644 --- a/crates/threads-xform/Cargo.toml +++ b/crates/threads-xform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-threads-xform" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/threads-xform" @@ -14,4 +14,4 @@ edition = "2018" [dependencies] failure = "0.1" walrus = "0.12.0" -wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.51" } +wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.52" } diff --git a/crates/typescript-tests/src/simple_struct.rs b/crates/typescript-tests/src/simple_struct.rs index 7474719a3226..82f6b79dd39b 100644 --- a/crates/typescript-tests/src/simple_struct.rs +++ b/crates/typescript-tests/src/simple_struct.rs @@ -6,8 +6,8 @@ pub struct A {} #[wasm_bindgen] impl A { #[wasm_bindgen(constructor)] - pub fn new() -> A { - A {} + pub fn new() -> WasmType { + instantiate! { A {} } } pub fn other() {} diff --git a/crates/typescript-tests/src/simple_struct.ts b/crates/typescript-tests/src/simple_struct.ts index 9583039950fc..dcf6f64c81f4 100644 --- a/crates/typescript-tests/src/simple_struct.ts +++ b/crates/typescript-tests/src/simple_struct.ts @@ -3,4 +3,4 @@ import * as wbg from '../pkg/typescript_tests'; const a = new wbg.A(); wbg.A.other(); a.foo(); -a.free(); +a[wbg.__wbg_free](); diff --git a/crates/wasm-conventions/Cargo.toml b/crates/wasm-conventions/Cargo.toml index dd88dce655c9..514b49310ade 100644 --- a/crates/wasm-conventions/Cargo.toml +++ b/crates/wasm-conventions/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-wasm-conventions" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/wasm-conventions" diff --git a/crates/wasm-interpreter/Cargo.toml b/crates/wasm-interpreter/Cargo.toml index af6c6c852997..f5dc756b43bd 100644 --- a/crates/wasm-interpreter/Cargo.toml +++ b/crates/wasm-interpreter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-wasm-interpreter" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/wasm-interpreter" diff --git a/crates/web-sys/Cargo.toml b/crates/web-sys/Cargo.toml index 3926f007a0ca..30afaf47b76f 100644 --- a/crates/web-sys/Cargo.toml +++ b/crates/web-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "web-sys" -version = "0.3.28" +version = "0.3.29" authors = ["The wasm-bindgen Developers"] readme = "./README.md" homepage = "https://rustwasm.github.io/wasm-bindgen/web-sys/index.html" @@ -22,16 +22,16 @@ test = false [build-dependencies] env_logger = { version = "0.7.0", optional = true } failure = "0.1.2" -wasm-bindgen-webidl = { path = "../webidl", version = "=0.2.51" } +wasm-bindgen-webidl = { path = "../webidl", version = "=0.2.52" } sourcefile = "0.1" [dependencies] -wasm-bindgen = { path = "../..", version = "0.2.51" } -js-sys = { path = '../js-sys', version = '0.3.28' } +wasm-bindgen = { path = "../..", version = "0.2.52" } +js-sys = { path = '../js-sys', version = '0.3.29' } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test = { path = '../test', version = '0.3.1' } -wasm-bindgen-futures = { path = '../futures', version = '0.4.1' } +wasm-bindgen-test = { path = '../test', version = '0.3.2' } +wasm-bindgen-futures = { path = '../futures', version = '0.4.2' } # This list is generated by passing `__WASM_BINDGEN_DUMP_FEATURES=foo` when # compiling this crate which dumps the total list of features to a file called diff --git a/crates/webidl/Cargo.toml b/crates/webidl/Cargo.toml index f4f5038aed28..f3efe7e7bf0b 100644 --- a/crates/webidl/Cargo.toml +++ b/crates/webidl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-webidl" -version = "0.2.51" +version = "0.2.52" authors = ["The wasm-bindgen Developers"] license = "MIT/Apache-2.0" categories = ["wasm"] @@ -19,5 +19,5 @@ log = "0.4.1" proc-macro2 = "1.0" quote = '1.0' syn = { version = '1.0', features = ['full'] } -wasm-bindgen-backend = { version = "=0.2.51", path = "../backend" } +wasm-bindgen-backend = { version = "=0.2.52", path = "../backend" } weedle = "0.10" diff --git a/examples/add/Cargo.toml b/examples/add/Cargo.toml index 1e3b83241dea..d9f9ccd3d05a 100644 --- a/examples/add/Cargo.toml +++ b/examples/add/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" diff --git a/examples/canvas/Cargo.toml b/examples/canvas/Cargo.toml index 6ddcdbfcb596..f32b4fdc146b 100644 --- a/examples/canvas/Cargo.toml +++ b/examples/canvas/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -js-sys = "0.3.28" -wasm-bindgen = "0.2.51" +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/char/Cargo.toml b/examples/char/Cargo.toml index a1026503f6ae..50e64adaa8e3 100644 --- a/examples/char/Cargo.toml +++ b/examples/char/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" diff --git a/examples/char/src/lib.rs b/examples/char/src/lib.rs index 41b5edc070f5..0aed4c326f35 100644 --- a/examples/char/src/lib.rs +++ b/examples/char/src/lib.rs @@ -16,15 +16,18 @@ pub struct Counter { #[wasm_bindgen] impl Counter { - pub fn default() -> Counter { + pub fn default() -> WasmType { log("Counter::default"); - Self::new('a', 0) + Self::new("a", 0) } - pub fn new(key: char, count: i32) -> Counter { + #[wasm_bindgen(constructor)] + pub fn new(key: &str, count: i32) -> WasmType { log(&format!("Counter::new({}, {})", key, count)); - Counter { - key: key, - count: count, + instantiate! { + Counter { + key: key.chars().next().unwrap(), + count: count, + } } } diff --git a/examples/closures/Cargo.toml b/examples/closures/Cargo.toml index 9d6835e1896f..c9897eb938dc 100644 --- a/examples/closures/Cargo.toml +++ b/examples/closures/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" -js-sys = "0.3.28" +wasm-bindgen = "0.2.52" +js-sys = "0.3.29" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/console_log/Cargo.toml b/examples/console_log/Cargo.toml index ed3bd056373b..63eead576dda 100644 --- a/examples/console_log/Cargo.toml +++ b/examples/console_log/Cargo.toml @@ -8,5 +8,5 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" -web-sys = { version = "0.3.28", features = ['console'] } +wasm-bindgen = "0.2.52" +web-sys = { version = "0.3.29", features = ['console'] } diff --git a/examples/dom/Cargo.toml b/examples/dom/Cargo.toml index 5f85176a290a..2927853bc619 100644 --- a/examples/dom/Cargo.toml +++ b/examples/dom/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/duck-typed-interfaces/Cargo.toml b/examples/duck-typed-interfaces/Cargo.toml index b7a2b57088d5..e3ca313a82b2 100644 --- a/examples/duck-typed-interfaces/Cargo.toml +++ b/examples/duck-typed-interfaces/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" diff --git a/examples/fetch/Cargo.toml b/examples/fetch/Cargo.toml index f8ef6781fd51..4ca5ac9a9336 100644 --- a/examples/fetch/Cargo.toml +++ b/examples/fetch/Cargo.toml @@ -8,9 +8,9 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = { version = "0.2.51", features = ["serde-serialize"] } -js-sys = "0.3.28" -wasm-bindgen-futures = "0.4.1" +wasm-bindgen = { version = "0.2.52", features = ["serde-serialize"] } +js-sys = "0.3.29" +wasm-bindgen-futures = "0.4.2" serde = { version = "1.0.80", features = ["derive"] } serde_derive = "^1.0.59" diff --git a/examples/guide-supported-types-examples/Cargo.toml b/examples/guide-supported-types-examples/Cargo.toml index 05a448c4d11f..e822dce54ab5 100644 --- a/examples/guide-supported-types-examples/Cargo.toml +++ b/examples/guide-supported-types-examples/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" diff --git a/examples/guide-supported-types-examples/src/exported_types.rs b/examples/guide-supported-types-examples/src/exported_types.rs index fb1ef1fcba28..0ceff8c183bd 100644 --- a/examples/guide-supported-types-examples/src/exported_types.rs +++ b/examples/guide-supported-types-examples/src/exported_types.rs @@ -6,7 +6,15 @@ pub struct ExportedNamedStruct { } #[wasm_bindgen] -pub fn named_struct_by_value(x: ExportedNamedStruct) {} +impl ExportedNamedStruct { + #[wasm_bindgen(constructor)] + pub fn new(inner: u32) -> WasmType { + instantiate! { ExportedNamedStruct { inner } } + } +} + +#[wasm_bindgen] +pub fn named_struct_by_value(x: WasmType) {} #[wasm_bindgen] pub fn named_struct_by_shared_ref(x: &ExportedNamedStruct) {} @@ -15,14 +23,22 @@ pub fn named_struct_by_shared_ref(x: &ExportedNamedStruct) {} pub fn named_struct_by_exclusive_ref(x: &mut ExportedNamedStruct) {} #[wasm_bindgen] -pub fn return_named_struct(inner: u32) -> ExportedNamedStruct { - ExportedNamedStruct { inner } +pub fn return_named_struct(inner: u32) -> WasmType { + ExportedNamedStruct::new(inner) } #[wasm_bindgen] pub struct ExportedTupleStruct(pub u32, pub u32); #[wasm_bindgen] -pub fn return_tuple_struct(x: u32, y: u32) -> ExportedTupleStruct { - ExportedTupleStruct(x, y) +impl ExportedTupleStruct { + #[wasm_bindgen(constructor)] + pub fn new(x: u32, y: u32) -> WasmType { + instantiate! { ExportedTupleStruct(x, y) } + } +} + +#[wasm_bindgen] +pub fn return_tuple_struct(x: u32, y: u32) -> WasmType { + ExportedTupleStruct::new(x, y) } diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index edc77b56f3aa..9d192148726c 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" diff --git a/examples/import_js/crate/Cargo.toml b/examples/import_js/crate/Cargo.toml index 5b2d42f51429..0f2a86fc3bc0 100644 --- a/examples/import_js/crate/Cargo.toml +++ b/examples/import_js/crate/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" diff --git a/examples/julia_set/Cargo.toml b/examples/julia_set/Cargo.toml index 6dbedf3c9b81..db789620ae6f 100644 --- a/examples/julia_set/Cargo.toml +++ b/examples/julia_set/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/paint/Cargo.toml b/examples/paint/Cargo.toml index 14f383b2c97a..0422bb060778 100644 --- a/examples/paint/Cargo.toml +++ b/examples/paint/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -js-sys = "0.3.28" -wasm-bindgen = "0.2.51" +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/performance/Cargo.toml b/examples/performance/Cargo.toml index a58b63f88a53..7e300e92dabf 100644 --- a/examples/performance/Cargo.toml +++ b/examples/performance/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" humantime = "1" [dependencies.web-sys] diff --git a/examples/raytrace-parallel/Cargo.toml b/examples/raytrace-parallel/Cargo.toml index bc1138a6147d..1c3334756a45 100644 --- a/examples/raytrace-parallel/Cargo.toml +++ b/examples/raytrace-parallel/Cargo.toml @@ -9,13 +9,13 @@ crate-type = ["cdylib"] [dependencies] console_error_panic_hook = "0.1" -js-sys = "0.3.28" +js-sys = "0.3.29" rayon = "1.1.0" rayon-core = "1.5.0" raytracer = { git = 'https://github.com/alexcrichton/raytracer', branch = 'update-deps' } futures-channel-preview = "0.3.0-alpha.18" -wasm-bindgen = { version = "0.2.51", features = ['serde-serialize'] } -wasm-bindgen-futures = "0.4.1" +wasm-bindgen = { version = "0.2.52", features = ['serde-serialize'] } +wasm-bindgen-futures = "0.4.2" [dependencies.web-sys] version = "0.3.23" diff --git a/examples/raytrace-parallel/src/lib.rs b/examples/raytrace-parallel/src/lib.rs index 696c565e5b93..01f0b699232f 100644 --- a/examples/raytrace-parallel/src/lib.rs +++ b/examples/raytrace-parallel/src/lib.rs @@ -28,13 +28,13 @@ impl Scene { /// Creates a new scene from the JSON description in `object`, which we /// deserialize here into an actual scene. #[wasm_bindgen(constructor)] - pub fn new(object: &JsValue) -> Result { + pub fn new(object: &JsValue) -> WasmType { console_error_panic_hook::set_once(); - Ok(Scene { + instantiate! { Scene { inner: object .into_serde() - .map_err(|e| JsValue::from(e.to_string()))?, - }) + .map_err(|e| JsValue::from(e.to_string())).unwrap(), + }} } /// Renders this scene with the provided concurrency and worker pool. @@ -43,11 +43,12 @@ impl Scene { /// spawned into `pool`. The `RenderingScene` state contains information to /// get notifications when the render has completed. pub fn render( - self, + me: WasmType, concurrency: usize, pool: &pool::WorkerPool, - ) -> Result { - let scene = self.inner; + ) -> Result, JsValue> { + let me = std::rc::Rc::try_unwrap(me)?.into_inner(); + let scene = me.inner; let height = scene.height; let width = scene.width; @@ -97,13 +98,13 @@ impl Scene { } }; - Ok(RenderingScene { - promise: wasm_bindgen_futures::future_to_promise(done), - base, - len, + Ok(RenderingScene::new( + wasm_bindgen_futures::future_to_promise(done), + base as u32, + len as u32, height, width, - }) + )) } } @@ -128,6 +129,11 @@ extern "C" { #[wasm_bindgen] impl RenderingScene { + #[wasm_bindgen(constructor)] + pub fn new(promise: Promise, base: u32, len: u32, height: u32, width: u32) -> WasmType { + instantiate! { RenderingScene { base: base as usize, len: len as usize, promise, width, height } } + } + /// Returns the JS promise object which resolves when the render is complete pub fn promise(&self) -> Promise { self.promise.clone() diff --git a/examples/raytrace-parallel/src/pool.rs b/examples/raytrace-parallel/src/pool.rs index e0b30404b4ba..1e71d09e97cb 100644 --- a/examples/raytrace-parallel/src/pool.rs +++ b/examples/raytrace-parallel/src/pool.rs @@ -39,22 +39,21 @@ impl WorkerPool { /// Returns any error that may happen while a JS web worker is created and a /// message is sent to it. #[wasm_bindgen(constructor)] - pub fn new(initial: usize) -> Result { - let pool = WorkerPool { + pub fn new(initial: u32) -> WasmType { + let owned = instantiate! { WorkerPool { state: Rc::new(PoolState { - workers: RefCell::new(Vec::with_capacity(initial)), + workers: RefCell::new(Vec::with_capacity(initial as usize)), callback: Closure::wrap(Box::new(|event: Event| { console_log!("unhandled event: {}", event.type_()); crate::logv(&event); }) as Box), }), - }; + }}; + let pool = owned.borrow_mut(); for _ in 0..initial { - let worker = pool.spawn()?; + let worker = pool.spawn().unwrap(); pool.state.push(worker); } - - Ok(pool) } /// Unconditionally spawns a new worker diff --git a/examples/request-animation-frame/Cargo.toml b/examples/request-animation-frame/Cargo.toml index e39b7b9e24a1..2ddfb54f470f 100644 --- a/examples/request-animation-frame/Cargo.toml +++ b/examples/request-animation-frame/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/todomvc/Cargo.toml b/examples/todomvc/Cargo.toml index 981f12799636..1eb3595b5d59 100644 --- a/examples/todomvc/Cargo.toml +++ b/examples/todomvc/Cargo.toml @@ -11,8 +11,8 @@ crate-type = ["cdylib"] askama = "0.7.2" [dependencies] -js-sys = "0.3.28" -wasm-bindgen = "0.2.51" +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" askama = "0.7.2" console_error_panic_hook = "0.1.5" diff --git a/examples/todomvc/src/lib.rs b/examples/todomvc/src/lib.rs index e0463ad75814..29db80fa2f2b 100644 --- a/examples/todomvc/src/lib.rs +++ b/examples/todomvc/src/lib.rs @@ -45,9 +45,9 @@ fn app(name: &str) { None => return, }; let controller = Controller::new(store, Rc::downgrade(&sched)); - if let Some(mut view) = View::new(Rc::clone(&sched)) { + if let Some(view) = View::new(Rc::clone(&sched)) { let sch: &Rc = &sched; - view.init(); + view.borrow_mut().init(); sch.set_view(view); sch.set_controller(controller); sched.add_message(Message::Controller(ControllerMessage::SetPage( diff --git a/examples/todomvc/src/scheduler.rs b/examples/todomvc/src/scheduler.rs index 59906ab2010e..ecec65dbcd9e 100644 --- a/examples/todomvc/src/scheduler.rs +++ b/examples/todomvc/src/scheduler.rs @@ -5,10 +5,12 @@ use crate::Message; use std::cell::RefCell; use std::rc::Rc; +use wasm_bindgen::WasmType; + /// Creates an event loop that starts each time a message is added pub struct Scheduler { controller: Rc>>, - view: Rc>>, + view: Rc>>>, events: RefCell>, running: RefCell, } @@ -32,7 +34,7 @@ impl Scheduler { } } - pub fn set_view(&self, view: View) { + pub fn set_view(&self, view: WasmType) { if let Ok(mut view_data) = self.view.try_borrow_mut() { *view_data = Some(view); } else { @@ -115,7 +117,7 @@ impl Scheduler { Message::View(e) => { if let Ok(mut view) = self.view.try_borrow_mut() { if let Some(ref mut ag) = *view { - ag.call(e); + ag.borrow_mut().call(e); } } else { exit("This might be a deadlock"); diff --git a/examples/todomvc/src/view.rs b/examples/todomvc/src/view.rs index 38cbc3ce7545..a1fd575df0e0 100644 --- a/examples/todomvc/src/view.rs +++ b/examples/todomvc/src/view.rs @@ -13,7 +13,6 @@ const ENTER_KEY: u32 = 13; const ESCAPE_KEY: u32 = 27; use wasm_bindgen::prelude::*; - /// Messages that represent the methods to be called on the View pub enum ViewMessage { UpdateFilterButtons(String), @@ -55,25 +54,61 @@ pub struct View { callbacks: Vec<(web_sys::EventTarget, String, Closure)>, } +#[wasm_bindgen] +impl View { + #[wasm_bindgen(constructor)] + pub fn construct( + sched: u32, + todo_list: u32, + todo_item_counter: u32, + clear_completed: u32, + main: u32, + toggle_all: u32, + new_todo: u32, + ) -> WasmType { + let sched = sched as *const Scheduler; + let todo_list = todo_list as *mut Element; + let todo_item_counter = todo_item_counter as *mut Element; + let clear_completed = clear_completed as *mut Element; + let main = main as *mut Element; + let toggle_all = toggle_all as *mut Element; + let new_todo = new_todo as *mut Element; + + assert!(!sched.is_null()); + assert!(!todo_list.is_null()); + assert!(!todo_item_counter.is_null()); + assert!(!clear_completed.is_null()); + assert!(!main.is_null()); + assert!(!toggle_all.is_null()); + assert!(!new_todo.is_null()); + + instantiate! { + View { + sched: RefCell::new(unsafe { Rc::from_raw(sched) }), + todo_list: unsafe { *Box::from_raw(todo_list) }, + todo_item_counter: unsafe { *Box::from_raw(todo_item_counter) }, + clear_completed: unsafe { *Box::from_raw(clear_completed) }, + main: unsafe { *Box::from_raw(main) }, + toggle_all: unsafe { *Box::from_raw(toggle_all) }, + new_todo: unsafe { *Box::from_raw(new_todo) }, + callbacks: Vec::new(), + } + } + } +} + impl View { /// Construct a new view - pub fn new(sched: Rc) -> Option { - let todo_list = Element::qs(".todo-list")?; - let todo_item_counter = Element::qs(".todo-count")?; - let clear_completed = Element::qs(".clear-completed")?; - let main = Element::qs(".main")?; - let toggle_all = Element::qs(".toggle-all")?; - let new_todo = Element::qs(".new-todo")?; - Some(View { - sched: RefCell::new(sched), - todo_list, - todo_item_counter, - clear_completed, - main, - toggle_all, - new_todo, - callbacks: Vec::new(), - }) + pub fn new(sched: Rc) -> Option> { + Some(View::construct( + Rc::into_raw(sched) as u32, + Box::into_raw(Box::new(Element::qs(".todo-list")?)) as u32, + Box::into_raw(Box::new(Element::qs(".todo-count")?)) as u32, + Box::into_raw(Box::new(Element::qs(".clear-completed")?)) as u32, + Box::into_raw(Box::new(Element::qs(".main")?)) as u32, + Box::into_raw(Box::new(Element::qs(".toggle-all")?)) as u32, + Box::into_raw(Box::new(Element::qs(".new-todo")?)) as u32, + )) } pub fn init(&mut self) { diff --git a/examples/wasm-in-wasm/Cargo.toml b/examples/wasm-in-wasm/Cargo.toml index d5322666b03e..15908db3bb18 100644 --- a/examples/wasm-in-wasm/Cargo.toml +++ b/examples/wasm-in-wasm/Cargo.toml @@ -8,5 +8,5 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" -js-sys = "0.3.28" +wasm-bindgen = "0.2.52" +js-sys = "0.3.29" diff --git a/examples/wasm2js/Cargo.toml b/examples/wasm2js/Cargo.toml index 5aef7e6a35b5..0f02e664104f 100644 --- a/examples/wasm2js/Cargo.toml +++ b/examples/wasm2js/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" diff --git a/examples/web-components-composed-composed-path/Cargo.toml b/examples/web-components-composed-composed-path/Cargo.toml new file mode 100644 index 000000000000..2bbcef0b538e --- /dev/null +++ b/examples/web-components-composed-composed-path/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "web-components-composed-composed-path" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'console', + 'Window', + 'Document', + 'Node', + 'Event', + 'EventTarget', + 'Element', + 'HtmlElement', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-composed-composed-path/index.html b/examples/web-components-composed-composed-path/index.html new file mode 100755 index 000000000000..935a1132e5ec --- /dev/null +++ b/examples/web-components-composed-composed-path/index.html @@ -0,0 +1,16 @@ + + + + + composed and composedPath demo + + + +

composed and composedPath demo

+ + + + + + + diff --git a/examples/web-components-composed-composed-path/main.js b/examples/web-components-composed-composed-path/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-composed-composed-path/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-composed-composed-path/package.json b/examples/web-components-composed-composed-path/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-composed-composed-path/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-composed-composed-path/src/lib.rs b/examples/web-components-composed-composed-path/src/lib.rs new file mode 100644 index 000000000000..03bf86d015f3 --- /dev/null +++ b/examples/web-components-composed-composed-path/src/lib.rs @@ -0,0 +1,70 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct OpenShadowHtmlElement; +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct ClosedShadowHtmlElement; + +#[wasm_bindgen] +impl OpenShadowHtmlElement { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + super(); + OpenShadowHtmlElement + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let p_elem = document.create_element("p").unwrap(); + p_elem.set_text_content(this.get_attribute("text").as_ref().map(String::as_str)); + + this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap().append_child(&p_elem).unwrap(); + } +} + +#[wasm_bindgen] +impl ClosedShadowHtmlElement { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + super(); + ClosedShadowHtmlElement + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let p_elem = document.create_element("p").unwrap(); + p_elem.set_text_content(this.get_attribute("text").as_ref().map(String::as_str)); + + this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Closed) + ).unwrap().append_child(&p_elem).unwrap(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let custom_elements = window.custom_elements(); + custom_elements.define( "open-shadow", &js_sys::Function::of::< OpenShadowHtmlElement>()).unwrap(); + custom_elements.define("closed-shadow", &js_sys::Function::of::()).unwrap(); + + document + .query_selector("html") + .unwrap() + .unwrap() + .add_event_listener_with_callback("click", Closure::wrap(Box::new(|e: web_sys::Event| { + web_sys::console::log_1(&e.composed().into()); + web_sys::console::log_1( e.composed_path().as_ref()); + }) as Box).as_ref().unchecked_ref()).unwrap(); +} \ No newline at end of file diff --git a/examples/web-components-composed-composed-path/webpack.config.js b/examples/web-components-composed-composed-path/webpack.config.js new file mode 100644 index 000000000000..a42db31a6911 --- /dev/null +++ b/examples/web-components-composed-composed-path/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html']), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-defined-pseudo-class/Cargo.toml b/examples/web-components-defined-pseudo-class/Cargo.toml new file mode 100644 index 000000000000..5c9a5ed6e339 --- /dev/null +++ b/examples/web-components-defined-pseudo-class/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "web-components-defined-pseudo-class" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'Window', + 'Document', + 'Node', + 'Element', + 'HtmlElement', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-defined-pseudo-class/index.html b/examples/web-components-defined-pseudo-class/index.html new file mode 100755 index 000000000000..b83b37b89c86 --- /dev/null +++ b/examples/web-components-defined-pseudo-class/index.html @@ -0,0 +1,37 @@ + + + + + :defined demo + + + + +

:defined demo

+ + + +

Standard paragraph example text

+ + + diff --git a/examples/web-components-defined-pseudo-class/main.js b/examples/web-components-defined-pseudo-class/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-defined-pseudo-class/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-defined-pseudo-class/package.json b/examples/web-components-defined-pseudo-class/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-defined-pseudo-class/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-defined-pseudo-class/src/lib.rs b/examples/web-components-defined-pseudo-class/src/lib.rs new file mode 100644 index 000000000000..f37964181669 --- /dev/null +++ b/examples/web-components-defined-pseudo-class/src/lib.rs @@ -0,0 +1,37 @@ +use wasm_bindgen::prelude::*; +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct SimpleCustomHtmlElement; + +#[wasm_bindgen] +impl SimpleCustomHtmlElement { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + super(); + SimpleCustomHtmlElement + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let div_elem = document.create_element("div").unwrap(); + div_elem.set_text_content(this.get_attribute("text").as_ref().map(String::as_str)); + + let shadow_root = this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap(); + shadow_root.append_child(&div_elem).unwrap(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + web_sys::window().unwrap() + .custom_elements() + .define( + "simple-custom", + &js_sys::Function::of::() + ).unwrap(); +} \ No newline at end of file diff --git a/examples/web-components-defined-pseudo-class/webpack.config.js b/examples/web-components-defined-pseudo-class/webpack.config.js new file mode 100644 index 000000000000..a42db31a6911 --- /dev/null +++ b/examples/web-components-defined-pseudo-class/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html']), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-edit-word/Cargo.toml b/examples/web-components-edit-word/Cargo.toml new file mode 100644 index 000000000000..a9c087a78bbd --- /dev/null +++ b/examples/web-components-edit-word/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "web-components-edit-word" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'Window', + 'Document', + 'DocumentFragment', + 'Node', + 'Event', + 'EventTarget', + 'Element', + 'HtmlElement', + 'HtmlInputElement', + 'HtmlTemplateElement', + 'CssStyleDeclaration', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-edit-word/index.html b/examples/web-components-edit-word/index.html new file mode 100755 index 000000000000..ed296c02340d --- /dev/null +++ b/examples/web-components-edit-word/index.html @@ -0,0 +1,30 @@ + + + + + edit-word demo + + + +

edit-word demo

+ + + + +

Morgan Stanley

+ 36 + Accountant +
+ +

My name is Chris, the man said.

+ + diff --git a/examples/web-components-edit-word/main.js b/examples/web-components-edit-word/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-edit-word/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-edit-word/package.json b/examples/web-components-edit-word/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-edit-word/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-edit-word/src/lib.rs b/examples/web-components-edit-word/src/lib.rs new file mode 100644 index 000000000000..38acad74e5d8 --- /dev/null +++ b/examples/web-components-edit-word/src/lib.rs @@ -0,0 +1,130 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct PersonDetailsHtmlElement; +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct EditWordHtmlElement; + +#[wasm_bindgen] +impl PersonDetailsHtmlElement { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + super(); + PersonDetailsHtmlElement + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let template = document.get_element_by_id("person-template").unwrap().unchecked_into::(); + let template_content = template.content(); + + let shadow_root = this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap(); + + let style = document.create_element("style").unwrap(); + style.set_text_content(Some(" + div { padding: 10px; border: 1px solid gray; width: 200px; margin: 10px; } + h2 { margin: 0 0 10px; } + ul { margin: 0; } + p { margin: 10px 0; } + ")); + + shadow_root.append_child(&style).unwrap(); + shadow_root.append_child(&template_content.clone_node_with_deep(true).unwrap()).unwrap(); + } +} + +#[wasm_bindgen] +impl EditWordHtmlElement { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + super(); + EditWordHtmlElement {} + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let shadow_root = this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap(); + + let form = document.create_element("form").unwrap().unchecked_into::(); + let input = document.create_element("input").unwrap().unchecked_into::(); + let span = document.create_element("span").unwrap().unchecked_into::(); + + let style = document.create_element("style").unwrap(); + style.set_text_content(Some("span { background-color: #eef; padding: 0 2px }")); + + shadow_root.append_child(&style).unwrap(); + shadow_root.append_child(&form).unwrap(); + shadow_root.append_child(&span).unwrap(); + + let text_content = this.text_content(); + let text_content = text_content.as_ref().map(String::as_str); + span.set_text_content(text_content); + input.set_value(text_content.unwrap()); + + form.append_child(&input).unwrap(); + form.style().set_property("display", "none").unwrap(); + span.style().set_property("display", "inline-block").unwrap(); + input.style().set_property("width", &format!("{}px", span.client_width())).unwrap(); + + this.set_attribute("tabindex", "0").unwrap(); + input.set_attribute("required", "required").unwrap(); + this.style().set_property("display", "inline-block").unwrap(); + + let span_ = span.clone(); + let form_ = form.clone(); + let input_ = input.clone(); + let click_handler = Closure::wrap(Box::new(move || { + use std::convert::TryInto; + span_.style().set_property("display", "none").unwrap(); + form_.style().set_property("display", "inline-block").unwrap(); + input_.focus().unwrap(); + input_.set_selection_range(0, input_.value().len().try_into().unwrap()).unwrap(); + }) as Box); + this.add_event_listener_with_callback("click", click_handler.as_ref().unchecked_ref()).unwrap(); + click_handler.forget(); + + let span_ = span.clone(); + let form_ = form.clone(); + let input_ = input.clone(); + let update_display = move || { + span_.style().set_property("display", "inline-block").unwrap(); + form_.style().set_property("display", "none").unwrap(); + span_.set_text_content(Some(&input_.value())); + input_.style().set_property("width", &format!("{}px", span_.client_width())).unwrap(); + }; + + let update_display_ = update_display.clone(); + form.add_event_listener_with_callback("submit", &Closure::once_into_js(move |e: web_sys::Event| { + update_display_(); + e.prevent_default(); + }).into()).unwrap(); + + let update_display = Closure::wrap(Box::new(update_display) as Box); + input.add_event_listener_with_callback( + "blur", + update_display.as_ref().unchecked_ref(), + ).unwrap(); + + update_display.forget(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + let window = web_sys::window().unwrap(); + + let custom_elements = window.custom_elements(); + custom_elements.define("person-details", &js_sys::Function::of::()).unwrap(); + custom_elements.define( "edit-word" , &js_sys::Function::of::< EditWordHtmlElement>()).unwrap(); +} \ No newline at end of file diff --git a/examples/web-components-edit-word/webpack.config.js b/examples/web-components-edit-word/webpack.config.js new file mode 100644 index 000000000000..a42db31a6911 --- /dev/null +++ b/examples/web-components-edit-word/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html']), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-editable-list/Cargo.toml b/examples/web-components-editable-list/Cargo.toml new file mode 100644 index 000000000000..9a9ff610cf3c --- /dev/null +++ b/examples/web-components-editable-list/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "web-components-editable-list" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'Window', + 'Document', + 'DocumentFragment', + 'DomTokenList', + 'Node', + 'NodeList', + 'NamedNodeMap', + 'Attr', + 'Event', + 'EventTarget', + 'Element', + 'HtmlElement', + 'HtmlInputElement', + 'HtmlCollection', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-editable-list/index.html b/examples/web-components-editable-list/index.html new file mode 100755 index 000000000000..ba5ccea6aaa3 --- /dev/null +++ b/examples/web-components-editable-list/index.html @@ -0,0 +1,22 @@ + + + + + Editable List | Web Components + + + + + + + + diff --git a/examples/web-components-editable-list/main.js b/examples/web-components-editable-list/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-editable-list/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-editable-list/package.json b/examples/web-components-editable-list/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-editable-list/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-editable-list/src/lib.rs b/examples/web-components-editable-list/src/lib.rs new file mode 100644 index 000000000000..48beae739d52 --- /dev/null +++ b/examples/web-components-editable-list/src/lib.rs @@ -0,0 +1,196 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct EditableList { + item_list: Option, +} + +#[wasm_bindgen] +impl EditableList { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + // establish prototype chain + super(); + EditableList { item_list: None } + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + // attaches shadow tree and returns shadow root reference + // https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow + let shadow = this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap(); + + // creating a container for the editable-list component + let editable_list_container = document.create_element("div").unwrap(); + + // get attribute values from getters + let title = this.title(); + let add_item_text = this.add_item_text(); + let list_items: Vec = this.items(); + + // adding a class toour container for the sake of clarity + editable_list_container.class_list().add(&js_sys::Array::of1(&"editable-list".into())).unwrap(); + + // creating the inner HTML of the editable list element + editable_list_container.set_inner_html(&format!(r#" + +

{}

+
    + {} +
+
+ + + +
+ "#, title, list_items.iter().map(|item: &String| format!(r#" +
  • {} + +
  • + "#, item)).collect::>().join(""), add_item_text)); + + // binding methods + // this.addListItem = this.addListItem.bind(this); + // this.handleRemoveItemListeners = this.handleRemoveItemListeners.bind(this); + // this.removeListItem = this.removeListItem.bind(this); + + // appending the container to the shadow DOM + shadow.append_child(&editable_list_container).unwrap(); + } + + // add items to the list + // #[wasm_bindgen(js_name="addListItem")] + fn add_list_item(&self, _: web_sys::Event) { + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let text_input = self + .shadow_root().unwrap() + .query_selector(".add-new-list-item-input").unwrap().unwrap() + .unchecked_into::(); + + if !text_input.value().is_empty() { + let li = document.create_element("li").unwrap(); + let button = document.create_element("button").unwrap(); + let children_length = self.item_list.as_ref().unwrap().children().length(); + + li.set_text_content(Some(text_input.value().as_str())); + button.class_list().add(&js_sys::Array::of2(&"editable-list-remove-item".into(), &"icon".into())).unwrap(); + button.set_inner_html("⊖"); + + self.item_list.as_ref().unwrap().append_child(&li).unwrap(); + self.item_list.as_ref().unwrap().children().item(children_length).unwrap().append_child(&button).unwrap(); + li.append_child(&button).unwrap(); + + Self::handle_remove_item_listeners(js_sys::Array::of1(&button)); + + text_input.set_value(""); + } + } + + // fires after the element has been attached to the DOM + #[wasm_bindgen(js_name="connectedCallback")] + pub fn connected_callback(&mut self) { + let remove_element_buttons = self.shadow_root().unwrap().query_selector_all(".editable-list-remove-item").unwrap(); + let add_element_button = self.shadow_root().unwrap().query_selector(".editable-list-add-item").unwrap().unwrap(); + + self.item_list = self.shadow_root().unwrap().query_selector(".item-list").unwrap(); + + let this = self as *mut Self; + let add_list_item = Closure::wrap(Box::new( + move |e| (unsafe { &mut *this }).add_list_item(e) + ) as Box); + + Self::handle_remove_item_listeners(remove_element_buttons.unchecked_into::()); + add_element_button.add_event_listener_with_callback_and_bool( + "click", + add_list_item.as_ref().unchecked_ref(), + false, + ).unwrap(); + + add_list_item.forget(); + } + + // gathering data from element attributes + // #[wasm_bindgen(getter)] + fn title(&self) -> String { + self.get_attribute("title").unwrap_or("".to_string()) + } + + // #[wasm_bindgen(getter)] + fn items(&self) -> Vec { + let mut items = Vec::new(); + + let attributes = self.attributes(); + for i in 0..attributes.length() { + let attr = attributes.item(i).unwrap(); + if attr.name().contains("list-item") { + items.push(attr.value()); + } + } + + items + } + + // #[wasm_bindgen(getter)] + fn add_item_text(&self) -> String { + self.get_attribute("add-item-text").unwrap_or("".to_string()) + } + + fn handle_remove_item_listeners(array_of_elements: js_sys::Array) { + let remove_list_item = Closure::wrap(Box::new( + Self::remove_list_item + ) as Box); + + array_of_elements.for_each(&mut |element: JsValue, _: u32, _: js_sys::Array| { + element + .unchecked_into::() + .add_event_listener_with_callback_and_bool( + "click", + remove_list_item.as_ref().unchecked_ref(), + false, + ).unwrap(); + }); + + remove_list_item.forget(); + } + + fn remove_list_item(event: web_sys::Event) { + event + .target().unwrap() + .unchecked_into::() + .parent_node().unwrap() + .unchecked_into::() + .remove(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + // let the browser know about the custom element + let window = web_sys::window().unwrap(); + + let custom_elements = window.custom_elements(); + custom_elements.define("editable-list", &js_sys::Function::of::()).unwrap(); +} diff --git a/examples/web-components-editable-list/style.css b/examples/web-components-editable-list/style.css new file mode 100755 index 000000000000..e1fce7260927 --- /dev/null +++ b/examples/web-components-editable-list/style.css @@ -0,0 +1,11 @@ +html { + font-size: 90%; +} + +body { + color: #2b2b2b; + font-family: sans-serif; + margin: 0 auto; + max-width: 350px; + padding-top: 5rem; +} diff --git a/examples/web-components-editable-list/webpack.config.js b/examples/web-components-editable-list/webpack.config.js new file mode 100644 index 000000000000..08169b40a83a --- /dev/null +++ b/examples/web-components-editable-list/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html', 'style.css']), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-element-details/Cargo.toml b/examples/web-components-element-details/Cargo.toml new file mode 100644 index 000000000000..64a7a0f81c7d --- /dev/null +++ b/examples/web-components-element-details/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "web-components-element-details" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'Window', + 'Document', + 'DocumentFragment', + 'Node', + 'Element', + 'HtmlElement', + 'HtmlTemplateElement', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-element-details/index.html b/examples/web-components-element-details/index.html new file mode 100755 index 000000000000..87eb74e2d03c --- /dev/null +++ b/examples/web-components-element-details/index.html @@ -0,0 +1,64 @@ + + + + + element-details - web component using <template> and <slot> + + + +

    element-details - web component using <template> and <slot>

    + + + + + slot + A placeholder inside a web + component that users can fill with their own markup, + with the effect of composing different DOM trees + together. +
    +
    name
    +
    The name of the slot.
    +
    +
    + + + template + A mechanism for holding client- + side content that is not to be rendered when a page is + loaded but may subsequently be instantiated during + runtime using JavaScript. + + + + + diff --git a/examples/web-components-element-details/main.js b/examples/web-components-element-details/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-element-details/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-element-details/package.json b/examples/web-components-element-details/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-element-details/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-element-details/src/lib.rs b/examples/web-components-element-details/src/lib.rs new file mode 100644 index 000000000000..89ca76d73aa5 --- /dev/null +++ b/examples/web-components-element-details/src/lib.rs @@ -0,0 +1,41 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct ElementDetails; + +#[wasm_bindgen] +impl ElementDetails { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + super(); + ElementDetails + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let template = document + .get_element_by_id("element-details-template").unwrap() + .unchecked_into::() + .content(); + + this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap() + .append_child(&template.clone_node_with_deep(true).unwrap()).unwrap(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + web_sys::window().unwrap() + .custom_elements() + .define( + "element-details", + &js_sys::Function::of::(), + ).unwrap(); +} \ No newline at end of file diff --git a/examples/web-components-element-details/webpack.config.js b/examples/web-components-element-details/webpack.config.js new file mode 100644 index 000000000000..a42db31a6911 --- /dev/null +++ b/examples/web-components-element-details/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html']), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-expanding-list-web-component/Cargo.toml b/examples/web-components-expanding-list-web-component/Cargo.toml new file mode 100644 index 000000000000..554b8b6df878 --- /dev/null +++ b/examples/web-components-expanding-list-web-component/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "web-components-expanding-list-web-component" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'Window', + 'Document', + 'Node', + 'NodeList', + 'Event', + 'EventTarget', + 'Element', + 'HtmlElement', + 'HtmlUListElement', + 'CssStyleDeclaration', + 'CustomElementRegistry', + 'ElementDefinitionOptions', +] diff --git a/examples/web-components-expanding-list-web-component/img/down.png b/examples/web-components-expanding-list-web-component/img/down.png new file mode 100755 index 000000000000..7e27f8ebb832 Binary files /dev/null and b/examples/web-components-expanding-list-web-component/img/down.png differ diff --git a/examples/web-components-expanding-list-web-component/img/right.png b/examples/web-components-expanding-list-web-component/img/right.png new file mode 100755 index 000000000000..9becf6e4ca7f Binary files /dev/null and b/examples/web-components-expanding-list-web-component/img/right.png differ diff --git a/examples/web-components-expanding-list-web-component/index.html b/examples/web-components-expanding-list-web-component/index.html new file mode 100755 index 000000000000..5748eb9861f4 --- /dev/null +++ b/examples/web-components-expanding-list-web-component/index.html @@ -0,0 +1,76 @@ + + + + + Expanding list web component + + + +

    Expanding list web component

    +
      +
    • UK +
        +
      • Yorkshire +
          +
        • Leeds +
            +
          • Train station
          • +
          • Town hall
          • +
          • Headrow
          • +
          +
        • +
        • Bradford
        • +
        • Hull
        • +
        +
      • +
      +
    • +
    • USA +
        +
      • California +
          +
        • Los Angeles
        • +
        • San Francisco
        • +
        • Berkeley
        • +
        +
      • +
      • Nevada
      • +
      • Oregon
      • +
      +
    • +
    + + + + diff --git a/examples/web-components-expanding-list-web-component/main.js b/examples/web-components-expanding-list-web-component/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-expanding-list-web-component/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-expanding-list-web-component/package.json b/examples/web-components-expanding-list-web-component/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-expanding-list-web-component/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-expanding-list-web-component/src/lib.rs b/examples/web-components-expanding-list-web-component/src/lib.rs new file mode 100644 index 000000000000..1f5af3e72444 --- /dev/null +++ b/examples/web-components-expanding-list-web-component/src/lib.rs @@ -0,0 +1,93 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +#[wasm_bindgen(prototype=web_sys::HtmlUListElement)] +pub struct ExpandingList; + +#[wasm_bindgen] +impl ExpandingList { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + // web_sys::window().unwrap().set_onload(Some(&Closure::once_into_js(|| { + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let uls = document.query_selector_all(":root ul").unwrap(); + let lis = document.query_selector_all(":root li").unwrap(); + + for i in 1..uls.length() { + uls .item(i).unwrap() + .unchecked_into::() + .style() + .set_property("display", "none").unwrap(); + } + + for i in 0..lis.length() { + let li = lis.item(i).unwrap(); + + let child_text = li.child_nodes().item(0).unwrap(); + let new_span = document.create_element("span").unwrap(); + + new_span.set_text_content(child_text.text_content().as_ref().map(String::as_str)); + li.insert_before(&new_span, Some(&child_text)).unwrap(); + li.remove_child(&child_text).unwrap(); + } + + let showul = Closure::wrap(Box::new(|e: web_sys::Event| { + let nextul = e.target().unwrap() + .unchecked_into::() + .next_element_sibling().unwrap() + .unchecked_into::(); + + let parent = nextul.parent_node().unwrap() + .unchecked_into::(); + + let style = nextul.style(); + + if style.get_property_value("display").unwrap() == "block" { + style.set_property("display", "none").unwrap(); + parent.set_attribute("class", "closed").unwrap(); + } else { + style.set_property("display", "block").unwrap(); + parent.set_attribute("class", "open").unwrap(); + } + }) as Box); + + let spans = document.query_selector_all(":root span").unwrap(); + + for i in 0..spans.length() { + let span = spans.item(i).unwrap().unchecked_into::(); + if let Some(_) = span.next_element_sibling() { + span.style().set_property("cursor", "pointer").unwrap(); + span.parent_node().unwrap() + .unchecked_into::() + .set_attribute("class", "closed").unwrap(); + span.set_onclick(Some(showul.as_ref().unchecked_ref())); + } + } + + showul.forget(); + // }).into())); + + instantiate! { + super(); + ExpandingList + } + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + let mut options = web_sys::ElementDefinitionOptions::new(); + options.extends("ul"); + + web_sys::window().unwrap() + .custom_elements() + .define_with_options( + "expanding-list", + &js_sys::Function::of::(), + &options + ).unwrap(); +} \ No newline at end of file diff --git a/examples/web-components-expanding-list-web-component/webpack.config.js b/examples/web-components-expanding-list-web-component/webpack.config.js new file mode 100644 index 000000000000..a1ecb7cf4b66 --- /dev/null +++ b/examples/web-components-expanding-list-web-component/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html', {from: 'img', to: 'img'}]), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-host-selectors/Cargo.toml b/examples/web-components-host-selectors/Cargo.toml new file mode 100644 index 000000000000..b49cdcc4d4c9 --- /dev/null +++ b/examples/web-components-host-selectors/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "web-components-host-selectors" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'Window', + 'Document', + 'Node', + 'Element', + 'HtmlElement', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-host-selectors/index.html b/examples/web-components-host-selectors/index.html new file mode 100755 index 000000000000..fd917c979d29 --- /dev/null +++ b/examples/web-components-host-selectors/index.html @@ -0,0 +1,46 @@ + + + + + Host selectors + + + + +
    +

    Host selectors example

    +
    +
    +
    +

    This is my first article.

    + +

    This article is rather lovely and exciting — it is all about animals, including Beavers, Bears, and Wolves. I love animals and I'm sure you will too; please let us know what your favorite animals are. Woo hoo!

    + +
    + +
    +

    This is my second article.

    + +

    This article is also quite exciting — it is all about colors, including Red, Blue, and Pink. A true joy indeed — funky exciting colors make the world go round. No more gray days for us.

    + +
    + + + +
    + + + + + diff --git a/examples/web-components-host-selectors/main.js b/examples/web-components-host-selectors/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-host-selectors/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-host-selectors/package.json b/examples/web-components-host-selectors/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-host-selectors/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-host-selectors/src/lib.rs b/examples/web-components-host-selectors/src/lib.rs new file mode 100644 index 000000000000..ba7bb9b39cf9 --- /dev/null +++ b/examples/web-components-host-selectors/src/lib.rs @@ -0,0 +1,49 @@ +use wasm_bindgen::prelude::*; +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct ContextSpan; + +#[wasm_bindgen] +impl ContextSpan { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + super(); + ContextSpan + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let style = document.create_element("style").unwrap(); + let span = document.create_element("span").unwrap(); + span.set_text_content(this.text_content().as_ref().map(String::as_str)); + + let shadow_root = this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap(); + shadow_root.append_child(&style).unwrap(); + shadow_root.append_child(&span).unwrap(); + + style.set_text_content(Some(r#" + span:hover { text-decoration: underline; } + :host-context(h1) { font-style: italic; } + :host-context(h1):after { content: " - no links in headers!" } + :host-context(article, aside) { color: gray; } + :host(.footer) { color : red; } + :host { background: rgba(0,0,0,0.1); padding: 2px 5px; } + "#)); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + // Define the new element + web_sys::window().unwrap() + .custom_elements() + .define( + "context-span", + &js_sys::Function::of::(), + ).unwrap(); +} \ No newline at end of file diff --git a/examples/web-components-host-selectors/styles.css b/examples/web-components-host-selectors/styles.css new file mode 100755 index 000000000000..51570abc6a05 --- /dev/null +++ b/examples/web-components-host-selectors/styles.css @@ -0,0 +1,25 @@ +html { + font-family: sans-serif; + background-color: #a60000; +} + +body { + margin: 0 auto; + width: 80%; + max-width: 800px; +} + +header, article, aside, footer { + background-color: white; + padding: 20px; + margin: 20px 0; +} + +header, footer { + background-color: #ddd; +} + +li, p { + font-size: 1.1rem; + line-height: 1.5; +} diff --git a/examples/web-components-host-selectors/webpack.config.js b/examples/web-components-host-selectors/webpack.config.js new file mode 100644 index 000000000000..11d276b75ea4 --- /dev/null +++ b/examples/web-components-host-selectors/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html', 'styles.css']), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-life-cycle-callbacks/Cargo.toml b/examples/web-components-life-cycle-callbacks/Cargo.toml new file mode 100644 index 000000000000..2bfd4375ee29 --- /dev/null +++ b/examples/web-components-life-cycle-callbacks/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "web-components-life-cycle-callbacks" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'console', + 'Window', + 'Document', + 'DocumentFragment', + 'Node', + 'Element', + 'HtmlElement', + 'HtmlButtonElement', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-life-cycle-callbacks/index.html b/examples/web-components-life-cycle-callbacks/index.html new file mode 100755 index 000000000000..a9cb780c22d1 --- /dev/null +++ b/examples/web-components-life-cycle-callbacks/index.html @@ -0,0 +1,23 @@ + + + + + Life cycle callbacks test + + + + +

    Life cycle callbacks test

    + +
    + + + +
    + + + diff --git a/examples/web-components-life-cycle-callbacks/main.js b/examples/web-components-life-cycle-callbacks/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-life-cycle-callbacks/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-life-cycle-callbacks/package.json b/examples/web-components-life-cycle-callbacks/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-life-cycle-callbacks/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-life-cycle-callbacks/src/lib.rs b/examples/web-components-life-cycle-callbacks/src/lib.rs new file mode 100644 index 000000000000..c1a6f2c84dda --- /dev/null +++ b/examples/web-components-life-cycle-callbacks/src/lib.rs @@ -0,0 +1,165 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +// Create a class for the element +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct Square; + +#[wasm_bindgen] +impl Square { + // Specify observed attributes so that + // attributeChangedCallback will work + #[wasm_bindgen(getter = observedAttributes)] + pub fn observed_attributes() -> js_sys::Array { + js_sys::Array::of2(&"c".into(), &"l".into()) + } + + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + // Always call super first in constructor + super(); + Square + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let shadow = this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap(); + + let div = document.create_element("div").unwrap(); + let style = document.create_element("style").unwrap(); + shadow.append_child(&style).unwrap(); + shadow.append_child(&div).unwrap(); + } + + #[wasm_bindgen(js_name="connectedCallback")] + pub fn connected_callback(&self) { + web_sys::console::log_1(&"Custom square element added to page.".into()); + self.update_style(); + } + + #[wasm_bindgen(js_name="disconnectedCallback")] + pub fn disconnected_callback(&self) { + web_sys::console::log_1(&"Custom square element removed from page.".into()); + } + + #[wasm_bindgen(js_name="adoptedCallback")] + pub fn adopted_callback(&self) { + web_sys::console::log_1(&"Custom square element moved to new page.".into()); + } + + #[wasm_bindgen(js_name="attributeChangedCallback")] + pub fn attribute_changed_callback(&self, _name: &str, _old_value: Option, _new_value: &str) { + web_sys::console::log_1(&"Custom square element attributes changed.".into()); + self.update_style(); + } + + fn update_style(&self) { + let shadow = self.shadow_root().unwrap(); + shadow.query_selector("style").unwrap().unwrap().set_text_content(Some(&format!( + " + div {{ + width: {0}px; + height: {0}px; + background-color: {1}; + }} + ", + self.get_attribute("l").unwrap_or(String::from("100")), + self.get_attribute("c").unwrap_or(String::from("red")), + ))); + } +} + +fn random(min: u32, max: u32) -> u32 { + js_sys::Math::floor(js_sys::Math::random() * ((max - min + 1) + min) as f64) as u32 +} + +struct Context { + add: web_sys::HtmlButtonElement, + update: web_sys::HtmlButtonElement, + remove: web_sys::HtmlButtonElement, + square: Option, +} + +impl Context { + fn new() -> Context { + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let mut me = Context { + add : document.query_selector(".add" ).unwrap().unwrap().unchecked_into(), + update: document.query_selector(".update").unwrap().unwrap().unchecked_into(), + remove: document.query_selector(".remove").unwrap().unwrap().unchecked_into(), + square: None, + }; + me.update_buttons(); + + me + } + + fn update_buttons(&mut self) { + let square_exists = self.square.is_some(); + self.add.set_disabled(square_exists); + self.update.set_disabled(!square_exists); + self.remove.set_disabled(!square_exists); + } + + // Create a custom square element + fn add(&mut self) { + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + self.square = document.create_element("custom-square").ok(); + let square = self.square.as_ref().unwrap(); + square.set_attribute("l", "100").unwrap(); + square.set_attribute("c", "red").unwrap(); + document.body().unwrap().append_child(square).unwrap(); + + self.update_buttons(); + } + + // Randomly update square's attributes + fn update(&mut self) { + let square = self.square.as_ref().unwrap(); + square.set_attribute("l", &random(50, 200).to_string()).unwrap(); + square.set_attribute("c", &format!("rgb({}, {}, {})", random(0, 255), random(0, 255), random(0, 255))).unwrap(); + } + + // Remove the square + fn remove(&mut self) { + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let square = self.square.take().unwrap(); + document.body().unwrap().remove_child(&square).unwrap(); + self.update_buttons(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + web_sys::window().unwrap() + .custom_elements() + .define("custom-square", &js_sys::Function::of::()).unwrap(); + + let context = Box::into_raw(Box::new(Context::new())); + + let add_handler = Closure::wrap(Box::new(move || unsafe { (*context).add(); }) as Box); + let update_handler = Closure::wrap(Box::new(move || unsafe { (*context).update(); }) as Box); + let remove_handler = Closure::wrap(Box::new(move || unsafe { (*context).remove(); }) as Box); + + let context = unsafe { &mut *context }; + + context.add .set_onclick(Some(add_handler .as_ref().unchecked_ref())); + context.update.set_onclick(Some(update_handler.as_ref().unchecked_ref())); + context.remove.set_onclick(Some(remove_handler.as_ref().unchecked_ref())); + + add_handler .forget(); + update_handler.forget(); + remove_handler.forget(); +} diff --git a/examples/web-components-life-cycle-callbacks/webpack.config.js b/examples/web-components-life-cycle-callbacks/webpack.config.js new file mode 100644 index 000000000000..a42db31a6911 --- /dev/null +++ b/examples/web-components-life-cycle-callbacks/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html']), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-popup-info-box-external-stylesheet/Cargo.toml b/examples/web-components-popup-info-box-external-stylesheet/Cargo.toml new file mode 100644 index 000000000000..d95634bfb948 --- /dev/null +++ b/examples/web-components-popup-info-box-external-stylesheet/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "web-components-popup-info-box-external-stylesheet" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'Window', + 'Document', + 'Node', + 'Element', + 'HtmlElement', + 'HtmlImageElement', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-popup-info-box-external-stylesheet/img/alt.png b/examples/web-components-popup-info-box-external-stylesheet/img/alt.png new file mode 100755 index 000000000000..2a9f4a052a41 Binary files /dev/null and b/examples/web-components-popup-info-box-external-stylesheet/img/alt.png differ diff --git a/examples/web-components-popup-info-box-external-stylesheet/img/default.png b/examples/web-components-popup-info-box-external-stylesheet/img/default.png new file mode 100755 index 000000000000..e4b48b1c6ab0 Binary files /dev/null and b/examples/web-components-popup-info-box-external-stylesheet/img/default.png differ diff --git a/examples/web-components-popup-info-box-external-stylesheet/index.html b/examples/web-components-popup-info-box-external-stylesheet/index.html new file mode 100755 index 000000000000..16d502e2bf83 --- /dev/null +++ b/examples/web-components-popup-info-box-external-stylesheet/index.html @@ -0,0 +1,19 @@ + + + + + Pop-up info box — web components + + + +

    Pop-up info widget - web components

    + +
    +
    + + +
    +
    + + + diff --git a/examples/web-components-popup-info-box-external-stylesheet/main.js b/examples/web-components-popup-info-box-external-stylesheet/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-popup-info-box-external-stylesheet/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-popup-info-box-external-stylesheet/package.json b/examples/web-components-popup-info-box-external-stylesheet/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-popup-info-box-external-stylesheet/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-popup-info-box-external-stylesheet/src/lib.rs b/examples/web-components-popup-info-box-external-stylesheet/src/lib.rs new file mode 100644 index 000000000000..db7008294bb3 --- /dev/null +++ b/examples/web-components-popup-info-box-external-stylesheet/src/lib.rs @@ -0,0 +1,76 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +// Create a class for the element +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct PopUpInfoExt; + +#[wasm_bindgen] +impl PopUpInfoExt { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + // Always call super first in constructor + super(); + PopUpInfoExt + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + // Create a shadow root + let shadow = this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap(); + + // Create spans + let wrapper = document.create_element("span").unwrap(); + wrapper.set_attribute("class", "wrapper").unwrap(); + + let icon = document.create_element("span").unwrap(); + icon.set_attribute("class", "icon").unwrap(); + icon.set_attribute("tabindex", "0").unwrap(); + + let info = document.create_element("span").unwrap(); + info.set_attribute("class", "info").unwrap(); + + // Take attribute content and put it inside the info span + let text = this.get_attribute("data-text").unwrap(); + info.set_text_content(Some(&text)); + + // Insert icon + let img_url = if this.has_attribute("img") { + this.get_attribute("img").unwrap() + } else { + "img/default.png".to_string() + }; + + let img = document.create_element("img").unwrap().unchecked_into::(); + img.set_src(&img_url); + icon.append_child(&img).unwrap(); + + // Apply external styles to the shadow dom + let link_elem = document.create_element("link").unwrap(); + link_elem.set_attribute("rel", "stylesheet").unwrap(); + link_elem.set_attribute("href", "style.css").unwrap(); + + // Attach the created elements to the shadow dom + shadow.append_child(&link_elem).unwrap(); + shadow.append_child(&wrapper).unwrap(); + wrapper.append_child(&icon).unwrap(); + wrapper.append_child(&info).unwrap(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + // Define the new element + web_sys::window().unwrap() + .custom_elements() + .define( + "popup-info", + &js_sys::Function::of::(), + ).unwrap(); +} \ No newline at end of file diff --git a/examples/web-components-popup-info-box-external-stylesheet/style.css b/examples/web-components-popup-info-box-external-stylesheet/style.css new file mode 100755 index 000000000000..9403122a41fa --- /dev/null +++ b/examples/web-components-popup-info-box-external-stylesheet/style.css @@ -0,0 +1,27 @@ +.wrapper { + position: relative; +} + +.info { + font-size: 0.8rem; + width: 200px; + display: inline-block; + border: 1px solid black; + padding: 10px; + background: white; + border-radius: 10px; + opacity: 0; + transition: 0.6s all; + position: absolute; + bottom: 20px; + left: 10px; + z-index: 3; +} + +img { + width: 1.2rem; +} + +.icon:hover + .info, .icon:focus + .info { + opacity: 1; +} diff --git a/examples/web-components-popup-info-box-external-stylesheet/webpack.config.js b/examples/web-components-popup-info-box-external-stylesheet/webpack.config.js new file mode 100644 index 000000000000..ef04d81b19d2 --- /dev/null +++ b/examples/web-components-popup-info-box-external-stylesheet/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html', 'style.css', {from: 'img', to: 'img'}]), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-popup-info-box-web-component/Cargo.toml b/examples/web-components-popup-info-box-web-component/Cargo.toml new file mode 100644 index 000000000000..280d4528ffff --- /dev/null +++ b/examples/web-components-popup-info-box-web-component/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "web-components-popup-info-box-web-component" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'console', + 'Window', + 'Document', + 'Node', + 'Element', + 'HtmlElement', + 'HtmlImageElement', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-popup-info-box-web-component/img/alt.png b/examples/web-components-popup-info-box-web-component/img/alt.png new file mode 100755 index 000000000000..2a9f4a052a41 Binary files /dev/null and b/examples/web-components-popup-info-box-web-component/img/alt.png differ diff --git a/examples/web-components-popup-info-box-web-component/img/default.png b/examples/web-components-popup-info-box-web-component/img/default.png new file mode 100755 index 000000000000..e4b48b1c6ab0 Binary files /dev/null and b/examples/web-components-popup-info-box-web-component/img/default.png differ diff --git a/examples/web-components-popup-info-box-web-component/index.html b/examples/web-components-popup-info-box-web-component/index.html new file mode 100755 index 000000000000..16d502e2bf83 --- /dev/null +++ b/examples/web-components-popup-info-box-web-component/index.html @@ -0,0 +1,19 @@ + + + + + Pop-up info box — web components + + + +

    Pop-up info widget - web components

    + +
    +
    + + +
    +
    + + + diff --git a/examples/web-components-popup-info-box-web-component/main.js b/examples/web-components-popup-info-box-web-component/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-popup-info-box-web-component/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-popup-info-box-web-component/package.json b/examples/web-components-popup-info-box-web-component/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-popup-info-box-web-component/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-popup-info-box-web-component/src/lib.rs b/examples/web-components-popup-info-box-web-component/src/lib.rs new file mode 100644 index 000000000000..752bd7656534 --- /dev/null +++ b/examples/web-components-popup-info-box-web-component/src/lib.rs @@ -0,0 +1,103 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +// Create a class for the element +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct PopUpInfo; + +#[wasm_bindgen] +impl PopUpInfo { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + // Always call super first in constructor + super(); + PopUpInfo + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + // Create a shadow root + let shadow = this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap(); + + // Create spans + let wrapper = document.create_element("span").unwrap(); + wrapper.set_attribute("class", "wrapper").unwrap(); + + let icon = document.create_element("span").unwrap(); + icon.set_attribute("class", "icon").unwrap(); + icon.set_attribute("tabindex", "0").unwrap(); + + let info = document.create_element("span").unwrap(); + info.set_attribute("class", "info").unwrap(); + + // Take attribute content and put it inside the info span + let text = this.get_attribute("data-text").unwrap(); + info.set_text_content(Some(&text)); + + // Insert icon + let img_url = if this.has_attribute("img") { + this.get_attribute("img").unwrap() + } else { + "img/default.png".to_string() + }; + + let img = document.create_element("img").unwrap().unchecked_into::(); + img.set_src(&img_url); + icon.append_child(&img).unwrap(); + + // Create some CSS to apply to the shadow dom + let style = document.create_element("style").unwrap(); + web_sys::console::log_1(&style.is_connected().into()); + + style.set_text_content(Some(" + .wrapper { + position: relative; + } + .info { + font-size: 0.8rem; + width: 200px; + display: inline-block; + border: 1px solid black; + padding: 10px; + background: white; + border-radius: 10px; + opacity: 0; + transition: 0.6s all; + position: absolute; + bottom: 20px; + left: 10px; + z-index: 3; + } + img { + width: 1.2rem; + } + .icon:hover + .info, .icon:focus + .info { + opacity: 1; + } + ")); + + // Attach the created elements to the shadow dom + shadow.append_child(&style).unwrap(); + web_sys::console::log_1(&style.is_connected().into()); + shadow.append_child(&wrapper).unwrap(); + wrapper.append_child(&icon).unwrap(); + wrapper.append_child(&info).unwrap(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + // Define the new element + web_sys::window().unwrap() + .custom_elements() + .define( + "popup-info", + &js_sys::Function::of::(), + ).unwrap(); +} \ No newline at end of file diff --git a/examples/web-components-popup-info-box-web-component/webpack.config.js b/examples/web-components-popup-info-box-web-component/webpack.config.js new file mode 100644 index 000000000000..a1ecb7cf4b66 --- /dev/null +++ b/examples/web-components-popup-info-box-web-component/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html', {from: 'img', to: 'img'}]), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-simple-template/Cargo.toml b/examples/web-components-simple-template/Cargo.toml new file mode 100644 index 000000000000..fce62194de1f --- /dev/null +++ b/examples/web-components-simple-template/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "web-components-simple-template" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'console', + 'Window', + 'Document', + 'DocumentFragment', + 'Node', + 'Element', + 'HtmlElement', + 'HtmlSlotElement', + 'HtmlTemplateElement', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-simple-template/index.html b/examples/web-components-simple-template/index.html new file mode 100755 index 000000000000..112749833c56 --- /dev/null +++ b/examples/web-components-simple-template/index.html @@ -0,0 +1,34 @@ + + + + + Simple template + + + +

    Simple template

    + + + + + Let's have some different text! + + + +
      +
    • Let's have some different text!
    • +
    • In a list!
    • +
    +
    + + + diff --git a/examples/web-components-simple-template/main.js b/examples/web-components-simple-template/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-simple-template/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-simple-template/package.json b/examples/web-components-simple-template/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-simple-template/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-simple-template/src/lib.rs b/examples/web-components-simple-template/src/lib.rs new file mode 100644 index 000000000000..12b4ad5e8611 --- /dev/null +++ b/examples/web-components-simple-template/src/lib.rs @@ -0,0 +1,48 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct MyParagraph; + +#[wasm_bindgen] +impl MyParagraph { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + super(); + MyParagraph + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let template = document + .get_element_by_id("my-paragraph").unwrap() + .unchecked_into::(); + + let template_content = template.content(); + + this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap() + .append_child(&template_content.clone_node_with_deep(true).unwrap()).unwrap(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + window.custom_elements().define( + "my-paragraph", + &js_sys::Function::of::(), + ).unwrap(); + + let slotted_span = document.query_selector("my-paragraph span").unwrap().unwrap(); + + web_sys::console::log_1(&slotted_span.assigned_slot().unwrap()); + web_sys::console::log_1(&slotted_span.slot().into()); +} diff --git a/examples/web-components-simple-template/webpack.config.js b/examples/web-components-simple-template/webpack.config.js new file mode 100644 index 000000000000..a42db31a6911 --- /dev/null +++ b/examples/web-components-simple-template/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html']), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-slotchange/Cargo.toml b/examples/web-components-slotchange/Cargo.toml new file mode 100644 index 000000000000..a0915d8db65a --- /dev/null +++ b/examples/web-components-slotchange/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "web-components-slotchange" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'console', + 'Window', + 'Document', + 'DocumentFragment', + 'Node', + 'NodeList', + 'Event', + 'EventTarget', + 'Element', + 'HtmlElement', + 'HtmlSlotElement', + 'HtmlTemplateElement', + 'CssStyleDeclaration', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-slotchange/index.html b/examples/web-components-slotchange/index.html new file mode 100755 index 000000000000..24359a18ba79 --- /dev/null +++ b/examples/web-components-slotchange/index.html @@ -0,0 +1,47 @@ + + + + + slotchange example + + + + +

    slotchange event example

    + + +
      +
    • Apples
    • +
    • Pears
    • +
    • Bananas
    • +
    • Oranges
    • +
    • Peaches
    • +
    • Strawberries
    • +
    • Blueberries
    • +
    + +

    A common, sweet, crunchy fruit, usually green or yellow in color.

    +

    A fairly common, sweet, usually green fruit, usually softer than Apples.

    +

    A long, curved, yellow fruit, with a fairly gentle flavor.

    +

    Orange in color, usually sweet but can be sharp, often contains pips.

    +

    An orange fruit with big stone in the middle, and sweet, juicy flesh.

    +

    A red fruit with yellow seeds on the outside; has a sweet flavor and a pretty shape.

    +

    They are berries and they are blue; sweet in flavor, small in size, round.

    +
    + + + + diff --git a/examples/web-components-slotchange/main.js b/examples/web-components-slotchange/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-slotchange/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-slotchange/package.json b/examples/web-components-slotchange/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-slotchange/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-slotchange/src/lib.rs b/examples/web-components-slotchange/src/lib.rs new file mode 100644 index 000000000000..3d8013cb403e --- /dev/null +++ b/examples/web-components-slotchange/src/lib.rs @@ -0,0 +1,91 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct SummaryDisplay; + +#[wasm_bindgen] +impl SummaryDisplay { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + super(); + SummaryDisplay + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let template = document + .get_element_by_id("summary-display-template").unwrap() + .unchecked_into::(); + + let template_content = template.content(); + + let shadow_root = this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap(); + + shadow_root.append_child(&template_content.clone_node_with_deep(true).unwrap()).unwrap(); + + let items = js_sys::Array::from(&this.query_selector_all("li").unwrap()); + let descriptions = Box::leak(Box::new(js_sys::Array::from(&this.query_selector_all("p").unwrap()))); + + fn update_display(description: web_sys::Element, item: &web_sys::HtmlElement) { + description.remove_attribute("slot").unwrap(); + + if description.get_attribute("data-name").unwrap() == item.text_content().unwrap() { + description.set_attribute("slot", "choice").unwrap(); + item.style().set_property("background-color", "#bad0e4").unwrap(); + } + }; + + let items_ = Box::leak(Box::new(items.clone())); + let click_handler = Closure::wrap(Box::new(move |e: web_sys::Event| { + items_.for_each(&mut |item: JsValue, _: u32, _: js_sys::Array| + item.unchecked_into::() + .style() + .set_property("background-color", "white") + .unwrap() + ); + + let item = e.target().unwrap().unchecked_into(); + descriptions.for_each(&mut |description: JsValue, _: u32, _: js_sys::Array| + update_display(description.unchecked_into(), &item) + ); + }) as Box); + + items.for_each(&mut |item: JsValue, _: u32, _: js_sys::Array| + item.unchecked_into::() + .add_event_listener_with_callback("click", click_handler.as_ref().unchecked_ref()) + .unwrap() + ); + + click_handler.forget(); + + let slots = this.shadow_root().unwrap().query_selector_all("slot").unwrap(); + + let slots_ = Box::leak(Box::new(slots.clone())); + let slotchange_handler = Closure::wrap(Box::new(move |_: web_sys::Event| { + let nodes = slots_.item(1).unwrap().unchecked_ref::().assigned_nodes(); + web_sys::console::log_1(&format!( + r#"Element in Slot "{}" changed to "{}"."#, + slots_.item(1).unwrap().unchecked_ref::().name(), + nodes.get(0).unchecked_ref::().outer_html(), + ).into()); + }) as Box); + + slots.item(1).unwrap().add_event_listener_with_callback("slotchange", slotchange_handler.as_ref().unchecked_ref()).unwrap(); + + slotchange_handler.forget(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + web_sys::window().unwrap() + .custom_elements() + .define("summary-display", &js_sys::Function::of::()).unwrap(); +} \ No newline at end of file diff --git a/examples/web-components-slotchange/webpack.config.js b/examples/web-components-slotchange/webpack.config.js new file mode 100644 index 000000000000..a42db31a6911 --- /dev/null +++ b/examples/web-components-slotchange/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html']), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-slotted-pseudo-element/Cargo.toml b/examples/web-components-slotted-pseudo-element/Cargo.toml new file mode 100644 index 000000000000..e7aed6ccfa8e --- /dev/null +++ b/examples/web-components-slotted-pseudo-element/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "web-components-slotted-pseudo-element" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'Window', + 'Document', + 'DocumentFragment', + 'Node', + 'Element', + 'HtmlElement', + 'HtmlTemplateElement', + 'CustomElementRegistry', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-slotted-pseudo-element/index.html b/examples/web-components-slotted-pseudo-element/index.html new file mode 100755 index 000000000000..5476dcd366ec --- /dev/null +++ b/examples/web-components-slotted-pseudo-element/index.html @@ -0,0 +1,41 @@ + + + + + ::slotted example + + + +

    ::slotted pseudo-element example

    + + + + +

    Morgan Stanley

    + 36 + Accountant +
    + + +

    Dr. Shazaam

    + Immortal + Superhero +
    + + +

    Boris

    + 27 + Time traveller +
    + + + diff --git a/examples/web-components-slotted-pseudo-element/main.js b/examples/web-components-slotted-pseudo-element/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-slotted-pseudo-element/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-slotted-pseudo-element/package.json b/examples/web-components-slotted-pseudo-element/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-slotted-pseudo-element/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-slotted-pseudo-element/src/lib.rs b/examples/web-components-slotted-pseudo-element/src/lib.rs new file mode 100644 index 000000000000..72f24199c0c4 --- /dev/null +++ b/examples/web-components-slotted-pseudo-element/src/lib.rs @@ -0,0 +1,50 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +#[wasm_bindgen(prototype=web_sys::HtmlElement)] +pub struct PersonDetails; + +#[wasm_bindgen] +impl PersonDetails { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + super(); + PersonDetails + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let template = document + .get_element_by_id("person-template").unwrap() + .unchecked_into::(); + let template_content = template.content(); + + let shadow_root = this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap(); + + let style = document.create_element("style").unwrap(); + style.set_text_content(Some(" + div { padding: 10px; border: 1px solid gray; width: 200px; margin: 10px; } + h2 { margin: 0 0 10px; } + ul { margin: 0; } + p { margin: 10px 0; } + ::slotted(*) { color: gray; font-family: sans-serif; } + ")); + + shadow_root.append_child(&style).unwrap(); + shadow_root.append_child(&template_content.clone_node_with_deep(true).unwrap()).unwrap(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + let window = web_sys::window().unwrap(); + + let custom_elements = window.custom_elements(); + custom_elements.define("person-details", &js_sys::Function::of::()).unwrap(); +} \ No newline at end of file diff --git a/examples/web-components-slotted-pseudo-element/webpack.config.js b/examples/web-components-slotted-pseudo-element/webpack.config.js new file mode 100644 index 000000000000..a42db31a6911 --- /dev/null +++ b/examples/web-components-slotted-pseudo-element/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html']), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/web-components-word-count-web-component/Cargo.toml b/examples/web-components-word-count-web-component/Cargo.toml new file mode 100644 index 000000000000..6d5e128795ea --- /dev/null +++ b/examples/web-components-word-count-web-component/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "web-components-word-count-web-component" +version = "0.1.0" +authors = ["The wasm-bindgen Developers"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" +console_error_panic_hook = "0.1.6" + +[dependencies.web-sys] +version = "0.3.28" +features = [ + 'Window', + 'Document', + 'Node', + 'Element', + 'HtmlParagraphElement', + 'CustomElementRegistry', + 'ElementDefinitionOptions', + 'ShadowRoot', + 'ShadowRootInit', + 'ShadowRootMode', +] diff --git a/examples/web-components-word-count-web-component/index.html b/examples/web-components-word-count-web-component/index.html new file mode 100755 index 000000000000..105ba952ec20 --- /dev/null +++ b/examples/web-components-word-count-web-component/index.html @@ -0,0 +1,23 @@ + + + + + Simple word count web component + + +

    Word count rating widget

    + +
    +

    Sample heading

    + +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc pulvinar sed justo sed viverra. Aliquam ac scelerisque tellus. Vivamus porttitor nunc vel nibh rutrum hendrerit. Donec viverra vestibulum pretium. Mauris at eros vitae ante pellentesque bibendum. Etiam et blandit purus, nec aliquam libero. Etiam leo felis, pulvinar et diam id, sagittis pulvinar diam. Nunc pellentesque rutrum sapien, sed faucibus urna sodales in. Sed tortor nisl, egestas nec egestas luctus, faucibus vitae purus. Ut elit nunc, pretium eget fermentum id, accumsan et velit. Sed mattis velit diam, a elementum nunc facilisis sit amet.

    + +

    Pellentesque ornare tellus sit amet massa tincidunt congue. Morbi cursus, tellus vitae pulvinar dictum, dui turpis faucibus ipsum, nec hendrerit augue nisi et enim. Curabitur felis metus, euismod et augue et, luctus dignissim metus. Mauris placerat tellus id efficitur ornare. Cras enim urna, vestibulum vel molestie vitae, mollis vitae eros. Sed lacinia scelerisque diam, a varius urna iaculis ut. Nam lacinia, velit consequat venenatis pellentesque, leo tortor porttitor est, sit amet accumsan ex lectus eget ipsum. Quisque luctus, ex ac fringilla tincidunt, risus mauris sagittis mauris, at iaculis mauris purus eget neque. Donec viverra in ex sed ullamcorper. In ac nisi vel enim accumsan feugiat et sed augue. Donec nisl metus, sollicitudin eu tempus a, scelerisque sed diam.

    + +

    +
    + + + + + diff --git a/examples/web-components-word-count-web-component/main.js b/examples/web-components-word-count-web-component/main.js new file mode 100644 index 000000000000..0e4503a2b16b --- /dev/null +++ b/examples/web-components-word-count-web-component/main.js @@ -0,0 +1,2 @@ +export default import('./pkg') + .catch(console.error); diff --git a/examples/web-components-word-count-web-component/package.json b/examples/web-components-word-count-web-component/package.json new file mode 100644 index 000000000000..2ee8794d9fc3 --- /dev/null +++ b/examples/web-components-word-count-web-component/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack", + "serve": "webpack-dev-server" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "1.0.1", + "text-encoding": "^0.7.0", + "copy-webpack-plugin": "^5.0.0", + "webpack": "^4.29.4", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/web-components-word-count-web-component/src/lib.rs b/examples/web-components-word-count-web-component/src/lib.rs new file mode 100644 index 000000000000..840c0b828df9 --- /dev/null +++ b/examples/web-components-word-count-web-component/src/lib.rs @@ -0,0 +1,75 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +// Create a class for the element +#[wasm_bindgen(prototype=web_sys::HtmlParagraphElement)] +pub struct WordCount; + +#[wasm_bindgen] +impl WordCount { + #[wasm_bindgen(constructor)] + pub fn new() -> WasmType { + let owned = instantiate! { + // Always call super first in constructor + super(); + WordCount + }; + let this = owned.borrow(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + // count words in element's parent element + let wc_parent = Box::new(this.parent_node().unwrap()); + + fn count_words(node: &web_sys::Node) -> usize { + let text = node.text_content().unwrap(); + text.split_whitespace().count() + } + + let count = format!("Words: {}", count_words(&*wc_parent)); + + // Create a shadow root + let shadow = this.attach_shadow( + &web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open) + ).unwrap(); + + // Create text node and add word count to it + let text = Box::new(document.create_element("span").unwrap()); + text.set_text_content(Some(&count)); + + // Append it to the shadow root + shadow.append_child(&*text).unwrap(); + + let wc_parent = Box::leak(wc_parent); + let text = Box::leak(text); + + let callback = Closure::wrap(Box::new(move || { + let count = format!("Words: {}", count_words(wc_parent)); + text.set_text_content(Some(&count)); + }) as Box); + + // Update count when element content changes + window.set_interval_with_callback_and_timeout_and_arguments_0( + callback.as_ref().unchecked_ref(), + 200, + ).unwrap(); + + callback.forget(); + } +} + +#[wasm_bindgen(start)] +pub fn main() { + console_error_panic_hook::set_once(); + + let mut options = web_sys::ElementDefinitionOptions::new(); + options.extends("p"); + + web_sys::window().unwrap() + .custom_elements() + .define_with_options( + "word-count", + &js_sys::Function::of::(), + &options + ).unwrap(); +} \ No newline at end of file diff --git a/examples/web-components-word-count-web-component/webpack.config.js b/examples/web-components-word-count-web-component/webpack.config.js new file mode 100644 index 000000000000..a42db31a6911 --- /dev/null +++ b/examples/web-components-word-count-web-component/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const webpack = require('webpack'); +const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin'); + +module.exports = { + entry: './main.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + }, + plugins: [ + new CopyWebpackPlugin(['index.html']), + new WasmPackPlugin({ + crateDirectory: path.resolve(__dirname, '.') + }), + // Have this example work in Edge which doesn't ship `TextEncoder` or + // `TextDecoder` at this time. + new webpack.ProvidePlugin({ + TextDecoder: ['text-encoding', 'TextDecoder'], + TextEncoder: ['text-encoding', 'TextEncoder'] + }) + ], + mode: 'development', +}; diff --git a/examples/webaudio/Cargo.toml b/examples/webaudio/Cargo.toml index 751eced81cb5..94a2bcbb3698 100644 --- a/examples/webaudio/Cargo.toml +++ b/examples/webaudio/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/webaudio/index.js b/examples/webaudio/index.js index 8b7ae8cce5a1..89b92254c915 100644 --- a/examples/webaudio/index.js +++ b/examples/webaudio/index.js @@ -11,7 +11,7 @@ import('./pkg') fm.set_fm_amount(0); fm.set_gain(0.8); } else { - fm.free(); + fm[rust_module.__wbg_free](); fm = null; } }); diff --git a/examples/webaudio/src/lib.rs b/examples/webaudio/src/lib.rs index c4f847944ef3..ed87d8ea6f0a 100644 --- a/examples/webaudio/src/lib.rs +++ b/examples/webaudio/src/lib.rs @@ -40,7 +40,29 @@ impl Drop for FmOsc { #[wasm_bindgen] impl FmOsc { #[wasm_bindgen(constructor)] - pub fn new() -> Result { + pub fn construct( + ctx: AudioContext, + primary: web_sys::OscillatorNode, + gain: web_sys::GainNode, + fm_gain: web_sys::GainNode, + fm_osc: web_sys::OscillatorNode, + fm_freq_ratio: f32, + fm_gain_ratio: f32, + ) -> WasmType { + instantiate! { + FmOsc { + ctx, + primary, + gain, + fm_gain, + fm_osc, + fm_freq_ratio, + fm_gain_ratio, + } + } + } + + pub fn new() -> Result, JsValue> { let ctx = web_sys::AudioContext::new()?; // Create our web audio objects. @@ -79,15 +101,15 @@ impl FmOsc { primary.start()?; fm_osc.start()?; - Ok(FmOsc { + Ok(FmOsc::construct( ctx, primary, gain, fm_gain, fm_osc, - fm_freq_ratio: 0.0, - fm_gain_ratio: 0.0, - }) + 0.0, + 0.0, + )) } /// Sets the gain for this oscillator, between 0.0 and 1.0. diff --git a/examples/webgl/Cargo.toml b/examples/webgl/Cargo.toml index c1909f8b9de8..3c4ff25ffb10 100644 --- a/examples/webgl/Cargo.toml +++ b/examples/webgl/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -js-sys = "0.3.28" -wasm-bindgen = "0.2.51" +js-sys = "0.3.29" +wasm-bindgen = "0.2.52" [dependencies.web-sys] version = "0.3.4" diff --git a/examples/websockets/Cargo.toml b/examples/websockets/Cargo.toml index 9aada369b3da..978571d84526 100644 --- a/examples/websockets/Cargo.toml +++ b/examples/websockets/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" [dependencies.web-sys] version = "0.3.22" diff --git a/examples/without-a-bundler/Cargo.toml b/examples/without-a-bundler/Cargo.toml index 987b5a59e9a9..a7509570f389 100644 --- a/examples/without-a-bundler/Cargo.toml +++ b/examples/without-a-bundler/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -wasm-bindgen = "0.2.51" +wasm-bindgen = "0.2.52" [dependencies.web-sys] version = "0.3.4" diff --git a/guide/src/contributing/design/exporting-rust-struct.md b/guide/src/contributing/design/exporting-rust-struct.md index 0bf5a837f102..daf322d9e64e 100644 --- a/guide/src/contributing/design/exporting-rust-struct.md +++ b/guide/src/contributing/design/exporting-rust-struct.md @@ -10,15 +10,23 @@ The `#[wasm_bindgen]` attribute can annotate both a `struct` and `impl` blocks to allow: ```rust -#[wasm_bindgen] +#[wasm_bindgen(prototype=Bar)] pub struct Foo { internal: i32, } #[wasm_bindgen] impl Foo { - pub fn new(val: i32) -> Foo { - Foo { internal: val } + #[wasm_bindgen(constructor)] + pub fn new(val: i32) -> WasmType { + instantiate! { + super("abc", 123); + Foo { internal: val } + } + } + + pub fn new_zero() -> WasmType { + Foo::new(0) } pub fn get(&self) -> i32 { @@ -33,42 +41,56 @@ impl Foo { This is a typical Rust `struct` definition for a type with a constructor and a few methods. Annotating the struct with `#[wasm_bindgen]` means that we'll -generate necessary trait impls to convert this type to/from the JS boundary. The -annotated `impl` block here means that the functions inside will also be made +inject an additional field into it and generate necessary trait impls to convert +this type (wrapped as `WasmType`, which is a vendored `Rc>`) to/from +the JS boundary. The `instantiate!` macro within a method marked as `constructor` +will inject a value for this field into the given struct literal, and evaluates +to the wrapped `WasmType`. + +The annotated `impl` block here means that the functions inside will also be made available to JS through generated shims. If we take a look at the generated JS code for this we'll see: ```js import * as wasm from './js_hello_world_bg'; -export class Foo { - static __construct(ptr) { - return new Foo(ptr); - } +const HOST_PTR = Symbol('HOST_PTR'); +const BORROW = Symbol('BORROW'); +const FREE = Symbol('FREE'); - constructor(ptr) { - this.ptr = ptr; +export class Foo extends Bar { + [BORROW](klass, mutable) { + // implementation detail omitted - see discussion below } - free() { - const ptr = this.ptr; - this.ptr = 0; - wasm.__wbg_foo_free(ptr); + [FREE]() { + // implementation detail omitted - see discussion below + } + + constructor(val) { + const _this = () => HOST_PTR in this ? this[HOST_PTR] : this[HOST_PTR] = addHeapObject(this); + const _super = (...args) => { super(...args); return _this(); } // derived classes only + try { + wasm.foo_new(val, addBorrowedObject(_super /* or `_this` in base classes */)); + } finally { + heap[stack_pointer++] = undefined; + } } - static new(arg0) { - const ret = wasm.foo_new(arg0); - return Foo.__construct(ret) + static new_zero() { + const ret = wasm.foo_new_zero(); + return takeObject(ret); } get() { - const ret = wasm.foo_get(this.ptr); + let me = this[BORROW](Foo, false); + const ret = wasm.foo_get(me); return ret; } - set(arg0) { - const ret = wasm.foo_set(this.ptr, arg0); - return ret; + set(val) { + let me = this[BORROW](Foo, true); + wasm.foo_set(me, val); } } ``` @@ -78,60 +100,194 @@ to JS: * Associated functions in Rust (those without `self`) turn into `static` functions in JS. -* Methods in Rust turn into methods in wasm. -* Manual memory management is exposed in JS as well. The `free` function is + +* Methods in Rust turn into methods in JS. + +* The method annotated as `#[wasm_bindgen(constructor)]` becomes the + JavaScript class's `constructor()`, enabling you to instantiate objects + from JavaScript using `new Foo()`. In addition to the parameters + declared on the Rust method, this constructor adds to the arguments that + are sent to Rust either (a) `_this`, a callback that returns the JavaScript + heap index of the canonical reference to the `this` object (first creating + it if necessary); or (b) `_super`, a callback that forwards its arguments + to `super()` in JS and then invokes the `_this` callback, returning the + latter's result. + +* Runtime borrows of the Rust object can be obtained via the `[BORROW]` + method; this takes a `klass` parameter in order to support inheritance, + where the desired Rust object might be some other type from the object's + prototype chain; and also a `mutable` parameter to support both mutable + (true) and immutable (false) borrows. + +* Manual memory management is exposed in JS as well. The `[FREE]` method is required to be invoked to deallocate resources on the Rust side of things. -To be able to use `new Foo()`, you'd need to annotate `new` as `#[wasm_bindgen(constructor)]`. +During object construction, the `instantiate!` macro invokes the provided +callback (in the case of `super()`, with any provided arguments), and then +injects into the provided struct literal an additional field (defined by +`wasm_bindgen` on the `struct` declaration) having the following value: + +* for base types, the callback's return value wrapped as a `JsValue`—i.e. a +canonical `JsValue` for the JavaScript shim object; -One important aspect to note here, though, is that once `free` is called the JS -object is "neutered" in that its internal pointer is nulled out. This means that -future usage of this object should trigger a panic in Rust. +* for derived types: + + * where the prototype type is imported, the callback's return value (which + in this case will again be the object's canonical JS heap index) wrapped + as the prototype type; or else + + * where the prototype type is exported, the inner value of the object at + the address in the `[WASM_PTR]` property of the JavaScript object + indicated by the callback's return value—this will necessarily be the + constructed instance of the prototype type owing to the necessarily + immediately-completed invocation of its own (super) constructor. + +Finally, the `instantiate!` macro wraps the instantiated Rust object as a +heap-allocated `WasmType`, the address of which it passes to an imported +intrinsic function for assignment to the `[WASM_PTR]` property of the +JavaScript object. + +The `[BORROW]` and `[FREE]` methods then use this `[WASM_PTR]` property when +calling into Rust in order for it to locate the underlying object. If an +attempt by `[FREE]` to free the Rust memory is successful, it nulls out the +`[WASM_PTR]` property in order to "neuter" the JS object and thereby ensure +that any future use of it will trigger a panic in Rust. + +(TODO: Use revocable proxies instead so that freed objects can be fully +disabled through revocation; currently types that derive from imported/JavaScript +types may remain partially functional and very broken. That said: the WeakMaps +TC39 proposal would negate any need to manually free these resources, instead +enabling them to be automatically freed when the JavaScript object is garbage +collected–and therefore no risk that objects could ever enter such a partially +functional very broken state). The real trickery with these bindings ends up happening in Rust, however, so let's take a look at that. ```rust // original input to `#[wasm_bindgen]` omitted ... +impl Foo { + // call an imported intrinsic function to initiate object + // construction via `new Foo()` in JavaScript, passing arguments + // as a heap-allocated slice of JsValues; intrinsic returns the + // value of the constructed object's `[WASM_PTR]` property, from + // which the WasmType is cloned and returned + pub fn new(val: i32) -> WasmType { + instantiate_via_js( Box::new([val.into()]) ) + } +} #[export_name = "foo_new"] -pub extern "C" fn __wasm_bindgen_generated_Foo_new(arg0: i32) -> u32 - let ret = Foo::new(arg0); - Box::into_raw(Box::new(WasmRefCell::new(ret))) as u32 +pub extern "C" fn __wasm_bindgen_generated_Foo_new(val: i32, __wbg_callback: u32) { + let __wbg_callback = unsafe { SuperCallback::from_abi(__wbg_callback) }; + + // expansion of instantiate! macro + { + // wrap the instantiated object in a WasmType + let wasm = WasmType::new(WasmRefCell::new(Foo { + internal : val, + + // invoke the callback and inject the result into `__proto__` field + __proto__: __wbg_callback.invoke( Box::new(["abc".into(), 123.into()]) ), + })); + + // update the JS object's `[WASM_PTR]` property + JsValue::set_wasm_pointer( + &*wasm.borrow(), + Box::into_raw(Box::new(WasmType::clone(&wasm))), + ); + + wasm + } +} + +#[no_mangle] +pub unsafe extern "C" fn __wbg_foo_borrow(ptr: u32, id: u64, mutable: bool) -> u32 { + let ptr = ptr as *mut WasmType; + assert_not_null(ptr); + + // borrow a RefMut (or Ref, per `mutable` argument) and map to + // object in prototype chain of type with matching `id`, upcast + // to Any (`get_underlying_*` functions are provided by blanket + // implementation) + if mutable { + Box::into_raw(Box::new(RefMut::map( + (**ptr).borrow_mut(), + |me| me.get_underlying_mut(id) + ))).into_abi() + } else { + Box::into_raw(Box::new(Ref::map( + (**ptr).borrow(), + |me| me.get_underlying_ref(id) + ))).into_abi() + } +} + +#[no_mangle] +pub unsafe extern "C" fn __wbindgen_foo_free(me: u32) { + let ptr = me as *mut WasmType; + assert_not_null(ptr); + + // consume the Rc and, if there are no others, ensure there are no active borrows + if let Ok(me) = WasmType::try_unwrap(*Box::from_raw(ptr)) { + me.borrow_mut(); + } +} + +impl RefFromWasmAbi for Foo { + type Abi = u32; + type Anchor = Ref<'static, Foo>; + + // downcast the received Ref to Ref + unsafe fn ref_from_abi(js: u32) -> Self::Anchor { + let ptr = js as *mut Ref<'static, dyn Any>; + assert_not_null(ptr); + Ref::map(*Box::from_raw(ptr), |val| val.downcast_ref().unwrap()) + } +} + +impl RefMutFromWasmAbi for Foo { + type Abi = u32; + type Anchor = RefMut<'static, Foo>; + + // downcast the received RefMut to RefMut + unsafe fn ref_from_abi(js: u32) -> Self::Anchor { + let ptr = js as *mut RefMut<'static, dyn Any>; + assert_not_null(ptr); + RefMut::map(*Box::from_raw(ptr), |val| val.downcast_mut().unwrap()) + } +} + +#[export_name = "foo_new_zero"] +pub extern "C" fn __wasm_bindgen_generated_Foo_new_zero() -> u32 { + let _ret = { Foo::new_zero() }; + _ret.return_abi() } #[export_name = "foo_get"] pub extern "C" fn __wasm_bindgen_generated_Foo_get(me: u32) -> i32 { - let me = me as *mut WasmRefCell; - wasm_bindgen::__rt::assert_not_null(me); - let me = unsafe { &*me }; - return me.borrow().get(); + let me = unsafe { Foo::ref_from_abi(me) }; + + // invoke underlying function + me.get() } #[export_name = "foo_set"] pub extern "C" fn __wasm_bindgen_generated_Foo_set(me: u32, arg1: i32) { - let me = me as *mut WasmRefCell; - wasm_bindgen::__rt::assert_not_null(me); - let me = unsafe { &*me }; - me.borrow_mut().set(arg1); -} + let mut me = unsafe { Foo::ref_mut_from_abi(me) }; -#[no_mangle] -pub unsafe extern "C" fn __wbindgen_foo_free(me: u32) { - let me = me as *mut WasmRefCell; - wasm_bindgen::__rt::assert_not_null(me); - (*me).borrow_mut(); // ensure no active borrows - drop(Box::from_raw(me)); + // invoke underlying function + me.set(arg1) } ``` As with before this is cleaned up from the actual output but it's the same idea -as to what's going on! Here we can see a shim for each function as well as a -shim for deallocating an instance of `Foo`. Recall that the only valid wasm -types today are numbers, so we're required to shoehorn all of `Foo` into a -`u32`, which is currently done via `Box` (like `std::unique_ptr` in C++). -Note, though, that there's an extra layer here, `WasmRefCell`. This type is the -same as [`RefCell`] and can be mostly glossed over. +as to what's going on! Here we can see a shim for each function as well as +shims for borrowing and deallocating an instance of `Foo`. Recall that the only +valid wasm types today are numbers, so we're required to shoehorn all of `Foo` +into a `u32`, which is currently done via `Box` (like `std::unique_ptr` in C++). +Note, though, that there's an extra layer here, `WasmType`. This type is the +same as [`Rc`] and can be mostly glossed over. The purpose for this type, if you're interested though, is to uphold Rust's guarantees about aliasing in a world where aliasing is rampant (JS). @@ -139,7 +295,7 @@ Specifically the `&Foo` type means that there can be as much aliasing as you'd like, but crucially `&mut Foo` means that it is the sole pointer to the data (no other `&Foo` to the same instance exists). The [`RefCell`] type in libstd is a way of dynamically enforcing this at runtime (as opposed to compile time -where it usually happens). Baking in `WasmRefCell` is the same idea here, +where it usually happens). Baking in `WasmType` is the same idea here, adding runtime checks for aliasing which are typically happening at compile time. This is currently a Rust-specific feature which isn't actually in the `wasm-bindgen` tool itself, it's just in the Rust-generated code (aka the diff --git a/guide/src/examples/web-components-composed-composed-path.md b/guide/src/examples/web-components-composed-composed-path.md new file mode 100644 index 000000000000..7ad88e6a549a --- /dev/null +++ b/guide/src/examples/web-components-composed-composed-path.md @@ -0,0 +1,16 @@ +# composed-composed-path + +Port of [MDN example](https://github.com/mdn/web-components-examples/tree/master/composed-composed-path). + +A very simple example that shows the behavior of the Event object composed and composedPath properties. + +[View full source code][code] or [view the compiled example online][online] + +[online]: https://rustwasm.github.io/wasm-bindgen/exbuild/web-components-composed-composed-path/ +[code]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/web-components-composed-composed-path + +## `src/lib.rs` + +```rust +{{#include ../../../examples/web-components-composed-composed-path/src/lib.rs}} +``` \ No newline at end of file diff --git a/guide/src/examples/web-components-defined-pseudo-class.md b/guide/src/examples/web-components-defined-pseudo-class.md new file mode 100644 index 000000000000..b536af0dc4a3 --- /dev/null +++ b/guide/src/examples/web-components-defined-pseudo-class.md @@ -0,0 +1,16 @@ +# defined-pseudo-class + +Port of [MDN example](https://github.com/mdn/web-components-examples/tree/master/defined-pseudo-class). + +A very simple example that shows how the [`:defined` pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:defined) works. + +[View full source code][code] or [view the compiled example online][online] + +[online]: https://rustwasm.github.io/wasm-bindgen/exbuild/web-components-defined-pseudo-class/ +[code]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/web-components-defined-pseudo-class + +## `src/lib.rs` + +```rust +{{#include ../../../examples/web-components-defined-pseudo-class/src/lib.rs}} +``` \ No newline at end of file diff --git a/guide/src/examples/web-components-edit-word.md b/guide/src/examples/web-components-edit-word.md new file mode 100644 index 000000000000..401d40d94c7d --- /dev/null +++ b/guide/src/examples/web-components-edit-word.md @@ -0,0 +1,16 @@ +# `` + +Port of [MDN example](https://github.com/mdn/web-components-examples/tree/master/edit-word). + +Wrapping one or more words in this element means that you can then click/focus the element to reveal a text input that can then be used to edit the word(s). + +[View full source code][code] or [view the compiled example online][online] + +[online]: https://rustwasm.github.io/wasm-bindgen/exbuild/web-components-edit-word/ +[code]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/web-components-edit-word + +## `src/lib.rs` + +```rust +{{#include ../../../examples/web-components-edit-word/src/lib.rs}} +``` \ No newline at end of file diff --git a/guide/src/examples/web-components-editable-list.md b/guide/src/examples/web-components-editable-list.md new file mode 100644 index 000000000000..d6471ee2bca8 --- /dev/null +++ b/guide/src/examples/web-components-editable-list.md @@ -0,0 +1,16 @@ +# `` + +Port of [MDN example](https://github.com/mdn/web-components-examples/tree/master/editable-list). + +A simple example showing how elements can be consolidated to create a list with addable/removable items. Items are added by using a `list-item` attribute or by entering text and clicking the plus sign. + +[View full source code][code] or [view the compiled example online][online] + +[online]: https://rustwasm.github.io/wasm-bindgen/exbuild/web-components-editable-list/ +[code]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/web-components-editable-list + +## `src/lib.rs` + +```rust +{{#include ../../../examples/web-components-editable-list/src/lib.rs}} +``` \ No newline at end of file diff --git a/guide/src/examples/web-components-element-details.md b/guide/src/examples/web-components-element-details.md new file mode 100644 index 000000000000..65bf3ab5009b --- /dev/null +++ b/guide/src/examples/web-components-element-details.md @@ -0,0 +1,16 @@ +# `` + +Port of [MDN example](https://github.com/mdn/web-components-examples/tree/master/element-details). + +Displays a box containing an HTML element name and description. Provides an example of an autonomous custom element that gets its structure from a `