play a sound

wasavi を実行中、音を出す場合がある。つまりビービー鳴らすモードを持つ vi と同様、必要なときにエラーベルを出す。もし「この音うるせーよ!!」と思われる場合は、ワークアラウンドとして [cci]:set bellvolume=0[/cci] とすれば、とりあえずミュート状態にはなる(例えばこの方がやっている方法だ)。将来的にはビジュアルベルに切り替わったりする気遣いをするかもしれない。

さてこの時に出す音は Audio 要素を使っていて、フロントエンド側で鳴らしている。音源は wasavi 起動時にバックエンドから送られてくる ogg または mp3 データである。

しかし、これは割と無駄なのではないか。複数のタブで wasavi を動かしているとして、各タブがもともと同じデータである音源のコピーを個別に抱えている状態だ。現状は 1 秒未満しか発音しないビープ音なのでデータ自体は数 KB だ。したがってものすごく容量を食っているというわけではないが、やっぱり無駄だ。

これを、Audio 要素をバックエンドで保持するようにして、フロントエンドで音が必要になったら発音リクエストを投げるだけにすれば、そういう無駄は解消できる。ということでやってみて、Opera と Chrome では思ったとおりになったのだけど、Firefox が問題だ。Firefox の Add on SDK 上のバックエンドは、Window ではない不可思議なサンドボックスオブジェクトがグローバルになっていて、つまり Audio 要素をバックエンド側で使えないのだった。

こういう場合、たいてい [cci]Cc[/cci] や [cci]Ci[/cci] といった Firefox 特有の黒魔術をごにょごにょして何とかなったりするものなのだが(Kosian では XHR や Blob や FormData についてごにょごにょしている)、黒魔術はしょせん黒魔術であり、あんまり使いたくない。なにしろ、AMO にアップする際の機械的チェックにすら「おい黒魔術使ってんぞ、気をつけろよな!」などと言われる始末である。禁止されているわけではなく、「気をつけろ」なのがミソではあるのだが。

さて、サウンドプレイヤについてもこの黒魔術を通して Audio 要素っぽいものを得ることはできるようなのだけど

var {Cc, Ci} = require("chrome");
var sound = Cc["@mozilla.org/sound;1"].createInstance(Ci.nsISound);
var uri = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.newURI(self.data.url(...), null, null);

sound.play(uri);

これで得られる nsISound は Audio 要素とは全然異なるものであって、たとえばボリュームの指定とかはできないようなのである。

kosian is here #7

wasavi を構成するコンポーネントとして

  • バックエンド
  • フロントエンド: エージェント
  • フロントエンド: wasavi 本体

の 3 種がある。バックエンドはいわゆるエクステンションのバックグラウンドスクリプト、フロントエンドはコンテントスクリプトなどと言われるものである。エージェントは textarea 要素を持つページにアタッチされて、wasavi を起動させるかどうかの監視などを行う。

これら 3 つが協調することで wasavi が動作する。協調するためにはメッセージングを行う必要がある。

フロントエンドとバックエンド同士のメッセージングは、各ブラウザのエクステンションがその仕組みを提供しているので問題はない。一方で不思議なことに、どのエクステンションも、ドキュメント同士のメッセージング機構はない。

おそらく、ドキュメント同士なら html の規格通りにクロスサイトメッセージングを使ってね! ということなのだろう。でも、[cci]window.postMessage()[/cci] によるやりとりというのは非常に汎用的なものなので、つまりだれでもリスンし放題なのである。怪しげなサイト上で wasavi を起動したりした場合、エージェントに対してクリティカルな情報(たとえば、wasavi のレジスタの内容は更新されるたび同期のためエージェントに内容が通知される)を直接投げるのはちょっとこわい。

ということで、ドキュメント同士のメッセージングとしてはバックエンドを経由してエクステンションのシステムの中で完結するようにした。正確には、もともとそうなっていたのだけど、ここ数ヶ月のソースは [cci]window.postMessage()[/cci] を使うようになっていた。それを元に戻した。

