Home > Latest topics

Latest topics 近況報告

たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。

萌えるふぉくす子さんだば子本制作プロジェクトの動向はもえじら組ブログで。

宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

Page 26/241: « 22 23 24 25 26 27 28 29 30 »

タブの追加・削除のアニメーション - Aug 08, 2010

Firefoxで、タブを閉じる時にいきなりタブが消えるんじゃなくちょっとずつ消えるようなアニメーションが実装されたようだ。ツリー型タブにアニメーション効果を加えた時にも書いたけど、状態が不連続に変化するよりも連続的に変化した方が分かりやすいことは間違いないので、この改良は歓迎すべき事だと思う。

使い勝手的な面とか見た目的な面とかでは多分他の人が言及していると思うので、僕は実装とかAPIとかの面で感心した点に言及してみる。

このパッチが入るちょっと前に「新しいタブを開く時にアニメーション効果を適用する」という処理も入ったんだけど、この2つには違う所が1点ある。それは、既定の状態がアニメーション有りか無しかという点だ。

  • gBrowser.addTab(uri)で、アニメーション有りでタブが開かれる。
  • gBrowser.addTab(uri, { skipAnimation : true })で、アニメーション無しでタブが開かれる。
  • gBrowser.removeTab(tab)で、アニメーション無しでタブが閉じられる。
  • gBrowser.removeTab(tab, { animate : true })で、アニメーション有りでタブが閉じられる。

今回のremoveTab()の既定の挙動とフラグ指定の仕様は大いに評価したい。何故かというと、これは互換性を最大限保つために必要なことだからだ。

タブを閉じる時にアニメーション効果を適用させるということは、タブに「閉じるという操作は行われたが、まだ実際にはDOMツリー状に存在している」という状態が加わったということだ。そのため、gBrowser.mTabContainer.childNodesで取得したタブの一覧の中に、本来であれば処理対象にしては行けないタブが含まれてしまうようになる。

これは大いに混乱を招く。アドオンの多くはタブを閉じる際のアニメーション効果のことなど知らないから、タブが閉じられたらすぐにDOMツリーからも消えることを期待している。そういう前提で書かれたコードが、片っ端から動かなくなってしまう恐れがある。

今回のパッチでは、この混乱を最小限にするための配慮が行われている。このような状態が発生する場面を「タブのクローズボックスがクリックされた時」と「Ctrl-Wなどのキーボードショートカットで現在のタブが閉じられようとした時」だけに限定することで、それ以外の場合は明示的に指示されない限りはアニメーションしないようにしてある。アドオンが要であるFirefoxとしては、APIの変更でアドオンが作りにくくなる・人気のアドオンが動作しなくなることのデメリットの方が、美しい設計のAPIにすることのメリットを上回ると考えられるから、この判断は合理的と言っていい。

今後の課題は、互換性を重視したことでAPIが分かりにくくなってしまった点をどうするか、ということではないかと個人的には思っている。特に、addTab()removeTab()という対になったAPIにおいて、似たような事をやるのにそれぞれやり方が違うというのは、あまり誉められる事ではないから。

ただ、今はとにかく、Mozillaが互換性を重視する決定をしてくれたことをありがたく思っている。

愚行権 - Aug 07, 2010

ロボット工学三原則っていうのがある。

  • 第一条 ロボットは人間に危害を加えてはならない。また、その危険を看過することによって、人間に危害を及ぼしてはならない。
  • 第二条 ロボットは人間にあたえられた命令に服従しなければならない。ただし、あたえられた命令が、第一条に反する場合は、この限りでない。
  • 第三条 ロボットは、前掲第一条および第二条に反するおそれのないかぎり、自己をまもらなければならない。

リンク先にも書いてあるけど、これは平易に言い直せば「何より安全で、それでいて便利で、できれば丈夫で」ということだ。この優先順位は非常に重要で、便利さのために安全性を犠牲にしてはならないし、丈夫さのために便利さを犠牲にしても(本来求められている要件を満たさないような物になってしまっても)いけない。ロボットだけでなく道具一般を作る時にも言える事だ。

ただ、これを厳密に守っているとできない事というのは当然出てくる。例えば、人を殺すロボットというのは作れない事になる。これは、ロボット工学三原則がまとめられた背景の1つに「ロボットが人を殺すんじゃないかという恐怖を払拭するため」という目的があるから、その目的においては願ったり適ったりなんだけど。しかし、この三原則が出てくる小説の中でも「人間が敢えて危険を冒す必要がある作業をしようとして、ロボットが止めに入ってしまうので作業が進まない」というジレンマが出てくる。多分、スタントマンみたいな事や、新薬の臨床試験なんかは、できないことになる。

人命とか安全性とかとトレードオフでしか得られない物を、どうしても得たい。という人の行動を正当化するのが、愚行権という言葉だ。僕も愚行権をよく行使しているという自覚がある。本当だったらちゃんと栄養バランスを考えた食事を取らないといけないんだろうけど、やる気がないから晩ご飯はお菓子だけで済ませてしまおう、とか。

ただ、そういうトレードオフな選択をできるのは、あくまで自分の事だけなんだよね。自分と同じくらい(あるいはそれ以上)に判断力のある相手に「君もそうするかい?」と訊く事はできても、「お前もそうしろ」と押しつける事はできない。「君もそうするかい?」と訊いた後に、相手が「俺もそうしよう」と同じ行動を取る事を選ぶか、それとも自身の考えに基づいて拒否して「俺はちゃんと野菜食べたいから、コンビニ行ってサラダ買ってくるわ」と別の行動を取るか、その選択は相手に委ねる事になる。

お互いがそういう風に自己決定権を尊重し発動しあえるという前提があるからこそ、他人同士というのはうまくやっていけるものなのだと、僕は思う。他人から自己決定権を奪うような事は、原則としてしてはいけないと思っている。相手にも自分にも自己決定権があるからこそ、「みんな好きなようにやればいいと思うよ」と僕は言える。

