building XPI

赤福プラスはFirefox版もビルドしている。個人的にはFirefoxを常用していないのでビルドする必要性は全然ないのだけど、Firefoxで動かしてみると思わぬバグを見つけたりするのでそういう意味での必要性はある。

そういうわけで動作することを保証するわけでも何でもない扱いなので、XPIをAMOで公開はしていない。あくまでgithubのリポジトリの隅っこに置いてるだけである。

さてそのXPIをビルドする際、rest APIが用意されていてweb-extからそれを呼び出すのだが、たまにビルドが失敗することがある。なんじゃこりゃ。

Before starting, please read and accept our Firefox Add-on Distribution Agreement as well as our Review Policies and Rules. The Firefox Add-on Distribution Agreement also links to our Privacy Notice which explains how we handle your information.

つまりAMOのアドオン配布ポリシーやルールに同意してね!という感じなのだが、同意するために具体的に何をすればいいのかは伝えてくれない。心の中ではい! 同意します! と念じても何も起こらない。

結論から言えば https://addons.mozilla.org/ja/developers/addon/api/key/ に行ってAPIキーを再承認すればいいらしいのだがいやそんなの分かんないでしょ。何をしたいんでしょうか…。あるいは実際に人によるポリシー同意を定期的に行わせるためにわざと不親切なエラーを出しているのかもしれない。考え過ぎか。

Kenya Television Network #4

Firefox で window.getSelection().modify() が動かない件。textarea 上で動かないのはそうなのだが、textarea の外では動く。textarea の外というのは例えば contentEditable=true にした要素のことだ。ということは textarea の代わりにそれをコメント欄に置けばいいことになる。

などと簡単に言ってしまったが、これはかなり面倒くさい。Chrome では textarea 上でも modify() は普通に動くし、個人的に Firefox は常用しておらずちょっとしたショートカットが動かなかろうが特に困らないので、「Firefox などというゴミみたいなダサいブラウザでは動きません」という対応でもいいのだが、それもなんなので、まあやってみよう。

ということでやってみた。とても疲れた。コメント欄が div 要素になったことで:

  • placeholder も自前で表示せざるを得なくなったのだが、その代わり多少表現の自由度が高まったのでとりあえず斜めにしてみた
  • 将来的には絵文字を画像で挿入したりできるかもしれない。その辺や絵文字パレットはやる気が満ち溢れてきたらやる

ところでこの赤福プラスが内包しているちょっとしたショートカットのうち Ctrl+A は 1 回押すと全選択し、全選択の状態でさらに押すと最初に押す前のキャレット位置から前方に向かって折り返し位置境界へ飛ぶ。これはそういうふうに動くように組んであり、仕様である。

Reloading Optimization #2

別のアプローチからも高速化を施してみよう。お知らせによると、2018/11/09 付でリロードの高速化が施されたとある。つまり、ふたばが標準で返してくる html における [リロード] ボタンの動作が変更されたということだ。

リロードボタンを押した時、従来はまず HEAD リクエストを飛ばして、更新されていたらページ全体をリロードという形になっていた(たぶん)が、11/9 の変更ではこのページ全体のリロードに代えて、XMLHttpRequest で [cci]futaba.php?mode=json[/cci] 云々をリクエストして、新しく増えたレスの情報だけを json 形式で受け取るようになっている。これによりまず転送量が劇的に減る。転送量が減ることでレスポンスタイムも短くなる。これは立派な差分取得 API と捉えられるので使わない手はない。

ということで組み込んでみた。ここで重要なのは、リロードの手段が 2 つになるということだ。差分形式で得られるのは新着レスとそうだねの全データのみであり、ID 付与やレス削除を同期するにはフルリロードしなければならない。なので、対応する UI としてはまず従来の [続きを読む] リンクの隣に、[差分で続きを読む] リンクを置くことが考えられる。あるいは逆にデフォルトを差分読み込みにして、[フルで続きを読む] でもいい。しかしこれはユーザに対していちいち気にしながらリロード手段を振り分けることを強いているわけで、あまりいい UI ではないかもしれない。自分を含めて、「」もとしあきもそんなに賢くない。

従ってあくまでリロードボタンは 1 個だけで、赤福プラス側がよきに計らって適宜フルと差分を切り替えるのが良いように思える。例えば最後にフルリロードした時刻を覚えておいて、そこから n 分経過するまでは差分リロードとかでどうか。n は定数でもいいし、スレッドの勢いか何かから動的に算出する形式でもいいだろう。

とそういうわけでそうしてみた。n は設定可能な定数にした。デフォルトは 2 分間隔でフルリロードする。この状態でいつもどおりスレッドを見ると、転送量はだいたい 90% 削減できるようだ。もちろん読み込みも速い。すごい。

このあたりの話の流れは、むしろ懐かしい。というのは12年くらい前に Opera 版の赤福プラスに続きを読む処理を加えた際、もともとリクエストヘッダに Range を追記しておんなじような転送量削減の措置を施していたからだ。ところがほどなくして Apache の Range の取り扱いに関して脆弱性が発覚してしまった。そんなわけで双葉の鯖もパッチが当てられ、Range は無事無視されるようになったのであった。がっくり。

Kenya Television Network #3

というわけで私的な環境におけるキーバインディングを整理したのだが、実は赤福プラス自身も textarea においていくつか Emacs ライクなショートカットを定義している。

でもそもそもこれ、いるんだろうか。textarea にショートカットがたくさん用意されていないと死んでしまうような輩は、そもそも自分でシステムレベルで有効な環境を構築している気がする。

まああっても害になるものではないし、残しておくかな。とりあえず設定にそういったショートカットを有効にするかどうかの項目だけ追加しておくことにしよう。

ところで javascript から textarea 内のキャレット位置を変更するには、selectionStart/selectionEnd をいじる他に、Selection#modify() がとても有用なのだが…どうもこれ、Firefox だと全然動かないみたい。63.0 on Linux、63.0.1 on Windows10 の両方で動かない。リファレンスを自分とこで用意しておきながら動かないってどういうことなんだろう。

Let the box be light

画像をクリックするとデフォルトで lightbox 的な振る舞いをするのだが、これにはいくつか積み残しになっている点があった。

まず、画像を原寸表示させた際。このモードではマウスのドラッグにより画像のスクロールが可能なのだが、ドラッグしたままポインタをブラウザ外に持っていき、そこでボタンを離したりすると状態管理の不整合が起きてドラッグしてないのに画像がポインタについて来るみたいなことになってしまう。

これはつまりポインタのキャプチャをしていないからなのだが、何故していないのかと言うと、正確には覚えていないけど多分 Presto Opera にその機能がなかったんだと思う。これを直したい。

次に、全体表示から原寸表示モードに移行した際に拡大の原点がつねに画像の中心なのが不便。たとえば漫画なんかを1枚絵にした画像の場合に、原寸表示にしたあと上端へスクロールさせなければならないのが煩わしい。これを解決したい。

最後に、スマホで撮った写真など、本来90度回転して表示されるべきものが、そうなっていない場合がある。これに対応するため、画像の回転機能が欲しい。

というわけで、上記を解決するために書き直した。

回転にも transition をかけたのでやたらスムーズにくるくる回るのが面白い。

2番めの問題を解決するために、従来は画像をクリックすると lightbox を抜ける動作をしていたのだが、新しいバージョンではクリックしたポイントを原点に拡大縮小するようになった。lightbox を抜けるには画像以外の部分をクリックする必要がある。これはちょっと優しくない仕様変更かもしれない。

それと、画像のドラッグ時にいわゆる bounce-back 機能をつけたのだけど。これをつけると Apple に訴えられるんでしたっけ? ヤバイのかな。

* * *

とかなんとかやってたらまたふたばが全滅してるし…。

sync, sync, sync

従来は設定を localStorage に持っていたが、[cci]chrome.storage.sync[/cci] に持つようにした。storage の読み出しは非同期的に行われる。バックエンドの接続と、DOMContentLoadedの待機と併せて、起動時に非同期に行われる処理がこれで3つになった。これらを [cci]Promise.all[/cci] を用いて並列的に処理するようにした。

