CEF・Electron からの乗り換えガイド

作成者: BlinkGTK Project
対象の版: BlinkGTK 1.2.5-build2 (Chromium 156.0.8078.47)

English

CEF (Chromium Embedded Framework) や Electron で作ったアプリを、BlinkGTK に移すか
どうかを判断するためのガイドです。今の構成でやっていることごとに、BlinkGTK で
何を使うかを表にしました。BlinkGTK に対応するものが無い機能は、表の中と
「対応していない機能と違うところ」にはっきり書いています。

表に出てくる BlinkGTK の関数は、すべて配布物の公開ヘッダ blink_gtk/blink_gtk.h
にあるものです。

どんな方向けか

WebKitGTK から移る方は WebKitGTK からの移行ガイド
を読んでください。こちらは置き換えがほぼ 1 対 1 です。

先に確かめること

乗り換えの前に、次の 4 点が今のアプリの条件に合うかを確かめてください。

項目 BlinkGTK の条件
対応 OS Linux の Wayland セッション専用です。X11 では動きません。Windows 向けには BlinkGTK for Windows を別に配布しています。macOS 版はありません
ツールキット GTK4 のアプリとして作ります。窓・メニュー・ダイアログは GTK のものを使います
アプリの言語 C。GObject Introspection を通して Python などからも使えます
Node.js 含みません。Electron のメインプロセスに書いていた処理は、C や Python などで書き直します

CEF や Electron のように 1 つのコードで Windows・macOS・Linux に配る構成が要件なら、
BlinkGTK は向きません。

考え方の違い

  1. WebView は GTK のウィジェット 1 つです。 blink_web_view_new() が返すのは
    GtkWidget* です。GtkWindow や GtkBox に入れれば表示されます。CEF の
    CefWindowInfo や Electron の BrowserWindow のように、エンジンが窓を持つことはありません
  2. ブラウザのプロセス構成をアプリが組み立てる必要はありません。 CEF の
    CefExecuteProcess() に当たる関数はありません。サブプロセス用の実行ファイルも
    アプリ側で用意しません。プロセスの起動と管理はライブラリの中で行います
  3. ハンドラはクラスではなく関数とシグナルで受けます。 CEF の CefClient に
    各ハンドラのクラスを返す形ではなく、WebView ごとに関数を登録するか、
    GObject のシグナルに接続します
  4. メインループは BlinkGTK のものを回します。 blink_gtk_run_main_loop() を使い、
    g_main_loop_run() や g_application_run() は使いません (後述)。
    GLib の g_timeout_add() と g_idle_add() はこのループの下でも動きます
  5. アプリの処理とページの間はメッセージで結びます。 Electron の ipcMain /
    ipcRenderer に当たる双方向のメッセージがあります。ページの中に C の関数を
    直接見せる仕組み (CEF の CefV8Handler など) はありません

対応表

表の「BlinkGTK」の列は、公開ヘッダにある関数・シグナル名です。シグナルは
g_signal_connect() で接続します。

初期化と終了

やりたいこと CEF Electron BlinkGTK
サブプロセスの入口 CefExecuteProcess() (不要) 不要。対応する関数はありません
エンジンの初期化 CefInitialize() app.whenReady() を待つ blink_gtk_init(&argc, &argv)。GTK や GLib の呼び出しより前にメインスレッドで呼びます。戻り値が FALSE なら失敗です
リソースの置き場所の指定 CefSettings の resources_dir_path / locales_dir_path (Electron が自分で持つので指定しない) blink_gtk_set_resources_path()。ICU のデータだけ別に置くなら blink_gtk_set_icu_data_path()。どちらも blink_gtk_init() より前に呼びます
描画方式の指定 コマンドラインのスイッチ app.disableHardwareAcceleration() など blink_gtk_set_gpu_mode()。blink_gtk_init() より前に呼びます
終了 CefShutdown() app.quit() blink_gtk_quit_main_loop() でループを抜けた後、blink_gtk_shutdown()
版の確認 cef_version_info() process.versions blink_gtk_get_version() / blink_gtk_get_chromium_version()

