2004-12-13

近況

情報産業それ自身が必ずしも情報化の恩恵に与しているとは限らない. 使いにくい社内のウェブシステムに不条理を感じながらそう愚痴る. 経費削減の圧力を考えるとこれが改善されることはなかろう. アルゴリズムの改善ではなく peephole optimization を重ねる経営陣は, O 記法の神話を信じない pragmatic engineer なのかもしれない.

目には目を. 局所最適化には局所最適化を. 使いにくいウェブには bookmarklet を. ということで, 書くことにした. たとえば担当分のバグ情報を BTS から自分のタスク管理用 Wiki に転載する bookmarklet. よくやる作業の手間が省けるのは気分がいい. 他にも勤怠情報システムの入力を自動化する, なんてのも目論むところ.

書いてみると案外簡単な bookmarklet, 専門のサイト ができるなど普及の兆しは見えるものの こういう局所用途に使う人はあまり目にしない. ブラックボックスとして使う人が多いのだろうか. なんとなく寂しいので, bookmarklet の簡単な入門を試みる. 暇な人はためしてみてください.

Bookmarklet

bookmarklet とは "javascript:" から始まる JavaScript のコードを URI として bookmark に 登録し, 好きなタイミングで呼びだすトリックのことをいう. たとえば

alert('Hello World');

というコードを bookmark に登録してみよう. : Hello World. 登録したエントリをブックマークから選ぶ. スクリプトが呼ばれる. それだけ. スクリプトは "呼ばれたページ" のコンテクストで動くため, 一見セキュリティ上の制約で難しそうなことができる. 自ら XSS するかんじ.

典型的な Bookmarklet : 表示しているページの情報をサーバに送る

もっとも典型的な bookmarklet は, ページ内の情報を使って新しい window を開くものだろう: パラメタにタイトルを渡して window を開く

window.open('http://www.dodgson.org/omo/marklet/show_params.cgi?title=' + document.title);undefined;

CGI の show_params.cgi は単にパラメタを一覧表示する. デバッグの時には便利かもしれない.

さて, URL に閲覧中ページの情報を埋めこんでサーバに送るこのテクニックは bookmarklet の基本中の基本と言える. MovableType などの blog tool が用意する記事投稿 bookmarlet や bloglines の "このページを講読する" bookmarklet はまさにこれである.

なお, javascript の URI は評価結果が HTML として描画される. たとえば以下のような URI を試してみよう: HTML を返すコード

javascript:'<html><body>Hello</body></html>'

一つ前のサンプルでは, 評価結果を undefined とすることでこれを防いでいる.

変数を使う

少し複雑なことをしようとすると, コードの中で変数を使いたくなる. たとえば for 文では "i" をつかいたい. bookmarklet は他人の書いたページのコンテクストで実行されるため, 変数名の重複に気をつけねばならない. そのためには以下のようなイディオムを使う. function object のイディオム

(function() { alert('Hello'); })()

匿名の function object を作って, すぐにそれを評価する, ということらしい. (ECMAScript の仕様書を眺めてもなぜこれが legal な記述なのかわからないけど気にしない...) 実際に変数を使った例 : ページ内の画像を全て非表示に

(function() { var imgs = document.getElementsByTagName('img'); for(var i=0; i< imgs.length; i++) { imgs[i].style.visibility = 'hidden'; } })()

文字列を与える: テキストボックスの表示, 選択範囲の取得

検索ダイアログのようなことを bookmarklet で実現するためには prompt() 関数を使う : 入力された文字列を表示

alert('[' + prompt('Enter Text') + '] is entered');

テキストボックスよりよく使うのは選択範囲にある文字列の取得だろう. これは IE と Mozilla で方法が異る. Mozilla は以下のとおり: 選択範囲の文字列を表示

alert(window.getSelection());

IE の場合

alert(window.selection.createRange().text);

世間の bookmarklet は多くの場合がんばって 両方対応しているようだ.

alert(window.selection ? window.selection.createRange().text : window.getSelection());

JavaSciprt プログラマの苦労が伺える.

新しい Window に好きな内容を与える

テキストボックスしか表示できない prompt() は UI として制約が大きすぎる. 自分で UI を作りたい時はもう少し工夫が必要になる. 自分で新しい window を開いて, そこに表示したい HTML の文字列を与ればいい : Hello New Window

