Home > Latest topics

Latest topics 近況報告

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

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

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

Page 9/243: « 5 6 7 8 9 10 11 12 13 »

XPIファイルのパッケージングの仕方を工夫してもあんまり報われないという事が分かった(僕の場合は) - May 30, 2011

先に結論だけ。

  • アドオンの規模にもよるだろうけど、僕の場合、XPIパッケージの作り方の工夫は大したパフォーマンスの向上には繋がりませんでした。
  • アドオンをインストールした時にFirefoxの起動がどれくらい遅くなるかのパフォーマンス計測の仕組みをMozillaが用意してくれているので、みんなそれを使うといいです。

Firefoxの起動を遅くするアドオンランキングなんてものが公開されて、そこにツリー型タブも堂々ランクインしてて、「多機能オールインワン型アドオンを散々批判してるくせに単機能とか言ってるお前のアドオンの方がよっぽど重てーじゃねえかプゲラ」的なコメントも見受けられる昨今(僕はメンテナンス性とか共存のしやすさとかの観点から多機能型を否定しているつもりなので、ある単機能のアドオンが必ずある多機能のアドオンより軽いはずだとかそんな事は言えないと思っているのですが)、皆様いかがお過ごしでしょうか。

自分はわりと富豪的なプログラムを書く方なので、欲しい機能を実現させるためにいろんなイベントを監視したり、メンテナンスしやすくするためにファイルを細かく分けたり、とかそういう事をやりがちなのですけれども、そういうのって一般的にFirefoxの起動とか動作とかを遅くさせるからやめなさいよというメッセージを、Mozillaは最近アドオン開発者に向けてよく発しています。上記のランキングもその一環ですね(あんまり遅いとこうして晒し挙げるぜ、という)。

で、その晒し挙げランキングのページからパフォーマンス改善の色々なテクニックがリンクされていて、これ読んで出直して来いよ、出直してこないならペナルティだぜ、という話なんですが、そのページの末尾でTalosというパフォーマンス測定ツールを使ってアドオンがFirefoxの起動速度に与える影響を計測する手順も紹介されてました。上記の晒し上げランキングもこれで計測した結果に基づいてるみたいです。

晒し上げランキングの中でツリー型タブがWindows XPで99% Slow(Firefoxの起動にかかる時間が2倍になる)と書かれてて、いくら僕でもちょっと信じがたかったので、実際に自分でもTalosを使ってパフォーマンス計測を試みてみました。僕が試験用に用意したWindows XP環境では、ツリー型タブ0.11.2011050602とNightlyとの組み合わせで35%前後という数値が出ていますが、これはランキングに出ている数値と大きく隔たりがあります。どういうことなんでしょうね。

まあそれはどうでもいいんですよこの際。とにかくこれが契機となって、自分の環境でもアドオンがFirefoxの起動速度に与える影響を機械的に測定できるようになったので、今後のパフォーマンス改善にはこれを1つの指標として使っていこうと思ってるわけです。


さて、そういう背景があってツリー型タブのリポジトリ上の最新版では、主要なコードをJavaScriptコードモジュール化してみたり、関連ライブラリの読み込みをXPCOMUtils.defineLazyGetter()でやるようにしてみたり(この機能はFirefox 3.6以降でしか使えないので、自動的にツリー型タブの次のリリースはFirefox 3.5対応を切り捨てる事になります)、読み込ませるスタイルシートの数を大幅に減らすようにしたり、ついでに属性セレクタの使用を減らしてみたり、という風な、アーキテクチャに変更が無い範囲で前述の「パフォーマンス改善のためのテクニック」を色々と取り込んでいるのですけれども、まだ実施していない項目の1つに「XPIパッケージの作り方を工夫する」という物がありました。

XPIパッケージというのは、アドオンを構成するファイル一式をZIP形式で圧縮した、配布用のパッケージです。Firefox 3.6までは、アドオンをインストールするとこのXPIパッケージが自動的に展開されて、ユーザのPCのストレージ上に個々のファイルが配置されるようになってました。ただ、ファイルの数が多いとFirefoxの起動に時間がかかるようになってしまうためとか諸々の理由から、XULファイルやCSSファイルやJavaScriptファイルなどはJARファイル(これも実態はZIP形式の書庫ファイルです)にまとめておいて、見かけ上のファイルアクセスを減らす事が推奨されていました。

JARファイルにせよXPIファイルにせよ、圧縮率を高くすればするほど中身を取り出すのには余計な時間がかかる事になります。なので、ユーザの環境にインストールされた時に1回だけ展開されるXPIファイルは圧縮率を最高にして保存して、インストールされた後も毎回中身を参照されるJARファイルは無圧縮で保存する、という事によって配布ファイルのサイズを小さくすると同時に実際の使用時のパフォーマンスも高く保つ……というのが従来のXPIファイルの作り方の定石でした。

ところがFirefox 4以降、パフォーマンス改善の一環として、Firefoxの構成ファイルは可能な限り1つのJARファイルにまとめられるようになりました。これは「omnijar」と呼ばれている新機能で、今まではバラバラのファイルとして配置されていたJavaScript製のXPCOMコンポーネントやJavaScriptコードモジュールまでも、他のXULファイルやCSSファイルのようにJARファイルの中に格納してしまって、更なるパフォーマンスの向上を実現しようというわけです。

Firefox 4本体がそうなったのに併せて、アドオンにもomnijarの仕組みが使われるようになりました。今までならXPIファイルをインストールしたらその中身を常に展開するようになっていたのが、Firefox 4でアドオンをインストールした場合は原則としてXPIファイルのままで保存されて、中身にはomnijarの仕組みでアクセスするようになったんですね。

ここで3つの問題が起こります。

  1. XPIの中にあるバイナリ形式のコンポーネントをそのまま使う事はできない。
  2. XPIファイルはインストール時に1回だけ展開される事を想定して最高の圧縮率にしている。毎回の起動時にその都度展開されると、その分余計な時間がかかる事になる。
  3. XPIファイルの中にJARファイルが格納されている、という入れ子構造のままで毎回使われる事になる。これもその分余計な時間がかかる事になる。

1については仕様上の制限という事で、こういう場合だけはFirefox 3.6の時と同様の形でXPIを展開した状態にしてインストールさせる必要があります。install.rdfにem:unpack="true"という指定を書き加えると、アドオンのインストール時にXPIファイルが展開されるようになります。

