Chrome拡張機能を勉強してみたメモ

# はじめに
SE8年目、準管理職のマルチタスクが常態化してきて＆突発タスクが多すぎて、自分の工数管理がおざなりになってきたので拡張機能を作ってみようと思い立ちました。
HTML/CSS/jsで作れるので弊社では新人研修にもよく題材にされてますが、自分でやってみたことはなく。一連の流れを私も頭に入れときたいな～～～～と長らく思っていたところに、ちょうどいいネタが降ってきたのでまずはお勉強タイムです。

例によって投稿物はあくまでもポンコツ記憶力を補助する備忘録です。

# 前提
- 調査内容は2023年5月20日時点のものです。
- Chrome拡張機能のmanifest versionは `3` を利用します。

# Chrome拡張機能の概要
## アーキテクチャ
https://developer.chrome.com/docs/extensions/mv3/architecture-overview/

content-script補足）https://developer.chrome.com/docs/extensions/mv3/content_scripts/
popup補足）https://developer.chrome.com/docs/extensions/mv3/user_interface/#popup
option補足）https://developer.chrome.com/docs/extensions/mv3/options/

```
./
　├ manifest.json // （必須）拡張機能の設定を記載する。
　　　　　　　　　　// 拡張機能の名称や利用する各種scripts,htmlファイルなどのパス、
                  // ChromeAPIキーなどを記載する。
　├ service-worker.js // （任意）Chrome拡張機能用のイベントハンドラーを担う「拡張」サービスワーカー。
　　　　　　　　　　// Chromeの操作に伴うイベントに対するアクションを実装することができる。
　├ scripts/*.js 
　　　　　　　　　　// （任意）Chrome拡張機能の中心となるスクリプトファイル。
　　　　　　　　　　// manifest.jsonにcontent-scriptとしてパスの記載が必要。
　　　　　　　　　　// ホストページ（拡張機能表示元Webページ）へJavaScriptコードの挿入をおこなう。
　　　　　　　　　　// ホストページのDOMツリーへのアクセスや一部のChromeAPI利用が可能。
　　　　　　　　　　// JavaScriptはホストページ自体のJavaScriptや他の拡張機能とは疎として動作する。
　　　　　　　　　　// ホストページ自体に追加の機能を実装する場合はこちらを使う。
　├ popup/popup.css, popup.js, popup.html
　　　　　　　　　　// （任意）拡張機能アイコンを押下したときに表示するポップアップ画面実装ファイル群。
　　　　　　　　　　// インラインJavaScriptは利用できないため、外出しする必要がある。
　├ options/options.css, options.js, options.html
　　　　　　　　　　// （任意）拡張機能をユーザーがカスタマイズするためのオプション画面実装ファイル群。
　　　　　　　　　　// 各種設定値はChromeのストレージに保存され、デバイス間での同期が可能。
　　　　　　　　　　// manifest.jsonにてstrage権限を付与する必要がある。
　└ icons/16.png, 32.png, 48.png, 128.png
　　　　　　　　　　// （任意）Chromeウェブストアで公開するために必要なアイコンファイル。
　　　　　　　　　　// サイズ別のリソースを含める必要がある。
```

## manufest.json
`contents_scripts`を利用する構成例の公式サンプルから例示。現在のmanifest_versionは`3`であることに注意。

https://developer.chrome.com/docs/extensions/mv3/manifest/

```json
{
  "manifest_version": 3,
  "name": "Reading Time",
  "description": "Add the reading time to Chrome Extension documentation articles",
  "version": "1.0",
  "icons": {
    "16": "images/icon-16.png",
    "32": "images/icon-32.png",
    "48": "images/icon-48.png",
    "128": "images/icon-128.png"
  },
  "content_scripts": [
    {
      "js": [
        "scripts/content.js"
      ],
      "matches": [
        "https://developer.chrome.com/docs/extensions/*",
        "https://developer.chrome.com/docs/webstore/*"
      ]
    }
  ]
}
```

`content_scripts`内の定義項目の内、今回必要な項目のみ抜粋して内容を説明する。`service-worker.js`内やcontents_scripts.js内で動的に設定することも可能。自由度…
公式ページ）https://developer.chrome.com/docs/extensions/mv3/content_scripts/

