Jetpack SDK 0.7 の Panel API
Jetpack SDK 0.7 では新たに Panel API が追加され、HTMLで記述されたGUIを表示可能なパネル型UIを追加するこが可能になりました。 Widget API で追加したボタン型UIと連携して、ボタンをクリックするとパネルを開くことも可能です。
基本的な使い方
Panel API を使うためには、まず panel モジュールをインポートします。なお、本記事のサンプルスクリプトでは exports.main
の記載などを省略しています。
var panels = require("panel");
次に、 Panel コンストラクタを使ってパネル型UIを作ります。引数には、色々なプロパティを有するオブジェクトを渡します。下記の例では 幅200、高さ150ピクセルのパネル上に指定したURLをロードします。
var myPanel = panels.Panel({
width : 200,
height: 150,
contentURL: "http://www.example.com/",
});
Panel コンストラクタで生成した panel オブジェクトの show
メソッドを呼び出すと、パネルを開くことができます。メソッドの引数に何も指定しない場合は親ウィンドウの中央にパネルが開かれます。
myPanel.show();
widget API の Widget コンストラクタの引数オブジェクトの panel プロパティに panel オブジェクトをセットすることで、ボタン型UIをクリックしてパネルを開くこともできます。
const widgets = require("widget");
var button = widgets.Widget({
label: "Test",
image: "chrome://browser/skin/Secure24.png",
panel: myPanel
});
widgets.add(button);
パネル型UIへ自前のHTMLをロードする
self API を使うと、パッケージの data フォルダ内に格納した自前のHTMLをパネル型UIへロードすることができます。
<html>
<body>
<p>Jetpack</p>
<button onclick="say('Hello');">Say Hello</button>
<button onclick="say('Bye');">Say Bye</button>
</body>
</html>
const self = require("self");
var myPanel = panels.Panel({
width : 200,
height: 150,
contentURL: self.data.url("panel.html"),
});
パネル型UIへコンテントスクリプトをロードする
引き続き、上記のパネルに配置されたボタンをクリックした際に、 Jetpack SDK の notifications API を使って通知を表示させるようにします。パネル型UIにロードされたHTMLからは直接 notifications API を呼び出すことはできず、2つの JavaScript コンテキスト間でメッセージを受け渡す、少し回りくどいやり方となります。
まず、 Panel コンストラクタの引数オブジェクトへ contentScriptURL プロパティを追加し、 self API を使って data フォルダ内に格納した panel.js のURLを配列で指定します。また、 contentScriptWhen プロパティに “ready” という値を指定することで、パネル内のHTMLロード完了時にスクリプトが実行されるようになります。
const self = require("self");
var myPanel = panels.Panel({
width : 200,
height: 150,
contentURL: self.data.url("panel.html"),
contentScriptURL: [self.data.url("panel.js")],
contentScriptWhen: "ready",
});
contentScriptURL プロパティによって読み込まれるスクリプト(コンテントスクリプト)は、HTMLに<script>タグで記述したスクリプトと異なる特殊な JavaScript コンテキストで実行されます。そのため、HTMLの window オブジェクトにアクセスするには明示的に window.
とする必要があります。以下の例では window オブジェクト直下に say
という関数を追加し、 button 要素の onclick 属性から呼び出し可能にしています。また、コンテントスクリプトでは特殊な変数 panel の sendMessage メソッドによって main.js 側の JavaScript コンテキストへ文字列(あるいはJSON文字列化可能なオブジェクトなど)を送ることができます。
window.say = function(text) {
panel.sendMessage(text);
};
panel オブジェクトの sendMessage で送られた文字列(あるいはJSON文字列化可能なオブジェクトなど)は、 main.js 側の JavaScript コンテキスト内の panel オブジェクトの onMessage コールバック関数によって受け取ることができます。以下の例では受け取った文字列を notifications API を使って通知として表示します。
const self = require("self");
var myPanel = panels.Panel({
width : 200,
height: 150,
contentURL: self.data.url("panel.html"),
contentScriptURL: [self.data.url("panel.js")],
contentScriptWhen: "ready",
onMessage: function(message, callback) {
require("notifications").notify({
title: "Message from Panel",
text: message
});
}
});
Jetpack SDK 0.7 の Notifications API
Jetpack SDK 0.7 では新たに Notifications API が追加され、Firefox のダウンロード完了通知などでお馴染みのスライド式の通知UIを表示することが可能になりました。 Notifications API を使うためには、まず notifcations モジュールをインポートします。
const notifications = require("notifications");
通知を表示するためには notify
メソッドを呼び出します。引数には、以下のプロパティを有するオブジェクトを渡します。
プロパティ | 概要 |
---|---|
title text |
通知に表示する文字列。 |
iconURL |
通知に表示する画像のURL。 self APIを使って自パッケージ内の data フォルダに格納した画像を指定することも可能。 |
data |
onClick の引数として渡される文字列。 |
onClick |
通知をクリックした際の処理。引数に data プロパティの値が渡される。 |
notifications.notify({
title: "Jetpack",
text: "This is a notification.",
iconURL: "chrome://browser/skin/Geolocation-64.png",
data: "test",
onClick: function(data) {
console.log(data);
},
});
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;
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 API の addTab
メソッドもありますが、 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);
};
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 以上では「無効化」または「削除」ボタンを押下した直後にコールバック処理が実行されます。
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>';
};
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); }); }
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
プロパティが削除され、不可視のフレーム内でのページ読み込みに使用されたメモリが解放されます。
Jetpack SDK 0.4 の Widget と Private Browsing API 使用例
この記事では、 Jetpack 0.4 で新たに追加された4つの標準APIのうち、 Widget と Private 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, ...])
のようにして複数のコールバック関数を一括して追加することも可能です。
動作確認
ボタンを押下することで、プライベートブラウジングが開始/停止されることを確認してください。また、停止直前に停止確認ダイアログが表示され、「いいえ」ボタンを押下するとプライベートブラウジングが停止されないことを確認してください。
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 側で検証可能となります。