月曜日, 8月 13, 2007

Greasemonkeyで遊ぶ(その1)

仕事中になかなかRubyを書くわけにもいかず、やっぱりJavaScript書いてることのほうが多い今日この頃はGreasemonkeyで自分好みにいろんなサイトを変更するのが仕事の合間のマイブームになりました。 Greasemonkey用に書いたソースはちょこっとだけ書き換えればOperaでも動くので、 普通のブラウジングをOperaに頼ってる私としては重宝します。

GreasemonkeyではCSSはJSから出力するしかないので、 まずGreasemonkeyじゃない普通のページでやりたい処理を実現してからそれをJSで出力するように移行するように作業をしています。 そこで以下のような例外に遭遇。

uncaught exception: [Exception... "Component is not available" nsresult: "0x80040111 (NS_ERROR_NOT_AVAILABLE)" location: "JS frame :: file:///C:/Documents%20and%20Settings/[省略]/components/greasemonkey.js :: anonymous :: line 700" data: no]

Operaでは動くのになんでだーと思いながら調べると、どうもobj.onclick=function(){}は使えないというブログのエントリを発見。 さらに検索を続けたところ以下のオライリーのサイトにたどり着きました。

O'Reilly Network -- Avoid Common Pitfalls in Greasemonkey

本当だったようです。 この記事が非常に役に立つのでメモっておきます。 (書籍にまとまってたりするのかなぁ) 翻訳チョー適当なので勘弁してください。

Pitfall #1: Auto-eval Strings(落とし穴1:自動評価文字列)

通常ブラウザで動かすJavaScriptでは、 setTimeout関数のようなコールバック関数を引数に取る関数にsetTimeout("my_func()", 1000);のように関数の文字列を渡すことができます。 内部では実行時に文字列を評価(eval)してくれているようです。 これがGreasemonkeyでは動かないらしい。

このような場合は、var func = function(){};のようにして関数リテラルにしてしまってfuncを第一引数に渡すか、 setTimeout(function(){}, 1000);のように無名関数を直接埋め込んでしまえば正しく実行されます。

Pitfall #2: Event Handlers(落とし穴2:イベントハンドラ)

まさにこれが私のハマったやつだったわけですが、 onclickやonchangeやonsubmitのようなイベントハンドラを使用したケースです。 私はよく以下のように書きます。

document.getElementById('my_obj').onclick = function(){
 alert('my_obj onclick');
};

これがダメ。getElementByIdはElement自身ではなくXPCNativeWrapperというラッパを返すため、 上記のようなコードではelementにonclickを設定するコードにはならないというもの。 (idやclassNameのようなプロパティはXPCNativeWrapperがちゃんとラッピングしているElementの値を返すようです)

これを回避するにはaddEventListener関数を使えとのこと。 上記のコードの代わりにこう書きます。 (Operaではonclickに直接無名関数を記述しても動きますが、以下の例でも正しく動きます)

document.getElementById('my_obj').addEventListener("click", function(){
 alert('my_obj clicked.');
}, false);
//3つめの引数はイベントバブリングの制御のための値
//「addEventListener バブリング」で検索すると分ります

Pitfall #3: Named Forms and Form Elements(落とし穴3:名前を付けたフォームとフォーム要素)

以下のようになっているフォームがあるとき。

<form id="gs">
<input name="q" type="text" value="foo">
</form>

以下のような書き方でqの値をとることができないようです。 (個人的にはこういう書き方しないのでなんとも...)

var q = document.gs.q.value;

Pitfall #4: Custom Properties(落とし穴4:カスタムプロパティ)

JavaScriptでは好きなプロパティを勝手に付けることができますが、 これもXPCNativeWrapperがあるためにできないとのこと。 以下のようなコードはエラーにはなりませんが、意図した通りに動きません。

var elmFoo = document.getElementById('foo');
elmFoo.myProperty = 'bar';

idなどのプロパティは普通にアクセスできますが、 自分でオリジナルのプロパティを付けたい場合は以下のようにsetAttributeを使います。

var elmFoo = document.getElementById('foo');
elmFoo.setAttribute('myProperty', 'bar');

セットした値を取得するにはgetAttributeを使います。 ここでちょっとハマったので注意。 getAttributeの戻り値は必ずString型になるようです。 (falseをセットしたら戻り値が'false'でtypeofしたところ'string'でしたから) フラグをセットする場合などはそのままfalseをセットするだけではダメですね。

Pitfall #5: Iterating Collections(落とし穴5:コレクションの数え上げ)

(ちょっと解釈を間違っているかもしれません) 以下の例を見てください。

//配列のループ
var list = [1,2,3,4];
for (var i = 0 ; i < list.length ; i++)
{
 alert(list[i]);
}

//オブジェクトのプロパティの取り出し
var obj = {'name': 'narucissus', 'age': 26};
for (var j in obj)
{
 alert(j + ' is ' + obj[j]);
}

変数listは([]を使って)配列として定義しています。 この配列のすべての要素にアクセスするときにはfor(;;)を使います。

これに対して変数objは({}を使って)連想配列(またはオブジェクト)として定義しています。 この連想配列の要素にアクセスするときにはfor(in)を使います。

通常この2つの用法は意識して区別していますが、 ブラウザが意図したとおりに実行してしまうせいか、配列に対してfor(in)を使用するケースが多いです。 引用元では上記のようには書いていませんが、これがダメだよということだと思います。 (違っていたら詳しい方コメントください)

あと5つもある...続きはウェブで。(ウェブだけど)

1 件のコメント:

匿名 さんのコメント...

自分もグリモンの onclick ではまってたので助かりました。
どちらかというと、グリモンの問題というより、Firefox の実装を知らなかっただけという感じかも知れないですけど、自力で探してたらなかなか原因が分からなかったと思います。

調べてみたら、MDCでもちゃんと説明されてますね。
「http://developer.mozilla.org/ja/docs/XPCNativeWrapper#Limitations_of_XPCNativeWrapper」
の「XPCNativeWrapper の制限事項」の項。