Calling into JavaScript from wasm


#1

I’m trying to build a Rust wasm wrapper around the [JavaScript speech synthesis API}(https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API/Using_the_Web_Speech_API#Speech_synthesis) so web-based Rust programs can provide speech output. But I’m having a hard time finding understandable examples of how to call JavaScript functions from Rust.

I looked through the webplatform crate, and found:

macro_rules! js {
    ( ($( $x:expr ),*) $y:expr ) => {
        {
            let mut arena:Vec<CString> = Vec::new();
            const LOCAL: &'static [u8] = $y;
            unsafe { ::webplatform::emscripten_asm_const_int(&LOCAL[0] as *const _ as *const libc::c_char, $(Interop::as_int($x, &mut arena)),*) }
        }
    };
    ( $y:expr ) => {
        {
            const LOCAL: &'static [u8] = $y;
            unsafe { ::webplatform::emscripten_asm_const_int(&LOCAL[0] as *const _ as *const libc::c_char) }
        }
    };
}

pub fn alert(s: &str) {
    js! { (s) b"\
        alert(UTF8ToString($0));\
    \0" };
}

I mean, I understand that declares a macro and an alert function, but not much beyond that. I’m also not clear how this works for calling functions on an object (I.e. speechSynthesis.getVoices()) or indeed if this is the best way of calling into JS. I know this is all very new, but if there are any good intros starting at first principles for JS interop then I’d very much appreciate them.

Thanks.


#2

Not sure there is much in the way of many tutorials beyond what is referenced in release notes.

The current wasm is implemented with emscripten and its boilerplate js. It is pretty much the same as asmjs.

JS calls are made using the emscripten C API.
https://kripken.github.io/emscripten-site/docs/api_reference/index.html
Maybe there are some tutorials in C that could help you.

Personally would try out making changes to existing code if I wanted to lean what is possible.

pub fn speak(s: &str) {
    js! { (s) b"\
        speechSynthesis.speak(new SpeechSynthesisUtterance(UTF8ToString($0)));\
    \0" };
}

In the future it will probably be possible to make use of wasm imported functions. No idea how much impact on performance using emscripten vs native JS imports has.


#3

The general rules for FFI apply here, with the additional limitation that the Rust side cannot read the JS memory.
The only way to “use Objects”, is to store them in an Array and tell Rust the index at which they are stored.
Then all FFI functions have to call into JS functions which handle the translation (object-id (“handle”) to object, utf-8 to String).

Unless you have done FFI before, I would recommend to use JSON to communicate between the two sides. Debugging is not easy and sometimes not really possible (unless you can read memory and know what the bytes mean).