先取り! Google Chrome Extensions

第4回Chrome Extensionsのこれから

この記事で取り上げているAPIは現在と使い方が異なっていたり、使用できなくなったものを含んでいます。

特にToolstrips APIは最新のChromeでは使用できなくなっています。詳しくは続・先取り! Google Chrome Extensionsをご覧ください。

前々回前回はSBMカウンタを中心にExtensionsの作り方を学んできました。今回はあと少しだけSBMカウンタを改良してExtensionとして仕上げを行いつつ、Chrome Extensionsの最新情報と今後の予定を見ていきます。

カスタマイズと設定の保存

2つのソーシャルブックマークサービスのブックマーク数を表示し、さらにウェブページ内のリンクにもブックマークを表示する、とSBMカウンタも少しずつ機能が増えてきました。このように機能が増えてくるとエンドユーザー側で機能をカスタマイズしたいという要望が出てきます。今回は、WebStorageを使ったユーザーのカスタマイズにスポットを当てていきます。

WebStorageとWebDatabase

まず、WebStorageについて簡単に説明します。WebStorageは、ブラウザ内のキーバリューストアとしてデータを保存・参照できる仕組みです。期限なく永続化するlocalStorageと、セッション(この場合、ブラウザを起動してから終了するまで)単位で永続化するsessionStorageの2つのAPIからなります。FirefoxにおいてDOMStorageと呼ばれる機能をベースとして、W3CのHTML5仕様の一部として標準化が進み、現在はHTML5から独立した仕様として策定が進んでいます。

また、WebDatabaseというブラウザ内でリレーショナルデータベースを扱える仕組みもあります。具体的にはブラウザ内部にSQLiteを持ち、SQLでデータを扱うことができます。こちらはGoogle Gearsの実装をベースとして、やはりHTML5の一部として標準化が進んでいましたが、一旦はWebStorageとして分離され、さらに現在はWebDatabaseとして独立した仕様になっています。

Google Chromeは、このWebStorageとWebDatabaseをバージョン 4からサポートします。今回は扱いやすいWebStorageを使ってExtensionに設定機能を実装してみたいと思います。

Extension API