問題は、本当にその決定が正当な物なのか?ということだ。

自己決定権の行使には、正確な判断材料が欠かせない。「めんどくさいなあ、お菓子で晩ご飯済ませちゃおうかな」という考えの決定には、「きちんとした食事を準備するのはめんどくさい」とか「お菓子だけ食べてると栄養バランスが偏る」とか「栄養バランスが大事」とか、いろんな情報が必要だ。諸々の情報を勘案して「それでも敢えてこの選択を取ろう」と判断したからこそ、その判断は自己決定権の行使として意味がある。「お菓子を食べると健康にいいんだよ、お菓子だけ食べてれば健康になれるんだよ」なんて間違った情報を吹き込まれてしまって、それに大きな影響を受けて下してしまった判断は、正当で意味のある判断と言えるのかどうか?

また、判断のエンジン自体も正しくないといけない。「何十年と先に起こり得るリスクと、今のこの一瞬の快楽とを天秤にかけて、敢えて今の快楽を取ろう」と判断するからこそ、その判断には意味がある。「将来って何? そんな事より、みんな毎日お菓子食べてるから僕もお菓子食べたい!!!!!!! お菓子おいしい!!!!!!」みたいな短絡的思考しかできないのだとしたら、慎重に考える事もできるけど敢えて短絡的になってみたのではなく、慎重に考えるという事がそもそもできないのだとしたら、その判断は、正当で意味のある判断と言えるのかどうか?

安全でないことをやることを他人に促して決意させるなら、その決定は、正しい情報と確かな判断力に基づいて行われていなくてはならない。正しい情報も提供できない、判断力を持っている事も確かめられないなら、危ないことはできないように設計しておくべき。そういう設計ができないのなら、そもそもそういうシステム自体を提供しないべき。そういう考え方が重要なのではないかと僕は思う。

話は変わるけど、そういう事を考え出して、子供を持つのは自分には無理なんじゃないか? とか、子供を持つというのは恐ろしい、とか、そういう風な事をふと思った。

だって、右も左も分からない、自己決定権を行使できるとはまだまだ言えない子供の人生を、自分が預かる事になるんですよ。自分一人の事なら、愚行権を気楽に行使できる。でも相手のことだとそうは言えない。特にその相手が、自分のしている事の良し悪しすらもよく分かっていないのなら。「子供の自主的な判断に任せてますから」と言って何も手を出さなければ、子供はただただ素直に自分にとっての快楽を追求して、お菓子ばっかり食べたりどっかの女を孕ませたりどっかの男に孕まされたり犯罪を犯したり、とにかく何でも馬鹿なことをするだろう(僕は性悪説の立場なのでそう考えてる)。

かといって、「子供に判断力なんか無いんだから」と何もかもを親が代わりに決めてしまうわけにもいかない。その「子供」が「大人」になる機会自体を潰してしまう、自分一人では何も決められない「大きな子供」に育ってしまうかもしれないし、あるいは、「自分のしたいことを何もさせてもらえない」というストレスで心を病んで不幸な人生を送らせてしまうかもしれない。

なんかもうその点だけで、世の人の親というのは尊敬に値するんじゃないかとすら思える。全くの放任でもなく、全くのがんじがらめでもない。完全に相手に委ねるのでもなく、完全に自分が判断を代行するのでもない。中間を行ったり来たりしながら最適なバランスで接する、そんなことを世の人の親はみんなやってるのか!?と思うと、うわー自分にはそんなこと無理だ、としか思えない。(そして実際、そうできなくてグダグダになった挙げ句、親子で万引きだとか自分で何も考えられない頭空っぽな大人だとか、そういうのが出てくるんだと思う。)

Windows 7のVirtualStoreに泣かされた - Jul 29, 2010

「C:\Program Files\foo\bar\hoge.txt」という位置にあるファイルを読み込んで処理するアプリケーションを開発していて、テストって事でhoge.txtを書き換えてみたのに、アプリケーションの方でhoge.txtを読み込んでみると、書き換える前の内容が返ってくるんですよ。え、このhoge.txtじゃなくて別の位置のhoge.txtを読みに行っちゃってるの? と思って「C:\Program Files\foo\bar\hoge.txt」を消してみると、「ファイルが見つかりません」ってエラーになるんですよね。だから見に行ってるのは確かにこのファイルで間違いないはずなのに。

僕は普段秀丸エディタを使ってるんですが、秀丸エディタって、書き込み権限が無いファイルを開くと「読み込み専用」って表示されて閲覧だけの状態になるんですよね。でも今回のhoge.txtはそうなってないから、ちゃんと書き換えも保存もできてるんですよ。一回秀丸エディタのウィンドウを閉じて、もう一度「C:\Program Files\foo\bar\hoge.txt」を開くと、さっき保存した時の内容になってるんで、書き込みに失敗したというわけでもない。なのに、自作のアプリで「C:\Program Files\foo\bar\hoge.txt」を開くと、秀丸エディタで書き換える前の内容が返ってくる。他にも同じ現象が起こってる人がいるみたいだし。何なんですかこれは?

……結論から言うと、犯人はWindows Vista以降で導入されたVirtualStoreという仕組みでした。

Windows Vista以降ではファイルに対して書き込みができない時に、「書き込めませんでした」というエラーを出す代わりに「C:\Users\ユーザ名\AppData\Local\VirtualStore」っていうフォルダの中にファイルを書き出すアプリケーションがあるみたいなんですね。今回は秀丸エディタがそうだったんですけど。実際、「C:\Users\ユーザ名\AppData\Local\VirtualStor\Program Files\foo\bar\hoge.txt」を見たら、さっき書き換えたはずの内容でhoge.txtが存在していました。