リソースは、指定しなければ実行ファイルと同じディレクトリを探し、そこに無ければ
ライブラリの置き場所から探します。RPM / DEB で入れた場合は指定は要りません。

メインループ

やりたいこと CEF Electron BlinkGTK
ループを回す CefRunMessageLoop() (Electron が回す) blink_gtk_run_main_loop()
ループを抜ける CefQuitMessageLoop() app.quit() blink_gtk_quit_main_loop()
アプリのループから少しずつ回す CefDoMessageLoopWork() — ありません。ループは blink_gtk_run_main_loop() に任せ、アプリの処理は GLib のタイマーやシグナルで行います
GtkApplication で作る — — blink_gtk_application_run()。g_application_run() の代わりに使います。最後の窓が閉じるとループを抜けます
多重起動を防ぐ — app.requestSingleInstanceLock() blink_gtk_application_run() は GApplication の単一インスタンスの動作に従います。2 つ目の起動は 1 つ目に引き継がれます

g_application_run() で回すと、ビルドも起動もできるのにページが読み込まれません。
GtkApplication を使うときは必ず blink_gtk_application_run() を使ってください。

窓と WebView の作成

やりたいこと CEF Electron BlinkGTK
WebView を作る CefBrowserHost::CreateBrowser() new BrowserWindow() blink_web_view_new()。返り値の GtkWidget* を gtk_window_set_child() などで窓に入れます
窓を作る CefWindowInfo / Views BrowserWindow GTK の gtk_window_new()
1 つの窓に複数の WebView を置く 複数のブラウザ WebContentsView WebView を複数作り、GtkBox などに並べます
保存領域を分ける (cookie・キャッシュ) CefRequestContext session.fromPartition() blink_web_view_new_with_profile("名前")
新しい窓を開く要求に答える CefLifeSpanHandler::OnBeforePopup() webContents.setWindowOpenHandler() decide-navigation シグナル。新しい窓の場合は blink_navigation_decision_create_view() で WebView を受け取り、アプリの窓に入れます
全画面の要求に答える CefDisplayHandler::OnFullscreenModeChange() enter-html-full-screen blink_web_view_set_fullscreen_handler()

BLINK_WEB_VIEW() で BlinkWebView* に変換してから BlinkGTK の関数に渡します。
GTK の関数にはそのまま渡せます。

URL の読み込みとナビゲーション

やりたいこと CEF Electron BlinkGTK
URL を開く CefFrame::LoadURL() loadURL() blink_web_view_load_uri()
HTML の文字列を表示する data URL loadURL('data:...') blink_web_view_load_html(view, html, base_uri)
戻る・進む・再読み込み・中止 GoBack() / GoForward() / Reload() / StopLoad() navigationHistory.goBack() など blink_web_view_go_back() / go_forward() / reload() / stop()
戻れるか・進めるか CanGoBack() / CanGoForward() navigationHistory.canGoBack() など blink_web_view_can_go_back() / can_go_forward()
読み込みの状態を受ける CefLoadHandler did-finish-load など load-changed シグナル (BLINK_LOAD_STARTED / COMMITTED / FINISHED)、失敗は load-failed
タイトル・URL の変化を受ける CefDisplayHandler::OnTitleChange() / OnAddressChange() page-title-updated / did-navigate title-changed / uri-changed シグナル。値は blink_web_view_get_title() / get_uri() で取ります
ページが始めた移動を止める・外部で開く CefRequestHandler::OnBeforeBrowse() will-navigate decide-navigation シグナルで、blink_navigation_decision_use() / ignore() / open_external() を呼びます

load-changed の BLINK_LOAD_STARTED は、blink_web_view_load_uri() や
load_html() を呼んだときにだけ届きます。リンクのクリックや戻る・進むでは届きません。
詳しくは シグナル API を見てください。

ページとの通信 (JavaScript の実行とメッセージ)

