Some spices for Qeema

Qeema によってキーボード周りを wasavi と赤福プラスで共通化したのだけど、キーボードの扱い方は両者でかなり異なる。

赤福プラスはいくつかのキーボードショートカットを定義しているが、それ以外に関しては手を付けない。一方 wasavi は全てのキー入力をリスンし、全てのキー入力を消費し(つまりデフォルトアクションをキャンセルし)、自前の処理を行う。この中で、全てのキー入力というのは composition events によって発生したものも含まれる。

ところが、composition events というのは単なる通知イベントなので、DOM の定義上はキャンセルできないのである。なので、qeema 側で compositionstart の時点での入力文字列を覚えておき、compositionend で復帰するようにしている。

ついでに、Presto Opera での composition events エミュレーションをできるだけシンプルな構造に書きなおした。まあ、いまさらこれをブラッシュアップしたところで誰も幸せにならないんですけどね……。

Signing an extension

Introducing Extension Signing: A Safer Add-on Experience
http://blog.mozilla.org/addons/2015/02/10/extension-signing-safer-experience/

より安全なアドオン体験を提供するため、拡張機能に署名を導入します
https://dev.mozilla.jp/2015/02/extension-signing-safer-experience/

ほう。

さらに、Add-on SDK 製のエクステンションに関しては、近い将来 cfx は用いられなくなり、node.js ベースのツールへ移行するというもある。

for Firefox #12

というわけで赤福プラスが Firefox でも動くようになって、また常用ブラウザ自体も Firefox に移行できた。まだメモ機能の代替品を見つけていないのだけど。

それはさておき、赤福プラスは wasavi とソースレベルではいろいろなものを共有している。例えば Kosian(バックエンドのエクステンションの抽象化ライブラリ)、Qeema(キーボード周りのライブラリ)、フロントエンドのエクステンションラッパー、Brisket(ビルドスクリプト群)と言ったものだ。

これらもいろいろと更新したわけなので、wasavi のほうに戻り、それらの共有部品を最新のものに置き換えてみたい。また、wasavi も Firefox 上で常用することになる。これはとっても大事だ。Firefox 版の wasavi というのは、これはこれでけっこう苦労して作ったのだが、まっさらのプロファイルでひと通りテストが通ればそれでおっけーということにしている。しかし世の Firefox で実際のところ拡張をひとつも入れてないまま稼働してるのなんて早々ないだろう。もうちょっと使い込んだプロファイルで常用しないといけない。

つまり、この度の常用ブラウザの移行は丁度いいチャンスなのである。

qeema

赤福プラスはいくつかのショートカットキーを受け付けるので keydown や keypress イベントをリスンするのだが、もう言うまでもないのだが、このあたりのイベントのブラウザごとの相違は壮絶なものがあるのである。そのへんを吸収している、wasavi が持っている keyManager を持ってきたい。件の、使用するショートカットを表明する処理もこれに入れてしまいたい。

というわけで、wasavi からそれを抜き出して別のリポジトリに仕立てた。キーボードのマネージャーなので略して qeema とした。ひき肉。
https://github.com/akahuku/qeema

ただひとつ問題があるのである。これを赤福プラスや wasavi に取り込む際、単に git のサブモジュールを使うわけにはいかない。というのは、qeema.js 自体は素の javascript ソースなのだが、これを Presto Opera で inject script として扱うには、ヘッダを付けなければいけない。


// ==UserScript==
// @name frontend of akahukuplus
// @include http://*.2chan.net/*/*.htm
// @include http://*.2chan.net/*/*.htm?*
// @include http://*.2chan.net/*/res/*.htm
// @include http://*.2chan.net/*/res/*.htm?*
// @exclude http://dec.2chan.net/up/*
// @exclude http://dec.2chan.net/up2/*
// ==/UserScript==

同じソースでも wasavi に取り込む際は @include 節がまた違った指定になるわけで、いずれにしてもサブモジュールそのままの状態で使えるわけではない。

うーんなにかうまい方法がないのかな。

