続・先取り! Google Chrome Extensions

第1回Chrome ExtensionsのAPI#1

こんにちは、ALBERTの太田です。今回からChrome4のリリースまでの間、Google Chrome Extensionsの詳細を追っていきたいと思います。なおタイトルの通り、この連載は先取り! Google Chrome Extensionsの続編となっています。

まず、重要なお知らせがあります。前回の特集で何度か使用していたToolstripsとMoleがベータリリースまでに削除されることが決まりました。その代わりとして、今回解説するBrowser Actionsが利用できるようになりますImportant: Toolstrips being removed in favor of browser actions⁠。また、Browser Actionsに合わせて、第3回で解説したPage Actions API新バージョンが提案されています。こちらも詳細は追って解説します。

そして、Chromeの今後の開発スケジュールが公開されました。こちらによると(2009年10月15日時点で⁠⁠、2009年10月末にBeta版の1回目のリリースがあり、12月10日に再度beta版の更新が行われ、最終的に2010年1月12日にChrome 4の正式リリースとなる予定が立っています。

それでは、今回はChrome ExtensionsのAPIの中から、Browser Actions APIとTabs APIの使い方を見ていきます。

Browser Actions API

Browser Actions APIはツールバーにボタンを追加するAPIです。⁠2009年10月12日時点では)まだドキュメントはなく、提案段階のドキュメントのみですが、実装はかなり進んでおりWindowsのdev版でその機能を試すことができます。

最初に、実装案のモックを見てみます。

図1 Browser Actionsのモック
図1 Browser Actionsのモック

