WebExtensionsによるFirefox用の拡張機能で設定画面の提供を容易にするライブラリ：Options.js

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

XULアドオンでは、設定UIを提供するのに便利な仕組みが用意されていました。文字入力欄やチェックボックス、ラジオボタンといった入力フィールドに対して、真偽型・整数型・文字列型それぞれの設定を関連付ける事により、設定UIの状態と設定値とが自動的に同期されるため、難しい事を考えなくても良いのが利点でした。標準的なUI要素でできる事の幅は限られていましたが、その制約が同時にガイドラインとなり、ユーザー視点においても、混乱することなく各アドオンの設定を編集できていたと言えるでしょう。

一方、WebExtensionsでは設定UIを提供する標準的な仕組みは用意されておらず、HTMLとJavaScriptの組み合わせで自力でUIを提供しなくてはなりません。そもそも設定値をどのように保存するのかすらも標準的なやり方が定まっておらず、採用した保存先によって設定の変更の監視方法も異なるため、設定値とUIの状態を同期するのも悩み所が多いです。そのためアドオンによって設定画面の作りはまちまちです。XULアドオンのように、何らかのガイドラインに従ってページ（HTML）を記述すれば設定値と状態が適切に同期されてくれる、というような仕組みが欲しくなる人も多いのではないでしょうか。

