Apr 30, 2009
swapBrowsersAndCloseOtherの変更への追従と、メソッドの動的な書き換えのメリット・デメリット
以前書いたtabbrowser要素のswapBrowsersAndCloseOther()
メソッドの上書きの話で書いてたサンプルが使えなくなっていたので直した。というか自作アドオンの機能を使おうとして正しく動かなかったのでコードを改めて見てみて、メソッドの書き換えの対象になってる箇所に変更があった事に気付いた(4月16日に変更が行われていたようだ)。
以下は、Shiretoko 3.5b5preに合わせて修正した後のバージョンのサンプルコード。
if ('swapBrowsersAndCloseOther' in aTabBrowser) {
eval('aTabBrowser.swapBrowsersAndCloseOther = '+
aTabBrowser.swapBrowsersAndCloseOther.toSource().replace(
'{',
'$& MyAddonService.destroyTab(aOurTab);'
).replace(
'if (aOurTab == this.selectedTab) {this.updateCurrentBrowser(',
'MyAddonService.initTab(aOurTab); $&'
)
);
}
if (aOurTab == this.selectedTab)
を目印にして「タブを入れ換えた後の再初期化処理」を入れてたんだけど、同じif文がこれより前の位置に増えたせいで、コードの挿入位置がずれてしまい、本来期待していた順番とは異なる順番で処理が行われてしまっていた。if文の中まで目印に含めるようにして一応回避したけど、これはこれで、if文の中を書き換えるアドオンがあると破綻してしまう可能性があるわけで……何かいい方法はないものだろうか。
今回の場合に限らず、「メソッドの入れ替えではなく動的な書き換えを行う」やり方には、こういう変更に逐一追従しなくてはならないという弱点がある。メソッドのインターフェース(引数の数や返り値)はそうそう変わらなくても、中身は結構頻繁に変わる。なので、気をつけておかないといけない。
もう1つの弱点として、書き換え対象のメソッドの中で参照されている変数がそのメソッドが定義された変数スコープからしか参照できないものであった場合に、それへの対処も必要となる。その変数の値がグローバルな名前空間からアクセス可能なものであれば、メソッドの書き換え時にその変数を同時に宣言し直してやればいいんだけど……
(function() {
var attrName = 'foo';
window.ExampleAddonService = {
method : function(aNode) {
aNode.setAttribute(attrName, true);
}
};
})();
// メソッドの書き換えには成功するが、attrNameという変数が
// 見つからなくなってしまうので、実行時にエラーになる
eval(
'window.ExampleAddonService.method = '+
window.ExampleAddonService.method.toSource().replace(
'{',
'{ AnotherAddonService.preProcess(aNode);'
)
);
この例のような場合になると、もうお手上げ。
それでも動的なメソッドの書き換えの方を僕が積極的に使う最大の理由は、同じやり方でメソッドを書き換える他のアドオンとの互換性を維持するためだ。別名で元のメソッドを保持しておくやり方の場合、他のアドオンが元のメソッドの名前で関数オブジェクトを取得しても、その関数の内容は元のメソッドとは全然違うから、書き換えられなくて動かなくなる、という事態が起こり得る。それを避けるためには、上の例のようなお手上げな事例を除き、可能な限り元のメソッドを動的に書き換えた方が良いということになる。
wikieditish message: Ready to edit this entry.