たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
今まで「IMAPとはサーバ上だけにメールを置いておく物なのだ」と思い込んでたので、POPから移行するのに少し抵抗があったんだけども、そうじゃなくてIMAPはPOPを包含するものでありPOP的な運用もできるのだと知って、それならいっちょ本気出して移行してみるかなと思って移行してみた。
会社マシンのThunderbirdでメールに付けたタグをどうにかして自宅のマシンからも利用できるようにしたい、という事を前々から思っていた。というのも、自宅ではプライベートのアドレスに来たメールをGmail経由でBecky! 2で受信(SPAMフィルタ代わり)して仕事メールは基本的に閲覧のみ、会社ではプライベートのアドレスに来たメールを仕事用アドレス宛に転送してThunderbirdで利用してたんだけれども、Thunderbirdを使い始めてみるとタグが便利すぎて(Tag Dialog万歳)、アドオンのバグ報告を受信したらとりあえず「未処理」「bug」のタグを付けて、処理したら「未処理」タグだけ外す、というやり方でバグを管理するような運用が染み着いてしまいまして。気がついたら、自宅のBecky! 2ではどのメールが未処理なのかさっぱり分からなくなってしまったんですわ。
まあタグで運用しなくてもフォルダ分けでもいいかなって思うんだけど、それでもとにかく家と出先とで同じフォルダ構成でメールが見えてくれないと困るわけで。じゃあIMAPだよねってなるけど、でもオフラインでメールが見れないのはイザって時に困るわけで。
やりたいのは、家PC・会社PC・Gmailの3箇所で同じデータを同期させるという運用。それぞれにメールの本文が全部ちゃんとあって、1箇所でメールを消したらそれが他の2箇所でもちゃんと反映されて、という感じにしたかった。一般的なPOPの使い方のように「受信したらサーバからはすぐ消す」というのはするつもりはない(POPの時も2箇所で受信させてたし)。
まずはアカウントウィザードでGmailのIMAP用アカウントを作成する。
アカウントができたらGmail用に設定を変更しておく。
mail.server.server*.trash_folder_name
を追加し、値を[Gmail]/ゴミ箱
に設定する。新規にGmail IMAP接続用のアカウントを作るのなら、Gmail IMAP Account Setupを使った方が楽。これをインストールすると、アカウント作成のウィザードにGmailのIMAP接続用の項目が加わる。すでに手作業でアカウントを作ってしまった後では役に立たないので、早とちりして自分でアカウントを作ってしまった僕のような人はご愁傷様です。
あと、IMAPでの運用に限らないけど、Gmailっぽいスレッドフロート式の使い方をするためには以下の設定もしておくといい。
mailnews.thread_pane_column_unthreads
をfalse
にする。この変更を行っておかないと、スレッド表示とソートを併用できない。Gmailでまともにメールの整理をしてなかった人は、これを機に整理するようにしてみましょう(僕のことです)。
というあたりを踏まえてラベルをいっぱい作っておく。
mail.check_all_imap_folders_for_new
を探して値をtrue
にする。こうすると、フォルダごとのプロパティでそのフォルダを新着チェック対象に設定する手間が省ける。extensions.checkCompatibility
をfalse
にしておかないとインストールできないので注意が必要。この3つを組み合わせることで、
という運用が可能になる。
Firefox Developers Conference 2009、企業でアドオンを開発してる所でただの宣伝ではない技術的な話が聞けそうなセッションということで、A3 「ビッツにおける拡張機能開発 (Wired-Marker 他)」見たいなあと思ってたんだけど、15:40からのトークセッションの打ち合わせがあったので見れなかったんですよね。Webに上がってる感想を見ると、AMOで公開した後のやりとりについて色々話してたそうで、うわー分かる分かる!見たかったなあ!と、悔しい思いをしてたんです。
今日になってチラシを改めて見ながら、僕の代わりにセッションを聞いてもらってた小野寺さんに「どんな発表だったん? 技術的に面白そうな所ありました?」と聞いたら、「ページ内の選択箇所の位置情報をXPathでURLに付与するとかなんとか」って言われて、「へぇーずいぶん前に僕も同じようなことをやったなあ、同じ事考える人がいるんだなあ。ひょっとして僕のコードコピペして使ったんかな?w 使われてたらちょっと嬉しいなあ。」と思って、でも公式サイト見てみても特に僕の名前とかLine Markerへの言及は無いし、「予想外したかなあ。でももし仮に僕のコードが入ってたらライセンス違反(笑)だよなあ。」と思いながらAMOの配布ページからXPIをダウンロードして中を見てみて、該当する箇所を見てみたんですよね。
わあ。懐かしいコードに出会ってしまった。
クリエイティブコモンズの表示-非営利-改変禁止ライセンス(ちなみにこのライセンスは、無料で見れる映像作品などに設定する場合に使われる「作者を明らかにしてタダで再配布しなさいよ、自分の物だと言い張ったりお金取ったりしたらあかんで、あと改造したり切り貼り素材に使ったりするのもあかんで」という性質のライセンスです)なので、具体的にコードの一部を引用して比較することは避けておきますが、content/hyperanchor/hyperanchor.jsの中で定義されてるgetXPathForNodeというメソッドと、Line MarkerのcontextMenuOverlay.jsの同名のメソッドを見比べると、変数の書き方とかループのラベルとか// fallback, for markers in another marker or other cases...
というコメントとかを見る限り、Line Marker由来のコードと見て間違いないみたいなんですよね。どうも。
amachangとAzaと後藤さんと通訳の人と打ち合わせしてる隣で堂々とCopyright(c) BITS Co., Ltd. and Prof. Okubo. All Rights Reserved.
なこのコードの解説が行われていたかと思うと、なんか笑えます。
まあそんなに独創性のあるアイデアでもないし、僕もコピペした後元のコードの作者の名前を書き忘れることがあるし、こういうのを作れる技術力のある人ならこの関数をスクラッチで書き直す(&改良する)のは大して苦じゃないでしょうから、このエントリに気付いて今後のバージョンでライセンス違反を解消してくれればいいねえと期待しつつ生暖かい目で見守りたいと思います。
で、皮肉るだけじゃあまりに非生産的なので、僕のコードに限らずMPL1.1/GPL2/LGPL2.1のTri-licenseなコード(Firefoxそのものもそうです)をこういう風に流用したい方へのアドバイスを書いておきます。トリプルライセンスなコードはトリプルライセンスまたはいずれか1つのライセンスを引き継げば問題無いので、
のような感じにしておくと、僕のようなうるさい奴が「ライセンス違反じゃー!! 天狗のしわざじゃー!!!」とどや顔で騒ぎ立てることが無くて良いと思いました。逆に言うと、そのようにしておけば誰に文句を言われることもなくFirefox内部のコードでもなんでもパクれるもとい再利用できるので、みんなどんどんFirefoxの中からコードをコピペすると良いと思います。
イベントの感想エントリより前にこんなの書いてる僕って何なんでしょうね。
追記。コメント欄にも書き込まれていますが、先方からメールでもご連絡をいただきました。ライセンス上の問題が解消されるまで一時的に、当該アドオンの公開を停止されるとのことです。当該アドオン自体の有用性を否定するつもりはありませんので、早く再公開されればいいなあと、アドオン開発者の一人として思っています。
なんでこういう嫌らしいエントリを書いて晒し上げにするわけ? メールで連絡してなしのつぶてだったら初めて晒すのでも十分なんじゃないのか? 的な批判を裸電球さんからいただきましたので改めてよくよく考えてみましたが、今回の自分の「目的」というのはどうも、「ライセンス違反を解消してもらうこと」ではなく「尊敬されなかった事への不快感の表明・発露」だったのでしょうね。
「オープンソース」という言葉が生まれたのはGNUの思想と無関係にGPLなどのライセンスの恩恵を受けるためだった、という歴史がありますが、そういう意味では僕自身も、「自分の名前をどこかに残すため」「自分の痕跡を残すため」「称賛を得るため」「遺伝子を残せないなら、代わりにコードと名前を残す」という風な大目的のためにオープンソースにフリーライドしているという部分が大きいのだと思います。だからこそ、「フリーソフトウェアの思想のため」でもなければ「コードが適切に再利用されるため」でもなく、あくまで「リスペクトされなかった、自分が蔑ろにされた、成果から得られたはずの称賛を横取りされた(と、事実はともかく自分は感じて)、それによって傷付いた自分がここにいるのだということを世界に対し表明し、同情を買い、自分の心を慰めるため」という目的を意識した行動を真っ先に取ったのでしょう。即ち、twitterやここのような「自分のフィールド」で、間接的な嫌らしい書き方で、相手を非難する、という行動を。
だからおそらく、メールで知らせて人知れず修正されたとしても、あるいは淡々と事実のみを記しても、僕の「目的」は果たされることはなかった。また、その瞬間の僕の頭には「ああ可哀想な僕たん。こんなに可哀想なんだから同情されるよね間違いないよね。」というシナリオができあがっていたから、そのシナリオに沿った行動以外を取る気にはなれなかった。
しかし一通り吐き出してやっと、おそらく大方の第三者が冷静に考えて思うであろう「馬鹿だなあ、そんな書き方をしたら余計に嫌われるだけだろ」という結論に自分自身も辿り着いたため、何ともばつの悪い、しかし今更これを引っ込める事もできない、後味の悪さと、自分という人間の小者っぷりを最大限にアピールしてしまった事への後悔を感じたわけです。今となっては、自分に対しても本件に関わった誰に対しても、残念な思いしか感じられません。
テキストリンクに続いてClipboard ObserverもJetpackに移植してみた。
テキストリンク同様に、普通のアドオンのClipboard ObserverのコードをJetpackで動くように手直しして、最後にJetpack用のコードを付け加えただけ……という感じ。今回は、テキストリンクでは使わなかったステータスバーへの項目追加のAPIを使ってる。
以下、詰まった点。
jetpack.storage
の使い方が分からない、というか、Web上に書かれてる情報が古くて役に立たない。APIリファレンスに書いてある書き方を試したら「この方法は古いです、もうすぐサポートしなくなります」なんてメッセージが出る。
とにかくドキュメント不足が深刻です。
追記。アウトラインリスト生成のやつでstorageの使い方がやっと分かったので、チェック状態を保存するようにした。
いわゆる軽量アドオンであるところのJetpackについて今度の日曜のFirefox Developers ConferenceでAza氏とトークショーじゃなかったトークセッションを行うにあたって、「全くJetpackを触らないまま行くのはさすがに失礼すぎるだろ常識的に考えて」と思ったので、テキストリンクをJetpack featureとして移植してみた。
以下、詰まった点。
==
で比較すると、ラップされてるノードが同じノードでもfalse
になる。===
や!==
で比較すればちゃんと意図通りに判定される。これは盲点だった。(普通にアドオンを作る時だと==
で期待通りに判定されるので)やっつけ移植なので、URLっぽい文字列をダブルクリックしたら新しいタブを開く、という機能しかないです。そのくせ25KBもありやがる。
スクリプトの中身はぶっちゃけ普通のテキストリンクのコードのコピペです。というか、Jetpackで動かないであろう部分を削っていって残ったのがコレなんで。最後の方にちょこっとだけ、JetpackのAPIを使ってページの読み込み完了を監視する処理が入ってて、そこが本題です。
いいな、と思った点。
Firefoxでは、タブがセッション情報も伴って復元された時に、SSTabRestoring
とSSTabRestored
という2つのイベントが発行される。イベントのoriginalTarget
はいずれも復元されたタブの要素で、SSTabRestoring
はセッション復元処理が走ったけれどもタブの読み込みは完了していない段階、SSTabRestored
はタブの読み込みが完了した段階で発行される。
これらのイベントが発行される場面は、3つある。
この3つの場合を、特に1とそれ以外とを判別したい、その方法を考えてみたよ、というのがこのエントリの主題です。
例えばツリー型タブでは、タブの親子関係が変更された時のインデントの変更やツリーの折りたたみ時にアニメーション効果を適用している。しかしながら、Firefoxのセッション復元時に複数のタブのツリー構造を一気に変更する場合には、いちいちアニメーションしてたら重くてしょうがない。なので、この時だけはアニメーション効果を適用しないようにしたいと僕は思った。
しかしながら、前述のSSTabRestoring
とSSTabRestored
からは、そのイベントが発行されたのが上記の3つの場面のうちいずれなのかが分からない。どの場面でイベントが発行されたのかを判別するには、他の情報も参照する必要がある。
実は1の場合については、セッションが復元される時にObserverServiceに登録したオブザーバに対してsessionstore-windows-restored
というメッセージが通知される。なので、これを監視すればいいんじゃないか?と、最初のうちは考えてた。
でも、話はそう単純には済まなかった。実際にイベントを監視してみると、セッション復元時には以下のような順番でイベントが発行されていることが分かった。
SSTabRestoring
イベントが発行される。sessionstore-windows-restored
が通知される。SSTabRestoring
イベントが順番に発行される。SSTabRestored
イベントが読み込みの終わった物から発行される。3と4はこの通りにならないこともあって、あるタブのSSTabRestoring
が発行されてすぐに読み込みが完了したタブについては、次のタブのSSTabRestoring
が発行される前にSSTabRestored
が発行される場合もある。
一番致命的なのは、複数タブのセッション復元が始まったことは通知されるのに、全部のタブのセッション復元が終わったことは通知されないという点だ。
sessionstore-windows-restored
が通知されたらその後に発行されたSSTabRestoring
は全部ウィンドウ単位のセッション復元の一部なんだな、と判断してしまうと、例えば10個のタブを開いたウィンドウのセッションを復元した後、1つタブを開いて、閉じて、開き直した時、そのタブまで「ウィンドウ単位のセッション復元の一部」と誤爆してしまう。SSTabRestored
が発行されるまでの間にSSTabRestoring
が発行されたタブ」がウィンドウ単位のセッション復元なんだな、と判断してしまうと、後続のタブのSSTabRestoring
よりも前にSSTabRestored
が発行された時に、セッション復元が終わってないのに終わったと見なされてしまう。という具合で、「ウィンドウ単位でのセッション復元の終わり」がいつなのかが分からないと、誤爆しまくりで全然役に立たない。
また、重ねて困ったことに、sessionstore-windows-restored
の通知はsubjectがnull
なので、どのウィンドウでセッション復元が開始されたのかすらオブザーバ側からは分からない。別のウィンドウでセッション復元がはじまった時に来た通知を見て「これからこのウィンドウで開き直されるタブは全部、ウィンドウ単位のセッション復元の一部なんだな」と判断してしまってはいけない。
dump()
をコードの中に埋め込んで調べたところ、nsSessionStore.js内の各処理とイベントは、以下のような順番で起こっているらしいということが分かった。
SSTabRestoring
が発火sessionstore-windows-restored
が通知されるSSTabRestoring
が発火×タブの個数分SSTabRestored
が発火×タブの個数分SSTabRestored
が発火したら、すべてのタブの復元が完了このうちnsSessionStore::restoreHistoryPrecursor()やnsSessionStore::restoreHistory()やnsSessionStore::restoreDocument_proxy()は、タブを1つ復元するだけの時にも使われてる。
よくコードをよく読むと、nsSessionStore::restoreHistoryPrecursor()の中で「復元するタブの数だけ新しくタブを開く」「それらのタブのtab.linkedBrowser.parentNode.__SS_data._tabStillLoading
をtrue
にセットする」という処理が行われて、その後でsessionstore-windows-restored
がオブザーバに通知されていた。(このtab.linkedBrowser.parentNode.__SS_data._tabStillLoading
は、タブの復元が完了した段階でundefined
になる。)
ということは、sessionstore-windows-restored
が通知された時にウィンドウ内の全部のタブを調べて、tab.linkedBrowser.parentNode.__SS_data._tabStillLoading
がtrue
であるタブが2つ以上ある(復元待ちのタブが複数ある)なら、そのウィンドウでこれからウィンドウ単位のセッション復元が行われようとしている、と考えてよいわけだ。
で、SSTabRestored
が発行される度に復元待ちのタブの数を確認して、復元待ちのタブの数が0になったら、ウィンドウ単位でのセッション復元が終わったと判断できる。
厳密には、「ウィンドウ単位でのセッションの復元中に別途タブを1個だけ復元した」という場合にもそのタブが「ウィンドウ単位のセッション復元の一環で復元された」と見なされてしまうので、この方法にも穴はある。たくさんのタブのツリー構造を一気に変更した時にアニメーションでクソ重くなるのを避けたい、という僕の目的ではこれで必要十分なので、これ以上は追求してない。
以上をコンパクトにまとめると、こんな感じになる。
var ObserverService = Cc['@mozilla.org/observer-service;1']
.getService(Ci.nsIObserverService);
var observer = {
restoringWindow : false,
getRestoringTabsCount : function() {
return Array.slice(gBrowser.mTabContainer.childNodes)
.filter(function(aTab) {
var owner = aTab.linkedBrowser;
var data = owner.parentNode.__SS_data;
return data && data._tabStillLoading;
}).length;
},
observe : function(aSubject, aTopic, aData) {
if (aTopic == 'sessionstore-windows-restored')
this.restoringWindow = this.getRestoringTabsCount() > 1;
},
handleEvent : function(aEvent) {
switch (aEvent.type) {
case 'load':
window.removeEventListener('load', this, false);
window.addEventListener('unload', this, false);
gBrowser.addEventListener('SSTabRestoring', this, false);
gBrowser.addEventListener('SSTabRestored', this, false);
return;
case 'unload':
ObserverService.removebserver(this, 'sessionstore-windows-restored');
window.removeEventListener('unload', this, false);
gBrowser.removeEventListener('SSTabRestoring', this, false);
gBrowser.removeEventListener('SSTabRestored', this, false);
return;
case 'SSTabRestoring':
this.onTabRestoring(aEvent);
return;
case 'SSTabRestored':
this.onTabRestored(aEvent);
return;
}
},
onTabRestoring : function(aEvent) {
var tab = aEvent.originalTarget;
if (this.restoringWindow) {
// ウィンドウ単位でのセッション復元時の処理
}
else {
// タブが個別に開き直された時の処理
}
},
onTabRestored : function(aEvent) {
if (this.restoringWindow)
this.restoringWindow = this.getRestoringTabsCount() > 0;
}
};
window.addEventListener('load', observer, false);
ObserverService.addObserver(observer, 'sessionstore-windows-restored', false);
セッションストアAPIを使ってタブにいろんな情報を紐付けて、その情報に基づいてあれこれしたい人には、役に立つんじゃないでしょうか。(でもそんな変なことやってる人はほとんどいないんだろうなー)
I would like to see a shortcut assigned to show/hide the tab bar with the next update. That would be very useful since I reckon, since I have to click every time I want to show/hide it, which every time I want to read some thing on the web, which is way too frequent.
次のアップデートで、タブバーの表示・非表示の切り替えのためのキーボードショートカットを追加して欲しいです。私が思うにそれはきっととても便利でしょう。Webで何かを読もうと思う度に、タブバーの表示・非表示を切り替えるために今は(タブバーを?)その都度クリックしていますが、この操作が頻繁すぎて面倒です。
I'm very sorry, currently I have no idea to add new shortcut to show/hide tab bar. Instead, if you press "Ctrl" key for a while, collapsed tab bar will be expanded while you press the key. I hope it helps you.
If you like, you can call internal methods to show/hide tab bar from keyboard shortcuts defined by keyconfig or KeySnail. For example:
// toggle mode shown <=> hidden (shown <=> shrunken)
TreeStyleTabBrowserAutoHide.toggleMode();
// set to "shown"
TreeStyleTabService.setTreePref('tabbar.autoHide.mode',
TreeStyleTabBrowserAutoHide.prototype.kMODE_DISABLED);
// set to "hidden"
TreeStyleTabService.setTreePref('tabbar.autoHide.mode',
TreeStyleTabBrowserAutoHide.prototype.kMODE_HIDE);
// set to "shrunken"
TreeStyleTabService.setTreePref('tabbar.autoHide.mode',
TreeStyleTabBrowserAutoHide.prototype.kMODE_SHRINK);
Note: These examples work on Tree Style Tab 0.8.2009100101 or later.
すみません、今の所タブバーの表示・非表示を切り替えるショートカットを加える予定はないです。その代わり、Ctrlキーを長押しすると、非表示になっていたタブバーがCtrlキーを押している間表示されるという機能があります。これが助けになることを願っています。
お好みで、タブバーの表示・非表示を切り替える内部的な機能をkeyconfigやKeySnailで定義されたショートカットから呼ぶこともできます。例は上記の通りです(ツリー型タブ0.8.2009100101以降で動作します)。
In Tree Style Tab extension, would it be possible to make the background (dark gray blank area) display an image? Maybe with a userstyle or script...
ツリー型タブで、タブの背景(暗いグレーの空白の領域)に画像を表示できませんか? ユーザースタイルシートかスクリプトを使えばできると思うんですが……
On Firefox 3.6, you'll be able to make the background color of the tabbar transparent, like following CSS in the userChrome.css:
tabbrowser[treestyletab-style][treestyletab-mode]
.tabs-stack,
tabbrowser[treestyletab-style][treestyletab-mode]
.tabbrowser-tabs,
tabbrowser[treestyletab-style][treestyletab-tabbar-position]
.tabbrowser-tabs {
background: transparent !important;
}
On Firefox 4:
:root[treestyletab-style][treestyletab-tabbar-position]
#appcontent,
:root[treestyletab-style][treestyletab-tabbar-position]
tabbrowser,
:root[treestyletab-style][treestyletab-tabbar-position]
.tabbrowser-strip {
background: transparent !important;
}
Another way, you can get transparent tab bar with a secret preference "extensions.treestyletab.tabbar.style.aero". Go to "about:config" and turn it to "true".
上記のようなCSSをuserChrome.cssに書くと、タブバーの背景色を透明にできます。1つ目はFirefox 3.6用、2つ目はFirefox 4用の指定です。
また別の方法として、隠し設定「extensions.treestyletab.tabbar.style.aero」でもタブバーを透明にできます。「about:config」を開いて、この設定の値を「true」に変更して下さい。
XUL/Migemo 0.12.xで、機能を他のアドオンとかから呼び出すためのAPIを刷新してみたよ。
古いAPI(はてなブックマーク拡張とかが使ってくれてるやつ)は僕自身が色々よく分からないまま作った物だったために、引数を文字列で渡さないといけないとか戻り値も正規表現オブジェクトではなく文字列だとか、色々と使い勝手が悪かったと思う。メインウィンドウ以外では呼び出す時にいちいちXPConnect使わないといかんし。
新しいAPIは、それに比べたらものっそシンプルになった。XPConnect使わなくてもmigemo.getRegExp('hoge')
とか書くだけで使えるし、戻り値もすぐに使える正規表現オブジェクトが返ってくるし。互換性を保つために旧APIはそのまま残してあるので、旧APIを使ってるアドオンが動かなくなるということはないけど、今後は使うなら新APIの方を使うのがお勧めです。
で、このAPIはWebページ内のスクリプトでもCPUの使用率を取得できるようにするAPIを提供する例のアドオンの技術の応用なので、Webページ内のスクリプトからもXUL/Migemoの機能を利用できてしまいます。
ただしスクリプトの実行権限の関係で、上記のmigemo.getRegExp('hoge')
のような、正規表現オブジェクトを直に受け取る機能は使えません。代わりにJavaScript/Migemo互換の、正規表現のソース文字列を返すAPI migemo.query('hoge')
などを使う必要があります。
XUL/Migemo 0.12.2以降が入ってる環境でこのページを表示してれば、以下のデモを試せるはず。
ページ内検索系のメソッドもあるんだけど、多分使いでがなさそうなので解説は用意してないです。
すでに上でもリンクしてるけど、エンジンごとページ内に埋め込めるJavaScript/Migemoという実装もあるから、XUL/Migemoを入れてるFirefoxユーザでないと使えないこのAPIってなんか意味あんの?と言われそう。深くはツッコまないでください。
以下、補足事項。
当初このエントリでは、「Webページ上のスクリプトからもmigemo.getRegExp('hoge')
のようにして正規表現を取得して利用できる」という風な書き方をしてたけど、これは大間違いだった。戻り値の正規表現オブジェクトが生成された実行コンテキストがUniversalXPConnect特権のある場所なのに対して、呼び出し元は特権のない普通のWebページ上であるため、それぞれの権限が違うので本来ならその正規表現の各メソッドは実行できないのが正しい。
ただ、Gecko 1.9.1(Firefox 3.5)以前のバージョンにはバグがあって、上記のようなセキュリティのチェックが働かないために、戻り値の正規表現の各メソッドを呼べてしまう状態になっていた。
このバグはGecko 1.9.2(Firefox 3.6)以降ではすでに修正済みで、実際、Trunk等で戻り値の正規表現オブジェクトのメソッドを呼ぼうとすると、その場で処理が中断されて Error: RegExp.prototype.toString called on incompatible ChromeObjectWrapper というエラーがエラーコンソール上に出力される。
ということで、JavaScript/Migemo互換のAPIとして正規表現オブジェクトのソース文字列だけを返すような機能を0.12.2で新たに加えた。文字列や数値などのプリミティブ値に対してはセキュリティのチェックが行われないようなので、こっちはGecko 1.9.2以降でもWeb上で使える。
Tab Progress BarのプログレスバーがFirefox 3.7のモックアップ風なのを見て「羨ましい!」と思ったのでInformational Tabのデフォルトスタイルをそのように変えてみた。一応、設定で今まで通り(ラベルの下に表示)にも戻せる。
で、やるならとことんやってみっか!と一念発起して、モックアップにあるような光るプログレスバーを再現しようと頑張ってみた。 伸びるバーの部分は背景画像で、ぽわーんと光った感じは-moz-box-shadowを使ってるので、Firefox 3.5じゃないと期待通りには見えない。
XUL/MigemoがTrunk(3.7a1pre)で動かなくなっていた件について原因を調べてた。
エラーメッセージに見覚えがあるなあと思って検索したら、前に書いたエントリがヒットした。
一個だけ躓いた所として、XPCOMコンポーネントの新しいメソッドをIDL定義に追加して引数にnsISelectionController型のオブジェクトを渡すようにしていた所、XPIDLでのコンパイルは通るんだけど実際に使う時に
NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO
というエラーが出てにっちもさっちもいかなくなってしまった。ダメ元で、引数の型をnsISupportsにして受け取り側でQueryInterfaceするようにしてみたところ、ちゃんと動いてくれた。一体何だったんだろうこれは。
ああ、前にも詰まってたところだったか……この時は結局「理由」が分からないままで、とりあえず動くようにはなったからということでそれ以上は調べなかったんだよね。
改めて検索してみたら、似たような問題にぶち当たった人がいたようだった。で、やっと何が問題の原因だったのかが分かった。
新しいXPCOMコンポーネントを定義する時に、インターフェースも新しく定義する場合、XPIDLを使ってインターフェースを定義してやらないといけない。
[scriptable, uuid(4aca3120-ae38-11de-8a39-0800200c9a66)]
interface xmIXMigemoFileAccess : nsISupports
{
AString getAbsolutePath(in AString filePath);
AString getRelativePath(in AString filePath);
AString getExistingPath(in AString absoluteOrRelativePath);
AString readFrom(in nsIFile file, in ACString encoding);
nsIFile writeTo(in nsIFile file, in AString content, in ACString encoding);
};
この時、メソッドの引数や返り値の型として、AStringやlongのようなプリミティブ型(?)だけでなく、nsIFile
のように他のインターフェースを使うこともできる。
この時気をつけないといけないのが、インターフェースには2つの識別子があるということ。1つは、上記の例でいえばinterface xmIXMigemoFileAccess
という部分で定義されているインターフェース名「xmIXMigemoFileAccess」、もう一つは[scriptable, uuid(4aca3120-ae38-11de-8a39-0800200c9a66)]
という部分で定義されているインターフェースID(IID)「4aca3120-ae38-11de-8a39-0800200c9a66」だ。
今回は、XUL/Migemoのコンポーネントの機能のうち、nsIDOMRangeやnsIDOMWindowを値の型として使っていた部分でエラーが起こっていた。
interface xmIXMigemoTextUtils : nsISupports
{
(略)
AString range2Text(in nsIDOMRange range);
(略)
具体的にはこの辺。どうも、Firefox 3.5から3.7a1preまでの間のどこかの時点で、nsIDOMRangeやnsIDOMWindowのIIDが変更されたらしい。XPIDLのコンパイル(.xptファイルの生成)には成功しても、そのあと3.7a1preのFirefoxが.xptを解釈する時に、IIDの方でnsIDOMRangeやnsIDOMWindowのインターフェース定義を探すために、「こんなIIDのインターフェースは定義されてないよ! インターフェースの情報が見つからないよ!」というエラーになっていたようだ。
とぴあさんに色々教えてもらった。元々、XPCOMの元になった(?)COMの世界つまりC++の世界では、インターフェースの識別にはIIDを使うのが原則というかIIDこそが本来の識別子で、nsIDOMRangeとかの名前はそれへの参照に過ぎないということなのだそうな。
インターフェースの内容が変化した時(メソッドの追加等)には、「古いインターフェースの定義が消されて、別のIIDで新たなインターフェースが定義された」というような扱いになるようだ。「IIDが変わった」と前述しているけれども、プログラム的には「IIDが変わっただけで同じインターフェース」なのではなく「全くの別物」という扱いだから、全く互換性は保証されない……というわけ。
なお、互換性を維持したままインターフェースに新しい機能を追加するためには、現在使われているインターフェースの定義はそのまま残して、それを継承した新しいインターフェースを定義する必要があるということになる。「nsIPrefBranch2」とか「nsIGlobalHistory3」などがそれにあたる。
nsIDOMRangeやnsIDOMWindowのIIDが3.7a1preのものと同じになっている新しいSDKを使って.xptファイルを作り直してやれば、3.7a1preでもXUL/Migemoが動くようになるはず。でも、そうすると今度はFirefox 3.5以下で動かなくなる。それは困る。
無難な解決策は、値の型として使うインターフェースを、古いFirefoxから新しいFirefoxまでの間でずっと変わっていないインターフェースにするということ。nsISupports(すべてのXPCOMの基底インターフェース)ならほぼ確実に使える。
interface xmIXMigemoTextUtils : nsISupports
{
(略)
AString range2Text(in nsISupports range);
(略)
このようにインターフェースの定義を変えた上で、実装の方で受け取った値をQueryInterface()
してやる。
range2Text : function(aRange) {
aRange.QueryInterface(Ci.nsIDOMRange);
var doc = aRange.startContainer;
(略)
こうすると、メソッドを呼ぶ時に、DOMのRangeオブジェクトをそのまま引数に渡せる状態を維持できる。
JavaScriptのレイヤからはIIDではなくヒューマン・リーダブルなインターフェース名だけを使うのが一般的なのだけれども、こうしておけばXPCOMのフレームワークが自動的に「新しいIIDのnsIDOMRangeインターフェース」を参照してくれる。実際にインターフェースで定義されている内容には変更が起こっているかもしれないから、確実な動作は保証できなくなるけれども、当該のインターフェースが「既にある機能はなくなったり変更されたりせず、新しい機能が追加されていくだけ」という傾向があるのなら、おおむね問題なく動作し続けてくれると考えられる。
とぴあさんが調べてくれたのだけれども、今回のトラブルの原因になったnsIDOMRangeは定義の頭の方に@status FROZEN
と書かれていて、本来であれば、IIDが変わることもなければメソッドやプロパティなどのインターフェース定義の内容が変わることもない、安心して永続的に使えるインターフェースだったはず……のようだ。
それが、Bug 396392 – Support for getClientRects and getBoundingClientRect in DOM Rangeに提出されたパッチでメソッドが追加されると同時にIIDも変更されてしまった。本当は、これはあってはならない事態らしい。当該バグのコメントでもnsIDOMRangeのIIDは元に戻して、変更はnsIDOMNSRange(Geckoの独自拡張の機能が色々定義されているインターフェース)に対して行うべきと書かれている。おそらく近いうちに、nsIDOMRangeのIIDは元の物に戻されて、XUL/Migemoが動かなくなってしまった問題も解消されるものと思われる。
個人的な感覚としては、インターフェースに変化が無くても実装が変わって挙動も変わりました……なんて事がMozillaではザラにあるので、インターフェースの部分でだけ「ちょっとでも変化があったらIIDは別の物! インターフェースとしても別物!」という風に厳密に区別しても意味なくね? と思う。ぶっちゃけ、「安心して使えるAPI」なんてのはMozillaの世界じゃリップサービスに過ぎないと思ってる。(とぴあさんには、それはプロジェクトのマネジメントがマズイという別のレイヤの問題だよねと言われた。)
あと、現在のFirefox(Gecko 1.9以降)では、自分で新しくインターフェースを定義してXPCOMコンポーネントを作る必要はほとんど無いと言っていい。JavaScriptでコンポーネントを定義してJavaScriptだけから使うのであれば、JavaScriptコードモジュールを使えばよくなった。また、起動直後に処理を行うような場合なんかには相変わらずXPCOMコンポーネントの定義が必要だけど、それは既存のインターフェース(nsISupportsやnsIObserver)だけでも事足りる。XPIDLが必要になるのは、JavaScriptで書かれた機能をC++のレイヤから呼び出したいような時だけだ。普通に開発する分には、こんな事で悩む必要は今や全くない。という事に気がついて、今更になって激しい徒労感を感じている。