Lobo Browser 用プラグインの作り方

Pure Java なウェブブラウザのひとつに、Lobo Browser がある。The Lobo Project で開発されていて、オープンソース

The Lobo Project
http://lobobrowser.org/

Lobo Browser での HTML 解析・表示には、同じプロジェクトで開発されている Cobra HTML Toolkit というライブラリが使用されていて、CSSJavaScript にもある程度対応している。
また、Lobo Browser にはプラグイン API が用意されている。だいぶ前に、この API を使って簡単なプラグインを作ってみたので、その時やったことをメモしておく。
ただし、最後に書くように、Lobo Browser 自体の成熟度が低いため、Firefox みたいなものを想像してはいけませんよ。

何ができるの

プラグインの作り方については、公式サイト内に説明がある。

Lobo Browser Plugin HOWTO
http://lobobrowser.org/browser/plugin.jsp

これによると、プラグインでできることは、大きく分けて以下の 4 つ。

ブラウザウィンドウをいじる
ウィンドウ内にメニューやボタンを追加したり、既存のものを変更したりできる。
新しいコンテントタイプへの対応
HTML 以外のコンテントタイプに対するハンドリングを追加して、ブラウザ内で表示させることができる。
新しい URL スキームへの対応
http とか ftp とか以外のスキームを定義することができる。
イベント
ブラウザ上での操作やエラー発生について、通知を受け取ることができる。

上記の説明ページから、Lobo Clientlet and Extensions API と書いてあるリンクを選択すると、プラグインAPIドキュメンテーションを読むことができる。
そして、読んでみると「いろいろ足りないような……」という気分に苛まれる。建前的には、「プラグインAPI にしか依存しないようにすれば、Lobo 以外のブラウザにも対応可能」ということらしいんだけど、実際には「Lobo や Cobra のクラスを参照しないとまともに作れない」ということが多くなりそう。もっとも、プラグインAPI 自体もたぶん他では使われてないんで、どうでもいいんだけど。

NavigatorExtension の実装クラスを作る

一番簡単そうなところってことで、Lobo で開いているページを、システムデフォルトのブラウザで開きなおすためのプラグインを作ってみた時のをメモしておく。
Lobo でプラグインを作るには、ブラウザ本体からのコールバックを受け取るために、org.lobobrowser.ua.NavigatorExtension を実装したクラスを用意する。「初期化」「終了」「ウィンドウを開く」「ウィンドウを閉じる」の各タイミングで、それぞれ対応するメソッドが呼ばれる。必要に応じて、ウィンドウをいじるとかイベントハンドラを追加するとかしたらいい好きにしたらいい。
今回は、新規ウィンドウが開くタイミングで、Tools メニューの下に「デフォルトブラウザで開きなおす」という項目を追加することにした。

package net.toyfish.lobo.openalt;

import javax.swing.JMenu;

import org.lobobrowser.ua.NavigatorExtension;
import org.lobobrowser.ua.NavigatorExtensionContext;
import org.lobobrowser.ua.NavigatorWindow;

/**
 * 表示中のページをデフォルトブラウザで開きなおす機能を追加します。
 * @author sardine
 */
public class OpenAlt implements NavigatorExtension {
	@Override
	public void init(NavigatorExtensionContext context) {
	}
	
	@Override
	public void destroy() {
	}

	@Override
	public void windowOpening(NavigatorWindow window) {
		JMenu tools = window.getMenu("lobo.tools");
		tools.addSeparator();
		tools.add(new OpenAltAction(window));
	}

	@Override
	public void windowClosing(NavigatorWindow window) {
	}
}

ここでのポイントは、windowOpening() で引数に渡された NavigatorWindow オブジェクトを (コンストラクタ経由で) 取っておいてるあたり。NavigatorWindow は GUI コンポーネントとしてウィンドウじゃなくて、ブラウザウィンドウと通信するためのファサードみたいなやつなので、後から GUI の階層をたどって取ろうとしても取れない! 取りにくい!

NavigatorWindow を利用する

