Skip to content

Add Windows WebView2 backend, port jsaddle-webkitgtk to GTK4/WebKitGTK 6.0 - #166

Merged
hamishmack merged 6 commits into
masterfrom
hkm/webview2-webkitgtk6
Jul 5, 2026
Merged

Add Windows WebView2 backend, port jsaddle-webkitgtk to GTK4/WebKitGTK 6.0#166
hamishmack merged 6 commits into
masterfrom
hkm/webview2-webkitgtk6

Conversation

@hamishmack

Copy link
Copy Markdown
Member

Summary

This PR adds a Windows backend, modernises the Linux backend, and makes the whole repo solve on every platform from one flat cabal.project.

  • New package jsaddle-webview2 — Windows backend hosting the app in a native Win32 window with a Microsoft Edge WebView2 control.
  • jsaddle-webkitgtk ported to GTK4 + WebKitGTK 6.0 — replaces the WebKit1/GTK3 code (and supersedes jsaddle-webkit2gtk, whose webkit2gtk-4.0 dependency no longer exists in nixpkgs).
  • cabal.project restructured — flat unconditional packages: list with per-package buildable: False platform guards.
  • Flake: haskell.nix bump + headless runtime smoke test for the WebKitGTK backend.

jsaddle-webview2

Mirrors jsaddle-wkwebview's design: async batches are evaluated as runJSaddleBatch(...) and results come back via chrome.webview.postMessage; synchronous results use window.prompt("JSaddleSync", …) intercepted natively in ScriptDialogOpening.

The C shim (cbits/WebView2Shim.c) is plain-C COM against the official WebView2.h — only the header is needed at compile time (fetched from the WebView2 NuGet package in nix/hix.nix for cross builds); WebView2Loader.dll is loaded dynamically at run time and the WebView2 runtime ships with Windows 11. WebView2 is single-threaded (STA), so all ICoreWebView2* calls are marshalled to the UI thread via PostMessage.

Cross-compiles from nix via hydraJobs.<system>.x86_64-w64-mingw32 (lib + demo exe verified). Not yet run on real Windows hardware — the prompt-bridge length limits and first-navigation timing are the known risks to check there.

jsaddle-webkitgtk (GTK4 / WebKitGTK 6.0)

Rewritten against gi-gtk4 / gi-webkit 6.x / gi-javascriptcore6: GLib main loop instead of gtk_main, windowSetChild/scrolledWindowSetChild, webViewEvaluateJavascript, and the WebKit 6 script-message + script-dialog (JSaddleSync prompt) bridge. Requires haskell.nix master (the WebKitGTK 6.0 pkg-config map entries — webkitgtk-6.0, javascriptcoregtk-6.0 — landed there recently); the flake input is bumped accordingly.

A new demo executable has a --smoke mode that asserts a full bridge round trip (async eval, plus a JS→Haskell callback through the synchronous prompt path) and exits 0/1.

Per-platform packaging

cabal.project if os(...) conditionals do not apply to packages: fields (silently ignored), so platform gating now lives in each .cabal file as buildable: False guards covering every component; the solver ignores dependencies of non-buildable components. Notes:

  • jsaddle-terminal's demo moved behind a manual demo flag (default off): reflex-dom-core caps base below new GHCs and otherwise makes the whole project unsolvable.
  • jsaddle-webkit2gtk stays out of the project: a missing pkg-config package (webkit2gtk-4.0) fails the solve regardless of buildable: guards.

CI / testing

  • Plans verified for x86_64-linux, x86_64-w64-mingw32 cross, javascript-unknown-ghcjs cross, and aarch64-darwin — each contains exactly the intended components.

  • New flake check checks.<linux>.webkitgtk-smoke (also in hydraJobs.checks) runs the WebKitGTK demo's --smoke mode headlessly under Xvfb inside the nix sandbox (dbus config from the store, WebKit's bubblewrap sandbox off, llvmpipe software EGL) and passes iff the bridge round-trips:

    SMOKE eval 6*7 = 42.0
    SMOKE callback got 123.0
    SMOKE-OK
    

Rewrite the backend against gi-gtk4 / gi-webkit 6.x / gi-javascriptcore6
(webkit2gtk 4.0 has been removed from nixpkgs; 6.0 is the current API):
GLib main loop instead of gtk_main, windowSetChild/scrolledWindowSetChild,
webViewEvaluateJavascript, and the WebKit 6 script-message +
script-dialog (JSaddleSync prompt) bridge.

Add a demo executable with a --smoke mode that asserts a full bridge
round trip (async eval + a JS→Haskell callback through the synchronous
prompt path) and exits 0/1, suitable for headless CI under xvfb-run.
New jsaddle backend hosting the app in a native Win32 window with a
Microsoft Edge WebView2 control, mirroring jsaddle-wkwebview's design:
async batches via chrome.webview.postMessage, synchronous results via a
window.prompt("JSaddleSync", …) intercepted in ScriptDialogOpening.

The C shim (cbits/WebView2Shim.c) does plain-C COM against the official
WebView2.h; only the header is needed at compile time —
WebView2Loader.dll is loaded dynamically at run time and the runtime
ships with Windows 11.  All ICoreWebView2 calls are marshalled to the
UI thread via PostMessage (WebView2 is single-threaded/STA).

