content-editable

wasavi はアクティブな要素が textarea、または特定の type を持った input 要素であるときに起動させられるわけだが、これを contenteditable 属性が適切な値にセットされ、内容を編集できる状態の要素にも拡大したい。

最近はそういう要素がけっこう使われているのである。たとえば Twitter で書き込む時の textarea っぽいアレは contenteditable=”true” にした div 要素だ。

編集可能要素を wasavi の対象にする場合、読み書きの際にちょっとした問題が出てくる。textarea/input の value はプレーンなテキストだが、編集可能要素はその内容の形式がプレーンテキストとは限らない。というより、ほとんどの場合 Node なのだ。

まず読み込みのことを考える。編集可能要素が Node であるというのはつまり Node のツリー構造であり、子孫要素を持つことができて、それらがブロック要素だったりインライン要素だったりする。それを踏まえた上でプレーンテキストに変換しないといけない。textContent 属性はそれらをまったく考慮しないのだ。

 * * *

というわけで、その辺りを考慮しつつプレーンテキストに変換し、wasavi へ送るようにした。このあたりを参考にした(が、コードはかなり違うものなっている)。
twitter
次は書き込みのほう。

hotkeys

wasavi を起動するためのキーストロークを監視するために、各ページに小さなスクリプトを走らせ、そこであろうことか Node とか HTMLTextAreaElementとか HTMLInputElementとかの prototype が持つ addEventListener() と removeEventListener() をフックしている。これは、wasavi を起動するためのキーストロークを完全にページ上のスクリプトから隠すためだ。なかなかアクロバットである。

しかしこれは、特にキーボード周りのエクステンションを wasavi と同時に動かした場合にそれぞれが正しく動くのか、微妙だ。もしエクステンションシステムがもっと優れたショートカットキーの仕組みを提供してくれていれば、そっちを使ったほうが絶対にいい。

Firefox の Add-on SDK には hotkeys モジュールがある。エクステンションのバックグラウンドでストロークとそのハンドラを登録する。ハンドラ内で各タブ上のエージェントにメッセージを投げてやればいい。

Chrome には Commands API がある。これが、ちょっとまだよくわからない。コマンドは manifest に登録するのである。ふーん、え? じゃスクリプトで動的に再定義はできないの? しかしドキュメントは何も答えてくれない……。こちらも基本的な仕組みはバックグラウンドでハンドラを登録するという形になる。

Opera にはない。また、Opera 15 も Commands API はまだサポートしていない。

というわけでとりあえず Firefox の場合はエージェント上でキーストロークを監視するのではなく、hotkeys モジュールを使用するようにした。その副作用として、今まではモディファイアとして shift と ctrl だけを認識するようにしていたのが、alt/meta/accel キーも使用できるようになっている。それぞれ wasavi の設定ページでは、a/m/x と記述する。たとえば alt+meta+p なら <a-m-p> と書く。

* * *

ははあ、わかった。Commands API では、スクリプトから動的にキーストロークを再定義することはできない。その代わり、Chrome の拡張機能ページの最下部に「キーボード ショートカット」というリンクがあり、対話的にストロークを変更できる。

なるほどねー!

しかし Commands API を通したキーストロークはかなり制限されているようだ。どうも

  • モディファイアが最低 1 種必須。ここで言うモディファイアとは Ctrl または Alt。もしかしたらキーボードによっては Meta や Command も有効かもしれない。
  • モディファイアではないキーは、0 ~ 9 および A ~ Z のみ。

というような感じらしい。

つまり wasavi を起動させるための Ctrl+Enter とかは定義できない。……だめじゃん。

referencing contents inside extension

wasavi.js は以前、大きくなりすぎたので分割したことがあった。一方でバックグラウンドで動いていろいろな縁の下の力持ち役を担う background.js はそのままだった。それもやはり分割した。

* * *

wasavi が起動すると、その実体は iframe なのだが、その src はこのサーバを指している。つまり wasavi を起動したことが、期せずして Apache のログからわかってしまう。これははっきり言って不要な情報だ。別にそんな情報で統計をとったりしたくはない。

なんでそんなことをしているのかというと、Opera が悪い。もし iframe が、エクステンション内部のファイルを指すことができれば、この外部へのアクセスは不要である。ただし何でもかんでも参照できるとセキュリティ上の問題になりうるわけで、今日びのブラウザは基本的にはそういうアクセスは禁止している。

ただし、Chrome はホワイトリストに特定のファイルを登録することで、その制限を回避できる。Firefox では、Chrome のようなシステマチックな解決法は(まだ)ないけど、iframe の src を data スキームの文字列にした上で PageMod の判定部を注意深くそれに対応させることでなんとかできる。