で、この状態で秀丸エディタで「C:\Program Files\foo\bar\hoge.txt」を開こうとすると、Windowsが「C:\Users\ユーザ名\AppData\Local\VirtualStor\Program Files\foo\bar\hoge.txt」の内容を返すみたいなんですよ。これはWindowsのファイル入出力の仕組みが透過的にやっていることらしくて、秀丸から見た時のファイルの位置はあくまで「C:\Program Files\foo\bar\hoge.txt」のままであるというのがポイント。で、この時メモ帳などの別のアプリケーションから「C:\Program Files\foo\bar\hoge.txt」を開くと、今度はVirtualStoreの方ではなく「C:\Program Files\foo\bar\hoge.txt」の実体の方が返される。その結果、「秀丸エディタとメモ帳でそれぞれ同じファイルを開いてるはずなのに、実際に見えてる内容が違う」という現象が起こる。

しかも最悪なことに、どうやら一回この状態に嵌ってしまうともうお手上げのようで、それ以後は秀丸ではVirtualStoreの方のファイルにしかアクセスできないんですよね。参った。もうVirtualStoreを全部消してしまいたい位なんですけど、VirtualStoreを見てみると他にもいろんなファイルが存在してるので、同じようにVirtualStoreの方の内容が返ってきてるお陰で正しく動作しているという状態のアプリケーションが他にもあるんだったら、不用意にVirtualStoreを消してしまうと大変なことになりかねないので消すわけにもいかない。

「コントロールパネル」→「管理ツール」→「ローカル セキュリティ ポリシー」で「セキュリティの設定」→「ローカル ポリシー」→「セキュリティ オプション」→「ユーザー アカウント制御:各ユーザの場所へのファイルまたはレジストリの書き込みエラーを仮想化する」を「無効」にするとVirtualStoreを無効にできるようなので、クリーンな環境でWindows VistaやWindows 7を使い始める時は、無効にしとくといいような気がします。本来できないはずの事をユーザに気取らせずにやろうとするから無理が生じるのであって、システムファイルをいじれるのは管理者権限がある人だけというルールを徹底していれば、無駄な混乱はしなくて済むのです。LinuxとかBSDとか、そういうものですし。

追記。付いたコメントの中に、僕の目には「Program Files以下にあるファイルをわざわざユーザが書き換えなきゃいけないという仕様が今の時代にはそもそもあり得ないでしょ。技術レベルの低い無能な似非プログラマがWindowsの仕様に逆ギレとか、まったく自業自得のくせに滑稽極まりないし。プププのプー」と言っているように見える物がいくつかあったけれども、Firefoxでいえばapplication.iniにあたるような、そう頻繁に書き換える訳じゃないけど可変のパラメータを外部ファイルで定義できるようにしておくためのファイルをいじってテストしてた時に遭遇した話なので、結構イラッとしました。

JavaScriptのスタックトレース - Jul 26, 2010

先日のブラウザー勉強会吾郷さんにお会いした際に、JavaScriptには言語の仕様として「例外がどこから投げられたのか」を知る為の仕組みが無いので独自のフレームワークを作る時に困ったという話を伺った。オレ標準JavaScript勉強会で何話せばいいか困ってた所だったので、それをネタに発表させてもらう事にした

改めてECMAScriptの仕様書を確認してみたけど、確かに3rd Editionと5th Editionでは、例外オブジェクトの機能としてはError.prototype.nameError.prototype.messageしか定義されていなかった(あとはconstructorとかtoString()とかその程度)。Wikipedia(英語の方)のECMAScriptの記事によるとECMAScript 3rd EditionはJavaScript 1.5とJScript 5.5の共通部分を抜き出す形で策定されたようで、実装してない方のIEに合わせてこうなったのか?とも思ったけど、調べてみたらJavaScript 1.5の頃はMozillaもError.prototype.stackはサポートしてなかった。それ以降にMozillaが独自に拡張した箇所ということのようだ。でも今回調べた限りではOperaもChromeもError.prototype.stackをサポートしていた(Operaの場合はこれに加えて、少しフォーマットが違うスタックトレースをError.prototype.stacktraceでも取得できる、ということをedvakfさんに教えていただいた)。メジャーなJavaScript実行環境でこれに対応してないのはIE(IE9PP3を含む)くらいのようだ……と思ったらSafari(Windows版)もサポートしていなかった。

吾郷さんのそれやUxUのようにデバッグを支援するためのフレームワークでは、スタックトレースは欠かせない要素だ。将来のECMAScriptの仕様に取り込まれてくれればいいのになあ、と思う。

勉強会ではせがわようすけさんに突っ込まれたけど、セキュリティのためにはなるべくこういう情報は出さない方がいいものらしい。しかし自分はスタックトレースの何がまずいのかがよく分かっていない。会場では「例えばスタックトレースでスクリプトのURLの中にセッションIDが含まれていたらセッションハイジャックされてしまう危険性がある」という例を教えていただいたけれども、それでもまだピンと来ていなかった。

さらにツッコミを受けたことで「なるほど、クロスドメインの制約を突破されてしまう」という事が問題なのだと分かった。ただ、実際にそれが問題になるケースってほんとにあるのかな? という疑問はまだ残っている。

分かりやすい話として、通常のXMLHttpRequestやiframeでは、別ドメインのドキュメントを読み込む事ができない・読み込んだとしてもその内容にスクリプトからアクセスすることはできない。

var iframe = document.createElement('iframe');
iframe.setAttribute('src', 'http://www.google.co.jp');
document.body.appendChild(iframe);
window.setTimeout(function() {
  try{
    alert(iframe.contentDocument.body);
  }
  catch(e){
    alert(e);
  }
}, 3000);

例えばこういうのは、Error: Permission denied for <http://piro.sakura.ne.jp> to get property HTMLDocument.body from <http://www.google.co.jp>.と言われてエラーになる。しかしスタックトレースにエラー行の詳細な情報が含まれていると、

try {
  document.write('<script type="text/javascript" src="http://www.google.co.jp/" async="false" defer="false"></script>');
}
catch(e) {
  var contentsFragment = e.stack;
}

とか

