Content ScriptsなChrome拡張をFirefox拡張に
追記(2015-10-16):WebExtensionsの発表
これを書いた数ヶ月後の8月21日、Mozillaが今後のアドオンをWebExtensionsというChrome拡張APIベースなものに移行していく計画を発表した。
- The Future of Developing Firefox Add-ons | Mozilla Add-ons Blog
- WebExtensionsが正式発表 XUL/XPCOMベースのアドオンは将来的に非推奨へ - Mozilla Flux
なので、今後のことを考えたりもともとの「Chrome拡張をFirefoxでも動かしたい」というのを考えるとWebExtensionsベースなものにしたほうが良さそう。実際に試したところ、Chrome拡張のmanifest.jsonにWebExtensionsで必要なメンバを足しただけでふつうに動いたので、Content ScriptsベースなChrome拡張ならほとんど手間を書けずにクロスブラウザな拡張にできそう。
というわけで、このエントリで書いたAdd-on SDKベースな拡張はいささかレガシーなものになりそうだけど、気になる方はお読みくださいな。
お手製のしょうもないChrome拡張をFirefox拡張にしてみた。といってもタイトルにある通りContent Scripts、つまりユーザースクリプトレベルなものなので、そんな面倒ではなかった。
Firefox拡張のContent Scripts
まずは同じコンセプトのものがFirefoxにあるのか、ドキュメントを探す。あった。
Many add-ons need to access and modify the content of web pages. But the main add-on code doesn't get direct access to web content. Instead, SDK add-ons need to factor the code that gets access to web content into separate scripts that are called content scripts. This page describes how to develop and implement content scripts.
Content scripts can be one of the more confusing aspects of working with the SDK, but you're very likely to have to use them. There are five basic principles:
- the add-on's main code, including "main.js" and other modules in "lib", can use the SDK high-level and low-level APIs, but can't access web content directly
- content scripts can't use the SDK's APIs (no access to globals
exports
,require
) but can access web content- SDK APIs that use content scripts, like page-mod and tabs, provide functions that enable the add-on's main code to load content scripts into web pages
- content scripts can be loaded in as strings, but are more often stored as separate files under the add-on's "data" directory
- a message-passing API allows the main code and content scripts to communicate with each other
拡張からページのDOMは直接触れられずContent Scripts経由というのはChromeと同じらしい。あとIsolated Worldというコンセプトも共通。
スクリプトの読み込み方は違いがあるけど、Content Scriptsのファイルはそのまま流用できそう(Chromeでしか対応してないものを使ってない限りは)。わーい。
jpm
Firefoxの場合はどうやらAdd-on SDKをインストールしないといけないらしい。これまではそのSDKがPythonベースのやつだったらしいんだけど、Fx38からNodeベースのjpmというものになったとのこと。
というわけで npm install -g jpm
でさくっとインストール後、jpm init
で名前やらエンドポイントやらを設定。
Content Scriptsの読み込み
Chrome拡張では manifest.json からContent Scriptを動作させたいURL(のパターン)とそのファイルを明示的に指定していた。
...
"content_scripts": [
{
"matches": [ "https://foo.bar/*" ],
"js": [ "baz.js" ],
"css": [ "quux.css" ]
},
...
Firefox拡張の場合はエンドポイントのファイルを用意して、そこからプログラマブルな感じで指定するらしい。jpm init
した場合はindex.js
ができる。
今回の拡張はページにスクリプトファイルをぶっこむものなので、page-modというAPIを使う。
const data = require('sdk/self').data
const pageMod = require('sdk/page-mod')
pageMod.PageMod({
include: "https://foo.bar/*",
contentScriptFile: [ data.url('baz.js') ],
contentStyleFile: [ data.url('quux.css') ]
})
ファイルの置き場
エンドポイントを指定したら jpm init
したディレクトリ直下にできたのだけれど、Content Scriptsのファイルとかはその中に data
ディレクトリを作ってそこに置かないといけないらしい。ここで少しはまった。initしたら作ってほしいな。
Chrome拡張はとくにディレクトリわけとかしてなかったので、そのまま読み込めないか試すもアウト。どうやらファイルを指定したとこで使ったself.data.url()
は data
ディレクトリ内のファイルのみしか読めないらしい。まじか…
気になったのでソースを見てみる。
exports.data = Object.freeze({
url: uri,
load: function read(path) {
return readURISync(uri(path));
}
});
はいはい。では uri
はというと…
const uri = (path="") =>
path.contains(":") ? path : addonDataURI + path.replace(/^\.\//, "");
ええ。それで addonDataURI
はというと、
const addonDataURI = baseURI + "data/";
(´Д`)
固定だ…ちょうmagicだ……
というわけであきらめかけてたんだけど、ふと data.url('../hoge.js')
としてみたら読み込めた。でもいいのかな…まあ動かなくなったら考えよう。
ディレクトリ固定、もやっとすることはあるけど拡張のレビューとか、他のひとの拡張を知りたいときには見るところが決まっていて便利なのだろうなとも思う。Chromeだとmanifest.json見ないといけないし。
その他
作った拡張は jpm xpi
でパッケージ化すれば再起動不要なアドオンとしてインストールできる。
Content Scriptレベルならふつーにできたけど、UIになにかぶっこむとかそういうのはAPIがだいぶ違うので、できるだけモジュールに落とすとかしないと共通部分が減るばかりな気がした。試したいんだけどそういう拡張を作ったことがない(…)ので、なんかアイデアを持とう。