WebView
Experimental. A widget that renders a useful subset of CSS 3 / HTML 5 (over CSS 2.1, HTML 4.01, CSS Level 1 and HTML 3.2) with the pure-Go
modernc.org/webviewengine. Read-only by default — a present-only view for "HTML/CSS you control", a richer alternative toTextfor things like complex bordered tables, floats, flexbox and positioned layout — with opt-in in-page JavaScript (see Scripting). It is still not a browser: there is no remote networking, and forms are not interactive yet. The API and the rendered output may change.
Live preview
A bordered, zebra-striped table with status pills — the kind of self-contained
HTML/CSS layout you'd reach past Text for:
Live preview. In the Player's built-in docs viewer (
scriptweaver --help), this rendersWebViewas a live, theme-aware widget you can interact with.
The engine paints the page into an off-screen RGBA buffer that is blitted into the widget. The view reflows to the widget's real width, scrolls when the content is taller (mouse wheel and drawn scroll bars), supports mouse text selection with Ctrl+C to copy, and reports link clicks to a callback.
Beyond the CSS 1 / HTML 3.2 basics (the box model with margin collapsing,
borders and backgrounds; fonts and text alignment; <table> with
colspan/rowspan, captions and automatic column widths; floats) it lays out the
CSS 2.1 additions — positioned layout (position: relative/absolute/fixed
with z-index and overflow clipping), inline-block, min/max sizing,
attribute and structural selectors, external <link rel=stylesheet> / @import
stylesheets, background-image, richer list markers, and the CSS 2.1 table
model (border-collapse, table-layout, border-spacing, empty-cells) — and
a useful slice of CSS 3 / HTML 5: @media queries (re-evaluated on resize),
flexbox
(display: flex with gap and flex-grow/-shrink/-basis), 2-D
transforms, custom properties (--name / var()), position: sticky,
generated content on ::before/::after, the :hover/:focus pseudo-classes,
border-radius, box-sizing and extended color (rgb()/hsl(), hex alpha);
the HTML 5 sectioning elements (<header>, <nav>, <main>, <article>,
<section>, <footer>, <figure>…), the new form-input types and data: URIs.
So moderately rich, self-contained documents lay out correctly. International
text is handled too (see Limitations).
Example
const wv = app.WebView({});
wv.pack.configure({ fill: 'both', expand: true });
wv.loadHtml(`
<!doctype html>
<html><head><style>
body { font-family: sans-serif; color: #222; margin: 16px; }
h1 { color: #1a5276; border-bottom: 2px solid #1a5276; }
table { border-collapse: collapse; }
th, td { border: 1px solid #999; padding: 4px 8px; }
th { background: #1a5276; color: white; }
a { color: #2e86c1; }
</style></head>
<body>
<h1>Release notes</h1>
<p>This is <b>bold</b>, <i>italic</i>, and a
<a href="https://example.com/changelog">link</a>.</p>
<table>
<tr><th>Widget</th><th>Status</th></tr>
<tr><td>Card</td><td>shipped</td></tr>
<tr><td>WebView</td><td>experimental</td></tr>
</table>
</body></html>`);
// The widget never navigates on its own — you decide what a link does.
wv.onLinkClick((href) => sw.sys.openURL(href));
Methods
| Method | Description |
|---|---|
loadHtml(html) |
Render an HTML string. Relative sub-resources (none, for an inline string) resolve against the process working directory; prefer inline styles and data: URIs here. |
loadUrl(url) |
Render a local document: a file path, a file:// URL, or a //zipfs:/… bundle path. The document's relative images and stylesheets resolve against it. Remote URLs (http, https, …) are refused — this view never reaches the network. |
onLinkClick(fn) |
Register a callback invoked with the link's href (a string) when the user clicks a hyperlink. Pass null to clear it. The widget itself does not follow links. |
text() |
Return the rendered document's visible text in reading order — useful for "copy all", search, or seeding an accessible description. |
enableScript() |
Opt this view into in-page JavaScript (see Scripting). Call it before loadHtml/loadUrl so the first document's <script>s run. Returns this; scripting is off by default. |
A pointer drag selects text and Ctrl+C copies the selection to the clipboard.
Options
WebView is a display widget (a themed frame holding the rendered image). It
takes the common container options:
| Option | Type | Description |
|---|---|---|
script |
boolean | Opt into in-page JavaScript at construction — equivalent to calling enableScript() before the first load. Default false. See Scripting. |
width |
number | Requested width in pixels. Usually you let geometry drive it (pack/grid/place) and the page reflows to fit. |
height |
number | Requested height in pixels. |
padding |
string | Inner padding, e.g. '8' or '8 4'. |
cursor |
string | The mouse cursor over the view. |
Scripting (opt-in)
A WebView is a read-only HTML/CSS surface by default. Pass
{ script: true } at construction, or call enableScript() before the first
load, to opt into the engine's JavaScript + DOM subsystem so in-page <script>
runs: a mutable DOM with coalesced reflow, events (addEventListener,
capture/bubble, preventDefault), timers and microtasks, native Promises and
async/await, ES modules, and a read-only getComputedStyle.
Page scripts run on the Player's own vendored QuickJS, in a separate runtime
with their own globals — they don't share your app's environment (no app/sw)
and can't drive the rest of the UI. Scripting is not remote-capable either: a
page's fetch / XMLHttpRequest and every sub-resource load stay local-only
(files and the in-process VFS), exactly like loadUrl. With scripting off,
<script> elements are ignored.
const wv = app.WebView({ script: true }); // or: app.WebView({}).enableScript()
wv.pack.configure({ fill: 'both', expand: true });
wv.loadHtml(`<button id="b">clicked 0×</button><script>
let n = 0;
document.getElementById('b').addEventListener('click', (e) => {
e.target.textContent = 'clicked ' + ++n + '×';
});
</script>`);
Limitations
This is an early, deliberately narrow widget. Known constraints:
- No full-page zoom yet. The page renders at 1× (HiDPI display scaling
aside); the engine's
SetZoomis not exposed. Vertical and horizontal scrolling do work — mouse wheel and drawn scroll bars when the content overflows the widget. - Fonts are the engine's own, not the Tk theme fonts, so body text will not pixel-match the rest of the UI. Latin / Greek / Cyrillic use the bundled Go fonts; text in scripts those faces don't cover (CJK, Arabic, Hebrew, Indic, Thai, …) falls back to the operating system's installed fonts. So international HTML renders real glyphs — with correct per-script shaping, right-to-left ordering and CJK line breaking — when a covering system font is present, and as missing-glyph boxes when one is not.
- Opaque to screen readers. The rendered page is an image; assistive
technology sees no structure. For accessible body text use
Textor MarkView; reserveWebViewfor presentation that the other widgets cannot do (e.g. intricate tables).text()can seed an accessible description. - Local only.
loadUrlreads local files and the in-process VFS; it never performs network I/O. Binary assets (images) from a//zipfsbundle are limited by the text-oriented VFS bridge — prefer on-disk files ordata:URIs for images. - Forms are not interactive yet.
<input>,<textarea>and<select>render, but they don't take keyboard focus and<form>s don't submit. - A bounded CSS 3 subset. Frames, CSS Grid and CSS
transitions/animations are not implemented, and the rendered output may
still change as the engine grows. (In-page
<script>is off unless you opt in — see Scripting.)