Everything becomes Blink

Microsoft Edge が Chrome と Firefox の両方の拡張をサポートするかもしれない? という噂について、そんなことができるのかなと訝しんだ記事を以前書いた。

実際のところは、こういうことのようだ。
http://rockridge.hatenablog.com/entry/2015/08/16/234729
https://wiki.mozilla.org/WebExtensions

なるほど。つまり Firefox 側で Blink の拡張のインターフェース互換のフレームワークを構築すると…。しかしそうすると Addon SDK の存在意義が根底から揺らいでしまう気が。それから、Mozilla 自身が XUL を捨てたがっているというもある。しかし、Firefox 本体や既存の拡張のシステムって相当 XUL と絡み合っているわけで、そちらの存在意義も揺らいでくるような。

もしかしたら、レンダリングエンジンを servo にスイッチするタイミングでそれらも一気にグレートリセットされるのかもしれない。いやー Firefox 向けに拡張を書く人は大変だなー(他人事)。

Synchronizing the settings #3

とは言ってみたものの、本当に大丈夫なんだろうか。

とりあえず wasavi のバックエンドが localStorage に保存するデータのキーを列挙してみよう。

  • version: 現在インストールされている wasavi のバージョン文字列
  • targets, exrc, shortcut, shortcutCode, fontFamily, fstab, quickActivation, qaBlacklist, logMode, sounds, soundVolume: オプションページから編集することのできる項目群
  • memo-*: body 要素に対して wasavi を起動した場合その内容は localStorage に対して読み書きされる。これを Memorandum と呼んでいて、localStorage 上の memo-* キーは各 URL ごとの Memorandum の内容を保持している。* の部分は URL の SHA-1 値
  • filesystem.*.tokens: 各クラウドストレージに接続した際のクレデンシャル
  • wasavi_lineinput_histories: 行入力モードで入力された値の履歴
  • wasavi_registers: レジスタの内容

こうしてみるとキャメルケースだったり [cci]-[/cci] で繋いでいたり [cci].[/cci] で繋いでいたり [cci]_[/cci] で繋いでいたりとてもアレだがまあそれはそれとして。ユーザーをスイッチした時、リセットされるべきものと触るべきでないものを明確にしたい。大体の区別は前者は Chrome にログインするアカウントに属する情報、後者はローカルマシンに属する情報ということだ。

まず version は明らかにローカルマシンに属する。これはいい。したがって同期されるべきではなく、かつユーザーがスイッチされたとしても変更されるべきでない。

次にオプションページの項目は当然ながら同期される対象であり、ユーザーがスイッチされた場合は新しいユーザが所持するデータのものに差し替えられるべきである。

ここまでは簡単だ。これ以降が難しい。

Memorandum はまだその仕様自体が固まっていない。とりあえず version と同様の扱いにしておこう。

クレデンシャル。クレデンシャルの情報はクリティカルであり同期で使いまわすのは怖い。一方でユーザースイッチの場合はどうなんだろう。クラウドストレージに対して受けた認証は Chrome のアカウントでもローカルマシンでもなく各ストレージのアカウントだ。ということは Chrome のアカウントがスイッチされたとしても触らないほうが良いのだろうか。なんとも判断できない。

行入力履歴とレジスタはまた異なる疑問点がある。使い勝手で考えると、これらが Chrome のデバイス間で共有されるのはまあまあ便利だと思う。しかしこれらの情報ってかなり頻繁に更新されるのだ。デバイス A で wasavi を起動し履歴とレジスタの内容を更新する。それを同期に載せる。デバイス B でも wasavi を起動する。同期イベントがやってくる。ローカルで更新した内容が上書きされる。そんな感じで不整合が簡単に起こりうる気がする。ということは実装の都合上同期しないほうがいいのかもしれない。ユーザースイッチの場合はどうか。これもなんとも判断できない。

…ということなのだが、いまいち確固たる理由付けによる区分けができない。

Synchronizing the settings #3

よくリファレンスを読んでみたところ Identify API に getProfileUserInfo() と onSignInChanged イベントがあり、もちろん前者は Chrome にログインしているユーザーの情報を得るために使い、後者はユーザーがスイッチされる時に発生するようだ。