2と3はパフォーマンス低下の問題です。前述の「パフォーマンス改善のためのテクニック」では2と3を理由として、無圧縮JARに固めてから最高圧縮率のXPIを作るのではなく、中身をJARに固めないで全体を圧縮率の低いXPIに固める(アーカイブを入れ子にしない)、という事が推奨されています。

しかしこれは見ての通り、Firefox 3.6以前のバージョン向けの定石と全く相反しています。Firefox 4のためにFirefox 3.6でのパフォーマンスを犠牲にするか、Firefox 3.6のためにFirefox 4でのパフォーマンスを犠牲にするか、どちらかを選ばないといけないという事になります。


でも、ほんとにそうなの? Firefox本体くらいの規模ならいざ知らず、それより遙かに小規模なアドオンでXPIファイルの作り方がそんなに起動速度に大きな影響を与えるの?

という事が気になったので、ツリー型タブのリポジトリ上のHEADでパッケージングの仕方だけを変えてTalosでパフォーマンスを計測してみました。結果は以下の通りでした。

パッケージングの仕方 1回目の結果2回目の結果3回目の結果平均余計にかかった起動時間起動時間の増加率
(アドオン無し) 2029.53msec2091.58msec2058.53msec2059.88msec0-
無圧縮JAR+圧縮率最高XPI 2354.05msec2341.63msec2279.21msec2324.96msec265.08msec12.87%
無圧縮JAR+圧縮率最高XPI+em:unpack="true" 2327.0msec2314.84msec2337.63msec2326.49msec266.61msec12.94%
無圧縮XPI 2287.58msec2380.79msec2331.26msec2333.21msec273.33msec13.27%

数値が物凄く大きいのは、試験環境がシングルコアなCeleronの上で動いてるVirtualPCの上で動いてるWindows XP SP3だったからです。無圧縮XPIは上記の「Firefo 4向けの推奨されるやり方」ではないのですが、CPUが低速なら展開に時間がかからない方が有利なんじゃないかと思ってそうしてみました。

表を見ての通り、XPIの作り方で劇的に起動速度が変わるという事はありませんでした。少なくともツリー型タブ程度の規模では、XPIファイルの作り方の違いを変えてもFirefoxの起動速度は大して早くならない、ぶっちゃけ好きなようにやればいいという事が言えると思います。

ちなみに、繰り返しテストする前に試験実行した時にはそもそもFirefox自体の起動時間が600ミリ秒ほど短い1400ミリ秒ほどであるという結果が出ていました。その時の計測結果も以下に記しておきます。

パッケージングの仕方 テスト実行時の結果余計にかかった起動時間起動時間の増加率
(アドオン無し) 1411.26msec0-
無圧縮JAR+圧縮率最高XPI 1730.58msec319.32msec22.63%
無圧縮JAR+圧縮率最高XPI+em:unpack="true" 1718.05msec306.79msec21.74%
無圧縮XPI 1779.53msec368.27msec26.10%

これを見ると、ツリー型タブがある時に絶対的に何ミリ秒余計な時間がかかるのか?という点では、先の表と併せて見ても、だいたい常に300ミリ秒くらい起動に余計に時間がかかってるみたいだという事が分かります。Firefox自体の起動が300ミリ秒くらいで済む環境でテストすると、ツリー型タブのせいで起動時間が2倍かかるようになると考えられます。もしこれが事実なら、上記の晒し上げランキングの元データは結構高速なマシンで計測した物という事になるのかもしれません。


結論としては、XPIファイルの作り方を見直すなんてのは最後の最後でいいと思います。それよりもアーキテクチャの変更とかの方が多分ずっと高速化には効きます。実際、ツリー型タブのここまでの改善の中では、レイジーゲッターとかJSコードモジュール化とかよりも、読み込んだまま使ってなかった大量のスタイルシートを必要最小限だけ読み込むようにして@importを減らしたりセレクタを単純化したりした時の方が、大きな数値上の差が出てたと思います。

あと、このエントリ内での計測結果がおかしいと思う人は自分でPythonとTalosとNightlyを用意してパフォーマンス計測をやってみるといいです。こういう物が既にある以上、今後は、数値による裏付けが無い「遅い」「重い」といった類の話は無視しちゃっていいと思いますよ(動作時の速度についての言及は除く。上記の文書で解説されてる手順で計測できるのはあくまで起動にかかる時間だけで、メニューを展開したりタブを切り替えたりといった操作のパフォーマンスまでは測定してないので)。

Firefoxと自分の関係を見直す - May 07, 2011

Twitterとかでポロポロこぼしてるのをまとめておきたいと思ったのでまとめることにした。要点だけ先に書いておくと、以下の3点。

  • Firefoxが世の中に受け入れられてたのは、オープンソースだからでもWeb標準だからでもなく、単純に「無料で手に入り、必要十分な機能を持ってて、ヘビーユーザの使用にも耐えるポテンシャルを持った、良いアプリケーションソフトウェア」だったから。
  • その点において世の中の関心は既にGoogle Chromeに移っている。
  • 今でもFirefoxにこだわってる人は、おそらく時代を読めてない(でも敢えてそうしてる所が多分ある)。

何度も書いてる話をまた繰り返すんだけど。

僕がMozillaに肩入れしだしたのは、僕がW3C信者で、Geckoエンジンが当時一番マシなCSS2の実装を持ってた(ように僕には思えた)からだった。ポジショニングがちゃんと仕様通りにできて、疑似要素とかも使えて、スタイルシートWebデザインで語られていた「あるべきWeb」を実現してくれる物だ、という風に僕は思ってた。

そのうち僕はMozillaの拡張機能開発にのめり込んでいったんだけれども、のめり込む中で「Web標準は世間知らずの学生やら学者やらが語るだけのオモチャで非現実的な絵空事、なんかでは決してないという事を証明したい」「プラットフォームを選ばずWeb標準技術でアプリケーションソフトウェアを作れるという事そのものが、その証明になる」という思いをだんだんと強めていった気がする。その後でUIがどうとか色々加わったものも有るんだけど、発端にあって且つ今もある核の1つであることは間違いないと思う。