for Firefox #11

赤福プラスに関して、大体のところは Firefox でも動くようになった。ただ、やはりまだキーバインドについて詰め切れていないところがある。

たとえばスペースキーに対して、通常の状態では

  • ページ前提のスクロール領域の末尾にすでにいるなら、続きを読む
  • そうでなければ、何もしない(つまり、ブラウザのデフォルトアクションに委ねる)

というハンドラを割り当てている。

一方、lightbox を実行している場合は

  • 表示している画像をスクロールさせる。ブラウザのデフォルトアクションは常に preventDefault() する

とさせている。

何が違うのか。前者は、もともとブラウザが持っているアクションを置き換えて、ちょっとした tweak を行っている。従ってその置き換えがエクステンションによって無効にされたとしても、必ずしも致命的な不具合にはならない。すなわちエクステンションによるキーバインディングよりも優先度が低い。

一方で後者は、ブラウザの機能とは全く別のものだ。この処理をエクステンションによって無効にされると、lightbox により画像を表示している裏で単にページがスクロールするだけになってしまう。全く機能が破綻してしまうということだ。従ってこのケースはエクステンションによるキーバインディングよりも優先度が高くなければならないのである。

エクステンションによるキーバインディングが行われても構わないか・あるいは困るかという表明を、なんとかエクステンション側に対して伝えられないだろうか。それを両者で共有し連携すれば辻褄が合う。

表明については、documentElement の data-なんとか属性に優先度の高いキーバインディングを DOMTokenList 形式で列挙すればよいのではないか。で、.keysnail.js の登録したハンドラ側では、登録したキーバインディングについてドキュメント側でその表明が行われていない場合にのみ自前の処理を行えばよい、はずだ。

 * * *

ということでやってみた。[cci][/cci] とした文書を用意する。.keysnail.js の preserve 領域に

function tdoc (ev) {
return ev.originalTarget.ownerDocument;
}

function twin (ev) {
return ev.originalTarget.ownerDocument.defaultView;
}

function dispatch (stroke, ev, handler) {
var doc = tdoc(ev);
if (!doc) return;

var m = doc.documentElement.getAttribute('data-prior-keys');
if (m && (' ' + m + ' ').indexOf(stroke) >= 0) {
var ev2 = key.stringToKeyEvent(stroke, true, 'keypress', true);
if (!doc.dispatchEvent(ev2)) {
return;
}
}

handler(doc, twin(ev), ev);
}

とし、実際のスペースキーに対するハンドラは

key.setViewKey('SPC', function (ev, arg) {
dispatch('SPC', ev, function (d, w) {
if (w.scrollY >= d.documentElement.scrollHeight - w.innerHeight) {
let customEvent = d.createEvent('CustomEvent');
customEvent.initCustomEvent('RequestMoreContent', true, true, {});

if (d.dispatchEvent(customEvent)) {
// do a special job
}
}
else {
w.scrollBy(0, Math.floor(w.innerHeight / 2));
}
});
}, 'ビューの半分の高さだけスクロールダウン', false);

とする。これにより、html 要素の data-prior-keys 属性に表明されているキーストロークは、いったんページのスクリプトに処理を丸投げする。ただし丸投げしたとしてもページスクリプト側で preventDefault() されていない場合は、keysnail 側のハンドラも実行する。一方、data-prior-keys 属性に表明されていないキーストロークは、即 keysnail で処理する。

丸投げの部分は、本来真面目にやろうとすると keydown/keyup もエミュレートしないといけなかったり、さらにキーリピート絡みも考えなければならなかったり、もっと言えば本当は key プロパティも正しくセットされるように initKeyEvent() ではなく KeyboardEvent() の方を使わないといけなかったりとかなり大規模になるのだが、まあ、とりあえず。

でも基本的にこのやり方、つまり文書側が使用する特定のキーストロークを外部に対して表明するという仕組みはかなり、いいと思う。こんな場末のブログとエクステンションだけが使うにはもったいない感じがする。どこか有名所の javascript のフレームワークとかがパクってくれないだろうか。

