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 に置いた。ちなみにちゃんとインストールできるかはまだ試していない。

for Firefox #6

Add-on SDK で作る拡張機能のうち、バックグラウンドで動くコードはモジュールとして作り、複数モジュールを用意した場合はそれぞれが非干渉になるようになっている。そういう仕組みとして固定されている。モジュールの機能を呼び出す場合は明示的にエクスポートした関数を通す。

で、そのエクスポートされた関数を呼び出した際に不思議なことが起きた。Kosian を初期化するために RegExp のインスタンスを含んだハッシュオブジェクトをオプション群として渡す。その RegExp インスタンス、どういうわけか instanceof RegExp が真にならないのである。なので一部の初期化が正しく行われない。相変わらず恐ろしく重くて遅い内蔵デバッガで覗いてみたりする分には普通に正規表現オブジェクトだし、Object.prototype.toString.call() してみても [cci][Object RegExp][/cci] が返ってくる。しかしながら instanceof RegExp は偽になる。

なんか嫌な感じ。

ちなみにモジュール内の関数で生成した正規表現オブジェクトをその場で判定すれば当然 instanceof RegExp は真になる。モジュール間での関数呼び出しの際に引数が何か操作されてる感じなのだ。嫌だなあ。なんか嫌な、品のないブラウザだ。

for Firefox #5

またしても window と unsafeWindow の違い。Add-on SDK のコンテントスクリプトにおいては、window はおなじみの Window ではない不思議なグローバルオブジェクトである。ページのグローバルオブジェクトとしては unsafeWindow を参照する。なぜそんな殺風景な命名なのかはわからない。contentWindow でいいと思うのだけど。

  • すでに何らかの動作にイベントリスナが結び付けられている時にそのリスナを呼び出したい際、リスナを直接呼び出すのではなく、コードから擬似的にイベントを発生させる手法をよく使う。リスナがスコープになくてもよくなるので。そういう場合、document.createEvent(…) して、init*Event(…) して、目的の要素に対して dispatchEvent(…) する。これが上手く行かない。init*Event() の引数内に view オブジェクトとして window を渡すとエラーになる。解法は window.unsafeWindow が存在する場合はそちらを渡す。
  • CustomEvent に対しても同様。
  • pushstate イベントをリスンしている。その状態で location.hash を更新すると Firefox でだけ pushstate イベントが発生する。どのブラウザで動作している場合hも、location.hash を更新するときは一時的に pushstate のイベントリスナを取り外すよう修正。

for Firefox #4

  • favicon に、スレ画像から canvas を経由して動的に生成した data スキームの文字列を割り当てているのだけど。ImageData オブジェクトのプロパティにアクセスすると Firefox ではよくわからないが権限の異なるゾーンをまたいでるよ的エラーになる。そんな内部構造から来る制限でエラー出されても困りますよ。

    似たような不具合によれば、new window.ImageData や canvas のコンテキストの createImageData() ではなく、new unsafeWindow.ImageData() とすると動く。ふーん。

  • CSS Transition を多用しているのだが、transition プロパティでショートハンド指定すると発生しない。transition-duration 等々で個別に指定すると発生する。なにそれ。まるで IE6 とかがやらかしそうなしょっぱいバグですけど……。

    それと、実際に確認したわけではないのだけれど、transitionend の発生するタイミングがおかしいとレポートしている blog の記事があったりする。本当ならひどいもんだね。

for Firefox #2

例えば続きを読んだ際に、最新のレス数を反映させたりする。それを行うには、簡単に言えば
レス
みたいなマークアップに対して、javascript で

document.getElementById('replies-total').textContent = '100';

などとすればいいわけだ。

しかし実際にはこの手のコードは赤福プラスの中にはない。実際のマークアップは
レス
などと binding 属性が付加してある。それで、汎用的な再バインド関数を呼ぶことにより、binding 属性に応じて自動的に更新させるようになっている。再バインド関数内では、バインドの接頭辞が [cci]xpath:[/cci] であればふたばのサーバが返した html をスクレイピングした結果の中間 xml からの xpath の結果、あるいは [cci]template:[/cci] であれば中間 xml をパラメータにして与えた XSL の変換の結果を用いて要素の内容を置換する。

この記事の話題はその、文書の一部分を更新するために呼ばれる XSL 変換だ。文書の一部といっても、変換に際して使用する一時的な文書は html 要素から始まるまっとうなそれである。つまり





とこんな感じ。この変換結果を、XSLTProcessor#transformToFragment() を用いて DocumentFragment として得る。

この時、返ってくる要素のツリー構造が Firefox とその他で微妙に違う。Presto Opera と Chrome は、DocumentFragment の子要素として body 要素の子要素を割り当てる。一方 Firefox は、単純に DocumentFragment の子要素として html 要素を割り当てる。

どちらが正しいのだろうか。正しさというのは違うか。そもそも ブラウザの javascript から利用できる XSLTProcessor のインターフェースは誰が考えて管理してるのかよくわからない。とりあえず Opera と Chrome の動作のほうが、そのまま単純に DocumentFragment を appendChild() の引数にできるので楽だし、何より多数派だ。一方で、動作としては Firefox のほうが素直ではある。body 要素を特別扱いします的な例外中の例外動作は何なんだろうか。DocumentFragment はあくまで断片なので html/head/body といった大物は対象にしない、みたいなルールがあるのかもしれない。しかし誰がその仕様を決めているのかよくわからない。

そんなわけで、得た DocumentFragment が body 要素を持っていたら、さらに body の子要素を DocumentFragment としてくくり出すようにした。くくり出す時に Range を使うのだが、これがちゃんと DocumentFragment にも作用するのか確証は持てない(DocumentFragment は Document のいわばサブクラスなので Range による操作も理屈の上では互換性があるはずなのだが、DocumentFragment に対する操作というのはけっこうどのブラウザも怪しい、あるいは怪しい時期があったのだ)。でもまあ、とりあえず動いているようだ。

function fixFragment (/*DocumentFragment*/f) {
var bodies = f.querySelectorAll('body');
if (bodies.length == 0) return f;
var r = document.createRange();
r.selectNodeContents(bodies[0]);
return r.cloneContents();
}

for Firefox

そんなわけで、Chrome で動かすのは割とすんなり行ったので、いよいよ Firefox 上で動かしてみるのだけれども。

やっぱり一筋縄では行かない。例えば、赤福プラス ver.3 以降というのはいろいろな場面で XSLTProcessor が大回転するのだが。XSLTProcessor に食わせる xml は、そのソースが最初にページをロードした際は body.innerHTML、XHR で続きを読んだ際はふたばのサーバが返す html そのものと微妙に違う。なので、それぞれがほぼ同じものじゃないとちょっと困るのである。そして、Presto Opera と Chrome の innerHTML は期待通りの動作をする。つまり body.innerHTML を参照したとしても、元のソース文字列に出来るだけ同じものを返してくれる。

が、どういうわけか Firefox の場合は innerHTML は勝手に属性の定義順を書き換えたものを返してくれたりするようなのだ。どういうポリシーがあってそんなことをしでかしてくれるのかはよくわからない。html から中間 xml を生成する処理の内容はすなわち、ソース文字列を正規表現でパースするのである。したがって属性の定義順が不定だとか、パターンで表現できる範囲を超えられるとちょっと困るのである。

現役の web ブラウザが複数ある中で、それぞれが得手不得手があるのは自然なことである。しかしあくまで個人的な感想ですが、Presto Opera に関してそういう場面に出くわした場合、「おうおうお前はこれは苦手なのかうんうんかわいい奴め」という気分になるのだけれど(Presto Opera がもはや現役ではないという事情もあるのだが)、Firefox に対しては「てめーこの程度のこともできねーのかよ! 何年 web ブラウザやってんだアホッ!」と思う。あくまで個人的な感想です。

