preload
10月 31

(2009-01-03追記) ここで紹介している clearSB のソースコード一式は、GitHub へ移管されました。


clearSB – GitHub


====



clearsb
firefox の右上にある検索ボックスは、検索後にフォームに検索文字列が残るようになっている。で、後ろから忍び寄ってきた人や、ちょっと席を離れたとき(大体ロックしてるけど)にスクリーンを覗いてきた人に何を検索したか見られることがあったりする。

今のところ、特に恥ずかしい思いを過去にしたわけでもないのだが、勝手に消えてくれた方がなんとなく嬉しい。というわけで、そんな機能を firefox の拡張(addon)で作ってみようと思い立った。SearchBox を Clear するので、名付けて clearsb (クリアエスビー) です。

参考にしたサイトは、次の通り。とくにまとめサイトの方がわかりやすかった。

できあがった拡張(xpi)はこちらの trac に置いておいたので、ご興味の方、どうぞお持ちください。win 環境 Firefox 2.0.0.3 と linux 環境 Firefox 2.0.0.6 で動作確認済です。初めて作ったものなので、何かと行き届かない部分はご容赦を。あ、あと、最低限のコードしかしてないので、環境によっては変な動きをするかもしれません。ご利用は自己責任でお願いします。

以下、作業メモ。xpi の実体は zip 圧縮されたファイルなので、伸張すれば、簡単に解読できますです。

まず、ディレクトリ構成は、こんな感じ。

<拡張を作るディレクトリのルート>$ ls -R
.:
chrome  chrome.manifest install.rdf
./chrome:
content  skin
./chrome/content:
about.xul  clearsb.js  clearsb.xul
./chrome/skin:
clearsb.png  clearsbb.png

上記の通り、ごく少量のファイルで拡張が作れてしまうわけだが、自作のアイコンや about 用のダイアログが不要であれば、用意するファイルは、次の 4 つだけで良い。

  • インストール方法を記述した /install.rdf
  • 配布ファイルの構成を記述した /chrome.manifest
  • 拡張の UI への割当て方法を記述した /chrome/content/clearsb.xul
  • 拡張の実処理を記述した /chrome/content/clearsb.js

これだけだから、中身も全部貼っておく。

/install.rdf

なお、em:id(GUID) はオンラインサービスで取得してきた。

/chrome.manifest

overlay chrome://browser/content/browser.xul chrome://clearsb/content/clearsb.xul
content clearsb jar:chrome/clearsb.jar!/content/
skin clearsb default jar:chrome/clearsb.jar!/skin/

拡張の実体とスキンがどこにあるかを規定するファイル。chrome:// で始まる URI を適宜書き換えれば、ディレクトリ構成はわりと自由にいけるぽい。

/chrome/content/clearsb.xul

ここで処理の実体である clearsb.js を指定している。

/chrome/content/clearsb.xuljs

var ClearSB = {
// 初期化処理 .. サーチアクションにリスナをバインドする
init: function() {
sb = document.getElementById("searchbar");
eval("sb.handleSearchCommand = " + sb.handleSearchCommand.toString().replace(
"{", "{ ClearSB.onSearch();"));
},
// サーチアクション時に呼ばれる
// 検索エンジンに文字列が渡るよう、1msec 後にフォームをクリアする
onSearch: function() {
setTimeout(function() {
sb = document.getElementById("searchbar");
sb._textbox.textValue = "";
}, 1);
}
};
// 起動時に上記のオブジェクトをロードする
window.addEventListener("load", ClearSB.init, false);

実は、肝心のこれだけのコードの書き方がなかなかわからなかった。まず、検索ボックスの検索アクションに function を bind させる方法がわからず(上記 init で eval してるところ)、次に、検索時に検索ボックスを空にすると検索エンジンに検索文字列が渡らないという現象に悩まされた(上記 onSearch で setTimeout してるところ)。
結局、いろいろ探しているうちに、userchrome.js の上で同じ事をやってる人を、こちらのブログのコメント欄で発見し、これを頼りに上記のように書いてみたら、ようやくうまくいった感じ。この辺のプロパティ名とかがオフィシャルにまとめてあるところが見付からないんだな.. どなたかご存知でしたら教えてください。

最後に、上述のまとめサイトのチュートリアルに掲載されているパッケージング用のスクリプトを用意して、こいつを PATH の通ったところに突っ込む。(今回の構成用に、微妙にファイル名をカスタマイズしてあります。)

