Google Drive

0.5.320 をリリースした

ところで wasavi が対応しているオンラインストレージは今のところ dropbox だけであるが、Google Drive と Microsoft Skydrive に対応する気がないこともない。対応しないままずるずる来ているのは、つまり 1 年ほど前、これらのサイトは試験的に、認証・承認に OAuth 2.0 を使っていたわけなのだが、その当時は OAuth 2.0 はまだ仕様が流動的だったのだ。

現在、OAuth 2.0 自体の規格はまだ正式に固まっているわけではないみたいだけど、google drive、skydrive ともに OAuth 2.0 での認証・承認が普通に行われるようになって久しいようなので、ぼちぼち対応し始めてもいい頃合いかもしれない。

OAuth 1.0 では、jsOAuth というライブラリを使わせていただいたのだが、2.0 はもうちょっとシンプルな仕様になっている。そこで、もっと薄いライブラリを自前で用意するか、あるいは極端に言えば、直接 xhr をいじる方向でもいけるような気がする。

ちなみに dropbox はいつ OAuth 2.0 に対応するのだろうか。1 年前から「将来は 2.0 に対応するかも!」みたいなアナウンスはあったけど未だに実際の行動がなされていないまま今に至っているような。

completion #4

ファイル名の補完もできるようにした。ファイル名は dropbox へのネットワークアクセスが発生するため、「間」が発生するのは避けられない。補完が非同期になる場合はこんな感じで「補完してますよー」的なものが出る:
completion1
補完が完了すると何項目のうちの何番目ですよ的なものが出る:
completion2

completion #3

set コマンドについて、オプション名を補完できるようにもした。また、オプション名の略語を展開するようにもした。つまり set ai と打って tab を押すと、set autoindent に展開される。また、shift+tab で補完リストを逆方向に辿れるようにした。

これで、補完対象となる 3 種のうち 2 つまでを作り終えた。残るのは dropbox のようなストレージ上のファイル名の補完だ。

この手の補完は、他の 2 つとは決定的に違う点があって、つまり補完リストを得るためにネットワークアクセスが発生するのである。そうすると、アクセスの頻度とか、遅延とか、ワーストケースを考えつつ組まなければいけない。

まずアクセス頻度を抑えるために補完リストは最低数分間はキャッシュするようにしよう。また遅延は、たとえばアクセスして 15 秒経過しても反応がない場合は中断するような監視タイマを設け、中断時は古いキャッシュを使いまわすような仕組みにすればいいんじゃないかな!

completion #2

まずは ex コマンド名の補完に絞って作ってみる。

基本的な考え方は、入力行が ^\s*([^”\S]*).*$ にマッチし、カーソルが \1 の中にある場合、つまり ex コマンド名の入力途中であるとみなされる場合、tab の押下により \1 の範囲内を補完し置き換えるようにすればいい。

しかしながら、これは本当に核の部分の基本的な仕組みであって、いろいろと考えることはある。

  • この正規表現はアドレスの指定を考慮していない。アドレス指定の文法はかなり複雑 なので、補完機能の中にパーサを入れるのはちょっと無駄だ。ex コマンド実行部のパーサを流用できるようにしたい。
  • ex コマンドは | で連結することができる。したがって、補完は行頭の場合のほか、| の直後にも行われないといけない。ただし、/ の引数やアドレス指定内のリテラル内での | 、および ” 以降のコメント内の | は ex コマンドの区切りではないので、補完の開始トリガーとみなしてはならない。これもやはり、ex コマンドパーサの結果を使用しないと正しい動作を行うことができない。

というわけで、ex コマンド実行部にまず拡張を施し、純粋にパースだけを行うことができるようにした。その結果を元に補完を行うかを判断することになる。

また、補完処理は補完の準備が整っていることを確認した後に非同期的に行うようにしないといけない。コマンド名のリストであれば同期的な処理で構わないが、dropbox 上のファイルパスの補完などを行うことを考えると非同期にならざるを得ない。

というわけで作ってみた。

  • 補完対象を置き換える範囲をどうするか? 上記の正規表現の \1 の部分を置き換えるようにすると、
    ver#foo
    みたいなのがあったとき(# はカーソルを示す)、補完後の文字列は例えば version とかになる。つまりカーソルの後ろの領域も置き換えられる。一方、vim では置き換えるのはカーソル位置までなのである。つまり versionfoo とかになる。どちらがいいか。

completion

: で ex コマンドの入力モードに入るが、この時にさまざまな補完機能を作るのを後回しにしていた。それを作る。

補完はいくつかのタイプに分けられる。

  • ex コマンド名の補完
  • file, edit, write, read コマンドの引数としてのファイル名の補完
  • set 時のオプション名の補完

これらの区別は、入力済みの ex コマンド文字列とカーソルの位置がそれぞれの形式にマッチするかということになるのだけど、ex コマンドは | で連結できる上、コマンドによっては | がその効力を失うものもある。その辺の判定もちゃんとやる必要がある。

また、ファイル名の補完というのはたとえば dropbox をバックエンドにするとして、バックグラウンド側に処理を投げ、dropbox の API を呼んで指定のパスのファイル一覧を得て…みたいなことをする必要がある。このへんの動きは非同期になる。

Chromebook

wasaviについての評価とか、要望とか、バグ報告というものは

  • github の issues
  • Chrome Web Store
  • Opera Addons
  • Mozilla Addons
  • ここのサーバのフォーラム
  • メール

辺りの窓口がある。それぞれぽつぽつとコンタクトがある。

その内のバグ報告に y/d/c 辺りのオペレーションが . コマンドの繰り返し対象にならない、というのがあって直したことがある。といってもそのバグ自体は別によくて、興味深いのは、報告してくれた方が Chromebook のユーザーのようであることなのだ。

むう… Chromebook …聞いたことがある……。

聞いたことはあるが、全体像がよくわからない代物ではある。そもそも日本国内では発売されていない。また Chrome OS は基本的にブラウザしか動かせず、すべてのアプリケーションは html と javascript(と flash と NaCL?)で作られるとかなんとか。ベースは Ubuntu らしいけど、ターミナルとかも開けないのかな? ぜんぜんわからないことばかりだ。

わからないが、本当にブラウザしか動かないのだとしたら、テキストエディタを使いたいけどメモ帳的なそれは使いにくいよー! vi とかemacs とか使いたいよー! といった向きには確かに wasavi は向いているかもしれない(そこで vi 云々を発想する人はそもそも Chrome OS の対象外のような気もするが)。

content-editable #2

次に書き込む場合の問題を考える。

wasavi からの書き込みがリクエストされた時点で、対象の要素が input か textarea であれば、value 属性を通してテキストを書き込むだけでいい。しかしそれ以外の要素ならば、DOM のメソッドを使ってツリー構造を構築しないといけない。

input/textarea 以外の要素に対して wasavi が起動したならば、wasavi 側もそれを認識して、出力は改行で区切られた文字列の配列の形式になっている。ツリーがどういう構造であるべきかはサイトに依存するような気もするけど、とりあえずそれを単に取り出して p 要素の内容にしていくことにする。

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 に関してはそれぞれ上記のような動きをさせることにした。