Firefox の XSLTProcessor に関しては、以前の記事にあるように [cci]disable-output-escaping[/cci] が最大の障壁だと考えていたのだが、そこに至る前にもけっこうな壁があるのである。ああ。

for chrome

赤福プラスを Chrome でも動くようにちょこちょこっと調整している。

まず Kosian の HEAD に追従させるべくバックエンドの微調整。これは特に問題はない。めんどくさいのはフロントエンドで、フロントエンド側というのは開発の極々初期に html のレイアウトを Chrome や Firefox で確認したくらいで、それ以降の全ては Presto Opera でしか動かしていないのであった。特に、XSLTProcessor という素敵な処理系の動きが Chrome でも Opera と同様なのかどうか動かしてみないとまったくわからない。

などと予想していたのだが、割とあっさり動いた。Chrome が内包する XSLTProcessor は xsl:import や xsl:include に対応していないなどの罠があるらしいが、今のところそれらは使っていないので大丈夫。ただ XSLTProcessor#translateToFragment() に失敗した場合、エラー情報を含んだ xml 文書を返してくれるのはどうなんですか。普通に素直に例外出していいんじゃないのかな。

最近、自由帳画像ばかり作っている

最近、自由帳画像ばかり作っている

次は Firefox で動かすようにするのだが、むしろこっちが本命なのだが(個人的な常用ブラウザを Presto Opera から Firefox に移行させつつあるので)、これまでの移植の経験から鑑みるに、たぶんこっちはいろいろとめんどくさい問題が出まくる。例えば [cci]disable-output-escaping[/cci] とか。

kosian is here

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

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

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

add a ad

赤福プラスを公開したわけだが、いくつか積み残している、つまり未実装のものがある。

まず、「自動追尾」なる機能について、ページ上にリンクはあるものの、その機能本体をまだまったく実装していない。それどころか、どういう仕様にするかもまだ決めていない。ぼんやりと思い描く分には、これはつまり自動追尾属性をスレッドに与えることにより、適当な間隔で自動的に「続きを読む」機能を実行し、加えてスレッドが落ちたらやはり自動的にそのログを MHT 形式で dropbox などに保存する……というものだ。なお、あらゆるスレッドにこの属性を付与されるとなかなかトラフィック的な問題もあるので、同時に登録できるのは n スレッドまで、みたいな制限は加えることになると思う。

次に、虹裏 may などに存在するお絵かき機能への対応をまだしていない。以前も書いたが、赤福プラス version 3.0 以降では、ページの内容が完全に赤福プラスの制御下にあるという構造になっている。これにより様々なメリットを享受できるのだが、一方でふたばのサーバが返す生の html に含まれる javascript コードでやっていることの再利用ができず、赤福プラス側で同じ処理を書かなければいけないというデメリットもある。で、お絵かき機能の処理をまだ書いていないということなのである。具体的にその処理とは、単にお絵かき要素の可視・不可視を切り替えるだけのことだ。

じゃあ、書けばいいじゃん! ということなのだが、いくつか気になる点がある。このお絵かき機能はあんまり高機能なものではないのである。これは、ふたばの管理人さんが「あえて」低機能なままにしているのだと思うが、別に高機能なオプションがあってもいいのではないか。また、お絵かき機能は flash で実装されているので、ハンドリングがややめんどいというのもある。

そこで、html5 の canvas を活用した、もう少しだけイケてるお絵かき機能を実装すればよいのではないかと考えている。加えて、ふたばに貼られた画像を引用する機能も欲しい。これにより、スレッド上でお絵かきのコラボレーションみたいなものが簡単に実現できることになる。

実は Opera に canvas が実装されてすぐ、レイヤーや自由領域選択やフィルタやその他もろもろといった、だいたい Photoshop Elements に相当する機能を備えたそういうアプリを書いてみたことがあるので、おそらくそれを持ってくることになると思う。

最後に気になっているのは、ふたば上の広告の扱いである。というわけでやっとタイトルに絡めることができたのだが、力尽きたのでまた明日。