Assign to a register

エディタを開いた時に特定のレジスタに常に任意の内容が入っているようにするには、vim では vimrc に

:let &z="foobar"

みたいにすると思う。この手のことは、wasavi ではまだできない(レジスタの内容自体は wasavi を閉じるときにエクステンションのバックエンドへ永続化されるので結果的に件のツイートの要件はすでに満たしている)。

できないというのは、vim の [cci]let[/cci] 文は、ex コマンドというよりは vimscript のいわゆる assignment statement で(ただしヘルプ上では expression command ということになっている)、wasavi にはまだスクリプティングの機能がないということだ。

もしその手の機能を設けるとしたら、ex コマンドに [cci]script[/cci] を新設して

:script wasavi.registers["z"] = "foobar";


:script << END wasavi.registers["z"] = "foobar"; END

な感じになると思う(スクリプトの文法がこうなるかどうかは未定だ)。vim のようにスクリプトの構造が ex コマンドのスーパーセットになっているような設計には、多分しない。

実は以前に script コマンドを実装しかけたことはあったのだが

  • wasavi の動作のうち、バックエンドにメッセージを送信して返事を待つ箇所は非同期だ。したがって、スクリプトも単にシーケンシャルに実行するわけにはいかない。つまり

    a = wasavi.registers["*"];
    print a;

    というようにはいかない。

    wasav.registers.get("*").then(function (content) {
    print a;
    });

    のような形式にせざるを得ない。すべてこの形式だとお気楽なスクリプトの範疇を超えている。同期か非同期かで文法を使い分けるとしてもそれも煩雑すぎる
  • スクリプトが例えば javascript であると定義するとそれを実行するのは eval 文になるが、それだとできることが多すぎる。何でもできすぎると動作の保証もセキュリティ面での安全性の保証もできない
  • 一方で完全に動作を制御可能なスクリプトインタープリタを実装し、その管理下でスクリプトを動かすとなるとコードサイズと実行速度の危険が危ない

などなど問題がありすぎてこりゃ時期尚早だということで棚上げになっている。どうしたものか。

Options page

前の記事で触れたように、かつては Chrome のエクステンションに関するオプションページは、1つのタブを消費するタイプだった。そのためのリファレンス

それが、最新のエクステンションの仕様では chrome://extensions のページにオーバーレイされる形で表示するようになっている。そのためのリファレンス。オプションページの構造自体は変わっていないのだが、表示される場所が違う。この場所の違いを吸収するため、[cci]chrome.runtime.openOptionsPage()[/cci] という API が新設されていて、manifest.json に記述されているオプションページの設定に従ってふさわしいそれを開くようになっている。

オーバーレイ表示といってもどうということはなく、要するに iframe みたいなものだ(実際には Shadow DOM や object 要素などを駆使したナウいものになっている)。ということは、やろうと思えば [cci]chrome.runtime.openOptionsPage()[/cci] を読んだ時、その時点のカレントタブにオーバーレイ表示することもできるはずだ。その方が無駄なタブ移動等々が少なくて混乱することはないのだが、実際はこの API を呼ぶと 必ず chrome://extensions を開いた上で(すでにそれを開いている場合はそれをアクティブにした上で)オーバーレイ表示する。これはまあきっと、偽装をさせないための制限なのだろう。

そういうわけで wasavi についても新しいオプションページで開くようにしてみたのだが、別にこれと言って新しくなったメリットはないわけで、リバートしてしまった。

wasavi and SSL

ペンディングになっていた件。つまり wasavi が参照する、appsweets.net 上のアドレスを http 化したいな、という件。そういったアドレスは 2 種類あって:

  • http://wasavi.appsweets.net/ – app モードのプレースホルダとなる URL
  • http://appsweets.net/authorized.html – オンラインストレージに接続した際のコールバック URL

これらを https 化するために、cloudflare の助けを借りることにした。cloudflare というのは CDN サービスだ。cloudflare 側にアカウントを作り諸々設定した上で appsweets.net ドメインに割り当てていたネームサーバを cloudflare が提供するそれに書き換える。そうすると appsweets.net を正引きすると今までは直接 xrea のサーバ s278 を指していたわけだが、cloudflare 内のサーバを指すようになる。このサーバがプロクシとして動作し、ブラウザ – cloudflare プロクシ – s278 という形でデータが流れる。このときメディアファイルについては cloudflare 側で適当にキャッシュされたりするのでその分 s278 側の転送量が抑えられる……等々のメリットがある。

そして SSL まわりだが、不思議なことに cloudflare に登録したドメインに SSL の証明書が即与えられる。従ってブラウザと cloudflare プロクシ間の接続については https でアクセスできるようになる。この時に使用するドメインはもちろん appsweets.net なので、結果的に appsweets.net が https でアクセスできるようになる。ちなみにここまで完全に無料である。なんか騙されたみたいな話だが、実際にこうなる。

そういうわけで、上記の 2 アドレスについても https を使えるようになる。

デメリットとしては appsweets.net の内容を cloudflare に 100% 預ける形で仲介させているので、悪意の有無を問わず、そちらで変な操作をされることが絶対にないとはいえない。SSL 化されるのはあくまでブラウザと cloudflare 間であって、cloudflare と appsweets.net 間は http でやりとりされる。従ってすごくセキュアというわけではない。ブラウザと cloudflare 間の SSL 接続はバーチャルホスト名ベースの割と新しい仕組み(SNI: Server Name Indication)を利用していて、古めの OS/ブラウザからはアクセスできない(たとえば、Presto Opera 12.17 ではアクセスできない)

Mysterious bound keyword

Firefox 46a だったか 47a だったか忘れたが、wasavi と赤福プラスが両方動かなくなったことがあって調べてみた所、

  1. 両方が使っている extension_wrapper.js 内で、それが Firefox + Add on SDK で動いているかどうかを判断している箇所があり
  2. Firefox のその該当バージョンで判断のもとになっている箇所が変更されたため

動かなくなっているということだった。

extension_wrapper.js は Chrome でも Opera でも Firefox でも共通して動くので、それぞれのブラウザのうちどれで動いているのかを判断しないといけない。Chrome なら window.chrome が存在しており、Opera なら window.opera が存在しているのでそれが判断の助けになる。。

一方 Add on SDK の PageMod における content script かどうか、というのを判断するためのあんしんあんぜんな方法はないように思える。とりあえずのところ、self.on が存在し、それが function であり、かつその toString() の値が “function on (.*?) { [native code] }” とかそんな感じになっていればまあたぶんきっと SDK ベースの content script だろう…というような判断をしている。”[native code]” がミソなのだが、これが最適解というわけでは全然ない。