やりたいこと CEF Electron BlinkGTK
ページで JavaScript を実行する CefFrame::ExecuteJavaScript() webContents.executeJavaScript() blink_web_view_execute_javascript(view, script, callback, user_data)。結果は JSON の文字列でコールバックに届きます。Promise ではありません
ページより先にスクリプトを入れる CefRenderProcessHandler::OnContextCreated() preload スクリプト blink_web_view_inject_user_script(view, script, TRUE)。最初の blink_web_view_load_uri() より前に登録します
CSS を入れる JavaScript で挿入 webContents.insertCSS() blink_web_view_inject_user_stylesheet()
ページ → アプリへ送る CefMessageRouter (window.cefQuery) ipcRenderer.send() → ipcMain.on() ページで window.blinkgtk.postMessage('名前', data)、アプリで blink_web_view_register_message_handler()
アプリ → ページへ送る ExecuteJavaScript() で関数を呼ぶ webContents.send() → ipcRenderer.on() アプリで blink_web_view_send_message_to_page()、ページで window.blinkgtk.addMessageHandler('名前', fn)
ページに見せる API を決める CefV8Handler で関数を登録 contextBridge.exposeInMainWorld() 対応するものはありません。window.blinkgtk の 2 つの関数でやり取りし、ページの側でラッパー関数を inject_user_script() で入れます
問い合わせて返事を待つ cefQuery の成功・失敗のコールバック ipcRenderer.invoke() / ipcMain.handle() 対応するものはありません。ページから postMessage で送り、アプリから send_message_to_page() で返します。要求と返事を対応付ける ID はページの側で付けてください

メッセージで気を付けること:

詳しくはアプリ連携 API の
「JS ↔︎ C メッセージング」を見てください。

独自スキームとリソースの置き方

やりたいこと CEF Electron BlinkGTK
スキーム名を登録する CefApp::OnRegisterCustomSchemes() protocol.registerSchemesAsPrivileged() blink_gtk_register_scheme("myapp") を blink_gtk_init() より前に呼びます。app・res・resource・ebook・blinkgtk は最初から登録されています
スキームの応答を返す CefRegisterSchemeHandlerFactory() + CefResourceHandler protocol.handle() blink_web_view_register_custom_scheme_full()。コールバックが URI を受け取り、本文を GBytes で、種類を out_mime で返します
アプリ内の HTML を表示する 独自スキームか file:// loadFile() 独自スキームで返すのが確実です。独自スキームのページは HTTPS と同じ扱いになり、fetch() も使えます
http / https の要求を横取りする CefResourceRequestHandler session.webRequest / protocol.handle('https') ありません

スキームの応答で気を付けること:

開発者ツールとデバッグ

やりたいこと CEF Electron BlinkGTK
開発者ツールを開く CefBrowserHost::ShowDevTools() webContents.openDevTools() blink_web_view_open_devtools(view, 0) でリモートデバッグを開き、blink_web_view_get_devtools_url() の URL を Chrome / Chromium で開くか、chrome://inspect から接続します
決まったポートで待ち受ける CefSettings の remote_debugging_port --remote-debugging-port blink_web_view_open_devtools(view, ポート番号)
開発者ツールを閉じる CloseDevTools() closeDevTools() blink_web_view_close_devtools()
開発者ツールを使わせない — devTools: false blink_web_view_set_content_policy() に BLINK_CONTENT_POLICY_NO_DEVTOOLS

アプリの窓の中に開発者ツールを並べて出す機能はありません。開発者ツールは
別の Chrome / Chromium の窓で開きます。

その他のよく使う機能

