Chromeエクステンションでcontentの情報を取得する

初めてChrome Extention作ってみました。
https://github.com/NewGyu/redmine_tickets_printable

そもそも論的なところはこんないいまとめ

- http://developer.chrome.com/extensions/overview.html
- http://dev.screw-axis.com/doc/chrome_extensions/guide/

があるので割愛して、自分が詰まったところをメモ代わりに書いておきます。

## ざっくり要件

今回作ったのは[Redmine](http://redmine.jp/overview/)のチケット一覧を付箋みたいにブロックを並べた見た目にするものです。

Redmineのチケット一覧はリスト形式で、それをA4印刷して貼りだすと小さくて読めないので大きく、付箋っぽくしようというものです。

before
![before.PNG](https://qiita-image-store.s3.amazonaws.com/0/31010/7c9ab78a-0ffc-ad66-b328-19743461d851.png)

after
![after.PNG](https://qiita-image-store.s3.amazonaws.com/0/31010/fb8a5c37-310e-eed0-d2fe-c64024643009.png)

そして、今回はブラウザアクションではなくページアクション（該当ページの場合にオムニボックスにアイコンが表示される奴）で、
![pageAction.PNG](https://qiita-image-store.s3.amazonaws.com/0/31010/4a6c6d52-5eac-5907-bf79-1e593eba9467.png)

別タブに印刷用ページを開く
![tab.PNG](https://qiita-image-store.s3.amazonaws.com/0/31010/38ea4cae-acc2-b1d4-57b3-f12e207b2b75.png)

というものです。

## オムニボックス（アドレスバー）にアイコンが出ない

まず最初に詰まったのがアイコンが出ないんですね。

```json:manifest.json
  "page_action": {
    "default_icon" : "icon/unko16.png"
    ,"default_title": "印刷用ページ出す"
  },
```

こう書いとけば出るんだろうくらいに思ってましたが、実際には明示的に[chrome.pageAction.show](http://developer.chrome.com/extensions/pageAction.html#method-show)する必要がありました。

### chrome.pageAction.showはbackgroundからじゃないと呼べない

どうも[content script](http://developer.chrome.com/extensions/content_scripts.html)を使うのが常套手段のようです。content scriptは懐かしい[GreaseMonkey](http://ja.wikipedia.org/wiki/Greasemonkey)みたいに、ページにリアクションするやつですね。
ところが、chrome.pageAction.showはcontent scriptからはコールできないようです。そのため、次のような周りくどいことをする必要がありました。

#### 1. まずバックグラウンドにメッセージを送る

```js:contentscript.js
chrome.runtime.sendMessage({issues: issues}, function(response) {});
```

今回はレスポンスを受けるcallbackに意味はありません。書かなきゃいけないから書いただけですね。

#### 2. バックグラウンドでメッセージを受けてそこでchrome.pageAction.showする

onMessageのリスナーにリアクションするcallbackを登録し、その中でやります。

```js:background/bk.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
  chrome.pageAction.show(sender.tab.id);
  sendResponse({tabid:sender.tab.id});
});
```

今回はレスポンスに意味はありません。書かなきゃまずいのかと思って書いただけです。
pageAction.showがtabidを要求しているのですが、それは第二引数のsenderから取れるみたいです。
実はここ結構悩みました。

## アイコンをクリックした時の新しいTabの作成ができない

さてさて、これでアイコンが出るようになったので次はアイコンをクリックした時にNew Tabを開きたいところです。これは[chrome.tabs.create](http://developer.chrome.com/extensions/tabs.html#method-create)を呼べばいいようですね。アイコンがクリックされた時のハンドリングはバックグラウンドで[pageAction.onClicked](http://developer.chrome.com/extensions/pageAction.html#event-onClicked)で。

```js:background/bk.js
chrome.pageAction.onClicked.addListener(function(tab){
    chrome.tabs.create({url:"printable.html?tabid=" + tab.id},function(tab){
    });
});
```

ん・・・が、出ない。
上記コードは最終的なもので動くのですが、この時点ではくだらないミスがあってcreateをcallする以前に実行時エラーでコケていたのです。

### バックグラウンドのconsole.logが出ない

…が、ChromeのDevToolのconsoleにログがでなくてそこがわかりませんでした。
バックグラウンドのログは別のところに出ます。chrome://extensions/ のページからバックグラウンドページのデバッガを起動して、
![extentions.PNG](https://qiita-image-store.s3.amazonaws.com/0/31010/104931ae-51fe-4e6d-1475-6313a78dfbf6.png)
そこのコンソールで確認する必要があります。

## tab create したページに元ページの情報を渡せない

これでようやくウン○をクリックするとNew Tabが開くようになりました。

あとは元となるRedmineのチケット一覧ページから必要なチケット情報をつまんできて並べるだけなのでjQueryでやればいいやと思っていたのですが…New Tabのprintable.htmlから元となるRedmineのチケット一覧ページのElementsをどうやって持ってきたらいいの…？　ここにイチバン悩んだのでした。

悩んだ末に結局とった方法は「バックグラウンドに一旦覚えさせる」でした。
もっといい方法があるような気もするのでここは教えていただけると助かります、というところです。

#### 1. content script から runtime.sendMessageに送りつける

バックグラウンドに情報を渡すこと自体は、runtime.sendMessageで送れます。
が、しかし、渡せるものに制限があります。jQueryオブジェクトをドカーンと渡したりはできません。そこでほしい情報をドキュメントのままではなく一旦json化しました。（下記例のissuesです）

```js:contentscript.js
var issues = $("table.issues tbody>tr.issue").map(function() {
   var td = $(this).find("td");
   return {
     id: td.filter(".id").text()
     ,tracker: td.filter(".tracker").text()
     ,project: td.filter(".project").text()
     ,subject: td.filter(".subject").text()
     ,requester: td.filter(".cf_57").text()
     ,client: td.filter(".cf_80").text()
     ,storyPoint: td.filter(".cf_64").text()
     ,hours: td.filter(".estimated_hours").text()
   };
}).get();

chrome.runtime.sendMessage({issues: issues}, function(response) {
  console.log("tabid="+response.tabid);
});
```

本当はこのjsonへの変換をウ○コをクリックした時にしたかったのですが、バックグラウンドからcontentへのアクセス方法がよくわからず。。[Tabオブジェクト](http://developer.chrome.com/extensions/tabs.html#type-Tab)からたどれるんですかね？

#### 2. バックグラウンドで大域変数に保存する

なーんかいまいちですが。localStrageに入れるのでもいいんでしょうね。

```js:background/bk.js
var issues = {};

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
  chrome.pageAction.show(sender.tab.id);
  issues[sender.tab.id] = request.issues;
  sendResponse({tabid:sender.tab.id});
});
```

複数TABから同時使用の想定はないのですが、念のためtabId別に領域を確保しています。

#### 3. printable.html からバックグラウンドの大域変数にアクセス

なんだかprintable.htmlに直接スクリプトを書くとエラーになったので実際には外部JSファイルです。

```js:printable.js
var template = $("#tmpl").html();
var bk = chrome.extension.getBackgroundPage();
var tabid= location.search.match(/tabid=([^&]+)/)[1];
document.write(Mustache.to_html(template, {issues: bk.issues[tabid]}));
```

テンプレートエンジン[Mustache](https://github.com/janl/mustache.js)のコードが入っているので本質が見えづらいですが、キモは```var bk = chrome.extension.getBackgroundPage();```と、```bk.issues```のところです。


というわけで大したことやってないけどいろいろ躓いたのでした。