Skip to content

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.

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:

Terminal
gjsify showcase minimalist-browser

You 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:postmessage and page:adwaita load built-in pages from an in-memory library.
  • A real URL like https://example.com is 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.

TypeScript
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>.

PackageWhat it needs
@gjsify/iframeHTMLIFrameElement plus IFrameBridge, which is a WebKit.WebView subclass
@gjsify/message-channelMessageEvent and the postMessage transport across the frame boundary
@gjsify/dom-elementsHTMLInputElement for the URL bar and the click handling
@gjsify/event-bridgeGTK clicks on the toolbar buttons
LibadwaitaAdw.ApplicationWindow, Adw.HeaderBar and Adw.ToolbarView in the GTK build

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.

showcases/dom/minimalist-browser/

  • src/browser-demo.ts holds BrowserCore: the history stack, the postMessage wiring and the built-in page:<name> library. It runs unchanged in both targets because it talks to a duck-typed IFrameHandle, which both a real <iframe> and IFrameBridge.iframeElement satisfy.
  • src/gjs/gjs.ts is the Adw.Application shell driving IFrameBridge.
  • src/browser/browser.ts exports mount(); browser-main.ts runs 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.

  • Bridge widgets puts IFrameBridge next 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.