2012-12-28
bfcache について覚えて帰ってもらいます。(転載)
JavaScript, Firefox, Webkit |
以下の内容は、『 mixi Engineers’ JavaScript Advent Calendar 2012 』の12月19日分として投稿したものの転載です。内容に差異はありません。
bfcache について覚えて帰ってもらいます。
こんばんは。
日々、一体お兄ちゃんだけど愛さえあれば関係ないのかどうなのか、そこのところについて確認作業を怠らないものです。
今日、偶然にもこれを閲覧してしまったみなさまには、近代ブラウザの誇る謎機能、 bfcache について覚えて帰っていただきます。どうぞよろしくお願いいたします。
bfcache (正式名称なのか、別名なのか定かではありませんが Back-Forwad Cache とも呼ばれます)をおもむろに Google さんに検索いただくと、権限のありそうな回答は
Using Firefox 1.5 caching(翻訳あり)
と
Working with BFCache(英語のみ)
です。どちらも Mozilla Developer Network の記事です。
さもあらんこの bfcache というのは、 HTML / DOM / CSS / ECMAScript 等の仕様でもなんでもなく、 UA であるブラウザ側が、ユーザの利便性を高めるために独自に実装しはじめたものです。その名前が表すとおり、戻る・進むボタンを押してページ遷移した際の体感速度を向上させてくれるもので、おそらくは Opera が最初に実装したものですが、 bfcache という名前をつけて、その機序(と回避方法)を正確に世間に表明したのは Firefox が最初でした。現在では、 Firefox と Webkit で、このふるまいをするという意味での狭義の bfcache が実装されています(追記: Webkit では "Page Cache" と呼ぶようです。原文頁末の kzys さんのコメントをご参照ください)。
そして、その時の記事( Firefox 1.5 の実装です)が、依然として最も権限のある記事になっています。そしてこの記事の不完全性が、 stackoverflow においてすら
「 bfcache を避けるにはどうしたらいいんだ?」
「 onunload イベントを付けろ」
「回避できない!」
「いや、俺は回避できた!」
「俺はできない!」
「俺はできる!」
「バーカバーカ」
「俺を…腰抜けって呼ぶんじゃねえ!」
という不毛な悲劇を数多く生んできました。
ぜひ、賢明な読者のみなさまにおかれましては、ここでこの bfcache について正確なところを把握して帰っていただきたいと思います。
bfcache とは二段階あります。
いきなりの最終回答、誠に恐縮ですが、世に「 bfcache 」と呼ばれるものには、その効果及び回避方法の面から言うと、二種類あります。
1つは、おそらく皆様もよくご存知の、
- onload ハンドラのスキップ
- JavaScript オブジェクト状態の保存
が行われる段階です。JavaScript アプリケーション開発においては、こちらがよく注目されます。
次に2つ目は、
- ブラウザのネットワーク層での暗黙リソースキャッシュ
です。こちらは、いわゆる「キャッシュを削除してください」のあのキャッシュなのですが、
「このリソースはキャッシュしてもよいものだ」かつ、
「そして今この状況はそのキャッシュしておいたリソースの複製をユーザにレスポンスすべきだ」
とブラウザが判断した時に、暗黙のうちに、ブラウザが内部的に保存しておいたリソースの複製を、ブラウザから上のレイヤーにとってはあたかもサーバが200番でレスポンスしたかのように、蘇らせる( HEAD 、 GET 等のネットワークアクセスは発生しない)。というものです。これも bfcache なのか?。と思うのですが、発生のタイミングが主に history トラバーサルで、かつ Firebug がこのリソース複製のレスポンスを bfcache と呼んでいることから、ここでは bfcache として扱います。
mixi では、いわゆる「ホーム」である home.pl 上で JavaScript が数多く動作するようになってから、1つ目の bfcache については対応を行って来ましたが、長らく、2つ目の bfcache については対応できておりませんでした。
そのため、
「 mixi 見る」
「通知エリアでイイネ!通知だ!」
「わーいクリック」
「通知消える」
「気になるニュースをクリックして遷移」
「ホームに「戻るボタン」で戻る」
「通知エリアでイイネ!通知だ!」
「わーいクリック」
「さっきと一緒じゃねえか!」
「 mixi オワコン」
といった悲劇を招いていました。
とはいえ、まずは1つ目から順番にご説明したいと思います。下記の PHP をご覧ください。
<!DOCTYPE html> <html> <head> </head> <body onload="document.getElementById('test').innerHTML='JS time:'+(new Date()).toString();"> <?php echo "PHP time:" . date("Y/m/d g:i:s"); ?> <p id="test"></p> <p><a href="modoru.php">traverse</a></p> <script type="text/javascript"> var kido = { ibuki : null }; window.onpageshow = function(evt) { if ( evt.persisted ) { alert("yay! bfcache!"); } }; </script> <button onclick="kido.ibuki='wow'">set</button> <button onclick="alert(kido.ibuki);">alert</button> </body> </html>
これは
- サーバ側の時刻を php から出力。
- クライアント側の時刻を onload ハンドラ内で出力。
- インライン JavaScript でオブジェクトを生成。
- ボタンクリックでそのオブジェクトのプロパティ値を変更できる。
というものです。
サンプルを表示しましたら(できれば Firefox が望ましいです)、
- 「 set 」ボタンを押し、
- 「 alert 」ボタンを押して "wow" と alert されるのを確認し、
- 2行目の、 JS から出力された時刻の秒のところに注目し、
- 「 traverse 」リンクをクリックし
- 遷移した先のページで、「戻るボタン」で戻ってください。
どうですか?。 "yay! bfcache!" と表示されましたか? JavaScript の時刻の秒のところが、先ほどと同じではありませんか?。そして、「 alert 」ボタンを押すと、依然として "wow" と表示されませんか?
これが、1つ目の bfcache です。これらはつまり
- インライン JavaScript 実行のスキップ
- onload ハンドラ実行のスキップ
- JavaScript オブジェクトの保存
- DOM 状態の保存
が行われ、それを示すように、
- window.onpageshow イベントのイベントオブジェクトの persisted プロパティが true
になった、ということを示しています。
え?。ならない?。「戻るボタン」で戻ってきても、 "yay!" って言われない?。秒が毎回更新される?。戻ってきたあとに「 alert 」ボタンを押すと、 "null" と表示される?
あるんです。そういう場合。特に、(おそらくですが)ネットワークと CPU が「十分に速い」とブラウザが判定した場合、つまり、
「このリソースはキャッシュしてもよいものだ」かつ、
「そして今この状況はそのキャッシュしておいたリソースの複製をユーザにレスポンスすべきだ」
という2つの条件のうち、良い意味であっても後者が満たされないとブラウザが考えた場合は、この1つ目の bfcache 処理は行われない場合があります。恐ろしいですね。誰かソース見てあとで僕に教えてください。
ここまでが、 bfcache 第1段階目の仕組みです。これを回避する方法は簡単で、
- onunload イベントにフックする。
です。これは、 bfcache の第1段階にのみ有効な回避方法で、かつ、空のハンドラでも良いので、とても簡単です。
こちらが body タグに onunload 属性をつけたものです。中身は空です。
これで、第1段階目の bfcache は絶対に起こらなくなりました。 window.onpageshow イベントの event.persisted は常に false ですし、 JS 秒も毎回更新されます。おめでとうございます。
さて、次が2つ目の bfcache です。まず、先程の onunload イベントにフックした例を、もう一度ご覧ください。今度は「 PHP time 」というところに注目して、
- 「 traverse 」リンクをクリック
- 「戻るボタン」で戻る
を行なってみてください。 onunload イベントにフックしたおかげで JS の出力する時刻は更新されても、 PHP から出力されている時刻は更新されていないと思います。今度は、どんなに速いマシン、太いネットワークでもなるはずです。
え、当たり前だぁ?。本当にこれ、当たり前だと思いますか?。では、次のソースを見てください。
<?php header("Cache-Control:no-cache"); ?> <!DOCTYPE html> <html> <head> </head> <body onload="document.getElementById('test').innerHTML='JS time:'+(new Date()).toString();" onunload=""> <?php echo "PHP time:" . date("Y/m/d g:i:s"); ?> <p id="test"></p> <p><a href="modoru.php">traverse</a></p> <script type="text/javascript"> var kido = { ibuki : null }; window.onpageshow = function(evt) { if ( evt.persisted ) { alert("yay! bfcache!"); } }; </script> <button onclick="kido.ibuki='wow'">set</button> <button onclick="alert(kido.ibuki);">alert</button> </body> </html>
Cache-Control:no-cache をレスポンスヘッダに載せることにしました。実際に動くものはこちら。
つまりこれは、 Cache-Control:no-cache と、 onunload イベントの合わせ技です。強そうです。どんなキャッシュも寄せ付けなさそうな感じがします。それでは、ぜひ
- 「 traverse 」リンクをクリック
- 「戻るボタン」で戻る
を行なってみてください。
どうですか?。 PHP 時間が更新されましたか?。おそらく、されないはずです。これが bfcache の第2段階です。 Firebug のネットワークパネルで、
「 BFCache のレスポンスを表示」というオプションを選択すると、ステータスのところに (BFCache) と明示されるようになるのが、この、リソースに対する bfcache です。
これは、 HTML だけではなく、 JS 、 CSS 、画像、すべての HTML 描画に関するリソースに適用されているようです。どこかのページで Firebug を有効にして、戻るボタンで戻ってみてください。結構多くのネットワークリソースが bfcache からレスポンスされて、実際にはネットワークリクエストが飛んでいないことがわかります。
考えなくても明らかなように、これは露骨にページ表示速度を向上させます。何しろ、 HEAD の確認にすら行きませんし、どうやらこれはオンメモリキャッシュのようです。そりゃ高速です。でも、困る時があるんです。
最近の JavaScript アプリケーションでは、 mixi のホームもそうですが、最初に HTML がサーブされた状態と、その後 JavaScript によってコンテンツが追加・更新・変更されていった状態の間には、結構な差異が生じます。
リソースの bfcache が有効になっていると、「戻るボタン」で戻ってきた際にこの差異が全部吹っ飛んでしまい、最初にネットワーク越しにちゃんとレスポンスされた HTML の状態に戻ってしまう。というわけです。
これには、 mixi では通常 onunload イベントにフックして、第1段階の bfcache を回避していることも影響しています。 bfcache をすべて受け入れるように設定されていれば、基本的に、 JavaScript によって追加されたコンテンツも、そのまま蘇ってくるはずだからです。
しかし、 bfcache が最初の登場した頃のブラウザ、特に Safari は、この実装が非常に雑で、(第1段階の) bfcache を有効にしたまま、セキュアな JavaScript アプリケーションを作成することが、とても困難でした。言い換えれば、簡単な対策であっても、一度それをしてしまうと、それ以降の開発に足枷をはめるものでした。
とはいえ今なら、 bfcache を最大限に活かし、 event.persisted プロパティをチェックし、 bfcache から蘇った JS アプリケーションでも、上手に動作させることは可能だと思います。ここのところは、ひとえに我々の技術不足と、大規模開発における制約があると感じます。
話を戻しますと、リソースの暗黙 bfcache がネックになるのは、 XHR など何らからの手法で取得する JSON データもそうかもしれません。最後にネットワーク越しにサーバから取得された JSON をいつもブラウザが保存していて、ユーザがどこかのページに行ってしばらくしてからおもむろに「戻るボタン」で戻ってきた場合も、その、最後の JSON (ずいぶん時間が経っていることでしょう)が、いかにも最新のふりをして、しれっと200番でレスポンスしてくるのです。
これを回避する方法は2つあります。
- Cache-Control:no-store をレスポンスする
- https プロトコルで、 Cache-Control:no-cache をレスポンスする。
です。 meta タグの http-equiv は通用しません。 HTTP レスポンスヘッダに載っていなければ駄目でした。また、ホストドキュメント( html )にだけこれを行うと、その html リソースだけ、常にネットワークから来るようになります。その HTML が使用する外部 JS や CSS ファイルには影響しません。影響させたければ、それらのリソースのレスポンスにも、この対策が必要です。
とりあえず、証明書高いんで、自分のドメイン用に持ってないんで、今回は no-store を載せてみます。ソースは以下です。
<?php header("Cache-Control:no-cache,no-store"); ?> <!DOCTYPE html> <html> <head> </head> <body onload="document.getElementById('test').innerHTML='JS time:'+(new Date()).toString();" onunload=""> <?php echo "PHP time:" . date("Y/m/d g:i:s"); ?> <p id="test"></p> <p><a href="modoru.php">traverse</a></p> <script type="text/javascript"> var kido = { ibuki : null }; window.onpageshow = function(evt) { if ( evt.persisted ) { alert("yay! bfcache!"); } }; </script> <button onclick="kido.ibuki='wow'">set</button> <button onclick="alert(kido.ibuki);">alert</button> </body> </html>
実際に表示してみてください。 PHP 時間も、 JS 時間も、必ず更新されます。「戻るボタン」で戻ってこようが、普通に遷移してこようが、必ず更新されます。
これが、つまり、「良かれ悪しかれ」、 bfcache を完全に回避した状態です。
ちなみに、手元の Firefox では、この2段階目への回避策を行うと、同時に1段階目も回避されるようです。Cache-Control:no-store で、 onunload へはフックしない例を用意してみました。いかがでしょうか。
以上が、今日覚えて帰っていただきたい bfcache の内容です。なんだか偉そうに説明しましたが、結局のところ、
「 history.back には no-cache は通用しなかった! no-store だった!しかもずっと前から!!」
という一言に尽きるわけです。
最後にブラウザごとに BrowserStack で試した感じですが(追記: 頁末の kzys さんのコメントもご参照ください)
Firefox では BrowserStack で試せる一番古い 3.0 から、綺麗に上記のとおりになっています。軽いサーバで第1段階がスキップされることもありました(でも、本当に軽さで決まっているかは、僕はソースを見ていないので確かなことは言えません)。
Chrome では、どうもわかりません。ひょっとしたら bfcache としてではなく、通常のキャッシュ機構として、上記で言うところの第2段階目の bfcache を実装しているのかなと思います。やはり history.back に対しては no-store じゃないとだめです。第1段階の bfcache は今は無いんじゃないかと思います。
Safari はますますよくわかりません。昔々、 1.x の時は、確実にありました。 mixi でもそこにハマって困ったことをよく覚えています。でも、今は Chrome 同様そもそも無いような感じがします。
IE はありません。10になってもありません。
以上、挙動・ふるまいから bfcache について調べてみました。ブラウザのソースを読むのが趣味の方、いらっしゃいましたら、ぜひこのところ、 firefox と webkit でどういう実装になっているか、教えていただけたら心の底から嬉しいです。
どうぞよろしくお願いいたします。
追記
でも、正直 no-store と no-cache の扱い、納得いかなくないですか?。と思いまして、 HTTP1.1 の RFC2616 を見てみたところ、
「ヒストリ機能はキャッシュ機構とは違う」
「ヒストリ機能はキャッシュ機構の期限に従う「べきでない」」
とか書いてありました…。そりゃ、 bfcache に no-cache 効かないです(効かないようにブラウザベンダも実装します)よね。だってこれ、1999年に標準化された仕様の話なんですもの…。
2011-10-02
Wikipedia が広すぎる。
WIkipedia ってリキッドなのは良いけど、ブラウザ全画面派には横幅が広すぎて文章読むのつらくなイカ?。特に自宅だとディスプレイが27インチなので、一行読むのに首が動いてしまいます。というわけで Stylish で狭くしました。
@namespace url(http://www.w3.org/1999/xhtml); @-moz-document domain("wikipedia.org") { body { width:900px; margin:0 auto!important; } div#content { margin-left:0!important; } }
というどうでもいい CSS を書いたでゲソ。ちなみに Stylish で作成すると @namespace が必ず付くけれど、これは必要あるのでゲソ?
2011-09-10
Pentadactyl を入れてみる。
Firefox |
http://dactyl.sourceforge.net/pentadactyl/
今のところのメインブラウザ(ネットサーフィンブラウザは Opera だけど)である Chrome に vimium を入れて楽しんでいたら、だんだん手が馴染んできました。
で、それは良いんですけども、会社のメイン環境である Ubuntu では Chrome さんの OTF フォントの扱いが雑でおかしなことになってしまうので、 Firefox がメイン。とすると、やはり vim ライクキーバインドを使いたい次第。とはいえ vimperator のあまりの overwhelming 感には辟易していた私でしたので、今回は Pentadactyl とやらを入れてみました。
まあ、開発してる人がかぶっていたりして、結果あまり使い勝手は変わりないのですが、仕方ないので慣れることにしました。自宅もメインを Firefox にして、自分改造完了です。
ちなみに .pentadactylrc は超シンプルです(後記:使ってるうちに、だんだん増えてきました…)。最初に mkp して出力したファイルをちょっとだけ編集しています。
" 自動生成したらできた奴ら loadplugins '\.(js|penta)$' group user " なんとなくナビゲーションツールバーを使うことが多いので縦が無駄になるけど出す。 set guioptions=T " jとkのスクロール量が足らないので、j/kはスクロール量を調整できるC-d/C-uに当ててしまって適用。j/k自体のスクロール量は変更できないらしい。 set scroll=5 noremap j <C-d> noremap k <C-u> " dでタブを閉じたとき、tab mixなどの「閉じた後の挙動」を無視して必ず右側のタブがフォーカスしてしまうので、dはC-wに当ててしまう。 " 前方カウント付きで複数タブを同時に閉じたときに挙動が怪しくなるが複数タブを同時に閉じることがないのでこれで問題ない。 noremap d <C-v><C-w> " ナビゲーションツールバーが欲しいときがあるので、ワンキーでトグルできるように。 map <C-t> :toolbartoggle ナビゲーションツールバー<CR> " tumblr ではjklrを透過 set passkeys+=tumblr.com\/dashboard:jklr " ヒントのキーを数字からホームポジションのキーに変更 set hintkeys=asdfg;lkjh " C-c と C-v は普通に使いたい noremap <C-c> <pass> noremap <C-v> <pass> cnoremap <C-c> <pass> cnoremap <C-v> <pass> inoremap <C-c> <pass> inoremap <C-v> <pass> " s と a でタブ切り替え noremap s <C-n> noremap a <C-p> " 失われた C-v の機能を C-u に当てたい noremap <C-u> <C-v>
guioptions は、会社だと開発用プラグインを多用するので最初からツールバーを出します。それ以外は j/k でのスクロール量の調整と、 tumblr での除外キー。
こんなところでしょうか。あんまり環境の設定を複雑にしていく(デフォルトから乖離させる)のは好みでは無いので、この辺で抑えておきたいところです。
2008-09-20
RollOutをください
JavaScript, Firefox, IE |
mouseleave/mouseenter, rollOut/rollOver
Flex には確か rollover/rollout と mouseover/mouseout が別にあって、IE にも mouseleave/mouseenter と mouseover/mouseout が別にあって、要するにそれが無くて mouseover/mouseout だけだとどうなるのかというと、
- 自分の子どもにマウスが乗ったら自分の mouseOut 発射
- (そんで次の瞬間)自分の子どもの mouseOver がバブって来て再度発射
という、みなさんも一度は体験なさっているであろう、プルダウンメニューとか小さいポップアップとか作ってるときに「ファシャーーーッ!貴様ーーーッ!」と激高して不健康になる大変な悪が出現するわけですね。
で、無いのが Firefox と Safari (と Opera)。Safari の JavaScript にはクソも期待していないし、Opera ファンの一人として「動かないことになれている」のでいいのですが、Firefox はなんとかして欲しい。Firefox ユーザは、なぜか自分たちを十字軍か何かと勘違いしているので、動かないときーきーうるさい。でも、何とかして欲しくて実は何とかなってるんじゃないのかと思い、ppk 兄さんのところを見たら
THE OTHER BROWSERS SHOULD IMPLEMENT THESE EVENTS AS SOON AS POSSIBLE.Event compatibility tables
と、兄さんも珍しく声を荒げてお怒りの様子である。だよなあ。
とにかく、ネイティブ実装無しにこれを普通に書くとどうなるかというと、onmouseout あたりに
if ( event.type == "mouseout" && event.relatedTarget ) { if ( event.relatedTarget == [私] ) return; else { var eventTarget = event.target; var parent = event.relatedTarget.parentNode; while ( parent ) { if ( parent == eventTarget ) return; parent = parent.parentNode; } } }
ファッキンなコードですけど、話としてこういうことを書かないとならな…、い、ですよねえ…?。イベントの relatedTarget が自分の子どもだったら「あ、無しで」っていう処理。でも違うのかなあ…。なんかあるのかなあ…。ちなみに IE だと HTMLElement か Node みたいなところに contains ってメソッドがあるんですけど、Gecko ないのかなあ。
やっぱだめなような…
探す方向で自分の限界を感じたので、じゃあ、フックして自分で作ればいいかと思ったら FF とSafari なら結構作るには作れそう。
function mouseleaveHook(event) { var evt = document.createEvent("Event"); evt.initEvent("mouseleave", false, true); // ここで上の判別をごにょごにょっとやって、OKなら this.dispatchEvent(evt); } [私].addEventListener("mouseout",mouseleaveHook,false); [私].addEventListener("mouseleave",function(){alert("yeah")},false);
でもこれ、なんかイヤ。さらに HTML の属性としてのイベントハンドラだと、うまく動かない。
※Firefoxで <div id="[私]" onmouseleave="alert('無視ですか');" onclick="alert('こっちは動く(あたりまえ)');">test</div>
属性イベントハンドラって、いつだれが JS と繋いでんのさ!
ということは、属性イベントハンドラは、addEventListener のラッパではないらしい。じゃあいつどうやってできるのさ、と思って色々調べてみても、なんかある瞬間にできる、としか思えない。上の例で行くと、
// Firefoxで [私].onclick !== undefined // onclick メソッドがある [私].onmouseleave === undefined [私].constructor.prototype.onclick === undefined // コンストラクタ(この場合 HTMLほにゃららElement)の prototype にはない [私].constructor.prototype.onmouseleave === undefined
って感じで、
typeof [私].attributes.onclick.nodeValue === "string" typeof [私].attributes.onmouseleave.nodeValue === "string" // 属性の値自体は同じく文字列
であり、Firebug で見ても onclick は属性としては別段変わったプロパティを持っていたりはしなかった。constructor.prototype(__proto__)には無いし、HTMLElement の JS API を見てもそれっぽいものは見あたらない。つまり属性は属性でそのままにしておいて、JS じゃない誰かが、この属性を利用して JavaScript の、しかも prototype じゃないインスタンスのメソッドをダイナミックに作ってるんじゃあ…?
ということで MSDN は得意な僕ですが、MDC 当たりをあたっても、あんまり普段見慣れてないところだからよくわからない…。検索したりなどしていると結局 Firefox のソースにご案内されたので、じっと見つめてみるとどうやら nsGenericHTMLElement の AfterSetAttr メソッドが臭い。nsContentUtils::IsEventAttributeName ってのを使って属性がイベントなのか調べてるし、こいつがさらに AddScriptEventListener ってのをやって、その先で CompileEventHandler とか BindCompiledEventHandler とかぷんぷんしまくることをやってるっぽい。
AfterSetAttr って、じっと見ると SetAttr の最後で実行されてて、どうやら SetAttr って XPCOM がうんにゃらかんにゃらで nsIDOMElement::setAttribute を JavaScript の Element.setAttribute から動かしてるのかなあ、とか妄想。
そこで IsEventAttributeName の nsContentUtils を見てみると、わーお、めっちゃイベント名直書き。こりゃあかん。これ使って生成されてる sEventTable ってのを JavaScript から動的に変えられる手法でもなけりゃ、勝手につくった HTML の属性から(勝手に作っちゃったらもうHTMLじゃないけど…) JavaScript に繋ぐのは無理なのかあ!
ぬわー!
明日考えよう。
参考ページ
2007-06-12
2006-02-02
FireBug
Firefox |
SitePoint Blog をぼんやり見ていたら、Firefox の良さそうなエクステンションが紹介されていました。
"King of Firefox Validators" なんて言われちゃってますが、高機能というよりは使い勝手がとても良いです。
個々の機能自体は、元々の JavaScript コンソールや DOM インスペクタ、Venkman に既にあるものですが、その中でも使用頻度の高いものをピックアップして、さらに使いやすいようにまとめてくれたエクステンションであると言えます!。
別のウィンドウではなく窓の中に展開し…、
- 「表示中の」ページのエラーだけ表示
- CSS、JavaScript等各種エラーの表示非表示切り替え
- そのまま表示中の画面の DOM エレメントをマウスで選択して、Computed Style や JavaScript プロパティ・メソッドを表示
- 表示中ページの window コンテキストで、JavaScript コードを入力して実行できる(コマンドラインインタプリタ)
JavaScript コンソールと DOM インスペクタと Venkman を一々行き来している人には、目から鱗のエクステンションだと思います…。
2005-12-02
Venkman for Firefox1.5
Firefox |
Firefox、1.5になってレンダリングも軽くなってキャッシングも賢くなって良いことずくめだなあ。とのんびりしていたら Venkman がインストールできなかった。ちなみにインストーラで選択を間違ったかDOMインスペクタも入らなかったので、こっちは個別に取りに行った。
しかし、DOMインスペクタと Venkman のために Firefox を使っている私であるから、Venkman が無いと心にぽっかり空洞感。でもにょろっと検索したら作った人がいた!。
From ユーケー。最高です。

