Chrome拡張機能の開発 その1-1：概要編

# この記事について
この記事はChrome拡張機能の[公式ページ](https://developer.chrome.com/extensions/overview)を翻訳、補足を追記した連載記事である。ひとつ前の記事は以下を参照。

Chrome拡張機能の開発 その1：拡張機能の基礎
https://qiita.com/gtracker64/items/95bec2b584638cc03388

# 基礎
* 拡張機能は、Google Chromeブラウザに機能を追加するHTML、CSS、JavaScript、画像、その他必要に応じて追加したファイルをzip形式でバンドルしたもの。
* 拡張機能の本質はWebページであり、通常のWebページと同様にブラウザが提供するすべてのAPIやJSON、XMLHttpRequest、HTML5を利用することができる。
* 拡張機能は**"ContentsScript"**や"Cross-Origin-XMLHttpRequest"を利用して、Webページやサーバとデータをやりとりできる。また、ブックマークやタブなどのブラウザ機能に対してもアクセスできる。

## 拡張機能のUI

* 拡張機能は**"browser action"または"page
 action"**のいずれかひとつを持っており、Google ChromeにUIを追加する。
<dl>
<dt>browser action</dt>
<dd>拡張機能がひとつのページに依存せずほぼ全てのページに関連する場合</dd>
<dt>page action</dt>
<dd>拡張機能をページに応じてアクティブまたは非アクティブ（アイコンはグレー表示)を切り替える時</dd>
</dl>
* 拡張機能（Chrome Apps）では、Chromeのコンテキストメニューへの追加、オプションページの提供、ページの表示方法を変更するコンテンツスクリプトの使用など、他の方法でUIを表示することもできる。拡張機能の完全なリストについては、[「開発者用ガイド」](https://developer.chrome.com/extensions/devguide)
を参照し、それぞれの実装の詳細を参照すること。

## ファイル
拡張機能は次のファイルで構成される。

* マニフェストファイル（manifest.json）
* ひとつ、または複数のHTMLファイル（ただし、拡張機能が**テーマ**の場合を除く)
* （オプション）ひとつ、または複数のJavaScriptファイル
* （オプション）その他、必要なファイル（例えば画像など）

これらのファイルは全てひとつのフォルダに格納される。また、拡張機能を配布する時は、これらのファイルは`.crx`という拡張子を持つ特別なZIPファイルにパッケージ化される。

[Chromeデベロッパーダッシュボード](https://accounts.google.com/ServiceLogin?service=chromewebstore&continue=https://chrome.google.com/webstore/developer/dashboard&passive=3600&hl=ja&authuser=0)を使用して拡張機能をアップロードすると、`.crx`ファイルが作成される。拡張機能の配布の詳細については、[「ホスティング」](https://developer.chrome.com/extensions/hosting)を参照。

## ファイルの参照
拡張機能には必要に応じて自由にファイルを追加することはできるが、どのように使用すれば良いのだろうか？通常、普通のHTMLページと同様に、相対URLを使用してファイルを参照できる。以下は、imagesという名前のサブフォルダにあるmyimage.pngというファイルを参照する例を示している。

```
<img src="images/myimage.png">
```

また、拡張機能のすべてのファイルには、次のような絶対URLでアクセスすることも可能。

```
chrome-extension://<extensionID>/<pathToFile>
```

<dl>
<dt>extensionID</dt>
<dd>拡張機能ごとにシステムが自動生成する固有の識別子。
拡張機能のIDは<code>chrome：//extensions</code>にて確認できる。</dd>
<dt>pathToFile</dt>
<dd>拡張機能のトップフォルダの下にあるファイルの場所。相対URLと同じ。</dd>
</dl>

拡張機能を開発中（パッケージ化する前)であれば、`extensionID`を変更することができる。具体的には、別のディレクトリから拡張機能（ディレクトリ）を読み込むと、拡張機能のIDが変わる。また、拡張機能をパッケージ化した時にも、再びIDが変わる。

拡張機能のソースで拡張機能内のファイルへのフルパスを指定する必要がある場合は、[定義済みメッセージ](https://developer.chrome.com/extensions/i18n#overview-predefined)である`@@extension_id`を使用して、ハードコードを回避できる。

拡張機能をパッケージ化すると（通常、ダッシュボードでアップロードすることによって）、拡張機能は永続的なIDを取得する。このIDは拡張機能を更新しても変更されない。永続的な拡張IDを取得した後は`@@extension_id`を実際のIDで置き換える事が可能。
> 逆に後から置き換える必要性があるのだろうか・・?

## マニフェストファイル
マニフェストファイル`manifest.json`には、最も重要なファイルや拡張機能が使用する機能など、拡張機能に関する情報を定義する。google.comの情報を利用する**"browser action"**の典型的なマニフェストファイルは次のとおり。

```json:manifest.json
{
  "name": "My Extension",
  "version": "2.1",
  "description": "Gets information from Google.",
  "icons": { "128": "icon_128.png" },
  "background": {
    "persistent": false,
    "scripts": ["bg.js"]
  },
  "permissions": ["http://*.google.com/", "https://*.google.com/"],
  "browser_action": {
    "default_title": "",
    "default_icon": "icon_19.png",
    "default_popup": "popup.html"
  }
}
```

詳細は[マニフェストファイルの詳細](https://developer.chrome.com/extensions/manifest)を参照。

## アーキテクチャ
* 多くの拡張機能は**"background page"**を持つ。これは拡張機能のメイン処理を持つ画面には表示されないページである。
* 拡張機能は、独自のUIを持つページ**"UI page"**を作る事ができる。
* 拡張機能とユーザーが読み込んだWebページ（拡張機能の外）と対話する必要がある場合は、**"Content Script"**を使用する必要がある。

### background page
* **background page**は`background.html`で定義され、拡張機能の動作を制御するJavaScriptコードを含めることができる。
* **background page**には以下の2つのタイプがある。

<dl>
<dt>persistent background pages</dt>
<dd>名前の通り、常にバックグラウンドで動いているページ。</dd>
<dt>event pages</dt>
<dd>必要に応じて開いたり、閉じたりするページ。常に実行する必要がある場合を除き、こちらの利用を推奨。</dd>
</dl>

詳細は[Event Pages](https://developer.chrome.com/extensions/event_pages)、または[Background Pages](https://developer.chrome.com/extensions/background_pages)を参照。

### UI Page
* 拡張機能には、拡張機能のUIを表示する通常のHTMLページを含めることができる。
* 例えば、ブラウザアクションにはポップアップがあり、これはHTMLによって実装される。
* 拡張機能には**"option page"**を作る事ができ、拡張機能の動作方法をカスタマイズ（設定）できる。
* 他の特殊なページには**"override page"**などがある。
* その他、拡張機能は[tabs.create](https://developer.chrome.com/extensions/tabs#method-create) APIまたはwindow.open（）を使用して、拡張機能に含まれる他のHTMLファイルを表示できる。  

以下は拡張機能のコンテキストに関する説明である。

* 拡張機能内のHTMLページは、お互いのDOMへの完全なアクセスを持ち、お互いのページ内に定義された関数を呼び出すことができる。次の図は、ブラウザアクションのポップアップのアーキテクチャを示している。
* ポップアップの内容は、HTMLファイル（popup.html）で定義されたWebページである。
* この拡張モジュールには背景ページ（background.html）もある。
* ポップアップはバックグラウンドページの関数を呼び出すことができるため、ポップアップにバックグラウンドページにある関数をわざわざコピーする必要はない。
  
![arch-2.gif](https://qiita-image-store.s3.amazonaws.com/0/100776/7aad1b76-492e-1566-befd-1a330efd6928.gif)

詳細は[Browser Actions](https://developer.chrome.com/extensions/browserAction)、[Options](https://developer.chrome.com/extensions/options)、[Override Pages](https://developer.chrome.com/extensions/override)、[ページ間の通信](https://developer.chrome.com/extensions/overview#pageComm)を参照。

### Cotent Script
* 拡張機能がユーザが読み込んだWebページとやりとりする必要がある場合、**"content script"**が必要となる。
* **"content script"**は、Webページのコンテキストで実行されるJavaScriptである。
* **"content script"**は、読み込まれたWebページの一部であり、パッケージ化された拡張機能の一部ではないと考えればわかりやすい。
* **"content script"**は、ブラウザが表示するWebページの詳細を読むことができ、ページを変更することもできる。
* 次の図では**"content script"**は、表示されたWebページのDOMの**読み取り・変更ができる**。ただし、拡張機能の**"background page"**のDOMは**変更できない**。



![arch-3.gif](https://qiita-image-store.s3.amazonaws.com/0/100776/6f2746b8-9e79-905b-f996-3b8e9c1a6c62.gif)

* ただし、**"content script"**は、親である拡張機能から完全に切り離されているわけではない。
* 次の図の矢印が示すように、**"content script"**は親の拡張機能とメッセージを交換できる。
* たとえば、**"content script"**は、ブラウザページでRSSフィードを見つけるたびにメッセージを送信することができる。
* または、**"background page"**が**"content script"**にブラウザページの外観（見た目）を変更するように要求するメッセージを送信することもできる。

![arch-cs.gif](https://qiita-image-store.s3.amazonaws.com/0/100776/ec8ec173-1119-2725-2a37-1b91048f370f.gif)

詳細は[Content Scripts](https://developer.chrome.com/extensions/content_scripts)を参照。

## chrome.* APIの利用
* 拡張機能は、Webページやアプリケーションで使用できるすべてのAPI（多くの場合はJavaScript）にアクセスできるだけでなく、ブラウザ自体の機能との統合を可能にするChrome専用のAPI（chrome。* APIとも呼ばれる）を使用することができる。
* たとえば、拡張機能はWebアプリケーションで標準のwindow.open（）メソッドを使用してURLを開くことができることが知られている。
* しかし、どのURLをどのウィンドウに表示するかを指定する場合は、拡張機能ではChrome専用の[tabs.create](https://developer.chrome.com/extensions/tabs#method-create)メソッドを使用できる。

### 非同期vs同期メソッド

* chrome.* APIのほとんどのメソッドは非同期であり、処理が完了するのを待つことなく、すぐに応答が戻る。
* そのため、処理の結果を知る必要がある場合（結果に応じて別の処理を行う場合)は、メソッドの引数にコールバック関数を渡す必要がある。
* そのコールバック関数は、メソッドの引数として渡した時点ではなく、処理が完了した後で実行される。次に非同期メソッドの例を示す。

```javascript:非同期メソッドの例
chrome.tabs.create(object createProperties, function callback)
```

* 他のchrome.*は同期メソッドである。同期メソッドは、すべての処理を完了するまで応答が戻らないため、コールバック関数を渡す必要はない。
* 多くの場合、同期メソッドには戻り値（とその型）がある。ここでは`runtime.getURL`メソッドを例に考える。

```javascript:同期メソッドの例
string chrome.runtime.getURL()
```
このメソッドは、`string`型のURLを同期的に返すメソッドで、コールバック関数を引数として渡す必要はない。

> 非同期メソッドとコールバック関数についてはWebアプリ(JavaScript)開発者なら良く知っているはずなので、これらの説明は蛇足かもしれない。

### コールバック関数の使用例

ここでは、ユーザーが現在表示しているタブを新しいURLに遷移させるものとする。
これを行うためには、現在のタブのIDを取得([tabs.query](https://developer.chrome.com/extensions/tabs#method-query)）し、そのタブを新しいURLに遷移([tabs.update](https://developer.chrome.com/extensions/tabs#method-update)）させる必要がある。

`query（）`が同期メソッドの場合は、次のようなコードを書くことができる。

```javascript:非同期関数の誤った使い方
//THIS CODE DOESN'T WORK
var tab = chrome.tabs.query({'active': true}); //WRONG!!!
chrome.tabs.update(tab.id, {url:newUrl});
someOtherFunction();
```

ただし、実際には`query（）`は非同期メソッドであるため、このコードは動かない。つまり、タブのID取得処理が完了するのを待たずに戻り、戻り値としてタブのIDを返さない。

`query（）`の仕様を確認すると、その構文としてコールバック関数を引数に渡す必要があることがわかるため、このメソッドは非同期であることがわかる。

```javascript:query()関数の構文
chrome.tabs.query(object queryInfo, function callback)
```

上記のコードを修正するには、引数にコールバック関数を渡す構文に修正する必要がある。次のコードは、`query（）`の結果を（tabsという名前の引数にて）取得し、`update（）`を呼び出すコールバック関数を定義する方法を示している。

```javascript:非同期関数の正しい使い方
//THIS CODE WORKS
chrome.tabs.query({'active': true}, function(tabs) {
  chrome.tabs.update(tabs[0].id, {url: newUrl});
});
someOtherFunction();
```

上記の例では、実際には1行目→4行目→2行目の順序で処理が実行される。もう少し詳しく言えば、`query()`の第二引数に渡されたコールバック関数は、`query()`の処理が完了した後(現在選択しているタブに関する情報にアクセス可能となった後)に実行される。

なお、`update（）`も非同期メソッドだが、この例では、`update()`の結果について何も処理を行わないので、コールバックパラメータは渡す必要がない。

### chrome.* APIの詳細
詳細については[chrome.* APIドキュメント](https://developer.chrome.com/extensions/api_index)を参照。

## ページ間の通信
拡張機能内のHTMLページ間にて、しばしば通信したい事がある。拡張機能のページはすべて同じスレッド上で同じプロセスで実行されるため、ページは相互に直接、関数の呼び出しを行うことができる。

拡張機能のページを検索するには、`getViews（）`や`getBackgroundPage（）`などの[chrome.extension](https://developer.chrome.com/extensions/extension)メソッドを使用する。

あるページが拡張機能内の他のページを参照している場合、そのページは他のページ上の関数を呼び出すことができ、DOMを操作することができる。

## データの保存とシークレットモード
拡張機能では、[chrome.strage](https://developer.chrome.com/extensions/storage) API、HTML5の[Web Storage API](https://w3c.github.io/webstorage/)（localStorageなど）、またはサーバーと通信してサーバ側にデータを保存できる。

何かを保存したいときは、まずシークレットモードのウィンドウかどうかを検討する。
デフォルトでは、シークレットモードでは拡張機能は実行されません。シークレットモードの時にはユーザが拡張機能に何をを期待しているかを検討する必要がある。

シークレットモードではウィンドウに履歴が残らない事が保証される。つまり、シークレットウィンドウのデータを処理する場合は、この約束を守るために最善を尽くす必要がある。たとえば、拡張機能が通常、ブラウジング履歴をクラウドに保存する機能を提供していた場合でも、シークレットウィンドウでは履歴を保存すべきでない。

一方、拡張機能はどんなウィンドウだとしても（シークレットモードかどうかに関係なく）データを保存することができる。

ウィンドウがシークレットモードかどうかを検出するためにはtabs.Tabまたはwindow.Windowオブジェクトの`incognito`プロパティを確認すれば良い。

```javascript:データ保存時にシークレットモードかどうかをチェックし、保存先を変える
function saveTabData(tab, data) {
  if (tab.incognito) {
    chrome.runtime.getBackgroundPage(function(bgPage) {
      bgPage[tab.url] = data;      // Persist data ONLY in memory
    });
  } else {
    localStorage[tab.url] = data;  // OK to store data
  }
}
```

## それで？
拡張機能の基礎は習得できたので、次はやりたいことに合わせて考えてみよう。

* [チュートリアル: Getting Started](https://developer.chrome.com/extensions/getstarted)
* [チュートリアル: デバッグ](https://developer.chrome.com/extensions/tut_debugging)
* [開発者ガイド](Developer's Guide)
* [サンプル集](https://developer.chrome.com/extensions/samples)
* [動画](https://www.youtube.com/view_play_list?p=CA101D6A85FE9D4B), 例えば[Extension Message Passing](https://www.youtube.com/watch?v=B4M_a7xejYI&feature=PlayList&p=CA101D6A85FE9D4B&index=6)など