Now browsing the SCRAPBLOG weblog archives.

about:feeds の置換で自前のXUL製フィードビューアを使う

昨年開催された Firefox Developers Conference にて、 Firefox 2 で新たに搭載された Feed Content Access API を利用した独自フィードビューアの実装例を示したのだが、 Firefox 標準のフィードプレビュー (chrome://browser/content/feeds/subscribe.xhtml) を自前のXUL製フィードビューアに置き換える方法がわからず、やむを得ず自前のXUL製フィードビューアの chrome URL を「はてなRSS」や「livedoor reader」と同じようにWebサービスとして登録し、フィードを読み込むとその chrome URL へ遷移させることでビューアを置き換えるようにしていた。
(詳しくは FeedContentAccessAPI.pdf の「第二段階」を参照)

しかし、 nanto さんによる XPCOM コンポーネントの置換: Days on the Moon のやり方をそのまんま使って「about:feeds」を置換することで、前述のような遠回りなやり方をせずとも、いとも簡単に Firefox 標準のフィードプレビューを自前のXUL製フィードビューアに置き換えることに成功した。「about:feeds」を置換するにあたり、自分で考えてコードを書く必要のある部分はせいぜい nsIAboutModule の newChannel メソッドくらいのもので、以下のように自前のXUL製フィードビューアの chrome URL でチャネルを作って返すだけ。

newChannel: function(aURI) {
    var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
    var channel = ios.newChannel("chrome://sample/content/feedview.xul", null, null);
    channel.originalURI = aURI;
    return channel;
},

この方法のおいしいところは、 Feed Stream Converter 側でパースした結果を nsIFeedResultService 経由で自前のXUL製フィードビューア側から取り出して使うことができるという点である。前述の苦し紛れの方法(Webサービスとして登録した chrome URL へ遷移)ではそれが不可能なので、遷移した先のXULで XMLHttpRequest とかを使って改めてフィードの取得とパースをしてやる必要があった。

ちなみに nsIFeedResultService とは、 Feed Stream Converter がフィードをパースした結果をグローバルにアクセス可能なオブジェクトとして保持することで、実際にフィードの画面表示を行う Feed Writer から参照できるようにするための XPCOM である。 Feed Stream Converter がパース結果を nsIFeedResultService へ登録する処理の流れは FeedConverter.js の handleResult を見ればわかる。 Firefox 標準のフィードプレビューを使う設定になっている場合、つまり設定値「browser.feeds.handler」が「ask」の場合、 nsIFeedResultService#addFeedResult でパース結果を登録してから「about:feeds」のチャネルを開くのに対して、Webサービスを使用する設定の場合、つまり設定値「browser.feeds.handler」が「web」の場合、 nsIWebContentConverterService#loadPreferredHandler を使って新たな URI をロードし直すだけの処理となっている。

なお、 nsIFeedResultService からのパース結果の取り出し方は、 FeedWriter.js の _getContainer を見ればわかる。

var channel = window.QueryInterface(Ci.nsIInterfaceRequestor)
              .getInterface(Ci.nsIWebNavigation)
              .QueryInterface(Ci.nsIDocShell_MOZILLA_1_8_BRANCH)
              .currentDocumentChannel;
var feedSvc = Cc["@mozilla.org/browser/feeds/result-service;1"]
              .getService(Ci.nsIFeedResultService);
var result = feedSvc.getFeedResult(channel.originalURI);  // nsIFeedResult

TOP

Firefox Developers Conference Summer 2007

昨日開催された Devcon 2007 のプレゼン資料をアップしました。その他にも、過去に使用したプレゼン資料も整理してまとめてアップしました。
» プレゼン資料のページ

なお、PowerPoint ファイルから PDF への変換は Drawloop を使用しました。登録さえすれば無料で Word / Excel / PowerPoint から PDF への変換が可能となる Webサービスで、 Ajax な UI もシンプルで使いやすい。以前は日本語フォント未対応だったが、今はまったく問題なし。大変ありがたいです。

TOP

userChrome.js でE4Xを使う

これまで、userChrome.js スクリプトでスタイルシートを追加することはできなくて、別途 userChrome.css を使用しなければならないと思っていた。ところが、nanto_viさんによるE4X in Firefox 発表資料: Days on the Moon の userChrome.js 用スクリプトを見ればわかるように、 E4Xを使うことによって userChrome.js のみでスタイルの定義追加が可能であることが判明。それだけでなく、E4Xを使えば userChrome.js 内に XML を直接書いてそのままブラウザへオーバーレイなんてことも簡単に可能。ボタンやメニューをたくさん追加したいときなんかに、いちいち document.createElement する必要がなくなる。すばらしい。

さっそく、 Colorful Tabs を修正して、 userChrome.css への追記を不要にさせた。

TOP

[userChrome.js] 軽量マウスジェスチャをWindows/Linuxに対応させる

通りすがりさんによるパッチをベースに、[userChrome.js] 軽量マウスジェスチャを Windows/Linux に対応させました。以下は今回修正した内容についてメモです。

以前はマウスジェスチャ中の状態遷移を数値型のフラグ _state を使って以下のように制御していた。

イベント _state フラグの変化 意味
mousedown 0 → 1 右クリック開始
mousemove (1のまま変化なし) 右クリックしたままマウス移動中
mouseup 1 → 2 ジェスチャ認識あり
1 → 3 ジェスチャ認識なし(マウスの移動量が微小)
contextmenu 2 → 0 コンテキストメニューの表示を抑止する
3 → 0 コンテキストメニューの表示を抑止しない

しかしながら、 Windows では上記のように mousedown→mousemove→mouseup→contextmenu という順序でイベントが発生するものの、 Linux では mousedown→contextmenu→mousemove→mouseup という順序で発生するため、制御がうまくいかなかった。そこで、数値型のフラグを廃止し、代わりに以下のような3つの真偽値フラグを使うようにした。

フラグ 意味
_isMouseDown 右クリックが押されているかどうか。
mousedown イベントで true になり、 mouseup イベントで false になる。
_suppressContext この後の contextmenu イベントを抑止するかどうか。
mouseup イベント発生時にジェスチャの認識があれば true にし、その後の contextmenu イベントを抑止する。
_shouldFireContext 後で contextmenu イベントを擬似的に発生させる必要があるかどうか。 Linux 専用。
Linux の場合は mousedown イベント直後に contextmenu イベントが発生するが、これを抑止した際にフラグを true にしておき、その後の mouseup イベント発生時にフラグが立っていれば擬似的に contextmenu を発生させる。

これによってスクリプトの一部は以下のように変更された。青色の部分が Linux 専用となる処理である。

    _isMouseDown: false,
    _suppressContext: false,
    _shouldFireContext: false,

    handleEvent: function(event)
    {
        switch (event.type) {
            case "mousedown": 
                // [1] ジェスチャ開始
                if (event.button == 2) {
                    this._isMouseDown = true;
                    this._startGesture(event);
                }
                break;
            case "mousemove": 
                // [2] ジェスチャ継続中
                if (this._isMouseDown) {
                    this._progressGesture(event);
                }
                break;
            case "mouseup": 
                // [3] ジェスチャ終了~アクション実行
                if (this._isMouseDown) {
                    this._isMouseDown = false;
                    this._suppressContext = !!this._directionChain;
                    this._stopGesture(event);
                    // [Linux] Win32を真似てmouseup後にcontextmenuを発生させる
                    if (this._shouldFireContext) {
                        this._shouldFireContext = false;
                        this._displayContextMenu(event);
                    }
                }
                break;
            case "contextmenu": 
                // [4-1] アクション実行後のコンテキストメニュー表示を抑止する
                // [4-2] 方向が認識されない微小な動きの場合は抑止しない
                // [Linux] mousedown直後のcontextmenuを抑止して...
                if (this._suppressContext || this._isMouseDown) {
                    this._suppressContext = false;
                    event.preventDefault();
                    event.stopPropagation();
                    // [Linux] ...代わりにmouseup後にcontextmenuを発生させる
                    if (this._isMouseDown) {
                        this._shouldFireContext = true;
                    }
                }
                break;
        }
    },

    _displayContextMenu: function(event)
    {
        var evt = event.originalTarget.ownerDocument.createEvent("MouseEvents");
        evt.initMouseEvent(
            "contextmenu", true, true, event.originalTarget.defaultView, 0,
            event.screenX, event.screenY, event.clientX, event.clientY,
            false, false, false, false, 2, null
        );
        event.originalTarget.dispatchEvent(evt);
    },

TOP

マウスジェスチャについてのアンケート結果

マウスジェスチャについてのアンケートにご協力ありがとうございました。

Q1. あなたはマウスジェスチャの拡張機能を使っていますか?

All-in-One Gestures を使っている 77
Optimoz Mouse Gestures を使っている 9
userChrome.js 用マウスジェスチャを使っている 99
使っていない 14

Q2. マウスジェスチャ機能は必要ですか?

はい 182
いいえ 8
わからない 7

Q3. ロッカージェスチャ機能(右クリックしながら左クリック)は必要ですか?

はい 56
いいえ 116
わからない 27

Q4. ホイールジェスチャ機能(右クリックしながらマウスホイール)は必要ですか?

はい 87
いいえ 93
わからない 20

Q5. ミドルクリックでのホイールジェスチャ機能(ミドルクリックしながらマウスホイール)は必要ですか?

はい 12
いいえ 158
わからない 30

Q6. タブバー上でのマウスホイールによってタブを切り替える機能は必要ですか?

はい 85
いいえ 98
わからない 17

Q7. マウスジェスチャ中の軌跡描画(マウストレイル)は必要ですか?

はい 36
いいえ 154
わからない 10

Q8. マウスジェスチャ中のステータスバー表示は必要ですか?

はい。「LR」のように、現在の方向を表示すべき。 31
はい。「LR (タブを開く)」のように、現在の方向と機能名称を表示すべき。 123
いいえ 41
わからない 5

Q9. ジェスチャの方向の表現方法はどれが最適ですか?

L、R、U、D 71
左、右、上、下 7
←、→、↑、↓ 111
わからない 11

Q10. 斜め方向のジェスチャの認識は必要ですか?

はい 18
いいえ 163
わからない 18

Q11. ジェスチャのタイムアウト(ジェスチャ中に数秒間じっとしているとジェスチャの認識を停止する機能)は必要ですか?

はい 114
いいえ 55
わからない 29

Q12. ジェスチャ中に通過したすべてのリンクをタブで開く機能は必要ですか?

はい 41
いいえ 120
わからない 38

Q13. 「タブを閉じる」機能に最適なジェスチャの割り当ては?

↓→ 68
46
↓↑ 10

Q14. 「新しいタブを開く」機能に最適なジェスチャの割り当ては?

25
19
←→ 7

Q15. 「閉じたタブを元に戻す」機能に最適なジェスチャの割り当ては?

↓← 20
↓↑ 18
↑↓ 11

Q16. マウスジェスチャに求めるものは?

動作の軽さ 168
機能の豊富さ 32
設定のしやすさ 100
ユーザスクリプトへの対応 52

TOP

ブックマークツリーの右クリックメニュー実装方式

Trunkへ正式に搭載されたことだし、そろそろPlacesの仕組みとかを勉強していこうかと思ったが、まず最初に気になるツリーの右クリックメニューの実装方式を見て一安心した。 chrome://browser/content/places/placesOverlay.xul を見ればわかるとおり、右クリックメニューが普通の popup 要素と menuitem 要素の集まりで実装されているのだ。これなら拡張機能が右クリックメニューへ独自のメニューを追加したければ、通常の XUL のオーバーレイで実現できる。

というのも、 Firefox 2 でのブックマークツリーの右クリックメニューは、右クリックでしたときの popupshowing イベント発生時に BookmarksCommand.createContextMenu という JavaScript 関数がすべての menuitem 要素を document.createElementNS で動的に生成してメニューを表示するという実装方式なのだ。これが大変厄介で、拡張機能から右クリックメニューへ新たなメニューを追加しようとしても、以下のような醜いやり方をせざるを得ない(もしかすると、別のもっと美しいやり方もあるかも?)。

方法1 BookmarksCommand.createContextMenu を、本来の関数を内包する独自の関数へ置き換える

これは Locate in Bookmark Folders という拡張機能で見かけたやり方だが、 menuitem 要素を動的に生成している関数を包含した独自の関数へ置き換えてしまい、ひとつの popupshowing イベントハンドラ内で Firefox 本来のメニューと拡張機能独自のメニューの両方を動的に生成する方法だ。

BookmarksCommand.originalCreateContextMenu = BookmarksCommand.createContextMenu;
BookmarksCommand.createContextMenu = function(aEvent, aSelection, aDS) {
  // まずはじめにオリジナルの関数を呼び出してから...
  this.originalCreateContextMenu(aEvent, aSelection, aDS);
  // document.createElement で menuitem 要素を生成し、 menupopup 要素 (aEvent.target) へ appendChild する。
  // 右クリックメニューの末尾ではなく特定位置へメニューを追加したければ insertBefore する。
};

方法2 右クリックメニューに対する popupshowing イベントを追加し、 JavaScript によって menuitem 要素をさらに追加する

まず、 chrome://browser/content/bookmarks/bookmarksTree.xml#bookmarks-tree-name を見るとわかるように、ブックマークツリーの右クリックメニューのポップアップ (menupopup 要素) は XBL で定義された bookmarks-tree 要素内の匿名要素であり、イベントハンドラ追加のために要素へアクセスするには、

var bmtree = document.getElementById("bookmarks-view");
var bmpopup = document.getAnonymousNodes(bmtree)[1];
// ちなみに[0]は[Object comment]

あるいは、

var bmtree = document.getElementById("bookmarks-view");
var bmpopup = document.getAnonymousElementByAttribute(bmtree, "onclick", "event.stopPropagation();");

なんていうややトリッキーなやり方をするしかないようだ。こうして得られたポップアップに対して、イベントハンドラを追加する。

function injectMenuItems(event) {
  // document.createElement で menuitem 要素を生成し、 menupopup 要素 (event.target) へ appendChild する。
  // 右クリックメニューの末尾ではなく特定位置へメニューを追加したければ insertBefore する。
}
bmpopup.addEventListener("popupshowing", injectMenuItems, false);

TOP

userChrome.js についてのアンケート

Q1. あなたは拡張機能「userChrome.js」を知っていますか?

Q2. あなたは拡張機能「userChrome.js」を使っていますか?

Q3. userChrome.jsを使っている方への質問 : あなたは何個のスクリプトを使用してますか?





Q4. userChrome.jsを使っている方への質問 : あなたは自分でスクリプトを作ったり、改造したりしますか?

Q5. 拡張機能「userChrome.js」に対する意見や要望など自由にお書きください。




TOP

Survey on “userChrome.js” extension

Q1. Do you know “userChrome.js” extension?

Q2. Are you using “userChrome.js” extension?

Q3. (A question for users of userChrome.js) How many scrips are you using now?





Q4. (A question for users of userChrome.js) Do you make your own scripts or remake existing scripts?

Q5. Feel free to write your opinions and requests about “userChrome.js” extension.




TOP

Bug 339546 – nsIFile.remove should allow option to send to Recycle Bin/Trash

現状、ファイルやディレクトリを削除するために nsIFile.remove をやるとディスクから完全削除されてしまう。ごみ箱へ送ることもできるようになるよう、期待したい。

Bug 339546 – nsIFile.remove should allow option to send to Recycle Bin/Trash

TOP

Gran Paradiso Alpha 4 で FUEL 使用不可

使い方がよくわからないので色々試してみたけど時間の無駄だった。
Bug 379139 – FUEL 0.1: Component/typelibs need to be added to installer manifests

TOP