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

おわり.

### サンプル
以下からクローンしておきます.

https://github.com/mdn/webextensions-examples

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

`web-ext run --bc -v`

するとこうなる．

![page_action.png](https://qiita-image-store.s3.amazonaws.com/0/230336/962080af-f65c-d8f4-07c8-dc80512d6c72.png)

### Firefox for Android で動かしてみる
先に開発者用の api_key を発行します. 開発者登録が必要ですが10分かからないと思います.

https://addons.mozilla.org/en-US/developers/addon/api/key/

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

https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Developing_WebExtensions_for_Firefox_for_Android#Install_and_run_your_extension_in_Firefox_for_Android

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

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

https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Debugging_Firefox_for_Android_with_WebIDE

# UI を実装する
プラグイン開発で避けて通れないのが，プラグインを起動したり設定したりするためのボタンの追加です．
追加できるボタンには `page_action` や `browser_action`, `sidebar_action` などが存在します.

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

https://github.com/mdn/webextensions-examples/tree/master/chill-out

![page_action.png](https://qiita-image-store.s3.amazonaws.com/0/230336/962080af-f65c-d8f4-07c8-dc80512d6c72.png)

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

https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/pageAction

### browser_action
https://github.com/mdn/webextensions-examples/tree/master/bookmark-it

![browser_action.png](https://qiita-image-store.s3.amazonaws.com/0/230336/8f273e11-0c4c-80fc-56ca-1586f3869c7c.png)

### sidebar_action
https://github.com/mdn/webextensions-examples/tree/master/annotate-page

![sidebar_action.png](https://qiita-image-store.s3.amazonaws.com/0/230336/02382780-2976-9fc6-7645-b7f795a168c6.png)

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

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

# ブラウザごとのサポート状況
https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs

### 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 が使えるかも

https://github.com/mdn/webextensions-examples/issues/194

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

https://addons.mozilla.org/en-US/developers/

### Chrome
こちらは開発者登録に[お金がかかります](https://developer.chrome.com/webstore/publish)．
登録は以下から行います．

https://chrome.google.com/webstore/developer/dashboard

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

https://wiki.mozilla.org/WebExtensions/FAQ#Does_the_WebExtensions_API_provide_access_to_preferences.3F

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

https://ftp.mozilla.org/pub/firefox/releases/52.5.3esr/