そうこうするうちにオープンソースというキーワードから就職が決まり、「オープンソース素晴らしい!」的な言説にそれまでよりも沢山触れるようになった。Mozillaも、「Firefoxはオープンソースだから多数の目によって監視されていて品質が高い」とかのアピールをよくしてた。Firefoxのシェアがちょっとずつ増えていって、「オープンソース素晴らしい!」「コミュニティ素晴らしい!」「バザール!」「カスタマイズ性が高いのって素晴らしい!」的な言説にもさらに多く触れるようになった気がするし、自分でもそういう事を言うようになった気がする。

  • Web標準技術に基づいているという事。
    • 仕様が特定のプラットフォームの習わしに囚われていない。無色透明である。
    • Webの礎を作った人達のお墨付きである。
  • オープンソースで、全てのコードを見られるという事。
    • トラブルの原因をとことん調べられる(根気さえあれば)。
    • 既存のコードを参考に派生物を作れる。誰にも気兼ねせず、恐怖に怯える事もなく、安心して成果物を公開できるし他人の成果物も取り込める。
    • 誰でも関われる。コードを書くだけでなく、検証したりバグを立てたりドキュメントを整備したりといった、自分にできる形でプロジェクトに参与できる。
  • カスタマイズ性が高い事。
    • 使っていて不満に思う所があれば、かなりの程度自由に挙動を変えられる。
    • 愛用の道具に少しずつ手を加えていく、秘密基地を少しずつ整備していく感覚で、居心地の良い空間を作っていける。

こういうものがMozillaの、Firefoxの価値だと僕は思うようになっていた。

Mozillaの拡張機能を開発する中で僕は、それなりの評価を世間から得る事ができた。Firefoxが世の中で受け入れられていく中で、僕の成果も評価されていった。W3C信者でCSSコミューンで啓蒙啓蒙と息巻いていた頃に抱いた「どうしてこんな素晴らしい物を皆分かってくれないんだ」という鬱屈、運動ができず特段勉強ができるわけでも人気者でもない自分がメインストリームの人達を横目に休み時間机に俯せていた時に感じていたやるせなさ、朝礼での表彰だとかトロフィーだとか優勝旗だとかに憧れながら無縁のままだった口惜しさ、そういった物から僕は解放されたような気がしていた。

僕は、僕を救ってくれたモノの事を、僕自身の存在意義をも仮託して支持するようになっていたのだと思う。


ただ、世間がFirefoxを評価していた最大の理由は、そういう事とは全く別の所にあったんだな。

世間が評価したのはあくまで、Ben GoodgerやDavid Hyattといった人達が「こういうブラウザがあったら使いやすくて便利なのに」と思ってスカンクワークス的にユーザ視点で作ったアプリケーションソフトウェアとしてのPhoenix、そしてその血を引くFirefoxだった。これは多分間違いない。

オープンソースだとかWeb標準だとかは、ある意味ではオマケの要素に過ぎなくて、ある意味では「今時そうであって当たり前だろ?」っていうレベルの話でしかない。カスタマイズ性だって、別にFirefoxのやり方でなきゃいけなかったなんて事は全然無くて、というかFirefoxのやり方(不安定且つドキュメント不在のAPIの山で、全てが個々人のアドオン開発者に丸投げ)なんて下策もいいとこで、結果的に「フツーの人」や「ヘビーユーザ」が欲しい機能が手に入るのであれば、それは安定したAPIと開発を支援する仕組みによって実現されるものであっても何ら問題ないどころか、そうであった方がユーザのためにも良い。

「必要なのはこの機能だ」「こんな機能は不要だ」といった判断を下して方針を定めて1つのビジョンの元に形作られたからこそ、Firefoxには「使いやすくて便利だ」という分かりやすいメッセージが備わって、小難しい事になんか興味の無い普通の人にも受け入れてもらえてたんだと思う。


つまり、こういう事だ。「オープンソースでWeb標準」なMozillaが元々そこにあった。その中から「便利なアプリ」のPhoenixそしてFirefoxが産まれてきて、その「便利なアプリ」に惹かれていろんな人が寄ってきた。でもそのうちに「便利なアプリ」を作った中心人物達はMozillaを去って、AppleやらGoogleやらに行ってしまった。そして彼らはそこでまた別の「便利なアプリ」を作り始めた。今またその「便利なアプリ」が新たにいろんな人の関心を集めている一方で、「便利なアプリ」を産んだ中心人物達がいなくなったMozillaにはFirefoxには、多くの人の関心を集める材料たり得ない「オープンソースでWeb標準」という部分だけが残された。

「今まで使ってたFirefoxの新しいバージョンが入ったから使ってみたけど、重くてヤダ。これだったらGoogle Chromeっていうの? こっちの方がいいや。」カジュアルにFirefoxを使い始めた人は、こうしてカジュアルにFirefoxから去って行っている。今Firefoxについて行っているのは、その残された部分に元から惹かれていた僕のような酔狂な人間と、Firefoxやそのアドオン(特にTab Mix Plusあたり)にロックインされて他に移れなくなってしまったドン詰まりの人間だけだったりするんじゃないのか。

さらに言うなら、前述した通りオープンソースもWeb標準もカスタマイズ性も今となっては「どのプレイヤーも備えていて当たり前の、最低限の要素」となっているのだとすれば、Firefoxに残された部分にすら何ら特別な独自性は無いという事にはならないか?


他のプレイヤーが持ち得ない全く独自のカラー、進むべき道を指し示す分かりやすい「方針」「指針」を持てないと、Firefoxの衰退のスピードは加速する一方なのではないか。

Ben GoodgerらがPhoenixをスリムに作れたのは、彼らに「こういうものを作りたい」という思いがあったからというだけでなく、スカンクワークスとして余計な横やりに晒される事無く初期の開発を進める事ができたからではないだろうか。まだ世の中のどこにも無いまだ見ぬ「より良い物」を求めて最先端をひた走っていたからこそ、そういう事ができていたのではないか? 10年来の「Mozillaユーザ」といったしがらみに囚われて、船頭多くして船山に登りまくりになっているようにも見えるFirefoxが、他の模倣・後追いではない独自の「最先端」を切り開いていく事は可能なのだろうか?

FirefoxボタンやApp Tabやステータスパネルといった新機能の導入は、OperaやChromeといった競合製品の猿真似に過ぎなかったりはしないか? Microsummariesやプロファイルマネージャの廃止は、「アンケート調査の結果」という一見するともっともらしい数字を楯にした思考停止だったりはしないか? 受け身に徹していたりはしないか? そこにビジョンはあるのか? 一本筋の通ったストーリーはちゃんとあるのか?