これが、今回動かなくなってしまったのである。それは、self.on の toString() の値が “function bound on (” というように、謎のキーワード bound が入るようになっていたからだ。bound……って何?

wasavi and content type of a page

issue #128 と関連して。

discuz という PHP アプリケーションがある。これは要するに中国版 phpBB であって、phpBB 同様にいわゆるフォーラム機能を提供する。中国語圏内ではとても有名なのだそうだ。ざっと見た感じそれほど phpBB との機能差は見受けられなく、なんで似たようなものを一から作りなおす必要があるのかよく分からないが、まあそれは特に問題ではない。ちなみに disqus とは何の関連もない。

で、wasavi を有効にしていると discuz 上での post が上手くいかないというクレームが来たのである。それに対応してみたい。

まずダウンロードし、ローカルにインストールした。エンコーディングによって何種類かあるが、とりあえず繁体字・UTF-8版を持ってきて、適当なディレクトリに展開する。discuz はバックエンドとして MySQL を使用するので、あらかじめ適当なデータベース、それにアクセスするためのユーザアカウントとパスワードを作っておく。ブラウザから [cci]展開ディレクトリ/upload/install/[/cci] にアクセスし、適当に必要な項目(含データベース情報)を埋めつつウィザードを進めるとインストールが完了する。それにしても繁体字ならなんとなく雰囲気で読めるんじゃないかと期待したが、さっぱりわからないものです。登録、がログインのことなのだそうだ。そうなんだ…。

そんなわけでその環境で試してみると、たしかに上手くいかない。discuz はサーバへの post は ajax ベースであり、隠れた iframe を経由している。post の応答である XML ドキュメントを iframe に受けて、その結果が動作を左右する。ここで wasavi のエージェントが XML ドキュメントに対して余計な script 要素を追加してしまうと、Chrome がそれを XML とみなさなくなってしまい、XMLDocument プロパティが無効になってしまう(このへんは深くは追ってないけど)。その後 discuz 側が無効な XMLDocument プロパティを参照して例外で中断してしまう…という筋書きだ。つまりつきつめると XML 文書に対して wasavi のエージェントを動かしてしまうことがバグの原因だ。

これは正に issue #128 と関連している。前の記事では text/html だけではなく application/xml(あるいは、+xml を含むもの)に対しても実行したほうがいいのかなと思ったわけだが、今回のバグは実行してはダメなパターンなのである。

エージェントは wasavi を起動するために textarea などの HTML 要素を含む文書を対象としなければいけない。なので、単純に考えれば対象となる文書は text/html か、application/xhtml のいずれかだ。ただしもう一つ可能性があって、サーバからは xml が送られてくるが、ブラウザ上でそれを XSLT で html に変換するという構造のアプリケーションだ。この手のアプリケーションを、そういう構造だと判断する処理はかなりめんどくさい。

しかしそんなへんてこな構造のアプリケーション存在するんだろうかと考えると、うーんまあ、ないか。ないな。うんうん。ないない。

というわけでエージェントを実行すべき contentType は上記の通り html と xhtml だけにしよう。xhtml にしても生きてるのか死んでるのかよく分からない状態ではあるが…。

two issues

issue #124

Shadow DOM というとてもナウいテクノロジーがあるのだが、Shadow DOM に含まれる textarea に対して wasavi を起動できないという issue。

なぜ起動できないのか。textarea が Shadow DOM に含まれているとき、そこで発生したイベントのターゲットがその textarea ではなく、Shadow DOM のルート要素になってしまうからだ。wasavi を起動させる直前にエージェントはターゲットが textarea とか、input とか、それ系の要素であることを確認しているので、そこで弾かれる。

で、実際にイベントが発生した要素を得る方法はわからなかった。その代わり、ルート要素の activeElement を参照すれば、それが高い確率でキーボードイベントを実際に発生させた要素であるはずなので、それを利用するようにした。このとき Shadow DOM 内の要素が更に入れ子の Shadow DOM ルートである可能性もあるので、無限ループにする工夫が必要、らしい。このへんは issue で挙げられた POC コードを参考にした。

* * *

issue #128

Chrome で wasavi をインストールした状態だと、PDF ファイルを開けないという不思議なバグ。ファイルを開けず、ページは白紙のままになる。

これは wasavi 本体というよりはエージェントの問題なのだが、白紙のままになる原因自体は不明。Chrome で PDF を開くというのはつまり PDF Viewer という PPAPI プラグインが差し込まれて実行されるということなので、その内部でやっていることと何か競合しているように思える。とりあえずエージェント側では DOMContentLoaded が発生しない。

そもそも PDF ファイル上でエージェントを実行する必要自体がないので、agent.js の先頭で document.contentType を見て [cci]^text/[/cci] の時だけ実行するよう修正。これだと普通の html ページに見えて、実は content-type が text/* ではない不思議なページだと動かなくなるわけだが、まあそれはそのページのほうがおかしいですよね…? と思ったけど [cci]application/xml[/cci] なんかの場合はありえるのか。はてどうするか。

Match about about:blank

issue #123

それが何なのかまださっぱり理解していないのだけど、https://www.visualstudio.com/ というものがある。たぶん一言で言い表わせば、Microsoft 版の github ということなんだろうと思う。そして件の issue は、ここで作る issue に含まれる textarea(実際には iframe の content editable な div)で wasavi が起動しないというもの。

起動しない理由は明快だ。その iframe に src 属性が含まれておらず、要するに内容がすべて動的に構築されているタイプだからだ。特に Chrome のエクステンションの場合、コンテントスクリプトは manifest.json で表明した URL に対してのみアタッチされる。src 属性が与えられていない iframe 要素は、実質的にはその内容は about:blank のはずであるが、あくまでも src 属性は空なので、どのコンテントスクリプトも結び付けられない。

ちなみにググってみると、こういうやりとりがあって、manifest.json の content_scripts.match_about_blank を true にすると [cci][/cci] やそれに類する URL パターンが about:blank も含むようになるそうなので、そうした。

ところで件の Microsoft 版 github のページを見てみると、content editable div には editarea なんちゃらというクラス名が入っている。EditArea というのはそういうライブラリがあるのだけど、それなのだろうか。未確認。また、サンプルページ は上記 match_about_blank の修正を施した上でも wasavi が動作しない。

Editing rich text with wasavi #4

ぼちぼち wasavi に組み込み始めたいのだが、1つ考えることがある。

contenteditable な要素にどのようにテキストの各行を格納するかは、サイトによってまちまちでありおおよそ以下の種類がある:

  1. 段落を div で区切る
  2. 段落を p で区切る
  3. 段落自体はテキストノードであり、br で区切る
  4. テキスト全体がテキストノードであり、\n そのもので区切る

この他、もちろん今回対応している gmail のように、完全なリッチエディットコントロールとして扱うか、それとも Twitter のように多少書式付けられる textarea 要素の亜種として扱うかの別もある。

面倒なのは、どのサイトがどのタイプかを機械的に判断することはまったくできないということだ。リストを保持して、泥臭く判断するしかないのである。例えばすでに issue として挙げられた件では、workflowy で使用されている contentEditable な div 要素ではテキストを 3. のパターンで格納しているが、このサイトがこれをどこかで表明しているわけではまったくない。wasavi 側で勝手にうまく辻褄を合わせるしかない。

とりあえずそのリストは agent.js 内に定数の形で持っている。もしかしたら将来的には、オプションページでユーザーが編集できるようにするかもしれない。

* * *

というわけで組み込んだ。何がどうなったのか再度まとめてみよう。

1. contentEditable な要素を wasavi で編集する際は、要素の内容を markdown に変換するようになった

2. markdown ではあるが例外があり、wasavi 独自のタグが含まれることがある
img、a、object、embed については markdown ではなく、元の要素への ID を持つリンク要素として表現される。例えば以下のような:


3. wasavi で編集した内容を contentEditable な要素に書き戻す際、いくつかの方法がある
方法は以下の通り:

  • html – 内容を markdown とみなした上で html を構築する
  • div – 内容の各行を div 要素に変換する
  • textAndBreak – 内容の各行をテキストノードに変換し、br 要素で区切る
  • plaintext – 内容全体を単体のテキストノードに変換する

これらのいずれかがサイトに応じて自動的に決定される。

4. wasavi のオプション writeas が新設された
3. で選択された値が writeas オプションに設定され、set コマンドによりユーザーが別の値を上書きできる。

Editing rich text with wasavi #3

markdown-test

  • DOM ツリーから markdown への変換は、自前で書くようにした。この処理は agent.js に内包させる必要があるので、できるだけコンパクトではないと困るのである
  • 逆に markdown からマークアップされた文字列を得るのは、これはなかなか大仕事であるため、とりあえず marked を使ってみることにした。このライブラリはおそらくバックエンド側に保持させることになると思う。

    そんなわけで、一度 markdown に落としたものを再びマークアップしてみたのが上記の図。左から元の DOM ツリー、生成された markdown、再構築された DOM ツリー。だいたいいい感じなのだが、いくつか marked についていくつか気になることがある:

    • [cci][/cci] のような、内容を持たない空要素のつもりで書いたタグをそのように扱ってくれない。開始タグとみなしてしまう。これは[cci][/cci] と冗長に書けば解決する
    • 画像のとおり、連続してはいるが空行で区切っていることで別個のものとして書いたつもりのリストをまとめてしまう。これは隣り合うリストについては 2 つの空行で区切ることでまとめられないようなので、そうする
    • markdown 自身の仕様の問題なのだけど、u 要素に対応する記法がない

    その他、marked はパラグラフを p 要素に置換する。これ自体は正しいのだが gmail はパラグラフを div で表現するため、ひと工夫する必要がある。README.md で記述されている通り、marked ではレンダラー、つまり markdown を走査して得た各要素の情報を実際に HTML でマークアップされた文字列に変換する部分を独立させてあり、かつ自由に上書きすることができる。その仕組みによって、デフォルトで p 要素が出力されるケースを div で置き換えることは可能だ。また同じやり方で [cci]**…**[/cci] が strong 要素になるのを b 要素に、[cci]_…_[/cci] が em 要素になるのを i 要素に上書きすることも必要。