たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。
以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
FireMobileSimulatorでのローカルプロキシ実装の試みを見て、UxUでデバッグ用ローカルプロキシみたいな事をできるようにしてみたい、と思った。
ただ、HTTPのことはこれっぽっちも分からない。ソケット通信も、一応独自プロトコルっぽいものを使って別プロファイルで動作中のFirefoxからテスト結果を受け取るということはできるようになったけど、それ以上の事は分かってないまま。なので、まじめにローカルプロキシを立てる以外の方法で、「特定のURIにアクセスしようとした時だけ、あらかじめ定義しておいたルールに従って別のリソースを返す」ということをできるようにしてみようと考えた。
僕が現在把握している方法としては、以下の物がある。
http-on-modify-request
イベントのタイミングでリダイレクトするやり方。shouldLoad()
の中でリダイレクトするやり方。1はthorikawaさんが頑張っておいでなので、それに期待している(ソースがGPL互換ならUxUにそのまま入れられるので)。他人のフンドシ。他力本願。ここでは後の2つについての挫折の経緯だけ書き留めておく。
結論を先に言っておくと、2も3も実装上の制限により全滅っぽい。やはり、ローカルプロキシをちゃんと実装するしか完全な解決策はないようだ。thorikawaさん期待age。
http-on-modify-request
イベントを使うやり方これは、現在FireMobileSimulator等ですでに使われている。nsHttpChannelはリクエストを送信する前にnsIObserverServiceを通じてhttp-on-modify-request
イベントを、レスポンスが返ってきた後にhttp-on-examine-response
とかhttp-on-examine-merged-response
とかhttp-on-examine-cached-response
とかのイベントを通知する(SubjectはnsHttpChannel自身)ので、このタイミングでリダイレクトをしようという話。
FireMobileSimulatorの場合、nsHttpChannelの現在の通信をキャンセルした上で、nsHttpChannelからnsIWebNavigationを引っ張ってきてloadURI()
で新しく通信を始めてる。
var redirected = 'http://www.example.com/';
var observer = {
observe : function(aSubject, aTopic, aData) {
if (aTopic == 'http-on-modify-request') {
var httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel)
.QueryInterface(Ci.nsIRequest);
// ここでキャンセル
httpChannel.cancel(Components.results.NS_ERROR_FAILURE);
// リクエストし直し
httpChannel.notificationCallbacks
.getInterface(Ci.nsIWebNavigation)
.loadURI(redirected, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
}
};
Cc['@mozilla.org/observer-service;1']
.getService(Ci.nsIObserverService)
.addObserver(observer, 'http-on-modify-request', false);
通常のbrowserやiframeの場合はこれでいいんだけど、img要素やXMLHttpRequestからの通信では失敗する。具体的にはhttpChannel.notificationCallbacks.getInterface(Ci.nsIWebNavigation)
の時点でエラーになる。例えばXMLHttpRequestによる通信なら、httpChannel.notificationCallbacks.getInterface(Ci.nsIXMLHttpRequest)
とすれば元のリクエストを取得できるので、こんな感じで場合に応じて再リクエストの方法を振り分けてやる必要がある。(XMLHttpRequestの場合については、どうやれば再リクエストできるのかまではたどり着けてない。もしかしたら無理なのかも。)
spec
を書き換える場合nsIChannelのURI
プロパティはnsIURIインターフェースの読み取り専用プロパティなので、通信先のURIを書き換えることはできない……ように見える。でも実はnsIURIのspec
プロパティの方は書き換え可能なので、ここに新しいURIを代入することででもリダイレクトできてしまう。
observe : function(aSubject, aTopic, aData) {
if (aTopic == 'http-on-modify-request') {
var httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
httpChannel.URI.spec = redirected;
}
とはいえこの方法は全くお勧めできない。動く場合もあるけど、動かない場合もある、という感じで実に不安定だ。nsHttpChannelの実装を見れば分かるけど、http-on-modify-request
が通知された段階ですでにその時のリクエスト先URIに基づいた初期化処理がいくつか終わってしまっているので、それと矛盾するURIを設定する(例えば、HTTPのリクエストだった物をFile URLにリダイレクトするとか)と、この後の内部処理でエラーが起こるっぽい。
http-on-examine-response
の方ではnsIHttpChannelのsetResponseHeader()
を利用できるので、Location
ヘッダにURIを設定してみたんだけど、ダメだった。残念ながらヘッダを解釈してくれなかった。
nsHttpChannelの実装を見たら、HTTPのステータスコードが301とか302とかの時だけLocation
ヘッダを見るようになってた。ステータスコードを保持しているプロパティは読み取り専用なので、強制的にLocation
ヘッダを読ませるということはできそうにない。
shouldLoad()
を使うやり方これはURNサポートなどで実際に使っている。詳しい方法は2007年当時のエントリに書いてあるけど、要約するとこういうことだ。
やってみると、一見上手く動いてくれてるように見えるんだけど、XPConnect特権がある実行コンテキストで作成したImageやnsIXMLHttpRequestのインスタンスからの通信が捕捉されなくて、UxUの用途では使えない感じだった。まあ仮に捕捉できたところで、元の通信を止めて別のURIで通信するようにさせる方法は分かってない(もしかしたら無いかもしれない)んだけど。
あと、shouldLoad()
の第2引数はnsIURIインターフェースなのでこれのspec
プロパティを書き換えたらどうだろう?と思ってやってみたけど、これもうまくいかなかった。browser要素等での読み込みの場合だと、nsDocShellのInternalLoad()
でエラーが発生する。実装を見た感じでは、nsIContentPolicyに処理が渡ってくるより前に元のURIに基づいてセキュリティ関係の機能が初期化されてるので、その後でURIを書き換えたのがいけなかったんじゃないかと思う。
とにかく、nsHttpChannelのインスタンスが作成された後からどうこうしようと思うのがそもそも手遅れくさい。それより前のステップでアクセス先のURIを書き換えようと思ったら、ローカルプロキシを立てる以外に手は無いようだ。
いいかげん諦めてMozilla Partyの発表資料作ることにします……
ローマ字入力で日本語ページ内の検索を可能にする「XUL/Migemo」拡張 - SourceForge.JP Magazine
さて、もう一つの検索方法である「英語(辞書アシスト検索)」であるが、これは英単語を入力するとその対訳となる日本語の単語が検索されるというモードだ。なぜか筆者の環境ではうまく機能しなかったのだが、一応使い方を説明しておく。
そんな機能実装した覚えないんですけど……
英語モードは、僕が正式にXUL/Migemoを引き継いだ後に行った大改造において、将来的に日本語だけでなく中国語や韓国語などのIMが必要な他の言語に対応できるよう、言語依存の主要モジュールを差し替え可能にした時に、日本語特有の処理(ローマ字入力を平仮名に変換するなどの処理)を含まないプレーンなモジュールの実装例として用意した物だ。
Firefox自身の通常の検索機能と比較して、このモードで具体的に起こる変化としては、「ext」まで入力した時点で「extension」「extend」等の英単語が強調表示されるようになる、という感じなんだけれども……これってぶっちゃけ無意味なんだよね。
あとは、発音記号付きのアルファベット等を区別せずに検索できるようになるけど、それとて日本語用のエンジンに既に包含されている機能だし。
強いて言えば、日本語とかローマ字とか全然興味がないし必要も無いという英語圏のユーザの人が、Safari風の強調表示だけ利用する時に、ほとんど何も余計なことをしないから邪魔にならない……ということくらいだろうか。英語モードのメリットは。とにかく、「これこれこういう凄いことができるようになる」といった類の意義は皆無だ。そのモードを作った本人が言うんだから、間違いないよ。
ノベルティやらパートナー企業の作るチラシやらが全部作り直しになるやんけ、という点を除けば、新しいドラフトはイイ感じに仕上がってきてると思う。
正直、初期のドラフトは端的に言って「え、これにリニューアル? ははは、またまたご冗談を」という印象しかなかった。「変えなきゃ、とにかく新しくしなきゃ」という意図が強く働いていなかったのか、何もかもを台無しにぶち壊していたように思う。それに比べたら最新のドラフトは、旧アイコンの解像度を上げた物として正当な進歩を遂げた形に落ち着いてると思った。カトキハジメの言うところの「マッハの戦い」とは、こういうことなんじゃないかと思う。
ここからまた変な方向に行ってしまわないかが心配だ。
くでんさんが報じておられる通り、Bug 418003 - Firefox is packaging unneeded images and iconsによって、過去に使われていたもののもう使われていない画像がデフォルトのテーマからまとめて削除された。Trunkでは既にそのようになってるけど、Firefox 3.5もそうなるんだろうか。だとしたら例によって「何でこんなギリギリになってからやるわけ?」と愚痴りたくなるわけですが。
削除されたファイルの一覧はMercurialのコミットログを見ると分かる。行の頭に「-」が付いている物が、削除されたファイルという事になる。
自分がアドオンの中で流用していて影響を受けるのは、「読み込み中」の状況を示すためのアイコンとして使っていたThrobber-small.gifだ。Throbber.png、Throbber.gif、Throbber-small.pngも一緒に削除されたので、これらを流用してる人は代わりのアイコンを参照するようにしないといけない。
これらのThrobber画像は、半透明のアニメーション画像に差し替える関係でFirefox 3.0の時点から既に使われなくなっていた。また、これらの新しいThrobber画像は16×16サイズのものしか用意されていないので、大きいサイズの画像が必要な場合は自作するか古いバージョンからぶっこ抜くしかない。
16×16については、以下のChrome URLで参照できる。
少なくとも今の時点では、Windows用、Linux用、Mac OS X用のいずれも同じパスで参照できるようだ。
ユーザが別のテーマを選択している場合もこの画像を参照したければ、以下のようにjarファイルの中のパスを直接指定する必要がある。
PHPとかerbのようなテンプレートをJavaScriptで。という話に書いたやつの続き。
大切な事なので3回言います。
<% for (var i = 0; i < 3; i++) { %>
今日は<%= today %>です。
<% } %>
オーケー?
こういう文字列をテンプレートとして解釈したい場面は多分よくあると思う。この例だと、today
という変数をどっか外部から与えて値を埋め込むことになる。で、この変数をどうやって渡したらええねん、と。
グローバル変数をがんがんに使って構わないのであれば、先にグローバル変数としてtoday
を定義しておけばいい。eval()
で実行されるコードの中からも普通に参照できる。
しかしアドオンのコードのように、個々のグローバル関数やグローバル変数の寿命が長い事が予想されるスクリプトでは、この手は使えない。
this.
を付けるparseTemplate()
の仕様としてはあくまで「書かれたコード片は第2引数で渡されたオブジェクトをthisとして実行されます」という風にしておいて、テンプレート風に書かれている方の文字列内のJavaScriptコード片では必ずthis.today
という風に書くようにする、という方法もある。
しかしいちいちthis.
を付けるのは面倒だし、そもそも僕がこのような記法を知ったきっかけであるERBでは、self.
なんて書く必要がなかった。同じような物は同じように使えた方がいい。なのでこの方法も使いたくない。
var
で宣言し直す先のエントリに当初書いていたコードでは、parseTemplate()
の第2引数で渡したオブジェクト(ハッシュ)のプロパティを走査してそれをvar
で変数として宣言し直す、ということをしてた。外の名前空間をなるべく汚さないための苦肉の策だ。
if (aContext && typeof aContext == 'object') {
for (var prop in aContext) {
if (!aContext.hasOwnProperty(prop)) continue;
__parseTemplate__codes.unshift('var '+prop+' = aContext.'+prop+';');
}
}
こんな感じ。
でも、これだとエラーになりそうな場合がけっこう考えられる。例えばハッシュのキーが01234
という文字列だと、実行されるJavaScriptのコードはvar 01234 = aContext.01234;
となってしまい、eval()
にかけた時点でSyntaxError: missing variable name
と怒られてしまう。
なので、こういうエラーの元になる名前のプロパティを除外して、安全な物だけをvar
で宣言する、ということをやりたかった。言い換えると、そのプロパティの名前を構成している文字列がJavaScriptの識別子として妥当かどうかを判別したかった。
ということでやっと、このエントリの本題に入る。
UxU 0.5.11に入れ損ねた。次版で標準のヘルパーメソッドに入れるけど、割といいかげんな実装で、たいした規模じゃないから、テストケースの中に直接書いて使ってもいいと思う。
function parseTemplate(aCode, aContext) {
var __parseTemplate__codes = [];
aCode.split('%>').forEach(function(aPart) {
var strPart, codePart;
[strPart, codePart] = aPart.split('<%');
__parseTemplate__codes.push('__parseTemplate__results.push('+
strPart.toSource()+
');');
if (!codePart) return;
if (codePart.charAt(0) == '=') {
__parseTemplate__codes.push('__parseTemplate__results.push(('+
codePart.substring(1)+
') || "");');
}
else {
__parseTemplate__codes.push(codePart);
}
});
var __parseTemplate__results = [];
with(aContext|| {}) {
eval('(function() { '+__parseTemplate__codes.join('\n')+' }).call(aContext|| {})');
}
return __parseTemplate__results.join('');
}
var source = <![CDATA[
大切な事なので3回言います。
<% for (var i = 0; i < 3; i++) { %>
今日は<%= today %>です。
<% } %>
オーケー?
]]>.toString();
var params = {
today : (new Date()).toString()
};
var result = parseTemplate(source, params);
レガシーだけどクロスブラウザな書き方だったら、こうか。
function parseTemplate(aCode, aContext) {
var __parseTemplate__codes = [];
aCode = aCode.split('%>');
var strPart, codePart;
for (var i in aCode) {
aCode[i] = aCode[i].split('<%');
strPart = aCode[i][0];
codePart = aCode[i].length == 1 ? null : aCode[i][1] ;
__parseTemplate__codes.push('__parseTemplate__results.push(unescape("'+
escape(strPart)+
'"));');
if (!codePart) continue;
if (codePart.charAt(0) == '=') {
__parseTemplate__codes.push('__parseTemplate__results.push(('+
codePart.substring(1)+
') || "");');
}
else {
__parseTemplate__codes.push(codePart);
}
}
var __parseTemplate__results = [];
with(aContext|| {}) {
eval('(function() { '+__parseTemplate__codes.join('\n')+' }).call(aContext|| {})');
}
return __parseTemplate__results.join('');
}
var source = '大切な事なので3回言います。\n'+
'<% for (var i = 0; i < 3; i++) { %>\n'+
' 今日は<%= today %>です。\n'+
'<% } %>\n'+
'オーケー?';
var params = {
today : (new Date()).toString()
};
var result = parseTemplate(source, params);
FUEL の Window/Tab 周りのハンドラは Firefox 3 では使うな - 8時40分が超えられない - subtech
書籍の中で詳しく解説しといてナンですが、アレをほんとに使う人がいたのか!ということにむしろ驚いてますよってなもんです。
まあ言う人に言わせれば、NSPRやらNeckoやらのよくわからない物が何層も積み重なった上にあるFirefoxを使うということ自体が「しんじらんねー」な感じなんでしょうけども。
not enough memory - snj: 最近,Firefoxはもっと拡張間で連携プレーした方が良いんじゃないかって思ってきた....
リンク先に書かれてる話からは少しズレる。どちらかというとこの前書いた互換性の話の方だ。
アドオン同士で連携するには、機能が使いやすくまとまってるということが当然必要だけど、それだけでなく、外部から安心して使えるということも非常に重要だと思う。公開されていて、今後のアップデートでも後方互換性が保証されていないと、安心して使えない。「ソースを掘り返してこういう機能を見つけたから使ってみてるけど、これって知らんうちに削除されてしまったりしないか?」という不安があってはいけない。機能が「今のバージョンにあるかないか」ではなく、「今後もあり続けるのかどうか」が重要だと思う。
ツリー型タブは以前からいくつかAPIを公開してはいたけど、基盤になる処理をもっと公開APIとして表に出すことにした。公開した物は原則として後方互換性を維持していく方針でいる。
マルチプルタブハンドラの方も地味にAPIを整備している。
もう僕は疲れたんで、この辺使ってよろしくやってくれってことで……
スペース区切りで入力したらAND検索する、という風なことができないかなーと思ってソースを見てみたら、Firefox 3からは正規表現リテラル(「/foobar/i」という風に書けば正規表現としてそのまま解釈される)やワイルドカード(「*」で0個以上の任意の文字にマッチする)を利用できるようになっていたということに今更気付いた。
今まで目grepしてた苦労はなんだったんだ。
マルチプルタブハンドラについてRockridge氏ほかから「メニューをカスタマイズできるようにしてくれ」という要望が挙がっていたのだけれども、Menu Editorという素晴らしいアドオンがあるのに自前で同じような機能を再実装するのは徒労感しか無いなあと思ったので、開き直って、マルチプルタブハンドラの設定ダイアログを以下のようにしてみた。
Menu EditorのAPIをよく分かってないので(ていうかそもそも公開APIなのかどうかも知らない)、タブ選択時のメニューをカスタマイズできるようにするためにちょっと強引な方法を使ってる。
で、同じようなこと(他のアドオンの有無を調べた上で設定を開く)を何度も書きたくなかったので、設定ダイアログに加えた変更の要点を他のアドオンと連携しやすくするためのライブラリとして分離してみた。
if (window['piro.sakura.ne.jp'].extensions.isInstalled('my.extension.id@example.com') &&
window['piro.sakura.ne.jp'].extensions.isEnabled('my.extension.id@example.com'))
window['piro.sakura.ne.jp'].extensions.goToOptions('my.extension.id@example.com');
アドオンがインストールされているかどうか・有効か無効かを調べるだけならFUELで事足りるので、あまり使い出がないといえば使い出がない。まあThunderbird 2あたりでだったらニーズがあるかもだけど。
ちなみにFUELで書く場合、アドオンがインストールされているかどうかはApplication.extensions.has('my.extension.id@example.com')
、有効か無効かはApplication.extensions.get('my.extension.id@example.com').enabled
で分かる。設定ダイアログのChrome URLを調べるAPIはなくて、それでこのライブラリを作ることにした次第です。