hogashi.*

日記から何から

Chrome拡張機能をManifestV3に上げる活動

 規模の小さい chrome-usercss-hogashi - Chrome ウェブストア から始めてみている。

 Manifest V3 migration checklist - Chrome Developers を見ると結構色々ある。いっこずつ対応していったらよいのだけど、難関だったのは background pages の Service Worker 化。

 色々 Google で検索しまくった結果、 localStorage を使っていたので Service Worker として invalid だったっぽい。のだけどエラーメッセージに何も現れてこなくてめちゃくちゃむずかった。

 Service Worker で使える localStorage の代替を調べると、 chrome.storage というのがある。これは Chrome 拡張機能の機能として存在していて、 permission に storage とか足すと使える。

developer.chrome.com

 データとしては key/value なのだけど、呼び出しが非同期になっている(あと callback 形式)ので、単純に置き換えはできなくて、ちまちま Promise に包んで使うように変えたりした。

 これでいいかと思ったらもう一回ハマって、 background pages からの sendMessage への応答 (sendResponse) が非同期だとおかしくなることがわかった。具体的には Unchecked runtime.lastError: The message port closed before a response was received みたいなエラーが出ていて、 sendResponse で返したはずの値が返っていない。

stackoverflow.com

 この stackoverflow の回答はめちゃくちゃおもてなし度があって、 Chrome 拡張機能を使っている側としてはそのエラーを出してる拡張機能を消したら解決するけど、開発側としてはそのエラー自体への対処を知りたいよね、そういう目的で見に来た人に向けて書くね、みたいな内容で助かった。

 それっぽいコードを書くと、 Promise が返る関数を呼んだとして、その then で sendResponse するような書き方にしたときにこうなる。

chrome.runtime.onMessage.addListener(() => {
  myAsyncFunction().then(result => {
    sendResponse(result);
  });
});

 MDN を見ると、非同期に返す方法は 2通りあると書かれている。

To send an asynchronous response, there are two options:

  • return true from the event listener. This keeps the sendResponse() function valid after the listener returns, so you can call it later. See an example.
  • return a Promise from the event listener, and resolve when you have the response (or reject it in case of an error). See an example.
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage

 イベントリスナから Promise を返すほうでやってみたけど、全然うまくいかなかった (なんかミスっているかもしれない……)。ここで Chrome 拡張機能API ドキュメントを見に行くと、非同期ならイベントリスナからは true を返してくれと書かれていた。実際 true を返すほうでやって解決した。 Chrome 拡張機能のほうは Promise を返すほうに対応してないのかも?

This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until sendResponse is called).

https://developer.chrome.com/docs/extensions/reference/runtime/#event-onMessage

 のでこういう感じでできた。

  chrome.runtime.onMessage.addListener(() => {
    myAsyncFunction().then(result => {
      sendResponse(result);
    });
+   return true;
  });

 chrome.storage に切り替えるけど、 localStorage のほうで育ててきた UserCSS が消えるともの悲しいので、ちゃんと localStorage からのデータ移行もやることにしている。 onInstalled で自動で移行するので基本的には意識しなくていいようにしたけど、何かの拍子におかしくなったとき用に、 localStorage からのエクスポートも別に用意した。ということは、移行中は (localStorage 使うので) Manifest V3 にはできないので、もうしばらく V2 のままでいる必要がある。

f:id:hogashi:20210801184132p:plain

 これでみなさまがデータ移行できたら Manifest V3 に上げてよい、となるのだけど、できたよ〜みたいなデータを送信したりしないことにしているので、どのくらい待つのがいいのかむずい (普通に使っていたら数ヶ月くらいで十分な気もする)。