Greasemonkey extension のコードから実行時のスコープを調べる

Greasemonkey script execution environment – Chagama Lab でGreasemonkeyのスクリプト実行時のスコープがどうなっているかの話が書かれていました。自分もAutoPagerizeのコードを読んだときに、コードがfunctionで囲まれているのを見てブックマークレット同様そう書かないとだめなものなんだなー(ほかのスクリプトand/orウインドウとスコープが共有されている)、と思ってたので、そんなことないよ、と書かれているのは新鮮でした。

いい機会なのでどうなっているのかはっきりさせておくべくGreasemonekyのextensionのコードをのぞいてみたらシンプルなつくりになっていてすぐ分かりました。

Greasemonkeyのバージョンは0.7.20070607です。
はじめに chrome/chromeFiles/content/browser.jsGM_BrowserUI.chromeLoad でGreasemonkeyの実行時に必要なイベントをフックしています。

  this.appContent = document.getElementById("appcontent");
  .... snip ....
  // hook various events
  GM_listen(this.appContent, "DOMContentLoaded", GM_hitch(this, "contentLoad"));
  GM_listen(this.contextMenu, "popupshowing", GM_hitch(this, "contextMenuShowing"));
  GM_listen(this.toolsMenu, "popupshowing", GM_hitch(this, "toolsMenuShowing"));

AutoPagerizeは、ベージのロードが終わったあとで有効になるんだなー、と思ってたのですが Greasemonkeyが実行されるタイミングが DOMContentLoaded だからと分かったのは思わぬ収穫。

このイベントから2段階くらい関数が呼ばれて、最終的にuser.jsを呼び出しているのは components/greasemonkey.jsinjectScripts でした。

      sandbox = new Components.utils.Sandbox(safeWin);

      logger = new GM_ScriptLogger(script);

      console = firebugConsole ? firebugConsole : new GM_console(script);

      storage = new GM_ScriptStorage(script);
      xmlhttpRequester = new GM_xmlhttpRequester(unsafeContentWin,
                                                 appSvc.hiddenDOMWindow);

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

      // hack XPathResult since that is so commonly used
      sandbox.XPathResult = Ci.nsIDOMXPathResult;

      // add our own APIs
      sandbox.GM_addStyle = function(css) { GM_addStyle(safeDoc, css) };
      sandbox.GM_log = GM_hitch(logger, "log");
      sandbox.console = console;
      sandbox.GM_setValue = GM_hitch(storage, "setValue");
      sandbox.GM_getValue = GM_hitch(storage, "getValue");
      sandbox.GM_openInTab = GM_hitch(this, "openInTab", unsafeContentWin);
      sandbox.GM_xmlhttpRequest = GM_hitch(xmlhttpRequester,
                                           "contentStartRequest");
      sandbox.GM_registerMenuCommand = GM_hitch(this,
                                                "registerMenuCommand",
                                                unsafeContentWin);

      sandbox.__proto__ = safeWin;

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

最後のevalInSandboxでコードをfunctionで囲んでくれているので確かにuser./js側でfunctionで書くのは意味がないと言えます。このComponents.utils.SandboxComponents.utils.evalInSandbox – MDCによるとFirefox1.5から導入されたそうで
その名の通りjsの実行時にsandboxを作って実行してくれるもの。コードを見るとsandbox.windowにブラウザのウインドウのwindowを代入しているけれど Greasemonkey script execution environment 2 – Chagama Labに書かれている

Global オブジェクト != window オブジェクトであることも分かる

という結果になっているということは、sandboxが勝手にwindowというプロパティをグローバルスコープにしたりはしないということみたいですね。

Greasemonkeyのコードはextensionシロウトでも読めるようなつくりだったので疑問があったらのぞいてみればわかりそうです。


About this entry