Add-on SDK to WebExtensions #5

ところが結局、Windows 上の Firefox で Selenium によるテストを行うところまで至っていない。現状では

  • Windows 上で
  • 任意の拡張を一時的に読み込ませつつ
  • そのプロファイルを使うよう Selenium に指示する

という術がないように思える。となるとテストのために一旦 xpi をビルドして、署名して、それを使うしかない。

ちなみに Selenium は現在 version 3 になっていて、Firefox に関しては Chrome の chromedriver.exe と同様 geckodriver.exe を介在させるようになっている。そのためにシステムプロパティ webdriver.gecko.driver に geckodriver.exe へのフルパスを書いておく必要がある。

ということで試してみたところ

File p = new File("C:\\path\\to\\profile");
WebDriver driver = new FirefoxDriver(p);

の呼び出し中に

Java heap space
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at org.openqa.selenium.remote.ProtocolHandshake.amendOssParamters(ProtocolHandshake.java:183)
at org.openqa.selenium.remote.ProtocolHandshake.createSession(ProtocolHandshake.java:61)
at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:141)
at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:82)
at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601)
at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:241)
at org.openqa.selenium.remote.RemoteWebDriver.(RemoteWebDriver.java:128)
at org.openqa.selenium.firefox.FirefoxDriver.(FirefoxDriver.java:259)
at org.openqa.selenium.firefox.FirefoxDriver.(FirefoxDriver.java:247)
at org.openqa.selenium.firefox.FirefoxDriver.(FirefoxDriver.java:242)
at org.openqa.selenium.firefox.FirefoxDriver.(FirefoxDriver.java:238)
at org.openqa.selenium.firefox.FirefoxDriver.(FirefoxDriver.java:131)
at WasaviTest.WasaviTest.createDriver(Unknown Source)
at WasaviTest.WasaviTest.beforeClass(Unknown Source)

……どうしろというのか。

 * * *

FirefoxDriver のコンストラクタに独自のプロファイルを与えるのではなくデフォルトのそれを使用するようにすると(要するに何も引数を与えないと)起動した。なにそれ。テストのためのツールである Selenium ががろくにテストされてない…。真面目にやってくださいよ本当に。

 * * *


FirefoxProfile p = new FirefoxProfile();
p.addExtension("/path/to/wasavi.xpi");
driver = new FirefoxDriver(p);

とやるととりあえず OutOfMemoryError で落ちることはないのだが、拡張を登録する処理で「アーカイブの中に install.rdf がないよ〜」的なエラーになる。この場合の wasavi.xpi は WebExtensions ベースの拡張なので install.rdf ではなく manifest.json を見に行ってくれないといけないのだが、Selenium 側がまだ WebExtensions ベースの拡張を知らない状態なのだろう。

うーん! だめじゃん。WebExtensions の拡張を Selenium でテストするのは時期尚早なのか。

Add-on SDK to WebExtensions #3

content script において window.Uint8Array を使おうとするとエラーになる。グローバルオブジェクトのそれを使わないといけない。両者は権限のドメインが違う、らしい。グローバルオブジェクトに Uint8Array が存在する場合はそれを、次に window に存在する場合はそれを使うようにした。

window. を前置するのをやめればいいじゃん、と思うかもしれないが、そうはいかない。赤福プラスでは window のプロパティを参照する際は必ず window を前置している。これは Presto Opera で動かすための措置で、Presto Opera の injected script では window がグローバルオブジェクトではないのである。したがって前置は必須なのだった。

それにしても、今 Presto Opera 使ってる人ってどんくらいいるのかなあ…? とはいえ赤福プラスはまだ Presto Opera もサポートしているので、動作確認のために新しい環境にも 12.16 を落としてインストール。

ところでメモやブックマークを同期しようとログインしようとしたらどうやっても弾かれるんですけど…。

Firefox の方は SDK 版から WebExtensions 版への自動更新が滞りなく行われることを確認。

さてこの SDK -> WebExtensions の作業において赤福プラスは前座でしかない。本丸は wasavi なのである。

Add-on SDK to WebExtensions #2

一通り動くようになった。

すでに上げた Port の引数の問題の他、content script で生成した XMLHttpRequest の動きが変。

  • Referer が吐かれない
  • Cookie が送出されない
  • open() の際、相対 URL を与えるとエラーになる

同様の問題を抱えている人がいた。そのやり取りによると、