try {
  var script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');
  script.setAttribute('src', 'http://www.google.co.jp/');
  script.setAttribute('async', 'false');
  script.setAttribute('defer', 'false');
  document.body.appendChild(script);
}
catch(e) {
  var contentsFragment = e.stack;
}

とか

// window.onerrorはECMAScriptの仕様にはない独自拡張
window.onerror = function(aMessage, aSource, aLineNumber) {
  // ここでaMessage, aSourceから情報を取れる可能性がある
}
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', 'http://www.google.co.jp/');
script.setAttribute('async', 'false');
script.setAttribute('defer', 'false');
document.body.appendChild(script);

という風にして、別ドメインのドキュメントの内容を部分的にとはいえ読み取れてしまう可能性がある、というわけだ。特に3番目のwindow.onerrorを使う例はMFSA 2010-47: エラーメッセージのスクリプトファイル名からのクロスサイトデータ漏えいで実際に脆弱性になっていたことが確認されていて、既に修正されている。

なお、実際に現行バージョンのFirefox・Opera・Chromeで試してみたところ、1番目・2番目のtry-catchを使った例ではそもそも例外を例外として(そもそも、エラーが発生したのかどうかすら)捕捉できなかったので、今の所問題にはならない。ただ、今は問題にならなくても、今後登場するJavaScriptの実装ではこういう場合でも情報を取れるようになっている可能性はあるので、将来的に脆弱性の原因になるかもしれないという警告は確かにアリだとは思う。という所までは何とか理解できた。

ブラウザのレベルでは、クロスドメインやクロスオリジンになる時だけは例外を出さないとか詳細な情報を出さないとか、そういう対応の仕方はあるだろうけれども、「ECMAScript」の仕様でそこに言及するのは変だろうなあ。というややこしい事情を勘案すると、もう丸ごと全部仕様からドロップしてしまえという判断になるんかなあ。

ヒステリーとか癇癪とか - Jul 13, 2010

癇癪を起こした事がない人、というのもいるんですね。というかもしかして、癇癪を起こした事がある人の方が割合的には少なかったりするんでしょうか? 僕は小さい頃から良く癇癪を起こす子だったと思うので、自分基準で考えてしまいますが、世間基準だと僕みたいなのは珍しい方なんですかね。

安い煽りにすぐ乗せられてしまう。嫌味を言われたらカチンと来て、何か言い返さないと気が済まない。我慢ができない。自分がいいと思った物の価値を他人が認めていないとイライラする。何で僕チンの思い通りにお前らは動かないんだよ!!!と考える。そういうのの行き着く先にある、頭の中が真っ白になってしまって理屈で物を考えられなくなって、それまでは「しょうがない」と思って我慢できていたはずの事でも我慢できなくなって噴出してきて、息が荒くなって、顔を真っ赤にして、歯を食いしばって、何か言いたいんだけど、でも思っている事の中身を言葉にする事ができなくて、だから何も言葉が出てこなくて、「フゥーッ!!!! フーッ!!!! ヒゥーッ!!! ヒグゥーッ!!!! ヒゥーッ!!!!」と過呼吸になる。

そういうタイプの人間なんですよね僕は。普段の生活で今はそういうのが出てくるような機会がほとんど無いけれども、強いストレスがかかるとたまに、そういう方向に転がり落ちそうになる。

発達障害とか精神病とか知恵遅れとかそういう奴なんだと思いますよ。日常生活で支障のない範囲、にギリギリ収まっているというだけで。「お前ごときが障害者を名乗るのはおこがましい」「お前ごときのようなありふれた人間が、自分は特別だと思いたいばかりに障害者を自称するから、本当の障害者が肩身の狭い思いをさせられるんだ。恥を知れ恥を。」等々、言いたい人は言えばいいと思いますが。

一昨日くらいですかね。そういう酷い癇癪を起こす夢を見たんですよ。この僕チンを裏切ったな!!!!!みたいな感じの、ひどく幼稚で自己中心的な理屈で癇癪を起こすという夢を。目が覚めた時にはリアルに過呼吸になってました。ああ、すっかり忘れていたけれどもこれが僕の本性なんだよなあ、という事を改めて意識しました。

障害児の第1子がいる状態で第2子を産むかどうかって話をたまたま読んで、なおのこと暗い気分になる今日この頃です。

XUL/Migemo 0.13.xで中身の方をだいぶ変えて(環境によっては)高速化しました - Jul 09, 2010

半分眠ったまんまで作業してたのでしょーもないregressionを仕込む→修正して公開→またregressionということを繰り返した結果、既に0.13.3になってしまったわけですが、バージョン0.13.0で結構大きくいじりました。機能的には全然変わってないですが。

  • 開発メモにちょっと書いてるけど、このバージョンからFirefox 2とThunderbird 2をサポートしなくなりました。
  • Gecko 1.8どころかGecko 1.9.0も切り捨てたという事で、Firefox 3.5より後で入った便利機能を心置きなく使えるようになったり、Firefox 2→Firefox 3の間で大きく変わったAPIの両方に対応するためのめんどくさい場合分けをゴソッと削除できたりしたので、だいぶスッキリした気がします。
    • ウィンドウごとに分ける必要がないコードでXPCOMコンポーネント化してなかった物は積極的にJavaScriptコードモジュール化しました。
    • Thunderbird専用のコードは、XPCOMコンポーネントにしてはあったものの、そんなに汎用性が無かったので、これも全部JavaScriptコードモジュール化しました。
  • 検索でヒットした箇所が多い時に、Safari風の強調表示等の機能を有効にしているとフリーズしてしまう問題について、ヒット箇所の強調表示を段階的に非同期で行うようにしてみました(0.13.4)。
  • Gecko 1.9.2以降ではnsIDOMWindowUtilsのnodesFromRect()を使うようにしたので、見えているスクロール位置から検索を始める処理が相当速くなりました。
  • Minefieldに最近入った機能のおかげで、DOM Rangeから検索用のテキストを取得する部分が爆速になりました。nodesFromRect()の効果と合わせて、Minefield 4.0b2preではページ内検索のストレスがほとんど無くなったと思います。