nix/hix.nix fetches the header from the WebView2 NuGet package and
passes it via extra-include-dirs for windows cross builds
(hydraJobs.x86_64-linux.x86_64-w64-mingw32).
cabal.project 'if os(...)' conditionals do NOT apply to 'packages:'
fields (they are silently ignored), so list every package
unconditionally and gate platforms in each .cabal with buildable: False
covering every component; the solver ignores dependencies of
non-buildable components, so this solves on all platforms.

- jsaddle-wkwebview / jsaddle-webkit2gtk: accurate platform guards
  (osx/ios and linux respectively, ghcjs stubs allowed).
- jsaddle-terminal: unix-only, and the demo exe moves behind a manual
  'demo' flag (default False) — reflex-dom-core caps base below new
  GHCs, which otherwise makes the whole project unsolvable.
- jsaddle-webkit2gtk stays out of the project: it needs pkg-config
  webkit2gtk-4.0, which no longer exists in nixpkgs, and a missing
  pkg-config package fails the solve regardless of buildable: guards.
  jsaddle-webkitgtk (GTK4 / WebKitGTK 6.0) replaces it.

Verified: hydraJobs plans for x86_64-linux, x86_64-w64-mingw32 cross,
javascript-unknown-ghcjs cross, and aarch64-darwin all solve and
contain exactly the intended components.
haskell.nix master now carries the WebKitGTK 6.0 pkg-config map entries
(webkitgtk-6.0 / javascriptcoregtk-6.0), so gi-webkit solves with plain
upstream — no override-input needed.

checks.<linux>.webkitgtk-smoke (also in hydraJobs.checks) runs the
jsaddle-webkitgtk demo's --smoke mode under Xvfb in the build sandbox:
dbus pointed at its store session.conf, WebKit's bubblewrap sandbox off
(cannot nest in the nix sandbox), and software EGL via llvmpipe with
glvnd/dri/gbm loader paths set to the store (no /run/opengl-driver).
Passes iff the jsaddle bridge round-trips (SMOKE-OK).

Also ignore nix result symlinks.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Windows-native JSaddle backend via Microsoft Edge WebView2, modernizes the Linux WebKitGTK backend to GTK4/WebKitGTK 6.0, and restructures packaging/Nix so the repo can solve and check consistently across platforms.

Changes:

  • Introduces jsaddle-webview2 (Win32 + WebView2) with a C shim and Haskell transport mirroring jsaddle-wkwebview.
  • Ports jsaddle-webkitgtk to GTK4 + WebKitGTK 6.0 and adds a headless --smoke demo mode plus a Nix/flake check to run it under Xvfb.
  • Makes multi-platform solving work from a flat cabal.project by pushing platform gating into per-package buildable: False guards.

Reviewed changes

Copilot reviewed 17 out of 19 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
nix/webkitgtk-smoke.nix Adds a headless WebKitGTK runtime smoke test derivation (Xvfb + dbus-run-session + llvmpipe).
nix/hix.nix Bumps compiler, adds WebView2 header fetch for cross builds, and expands cross platforms.
jsaddle-wkwebview/jsaddle-wkwebview.cabal Tightens native buildability to macOS/iOS (with ghcjs/javascript stubs elsewhere).
jsaddle-webview2/src/Language/Javascript/JSaddle/WebView2/Internal.hs Implements the JSaddle transport wiring for WebView2 (async + prompt-based sync bridge).
jsaddle-webview2/src/Language/Javascript/JSaddle/WebView2.hs Public API for running JSaddle apps in a WebView2-backed native window.
jsaddle-webview2/README.md Documents how WebView2 backend works and how to build/run it.
jsaddle-webview2/LICENSE Adds MIT license file for the new package.
jsaddle-webview2/jsaddle-webview2.cabal Defines the new Windows-only package + demo component.
jsaddle-webview2/demo/Main.hs Adds a minimal demo app (counter + ticker).
jsaddle-webview2/cbits/WebView2Shim.c Provides the Win32/WebView2 hosting shim, message-loop marshalling, and JS bridge integration.
jsaddle-webkitgtk/src/Language/Javascript/JSaddle/WebKitGTK.hs Ports backend to GTK4/WebKitGTK 6.0 and updates the bridge mechanism.
jsaddle-webkitgtk/jsaddle-webkitgtk.cabal Updates dependencies and platform gating; adds a Linux-only demo executable.
jsaddle-webkitgtk/demo/Main.hs Adds demo with --smoke mode for CI headless runtime verification.
jsaddle-webkit2gtk/jsaddle-webkit2gtk.cabal Adds explicit Linux-only native buildability guard (plus ghcjs/javascript stubs).
jsaddle-terminal/jsaddle-terminal.cabal Adds demo flag gating and marks Windows as non-buildable for POSIX raw-mode terminal code.
flake.nix Wires the new webkitgtk-smoke check into checks and hydraJobs on Linux.
flake.lock Updates flake inputs (notably haskell.nix / nixpkgs pins) to support newer GTK/WebKit mapping.
cabal.project Flattens unconditional package list and documents why per-package buildable guards are used.
.gitignore Ignores Nix result* outputs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread jsaddle-webview2/README.md
Comment thread jsaddle-webview2/jsaddle-webview2.cabal Outdated
Comment thread jsaddle-webview2/cbits/WebView2Shim.c
hamishmack and others added 2 commits July 5, 2026 23:40
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@hamishmack
hamishmack merged commit 1d40dab into master Jul 5, 2026
1 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants