たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
既にご存じの方もいらっしゃるかとは思いますが、オライリーから「Firefox Hacks Rebooted」という本が出ました。3年前に出た「Firefox 3 Hacks」の続き……でもないのですが、似たようなコンセプトで「今」の話をまとめた本です。
本1冊を通して1つのストーリーに基づいて何かを体系立てて解説する、という本ではなくて、各著者が自分の得意分野で好きなように書きたい事を書きまくった本、というのが実情を正しく言い表している気がします(元々、オライリーの「○○ Hacks」というタイトルは「なんでもあり」のブランドなんだそうで……その言葉に甘えてしまいました)。
以下、各章の対象読者層と思われる層、内容の簡単な紹介と、定量的に傾向を把握するための手がかりとしてページ数と全体に対する割合を表にしてみました。これを見ると一発で分かりますが、僕(4章と6章の一部を担当)の暴走が著しいですね。やばい。
章 | 主な対象読者層 | 内容 | ページ数 | 全体の中でのパーセンテージ |
---|---|---|---|---|
1章 | エンドユーザ | Firefoxの新機能紹介など基本的な事。Firefox 3.6とFirefox 4以降との間で何が変わったのかのまとめ。 | 54 | 11% |
2章 | 開発者寄りのユーザ | VimperatorとKeySnailの解説。と、Twitter用アドオンの解説。ブラウザとしてのFirefoxをガンガン使いこなしていく人向け。 | 42 | 9% |
3章 | アドオン開発者 | Add-on SDKを使ったアドオン開発のチュートリアル。アドオンの開発をこれから始めたいという人向け。 | 68 | 14% |
4章 | アドオン開発経験者 | Bootstrapped Extensions、ChromeWorker、e10sなど、Add-on SDKよりも下のレイヤの技術の解説。今既にアドオンを開発してるという人や、Add-on SDKではできない・標準ライブラリでカバーされてない範囲の事に手を出そうと思ってる人向け。 | 156 | 33% |
5章 | Webデベロッパー | Firefoxで利用できるHTML5関連技術やECMAScriptの紹介。Webデベロッパー(主にフロントエンド)向け。 | 98 | 21% |
6章 | アドオン開発経験者、Webデベロッパー | ハードウェア寄りの話と、その他のこぼれ話。アドオンやWebアプリの開発で、ネイティブアプリ並の事をやりたい人向けの話が多め。かも。 | 56 | 12% |
自分の担当箇所だけ細かく見てみると、ページ数と全体に対する割合はこんな感じです。
調子に乗って脳汁ドバドバ出して書きまくる→ページ数が増える→値段上がる→損益分岐点も上がる という危険なコンボが発動してしまったためかお値段高めとなっておりますが、自分の担当箇所も他の方の担当箇所もひっくるめて、腰を据えて読むに値する情報ばかりなんじゃないかなーと思っております。Webで難なく読める文章の長さと、(紙でも電子でも)書籍の形になってた方が読みやすい文章の長さって、やっぱり違うと思いますしね。
本書のサポートサイトも公開されており、正誤表や本文中の各コードリストのダウンロード、本文の一部を切り出したサンプルPDFの無償公開などがあります。正誤表等はこれから更新されていくと思いますので、本書をお読みになられる際はこちらのサイトも併せてご参照いただければ幸いです。
以下は、思い出話です。
本書の企画がスタートしたのは、Firefox 4の正式版が出る前の2010年5月頃だったと思います(今メールボックスの一番古いメールの日付を見て確認した)。当時はまだ内部バージョンがFirefox 3.7とか言われていた頃で、Aero Glassが標準で入るのか?とか、そんな事を言ってた時期でした(という事も今Wikipediaを見て確認した)。
それからしばらく、どんな内容にするのがいいか? どんな人に執筆を依頼しようか?(今著者の一覧に名前が載ってる方の中には、最初の時点で名前が挙がってた著者の側から「是非この人にも書いてもらいたい!」と引っ張り込んだ人もいるのです) という事を話し合っていて、執筆者が確定したのが8月。そこからぼちぼち書き始めて、なんやかやで1年経ってしまいました(普通はここまで難産にはならないみたいです)。その間にFirefoxのバージョン番号もどんどん上がっていって、もうすぐFirefox 8ですよ奥さん! 時間が過ぎるのは本当に早いものですね。
最初の頃は、自分は「Firefox 3 Hacksで書いた内容をFriefox 4時点での情報に基づいてリライトするか?」程度の事を考えていました。でも、それじゃあ書いてる自分がつまらないし、それこそすぐに陳腐化してしまいます。それでうんうん唸りながら調べていくうちに、Firefox 4以降の話で新しい基盤の技術になりそうなトピックがいくつかあるという事が分かってきたので、それじゃあってんで、そこをターゲットにして書いていく事にしました。実は、自分の担当箇所は半年くらい前にほぼ完成してたんですよね。そこから本書の発売までの間は、(他の事に時間を使ってたからというのもありますが、)細かい記述のアップデート程度しかやってません。高速リリースになってからガンガン方針が変わったり実装が変わったりしてて、本書に書いた「テクニック」の中にも、既に「もうこんな回りくどい事しなくてもいいんだけどなぁ」な感じになってしまった話題がいくつかあるにはあるのですが(校正の詰めの詰めに入った段階でまたどどっと変更が入ってきて、どーしても間に合わなかったんです……)、全体としてはちゃんと今でも、そしてこれからも通用する話になってると思います。あの時の自分の目の付け所は、そんなには間違ってなかった、はず。
紆余曲折があった末にようやく世に出る事ができた本書ですが、アドオン開発者の方の手助けとなり、また、アドオン開発に手を出してみようと考えている方の手引きとなれば、幸いです。
他の著者の方々による本書の紹介のエントリ:
Firefox Developers Conference 2010の時に再起動不要なアドオンの作り方を調べた時、調査の成果を元にJetpack SDKもといAdd-on SDKを使わずにFirefox 3.6とMinefieldの両方に対応したBootstrapped Extensionを作るためのテンプレを作った。それ以後、新しく作るアドオンでBootstrapped Extensionにできそうな物はなるべくそのように開発していこうと思ってて、Back to Owner Tabは実際そうして作ったアドオンの第1号ということになる。
SDKの使い方や仕組みを調べた時にもちょっと思ったんだけど、Back to Owner Tabを作って、「設定ダイアログを持てないのがこんなに辛いとは……」ということを改めて感じた。しかしBootstrapped Extensionで設定ダイアログを提供するのは簡単には実現できなさそうだったので、ずっと諦めてた。
今回、うまく解決できそうな方法をふと思いついて、実際試してみたら案外うまくいったので、テンプレの一部としてライブラリ化してみた。Back to Owner Tabの設定ダイアログもこれで提供してる。
Firefox 2以降ではXULにprefwindowというウィジェットが存在していて、これはFirefox自体の設定ダイアログにも使われている物で、非常に出来がよい。今ある拡張機能の多くも、これを使って設定ダイアログを提供している。
prefwindowができるまでは「設定値を保持して、変更を監視して、変更があったらUIに反映して、ダイアログがキャンセルされたら変更を破棄して……」てな事をいちいち考えてゼロから設定UIを設計しなきゃいけなかったから、非常に辛かった。そういう思いがあるので、Bootstrapped Extensionでも是非これを使いたかったんだけど、Bootstrapped Extensionの「Chrome URLを登録できない」という制限のせいで無理だった。
どうしてもやりたければ、XUL以外の方法で頑張って設定UIを作るか、XULで物凄く苦労して作るしか無いってことになって、それはどっちも嫌だった。せっかくprefwindowという素晴らしい物が目の前にあるのに、それを使わないでオレオレUIをゼロから作るなんて、馬鹿馬鹿しくてやってられない。
せめて最新のSDKだったら設定ダイアログを作る機能が入ってたりしないだろうか? あるんならそれを丸パクリできないかな? と思ってたんだけど、SDKに設定ダイアログのためのAPIが入るのはまだ先のことらしいと聞いて、それも諦めざるを得なかった。
それで仕方なく「about:configでカスタマイズしてね」ということにしてたんだけど、まあ普通に考えてこれはエンドユーザ向けとしては酷い話なわけですよ。自分で使う時も、マウス操作だけで設定を変えられないのは困る。
そんな風に悶々としていて、ふと、「そういえばuserChrome.jsとかではdata: URIでXULドキュメントを動的に作ってたよな」ということに思い至った。試しにdata: URIにprefwindowで作った設定ダイアログの内容を突っ込んでnsIWindowWatcherのopenWindow()
でウィンドウとして開いてみたら、少なくともMinefield 4.0b11preでは正常に動いてくれた。data: URIでドキュメントを読み込んだりウィンドウを開いたりする時はオープン元のスクリプトの権限が引き継がれるっぽいので、file: URLでXULを読み込ませた時のような問題も起こらないようだ。
また、E4Xを使えばJavaScriptの中に直接XMLを書けて、しかも属性値にJavaScriptの変数を埋め込める。これなら、propertiesファイルベースでのローカライズでもラベル等を埋め込むのが苦にならない。で、そうして作ったE4XのXMLオブジェクトをtoXMLString()
すれば、data: URIで読み込ませるダイアログのためのソースコードが得られる。
ということで、「XULのソースコードを文字列として受け取ってdata: URIにしてウィンドウを開く機能」と「アドオンマネージャでアドオンの設定を開くボタンが押下された時に前述の機能を呼び出す機能」を作れば、Bootstrapped Extensionでもprefwindowベースの設定ダイアログを提供できる事が分かった。
propertiesファイルの内容はローカライズ済みの文字列をXULのstringbundle要素を介して取得する方法が一般的だと思うけど、より低レベルな実装のnsIStringBundleServiceを直接呼べば、JavaScriptコードモジュールやBootstrapped Extensionの中からでもpropertiesファイルの中身を簡単に取り出せる。(stringbundle要素はXBLでそういう処理を行うようにしてあるに過ぎない。)
上記のテンプレには、nsIStringBundleServiceをラップしてstringbundle要素と同じインターフェースで使えるようにするライブラリを既に入れてある。
var bundle = require('path/to/lib/locale.js')
.get('file://.../messages.properties');
var title = bundle.getString('config.title');
という風にすると、ブラウザのUIの言語がjaでmessages.properties.jaというファイルがあればそれが使われて、存在しない場合はmessages.properties.en-US→messages.properties(サフィックス無し)とフォールバックしていく。
Back to Owner Tabに入ってるバージョンのライブラリには含まれてないけど、テンプレのHEADでは resolve('path/to/file')
とすると現在のファイルを起点にして相対パスを解決できるようにしてあので、こういう風にも書ける。
var bundle = require('path/to/lib/locale.js')
.get(resolve('locale/messages.properties'));
なお、Bootstrapped Extensionの場合は読み込んだpropertiesファイルの内容がキャッシュされたままになってしまうとまずいので、shutdown()
の時にはnsIStringBundleServiceのflushBundles()
というメソッドを呼んで、メモリ上のキャッシュを消すようにする必要がある。このライブラリでもそうしてて、自分でnsIStringBundleServiceを使う時はここに気をつけないといけない。
Bootstrapped ExtensionやJavaScriptコードモジュールの名前空間からで既存のDOMWindowを取れない状態でウィンドウを開きたい場合には、nsIWindowWatcherのopenWindow()
を使う必要がある。このメソッドに渡すURIとしてdata: URIを指定すれば、その内容のウィンドウがChrome権限で開かれる。
var uri = 'data:application/vnd.mozilla.xul+xml,'
+ source;
Cc['@mozilla.org/embedcomp/window-watcher;1']
.getService(Ci.nsIWindowWatcher)
.openWindow(null, uri, '_blank',
'chrome,titlebar,toolbar,centerscreen',
null);
ソース文字列の生成は、E4Xを使うと楽にできる。
var xml = <>
<prefwindow id="backtoowner-config"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title={bundle.getString('title')}>
<prefpane id="prefpane-general"
label={bundle.getString('general')}/>
</prefwindow>
</>;
var source = '<?xml version="1.0"?>'
+'<?xml-stylesheet href="chrome://global/skin/"?>'
+xml.toXMLString();
source = encodeURIComponent(source);
ラベル文字列等に日本語が入る場合を考慮して、ソース文字列はdata: URIに繋げる前にエスケープしておこう(これをしないと文字化けする)。
基本的にはこれでいいんだけど、実際このまま使うと何となく気持ち悪い。というのも、prefwindowは画面上でのウィンドウの位置や大きさ、最後に選択されていたパネルの名前等をプロファイル内のlocalstore.rdfに自動的に保存するんだけど、この時保存されるデータはウィンドウのURIをキーとして紐付けられているから、data: URIのウィンドウだとlocalstore.rdfが肥大化してしまう。しかも、設定項目を追加したりしてウィンドウの中身が変わるとdata: URIも変わるから、前回情報を保存した時のエントリとは別のエントリが作られてしまって、古いエントリは自動的には消えないから、localstore.rdfが際限なく膨らんでいってしまう事になる。長く使うことを考えたら、これはよくない。
で、何かいい方法はないか考えてみた。
document.write()
でウィンドウの中身を動的に書き換える。nsIWindowWatcherのopenWindow()
は開かれたウィンドウのDOMWindowオブジェクトを返す。なので、こんな風にできないかと最初は考えた。
var window = ww.openWindow(...);
window.document
.open('application/vnd.mozilla.xul+xml');
window.document.write(source);
window.document.close();
が、駄目だった。この方法でドキュメントを書き出すと強制的にHTMLのパーサーが使われてしまうらしく、XULのソースを書き出しても全く動作しない状態になってしまった。
次に、DOM操作でやることを考えた。
XUL Documentの中でRangeを作ってcreateContextualFragment()
すると、XULのソース文字列からDOMDocumentFragmentを作ることができる。こうしてルート要素も含めたドキュメント全体を作り直してゴソッと入れ替えたらどうなるだろうか。
var window = ww.openWindow(...);
var range = window.document.createRange();
range.selectNode(window.document.documentElement);
var fragment = range.createContextualFragment(xml.toXMLString());
window.document.replaceChild(fragment, window.document.documentElement);
実際やってみたら、これは期待通りには動いてくれなかった。多分、開かれたウィンドウの内容がまだ完全には読み込まれ切っていない状態でDOM操作を外から無理矢理やろうとしたからなんだろうと思う。
開かれたウィンドウのDOMContentLoadedを待ってからDOMを操作すればprefwindowについてはちゃんと動くみたいなんだけど、dialogとかのもっと汎用的な物も受け付ける事や、ダイアログの中にDOMContentLoadedを待って処理を開始するようなコードを含めたい時に、それでは問題が起こる気がする。
そういうわけで今のバージョンでは、ウィンドウを開く時にnsIVariantの形でソース文字列を渡して、開かれた側のウィンドウの中に1つだけ置いたscript要素でarguments[0]
を受け取ってすぐにドキュメントを書き換えるようにしてみている。要点だけ抜き出すとこんな感じ。
var variant = Cc['@mozilla.org/variant;1'].createInstance(Ci.nsIWritableVariant);
variant.setFromVariant(source);
ww.openWindow(
null,
'data:application/vnd.mozilla.xul+xml,'
+encodeURIComponent(
'<?xml version="1.0"?>\n'
+'<!-- ' + aURI + ' -->\n'
+'<?xml-stylesheet href="chrome://global/skin/"?>\n'
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"><![CDATA[
var d = document;
var e = d.documentElement;
var r = d.createRange();
r.selectNode(e);
d.replaceChild(r.createContextualFragment(arguments[0]), e);
r.detach();
]]></script>
</window>.toXMLString()
),
'_blank',
'chrome,titlebar,toolbar,centerscreen',
variant
);
URIをコメントで埋め込んでるのは、複数のアドオンでこのライブラリを使ってる時に衝突しないようにしたかったから。
実はここに辿り着くまでに結構紆余曲折があった。最初はルート要素を差し替えられるという事に気づいていなかったので、「ルート要素の属性値の情報」と「子孫要素のソース」をそれぞれ取り出してやってみていた。E4Xは実はよく分かってないからちょっと苦労した。
var content = xml.*.toXMLString(); // 子孫要素だけ抽出
var container = xml.copy(); // ルート要素だけ取り出すためにまず一旦全体を複製
var attributes = container[0].attributes(); // ルート要素の属性のリストを取得
var attributesHash = {};
for each (var attribute in attributes) { // 属性名と値のペアのハッシュにする
let name = attribute.name();
attributesHash[name] = attribute.toString(); // 属性値はtoString()で取れる
delete container[0]['@'+name]; // removeAttribute()に相当する操作はこう書く
}
// 子孫要素を削除して、コンテンツ差し替え用のスクリプトに入れ換える
delete container.*;
container.script = <script type="application/javascript">{this._loader}</script>;
container = container.toXMLString();
最初はこれでいいかと思ってたんだけど、コンテンツ差し替え用のスクリプトが走ってる時点で既にprefwindowのXBLのコンストラクタが実行されてしまっていて、複数のprefpaneがあるときの切り替え機能が働いてなかった。
ルート要素をもう一度作りなおしてドキュメントに挿入すればXBLのコンストラクタが実行されるはず、ということでdocument.removeChild(document.documentElement);
の後でdocument.appendChild(newRoot);
みたいな風な事も試してみたけど、一瞬documentElement
が無くなってしまうのがマズいようで、他のどっかの処理がルート要素を参照するタイミングとぶつかってエラーになってしまった。ここはdocument.replaceChild(newRoot, document.documentElement);
で一気に差し替えてしまうのが正解だった。昔のGeckoはreplaceChild()
でクラッシュする事があったような気がするので無意識に使用をずっと避けてきてたんだけど、Firefox 3.6以降ともなるとさすがに問題なく動いた。
あと、生成した新しいルート要素を挿入しようとするとエラーになる場合もあって、その時は挿入しようとしているノードをdocument.importNode()
に1回通してやれば動いた。まあ最終的にはそれ無しで乗り切れたんだけど。
これも難儀した。
Firefoxのアドオンマネージャは、install.rdfのoptionsURL
に書かれてるURIをwindow.openDialog()
で(Mac OS X以外では)モーダルダイアログとして開くようになっている。しかし前述の通りXULの設定ダイアログを読み込ませたかったらChrome URLにしないといけないので、これはそのままは使えない事になる。
とはいえ、optionsURLにはChrome URLしか使えないという話ではない。
最初は、ここに直接javascript:ダイアログを開くためのコード
という感じのスクリプトを書く事を考えてみた。でもメタデータの置き場所であるinstall.rdfにそういう実装を含めるのはどうかと思うし、そもそもここにjavascript:スキームを使って書いたスクリプトは普通にコンテンツ領域の権限でしか実行されないようなので、nsIObserverServiceを使ってイベントを発行するとかそういう高度な事は全然できなかった(実際試して無理だった)。
次に、nsIWindowWatcherでの監視とかnsIObserverServiceを介して送られるchrome-document-global-createdとかcontent-document-global-createdとかの通知でもって「あらかじめ登録されていたURI」が読み込まれた事を検知して、その読み込み(ウィンドウのオープン処理)をキャンセルして別の設定ダイアログを開くという事を考えた。
これは結構イイ線まで行ったんだけど、「Firefoxのアドオンマネージャが開いたウィンドウ」が一瞬だけ見えてしまうという点をどうしても回避できなかった。openDialog()
の実装を辿ってnsGlobalWindow::OpenDialog() → nsGlobalWindow::OpenInternal() → nsWindowWatcher::OpenWindowJS() → nsWindowWatcher::OpenWindowJSInternal()と掘り返して、何か抜け道はないか探しまくったんだけど、内部で新しいDOMWindowが生成されてウィンドウが実際に画面に出現する事がほとんど確定した後で初めてnsIDocShellのloadURI()
にURIが渡されてる(この間URI文字列はC++の普通の変数で引き回されてるだけでJavaScriptの層からは触りようがない)みたいで、ウィンドウがユーザの目に触れる前にどうこうってのは原理的に無理っぽい。
いや、nsIContentPolicyインターフェースを備えたXPCOMコンポーネントを作ってカテゴリマネージャに登録すれば、もしかしたらそこに割り込む事はなんとかできるかもしれない。けど、設定ダイアログ1つ開くためだけにそこまでする元気はなかったし、求める機能に対してコードが膨大になりすぎるから普通にこれはないわーって感じだしで、そういう方向性は最初から切り捨ててる。
結局、すべてのドキュメントの読み込みをchrome-document-global-createdとcontent-document-global-createdの通知で捕捉して、アドオンマネージャのウィンドウが開かれた時にはボタンのクリック操作を監視するイベントリスナを登録し、「設定」ボタンをユーザがクリックした瞬間を捕捉して、登録済みのURIだったらイベントをキャンセルして別のダイアログを開く、という所に落ち着いた。これだとアドオンマネージャの実装(現在どのアドオンが選択されているのか、そのアドオンの設定ダイアログのURIは何なのか、を取得する方法)にべったりになってしまわざるを得ないんだけど、まあその辺のレイヤだったら変更があっても追随するのはそう大変じゃなかろうと思って、妥協する事にした。
冒頭に書いたけど、ここまでのまとめがBootstrapped Extensionsテンプレートにライブラリとして入ってる。他のファイルの内容に可能な限り依存しないように作ったつもりなので、改造再利用も不可能ではないと思う。実際にどう使うのかというのはBack to Owner Tabでの利用例が参考になるかもしれない(って単にE4XでダイアログをJavaScriptのコードの中に埋め込んでるだけだけど)。
ほんとはFirefox 4に向けて記事を書いていかなきゃいけないんだけど、こういう「あれも駄目でした、これも駄目でした、結局こういう所に落ち着かざるを得ませんでした」という紆余曲折は本に入れてもウケないんじゃないかなーって思ったので、ここに書く事にした。また次に同じような事をしようと思った時に同じ轍を踏まなくてもいいように。技術情報のドキュメントは、未来の自分のために残しておく物なのです。
Developers Conferenceでの発表向けの内容に盛り込めそうにない話を書いていく。
話のテーマを決めるにあたって、とにかく最初は1つJetpack SDKでアドオンを書いてみようと思ったんですよ。
でも、「こう書くと動きますよ」って言われてもなんだか信じられなかったというか、どういう経路でどういう風にしてそのコードが読み込まれるのか分からなくて、スクリプトの書き方次第じゃ他のコードに悪影響を及ぼしたりすることもあるんじゃないのか?(prototoype.jsのObject汚染みたいな感じで)とか、名前空間の衝突は大丈夫なのか? とか、そういうどーでもいい所が気になって気になって、仕組みが分からないまま使うのは怖いと思った。何をやってよくて、何をやったら駄目なのか、というのが分からないのが怖かった。
その境目を見極めたくて、Jetpack自体の成り立ちを探ってみたのですよ。
特に謎だったのが、他のモジュールを読み込むrequire()
。これが一体どこで処理されているのか。main.js(Jetpackでアドオンを作る時の主たる実装になるファイルの固定の名前)もどこかからrequire()
されているのなら、require()
の実装を見たら謎が解けるんじゃないかなーと思った。
exports
やrequire()
はここで定義されている。
EXPORTED_SYMBOLS
を使うやり方)にも対応してるみたい。Components.utils.evalInSandbox()
が呼ばれていることが分かった。ライブラリのモジュールもmain.jsも全部これで実行される。
require("chrome")
の実態が、これ。Jetpackとは、この基本的な仕組みに基づいて、個々のモジュールの名前空間を分け、それらを安全に連携できるようにしたもの。と言える。MozLabでmodule_manager.jsというものが過去に使われていて、それを想起させられた。
module_manager.jsはJavaScriptコードモジュールが無かった頃にJavaScriptそのものの言語仕様を超えた所で高度なモジュール化を行おうとしていたらしく、渡されたモジュールの名前からパスを生成してファイルを読み込んでサンドボックス内で実行してそのグローバルオブジェクトを保持し続けて……ということを普通のJavaScriptの機能とmozIJSSubScriptLoaderでやってた。Jetpackの仕組みはそれのずっとスマートなバージョンなのかなと思った。
今までの開発手法しか知らなくてJetpackがさっぱり分からないという僕みたいな時代後れの人でも、securable-module.jsとcuddlefish.jsを起点にすれば、他のモジュールも無理なく読んでいけるんじゃないだろうか。
僕はJetpackに対していろんな意味で期待していたはずなのに、自分がJetpackでバリバリ開発する姿はいまいち想像できないでいる。Jetpackの話が出始めた頃よりも、JetpackがrebootされてSDKとなってからの方がむしろ、「あれ、なんか僕ってJetpack使ってなさそうなんじゃね?」感が強くなっていっている気がする。Jetpackは僕を幸せにしてくれるのか? くれないのか? それが分からない。
Firefox Developers Conferenceでの発表について「Jetpackからよりディープな世界へのステップアップや、あるいはその逆に、これまでの手法でアドオンを開発していた人達がJetpackにステップアップするには? という風な話題」というオーダーをもらって、ようやっと重い腰を上げてJetpack SDK(とPython)をインストールしてみた。で、とりあえずJetpackの流儀というのを理解しなきゃと思って実際にアドオンを書いてみようとしていきなり挫折して、ドキュメントを読んでみようとしたけどどこから読めばいいのかすら分からなくて頭クラクラで、ヤケクソでJetpack SDKそのもののコードを読んでみたりして、という事をやりながらモヤモヤと考えてた。何故僕はこんなにも、Jetpackに乗り切れていないのか。
Jetpack SDKのドキュメントをちょっと読んで雰囲気を眺めただけでも、僕の今までの知識はまるで役に立たないということはよく分かった。
アドオンを構成するコードは、大雑把に言うと
の2つのレイヤに分けることができる。
これまでのアドオン開発においては、2の部分の開発コストが非常に大きかった。W3CのDOMであるとかCSSであるとかのWeb標準の知識がベースにあるとはいっても、XULやXPCOMというMozilla specificな技術を覚えなければならないし、覚えた上で、さらに工夫しないといけない。はっきり言って、アドオンのコードのほとんどは2のための物で、1のためのコードなんてのはほんの一部分だけだったりする。2の部分をどうやって解決するかというのが問題の大部分を占めていて、ほとんどの人はおそらくその段階で挫折してしまって、本題である1の部分に取りかかる事すらできないのだと思う。
僕は、そこ(2のレイヤ)に膨大な時間を注ぎ込んできた。そこに時間を取られるせいでFirefox本体の開発に貢献とかそんなとこまで頭が回らないよ、というくらいの勢いで。その結果蓄積された大量のバッドノウハウこそが、今の僕の武器であり価値なんだと思う。
でも、そういうバッドノウハウはすぐに陳腐化する。また、せっかく覚えたXULやXPCOMの知識も無駄になる時がある。特に、Gecko 2.0からはすべてのインターフェースが凍結されなくなるということは、これからはnsIPrefBranch::getBoolPref()
のような頻出のインターフェースすら安心して使えなくなるという事で、かかるコストと得られる物が全然釣り合わないんじゃないのか。
という事を考えると、「だからこそJetpackなんだよ」という話は理解できる。Jetpack SDKは、2の部分をアドオン開発者の代わりにカバーするライブラリ集でもある。Gecko 2.0以降のインターフェースの不安定さをJetpack SDKが吸収して隠蔽してくれるから、開発者は2の部分のためにかける労力が要らなくなって、全力を1の部分の開発に注げるようになる。という寸法だ。1と2の両方に(特に2の部分に異常に多くの)力を注がなくてはいけないロートルの開発者と、1のことだけ考えて開発していればいい新しい開発者、どっちの方が生産性が高くてクリエイティブな結果を沢山生み出せるか。あるいは、どっちの方が労力が少なく済んで、長くメンテナンスし続けられるか。そういう話だ。
しかし、ホントにそんなにうまくいくのかな?
今でも、過去にFUELという「アドオン開発者向けの、Firefox組み込みのライブラリ」が作られて、それが「XULやXPCOMといった小難しい物を隠蔽すること」を目指していたはずなのに、仕様が十分に錬られてないわ利用者(アドオン開発者)にとっての使いやすさという視点が欠けてるわ実装もあまりにやっつけ仕事でヘビーな利用には全然耐えられないわ(普通に使うとメモリリークしまくる)で、あっという間に「ああ、そんな物もあったっけ……でも使ってる人いるの?」な位置に落ちぶれてしまった。搭載当初は何も特別な準備をしなくても使えるように作られてたのに、Firefox 4からは「コイツの初期化に時間かかるから、初期状態で使えるようにわざわざしておく必要ないよね」と「一級市民のAPI」の地位を追われてしまった。という事実がある。あの頃「FUELで状況はよくなるはずだよ!」という風な事を言って、紹介を書いたりして安易に勧めていた人達は、自分も含めて戦犯として裁かれなきゃいかんね。
そういう前例を見ると、Jetpackも、今は威勢のいいことを言っていても、これから先Firefoxの仕様が変わった時に、「JetpackのライブラリのAPIを維持してFirefoxの仕様変更を頑張って吸収する」方向ではなく「JetpackのライブラリのAPIを変えてFirefoxの仕様変更に合わせる」方向に流れてしまうんじゃないのか、「XULやXPCOMといったコロコロ変わる厄介なAPIの上に、Jetpackというこれまたコロコロ変わる新しい厄介なAPIが加わっただけ」になってしまうんじゃないだろうか、と僕は思ってしまうんだ。それじゃあただの第2のFUELだ。
そこで頑張って自分で貢献するんだよ、って言われても、そもそも前述の2の所の開発で悩まされる事から解放されるためのJetpackのはずなのに……って思うわけですよ。
また、Jetpack SDKの提供するAPIをキモく感じてしまった、というのもある。APIで提供される機能がどうこう以前に、API越しでしかFirefoxに触れないっていうことがストレスになった。
例えばタブのコンテンツ領域になってるフレームの生に近いAPIには、今までだったらgBrowser.mTabContainer.childNodes[n].linkedBrowser.docShell
でアクセスできた。べつにフツーにフツーのアドオンを作るだけだったらそんなとこ触る必要ないけど、いざというときにはそういう低レベルのAPIから裏口を叩けば何とかなるという、なんていうんだろ、安心感? みたいな? そういうのがあった。
しかしJetpackではこれが隠蔽される。APIで用意された範囲の機能にしかアクセスできない。ということは、やりたいことができそうに無かった時、今までよりもずっと手前の時点で諦めなきゃいけないんじゃないかって気がして、今すぐそれをやりたいかどうか以前に、もう、それができなくなるってだけで息苦しくて窮屈で「うああああ嫌だ嫌だ嫌だ」となってしまう。
まだJetpackがrebootされるよりも前の頃、生のXUL要素に触れるraw
というプロパティがあった事について、僕はそれを激しく非難した。そんなのがあったら将来的なAPIの互換性を保てなくなってしまうじゃないか、と。その認識は今でも変わっていない。しかし自分がいざ当事者になってみて初めて、その窮屈さ不自由さを身に染みて実感した。(僕がChromeの拡張機能に手を出そうとしなかったのは、これを本能的に避けていたからなのかもしれない。)
そもそも自分がMozillaに肩入れしてるのは何でだったのか。よく考えてみるまでもなく何度も言ってるけど、プロダクトそのものがW3CのWeb標準の技術に基づいているから、そしてそれらの技術にちゃんと対応してたから、というのが一番最初にあった理由なんだよね。
「W3CのDOM? XML? CSS3? そんなの全然Webで使えないじゃん、IEで使えない物に意味なんてないよ。」
「W3Cの仕様なんて、現実見てない頭でっかちの奴らが決めたものだろ? 名前がやたら長ったらしい(例:document.getElementById(id)
はIEのDOM0だとdocument.all.id
に相当)し、キモすぎる。」
「デファクトスタンダード(事実上の標準仕様)こそがすべてだよ。デジュールスタンダード(標準はこれです、という形で作られた標準仕様)なんかに意味はないよ。」
そんなWeb標準冬の時代に、CSS2のポジショニングにも疑似要素にも疑似クラスにもかなりのレベルで対応してて、W3Cの仕様書にある通りの書き方でちゃんとレンダリングしてくれるGeckoは、実に素晴らしいものだと本気で思ったし、ブラウザのUI自体すらもW3Cの仕様通りのWeb標準技術をベースにして作られてると知った時にはもう、泣いて喜ぶ勢いでしたよ。
「ああ、世間であんなに冷遇されてるWeb標準が、ここにはちゃんと息づいてる!!」それが僕のMozillaとの関わりの始まりだった。僕にとってMozillaは、Web標準の象徴だった。W3C信者だった僕にとっては、魅上照ばりに「あなたが神か」てなもんでした。
でも気がついたら、RDFを使う部分はどんどん減らされて、MNGサポートもドロップして代わりにAPNGなんてMozilla独自の画像形式になってしまって、SOAPサポートもドロップして(だったよね?確か。)……そんな感じで「世間ではまだ広く使われてないけどWeb標準の技術に対応してる、だからまだマイナーなWeb標準の技術でも実際に動くアプリケーションを僕でも作って実証できる、Web標準はちゃんと役に立つんだって事を証明できる」と思ってた余地がどんどん減っていって。その一方で、「次のスタンダードはWebKitだ!」と「標準」のお株を奪われて、新しいWeb標準への準拠度で後れを取って、性能面でも引き離されちゃって。
さらにはユーザの側からも開発者の側からも「XULなんてクソ重い無駄なもんなくしちまえ」みたいな声が出てきて。Jetpackって、XPCOMが廃止されてもXULが廃止されても拡張機能向けのAPIの互換性を保てるようにっていうことで出てきたんだったと記憶してるんですけど、ということは、Jetpackが最高にうまくいったらXULもなくなっちゃうって事ですか? CSS3で外観を変えたり、XPathでUI要素をゴソッと収集したり、そういうのがなくなっちゃうって事ですか? みたいな。
切ないね……
だいたいさあ、安定したAPIになる保証もないのにオレオレAPIをどんどん重ねてくっていうのが気にくわんのですよ!! document.allとかlayersとか混沌として色々分断されてた世の中が、せっかくWeb標準のおかげで見通し良くなってまとまってきたと思ったのに! prototype.jsとかDojoとかMochikitとかjQueryとかYUIとかExtJSとかオレオレな実装が乱立してきてさあ!! なんなの!! もう!!! Web標準だけあればいいじゃん!!!! そんでもってUI用の言語として開発されたXULでいいじゃん!!!! なんでHTMLで無理矢理UI作ろうとするんだよ!!!!! XMLっていう仕組みがあるんだから、それに則った上で用途に適した道具を選ぼうよ!!!!! そういう事を考えもしないで、見慣れてるからってだけでdivだのspanだのでオレオレUI作ってさあ!!!!!!!! なんなんだよそれ!!!!!!!!!
はあ……
そんな感じで考えれば考えるほどダウンな気持ちになってきたんだけど、さらに調査を進めていく中で、Jetpackの今のAPIの基礎になる部分はCommonJSの仕様に則る形で開発されているという事を知って、「おおっ!?」と思った。
僕がJetpackの前のAPIでとても「うへぇ」ってなってたのは、ライブラリの読み込み方の部分だった。Greasemonkey等で広く使われてたDocComment風の記法じゃない、jetpack.future.XXX
とかのアクセス方法、あれがすごい気持ち悪かった。なんでここでまた無駄にオレオレルールを増やすかなあ!? と思って色々萎えた記憶がある。
でも今のJetpackでは、ライブラリを呼ぶ時はrequre('ライブラリ名')
、ライブラリを作る時はexports.プロパティ名
に値や関数をセットするというルールになっていた。これは、サーバサイドでJavaScriptを実行するnode.js等の環境でAPIを共通化しようということで議論が進められている、CommonJSのルールだ(そうだ)。これは、Webの開発者にとって親しみやすい物にしよう、独自拡張オレオレルールでなんでもやるんじゃなくて足並み合わせる所はきちんと合わせていこうという意図の顕れだと、僕には思えた。MNGとAPNGの件では見損なったけど、この件では見直した。
あと、Mozillaの中の構造とか全然知らない人にペアプログラミング形式で「ちょっとしたアドオンを作ってみましょうか」とJetpackベースでのやり方を指南(?)しようとして、ああやっぱりこのアプローチは間違ってないんだなということを実感した。
初学者とかWebデベロッパーとか、Mozillaヲタじゃなくてもアドオンを作りやすくなってると思う。Pythonをインストールしなきゃならんとかコマンドラインでやらなきゃならんとかの点は、これからどうにでもなることだ。今まで取りこぼされていた人達をすくい上げる基礎になる物が、今のJetpackには確かに揃いつつあるのだと思う。今までの「動的に適用されるパッチ」でしかなかったアドオンの路線のままでは絶対になし得なかったことが、Jetpackでなら確かに可能になるのだと思う。
しかしまあ、改めて考えると、僕がやっていた事も先人から見れば十分「なんだあの窮屈な世界は」てなもんなんだろうね。C++の生の実装に触ることなくその上に幾層にも積み上げられた物の上で僕はずっと遊んできたわけだけれども、その世界での制限には目を向けることもなく、全てを所与の物として受け入れてありがたがっていた。そんな僕が、GreasemonkeyやGoogle Chromeの拡張機能やJetpackに対して「なんだあの窮屈な世界は」なんて言うのはまったく笑い話でしかないのだろう。
下の層に目を向ければ、ハードウェア寄りのレイヤではそれこそ鬼のような非互換の嵐で、それを吸収するためのOSのレイヤがあって。上の層に目を向ければ、プラットフォームどころかデバイスの違いすら吸収するWebがあって。その中間層であるところのブラウザを作る段階で、プラットフォーム間の互換性がどうだのバージョン間の互換性がああだの言ってるのは、ちゃんちゃらおかしい。
そういう中途半端な所に(Web製作の世界から)逃げ延びて住み着いていた僕が、上の層から僕の居場所をなくそうと迫ってくるJetpackに恐怖を抱いて、拒絶反応を示していた。版図を広げようとしている若手の前で竹槍を振り回してた。ああ、実に老害だ……
それに、Web標準Web標準とわめいてみても、WebKitが中心になってしまった今のWebじゃあGeckoの方がはるかにオレオレ実装なはぐれ者だしおまけに10年以上前のコードを引きずってる骨董品だし、XULもXBLもXMLという仕組みの上に成り立っているとはいってもそれ自体はみんなの合意が得られた標準仕様じゃないわけだし。「神!私は仰せの通りに!」と崇めてるうちに、僕はWeb標準とMozillaのオレオレの見境が付かなくなってたのか……
とりとめがないままにモヤモヤした物を吐き出してきたわけだけれども、それと平行してJetpackの内側を覗いてみたりして、光明も見えたりして、自分の勘違いも見えてきて、いくらかスッキリした気はする。
あと、最近は人生っていいものかもしれないなあと思うようになってきたので、老害と言われようともそれでも往生際悪く地味に追いすがっていこうと思ってます。オメガギーク!
今のFirefox用アドオンの開発にかかる手間を省いて、インストールやアンインストールの際にFirefoxを再起動しなくても済むようにする、Firefox 4向けの新しいアドオンの仕組みであるところのJetpack。 最初はGreasemonkeyとuserChrome.jsの合いの子みたいな感じでスタートしてたみたいですが、いつの間にかRebootedになって、SDKを使って開発するという形の物に変わってしまっていました。Rebootedになってからの情報はGomitaさんが色々と記事を書いて下さっています。
Rebooted前に僕もちょっとだけJetpack用にテキストリンクやクリップボード監視を書き直してみたりしたんですが、Rebootedになってからは全くフォローできていませんでした。
で、この度改めてちょっと本腰入れてJetpack作ってみようと思ったんです。
はい、できました。
うん。どこから見てもJetpack。
SDKベースになっても作るのは簡単でした。Jetpack素晴らしいですね。
そういうわけでリアルJetpackです。制作期間は大体1.5週間くらいでしょうか。夏コミの時にふぉくす子コスで手伝ってもらったhknさんがはやぶさで有名な秋の『』さんに会場で偶然遭遇したそうで、背負い物イイナー!ということで面白そうだから冬までには作ってみようと思っていたのですが、去る10月10日に開催された痛Gふぇすた2010にふぉくす子の参加要請があったため、それに合わせて突貫作業で作ってみました。
芯にペットボトルを使い、先端部分とノズル周りと背負うためのベルトさえ作れば簡単にできるんじゃね? と思ってやってみたんですが、リアル工作なんてものすげー久しぶりだったので少々苦労しました。ちゃんとした設計図を作らないまま現物合わせで切ったり貼ったりしたのであちこち微妙に歪んでいますが、遠目に見たら分からないので気にしないことにします。
制作は先端部分から取りかかりました。芯にしたのはペプシコーラのペットボトルで、口をノズル側にした(今回はやらないけど、いつかチャンスがあったらペットボトルロケット的なギミックを仕込んだバージョンを作る事を想定して、そのようにしています)ので先端側にはペットボトルの尻が来たわけですが、5角形というか星形というかそういう変な形をした尻だったので、先端の断面の形に切った厚紙5枚を組み合わせてガムテープでぐるぐる巻にして大まかな形を作った後、普通の紙粘土で覆って表面を丸っこくしました。
乾燥させて紙ヤスリで表面をなめらかにして色を塗ればそれで十分かなーと思ってたんですが、この紙粘土が乾燥してもメチャメチャ脆くて、塗装中も段ボールに先っぽをぶつけただけでボロッと崩れるとか、掃除機で表面の削りカスを吸い取ろうとしたら芯への食いつきが悪いせいで端の方からベリッとはがれるとか、こいつのせいで手間がだいぶ増した気がします。とにかくこんなに脆いんじゃそのまま使うとヤバイと思ったので、塗装した後で透明ニスを何回も重ね塗りして補強しておきました。ニスを塗る工程は当初は想定してませんでしたが、小さい頃に紙粘土工作をした後に必ずニスを塗らされたのはこういう事だったのだなあと、今更になって当時やった事の意味をを思い知らされました。
あと、形を出した後で思い出しましたが、確かハンズにこういう形の発泡スチロールの塊が売られていた気がしますので、最初からそれを使えばよかったなあと後悔しました。紙粘土は表面だけに使ったといっても、大きさが大きさだったので結局ここだけで500グラム以上になってたみたいですし。軽く作る工夫は大事です。
胴体は元のデザインにあるスリットを開けておいたボール紙をペットボトルに巻き付けただけです。
ノズル部分は、戦闘機のベクタードスラストノズルみたいなデザインだと思い込んでいたためにその前提でプランを考えていたのですが、胴体のスリットを開口するために元のデザインを確認したら、全然違ってました。でも先すぼまりな形を継ぎ目無しに作るのはボール紙じゃキツいわと思ったので、元デザイン無視でやはりベクタードスラストっぽいデザインを意識した物にしてみました(配色もそれを意識してます)。 あと、先端を紙粘土にしたことによってすごいトップヘビーになってしまったので、バランスを取るために、外からは見えませんがペットボトルの口の周りに紙粘土を重りとして巻いてあります。これで結局、全体で1kg以上はある感じの重たい装備になってしまいました。
胴体のボール紙の継ぎ目とか先端と胴体の間の所とかをごまかすために細切りのボール紙を巻いて、隙間をホットボンドで埋め、形としては完成しました。
着色は、スプレーで先端とノズルに色を付けた後、先端とノズルをマスキングして胴体のシルバーをスプレーで塗るつもりだったのですが、前述した通り紙粘土部分がメチャメチャ脆くて、これじゃマスキングテープで確実に表面がボロボロはがれるわと思ったので、先に先端(とノズル)だけニスでコーティングすることにしました。2時間以上乾燥→重ね塗り、で2~3重に塗ったので、結構頑丈になったと思います。あと、塗装は缶スプレーだけでいいかなと思ってましたが、実際やってみると赤色は結構下地のアラが目立ってしまいましたので、先にサーフェイサーで灰色一色にしてから赤や茶色を塗りました。胴体は、銀色の隠蔽力の高さに期待して特にそういう事はせず、ボール紙に銀色のスプレーを吹いてニスを塗っただけです。ニスが乾いた後で、ノズルがテカテカ光ってるのは変かなーと思ったので、最後に胴体をマスキングしてノズルの部分だけつや消しクリアーを吹いてあります。
左右の胴体の連結やベルトの固定方法については、当初は何も考えていなかったのですが、いい材料はないかと思ってホームセンターの商品を眺めていたら簡単に曲げられそうな穴あきの金属の板があったので、それを曲げて形を作って左右の部品をネジ留めしてからタッピングビスで胴体に固定することにしました。
ベルトは100円ショップで買った物を適当に切って使ってます。しかし実際に背負ってみるとベルトが肩に食い込んで痛かったので、そのうちクッション材(カバンの肩紐にあるようなやつ)を付けたいとは思っています。
表面がガタガタであまり見れたものではありませんが、大きな写真をFlickrに置いてあります。ふぉくす子が背負っている様子の写真はもえじらブログの方にあります。
23日にMozilla Japanで行われたコアJetpackミーティングに参加してきました。2月にGomitaさんとあかつかさんがMozilla Corporationまで行く時に持って行く意見・アイデア等をまとめるのが趣旨の会合でした。Mozilla信者な視点だけからでは意見が偏るんじゃないかと思ったので、Chrome拡張機能のえらい人のos0xさんにも来てもらいました。
議題は「Jetpackのスクリプトの開発環境について」「Jetpack Galleryについて」「Jetpackが提供するAPIについて」の3つでした。以下、思い出しながらグダグダ書いてみます。
既存のアドオンを冷遇していくとかの話はさておき、これまで普通のアドオンを作ってた人が、Jetpackで事足りるような機能についてはJetpackでやるようになる、という未来を実現するには何が必要か? という話をしました。
結論としては、Bespin要らないんじゃね、ていうか好きなエディタを使わせて下さい、的な所に意見が集中しました。Bespin自体技術デモの性格が強いような物で、IMEがまともに使えない等のどうしようもない問題を沢山抱えていて、はっきり言って「これで(vim|Emacs|秀丸)を捨てられる!」みたいな未来は当分、つうか絶対に来ないと思われるわけで。かといって、eclipseみたいなファットな開発環境をJetpackの中に突っ込むというのも、それはなんか違うんじゃないの? とも思うし。既にデバッグ用にはFirebugを使うのがお勧めみたいな事も言ってる事だし、適材適所でそれぞれ分担すりゃいいじゃないの。頑張ってここ(BespinとJetpackの連携)に色々と機能追加する事にリソースを作よりも別の所で頑張りませんか? というのが、参加者達の総意だった気がします。
「新規のアドオン開発者」として取り込みたいターゲットと思われるWeb制作の現場の人のためには何をすればいいか? という事を次に議論したんですが、参加者の中にWebデベロッパが一人もいないという状況だったので(ひでーな!)想像だけで議論した限りでは、とりあえず、「スクリプトの頭と尻にお約束の構文を書いたら後はwindow
等の見慣れたキーワードに普通にアクセスできる」ような状態にしておいた方がいいんじゃないのか、という事を提案しました。Greasemonkeyが受け入れられたのって、Webページの中に埋め込まれるスクリプトと全く同じ感覚で書けたからだと思うんですよね。で、Webのスクリプトを書く時、名前空間の違いとかクロスウィンドウなスクリプトとかそういうのを意識する事ってまず無くて、1つのフレームの中で動くスクリプトを書く感覚が染み着いてるんじゃないのかと。その感覚でスクリプトを書けるかどうかってのが、Webデベロッパに対する第1印象を決定づけるんじゃないのだろうかと。ちなみに、Google Chromeではこの要求に対する回答として、「コンテントスクリプト」という種類の拡張機能であればWebページ内のスクリプトと同じ感覚で書けるという設計になってるようです。
一般ユーザ、つまり今までアドオンを作った事もなければスクリプトを書いた事もないという層の人に対してはどうか。
グラフィカルプログラミングがどうとかそういう話もチラッと出はしたんですが、そこに力を入れるのもやっぱりJetpackの仕事じゃないんじゃないの? と。そういうのは大学の研究室とかで頭のいい人達が散々やってるわけだし。
ポイントは、「簡単にプログラムをかけるかどうか」「知識が無くてもプログラムをかけるかどうか」ではなくて、そもそも「プログラムを書かなくてもカスタマイズできるかどうか」なんじゃないのか、という指摘が出てました。Ubiquityでは複数のコマンドをユーザが組み合わせて使う事を想定している(これはコマンドラインで複数のコマンドをパイプで繋げて使うような感覚)、というのを引き合いに出して、例えるなら「新しい文房具を簡単に作れるようにする」んじゃなくて「ハサミとホッチキスとセロテープ、という道具を手に入れたユーザがそれを好きなように組み合わせて使えるようにする」という風な。そこが大事なんじゃないのか、と。だから例えばAutoPagerizeのSITEINFO on wedataとか、アップローダに勝手改造版スクリプトをアップロードするみたいな、「スクラッチでスクリプトを書ける人以外でも気軽に改良できる、参加できる」ような仕組みがあるといいんじゃないか? という提案が出ました。そういうAPIがJetpackに標準で入ってて、Jetpack GalleryでユーザがSITEINFOをどんどん追加できるようになってて、APIを使っていればそれを簡単に取得できる、みたいな仕組みが用意されていれば、もっと「Webを便利にしてくれる人」の裾野が広がるのではないかなー。と、僕は思ってます。
Jetpack Galleryへの要望・提案としては、まず前述の話にも出た、JetpackのAPIとも連携したwedataのような仕組みがあるといいんじゃないかという話を出しました。
それと、ドキュメント類が不足してる問題への解として、Galleryに登録されてるスクリプト全部をオンラインで検索できるMXRみたいな機能があればいいんじゃないの?という話も出しました。実際、自分がアドオン作る時でも、使い方が分からないAPIはMDCのドキュメントを見るだけでなく、MXRでFirefoxのソースを検索して「ほうほうこうやって使うのか」と調べる場合が非常に多いです(MDCにドキュメントが無ければそれしかないし)。ドキュメントを書け書けと言っても人はめんどくさがって絶対にそんなもん用意せんのだから、じゃあ既にあるコードをスニペットとして全部活用できるようにすりゃあいいじゃないか、という意図での提案です。
現状のGalleryにはレビューの仕組みがないので危険だ、という話も出ました。これは次の話にも関係してるので、そっちで詳しく書く事にします。
ここは議論が割れました。あかつかさんはraw
のような仕組み、つまりAPIが用意されてない部分にアクセスしてその気になればなんでもできるような余地を残しておいた方がいいんじゃないのか、という立場で、僕とかは、そういう余地は一切残すべきでないという立場で話をしました。
確かに、そういう余地があったからこそ今のFirefoxのアドオンはここまで色々なものが出てきたわけで、そういう自由度をなくしてしまうのは残念だという感覚は、僕もよく分かります。ただ、だからってFrozenじゃない所に全部アクセスできるようにしてしまうというのも乱暴すぎて、それじゃあ結局今のアドオンのように、Firefoxのバージョンが上がったら使えなくなっちゃいました、アドオンのバージョンアップ待ちです、みたいな状況が絶対また出てくる。それって、Jetpackが謳う「安定したAPIでバージョンごとの非互換から解放されます」という話とまるっきり矛盾してしまうわけです。
それに、レビューの問題もある。今はJetpack GalleryにはAMOのような「レビューが通らないと一般公開されない」という風な仕組みはないけど、「最初は無難そうなスクリプトで登録して関係者を欺いて、後からプライバシーぶっこ抜きなコードを追加したものを自動アップデートで一斉配信して、悪意のある開発者がウマー」という事態を防ぐには(今のGalleryではこれが出来てしまう!)、レビューの仕組みは絶対必要になります。
その時に、rawで生のXPCOM等を直接触れてしまう仕組みがあると、レビュワーは結局全部のコードをしっかり精査しないといけないことになる。レビュワーにも知識と経験が求められる。それでは、今のAMOにありがちな「有能なレビュワーのリソースは限られてるので、レビュー完了まで何ヶ月も待たされてしまう。なので、何ヶ月も最新版が公開されないで放置されてしまう。」という風な状態が、Jetpack Galleryにも発生してしまうわけです。
Jetpackが提供するAPIしか使えないのなら、そういう状況に陥る事を防げる。例えば、「Jetpackが提供するAPIでなければファイルにアクセスできないと」いう仕様で、スクリプトの先頭に書く各種の宣言部分で「このパス以下のファイルしか読み込みません」という宣言を書かなければファイルアクセスは一切できない、ファイルアクセスをする時も宣言された範囲のファイルにしかアクセスできない、そういう設計になったとしましょう。すると、レビュワーは冒頭の宣言部分だけ読めば「こいつは危険だな」とか「こいつは安全だ」といった判断ができて、レビューが早く進むようになる。また、スクリプト作者が面倒くさがって「あらゆるファイルにアクセスする可能性があります」みたいな宣言を書くと、レビュワーから疑われていつまでも放置されるから、いつまで経っても公開されないことになる。なのでスクリプト作者は詳細な指定を宣言に嫌でも書かないといけない事になる。そういう風に、みんながみんな自分の利益を最大化する事が結果としてコミュニティ全体の利益に繋がるような仕組みを、今から作る事ができるわけです。
Jetpackという仕組みをゼロから作ろうとしてる今こそ、過去のアドオンの「Firefoxのバージョンに依存する」「レビューにめちゃめちゃ時間がかかって、いつまでも最新バージョンが公開されない」という問題と決別するチャンスなんですよ。なんでそのチャンスをみすみす見逃して、同じ問題をまたJetpackの世界に持ち込むんですか? と。
だいたい、Jetpackが本体に入った後も既存のアドオンの仕組みは依然として残り続けるわけで、偉い人はXULとXPCOMを全部なくすとか言ってるけど実際にコアのコードを書いてる人曰く「そんなの絶対無理(加藤さん・談)」なわけで、だったら「安定したAPIのJetpackで、まずは作ってみる。それでできない事が出てきたら、自由度の高い(でもAPIが不安定な)既存のアドオンの仕組みで作る方にステップアップする」というスキームでやっていけばいいじゃないですか。と。
他には、プラットフォーム(XULRunner、Geckoのレイヤ)でやるべき事とJetpackがやるべき事はちゃんと切り分けて欲しいという話も出ました。例えば音声取り込み用のAPIなんてのは、Jetpackにプラットフォーム別のバイナリをブチ込むんじゃなくて、Geckoで面倒見るべき話でしょう。そのせいでJetpackの構成ファイルが何MBにもなってダウンロードも起動も遅くなって、その一方で、ほんのちょっと起動を速くするためにFUELを廃止するって、何ですかそれは? 何か大事な事を履き違えてるんじゃあないですか? と。
とにかく、Jetpackというプロジェクトが何にフォーカスしているのか、プロジェクトのポリシーが僕には全然見えない。Jetpackをウォッチし続けてるcon_mameさんもteramakoさんも、Jetpackの方針が揺らいでるように見えるという風におっしゃってました。なので、Mozilla Corporationに行く時にはGomitaさんとあかつかさんにはぜひとも、Jetpackが最優先しようとしているのは何なのか? 他のものを犠牲にしてでも貫かないといけないと考えているポイントはどこなのか? というプロジェクトの指針を明らかにしてきて欲しい、という事をこのミーティングの参加者達の最大の関心事として託しておきました。
ミーティングの最中にtwitterで言及されて、MicrosoftのAzureの話を読みました。既存の開発者のために腐心しすぎるとイノベーションが起こらなくなってしまう、僕のようなロートルがいつまでもでかい顔して居座っていられるような状況にしてしまっては新しいプレーヤーが参入して来れない、というのは実に耳の痛い話だと思いました。
もちろん将来的に、いつかはJetpack自体も今のアドオンのように「もう時代遅れだ」と冷遇され切り捨てられる時代が来るのかもしれません。が、それはその時に考えればいい事なんじゃないかと僕は思います。
しかし自分が特に耳が痛いのは、既存プレーヤーがデカいツラしやがるという話です。自分の既得権益、自分の居場所を守るために必死に抵抗すればするほど、自分自身が今のまま何も変わらずに一線に立ち続けようとすればするほど、自分がすがりついている物の未来を奪う事になって、自分も他の人も不幸にする。「シーンの最前線から退く覚悟はあるか?」というエントリは、本文の内容は全然この話題と関係ないし趣旨も違うけど、でも僕は最近この事を考えがちなので、タイトル見ただけでこの事に結び付けて受け取ってドキッとしました。みんなのためを思うなら、老兵は黙って去って、不幸になるのは自分だけで終わらせないといけないんじゃないのか。
Microsoftは、Windows 95上でWindows 3.1用のアプリの動作を保証するために、Windowsの側で頑張って対策をしてまで互換性を維持したそうです。VistaのUltimateだったかWindows 7だったかではWindows XPとの互換性を維持するために仮想マシンまで用意しました。昔のAPIでも、「obsolete」という風にはMSDNに書いてあっても、動く事は動くという状態でずっと残ってるそうです。そういう「とにかく過去のソフトウェア資産を使い続けられるようにする」戦略をMicrosoftは取り続けて、製品はいまいち垢抜けない物ばかりになってしまいましたが、その結果としてシェアはものすごい事になりました。圧倒的多数の凡人がちょっとずつ落とすお金のおかげで、Microsoftは食えてます。
Appleは、ジョブズが思い描く「素晴らしいプロダクト」を目指すために、古いAPIを容赦なく切り捨ててがんがんアップデートしてます。Mac OS Xで「obsolete」となったAPIは、2つ後のバージョンくらいではもう綺麗さっぱり消えてしまうそうです。その結果、ついていけない脱落者が多くなるし、世界の過半数を占めるようなものすごいシェアは獲れてない。けれども強烈な信者ができて、彼らのおかげでAppleは食えてるわけです。
Googleは広告のシェアをガッツリ掴む事で、Operaはモバイル市場をガッツリ掴む事で、食っていってますよね。
で、Mozillaはどんな道を歩むつもりなんでしょうか。
今までの路線を見る限り、安定版リリースの寿命は短い(次のメジャーバージョンが出たら前のバージョンは半年で更新が打ち切られる)し、安定したAPIになるとか言ってたFUELもメジャーバージョン2つと保たずに黒歴史になってしまいそうだし、どう見てもゲイツよりはジョブズの生き方に見える。でも、ジョブズがジョブズの生き方を貫けてるのはあくまで、信者がお金を出して高い製品を買ってくれてるからですよね。でもこれって、言ってみればブランド物の生き残り戦略ですよね。Mozillaは物を売ってないから、その戦略じゃあ明日のおまんまが食えなくなるんじゃないの? 開発者を養っていけなくなるんじゃないの? そこが僕は心配でならないです。「いやオープンソースでバザールだから開発者はみんな善意で関わり続けてくれるんだよ」、という反論はすぐに思いつきますけど、今は世間はみんなWebkitに流れてますよね。オープンソースの開発者も、Geckoより将来有望な(ように見える)Webkitに流れていくんじゃないですか? その時、Geckoに関わり続けてくれる開発者はどれくらい残るんでしょうか?
上に挙げた各社のどれとも違う別の道を歩む、というのが多分、Mozillaにとっての模範解答なんでしょうけどね。その「別の道」ってのが一体何なのか、僕にはまだよく分からんです。
同ネタ既出の予感だけど。
amachangのHTML5のクションアウトライン解析ライブラリを使わせてもらって、HTML5ベースのセクションアウトラインに基づくページ内目次を生成し、ページの右上に貼り付けるJetpack featureを作ってみた。先日のCSS Niteの時にブツクサ言ってたのを形にした。
amachangのライブラリのライセンスが不明だったので、このJetpack featureもライセンスは明示してません。あしからず。ライブラリのライセンスはパブリックドメインとのことなので、これもパブリックドメインに……と言いたいところですが日本ではPDは存在しないので、とりあえず皆さん安心して使えるよう僕はMITライセンスが好きなのでMITライセンスとしておきます。
所感。
rgba()
とか-moz-box-shadow
とか使いまくってますよ。追記。リクエストがあったので、スライドバーへの表示に対応してみた。また、ステータスバー上のチェックボックスで、ページ内のタブの表示・非表示を切り替えられるようにした。チェックボックスの状態はFirefoxを終了した後も保持される(storageのAPIの使い方がやっと分かったので使ってみた)。
今度はJetpack用のスクラッチで。ステータスバー上に「左のタブを閉じる」「右のタブを閉じる」「他のタブを閉じる」ボタンを追加します。そんだけ。
この程度の物ならサクッと作れる。とても楽です。
追記。タブの順番を並べ替えていると期待通りに動かないバグがあったので修正した。
テキストリンクに続いてClipboard ObserverもJetpackに移植してみた。
テキストリンク同様に、普通のアドオンのClipboard ObserverのコードをJetpackで動くように手直しして、最後にJetpack用のコードを付け加えただけ……という感じ。今回は、テキストリンクでは使わなかったステータスバーへの項目追加のAPIを使ってる。
以下、詰まった点。
jetpack.storage
の使い方が分からない、というか、Web上に書かれてる情報が古くて役に立たない。APIリファレンスに書いてある書き方を試したら「この方法は古いです、もうすぐサポートしなくなります」なんてメッセージが出る。
とにかくドキュメント不足が深刻です。
追記。アウトラインリスト生成のやつでstorageの使い方がやっと分かったので、チェック状態を保存するようにした。
いわゆる軽量アドオンであるところのJetpackについて今度の日曜のFirefox Developers ConferenceでAza氏とトークショーじゃなかったトークセッションを行うにあたって、「全くJetpackを触らないまま行くのはさすがに失礼すぎるだろ常識的に考えて」と思ったので、テキストリンクをJetpack featureとして移植してみた。
以下、詰まった点。
==
で比較すると、ラップされてるノードが同じノードでもfalse
になる。===
や!==
で比較すればちゃんと意図通りに判定される。これは盲点だった。(普通にアドオンを作る時だと==
で期待通りに判定されるので)やっつけ移植なので、URLっぽい文字列をダブルクリックしたら新しいタブを開く、という機能しかないです。そのくせ25KBもありやがる。
スクリプトの中身はぶっちゃけ普通のテキストリンクのコードのコピペです。というか、Jetpackで動かないであろう部分を削っていって残ったのがコレなんで。最後の方にちょこっとだけ、JetpackのAPIを使ってページの読み込み完了を監視する処理が入ってて、そこが本題です。
いいな、と思った点。