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
This commit is contained in:
Alex Crichton 2018-11-08 10:58:55 -08:00
parent d646b29bb7
commit 5b76a6291e
10 changed files with 146 additions and 81 deletions

View File

@ -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 = <JsValue as RefFromWasmAbi>::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 = <JsValue as RefFromWasmAbi>::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 {

View File

@ -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::<web_sys::XmlHttpRequestUpload>();
let y: &web_sys::XmlHttpRequestEventTarget = &x;
drop(y);
}

View File

@ -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<Item = String> + '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<String>) {
fn fill_superclasses(
&self,
interface: &str,
set: &mut BTreeSet<&'a str>,
list: &mut Vec<String>,
) {
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);
}
}
}

View File

@ -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::<web_sys::Node>::as_ref(&body).append_child(val.as_ref())?;
body.append_child(&val)?;
Ok(())
}

View File

@ -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::<web_sys::HtmlCanvasElement>()
.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::<web_sys::HtmlCanvasElement>()?;
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::<web_sys::CanvasRenderingContext2d>()
.unwrap();
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
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<FnMut(_)>);
(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<FnMut(_)>);
(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<FnMut(_)>);
(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(())
}

View File

@ -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(())
}
}

View File

@ -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()?;

View File

@ -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::<web_sys::HtmlCanvasElement>()
.map_err(|_| ())
.unwrap();
.dyn_into::<web_sys::HtmlCanvasElement>()?;
let context = canvas
.get_context("webgl")
.get_context("webgl")?
.unwrap()
.unwrap()
.dyn_into::<WebGlRenderingContext>()
.unwrap();
.dyn_into::<WebGlRenderingContext>()?;
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::<WebAssembly::Memory>().unwrap().buffer();
let memory_buffer = wasm_bindgen::memory().dyn_into::<WebAssembly::Memory>()?.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(

View File

@ -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)
--------------------------------------------------------------------------------

View File

@ -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<HtmlElement> for HtmlAnchorElement
impl AsRef<Element> for HtmlAnchorElement
impl AsRef<Node> for HtmlAnchorElement
impl AsRef<EventTarget> for HtmlAnchorElement
impl AsRef<Object> for HtmlAnchorElement
impl AsRef<JsValue> 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