5.8 KiB
Path A POC — WebKitGTK + C99: Findings
Status: ✅ Working
A 17 KB C99 binary opens a GTK window with a URL bar and a WebKitWebView, and successfully loads real-world HTTPS pages.
Verified load:
[loaded] https://laantungir.net/ -- title: Laan Tungir
laantungir.net is a real site (the one from the FIPS setup docs) and
WebKitGTK fetched, rendered, and parsed its <title> correctly.
Environment
- Debian 13 (trixie), x86_64
- WebKitGTK 2.52.3 (API
webkit2gtk-4.1) - GTK 3.24.49
- gcc 14.2.0
libwebkit2gtk-4.1-devinstalled via apt (~oneapt install)
What we built
main.c— ~130 lines of C99. AGtkWindowcontaining aGtkBoxwith aGtkEntryURL bar and aWebKitWebView. Signals:- URL entry
activate→webkit_web_view_load_uri() load-changed→ sync URL bar + print[loaded]onWEBKIT_LOAD_FINISHEDload-failed→ print[failed]with the error
- URL entry
Makefile— pkg-config driven, onemake.
Time to first page load
Very fast. From "install package" to "page rendered":
apt install libwebkit2gtk-4.1-dev— one command.- Write ~130 lines of C.
make→ 17 KB binary.- Run → page loads.
Total active effort: under an hour, most of which was the C file.
Problems encountered (the friction we came to measure)
1. -DGTK_DISABLE_DEPRECATED breaks the build (hard error)
Adding -DGTK_DISABLE_DEPRECATED to be "clean" caused:
WebKitContextMenuItem.h:56:60: error: unknown type name 'GtkAction'
WebKitGTK's context-menu API still references GtkAction, which GTK has
deprecated and hides behind GTK_DISABLE_DEPRECATED. Lesson: do not
define GTK_DISABLE_DEPRECATED when building against WebKitGTK 4.1. This is
mildly concerning for the "reckless" goal — it means WebKitGTK drags along
some legacy GTK API surface.
2. -Wpedantic is unusable with GObject headers (warning storm)
-std=c99 -Wpedantic produces dozens of redefinition of typedef warnings
because GObject's WEBKIT_DECLARE_* / G_DECLARE_* macros redeclare typedefs
(a pattern that is legal in C11 but flagged under C99 -Wpedantic). Lesson:
drop -Wpedantic, or move to -std=c11/-std=gnu11 if strict pedantry
matters. -Wall -Wextra is fine and clean.
3. Header layout is non-obvious
The pkg-config include is -I/usr/include/webkitgtk-4.1, and the umbrella
header is #include <webkit2/webkit2.h>. Individual headers live under
webkit/ (e.g. webkit/WebKitWebView.h), not under webkit2/WebKit/ as the
API docs sometimes imply. Not a real problem once you know to just include the
umbrella — but worth noting.
4. gtk_main() is deprecated in GTK 4 (not a problem here)
We targeted GTK 3 (WebKitGTK 4.1 API pairs with GTK 3). GTK 4 would require
gtk_application_run() and a different lifecycle. Since WebKitGTK's 4.1 API is
GTK-3-based, this is the correct pairing for now. A GTK-4-based WebKit
(webkitgtk-6.0) exists in newer distros but is not in Debian 13.
What this tells us about the reckless browser goals
FIPS integration (custom URI scheme) — looks straightforward
WebKitGTK has a first-class custom URI scheme API:
webkit_web_context_register_uri_scheme(). You register a scheme (e.g.
fips) and get a WebKitURISchemeRequest callback where you feed bytes into
an input stream. This is exactly the hook we need to route fips:// (or
*.fips) requests to the FIPS TUN interface. The header
webkit/WebKitURISchemeRequest.h is present. No obvious blocker.
Nostr signing (window.nostr injection) — looks straightforward
WebKitGTK exposes JavaScriptCore via webkit/WebKitUserContentManager.h and
jsc/jsc.h. You can inject a script into every page with
webkit_user_content_manager_add_script() and a
WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES +
WEBKIT_USER_INJECTED_FRAMES_ALL_FRAMES script. The injected script can define
window.nostr = { getPublicKey, signEvent, ... } and bridge calls back to the
C host via jsc_value_object_set_property or a message handler. No obvious
blocker, and no extension system needed — this is the native mechanism.
Security stripping — needs investigation
WebKitGTK exposes WebKitSettings (disable JS, enable devtools, etc.) and
WebKitWebContext / WebKitWebsiteDataManager for cookies, TLS, etc. The
specific "reckless" asks:
- Disable CORS / same-origin — not exposed as a simple setting in the 4.1
API. Likely requires a
WebKitWebExtension(a separate shared lib loaded into the web process) that hooks the WebCore loader, or building WebKit from source with patches. This is the biggest unknown. - Accept any certificate —
WebKitWebContexthas aload-failed-with-tls-errorssignal where you can callwebkit_web_context_allow_tls_certificate_for_host(). Easy. - Shared identity across origins — cookies are per-
WebKitWebContext; a single shared context gives a shared cookie jar by default. Easy.
The CORS/same-origin strip is the one item that may require deeper engine access (a WebExtension or a custom build). Worth a follow-up spike.
Binary size (actual)
- Host binary (
reckless_webkit): 17 KB (stripped of debug info by-O2). - Engine library
libwebkit2gtk-4.1.so: ~35 MB on this system. - The host binary is tiny because it links dynamically against the system WebKitGTK. This matches the "smallest practical footprint on a Linux desktop" prediction in the options doc.
Verdict for Path A
WebKitGTK + C99 is a very comfortable embedding path. The C API is genuine (no C++ required), the docs are adequate, custom URI schemes and JS injection are first-class, and a real site loaded on the first try. The only real unknown for the full reckless browser is CORS/same-origin stripping, which may need a WebExtension or source patch.
Path A is a strong candidate for the primary engine.