Emacsには xwidget-webkit
という機能がある。これは端的にいうとWebブラウザの機能で、Emacsのバッファ上にWebブラウザを配置できる。EmacsのWebブラウザは他にも eww
などあるけれど、こちらはどちらかというテキストベースのWebブラウザで、JavaScriptエンジンもないし、レンダリングエンジンも自前の独自実装だったりする。 xwidget-webkit
は webkit
と名前に入っている通り、 Webkit
をEmacsのバッファに Xwidget
として組み込む。その正体は本物の Webkit
であるため、レンダリングエンジンやJavaScriptエンジンも Webkit
に内蔵されているものを使う。例えばGoogle ChromeやSafariのようなWebブラウザと同じように動作する。
Google ChromeやSafariのようなWebブラウザにはWebkit以外の所で実装されている機能があるが、 xwidget-webkit
にはそれらが無いし、セキュリティ的な問題で不安になるような点もあるので、インターネット上のリソースを手放しで読み込む事はできない。しかし自分が管理するリソースを読み込んだり、検証用のツールとしては使える。今回はこの機能を少し使いながら、考え事をしてみようと思う。
テスト用に以下のファイルを作成する。
main.html
<html>
<body>
<h1 id="testing">Hello world!</h1>
</body>
</html>
このHTMLを xwidget-webkit
で表示するには xwidget-webkit-browse-url
を使う。
(xwidget-webkit-browse-url
(format "file://%s" (expand-file-name "./main.html")))
Emacsは *xwidget-webkit: *
というバッファを作成し、このHTMLをレンダリングした状態でそのバッファに表示する。 xwidget-webkit
はこの小さなWebブラウザを操作する為の関数をいくつか用意している。
ページを再読み込みするには xwidget-webkit-reload
を使う。
(xwidget-webkit-reload)
この式を評価すると *xwidget-webkit: *
バッファに表示されているWebページが再読み込みされる。他にも画面のズームを操作する xwidget-webkit-zoom-in
や xwidget-webkit-zoom-out
、画面をスクロールする xwidget-webkit-scroll-down
や xwidget-webkit-scroll-up
など実装されている。
eww
はJavaScriptエンジンを搭載していない。だから最近のフロントエンドの技術(ReactやVueのような)で実装されたフロントエンドは正しく表示できない。 xwidget-webkit
は Webkit
を利用しており、 Webkit
はJavaScriptエンジンを搭載しているから、JavaScriptは当然動くしReactやVueで書かれたフロントエンドも正しく表示できる。そうなるとEmacsからWebkitへJavaScriptを送って実行したい。 xwidget-webkit
は、その関数として xwidget-webkit-execute-script
を提供している。
先頭のh1タグの文字色を変更するJavaScriptを送って実行してみる。
(xwidget-webkit-execute-script
(xwidget-webkit-last-session)
"document.querySelector('h1').style.color = 'red';")
これを実行すると、先頭のh1タグの文字色が赤に変わる。
実行したJavaScriptの結果をEmacs側で取得したい事もある。JavaScriptで実装された関数を呼び出し、その戻り値をEmacs Lispで利用したい場合などがそうだ。 xwidget-webkit-execute-script
は第3引数にコールバック関数を渡す事ができる。このコールバック関数は、第2引数で渡したJavaScriptを評価した後、最後に評価された値を引数として呼び出される。
次のようなコールバック関数を考える。
(defun testing-callback-fn (args)
(setq testing-args args))
渡された引数を testing-args
にsetqするという単純な処理を行う。この関数を xwidget-webkit-execute-script
のコールバックとして指定する。処理を単純にするため、評価するJavaScriptは数字のリテラルを評価する。
(xwidget-webkit-execute-script
(xwidget-webkit-last-session)
"1;"
#'testing-callback-fn)
この式を評価してもnilが返される。ただし正常に実行できていれば、コールバック関数が呼び出され testing-args
に値が設定されている。確認してみよう。
testing-args
1.0
変換処理が入るため整数が浮動小数になってはいるが 1.0
が返却されている。
次に整数と文字列の要素からなるリストを評価してみよう。
(xwidget-webkit-execute-script
(xwidget-webkit-last-session)
"[1, 'aaa'];"
#'testing-callback-fn)
これは正常に完了する。JavaScriptのリストはEmacs Lispのvectorに変換される。
testing-args
[1.0 aaa]
結果だけを見るとaaaは一見シンボルに見えなくもないけれど、これは文字列だ。
(type-of (seq-elt testing-args 1))
string
JavaScriptのリストは評価できたが、Objectは評価できない。以下の式は何かのエラーを発する訳ではないけれど、コールバック関数は呼び出されない。
(xwidget-webkit-execute-script
(xwidget-webkit-last-session)
"{'aaa': 1, 'bbb': 2};"
#'testing-callback-fn)
もしJavaScriptのObjectをEmacs Lispに戻したいのなら JSON.stringify()
を使い文字列化すると良い。
(xwidget-webkit-execute-script
(xwidget-webkit-last-session)
"JSON.stringify({'aaa': 1, 'bbb': 2});"
#'testing-callback-fn)
この式は成功し、コールバックが呼び出される。
testing-args
{"aaa":1,"bbb":2}
型を確認すると文字列が設定されている。
(type-of testing-args)
string
この結果をEmacs Lispの json-read-from-string
に渡せば、JSON形式の文字列を連想リストに変換できるだろう。
(json-read-from-string testing-args)
((aaa . 1) (bbb . 2))
当然、自前で作成した関数も呼び出す事ができる。
(xwidget-webkit-execute-script
(xwidget-webkit-last-session)
"function func_yay() {
return 'YAY!!';
}
func_yay();
"
#'testing-callback-fn)
事前に func_yay
関数を定義し、それを呼び出す。 func_yay
関数は YAY!!
という文字列を返却するため、最終的に評価された結果は YAY!!
という文字列となり、それがコールバックに渡される。
testing-args
YAY!!
ここまで来ると、もうJavaScriptにできる事は概ねできるだろうと想像が付く。当然だがDOM操作も行える。h1タグを取得し、その内部の文字列を取得してみよう。
(xwidget-webkit-execute-script
(xwidget-webkit-last-session)
"document.querySelector('h1').textContent;"
#'testing-callback-fn)
評価すると Hello world!
という文字列が取得できる。
testing-args
Hello world!
気を付けないといけない事は、JavaScriptがエラーしても、それを補足する方法が無い事だ。送信しているのはJavaScriptの構文で記述した文字列であるため、例えば構文的に不正な状態で送信したとしても、 xwidget-webkit-execute-script
が例外を出力する訳でもなく、エラーメッセージがどこかに出力される訳でもない。Webkitのコンソールログには何かしら出力されているようにも思うけれど、それを取得する方法を見付ける事ができなかった。
またJavaScript側からEmacsを操作する方法もない。例えばJavaScriptでイベントリスナーを登録し、そのイベント発火時にEmacs Lispを呼び出すといった事はできない。逆にこれはできてしまうと、セキュリティ的に重大な問題になりそうでもある。
もし同様の事をしたいのであれば、JavaScript側のイベントリスナーではWebkitで管理しているオブジェクトの状態を変更するまでに留め、Emacs側からタイマーやアイドルタイマーを使い、その値をポーリングする事で似たような事を実現できると思う。
今回はEmacsのxwidget-webkitの機能について少し考えた。この機能の安全性を考えながら、用法容量を守って使っていきたいと思う。