Chrome拡張機能(Manifest V3)の開発で知ったこと

皆さん、Chrome拡張機能をご存知ですか? Chrome拡張機能は、Chromeブラウザをカスタマイズするための機能です。

私は、Chrome拡張機能を過去(数年前)に2つ作っていて、その当時は、Chrome拡張機能の仕様であるManifest V2に従っていました。 そして、今再び、Chrome拡張機能で作りたいものができたので、久々に作ろうと決意しました。 作ろうと思ったものの、どうやら今のChrome拡張機能の仕様はManifest V3を推奨しているようです。 そこで、今回、開発した際に知ったことをまとめようと思います。

ちなみに、実際に作ったものは次のものです。

Chrome拡張機能の概要について詳しく知りたい方は、What are extensions? - Chrome Developersをご覧ください。

Chrome Extensions Components

Chrome拡張機能は、主に次の4つのコンポーネントが存在します。

  • Background Scripts
    • サービスワーカー上で動作し、ブラウザ上のイベント駆動(ページ遷移やブックマーク差所など)で反応します。
    • manifestbackgroundフィールドで設定します。
  • Content Scripts
    • Webページのコンテキスト上で動作し、DOMへアクセスできます。
    • manifestcontent_scriptsフィールドで設定します。
  • UI Elements
    • URLバーの右側にあるボタンを押した(Action)際に表示されるUIです。
    • ブラウザ体験を損なわさない最低限の機能だけの提供を推奨されています。
    • manifestactionフィールドで設定します。
  • Options Page
    • Chrome拡張機能アイコンを右クリックして、オプションを選択すると表示されるUIです。
    • Chrome拡張機能をカスタマイズしたい設定ページに使います。
    • manifestoptions_pageフィールドで設定します。

私なりに、これらのコンポーネントの使い分けを考えると、次になります。

  • DOMへアクセスする必要がある
    • Content Scripts を使う
  • ページに依存しない処理がある
    • Background Scripts を使う
  • 環境変数の設定が必要
    • Option Page

UI Elementsは、基本的に必要ないのかなと思いました。

Debug

デバッグって、どうやるんでしょうか。

こちらにやり方が書いてありました。 私なりに解釈した結果、次の2つで使い分けるのかなと思います。


manifest.jsonファイルに記述で誤りがあるなどで、Chrome拡張機能がロードできない場面があります。 そういうときは、次の手順を実行します。

  1. chrome://extensions へアクセス
  2. 次の図にあるようなERRORボタンをクリック

chrome extensions debug

恐らく、何かしらエラーメッセージが出力されていると思います。 それを解決しましょう。


  • ② ①以外の場合

Chrome拡張機能はロードできるが、期待通りに動作しない場面があると思います。 そういうときは、DevToolsを開きましょう。

  • Background Scripts の場合
    • chrome://extensions へアクセスし、inspect viewsの右にあるリンクをクリック。(上図)
      • DevToolsが開きます。
  • Content Scripts, UI Elements, Options Page の場合
    • UI上で右クリックして Inspect をクリック
      • DevToolsが開きます。

DevToolsには、consoleタブがあるはずです。そこのログメッセージを確認しましょう。

Message Passing

コンポーネント間で、通信するのは、どうしたら良いのでしょうか。 例えば、Content ScriptsからBackground Scriptsへデータを渡したいときなどです。 次の資料が、参考になります。

資料を読むと、次のようなパターンの通信ができるようです。

通信の具体的なコードは、chrome.runtime.sendMessageメソッドを使います。 Background ScriptsからContent Scriptsへ通信する場合、どのChromeタブに送信するかchrome.tabs.queryで事前にidを見つけておく必要があります。

また、後で紹介しますが、Web Accessible Resourcesでアクセス可能なJavascriptをWebページへInject(document.querySelector('body').append())した場合、そのJavascriptとContent Scriptsの通信は、window.postMessagewindow.addEventListenerを使いましょう。 chrome.runtimeが使えないので。

Web Accessible Resources

Content ScriptsからWebページのDOMへアクセスできますが、windowオブジェクトにある変数へアクセスすることができません。

windowオブジェクトへアクセスするには、Web Accessible Resourcesを使う方法があります。


具体的にコードで説明しましょう。

manifest.jsonで必要なフィールドの例は、次のとおりです。

{
  "manifest_version": 3,
  "content_scripts": [
    {
      "js": [
        "content-script.js"
      ],
      "matches": [
        "https://*/*"
      ]
    }
  ],
  "web_accessible_resources": [
    {
      "resources": [
        "web_accessible_resources.js"
      ],
      "matches": [
        "https://*/*"
      ]
    }
  ]
}

Content ScriptsとWeb Accessible ResourcesのJavascriptは次のとおりです。

// content-script.js
const injectScript = (filePath, tag) => {
    var node = document.getElementsByTagName(tag)[0];
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    script.setAttribute('src', filePath);
    node.appendChild(script);
}
injectScript(chrome.runtime.getURL('web_accessible_resources.js'), 'body');
// web_accessible_resources.js
console.log(window['hoge']);
// Content Scriptsへ通信する場合は、window.postMessageを使います。

このように、web_accessible_resources.jsをWebページのbodyタグへappendします。 そのweb_accessible_resources.jsでは、windowオブジェクトにアクセスすることができます。

chrome.webRequest API

Chromeブラウザでネットワークトラフィックを監視するChrome拡張機能APIがあります。 それが、chrome.webRequestです。

これがあれば、Webページでどういうリクエストが発生しているか分かるようになります。 manifest.jsonのフィールドで、host_permissionsの設定が必要です。


サンプルで、Background Scriptsのコードを紹介します。 まず、manifest.jsonの必要なフィールドを書きます。

{
  "manifest_version": 3,
  "host_permissions": [
    "https://*/*"
  ],
  "background": {
    "service_worker": "background.js"
  }
}

次に、Webページからリクエストが完了(onCompleted)したイベントを監視するコードを書きます。

// background.js
chrome.webRequest.onCompleted.addListener(
  async (details) => {
    console.log(`request url is ${details.url}`);
  },
  {
      urls: [
          "https://*/*"
      ]
  },
  ["responseHeaders"] // responseHeadersをdetailsオブジェクトに含めることができます。
);

このdetailsにはリクエストのURLが含まれています。さらに詳しく知りたい人は、こちらをご確認ください。

最後に

Chrome拡張機能、久々に開発してみると、進化しすぎていてキャッチアップに苦労しました。 私と同じような方の助けになれば、幸いです。