たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
アドオンのテキストリンクは他のアドオンに比べて異常に更新頻度が高いように思えますがなぜでしょうか?
いちんちに5回も6回も更新してた頃があったって信じられるかい?
テキストリンク 3.0.2009031701で、Firefox 3上では部分的に処理が高速になった。具体的には、Rangeを文字列にする処理がそう。Venkmanでプロファイルを取ってみたらここが滅茶苦茶頻繁に呼ばれてて、ここが遅いと全部が遅くなるという感じで他の部分に影響してたんだけど、nsIDocumentEncoderで代用できる事にやっと気がついた。
nsIDocumentEncoderについては、以前に選択範囲からHTMLのソースを取得する方法を調べてて行き着いたnsISelectionPrivateの実装を見て、存在は知ってた。これを使うことができれば、HTMLを選択してコピー&テキストエディタにペーストした時のように、BR要素の位置で改行されたりP要素の位置で空行が入ったりSCRIPT要素の内容を除外したりといった、よくある処理が行われた後の整形済みテキストを取得できるんじゃないか、と思って色々試してみたんだけど、その時は、JavaScriptからコンポーネントの機能にアクセスできないようだったので結局諦めてた。でも今日になってふと試してみたら、いつの間にかJavaScriptからもCi.nsIDocumentEncoderが見えるようになってて、これ幸いと使ってみたところかなり期待通りの結果が得られたので、そのまま採用した。
使い方はこんな感じ。
// インスタンスを取得
var encoder = Cc['@mozilla.org/layout/documentEncoder;1?type=text/plain']
.createInstance(Ci.nsIDocumentEncoder);
// 変換対象のドキュメント、変換先の形式、変換ロジックのフラグを渡して初期化
encoder.init(content.document,
'text/plain',
encoder.OutputBodyOnly | encoder.OutputLFLineBreak);
// DOMRangeをセットして……
encoder.setRange(range);
// 文字列に変換する
var result = encoder.encodeToString();
前述した通り、HTML的に非表示になる事が期待されてる要素が除外されたり、画面上の改行位置で文字列の方にも改行文字が入ってくれたりと、単純にDOMRangeのtoString()
で文字列化するだけだと問題になる点がこれで一挙に解決される。
JavaScriptから使えるのはGecko 1.9以降のみのようなので、Firefoxの場合は3以降に限定ということになる。Firefoxは2のサポートが切れてるからまあいいんだけど、ThunderbirdはまだGecko 1.8系のままなので、恩恵にあずかれないのが残念だ。
nsIDocumentEncoderを使うようにした副次的なメリットとして、<td>URI1</td><td>URI2</td>
のようにセルの間にホワイトスペース文字が無いテーブルでも、nsIDocumentEncoderで文字列化する時はセルの間にタブ文字が入ってくれるため、それぞれ別々のURI文字列として検出できるようになった(最初の段階の「Rangeを文字列化してURIっぽい文字列を正規表現で探す」処理において、それぞれのセルに書かれたURI文字列がちゃんと別々の物としてヒットするようになった)。
追記。3.0.2009031801でさらに高速化した。高速化っていうか、なんていうか……今まで「URIっぽい文字列をマッチング→マッチングしたURIっぽい文字列をページ内検索→ちゃんとしたURIかどうか絞り込み」とやってて、ページ内検索が大量に発生すれば発生する程スピードが遅くなってたんだけど、これを「URIっぽい文字列をマッチング→ちゃんとしたURIかどうか絞り込み→ページ内検索」となるように(ある程度)処理の順番を入れ換えたところ、ページによってはアホかってぐらい速くなった。なんでここに気付かなかったんだろう。マヌケすぎる。
Mozilla Fluxでとりあげてくれてるけど、ツリー型タブがAMOのスタッフおすすめリストに載った模様です。
ちょっと前にメール来てたけどよく分かんなくて放置してたけど、Mozilla Fluxの記事に色々詳しい情報があったんで「へーそういうことなのかー」と思って統計を見てみたら、ダウンロード数爆増してて吹いた。 前述のメールが来てたのが3月頭で、グラフが飛んでる所がそれ。それまで一日数百件のダウンロードだったのが、10倍くらいに跳ね上がってる。
だからというわけでもないわけでもないんだけど、昨日更新した。久しぶりにAMOの方のレビューを見てみたら「これに対応してくれ」というアドオンの名前がいくつか挙がってたので、それへの対応。あと前回エンバグしてた部分の修正。
Firefox 3 Hacks にあるSQLがあまりに長い件 - hogehogeでツッコミを入れられてしまいました……SQLむずい><わけわかんない><
SQLといえば、Firefox 3.5でも機能に影響を与えない範囲で内部的なSQL文がいくつか修正されたそうでるかもしれないそうだ。
でもTrunkにチェックインされたパッチを見ても、どうしてこれで速くなるのか、何が良くなかったのかが、さっぱりわかんない。
XUL/Migemoのロケーションバー周りの機能でもSQLをがんがん使いまくってるけど、Firefox 3 Hacksの件で明らかなように僕のSQL知識不足は深刻なので、きっと物凄いオーバーヘッドがあると思う。誰か改善してくれないかなあー。
Mozilla Fluxで取り上げられてて知ったけど、いつかのチュートリアルがMDCに掲載されたようです。英訳して使いたいというオファーがあったところまでは把握してたけど、その後どうなったかまでは知らなかった。
2、3、4章の頭にクレジットが入ってるから会社の宣伝になってくれるかも?と思ったけど、これ英語ですから! 日本の人は見ませんから!(しょぼーん)
可知さんのライセンスの話もちゃんと英訳されてる。この付録のパートは外した方がいいんじゃないの的な話もあるみたいだけど、個人的には、ライセンスのことがよく分からなくて手を出さないという人がいるんじゃないかとか、よく知らないままライセンス違反してしまう人が出てきやしないかとか、ライセンスがついてないコードが世に出ても自分のアドオンの中に取り込めないからできればみんなライセンスをきちんと設定しといてほしいなあとか、そういう事を考えて付録にしてもらってたので、できれば今後も残しておいて欲しいところだ。
ざっと見て2カ所誤記を見つけた(もしかして元の日本語版にあったミスだろうか……)ので直しておいた。
ディスカッション用のページを見るに、現状の「Firefox拡張機能開発チュートリアル」の英訳版はあくまでスタートラインで、これを叩き台にしてもっと内容を拡充したいという意向があるようだ。でもこれにはちょっと複雑な思いがある。元はといえばSoftware Design誌に掲載するために書かれた記事なので、紙面の都合上だいぶコンパクトに内容をまとめてあって、物足りない部分は多々あると思うんだけど、逆に言うと、「最低限これだけ押さえておかないと」っていう点に厳選して書いてなおこれだけの分量があるので、無闇に内容を書き足すと「こんな長いの読んでらんないよ!」と初学者をウンザリさせてしまうんじゃないかと僕は危惧してる。(という風なことをコメントに書き足してみた)
ともあれ、非英語圏発のコンテンツが大きく取り上げられる事ってあまり多くないと思うので、感慨深いです。
エントリ書きかけの状態でうっかりタブ閉じたか何だったかで書きかけの文がどっか行っちゃって、書くの忘れてた。
コンテキストメニューの展開が場合によってはとんでもなく遅くなる問題については、一応の解決を見たというかこれ以上は自分の頭では速くできないなあという所まで行き着いた感じ。やったことはというと、メニュー展開時には最初と最後のURI文字列だけ検出して、それ以外は実際に操作を確定するまで検索しないようにしたっていう、実に単純なアレですハイ。すぐに処理を打ち切るようになったから選択範囲の大きさはもう影響しなくて、選択範囲の始点(終点)からその中に含まれる最初(最後)のURI文字列までの間にあるURIっぽい文字列の数によって、遅くなるかどうかが決まるという感じになりました。
その関係で、メニュー項目をポイントした時に選択範囲に含まれてるURI文字列をツールチップで列挙するにあたって、ツールチップの内容をちょっとずつ更新するようにした。これにはJavaScript 1.7以降のyieldを使っていて、yieldがあったからこれができたと言っても過言ではない……と思う(Firefox 2未満を切り捨ててなかったらこの決定はできなかった)。とにかくみんなyieldをもっと活用するべきだよ! と今更ながらに推してみたりして。
細かい事だけど、「一部だけ選択されてるURI文字列」というのを考慮するにあたって、選択範囲の境界がURIに含まれうる文字である場合は、URI文字列らしき文字が出現し続ける間Rangeを前後に拡張する、という風に考え方を変えてみた。今までみたいに固定の文字数で前後にRangeを拡張するのと違って、ある意味では無駄なくURIを検出できるようになったんじゃなかろうか。オーバーヘッドの大きさで相殺されちゃってる気もするけど。
某スレで僕がtextLink.uc.jsの普及を阻害している!これは陰謀だ!的な書き込みがあって吹いた。どっからそんな発想が出てくんだ。……と思ったけど、よく考えてみたら確かに前々からuserChrome.jsスクリプトにはどちらかというと否定的というか批判的な発言をしてきてるから、そういうことをやりかねないと思われるのも無理はないのか。(ちなみに、改めて言っとくと、本来userChrome.jsを使うのに相応しくない知識レベルの人がホイホイ使ってドツボに嵌る事とか、自動アップデートのできないスクリプトをそういう人のために作ったり配ったりする事とかに対して僕は否定的なのであって、全部織り込み済みで使う人や、技術者レベルの人向けにしれっと公開する事とかについては、僕がとやかく言う筋合いではないと思ってますよ。)
で、そういえばちゃんと中を見てなかったなと思ってtextLink.uc.jsを見てみたんだけど、networl.enableIDNがtrueだったらフツーに全角英数字で書かれたURIを読み込めるってマジすか! ってか試したら確かにいけてビビった。これって国際化ドメインの仕様? 無い知恵絞って一生懸命変換ルーチン書いたのは全くの無駄だったのか……今更だけど激しい徒労感に襲われました。あと、もしかしたら前にも書いたかもしれんけど、nsIFindを使わないというアプローチに自分は気付いてなかったので、こういうやり方があったのか……と唸ってしまった。まぁ、選択範囲の複数のURI文字列を収集するという風な事をやろうとするとこのままではうまく行かなさそうなので、今の方法を全面的に捨てた方がいいとまでは言えないみたいなんだけど。
そういえばbox-shadow(-moz-box-shadow)が使えるんだったなと思って、Firefox 3.1……じゃなくて3.5以降ではツリー表示したタブに影を付けるようにしてみた。
「新しいタブ」ボタンの影だけは背景画像です。
ページ全体を選択したりしてるとコンテキストメニューの展開が遅くなる件について、処理を色々スキップするようにして、だいぶ速くなった。
やってることというのは、メニューを表示する時に毎回選択範囲の中のURIを全部検出するんじゃなく、選択範囲の中で最初のURIっぽい文字列と最後のURIっぽい文字列だけ検出するようにするという変更。ツールチップで全部のURIを表示する機能はなくさざるを得ない、あるいはプログレッシブ表示みたいな感じになると思う。
で、テスト用に使ってるページでは、最初のURIの検出に約300ミリ秒、最後のURIの検出に500ミリ秒程かかってる。合わせて大体1秒近いので、まだ結構重たい。Venkmanでプロファイルを取って見てみた感じでは、1つ1つの処理は20ミリ秒とかそのくらいなんだけど、それが何十と重なってトータルでこの重さになってる。
「URIっぽいんじゃね?」と思われるものの実はURIではない、という文字列(例えばHH:MM:SS形式で示された時刻やid:XXXXなど)の数が多いとこうなってしまう。URIっぽい文字列の検出条件を厳しくすれば回避できる(例えばXXX://で始まってない物を除外するようにすれば先の例はすべてスキップできる)けど、それはちょっとやりたくない……
逆に考えるんだ。自動テストのおかげでリグレッションがこのレベルにおさまってると考えるんだ。
いや実際これがなかったらほんとに日に5回6回はアップデートすることになってたかもしれんですよ。いつかの頃みたいに。
コンテキストメニューやダブルクリックがめちゃめちゃ重くなることがあったのは、呆れる位しょうもないミスのせい。「クリック位置の前後のテキストからURIっぽい文字列を探す」時に、部分選択されたURIの全体を取得するために、一定の文字数に達するまで前後にRangeを広げるようになってるんだけど、この時にループを回す方向を逆にしてしまってて、一番近いノードじゃなくて一番遠いノードから数えてた。そりゃ遅くもなるって。ログによると18日の変更によるリグレッションだった。
改善されたとはいっても原理的に若干のもたつきは避けられない(特にページ全選択した状態でメニュー開いたりしたらやっぱり死ぬ程時間かかる)んだけど、まあ、ページの中をうっかりクリックする度に数秒間固まるのに比べればまだマシってことで……
Venkmanのプロファイラの使い方がやっと分かったんで、パフォーマンスの改善にも取り組みたいところです。
中野さんが、JavaScriptにはsleep(一定時間待ってから次の処理に進むという命令文)が無いせいでテストを書くのに難儀したという話を書かれているけれども。まさにこれをどうにかしたくて、UxUは進化してきたようなものと言える。
知ってる人は知ってるだろうけど、Firefox/Thunderbirdアドオン向けの自動テスト実行ツール・UxUでは、テストを書く上でsleepに相当する機能を利用できる。これは、JavaScript 1.7でジェネレータ・イテレータ機能を実現するために追加されたyield式のトリッキーな使い方だ。
これを実現しているのは、lib/utils.jsのdoIteration()
と、test/test_case.jsのrun()
ということになる。ここから要点だけを取り出すと、以下のような事をしている。
まず、スクリプトの書き手は、「sleepを使いたい処理」を関数として定義する。この時、sleep
の代わりにyield
を使う。
var task = function() {
doSomething1();
yield 1000;
doSomething2();
yield 1000;
doSomething3();
}
次に、スクリプトの書き手は、この関数を後述するdoIteration()
に渡す。すると、doIteration()
がよしなに計らって、「doSomething1()
を実行した後、1000ミリ秒待って、doSomething2()
を実行し、また1000ミリ秒待って、doSomething3()
を実行する」という風な形で先程の関数の内容を実行する。
この時のdoIteration()
の内容は、以下のような感じ。
function doIteration(aTask) {
if (typeof aTask == 'function') {
// 渡されたのが関数だったら、まず、評価した返り値を得る。
aTask = aTask();
}
if (!aTask ||
!('next' in aObject) ||
!('send' in aObject) ||
!('throw' in aObject) ||
!('close' in aObject) ||
aObject != '[object Generator]') {
// 渡されたオブジェクトまたは関数の返り値が
// ジェネレータ・イテレータではない場合、何もしない。
return;
}
// ここからがミソ!
// 全部の処理が終わったかどうか、を示すオブジェクトを定義
var finishFlag = { value : false, error : null };
var last = 0; // スリープ開始時点の時刻を保持する変数
var sleep = 0; // スリープの長さ(秒数)を保持する変数
var timer = window.setInterval(function() {
if (
// スリープの長さがちゃんと指定されていて
sleep > 0 &&
// スリープ開始時点からの経過時間がスリープとして
// 指定された時間未満であれば
(Date.now() - last) < sleep
) {
// ここで処理を終えて、100ミリ秒後まで待つ。
return;
}
// スリープとして指定された時間が経過したので、処理を進める。
try {
// 次にyieldが登場するまでの間の処理を実行。
sleep = aTask.next();
// next()の返り値はyield式に渡された値。
last = Date.now(); // スリープ開始時刻を保持して
return; // 処理を一旦終えて100ミリ秒後を待つ。
}
catch(e if e instanceof StopIteration) {
// 最後のyield式の後の内容が実行されて、定義された関数の内容が
// 最後まですべて実行されると、StopIteration例外が発生する。
// よって、処理完了とみなす。
finishFlag.value = true;
}
catch(e) {
// それ以外の未知の例外が発生した時は、処理中断とする。
finishFlag.error = e;
}
// 100ミリ秒ごとの繰り返し処理を停止。
window.clearInterval(timer);
}, 100);
return finishFlag;
}
UxUの内部でやってる事は基本的にはこういう事。ただ、実際にはもうちょっと使い勝手をよくするために細かい処理が加わってる。
doIteration()
が中で何をやってるのかを知らなければ、パッと見は、sleepという命令文の名前がyieldに変わっただけのようにすら見えるんじゃないだろうか。そこが、このやり方の狙いだ。スクリプトの書き手はタイムアウトだのコールバックだのといった難しい事を何も考えなくても良くて、単に「sleep文に相当する機能が加わったJavaScript」として好きなように処理を書く事ができる。テストを書くための工数が大幅に削減される(かもしれない)ので、テストを書くのが苦にならず、ばりばりテストを書けるようになる(はず)。その結果、充実したテストのおかげでより安心して開発に専念できるようになる(はず)。という理屈です。
ちなみに、勘のいい人は気付くだろうけど、setInterval()
に渡している関数の冒頭に以下の内容を挿入すれば、doIteration()
をいくらでも入れ子にできるようになる。
if (
typeof sleep == 'object' &&
(!sleep.value || !sleep.error)
) {
return;
}
var task = function() {
doSomething1();
yield 1000;
yield doIteration(function() {
doSomething2();
yield 500;
doSomething3();
yield doIteration(function() {
doSomething4();
yield 100;
doSomething5();
});
});
doSomething6();
};
doIteration(task);
さらに、こんなこともできる。
var task = function() {
doSomething1();
yield doIteration(function() {
var flag = { value : false; }
frame.addEventListener('load', function() {
frame.removeEventListener('load', arguments.callee, false);
flag.value = true;
}, true);
frame.contentDocument
.defaultView
.location.href = 'http://www.example.com/';
yield flag;
// フレームの読み込みが終わったらここに進む
doSomething2();
});
doSomething3();
};
doIteration(task);
この辺をもっと簡単に書けるようにヘルパーメソッドを色々と整備したのがUxUのテスト実行環境、と思ってもらえれば大体それで正解です。
25日追記。他にも色々やり方があるようです。(どっちもMozilla限定だけど)