A small, multi-tab demo web browser written in Java 21 + Swing. Built as a showcase for the swingwebview component (the JComponent that embeds a native WebView -- WebKit on macOS / Linux, Chromium WebView2 on Windows).
Everything a "basic browser" needs, deliberately in as little Swing
code as possible so the
WebViewComponent
APIs being demonstrated stand out:
- Address bar -- type a URL or a search query. Bare hostnames are
auto-
https://'d; anything that doesn't look like a URL is forwarded to DuckDuckGo. - Back / forward / reload / home -- per-tab navigation history,
driven by a JS shim that hooks
pushState/replaceState/popstateso the back stack reflects link clicks and SPA navigation, not only URL-bar input. - Tabs -- new tab, middle-click or ✕ to close. Each tab owns its
own
WebViewComponentand its own console buffer. The window title and tab strip both follow the active page's<title>. - Popups -- View → Popup Behavior selects how browser-initiated
popups (
window.openandtarget="_blank"links / forms) are handled, for tabs opened from then on:- Native (default) -- host each popup in a separate native
top-level window (
PopupDisposition.NATIVE_WINDOW). - Tab -- open the popup in a new tab using swingwebview's
adopt strategy (
PopupDisposition.ADOPT+WebViewComponent.adoptPopup). The tab hosts the engine's own opener-linked child, so a<form method="post" target="…">popup keeps its POST body andwindow.opener/postMessagekeep working -- unlike blocking the popup and re-opening its URL (a GET, which would lose both). - None -- suppress popups: a document-start shim rewrites
window.open/target="…"to the current window, so the popup navigates in place.
- Native (default) -- host each popup in a separate native
top-level window (
- User Agent -- View → User Agent selects the
User-AgentHTTP header sent for requests: Default (the engine's built-in UA), or a modern desktop Safari, Chrome, Edge, or Firefox string (viaWebViewComponent.setUserAgent). Applies to new tabs and reloads the current tab so the change takes effect immediately. - View Headers -- View → View Headers… opens a diagnostic dialog
for figuring out why a site treats the browser differently. Two tabs:
Engine response re-fetches the current URL from inside the page, so
the response headers reflect the engine's real session (cookies, the
User-Agent actually sent, real TLS) -- JavaScript can't read the
outgoing request headers, so those aren't shown here. HTTP probe
issues the same request from a Java
HttpClient, showing the full request headers sent and the response status/headers/body, and lets you re-run it under any of the User-Agent presets. The probe is a separate session with a different TLS fingerprint, so treat it as an approximation of the real request. - Bookmarks -- starable on the toolbar, persisted to
~/.swingwebbrowser/bookmarks.tsv. Saved bookmarks appear as menu items under Bookmarks; full management dialog via Bookmarks → Manage Bookmarks…. - History -- every navigation is appended (with timestamp) to
~/.swingwebbrowser/history.tsv. History → Show History… opens a sortable table you can double-click to reopen any URL. - Developer console -- collapsible bottom panel that captures
every
console.{log,info,warn,error,debug}call from the active tab (colorised by level), plus a one-line JS eval prompt and a one-click "Open native DevTools" button (uses the WebView2 / WebKit inspector on Windows / Linux).
- JDK 21 (build) / 8 or newer (runtime -- the project compiles to Java 21 bytecode but only uses APIs available since 8).
- Maven 3.9+.
- Platform-specific WebView runtime:
- macOS -- nothing extra; uses the system WKWebView.
- Linux --
libgtk-3-0,libwebkit2gtk-4.1-0(or-4.0-37on older distros),libxt6. - Windows -- the Microsoft Edge WebView2 Runtime. Ships with Windows 11 and current Edge; on older Windows install the Evergreen runtime from https://developer.microsoft.com/microsoft-edge/webview2/.
The native libraries are bundled inside the
ca.weblite:webview:1.1.0
artifact on Maven Central -- no extra install step for the library
itself.
# Linux / macOS
./run-linux.sh # or ./run-mac.sh
# Windows
run-windows.batThe launcher scripts call mvn package the first time, then java
with the built jar plus its dependencies on the classpath.
Or, manually:
mvn package
java -cp "target/swingwebbrowser-1.0-SNAPSHOT.jar:target/lib/*" \
ca.weblite.swingwebbrowser.SwingWebBrowser
# Pass an initial URL:
java -cp "target/swingwebbrowser-1.0-SNAPSHOT.jar:target/lib/*" \
ca.weblite.swingwebbrowser.SwingWebBrowser https://openjdk.orgWebViewComponent.create() picks heavyweight (macOS / Windows) or
lightweight (Linux) by default. To force a mode:
./run-linux.sh --heavyweight # GTK reparenting; native input
./run-linux.sh --lightweight # offscreen + Swing-composited (default)Or set the system property directly:
-Dca.weblite.webview.mode=heavyweight|lightweight.
| Action | Shortcut |
|---|---|
| Focus URL bar | ⌘L / Ctrl-L |
| Back / Forward | Alt-← / Alt-→ |
| Reload | ⌘R / Ctrl-R |
| New Tab | ⌘T / Ctrl-T |
| Close Tab | ⌘W / Ctrl-W |
| Bookmark this Page | ⌘D / Ctrl-D |
| Toggle Developer Console | ⌘⇧J / Ctrl-Shift-J |
| Quit | ⌘Q / Ctrl-Q |
A short tour of the source files (under
src/main/java/ca/weblite/swingwebbrowser/):
| File | Responsibility |
|---|---|
SwingWebBrowser.java |
main() -- installs FlatLaf and opens a BrowserFrame. |
BrowserFrame.java |
The JFrame -- toolbar, tab strip, menu bar, and the collapsible dev console. |
BrowserTab.java |
One WebViewComponent plus its back/forward stacks and console buffer. Injects a JS shim into every loaded page so pushState / popstate / hashchange navigations and <title> changes are observable from Java. |
DevConsolePanel.java |
Captures every console.* call from the active tab, renders them with per-level colours, runs JS from the eval prompt, opens the native DevTools window. |
BookmarkStore.java |
Tab-separated file persistence for bookmarks. |
HistoryStore.java |
Append-only TSV log of every navigated URL. |
In rough order of how they show up in the source:
WebViewComponent.create()-- factory that picks heavyweight or lightweight per platform.setUrl(String)-- navigation; all back / forward / bookmark / history-replay paths go through this single method.setDebug(true)-- enables the platform DevTools / inspector.addOnBeforeLoad(String)-- injects the pushState-and-title-tracking JS shim at document-start for every navigation.addJavascriptCallback(String, JavascriptCallback)-- the JS shim posts URL/title updates back via awindow.__swb_nav(...)callback. Demonstrates the Java-side parsing of the bind-shim envelope ({"name":..., "seq":..., "args":["..."]}).eval(String)-- the dev console's one-line prompt feeds straight into this.addConsoleListener(ConsoleListener)-- the dev console subscribes to every capturedconsole.*line.openDevTools()-- one-click access to the platform's native inspector.
MIT. See LICENSE.
- WebView library: swingwebview by Steve Hannah.
- Original webview: webview by Serge Zaitsev.