ユーザー設定 (User Preferences)

ユーザー設定とは、アドオンの設定を管理する機能です。

XPCOM interfaces

設定システムにアクセスするには、XPCOMを使用します。

nsIPrefBranch

nsIPrefBranchは、次のようにすることで取得できます。

var prefBranch = Components.classes[ '@mozilla.org/preferences-service;1' ]
    .getService( Components.interfaces.nsIPrefBranch );

またはnsIPrefServiceから、getBranch()でも取得できます。

メソッド
メソッド
void addObserver( in string aDomain, in nsIObserver aObserver, in boolean aHoldWeak )
void removeObserver( in string aDomain, in nsIObserver aObserver )
void getChildList( in string aStartingAt, [optional] out unsigned long aCount, [array, size_is(aCount), retval] out string aChildArray )
void deleteBranch( in string aStartingAt )
void resetBranch( in string aStartingAt )
long getPrefType( in string aPrefName )
boolean prefHasUserValue( in string aPrefName )
void clearUserPref( in string aPrefName )
void lockPref( in string aPrefName )
void unlockPref( in string aPrefName )
boolean prefIsLocked( in string aPrefName )
単純型を読み書きするメソッド
区分 メソッド
取得 long getIntPref( in string aPrefName )
string getCharPref( in string aPrefName )
boolean getBoolPref( in string aPrefName )
設定 void setIntPref( in string aPrefName, in long aValue )
void setCharPref( in string aPrefName, in string aValue )
void setBoolPref( in string aPrefName, in long aValue )
複合型を読み書きするメソッド
メソッド
void getComplexValue( in string aPrefName, in nsIIDRef aType, [iid_is(aType), retval] out nsQIResult aValue )
void setComplexValue( in string aPrefName, in nsIIDRef aType, in nsISupports aValue )

nsIPrefService

nsIPrefServiceは、次のようにすることで取得できます。

var prefService = Components.classes[ '@mozilla.org/preferences-service;1' ]
    .getService( Components.interfaces.nsIPrefService );
メソッド
メソッド
nsIPrefBranch getBranch( in string aPrefRoot )
nsIPrefBranch getDefaultBranch( in string aPrefRoot )
void resetPrefs()
void resetUserPrefs()
void savePrefFile( in nsIFile aFile )
void readUserPrefs( in nsIFile aFile )

getBranch()

ユーザー設定へアクセスするためのnsIPrefBranchを取得します。

nsIPrefBranch getBranch(
    in string aPrefRoot
    );
getBranch() - nsIPrefService - XPCOM Interface Reference | MDN

設定値の読み書き

about:configで、既存のアドオンの設定を確認できます。設定値の使用方法は、これが参考になります。

設定名 (preference name)

設定値の名前は、

  1. extensions.アドオン名.
  2. アドオン名.

のいずれかの形式から始めるのが慣習のようです。しかしアドオンを配布することを考えるならば、1番目の書式とすべきです。

all preference names should begin with "extensions.", followed by the add-on name or some other unique identifier

Review Process :: Add-on Documentation :: Developer Hub :: Add-ons for Firefox

また設定名はドット区切りで記述されることが多いようですが、システム上はこのドットに意味はなく、文字列の一部とみなされます。設定 "木" についての詳細 - Preferences - Code snippets | MDN

単純型 (Simple types)

取得 設定
整数※1 getIntPref() setIntPref()
文字列 getCharPref() setCharPref()
論理値 getBoolPref() setBoolPref()

※1 浮動小数点数は使用できません。よってその必要があるならば文字列として定義し、使用時に文字列と数値を変換します。

これらのメソッドは、設定値の型に応じて正しく使い分けなければなりません。さもなくば例外が発生します。

long getIntPref(
    in string aPrefName
    );
getIntPref() - nsIPrefBranch - Mozilla | MDN
void setIntPref(
    in string aPrefName,
    in long aValue
    );
