Now browsing the SCRAPBLOG weblog archives.

while ループ条件中の in 演算子の不思議な挙動

JavaScript で単純なキーバリュー型のオブジェクトから、新しい一意のキーを生成する関数を作った(HTML にしたソースコードはこちら)。

function func() {
    var obj = {1:1, 2:2, 3:3};
    var i = 0;
    do { i++; } while (i in obj);
    return i;
}
alert(func());
alert(func());
alert(func());

これを Firefox 3.6 で実行すると、なぜか結果が 4, 4, 2 だったり 4, 2, 2 だったりと毎回結果が変わる不思議な動作となる。 JavaScript のJIT機能を無効にした場合や、IEなどの他ブラウザでは想定どおり 4, 4, 4 となる。不思議なことに while 文の条件を while (i in obj === true) にした場合も 4, 4, 4 となる。

while ループの条件中でこういう in の使い方をするときは以下のいずれかの方式にしたほうが安全っぽい。

while (obj[i] !== undefined)
while (obj.hasOwnProperty(i))

TOP

Jetpack SDK 0.5 の Tabs API

Jetpack SDK 0.5 で追加された Tabs API を使用すると、指定したURLを新しいタブやウィンドウで開いたり、各タブ内に表示されたページのDOMへアクセスしたり、タブの開閉やページの読み込みなどのイベントに対してコールバック処理を追加したりすることが可能です。

Tabs API を使用するには、はじめに require 関数でモジュールをインポートします。

var tabs = require("tabs");

タブを開く

Tabs API の open メソッドで、指定したURLを新しいタブやウィンドウで開くことが可能です。似た機能を持ったAPIとして、 tab-browser APIaddTab メソッドもありますが、 Tabs API の open メソッドを使用することが推奨されています。

open メソッドの引数に直接URLを指定した場合、新しいタブへ開くと同時にフォーカスします。

tabs.open("http://www.example.com/");

一方、引数を下記のようなオブジェクト形式とした場合、指定したURLを新しいバックグラウンドのタブで開きます。

tabs.open({
    url: "http://www.example.com/",
    inBackground: true
});

各タブの情報を取得する・タブ内のページのDOMへアクセスする

Tabs API の activeTab プロパティによって現在のタブ(Tab オブジェクト)を取得したり、 Tabs API のオブジェクト自体を for ~ in ループで回すことで全ウィンドウ内の全タブ(Tab オブジェクト)を列挙したりすることが可能です。 Tab オブジェクトは、 title プロパティでタブのタイトルやURLを取得したり、 contentDocument プロパティでタブ内に表示されたページのDOMへアクセスしたりすることが可能です。

下記は、現在のタブのタイトルとURLを取得し、現在のタブ内に表示されたページのDOMの window.alert メソッドを使って表示させる例です。