for Firefox #10

特に CSS Transition による再描画がカクカクになってしまう事があると書いたが、どうも iframe による広告がてんこ盛りのメインコンテンツで、おまけに 2 ちゃんねるのまとめが載ってある系のまとめサイトを開いてしまうとそういう状態になる気がする。iframe が多すぎると色々おかしくなるのは Presto Opera ではよく経験した(読み込みが終わらなくなるとか、スクロールがカクカクになるとか、不要な履歴が追加されてしまうとか)が、Firefox でも無問題というわけではないようだ。へぇ。

ううん。でもどうしようもないかも。

赤福プラスで、youtube へのリンクを見つけたら埋め込みのプレイヤーに展開するようにしているのだが、これが意外にメモリを食っている。about:memory で見てみると、ひとつあたりだいたい 8MB ほど。○○な動画教えてよ系のスレを開いてしまうとなかなか厳しい。

そこで、ドキュメントの scroll イベントで何かしてみることにした。つまり、埋め込みプレイヤーが、今現在見えている文書のスクロール位置から十分離れている場合は、プレイヤーを除去してしまう。逆にスクロール位置に近づいたらあらためて生成する。Twitter でも同じことをしている。

問題があるとすると、動画を見ていたが一時停止してまたあとで見よう…と思いつつ一旦遠く離れた位置にスクロールしてしまうと、問答無用でプレイヤーが削除されてしまうので一時停止したことも綺麗さっぱり忘れられてしまうことだが。

for Firefox #9

KeySnail の設定を行う。と言ってもとりあえず ~/.keysnail.js を書いて読み込ませるだけだ。また、そんなにガチガチにオレオレキーバインドしまくりというわけでもない。なんとなくそれっぽく hjkl が使えるとか、テキスト入力時に C-f とか C-b とかが使えれば良いのだ。

ただ、単にキーにただひとつの機能を割り当てるものの他に、いくつか特殊なのはある。

まず C-h に、履歴を戻る機能を割り当てる。ただし、戻れる履歴がない場合、つまり履歴の先頭まで戻った場合はタブを閉じる:

key.setViewKey('C-h', function (ev) {
. if (getBrowser().canGoBack) {
. . BrowserBack();
. }
. else {
. . BrowserCloseTabOrWindow();
. }
}, '戻る、または閉じる', false);

この時あらかじめ用意されている Firefox の機能を呼び出すために、BrowserBack とか、key.generateKey(引数は仮想キーコード) とか、goDoCommand(引数は内部コマンドの文字列表現) とか、undoCloseTab とか、命名規則から引数から何から何まで統一されていないカオスが揃っていて調べるのがとってもめんどくさい。

次にスペースキーに、ビューの高さの半分だけスクロールする機能を割り当てる。ただし、すでにスクロールできる末尾に達していた場合は何かをする。何かって何かといえば、Presto Opera に倣って言えば

  • [cci_html][/cci_html] な要素があればそれが指すアドレスへ移動する
  • 次へ とか next とか書いてあるそれっぽいリンクがあったらそのアドレスへ移動する
  • 画像っぽいものリンクをリストアップし、連番にソートし、順番にそのアドレスへ移動する。すべて辿ったら元のページに戻る

的な、よろしくやってくれる系の機能だ。ただこれ、link 要素程度ならともかく残りを実装するのかなり大変なので後回し。

それと、この機能は赤福プラスと少し関係する。赤福プラス側ではすでにスクロールできる末尾に達していた場合は続きを読むようにしているのだ。しかしスペースキーを KeySnail 側で消費してしまうと赤福プラス側のそれが呼ばれることはない。そこで、何かするのに先立って RequestMoreContent というカスタムイベントをドキュメントに対して発生させるようにした。そのイベントが preventDefault() されていなければ、何かする……というふうにすればよい。もちろん赤福プラス側ではそのイベントをリスンし、続きを読み、preventDefault() するのである。

