Now browsing the archives for 6月, 2006.
Chrome Providers (content, locale, skin)
Chrome Providers (という言い方は今まで知らなかったが)には、 content, locale, skin の3種があることは XULチュートリアルの最初の方にも書いてあるように基本的な事柄である。 content には XUL や JavaScript といったメインソースコードが格納され、 locale には DTD や properties の言語リソースが格納され、 skin にはスタイルシートや画像が格納される。
バカ正直に拡張子 xul か js のファイルは content, dtd か properties なら locale, css か png か gif とかなら skin、と決め付ければいいのかと思いきや、必ずしもそうではない。ポイントは、 locale は Firefox 自体の general.useragent.locale の設定値によって内容が可変であり、 skin は Firefox 自体のテーマによって内容が可変である、ということである。
- ヘルプとして参照されるHTML形式のファイルがある。ヘルプの内容をローカライズ可能にしたいなら、 locale 配下に置けばよい。
- バインディングするための CSS がテーマによって改変されては困る。そういった CSS は当然 content 配下に置くべきである。
- 拡張機能のために作成したアイコンのデザインが気に入っているので、サードパーティーのテーマ作者が勝手に独自のアイコンに改変することを禁じたい。それなら skin に置くべきアイコン画像をすべて content に置けばよい。
- 「保存」ボタンに割り当てるアクセスキーを言語ごとに変えたい。英語なら Save なので S にしたいけど、ドイツ語なら Außer なので A にしたい。それなら XUL は <button accesskey=”&savebutton.accesskey;”> として、savebutton.accesskey を locale 中の DTD ファイルで定義してやればよい。こういったDTDの使い方は頻繁に見かける。
- 言語ごとにダイアログのサイズがフィットするようにしたい。それなら XUL を <window width=”&window.width;”> として、window.width を locale 中の DTD ファイルで定義してやればよい。こういうやり方は実際に chrome://browser/content/safeMode.xul などで見かけることができる。
2つの拡張機能でchrome URLがバッティングしたら?
すべての拡張機能は em:ID で一意に識別されている。
たとえば、ある拡張機能A(その em:ID は extension@example.com)がインストールされている状態で、別の拡張機能B(その em:ID は同じく extension@example.com)をインストールしたとする。たとえAとBが別物でも、 em:ID が同じ限りは共存できない。インストールしてFirefox再起動後、Aは消え去り、代わりにBが有効になる。
インストールされた拡張機能のパッケージ内のファイルは、 chrome URL によって一意に識別される。では、異なる em:ID を有する2つの拡張機能が同じ chrome URL を使用した場合、どうなるか?
ある拡張機能 foo がある。その chrome.manifest には、
content test jar:chrome/test.jar!/content/test/
と定義されており、 chrome://test/content/a.xul や chrome://test/content/b.xul が存在する。
まったく別の拡張機能 bar がある。その chrome.manifest には、foo と同様に
content test jar:chrome/test.jar!/content/test/
と定義されており、 chrome://test/content/b.xul や chrome://test/content/c.xul が存在する。
fooをインストール後、barもインストールした。
fooとbarは em:ID が異なるので、両者が無事にインストールされ、拡張機能マネージャにも表示されている。
chrome://test/content/a.xul は foo のものが表示された。
chrome://test/content/b.xul は bar のものが表示された。
chrome://test/content/c.xul は bar のものが表示された。
予想通りの結果となった。
DOMノードのアイソレート
ScrapBook の DOMイレーサー という機能はページ中のクリックした箇所を削除する機能であるが、右クリックによってクリックした箇所以外を削除するという裏技的な機能も有する。この機能を DOMアイソレータ と呼んでいる。
これまでのバージョンでは DOMアイソレータ の仕様は、クリックした箇所のノードを body の直下(つまり body.firstChild の位置)へ insertBefore し、 body の firstChild 以外の全ての childNode を removeChild するという処理の流れになっている。わかりやすく言えば、クリックした箇所のノードを切り離してトップレベルに持ってきて、それ以外を削除するということである。
しかし、このような処理ではCSSとの整合性が悪くなり、ページの見た目が崩れやすい問題があった。そこで、ScrapBook 1.0.12 (Build ID 20060625) では、body ~ クリックした箇所のノードまでの道は残して、道から外れている不要なノードを削除するような処理へと改変した。以下はその部分の処理を抜粋した関数である。
function isolateNode(aNode) { if ( !aNode || !aNode.ownerDocument.body ) return; while ( aNode != aNode.ownerDocument.body ) { var parent = aNode.parentNode; var child = parent.lastChild; while ( child ) { dump((child == aNode ? "o" : "x") + " " + parent.nodeName + " " + child.nodeName + " "); var prevChild = child.previousSibling; if ( child != aNode ) parent.removeChild(child); // 前のノードへ移動 child = prevChild; } // 親ノードへ移動 aNode = parent; } }
非同期ループ処理 (7) - 同期非同期複合型
これまで述べてきた非同期ループ処理の問題点として、 setTimeout の間隔を0ミリ秒にしたところで、全体としては単純な for ループよりもかなり時間がかかってしまうことである。
そこで、 setTimeout しない同期的なループ処理も組み合わせて適度に高速化を図る。
下記の例では3回の処理を一単位としている。
var syncAsyncProcessor = { _array : [], _count : -1, start : function(aArray) { // 開始処理 dump("start "); // 初期化 this._count = -1; this._array = aArray; this._next(); }, _next : function() { var elt = this._array.shift(); if ( elt ) { if ( ++this._count % 3 == 0 ) // 数回に一度、非同期 setTimeout(function(){ syncAsyncProcessor._process(elt); }, 1000); else // それ以外は同期 syncAsyncProcessor._process(elt); } else { setTimeout(function(){ syncAsyncProcessor._finish(); }, 1000); } }, _process : function(aElt) { // 処理 dump("processing (" + this._count + ")... " + aElt + " "); // 次の処理へ this._next(); }, _finish : function() { // 終了処理 dump("finish "); }, }; syncAsyncProcessor.start(['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']);
非同期ループ処理 (6) - 列挙型
配列でなく列挙型でも同じようなことができる。
下記の例ではブックマークのデータソースから全リソースを取り出し、リソースに対してなんらかの処理を行う。
var asyncEnumProcessor = { _enumerator : null, start : function(aEnumerator) { // 開始処理 dump("start "); // 初期化 this._enumerator = aEnumerator; this._next(); }, _next : function() { if ( this._enumerator.hasMoreElements() ) { var elt = this._enumerator.getNext(); setTimeout(function(){ asyncEnumProcessor._process(elt); }, 0); } else { setTimeout(function(){ asyncEnumProcessor._finish(); }, 0); } }, _process : function(aElt) { aElt.QueryInterface(Components.interfaces.nsIRDFResource); // 処理 dump("processing... " + aElt.Value + " "); // 次の処理へ this._next(); }, _finish : function() { // 終了処理 dump("finish "); }, }; var RDF_SVC = Components.classes['@mozilla.org/rdf/rdf-service;1'].getService(Components.interfaces.nsIRDFService); var dataSource = RDF_SVC.GetDataSource("rdf:bookmarks"); var resourceEnum = dataSource.GetAllResources(); asyncEnumProcessor.start(resourceEnum);
非同期ループ処理 (5) - 進捗表示2
非同期ループ処理 (4) と似ているが、配列から shift して要素を取り出すのではなく、配列全体を保持しつつ位置 _index を加算しながら要素を取り出している。この方法でもプログレスバーなどで進捗状況を表示できる。
var asyncProgressiveProcessor2 = { _index : -1, _array : [], start : function(aArray) { // 開始処理 dump("start "); // 初期化 this._array = aArray; this._index = -1; this._next(); }, _next : function() { if ( ++this._index < this._array.length ) { setTimeout(function(){ asyncProgressiveProcessor2._process(); }, 500); } else { setTimeout(function(){ asyncProgressiveProcessor2._finish(); }, 500); } }, _process : function() { var elt = this._array[this._index]; // 処理 dump("processing (" + (this._index+1) + "/" + this._array.length + ")... " + elt + " "); // 次の処理へ this._next(); }, _finish : function() { // 終了処理 dump("finish "); }, }; asyncProgressiveProcessor2.start(['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']);
非同期ループ処理 (4) - 進捗表示1
非同期ループ処理 (3) に、配列の長さ _length と現在の処理数 _count を追加した。
これによってプログレスバーなどで進捗状況を表示できるようになる。
var asyncProgressiveProcessor = { _count : 0, _length : 0, _array : [], start : function(aArray) { // 開始処理 dump("start "); // 初期化 this._array = aArray; this._count = 0; this._length = this._array.length; this._next(); }, _next : function() { var elt = this._array.shift(); if ( elt ) { this._count++; setTimeout(function(){ asyncProgressiveProcessor._process(elt); }, 500); } else { setTimeout(function(){ asyncProgressiveProcessor._finish(); }, 500); } }, _process : function(aElt) { // 処理 dump("processing (" + this._count + "/" + this._length + ")... " + aElt + " "); // 次の処理へ this._next(); }, _finish : function() { // 終了処理 dump("finish "); }, }; asyncProgressiveProcessor.start(['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']);
非同期ループ処理 (3)
非同期ループ処理 (2) とあまり変わらないが、次の処理へ進む部分を _next として切り分けており、より見やすくなっている。
var asyncProcessor = { _array : [], start : function(aArray) { // 開始処理 dump("start "); // 初期化 this._array = aArray; this._next(); }, _next : function() { var elt = this._array.shift(); if ( elt ) { setTimeout(function(){ asyncProcessor._process(elt); }, 500); } else { setTimeout(function(){ asyncProcessor._finish(); }, 500); } }, _process : function(aElt) { // 処理 dump("processing... " + aElt + " "); // 次の処理へ this._next(); }, _finish : function() { // 終了処理 dump("finish "); }, }; asyncProcessor.start(['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']);
非同期ループ処理 (2)
開始処理~処理~終了処理という形で見やすく切り分けた方法。
var simpleAsyncProcessor = { _array : [], start : function(aArray) { // 開始処理 dump("start "); // 初期化 this._array = aArray; this._process(); }, _process : function() { var elt = this._array.shift(); if ( !elt ) { simpleAsyncProcessor._finish(); return; } // 処理 dump("processing... " + elt + " "); // 次の処理へ setTimeout(function(){ simpleAsyncProcessor._process(); }, 500); }, _finish : function() { // 終了処理 dump("finish "); }, }; simpleAsyncProcessor.start(['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']);
非同期ループ処理 (1)
要素数が多い配列に対して順番に処理を行うような場合に、 for や while で単純にループさせると、処理が終わるまでに一時的な無応答状態に陥ってしまう、あるいは時間がかかりすぎてスクリプトを続行するかどうかの警告が表示されてしまうことがある。
そこで、setTimeoutを連発してこれを防ぐ。
まずは、対象となる配列を引数に再帰的に関数を呼び出す方法。
なお、下記の例では確認しやすいように500ミリ秒後に次の処理へ進むが、実際は0ミリ秒で構わない。
function processRecursive(aArray) { var elt = aArray.shift(); if ( elt ) { // 処理 dump("processing... " + elt + " "); setTimeout(function(){ processRecursive(aArray); }, 500); } else { // 終了処理 dump("finish "); } } processRecursive(['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']);