BlinkGTK GObject Signals API Reference

Version: 1.2.0-build2
Last updated: 2026-07-30
Language: English |

日本語


About this page

Reference for the seven 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.0-build2.

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)

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

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);

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