Talk about news #2

PhoneticNews は一種の RSS リーダーと考えることができるが(ただし NHK ニュースに関しては公式サイトが内部的に利用している、限りなく RSS に近い独自形式の XML を利用している)、任意のアドレスのフィードを利用できるわけではないので用途はかなり限定的だ。

なぜフィードを絞っているのかというと、発話の際のトラフィックの問題だ。PhoneticNews は発話データを得るのに VoiceTextAPI を利用している。この API のエンドポイントに文字列を POST すると、それを読み上げた発話の aac データが返ってくる。

この VoiceText へのネットワークアクセスは PhoneticNews をインストールした Chrome がそれぞれ個別に行うため、任意のフィードを登録できるようにすると VoiceText 側の負荷が結構なものになるかもしれないのである。まあ、勝手な想像なのですが。

API のリファレンスには呼び出しに関する回数や頻度の制限などは記述されていないので、どこまで許されるのか実際わからないのであった。うーんお問合せしたほうがいいのかなー。

いずれにしてもできうることを考えると、

  • 各 Chrome から個別に VoiceText にアクセスさせることをやめて、ここのサーバを経由させる。ここのサーバが代理的に VoiceText へのやり取りを行う: ここのサーバへの負荷はどうするのかという話になるが、現状でここのサーバは CloudFlare のお世話になっているのでたぶん負荷の面では大丈夫
  • 発話データを Chrome 自身の tts で生成する: ただし、日本語の発話データは OSX か、Windows10 以降じゃないと取得できない、らしい

あたりになると思う。はてさて。

Talk about news

PhoneticNews という Chrome エクステンションを前書いたのだが、いくつか既知のバグがあり、特に Chrome の起動時に二重に読み上げが行われる現象がある。この機会に探ってみた。

このエクステンションは定期的に NHK のニュースを読み込み、それを読み上げる。そのためにバックグラウンドで定期的に読み込み処理、読み上げ処理を走らせることが動作の核になる。一般的にそれを実現するには setTimeout や setInterval を使うのだが、Chrome のエクステンションの場合はバックグラウンドページの構造について Event Pages という別のアプローチがあり、PhoneticNews もそれを使っている。しかしこれがなかなかに癖があり、正しくそれに対応していなかったのがバグの原因になっている。

エクステンションのバックグラウンドが Event Pages のルールに従うか否かで変わる最大の物は、つまりバックグラウンドページが勝手にアンロードされるか否かということだ。バックグラウンドがアイドル状態になると即アンロードされる。アンロードというのは Chrome ブラウザの環境下においては、エクステンションに割り当てられたプロセスを終了するという意味だ。つまり Event Pages に対応すると、タスクマネージャを開いた時ずらっと Chrome が並ぶのを幾ばくか減らすことができるというわけだ。これはもちろんその分のメモリを開放するという意味もあるが、特にモバイル機器において不要な負荷を減らしバッテリーの寿命を伸ばすという効果があるのだと思う。

さて Event Pages では setTimeout/setInterval の代わりに chrome.alarms を使う。これにアラームを登録すると指定したタイミングにバックグラウンドがロードされ、次に chrome.runtime.onStartup に登録したイベントハンドラが呼ばれ、最後にアラームに登録したイベントハンドラが呼ばれる。

このアラームが特徴的なのは、登録が Chrome ブラウザに対して永続的であるということだ。定期的に発生するアラームは、エクステンションのインストール時にただ 1 度登録すればいいのである。PhoneticNews ではそれを知らず、アラームの登録をエクステンションのスタートアップ時に毎回行っていた。これが起動時に二重に読み上げが行われていた原因だ。

その他、従来は NHK ニュースだけに限っていたのを他のニュース配信社もいくつか含めるようにした。
phoneticnews-options
これ以外に、例えば共同通信や時事通信あたりも入れようかなと思ったのだが、なぜか日本の配信社は RSS フィードを公開していない、あるいはこっそりとしか公開していないところが多い。