よかったよかった。これを使えば大丈夫。

Synchronizing the settings #2

とりあえず Chrome の場合のみ、[cci]chrome.storage.sync[/cci] を通して同期された設定を参照するようにした。Presto Opera、Firefox では設定は同期されない。また Blink Opera では前述の API 自体は使えるのだが、今のところ同期しないので実質的に現状と変わらない。

ところで組んでいて気がついたのだけれど。

この同期されるデータというのが何に属しているのか。もちろん Chrome に入力する Google アカウントだ。一方で Chrome には任意のタイミングでこのアカウントをログアウトしたり、別のアカウントにスイッチするようなインターフェースが用意されている。

ということは、エクステンションからアカウントに属するストレージを参照できる以上、現在のアカウントのある程度の情報やアカウントがスイッチされたというイベントも受け取れることができないと辻褄が合わなくなる。

しかし探してみてもそういうものが見つからない。[cci]chrome.storage.onChanged.addListener[/cci] で内容の変化に対するイベントをリスンすることができるが、例えばアカウントをスイッチした時に何かイベントが発生するということはないようだ。現在のログイン状態を知ることができればまあまあなんとかできるかもしれないが、そういう API もない感じ。いっそ [cci]chrome://sync[/cci] をスクレイピングとか…? いやまさか。

これ、Wasavi に限らず storage API を使っているエクステンションに共通する問題だと思うんだけど。つまり Chrome にログインしたあと、各エクステンションのバックグラウンドをリロードするか、あるいは単に Chrome 全体を再起動しないと辻褄が合わなくなると思うんだけど。

しかし google 様がそんな仕様にするのだろうか。何か勘違いしているのかもしれない。もう少し調べてみよう。

Alternative NTW

Chrome で Ctrl-[ntw] を使うことができない件。

その代わりに Alt-[ntw] でも同じ機能を呼び出すようにした。Selenium でのテストもそちらを使用する。

ついでと言っては何だが、[cci]map[/cci] の左辺に指定するキーストロークで Alt を併用したものを受け付けるようにした。ただし完全ではなく、保証できるのは Alt-[a-z] だけ。この辺は DOM3 KeyboardEvent がフルに実装されればもうちょっとましになると思う(つまり、キーボードレイアウトに依らずにすべてのキーについて何が押されたかを正確に判断できる)のだけど、それはまあ 1 年後くらいかな…。このへんは何故か Chrome のほうが W3C の規格からずれている。

Presto Opera is gone

Selenium を 2.46.0 に更新したところ、2 点。

まず付属の jar から OperaDriver が消えていた。

つまり、そういうことなのだろう。

wasavi はまだ、とりあえずは Presto Opera でも動作するようにする。これはつまりできるだけ Chrome/Firefox/Opera とで共通して動くコードを書くのを心がけるということだ。一方で段階的に Presto Opera の専用コードは削除していくし、新規に書くこともない。たとえば、qeema から既に Presto Opera 対応の部分はざっくり消した。

ただし、前述の通り Selenium は OperaDriver を deprecated にしたわけなので、通しての機能テストを Presto Opera に施すことはできない。従って wasavi が Presto Opera 上でひと通り動作することを保証することはできなくなる。

ところで Presto Opera が脱落すると wasavi が動作すると公式に謳えるブラウザが Chrome と Firefox だけになってしまい若干寂しい。聞く所によると Microsoft Edge は Chrome のそれをパク、いや非常に高い互換性を持つ拡張の仕組みを持つらしいが…。

ただ同時に、どういうわけか Firefox の拡張とも高い互換性を持つ、という噂があったりよく分からない。そんなこと可能なんだろうか。とにかくいよいよ Windows 10 のリリースが近いが、Windows 10 に同梱される Edge にはまだ拡張の仕組みは組み入れられておらず、大体今年中に形になればいいかな程度の完成度だという。

次に。

Chrome はいくつかのキーボード・ショートカットを予約している。たとえば [cci]Ctrl+T[/cci] とか [cci]Ctrl+W[/cci] とか [cci]Ctrl+P[/cci] とか、つまりブラウジングするにあたってとても基本のもの。予約しているというのはどういうことか。スクリプトからそれらのキー入力を得ることができないのだ。これらのキー入力の場合 keydown イベント等自体が発生しないのでどうしようもない。google 神に歯向かう手段がまったく用意されていない。

