2018年5月9日水曜日

ChromeExtensionとWebExtensionsのコードを共通化したい(追記)

この文書作成時のソフトウェアバージョンは以下です。

  • Google Chrome 66.0.3359.139 (64bit)
  • Firefox Quantum 59.0.3 (64 ビット)

結論から言うと、manifestをプラットフォームごとに分岐させ、js上はchrome.runtime.idの値で分岐させれば多重管理は防げる。

ChromeExtensionとWebExtensionsには互換性がある、しかし

ブラウザ拡張に用いるAPIはブラウザごとに違う。
OperaとChromeで採用されているのはextension API
Firefoxで採用されているのはWebExtensions API
MDNによれば、

WebExtension API で開発する拡張機能は、ブラウザー間で互換性が維持されるように設計されており、大半は Google Chrome や Opera でサポートされている extension API とコード互換性があります。これらのブラウザー向けに書かれた拡張機能はほとんどの場合、少しの変更を加えるだけで Firefox でも動くようになります。1

とのことなので、ひとつのコードベースでChromeもFirefoxもサポートしたいところ。
しかし、非互換API2があったり、互換とされるAPIであっても微妙に動きが異なったりする。

例えば、EventPageでコンテキストメニューに項目を追加するにはChromeExtensionでは以下のコードが推奨されている3
が、これをFirefoxで動かすとブラウザを閉じたときにコンテキストメニューはなくなり、再度インストールするまでコンテキストメニューに項目が追加されなくなる。(しかもパッケージ化していない拡張機能はブラウザが閉じるまでしか有効にならないのでテストできない! なんてこった)

background.js
chrome.runtime.onInstalled.addListener(function(){
    chrome.contextMenus.create({
        ...
    });
});

UserAgentからブラウザを判別して処理を分岐する方法

とりあえず思いついたのがUserAgent文字列をパースしてブラウザを判別する方法。
パーサは勿論既存のものを使う。
今回はこちらを拝借https://github.com/faisalman/ua-parser-js
で、分岐したい部分を愚直に分岐する。

background.js
var parser = new UAParser();
if(parser.getResult().browser.name === 'Chrome'){
    chrome.runtime.onInstalled.addListener(createContextMenus);
}
else{
    createContextMenus();
}

(今回はChromeとFirefoxのことしか考えていない)

他にいい方法があれば教えてください

ください!!


ここから追記
教えていただきました! ありがとうございます!

chrome.runtime.id

Chromeではchrome.runtime.idの値は拡張機能一覧に表示されるIDと一致する。
image.png (20.3 kB)

Firefoxではmanifestに記載したIDと一致する

manifest.json
{
    ...
    "applications" :{
        "gecko" : {
            "id" : "test@example.com"
        }
    },
    ...
}

Chromeではmanifestにapplicationsキーが書かれていると

Unrecognized manifest key 'applications'.

とエラーを吐かれてしまうので、なんとかする必要がある。

ビルド時にmanifestを分岐する

結局分岐させた。
firefox用のmanifestを記載したjsonを作っておき、ビルド時にマージする。今回はGulpを使ってみた。

manifest-firefox.json
{
    "applications" :{
        "gecko" : {
            "id" : "test@example.com"
        }
    }
}
gulpfile.js
var gulp = require('gulp');
var mergeJson = require('gulp-merge-json');
var rimraf = require('rimraf')

gulp.task('clean', function(callback){
    rimraf('release/firefox', callback)
});

gulp.task('releaseForFirefox', ['clean'], function(){
    gulp.src(['manifest.json', 'manifest-firefox.json'])
    .pipe(mergeJson({
        fileName: 'manifest.json',
    }))
    .pipe(gulp.dest('release/firefox'));

    // ソースのコピーとかアーカイブとか
    ...
});

gulp.task('releaseForChrome', ['clean'], function(){
    gulp.src('manifest.json').pipe(gulp.dest('release/chrome'));

    // ソースのコピーとかアーカイブとか
    ...
});

結局manifestは分岐することになったけど多重管理を防ぐことはできたので満足。
jsのソースも一本化できた。

background.js
if(chrome.runtime.id === 'test@example.com'){
    createContextMenus();
}
else{
    chrome.runtime.onInstalled.addListener(createContextMenus);
}

0 コメント:

コメントを投稿