たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
ちょっと前に、Tab Utilities Mini、というのが出てきたらしいですね。
改めて、今現在それ系のアドオンがどれだけあるのかリストアップしてみて、乾いた笑いしか出てこなかった。
PlusにLiteにCEにMiniに……それはギャグで言っているのか? タブ統合型アドオンの比較表みたいな物がないと見分けつかないよ。既にこれだけあるのにまだ同じ事をやるんだ、っていう事に呆れる。
しかも、よく見たらTab UtilitiesとTab Utilities MiniとTab Utilities Liteって作者同じじゃないすか。ますます意味が分からない。
/ || ̄ ̄|| ∧_∧ |.....||__|| ( ) どうしてこうなった・・・ | ̄ ̄\三⊂/ ̄ ̄ ̄/ | | ( ./ / ___ / || ̄ ̄|| ∧_∧ |.....||__|| ( ^ω^ ) どうしてこうなった!? | ̄ ̄\三⊂/ ̄ ̄ ̄/ | | ( ./ / ___ ♪ ∧__,∧.∩ / || ̄ ̄|| r( ^ω^ )ノ どうしてこうなった! |.....||__|| └‐、 レ´`ヽ どうしてこうなった! | ̄ ̄\三 / ̄ ̄ ̄/ノ´` ♪ | | ( ./ / ___ ♪ ∩∧__,∧ / || ̄ ̄|| _ ヽ( ^ω^ )7 どうしてこうなった! |.....||__|| /`ヽJ ,‐┘ どうしてこうなった! | ̄ ̄\三 / ̄ ̄ ̄/ ´`ヽ、_ ノ | | ( ./ / `) ) ♪
すげー悪く言うけどさ、ほんとすげー悪く言うけどさ、今日はセンスタソになっちゃうけどさ、タブブラウザ拡張(Tabbrowser Extensions、TBE)を作ってた僕自身も含めて、こういう事やる人ってほんっとセンスないよね。
日本のメーカーがiPhoneを作るとこうなるって話なんかもそうだけど、センスが無い人は「カタログスペック上の数値が高い物」「カタログ上の機能が多い物」という観点からしか「良さ」をアピールできないんだよね。まあ、そんなセンス無しでもiPhoneを触ったら「キモチイイ!!!」って感覚は多分抱くんだよね。ただ、残念な事に、その感想が脳内のフィルタをいくつも通ってその人の口から出力される時には、どーいうわけか「あれもできる、これもできる、だから素晴らしい」的なくっだらない表現に「翻訳」されちゃうわけですよ。
同じ事が、そういう人が物を作る時にも起こるわけですよ。物を作る以上、そりゃあ、自分で「こりゃ駄目だ」って思うものよりは「これは良い物だ」って思える物を作りたくなる。でもセンスがないから、どう作れば「良い物」になるかがわかんない。しょうがないから、自分が「良い」って思った事があるものを切り貼りして集めてくるしかないワケですよ。そういう風にセンスの無い人間が、下手に決定権を持っちゃったり実行力を持っちゃったりするから、こういう物が世の中に出てきてしまうんだ。
で、こういうアドオンってほんと罪作りだと思うんだ。
カタログスペックだけはやたら高いから、よく分かってない人は「多分良い物なんだろう、カタログスペックはいいみたいだから」って同じ発想で飛びついちゃうんだよきっと。「それらの機能が本当に自分を幸せにしてくれるのかどうか」なんて分からないままに。
それで機能の8割くらいが結局使われなかったとしても、まあ、ユーザが満足してるならそれでいいじゃんっていう考え方はある。メモリだのなんだのの面で無駄が大きくても、そんなの関係ない。「無駄がないけど目的は達成されない」のと「無駄はあるけど目的は達成される」のとだったら、後者は前者に対して圧倒的に正しい。
でもさあ。「無駄がある」だけじゃなく「囲い込まれてしまう」っていう事まで加わってしまったら、それはさすがにあかんやろって思うんですよ。なんで多機能オールインワン型のアドオンが囲い込みになってしまうのかという話は過去にも詳しく書いたから、そっちを読んで欲しいんだけど。囲い込みたくて囲い込んでるんじゃなくて、囲い込もうというつもりはなかったのに結果的に囲い込みになってしまう、囲い込んだ後にちゃんと維持し続けていけるだけのキャパシティもないのにそういう状況を作り出してしまう、それが良くない。囲い込みって、やる方にも体力がいるんだよ。あらゆる物を自前で用意しなきゃいけない。そんなの個人で趣味でやれるような事じゃない。それでは作り手も使い手もみんな不幸になってしまうよ。
僕はFirefox 1.5までのバージョンにしか対応できていなかったTBEに自分自身が激しくロックインされてしまって、Firefox 2にいつまで経ってもアップグレードできなかった。そんな馬鹿みたいな事に他人まで巻き込んだらあかんよ……
(ツリー型タブから「ロケーションバーから新しいタブを開く」「リンクを新しいタブで開く」を分離することにしたきっかけがこの出来事だったので、それについて書こうと思って色々思い返したり調べ直したりしてたんだけど、結局こういう話に落ち着いた。)
Tree Style TabとMultiple Tab Handlerのドラッグ&ドロップ周りのコードを色々書き直した。
発端は、「Tab Utilitiesと一緒に使うとタブのドラッグ&ドロップが期待通りに動かない」という報告だった。しかし、「また衝突かよー」と思いながらメールの内容を読むと、想像していたのとはちょっと状況が違った。
僕はてっきり、Tab Utilitiesのイベント処理とツリー型タブのイベント処理がかちあって変な事になってるんだとばかり思ってたんだけど、そうではなくて、Tab Utilitiesが提供する「複数のタブを選択してまとめてドラッグ&ドロップする」機能で複数のタブを1つのタブの上にドロップしても、選択されていたタブのうち1つしかドロップ先のタブの子タブにならない、という状況だった。その人はすでにTab Utilitiesの作者にも問い合わせていたようで、Tab Utilitiesの作者の回答に「ツリー型タブにマルチプルタブハンドラのためのコードしか含まれていないのがそもそもの問題だ。ツリー型タブの方で修正してもらってくれ。」みたいなコメントがあったそうだ。
それでTab Utilitiesを見てみたら、確かに複数のタブを選択する機能があったんだけど、その時のタブの情報の受け渡しにHTML5のドラッグ&ドロップのイベントの複数のデータの指定の仕組みが使われているようだった。
という事情があって、マルチプルタブハンドラではドラッグ操作のイベントそのものには介入しないようにしてたんだけど、Tab Utilitiesがそういう事をやってるんだったら、「複数タブのドラッグ操作」に特化したアドオンであるマルチプルタブハンドラが黙って見てるわけにはいかない。なので、マルチプルタブハンドラではバージョン0.6から、ついでにツリー型タブもバージョン0.11.2010120101から、タブをドラッグ&ドロップするときはドラッグしようとしているすべてのタブをデータトランスファーに渡すようにした。ついでにドラッグフィードバックイメージも自前で生成するようにして、複数タブのドラッグ時はドラッグ中のすべてのタブのサムネイルを重ねて表示するようにしてみた。
それで「ああやっと時代に追いつけたわ」と安心してたんだけど、Windowsでテストしてみたら、せっかく生成したドラッグフィードバックイメージがぜんぜん表示されないんでやんの。どうも、Windows版のFirefox 3.6以前は、mozSetDataAt()
で複数のデータを指定するとドラッグフィードバックイメージが表示されないというバグがあるようだ。Ubuntu上のFirefox 3.6や、Windows上でもMinefieldでは問題なかったんだけど。頑張りの報われなさに切なくなった。
ツリー型タブの場合は、タブのドラッグ&ドロップでツリーを編集する場合があるから、マルチプルタブハンドラみたいにTabMoveイベントをトリガーとして後から必要な処理を行うということはできなかった。なので、Firefox自身のドラッグ&ドロップの処理に介入する形の設計にせざるを得なかった。
その後HTML5のドラッグ&ドロップのAPIが実装されて、Firefox 3.5ではFirefoxのタブのドラッグ&ドロップのためのコード自体もそれをベースに書き直された。これはまだFirefox 3.0と同じようなやり方でドラッグ&ドロップの処理に介入できる余地があった。
しかしFirefox 4ではタブのドロップとかドラッグオーバーとかの処理がXBLのイベントハンドラの中にベタ書きされるようになってしまって、メソッドの中に処理を注入するやり方ではドラッグ&ドロップの操作に介入できなくなってしまった。なので、仕方ないからdragstartとかdropとかのイベントにキャプチャリングフェーズで割り込んでツリー型タブの側で全部自分で処理するようにした。
という経緯があって、結果的にツリー型タブのドラッグ&ドロップ周りの処理はFirefox 3.0向けとFirefox 3.5~3.6向けとFirefox 4向けとで3種類の方法が同居してかなりシッチャカメッチャカな状態になっていた。あとタブバー自体のドラッグ&ドロップの処理もあって、それぞれがあちこちに散らばっててだいぶ訳のわからんことになってた。
そんな状態だったから、昨日のリリースでは案の定リグレッションしてドラッグ&ドロップが盛大にぶっ壊れてた。これはもう駄目かもわからんねと思ったので、Firefox 3.0向けのコードを全廃したついでにFirefox 3.5~3.6に対してもFirefox 4向けのやり方を使うようにして、ドラッグ&ドロップのコードを専用のクラスに集めて整理することにした。
安定性とメンテナンス性を取った結果、タブのドラッグ&ドロップのイベント処理はFirefox本体のものを完全に無視する形になっているので、他のアドオンとの機能の両立という点では残念なことになっているかもしれない。タブのドラッグ&ドロップに介入するためのきちんとしたAPIが整理されていれば、こんな思いをしなくて済むのになあ。
今まではTree Style Tabでタブバーを横置き(上または下にタブバーを配置するモード)にした時特有の機能というのは特に設けていなかった、というか横置きで階層化されたグループ化なんて全然使いにくいし誰も使わないだろと思って割となおざりにしてたら、案外使われてるみたいで時々このモード特有のバグ報告を貰ってたわけですが、Opera 11でタブスタッキングとかいう機能が加わるとかで「そんなんXULでもできるわい!!! つうかもっと前からやっとるわい!!!」とカッとなって、タブが重なってるっぽく表示するようにしてみた。
種を明かしてしまうと、元々「折り畳まれたタブ」はvisibility: collapseで見えないようにしてたのを、collapseにせずにvisibleのままで親のタブの下にz-indexを調整して重ねるようにしたというだけ。諸々の事情でMinefieldでしかこの表示にはならないようになってる(Firefox 3.6だと今まで通り)。
このようにしたおかげで、
というメリットを図らずも得られてしまった。
しかしIDEA*IDEAの記事の小さいスクリーンショットと動画だけ見てこういう表示にしたんだけど、もっと大きなスクリーンショットだと、実際は全然違う形だった。まあ、今更だからこのままいくけどさ……
あと、このリリースから「リンクをクリックした時にそれを自動的にタブで開く」系の機能と「ロケーションバーからの入力で常にタブを開く」系の機能を別のアドオンに分割することにした。
既にあったOpen Bookmarks in New Tabと並べて「新しくタブを開く系3兄弟」的な。
といった理由があっての決定です。多機能なのがいい人はツリー型タブを捨ててTab Kitあたりに乗り換えるといいと思います。
ツリー型タブのGoogle Chrome版は作らないの?という問い合わせを一時期よくもらってたんだけど、とうとう出た。Google Chrome版Tree Style Tab。といっても僕が作ったんじゃなくて、ジェバンニが一晩でやってくれました。的な。
ただ、やはり僕が懸念していたように、形態としては「ツールバーのボタンをクリックしたらポップアップでツリーが出る」という物のようで、実際試してみても違和感があった…… 僕は「ツリーが常に見えている事」がTSTの常用には欠かせないと思っているので、この形の物はやはり辛い。
Side Tabs(起動オプション --enable-vertical-tabs で有効になる)が入っている最近のChromeでなら、あともうちょっと頑張ればなんとかなりそうな気がしなくもない。ページのタイトルの頭にスペースを挿入して擬似的にタブをインデント表示するとか。
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の内側を覗いてみたりして、光明も見えたりして、自分の勘違いも見えてきて、いくらかスッキリした気はする。
あと、最近は人生っていいものかもしれないなあと思うようになってきたので、老害と言われようともそれでも往生際悪く地味に追いすがっていこうと思ってます。オメガギーク!
僕がアドオン開発でXBLの利用を避けることが多いのは、XBLを使ってると、他のアドオンやサードパーティ製のテーマと衝突した時ににっちもさっちもいかなくなってしまうことが多かった(という印象が強い)り、複数のバージョンのFirefoxに対応しようと思うとドツボにハマったりしたからだ。
例えばツリー型タブのようなアドオンを作る時に、「タブのDOMノードに、子タブの一覧を取得するためのchildTabs
というプロパティを追加したいな」と思ったとする。思ったっていうか、タブブラウザ拡張でかつてツリー表示機能を実装した時には実際そうしてたんだけど。それをXBLでやるとこんな風になるだろう。
<binding id="tabbrowser-tab"
extends="chrome://browser/content/tabbrowser.xml#tabbrowser-tab">
<implementation>
<property name="childTabs" readonly="true">
<getter><![CDATA[
...
]]></getter>
</property>
</implementation>
</binding>
同時に、こういうCSSも書くことになる。
.tabbrowser-tab {
-moz-binding: url("mybinding.xml#tabbrowser-tab");
}
XBLではextends
で他のバインディング定義を継承することができる。chrome://browser/content/tabbrowser.xml というのは、Firefox 3.6でタブブラウズ関係の機能を定義してるバインディングなので、これで「Firefox本来のタブの機能に加えて新しい機能を定義する」という事が簡単にできる。
が、これは同時に欠点でもある。XULの機能のかなりの部分はXBLで定義されているので、extends
を書き忘れるとマトモに動かなくなってしまうことが結構ある。だから、独自のバインディングを適用する時は、適用先の要素に既にバインディングが適用されているかどうかを調べて、現在適用されているバインディングのURIを独自のバインディングのextends
に書いておかないといけない。
そして、「元々適用されているバインディングのURI」は簡単には同定できない。Firefoxのバージョンによっても変わるし、WindowsかMac OS Xかによっても変わるし、サードパーティ製のテーマでバインディングが上書きされているかもしれないし、アドオンがバインディングを適用しているかもしれない。ひょっとしたらユーザがuserChrome.cssでバインディングの指定を変えているかもしれない。継承の順番は、自分のXBL→tabbrowser.xml→tabbox.xml→general.xml かもしれないし、自分のXBL→テーマのXBL→tabbrowser.xml→tabbox.xml→general.xml かもしれないし、自分のXBL→他のアドオンのXBL→テーマのXBL→tabbrowser.xml→tabbox.xml→general.xml かもしれない。「こう書いておけば大丈夫」という単一の継承元のURIは存在しないのだ。
さらにタチが悪いことに、XBLの定義ファイルの中に書かれたextends
は動的には書き換えられない。現在適用されているバインディングのURIをgetComputedStyle()
で取得しても、それを後からXBLのextends
に指定するという事はできない。(ひょっとしたらdata: URLを使えばできるかもしれないけど、僕はそんなのやりたくないし見たくもない……)
だから、XBLを使うアドオン同士ではバインディングの優先権の取り合いになる。CSSは後から読み込まれたもの・セレクタの指定の詳細度が高いものほど優先的に適用されるから、後からインストールしたアドオンのせいでそれまで使っていたアドオンのバインディングが適用されなくなる、なんてこともしょっちゅうあった。タブブラウザのタブ要素なんて、激戦区中の激戦区と言っていいだろう。
そう考えると、ほんの2~3個のプロパティだとかメソッドだとかを追加するためだけにこれだけのリスクを負うのは到底割に合わない。それだったら、要素のDOMノードに直接プロパティを加えるのを諦めて、コントローラやサービスになるようなクラスを作ってそっちに必要な処理を集約させる方がずっと楽だ。(だからツリー型タブでは、前述の例のようなバインディングを使う代わりに、gBrowser.treeStyleTab.getChildTabs(aTab)
で子タブの配列を得るように設計した。)
というのが、僕の出した結論だった。
そういう話なので、僕は、「何が何でもXBLを使うな」とまで言うつもりはない。前述のようなバインディングの適用の優先権争いが起こらない場所、例えば独自のXULRunnerアプリケーションだったり、独自のサイドバーパネルだったり……という部分でなら、XBLはいくらでも使っていいと思ってる。
例えば、派生の要素型がたくさんあるようなケースではXBLの継承が威力を発揮する。会社で一時期やってたXULRunnerアプリのプロジェクトでは、継承を多用することで結構工数を削減できた(と思う)。また、anonymous contentsの追加は(特に、既に存在しているDOMツリーのノードの親子関係の間に安全に割り込むような物は)、XBLでなければできないことだ。
ただ、XBLは、1つのウィンドウの中にいろんな人が好き勝手に書いたコードが同居する「Firefoxのアドオン」という分野とは、すこぶる相性が悪い。JavaScriptで書く物でもCSSで書く物でも「最初にやった者勝ち」な所はどこかしらあるけれども、XBLの場合はそれが顕著だし、「後から来た者が上手く隙間に入り込む」という風な余地が全然無い。だから僕は、XBLをなるべく避ける形でコードを書くように努めてるんだな。
XBLはアドオン同士の衝突の原因になりやすい。だからXBLはあまり使わないように僕はしてる。
XBLを使うと、DOMノードにgetterやsetterになってるプロパティを定義したり、独自のメソッドを追加したりできる。でも、それらはJavaScriptのテクニックで代用できないこともない。JavaScriptのレベルで目的を達成するやり方として、僕は最近よく、こんな設計をしてる。
function MyController(aNode) {
this._node = aNode;
this.init();
}
MyController.prototype = {
get property() {
...
},
set property(aVaule) {
...
},
method : function() {
...
},
init : function() {
this._node.addEventListener('...', this, false);
...
},
destroy : function() {
this._node.removeEventListener('...', this, false);
...
},
handleEvent : function(aEvent) {
...
}
};
var node = document.createElement('box');
node.controller = new MyController(node);
かなりの部分はこういったやり方で目的を達成できると思う。ツリー型タブなんかもこれに近い実装になってる。
ただ、オートコンプリートのテキストボックスの挙動を変えるだとかの、本体で定義されている物を置き換える場面では、たまにこの方法だけでは不十分なことがある。例えば<textbox type="autocomplete" />
な要素のmaxDropMarkerRows
や<panel type="autocomplete" />
な要素のoverrideValue
やなんかはreadonlyなプロパティとしてXBLで定義されてしまっているので、これらが返す値を変えたいと思うと結構厄介な事になる。
XBLを使わないでサクッと済ませようとすると、__defineGetter__()
を使う方法がまずは思い浮かぶ。Firefox 3.0以降ではDOMノードに対して__defineGetter__()
を使えるので、上記の例のコードのinit()
あたりでそれを使ってやるという感じだ。実際、XUL/MigemoではoverrideValue
で任意の値を返すためにそうしてる。
でも、この方法はできれば使わない方がいいのかもなと思ってる。そう思ったきっかけは、同じようなことをやるコードの自動テストを書いていた時。setUpとtearDownで毎回ウィンドウを開いたり閉じたりとやってると時間がかかってしょうがないからUxU組み込みのフレームにページを読み込ませて……という風にしてみたら、セキュリティの制限に引っかかってしまった。object.__defineGetter__(name, getter)
のobjectとgetterの属してる名前空間が違うと、Illegal valueとか言われてエラーになってしまった。それでTrunkでの__defineGetter__()
の実装を見てみたら、この両者のコンパートメントが違う場合はゲッタの登録を拒否するような設計になってた。こういうセキュリティの制限を回避してreadonlyなプロパティの働きを置き換えようと思ったら、どうもやはり、XBLを使うしかないようだ。
そもそもなんでoverrideValue
がreadonlyなんだよ、なんで書き換え可能なただのフィールドになってないんだよ、って思って来歴を調べてみたら、nsIAutoCompletePopupインターフェースのoverrideValue
は昔のオートコンプリートの実装におけるgetOverrideValue()
メソッドがその祖先で、当時のコードには「こいつの働きを変えたかったらXBLでオーバーライドしろ」ってコメントが書いてあった。今のFirefoxのオートコンプリートの実装にはこのコメントがなかったので、なんでreadonlyになってるんだよという不満しか抱きようがなかった。getXXXとなってたメソッドをプロパティの形に置き換えるなら、確かにそれはreadonlyになるだろう。
でもどうせプロパティに変えたんだったらwritableにしたってよかったはず。ほんとに、何でこんな設計にするんだろう……
かつてIE6が一番先進的なブラウザだった頃は、ブラウザに機能を加える物といったら、「ツールバー」か「コンテキストメニューの追加項目」くらいしかなかった気がする。そもそも、「ブラウザにツールバーを追加できますよ」だけでもずいぶんすごいことであったような気がする。僕がそれ以外を知らなかっただけかも知れないけど。(具体的に僕が「その頃」の代表的なブラウザとして今思い浮かべているのはIE6とNetscape Communicator 4.xです。)
その頃は、MicrosoftとかNetscapeとかがリリースしてるメジャーなブラウザの使い勝手に不満があっても、プログラミングの知識がないフツーの人は、我慢するか、既にある別のブラウザ(iCabとかOperaとか)を探すかしか無かった。そもそもこの時代、ちょっと前までブラウザは4000円とか9000円とかお金払って「買う物」であったから、基本的には「買った物をそのまま使うか、買わないか」という選択肢しかなかったとも言える。
それでも、腕に覚えのある人なら、コアであるレンダリングエンジンにはIEの物を流用して、それ以外のブラウザのUI自体を頑張って全部作り直すということは可能だったようだ。それで出てきたのがDonutだったりSleipnirだったりLunascapeだったりのいわゆるIEコンポーネントブラウザだった。メジャーなブラウザがリリース計画とか顧客とか組織とか色んな都合で足踏みしている間に、個人の開発者あるいは小規模な開発チームであるが故のフットワークの軽さによって、凡庸なメジャー製品では手に入らなかった「痒い所に手が届く使い勝手の良さ」を提供したことで、それらIEコンポーネントブラウザはパワーユーザの支持を得るに至ったのだろう。
IEを使うのなら、できる「機能拡張」はせいぜいツールバーの追加かコンテキストメニューの機能追加くらい。なぜなら、IEが開発者向けに開いていた「ブラウザの個々に機能を追加できますよ」というポイントが限られていたから。(あるいは、もっと色々できたのかもしれないけど、それくらいしかできないという印象が強かった。)それ以上の物が欲しかったら、そういう機能を提供するIEコンポーネントブラウザに乗り換えるしかない。それが、あの頃に取り得た現実的な選択肢の全てだったのだと思う。
僕にとっては、そういう「暗黒時代」はMozillaとの出会いで終わった。当時はまだMozilla Suite(Seamonkey)がメインラインで、バージョンはM16とか0.6とか言われてた頃だったか。当初はCSS2に一番真っ当に対応してたブラウザだったからという理由で使い始めたけど、使い続ける理由はいつの間にか、「一番痒い所に手が届くから」になっていた。
Ben Goodger氏が当時を振り返って語ったエントリにもあるけれど、Mozillaの設計上の特性からくる「拡張性の高さ」はホントにぶっ飛んでた。
Mozilla以前は、
「標準のUIに不満がある? Googleサジェストが使える検索ボックスが欲しい? じゃあ、このツールバーを追加して下さい。ほら、このツールバーの中でならもっと快適に過ごせますよ! Googleサジェストの結果もポップアップされますよ! まあ、このツールバーの世界から一歩でも外に出ると、また今まで通りの世界に逆戻りですけどね。」
「え、このボタンだけ切り離してウィンドウの下の方に置いておきたい? そんなことできるわけないでしょ。この素敵な便利機能は、このツールバーの世界から外には持ち出せませないんですよ。」
「え、専用のツールバーなんかいらないから、本来のアドレスバーから色んな検索エンジンでWeb検索できるようにしてくれって? そりゃ無理ですよ。このツールバーの枠の中の事ならどうとでもできるけど、枠の外は元々作られてた通りにしか動かないんだもの。」
こうだった。ツールバーという細長い箱の中、コンテキストメニューというメニューの中、そういう様式の中でないと何もできなかったっぽかった。
あるいは、こうだった。
「このブラウザに乗り換えたら、こんな便利な機能が使えますよ!」
「え、こっちのブラウザのこの機能が欲しいって? そんなこと言われても、それは別のソフトだし……正直、そんなもん知らんがな。」
「パソコン盗まれてソースコードが失なわれちゃいました! もうメンテナンスできません!」
でもMozillaではそうじゃなかった。
「標準のUIに不満がある? じゃあ、そこをピンポイントで解決しちゃえばいいよ。」
「ボタンはウィンドウの上じゃなくて下の方にあって欲しい? じゃあそうすればいいよ。ほら、ツールバーのボタンをウィンドウの下に移動できるようになった。」
「アドレスバーから色んな検索エンジンで検索できるようにしたい? じゃあそうすればいいよ。ほら、GoogleやAmazonの検索結果がアドレスバーの履歴と一緒に表示されるようになった。」
こうだ。実にシンプルだった。「イラッ」と来たまさにその点を、イメージしていた通りに、一番ストレスのない形で解決できる。ツールバーという細長い四角い枠の中であるとか、たった1人の作者の都合であるとかに、囲い込まれなくてよかった。使いたい機能を使いたいように組み合わせて使えた。
具体的な例をもっと挙げると、例えば、ツリー型タブとマルチプルタブハンドラと情報化タブの併用みたいなことができるのかどうか、ってことなんですよ。
タブで開いてるページをツリー表示するポップアップを表示する拡張機能。うん、それは多分便利。
開いてるタブのリストを表示して、任意のアイテムを選択してまとめて操作するポップアップを表示する拡張機能。うん、それも多分便利。
開いてる全てのタブの内容をサムネイルで一覧表示するポップアップを表示する拡張機能。うん、それも多分便利。
で、それを1つにまとめて同時に使えるの? ツリー表示されていて、気が向いたらそれを複数個選択してまとめて閉じられて、それらには常時サムネイルが表示されてる、というソリューションは誰でも得られるの? エンドユーザでも? って事なんですよ。
「ツリー表示してサムネイルも表示して複数選択もできるUI、を提供する1つの拡張機能」があればいい? 「ツリー表示してサムネイルも表示して複数選択もできるUI、を持ったIEコンポーネントブラウザ」があればいい? そういう物がもしあるのならそれを使うのもいいだろうし、作れる能力があるのなら作って全然いいと思う。でも、そういう物が無かったらどうなのか? 誰も作っていなかったら? そして自分でそれを作る知識は無いというのなら?
また、3つの機能を持った物くらいならともかく、要求事項が4つ5つと増えていったらどうなるか。条件が増えれば増えるほど、既製品で要求を完全に満たす物は見つけにくくなるだろう。妥協が増えてくるだろう。その逆に、個々の要求事項を満たす物を集めてきてそれで1つの物として使えるのなら、何も我慢しなくて済む。
だから僕は、Firefoxを捨てられないんだ。「Operaならあれもできるよ、これもできるよ」って言われても、「Chromeなら爆速だよ」って言われても、「ほうほう、ではこのポップアップパネルの下の端にこれこれこういうボタンを置いておきたいんだけど、そういうことはできるのかね? え、できない? ああそう……それじゃ日々の『イラッ』はなくならないなあ」と思ってしまうんだな。(だからFirefoxが好きなんですよ、っていうのが全く無いとは言い切れないけど、むしろ、他の物もそうだったらいいのに、でも残念ながらそうじゃないから諦めてFirefox使い続けるしかないのか、って思ってる所も結構ある。)
でも、時代は今また「用意された枠の中でならなんでもできますよ、でもそこからは一歩もはみ出せませんよ」の方向に戻ろうとしている。(いや、あの頃に比べたらずっと洗練されたAPIで、できることの幅も広がっているようなのだけれども。)何故なのか。
理由はたくさんあるようだけど、多分一番重要なのは、「みんな、そんなに自由でなくていい」って事なんだろうね。
頑固で融通の利かない馬鹿で順応性が低い僕にとっては、こうだ。「イラッと来たまさにその部分がピンポイントで解決されてくれないと、我慢ならない。ちょっとでも遠回りしないといけないのは、もう嫌。このツールバーの中でならそんな不満は起こりませんよ、なんて言われても、ツールバーなんていらんし。そんなん興味ない。今目の前にあるコイツがどうにかなってくれないと嫌なの。」でも、普通の人は僕なんかよりもっと頭が柔らかくて順応性が高いから、苦にならないんだろう。「こういうとこが不満で、なんとかして欲しいんだけど。え、代わりにこれを使わないといけないの? ふーん、まあ、いいけど。」で受け入れてしまえるのだろう。
(というか多分そもそも「こういうとこが不満で」なんて思わなくて、今ある物でだいたい満足できてしまうんだろう。思い入れも無ければ、一日中それと接するわけでもない、1日1回1時間くらいしかブラウザを操作しない、だからそんな深刻な不満を抱きようがないんじゃあないかな。頭が固いとか柔らかいとか以前に。)
(あと、これは、「普通の人」を揶揄する話ではない。そういう人達の方が環境の変化に柔軟に適応できる能力があるって事で、それは人として素晴らしいことだと僕は思う。こうやりたいと決めたやり方以外だとストレスを感じてしまうとか、そういうどうでもいいことにこだわってしまう性根というのは、人として問題があると思う。)
Jetpackがrebootされる前、まだJavaScriptのファイルいっこで完結してた頃、僕は「JetpackはGreasemonkeyやChromeの拡張機能とAPIのレベルで互換性を設けるべきだ」と強く思っていた。なので今回のOperaの「拡張機能を標準化しよう」っていう話に僕は賛成する。既に、ChromeとSafariの間では既にそういう状況になっていると聞いた気がする。大抵の人がそれで満足できる、ツールバーのボタンなり、Webページ内で自動実行されるスクリプトなり、そういう「どのブラウザでも共通して利用できそうな物」には、互換性があっていいと思う。僕だってひょっとしたら、ユーザになるかもしれないし。OperaやSafariをメインで使ってる人が作った拡張機能の恩恵に僕がFirefoxで与れる、そういうことがあり得るかも知れないし。
ただ、Firefoxと同じくらいになんでもできる環境になってくれない限りは、僕自身は軸足をChromeとかその後に出てくる物とかには移せないんじゃないかなー、と思ってる。些細な「イラッ」に目を瞑らずにストレートにそれを解消することができる、という居心地の良さに、僕はあまりにドップリと浸かりきってしまっているので。あまりに「自分を変えなくていい」事に慣れきってしまっているので。
この間悩まされたばかりのomnijarの話である、Taras’ Blog » Blog Archive » Firefox 4: jar jar jarの勝手翻訳です。分からない所が多かったので創作的意訳が多めですが気にしません。
ファイルを開く処理というものは、システムコールのための小さなオーバーヘッドや、ディスクからのデータの先読みによる大きなオーバーヘッドがある、比較的重たい処理です。データの物理的な配置の状態やディスクの種類によっては、この処理は今時の高速なCPUに対して、複数の異なるファイルそれぞれの全ての断片を先読みするためにディスク上をヘッドが激しく行き来する間、長々と無駄に暇をもてあまさせる事にもなり得ます。
約2年前、私(訳注:元エントリの著者のTaras Glek)はディスク上にある裸のファイルを収集してjarファイルに詰め込む作業を始めました(例:bug 508421)。また、コードのクリーンアップやmmapへの移行などを通じて、私達はjarファイルの読み込み処理の効率も可能な限り改善しました。そして最終的には、通常の起動時にディスクから読み込まれていた、アプリケーションを構成するデータファイルの全てが、jarファイルの中に格納されるようになりました。しかし残念ながら、私達は4つのjarファイル(toolkit、chrome、そして2つのロケール用jarファイル)に構成ファイルをまとめる所までしか至れませんでした−−何ともマヌケな事ですけれども。またXPCOMの制限により、多数の裸のファイルがまだ、Firefoxのアップデートや拡張機能のインストールの度にディスクから読み込まれる状態となっていました。
最近、Michael Wuがomnijarという<ruby><rb>滅茶苦茶にヤバい物を解き放ち</rb><rp>(</rp><rt>非常に影響範囲の大きな変更を導入し</rt><rp>)</rp></ruby>ました。これはAndroid用パッケージ作成での必要に迫られて行われた非常に大きな取り組みです。今や、アプリケーション起動時に必要なデータは常に単一のファイルから読み込まれるようになりました。これはデータの位置の集約化、ファイル走査の手間の短縮、そして待ち時間の短縮に繋がります。複数のファイルを1つに小さくまとめる事の1つの利点としては、OSがディスクからデータを読み込む際に、大抵の場合はアプリケーションが要求したよりも大きな単位で、投機的にディスクからデータを読み込む事も挙げられます。これにより、隣接するファイルの読み込みがより自由に行えるようになります。残念ながら、Firefoxを実際に実行せずにファイルアクセスの順番を予測するための良い方法がなかったため、ここにはさらなる改善の余地があります。
さて、今や全てのデータが1つのファイルの中に置かれましたので、論理的な次のステップは、それらをより賢く格納する事でした。それを行うための唯一の方法は、Firefoxの起動時の処理を分析して、jarファイル内のファイル配置をその結果に基づいて並べ替える事です。残念な事に、jarファイル内の全ての項目を連続的に配置するにあたって、私達はまだ次善の策を取っています。これは、ZIP(jarファイルの実態はZIPファイルです)のインデックスが伝統的にファイルの終端に配置されている事に依っています。Wikipediaの記事にこれを図示した物があります。
先読みの利点を最大限に活かし、ディスクの走査を最小限にするためには、格納されたファイルのインデックスがZIPファイルの先頭に置かれているのが望ましいです。そのため、私は私達のZIPファイル内のデータの配置を、
<項目1><項目2>…<項目N><インデックス><インデックスの終端>
から
<最初に読み込まれる、最後の項目の位置のオフセット値><インデックス><インデックスの終端><項目1><項目2>…<項目N><インデックスの終端>
に変更しました。
ここで私がやったのは、<インデックスの終端>のオフセット値を常に4になるようにした(どうでもいい事に拘るZIPアーカイバはインデックスのオフセットがNULLだとフリーズしてしまう事があるので、これは0にすることはできません)だけです。その際、インデックスは常に前のインデックスの終端に続けて配置されなければならないという仕様を満たすために、私は最初の物と全く同じ2つ目の<インデックスの終端>を加えました。また、私は、過度に用心深いZIPアーカイバによって強制された、どれだけのデータを最初に先読みしておく事ができるかを示す数値を格納するための追加の余白も利用する事にしました。
これによって、最適化されていないomnijarに比べてディスクI/Oの2〜3倍の削減を実現しました。これは裸のファイルをomnijarにすることで20〜100倍以上の高速化が実現できた事の最大の要因です。
私がZIPの仕様を読んでガッカリしたのは、ZIPアーカイバの中にはZIPファイルが仕様が許容しているよりもずっと厳格な形式になっている事を期待している物があるということです。以前のバージョンのFirefoxや、Microsoft WindowsのZIPサポート(訳注:圧縮フォルダ)、WinRAR、UNIXのZIPアーカイバなどは、私の最適化されたjarファイルを受け付けてくれますが、7-Zipや壊れたアンチウィルスソフト(スキャン対象を無闇矢鱈に限定する事はセキュリティ上危険です)はこれらを開く事ができません。
豆知識:これは、受け付けるZIPファイルの内容を選り好みするソフトウェアのせいで困らされた最初のケースではありません。例えば、Android標準のAPK読み込み処理は、Android用のパッケージが0バイトの大きさの項目を含んだZIPファイルである場合に、いちいちしつこくそれを警告してきます。これは、APK形式のファイルをWindowsにおける自己解凍形式のEXEファイルのように使う事ができないということです。Michael Wuはこの問題を解決するための独自の読み込み用ライブラリを書いている所です。
omnijarは十分に素晴らしいとはまだ言えないということで、Michael Wuはさらに先に進んで拡張機能をomnijar化しました(訳注:アドオンがXPIのままで認識されるようになった事を述べたエントリ)。ほとんどの拡張機能はXPIから裸のファイルに展開される必要が無くなる事でしょう。これは、拡張機能の作者が上記のような最適化されたjar形式を、Firefoxの起動を高速化するために利用できるという事を意味します。
起動処理の高速化用のキャッシュをjarに切り替える事によって、私達は最初の起動処理をさらに最適化できるようになることでしょう。私が最適化されたjarファイルに加えた先読み用の情報を実際に利用する事によって、jarファイルのI/Oを半減できる可能性もあります。
2点だけ:
1つ目。あなたがどのように言おうと、これらの「最適化された」jarファイルはもはやZIPファイルではありません。仕様は非常に明確で、インデックスはファイルの末尾になくてはなりません。
あなたがやった事に間違った点はありませんが、しかし、それらのファイルをZIPであるかのように偽ろうとした上で、ZIPを扱うアプリケーションがそれらに対して文句を言わないようになる事を望むというのは、良い事ではありません。それよりもあなたは、最適化されたファイルの拡張子を変更することにして、最適化とその解除を手動で行うためのツールを提供した方がよいでしょう。
私は、例えば7-Zipは仕様に対して非常に厳格に設計されているがために、これらのファイル形式をサポートする事はあり得ないだろうと確信しています。
2つ目。私はFirefoxのコールドスタート(訳注:キャッシュ等にファイルが読み込まれていない状態からの起動)が(特に他のブラウザと比べて)遅いということを体感して、少し調べてみる事にしました。新規作成したプロファイルであってもまだ起動は遅かったです。私は、Firefoxは起動時に18個ものDLL(それらは全て、Firefox/Minefieldのインストール先ディレクトリに置かれています)を読み込んでいるという事に気がつきました。これは非常に良くない事で、ファイルの数の問題だけでなく、Windowsではセキュリティ対策ソフト(既定の状態であればWindows Defender、大抵の場合は何らかのアンチウィルスソフト)においての問題もあります。
私のマシン上では、ファイルのスキャンは1つあたりだいたい50ミリ秒を要していますが、この処理時間はファイルサイズには影響を受けません。Firefoxの場合、起動に数秒を要してしまいます。セキュリティ対策のソフトが大抵の場合は必要が生じた際の動的なスキャンの結果をキャッシュしていて、再起動されるか定義ファイルが更新されるまでの間は再スキャンが行われないために、この問題がウォームスタート(訳注:ファイルがキャッシュ等に読み込まれた状態からの起動)の場合には起こらないという事には注意して下さい。
いくつかの他のブラウザは、起動時にDLLを2つだけ読み込んでいて、他は必要が生じた時(動画の再生、WebGLを使う場合など)に動的に読み込んでいます。これはFirefoxの場合よりもずっといい具合に動作しているように思われます。
検索してみた所、私はLinuxについてのこの件に対するバグをいくつか見つけましたが、Windowsについてのバグは見つかりませんでした。それらが1つの大きなDLL(xul.dll)とそれ以外の小さなDLL(それらは無条件に読み込まれる)となっているので、全てを1つのファイルにまとめるのは理にかなっていると思います。
Ben、あなたが注目した多数のDLLを提供している点についてはあなたの指摘は的を射ています。それについてのバグは https://bugzilla.mozilla.org/show_bug.cgi?id=561842 です。残念ながら、私達はこの作業をFirefox 4のリリースには間に合わせられませんので、後のリリース版で反映される事でしょう。
私はアンチウィルスソフトが起動時の処理に与える影響についても、より詳しい情報を得たいです。