とはいえ wasavi ではこれらの特別なショートカットのいくつかにも機能を割り当てている。たとえば行入力中の [cci]Ctrl+W[/cci] はカーソルの前の単語を削除する。

従来、それらの機能をテストする際は、当然ながら Ctrl と W を同時に押したような擬似的なキーストロークを生成するとテストできないので、そのかわり U+0017 を生成していた。[cci]Ctrl+W[/cci] は wasavi においては U+0017 とみなされるのでまあこれでよかろーということなのだが。ところが数日前に Chrome が version 44 に上がったせいかこの手法が通じなくなってしまった。U+0017 を生成しても Chrome に [cci]Ctrl+W[/cci] とみなされるようになってしまったようなのだ。

そんなわけで多くのテストが失敗するようになってしまった。うーんどうしたものかな。

be a developer #2

例の Firefox の署名周り。公式のドキュメントとしては https://dev.mozilla.jp/2015/02/extension-signing-safer-experience/ あたり。

このドキュメントには書いていないが、ものすごく重要なことがある。それは AMO に提出した拡張がレビューされるまで、月単位で待たなければならないということだ。レビューされるのが早いか、弥勒菩薩が降臨するのが早いか…という勢いである。枯れた安定した拡張ならともかく、新しく次々と更新されるホットなそれにとっては AMO はまったく頼りない場所であるということだ。

それを補うために、従来はベータチャネルというものがあって、1.0.0beta みたいに名づけたバージョンはベータ版扱いになり、アップした際の機械的なコードバリデータでのチェックでエラーさえ出なければとりあえず即 AMO 上に乗る仕組みがあった(ただし、当然 AMO がレビュー済みというお墨付きは得られない。また事前に full レビューを済ましたものでなければならない)。

今回署名周りの変更に従って、どうもこのベータチャネルのフローも変化している感じがする。コードバリデータでエラーも警告も一切でない 100 点満点の結果を出せば、おそらく従来通り即 AMO に乗るのだろう。しかし警告を出すと人の手によるコードレビューに送るフローしか選択できなくなってしまう。そして人の手によるレビューは前述の通り月単位で時間がかかるのである。したがって、「不安定かもしれないけど即公開される」というベータチャネルの意義を AMO 自身が完全にぶっ壊しているということになる。

ここで、「警告を出さないクリーンなコードにしないお前が悪いんじゃん」と思われるかもしれない。本質的にはそうだろう。しかし現実的にはコードバリデータがどうも信用できないのだ。警告ではないものにまで言いがかりをつけてくるポンコツに思えて仕方がないのだ。

たとえば以下のように、setTimeout() や setInterval() の第 1 引数に関数リテラルではないものを渡すとバリデータは警告を出す。しかも、reject severity: high という扱いで。

handler = function () { ... };
:
:
setTimeout(handler, 100);

これが何故警告の対象になるのかというとおそらく、コールバック関数を変数経由にすると、その変数の中身を外部から書き換えられてしまい、任意の関数を送り込まれる危険性がある……的なものだと思う。だからコールバックは関数リテラルで書けよとバリデータは言ってくる。

しかしこの場合、問題の本質はコールバックを変数経由にした場合にその変数が外部から操作可能なスコープにあるかどうかであって、関数リテラルで書いているかどうかではないと思うのです。そういう視点ではなく、バリデータは単に字面しか見ていない感じがする。信用できない。

ちなみにこれを

handler = function () { ... };
:
:
setTimeout(function(){handler()}, 100);

と書き換えると警告は出ないのだが、これじゃあバリデータくんが危険視するらしき危険性はまったく除去されてない。これを見逃すというのは、やはりバリデータは単に字面しか見ていない感じがする。信用できない。

Mozilla は自分のところで javascript エンジンを作っているのだから、自慢のなんとかモンキーをベースにもっとちゃんとしたバリデータを作れると思うのです。それをやっていない。バカでも作れる、grep に毛が生えたようなツールしか作っていない。

他にもたくさん「なんでこれが警告扱いになるのか意味わかんねーです…」というものをたくさんお出しされてとってもげんなり来る。

