Qt for WebAssembly

morten.sorvig@qt.io

Demo: Qt Design Studio Viewer

https://qt-webassembly.io/designviewer/

Agenda

Motivation

Getting Started

Development Topics

  • local file access
  • networking
  • multithreading
  • drawing to multiple canvases

Work-in-Progress Topics


Presentation Source: git.qt.io/mosorvig/qtws19-wasm

Web Applications

Build once, deploy everywhere

Security through sandboxing

No installation required

Publish instantly; no app-store review

Warning: Work In Progress!

No system font support (bring your own fonts)

No accessibility/screen reader support

No support for re-entering the event loop - e.g. with QDialog::exec()

Getting Started

Install Emscripten, build "Hello World"

Build Qt from source

Build Qt for WebAssembly

Enable Qt Creator WebAssembly Plugin (4.11)

Emscripten: emsdk

Action Description
List ./emsdk list --old
Install ./emsdk install sdk-1.38.27-64bit
Activate ./emsdk activate --embedded sdk-1.38.27-64bit
Environment source ./emsdk_env.sh
Build em++ hello.cpp -o hello.html

Emscripten: hello.html

Qt from git

Action Description
Clone git clone https://code.qt.io/qt/qt5.git
Get Modules ./init-repository --module-subset= "qtbase, qtdeclarative, -qtwebengine"
Configure ./configure -release -no-thread -static -nomake examples -xplatform wasm-emscripten
Build make module-qtbase module-qtdeclarative

Application Build Output

Name Source Purpose
app.html Qt HTML container
qtloader.js Qt JS API for loading Qt apps
app.js emscripten app JavaScript runtime
app.wasm emscripten app binary

Wasm Binary Sizes

Typical compression ratios: 3:1 - 5:1

Modules gzip brotli
Core Gui 2.8MB 2.1MB
Core Gui Widgets 4.3MB 3.2MB
Core Gui Widgets Quick Charts 8.6MB 6.3MB

Development Web Server

Purpose: serve files from localhost and/or local ip, provide secure context, set headers

Protocol
file:// does not work
http://localhost/ python3 -m http.server
https:// use custom server

pyserver

https server in ~40 lines of python (QTBUG-79087)

Generates certifactes using mkcert

Serves from localhost and local ip

Sets headers required for multi-threading

Local File Access

The web sandbox prevents direct file system access

The web platform has API for opening a file dialog, and starting a file download

Qt has new QFileDialog API which covers this use case

Local File Access

Callback-based getOpenFileContent()


QFileDialog::getOpenFileContent("*.txt",
    [](const QString &fileName,
       const QByteArray &fileContent) {

       qDebug() << "Got" << fileContent.count()
                << "bytes from" << fileName;
});

Fire-and-forget saveFile()


QByteArray content = ...;
QString fileNameHint = ...;
QFileDialog::saveFileContent(content, fileNameHint);
                    

Networking

API Protocol Notes
QNetworkAccessManager Http(s) Same Origin / CORS host
QWebSocket WebSocket Any host
QAbstractSocket WebSocket Websockify forwarding host

Multithreading

mandelbrot demo

mandelbrot source

Enabling Multithreading

Configure Qt with -feature-thread

Configure App

  • QMAKE_WASM_TOTAL_MEMORY
  • QMAKE_WASM_PTHREAD_POOL_SIZE

Enable threading in browser

  • javascript.options.shared_memory (Firefox)
  • Headers: Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy

Multithreading Tips

Read Emscripten's Pthreads Support documentation

Don't block the main thread!

That includes when calling pthread_join()

Rendering to Multiple Canvases

Draw to multiple canvases from a single wasm instance

Each canvas maps to a QScreen

multicanvas demo

multicanvas source

Rendering to Multiple Canvases

Add canvas from JavaScript code


let canvas = ...
qtloader.addCanvasElement(canvas);

Handle screenAdded signal


QObject::connect(qApp, &QGuiApplication::screenAdded, 
                 [=](QScreen *screen) {
    QWindow *window = new AppWindow();
    window->setScreen(screen);
    window->showFullScreen();
});

The Qt Quick Web Runtime

Binary distribution of Qt Quick

Install with e.g. "git clone" or "npm install"

JavaScript API:


let containerElement = ...
let qmlSourceCode = ...
let qtQuick = new QtQuickRuntime({
    container: containerElement
});
qtQuick.setQmlSourceCode(qmlSourceCode)
qtQuick.load()

Watch QTBUG-74293 for updates

Qml Live Editor

Accessibility Prototype

Work-in-progress support for screen readers

accessibility demo

Thanks!

doc.qt.io/qt-5/wasm.html

wiki.qt.io/Qt_for_WebAssembly

git.qt.io/mosorvig/qtws19-wasm