BlinkGTK GObject Signals API リファレンス

バージョン: 1.2.5-build5
最終更新: 2026-07-30
言語: 日本語 |

English


このページについて

BlinkWebView が発する GObject シグナル 8 本のリファレンスです。ページの
ロード状態・URL・タイトルの変化、新規ウィンドウ要求、JS からのメッセージ、
権限要求を受け取れます。

本ページはエンジン実装を実測して書かれています。ハンドラやシグナルの状況は
全体は 2026-08-04 時点 (1.2.0-build2) で確認し、その後に追加・変更した行は、行ごとに版を書いています。

旧版 (2026-07-29 以前) からの重大な訂正: 旧版は title-changed /
uri-changed のコールバックに文字列引数があるかのように記載していましたが、
両シグナルに引数はありません。旧版のシグネチャどおりに書くと
user_data が文字列引数の位置に入り、クラッシュやメモリ破壊の原因に
なります。本ページのシグネチャが実装どおりの正です。

シグナル一覧 (実装どおり)

シグナル名 追加引数 発火タイミング 用途
load-changed BlinkLoadEvent ロード状態変化 (下記の表に注意) プログレス表示、UI更新
load-failed error_code (int), failing_uri (この順) コミットされない失敗のみ (下記) エラーハンドリング
title-changed なし — 値は get_title() で取る タイトル変化 ウィンドウタイトル更新
uri-changed なし — 値は get_uri() で取る URL 変化 アドレスバー更新
new-window-requested url window.open() / target="_blank" 新規ウィンドウ要求の通知 (制御は decide-navigation で、下記)
decide-navigation BlinkNavigationDecision* → gboolean ページ発の移動・新しいウィンドウ 行き先をアプリが決める (v1.2.3-build2 以降、下記)
message-received name, data (2 引数) ページ JS からの postMessage JS→C 連携 (アプリ連携 API)
permission-request BlinkPermissionRequest* ページが権限を要求 位置情報・通知等の許可判断 (既定は拒否・同期応答必須)
render-path-changed path_id, result, gate_ms, seq (4 引数) 描画経路スワップ完了 switch_render_path() の結果通知 (v1.2.0-build9 以降)

GtkWidget 由来のプロパティ通知 notify::uri / notify::title /
notify::is-loading も使えます。

WebKitGTK からの移行では、notify::uri / notify::title をそのまま流用するのが
最も簡単です。

まず知っておく 3 つの事実

  1. エンジン発のシグナルは全て GTK メインスレッドで届きます (Chromium の
    UI スレッド = GTK メインループ)。ハンドラから GTK API を直接呼べます。
    一方、load_uri() が同期発火するシグナル (下記) は「API を呼んだ
    スレッド」で走るため、BlinkGTK API はメインスレッドから呼んでください。
  2. title-changed / uri-changed に引数はありません。 値はハンドラ内で
    blink_web_view_get_title() / blink_web_view_get_uri() から取ります。
  3. getter が返す文字列は内部の静的バッファで、次に同じ getter を呼ぶと
    上書きされます。保持するなら g_strdup() してください。

基本形

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 */
    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

シグネチャ

void user_function(BlinkWebView* web_view,
                   BlinkLoadEvent load_event,
                   gpointer user_data);

BlinkLoadEvent の値 (明示値 — 順序に注意)

typedef enum {
    BLINK_LOAD_STARTED    = 0,
    BLINK_LOAD_COMMITTED  = 1,
    BLINK_LOAD_FINISHED   = 2,
    BLINK_LOAD_REDIRECTED = 3   /* 予約 — 現在のランタイムは発火しません */
} BlinkLoadEvent;

各値の実際の意味

値 実際の発火条件
STARTED blink_web_view_load_uri() / load_html() を呼んだときに同期発火。それ以外では来ません — リンククリック・location.href・go_back()/go_forward()/reload()・ページ側起点の遷移では発火しない (WebKitGTK との最大の非互換点)
COMMITTED ナビゲーションのコミット時。ただしフレームの区別なし — iframe のロードや、pushState / fragment 移動 (same-document) でも毎回発火します。「新しいページに入った」の判定には使えません
FINISHED メインフレームの onload 完了。iframe では来ない。pushState / fragment では来ない。履歴の戻る/進むがキャッシュ復元 (BFCache) だった場合も来ない

操作別の発火パターン (実測)

