BlinkGTK GObject Signals API Reference

Version: 1.2.2-build6
Last updated: 2026-07-30
Language: English |

日本語


About this page

Reference for the eight GObject signals emitted by BlinkWebView: load
state, URL and title changes, new-window requests, messages from page JS,
and permission requests.

This page is written from measurements of the actual engine implementation.
The wiring status of each handler was re-checked against 1.2.2-build6.

Major correction from earlier editions (before 2026-07-30): previous
editions showed title-changed / uri-changed callbacks with a string
argument. Neither signal has any extra argument. Code written against
the old signatures receives user_data in the string-argument position —
a recipe for crashes and memory corruption. The signatures on this page
match the implementation.

Signal list (as implemented)

Signal Extra arguments When it fires Use
load-changed BlinkLoadEvent Load state changes (see the table below) Progress display, UI updates
load-failed error_code (int), failing_uri (in this order) Only non-committed failures (see below) Error handling
title-changed none — read the value with get_title() Title changes Window title updates
uri-changed none — read the value with get_uri() URL changes Address bar updates
new-window-requested url window.open() / target="_blank" Notification of new-window requests (no control, see below)
message-received name, data (two arguments) postMessage from page JS JS-to-C bridge (App Integration API)
permission-request BlinkPermissionRequest* Page requests a permission Allow/deny geolocation, notifications, … (default deny, synchronous response required)
render-path-changed path_id, result, gate_ms, seq (4 arguments) Render-path swap completed Result of switch_render_path() (v1.2.0-build9+)

The property notifications notify::uri / notify::title /
notify::is-loading also work (when migrating from WebKitGTK, reusing
notify::uri / notify::title unchanged is the easiest path).

Three facts to know first

  1. All engine-originated signals arrive on the GTK main thread (the
    Chromium UI thread is the GTK main loop). You may call GTK APIs from
    handlers. However, the signals that load_uri() fires synchronously (see
    below) run on whatever thread called the API — so call BlinkGTK APIs
    from the main thread
    .
  2. title-changed / uri-changed carry no arguments. Read the values
    inside the handler with blink_web_view_get_title() /
    blink_web_view_get_uri().
  3. Strings returned by getters live in an internal static buffer and are
    overwritten by the next call to the same getter. g_strdup() them if you
    keep them.

Basic form

static void on_title_changed(BlinkWebView *view, gpointer user_data) {
    GtkWindow *win = GTK_WINDOW(user_data);
    const char *title = blink_web_view_get_title(view);   /* getter, not an argument */
    gtk_window_set_title(win, title ? title : "BlinkGTK");
}

static void on_uri_changed(BlinkWebView *view, gpointer user_data) {
    GtkEditable *entry = GTK_EDITABLE(user_data);
    const char *uri = blink_web_view_get_uri(view);
    gtk_editable_set_text(entry, uri ? uri : "");
}

g_signal_connect(view, "title-changed", G_CALLBACK(on_title_changed), win);
g_signal_connect(view, "uri-changed", G_CALLBACK(on_uri_changed), entry);

load-changed

Signature

void user_function(BlinkWebView* web_view,
                   BlinkLoadEvent load_event,
                   gpointer user_data);

BlinkLoadEvent values (explicit — note the order)

typedef enum {
    BLINK_LOAD_STARTED    = 0,
    BLINK_LOAD_COMMITTED  = 1,
    BLINK_LOAD_FINISHED   = 2,
    BLINK_LOAD_REDIRECTED = 3   /* reserved — not emitted by the current runtime */
} BlinkLoadEvent;

What each value actually means

Value Actual firing condition
STARTED Fires synchronously when you call blink_web_view_load_uri() / load_html() — and at no other time. Link clicks, location.href, go_back()/go_forward()/reload() and page-initiated navigations do not fire it (the biggest incompatibility with WebKitGTK)
COMMITTED On navigation commit — with no frame filtering: iframes, pushState and fragment moves (same-document) all fire it. Not usable as "we entered a new page"
FINISHED Main-frame onload completion. Not fired for iframes, for pushState/fragment moves, or when back/forward restores from the back-forward cache

Firing patterns by operation (measured)

