WebExtensionsによるFirefox用の拡張機能で、「以後確認しない」のようなチェックボックスを伴った確認ダイアログを表示するライブラリ：RichConfirm.js

（この記事は、Firefoxの従来型アドオン（XULアドオン）の開発経験がある人向けに、WebExtensionsでの拡張機能開発でのノウハウを紹介する物で、[所属会社のブログの2018年3月26日の記事](https://www.clear-code.com/blog/2018/3/26.html)の再掲です。）

# `Services.prompt.confirmEx()`を代替する

Firefoxの従来型アドオンでは[nsIPromptService](https://developer.mozilla.org/ja/docs/Code_snippets/Dialogs_and_Prompts)という内部APIを使って、「YES・NO・キャンセルの三択のダイアログ」を表示したり、「以後この確認を表示しない」のようなチェックボックスを伴った確認ダイアログを表示したりできました。

これと同等の事をWebExtensionsベースの拡張機能でやろうとすると、意外と面倒だという事実に行き当たります。

 * `window.confirm()`や`window.alert()`では「以後この確認を表示しない」のようなチェックボックスを提供できない。
 * `window.confirm()`では「OK」と「キャンセル」の二択しかできず、三択以上の選択肢を提供できない。
 * コンテンツ領域内に埋め込む形で独自の確認ダイアログ風UIを作るのは手間がかかる。ボタンのフォーカス切り替えなどのキーボード操作にも対応するとなると、なおさら大変。
 * バックグラウンドスクリプトから任意のタイミングで確認を行うためには、コンテントスクリプトとの間での通信が必要。

そこで、`nsIPromptService`の`confirmEx()`や`select()`に相当する機能を簡単に利用できるようにする軽量なライブラリとして、[RichConfirm.js](https://github.com/piroor/webextensions-lib-rich-confirm)という物を開発しました。
![（サイドバー内に表示された確認ダイアログ風UI）](https://qiita-image-store.s3.amazonaws.com/0/61387/a06a2056-1266-bfb3-e359-dad9921cc3af.png)


使い方は以下の通りです。

 1. `RichConfirm.js`をパッケージに含める。
 2. バックグラウンドページやサイドバーなどから`<script type="application/javascript" src="./RichConfirm.js"></script>`のようにして読み込む。
 3. ダイアログを表示したい場面で、`RichConfirm.show()`または`RichConfirm.showInTab()`を呼ぶ。

以下、それぞれの場合について詳しく説明します。

# スクリプトを実行したページの中でダイアログを表示する

サイドバーやコンテントスクリプト内でそのページ内にダイアログを表示したい場合には、`RichConfirm.show()`を使います。このメソッドはオブジェクト形式で以下の引数を受け付けます。

```javascript
RichConfirm.show({
  message:      '実行しますか？',   // 表示するメッセージ
  buttons:      ['はい', 'いいえ'], // 選択肢のラベルの配列
  checkMessage: '以後確認しない',   // チェックボックスのラベル
  checked:      false              // チェックボックスの初期状態
});
```

メソッドの戻り値は`Promise`になっており、以下のいずれかの方法で結果を受け取る事ができます。

```javascript
// Promiseとして使う
RichConfirm.show(...).then(result => {
  // 結果を使った処理
  console.log(result);
});

// async-awaitで使う
async confirmAndDo() {
  var result = await RichConfirm.show(...);
  // 結果を使った処理
  console.log(result);
}
```

Promiseが解決された結果の値は、以下のような形式のオブジェクトになっています。

```javascript
{
  buttonIndex: 0,
  // 押されたボタンの番号。選択肢の配列の要素に対応し、
  // どれも選択されなかった場合は`-1`となる。
  checked: true
  // チェックボックスの状態。
}
```

# タブの中でダイアログを表示する

バックグラウンドスクリプトから現在のタブの中にダイアログを表示したい場合には、`RichConfirm.showInTab()`を使います。

このメソッドを使うには`activeTab`または`tabs`の権限が必要です。
`RichConfirm.show()`と同じインターフェースで利用でき、戻り値の取り扱いも`RichConfirm.show()`と同じです。

```javascript
RichConfirm.show({
  message:      '実行しますか？',   // 表示するメッセージ
  buttons:      ['はい', 'いいえ'], // 選択肢のラベルの配列
  checkMessage: '以後確認しない',   // チェックボックスのラベル
  checked:      false              // チェックボックスの初期状態
});
```

`RichConfirm.show()`には無い特長として、メソッドの第1引数として`tabs.Tab`の`id`（整数）を指定すると、現在のタブ以外の任意のタブにダイアログを表示させる事ができます。例えば、以下は現在のタブの右隣のタブにダイアログを表示する例です。

```javascript
async function confirmInNextTab() {
  var [current, tabs] = await Promise.all([
    browser.tabs.getCurrent(),
    browser.tabs.query({})
  ]);
  var nextTab = tabs.filter(tab => tab.windowId == current.windowId)
                    .filter(tab => tab.index == current.index + 1)[0] || current;
  var result = await RichConfirm.show(nextTab.id, {
    message: ...
  });
  ...
}
```

# まとめ

XULアドオンで一般的に使われていたnsIPromptServiceの`confirmEx()`をの代替となる軽量ライブラリである[RichConfirm.js](https://github.com/piroor/webextensions-lib-rich-confirm)について、その使い方を解説しました。

ネイティブアプリケーションの開発に近い部分があったXULアドオンとは異なり、WebExtensionsベースでの拡張機能開発は、どちらかというとWebアプリの開発に近いと言えます。Webアプリの開発に慣れた人にとっては、古くは[jQuery UI](https://jqueryui.com/)や、近代的な物では[Bootstrap](https://getbootstrap.com/)や[React](https://reactjs.org/)を使う方がやりやすいかもしれません。ただ、ピンポイントで`confirmEx()`と同等の事がしたいだけという場合には、これらのライブラリの使い方を一から覚えるのは億劫に感じるものです。そういった場合の手軽な選択肢として、RichConfirm.jsを試してみてはいかがでしょうか。