2006-09-28

Firefoxの拡張MozLabの中に含まれるMozReplがヤバすぎる件について

MozLabという拡張を昨日知ったのですが
http://dev.hyperstruct.net/trac/mozlab

この中に含まれているMozReplというのがヤバい。Firefoxにtelnet接続できるようになる。

とりあえずRubyで書いた簡単なサンプル、今見ているページをリロードするだけ。
require 'net/telnet'
telnet = Net::Telnet.new({
    "Host" => "localhost",
    "Port" => 4242
}){|c| print c}
telnet.puts("content.location.reload(true)")
telnet.close

ひたすら自分が見ているURLとページタイトルを記録する系とか簡単に作れそう。
今見ているページのURLとタイトルを取得するサンプル。
require 'net/telnet'
prompt = /^repl> /
telnet = Net::Telnet.new({
    "Host" => "localhost",
    "Port" => 4242,
    "Prompt" => prompt,
})

telnet.waitfor(prompt)
telnet.cmd("content.location.href") do |c|
  print c.gsub(prompt,"")
end
telnet.cmd("content.document.title") do |c|
  print c.gsub(prompt,"")
end
telnet.close

ただし一秒おきとかに実行してたらそれなりにFirefoxの側に負荷がかかるようなので注意。
まだ詳しく調べてはいないんだけど、原理的にはFirefoxの全操作をtelnetから叩くことができそう。

about:configからextensions.mozlab.mozrepl.autoStartをtrueにすればFirefoxの起動時に自動でtelnet接続できる状態になる。extensions.mozlab.mozrepl.portでポートの変更も可能。localhost以外からの接続は受け付けないようなので、起動しただけでクラックされるようなことはないだろう。もちろん自己責任でFirefoxをぶっ壊すようなコードを実行することもできる、と思う。

ポートフォワード仕込んでMacBookのFirefoxを遠隔操作してみたり。必須の拡張がまた増えた。

2006-09-22

SafariのAjaxの文字化けをクライアント側だけで対応するバッドノウハウ

SafariでXMLHttpRequestのresponseTextが文字化けするという話。
http://blog.33rpm.jp/garbled-on-safari.html

最近のバージョンだとcontent-typeがちゃんとしてれば化けなかったような記憶があるけど、まあともかくとして、Safariで文字化けするのはJavaScript側だけで対処することができたりする。
http://kawa.at.webry.info/200511/article_9.html

これ読んでほんとかよ、って感じだったんだけど案外役に立った。livedoor Readerで使われてたり。
実際に使われてるコードはこんなの。
if(browser.isKHTML){
    ajax.filter.add(function(t){
        var esc = escape(t);
         return(esc.indexOf("%u") < 0 && esc.indexOf("%") > -1) ? decodeURIComponent(esc) : t
    })
}

ブラウザがSafariの時だけresponseTextにフィルターを適用するようになっている。
直し方わかってんなら直せよ、ってことで、このブログのナビゲーションのやつも直してみた。
var ajax_filter = function(t){return t};
if(navigator.appVersion.indexOf( "KHTML" ) > -1){
    ajax_filter = function(t){
        var esc = escape(t);
        return(esc.indexOf("%u") < 0 && esc.indexOf("%") > -1) ? decodeURIComponent(esc) : t
    }
}

こんなん書いてresponseText処理する前にはさんだ。

ブラウザ依存の処理を書くときは関数内で細かく処理を振り分けたりしないで、そもそも関数自体全く別のものに切り替えてしまう、ってのが好き。そうすると他のブラウザで余計な分岐処理を通らずに済む。この場合だと振り分け部分のコストは全然たいしたこと無いけど、DOM参照したりすると大したことあったりする。

ってのがここら辺に書いてあったりする。
http://dean.edwards.name/weblog/2005/12/js-tip1/

2006-09-09

2006-09-06

JavaScriptでPythonのsetみたいなの

http://la.ma.la/misc/js/set/

Pythonのset型をjsに移植してみた。setってのは要素が重複しないリスト。
http://www.python.jp/doc/release/lib/types-set.html

配列として扱いたいケースが多いように思うのでArrayを拡張してSetのメソッドを加える感じで作った。制約付きの配列、みたいな感覚で扱えるように。addやupdateを使わずにpushを使って要素を追加すると重複してしまう(pushも上書きすればいいけど、直接値を代入されたらどうせ防げない)。なので厳格ではない。格納できるのはstringとnumberとboolean型のみ。でもエラーが出るわけでもない。そこら辺いい加減なので作り直すかも。