……というかそもそもの話として、「進むべき方向が分からない。プロジェクトが生き残るために、進むべき道を定めなければ。」なんて考え方をしてるなら、その時点でもう終わっちゃってるとも思う。進みたい道があるから進む、その道を進むためにプロジェクトを立てる、それが当たり前のあり方だ。組織の自己保全が目的となった組織の末路なんて、今更語るまでも無い。プロジェクトの自己保全が目的となったプロジェクトだなんて、一体何の冗談か。プロジェクトとしての役割を果たし終えたのであれば、プロジェクトは解散しないといけない。

Netscapeのための「無償労働力集めの場」ではなくなったMozillaプロジェクトの目的は、一体何なのか。その目的はまだ達成されきってなどいなくて、今も果たし続けなければならない物なのか。The Mozilla Manifestoが「何かを言っているようで実は何も言っていない」言い訳などではないなら、ここから演繹される物がFirefoxの進む道なのだろう。

その道に、僕はどのように関われるのか。あるいは、どのように関われないのか。もうすぐ三十路に突入する僕は、個人的な承認欲求が満たされるかどうかといった話に留まらず、仕事上での関わりも含めて、いいかげん真面目に考えないといけないという気がしている。

js-ctypesで期待した通りにガーベジコレクトされてくれないから自分でmalloc/freeする - Mar 27, 2011

最初に要点だけまとめておくと、

  • js-ctypesを使う時は、JavaScriptのコード側では基本的にメモリの管理のことは考えなくてもいい(ガーベジコレクト任せにしていい)事になってる。
  • しかし実際使ってみると、JavaScriptからjs-ctypes経由で自分でmalloc/freeしないとどうにもならないという場面があるみたい。
  • なのでJavaScriptからjs-ctypes経由でmalloc/freeする方法とその実例を紹介する。

という話です。

続きを表示する ...

64bit整数を使わないという、js-ctypesの最適化ノウハウ - Mar 27, 2011

先に結論だけ書くと、

  • js-ctypesでCのライブラリから帰ってきた64ビット整数を32ビット整数2つで代用できる場面では、32ビット整数にしておいた方が何倍も速くなることがある
  • ctypes.uint32_tとctypes.unsigned_longが同じ意味になる場合(Win32/Win64など)はctypes.uint32_tを使った方がいい

という話です。以下、実際にどういうケースでこれが役立つかの説明です。

続きを表示する ...

js-ctypes - Mar 20, 2011

js-ctypesはFirefox 3.6から利用できるMozillaの独自の機能で、平たく言うとC言語の実装の中で定義された関数をJavaScriptから呼べるようにするという物。Pythonにctypesという機能があって、それのJavaScript版がjs-ctypes。

Firefox 3.6(Gecko 1.9.2)ではできる事の制限が厳しかったので使えるケースがあんまり無かったようなんだけど、Firefox 4(Gecko 2.0)では構造体がサポートされたので一気に使える場面が増えた。らしい。

システムモニターをFirefox 4に対応させなきゃねと思ってたんだけど、Compartmentがどうとか色々変更があったのを全部調べてたら絶対自分の手に負えん!!と思ったので、いっそのことjs-ctypesで実装すりゃいいんじゃね? と思って、試行錯誤しながらやってみてる。試行錯誤の様子はリポジトリを見るとバレバレです。

js-ctypesのいい所:

  • コンパイルしなくていい。SDKやらビルド環境やらを整えるのに苦労しなくてもいい。
  • 単にバイナリを用意できてないだけで、その環境でバイナリをビルドしさえすれば大丈夫なのに……って場合には、多分そのまま動く。(動かない場合もある)
  • CとJavaScriptの境界で動作するコードで考えなきゃいけなかった諸々の事(JavaScriptのコンテキストがどうだとか、nsIVariantを経由したりJSObjにしたりとか、いろんな事)を考えなくてもいい。

困った所:

  • 結局はCなので、C言語が分かってないとどうしようもない。(僕はjs-ctypesでちょっとC言語への理解が深まりました……)
  • Cでの開発だったらヘッダファイルをインクルードすればそれでいいという場面でも、js-ctypes用に構造体の定義をJavaScriptで全部書き直さないといけない。
  • 取得した値が数値になってると思って「+」演算子で計算しようとするとハマる。数値を返すような関数でも返ってくる値はjs-ctypesによってラップされたオブジェクトなので、「+」演算子で繋げると文字列連結になってしまう(「-」などの、数値型に暗黙のうちに変換する演算子であれば問題は起こらない)。
    • 必ずparseInt()する、みたいな癖を付けとくとハマらなくていいと思う。
  • JavaScriptの書き方が悪くてJITされてないせいもあるのかもしれないけど、ループが遅くいからか、Cで書くよりずいぶんパフォーマンスが落ちる場面がある。メモリ消費量の計算だけでCPUを20%近く使っちゃうことになったりとか……

JavaScriptでFirefoxをクラッシュさせたかったらjs-ctypesでメモリ破壊とかやると手っ取り早いですよ! と、数え切れないほどFirefoxをクラッシュさせて思いました。

「巻き戻し/早送りボタン」でクラッシュする件 - Mar 16, 2011

14日くらいから、Rewind/Fastforward Buttonsを入れてるとFirefoxが頻繁にクラッシュするという現象が発生するようになっているようです。wedataのAutoPagerize用データベースに新しく追加された項目がトリガーになって、長すぎる正規表現か何かの制限に引っかかるようになってクラッシュしているものと考えられます。問題としては認識しているのですが、現在修正のための時間を取れない状態なので、アドオンを無効化するか、about:configで以下の設定を変更して回避して下さい。

  • rewindforward.related.use.siteInfo →falseに変更
  • rewindforward.siteinfo.importFrom → ""(空文字)に変更

Compatibility problem with Tab Mix Plus - Feb 07, 2011

I got a mail from Tab Mix Plus developers team. So I updated compatibility codes in Tree Style Tab for the latest dev-build of TMP. After that I got another mail again, and he said that the latest TST doesn't work with the last public release of TMP. This is the reply:

Hello, onemen.

First, I really think TMP helps very huge people from poor tabbed browsing features of Firefox itself. It is a great thing. So, I hope my addons including TST work with TMP correctly.

However, I'm afraid I can't support both versions of TMP (the latest dev-build and the last public version) anymore, because I believe that they are too different to support simple hacks. I already removed many codes based on the latest dev-build of TMP by this commit, so I can't believe TST works with the last public release only with minor changes only about symbols (function names). And, if I restore codes for old TMP, then both old and latest TMP will override them again and re-introduce many unexpected problems. That is too terrible.

