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:
/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:
# 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/:
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:
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/:
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:
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:
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.
tools/web, tools/aura-web · Download Markdown