play a sound #2

SDK のドキュメントをつらつらを眺めてみると、HiddenFrame なるものを見つけた。これは要するに、バックエンド側で保持することのできる iframe のようなものらしい。HiddenFrame 内に構築される window はごくごく普通のそれであり、Audio コンストラクタも持っている。

そういうわけで、Firefox においては、HiddenFrame 経由で Audio 要素を得ることができた。子供の window で生成したオブジェクトをそのまま親の側で使用できてしまうのがかなり気持ち悪いが、たぶん、きっと、だいじょうぶ。

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 要素とは全然異なるものであって、たとえばボリュームの指定とかはできないようなのである。

Positioning

文書のスクロール量によりtextareaが半分隠れている場合などを考慮して初期の位置決めにおけるクリッピングを真面目にやるようにした。これにより gmail などで wasavi 自身がビューからはみ出すことがなくなった。

ということでスクリーンショットでも貼ろうかと思ったのだけど、なかなか gmail のページはおいそれとお外に出せないプライベートな情報満載なのであった。

ところで、最近 github に上がった issue はほぼ対応でき、ぼちぼちエクステンションストア上の安定版(ということになっている) wasavi も更新する時期に来ている。ただ設定ページでショートカットキー書くのたるいよー! 入力したキーストロークを自動的にショートカットキー記述にしてよー! 的なアレが残っていて、これは安定版にはタイミング的に入らないかもしれない。なぜこれを残していたのかといえば、設定ページ自身のスクリプトの構成も変えたので、そのへんが固まってからにしようということだったのである。

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 は結局使うことになる。

kosian is here #3

kosian ベースの wasavi を Firefox で動かすべくあちこち直している。

Chrome と Presto Opera のエクステンションの仕組みは、もちろん同じではないにしても、いとこ程度には基本的な構造は似通っているので、一方が動けば他方はそれに合わせて直すだけでいいので楽なのだ。

問題は Firefox の Add-on SDK なのである。このプラットフォームで作る拡張機能というのは、かなり異質だ。Chrome や Opera の遠い親戚の、隣に住んでいるバングラデシュ人くらい異質だ。

Add-on SDK でいわゆる content script 的なものを動かすには、PageMod API を使う。このとき、どの URL で content script をアタッチし、あるいはどの URL ならアタッチしないかという include と exclude の情報が必要になる。wasavi の場合フロントエンドは agent と wasavi 本体にコードがわかれていてアタッチの仕方が若干複雑なので、exclude 機能は必須である。で、どちらを指定する機能も、Chrome と Opera は当然持っている。

Add-on SDK の PageMod は include の指定しかできないのである。意味が、わからない。そして、最新のドキュメントを見ると exclude 機能は Firefox 32 で実装されるよ! 乞うご期待! という扱いになっているのである。意味がわからない。

どうするか。include には URL がマッチすべき文字列か正規表現オブジェクトを渡すことになっている。ここで PageMod が求める正規表現オブジェクトとは必ずしも RegExp のインスタンスである必要はなく、test() と exec() メソッドを備えていてそれぞれがそれぞれの働きをすれば通してくれるので、今まで wasavi ではそのように振る舞う独自のオブジェクトを渡していた。この時点でちょっとアクロバティックである。

で、Firefox 30 でそのコードを動かしてみると見事に動かない。なんと include に渡されたものが文字列か RegExp インスタンスかどうかのチェックを行うようになったのである。このチェック自体は正しい。しかし前述の通り Firefox 30 の PageMod には exclude 機能はない。意味がわからない。どうしろというのか。

仕方がないのでさらにアクロバティックなことをして(互換性はないのに constructor を RegExp にする)動かすようにしたのだが、もちろんかなり嫌なコードである。なお、さらに PageMod 内部で摩訶不思議な RegExp インスタンスのキャッシュ機構が働いているらしく、toString() も上書きしないとダメだったりする。本当にがっかりさせられる。

最初から exclude 機能をつけてくれればこんな変なことをする必要はないのだし、そもそも include 機能があって exclude はないという API のデザインセンスの意味がわからない。

こういう記録が残るブログであまり穏やかではないことは書きたくないのだけど、Add-on SDK チームってバカなんかじゃないかと思う。

kosian is here #2

wasavi-kosian そういうわけで、kosian ベースの wasavi が Chrome と Opera で動くようになった。次は Firefox。

ところで、Linux 版の Opera 12.16 というのは、全体的にはよく出来ているのだけど、「なんでそこを見落とすの!?」という箇所がバグっている不思議なプロダクトである。

  • 数日起動させっぱなしにすると、ソケットやファイルを開きすぎです!的なエラーが出て何も読み込めなくなる。つまりいったん開いたそれらを閉じる処理を、全てではないが一部見逃している感じ。なんでそこを見落とすの…
  • IME との連携は壊滅的にめちゃくちゃで、まったくテストされていないと言っていい。プリエディットや変換候補のポップアップが出る位置、プリエディット中の ctrl などのモディファイアキーの取り扱い、アドレスバーにカーソルがある場合の不自然に中途半端な IME の対応、などなど。また Windows 版で苦労してでっち上げた擬似 Composition Events も、input イベントなどの発生仕様が Windows 版よりもさらにぶっ壊れているのでそもそも Linux 版の Opera 上の wasavi で日本語入力はまったくできない
  • 詳細は忘れたけど、エクステンションとして読み込むソースファイルがシンボリックリンクだった場合に Windows 版と振る舞いが異なる
  • 詳細は忘れたけど、ショートカットキーに定義したストロークがまったく無視されることが割とけっこうある

なんとなく、作った奴はかなりの切れ者なのだろう。しかしテストした奴が極めて少人数だったか、相当ぼんやりしていたか、あるいはぐでんぐでんに酔っ払いながら仕事したのだろうと思わせる惜しい出来なのである。

kosian is here

以前にも書いたとおり、wasavi のバックエンドから汎用的な部分を抜き出す作業をしている。

  • wasavi のバックエンドから汎用的な部分を抜き出す。これは kosian ライブラリと呼んでいる: 完了
  • kosian を中心にして赤福プラスを作る: Presto Opera 版はとりあえず公開済み
  • kosian を中心にして wasavi のバックエンドを書き直す: いまここ
  • 赤福プラスを Presto Opera 以外でも動くようにする

kosian とは漉し餡のことではあるが、そもそも赤福の中心部は餡じゃなく餅であったり、山葵と餡は絶対に巡り合わない人たちだったり、かなり支離滅裂なネーミングであったりする。