バージョン: 1.2.0-build2
最終更新: 2026-07-30
言語: 日本語 |
ページが要求する ブラウザの UI
をアプリが引き受けるための API です。
JavaScript の
alert()、ファイル選択、証明書エラーの扱いを、アプリ自身の
ダイアログ・ポリシーで実装できます。
本ページはエンジン実装を実測して書かれています。ハンドラの配線状況は
1.2.0-build2 時点で再確認しました。ハンドラごとに
成熟度が異なるため、最初に対応状況を示します。
| ハンドラ | 現在の状態 |
|---|---|
| JavaScript ダイアログ | 動作します (制約: 同時に扱えるダイアログは 1 つ、beforeunload は届かない) |
| ファイル選択 | 動作します (制約: ディレクトリアップロード非対応) |
| 証明書エラー | 動作します (同期応答のみ・リクエストごとに毎回呼ばれる) |
| 全画面 (Fullscreen API) | 動作します (制約: 状態同期は片方向) |
| ダウンロード | 未配線 — 登録できますが呼ばれません (現状と回避策) |
| HTTP 認証 | 未配線 — 登録できますが呼ばれません (現状と回避策) |
旧版 (2026-07-29) からの訂正: 既定動作表に「ダウンロード →
~/Downloads/に保存」「証明書エラー → 記録して続行」とありましたが、
どちらも誤りです。実際は「ダウンロード → 保存されない」「証明書エラー →
ブロック」です。下の表が実測に基づく正です。
callback に NULL
を渡すとuser_data の寿命管理はアプリの責任です
(GDestroyNotifymessage /
accept_types など) はハンドラからg_strdup() で複製して| ページの要求 | ハンドラ未設定時の既定動作 |
|---|---|
alert() |
表示されず、スクリプトはそのまま続行 |
confirm() |
表示されず、false が返る |
prompt() |
表示されず、null が返る (空文字列ではない) |
beforeunload |
常に離脱を許可 (ハンドラを設定しても変わらない) |
ファイル選択 (<input type="file">) |
キャンセル |
| ダウンロード | 保存されない (開始直後に内部キャンセル。通知なし) |
| HTTP 認証 (401 / 407) | キャンセル (サーバの 401 レスポンスがそのまま表示される) |
| 証明書エラー | ブロック (ページは白紙になり
load-failed シグナルが発火) |
全画面要求 (requestFullscreen()) |
ページ側は成功扱い (要素はビュー内で拡大) だが、ウィンドウは全画面化されない中途半端な状態になる — ハンドラの実装を推奨 |
| したいこと | API | 状態 |
|---|---|---|
| alert / confirm / prompt を自前 UI で | blink_web_view_set_javascript_dialog_handler() |
動作 |
| ファイル選択を自前 UI で | blink_web_view_set_file_chooser_handler() +
blink_web_view_file_chooser_response() |
動作 |
| 証明書エラーを判断する | blink_web_view_set_certificate_error_handler() |
動作 |
| 全画面要求に応答する | blink_web_view_set_fullscreen_handler() +
exit_fullscreen() / is_fullscreen() |
動作 |
| ダウンロード先を決める | blink_web_view_set_download_handler() |
未配線 |
| HTTP 認証に応答する | blink_web_view_set_auth_handler() +
blink_web_view_auth_response() |
未配線 |
alert() / confirm() / prompt()
の要求を受け取ります。
シグネチャ:
typedef enum {
BLINK_JS_DIALOG_ALERT = 0,
BLINK_JS_DIALOG_CONFIRM = 1,
BLINK_JS_DIALOG_PROMPT = 2,
BLINK_JS_DIALOG_BEFORE_UNLOAD = 3, /* 予約 — 現在は届かない (下記) */
} BlinkJSDialogType;
typedef void (*BlinkJSDialogResponseCallback)(gboolean success,
const char* input_text,
gpointer user_data);
typedef gboolean (*BlinkJSDialogRequestCallback)(
BlinkWebView* web_view,
BlinkJSDialogType dialog_type,
const char* message,
const char* default_prompt,
BlinkJSDialogResponseCallback response_callback,
gpointer response_user_data,
gpointer user_data);
void blink_web_view_set_javascript_dialog_handler(
BlinkWebView* web_view,
BlinkJSDialogRequestCallback callback,
gpointer user_data);ハンドラは自前のダイアログを表示し、ユーザーの応答が決まったら
必ず response_callback
を呼びます。非同期でかまいません —
ハンドラから TRUE
を返してダイアログを表示し、ボタンが押されてから
response_callback を呼べます。
success: OK なら TRUE、キャンセルなら
FALSEinput_text: prompt の入力文字列 (それ以外は
NULL でよい)FALSE を返すと既定動作 (上表)
になりますcallback に NULL を渡すと解除です| 観点 | 動作 |
|---|---|
| スレッド | GTK メインスレッド。GTK ダイアログを直接開いてよい |
| ページ側の待ち方 | alert() 等は同期 IPC。応答するまでそのページの
JavaScript は完全に停止する |
| 応答しないと | ページは永久に停止する。タイムアウトによる救済はない。さらに同じレンダラプロセスに属する他の WebView も入力を受け付けなくなる |
message の中身 |
改行は \n に正規化されて届く (\r\n /
\r は \n になる) |
| 呼び出し元の区別 | どのフレームからの要求かは渡らない。iframe 内の広告の
alert() も、メインページの alert()
と区別できない。message にサイト名を含めない自前 UI
だと、なりすまし表示に使われる余地がある |
| beforeunload | ハンドラには届かない。エンジンが常に「離脱許可」で即応答するため、「このページを離れますか」の確認は現在実装できない
(BLINK_JS_DIALOG_BEFORE_UNLOAD は将来のための予約値) |
| ダイアログの取り下げ | ページ遷移・タブ破棄・レンダラクラッシュが起きても「ダイアログを閉じてよい」通知は来ない。アプリのダイアログは出たままになるため、load-changed
シグナルで自前ダイアログを閉じる保険を入れておくとよい |
response_callback の応答先は プロセス全体で 1
スロットしかありません。
1 つ目のダイアログに応答する前に 2 つ目の要求が来ると (別 WebView
から、
または別 iframe から)、1
つ目の応答先が失われ、そのページは永久に停止
します。手元に保持していた古い response_callback
を呼ぶと、新しい方の
ダイアログに応答してしまいます。
response_user_data は現在の実装では使われません
(何を渡してもresponse_callback
には届きません)。応答に文脈が必要な場合は自前のtypedef struct {
BlinkJSDialogResponseCallback respond;
gpointer respond_data;
} ConfirmCtx;
static void on_confirm_choice(GObject *src, GAsyncResult *res, gpointer data) {
ConfirmCtx *ctx = data;
int btn = gtk_alert_dialog_choose_finish(GTK_ALERT_DIALOG(src), res, NULL);
ctx->respond(btn == 1, NULL, ctx->respond_data);
g_free(ctx);
}
static gboolean on_js_dialog(BlinkWebView *view, BlinkJSDialogType type,
const char *message, const char *default_prompt,
BlinkJSDialogResponseCallback respond,
gpointer respond_data, gpointer user_data) {
if (type != BLINK_JS_DIALOG_CONFIRM)
return FALSE; /* alert / prompt は既定動作 */
GtkAlertDialog *dlg = gtk_alert_dialog_new("%s", message);
const char *buttons[] = { "キャンセル", "OK", NULL };
gtk_alert_dialog_set_buttons(dlg, buttons);
gtk_alert_dialog_set_default_button(dlg, 1);
gtk_alert_dialog_set_cancel_button(dlg, 0);
ConfirmCtx *ctx = g_new0(ConfirmCtx, 1);
ctx->respond = respond;
ctx->respond_data = respond_data;
gtk_alert_dialog_choose(dlg, GTK_WINDOW(user_data), NULL,
on_confirm_choice, ctx);
g_object_unref(dlg);
return TRUE;
}GtkAlertDialog は生成時に message
を複製するため、ハンドラから戻った後も
表示は安全です。自前ウィジェットに表示する場合は
g_strdup(message) して
ください。
<input type="file"> の要求を受け取ります。
シグネチャ:
typedef gboolean (*BlinkFileChooserRequestCallback)(
BlinkWebView* web_view,
gboolean allow_multiple,
const char* accept_types,
gpointer user_data);
void blink_web_view_set_file_chooser_handler(
BlinkWebView* web_view,
BlinkFileChooserRequestCallback callback,
gpointer user_data);ハンドラで GtkFileDialog
などを開き、選択が決まったら
blink_web_view_file_chooser_response() を呼びます。
accept 属性の内容が MIME タイプ →
拡張子の順に並べ替えられ、全て
小文字化されたカンマ区切り文字列で届きます。属性の記述順は保存されません。
<input type="file" accept=".PNG, image/jpeg, .txt">→ ハンドラに届く文字列: "image/jpeg,.png,.txt"
accept 属性がない場合は 空文字列
"" です (NULL は来ません)。
不正な要素 (MIME 形式でも .拡張子 形式でもないもの)
は届く前に
除去されています。
シグネチャ:
void blink_web_view_file_chooser_response(BlinkWebView* web_view,
const char** file_paths);file_paths は NULL
終端の絶対パス配列です。配列と文字列は呼び出し時に
複製されるので、呼び出し後に解放して構いません。
const char *paths[] = { "/home/user/photo.jpg", NULL };
blink_web_view_file_chooser_response(view, paths);NULL (または先頭が
NULL の空配列) を渡します。cancel
イベントが発火し、選択済みだったファイルは保持| 観点 | 動作 |
|---|---|
| パスの検証 | 一切行われません。存在しないパスでも
change
イベントは発火し、ページが読み取ろうとした時点で初めて失敗します。相対パスは事実上機能しません
— 必ず絶対パスを渡してください |
| セキュリティ | 応答したパスには、そのページからの読み取り権限が与えられます。ページが要求したファイル種別かどうか、ユーザーが本当に選んだファイルかどうかの確認はアプリの責任です |
| 複数選択 | allow_multiple が FALSE
のとき複数パスを返すと、単一選択のはずの <input>
に複数ファイルが入った不正な状態になります。アプリ側で 1
個に絞ってください |
ディレクトリ (webkitdirectory) |
非対応です。ディレクトリ要求は
allow_multiple = FALSE
の通常要求と区別できない形で届き、応答してもページ側の
webkitRelativePath が空になるため機能しません |
ハンドラが FALSE を返したとき |
要求はキャンセルされます。その後に
file_chooser_response() を呼ばないでください
(すでに応答済みの要求への二重応答となり、動作が未定義です) |
| 応答しないままにすると | その WebView
のファイル選択が以後すべて即キャンセルになり、さらに
window.open() もブロックされます。ダイアログを閉じる経路
(キャンセルボタン・Esc・ウィンドウクローズ) の全てで必ず response
を呼んでください |
| ページ遷移後の応答 | pending 中にページ遷移すると要求は内部で失効します。その後の response は静かに無視されます (クラッシュしません) |
static void on_file_open(GObject *src, GAsyncResult *res, gpointer data) {
BlinkWebView *view = BLINK_WEB_VIEW(data);
GFile *file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(src), res, NULL);
if (file) {
char *path = g_file_get_path(file);
const char *paths[] = { path, NULL };
blink_web_view_file_chooser_response(view, paths);
g_free(path);
g_object_unref(file);
} else {
blink_web_view_file_chooser_response(view, NULL); /* キャンセル */
}
}
static gboolean on_file_chooser(BlinkWebView *view, gboolean allow_multiple,
const char *accept_types, gpointer user_data) {
GtkFileDialog *dlg = gtk_file_dialog_new();
gtk_file_dialog_open(dlg, GTK_WINDOW(user_data), NULL, on_file_open, view);
g_object_unref(dlg);
return TRUE;
}キャンセル経路 (file == NULL) でも必ず response
を呼んでいる点が重要です。
SSL 証明書エラーの続行可否を判断します。
シグネチャ:
typedef gboolean (*BlinkCertErrorCallback)(
BlinkWebView* web_view,
const char* url,
const char* error_description,
gpointer user_data);
void blink_web_view_set_certificate_error_handler(
BlinkWebView* web_view,
BlinkCertErrorCallback callback,
gpointer user_data);TRUE で続行、FALSE でブロックします。
既定 (ハンドラ未設定)
は全ブロックです。自己署名証明書の社内サイトや
開発サーバを表示したい場合にだけ、ハンドラで明示的に TRUE
を返します。
外部の任意サイトを表示する用途では、既定のままが安全です。
| 観点 | 動作 |
|---|---|
| 応答方式 | 同期のみ。ハンドラの戻り値で即決定され、後から応答する API はありません。ハンドラ実行中は UI スレッドが止まるため、ダイアログを出してユーザーに聞く実装はできません — 許可リスト等の即時判定にしてください |
| 呼ばれる頻度 | エラーを起こすリクエストごとに毎回呼ばれます。判断は記憶されません。証明書が不正なホストから画像を 20 枚読むページでは 20 回呼ばれ得ます |
url |
エラーを起こした個々のリソースの URL です。メインページとは限らず、サブリソース (画像・fetch) の場合はその URL が入ります。メインフレームかどうかを区別する手段は現在ありません |
error_description |
net::ERR_CERT_AUTHORITY_INVALID のような
Chromium net
エラーの識別子文字列です。人間向けの説明文ではありません。証明書本体・有効期限・発行者は渡りません
— 判断材料は実質 URL のホスト名です |
| ブロック時のページ | Chrome
のような警告ページは出ず、白紙になります。同時に
load-failed シグナル (error_code は -200
番台の負値) が発火するので、自前のエラー画面はそちらで出します |
TRUE の効き方 |
HSTS が有効なホストでも続行できてしまいます (Chrome
では続行不能なケース)。TRUE
を返す条件は最小限にしてください |
| 届かないケース | Service Worker 経由のリクエストの証明書エラーはハンドラに届かず常にブロックされます。また DevTools 接続中はツール側が判断を横取りする場合があります |
static gboolean on_cert_error(BlinkWebView *view, const char *url,
const char *error, gpointer user_data) {
/* 社内の自己署名ホストだけ続行を許可。判断は同期で返す */
gboolean allow = g_str_has_prefix(url, "https://intranet.example.jp/");
g_message("cert error [%s] %s -> %s", error, url,
allow ? "continue" : "block");
return allow;
}
/* ブロックされたときの画面は load-failed 側で用意する */
static void on_load_failed(BlinkWebView *view, int error_code,
const char *failing_uri, gpointer user_data) {
if (error_code <= -200 && error_code > -300) { /* net の証明書エラー帯 */
blink_web_view_load_uri(view, "app://error/cert.html");
}
}app:// の自前エラーページは [アプリ連携 API
のカスタムスキーム]
(./app-integration-api-ja.md) で配信できます。load-failed
のパラメータは
(error_code, failing_uri) の順です (Signals API)。
ページの requestFullscreen() /
全画面解除の要求を受け取ります。
シグネチャ:
typedef void (*BlinkFullscreenCallback)(BlinkWebView* web_view,
gboolean enter_fullscreen,
gpointer user_data);
void blink_web_view_set_fullscreen_handler(BlinkWebView* web_view,
BlinkFullscreenCallback callback,
gpointer user_data);
void blink_web_view_exit_fullscreen(BlinkWebView* web_view);
gboolean blink_web_view_is_fullscreen(BlinkWebView* web_view);| 観点 | 動作 |
|---|---|
| ハンドラ未設定時 | ページ側の requestFullscreen()
は成功扱いになります
(拒否されません)。要素はビュー内いっぱいに広がりますが、GTK
ウィンドウは全画面化されません —
この中途半端な状態を避けるため、動画等を扱うアプリではハンドラの実装を推奨します |
| ハンドラの役割 | enter_fullscreen に応じて
gtk_window_fullscreen() /
gtk_window_unfullscreen() を呼ぶのが基本形です |
| 状態同期は片方向 | エンジン → アプリの通知のみです。ユーザーが WM
側の操作で全画面を解除してもエンジンには伝わりません。アプリ側から解除するときは
gtk_window_unfullscreen() だけでなく必ず
blink_web_view_exit_fullscreen() を呼んでください
(これを介すと Blink 側が解除され、ハンドラに
enter_fullscreen=FALSE が届きます) |
| ESC キー | エンジンは処理しません。アプリで実装します (capture
フェーズのキーコントローラで ESC を拾い exit_fullscreen()
を呼ぶ) |
| 要求元の識別 | iframe 由来かどうか等の情報は渡りません |
static void on_fullscreen(BlinkWebView *view, gboolean enter,
gpointer user_data) {
GtkWindow *win = GTK_WINDOW(user_data);
if (enter)
gtk_window_fullscreen(win);
else
gtk_window_unfullscreen(win);
}
/* ESC で解除 (エンジンは ESC を処理しないためアプリの責務) */
static gboolean on_key(GtkEventControllerKey *c, guint keyval, guint code,
GdkModifierType state, gpointer user_data) {
BlinkWebView *view = BLINK_WEB_VIEW(user_data);
if (keyval == GDK_KEY_Escape && blink_web_view_is_fullscreen(view)) {
blink_web_view_exit_fullscreen(view); /* Blink 側から正しく解除 */
return TRUE;
}
return FALSE;
}
blink_web_view_set_fullscreen_handler(BLINK_WEB_VIEW(view), on_fullscreen, win);このハンドラは現在も配線されておらず、登録しても呼ばれません。
typedef char* (*BlinkDownloadRequestCallback)(
BlinkWebView* web_view,
const char* url,
const char* suggested_filename,
const char* mime_type,
gpointer user_data);
void blink_web_view_set_download_handler(
BlinkWebView* web_view,
BlinkDownloadRequestCallback callback,
gpointer user_data);実際の挙動:
<a download>、Content-Disposition: attachment
など)、開始直後に内部でキャンセルNULL を返すと
~/Downloads/ に配線はエンジン側の既知課題として管理しています。API
シグネチャは配線後も
このまま使える予定なので、登録コードを書いておくこと自体は無害です
(呼ばれないだけです)。
ファイルの保存が必要なアプリでは、ページ内 JS
で取得してアプリに渡す
構成が現実的です (アプリ連携
API の
JS ブリッジを利用):
/* ページ側: ファイルを取得して C 側へ送る (小さめのファイル向け) */
const buf = await (await fetch(fileUrl)).arrayBuffer();
window.blinkgtk.postMessage(buf); /* ArrayBuffer はバイナリのまま届く */C 側は message-received
でバイトを受け取り、GFile 等で書き出します。
保存先の決定・上書き確認・進捗表示まで、アプリの UI
として完全に制御
できます。
このハンドラも現在も配線されておらず、登録しても呼ばれません。
blink_web_view_auth_response()
は現状、常に何もしません。
typedef gboolean (*BlinkAuthRequestCallback)(
BlinkWebView* web_view,
const char* url,
const char* realm,
gboolean is_proxy,
gpointer user_data);
void blink_web_view_set_auth_handler(
BlinkWebView* web_view,
BlinkAuthRequestCallback callback,
gpointer user_data);
void blink_web_view_auth_response(BlinkWebView* web_view,
const char* username,
const char* password);実際の挙動: HTTP 401 / プロキシ 407
の認証要求は常に即キャンセル
され、サーバの 401 レスポンスボディがそのまま表示されます。
回避策は限定的です:
fetch() に
Authorization ヘッダを配線はダウンロードと合わせてエンジン側の既知課題として管理しています。
動作するハンドラ 3 種だけを組み合わせた完全な例です。
#include <blink_gtk/blink_gtk.h>
#include <gtk/gtk.h>
typedef struct {
BlinkJSDialogResponseCallback respond;
gpointer respond_data;
} ConfirmCtx;
static void on_confirm_choice(GObject *src, GAsyncResult *res, gpointer data) {
ConfirmCtx *ctx = data;
int btn = gtk_alert_dialog_choose_finish(GTK_ALERT_DIALOG(src), res, NULL);
ctx->respond(btn == 1, NULL, ctx->respond_data);
g_free(ctx);
}
static gboolean on_js_dialog(BlinkWebView *view, BlinkJSDialogType type,
const char *message, const char *default_prompt,
BlinkJSDialogResponseCallback respond,
gpointer respond_data, gpointer user_data) {
if (type != BLINK_JS_DIALOG_CONFIRM)
return FALSE; /* alert / prompt は既定動作 */
GtkAlertDialog *dlg = gtk_alert_dialog_new("%s", message);
const char *buttons[] = { "キャンセル", "OK", NULL };
gtk_alert_dialog_set_buttons(dlg, buttons);
gtk_alert_dialog_set_default_button(dlg, 1);
gtk_alert_dialog_set_cancel_button(dlg, 0);
ConfirmCtx *ctx = g_new0(ConfirmCtx, 1);
ctx->respond = respond;
ctx->respond_data = respond_data;
gtk_alert_dialog_choose(dlg, GTK_WINDOW(user_data), NULL,
on_confirm_choice, ctx);
g_object_unref(dlg);
return TRUE;
}
static void on_file_open(GObject *src, GAsyncResult *res, gpointer data) {
BlinkWebView *view = BLINK_WEB_VIEW(data);
GFile *file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(src), res, NULL);
if (file) {
char *path = g_file_get_path(file);
const char *paths[] = { path, NULL };
blink_web_view_file_chooser_response(view, paths);
g_free(path);
g_object_unref(file);
} else {
blink_web_view_file_chooser_response(view, NULL);
}
}
static gboolean on_file_chooser(BlinkWebView *view, gboolean allow_multiple,
const char *accept_types, gpointer user_data) {
GtkFileDialog *dlg = gtk_file_dialog_new();
gtk_file_dialog_open(dlg, GTK_WINDOW(user_data), NULL, on_file_open, view);
g_object_unref(dlg);
return TRUE;
}
static gboolean on_cert_error(BlinkWebView *view, const char *url,
const char *error, gpointer user_data) {
return g_str_has_prefix(url, "https://intranet.example.jp/");
}
int main(int argc, char **argv) {
blink_gtk_init(&argc, &argv);
GtkWidget *win = gtk_window_new();
gtk_window_set_default_size(GTK_WINDOW(win), 1024, 768);
GtkWidget *view = blink_web_view_new();
gtk_window_set_child(GTK_WINDOW(win), view);
blink_web_view_set_javascript_dialog_handler(BLINK_WEB_VIEW(view),
on_js_dialog, win);
blink_web_view_set_file_chooser_handler(BLINK_WEB_VIEW(view),
on_file_chooser, win);
blink_web_view_set_certificate_error_handler(BLINK_WEB_VIEW(view),
on_cert_error, NULL);
gtk_window_present(GTK_WINDOW(win));
blink_web_view_load_uri(BLINK_WEB_VIEW(view), "https://example.com/");
return blink_gtk_run_main_loop();
}ビルド:
cc -o handlers-demo handlers-demo.c $(pkg-config --cflags --libs blinkgtk-0.1)| 項目 | 現状 |
|---|---|
| ダウンロード | 未配線。ダウンロードは無通知でキャンセルされる |
| HTTP 認証 | 未配線。401/407 は常にキャンセル。auth_response()
は無効 |
| beforeunload | ハンドラに届かず、常に離脱許可 |
| JS ダイアログの同時数 | プロセス全体で 1 つ。2 つ目の要求で 1 つ目のページが停止する |
| ダイアログの取り下げ通知 | ない。ページ遷移してもアプリのダイアログは残る |
| ディレクトリアップロード | 非対応 (webkitRelativePath が空になる) |
| 証明書エラーの非同期判断 | 不可 (同期戻り値のみ)。ユーザー確認ダイアログは出せない |
| 証明書エラーの判断の記憶 | されない。リクエストごとに毎回呼ばれる |
| 要求元フレームの識別 | 不可 (JS ダイアログ・証明書エラーとも、メインフレームか iframe かは渡らない) |
user_data の解放通知 |
ない (GDestroyNotify 引数なし) |
| GObject シグナル版 | ない (本ページの 5 ハンドラは C
関数ポインタのみ。言語バインディングからは権限要求 =
permission-request シグナルのみ利用可) |
permission-request
シグナル (Signals API) —
シグナルハンドラ内で同期的に allow/deny
を呼ぶ必要があります| バージョン | 変更 |
|---|---|
| v1.1.0 (2026-07-30 追補) | 全画面 (Fullscreen API) ハンドラの節を新設 (従来どの doc にも実体解説がなかった) |
| v1.1.0 (2026-07-30) | エンジン実装の実測に基づき全面改訂。既定動作表を訂正 (証明書エラー = ブロック / ダウンロード = 保存されない / prompt = null)。ダウンロード・HTTP 認証の未配線と回避策、JS ダイアログの同時 1 件制約、beforeunload 未実装、accept_types の実形式、応答忘れの帰結を明記 |
| v1.1.0 (2026-07-29) | 本ページ初版 |