たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
テキストリンク 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)」というキーワードからはなかなか見つけられない名前なのが困りものだ。
前のエントリでも触れてるけど、Firefox 3.1ではタブでない要素がタブバーに入る可能性が出てきたので、安全なタブの取得方法を色々考えてみた。
まず、すべてのタブのリスト。
function getTabs(aTabBrowser) {
return aTabBrowser.ownerDocument.evaluate(
'descendant::*[local-name()="tab"]',
aTabBrowser.mTabContainer,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
}
var tabs = getTabs(gBrowser);
for (var i = 0, maxi = tabs.snapshotLength; i < maxi; i++) {
alert(tabs.snapshotItem(i).label);
}
配列でないと困る時はこう。
function getTabsArray(aTabBrowser) {
var tabs = getTabs(aTabBrowser);
var array = [];
for (var i = 0, maxi = tabs.snapshotLength; i < maxi; i++) {
array.push(tabs.snapshotItem(i).label);
}
return array;
}
特定のタブの「次のタブ」。
function getNextTab(aTab) {
return aTab.ownerDocument.evaluate(
'following-sibling::*[local-name()="tab"][1]',
aTab,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
}
特定のタブの「前のタブ」。
function getPreviousTab(aTab) {
var tabs = aTab.ownerDocument.evaluate(
'preceding-sibling::*[local-name()="tab"]',
aTab,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
return tabs.snapshotItem(tabs.snapshotLength-1);
}
preceding-sibling
軸を使った時の返り値のノードの並びは軸方向の出現順ではなく文書順になるので、注意がいる。
function getPreviousTab(aTab) {
return aTab.ownerDocument.evaluate(
'preceding-sibling::*[local-name()="tab"][1]',
aTab,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
}
タブの個数、最初のタブ、最後のタブも。
function getTabCount(aTabBowser) {
return aTabBrowser.ownerDocument.evaluate(
'count(descendant::*[local-name()="tab"])',
aTabBrowser.mTabContainer,
null,
XPathResult.NUMBER_TYPE,
null
).numberValue;
}
function getFirstTab(aTabBowser) {
return aTabBrowser.ownerDocument.evaluate(
'descendant::*[local-name()="tab"][1]',
aTabBrowser.mTabContainer,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
}
function getLastTab(aTabBowser) {
return aTabBrowser.ownerDocument.evaluate(
'descendant::*[local-name()="tab"][last()]',
aTabBrowser.mTabContainer,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
}
こういう時自分はついついXPathを使ってしまうんだけど、速度的にはどうなんだろう。ちゃんと計測してないから分からない。Array.slice(gBrowser.mTabContainer.childNodes).filter(function(aNode) { return aNode.localName == 'tab'; })
とかの方が普通に速かったらごめんなさい。
childNodes
を参照してるコードがヤバイというのはホントに酷い話だなあと思うけど、Google ChromeやIE7以降と同じユーザ体験を実現しようと思ったらどっかの時点でこれには手を出さなきゃいけなかった訳で、もうみんな腹括って粛々と受け入れるしかないんじゃないかと思うんだなあ僕は。
追記。例によってきっとガン無視されるだろうけどバグ立ててみた。
追記。どうやら僕が立てたバグに関しては杞憂だったみたい……そもそもこのエントリに書いた話も今回の修正以前から潜在的にはあった問題のようだし。むしろヤバイのはalice0775氏が着目してる問題で、実際のノードの並び順と表示される順番とが食い違うことがあるようだ。この手の問題は僕も過去にXBLで色々試してた時に遭遇して、諦めて「こういう事はしない」という風に自分ルールで決めた記憶がある。バックアウトするのもいいけど、そもそもこれはXBLの匿名内容のレイアウトに絡む根深い問題に起因してるようなので、ここらで誰かが本気出して低層の所を直してくれないことにはどうにもならん気がする。
26日追記。「実際のノードの並び順と表示される順番とが食い違う」件は、新しいNightlyを試したら再現しなくなった。僕の全面的な勘違いだったのか、それとも別の所で修正されたのか…… あとgetBrowser().mTabContainer.selectedIndex プロパティと getItemAtIndex メソッドじゃあダメなのかな
と言われたので調べてみたけど、現状のgetItemAtIndex()
はchildNodes.item()
のエイリアスに過ぎないようなので、やはり万全を期すなら別の手(このエントリに書いたような)を考えておいた方がいい気はする。
タブのドラッグ&ドロップに関する色んなバグを一挙に解決するパッチと最後のタブの右に「新しいタブ」ボタンを表示するようにするパッチの両方が入って、タブ回りが色々変わった。何はともあれ、タブをクリックしようとしただけなのにうっかりドラッグになってしまって別ウィンドウが開かれてムキー、という事がなくなってよかった。
とりあえず自分が把握してる限りでは、ツリー型タブ等でscrollbox内の方の「新しいタブ」ボタン(今回追加された奴)をdisplay: none;
で消してるせいか、ボタンを再表示させた時や新しいタブを追加した時などにタブやボタンの並び順が盛大にぶっ壊れるようになった。display: -moz-box; visibility: collapse;
にしておくとこの問題は起こらないっぽいので、次版ではそうする予定。(コードはもうコミットしてある)
あと、gBrowser.mTabContainer.childNodes
が返す内容がタブとは限らなくなった(どんな要素でもタブバー内に置けるようになったので)ということで、タブを取得するのにこのプロパティを使ってる人は一応気をつけといた方がいいと思う。てかtabbrowser要素のmTabs
自体がこれを参照してるし、現実的にはタブ以外を置いたら色んな所がぶっ壊れる事が予想されるから「置くなよ! 絶対置くなよ!!」って言ってるのと同じだよなあ。まあ一応対応しとくけどさ……
またさらに気付いたけど、タブのnextSibling
やpreviousSibling
もタブでない値を返す可能性がある状態になってる。XBLのソースに埋め込まれてるコメントノードの位置もタブの間に何故か移動してしまったりする事があるようで、前後のタブをこのプロパティで参照してる場合には被害を受ける事になる。ここも注意が必要だ。
全然話は変わるけど。
アドオンで要素の表示・非表示をCSSのdisplayプロパティで制御するにあたって、display: none;
で非表示にするのはセオリー通りで別にいいんだけど、表示させる時にdisplay: inline;
とかdisplay: block;
とかにしてる人がたまにいてもにょる。Web上のCSSではそれで正解なんだけど、XULでこれをやるとボックスの並び順がぶっ壊れたりレイアウトがおかしくなったりする事があるので、よっぽどの事情(タブバーを多段表示させる時とか)がない限りはあくまでdisplay: -moz-box;
の方を使って欲しい。最近見かけたものではPathtraqがそうだった。
XUL要素の表示・非表示を制御するには大別して3つ、細かく分けると7つの方法がある。
node.hidden = true
と node.hidden = false
node.setAttribute('hidden', true)
と node.removeAttribute('hidden')
node.style.display = 'none'
と node.style.display = '-moz-box'
node.collapsed = true
と node.collapsed = false
node.setAttribute('collapsed', true)
と node.removeAttribute('collapsed')
node.style.visibility = 'collapse'
と node.style.visibility = 'visible'
node.style.visibility = 'hidden'
と node.style.visibility = 'visible'
いくつかのXUL要素では、hidden
プロパティやcollapsed
プロパティに真偽値を代入しても何も起こらないので、setAttribute()
とremoveAttribute()
とした方がより確実ではある。プロパティでの代入にせよDOMの属性値での指定にせよ、最終的にはCSSでの指定と同じ物として扱われる。xul.cssの頭の方を見れば、どういう事かよく分かると思う。
上の例でnode.setAttribute('hidden', false)
ではなくnode.removeAttribute('hidden')
と書いているのにも理由がある。テーマやアドオンの中には「要素のhidden
属性の値がtrue
であるかどうか」ではなく「hidden
属性に何らかの値がセットされているかどうか」しか考えてない場合があって、false
を設定したのに非表示のままになってしまう、というような事がたまにあるから。collapsed
にも同じ注意が必要。
XULの世界的には物凄く基本的な事なんだけど、HTML+CSSの世界から入ってきたばっかりの人はこの辺の事を知らないままでいるかもしれないので、一応書いてみた。
こういう事(why:何故そう書かねばならないか)って、アドオンの実際のコード(how:どう書いたらよいか)をいくら見てても分からないんだよなあ……ほんとXULは地獄だぜ。
このへんで触れてた件に、Firefox 3.1で変化があるみたい。
Bug 446026 – restore utility of eval(s, o)
でも何が変わったのか(何をできて、何をできないのか)よく分かってない。
mozIStorageStatement.finalize() メモ - 1/4ガロン
うっ。僕の知識は古かった。Firefox 3 Hacksで、createStatement()
で作ったステートメントは使い終わったらstatement.reset()
、と書いてましたが、statement.finalize()
が正解だったようです。
が、一律にこう置き換えるとよい、というわけでもないのがややこしい所です。mozIStorageStatementのfinalizeメソッドはどうやらFirefox 3.0.x(Gecko 1.9)で導入された物のようで、Gecko 1.8系では存在しません。なのでメソッドを呼ぶ前に存在確認をしておかないといけない。Firefox 2なんてもうサポート終了してんじゃん!と思うかもですが、Thunderbirdはまだ2.0.0.xが現役なのでもうしばらくは気をつけないといけないんですハイ。
ところで、僕はずっと誤解してしまってたんですが、ステートメントはstatement.reset()
でリセットすると、バインドするパラメータを変えて何度も使い回せるんですね。createStatement()
のオーバーヘッドがどのくらいなのかよく分からないのでアレなんですが、SQL文が変わらない時はキャッシュしたステートメントを使い回すようにするという形で、高速化を図れるでしょうか。XUL/Migemoに早速活かしてみたいと思います。
Browser chrome tests - MDC 見ながらなんとか環境整えてビルドしてテスト実行して……という風な事をしてみたけど、テスト結果のあまりのわかりにくさに閉口した。しかも一個テストがこけたら後の物も続けて失敗するし。クリーンなプロファイルで起動してくれるのはいいけど、あとがもうメタメタすぎて、とても常用(?)する気にならないよ……
というわけでUxUの次のテーマはFirefoxのソースツリー上にある自動テストを実行する機能ということにしたいと思います。
waitForExplicitFinish()
とfinish()
での非同期テストをサポートする。この辺が鍵でしょうか。
追記。概要をつかむために説明を翻訳した。これによると、「browser_*.js」という名前のファイルだけがブラウザ用のテストとして認識されるようなので、ファイル名がこのルールに一致していたら上記のような特別な処理を行うようにする、という感じで行けそう。