setIntPref() - nsIPrefBranch - Mozilla | MDN

オブジェクトは、JSONに変換することで文字列として処理できます。また文字列に変換できる要素から構成される配列ならば、Array.join()でも同様です。

取得

var prefService = Components.classes[ '@mozilla.org/preferences-service;1' ]
    .getService( Components.interfaces.nsIPrefService );

var branch = prefService.getBranch( 'extensions.MyAddon.' );
var value = branch.getIntPref( 'pref1' );

設定名のドット区切りに意味はないため、次のように指定しても同じです。

var branch = prefService.getBranch( '' );
var value = branch.getIntPref( 'extensions.MyAddon.pref1' );

またnsIPrefServiceではなくnsIPrefBranchを取得することで、

var prefBranch = Components.classes[ '@mozilla.org/preferences-service;1' ]
    .getService( Components.interfaces.nsIPrefBranch );

var value = prefBranch.getIntPref( 'extensions.MyAddon.pref1' );

のようにも記述できます。

有効な値が存在しない設定名から読み込もうとすると「NS_ERROR_UNEXPECTED: Component returned failure code: 0x8000ffff (NS_ERROR_UNEXPECTED) [nsIPrefBranch.getIntPref]」として例外が発生します。その設定名で記録する処理が存在していても、既定の設定で初期値が指定されていないと、ユーザーのリセットの操作により設定値が削除されることがあります。

よって設定の取得時には例外に備えるか、さもなくば既定値を設定します。

設定

書き込みも読み込みと同様で、nsIPrefBranchのメソッドを呼び出します。

var prefBranch = Components.classes[ '@mozilla.org/preferences-service;1' ]
    .getService( Components.interfaces.nsIPrefBranch );

prefBranch.setCharPref( 'extensions.MyAddon.pref1', 'abc' );

不正な型によるアクセス

たとえば文字列として作成されたユーザー設定を、整数用のメソッドであるgetIntPref()で読もうとすると、「Component returned failure code: 0x8000ffff (NS_ERROR_UNEXPECTED) [nsIPrefBranch.getIntPref]」として例外が発生します。

この問題には、ユーザー設定の型を事前に調べることで対処できます。それを行うのが次のgetPrefType()です。

型の調査
long getPrefType(
    in string aPrefName
    )
getPrefType() - nsIPrefBranch - XPCOM Interface Reference | MDN

branch.getPrefType( aPrefName )として設定名を指定して呼び出すと、その設定の型に応じて定数が返されます。このメソッドの用法は、以下のサンプルを参考にしてください。

var prefService = Components.classes[ '@mozilla.org/preferences-service;1' ]
    .getService( Components.interfaces.nsIPrefService );

var branch = prefService.getBranch( 'extensions.MyAddon.' );

var value = null;
var prefName = 'pref1';

switch( branch.getPrefType( prefName ) )
{
    case branch.PREF_STRING:
        value = branch.getCharPref( prefName );
        break;

    case branch.PREF_INT:
        value = branch.getIntPref( prefName );
        break;

    case branch.PREF_BOOL:
        value = branch.getBoolPref( prefName );
        break;

    case branch.PREF_INVALID:
        break;
}

複合型 (Complex types)

複合型では単純型の3つの型、つまり整数、文字列、論理値以外の型を扱えます。

取得 設定
getComplexValue() setComplexValue()
void getComplexValue(
    in string aPrefName,
    in nsIIDRef aType,
    [iid_is(aType), retval] out nsQIResult aValue
    );
getComplexValue() - nsIPrefBranch - XPCOM Interface Reference | MDN
void setComplexValue(
    in string aPrefName,
    in nsIIDRef aType,
    in nsISupports aValue
    );

aTypeには、次のいずれかの値を指定します。

  • NsISupportsString … Unicode文字列用
  • NsIPrefLocalizedString … Unicode文字列用 (設定値がない場合は、既定値から読み込む)
  • NsILocalFile … パス用 (絶対パス)
  • nsIRelativeFilePref … パス用 (特別なディレクトリからの相対パス)
  • NsIFileSpec (非推奨)

設定値の確認

about:configで、設定値の確認や編集を行えます。

設定値の削除

作成した設定値は、直接削除することはできません。その設定値を作成しないようにコードを修正し、設定値が既定値であることを確認してからFirefoxを再起動することで、設定ファイル (prefs.js) から削除されます。

なお設定値を既定値に戻すには、対象の値を右クリックしコンテキストメニューから[リセット]を実行します。

設定値の変更の監視 (設定オブザーバ)

設定値をブラウザのロード時に初期化している場合、ユーザーに設定ウィンドウで設定を変更されても、その設定はブラウザを再起動するまで反映されません。この問題に対処するには、設定の変更を監視するようにします。

addObserver()

addObserver()で、設定値の変更を監視 (オブザーブ) できます。このとき監視する対象は、他の拡張の設定でも、ブラウザの既定の設定でも構いません。

addObserver(
    in string aDomain,        // 監視対象の設定値の名前
    in nsIObserver aObserver, // 変更の通知を受け取るオブジェクト
    in boolean aHoldWeak      // trueならばaObserverの弱い参照を、さもなくば強い参照を保持する
    )
addObserver() - nsIPrefBranch - XPCOM Interface Reference | MDN

引数のaDomainには、getBranch()で指定した設定名に続く文字を指定します。たとえば、

  • MyAddon.aa
  • MyAddon.ab
  • MyAddon.bb

という設定名があるとき、

var branch = prefService.getBranch( 'MyAddon.' );
branch.addObserver( 'a', this, false );

とすると、[MyAddon.a]から始まる「MyAddon.aa」と「MyAddon.ab」の2つが監視対象となります。一方で第1引数のaDomainを空文字とすると、[MyAddon.]から始まる「MyAddon.bb」も含めた3つが対象となります。

オブザーバの削除

addObserver()でオブザーバを追加したならば、それが不要になったときにはremoveObserver()でそれを取り除く必要があります。さもなくば監視対象が変更されるたびに、observe()が呼ばれ続けることになります。

void removeObserver(
    in string aDomain,       // 監視対象としてあった設定値の名前
    in nsIObserver aObserver // 変更の通知を受け取るオブジェクト
    );
removeObserver() - nsIPrefBranch - XPCOM Interface Reference | MDN

addObserver()で追加したオブザーバーを削除するならば、引数のaDomainaObserverは、addObserver()で指定した同名の引数と同じものとします。

サンプルコード

var prefObserver =
{
    branch: null,
    register: function()
    {
        var prefService = Components.classes[ '@mozilla.org/preferences-service;1' ]
            .getService( Components.interfaces.nsIPrefService );

        // ユーザー設定へアクセスするためのnsIPrefBranchを取得する
        this.branch = prefService.getBranch( 'extensions.MyAddon.' );

        // … そのユーザー設定に、オブザーバーを追加する
        // … 第2引数にthisを渡すことで、通知をthis.observe()が受け取ることになる
        this.branch.addObserver( '', this, false );
    },

    unregister: function()
    {
        this.branch.removeObserver( '', this );
    },

    // 監視対象の設定が変更されると、呼び出されるメソッド
    observe: function( aSubject, aTopic, aData )
    {
        switch( aData )
        {
            case 'pref1':
                // 'extensions.MyAddon.pref1' が変更された
                break;

            case 'pref2':
                // 'extensions.MyAddon.pref2' が変更された
                break;
        }
    }
};

window.addEventListener(
    'load', function() { prefObserver.register(); }, false );

window.addEventListener(
    'unload', function() { prefObserver.unregister(); }, false );

observe()

observe()は、addObserver()の第2引数で指定するオブジェクトにobserveの名前で定義することで、監視対象の設定が変更されるたびに呼び出されます。

