Minimalist Browser
A small browser shell: URL bar, back, forward and reload, and an <iframe> filling the rest of the window.
This is how you embed web content inside a GTK app. Write against HTMLIFrameElement the way you would on a web page, and on GJS you get a full WebKit.WebView behind it, with cookies, JavaScript, CSS and postMessage all working. Reports, help pages, OAuth flows, third-party widgets: same pattern for all of them.
Live demo
Section titled “Live demo”Run it on GJS
Section titled “Run it on GJS”This is one of two showcases that declare ["gjs"] rather than all four runtimes. Its content area is a WebKit.WebView reached through a typelib, so it ships no --app node bundle and --runtime node tells you so instead of crashing:
gjsify showcase minimalist-browserYou need WebKitGTK 6.0 installed. gjsify system-check tells you if it is missing.
A GTK 4 window opens with an Adw.HeaderBar: flat navigation buttons, a Gtk.Entry for the URL, and a home button. Below it a “Demo pages” strip links to the built-in pages, and a status bar sits at the bottom.
The URL bar takes two kinds of address:
page:welcome,page:about,page:postmessageandpage:adwaitaload built-in pages from an in-memory library.- A real URL like
https://example.comis loaded by WebKit.
Every built-in page posts { type: 'page-loaded', title, url } to its parent on DOMContentLoaded, and the status bar shows it. That round trip is the same code in both targets.
Run it in the browser
Section titled “Run it in the browser”import { mount } from '@gjsify/example-dom-minimalist-browser/browser';
const handle = mount(document.getElementById('app')!, { homeUrl: 'page:welcome',});
handle.navigate('https://example.com');The same input and button handlers, wrapped around a real <iframe>.
What it uses
Section titled “What it uses”| Package | What it needs |
|---|---|
@gjsify/iframe | HTMLIFrameElement plus IFrameBridge, which is a WebKit.WebView subclass |
@gjsify/message-channel | MessageEvent and the postMessage transport across the frame boundary |
@gjsify/dom-elements | HTMLInputElement for the URL bar and the click handling |
@gjsify/event-bridge | GTK clicks on the toolbar buttons |
| Libadwaita | Adw.ApplicationWindow, Adw.HeaderBar and Adw.ToolbarView in the GTK build |
Pick the right iframe API
Section titled “Pick the right iframe API”IFrameBridge gives you two APIs, and either is fine.
The standard one is iframe.src, iframe.srcdoc, iframe.contentWindow.postMessage() and addEventListener('message'). Use it when you want code that also runs in a browser, which is what this showcase does.
The WebKit one is on the bridge itself: loadUri(), loadHtml(), postMessage(), goBack(), goForward(), canGoBack, canGoForward, and reload() inherited from WebKit.WebView. Use it when you want WebKit’s own back and forward list rather than one you track yourself.
On GJS, contentWindow is null until the first navigation finishes. Re-attach your message listener from onReady() after each load, the way src/gjs/gjs.ts does. A real <iframe> keeps contentWindow across navigations, so the browser build does not need this.
Copy the structure
Section titled “Copy the structure”showcases/dom/minimalist-browser/
src/browser-demo.tsholdsBrowserCore: the history stack, thepostMessagewiring and the built-inpage:<name>library. It runs unchanged in both targets because it talks to a duck-typedIFrameHandle, which both a real<iframe>andIFrameBridge.iframeElementsatisfy.src/gjs/gjs.tsis theAdw.Applicationshell drivingIFrameBridge.src/browser/browser.tsexportsmount();browser-main.tsruns it full-page.
Both targets keep their own history stack rather than calling into the frame’s history, because browsers block contentWindow.history.go(-1) across origins. Doing it the same way in both keeps the shared core honest.
Related
Section titled “Related”- Bridge widgets puts
IFrameBridgenext to the canvas, WebGL and video bridges and says when to reach for each. - Adwaita widgets covers the header bar and toolbar view this shell is built from.