Greasemonkey スクリプトとイベントで通信 ― 2008年06月26日 08時12分
「Greasemonkeyスクリプトとウインドウ間で安全に通信する」にて、DOM イベントを用いた Web ページと Greasemonkey スクリプトとの通信について述べられています。そちらでは dispatchEvent メソッドの返り値による 1 bit 通信に触れていますが、やはりもっと自由にデータをやり取りしたいもの。そのためにはどのような方法があるでしょうか。
独自プロパティ
真っ先に思いつくのは、Web ページ側でイベントオブジェクトを作成した際、独自プロパティを追加する方法ですが、これはだめです。Greasemonkey スクリプト側ではイベントオブジェクトの独自プロパティを取得できません。event.wrappedJSObject.myProperty
のように wrappedJSObject を介せば取得できますが、せっかく安全のため Firefox 側でラッパーに包んでくれたのに、それを外すべきではありません。wrappedJSObject は危険です。
CustomEvent
こんなこともあろうかと DOM 3 Events 草案では CustomEvent というイベントが用意されています。これを使えばイベントオブジェクトに独自のデータを保持できます。しかし、Firefox 3 は DOM 3 Events に対応していません。終了。
CommandEvent
Firefox 3 では新しく CommandEvent というイベントに対応しています。これは XUL での利用を考えて追加されたものですが、Web ページから作成することもできます。CommandEvent には文字列型の command プロパティがあるので、ここにデータを格納できます。JSON などを使えば複雑な構造のデータもやり取り可能です。
// Greasemonkey スクリプト
document.addEventListener("GMPingCommand", function (request) {
var response = document.createEvent("CommandEvent");
response.initCommandEvent("GMPongCommand", true, false,
'You said "' + request.command + '"');
document.dispatchEvent(response);
}, false);
// Web ページ
window.addEventListener("load", function (event) {
var request = document.createEvent("CommandEvent");
request.initCommandEvent("GMPingCommand", true, false,
"Hello, world!");
document.dispatchEvent(request);
}, false);
document.addEventListener("GMPongCommand", function (response) {
alert(response.command); // => You said "Hello, world!"
}, false);
DataContainerEvent
Firefox 3 では新しく DataContainerEvent というイベントに対応しています。これも XUL での利用を考えて追加されたものですが、Web ページから作成することもできます。DataContainerEvent に専用の初期化メソッドはありませんが、getData メソッドと setData メソッドがあります。つまり、このイベントオブジェクトは辞書として扱えるのです。基本的に任意の型の値を格納できますが、Web ページと Greasemonkey スクリプトの間で使おうとするとセキュリティ制限がかかります。
Web ページで値を設定し、Greasemonkey スクリプトでそれを取得する場合、プリミティブ値 (undefined、null、文字列値、数値、真偽値) はそのまま取得できますが、JavaScript のオブジェクトや配列は取得できず null が返ります。Window オブジェクトや DOM ノードオブジェクトはラッパーに包まれます。なお、存在しないキーの値を取得しようとすると null が返ります。
逆に、Greasemonkey スクリプトで値を設定し、Web ページでそれを取得する場合、JavaScript のオブジェクトや配列もそのまま返されます。しかし、権限の異なるオブジェクトをさらすことになるので、データのやり取りはプリミティブ値のみにとどめるべきでしょう。Window オブジェクトや DOM ノードオブジェクトに関しては、ラッパーを値に設定してもラッパーが外されて返されます。
// Greasemonkey スクリプト
document.addEventListener("GMPingDataContainer", function (request) {
alert(request.getData("number")); // => 42
alert(request.getData("object")); // => null
alert(request.getData("window"));
// => [object XPCNativeWrapper [object Window]]
var response = document.createEvent("DataContainerEvent");
response.initEvent("GMPongDataContainer", true, false);
response.setData("data",
'You said "' + request.getData("data") + '"');
document.dispatchEvent(response);
}, false);
// Web ページ
window.addEventListener("load", function (event) {
var request = document.createEvent("DataContainerEvent");
request.initEvent("GMPingDataContainer", true, false);
request.setData("number", 42);
request.setData("object", { foo: 42 });
request.setData("window", window);
request.setData("data", "Hello, world!");
document.dispatchEvent(request);
}, false);
document.addEventListener("GMPongDataContainer", function (response) {
alert(response.getData("data")); // => You said "Hello, world!"
}, false);
MessageEvent
Firefox 3 では新しく MessageEvent というイベントに対応しています。これは HTML 5 草案で定義されているもので、文書間メッセージングなどで利用されます。文書間メッセージングについては、現在好評発売中の WEB+DB PRESS Vol. 45 の連載「JavaScrit + ブラウザ探検 第 2 回 気になる Firefox 3 の新機能」に詳しく載っていますので、ぜひ購入しましょう。MessageEvent には文字列型の data プロパティがあるので、ここにデータを格納できます。
// Greasemonkey スクリプト
document.addEventListener("GMPingMessage", function (request) {
var response = document.createEvent("MessageEvent");
response.initMessageEvent("GMPongMessage", true, false,
'You said "' + request.data + '"',
location.protocol + "//" + location.host,
"", window);
document.dispatchEvent(response);
}, false);
// Web ページ
window.addEventListener("load", function (event) {
var request = document.createEvent("MessageEvent");
request.initMessageEvent("GMPingMessage", true, false,
"Hello, world!",
location.protocol + "//" + location.host,
"", window);
document.dispatchEvent(request);
}, false);
document.addEventListener("GMPongMessage", function (response) {
alert(response.data); // => You said "Hello, world!"
}, false);
ここでは initMessageEvent メソッドの第 5 引数 origin と第 7 引数 source にそれぞれページの生成元と Window オブジェクトを渡しましたが、これは空文字列と null でも構わないでしょう。
XULCommandEvent
Greasemonkey スクリプトではなく拡張機能ですが、分割ブラウザもイベントを用いた API を提供しています。そこで使われているのが XULCommandEvent で、上記三つとは異なり Firefox 2 以下でも使えるという利点があります。ただし、直接文字列を格納することはできないので、別のイベントのイベント名を使うという荒業に出ています。ちなみにこの API は Stylish の作者の提案をきっかけに作られたそうです。
独自属性
以上の方法はイベントのみで完結する、すなわち文書構造をいじらないことを前提としたものでした。文書構造を変更し、時には不正な HTML 文書になってもいいというのなら、HTML 要素を追加したり要素に独自属性を追加したりすることで、その要素、属性経由で値をやり取りできます。Firebug がとっているのと同じような方法です。
名前空間
イベントを用いて通信する以上、あらかじめイベント名を決めておく必要があります。ここで、安易にイベント名を決めてしまうと、複数の Greasemonkey スクリプトや拡張機能の間で名前が衝突してしまうかもしれません。そこで、DOM 3 Events 草案ではイベントの種類を特定するのに、従来のイベント名に加えて名前空間 URI を用いるようになっています。しかし、Firefox 3 は DOM 3 Events に対応していません。終了。
と終わってしまうのもなんなので、ここは DOM 2 Events の流儀に従うことにしましょう。MyGMMessage のようにイベント名に接頭辞 (この例では MyGM) をつけるのです。ただし、DOM という接頭辞は DOM Events によって予約されているので使ってはいけません。DOMContentLoaded や DOMMouseScroll などはだめな名前の典型です。
なお、DOM 3 Events のことも考えれば、イベント名は XML 名前空間における NCName、すなわち名前空間接頭辞を含まない XML の要素名に使える文字列にすべきでしょう。
まとめ
個人的には、Firefox 3 を対象にするなら CommandEvent がよいと思います。上で書いた中では使い方も一番シンプルですし、やり取りするデータが文字列に限定されるので余計な心配を減らせます。
さて、使えそうなイベントが三つも追加された Firefox 3 ですが、これ以外にもさまざまな新機能が搭載されています。現在好評発売中の WEB+DB PRESS Vol. 45 の連載「JavaScrit + ブラウザ探検 第 2 回 気になる Firefox 3 の新機能」に詳しく載っていますので、ぜひ購入しましょう。大切なことなので 2 回言いました。
コメント
トラックバック
このエントリのトラックバックURL: http://nanto.asablo.jp/blog/2008/06/26/3596261/tb
コメントをどうぞ
※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。
※投稿には管理者が設定した質問に答える必要があります。