17

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.

2
  • 1
    Maybe stdweb doesn't have clean way to access InputElement.files now. github.com/koute/stdweb/commit/… Commented Jul 4, 2018 at 2:47
  • See also this bug report about reading files with wasm-bindgen. It contains some pointers that might help you. Commented Jan 22, 2023 at 18:59

3 Answers 3

11
+50

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();
}
Sign up to request clarification or add additional context in comments.

2 Comments

Is reader.js the name of the WASM module the main.rs gets packaged into?
@EricH yes I think so. Not sure after two years :D. There also might be better a better way to do this now.
4

The following code is what I use to interact with another javascript library to read a sql file all without using javascript. This is based on the wasm-bindgen library, and I believe may be helpful to newer folks stumbling onto this answer.

[wasm_bindgen]
pub fn load_accounts_from_file_with_balances(file_input : web_sys::HtmlInputElement) {
    //Check the file list from the input
    let filelist = file_input.files().expect("Failed to get filelist from File Input!");
    //Do not allow blank inputs
    if filelist.length() < 1 {
        alert("Please select at least one file.");
        return;
    }
    if filelist.get(0) == None {
        alert("Please select a valid file");
        return;
    }
    
    let file = filelist.get(0).expect("Failed to get File from filelist!");

    let file_reader : web_sys::FileReader = match web_sys::FileReader::new() {
        Ok(f) => f,
        Err(e) => {
            alert("There was an error creating a file reader");
            log(&JsValue::as_string(&e).expect("error converting jsvalue to string."));
            web_sys::FileReader::new().expect("")
        }
    };

    let fr_c = file_reader.clone();
    // create onLoadEnd callback
    let onloadend_cb = Closure::wrap(Box::new(move |_e: web_sys::ProgressEvent| {
        let array = js_sys::Uint8Array::new(&fr_c.result().unwrap());
        let len = array.byte_length() as usize;
        log(&format!("Blob received {}bytes: {:?}", len, array.to_vec()));
        // here you can for example use the received image/png data
        
        let db : Database = Database::new(array);

        //Prepare a statement
        let stmt : Statement = db.prepare(&sql_helper_utility::sql_load_accounts_with_balances());
        stmt.getAsObject();

        // Bind new values
        stmt.bind();

        while stmt.step() { //
            let row = stmt.getAsObject();
            log(&("Here is a row: ".to_owned() + &stringify(row).to_owned()));
        }

    }) as Box<dyn Fn(web_sys::ProgressEvent)>);

    file_reader.set_onloadend(Some(onloadend_cb.as_ref().unchecked_ref()));
    file_reader.read_as_array_buffer(&file).expect("blob not readable");
    onloadend_cb.forget();

}

Comments

1

I managed to access the file object and pass it to the FileReaderin the following way:

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| {
        let file = js!{return @{&file_input_element}.files[0]};
        let real_file: stdweb::web::Blob = file.try_into().unwrap();

        reader.read_as_text(&real_file);
    }

This code compiles. However, the data never gets available via reader.result().

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.