Author: BlinkGTK Project
Last updated: 2026-05-12
Audience: application developers building on
BlinkGTK
This document collects implementation patterns learned from building
applications on BlinkGTK. Read it
alongside the BlinkGTK API reference
(docs/04-user-guides/api-reference/).
It incorporates suggestions from client implementers (Issue #81 and others).
Deferring post-load work — setting zoom, applying an initial
highlight, adjusting scroll position — with
g_timeout_add() "to be safe" is not recommended.
// Anti-pattern (fixed delay)
g_timeout_add(400, deferred_zoom_and_highlight_cb, self);Problems:
scrollIntoView runs
before the DOM is complete — this can produce agtk_widget_compute_point assertionsload-changed signal with
BLINK_LOAD_FINISHEDCatch BLINK_LOAD_FINISHED from
BlinkWebView's load-changed signal and run the
work synchronously.
g_signal_connect(web_view, "load-changed",
G_CALLBACK(on_load_changed), self);
static void on_load_changed(BlinkWebView* web_view,
BlinkLoadEvent ev,
gpointer user_data) {
if (ev != BLINK_LOAD_FINISHED) return;
MyAppView* self = MY_APP_VIEW(user_data);
if (self->shutting_down) return;
apply_zoom_and_pending_highlight(self);
// consume queued work such as autoplay chains here as well
}BLINK_LOAD_FINISHED guaranteesdocument.body.innerHTML and friends are settledBecause both hold, it is safe to issue scrollIntoView
and execute_javascript calls at this point.
Removing fixed delays has been observed to markedly reduce post-load
assertion failures.
Waiting for GStreamer preroll follows the same idea: replace fixed
delays with an implementation that
watches both ASYNC_DONE and
STATE_CHANGED → PAUSED and then performs the deferred seek.
Preroll
completion is then detected with no fixed delay at all.
Applying "events, not timers" consistently across
the application substantially reduces
timing-related bugs.
blink_web_view_inject_user_stylesheet()<style> with JavaScript on
every page// Anti-pattern (JS injection per page)
static void on_load_changed(BlinkWebView* view, BlinkLoadEvent ev, ...) {
if (ev != BLINK_LOAD_FINISHED) return;
blink_web_view_execute_javascript(view,
"var s = document.createElement('style');"
"s.textContent = '...';"
"document.head.appendChild(s);",
NULL);
}Problems:
execute_javascript) on every
navigationgtk_widget_compute_point assertion
during JS-driven DOM manipulationinject_user_stylesheetblink_web_view_inject_user_stylesheet() (since v1.0.0)
automatically re-injects
after navigation, so a single call at startup applies
to every page.
// At startup (called once, after BLINK_LOAD_FINISHED)
static const gchar* CUSTOM_CSS =
"body, p, span, div, ruby {"
" font-family: 'Noto Serif CJK JP', serif !important;"
"}"
"*[style*='vertical'], .vertical-rl {"
" font-feature-settings: 'vert' 1, 'vrt2' 1;"
"}";
blink_web_view_inject_user_stylesheet(BLINK_WEB_VIEW(web_view),
CUSTOM_CSS);| Aspect | Anti-pattern | Recommended |
|---|---|---|
| IPC calls on navigation | Every time | None (automatic re-injection) |
| Coverage of vertical-writing font features | Misses depending on timing | 100% |
| Risk from JS DOM manipulation | Present (Issue #78-class assertions) | None |
| Momentary "unstyled" flash | Possible | Does not occur |
This is especially valuable for content made of many XHTML files, such as EPUB and DAISY.
How to ship an application that includes the BlinkGTK runtime
(roughly 617 MB: 513 .so files plus data
files) as a portable bundle that runs wherever the user unpacks it.
my-app-X.Y.Z/
├── bin/
│ └── my-app (rpath = $ORIGIN/../lib)
├── lib/
│ ├── libblinkgtk.so
│ ├── libblink_*.so (513 files)
│ ├── icudtl.dat
│ ├── content_shell.pak
│ ├── snapshot_blob.bin
│ └── v8_context_snapshot.bin
├── share/my-app/
│ └── resources/
├── my-app.sh (launcher wrapper)
├── README.txt
└── VERSION
BLINKGTK_LIBS = -L$(BLINKGTK_REAL_LIBDIR) \
-Wl,-rpath,'$$ORIGIN/../lib' \
-Wl,-rpath,$(BLINKGTK_REAL_LIBDIR) \
-Wl,-rpath-link,$(BLINKGTK_REAL_LIBDIR) \
-lblinkgtk
$ORIGIN/../lib resolves libraries relative to
the binary's own location, so the bundle works no
matter where the user unpacks it. Keeping the absolute-path rpath as a
fallback lets make run work
during development, which improves the developer experience.
#!/bin/sh
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
export LD_LIBRARY_PATH="$APP_DIR/lib:$LD_LIBRARY_PATH"
export GDK_BACKEND="${GDK_BACKEND:-wayland}"
export MYAPP_DATA_DIR="$APP_DIR/share/my-app"
cd "$APP_DIR/share/my-app" || exit 1
exec "$APP_DIR/bin/my-app" "$@"gchar* my_app_resolve_resource(const char* relative) {
const char* base = g_getenv("MYAPP_DATA_DIR");
if (base && *base) {
return g_build_filename(base, relative, NULL);
}
return g_strdup(relative); // fall back to CWD during development
}Having both paths is convenient: development builds
(make run) work relative to the CWD with no
environment variable, while the bundle launcher sets
MYAPP_DATA_DIR so the app reads
share/my-app/resources/... from inside the bundle.
As one data point: about 220 MB compressed as tar.gz,
about 650 MB unpacked.
tar -xzf my-app-X.Y.Z-linux-x64.tar.gz
cd my-app-X.Y.Z/
./my-app.shSystem dependencies are limited to OS-standard components such as
GTK4, GStreamer, and libxml2; running
on Fedora 44 with no additional installation has been reported.
All three patterns in this document embody the same principles:
g_timeout_add)$ORIGIN plus environment variables rather than
hard-coded absolute pathsBlinkGTK is designed as a GTK4-native GObject type with a full
complement of signals, properties, and C
API. Using them keeps application code short and stable.
| API | Purpose | Pattern |
|---|---|---|
blink_web_view_new() |
Create the widget | §1, at startup |
load-changed signal |
Detect DOM completion | §1.2 |
BLINK_LOAD_FINISHED event |
Determine the completion moment | §1.2 |
blink_web_view_inject_user_stylesheet() |
Persistent CSS injection | §2.2 |
blink_web_view_execute_javascript() |
Run JS (only when necessary) | §2.1 (anti-pattern) |
blink_web_view_load_uri() |
Load a URL | §1 |
See docs/04-user-guides/api-reference/ for details.
This document continuously incorporates suggestions from client
implementers. Please send proposals for
new patterns as GitHub issues.
docs/04-user-guides/api-reference/ (C API
reference)docs/04-user-guides/getting-started/ (getting started
guide)docs/04-user-guides/troubleshooting-en.md
(troubleshooting)Reported originally by: the client implementer
community (Issue #81)
Compiled and published by: BlinkGTK Project