メニューが選択されたら、先ほど取っておいた NavigatorWindow を使って、表示中の URL を取得する。
細かく分けると、まず、ヒストリ上の 1 コマ分を表す NavigationEntry を NavigatorWindow から取得する。ひとつの NavigationEntry は 1 つ以上のフレームで構成され、こちらは NavigatorFrame で表現される。

package net.toyfish.lobo.openalt;

import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import javax.swing.AbstractAction;

import org.lobobrowser.ua.NavigationEntry;
import org.lobobrowser.ua.NavigatorWindow;

public class OpenAltAction extends AbstractAction {
	private NavigatorWindow window;
	
	public OpenAltAction(NavigatorWindow window) {
		super("デフォルトブラウザで開きなおす");
		this.window = window;
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		NavigationEntry entry = window.getCurrentNavigationEntry();
		if (entry != null) {
			final URL url = entry.getUrl();
			NavigatorFrame f = window.getTopFrame();
			f.invokeLater(new Runnable() {
				public void run() {
					URI uri;
					try {
						String s = url.toString();
						if (s.startsWith("file:")) {
							//パスに空白を含むfile: URLだと
							//URL#toURI()に失敗するので……
							File f = new File(s.substring(5));
							uri = f.toURI();
						} else {
							uri = url.toURI();
						}
					} catch (URISyntaxException e1) {
						e1.printStackTrace();
						return;
					}
						
					try {
						Desktop.getDesktop().browse(uri);
					} catch (IOException e1) {
						e1.printStackTrace();
					}
				}
			});
		}
	}
}

途中でコメントを入れている、file: URL の場合についてちょっと補足。
ローカルファイルを開くときに Lobo は「"file:" + ファイルのフルパス」で URL を作るんだけど、パスに空白を含んでいてもとくにエスケープしてくれない。この状態で java.net.URL#toURI() を呼び出すと例外が発生する。
今回は java.awt.Desktop#browser() に java.net.URI を渡す必要があるので、file: の後ろの部分だけを取り出した後、java.io.File から java.net.URI を取得している。
本来はオープンソースなんだからパッチ作るべきだけど、今回はプラグイン側で暫定対処。

アーカイブ

以上で Java での実装は終わり。あとは jar ファイルにまとめればいいんだけど、クラスに加えて、jar 内のルートディレクトリに lobo-extension.properties という名前のファイルを入れておく必要がある。

extension.name=OpenAlt Extension
extension.description=Adds OpenAlt button.                        
extension.by=sardine
extension.version=0.1  
extension.class=net.toyfish.lobo.openalt.OpenAlt
extension.priority=4

このファイルが微妙にクセモノ。最初、公式サイトの例を見ながら作ったら、まともに読み込まれなかった。原因がよくわからなかったので、既存のプラグインからコピーしたのを書き換えて使ったらうまくいった。
そういうわけで、priority の 4 って何よ? と聞かれてもわかりません。

配備

できたら jar ファイルは、Lobo インストール先の ext ディレクトリに入れれば、起動時に認識される。
クラスや lobo-extension.properties に何か間違いがある場合、ここでエラーになる。そして、あろうことか Lobo 自体が起動に失敗して、そのまま何も言わずに終了する。
一応、例外が起きたら標準出力に出力されることになってるんだけど、初期化タイミングか何かの問題 (詳細忘れた) で、例外が起きても何も出力されないことがある。

感想

今回書いたのは僕が 2 つ目に作ったやつで、最初に作ったのは、PageUp/PageDown/Home/End でページをスクロールできるようにするプラグインだった。裏を返すと、デフォルトの Lobo でこれらのキーを押しても何も起こらないということ。現状の Lobo はまだまだ改善の余地が多すぎる。
上の方でも書いたようにプラグインAPI が最小限しかないので、キー押下を補足するには Lobo/Cobra 内部のクラスをさわらなければならない。結局その後 Lobo を使うのをやめてしまったんだけど、Firefox とか Opera とかみたいなすでに成熟したブラウザに近づこうとすると、かなり Lobo べったりになると思う。