Now browsing the SCRAPBLOG weblog archives.

Jetpack SDK 0.7 の Clipboard API

Jetpack SDK 0.7 では新たに Clipboard API が追加され、クリップボードへのコピー、およびコピーされたデータの取得が可能になりました。 Clipboard API を使うためには、まず clipboard モジュールをインポートします。

const clipboard = require("clipboard");

クリップボードへのコピー

クリップボードへ文字列をコピーするには、 set メソッドを使います。

clipboard.set("hello");

set メソッドの第2引数へデータ形式を指定して、HTMLソースをコピーすることも可能です。現在対応しているデータ形式は text, html のみですが、将来的には画像やファイルなどの形式にも対応するようです。第2引数を省略した場合は text 形式となります。

clipboard.set("<em>hello</em>", "html");

クリップボード内のデータ取得

クリップボード内のデータを取得するには、 get メソッドを使います。

var data = clipboard.get();

第2引数へデータ形式を指定した場合、その形式のデータを取得できます。第2引数を省略した場合は text 形式となります。

var data = clipboard.get("html");

クリップボード内のデータ形式を調べる

現在クリップボードへコピーされているデータの形式を調べるには、 currentFlavors プロパティを使います。データ形式はひとつとは限りませんので、戻り値は配列となります。

var types = clipboard.currentFlavors;

TOP

nsITransactionManager を使ったトランザクション管理

拡張機能やXULアプリにて、ユーザの操作に対する「元に戻す」「やり直し」機能を実装する際、 nsITransactionManager が便利です。例えば Firefox 本体ではブックマークの追加/削除/移動などが nsITransactionManager によってトランザクション管理されています。

基本形

ユーザがボタンをクリックすると、金額が加算されて合計金額がテキストボックスに表示されるような、簡単なXULアプリを作ってみます。

bank.xul
<?xml version="1.0"?>

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

<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
      title="Bank" onload="init();">

    <script type="application/x-javascript" src="bank.js" />

    <textbox id="total" />
    <hbox>
        <button label="Deposit $1"   oncommand="deposit(1);" />
        <button label="Deposit $10"  oncommand="deposit(10);" />
        <button label="Deposit $100" oncommand="deposit(100);" />
    </hbox>

</page>

引き続き JavaScript で機能を実装します。合計金額を gTotalMoney というグローバル変数に保持し、合計金額をテキストボックスに表示するための updateUI 関数を作っておきます。 onload イベント発生時に呼び出される init 関数では、 updateUI で合計金額の初期値「$0」を表示します。
ボタンをクリックしたときに呼び出される deposit 関数では、引数に渡された金額を gTotalMoney に加算した後、 updateUI で合計金額の表示を更新します。

bank.js
var gTotalMoney = 0;

function init() {
    updateUI();
}

function updateUI() {
    document.getElementById("total").value = "$" + gTotalMoney.toString();
}

function deposit(aMoney) {
    gTotalMoney += aMoney;
    updateUI();
}

トランザクション管理

では、上記のアプリでボタンをクリックした後、その処理を元に戻す/やり直しできるようにします。

はじめに、一連のトランザクションを管理するための nsITransactionManager インスタンスであるグローバル変数 gTxnManager およびその初期化処理を追加します。

bank.js
var gTotalMoney = 0;
var gTxnManager;

function init() {
    gTxnManager = Components.classes["@mozilla.org/transactionmanager;1"].
                  createInstance(Components.interfaces.nsITransactionManager);
    updateUI();
}

トランザクション管理をして元に戻す/やり直し可能にするためには、個々の処理を nsITransaction インタフェースを実装したオブジェクトとして記述する必要があります。今回の場合は預金処理を表す DepositTxn クラスを以下のような設計で作ります。

メンバ 概要
_money コンストラクタの引数に渡された金額を内部的に保持するためのプロパティ。
doTransaction このトランザクションを実行する際に呼び出されるメソッド。
合計金額へ _money 分だけ加算する。
undoTransaction このトランザクションを元に戻す際に呼び出されるメソッド。
合計金額から _money 分だけ減算する。
redoTransaction このトランザクションをやり直しする際に呼び出されるメソッド。
通常は doTransaction と同じ処理を実行すればよい。
merge
isTransient
詳細不明。
function DepositTxn(aMoney) {
    this._money = aMoney;
}

DepositTxn.prototype = {
    doTransaction  : function() { gTotalMoney += this._money; },
    undoTransaction: function() { gTotalMoney -= this._money; },
    redoTransaction: function() this.doTransaction(),
    merge: function() false,
    get isTransient() false,
};

deposit 関数を書き換えて、合計金額を直接変更する代わりに、預金処理トランザクションクラスのインスタンスを生成して gTxnManagerdoTransaction メソッドへ渡します。こうすることで、内部的に DepositTxn インスタンスの doTransaction が呼び出されて合計金額への加算が行われ、なおかつその処理が元に戻す処理のスタックへ追加され、必要に応じて元に戻すことが可能となります。