もっと真面目にやってほしい。

Longterm goal

中長期的な目標のうち大きめのものを一応メモしておこう。

まずはモデルとビューを分離したい。現状では wasavi が保持しているテキストの実体は iframe に表示されている html 文書そのものなのだ。それにより再表示の部分は完全にブラウザ任せにできるメリットはあるのだけど。しかしせいぜい 100 行程度の小さな文書であればいいのかもしれないが、それ以上になるとメモリ消費量や再表示の速度などなかなかきつくなってくると思う。

なので、テキスト自体は単なる String の配列か、あるいは XMLDocument にしておいて、表示の部分はまさにビューに表示すべき領域だけを描画するようにしたい。スクロール処理も含めてそのためのプロトコル的なものを考える必要がある。

それからモデルとビューの分離は、もうひとつ重要な変化をもたらす。現状 wasavi のエディタ部分のコードはすべて iframe 側にある。つまり wasavi のエディタとしての機能をもたらすコードは原則的には iframe の生成のたびに評価・実行されるということだ。コードは現状一万行以上あるわけで、これは大いにリソースの無駄だと思う。もちろん、ほとんどの場面でブラウザが持つコードキャッシュがそれをカバーしてくれるわけだけど、常にそうとは限らない。

これが、モデルとビューを分離するとどうなるか。エディタとしてのコードをバックエンド側に常住させることができるのだ。機能の区分けとしてはあくまでフロントエンドなので位置づけが難しい(フロントエンドのバックエンド側?)が wasavi が起動するたびにバックエンド側で

instances.push(new WasaviFrontend)

的に常駐しているコードからインスタンスを生成して割り当てれば、起動のたびに巨大なコード群を評価させる必要はなくなる。iframe 側が担当するのはキー入力の受付と、バックエンドへの送出、バックエンドから送られてくる再表示コマンド列の評価実行だけということになる。

Synchronizing the settings

以前、wasavi の設定を同期可能にするとして、どうしたものかというのを少し考えたことがあって、そのときは exrc に秘密の情報を書いている場合もあるかもしれないし、全自動で同期させるのはどうなのかということで棚上げにしていた。しかしじゃあ半自動ならいいのかというとそれはそれでなかなか使い勝手が悪そうだ。ということでやっぱり全自動でいいんじゃないのかなあという気分になりつつある。そもそも exrc に秘密の情報を書くことがあるだろうか。ない。

具体的には、現在は設定の保持にバックグラウンドの localStorage を使っているところを、例えば Chrome であれば chrome.storage.sync 系の API を使う。少し面倒なのは、localStorage であれば値の代入や読み出しは同期的に行えるのに対して chrome.storage は非同期的なのでその辺の修正は必要だという点。しかし実は下準備はすでにしてあって、バックグラウンドの初期化は Promise を使うようにしたので同期だろうが非同期だろうが特に問題はない。

Firefox はどうなのか。Firefox にも同期の仕組みはあるのだが、エクステンションに公開されたストレージ API というのはない感じ。少なくとも Add-on SDK にはない。そのかわり割と有名なテクニックとして about:config にリストされる Firefox 自身の設定のうち services.sync.prefs.sync.extensions. で始まるものは extensions. 以降の部分のキーが同期対象になるというものがある。

これ、同期が完了する前に読み出したりしたらどうなるんだろうか。Chrome の同期 API の場合は同期された値を読みだした時のコールバックを使用するわけなのだけど。Firefox ではどうなるの? 逆に同期完了前に値を設定したりしたらどうなるの? わたしの救助信号とどいてるの? Oh, 答えて。

The submission

issue #87

たいていのフォームで textarea って最後の方(つまり、submit ボタンの近く)にあるから、[cci]:wq[/cci] でついでにサブミットしちゃえばいいんじゃね? というアグレッシブな提案。

wq コマンドは非常に基本的なものなので、それによって便利になる web ページは随分あるだろうが、何もかもめちゃくちゃになる web ページも同じくらいあるだろう。

ということで wq にそういう機能を持たせるのではなく、新しいコマンドを新設する方向で考えている。例えば [cci]:wqs[/cci] (write-quite-submit) のような。