【chrome拡張】setTimeout・setIntervalは使えない?(Manifest V3対応)

chrome拡張

Manifest V3に対応したchrome拡張開発において、setTimeout・setIntervalが意図した動作をせず、ドキュメントを確認したところ、以下の記述がありました。

setTimeout() メソッドや setInterval() メソッドで、
遅延オペレーションや周期オペレーションを使用するのが一般的です。
ただし、Service Worker が終了するとタイマーがキャンセルされるため、
Service Worker でこれらの API が失敗することがあります。

https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers?hl=ja#convert-timers

拡張機能service workerはchromeの裏側で動くプログラムです。常に常駐しているわけではなく、以下の条件を満たすと拡張機能service workerは停止します。

  • 操作が行われない状態で 30 秒経過した後。このタイマーは、イベントを受信するか拡張機能 API を呼び出すとリセットされます。
  • 1 件のリクエスト(イベントや API 呼び出しなど)の処理に 5 分以上かかる場合。
  • fetch() レスポンスが届くまでに 30 秒以上かかる場合。
https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle?hl=ja#idle-shutdown

特に今回の、「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.jsindex.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分ほど回しましたが、両者共にタイマーが止まることはありませんでした。

16倍速

検証2:setTimeout, setIntervalの間隔が31秒のとき

コンテンツスクリプトの方はタイマーが動き続けますが、拡張機能service workerの方はタイマーは増えませんでした。拡張service workerの動作状況も「Service Worker (無効)」となったいたので、終了条件の「操作が行われない状態で 30 秒経過した後。このタイマーは、イベントを受信するか拡張機能 API を呼び出すとリセットされます。」を満たして停止したのだと思われます。30秒以上の間隔で実行する何かを拡張機能service workerで動かす場合は、chrome APIのalarmsを使うべきです。

8倍速

おわりに

setTimeout・setIntervalはManifest V3になっても完全に使えないわけではないです。拡張機能service workerでsetTimeout・setIntervalに大きな数字をセットして動かしたい場合、chrome APIのalarmsを代わりに使いましょう。

また、今回作成したchrome拡張は例外処理停止処理実装してないので、拡張機能service workerが動き続けちゃいます。chromeから忘れずに消すようにしてください。

コメント

タイトルとURLをコピーしました