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 を登録したプロファイルを用意する必要がある。

Testing with selenium javascript binding #2

mocha のテストをすべて終えると最後に結果が表示される。こんなふうに:

basic test -- 2 tests, 50.00%
--------------------

1 passing (2s)
1 failing

1) basic test should get the title of test frame page:

AssertionError: 'wasavi test frame' == 'wasavi test frame?'
+ expected - actual

-wasavi test frame
+wasavi test frame?

at Object.eq (src/wd-tests/src/all-tests.js:359:10)
at Context. (src/wd-tests/src/basic-test.js:11:10)
at Generator.next ()
at pump (/home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:3221:25)
at callNext (/home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:3207:7)
at ManagedPromise.invokeCallback_ (/home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:1366:14)
at TaskQueue.execute_ (/home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:2970:14)
at TaskQueue.executeNext_ (/home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:2953:27)
at asyncRun (/home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:2813:27)
at /home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:676:7
at process._tickCallback (internal/process/next_tick.js:103:7)
From: Task: basic test should get the title of test frame page
at Context.ret (/home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/testing/index.js:185:10)
at /home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/testing/index.js:104:5
at ManagedPromise.invokeCallback_ (/home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:1366:14)
at TaskQueue.execute_ (/home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:2970:14)
at TaskQueue.executeNext_ (/home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:2953:27)
at asyncRun (/home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:2813:27)
at /home/akahuku/.nvm/versions/node/v7.2.1/lib/node_modules/selenium-webdriver/lib/promise.js:676:7
at process._tickCallback (internal/process/next_tick.js:103:7)

見てのとおり殆どがスタックトレースの情報なのだ。こんなのいらないよ…。いや役に立つときはあるにはあるだろうけど、少なくとも mocha や selenium 内部のスタックトレースは普通はいらない。

そんなわけで、アサートを単に assert.equal() を呼ぶのではなく、

try {
assert.equal(actual, expected);
}
catch (ex) {
ex.stack = ex.stack.replace(/* cute regexp */, '');
throw ex;
}

なんてコードが考えられる。これはこれですごく嫌らしいコードなのだが(stack プロパティが書き換えられていいの?)、これでやってみてもスタックトレースは空にならない。上記の [cci]From: Task:[/cci] で始まる行以降の部分は、すでに selenium が stack に追記済みなのである。なので、stack を操作して再度 throw してもそれを selenium がまたいじるので、スタックトレースは空にならない。selenium にそのへんを何とかするオプションがあるのかはまだ調べてない。

ということで、とりあえずのワークアラウンドは
mocha --timeout=60000 --reporter=almost-min src/wd-tests/src/all-tests.js | sed -e '/^\\s*at\\s*/d' -e '/^\\s*From:\\s*Task:/d'
てな感じに、すべてのテストが完了した後に sed で削ぎ落とすことくらいしか思いつかない。これだと、mocha ならではの色とりどりのテスト結果ではなくなってしまうが、まあ個人的にはあれはちょっと華美すぎだと思うので、これはこれでいいかな…。

Testing with selenium javascript binding

wasaviの機能テストは今javaで書いているのだがjavaでなければならない理由は別にない。なぜjavaを選択したのかといえば、かつてはwasaviのビルドにantを使っていたのでそのへんの相性だとか、Seleniumのコード例として割とjavaが多く検索に引っかかるとか、公式のAPIリファレンスがjavadocだったからとか、その程度の理由だ。

しかし機能テストの個数が増えてくるとjavaだと困ったことにコンパイルにもそれなりに時間がかかってくるわけでなかなかストレスフルだ。それ以前にテストが全体的に遅い。ブラウザを起動する速度すら遅い。これを期に、スクリプト言語に移行したい。wasavi場合それ自体をJavascriptで書いているのだから、Javascriptに移行するのが自然だ。なによりJavascriptへのバインディングはSeleniumが公式でメンテしているのであんしんあんぜんということだ。ちなみに実はビルドの際に呼び出されるちまちましたスクリプト群も以前はRubyで書いていたのだが全部Javascriptに書きなおした。

