先取り! Google Chrome Extensions

第3回 Chrome Extensionsの作り方#2

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

複数のURLに対してブックマーク数を取得するには,はてなブックマークの場合ははてなブックマーク件数取得APIが,deliciousではFeeds APIのSummary information about a URLが使用できます。なお,deliciousのAPIを使うためにURLのmd5ハッシュを計算する必要があるため,JavaScriptでハッシュアルゴリズムのmd5.jsを使用します。md5.jsには,光成氏による,より最適化された実装もありますが,Chromeでは大きな差にならないとのことなので,今回はシンプルな実装を採用しています。

では,Background Page側でURLに対応するブックマーク数を取得して,再びContent Script側にデータを渡します。なお,はてなブックマークのXMLRPC APIを利用する部分については解説を省略させていただきます。ご了承ください。

Content Scriptへメッセージの送信#2 はてな API

var parser = new DOMParser(), text = xhr.responseText;
var doc = parser.parseFromString(text, 'text/xml');
var res = [];
$X('//member',doc).forEach(function(member,i){
  var vals = $X('*',member);
  res.push({
    url: vals[0].textContent,
    total_posts: vals[1].textContent
  });
});
con.postMessage({
  "count":{
    "id": "hatena",
    "items": res,
    "api_link": service.api_link
  }
});

はてなブックマーク件数取得APIで取得したXMLをパースし,XPathでデータを取り出してContent Script側に渡しています。

Content Scriptへメッセージの送信#2 delicious API

var api = 'http://feeds.delicious.com/v2/json/urlinfo';
var param = 'hash=' + info.links.map(function(url){
  return encodeURIComponent(md5.hex(url));
}).join('&hash=');
var xhr = new XMLHttpRequest();
xhr.open('post',api + '?' + param, true);
xhr.onload = function(){
  var res = JSON.parse(xhr.responseText);
  con.postMessage({
    "count":{
      "id": "delicious",
      "items": res,
      "api_link": service.api_link
    }
  });
};
xhr.send(null);

deliciousのAPIはURLをmd5に変換する必要があるものの,フォーマットをJSONで受け取れるため扱いやすくなっています。

Background Pageからのメッセージの受信と件数の表示

connection.onMessage.addListener(function(info){
  if (info.matched) {
    siteinfo = info.matched;
    SeachInlineLinks(document);
  } else if (info.count) {
    Counter(info.count);
  }
});
function Counter(data){
  var id = data.id, api_link = data.api_link;
  var class_name = 'sso_'+id+'_bookmark_counter_element';
  data.items.forEach(function(item){
    if (item.total_posts < 1) return;
    var links = CountItem[item.url];
    links.forEach(function(link){
      if (link.nextSibling &&
        class_name === link.nextSibling.className){
        return;
      }
      var parent = link.parentNode;
      var a = document.createElement('a');
      a.href = api_link.replace(/#\{([^}]+)\}/g,
          function(_, _$){return item[_$] || '';});
      a.className = class_name;
      a.textContent = item.total_posts + ' user' +
          (item.total_posts === '1' ? '' : 's');
      parent.insertBefore(a, link.nextSibling);
      CountedItems.push(link);
    });
  });
}

メッセージを受け取るContent Script側は,やはりメッセージの内容によって処理を分岐するようにします。ブックマーク数のデータを受け取ったら,URLに対応するリンク要素に対して,ブックマーク数をそのリンクの後ろに追加します。

Node.parentNode.insertBefore(NewNode, Node.nextSibling)

これは特定のノードの後ろに新しいノードを追加するときに使用するイディオムとして覚えておいて損はないでしょう(なお,Node.nextSiblingはnullである可能性がありますが,insertBeforeは第2引数がnullである場合は,新しい要素を末尾に追加するので問題ありません)⁠

最後になりましたが,最初にmanifest.jsonで定義したbookmark.cssで,追加したブックマーク数リンクのスタイルを定義します。ページ側のスタイルとぶつからないように長めのクラス名を付けてありますので,それを元にCSSを書きます。

Content ScriptのCSS(bookmark.css)

a.sso_hatena_bookmark_counter_element,
a.sso_delicious_bookmark_counter_element{
  font-weight:bold !important;
  padding:0 2px !important;
  display:inline !important;
  font-size:10px !important;
}
a.sso_hatena_bookmark_counter_element{
  background:#ffffff !important;
}
a.sso_delicious_bookmark_counter_element{
  background:#0066cc !important;
}
a.sso_hatena_bookmark_counter_element:link,
a.sso_hatena_bookmark_counter_element:visited{
  color:#ff8888 !important;
  text-decoration:none !important;
}
a.sso_delicious_bookmark_counter_element:link,
a.sso_delicious_bookmark_counter_element:visited{
  color:white !important;
  text-decoration:none !important;
}
a.sso_hatena_bookmark_counter_element:hover{
  color:#ff6666 !important;
  text-decoration:none !important;
}
a.sso_delicious_bookmark_counter_element:hover{
  color:white !important;
  background:#0099cc !important;
  text-decoration:none !important;
}

ページ側のスタイルより優先されるように!importantを付けています(余談ですが,Content Scriptsのcssはhtml要素の直下に挿入されます。HTMLの仕様ではhtml要素の子になれるのはhead要素かbody要素と決まっているので,少々行儀の悪い挙動です……)⁠

以上をパッケージしたファイルは下記にあります。前回とは別ファイルになっています。

SBM Counter2 for Chrome 4

まとめ

今回は前回に続いてExtension APIの使い方をContent ScriptとBackground Pageの連携部分を中心に実習しました。次回はChrome 4での新機能やExtensionsの今後について,最新事情を追っていきたいと思います。

著者プロフィール

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

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

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