runtime.onStartup を有効無効切り替え時にも呼び出す
はじめに
Manifest V3 で拡張機能のバックグラウンド処理は、無期限に生存できなくなりました。
それに伴い、chrome.runtime.onInstalled
/chrome.runtime.onStartup
が重要な処理を担うようになりました。ただし、これには問題があります。拡張機能の有効無効切り替えを認識できないことです。
無効状態の拡張機能が有効状態に遷移した時、chrome.runtime.onStartup
は発火しません。この問題を解決する必要があります。
問題
background.jschrome.runtime.onInstalled.addListener(onInstalled);
chrome.runtime.onStartup.addListener(onStartup);
拡張機能の有効無効切り替え時にonStartup
が動作しません。
解決策1
background.jschrome.runtime.onInstalled.addListener(onInstalled);
//chrome.runtime.onStartup.addListener(onStartup);
onStartup();
// 備考:Service Worker の復帰毎に処理を実施します
background.js の起動毎にonStartup
処理を実行します。
愚かな行為であることは認識していますが、これはある程度有効に機能します。
解決策2
background.jschrome.runtime.onInstalled.addListener(onInstalled);
chrome.runtime.onStartup.addListener(onStartup);
chrome.management.onEnabled.addListener((info) => {
if (info.id === chrome.runtime.id) {
onStartup();
}
// 備考:起動時にブラウザ内の有効な拡張機能毎に呼び出されます
// もちろん、有効無効切り替え時にも呼び出されます
});
この方法は、有効無効の切り替えに対して有効に機能しますが、問題を抱えています。
onStartup
は、ブラウザ起動時に2回呼び出されます。chrome.runtime.onStartup
,chrome.management.onEnabled
の両方からonStartup
が呼び出されることが原因です。
management
権限を必要とします。chrome.management
へアクセスするために追加で権限が必要になります。この権限は、ブラウザ上の他の拡張機能の状態を取得する権限です。これは、拡張機能に分不相応な権限を付与することになります。
参考
解決策3
background.jsconst startup = async () => {
const sessionStorage = await chrome.storage.session?.get({startup:false});
if (!sessionStorage?.startup) {
await chrome.storage.session?.set({startup:true});
await onStartup();
}
// 備考:有効無効時は chrome.runtime.onStartup が呼ばれない対策
// 備考:chrome.storage.session
// 更新時、内容は消える
// 有効無効時、内容は消える
// Service Worker 復帰時、内容は残る
// 対応時期:Chrome 102, Firefox115
};
chrome.runtime.onInstalled.addListener(onInstalled);
//chrome.runtime.onStartup.addListener(onStartup);
startup();
新機能のchrome.storage.session
を使用する方法です。storage
権限を必要としますが、一般的にこの権限が問題となることはないはずです。
参考
最終案
background.jslet startupingPromise = null;
const startuping = async () => {
const sessionStorage = await chrome.storage.session?.get({startup:false});
if (!sessionStorage?.startup) {
await chrome.storage.session?.set({startup:true});
const manifest = chrome.runtime.getManifest();
const localStorage = await chrome.storage.local.get({extension_version:''});
if (manifest.version != localStorage.extension_version) {
await chrome.storage.local.set({extension_version:manifest.version});
await onInstalled();
}
// 備考:無効状態の拡張機能が更新しても chrome.runtime.onInstalled が呼ばれない対策(Chrome 限定?)
// see https://issues.chromium.org/issues/41116832
await onStartup();
}
// 備考:有効無効時は chrome.runtime.onStartup が呼ばれない対策
// 備考:chrome.storage.session
// 更新時、内容は消える
// 有効無効時、内容は消える
// Service Worker 復帰時、内容は残る
// 対応時期:Chrome 102, Firefox115
// 備考:onInstalled, onStartup の実行順・実行タイミングを保証します
// 標準機能では、 onStartup > onInstalled の順で実行されることがあります
// また、並行(非同期)に実行されることがあります
};
const startup = async () => {
if (startupingPromise) {
await startupingPromise;
} else {
startupingPromise = startuping();
await startupingPromise;
//startupingPromise = null;
}
// 備考:並行実行を阻止する。完了を待機する。シングルトン
};
chrome.runtime.onInstalled.addListener(startup);
chrome.runtime.onStartup.addListener(startup);
startup();
// 備考:chrome.runtime.onStartup を呼び出さないと、
// 起動時に background.js が動作しない対策(Firefox 限定?)
※onInstalled
は、バージョンアップ毎に呼び出されます。
更新毎には、呼び出されません。
通常は、問題になりませんがデバッグで問題になることがあります。
※次の要素名は変更可能です。
chrome.storage.session
: {startup}
chrome.storage.local
: {extension_version}
備考
onStartup
は、次の機能の初期化に必要になります。
chrome.action.setPopup({popup});
// 備考:クリックとポップアップ両方に対応する場合、設定が必要になります
chrome.contextMenus.create();
// 備考:起動時は、メニューがないため、作成する必要があります
備考:無効状態で更新されても更新イベントが発火しない
無効状態の拡張機能が更新してもchrome.runtime.onInstalled
が呼び出されない。
(Chrome 限定?)
備考:スタートアップキャッシュクリア済みの起動時
スタートアップキャッシュクリア済みの起動時は、 Firefox のコンテキストメニューの初期化が必要になる。だが、chrome.runtime.onStartup
なしの起動時は、 background.js が呼び出されない。(スタートアップキャッシュクリアなしの Firefox 起動時は、コンテキストメニューがあるため、問題とはならない)
- Profile folder(xxx.default)/startupCache (手動削除)
- [about:support] > [Clear startup cache..]