操作 STARTED COMMITTED FINISHED uri-changed
load_uri() (通常遷移) ○ ○ ○ ○ (2 回のことあり、下記)
load_html() ○ ○ ○ —
リンククリック / location.href — ○ ○ ○
pushState / fragment (#foo) — ○ — ○
go_back() / go_forward() (通常) — ○ ○ ○
同 (BFCache 復元時) — ○ — ○
reload() — ○ ○ — (URL 不変)
iframe のロード・遷移 — ○ (iframe ごと) — —

FINISHED 時点で保証されること

FINISHED の直前に、エンジンは注入 CSS (アプリ連携 API)
と JS メッセージブリッジ (window.blinkgtk) のセットアップを済ませています。
FINISHED ハンドラの中から execute_javascript() を呼べばブリッジは使えます。

ただし FINISHED は onload であり、ページが動的に組み立てるコンテンツの完成
までは意味しません (描画完成の通知は現在未提供です)。

スピナー (読み込み中表示) の正しい止め方

1.2.5 からは notify::is-loading で切り替えるのが確実です。 読み込みの開始と終了のたびに
通知が来て、blink_web_view_is_loading() の値が変わっています。STARTED が来ない操作
(戻る・再読み込み) や、FINISHED が来ない応答 (load_uri() で開いた HTTP 204) でも来ることを
確かめています (ダウンロードでは試していません)。1.2.4 までは、読み込みの終わりに
FALSE の通知が来ませんでした。

load-changed だけで切り替える場合は、FINISHED だけで止めると永久スピナーになるケースがあります:

/* FINISHED または load-failed で止め、無音ケースはタイムアウトで拾う */

load-failed

シグネチャ

void user_function(BlinkWebView* web_view,
                   int error_code,
                   const char* failing_uri,
                   gpointer user_data);

引数は (error_code, failing_uri) の順です。 逆順で書いたコールバックは
ポインタを整数として読み、クラッシュや文字化けの原因になります。

いつ発火するか (重要 — 直感と異なります)

発火条件は「コミットされずに終わったナビゲーション + net エラーあり」
だけです。帰結:

ケース load-failed 実際に起きること
ユーザー操作・新しい遷移によるキャンセル ○ (最頻) error_code = -3 (ERR_ABORTED)。正常な操作でも頻繁に飛ぶため、無条件にエラーダイアログを出すと乱発します
DNS 失敗・接続不能 (例: -105, -102) — (原則) Chromium がエラーページをコミットするため、COMMITTED + FINISHED が来て既定のエラーページが表示されます
HTTP 404 / 500 — 正常コミット (net エラーではない)。COMMITTED + FINISHED
画像・CSS・fetch などサブリソースの失敗 — ナビゲーションではないため対象外
iframe のナビゲーション中断 ○ failing_uri は iframe の URL — メインページの URL と比較するガードを推奨
ダウンロード化 — どのシグナルも来ません
load_uri() で開いた HTTP 204 ○ error_code = -3。FINISHED は来ない (1.2.5 で確認。205・リンクからの 204 は未確認)

error_code の実際の型

値は Chromium の net エラーコードの生の負値です (-3, -105, …)。
ヘッダの BlinkLoadError enum (BLINK_LOAD_ERROR_NETWORK 等) は現在の
ランタイムでは使われません — C では int として扱ってください。

値 定数名 (参考) 意味
-3 net::ERR_ABORTED 中断 (最頻・正常操作でも発生)
-7 net::ERR_TIMED_OUT タイムアウト
-105 net::ERR_NAME_NOT_RESOLVED DNS 解決失敗 (通常はエラーページがコミットされ load-failed は来ない)

完全なリストは Chromium net_error_list.h を参照してください。

実装パターン

static void on_load_failed(BlinkWebView *view, int error_code,
                           const char *failing_uri, gpointer user_data) {
    if (error_code == -3)   /* ERR_ABORTED: 正常操作の副産物。無視が基本 */
        return;
    const char *current = blink_web_view_get_uri(view);
    if (current && failing_uri && strcmp(current, failing_uri) != 0)
        return;             /* iframe など、表示中ページ以外の失敗 */
    g_message("load failed: %d %s", error_code, failing_uri);
}

uri-changed / title-changed

シグネチャ (両方とも追加引数なし)

void user_function(BlinkWebView* web_view, gpointer user_data);

値はハンドラ内で blink_web_view_get_uri() / blink_web_view_get_title()
で取得します。

uri-changed の動作の詳細

観点 動作
発火点 ①load_uri() 呼び出し時 (同期) ②ナビゲーションのコミット時。redirect の途中経過では来ません
同一遷移で 2 回来ることがある ①は渡された文字列、②は正規化済み URL で重複判定するため、"http://example.com" → "http://example.com/" のような差で 2 回発火します。履歴スタックを自作する場合はアプリ側でも重複排除してください
①の時点の get_uri() まだコミットされていない URL を返します。遷移が失敗するとこの値は「実際には表示されなかった URL」になります
中断時の巻き戻し ありません。失敗した遷移の URL がアドレスバーに残る形になるため、load-failed で get_uri() を読み直して戻すのが確実です
load_html() 発火しません

title-changed の動作の詳細

観点 動作
発火条件 ページの <title> 設定時・document.title の動的変更時 (メインフレームのみ)
<title> の無いページ 発火しません (空文字で来るのではなく無音)
履歴の戻る/進む 履歴側に同じタイトルが記録済みだと発火しないことがあります。タブ UI は notify::title の併用が確実です
値の整形 前後の空白・改行は除去済みで届きます
get_title() の返り値 空になりません — タイトル未設定ページでは URL を整形した文字列が返ります。「タイトル無し」を空文字で判定するコードは動きません

new-window-requested

シグネチャ

void user_function(BlinkWebView* web_view,
                   const char* url,
                   gpointer user_data);

これは「通知」です — 制御は decide-navigation で

window.open() や <a target="_blank"> が起きたことを知らせるシグナルで、
このシグナルでは、開かせない・別の場所に開く、という制御はできません (戻り値なし)。
制御するには、v1.2.3-build2 以降の decide-navigation を使います。
decide-navigation がハンドラで決めた場合、このシグナルは出ません。
現在のエンジンはシングルウィンドウ動作で、シグナル発火の時点で既に同じ
WebView を対象 URL へナビゲートし始めています
。


decide-navigation (v1.2.3-build2 以降)

ページが始めた移動と新しいウィンドウの行き先を、アプリが決めます。
本の中の外部リンクをシステムのブラウザで開く、target="_blank" を別のビューで開く、
意図しないサイトへの移動を止める、といった用途です。

static gboolean on_decide(BlinkWebView *view, BlinkNavigationDecision *d, gpointer user_data) {
    const char *uri = blink_navigation_decision_get_uri(d);
    if (blink_navigation_decision_get_decision_type(d) == BLINK_NAVIGATION_DECISION_NEW_WINDOW) {
        GtkWidget *nv = blink_navigation_decision_create_view(d);  /* opener を保つ */
        GtkWidget *win = gtk_window_new();
        gtk_window_set_child(GTK_WINDOW(win), nv);
        gtk_window_present(GTK_WINDOW(win));
        return TRUE;
    }
    if (!g_str_has_prefix(uri, "https://example.org/")) {
        blink_navigation_decision_open_external(d);  /* 本の外はシステムのブラウザで */
        return TRUE;
    }
    return FALSE;  /* 既定の動作に任せる */
}
g_signal_connect(view, "decide-navigation", G_CALLBACK(on_decide), NULL);

何が届くか

答え方 (どれか 1 つを 1 回)

関数 何が起きるか
blink_navigation_decision_use() 今のビューで開く (既定と同じ)
blink_navigation_decision_ignore() 開かない
blink_navigation_decision_open_external() デスクトップの既定のハンドラ (システムのブラウザ等) で開く。ビューは動かない。http / https / mailto のみ。それ以外は止めるだけ
blink_navigation_decision_open_in_view(d, other) アプリが用意した別のビューに URI を読み込む。新しいウィンドウの場合、ページの window.open() の戻り値は閉じたウィンドウになり、window.opener は付かない
blink_navigation_decision_create_view(d) 新しいウィンドウのときだけ。ページのために作られた内容を引き取った新しいビューを返す。window.open() の戻り値と window.opener はつながったまま。同じ profile を使う

判断材料

確かめた範囲

release/scripts/check-navigation-decision.sh で、次を確かめています (software 経路)。

EGL 経路 (BLINKGTK_GPU_MODE=egl) で、create_view の新しいビューに内容が出るかは確かめていません。


message-received

ページ JS の window.blinkgtk.postMessage() を受けるシグナルです。
引数は (name, data) の 2 つ (チャネル名と Base64 データ)。詳細な
セマンティクス (エンコーディング・多重購読・C API 版ハンドラとの関係) は
アプリ連携 API にまとめてあります。


権限要求 (permission-request)

位置情報・通知などの権限要求を受けるシグナルです。

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);   /* 通知だけ許可 */
    else
        blink_permission_request_deny(req);
    return TRUE;   /* 処理した */
}