用途としてはAさんとBさんとCさんがブックマークしてるURLとか、にも関わらずあなたがブックマークしてないURLとか、そういうのを計算するのを楽にしたいなーと思って丁度Pythonの本読んでたら具合が良さそうなので書いてみた感じ。

OperaでJSONPを非同期リクエストする

JSONP が Opera だと非同期処理できない
http://d.hatena.ne.jp/secondlife/20060906/1157515075

に書かれているとおりOperaだとscript要素を足した瞬間にJavaScriptの実行が止まって、ロード完了まで後続のスクリプトが実行されなくなります。

サンプル
http://la.ma.la/misc/js/opera_jsonp_test.html

そこで、リクエストの度にダミーのIFRAMEを作って、そのIFRAME内のcontentWindowで実行するという方法を試してみました。
IFRAME内でJSONをロードするサンプル
http://la.ma.la/misc/js/iframe_jsonp_request.html

なんかIEで動かないっぽいけど気にしない。相変わらずタイマーは停止しますが、appendChildした後のscriptも実行されます。ただIFRAMEを生成する分遅くなっていまいちな感じです。で、一見並列でリクエストされているように見えるのですが、コネクションを見張ってみたら、最初のscriptがロード完了するまで次のscriptを読まない、リクエストすらしていないようでした。

そして色々考えた結果、script要素を追加するタイミングをsetTimeoutでずらしてやればいいんじゃないかと言うことでやってみたのがこれ。
http://la.ma.la/misc/js/jsonp_request_with_delay.html

ディレイなしだとスクリプト追加直後に書いたコードが、外部スクリプトのロード後に実行されてうまく動かないけれど、ディレイを入れるとうまく動く。他のブラウザではどちらでも問題は無し。

非同期と言えるかどうかは微妙だけど、読み込み完了まで何も実行されない、という状況はある程度回避できそう。

まとめると
- Operaではタイマーが止まるのでタイムアウトを処理するのはなんか無理っぽい
- IFRAME使ってもscriptは順次読み込まれて、並列にリクエストされることはない
- script挿入のタイミングをずらしてやればコネクション数の管理なんかはできる

これぐらいか。

IFRAMEの中で処理する方法は、IFRAME内で実行されて名前空間が分かれるのでコールバック関数名を共通にできる(ユニークなIDをつけなくてもいいので、ブラウザのキャッシュが効きやすい)っていうメリットがあるんだけど、その他もろもろのデメリットを考えると大したメリットでもないかなーという具合。

2006-09-01

history.backが成功したかどうかを判別する

今も昔も大変よく使われている古典的JavaScriptの一つjavascript:history.back()なんですが、実際に戻る操作が成功したのかどうかを判別することができません。タブブラウザなんかを使っててミドルクリックで新規タブで開いてたりすると、history.backをクリックしても無反応で何も起こらない、なんてことよくありませんか?

そんなわけなのでhistory.backを実行後にページ移動が発生しているかどうかを監視して、戻るに失敗したときに特定の処理を発生させることができるような関数を作ってみました。

function try_back(errback){
    var bs = false;
    Event.observe(window,"unload",function(){bs=true});
    Event.observe(window,"beforeunload",function(){bs=true});
    history.back();
    switch(typeof errback){
        case "function": setTimeout(function(){if(!bs) errback()},100);break;
        case "string"  : setTimeout(function(){if(!bs) location.href=errback},100);break;
    }
    return bs;
}
// 使い方
// 戻るに失敗したときの処理
try_back(function(){window.status="戻る失敗"});
// もしくは戻るに失敗したときにジャンプするURL
try_back("http://example.com");


Event.observeは使ってるライブラリに合わせて適当に書き換えてください。
IEやFirefoxだとhistory.backを実行した後、即座にwindow.onbeforeunloadが実行されるみたいです。Operaはonbeforeunloadが実装されてないのでunloadでも使えるようにしてあります。

window.onbeforeunloadはページから立ち去ってよろしいですか?とメッセージを出すのに使ったりするイベントハンドラ。Google Spreadsheetsなんかで使われてますね。