やりたいこと CEF Electron BlinkGTK
alert / confirm / prompt CefJSDialogHandler (Electron が出す) blink_web_view_set_javascript_dialog_handler()
ファイルの選択 CefDialogHandler dialog.showOpenDialog() ページの <input type="file"> には blink_web_view_set_file_chooser_handler()。アプリから出すなら GTK の GtkFileDialog
ダウンロード CefDownloadHandler will-download blink_web_view_set_download_handler()。進み具合は download-started シグナルで受け取る BlinkDownload から
HTTP 認証 CefRequestHandler::GetAuthCredentials() login イベント blink_web_view_set_auth_handler()
証明書のエラー CefRequestHandler::OnCertificateError() certificate-error blink_web_view_set_certificate_error_handler()
権限の要求 (位置情報・カメラ等) CefPermissionHandler session.setPermissionRequestHandler() permission-request シグナル。応答は blink_permission_request_allow() / deny()。何もしなければ拒否です
cookie CefCookieManager session.cookies blink_web_view_get_cookies() / set_cookie() / delete_all_cookies()
User-Agent CefSettings の user_agent setUserAgent() blink_web_view_set_user_agent()
ページ内の検索 CefBrowserHost::Find() findInPage() blink_web_view_find_in_page() / stop_finding()
拡大率 SetZoomLevel() setZoomFactor() blink_web_view_set_zoom_level()
PDF に書き出す PrintToPDF() printToPDF() blink_web_view_print_to_pdf_with_options()
画面を画像にする — capturePage() blink_web_view_capture_screenshot() / _async()

配布 (パッケージと依存)

やりたいこと CEF Electron BlinkGTK
ビルド CEF のバイナリ配布物と CMake npm pkg-config --cflags --libs blinkgtk-0.1 でコンパイル・リンクします
実行に要るもの CEF の共有ライブラリとリソースをアプリと一緒に配る Electron の実行ファイルごと配る runtime のパッケージ (RPM: blinkgtk-bin、DEB: libblinkgtk-0.1-0)。GTK4 は OS のものを使います
ビルドに要るもの — — devel のパッケージ (RPM: blinkgtk-bin-devel、DEB: libblinkgtk-0.1-dev)。Python などから使うなら gir のパッケージ (RPM: blinkgtk-bin-gir) も入れます
アプリとまとめて配る アプリのディレクトリに入れて配る electron-builder など ポータブルアプリ作成ガイド の手順で tar.gz にまとめます

リンクは pkg-config を通してください。手で -lblinkgtk だけを書くと、
ビルドも起動もできるのにページが表示されないことがあります。理由と確かめ方は
組み込みガイド にあります。

アプリと一緒に配る場合は、THIRD_PARTY_NOTICES.html などのライセンス表記も
一緒に配る必要があります。ポータブルアプリ作成ガイドの
「ライセンス上の義務」を見てください。

最小の例 (C)

独自スキーム app:// でアプリ内の HTML を返し、ページのボタンからアプリへ
メッセージを送り、アプリから返事を返す例です。Electron の
ipcRenderer.send() と webContents.send() の組み合わせに当たります。

/* cef-electron-migration.c */
#include <blink_gtk/blink_gtk.h>
#include <gtk/gtk.h>
#include <string.h>

static const char page_html[] =
    "<!doctype html><meta charset='utf-8'><title>sample</title>"
    "<button id='b'>アプリに送る</button><p id='out'></p>"
    "<script>"
    "window.blinkgtk.addMessageHandler('reply', function (data) {"
    "  document.getElementById('out').textContent = data;"
    "});"
    "document.getElementById('b').onclick = function () {"
    "  window.blinkgtk.postMessage('hello', 'ping');"
    "};"
    "</script>";

/* app://main/... への要求ごとに呼ばれる。URI 以外の情報は渡らない */
static GBytes* on_app_scheme(BlinkWebView* view, const char* uri,
                             char** out_mime, gpointer user_data) {
  (void)view; (void)uri; (void)user_data;
  *out_mime = g_strdup("text/html");
  return g_bytes_new_static(page_html, strlen(page_html));
}

/* ページの postMessage('hello', ...) を受ける。data は Base64 */
static void on_hello(const char* name, const guint8* data, gsize length,
                     gpointer user_data) {
  BlinkWebView* view = BLINK_WEB_VIEW(user_data);
  gchar* encoded = g_strndup((const gchar*)data, length);
  gsize decoded_len = 0;
  guchar* decoded = g_base64_decode(encoded, &decoded_len);
  g_print("%s: %.*s\n", name, (int)decoded_len, (const char*)decoded);
  blink_web_view_send_message_to_page(view, "reply", "pong");
  g_free(decoded);
  g_free(encoded);
}

