既存の自作scriptを簡単にChrome拡張機能にするためのテンプレートを作った

Chrome拡張機能を初めて作ったので作り方の備忘録です。
きっかけは、コーディングしたWebページをデバッグする際に使っていた自作のスクリプト。これまではHTMLファイルにscriptタグを挿入して実行していましたが、ブラウザのボタンから実行できた方が便利です。そこで、既存のスクリプトからコピペで簡単に「Chrome拡張機能化」できるようなテンプレートを作っておくことにしました。この記事では、その自作テンプレート[cxt-template](https://github.com/kazhashimoto/cxt-template)について書きます。対応するプラットフォームは、[Manifest V3 for Chrome Extensions (MV3)](https://developer.chrome.com/docs/extensions/mv3/intro/)です。

ソース: https://github.com/kazhashimoto/cxt-template

cxt-templateはそれ自体がChrome拡張として動作し、提供する機能とUIは単純です。

- 表示中のページのコンテンツに「ユーザー定義のスクリプト」のコードをinjectして実行する。※スタブとして記述されているscriptは、console.logに１行出力するだけのものです。
- 表示中のページのコンテンツに「ユーザー定義のCSS」をinjectする
- オプション項目の設定値の保存と読み込み
- UI: オプションページ（サンプル）
- UI: Chromeツールバーに表示される拡張アイコンのトグル（ON/OFF表示）

この拡張機能に対するボタンのアイコンイメージは提供しません。ツールバーには、Chromeがデフォルトで設定した"cxt-template"の頭文字"C"が表示されるだけです。

## インストール
適当なディレクトリにリポジトリをcloneします。

```shell-session
$ git clone https://github.com/kazhashimoto/cxt-template.git
```

Chromeを起動して、以下の手順でcxt-templateを拡張機能として読み込みます。

1. `chrome://extensions/`にアクセスし、拡張機能の管理ページを開きます。（画面の詳細は[こちら](https://developer.chrome.com/docs/extensions/mv3/getstarted/)）
1. 「デベロッパーモード」をONにします。
1. 「パッケージ化されていない拡張機能を読み込む」をクリックします。
1. ファイル読み込みのダイアログで、ローカルに展開したリポジトリの`./cxt-template/extension`ディレクトリを選択します。

適当なWebサイトを開いてDevToolsを起動し、cxt-templateのボタンをクリックすると、スタブのscriptが実行されたことを示すメッセージがconsole.logに出力されます。右側のObjectの内容はoptionsの初期値です。

```
### start process ### ▶︎Object
```

## ファイル構成
テンプレートのファイル一式は、extensionフォルダにあります。ファイル構成は下表のとおりです。

| ファイル名 | 説明 |
| --- | --- |
| background.js | Chrome拡張のservice workerに使われる[backgroundスクリプト](https://developer.chrome.com/docs/extensions/mv3/service_workers/)です。|
| content.css | Chrome拡張からターゲットのWebページのコンテンツに挿入される「ユーザー定義のスタイルシート」です。 |
| content.js | Chrome拡張からターゲットのWebページのコンテンツに挿入される「ユーザー定義のscript」です。 |
| manifest.json | Chrome拡張の[manifest](https://developer.chrome.com/docs/extensions/mv3/manifest/)ファイルです。 |
| options.css | オプションページのスタイルシートです。 |
| options.html | Chrome拡張の[オプションページ](https://developer.chrome.com/docs/extensions/mv3/options/)のHTMLファイルです。 |
| options.js | オプションページのロジックを実装するJavaScriptファイルです。 |

## Permissions
manifest.jsonで指定しているpermissionは以下の３つです。

```json:manifest.json
"permissions": [
  "activeTab", "scripting", "storage"
],
```

| permission | 目的
| --- | --- |
| [activeTab](https://developer.chrome.com/docs/extensions/mv3/manifest/activeTab/) | ユーザーが拡張アイコンをクリックして呼び出した時、現在アクティブなタブへのアクセス権を一時的に与えるため。 |
| scripting | [chrome.scripting]()APIを使用して、ターゲットのWebページのコンテンツにスクリプトやスタイルシートを挿入するため |
| storage | [chrome.storage](https://developer.chrome.com/docs/extensions/reference/storage/) APIを使用して、optionの設定値を保存・読み込みするため　|

## ユーザー定義のscript
ユーザー定義のscriptはcontent.jsの関数内に記述します。
content.jsは以下の構造になっています。機能の組み込みにコーディングが必要な箇所は、コメントに示した[1],[2],[3]の部分です。

```javascript:content.js
(function(classname, init_options, process) {
  // ....
})('_example',   // [1] <body>に追加するclass名
function(options) {
  // [2] オプションの初期値を設定するコードをここに書く
},
function(options, active) {
  // [3] 機能を実装するコードの本体をここに書く
  if (!active) {
    // アイコンの状態がOFFになって呼ばれた時
    // OFFの時の処理をここに書く
    return;
  }
  // 以下、アイコンの状態がONの時のコード
});
```

class名[1]は次のようにbody要素に設定されます。
表示中のタブについて、ユーザーがツールバーのcxt-templateアイコンを最初にクリックした時、もしくは状態がOFF表示のアイコンをクリックした時：

```html:html
<body class="_example _example-active">
```
状態がON表示のアイコンをクリックした時：

```html:html
<body class="_example">
```

### オプションページ
options.htmlは、サンプルとして２つのcheckboxと２つのカラーピッカーを含んでいます。
ユーザーが「保存」ボタンをクリックすると、以下のプロパティを持つオブジェクトの設定値がChromeのstorageに書き込まれます。

```:optionsオブジェクトの例
{
  items: {item1: true, item2: true},
  colors: {color1: #ff0000, color2: #0000ff}
}
```

ツールバーのcxt-templateアイコンがクリックされると、storageからオプションの設定値が読み込まれ、上記[3]に示したユーザー定義scriptを実行する関数の１番目のパラメーターにセットされます。

```javascript:javascript
function(options, active)
```

storageに保存されたオプションがない場合、[3]のscriptに渡されるoptionにはプリセット値（上記[2]で初期化した値）が入っています。プリセット値かどうかは、プロパティ`preset`の値(true/false)で確認できます。

```javascript:javascript
  if (options.preset) { // optionsには初期値が入っている
    ...
  } else { // optionsにはstorageから読み込まれた値が入っている
    ...
  }
```

### 実施例
cxt-templateを使ってサクッと書いたChrome拡張のサンプル[img-marker](https://github.com/kazhashimoto/img-marker)です。img-markerは、カレントのタブに表示されたページのコンテンツに含まれるimg要素、および背景画像が設定された要素について枠線を表示します。

ソース: https://github.com/kazhashimoto/img-marker