一方、Opera は無理。いやエラーページを乗っ取ったりオレオレスキームの URL を開かせたりすればできないことはないんだけど、なんか、特定のバージョンの特定の動作に依存するようなすごく嫌な感じのハックになる。

というわけで、仕方なく、wasavi 起動時はここのサーバの html ファイルを参照するという動作を強いられていたのだった。

しかし Opera 12 はもう歴史上の、過去のブラウザである。Opera を気にすることなく、Chrome と Firefox、そしてもちろん Opera 15 に関してはそれぞれ上記のような動きをさせることにした。

Where do Opera go? #2

Opera 15 から、そのレンダリングエンジンは Blink に移行した、というよりアプリケーションの骨組みが Chromium ベースになった。これに従い、エクステンションの仕組みもまた Chromium のそれに準ずるようになった。Chromium エクステンションと同様の manifest.json を書き、Chromium が提供する API を使ってエクステンションを組み立てていくことになる。ただし API はサブセット(+ Opera 独自のもの)になっている。

どの程度のサブセットかは、Chromium が提供する API と、Opera 15+ の それ を参照のこと。

Opera 12 までのエクステンションは拡張子が .oex だったが、15+ では .nex になる。構造は Chrome の .crx とまったく同じ。Opera 15+ がサポートする API だけを使っているエクステンションなら、.crx のまま使えてしまうはずだ。

また、Opera のエクステンションカタログ(の開発者ページ)では登録済み oex に対する Convert 機能が追加されているようだ。これは何をするのかといえば、oex のアーカイブに oex の API をエミュレートするでっかいラッパーライブラリを付加して、全ファイルを含んだ zip を返してくれる。この oex エミュレーションライブラリがかなりの力作で、全体で 450KB くらいある。これを書いた人は本当によくがんばったと思う。

残念ながらというか幸いにというか、wasavi の場合はもともと Chrome でも動くようにしているので、そのライブラリを使うことはないのだが…。

あと、「一体 Opera は DOM3 Composition Events をいつ実装するの? 明日なの? 今年中なの? 今世紀中なの?」という諦め半分の心の叫びがタナボタ式に解決してしまったのが割というかかなり嬉しい。

Mysterious Color #2

とりあえず ace のソースなんかを見てみる。バッファへの編集は edit_session.js でやっていて、色分けのトークンの切り出しは background_tokenizer.js でやっている。切り出しの処理は setTimeout で分割される。

ソースを見た感じ、再解析は編集の行われた行以降? それでいいのかな。

 * * *

ところでスクリプティングやシンタックスハイライティングについて下調べしているわけだが、すぐ実装するわけではない。0.5 での目標は、まず安定して編集できるようにすること。textarea の拡張として常用できるようにすること。いろんな機能があるけど不安定なエディタなんて誰も使わないのだ。

その後でまず、モデルとビューを分離させたい。今はバッファはすなわち DOM の要素群であり、したがってビューを兼ねている。これはよくない。バッファは単に 1 行を要素にとる配列にし、ビューは見えている部分だけを描画させるようにしないと、数万行のテキストを編集する際にメモリ消費がひどいことになる(はず)。

 * * *

Webkit Opera のデスクトップ版っていつお披露目されるのかな。Opera が Webkit ベースになれば、つまり Composition Events ももれなくついてくるということなので、今やってるような気持ちの悪いエミュレーションはまったく要らなくなる。そうすればいろいろとコードをスッキリさせられる。かなり楽しみだ。

Mysterious Color

Syntax Hilighting のことを考えるのだが、いまいち実装のイメージが湧かない。ところで “Syntax” というけれど、巷のテキストエディタって文法的なものまで見ているものなんだろうか? あるいは、単にキーワードを識別しているだけ? まずそこからが分からない。それから、テキストの編集が行われると当然その行の解析をやり直すことになるのだけど、その行だけではなく、場合によっては編集した行の周辺の影響を受けたり与えたりする。むしろそのケースが殆んどかもしれない。その場合、再解析を行う行はカーソル行ではなく n 行遡った行、ということになるのだが。その n はどーいう理屈で求めるのか? それとも編集が行われるたび先頭行から解析し直すのだろうか。

vim に限って言えば、仕組みは :help :syntax すればだいたい分かる。しかし再解析行を指定できたりするのは自由度が高いといえば聞こえはいいが、実装の都合が見えすぎてる気も……。もしかしたら参考にすべき実装とはちょっと言えないのかもしれない。

もうちょっと色々なエディタのソースを見てみる必要があるかな。うーんめんどいなあ!

Interception