g_signal_connect(view, "permission-request", G_CALLBACK(on_permission), NULL);

仕様

/* 後から答える例 (1.2.3-build2 以降) */
static void on_answer(gboolean allow, gpointer data) {
    BlinkPermissionRequest *req = data;
    blink_permission_request_set_remember(req, TRUE);   /* 次回から聞かない */
    if (allow) blink_permission_request_allow(req);
    else       blink_permission_request_deny(req);
    blink_permission_request_unref(req);
}

static gboolean on_permission(BlinkWebView *view, BlinkPermissionRequest *req,
                              gpointer user_data) {
    if (g_strcmp0(blink_permission_request_get_type_name(req), "screen-wake-lock") != 0) {
        blink_permission_request_deny(req);
        return TRUE;
    }
    blink_permission_request_ref(req);
    ask_reader(blink_permission_request_get_origin(req), on_answer, req);  /* ダイアログを出す */
    return TRUE;
}

要求種別の一覧 (v1.2.2-build6 以降)

読書アプリが実際に使うもの:

名前 何を求めているか
"screen-wake-lock" 読書中に画面を消させない
"system-wake-lock" 端末を休止させない
"local-fonts" 端末に入っているフォントの一覧を読む
"persistent-storage" しおり・注釈を自動削除の対象から外す
"automatic-fullscreen" 操作なしで全画面へ入る
"pointer-lock" / "keyboard-lock" ページ送りの操作を占有する
"storage-access" / "top-level-storage-access" 別オリジンの保存領域を読む

ネットワーク:

名前 何を求めているか
"local-network-access" ローカルまたはループバックのアドレスへ接続する
"local-network" ローカルアドレスへ接続する
"loopback-network" ループバック (127.0.0.1 等) へ接続する
"background-fetch" ページを閉じても続く転送を始める
"background-sync" / "periodic-background-sync" 後から / 定期的に同期する
"payment-handler" 支払いの処理を引き受ける

入出力・機器:

名前 何を求めているか
"geolocation" / "geolocation-approximate" 現在地 / おおよその現在地
"notifications" 通知を出す
"audio-capture" / "video-capture" マイク / カメラ
"camera-pan-tilt-zoom" カメラの向きと倍率を動かす
"display-capture" 画面を取り込む
"captured-surface-control" 取り込んだ画面を操作する
"speaker-selection" 出力先のスピーカーを選ぶ
"midi" MIDI 機器
"sensors" 加速度・傾きなどのセンサー
"nfc" NFC
"smart-card" スマートカード
"web-printing" 印刷
"clipboard" クリップボードの読み書き
"idle-detection" 利用者が離席したかを知る
"window-management" 画面の配置とウィンドウの位置
"vr" / "ar" / "hand-tracking" WebXR
"protected-media-identifier" 保護された再生の識別子
"web-app-installation" アプリとしての導入

"unknown" が返るのは、Chromium 側に新しい種別が増えて BlinkGTK が
追随していない場合だけです。
その状態は
release/scripts/check-permission-type-names.sh が検出します。

カメラとマイク (v1.2.3-build2 以降)

ページが getUserMedia({audio: true, video: true}) を呼ぶと、permission-request が
1 回だけ届きます。このとき blink_permission_request_get_type_names() は、
"audio-capture" と "video-capture" の両方を返します。許可すればトラックが使えるようになり、拒否すれば
ページには NotAllowedError が返ります。覚えた決定があれば、シグナルを出さずにそれに従います。

view ごとに profile / cache を分ける (v1.2.2-build6 以降)

blink_web_view_new() で作った view は、1 プロセス内で profile と cache を
共有します
。同じ URL を複数の view で開くと、2 つ目以降はキャッシュに当たり、
サーバへの要求は 1 回に畳まれます。

分けたい場合は blink_web_view_new_with_profile() を使います。

GtkWidget *a = blink_web_view_new_with_profile("tab1");
GtkWidget *b = blink_web_view_new_with_profile("tab2");

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

