jetpack library 事始め
DESCRIPTION
Mozilla 勉強会@東京 5th の発表資料TRANSCRIPT
Jetpack Library 事始め
2011/01/16あすかぜ (海津智宏)
@asukaze55
自己紹介• http://www.asukaze.net/• RemoveTabs, Tabloc という、小さな拡張を公開してい
ます。• Add-on SDK (Jetpack SDK) の開発状況を追いかけて、メ
モをまとめています。• Mozilla とは関係ないですが、 Cassava Editor という
CSV エディタを公開しています。• 2010/12/01 に転職しました。転職先の会社
は、 Mozilla とは商売敵かもしれません :)• ヒキコモリ体質のため、 Mozilla の勉強会等に参加す
るのは初めてです。皆様よろしくお願いします。
Add-on SDK (Jetpack SDK) とは?• 現在開発中の、新しい拡張開発キット。• XUL などの知識を必要とせず、 HTML, JavaScript,
CSS の知識だけで拡張が開発できるかも。• API は Firefox のバージョンに非依存。拡張を再
パッケージングするだけで Firefox の新しいバージョンに対応できるはず。
• 開発した拡張は再起動不要でインストール/アンインストール可能。
• サードパーティが自由に API を追加できる。
History
Jetpack Prototype
Jetpack Reboot Jetpack SDK Add-on SDK
2009/05 2010/03 2010/12
開発終了
0.1 ~ 0.9 β1
feature-stable
Prototype と SDK
Firefox
API(Jetpack Prototype 拡張 )
Jetpack Jetpack Jetpack
Firefox
Jetpack
Core
Jetpack
Core
Jetpack
Core
Jetpack Prototype Add-on SDK
Add-on SDK 1.0b1 の API(addon-kit)
• ページコンテンツの変更• ウィンドウ操作・タブ操作• ウィジェット表示・パネル表示• コンテキストメニュー表示• 選択範囲の取得• HTTP リクエスト送信・ DOM 操作• クリップボードの取得・更新• アラートメッセージ表示• プライベートブラウジングの開始・終了• ストレージへの情報保存 以上!
Add-on SDK 1.0b1 の API(addon-kit)
• 標準 API で GreaseMonkey +α のことはできる。• が、 XUL ベースの拡張に比べると、できるこ
とは限られている。– ブラウザ自体の UI を拡張できるのはウィジェット
とコンテキストメニューのみ
• 低レベル API を使って自作ライブラリを作れば、なんでもできる!
Add-on SDK の低レベル API(api-utils)
• Components オブジェクトへのアクセス– {Cc, Ci, Cu, Cr, Cm, components} = require(“chrome”);
• tabbrowser 要素や window 要素へのアクセス– require(“tab-browser”)– require(“window-utils”)
• API 提供用のヘルパ関数– require(“unload”).ensure()– require(“api-utils”).publicConstructor()– require(“errors”). catchAndLog()
できることの例• ブラウザ内の任意の場所に XUL 要素を追
加できる。• ブラウザ内の任意の場所にイベントリス
ナを仕掛けられる。
考慮すべきこと1. 拡張が再起動不要となるようにする2. Firefox, SDK のバージョンに非依存な API
を提供する3. addon-kit の API と似た使い方を提供す
る1. construct/destroy モデル2. EventEmitter モデル3. エラー発生時のスタックトレース出力
4. その他
1) 再起動不要• 拡張が無効化/アンインストールされた
ときに状態を元に戻すのはライブラリの責任。– ブラウザに対する変更を全て覚えておき、終
了時に元に戻す。• api-utils 内の unload モジュールを使うと、
終了時の処理を登録できる。– イベントの伝播は bootstrap.js → harness →
cuddlefish → unload という流れ。
unload モジュールfunction Foo() { // 変更処理 require("unload").ensure(this);}
Foo.prototype = { unload: function(reason) { // 復帰処理 }};
function bar() { // 変更処理 require("unload").when( function(reason){ // 復帰処理 });}
ensure 関数にオブジェクトを登録する。終了時には、オブジェクトごとに1 回ずつ unload 関数が実行される。
when 関数にコールバック関数を登録する。複数回 when を呼び出すと、その回数分処理が実行される。
Firefox 5.x
api-utils 2.x
addon-kit 2.x
MyLibrary 2.0
MyExtension 2.0
2) バージョン非依存
変わらないはず
変わるかも
変わるかも
Firefox 4.0
api-utils 1.0b1
addon-kit 1.0b1
MyLibrary 1.0
MyExtension 1.0変えちゃダメ
2) バージョン非依存• Firefox の内部構造に依存しない、汎用的
な API を提供する• 今後の Firefox の変化を先取りし、あらか
じめ備えておく– E10S 対応!!
E10S (Electrolysis) とは?• 「プロセス分離」のコードネーム。• 今後、ブラウザ本体と Web ページのコンテンツと
Jetpack 拡張は、それぞれ別プロセスで動作するようになる。
firefox.exe
jetpack process content process←メッセージ通信→
×直接アクセスはできない
コンテンツプロセスの分離 を考慮した API
var pageSourceItem = contextMenu.Item({ label: "Edit Page Source", contentScript: 'on("click", function (node, data) {' + ' postMessage(document.URL);' + '});', onMessage: function (pageURL){ editSource(pageURL); }});
別プロセスで動作するスクリプ
ト
メッセージ通信
プロセス間で関数オブジェクトは受け渡しできないので、コンテンツプロセスで動作する部分はスクリプトを文字列もしくは URL で受け渡す。JSON 形式に変換可能なオブジェクトはメッセージ通信で受け渡し可能。
content モジュールfor (window in require("window-utils").windowIterator()) { require("content").Worker({ window: window, contentScript: 'postMessage("message!");', onMessage: function(data) { console.log(data); } });}
Worker として登録したスクリプトは対象の window 上で動作する。オプションは page-mod や context-menu と同様。
Jetpack プロセスの分離を考慮した API
• cfx run に 「 --e10s」オプションを付加することで試行できる。
• SDK 本体の対応もこれから。今後の動向に注目。 – self-e10s-adapter などは参考になる
> cfx run --e10s -b "C:\Program Files\Minefield\firefox.exe"…Error: Module 'widget' requires chrome privileges and has no e10s adapter.OK…
いまここ1. 拡張が再起動不要となるようにする2. Firefox, SDK のバージョンに非依存な API
を提供する3. addon-kit の API と似た使い方を提供する
1. construct/destroy モデル2. EventEmitter モデル3. エラー発生時のスタックトレース出力
4. その他
3-1) construct/destroy モデルwidgets.add(widgets.Widget({ label: …, image: …, onClick: …}));
widgets.remove(w);
add/ remove モデルはもう古い!
widgets.Widget({ label: …, image: …, onClick: …});
w.destroy();
時代は construct/destroy モデル!
コンストラクタの中で初期化処理まで実行する。明示的に変更を元に戻す時は destroy メソッドを使う。
コンストラクタ作成時に便利な関数
• new ClassName({…}) でも ClassName({…}) だけでもオブジェクトを作れるようにする。– require(“api-utils”).publicConstructor()
• オプションの内容をチェックする。– require(“api-utils”).validateOptions()
• Array もしくは スカラ値を受け取るプロパティを作る。– require(“collection”).addCollectionProperty()
3-2) EventEmitter モデルtabs.onOpen = function(){…};tabs.onReady = function(){…};tabs.onActivate = function(){ …};tabs.onDeactivate = function(){ …};
onXXX プロパティはもう古い!
tabs.on(‘Open’, function(){…});tabs.on(‘Ready’, function(){…});tabs.on(‘Activate’, function(){ …});tabs.on(‘Deactivate’, function(){ …});
時代は EventEmitter モデル!
EventEmitter を使うことで、全てのイベントについて_emit() でのイベント送信、 on() でのイベント受信に統一できる。
events モジュールconst { EventEmitter } = require("events");var object = EventEmitter.compose({ constructor: function() {…}, fire: function() { this._emit('event1'); }})();
object.on('event1', function(){ console.log("Event Fired!");});object.fire();
EventEmitter.compose()() は Trait という形のオブジェクトを返す。_emit() など 「 _」から始まるメソッドは、 this からしか呼び出せない。
3-3) スタックトレース出力• 実行時エラーの発生時、放っておくと何も言わずに処理が終了してしまう。きちんとスタックトレースを出力するように注意する。– EventEmitter を使っていれば、自動的にやっ
てくれるので問題ない。– それ以外でコールバック関数に処理を渡すと
きは、 require(“errors”).catchAndLog() を使う。
いまここ1. 拡張が再起動不要となるようにする2. Firefox, SDK のバージョンに非依存な API
を提供する3. addon-kit の API と似た使い方を提供する
1. construct/destroy モデル2. EventEmitter モデル3. エラー発生時のスタックトレース出力
4. その他
配布• https://builder.addons.mozilla.org/ にライブ
ラリとして登録する。– が、、、 Add-on Builder ではライブラリを検索できない!
–編集画面の「 Use Library」で名前を検索できるのが唯一の検索手段
• 今後の発展を祈ります。
参考にすべきライブラリ• addon-kit• api-utils
• 本家の API がつい最近までコロコロ変わっていたので、サードパーティで十分な品質のライブラリはまだできていない(と予想。なにぶん検索できないのでよくわかりませんが)。
• ベータ版が出て、 API が固まりつつある今が参入時?
ライブラリ例
ロケーションバーアイコン API
• ロケーションバーの中にアイコンを表示する API を作ってみる
API 設計• パッケージ名 : location-bar• モジュール名 : location-bar• クラス名 : Icon– コンストラクタ : label, tooltip, contentURL,
onClick– プロパティ : なし
使い方const locationBar = require("location-bar");const tabs = require("tabs");
locationBar.Icon({ label: "Mozilla website", contentURL: "http://www.mozilla.org/favicon.ico", onClick: function() { tabs.open("http://www.mozilla.org/"); }});locationBar.Icon({ label: "Google website", contentURL: "http://www.google.com/favicon.ico", onClick: function() { tabs.open("http://www.google.com/"); }});
動作• アイコン追加時:– 全てのウィンドウにアイコンを追加する
• アイコン削除時:– 全てのウィンドウからアイコンを削除する
• ウィンドウ追加時:– ウィンドウに全てのアイコンを追加する
• ウィンドウ削除時、拡張アンロード時:– ウィンドウから全てのアイコンを削除する
コンストラクタ:オプションチェック
const apiUtils = require("api-utils");const { EventEmitter } = require("events");const windowUtils = require("window-utils");
var icons = [];
exports.Icon = EventEmitter.compose({ constructor: function Icon(options) { options = apiUtils.validateOptions(options, { label: { is: ["string"] }, tooltip: { is: ["null", "undefined", "string"] }, contentURL: { is: ["string"] }, onClick: { is: ["null", "undefined", "function"] } });
コンストラクタ:初期化 this.id = "locationbaricon:" + require("xpcom").makeUuid().toString(); this._label = options.label; this.tooltip = ("tooltip" in options) ? options.tooltip : this._label this.contentURL = options.contentURL; if ("onClick" in options) { this.on("click", options.onClick); } icons.push(this); for (window in windowUtils.windowIterator()) { addItem(window, this); } },
destroy destroy : function(){ var idx = icons.indexOf(this); if (idx > -1) { icons.splice(idx, 1); } for (window in windowUtils.windowIterator()) { removeItem(window, this); } }});
アイコンの追加function addItem(window, icon) { var doc = window.document; var urlbar = doc.getElementById('urlbar'); if(urlbar) { var button = doc.createElement('toolbarbutton'); button.setAttribute("id", icon.id); button.setAttribute("tooltiptext", icon.tooltip); button.style.listStyleImage = 'url(' + icon.contentURL + ')'; button.addEventListener('click', function() icon._emit('click'), false); var goButton = doc.getElementById('urlbar-go-button'); urlbar.insertBefore(button, goButton); }}
アイコンの削除function removeItem(window, icon) { var doc = window.document; var urlbar = doc.getElementById('urlbar'); if(urlbar) { var button = doc.getElementById(icon.id); urlbar.removeChild(button); }}
ウィンドウの監視var windowManager = { init: function () { var windowTracker = new (windowUtils.WindowTracker)(this); require("unload").ensure(windowTracker); }, onTrack: function (window) { for(var i=0; i<icons.length; i++){ addItem(window, icons[i]); } }, onUntrack: function (window) { for(var i=0; i<icons.length; i++){ removeItem(window, icons[i]); } }}windowManager.init();
まとめ• 自作ライブラリを作ることによっ
て、 Jetpack の可能性は大きく広がる。• XUL の構造まで把握できる人がライブラ
リを作り、一般開発者の人がそのライブラリを活用してサクッと拡張を開発する、といった分担ができるようになることを期待します。
Thank you!