6 posts tagged “greasemonkey”
きっかけは MyMiniCity というサービスで,アクセス数に応じて自分の街が発展するという性質から Twitter 上にやたらと MyMiniCity へのリンクが貼られるようになった.個人的には興味がないのでスルーしていたが,そのうち巧妙な釣り文句と共に TinyURL を噛ませたリンクが貼られるようになって,迂闊に踏んでしまうとどうも悔しい.悔しいので対策することにした.
TinyURL のリンクを自動 decode している人は多いと思うが,自分も 自作 decoder を利用しているので,別途ページ中のリンクを走査して MyMiniCity らしきリンクをどうにかする Greasemonkey script を書けば解決するだろう.サクっと書いた.
// myminicity link detector
// (毎回 RegExp をコンパイルしているのは見逃して><)
var anchors = node.getElementsByTagName('a');
for (var n = 0, len = anchors.length; n < len; ++n) {
var anchor = anchors[n];
if (anchor.href.match(/^http:\/\/[^\.]+\.myminicity\.com\b/))
anchor.textContent = '<MyMiniCity Link>';
}
こいつを decoder script の後に動くようにしておけば偽装 MyMiniCity リンクもイチコロだぜ!! と思いきや,どうもうまく動かない.それもそのはずで,TinyURL decoder は tinyurl.com に対して decode 要求を行う際に GM_xmlhttpRequest による非同期通信を利用しており,decoder script が終了して更に myminicity link detector が終了してから実際の decode が行われる可能性もある.後続の処理を確実に動かすためには GM_xmlhttpRequest の onload ハンドラ内で行うしかないが,TinyURL decoder 内部に MyMiniCity 依存の処理を加えるのは気が引ける.
ところで,TinyURL 以外の URL 短縮サービスで MyMiniCity リンクが貼られることもあって,できればこれらも対応したい.最近は xrl.us という URL をよく見かける.他にも urltea.com, qurl.com, z.la など数えるとキリがないが,これらの中には明示的な decode 手段を用意していないものが多い.encode 用 API は用意されているが,decode はブラウザ任せとういか,実際に短縮 URL にリクエストしてみてリダイレクト時の location レスポンスヘッダを参照するしかない場合がある.GM_xmlhttpRequest は (自分の知る限り) リダイレクトに自動追随してしまうのでリダイレクト時の location レスポンスヘッダを参照することは恐らくできない.困ったものだ.
というか,decode API はともかく URL 短縮サービスは TinyURL のような Preview Mode を必ず備えているべきだと思うのだがどうよ.危なくね?
閑話休題,他の URL 短縮サービスに対応するとしても,要はどれもページ内のリンクを走査して何かを処理するというところは同じなので,その部分をうまく plugin 化できないものだろうか.AutoPagerize.addFilter のようにハンドラを登録して順次処理してくれる感じで.問題は前述の通り非同期処理を挟む可能性があるので単純にループでハンドラを回すだけでは目的は達成できない.
ハンドラが 3 つある場合は,このような処理をしたいわけだ:
if (should_do_1) {
GM_xmlhttpRequest({
onload: function(r) {
do_1();
if (should_do_2) {
GM_xmlhttpRequest({
onload: function(r) {
do_2();
if (should_do_3) {
GM_xmlhttpRequest({
onload: function(r) {
do_3();
else の場合も同様に次のハンドラへ処理を渡す.
というわけで,できた.
ページ内のリンクを走査し,登録されたハンドラ群に処理を渡すだけの Link Manipulator と,Link Manipulator の plugin として動作する TinyURL decoder と xrl.us decoder (これらは一つにまとめた),そして MyMiniCity のリンクを検出してアンカーテキストを置き換える detector を作った.detector は Twitter/Favotter 上で動作し,アンカーテキストを置き換えずに post そのものを非表示にすることもできる (今のところ要ソース修正 ;-).decoder は xrl.us → TinyURL といった二重 decode にも一応対応した.もちろん AutoPagerize 対応.
- Link Manipulator (管理用 script. 必須)
- Shorten URL Expander (各種短縮 URL 展開 plugin)
- Anti MyMiniCity (MyMiniCity リンク検出 plugin)
非同期処理が前提なので,スクリプトの実行順序に対して sensitive なのが玉に瑕.AutoPagerize → Link Manipulator → plugin scripts の順で並んでいないと意図通りに動かない (間に別のスクリプトが入るのは問題ないが).MyMiniCity のリンクをどうにかする場合は,事前に decoders を動かしておく必要がある.
TODO:
- decode 結果のキャッシュとクリア
- plugin 毎に処理するノードをカスタマイズできないか
- TinyURL, xrl.us 以外の短縮 URL 展開対応 → z.la に対応した (12/23)
というか,いまさら TinyURL ネタかよ :-(
install すると,favotter 上の各 post の下に星が出てきてクリックすると fav れる.既 fav かどうかは調べていないので,とりあえず全て白い星が表示される.fav った後は unfav もできる.
ついでに Twitter 上への permalink も設置する.
install: favotter-favor.user.js
なんか,自分が AutoPagerize をインストールした数日後 (7/10) に新しいバージョンがリリースされていて,あまつさえ addFilter なんつー機能が増えているジャマイカ.なんてことに二ヶ月くらい気付かなかった.
ので de-tinyurl も追随しました.
- AutoPagerize.addFilter を利用して,力技対応をやめた.
- query string 付き URL の decode がおかしかったのを修正 (なぜ気付かない?
前回の続き.「初心者向け Javascript 勉強会」の Greasemonkey に関する資料が公開されているブログエントリのコメント欄にて補足的なやりとりが見られる.どうやら Greasemonkey から提供された window オブジェクトはスクリプト間で共有されるらしい.確認してみよう.ようやく Firebug の "console" を覚えたのでソレで.
console.log('script a');
a = 10
window.b = 11;
if (typeof a != 'undefined') console.log(a);
if (typeof b != 'undefined') console.log(b);
if (typeof c != 'undefined') console.log(c);
if (typeof d != 'undefined') console.log(d);
もう一つ.
console.log('script b');
c = 20;
window.d = 21;
if (typeof a != 'undefined') console.log(a);
if (typeof b != 'undefined') console.log(b);
if (typeof c != 'undefined') console.log(c);
if (typeof d != 'undefined') console.log(d);
コンソールには "script 1" → "10" → "11" → "script 2" → "11" → "20" → "21" の順で表示された.window オブジェクトに定義したプロパティが引き継がれていることが分かる.script 2 側で "10" が表示されていないことから,Global オブジェクト != window オブジェクトであることも分かる (スコープチェイン中で a が未定義であることが前提).
で,ひとつ重要な事柄が.どうやら Opera の UserJS というもので Greasemonkey と似たようなことができるらしく,こちらでは機能が設けられた目的の違いから Greasemonkey のような保護が効かないようだ.つまり,スクリプトを Opera 対応させたい場合は匿名関数で wrap した方が良い,ということになる (その他,様々な cross browser hack も必要になるのだろう).恐らく,スピーカの方もこの件を伝えたかったのだと思う.
以上,今朝 Twitter で「グリモンスクリプト中の匿名関数は時代遅れのバッドノウハウ」と,やや刺激的に紹介されてしまったのでビビって補足している次第 (まぁでも間違ってないよな).ぶはは ;D
去る 7/14 に Twitter 発の「初心者向け Javascript 勉強会」が株式会社ノッキングオンにて開催された (こういった勉強会などを対象に会議室を提供して頂けるようだ.感謝!!).長文を書くのが苦手なので,あまり「まとめ」染みたことはできないのだが,印象に残っている範囲で一点だけ (一点かよ!!).
実は先週初めて Greasemonkey に触れて色々と遊んでいたのだが,学習用に目にした殆どのスクリプトにおいてコード本体を匿名関数で wrap しているのが気になった.
(function(){
// こんな感じ
})();
このケースで匿名関数を使う利点として,パッと思いつくのはこれくらい.
- 現行変数オブジェクトのプロパティを汚染しない
- 途中で return できる
- 自分自身を呼び出せる
2 は場合によっては使えるかもしれないが必須というわけではないし,3 が必要になることはそうないだろう.やはり 1 を目的にしているような気がする.script 要素から呼び出された外部スクリプトの実行コンテキストは呼び出し元と同一であることは周知の事実だが,別ファイルとして管理される greasemonkey script もそれと同様ということだろうか.
以前,Twitter 上の誰かの Favorite 経由で「gm を function で囲むのって unsafeWindow がなかったころのバッドノウハウだよね。なんとなく今でもラップしるけど、いらなんだよね。」 という発言を見てからは「なんだ,要らないじゃないか」と盲目的に思っていたのだが,Javascript 勉強会での Greasemonkey の話題でもやはり匿名関数による wrapping されたコードが雛型として挙がっていた.自分にとってこの点は非常にタイムリーだったので質問しようと思ったのだが,矢継ぎ早に以下の捕捉があった.
- これ (wrapping) がなくても不用意に元スクリプト環境を壊すことは無い.元スクリプト環境に触るには unsafeWindow を経由する必要がある.
- しかし,同一ページ上で動作する複数の greasemonkey script 同士で干渉が起きることを防ぐため,これ (wrapping) は入れなければならない.
うむ~,なるほど.前者は理解していたが,後者が本当であれば wrapping は必要ということになる.帰ってから試そう.という経緯で書いているのがこのエントリである.
恐らく事実上 official だと思われる "Dive Into Greasemonkey" の一節 を確認してみると「スクリプト間に悪影響を及ぼさないように取り計らってくれる (意訳)」,「自動的に匿名関数で wrap される (意訳)」等の記述が見られるので,やっぱり問題ないような気もする.でも実際に試してみた方がいいのだろう.
まずは "Dive Into Greasemonkey" にあった「自動的に匿名関数で云々」に少々引っかかりを感じるのだが,単に匿名関数で wrap されるだけであればスコープチェイン上に呼び出し元の変数オブジェクトが存在し,かつそれを触れることになってしまう.
例えば,こんな page script があったとして
var a = 10;
該当ページで以下の greasemonkey script が動作したとする.
a = 20;
動作後,a の値は (期待通り) 10 であるので,やはり単なる匿名関数ではないのだろう.一安心.
ん,まてよ.greasemonkey script が自動的に匿名関数内で動作するのであれば,先に挙げた
- 途中で return できる
- 自分自身を呼び出せる
も動くのでは!?
alert('a');
return;
alert('b');
うむ,return できる ("b" は表示されないし,return 時にエラーも起こらない).
更に自己再帰.
var i = arguments[0] || 0;
alert('i = ' + i);
if (i < 3) arguments.callee(i + 1);
おお,呼び出せる.これは知らなかった :-D
で,次.複数の greasemonkey script を動かした場合.
// greasemonkey script 1
alert('script 1');
var a = 11;
b = 12;
if (typeof a != 'undefined') alert(a);
if (typeof b != 'undefined') alert(b);
if (typeof c != 'undefined') alert(c);
if (typeof d != 'undefined') alert(d);
そして別ファイル.
// greasemonkey script 2
alert('script 2');
var c = 21;
d = 22;
if (typeof a != 'undefined') alert(a);
if (typeof b != 'undefined') alert(b);
if (typeof c != 'undefined') alert(c);
if (typeof d != 'undefined') alert(d);
alert された順番は "script 1" → "11" → "12" → "script 2" → "21" → "22" となり,やはり相互のスクリプト間で環境は独立しているように見える.
というわけで,やはり明示的に匿名関数で wrap する必要はないと改めて思ったのだが,まぁ単に「昔は必要だった (未確認) 」のと「コピペによる伝播」の影響が大きいんだろうなぁ.
当方,素人につき突っ込み歓迎 (免罪符).
2007-07-22 追記
補足エントリを書いた.
昨日,初めてグリモンを作ってみて,あーこりゃたのしーみたいな状態なわけですよ.
で,TinyUrl をデコードできないかという発言と AutoPagerize 対応しないかなという発言に反応してチトやってみた.現実逃避.グリモン暦二日なので色々とご容赦いただきたい ;D
AutoPagerize 対応部分がチト強引かなぁ.
追加された部分だけ走査すると早いんだろうなー.
- 早速,onscroll の度に getElementsByTagName を実行するという鬼畜仕様を改善.scroll が落ち着くたびに走査.
- 早速,フラグの true と false 間違えてたので修正 (死亡).
追記 (2007-09-15)