| 名称 | タイプ | 説明 |
|:-:|:-:|:-|
| matches | Array<string> | （必須）スクリプトを挿入するホストページURLパターン。[詳細](https://developer.chrome.com/docs/extensions/mv3/match_patterns/) |
| css | Array<string> | （任意）利用するCSSファイルリスト。ホストページのDOMが構築される前に、配列の順で挿入される。 |
| js | Array<string> | （任意）利用するJSファイルリスト。ルートディレクトリからの相対パスで記載する。 |
| runAt | string | （任意）`content_script`の挿入タイミングを指定する。デフォルトは`document_idle`で、特に必要がなければこれを利用する。`document_start`はCSS読み込み後・DOM構築、スクリプト実行前。`document_end`はDOM構築後・画像などのサブリソース読み込み前。 |


## Chrome.Strage
拡張機能に関するデータの保存は、ChromeのStrage APIを介しておこなう。
ユーザーのキャッシュクリアなどとは連動せず、シークレットモード利用時も保持される。

https://developer.chrome.com/docs/extensions/reference/storage/

| 保管場所 | 概要 | 容量制限 | データ削除契機 | 暗号化有無 |
|:-:|:-|:-:|:-:|:-:|
| strage.local  | ローカルストレージに保存する。`unlimitedStorage`許可を得ることで大量なデータを保存するためにも利用可能。  |  5MB（無制限可能） | 拡張機能削除時に削除 | なし |
| strage.sync  |  ユーザーがログインしているすべてのChromeブラウザに同期される保存領域。同期が無効、またはオフライン時はstrage.localに保存され、オンラインに戻ると同期を再開する。 | 100KB（項目ごと8KB）  | 拡張機能削除時に削除 | なし |
| strage.session  | 同一session利用中のみデータを保持する。基本的にコンテンツスクリプトに公開されない。機密データや実行中のグローバル変数保存のために利用する。 | 10MB  | sessionが切れたとき | あり |

StrageAPIの利用には、manifest.json内で権限取得が必要。
```json
{
  "name": "My extension",
  ...
  "permissions": [
    "storage"
  ],
  ...
}
```

ここではstrage.syncでの実装サンプルをコピる。
※key,valueで値が入る。1つのkeyごとに8KBということかなー
```javascript
chrome.storage.sync.set({ key: value }).then(() => {
  console.log("Value is set to " + value);
});

chrome.storage.sync.get(["key"]).then((result) => {
  console.log("Value currently is " + result.key);
});
```

ストレージへの変更をイベントハンドラーで取得してアクションを取ることも可能。

# 開発準備
1. [拡張機能](chrome://extensions/)ページを開く
1. 右上の「デベロッパーモード」を活性にする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275513/c3bdb7d7-c6ea-a5c1-ae47-5c59514286a0.png)
1. 「パッケージ化されていない拡張機能を読み込む」を押下し、開発したリソースを読み込む。エラーが発生する場合は「再読み込み」にて確認可能。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275513/271134d5-3850-251b-958e-a51286435802.png)
1. 読み込んだ後は右下の更新ボタンで再読み込みをすることが出来るので、都度動作確認しながら開発が可能。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275513/2100cdca-77dc-6454-cc95-8a96d9870ed6.png)
1. 今回は拡張機能のボタンを押下したら豆腐（白い箱）が出るぜ、までしか作っていないのでここまで。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275513/3e966974-845a-25d9-aa03-664c9320162b.png)

manifest.jsonはこんな感じ。matchesを`<all_urls>`にしているため、すべてのページで「何」を押下したらポップアップが出ます。
```json
{
    "name": "何に何時間くん",
    "description": "ラベルごとに時間を計測できます。1つのタイマーがカウントアップする間、他のタイマーは停止しているので、マルチタスクのそれぞれに何時間使ったのかを簡単に把握することが出来ます。",
    "version": "1.0.0",
    "manifest_version": 3,
    "action": {
        "default_popup": "index.html"
    },
    "content_scripts": [
        {
        "js": [
            "scripts/content.js"
        ],
        "css": [],
        "matches": ["<all_urls>"]
        }
    ]
}
```

# 何を作るのか
以下思いつきラフ図。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275513/8d10d2f0-460b-8b67-8c75-7d39750706b7.png)
※URL見ながら、「あ、QiitaもS3使ってんだ～」しました。AWS様いつもお世話になっております。

こんなの出来たらいいな～～～の書き出しまでした。深夜テンションしか時間がないぞ……

### 機能概要
- タイマーリスト画面
    - hh:mmフォーマットでカウントアップするタイマーを表示する。（1分未満は表示上切り捨て）
    - タイマーは複数定義可能であり、画面右上からリスト表示する。
    - 同じタイミングではひとつのタイマーのみがカウントアップ状態になるものとし、その他は停止状態とする。
        - カウントアップ中タイマーはミントグリーンの背景色を利用する。タイマーは「▶ hh:mm」のフォーマットで表示する。
        - カウントアップ停止中タイマーはライトグレーの背景色を利用する。タイマーは「■ hh:mm」のフォーマットで表示する。
        - タイマーがひとつのみの場合は停止不可とする。
        - タイマーを押下することで、該当のタイマーのカウントアップを再開する。
    - リスト下部に、合計時間およびリセットボタンを表示する。リセットボタンを押下することで、すべてのタイマーのカウントを00:00にリセットする。
- オプション画面
    - 定義したタイマーの一覧（表示順・表示名・非表示フラグ）を表示する。
    - タイマーの編集（タイマー追加・ラベル/表示順/表示変更・非表示切り替え・タイマー削除）機能を提供する。

### データ構造
| 物理名_大 | 物理名_小 | 論理名 | 型 | 説明 |
| :-- | :-- | :-- | :-- | :-- |
| timerList |  |タイマーリスト | Map |  |
|  | displayOrder |表示順 | int | タイマーのソート順 |
|  | label |表示名 | string | 空文字可能 |
|  | isHidden |非表示フラグ | boolean | デフォルトfalse |
|  | count |カウント | int | 0sから1秒ずつカウントアップする。hh:mm表示で1分未満は切り捨て表示。 |
|  | isStop |停止フラグ | boolean | デフォルトtrue。他のタイマーが動作中は停止する。|


また開発中にメモりたくなったら増えます。