try {
return XPCNativeWrapper(new window.wrappedJSObject.XMLHttpRequest());
}
catch(evt){
return new XMLHttpRequest();
}

こんな感じで Firefox に限り意味の分からない書き方をすることで解決できるそうな。確かにこれでうまく行くけど…早く直してほしい。

ところでもうひとつ、ID の問題がある。Chrome のエクステンションは基本的にそれを識別する ID というものを開発者が意識する必要はない。エクステンションをローカルで crx にパッケージングした際に生成される .pem ファイルは取り扱いに気をつける必要はあるが、今日ではローカルパッケージングは zip で事足りるので .pem ファイルを生成する必要自体が基本的にない。

WebExtensions の場合は一応その流儀に倣っているが、しかしやはり Firefox の拡張らしく、ID は裏で健在だったりする。というのも署名を mozilla のサーバにつけてもらうと .web-extension-id という ID が書かれたドットファイルをもれなくプレゼントされるのである。うーんなんかイケてなくないですか。

一方で陽に ID を取り扱うこともできる。manifest.json の applications に

"applications": {
"gecko": {
"id": "jid1-ytdk6oePtVeu1A@jetpack",
"strict_min_version": "42.0",
"update_url": "https://github.com/akahuku/akahukuplus/raw/master/dist/firefox.json"
}
},

とこんな感じに書けばいい。ただよく分かんないことに、こう明示したとしてもやはり .web-extensions-id ファイルはプレゼントされる。いらないんですけど!

ところで上記の ID、いかにも Add on SDK 製の拡張っぽい書式だが、実際に SDK 版の赤福プラスの ID だ。というのも、おそらく流用しないと SDK 版から WebExtensions 版へのスムーズな自動更新が行えないからだ。うーんなんだかかっこ悪いなあ…。

Add-on SDK to WebExtensions

Firefox の拡張は本格的に WebExtensions ベースへの移行が始まっていて、一応のロードマップでは来年の 11 月には Firefox 57 において WebExtensions ベースではない旧来の拡張の読み込みをしないようになるという

拙作について考えてみると、あべアニの拡張の Firefox 版は WebExtensions で新規に書いたのだけど、既存の Addon SDK ベースの拡張は移行作業というものをしないといけない。具体的には赤福プラスと wasavi だ。あとは Webliopane だけど…これはどうかな。そもそも人様のものだし。

で、赤福プラスと wasavi に関しては SDK ベースではあるが、もともと Kosian という Chrome の拡張に近づけて書くためのラッパをかましているので、それぞれの拡張自身のコードに関しては大掛かりに書き換える必要はない。Firefox においてはただ単に動作の基盤を WebExtensions に差し替えればいい、はず、だ。たぶん。

というわけで、とりあえず赤福プラスをちまちまといじり始めたのだが。実際アプリケーションとしてのコードの修正よりむしろ WebExtensions の微妙な発展途上さに振り回されている。

  • まず content script と background の間を long-lived port で接続している。この際、background では port の onMessage イベントハンドラを

    function handlePortMessage (message, port) {
    ;
    ;
    }

    という風に定義している。最初の引数が飛んできたメッセージ、次の引数が使用された runtime.Port オブジェクトだ。このイベントハンドラの引数リストは Chrome の API ドキュメントに記述してある。

    で、それを Firefox の WebExtensions で動かすと、第 2 引数である port が渡されてこないのであった。これはきっとバグだろう

箇条書きにした割に 1 つしか書いてないが、きっと開発を進めるごとにいろいろ出てくる。

ところで web-ext ツールというものがあり、これは従来の cfx とか jpm に相当する。これで [cci]web-ext run[/cci] とすると一時的なプロファイルによって Firefox が起動する。開発中の拡張は自動的に読み込まれている。そして面白いことに、その状態でソースファイルを更新すると拡張が自動的にリロードされる。これはちょっと便利。

Mysterious bound keyword

Firefox 46a だったか 47a だったか忘れたが、wasavi と赤福プラスが両方動かなくなったことがあって調べてみた所、

  1. 両方が使っている extension_wrapper.js 内で、それが Firefox + Add on SDK で動いているかどうかを判断している箇所があり
  2. Firefox のその該当バージョンで判断のもとになっている箇所が変更されたため

動かなくなっているということだった。

extension_wrapper.js は Chrome でも Opera でも Firefox でも共通して動くので、それぞれのブラウザのうちどれで動いているのかを判断しないといけない。Chrome なら window.chrome が存在しており、Opera なら window.opera が存在しているのでそれが判断の助けになる。。

