4

I'm building a Chrome extension and I opted to use some WebAssembly functionality. I'm using wasm-pack to build the source because it provides a --target web that reduces the complexity of plugging in the Wasm functions. Passing integer values between Rust and JS works seamlessly, but I can't seem to be able to pass a string to Rust and vice versa.

Here is what I am working with:

#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);

    #[wasm_bindgen(js_namespace = console)]
    fn log(x: &str);
} 

#[wasm_bindgen]
pub extern "C" fn add_two(x: i32) -> i32 {
   x + 2
}

#[wasm_bindgen]
pub fn hello(name: &str) {
    log("Hello") // <-- passing a '&str' directly works. I can see it in the browser.
    log(name) // <-- does not seem to work. There is no output
    alert(&format!("Hello {}", name)); // <- Only output im getting is "Hello !"
}

Update: More information on how i'm importing and instantiating wasm

After building with wasm-pack and importing the pkg directory generated into my JS folder. I make the contents of the pkg directory available to the project through the manifest.json file as a web_resource.

Here is how i'm loading the script in my content_script.js

(async function() {
  // Get the JS File
  const src = await import("/pkg/rusty.js");
  // Fetch the wasm file.
  const wasm_src = chrome.extension.getURL("/pkg/rusty_bg.wasm");
  //src has an exported function 'default' that initializes the WebAssembly module.
  let wasm = await src.default(wasm_src);

  wasm.hello("stack-overflow");
})();

I also noticed that my generated wasm_bg file has some Rust error output at the bottom . Error output in wasm_bg

mar-tina
  • 45
  • 6
  • Your question might be answered by the answers of [How to return a string (or similar) from Rust in WebAssembly?](https://stackoverflow.com/q/47529643/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Mar 02 '20 at 20:26
  • There is a minimal difference in that the question posted doesn't use wasm-bindgen. Meaning I should be able to pass a string considering it does all the heavy work on my behalf. – mar-tina Mar 02 '20 at 20:32
  • All of options you've shown should definitely work; what seems to be the issue and not shown in the code above is how you're getting the `wasm` object that you're calling functions on. Make sure you're importing the main generated JavaScript module and not just the WebAssembly file directly. – RReverser Mar 12 '20 at 18:10
  • @RReverser . I'm running this in a chrome extension. Meaning i had to use wasm-pack to get usable JS output that could easily be imported in my extension files. `wasm-pack build --target web -- --features wee_alloc`.Which output a pkg directory with JS that could be loaded in chrome without any import errors – mar-tina Mar 14 '20 at 15:29
  • @mar-tina That still doesn't show how you're getting the `wasm` object. Can you please update the code snippet to show how you're importing the generated package? – RReverser Mar 14 '20 at 17:51
  • @RReverser. Code updated with the necessary information – mar-tina Mar 16 '20 at 15:18
  • @mar-tina Thanks, everything looks good so far. The "error" in the screenshot is unrelated - you're just looking at all static data in the Wasm file, which includes error strings for potential messages. – RReverser Mar 16 '20 at 21:28
  • @mar-tina On a second look, actually, as suspected initially, the issue is in how you're invoking the methods. I've posted a detailed answer with a fix - let me know if it helps :) – RReverser Mar 16 '20 at 21:55

1 Answers1

5

The issue is in how you're loading the code:

(async function() {
  // Get the JS File
  const src = await import("/pkg/rusty.js");
  // Fetch the wasm file.
  const wasm_src = chrome.extension.getURL("/pkg/rusty_bg.wasm");
  //src has an exported function 'default' that initializes the WebAssembly module.
  let wasm = await src.default(wasm_src);

  wasm.hello("stack-overflow");
})();

wasm returned from .default(...) is an object with raw WebAssembly exports that can operate only on raw numbers.

In this case what's happening is that wasm.hello expects two integer numbers - pointer and length of the string in WebAssembly memory - and JavaScript happily converts "stack-overflow" to 0 and provides another 0 as a default value, which is why you're ending up with an empty string on the Rust side.

What you want instead is the wrapped version of the function that takes care of proper conversions. These live directly on imports of the .js file:

(async function() {
  // Get the JS File
  const rusty = await import("/pkg/rusty.js");
  // Fetch the wasm file.
  const wasm_src = chrome.extension.getURL("/pkg/rusty_bg.wasm");
  // rusty has an exported function 'default' that initializes the WebAssembly module.
  await rusty.default(wasm_src);

  rusty.hello("stack-overflow"); // it works!
})();
mar-tina
  • 45
  • 6
RReverser
  • 1,940
  • 14
  • 15