var tab = tabs.activeTab;
tab.contentWindow.alert(tab.title + "
" + tab.location);

Tab オブジェクトの面白い機能として、 thumbnail プロパティでタブ内に表示されたページのサムネイル画像を html:canvas 要素として取得することが可能です。下記は、全ウィンドウ内の全タブについてサムネイル画像を取得して data: URL へ変換し、それらを表示するHTMLを新しいウィンドウで開く例です。

var html = "";
for (var tab in tabs) {
    html += '<img src="' + tab.thumbnail.toDataURL() + '"/>';
}
tabs.open({
    url: "data:text/html,<html><body>" + html + "</body></html>";,
    inNewWindow: true
});

タブを閉じる・移動する

Tab オブジェクトには、タブを閉じる close、タブを同一ウィンドウ内の別の位置へ移動する move、タブへフォーカスする activate などのメソッドもあります。下記は、現在のタブを閉じる例です。

tabs.activeTab.close();

タブ関連イベントへのコールバック処理

onOpen(タブを開いたとき), onClose(タブを閉じたとき), onActivate(アクティブなタブが変化したとき), onReady(タブ内のページのDOMツリー構築時 = DOMContentLoaded イベント発生時), onPaint(タブ内のページで再描画発生時 = MozAfterPaint イベント発生時)などに対してコールバック処理を追加することができます。下記は、タブ内のページのDOMツリー構築完了時に、そのページのURLをコンソールへ表示する例です。

tabs.onReady = function(tab) {
    console.log("onReady: " + tab.location.href);
};

TOP

Jetpack SDK 0.5 の Unload API

Jetpack SDK の Unload API を使用すると、以下の例のように Firefox の終了や拡張機能の無効化などのシグナルを検知してコールバック処理を実行することが可能となります。

require("unload").when(function(reason) {
    switch (reason) {
        case "disable": 
            // 拡張機能が無効化される直前に実行する処理
            break;
        case "uninstall": 
            // 拡張機能が削除される直前に実行する処理
            break;
        case "shutdown": 
            // アプリケーション終了直前に実行する処理
            break;
    }
});

Firefox 3.6 以下ではアドオンマネージャで該当する拡張機能の「無効化」または「削除」ボタンを押下して Firefox を終了または再起動する直前にコールバック処理が実行されるのに対して、 Firefox 4.0b2 以上では「無効化」または「削除」ボタンを押下した直後にコールバック処理が実行されます。

TOP

Jetpack SDK 0.5 の Selection API

Jetpack SDK 0.5 で追加された Selection API を使用すると、選択範囲の取得や変更などが可能となります。

Selection API を使用するためには、はじめに require 関数でモジュールをインポートします。

var selection = require("selection");

選択範囲の取得

Selection API の text および html プロパティで選択範囲の文字列あるいはHTMLソースを取得・変更することが可能です。また、Ctrlキーを押下しながら複数の範囲を選択した場合、 contigious プロパティが false となります。この時、 Selection API のオブジェクト自体に対して for ~ in ループで個々のサブ選択範囲(Selection オブジェクト)を取得することが可能です。

以下は、選択範囲が複数ある場合を考慮し、全ての選択範囲のHTMLソースをコンソールへ出力する例です。

if (selection.contiguous) {
    // 選択範囲がひとつだけの場合
    console.log(selection.html);
}
else {
    // 複数の選択範囲がある場合
    for (var subsel in selection) {
        console.log(subsel.html);
    }
}

範囲を選択したときのコールバック処理

onSelect プロパティによって、範囲を選択したときのコールバック処理を追加することが可能です。以下は、範囲を選択した時に、その選択範囲を <span> タグで囲み、蛍光ペンで着色したような表示にする例です。

selection.onSelect = function() {
    selection.html = '<span style="background-color: yellow;">' + selection.html + '</span>';
};

TOP

Jetpack SDK 0.5 の Reuqest API

Jetpack SDK 0.5 で追加された Request API を使用すると、 XMLHttpRequest によってWebサーバとデータを送受信する処理をより簡単に実装できます。特に、レスポンスがJSONかXML形式であるようなWebサービスのAPIを利用する場合に重宝しそうです。

この記事では、 Request API を使い、Twitter でキーワード「Firefox 4」を含むツイートの検索結果をコンソールへ列挙する例を紹介します。

Twitter のAPI仕様

実装に入る前に、 Twitter の検索用APIの仕様を簡単に示しておきます。

リクエスト

キーワード xxx で検索する場合、以下のようなURLへGETメソッドで送信する。
http://search.twitter.com/search.json?q=xxx

レスポンス

キーワードにマッチしたツイートのデータが、下記のようなJSON形式で返る。

{
    "results": [
        { "text": "(1番目のツイートの内容)", "from_user": "(1番目のツイートの発言者)" ... },
        { "text": "(2番目のツイートの内容)", "from_user": "(2番目のツイートの発言者)" ... },
        { "text": "(3番目のツイートの内容)", "from_user": "(3番目のツイートの発言者)" ... },
        ...
    ]
}

実装

まず、 require 関数でモジュールをインポートします。

var requests = require("request");

次に、 Request APIの Request コンストラクタを用いて、 Request オブジェクトのインスタンスを生成します。コンストラクタの引数には、以下のプロパティを有するオブジェクトを渡します。

プロパティ 概要
url データ送信先のURL
onComplete データ受信時(XHRでいうところの readyState == 4)のコールバック処理
headers 必要に応じてリクエストヘッダ(User-Agent や Referer)をオブジェクト形式でセットする。例:
headers: { "User-Agent": "MyApp", Referer: "http://..." },
content 必要に応じてリクエストのパラメータをオブジェクト形式でセットする。
contentType 必要に応じてHTTPヘッダの Content-Type の値をセットする。
デフォルトでは application/x-www-form-urlencoded

生成した Request インスタンスの get メソッドを呼び出すと、GETメソッドでリクエストが送信されます。なお、 post メソッドを呼び出すと、POSTメソッドでリクエストが送信されます。

// Request インスタンスの生成
var request = requests.Request({
    url: "http://search.twitter.com/search.json",
    content: { q: "Firefox 4" },
    onComplete: function () {
        // ToDo
    }
});
// GETメソッドで送信
request.get();

Webサーバからのレスポンスが返って onComplete メソッドがコールバックされると、 Request インスタンスの response プロパティからレスポンス内容(Response オブジェクト)を取得することができるようになります。 Response オブジェクトは色々なプロパティを有し、 json プロパティや xml プロパティでレスポンスのボディ部をJSONあるいはXML形式でパースした結果を取得したり、 headers プロパティでレスポンスのヘッダ部の各フィールド値を取得したりすることが可能です。

今回はレスポンスのボディ部をJSON形式でパースし、前述のAPI仕様にあるとおり "results" プロパティでツイートのデータの配列を取得し、配列の各要素に対して "from_user", "text" プロパティで発言者と発言内容を取得します。

    onComplete: function () {
        var results = this.response.json.results;
        results.forEach(function(result) {
            console.log("user: " + result.from_user);
            console.log("text: " + result.text);
        });
    }

TOP

Jetpack SDK 0.4 の Page Worker API

HTTP(S)によりWebサーバとデータを送受信するには、 xhr API で XMLHttpRequest のインスタンスを生成する方法や、 Jetpack SDK 0.5 で追加された Request API を使用する方法があります。しかし、これらのAPIではWebサーバからのレスポンスがHTMLデータだった場合、特定のノードにある文字列を抽出したりする処理がやや困難となります。

一方、 Jetpack SDK 0.4 で追加された Page Worker API を用いると、不可視のフレーム(iframe 要素)内に指定したURLのHTMLドキュメントをロードし、パースされた結果をDOM操作することが可能となります。この記事では、 Page Worker API を用いて Wikipedia の Internet に関するページを不可視のフレームに読み込んで見出し(H2 > SPAN要素)を列挙する例を紹介します。

基本的な使い方

まず、 require 関数でモジュールをインポートします。

var pageWorker = require("page-worker");

Page Worker APIの Page コンストラクタを用いて、 Page オブジェクトのインスタンスを生成します。コンストラクタの引数には、以下のプロパティを有するオブジェクトを渡します。

プロパティ 概要
content 不可視のフレームにロードするURL、またはHTMLソース
onReady ロード完了時のコールバック処理
allow 不可視のフレーム内でのスクリプト実行を許可するかどうかなどを指定するためのオプション。
allow: { script: false } でスクリプトの実行を不許可に設定することが可能。

生成した Page インスタンスを add メソッドへ渡すと、不可視のフレーム内への読み込みが開始されます。

var page = pageWorker.Page({
    content: "http://en.wikipedia.org/wiki/Internet",
    onReady: function() {
        // ToDo
    }
});
pageWorker.add(page);

不可視のフレームへの読み込みが完了すると、 Page インスタンスの window および document プロパティ経由で不可視のフレーム内のDOMへアクセスすることができるようになります。以下の例では、不可視のフレームへの読み込み完了時、 HTMLのURLおよびタイトルを取得してコンソールへ表示し、さらにH2要素直下にあるSPAN要素の中身の文字列を列挙して表示します。

    onReady: function() {
        var url = this.window.location.href;
        var title = this.document.title;
        console.log(url + "
" + title);
        var elts = this.document.querySelectorAll("h2 > span");
        Array.forEach(elts, function(elt) {
            console.log(elt.textContent);
        });
        pageWorker.remove(this);
    }

なお、 onReady の最後で Page オブジェクトのインスタンス自身を PageWorker の remove メソッドへ渡すことで、 window および document プロパティが削除され、不可視のフレーム内でのページ読み込みに使用されたメモリが解放されます。

TOP

Jetpack SDK 0.4 の Simple Storage API

拡張機能の設定値のような少量のデータを保存する際には Preferences Service API を使用しますが、より多くのデータを永続的に(Firefox を終了しても保持されるように)保存するには、 Simple Storage API が便利です。

基本的な使い方

はじめに require 関数でライブラリをインポートします。

var simpleStorage = require("simple-storage");

もっとも単純な方法は、 storage プロパティへ直接値をセットする方式です。以下は “test” という文字列を保存した後、値を取り出す例です。

simpleStorage.storage = "test";
console.log(simpleStorage.storage);    // test

文字列だけでなく、数値、配列、オブジェクトなどをセットして保存可能です。

simpleStorage.storage = { "foo": 100, "bar": 200 };
console.log(simpleStorage.storage.foo);    // 100

上記は以下のように書くことも可能です。

simpleStorage.storage.foo = 100;
simpleStorage.storage.bar = 200;

データの保存先

Simple Storage APIを使って保存したデータは、プロファイルフォルダ配下の jetpack{パッケージマニフェストのID}simple-storagestore.json へJSON形式で保存されます。また、ファイルへの出力タイミングは Jetpack SDK 0.5 時点では Firefox 終了時のみですが、将来的には Firefox 使用中も最大で5分に1回出力される仕様となるようです。

データ保存可能サイズ

ひとつの拡張機能が保存できるデータサイズは5MBまでとなっています。 Simple Storage API の quotaUsage プロパティにて現在のデータ使用量(5MBを1.0とした割合)を調べたり、 onOverQuota プロパティにてデータ使用量が最大値を超過したときのコールバック処理を追加したりすることも可能です。ただし、 Jetpack SDK 0.5 時点では正常に動作しないようです。

console.log(simpleStorage.quotaUsage);
simpleStorage.onOverQuota = function() {
    // データ使用量が最大値を超過したときのコールバック処理
};

TOP

Jetpack SDK 0.4 の Widget と Private Browsing API 使用例

この記事では、 Jetpack 0.4 で新たに追加された4つの標準APIのうち、 WidgetPrivate Browsing を使用して実際に機能を開発する手順を解説します。なお、解説はメインプログラムの作成手順以降となります。 Jetpack SDK 自体の基本的な使い方については、 はじめての Jetpack SDK 0.2 を参照してください。

完成イメージ

プライベートブラウジングを開始/停止するためのボタンを有する Jetpack 拡張機能を、以下のように3段階に分けて実装します。

  • フェーズ1: Widget API を使ってUIを追加する
  • フェーズ2: Private Browsing API を使ってボタン押下時にプライベートブラウジングを開始/停止する
  • フェーズ3: Simple Dialog API (自作ライブラリ)を使ってプライベートブラウジング停止確認ダイアログを表示する

フェーズ1: Widget API を使ってUIを追加する

一般的な拡張機能では Firefox のメニューバー、ツールバー、ステータスバーなど様々な箇所にUIを追加することが可能で、それゆえUIの一貫性が保たれなくなる問題があります。 Jetpack では Widget API を使ってUIを追加することで、すべての拡張機能のUIの一貫性が保たれるようになります。現在のところ、 Widget API ではステータスバー上部に現れる拡張機能専用のぶっといバーにボタン型UIが追加可能ですが、このUI仕様は今後変更される予定です(参考)。

フェーズ1では、 Widget API を使って非常に単純なボタン型UIを追加します。まず、 main.js 内に以下のように記述して Widget API のモジュールを読み込みます。

const widgets = require("widget");

引き続き main.js へ以下のような main 関数を記述します。

exports.main = function(options, callbacks) {
    var button = widgets.Widget({
        label: "Start/Stop Private Browsing",
        image: "chrome://browser/skin/Privacy-48.png",
        onClick: function(event) {
            // ToDo
        }
    });
    widgets.add(button);
};

はじめに Widget API の Widget(options) コンストラクタを用いてボタンのインスタンスを生成します。コンストラクタの引数 options には下記の3つのプロパティを指定します。

プロパティ 詳細
label ボタンに対する説明の文言。画面に表示されないが、アクセシビリティの観点から指定が必要。
image ボタンのアイコン画像のURL。アイコン画像は24×24ピクセルにリサイズされる。
onClick ボタン押下時に実行するコールバック関数。

なお、 content プロパティに HTML をセットすることで、単純なボタンではなく凝ったUIを作ることも可能です(参考)。

生成したボタンのインスタンスは、 Widget API の add メソッドを用いて実際に追加します。ここまでで、ひとまず「cfx run -a firefox」コマンドで動作確認し、ボタンが表示されることを確認してください。なお、ボタンが配置されるバーは Ctrl+Shift+U で表示/非表示可能です。

フェーズ2: Private Browsing API を使ってボタン押下時にプライベートブラウジングを開始/停止する

フェーズ2では、 Private Browsing API を使ってボタン押下時にプライベートブラウジングを開始/停止できるようにします。はじめに main.js の冒頭部分に以下の内容を追加し、 Private Browsing API モジュールを読み込みます。

const privateBrowsing = require("private-browsing");

さらに、 Widget(options) コンストラクタの onClick オプションを以下のように修正します。

        onClick: function(event) {
            privateBrowsing.active = !privateBrowsing.active;
        }

Private Browsing API の active プロパティは真偽値で、現在プライベートブラウジング中かどうかを調べたり、値を変更することでプライベートブラウジングを開始/停止することができます。

フェーズ3: Simple Dialog API を使ってプライベートブラウジング停止確認ダイアログを表示する

Private Browsing API にはプライベートブラウジング開始/停止の直前/直後に呼び出すコールバック関数を追加/削除するためのメソッドが用意されています。フェーズ3では、プライベートブラウジング停止直前に確認ダイアログを表示する機能を実装します。

まず、 main.js の冒頭部分に以下の内容を追加し、自作ライブラリである Simple Dialog API モジュールを読み込みます。

const simpleDialog = require("simple-dialog");

Simple Dialog API の詳細は はじめての Jetpack SDK 0.2 の「自作ライブラリの作成」の項を参照してください。記載されているソースコードをコピペして main.js と同一フォルダ内に「simple-dialog.js」として格納してください。

次に、 main 関数内の最後に以下の内容を追加します。

    privateBrowsing.onBeforeStop = function(cancel) {
        var yes = simpleDialog.confirmYesNo("Do you want to stop private browsing?");
        if (!yes)
            cancel();
    };

Private Browsing API の onBeforeStop プロパティへ、プライベートブラウジング停止直前に呼び出されるコールバック関数をセットします。コールバック関数内では Simple Dialog API の confirmYesNo メソッドを使って確認メッセージを表示し、ユーザが「いいえ」ボタンを押したらコールバック関数の引数 cancel を実行し、停止処理をキャンセルします。

なお、 onBeforeStop.add(callback) メソッドを使ってコールバック関数 callback を追加したり、 onBeforeStop.add([callback1, callback2, ...]) のようにして複数のコールバック関数を一括して追加することも可能です。

動作確認

ボタンを押下することで、プライベートブラウジングが開始/停止されることを確認してください。また、停止直前に停止確認ダイアログが表示され、「いいえ」ボタンを押下するとプライベートブラウジングが停止されないことを確認してください。

TOP

Jetpack SDK 0.4 でのパッケージマニフェストの id プロパティの仕様変更

Jetpack SDK 0.4 ではパッケージマニフェスト(package.json)の id プロパティに関する仕様変更があり、自分で決めたIDを明示的に記述することができなくなり、代わりにSDKによって自動生成されるようになりました。

SDK 0.3 以前の仕様

SDK 0.3 以前のバージョンでは、 id プロパティの取り扱いについて以下2通りの方式がありました。

方式1: 明示的に記述する

hello-world@xuldev.org のようにメールアドレス形式のIDを自分で決めて、パッケージマニフェスト中に id プロパティとして明示的に記述する方式です。「cfx xpi」コマンドによってXPIインストーラを生成すると、この id プロパティが拡張機能のインストールマニフェストの <em:id> タグの値になります。今まで一般的な拡張機能を開発したことがある方であればこちらの方式のほうが馴染み易いと思います。

方式2: SDKで自動生成する

パッケージマニフェストには id プロパティを記述せず、SDKによってIDを自動生成する方式です。「cfx xpi」コマンドでXPIインストーラを生成すると、SDKによって自動的にIDが生成され、XPIインストーラ中のインストールマニフェストの <em:id> タグの値となります。この方式ですと、XPIインストーラを生成するたびに毎回異なるIDが自動生成されるため、新しいバージョンを作って Firefox へインストールするたびに別物の拡張機能としてインストールされてしまう問題がありました。

SDK 0.4 での仕様

SDK 0.4 以降では、 SDK 0.3 以前での方式1のように自分で決めたIDを明示的に記述することはできなくなり、IDはSDKで自動生成された公開鍵・秘密鍵のペアとして管理されるようになりました。

これまで方式1のようにパッケージマニフェスト中に自分で決めたIDを id プロパティとして記述していた場合、いったん id プロパティを削除してください。 id プロパティが記述されていない状態で、SDKで「cfx run」または「cfx xpi」コマンドを実行すると、以下のようなメッセージとともにIDが自動生成されてパッケージマニフェストに id プロパティの記述が追加されます。

No 'id' in package.json: creating a new keypair for you.
package.json modified: please re-run 'cfx run'

また、このとき、「~/.jetpack/keys/」(Windows の場合「%USERPROFILE%.jetpackkeys」)配下に、自動生成されたIDに対応する公開鍵・秘密鍵のペアなどが記述されたファイルが生成されます。

再度「cfx run」または「cfx xpi」を実行すると、今度は普通に起動またはXPIインストーラが生成されます。

複数のPCで開発する場合

SDK 0.4 では、パッケージのソースファイルを丸ごと別のPCへコピーしてから「cfx run」や「cfx xpi」コマンドを実行するとエラーとなります。もしあなたがそのパッケージの正規開発者ならば、前述の公開鍵・秘密鍵のペアなどが記述されたファイルも一緒にコピーして適切なディレクトリへ配置する必要があります。もしあなたがそのパッケージの派生版を開発しようとしているのならば、パッケージマニフェストの id プロパティを削除して再度SDKによって自動生成する必要があります。このような仕組みによって、そのパッケージが正当な開発者によって作られたものであることが Firefox 側で検証可能となります。

TOP

Jetpack SDK 0.4 で cfx コマンドのユーザ定義オプションを設定する

Jetpack SDK 0.4 では、 cfx コマンド実行時に頻繁に使うオプションをあらかじめ local.json に記述しておき、 cfx コマンド実行時に簡単に呼び出すことが可能となりました。

cfx run のオプション

Jetpack SDK では、拡張機能を動作確認するときに「cfx run」コマンドを用いますが、このとき、通常のインストール先とは異なる Firefox を起動して実行したい場合に「-b」オプションを付加したり、指定したプロファイルから起動したい場合「-P」オプションを付加します。以下は testuser というプロファイルで Firefox 3.7 上で拡張機能の動作確認をする例です。

cfx run -a firefox -b "%ProgramFiles%Mozilla Firefox 3.7firefox.exe" -P "%appdata%MozillaFirefoxProfiles    estuser"

cfx コマンドのユーザ定義オプション

前述のような長いオプションを毎回入力するのは面倒です。そこで、 Jetpack SDK 0.4 で導入された local.json に頻繁に使用するオプションを記述しておくと、 cfx コマンドから簡単に呼び出し可能となります。まず、 SDK の展開先フォルダ直下に local.json というファイルを作成し、下記のような内容を記述します。

{
  "configs": {
    "ff37": [
      "-a", "firefox",
      "-b", "C:Program FilesMozilla Firefox 3.7firefox.exe",
      "-P", "C:UsersadminAppDataRoamingMozillaFirefoxProfilestestuser"
    ]
  }
}

すると、 cfx コマンドの --use-config オプションにより local.json に記載した ff37 という名前のユーザ定義オプションが有効になります。 --use-config=ff37 の代わりに -g ff37 としても構いません。

cfx run --use-config==ff37

もちろん cfx run だけでなく cfx xpi などでも同様の方式で local.json に定義したオプションを呼び出し可能です。なお、 local.json 内ではバックスラッシュを「」とエスケープすること、また Windows の環境変数は使用できないことにご注意ください。

cfx コマンドのデフォルトオプション

default という名前のユーザ定義オプションを記述すると、これは --use-config オプション未指定時のデフォルトオプションとして自動的に呼び出されます。例えば、 local.json へ以下のように記述を追加します。

{
  "configs": {
    "default": [
      "-a", "firefox",
      "-P", "C:UsersadminAppDataRoamingMozillaFirefoxProfilestestuser"
    ],
    "ff37": [
      "-a", "firefox",
      "-b", "C:Program FilesMozilla Firefox 3.7firefox.exe",
      "-P", "C:UsersadminAppDataRoamingMozillaFirefoxProfilestestuser"
    ]
  }
}

すると、単に cfx run などを実行したとき、設定名 default で定義したデフォルトオプションが有効となります。

TOP