(function() { d = window.open('').document; d.open(); d.write('<html><body>Hello</body></html>'); d.close(); })();

この方法の強力な点は, 新しい window が親の window と同じ URI を持っていることである. これはスクリプトが対象コンテンツと同じドメインで動き, セキュリティ上の制約を受けないことを意味する. 遠慮なく親 window の DOM にアクセスしよう. phisher にとっては羨しい話に違いない.

一行野郎からの脱出 : 外部のスクリプトを読み込む

基本的に one-liner である bookmarklet は, 長いコードを書こうとすると著しくメンテナンス性が下がる. 一行に詰め込みきれず挫けるのは惜しい. 外部にあるスクリプトを読み込む以下のテクニックを使うと, この制約を克服できる : Hello External Script

(function() { s = document.createElement('script'); s.setAttribute('src', 'http://www.dodgson.org/omo/marklet/hello.js'); document.documentElement.appendChild(s); })();

<script src='http://www.dodgson.org/omo/marklet/hello.js'/> という element を新しく作って document に追加するわけ. 追加するタイミングで element は評価され, スクリプトが読み込まれる.

複雑な bookmarklet を公開している人にとっては, コードの可読性が上がるだけでなく常に最新版を配布できるというメリットもある. またサーバサイドで動的に生成したコンテンツを bookmarklet から利用できるのは応用範囲が広い.

なお, このテクニックにはウェブサーバが必要になる. localhost に起動しておくと便利.

文字コードの問題

URL に日本語を埋め込む場合, エンコーディングは現在表示しているページと同じものか, 特定のエンコーディングになる. (ブラウザに依存する.) ウェブサイトによってはエンコーディングを仮定していることがあり, bookmarklet からそういうページを開こうとすると文字化けがおこる. (スクリプトからコンテンツの文字エンコーディングを知ることはできないため, Google のように URL のパラメタにエンコードを指定する場合でも問題は残る.)

この問題はクライアント側だけでは解決できない. CGI で URL の文字コードを適当に(以下のサンプルでは Shift_JIS に)変換, リダイレクトすることで回避してみた.

例を示そう: 選択範囲を google で検索する (変換なし)

(function() { window.open('http://dodgson.org/omo/marklet/convurl.cgi?http://www.google.com/search?ie=Shift_JIS&q=' + window.getSelection()); })()

CGI のソースはこれ: convurl.cgi. NKF を使っても, 最近増えてきた UTF-8 のページには対応できない...

応用編

これでだいたい要素技術が揃った. あとは実際にコードを書いてみよう. 長めのサンプルを書いてみた. はてなダイアリでコメントした人の about 一覧を表示する. (Mozilla 系のみ対応. ちょっと遅いので, 適度にコメントの多いページ, ex. ryoko 日記 で試してみてください.) 本体は list_about.js. サーバサイドの支援は使っていない. かわりにセキュリティの境界をすりぬけたしるしとして XMLHttpRequest を試してみた. 実際に書いてみると, 長い JavaScript のコードも one-liner でないならそう苦労しないことがわかる.

Glue としての Bookmarklet

使いにくいシステムとフロントエンドの繋ぎ目にある層を glue と呼ぶ. UNIX のシェルスクリプトはその典型. ウェブでは CGI 類が glue の役割を果たしてきた. ただ, CGI を glue として使うのは面倒が多い. 大半のサイト は Web ブラウザという高機能クライアントと強く結合しているため, その繋ぎ目に glue をねじこもうとすると難儀する.

SOAP や REST のような web service の仕組みはサーバ側でこの結合を緩める. web service の恩恵をうけた glue の例はたとえば Amazon WS を使ったオンライン書店. RSS も同じようにサーバ側でブラウザヘの coupling を緩める. 簡単なスキーマの XML でコンテンツを公開することによってサバとブラウザの中間でのコンテンツ加工はしやすくなる.

頑固者のサーバが相手の場合は client 側に細工が必要だ. IE コンポーネントを使った特定用途専用ブラウザ(airWeb, mixi ブラウザなど) や Mozilla の extension はこのアププローチ. ブラウザの助力を得られるこのアプローチは, 強力だが開発が面倒で気軽に作れない. 機能が制限されるかわりに開発やインストールの簡単な bookmarklet は, このクライアント側 glue の簡易版だと言える. 細かいことは気にせず, トライアンドエラーでさっと書こう.

参考サイト