たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
Tab Progress BarのプログレスバーがFirefox 3.7のモックアップ風なのを見て「羨ましい!」と思ったのでInformational Tabのデフォルトスタイルをそのように変えてみた。一応、設定で今まで通り(ラベルの下に表示)にも戻せる。
で、やるならとことんやってみっか!と一念発起して、モックアップにあるような光るプログレスバーを再現しようと頑張ってみた。 伸びるバーの部分は背景画像で、ぽわーんと光った感じは-moz-box-shadowを使ってるので、Firefox 3.5じゃないと期待通りには見えない。
その名もズバリ「Open Bookmarks in New Tab(ブックマークを新しいタブで開く)」。何のひねりもない。
ツリー型タブにこの機能を付けれという要望が何度も何度もいろんな人から寄せられていて、しかしどうも調べてみると、Tab Mix Plusの一機能としてはこういう機能があるものの、これだけを実現してくれるアドオンが実は存在してなかったらしい(userChrome.jsを使える人はそっちで解決してしまうから、アドオンでなければ使えないというレベルのユーザには行き渡っていない?)、ということで作った次第です。userChrome.jsでやる人が多いんだろうなあということからも分かるように、メインの実装はたったこんだけ。これ以上機能を追加するつもりはないです。全く。
工夫?というか、実際使ってみて感じたことをフィードバックした点としては、ブックマークを中クリックした時にも常にタブで開くようにしてる、というあたりでしょうか。
作り手としてバカ正直に考えると、「普通の左クリックと中クリックの挙動をそれぞれ反転させればいいんじゃね?(そうすれば新しいタブで開きたい時と現在のタブに読み込ませたい時に使い分けれて便利じゃね?)」ということでそうしてしまいそう(事実、最初はそうしてた)なんだけど、実際に使ってみると自分の場合はブックマークを中クリックすることが癖になってて、タブで開きたいのに現在のタブに読み込まれて「ムキー!!!」となってしまった。
それに、こういう要望を出す人というのは多分、ミドルクリックでタブで開けるということをそもそも知らない(ミドルクリックという操作がある事自体を知らない)か、2ボタンマウスを使ってるかで、ハナから操作を使い分ける気なんか無いんだろうなあ。とも考えられる。
つまり「操作によって挙動を変えるという自由」が、混乱の元であったり、そもそも誰もそんな自由を欲してないんじゃないか、と。なので、「左クリックでも中クリックでもとにかくブックマークは新しいタブで開く」という挙動を初期設定としておいた。設定を変更すれば、中クリックした時は現在のタブに読み込ませるという挙動にもできるけど、作者の推奨設定はあくまでこうですよってこと。
先週1週間は夏休み取って家に缶詰でずっともえじら組のマンガ描いてたんだけど、その間大量にバグ報告が来てたのをずっと見て見ぬふりしてたのを今週になってやっと修正した。
ブックマークフォルダの内容をタブで開けなくなるという問題はFirefox 3.0.xでのみ発生する問題で、原因はJavaScriptコードモジュールのPlacesUtilsにFirefox 3.5から追加された機能をそうとは知らずに使ってしまっていたせいだった。
あとブックマーク周りの変更が結構ボロボロだったのをだいぶ直した。特にスターアイコンのことは自分であんまり使わないからすっかり忘れてて、直すのに難儀した。Firefox自身がeditBookmarkOverlay.xulを動的に読み込んでいて、そのeditBookmarkOverlay.xulに対してツリー型タブがオーバーレイを適用しているために問題が……とか、とてもバッドノウハウくさい。結局、XULオーバーレイでどうこうするのは諦めてJavaScriptで動的にDOM要素を生成して挿入することにした。
var range = document.createRange();
range.selectNodeContents(container);
range.collapse(false);
range.insertNode(range.createContextualFragment(<![CDATA[
<row align="center" id="treestyletab-parent-row">
<label id="treestyletab-parent-label"
control="treestyletab-parent-menulist"/>
<menulist id="treestyletab-parent-menulist"
flex="1"
oncommand="TreeStyleTabBookmarksServiceEditable.onParentChange();">
<menupopup id="treestyletab-parent-popup">
<menuseparator id="treestyletab-parent-blank-item-separator"/>
<menuitem id="treestyletab-parent-blank-item"
value=""/>
</menupopup>
</menulist>
</row>
]]>.toString().replace(/^\s*|\s*$/g, '').replace(/>\s+</g, '><')));
range.detach();
こんな感じにしておけば、XULの「タグを書くだけでUIを作れる」という利点をそれほど殺さなくても済む……と思う。E4XのXMLオブジェクトを生成した物を既存のDOMツリーに直接組み込むことができれば話は早いんだけど、そういうことは無理っぽいので、createContextualFragment()
にしてる。ここではE4XのCDATAマーク区間をヒアドキュメント代わりに使ってるんだけど、文字列置換でタグの間の空白文字を消してるのと、toString()
をわざわざ書いていることに注意が必要。前者を忘れると要素ノードの間にいちいちテキストノードが生成されてややこしいことになるし、後者をを忘れるとStringクラスの物ではなくXMLオブジェクト自身の方のreplace()
メソッドが呼ばれてしまって文字列置換にならないので。
修正ついでに、ブックマーク項目の「親のタブ」を設定する機能について、もうちょっと自由に使えるように手を入れてみた。ツリー構造を書き換えるのと同時に、ツリーとして表示される時の順番に合わせてブックマークを自動的に並べ替えるようにした。
ツリー型タブ 0.8.2009073101/02で、「このツリーをブックマーク」や「すべてタブをブックマーク」した時に、ツリーの構造を含めてブックマークを保存するようにしてみた。だいぶ前から要望を受けてて、「確かにそうするべきだよなあ」とは思ってたんだけど、どうやって実現すればいいかで悩んでた。でもFirefox 2のサポートを切ったことによって、API経由でPlacesデータベースに色んな情報を簡単に保存できるようになったので、思い切って実装してみた。
他のアドオンからもこの機能を使えるように、APIを用意してある。複数のタブからブックマークを作成する場合、以下のように、PlacesUIUtils.showMinimalAddMultiBookmarkUI()
でブックマークの追加を行う前後にツリー型タブのAPIを呼んでやると、タブのツリー構造がブックマークに保存される。
var tabs = Array.slice(gBrowser.mTabContainer.childNodes);
var isTSTBookmarksTreeStructureAvailable = (
'TreeStyleTabBookmarksService' in window &&
'beginAddBookmarksFromTabs' in TreeStyleTabBookmarksService &&
'endAddBookmarksFromTabs' in TreeStyleTabBookmarksService
);
if (isTSTBookmarksTreeStructureAvailable)
TreeStyleTabBookmarksService.beginAddBookmarksFromTabs(tabs);
try {
PlacesUIUtils.showMinimalAddMultiBookmarkUI(tabs.map(function(aTab) { return aTab.linkedBrowser.currentURI; }));
}
catch(e) {
}
if (isTSTBookmarksTreeStructureAvailable)
TreeStyleTabBookmarksService.endAddBookmarksFromTabs();
このAPIはマルチプルタブハンドラでもさっそく使ってる。
やってることはどういう事かというと……
TreeStyleTabBookmarksService.beginAddBookmarksFromTabs()
の方では、ブックマークされる予定のタブのツリー構造をシリアライズして内部に保持した上で、ブックマークの監視を開始する。PlacesUIUtils.showMinimalAddMultiBookmarkUI()
で複数のブックマーク項目が新たに作成される。この時、TreeStyleTabBookmarksService
はブックマークの追加を監視していて、新しく作られたブックマークのIDを内部に保持する。TreeStyleTabBookmarksService.endAddBookmarksFromTabs()
の中で、追加されたブックマークと元になったタブとを対応させ、ツリー構造の情報(親のタブにあたるブックマーク項目はどれか、という情報)を、ブックマークのアノテーションとして保存する。この時、タブの数と作られたブックマークの数とが一致しない場合(ブックマークの追加がキャンセルされたとか、未知の機能によってタブと関係ないブックマークが同時に作成されたとか)は想定外のエラーということで、何もせず終了する。とりあえず一番簡単なやり方で実装してみたので、保存した後のブックマークの順番や親子関係をいじくり回すとちょっと変なことになる。一応、そんなに大きな問題は起こらないで見た目上は何となく自然な形に収まるように、と工夫はしてみたんだけど……どうだろう。
保存された「どのタブが親か?」という情報は、ブックマークのプロパティから編集できるようにしてある。親を付け替えられるようにしてみたけど、横着してるのでちょっと制限が厳しい。そのうち、親を付け替えたらそれに応じてブックマーク項目自体の親フォルダ内での位置も自動的に入れ替えるようにでもしてみようかなー。
巻き戻し/早送りボタンでSITEINFOを使うようにしてるとクラッシュする件。wedataから取得するデータの先頭の方をスキップするようにしたら落ちなくなったので、どうも正規表現が長すぎ(内部的に、すべてのSITEINFOのURLマッチング用の部分を繋げておいて「マッチするルールがあるか無いか」だけを調べるようにしてるんだけど、その正規表現が長すぎ?)なのが原因っぽい。wedataの更新履歴を見ると、この数日の間にもいくつかルールが追加されてるみたいだし。
で、とりあえず250件ごとに区切って正規表現を作るようにしてみたところ、手元の環境では落ちなくなったみたいなので、修正版として速攻で公開してみた。
しかし念のため全部のURLマッチング用の正規表現を繋げた正規表現を使ってテストしてみたところ、これだけではクラッシュしなかった。処理が走るタイミングにも依るんだろうか? これじゃいつまた問題が再発するか分からんよ……
ともあれ、今までの「でかい正規表現にマッチするかどうか判定」→「全部のSITEINFOをループで調べる」というのに比べると、250個単位で「正規表現にマッチするかどうか判定」→「その250個の範囲のSITEINFOをループで調べる」という風に変わったので、場合によっては多少高速になったんじゃないかなーと期待している。
参考までに、テストに使った巨大な正規表現は以下の通り。
マルチプルタブハンドラではタブを選択した後に「すべてのURIをコピー」で選択したタブのURIをコピーすることができて、その時の形式を「URIだけ」「タイトルとURI」「HTMLのリンク」の3種類から選べるようになってたんだけど、今回のバージョンアップで、ここにさらにユーザが好みの形式を追加できるようにしてみた。ついでにマイナーバージョンも1つ上げてみた。
プレースホルダにはCopy URL +と同じものを利用できる(ツールチップの説明には書いてないけど%LOCAL_TIME%
と%UTC_TIME%
も使える)。自分は以下のものを追加して使ってる。
<li><a href="%URL_HTMLIFIED%">%TITLE_HTMLIFIED%</a></li>
[%TITLE_HTMLIFIED%](%URL_HTMLIFIED% "%TITLE_HTMLIFIED%")
* [%TITLE_HTMLIFIED%](%URL_HTMLIFIED% "%TITLE_HTMLIFIED%")
((<%TITLE_HTMLIFIED%|URL:%URL_HTMLIFIED%>))
* ((<%TITLE_HTMLIFIED%|URL:%URL_HTMLIFIED%>))
[[%TITLE_HTMLIFIED%|%URL_HTMLIFIED%]]
* [[%TITLE_HTMLIFIED%|%URL_HTMLIFIED%]]
nsIZipWriterを使えばアドオンでZIPアーカイブを作れるということを割と最近になってやっと知ったので、アドオンのXPIへのパッケージングまで全部アドオンでできるやん!と思ったんだけど、使い方がよく分からんかったので、実験と練習もかねてコンテキストメニュー拡張の短縮構文で使える汎用的な処理を書いてみた。
要点だけ抜き出すとこんな感じ。
// aSources = nsIFileの配列
// aZipFile = 作るZIPアーカイブのnsIFile
// aCompressionLevel = 圧縮率(0で無圧縮、9で最大圧縮、デフォルトは6)
zipFilesAs : function(aSources, aZipFile, aCompressionLevel)
{
// Firefox 3以降じゃないと動かないよ!
if (!('nsIZipWriter' in Components.interfaces))
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
if (!aSources || !aSources.length || !aZipFile) return;
if (aZipFile.exists() && !this.isZipFile(aZipFile))
aZipFile.remove(true);
const PR_RDWR = 0x04;
const PR_CREATE_FILE = 0x08;
const PR_APPEND = 0x10;
const PR_TRUNCATE = 0x20;
var writer = Components
.classes['@mozilla.org/zipwriter;1']
.createInstance(Components.interfaces.nsIZipWriter);
if (aCompressionLevel === void(0))
aCompressionLevel = writer.COMPRESSION_DEFAULT;
// 新規ZIPアーカイブと既存のZIPアーカイブではフラグが違う
var flags = aZipFile.exists() ?
PR_RDWR | PR_APPEND :
PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE ;
writer.open(aZipFile, flags);
aSources.forEach(function(aFile) {
var entry = this+aFile.leafName;
// フォルダに対応するエントリの有無を調べる時は、
// エントリ名の末尾に「/」が必要
var entryForCheck = aFile.isDirectory() ? entry+'/' : entry ;
// すでにあるエントリで、フォルダではない物は削除。
// (フォルダを削除しないのは、内容をマージするため)
if (!aFile.isDirectory() && writer.hasEntry(entryForCheck))
writer.removeEntry(entry, false);
// 新しいエントリを追加
if (!writer.hasEntry(entryForCheck))
writer.addEntryFile(entry, aCompressionLevel, aFile, false);
if (aFile.isDirectory()) {
var files = aFile.directoryEntries;
while (files.hasMoreElements())
{
arguments.callee.call(
entry+'/',
files.getNext().QueryInterface(Components.interfaces.nsILocalFile)
);
}
}
}, '');
writer.close();
},
// ファイルの内容を実際にZIPアーカイブとして読んでみて、ZIPアーカイブかどうか調べる
isZipFile : function(aFile)
{
var isZip = false;
if (!aFile || !aFile.exists()) return isZip;
var reader = Components
.classes['@mozilla.org/libjar/zip-reader;1']
.createInstance(Components.interfaces.nsIZipReader);
try {
reader.open(aFile);
try {
var entries = reader.findEntries('*');
while (entries.hasMore())
{
entries.getNext();
isZip = true;
break;
}
if (!isZip) {
entries = reader.findEntries('*/');
while (entries.hasMore())
{
entries.getNext();
isZip = true;
break;
}
}
}
catch(e) {
}
reader.close();
}
catch(e) {
}
return isZip;
},
Mozilla Party JP 10.0のライトニングトークでの発表資料「UxUを使った自動テストで安心アドオン開発」を公開しました。何度か練習してなんとか5分以内に収まるように直前まで削ってましたが、結局早口でまくし立てるばかりの通訳泣かせな発表になってしまいました。
宴会で、おそらくAir Mozillaあたりで使うであろうメッセージビデオの撮影に呼び出された。当たり障りのない無難なことを喋った(自粛してそうしたわけではなくそれくらいしか言うことがなかった)ので採用されない可能性は高いと思う。
その後残りのメンバーの一部とカラオケに行き、終電の時間で仕切り直して徹夜カラオケして、帰ってきて寝て起きたら19時だった。親に電話した(荷物が届いたことを連絡しようと思った)ら「声がおかしいぞ」と言われた。
Mozilla Partyの発表用資料を準備してる最中、自分で作っておきながら画像の埋め込みの仕様がクソ過ぎることにブチギレて、高橋メソッド in XULを久しぶりに改良してた。
公開ページはまだ更新してない。改良されたバージョンはリポジトリに置いてある。
not enough memory - snj: 最近,Firefoxはもっと拡張間で連携プレーした方が良いんじゃないかって思ってきた....
リンク先に書かれてる話からは少しズレる。どちらかというとこの前書いた互換性の話の方だ。
アドオン同士で連携するには、機能が使いやすくまとまってるということが当然必要だけど、それだけでなく、外部から安心して使えるということも非常に重要だと思う。公開されていて、今後のアップデートでも後方互換性が保証されていないと、安心して使えない。「ソースを掘り返してこういう機能を見つけたから使ってみてるけど、これって知らんうちに削除されてしまったりしないか?」という不安があってはいけない。機能が「今のバージョンにあるかないか」ではなく、「今後もあり続けるのかどうか」が重要だと思う。
ツリー型タブは以前からいくつかAPIを公開してはいたけど、基盤になる処理をもっと公開APIとして表に出すことにした。公開した物は原則として後方互換性を維持していく方針でいる。
マルチプルタブハンドラの方も地味にAPIを整備している。
もう僕は疲れたんで、この辺使ってよろしくやってくれってことで……