そして、そろそろ各ブラウザのエクステンションストアに置いてある版もここのところのテストが終わったら更新する頃合いだと思う。

kosian is here #6

wasavi のフロントエンドとバックエンドとの通信の方式について若干の変更を行いたい。

そのあたりはもちろん kosian 側で抽象化してあるのだが、wasavi はまず Chrome 上で作り始めたので、Chrome のエクステンションにおけるバックグラウンドとコンテントスクリプトの通信の作法をだいたいそのまま持ってきている。

つまり
extension.postMessage(message [, callback])
というメソッドを用いる。この callback がこの記事の主役である。

この callback を指定することにより、投げたメッセージに対する直接の応答を得ることができる。つまり非常に限定されたメッセージリスナとみなすことができる。一方、フロントエンドはコンテントスクリプト毎の汎用メッセージもリスンしていて:
chrome.extension.onRequest.addListener(handleMessage);
こちらは例えば他のタブで動作中の wasavi で設定が変更されたという同期通知や、dropbox(など)からのファイル読み込みの進捗通知などを扱う。

要するに、2 種類のメッセージリスナを扱っている。この状況で、それぞれが扱うメッセージの性格が完全に分離されているのならば、別に 2 種類あることは悪いことではないのだけど。がしかし上記のファイル読み込みの進捗通知というものが、微妙にどっちつかずな存在なのである。

ファイル読み込みは、フロントエンドからバックエンドへパスなどを渡し、バックエンドで認証などを済まし、読み込むためのリクエストを発行し、その結果をフロントエンドへ返す。フロントエンド側で見るとこの処理は ex コマンド [cci]read[/cci] の処理ということになり、そのハンドラの中で完結するはずのものである。が、実際は [cci]read[/cci] コマンド実行中のバックエンドからのメッセージは、汎用メッセージ側で処理している。これはやや不格好だ。

なぜ postMessage() の callback を使わないのかといえば、つまり [cci]read[/cci] 実行中、バックエンドからのメッセージは複数回送信されるからである。callback は 1 度呼び出されて終わりなのだ。これを拡張し、複数回 callback が呼び出される構造にすれば、必要な処理はすべて [cci]read[/cci] ハンドラの中に掛けば済むようになって、収まりがよいはずだ。

というわけでそうした。ここで、ソースの修正は割とすぐに終わったのだけど、実際に Opera 12 で動かしてみるとさっぱり狙った動作をせず、あーでもないこーでもないとした挙句、単に Opera を再起動させたらちゃんと動くようになったというなんとも言えない事件があったのだが、まあ Opera においては特に珍しいことではないので、いいのだ。

kosian is here #5

kosian ベースの wasavi の作成がひと通り作り終わったので、Selenium によるテストをしている。Chrome、Opera、Firefox のそれぞれでテストをしているのだが、

  • Chrome: 安定して自動テストを行うことができる。また、速い
  • Opera: 不安定な時期もあったが、最終バージョンの operadriver においては安定している。遅くはない
  • Firefox: キーボード入力のエミュレーションがろくに動かない。wasavi のテストでこれは致命的だ。また、べらぼうに遅い。Chrome/Opera での全テストがだいたい 80 分ほどで完走するのに対し、2 〜 3 倍ほどの時間がかかる。それから、必ずフォアグラウンドの状態にしておかないとまともに動かない

と、こんな調子である。Firefox だけが根本的にダメダメなのが気になる。なんとなく環境依存の現象がしないでもないがそんなほいほいテストマシン用意できないしなあ…。

kosian is here #4

  • Firefox でもだいたい動くようにした
  • エクステンションのビルドは make で行うようにした

ということで、Ant は使わなくなったのだが、機能テストは例によって Selenium を用いるので、Ant というか java は結局使うことになる。

hang on search?

issue #20 で、検索するとパターンによって無限ループになってタブを閉じるしかなくなるというのがある。特に [cci]|[/cci] とかが危険らしい。なるほどなかなかそんな雰囲気がしないでもない。

