Chrome拡張機能って案外簡単に作れます

# 拡張機能を自作する意味

きっかけはあるWebアプリを使っていて微妙に使いにくい、または欲しい機能がないことでした
アプリのアドオンや公開されているChrome拡張機能では求める要件を満たせるものがなく、どうにか解決できないものかと模索した結果、Chrome拡張を自作するといった方法を思いつきました
また、幸いにも求める機能は些細なものなのであまり時間をかけなくても自作できそうな見込みもありました
結果として、予想していたよりも容易に自作の拡張機能を導入できたため、ナレッジの整理も兼ねて記事にしてみました
Chrome拡張機能では多くのAPIが提供されており、詳細な使い方や各種仕様については公式ドキュメントのリンクを参考にしてください

> Chrome拡張 公式ドキュメント
https://developer.chrome.com/docs/extensions

:::note warn
本記事では拡張機能を自身で利用する想定でまとめています
作成した拡張機能を公開する際には審査があるためご注意ください

:::

# 拡張機能作成のために必要なもの

作成するためにはいくつか前提が必要です

- **Webブラウザ（Chrome）**
- **JavaScriptの知識（DOM操作やイベントリスナーが理解できる程度）**
- **HTML & CSSの知識**
- **エディタ（VSCode推奨）**

# 拡張機能作成の流れ

1. `manifest.json`を作成する
2. 必要に応じてベースとなるファイルを作成する
3. Chromeに拡張機能を追加する
4. 追加した機能の動作を確認する
5. 必要な機能を実装する

上記は拡張機能の土台を作成しChromeで動作確認後、要件に沿った実装を行う流れとなっているため、少し冗長な手順となっています
手順5の「必要な機能を実装する」の実施にあたっては、Chrome拡張の仕組みについて理解が必要になるため、記事の後半で解説します

# 拡張機能を作成してみよう

以下に従って、実際に拡張機能を作成していきましょう

## 1. `manifest.json`を作成する

お好みの名前で拡張機能用のディレクトリを作成してください
サンプルでは「original-extension」ディレクトリを作成します

```bash
$ mkdir original-extension
```

作成したら作成したディレクトリに移動し、以下の内容で`manifest.json`を作成します

```manifest.json
{
  "manifest_version": 3,
  "name": "Original Extension",
  "version": "1.0",
  "permissions":[],
  "action": {
    "default_popup": "popup.html"
  }
}
```

:::note info
`name`は拡張機能の名称になるため、任意の名前に変えて問題ありません
ここでの`manifest.json`は手っ取り早く土台を作成するため、また簡単に動きが体験できるものを作成するために必要最低限の内容になっていますので、今後の本格的に実装するタイミングで適宜修正していきます
:::

> `manifest.json`のフォーマットに関する公式ドキュメント
https://developer.chrome.com/docs/extensions/reference/manifest

## 2. 必要に応じてベースとなるファイルを作成する

まずは自作の拡張機能としてChromeにインストールするため、
続けて`popup.html`と`popup.js`を作成します

:::note warn
`popup.html`と`popup.js`は拡張機能のアイコンクリック時のポップアップ表示用となるので、作成したい機能に不要であるならば後に削除して問題ありません。
:::

```popup.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Original Extension</title>
  </head>
  <body>
    <button id="btn_run">run</button>
    <script src="popup.js"></script>
  </body>
</html>
```

```popup.js
document
  .getElementById("btn_run")
  .addEventListener("click", async () => {
    alert("クリックされました");
  });
```

これでベースの作成は完了です

## 3. Chromeに拡張機能を追加する

新しいタブでchrome://extensions/ にアクセスして右上のデベロッパーツールをオンにします
デベロッパーツールを有効化したら「パッケージ化されていない拡張機能を読み込む」から、作成したディレクトリ（サンプルではoriginal-extension）を選択します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189530/3f8c5b60-7274-72c1-7f02-9e5dc969dbe0.png)

無事読み込むことができれば以下の様に拡張機能一覧に自作の拡張機能が表示されます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189530/a77c09c9-650a-3733-4b81-aa88c072259c.png)

## 4. 追加した機能の動作を確認する

1. 拡張機能アイコンをクリックして表示される一覧に自作の拡張機能が表示されているはずなのでピン留めアイコンをクリックしておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189530/580f55de-93d7-905a-340e-ba8f6642d77e.png)

2. 拡張機能アイコンの隣に表示されたアイコンをクリックします（画像では「O」と表示）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189530/28446c0c-db02-8f10-0ed4-e59f073fcc51.png)

3. `popup.html`の内容が表示されるので「run」ボタンをクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189530/c7df69a4-0cee-3001-84fe-1c0e29ce00a9.png)

4. アラートが表示されたら確認完了です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189530/58d888f1-7491-69b3-4439-b9cf65dafe9c.png)

以上で自作の拡張機能を実装することができました
この拡張機能をベースに実現したい機能を追加していきます

