Web Extensions で Firefox (Chrome, Android) プラグインを開発する


あまり需要ない気もしますが,昔の firefox plugin を web extensions で書き直す際にハマった所など.

試した環境:

  • Firefox 57.0.4
  • Chrome 63.0.3239.132
  • Android (バージョンを忘れた...)
  • web-ext 2.2.2

開発環境のインストール

nodejs の web-ext command をインストールします.

  • npm install -g web-ext

おわり.

サンプル

以下からクローンしておきます.

Desktop で動かしてみる

シェルなどを開いて適当なプラグインのディレクトリ,例えば chill-out へ移動して

web-ext run --bc -v

するとこうなる.

Firefox for Android で動かしてみる

先に開発者用の api_key を発行します. 開発者登録が必要ですが10分かからないと思います.

以下の手順に従って作業していきます.

  • web-ext sign で xpi を生成します.
  • Android へ Firefox をインストールしておきます.
  • Android をデバッグモードでつなぐなどして xpi を push します.
  • Firefox を開いて file:///mnt/sdcard/ を開きます. xpi をクリックするとインストールされます.

インストールしたプラグインをデバッグには以下を使います(詳細略).

UI を実装する

プラグイン開発で避けて通れないのが,プラグインを起動したり設定したりするためのボタンの追加です.
追加できるボタンには page_actionbrowser_action, sidebar_action などが存在します.

page_action

うち Android が対応しているのは page_action だけです.
どんな感じで動くのかは以下のサンプルを試してみるとわかります.

使い方は以下のページを見つつ,サンプル中の manifest.json と background.js を見るとよいです.

browser_action

sidebar_action

CSS

Bootstrap についてはスクリプトに依存しない部分はすんなり使えます(bootstrap.min.css font.min.css とか).
スクリプトに依存する部分 bootstrap.min.js とか jquery.min.js を使うのは正直なところ,かなりめんどくさいです.
コンソールを覗くと大量に警告やエラーが出ます.
どうもプラグインに同梱するスクリプトにいろいろと制限があることが原因のようです.

webpack

あまりお勧めはしませんが使えなくはないです.
1度目の公開前に review が入るのですが,minimize して配布する場合には pack 前のソースと pack 方法について提供を求められます.

ブラウザごとのサポート状況

Chrome

ほぼすべての API が Promise に対応していないので callback を渡す必要がある.
(拡張機能に関してはやる気がないのかもしれない).

存在しない API に対するハンドリング

非常に雑な実装ですと例えばこのようになります.

/*
 * Firefox の場合は基本的に prefix が `browser.` の API を使うのですが chrome にはそれがありません.
 * また Firefox は `browser.` と `chrome.` でたまに挙動が異なる API があります.
 */
function getBrowser() {
  try {
    return browser;
  } catch (err) {
    return chrome;
  }
}

// tabs.query API のラップ関数
function tabsQuery(query) {
  return new Promise(function (resolv, reject) {
    try {
      // callback なしで呼んでみる
      let querying = getBrowser().tabs.query(query);
      if (!querying) {
        // Promise object が返ってこないなら callback を使う
        getBrowser().tabs.query(query, resolv);
        return;
      }
      querying.then(resolv).catch(reject);
    } catch (err) {
      // schema error が起きたらおとなしく callback を使う
      try {
        getBrowser().tabs.query(query, resolv);
      } catch (err) {
        reject('browser.tabs.query unsupported!');
      }
    }
  });
}

// 使ってみる
let gettingAllTabs = tabsQuery({});
gettingAllTabs.then((tabs) => {
  console.log('# of tabs ' + tabs.length);
  for (let tab of tabs) {
    // tab に対する操作
  }
}).catch(console.warn);

メモ:
例外を起こして catch するやり方はパフォーマンスが著しく落ちる.
以下にある shim/polyfill が使えるかも

公開

開発者登録を行った後,以下のページからガイドに従ってぽちぽちクリックしてけば終わります.
プラグインをアップロードする前に,ローカル環境で web-ext lint しておくと間違った API の使い方について指摘してもらえます.

Chrome

こちらは開発者登録にお金がかかります
登録は以下から行います.

以前の SDK で使えた API について

以前は Components.classes という API から様々なブラウザオブジェクトが呼べたのですが,Web Extensions では呼べなくなっています. 例えば Components.classes を通した preferences などへの直接アクセスはできなくなりました.

以下においてある ESR については Components.classes API などを使った昔のプラグインがそのまま使えますが,
そのうちサポート期限が切れる予定です.