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 #4

というわけで、Firefox 版の wasavi も WebExtensions の基盤のもとで動かしてみて、とりあえず起動することは確認したのだが。すべての機能が正しく動作するかどうかは、機能テストを通して動かさないと保証できない。

従来この機能テストというのは Windows7 マシンで行っていた。しかし過去の記事のとおり、このマシンには Windows10 を新規インストールしたので過去の環境はさっぱり残っていないのであった。なので、新しく構築しないといけない。

wasavi の機能テストは Selenium を使っていたのだが、この環境を新たに構築するのはものすごく面倒くさい。Java SDK を入れて、Ant を入れて、Selenium の java バインディングを入れて、JUnit を入れて、OperaDriver を入れて、ChromeDriver を入れて、適当な www サーバを入れて、make を入れて、ソースを rsync して……という具合だ。ものすごく面倒くさい。ものすごく面倒くさい。インストールした後も更に面倒くさい。Selenium や ChromeDriver は割とちょくちょくアップデートされていて、しかも最新の組み合わせじゃないと動かなかったりするからだ。アップデート作業に煩わされたくないし、そもそももはや Windows マシンは開発には使ってなくて、時々地デジのテレビを見るためのものでしかない。余計なものを入れて不安定にさせたくない。

とはいえ機能テストはせざるを得ない。なんとかせめてインストールとアップデート作業だけでも楽に出来ないだろうか……。ということで調べてみたら、なんと Windows10 では OneGet、あるいは PackageManagement という標準のパッケージシステムが用意されているという。それが従来からある、chocolatey などの MS 公式ではないパッケージシステムを取り込む仕組みになっているという。ほー、これはなかなか野心的でいいんじゃないの。

というわけで試してみた。ちなみにこの文書がよくまとまっていてわかりやすい。…のだが。しかし使ってみてまだなんとも洗練されてないなという感じがした。件の文書のとおり動作が不確実なところがあったり機能が足りなかったりする。それからたとえば JDK であれば 32bit、64bit のどれを入れるかみたいな細かい指定がどうすればいいのかわかんない。chocolatey であればそういうスイッチが用意されているが PackageManagement でそれが有効なのかよくわからなかった。あと基本的に PowerShell で作業するのだが、PowerShell いいと思うけどおっそいんだよね……。

そんなわけで chocolatey を直接使うことにした。これの管理のもとで入れるのは:

  • jdk8
  • ant
  • selenium-all-drivers
  • node.js
  • googlechrome
  • opera
  • firefox

など。不思議なことに、Selenium の公式のクライアントライブラリの 1 つである java バインディングは chocolatey に登録されていない。それからこの辺はよく知らないのだけど java で JSON は標準では使えないようなので、適当にググッて json-20140107.jar なるものを調達してこないといけない……等々、いくつかのものは個別に入れる必要がある。うーん java バインディングも chocolatey の管理下になんとかならないものかな。

あとは Cygwin 側で

  • make
  • wget

が必要。

それから npm で

  • web-ext

を入れる。

いやはや大変だ。

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 版へのスムーズな自動更新が行えないからだ。うーんなんだかかっこ悪いなあ…。

psd

psd といえば Photoshop のファイルだが、この記事の psd は profile-sync-daemon である。

これは

  • (systemd の)デーモンとして動作し、ブラウザの起動を監視する
  • 動作開始時、ブラウザのプロファイルディレクトリを tmpfs にコピーし、元のプロファイルディレクトリはコピー先へのシンボリックリンクへと置き換える
  • これにより実質的かつ透過的にブラウザはメモリ上のプロファイルを使用することになる。結果、体感速度の向上と、デバイスへの書き込み頻度の低減がもたらされる
  • tmpfs 上のプロファイルは適当な間隔(標準で 1 時間毎、変更可)で実際のファイルシステムに書き戻される

というもので、HDD なマシンでは体感で分かるほど明らかにブラウザのレスポンスが良くなる。すごい。SSD ではそれほどでもないが、その代わり書き込み頻度の低減が非常に有効。ブラウザのプロファイルディレクトリというものはそれはもう常時ものすごい勢いで読み書きされているものなのです。

Ubuntu での使い方:

  1. PPA からインストール
    $ sudo add-apt-repository ppa:graysky/utils
    $ sudo apt-get update
    $ sudo apt-get install profile-sync-daemon

    ※Ubuntu 16.10 からは公式のリポジトリに入っているそうなので [cci]$ sudo apt-get install profile-sync-daemon[/cci] でいい、はず
  2. ホームディレクトリに設定ファイルの雛形を書き出す
    $ psd
  3. エディタで編集
    $ vim ~/.config/psd/psd.conf
  4. 設定ファイルをパースさせてみる
    $ psd p
    Profile-sync-daemon v6.31 on Ubuntu 16.04.1 LTS

    Systemd service is currently inactive.
    Systemd resync-timer is currently inactive.
    Overlayfs technology is currently inactive.

    Psd will manage the following per /home/akahuku/.config/psd/.psd.conf:

    browser/psname: chromium/chromium
    owner/group id: akahuku/1000
    sync target: /home/akahuku/.config/chromium
    tmpfs dir: /run/user/1000/akahuku-chromium
    profile size: 56M
    recovery dirs: none

    browser/psname: firefox/firefox
    owner/group id: akahuku/1000
    sync target: /home/akahuku/.mozilla/firefox/2ttp4k9g.default
    tmpfs dir: /run/user/1000/akahuku-firefox-2ttp4k9g.default
    profile size: 7.0M
    recovery dirs: none

  5. デーモンとして登録して実行開始
    $ systemctl --user start psd
  6. 自動起動するようにする
    $ systemctl --user enable psd
    Created symlink from /home/akahuku/.config/systemd/user/default.target.wants/psd.service to /usr/lib/systemd/user/psd.service.

