AURA in the browser

AURA can run entirely in the browser as a WebAssembly application. The browser port uses the existing C++ scene graph, Skia, SDL3, parser, layout code and bindings. It does not translate the interface into HTML widgets. The initial target is interactive AURA web applications; the playground uses the same runtime.

Three entry points

Application What runs in the browser?
aura-hello The small example with C++ generated at build time and an interactive counter
aura-demo The complete gallery as a C++ application, with all eight pages and explicit platform limitations
Playground Editable AURA source beside an interactive preview; changes use the dynamic loader

The standalone examples exercise the compiled execution path. The playground exercises the dynamic path with the same parser and scene graph. The editor does not compile C++. Its small examples react through inline scripts; the complete demo's controllers are already part of its Wasm build.

The demo supports URLs such as:

text
/web/demo.html?language=en
/web/demo.html?dynamic=1&language=en
/web/demo.html?page=motion

dynamic=1 loads the bundled .aura files at runtime. The project files and C++ controllers of the entire gallery cannot currently be edited in the playground.

Build

Requirements are a native C++20 compiler for aurac, CMake, Ninja, Git, Python 3 and an activated Emscripten SDK 4.0.23. Skia is pinned to a specific m144 revision. All Skia and AURA archives must use matching Wasm, threading and exception settings; native Skia libraries cannot be used for this.

From the AURA checkout:

bash
# Install and activate SDK 4.0.23 first.
# These directory names are examples and can be chosen freely.
tools/web/build-skia.sh /path/to/emsdk /path/to/skia
tools/web/build.sh /path/to/emsdk /path/to/skia build/web

build-skia.sh downloads the pinned Skia source revision and required dependencies, then builds real C++ libraries for Wasm. The port does not use a second CanvasKit JavaScript rendering layer. An existing Skia checkout at another revision is not switched automatically.

build.sh first builds a native host aurac, then aura-hello, aura-demo and the dynamic preview. Output is written to build/web/web/:

text
hello.html
hello.js / hello.wasm / hello.data
demo.html
... associated JavaScript, Wasm and resource files
preview.html
... associated JavaScript, Wasm and resource files

The exact arrangement of additional worker files depends on the Emscripten build. Always serve the complete output directory, not just the HTML files. AURA_WEB_JOBS limits parallel build jobs; without it, the script uses at most eight.

Run locally and integrate the website

For the standalone applications:

bash
python3 tools/web/serve.py build/web/web

The server prints its local URL, by default http://127.0.0.1:8798. It includes the headers required for browser threads.

For the website, copy the complete browser output into docs/website/web/. The website build copies it into dist/web/:

bash
mkdir -p docs/website/web
cp -a build/web/web/. docs/website/web/
python3 docs/website/build.py
python3 docs/website/check.py
python3 tools/web/serve.py docs/website/dist

Documentation can still be built without the browser payload; the playground explicitly reports the missing runtime. It does not simulate successful AURA execution. The playground uses the real Wasm application.

Hosting and browser threads

The first port uses Emscripten pthreads and SharedArrayBuffer, including for existing background tasks. The top-level website therefore needs cross-origin isolation and HTTPS in production. Responses must include these headers:

text
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

The playground page itself must be served this way; headers only on its embedded preview are insufficient. Resources load from the same origin. Any future external images, scripts or frames must use the appropriate CORS/CORP rules.

The website build generates an Apache .htaccess with these headers and the application/wasm MIME type. This requires mod_headers, mod_mime and suitable AllowOverride permissions. Other servers need equivalent configuration. A plain python -m http.server does not set these headers.

Lifetime and event loop

Natively, Application::run() blocks until the application exits. In the browser, run() registers frame callbacks and returns. The browser continues to call the application afterwards. The app, context, controllers and UI objects used by callbacks must therefore outlive the return from main().

A small application can ensure this lifetime with static objects:

cpp
int main()
{
    static AuraApplication app({.title = "Hello"});
    if (!app.init()) return 1;
    static AuraBasicContext<> ctx(app);
    static auto ui = views::Hello::build(app.addFrame(), ctx);
    return app.run();
}

In larger applications, one long-lived application object can own the context, controllers and scene. A browser callback must not keep using a local variable whose destructor runs when main() returns. Do not block the browser event loop with a separate wait loop.

Edit live

The playground sends source files as data to a same-origin preview frame. The real parser and semantic rules check the files there; the loader then constructs a new scene graph. Only a successful build replaces the previous scene. On syntax or construction errors, the last valid preview remains interactive; diagnostics show a file, line and column where available.

Every successful change restarts the example state. “Update live” briefly batches typing; “Run” or Ctrl/⌘+Enter executes immediately. “Reset example” restores the original source. Click a diagnostic to select its location in the editor. Hello, slider and text input are the first editable examples.

Source code is not sent to a server for execution. JavaScript and Lua inside AURA handlers run in aura-script, not through eval in the website JavaScript. Existing script limits remain active. The frame checks message origin and sender; update responses are sequenced so old responses cannot overwrite newer diagnostics.

Initial port limitations

Area Browser support
Widgets, text, layout, states, bindings, animations Shared C++ code; the demo shows the available features
Resources Bundled files in the virtual filesystem; arbitrary local paths on the visitor's machine are inaccessible
Tasks and timers Browser threading/event-loop integration; no separate blocking UI loop
HTTP client A browser adapter is not implemented yet; aura-net is unavailable in this port
HTTP servers, custom TLS servers/certificate tests, remote HTTP server Unavailable
Child processes, SCSI, hotplug/usbmuxd and native system data Unavailable; the demo reports the limitation instead of pretending to provide results
CEF WebView Unavailable; WebView is not a browser-inside-the-browser substitute
HEIF/HEIC Disabled in this build

The playground limits an update to 32 files, 256 KiB per file and 1 MiB of source in total. Paths must be valid relative .aura paths without directory traversal. Nesting and component expansion are also limited to 128 levels and source to 100,000 tokens; recursive component cycles are rejected. These limits apply to the editable preview, not to the size of a deployed Wasm application.

In this initial browser runtime, range() is limited to 10,000 entries and rejects non-finite values. At most 20,000 scene nodes may exist at once, including both a new and the still-visible old preview. Applications can adjust the node budget at build time using AURA_WEB_NODE_LIMIT. These runtime limits also cover later binding rebuilds.

Rendering uses a canvas. Complete semantic browser accessibility, browser-style text selection, comprehensive mobile keyboard/IME testing and validation across all browsers remain open work. A working example does not mean every native API is available on the web.

Each application currently ships its own compiled runtime files. Download size, startup time, cache behaviour and memory consumption need to be measured for specific applications. Server-side compression and suitable caching can be added later; no blanket performance claim is made here.

Expression depth is limited to 128 levels by parseBounded() during parsing; long flat operator sequences and when chains are checked before constructing deep ASTs. Bundled third-party notices.

Documentation source: tools/web, tools/aura-web · Download Markdown

Search for topics, widgets or APIs.