続・先取り! Google Chrome Extensions

第2回 User ScriptsとContent Scripts

この記事を読むのに必要な時間:およそ 5 分

User Scriptsの作り方

それでは,実際に簡単なスクリプトを書いてみます。今回はGoogleの検索結果にfaviconと番号を表示するUser Scriptを作成してみます。まずはメタデータを記述します。

メタデータの記述

// ==UserScript==
// @name           Google Search Number Favicon
// @match          http://www.google.com/search*
// @match          http://www.google.co.jp/search*
// ==/UserScript==

スクリプト名はGoogle Search Number Favicon(単語を並べただけで恐縮ですが…)としました。@matchでGoogleの検索結果のURLを表現します。matchの部分はhttp://www.google.*/search*のように記述したいところですが,残念ながらhostパートの*は//の直後でしか使えません。

それでは本体のプログラミングに入ります。と,その前にGoogleの検索結果のHTMLを確認しておきます。適当な検索を表示し,右クリックから「要素の検証」を選択します。するとDeveloper Toolsが立ち上がり,右クリックした辺りがハイライトされて表示されます。すると,図1のように検索結果のリンクはclass="l"を持ち,h3要素を親に持つことが確認できると思います。

図1 Web Inspectorによる解析

図1 Web Inspectorによる解析

今回はこれを元に要素にアクセスします。DOM要素を取得する方法は手軽なSelector APIと高機能なXPathの2つがお薦めです。今回はSelector APIを使用します。

要素の取得とナンバリング

var links = document.querySelectorAll('h3 > a.l');
for (var i = 0, len = links.length;i < len; i++) {
   var link = links[i];
   var num = document.createTextNode(i+1 + " ");
   link.parentNode.insertBefore(num,link);
}

querySelectorAllで⁠l⁠というクラス名を持つA要素を取得し,その要素の前に1から順番にテキストを追加しています。本当にシンプルなスクリプトですが,ひとまずナンバリングができました。

しかし,このままでは2ページ目でも番号を1から振り直してしまいます。そこでパラメータから初期値(start)を取得してその値に応じて初期値を変えてみます。

初期値の取得と反映

var links = document.querySelectorAll('h3 > a.l');
var start = 1;
var match = location.search.match(/&?start=(\d+)&?/);
if (match && match[1]) {
   start = parseInt(match[1], 10) + 1;
}
for (var i = 0, len = links.length;i < len; i++) {
   var link = links[i];
   var num = document.createTextNode(String(start + i) + " ");
   link.parentNode.insertBefore(num,link);
}

location.searchからstart=10の数値部分を取り出し,数値が取れればそれを初期値として処理しています。こういったパラメータを使用する場合はその扱いに十分に注意する必要があります。今回は\dにマッチした数字をparseIntで数値に変換し,createTextNodeで文字列ノードにしてページに追加していますので,クロスサイトスクリプトが起こる危険性はありません。しかし,URLから取得した値を不用意にinnerHTMLなどに挿入すれば簡単にクロスサイトスクリプトが可能になってしまいます(もちろん,これはJavaScript一般に言えることです)⁠

続いてfaviconの表示を実装してみます。

faviconの挿入

var links = document.querySelectorAll('h3 > a.l');
var FAVICON = 'http://www.google.com/s2/favicons?domain=';
for (var i = 0, len = links.length;i < len; i++) {
  var link = links[i];
  var favicon = document.createElement('img');
  favicon.src = FAVICON + link.host;
  link.parentNode.insertBefore(favicon,link);
}

faviconの表示にはGoogleのAPIを使用しました。A要素の取得方法はナンバリングの時と全く同じです。A要素はhref属性のほかに,hostやpathnameなどのプロパティを持っているので,それを利用しています。では最後に今までのスクリプトをまとめます。

スクリプトの全体

// ==UserScript==
// @name           Google Search Number Favicon
// @include        http://www.google.com/search*
// @include        http://www.google.co.jp/search*
// ==/UserScript==
(function(){
  var FAVICON ='http://www.google.com/s2/favicons?domain=';
  var start = 1;
  var match = location.search.match(/&?start=(\d+)&?/);
  if (match && match[1]) {
    start = parseInt(match[1], 10) + 1;
  }
  var links = document.querySelectorAll('h3 > a.l');
  for (var i = 0, len = links.length;i < len; i++) {
    var link = links[i];
    var num = document.createTextNode(start + i + " ");
    link.parentNode.insertBefore(num, link);
    var favicon = document.createElement('img');
    favicon.src = FAVICON + link.host;
    link.parentNode.insertBefore(favicon,link);
  }
})();

スクリプト全体を無名関数(function(){})で囲みました。これはChromeのUser Scriptsには必要のない処理です。ただし,OperaのUserJavaScriptではこの無名関数がないとページ側の変数を汚染してしまいます。今回書いたスクリプトはChromeだけでなく,Opera,Greasemonkey,GreaseKitでも動作しますので,そういった点も考慮しておきます。また,これに合わせてメタデータの記述をChrome専用の@matchではなく@includeに変更しています。

これでこのスクリプトは完成です。なお,このスクリプトはサンプル用にシンプルな実装になることを優先しています。このスクリプトの改善点としては,数字の表示はスタイルシートのlist-styleで行ってolタグのstart属性で値を制御する方法や,faviconの表示はCSSのbackground-imageで表示してキャッシュを活用しつつ表示を軽くする方法などが考えられます(特に,元々のリンクの表示位置がずれることがないように実装することで,体感的な表示速度を大幅に改善できると思われます)⁠また,Googleの検索結果のリンク先URLがリダイレクトページを挟む状態になっているとFaviconの表示が正常に機能しません。その点も改善の余地があります。

Content Scriptsとして拡張化

User Scriptsとして作成したスクリプトはそのままContent ScriptsとしてExtensionにすることが可能です。下記のようなmanifest.jsonを用意してスクリプトと同じフォルダに置き,chrome://extensions/ ページのDeveloper ToolでパッケージングするだけでExtensionの出来上がりです。なお,Content Scriptとして実行する場合,user.jsファイルのメタデータは使用されない点に注意してください。

拡張用manifest

{
  "name": "Google Search Number",
  "version": "0.1",
  "content_scripts": [
    {
      "js": [
        "google_search_number_fav.user.js"
      ],
      "matches": [
        "http://www.google.com/search*",
        "http://www.google.co.jp/search*"
      ]
    }
  ]
}

最後に,Greasemonkey ScriptやChrome ExtensionsのAutoPagerizeで継ぎ足されたページにも対応させたスクリプトを公開しておきます。

まとめ

Google ChromeのUser Scriptsについて,Greasemonkeyの概要から実際のスクリプトの作り方を解説しました。同時にChrome Extensionsの作りやすさも実感していただけたのではないかと思います。

また,ユーザーサイドスクリプトはアイディアひとつで役に立つ機能でも,面白い機能でも手軽に実現できるので,プログラミングの勉強の題材としても優れています。この機会に試してみて頂ければ幸いです。

次回は,Page Action ver2やOption PageなどのExtensions APIの解説を予定しています。

著者プロフィール

太田昌吾(おおたしょうご,ハンドルネーム:os0x)

1983年生まれ。JavaScriptをメインに,HTML/CSSにFlashなどのクライアントサイドを得意とするウェブエンジニア。2009年12月より、Google Chrome ExtensionsのAPI Expertとして活動を開始。

URLhttp://d.hatena.ne.jp/os0x/

コメント

コメントの記入