Operation STARTED COMMITTED FINISHED uri-changed
load_uri() (normal navigation) ○ (sometimes twice, see below)
load_html()
Link click / location.href
pushState / fragment (#foo)
go_back() / go_forward() (normal)
Same, restored from BFCache
reload() — (URL unchanged)
iframe load / navigation ○ (per iframe)

What is guaranteed at FINISHED

Right before FINISHED, the engine has finished applying injected stylesheets
(App Integration API) and the JS message
bridge (window.blinkgtk). Calling execute_javascript() from a FINISHED
handler can already use the bridge.

FINISHED means onload, though — not that dynamically-built content has
finished appearing (a paint-complete notification is not yet provided).

Stopping a loading spinner correctly

Stopping only on FINISHED can leave the spinner running forever:


load-failed

Signature

void user_function(BlinkWebView* web_view,
                   int error_code,
                   const char* failing_uri,
                   gpointer user_data);

The arguments come in (error_code, failing_uri) order. A callback in the
reverse order reads a pointer as an integer.

When it fires (important — counterintuitive)

The only condition is "a navigation that ended without committing, with a
net error
". Consequences:

Case load-failed What actually happens
Cancelled by the user or by a newer navigation ○ (most frequent) error_code = -3 (ERR_ABORTED). Fires during perfectly normal use — an unconditional error dialog will spam the user
DNS failure / connection refused (e.g. -105, -102) — (usually) Chromium commits an error page, so you get COMMITTED + FINISHED and the built-in error page is shown
HTTP 404 / 500 Normal commit (not a net error). COMMITTED + FINISHED
Subresource failures (images, CSS, fetch) Not navigations
Aborted iframe navigation failing_uri is the iframe's URL — guard by comparing against the main page URL
Turned into a download / 204 / 205 No signal at all

The real type of error_code

Values are raw negative Chromium net error codes (-3, -105, …). The
header's BlinkLoadError enum (BLINK_LOAD_ERROR_NETWORK etc.) is unused
by the current runtime — treat the argument as an int in C.

Value Constant (reference) Meaning
-3 net::ERR_ABORTED Aborted (most frequent; occurs in normal use)
-7 net::ERR_TIMED_OUT Timeout
-105 net::ERR_NAME_NOT_RESOLVED DNS failure (usually commits an error page instead of firing load-failed)

Full list: Chromium net_error_list.h.

Implementation pattern

static void on_load_failed(BlinkWebView *view, int error_code,
                           const char *failing_uri, gpointer user_data) {
    if (error_code == -3)   /* ERR_ABORTED: byproduct of normal use; ignore */
        return;
    const char *current = blink_web_view_get_uri(view);
    if (current && failing_uri && strcmp(current, failing_uri) != 0)
        return;             /* failure of something other than the shown page */
    g_message("load failed: %d %s", error_code, failing_uri);
}

uri-changed / title-changed

Signature (no extra arguments for either)

void user_function(BlinkWebView* web_view, gpointer user_data);

Read the values inside the handler with blink_web_view_get_uri() /
blink_web_view_get_title().

uri-changed behavior details

Aspect Behavior
Firing points ① synchronously inside load_uri(), ② on navigation commit. Not fired mid-redirect
Can fire twice for one navigation ① records the string you passed, ② compares against the normalized URL, so differences like "http://example.com" vs "http://example.com/" produce two emissions. Deduplicate on your side if you maintain your own history
get_uri() at point ① Returns the not-yet-committed URL. If the navigation then fails, that value was never actually shown
Rollback on abort None. The failed URL lingers in your address bar; re-read get_uri() from load-failed to restore it
load_html() Does not fire it

title-changed behavior details

Aspect Behavior
Firing condition When the page sets <title> or changes document.title (main frame only)
Pages without <title> No emission (silence — not an empty string)
History back/forward If the history entry already carries the same title, it may not fire. For tab UIs, also listen to notify::title
Value shaping Leading/trailing whitespace is already trimmed
get_title() return Never empty — for untitled pages it returns a prettified URL. Code that detects "no title" via empty string won't work

new-window-requested

Signature

void user_function(BlinkWebView* web_view,
                   const char* url,
                   gpointer user_data);

This is a notification — you cannot control it

It tells you that window.open() or <a target="_blank"> happened; you
cannot block it or open it elsewhere
(no return value). The current engine
is single-window: by the time the signal fires it has already started
navigating this same WebView to the target URL.


message-received

Receives window.blinkgtk.postMessage() from page JS. Two arguments:
(name, data)
— channel name and Base64 data. Full semantics (encodings,
multi-subscriber behavior, relation to the C-API handler) are documented in
the App Integration API.


Permission requests (permission-request)

Receives permission requests such as geolocation and notifications.

static gboolean on_permission(BlinkWebView *view,
                              BlinkPermissionRequest *req,
                              gpointer user_data) {
    const char *kind = blink_permission_request_get_type_name(req);
    if (g_strcmp0(kind, "notifications") == 0)
        blink_permission_request_allow(req);   /* allow notifications only */
    else
        blink_permission_request_deny(req);
    return TRUE;   /* handled */
}

g_signal_connect(view, "permission-request", G_CALLBACK(on_permission), NULL);

Semantics

List of request kinds (v1.2.2-build6 and later)

What a reading application actually uses:

Name What it asks for
"screen-wake-lock" Keep the screen on while reading
"system-wake-lock" Keep the device from suspending
"local-fonts" Read the list of fonts installed on the device
"persistent-storage" Keep bookmarks and annotations from being evicted
"automatic-fullscreen" Enter fullscreen without a user gesture
"pointer-lock" / "keyboard-lock" Take over page-turning input
"storage-access" / "top-level-storage-access" Read another origin's storage

Network:

Name What it asks for
"local-network-access" Connect to a local or loopback address
"local-network" Connect to a local address
"loopback-network" Connect to loopback (127.0.0.1 and so on)
"background-fetch" Start a transfer that outlives the page
"background-sync" / "periodic-background-sync" Sync later / periodically
"payment-handler" Handle payments

Input and devices:

Name What it asks for
"geolocation" / "geolocation-approximate" Location / coarse location
"notifications" Show notifications
"audio-capture" / "video-capture" Microphone / camera
"camera-pan-tilt-zoom" Move and zoom the camera
"display-capture" Capture the screen
"captured-surface-control" Control the captured surface
"speaker-selection" Choose the output speaker
"midi" MIDI devices
"sensors" Accelerometer, orientation and similar sensors
"nfc" NFC
"smart-card" Smart cards
"web-printing" Printing
"clipboard" Read and write the clipboard
"idle-detection" Know whether the user stepped away
"window-management" Screen layout and window placement
"vr" / "ar" / "hand-tracking" WebXR
"protected-media-identifier" Identifier for protected playback
"web-app-installation" Install as an application

"unknown" is returned only when Chromium gained a kind that BlinkGTK has
not caught up with yet.
release/scripts/check-permission-type-names.sh
detects that state.

Separate profile and cache per view (v1.2.2-build6 and later)

Views created with blink_web_view_new() share one profile and cache within a
process
. Opening the same URL in several views serves all but the first from
that shared cache, so the server sees a single request.

Use blink_web_view_new_with_profile() to separate them.

GtkWidget *a = blink_web_view_new_with_profile("tab1");
GtkWidget *b = blink_web_view_new_with_profile("tab2");

API

const char* blink_permission_request_get_type_name(BlinkPermissionRequest* request);
void        blink_permission_request_allow(BlinkPermissionRequest* request);
void        blink_permission_request_deny(BlinkPermissionRequest* request);

render-path-changed (v1.2.0-build9+)

Completion notification for a live render-path switch (BlinkShift) requested
with blink_web_view_switch_render_path(). The switch is asynchronous; this
signal is the authoritative result (the function's return value only says
whether the request was accepted).

static void on_render_path_changed(BlinkWebView *view,
                                   const char *path_id,   /* "P1" / "P2" */
                                   const char *result,    /* see table */
                                   int gate_ms,           /* time taken, ms */
                                   int seq,               /* this swap's number */
                                   gpointer user_data) {
    g_print("render path -> %s (%s, %d ms, seq=%d)\n",
            path_id, result, gate_ms, seq);
}

g_signal_connect(view, "render-path-changed",
                 G_CALLBACK(on_render_path_changed), NULL);
int seq = blink_web_view_switch_render_path(view, "P2");
if (seq == 0)
    g_warning("switch request was not accepted");
/* seq is assigned synchronously when the request is accepted, and the
 * completion signal carries the same number — so records taken before and
 * after a swap can be keyed to the exact swap they describe. Matching by
 * time proximity is unreliable when swaps run back to back. */

Meaning of result

Value Meaning What your app should do
"ok" The new path renders stably Success. get_render_path() now returns the new path
"rollback" The new path failed the render gate; the previous path was restored The screen is intact. The switch did not take effect
"fail" The previous path could not be restored either Treat as an error

Notes


Emission order

Guaranteed by the implementation:

Not guaranteed:


Lifecycle and threading


Practical pattern: updating browser UI

#include <blink_gtk/blink_gtk.h>
#include <gtk/gtk.h>
#include <string.h>

typedef struct {
    GtkWidget *window;
    GtkWidget *url_entry;
    GtkWidget *spinner;
    guint      spinner_timeout;
} BrowserUI;

static gboolean stop_spinner_fallback(gpointer data) {
    BrowserUI *ui = data;
    gtk_widget_set_visible(ui->spinner, FALSE);
    ui->spinner_timeout = 0;
    return G_SOURCE_REMOVE;
}

static void on_load_changed(BlinkWebView *view, BlinkLoadEvent ev,
                            gpointer user_data) {
    BrowserUI *ui = user_data;
    if (ev == BLINK_LOAD_STARTED) {
        gtk_widget_set_visible(ui->spinner, TRUE);
        /* safety net for the silent cases (downloads, 204) */
        if (ui->spinner_timeout)
            g_source_remove(ui->spinner_timeout);
        ui->spinner_timeout =
            g_timeout_add_seconds(30, stop_spinner_fallback, ui);
    } else if (ev == BLINK_LOAD_FINISHED) {
        gtk_widget_set_visible(ui->spinner, FALSE);
        if (ui->spinner_timeout) {
            g_source_remove(ui->spinner_timeout);
            ui->spinner_timeout = 0;
        }
    }
}

static void on_load_failed(BlinkWebView *view, int error_code,
                           const char *failing_uri, gpointer user_data) {
    BrowserUI *ui = user_data;
    gtk_widget_set_visible(ui->spinner, FALSE);
    if (error_code == -3)
        return;   /* ERR_ABORTED: byproduct of normal use */
    g_message("load failed: %d %s", error_code,
              failing_uri ? failing_uri : "");
}

static void on_uri_changed(BlinkWebView *view, gpointer user_data) {
    BrowserUI *ui = user_data;
    const char *uri = blink_web_view_get_uri(view);
    gtk_editable_set_text(GTK_EDITABLE(ui->url_entry), uri ? uri : "");
}

static void on_title_changed(BlinkWebView *view, gpointer user_data) {
    BrowserUI *ui = user_data;
    const char *title = blink_web_view_get_title(view);
    char *t = g_strdup_printf("%s - MyBrowser",
                              (title && *title) ? title : "(untitled)");
    gtk_window_set_title(GTK_WINDOW(ui->window), t);
    g_free(t);
}

static void setup_signals(BlinkWebView *view, BrowserUI *ui) {
    g_signal_connect(view, "load-changed", G_CALLBACK(on_load_changed), ui);
    g_signal_connect(view, "load-failed", G_CALLBACK(on_load_failed), ui);
    g_signal_connect(view, "uri-changed", G_CALLBACK(on_uri_changed), ui);
    g_signal_connect(view, "title-changed", G_CALLBACK(on_title_changed), ui);
}

Migrating from WebKitGTK

WebKitGTK BlinkGTK Notes
load-changed (STARTED) load-changed (STARTED) Incompatible: BlinkGTK's STARTED only fires when you call load_uri()/load_html() — never for link clicks. Don't key your spinner start on STARTED alone
load-changed (REDIRECTED) Never fires in BlinkGTK
load-changed (COMMITTED / FINISHED) Same names COMMITTED additionally fires for iframes / same-document navigations
load-failed load-failed Different arguments (int + uri instead of GError; error_code comes first). Narrower firing conditions (see the table on this page)
notify::title / notify::uri Work as-is The easiest migration path. The dedicated signals (title-changed / uri-changed) carry no arguments — that's the only trap
create (new window) new-window-requested Incompatible: notification only; you cannot block or open in another WebView
permission-request permission-request Synchronous response required (no deferred answers as in WebKitGTK)

Known limitations

Item Status
Redirect detection Not possible (BLINK_LOAD_REDIRECTED is reserved)
Detecting page-initiated navigation start Not possible (STARTED fires only on API calls)
Controlling new-window requests Not possible (notification only; always opens in the same view)
load-failed for committed failures (DNS etc.) Does not fire (the built-in error page is shown instead)
Paint-complete notification Not provided (FINISHED means onload)
permission-request from language bindings Not possible (raw-pointer argument)

Changelog

Version Changes
v1.1.0 (2026-07-30) Complete revision based on measured engine behavior. Corrected the title-changed / uri-changed signatures (no arguments — the old signatures caused crashes). Documented that BLINK_LOAD_REDIRECTED never fires, that STARTED fires only on API calls, the real load-failed conditions (non-committed failures only / -3 most frequent), the per-operation firing table, emission ordering, the silent cases (downloads, 204) with spinner countermeasures, and that new-window-requested is notification-only
v1.1.0 (before 2026-07-29) Older editions