// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only

/*!
\page wasm.html
\title Qt for WebAssembly
\brief Runs Qt applications in a secure sandbox in the browser.

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 \l{supported modules}.

\ingroup supportedplatform

\section1 Getting Started with Qt for WebAssembly

Building Qt applications for WebAssembly is similar to building Qt for other platforms. You
need to install an SDK (Emscripten), install Qt (or build Qt from source), and finally, build
the application. Some differences exist, for example, Qt for WebAssambly supports fewer modules
and less features than other Qt builds.

\section2 Installing Emscripten

\l{https://emscripten.org/docs/introducing_emscripten/index.html}{Emscripten}
is a toolchain for compiling to WebAssembly. It lets you run Qt on the web at
near-native speed without browser plugins.

Refer to the \l{https://emscripten.org/docs/getting_started/index.html}
{Emscripten documentation} for more information about installing the
Emscripten SDK.

After installation, you should have the Emscripten compiler in your path.
Check this with the following command:
\badcode
    em++ --version
\endcode

Each minor version of Qt targets a specific Emcsripten 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
\l{https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md}{ABI compatibility}
between versions.

The Emcsripten versions are:
\list
   \li Qt 6.2: 2.0.14
   \li Qt 6.3: 3.0.0
   \li Qt 6.4: 3.1.14
\endlist

Use \c emsdk to install specific \c Emscripten versions. For example, to install
it for Qt 6.4 enter:
\list
    \li ./emsdk install 3.1.14
    \li ./emsdk activate 3.1.14
\endlist

On Windows, Emscripten is in your path after installation. On macOS or Linux
you need to add it to your path, like this:

\badcode
    source /path/to/emsdk/emsdk_env.sh
\endcode

Check this with the following command:

\badcode
    em++ --version
\endcode

You can build Qt from source if you require more flexibility when selecting the Emcsripten
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.

\section2 Installing Qt

Download Qt from the Downloads section of your Qt account. We provide builds
for Linux, macOS, and Windows as development platforms.

The binary builds are designed to run on as many browsers as possible,
and do not enable features such as threads or SIMD support.

\target wasm-building-qt-from-source
\section2 Building Qt from Source

Building from source lets you set Qt configuration options such as thread
support, OpenGL ES level, or SIMD support. Download the Qt sources from the
Downloads section of your Qt account.

Configure Qt as a cross-compile build for the \c wasm-emscripten platform.
This sets the \c -static, \c -no-feature-thread, and \c{-no-make examples}
configure options. You can enable thread support with the \c -feature-thread,
configure option. Shared library builds are not supported.

You need a host build of the same version of Qt and specify that path in the \e
QT_HOST_PATH CMake variable or by using the \c -qt-host-path configure argument.

Although it should be detected, you may optionally set the
\e{CMAKE_TOOLCHAIN_FILE} CMake variable to the Emscripten.cmake toolchain file
that comes with Emscripten SDK. This can be done by setting the environment
variable \e{CMAKE_TOOLCHAIN_FILE} or by passing
\c{CMAKE_TOOLCHAIN_FILE=/path/to/Emscripten.cmake} to configure.

\badcode
    ./configure -qt-host-path /path/to/Qt -platform wasm-emscripten -prefix $PWD/qtbase
\endcode

On Windows, make sure you have MinGW in your \c PATH
and configure with the following:

\badcode
    configure -qt-host-path C:\Path\to\Qt -no-warnings-are-errors -platform wasm-emscripten -prefix %CD%\qtbase
\endcode

Then build the required modules:

\badcode
    cmake --build . -t qtbase -t qtdeclarative [-t another_module]
\endcode

\section2 Building Applications on the Command Line

Qt for WebAssembly supports building applications using qmake and make, or CMake with ninja or make.

\badcode
   $ /path/to/qt-wasm/qtbase/bin/qt-cmake .
   $ cmake --build .
\endcode

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.

\section2 Running Applications

Running the application requires a web server. The build output files are all
static content, so any web server will do. Some use cases might require special
server configuration, such as providing https certificates or setting http headers
required to enable multithreading support.

\section3 Emrun

Emscripten provides the \e emrun utility for test-running applications. Emrun
starts a web server, launches a browser, and will also capture and forward
stdout/stderr (which will normally go to the JavaScript console).

\badcode
    /path/to/emscripten/emrun --browser=firefox appname.html
\endcode

\section3 Python http.server

Another option is to start a development web server and then launch the web
browser separately. One of the simplest options is http.server from Python:

\badcode
    python -m http.server
\endcode

Note that this is only a simple webserver and does not support SharedArrayBuffer required for
threading, as the required COOP and COED headers mentioned below are not sent.

\section3 qtwasmserver

Qt provides a developer web server which uses \l{https://github.com/FiloSottile/mkcert}{mkcert}
to generate https certificates. This allows testing web features which require
a \l{https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts}{secure context}.
Note that delivery over http://localhost is also considered secure, without requiring a
certificate.

The web server also sets the \l{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy}{COOP}
and \l{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy}{COEP}
headers to values which enables support for \l{https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer}{SharedArrayBuffer}
and multi-threading.

The qtwasmserver script starts one server which binds to localhost by default. You may
add additional addresses using the \e -a command-line argument, or use \e --all to bind
to all available addresses.

\badcode
    python /path/to/qtbase/util/wasm/qtwasmserver/qtwasmserver.py --all
\endcode

\section2 Building Applications using Qt Creator

    \l{https://doc.qt.io/qtcreator/creator-setup-webassembly.html}{Setting Up Qt Creator for WebAssembly}.

\section2 Deploying Applications on the web

Building an application generates several files (substitute "app" with
the application name in the following table).

\table
    \header
        \li Generated file
        \li Brief Description
    \row
        \li app.html
        \li HTML container
    \row
        \li qtloader.js
        \li JavaScript API for loading Qt apps
    \row
        \li app.js
        \li JS API for loading Qt apps
    \row
        \li app.wasm
        \li app binary
\endtable

You can deploy \e {app.html} as-is, or discard it in favor favor of a custom html
file. Smaller adjustments, such as changing the splash screen image from the Qt
logo to the app logo, is also possible. In both cases, \e {qtloader.js} provides a JavaScript
API for loading the application.

We recommend compressing the wasm file using e.g. gzip or brotli before deployment,
as this can provide a significant reduction in file size.

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.

\section1 Supported Browsers
\section2 Desktop

Qt for WebAssembly is developed and tested on the following browsers:

\list
    \li Chrome
    \li Firefox
    \li Safari
    \li Edge
\endlist

Qt should run if the browser supports WebAssembly. Qt has a fixed WebGL
requirement, even if the application itself does not use hardware accelerated
graphics. Browsers that support WebAssembly often support WebGL, though
some browsers blacklist older or unsupported GPUs. s/\e {qtloader.js}
provides APIs to check if WebGL is available.

Qt does not make direct use of operating system features and it makes
no difference if, for example, FireFox runs on Windows or macOS. Qt
does use some operating system adaptations, for example for ctrl/cmd
key handling on macOS.

\section2 Mobile

Qt for WebAssembly applications runs on mobile browsers such as mobile
Safari and Android Chrome.

\section1 Supported Qt Modules

Qt for WebAssembly supports a subset of the Qt modules and features. Tested
modules are listed below, other modules may or may not work.

\list
    \li \l {Qt Core}
    \li \l {Qt GUI}
    \li \l {Qt Network}
    \li \l {Qt Widgets}
    \li \l {Qt QML}
    \li \l {Qt Quick}
    \li \l {Qt Quick Controls}
    \li \l {Qt Quick Layouts}
    \li \l {Qt 5 Core Compatibility APIs}
    \li \l {Qt Image Formats}
    \li \l {Qt OpenGL}
    \li \l {Qt SVG}
    \li \l {Qt WebSockets}
\endlist

In all cases, module support may not be complete and and there may be
additional limitations, either due to the browser sandbox or due to
incompleteness of the Qt platform port. See \l {Developing with Qt for WebAssembly}
for further info.

Qt for WebAssembly Technology Preview modules and features. These features
may require to reconfigure and build Qt. They may contain features that
are still experimental in the browsers or Emscripten.

\list
    \li \l {Thread Support in Qt}{Qt Threading}
    \li \l {Qt Concurrent}
    \li \l {#Asyncify}{Emscripten Asyncify}
    \li \l {Network Programming with Qt}{Qt Sockets}
\endlist

\section1 Developing with Qt for WebAssembly
\section2 OpenGL and WebGL

Qt requires WebGL, also for applications which do not use OpenGL directly. All
relevant browsers support WebGL, but note that some browsers blacklist certain
older GPUs. The Qt loader will detect this and display an error message.

Qt detects WebGL as OpenGL ES, with the following version mapping:

\table
    \header
        \li OpenGL
        \li WebGL
    \row
        \li OpengL ES 2
        \li WebGL 1
    \row
        \li OpengL ES 3
        \li WebGL 2
\endtable

OpengL ES 2 is selected by default, OpenGL ES 3 can be enabled by configuring Qt with
the \c {-feature-opengles3} option.

Web and Desktop OpenGL differences are documented in
\l {https://www.khronos.org/registry/webgl/specs/latest/1.0/#6}
{WebGL and OpenGL Differences}.
There are additional differences between WebGL 1.0 and WebGL 2.0,
documented in the \l {https://www.khronos.org/registry/webgl/specs/latest/2.0}
{WebGL 2.0 Specification}.

A WebGL-friendly subset of ES2 (and ES3) is used by default. If you need to use
\c glDrawArrays and \c glDrawElements without bound buffers, you can enable full
ES2 support by adding

\badcode
target_link_options(<your target> PRIVATE "SHELL:-s FULL_ES2=1")
\endcode

and/or full ES3 emulation by adding

\badcode
target_link_options(<your target> PRIVATE "SHELL:-s FULL_ES3=1")
\endcode

to your project's \c {CMakeFiles.txt}.

\section2 Multithreading

Qt supports multithreading on WebAssembly, however this feature is experimental
and is not enabled by default. Thread support can be enabled by building Qt from
source and using the "-feature-thread" configure flag.

The Qt for WebAssembly binary packages do not support multithreading.

Emscripten implements support for pthreads using web workers, and this abstraction
is not completely leak free. See \l {https://emscripten.org/docs/porting/pthreads.html}{Pthreads Support}
for further info.

Multithreading requires browser support for SharedArrayBuffer, see \l {https://caniuse.com/sharedarraybuffer}
{ caniuse sharedarraybuffer } for current supported status. If supported, SharedArrayBuffer will
be enabled provided the web server sets the COOP and and COEP headers:
\list
\li Cross-Origin-Opener-Policy: same-origin
\li Cross-Origin-Embedder-Policy: require-corp
\endlist

\section2 SIMD

The LLVM compiler supports generating \l{https://emscripten.org/docs/porting/simd.html}{WebAssembly SIMD}.
Pass the -msimd128 flag at compile time to enable. This enables LLVM auto-vectorization, which
makes it possible to benefit from SIMD without making source code modifications.

You can target WebAssembly SIMD directly using either GCC/Clang SIMD Vector Extensions or WASM
SIMD128 intrinsics. For more information, see the Emscripten
\l{https://emscripten.org/docs/porting/simd.html}{ SIMD documentation }.

In addition, Emscripten supports emulating/translating x86 SSE instructions to Wasm SIMD instructions.
Enable by building Qt from source and configure with the "-sse2" option. This adds support for SSE1,
SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, and 128-bit AVX instructions, and enables Qt's SSE code paths.
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'

\section2 Networking

Qt provides limited support for networking. In general, network protocols which are
already in use on the web can be use also from Qt, while others are not directly
available due to the web sandbox.

The following protocols are supported:

\list
\li QNetworkAccessManager http requests to the web page origin server, or to a
server which supports CORS. This includes XMLHttpRequest from QML.
\li QWebSocket connections to any host. Note that web pages served over the secure https protocol
allows websockets connections over secure wss protocol only.
\li Emulated POSIX TCP Sockets over WebSockets, using functionality provided by
\l{https://emscripten.org/docs/porting/networking.html#emulated-posix-tcp-sockets-over-websockets}
{Emscripten}. Note that this requires running a \l{https://github.com/novnc/websockify}
{forwarding server} which handles socket translation.
\endlist

All other network protocols are not supported.

\section2 Local File Access

File system access is sandboxed on the web, and this has implications for how the
application works with files. The Web platform provides APIs for accessing the local
file system in a way which is under user control, as well as APIs for accessing
persistent storage. Emscripten and Qt wraps these features and provides APIs which
are easier to use from C++ and Qt-based applications.

The web platform provides features for accessing local files and persistent storage:
\list
\li <input type="file"> for showing a native open-file dialog where the user can pick a file.
\li IndexedDB provides persistent local storage (not accessible outside the browser)
\endlist

Emscripten provides several file systems with a POSIX like API. These include:
\list
\li the MEMFS ephemeral file system which stores files in-memory
\li the IDBFS persistent file system which stores files using IndexedDB
\endlist

Emscripten mounts a temporary MEMFS filesystem to "/" at app startup. This means
that QFile can be used, and will read and write files to memory by default. Qt
provides other API as well:

\list
\li QSettings has an IndexedDB-based backend; Note that QSettings is
asynchronous on WebAssembly.
\li \l {https://doc.qt.io/qt-5/qfiledialog.html#getOpenFileContent}{QFileDialog::getOpenFileContent() }
opens a native file dialog where the user can pick a file
\li \l {https://doc.qt.io/qt-5/qfiledialog.html#saveFileContent}{QFileDialog::saveFileContent()}
saves a file to the local file system via file download}
\endlist

\section2 Clipboard Access

Qt supports copying and pasting text to the system clipboard, with some differences due
to the web sandbox. In general clipboard access require user permission, which can be obtained
by handling an input event (e.g. CTRL+c), or by using the
\l{https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API}{Clipboard API}.

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.

At the time of writing the following browsers support the Clipboard API, see
\l{https://caniuse.com/?search=Clipboard}{caniuse} for the current support level.

\list
    \li Chrome supports the Clipboard API
    \li Firefox supports the Clipboard API behind a flag:
        dom.events.asyncClipboard.dataTransfer
\endlist

\section2 Fonts

The Qt WASM module contains 3 embedded fonts: "Bitstream Vera Sans" (fallback font), "DejaVu Sans", "DejaVu Sans Mono".

These fonts provide a limited character set. Qt provides several options for adding additional fonts:

One is using FontLoader in QML,
which can either fetch a font by URL or using
\l{https://doc.qt.io/qt-6/resources.html}{Qt Resource System}
(the same way the usual desktop apps work).

The other way to use font is to add it via
\l{https://doc.qt.io/qt-6/qfontdatabase.html#addApplicationFontFromData}{QFontDatabase::addApplicationFontFromData}.

\section2 Application Startup and the Event Loop

Qt for WebAssembly supports the standard Qt startup approach, where the application
creates a QApplication object and calls the exec function:

\code
int main(int argc, char **argc)
{
    QApplication app(argc, argv);

    QWindow appWindow;

    return app.exec();
}
\endcode

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.

\code
QApplication *g_app = nullptr;
AppWindow *g_appWindow = nullptr;

int main(int argc, char **argc)
{
    g_app = new QApplication(argc, argv);
    g_appWindow = new AppWindow();
    return 0;
}
\endcode

\section3 Asyncify

The default build of Qt for WebAssembly does not support reentering the event loop,
for example by calling QEventLoop::exec() or QDialog::exec(), due to restrictions
of the web platform.

Emscripten's \l{https://emscripten.org/docs/porting/asyncify.html}{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:
\list
    \li QDialogs, QMessageBoxes with return values.
    \li Drag and drop (specifically drag).
    \li Nested/secondary event loops exec().
\endlist

As of Qt 6.4, Asyncify support is enabled in the binary package, but needs to be enabled for
applications by adding -sASYNCIFY -Os to linker options:

CMake:
\badcode
set target_link_options(<target> PUBLIC -sASYNCIFY -Os)
\endcode

qmake:
\badcode
QMAKE_LFLAGS += -sASYNCIFY -Os
\endcode

Enabling asyncify adds overhead in the form of increased binary sizes and increased CPU
usage. Build with optimizations enabled to minimize the overhead.

\section2 Debugging and Profiling

Wasm debugging is done on browser JavaScript console, debugging applications on Wasm
directly within Qt Creator is not possible.

\list
\li Qt debug and logging output is printed on the JavaScript console, which can be
accessed via browser "Developer Tools" or similar.
\li Source maps for stepping through code, can be created by re-configuring Qt with
the --device-option QT_WASM_SOURCE_MAP=1, and building a debug build.
\list
    \li \l {https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map}
    \li \l {https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps#source_maps_in_devtools_sources_panel}
\endlist
\li Debug symbols via DWARF are also enabled if the program is linked with the -g flag (tested with Chrome)
    \li This will require this extension: \l {https://goo.gle/wasm-debugging-extension}
    \li See also \l {https://developer.chrome.com/blog/wasm-debugging-2020/}
\li Mobile browsers can use remote debugging
\list
    \li \l {https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging}
    \li \l {https://developers.google.com/web/tools/chrome-devtools/remote-debugging}
    \li \l {https://developer.apple.com/safari/tools/}
\endlist
\li 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.
\li 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
\endlist

You can add more verbosity to help debug using Emscripten linker arguments:

\list
    \li -s LIBRARY_DEBUG=1 (print out library calls)
    \li -s SYSCALL_DEBUG=1 (print out sys calls)
    \li -s FS_LOG=1  (print out filesystem operations)
    \li -s SOCKET_DEBUG (print out socket, network data transfer)
\endlist

Using cmake:
\code
   target_link_options(<your target> PRIVATE "SHELL:-s LIBRARY_DEBUG=1")
\endcode
Using qmake:
\code
    QMAKE_LFLAGS_DEBUG += -s LIBRARY_DEBUG=1
\endcode

\section2 Asyncify

Asyncify is an Emscripten feature which enables synchronous C++ code to interact with asynchronous JavaScript.

Qt uses it to allow calling blocking APIs like one level of nested QEventLoop::exec() and returning normally.

Asyncify's unwinding and rewinding the stack add overhead to executable size as well as performance.
To enable asyncify, configure Qt using:
\code
     -device-option QT_EMSCRIPTEN_ASYNCIFY=1
\endcode

\section2 Known Issues

\list
\li Nested event loops are not supported. Applications should not call API like QDialog::exec()
and QEventLoop::exec(). Experimental feature Asyncify could be used.
\li Printing is not supported
\li QDnsLookup lookups, QTcpSocket, QSsl do not work and are not supported due to the web sandbox
\li Accessibility is not supported: Qt draws application content to a canvas element and does not
use (other) native DOM elements.
\li 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.
\li 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.
\li Native styles for Windows and macOS are not supported as Wasm as a platform
is not providing that capability
\li 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 CMakeFiles.txt: set(QT_WASM_INITIAL_MEMORY, "50MB");
\li add_executable in CMakeLists.txt does not produce <target>.html or copy qtloader.js. Use
 qt_add_executable instead.
\li QWebSocket connections are supported by Emscripten only on the main thread.

\endlist

\section1 Other Topics
\section2 Qt Configure Options Reference

The following configure options are relevant when building Qt for WebAssembly from source.

\table
    \header
        \li Configure Argument
        \li Brief Description
    \row
        \li -sse2
        \li Enables SIMD support.
    \row
        \li -feature-thread
        \li Multi-threaded wasm
    \row
        \li -device-option QT_WASM_SOURCE_MAP=1
        \li Debugging option for creating source maps
    \row
        \li -feature-opengles3
        \li Use opengles3 in addition to the default opengles2
    \row
        \li -device-option QT_EMSCRIPTEN_ASYNCIFY=1
        \li Use asyncify
\endtable

\section2 Typical Download Sizes
Expected footprint (download size): Wasm modules as produced by the compiler can be
large, but compress well:

\table
\header
    \li Example
    \li gzip
    \li brotli
\row
    \li helloglwindow (QtCore + QtGui)
    \li 2.8M
    \li 2.1M
\row
    \li wiggly widget (QtCore + QtGui + QtWidgets)
    \li 4.3M
    \li 3.2M
\row
    \li SensorTag (QtCore + QtGui + QtWidgets + QtQuick + QtCharts)
    \li 8.6M
    \li 6.3M
\endtable

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.

\section2 Examples

\list
        \li \l {https://www.qt.io/web-assembly-example-industrial-panel}
               {Industrial Panel Demo}
        \li \l {https://www.qt.io/web-assembly-example-slate}
               {QMainWindow app}
        \li \l {https://www.qt.io/web-assembly-example-gallery}
               {A gallery of available controls in Qt Quick Controls}
        \li \l {https://www.qt.io/web-assembly-example-pizza-shop}
               {Web app for ordering pizzas}
\endlist

\section1 External resources

\list
    \li \l {WebAssembly Resource site}
\endlist

\section1 License

Qt for WebAssembly is available under commercial licenses from \l{The Qt Company}.
In addition, it is available under the \l{GNU General Public License, version 3}.
See \l{Qt Licensing} for further details.

*/
