たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
Firefox上でいくつかのサブウィンドウを開いていて、それらのウィンドウすべてがワンセットで他のウィンドウより前に出てきていて欲しい場面、というのがある。
例えば、GIMPはツールボックス等が複数のウィンドウにばらけている。これがもし、画像を編集するウィンドウにフォーカスしたらツールボックスのウィンドウがその背後に隠れてしまうという風になっていると、まるで作業にならないだろう(古いバージョンのGIMPをWindowsで使った時にはそんな風になってて頭を抱えた記憶がある)。
また、そういうワンセットで表示されていて欲しいウィンドウ達が、同時に起動している他のアプリケーションのウィンドウの前と後ろにそれぞればらけてしまうというのも、使う時に地味にうざい。
Firefoxの上で、拡張機能が開くウィンドウでGIMPのウィンドウ群のような振る舞いをさせるにはどうすればよいのか。このエントリでは2つの方法を紹介する。
まず1つ目。nsIXULWindowインターフェースのzLevelプロパティを使うと、Firefoxのプロセスが開くウィンドウの重ね合わせの順序をある程度制御する事ができる。一番単純なやり方は、ウィンドウを一時的に最前面表示に切り替えて、その後すぐに元に戻す、という方法だろう。
var XULWindow = window .top .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem) .treeOwner .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIXULWindow); var originalZIndex = XULWindow.zLevel; XULWindow.zLevel = Ci.nsIXULWindow.highestZ; window.setTimeout(function() { XULWindow.zLevel = originalZIndex; }, 0);
ただ、仕様上の制限のため、この方法はWindowsでしか使えない。少なくともUbuntu 10.04のGnomeでは機能しなかった。
代わりに考えられるもう1つのやり方が、フォーカスを使うやり方だ。単純に考えても、window.focus()
でウィンドウにフォーカスを与えると、強制的にそのウィンドウを最前面に持ってくる事ができる。
しかしこの方法にも問題がある。普通にwindow.focus()
すると、例えばそのウィンドウの中の特定のテキストボックスにフォーカスしていたとしても、そのフォーカスが失われてしまう事になる。
この問題を回避するには、Gecko 1.9.2/Firefox 3.6から導入されたフォーカスマネージャを使わないといけない。具体的には以下のようにする。
var FocusManager = Cc['@mozilla.org/focus-manager;1'] .getService(Ci.nsIFocusManager); // 現在フォーカスされている要素を取得する。 // 第1引数:検索する最上位のDOMWindow // 第2引数:再帰的な検索を行うかどうかのフラグ(trueを渡す) // 第3引数:その要素が含まれているフレームのDOMWindowを // 受け取るためのスロットとなるオブジェクト var focusedWindow = {}; var focusedElement = FocusManager.getFocusedElementForWindow(window, true, focusedWindow); // 現在のフォーカスが何によって与えられたかの情報を取得する。 var reason = FocusManager.getLastFocusMethod(focusedWindow.value); // フレームにフォーカスする。 focusedWindow.value.focus(); // フォーカスされていた要素がある時は、その要素にもフォーカスする。 if (focusedElement) { // フォーカスを与える時に、スクロール状態等に変更を加えないように指定する let flags = Ci.nsIFocusManager.FLAG_RAISE | Ci.nsIFocusManager.FLAG_NOSCROLL | Ci.nsIFocusManager.FLAG_NOSWITCHFRAME | reason; FocusManager.setFocus(focusedElement, flags); }
こうすれば、Linuxでも要素のフォーカス状態を失わせずにウィンドウを最前面に持って来れる。ウィンドウのフォーカスを動的に切り替えるため、その都度各ウィンドウが画面上でぺかぺか点滅してしまう(一瞬だけフォーカスされて、その直後にフォーカスが失われるため)、というデメリットはあるが。
最初に要点だけまとめておくと、
という話です。
先に結論だけ書くと、
という話です。以下、実際にどういうケースでこれが役立つかの説明です。
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のいい所:
困った所:
parseInt()
する、みたいな癖を付けとくとハマらなくていいと思う。JavaScriptでFirefoxをクラッシュさせたかったらjs-ctypesでメモリ破壊とかやると手っ取り早いですよ! と、数え切れないほどFirefoxをクラッシュさせて思いました。
JSDeferredは非同期処理の制御に特化しててサイズも小さくて素敵な軽量ライブラリだ!と僕は思ってるんだけど、世の中を見回してみると「軽量ライブラリ」って言われてる物は3KB未満とかそういうのが結構あるようだし、jQueryも1.5.1のminified版は28KBって書いてあるし、そうなるとjsdeferred.jsのコメント付き版が0.3.4で19KBというのは「軽量」と呼ぶにはひょっとしてちょっとでかいのかな……という気がしてきた。
なので、JS MinifierとかPackerとかそういう風なやつでどれくらい小さくなるのか実験してみた。
/packer/はそのままだと構文エラーで動かなくなってしまった。}Deferred.
って部分が7箇所あってこれがエラーになってるので、全部 };Deferred.
に置換(Base62圧縮後だと }4.
を };4.
に置換)したら一応エラーは出なくなった。/packer/の構文解析が貧弱なせいっぽいので、JSDeferredの側で問題になる所にあらかじめセミコロンを入れておけば、この問題は無くなるっぽい気がする。……と書いたからか、ちょよんごさんが対応してくれた。ありがとうございます!
あと、closure-compiler を通すと 2668bytes でした (jsdeferred のレポジトリで rake すると URL が出るようになってます)
というアドバイスも頂いたので早速Closure Compilerにかけてみた所確かに小さくなったのですが、シンボル類まで全部失われちゃってこれ単体だと他のスクリプトと組み合わせられないのが残念ですね。(Closure Compilerは他のスクリプトも全部合わせて一緒にコンパイルして使うのが前提ってことなんだろう)
96桁目のセルに所定の値が入ったCSVを作る、みたいな事をやらなきゃいけなくなったんだけど、GnumericにしろExcelにしろカラム名が数字じゃなくてA, B, C, ... Z, AA, AB, ... ZZ, AAA, ...というヘンテコ表記になってるからどこのセルに値を入れればいいかわかんないよウワァァァァン!!!! となったのでJavaScriptで解決した。
var input = prompt('input number or column name');
if (!input) return;
var symbols = 'abcdefghijklmnopqrstuvwxyz';
var result;
if (/^[0-9]+$/.test(input)) {
input = parseInt(input);
result = [];
while (input > 0) {
result.unshift(symbols.charAt((input - 1) % symbols.length));
input = Math.floor((input - 1) / symbols.length);
}
result = result.join('').toUpperCase();
}
else {
result = 0;
input = input.toLowerCase().split('').reverse();
for (var i = 0, maxi = input.length; i < maxi; i++) {
result += (symbols.indexOf(input[i]) + 1) * Math.pow(symbols.length, i);
}
}
alert(result);
カラム名→カラム番号の計算方法は分かったけど、頭が悪い僕にはカラム番号→カラム名の計算式が分からなかったので、検索して出てきたエクセルの1000列目は ALL - つまみ食うのアルゴリズムを丸パクリした。阿呆ですんません……
なんでそこで詰まったかっていうと、単純な26進数の変換ではうまくいかなかったからなんですよね。A==0、B==1という感じで26進数として扱うと、26==Zで27==BAになるんだけど、実際は繰り上がった後はBAじゃなくてAAにならなきゃいけない。そこの所を考慮した計算を多分簡単なループで実現できるとは思ったんだけど、頭が働かなくて無理だった。
Tree Style TabとMultiple Tab Handlerを更新した。
今回のアップデートでも例によってMinefield対応のための修正をちょっとずつ入れてるんだけど、その中で1つ、なかなか気付いてなくてハマってた所があった。それはカスタムイベントを使ってた部分。
DOM2 Eventsではこんな風にして任意のDOMイベントを発行できる。
var event = document.createEvent('Events');
event.initEvent('MyCustomEvent', true, false);
event.status = 'current status';
event.tab = tab;
gBrowser.dispatchEvent(event);
受け取る側はこれをaddEventListener()
で登録したリスナで拾うようにすれば、各々のモジュールの結合度合いを弱められる。なので僕は自分のアドオンでも積極的にこれを使ってる。
が、これがMinefieldでは使えなくなってた。
多分Compartment(JavaScriptのメモリ空間をスクリプトのオリジンだったかウィンドウだったかごとに分ける機能)が入ったからだと思うんだけど、Chrome URLのスクリプトで上記の例のように追加した任意のプロパティを、JavaScriptコードモジュール側のイベントリスナで参照できなくなってた。上記の例だと、捕捉したイベントのevent.tab
がundefined
になってしまってて、こういうやり方で情報を引き渡してた部分がエラーになってしまってた。wrappedJSObject
もundefined
なので、生のオブジェクトを辿る事もできなかった。
MDCにある任意のカスタムイベントを実装する方法の詳しい説明によると、XPIDLでインターフェースを定義してC++で実装を書いてという事をやれば、今までと完全に同じAPIで任意のイベントを発行できるようなんだけど、それはちょっと重たすぎてやる気になれない。
なので次善の策として、汎用のデータを受け渡すためのイベント型があればそれを使おうと思って検索したら、Firefox 3以降ではDataContainerEventとかMessageEventとかの型のイベントが利用可能になってたという事を知った(今更)。
渡すデータがJSON文字列化できる物なら、WebSocketで定義されてるMessageEventがいいっぽい。
var event = document.createEvent('MessageEvent');
event.initMessageEvent('MyCustomEvent', true, false,
JSON.stringify({ status : 'current status',
tab : tab.getAttribute('id') }),
'', '', null);
gBrowser.dispatchEvent(event);
受け取った側はJSON.parse(event.data)
でデータを復元できる。
DOM要素とかも渡したいなら、nsIVariant型でデータを受け渡せるDataContainerEventを使うしか無さげ。
var event = document.createEvent('DataContainerEvent');
event.initEvent('MyCustomEvent', true, false);
event.setData('status', 'current status');
event.setData('tab', tab);
gBrowser.dispatchEvent(event);
受け取った側はevent.getData('tab')
のようにしてデータを取得できる。
ということで、プロパティアクセスにしてた所は全部DataContainerEventのやり方を使うように直した。ただ、後方互換性のためにプロパティアクセスでも情報はセットしてあって、同じCompartmentのスクリプトからなら多分今まで通りのやり方でも情報を受け取れると思う。
あと、DataContainerEventの存在を知る前に、MDCのドキュメントに書いてあった「イベント名がnsDOMで始まってない物は任意の情報は受け渡せないよ」という部分を読んでイベント名を「nsDOMTreeStyleTab...」という感じに変えていて、実際これでちゃんと動くようになった部分もあったんだけど、結局DataContainerEventにするようにしたからこれは結果的には余計だったかもしれない……
Developers Conferenceでの発表向けの内容に盛り込めそうにない話を書いていく。
話のテーマを決めるにあたって、とにかく最初は1つJetpack SDKでアドオンを書いてみようと思ったんですよ。
でも、「こう書くと動きますよ」って言われてもなんだか信じられなかったというか、どういう経路でどういう風にしてそのコードが読み込まれるのか分からなくて、スクリプトの書き方次第じゃ他のコードに悪影響を及ぼしたりすることもあるんじゃないのか?(prototoype.jsのObject汚染みたいな感じで)とか、名前空間の衝突は大丈夫なのか? とか、そういうどーでもいい所が気になって気になって、仕組みが分からないまま使うのは怖いと思った。何をやってよくて、何をやったら駄目なのか、というのが分からないのが怖かった。
その境目を見極めたくて、Jetpack自体の成り立ちを探ってみたのですよ。
特に謎だったのが、他のモジュールを読み込むrequire()
。これが一体どこで処理されているのか。main.js(Jetpackでアドオンを作る時の主たる実装になるファイルの固定の名前)もどこかからrequire()
されているのなら、require()
の実装を見たら謎が解けるんじゃないかなーと思った。
exports
やrequire()
はここで定義されている。
EXPORTED_SYMBOLS
を使うやり方)にも対応してるみたい。Components.utils.evalInSandbox()
が呼ばれていることが分かった。ライブラリのモジュールもmain.jsも全部これで実行される。
require("chrome")
の実態が、これ。Jetpackとは、この基本的な仕組みに基づいて、個々のモジュールの名前空間を分け、それらを安全に連携できるようにしたもの。と言える。MozLabでmodule_manager.jsというものが過去に使われていて、それを想起させられた。
module_manager.jsはJavaScriptコードモジュールが無かった頃にJavaScriptそのものの言語仕様を超えた所で高度なモジュール化を行おうとしていたらしく、渡されたモジュールの名前からパスを生成してファイルを読み込んでサンドボックス内で実行してそのグローバルオブジェクトを保持し続けて……ということを普通のJavaScriptの機能とmozIJSSubScriptLoaderでやってた。Jetpackの仕組みはそれのずっとスマートなバージョンなのかなと思った。
今までの開発手法しか知らなくてJetpackがさっぱり分からないという僕みたいな時代後れの人でも、securable-module.jsとcuddlefish.jsを起点にすれば、他のモジュールも無理なく読んでいけるんじゃないだろうか。
XBLはアドオン同士の衝突の原因になりやすい。だからXBLはあまり使わないように僕はしてる。
XBLを使うと、DOMノードにgetterやsetterになってるプロパティを定義したり、独自のメソッドを追加したりできる。でも、それらはJavaScriptのテクニックで代用できないこともない。JavaScriptのレベルで目的を達成するやり方として、僕は最近よく、こんな設計をしてる。
function MyController(aNode) {
this._node = aNode;
this.init();
}
MyController.prototype = {
get property() {
...
},
set property(aVaule) {
...
},
method : function() {
...
},
init : function() {
this._node.addEventListener('...', this, false);
...
},
destroy : function() {
this._node.removeEventListener('...', this, false);
...
},
handleEvent : function(aEvent) {
...
}
};
var node = document.createElement('box');
node.controller = new MyController(node);
かなりの部分はこういったやり方で目的を達成できると思う。ツリー型タブなんかもこれに近い実装になってる。
ただ、オートコンプリートのテキストボックスの挙動を変えるだとかの、本体で定義されている物を置き換える場面では、たまにこの方法だけでは不十分なことがある。例えば<textbox type="autocomplete" />
な要素のmaxDropMarkerRows
や<panel type="autocomplete" />
な要素のoverrideValue
やなんかはreadonlyなプロパティとしてXBLで定義されてしまっているので、これらが返す値を変えたいと思うと結構厄介な事になる。
XBLを使わないでサクッと済ませようとすると、__defineGetter__()
を使う方法がまずは思い浮かぶ。Firefox 3.0以降ではDOMノードに対して__defineGetter__()
を使えるので、上記の例のコードのinit()
あたりでそれを使ってやるという感じだ。実際、XUL/MigemoではoverrideValue
で任意の値を返すためにそうしてる。
でも、この方法はできれば使わない方がいいのかもなと思ってる。そう思ったきっかけは、同じようなことをやるコードの自動テストを書いていた時。setUpとtearDownで毎回ウィンドウを開いたり閉じたりとやってると時間がかかってしょうがないからUxU組み込みのフレームにページを読み込ませて……という風にしてみたら、セキュリティの制限に引っかかってしまった。object.__defineGetter__(name, getter)
のobjectとgetterの属してる名前空間が違うと、Illegal valueとか言われてエラーになってしまった。それでTrunkでの__defineGetter__()
の実装を見てみたら、この両者のコンパートメントが違う場合はゲッタの登録を拒否するような設計になってた。こういうセキュリティの制限を回避してreadonlyなプロパティの働きを置き換えようと思ったら、どうもやはり、XBLを使うしかないようだ。
そもそもなんでoverrideValue
がreadonlyなんだよ、なんで書き換え可能なただのフィールドになってないんだよ、って思って来歴を調べてみたら、nsIAutoCompletePopupインターフェースのoverrideValue
は昔のオートコンプリートの実装におけるgetOverrideValue()
メソッドがその祖先で、当時のコードには「こいつの働きを変えたかったらXBLでオーバーライドしろ」ってコメントが書いてあった。今のFirefoxのオートコンプリートの実装にはこのコメントがなかったので、なんでreadonlyになってるんだよという不満しか抱きようがなかった。getXXXとなってたメソッドをプロパティの形に置き換えるなら、確かにそれはreadonlyになるだろう。
でもどうせプロパティに変えたんだったらwritableにしたってよかったはず。ほんとに、何でこんな設計にするんだろう……
JavaScriptでアニメーション(モーショントゥイーンなど)をやろうと思うと、setTimeout()
とかsetInterval()
とかのタイマーを使うことになる。でもタイマーを複数個同時に走らせるのはリソース的に無駄だしオーバーヘッドが大きいせいでアニメーション効果ががたついてしまうようになったりするので、複数のアニメーションを同時に進行させるなら、1つのタイマーで複数のアニメーションを同時に処理した方がいい。
というわけで、そのためのライブラリをだいぶ前に作った。これは実際にツリー型タブ等で使ってる。以下のようにすると、タイマーの開始とか停止とかをヨロシクやってくれる。
var animationManager = window['piro.sakura.ne.jp'].animationManager;
// JavaScriptコードモジュールとして読み込むなら
// Components.utils.import('resource://myaddon-module/animationManager.js');
// で animationManager がエクスポートされる
var tab = gBrowser.selectedTab;
var task = function(aTime, aBeginningValue, aTotalChange, aDuration) {
// aTime:アニメーション開始時点からの経過時間(ミリ秒)
tab.style.marginLeft = (aBeginningValue + (aTime / aDuration * aTotalChange))+'px';
return aTime > aDuration; // trueを返すとその時点でアニメーション終了
};
animationManager.addTask(
task, // アニメーションの処理そのものとなる関数
parseInt(getComputedStyle(tab, null).marginLeft), // aBeginningValue:初期値
50, // aTotalChange:変化量
250 // aDuration:アニメーションにかける時間(ミリ秒)
);
この例だとmargin-leftが今の値から50増加するアニメーションだけど、変化量の指定をマイナスにすればそれだけで、今の値からmargin-leftが50減るアニメーションになる。(ちなみに、アニメーションの処理となる関数が受け取る引数の形式がなんでこうなってるのかについては、「高速な環境ではたくさん描画していいけど、低速な環境だと再描画を減らしてほしい。とにかく、1回のアニメーションは決まった時間の中できちんと終わらせたい。」という可変フレームなアニメーションをやりやすくするためです。)
既に動いてるアニメーションを中止する時は、登録した関数をremoveTask()
に渡す。
animationManager.removeTask(task);
// すべてのアニメーションを中止するなら
// animationManager.removeAllTasks();
中止ではなくてアニメーションを一時停止→再開させたい場合は、こう。
animationManager.stop(); //停止
// ...何かの処理...
animationManager.start(); // 再開
という感じのライブラリなんだけど、これを最近mozilla-centralに入った新しいアニメーションのためのAPIに対応させてみた。APIとしての変更は、addTask()
の最後の引数にDOMWindowを受け取るようになったという点のみ。
animationManager.addTask(
task,
parseInt(getComputedStyle(tab, null).marginLeft),
50,
250,
window // アニメーションを行うウィンドウ
);
リンク先で紹介されているmozRequestAnimationFrame/MozBeforePaintが利用できる環境ではそれを使用して、そうじゃない時は今まで通りsetInterval()
で処理する。ライブラリを使う側のコードは、Firefoxのバージョンの違いを意識する必要は全く無い。ただ、APIを生で使う時に比べるとオーバーヘッドがあるから、新APIのメリットぶちこわしかもしんない。
リンク先のエントリを見た感じでは、理屈としては一般的な可変フレームレートのアニメーションの考え方に基づいているみたいなんだけど、DOMのイベントをトリガーにしないといけなかったり次のフレームを描画するためにいちいちメソッドを呼ばないといけなかったりというのは煩わしいと思ったので、せっかくだからライブラリ側で面倒を見るようにしてみた。