To be honest, it was very painful to read dynamic-eval codes in TMP and TST itself because they are many tree-times eval-ed codes (defined by TMP => overridden by TST => overridden by TMP again). So I don't want to do such a painful work again for the last public release...

Yes, I apologize that I'm also using many eval() to hack TMP. So I believe that both addons TMP and TST should remove all eval-based hacks for each other and make themselves plaggable via their public APIs. TST already defines some public APIs. I agree that they are too less APIs to make compatible TMP with TST now. I'll add new APIs to do it if they are really required for high compatibility. I'll make efforts to keep stable those APIs in future versions. I don't know what APIs are required for TMP, so, I hope to listen your idea.

On the other hand, I hope that TMP provides some public (and stable) APIs for addon developers, like:

  • An API to add extra properties for TabmixSessionData
  • Custom DOM events for TMP specific features

If there is any public document already, could you tell me the URI?

regards,

I can't believe that I keep the current method (eval-based dirty hacks) to make them compatible.

Firefoxでtarget="_blank"なリンクから新しいタブやウィンドウが開かれないようにしたい人向けの話 - Feb 04, 2011

Back to Owner Tab公開したよという話を書いた時にそもそも勝手に新しいタブを開かないようにしたいという意見のトラックバックがあった。そこのコメント欄で「なんでブラウザがそういう機能を提供してくれてないのさ」って議論になってた。

それでふと思い出したんだけど、そういえば昔のMozillaにはtarget属性の指定を無視して現在のタブでリンクを開く機能がなかったっけ? 削除されたのかな? と思って調べてみたら、自分でもすっかり存在を忘れてたけどこの機能はFirefox 4でも健在で、隠し設定としてちゃんと生き残っていた。about:configを開いて「browser.link.open_newwindow」を「1」に変更すれば、target属性によって新しいウィンドウやタブを開くように指定されているリンクでも、普通のリンクと同じように元のページを置き換える形で遷移するようになる。(この機能が入る前に「新しいウィンドウを開くリンク」の挙動を置き換えようとして、旧タブブラウザ拡張では相当な無茶をやっておりました。懐かしい話です。)

実は、Firefoxの設定ダイアログで「新しいウィンドウではなく新しいタブで開く」っていうチェックボックスを切り替えると、この設定の値が「2(新規ウィンドウで開く)」と「3(新規タブで開く)」でトグルするようになってる。「1(現在のタブで開く)」は普通に設定ダイアログを使ってたら選択できないので、about:configか何か別の手段を使わないといけないというわけ。

変更履歴をどんどん遡っていってみたところ、ごく初期の設定ダイアログでは確かに3つの選択肢から1つを選ぶようなUIになってたんだけど、これがFirefox 2でprefwindowベースの設定ダイアログ置き換えられた時に、しれっと「新しいウィンドウで開く」と「新しいタブで開く」の2者択一になってた(現在のタブで開くという選択肢が消えていた)。その時の議論には特に情報はなかったんだけど、「新しいウィンドウで開く」と「新しいタブで開く」の2者択一のUIから今のチェックボックス型UIに変わった時のバグの方を見てみると、まさに前述のエントリで述べられているような議論が繰り広げられていた。

で、ざっと見た感じでは、 「リンクのtarget属性とそれ以外の場合のために2つも設定項目作る意味なくね? 1個でいいんじゃね?」→ 「そもそもなんでUIから1を選択できるようになってないんだ?」→ 「破壊的な挙動になるオプションだから敢えて選択できないようにしたんだよ。」 「そのオプションを選択してたらクラッシュしたこともあったしね。」 「実際、open_newwindow=1に設定するとかなりのWebサイトがぶっ壊れるよ。そりゃウィンドウなんて開くべきじゃないのはみんな分かってるけど、ユーザを混乱させないためにはIEと互換性のある挙動にしとかないと。」→ 「JavaScriptで開かれるウィンドウについてはopen_newwindow=1にしたら色々危ないのは分かるけど、リンクのtarget属性だったら無視しても問題ないでしょ?」→ 「どうしてもアドオン無しでやりたいならこういう方法もあるよ。

という感じで、

  • リンクの場合とJavaScriptの場合で新規ウィンドウ要求をどう扱うかの2つの設定項目は、初心者ユーザの事を優先して1つにまとめることにする。これは譲らない。
  • そうすると、open_newwindow=1にするとWebサイトの動作を壊すなどのトラブルが起こりうる。なので、この値は設定UIには表示しない非推奨の隠し設定のままとする。
  • リンクの場合とJavaScriptの場合で新規ウィンドウ要求をどう扱うかというのを細かく制御したい上級者ユーザは、アドオンを使うなり何なりして対処するように。

という結論に至った……みたいだ。

ということで、すっかり忘れてた&調べ直してて改めて思い出したわけだけれども、browser.link.open_newwindowを1にすると、target属性があるリンクだけでなく、window.open()の読み込み先も現在のタブになってしまうために、親ウィンドウを参照するようなスクリプトが含まれてるページはまともに動かなくなる恐れがあるので、これはお薦めできません。トラブル覚悟でこの設定を使ってもいいけど、target属性があるリンクだけ同じタブで開くようにしたいという場合には、そういう機能を提供するアドオンを使うなり何なりしないといけないよーです。

まあ、いろんな場合に対して細かく挙動を振り分けたいという人は既に(当時の)Mozillaが考える所のメインターゲットではなかったのでした、そういう人は自分がマイノリティであることを自覚してDIYの精神で暮らすしかないですね、という話なのでした。

Bootstrapped Extensionsで設定ダイアログを提供するためのライブラリを作ったよ - Jan 28, 2011

Firefox Developers Conference 2010の時に再起動不要なアドオンの作り方を調べた時、調査の成果を元にJetpack SDKもといAdd-on SDKを使わずにFirefox 3.6とMinefieldの両方に対応したBootstrapped Extensionを作るためのテンプレを作った。それ以後、新しく作るアドオンでBootstrapped Extensionにできそうな物はなるべくそのように開発していこうと思ってて、Back to Owner Tabは実際そうして作ったアドオンの第1号ということになる。

SDKの使い方や仕組みを調べた時にもちょっと思ったんだけど、Back to Owner Tabを作って、「設定ダイアログを持てないのがこんなに辛いとは……」ということを改めて感じた。しかしBootstrapped Extensionで設定ダイアログを提供するのは簡単には実現できなさそうだったので、ずっと諦めてた。