さて、設定機能を実装する前に、ToolstripsとBackground Pageで情報を共有できるようにしておく必要があります。というのも、設定画面で保存した情報をすべてのToolstripsとBackground Pageに個別に送るというのは避けたいので、ここはBackground Pageとのやり取り1つで完結するようにしたいと思います。これは、Extension APIのchrome.extension.getBackgroundPageで簡単に実現できます(ただし、半永続化されているBackground Pageのオブジェクトはメモリーリークを起こしやすいので、その点には十分注意する必要があります。本稿ではメモリリークについて十分なチェックを行っているとは言えません。ご了承ください⁠⁠。

まず、Background PageにSBMConfというグローバルプロパティを作成し、サービスの情報を入れておきます。

Background PageにSBMConfを用意
window.SBMConf = {
  Services:Services
};

Toolstrip側では、getBackgroundPageでBackground Pageのグローバルオブジェクトを取得し、そこからSBMConf、Servicesとアクセスします。これでBackground PageとToolstripsでデータを共有できました。

getBackgroundPageの利用
var BackGround = chrome.extension.getBackgroundPage();
var SBMConf = BackGround.SBMConf;
/* 省略 */
var services = SBMConf.Services.map(function(service, i){
/* 省略 */
});

続いて、サービスごとにON/OFFを切り替える機能を実装してみます。まず、設定ページを用意します。設定ページを呼び出す方法は今のところToolstripか、Page Actionのどちらかになります(ほかには chrome://extensions/ から呼び出す方法などが提案されています⁠⁠。今回はPage Actionを使用します(同時に前回作成したPageActionは削除します⁠⁠。

manifest.jsonの取得とPageActionの登録
function get_manifest(callback){
  var xhr = new XMLHttpRequest();
  xhr.onload = function(){
    callback(JSON.parse(xhr.responseText));
  };
  xhr.open('GET','./manifest.json',true);
  xhr.send(null);
}
get_manifest(function(manifest){
  var configAction = manifest.page_actions[0];
  chrome.pageActions[configAction.id].addListener(
  function(id, tabinf){
    var width = 480;
    var height = 500;
    var left = (screen.width / 2) - (width / 2);
    var top = (screen.height / 2) - (height / 2);
    var param = 'width=' + width + ', height=' + height +
        ', top=' + top + ', left=' + left;
    var name = manifest.name + '_config';
    var win = window.open('config.html', name, param);
    win.focus();
  });
  chrome.tabs.onUpdated.addListener(
  function(tabid, inf){
    if (inf.status !== 'loading') return;
    chrome.tabs.get(tabid, function(tab){
      chrome.pageActions.enableForTab(configAction.id,
          {tabId: tab.id, url: tab.url});
    });
  });
});

少々トリッキーですが、manifest.jsonをパースして得られるpage_actionsの定義を使ってPage Actionを登録する方法を使ってみました。Page Actionのアイコンがクリックされた際はwindow.openで小窓を開いています。chrome.windows.createではなくwindow.openを使用しているのは、よりシンプルな小窓を開くためです(本来ならshowModalDialogを使用したいのですが、拡張として機能しないバグ(?)のためwindow.openを使用しました⁠⁠。

config.htmlの一部(サービスの選択)
<h3>使用するサービス</h3>
<ul id="service_list">
</ul>
サービスの選択の制御
var service_list = document.getElementById('service_list');
SBMConf.Services.forEach(function(service){
  var item = document.createElement('li');
  var box = document.createElement('input');
  box.type = 'checkbox';
  box.addEventListener('click', function(){
    service.enable = box.checked;
  },false);
  box.checked = service.enable;
  var _id = box.id = 'enable_' + service.id;
  item.appendChild(box);
  var label = document.createElement('label');
  label.htmlFor = _id;
  label.textContent = service.name;
  item.appendChild(label);
  service_list.appendChild(item);
});

やはりgetBackgroundPageで取得したSBMConf.Servicesからサービスのリストを作成し、チェックボックスでON/OFFを制御しています。各Serviceにはenableというプロパティを用意してあり、これがtrue/falseを取るようになっています。ここまでで、下記のように(ブラウザを閉じるまで記憶される)サービスごとのオンオフ機能が実装できました。

図1 サービスの選択
図1 サービスの選択

localStorageの利用

さて、ようやく本題であるWebStorageに入ります。といっても、localStorage自体はキーに文字列を保存するシンプルなAPIとして簡単に扱えます。今回はサービスのIDをキーに localStorage['enable_hatena'] のように値を参照し、値を持っていればその値を初期値とします。またserviceオブジェクトには状態をセットするためのset_enableメソッドを追加します。

Background PageでのlocalStorageの利用
Services.forEach(function(service){
  var enable = localStorage['enable_' + service.id];
  if (enable) {
    service.enable = JSON.parse(enable);
  }
  service.set_enable = function(state){
    service.enable = state;
    localStorage['enable_' + service.id] = state;
  }
});

さきほどの、チェックボックスをオンオフした際の処理を修正し、set_enableメソッドを呼び出すようにします。

サービスの切り替えの修正
  box.addEventListener('click', function(){
    //service.enable = box.checked;
    service.set_enable(box.checked);
  },false);

ここでのポイントは、localStorageに触る部分をBackground Pageに集約している点です。こういったデータを扱う処理は1か所にまとめるのが望ましく、1か所にまとめるといえばBackground Pageとなります。なお、このほかに設定項目として、ウェブページ内にブックマーク数を表示する機能のON/OFF、ブックマーク数を表示しないURLを指定するフィルタリングの設定なども実装しましたが、仕組みとしてはほとんど変わらないため、説明は割愛させて頂きます。

設定ページのスタイル

さて、設定ページの機能は実装できましたが、見た目について調整していません。開発者向けとしてはこのままでも良さそうですが、ちょうどExtensionsの設定ページ用のスタイルChromium-extensionsグループで公開されていましたので、そちらを利用してみます。

Chromium-extensionsグループのダウンロードページに、DemoNativeOptions.crxというファイルがあります。こちらに含まれるchrome-native-look.cssというCSSファイルをお借りしました。

設定ページのHTML
<ul class="tabs" id="menu_tabs">
  <li class="basics"><a href="" class="active"><span>機能</span></a></li>
  <li class="filters"><a href=""><span>フィルタ</span></a></li>
</ul>
<section id="basics" class="content">
  <input type="checkbox" id="config_inline">
  <label for="config_inline">Web ページ内でのブックマーク数の表示</label>
  <p class="indent">
    <a href="http://b.hatena.ne.jp/guide/firefox_addon" target="_blank">はてなブックマーク Firefox 拡張</a><a href="http://wiki.github.com/hatena/hatena-bookmark-xul/web" target="_blank">Web ページ内にはてなブックマークの情報を表示する</a>機能を参考にしています。</p>
  <h3>使用するサービス</h3>
  <ul id="service_list">
  </ul>
</section>
<section id="filters" class="content hide">
  <p>ブックマーク数を表示したくないページのURLを正規表現で指定できます。</p>
  <ul id="filter_list">
  </ul>
  <input type="text" id="filter_text" value="">
  <button id="add_filter">追加</button>
</section>
<button class="fright" onclick="window.close();">Close</button>

tabsというクラス名のul要素はタブメニューになり、contentというクラス名を持つ要素がタブの中身になります。一部、sectionを使用したので下記のCSSを追加しました。

追加のCSS
body, ul, li{
  margin:0;
  padding:0;
}
section ul{
  list-style-type:none;
}
section{
  display:block;
}
#filter_list input[type='text']{
  width:300px;
}

タブメニューを制御するスクリプトは下記の通りです。クリックしたボタンのクラス名をactiveに、表示しないタブ(section)のクラス名にhideを加えるだけの処理です。

タブメニューの制御
var sections = $X('/html/body/section[contains(@class, "content")]');
$X('id("menu_tabs")/li/a').forEach(function(btn,i,btns){
  btn.addEventListener('click',function(evt){
    evt.preventDefault();
    btns.forEach(function(btn){btn.className = '';})
    btn.className = 'active';
    var active_id = btn.parentNode.className;
    sections.forEach(function(section){
      if (section.id === active_id){
        section.className = 'content';
      } else {
        section.className = 'content hide';
      }
    })
  }, false);
});

これで、図2のような設定ページが出来上がりました。

図2 設定ページ
図2 設定ページ

開発途中のExtensions API

ここまでは、⁠2009年8月末時点で利用可能だった)比較的安定した機能を中心に説明してきました。ここからは2009年9月に実装されたばかりで不安定であったり、実装途中の最新APIについて見ていきます。以下の情報は2009年9月中旬時点での情報ですので、実装状況などは参考程度に見てください。

AutoUpdate

Extensionを自動でアップデートする仕組みが4.0.207.0から安定して動作するようになりました。さらに、4.0.211.4からは手動でアップデート行うこともできるようになっています。AutoUpdateを行うためには、manifest.jsonにアップデート情報を定義したXMLのURLを指定し、そのXMLでバージョンとcrxファイルの場所を指定します。

まず、manifest.jsonにupdate_urlを記述をします。

update_urlの定義
  "update_url": "http://ss-o.net/chrome_extension/sbm_counter/updates.xml",

次に、updates.xmlの中身を記述します。下記の通り、ExtensionのIDとcrxファイルの場所、バージョンを指定しています。

updates.xmlの記述
<?xml version="1.0" encoding="UTF-8"?>
<gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
  <app appid="pdoiecdfkkomendcbkpddhlklnompmbn">
    <updatecheck codebase="http://ss-o.net/chrome_extension/sbm_counter.crx" version="3.0.0" />
  </app>
</gupdate>

Extensionを更新した際は3.0.1のようにバージョンナンバーを上げ、⁠同じpemファイルで)パッケージングしたcrxファイルを所定の場所にアップし、updates.xmlを更新すればアップデートの対象となります。デフォルトでは1時間おきに更新チェックを行いますが、--extensions-update-frequencyオプションでチェック間隔を秒単位で指定できます。また、 chrome://extensions/ ページの右上にあるUpdate extensions nowボタンで手動で更新チェックを行うことも可能です。

なお、SBMカウンタではパッケージ内にupdates.xmlが含まれていますが、本来は必要ありません。筆者のファイル管理の都合でそのようになっているだけですので、その点はご承知ください。

Icons

Extensionにはアイコンを指定でき、インストールの際などにそのアイコンを表示させることができます。128px, 64px, 32px, 16pxのアイコンを指定することができ、インストール時には128pxのアイコンが表示されます。2009年9月中旬時点では、アイコンが使用されるのはインストール時のみのようですが、今後 chrome://extensions/ ページなどでも使用されると思われます。

iconsの記述
  "icons": {
    "128": "sbm_icon_128.gif",
    "64": "sbm_icon_64.gif",
    "32": "sbm_icon_32.gif",
    "16": "sbm_icon.png"
  },

国際化(i18n)

i18n(Internationalization⁠⁠ APIはリファレンスにはchrome.i18n.getAcceptLanguagesしかありませんが、一部manifest.jsonの国際化にも対応しています。_localesというフォルダを作成し、その中にさらにja、en_USのように言語ごとのフォルダを作成し、その中にmessages.jsonという名前でjsonファイルを作成します。その中身は下記の通りです。

ja/messages.jsonの記述
{
  "chrome_extension_name": {
    "message": "SBMカウンタ"
  },
  "chrome_extension_description": {
    "message": "ソーシャルブックマークでのブックマーク数を表示するGoogle Chrome拡張です"
  }
}
en_US/messages.jsonの記述
{
  "chrome_extension_name": {
    "message": "SBM Counter"
  },
  "chrome_extension_description": {
    "message": "Social Bookmark Counter Extension"
  }
}
manifest.jsonでのデフォルトの指定
  "default_locale": "en_US",

言語設定を日本語にしていれば、 chrome://extensions/ ページでの拡張の名前と説明が日本語が表示されます。対応するlocaleがない場合、manifest.jsonで指定するデフォルト指定が使用されます。ただし、執筆時点でこの動作が確認できたのはChromiumのDeveloper Buildの26664です。dev版ではこの記事が公開された時点では利用できていない可能性があります。

Mole API

こちらもまだドキュメントが揃っていないAPIですが、Moleと呼ばれるToolstripsの拡張APIがあります。Moleの呼び出し方は2通りあり、1つはToolstrip APIから呼び出します。

chrome.toolstrip.expandによるMoleの呼び出し
chrome.toolstrip.expand({height:300,url:'config_on_mole.html'},function(){})
図3 Moleの表示画面
図3 Moleの表示画面

このように、chrome.toolstrip.expandメソッドでToolstrip領域の上にhtmlを表示することができます。このMoleは設定画面などに使うのにちょうど良さそうです。Moleを閉じる際はExtension名の部分図3ではSBMカウンタの部分)をクリックすると閉じることができます。

manifest.jsonでのMoleの指定
  "toolstrips": [
    {
      "mole": "config_on_mole.html",
      "mole_height": 300,
      "path": "toolstrip.html"
   }
  ],

このように、manifest.jsonでMoleを指定することもできます。この場合、Toolstripにマウスを乗せた際に表示されるExtension名をクリックするとMoleが表示されます。ただし、執筆時点ではMoleはChromeのクラッシュを起こしやすい模様です。

show-extensions-on-top

--show-extensions-on-topという起動オプションをつけてChromeを起動すると、Toolstripsをブックマークバーと統合することができます。Toolstripsで表示領域が減ることを気にされている方には朗報といえます。ただし、2009年9月中旬時点では--show-extensions-on-topとMoleを組み合わせるとほぼ確実にクラッシュするので要注意です。バグの詳細はIssue 21271 - chromium - --show-extensions-on-top: moles don't work - Project Hosting on Google Codeで確認いただけます。

executeScript API

こちらも本稿の執筆時点ではdev版には実装されていないAPIです。動的にJavaScriptを実行・CSSを適用するAPIで、使い勝手のよいAPIになる見込みです。JavaScriptを実行するchrome.tabs.executeScriptとCSSを適用するchrome.tabs.insertCSSの2つのメソッドからなり、それぞれファイルをソースとして使用することも、コード片を使用することもできます。

chrome.tabs.executeScriptのサンプルコード
  chrome.tabs.executeScript(tabId, {file:'sample.js'},function() {
  }));
  chrome.tabs.insertCSS(tabId, {code:'a img{border:none;}'},function() {
  }));

なお、上記のtabIdは省略可能で、省略した場合はアクティブなタブで実行されます。また、executeScript・insertCSS自体はそのスクリプトの実行が成功したか否かをBooleanで返します。

Mac版、Linux版

Google ChromeのMac版、Linux版について少しだけ触れておきます。現在、どちらもdev版がリリースされており、Extensionsを含めて実際に試すことができるようになっています。予定では、Chrome 4のbeta版はMac版とLinux版もリリースされる見込みとなっています。また、Mac版、Linux版の正式リリースはChrome 4になる見込みです。

なお、Mac、Linux版のChromeではExtensionsのパッケージングが未実装Issue 20668Issue 20669ですが、crxmakeを使用してExtensionsの開発を行うことが可能です。

最後に、今回の特集で作成したSBMカウンタをパッケージしたファイルを下記に置きました。今後、こちらのファイルを自動更新の対象として更新しますので、是非ご利用頂ければと思います。

SBM Counter

まとめ

4回にわたって、Google Chrome Extensionsの仕様・実装を追ってきましたが、いかがでしたでしょうか? やや急ぎ足になってしまったかもしれませんが、最初の特集としては十分な内容を盛り込めたのではないかと思っています。また、ExtensionsはChrome 4でのリリースに向けて、急ピッチで開発が進んでいます(余談ですが、RSS Extensionがデフォルトで搭載される予定もあります⁠⁠。今回の内容はもしかするとすぐさま過去のものになってしまうかもしれません。今後もできる限り最新の情報を提供していきたいと思っておりますので、その際はまたよろしくお願いいたします。それでは、短い間でしたがお付き合い頂きまして誠にありがとうございました。

おすすめ記事

記事・ニュース一覧