これは由々しき問題だ……ということですぐ直そうとしてみたのだけど、再現しない。ということでどういうパターンだと発生するのか聞いているところ。

visual in vi #8

vim 同様、V を押すことで行単位の選択もできるようにした。モード名は bound_line。また、bound モード中に bound_line モードに入ると(あるいは逆に bound_line から bound でも)、現在の選択範囲を活かしたまま文字単位・行単位が切り替わるようにした。これも vim と同様。

ところで vi では、空行以外の行においてカーソルを改行の上に置くことはできない。vim もこれは同じだが、ただし visual 系モードに入るとその制限が取り払われる。これはこれでまあそういうものなのかな、ということで一応付けてみたのだが……これ、もしかしてなくてもよくなくなくない? とりあえずオミットしておこう。クレームが付いたらつける。

それにしてもかなり wasavi.js に手が入った。とりあえずの機能は実装し終えたのでモードを切り替えてテスト三昧の毎日になるのである。

visual in vi #7

vim の visual モード中に実行できる編集コマンドのうち、大文字のものはほぼ、操作対象が行単位になる。行単位ということは、選択範囲の端点の桁位置は使用されないということだ。行位置だけが意味を持つ。

行単位の操作においては、特に削除処理について文字単位でのそれとの違いを意識する必要がある。具体的には行にマークが含まれる場合である。マークを含んだ行を文字単位で全削除(つまり [cci]^.*$[/cci] を削除)したとしても、マークは空行の先頭位置に収斂され、削除されない。一方、行単位での削除(つまり [cci]^.*\n[/cci])の場合はマークごと削除される。これは決定的な違いだ。ちなみに削除に対応する undo 情報にはマークも含まれるので、undo すれば行全体とともにマークも復活する。

ところで、最後に使用した選択範囲はマーク [cci]<[/cci] と [cci]>[/cci] で参照することができる。では、visual モードを経由して行単位での削除を行った場合、それらのマークはどうなるのか? というと vim では削除後もそれらのマークは生き残っているのである。もちろん本来の選択範囲は削除されているので、全然関係ない別の行を指していることになる。これはなんというかバグなのか仕様なのか判別しづらい。

wasavi では行単位で削除を行ったあとは [cci]<[/cci] と [cci]>[/cci] マークは無効になるようにした。

visual in vi #6

gq
command モードでの [cci]gq[/cci] はオペレータとして扱われ、後続するモーションによって移動したカーソルとの間の領域に「textwidth の幅に収まるよう適切に文字を充填したり適切に改行を挿入する」という処理を行う。ここで、カウントを指定し、かつモーションが [cci]_[/cci] またはそのエイリアス(つまり [cci]gqgq[/cci] または [cci]gqq[/cci])だった場合、カウントは [cci]gq[/cci] 自体に作用し、相当する行数が再フォーマットの対象になる。

何を言いたいのかというと command モードでは [cci]3gqq[/cci] とか指定した場合、カーソル以下の 3 行を再フォーマットしろ、という意味になる。カウント := 縦方向の行数。

vim では、これが visual モードになると、カウントは textwidth オプションを一時的に上書きする。カウントの意味合いが垂直方向から水平方向に完全に切り替わる。カウント := 横方向の文字数。

これ、良いインターフェースか? と問われればもちろん全然良くないんだけどどうしたものかなー…。

gv
vim では、command モードでの [cci]gv[/cci] は、最後に使用した選択領域を再びアクティブにしつつ visual モードへ遷移する。ということで wasavi にも実装したのだけど、では visual モード中に [cci]gv[/cci] したらどうなるんでしたっけ? と思いつつやってみたら、やはり command モードでのそれと同じ動作をした。つまり最後の選択領域の端点と、アクティブな端点は個別に管理しているようだ。

それに倣い、wasavi でも bound モードでのアクティブな選択領域の端点はプライベートなマークを使用するようにした。