このようにOmniBox(アドレスバー)の隣に任意のボタンを追加できます。FirefoxのAdd-onでもよく使用される機能です。ツールバーの領域には限りがあるので、解決策として図1のように数が多い場合は隠れるようになる予定です(Windowsのクイック起動とよく似ています⁠⁠。このBrowser Actionsの使い方と詳細を解説します。

Browser Actionsの使い方

では、サンプルを見ていきます。Browser Actionsを使用するには、例によってまずmanifest.jsonにその定義を記述します。

なお、今回はChromiumのリポジトリにある、Browser Actionsの印刷ボタンをサンプルとして使用します。

manifest.jsonでのbrowser_actionの定義
{
  "name": "Print this page browser action extension",
  "description": "This extension adds a print button to the toolstrip",
  "version": "1.0",
  "background_page": "background.html",
  "permissions": [
    "tabs", "http://*/*", "https://*/*"
  ],
  "browser_action": {
      "default_icon": "images/icon19.png",
      "default_title": "Browser Action"
  }
}

browser_actionというプロパティに、default_title(マウスオーバー時に表示されるテキスト)とdefault_icon(ボタンとして使用される画像)を指定しています。default_iconは必須です。この記述だけでボタンの設定は完了です。続いて、ボタンをクリックした際のアクションをBackground Pagesで定義します。

Background Pageでのbrowser_actionの定義(Script部分のみ)
chrome.browserAction.onClicked.addListener(function(tab) {
  var action_url = "javascript:window.print();";
  chrome.tabs.update(tab.id, {url: action_url});
});

このように、chrome.browserAction.onClickedでボタンがクリックされた際のアクションを定義できます。これでボタンをクリックしたら現在選択しているタブを印刷するシンプルな機能を実現できます(印刷ではなく印刷プレビューであればもっと実用的なのですが、今のところ印刷プレビューを呼び出すAPIは用意されていないようです⁠⁠。

Badgeの使い方

Browser ActionsにはBadgeと呼ばれるボタン上にテキストを表示する機能があります。このBadgeの使い方を見てみます。

Badgeの操作
chrome.browserAction.setBadgeText({text:"111", tabId:tab.id});
chrome.browserAction.setBadgeBackgroundColor({color:[190, 190, 190, 230], tabId:tab.id});
chrome.browserAction.setIcon({path:'icon.png', tabId:tab.id});
chrome.browserAction.setTitle({title:'MyButton', tabId:tab.id});

このように、Background PagesやToolstripsなどの拡張コンテキストからchrome.browserActionのメソッドとして、setBadgeText、setBadgeBackgroundColor、setIcon、setTitleが使用できます。それぞれtabIdを指定することでタブごとに値を設定することが可能です。

setBadgeTextは4文字以内のテキストを指定します(2009年10月12日時点では、数値を渡すとエラーになるので注意が必要です⁠⁠。setBadgeBackgroundColorはBadgeの背景色を配列で指定します。赤、緑、青、透明度の順となっており、CSSなどで使われるRGBAです。

ssetIconはアイコン画像のパス、もしくはImageDataを渡すことができます。ImageDataを渡すことができるので、canvasを使って動的に作ったイメージをボタンにすることが可能です。

Popupの使い方

Popupは図2のモックのように、Browser Actionのボタンをクリックした際に任意のHTMLを表示するAPIです。

図2 Popupのモック
図2 Popupのモック

こちらもmanifest.jsonでHTMLのパスを指定します。

Popupの定義
  "browser_action": {
      "default_icon": "icon.png",
      "default_title": "Popup Sample",
      "popup": "popup.html"
  }

Browser Actionsを使ったExtensionの作成

それでは、Browser ActionsのBadgeを使った簡単なExtensionを作成してみます。Badgeは何かの件数を表示するのに適しており、公式のサンプルとしてGmail checkerなどが存在します。そこで、今回は手軽なAPIが存在するlivedoor Readerの未読件数を表示するExtensionを作成します。

livedoor Readerの未読件数API

livedoor Readerには未読件数を返すシンプルなAPIが存在します。具体的には http://rpc.reader.livedoor.com/notify?user=USERNAME というURLにリクエストを投げると、未読件数がテキストで返ってきます。

未読件数へのアクセス
var API = 'http://rpc.reader.livedoor.com/notify?user=';
function check(){
  var xhr = new XMLHttpRequest();
  var url = API + UserName;
  xhr.open('GET', url, true);
  xhr.onload = function(){
    var res = xhr.responseText.substring(1);
    var count = parseInt(res, 10);
    if (count > 0) {
      update(String(count));
      setTimeout(check, 60 * 1000);
    } else {
      update('!');
    }
  };
  xhr.onerror = function(){
    update('!');
  };
  xhr.send(null);
}

未読件数のテキストは |100|| という形で返ってきます。先頭の|を取り除けば、parseIntが使用できるので、substringで先頭の文字だけ取り除いています。また、件数取得が成功していたら、setTimeoutで1分間置いて取得を繰り返しています。

続いて、Badgeの更新処理を見ていきます。Badgeは4文字までしか表示できないので、未読件数が5ケタになる場合は指数表記による短縮表記になるように実装しました。

Badgeの更新処理
var LIMIT = 4;
var LIMIT_COUNT = Math.pow(10, LIMIT);
function update(count){
  if (parseInt(count,10) >= LIMIT_COUNT) {
    count = abbr_exp_notation(parseInt(count,10), LIMIT+1);
  }
  chrome.browserAction.setBadgeText(count);
}
function abbr_exp_notation(count, i){
  if (count < Math.pow(10, i)) {
    var _i = i-2;
    return Math.floor(count / Math.pow(10, _i)) + 'e' + _i;
  } else {
    return abbr_exp_notation(count, ++i);
  }
}

JavaScriptにおいて、45e3は45000(45×(10の3乗⁠⁠)を意味します。これは主にコンピューター上でよく用いられる表記で、一般的ではありませんが、4文字という制約からこの表記を採用しました。

Popupによる簡易設定フォーム

続いて、ユーザーIDを入力するフォームを用意します。今回はPopupを使って簡易フォームを作成します。まずはmanifest.jsonでpopupを定義します。

manifest.jsonの記述
  "browser_action": {
      "name": "LDR Unread",
      "icons": ["ldr_favicon.png"],
      "popup": { "path": "popup.html", "height": 40 }
  }

中身となるHTML/CSSを用意します。

PopupのHTML
<div>
  <input type="text" placeholder="UserName" id="username">
</div>
PopupのCSS
body {
  overflow: hidden;
  margin: 0px;
  padding: 0px;
  background: #dadae7;
  height:38px;
  border:1px solid #000033;
}
body > div{
  padding:6px;
}
body > div > input{
  width:100px;
}

さらに、このフォームを処理するJavaScriptを用意します。

PopupのJavaScript
var BG = chrome.extension.getBackgroundPage();
var username = document.getElementById('username');
username.addEventListener('change',function(){
  BG.init(this.value);
});
if (BG.UserName) {
  username.value = BG.UserName;
}

Popup側では、chrome.extension.getBackgroundPageでBackground Pageのグローバルオブジェクトを取得し、ユーザー名が入力されたら初期化処理を行うようにしています。

最後になりますが、Background Pageでの初期化処理を記述します。

Background Pageでの初期化処理
var UserName = localStorage.ldrUserName;
if (UserName) {
  check();
}
function init(name){
  UserName = localStorage.ldrUserName = name;
  check();
}

Popupで入力されたユーザー名をlocalStorageにも保存しておき、ブラウザを終了しても設定が保存されるようにしています。

本当にシンプルですが、これでこのExtensionは完成です。インストールはこちらからどうぞ

Inter Extension Communication

Extensions同士での連携のために、InterExtensionCommunicationというAPIが用意されていますRevision 28565 以降⁠。こちらの使い方を簡単に見てみます。

Extension A から Bへのメッセージの送信
var B_id = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
var MyName = 'A';
var port = chrome.extension.connect(B_id, {name: MyName});
port.postMessage({message: 'Bさん、こんにちは'});
port.onMessage.addListener(function(msg) {
  console.log(msg);
  port.disconnect();
});

まず、メッセージを送信したいExtensionのIDを把握する必要があります。これは chrome://extensions/ などで確認できます。そのIDに対してchrome.extension.connectでコネクションを作成します。このときnameで名前を指定しておくと、受け取り側で誰からのメッセージか判断しやすくなります。

Extension Bでの、Aからのメッセージの受信とAへの返信
var A_name = 'A';
chrome.extension.onConnectExternal.addListener(
function(port) {
  port.onMessage.addListener(function(msg, info) {
    if (info.name === A_name) {
      port.postMessage({message: 'Aさん、こんにちは'});
    }
  });
});

続いて、メッセージの受け取る側の処理です。chrome.extension.onConnectExternalでほかのExtensionsからの接続を受け、メッセージを受信できる状態にします。メッセージを受け取った際の第2引数(ここではinfoという名前)には、メッセージを送信した側のExtensionの情報(idなど)が入っています。

Tabs/Windows APIの操作

最後に、Tabs/Windows APIの汎用的な操作をコードスニペットとしてまとめます。多くの関数はTabオブジェクトを引数として渡します。TabオブジェクトはTabs APIのgetgetSelectedなどのほか、Content Scriptからメッセージを受け取った際の第2引数のtabプロパティからも取得することができます。

Tabs/Windows操作のコードスニペット
//新しいタブを現在のタブの隣に開く
function open_tab(tab){
  chrome.tabs.create({
    index:tab.index+1,
    url:'chrome://newtab/',
    selected:true
  });
}
//新しいタブをバックグラウンドで現在のタブの隣に開く
function open_tab_background(tab){
  chrome.tabs.create({
    index:tab.index+1,
    url:'chrome://newtab/',
    selected:false
  });
}
//新しいタブを右端に開く
function open_tab_last(){
  chrome.tabs.create({
    url:'chrome://newtab/',
    selected:true
  });
}
//新しいタブをバックグラウンドで右端に開く
function open_tab_last_background(){
  chrome.tabs.create({
    url:'chrome://newtab/',
    selected:false
  });
}
//空白のタブを現在のタブの隣に開く
function open_blank_tab(tab){
  chrome.tabs.create({
    index:tab.index+1,
    url:'about:blank',
    selected:true
  });
}
//空白のタブを右端に開く
function open_blank_tab_last(){
  chrome.tabs.create({
    url:'about:blank',
    selected:true
  });
}
//空白のタブをバックグランドで現在のタブの隣に開く
function open_blank_tab_background(tab){
  chrome.tabs.create({
    index:tab.index+1,
    url:'about:blank',
    selected:false
  });
}
//空白のタブをバックグランドで右端に開く
function open_blank_tab_background(){
  chrome.tabs.create({
    url:'about:blank',
    selected:false
  });
}
//現在のタブを閉じる
function close_tab(tab){
  chrome.tabs.remove(tab.id);
}
//新しいウィンドウを開く
function open_window(){
  chrome.windows.create({url:'chrome://newtab/'});
}
//新しいウィンドウを空白ページで開く
function open_blank_window(){
  chrome.windows.create({url:'about:blank'});
}
//現在のウィンドウを閉じる
function close_window(tab){
  chrome.windows.remove(tab.windowId);
}
//現在のタブの右隣のタブを選択
function right_tab(tab){
  chrome.tabs.getAllInWindow(tab.windowId, function(tabs){
    tabs.forEach(function(_t,i){
      if (_t.id === tab.id){
        var newtab = tabs[i+1] || tabs[0];
        if (newtab){
          chrome.tabs.update(newtab.id, {selected:true});
        }
      }
    });
  });
}
//現在のタブの左隣のタブを選択
function left_tab(tab){
  chrome.tabs.getAllInWindow(tab.windowId, function(tabs){
    tabs.forEach(function(_t,i){
      if (_t.id === tab.id){
        var newtab = tabs[i-1] || tabs[tabs.length-1];
        if (newtab){
          chrome.tabs.update(newtab.id, {selected:true});
        }
      }
    });
  });
}
//右端のタブを選択
function last_tab(tab){
  chrome.tabs.getAllInWindow(tab.windowId, function(tabs){
    var newtab = tabs[tabs.length-1];
    if (newtab){
      chrome.tabs.update(newtab.id, {selected:true});
    }
  });
}
//左端のタブを選択
function first_tab(tab){
  chrome.tabs.getAllInWindow(tab.windowId, function(tabs){
    var newtab = tabs[0];
    if (newtab){
      chrome.tabs.update(newtab.id, {selected:true});
    }
  });
}
//現在のタブ以外を閉じる
function close_other_tabs(tab){
  chrome.tabs.getAllInWindow(tab.windowId, function(tabs){
    tabs.forEach(function(_t,i){
      if (_t.id !== tab.id) {
        chrome.tabs.remove(_t.id);
      }
    });
  });
}
//現在のタブより右のタブを閉じる
function close_right_tabs(tab){
  chrome.tabs.getAllInWindow(tab.windowId, function(tabs){
    tabs.reverse().some(function(_t,i){
      if (_t.id !== tab.id) {
        chrome.tabs.remove(_t.id);
      } else {
        return true;
      }
    });
  });
}
//現在のタブより左のタブを閉じる
function close_left_tabs(tab){
  chrome.tabs.getAllInWindow(tab.windowId, function(tabs){
    tabs.some(function(_t,i){
      if (_t.id !== tab.id) {
        chrome.tabs.remove(_t.id);
      } else {
        return true;
      }
    });
  });
}
//現在のタブを複製する(ただし、履歴は引き継げない)
function clone_tab(tab){
  chrome.tabs.create({index:tab.index+1,url:tab.url});
}

このようにどの操作もかなりシンプルに記述できます。タブの選択を切り替えたい場合はchrome.tabs.updateでselected:trueだけを送る点だけは少しわかり難いかもしれません。

そのほか、JavaScriptのTipsとして、配列の走査を途中で中断したい場合はArray.someを使用して、return trueをするとそこで走査を止めることができます(このとき、someはtrueを返します⁠⁠。

まとめ

今回は前回の特集記事以降に実装されたBrowser Actions APIを中心に、Inter Extension Communication APIと、よく使うTabs APIの操作方法をまとめました。次回はExtensionsから少し離れ、User Scriptsについて解説を行う予定です。

おすすめ記事

記事・ニュース一覧