アイコン押下時のポップアップでは最後に読み込んだ 1 件のニュースだけを表示していたのだが、最大 10 件にまで拡大した。
phoneticnews-popup

Optimize a loading of assets

赤福プラスの話。

赤福プラス 3.x はふたばの画像掲示板を開いた時に DOMContentLoaded のタイミングで一旦ドキュメントをすべて書き換える。これはなかなかアグレッシブな動作で、3.x 未満の赤福プラスや、おそらくは他の、ブラウザ上で動作するふたば閲覧エクステンションはあくまでふたばがネイティブで送出するコンテンツに何かを「付け足す」ようになっていると思う。赤福プラス 3.x はそうではなく、完全に書き換える。元のドキュメントからメタな中間 XML ドキュメントを生成し、それを XSLT によって HTML に変換・再構築し、元のドキュメントに上書きする。

さて DOMContentLoaded というのは定義としては DOM の構造を構築し終わった段階で発火するイベントで、load イベントに比べてかなり早い段階で発生する。しかし、これはドキュメントに付随するスタイルシートやスクリプトや画像や iframe で指定されるサブフレームのドキュメントをまだ読んでいない段階というわけでは *ない*。もしかしたら並行して読んでいる途中かもしれないし、そうでないかもしれない。特に最新のブラウザほど、並行動作をしている割合が高い。

このブラウザの動作は、赤福プラスの仕様とは相容れない。もしその手のアセットを読み込むとしても、それは赤福プラスが管理する文書の下で読み込まれるべきで、その前段階でネットワークアクセスが発生するのは全て無駄になるため、好ましくない。

そういうわけで、その辺りを最適化するために以前から Presto Opera では外部の script 要素の動作をブロックしたりしていた。似たようなことは Chrome の WebRequest API でもできるわけで、やってみた。

やることは、赤福プラスがページを再構築している間、そのページから発生するふたば外へのリクエストをすべてブロックすることである。そのために、

  • バックエンドの main.js にタブ ID をキーとするハッシュ initializingTabIds を用意する
  • WebRequest API によりすべてのリクエストをリスンする
  • ふたばの画像掲示板の URL がアクセスされたら、そのタブ ID を initializingTabIds に記録する
  • その他の何かがアクセスされた場合、それが属するタブ ID が initializingTabIds に含まれていれば、リクエストをキャンセルする
  • 赤福プラスのフロントエンドがページを再構築を完了したら、バックエンドへ initialized メッセージを投げる
  • バックエンドが initialized メッセージを受け取ったら、そのタブ ID を intializingTabIds から削除する

という処理を加えた。

ところで WebRequest を使うには manifest.json の permissions に “webrequest” および対象となる URL パターンを含める必要がある。今回の場合は URL は http://*/* および https://*/*/ というかなり広範囲に及ぶ強いパーミッションを必要とする。その場合 webstore での審査もけっこう時間がかかるようだ。昨日の午後提出して今審査が通った。

ちなみに Firefox 版はそういう最適化をしていないので読み込みが一番遅い。気が向いたらがんばる。

Bringing a tab to the front

Chrome や Firefox は標準状態だと新規タブを開いたときそれをバックグラウンドにするのだが、個人的にはその仕様は使いにくく、フォアグラウンド(アクティブ)にしたい。Firefox の場合はそういうふうにするオプションが本体にあるので単にそれを使えばいい。しかし Chrome にはそういった標準的オプションはない。代わりにエクステンションを入れるしかない。いくらなんでもこの程度の機能は本体が持ってていいと思うんですけど。

というわけで、今までは Tabs to the front! というエクステンションを入れていた。

しかし、どうもこれが cVim と相性が悪い。Chrome 上で target=”_blank” なリンクをクリックして作成したタブは確かに常にアクティブになるのだが、cVim がエクステンションの chrome.tabs.* API を用いて生成したタブについては、アクティブになったりならなかったりとても不安定なのだ。