Minefieldで検索が速くなったのは、Bug 39098 – Elements with visibility:hidden, visibility:collapse, or display:none get copied to the clipboardがfixされたおかげです。

  • これまでXUL/Migemoのページ内検索が非常に重かったのは、Rangeを一旦文字列にして正規表現でマッチングして、その結果を使ってもう一度ページ内検索するという仕組みに理由があります。
  • この時、RangeをそのままtoString()で文字列にすると、CSSで非表示になっている要素のテキストまで一緒に取得されてしまうのですが、その後rangefindで検索する時は非表示のテキストは検索対象にならないため、場合によってはヒット箇所が飛ばされてしまったり検索が止まってしまったりという問題が起こります
  • そこで現在のXUL/Migemoでは、
    DOMWindow.QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIWebNavigation)
       .QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsISelectionDisplay)
       .QueryInterface(Ci.nsISelectionController)
       .checkVisibility(textNode, 0, textNode.nodeValue.length)
    というやり方で個々のテキストノードの可視・不可視の状態を判別して不可視のテキストを除外した結果を取得して、正規表現のマッチングに使うようにしていました。
  • しかしこの方法は、テキストノードの数が増えると判断の回数も同じだけ増えてしまうため、MXRのページのように内容が小さなテキストノードに分断されているページだととんでもなく処理に時間がかかってしまいます。
  • 5月末にBug 39098で入った変更によって、テキストリンクの高速化の時にも使ったnsIDocumentEncoderに、「非表示状態のテキストを結果の文字列に出力しない」というオプションが加わりました。なのでMinefield上では、この機能を使ってRangeの中の可視状態のテキストだけを一気に取得するようにしました。

Minefield 4.0b2preにXUL/Migemo 0.13.xを入れてMXRのページでページ内検索してみると、効果の程がよく分かると思います。僕は、今すぐにでもFirefox 3.6を窓から投げ捨ててしまいたくなりました。

あとはJägerMonkeyが入ってくれれば……

ところで、他のアドオンからXUL/MigemoのAPIを呼び出す時はちょっと注意が必要になってます。

  • ずっと前に「ユーザ辞書の内容がWebページのスクリプトから取れてしまうのはセキュリティ的に宜しくない」という指摘を受けたんですが、どうやってこの問題を回避すればいいものか悩んで、結局、グローバルなJavaScriptのプロパティのmigemoオブジェクトから機能を呼び出す時には常にシステム辞書の内容だけを返すように仕様を変えました。
  • ユーザ辞書に追加された単語も含めた正規表現を取得したい場合、Components.utils.import('resource://xulmigemo-modules/service.jsm')でユーティリティを読み込んでXMigemoCoreの各メソッドを呼ぶか、Cc['@piro.sakura.ne.jp/xmigemo/factory;1'].getService(Ci.xmIXMigemoFactory).getService('ja')てな感じでXPCOMコンポーネントを直接呼び出すかする必要があります。
  • XMigemoCoreでもxmIXMigemoCoreでもどっちにしても、これらが持つ機能で正規表現を生成する時は戻り値は常に文字列(正規表現のソース文字列)になるので、その都度new RegExp()してやる必要があります。
  • 辞書が絡まない機能はmigemoのメソッドを使って問題ありません。XUL/MigemoのUI周りでもそうしてます。

nsIDOMWindowUtilsのnodesFromRect - Jul 07, 2010

nsIDOMWindowUtilsのnodesFromRect、解説がなくてさっぱり使い方が分からなかったので使ってみて調べた結果を記しておく。

インターフェースの定義はこんなん。

nsIDOMNodeList nodesFromRect(in float aX,
                             in float aY,
                             in float aTopSize, 
                             in float aRightSize,
                             in float aBottomSize,
                             in float aLeftSize,
                             in boolean aIgnoreRootScrollFrame,
                             in boolean aFlushLayout);

8個もの引数を取るんですよ……

  • Gecko 1.9.2(Firefox 3.6)から使える。
  • 矩形で示された範囲から、その範囲に含まれるDOMノードを収集して返す。
  • Fennec(Firefox Mobile)用に実装された(Bug 489127 – nodesFromRect required for better usability on mobile devices)。
    • Fennecはタッチスクリーン型のスマートフォンで使われる前提のプロダクト。
    • 指で触った所にあるリンクを拾いたい。
    • でも、小さい画面だと正確にリンクをタッチするのはどう考えてもムリ。
    • そういう時は、タッチスクリーンで検出した座標の周囲のある程度の範囲も含めて取得して、リンクが2個以上あったらそれぞれを選択肢として表示するなどの対策を取れたらいいよね。

そういう経緯で実装されたので、第1から第6までの引数は経緯から考えたら納得のいく指定の仕方と言えよう。

  • 第1引数・第2引数は検索の中心座標を示す。「現在のスクロール位置」は0, 0になる(scrollX, scrollYを指定すると「ビューポートの左上からscrollX, scrollY」という意味になってしまうので注意!)。
  • 第3、第4、第5、第6引数は、検索する範囲を中心座標から上・右・下・左のそれぞれの方向に何ピクセル広げるかを示す。この並び順はCSSでお馴染みの物ですね。
  • 第7引数は、親フレームのスクロール状態を考慮するかどうかを示す真偽値。らしい。falseを渡した場合、ビューポートの外の矩形を範囲として指定すると空のノードリストが返るみたい。
  • 第8引数は、未確定状態のレイアウト情報を確定するかどうかを示す真偽値。らしい。

戻り値のノードリストは、どういう訳か文書中の登場順の逆順で返ってくるようだ。

XUL/MigemoのMinefield対応に向けてのメモ - Jul 07, 2010