そういえば sync って Firefox や Opera だとどういう扱いなんでしたっけ? Firefox は対応しているようだ。Opera は対応していないらしい。対応はしていないが、chrome.storage.local にフォールバックされるようで呼び出してもエラーにはならない。なにそれ。Opera Sync があるんだからそれとくっつけてよ。

カタログのリロード処理を見直した。また、デフォルトのソート順の場合はページ数を表示するようにした。選択したカタログのソート順を覚えておき、ページ全体をリロードした際も再現するようにもした。

ところで、xsl において、あるコンディションによって出力する内容を変えたいときがある。例えば出力する要素のクラス名に特定の文字列を状況によって含めたり、外したりしたい場合。

...

のようなことをしたい。波括弧に囲まれた属性値テンプレートは xpath の式だ。当然上記はエラーになる。

そんなわけで

...

のようなとてもトリッキーな書き方をせざるを得ない。= 演算子が true か false かを返し、number() がそれを 1 か 0 に変換し、式が真の場合は hide の先頭から4文字(つまり “hide” そのもの)、偽の場合は先頭から0文字(つまり空文字列)を出力する。

もうちょっとスマートな方法がないものかな。

WebExtensions and clipboard operation, revised

現状では赤福プラスのファーストクラスブラウザは Chrome なのだが、一応 Firefox の WebExtensions でも動作確認はしている。でもどうなんでしょう。そもそも Firefox たまにしか立ち上げないし、また Firefox 上の他のふたば用エクステンションはここの所かなりたくさんリリースされているので、そこに加わるのもなあという感じ。

まあそれはそれとして。

WebExtensions では、パッケージ内のリソースを参照する際は moz-extension: スキームを使う。Chrome では chrome-extension: だ。これは内部で使用している xsl ファイルに埋め込まれたスタイルシートの中の background-image なんかで問題になる。background-image を使用する箇所だけを別の css ファイルにして解決(あんまりイケている解法ではない)。

以前クリップボードの取り扱いについて四苦八苦したことがあったが、基本的に変わってない。なぜか Chrome と全く違うクリップボード操作を強いられる。ただしそれとは別に、javascript からクリップボードを扱うための API ができて、割と最新のブラウザなら使えるようだ。とりあえず Chrome 69 なら使える。Firefox 63 では about:config から dom.events.asyncClipboard.dataTransfer を true にすることで使える。デフォルトの状態では使えない。

そういうわけなので


if ('clipboard' in navigator)
navigator.clipboard.writeText(text);
else if (IS_GECKO)
setClipboardGecko(text);
else
setClipboard(text);

…みたいな感じにした。

Testing with selenium javascript binding #4

そういうわけで Linux 上では WebExtensions 版の wasavi の、Selenium による、Firefox 上の機能テストを一通り通せるようになったのだが、結局これを Windows 上ではできていない。過去の記事で何度か書いているが Windows では既存のプロファイルでもって Firefox を起動させることができず、かつ新規プロファイルに WebExtensions ベースの拡張を登録させつつ Firefox を起動させることもできない。両方できないと身動きが取れない。

実を言うと Linux 上でもテストが問題なく滞りなくできてるわけではない。例えば:

  • なにかが leak してますよ〜的なログがいっぱい出る。
    (node) warning: possible EventEmitter memory leak detected. 11 listeners added.
    云々。このエラー自体は書いてあるとおりのそのままの意味なんだろうけど、Chrome でテストする際は出ないので Selenium 内の Firefox とのやり取りをするコードにおける問題だと思う
  • wasavi でテストする際は当然ながら様々なキーストロークを発生させる必要がある。で、[cci]selenium.Key.chord()[/cci] というものがあり、[cci]chord(Key.CONTROL, ‘c’)[/cci] などと呼ぶと ctrl+C を押されたとブラウザに振る舞わせるような擬似的なキーストロークを生成する。Key[なんとか] は、型自体は普通の string で、Unicode の Private Use Area 内の文字である。chord 自体は単に引数を全部 join(”) したものに Key.NULL をくっつけたものを返す。したがって擬似的なキーストロークもまた単純な文字列である。さてそういうルールのもとに構成された chord を送出したあと、geckodriver かあるいは Firefox 本体が例えば前述の例だとそれを ctrl+C としてデコードしてくれないと困るわけなのだが。困ったことに、誰もデコードしてくれない。そのまんま PUA が入力され、テストは失敗する。ひどい
  • テスト中 Web Content なるプロセスが生成されて、これが60〜80%の CPU パワーを消費する。同時に Firefox 自体のプロセスが残りの CPU パワーを消費する。そんなわけでテスト中は待ってる以外にほとんど何もできないし、そもそも端末以外のアプリケーションを全て落とした状態じゃないとテスト自体がうまく始まらない。Chrome を起動しながらとか成功した試しがないのである。そして CPU パワーはめちゃくちゃ食いまくる割にテストの進行は Chrome でのそれより 2 割増しくらいで遅い