そこで、Tabs to the front! を使うのはやめ、cVim 側で対応することにした。cVim 側で新しいタブを開くのは単に t キーを押す。これは [cci]:TabNew[/cci] というキーストロークに仮想的にマップされているのだが、これを [cci]:TabNew![/cci] にマップし直すことで([cci]![/cci] を付ける)新しいタブがフォアグラウンドで開くことを指定する。これで解決!

と思ったら全然解決せず、不安定なままなのである。困ったことに cVim のバックグラウンドをデバッグし、実際に新規タブを生成するところで止めてみると確かに active パラメータは正しく渡されている。というかデバッガを起動している状態では思った通りの動作をするのだが閉じると不安定な状態に戻ってしまうのだ。ひっどい。つまりこれは、chrome.tabs.create() がなんかバグってんじゃないのかしらん?

この不具合は、しかし前述の通り Tabs to the front! を有効にしても直らない。このエクステンションは何をやっているのかといえば非常にシンプルで:

chrome.tabs.onCreated.addListener(function (tab) {
chrome.tabs.update(tab.id, {active: true});
});

とまあ実にこれだけなのである。要するに新しいタブが作成されたことを察知したら、すかさずそれをアクティブに更新している。

これが動いたり動かなかったりするというのは、おそらくは [cci]chrome.tabs.update()[/cci] と、chrome 内でタブを deactive にしている「何か」との、実行される順番がその時々で不定ということなんだろう。ということで、

chrome.tabs.onCreated.addListener(function (tab) {
setTimeout(function () {
chrome.tabs.update(tab.id, {active: true});
}, 100);
});

なんて感じに遅延を挟んでやると思った通りの動きになる。しかし Tabs to the front! は人様のエクステンションなのでいじることはできない。どうしたものかな。

タブの動作の変更といえば拙作の Tabqueue があったので、そっちに機能を持たせることにした。Tabqueue とは、あるタブを閉じた後にどのタブをアクティブにするかについて、Opera12 のようにタブをアクティブ化した順のキューに応じて決定するという Presto Opera への未練タラタラなエクステンションである。従来はその機能に絞った単機能のエクステンションだったので、複数の機能を持たせるにあたり新しくオプションページを作成し
tabqueue-options
それを通して有効・無効を指定するようにした。ちなみに設定は chrome.storage.sync に保存されるようにしたので、複数のデバイス間で同期する。

ところでいつのまにかエクステンションのオプションはこのようにオーバーレイ形式になっていた。従来の、タブを1枚消費するタイプは obsolete なのだという。知らなかったそんなの…。

そういうわけで更新した

vi flavored browser UX #3

実は今現在最新の Firefox Developer Edition 46.0a では Keysnail が動作しないので、とても不便である。

せっかく Chrome 上での vi flavored なエクステンション群の評価をしたので、cVim をベースに uBlock、ScriptBlock 等を入れて色々整えてみた所割合快適な感じになってしまって、あれ? これ Firefox もう窓から投げ捨ててしまってもいいんじゃないの? という気になりつつある。

従来、どういうわけか Linux 上で Chromium(Opera も)を動かすとオムニバー上のキー入力が耐えられないほど重かった。また Chrome ではオムニバー周辺をエクステンションからいじることはほとんどできない。アドレスを編集するときに [cci]^F[/cci] [cci]^B[/cci] [cci]^H[/cci] 位は使いたいのだ。そのあたりが Firefox を常用ブラウザに選んだ理由だったのだが、オムニバーの代わりに cVim のコマンドラインを代用すれば今まで感じていた不満が何とかなってしまうわけで、そんなわけで 3 日に 1 回くらい落ちる Firefox Developer Edition を常用する理由がなくなってしまった。

vi flavored browser UX

