This note covers the engineering behind offline visualizations. User syntax, behavior, and limitations belong in that guide.
lib/src/visualization/ owns renderer contracts, typed results, diagnostics,
revision cancellation, scheduling, generated-SVG normalization, OpenAPI
dependency resolution, and bounded memory and disk caches. Cache keys include
the engine and sanitizer versions, source, theme, render profile, options, and
hashes of local dependencies.
The Linux runner exposes a first-party platform channel backed by WebKitGTK 4.1. One reusable hidden render view handles Mermaid, PlantUML, OpenAPI parsing, SVG rasterization, and MathJax. BusyMark-owned windows are created only for the full interactive OpenAPI reference. Inline previews are Flutter SVG or PNG widgets, not embedded live browser views.
The WebKit host uses an ephemeral context and an allow-listed private resource scheme. It disables storage, cookies, media, WebRTC, developer tools, popups, permissions, context menus, and unapproved navigation. Its content security policy blocks networking, frames, objects, forms, plugins, and remote fonts. Engine SVG is treated as untrusted and normalized before Flutter or Typst sees it. The WebKit subprocess sandbox remains enabled for ordinary Linux packages and the strict Snap.
D2 runs its pinned Linux executable directly, never through a shell. Source,
time, output, dimensions, and environment are bounded, and each execution uses
a fresh temporary directory. Safe SVG remains vector output; unsupported CSS,
embedded fonts, conflicting cascade behavior, animation, or foreignObject
content is sanitized and rasterized locally.
OpenAPI parsing accepts only bounded local references anchored to the canonical workspace. The interactive Scalar view receives bundled content with agent, telemetry, authentication persistence, proxying, API requests, remote fonts, and custom fetches disabled.
Startup registers the channel and a lightweight host. A launch without a
visualization request creates no visualization WebKit context, hidden view, or
harness. There is no idle preloading or first-frame gate; the release smoke can
still render before runApp().
The first accepted render operation queues initialization on the GTK main context. Requests share one ephemeral context, view, and harness load. The renderer moves through dormant, scheduled, loading, ready, failed, and stopped states. Loading includes the harness's asynchronous ready event and its existing 15-second deadline. Dart's existing request deadlines include initialization. Malformed, oversized, unsupported, cancellation, and PNG clipboard operations do not start WebKit. Cancelling all scheduled requests avoids construction.
The harness registers its dispatcher without evaluating an engine. The first
operation imports a fixed, offline bundle for Mermaid, PlantUML, MathJax, or
OpenAPI. SVG rasterization needs only the harness. PlantUML alone loads the
bundled Graphviz script. Module evaluation is reused within the view, and
concurrent imports share the browser's module registry. Engine load failures
reject their request with visualization.hostUnavailable and retire the failed
view and its module registry. Queued work and retries use a fresh restricted
view in the same ephemeral context. Process recovery also creates a fresh
registry. Each engine retains its existing validation,
configuration, and dependencies. Math includes all offline NewCM font tables.
First use still pays for the WebKit process, harness, and requested engine;
subsequent requests reuse them. Those costs must be measured alongside plain
startup when assessing performance, including preparation that an eager
baseline completed before receiving its first request.
Opening an interactive reference first creates the shared context and its visible view, without loading the offscreen harness. A later render uses that context. Reference windows retain their normal acceleration policy.
Initialization failure fails the queue and releases the failed view; later
requests can retry. Renderer-process termination recreates the hidden view with
the same restrictions and offscreen acceleration policy. Cancellation marks
active work as responded before cancelling its callback, preventing a late
success. Shutdown removes scheduled work, cancels initialization and rendering,
fails queued calls, closes reference windows, and disconnects view callbacks.
Late channel calls return visualization.hostUnavailable until host disposal,
which detaches the channel handler. Async
callbacks retain the host until completion, check its state and view identity,
and cannot recreate resources after shutdown. Private resource callbacks own
their resource path independently of the host.
Set BUSYMARK_VISUALIZATION_TRACE=1 for opt-in monotonic timings on stderr:
context construction, view construction, offscreen realization/show, harness
dispatch, and request acceptance, start, JavaScript delivery, and completion. It logs no document source. Moving
construction to first use removes unused startup work; it does not establish
the cause or elimination of a rare WebKit construction stall.
Do not duplicate pinned versions or hashes in documentation. The authoritative sources are:
tools/visualization/package.jsonandpackage-lock.jsonfor web engines and transitive packages;tools/fetch_d2.shfor the D2 release, architecture, and verified hashes;tools/fetch_typst.shfor Typst release artifacts and hashes;linux/CMakeLists.txtandsnap/snapcraft.yamlfor bundle layout; and.github/workflows/flutter-linux.ymlfor release and Snap verification.
The web bundle is built with npm ci --ignore-scripts. Build steps verify direct
upstream artifacts and stage package metadata, licenses, and notices. Node.js is
a build dependency only.
Run the deterministic Dart suite with the bundled executables after building:
flutter analyze
BUSYMARK_D2_PATH=build/linux/x64/debug/bundle/libexec/busymark/d2 \
BUSYMARK_TYPST_PATH=build/linux/x64/debug/bundle/libexec/busymark/typst \
flutter testRun the real WebKit and D2 corpus under X11:
xvfb-run -a -s '-screen 0 1280x1024x24' \
env GDK_BACKEND=x11 LIBGL_ALWAYS_SOFTWARE=1 \
bash tools/test_web_render_host.sh
xvfb-run -a -s '-screen 0 1280x1024x24' \
env GDK_BACKEND=x11 LIBGL_ALWAYS_SOFTWARE=1 \
/usr/bin/python3 -u tools/visualization_smoke.py \
--assets build/linux/x64/debug/visualization/web \
--d2 build/linux/x64/debug/d2/linux-x86_64/d2 \
--demo test/fixtures/markdown/basic.md \
--plantuml-corpus test/fixtures/visualization/plantuml-conformance.mdThe release binary has a gated product-path smoke entry point:
verification_dir="$(mktemp -d)"
BUSYMARK_RELEASE_SMOKE=1 \
build/linux/x64/release/bundle/busymark \
--visualization-release-smoke="$verification_dir/report.json"The report must contain "ok": true; the same directory receives the generated
PDF and HTML fixtures. The Linux workflow is authoritative for compositor setup,
X11 and Wayland coverage, the strict Snap run, and required environment flags.
The native host and Python corpus harness disable hardware compositing only for
their offscreen WebKit views, before realization and after process recovery.
GTK offscreen windows cannot provide the GL context that accelerated compositing
requires. Normal startup does not require a WebKit environment override, and
Flutter and visible API-reference windows retain their normal rendering settings.
Smoke tests must not globally disable WebKit compositing; LIBGL_ALWAYS_SOFTWARE
is only used to provide Mesa rendering on headless CI displays.