Chrome拡張開発 超概要

社内勉強会用資料です。

## はじめに

Chrome拡張の開発にライブラリやSDKは必要なく、js のみで開発できます。
フォルダ構成も自由で、配置した js を [manifest.json](#manifestjson) で定義して使用します。

```:フォルダ構成例
[アプリ名フォルダ]
　├ manifest.json
　├ js
　│　├ content.js
　│　└ background.js
　├ css
　│　└ main.css
　├ html
　│　└ option.html
　└ img
　　├ 16x16.png
　　└ 32x32.png
```

拡張には[いくつか種類](#主な拡張の種類)があり、実行されるタイミングや場所などによって種類が分けられています。
[manifest.json](#manifestjson) で Chrome のどの拡張を開発するのかを定義します。

## manifest.json

公式 : [Manifest File Format](https://developer.chrome.com/apps/manifest)

- 拡張名やバージョン、説明、JSの紐づけや権限設定など、全ての設定はこのファイルで行います
- 複数指定できる項目は上から順に読み込まれるため、依存関係がある場合は注意が必要です

```json:manifest.json設定例
{
  "name": "拡張の名前(拡張設定画面に出る)",
  "description": "拡張の説明(拡張設定画面に出る)",
  "version": "拡張のバージョン",
  "manifest_version": 2,
  "content_scripts": [
    {
      "matches": [ "<all_urls>" ],
      "js": ["js/content_script.js"],
      "css": ["css/main.css"]
    }
  ],
  "background": {
    "scripts": ["js/background.js"]
  },
  "permissions": [
    "tabs",
    "clipboardRead",
    "clipboardWrite",
    "storage",
    "cookies",
  ],
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "options_page": "html/options.html"
}
```

- **manifest_version**
マニフェストのバージョン。
2019/10/24現在、新しく開発する場合は `2` で固定です。

- **content_scripts**
[後述](#content-scripts)する `Content Scripts` を定義します。
複数定義可。
    - **matches**  
`Content Scripts` を挿入するページのURLを正規表現で指定出来ます。  
余計なページで実行すると不具合が起きたり遅くなったりするので、最低限にしておくのがベストです。  
`<all_urls>` で全てのページへ挿入されます。

- **background**
[後述](#background-scripts)する `Background Scripts` を定義します。
1つだけ定義可。( js は分割できます)

- **permissions**
使用する Chrome API の種類をここで定義します。
定義されていないAPIは使えません。
セキュリティ上の観点から、最低限にしておくのがベストです。
必要以上の権限を設定すると審査に通りにくくなるらしい。

## ローカルからインストールする  

開発中にもChrome上で動作させますが、
まだストアには公開されていないためローカルの 拡張 を Chrome へ入れる必要があります。

- アドレスバーに`chrome://extensions/`と入力、Enter。
- 右上の`デベロッパーモード`を ON にする。
- `[アプリ名フォルダ]`を Chrome の画面に D&D → Install !

ちなみにこの方法でインストールすると、Chrome起動時にポップアップが出るようになります。
公式に消す方法は存在せず、Chromeのバイナリを書き換えるしか方法はないようです。

参考 : [【Chrome】「デベロッパーモードの拡張機能を無効にする」ポップアップをバイナリ書き換えで消し飛ばす](https://higehito.qrunch.io/entries/wEALQJrQKZT5f8Td)

## 公式ドキュメント

### [Develop Extensions](https://developer.chrome.com/extensions/devguide)  

チュートリアルや API リファレンス、サンプルなど豊富に揃ってます。

## 主な拡張の種類

- Content Scripts ★
- Background Scripts ★
- Page Action / Browser Action
- Override Pages
- Context Menus
- Omnibox
- Option Page

### Content Scripts

公式 : [Content Scripts](https://developer.chrome.com/extensions/content_scripts)

- 表示しているページに挿入される js  
- タブを開いたりリロードした際に読み込まれます  
- マウス操作やキーボード操作、タブの操作等をイベントとして拾えます  
- 表示中のDOMも操作できます  
- 外部通信は出来ません(CORSが設定されていれば出来る、たぶん)
- **使用できるAPIが制限されています**

### Background Scripts

公式 : [Manage Events with Background Scripts](https://developer.chrome.com/extensions/background_pages)

- Chromeの裏で動く js
- ブラウザ起動時 or 拡張有効化時に1回だけ読み込まれます
- 重い処理や通信系などはこちらで行います
- 直接DOMの操作は取得出来ないのでタブを指定してスクリプトを送り込む等の工夫が必要です
- 外部通信が出来ます
- **ほぼ全てのAPIを使えます**

### Page Action / Browser Action

公式 : [Page Actions](https://developer.chrome.com/extensions/pageAction) / [Browser Actions](https://developer.chrome.com/extensions/browserAction)

- ブラウザの右上にある拡張のアイコンをクリックしたときに動く js や html です
- 特定のページでのみ有効か全体で有効かの違いです

### Override Pages

公式 : [Override Pages](https://developer.chrome.com/extensions/override)

- 新規タブ作成画面、ブックマークマネージャ画面、履歴画面のいずれかを差し替えることが出来ます

### Context Menus

公式 : [Context Menus](https://developer.chrome.com/extensions/contextMenus)

- 右クリック時のメニューを拡張できます

### Omnibox

公式 : [Omnibox](https://developer.chrome.com/extensions/omnibox)

- 検索バーでの操作を拡張できます

### Option Page

公式 : [Options Pages](https://developer.chrome.com/extensions/options)

- 設定ページです
- 右上のアイコンから飛べます
- 設定は基本的にローカルストレージに保存して利用します

## Content Scripts と Background Scripts での通信（Message Passing）

DOMの操作やイベントの検知は `Content Scripts` で行いますが、APIが制限されているので細かいことが出来ない場合が多いです。
その場合は `Background Scripts` へ制御を移して処理を行います。
`Message Passing` という各スクリプトでやり取りが出来る仕組みがあります。

公式 : [Message Passing](https://developer.chrome.com/extensions/messaging)

### content_script.js から background.js へ Message Passing を行う例

処理は 1 → 2 → 3 の順で実行されます。

```javascript:content_script.js
chrome.runtime.sendMessage({

    // 好きなオブジェクトを渡せる ====== 1
    mode: "foo",
    html: document.body.outerHTML // htmlを渡したり

}, response => {

    // background.js からのコールバック(sendResponse) ====== 3
    console.log(response); // bar
});
```

```javascript:background.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {

    // 渡されたオブジェクトが request として入ってくる ====== 2
    let mode = request.mode; // foo
    let restext = request.html; // 表示中ページのhtml

    // 処理分岐書いたり
    switch(mode) {
        case "foo": {
            // 処理
            // ...
            // ...
            sendResponse("bar"); // content_script.js にコールバック
            break;
        }
        default: {
            break;
        }
    }
    return true;
});
```

## よく使うAPI

処理はほぼ全てコールバックで続けていくので地獄にならないよう工夫が必要です。

- `chrome.tabs.create(object createProperties, function callback)`
新しいタブを作成します。
オプションで「どのウィンドウに作るか」「どの位置に作るか」「アクティブにするか」等を指定できます。

- `chrome.tabs.query(object queryInfo, function callback)`
タブの情報を取得します。
オプションで「どのウィンドウから取得するか」「特定のURLを開いているタブ」「アクティブなタブ」等を指定できます。

- `chrome.tabs.executeScript(integer tabId, object details, function callback)`
指定したタブでスクリプトを実行できます。
スクリプトを直接書いたり、ファイルを指定したりもできます。
実行タイミングの指示も可能です。
タブを指定しないといけないので、 chrome.tabs.query と併用することが多いです。

- `StorageArea.get(string or array of string or object keys, function callback)`
`StorageArea.set(object items, function callback)`
ローカルストレージを利用します。
  - `StorageArea`
      - `chrome.storage.local` 拡張ごとのローカルストレージ
      - `chrome.storage.sync` アカウントごとに共通なローカルストレージ

- `chrome.cookies.getAll(object details, function callback)`
全ドメインのクッキーを取得します。
`getAll` ではなく `get` を使えばドメイン指定できます。

- `chrome.runtime.sendMessage(string extensionId, any message, object options, function responseCallback)`
`Content Scripts` から `Background Scripts` へメッセージを送ります。
[上で紹介したもの](#content-scripts-と-background-scripts-での通信message-passing)です。

- `chrome.tabs.sendMessage(integer tabId, any message, object options, function responseCallback)`
`Background Scripts` から `Content Scripts` へメッセージを送ります。
上で紹介した通信の逆方向バージョン。
外部通信、処理を行った結果を Content Scripts へ渡したいときに使います。

## まとめ

`manifest.json`で定義した構造、ファイル名で html / js / css が動けばいいので、TypeScript + babel や webpack 等を使って作ることも出来ます。
ライブラリやAnguler・React等のフレームワークも動きます。
  
**ストアに公開した拡張は利用者からソース見れてしまうので要注意。**
[直接ソースを見られるようにしたサイト](https://crx.dam.io/)もあります。(zipでDL出来るサービス付き)