Kanasan.JS Greasemonkey チュートリアル読書会

一時間ほど遅刻…。「参加者ブログ一覧」は止めたのかな。

http://diveintogreasemonkey.org/
  • 古すぎて鵜呑みにできない。
  • コードがあまり美しくない。
  • しかしユーモアは素晴らしい。
@(in|ex)clude
//components/greasemonkey.js 106
  domContentLoaded: function(wrappedContentWin, chromeWin) {
    var unsafeWin = wrappedContentWin.wrappedJSObject;
    var unsafeLoc = new XPCNativeWrapper(unsafeWin, "location").location;
    var href = new XPCNativeWrapper(unsafeLoc, "href").href;
    var scripts = this.initScripts(href);
//components/greasemonkey.js 210
  initScripts: function(url) {
    function testMatch(script) {
      return script.enabled && script.matchesURL(url);
    }

    return GM_getConfig().getMatchingScripts(testMatch);
//chrome/chromeFiles/content/config.js 380
  getMatchingScripts: function(testFunc) { return this._scripts.filter(testFunc); },

domContentLoaded 時に location.href と比較。

//chrome/chromeFiles/content/config.js 439
Script.prototype = {
  matchesURL: function(url) {
    function test(page) {
      return convert2RegExp(page).test(url);
    }

    return this._includes.some(test) && !this._excludes.some(test);

それぞれ convert2RegExp正規表現に変換してテスト。キャッシュしてない。

//chrome/chromeFiles/content/convert2RegExp.js 3
function convert2RegExp( pattern ) {

長いので載せないが,この convert2RegExp の中身がなかなか楽しい。

  1. 一文字ずつ愚直に置換
    • 「*」→「.*」
    • 制御文字をエスケープ
    • 空白の除去
  2. 「\.tld」→ mighty TLD RegExp
convert2RegExp('http:// *.google.* /search?*') ===
  /^http:\/\/.*\.google\..*\/search\?.*$/i
convert2RegExp('http://*.google.tld/search?*') ===
  /^http:\/\/.*\.google.(?:demon\.co\.uk|esc\.edu\.ar|(?:c[oi]\.)? .../i
アドオンより遅い?
//components/greasemonkey.js 276
      script.requires.forEach(function(req){
        var contents = req.textContent;
        var lineCount = contents.split("\n").length;
        requires.push(contents);
        offset += lineCount;
        offsets.push(offset);
      })
      script.offsets = offsets;

      var scriptSrc = "\n" + // error line-number calculations depend on these
                         requires.join("\n") +
                         "\n" +
                         contents +
                         "\n";
      if (!script.unwrap)
        scriptSrc = "(function(){"+ scriptSrc +"})()";
      if (!this.evalInSandbox(scriptSrc, url, sandbox, script) && script.unwrap)
        this.evalInSandbox("(function(){"+ scriptSrc +"})()",
                           url, sandbox, script); // wrap anyway on early return
//components/greasemonkey.js 320
  evalInSandbox: function(code, codebase, sandbox, script) {
    if (!(Components.utils && Components.utils.Sandbox)) {
      var e = new Error("Could not create sandbox.");
      GM_logError(e, 0, e.fileName, e.lineNumber);
    } else {
      try {
        // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=307984
        var lineFinder = new Error();
        Components.utils.evalInSandbox(code, sandbox);

該当ページ毎にいちいち evalInSandbox するので効率は良くない。

  • 特定のページ用なら許せる。
  • AutoPagerize みたいにほとんどのページで実行されるようなのはアドオンにするのが良さそうに思える。
GM_setValue(name, val)
//components/greasemonkey.js 255
      sandbox.GM_setValue = GM_hitch(storage, "setValue");
//chrome/chromeFiles/content/miscapis.js 1
function GM_ScriptStorage(script) {
  this.prefMan = new GM_PrefManager(["scriptvals.",
                                     script.namespace,
                                     "/",
                                     script.name,
                                     "."].join(""));
}

GM_ScriptStorage.prototype.setValue = function(name, val) {
  if (!GM_apiLeakCheck("GM_setValue")) {
    return;
  }

  this.prefMan.setValue(name, val);
};
//chrome/chromeFiles/content/prefmanager.js 11
function GM_PrefManager(startPoint) {
  if (!startPoint) {
    startPoint = "";
  }

  startPoint = "greasemonkey." + startPoint;

  var pref = Components.classes["@mozilla.org/preferences-service;1"]
                       .getService(Components.interfaces.nsIPrefService)
                       .getBranch(startPoint);
//chrome/chromeFiles/content/prefmanager.js 65
  /**
   * sets the named preference to the specified value. values must be strings,
   * booleans, or integers.
   */
  this.setValue = function(prefName, value) {

以上を経て greasemonkey.{script.namespace}/{script.name}.{name} に格納。

その他