mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-28 04:22:38 +03:00
Implement support for WebIDL Callback
types
This commit adds support for the WebIDL `Callback` type by translating all callbacks to the `js_sys::Function` type. This will enable passing raw JS values into callbacks as well as Rust valus using the `Closure` type. This commit doesn't currently implement "callback interfaces" in WebIDL, that's left for a follow-up commit.
This commit is contained in:
parent
1cd2229c66
commit
457efc0f31
@ -7,5 +7,5 @@ fn take_and_return_a_bunch_of_slices() {
|
||||
let f = ArrayBufferTest::new().unwrap();
|
||||
let x = f.get_buffer();
|
||||
f.set_buffer(None);
|
||||
f.set_buffer(Some(x));
|
||||
f.set_buffer(Some(&x));
|
||||
}
|
||||
|
@ -146,3 +146,13 @@ global.MixinFoo = class MixinFoo {
|
||||
global.Overloads = class {
|
||||
foo() {}
|
||||
};
|
||||
|
||||
global.InvokeCallback = class {
|
||||
invoke(f) { f(); }
|
||||
callAdd(f) {
|
||||
return f(1, 2);
|
||||
}
|
||||
callRepeat(f) {
|
||||
return f('ab', 4);
|
||||
}
|
||||
};
|
||||
|
@ -1,4 +1,6 @@
|
||||
use wasm_bindgen_test::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/simple.rs"));
|
||||
|
||||
@ -124,3 +126,26 @@ fn overload_naming() {
|
||||
o.foo_with_arg_and_f32("x", 2.0);
|
||||
o.foo_with_arg_and_i16("x", 5);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn callback() {
|
||||
let o = InvokeCallback::new().unwrap();
|
||||
{
|
||||
static mut HIT: bool = false;
|
||||
let cb = Closure::wrap(Box::new(move || {
|
||||
unsafe { HIT = true; }
|
||||
}) as Box<FnMut()>);
|
||||
o.invoke(cb.as_ref().unchecked_ref());
|
||||
assert!(unsafe { HIT });
|
||||
}
|
||||
|
||||
let cb = Closure::wrap(Box::new(move |a, b| {
|
||||
a + b
|
||||
}) as Box<FnMut(u32, u32) -> u32>);
|
||||
assert_eq!(o.call_add(cb.as_ref().unchecked_ref()), 3);
|
||||
|
||||
let cb = Closure::wrap(Box::new(move |a: String, b| {
|
||||
a.repeat(b)
|
||||
}) as Box<FnMut(String, usize) -> String>);
|
||||
assert_eq!(o.call_repeat(cb.as_ref().unchecked_ref()), "abababab");
|
||||
}
|
||||
|
12
crates/webidl-tests/simple.webidl
vendored
12
crates/webidl-tests/simple.webidl
vendored
@ -95,3 +95,15 @@ interface Overloads {
|
||||
void foo(DOMString arg, optional long a);
|
||||
void foo(DOMString arg, (float or short) b);
|
||||
};
|
||||
|
||||
callback MyCallback = any();
|
||||
callback AddCallback = long(long a, long b);
|
||||
callback RepeatCallback = DOMString(DOMString a, long cnt);
|
||||
callback GetAnswer = long();
|
||||
|
||||
[Constructor()]
|
||||
interface InvokeCallback {
|
||||
void invoke(MyCallback callback);
|
||||
long callAdd(AddCallback callback);
|
||||
DOMString callRepeat(RepeatCallback callback);
|
||||
};
|
||||
|
@ -34,6 +34,7 @@ pub(crate) struct FirstPassRecord<'src> {
|
||||
pub(crate) namespaces: BTreeMap<&'src str, NamespaceData<'src>>,
|
||||
pub(crate) includes: BTreeMap<&'src str, BTreeSet<&'src str>>,
|
||||
pub(crate) dictionaries: BTreeMap<&'src str, DictionaryData<'src>>,
|
||||
pub(crate) callbacks: BTreeSet<&'src str>,
|
||||
}
|
||||
|
||||
/// We need to collect interface data during the first pass, to be used later.
|
||||
@ -135,12 +136,9 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> {
|
||||
Namespace(namespace) => namespace.first_pass(record, ()),
|
||||
PartialNamespace(namespace) => namespace.first_pass(record, ()),
|
||||
Typedef(typedef) => typedef.first_pass(record, ()),
|
||||
Callback(callback) => callback.first_pass(record, ()),
|
||||
|
||||
Implements(_) => Ok(()),
|
||||
Callback(..) => {
|
||||
warn!("Unsupported WebIDL Callback definition: {:?}", self);
|
||||
Ok(())
|
||||
}
|
||||
CallbackInterface(..) => {
|
||||
warn!("Unsupported WebIDL CallbackInterface definition: {:?}", self);
|
||||
Ok(())
|
||||
@ -685,6 +683,13 @@ impl<'src> FirstPass<'src, &'src str> for weedle::namespace::OperationNamespaceM
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> FirstPass<'src, ()> for weedle::CallbackDefinition<'src> {
|
||||
fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, _: ()) -> Result<()> {
|
||||
record.callbacks.insert(self.identifier.0);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FirstPassRecord<'a> {
|
||||
pub fn all_superclasses<'me>(&'me self, interface: &str)
|
||||
-> impl Iterator<Item = String> + 'me
|
||||
|
@ -28,6 +28,7 @@ pub(crate) enum IdlType<'a> {
|
||||
Object,
|
||||
Symbol,
|
||||
Error,
|
||||
Callback,
|
||||
|
||||
ArrayBuffer,
|
||||
DataView,
|
||||
@ -293,6 +294,8 @@ impl<'a> ToIdlType<'a> for Identifier<'a> {
|
||||
Some(IdlType::Dictionary(self.0))
|
||||
} else if record.enums.contains_key(self.0) {
|
||||
Some(IdlType::Enum(self.0))
|
||||
} else if record.callbacks.contains(self.0) {
|
||||
Some(IdlType::Callback)
|
||||
} else {
|
||||
warn!("Unrecognized type: {}", self.0);
|
||||
None
|
||||
@ -364,6 +367,7 @@ impl<'a> IdlType<'a> {
|
||||
IdlType::Object => dst.push_str("object"),
|
||||
IdlType::Symbol => dst.push_str("symbol"),
|
||||
IdlType::Error => dst.push_str("error"),
|
||||
IdlType::Callback => dst.push_str("callback"),
|
||||
|
||||
IdlType::ArrayBuffer => dst.push_str("array_buffer"),
|
||||
IdlType::DataView => dst.push_str("data_view"),
|
||||
@ -426,6 +430,17 @@ impl<'a> IdlType<'a> {
|
||||
|
||||
/// Converts to syn type if possible.
|
||||
pub(crate) fn to_syn_type(&self, pos: TypePosition) -> Option<syn::Type> {
|
||||
let anyref = |ty| {
|
||||
Some(match pos {
|
||||
TypePosition::Argument => shared_ref(ty, false),
|
||||
TypePosition::Return => ty,
|
||||
})
|
||||
};
|
||||
let js_sys = |name: &str| {
|
||||
let path = vec![rust_ident("js_sys"), rust_ident(name)];
|
||||
let ty = leading_colon_path_ty(path);
|
||||
anyref(ty)
|
||||
};
|
||||
match self {
|
||||
IdlType::Boolean => Some(ident_ty(raw_ident("bool"))),
|
||||
IdlType::Byte => Some(ident_ty(raw_ident("i8"))),
|
||||
@ -446,21 +461,11 @@ impl<'a> IdlType<'a> {
|
||||
TypePosition::Argument => Some(shared_ref(ident_ty(raw_ident("str")), false)),
|
||||
TypePosition::Return => Some(ident_ty(raw_ident("String"))),
|
||||
},
|
||||
IdlType::Object => {
|
||||
let path = vec![rust_ident("js_sys"), rust_ident("Object")];
|
||||
let ty = leading_colon_path_ty(path);
|
||||
Some(match pos {
|
||||
TypePosition::Argument => shared_ref(ty, false),
|
||||
TypePosition::Return => ty,
|
||||
})
|
||||
},
|
||||
IdlType::Object => js_sys("Object"),
|
||||
IdlType::Symbol => None,
|
||||
IdlType::Error => None,
|
||||
|
||||
IdlType::ArrayBuffer => {
|
||||
let path = vec![rust_ident("js_sys"), rust_ident("ArrayBuffer")];
|
||||
Some(leading_colon_path_ty(path))
|
||||
},
|
||||
IdlType::ArrayBuffer => js_sys("ArrayBuffer"),
|
||||
IdlType::DataView => None,
|
||||
IdlType::Int8Array => Some(array("i8", pos, false)),
|
||||
IdlType::Uint8Array => Some(array("u8", pos, false)),
|
||||
@ -473,14 +478,7 @@ impl<'a> IdlType<'a> {
|
||||
IdlType::Float32Array => Some(array("f32", pos, false)),
|
||||
IdlType::Float64Array => Some(array("f64", pos, false)),
|
||||
|
||||
IdlType::ArrayBufferView | IdlType::BufferSource => {
|
||||
let path = vec![rust_ident("js_sys"), rust_ident("Object")];
|
||||
let ty = leading_colon_path_ty(path);
|
||||
Some(match pos {
|
||||
TypePosition::Argument => shared_ref(ty, false),
|
||||
TypePosition::Return => ty,
|
||||
})
|
||||
},
|
||||
IdlType::ArrayBufferView | IdlType::BufferSource => js_sys("Object"),
|
||||
IdlType::Interface(name)
|
||||
| IdlType::Dictionary(name) => {
|
||||
let ty = ident_ty(rust_ident(camel_case_ident(name).as_str()));
|
||||
@ -495,15 +493,7 @@ impl<'a> IdlType<'a> {
|
||||
IdlType::Nullable(idl_type) => Some(option_ty(idl_type.to_syn_type(pos)?)),
|
||||
IdlType::FrozenArray(_idl_type) => None,
|
||||
IdlType::Sequence(_idl_type) => None,
|
||||
IdlType::Promise(_idl_type) => {
|
||||
let path = vec![rust_ident("js_sys"), rust_ident("Promise")];
|
||||
let ty = leading_colon_path_ty(path);
|
||||
if pos == TypePosition::Argument {
|
||||
Some(shared_ref(ty, false))
|
||||
} else {
|
||||
Some(ty)
|
||||
}
|
||||
}
|
||||
IdlType::Promise(_idl_type) => js_sys("Promise"),
|
||||
IdlType::Record(_idl_type_from, _idl_type_to) => None,
|
||||
IdlType::Union(idl_types) => {
|
||||
// Handles union types in all places except operation argument types.
|
||||
@ -529,9 +519,10 @@ impl<'a> IdlType<'a> {
|
||||
|
||||
IdlType::Any => {
|
||||
let path = vec![rust_ident("wasm_bindgen"), rust_ident("JsValue")];
|
||||
Some(leading_colon_path_ty(path))
|
||||
anyref(leading_colon_path_ty(path))
|
||||
},
|
||||
IdlType::Void => None,
|
||||
IdlType::Callback => js_sys("Function"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +138,7 @@ fn builtin_idents() -> BTreeSet<Ident> {
|
||||
vec![
|
||||
"str", "char", "bool", "JsValue", "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64",
|
||||
"usize", "isize", "f32", "f64", "Result", "String", "Vec", "Option",
|
||||
"ArrayBuffer", "Object", "Promise",
|
||||
"ArrayBuffer", "Object", "Promise", "Function",
|
||||
].into_iter()
|
||||
.map(|id| proc_macro2::Ident::new(id, proc_macro2::Span::call_site())),
|
||||
)
|
||||
|
@ -5,4 +5,99 @@ source lives at `wasm-bindgen/crates/web-sys`.
|
||||
|
||||
The `web-sys` crate is **entirely** mechanically generated inside `build.rs`
|
||||
using `wasm-bindgen`'s WebIDL frontend and the WebIDL interface definitions for
|
||||
Web APIs.
|
||||
Web APIs. This means that `web-sys` isn't always the most ergonomic crate to
|
||||
use, but it's intended to provide verified and correct bindings to the web
|
||||
platform, and then better interfaces can be iterated on crates.io!
|
||||
|
||||
### Using `web-sys`
|
||||
|
||||
Let's say you want to use an API defined on the web. Chances are this API is
|
||||
defined in `web-sys`, so let's go through some steps necessary to use it!
|
||||
|
||||
First up, search the [api documentation][api] for your API. For example if
|
||||
we're looking for JS's [`fetch`][jsfetch] API we'd start out by [searching for
|
||||
`fetch`][search-fetch]. The first thing you'll probably notice is that there's
|
||||
no function called `fetch`! Fear not, though, as the API exists in multiple
|
||||
forms:
|
||||
|
||||
* [`Window::fetch_with_str`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Window.html#method.fetch_with_str)
|
||||
* [`Window::fetch_with_request`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Window.html#method.fetch_with_request)
|
||||
* [`Window::fetch_with_str_and_init`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/str_and_inituct.Window.html#method.fetch_with_str_and_init)
|
||||
* [`Window::fetch_with_request_and_init`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Window.html#method.fetch_with_request_and_init)
|
||||
|
||||
What's happening here is that the [`fetch` function][fetchfn] actually supports
|
||||
multiple signatures of arguments, and we've taken the WebIDL definition for this
|
||||
function and expanded it to unique signatures in Rust (as Rust doesn't have
|
||||
function name overloading).
|
||||
|
||||
When an API is selected it should have documentation pointing at MDN indicating
|
||||
what web API its binding. This is often a great way to double check arguments
|
||||
and such as well, MDN is a great resource! You'll also notice in the
|
||||
documentation that the API may require some `web-sys` Cargo features to be
|
||||
activated. For example [`fetch_with_str`] requires the `Window` feature to be
|
||||
activated. In general an API needs features corresponding to all the types
|
||||
you'll find in the signature to be activated.
|
||||
|
||||
To load up this API let's depend on `web-sys`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.1", features = ['Window'] }
|
||||
|
||||
# Or optionally,
|
||||
# [target.wasm32-unknown-unknown.dependencies]
|
||||
# ...
|
||||
```
|
||||
|
||||
> **Note**: Currently `web-sys` is not available on crates.io so you'll also
|
||||
> need to do this in your manifest:
|
||||
>
|
||||
> ```toml
|
||||
> [patch.crates-io]
|
||||
> web-sys = { git = 'https://github.com/rustwasm/wasm-bindgen' }
|
||||
> wasm-bindgen = { git = 'https://github.com/rustwasm/wasm-bindgen' }
|
||||
> ```
|
||||
|
||||
And next up we can use the API like so:
|
||||
|
||||
```rust
|
||||
extern crate web_sys;
|
||||
extern crate wasm_bindgen;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::Window;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn run() {
|
||||
let promise = Window::fetch_with_str("http://example.com/");
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
and you should be good to go!
|
||||
|
||||
### Type translations in `web-sys`
|
||||
|
||||
Most of the types specified in WebIDL have relatively straightforward
|
||||
translations into `web-sys`, but it's worth calling out a few in particular:
|
||||
|
||||
* `BufferSource` and `ArrayBufferView` - these two types show up in a number of
|
||||
APIs that generally deal with a buffer of bytes. We bind them in `web-sys`
|
||||
with two different types, `Object` and `&mut [u8]`. Using `Object` allows
|
||||
passing in arbitrary JS values which represent a view of bytes (like any typed
|
||||
array object), and `&mut [u8]` allows using a raw slice in Rust. Unfortunately
|
||||
we must pessimistically assume that JS will modify all slices as we don't
|
||||
currently have information of whether they're modified or not.
|
||||
|
||||
* Callbacks are all represented as `js_sys::Function`. This means that all
|
||||
callbacks going through `web-sys` are a raw JS value. You can work with this
|
||||
by either juggling actual `js_sys::Function` instances or you can create a
|
||||
`Closure<FnMut(...)>`, extract the underlying `JsValue` with `as_ref`, and
|
||||
then use `JsCast::unchecked_ref` to convert it to a `js_sys::Function`.
|
||||
|
||||
[api]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/
|
||||
[jsfetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
|
||||
[search-fetch]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/?search=fetch
|
||||
[fetchfn]: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
|
||||
[`fetch_with_str`]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Window.html#method.fetch_with_str
|
||||
|
@ -191,7 +191,7 @@ impl<T> Closure<T>
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<JsValue> for Closure<T> {
|
||||
impl<T: ?Sized> AsRef<JsValue> for Closure<T> {
|
||||
fn as_ref(&self) -> &JsValue {
|
||||
&self.js
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user