ふたたび github の issue から。

  • Chrome で、オプションページから設定を変更しても既存のタブ上の textarea に反映されない?
  • github の issue がまさにそうなのだが、wasavi を起動するためのショートカットキーが textarea に結び付けれたページ固有のイベントリスナと競合することがある

後者について考えてみる。textarea 上でキー入力があった時、それが wasavi を起動させるためのものかを判断するために、あらゆるページで agent.js という小さなスクリプトを走らせている。この中で [cci]window.addEventListener(‘keydown’, …)[/cci] としてそこで判断している。

一方で、ページに付属するスクリプトが textarea や window や document に addEventListener() する場合もある。複数のイベントリスナは登録順に実行される。ここで [cci]Ctrl+Enter[/cci] を特別なキーバインドとして認識する複数のイベントリスナがあったとき、競合が起こる。github の issue では wasavi の起動とコメントの送信が同時に行われてしまう。

実は開発の初期の段階では、agent.js で window と document の addEventListener() のフックというなかなかの荒業をやっていた。addEventListener() を乗っ取り、wasavi が起動中なら wasavi に関連しないキー入力イベントは無効にしてしまうのだ。しかしたしかに荒業すぎるので、取り外していた。

とはいえ、やはりリスナの競合の回避はそういう方法を使わない限り難しい。ということで復活させた。フックの対象は window/document ではなく window.Node.prototype.addEventListener() にした。つまりあらゆるノードが影響を受ける。荒業どころではなく傍若無人なレベルに達している気がするが……。いや問題なく動くのなら別に傍若無人だろうといいと思うけど、似たようなエクステンションと共存させたとき、うまく動くのかな、とちょっと心配ではある。

wasavi 0.5.293 released

リリースした。

変更点
  • 可能なら限定的にブラウザのスペルチェッカが働くようにした
  • コンテキストメニューから起動できるようにした
  • 編集済みフラグが undo の状態に追従するようにした
  • en のメッセージを contrib されたもので更新した
  • ja のメッセージを en に合わせて更新した
ダウンロード

wasavi for Firefox is available

1月19日に AMO へアップデートを申請した wasavi 0.5.281 が今日フルレビューを通った。まるまる3週間かかったことになる。AMO からの素敵なお便りメールを受信する設定にすると、

Most updates are being reviewed within 2 weeks.

などと高らかに謳っている自画自賛メールがやたら届くわけだが、それはあくまで平均の数値であって実際にはなかなかそう上手くは行っていない。

別に Mozilla のそういうスタンスがダメだというわけではない。Chrome のように機械的にチェックするだけとか、Opera のようにおめーそれ体裁しかみてねーだろ! といったスタンスに比べたら、時間がかかってもひと通りソースコードを見てもらえるというのは良いことだ。とても良いことだ。

それにしてもちょっとレビューに時間がかかり過ぎじゃないですかね……ということなのだ。Mozilla って別に爪に火灯すような財政状況でもないと思うんだけど、なんでレビュアーを増やさないのかな。

Scripting #7

とりあえずの取っ掛かりとしてスクリプトを動かす基本的な部分は作った。が、このアーティクルではスクリプティングについて特に書くことはない。github で要望や pull request が来ていたという話なのだ。

  • スペルチェッカがほしい
  • コンテキストメニューから wasavi を起動できるようにしてほしい

こんなところである。また、pull request の方はメッセージや readme を自然な英語に書き換えてもらった。これは嬉しい。

さてこの中で興味深いのは、スペルチェッカだと思う。ピュアな vi にはそんな機能はない。また、nvi にもない。vim はある。ちなみに elvis にもあるらしい。

といっても、同等の機能を javascript で実装するのはなかなか面倒だ。実装というか、辞書を用意するのが大変だ。そこで、単にブラウザの機能で入力用の textarea の spellcheck 属性を true にすることにする。とりあえず input モード時、間違った綴りの単語を区別することはできるようになる。Opera では間違った綴りの単語に赤線が引かれる。
wasavi_spell_checker
ただ、たとえば Opera の場合、間違った綴りの単語の上でコンテキストメニューを出すと正しい綴りの単語が提示されて、単語を置き換えたりできるのだが、wasavi は個々の要素に対するコンテキストメニューを封じているので、置換はできないのであった。本当にただ、綴りが間違っているかどうかを知ることができるだけである。また、input モードを抜けると赤線は消去される。

spellcheck 属性は、html5 で規定されている。したがって html5 対応と謳うブラウザはだいたいスペルチェック機能を持っていると考えてよい(必須の条件ではない)のだが、どうせブラウザがスペルチェック機能を内包するのなら、javascript の API も規定してくれればいいのになー。