28 - 12 - 2022 - Expert blogs

FigmaQML on WebAssembly

This text covers porting FigmaQML application to WebAssembly. Here I discuss on obstacles, elaborate reasons behind them, and introduce workarounds used. I hope this is useful reading for anyone working with Qt for WebAssembly.

WebAssembly (aka WASM) is a standard hardware independent binary format and interface set for applications executed in a sandboxed environment. The sandbox environment is an isolated virtual machine, separated from the rest of the system and hence potentially unsafe software code can execute without affecting network resources, files or applications. The most common sandboxed environment is a web browser. Web pages execute code without access to the host machine.

Qt is a C++ software development platform topping QML language as a high level user interface definition language. Qt has had WebAssembly build target for a while as a technology preview and finally on Qt 6.4 onwards, it is supported as a build target. Qt for WebAssembly enables building Qt applications on WebAssembly and to be executed in the browser tab.

FigmaQML 1.0 was developed two years ago and can be built on Windows, Linux and MacOS - the major Qt supported platforms. However, along Qt 6.4 it got time to get it running on WebAssembly. This is a story about that journey. FigmaQML 2.0 supporting WebAssembly can be run here.

I have gained experience of WebAssembly with the Neuropia. It is a C++ library that for WebAssembly has Javascript bindings and hence can do neural network calculations for the web page, all visible parts are implemented using web tools. That, like Qt for Webassembly, is using Emscripten, a WebAssembly toolchain.

FigmaQML was never planned to be executed on any other platforms than the three listed earlier. WebAssembly is having certain limitations and restrictions that caused certain troubles, as it was not considered at all. Yet, I have to admit that it would have been an easier task if I have have not made some questionable choices in the first place for FigmaQML 1.0. FigmaQML is not a commercial project, and it started as a command line tool, a hobby project, and then not expecting commercial quality when the task was just to finish it without any engagement and therefore some effort saving decisions were very acceptable.

Next, I will introduce obstacles when building up WebAssembly support for FigmaQML. The very first stumble was to figure out that Emscripten version has to be exactly 3.14.1. That is the version that Qt 6.4 uses and any newer did not work well.

Secure Sockets and QtConcurrent

Qt for WebAssembly does not support all Qt libraries. I was lucky with FigmaQML as it has the optional use of Secure Socket and QtConcurrent: Secure Sockets are not required for the Figma service connection, and concurrency was a later performance improvement and I kept the original non-concurrent implementation for debugging purposes. In WebAssembly Secure Sockets are not supported due to the WebAssembly security model. Qt Concurrent is an experimental feature and would require re-build Qt from sources, and it was very natural to fall back to non-concurrent code and accept some performance hits.

File access

Another impact of the WebAssembly security model is restricted file access. Sandboxed applications do not have access to the machine filesystem, there is access only to the virtual sandboxed file system, which either cannot be accessed by any other application, e.g. File Manager. However Qt provides specific dialogs for WASM applications for the user to write and read files from the system side. That provides the application user full control over how to exchange files out side of the sandboxed environment. Applying dialogs required some changes as WebAssembly dialogs can only read or write a single file at a time, whereas FigmaQML 1.0 design was to pick a folder where generated QML structure was written.

The solution was to store QML structure into a zip file and then let the user save that file via Qt dialog. Unfortunately Qt does not support creating zip files and I had to look for third-party libraries. I chose to utilize Quazip on top of zlib - but neither of those did not have WASM support. Therefore I had to fork and do some modifications. Pull requests are on their way, and before that forks are found on my github site.

Secondly I had to re-write font mapping dialogs. FigmaQML has a concept of font mapping as when Figma has its fonts, and QML uses system fonts, there has to be a way to define how those fonts correspond to each other. Since WebAssembly applications do not have access to system fonts I had to design an UI to import fonts from system fonts into the WebAssembly file system.

Asynchronous operations

WebAssembly is designed to work on browsers, where page responsiveness is crucial. Basically, any UI application is not allowed to get frozen even if there is some heavy processing ongoing. Qt is designed for application development and there is extensive support for smooth-behaving applications.

When a traditional Qt application starts, it shortly initializes itself and then enters into the event loop until exit. In the event loop it awaits for events - external, like user input or socket communication events, or internal, like timer events. Received events trigger some processing, and the assumption is not to spend too much time with a single event, thus the user won't notice any delays and the application feels responsive. Some events can trigger more events and therefore there can be nested event loops. This lets the application be responsive to external events, and then continue the processing of the current event when the nested event loop exits.

WebAssembly works differently: when an application is started, it waits for user input, and like a web page, there is no need for any event loop. Qt application is written exclusively for WebAssembly, even the main event loops can be omitted. However Qt applications requiring an event loop can get working with Emscripten Asyncify. Yet there are caveats even then: on WebAssemby event loops cannot be nested. Alternatively, the nested event loops processing can be done in another thread, simultaneously with user event processing. However, Qt expects that threads have their own event loops, and that is not supported on Qt for WebAssembly. That makes multiple threads not a feasible option.

The FigmaQML 1.0 uses extensively nested event loops. The Figma parsing was designed to be a single pass. That means when data from Figma graphics is received, it is converted to QML while reading. If the content has references to external images, nodes (referring graphics representations), etc. - the current read has to halt, content data has to be fetched from the network process, and then current processing can be continued. The nested event loops took care that the application stayed responsive during meandering processing. However, that was not possible with WebAssembly.

To get rid of event loops I had to do some refactoring of how data is received. I took advantage of the existing download data cache. In FigmaQML most of the time is spent on downloading, and waiting for data from the Figma Server. Therefore, it's crucial that each data item is downloaded only once even if it can be used multiple times in graphics. Compared to downloading the local data parsing and processing is very fast.

With cache, I was able to fake my single pass parser to restart parsing from the beginning whenever some download was needed. Meaning that instead of starting a download in a nested event loop, the parser sends an event when finished the download. As there are no nested event loops, parsing cannot continue and re-start from the beginning. However, if the data is found from the cache, there is no need for spawning time-consuming asynchronous operations, no need for download, and thus no re-start. Therefore eventually all data is found in the cache and the parsing is able to finish as a single pass.

Notable is that the event loop's limits can also cause issues with the QML code. There was a timer function created with createQmlObject and it get jammed on WebAssembly build. I had to rewrite that as a traditional QML Timer.

Exceptions

Although Emscripten supports C++ exceptions, Qt for WebAssembly binary release does not. Traditionally Qt applications do not use exceptions; instead, functions use return values to indicate erroneous states. FigmaQML parsing uses exceptions due to it is C++ standard way to pop off from a deep function with an error message. I had to change that to a simple concept of storing the last error message and use std::optional to wrap up each return value that may fail. Upon error, a functions pop back and then emits a Qt error signal to indicate the error to the user.

Summary

The WebAssembly for Qt set me free from compiling, testing, and deploying FigmaQML for each platform, and now it can be executed on any platform having sufficient browser without the user having any safety concerns running the application. In general, Qt for WebAssembly supports Qt functionality well, yet porting from applications designed to run on other, hardware platforms, may include a good set of surprises as seen here. Anyhow, if I have initially designed FigmaQML to be a well-behaving Qt application, i.e. no nested event loops or exceptions, that would have saved me a lot of work - then changes, like file dialogs, would be just a busy weekend.

Markus Mertama
writer

Markus Mertama

Senior Software Developer, Insta Advance

Share article

Stay on top of the industry trends and subscribe to our newsletter

The most important news, inspiring articles, and up-to-date insights from our experts across various industries and information about our upcoming events.

Accept the terms and conditions. We handle your information responsibly.
Please review our privacy policy.