chrome特権つきのLDRize Minibufferコマンドを作る

2007.11.13 追記

すいませんこれちょっとうまくいかないかもです。
non-privilegedの関数からだとprivilegedで定義された関数のなかでも制約があるみたいです。

2007.11.27 追記

とりあえずGreasemonkeyに特権関数を追加するprivileged monkeyになりました。

やっとGreasemonkeyスクリプトのsandboxオブジェクトにFirefox extensionから簡単にアクセスする方法をひねり出した。まだ何も実装していないけれど、これでMinibufferから

pinned-node | images | save-as localdisk

で、ピンをつけたparagraphの中にある画像をローカルに保存する、とか

pinned-node | images | save-as flickr

それをFlickrにアップロードするみたいなコマンドを実装することができるようになる。

Greasemonkeyスクリプトのsandbox

Greasemonkeyはgreasemonkey.jsの中にあるgreasemonkeyServiceのメソッドinjectScriptsでGreasemonkeyスクリプトのグローバルスコープになるsandboxを作って全てのスクリプトを実行している。

  injectScripts: function(scripts, url, unsafeContentWin, chromeWin) {
    var sandbox;

... snip ...
    for (var i = 0; script = scripts[i]; i++) {
      sandbox = new Components.utils.Sandbox(safeWin);

... snip ...
      sandbox.window = safeWin;
      sandbox.document = sandbox.window.document;
      sandbox.unsafeWindow = unsafeContentWin;

... snip ...
      this.evalInSandbox("(function(){\n" +
                         getContents(getScriptFileURI(script.filename)) +
                         "\n})()",
                         url,
                         sandbox,
                         script);
    }
  },

Minibufferはsandbox.window.Minibufferを介して他のスクリプトからコマンドを追加することができるようになっているけれど、そのsandboxはローカル変数になっていてinjectScriptsの実行が終わるとgreasemonkeyServiceのインスタンスから参照できなくなるのでなんとかしてsandboxをゲットする必要がある。

さいわいgreasemonkeyServiceというXPCOMコンポーネントのインスタンスは、拡張機能の名前空間からGreasemonkey本体が使うGM_BrowserUIという名前経由でGM_BrowserUI.gmSvcで参照できる。greasemonkeyServiceはXPCOMコンポーネントなため、IDLで記述されたメソッドしかアクセスできない。と思ってたんだけどjsで実装されているXPCOMコンポーネントならば実はJavaScript XPCOM コンポーネントの状況に書いてあるwrappedJSObjectを通して全部のメソッドにアクセスできるのでした。

まれに、JS コードの呼び出しから、実装している JS コンポーネントの JSObjectに実際にアクセスする必要がある可能性があります。このため、 xpcom コンポーネントの回りの xpconnect ラッパーは、現在 wrappedJSObject というプロパティをサポートしています。

と書かれております。好き勝手にいじり回せて便利ですね。

というわけでinjectScriptsのコードを無理矢理書き換えてevalすれば特権つきのMinibufferコマンドを作れそうです。

                    var wo = GM_BrowserUI.gmSvc.wrappedJSObject;
                    var code = wo.injectScripts.toSource();
                    code = code.replace(/}\)$/, '\
                        var tab = chromeWin.top.document.getElementById("content"); \
                        var browser = tab.getBrowserForDocument(sandbox.window.document); \
                        chromeWin.top.MinibufferPrivilegedCommands.onGMScriptsExecuted(sandbox); \
                    })' );
                    wo.injectScripts = eval(code, wo);

セキュリティ

sandboxは全てのGreasemonkeyスクリプトで共有されているので、特権をつけたいGreasemonkeyスクリプト以外からも特権つきのコマンドを参照できるようになってしまいます。

任意の特権コードを実行できるようになるわけではないので(意識的に可能にするように書かなければsandboxから任意コードを特権付きで実行することはできないはず)、信頼してインストールしていることが前提にあるGreasemonkeyスクリプトから特権がなければ実現できない特定の機能を呼び出せてもいいかなあとも思いますが、なんとかしてできないようにするに越したことはないと思います。

どうやって実現するかは今後の課題で。

top.MinibufferPrivilegedCommands = {
    success: false,
    initailzed: false,

    init: function () {
        var retry = 30;
        var timerid = null;
        var interval = 1000;

        if ( this.initailzed )
            return;

        var self = this;
        this.timerid = window.setInterval( function () {
            var stop = ( GM_BrowserUI || retry < 0 );

            if ( GM_BrowserUI ) {
                var browser = GM_BrowserUI.tabBrowser.
                                getBrowserForDocument(window.content.document);
                if ( ! browser ) {
                    stop = true;
                } else if ( ! self.success ) {
                    var wo = GM_BrowserUI.gmSvc.wrappedJSObject;
                    var code = wo.injectScripts.toSource();
                    code = code.replace(/}\)$/, '\
                        var tab = chromeWin.top.document.getElementById("content"); \
                        var browser = tab.getBrowserForDocument(sandbox.window.document); \
                        chromeWin.top.MinibufferPrivilegedCommands.onGMScriptsExecuted(sandbox); \
                    })' );
                    wo.injectScripts = eval(code, wo);
                    self.success = true;
                }
            }

            if ( stop ) {
                window.clearInterval(timerid);
                timerid = null;
            }

        } , this.interval );
        this.initailzed = true;
    },
    onGMScriptsExecuted: function (sandbox) {
        sandbox.Minibuffer.addCommand( ...... );
    }
};

About this entry