#!/bin/sh -f
x=$(pwd)
x=${x##*/}
rm $x.xpi
# archive chrome (non-compressed)
mkdir -p build/chrome
cd chrome
find . ! -name '.*' -exec zip -0 ../build/chrome/$x.jar {} ;
cd ..
cp -f chrome.manifest install.rdf build
# archive whole (compressed)
cd build
find . ! -name . -exec zip -9 ../$x.xpi {} ;
cd ..
rm -rf build

これを、冒頭に書いたルートのディレクトリから実行してやると、xpi ができあがり。簡単な事をやりたかった割には時間がかかってしまった。でも JavaScript でブラウザを拡張していくという感覚は面白いな。乗り遅れたな..

あ、最後に、アイコン作ってくれた嫁さんと、動作確認に協力してくれた(「小倉智昭」を検索しっぱなしで恥ずかしい思いをしたばかりの) 弟に感謝。

(2007/10/31 18:59 追記) clearsb.js のファイル名表記が間違ってたので直しました。

Tagged with:
8月 20

lighttpd-1.4.15 + FastCGI な環境で、HTTP リクエストヘッダ中の任意のレコードを参照して、その内容によって環境変数を設定しておきたい、という状況。apache であれば SetEnvIf ディレクティブを使うことで、

SetEnvIf X-Hoge ".*" bar=yes

とかで設定できるが、lighttpdではこういう事ができないらしい。lighttpdの設定でSetEnvIfのような条件分岐処理を書こうとすると、マニュアルのConditional Configurationによれば、

<field> <operator> <value> {

}

てな記述ができるのだが、<field> に指定できるのは $HTTP や $SERVER で参照できる、固定の変数だけのようだ。$HTTP["X-Hoge"] とか試しに指定してみたけど、syntax error で無視されてしまう。

デフォルト提供されている mod_xx や設定方法のマニュアルを当たってみたり、あと、騙し騙し使えそうな設定方法を試行してみたが、どれもダメだった。mod_setenv を使えば環境変数の設定まではできるんだが、その手前の条件記法を拡張する事ができないみたいだ。

ということで、lighttpd の拡張プラグインを書いてみようと思い立った。条件記法の拡張まではできなさそう(うまくやればできるかも??)だが、デフォルト指定できない特定のHTTPリクエストヘッダを参照し、環境変数を設定する、というだけであれば、プラグインで書けそうだ。ということで、プラグイン作成の作業メモ。

Writing Plugins のページに簡単なチュートリアルがあるので、これに従ってプラグインを書いてみた。プラグインの作成ステップは、大きく以下のような感じ。

  1. mod_skeleton.c からスケルトンコードのコピー
  2. スケルトンコードを書き換える形でプラグインを記述
  3. プラグインの Makefile.am の修正
  4. プラグインの make .. Makefile.in の生成と作成したプラグインのプリコンパイル
  5. lighttpd の configure .. –enable-maintainer-mode で Makefile を書き換える
  6. lighttpd の make && make install

本家のチュートリアルには上記 (4) のステップが抜けているので注意((6) も抜けてるけど)。あと、プラグインの作成には lighttpd の動作モデルと、プラグインがフックできるインタフェース(エントリポイント)の概要くらいは見ておく必要があると思う。

プラグインソースコードの構成(レイアウト)は、上記のチュートリアルを参照。意訳すると、

  1. プラグイン処理中で使用する構造体の定義
  2. 構造体の初期化処理
  3. configuration file の parse 処理 .. set_defaults
  4. プラグインが条件分岐中で適用された場合の処理 .. patch-function
  5. プラグインのメイン処理
  6. プラグインの初期化処理(プラグイン中で定義した関数と、プラグインインタフェースとの対応はここで定義する) .. plugin_init

こんなかんじの構成になっている。

あとは、既存のプラグインソースなどを参考に、プラグインを書いていく。今回書いたプラグイン mod_addenv.c は、こんなかんじ。

#include 
#include 
#include "base.h"
#include "log.h"
#include "buffer.h"
#include "plugin.h"
/* plugin config for all request/connections */
typedef struct {
int handled;
} handler_ctx;
typedef struct {
array *match;
} plugin_config;
typedef struct {
PLUGIN_DATA;
plugin_config **config_storage;
plugin_config conf;
} plugin_data;
static handler_ctx * handler_ctx_init() {
handler_ctx * hctx;
hctx = calloc(1, sizeof(*hctx));
hctx->handled = 0;
return hctx;
}
static void handler_ctx_free(handler_ctx *hctx) {
free(hctx);
}
/* init the plugin data */
INIT_FUNC(mod_addenv_init) {
plugin_data *p;
p = calloc(1, sizeof(*p));
return p;
}
/* detroy the plugin data */
FREE_FUNC(mod_addenv_free) {
plugin_data *p = p_d;
UNUSED(srv);
if (!p) return HANDLER_GO_ON;
if (p->config_storage) {
size_t i;
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s = p->config_storage[i];
array_free(s->match);
free(s);
}
free(p->config_storage);
}
free(p);
return HANDLER_GO_ON;
}
/* handle plugin config and check values */
SETDEFAULTS_FUNC(mod_addenv_set_defaults) {
plugin_data *p = p_d;
size_t i = 0;
config_values_t cv[] = {
{ "addenv.env", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },       /* 0 */
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
if (!p) return HANDLER_ERROR;
p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s;
s = calloc(1, sizeof(plugin_config));
s->match    = array_init();
cv[0].destination = s->match;
p->config_storage[i] = s;
if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
return HANDLER_ERROR;
}
}
return HANDLER_GO_ON;
}
#define PATCH(x) 
p->conf.x = s->x;
static int mod_addenv_patch_connection(server *srv, connection *con, plugin_data *p) {
size_t i, j;
plugin_config *s = p->config_storage[0];
PATCH(match);
/* skip the first, the global context */
for (i = 1; i < srv->config_context->used; i++) {
data_config *dc = (data_config *)srv->config_context->data[i];
s = p->config_storage[i];
/* condition didn't match */
if (!config_check_cond(srv, con, dc)) continue;
/* merge config */
for (j = 0; j < dc->value->used; j++) {
data_unset *du = dc->value->data[j];
if (buffer_is_equal_string(du->key, CONST_STR_LEN("addenv.env"))) {
PATCH(match);
}
}
}
return 0;
}
#undef PATCH
URIHANDLER_FUNC(mod_addenv_uri_handler) {
plugin_data *p = p_d;
size_t i, j;
handler_ctx *hctx;
if (con->plugin_ctx[p->id]) {
hctx = con->plugin_ctx[p->id];
} else {
hctx = handler_ctx_init();
con->plugin_ctx[p->id] = hctx;
}
if (hctx->handled) {
return HANDLER_GO_ON;
}
hctx->handled = 1;
mod_addenv_patch_connection(srv, con, p);
for (i = 0; i < con->request.headers->used; i++) {
data_string *dsh;
dsh = (data_string *)con->request.headers->data[i];
if(dsh->key->used && dsh->value->used && (0 == strcasecmp(dsh->key->ptr, "X-HOGE"))) {
for (j = 0; j < p->conf.match->used; j++) {
data_string *ds = (data_string *)p->conf.match->data[j];
data_string *ds_dst;
if (NULL == (ds_dst = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) {
ds_dst = data_string_init();
}
buffer_copy_string_buffer(ds_dst->key, ds->key);
buffer_copy_string_buffer(ds_dst->value, ds->value);
array_insert_unique(con->environment, (data_unset *)ds_dst);
}
}
}
/* not found */
return HANDLER_GO_ON;
}
REQUESTDONE_FUNC(mod_addenv_reset) {
plugin_data *p = p_d;
UNUSED(srv);
if (con->plugin_ctx[p->id]) {
handler_ctx_free(con->plugin_ctx[p->id]);
con->plugin_ctx[p->id] = NULL;
}
return HANDLER_GO_ON;
}
/* this function is called at dlopen() time and inits the callbacks */
int mod_addenv_plugin_init(plugin *p) {
p->version     = LIGHTTPD_VERSION_ID;
p->name        = buffer_init_string("addenv");
p->init        = mod_addenv_init;
p->handle_uri_clean  = mod_addenv_uri_handler;
p->set_defaults  = mod_addenv_set_defaults;
p->cleanup     = mod_addenv_free;
p->handle_request_done = mod_addenv_reset;
p->data        = NULL;
return 0;
}

このソースを mod_addenv.c として保存し、lighttpd-1.4.15/src 以下で make したあと、./configure –enable-maintainer-mode && make && make install する。既存の環境がある場合は、テスト用に –prefix 指定して違うところに入れた方がいいかも。あとは lighttpd.conf で、

server.modules = ( "mod_addenv" )
addenv.env = (
 "bar" => "yes"
)

とか設定しておけば、lighttpd でも上述の SetEnvIf のような効果が得られる。X-HOGE をソース中に書いているのがダサいな。なんとかしたい。

ところで、プラグインの記述方法は、ちょっと癖があるようにも思えたが、こういうモノなのだろうか。まあ、一度やってみておいて損は無いかな、とはおもった。他にもいろいろ使い道ありそうだし。

Tagged with: