From 5b76a6291e5083b24f96b96a4939f18ec9d9d4df Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Nov 2018 10:58:55 -0800 Subject: [PATCH] Implement `Deref` for all imported JS types This commit implements the first half of [RFC #5] where the `Deref` trait is implemented for all imported types. The target of `Deref` is either the first entry of the list of `extends` attribute or `JsValue`. All examples using `.as_ref()` with various `web-sys` types have been updated to the more ergonomic deref casts now. Additionally the `web-sys` generation of the `extends` array has been fixed slightly to explicitly list implementatoins in the hierarchy order to ensure the correct target for `Deref` is chosen. [RFC #5]: https://github.com/rustwasm/rfcs/blob/master/text/005-structural-and-deref.md --- crates/backend/src/codegen.rs | 22 +++++++++-- crates/web-sys/tests/wasm/main.rs | 11 +++++- crates/webidl/src/first_pass.rs | 17 ++++++--- examples/dom/src/lib.rs | 5 +-- examples/paint/src/lib.rs | 47 ++++++++++------------- examples/raytrace-parallel/src/lib.rs | 6 +-- examples/webaudio/src/lib.rs | 38 +++++++----------- examples/webgl/src/lib.rs | 25 ++++++------ guide/src/SUMMARY.md | 1 + guide/src/web-sys/inheritance.md | 55 +++++++++++++++++++++++++++ 10 files changed, 146 insertions(+), 81 deletions(-) create mode 100644 guide/src/web-sys/inheritance.md diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 3df58c40..b812ce24 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -535,7 +535,7 @@ impl ToTokens for ast::ImportType { use wasm_bindgen::convert::RefFromWasmAbi; use wasm_bindgen::describe::WasmDescribe; use wasm_bindgen::{JsValue, JsCast}; - use wasm_bindgen::__rt::core::mem::ManuallyDrop; + use wasm_bindgen::__rt::core; impl WasmDescribe for #rust_name { fn describe() { @@ -589,13 +589,13 @@ impl ToTokens for ast::ImportType { impl RefFromWasmAbi for #rust_name { type Abi = ::Abi; - type Anchor = ManuallyDrop<#rust_name>; + type Anchor = core::mem::ManuallyDrop<#rust_name>; #[inline] unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor { let tmp = ::ref_from_abi(js, extra); - ManuallyDrop::new(#rust_name { - obj: ManuallyDrop::into_inner(tmp), + core::mem::ManuallyDrop::new(#rust_name { + obj: core::mem::ManuallyDrop::into_inner(tmp), }) } } @@ -657,6 +657,20 @@ impl ToTokens for ast::ImportType { }; }).to_tokens(tokens); + let deref_target = match self.extends.first() { + Some(target) => quote! { #target }, + None => quote! { JsValue }, + }; + (quote! { + impl core::ops::Deref for #rust_name { + type Target = #deref_target; + + #[inline] + fn deref(&self) -> &#deref_target { + self.as_ref() + } + } + }).to_tokens(tokens); for superclass in self.extends.iter() { (quote! { impl From<#rust_name> for #superclass { diff --git a/crates/web-sys/tests/wasm/main.rs b/crates/web-sys/tests/wasm/main.rs index 71d88fff..5d72af04 100644 --- a/crates/web-sys/tests/wasm/main.rs +++ b/crates/web-sys/tests/wasm/main.rs @@ -7,7 +7,8 @@ extern crate wasm_bindgen_futures; extern crate wasm_bindgen_test; extern crate web_sys; -use wasm_bindgen_test::wasm_bindgen_test_configure; +use wasm_bindgen::{JsValue, JsCast}; +use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); @@ -56,3 +57,11 @@ pub mod table_element; pub mod title_element; pub mod xpath_result; pub mod indexeddb; + +#[wasm_bindgen_test] +fn deref_works() { + let x = JsValue::from(3); + let x = x.unchecked_into::(); + let y: &web_sys::XmlHttpRequestEventTarget = &x; + drop(y); +} diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 0fc18c3a..663c726f 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -735,11 +735,17 @@ impl<'src> FirstPass<'src, ()> for weedle::CallbackInterfaceDefinition<'src> { impl<'a> FirstPassRecord<'a> { pub fn all_superclasses<'me>(&'me self, interface: &str) -> impl Iterator + 'me { let mut set = BTreeSet::new(); - self.fill_superclasses(interface, &mut set); - set.into_iter() + let mut list = Vec::new(); + self.fill_superclasses(interface, &mut set, &mut list); + list.into_iter() } - fn fill_superclasses(&self, interface: &str, set: &mut BTreeSet) { + fn fill_superclasses( + &self, + interface: &str, + set: &mut BTreeSet<&'a str>, + list: &mut Vec, + ) { let data = match self.interfaces.get(interface) { Some(data) => data, None => return, @@ -749,8 +755,9 @@ impl<'a> FirstPassRecord<'a> { None => return, }; if self.interfaces.contains_key(superclass) { - if set.insert(camel_case_ident(superclass)) { - self.fill_superclasses(superclass, set); + if set.insert(superclass) { + list.push(camel_case_ident(superclass)); + self.fill_superclasses(superclass, set, list); } } } diff --git a/examples/dom/src/lib.rs b/examples/dom/src/lib.rs index acf796f8..a30af3bc 100644 --- a/examples/dom/src/lib.rs +++ b/examples/dom/src/lib.rs @@ -16,10 +16,7 @@ pub fn run() -> Result<(), JsValue> { let val = document.create_element("p")?; val.set_inner_html("Hello from Rust!"); - // Right now the class inheritance hierarchy of the DOM isn't super - // ergonomic, so we manually cast `val: Element` to `&Node` to call the - // `append_child` method. - AsRef::::as_ref(&body).append_child(val.as_ref())?; + body.append_child(&val)?; Ok(()) } diff --git a/examples/paint/src/lib.rs b/examples/paint/src/lib.rs index b842bac8..67910de9 100644 --- a/examples/paint/src/lib.rs +++ b/examples/paint/src/lib.rs @@ -8,29 +8,19 @@ use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; #[wasm_bindgen] -pub fn main() { +pub fn main() -> Result<(), JsValue> { let document = web_sys::window().unwrap().document().unwrap(); let canvas = document - .create_element("canvas") - .unwrap() - .dyn_into::() - .map_err(|_| ()) - .unwrap(); - (document.body().unwrap().as_ref() as &web_sys::Node) - .append_child(canvas.as_ref() as &web_sys::Node) - .unwrap(); + .create_element("canvas")? + .dyn_into::()?; + document.body().unwrap().append_child(&canvas)?; canvas.set_width(640); canvas.set_height(480); - (canvas.as_ref() as &web_sys::HtmlElement) - .style() - .set_property("border", "solid") - .unwrap(); + canvas.style().set_property("border", "solid")?; let context = canvas - .get_context("2d") + .get_context("2d")? .unwrap() - .unwrap() - .dyn_into::() - .unwrap(); + .dyn_into::()?; let context = Rc::new(context); let pressed = Rc::new(Cell::new(false)); { @@ -41,9 +31,10 @@ pub fn main() { context.move_to(event.offset_x() as f64, event.offset_y() as f64); pressed.set(true); }) as Box); - (canvas.as_ref() as &web_sys::EventTarget) - .add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref()) - .unwrap(); + canvas.add_event_listener_with_callback( + "mousedown", + closure.as_ref().unchecked_ref(), + )?; closure.forget(); } { @@ -57,9 +48,10 @@ pub fn main() { context.move_to(event.offset_x() as f64, event.offset_y() as f64); } }) as Box); - (canvas.as_ref() as &web_sys::EventTarget) - .add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref()) - .unwrap(); + canvas.add_event_listener_with_callback( + "mousemove", + closure.as_ref().unchecked_ref(), + )?; closure.forget(); } { @@ -70,9 +62,12 @@ pub fn main() { context.line_to(event.offset_x() as f64, event.offset_y() as f64); context.stroke(); }) as Box); - (canvas.as_ref() as &web_sys::EventTarget) - .add_event_listener_with_callback("mouseup", closure.as_ref().unchecked_ref()) - .unwrap(); + canvas.add_event_listener_with_callback( + "mouseup", + closure.as_ref().unchecked_ref(), + )?; closure.forget(); } + + Ok(()) } diff --git a/examples/raytrace-parallel/src/lib.rs b/examples/raytrace-parallel/src/lib.rs index c8d4a3ff..db4ca103 100644 --- a/examples/raytrace-parallel/src/lib.rs +++ b/examples/raytrace-parallel/src/lib.rs @@ -139,7 +139,7 @@ impl WorkerPool { // thread? // * Need to handle the `?` on `post_message` as well. array.push(&wasm_bindgen::memory()); - worker.post_message(array.as_ref())?; + worker.post_message(&array)?; worker.set_onmessage(Some(callback.as_ref().unchecked_ref())); worker.set_onerror(Some(callback.as_ref().unchecked_ref())); workers.push(worker); @@ -355,10 +355,10 @@ impl Shared { self.scene.height as f64, )?; let arr = Array::new(); - arr.push(data.as_ref()); + arr.push(&data); arr.push(&JsValue::from(done)); arr.push(&JsValue::from(self.id as f64)); - global.post_message(arr.as_ref())?; + global.post_message(&arr)?; Ok(()) } } diff --git a/examples/webaudio/src/lib.rs b/examples/webaudio/src/lib.rs index e518ce7e..fa283192 100644 --- a/examples/webaudio/src/lib.rs +++ b/examples/webaudio/src/lib.rs @@ -2,7 +2,7 @@ extern crate wasm_bindgen; extern crate web_sys; use wasm_bindgen::prelude::*; -use web_sys::{AudioContext, AudioNode, OscillatorType}; +use web_sys::{AudioContext, OscillatorType}; /// Converts a midi note to frequency /// @@ -60,34 +60,24 @@ impl FmOsc { fm_osc.set_type(OscillatorType::Sine); fm_osc.frequency().set_value(0.0); - // Create base class references: - { - let primary_node: &AudioNode = primary.as_ref(); - let gain_node: &AudioNode = gain.as_ref(); - let fm_osc_node: &AudioNode = fm_osc.as_ref(); - let fm_gain_node: &AudioNode = fm_gain.as_ref(); - let destination = ctx.destination(); - let destination_node: &AudioNode = destination.as_ref(); + // Connect the nodes up! - // Connect the nodes up! + // The primary oscillator is routed through the gain node, so that + // it can control the overall output volume. + primary.connect_with_audio_node(&gain)?; - // The primary oscillator is routed through the gain node, so that - // it can control the overall output volume. - primary_node.connect_with_audio_node(gain.as_ref())?; + // Then connect the gain node to the AudioContext destination (aka + // your speakers). + gain.connect_with_audio_node(&ctx.destination())?; - // Then connect the gain node to the AudioContext destination (aka - // your speakers). - gain_node.connect_with_audio_node(destination_node)?; - - // The FM oscillator is connected to its own gain node, so it can - // control the amount of modulation. - fm_osc_node.connect_with_audio_node(fm_gain.as_ref())?; + // The FM oscillator is connected to its own gain node, so it can + // control the amount of modulation. + fm_osc.connect_with_audio_node(&fm_gain)?; - // Connect the FM oscillator to the frequency parameter of the main - // oscillator, so that the FM node can modulate its frequency. - fm_gain_node.connect_with_audio_param(&primary.frequency())?; - } + // Connect the FM oscillator to the frequency parameter of the main + // oscillator, so that the FM node can modulate its frequency. + fm_gain.connect_with_audio_param(&primary.frequency())?; // Start the oscillators! primary.start()?; diff --git a/examples/webgl/src/lib.rs b/examples/webgl/src/lib.rs index 5e338fa2..f65b8d59 100644 --- a/examples/webgl/src/lib.rs +++ b/examples/webgl/src/lib.rs @@ -8,20 +8,16 @@ use web_sys::{WebGlProgram, WebGlRenderingContext, WebGlShader}; use js_sys::{WebAssembly}; #[wasm_bindgen] -pub fn draw() { +pub fn draw() -> Result<(), JsValue> { let document = web_sys::window().unwrap().document().unwrap(); let canvas = document.get_element_by_id("canvas").unwrap(); let canvas: web_sys::HtmlCanvasElement = canvas - .dyn_into::() - .map_err(|_| ()) - .unwrap(); + .dyn_into::()?; let context = canvas - .get_context("webgl") + .get_context("webgl")? .unwrap() - .unwrap() - .dyn_into::() - .unwrap(); + .dyn_into::()?; let vert_shader = compile_shader( &context, @@ -32,7 +28,7 @@ pub fn draw() { gl_Position = position; } "#, - ).unwrap(); + )?; let frag_shader = compile_shader( &context, WebGlRenderingContext::FRAGMENT_SHADER, @@ -41,23 +37,23 @@ pub fn draw() { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); } "#, - ).unwrap(); - let program = link_program(&context, [vert_shader, frag_shader].iter()).unwrap(); + )?; + let program = link_program(&context, [vert_shader, frag_shader].iter())?; context.use_program(Some(&program)); let vertices: [f32; 9] = [-0.7, -0.7, 0.0, 0.7, -0.7, 0.0, 0.0, 0.7, 0.0]; - let memory_buffer = wasm_bindgen::memory().dyn_into::().unwrap().buffer(); + let memory_buffer = wasm_bindgen::memory().dyn_into::()?.buffer(); let vertices_location = vertices.as_ptr() as u32 / 4; let vert_array = js_sys::Float32Array::new(&memory_buffer).subarray( vertices_location, vertices_location + vertices.len() as u32, ); - let buffer = context.create_buffer().unwrap(); + let buffer = context.create_buffer().ok_or("failed to create buffer")?; context.bind_buffer(WebGlRenderingContext::ARRAY_BUFFER, Some(&buffer)); context.buffer_data_with_array_buffer_view( WebGlRenderingContext::ARRAY_BUFFER, - vert_array.as_ref(), + &vert_array, WebGlRenderingContext::STATIC_DRAW, ); context.vertex_attrib_pointer_with_i32(0, 3, WebGlRenderingContext::FLOAT, false, 0, 0); @@ -71,6 +67,7 @@ pub fn draw() { 0, (vertices.len() / 3) as i32, ); + Ok(()) } pub fn compile_shader( diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 1faa7077..47f9ec9b 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -83,6 +83,7 @@ - [Cargo Features](./web-sys/cargo-features.md) - [Function Overloads](./web-sys/function-overloads.md) - [Type Translations](./web-sys/type-translations.md) + - [Inheritance](./web-sys/inheritance.md) -------------------------------------------------------------------------------- diff --git a/guide/src/web-sys/inheritance.md b/guide/src/web-sys/inheritance.md new file mode 100644 index 00000000..211117f3 --- /dev/null +++ b/guide/src/web-sys/inheritance.md @@ -0,0 +1,55 @@ +# Inheritance in `web-sys` + +Inheritance between JS classes is the bread and butter of how the DOM works on +the web, and as a result it's quite important for `web-sys` to provide access to +this inheritance hierarchy as well! There are few ways you can access the +inheritance hierarchy when using `web-sys`. + +### Accessing parent classes using `Deref` + +Like smart pointers in Rust, all types in `web_sys` implement `Deref` to their +parent JS class. This means, for example, if you have a `web_sys::Element` you +can create a `web_sys::Node` from that implicitly: + +```rust +let element: &Element = ...; + +element.append_child(..); // call a method on `Node` + +method_expecting_a_node(&element); // coerce to `&Node` implicitly + +let node: &Node = &element; // explicitly coerce to `&Node` +``` + +Using `Deref` allows ergonomic transitioning up the inheritance hierarchy to the +parent class and beyond, giving you access to all the methods using the `.` +operator. + +### Accessing parent classes using `AsRef` + +In addition to `Deref`, the `AsRef` trait is implemented for all types in +`web_sys` for all types in the inheritance hierarchy. For example for the +`HtmlAnchorElement` type you'll find: + +```rust +impl AsRef for HtmlAnchorElement +impl AsRef for HtmlAnchorElement +impl AsRef for HtmlAnchorElement +impl AsRef for HtmlAnchorElement +impl AsRef for HtmlAnchorElement +impl AsRef for HtmlAnchorElement +``` + +You can use `.as_ref()` to explicitly get a reference to any parent class from +from a type in `web_sys`. Note that because of the number of `AsRef` +implementations you'll likely need to have type inference guidance as well. + +### Accessing child clases using `JsCast` + +Finally the `wasm_bindgen::JsCast` trait can be used to implement all manner of +casts between types. It supports static unchecked casts between types as well as +dynamic runtime-checked casts (using `instanceof`) between types. + +More documentation about this can be found [on the trait itself][jscast] + +[jscast]: https://docs.rs/wasm-bindgen/0.2/wasm_bindgen/trait.JsCast.html