テストのために VimFx を入れたり外してたりしたわけだが、普段はこの手の、ブラウザのインターフェースを vi 風味にする拡張というものを使っていない。正確には、Keysnail によって最低限の vi っぽいショートカット(hjlk とかその程度)を付け足してはいるが、その程度だ。hit-a-hint などのナウい機能は使わなくても何とかなっている。

これはなぜかと改めて振り返ってみると:

  • トラックポイント付きのキーボードを常用しているので、そもそもポインティングデバイスとキーボードの併用にストレスがあまりない
  • wasavi の動作テストをするにあたって、特定の拡張の影響を排除したい
  • この手の拡張を入れると、web ページが個別に定義するキーボードショートカットと拡張自身のショートカットのどちらを優先するかという問題が必ずつきまとう。この手の問題に煩わされたくない

ということだろうと思う。

そういうわけで個人的な要求としては、「ブラウザのナビゲーションにおいて vi っぽいショートカットは多少はあれば嬉しいけど、おおがかりに vi っぽくはしなくていいし、してほしくない」ということなのである。現在常用している Firefox においては、それは Keysnail で自分でちまちまとスクリプトを書けば対応できる。一方で Chrome はどうだろうか。Chrome も常用とはいかないが、それなりに使っている。

まず vi flavored なエクステンションはどうだろうか。Chrome におけるその手のエクステンションを列挙してみると:

…なんでこんなにあるんだ?

Access local files from wasavi #3

実際に wasavi 上のファイルシステムの1つとして組み込む。

のだが、その前に、ファイルの読み書きの他にもう一つコマンドが必要だ。それは任意のディレクトリ内のファイル群のリストで、いわゆる ls だ。これは wasavi では tab キーによるファイル名の補完時に呼ばれる。ls 的な処理を書いていて、いくつか奇妙なことに気がついた。

Chrome の filesystem API でそれを行うには、

  1. ルートとなる directoryEntry から、任意のパスへの directoryEntry を得る
  2. 得た directoryEntry の createReader() を呼び出す
  3. 得た DirectoryReader インスタンスの readEntries() を複数回呼び出す。

という処理を行う必要がある。

このとき、1. がなんだか奇妙だ。

rootEntry.getDirectory(
'', // path
{}, // options
function (dir) {
// success callback
},
function (err) {
// error callback
}
);

のうち、path。これは絶対パスか、相対パスを指定すると定義されている。rootEntry の実体が [cci]/home/akahuku[/cci] だったとして、例えばその中の [cci]bin[/cci] ディレクトリの下のファイルリストを取りたい場合、

  • bin
  • ./bin
  • /bin

いずれでも同じ結果が返るはずである。しかし、Chrome の実装では絶対パスを与えるとエラーになる。確かに、そもそもこの文脈での絶対パスというものの定義がよく分からない。結局のところ rootEntry からの相対パスとして扱えばいいのか、実際のファイルシステム上の絶対パスとして扱うべきものなのか W3C の仕様定義には書いてない。うーん?

もうひとつ、fileEntry や directoryEntry の親である fullPath プロパティもなんか変。上記の通り [cci]/home/akahuku[/cci] に対応する directoryEntry の fullPath を参照すると、[cci]/akahuku[/cci] なのである。…なんで? 仕様定義では、このプロパティは

The full absolute path from the root to the entry.

である。つまり [cci]/[/cci] じゃないとおかしい。

このへんの辻褄を何とか合わせる必要がある。

Access local files from wasavi

issue を少し消化する。その中に、ローカルファイルシステムのサポートというものがあり、ちょっと考えている。ちなみに、この issue を書いた人は Chrome ユーザーだ。