void observe(
    in nsISupports aSubject, // 監視対象のnsIPrefBranch
    in string aTopic,        // "nsPref:changed"などの値
    in wstring aData         // 変更された設定値の名前
    );
nsIObserver - XPCOM Interface Reference | MDN

引数のaSubjectには、オブザーバを設定したnsIPrefBranchが渡されます。つまり、

aSubject.getIntPref( aData );

のようにすることで、新しい設定値を取得できます。このとき引数のaDataには、getBranch()で指定した設定名に続く文字列が渡されます。たとえば「extensions.MyAddon.pref1」という設定名があるとして、

branch = prefService.getBranch( 'extensions.MyAddon.' );
branch.addObserver( 'p', this, false );

のようにオブザーバが追加されているとします。このとき「extensions.MyAddon.pref1」に変更があると、observe()の引数aDataには'pref1'という文字列が渡されます。一方で、もし

branch = prefService.getBranch( 'exten' );
branch.addObserver( '', this, false );

のようにオブザーバが追加されているとすると、'sions.MyAddon.pref1'が渡されます。

既定の設定 (Default Preferences)

既定の設定は、defaults\preferences\フォルダに拡張子を「js」としてファイルを配置します。配置するフォルダと拡張子が重要であり、ファイル名は意味を持ちません。複数のファイルを配置した場合は、それらのすべてが読み込まれます。

値の設定は、pref()関数で行います。

pref('name', value);

この関数では設定名と値のみを指定し、型を指定する必要はありません。それはvalueの型に応じて適切に設定されます。設定値が複数ある場合には、セミコロンで区切ってこの関数を複数記述します。なお、このファイルに記述できるのはこの関数とコメントのみで、それは次のようにします。

/* comment */
pref( 'extensions.MyAddon.a', 1 );     // 整数値
pref( 'extensions.MyAddon.c', 'foo' ); // 文字列
pref( 'extensions.MyAddon.d', true );  // 真偽値

記述に誤りがあると、それ以降の設定が無効となります。

拡張子はjsでありJavaScript風に記述しますが、これはJavaScriptではありません。よって変数や式は記述できず、文末のセミコロンを省略できないなど文法も異なります。

about:configでリセットされたときは、この既定の設定の値が設定されます。もしこの既定の設定がなければその設定値は無効な状態となり、その値を読み込もうとすると「NS_ERROR_UNEXPECTED: Component returned failure code: 0x8000ffff (NS_ERROR_UNEXPECTED) [nsIPrefBranch.getIntPref]」として例外が発生します。

特殊文字のエスケープ

値に文字列を指定する場合、文字列を「'」で囲った場合には、文字列中に「'」を直接記述することはできません。これは「"」で文字列を囲むか、「\'」のようにエスケープすることで解決できます。このような問題は、JavaScriptの文字列リテラルをエスケープすることと同じです。

設定ウィンドウ (prefwindow)

設定用のウィンドウも他のUIと同様に、XULで定義します。そのときルート要素は<prefwindow>とし、その子要素に<prefpane>でペインを配置します。

設定項目は<preferences>のなかで<preference>で定義します。<preference>のname属性が設定値の名前となり、typeで下表のいずれかの型を指定します。

typeの値
種類 設定値 意味
単純型 bool 真偽値
int 整数値
string 文字列
複合型 unichar Unicode文字列
wstring ローカライズされた文字列
file ファイル

typeと異なる型の値が設定された場合は、typeの型に変換されて設定に格納されます。たとえばtypeをintとしたときに"12AB34"が設定されると、"12"が格納されます。

すでに存在する設定と同名で、しかし型が異なる設定値は設定できません。設定しようとしても無視されます。

<textbox>などの入力要素で、設定対象の<preference>のid属性をpreference属性で指定します。こうすることで設定ウィンドウに現在の設定値が自動で表示され、また設定を変更したときにはその値が保存されます。

サンプル

<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin/"?>

