BlinkGTK API リファレンスマニュアル — 第 1 章 概観とライフサイクル

作成者 BlinkGTK-Readium プロジェクト (consumer/embedder 視点、#121)
対象バージョン BlinkGTK 1.2.1 (Chromium 152.0.7977.64)
位置づけ consumer (組み込み側) が実運用で得た知見を含む利用者マニュアル

API セマンティクスの正は、公開ヘッダ blink_gtk/blink_gtk.h と上流のレビューに従う。


1.1 BlinkGTK とは

BlinkGTK は、Chromium の Blink レンダリングエンジンを GTK4 ウィジェットとして
アプリケーションに組み込むためのライブラリである。WebKitGTK と同様の設計思想の
C API(BlinkWebView)を提供する。GTK4 アプリケーションは、ウェブコンテンツの
表示・ナビゲーション・JavaScript 連携・カスタム URI スキーム配信などを利用できる。
Chromium 本体をビルドする必要はない。 prebuilt パッケージ(共有ライブラリ +
ヘッダ + pkg-config)へリンクするだけでよい。本マニュアルは EPUB ビューワ(BlinkGTK-Readium)として
BlinkGTK を実運用している embedder の視点で書かれている。

1.2 アーキテクチャ概観

階層は上から下へ次のとおり。

  1. embedder(あなたのアプリ) — GTK4 アプリケーション。ウィンドウ・メニュー等の
    chrome を GTK で構築し、その中に BlinkWebView を 1 つの子ウィジェットとして置く。
  2. BlinkWebView(BlinkGTK 公開 API 層)GtkWidget のサブクラス(GObject)。
    ロードイベントのシグナル、カスタムスキーム、JS ブリッジ等の C API をここで受ける。
  3. Chromium(マルチプロセス) — Browser プロセス(embedder と同居)の下に
    Renderer / GPU 等の子プロセスが起動し、実際のレイアウト・描画を行う。

embedder が直接触るのは 2 層目までであり、Chromium のプロセスモデルは背後に隠れる。
ただし後述の「実運用の落とし穴」(サンドボックス、プロファイル、RUNPATH)は
このマルチプロセス構造に由来するため、存在だけは意識しておくとトラブル時に早い。

ヘッダとリンク

#include <blink_gtk/blink_gtk.h>
pkg-config --cflags --libs blinkgtk-0.1

include するのは blink_gtk.h だけです。

v1.2.0-build5 以前は、GObject 化以前 (2025 年) の内部ヘッダ blink_web_view.h
も同梱されていました。blink_gtk.h と同時に include すると同名 typedef が
別の型を指すため、確定的にコンパイルエラーになります。

error: conflicting types for 'BlinkWebView'; have 'struct BlinkWebView'

使えないヘッダを置いておく理由がないため、それ以降は同梱していません
blink_gtk.h に無い機能が要る場合は Issue でご相談ください。

アプリケーションが include するのは blink_gtk/blink_gtk.h のみとすること。

v1.2.0-build6 以降は除外済みです (2026-09-03 に、当時公開されていた
devel パッケージをすべて展開して確認)。現在配られている
ヘッダは blink_gtk.h / blinkgtk_export.h / blinkgtk_version.h の 3 つです。

スレッド安全性ポリシー

全ての BlinkGTK 公開 API は GTK メインスレッドからのみ呼び出せる
(ヘッダ冒頭の Thread Safety Policy を参照)。コールバック
(JavaScript 結果、cookie、print 等)も常に GTK メインスレッドで呼ばれる。
ワーカースレッドからは g_idle_add() でメインスレッドへディスパッチする。

1.3 ライフサイクル全体像

embedder のプロセスは次のフェーズを一方向に進む。

フェーズ 主な API 備考
(0) init 前設定 blink_gtk_set_resources_path() blink_gtk_init() より前に呼ぶ必要がある
(1) 初期化 blink_gtk_init() 全 GTK/GLib 呼び出しより前・メインスレッドで 1 回
(2) WebView 生成 blink_web_view_new() / _new_with_gpu_mode() / _new_container() GtkWidget として返る
(3) ロード前登録 blink_web_view_register_custom_scheme_full()register_message_handler()g_signal_connect(load-changed) load_uri() より前に済ませる
(4) 配置・表示 gtk_window_set_child()gtk_window_present() 通常の GTK4 ウィジェットとして扱う
(5) ロード blink_web_view_load_uri() 以後 load-changed シグナルで進行を受ける
(6) メインループ blink_gtk_run_main_loop() / blink_gtk_quit_main_loop() GTK ではなく Chromium の RunLoop
(7) 終了 blink_gtk_shutdown() run_main_loop() 復帰後(明示呼び出しを推奨。下記参照)

blink_gtk_shutdown() の要否とタイミング(上流 #123 レビューでの正式見解)

呼び方 評価
run_main_loop() 復帰後に呼ぶ 推奨。teardown 順序が決定的になる(下記の最小完全例はこの形)
ウィンドウ close ハンドラ等 RunLoop 実行中に呼ぶ 安全。デッドロックせず遅延 shutdown に切り替わり、run_main_loop() 復帰後に実処理が走る
呼ばない 動作はする(atexit の安全網が働く)が、teardown が atexit 任せで順序が非決定的非推奨

未呼出でも壊れないのは、ContentMainRunner::Run 成功後に atexit ハンドラが登録されており、
AtExitManager 生存中に自動 teardown されるため(Issue #102-C)。歴史的に未呼出の embedder が
動いていた理由もこれ。ただし順序が決定的になる明示呼び出しを推奨する。

(0) init 前設定

以下は blink_gtk_init() の前に呼ぶことがヘッダで明示されている。

void blink_gtk_set_devtools_locale(const char* locale);   /* NULL=システムロケール追従 */
const char* blink_gtk_get_devtools_locale(void);
void blink_gtk_set_icu_data_path(const char* path);       /* icudtl.dat の絶対パス */
void blink_gtk_set_resources_path(const char* path);      /* pak/snapshot 群のルート */

通常はパッケージインストール先 <prefix>/lib/chromium/ が自動検出されるため
指定不要。非標準配置(自アプリへの同梱等)のときだけ set_resources_path()
指定する。set_icu_data_path() と同時指定時は resources_path が優先される。

(1) 初期化

gboolean blink_gtk_init(int* argc, char*** argv);

argc/argv を渡すと Chromium 系コマンドラインフラグ(--no-sandbox 等)が
ここで解釈される。戻り値 FALSE は初期化失敗。メインスレッドで、他のあらゆる
GTK/GLib 呼び出しより前に 1 回だけ呼ぶ。

(2) WebView 生成

GtkWidget* blink_web_view_new(void);
GtkWidget* blink_web_view_new_with_gpu_mode(BlinkGpuMode mode);
GtkWidget* blink_web_view_new_container(void);
BlinkGpuMode blink_web_view_get_gpu_mode(BlinkWebView* web_view);
typedef enum {
  BLINK_GPU_MODE_SOFTWARE    = 0,  /* CPU 描画のみ(既定・GPU 不要) */
  BLINK_GPU_MODE_SWIFTSHADER = 1,  /* SwiftShader による GL エミュレーション */
  BLINK_GPU_MODE_EGL         = 2,  /* ネイティブ EGL ハードウェア GPU */
} BlinkGpuMode;

(3) ロード前登録

load_uri() を呼ぶ前に、コンテンツ側から発生するイベントの受け口を揃える。

/* カスタム URI スキーム(バイナリ対応版。Issue #103) */
void blink_web_view_register_custom_scheme_full(
    BlinkWebView* web_view, const char* scheme,
    BlinkCustomSchemeBytesCallback callback, gpointer user_data);

/* JS→C メッセージ(window.blinkgtk.postMessage) */
void blink_web_view_register_message_handler(
    BlinkWebView* web_view, const char* name,
    BlinkMessageCallback callback, gpointer user_data);

ロードイベントは GObject シグナル "load-changed" で受ける。

typedef enum {
  BLINK_LOAD_STARTED    = 0,
  BLINK_LOAD_COMMITTED  = 1,
  BLINK_LOAD_FINISHED   = 2,
  BLINK_LOAD_REDIRECTED = 3  /* 予約。現行ランタイムは発火しない */
} BlinkLoadEvent;

consumer 知見: BLINK_LOAD_REDIRECTED は将来予約であり現行では発火しない。
また enum の整数値は v1.0.10 iter14 で「実際に emit される値」に合わせて明示化
された経緯がある(それ以前は ev == BLINK_LOAD_FINISHED が黙って偽になる版が
存在した)。古いパッケージと混ぜないこと。

(4)〜(5) 配置とロード

BlinkWebView は普通の GTK4 ウィジェットとして gtk_window_set_child() で置き、
gtk_window_present() 後に blink_web_view_load_uri() を呼ぶ。

void blink_web_view_load_uri(BlinkWebView* web_view, const char* uri);
void blink_web_view_load_html(BlinkWebView* web_view, const char* html, const char* base_uri);

(6) メインループ

int  blink_gtk_run_main_loop(void);   /* 0=成功。復帰までブロック */
void blink_gtk_quit_main_loop(void);

g_application_run() ではなく Chromium の base::RunLoop を回す。GTK と
Chromium のライフサイクル衝突を避け、シャットダウン順序を制御するための設計で
ある。GLib の g_timeout_add() / g_idle_add() はこのループ下でも発火する
(当方は録画・診断タイマを GLib timeout で常用)。終了させたい箇所
(ウィンドウの close-request 等)から blink_gtk_quit_main_loop() を呼ぶ。

(7) 終了

void blink_gtk_shutdown(void);

run_main_loop() 復帰後、プロセス終了直前に呼ぶ。

1.4 最小完全例(GTK4 への載せ方)

実運用 embedder(BlinkGTK-Readium の readium-launcher)の実フローを 1 本に
簡約したコンパイル可能な例。カスタムスキーム app:// でローカルディレクトリを
配信し、ロード完了をシグナルで受ける。

/* minimal-embedder.c — BlinkGTK 最小 embedder(GTK4) */
#include <blink_gtk/blink_gtk.h>
#include <gtk/gtk.h>
#include <string.h>

/* app://host/<path> をカレントディレクトリから配信(バイナリ可)。NULL=404 */
static GBytes* scheme_cb(BlinkWebView* wv, const char* uri,
                         char** out_mime, gpointer ud) {
  (void)wv; (void)ud;
  const char* p = strstr(uri, "://");
  if (!p) return NULL;
  const char* slash = strchr(p + 3, '/');           /* host の後の '/' */
  const char* rel = slash ? slash + 1 : "index.html";
  if (*rel == '\0') rel = "index.html";
  if (strstr(rel, "..")) return NULL;               /* path traversal 防御 */
  char* contents = NULL; gsize len = 0;
  if (!g_file_get_contents(rel, &contents, &len, NULL)) return NULL;
  if (out_mime && g_str_has_suffix(rel, ".html"))
    *out_mime = g_strdup("text/html");              /* NULL なら URI から推測 */
  return g_bytes_new_take(contents, len);
}

static void on_load_changed(BlinkWebView* wv, BlinkLoadEvent ev, gpointer ud) {
  (void)ud;
  if (ev == BLINK_LOAD_FINISHED)
    g_print("loaded: %s (title=%s)\n",
            blink_web_view_get_uri(wv), blink_web_view_get_title(wv));
}

static gboolean on_close(GtkWindow* w, gpointer ud) {
  (void)w; (void)ud;
  blink_gtk_quit_main_loop();
  return FALSE;
}

int main(int argc, char* argv[]) {
  /* (0) 非標準配置なら blink_gtk_set_resources_path() をここで。 */
  /* (1) 初期化(全 GTK/GLib 呼び出しより前) */
  if (!blink_gtk_init(&argc, &argv)) return 1;

  /* (2) ウィンドウ + WebView(software 描画を推奨) */
  GtkWidget* window = gtk_window_new();
  gtk_window_set_default_size(GTK_WINDOW(window), 1024, 768);
  GtkWidget* webview = blink_web_view_new_with_gpu_mode(BLINK_GPU_MODE_SOFTWARE);

  /* (3) ロード前登録 */
  blink_web_view_register_custom_scheme_full(
      BLINK_WEB_VIEW(webview), "app", scheme_cb, NULL);
  g_signal_connect(webview, "load-changed", G_CALLBACK(on_load_changed), NULL);
  g_signal_connect(window, "close-request", G_CALLBACK(on_close), NULL);

  /* (4)〜(5) 配置・表示・ロード */
  gtk_window_set_child(GTK_WINDOW(window), webview);
  gtk_window_present(GTK_WINDOW(window));
  blink_web_view_load_uri(BLINK_WEB_VIEW(webview),
                          argc > 1 ? argv[1] : "app://local/index.html");

  /* (6)〜(7) メインループ → 終了 */
  int rc = blink_gtk_run_main_loop();
  blink_gtk_shutdown();
  return rc;
}

ビルドと起動:

export PKG_CONFIG_PATH="$BLINKGTK_PKG/lib/pkgconfig:$PKG_CONFIG_PATH"
cc -O2 -Wall -o minimal-embedder minimal-embedder.c \
   -Wl,-rpath,'$ORIGIN' \
   $(pkg-config --cflags --libs blinkgtk-0.1)

# 実行はランタイム(.so + chromium リソース)と同じディレクトリに置くのが確実
cp minimal-embedder "$BLINKGTK_PKG/lib/chromium/"
"$BLINKGTK_PKG/lib/chromium/minimal-embedder" --no-sandbox --no-zygote

-Wl,-rpath,'$ORIGIN' と「lib/chromium に置いて起動」の理由は次節。

1.5 実運用の落とし穴(consumer 知見)

ゼロ知識の新規 embedder が 1 往復で動かせるよう、当方が実際に踏んだものを率直に挙げる。

(a) RUNPATH / ライブラリ解決 — V8 snapshot mismatch 即死

libblinkgtk.so は同一パッケージの Chromium リソース(v8_context_snapshot.bin
等)と厳密に対でなければならない。.pc 由来の絶対 rpath だけでビルドしたバイナリを、別のパッケージ
lib/chromium/ にコピーして起動しないでください。ビルド時パッケージの .so を
読み込んでしまいます。V8 snapshot mismatch で renderer が即死します
(当方 2026-07-07 実証)。対策は次のどちらか。

配布されたサンプルバイナリは RUNPATH がビルド時パスに焼き込まれていることが
あるため、パッケージを移動・差し替えて使う場合は自前ビルドが安全である。

当方の実測では、--user-data-dir を渡してもプロファイルは分離されず、
~/.blink_gtk/ が同一ユーザーの全 BlinkGTK プロセスで共有される。さらに
ディスクへの永続化(localStorage 等)は SIGTERM などの正常終了経路でのみ
行われ、SIGKILL や _exit() では失われる。テストで状態を汚した場合は
~/.blink_gtk/ 配下の該当データを手で消す運用になる。複数アプリの同時運用や
プロファイル分離が要件なら、この挙動を前提に設計すること。

(c) --no-sandbox --no-zygote が必要な環境がある

Chromium サンドボックスが構成できない環境(コンテナ、user namespace 制限、
一部の開発環境)では、blink_gtk_init() に渡す argv に --no-sandbox --no-zygote
を含めないと子プロセスが起動できない。当方の実運用起動スクリプトは常時この
2 フラグ付きで起動している。環境変数での同等指定は第 9 章 BLINKGTK_NO_SANDBOX
を参照。セキュリティ上の含意(サンドボックス無効化)は理解の上で使うこと。

(d) GPU モードは software 推奨・プロセス全体で 1 つ

EGL(ハードウェア GPU)経路は実験的であり、当方は評価の結果「当面 software で
運用」に決定している(縦書き EPUB 本文の描画・スクリーンショット採取とも
software で安定)。また前述のとおりモードはプロセス単位なので、WebView ごとの
切り替えはできない。

(e) オフスクリーンキャプチャは「見たまま」ではない

blink_web_view_capture_screenshot() 系は renderer 側で再描画したフレームを
保存するため、コンポジタ表示段の不具合(表示だけ白紙になる等)は写らないことが
ある。表示系の検証を capture PNG だけで合格判定しないこと(当方はこれで
False Pass を経験し、GTK 合成結果を直接読む検査を併用している)。

1.6 バージョン確認

const char* blink_gtk_get_version(void);           /* 例 "1.2.1" */
const char* blink_gtk_get_chromium_version(void);  /* 例 "152.0.7977.64" */

/* build 番号 (Since 1.2.2)。build 番号は「同じ版を作り直した回数」です。
 * 同じ版でも build が違えば挙動が違うことがあります。 */
const char* blink_gtk_get_build(void);             /* 例 "2"。不明なら NULL */
const char* blink_gtk_get_version_full(void);      /* 例 "1.2.1-build3"。必ず非 NULL */

コンパイル時マクロ(BLINKGTK_VERSION / CHROMIUM_VERSION)ではなく、実行時に
リンクされている .so から取得する(consumer の再ビルドなしで表示が追従する)。
ログ・バグ報告には必ずこの実行時値を記録することを勧める。

1.7 他章への前方参照

(章題は #121 合意目次の第 1 弾時点の予定。確定は上流レビューに従う)

本章の情報源

初稿は下記により起草した。その後 2026-09-03 と 2026-09-08 に、それぞれ
当時の最新の配布物で再確認
し、ヘッダ同梱の記述と版数の例を実体に合わせた。