BlinkGTK ベストプラクティス集

作成者: BlinkGTK Project
最終更新: 2026-05-12
対象: BlinkGTK を使ったアプリケーション開発者


はじめに

BlinkGTK を用いたアプリケーション開発で得られた実装パターンを集約した
ベストプラクティス集です。本書は BlinkGTK 本体の API リファレンス
(docs/04-user-guides/api-reference/) と併せて参照してください。

クライアント実装者からの提案 (Issue #81 ほか) を反映しています。


1. 固定遅延ゼロのイベント駆動パターン

1.1 アンチパターン: 固定遅延での DOM 操作

ページロード後の zoom 設定、初期ハイライト、スクロール位置調整等を、
「安全のため」に g_timeout_add() で遅延させる実装は推奨できません。

// アンチパターン (固定遅延)
g_timeout_add(400, deferred_zoom_and_highlight_cb, self);

問題点:

  1. DOM 完成前に scrollIntoView が走る経路がある — GTK
    gtk_widget_compute_point の assertion が連発する可能性
  2. 環境/負荷によって遅延が足りない — 挙動が不安定
  3. 遅延中に shutting_down 状態に入る — use-after-free のリスク

BlinkWebViewload-changed シグナルから BLINK_LOAD_FINISHED
を捕捉して同期実行する形が安全です。

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);
    // autoplay 連鎖等もここで消費
}

両方が保証されるため、scrollIntoView / execute_javascript 系を
安全に発行できます。固定遅延を撤廃して以降、ロード後の assertion
発火が顕著に減少することが確認されています。

1.4 補足: GStreamer など他のサブシステムでも同様

GStreamer の preroll 完了待ちも同じ思想で ASYNC_DONE +
STATE_CHANGED → PAUSED の双方を拾って deferred seek を実行する
実装に置き換えると、固定遅延ゼロで preroll 完了を捕捉できます。

アプリ全体で「タイマーよりイベント駆動」の方針を貫くと、
タイミング起因のバグが大幅に減ります。


2.1 アンチパターン: ページごとの JavaScript で <style> 注入

// アンチパターン (ページごとに JS で注入)
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);
}

問題点:

  1. ページ遷移ごとの IPC コール (execute_javascript) が発生
  2. JS による DOM 操作中に gtk_widget_compute_point assertion を
    踏むリスク
  3. JS タイミングずれで一瞬「無スタイル状態」が見える

2.2 推奨パターン: inject_user_stylesheet の永続再注入

blink_web_view_inject_user_stylesheet() (v1.0.0 以降) は、ページ遷移後の自動
再注入が効くため、起動時 1 回呼び出すだけで全ページに適用
されます。

// 起動時 (BLINK_LOAD_FINISHED 後の 1 回のみ呼出)
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);

2.3 効果

観点 アンチパターン 推奨パターン
ページ遷移時の IPC コール 毎回発生 不要 (自動再注入)
縦書きフォント機能の適用率 タイミング次第で漏れ 100%
JS DOM 操作リスク あり (Issue #78 系 assertion) なし
「無スタイル状態」の瞬間 発生する可能性 発生しない

EPUB / DAISY のような複数 XHTML を持つコンテンツでは特に有効です。


3. 1 ディレクトリ完結ポータブルバンドル

BlinkGTK ランタイム (約 617MB、513 個の .so + データファイル) を
含むアプリを、ユーザーが好きな場所に展開するだけで動くポータブル
バンドルにする方法です。

3.1 配布ディレクトリ構成

my-app-X.Y.Z/
├── bin/
│   └── my-app                  (rpath = $ORIGIN/../lib)
├── lib/
│   ├── libblinkgtk.so
│   ├── libblink_*.so (513 個)
│   ├── icudtl.dat
│   ├── content_shell.pak
│   ├── snapshot_blob.bin
│   └── v8_context_snapshot.bin
├── share/my-app/
│   └── resources/
├── my-app.sh                   (起動ラッパー)
├── README.txt
└── VERSION

3.2 バイナリ側の rpath 設定 (Makefile)

BLINKGTK_LIBS = -L$(BLINKGTK_REAL_LIBDIR) \
                -Wl,-rpath,'$$ORIGIN/../lib' \
                -Wl,-rpath,$(BLINKGTK_REAL_LIBDIR) \
                -Wl,-rpath-link,$(BLINKGTK_REAL_LIBDIR) \
                -lblinkgtk

$ORIGIN/../libバイナリ位置基準にライブラリを解決するため、
ユーザーがバンドルディレクトリを任意の場所に展開しても動作します。
絶対パス rpath はフォールバックとして残し、開発ビルド時の
make run でも動くようにしておくと開発体験が向上します。

3.3 起動ラッパー

#!/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" "$@"

3.4 リソースパス解決ヘルパー

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);  // 開発時は CWD 互換
}

開発ビルド (make run) では環境変数なしで CWD 基準、バンドル起動
ではラッパーが MYAPP_DATA_DIR を設定してバンドル内
share/my-app/resources/... を参照、という二刀流が便利です。

3.5 配布サイズと検証

tar.gz 圧縮で 220MB 前後、展開後 650MB 前後が一例です。

tar -xzf my-app-X.Y.Z-linux-x64.tar.gz
cd my-app-X.Y.Z/
./my-app.sh

システム依存は GTK4 / GStreamer / libxml2 等 OS 標準のみで、
Fedora 44 で追加インストール不要で動作する事例が報告されています。


4. 共通原則: 「タイマーよりイベント駆動」「JS DOM 操作より C API」

本書の 3 つのパターンは、いずれも以下の共通原則を体現しています:

  1. タイマー (g_timeout_add) よりイベント駆動シグナル
  2. JS による DOM 操作より、BlinkGTK が提供する C API
  3. ハードコードされた絶対パスより、$ORIGIN + 環境変数

BlinkGTK は GTK4 ネイティブの GObject 型として設計されており、
シグナル / プロパティ / C API がしっかり用意されています。これらを
活用することで、アプリ実装は短く・安定したものになります。


5. 関連 API リファレンス

API 用途 パターン
blink_web_view_new() Widget 生成 §1 起動時
load-changed signal DOM 完成検知 §1.2
BLINK_LOAD_FINISHED event 完成タイミング判定 §1.2
blink_web_view_inject_user_stylesheet() 永続 CSS 注入 §2.2
blink_web_view_execute_javascript() JS 実行 (必要時のみ) §2.1 (アンチパターン)
blink_web_view_load_uri() URL 読み込み §1

詳細は docs/04-user-guides/api-reference/ を参照。


6. 今後の追加パターン

本書はクライアント実装者からの提案を継続的に反映します。
新規パターン提案は GitHub Issue でお寄せください。

反映予定 (将来)


7. 関連文書


Reported originally by: クライアント実装者コミュニティ (Issue #81)
整理・公開: BlinkGTK Project