今回、うまく解決できそうな方法をふと思いついて、実際試してみたら案外うまくいったので、テンプレの一部としてライブラリ化してみたBack to Owner Tabの設定ダイアログもこれで提供してる

Bootstrapped Extensionで設定ダイアログを提供するのは難しい

Firefox 2以降ではXULにprefwindowというウィジェットが存在していて、これはFirefox自体の設定ダイアログにも使われている物で、非常に出来がよい。今ある拡張機能の多くも、これを使って設定ダイアログを提供している。

prefwindowができるまでは「設定値を保持して、変更を監視して、変更があったらUIに反映して、ダイアログがキャンセルされたら変更を破棄して……」てな事をいちいち考えてゼロから設定UIを設計しなきゃいけなかったから、非常に辛かった。そういう思いがあるので、Bootstrapped Extensionでも是非これを使いたかったんだけど、Bootstrapped Extensionの「Chrome URLを登録できない」という制限のせいで無理だった。

  • prefwindowも含めて、XULを使うにはChrome URLでFirefoxにファイルを読み込ませないといけない。
    • Minefieldではfile:やresource:で始まるURLでXULを読み込ませても表示されなくなった(リモートXULの廃止)。
  • 仮にXULファイルを読み込ませることができても、表示する文字列をローカライズできない。
    • XULでの多言語対応で欠かせないDTDファイルは、セキュリティの制限によりChrome URLに置かれた物(chrome://foobar/locale/foobar.dtdなど)しか読み込めない。
    • propertiesファイルから文字列を読み込んでJavaScriptで動的に埋め込むということは無理ではなさそうだけど、死ぬ程めんどくさい。XULの「タグで書くだけでいい」「静的なファイルに内容をまとめておける」という利点が完全に死ぬ。

どうしてもやりたければ、XUL以外の方法で頑張って設定UIを作るか、XULで物凄く苦労して作るしか無いってことになって、それはどっちも嫌だった。せっかくprefwindowという素晴らしい物が目の前にあるのに、それを使わないでオレオレUIをゼロから作るなんて、馬鹿馬鹿しくてやってられない。

せめて最新のSDKだったら設定ダイアログを作る機能が入ってたりしないだろうか? あるんならそれを丸パクリできないかな? と思ってたんだけど、SDKに設定ダイアログのためのAPIが入るのはまだ先のことらしいと聞いて、それも諦めざるを得なかった。

それで仕方なく「about:configでカスタマイズしてね」ということにしてたんだけど、まあ普通に考えてこれはエンドユーザ向けとしては酷い話なわけですよ。自分で使う時も、マウス操作だけで設定を変えられないのは困る。

解決の糸口

そんな風に悶々としていて、ふと、「そういえばuserChrome.jsとかではdata: URIでXULドキュメントを動的に作ってたよな」ということに思い至った。試しにdata: URIにprefwindowで作った設定ダイアログの内容を突っ込んでnsIWindowWatcherのopenWindow()でウィンドウとして開いてみたら、少なくともMinefield 4.0b11preでは正常に動いてくれた。data: URIでドキュメントを読み込んだりウィンドウを開いたりする時はオープン元のスクリプトの権限が引き継がれるっぽいので、file: URLでXULを読み込ませた時のような問題も起こらないようだ。

また、E4Xを使えばJavaScriptの中に直接XMLを書けて、しかも属性値にJavaScriptの変数を埋め込める。これなら、propertiesファイルベースでのローカライズでもラベル等を埋め込むのが苦にならない。で、そうして作ったE4XのXMLオブジェクトをtoXMLString()すれば、data: URIで読み込ませるダイアログのためのソースコードが得られる。

ということで、「XULのソースコードを文字列として受け取ってdata: URIにしてウィンドウを開く機能」と「アドオンマネージャでアドオンの設定を開くボタンが押下された時に前述の機能を呼び出す機能」を作れば、Bootstrapped Extensionでもprefwindowベースの設定ダイアログを提供できる事が分かった。

propertiesファイルでローカライズする

propertiesファイルの内容はローカライズ済みの文字列をXULのstringbundle要素を介して取得する方法が一般的だと思うけど、より低レベルな実装のnsIStringBundleServiceを直接呼べば、JavaScriptコードモジュールやBootstrapped Extensionの中からでもpropertiesファイルの中身を簡単に取り出せる。(stringbundle要素はXBLでそういう処理を行うようにしてあるに過ぎない。)

上記のテンプレには、nsIStringBundleServiceをラップしてstringbundle要素と同じインターフェースで使えるようにするライブラリを既に入れてある。

var bundle = require('path/to/lib/locale.js')
               .get('file://.../messages.properties');
var title = bundle.getString('config.title');

という風にすると、ブラウザのUIの言語がjaでmessages.properties.jaというファイルがあればそれが使われて、存在しない場合はmessages.properties.en-US→messages.properties(サフィックス無し)とフォールバックしていく。

Back to Owner Tabに入ってるバージョンのライブラリには含まれてないけど、テンプレのHEADでは resolve('path/to/file') とすると現在のファイルを起点にして相対パスを解決できるようにしてあので、こういう風にも書ける。

var bundle = require('path/to/lib/locale.js')
               .get(resolve('locale/messages.properties'));

なお、Bootstrapped Extensionの場合は読み込んだpropertiesファイルの内容がキャッシュされたままになってしまうとまずいので、shutdown()の時にはnsIStringBundleServiceのflushBundles()というメソッドを呼んで、メモリ上のキャッシュを消すようにする必要がある。このライブラリでもそうしてて、自分でnsIStringBundleServiceを使う時はここに気をつけないといけない。

E4Xで作ったXULコード片から動的にChromeウィンドウを開く

Bootstrapped ExtensionやJavaScriptコードモジュールの名前空間からで既存のDOMWindowを取れない状態でウィンドウを開きたい場合には、nsIWindowWatcherのopenWindow()を使う必要がある。このメソッドに渡すURIとしてdata: URIを指定すれば、その内容のウィンドウがChrome権限で開かれる。

var uri = 'data:application/vnd.mozilla.xul+xml,'
         + source;
Cc['@mozilla.org/embedcomp/window-watcher;1']
  .getService(Ci.nsIWindowWatcher)
  .openWindow(null, uri, '_blank',
              'chrome,titlebar,toolbar,centerscreen',
              null);

ソース文字列の生成は、E4Xを使うと楽にできる。

var xml = <>
      <prefwindow id="backtoowner-config"
                  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
                  title={bundle.getString('title')}>
        <prefpane id="prefpane-general"
                  label={bundle.getString('general')}/>
      </prefwindow>
    </>;
var source = '<?xml version="1.0"?>'
             +'<?xml-stylesheet href="chrome://global/skin/"?>'
             +xml.toXMLString();
source = encodeURIComponent(source);

ラベル文字列等に日本語が入る場合を考慮して、ソース文字列はdata: URIに繋げる前にエスケープしておこう(これをしないと文字化けする)。

基本的にはこれでいいんだけど、実際このまま使うと何となく気持ち悪い。というのも、prefwindowは画面上でのウィンドウの位置や大きさ、最後に選択されていたパネルの名前等をプロファイル内のlocalstore.rdfに自動的に保存するんだけど、この時保存されるデータはウィンドウのURIをキーとして紐付けられているから、data: URIのウィンドウだとlocalstore.rdfが肥大化してしまう。しかも、設定項目を追加したりしてウィンドウの中身が変わるとdata: URIも変わるから、前回情報を保存した時のエントリとは別のエントリが作られてしまって、古いエントリは自動的には消えないから、localstore.rdfが際限なく膨らんでいってしまう事になる。長く使うことを考えたら、これはよくない。

で、何かいい方法はないか考えてみた。

  1. data: URIは必要最小限の長さの固定の物にして、document.write()でウィンドウの中身を動的に書き換える。
  2. data: URIは必要最小限の長さの固定の物にして、DOM操作でウィンドウの中身を動的に書き換える。

nsIWindowWatcherのopenWindow()は開かれたウィンドウのDOMWindowオブジェクトを返す。なので、こんな風にできないかと最初は考えた。

var window = ww.openWindow(...);
window.document
  .open('application/vnd.mozilla.xul+xml');
window.document.write(source);
window.document.close();

が、駄目だった。この方法でドキュメントを書き出すと強制的にHTMLのパーサーが使われてしまうらしく、XULのソースを書き出しても全く動作しない状態になってしまった。

次に、DOM操作でやることを考えた。

XUL Documentの中でRangeを作ってcreateContextualFragment()すると、XULのソース文字列からDOMDocumentFragmentを作ることができる。こうしてルート要素も含めたドキュメント全体を作り直してゴソッと入れ替えたらどうなるだろうか。

var window = ww.openWindow(...);
var range = window.document.createRange();
range.selectNode(window.document.documentElement);
var fragment = range.createContextualFragment(xml.toXMLString());
window.document.replaceChild(fragment, window.document.documentElement);

実際やってみたら、これは期待通りには動いてくれなかった。多分、開かれたウィンドウの内容がまだ完全には読み込まれ切っていない状態でDOM操作を外から無理矢理やろうとしたからなんだろうと思う。

開かれたウィンドウのDOMContentLoadedを待ってからDOMを操作すればprefwindowについてはちゃんと動くみたいなんだけど、dialogとかのもっと汎用的な物も受け付ける事や、ダイアログの中にDOMContentLoadedを待って処理を開始するようなコードを含めたい時に、それでは問題が起こる気がする。

そういうわけで今のバージョンでは、ウィンドウを開く時にnsIVariantの形でソース文字列を渡して、開かれた側のウィンドウの中に1つだけ置いたscript要素でarguments[0]を受け取ってすぐにドキュメントを書き換えるようにしてみている。要点だけ抜き出すとこんな感じ。

var variant = Cc['@mozilla.org/variant;1'].createInstance(Ci.nsIWritableVariant);
variant.setFromVariant(source);

ww.openWindow(
  null,
  'data:application/vnd.mozilla.xul+xml,'
  +encodeURIComponent(
   '<?xml version="1.0"?>\n'
   +'<!-- ' + aURI + ' -->\n'
   +'<?xml-stylesheet href="chrome://global/skin/"?>\n'
   +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
      <script type="application/javascript"><![CDATA[
        var d = document;
        var e = d.documentElement;
        var r = d.createRange();
        r.selectNode(e);
        d.replaceChild(r.createContextualFragment(arguments[0]), e);
        r.detach();
      ]]></script>
    </window>.toXMLString()
  ),
  '_blank',
  'chrome,titlebar,toolbar,centerscreen',
  variant
);

URIをコメントで埋め込んでるのは、複数のアドオンでこのライブラリを使ってる時に衝突しないようにしたかったから。

実はここに辿り着くまでに結構紆余曲折があった。最初はルート要素を差し替えられるという事に気づいていなかったので、「ルート要素の属性値の情報」と「子孫要素のソース」をそれぞれ取り出してやってみていた。E4Xは実はよく分かってないからちょっと苦労した。

var content = xml.*.toXMLString(); // 子孫要素だけ抽出
var container = xml.copy(); // ルート要素だけ取り出すためにまず一旦全体を複製
var attributes = container[0].attributes(); // ルート要素の属性のリストを取得
var attributesHash = {};
for each (var attribute in attributes) { // 属性名と値のペアのハッシュにする
  let name = attribute.name();
  attributesHash[name] = attribute.toString(); // 属性値はtoString()で取れる
  delete container[0]['@'+name]; // removeAttribute()に相当する操作はこう書く
}
// 子孫要素を削除して、コンテンツ差し替え用のスクリプトに入れ換える
delete container.*;
container.script = <script type="application/javascript">{this._loader}</script>;
container = container.toXMLString();

最初はこれでいいかと思ってたんだけど、コンテンツ差し替え用のスクリプトが走ってる時点で既にprefwindowのXBLのコンストラクタが実行されてしまっていて、複数のprefpaneがあるときの切り替え機能が働いてなかった。

ルート要素をもう一度作りなおしてドキュメントに挿入すればXBLのコンストラクタが実行されるはず、ということでdocument.removeChild(document.documentElement);の後でdocument.appendChild(newRoot);みたいな風な事も試してみたけど、一瞬documentElementが無くなってしまうのがマズいようで、他のどっかの処理がルート要素を参照するタイミングとぶつかってエラーになってしまった。ここはdocument.replaceChild(newRoot, document.documentElement);で一気に差し替えてしまうのが正解だった。昔のGeckoはreplaceChild()でクラッシュする事があったような気がするので無意識に使用をずっと避けてきてたんだけど、Firefox 3.6以降ともなるとさすがに問題なく動いた。

あと、生成した新しいルート要素を挿入しようとするとエラーになる場合もあって、その時は挿入しようとしているノードをdocument.importNode()に1回通してやれば動いた。まあ最終的にはそれ無しで乗り切れたんだけど。

アドオンマネージャからアドオンの設定ダイアログを開こうとした時に、動的に生成したダイアログを開く処理を呼ぶ

これも難儀した。

Firefoxのアドオンマネージャは、install.rdfのoptionsURLに書かれてるURIをwindow.openDialog()で(Mac OS X以外では)モーダルダイアログとして開くようになっている。しかし前述の通りXULの設定ダイアログを読み込ませたかったらChrome URLにしないといけないので、これはそのままは使えない事になる。

とはいえ、optionsURLにはChrome URLしか使えないという話ではない。

最初は、ここに直接javascript:ダイアログを開くためのコードという感じのスクリプトを書く事を考えてみた。でもメタデータの置き場所であるinstall.rdfにそういう実装を含めるのはどうかと思うし、そもそもここにjavascript:スキームを使って書いたスクリプトは普通にコンテンツ領域の権限でしか実行されないようなので、nsIObserverServiceを使ってイベントを発行するとかそういう高度な事は全然できなかった(実際試して無理だった)。

次に、nsIWindowWatcherでの監視とかnsIObserverServiceを介して送られるchrome-document-global-createdとかcontent-document-global-createdとかの通知でもって「あらかじめ登録されていたURI」が読み込まれた事を検知して、その読み込み(ウィンドウのオープン処理)をキャンセルして別の設定ダイアログを開くという事を考えた。

これは結構イイ線まで行ったんだけど、「Firefoxのアドオンマネージャが開いたウィンドウ」が一瞬だけ見えてしまうという点をどうしても回避できなかった。openDialog()の実装を辿ってnsGlobalWindow::OpenDialog()nsGlobalWindow::OpenInternal()nsWindowWatcher::OpenWindowJS()nsWindowWatcher::OpenWindowJSInternal()と掘り返して、何か抜け道はないか探しまくったんだけど、内部で新しいDOMWindowが生成されてウィンドウが実際に画面に出現する事がほとんど確定した後で初めてnsIDocShellのloadURI()にURIが渡されてる(この間URI文字列はC++の普通の変数で引き回されてるだけでJavaScriptの層からは触りようがない)みたいで、ウィンドウがユーザの目に触れる前にどうこうってのは原理的に無理っぽい。

いや、nsIContentPolicyインターフェースを備えたXPCOMコンポーネントを作ってカテゴリマネージャに登録すれば、もしかしたらそこに割り込む事はなんとかできるかもしれない。けど、設定ダイアログ1つ開くためだけにそこまでする元気はなかったし、求める機能に対してコードが膨大になりすぎるから普通にこれはないわーって感じだしで、そういう方向性は最初から切り捨ててる。

結局、すべてのドキュメントの読み込みをchrome-document-global-createdとcontent-document-global-createdの通知で捕捉して、アドオンマネージャのウィンドウが開かれた時にはボタンのクリック操作を監視するイベントリスナを登録し、「設定」ボタンをユーザがクリックした瞬間を捕捉して、登録済みのURIだったらイベントをキャンセルして別のダイアログを開く、という所に落ち着いた。これだとアドオンマネージャの実装(現在どのアドオンが選択されているのか、そのアドオンの設定ダイアログのURIは何なのか、を取得する方法)にべったりになってしまわざるを得ないんだけど、まあその辺のレイヤだったら変更があっても追随するのはそう大変じゃなかろうと思って、妥協する事にした。

まとめ

冒頭に書いたけど、ここまでのまとめがBootstrapped Extensionsテンプレートライブラリとして入ってる。他のファイルの内容に可能な限り依存しないように作ったつもりなので、改造再利用も不可能ではないと思う。実際にどう使うのかというのはBack to Owner Tabでの利用例が参考になるかもしれない(って単にE4XでダイアログをJavaScriptのコードの中に埋め込んでるだけだけど)。

ほんとはFirefox 4に向けて記事を書いていかなきゃいけないんだけど、こういう「あれも駄目でした、これも駄目でした、結局こういう所に落ち着かざるを得ませんでした」という紆余曲折は本に入れてもウケないんじゃないかなーって思ったので、ここに書く事にした。また次に同じような事をしようと思った時に同じ轍を踏まなくてもいいように。技術情報のドキュメントは、未来の自分のために残しておく物なのです。

いつの間にかインラインフレームを透過できるようになっていた - Jan 25, 2011

Bug 591652 – Make the tab view (Panorama) background transparent to reveal glass (if enabled) on Windowsというバグがfixされて、Panoramaの背景が透過されるようになった。Windows Vista以降でAero Glassを有効にしてれば、見事なスケスケになる。

で、ほうほうと思ってチェックインされたパッチを見てみたんだけど、「Aero Glassじゃない時だけ背景を指定する」というコードしかない。ひょっとしてフレームを透過するバックエンドってもうずっと前から入ってたの? そういえばPanorama用のiframeには transparent="true" という指定がずいぶん前からあったような気がするけど、特に透過されてる様子もなかったから、まだ実装されてないのかと思ってた。

それでMinefield 4.0b10preを起動してDOM InspectorでFirefoxのDOMツリーをいじって試してみたら、browser要素とかiframe要素とかに transparent="true" という属性の指定を加えて、そのフレーム要素・祖先要素すべて・フレームの中に含まれるドキュメントのbodyを background: transparent !important; にするだけで、普通のWebページでもデスクトップの壁紙が透けて見えるようになった。これは面白い。

最小構成だとこう。

parent.xul:

<?xml version="1.0"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        style="-moz-appearance: -moz-win-glass; background: transparent;"
        width="200" height="200">
  <iframe transparent="true"
          flex="1"
          style="background: transparent;"
          src="child.xul"/>
</window>

child.xul:

<?xml version="1.0"?>
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
      style="-moz-appearance: none; background: transparent;">
  <label value="Hellom, transparent world!"/>
</page>

この2つのファイルをテキスト形式でデスクトップに保存して、エラーコンソールから window.openDialog('file:///c:/Users/username/Desktop/parent.xul'); とやれば試せる。

Aero Glassじゃない時は普通の背景を表示させたいなら、CSSの方にこう書いておけばいい。

window:-moz-system-metric(windows-compositor) {
  background: -moz-field;
}

夢が広がりまくりですね!

Page 9/243: « 5 6 7 8 9 10 11 12 13 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき