【Javascript】爆速でChrome拡張プラグイン開発を試す。作り方は意外とかんたんでした。

はじめに

みなさん、こんにちは!

今回は、Chrome拡張機能(プラグイン)をサクッと触ってみようと思います。
Chrome拡張がどうやらHTML+CSS+Javascriptで作れるようなので、どんなものなのか試してみたくなったからです。

とりあえず、簡単だけれど役に立ちそうな機能を作りたいので…
現在表示されているwebページから、必要な情報だけをボタンクリックで抽出できるような機能を作ることを目的にしようと思います。
ちなみに、この「webページから必要な部分を抜き出す」ことをスクレイピングといいます。
通常、スクレイピングは、phpなどでhtmlソースを取得する所から始めますが、今回はとりあえずjs縛りなんでクロスドメインのhtmlソースを単なるjsだけで取得することはできないので、表示されているページの中から必要な部分を抜き出すまでの機能を作りました。しかもGoogleの検索結果ページ限定で。

Chrome拡張機能開発の概要を少し

早速作る!前に、Chrome拡張について最低限の知識を少し。

Chrome拡張の種類

3種類の拡張種類があります。

  • BrowserAction(これ重要)
    • ツールバーにアイコンが表示されて、ボタンクリックで拡張機能が駆動するタイプ
  • PageAction(これ不要)
    • URLバーに表示させるタイプのもの(だった?)
  • OverriderAction(これ不要)
    • ページ書き換えを行う

とありますが、まぁ、BrowserActionが一般的でしょうし、私はこれしか知りません。

2 + 1種類のJavascriptの世界

Chrome拡張では、同じ.jsファイルでも住む世界が異なるものがあります。それがContent ScriptとBackground Pageです。

  • Content Script
    • 指定したページで実行されるjavascript
    • 使用する場合は、manifest.jsonに”content_scripts”と書いて値を設定
      • 「どこのページで」, 「どんなscriptファイルを実行するか」などを設定します
    • 制約がある(ChromeAPI群で使えないものがある,ページ内変数にアクセスできないなど)
  • Background Page(Event Page)
    • バックグラウンドで実行されるjavascript
    • 使用する場合は、manifest.jsonに”background”と書いて値を設定
    • 制約がない
    • Background Pageは常にbackgroundで動くんで、Event Pageで必要な時に実行する方が良い

大きな2種類は上記のような感じです。
基本はContent Scriptで、必要であればEvent Pageをキックする感じが王道なんでしょうかね?

さらに、それとは別に、もう一つ異なる世界のjavascriptがあります。

  • popupの中のJavascript
    • BrowserActionでChrome拡張機能を作っている時の話です。
      ツールバーのアイコンをクリックした時に、htmlのポップアップを表示することができ、さらにここからjavascriptを実行して…ということができます。
      このjavascriptは、ポップアップ内のDOMしか触れなかったり(確か)、前述のjavascriptとはまた別の世界の住人な気がするので + 1として記述しておきます

最初に、ポップアップ内のJavascriptから直接webページのDOMにアクセスしようとしたけれど、なんだか全然対象要素が取れない!とかで嵌った記憶があります…

とりあえず、大事なのは、同じJavascriptでもそれぞれ住んでいる世界が違うということをちゃんと認識しておくことですね。

Chrome拡張機能に必要なファイル

Chrome拡張開発を行う上で、絶対必要なファイルは、

  • manifest.json
  • jsファイル1つ

です。
manifest.jsonには、プラグインに関する情報を記述します。
例えば、拡張機能の名称, アイコン, バージョン, ポップアップを表示するならアイコンクリック時に表示するhtmlファイル,使用するjs名などを記述します。
jsファイルの方は、いくつでも良いですが、最低1つは無いと何もしてくれないカスプラグインになってしまいますから。

とりあえず、上記2ファイルで最低限動くものは作れますが、今回はスクレイピングの実行タイミングをユーザに決定させたいのと、結果を何処かに表示しないといけないので、拡張機能アイコンクリック⇒ポップアップが表示される⇒スクレイピング実行ボタン押下で処理開始⇒結果がポップアップ内に表示される、みたいな動きの機能を作ります。
この構成でいく場合は、ポップアップ側の挙動を行わせるためのhtml+javascriptを別途用意する必要があります。
これが前述の2 + 1種類の世界の+1の世界を担当する側です。

作ってみた拡張機能

chromeex1

ツールバーの”M”というアイコン(アイコン画像をセットしていないんで)をクリックすると、「GET」と「RESULT」だけ表示されたポップアップが開き、
「GET」ボタンを押すと、今開いているGoogle検索ページのタイトル一覧を抜き出して表示してくれる、という機能です。

わお!シンプル。

勉強用に、このChrome拡張のzipを置いておきます⇒myscraper,zip

ちなみに、作った拡張機能をChromeに取り込むためには、
ツールバーのメニューボタン⇒設定(S)⇒拡張機能(左側のメニュー)
で表示される、拡張機能画面の左上にあるボタン、”パッケージ化されていない拡張機能を読み込む…”を押して、取り込みたい拡張機能のルートフォルダを指定してあげればokです。
有効 チェックを付ければちゃんとツールバーにアイコンが表示されるはずです。

myscraper.zipでいうと、解凍した後にできる、myscraperというフォルダを指定してあればokです。

 

ソースコードはこんな感じです

  • myscraper
    • manifest.json
    • doScraping.js
    • foreground
      • popup.html
      • js
        • main.js

ルートフォルダはmyscraperで、その直下にmanifest.json, doScraping.jsがあります。
foregroundというフォルダの中には、ツールバーのアイコンをクリックした時に表示されるポップアップ画面関連のhtml, jsファイルが納められているという感じです。

manifest.json

{
  "name": "My Scraper",
  "description": "scraping test",
  "version": "0.0.1",
  "manifest_version": 2,
  "browser_action": {
    "default_popup": "foreground/popup.html"
  },
  "content_scripts": [{
    "matches": ["http://www.google.co.jp/*", "https://www.google.co.jp/*"],
    "js": ["doScraping.js"],
    "run_at": "document_end"
  }],
  "permissions": [
    "tabs"
  ]
}

name, description, versionはそのまんまなので割愛、
manifest_versionは2を指定してあげてください。とりあえず。おまじないです。

今回はツールバーに表示したかったのでbrowser_actionを指定して、その中でクリック時に開くポップアップのhtmlファイルをdefault_popupで指定している感じです。

前述のcontent_scriptsも定義しています。これは、表示しているwebページ内のDOMを取得して抽出して返却させるのに使用しています。
matchesプロパティでは、どのwebページで有効にするか、
jsプロパティは使用対象のjsファイル名、
run_atはいつscriptを実行するかを決定します。なくてもokです。どうせクリックする時にはDocumentの準備が終わっているはずなんで。

permissionsでは、tabsを指定しています。
chrome apiのtabに関するものを使用するためです。

doScraping.js

chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
	var result = "<ul>";
	var el_titles = document.querySelectorAll("#ires .g .r a");

	for(var i = 0; i < el_titles.length; i++){
		result += "<li>" + el_titles[i].innerHTML + "</li>";
	}

	result += "</ul>";
	sendResponse(result);
});

chrome.runtime.onMessage.addListenerは、chromeの提供するapiで、外部からメッセージが送られてきた場合に起動するイベントを仕掛けることができます。
使い方はソースの通りで、イベント駆動時の挙動を示すfunctionを引数として渡します。
そのコールバックfunctionには、msg, sender, sendResponseが渡されるらしいです…
が、私はmsgしか使ったことがないので他の引数については割愛します。

msgは、渡してきた側の実装によりますが、今回私はmsgに何も入れてません!
ので、この引数群は実際は特に意味がないです。はい。

コールバック内では、webページのDOM要素から抽出対象を抜き出して、sendResponseでキック元に値を返却しています。

popup.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<button id="ChromePlugin-doScrape">GET</button>
<div class="result-wrapper" style="width: 300px;">
    <h3>RESULT</h3>
    <div id="ChromePlugin-result-disp"></div>
</div>

<script src="js/main.js"></script>

</body>
</html>

このhtmlは、ツールバーのアイコンを押下した時に開くポップアップを定義しています。
実際の動きは最後に読み込んでいるmain.jsに記述してあります。
GET のボタンを押下した時にDOM抽出イベントをキックするためのクリックイベントがmain.jsによって付与されます。

main.js

document.addEventListener("DOMContentLoaded", function() {
	document.getElementById("ChromePlugin-doScrape").addEventListener("click", function(){
		
		chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
            chrome.tabs.sendMessage(tabs[0].id, {},
            function(msg) {
              document.getElementById("ChromePlugin-result-disp").innerHTML = msg;
            });
        });

	});
});

このjsは、ポップアップのDOMがロードされた時に駆動するイベントリスナが記述されています。
その中で、先程のGETボタンにクリックイベントをセットしています。

GETボタンが押下されると、chrome.tabsのsendMessageメソッドを使用して、メッセージを発行しています。
このsendMessageが動くと、doScraping.jsのメッセージリスナが駆動して、webページスクレイピング⇒編集⇒返却となり、こちら側jsのsendMessageのコールバックが駆動します。そこでポップアップ側htmlに結果を表示しています。

 

おわりに

Chromeプラグイン開発で重要な概念は、javascriptの存在世界がそれぞれ異なり、それぞれmessageのやりとりを行って連携するよ、という部分でしょうか。
このあたりが分かれば、意外とすんなり開発できるかなと思います。
ただし、javascriptを分かっている場合ですが…

今回は以上です!