作業メモ。

  • gFindBarの遅延初期化には対応した。window.watch('gFindBarInitialized', function() { ... })で、遅延初期化のタイミングで初期化を行える。
  • XPCOMコンポーネントの登録方法の仕様変更にも対応した。これから先、できれば仕様は変わらないでいて欲しいけど……
    • classDescriptionにホワイトスペース文字が入ってると、カテゴリへの登録がうまくいかないかも。nsUpdateTimerManagerに対応するマニフェストファイルなんかを見てみた感じでは、実装のクラス名をそのままclassDescriptionにするといいのだろうか?
    • nsSidebar.jsに対応するマニフェストファイルを見て分かったけど、今までカテゴリマネージャに登録する必要があったケースは全部マニフェストファイルでやるという事のようだ。マニフェストファイルの方に書いておかないと、ちゃんと認識されなかった。
  • これまで、Firefox 2からFirefox 3.6の間でメソッド名やプロパティ名が変わった物はFirefox 2に合わせるようにしてたけど、今後はMinefield 4.0b2pre基準にする事にする。今の所はほとんどFirefox 3.6と同じだけど。
  • Firefox 2からFirefox 3.6までの間それぞれのためのコード、特にFirefox 2用とそれ以降用とでコードを書き分けてた部分が多かったので、思い切ってFirefox 2用のコードは全廃する事にした。
  • rangefindを使う時に注意がいる
  • 他アドオンとの連携にも注意がいる

しないといけないなーと思ってる課題。

  • Minefieldに既に入ってる、スマートロケーションバーの候補として出てきたplaceが既にタブで開かれている場合にそのタブに切り替える機能への対応。
  • Thunderbird 3。とりあえずフォルダペイン内での絞り込みはできるようにしとかないと……とは思ってる。

スマートロケーションバー関係を調べて分かった事。

  • Minefieldは、タブで閲覧中のページをtabbrowserが持ってるプログレスリスナで常時監視してて、タブで閲覧中のplaceの一覧をmoz_openpages_tempという名前のテーブルに保存している。
    • このテーブルはplace_idopen_countという2つのカラムを持ち、ページの遷移に応じて内容が随時更新される。
  • 検索でヒットしたplaceについて、このmoz_openpages_tempopen_countが0より大きい物は、オートコンプリートの候補として返される時にURIの前にmoz-action:switchtab,という文字列が付与され、lichlistitemのtype属性用の値にはactionという値が設定される。
  • スマートロケーションバーのオートコンプリートの実装はいつの間にかJavaScriptになっていた。Cより読み慣れてるから助かる。
  • この機能が有効になるのは、オートコンプリート用のtextboxのautocompletesearchparam属性の値にenable-actionsという文字列が含まれている時だけのようだ。
    • 機能が無効になっている時は、単に上記の「URIの前にmoz-action:switchtab,という文字列が付与され~」という処理がスキップされる。
    • この事から分かる通り、moz_openpages_tempopen_countはオートコンプリートの候補の並び順には影響しない。あくまでfrequencyベースで検索していて、ヒットした候補の中にたまたま現在タブで開かれているplaceが含まれていた場合にだけ、この機能が発動するという仕様のようだ。
  • ロケーションバーへの入力時に「タブで開いてる奴だけ表示」という風に制限する時の既定のキーワードは「%」。browser.urlbar.default.behaviorで指定する時のフラグは128(1 << 7)。

Minefield 4.0b2preではどうも同期的な処理がことごとく失敗するようになっている気がする - Jul 06, 2010

XUL/MigemoのMinefield 4.0b2pre対応のために色々検証していて、1つとても困った問題にぶち当たった。rangefindを使ってWebページ中の要素を装飾する時に、前から後ろに向かって処理を行うと検索が止まってしまう。

Components.utils.import('resource://gre/modules/debug.js'); 
const Cc = Components.classes;
const Ci = Components.interfaces;

function decorate() {
  var span = d.createElement('span');
  span.setAttribute('style','font-size:150%;');
  foundRange.surroundContents(span);
}

/* コンテンツ領域にテスト用の内容をロードする */
var d = Cc['@mozilla.org/appshell/window-mediator;1']
          .getService(Ci.nsIWindowMediator)
          .getMostRecentWindow('navigator:browser')
          .content.document;
d.documentElement.innerHTML = 'Firefox, Firefox, Firefox.';
d.documentElement.clientTop; /* ←伏線 */

/* rangefindを初期化する */
var find = Cc['@mozilla.org/embedcomp/rangefind;1'].createInstance(Ci.nsIFind);
find.findBackwards = false; /* 前から後ろに向かって検索 */
find.caseSensitive = false;

var findRange= d.createRange(); /* 検索する範囲 */
findRange.selectNodeContents(d.documentElement);
var startPoint = findRange.cloneRange(); /* 検索の始点 */
startPoint.collapse(true);
var endPoint = findRange.cloneRange(); /* 検索の終点 */
endPoint.collapse(false);

/* 検索を実行 */
var term = 'Firefox';
var foundRange = find.Find(term, findRange, startPoint, endPoint);
NS_ASSERT(foundRange !== null, '1回目で失敗');

decorate(); /* DOMツリーを編集して装飾する */

/* 検索の範囲を変える(編集した箇所より後を検索の範囲にする)*/
findRange.setStart(foundRange.endContainer, foundRange.endOffset);
startPoint.setEnd(foundRange.endContainer, foundRange.endOffset);
startPoint.collapse(false);

/* もう一度検索を実行 */
foundRange = find.Find(term, findRange, startPoint, endPoint);
NS_ASSERT(foundRange !== null, '2回目で失敗');

decorate(); /* DOMツリーを編集して装飾する */

エラーコンソールにこれをコピペして実行してみると、2回目の方で必ず失敗してしまう事が分かる。本当は「Firefox」という文字列が2箇所装飾されて欲しいのに、最初の1箇所だけで処理が止まってしまう。

これ、上のサンプルの中で伏線と書いている部分が鍵なんだけど、どうもこういうことらしい。

  • DOMツリーを編集すると、編集した箇所から先の範囲が「不確定」な状態になる。
  • 「不確定」な範囲に対しては、rangefindは一切の検索を行えない。
  • DOM要素のプロパティにアクセスするなどしてレイアウト情報を参照すると、状態が強制的に「確定」される。
  • または、setTimeout()等で少し遅らせて処理を行えば、その時には状態が「確定」されている。
  • 状態が「確定」されると、その範囲をまた検索できるようになる(最初の方にある d.documentElement.clientTop は、実はそのための物)。

1回目の検索結果のRangeの箇所でDOMツリーを切った貼ったしているので、その箇所より後の部分はどう頑張ってもそのままの流れでは検索できないようになってしまっている、ということのようだ。

なので、

  • 2回目以降の検索を実行する前に span.clientTop あたりにアクセスしてやれば(これ以外にもclientLeftでもoffsetWidthでもレイアウト系のプロパティなら何でもいいっぽい)、whileforのループを回し続ける事ができる。
  • 毎回setTimeout()で状態の「確定」を待ってやるというやり方でもよい。

という風な回避策があると言える。でも、後者は言わずもがな、前者も毎回レイアウト情報を参照するからクソ重くなりそうで、できればどっちの方法もとりたくない所だ。

将来的にどうなるのかは知らんけど、とりあえず今のところは、後方検索(Rangeの後ろの方から前の方に向かって検索する)ならこの問題に引っかからずに済むみたい。編集した箇所から先の部分が「不確定」になっても、後方検索だと「編集した箇所から先=もう検索が終わった範囲」なので。(→と思ってたけどやっぱり動作が怪しいので安全めな方に倒すということで毎回clientTopにアクセスする方法を使う事にした。なんか負けた気分。)

この「rangefindでループを回してDOMツリーを切った貼ったして装飾する」というやり方はFirefox 2以前のページ内検索における「すべて強調表示」の実装方法だったんだけど、今のFirefoxではDOMツリーはいじらずに強調箇所を選択範囲として処理するようになってて、この問題は問題にならないようだ。今でもFirefox本体でこういうことをやってるところがあれば「これってregressionなんじゃないの」とbugzillaに報告できるところだと思うんだけど……

Firefoxのバージョン間の差異を吸収するライブラリをこそ、僕は欲しているというのに。 - Jul 03, 2010

Firefox 3.6以前とMinefieldとでは、アドオンマネージャのAPIがまるっきり変わってしまった。

Firefox 3.6以前のアドオンマネージャ(Cc['@mozilla.org/extensions/manager;1'].getService(Ci.nsIExtensionManager))は同期的なAPIで、頑張ってラップすればvar enabled = isEnabled('treestyletab@piro.sakura.ne.jp');みたいな感じで「その場で結果を取得する」ことができた。でも今のMinefield(アドオンマネージャがタブで開かれるようになった奴)では、インストール済みのアドオンの情報を取得しようと思ったら必ずコールバック関数を使った非同期なAPIでやらなきゃいけないようになってしまった。

// このコードはMinefield(Firefox 4)以降でないと動かない
Components.utils.import('resource://gre/modules/AddonManager.jsm');
AddonManager.getAddonByID(
  'treestyletab@piro.sakura.ne.jp',
  function(aAddon) {
    if (aAddon && aAddon.isActive) {
      // ツリー型タブがインストール済みで、
      // 且つ有効化されている時の処理
    }
    else {
      // ツリー型タブが利用できない時の処理
    }
})

Firefox 3.6以前と今のMinefield(つまり将来のFirefox 4)の両方に対応しようと思うと、この差異をどうやって吸収するかがネックになる。そこで、メインスレッドの処理を一時停止して処理の完了を待つ裏技を使って、同期的なやり方で他のアドオンの有効・無効の状態を調べたり設定ダイアログを開いたりするためのライブラリをMinefieldでもそのまま使えるようにしてみた。

// このライブラリを使うと、Firefox 3.6でもMinefieldでも
// 違いを意識しないでコードを書けるようになる。
var extensions = window['piro.sakura.ne.jp'].extensions;
if (extensions.isAvailable('treestyletab@piro.sakura.ne.jp')) {
  // ツリー型タブが利用できる時の処理
}
else {
  // ツリー型タブが利用できない時の処理
}

そしたら、Minefieldで起動時にセッションが復元されないという現象に遭遇してしまった。条件を絞り込んでいくと、どうもアドオンマネージャのタブが開かれた状態のセッションが復元される時に問題が起こっていて、さらに辿っていくと、上記の裏技で新アドオンマネージャの処理を止めていると、アドオンマネージャのタブの読み込みが阻害されてしまってセッション復元が半端な所で止まってしまうという事のようだった。

仕方がないので、Firefox 3.6以前のやり方に合わせるのではなく今のMinefieldのやり方に合わせる方向でestensions.jsのAPIを拡張して、今後はそっちの使い方を推奨する事にした。

// 新しい書き方。これも、Firefox 3.6でもMinefieldでも
// 違いを意識しないでコードを書いて大丈夫。
var extensions = window['piro.sakura.ne.jp'].extensions;
extensions.isAvailable('treestyletab@piro.sakura.ne.jp', {
  ok : function() { /* ツリー型タブが利用できる時の処理 */ },
  ng : function() { /* ツリー型タブが利用できない時の処理 */ }
});

Firefox 3.6以前でこのAPIを呼んだ場合は、非同期にならずにその場でコールバック関数が呼ばれるという実装上の違いがあるけれども、基本的に非同期で実行される前提でコードを書いておきさえすれば、Firefox 3.6以前でもMinefieldでもそのまま動くようになってる。コールバック関数を渡さなければ今まで通りの同期的なAPIとして動作するので、コールバック関数はどーしても使いたくない!という場合は、非推奨ではあるけど今まで通りの使い方もできる。