function deposit(aMoney) {
    var txn = new DepositTxn(aMoney);
    gTxnManager.doTransaction(txn);
    updateUI();
}

次に、アプリのUIへ元に戻す/やり直しするためのボタンを追加します。

bank.xul
    <hbox>
        <button id="undoButton" label="Undo" oncommand="undo();" />
        <button id="redoButton" label="Redo" oncommand="redo();" />
    </hbox>

それぞれのボタン押下時の処理として、 nsITransactionManager の undoTransaction および redoTransaction メソッドを呼び出します。すると、元に戻す/やり直し処理のスタックの最後尾にあるトランザクション(nsITransaction インスタンス)の undoTransaction および redoTransaction メソッドが実行されます。その後、合計金額の表示を更新します。

bank.js
function undo() {
    gTxnManager.undoTransaction();
    updateUI();
}

function redo() {
    gTxnManager.redoTransaction();
    updateUI();
}

バッチ処理

例えば複数のブックマークを選択して削除した後に元に戻すと、複数のブックマークが一括して復元されます。このように、一連のトランザクションをバッチ処理として実行する場合の元に戻す/やり直しを実装します。

はじめに、アプリのUIへ複数の金額を一括して預金するためのボタンを追加します。

bank.xul
        <button label="Deposit $1+$10+$100" oncommand="depositSet([1,10,100]);" />

depositSet 関数は引数に渡された金額の配列すべてについて合計金額へ加算します。このとき、 nsITransactionManager の beginBatch メソッドを呼んでから doTransaction で個々の金額についての預金処理を実行し、最後に endBatch を呼ぶようにします。これにより、一連の預金処理がグループ化され、元に戻す際に一括して元に戻す処理が実行されます。

bank.js
function depositSet(aMoneys) {
    gTxnManager.beginBatch();
    aMoneys.forEach(function(money) {
        var txn = new DepositTxn(money);
        gTxnManager.doTransaction(txn);
    });
    gTxnManager.endBatch();
    updateUI();
}

元に戻す/やり直しスタックのリセット

nsITransactionManager の clear メソッドによって、元に戻す/やり直しスタックをいったんリセットすることができます。このメソッドを使って預金処理を確定させるボタンを作ってみます。

bank.xul
        <button id="completeButton" label="Complete" oncommand="complete();" />
bank.js
function complete() {
    gTxnManager.clear();
}

元に戻す/やり直しスタック内の個数

nsITransactionManager の numberOfUndoItems, numberOfRedoItems プロパティで元に戻す/やり直しスタック内の個数を調べることができます。これを使って元に戻す/やり直し可能なときだけボタンを押下可能にするようにしてみます。

bank.js
function updateUI() {
    document.getElementById("total").value = "$" + gTotalMoney.toString();
    var canUndo = gTxnManager.numberOfUndoItems > 0;
    var canRedo = gTxnManager.numberOfRedoItems > 0;
    function setDisabled(id, disabled) {
        var elt = document.getElementById(id);
        if (disabled)
            elt.setAttribute("disabled", "true");
        else
            elt.removeAttribute("disabled");
    }
    setDisabled("undoButton", !canUndo);
    setDisabled("redoButton", !canRedo);
    setDisabled("completeButton", !canUndo && !canRedo);
}

トランザクションの監視

nsITransactionManager の AddListener に nsITransactionListener インタフェースを実装したオブジェクトを渡して、トランザクションを監視することができます。 RemoveListener で監視を終了するのを忘れずに。

bank.xul
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
      title="Bank" onload="init();" onunload="uninit();">
bank.js
function init() {
    gTxnManager = Components.classes["@mozilla.org/transactionmanager;1"].
                  createInstance(Components.interfaces.nsITransactionManager);
    gTxnManager.AddListener(gTxnListener);
    updateUI();
}

function uninit() {
    gTxnManager.RemoveListener(gTxnListener);
}

nsITransactionListener は多くのメソッドを持ち、元に戻す/やり直し処理の直前/直後などにコールバック処理を設定することができます。今回は元に戻す処理の直前に確認のダイアログを表示し、「キャンセル」ボタンを押下することで中止できるようにします。以下のように willUndo メソッドで true を返すと、処理を中止することができます。

var gTxnListener = {
    willDo: function() {},
    didDo: function() {},
    willUndo: function(aMgr, aTxn) {
        if (!window.confirm("Would you like to undo?"))
            return true;
    },
    didUndo: function() {},
    willRedo: function() {},
    didRedo: function() {},
    willBeginBatch: function() {},
    didBeginBatch: function() {},
    willEndBatch: function() {},
    didEndBatch: function() {},
    willMerge: function() {},
    didMerge: function() {},
};

TOP

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