Qt for Webassembly lets you to run Qt applications on the web.
WebAssembly (abbreviated Wasm) is a binary instruction format intended to be executed in a virtual machine, for example in a web browser.
With Qt for WebAssembly, you can distribute your application as a web application that runs in a browser sandbox. This approach is suitable for web distributed applications that do not require full access to host device capabilities.
Note:
Qt for WebAssembly is a supported platform, but some modules are not yet supported or are in Tech Preview. See
Supported Qt Modules
.
After installation, you should have the Emscripten compiler in your path. Check this with the following command:
em++ --version
Each minor version of Qt targets a specific Emscripten version, which remains unchanged in patch releases. Qt's binary packages are built using the target Emscripten version. Applications should use the same version since Emscripten does not guarantee
ABI compatibility
between versions.
The Emscripten versions are:
Qt 6.2: 2.0.14
Qt 6.3: 3.0.0
Qt 6.4: 3.1.14
Qt 6.5: 3.1.25
Qt 6.6: 3.1.37
Qt 6.7: 3.1.50
Qt 6.8: 3.1.56
Qt 6.9: 3.1.70
Use
emsdk
to install specific
Emscripten
versions. For example, to install it for Qt 6.8 enter:
./emsdk install 3.1.70
./emsdk activate 3.1.70
On Windows, Emscripten is in your path after installation. On macOS or Linux you need to add it to your path, like this:
source /path/to/emsdk/emsdk_env.sh
Check this with the following command:
em++ --version
You can build Qt from source if you require more flexibility when selecting the Emscripten version. In this case the versions above are minimum versions. Later versions are expected to work but may introduce behavior changes which require making changes to Qt.
Building the application generates several output files, including a .wasm file that contains the application and Qt code (statically linked), a .html file that can be opened in the browser to run the application.
Note:
Emscripten produces relatively large .wasm files at the "-g" debug level. Consider linking with "-g2" for debug builds.
The web server also sets the
COOP
and
COEP
headers to values which enables support for
SharedArrayBuffer
and multi-threading.
The qtwasmserver script starts one server which binds to localhost by default. You may add additional addresses using the
-a
command-line argument, or use
--all
to bind to all available addresses.
Enabling certain features, such as multi-threading and SIMD, produces .wasm binaries that are incompatible with browsers that do not support the enabled feature. It is possible to work around this limitation by building multiple .wasm files and then use JavaScript feature detection to select the correct one, but note that Qt does not provide any functionality for doing this.
The qtLoad() function returns a promise, which yelds an Emscripten instance when awaited. The instance provides access to Embind exported functions. Qt exports several such functions, and these functions make up the instance API.
In all cases, module support may not be complete and there may be additional limitations, either due to the browser sandbox or due to incompleteness of the Qt platform port. See Developing with Qt for WebAssembly for further info.
WebGL closely conforms to OpenGL ES, with the following version mapping:
OpenGLWebGL
OpengL ES 2WebGL 1
OpengL ES 3WebGL 2
Qt useses the highest available WebGL version. This is typically WebGL 2 on today's browsers, but might be WebGL 1 if limited by hardware. We recommend targeting devices which support WebGL 2 with Qt for WebAssembly.
A WebGL-friendly subset of ES2 (and ES3) is used by default. If you need to use glDrawArrays and glDrawElements without bound buffers, you can enable full ES2 support by adding
Existing threading code can generally be reused, but may need to be modified to work around specifics of the pthread implementation. Some Emscripten and Qt features are not supported, this includes the thread proxying feature and the Qt Quick threaded render loop.
Be aware that it is especially important to not block the main thread on Qt for WebAssembly, since the main thread might be required to service requests from secondary threads. For example, all timers in Qt are scheduled on the main thread, and will not fire if the main thread is blocked. Another example is that creating a new web worker (for a thread) can only be done from the main thread.
Emscripten provides some mitigations for this. Short-term waits such as acquiring a mutex lock is supported by busy-waiting and processing events while waiting for the lock. Longer waits on the main thread should be avoided. In particular, the common practice of calling QThread::wait() or pthread_join() to wait for a secondary thread will not work, unless the application can guarantee that the thread (and web worker) has already been started, and will be able to complete without assistance from the main thread at the time that the wait() or join() call is made.
The multithreading feature requires browser support for the SharedArrayBuffer API. (Normally, Emscripten stores the heap in an ArrayBuffer object. For multithreading, the heap must be shared with web workers and a SharedArrayBuffer is needed) This API is generally available in all modern browsers, but may be disabled if certain security requirements are not met. WebAssembly binaries with thread support enabled will then fail to run, also if the binary does not actually start a thread.
Enabling SharedArrayBuffer requires a secure browsing context (where the page is served over https:// or http://localhost), and that the page is in cross-origin isolated mode. The latter can be done by setting the so called COOP and COEP headers on the web server:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Emscripten supports WebAssembly SIMD, which provides 128-bit SIMD types and operations for WebAssembly.
Build Qt from source and configure with the -feature-wasm-simd128 flag to enable; this will pass the -msimd128 flag at compile and link time. Note that Qt does not contain wasm-simd optimized code paths at this point, however enabling wasm-simd will enable compiler auto-vectorization where the compiler can use the SIMD instructions.
You can target WebAssembly SIMD directly using either GCC/Clang SIMD Vector Extensions or WASM SIMD128 intrinsics. For more information, see the Emscripten SIMD documentation .
In addition, Emscripten supports emulating/translating x86 SSE instructions to Wasm SIMD instructions. Qt does not use this emulation, as the use of SSE SIMD instructions that have no native Wasm SIMD equivalent may cause reduced performance.
Note that SIMD-enabled binaries are incompatible with browsers that do not support WebAssembly SIMD, also if the SIMD code paths are not called at run-time. SIMD support may need to be enabled in the browsers advanced configurations, such as 'about:config' or 'chrome:flags'
QNetworkAccessManager http requests to the web page origin server, or to a server which supports CORS. This includes XMLHttpRequest from QML.
QWebSocket connections to any host. Note that web pages served over the secure https protocol allows websockets connections over secure wss protocol only.
Emulated POSIX TCP Sockets over WebSockets, using functionality provided by Emscripten. Note that this requires running a forwarding server which handles socket translation.
All other network protocols are not supported.
Note: QWebSocketServer is not supported due to browser limitations. Browsers restrict server-side socket functionality to ensure security within the web sandbox. Consequently, any functionality relying on QWebSocketServer for accepting incoming network connections cannot be utilized within the web environment.
Browsers that support the Clipboard API are preferred. Note that a requirement for this API is that the web page is served over a secure connection (e.g. https), and that some browsers my require changing configuration flags.
Chrome version 66 and Safari version 13.1 support the Clipboard API
Firefox version 90 supports the Clipboard API if you enable the following flags in 'about:config':
int main(int argc,char**argv)
QApplication app(argc, argv);
QWindow appWindow;
return app.exec();
The exec() call above normally blocks and processes events until application shutdown. Unfortunately this is not possible on the web platform where blocking the main thread is not allowed. Instead, control must be returned to the browser's event loop after processing each event.
Qt works around this by making exec() return main thread control to the browser, while preserving the stack. From the point of view of application code, the exec() function is entered and event processing happens as usual. However, the exec() call never returns, also not on application exit.
This behavior is usually acceptable since the browser will free up application memory at app shutdown time. It does mean that shutdown code does not run, since the application object is leaked and its destructor does not run.
You can avoid this by rewriting main() to be asynchronous, which is possible since Emscripten does not exit the runtime when main() returns. Application code then omits making the exec() call, and can shut down Qt cleanly by deleting the top-level window and application objects.
Emscripten's asyncify feature lifts these restrictions by allowing synchronous calls (like QEventLoop::exec() and QDialog::exec()) to yield to the event loop. Nested calls are not supported, and for this reason asyncify is not used for the top-level QApplication::exec() call.
Features that require asyncify are:
QDialogs, QMessageBoxes with return values.
Drag and drop (specifically drag).
Nested/secondary event loops exec().
Enable asyncify by adding the "-sASYNCIFY -Os" flags to the linker options:
CMake:
target_link_options(<your target> PUBLIC -sASYNCIFY -Os)
qmake:
QMAKE_LFLAGS += -sASYNCIFY -Os
Enabling asyncify adds overhead in the form of increased binary sizes and increased CPU usage. Build with optimizations enabled to minimize the overhead.
Asyncify JSPI (JavaScript Promise Integration)
JSPI is a new asyncify backend implemented using native browser support instead of code transformations. Compared to Emscripten asyncify this improves application link times and does not increase code size.
The JSPI WebAssembly feature is current in the "Implementation" phase, and is not yet standardized.
Enable JSPI by adding the "-sJSPI" flag to the linker options:
To stop execution on a certain line and popup the browser debugger programmatically, you can add the function emscripten_debugger(); to the application source code.
Profiling can be accomplished by using a debug build and the JavaScript console profiling features. Qt adds –profiling-funcs to the linker arguments in debug builds, which preserve function names in profiling
You can add more verbosity to help debug using Emscripten linker arguments:
-s LIBRARY_DEBUG=1 (print out library calls)
-s SYSCALL_DEBUG=1 (print out sys calls)
-s FS_LOG=1 (print out filesystem operations)
-s SOCKET_DEBUG (print out socket, network data transfer)
You can disable the following features to reduce binary size (usually by 10-15%):
Configure ArgumentBrief Description
-no-feature-cssparserParser for Cascading Style Sheets.
-no-feature-datetimeeditEditing dates and times (depends on datetimeparser).
-no-feature-datetimeparserParsing date-time texts.
-no-feature-dockwidgetDocking widgets inside a QMainWindow or floating them as top-level windows on the desktop.
-no-feature-gesturesFramework for gestures.
-no-feature-mimetypeMimetype handling.
-no-feature-qml-networkNetwork transparency.
-no-feature-qml-list-modelListModel QML type.
-no-feature-qml-table-modelTableModel QML type.
-no-feature-quick-canvasCanvas item.
-no-feature-quick-pathPath elements.
-no-feature-quick-pathviewPathView item.
-no-feature-quick-treeviewTreeView item.
-no-feature-style-stylesheetWidget style which is configurable via CSS.
-no-feature-tableviewDefault model/view implementation of a table view.
-no-feature-texthtmlparserParser for HTML.
-no-feature-textmarkdownreaderMarkdown (CommonMark and GitHub) reader.
-no-feature-textodfwriterODF writer.
Note that calling QApplication::exec() is not supported when exceptions are enabled, due to internal implementation details. Instead, write main() in the form where it returns early and does not call exec(), as described in Application Startup and the Event Loop.
Shared Libraries and Dynamic Linking Developer Preview
Dynamic linking support is currently in developer preview. The implementation is suitable for prototyping and evaluation, but is not suitable for production use. Current limitations and restrictions include:
The Emscripten SDK must be patched. Use emsdk 3.1.70, and apply this patch: patch#1.
Multithreading is not supported.
Asyncify is not supported.
Qt applications built with dynamic linking require two additional files to be present alongside the binaries: qt_plugins.json and qt_qml_imports.json. These files specify a list of shared libraries that will be loaded at application startup. There are helper scripts available to generate them: preload_qt_plugins.py and preload_qml_imports.py. To demonstrate how to use these scripts, a helper script named generate_default_preloads_for_<target>.sh will be provided.
The web server hosting the application must have the Qt shared libraries available. This can be accomplished by copying the contents of the Qt installation folder to the web server, or by creating a file system link.
Fonts: Wasm sandbox does not allow access to system fonts. Font files must be distributed with the application, for example in Qt resources or downloading. Qt for WebAssembly itself embeds one such font.
There may be artifacts of uninitialized graphics memory on some Qt Quick Controls 2 components, such as checkboxes. This can sometimes be seen on HighDPi displays.
Native styles for Windows and macOS are not supported as Wasm as a platform is not providing that capability
Link time error such as "wasm-ld: error: initial memory too small", requires adjustment of the initial memory size. Use QT_WASM_INITIAL_MEMORY to set the initial size in kb, which must be a multiple of 64KB (65536). Default is 50 MB. In CMakeLists.txt: set_target_properties(<target> PROPERTIES QT_WASM_INITIAL_MEMORY "150MB")
add_executable in CMakeLists.txt does not produce <target>.html or copy qtloader.js. Use qt_add_executable instead.
QWebSocket connections are supported by Emscripten only on the main thread.
QWebSockets for WebAssembly does not support sending ping or pong frames, as the API available to web pages and browsers does not expose this functionality.
Runtime error such as "RangeError: Out of memory" can be worked around by setting MAXIMUM_MEMORY to a value the device supports, for example
Compression is typically handled on the web server side, using standard compression features: the server compresses automatically or picks up pre-compressed versions of the files. There's generally no need to have special handling of Wasm files.