あとは普通にブラウザを起動すればいい。

A new machine

Thinkcentre M71z というものを 1 万円で入手したので開発はそれで行うようにしたい。今となっては若干古めの、第 2 世代 Core i3 ではあるが、今開発に使ってるマシンよりベンチマークの数値は 5 倍くらいアップする。そんなに。

というわけで、ぼちぼち環境を整えないといけないのだけど。
めんどくさいなあ。
「」がうちに来て代わりにやってくれればいいのに。
おっぱいくらい触らせてあげるから。

ちなみにその 1 万円のうちの幾ばくかは投げ銭で頂いたお金を使わせてもらった。この場を借りて感謝したい。

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 が起動する。開発中の拡張は自動的に読み込まれている。そして面白いことに、その状態でソースファイルを更新すると拡張が自動的にリロードされる。これはちょっと便利。

creating NTFS native symbolic link in cygwin #2

以前 Cygwin でシンボリックリンクを作るにはどうすればいいか調査したことがあった

月日は流れ、ついに Windows 10 では(開発者モードに限り)特権のエスカレーションを経ることなく一般ユーザのままシンボリックリンクを作れるようになるそうな。

へえ。しかし個人的にはもうどうでもいいトピックだったりする。というのも一応 Cygwin は入れているが、もはや Windows 上で何か開発するということが殆どない。

ちなみにその更新がもたらされる Windows 10 Creators Update は、2017年3月にリリースの予定だそうな。

The TV program time table #9

虹裏でアナウンスした際に Vivaldi で動かした時、お気に入りのチャンネルを定義する場合のドラッグ&ドロップが正常に動作しないという報告を受けた。

調べてみると、ドラッグ&ドロップのためにはいくつかのイベントを処理しないといけないわけだが、その中でドロップされた際の drop イベントと、ドラッグプロセスが完全に終了した際の dragend イベントの発行される順番の問題のようだ。

仕様では、常に drop -> dragend の順番であり、あべアニもそういう前提で組んであり、実際 Chrome、Opera、Firefox、Edge でテストした際もそういうふうに動作した。Vivaldi ではテストしなかったのだが、これは Vivaldi は結局 Chrome ファミリーであり、Chrome で動けばきっと多分 Vivaldi でも同様のはず…という判断なのだが、びっくりすることに Vivaldi では drop イベントが最後に発行するようなのである。

なんで…?

前述の通り drop -> dragend なのはそういう規格だし、Chrome 自体がそういうふうに動作するのに、何を意図して変更したんだろう? 分からないがとにかく、いずれの順番でも正しくドラッグ&ドロップが動作するように修正。

The TV program time table #8

Firefox 版は AMO に置くのを諦め、ここでホストすることにした。

Chrome 版のソースを Firefox の WebExtensions のシステムで動かす際、以下の点が Chrome と異なる。

  • [cci]chrome.runtime.onInstalled[/cci] がまだない。したがって、
    'onInstalled' in chrome.runtime && chrome.runtime.onInstalled.addListener(function () { });
    みたいな書き方をする必要がある。
  • content script で生成した CustomEvent にオブジェクトを含んだ detail を与え、ディスパッチする。それをページスクリプトでリスンすると、detail が正しく引き渡されない。Firefox では権限の異なるスクリプト間ではプリミティブな型以外のものを渡せないようだ。そんなわけで、Firefox で動かす際は detail には JSON.stringify() したものを与え、受け取る際も JSON.parse() しないといけない
  • ソースはほぼ Chrome 版と共有できるが、パッケージングはかなり異なる。ストアに登録する際は Chrome 同様にソースディレクトリを単に zip するだけでもよいが、web-ext ツールを用いてビルドと署名をコマンドラインから行う方法も用意されている。
  • 自前でホストする場合、更新情報も自分で管理しないといけない。そのために、manifest.json に

    "applications": {
    "gecko": {
    "id": "abeani-extension@appsweets.net",
    "strict_min_version": "42.0",
    "update_url": "https://appsweets.net/abeani/updates.json"
    }
    },

    というものを含める必要がある。このエントリは、Chrome は無視するのでこの manifest を Chrome に与えてもエラーにはならない。updates.json の内容は

    {
    "addons": {
    "abeani-extension@appsweets.net": {
    "updates": [
    {
    "version": "1.0.8",
    "update_link": "https://appsweets.net/abeani/extension/AbeaniExtension.xpi"
    }
    ]
    }
    }
    }

    こんな感じ。

    調子に乗って Edge でも動かしてみたのだが、まず Edge は chrome.alarms がないので普通のタイマーで代替しないといけないのだが、それは代替手段があるのだから別にいい。だがなんと、content script から chrome.runtime.sendMessage() でバックグラウンドにメッセージを送る機能がまだ実装されていない。これがないとどうにもならない。ひどい。というわけで Edge 版はしばらくペンディングだ。MS さん真面目にやってくださいよ。