3

This article demonstrates how to access the DOM in WebAssembly from a C program:

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

unsigned int EMSCRIPTEN_KEEPALIVE IncrementClickCountOnValue()
{
    // Static variable that hold how many times this function was clicked
    static int clicks=0;

    // Modify the DOM, through a Javascript call provided by EM_ASM_, getElementById  is the DOM API used
    EM_ASM_( {document.getElementById("run").value='Webassembly click count: '+$0}, ++clicks );
    return 1;
}

If you compile (emcc dom.c -O1 -s MODULARIZE=1 -s WASM=1 -o dom.js) and run it (emrun --no_browser --port 8080 .), it works as expected.

How can I do the same without C, i. e. what would be the equivalent of EM_ASM_( {document.getElementById("run").value='Webassembly click count: '+$0}, ++clicks ); in the WebAssembly text format?

Glory to Russia
  • 17,289
  • 56
  • 182
  • 325

1 Answers1

8

There's no equivalent purely in WebAssembly text format because the WebAssembly environment doesn't know anything about the DOM (or any of the browser APIs) or how to manipulate it.

What WebAssembly can do though is import functions from its host environment, such as the browser. You can then call those functions from within WebAssembly using the call instruction by providing the imported function index or by a name.

Here's an example of incrementing a static (global) in WebAssembly and then updating the DOM based on the count:

;; clicks.wat

(module

    ;; import the updateClickCount function from JavaScript land

    (import "button" "updateClickCount"
        (func $updateClickCount
            (param $x i32)
        )
    )

    ;; Define a mutable static i32 initialized at 0

    (global $count (mut i32) (i32.const 0))

    ;; Define and export a function to JS land

    (func (export "onClick")

        ;; Increment the global count

        get_global $count
        i32.const 1
        i32.add
        set_global $count

        ;; Push the count on the stack and call the imported function

        get_global $count
        call $updateClickCount
    )
)

This might be the corresponding HTML/JS to load and set the imports and hook up to the DOM:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Clicks</title>
</head>
<body>

    <button id="click">CLICK ME</button>
    <p id="numclicks"></p>

    <script>

        // This is what we'll make available to wasm

        const imports = {
            button: {
                updateClickCount: (num) => document.getElementById('numclicks').innerHTML = num.toString()
            }
        };

        WebAssembly.instantiateStreaming(fetch('clicks.wasm'), imports)
            .then(obj => {

                const exports = obj.instance.exports;

                // When the button is clicked, call the function in wasm

              document.getElementById('click').addEventListener('click', () => {

                    exports.onClick();
                });

            }).catch((err) => console.error(err));
    </script>
</body>
</html>

WebAssembly studio example (click build and run): https://webassembly.studio/?f=ff0fei4xgd9

Matt Harrison
  • 13,381
  • 6
  • 48
  • 66