たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
以前、update.rdf関係を半自動生成するために頑張ったことがあって、各アドオンの紹介ページのHTMLからupdate.rdf(未署名)を作るあたりまでは自動化できてたんだけど、そこ止まりになっててまだまだ手動でやらなきゃいけないことが多くて、億劫で余計にリリースが滞る……という状況になっている。
それで、これじゃいかん!と思ってもっと自動化を進めることにして、とりあえずリポジトリのmaster/HEADを元に自動でテスト用ビルドを作るようにはした。
で、その知見を元にもう少し頑張って、開発版ではなくリリース版の方ももっと自動化するというチャレンジをしている。
リリースしてない状態が長く続いてる今このタイミングで12月29日、今年最後の肉の日ならこれは肉リリースにかこつけて溜まりに溜まってた物を一斉放出するいい機会だ……と思って、リリースできていなかった物をまとめて更新した。
最後にリリースしてから1年くらい経ってる物もあった。自分で使ってるのは常にmasterのHEADだから、リリースしなくちゃっていう圧力が働きにくくて、つい放置してしまう。それにしてもこれだけの数をまとめてというのは初めてなんじゃないかと思う。
ついでに、だいぶ前に実験的に作ってそれっきり放置していたSuspend Tabも、体裁を整えて一緒に公開した。類似アドオンが既にいくつもある激戦区にわざわざ突っ込んで行かんでも……というのはすごく思うけど、ツリー型タブと併用できる物が他に無いのでは仕方が無い。
1月には早々にFirefox 18が公開される予定になってたと思うし、それより前には公開しておかないと、また「動かないんだけど」の報告の嵐になるだろうなあ……という思いもあって、今を逃す手は無いなと。そういうわけで今年最後の肉リリースに踏み切った次第です。
再起動の要らないアドオンをAdd-on SDK無しで開発するときの簡易的なテンプレートのような物として作っているRestartlessは、ツールバーボタンの定義や設定ダイアログの定義にE4Xを使う前提で設計してた。静的なXULファイルを別に用意しなくても、JavaScriptで書かれたロジックの中にリテラルとしてXULの定義を埋め込める、ということで重宝してたんだけど、E4Xのデフォルト無効化で完全にハシゴを外された形になってしまった。
E4Xを多用してた方々各方面悲喜こもごもあるようで、自力でスクリプトをパースしてCDATAマーク区間だけでも認識できるようにするとか色々なアプローチが試みられているようなのだけれども、僕はというと、面倒なのでE4Xはスッパリ諦める事にした。元はといえば、従来のやり方に近いやり方で再起動の要らないアドオンをラクに開発したいというのがRestartless開発の動機だったのだから、何か代わりの手段があるのであれば、E4Xにこだわる理由はなかったし。
まずE4Xで定義されたXULのコード片を受け取る部分についてだけど、これらは内部的にはXMLのソースコードの文字列に変換した後、RangeのcreateContextualFragmentでDOMDocumentFragmentにしてたので、インプットはただの文字列でも構わなかった。なのでToolbarItem.jsとconfig.jsは以下の点だけ修正して終わりとした。
ツールバーボタンの定義は、手間だけど文字列リテラルに書き直すことにした(簡単なスクリプトでインデントも含めてそれっぽく置換した)。ちょよんごさんのnode-hereを移植することも考えたんだけど、単に改行込みのコード片を文字列として取得できるだけだと、E4Xの時みたいな「属性値やテキストノードの値にJSの変数の値を入れる」ということが簡単にはできなくて、そこの面倒さの方が気になったので、今回は見送ることにした。
ただ、設定ダイアログくらいの規模となると、一々文字列リテラルにしてたら埒があかないのでそこだけはなんとかしたかった。これについては、Firefox 8くらいから後のバージョンではchrome.manifestでcontentやlocaleのChrome URLを動的に登録・解除できるようになってるので、昔ながらのやり方に戻って静的なXULファイルで書くようにした。
ロケールもこの要領でpropertiesファイルからDTDに書き直せばいいんだけど、Fox Splitterみたいにロケールの数が多いとそんなのやってらんないし、Fox Splitterの場合は設定ダイアログとブラウザ上のUIとで同じラベルを使い回したりしていて、どれをpropertiesファイルに残してどれをDTDにするのかとかを考えるのももう無理っぽかったので、逆転の発想で、属性値やテキストノードの値に{{ JavaScriptの文 }}
という書き方を許容するようにして、XULドキュメントが開かれたときに自動的に解決するユーティリティを加えて、propertiesファイルをDTD代わりに使うことにしてみた。DOMContentLoadedのタイミングだと遅すぎるようだったので、今の所は待ち無しで実行してるけど、XBLの初期化タイミングが狂うリスクがあるので、将来的には問題が起こるかもしれない。(……と、ここまで書いて気付いたけど、node-hereを移植した上でこれと同じ記法を許容するようにすればよかったかなぁ。もう後の祭りだけど。)
Restartlessベースで作ってた各アドオン(親のタブに戻る、Fox Splitter、あと開発中で放置してた「Suspend Tab」)も更新済みで、masterベースのXPIはこの間から提供してる自動ビルドが既に利用できる状態になってる。Nightlyで使ってた人がもしいたら、これで動くようになってると思う。
……node-here.jsの移植はやらないでおくって書いたけど、結局やっぱり必要になってしまったので移植した。スタックトレースから列番号を取れないというGecko(SpiderMonkey?)の制限のため、1行の中に2個以上のhere()
は書けない。
リリースにまつわる諸々の作業が面倒くさくて、about:newtabには機能ができてすぐ対応したのに、リリースしてなかったせいで今頃「about:newtabを空のタブとして認識してくれないんだが」系の「不具合報告」が届くようになってきていて、他にも障害報告に対して「それmasterのHEADではもう直ってます」な場合が時々あって、あと時々「ナイトリービルドを提供してくれ」っていう声も見かけてはいたので、ちょっと頑張って/xul/xpi/nightly/でそれっぽい物を提供するようにしてみた。
どうやってるかというと、自宅にあるUbuntuマシン(NASも兼ねていて24時間動きっぱなしの自宅サーバ)上でcronjobを動かしているだけ。1点だけ工夫が必要だったのは、update.rdfに署名するという部分。update.rdfのデジタル署名にはMcCoyを使うんだけど、これだとcronjobとして自動実行させるのには都合が悪いので、MDNのページで紹介されてたMX-Toolsというのを使うようにした。
前提はこんな感じ。
特定の1つのアドオンについて「masterのHEADをpullして、変更があったらXPIにして、update.rdfを作って、アップロードする」というスクリプトは、試行錯誤の結果、こんな感じになった。
upload_nightly_xpi.sh:
#!/bin/bash
# MX-Tools等を同じディレクトリに置いているという前提で、
# このファイルのパスを基準にして、他のツールの位置を
# 指定するため、ディレクトリのフルパスを最初に得ておく。
tools_dir=$(cd $(dirname $0) && pwd)
remote_user=piro
remote_host=piro.sakura.ne.jp
remote_dist_dir=~/www/xul/xpi/nightly
actual_dist_dir=http://piro.sakura.ne.jp/xul/xpi/nightly
# ファイルの置き場所等はオプションで得る事にする。
while getopts ab:d:i: OPT
do
case $OPT in
# 変更が無くても強制的にアップロードする指定。テスト用。
"a" ) always=1 ;;
# keyfile.pem等を置いているディレクトリのパス。
"b" ) base_dir="$OPTARG" ;;
# リポジトリをcloneした先のディレクトリ。
"d" ) project_dir="$OPTARG" ;;
# SSH接続に使う秘密鍵のパス。
"i" ) secret_key="$OPTARG" ;;
esac
done
if [ "$project_dir" = '' ]; then
echo "no project directory specified"
exit 1
fi
if [ "$base_dir" = '' ]; then
echo "no base directory specified"
exit 1
fi
if [ "$secret_key" = '' ]; then
echo "no secret key specified"
exit 1
fi
# sedのオプションの違いを吸収しておく。
case $(uname) in
Darwin|*BSD|CYGWIN*) sed="sed -E" ;;
*) sed="sed -r" ;;
esac
# pullした時のメッセージを見て、変更があったかどうかを調べる。
cd $project_dir
pull_result=$(git pull --rebase)
updated=$(if [ "$pull_result" != "Already up-to-date." -a \
"$pull_result" != "Current branch master is up to date." ];\
then echo "1"; fi)
if [ "$updated" = "1" -o "$always" = "1" ]; then
package_name=$(cat $project_dir/Makefile | \
grep "PACKAGE_NAME" | \
head -n 1 | cut -d "=" -f 2 | \
$sed -e "s/\\s*//#")
public_key=$(cat $base_dir/keyfile.pub | \
grep -v -E "^--" \
tr -d "\r" | tr -d "\n")
# ナイトリービルド用として、install.rdfを書き換える。
# バージョン番号の末尾に今日の日付を付ける。
$sed -e "s/(em:version=\"[^\"]+)\\.[0-9]{10}/\\1/" \
-i install.rdf
$sed -e "s/(em:version=\"[^\"]+)/\\1.$(date +%Y%m%d)00.$(date +%H%M%S)/" \
-i install.rdf
update_rdf=${package_name}.update.rdf
# 古いupdate.rdfが残っていたら消す。
rm $update_rdf
# update.rdfの参照先と、公開鍵を書き換える。
$sed -e "s#/xul/update.rdf#/xul/xpi/nightly/updateinfo/${update_rdf}#" \
-i install.rdf
$sed -e "s#([^/]em:updateKey[=>\"]+)[^\"<]+#\\1${public_key}#" \
-i install.rdf
# 自動生成されたバージョン番号は、後で使うので控えておく。
version=$(cat install.rdf | \
grep "em:version" | head -n 1 | \
$sed -e "s#[^\">]*[\">]([^\"<]+).*#\\1#")
# ビルドした後、リポジトリの内容を元に戻しておく。
# (次にpullする時に、未コミットの変更があるという
# 警告が出ないようにする。)
make
git reset --hard
file=$(ls *.xpi | grep -v "_noupdate" | head -n 1)
# キャッシュが使われる事の無いように、
# ユニークなダウンロード用URLにする。
update_link=${actual_dist_dir}/${file}?version=${version}
if [ "$file" != "" -a -f $file ]; then
ssh_remote=${remote_user}@${remote_host}
# 公開先のディレクトリを作っておく。
ssh -i $secret_key ${ssh_remote} \
mkdir -p ${remote_dist_dir}/updateinfo
# XPIをアップロードする。
scp -i $secret_key ./$file \
${ssh_remote}:${remote_dist_dir}/
# XPIからupdate.rdfを自動生成し、それもアップロードする。
$tools_dir/mxtools/uhura -o $update_rdf \
-k $base_dir/keyfile.pem $file $update_link
scp -i $secret_key ./$update_rdf \
${ssh_remote}:${remote_dist_dir}/updateinfo/
fi
fi
exit 0
uhuraを使うには、依存パッケージを入れておく必要がある。
$ sudo apt-get install openssl unzip libexpat1-dev
$ cpan
(色々セットアップ。基本的には全部YesでOKだと思う。)
$ sudo cpan install XML::Parser RDF::Core Digest::SHA1 Convert::ASN1
アドオン1つだけでテストしてみた。
$ ./upload_nightly_xpi.sh -b ~piro/xul \
-d ~piro/xul/autodisableime \
-i ~/.ssh/id_rsa.nopassword \
-a
これでうまく動くことを確認できたので、複数リポジトリを全部処理するスクリプトを作って、そちらをcronで走らせるようにした。
upload_nightly_xpis.sh
#!/bin/bash
tools_dir=$(cd $(dirname $0) && pwd)
# 鍵の指定などを丸ごと引き渡すようにしたいので、
# このスクリプト自身では使わないオプションについても
# 受け付けるようにしてる。
while getopts ab:d:i: OPT
do
case $OPT in
"b" ) base_dir="$OPTARG" ;;
esac
done
if [ "$base_dir" = '' ]; then
echo "no base directory specified"
exit 1
fi
cd $base_dir
for dirname in *
do
if [ -d $dirname ]; then
${tools_dir}/upload_nightly_xpi.sh -d $base_dir/$dirname "$@"
fi
done
で、これを日に2回動かすよう、crontabに 0 7,19 * * * upload_nightly_xpis.sh -b ~piro/xul -i ~/.ssh/id_rsa.nopassword
とかいう感じで書いてる。
仮想マシンでも何でもいいけど、Linuxな環境があると自動化がはかどるな……と思いました(手前味噌)。
2012年11月26日追記:スクリプトにミスがあってinstall.rdfに埋め込む公開鍵が置き換わっていなかったので修正した。
2年前のブラウザー勉強会で、FirefoxやThunderbirdの一括導入と企業での導入事例についての話をやったけど、その時に「こういう物を使ってます」と話だけ出した「メタインストーラ(FirefoxやThunderbirdのインストーラをキックしてサイレントインストールした上で、アドオンも一緒にインストールする物。設定を作り込んだ状態のFirefoxやThunderbirdを一括導入するために使う)」だけど、実はちょっと前からGitHubでひっそりと公開してた。導入案件の相談があってもリソース配分の関係で受けられないという事が何度かあったので、ツール公開しとくから自分でできる人は自分でやってねという話です。
が、過去の案件で要望が上がる度に場当たり的に機能追加や修正を繰り返してきて、それなのに目の前の導入案件をこなす事に手一杯でドキュメントが整備できてなかったせいで、公開したはいいけど結局弊社(というか僕)でないと使えない状態だった。ドキュメントを書こうと思っても、設定の仕方とかが煩雑ですっきり解説できないものだから、ドキュメント整備もなかなか進んでなかった。
それがこのところ、Mozilla関係の作業が少し落ち着いてて時間を確保しやすかったので、じゃあやるか!と頑張って、ドキュメント整備と設定方法の改善をちまちま進めてた(解説しにくかったのは実装が宜しくなかったからで、実装がすっきりしていれば解説もしやすいわけで、ドキュメントを書く事が契機となって実装が改善されるというのはよくある話なのです)。その締めくくりとして、簡単なチュートリアル込みの使い方解説エントリを会社のブログで公開した。アドオンを同梱して導入するだけのインストーラなら多分そんなに迷わないで作れるんじゃないかなと思う。
このFx Meta Installerは、FirefoxのWindows版インストーラと同じくNSISスクリプトで開発してるんだけど、思い返してみれば、僕のNSISとの付き合いはPortable Firefox(Firefox for Androidではなく、USBメモリで実行ファイルとプロファイルを持ち運ぶアレ)からだった。NSISはインストーラを作るための物なんだけど、使いよう次第ではPortable Firefoxみたいなこと(プロファイルをテンポラリフォルダにコピーして、Firefoxを起動して、Firefoxが終了したらプロファイルをUSBメモリに書き戻す)もできる。ずーっと前にMozilla Japanが秋葉原と渋谷でFirefoxの入ったCD-ROMを配るというプロモーションをやった事があったけど、あの時配ってたCDの「ディスクを入れたらautorunでFirefoxがお試し用に起動する」っていうやつは、実は、Portable Firefoxを改造したか参考にしたかして僕が作ってたのですよね。それがなかったら今のFx Meta Installerはなかったかもしれないと思うと、ちょっと感慨深い。
あと、Fx Meta Installerの設定項目だったり機能だったりを見てると、ああこんな事もあったなあ……と過去の案件の事を色々思い出させられる。Linuxでもビルドできるようにしたのは、普段使ってるUbuntu入りのLet's noteだけ持ってお客さんの所で動作を試して直してビルドしてまた試して……というのをやるためだったし、解説エントリでは触れてないけど、メタインストーラのバイナリに電子署名するための設定を見てると、グローバルサインで証明書を買う手続きをしたなあという事を思い出すし。いまFx Meta Installerに実装されてる機能は、お客さんの要望だったり状況だったりの必要に迫られて追加したものがほとんどなので、ある意味でこいつは僕の仕事(の1つ)の履歴そのものとも言えるのかもしれない。
Tabs OutlinerというGoogle Chrome/Chromium用拡張機能がある。説明をよく読むと、Firefoxでツリー型タブを使っていた人が、Google Chromeに乗り換えるにあたって作った物らしい。
以前試した時は、リンクからタブを開いてもツリーが構築されないというのが「えっ」という感じであんまりよく試さないでアンインストールしちゃったんだけど、今日なんとなくまたインストールしてオプションを開いてみたら「Tree Style Tabs」ってチェックボックスがあって、そこに書いてあった説明を見たら「これONにしたら自動でツリーになるようになる」的な感じっぽかったので試してみたら確かにリンクから開いたタブが勝手に子タブになってくれた。
ということで、ツリー型タブにロックインされてしまってるせいでFirefoxからGoogle Chromeに乗り換えられないという人(時々そういう話を目にするのです)にとってこれはマジに救世主になるかもしれないなと思ってもうちょっとだけ試してみてた。タブの切り替えがダブルクリックじゃないとだめだったり、他の拡張機能との連携はさすがにできないっぽかったり、Google Chrome本体のメニューから終了したらツリー構造が保存されなかったりで、僕自身が乗り換える先としては厳しいなあ……とは感じたけど、そういう所が気にならない人なら、普通に使えるんだと思う。
そんな「Tree Style Tabs」モードなんだけど、チェックボックスの下にすごく長い注意書きが付いてた。僕が読み取れた大意としては、「そういう要望がものっそ沢山寄せられるから機能を付けてるけど、自分(作者)はこのモードはお薦めしない。試してみてもいいけど、このモードだけで使うんじゃなくて、このモードをOFFにしたTabs Outliner本来の状態でも是非使ってみて欲しい。」ということが書いてあるようだった。同様のことがTabs Outlinerの配布ページにも書いてあって、作者の人によると、ツリー表示は情報の一覧性が悪くて、フラットなタブのリストの方が可読性が高いぞ、と。ツリー、ずいぶん嫌われたもんですね……
この見解の違いは、タブとタブのツリーという物をその人がどう捉えているのか? という所から生じているのだと思う。
僕にとって、タブのツリーは(生存期間は短いけれども)ある情報を起点にしてあちこちさまよい歩いた足跡そのものであって、どのページからどのタブへ辿り着いたのかが視覚化されているということが非常に重要だ。僕はその関係性を目で追って「分かれ道になったノードはどれだったっけ」と探している(ちなみに、この時見ているのは主にfaviconと情報化タブが提供するサムネイルで、タブのラベル文字列は注視していない)。1階層のグループ化しかできないPanoramaだけでは不十分なのは、「新しいタブを開いて分かれ道になったノードがどれだったか」という情報が欠落してしまうからだ。
そのくらい憶えておけよって言われるだろうけど、僕はほんとに頭が悪いというか物覚えが悪いというかバカなので、「そのくらい」がもう悲しいくらい絶望的なほどに覚えられない。覚えられないから、そのまま視覚化して置いておく。僕にとってタブのツリーは、脳に収まりきらない情報を置いておく外部記憶になっていると言える。メインメモリの中に情報が乗り切らなくて、画面の中にしか置いておけないのだから、多少アクセス速度が遅いとしても、そこに置いてある情報を毎回取りに行く。そういう使い方をしていると思う。
でも、「そのくらい」を覚えるのが苦にならない人にとっては、オーバーヘッドが大きすぎてまどろっこしいのかもしれない。タブ同士の関係はこぼれることなくメインメモリの上に載りきっているから、わざわざ画面の中にまで同じ情報を残しておく必要性が無い。そういうことなんじゃあないだろうか、と思う。
極端なことを言えば、ツリー型タブというのは脳味噌の性能が極めて低い障碍者がフツーの人に後れを取らないように、フツーの人に合わせて作られた社会の中で日常生活を送るために不足分を補うために使う、義肢とか車椅子とかそういう物に相当するのかもしれない。電動車椅子に乗れば確かに誰でもラクに移動できるけど、自分の足で立って歩ける人は、車椅子を降りて歩いた方が自由にもっといろんな所に行ける。また、電動車椅子に慣れきってしまうと、脚がひなびて自力で立てなくなってしまうかもしれない。使わなくても生きていけるなら、使わない方が健康でいられるのかもしれない。ツリー型タブという物については。
ツリー型タブが重いという声をたまに見かけるんだけど、そういうのを見かける度に「ほんとかなあ?」とついつい思ってしまう。
もちろん、素のFirefoxに対して余計な処理を加えるわけだから、そりゃあ、元の状態より重くはなると思う。でも、「重い」って言ってる人の言ってる「重い」は、なんというか、そういうレベルのことを言ってるわけではないって気がする。もっと鈍重な、全体的に動作がヤバイくらい緩慢になる、そういうのを指してる気がしてる。
で、そういう現象が起こるとして、本当にそれがツリー型タブのせいなのかどうか。ツリー型タブを使っている時と使っていない時とで、同じ数のタブを開いていて、同じ時間だけブラウジングした状態で、ツリー型タブがある場合だけ顕著に性能が劣化するのかどうか。というのが、この件で自分が一番気になっているポイントなのです。
何故僕はこうまで頑なに「ツリー型タブが原因で遅くなっているのだ」と素直に認めようとしないのかというと、基本的にツリー型タブはブラウザのコンテンツ領域や履歴にはタッチしないように設計しているつもりなので、「コンテンツ領域に起因するメモリリークがある」とか「履歴を消去したら軽くなった」とかの報告があるという事自体が、どうにも不可解なのです。
実際、about:memoryではタブ毎にメモリの使用状況を確認できますが、「about:memoryを見ると、メモリが解放されていないことが分かる」と言われた再現条件をこちらで試行しても問題が再現せず、その後報告者の環境でもツリー型タブだけをインストールした状況では問題が再現しなくて、どうやら他のアドオンがこの問題の原因になっているのではないかという話になったこともありました。
ツリー型タブがあるとタブを開く数が増える&1つ1つのタブの生存期間が長くなる傾向があると思うので、他のアドオンやFirefox自身が潜在的に抱えてはいるものの普段の利用では無視できる程度だったという問題があったとしたら、それらがより顕在化しやすい状態になるとは思います。例えばあるアドオンを入れていると全体の動作が0.1秒遅くなるとして、5つくらいタブを開いている状況ではそれが気にならなかった。でも、ツリー型タブを入れてタブを50とか開きっぱなしにしていると、些細な重さでもそのまま×50されるから、シャレにならない重さになる。こういう事は十分にあり得ます。でもツリー型タブ単体でそういう事が起こるとはどうしても自分には考えにくいのです。
「コンテンツ領域への参照を残しているせいでメモリリークが発生する」というのは自分もアドオン開発初期にはよくやらかしていたミスなので、かなり後の方になって開発したツリー型タブの頃にはそこらへんのことは想定に入れられるようになっていて、多少速度を犠牲にしてでも安全になるように、だいぶ気をつけて設計したという認識があります。「そこまでやらんでもええんちゃうん」って所まで偏執的にやってることすらあります。Firefox本体のコードでもaddEventListenerした物をremoveEventListenerせずに放りっぱなし(多分ガーベジコレクション任せ?)になってるのとか見ると、怖いなーって思ってしまうくらいです。
その一方で、僕は「これこれこのアドオンと衝突してるんだけど」という報告を受ける度にそのアドオンのソースを見るという事がこれまで結構あったのですが、ソース見てげんなりすることが割とありました。これメモリリークするやろ、とか。で、そういう問題を解決しようと思ったら、根本的なアーキテクチャの変更が必要だったりして。そういうとこまで見だすときりが無いし、大体それは僕の領分でもないので、ツリー型タブとの衝突の原因になってるとこだけ見てあとは見て見ぬふりしてるんですけども。
そういう惨状を見てるから、僕の心情としてはついつい、まず最初に他の原因の方を疑ってしまいます。それらの可能性がなくてちゃんと明らかに「ツリー型タブが原因だ」と断言できる、という所にまで絞り込まれてないと(はい。ほんとにツリー型タブが原因で問題が起こってる可能性も、もちろんあるとは思ってます。そこまで完全否定はできないです。)、調べようという気になかなかなれないです。これって、思い上がりすぎですかね?
あと、それとは全然別の話として、ツリー構造が何らかの理由で壊れてしまうせいで、無限ループが発生してフリーズしてしまう……という事は時々起こるみたいなので、そういうのはもう完全にツリー型タブの責任なので今後も粛々と直していきたいとは思ってます。
いずれの場合にしても、メンテナンスに十分に時間を割けない現状では「ツリー型タブのせいで問題が起こってる、かもしれない」という段階では動けなくて、「間違いなくツリー型タブのせいで問題が起こってて、この手順で100%再現できる」という所まで絞り込んでもらってることが、こっちで対処できるためには絶対に欠かせない条件という感じです。できれば「この部分が問題になっている」という所まで明らかにしてもらえてると嬉しいし、もっと言えば修正パッチをpull requestでもらえるのがベストなんですが。
それから、何と言おうとツリー型タブを入れてFirefoxの動作がおかしくなり、ツリー型タブを削除したことでこれらが解決されたことは事実なのです。
という風な話はもう何度あったか覚えてられないくらいにあって、GitHubのIssue Trackerを探してもゴロゴロでてくるんだけど、詳しく聞いてみると大概は他のアドオンとの衝突です(全部がそうだというわけではないですが、感覚的には、そうである場合の方が多かったという印象です)。こういうのも、じゃあどのアドオンと衝突してるのかという所まで明らかにさえしてもらえれば、何か手の打ちようが出てきくる可能性がありますので、より快適な生活を望む場合には、衝突の解消に協力してもらえたらなーって思います。
Mozilla勉強会@東京 7thでの発表資料やサンプル実装を公開しました。
発表時は準備不足のため時間の配分に失敗し、満足な説明を行えませんでした。すみません。発表時に喋ろうと思っていた内容のメモも併せて公開していますので、そちらもご覧頂ければと思います。
ツリー型タブを3ヶ月ぶりに更新した。
地味な修正が多い中で特に地味な修正だけど、セッション復元でツリー構造が壊れるという問題について一定の改善を見られたのではないかと思ってる。実際に効果があったのかどうかは、今後同様のバグ報告が減るかどうかで見るしかない。
何故ツリーが壊れるか、という事の前にそもそもツリーが壊れるってどういう事やねん、という話なんだけど、ツリー型タブを使っててごく希に、「リンクから新しい子タブを開いても子タブにならない」とか「親にあたるタブには折り畳みのためのつまみが表示されているのに、子になったはずのタブはインデントされておらず、つまみをクリックしても子になったはずのタブが折り畳まれない」とかそういう怪しい挙動になる事があった。
何故そういう事が起こるのか。これはタブの管理方法に理由がある。
ツリー型タブでは、個々のタブに一意なIDをあらかじめふっておいて、tab.setAttribute(TreeStyleTabService.KPARENT, parentTab.getAttribute(TreeStyleTabService.kID))
とか tab.setAttribute(TreeStyleTabService.kCHILDREN, childTabs.map(function(aChild) { return aChild.getAttribute(TreeStyleTabService.kID); }).join('|'))
とかいう感じにIDの文字列をキーにして互いの関係を保持している。親のタブや子のタブを取得する時は、TreeStyleTabService.getParentTab(tab)
とした時にその中で tab.getAttribute(TreeStyleTabService.KPARENT)
して取得したIDを使って(DOM3 XPathなどで)実際の要素を改めて取得するという風になってる。何故こうしたかというと、
tab.parentTab = parentTab
のようにしてしまうと、うっかり参照を消し忘れたらいつまで経ってもメモリが解放されないんじゃないか、という心配があった。(意図しない事態の発生を防ぐため)setAttribute()
するのと同じタイミングで SessionStore.setTabValue(tab, TreeStyleTabService.kPARENT, parentTabId)
のようにしてセッションに情報を複製しており、クラッシュ時のツリーの復元などに役立てている。(ツリー構造の永続的な保存のため)といった理由があってのことだった。
これはそれなりに堅牢なやり方なはずだと思ってたんだけど、実際にできあがってからJavaScriptデバッガでプロファイリングしてみたら、IDの文字列からタブの要素ノードを探すという処理が呼ばれる回数が突出して多くて、それが結構無駄な待ち時間を増やす元になっているらしかった。それで、もう少し高速に動作するような仕組みを後から加える事にした。setAttribute(TreeStyleTabService.kID, id)
するのと同じタイミングで、gBrowser.treeStyleTab.tabsHash[id] = tab;
としてハッシュテーブルを作っておき、それ以後はそちらだけを参照するという、ごくシンプルな物だ。
上記のおかしな挙動は、このハッシュテーブルが原因だっていた。このハッシュテーブルの仕組みが上手く働くためには、「ハッシュテーブルに入っていないタブはない(すべてのタブがハッシュテーブルで管理されている)」という前提が必要になる。もし万が一何らかの理由でハッシュテーブルの管理から漏れてしまうと、そのタブはAPI経由では永久に見つけられないことになってしまう。そのタブを親として子タブを登録する、という風な事はできるのに、その子タブに保存された情報から親のタブを探そうとしても、ハッシュテーブルに無いタブだから見つけられない。こういう不整合の発生は考慮に入っていなかったので、親子関係に基づくインデント幅の調整などの処理が中途半端な所で止まってしまって、上記のような色々と不可思議な現象が発生していた。
色々調べた結果、大量のタブが一気にセッション復元された時に「現在のタブ」になっていたタブが、このハッシュテーブルの管理から漏れてしまうことがあるようだという事が分かった。なので、そのような場合もちゃんとハッシュテーブルに入るようにした。これによって、手元で再現性100%だったケースについては問題が起こらないようになった。これまでに報告があった全ての問題がこれと同一の原因かどうかは不明だけれども、今までよりは問題が起こりにくくなったという事は言えると思う。
あと似たような問題で、タブの復元のタイミングによっては「ツリー構造だけの高速な復元」ができなくなるという現象も起こっていた。ツリー型タブが完全に初期化されるよりも前にFirefoxのセッション復元の仕組みによってタブが復元されていた、という想定外の状況が発生していて、それで色々な前提が崩れてしまっていた。理屈から言ってなんでそうなるのかがてんで分からなくて、どうにかしてタブが復元されるよりも前のタイミングに割り込みたかったんだけど、どうも無理っぽかったので、既にタブが復元されてしまっていた場合でもちゃんとそれらのタブを「後から初期化」するようにした。
そういう地味な修正ばかりが入っている版です。
複数のプロファイル、複数のバージョンを検証用に用意しないといけなくて、簡単に使い分けられるような環境を作る必要があるのです。
/usr/share/applications/firefox-custom.desktopとして以下の内容でファイルを作成する。
[Desktop Entry]
Version=1.0
Name=Firefox Web Browser (custom)
Name[ja]=Firefox ウェブ・ブラウザ (custom)
Comment[ja]=ウェブを閲覧します
GenericName=Web Browser
GenericName[ja]=ウェブ・ブラウザ
Exec=firefox %u
Terminal=false
X-MultipleArgs=false
Type=Application
Icon=firefox
Categories=GNOME;GTK;Network;WebBrowser;
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rss+xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;x-scheme-handler/chrome;video/webm;application/x-xpinstall;
StartupWMClass=Firefox
StartupNotify=true
X-Ayatana-Desktop-Shortcuts=NewWindow;Sub;ESR;Stable;Beta;Nightly;
[NewWindow Shortcut Group]
Name=Open a New Window
Name[ja]=新しいウィンドウを開く
Exec=firefox -new-window
TargetEnvironment=Unity
[Sub Shortcut Group]
Name=Sub
Exec=firefox -no-remote -p sub
TargetEnvironment=Unity
[ESR Shortcut Group]
Name=ESR
Exec=/home/piro/opt/firefox-esr/firefox -no-remote -p esr
TargetEnvironment=Unity
[Stable Shortcut Group]
Name=Stable
Exec=/home/piro/opt/firefox-stable/firefox -no-remote -p stable
TargetEnvironment=Unity
[Beta Shortcut Group]
Name=Beta
Exec=/home/piro/opt/firefox-beta/firefox -no-remote -p beta
TargetEnvironment=Unity
[Nightly Shortcut Group]
Name=Nightly
Exec=/home/piro/opt/firefox-trunk/firefox -no-remote -p trunk
TargetEnvironment=Unity
これで、アイコンを右クリックして各プロファイルで起動できるようになった。
ユーザのホーム以下にファイルを作成して複数のアイコンを登録してみたりとか色々試みてたんだけど、自分で付けた名前と表示名が一致しないとか色々動作が怪しくて、結局このような所(グローバルな設定ファイルとして作成)に落ち着いた。
2014年8月21日追記。Ubuntu 14.04LTSで、Thunderbird用の物も作った。
[Desktop Entry]
Encoding=UTF-8
Name=Thunderbird Mail (custom)
Name[ja]=Thunderbird電子メールクライアント (custom)
Comment=Send and receive mail with Thunderbird
Comment[ja]=メールの読み書き
GenericName=Mail Client
GenericName[ja]=電子メールクライアント
Keywords=Email;E-mail;Newsgroup;Feed;RSS
Keywords[ja]=Eメール;イーメール;mail;e-mail;email;メール;電子メール;ニュースグループ;ネットニュース;RSS;フ>ィードリーダー;書く;読む;Mozilla
Exec=thunderbird %u
Terminal=false
X-MultipleArgs=false
Type=Application
Icon=thunderbird
Categories=Application;Network;Email;
MimeType=x-scheme-handler/mailto;application/x-xpinstall;
StartupNotify=true
Actions=Compose;Contacts;AnotherProfile
[Desktop Action Compose]
Name=Compose New Message
Name[ja]=新しいメッセージの作成
Exec=thunderbird -compose
OnlyShowIn=Messaging Menu;Unity;
[Desktop Action Contacts]
Name=Contacts
Name[ja]=連絡先
Exec=thunderbird -addressbook
OnlyShowIn=Messaging Menu;Unity;
[Desktop Action AnotherProfile]
Name=Start with Another Profile
Exec=thunderbird -no-remote -p sub
OnlyShowIn=Messaging Menu;Unity;
Firefoxの物を作った時と微妙に様式が変わっているようだ。