Search code examples
javascriptrustwebassembly

How to read a file with JavaScript to WebAssembly?


How can I pass a File to be read within the WebAssembly memory context?

Reading a file in the browser with JavaScript is easy:

<input class="file-selector" type="file" id="files" name="files[]" />

I was able to bootstrap WebAssembly code written in Rust with the crate stdweb, add an event listener to the DOM element and fire up a FileReader:

let reader = FileReader::new();
let file_input_element: InputElement = document().query_selector(".file-selector").unwrap().unwrap().try_into().unwrap();
file_input_element.add_event_listener(enclose!( (reader, file_input_element) move |event: InputEvent| {
    // mystery part
}));

In JavaScript, I would get the file from the element and pass it to the reader, however, the API of stdweb needs the following signature:

pub fn read_as_array_buffer<T: IBlob>(&self, blob: &T) -> Result<(), TODO>

I have no idea how to implement IBlob and I am sure that I am missing something obvious either with the stdweb API or in my understanding of WebAssembly/Rust. I was hoping that there is something less verbose than converting stuff to UTF-8.


Solution

  • It works when the FileReader itself is passed from JavaScript to WebAssembly. It also seems like a clean approach because the data has to be read by the JavaScript API anyway - no need to call JS from WASM.

    index.html

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Read to wasm</title>
    </head>
    <body>
    <input type="file" id="file-input"/>
    <script src="reader.js"></script>
    <script>
        var fileReader = new FileReader();
        fileReader.onloadend = e => Rust.reader
                .then(reader=> {
                    window.alert(reader.print_result(fileReader));
                });
    
        var fileInputElement = document.getElementById("file-input");
        fileInputElement.addEventListener("change", e => fileReader.readAsText(fileInputElement.files[0]));
    </script>
    </body>
    </html>
    

    main.rs

    #![feature(proc_macro)]
    
    #[macro_use]
    extern crate stdweb;
    
    use stdweb::js_export;
    use stdweb::web::FileReader;
    use stdweb::web::FileReaderResult;
    
    #[js_export]
    fn print_result(file_reader: FileReader) -> String {
        match file_reader.result() {
            Some(value) => match value {
                FileReaderResult::String(value) => value,
                _ => String::from("not a text"),
            }
            None => String::from("empty")
        }
    }
    
    fn main() {
        stdweb::initialize();
    
        stdweb::event_loop();
    }