/* execute_javascript の結果。JSON の文字列で、実行できなければ空文字列 */
static void on_title(const char* result, gpointer user_data) {
  (void)user_data;
  g_print("document.title = %s\n", result[0] ? result : "(未実行)");
}

static void on_load_changed(BlinkWebView* view, BlinkLoadEvent event,
                            gpointer user_data) {
  (void)user_data;
  if (event == BLINK_LOAD_FINISHED)
    blink_web_view_execute_javascript(view, "document.title", on_title, NULL);
}

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

int main(int argc, char* argv[]) {
  /* CefInitialize() に当たる。GTK より先に呼ぶ */
  if (!blink_gtk_init(&argc, &argv)) {
    g_printerr("blink_gtk_init() failed\n");
    return 1;
  }

  GtkWidget* window = gtk_window_new();
  gtk_window_set_title(GTK_WINDOW(window), "BlinkGTK");
  gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
  g_signal_connect(window, "close-request", G_CALLBACK(on_close), NULL);

  /* CreateBrowser() / new BrowserWindow() に当たる */
  GtkWidget* web_view = blink_web_view_new();
  gtk_window_set_child(GTK_WINDOW(window), web_view);
  BlinkWebView* view = BLINK_WEB_VIEW(web_view);

  blink_web_view_register_custom_scheme_full(view, "app", on_app_scheme, NULL);
  blink_web_view_register_message_handler(view, "hello", on_hello, view);
  g_signal_connect(web_view, "load-changed", G_CALLBACK(on_load_changed), NULL);

  blink_web_view_load_uri(view, "app://main/index.html");
  gtk_window_present(GTK_WINDOW(window));

  /* CefRunMessageLoop() に当たる */
  int status = blink_gtk_run_main_loop();

  /* CefShutdown() に当たる */
  blink_gtk_shutdown();
  return status;
}

ビルドと実行:

cc cef-electron-migration.c -o cef-electron-migration $(pkg-config --cflags --libs blinkgtk-0.1)
./cef-electron-migration

Wayland のセッションで実行してください。ボタンを押すと、端末に hello: ping と出て、
ページに pong と表示されます。

対応していない機能と違うところ

次の機能は、BlinkGTK の公開 API に対応するものがありません。

機能 CEF / Electron での呼び方 代わりの方法
macOS 版・X11 での動作 (両方とも対応) ありません
Node.js Electron のメインプロセスと preload C や GObject Introspection 経由の言語で書きます
アプリのループから少しずつ回す CefDoMessageLoopWork() blink_gtk_run_main_loop() に任せ、GLib のタイマーを使います
画面外で描いて画素を受け取る CEF の windowless rendering (OnPaint) ありません。1 枚ずつなら blink_web_view_capture_screenshot()
ページに C の関数を直接見せる CefV8Handler / contextBridge window.blinkgtk のメッセージと、inject_user_script() で入れるラッパー関数
問い合わせと返事の組 cefQuery / ipcRenderer.invoke() メッセージを 2 本組み合わせます
http / https の要求の横取り・書き換え CefResourceRequestHandler / session.webRequest ありません。アプリが返す内容は独自スキームで配ります
スキームの応答の状態コード・ヘッダ・リダイレクト CefResourceHandler / protocol.handle() の Response ありません。本文と種類だけを返せます
印刷ダイアログ CefBrowserHost::Print() / webContents.print() ありません。PDF への書き出しは blink_web_view_print_to_pdf_with_options()
アプリの窓の中の開発者ツール ShowDevTools() / openDevTools() リモートデバッグを別の Chrome / Chromium で開きます
拡張機能 CEF・Electron の拡張機能の読み込み ありません
アプリの自動更新 Electron の autoUpdater ありません。OS のパッケージ管理で更新します
メニュー・トレイ・通知 Electron の Menu / Tray / Notification GTK と GLib の機能 (GMenu、GNotification など) を使います

ほかに、次の点が違います。

足りない機能が乗り換えの妨げになる場合は、
不具合報告・要望 または
contact@blinkgtk.org までお知らせください。

次に読むページ

変更履歴

日付 内容
2026-10-19 初版