render-path-changed (v1.2.0-build9 以降)

blink_web_view_switch_render_path() で要求した描画経路のライブ切替
(BlinkShift) の完了通知です。切替は非同期です。切替が成功したかどうかは、
このシグナルで受け取ってください (関数の返り値が示すのは「要求を受け付けたか」だけです)。

static void on_render_path_changed(BlinkWebView *view,
                                   const char *path_id,   /* "P1" / "P2" */
                                   const char *result,    /* 下表 */
                                   int gate_ms,           /* 切替に要した ms */
                                   int seq,               /* この swap の通し番号 */
                                   gpointer user_data) {
    g_print("render path -> %s (%s, %d ms, seq=%d)\n",
            path_id, result, gate_ms, seq);
}

g_signal_connect(view, "render-path-changed",
                 G_CALLBACK(on_render_path_changed), NULL);
int seq = blink_web_view_switch_render_path(view, "P2");
if (seq == 0)
    g_warning("switch 要求が受理されなかった");
/* seq は要求受理時に同期で確定します。完了 signal の seq 引数に同じ番号が
 * 載るため、swap の前後で採った記録 (ページ位置など) を「当の swap」に
 * 鍵付けできます。連続 swap では時刻の近さでの突き合わせは当てになりません */

result の意味

値 意味 アプリの扱い
"ok" 新経路で安定して描画している 成功。get_render_path() も新経路を返す
"rollback" 新経路が描画ゲートを通らず、旧経路に戻して継続 画面は壊れていない。切替は不成立
"fail" 旧経路にも戻せなかった エラーとして扱う

仕様


発火順序

実装で保証される順序:

保証されない順序:


ライフサイクルとスレッド


実用パターン: ブラウザ 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);
        /* 無音ケース (ダウンロード化・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 は正常操作の副産物 */
    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);
}

WebKitGTK からの移行

WebKitGTK BlinkGTK 注意
load-changed (STARTED) load-changed (STARTED) 非互換: BlinkGTK の STARTED は load_uri()/load_html() を呼んだときだけ。リンククリック等では来ません — スピナー開始は STARTED に依存しない設計に
load-changed (REDIRECTED) — BlinkGTK では発火しません
load-changed (COMMITTED / FINISHED) 同名 COMMITTED は iframe / same-document でも多重発火する点が異なります
load-failed load-failed 引数形式が違う (GError ではなく int + uri、順序は error_code が先)。発火条件も狭い (本ページの表を参照)
notify::title / notify::uri そのまま使えます 移行はこれが最も簡単。専用シグナル (title-changed / uri-changed) は引数なしの点だけ注意
create (新規ウィンドウ) decide-navigation + blink_navigation_decision_create_view() v1.2.3-build2 以降。新しいビューを返し、window.opener を保つ。それより前は通知のみ (new-window-requested)
decide-policy (NAVIGATION_ACTION / NEW_WINDOW_ACTION) decide-navigation v1.2.3-build2 以降。答えは use / ignore / open_external / open_in_view / create_view
permission-request permission-request 同期応答必須 (WebKitGTK のような遅延応答は不可)

制限事項まとめ

項目 現状
リダイレクトの検知 不可 (BLINK_LOAD_REDIRECTED は予約値)
ページ側起点ナビの開始検知 不可 (STARTED は API 呼び出し時のみ)
新規ウィンドウ要求の制御 v1.2.3-build2 以降は decide-navigation で可。それより前は不可 (通知のみ・常に同一 view で開く)
コミットされる失敗 (DNS 等) の load-failed 来ない (既定エラーページが表示される)
描画完成の通知 未提供 (FINISHED は onload まで)
言語バインディングからの permission-request 不可 (生ポインタ引数)

関連

変更履歴

バージョン 変更
v1.1.0 (2026-07-30) エンジン実装の実測に基づき全面改訂。title-changed / uri-changed のシグネチャを訂正 (引数なし — 旧版はクラッシュを招く誤記)。BLINK_LOAD_REDIRECTED が発火しない事実、STARTED が API 呼び出し時のみの事実、load-failed の実発火条件 (コミットされない失敗のみ / -3 が最頻)、操作別発火パターン表、発火順序、無音ケース (ダウンロード・204) とスピナー対策、new-window-requested が通知専用である事実を明記
v1.1.0 (2026-07-29 以前) 旧版