FUELとかJavaScriptコードモジュールとか、Firefox本体の方で色々ユーティリティっぽい物が用意されつつあるけど、僕の立場(複数のバージョンのFirefoxをサポートしたいという前提がある)では、それらはまるっきり役に立たない。新しいバージョンのFirefoxで標準のコードモジュールが増えた所で、現行のリリース版のFirefoxにも対応させるなら、結局それは使えないのだから。しかも、Firefoxのバージョンが上がったらAPIが使えなくなっちゃいましたなんて事もザラにある(今回の話も、nsIExtensionManagerがゴッソリなくなってしまったせいで起こった問題だ)。

そういう「簡単に書けますよ」っていうだけのAPIは、もう、ぶっちゃけどうでもいい。そんな物より、Firefoxの複数のバージョン間での差異を吸収するライブラリこそが僕には必要なんだ。新しいやり方に合わせて書いておけば古いバージョンのFirefoxでもそのまま使える、というのでも、古いやり方のままで新しいバージョンのFirefoxでも動く、というのでも、どっちでもいいんだけど、とにかく1つの記述でどっちのバージョンでも動くようにしておきたい。そうじゃないと、新しいFirefox用と古いFirefox用とで目的が重複するコードがどんどん増えていって、片方は直したけどもう片方は直し忘れてたみたいな穴がどんどん増えていって、すぐ破綻してしまう。

FUELはFirefoxのバージョン間の違いを意識しないで使えるようなAPIの提供を目指してたはずだと思ってたけど、今となってはその計画も頓挫してすっかりうち捨てられてしまったような印象がある。実際、今回の件についてもFUELのAPIは互換性を失う形であっさり変更されてしまってて、もうFirefox 3.6の物と同じ使い方はできないし、Firefox 3.6の物の使い方もMinefieldではできない。JetpackはRebootで明後日の方向に飛んで行ってしまって、少なくとも「Firefox 4から先」の事しか眼中になくてFirefox 3.x系はガン無視っぽい。結局、Mozilla本家はアテにならない。アドオン作者が自分達でやる以外にない。

そういう理由で作ったライブラリ類をリポジトリの中にまとめて置いてあるので、似たような事を考えてる人は覗いてみるといいかもしれない。JavaScriptコードモジュールとして使える物はmodestのJavaScriptコードモジュールの紹介ページに簡単な紹介を書いておいたけど、どれもソースの頭の方に用例を付けてあるので、まあ見てもらえばだいたい分かるんじゃないかな。

一応、簡単な説明。

  • jstimer.jsm:JavaScriptコードモジュール等のDOMWindowを参照しづらい場面でsetTimeout()とかsetInterval()とか書けるようにするライブラリ。
  • namespace.jsm:JavaScriptコードモジュール同士で名前空間を共有できるようにするライブラリ。同じJavaScriptコードモジュールの同じ内容のファイルを複数のアドオンでそれぞれ別々に持たせて、それぞれのメモリ空間も別々に確保される、というのがものすごくあほらしく思えたので作ってみた。
  • animationManager.js:JavaScriptでアニメーションさせる時に、複数アドオンで1つのタイマーを使い回して効率よくアニメーションさせるためのライブラリ。jstimer.jsmと併用すればJavaScriptコードモジュールとしても使える。
  • arrowScrollBoxScrollHelper.js:arrowscrollboxの中に1つ大きなボックスが入っていてその中に小さなボックスがたくさんある、という場面でarrowscrollboxのスクロール処理がぶっ壊れる問題を回避するライブラリ。
  • autoScroll.js:mousemoveまたはdragoverイベントに基づいてタブバーの自動スクロールを行うライブラリ。
  • bookmarkMultipleTabs.xul / bookmarkMultipleTabs_bookmarkPropertiesOverlay.xul:複数のタブをまとめて1つのブックマークフォルダにブックマークするためのライブラリ。Firefox本体の機能だと「全部のタブを保存」しかできないので。
  • boxObject.js:HTMLDocumentのgetBoxObjectFor()を使ってたコードを、手直しせずにそのままFirefox 3.5とかで動くようにするためのライブラリ。
  • extensibleToolbarButton.css / extensibleToolbarButton.xml / extensibleToolbarButton.xul:Firefox本体のツールバーボタンに後からappendChild()とかで内容を追加できるようにするためのライブラリ。
  • extensions.js:このエントリでメインの話題にしてる、「他のアドオンがインストールされているかどうかを調べる」「他のアドオンの設定ダイアログを開く」といった事を簡単に行えるようにするライブラリ。
  • operationHistory.js:何かの操作をアンドゥ・リドゥできるようにしたいときのための汎用的な履歴管理ライブラリ。詳しくは解説のエントリを参照してください。
  • prefs.js:FUEL/STEELが無いような古いバージョンも対象にする場合向けの、簡単に設定を読み書きできるようにするライブラリ。
  • stopRendering.js:ウィンドウ内の再描画を一旦止めて、その間に色々GUIをいじくる処理をして、最後にまとめて表示に反映させる、という事をやるためのライブラリ。画面がチラついてなんか気持ち悪い、という不快感を和らげるのが目的。Firefox 3.6以前とMinefieldとでは実装が中身の自動的に切り替わるけど、APIとしてはFirefoxのバージョンの違いを意識せずに使えるようになってる。
  • stringBundle.js:stringbundle要素をXULの方に埋め込まなくても、同じAPIでpropertiesファイルの中の文字列を読めるようにするAPI。
  • tabFx2Compatible.css / tabFx2Compatible.xml / tabFx2Compatible.xul:Firefox 3以降でtabbrowserのtabの中にappendChild()とかで要素を追加できるようにするためのライブラリ。Firefox 2以前のDOMツリー構造を再現して、中にいくつかのボックスを増やす。
  • UninstallationListener.js:アドオンのアンインストールが行われたタイミングで、変更した設定を自動的に元に戻すとかの後片付け的な処理をやるためのライブラリ。Minefieldでは多分動かなくなってる気がするので、後で直しときます。

Page 26/241: « 22 23 24 25 26 27 28 29 30 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき