Webassembly can’t access the DOM. What?

What is the DOM? I am sure Webassembly is going to bring developers from areas where the DOM concept is not familiar, like myself. This post explains what I have learned about the DOM and it contains a small Webassembly program that is going to change the content of a HTML document.

DOM

DOM stands for Document Object Model, there is an official DOM definition given by W3, you can check it out, I am sure it is correct, well, it must be, they define what it is!

That is a little too deep for a first look into it. It is like having to study molecular biology when you want to decide what yogurt to buy…

DOM Scripting Web Design with JavaScript and the Document Object Model is a book that made me view the DOM in a simpler way. The DOM is just a representation of a document, an object representation. It also allows the document to be changed. If the word Map is used instead of Model, then it would be clear that the DOM could help finding elements(or nodes) inside the document. What document? The document the browser is showing.

OK I will stop here, no wait, there is also two other important facts, the DOM is unrelated with any language, and it belongs to the browser.

Before writing this article, I had promise myself to keep the definition in one paragraph, and I failed…

From the programmers point of view: The DOM is a data structure and API that allow us to read and modify the document being presented by the browser. Much simpler!

Why Webassembly Can’t Access the DOM?

It appears Webassembly access to the DOM was left out because it could be done through Javascript, also there was a lack of time to get it implemented by the MVP launch. There is no fundamental issue that would prevent accessing the DOM directly from Webassembly. Webassembly access to the DOM is in the road map.

With that in hand, lets do something Webassembly.

Using Webassembly to Change a Button Value

A button value is part of the DOM. In the following example we create an HTML document that defines a button with a default message (“click here to see wasm changing this message”), and we will have a Webassembly function that will change that value. The new value will show how many times the user have clicked the button. The variable that keeps this count is defined inside the Webassembly module. We also explain how EMSCRIPTEN makes it easy to do all of this.

Following is the HTML code (content of dom.html file), very simple, it basically defines a button, loads a Webassembly module and executes the Webassembly function IncrementClickCountOnValue() when the button is clicked.


<html>
<head>
  <script type="text/javascript" src="dom.js"></script>
  <script>
  
    // EMSCRIPTEN "-s MODULARIZED=1" option makes it easy to load a webassembly module,
    // it generates a .js file with a Module() function that does the job.
    // The .js file also puts some glue code that allows changing the DOM from within
    // the Webassembly code (through JavaScript)
    Module()
    .then(function(instance){
        var exports = instance['asm']; // the .js file puts the exports in the 'asm' field
        var IncrementClickCountOnValue = exports._IncrementClickCountOnValue;
        // Add button listener
        var button = document.getElementById('run');
        button.addEventListener('click', function() {
            // .
            // Finally, the wasm code being called here, it changes the button value!
            // .
            IncrementClickCountOnValue();
        }, false);
      }
  );
  </script>
</head>
<body>
  <input type="button" id="run" value="click here to see wasm changing this message"/>
</body>
</html>

The following C code (content of dom.c), only has one function, it uses the EM_ASM_ interface macro. Take a look at the comments in the code for detailed explanations.


//
// This function will change the HTML value of a node(or element) that has id=run
//
// Important things to note:
//
// EMSCRIPTEN_KEEPALIVE
//  It forces this function to be present in the webassembly module.
//  There is no reference to this function in the module, the optimization would
//  wipe it out as it appears to be dead code.
//  This function is called from Javascript though
//
// EM_ASM_
//  EMSCRIPTEN macro that does all the interface with Javascript by us.
//  It creates a function inside the generated javascript file and call it from
//  here.
//  Generated function from the Javascript file:
//  var ASM_CONSTS = [function($0) { document.getElementById("run").value='Webassembly click count: '+$0 }];
// 
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;
}

Compiling it:


emcc dom.c -O1 -s MODULARIZE=1 -s WASM=1 -o dom.js


This compilation line takes the dom.c file and generates these two files:

  • dom.js contains the helper javascript code generated by EMSCRIPTEN. Important to note it contains the Module() function that loads the webassembly module. It also contains the Javascript functions that are generated by EM_ASM_. Note that “-s MODULARIZE=1” is the option that requests EMSCRIPTEN to generate the Module() function, the code becomes much cleaner with this option.
  • dom.wasm contains the wasm module.

A note about optimization. I generally use -O1 because it allows a human to look at the generated code, -Oz makes it difficult. Comparison between optimization option and file sizes:

File -O1 -Oz %
dom.js 66,535 bytes 16,916 bytes 74 % reduction
dom.wasm 10,182 bytes 221 bytes 98 % reduction

To run the code, just start emrun to serve our page and point your browser to: http://localhost:8080/dom.html


emrun --no_browser --port 8080 .

Conclusion

It was surprisingly easy to modify the DOM from within Webassembly, EMSCRIPTEN does all the work for us. This is an area that is going to change a lot in the future, so it might be wise to concentrate all the code that deals with the DOM in one file because when accessing the DOM from Webassembly becomes standard, it would be easy to port from EMSCRIPTEN macros into the new method.


Leave a message below. Webassembly is evolving rapidly, please let me know if this post got outdated.

Enjoyed this post?

Don't miss new posts: Share it with your friends:

Leave a Reply

Your email address will not be published. Required fields are marked *