Now browsing the archives for 12月, 2006.

拡張機能が無効あるいは削除される前に何らかの処理を実行したい

ある拡張機能が無効あるいは削除される前に何らかの処理を実行したい場合がある。

拡張機能のインストールやアンインストール、無効・有効の変更の実際の処理は、 Firefox を起動してからブラウザのウィンドウが開かれるまでの間に行われるが、どうやらこのタイミングにフックして、無効あるいは削除対象の拡張機能の中の処理を実行することはできないもよう。

そのかわり、「ツール」→「アドオン」から「無効」あるいは「削除」ボタンをクリックすることである拡張機能を削除しようとするタイミングであれば、その拡張機能の中の処理を実行することは可能だ。拡張機能のインストール・アンインストール、有効・無効などの操作をしようとしたとき、「em-action-requested」というトピック名のグローバルな通知が送られるので、これを nsIObserver インタフェースを実装したオブザーバによって監視することで実現される。

トピック名「em-action-requested」のグローバルな通知が送られる時、observe メソッドに渡される引数から、どの拡張機能に対してどういう処理を行うかを判別できる。「削除」ボタンをクリックして拡張機能を削除しようとしたのであれば、3番目の引数は「item-uninstalled」である。したがってこのタイミングで、不要な設定値をリセットするなどの何らかの処理を実行させることが可能。ただし、一度削除しようとした後でも「キャンセル」ボタンをクリックして削除をやめることができるため、削除しようとした時点で処理を実行してしまうのは時期尚早といえる。この時点ではフラグを上げ下げするだけにして、実際に処理を行うのは Firefox を終了させる直前(トピック名 “quit-application” の通知が送信されるタイミング)まで待つといった工夫が必要だ。

問題点としては、「ツール」→「アドオン」からではなく、プロファイルフォルダの extensions フォルダから直接拡張機能のフォルダを削除してアンインストールした場合には対応できない点が挙げられる。

以下は “em-action-requested” の通知を監視する nsIObserver インタフェースを実装した独自XPCOMコンポーネントのコードの一部である。なお、 nsIModule::registerSelf() が呼ばれたタイミングで nsICategoryManager::addCategoryEntry() によってコンポーネントを “app-startup” カテゴリに登録することで、 Firefox 起動時(ブラウザのウィンドウが開く前)にオブザーバの登録処理を実行させることが可能となる。

_toBeDisabled: false,
_toBeUninstalled: false,

/**
 * nsIObserver
 */
observe: function(aSubject, aTopic, aData)
{
    var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
    switch (aTopic)
    {
        // Firefox起動時
        case "app-startup":
            // オブザーバを登録
            os.addObserver(this, "em-action-requested", false);
            os.addObserver(this, "quit-application", true);
            break;
        case "em-action-requested":
            const extid = "myextension@mysite.com";    // 拡張機能のID
            if (aSubject instanceof Ci.nsIUpdateItem && aSubject.id == extid)
            {
                switch (aData)
                {
                    case "item-disabled":
                        // 拡張機能を無効にしようとした
                        this._toBeDisabled = true;
                        break;
                    case "item-uninstalled":
                        // 拡張機能を削除しようとした
                        this._toBeUninstalled = true;
                        break;
                    case "item-cancel-action":
                        // 無効あるいは削除する処理をキャンセルした
                        this._toBeDisabled = false;
                        this._toBeUninstalled = false;
                        break;
                }
            }
            break;
        // Firefox終了時
        case "quit-application":
            // オブザーバの登録を解除
            os.removeObserver(this, "em-action-requested");
            os.removeObserver(this, "quit-application");
            if (this._toBeDisabled)
            {
                // 拡張機能を無効にする前に何らかの処理を実行
            }
            if (this._toBeUninstalled)
            {
                // 拡張機能を削除する前に何らかの処理を実行
            }
            break;
    }
},

TOP

拡張機能が設定値「browser.sessionstore.restore_prompt_uri」を変更することの危険性

先日開催された Firefox Developers Conference 2006 の個別セッションのコーナーにて、設定値「browser.sessionstore.restore_prompt_uri」を変更することで、クラッシュからのセッション回復時に拡張機能が独自にこしらえたプロンプトを使用することが可能となる、という話をして、実際にカウントダウン式のセッション回復プロンプトを動かすデモを行った。
これに関してPiroさんから、「拡張機能を削除して変更した設定値がそのまま残ってしまった場合、何らかのフォールバックはあるのか?」という指摘を頂いた。

調べてみたところ、nsSessionStartup.js の250行目あたりからを見ると、セッション回復プロンプトを開く処理は nsIWindowWatcher::openWindow() メソッドによって実行されていることがわかる。このメソッドの引数として「browser.sessionstore.restore_prompt_uri」の値を渡してウィンドウを開くのであるが、その値によって以下のような挙動となる。

存在するchrome: URI ウィンドウが開き、XULなどがロードされる。例えば、拡張機能独自のプロンプトが開かれる。
存在しないchrome: URI ウィンドウは開かず、何も起こらない。処理は先に進まない。
空の文字列 標準のセッション回復プロンプトが開かれる。
http: URI ウィンドウが開いてWebページがロードされる。ウィンドウのxボタンをクリックして閉じれば、セッションが回復する。
明らかにURIではない文字列 プロンプトは開かれず、セッションが回復する。これは openWindow メソッドで発生した例外をキャッチするフォールバック処理があるため。

やはり問題となるのは、拡張機能を削除した場合に存在しないchrome: URIが設定値として残ってしまうケースだろう。この場合の危険性は以下のような話に例えることができる。

Firefox 初級ユーザであるA君は、「Countdown Recovery」という拡張機能をインストールした。
すると設定値「browser.sessionstore.restore_prompt_uri」が「chrome://countdownrecovery/content/prompt.xul」に変更され、クラッシュからのセッション回復時に当該URIのプロンプトが開かれるようになった。
しばらくしてA君はこの拡張機能が気に入らなくなったので、アンインストールした。このとき、変更された設定値がそのまま残ってしまったことに、A君は当然気付くはずもなかった。
後日A君の Firefox がクラッシュしたため再起動したところ、何度試しても Firefox のブラウザウィンドウは開かれなかった。不思議に思ったA君は Firefox を再インストールしたが、結果は同じ。
困ったA君は Firefox 中級ユーザであるB君に助けを求め、セーフモードで起動しろというアドバイスに従って試したが、やはり Firefox のウィンドウは出現しない。これにはB君もお手上げ状態で、最後の手段としてプロファイルを作り直すしかないと判断した。A君は新しいプロファイルを作ることでようやく Firefox を起動することに成功したが、泣く泣くカスタマイズを一からやり直すこととなった。

この例え話で起こった症状は、Firefox を終了させた状態で prefs.js に書き込まれている問題の設定値を削除するか、あるいは sessionstore.js および sessionstore.bak を削除した上でセッション回復させずに Firefox を再起動し、about:config から設定値をリセットすることで解決される。

一般に、拡張機能をアンインストールしても、prefs.js に書き込まれたその拡張機能に関係する設定値などはそのまま残ってしまう。また、拡張機能が独自に生成したファイルなどのデータも当然残ったままだ。しかし、Piroさんの話では NoScript という拡張機能ではアンインストール時に何らかの処理を行う仕掛けがあるそうなので、さっそく調べてみた。

TOP