key.setViewKey('SPC', function (ev, arg) {
. let w = twin(ev);
. let d = tdoc(ev);
. if (w.scrollY >= d.documentElement.scrollHeight - w.innerHeight) {
. . let customEvent = d.createEvent('CustomEvent');
. . customEvent.initCustomEvent('RequestMoreContent', true, true, {});

. . if (d.dispatchEvent(customEvent)) {
. . . // 何かする
. . }
. }
. else {
. . w.scrollBy(0, Math.floor(w.innerHeight / 2));
. }
}, 'ビューの半分の高さだけスクロールダウン', false);

for Firefox #8

というわけで、ぼちぼち Firefox 版の赤福プラスが動き始めてきたので常用し始めてみる。まず、Firefox といえばエクステンションでいかようにもできるブラウザということなのだが、逆に言うと素のままで使うのはなかなか厳しいということである。とは言うもののエクステンション漬けになるのも後々困りそうなので、できるだけ最小限のものに絞って入れてみる:

  • AdBlock Plus
  • Gmail Notifier
  • Hide Caption Titlebar Plus
  • KeySnail
  • TabDeque
  • YesScript
  • ブックマークを新しいタブで開く
TOWN AGE はいいアルバムだと思います

TOWN AGE はいいアルバムだと思います

Presto Opera でやっていたようなキーバインドは、その一切を KeySnail で行うようにした。これはこれで奥が深いので keysnail.js の内容はいろいろやってみる必要があるのだけどとりあえずそれはさておき。

現役のブラウザということで、ナウいページでもちゃんと表示できるという安心感はあるのだけど。Presto Opera に比べてブラウザの動作の表も裏も javascript で制御される割合が大きいためなのかどうか、なんというか、Presto Opera に比べて全体的に 1 割〜 2 割増しで動作が緩慢な気がする。レンダリング速度自体は速い。しかしたとえば何がしかのボタンを押してから反応が返ってくるまでの間のようなものは Presto Opera に比べても微妙に鈍くさい。

Presto Opera は javascript の動作速度こそ最前線のブラウザに比べて一歩落ちるものの、レンダリング自体はまだ結構速いし、アプリケーション全体の速度もなかなかチャキチャキしてたんだなあと再認識した。Firefox は全くその逆の特性になっている。

また、数時間使っていると、そのせいなのかあるいは別の理由があるのかレンダリングがかくかくになってしまうことがあるような気がする。かくかくというのはつまり CSS Transition のようにせめて 30fps で安定して再描画してくれないと困るものがコマ送りのようになってしまうのである。なにがトリガーなのかはいまいちわからないがとりあえず適当な間隔で再起動すれば直る…のだけど、これじゃ Presto Opera と変わらない(こっちはこっちでソケットの解放をし忘れるという盛大なバグがあってやっぱり適当な間隔で再起動が必要)。

あとは、IME のハンドリングがまともなのがうれしい。Linux 版の Presto Opera のそれはもう、なんというか、テスターに正拳ぶち込みたくなるくらいめっちゃくちゃのしっちゃかめっちゃかなので……。

for Firefox #7

  • [cci]disable-output-escaping[/cci] にいよいよ手を入れる。これは何かというと、XSL による変換を行う際にすでにマークアップされているであろう文字列をそのまま出力するというオプションなのだけど、Firefox が内蔵する XSLTProcessor はこれに対応していない。

    XSLProcessor へのオプションの与え方といったレベルではワークアラウンドはない、と思う。

    というわけでどうするかというと、マークアップ済みの文字列を要素の内容としてではなく、要素の data-doe 属性に収めて出力させる(doe は disable-output-escaping の略)。得たフラグメントをドキュメントに追加した後、querySelectorAll(“[data-doe]”) で抜き出し、それぞれを insertAdjacentHTML() で挿入させるようにした。

  • xpi を生成するよう Makefile を追加した。生成物は https://github.com/akahuku/akahukuplus/tree/master/dist に置いた。ちなみにちゃんとインストールできるかはまだ試していない。