<prefwindow xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  id="pref">
  <prefpane>
    <preferences>
      <preference type="bool" id="AAA" name="extensions.MyAddon.A" />
      <preference type="int" id="BBB" name="extensions.MyAddon.B" />
    </preferences>
    <checkbox preference="AAA" label="A" />
    <textbox preference="BBB" />
  </prefpane>
</prefwindow>

このサンプルでの設定値は、about:configで次のように確認できます。

prefwindow要素のid属性は必須です。このことは開発者センターの検証で、「<prefwindow>` elements without `id` attributes cause errors to be reported in the error console and prevent persistence of certain properties of the dialog.」のように記されています。

またXULは、XUL Editorでプレビューを確認しながら記述すると簡単です。そのとき設定値は、about:configで確認できます。

複数のペイン

prefpane要素を複数定義することで、設定項目をペインごとにまとめられます。ペインの名前をlabel属性で、ペインのIDはid属性で定義します。このid属性は必須で、これを定義しないとペインの切替が機能しません。Using multiple prefpanes - prefpane - XUL | MDN

<prefpane id="pane1" label="Pane1">
  <preferences>
    <preference type="bool" id="AAA" name="aaa.A" />
  </preferences>
  <checkbox preference="AAA" label="A" />
</prefpane>

<prefpane id="pane2" label="Pane2">
  ...
</prefpane>

ラジオボタン

radioの値を記録するには、preferenceをradiogroupに設定します。そしてradioのそれぞれの値を、valueで指定しておきます。

<preferences>
  <preference type="int" id="AAA" name="extensions.MyAddon.A" />
</preferences>
<radiogroup preference="AAA">
  <radio label="A" value="0" />
  <radio label="B" value="1" />
  <radio label="C" value="2" />
</radiogroup>

アドオンマネージャへの[設定]ボタンの追加

アドオンマネージャ (Add-on Manager) のアドオンの項目に、

のような[設定]ボタンを追加するには、install.rdfoptionsURL

<em:optionsURL>chrome://myPackage/content/prefs.xul</em:optionsURL>
Adding preferences to an extension | MDN

のように、設定ウィンドウのxulファイルを指定します。

拡張機能のルートにoptions.xulというファイルが置かれていると、この設定は無視され、そのファイルがインラインオプションとして開かれます。

プラットフォームによっては、アドオンマネージャから開くウィンドウはモーダル (他のウィンドウを操作できないウィンドウ) で表示されます。モードレスとするには、アドオンマネージャ以外からopenDialog()で開く必要があります。588720 – Addon preference dialog shouldn't be modal when opened from addon manager page

設定の更新タイミング

既定ではprefwindowの[OK]ボタンがクリックされたときに、preference要素のname属性で指定された設定に書き込まれます。

設定が変更されたタイミングでユーザー設定を更新したい場合には、その設定のpreference要素のinstantApply属性をtrueとします。

<preference instantApply="true" type="bool" id="A" name="extensions.MyAddon.A" />
instantApply - Mozilla | MDN

インラインオプション (Inline options)

設定画面を設定ウィンドウのようなウィンドウではなく、アドオンの詳細ページに埋め込む形で表示できます。

設定画面をインラインオプションとするには、

  • ファイル名をoptions.xulとして、拡張機能のルートに置く
  • Install Manifestに、
    <em:optionsURL>chrome://myPackage/content/options.xul</em:optionsURL>
    <em:optionsType>2</em:optionsType>
    
    のように定義する

の2つの方法があります。UIとなるXULは、次のように定義します。

<?xml version="1.0"?>

<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <setting type="bool" pref="extensions.MyAddon.A" title="設定項目1" />
    <setting type="string" pref="extensions.MyAddon.B" title="設定項目2" />
</vbox>

ここでは設定ウィンドウのような<prefwindow>や<preferences>は不要で、ただ<setting>のみで記述します。

Firefoxアドオンの情報サイトから、まとめて検索