ということでJavascriptによるテストコードの基盤を書いているのだが。基盤とはなんのこっちゃというと、各々の記述されたテストはページ上のtextarea、あるいは時にはdiv要素に対してwasaviが起動していることを前提としている。なのでテストの前段階でCtrl+Enterを送出してwasaviを起動させ、実際に起動したことを確認するコード、後段階でwasaviを終了させるコードを挟む必要があるのだ。そういったことをするために、基本的には


@test
public void testFoo () {
startWasavi();
// test body
;
closeWasavi();
}

なんてふうにテスト自体の先頭の末尾に何がしかの処理を挟むことになる。しかしこれはめんどくさいわけで、もうちょっと賢く特定の処理を割りこませたい。

となると次の案としては当然setUp()とtearDown()を定義してそこでwasaviを開いたり閉じたりすればよいのである。ここまでは実に普通だ。めんどくさいのはここからだ。

テストの中には、textarea要素ではなくcontenteditableなdiv要素とか、あるいはセクション・パラグラフ・センテンスをテストするために予めそのための初期本文が必要であるとか、初期化のタイプにもいくつかのバリエーションがある。そのへんもテスト本体に手を入れることなく1箇所で面倒を見たい。たとえばセクションに対するテストであればtestSectionなんとか、ってメソッド名なので、それを利用すればいい。あるいはアノテーションでもまあいい。しかし…困ったことにsetUp()にはそういったテスト自体のメタ情報は渡されないのであった。そのかわり、JUnitの場合TestWatcherというクラスがあってテストスイートの進行具合を観測することができるので、これを使う。

さて、これがJUnitからmochaへ移行するとどうなるか。setUp()とtearDown()は、beforeEach()とafterEach()という同機能が用意されているので単にそれを使えばいい。しかしやはり困ったことにこれらのメソッドにもテスト自体のメタ情報は渡されないのであった。というわけでTestWatcherの代替品を探さないといけないのだが、ない。

無理やり代替品を2つ考えてみると、まずReporterというものがありこれがテスト結果を表示する。これが内包するリスナはテストランナーから送出される各種イベントを受信して、その中にはテストのメタ情報が含まれる。なのでそれを使ってテストスイートへリダイレクトする。これのconsは好きなReporterを使えなくなるという点。

もうひとつは、テストの定義にit()を使うのでそれをラップして、

var testNames = [];
var originalIt = it;
it = function (should, fn) {
testName.push(should);
return originalIt.apply(undefined, arguments);
};

あとは beforeEach()内で[cci]testNames[/cci]から順々に表明を取り出せばそれは個々のテストを区別できる。これのconsは、必ずしもテストがit()の呼び出し順で実行されるとは限らないという点。

というわけでどちらもイマイチな点がある。うーむ。

 * * *

もうひとつ。JavascriptのSeleniumバインディングは、ほぼすべてのAPIがPromiseを返す仕様なのだ。だから、Javaであれば同期的に

WebElement el = driver.findElement(By.id("foobar"));
el.click();

などと何も考えずに書いていたコードは

driver
.findElement(By.id('foobar'))
.then(el => {el.click()});

というような感じになる。やることがシーケンシャルなのであれば.then()の羅列でいいのだがしかし分岐やループが入ってくるととたんに面倒くさくなってくる。それで、WebDriver.promise オブジェクトが用意されていてこれがジェネレータを引数に取り、ジェネレータがyieldしなくなるまで呼び出し続け、最後にresolve()するというナイスな関数consume()を持っているので

WebDriver.promise.consume(function*() {
var el = yield driver.findElement(By.id('foobar'));
yield el.click();
});

てな具合に同期的っぽく書ける。というかこれを使わないととてもじゃないけど書いてられない。気をつけないといけないことが一つあり、WebDriver.promiseはdeprecateである。なので将来的にはこれはasync/await版にしないといけない。

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