そこで、[`Configs.js`を使って表現された設定](https://qiita.com/piroor/items/a56946c327dcc93a8ce4)と併用する事を前提に、一定のガイドラインに則って記述されたHTMLのページと設定値の状態を適切に同期して、「設定UI」としてのページを提供する事を支援する軽量ライブラリとして、[`Options.js`](https://github.com/piroor/webextensions-lib-options)という物を開発しました。
このライブラリを使うと、以下のような設定画面を比較的簡単に作成できます。

![IE View WEでの使用例](https://qiita-image-store.s3.amazonaws.com/0/61387/bfdc55dc-1e91-2988-c8f9-ce8f8a5c09ac.png)
![Multiple Tab Handlerでの使用例](https://qiita-image-store.s3.amazonaws.com/0/61387/69f65cd6-6424-7118-ff79-45c57454dfc5.png)



# 基本的な使い方

## 必要な権限

このライブラリを使う事自体には、特別な権限は必要ありません。ただし、設定の読み書きに[`Configs.js`](20180612)を使うため、間接的に`storage`の権限が必要となります。

```javascript
{
  ...
  "permissions": [
    "storage", 
    ...
  ],
  ...
}
```

## 読み込みと初期化

このライブラリを使うためには、設定画面を提供するページからファイルを読み込みます。依存ライブラリである`Configs.js`と併せて読み込む場合は以下のようになります。


```html
<script type="application/javascript" src="path/to/Configs.js"></script>
<script type="application/javascript" src="path/to/Options.js"></script>
<script type="application/javascript" src="init.js"></script>
```

`Options.js`を読み込むと、その名前空間で`Options`というクラスが使えるようになります。初期化処理は、単に`Options`の引数に`Configs.js`のインスタンスを渡して`new Options()`とするだけです。例えば以下の要領です。


```javascript
// init.jsの内容
var configs = new Configs({
  enabled:  true,                  // チェックボックスにする
  urls:     'http://example.com/', // 複数行の文字入力にする
  position: 2,                     // 0, 1, 2のいずれか。ドロップダウンリストで選択する
  theme:    'default'              // 'default', 'light', 'dark'のいずれかをラジオボタンで選択する
});

var options = new Options(configs);
```

これだけで、設定の読み込みが完了し次第、そのページに書かれたフォーム要素と設定値とが同期するようになります。フォーム要素の状態（チェック状態、入力内容など）を変更すると、その結果は即座に`configs`で定義された設定に反映されます。

## 設定画面で使えるフォーム要素

`Options.js`は、以下の種類のフォーム要素に対応しています。

 * チェックボックス（`<input type="checkbox">`）
 * ラジオボタン（`<input type="radio">`）
 * 文字入力（`<input type="text">`）
 * パスワード入力`<input type="password">`）
 * 数値入力（`<input type="number">`）
 * 非表示（`<input type="hidden">`）
 * ドロップダウンリストによる選択（`<select>`と`<option>`）
 * 複数行の文字入力（`<textarea>`）

`Options.js`自体は基本的にフォーム要素を自動生成する事はなく、利用者が好みの方法で記述したフォーム要素に対して使えるようになっています。基本的には、「`configs`（`Configs.js`のインスタンス）のプロパティ名」と「それと同じ`id`を持つフォーム要素」（ラジオボタンだけは「`configs`のプロパティ名と同じ`name`を持つ項目群」）が自動認識され、設定値とフォーム要素の`value`プロパティの値（チェックボックスは`checked`の値、ラジオボタンは`value`が設定値と一致する項目のチェック状態）が同期されます。前述の例の`init.js`で定義している設定に対応するフォーム要素の例を以下に示します。

真偽値の設定に対応するチェックボックスは、以下の要領です。

```html
<p><label><input id="enabled" type="checkbox">機能を有効にする</label></p>
```

文字列型の設定に対応する入力欄は、以下の要領です。

```html
<p><label>URL：<input type="text" id="urls"></label></p>

<!-- 複数行の入力欄にする場合：
<p><label>URL：<textarea id="urls"></textarea></label></p>
-->
```

値があらかじめ決められたいくつかの値の中のいずれかになるという選択式の設定に対しては、以下のようにして選択肢を提供できます。

```html
<p><label>表示位置：
     <select id="position">
       <option value="0">左</option>
       <option value="1">中央</option>
       <option value="2">右</option>
     </select>
   </label></p>
```

```html
<p><label>テーマ：
     <input name="theme" type="radio" value="default">既定</option>
     <input name="theme" type="radio" value="light">明るい</option>
     <input name="theme" type="radio" value="dark">暗い</option>
   </label></p>
```

設定の型は既定値の型と一致するように自動的に変換されるため、上記のドロップダウンリストのような例で値が`"0"`のような文字列型になってしまうという事はありません。

なお、Managed Storageで管理者が設定を指定している場合、対応する設定項目は読み取り専用の状態になります。


## `about:config`に相当する機能を付ける

XULアドオンでは、いわゆる隠し設定を定義しておき、`about:config`で値を変更して細かい挙動を制御するという事ができました。しかしWebExtensionsベースのアドオンでは統一的な設定の保存先が無く、またstorage APIで保存された情報には基本的にはアドオンの中からしかアクセスできません。`about:debugging`からデバッガを起動すればアドオンの名前空間で任意のスクリプトを実行して設定を変えられますが、設定変更のためだけにデバッガを起動するのは億劫なものです。

このような問題への回避策として、`Options.js`は初期化時に渡された`configs`に定義されている全ての設定を一覧し変更できるようにする機能が含まれています。UIは以下のような要領です。

![自動生成された設定一覧](https://qiita-image-store.s3.amazonaws.com/0/61387/4c19eb3a-3740-c18e-5b87-49eda93c6fd3.png)

全設定の一覧は、`Options`のインスタンスが持つ`buildUIForAllConfigs()`メソッドを実行するとページの末尾に挿入されます。

```javascript
options.buildUIForAllConfigs();
```

また、メソッドの引数に任意のコンテナ要素を渡すと、その最後の子として全設定の一覧が挿入されます。

```javascript
options.buildUIForAllConfigs(document.querySelector('#debug-configs'));
```

全設定の一覧の表示・非表示を切り替えるという機能は特にありません。ユーザーには見えないようにしておきたい場合、`configs.debug`のような設定を定義しておき、その変更を監視して以下の要領でコンテナ要素の表示・非表示を切り替えるといった形を取ると良いでしょう。

```javascript
configs.$addObserver(aKey => {
  if (aKey == 'debug') {
    const container = document.querySelector('#debug-configs');
    container.style.display = configs[aKey] ? 'block' : 'none';
  }
});
```


# まとめ

以上、Firefox用のWebExtensionsベースのアドオンにおける設定画面の提供を容易にするライブラリである`Options.js`の使い方を解説しました。

XULアドオンでの感覚に近い開発を支援する軽量ライブラリは他にもいくつかあります。以下の解説も併せてご覧下さい。

 * [設定の読み書きを簡単にする`Configs.js`](https://qiita.com/piroor/items/a56946c327dcc93a8ce4)
 * [キーボードショートカットの変更用UIを提供する`ShortcutCustomizeUIMenuUI.js`](https://qiita.com/piroor/items/a9f0a1bb5cebb7f3ad42)
 * [メニューやポップアップパネル風のUIを提供する`MenuUI.js`](https://qiita.com/piroor/items/b7a3a983ba4bdd22bdb9)
 * [「以後確認しない」というチェックボックス付きの確認ダイアログ風UIを提供する`RichConfirm.js`](https://qiita.com/piroor/items/588fcb9ef94f913a849d)