一方 Add on SDK の PageMod における content script かどうか、というのを判断するためのあんしんあんぜんな方法はないように思える。とりあえずのところ、self.on が存在し、それが function であり、かつその toString() の値が “function on (.*?) { [native code] }” とかそんな感じになっていればまあたぶんきっと SDK ベースの content script だろう…というような判断をしている。”[native code]” がミソなのだが、これが最適解というわけでは全然ない。

これが、今回動かなくなってしまったのである。それは、self.on の toString() の値が “function bound on (” というように、謎のキーワード bound が入るようになっていたからだ。bound……って何?

vi flavored browser UX #3

実は今現在最新の Firefox Developer Edition 46.0a では Keysnail が動作しないので、とても不便である。

せっかく Chrome 上での vi flavored なエクステンション群の評価をしたので、cVim をベースに uBlock、ScriptBlock 等を入れて色々整えてみた所割合快適な感じになってしまって、あれ? これ Firefox もう窓から投げ捨ててしまってもいいんじゃないの? という気になりつつある。

従来、どういうわけか Linux 上で Chromium(Opera も)を動かすとオムニバー上のキー入力が耐えられないほど重かった。また Chrome ではオムニバー周辺をエクステンションからいじることはほとんどできない。アドレスを編集するときに [cci]^F[/cci] [cci]^B[/cci] [cci]^H[/cci] 位は使いたいのだ。そのあたりが Firefox を常用ブラウザに選んだ理由だったのだが、オムニバーの代わりに cVim のコマンドラインを代用すれば今まで感じていた不満が何とかなってしまうわけで、そんなわけで 3 日に 1 回くらい落ちる Firefox Developer Edition を常用する理由がなくなってしまった。

vi flavored browser UX

テストのために VimFx を入れたり外してたりしたわけだが、普段はこの手の、ブラウザのインターフェースを vi 風味にする拡張というものを使っていない。正確には、Keysnail によって最低限の vi っぽいショートカット(hjlk とかその程度)を付け足してはいるが、その程度だ。hit-a-hint などのナウい機能は使わなくても何とかなっている。

これはなぜかと改めて振り返ってみると:

  • トラックポイント付きのキーボードを常用しているので、そもそもポインティングデバイスとキーボードの併用にストレスがあまりない
  • wasavi の動作テストをするにあたって、特定の拡張の影響を排除したい
  • この手の拡張を入れると、web ページが個別に定義するキーボードショートカットと拡張自身のショートカットのどちらを優先するかという問題が必ずつきまとう。この手の問題に煩わされたくない

ということだろうと思う。

そういうわけで個人的な要求としては、「ブラウザのナビゲーションにおいて vi っぽいショートカットは多少はあれば嬉しいけど、おおがかりに vi っぽくはしなくていいし、してほしくない」ということなのである。現在常用している Firefox においては、それは Keysnail で自分でちまちまとスクリプトを書けば対応できる。一方で Chrome はどうだろうか。Chrome も常用とはいかないが、それなりに使っている。

まず vi flavored なエクステンションはどうだろうか。Chrome におけるその手のエクステンションを列挙してみると:

…なんでこんなにあるんだ?

Chromium versus Firefox on xubuntu

かつて、Presto Opera からの移行先をいろいろ検討した結果、Firefox を選択したわけなのだが、残念ながら、Firefox がベストなブラウザというわけでは全然ない。

xubuntu のノート上で Firefox を起動し、ノートをサスペンドし、リジュームする。そうすると困ったことにやたらノートが発熱する。不思議なことに load average は特段変化はないのだけれど、しかしノートの底面は信じられないほど熱くなる。Chromium ではそういうことはない。

そのほかにも、Linux 版の Firefox はなかなか不安定だ。入れているのが Developer Edition というのを差し引いたとしても週に数度は落ちるし、Flash のコンテンツは「運が良ければ見られる」レベルだし、そもそも Linux 版の Flash をどうするのかステートメントがいまいちはっきりしないし、製品はもとより拡張の行く末を始めとして Mozilla の動き自体が迷走に迷走を重ねていて、Mozilla というブランドの信用性はもう本当に、急降下している。

そんなわけで Chromium もちょっとずつ使うようにしているのだけど、困ったことにこちらにもいくつか問題があって、オムニボックス上の文字入力がやたら重いのである。キーを 1 つ打って、そのレスポンスが帰ってくるまでに数秒かかる。これはもしかしたら、いわゆる「おま環」なのかもしれない。ググってみるとプロファイルを一新すると治る的な記事もある。しかしこの現象、うちの環境では Chromium だけではなく Opera Developer でも同じなのだ。それなりに普遍的な不具合ではないのだろうか。

weblioPane fixed #2

以前 weblioPane の変更版を作ったのだが、それ以来いくつかのリクエストがあって:

  • pdf 上で動作しない(オリジナルはできた)
  • mht 上で動作しない(オリジナルはできた)
  • EPubReader 上で動作しない(オリジナルは不明)

なぜオリジナルの weblioPane で出来ていたことが、変更版ではできなくなっているかというと、これは選択範囲が生成されたことを検知する仕組みを変えたからだ。

オリジナル版では SDK が提供する selection モジュールを使用しているのだが、しかしこのモジュール、e10s を有効にすると動かなくなる。この不具合は Mozilla 内でも認識済みのようなのだが、どういうわけか一向に治る気配がない。

そんなわけで、変更版では selection モジュールではなく PageMod モジュールでコード片を各ページに差し込み、選択範囲が生成されたイベントを拡張側に通知するようにしてある。従って、変更版で動かなくなったページがあるとしたらこの変更点が最も怪しいということになる。

cfx to jpm #2

などといいつつ、jpm の動作をいろいろ見ている。

cfx と同様、[cci]jpm xpi[/cci] とすることで xpi を生成することができる。ただし、[cci]–pkgdir[/cci] オプションや [cci]–update-link[/cci] オプションがどういう訳か削除されている。前者は、xpi をアーカイブする際のベースディレクトリを指定する。これがないということになっても、単に [cci]cd && jpm xpi[/cci] などとすればよいのでそれほど問題ではない。しかし後者が問題だ。

[cci]cfx xpi[/cci] においては以下のオプションが有効で:

  • –update-link: 拡張を自動更新する際の最新の xpi ファイルの場所を指定する。このオプションを指定すると、cfx は update.rdf を生成する
  • –update-url: 拡張を自動更新する際の更新情報を示す RDF ファイルの場所を指定する。この情報は xpi 内の install.rdf に埋め込まれる

この違いはとてもわかりにくい。前者は [cci]–location-of-latest-xpi[/cci]、後者は [cci]–location-of-update-manifest-rdf[/cci] などと脳内で変換する必要がある。

で。jpm では前述の通り [cci]–update-link[/cci] オプションが削除されている。従って、jpm は update.rdf を一切生成しない。そのため jpm ベースの開発サイクルでは update.rdf を自前で生成しなければならなくなっている。なぜこのような仕様変更が行われたのかはよく分からないが、自前で update.rdf を制御する場面というのは、つまり拡張を AMO 以外の場所で配布する状況ということだ。AMO に載せるのなら気にする必要はない。つまり update.rdf の生成機能が取り除かれているのは野良拡張の配布をやめろという暗黙的な圧力なのかもしれない。

この手の悪意に満ちた嫌がらせはこれだけではない。拡張に自前で署名を施すためのツールとして McCoy というものがあるのだが、これもあからさまに使いにくい。そして、「使いにくいけど将来的には直すので期待してね!」なんつったりしちゃったりしてるのだが、もちろん今現在になっても使いやすくなってはいない。

一方で Mozilla は

インストールを Mozilla の配布チャンネルに限定することは余計な制約であると私たちは考えます

とかなんとか言っている。なーんか、言ってることとやってることが全然違いますね。こんなことばかりやってると、加速度的に信用を失うと思うんですけど。

それはさておき、update.rdf のテンプレートから任意の箇所を package.json の値で置換するような適当な javascript のスクリプトを書いた。javascript で書いたのは、jpm 自体が node.js で動かすスクリプトなのでそれに合わせて。

テンプレートとしてこういうものを用意して






  • {{version}}


    {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
    {{engines.firefox.minVersion}}
    {{engines.firefox.maxVersion}}
    https://example.org/example.xpi






  • package.json とテンプレートを両方読んで、プレースホルダ({{〜}} の部分)を package.json の該当する値で埋める。package.json に値が存在しなければ、プレースホルダを含む要素自体を空に置き換えるようにした。

    とりあえず webliopane fx40fixed を jpm でビルドするようにしてみた。