Manifest V3に対応したchrome拡張開発において、setTimeout・setIntervalが意図した動作をせず、ドキュメントを確認したところ、以下の記述がありました。
setTimeout() メソッドや setInterval() メソッドで、
https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers?hl=ja#convert-timers
遅延オペレーションや周期オペレーションを使用するのが一般的です。
ただし、Service Worker が終了するとタイマーがキャンセルされるため、
Service Worker でこれらの API が失敗することがあります。
拡張機能service workerはchromeの裏側で動くプログラムです。常に常駐しているわけではなく、以下の条件を満たすと拡張機能service workerは停止します。
https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle?hl=ja#idle-shutdown
- 操作が行われない状態で 30 秒経過した後。このタイマーは、イベントを受信するか拡張機能 API を呼び出すとリセットされます。
- 1 件のリクエスト(イベントや API 呼び出しなど)の処理に 5 分以上かかる場合。
fetch()
レスポンスが届くまでに 30 秒以上かかる場合。
特に今回の、「setTimeout・setIntervalを使用する」場合、1つめの条件を満たす可能性が高いので、それを承知した上で、setTimeout・setIntervalを使用する必要があります。
なお、停止したかどうかは、拡張機能ライブラリで確認ができます。停止している場合、「Service Worker (無効)」と表示されているので、そちらで確認ができます。
Chrome拡張機能作成
作成するのは以下の6つのファイルです。格納するフォルダによってmanifest.jsonの記述を変えてください。コンテンツスクリプト・拡張機能service workerそれぞれでsetTimeout・setIntervalが動作するように実装します。また、検証にはsetIntervalを用いてますが、setTimeoutでも同様の結果になります。
ファイル名 | 概要 |
index.html | 拡張機能の見た目を決めるhtmlファイル。 |
samplePage.html | コンテンツファイルの処理対象のhtml。ローカル保存で |
content.js | コンテンツスクリプト。samplePage.htmlで動かします |
service-worker.js | 拡張機能service worker。 |
popup.js | index.htmlファイル上のボタンの処理を制御するjsファイル。 |
manifest.json | 拡張機能の設定ファイル。 |
index.html
<!DOCTYPE html>
<html>
<head>
<title>Chrome Extension</title>
</head>
<body>
<h1>Chrome Extension</h1>
<button id="contentScriptTimer">Content Script Timer</button>
<button id="serviceWorkerTimer">Service Worker Timer</button>
<script src="popup.js"></script>
</body>
</html>
samplePage.html
<!DOCTYPE html>
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<h3>content-script</h3>
<p>Timer: <span id="contentScriptTimer">0</span></p>
<h3>service-worker</h3>
<p>Timer: <span id="serviceWorkerTimer">0</span></p>
</body>
</html>
content.js
let timer;
let contentScriptTimerCount = 0;
function startContentScriptTimer() {
timer = setInterval(function () {
contentScriptTimerCount++;
document.getElementById("contentScriptTimer").textContent = contentScriptTimerCount;
}, 31000);
}
function startServiceWorkerTimer(serviceWorkerTimerCount) {
document.getElementById("serviceWorkerTimer").textContent = serviceWorkerTimerCount;
}
chrome.runtime.onMessage.addListener(function (request) {
if (request.action === "startTimerFromContentScript") {
startContentScriptTimer();
} else if (request.action === "startTimerFromServiceWorker") {
startServiceWorkerTimer(request.count);
}
});
service-worker.js
let count = 0;
chrome.runtime.onMessage.addListener(function (request) {
if (request.action === "startTimerFromServiceWorker") {
timer = setInterval(function () {
count++;
chrome.tabs.query({ url: "file:///C:/timer-experiment/samplePage.html" }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, { action: "startTimerFromServiceWorker", count: count });
});
}, 1000);
}
});
popup.js
document.getElementById("contentScriptTimer").addEventListener("click", function () {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, { action: "startTimerFromContentScript" });
});
});
document.getElementById("serviceWorkerTimer").addEventListener("click", function () {
chrome.runtime.sendMessage({ action: "startTimerFromServiceWorker" });
});
manifest.json
{
"manifest_version": 3,
"name": "Timer Experiment",
"description": "Timer Experiment",
"version": "1.0",
"action": {
"default_popup": "index.html"
},
"permissions": ["activeTab"],
"content_scripts": [
{
"js": ["content.js"],
"matches": ["file:///C:/timer-experiment/samplePage.html"]
}
],
"background": {
"service_worker": "service-worker.js"
}
}
検証1:setTimeout, setIntervalの間隔が1秒のとき
こちらはコンテンツスクリプト・拡張機能service worker共に動作に問題ありません。拡張機能service workerが無効になることはありません。5分ほど回しましたが、両者共にタイマーが止まることはありませんでした。
検証2:setTimeout, setIntervalの間隔が31秒のとき
コンテンツスクリプトの方はタイマーが動き続けますが、拡張機能service workerの方はタイマーは増えませんでした。拡張service workerの動作状況も「Service Worker (無効)」となったいたので、終了条件の「操作が行われない状態で 30 秒経過した後。このタイマーは、イベントを受信するか拡張機能 API を呼び出すとリセットされます。」を満たして停止したのだと思われます。30秒以上の間隔で実行する何かを拡張機能service workerで動かす場合は、chrome APIのalarmsを使うべきです。
おわりに
setTimeout・setIntervalはManifest V3になっても完全に使えないわけではないです。拡張機能service workerでsetTimeout・setIntervalに大きな数字をセットして動かしたい場合、chrome APIのalarmsを代わりに使いましょう。
また、今回作成したchrome拡張は例外処理や停止処理を実装してないので、拡張機能service workerが動き続けちゃいます。chromeから忘れずに消すようにしてください。
コメント