いうまでもなく、Chrome の extension はローカルファイルシステムにアクセスすることはできない。issue の中でこれは使えるのではないか? という API がいくつか挙げられているのだが:

  • https://developer.chrome.com/apps/fileSystem: これは、いわゆる Chrome apps 向けの API だ。Chrome apps は extension と違い、よりローカルのリソースに近い層にアクセスできる API を使用できる。しかし extension からこの API を使うことはできない
  • http://www.html5rocks.com/en/features/file_access: これはローカルではあるが、ただし「サンドボックス」なファイルシステムを操作する API だ。従って既存のファイルを直接いじったりはできない

というわけで、残念ながらすぐに使えるというものはない。

現実的な妥協案としては、wasavi のうち http://wasavi.appsweets.net/ 上での動作モードを分離する、つまり extension としての wasavi、apps としての wasavi を独立してリリースするというものが考えられる。そうすれば apps モードはローカルファイルシステムにアクセスできて、よりローカルなアプリケーションっぽくなる。

この案の弱点としては、

  • 2 つのリリースを考えなければならないのが開発側として面倒
  • 各ページの wasavi はバックグラウンドを通して各種ヒストリやレジスタの内容や exrc その他を共有している。リリースが別になると、その繋がりから抜けることになる。特にレジスタと exrc が別になるのはすごく使い勝手が悪そうだ

もう少し別の角度から何か出てこないだろうか。もしも apps をアプリケーションではなく、ライブラリ的に使うことができればいいのだが。つまりエクステンションから接続を受け、ローカルファイルシステムの読み書きを代行する中間層として動作してくれればいいのである。そんなことが可能だろうか。

まあたぶん、こんな感じで誰でも思いつきそーな使い方は当然できないんだろうけど、一応それを確かめるためにちょっと試してみよう。

Chromium versus Firefox on xubuntu

かつて、Presto Opera からの移行先をいろいろ検討した結果、Firefox を選択したわけなのだが、残念ながら、Firefox がベストなブラウザというわけでは全然ない。

xubuntu のノート上で Firefox を起動し、ノートをサスペンドし、リジュームする。そうすると困ったことにやたらノートが発熱する。不思議なことに load average は特段変化はないのだけれど、しかしノートの底面は信じられないほど熱くなる。Chromium ではそういうことはない。

そのほかにも、Linux 版の Firefox はなかなか不安定だ。入れているのが Developer Edition というのを差し引いたとしても週に数度は落ちるし、Flash のコンテンツは「運が良ければ見られる」レベルだし、そもそも Linux 版の Flash をどうするのかステートメントがいまいちはっきりしないし、製品はもとより拡張の行く末を始めとして Mozilla の動き自体が迷走に迷走を重ねていて、Mozilla というブランドの信用性はもう本当に、急降下している。

そんなわけで Chromium もちょっとずつ使うようにしているのだけど、困ったことにこちらにもいくつか問題があって、オムニボックス上の文字入力がやたら重いのである。キーを 1 つ打って、そのレスポンスが帰ってくるまでに数秒かかる。これはもしかしたら、いわゆる「おま環」なのかもしれない。ググってみるとプロファイルを一新すると治る的な記事もある。しかしこの現象、うちの環境では Chromium だけではなく Opera Developer でも同じなのだ。それなりに普遍的な不具合ではないのだろうか。

tabqueue released

Opera12 の場合、各タブがアクティブになった順番を覚えていて、あるタブを閉じた時はその順番を逆にさかのぼることで、残ったタブのうちどれをアクティブにするかを判断する。これはとても賢い。しかし、Opera12 以外のタブは一切こういった動作をしない。

Firefox の場合は、まあいろいろあるんだと思うけど、とりあえず Tab Deque を入れることで同じ動作になる。

Chrome の場合はどうか。Chrome の場合は探しても見つからなかった。そんなわけで、ないものは作るの精神で、作った:

https://chrome.google.com/webstore/detail/tabqueue/pghkhbkcicjcmgobjcgcabpmngbljill

とりあえず虹裏でスレを立てて様子を見てみたのだが、TPC というものがすでにあるらしい。ほんとだ。

うーん、まあ、いいか!