# Chrome拡張機能のポイントを解説

拡張機能のベースを作成できたと思いますので、ここでChrome拡張によってできることを整理したいと思います
Chromeで拡張機能を作成したいと考えたときに、知っておくべき要素として以下の4点がありますので、それぞれ解説します

- [Manifest](#manifest)
- [Content script](#content-script)
- [Toolbar action](#toolbar-action)
- [Service Worker](#service-worker)

先ほどのベース作成ではToolbar actionの仕組みを利用してアラートを表示しました

## Manifest

公式ドキュメントによると以下の様に説明されています

> 拡張機能のマニフェストは、特定のファイル名を持つ必要がある唯一のファイル（`manifest.json`）です。また、拡張機能のルート ディレクトリに配置する必要があります。このマニフェストでは、重要なメタデータの記録、リソースの定義、権限の宣言、バックグラウンドとページで実行するファイルの指定を行います。

このManifestを中心に拡張機能は構成されており、追加したい機能があればこのファイルを修正するのが基本の流れになります
また、アイコンなどの画像ファイルの定義や拡張機能が持つアクセス権限などもManifestが管理しています

ここでは`manifest.json`で頻出の項目を解説します

### `manifest_version`

Chrome拡張機能の仕様バージョンです
最新はv3となるため特に気にせずv3を設定すれば問題ありません
ただし、参考にするドキュメントによってはv2の仕様で記載されている事があるため、どのバージョンでの説明なのか注意する必要があります

### `content_scripts`

```
content_scripts: []
```

特定のWebページに対してスクリプトやCSSを挿入したい場合に利用します（別途説明します）

### `action`

```
action: {}
```

ツールバーに表示されている拡張機能アイコンの見た目やクリック時の動作などを定義します（別途説明します）

### `background`

```
background: {}
```

拡張機能のService Workerを利用する場合に追加します
各種イベントに対して実行したい処理（イベントハンドラ）を定義したJavaScriptファイルを紐づけします

### `options_ui`

```
options_ui: {
  page: 'options/options.html',
  open_in_tab: true,
},
```

拡張機能のオプション画面が必要な場合に追加します
オプション画面は拡張機能のアイコンを右クリックした際のメニューから「オプション」をクリック、または拡張機能の詳細画面から「拡張機能のオプション」をクリックすることで表示可能です
このオプション画面での設定値は基本的に拡張機能の`storage`を利用して保持することになります
そして保持した設定値をService Workerなどで取得して利用します

### `permissions`

```
permissions: ['activeTab', 'scripting', 'storage', 'tabs']
```

作成する拡張機能が利用するAPIによって、拡張機能に権限を明示的に与える必要があります
そのため、`permissions`をManifestに追加して拡張機能に必要な権限をリスト形式で定義します
なお、ユーザーの意思で省略できる権限については`optional_permissions`によって定義可能です

### `host_permissions`

```
 host_permissions: ['<all_urls>']
```

拡張機能によってWebページへの操作が発生する場合に許可されるホストのリストを定義します

### `web_accessible_resources`

```
"web_accessible_resources": [
  {
    "resources": ["sample.png"],
    "matches": ["https://*/*"]
  }
]
```

Webページからアクセスできる拡張機能に含まれるファイルを定義します
例えば表示しているページに拡張機能で用意した画像を埋め込みたい場合は、その画像をアクセス可能なリソースとして定義します

> Manifestの公式ドキュメント
https://developer.chrome.com/docs/extensions/reference/manifest

## Content script

DOMへのアクセスが可能です
表示しているページの要素を取得したり、逆に変更を加えるためにはContent scriptを利用する必要があります
Content scriptではスクリプトを実行するタイミングや特定のURLにマッチする場合のみに限定する事も可能です
注意点として、Content scriptは表示しているページとは独立したコンテキストにて実行されるため、`window`オブジェクトへアクセスすることはできません

### 利用方法

ベースとなる`manifest.json`を以下の様に修正します
```manifest.json
{
  "manifest_version": 3,
  "name": "Original Extension",
  "version": "1.0",
  "permissions":[],
  "content_scripts": [
    {
      "matches": [ "http://*/*", "https://*/*"],
      "js": ["content-script.js"]
    }
  ],
  "action": {
    "default_popup": "popup.html"
  }
}
```

`content_scripts`の項目が追加されています
その子要素である`matches`は必須の項目であり、`js`で指定したスクリプトを動作させる条件を指定します
つまり`matches`の条件に合致する場合は自動でスクリプトが動作することになります
動作タイミングが重要な場合は、`run_at`の項目を追加して動作タイミングを指定する事もできます

:::note info
`manifest.json`に`content_scripts`項目を追加する方法は静的宣言であり、より複雑な条件で動作させたい、動作させるスクリプトを変えたいといった場合は動的宣言（詳細は割愛）によってContent scriptを設定します
:::

> Content scriptの公式ドキュメント
https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts

## Toolbar action

Chromeツールバーにある拡張機能メニュー（パズルピースのアイコン）に表示されるインストール済みの拡張機能のアイコンをアクションアイコンと呼びます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189530/cc3218d8-2f8c-2cc0-ba8f-c25346bdd42f.png)

Toolbar actionではアクションアイコンをクリックしたときにポップアップを表示したり、アイコン上にバッジを表示する事が可能です
個人的にはポップアップを独自機能のメニュー、機能の実行や入力画面など拡張機能の中核となるような使い方をしています
他にもアクションアイコン（またはポップアップ内のボタン）のクリックをトリガとして、先述のContent scriptを通して、表示されているページのDOMへ間接的にアクセスすることが可能です

### 利用方法

Toolbar actionは前半で作成した`manifest.json`で既に利用しており、`action`項目によって設定します
`manifest.json`を以下の様に修正することで、アクションアイコンとして表示するアイコン画像やホバー時のツールチップ表示内容の設定が可能です

```manijest.json
{
  "manifest_version": 3,
  "name": "Original Extension",
  "version": "1.0",
  "permissions":[],
  "action": {
    "default_popup": "popup.html",
    "default_title": "自作の拡張機能です",
    "default_icon": {
      "16": "images/icon_16x16.png",
      "48": "images/icon_48x48.png",
      "28": "images/icon_128x128.png"
    }
  } 
}
```

`default_icon`に設定する画像ファイルは自身で用意し、`manifest.json`と同じディレクトリ配下に格納してください
サンプルでは`manifest.json`と同じディレクトリに`images`ディレクトリを用意し、その中に3種類のサイズでアイコンを格納している状況を想定しています

> Toolbar actionの公式ドキュメント
https://developer.chrome.com/docs/extensions/reference/api/action

## Service Worker

まずWeb APIにおけるService Workerとは別物であることに注意が必要です
ここではChrome拡張機能としてのService Workerを説明します
また、古いChrome拡張機能のバージョンであるManifest V2でのBackground Scriptとも区別する必要があります
[Background Scriptとの違い](https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers)
詳しく説明する場合それだけで１つの記事になるため、ここではどういった場面で利用できるかを記載します
※一部の利用例ですので他にも様々な利用方法があります

### 利用シーン

* 拡張機能がインストールされた場合に独自のページを表示させたい
* 時間のかかる処理をバックグラウンドで実行させたい（ただし最長で5分まで）
* 新しいページへの移動、通知をクリック、タブを閉じるといったブラウザのイベントをトリガとする処理を実行したい

Service Workerは多くの拡張機能APIを利用できるため基本的にDOM操作以外であれば何でもできる印象です
ただし、特定条件で適宜シャットダウンされるため、データは必要に応じてchrome.storage APIなどに保持しておきましょう

### 利用方法

`manifest.json`を以下の様に修正します

```manijest.json
{
  "manifest_version": 3,
  "name": "Original Extension",
  "version": "1.0",
  "permissions":[],
  "background": {
    "service_worker": "service-worker.js"
  },
  "action": {
    "default_popup": "popup.html"
  } 
}
```

`background`項目が追加されています
`service-worker.js`にはイベントハンドラの登録処理などを記述します


> Service Workerの公式ドキュメント
https://developer.chrome.com/docs/extensions/develop/concepts/service-workers

# その他できること

Chrome拡張機能では他にも様々な**できること**があります
いくつかピックアップして紹介します

### テーマの変更

`manifest.json`に`theme`項目を追加するとブラウザのテーマを変更する拡張機能を作成できます

### ブックマーク マネージャー、履歴、新しいページのUI変更

例えばChromeで新しいタブを開くとgoogleの検索フォームが表示されますが、この画面を任意のコンテンツに変更することができます
この機能によって自分好みの使いやすいUIを実現できます

### サイドバー表示

Side Panel APIを利用するとメインのページとは別に拡張機能で用意されたコンテンツをサイドバーとして表示できます
このサイドバーはタブを切り替えても同じ内容が表示されるため、タブを跨いで共通の内容を利用可能です

### タブの管理

chrome.tabs APIを利用するとブラウザで開かれているタブの一覧やアクティブなタブの取得、タブの作成や変更といった操作が可能になります

### ネットワークリクエストの傍受、ブロック、変更

chrome.declarativeNetRequest APIを利用するとネットワークリクエストを監視し、定義したルールに沿ってリダイレクト、ブロック、クエリパラメータの追加など様々な制御を加えることが可能になります

# 次回の内容について

今回の記事だけでまとめきれなかったので2本立てとなりそうです
次回は以下のトピックを扱う予定です

- メッセージ通信の仕組み
- デバッグ方法
- 拡張機能実装時の注意点
- 拡張機能テンプレートの利用
- React & TypeScriptを活用した拡張機能の実装
- Tailwind CSSでお手軽デザイン
- Jestでのテスト自動化

少し時間がかかるかもしれませんが楽しみにしていただければ幸いです