Chrome拡張の作り方とウェブ標準との付き合い方
Shogo Ohta, 2010-9-27
stable(安定版)、beta(テスト版)、dev(開発版)の3つのチャンネルとCanary buildと呼ばれるリリースがある
Early Access Release Channels - The Chromium Projectsからインストール可能。
安定版は名前の通り、一般ユーザーが使用するリリース、(セキュリティフィックスを除いて)6週間おきのリリースを目標
テスト版は、主に安定版のリリース前に、安定化のためにリリースされる、時期によってはstableと同時に更新される
開発版は、安定版のリリース後、次のバージョンに向けた新機能追加を主目的(そのため不安定になることも多々)にリリースされる、1週間に1回程度のリリース
Canary版はChromeとは別のアプリケーションとしてインストールされる。Chromiumから自動的に作られているので、誰もテストしていない状態でアップデートされる。実験的なバージョンなのでデフォルトブラウザにすることはできない。
| WebKit | Chromium |
| Safari | Google Chrome |
Chromeではデベロッパーツールという名前
いわゆるFirebug
HTML/CSS/JavaScriptで作る、ブラウザをもっと便利にするモノ
とにかく作るのは簡単、でも実現が難しいことも多い
APIはまだまだ少ないが、バックグラウンド処理、クロスドメイン通信、データの永続化だけで多くのことが可能
拡張コンテキスト、コンテントコンテキスト、ページコンテキストの3つのコンテキストが存在し、それぞれは完全に分かれているので、お互いが干渉してしまうことはない。さらに、拡張同士も独立したコンテキストで実行される。
拡張コンテキストはタブ操作やクロスドメイン通信などの特権を実行でき、コンテントコンテキストと通信したり、スクリプトを実行したりといったことができます。
コンテントコンテキスト(Content Scripts)は特権を持っていませんが、読み込んだページのDOMを操作することができ、拡張コンテキストと相互に通信できます。
ページコンテキストは通常のウェブページで実行されるコンテキストで、コンテントコンテキストとはDOM経由でしかやり取りできませんし、拡張コンテキストとは完全に分断されています
「悪意のあるページで拡張の特権を利用されてしまう」といったことが起こりにくい仕様になっているが、開発者から見ると面倒に感じる部分も…
拡張を定義するjsonファイル。名前、バージョンのほか、アイコンや拡張の持つ権限、Content Scriptsを実行するURLの定義など。
manifest.jsonで宣言していない機能は使用できない。
セキュリティを高めつつ、機械的にその拡張の権限を判断して、インストールするユーザーに警告を出すことができる。
Twitterのハッシュタグなどの検索結果をNotifications APIを使って通知する拡張
まず、manifest.jsonと各ファイルを用意
日本語はそのまま記述できるが、文字コードはUTF-8(BOMなし)にする必要がある。
{
"name": "Tweet Notify",
"description": "特定のキーワードが含まれるTweetをデスクトップに表示します",
"version": "0.2.0",
"background_page": "background.html",
"options_page": "options.html",
"browser_action": {
"default_title": "ハッシュタグを入力",
"default_icon": "Twitter-icon.png",
"popup": "popup.html"
},
"icons": {
"48": "Twitter-icon.png"
},
"permissions": [
"notifications",
"http://search.twitter.com/",
"http://twitter.com/"
]
}
Twitterで検索して結果をNotifications APIで表示させるなど、メイン処理を行う
var keyword = localStorage.keyword;
var CheckTime = 60;
if (!localStorage.options){
localStorage.options = JSON.stringify({auto_close:true,auto_close_time:9});
}
var options = JSON.parse(localStorage.options);
var notifies = {};
var timer;
init(keyword);
window.onbeforeunload = function(){
clean();
};
function init(tag){
localStorage.keyword = keyword = tag;
clean();
keyword && start();
}
function clean(){
clearTimeout(timer);
Object.keys(notifies).forEach(function(id){
var note = notifies[id];
note.notify.cancel();
});
}
function xhr(url, callback, error){
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.onload = function(){callback(xhr);};
xhr.onerror = function(){
console.error(xhr);
if (error) error();
};
xhr.send();
}
function restart(){
chrome.browserAction.setBadgeText({text:'!'});
setTimeout(function(){
start();
}, 60 * 1000);
}
function start(){
var api = 'http://search.twitter.com/search.json';
var url = api + '?q=' + encodeURIComponent(keyword);
main(url);
function main(url){
xhr(url, function(res){
var text = res.responseText, data;
try {
data = JSON.parse(text);
} catch (e) {
console.error(e);
restart();
return;
}
if (data.results && data.results.length) {
data.results.reverse().forEach(function(item, i){
var url = 'chrome-extension://' + location.host + '/note.html#'+item.id;
var notify = webkitNotifications.createHTMLNotification(url);
notify.ondisplay = function(){
if (options.auto_close && options.auto_close_time > 0){
setTimeout(function(){
notify.cancel();
}, options.auto_close_time * 1000);
}
};
notify.onclose = function(){
delete notifies[item.id];
};
notify.show();
notifies[item.id] = {notify:notify, tweet:item};
});
chrome.browserAction.setTitle({title:String(data.results.length) + ' tweet(s)!'});
} else {
chrome.browserAction.setTitle({title:'no tweet..'});
}
var count = 0;
(function loop(){
timer = setTimeout(function(){
if (++count > CheckTime) {
main(api + data.refresh_url);
} else {
chrome.browserAction.setBadgeText({text:String(CheckTime-count)});
loop();
}
}, 1000);
})();
},function(){
restart();
});
}
}
chrome.extension.onRequest.addListener(function(message, sender, sendResponse){
sendResponse(notifies[message.id].tweet);
});
任意のハッシュタグを入力するインターフェースとして使用
<!DOCTYPE html>
<html>
<style>
body {
margin: 0px;
padding: 0px;
}
body > div{
padding:7px;
}
body > div > input{
width:100px;
}
</style>
<div>
<label for="keyword">search:</label>
<input type="search" value="" placeholder="hashtag or keyword" id="keyword">
</div>
<script>
var BG = chrome.extension.getBackgroundPage();
var keyword = document.getElementById('keyword');
keyword.addEventListener('change',function(){
BG.init(this.value);
});
if (BG.keyword) {
keyword.value = BG.keyword;
}
</script>
</html>
Notifications APIで表示される小窓用のHTML
<!DOCTYPE html>
<html>
<style>
body{
font-family:sans-serif;
color:#333;
}
a{
color:#003366;
text-decoration:none;
}
#prof{
float:left;
max-width:48px;
min-width:48px;
}
</style>
<a id="user" target="_blank"><img id="prof"></a>
<a id="tweet" target="_blank"></a>
<script>
chrome.extension.sendRequest({id:location.hash.slice(1)}, function(item){
document.querySelector('#user').href = 'http://twitter.com/' + item.from_user;
document.querySelector('#prof').src = item.profile_image_url;
var tweet = document.querySelector('#tweet');
tweet.href = 'http://twitter.com/' + item.from_user + '/status/' + item.id;
tweet.textContent = item.text;
});
</script>
</html>
オプション設定用html
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<title>Tweet Notify Options</title>
<style>
body {
margin: 0px;
padding: 0px;
}
body > div{
padding:7px;
}
body > div > input{
width:100px;
}
</style>
<p><input type="checkbox" id="auto_close"><label for="auto_close">自動的に閉じる</label>
<p><input type="number" id="auto_close_time" min="0" max="30" step="1" value="9"><label for="auto_close_time">閉じるまでの時間(秒)</label> (<span id="auto_close_time_output">9</span>秒)
<script>
var options = chrome.extension.getBackgroundPage().options;
var auto_close = document.getElementById('auto_close');
auto_close.onchange = function(){
options.auto_close = this.checked;
};
if (options.auto_close) {
auto_close.checked = options.auto_close;
localStorage.options = JSON.stringify(options);
}
var auto_close_time = document.getElementById('auto_close_time');
var auto_close_time_output = document.getElementById('auto_close_time_output');
auto_close_time.onchange = function(){
auto_close_time_output.textContent = options.auto_close_time = this.value;
localStorage.options = JSON.stringify(options);
};
if (options.auto_close_time) {
auto_close_time.value = options.auto_close_time;
auto_close_time.onchange();
}
</script>
</html>
Twitterのアイコン
BrowserAction、PageActionにはアイコンが必須
以上のファイルを1つのフォルダ(もちろん階層わけしてもOK)に置き、拡張機能ページで「デベロッパーモード」選択し、パッケージ化されてない拡張の読み込みを行います
TweetNotifyから実際にインストールできます
最新の情報は、Web Platform Status (The Chromium Projects)にまとまっています
Chrome拡張はHTML/CSS/JavaScriptで作る
ベースとなるのはJavaScriptであり、多少の専用APIはあるものの主にJavaScriptで実現可能なことに制限される
JavaScriptで実現可能なことはどんどん増えている
大抵のことはブラウザで完結する→Chrome OS
本来ならHTML5は仕様であり、それ以上の意味を持たないが、「HTML5」という言葉は場合によってより大きい意味で使われている
いわゆるバズワードとしてのHTML5
バズワードとして使われることは決して悪いことではない。むしろ、HTML5が成功するかどうかはそういった幅広く普及するかどうかに掛かっているはず