前述のプロファイル関連の不具合と合わせて、どーにもこーにも…… Firefox が絡む Selenium のテスト環境はいつも質が悪い、質が低いと言わざるを得ない。どうなってんのかなあこれ……。この辺が快適になってくれないと、Firefox 向けの拡張を作るという事自体にくじけてしまいそうなのだけど。

Testing with selenium javascript binding #3

テストを開始しても、Chrome は起動するもののテストページへのナビゲーションが発生せず、そのままタイムアウトで失敗してしまうことがたまにある。これの原因がよくわからない。とりあえずエラーメッセージとしては “unable to discover open pages” というものが返される。

原因はよくわからない。テスト用とは別の常用の Chrome を起動している状態でテストを開始すると、そういう状況が発生することがあるが、しないこともある。常用の Chrome を落とした状態でも発生することがある。また youtube とかの動画を再生中だと発生する確率が上がるような気がしないでもない。要するに chromedriver が Chrome を起動するものの、複数ある Chrome のうち自分が起動させたものを見失うことがある、というような感じなのだが……そんなわけあるかいな。

上記のエラーメッセージでググっても特にこれだというものもない。謎。

ちなみに Linux 版の Chrome で UI のロケールを任意のものにするには、[cci]LANGUAGE=en google-chrome[/cci] などとする。LANG でも LC_ALL でもなく、LANGUAGE。また、[cci]–lang[/cci] スイッチは効かない。

 * * *

というわけで、とりあえずすべての機能テストを java から javascript へ移植した。疲れた。次にこれを Windows 上の Firefox にて通してテストする。とその前に、Linux 上でも動かしてみよう。

ナウい Firefox で Selenium のテストをするには、geckodriver が必要なので、これをパスの通ったところに置いておく。


var options = new firefox.Options();
options.setProfile(profilePath);
result = new webdriver.Builder()
.withCapabilities(webdriver.Capabilities.firefox())
.setFirefoxOptions(options)
.build()

こんな感じで起動。これは既存のプロファイルを利用するような動作を意図しているが、どうも既存のプロファイルを /tmp あたりにまるごとコピーしてから起動するような感じがする。そのため実際に Firefox のウィンドウが表示されるまでは結構待たされる。

それから、webdriver.actions().sendKeys().perform() が未実装なのだそうでエラーになる。その代わり WebElement#sendKeys() を使う。geckodriver 自体が新しいプロジェクトなので、すべての想定された機能が実装されるまでにはもうちょっとかかる雰囲気。

 * * *

結局のところ wasavi でテストするには

  1. [cci]npm install -g selenium-webdriver[/cci]
  2. [cci]npm install -g chromedriver geckodriver operadriver[/cci]
  3. [cci]npm install -g mocha[/cci]
  4. [cci]npm install[/cci]

と入れて、[cci]make run-chrome[/cci] としてとりあえずテスト用プロファイルでもって起動し、開発者モードで wasavi を組み込み、ついでに dropbox などに wasavi からアクセスして認証を得ておき、ブラウザを閉じてから [cci]make test-chrome[/cci] とする……という感じ。ただし Firefox に関しては、過去の記事の通りオンザフライで WebExtensions ベースの拡張を登録するのが現在のところできないので、wasavi をビルドした上で xpi を登録したプロファイルを用意する必要がある。