# js_ffi
**this library creates a bridge to javascript in the browser at runtime using web assembly**
A foreign function interface(FFI) library for invoking Javascript functions from Web Assembly for many programming languages
- [x] no code generation or special cargo components
- [x] support for callbacks (e.g. `setTimeout`)
- [x] futures based on callbacks
- [x] memory as a parameter
- [x] wrapper library for Rust
- [x] works with C or C++, [check out examples here](https://github.com/richardanaya/js_ffi/tree/master/examples_in_c)
- [x] typed arrays
- [x] can be executed in a web worker
This project has similarities to Javascript's `.call(,a0,a1,...)` but with the limitations of Web Assembly's function call restrictions.
## Hello World! in Rust
**note js_ffi is language agnostic, I just used Rust as example because I like it**
```toml
[dependencies]
js_ffi = "0.6"
```
```rust
use js_ffi::*;
#[no_mangle]
pub fn main() -> () {
register_(console.log).invoke_1("Hello World");
}
```
```html
```
```makefile
# cli commands for building web assembly
build:
@RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release
@cp target/wasm32-unknown-unknown/release/helloworld.wasm .
lint:
@cargo fmt
serve:
python3 -m http.server 8080
```
# Drawing
See demo [here](https://richardanaya.github.io/js_ffi/examples/canvas/)
```rust
use js_ffi::*;
#[no_mangle]
fn main() {
let screen = register_function("document.querySelector").call_1(&DOCUMENT, "#screen").to_js_object();
let ctx = register_function("document.querySelector").call_1(&screen, "#screen").to_js_object();
let fill_style = register_function("function(color){
this.fillStyle = color;
}");
let fill_rect = register_function("CanvasRenderingContext2D.prototype.fillRect");
fill_style.call_1(&ctx, "red");
fill_rect.call_4(&ctx, 0.0, 0.0, 50.0, 50.0);
fill_style.call_1(&ctx, "green");
fill_rect.call_4(&ctx, 15.0, 15.0, 50.0, 50.0);
fill_style.call_1(&ctx, "blue");
fill_rect.call_4(&ctx, 30.0, 30.0, 50.0, 50.0);
}
```
## Event Listener
```rust
use js_ffi::*;
#[no_mangle]
fn main() {
let btn = register_function("document.querySelector").call_1(&DOCUMENT, "#button").to_js_object();
register_function("Node.prototype.addEventListener").call_2(
&btn,
"click",
create_callback_0(|| {
register_function("window.alert").invoke_1("I was clicked");
}),
);
}
```
## Async Example
Using an [`executor`](https://www.github.com/richardanaya/executor) library we can easily turn callbacks into futures and run behavior asynchronously.
```rust
use js_ffi::*;
#[no_mangle]
pub fn main() -> () {
executor::spawn(async {
let console_log = register_function("console.log");
console_log.invoke_1("Hello");
sleep(1000).await;
console_log.invoke_1("world!");
});
}
fn sleep(millis: u32) -> impl core::future::Future {
let set_timeout = register_function("window.setTimeout");
let (future, cb) = create_callback_future_0();
set_timeout.invoke_2(cb, millis);
future
}
```
## Third Party
Wrap third party libraries. Anything function in global space should be able to be wrapped and invoked.
```rust
use js_ffi::*;
#[no_mangle]
fn main() {
let jquery_handle = register_function("$");
let jquery_on_handle = register_function("jQuery.prototype.on");
let alert = register_function("(msg)=>window.alert(msg)");
let body = jquery_handle.invoke_1("body").to_js_object();
jquery_on_handle.call_2(
&body,
"click",
create_callback_1(move |_event| {
alert.invoke_1("I was clicked!");
}),
);
}
```
```html
```
# Standard Web Libraries
A collection of libraries exist that expose javascript functionality so you don't have to implement it yourself. Just add them to your project and go!
* [web_console](https://github.com/richardanaya/web_console)
* [web_random](https://github.com/richardanaya/web_random)
* [web_timer](https://github.com/richardanaya/web_timer)
## How it works
1. Get a handle to some Javascript function using `register_function`. Re-use this handle as often as possible.
2. If you are invoking this function as a regular function, use the appropriate `invoke_*` function based on the number of arguments you are passing (`invoke_1`,`invoke_7`,etc.).
3. If you are invoking this function as a method of an object represented by a `JSValue`, use the appropriate `call_*` function based on the number of arguments you are passing (`call_1`,`invoke_7`,etc.) and make sure your object is the first paramter.
# Don't like Rust?
The script `js_ffi.js` has nothing Rust specific.
* Operations execute through an interface specified in this [`js_ffi.h`](https://github.com/richardanaya/js_ffi/blob/master/js_ffi.h)
* `js_ffi` expects an entry point `main()`
* If you plan on having your module receive data it must implement `jsffimalloc(i32) -> i32`
* If you plan on having your module receive callbacks it must implement `jsfficallback(i32,f32,f32,f32,f32,f32,f32,f32,f32,f32,f32)`
* strings are simply c-strings in memory that end in a `0` character.
# Run as a webworker
```js
// main.js
let w = new Worker("worker.js");
w.postMessage("go");
```
```js
// worker.js
importScripts("https://cdn.jsdelivr.net/gh/richardanaya/js_ffi/js_ffi.js")
onmessage = function() {
js_ffi.run("helloworld.wasm");
}
```
# License
This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in `js_ffi` by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.