たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。
以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
逆に考えるんだ。自動テストのおかげでリグレッションがこのレベルにおさまってると考えるんだ。
いや実際これがなかったらほんとに日に5回6回はアップデートすることになってたかもしれんですよ。いつかの頃みたいに。
コンテキストメニューやダブルクリックがめちゃめちゃ重くなることがあったのは、呆れる位しょうもないミスのせい。「クリック位置の前後のテキストからURIっぽい文字列を探す」時に、部分選択されたURIの全体を取得するために、一定の文字数に達するまで前後にRangeを広げるようになってるんだけど、この時にループを回す方向を逆にしてしまってて、一番近いノードじゃなくて一番遠いノードから数えてた。そりゃ遅くもなるって。ログによると18日の変更によるリグレッションだった。
改善されたとはいっても原理的に若干のもたつきは避けられない(特にページ全選択した状態でメニュー開いたりしたらやっぱり死ぬ程時間かかる)んだけど、まあ、ページの中をうっかりクリックする度に数秒間固まるのに比べればまだマシってことで……
Venkmanのプロファイラの使い方がやっと分かったんで、パフォーマンスの改善にも取り組みたいところです。
Shredderに取り込まれたパッチだけど、2つとも、「Thunderbird 2.0.0.xには入れらんないよ」と蹴られてしまった。「そんなに重要じゃないから」だそうです。ちょっと待ってよ、日本語環境じゃどっちもめっさ重要やん。そしてThunderbirdはレガシーなメールソフトが必要とされ続けてる日本でこそシェアの伸びが期待できるわけじゃないすか。それなのに。
Firefoxでは中野さんの頑張りのおかげかマーケティングの都合なのか2.0.0.x系のセキュリティアップデートでも日本語環境特有の問題の修正を入れてもらえてたそうだけど、人もいないしMozilla内部じゃ注目度も低いThunderbirdでは絶望的ですかね……
中野さんが、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限定だけど)
ロケール更新ついでに。
直した問題は、「www.」で始まるドメイン名をダブルクリックした時の補完結果が「http:\/\/www.〜」になってしまうせいで読み込みに失敗するというもの。これはデフォルト設定として指定していた補完ルールのミスで、変更したのも設定ファイルだけ。デフォルト設定から変更して使ってる人は、「http:\/\」となっている所を「http://」に書き換えると、問題が直ります。
で、何故この問題を見落としてしまっていたのか、なんだけれども。
この設定が影響する処理については、既にUxU用のテストを作成してあって、自動テストを走らせた時にはこの問題は発生していなかった。テストの内容自体には問題はなくて、問題はUxUの方にあった。
テキストリンク用のテストでは、UxU 0.5.3で追加した新しいヘルパーメソッドのutils.loadPrefs()
を使っていて、defaults以下にある設定ファイルを読み込んだ上でテストを行っていた。調べてみたら、これがまずかった。
この設定ファイルは元々はcontent以下に置いてあった物で、旧バージョンでは「ファイルの内容を文字列として読み込んだ後に評価して……」てな事を全部自前でやってたんだけど、Firefox 2未満のサポート打ち切りに際して、defaultsフォルダ以下にファイルを置いて読み込み処理はFirefox自身に任せるようにしたという経緯がある。
アドオンの設定ファイルはJavaScriptの関数呼び出しの形で設定が保存されている。なので旧バージョンでは、ファイルの内容を文字列として読み込んだ後、eval()
でJavaScriptとして評価して設定内容を読み込んでいた。文字列の設定値として「http:\/\/」と書かれた箇所は、無意味なエスケープになるので、この時自動的に「http://」と解釈される。なので、冒頭に書いたような問題は今まで起こっていなかった。
defaults以下に置かれたファイルをFirefox自身が読み込む時も同じような感じなのだろう、と思いこんでたんだけど、よく調べてみたらそうじゃなかった。defaults以下に置かれた設定ファイルはlibprefモジュールのprefreadという、JavaScriptパーサではない独自のパーサによって解釈されていて、よくよく見てみると、\"
、\'
、\
、\r
、\n
、\xXX
(16進数表記でのエスケープ)、\uXXXX
(Unicodeエスケープ)以外のエスケープはエスケープ文字を含んだ文字列として取得されるという事が判明した。例えば\t
はタブ文字ではなく"\t"
になるし、\/
は"\/"
になる。これは予想外だった……
で、このあたりの挙動を再現するコードをゼロから書こうとしたら死ねると思ったので、結局、prefreadをまるごとJavaScriptに移植する事にした。といってもJavaScriptの文法はCに近い(らしい)ので、コピペして少し書き換えたという感じ。
UxU自体のライセンスはGPLなんだけど、ソースコードとして入手する限り、このファイルのライセンスは元バージョンと同じMPL/GPL/LGPLとして使えるはずなので、まぁ、そんなに使い出はないと思うけど使いたい人がいたらどーぞ。
直った直った書いてたけど見落としがありましたごめんなさい。
コンテキストメニューを開くと固まる、という極端なケースの事例としてGMailでの現象を見て、他の所で起こってるらしいコンテキストメニューがらみの問題もこれ(スタイルシートに対してマッチングを行って激遅になってる)に違いない!と思い込んでしまってたんだけども、それ以外に、テキストノードが細かく分割されてる場面でも超スローになってしまうことがあることに、今朝やっと気付きました。
修正箇所を見ると分かるけど、何でか手抜きでXPath式の評価をループごとに毎回行うように書いてたせいで、表みたいにテキストが細かく分割されてる場面でXPath式の評価が無駄に何度も行われてしまい、それで時間を食ってた。式の評価を最初の1回だけにしたら、場合によっては100倍くらい速くなった。正直、document.evaluate()
の実行コストを甘く見てた。
実に恥ずかしいミスですね。
テキストリンク 3.0.2009021601で、inputやtextareaの中のURI文字列も処理できるようにしてみた。といっても、テキスト入力中にダブルクリックするだけで開かれるとかそんなのはウザすぎるので、あくまでURI文字列選択中にコンテキストメニューを開いた時だけの機能だけど。ブログとか日記とか書いてる途中でふと確認したくなった時、なんかに使えるんじゃないかと思う。
DOM2 RangeのcompareBoundaryPointsをnsIDOMNSEditableElementの編集領域の中のRange同士の間で使うと、何故だかNS_ERROR_DOM_WRONG_DOCUMENT_ERR例外が発生してしまったので、仕方ないからnsIDOMNSRangeのcomparePointで解決するようにしてみた。独自仕様な上にどうにも動作が怪しい感じなので、できればcompareBoundaryPointsだけで済ませたかったんだけど、なかなかうまくいかない。
GMailでコンテキストメニューが固まる問題は、本文だけじゃなくヘッダ領域の中のstyle要素の内容までURI文字列の検索対象にしてしまってたせいだった(全面リライトにあたって、DOM3 XPathで代用できそうな所を片っ端からDOM3 XPathに置き換えたんだけど、XPath式の評価結果が書き直し前と違ってたことに気付いてなかった)。スタイル宣言が全部URI文字列らしき物として検出されてしまって、それで無限ではないんだけど数千回のループが発生して、途中で処理を打ち切られてたという感じ。XPath式を工夫してbodyの中だけを検索対象にするようにしたら直った。
この辺の修正に際してもまたテストを書き足した。こんな感じでテストが充実してくれば、多分、将来的にはregressionは減る方向に向かうと思うんだけど……
テキストリンク更新した。1つ前のバージョンから、Firefox 1.5等の古い奴はサポートしなくなってる。
設定ダイアログもprefwindowベースで書き直した(今まではSeamonkeyと共通で動作させるためにdialogベースで自力で書いてた、というかSuite時代はそうせざるを得なかった)ので、多分Mac OS Xで起こってるという設定まわりの問題は解消されてるんじゃないかと思うんだけど……どうだろう。
バージョン番号は上がったけど、機能的には大して変わってない。新機能と言えばせいぜい、選択範囲のURI文字列をまとめてコピーする機能くらいだ。内部的には、Firefox 3以降での「複数の選択範囲(ページ内をCtrl-ドラッグすると離れた位置に飛び飛びで選択範囲を作れる)」への対応のために抜本的な改修を行ったんだけれども、それって多分使う側にとっては割とどうでもいいことだろうなあ。
あと、このバージョンからガッツリ自動テストするようにした。
ところで、公開中のアドオンのいくつかについて「とっととFirefox 3.1対応しろやゴラァ」メールがMozilla Product Teamから届いた。「対応しないとお勧めリストから消すぞゴラァ」みたいな。
最近、恐怖症っていうか神経症っていうか……自動テストで検証されてない事が怖くて仕方ない。一通りテストを書き終えるまで、次をリリースするのが怖い。リリース工程をとても億劫に感じてしまって、どうにかしてそれを乗り越えてやっとリリースしたと思ったら、しょうもないregressionがあることに気がついて、またあの面倒なリリース工程をやらないといけないのかと思うと気が重くなって。
追記。とかなんとか言いながら結局テスト未完のままリリースしてしまった。
テストがあるからってバグがない訳ではない……XUL/Migemoもまだまだモロモロと障害が見つかってる。ただ、自動化のプロセスがほぼできあがってるので、新たに問題がおこるケースが見つかったら、それをテストケースに追加して、コードを修正して、全体のテストが完遂すること(=regressionが無いこと)を確認して、過去に自分が把握してた範囲についてはある程度の自信を持ってリリースできる。自動化ができてないと、その自信を持てない。直したと思った物がすぐ後ろから崩れてるかもしれないという恐怖に駆られる。
次の版をリリースする前にやっとかなきゃ、と毎回毎回思いながら放置してた、ロケーションバーまわりの自動テストを昨日の晩からちょっとずつ作り始めてる。
どっちもUxU用のテストケースです。Firefox 3.1で導入されたキーワードによる絞り込みのテストは以前書いてたので、それ以外の、SQL文を生成したりとかデータベースから結果を取り出したりとかの部分のテストを今作ってる。
何故ずっと後回しにしてしまってたかというと、Placesのデータベースが絡んでくるから、Railsのfixtureみたいなのを上手くやれる仕組みができないことにはどうにもならないと思ってたせい。プロファイルを指定してテストを実行する機能を使えば問題ないんだけど、これはテストの実行がめちゃんこ遅くなるからなるべく使いたくないわけで……(←ひどい)
でもそれを言い訳にしていつまでも放置してる方が良くないよなと思ったので、一念発起して、Placesデータベースとの間でべったりだった所とかメソッド間の依存でシッチャカメッチャカになってた所とかを整理して、純粋にロジックだけの単体テストをしやすいように修正を進めてる。千里の道も一歩から……ちょっとずつ切り崩して、ゴールに近づいていこう。
最近ずっと仕事でまたRuby on Railsやってて、前は既に動いてたプロジェクトのお手伝いという立場だったんだけど今回はゼロからのスクラッチだったから、何か作ってはすぐテスト書いて……みたいなサイクルで作業してて、その影響だと思う。テスト書かなきゃ気持ち悪い、みたいな。
しかしずっとRubyばっか書いてたから、ついつい普通のハッシュでhash.each
とか書きそうになってしまったり、メソッドの最後のreturnを書かずに値だけ書いてしまいそうになったり、行末のセミコロンを書き忘れそうになったり、ハッシュの前後を囲うブラケットを書き忘れそうになったり、メソッドを呼び出す時に括弧を書き忘れそうになったり、function
と書くべき所をdef
と書きそうになってしまったり、だんだん思考が侵食されてきてて困る。Ruby脳の恐怖だ。
5日追記。
ダミーのデータだけを登録したplaces.sqliteを用意して、それを使って検索のテストを行うようにしてみた。SQLite Managerを使ったけど、結構めんどかった。fixtureをCSVかなんかで用意してテスト実行時に自動でSQLiteデータベースを作成する、みたいな機能がUxUに欲しくなってくる……
落ち穂拾い #6 - Mozilla Fluxで、Firefox 3.0から3.1(beta3前まで)のXPCOMコンポーネントのインターフェースの変化まとめが紹介されてたので見てみた。これはすごいな……だいぶ確認の労力を削減できるんじゃないか? まあインターフェースが変わってなくても実装が変わって影響が出てる、という箇所もひょっとしたらあるかもだけど、大まかなチェックには使える。
ところでこのサイトどっかで見たなと思ったら、Nightly Tester Toolsの作者の人だった。でアドオン一覧のページがあったから見てみたら、/Find Bar/という気になる名前があった。ページ内検索で正規表現を使えるようにする、という物だ。ってこれ、XUL/Migemoともろかぶりじゃん!! 中身見てみたけど、やってることはやはり同じような感じだった。参考にできる所があったらパクらせて貰おう(GPL/LGPL/MPLのトリプルライセンスなので、GPLのXUL/Migemoにはコードを取り込める)。
しかし「Migemo」といい「/Find Bar/」といい、「正規表現(regexp)」というキーワードからはなかなか見つけられない名前なのが困りものだ。